合并请求 #4

Merged
pnju9rpvk merged 1 commits from rkm into main 6 months 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: 'cloud1-4gczmwok7cacf142' // 使用您的云环境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: 'cloud1-5gqeisa06659849a' // 使用您的云环境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: 'cloud1-5gqeisa06659849a' // 使用您的云环境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: 'cloud1-5gqeisa06659849a' // 使用您的云环境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: 'cloud1-5gqeisa06659849a'
});
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,140 @@
// 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) {
wx.cloud.init({
env: 'cloud1-5gqeisa06659849a', // 云开发环境ID
traceUser: true // 是否将用户访问记录到用户管理中,用于多用户访问的场景
});
console.log('云开发初始化成功');
// 静默获取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>

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,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">
<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,192 @@
// 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();
}
},
// 下拉刷新
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,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,61 @@
<view class="secondhand-container">
<!-- 顶部搜索栏 -->
<view class="search-container">
<view class="search-box">
<image class="search-icon" src="/images/search.png"></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">+</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,201 @@
// 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
});
},
// 校验表单
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": "wx5c58a3560c248707",
"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

@ -0,0 +1,924 @@
/*! *****************************************************************************
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.
***************************************************************************** */
interface IAPIError {
errMsg: string
}
interface IAPIParam<T = any> {
config?: ICloudConfig
success?: (res: T) => void
fail?: (err: IAPIError) => void
complete?: (val: T | IAPIError) => void
}
interface IAPISuccessParam {
errMsg: string
}
type IAPICompleteParam = IAPISuccessParam | IAPIError
type IAPIFunction<T, P extends IAPIParam<T>> = (param?: P) => Promise<T>
interface IInitCloudConfig {
env?:
| string
| {
database?: string
functions?: string
storage?: string
}
traceUser?: boolean
}
interface ICloudConfig {
env?: string
traceUser?: boolean
}
interface IICloudAPI {
init: (config?: IInitCloudConfig) => void
[api: string]: AnyFunction | IAPIFunction<any, any>
}
interface ICloudService {
name: string
getAPIs: () => { [name: string]: IAPIFunction<any, any> }
}
interface ICloudServices {
[serviceName: string]: ICloudService
}
interface ICloudMetaData {
session_id: string
}
declare class InternalSymbol {}
interface AnyObject {
[x: string]: any
}
type AnyArray = any[]
type AnyFunction = (...args: any[]) => any
/**
* extend wx with cloud
*/
interface WxCloud {
init: (config?: ICloudConfig) => void
callFunction(param: OQ<ICloud.CallFunctionParam>): void
callFunction(
param: RQ<ICloud.CallFunctionParam>
): Promise<ICloud.CallFunctionResult>
uploadFile(param: OQ<ICloud.UploadFileParam>): WechatMiniprogram.UploadTask
uploadFile(
param: RQ<ICloud.UploadFileParam>
): Promise<ICloud.UploadFileResult>
downloadFile(
param: OQ<ICloud.DownloadFileParam>
): WechatMiniprogram.DownloadTask
downloadFile(
param: RQ<ICloud.DownloadFileParam>
): Promise<ICloud.DownloadFileResult>
getTempFileURL(param: OQ<ICloud.GetTempFileURLParam>): void
getTempFileURL(
param: RQ<ICloud.GetTempFileURLParam>
): Promise<ICloud.GetTempFileURLResult>
deleteFile(param: OQ<ICloud.DeleteFileParam>): void
deleteFile(
param: RQ<ICloud.DeleteFileParam>
): Promise<ICloud.DeleteFileResult>
database: (config?: ICloudConfig) => DB.Database
CloudID: ICloud.ICloudIDConstructor
CDN: ICloud.ICDNConstructor
}
declare namespace ICloud {
interface ICloudAPIParam<T = any> extends IAPIParam<T> {
config?: ICloudConfig
}
// === API: callFunction ===
type CallFunctionData = AnyObject
interface CallFunctionResult extends IAPISuccessParam {
result: AnyObject | string | undefined
}
interface CallFunctionParam extends ICloudAPIParam<CallFunctionResult> {
name: string
data?: CallFunctionData
slow?: boolean
}
// === end ===
// === API: uploadFile ===
interface UploadFileResult extends IAPISuccessParam {
fileID: string
statusCode: number
}
interface UploadFileParam extends ICloudAPIParam<UploadFileResult> {
cloudPath: string
filePath: string
header?: AnyObject
}
// === end ===
// === API: downloadFile ===
interface DownloadFileResult extends IAPISuccessParam {
tempFilePath: string
statusCode: number
}
interface DownloadFileParam extends ICloudAPIParam<DownloadFileResult> {
fileID: string
cloudPath?: string
}
// === end ===
// === API: getTempFileURL ===
interface GetTempFileURLResult extends IAPISuccessParam {
fileList: GetTempFileURLResultItem[]
}
interface GetTempFileURLResultItem {
fileID: string
tempFileURL: string
maxAge: number
status: number
errMsg: string
}
interface GetTempFileURLParam extends ICloudAPIParam<GetTempFileURLResult> {
fileList: string[]
}
// === end ===
// === API: deleteFile ===
interface DeleteFileResult extends IAPISuccessParam {
fileList: DeleteFileResultItem[]
}
interface DeleteFileResultItem {
fileID: string
status: number
errMsg: string
}
interface DeleteFileParam extends ICloudAPIParam<DeleteFileResult> {
fileList: string[]
}
// === end ===
// === API: CloudID ===
abstract class CloudID {
constructor(cloudID: string)
}
interface ICloudIDConstructor {
new (cloudId: string): CloudID
(cloudId: string): CloudID
}
// === end ===
// === API: CDN ===
abstract class CDN {
target: string | ArrayBuffer | ICDNFilePathSpec
constructor(target: string | ArrayBuffer | ICDNFilePathSpec)
}
interface ICDNFilePathSpec {
type: 'filePath'
filePath: string
}
interface ICDNConstructor {
new (options: string | ArrayBuffer | ICDNFilePathSpec): CDN
(options: string | ArrayBuffer | ICDNFilePathSpec): CDN
}
// === end ===
}
// === Database ===
declare namespace DB {
/**
* The class of all exposed cloud database instances
*/
class Database {
readonly config: ICloudConfig
readonly command: DatabaseCommand
readonly Geo: IGeo
readonly serverDate: () => ServerDate
readonly RegExp: IRegExpConstructor
private constructor()
collection(collectionName: string): CollectionReference
}
class CollectionReference extends Query {
readonly collectionName: string
private constructor(name: string, database: Database)
doc(docId: string | number): DocumentReference
add(options: OQ<IAddDocumentOptions>): void
add(options: RQ<IAddDocumentOptions>): Promise<IAddResult>
}
class DocumentReference {
private constructor(docId: string | number, database: Database)
field(object: Record<string, any>): this
get(options: OQ<IGetDocumentOptions>): void
get(options?: RQ<IGetDocumentOptions>): Promise<IQuerySingleResult>
set(options: OQ<ISetSingleDocumentOptions>): void
set(options?: RQ<ISetSingleDocumentOptions>): Promise<ISetResult>
update(options: OQ<IUpdateSingleDocumentOptions>): void
update(
options?: RQ<IUpdateSingleDocumentOptions>
): Promise<IUpdateResult>
remove(options: OQ<IRemoveSingleDocumentOptions>): void
remove(
options?: RQ<IRemoveSingleDocumentOptions>
): Promise<IRemoveResult>
watch(options: IWatchOptions): RealtimeListener
}
class RealtimeListener {
// "And Now His Watch Is Ended"
close: () => Promise<void>
}
class Query {
where(condition: IQueryCondition): Query
orderBy(fieldPath: string, order: string): Query
limit(max: number): Query
skip(offset: number): Query
field(object: Record<string, any>): Query
get(options: OQ<IGetDocumentOptions>): void
get(options?: RQ<IGetDocumentOptions>): Promise<IQueryResult>
count(options: OQ<ICountDocumentOptions>): void
count(options?: RQ<ICountDocumentOptions>): Promise<ICountResult>
watch(options: IWatchOptions): RealtimeListener
}
interface DatabaseCommand {
eq(val: any): DatabaseQueryCommand
neq(val: any): DatabaseQueryCommand
gt(val: any): DatabaseQueryCommand
gte(val: any): DatabaseQueryCommand
lt(val: any): DatabaseQueryCommand
lte(val: any): DatabaseQueryCommand
in(val: any[]): DatabaseQueryCommand
nin(val: any[]): DatabaseQueryCommand
geoNear(options: IGeoNearCommandOptions): DatabaseQueryCommand
geoWithin(options: IGeoWithinCommandOptions): DatabaseQueryCommand
geoIntersects(
options: IGeoIntersectsCommandOptions
): DatabaseQueryCommand
and(
...expressions: Array<DatabaseLogicCommand | IQueryCondition>
): DatabaseLogicCommand
or(
...expressions: Array<DatabaseLogicCommand | IQueryCondition>
): DatabaseLogicCommand
nor(
...expressions: Array<DatabaseLogicCommand | IQueryCondition>
): DatabaseLogicCommand
not(expression: DatabaseLogicCommand): DatabaseLogicCommand
exists(val: boolean): DatabaseQueryCommand
mod(divisor: number, remainder: number): DatabaseQueryCommand
all(val: any[]): DatabaseQueryCommand
elemMatch(val: any): DatabaseQueryCommand
size(val: number): DatabaseQueryCommand
set(val: any): DatabaseUpdateCommand
remove(): DatabaseUpdateCommand
inc(val: number): DatabaseUpdateCommand
mul(val: number): DatabaseUpdateCommand
min(val: number): DatabaseUpdateCommand
max(val: number): DatabaseUpdateCommand
rename(val: string): DatabaseUpdateCommand
bit(val: number): DatabaseUpdateCommand
push(...values: any[]): DatabaseUpdateCommand
pop(): DatabaseUpdateCommand
shift(): DatabaseUpdateCommand
unshift(...values: any[]): DatabaseUpdateCommand
addToSet(val: any): DatabaseUpdateCommand
pull(val: any): DatabaseUpdateCommand
pullAll(val: any): DatabaseUpdateCommand
project: {
slice(val: number | [number, number]): DatabaseProjectionCommand
}
aggregate: {
__safe_props__?: Set<string>
abs(val: any): DatabaseAggregateCommand
add(val: any): DatabaseAggregateCommand
addToSet(val: any): DatabaseAggregateCommand
allElementsTrue(val: any): DatabaseAggregateCommand
and(val: any): DatabaseAggregateCommand
anyElementTrue(val: any): DatabaseAggregateCommand
arrayElemAt(val: any): DatabaseAggregateCommand
arrayToObject(val: any): DatabaseAggregateCommand
avg(val: any): DatabaseAggregateCommand
ceil(val: any): DatabaseAggregateCommand
cmp(val: any): DatabaseAggregateCommand
concat(val: any): DatabaseAggregateCommand
concatArrays(val: any): DatabaseAggregateCommand
cond(val: any): DatabaseAggregateCommand
convert(val: any): DatabaseAggregateCommand
dateFromParts(val: any): DatabaseAggregateCommand
dateToParts(val: any): DatabaseAggregateCommand
dateFromString(val: any): DatabaseAggregateCommand
dateToString(val: any): DatabaseAggregateCommand
dayOfMonth(val: any): DatabaseAggregateCommand
dayOfWeek(val: any): DatabaseAggregateCommand
dayOfYear(val: any): DatabaseAggregateCommand
divide(val: any): DatabaseAggregateCommand
eq(val: any): DatabaseAggregateCommand
exp(val: any): DatabaseAggregateCommand
filter(val: any): DatabaseAggregateCommand
first(val: any): DatabaseAggregateCommand
floor(val: any): DatabaseAggregateCommand
gt(val: any): DatabaseAggregateCommand
gte(val: any): DatabaseAggregateCommand
hour(val: any): DatabaseAggregateCommand
ifNull(val: any): DatabaseAggregateCommand
in(val: any): DatabaseAggregateCommand
indexOfArray(val: any): DatabaseAggregateCommand
indexOfBytes(val: any): DatabaseAggregateCommand
indexOfCP(val: any): DatabaseAggregateCommand
isArray(val: any): DatabaseAggregateCommand
isoDayOfWeek(val: any): DatabaseAggregateCommand
isoWeek(val: any): DatabaseAggregateCommand
isoWeekYear(val: any): DatabaseAggregateCommand
last(val: any): DatabaseAggregateCommand
let(val: any): DatabaseAggregateCommand
literal(val: any): DatabaseAggregateCommand
ln(val: any): DatabaseAggregateCommand
log(val: any): DatabaseAggregateCommand
log10(val: any): DatabaseAggregateCommand
lt(val: any): DatabaseAggregateCommand
lte(val: any): DatabaseAggregateCommand
ltrim(val: any): DatabaseAggregateCommand
map(val: any): DatabaseAggregateCommand
max(val: any): DatabaseAggregateCommand
mergeObjects(val: any): DatabaseAggregateCommand
meta(val: any): DatabaseAggregateCommand
min(val: any): DatabaseAggregateCommand
millisecond(val: any): DatabaseAggregateCommand
minute(val: any): DatabaseAggregateCommand
mod(val: any): DatabaseAggregateCommand
month(val: any): DatabaseAggregateCommand
multiply(val: any): DatabaseAggregateCommand
neq(val: any): DatabaseAggregateCommand
not(val: any): DatabaseAggregateCommand
objectToArray(val: any): DatabaseAggregateCommand
or(val: any): DatabaseAggregateCommand
pow(val: any): DatabaseAggregateCommand
push(val: any): DatabaseAggregateCommand
range(val: any): DatabaseAggregateCommand
reduce(val: any): DatabaseAggregateCommand
reverseArray(val: any): DatabaseAggregateCommand
rtrim(val: any): DatabaseAggregateCommand
second(val: any): DatabaseAggregateCommand
setDifference(val: any): DatabaseAggregateCommand
setEquals(val: any): DatabaseAggregateCommand
setIntersection(val: any): DatabaseAggregateCommand
setIsSubset(val: any): DatabaseAggregateCommand
setUnion(val: any): DatabaseAggregateCommand
size(val: any): DatabaseAggregateCommand
slice(val: any): DatabaseAggregateCommand
split(val: any): DatabaseAggregateCommand
sqrt(val: any): DatabaseAggregateCommand
stdDevPop(val: any): DatabaseAggregateCommand
stdDevSamp(val: any): DatabaseAggregateCommand
strcasecmp(val: any): DatabaseAggregateCommand
strLenBytes(val: any): DatabaseAggregateCommand
strLenCP(val: any): DatabaseAggregateCommand
substr(val: any): DatabaseAggregateCommand
substrBytes(val: any): DatabaseAggregateCommand
substrCP(val: any): DatabaseAggregateCommand
subtract(val: any): DatabaseAggregateCommand
sum(val: any): DatabaseAggregateCommand
switch(val: any): DatabaseAggregateCommand
toBool(val: any): DatabaseAggregateCommand
toDate(val: any): DatabaseAggregateCommand
toDecimal(val: any): DatabaseAggregateCommand
toDouble(val: any): DatabaseAggregateCommand
toInt(val: any): DatabaseAggregateCommand
toLong(val: any): DatabaseAggregateCommand
toObjectId(val: any): DatabaseAggregateCommand
toString(val: any): DatabaseAggregateCommand
toLower(val: any): DatabaseAggregateCommand
toUpper(val: any): DatabaseAggregateCommand
trim(val: any): DatabaseAggregateCommand
trunc(val: any): DatabaseAggregateCommand
type(val: any): DatabaseAggregateCommand
week(val: any): DatabaseAggregateCommand
year(val: any): DatabaseAggregateCommand
zip(val: any): DatabaseAggregateCommand
}
}
class DatabaseAggregateCommand {}
enum LOGIC_COMMANDS_LITERAL {
AND = 'and',
OR = 'or',
NOT = 'not',
NOR = 'nor'
}
class DatabaseLogicCommand {
and(...expressions: DatabaseLogicCommand[]): DatabaseLogicCommand
or(...expressions: DatabaseLogicCommand[]): DatabaseLogicCommand
nor(...expressions: DatabaseLogicCommand[]): DatabaseLogicCommand
not(expression: DatabaseLogicCommand): DatabaseLogicCommand
}
enum QUERY_COMMANDS_LITERAL {
// comparison
EQ = 'eq',
NEQ = 'neq',
GT = 'gt',
GTE = 'gte',
LT = 'lt',
LTE = 'lte',
IN = 'in',
NIN = 'nin',
// geo
GEO_NEAR = 'geoNear',
GEO_WITHIN = 'geoWithin',
GEO_INTERSECTS = 'geoIntersects',
// element
EXISTS = 'exists',
// evaluation
MOD = 'mod',
// array
ALL = 'all',
ELEM_MATCH = 'elemMatch',
SIZE = 'size'
}
class DatabaseQueryCommand extends DatabaseLogicCommand {
eq(val: any): DatabaseLogicCommand
neq(val: any): DatabaseLogicCommand
gt(val: any): DatabaseLogicCommand
gte(val: any): DatabaseLogicCommand
lt(val: any): DatabaseLogicCommand
lte(val: any): DatabaseLogicCommand
in(val: any[]): DatabaseLogicCommand
nin(val: any[]): DatabaseLogicCommand
exists(val: boolean): DatabaseLogicCommand
mod(divisor: number, remainder: number): DatabaseLogicCommand
all(val: any[]): DatabaseLogicCommand
elemMatch(val: any): DatabaseLogicCommand
size(val: number): DatabaseLogicCommand
geoNear(options: IGeoNearCommandOptions): DatabaseLogicCommand
geoWithin(options: IGeoWithinCommandOptions): DatabaseLogicCommand
geoIntersects(
options: IGeoIntersectsCommandOptions
): DatabaseLogicCommand
}
enum PROJECTION_COMMANDS_LITERAL {
SLICE = 'slice'
}
class DatabaseProjectionCommand {}
enum UPDATE_COMMANDS_LITERAL {
// field
SET = 'set',
REMOVE = 'remove',
INC = 'inc',
MUL = 'mul',
MIN = 'min',
MAX = 'max',
RENAME = 'rename',
// bitwise
BIT = 'bit',
// array
PUSH = 'push',
POP = 'pop',
SHIFT = 'shift',
UNSHIFT = 'unshift',
ADD_TO_SET = 'addToSet',
PULL = 'pull',
PULL_ALL = 'pullAll'
}
class DatabaseUpdateCommand {}
class Batch {}
/**
* A contract that all API provider must adhere to
*/
class APIBaseContract<
PromiseReturn,
CallbackReturn,
Param extends IAPIParam,
Context = any
> {
getContext(param: Param): Context
/**
* In case of callback-style invocation, this function will be called
*/
getCallbackReturn(param: Param, context: Context): CallbackReturn
getFinalParam<T extends Param>(param: Param, context: Context): T
run<T extends Param>(param: T): Promise<PromiseReturn>
}
interface IGeoPointConstructor {
new (longitude: number, latitide: number): GeoPoint
new (geojson: IGeoJSONPoint): GeoPoint
(longitude: number, latitide: number): GeoPoint
(geojson: IGeoJSONPoint): GeoPoint
}
interface IGeoMultiPointConstructor {
new (points: GeoPoint[] | IGeoJSONMultiPoint): GeoMultiPoint
(points: GeoPoint[] | IGeoJSONMultiPoint): GeoMultiPoint
}
interface IGeoLineStringConstructor {
new (points: GeoPoint[] | IGeoJSONLineString): GeoLineString
(points: GeoPoint[] | IGeoJSONLineString): GeoLineString
}
interface IGeoMultiLineStringConstructor {
new (
lineStrings: GeoLineString[] | IGeoJSONMultiLineString
): GeoMultiLineString
(
lineStrings: GeoLineString[] | IGeoJSONMultiLineString
): GeoMultiLineString
}
interface IGeoPolygonConstructor {
new (lineStrings: GeoLineString[] | IGeoJSONPolygon): GeoPolygon
(lineStrings: GeoLineString[] | IGeoJSONPolygon): GeoPolygon
}
interface IGeoMultiPolygonConstructor {
new (polygons: GeoPolygon[] | IGeoJSONMultiPolygon): GeoMultiPolygon
(polygons: GeoPolygon[] | IGeoJSONMultiPolygon): GeoMultiPolygon
}
interface IGeo {
Point: IGeoPointConstructor
MultiPoint: IGeoMultiPointConstructor
LineString: IGeoLineStringConstructor
MultiLineString: IGeoMultiLineStringConstructor
Polygon: IGeoPolygonConstructor
MultiPolygon: IGeoMultiPolygonConstructor
}
interface IGeoJSONPoint {
type: 'Point'
coordinates: [number, number]
}
interface IGeoJSONMultiPoint {
type: 'MultiPoint'
coordinates: Array<[number, number]>
}
interface IGeoJSONLineString {
type: 'LineString'
coordinates: Array<[number, number]>
}
interface IGeoJSONMultiLineString {
type: 'MultiLineString'
coordinates: Array<Array<[number, number]>>
}
interface IGeoJSONPolygon {
type: 'Polygon'
coordinates: Array<Array<[number, number]>>
}
interface IGeoJSONMultiPolygon {
type: 'MultiPolygon'
coordinates: Array<Array<Array<[number, number]>>>
}
type IGeoJSONObject =
| IGeoJSONPoint
| IGeoJSONMultiPoint
| IGeoJSONLineString
| IGeoJSONMultiLineString
| IGeoJSONPolygon
| IGeoJSONMultiPolygon
abstract class GeoPoint {
longitude: number
latitude: number
constructor(longitude: number, latitude: number)
toJSON(): Record<string, any>
toString(): string
}
abstract class GeoMultiPoint {
points: GeoPoint[]
constructor(points: GeoPoint[])
toJSON(): IGeoJSONMultiPoint
toString(): string
}
abstract class GeoLineString {
points: GeoPoint[]
constructor(points: GeoPoint[])
toJSON(): IGeoJSONLineString
toString(): string
}
abstract class GeoMultiLineString {
lines: GeoLineString[]
constructor(lines: GeoLineString[])
toJSON(): IGeoJSONMultiLineString
toString(): string
}
abstract class GeoPolygon {
lines: GeoLineString[]
constructor(lines: GeoLineString[])
toJSON(): IGeoJSONPolygon
toString(): string
}
abstract class GeoMultiPolygon {
polygons: GeoPolygon[]
constructor(polygons: GeoPolygon[])
toJSON(): IGeoJSONMultiPolygon
toString(): string
}
type GeoInstance =
| GeoPoint
| GeoMultiPoint
| GeoLineString
| GeoMultiLineString
| GeoPolygon
| GeoMultiPolygon
interface IGeoNearCommandOptions {
geometry: GeoPoint
maxDistance?: number
minDistance?: number
}
interface IGeoWithinCommandOptions {
geometry: GeoPolygon | GeoMultiPolygon
}
interface IGeoIntersectsCommandOptions {
geometry:
| GeoPoint
| GeoMultiPoint
| GeoLineString
| GeoMultiLineString
| GeoPolygon
| GeoMultiPolygon
}
interface IServerDateOptions {
offset: number
}
abstract class ServerDate {
readonly options: IServerDateOptions
constructor(options?: IServerDateOptions)
}
interface IRegExpOptions {
regexp: string
options?: string
}
interface IRegExpConstructor {
new (options: IRegExpOptions): RegExp
(options: IRegExpOptions): RegExp
}
abstract class RegExp {
readonly regexp: string
readonly options: string
constructor(options: IRegExpOptions)
}
type DocumentId = string | number
interface IDocumentData {
_id?: DocumentId
[key: string]: any
}
type IDBAPIParam = IAPIParam
interface IAddDocumentOptions extends IDBAPIParam {
data: IDocumentData
}
type IGetDocumentOptions = IDBAPIParam
type ICountDocumentOptions = IDBAPIParam
interface IUpdateDocumentOptions extends IDBAPIParam {
data: IUpdateCondition
}
interface IUpdateSingleDocumentOptions extends IDBAPIParam {
data: IUpdateCondition
}
interface ISetDocumentOptions extends IDBAPIParam {
data: IUpdateCondition
}
interface ISetSingleDocumentOptions extends IDBAPIParam {
data: IUpdateCondition
}
interface IRemoveDocumentOptions extends IDBAPIParam {
query: IQueryCondition
}
type IRemoveSingleDocumentOptions = IDBAPIParam
interface IWatchOptions {
// server realtime data init & change event
onChange: (snapshot: ISnapshot) => void
// error while connecting / listening
onError: (error: any) => void
}
interface ISnapshot {
id: number
docChanges: ISingleDBEvent[]
docs: Record<string, any>
type?: SnapshotType
}
type SnapshotType = 'init'
interface ISingleDBEvent {
id: number
dataType: DataType
queueType: QueueType
docId: string
doc: Record<string, any>
updatedFields?: Record<string, any>
removedFields?: string[]
}
type DataType = 'init' | 'update' | 'replace' | 'add' | 'remove' | 'limit'
type QueueType = 'init' | 'enqueue' | 'dequeue' | 'update'
interface IQueryCondition {
[key: string]: any
}
type IStringQueryCondition = string
interface IQueryResult extends IAPISuccessParam {
data: IDocumentData[]
}
interface IQuerySingleResult extends IAPISuccessParam {
data: IDocumentData
}
interface IUpdateCondition {
[key: string]: any
}
type IStringUpdateCondition = string
interface IAddResult extends IAPISuccessParam {
_id: DocumentId
}
interface IUpdateResult extends IAPISuccessParam {
stats: {
updated: number
// created: number,
}
}
interface ISetResult extends IAPISuccessParam {
_id: DocumentId
stats: {
updated: number
created: number
}
}
interface IRemoveResult extends IAPISuccessParam {
stats: {
removed: number
}
}
interface ICountResult extends IAPISuccessParam {
total: number
}
}
type Optional<T> = { [K in keyof T]+?: T[K] }
type OQ<
T extends Optional<
Record<'complete' | 'success' | 'fail', (...args: any[]) => any>
>
> =
| (RQ<T> & Required<Pick<T, 'success'>>)
| (RQ<T> & Required<Pick<T, 'fail'>>)
| (RQ<T> & Required<Pick<T, 'complete'>>)
| (RQ<T> & Required<Pick<T, 'success' | 'fail'>>)
| (RQ<T> & Required<Pick<T, 'success' | 'complete'>>)
| (RQ<T> & Required<Pick<T, 'fail' | 'complete'>>)
| (RQ<T> & Required<Pick<T, 'fail' | 'complete' | 'success'>>)
type RQ<
T extends Optional<
Record<'complete' | 'success' | 'fail', (...args: any[]) => any>
>
> = Pick<T, Exclude<keyof T, 'complete' | 'success' | 'fail'>>

