Compare commits

..

No commits in common. 'main' and 'master' have entirely different histories.
main ... master

141
.gitignore vendored

@ -1,132 +1,17 @@
# ---> Node
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# npm 构建产物
miniprogram_npm/
dist/
build/
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
# node_modules
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# 缓存文件
npm-debug.log*
yarn-error.log*
*.lock
# 开发工具临时文件
.project
.idea/
.vscode/

@ -1,2 +0,0 @@
# nodejs-wxapp-learning

@ -0,0 +1,50 @@
// app.js
const { isAuthenticated } = require('./utils/token');
App({
onLaunch() {
// 展示本地存储能力
const logs = wx.getStorageSync('logs') || []
logs.unshift(Date.now())
wx.setStorageSync('logs', logs)
// 登录
wx.login({
success: res => {
// 发送 res.code 到后台换取 openId, sessionKey, unionId
}
})
// 检查token有效性并自动跳转
this.checkTokenAndRedirect();
},
globalData: {
userInfo: null
},
/**
* 检查token有效性并自动跳转到相应页面
*/
checkTokenAndRedirect() {
if (isAuthenticated()) {
console.log('用户已认证,跳转到首页');
// 如果用户已经登录,跳转到首页
wx.switchTab({
url: '/pages/index/index',
fail: (error) => {
console.error('跳转到首页失败:', error);
}
});
} else {
console.log('用户未认证,跳转到登录页');
// 如果用户未登录,跳转到登录页
wx.redirectTo({
url: '/pages/login/login',
fail: (error) => {
console.error('跳转到登录页失败:', error);
}
});
}
}
})

@ -0,0 +1,49 @@
{
"pages": [
"pages/login/login",
"pages/index/index",
"pages/category/category",
"pages/mine/mine"
],
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "底部TabBar示例",
"navigationBarTextStyle": "black"
},
"tabBar": {
"color": "#dbdbdb",
"selectedColor": "#fff",
"backgroundColor": "#FF9966",
"borderStyle": "black",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "img/tab/home_active.png",
"selectedIconPath": "img/tab/home.png"
},
{
"pagePath": "pages/category/category",
"text": "分类",
"iconPath": "img/tab/category_active.png",
"selectedIconPath": "img/tab/category.png"
},
{
"pagePath": "pages/mine/mine",
"text": "我的",
"iconPath": "img/tab/mine_active.png",
"selectedIconPath": "img/tab/mine.png"
}
]
},
"requiredPrivateInfos": ["getLocation"],
"permission": {
"scope.userLocation": {
"desc": "你的位置信息将用于获取当前位置并在地图上显示"
}
},
"componentFramework": "glass-easel",
"sitemapLocation": "sitemap.json",
"lazyCodeLoading": "requiredComponents"
}

@ -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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

20
node_modules/.package-lock.json generated vendored

@ -0,0 +1,20 @@
{
"name": "wxapp",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"node_modules/@vant/weapp": {
"version": "1.11.7",
"resolved": "https://registry.npmjs.org/@vant/weapp/-/weapp-1.11.7.tgz",
"integrity": "sha512-Rwn9BBnb4kHSV4XmvBicwtd42J+amEUfnFDcXJsGNPNX4a9c/DoT6YLsm4X1wB2+sQbdiQsbFBLAvGRBxCkD8g==",
"license": "MIT"
},
"node_modules/ant-design-mini": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/ant-design-mini/-/ant-design-mini-0.0.1.tgz",
"integrity": "sha512-pMtB6VUth5FVaSKho/rl7oDTrYl3cuuckDrEkDeTwqdiylfe2nm5OOomWg+oujpl8XV/SCpUOqmRybfBDOjXDg==",
"license": "ISC"
}
}
}

@ -0,0 +1,11 @@
{
"name": "ant-design-mini",
"version": "0.0.1",
"description": "ant-design-mini",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "liumin",
"license": "ISC"
}

@ -0,0 +1 @@
console.log('hello world')

30
package-lock.json generated

