Compare commits

...

3 Commits

Author SHA1 Message Date
ZY 6613402217 更新报告
4 weeks ago
ZY 63b5057868 Merge branch 'zy' of https://bdgit.educoder.net/pnju9rpvk/store_node into zy
4 weeks ago
ZY 361d4518a9 更新报告
4 weeks ago

@ -0,0 +1 @@
{"containers":[],"config":{}}

14
.gitignore vendored

@ -0,0 +1,14 @@
# Windows
[Dd]esktop.ini
Thumbs.db
$RECYCLE.BIN/
# macOS
.DS_Store
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
# Node.js
node_modules/

@ -0,0 +1,112 @@
// 云函数completeOrder
const cloud = require('wx-server-sdk')
// 初始化cloud
cloud.init({
env: cloud.DYNAMIC_CURRENT_ENV // 使用动态环境ID支持多开发者共享
})
const db = cloud.database()
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext()
const openid = wxContext.OPENID
// 获取请求参数
const { orderId, comment, rating } = event
if (!orderId) {
return {
success: false,
message: '参数错误未提供订单ID'
}
}
// 使用事务处理
const transaction = await db.startTransaction()
try {
// 获取订单信息
const orderResult = await transaction.collection('orders').doc(orderId).get()
const order = orderResult.data
if (!order) {
await transaction.rollback()
return {
success: false,
message: '订单不存在'
}
}
// 验证操作权限(只有买家/接单方可以完成订单)
if (order.buyerOpenid !== openid) {
await transaction.rollback()
return {
success: false,
message: '无权操作此订单'
}
}
// 判断订单状态是否为进行中
if (order.status !== 'processing') {
await transaction.rollback()
return {
success: false,
message: '只有进行中的订单可以被完成'
}
}
// 更新订单状态
await transaction.collection('orders').doc(orderId).update({
data: {
status: 'completed',
comment: comment || '',
rating: rating || 5,
completeTime: db.serverDate(),
updateTime: db.serverDate()
}
})
// 更新任务或商品状态
let targetCollection = ''
if (order.type === 'secondhand') {
targetCollection = 'secondhand_goods'
}
if (targetCollection) {
await transaction.collection(targetCollection).doc(order.itemId).update({
data: {
status: 'completed',
updateTime: db.serverDate()
}
})
}
// 更新用户信息(增加完成数量)
if (order.type === 'secondhand') {
await transaction.collection('users').where({
openid: openid
}).update({
data: {
secondhandCount: db.command.inc(1)
}
})
}
// 提交事务
await transaction.commit()
return {
success: true,
message: '订单完成成功'
}
} catch (error) {
// 回滚事务
await transaction.rollback()
return {
success: false,
message: '订单完成失败',
error: error.message
}
}
}

@ -0,0 +1,15 @@
{
"name": "completeorder",
"version": "1.0.0",
"description": "完成订单",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"wx-server-sdk": "~2.6.3"
}
}

@ -0,0 +1,108 @@
// 云函数createOrder
const cloud = require('wx-server-sdk')
// 初始化cloud
cloud.init({
env: cloud.DYNAMIC_CURRENT_ENV // 使用动态环境ID支持多开发者共享
})
const db = cloud.database()
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext()
const openid = wxContext.OPENID
// 获取请求参数
const {
type, // 'secondhand'
itemId,
note
} = event
if (!type || !itemId) {
return {
success: false,
message: '参数错误'
}
}
// 事务处理
const transaction = await db.startTransaction()
try {
let item, itemCollection, status
// 只支持二手商品
if (type === 'secondhand') {
itemCollection = 'secondhand_goods'
status = 'sold' // 二手商品状态变为已售出
} else {
return {
success: false,
message: '类型错误'
}
}
// 获取商品信息
const itemResult = await transaction.collection(itemCollection).doc(itemId).get()
item = itemResult.data
if (!item) {
await transaction.rollback()
return {
success: false,
message: '商品不存在'
}
}
// 检查状态是否可购买
if (item.status !== 'available') {
await transaction.rollback()
return {
success: false,
message: '该商品已售出'
}
}
// 更新商品状态
await transaction.collection(itemCollection).doc(itemId).update({
data: {
status,
updateTime: db.serverDate()
}
})
// 创建订单
const orderResult = await transaction.collection('orders').add({
data: {
type,
itemId,
title: item.title,
price: item.price,
publisherOpenid: item.openid, // 发布者openid
buyerOpenid: openid, // 购买者openid
note,
status: 'waiting', // 订单状态:待完成
createTime: db.serverDate(),
updateTime: db.serverDate()
}
})
// 提交事务
await transaction.commit()
return {
success: true,
orderId: orderResult._id,
message: '下单成功'
}
} catch (error) {
// 回滚事务
await transaction.rollback()
return {
success: false,
message: '下单失败',
error: error.message
}
}
}

@ -0,0 +1,14 @@
{
"name": "createorder",
"version": "1.0.0",
"description": "创建订单",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"wx-server-sdk": "~2.6.3"
}
}

@ -0,0 +1,55 @@
// 云函数getGoodsDetail
const cloud = require('wx-server-sdk')
// 初始化cloud
cloud.init({
env: cloud.DYNAMIC_CURRENT_ENV
})
const db = cloud.database()
exports.main = async (event, context) => {
const { goodsId } = event
if (!goodsId) {
return {
success: false,
message: '参数错误'
}
}
try {
// 获取商品详情
const goodsResult = await db.collection('secondhand_goods').doc(goodsId).get()
if (!goodsResult.data) {
return {
success: false,
message: '商品不存在'
}
}
// 获取卖家信息
const sellerResult = await db.collection('users').where({
openid: goodsResult.data.openid
}).field({
nickName: true,
avatarUrl: true
}).get()
const seller = sellerResult.data.length > 0 ? sellerResult.data[0] : {}
return {
success: true,
data: {
...goodsResult.data,
seller
}
}
} catch (error) {
return {
success: false,
message: '获取商品详情失败',
error: error.message
}
}
}

@ -0,0 +1,14 @@
{
"name": "getgoodsdetail",
"version": "1.0.0",
"description": "获取二手商品详情",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"wx-server-sdk": "~2.6.3"
}
}

@ -0,0 +1,111 @@
// 云函数getMyOrders
const cloud = require('wx-server-sdk')
// 初始化cloud
cloud.init({
env: cloud.DYNAMIC_CURRENT_ENV // 使用动态环境ID支持多开发者共享
})
const db = cloud.database()
const _ = db.command
const $ = db.command.aggregate
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext()
const openid = wxContext.OPENID
// 获取请求参数
const {
type = 'all', // 订单类型all, secondhand
status = 'all', // 订单状态all, waiting, processing, completed, cancelled
role = 'buyer', // 角色buyer(买家), publisher(卖家)
pageNum = 1,
pageSize = 10
} = event
// 构建查询条件
let query = {}
// 按角色筛选
if (role === 'buyer') {
query.buyerOpenid = openid
} else if (role === 'publisher') {
query.publisherOpenid = openid
}
// 按类型筛选
if (type !== 'all') {
query.type = type
}
// 按状态筛选
if (status !== 'all') {
query.status = status
}
try {
// 计算总数
const countResult = await db.collection('orders')
.where(query)
.count()
const total = countResult.total
// 查询订单数据
const orderResult = await db.collection('orders')
.where(query)
.orderBy('createTime', 'desc')
.skip((pageNum - 1) * pageSize)
.limit(pageSize)
.get()
// 获取关联的商品信息
const orders = orderResult.data
const ordersWithDetails = []
for (const order of orders) {
let detailCollection = ''
if (order.type === 'secondhand') {
detailCollection = 'secondhand_goods'
}
try {
// 获取关联信息
if (detailCollection) {
const detailResult = await db.collection(detailCollection)
.doc(order.itemId)
.field({
title: true,
description: true,
images: true,
price: true
})
.get()
ordersWithDetails.push({
...order,
itemDetail: detailResult.data || {}
})
} else {
ordersWithDetails.push(order)
}
} catch (error) {
// 如果获取详情失败,仍然返回订单基本信息
ordersWithDetails.push(order)
}
}
return {
success: true,
data: ordersWithDetails,
total,
pageNum,
pageSize,
hasMore: total > pageNum * pageSize
}
} catch (error) {
return {
success: false,
message: '获取订单失败',
error: error.message
}
}
}

@ -0,0 +1,14 @@
{
"name": "getmyorders",
"version": "1.0.0",
"description": "获取我的订单",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"wx-server-sdk": "~2.6.3"
}
}

@ -0,0 +1,76 @@
// 云函数getSecondhandGoods
const cloud = require('wx-server-sdk')
// 初始化cloud
cloud.init({
env: cloud.DYNAMIC_CURRENT_ENV
})
const db = cloud.database()
const _ = db.command
exports.main = async (event, context) => {
// 从请求中获取参数
const {
category = 'all',
keyword = '',
pageNum = 1,
pageSize = 10,
sortBy = 'createTime'
} = event
// 构建查询条件
let query = {}
// 根据分类筛选
if (category !== 'all') {
query.category = category
}
// 根据关键词搜索标题
if (keyword) {
query.title = db.RegExp({
regexp: keyword,
options: 'i',
})
}
// 只查询未售出的商品
query.status = 'available'
// 计算总数
const countResult = await db.collection('secondhand_goods')
.where(query)
.count()
const total = countResult.total
// 构建排序条件
let orderBy = {}
switch(sortBy) {
case 'price':
orderBy = { price: 1 }
break
case 'price-desc':
orderBy = { price: -1 }
break
case 'createTime':
default:
orderBy = { createTime: -1 }
}
// 查询数据
const goods = await db.collection('secondhand_goods')
.where(query)
.orderBy(Object.keys(orderBy)[0], Object.values(orderBy)[0] > 0 ? 'asc' : 'desc')
.skip((pageNum - 1) * pageSize)
.limit(pageSize)
.get()
return {
success: true,
data: goods.data,
total,
pageNum,
pageSize,
hasMore: total > pageNum * pageSize
}
}

@ -0,0 +1,14 @@
{
"name": "getsecondhandgoods",
"version": "1.0.0",
"description": "获取二手商品列表",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"wx-server-sdk": "~2.6.3"
}
}

@ -0,0 +1,66 @@
// 云函数getUserInfo
const cloud = require('wx-server-sdk')
// 初始化cloud
cloud.init({
env: 'cloud1-5gqeisa06659849a'
})
const db = cloud.database()
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext()
const openid = wxContext.OPENID
try {
// 获取用户信息
const userInfo = await db.collection('users').where({
openid: openid
}).get()
if (userInfo.data.length === 0) {
return {
success: false,
message: '用户不存在'
}
}
// 获取订单计数
const waitingOrders = await db.collection('orders').where({
buyerOpenid: openid,
status: 'waiting'
}).count()
const processingOrders = await db.collection('orders').where({
buyerOpenid: openid,
status: 'processing'
}).count()
const completedOrders = await db.collection('orders').where({
buyerOpenid: openid,
status: 'completed'
}).count()
const refundOrders = await db.collection('orders').where({
buyerOpenid: openid,
status: 'refund'
}).count()
return {
success: true,
userInfo: userInfo.data[0],
orderCount: {
waiting: waitingOrders.total,
processing: processingOrders.total,
completed: completedOrders.total,
refund: refundOrders.total
}
}
} catch (error) {
console.error('获取用户信息失败', error)
return {
success: false,
message: '获取用户信息失败',
error: error.message
}
}
}

@ -0,0 +1,14 @@
{
"name": "getuserinfo",
"version": "1.0.0",
"description": "获取用户信息",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"wx-server-sdk": "~2.6.3"
}
}

@ -0,0 +1,14 @@
{
"name": "initdatabase",
"version": "1.0.0",
"description": "初始化数据库及测试数据",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"wx-server-sdk": "~2.6.3"
}
}

@ -0,0 +1,53 @@
// 云函数login
const cloud = require('wx-server-sdk')
// 初始化cloud
cloud.init({
env: cloud.DYNAMIC_CURRENT_ENV // 使用动态环境ID支持多开发者共享
})
const db = cloud.database()
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext()
const openid = wxContext.OPENID
try {
// 查询用户是否已存在
const userResult = await db.collection('users').where({
openid: openid
}).get()
// 用户不存在,创建新用户
if (userResult.data.length === 0) {
const userInfo = event.userInfo || {}
// 创建新用户记录
await db.collection('users').add({
data: {
openid: openid,
nickName: userInfo.nickName || '宿小君用户',
avatarUrl: userInfo.avatarUrl || '',
gender: userInfo.gender || 0,
createTime: db.serverDate(),
updateTime: db.serverDate(),
balance: 0, // 初始余额
credit: 100, // 初始信用分
secondhandCount: 0 // 二手交易完成数
}
})
}
return {
success: true,
openid: openid,
unionid: wxContext.UNIONID || '',
appid: wxContext.APPID,
env: wxContext.ENV || ''
}
} catch (error) {
return {
success: false,
error: error.message
}
}
}

@ -0,0 +1,14 @@
{
"name": "login",
"version": "1.0.0",
"description": "用户登录",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"wx-server-sdk": "~2.6.3"
}
}

@ -0,0 +1,62 @@
// 云函数publishSecondhandGoods
const cloud = require('wx-server-sdk')
// 初始化cloud
cloud.init({
env: cloud.DYNAMIC_CURRENT_ENV
})
const db = cloud.database()
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext()
const openid = wxContext.OPENID
// 获取请求参数
const {
title,
description,
price,
condition,
category,
images = []
} = event
// 验证必填字段
if (!title || !price || !category) {
return {
success: false,
message: '请填写所有必填字段'
}
}
try {
// 添加新商品
const result = await db.collection('secondhand_goods').add({
data: {
openid,
title,
description,
price: parseFloat(price),
condition,
category,
images,
status: 'available', // 状态:可购买
createTime: db.serverDate(),
updateTime: db.serverDate(),
publishTime: new Date().toISOString()
}
})
return {
success: true,
goodsId: result._id,
message: '商品发布成功'
}
} catch (error) {
return {
success: false,
message: '商品发布失败',
error: error.message
}
}
}