@ -0,0 +1,636 @@
/*! *****************************************************************************
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.Component {
type Instance<
TData extends DataOption,
TProperty extends PropertyOption,
TMethod extends Partial<MethodOption>,
TCustomInstanceProperty extends IAnyObject = {},
TIsPage extends boolean = false
> = InstanceProperties &
InstanceMethods<TData> &
TMethod &
(TIsPage extends true ? Page.ILifetime : {}) &
TCustomInstanceProperty & {
/** 组件数据,**包括内部数据和属性值** */
data: TData & PropertyOptionToData<TProperty>
/** 组件数据,**包括内部数据和属性值**(与 `data` 一致) */
properties: TData & PropertyOptionToData<TProperty>
}
type TrivialInstance = Instance<
IAnyObject,
IAnyObject,
IAnyObject,
IAnyObject
>
type TrivialOption = Options<IAnyObject, IAnyObject, IAnyObject, IAnyObject>
type Options<
TData extends DataOption,
TProperty extends PropertyOption,
TMethod extends MethodOption,
TCustomInstanceProperty extends IAnyObject = {},
TIsPage extends boolean = false
> = Partial<Data<TData>> &
Partial<Property<TProperty>> &
Partial<Method<TMethod, TIsPage>> &
Partial<OtherOption> &
Partial<Lifetimes> &
ThisType<
Instance<
TData,
TProperty,
TMethod,
TCustomInstanceProperty,
TIsPage
>
>
interface Constructor {
<
TData extends DataOption,
TProperty extends PropertyOption,
TMethod extends MethodOption,
TCustomInstanceProperty extends IAnyObject = {},
TIsPage extends boolean = false
>(
options: Options<
TData,
TProperty,
TMethod,
TCustomInstanceProperty,
TIsPage
>
): string
}
type DataOption = Record<string, any>
type PropertyOption = Record<string, AllProperty>
type MethodOption = Record<string, Function>
interface Data<D extends DataOption> {
/** 组件的内部数据,和 `properties` 一同用于组件的模板渲染 */
data?: D
}
interface Property<P extends PropertyOption> {
/** 组件的对外属性,是属性名到属性设置的映射表 */
properties: P
}
interface Method<M extends MethodOption, TIsPage extends boolean = false> {
/** 组件的方法,包括事件响应函数和任意的自定义方法,关于事件响应函数的使用,参见 [组件间通信与事件](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/events.html) */
methods: M & (TIsPage extends true ? Partial<Page.ILifetime> : {})
}
type PropertyType =
| StringConstructor
| NumberConstructor
| BooleanConstructor
| ArrayConstructor
| ObjectConstructor
| null
type ValueType<T extends PropertyType> = T extends null
? any
: T extends StringConstructor
? string
: T extends NumberConstructor
? number
: T extends BooleanConstructor
? boolean
: T extends ArrayConstructor
? any[]
: T extends ObjectConstructor
? IAnyObject
: never
type FullProperty<T extends PropertyType> = {
/** 属性类型 */
type: T
/** 属性初始值 */
value?: ValueType<T>
/** 属性值被更改时的响应函数 */
observer?:
| string
| ((
newVal: ValueType<T>,
oldVal: ValueType<T>,
changedPath: Array<string | number>
) => void)
/** 属性的类型(可以指定多个) */
optionalTypes?: ShortProperty[]
}
type AllFullProperty =
| FullProperty<StringConstructor>
| FullProperty<NumberConstructor>
| FullProperty<BooleanConstructor>
| FullProperty<ArrayConstructor>
| FullProperty<ObjectConstructor>
| FullProperty<null>
type ShortProperty =
| StringConstructor
| NumberConstructor
| BooleanConstructor
| ArrayConstructor
| ObjectConstructor
| null
type AllProperty = AllFullProperty | ShortProperty
type PropertyToData<T extends AllProperty> = T extends ShortProperty
? ValueType<T>
: FullPropertyToData<Exclude<T, ShortProperty>>
type FullPropertyToData<T extends AllFullProperty> = ValueType<T['type']>
type PropertyOptionToData<P extends PropertyOption> = {
[name in keyof P]: PropertyToData<P[name]>
}
interface InstanceProperties {
/** 组件的文件路径 */
is: string
/** 节点id */
id: string
/** 节点dataset */
dataset: Record<string, string>
}
interface InstanceMethods<D extends DataOption> {
/** `setData`
* `this.data`
*
* ****
*
* 1. ** this.data this.setData **
* 1. JSON
* 1. 1024kB
* 1. data value `undefined`
*/
setData(
/**
*
* `key: value` `this.data` `key` `value`
*
* `key` `array[2].message``a.b.c.d` this.data
*/
data: Partial<D> & IAnyObject,
/** setData引起的界面更新渲染完毕后的回调函数最低基础库 `1.5.0` */
callback?: () => void
): void
/** 检查组件是否具有 `behavior` 检查时会递归检查被直接或间接引入的所有behavior */
hasBehavior(behavior: Behavior.BehaviorIdentifier): void
/** 触发事件,参见组件事件 */
triggerEvent<DetailType = any>(
name: string,
detail?: DetailType,
options?: TriggerEventOption
): void
/** 创建一个 SelectorQuery 对象,选择器选取范围为这个组件实例内 */
createSelectorQuery(): SelectorQuery
/** 创建一个 IntersectionObserver 对象,选择器选取范围为这个组件实例内 */
createIntersectionObserver(
options: CreateIntersectionObserverOption
): IntersectionObserver
/** 使用选择器选择组件实例节点,返回匹配到的第一个组件实例对象(会被 `wx://component-export` 影响) */
selectComponent(selector: string): TrivialInstance
/** 使用选择器选择组件实例节点,返回匹配到的全部组件实例对象组成的数组 */
selectAllComponents(selector: string): TrivialInstance[]
/**
* `wx://component-export`
*
* [`2.8.2`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
**/
selectOwnerComponent(): TrivialInstance
/** 获取这个关系所对应的所有关联节点,参见 组件间关系 */
getRelationNodes(relationKey: string): TrivialInstance[]
/**
* callback setData setData
*
* [`2.4.0`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
**/
groupSetData(callback?: () => void): void
/**
* custom-tab-bar
*
* [`2.6.2`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
**/
getTabBar(): TrivialInstance
/**
*
*
* [`2.7.1`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
**/
getPageId(): string
/**
* [](https://developers.weixin.qq.com/miniprogram/dev/framework/view/animation.html)
*
* [`2.9.0`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
**/
animate(
selector: string,
keyFrames: KeyFrame[],
duration: number,
callback?: () => void
): void
/**
* [](https://developers.weixin.qq.com/miniprogram/dev/framework/view/animation.html)
*
* [`2.9.0`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
**/
animate(
selector: string,
keyFrames: ScrollTimelineKeyframe[],
duration: number,
scrollTimeline: ScrollTimelineOption
): void
/**
* [](https://developers.weixin.qq.com/miniprogram/dev/framework/view/animation.html)
*
* [`2.9.0`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
**/
clearAnimation(selector: string, callback: () => void): void
/**
* [](https://developers.weixin.qq.com/miniprogram/dev/framework/view/animation.html)
*
* [`2.9.0`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
**/
clearAnimation(
selector: string,
options?: ClearAnimationOptions,
callback?: () => void
): void
getOpenerEventChannel(): EventChannel
}
interface ComponentOptions {
/**
* [slot](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/wxml-wxss.html#组件wxml的slot)
*/
multipleSlots?: boolean
/**
* [](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/wxml-wxss.html#组件样式隔离)
*/
addGlobalClass?: boolean
/**
* [](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/wxml-wxss.html#组件样式隔离)
*/
styleIsolation?:
| 'isolated'
| 'apply-shared'
| 'shared'
| 'page-isolated'
| 'page-apply-shared'
| 'page-shared'
/**
* [](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/pure-data.html) 是一些不用于界面渲染的 data 字段,可以用于提升页面更新性能。从小程序基础库版本 2.8.2 开始支持。
*/
pureDataPattern?: RegExp
/**
* [](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/wxml-wxss.html#%E8%99%9A%E6%8B%9F%E5%8C%96%E7%BB%84%E4%BB%B6%E8%8A%82%E7%82%B9) 使自定义组件内部的第一层节点由自定义组件本身完全决定。从小程序基础库版本 [`2.11.2`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html) 开始支持 */
virtualHost?: boolean
}
interface TriggerEventOption {
/**
*
* `false`
*/
bubbles?: boolean
/** 穿false
*
* `false`
*/
composed?: boolean
/**
*
* `false`
*/
capturePhase?: boolean
}
interface RelationOption {
/** 目标组件的相对关系 */
type: 'parent' | 'child' | 'ancestor' | 'descendant'
/** 关系生命周期函数当关系被建立在页面节点树中时触发触发时机在组件attached生命周期之后 */
linked?(target: TrivialInstance): void
/** 关系生命周期函数当关系在页面节点树中发生改变时触发触发时机在组件moved生命周期之后 */
linkChanged?(target: TrivialInstance): void
/** 关系生命周期函数当关系脱离页面节点树时触发触发时机在组件detached生命周期之后 */
unlinked?(target: TrivialInstance): void
/** 如果这一项被设置则它表示关联的目标节点所应具有的behavior所有拥有这一behavior的组件节点都会被关联 */
target?: string
}
interface PageLifetimes {
/**
*
* /
*/
show(): void
/**
*
* / `navigateTo` `tab`
*/
hide(): void
/**
*
*
*/
resize(size: Page.IResizeOption): void
}
type DefinitionFilter = <T extends TrivialOption>(
/** 使用该 behavior 的 component/behavior 的定义对象 */
defFields: T,
/** 该 behavior 所使用的 behavior 的 definitionFilter 函数列表 */
definitionFilterArr?: DefinitionFilter[]
) => void
interface Lifetimes {
/** `created``attached``ready``moved``detached` `lifetimes` `lifetimes`
*
* `2.2.3` */
lifetimes: Partial<{
/**
* `setData`
*
* [`1.6.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
*/
created(): void
/**
*
*
* [`1.6.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
*/
attached(): void
/**
*
*
* [`1.6.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
*/
ready(): void
/**
*
*
* [`1.6.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
*/
moved(): void
/**
*
*
* [`1.6.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
*/
detached(): void
/**
*
*
* [`2.4.1`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
*/
error(err: Error): void
}>
/**
* @deprecated `2.2.3` lifetimes
*
*
*
* [`1.6.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
*/
created(): void
/**
* @deprecated `2.2.3` lifetimes
*
*
*
* [`1.6.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
*/
attached(): void
/**
* @deprecated `2.2.3` lifetimes
*
*
*
* [`1.6.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
*/
ready(): void
/**
* @deprecated `2.2.3` lifetimes
*
*
*
* [`1.6.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
*/
moved(): void
/**
* @deprecated `2.2.3` lifetimes
*
*
*
* [`1.6.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
*/
detached(): void
/**
* @deprecated `2.2.3` lifetimes
*
*
*
* [`2.4.1`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
*/
error(err: Error): void
}
interface OtherOption {
/** 类似于mixins和traits的组件间代码复用机制参见 [behaviors](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/behaviors.html) */
behaviors: Behavior.BehaviorIdentifier[]
/**
* properties data [](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/observer.html)
*
* [`2.6.1`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
*/
observers: Record<string, (...args: any[]) => any>
/** 组件间关系定义,参见 [组件间关系](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/lifetimes.html) */
relations: {
[componentName: string]: RelationOption
}
/** 组件接受的外部样式类,参见 [外部样式类](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/wxml-wxss.html) */
externalClasses?: string[]
/** 组件所在页面的生命周期声明对象,参见 [组件生命周期](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/lifetimes.html)
*
* [`2.2.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html) */
pageLifetimes?: Partial<PageLifetimes>
/** 一些选项(文档中介绍相关特性时会涉及具体的选项设置,这里暂不列举) */
options: ComponentOptions
/** 定义段过滤器,用于自定义组件扩展,参见 [自定义组件扩展](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/extend.html)
*
* [`2.2.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html) */
definitionFilter?: DefinitionFilter
/**
* 使 `behavior: wx://component-export` selectComponent [](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/events.html)
* [`2.2.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html) */
export: () => IAnyObject
}
interface KeyFrame {
/** 关键帧的偏移,范围[0-1] */
offset?: number
/** 动画缓动函数 */
ease?: string
/** 基点位置,即 CSS transform-origin */
transformOrigin?: string
/** 背景颜色,即 CSS background-color */
backgroundColor?: string
/** 底边位置,即 CSS bottom */
bottom?: number | string
/** 高度,即 CSS height */
height?: number | string
/** 左边位置,即 CSS left */
left?: number | string
/** 宽度,即 CSS width */
width?: number | string
/** 不透明度,即 CSS opacity */
opacity?: number | string
/** 右边位置,即 CSS right */
right?: number | string
/** 顶边位置,即 CSS top */
top?: number | string
/** 变换矩阵,即 CSS transform matrix */
matrix?: number[]
/** 三维变换矩阵,即 CSS transform matrix3d */
matrix3d?: number[]
/** 旋转,即 CSS transform rotate */
rotate?: number
/** 三维旋转,即 CSS transform rotate3d */
rotate3d?: number[]
/** X 方向旋转,即 CSS transform rotateX */
rotateX?: number
/** Y 方向旋转,即 CSS transform rotateY */
rotateY?: number
/** Z 方向旋转,即 CSS transform rotateZ */
rotateZ?: number
/** 缩放,即 CSS transform scale */
scale?: number[]
/** 三维缩放,即 CSS transform scale3d */
scale3d?: number[]
/** X 方向缩放,即 CSS transform scaleX */
scaleX?: number
/** Y 方向缩放,即 CSS transform scaleY */
scaleY?: number
/** Z 方向缩放,即 CSS transform scaleZ */
scaleZ?: number
/** 倾斜,即 CSS transform skew */
skew?: number[]
/** X 方向倾斜,即 CSS transform skewX */
skewX?: number
/** Y 方向倾斜,即 CSS transform skewY */
skewY?: number
/** 位移,即 CSS transform translate */
translate?: Array<number | string>
/** 三维位移,即 CSS transform translate3d */
translate3d?: Array<number | string>
/** X 方向位移,即 CSS transform translateX */
translateX?: number | string
/** Y 方向位移,即 CSS transform translateY */
translateY?: number | string
/** Z 方向位移,即 CSS transform translateZ */
translateZ?: number | string
}
interface ClearAnimationOptions {
/** 基点位置,即 CSS transform-origin */
transformOrigin?: boolean
/** 背景颜色,即 CSS background-color */
backgroundColor?: boolean
/** 底边位置,即 CSS bottom */
bottom?: boolean
/** 高度,即 CSS height */
height?: boolean
/** 左边位置,即 CSS left */
left?: boolean
/** 宽度,即 CSS width */
width?: boolean
/** 不透明度,即 CSS opacity */
opacity?: boolean
/** 右边位置,即 CSS right */
right?: boolean
/** 顶边位置,即 CSS top */
top?: boolean
/** 变换矩阵,即 CSS transform matrix */
matrix?: boolean
/** 三维变换矩阵,即 CSS transform matrix3d */
matrix3d?: boolean
/** 旋转,即 CSS transform rotate */
rotate?: boolean
/** 三维旋转,即 CSS transform rotate3d */
rotate3d?: boolean
/** X 方向旋转,即 CSS transform rotateX */
rotateX?: boolean
/** Y 方向旋转,即 CSS transform rotateY */
rotateY?: boolean
/** Z 方向旋转,即 CSS transform rotateZ */
rotateZ?: boolean
/** 缩放,即 CSS transform scale */
scale?: boolean
/** 三维缩放,即 CSS transform scale3d */
scale3d?: boolean
/** X 方向缩放,即 CSS transform scaleX */
scaleX?: boolean
/** Y 方向缩放,即 CSS transform scaleY */
scaleY?: boolean
/** Z 方向缩放,即 CSS transform scaleZ */
scaleZ?: boolean
/** 倾斜,即 CSS transform skew */
skew?: boolean
/** X 方向倾斜,即 CSS transform skewX */
skewX?: boolean
/** Y 方向倾斜,即 CSS transform skewY */
skewY?: boolean
/** 位移,即 CSS transform translate */
translate?: boolean
/** 三维位移,即 CSS transform translate3d */
translate3d?: boolean
/** X 方向位移,即 CSS transform translateX */
translateX?: boolean
/** Y 方向位移,即 CSS transform translateY */
translateY?: boolean
/** Z 方向位移,即 CSS transform translateZ */
translateZ?: boolean
}
interface ScrollTimelineKeyframe {
composite?: 'replace' | 'add' | 'accumulate' | 'auto'
easing?: string
offset?: number | null
[property: string]: string | number | null | undefined
}
interface ScrollTimelineOption {
/** 指定滚动元素的选择器(只支持 scroll-view该元素滚动时会驱动动画的进度 */
scrollSource: string
/** 指定滚动的方向。有效值为 horizontal 或 vertical */
orientation?: string
/** 指定开始驱动动画进度的滚动偏移量,单位 px */
startScrollOffset: number
/** 指定停止驱动动画进度的滚动偏移量,单位 px */
endScrollOffset: number
/** 起始和结束的滚动范围映射的时间长度,该时间可用于与关键帧动画里的时间 (duration) 相匹配,单位 ms */
timeRange: number
}
}
/** ComponentComponent
*
* * 使 `this.data` 使 `setData`
* * `this` 访
* * data `dataXyz` WXML `data-xyz=""` dataset
* * 使 data
* * `2.0.9` data
* * `bug` : type Object Array `this.setData` observer observer `newVal` `oldVal` `changedPath`
*/
declare let Component: WechatMiniprogram.Component.Constructor