@ -0,0 +1,30 @@
{
"name": "wxapp",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "wxapp",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@vant/weapp": "^1.11.7",
"ant-design-mini": "^0.0.1"
},
"devDependencies": {}
},
"node_modules/@vant/weapp": {
"version": "1.11.7",
"resolved": "https://registry.npmjs.org/@vant/weapp/-/weapp-1.11.7.tgz",
"integrity": "sha512-Rwn9BBnb4kHSV4XmvBicwtd42J+amEUfnFDcXJsGNPNX4a9c/DoT6YLsm4X1wB2+sQbdiQsbFBLAvGRBxCkD8g==",
"license": "MIT"
},
"node_modules/ant-design-mini": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/ant-design-mini/-/ant-design-mini-0.0.1.tgz",
"integrity": "sha512-pMtB6VUth5FVaSKho/rl7oDTrYl3cuuckDrEkDeTwqdiylfe2nm5OOomWg+oujpl8XV/SCpUOqmRybfBDOjXDg==",
"license": "ISC"
}
}
}

@ -0,0 +1,20 @@
{
"name": "wxapp",
"version": "1.0.0",
"main": "app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "YuLeYuan",
"license": "ISC",
"dependencies": {
"@vant/weapp": "^1.11.7",
"ant-design-mini": "^0.0.1"
},
"repository": {
"type": "git",
"url": "https://bdgit.educoder.net/pq54ntls9/nodejs-wxapp-learning.git"
},
"description": ""
}

@ -0,0 +1,66 @@
// pages/category/category.js
Page({
/**
* 页面的初始数据
*/
data: {
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
}
})

@ -0,0 +1,3 @@
{
"usingComponents": {}
}

@ -0,0 +1,2 @@
<!--pages/category/category.wxml-->
<text>pages/category/category.wxml</text>

@ -0,0 +1 @@
/* pages/category/category.wxss */

@ -0,0 +1,66 @@
// pages/home/home.js
Page({
/**
* 页面的初始数据
*/
data: {
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
}
})

@ -0,0 +1,3 @@
{
"usingComponents": {}
}

@ -0,0 +1,5 @@
<!--pages/home/home.wxml-->
<text>pages/home/home.wxml</text>
<view>
</view>

@ -0,0 +1 @@
/* pages/home/home.wxss */

@ -0,0 +1,153 @@
// pages/index/index.js
const { get } = require('../../utils/request');
Page({
/**
* 页面的初始数据
*/
data: {
productList: [], // 商品列表数据
loading: false, // 加载状态
hasError: false, // 错误状态
errorMsg: '' // 错误消息
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
// 页面加载时获取商品列表
this.getProductList();
},
/**
* 获取商品列表
*/
async getProductList() {
try {
// 设置加载状态
this.setData({
loading: true,
hasError: false
});
// 调用API获取商品列表
const response = await get('/api/products');
// 根据实际返回的数据结构调整 - 从 products 字段中提取数据
let productList = response.products || [];
// 处理商品数据确保图片URL正确
productList = productList.map(item => {
// 根据实际后端返回的数据结构优先使用已有的imageUrl否则尝试其他可能的图片字段
let processedImageUrl = item.imageUrl || item.image || item.img || item.pic || item.picture || '';
// 如果图片URL是相对路径拼接基础URL
if (processedImageUrl && !processedImageUrl.startsWith('http')) {
if (processedImageUrl.startsWith('/')) {
processedImageUrl = `http://localhost:3000${processedImageUrl}`;
} else {
processedImageUrl = `http://localhost:3000/${processedImageUrl}`;
}
}
return {
...item,
imageUrl: processedImageUrl
};
});
// 更新页面数据
this.setData({
productList: Array.isArray(productList) ? productList : [],
loading: false
});
console.log('商品列表获取成功:', productList);
} catch (error) {
console.error('获取商品列表失败:', error);
this.setData({
hasError: true,
errorMsg: error.message || '获取商品列表失败',
loading: false
});
}
},
/**
* 处理商品点击事件
*/
onProductTap(e) {
const product = e.currentTarget.dataset.product;
console.log('点击了商品:', product);
// 这里可以跳转到商品详情页
// wx.navigateTo({
// url: `/pages/product/detail?id=${product.id}`
// });
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
// 页面显示时重新获取商品列表
// this.getProductList();
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
// 下拉刷新时重新获取商品列表
this.getProductList().finally(() => {
wx.stopPullDownRefresh(); // 停止下拉刷新动画
});
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
// 这里可以实现分页加载更多商品
console.log('滚动到底部');
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
}
})

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