@ -0,0 +1,14 @@
{
"name": "publishsecondhandgoods",
"version": "1.0.0",
"description": "发布二手商品",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"wx-server-sdk": "~2.6.3"
}
}

@ -0,0 +1,97 @@
// 云函数 updateUserInfo
const cloud = require('wx-server-sdk');
// 初始化cloud
cloud.init({
env: cloud.DYNAMIC_CURRENT_ENV // 使用动态环境ID支持多开发者共享
});
const db = cloud.database();
exports.main = async (event, context) => {
console.log('updateUserInfo 云函数开始执行,参数 event:', event);
const wxContext = cloud.getWXContext();
const openid = wxContext.OPENID; // 获取用户的openid
console.log('获取到的 openid:', openid);
if (!openid) {
console.warn('未获取到用户身份信息');
return {
success: false,
message: '未获取到用户身份信息'
};
}
const { userInfo } = event;
console.log('接收到的 userInfo:', userInfo);
if (!userInfo) {
console.warn('未提供用户信息');
return {
success: false,
message: '未提供用户信息'
};
}
try {
// 检查用户是否已存在
const userCheck = await db.collection('users').where({
openid: openid
}).get();
console.log('数据库查询结果:', userCheck);
if (userCheck.data.length === 0) {
// 用户不存在,创建新用户
const addRes = await db.collection('users').add({
data: {
openid: openid,
nickName: userInfo.nickName || '宿小君用户',
avatarUrl: userInfo.avatarUrl || '',
gender: userInfo.gender || 0,
createTime: db.serverDate(),
updateTime: db.serverDate(),
balance: 0, // 初始余额
credit: 100, // 初始信用分
secondhandCount: 0 // 二手交易完成数
}
});
console.log('新用户创建成功记录ID:', addRes._id);
return {
success: true,
message: '用户创建成功',
isNewUser: true
};
} else {
// 用户已存在,更新信息
const updateRes = await db.collection('users').where({
openid: openid
}).update({
data: {
nickName: userInfo.nickName,
avatarUrl: userInfo.avatarUrl,
gender: userInfo.gender,
updateTime: db.serverDate()
}
});
console.log('用户信息更新成功,更新结果:', updateRes);
return {
success: true,
message: '用户信息更新成功',
isNewUser: false
};
}
} catch (err) {
console.error('更新用户信息失败', err);
return {
success: false,
message: '更新用户信息失败',
error: err.message
};
}
};

@ -0,0 +1,14 @@
{
"name": "updateuserinfo",
"version": "1.0.0",
"description": "更新用户信息",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"wx-server-sdk": "~2.6.3"
}
}

@ -0,0 +1,55 @@
// 云函数 userLogin/index.js
const cloud = require('wx-server-sdk');
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV });
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext();
// 拿到用户 openid 和 unionid
const openid = wxContext.OPENID;
const userInfo = event.userInfo;
// 查询数据库中是否存在该用户
const db = cloud.database();
const users = db.collection('users');
const existingUser = await users.where({ _openid: openid }).get();
if (existingUser.data.length > 0) {
// 用户已存在,更新信息
await users.doc(existingUser.data[0]._id).update({
data: {
nickName: userInfo.nickName,
avatarUrl: userInfo.avatarUrl,
updateTime: new Date()
}
});
return {
success: true,
isNewUser: false,
userInfo: userInfo,
openid: openid
};
} else {
// 用户不存在,新建用户
await users.add({
data: {
_openid: openid,
nickName: userInfo.nickName,
avatarUrl: userInfo.avatarUrl,
createTime: new Date(),
balance: 0,
credit: 100,
coupons: 0
}
});
return {
success: true,
isNewUser: true,
userInfo: userInfo,
openid: openid
};
}
};

@ -12,7 +12,7 @@
"backgroundTextStyle": "light",
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTitleText": "宿小君",
"navigationBarTitleText": "二手交易商城",
"backgroundColor": "#f7f7f7"
},
"tabBar": {

@ -0,0 +1,10 @@
/**app.wxss**/
.container {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
padding: 200rpx 0;
box-sizing: border-box;
}

@ -0,0 +1,153 @@
// app.ts
App<IAppOption>({
globalData: {
userInfo: null,
openid: '',
isLoggedIn: false,
hasUserInfo: false,
canIUseGetUserProfile: false
},
onLaunch() {
// 展示本地存储能力
const logs = wx.getStorageSync('logs') || [];
logs.unshift(Date.now());
wx.setStorageSync('logs', logs);
// 初始化云开发环境
if (wx.cloud) {
// 尝试使用配置文件中的环境ID如果没有则使用动态环境ID
let envId = 'cloud1-4gczmwok7cacf142'; // 默认环境ID
try {
// 如果配置文件存在则使用配置文件中的环境ID
const config = require('./config/env.js');
if (config && config.cloudEnvId) {
envId = config.cloudEnvId;
}
} catch (e) {
console.log('未找到环境配置文件使用默认环境ID');
}
wx.cloud.init({
env: envId,
traceUser: true // 是否将用户访问记录到用户管理中,用于多用户访问的场景
});
console.log('云开发初始化成功使用环境ID:', envId);
// 静默获取openid
wx.cloud.callFunction({
name: 'login',
success: res => {
console.log('云函数登录成功', res);
if (res.result && res.result.success) {
this.globalData.openid = res.result.openid;
wx.setStorageSync('openid', res.result.openid);
}
},
fail: err => {
console.error('云函数登录失败', err);
}
});
// 检查本地是否有用户信息
const userInfo = wx.getStorageSync('userInfo');
if (userInfo) {
this.globalData.userInfo = userInfo;
this.globalData.hasUserInfo = true;
this.globalData.isLoggedIn = true;
} else {
this.globalData.hasUserInfo = false;
this.globalData.isLoggedIn = false;
}
// 检查用户信息获取能力
if (wx.getUserProfile) {
this.globalData.canIUseGetUserProfile = true;
}
} else {
console.error('请使用 2.2.3 或以上的基础库以使用云能力');
}
console.log('小程序启动成功');
},
// 静默登录获取openid但不弹窗要求用户授权
silentLogin() {
wx.cloud.callFunction({
name: 'login',
data: {},
success: res => {
console.log('云函数登录成功', res);
this.globalData.openid = res.result.openid;
this.globalData.isLoggedIn = true;
wx.setStorageSync('openid', res.result.openid);
// 尝试从数据库获取用户信息
wx.cloud.callFunction({
name: 'getUserInfo',
data: { openid: res.result.openid },
success: userRes => {
console.log('获取用户信息成功', userRes);
if (userRes.result && userRes.result.userInfo) {
this.globalData.userInfo = userRes.result.userInfo;
this.globalData.hasUserInfo = true;
wx.setStorageSync('userInfo', userRes.result.userInfo);
}
},
fail: err => {
console.error('获取用户信息失败', err);
}
});
},
fail: err => {
console.error('云函数登录失败', err);
}
});
},
// 获取用户信息(需要用户主动授权)
getUserProfile(callback: Function) {
wx.getUserProfile({
desc: '用于完善用户资料',
success: (res: any) => {
// 更新全局数据
this.globalData.userInfo = res.userInfo;
this.globalData.hasUserInfo = true;
wx.setStorageSync('userInfo', res.userInfo);
// 保存到云数据库
wx.cloud.callFunction({
name: 'updateUserInfo',
data: {
userInfo: res.userInfo
},
success: (result: any) => {
console.log('用户信息上传成功', result);
},
fail: (err: any) => {
console.error('用户信息上传失败', err);
}
});
// 执行回调
if (callback && typeof callback === 'function') {
callback(res.userInfo);
}
},
fail(res: any) {
wx.showToast({
title: '授权失败,部分功能将受限',
icon: 'none'
});
}
});
},
onShow() {
console.log('小程序显示');
},
onHide() {
console.log('小程序隐藏');
},
onError(error) {
console.error('小程序错误:', error);
}
})

@ -0,0 +1,5 @@
{
"component": true,
"styleIsolation": "apply-shared",
"usingComponents": {}
}

@ -0,0 +1,96 @@
.weui-navigation-bar {
--weui-FG-0:rgba(0,0,0,.9);
--height: 44px;
--left: 16px;
}
.weui-navigation-bar .android {
--height: 48px;
}
.weui-navigation-bar {
overflow: hidden;
color: var(--weui-FG-0);
flex: none;
}
.weui-navigation-bar__inner {
position: relative;
top: 0;
left: 0;
height: calc(var(--height) + env(safe-area-inset-top));
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
padding-top: env(safe-area-inset-top);
width: 100%;
box-sizing: border-box;
}
.weui-navigation-bar__left {
position: relative;
padding-left: var(--left);
display: flex;
flex-direction: row;
align-items: flex-start;
height: 100%;
box-sizing: border-box;
}
.weui-navigation-bar__btn_goback_wrapper {
padding: 11px 18px 11px 16px;
margin: -11px -18px -11px -16px;
}
.weui-navigation-bar__btn_goback_wrapper.weui-active {
opacity: 0.5;
}
.weui-navigation-bar__btn_goback {
font-size: 12px;
width: 12px;
height: 24px;
-webkit-mask: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='24' viewBox='0 0 12 24'%3E %3Cpath fill-opacity='.9' fill-rule='evenodd' d='M10 19.438L8.955 20.5l-7.666-7.79a1.02 1.02 0 0 1 0-1.42L8.955 3.5 10 4.563 2.682 12 10 19.438z'/%3E%3C/svg%3E") no-repeat 50% 50%;
mask: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='24' viewBox='0 0 12 24'%3E %3Cpath fill-opacity='.9' fill-rule='evenodd' d='M10 19.438L8.955 20.5l-7.666-7.79a1.02 1.02 0 0 1 0-1.42L8.955 3.5 10 4.563 2.682 12 10 19.438z'/%3E%3C/svg%3E") no-repeat 50% 50%;
-webkit-mask-size: cover;
mask-size: cover;
background-color: var(--weui-FG-0);
}
.weui-navigation-bar__center {
font-size: 17px;
text-align: center;
position: relative;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
font-weight: bold;
flex: 1;
height: 100%;
}
.weui-navigation-bar__loading {
margin-right: 4px;
align-items: center;
}
.weui-loading {
font-size: 16px;
width: 16px;
height: 16px;
display: block;
background: transparent url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='80px' height='80px' viewBox='0 0 80 80' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3Eloading%3C/title%3E%3Cdefs%3E%3ClinearGradient x1='94.0869141%25' y1='0%25' x2='94.0869141%25' y2='90.559082%25' id='linearGradient-1'%3E%3Cstop stop-color='%23606060' stop-opacity='0' offset='0%25'%3E%3C/stop%3E%3Cstop stop-color='%23606060' stop-opacity='0.3' offset='100%25'%3E%3C/stop%3E%3C/linearGradient%3E%3ClinearGradient x1='100%25' y1='8.67370605%25' x2='100%25' y2='90.6286621%25' id='linearGradient-2'%3E%3Cstop stop-color='%23606060' offset='0%25'%3E%3C/stop%3E%3Cstop stop-color='%23606060' stop-opacity='0.3' offset='100%25'%3E%3C/stop%3E%3C/linearGradient%3E%3C/defs%3E%3Cg stroke='none' stroke-width='1' fill='none' fill-rule='evenodd' opacity='0.9'%3E%3Cg%3E%3Cpath d='M40,0 C62.09139,0 80,17.90861 80,40 C80,62.09139 62.09139,80 40,80 L40,73 C58.2253967,73 73,58.2253967 73,40 C73,21.7746033 58.2253967,7 40,7 L40,0 Z' fill='url(%23linearGradient-1)'%3E%3C/path%3E%3Cpath d='M40,0 L40,7 C21.7746033,7 7,21.7746033 7,40 C7,58.2253967 21.7746033,73 40,73 L40,80 C17.90861,80 0,62.09139 0,40 C0,17.90861 17.90861,0 40,0 Z' fill='url(%23linearGradient-2)'%3E%3C/path%3E%3Ccircle id='Oval' fill='%23606060' cx='40.5' cy='3.5' r='3.5'%3E%3C/circle%3E%3C/g%3E%3C/g%3E%3C/svg%3E%0A") no-repeat;
background-size: 100%;
margin-left: 0;
animation: loading linear infinite 1s;
}
@keyframes loading {
from {
transform: rotate(0);
}
to {
transform: rotate(360deg);
}
}

@ -0,0 +1,105 @@
Component({
options: {
multipleSlots: true // 在组件定义时的选项中启用多slot支持
},
/**
*
*/
properties: {
extClass: {
type: String,
value: ''
},
title: {
type: String,
value: ''
},
background: {
type: String,
value: ''
},
color: {
type: String,
value: ''
},
back: {
type: Boolean,
value: true
},
loading: {
type: Boolean,
value: false
},
homeButton: {
type: Boolean,
value: false,
},
animated: {
// 显示隐藏的时候opacity动画效果
type: Boolean,
value: true
},
show: {
// 显示隐藏导航隐藏的时候navigation-bar的高度占位还在
type: Boolean,
value: true,
observer: '_showChange'
},
// back为true的时候返回的页面深度
delta: {
type: Number,
value: 1
},
},
/**
*
*/
data: {
displayStyle: ''
},
lifetimes: {
attached() {
const rect = wx.getMenuButtonBoundingClientRect()
wx.getSystemInfo({
success: (res) => {
const isAndroid = res.platform === 'android'
const isDevtools = res.platform === 'devtools'
this.setData({
ios: !isAndroid,
innerPaddingRight: `padding-right: ${res.windowWidth - rect.left}px`,
leftWidth: `width: ${res.windowWidth - rect.left }px`,
safeAreaTop: isDevtools || isAndroid ? `height: calc(var(--height) + ${res.safeArea.top}px); padding-top: ${res.safeArea.top}px` : ``
})
}
})
},
},
/**
*
*/
methods: {
_showChange(show: boolean) {
const animated = this.data.animated
let displayStyle = ''
if (animated) {
displayStyle = `opacity: ${
show ? '1' : '0'
};transition:opacity 0.5s;`
} else {
displayStyle = `display: ${show ? '' : 'none'}`
}
this.setData({
displayStyle
})
},
back() {
const data = this.data
if (data.delta) {
wx.navigateBack({
delta: data.delta
})
}
this.triggerEvent('back', { delta: data.delta }, {})
}
},
})

@ -0,0 +1,64 @@
<view class="weui-navigation-bar {{extClass}}">
<view class="weui-navigation-bar__inner {{ios ? 'ios' : 'android'}}" style="color: {{color}}; background: {{background}}; {{displayStyle}}; {{innerPaddingRight}}; {{safeAreaTop}};">
<!-- 左侧按钮 -->
<view class='weui-navigation-bar__left' style="{{leftWidth}};">
<block wx:if="{{back || homeButton}}">
<!-- 返回上一页 -->
<block wx:if="{{back}}">
<view class="weui-navigation-bar__buttons weui-navigation-bar__buttons_goback">
<view
bindtap="back"
class="weui-navigation-bar__btn_goback_wrapper"
hover-class="weui-active"
hover-stay-time="100"
aria-role="button"
aria-label="返回"
>
<view class="weui-navigation-bar__button weui-navigation-bar__btn_goback"></view>
</view>
</view>
</block>
<!-- 返回首页 -->
<block wx:if="{{homeButton}}">
<view class="weui-navigation-bar__buttons weui-navigation-bar__buttons_home">
<view
bindtap="home"
class="weui-navigation-bar__btn_home_wrapper"
hover-class="weui-active"
aria-role="button"
aria-label="首页"
>
<view class="weui-navigation-bar__button weui-navigation-bar__btn_home"></view>
</view>
</view>
</block>
</block>
<block wx:else>
<slot name="left"></slot>
</block>
</view>
<!-- 标题 -->
<view class='weui-navigation-bar__center'>
<view wx:if="{{loading}}" class="weui-navigation-bar__loading" aria-role="alert">
<view
class="weui-loading"
aria-role="img"
aria-label="加载中"
></view>
</view>
<block wx:if="{{title}}">
<text>{{title}}</text>
</block>
<block wx:else>
<slot name="center"></slot>
</block>
</view>
<!-- 右侧留空 -->
<view class='weui-navigation-bar__right'>
<slot name="right"></slot>
</view>
</view>
</view>

@ -0,0 +1,16 @@
// 环境配置文件
// 每个开发者需要根据自己的云开发环境修改此文件
module.exports = {
// 云开发环境ID
// 请替换为您自己的云开发环境ID
// 获取方式:微信开发者工具 -> 云开发 -> 设置 -> 环境ID
cloudEnvId: 'cloud1-4gczmwok7cacf142',
// 是否使用动态环境ID推荐设置为true支持多开发者共享
useDynamicEnv: true,
// 其他环境配置
debug: true,
version: '1.0.0'
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 703 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 699 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 625 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 926 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 734 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 782 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 714 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 678 B

@ -0,0 +1,130 @@
// category.js
Page({
data: {
categoryName: '',
categoryId: '',
products: []
},
onLoad(options) {
const { category, categoryId } = options
this.setData({
categoryName: category,
categoryId: categoryId
})
// 根据分类加载商品数据
this.loadProductsByCategory(category)
},
// 根据分类加载商品
loadProductsByCategory(category) {
// 模拟不同分类的商品数据
const categoryProducts = {
'教材': [
{
id: 1,
title: '教材《高等数学》',
price: 20,
image: '/images/default-goods-image.png',
category: '教材',
description: '无笔记2022年出版适合大一使用'
},
{
id: 2,
title: '英语四级词汇书',
price: 15,
image: '/images/default-goods-image.png',
category: '教材',
description: '全新未拆封,附带音频资料'
},
{
id: 3,
title: '线性代数教材',
price: 18,
image: '/images/default-goods-image.png',
category: '教材',
description: '8成新有少量笔记'
}
],
'数码产品': [
{
id: 4,
title: 'iPhone充电器',
price: 25,
image: '/images/default-goods-image.png',
category: '数码产品',
description: '原装正品9成新'
},
{
id: 5,
title: '蓝牙耳机',
price: 50,
image: '/images/default-goods-image.png',
category: '数码产品',
description: '小米蓝牙耳机,音质清晰'
}
],
'日用品': [
{
id: 6,
title: '洗发水',
price: 30,
image: '/images/default-goods-image.png',
category: '日用品',
description: '海飞丝洗发水500ml全新'
}
],
'服装': [
{
id: 7,
title: '运动鞋',
price: 80,
image: '/images/default-goods-image.png',
category: '服装',
description: '耐克运动鞋42码8成新'
},
{
id: 8,
title: '牛仔裤',
price: 45,
image: '/images/default-goods-image.png',
category: '服装',
description: '李宁牛仔裤30码9成新'
}
],
'运动器材': [
{
id: 9,
title: '篮球',
price: 60,
image: '/images/default-goods-image.png',
category: '运动器材',
description: '斯伯丁篮球7成新'
}
],
'其他': [
{
id: 10,
title: '台灯',
price: 35,
image: '/images/default-goods-image.png',
category: '其他',
description: 'LED台灯护眼功能9成新'
}
]
}
this.setData({
products: categoryProducts[category] || []
})
},
// 跳转到商品详情页
goToDetail(e) {
const product = e.currentTarget.dataset.product
wx.navigateTo({
url: `/pages/detail/detail?id=${product.id}&title=${product.title}&price=${product.price}&image=${product.image}&description=${product.description}&category=${product.category}`
})
}
})

@ -0,0 +1,23 @@
<view class="container">
<!-- 顶部分类名称 -->
<view class="header">
<text class="category-title">{{categoryName}}</text>
</view>
<!-- 商品列表 -->
<view class="products-list">
<view class="product-item" wx:for="{{products}}" wx:key="id" bindtap="goToDetail" data-product="{{item}}">
<image class="product-image" src="{{item.image}}" mode="aspectFill"></image>
<view class="product-info">
<text class="product-title">{{item.title}}</text>
<text class="product-price">¥{{item.price}}</text>
<button class="detail-btn" catchtap="goToDetail" data-product="{{item}}">查看详情</button>
</view>
</view>
</view>
<!-- 空状态 -->
<view class="empty-state" wx:if="{{products.length === 0}}">
<text class="empty-text">暂无商品</text>
</view>
</view>

@ -0,0 +1,5 @@
{
"navigationBarTitleText": "商品详情",
"navigationBarBackgroundColor": "#667eea",
"navigationBarTextStyle": "white"
}

@ -0,0 +1,96 @@
/* detail.wxss */
.container {
background-color: #f5f5f5;
min-height: 100vh;
}
/* 商品主图 */
.product-image-section {
width: 100%;
height: 500rpx;
background: white;
}
.product-image {
width: 100%;
height: 100%;
background: #f0f0f0;
}
/* 商品信息 */
.product-info-section {
background: white;
padding: 30rpx;
margin-bottom: 20rpx;
}
.product-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
}
.product-price {
font-size: 48rpx;
color: #e74c3c;
font-weight: bold;
margin-bottom: 20rpx;
}
.product-description {
font-size: 28rpx;
color: #666;
line-height: 1.6;
}
/* 联系卖家 */
.contact-section {
background: white;
padding: 30rpx;
margin-bottom: 20rpx;
}
.contact-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
}
.contact-info {
display: flex;
flex-direction: column;
gap: 10rpx;
}
.contact-text {
font-size: 28rpx;
color: #333;
}
.contact-note {
font-size: 24rpx;
color: #999;
}
/* 操作按钮 */
.action-section {
padding: 30rpx;
}
.back-btn {
width: 100%;
height: 80rpx;
line-height: 80rpx;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 12rpx;
font-size: 28rpx;
font-weight: 500;
}
.back-btn:active {
opacity: 0.8;
}