File diff suppressed because it is too large Load Diff

@ -0,0 +1,259 @@
/*! *****************************************************************************
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.Page {
type Instance<
TData extends DataOption,
TCustom extends CustomOption
> = OptionalInterface<ILifetime> &
InstanceProperties &
InstanceMethods<TData> &
Data<TData> &
TCustom
type Options<
TData extends DataOption,
TCustom extends CustomOption
> = (TCustom & Partial<Data<TData>> & Partial<ILifetime>) &
ThisType<Instance<TData, TCustom>>
type TrivialInstance = Instance<IAnyObject, IAnyObject>
interface Constructor {
<TData extends DataOption, TCustom extends CustomOption>(
options: Options<TData, TCustom>
): void
}
interface ILifetime {
/**
*
* onLoad
*/
onLoad(
/** 打开当前页面路径中的参数 */
query: Record<string, string | undefined>
): void | Promise<void>
/**
*
* /
*/
onShow(): void | Promise<void>
/**
*
*
*
* API `wx.setNavigationBarTitle``onReady`
*/
onReady(): void | Promise<void>
/**
*
* / `navigateTo` `tab`
*/
onHide(): void | Promise<void>
/**
*
* `redirectTo``navigateBack`
*/
onUnload(): void | Promise<void>
/**
*
*
* - `app.json``window``enablePullDownRefresh`
* - `wx.startPullDownRefresh`
* - `wx.stopPullDownRefresh`
*/
onPullDownRefresh(): void | Promise<void>
/**
*
*
* - `app.json``window``onReachBottomDistance`
* -
*/
onReachBottom(): void | Promise<void>
/**
*
* `<button>` `open-type="share"`
*
* ****
*
* return Object
*/
onShareAppMessage(
/** 分享发起来源参数 */
options: IShareAppMessageOption
): ICustomShareContent | void
/**
*
*
* Beta Android [ (Beta)](https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/share-timeline.html)
*
* 2.11.3
*/
onShareTimeline(): ICustomTimelineContent | void
/**
*
*
*/
onPageScroll(
/** 页面滚动参数 */
options: IPageScrollOption
): void | Promise<void>
/** 当前是 tab 页时,点击 tab 时触发,最低基础库: `1.9.0` */
onTabItemTap(
/** tab 点击参数 */
options: ITabItemTapOption
): void | Promise<void>
/** 窗口尺寸改变时触发,最低基础库:`2.4.0` */
onResize(
/** 窗口尺寸参数 */
options: IResizeOption
): void | Promise<void>
/**
*
* 2.10.3 7.0.15 iOS
*/
onAddToFavorites(options: IAddToFavoritesOption): IAddToFavoritesContent
}
interface InstanceProperties {
/** 页面的文件路径 */
is: string
/** 到当前页面的路径 */
route: string
/** 打开当前页面路径中的参数 */
options: Record<string, string | undefined>
}
type DataOption = Record<string, any>
type CustomOption = Record<string, any>
type InstanceMethods<D extends DataOption> = Component.InstanceMethods<D>
interface Data<D extends DataOption> {
/**
*
* `data` 使****
*
* `data` `JSON``data``JSON`
*
* `WXML`
*/
data: D
}
interface ICustomShareContent {
/** 转发标题。默认值:当前小程序名称 */
title?: string
/** 转发路径,必须是以 / 开头的完整路径。默认值:当前页面 path */
path?: string
/** 自定义图片路径可以是本地文件路径、代码包文件路径或者网络图片路径。支持PNG及JPG。显示图片长宽比是 5:4最低基础库 `1.5.0`。默认值:使用默认截图 */
imageUrl?: string
}
interface ICustomTimelineContent {
/** 自定义标题,即朋友圈列表页上显示的标题。默认值:当前小程序名称 */
title?: string
/** 自定义页面路径中携带的参数,如 `path?a=1&b=2` 的 “?” 后面部分 默认值:当前页面路径携带的参数 */
query?: string
/** 自定义图片路径,可以是本地文件路径、代码包文件路径或者网络图片路径。支持 PNG 及 JPG。显示图片长宽比是 1:1。默认值默认使用小程序 Logo*/
imageUrl?: string
}
interface IPageScrollOption {
/** 页面在垂直方向已滚动的距离单位px */
scrollTop: number
}
interface IShareAppMessageOption {
/**
*
*
* - `button`
* - `menu`
*
* `1.2.4`
*/
from: 'button' | 'menu' | string
/** `from` `button` `target` `button` `undefined`
*
* `1.2.4` */
target: any
/** `<web-view>``<web-view>`url
*
* `1.6.4`
*/
webViewUrl?: string
}
interface ITabItemTapOption {
/** 被点击tabItem的序号从0开始最低基础库 `1.9.0` */
index: string
/** 被点击tabItem的页面路径最低基础库 `1.9.0` */
pagePath: string
/** 被点击tabItem的按钮文字最低基础库 `1.9.0` */
text: string
}
interface IResizeOption {
size: {
/** 变化后的窗口宽度,单位 px */
windowWidth: number
/** 变化后的窗口高度,单位 px */
windowHeight: number
}
}
interface IAddToFavoritesOption {
/** 页面中包含web-view组件时返回当前web-view的url */
webviewUrl?: string
}
interface IAddToFavoritesContent {
/** 自定义标题,默认值:页面标题或账号名称 */
title?: string
/** 自定义图片,显示图片长宽比为 11默认值页面截图 */
imageUrl?: string
/** 自定义query字段默认值当前页面的query */
query?: string
}
interface GetCurrentPages {
(): Array<Instance<IAnyObject, IAnyObject>>
}
}
/**
* `Object`
*/
declare let Page: WechatMiniprogram.Page.Constructor
/**
*
* ____
* - ____
* - `App.onLaunch` `getCurrentPages()` `page`
*/
declare let getCurrentPages: WechatMiniprogram.Page.GetCurrentPages
Loading…
Cancel
Save