@ -0,0 +1,46 @@
<!--pages/index/index.wxml-->
<view class="container">
<!-- 页面标题 -->
<view class="header">
<text class="title">商品列表</text>
</view>
<!-- 加载状态 -->
<view wx:if="{{loading}}" class="loading-container">
<text>加载中...</text>
</view>
<!-- 错误状态 -->
<view wx:elif="{{hasError}}" class="error-container">
<text class="error-msg">{{errorMsg}}</text>
<button bindtap="getProductList" class="retry-btn">重新加载</button>
</view>
<!-- 商品列表 -->
<view wx:else class="product-list">
<view
wx:for="{{productList}}"
wx:key="id"
class="product-item"
bindtap="onProductTap"
data-product="{{item}}"
>
<image
src="{{item.imageUrl || 'https://via.placeholder.com/200x200'}}"
class="product-image"
mode="aspectFill"
lazy-load="{{true}}"
/>
<view class="product-info">
<text class="product-name">{{item.name}}</text>
<text class="product-price">¥{{item.price}}</text>
<text class="product-description">{{item.description}}</text>
</view>
</view>
<!-- 空状态 -->
<view wx:if="{{productList.length === 0}}" class="empty-state">
<text>暂无商品</text>
</view>
</view>
</view>

@ -0,0 +1,110 @@
/* pages/index/index.wxss */
.container {
padding: 20rpx;
background-color: #f5f5f5;
min-height: 100vh;
}
.header {
padding: 20rpx 0;
text-align: center;
margin-bottom: 20rpx;
}
.title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.loading-container,
.error-container {
display: flex;
justify-content: center;
align-items: center;
height: 200rpx;
text-align: center;
}
.error-container {
flex-direction: column;
}
.error-msg {
color: #ff4d4f;
margin-bottom: 20rpx;
}
.retry-btn {
background-color: #1890ff;
color: white;
border-radius: 8rpx;
padding: 10rpx 20rpx;
}
.product-list {
display: flex;
flex-direction: column;
}
.product-item {
display: flex;
background: white;
margin-bottom: 20rpx;
border-radius: 12rpx;
overflow: hidden;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
}
.product-image {
width: 200rpx;
height: 200rpx;
background-color: #f0f0f0;
}
.product-info {
flex: 1;
padding: 20rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.product-name {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 10rpx;
display: block;
/* 限制一行显示,超出省略 */
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.product-price {
font-size: 32rpx;
color: #ff6b35;
font-weight: bold;
margin-bottom: 10rpx;
display: block;
}
.product-description {
font-size: 26rpx;
color: #666;
display: block;
/* 限制两行显示,超出省略 */
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.empty-state {
text-align: center;
padding: 100rpx 0;
color: #999;
}

@ -0,0 +1,140 @@
const { post } = require('../../utils/request');
const { setToken, setUserInfo, isAuthenticated } = require('../../utils/token');
Page({
/**
* 页面的初始数据
*/
data: {
account:"",
password:""
},
// 账号输入监听(可选,用于实时校验)
handleAccountInput(e) {
this.setData({ account: e.detail });
},
// 密码输入监听
handlePasswordInput(e) {
this.setData({ password: e.detail });
},
// 重置表单
handleReset() {
this.setData({
account: '',
password: ''
});
wx.showToast({ title: '已重置', icon: 'none' });
},
// 登录提交
async handleLogin() {
const { account, password } = this.data;
// 基础校验
if (!account.trim()) {
wx.showToast({ title: '请输入账号', icon: 'none' });
return;
}
if (!password.trim()) {
wx.showToast({ title: '请输入密码', icon: 'none' });
return;
}
try {
const res = await post('/api/login', {
username: account, // 或 phone根据后端字段调整
password: password
}, {
showLoading: true,
loadingText: '登录中...'
});
// 假设成功返回 { token: 'xxx', userInfo: {...} }
setToken(res.token);
setUserInfo(res.userInfo);
wx.showToast({ title: '登录成功', icon: 'success' });
// 跳转首页
wx.switchTab({ url: '/pages/index/index' }); // 假设首页是 tab 页
} catch (error) {
// 错误已在 request 中统一提示,这里可不做处理
console.error('登录失败:', error);
if (error.statusCode === 401) {
wx.showToast({
title: `用户名或密码错误`,
icon: 'none'
});
}
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
// 检查本地是否已存在有效token如果存在则直接跳转到首页
if (isAuthenticated()) {
wx.showToast({
title: '您已登录',
icon: 'none'
});
setTimeout(() => {
wx.switchTab({
url: '/pages/index/index'
});
}, 1000); // 延迟1秒跳转让用户看到提示
}
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
}
})

@ -0,0 +1,8 @@
{
"navigationBarTitleText": "登录",
"enablePullDownRefresh": false,
"usingComponents": {
"van-field": "@vant/weapp/field/index",
"van-button": "@vant/weapp/button/index"
}
}

@ -0,0 +1,37 @@
<view class="login-container">
<!-- Vant 输入框:账号/手机号 -->
<van-field
v-model="{{account}}"
label="账号"
placeholder="请输入账号/手机号"
border="{{true}}"
bind:change="handleAccountInput"
/>
<!-- Vant 输入框:密码(加密显示) -->
<van-field
v-model="{{password}}"
label="密码"
type="password"
placeholder="请输入密码"
border="{{true}}"
bind:change="handlePasswordInput"
clearable="{{true}}"
/>
<!-- Vant 按钮组 -->
<view class="btn-group">
<van-button
type="primary"
size="large"
bind:click="handleLogin"
style="flex:1;margin-right:10rpx;"
>登录</van-button>
<van-button
type="default"
size="large"
bind:click="handleReset"
style="flex:1;"
>重置</van-button>
</view>
</view>

@ -0,0 +1,12 @@
.login-container {
padding: 40rpx 30rpx;
display: flex;
flex-direction: column;
gap: 20rpx;
}
.btn-group {
display: flex;
margin-top: 30rpx;
width: 100%;
}

@ -0,0 +1,113 @@
Page({
data: {
// 地图初始中心点(默认北京天安门)
latitude: 39.909,
longitude: 116.397,
scale: 16,
markers: [],
currentAddress: '点击下方按钮获取当前位置',
amapKey: 'b7cf2e80f99e2fbb46fa172ef62e71a0' // 替换为你申请的高德地图API Key
},
onLoad: function(options) {
// 页面加载时不自动获取位置,等待用户点击按钮
},
// 获取当前位置
getCurrentLocation: function() {
const that = this;
// 使用微信原生API获取位置
wx.getLocation({
type: 'gcj02', // 国内一般使用gcj02坐标系
success: function(res) {
const latitude = res.latitude;
const longitude = res.longitude;
console.log('获取到的真实位置:', latitude, longitude); // 调试用
// 更新地图中心点
that.setData({
latitude: latitude,
longitude: longitude
});
// 根据坐标获取地址信息
that.getAddressByCoordinate(latitude, longitude);
// 添加当前位置标记(使用定位图标)
that.addMarker(latitude, longitude, '我的位置');
},
fail: function(err) {
console.error('获取位置失败', err);
wx.showModal({
title: '提示',
content: '无法获取您的位置,请检查是否授权位置权限',
showCancel: false
});
}
});
},
// 根据坐标获取地址信息使用高德地图API
getAddressByCoordinate: function(lat, lng) {
const that = this;
const url = `https://restapi.amap.com/v3/geocode/regeo`;
wx.request({
url: url,
data: {
key: this.data.amapKey,
location: `${lng},${lat}`,
extensions: 'all'
},
method: 'GET',
success: function(res) {
if (res.data.status === '1' && res.data.regeocode) {
const address = res.data.regeocode.formatted_address || '未知位置';
that.setData({
currentAddress: address
});
} else {
that.setData({
currentAddress: '获取地址信息失败'
});
}
},
fail: function(err) {
console.error('获取地址失败', err);
that.setData({
currentAddress: '网络错误,无法获取地址'
});
}
});
},
// 添加标记点
addMarker: function(lat, lng, title) {
const newMarker = {
id: Date.now(),
latitude: lat,
longitude: lng,
title: title,
iconPath:'../../img/location.png', // 使用定位图标,如果不存在可以用系统默认图标
width: 30,
height: 30,
rotate: 0,
alpha: 1,
callout: {
content: title,
display: 'ALWAYS',
padding: 8,
borderRadius: 4,
bgColor: '#ffffff',
color: '#000000',
fontSize: 12,
boxShadow: '0 2rpx 4rpx rgba(0, 0, 0, 0.2)'
}
};
this.setData({
markers: [newMarker] // 只保留当前位置标记
});
}
});

@ -0,0 +1,4 @@
{
"navigationBarTitleText": "地图定位",
"usingComponents": {}
}

@ -0,0 +1,14 @@
<view class="container">
<!-- 地图组件 - 占满全屏 -->
<map
id="myMap"
longitude="{{longitude}}"
latitude="{{latitude}}"
scale="{{scale}}"
markers="{{markers}}"
style="width: 100%; height: 100%;">
</map>
<!-- 悬浮定位按钮 -->
<button class="locate-btn" bindtap="getCurrentLocation">定位当前位置</button>
</view>

@ -0,0 +1,19 @@
.container{
padding: 0;
}
.locate-btn {
position: absolute;
bottom: 40rpx;
left: 50%;
transform: translateX(-50%);
z-index: 999;
padding: 20rpx 40rpx;
background-color: #007aff;
color: white;
border-radius: 10rpx;
border: none;
font-size: 32rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.2);
min-width: 200rpx;
text-align: center;
}

@ -0,0 +1,46 @@
{
"compileType": "miniprogram",
"libVersion": "3.14.2",
"packOptions": {
"ignore": [],
"include": []
},
"setting": {
"coverView": true,
"es6": true,
"postcss": true,
"minified": true,
"enhance": true,
"showShadowRootInWxmlPanel": true,
"babelSetting": {
"ignore": [],
"disablePlugins": [],
"outputPath": ""
},
"compileWorklet": false,
"uglifyFileName": false,
"uploadWithSourceMap": true,
"packNpmManually": true,
"packNpmRelationList": [
{
"packageJsonPath": "./package.json",
"miniprogramNpmDistDir": "./"
}
],
"useCompilerPlugins": [],
"minifyWXSS": true,
"minifyWXML": true,
"localPlugins": false,
"condition": false,
"swc": false,
"disableSWC": true,
"disableUseStrict": false
},
"condition": {},
"editorSetting": {
"tabIndent": "auto",
"tabSize": 2
},
"appid": "wx9bfeebe2956c4c5b",
"simulatorPluginLibVersion": {}
}

@ -0,0 +1,23 @@
{
"description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
"projectname": "wxapp",
"setting": {
"compileHotReLoad": true,
"urlCheck": false,
"coverView": true,
"lazyloadPlaceholderEnable": false,
"skylineRenderEnable": false,
"preloadBackgroundData": false,
"autoAudits": false,
"useApiHook": true,
"showShadowRootInWxmlPanel": true,
"useStaticServer": false,
"useLanDebug": false,
"showES6CompileOption": false,
"bigPackageSizeSupport": false,
"checkInvalidKey": true,
"ignoreDevUnusedFiles": true
},
"libVersion": "3.14.2",
"condition": {}
}

@ -0,0 +1,7 @@
{
"desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
"rules": [{
"action": "allow",
"page": "*"
}]
}

@ -0,0 +1,118 @@
// utils/request.js
const { getAuthHeader, clearAuthData } = require('./token');
const BASE_URL = 'http://localhost:3000'; // 替换为你的后端接口域名
/**
* 封装 wx.request
* @param {Object} options - 请求配置
* @param {string} options.url - 接口路径不包含域名
* @param {string} [options.method='GET'] - 请求方法
* @param {Object} [options.data] - 请求参数
* @param {boolean} [options.showLoading=true] - 是否显示加载提示
* @param {string} [options.loadingText='加载中...'] - 加载提示文字
*/
const request = (options) => {
const {
url,
method = 'GET',
data = {},
showLoading = true,
loadingText = '加载中...'
} = options;
return new Promise((resolve, reject) => {
let loadingHide = () => {};
// 显示 loading
if (showLoading) {
wx.showLoading({
title: loadingText,
mask: true
});
loadingHide = wx.hideLoading;
}
wx.request({
url: BASE_URL + url,
method,
data,
header: getAuthHeader(),
success: (res) => {
const { statusCode, data: responseData } = res;
if (statusCode === 200) {
// 假设后端返回格式:{ code: 200, data: {}, msg: 'success' }
if (responseData.code === 200) {
resolve(responseData.data);
} else {
// 特殊处理401未授权错误
if (responseData.code === 401 || statusCode === 401) {
// token可能已过期清除本地认证信息
clearAuthData();
wx.showToast({
title: '登录已过期,请重新登录',
icon: 'none'
});
// 跳转到登录页
setTimeout(() => {
wx.redirectTo({
url: '/pages/login/login'
});
}, 1500);
reject(responseData);
} else {
// 其他业务错误(如参数错误等)
wx.showToast({
title: responseData.msg || '请求失败',
icon: 'none'
});
reject(responseData);
}
}
} else {
// HTTP 状态码非 200
if (statusCode === 401) {
// token可能已过期清除本地认证信息
clearAuthData();
wx.showToast({
title: '登录已过期,请重新登录',
icon: 'none'
});
// 跳转到登录页
setTimeout(() => {
wx.redirectTo({
url: '/pages/login/login'
});
}, 1500);
} else {
// wx.showToast({
// title: `请求错误 ${statusCode}`,
// icon: 'none'
// });
}
reject(res);
}
},
fail: (err) => {
wx.showToast({
title: '网络异常,请稍后重试',
icon: 'none'
});
reject(err);
},
complete: () => {
// 隐藏 loading
loadingHide();
}
});
});
};
module.exports = {
request,
get: (url, data, options = {}) => request({ ...options, url, method: 'GET', data }),
post: (url, data, options = {}) => request({ ...options, url, method: 'POST', data })
};

@ -0,0 +1,117 @@
// utils/token.js - Token管理工具
const TOKEN_KEY = 'token';
const USER_INFO_KEY = 'userInfo';
/**
* 保存token到本地存储
* @param {string} token - 认证令牌
*/
const setToken = (token) => {
try {
wx.setStorageSync(TOKEN_KEY, token);
} catch (error) {
console.error('保存token失败:', error);
}
};
/**
* 获取本地存储的token
* @returns {string} token值
*/
const getToken = () => {
try {
return wx.getStorageSync(TOKEN_KEY) || '';
} catch (error) {
console.error('获取token失败:', error);
return '';
}
};
/**
* 删除本地存储的token
*/
const removeToken = () => {
try {
wx.removeStorageSync(TOKEN_KEY);
} catch (error) {
console.error('删除token失败:', error);
}
};
/**
* 保存用户信息到本地存储
* @param {Object} userInfo - 用户信息对象
*/
const setUserInfo = (userInfo) => {
try {
wx.setStorageSync(USER_INFO_KEY, userInfo);
} catch (error) {
console.error('保存用户信息失败:', error);
}
};
/**
* 获取本地存储的用户信息
* @returns {Object} 用户信息对象
*/
const getUserInfo = () => {
try {
return wx.getStorageSync(USER_INFO_KEY) || null;
} catch (error) {
console.error('获取用户信息失败:', error);
return null;
}
};
/**
* 删除本地存储的用户信息
*/
const removeUserInfo = () => {
try {
wx.removeStorageSync(USER_INFO_KEY);
} catch (error) {
console.error('删除用户信息失败:', error);
}
};
/**
* 清除所有认证相关信息
*/
const clearAuthData = () => {
removeToken();
removeUserInfo();
};
/**
* 检查是否已登录是否有有效token
* @returns {boolean} 是否已登录
*/
const isAuthenticated = () => {
const token = getToken();
return !!token;
};
/**
* 获取完整的认证头信息
* @returns {Object} 包含认证信息的header对象
*/
const getAuthHeader = () => {
const token = getToken();
return {
'Content-Type': 'application/json',
'Authorization': token ? `Bearer ${token}` : ''
};
};
module.exports = {
setToken,
getToken,
removeToken,
setUserInfo,
getUserInfo,
removeUserInfo,
clearAuthData,
isAuthenticated,
getAuthHeader
};

@ -0,0 +1,19 @@
const formatTime = 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 => {
n = n.toString()
return n[1] ? n : `0${n}`
}
module.exports = {
formatTime
}
Loading…
Cancel
Save