@ -0,0 +1,61 @@
/**index.less**/
page {
height: 100vh;
display: flex;
flex-direction: column;
}
.scrollarea {
flex: 1;
overflow-y: hidden;
}
.userinfo {
display: flex;
flex-direction: column;
align-items: center;
color: #aaa;
width: 80%;
}
.userinfo-avatar {
overflow: hidden;
width: 128rpx;
height: 128rpx;
margin: 20rpx;
border-radius: 50%;
}
.usermotto {
margin-top: 200px;
}
.avatar-wrapper {
padding: 0;
width: 56px !important;
border-radius: 8px;
margin-top: 40px;
margin-bottom: 40px;
}
.avatar {
display: block;
width: 56px;
height: 56px;
}
.nickname-wrapper {
display: flex;
width: 100%;
padding: 16px;
box-sizing: border-box;
border-top: .5px solid rgba(0, 0, 0, 0.1);
border-bottom: .5px solid rgba(0, 0, 0, 0.1);
color: black;
}
.nickname-label {
width: 105px;
}
.nickname-input {
flex: 1;
}

@ -0,0 +1,54 @@
// index.ts
// 获取应用实例
const app = getApp<IAppOption>()
const defaultAvatarUrl = 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0'
Component({
data: {
motto: 'Hello World',
userInfo: {
avatarUrl: defaultAvatarUrl,
nickName: '',
},
hasUserInfo: false,
canIUseGetUserProfile: wx.canIUse('getUserProfile'),
canIUseNicknameComp: wx.canIUse('input.type.nickname'),
},
methods: {
// 事件处理函数
bindViewTap() {
wx.navigateTo({
url: '../logs/logs',
})
},
onChooseAvatar(e: any) {
const { avatarUrl } = e.detail
const { nickName } = this.data.userInfo
this.setData({
"userInfo.avatarUrl": avatarUrl,
hasUserInfo: nickName && avatarUrl && avatarUrl !== defaultAvatarUrl,
})
},
onInputChange(e: any) {
const nickName = e.detail.value
const { avatarUrl } = this.data.userInfo
this.setData({
"userInfo.nickName": nickName,
hasUserInfo: nickName && avatarUrl && avatarUrl !== defaultAvatarUrl,
})
},
getUserProfile() {
// 推荐使用wx.getUserProfile获取用户信息开发者每次通过该接口获取用户个人信息均需用户确认开发者妥善保管用户快速填写的头像昵称避免重复弹窗
wx.getUserProfile({
desc: '展示用户信息', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
success: (res) => {
console.log(res)
this.setData({
userInfo: res.userInfo,
hasUserInfo: true
})
}
})
},
},
})

@ -0,0 +1,5 @@
{
"usingComponents": {
"navigation-bar": "/components/navigation-bar/navigation-bar"
}
}

@ -0,0 +1,16 @@
page {
height: 100vh;
display: flex;
flex-direction: column;
}
.scrollarea {
flex: 1;
overflow-y: hidden;
}
.log-item {
margin-top: 20rpx;
text-align: center;
}
.log-item:last-child {
padding-bottom: env(safe-area-inset-bottom);
}

@ -0,0 +1,21 @@
// logs.ts
// const util = require('../../utils/util.js')
import { formatTime } from '../../utils/util'
Component({
data: {
logs: [],
},
lifetimes: {
attached() {
this.setData({
logs: (wx.getStorageSync('logs') || []).map((log: string) => {
return {
date: formatTime(new Date(log)),
timeStamp: log
}
}),
})
}
},
})

@ -0,0 +1,7 @@
<!--logs.wxml-->
<navigation-bar title="查看启动日志" back="{{true}}" color="black" background="#FFF"></navigation-bar>
<scroll-view class="scrollarea" scroll-y type="list">
<block wx:for="{{logs}}" wx:key="timeStamp" wx:for-item="log">
<view class="log-item">{{index + 1}}. {{log.date}}</view>
</block>
</scroll-view>

@ -0,0 +1,402 @@
// pages/profile/index.js
const app = getApp();
Page({
data: {
hasUserInfo: false,
canIUseGetUserProfile: false,
userInfo: {
avatarUrl: '',
nickName: '',
school: '',
studentId: ''
},
wallet: {
balance: '0.00',
points: 0,
coupons: 0
},
orderCount: {
waiting: 0,
processing: 0,
completed: 0,
refund: 0
},
showEditDialog: false, // 控制编辑弹窗显示
editUserInfo: { // 编辑时临时存储用户信息
avatarUrl: '',
nickName: '',
school: '',
studentId: ''
}
},
onLoad: function(options) {
console.log('个人中心页面加载');
// 检查 wx.getUserProfile 是否可用
if (wx.getUserProfile) {
this.setData({
canIUseGetUserProfile: true
});
}
// 如果已经有用户信息,初始化数据
if (app.globalData.userInfo) {
this.setData({
hasUserInfo: true,
userInfo: {
avatarUrl: app.globalData.userInfo.avatarUrl,
nickName: app.globalData.userInfo.nickName,
school: app.globalData.userInfo.school,
studentId: app.globalData.userInfo.studentId,
}
});
}
},
onShow: function() {
console.log('个人中心页面显示');
// 先尝试用全局状态判断是否登录
if (app.globalData.isLoggedIn) {
// 已登录,直接拉取钱包和订单数据
this.getUserWalletInfo();
this.getUserOrderCount();
} else {
// 没登录,尝试从缓存取用户信息
const userInfo = wx.getStorageSync('userInfo');
if (userInfo) {
// 缓存里有用户信息,设置全局状态和页面数据
app.globalData.userInfo = userInfo;
app.globalData.hasUserInfo = true;
app.globalData.isLoggedIn = true;
this.setData({
hasUserInfo: true,
userInfo: {
avatarUrl: userInfo.avatarUrl,
nickName: userInfo.nickName,
school: userInfo.school,
studentId: userInfo.studentId
}
});
// 缓存里有用户信息,也拉取钱包和订单数据
this.getUserWalletInfo();
this.getUserOrderCount();
} else {
// 缓存没有用户信息,设置页面为未登录状态
this.setData({
hasUserInfo: false
});
}
}
},
// 获取微信用户信息(新版)
getUserProfile: function () {
// 检查本地是否已登陆过
const storedUser = wx.getStorageSync('userInfo');
console.log('获取缓存 userInfo:', storedUser);
if (storedUser) {
// 如果已经登录过,直接使用缓存数据
app.globalData.userInfo = storedUser;
app.globalData.hasUserInfo = true;
app.globalData.isLoggedIn = true;
this.setData({
hasUserInfo: true,
userInfo: {
avatarUrl: storedUser.avatarUrl,
nickName: storedUser.nickName,
school: storedUser.school,
studentId: storedUser.studentId
}
});
wx.showToast({
title: '欢迎回来',
icon: 'success'
});
return; // 不再调用 getUserProfile直接结束
}
// 第二步:首次登录时走下面流程
wx.showLoading({ title: '登录中...' });
wx.getUserProfile({
desc: '用于完善用户资料',
success: (res) => {
console.log('获取用户信息成功', res);
const userInfo = res.userInfo;
// 先把头像和昵称放到编辑弹窗临时数据里,弹窗让用户完善
this.setData({
editUserInfo: {
avatarUrl: userInfo.avatarUrl,
nickName: userInfo.nickName,
school: userInfo.school,
studentId: userInfo.studentId
},
showEditDialog: true
});
wx.hideLoading();
},
fail: () => {
wx.hideLoading();
wx.showToast({
title: '授权失败,请重试',
icon: 'none'
});
}
});
},
// 选择新头像
chooseAvatar: function (e) {
const avatarUrl = e.detail.avatarUrl;
this.setData({
'editUserInfo.avatarUrl': avatarUrl
});
},
// 昵称输入监听
onNickNameInput: function (e) {
this.setData({
'editUserInfo.nickName': e.detail.value
});
},
// 学校输入监听
onschoolInput: function (e) {
this.setData({
'editUserInfo.school': e.detail.value
});
},
// 学号输入监听
onstudentIdInput: function (e) {
this.setData({
'editUserInfo.studentId': e.detail.value
});
},
// 取消编辑,关闭弹窗
cancelEdit: function () {
this.setData({
showEditDialog: false
});
},
// 确认编辑,更新用户信息并登录
confirmEdit: function () {
const self = this;
const newUserInfo = this.data.editUserInfo;
if (!newUserInfo.nickName || newUserInfo.nickName.trim() === '') {
wx.showToast({
title: '昵称不能为空',
icon: 'none'
});
return;
}
wx.showLoading({ title: '更新中...' });
// 调用云函数更新用户信息
wx.cloud.callFunction({
name: 'updateUserInfo',
data: { userInfo: newUserInfo },
success: (res) => {
wx.hideLoading();
if (res.result && res.result.success) {
// 更新全局和本地缓存
app.globalData.userInfo = newUserInfo;
app.globalData.hasUserInfo = true;
app.globalData.isLoggedIn = true;
wx.setStorageSync('userInfo', newUserInfo);
// 更新页面显示
self.setData({
userInfo: {
...self.data.userInfo,
avatarUrl: newUserInfo.avatarUrl,
nickName: newUserInfo.nickName,
school: newUserInfo.school,
studentId: newUserInfo.studentId
},
hasUserInfo: true,
showEditDialog: false
});
wx.showToast({
title: '更新成功',
icon: 'success'
});
// 调用 wx.login 登录云函数
wx.login({
success(loginRes) {
if (loginRes.code) {
wx.cloud.callFunction({
name: 'userLogin',
data: {
code: loginRes.code,
userInfo: newUserInfo
},
success(cloudRes) {
console.log('userLogin 成功:', cloudRes);
},
fail(err) {
console.error('userLogin 云函数失败', err);
}
});
}
}
});
} else {
wx.showToast({
title: res.result.message || '更新失败',
icon: 'none'
});
}
},
fail: () => {
wx.hideLoading();
wx.showToast({
title: '网络错误,请重试',
icon: 'none'
});
}
});
},
// 退出登录
logout: function () {
wx.clearStorageSync();
app.globalData.userInfo = null;
app.globalData.hasUserInfo = false;
app.globalData.isLoggedIn = false;
this.setData({
hasUserInfo: false,
userInfo: {
avatarUrl: '',
nickName: '',
school: '某某大学',
studentId: '学号认证'
},
wallet: {
balance: '0.00',
points: 0,
coupons: 0
},
orderCount: {
waiting: 0,
processing: 0,
completed: 0,
refund: 0
}
});
wx.showToast({
title: '已退出登录',
icon: 'success'
});
},
// 获取钱包信息
getUserWalletInfo: function() {
const self = this;
wx.cloud.callFunction({
name: 'getUserInfo',
data: {},
success: function(res) {
if (res.result && res.result.success && res.result.userInfo) {
const userInfo = res.result.userInfo;
self.setData({
wallet: {
balance: userInfo.balance ? userInfo.balance.toFixed(2) : '0.00',
points: userInfo.credit || 100,
coupons: userInfo.coupons || 0
}
});
} else {
self.setData({
wallet: {
balance: '0.00',
points: 100,
coupons: 0
}
});
}
},
fail: function() {
self.setData({
wallet: {
balance: '0.00',
points: 100,
coupons: 0
}
});
}
});
},
// 获取订单数量
getUserOrderCount: function() {
const self = this;
wx.cloud.callFunction({
name: 'getUserInfo',
data: {},
success: function(res) {
if (res.result && res.result.success && res.result.orderCount) {
self.setData({
orderCount: res.result.orderCount
});
} else {
self.setData({
orderCount: {
waiting: 0,
processing: 0,
completed: 0,
refund: 0
}
});
}
},
fail: function() {
self.setData({
orderCount: {
waiting: 0,
processing: 0,
completed: 0,
refund: 0
}
});
}
});
},
// 页面跳转,需登录才能访问
navigateTo: function(e) {
var url = e.currentTarget.dataset.url;
if (!app.globalData.isLoggedIn) {
wx.showToast({
title: '请先登录',
icon: 'none'
});
return;
}
wx.navigateTo({ url: url });
},
// 分享配置
onShareAppMessage: function() {
return {
title: '宿小君 - 校园生活助手',
path: '/pages/index/index'
};
}
});

@ -0,0 +1,124 @@
<view class="profile-container">
<!-- 用户信息区域 -->
<view class="user-info">
<view class="user-info-bg"></view>
<view class="user-info-content" style="height: 250rpx; display: flex; box-sizing: border-box; left: 0rpx; top: 0rpx">
<image class="avatar" src="{{userInfo.avatarUrl || '/images/default_avatar.png'}}"></image>
<view class="user-details" wx:if="{{hasUserInfo}}">
<text class="nickname">{{userInfo.nickName}}</text>
<text class="school">{{userInfo.school}} {{userInfo.studentId}}</text>
</view>
<!-- 新版获取用户信息按钮 -->
<button class="login-btn" wx:if="{{!hasUserInfo}}" bindtap="getUserProfile">授权登录</button>
</view>
<!-- 编辑昵称头像弹窗 -->
<!-- 2025-8-1任柯敏添加 -->
<view wx:if="{{showEditDialog}}" class="edit-dialog-mask">
<view class="edit-dialog">
<!-- <text class="dialog-title">完善个人信息</text> -->
<image class="edit-avatar" src="{{editUserInfo.avatarUrl}}"></image>
<button open-type="chooseAvatar" bindchooseavatar="chooseAvatar">点击更换头像</button>
<input class="edit-nickname-input" placeholder="请输入昵称" value="{{editUserInfo.nickName}}" bindinput="onNickNameInput" />
<input class="edit-nickname-input" placeholder="请输入学校名称" value="{{editUserInfo.school}}" bindinput="onschoolInput" />
<input class="edit-nickname-input" placeholder="请输入学号" value="{{editUserInfo.studentId}}" bindinput="onstudentIdInput" />
<view class="dialog-btns">
<button bindtap="cancelEdit">取消</button>
<button bindtap="confirmEdit">确认</button>
</view>
</view>
</view>
</view>
<!-- 我的钱包 -->
<view class="wallet-card">
<view class="wallet-item">
<text class="wallet-value">{{wallet.balance}}</text>
<text class="wallet-label">余额</text>
</view>
<view class="wallet-divider"></view>
<view class="wallet-item">
<text class="wallet-value">{{wallet.points}}</text>
<text class="wallet-label">积分</text>
</view>
<view class="wallet-divider"></view>
<view class="wallet-item">
<text class="wallet-value">{{wallet.coupons}}</text>
<text class="wallet-label">优惠券</text>
</view>
</view>
<!-- 订单区域 -->
<view class="section-card">
<view class="section-header">
<text class="section-title">我的订单</text>
<navigator url="/pages/profile/orders" class="section-more">
<text>全部订单</text>
<image class="arrow-icon" src="/images/arrow.png"></image>
</navigator>
</view>
<view class="order-types">
<navigator url="/pages/profile/orders?status=waiting" class="order-type-item">
<image class="order-type-icon" src="/images/waiting.png"></image>
<text>待付款</text>
<text class="badge" wx:if="{{orderCount.waiting > 0}}">{{orderCount.waiting}}</text>
</navigator>
<navigator url="/pages/profile/orders?status=processing" class="order-type-item">
<image class="order-type-icon" src="/images/processing.png"></image>
<text>进行中</text>
<text class="badge" wx:if="{{orderCount.processing > 0}}">{{orderCount.processing}}</text>
</navigator>
<navigator url="/pages/profile/orders?status=completed" class="order-type-item">
<image class="order-type-icon" src="/images/completed.png"></image>
<text>已完成</text>
<text class="badge" wx:if="{{orderCount.completed > 0}}">{{orderCount.completed}}</text>
</navigator>
<navigator url="/pages/profile/orders?status=refund" class="order-type-item">
<image class="order-type-icon" src="/images/refund.png"></image>
<text>退款/售后</text>
<text class="badge" wx:if="{{orderCount.refund > 0}}">{{orderCount.refund}}</text>
</navigator>
</view>
</view>
<!-- 我的服务 -->
<view class="section-card">
<view class="section-header">
<text class="section-title">我的服务</text>
</view>
<view class="service-list">
<view class="service-item" bindtap="navigateTo" data-url="/pages/profile/my_posts">
<image class="service-icon" src="/images/my_posts.png"></image>
<text>我的发布</text>
</view>
<view class="service-item" bindtap="navigateTo" data-url="/pages/profile/favorites">
<image class="service-icon" src="/images/favorites.png"></image>
<text>我的收藏</text>
</view>
<view class="service-item" bindtap="navigateTo" data-url="/pages/profile/address">
<image class="service-icon" src="/images/address.png"></image>
<text>收货地址</text>
</view>
<view class="service-item" bindtap="navigateTo" data-url="/pages/profile/feedback">
<image class="service-icon" src="/images/feedback.png"></image>
<text>意见反馈</text>
</view>
<view class="service-item" bindtap="navigateTo" data-url="/pages/profile/settings">
<image class="service-icon" src="/images/settings.png"></image>
<text>设置</text>
</view>
<view class="service-item" bindtap="navigateTo" data-url="/pages/profile/settings">
<image class="service-icon" src="/images/settings.png"></image>
<text>联系客服</text>
</view>
</view>
</view>
<!-- 客服按钮 -->
<!-- <button class="customer-service" open-type="contact">联系客服</button> -->
<button class="logout-btn" wx:if="{{hasUserInfo}}" bindtap="logout">退出登录</button>
</view>

@ -0,0 +1,316 @@
.profile-container {
min-height: 100vh;
background-color: #f7f7f7;
padding-bottom: 40rpx;
}
/* 用户信息区域 */
.user-info {
position: relative;
height: 300rpx;
overflow: hidden;
}
.user-info-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #3A86FF;
z-index: 1;
}
.user-info-content {
position: relative;
display: flex;
align-items: center;
padding: 40rpx 30rpx;
z-index: 2;
}
.avatar {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
border: 4rpx solid #ffffff;
margin-right: 30rpx;
}
.user-details {
display: flex;
flex-direction: column;
}
.nickname {
color: #ffffff;
font-size: 36rpx;
font-weight: bold;
margin-bottom: 10rpx;
}
.school {
color: rgba(255, 255, 255, 0.8);
font-size: 24rpx;
}
.login-btn {
background-color: rgba(255, 255, 255, 0.2);
color: #ffffff;
font-size: 28rpx;
padding: 12rpx 40rpx;
border-radius: 32rpx;
border: 1rpx solid rgba(255, 255, 255, 0.5);
line-height: 1.5;
}
.login-btn::after {
border: none;
}
/* 钱包卡片 */
.wallet-card {
display: flex;
justify-content: space-around;
align-items: center;
margin: -50rpx 30rpx 30rpx;
padding: 30rpx 0;
background-color: #ffffff;
border-radius: 12rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
position: relative;
z-index: 3;
}
.wallet-item {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
}
.wallet-value {
font-size: 36rpx;
color: #333333;
font-weight: bold;
margin-bottom: 8rpx;
}
.wallet-label {
font-size: 24rpx;
color: #999999;
}
.wallet-divider {
width: 1rpx;
height: 50rpx;
background-color: #eeeeee;
}
/* 功能区块 */
.section-card {
margin: 0 30rpx 30rpx;
padding: 30rpx;
background-color: #ffffff;
border-radius: 12rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30rpx;
}
.section-title {
font-size: 32rpx;
color: #333333;
font-weight: bold;
}
.section-more {
display: flex;
align-items: center;
font-size: 24rpx;
color: #999999;
}
.arrow-icon {
width: 24rpx;
height: 24rpx;
margin-left: 6rpx;
}
/* 订单类型 */
.order-types {
display: flex;
justify-content: space-between;
}
.order-type-item {
display: flex;
flex-direction: column;
align-items: center;
position: relative;
}
.order-type-icon {
width: 60rpx;
height: 60rpx;
margin-bottom: 12rpx;
}
.order-type-item text {
font-size: 24rpx;
color: #666666;
}
.badge {
position: absolute;
top: -10rpx;
right: -10rpx;
background-color: #FF5252;
color: #ffffff;
font-size: 20rpx;
border-radius: 20rpx;
padding: 2rpx 10rpx;
min-width: 32rpx;
text-align: center;
}
/* 服务列表 */
.service-list {
display: flex;
flex-wrap: wrap;
}
.service-item {
width: 33.33%;
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 30rpx;
}
.service-icon {
width: 60rpx;
height: 60rpx;
margin-bottom: 12rpx;
}
.service-item text {
font-size: 24rpx;
color: #666666;
}
/* 客服按钮 */
.customer-service {
width: 90%;
height: 88rpx;
line-height: 88rpx;
font-size: 30rpx;
color: #666666;
background-color: #ffffff;
border-radius: 44rpx;
margin-top: 30rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
/* 新增选择昵称和头像功能 */
/* 遮罩层 */
.edit-dialog-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5); /* 半透明黑色遮罩 */
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
}
/* 弹窗主体 */
.edit-dialog {
width: 85%;
max-width: 400rpx;
background-color: #fff;
border-radius: 20rpx;
padding: 30rpx 30rpx 40rpx 30rpx;
box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.15);
text-align: center;
}
/* 标题 */
.dialog-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 25rpx;
}
/* 头像图片 */
.edit-avatar {
width: 120rpx;
height: 120rpx;
border-radius: 60rpx;
margin: 0 auto 20rpx auto;
object-fit: cover;
border: 2rpx solid #1AAD19; /* 微信绿边框 */
}
/* 更换头像按钮 */
button[open-type="chooseAvatar"] {
display: inline-block;
background-color: #1AAD19;
color: #fff;
font-size: 28rpx;
padding: 10rpx 25rpx;
border-radius: 40rpx;
margin-bottom: 25rpx;
border: none;
}
/* 昵称输入框 */
.edit-nickname-input {
width: 100%;
height: 50rpx;
line-height: 50rpx;
font-size: 28rpx;
padding: 0 15rpx;
border: 1rpx solid #ddd;
border-radius: 10rpx;
box-sizing: border-box;
margin-bottom: 30rpx;
color: #333;
}
/* 底部按钮容器 */
.dialog-btns {
display: flex;
justify-content: space-between;
gap: 20rpx;
}
/* 取消按钮 */
.dialog-btns button:first-child {
flex: 1;
background-color: #f5f5f5;
color: #666;
font-size: 28rpx;
padding: 12rpx 0;
border-radius: 12rpx;
border: none;
}
/* 确认按钮 */
.dialog-btns button:last-child {
flex: 1;
background-color: #1AAD19;
color: white;
font-size: 28rpx;
padding: 12rpx 0;
border-radius: 12rpx;
border: none;
}

@ -0,0 +1,201 @@
// pages/profile/orders.js
Page({
data: {
currentTab: 'all',
orders: [],
page: 1,
pageSize: 10,
hasMoreOrders: true,
isLoading: false
},
onLoad: function (options) {
// 如果有状态参数,则切换到对应状态选项卡
if (options.status) {
this.setData({
currentTab: options.status
});
}
// 加载订单列表
this.loadOrders(true);
},
// 切换选项卡
switchTab: function (e) {
const tab = e.currentTarget.dataset.tab;
this.setData({
currentTab: tab,
orders: [],
page: 1,
hasMoreOrders: true
});
this.loadOrders(true);
},
// 加载订单列表
loadOrders: function (refresh = false) {
if (this.data.isLoading || (!refresh && !this.data.hasMoreOrders)) {
return;
}
this.setData({ isLoading: true });
if (refresh) {
wx.showLoading({
title: '加载中...',
});
}
// 模拟请求数据
setTimeout(() => {
// 模拟订单数据 - 只保留二手超市相关订单
const mockOrders = [
{
id: 1,
orderType: '二手商品',
status: 'processing',
statusText: '已付款',
title: 'iPad Pro 2021 二手95新',
price: 4500,
image: '/images/ipad.jpg',
createTime: '2023-05-19 10:15'
},
{
id: 2,
orderType: '二手商品',
status: 'completed',
statusText: '已完成',
title: '微积分教材 同济第七版',
price: 20,
image: '/images/book.jpg',
createTime: '2023-05-17 16:20'
},
{
id: 3,
orderType: '二手商品',
status: 'refund',
statusText: '退款中',
title: '自行车 捷安特 ATX',
price: 800,
image: '/images/bike.jpg',
createTime: '2023-05-16 09:30'
},
{
id: 4,
orderType: '二手商品',
status: 'waiting',
statusText: '待付款',
title: 'AirPods Pro 二代',
price: 1200,
image: '/images/airpods.jpg',
createTime: '2023-05-15 14:20'
},
{
id: 5,
orderType: '二手商品',
status: 'completed',
statusText: '已完成',
title: '学习桌 可调节高度',
price: 150,
image: '/images/table.jpg',
createTime: '2023-05-14 11:45'
}
];
// 根据当前选项卡筛选订单
let filteredOrders = this.data.currentTab === 'all' ?
mockOrders :
mockOrders.filter(item => item.status === this.data.currentTab);
// 模拟分页
const start = (this.data.page - 1) * this.data.pageSize;
const end = start + this.data.pageSize;
const pageOrders = filteredOrders.slice(start, end);
// 更新数据
if (refresh) {
this.setData({
orders: pageOrders,
hasMoreOrders: pageOrders.length === this.data.pageSize,
page: this.data.page + 1,
isLoading: false
});
wx.hideLoading();
wx.stopPullDownRefresh();
} else {
this.setData({
orders: [...this.data.orders, ...pageOrders],
hasMoreOrders: pageOrders.length === this.data.pageSize,
page: this.data.page + 1,
isLoading: false
});
}
}, 1000);
},
// 加载更多
loadMore: function () {
if (!this.data.isLoading && this.data.hasMoreOrders) {
this.loadOrders();
}
},
//雷雨田2025.8.30添加
confirmOrder() {
if (!this.data.address) {
wx.showToast({ title: '请选择地址', icon: 'none' })
return
}
wx.showToast({ title: '下单成功', icon: 'success' })
},
// 下拉刷新
onPullDownRefresh: function () {
this.setData({
page: 1,
hasMoreOrders: true
});
this.loadOrders(true);
},
// 查看订单详情
viewOrderDetail: function (e) {
const orderId = e.currentTarget.dataset.id;
wx.navigateTo({
url: `/pages/profile/order_detail?id=${orderId}`
});
},
// 取消订单
cancelOrder: function (e) {
const orderId = e.currentTarget.dataset.id;
wx.showModal({
title: '确认取消',
content: '确定要取消此订单吗?',
success: (res) => {
if (res.confirm) {
// 模拟取消订单
wx.showLoading({
title: '处理中...',
});
setTimeout(() => {
wx.hideLoading();
// 更新本地订单状态或重新加载订单列表
const orders = this.data.orders.filter(item => item.id !== orderId);
this.setData({
orders: orders
});
wx.showToast({
title: '订单已取消',
icon: 'success'
});
}, 1000);
}
}
});
}
})

@ -0,0 +1,62 @@
<view class="orders-container">
<!-- 订单状态选项卡 -->
<view class="tabs">
<view class="tab-item {{currentTab === 'all' ? 'active' : ''}}" data-tab="all" bindtap="switchTab">
全部
</view>
<view class="tab-item {{currentTab === 'waiting' ? 'active' : ''}}" data-tab="waiting" bindtap="switchTab">
待付款
</view>
<view class="tab-item {{currentTab === 'processing' ? 'active' : ''}}" data-tab="processing" bindtap="switchTab">
进行中
</view>
<view class="tab-item {{currentTab === 'completed' ? 'active' : ''}}" data-tab="completed" bindtap="switchTab">
已完成
</view>
<view class="tab-item {{currentTab === 'refund' ? 'active' : ''}}" data-tab="refund" bindtap="switchTab">
退款/售后
</view>
</view>
<!-- 订单列表 -->
<scroll-view scroll-y class="order-list" bindscrolltolower="loadMore">
<view class="empty-tip" wx:if="{{orders.length === 0}}">
<image class="empty-icon" src="/images/empty.png"></image>
<text>暂无订单</text>
</view>
<view class="order-item" wx:for="{{orders}}" wx:key="id" bindtap="viewOrderDetail" data-id="{{item.id}}">
<!-- 订单头部信息 -->
<view class="order-header">
<view class="order-type">{{item.orderType}}</view>
<view class="order-status {{item.status}}">{{item.statusText}}</view>
</view>
<!-- 订单主体内容 -->
<view class="order-content">
<image class="order-image" src="{{item.image}}" mode="aspectFill"></image>
<view class="order-info">
<view class="order-title">{{item.title}}</view>
<view class="order-price">¥{{item.price}}</view>
<view class="order-time">{{item.createTime}}</view>
</view>
</view>
<!-- 订单操作按钮 -->
<view class="order-footer">
<button class="order-btn cancel" wx:if="{{item.status === 'waiting'}}" catchtap="cancelOrder" data-id="{{item.id}}">取消订单</button>
<button class="order-btn primary" wx:if="{{item.status === 'waiting'}}">付款</button>
<button class="order-btn primary" wx:if="{{item.status === 'processing'}}">确认收货</button>
<button class="order-btn" wx:if="{{item.status === 'completed'}}">再次购买</button>
<button class="order-btn" wx:if="{{item.status === 'completed'}}">评价</button>
</view>
</view>
<!-- 加载更多 -->
<view class="loading-more" wx:if="{{hasMoreOrders && orders.length > 0}}">
<text>加载中...</text>
</view>
<view class="no-more" wx:if="{{!hasMoreOrders && orders.length > 0}}">
<text>没有更多订单了</text>
</view>
</scroll-view>
</view>

@ -0,0 +1,187 @@
.orders-container {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f7f7f7;
}
/* 选项卡样式 */
.tabs {
display: flex;
background-color: #ffffff;
border-bottom: 1rpx solid #eeeeee;
position: sticky;
top: 0;
z-index: 10;
}
.tab-item {
flex: 1;
text-align: center;
padding: 24rpx 0;
font-size: 28rpx;
color: #666666;
position: relative;
}
.tab-item.active {
color: #3A86FF;
font-weight: bold;
}
.tab-item.active::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 40rpx;
height: 4rpx;
background-color: #3A86FF;
border-radius: 2rpx;
}
/* 订单列表样式 */
.order-list {
flex: 1;
padding: 20rpx;
}
.empty-tip {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
}
.empty-icon {
width: 200rpx;
height: 200rpx;
margin-bottom: 20rpx;
}
.empty-tip text {
font-size: 28rpx;
color: #999999;
}
.order-item {
background-color: #ffffff;
border-radius: 12rpx;
margin-bottom: 20rpx;
overflow: hidden;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.order-header {
display: flex;
justify-content: space-between;
padding: 20rpx;
border-bottom: 1rpx solid #f2f2f2;
}
.order-type {
font-size: 28rpx;
color: #333333;
}
.order-status {
font-size: 28rpx;
}
.order-status.waiting {
color: #FF9800;
}
.order-status.processing {
color: #3A86FF;
}
.order-status.completed {
color: #4CAF50;
}
.order-status.refund {
color: #9E9E9E;
}
.order-content {
display: flex;
padding: 20rpx;
border-bottom: 1rpx solid #f2f2f2;
}
.order-image {
width: 160rpx;
height: 160rpx;
border-radius: 8rpx;
margin-right: 20rpx;
}
.order-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.order-title {
font-size: 28rpx;
color: #333333;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.order-price {
font-size: 32rpx;
color: #FF5252;
font-weight: bold;
}
.order-time {
font-size: 24rpx;
color: #999999;
}
.order-footer {
display: flex;
justify-content: flex-end;
padding: 20rpx;
}
.order-btn {
margin-left: 20rpx;
padding: 0 30rpx;
height: 60rpx;
line-height: 60rpx;
font-size: 26rpx;
color: #666666;
background-color: #f7f7f7;
border-radius: 30rpx;
min-width: 120rpx;
}
.order-btn.primary {
background-color: #3A86FF;
color: #ffffff;
}
.order-btn.cancel {
background-color: #ffffff;
color: #999999;
border: 1rpx solid #eeeeee;
}
.order-btn::after {
border: none;
}
.loading-more, .no-more {
text-align: center;
padding: 20rpx 0;
font-size: 24rpx;
color: #999999;
}

@ -0,0 +1,24 @@
// profile.js
Page({
data: {
userInfo: {
name: '用户XXX',
avatar: '/images/icons/avatar.png',
desc: '中南民族大学学生'
}
},
onLoad() {
console.log('个人中心页面加载完成')
},
// 显示功能提示
showFeatureTip(e) {
const feature = e.currentTarget.dataset.feature
wx.showToast({
title: `${feature}功能暂未开放`,
icon: 'none',
duration: 2000
})
}
})

@ -0,0 +1,5 @@
{
"navigationBarTitleText": "个人中心",
"navigationBarBackgroundColor": "#667eea",
"navigationBarTextStyle": "white"
}

@ -0,0 +1,44 @@
<view class="container">
<!-- 用户信息 -->
<view class="user-section">
<view class="user-info">
<image class="user-avatar" src="/images/icons/avatar.png" mode="aspectFill"></image>
<view class="user-details">
<text class="user-name">用户XXX</text>
<text class="user-desc">中南民族大学学生</text>
</view>
</view>
</view>
<!-- 功能入口 -->
<view class="features-section">
<view class="feature-item" bindtap="showFeatureTip" data-feature="收藏">
<view class="feature-icon">❤️</view>
<text class="feature-name">我的收藏</text>
<view class="feature-arrow">></view>
</view>
<view class="feature-item" bindtap="showFeatureTip" data-feature="发布">
<view class="feature-icon">📝</view>
<text class="feature-name">我的发布</text>
<view class="feature-arrow">></view>
</view>
<view class="feature-item" bindtap="showFeatureTip" data-feature="订单">
<view class="feature-icon">📋</view>
<text class="feature-name">我的订单</text>
<view class="feature-arrow">></view>
</view>
<view class="feature-item" bindtap="showFeatureTip" data-feature="设置">
<view class="feature-icon">⚙️</view>
<text class="feature-name">设置</text>
<view class="feature-arrow">></view>
</view>
</view>
<!-- 版本信息 -->
<view class="version-section">
<text class="version-text">版本 1.0.0</text>
</view>
</view>

@ -0,0 +1,97 @@
/* profile.wxss */
.container {
background-color: #f5f5f5;
min-height: 100vh;
}
/* 用户信息 */
.user-section {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 40rpx 30rpx;
margin-bottom: 20rpx;
}
.user-info {
display: flex;
align-items: center;
}
.user-avatar {
width: 120rpx;
height: 120rpx;
border-radius: 60rpx;
margin-right: 30rpx;
border: 4rpx solid rgba(255, 255, 255, 0.3);
}
.user-details {
flex: 1;
}
.user-name {
font-size: 36rpx;
color: white;
font-weight: bold;
display: block;
margin-bottom: 10rpx;
}
.user-desc {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.8);
display: block;
}
/* 功能入口 */
.features-section {
background: white;
border-radius: 16rpx;
margin: 0 20rpx 20rpx 20rpx;
overflow: hidden;
}
.feature-item {
display: flex;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
transition: background-color 0.3s ease;
}
.feature-item:last-child {
border-bottom: none;
}
.feature-item:active {
background-color: #f8f9fa;
}
.feature-icon {
font-size: 40rpx;
margin-right: 20rpx;
width: 40rpx;
text-align: center;
}
.feature-name {
flex: 1;
font-size: 32rpx;
color: #333;
font-weight: 500;
}
.feature-arrow {
font-size: 32rpx;
color: #ccc;
}
/* 版本信息 */
.version-section {
text-align: center;
padding: 40rpx 0;
}
.version-text {
font-size: 24rpx;
color: #999;
}

@ -0,0 +1,100 @@
// pages/secondhand/detail.js
Page({
data: {
goods: {
id: 1,
title: 'iPad Pro 2021 二手95新',
price: 4500,
condition: '95新',
description: '正品iPad Pro 2021款11英寸WiFi版128G存储银色。机器成色非常好无磕碰无划痕电池健康96%,原装充电器配件齐全。有需要的请联系我。',
images: [
'/images/ipad_1.jpg',
'/images/ipad_2.jpg',
'/images/ipad_3.jpg'
],
publishTime: '2023-05-20 14:30',
category: 'electronics',
seller: {
id: 'user456',
name: '张同学',
avatar: '/images/avatar.png',
verifyType: '大三学生'
}
}
},
onLoad: function(options) {
const goodsId = options.id;
// 根据goodsId从服务器获取商品详情
// 这里模拟获取数据
this.getGoodsDetail(goodsId);
},
getGoodsDetail: function(goodsId) {
// 模拟从服务器获取数据
// 实际场景中应该发起网络请求
console.log('获取商品详情ID:', goodsId);
// 现在使用的是模拟数据,实际应该替换成从服务器获取的数据
},
previewImage: function(e) {
const index = e.currentTarget.dataset.index;
wx.previewImage({
current: this.data.goods.images[index],
urls: this.data.goods.images
});
},
contactSeller: function() {
wx.showModal({
title: '联系卖家',
content: '联系电话138****5678\n微信zhangsan123',
showCancel: false
});
},
buyNow: function() {
wx.showModal({
title: '确认购买',
content: `您确定要购买"${this.data.goods.title}"吗?价格:¥${this.data.goods.price}`,
success: (res) => {
if (res.confirm) {
// 确认购买,调用购买接口
this.confirmPurchase();
}
}
});
},
confirmPurchase: function() {
// 模拟调用购买接口
wx.showLoading({
title: '处理中...',
});
// 模拟网络请求延迟
setTimeout(() => {
wx.hideLoading();
wx.showToast({
title: '购买成功',
icon: 'success',
duration: 2000,
success: () => {
// 延迟返回,让用户看到成功提示
setTimeout(() => {
wx.navigateBack();
}, 1500);
}
});
}, 1500);
},
onShareAppMessage: function() {
return {
title: this.data.goods.title,
path: `/pages/secondhand/detail?id=${this.data.goods.id}`,
imageUrl: this.data.goods.images[0]
};
}
})

@ -0,0 +1,51 @@
<view class="detail-container">
<!-- 商品图片轮播 -->
<swiper class="goods-swiper" indicator-dots="{{true}}" autoplay="{{true}}" interval="{{3000}}" duration="{{500}}">
<swiper-item wx:for="{{goods.images}}" wx:key="index">
<image src="{{item}}" class="slide-image" mode="aspectFill" bindtap="previewImage" data-index="{{index}}"/>
</swiper-item>
</swiper>
<!-- 商品基本信息 -->
<view class="goods-info">
<view class="goods-price">¥{{goods.price}}</view>
<view class="goods-title">{{goods.title}}</view>
<view class="goods-meta">
<text class="goods-condition">{{goods.condition}}</text>
<text class="goods-time">发布于 {{goods.publishTime}}</text>
</view>
</view>
<!-- 商品详情 -->
<view class="goods-details">
<view class="section-title">商品详情</view>
<view class="goods-desc">{{goods.description}}</view>
</view>
<!-- 卖家信息 -->
<view class="seller-info">
<view class="section-title">卖家信息</view>
<view class="seller-profile">
<image class="seller-avatar" src="{{goods.seller.avatar}}"></image>
<view class="seller-detail">
<text class="seller-name">{{goods.seller.name}}</text>
<text class="seller-meta">校园认证 · {{goods.seller.verifyType}}</text>
</view>
</view>
</view>
<!-- 底部操作栏 -->
<view class="bottom-bar">
<view class="contact-container">
<button class="contact-btn" open-type="share">
<image class="btn-icon" src="/images/share.png"></image>
<text>分享</text>
</button>
<button class="contact-btn" bindtap="contactSeller">
<image class="btn-icon" src="/images/contact.png"></image>
<text>联系卖家</text>
</button>
</view>
<button class="buy-btn" bindtap="buyNow">立即购买</button>
</view>
</view>

@ -0,0 +1,163 @@
.detail-container {
min-height: 100vh;
background-color: #f7f7f7;
padding-bottom: 120rpx;
}
/* 轮播图样式 */
.goods-swiper {
width: 100%;
height: 750rpx;
}
.slide-image {
width: 100%;
height: 100%;
}
/* 商品信息样式 */
.goods-info {
background-color: #ffffff;
padding: 30rpx;
margin-bottom: 20rpx;
}
.goods-price {
font-size: 44rpx;
color: #FF5252;
font-weight: bold;
margin-bottom: 16rpx;
}
.goods-title {
font-size: 32rpx;
color: #333333;
font-weight: bold;
margin-bottom: 16rpx;
line-height: 1.5;
}
.goods-meta {
display: flex;
justify-content: space-between;
font-size: 24rpx;
color: #999999;
}
/* 商品详情样式 */
.goods-details, .seller-info {
background-color: #ffffff;
padding: 30rpx;
margin-bottom: 20rpx;
}
.section-title {
font-size: 30rpx;
color: #333333;
font-weight: bold;
margin-bottom: 20rpx;
position: relative;
padding-left: 20rpx;
}
.section-title::before {
content: '';
position: absolute;
left: 0;
top: 6rpx;
width: 6rpx;
height: 30rpx;
background-color: #3A86FF;
border-radius: 3rpx;
}
.goods-desc {
font-size: 28rpx;
color: #666666;
line-height: 1.6;
}
/* 卖家信息样式 */
.seller-profile {
display: flex;
align-items: center;
}
.seller-avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
margin-right: 20rpx;
}
.seller-detail {
display: flex;
flex-direction: column;
}
.seller-name {
font-size: 28rpx;
color: #333333;
margin-bottom: 6rpx;
}
.seller-meta {
font-size: 24rpx;
color: #999999;
}
/* 底部操作栏 */
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 100rpx;
display: flex;
background-color: #ffffff;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
width: 100%;
box-sizing: border-box;
padding: 0;
}
.contact-container {
display: flex;
width: 45%; /* 分享和联系卖家占45%宽度 */
}
.contact-btn {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
font-size: 26rpx;
color: #666666;
background-color: #ffffff;
border-radius: 0;
padding: 0;
line-height: normal;
height: 100rpx;
}
.contact-btn::after {
border: none;
}
.btn-icon {
width: 36rpx;
height: 36rpx;
margin-right: 10rpx;
}
.buy-btn {
width: 55%; /* 立即购买占55%宽度 */
display: flex;
align-items: center;
justify-content: center;
font-size: 30rpx;
font-weight: bold;
color: #ffffff;
background-color: #FF5252;
height: 100rpx;
}

@ -0,0 +1,179 @@
Page({
data: {
currentCategory: 'all',
goodsList: [],
pageNum: 1,
pageSize: 10,
hasMoreGoods: true,
isLoading: false
},
onLoad: function (options) {
console.log('二手超市页面加载');
this.loadGoodsList(true);
},
onPullDownRefresh: function () {
console.log('下拉刷新');
this.setData({
pageNum: 1,
hasMoreGoods: true
});
this.loadGoodsList(true);
},
switchCategory: function (e) {
const category = e.currentTarget.dataset.category;
console.log('切换分类:', category);
this.setData({
currentCategory: category,
pageNum: 1,
goodsList: [],
hasMoreGoods: true
});
this.loadGoodsList(true);
},
searchGoods: function (e) {
const keyword = e.detail.value;
if (!keyword.trim()) {
return;
}
console.log('搜索商品:', keyword);
this.setData({
pageNum: 1,
goodsList: [],
hasMoreGoods: true
});
this.loadGoodsList(true, keyword);
},
loadGoodsList: function (refresh, keyword) {
refresh = refresh || false;
keyword = keyword || '';
console.log('加载商品列表, 刷新:', refresh, '关键词:', keyword);
// 模拟商品数据
const mockGoods = [
{
id: 1,
title: 'iPad Pro 2021 二手95新',
price: 4500,
condition: '95新',
images: ['/images/ipad.jpg'],
publishTime: '3小时前',
category: 'electronics'
},
{
id: 2,
title: '微积分教材 同济第七版',
price: 20,
condition: '8成新',
images: ['/images/book.jpg'],
publishTime: '1天前',
category: 'books'
},
{
id: 3,
title: '耐克运动鞋 Air Max 270',
price: 350,
condition: '9成新',
images: ['/images/shoes.jpg'],
publishTime: '2天前',
category: 'clothes'
},
{
id: 4,
title: '宿舍小桌子 可折叠',
price: 50,
condition: '全新',
images: ['/images/table.jpg'],
publishTime: '3天前',
category: 'daily'
},
{
id: 5,
title: '自行车 捷安特 ATX',
price: 800,
condition: '8成新',
images: ['/images/bike.jpg'],
publishTime: '5天前',
category: 'others'
},
{
id: 6,
title: 'AirPods Pro 无线耳机',
price: 850,
condition: '9成新',
images: ['/images/airpods.jpg'],
publishTime: '1周前',
category: 'electronics'
}
];
// 根据分类和关键词筛选商品
var filteredGoods = mockGoods;
var self = this;
if (this.data.currentCategory !== 'all') {
filteredGoods = mockGoods.filter(function(item) {
return item.category === self.data.currentCategory;
});
}
if (keyword) {
filteredGoods = filteredGoods.filter(function(item) {
return item.title.toLowerCase().indexOf(keyword.toLowerCase()) !== -1;
});
}
// 模拟分页
var start = (this.data.pageNum - 1) * this.data.pageSize;
var end = start + this.data.pageSize;
var pageGoods = filteredGoods.slice(start, end);
// 更新数据
if (refresh) {
this.setData({
goodsList: pageGoods,
hasMoreGoods: pageGoods.length === this.data.pageSize,
pageNum: this.data.pageNum + 1
});
wx.stopPullDownRefresh();
} else {
this.setData({
goodsList: this.data.goodsList.concat(pageGoods),
hasMoreGoods: pageGoods.length === this.data.pageSize,
pageNum: this.data.pageNum + 1
});
}
},
loadMore: function () {
console.log('加载更多商品');
if (this.data.hasMoreGoods) {
this.loadGoodsList();
}
},
navigateToDetail: function (e) {
var goodsId = e.currentTarget.dataset.id;
wx.navigateTo({
url: '/pages/secondhand/detail?id=' + goodsId
});
},
navigateToPublish: function () {
wx.navigateTo({
url: '/pages/secondhand/publish'
});
},
// 处理图片加载错误
handleImageError: function() {
console.log('图片加载错误处理已注册');
}
})

@ -0,0 +1,5 @@
{
"enablePullDownRefresh": true,
"backgroundTextStyle": "dark",
"usingComponents": {}
}

@ -0,0 +1,62 @@
<view class="secondhand-container">
<!-- 顶部搜索栏 -->
<view class="search-container">
<view class="search-box">
<!-- 雷雨田2025.8.30添加 -->
<image class="search-icon" src="/images/new_search.png" style="width: 62rpx; display: block; box-sizing: border-box; height: 60rpx"></image>
<input class="search-input" placeholder="搜索二手商品" confirm-type="search" bindconfirm="searchGoods" />
</view>
</view>
<!-- 分类导航 -->
<scroll-view scroll-x class="category-scroll">
<view class="category-list">
<view class="category-item {{currentCategory === 'all' ? 'active' : ''}}" bindtap="switchCategory" data-category="all">
全部
</view>
<view class="category-item {{currentCategory === 'electronics' ? 'active' : ''}}" bindtap="switchCategory" data-category="electronics">
电子产品
</view>
<view class="category-item {{currentCategory === 'books' ? 'active' : ''}}" bindtap="switchCategory" data-category="books">
书籍教材
</view>
<view class="category-item {{currentCategory === 'daily' ? 'active' : ''}}" bindtap="switchCategory" data-category="daily">
日用品
</view>
<view class="category-item {{currentCategory === 'clothes' ? 'active' : ''}}" bindtap="switchCategory" data-category="clothes">
衣物服饰
</view>
<view class="category-item {{currentCategory === 'others' ? 'active' : ''}}" bindtap="switchCategory" data-category="others">
其他
</view>
</view>
</scroll-view>
<!-- 商品列表 -->
<scroll-view scroll-y class="goods-container" bindscrolltolower="loadMore">
<view class="goods-list">
<view class="goods-item" wx:for="{{goodsList}}" wx:key="id" bindtap="navigateToDetail" data-id="{{item.id}}">
<image class="goods-image" src="{{item.images[0]}}" mode="aspectFill"></image>
<view class="goods-info">
<view class="goods-title">{{item.title}}</view>
<view class="goods-price">¥{{item.price}}</view>
<view class="goods-meta">
<text class="goods-time">{{item.publishTime}}</text>
<text class="goods-condition">{{item.condition}}</text>
</view>
</view>
</view>
</view>
<!-- 加载更多 -->
<view class="loading-more" wx:if="{{hasMoreGoods}}">
<text>加载中...</text>
</view>
<view class="no-more" wx:if="{{!hasMoreGoods && goodsList.length > 0}}">
<text>没有更多商品了</text>
</view>
</scroll-view>
<!-- 发布按钮 -->
<view class="publish-btn" bindtap="navigateToPublish" style="height: 118rpx; display: flex; box-sizing: border-box; left: 604rpx; top: 1047rpx; width: 118rpx; position: fixed">+</view>
</view>

@ -0,0 +1,149 @@
.secondhand-container {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f7f7f7;
}
/* 搜索栏样式 */
.search-container {
padding: 20rpx 30rpx;
background-color: #ffffff;
}
.search-box {
display: flex;
align-items: center;
background-color: #f2f2f2;
border-radius: 32rpx;
padding: 10rpx 20rpx;
}
.search-icon {
width: 32rpx;
height: 32rpx;
margin-right: 10rpx;
}
.search-input {
flex: 1;
height: 60rpx;
font-size: 28rpx;
}
/* 分类导航样式 */
.category-scroll {
background-color: #ffffff;
white-space: nowrap;
padding: 0 0 16rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.category-list {
display: inline-flex;
padding: 0 20rpx;
}
.category-item {
display: inline-block;
padding: 12rpx 24rpx;
margin: 0 10rpx;
font-size: 28rpx;
color: #666666;
border-radius: 24rpx;
transition: all 0.3s;
}
.category-item.active {
background-color: #3A86FF;
color: #ffffff;
}
/* 商品列表样式 */
.goods-container {
flex: 1;
padding: 20rpx 30rpx;
box-sizing: border-box;
width: 100%;
}
.goods-list {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
width: 100%;
}
.goods-item {
width: 47%;
background-color: #ffffff;
border-radius: 12rpx;
overflow: hidden;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
box-sizing: border-box;
}
.goods-image {
width: 100%;
height: 320rpx;
object-fit: cover;
}
.goods-info {
padding: 16rpx;
width: 100%;
box-sizing: border-box;
}
.goods-title {
font-size: 28rpx;
color: #333333;
margin-bottom: 10rpx;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
height: 76rpx;
}
.goods-price {
font-size: 32rpx;
color: #FF5252;
font-weight: bold;
margin-bottom: 8rpx;
width: 100%;
overflow: visible;
}
.goods-meta {
display: flex;
justify-content: space-between;
font-size: 22rpx;
color: #999999;
}
.loading-more, .no-more {
text-align: center;
padding: 20rpx 0;
font-size: 24rpx;
color: #999999;
}
/* 发布按钮样式 */
.publish-btn {
position: fixed;
right: 40rpx;
bottom: 120rpx;
width: 100rpx;
height: 100rpx;
background-color: #3A86FF;
color: #ffffff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 60rpx;
box-shadow: 0 4rpx 20rpx rgba(58, 134, 255, 0.3);
}

@ -0,0 +1,214 @@
// pages/secondhand/publish.js
Page({
data: {
images: [],
category: '',
title: '',
price: '',
condition: '',
description: '',
tradeMethod: '',
contact: ''
},
onLoad: function (options) {
// 初始化页面
},
// 选择图片
chooseImage: function () {
const that = this;
wx.chooseImage({
count: 6 - that.data.images.length,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: function (res) {
// 更新图片数组
that.setData({
images: [...that.data.images, ...res.tempFilePaths]
});
}
});
},
// 预览图片
previewImage: function (e) {
const index = e.currentTarget.dataset.index;
wx.previewImage({
current: this.data.images[index],
urls: this.data.images
});
},
// 删除图片
deleteImage: function (e) {
const index = e.currentTarget.dataset.index;
const images = this.data.images;
images.splice(index, 1);
this.setData({
images: images
});
},
// 选择分类
selectCategory: function (e) {
this.setData({
category: e.currentTarget.dataset.category
});
},
// 输入标题
inputTitle: function (e) {
this.setData({
title: e.detail.value
});
},
// 输入价格
inputPrice: function (e) {
this.setData({
price: e.detail.value
});
},
// 选择新旧程度
selectCondition: function (e) {
this.setData({
condition: e.currentTarget.dataset.condition
});
},
// 输入描述
inputDescription: function (e) {
this.setData({
description: e.detail.value
});
},
// 选择交易方式
selectTradeMethod: function (e) {
this.setData({
tradeMethod: e.currentTarget.dataset.method
});
},
// 输入联系方式
inputContact: function (e) {
this.setData({
contact: e.detail.value
});
},
//雷雨田2025.8.30添加
handleSubmit() {
if (!this.data.title) {
wx.showToast({ title: '标题不能为空', icon: 'none' })
return
}
if (!this.data.desc) {
wx.showToast({ title: '描述不能为空', icon: 'none' })
return
}
// 校验通过 -> 调用 showSuccess
wx.showToast({ title: '发布成功', icon: 'success' })
},
// 校验表单
validateForm: function () {
if (this.data.images.length === 0) {
wx.showToast({
title: '请至少上传一张商品图片',
icon: 'none'
});
return false;
}
if (!this.data.category) {
wx.showToast({
title: '请选择商品分类',
icon: 'none'
});
return false;
}
if (!this.data.title) {
wx.showToast({
title: '请输入商品名称',
icon: 'none'
});
return false;
}
if (!this.data.price) {
wx.showToast({
title: '请输入商品价格',
icon: 'none'
});
return false;
}
if (!this.data.condition) {
wx.showToast({
title: '请选择商品新旧程度',
icon: 'none'
});
return false;
}
if (!this.data.description) {
wx.showToast({
title: '请输入商品描述',
icon: 'none'
});
return false;
}
if (!this.data.tradeMethod) {
wx.showToast({
title: '请选择交易方式',
icon: 'none'
});
return false;
}
if (!this.data.contact) {
wx.showToast({
title: '请输入联系方式',
icon: 'none'
});
return false;
}
return true;
},
// 提交发布
submitPublish: function () {
if (!this.validateForm()) return;
// 这里应该调用发布商品的接口
wx.showLoading({
title: '发布中...'
});
// 模拟上传过程
setTimeout(() => {
wx.hideLoading();
wx.showToast({
title: '发布成功',
icon: 'success',
duration: 2000,
success: () => {
// 延迟返回,让用户看到成功提示
setTimeout(() => {
wx.navigateBack();
}, 1500);
}
});
}, 2000);
},
// 取消发布
cancelPublish: function () {
wx.showModal({
title: '确认取消',
content: '确定要放弃发布吗?已填写的内容将不会保存。',
success: (res) => {
if (res.confirm) {
wx.navigateBack();
}
}
});
}
})

@ -0,0 +1,75 @@
<view class="publish-container">
<view class="form-item">
<text class="form-label">商品图片</text>
<view class="image-uploader">
<view class="image-item" wx:for="{{images}}" wx:key="index">
<image class="uploaded-image" src="{{item}}" mode="aspectFill" bindtap="previewImage" data-index="{{index}}"></image>
<view class="delete-btn" catchtap="deleteImage" data-index="{{index}}">×</view>
</view>
<view class="upload-btn" bindtap="chooseImage" wx:if="{{images.length < 6}}">
<text class="upload-icon">+</text>
<text class="upload-text">上传图片</text>
</view>
</view>
</view>
<view class="form-item">
<text class="form-label">商品分类</text>
<view class="category-selector">
<view class="category-item {{category === 'electronics' ? 'active' : ''}}" bindtap="selectCategory" data-category="electronics">电子产品</view>
<view class="category-item {{category === 'books' ? 'active' : ''}}" bindtap="selectCategory" data-category="books">书籍教材</view>
<view class="category-item {{category === 'daily' ? 'active' : ''}}" bindtap="selectCategory" data-category="daily">日用品</view>
<view class="category-item {{category === 'clothes' ? 'active' : ''}}" bindtap="selectCategory" data-category="clothes">衣物服饰</view>
<view class="category-item {{category === 'others' ? 'active' : ''}}" bindtap="selectCategory" data-category="others">其他</view>
</view>
</view>
<view class="form-item">
<text class="form-label">商品名称</text>
<input class="form-input" placeholder="请输入商品名称" maxlength="30" bindinput="inputTitle" value="{{title}}" />
<text class="char-count">{{title.length}}/30</text>
</view>
<view class="form-item">
<text class="form-label">商品价格</text>
<view class="price-input-container">
<text class="price-prefix">¥</text>
<input class="price-input" type="digit" placeholder="请输入价格" bindinput="inputPrice" value="{{price}}" />
</view>
</view>
<view class="form-item">
<text class="form-label">新旧程度</text>
<view class="condition-selector">
<view class="condition-item {{condition === '全新' ? 'active' : ''}}" bindtap="selectCondition" data-condition="全新">全新</view>
<view class="condition-item {{condition === '9成新' ? 'active' : ''}}" bindtap="selectCondition" data-condition="9成新">9成新</view>
<view class="condition-item {{condition === '8成新' ? 'active' : ''}}" bindtap="selectCondition" data-condition="8成新">8成新</view>
<view class="condition-item {{condition === '7成新及以下' ? 'active' : ''}}" bindtap="selectCondition" data-condition="7成新及以下">7成新及以下</view>
</view>
</view>
<view class="form-item">
<text class="form-label">商品描述</text>
<textarea class="form-textarea" placeholder="请详细描述您的商品,如品牌、规格、购买时间、使用感受等" maxlength="500" bindinput="inputDescription" value="{{description}}"></textarea>
<text class="char-count">{{description.length}}/500</text>
</view>
<view class="form-item">
<text class="form-label">交易方式</text>
<view class="trade-selector">
<view class="trade-item {{tradeMethod === 'face' ? 'active' : ''}}" bindtap="selectTradeMethod" data-method="face">面交</view>
<view class="trade-item {{tradeMethod === 'express' ? 'active' : ''}}" bindtap="selectTradeMethod" data-method="express">邮寄</view>
<view class="trade-item {{tradeMethod === 'both' ? 'active' : ''}}" bindtap="selectTradeMethod" data-method="both">均可</view>
</view>
</view>
<view class="form-item contact-info">
<text class="form-label">联系方式</text>
<input class="form-input" placeholder="请输入您的手机号码" bindinput="inputContact" value="{{contact}}" />
</view>
<view class="btn-container">
<button class="cancel-btn" bindtap="cancelPublish">取消</button>
<button class="publish-btn" bindtap="submitPublish">发布</button>
</view>
</view>

@ -0,0 +1,178 @@
.publish-container {
padding: 30rpx;
background-color: #f7f7f7;
min-height: 100vh;
}
.form-item {
margin-bottom: 30rpx;
background-color: #ffffff;
border-radius: 12rpx;
padding: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
position: relative;
}
.form-label {
display: block;
font-size: 28rpx;
color: #333333;
margin-bottom: 15rpx;
font-weight: bold;
}
/* 图片上传区域 */
.image-uploader {
display: flex;
flex-wrap: wrap;
}
.image-item, .upload-btn {
width: 180rpx;
height: 180rpx;
margin-right: 20rpx;
margin-bottom: 20rpx;
position: relative;
}
.uploaded-image {
width: 100%;
height: 100%;
border-radius: 8rpx;
}
.delete-btn {
position: absolute;
top: -20rpx;
right: -20rpx;
width: 40rpx;
height: 40rpx;
border-radius: 50%;
background-color: rgba(0, 0, 0, 0.6);
color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
}
.upload-btn {
border: 1rpx dashed #cccccc;
border-radius: 8rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.upload-icon {
font-size: 60rpx;
color: #cccccc;
line-height: 1;
}
.upload-text {
margin-top: 10rpx;
font-size: 24rpx;
color: #999999;
}
/* 分类选择器 */
.category-selector, .condition-selector, .trade-selector {
display: flex;
flex-wrap: wrap;
}
.category-item, .condition-item, .trade-item {
padding: 10rpx 20rpx;
margin: 0 20rpx 20rpx 0;
border-radius: 8rpx;
font-size: 28rpx;
background-color: #f2f2f2;
color: #666666;
}
.category-item.active, .condition-item.active, .trade-item.active {
background-color: #3A86FF;
color: #ffffff;
}
/* 输入框样式 */
.form-input {
width: 100%;
height: 80rpx;
border: 1rpx solid #eeeeee;
border-radius: 8rpx;
padding: 0 20rpx;
font-size: 28rpx;
color: #333333;
box-sizing: border-box;
}
.char-count {
position: absolute;
right: 20rpx;
bottom: 20rpx;
font-size: 24rpx;
color: #999999;
}
.price-input-container {
display: flex;
align-items: center;
border: 1rpx solid #eeeeee;
border-radius: 8rpx;
padding: 0 20rpx;
height: 80rpx;
}
.price-prefix {
font-size: 32rpx;
color: #FF5252;
margin-right: 10rpx;
font-weight: bold;
}
.price-input {
flex: 1;
height: 80rpx;
font-size: 28rpx;
color: #333333;
}
.form-textarea {
width: 100%;
height: 200rpx;
border: 1rpx solid #eeeeee;
border-radius: 8rpx;
padding: 20rpx;
font-size: 28rpx;
color: #333333;
box-sizing: border-box;
}
/* 按钮区域 */
.btn-container {
display: flex;
justify-content: space-between;
margin-top: 60rpx;
}
.cancel-btn, .publish-btn {
width: 45%;
height: 88rpx;
line-height: 88rpx;
text-align: center;
border-radius: 44rpx;
font-size: 32rpx;
}
.cancel-btn {
background-color: #f2f2f2;
color: #666666;
}
.publish-btn {
background-color: #3A86FF;
color: #ffffff;
}

@ -0,0 +1,14 @@
/// <reference path="./types/index.d.ts" />
interface IAppOption {
globalData: {
userInfo: WechatMiniprogram.UserInfo | null;
openid: string;
isLoggedIn: boolean;
hasUserInfo: boolean;
canIUseGetUserProfile: boolean;
};
userInfoReadyCallback?: WechatMiniprogram.GetUserInfoSuccessCallback;
silentLogin: () => void;
getUserProfile: (callback: (userInfo: WechatMiniprogram.UserInfo) => void) => void;
}

@ -0,0 +1,2 @@
// 微信小程序类型定义
type IAnyObject = Record<string, any>

@ -0,0 +1,19 @@
export const formatTime = (date: Date) => {
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
const hour = date.getHours()
const minute = date.getMinutes()
const second = date.getSeconds()
return (
[year, month, day].map(formatNumber).join('/') +
' ' +
[hour, minute, second].map(formatNumber).join(':')
)
}
const formatNumber = (n: number) => {
const s = n.toString()
return s[1] ? s : '0' + s
}

@ -0,0 +1,11 @@
{
"name": "miniprogram-ts-less-quickstart",
"version": "1.0.0",
"description": "",
"scripts": {
},
"keywords": [],
"author": "",
"license": "",
"dependencies": {}
}

@ -47,7 +47,7 @@
"ignore": [],
"include": []
},
"appid": "wxd7d00921edfeb9df",
"appid": "wx6fb98f37027a7f79",
"cloudfunctionTemplateRoot": "cloudfunctionTemplate/",
"cloudfunctionRoot": "cloudfunctions/"
}

@ -19,5 +19,6 @@
"checkInvalidKey": true,
"ignoreDevUnusedFiles": true
},
"condition": {}
"condition": {},
"description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html"
}

@ -0,0 +1,30 @@
{
"compilerOptions": {
"strictNullChecks": true,
"noImplicitAny": true,
"module": "CommonJS",
"target": "ES2020",
"allowJs": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"noImplicitThis": true,
"noImplicitReturns": true,
"alwaysStrict": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"strict": true,
"strictPropertyInitialization": true,
"lib": ["ES2020"],
"typeRoots": [
"./typings"
]
},
"include": [
"./**/*.ts"
],
"exclude": [
"node_modules"
]
}

@ -0,0 +1,8 @@
/// <reference path="./types/index.d.ts" />
interface IAppOption {
globalData: {
userInfo?: WechatMiniprogram.UserInfo,
}
userInfoReadyCallback?: WechatMiniprogram.GetUserInfoSuccessCallback,
}

@ -0,0 +1 @@
/// <reference path="./wx/index.d.ts" />

@ -0,0 +1,74 @@
/*! *****************************************************************************
Copyright (c) 2021 Tencent, Inc. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
***************************************************************************** */
/// <reference path="./lib.wx.app.d.ts" />
/// <reference path="./lib.wx.page.d.ts" />
/// <reference path="./lib.wx.api.d.ts" />
/// <reference path="./lib.wx.cloud.d.ts" />
/// <reference path="./lib.wx.component.d.ts" />
/// <reference path="./lib.wx.behavior.d.ts" />
/// <reference path="./lib.wx.event.d.ts" />
declare namespace WechatMiniprogram {
type IAnyObject = Record<string, any>
type Optional<F> = F extends (arg: infer P) => infer R ? (arg?: P) => R : F
type OptionalInterface<T> = { [K in keyof T]: Optional<T[K]> }
interface AsyncMethodOptionLike {
success?: (...args: any[]) => void
}
type PromisifySuccessResult<
P,
T extends AsyncMethodOptionLike
> = P extends { success: any }
? void
: P extends { fail: any }
? void
: P extends { complete: any }
? void
: Promise<Parameters<Exclude<T['success'], undefined>>[0]>
}
declare const console: WechatMiniprogram.Console
declare const wx: WechatMiniprogram.Wx
/** 引入模块。返回模块通过 `module.exports` 或 `exports` 暴露的接口。 */
declare function require(
/** 需要引入模块文件相对于当前文件的相对路径,或 npm 模块名,或 npm 模块路径。不支持绝对路径 */
module: string
): any
/** 引入插件。返回插件通过 `main` 暴露的接口。 */
declare function requirePlugin(
/** 需要引入的插件的 alias */
module: string
): any
/** 插件引入当前使用者小程序。返回使用者小程序通过 [插件配置中 `export` 暴露的接口](https://developers.weixin.qq.com/miniprogram/dev/framework/plugin/using.html#%E5%AF%BC%E5%87%BA%E5%88%B0%E6%8F%92%E4%BB%B6)
*
*
*
* `2.11.1` */
declare function requireMiniProgram(): any
/** 当前模块对象 */
declare let module: {
/** 模块向外暴露的对象,使用 `require` 引用该模块时可以获取 */
exports: any
}
/** `module.exports` 的引用 */
declare let exports: any

File diff suppressed because it is too large Load Diff

@ -0,0 +1,270 @@
/*! *****************************************************************************
Copyright (c) 2021 Tencent, Inc. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
***************************************************************************** */
declare namespace WechatMiniprogram.App {
interface ReferrerInfo {
/** App appId
*
* referrerInfo.appId
* - 1020 profile appId
* - 1035 appId
* - 1036App appId
* - 1037 appId
* - 1038 appId
* - 1043 appId
*/
appId: string
/** 来源小程序传过来的数据scene=1037或1038时支持 */
extraData?: any
}
type SceneValues =
| 1001
| 1005
| 1006
| 1007
| 1008
| 1011
| 1012
| 1013
| 1014
| 1017
| 1019
| 1020
| 1023
| 1024
| 1025
| 1026
| 1027
| 1028
| 1029
| 1030
| 1031
| 1032
| 1034
| 1035
| 1036
| 1037
| 1038
| 1039
| 1042
| 1043
| 1044
| 1045
| 1046
| 1047
| 1048
| 1049
| 1052
| 1053
| 1056
| 1057
| 1058
| 1059
| 1064
| 1067
| 1069
| 1071
| 1072
| 1073
| 1074
| 1077
| 1078
| 1079
| 1081
| 1082
| 1084
| 1089
| 1090
| 1091
| 1092
| 1095
| 1096
| 1097
| 1099
| 1102
| 1124
| 1125
| 1126
| 1129
interface LaunchShowOption {
/** 打开小程序的路径 */
path: string
/** 打开小程序的query */
query: IAnyObject
/**
* - 1001使2.2.4
* - 1005
* - 1006
* - 1007
* - 1008
* - 1011
* - 1012
* - 1013
* - 1014
* - 1017
* - 10197.0.0
* - 1020 profile
* - 1023
* - 1024 profile
* - 1025
* - 1026
* - 1027使
* - 1028
* - 1029
* - 1030
* - 1031
* - 1032
* - 1034
* - 1035
* - 1036App
* - 1037
* - 1038
* - 1039
* - 1042
* - 1043
* - 1044 shareTicket [](https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/share.html)
* - 1045广
* - 1046广
* - 1047
* - 1048
* - 1049
* - 1052
* - 1053
* - 1056
* - 1057
* - 1058
* - 1059
* - 1064Wi-Fi
* - 1067广
* - 1069
* - 1071
* - 1072
* - 1073
* - 1074
* - 1077
* - 1078Wi-Fi
* - 1079
* - 1081
* - 1082
* - 1084广
* - 1089使2.2.4
* - 1090使
* - 1091
* - 1092
* - 1095广
* - 1096
* - 1097
* - 1099
* - 1102 profile
* - 1124
* - 1125
* - 1126
* - 1129访 [](https://developers.weixin.qq.com/miniprogram/dev/reference/configuration/sitemap.html)
*/
scene: SceneValues
/** shareTicket详见 [获取更多转发信息]((转发#获取更多转发信息)) */
shareTicket: string
/** 当场景为由从另一个小程序或公众号或App打开时返回此字段 */
referrerInfo?: ReferrerInfo
}
interface PageNotFoundOption {
/** 不存在页面的路径 */
path: string
/** 打开不存在页面的 query */
query: IAnyObject
/** 是否本次启动的首个页面(例如从分享等入口进来,首个页面是开发者配置的分享页面) */
isEntryPage: boolean
}
interface Option {
/**
*
*
*/
onLaunch(options: LaunchShowOption): void
/**
*
*
*/
onShow(options: LaunchShowOption): void
/**
*
*
*/
onHide(): void
/**
*
* api
*/
onError(/** 错误信息,包含堆栈 */ error: string): void
/**
*
*
*
* ****
* 1. `onPageNotFound`
* 2. `onPageNotFound` `onPageNotFound`
*
* 1.9.90
*/
onPageNotFound(options: PageNotFoundOption): void
/**
* Promise 使 [wx.onUnhandledRejection](https://developers.weixin.qq.com/miniprogram/dev/api/base/app/app-event/wx.onUnhandledRejection.html) 绑定监听。注意事项请参考 [wx.onUnhandledRejection](https://developers.weixin.qq.com/miniprogram/dev/api/base/app/app-event/wx.onUnhandledRejection.html)。
* **** [wx.onUnhandledRejection](https://developers.weixin.qq.com/miniprogram/dev/api/base/app/app-event/wx.onUnhandledRejection.html) 一致
*/
onUnhandledRejection: OnUnhandledRejectionCallback
/**
* 使 wx.onThemeChange
*
* 2.11.0
*/
onThemeChange: OnThemeChangeCallback
}
type Instance<T extends IAnyObject> = Option & T
type Options<T extends IAnyObject> = Partial<Option> &
T &
ThisType<Instance<T>>
type TrivialInstance = Instance<IAnyObject>
interface Constructor {
<T extends IAnyObject>(options: Options<T>): void
}
interface GetAppOption {
/** `App` AppApp
*
* 2.2.4
*/
allowDefault?: boolean
}
interface GetApp {
<T = IAnyObject>(opts?: GetAppOption): Instance<T>
}
}
declare let App: WechatMiniprogram.App.Constructor
declare let getApp: WechatMiniprogram.App.GetApp

@ -0,0 +1,68 @@
/*! *****************************************************************************
Copyright (c) 2021 Tencent, Inc. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
***************************************************************************** */
declare namespace WechatMiniprogram.Behavior {
type BehaviorIdentifier = string
type Instance<
TData extends DataOption,
TProperty extends PropertyOption,
TMethod extends MethodOption,
TCustomInstanceProperty extends IAnyObject = Record<string, never>
> = Component.Instance<TData, TProperty, TMethod, TCustomInstanceProperty>
type TrivialInstance = Instance<IAnyObject, IAnyObject, IAnyObject>
type TrivialOption = Options<IAnyObject, IAnyObject, IAnyObject>
type Options<
TData extends DataOption,
TProperty extends PropertyOption,
TMethod extends MethodOption,
TCustomInstanceProperty extends IAnyObject = Record<string, never>
> = Partial<Data<TData>> &
Partial<Property<TProperty>> &
Partial<Method<TMethod>> &
Partial<OtherOption> &
Partial<Lifetimes> &
ThisType<Instance<TData, TProperty, TMethod, TCustomInstanceProperty>>
interface Constructor {
<
TData extends DataOption,
TProperty extends PropertyOption,
TMethod extends MethodOption,
TCustomInstanceProperty extends IAnyObject = Record<string, never>
>(
options: Options<TData, TProperty, TMethod, TCustomInstanceProperty>
): BehaviorIdentifier
}
type DataOption = Component.DataOption
type PropertyOption = Component.PropertyOption
type MethodOption = Component.MethodOption
type Data<D extends DataOption> = Component.Data<D>
type Property<P extends PropertyOption> = Component.Property<P>
type Method<M extends MethodOption> = Component.Method<M>
type DefinitionFilter = Component.DefinitionFilter
type Lifetimes = Component.Lifetimes
type OtherOption = Omit<Component.OtherOption, 'options'>
}
/** 注册一个 `behavior`,接受一个 `Object` 类型的参数。*/
declare let Behavior: WechatMiniprogram.Behavior.Constructor

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save