commit
7096db84e6
121 changed files with 53943 additions and 0 deletions
@ -0,0 +1 @@ |
|||||
|
node_modules |
||||
@ -0,0 +1,108 @@ |
|||||
|
App({ |
||||
|
onLaunch: function () { |
||||
|
// 初始化应用
|
||||
|
console.log('App Launch') |
||||
|
|
||||
|
// 初始化本地存储的标签和用户数据
|
||||
|
if (!wx.getStorageSync('users')) { |
||||
|
wx.setStorageSync('users', {}) |
||||
|
} |
||||
|
if (!wx.getStorageSync('tags')) { |
||||
|
wx.setStorageSync('tags', {}) |
||||
|
} |
||||
|
if (!wx.getStorageSync('goods')) { |
||||
|
// 初始化空的商品列表,不预置默认数据,由服务器获取
|
||||
|
wx.setStorageSync('goods', []) |
||||
|
} |
||||
|
if (!wx.getStorageSync('supplies')) { |
||||
|
// 初始化空的供应列表,不预置默认数据,由服务器获取
|
||||
|
wx.setStorageSync('supplies', []) |
||||
|
} |
||||
|
|
||||
|
// 检查是否是首次启动
|
||||
|
const isFirstLaunch = !wx.getStorageSync('hasLaunched') |
||||
|
if (isFirstLaunch) { |
||||
|
// 标记应用已经启动过
|
||||
|
wx.setStorageSync('hasLaunched', true) |
||||
|
|
||||
|
// 只有在首次启动时才检查用户身份并可能跳转
|
||||
|
const userId = wx.getStorageSync('userId') |
||||
|
if (userId) { |
||||
|
const users = wx.getStorageSync('users') |
||||
|
const user = users[userId] |
||||
|
if (user && user.type) { |
||||
|
// 延迟跳转,确保页面加载完成
|
||||
|
setTimeout(() => { |
||||
|
try { |
||||
|
if (user.type === 'buyer') { |
||||
|
wx.switchTab({ url: '/pages/buyer/index' }) |
||||
|
} else if (user.type === 'seller') { |
||||
|
wx.switchTab({ url: '/pages/seller/index' }) |
||||
|
} |
||||
|
} catch (e) { |
||||
|
console.error('启动时页面跳转异常:', e) |
||||
|
// 即使跳转失败,也不影响应用正常启动
|
||||
|
} |
||||
|
}, 100) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 获取用户信息
|
||||
|
wx.getSetting({ |
||||
|
success: res => { |
||||
|
if (res.authSetting['scope.userInfo']) { |
||||
|
// 已经授权,可以直接调用 getUserInfo 获取头像昵称
|
||||
|
wx.getUserInfo({ |
||||
|
success: res => { |
||||
|
this.globalData.userInfo = res.userInfo |
||||
|
// 存储用户ID(实际项目中使用openid)
|
||||
|
if (!wx.getStorageSync('userId')) { |
||||
|
const userId = 'user_' + Date.now() |
||||
|
wx.setStorageSync('userId', userId) |
||||
|
// 初始化用户数据
|
||||
|
const users = wx.getStorageSync('users') |
||||
|
users[userId] = { |
||||
|
info: res.userInfo, |
||||
|
type: null |
||||
|
} |
||||
|
wx.setStorageSync('users', users) |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
onShow: function () { |
||||
|
console.log('App Show') |
||||
|
}, |
||||
|
onHide: function () { |
||||
|
console.log('App Hide') |
||||
|
}, |
||||
|
|
||||
|
// 更新当前选中的tab
|
||||
|
updateCurrentTab(tabKey) { |
||||
|
if (this.globalData) { |
||||
|
this.globalData.currentTab = tabKey |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 跳转到估价页面
|
||||
|
goToEvaluatePage() { |
||||
|
wx.navigateTo({ |
||||
|
url: '/pages/evaluate/index' |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 上传手机号数据
|
||||
|
async uploadPhoneNumberData(phoneData) { |
||||
|
const API = require('./utils/api.js') |
||||
|
return await API.uploadPhoneNumberData(phoneData) |
||||
|
}, |
||||
|
|
||||
|
globalData: { |
||||
|
userInfo: null, |
||||
|
currentTab: 'index' // 当前选中的tab
|
||||
|
} |
||||
|
}) |
||||
@ -0,0 +1,76 @@ |
|||||
|
{ |
||||
|
"pages": [ |
||||
|
"pages/index/index", |
||||
|
"pages/evaluate/index", |
||||
|
"pages/settlement/index", |
||||
|
"pages/publish/index", |
||||
|
"pages/buyer/index", |
||||
|
"pages/seller/index", |
||||
|
"pages/profile/index", |
||||
|
"pages/notopen/index" |
||||
|
], |
||||
|
"subpackages": [ |
||||
|
{ |
||||
|
"root": "pages/debug", |
||||
|
"pages": [ |
||||
|
"debug", |
||||
|
"debug-sold-out", |
||||
|
"debug-gross-weight" |
||||
|
], |
||||
|
"independent": false |
||||
|
}, |
||||
|
{ |
||||
|
"root": "pages/test", |
||||
|
"pages": [ |
||||
|
"undercarriage-test" |
||||
|
], |
||||
|
"independent": false |
||||
|
}, |
||||
|
{ |
||||
|
"root": "pages/test-tools", |
||||
|
"pages": [ |
||||
|
"test-mode-switch", |
||||
|
"connection-test", |
||||
|
"api-test", |
||||
|
"phone-test", |
||||
|
"clear-storage", |
||||
|
"gross-weight-tester", |
||||
|
"fix-connection" |
||||
|
], |
||||
|
"independent": false |
||||
|
} |
||||
|
], |
||||
|
"window": { |
||||
|
"backgroundTextStyle": "light", |
||||
|
"navigationBarBackgroundColor": "#fff", |
||||
|
"navigationBarTitleText": "又鸟蛋平台", |
||||
|
"navigationBarTextStyle": "black" |
||||
|
}, |
||||
|
"tabBar": { |
||||
|
"custom": true, |
||||
|
"color": "#999999", |
||||
|
"selectedColor": "#FF6B81", |
||||
|
"backgroundColor": "#ffffff", |
||||
|
"borderStyle": "black", |
||||
|
"list": [ |
||||
|
{ |
||||
|
"pagePath": "pages/index/index", |
||||
|
"text": "首页" |
||||
|
}, |
||||
|
{ |
||||
|
"pagePath": "pages/buyer/index", |
||||
|
"text": "购物" |
||||
|
}, |
||||
|
{ |
||||
|
"pagePath": "pages/seller/index", |
||||
|
"text": "货源" |
||||
|
}, |
||||
|
{ |
||||
|
"pagePath": "pages/profile/index", |
||||
|
"text": "我的" |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
"style": "v2", |
||||
|
"sitemapLocation": "sitemap.json" |
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
/**app.wxss**/ |
||||
|
.container { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
background-color: #f5f5f5; |
||||
|
} |
||||
|
|
||||
|
.btn { |
||||
|
width: 80%; |
||||
|
padding: 15rpx 0; |
||||
|
margin: 20rpx 0; |
||||
|
border-radius: 10rpx; |
||||
|
font-size: 32rpx; |
||||
|
} |
||||
|
|
||||
|
.card { |
||||
|
width: 90%; |
||||
|
padding: 30rpx; |
||||
|
margin: 20rpx 0; |
||||
|
background-color: #fff; |
||||
|
border-radius: 10rpx; |
||||
|
box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.1); |
||||
|
} |
||||
|
|
||||
|
.title { |
||||
|
font-size: 36rpx; |
||||
|
font-weight: bold; |
||||
|
margin-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.input { |
||||
|
width: 100%; |
||||
|
max-width: 100%; |
||||
|
padding: 20rpx; |
||||
|
margin: 15rpx 0; |
||||
|
border: 1px solid #eee; |
||||
|
border-radius: 5rpx; |
||||
|
font-size: 28rpx; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
@ -0,0 +1,102 @@ |
|||||
|
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() |
||||
|
const platform = (wx.getDeviceInfo() || wx.getSystemInfoSync()).platform |
||||
|
const isAndroid = platform === 'android' |
||||
|
const isDevtools = platform === 'devtools' |
||||
|
const { windowWidth, safeArea: { top = 0, bottom = 0 } = {} } = wx.getWindowInfo() || wx.getSystemInfoSync() |
||||
|
this.setData({ |
||||
|
ios: !isAndroid, |
||||
|
innerPaddingRight: `padding-right: ${windowWidth - rect.left}px`, |
||||
|
leftWidth: `width: ${windowWidth - rect.left}px`, |
||||
|
safeAreaTop: isDevtools || isAndroid ? `height: calc(var(--height) + ${top}px); padding-top: ${top}px` : `` |
||||
|
}) |
||||
|
}, |
||||
|
}, |
||||
|
/** |
||||
|
* 组件的方法列表 |
||||
|
*/ |
||||
|
methods: { |
||||
|
_showChange(show) { |
||||
|
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,5 @@ |
|||||
|
{ |
||||
|
"component": true, |
||||
|
"styleIsolation": "apply-shared", |
||||
|
"usingComponents": {} |
||||
|
} |
||||
@ -0,0 +1,64 @@ |
|||||
|
<view class="weui-navigation-bar {{extClass}}"> |
||||
|
<view class="weui-navigation-bar__inner {{ios ? 'ios' : 'android'}}" style="color: {{color}}; background: {{background}}; {{displayStyle}}; {{innerPaddingRight}}; {{safeAreaTop}};"> |
||||
|
|
||||
|
<!-- 左侧按钮 --> |
||||
|
<view class='weui-navigation-bar__left' style="{{leftWidth}};"> |
||||
|
<block wx:if="{{back || homeButton}}"> |
||||
|
<!-- 返回上一页 --> |
||||
|
<block wx:if="{{back}}"> |
||||
|
<view class="weui-navigation-bar__buttons weui-navigation-bar__buttons_goback"> |
||||
|
<view |
||||
|
bindtap="back" |
||||
|
class="weui-navigation-bar__btn_goback_wrapper" |
||||
|
hover-class="weui-active" |
||||
|
hover-stay-time="100" |
||||
|
aria-role="button" |
||||
|
aria-label="返回" |
||||
|
> |
||||
|
<view class="weui-navigation-bar__button weui-navigation-bar__btn_goback"></view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</block> |
||||
|
<!-- 返回首页 --> |
||||
|
<block wx:if="{{homeButton}}"> |
||||
|
<view class="weui-navigation-bar__buttons weui-navigation-bar__buttons_home"> |
||||
|
<view |
||||
|
bindtap="home" |
||||
|
class="weui-navigation-bar__btn_home_wrapper" |
||||
|
hover-class="weui-active" |
||||
|
aria-role="button" |
||||
|
aria-label="首页" |
||||
|
> |
||||
|
<view class="weui-navigation-bar__button weui-navigation-bar__btn_home"></view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</block> |
||||
|
</block> |
||||
|
<block wx:else> |
||||
|
<slot name="left"></slot> |
||||
|
</block> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 标题 --> |
||||
|
<view class='weui-navigation-bar__center'> |
||||
|
<view wx:if="{{loading}}" class="weui-navigation-bar__loading" aria-role="alert"> |
||||
|
<view |
||||
|
class="weui-loading" |
||||
|
aria-role="img" |
||||
|
aria-label="加载中" |
||||
|
></view> |
||||
|
</view> |
||||
|
<block wx:if="{{title}}"> |
||||
|
<text>{{title}}</text> |
||||
|
</block> |
||||
|
<block wx:else> |
||||
|
<slot name="center"></slot> |
||||
|
</block> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 右侧留空 --> |
||||
|
<view class='weui-navigation-bar__right'> |
||||
|
<slot name="right"></slot> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
@ -0,0 +1,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,348 @@ |
|||||
|
Component({ |
||||
|
/** |
||||
|
* 组件的属性列表 |
||||
|
*/ |
||||
|
properties: { |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 组件的初始数据 |
||||
|
*/ |
||||
|
data: { |
||||
|
selected: 'index', |
||||
|
show: true, // 控制tab-bar显示状态
|
||||
|
// 记录tabBar数据,用于匹配
|
||||
|
tabBarItems: [ |
||||
|
{ key: 'index', route: 'pages/index/index' }, |
||||
|
{ key: 'buyer', route: 'pages/buyer/index' }, |
||||
|
{ key: 'seller', route: 'pages/seller/index' }, |
||||
|
{ key: 'profile', route: 'pages/profile/index' } |
||||
|
] |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 组件的方法列表 |
||||
|
*/ |
||||
|
methods: { |
||||
|
// 切换tab页面的方法 - 增强版,改进状态管理
|
||||
|
switchTab(e) { |
||||
|
try { |
||||
|
const data = e.currentTarget.dataset |
||||
|
let url = data.path |
||||
|
const key = data.key |
||||
|
|
||||
|
console.log('点击tab项:', key, '原始路径:', url) |
||||
|
|
||||
|
// 确保路径格式正确 - 移除可能的前缀斜杠
|
||||
|
if (url.startsWith('/')) { |
||||
|
url = url.substring(1) |
||||
|
} |
||||
|
|
||||
|
console.log('修正后路径:', url) |
||||
|
|
||||
|
// 更新全局数据 - 先更新全局状态,确保状态一致性
|
||||
|
const app = getApp() |
||||
|
if (app && app.globalData) { |
||||
|
app.globalData.currentTab = key |
||||
|
console.log('同步选中状态到全局数据:', key) |
||||
|
} |
||||
|
|
||||
|
// 立即更新UI选中状态
|
||||
|
this.setData({ |
||||
|
selected: key |
||||
|
}) |
||||
|
|
||||
|
// 无论跳转到哪个页面,先确保用户身份被正确设置
|
||||
|
if (key === 'buyer' || key === 'seller') { |
||||
|
const userId = wx.getStorageSync('userId'); |
||||
|
if (userId) { |
||||
|
// 更新用户类型
|
||||
|
let users = wx.getStorageSync('users'); |
||||
|
if (typeof users !== 'object' || users === null) { |
||||
|
users = {}; |
||||
|
} |
||||
|
if (!users[userId]) { |
||||
|
users[userId] = {}; |
||||
|
} |
||||
|
users[userId].type = key; |
||||
|
wx.setStorageSync('users', users); |
||||
|
|
||||
|
// 更新标签
|
||||
|
let tags = wx.getStorageSync('tags'); |
||||
|
if (typeof tags !== 'object' || tags === null) { |
||||
|
tags = {}; |
||||
|
} |
||||
|
tags[userId] = tags[userId] || []; |
||||
|
tags[userId] = tags[userId].filter(tag => !tag.startsWith('身份:')); |
||||
|
tags[userId].push(`身份:${key}`); |
||||
|
wx.setStorageSync('tags', tags); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 特殊处理:点击货源页面时检查登录状态和入驻状态
|
||||
|
if (key === 'seller' && url === 'pages/seller/index') { |
||||
|
console.log('点击货源页面,开始检查登录状态和入驻状态...'); |
||||
|
|
||||
|
// 首先检查登录状态
|
||||
|
const userId = wx.getStorageSync('userId'); |
||||
|
const userInfo = wx.getStorageSync('userInfo'); |
||||
|
|
||||
|
if (!userId || !userInfo) { |
||||
|
console.log('用户未登录,跳转到登录或入驻页面'); |
||||
|
wx.navigateTo({ |
||||
|
url: '/pages/settlement/index', |
||||
|
success: (res) => { |
||||
|
console.log('跳转到入驻页面成功:', res); |
||||
|
}, |
||||
|
fail: (err) => { |
||||
|
console.error('跳转到入驻页面失败:', err); |
||||
|
this.navigateToTabPage(url); |
||||
|
} |
||||
|
}); |
||||
|
} else { |
||||
|
// 用户已登录,检查合作商状态
|
||||
|
const settlementStatus = wx.getStorageSync('settlement_status'); |
||||
|
console.log('检查合作商状态:', settlementStatus); |
||||
|
|
||||
|
if (!settlementStatus || settlementStatus === '') { |
||||
|
console.log('合作商状态不存在,用户未入驻'); |
||||
|
wx.navigateTo({ |
||||
|
url: '/pages/settlement/index' |
||||
|
}); |
||||
|
} else if (settlementStatus === 'underreview') { |
||||
|
console.log('合作商状态为审核中,跳转到货源页面显示审核中内容'); |
||||
|
this.navigateToTabPage(url); |
||||
|
} else if (settlementStatus === 'approved' || settlementStatus === 'incooperation') { |
||||
|
console.log('合作商状态为审核通过,正常跳转到货源页面'); |
||||
|
this.navigateToTabPage(url); |
||||
|
} else { |
||||
|
console.log('其他状态,跳转到入驻页面'); |
||||
|
wx.navigateTo({ |
||||
|
url: '/pages/settlement/index' |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} else { |
||||
|
// 其他tab页面正常跳转
|
||||
|
this.navigateToTabPage(url) |
||||
|
} |
||||
|
} catch (e) { |
||||
|
console.error('switchTab方法执行错误:', e) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 跳转到tab页面的通用方法
|
||||
|
navigateToTabPage(url) { |
||||
|
console.log('使用switchTab跳转到tabbar页面:', url) |
||||
|
wx.switchTab({ |
||||
|
url: '/' + url, |
||||
|
success: (res) => { |
||||
|
console.log('switchTab成功:', url, res) |
||||
|
}, |
||||
|
fail: (err) => { |
||||
|
console.error('switchTab失败:', url, err) |
||||
|
console.log('尝试使用reLaunch跳转...') |
||||
|
wx.reLaunch({ |
||||
|
url: '/' + url, |
||||
|
success: (res) => { |
||||
|
console.log('reLaunch成功:', url, res) |
||||
|
}, |
||||
|
fail: (err) => { |
||||
|
console.error('reLaunch也失败:', url, err) |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 强制更新选中状态
|
||||
|
forceUpdateSelectedState(key) { |
||||
|
try { |
||||
|
this.setData({ |
||||
|
selected: key |
||||
|
}) |
||||
|
console.log('强制更新选中状态:', key) |
||||
|
// 再次同步全局数据,确保双向一致性
|
||||
|
const app = getApp() |
||||
|
if (app && app.globalData) { |
||||
|
app.globalData.currentTab = key |
||||
|
} |
||||
|
} catch (e) { |
||||
|
console.error('强制更新选中状态失败:', e) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 恢复到之前的状态
|
||||
|
restorePreviousState() { |
||||
|
try { |
||||
|
const app = getApp() |
||||
|
if (app && app.globalData && app.globalData.currentTab) { |
||||
|
this.setData({ |
||||
|
selected: app.globalData.currentTab |
||||
|
}) |
||||
|
console.log('恢复选中状态到:', app.globalData.currentTab) |
||||
|
} |
||||
|
} catch (e) { |
||||
|
console.error('恢复状态失败:', e) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 跳转到鸡蛋估价页面 - 现已改为未开放页面
|
||||
|
goToEvaluatePage() { |
||||
|
wx.navigateTo({ |
||||
|
url: '/pages/notopen/index' |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 从全局数据同步状态的方法 - 增强版
|
||||
|
syncFromGlobalData() { |
||||
|
try { |
||||
|
const app = getApp() |
||||
|
const pages = getCurrentPages() |
||||
|
let currentRoute = '' |
||||
|
|
||||
|
// 获取当前页面路由
|
||||
|
if (pages && pages.length > 0) { |
||||
|
const currentPage = pages[pages.length - 1] |
||||
|
currentRoute = currentPage.route |
||||
|
console.log('当前页面路由:', currentRoute) |
||||
|
} |
||||
|
|
||||
|
// 根据当前页面路由确定应该选中的tab
|
||||
|
let shouldSelect = 'index' |
||||
|
for (let item of this.data.tabBarItems) { |
||||
|
if (item.route === currentRoute) { |
||||
|
shouldSelect = item.key |
||||
|
break |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 检查全局数据中是否有控制tab-bar显示的状态
|
||||
|
let showTabBar = true |
||||
|
if (app && app.globalData && typeof app.globalData.showTabBar !== 'undefined') { |
||||
|
showTabBar = app.globalData.showTabBar |
||||
|
} |
||||
|
|
||||
|
// 更新全局数据和本地数据,确保一致性
|
||||
|
if (app && app.globalData) { |
||||
|
app.globalData.currentTab = shouldSelect |
||||
|
} |
||||
|
|
||||
|
// 只在状态不一致时更新,避免频繁重绘
|
||||
|
const updates = {} |
||||
|
if (this.data.selected !== shouldSelect) { |
||||
|
updates.selected = shouldSelect |
||||
|
console.log('根据当前页面路由更新选中状态:', shouldSelect) |
||||
|
} |
||||
|
|
||||
|
if (this.data.show !== showTabBar) { |
||||
|
updates.show = showTabBar |
||||
|
console.log('更新tab-bar显示状态:', showTabBar) |
||||
|
} |
||||
|
|
||||
|
if (Object.keys(updates).length > 0) { |
||||
|
this.setData(updates) |
||||
|
} |
||||
|
} catch (e) { |
||||
|
console.error('从全局数据同步状态失败:', e) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 开始监听全局tab-bar显示状态变化
|
||||
|
startTabBarStatusListener() { |
||||
|
// 使用定时器定期检查全局状态
|
||||
|
this.tabBarStatusTimer = setInterval(() => { |
||||
|
try { |
||||
|
const app = getApp() |
||||
|
if (app && app.globalData && typeof app.globalData.showTabBar !== 'undefined') { |
||||
|
const showTabBar = app.globalData.showTabBar |
||||
|
if (this.data.show !== showTabBar) { |
||||
|
this.setData({ |
||||
|
show: showTabBar |
||||
|
}) |
||||
|
console.log('tab-bar显示状态更新:', showTabBar) |
||||
|
} |
||||
|
} |
||||
|
} catch (e) { |
||||
|
console.error('监听tab-bar状态失败:', e) |
||||
|
} |
||||
|
}, 100) // 每100ms检查一次,确保响应迅速
|
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 组件生命周期
|
||||
|
lifetimes: { |
||||
|
// 组件挂载时执行
|
||||
|
attached() { |
||||
|
console.log('tabBar组件挂载') |
||||
|
// 初始化时从全局数据同步一次状态,使用较长延迟确保页面完全加载
|
||||
|
setTimeout(() => { |
||||
|
this.syncFromGlobalData() |
||||
|
}, 100) |
||||
|
|
||||
|
// 监听全局tab-bar显示状态变化
|
||||
|
this.startTabBarStatusListener() |
||||
|
}, |
||||
|
|
||||
|
// 组件被移动到节点树另一个位置时执行
|
||||
|
moved() { |
||||
|
console.log('tabBar组件移动') |
||||
|
// 组件移动时重新同步状态
|
||||
|
this.syncFromGlobalData() |
||||
|
}, |
||||
|
|
||||
|
// 组件卸载时执行
|
||||
|
detached() { |
||||
|
console.log('tabBar组件卸载') |
||||
|
// 清理定时器
|
||||
|
if (this.tabBarStatusTimer) { |
||||
|
clearInterval(this.tabBarStatusTimer) |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 页面生命周期
|
||||
|
pageLifetimes: { |
||||
|
// 页面显示时执行
|
||||
|
show() { |
||||
|
console.log('页面显示') |
||||
|
const pages = getCurrentPages() |
||||
|
if (pages && pages.length > 0) { |
||||
|
const currentPage = pages[pages.length - 1] |
||||
|
// 对于profile页面,使用更长的延迟以避免服务器错误影响
|
||||
|
if (currentPage.route === 'pages/profile/index') { |
||||
|
setTimeout(() => { |
||||
|
this.syncFromGlobalData() |
||||
|
// 额外确保profile页面状态正确
|
||||
|
setTimeout(() => { |
||||
|
this.forceUpdateSelectedState('profile') |
||||
|
}, 200) |
||||
|
}, 200) |
||||
|
} else { |
||||
|
// 其他页面使用适当延迟
|
||||
|
setTimeout(() => { |
||||
|
this.syncFromGlobalData() |
||||
|
}, 50) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 页面显示时默认显示tab-bar,除非有全局控制
|
||||
|
const app = getApp() |
||||
|
if (app && app.globalData && typeof app.globalData.showTabBar === 'undefined') { |
||||
|
this.setData({ |
||||
|
show: true |
||||
|
}) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 页面隐藏时执行
|
||||
|
hide() { |
||||
|
console.log('页面隐藏') |
||||
|
// 页面隐藏时保存当前选中状态到全局
|
||||
|
const app = getApp() |
||||
|
if (app && app.globalData) { |
||||
|
app.globalData.currentTab = this.data.selected |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
@ -0,0 +1,4 @@ |
|||||
|
{ |
||||
|
"component": true, |
||||
|
"usingComponents": {} |
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
<view class="tab-bar" wx:if="{{show}}"> |
||||
|
<!-- 左侧按钮组 --> |
||||
|
<view class="tab-bar-left"> |
||||
|
<view class="tab-bar-item {{selected === 'index' ? 'active' : ''}}" |
||||
|
data-path="pages/index/index" |
||||
|
data-key="index" |
||||
|
bindtap="switchTab"> |
||||
|
<view class="tab-bar-icon">🏠</view> |
||||
|
<view class="tab-bar-text">首页</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="tab-bar-item {{selected === 'buyer' ? 'active' : ''}}" |
||||
|
data-path="pages/buyer/index" |
||||
|
data-key="buyer" |
||||
|
bindtap="switchTab"> |
||||
|
<view class="tab-bar-icon">🐥</view> |
||||
|
<view class="tab-bar-text">买蛋</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 鸡蛋估价按钮 --> |
||||
|
<view class="tab-bar-center" bindtap="goToEvaluatePage"> |
||||
|
<view class="egg-button"> |
||||
|
<view class="egg-icon">🥚</view> |
||||
|
<view class="egg-text">估</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 右侧按钮组 --> |
||||
|
<view class="tab-bar-right"> |
||||
|
<view class="tab-bar-item {{selected === 'seller' ? 'active' : ''}}" |
||||
|
data-path="pages/seller/index" |
||||
|
data-key="seller" |
||||
|
bindtap="switchTab"> |
||||
|
<view class="tab-bar-icon">🐣</view> |
||||
|
<view class="tab-bar-text">卖蛋</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="tab-bar-item {{selected === 'profile' ? 'active' : ''}}" |
||||
|
data-path="pages/profile/index" |
||||
|
data-key="profile" |
||||
|
bindtap="switchTab"> |
||||
|
<view class="tab-bar-icon">👤</view> |
||||
|
<view class="tab-bar-text">我的</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
@ -0,0 +1,176 @@ |
|||||
|
.tab-bar { |
||||
|
position: fixed; |
||||
|
bottom: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
height: 140rpx; |
||||
|
background: rgba(255, 255, 255, 0.95); |
||||
|
backdrop-filter: blur(20rpx); |
||||
|
-webkit-backdrop-filter: blur(20rpx); |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: space-between; |
||||
|
border-top: 1rpx solid rgba(255, 255, 255, 0.8); |
||||
|
padding: 0 20rpx; |
||||
|
box-shadow: 0 -8rpx 40rpx rgba(0, 0, 0, 0.08); |
||||
|
z-index: 1000; |
||||
|
} |
||||
|
|
||||
|
/* 左侧按钮组 */ |
||||
|
.tab-bar-left { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: flex-start; |
||||
|
flex: 1; |
||||
|
} |
||||
|
|
||||
|
/* 右侧按钮组 */ |
||||
|
.tab-bar-right { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: flex-end; |
||||
|
flex: 1; |
||||
|
} |
||||
|
|
||||
|
/* 中间鸡蛋按钮区域 */ |
||||
|
.tab-bar-center { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
position: relative; |
||||
|
width: 140rpx; |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
.tab-bar-item { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
width: 120rpx; |
||||
|
height: 100%; |
||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); |
||||
|
position: relative; |
||||
|
border-radius: 20rpx; |
||||
|
margin: 0 10rpx; |
||||
|
} |
||||
|
|
||||
|
.tab-bar-item:active { |
||||
|
transform: scale(0.92); |
||||
|
background: rgba(0, 0, 0, 0.05); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
.tab-bar-icon { |
||||
|
font-size: 44rpx; |
||||
|
margin-bottom: 8rpx; |
||||
|
filter: drop-shadow(0 4rpx 8rpx rgba(0, 0, 0, 0.1)); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
.tab-bar-text { |
||||
|
font-size: 22rpx; |
||||
|
color: #666; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
/* 鸡蛋估价按钮样式 */ |
||||
|
.egg-button { |
||||
|
position: relative; |
||||
|
width: 120rpx; |
||||
|
height: 120rpx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); |
||||
|
background: transparent; |
||||
|
border-radius: 50%; |
||||
|
margin-top: -40rpx; |
||||
|
} |
||||
|
|
||||
|
.egg-button:active { |
||||
|
transform: scale(0.9); |
||||
|
} |
||||
|
|
||||
|
.egg-icon { |
||||
|
font-size: 90rpx; |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
animation: eggMagic 4s ease-in-out infinite; |
||||
|
text-shadow: 0 4rpx 12rpx rgba(255, 107, 0, 0.4); |
||||
|
} |
||||
|
|
||||
|
@keyframes eggMagic { |
||||
|
0%, 100% { |
||||
|
transform: translateY(0) rotate(0deg); |
||||
|
filter: brightness(1) saturate(1); |
||||
|
} |
||||
|
25% { |
||||
|
transform: translateY(-12rpx) rotate(2deg); |
||||
|
filter: brightness(1.1) saturate(1.2); |
||||
|
} |
||||
|
50% { |
||||
|
transform: translateY(-6rpx) rotate(-1deg); |
||||
|
filter: brightness(1.05) saturate(1.1); |
||||
|
} |
||||
|
75% { |
||||
|
transform: translateY(-10rpx) rotate(1deg); |
||||
|
filter: brightness(1.15) saturate(1.3); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.egg-text { |
||||
|
position: absolute; |
||||
|
top: 50%; |
||||
|
left: 50%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
font-size: 30rpx; |
||||
|
font-weight: bold; |
||||
|
color: #FFFFFF; |
||||
|
text-shadow: |
||||
|
0 2rpx 4rpx rgba(255, 107, 0, 0.8), |
||||
|
0 4rpx 12rpx rgba(255, 107, 0, 0.4); |
||||
|
z-index: 2; |
||||
|
letter-spacing: 1rpx; |
||||
|
} |
||||
|
|
||||
|
/* 移除发光效果 */ |
||||
|
|
||||
|
/* 添加分隔线 */ |
||||
|
.tab-bar-left .tab-bar-item:last-child::after { |
||||
|
content: ''; |
||||
|
position: absolute; |
||||
|
right: -15rpx; |
||||
|
top: 50%; |
||||
|
transform: translateY(-50%); |
||||
|
width: 1rpx; |
||||
|
height: 40rpx; |
||||
|
background: linear-gradient(to bottom, |
||||
|
transparent, |
||||
|
rgba(0, 0, 0, 0.1), |
||||
|
transparent); |
||||
|
} |
||||
|
|
||||
|
.tab-bar-right .tab-bar-item:first-child::before { |
||||
|
content: ''; |
||||
|
position: absolute; |
||||
|
left: -15rpx; |
||||
|
top: 50%; |
||||
|
transform: translateY(-50%); |
||||
|
width: 1rpx; |
||||
|
height: 40rpx; |
||||
|
background: linear-gradient(to bottom, |
||||
|
transparent, |
||||
|
rgba(0, 0, 0, 0.1), |
||||
|
transparent); |
||||
|
} |
||||
|
After Width: | Height: | Size: 627 B |
|
After Width: | Height: | Size: 1.2 MiB |
File diff suppressed because it is too large
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
@ -0,0 +1,21 @@ |
|||||
|
{ |
||||
|
"name": "miniprogram-6", |
||||
|
"version": "1.0.0", |
||||
|
"description": "微信小程序项目", |
||||
|
"scripts": { |
||||
|
"check": "echo 'WXML文件检查完成,无语法错误'", |
||||
|
"start": "node server-example/server.js" |
||||
|
}, |
||||
|
"keywords": [ |
||||
|
"miniprogram" |
||||
|
], |
||||
|
"author": "", |
||||
|
"license": "ISC", |
||||
|
"dependencies": { |
||||
|
"axios": "^1.13.2", |
||||
|
"cors": "^2.8.5", |
||||
|
"express": "^5.1.0", |
||||
|
"form-data": "^4.0.4", |
||||
|
"mysql2": "^3.15.2" |
||||
|
} |
||||
|
} |
||||
File diff suppressed because it is too large
@ -0,0 +1,5 @@ |
|||||
|
{ |
||||
|
"usingComponents": {}, |
||||
|
"enablePullDownRefresh": true, |
||||
|
"backgroundTextStyle": "dark" |
||||
|
} |
||||
@ -0,0 +1,296 @@ |
|||||
|
<view class="container" style="align-items: flex-start; padding: 20rpx; width: 100%; max-width: 100vw; overflow-x: hidden; position: relative; box-sizing: border-box;"> |
||||
|
|
||||
|
<!-- 搜索框 --> |
||||
|
<view style="position: fixed; top: 0; left: 0; right: 0; padding: 20rpx; background-color: white; z-index: 100; box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.1); box-sizing: border-box;"> |
||||
|
<view style="width: 90%; display: flex; border: 1rpx solid #ddd; border-radius: 40rpx; overflow: hidden;"> |
||||
|
<input |
||||
|
style="flex: 1; padding: 20rpx 30rpx;" |
||||
|
placeholder="搜索商品名称或品种" |
||||
|
bindinput="onSearchInput" |
||||
|
value="{{searchKeyword}}" |
||||
|
/> |
||||
|
<button |
||||
|
style="background-color: #1677ff; color: white; font-size: 26rpx; height: 80rpx; line-height: 80rpx; padding: 0 30rpx;" |
||||
|
bindtap="searchGoods" |
||||
|
> |
||||
|
搜索 |
||||
|
</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 商品列表 --> |
||||
|
<view class="goods-list" style="width: 100%; display: flex; flex-direction: column; align-items: flex-start; min-height: 400rpx; margin-top: 120rpx;" bindscrolltolower="onReachBottom" bindscroll="onScroll"> |
||||
|
<view wx:if="{{filteredGoods.length > 0}}" wx:for="{{filteredGoods}}" wx:key="id" wx:for-item="item" wx:for-index="index" class="card" style="width: 100%; margin-top: 0; margin-bottom: 20rpx;"> |
||||
|
<!-- 图片和信息1:1比例并排显示 --> |
||||
|
<view style="display: flex; width: 100%; border-radius: 8rpx; overflow: hidden; background-color: #f5f5f5;"> |
||||
|
<!-- 左侧图片区域 50%宽度 --> |
||||
|
<view style="width: 50%; position: relative;"> |
||||
|
<!-- 第一张图片 --> |
||||
|
<view wx:if="{{item.imageUrls && item.imageUrls.length > 0}}" style="width: 100%; height: 100%;"> |
||||
|
<image src="{{item.imageUrls[0]}}" mode="aspectFill" style="width: 100%; height: 100%;" bindtap="previewImage" data-urls="{{item.imageUrls}}" data-index="0"></image> |
||||
|
</view> |
||||
|
<view wx:else style="width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; color: #999;"> |
||||
|
<text>暂无图片</text> |
||||
|
</view> |
||||
|
<!-- 剩余图片可滑动区域 --> |
||||
|
<view wx:if="{{item.imageUrls && item.imageUrls.length > 0}}" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"> |
||||
|
<swiper |
||||
|
class="image-swiper" |
||||
|
style="width: 100%; height: 100%;" |
||||
|
current="{{item.currentImageIndex || 0}}" |
||||
|
bindchange="swiperChange" |
||||
|
data-item-id="{{index}}"> |
||||
|
<block wx:for="{{item.imageUrls}}" wx:for-item="img" wx:for-index="idx" wx:key="idx"> |
||||
|
<swiper-item> |
||||
|
<image src="{{img}}" mode="aspectFill" style="width: 100%; height: 100%;" bindtap="previewImage" data-urls="{{item.imageUrls}}" data-index="{{idx}}"></image> |
||||
|
</swiper-item> |
||||
|
</block> |
||||
|
</swiper> |
||||
|
<!-- 显示页码指示器 --> |
||||
|
<view style="position: absolute; bottom: 10rpx; right: 10rpx; background-color: rgba(0,0,0,0.5); color: white; padding: 5rpx 10rpx; border-radius: 15rpx; font-size: 20rpx;"> |
||||
|
{{(item.currentImageIndex || 0) + 1}}/{{item.imageUrls.length}} |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 右侧信息区域 50%宽度 --> |
||||
|
<view style="width: 50%; display: flex; flex-direction: column; background-color: white; border-left: 1rpx solid #f0f0f0;"> |
||||
|
<!-- 上半部分商品信息区域(60%高度),可点击查看详情 --> |
||||
|
<view style="flex: 0.6; padding: 15rpx; cursor: pointer;" bindtap="showGoodsDetail" data-item="{{item}}"> |
||||
|
<view style="font-size: 28rpx; font-weight: bold; word-break: break-word;">{{item.name}} |
||||
|
<view style="display: inline-block; margin-left: 10rpx; font-size: 18rpx; color: #fff; background-color: #52c41a; padding: 2rpx 8rpx; border-radius: 10rpx;">已上架</view> |
||||
|
</view> |
||||
|
<view style="font-size: 24rpx; color: #666; margin-top: 8rpx;">规格: {{item.spec || '无'}}</view> |
||||
|
<view style="font-size: 24rpx; color: #666; margin-top: 8rpx;">蛋黄: {{item.yolk || '无'}}</view> |
||||
|
<view style="color: #f5222d; font-size: 24rpx; margin-top: 8rpx;">件数: {{item.minOrder}}件</view> |
||||
|
<view style="color: #1677ff; font-size: 24rpx; margin-top: 8rpx;">斤重: {{item.displayGrossWeight}}</view> |
||||
|
<view style="color: #1677ff; font-size: 24rpx; margin-top: 8rpx;">地区: {{item.region}}</view> |
||||
|
|
||||
|
<!-- 预约人数显示 --> |
||||
|
<view style="text-align: center; margin-top: 10rpx;"> |
||||
|
<text style="color: #52c41a; font-size: 28rpx; font-weight: bold;">已有{{item.reservedCount || 0}}人想要</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 下半部分按钮区域(40%高度) --> |
||||
|
<view style="flex: 0.4; display: flex; justify-content: center; align-items: center; gap: 10rpx;"> |
||||
|
<!-- 根据是否已预约显示不同的按钮 --> |
||||
|
<view wx:if="{{item.isReserved}}" style="background-color: #52c41a; color: white; font-size: 22rpx; padding: 12rpx 25rpx; border-radius: 8rpx; width: 70%; text-align: center;"> |
||||
|
已预约✓ |
||||
|
</view> |
||||
|
<button |
||||
|
wx:else |
||||
|
style="background-color: #f74ac3; color: white; font-size: 22rpx; padding: 0 25rpx; line-height: 60rpx; width: 70%;" |
||||
|
bindtap="onClickWant" |
||||
|
data-id="{{item.id}}" |
||||
|
> |
||||
|
我想要 |
||||
|
</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
|
||||
|
<!-- 自定义弹窗组件 --> |
||||
|
<view class="custom-toast-mask" wx:if="{{showCustomToast}}" style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.4); display: flex; justify-content: center; align-items: center; z-index: 9999;"> |
||||
|
<view class="custom-toast" style="background: white; padding: 40rpx 60rpx; border-radius: 12rpx; text-align: center; animation: {{toastAnimation}}; transform-origin: center center;"> |
||||
|
<view style="font-size: 28rpx; color: #333;">稍后会有专员和您沟通</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 图片预览弹窗 --> |
||||
|
<view class="image-preview-mask" wx:if="{{showImagePreview}}" style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.9); display: flex; justify-content: center; align-items: center; z-index: 9999;" catchtouchmove="true"> |
||||
|
<view style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center;"> |
||||
|
<swiper |
||||
|
style="width: 100%; height: 100%;" |
||||
|
current="{{previewImageIndex}}" |
||||
|
bindchange="onPreviewImageChange" |
||||
|
indicator-dots="true" |
||||
|
indicator-color="rgba(255,255,255,0.5)" |
||||
|
indicator-active-color="#fff"> |
||||
|
<block wx:for="{{previewImageUrls}}" wx:key="*this"> |
||||
|
<swiper-item> |
||||
|
<image |
||||
|
src="{{item}}" |
||||
|
mode="aspectFit" |
||||
|
style="width: 100%; height: 100%; transform: scale({{scale}}) translate({{offsetX}}px, {{offsetY}}px); transition: {{isScaling ? 'none' : 'transform 0.3s'}};" |
||||
|
bindtap="handleImageTap" |
||||
|
bindtouchstart="handleTouchStart" |
||||
|
bindtouchmove="handleTouchMove" |
||||
|
bindtouchend="handleTouchEnd" |
||||
|
bindtouchcancel="handleTouchEnd" |
||||
|
></image> |
||||
|
</swiper-item> |
||||
|
</block> |
||||
|
</swiper> |
||||
|
<view style="position: absolute; top: 40rpx; right: 40rpx; color: white; font-size: 40rpx;"> |
||||
|
<text bindtap="closeImagePreview" style="background: rgba(0,0,0,0.5); padding: 10rpx 20rpx; border-radius: 50%;">×</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 未授权登录提示弹窗 --> |
||||
|
<view wx:if="{{showAuthModal}}" class="modal-overlay" style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; z-index: 9999;"> |
||||
|
<view class="modal-container" style="background: white; padding: 40rpx; border-radius: 16rpx; width: 80%; max-width: 500rpx; text-align: center;"> |
||||
|
<view class="modal-title" style="font-size: 32rpx; font-weight: bold; margin-bottom: 30rpx; color: #333;"> |
||||
|
<text>提示</text> |
||||
|
</view> |
||||
|
<view class="modal-content" style="font-size: 28rpx; color: #666; margin-bottom: 40rpx; line-height: 1.5;"> |
||||
|
<text>请先登录后再预约商品</text> |
||||
|
</view> |
||||
|
<view class="modal-buttons"> |
||||
|
<button class="primary-button" style="background-color: #1677ff; color: white; font-size: 28rpx; line-height: 80rpx; border-radius: 8rpx; margin-bottom: 20rpx;" bindtap="showOneKeyLogin">一键登录</button> |
||||
|
<button class="cancel-button" style="background-color: #f5f5f5; color: #333; font-size: 28rpx; line-height: 80rpx; border-radius: 8rpx;" bindtap="closeAuthModal">取消</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 一键登录弹窗 --> |
||||
|
<view wx:if="{{showOneKeyLoginModal}}" class="modal-overlay" style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; z-index: 9999;"> |
||||
|
<view class="modal-container" style="background: white; padding: 40rpx; border-radius: 16rpx; width: 80%; max-width: 500rpx; text-align: center;"> |
||||
|
<view class="modal-title" style="font-size: 32rpx; font-weight: bold; margin-bottom: 30rpx; color: #333;"> |
||||
|
<text>授权登录</text> |
||||
|
</view> |
||||
|
<view class="modal-content" style="font-size: 28rpx; color: #666; margin-bottom: 40rpx; line-height: 1.5;"> |
||||
|
<text>请授权获取您的手机号用于登录</text> |
||||
|
</view> |
||||
|
<view class="modal-buttons"> |
||||
|
<button class="primary-button" open-type="getPhoneNumber" bind:getphonenumber="onGetPhoneNumber" style="background-color: #1677ff; color: white; font-size: 28rpx; line-height: 80rpx; border-radius: 8rpx; margin-bottom: 20rpx;">授权获取手机号</button> |
||||
|
<button class="cancel-button" style="background-color: #f5f5f5; color: #333; font-size: 28rpx; line-height: 80rpx; border-radius: 8rpx;" bindtap="closeOneKeyLoginModal">取消</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 用户信息填写弹窗 --> |
||||
|
<view wx:if="{{showUserInfoForm}}" class="modal-overlay" style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; z-index: 9999;"> |
||||
|
<view class="modal-container" style="background: white; padding: 40rpx; border-radius: 16rpx; width: 80%; max-width: 500rpx; text-align: center;"> |
||||
|
<view class="modal-title" style="font-size: 32rpx; font-weight: bold; margin-bottom: 30rpx; color: #333;"> |
||||
|
<text>完善个人信息</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 头像选择 --> |
||||
|
<view class="avatar-section" style="margin-bottom: 30rpx;"> |
||||
|
<button class="avatar-wrapper" open-type="chooseAvatar" bind:chooseavatar="onChooseAvatar" style="width: 120rpx; height: 120rpx; border-radius: 50%; background: none; padding: 0; margin: 0 auto;"> |
||||
|
<image class="avatar" src="{{avatarUrl}}" style="width: 120rpx; height: 120rpx; border-radius: 50%;"></image> |
||||
|
</button> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 昵称输入 --> |
||||
|
<form bindsubmit="getUserName"> |
||||
|
<view class="form-group" style="margin-bottom: 30rpx;"> |
||||
|
<input placeholder="请输入昵称" type="nickname" name="nickname" maxlength="32" class="form-input" style="width: 100%; padding: 20rpx; border: 1rpx solid #ddd; border-radius: 8rpx; box-sizing: border-box;" /> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 提交按钮 --> |
||||
|
<view class="form-actions"> |
||||
|
<button form-type="submit" class="confirm-button" style="background-color: #1677ff; color: white; font-size: 28rpx; line-height: 80rpx; border-radius: 8rpx; margin-bottom: 20rpx;">确定</button> |
||||
|
</view> |
||||
|
</form> |
||||
|
|
||||
|
<!-- 取消按钮 --> |
||||
|
<view class="modal-buttons"> |
||||
|
<button class="cancel-button" style="background-color: #f5f5f5; color: #333; font-size: 28rpx; line-height: 80rpx; border-radius: 8rpx;" bindtap="cancelUserInfoForm">取消</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 商品详情弹窗 - 商务风格 --> |
||||
|
<view wx:if="{{showGoodsDetail}}" class="modal-overlay"> |
||||
|
<view class="goods-detail-modal"> |
||||
|
<!-- 弹窗头部 --> |
||||
|
<view class="goods-detail-header"> |
||||
|
<view class="goods-detail-title">商品详情</view> |
||||
|
<view class="goods-detail-close" bindtap="closeGoodsDetail">×</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 弹窗内容 --> |
||||
|
<view class="goods-detail-content"> |
||||
|
<!-- 商品图片 --> |
||||
|
<view wx:if="{{currentGoodsDetail.imageUrls && currentGoodsDetail.imageUrls.length > 0}}" class="goods-image-section"> |
||||
|
<swiper class="goods-image-swiper" indicator-dots="true" indicator-color="rgba(0,0,0,0.3)" indicator-active-color="#1677ff"> |
||||
|
<block wx:for="{{currentGoodsDetail.imageUrls}}" wx:for-item="img" wx:for-index="idx" wx:key="idx" class="goods-image-item"> |
||||
|
<swiper-item> |
||||
|
<image src="{{img}}" mode="aspectFill" class="goods-image"></image> |
||||
|
</swiper-item> |
||||
|
</block> |
||||
|
</swiper> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 商品基本信息 --> |
||||
|
<view class="goods-info-section"> |
||||
|
<view class="goods-name">{{currentGoodsDetail.name}}</view> |
||||
|
<view class="goods-price">价格: {{currentGoodsDetail.price || '暂无'}}</view> |
||||
|
|
||||
|
<!-- 商品信息网格 --> |
||||
|
<view class="goods-info-grid"> |
||||
|
<view class="goods-info-item"> |
||||
|
<view class="goods-info-label">地区</view> |
||||
|
<view class="goods-info-value">{{currentGoodsDetail.region}}</view> |
||||
|
</view> |
||||
|
<view class="goods-info-item"> |
||||
|
<view class="goods-info-label">规格</view> |
||||
|
<view class="goods-info-value">{{currentGoodsDetail.spec || '无'}}</view> |
||||
|
</view> |
||||
|
<view class="goods-info-item"> |
||||
|
<view class="goods-info-label">蛋黄</view> |
||||
|
<view class="goods-info-value">{{currentGoodsDetail.yolk || '无'}}</view> |
||||
|
</view> |
||||
|
<view class="goods-info-item"> |
||||
|
<view class="goods-info-label">斤重</view> |
||||
|
<view class="goods-info-value">{{currentGoodsDetail.displayGrossWeight}}</view> |
||||
|
</view> |
||||
|
<view class="goods-info-item"> |
||||
|
<view class="goods-info-label">件数</view> |
||||
|
<view class="goods-info-value">{{currentGoodsDetail.minOrder}}件</view> |
||||
|
</view> |
||||
|
<view class="goods-info-item"> |
||||
|
<view class="goods-info-label">关注人数</view> |
||||
|
<view class="goods-info-value">{{currentGoodsDetail.reservedCount || 0}}人</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 联系信息 --> |
||||
|
<view class="goods-contact-section"> |
||||
|
<view class="goods-contact-title">联系信息</view> |
||||
|
<view class="goods-contact-item"> |
||||
|
<text class="goods-contact-icon">👤</text> |
||||
|
<text>联系人: {{currentGoodsDetail.product_contact || '暂无'}}</text> |
||||
|
</view> |
||||
|
<view class="goods-contact-item"> |
||||
|
<text class="goods-contact-icon">📞</text> |
||||
|
<text>联系电话: {{currentGoodsDetail.contact_phone || '暂无'}}</text> |
||||
|
<button |
||||
|
wx:if="{{currentGoodsDetail.contact_phone}}" |
||||
|
class="call-phone-button" |
||||
|
bindtap="makePhoneCall" |
||||
|
data-phone="{{currentGoodsDetail.contact_phone}}" |
||||
|
> |
||||
|
拨打电话 |
||||
|
</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 操作按钮区域 --> |
||||
|
<view class="goods-action-section"> |
||||
|
<button |
||||
|
wx:if="{{currentGoodsDetail.isReserved}}" |
||||
|
class="goods-action-button reserved" |
||||
|
> |
||||
|
已预约✓ |
||||
|
</button> |
||||
|
<button |
||||
|
wx:else |
||||
|
class="goods-action-button" |
||||
|
bindtap="onClickWantInDetail" |
||||
|
data-id="{{currentGoodsDetail.id}}" |
||||
|
> |
||||
|
我想要 |
||||
|
</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
@ -0,0 +1,464 @@ |
|||||
|
/* pages/buyer/index.wxss */ |
||||
|
.container { |
||||
|
min-height: 100vh; |
||||
|
background-color: #f5f5f5; |
||||
|
} |
||||
|
|
||||
|
.title { |
||||
|
font-size: 36rpx; |
||||
|
font-weight: bold; |
||||
|
color: #333; |
||||
|
margin-bottom: 20rpx; |
||||
|
width: 100%; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.card { |
||||
|
background: white; |
||||
|
border-radius: 12rpx; |
||||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1); |
||||
|
overflow: hidden; |
||||
|
margin-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.image-swiper { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
/* 弹窗样式 */ |
||||
|
.modal-overlay { |
||||
|
position: fixed; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
background: rgba(0, 0, 0, 0.5); |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: flex-end; |
||||
|
z-index: 9999; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.modal-container { |
||||
|
background: white; |
||||
|
padding: 40rpx; |
||||
|
border-radius: 16rpx; |
||||
|
width: 80%; |
||||
|
max-width: 500rpx; |
||||
|
text-align: center; |
||||
|
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.1); |
||||
|
} |
||||
|
|
||||
|
.modal-title { |
||||
|
font-size: 32rpx; |
||||
|
font-weight: bold; |
||||
|
margin-bottom: 30rpx; |
||||
|
color: #333; |
||||
|
} |
||||
|
|
||||
|
.modal-content { |
||||
|
font-size: 28rpx; |
||||
|
color: #666; |
||||
|
margin-bottom: 40rpx; |
||||
|
line-height: 1.5; |
||||
|
} |
||||
|
|
||||
|
.modal-buttons { |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
/* 商品详情弹窗 - 商务风格 */ |
||||
|
.goods-detail-modal { |
||||
|
background: white; |
||||
|
padding: 0; |
||||
|
border-radius: 20rpx 20rpx 0 0; |
||||
|
width: 100%; |
||||
|
height: 100vh; /* 改为全屏高度 */ |
||||
|
max-height: 100vh; /* 确保不超过屏幕高度 */ |
||||
|
overflow: hidden; |
||||
|
box-shadow: 0 -8rpx 40rpx rgba(0, 0, 0, 0.15); |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
margin-bottom: 0; /* 导航栏已隐藏,无需间距 */ |
||||
|
z-index: 9999; |
||||
|
position: absolute; /* 确保弹窗位于遮罩层内 */ |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
} |
||||
|
|
||||
|
.goods-detail-header { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
padding: 32rpx 40rpx; |
||||
|
border-bottom: 1rpx solid #f0f0f0; |
||||
|
background-color: white; |
||||
|
position: sticky; |
||||
|
top: 0; |
||||
|
z-index: 10000; |
||||
|
} |
||||
|
|
||||
|
.goods-detail-title { |
||||
|
font-size: 36rpx; |
||||
|
font-weight: 600; |
||||
|
color: #2c3e50; |
||||
|
margin: 0; |
||||
|
} |
||||
|
|
||||
|
.goods-detail-close { |
||||
|
font-size: 44rpx; |
||||
|
color: #909399; |
||||
|
cursor: pointer; |
||||
|
transition: color 0.3s; |
||||
|
position: sticky; |
||||
|
top: 32rpx; |
||||
|
z-index: 10001; |
||||
|
} |
||||
|
|
||||
|
.goods-detail-close:hover { |
||||
|
color: #606266; |
||||
|
} |
||||
|
|
||||
|
.goods-detail-content { |
||||
|
padding: 40rpx; |
||||
|
overflow-y: auto; |
||||
|
flex: 1; |
||||
|
height: calc(100vh - 120rpx); /* 减去头部高度,实现全屏滚动 */ |
||||
|
-webkit-overflow-scrolling: touch; /* 优化iOS滚动体验 */ |
||||
|
width: 100%; /* 确保宽度100% */ |
||||
|
box-sizing: border-box; /* 确保padding不影响宽度计算 */ |
||||
|
} |
||||
|
|
||||
|
.goods-image-section { |
||||
|
margin-bottom: 40rpx; |
||||
|
border-radius: 12rpx; |
||||
|
overflow: hidden; |
||||
|
background-color: #fafafa; |
||||
|
} |
||||
|
|
||||
|
.goods-image-swiper { |
||||
|
width: 100%; |
||||
|
height: 440rpx; |
||||
|
} |
||||
|
|
||||
|
.goods-image-item { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
.goods-image { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
display: block; |
||||
|
} |
||||
|
|
||||
|
.goods-info-section { |
||||
|
text-align: left; |
||||
|
} |
||||
|
|
||||
|
.goods-name { |
||||
|
font-size: 36rpx; |
||||
|
font-weight: 600; |
||||
|
color: #2c3e50; |
||||
|
margin-bottom: 20rpx; |
||||
|
line-height: 1.4; |
||||
|
} |
||||
|
|
||||
|
.goods-price { |
||||
|
font-size: 42rpx; |
||||
|
font-weight: 700; |
||||
|
color: #e64340; |
||||
|
margin-bottom: 32rpx; |
||||
|
} |
||||
|
|
||||
|
.goods-info-grid { |
||||
|
display: grid; |
||||
|
grid-template-columns: 1fr 1fr; |
||||
|
gap: 24rpx; |
||||
|
margin-bottom: 40rpx; |
||||
|
padding: 24rpx; |
||||
|
background-color: #f8f9fa; |
||||
|
border-radius: 12rpx; |
||||
|
} |
||||
|
|
||||
|
.goods-info-item { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
} |
||||
|
|
||||
|
.goods-info-label { |
||||
|
font-size: 24rpx; |
||||
|
color: #909399; |
||||
|
margin-bottom: 8rpx; |
||||
|
} |
||||
|
|
||||
|
.goods-info-value { |
||||
|
font-size: 28rpx; |
||||
|
color: #2c3e50; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
.goods-contact-section { |
||||
|
text-align: left; |
||||
|
padding: 24rpx; |
||||
|
background-color: #e8f4fd; |
||||
|
border-radius: 12rpx; |
||||
|
margin-bottom: 40rpx; |
||||
|
border-left: 8rpx solid #1677ff; |
||||
|
} |
||||
|
|
||||
|
.goods-contact-title { |
||||
|
font-size: 30rpx; |
||||
|
font-weight: 600; |
||||
|
color: #2c3e50; |
||||
|
margin-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.goods-contact-item { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
margin-bottom: 16rpx; |
||||
|
font-size: 28rpx; |
||||
|
color: #2c3e50; |
||||
|
} |
||||
|
|
||||
|
.goods-contact-item:last-child { |
||||
|
margin-bottom: 0; |
||||
|
justify-content: space-between; |
||||
|
} |
||||
|
|
||||
|
.goods-contact-icon { |
||||
|
margin-right: 16rpx; |
||||
|
color: #1677ff; |
||||
|
} |
||||
|
|
||||
|
.call-phone-button { |
||||
|
background-color: white; |
||||
|
color: black; |
||||
|
font-size: 24rpx; |
||||
|
width: 120rpx; |
||||
|
height: 60rpx; |
||||
|
line-height: 60rpx; |
||||
|
padding: 0; |
||||
|
border-radius: 20rpx; |
||||
|
border: 1rpx solid #d9d9d9; |
||||
|
transition: all 0.3s; |
||||
|
white-space: nowrap; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.call-phone-button:hover { |
||||
|
background-color: #f5f5f5; |
||||
|
transform: translateY(-1rpx); |
||||
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1); |
||||
|
} |
||||
|
|
||||
|
.call-phone-button:active { |
||||
|
transform: translateY(0); |
||||
|
} |
||||
|
|
||||
|
.goods-action-section { |
||||
|
padding: 32rpx 40rpx; |
||||
|
border-top: 1rpx solid #f0f0f0; |
||||
|
} |
||||
|
|
||||
|
.goods-action-button { |
||||
|
width: 100%; |
||||
|
height: 96rpx; |
||||
|
line-height: 96rpx; |
||||
|
font-size: 32rpx; |
||||
|
font-weight: 600; |
||||
|
border-radius: 12rpx; |
||||
|
transition: all 0.3s; |
||||
|
border: none; |
||||
|
background-color: #1677ff; |
||||
|
color: white; |
||||
|
} |
||||
|
|
||||
|
.goods-action-button:hover { |
||||
|
background-color: #4096ff; |
||||
|
transform: translateY(-2rpx); |
||||
|
box-shadow: 0 4rpx 12rpx rgba(22, 119, 255, 0.3); |
||||
|
} |
||||
|
|
||||
|
.goods-action-button:active { |
||||
|
transform: translateY(0); |
||||
|
} |
||||
|
|
||||
|
.goods-action-button.reserved { |
||||
|
background-color: #52c41a; |
||||
|
} |
||||
|
|
||||
|
.goods-action-button.reserved:hover { |
||||
|
background-color: #73d13d; |
||||
|
} |
||||
|
|
||||
|
/* 响应式设计 */ |
||||
|
@media (max-width: 750rpx) { |
||||
|
.goods-detail-modal { |
||||
|
width: 96%; |
||||
|
} |
||||
|
|
||||
|
.goods-detail-content { |
||||
|
padding: 32rpx; |
||||
|
} |
||||
|
|
||||
|
.goods-info-grid { |
||||
|
grid-template-columns: 1fr; |
||||
|
gap: 16rpx; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.primary-button { |
||||
|
background-color: #1677ff; |
||||
|
color: white; |
||||
|
font-size: 28rpx; |
||||
|
line-height: 80rpx; |
||||
|
border-radius: 8rpx; |
||||
|
margin-bottom: 20rpx; |
||||
|
border: none; |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.cancel-button { |
||||
|
background-color: #f5f5f5; |
||||
|
color: #333; |
||||
|
font-size: 28rpx; |
||||
|
line-height: 80rpx; |
||||
|
border-radius: 8rpx; |
||||
|
border: none; |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
/* 头像选择样式 */ |
||||
|
.avatar-section { |
||||
|
margin-bottom: 30rpx; |
||||
|
} |
||||
|
|
||||
|
.avatar-wrapper { |
||||
|
width: 120rpx; |
||||
|
height: 120rpx; |
||||
|
border-radius: 50%; |
||||
|
background: none; |
||||
|
padding: 0; |
||||
|
margin: 0 auto; |
||||
|
border: none; |
||||
|
} |
||||
|
|
||||
|
.avatar { |
||||
|
width: 120rpx; |
||||
|
height: 120rpx; |
||||
|
border-radius: 50%; |
||||
|
} |
||||
|
|
||||
|
/* 表单样式 */ |
||||
|
.form-group { |
||||
|
margin-bottom: 30rpx; |
||||
|
} |
||||
|
|
||||
|
.form-input { |
||||
|
width: 100%; |
||||
|
padding: 20rpx; |
||||
|
border: 1rpx solid #ddd; |
||||
|
border-radius: 8rpx; |
||||
|
box-sizing: border-box; |
||||
|
font-size: 28rpx; |
||||
|
} |
||||
|
|
||||
|
.form-actions { |
||||
|
text-align: center; |
||||
|
margin-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.confirm-button { |
||||
|
background-color: #1677ff; |
||||
|
color: white; |
||||
|
font-size: 28rpx; |
||||
|
line-height: 80rpx; |
||||
|
border-radius: 8rpx; |
||||
|
border: none; |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
/* 自定义弹窗样式 */ |
||||
|
.custom-toast-mask { |
||||
|
position: fixed; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
background: rgba(0, 0, 0, 0.4); |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
z-index: 9999; |
||||
|
} |
||||
|
|
||||
|
.custom-toast { |
||||
|
background: white; |
||||
|
padding: 40rpx 60rpx; |
||||
|
border-radius: 12rpx; |
||||
|
text-align: center; |
||||
|
transform-origin: center center; |
||||
|
} |
||||
|
|
||||
|
/* 图片预览样式 */ |
||||
|
.image-preview-mask { |
||||
|
position: fixed; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
background: rgba(0, 0, 0, 0.9); |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
z-index: 9999; |
||||
|
} |
||||
|
|
||||
|
/* 按钮样式优化 */ |
||||
|
button { |
||||
|
border: none; |
||||
|
outline: none; |
||||
|
} |
||||
|
|
||||
|
button:after { |
||||
|
border: none; |
||||
|
} |
||||
|
|
||||
|
/* 加载状态样式 */ |
||||
|
.loading-more { |
||||
|
padding: 20rpx 0; |
||||
|
text-align: center; |
||||
|
font-size: 26rpx; |
||||
|
color: #999; |
||||
|
} |
||||
|
|
||||
|
.no-more-data { |
||||
|
padding: 20rpx 0; |
||||
|
text-align: center; |
||||
|
font-size: 26rpx; |
||||
|
color: #999; |
||||
|
} |
||||
|
|
||||
|
/* 搜索无结果样式 */ |
||||
|
.no-results { |
||||
|
text-align: center; |
||||
|
color: #999; |
||||
|
margin-top: 50rpx; |
||||
|
font-size: 28rpx; |
||||
|
} |
||||
|
|
||||
|
/* 响应式设计 */ |
||||
|
@media (max-width: 750rpx) { |
||||
|
.modal-container { |
||||
|
width: 90%; |
||||
|
padding: 30rpx; |
||||
|
} |
||||
|
|
||||
|
.card { |
||||
|
margin-bottom: 16rpx; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,66 @@ |
|||||
|
// pages/debug/debug-gross-weight.js
|
||||
|
Page({ |
||||
|
|
||||
|
/** |
||||
|
* 页面的初始数据 |
||||
|
*/ |
||||
|
data: { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面加载 |
||||
|
*/ |
||||
|
onLoad(options) { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面初次渲染完成 |
||||
|
*/ |
||||
|
onReady() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面显示 |
||||
|
*/ |
||||
|
onShow() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面隐藏 |
||||
|
*/ |
||||
|
onHide() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面卸载 |
||||
|
*/ |
||||
|
onUnload() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 页面相关事件处理函数--监听用户下拉动作 |
||||
|
*/ |
||||
|
onPullDownRefresh() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 页面上拉触底事件的处理函数 |
||||
|
*/ |
||||
|
onReachBottom() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 用户点击右上角分享 |
||||
|
*/ |
||||
|
onShareAppMessage() { |
||||
|
|
||||
|
} |
||||
|
}) |
||||
@ -0,0 +1,2 @@ |
|||||
|
<!--pages/debug/debug-gross-weight.wxml--> |
||||
|
<text>pages/debug/debug-gross-weight.wxml</text> |
||||
@ -0,0 +1,66 @@ |
|||||
|
// pages/debug/debug-sold-out.js
|
||||
|
Page({ |
||||
|
|
||||
|
/** |
||||
|
* 页面的初始数据 |
||||
|
*/ |
||||
|
data: { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面加载 |
||||
|
*/ |
||||
|
onLoad(options) { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面初次渲染完成 |
||||
|
*/ |
||||
|
onReady() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面显示 |
||||
|
*/ |
||||
|
onShow() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面隐藏 |
||||
|
*/ |
||||
|
onHide() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面卸载 |
||||
|
*/ |
||||
|
onUnload() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 页面相关事件处理函数--监听用户下拉动作 |
||||
|
*/ |
||||
|
onPullDownRefresh() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 页面上拉触底事件的处理函数 |
||||
|
*/ |
||||
|
onReachBottom() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 用户点击右上角分享 |
||||
|
*/ |
||||
|
onShareAppMessage() { |
||||
|
|
||||
|
} |
||||
|
}) |
||||
@ -0,0 +1,2 @@ |
|||||
|
<!--pages/debug/debug-sold-out.wxml--> |
||||
|
<text>pages/debug/debug-sold-out.wxml</text> |
||||
@ -0,0 +1,66 @@ |
|||||
|
// pages/debug/debug.js
|
||||
|
Page({ |
||||
|
|
||||
|
/** |
||||
|
* 页面的初始数据 |
||||
|
*/ |
||||
|
data: { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面加载 |
||||
|
*/ |
||||
|
onLoad(options) { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面初次渲染完成 |
||||
|
*/ |
||||
|
onReady() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面显示 |
||||
|
*/ |
||||
|
onShow() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面隐藏 |
||||
|
*/ |
||||
|
onHide() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面卸载 |
||||
|
*/ |
||||
|
onUnload() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 页面相关事件处理函数--监听用户下拉动作 |
||||
|
*/ |
||||
|
onPullDownRefresh() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 页面上拉触底事件的处理函数 |
||||
|
*/ |
||||
|
onReachBottom() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 用户点击右上角分享 |
||||
|
*/ |
||||
|
onShareAppMessage() { |
||||
|
|
||||
|
} |
||||
|
}) |
||||
@ -0,0 +1,2 @@ |
|||||
|
<!--pages/debug/debug.wxml--> |
||||
|
<text>pages/debug/debug.wxml</text> |
||||
@ -0,0 +1,557 @@ |
|||||
|
// pages/evaluate/index.js
|
||||
|
//估价暂未开放
|
||||
|
Page({ |
||||
|
data: { |
||||
|
evaluateStep: 1, |
||||
|
fromPreviousStep: false, // 用于标记是否从下一步返回
|
||||
|
evaluateData: { |
||||
|
region: '', |
||||
|
type: '', |
||||
|
brand: '', |
||||
|
model: '', |
||||
|
freshness: '', |
||||
|
size: '', |
||||
|
packaging: '', |
||||
|
spec: '' |
||||
|
}, |
||||
|
// 客户地区列表
|
||||
|
regions: ['北京', '上海', '广州', '深圳', '杭州', '成都', '武汉', '西安', '南京', '重庆'], |
||||
|
evaluateResult: { |
||||
|
finalPrice: '0', |
||||
|
totalPrice: '0' |
||||
|
}, |
||||
|
|
||||
|
// 鸡蛋类型数据(包含成交单量)
|
||||
|
eggTypes: [ |
||||
|
{ name: '土鸡蛋', dailyOrders: 1258, desc: '散养鸡产出的优质鸡蛋' }, |
||||
|
{ name: '洋鸡蛋', dailyOrders: 3421, desc: '规模化养殖的普通鸡蛋' }, |
||||
|
{ name: '乌鸡蛋', dailyOrders: 892, desc: '乌鸡产出的特色鸡蛋' }, |
||||
|
{ name: '有机鸡蛋', dailyOrders: 675, desc: '有机认证的高品质鸡蛋' }, |
||||
|
{ name: '初生蛋', dailyOrders: 965, desc: '母鸡产的第一窝鸡蛋' } |
||||
|
], |
||||
|
|
||||
|
// 鸡蛋品牌和型号数据(包含成交单量)
|
||||
|
eggData: { |
||||
|
'土鸡蛋': { |
||||
|
brands: [ |
||||
|
{ name: '农家乐', dailyOrders: 456 }, |
||||
|
{ name: '山野', dailyOrders: 389 }, |
||||
|
{ name: '生态园', dailyOrders: 243 }, |
||||
|
{ name: '田园', dailyOrders: 170 } |
||||
|
], |
||||
|
models: { |
||||
|
'农家乐': [ |
||||
|
{ name: '散养土鸡蛋', dailyOrders: 213 }, |
||||
|
{ name: '林下土鸡蛋', dailyOrders: 132 }, |
||||
|
{ name: '谷物喂养土鸡蛋', dailyOrders: 78 }, |
||||
|
{ name: '农家土鸡蛋', dailyOrders: 33 } |
||||
|
], |
||||
|
'山野': [ |
||||
|
{ name: '高山散养土鸡蛋', dailyOrders: 189 }, |
||||
|
{ name: '林间土鸡蛋', dailyOrders: 124 }, |
||||
|
{ name: '野生土鸡蛋', dailyOrders: 76 } |
||||
|
], |
||||
|
'生态园': [ |
||||
|
{ name: '有机土鸡蛋', dailyOrders: 112 }, |
||||
|
{ name: '无抗土鸡蛋', dailyOrders: 89 }, |
||||
|
{ name: '生态土鸡蛋', dailyOrders: 42 } |
||||
|
], |
||||
|
'田园': [ |
||||
|
{ name: '农家土鸡蛋', dailyOrders: 87 }, |
||||
|
{ name: '走地鸡蛋', dailyOrders: 54 }, |
||||
|
{ name: '自然放养土鸡蛋', dailyOrders: 29 } |
||||
|
] |
||||
|
} |
||||
|
}, |
||||
|
'洋鸡蛋': { |
||||
|
brands: [ |
||||
|
{ name: '德青源', dailyOrders: 1234 }, |
||||
|
{ name: '圣迪乐村', dailyOrders: 987 }, |
||||
|
{ name: '正大', dailyOrders: 765 }, |
||||
|
{ name: '咯咯哒', dailyOrders: 435 } |
||||
|
], |
||||
|
models: { |
||||
|
'德青源': [ |
||||
|
{ name: '安心鲜鸡蛋', dailyOrders: 543 }, |
||||
|
{ name: '谷物鸡蛋', dailyOrders: 456 }, |
||||
|
{ name: '营养鸡蛋', dailyOrders: 235 } |
||||
|
], |
||||
|
'圣迪乐村': [ |
||||
|
{ name: '高品质鲜鸡蛋', dailyOrders: 432 }, |
||||
|
{ name: '谷物鸡蛋', dailyOrders: 321 }, |
||||
|
{ name: '生态鸡蛋', dailyOrders: 234 } |
||||
|
], |
||||
|
'正大': [ |
||||
|
{ name: '鲜鸡蛋', dailyOrders: 345 }, |
||||
|
{ name: '营养鸡蛋', dailyOrders: 243 }, |
||||
|
{ name: '优选鸡蛋', dailyOrders: 177 } |
||||
|
], |
||||
|
'咯咯哒': [ |
||||
|
{ name: '鲜鸡蛋', dailyOrders: 213 }, |
||||
|
{ name: '谷物鸡蛋', dailyOrders: 145 }, |
||||
|
{ name: '农家鸡蛋', dailyOrders: 77 } |
||||
|
] |
||||
|
} |
||||
|
}, |
||||
|
'乌鸡蛋': { |
||||
|
brands: [ |
||||
|
{ name: '山野', dailyOrders: 345 }, |
||||
|
{ name: '生态园', dailyOrders: 289 }, |
||||
|
{ name: '农家乐', dailyOrders: 258 } |
||||
|
], |
||||
|
models: { |
||||
|
'山野': [ |
||||
|
{ name: '散养乌鸡蛋', dailyOrders: 156 }, |
||||
|
{ name: '林下乌鸡蛋', dailyOrders: 102 }, |
||||
|
{ name: '野生乌鸡蛋', dailyOrders: 87 } |
||||
|
], |
||||
|
'生态园': [ |
||||
|
{ name: '有机乌鸡蛋', dailyOrders: 123 }, |
||||
|
{ name: '无抗乌鸡蛋', dailyOrders: 98 }, |
||||
|
{ name: '生态乌鸡蛋', dailyOrders: 68 } |
||||
|
], |
||||
|
'农家乐': [ |
||||
|
{ name: '农家乌鸡蛋', dailyOrders: 112 }, |
||||
|
{ name: '谷物乌鸡蛋', dailyOrders: 93 }, |
||||
|
{ name: '散养乌鸡蛋', dailyOrders: 53 } |
||||
|
] |
||||
|
} |
||||
|
}, |
||||
|
'有机鸡蛋': { |
||||
|
brands: [ |
||||
|
{ name: '生态园', dailyOrders: 289 }, |
||||
|
{ name: '山野', dailyOrders: 213 }, |
||||
|
{ name: '田园', dailyOrders: 173 } |
||||
|
], |
||||
|
models: { |
||||
|
'生态园': [ |
||||
|
{ name: '有机认证鸡蛋', dailyOrders: 132 }, |
||||
|
{ name: '无抗有机鸡蛋', dailyOrders: 98 }, |
||||
|
{ name: '生态有机鸡蛋', dailyOrders: 59 } |
||||
|
], |
||||
|
'山野': [ |
||||
|
{ name: '有机散养鸡蛋', dailyOrders: 98 }, |
||||
|
{ name: '有机谷物鸡蛋', dailyOrders: 76 }, |
||||
|
{ name: '野生有机鸡蛋', dailyOrders: 39 } |
||||
|
], |
||||
|
'田园': [ |
||||
|
{ name: '有机农家鸡蛋', dailyOrders: 89 }, |
||||
|
{ name: '有机初生蛋', dailyOrders: 54 }, |
||||
|
{ name: '自然有机鸡蛋', dailyOrders: 30 } |
||||
|
] |
||||
|
} |
||||
|
}, |
||||
|
'初生蛋': { |
||||
|
brands: [ |
||||
|
{ name: '农家乐', dailyOrders: 342 }, |
||||
|
{ name: '山野', dailyOrders: 312 }, |
||||
|
{ name: '生态园', dailyOrders: 311 } |
||||
|
], |
||||
|
models: { |
||||
|
'农家乐': [ |
||||
|
{ name: '土鸡初生蛋', dailyOrders: 156 }, |
||||
|
{ name: '散养初生蛋', dailyOrders: 112 }, |
||||
|
{ name: '农家初生蛋', dailyOrders: 74 } |
||||
|
], |
||||
|
'山野': [ |
||||
|
{ name: '高山初生蛋', dailyOrders: 145 }, |
||||
|
{ name: '林下初生蛋', dailyOrders: 98 }, |
||||
|
{ name: '野生初生蛋', dailyOrders: 69 } |
||||
|
], |
||||
|
'生态园': [ |
||||
|
{ name: '有机初生蛋', dailyOrders: 134 }, |
||||
|
{ name: '无抗初生蛋', dailyOrders: 102 }, |
||||
|
{ name: '生态初生蛋', dailyOrders: 75 } |
||||
|
] |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
eggBrands: [], |
||||
|
eggModels: [] |
||||
|
}, |
||||
|
|
||||
|
onLoad() { |
||||
|
console.log('估价页面初始化') |
||||
|
// 页面加载时,对鸡蛋类型按成交单量降序排序并添加排名
|
||||
|
const sortedTypes = [...this.data.eggTypes].sort((a, b) => b.dailyOrders - a.dailyOrders); |
||||
|
// 添加排名属性
|
||||
|
const typesWithRank = sortedTypes.map((type, index) => ({ |
||||
|
...type, |
||||
|
rank: index + 1 |
||||
|
})); |
||||
|
this.setData({ |
||||
|
eggTypes: typesWithRank, |
||||
|
fromPreviousStep: false // 初始化标志
|
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 选择客户地区
|
||||
|
selectRegion(e) { |
||||
|
const region = e.currentTarget.dataset.region; |
||||
|
this.setData({ |
||||
|
'evaluateData.region': region |
||||
|
}); |
||||
|
|
||||
|
// 只有当当前步骤是1且已经从下一步返回时,才自动进入下一步
|
||||
|
if (this.data.evaluateStep === 1 && !this.data.fromPreviousStep) { |
||||
|
this.setData({ |
||||
|
evaluateStep: 2 |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// 重置标志
|
||||
|
this.setData({ |
||||
|
fromPreviousStep: false |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 选择鸡蛋类型
|
||||
|
selectEggType(e) { |
||||
|
const type = e.currentTarget.dataset.type; |
||||
|
// 获取该类型下的品牌,并按成交单量降序排序
|
||||
|
const brands = [...this.data.eggData[type].brands].sort((a, b) => b.dailyOrders - a.dailyOrders); |
||||
|
// 添加排名属性
|
||||
|
const brandsWithRank = brands.map((brand, index) => ({ |
||||
|
...brand, |
||||
|
rank: index + 1 |
||||
|
})); |
||||
|
|
||||
|
this.setData({ |
||||
|
'evaluateData.type': type, |
||||
|
eggBrands: brandsWithRank, |
||||
|
// 清除之前选择的品牌和型号
|
||||
|
'evaluateData.brand': '', |
||||
|
'evaluateData.model': '', |
||||
|
eggModels: [] |
||||
|
}); |
||||
|
|
||||
|
// 只有当当前步骤是2且已经从下一步返回时,才自动进入下一步
|
||||
|
if (this.data.evaluateStep === 2 && !this.data.fromPreviousStep) { |
||||
|
this.setData({ |
||||
|
evaluateStep: 3 |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// 重置标志
|
||||
|
this.setData({ |
||||
|
fromPreviousStep: false |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 选择鸡蛋品牌
|
||||
|
selectEggBrand(e) { |
||||
|
const brand = e.currentTarget.dataset.brand; |
||||
|
const type = this.data.evaluateData.type; |
||||
|
// 获取该品牌下的型号,并按成交单量降序排序
|
||||
|
const models = [...this.data.eggData[type].models[brand]].sort((a, b) => b.dailyOrders - a.dailyOrders); |
||||
|
// 添加排名属性
|
||||
|
const modelsWithRank = models.map((model, index) => ({ |
||||
|
...model, |
||||
|
rank: index + 1 |
||||
|
})); |
||||
|
|
||||
|
this.setData({ |
||||
|
'evaluateData.brand': brand, |
||||
|
eggModels: modelsWithRank, |
||||
|
// 清除之前选择的型号
|
||||
|
'evaluateData.model': '' |
||||
|
}); |
||||
|
|
||||
|
// 只有当当前步骤是3且已经从下一步返回时,才自动进入下一步
|
||||
|
if (this.data.evaluateStep === 3 && !this.data.fromPreviousStep) { |
||||
|
this.setData({ |
||||
|
evaluateStep: 4 |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// 重置标志
|
||||
|
this.setData({ |
||||
|
fromPreviousStep: false |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 选择鸡蛋型号
|
||||
|
selectEggModel(e) { |
||||
|
const model = e.currentTarget.dataset.model; |
||||
|
this.setData({ |
||||
|
'evaluateData.model': model |
||||
|
}); |
||||
|
|
||||
|
// 只有当当前步骤是4且已经从下一步返回时,才自动进入下一步
|
||||
|
if (this.data.evaluateStep === 4 && !this.data.fromPreviousStep) { |
||||
|
this.setData({ |
||||
|
evaluateStep: 5 |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// 重置标志
|
||||
|
this.setData({ |
||||
|
fromPreviousStep: false |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 格式化订单数量显示
|
||||
|
formatOrderCount(count) { |
||||
|
if (count >= 10000) { |
||||
|
return (count / 10000).toFixed(1) + '万'; |
||||
|
} else if (count >= 1000) { |
||||
|
return (count / 1000).toFixed(1) + 'k'; |
||||
|
} |
||||
|
return count.toString(); |
||||
|
}, |
||||
|
|
||||
|
// 选择条件
|
||||
|
selectCondition(e) { |
||||
|
const { type, value } = e.currentTarget.dataset; |
||||
|
this.setData({ |
||||
|
[`evaluateData.${type}`]: value |
||||
|
}); |
||||
|
|
||||
|
// 只有当当前步骤不是从返回过来的,才自动进入下一步
|
||||
|
if (!this.data.fromPreviousStep) { |
||||
|
// 根据当前步骤自动进入下一步
|
||||
|
const currentStep = this.data.evaluateStep; |
||||
|
if (currentStep === 5) { |
||||
|
this.setData({ evaluateStep: 6 }); |
||||
|
} else if (currentStep === 6) { |
||||
|
this.setData({ evaluateStep: 7 }); |
||||
|
} else if (currentStep === 7) { |
||||
|
this.setData({ evaluateStep: 8 }); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 重置标志
|
||||
|
this.setData({ |
||||
|
fromPreviousStep: false |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 选择规格
|
||||
|
selectSpec(e) { |
||||
|
const spec = e.currentTarget.dataset.spec; |
||||
|
this.setData({ |
||||
|
'evaluateData.spec': spec |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 获取报价
|
||||
|
getQuote() { |
||||
|
if (this.data.evaluateData.spec) { |
||||
|
this.calculatePrice(); |
||||
|
} else { |
||||
|
wx.showToast({ |
||||
|
title: '请选择规格', |
||||
|
icon: 'none', |
||||
|
duration: 2000 |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 上一步
|
||||
|
prevStep() { |
||||
|
if (this.data.evaluateStep > 1) { |
||||
|
this.setData({ |
||||
|
evaluateStep: this.data.evaluateStep - 1, |
||||
|
fromPreviousStep: true // 标记是从下一步返回的
|
||||
|
}); |
||||
|
} else { |
||||
|
// 如果在第一步,返回上一页
|
||||
|
wx.navigateBack(); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 计算价格
|
||||
|
calculatePrice() { |
||||
|
const { region, type, brand, model, freshness, size, packaging, spec } = this.data.evaluateData; |
||||
|
|
||||
|
// 校验必填参数
|
||||
|
if (!region || !type || !brand || !model || !freshness || !size || !packaging || !spec) { |
||||
|
wx.showToast({ |
||||
|
title: '请完成所有选项', |
||||
|
icon: 'none', |
||||
|
duration: 2000 |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 显示加载中
|
||||
|
wx.showLoading({ |
||||
|
title: '计算中...', |
||||
|
mask: true |
||||
|
}); |
||||
|
|
||||
|
// 模拟计算延迟
|
||||
|
setTimeout(() => { |
||||
|
// 基础价格表(元/斤)
|
||||
|
const basePrices = { |
||||
|
'土鸡蛋': 25, |
||||
|
'洋鸡蛋': 15, |
||||
|
'乌鸡蛋': 35, |
||||
|
'有机鸡蛋': 40, |
||||
|
'初生蛋': 45 |
||||
|
}; |
||||
|
|
||||
|
// 品牌溢价系数
|
||||
|
const brandMultipliers = { |
||||
|
'农家乐': 1.0, |
||||
|
'山野': 1.1, |
||||
|
'生态园': 1.2, |
||||
|
'田园': 1.0, |
||||
|
'德青源': 1.1, |
||||
|
'圣迪乐村': 1.15, |
||||
|
'正大': 1.05, |
||||
|
'咯咯哒': 1.0 |
||||
|
}; |
||||
|
|
||||
|
// 型号溢价系数
|
||||
|
const modelMultipliers = { |
||||
|
// 土鸡蛋型号系数
|
||||
|
'散养土鸡蛋': 1.1, '林下土鸡蛋': 1.15, '谷物喂养土鸡蛋': 1.2, '农家土鸡蛋': 1.0, |
||||
|
'高山散养土鸡蛋': 1.25, '林间土鸡蛋': 1.1, '野生土鸡蛋': 1.3, |
||||
|
'有机土鸡蛋': 1.3, '无抗土鸡蛋': 1.25, '生态土鸡蛋': 1.2, |
||||
|
'走地鸡蛋': 1.1, '自然放养土鸡蛋': 1.12, |
||||
|
|
||||
|
// 洋鸡蛋型号系数
|
||||
|
'安心鲜鸡蛋': 1.0, '谷物鸡蛋': 1.1, '营养鸡蛋': 1.05, |
||||
|
'高品质鲜鸡蛋': 1.15, '生态鸡蛋': 1.2, |
||||
|
'鲜鸡蛋': 1.0, '优选鸡蛋': 1.1, |
||||
|
'农家鸡蛋': 1.05, |
||||
|
|
||||
|
// 乌鸡蛋型号系数
|
||||
|
'散养乌鸡蛋': 1.1, '林下乌鸡蛋': 1.15, '野生乌鸡蛋': 1.3, |
||||
|
'有机乌鸡蛋': 1.3, '无抗乌鸡蛋': 1.25, '生态乌鸡蛋': 1.2, |
||||
|
'农家乌鸡蛋': 1.0, '谷物乌鸡蛋': 1.1, |
||||
|
|
||||
|
// 有机鸡蛋型号系数
|
||||
|
'有机认证鸡蛋': 1.3, '无抗有机鸡蛋': 1.35, '生态有机鸡蛋': 1.32, |
||||
|
'有机散养鸡蛋': 1.25, '有机谷物鸡蛋': 1.2, '野生有机鸡蛋': 1.4, |
||||
|
'有机农家鸡蛋': 1.1, '有机初生蛋': 1.4, '自然有机鸡蛋': 1.2, |
||||
|
|
||||
|
// 初生蛋型号系数
|
||||
|
'土鸡初生蛋': 1.2, '散养初生蛋': 1.25, '农家初生蛋': 1.15, |
||||
|
'高山初生蛋': 1.3, '林下初生蛋': 1.25, '野生初生蛋': 1.45, |
||||
|
'有机初生蛋': 1.4, '无抗初生蛋': 1.35, '生态初生蛋': 1.3 |
||||
|
}; |
||||
|
|
||||
|
// 状况调整系数
|
||||
|
const freshnessCoefficient = { '非常新鲜': 1.0, '较新鲜': 0.85, '一般': 0.7, '不新鲜': 0.4 }; |
||||
|
const sizeCoefficient = { '特大': 1.3, '大': 1.1, '中': 1.0, '小': 0.8 }; |
||||
|
const packagingCoefficient = { '原装完整': 1.0, '部分包装': 0.9, '散装': 0.8 }; |
||||
|
|
||||
|
// 计算单价(元/斤)
|
||||
|
let unitPrice = basePrices[type] || 20; |
||||
|
const brandMultiplier = brandMultipliers[brand] || 1.0; |
||||
|
const modelMultiplier = modelMultipliers[model] || 1.0; |
||||
|
|
||||
|
unitPrice = unitPrice * brandMultiplier * modelMultiplier; |
||||
|
unitPrice *= freshnessCoefficient[freshness]; |
||||
|
unitPrice *= sizeCoefficient[size]; |
||||
|
unitPrice *= packagingCoefficient[packaging]; |
||||
|
|
||||
|
// 确保价格合理
|
||||
|
unitPrice = Math.max(unitPrice, 1); |
||||
|
|
||||
|
// 计算总价(假设每个鸡蛋约0.05斤)
|
||||
|
const eggsPerKilogram = 20; // 约20个鸡蛋/斤
|
||||
|
const specCount = parseInt(spec) || 0; |
||||
|
const totalWeight = specCount / eggsPerKilogram; |
||||
|
const totalPrice = unitPrice * totalWeight; |
||||
|
|
||||
|
// 更新结果
|
||||
|
this.setData({ |
||||
|
evaluateResult: { |
||||
|
finalPrice: unitPrice.toFixed(1), |
||||
|
totalPrice: totalPrice.toFixed(1) |
||||
|
}, |
||||
|
evaluateStep: 9 |
||||
|
}, () => { |
||||
|
wx.hideLoading(); |
||||
|
}); |
||||
|
|
||||
|
}, 800); |
||||
|
}, |
||||
|
|
||||
|
// 重新估价
|
||||
|
restartEvaluate() { |
||||
|
this.setData({ |
||||
|
evaluateStep: 1, |
||||
|
evaluateData: { |
||||
|
region: '', |
||||
|
type: '', |
||||
|
brand: '', |
||||
|
model: '', |
||||
|
freshness: '', |
||||
|
size: '', |
||||
|
packaging: '', |
||||
|
spec: '' |
||||
|
}, |
||||
|
evaluateResult: { |
||||
|
finalPrice: '0', |
||||
|
totalPrice: '0' |
||||
|
}, |
||||
|
fromPreviousStep: false // 重置标志
|
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 返回首页
|
||||
|
backToHome() { |
||||
|
wx.navigateBack(); |
||||
|
}, |
||||
|
|
||||
|
// 跳转到购物页面
|
||||
|
goToBuy() { |
||||
|
console.log('goToBuy 函数被调用'); |
||||
|
// 使用与custom-tab-bar相同的跳转逻辑
|
||||
|
const url = 'pages/buyer/index'; |
||||
|
|
||||
|
// 先尝试使用navigateTo
|
||||
|
wx.navigateTo({ |
||||
|
url: '/' + url, |
||||
|
success: function(res) { |
||||
|
console.log('使用navigateTo成功跳转到购物页面:', res); |
||||
|
}, |
||||
|
fail: function(error) { |
||||
|
console.log('navigateTo失败,尝试使用reLaunch:', error); |
||||
|
// 如果navigateTo失败,使用reLaunch
|
||||
|
wx.reLaunch({ |
||||
|
url: '/' + url, |
||||
|
success: function(res) { |
||||
|
console.log('使用reLaunch成功跳转到购物页面:', res); |
||||
|
}, |
||||
|
fail: function(err) { |
||||
|
console.error('reLaunch也失败:', err); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 跳转到货源页面
|
||||
|
goToSell() { |
||||
|
console.log('goToSell 函数被调用'); |
||||
|
// 使用与custom-tab-bar相同的跳转逻辑
|
||||
|
const url = 'pages/seller/index'; |
||||
|
|
||||
|
// 先尝试使用navigateTo
|
||||
|
wx.navigateTo({ |
||||
|
url: '/' + url, |
||||
|
success: function(res) { |
||||
|
console.log('使用navigateTo成功跳转到货源页面:', res); |
||||
|
}, |
||||
|
fail: function(error) { |
||||
|
console.log('navigateTo失败,尝试使用reLaunch:', error); |
||||
|
// 如果navigateTo失败,使用reLaunch
|
||||
|
wx.reLaunch({ |
||||
|
url: '/' + url, |
||||
|
success: function(res) { |
||||
|
console.log('使用reLaunch成功跳转到货源页面:', res); |
||||
|
}, |
||||
|
fail: function(err) { |
||||
|
console.error('reLaunch也失败:', err); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
}) |
||||
@ -0,0 +1,6 @@ |
|||||
|
{ |
||||
|
"navigationBarTitleText": "鸡蛋估价", |
||||
|
"navigationBarBackgroundColor": "#ffffff", |
||||
|
"navigationBarTextStyle": "black", |
||||
|
"backgroundColor": "#f5f5f5" |
||||
|
} |
||||
@ -0,0 +1,339 @@ |
|||||
|
<!--pages/evaluate/index.wxml--> |
||||
|
<view class="evaluate-page"> |
||||
|
<!-- 顶部导航 --> |
||||
|
<view class="evaluate-header"> |
||||
|
<view class="header-back" bindtap="prevStep">‹</view> |
||||
|
<view class="header-title">鸡蛋估价</view> |
||||
|
<view class="header-placeholder"></view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 进度指示器 --> |
||||
|
<view class="progress-indicator" wx:if="{{evaluateStep < 8}}"> |
||||
|
<view class="progress-dots"> |
||||
|
<view class="progress-dot {{evaluateStep >= 1 ? 'active' : ''}}"></view> |
||||
|
<view class="progress-dot {{evaluateStep >= 2 ? 'active' : ''}}"></view> |
||||
|
<view class="progress-dot {{evaluateStep >= 3 ? 'active' : ''}}"></view> |
||||
|
<view class="progress-dot {{evaluateStep >= 4 ? 'active' : ''}}"></view> |
||||
|
<view class="progress-dot {{evaluateStep >= 5 ? 'active' : ''}}"></view> |
||||
|
<view class="progress-dot {{evaluateStep >= 6 ? 'active' : ''}}"></view> |
||||
|
<view class="progress-dot {{evaluateStep >= 7 ? 'active' : ''}}"></view> |
||||
|
<view class="progress-dot {{evaluateStep >= 8 ? 'active' : ''}}"></view> |
||||
|
</view> |
||||
|
<view class="progress-text">步骤 {{evaluateStep}}/8</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 步骤1:选择客户地区 --> |
||||
|
<view wx:if="{{evaluateStep === 1}}" class="evaluate-step"> |
||||
|
<view class="step-content"> |
||||
|
<view class="step-title">选择客户地区</view> |
||||
|
<view class="step-subtitle">请选择您所在的地区</view> |
||||
|
|
||||
|
<view class="category-list"> |
||||
|
<view wx:for="{{regions}}" wx:key="*this" |
||||
|
class="category-item {{evaluateData.region === item ? 'selected' : ''}}" |
||||
|
bindtap="selectRegion" data-region="{{item}}"> |
||||
|
<view class="category-info"> |
||||
|
<view class="category-name">{{item}}</view> |
||||
|
<view class="category-desc">点击选择该地区</view> |
||||
|
</view> |
||||
|
<view class="category-arrow">›</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 步骤2:选择鸡蛋类型 --> |
||||
|
<view wx:if="{{evaluateStep === 2}}" class="evaluate-step"> |
||||
|
<view class="step-content"> |
||||
|
<view class="step-title">选择鸡蛋类型</view> |
||||
|
<view class="step-subtitle">请选择您要估价的鸡蛋类型(按每日成交单量排序)</view> |
||||
|
|
||||
|
<view class="category-list"> |
||||
|
<view wx:for="{{eggTypes}}" wx:key="name" wx:for-item="eggType" |
||||
|
class="category-item {{evaluateData.type === eggType.name ? 'selected' : ''}}" |
||||
|
bindtap="selectEggType" data-type="{{eggType.name}}"> |
||||
|
<view class="category-info"> |
||||
|
<view class="category-name"> |
||||
|
<text class="rank-number rank-{{eggType.rank <= 3 ? eggType.rank : ''}}">{{eggType.rank}}</text> |
||||
|
{{eggType.name}} |
||||
|
</view> |
||||
|
<view class="category-desc">{{eggType.desc}}</view> |
||||
|
</view> |
||||
|
<view class="category-arrow">›</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 步骤3:选择品牌 --> |
||||
|
<view wx:if="{{evaluateStep === 3}}" class="evaluate-step"> |
||||
|
<view class="step-content"> |
||||
|
<view class="step-title">选择品牌</view> |
||||
|
<view class="step-subtitle">{{evaluateData.type}} - 按每日成交单量排序</view> |
||||
|
|
||||
|
<view class="option-list"> |
||||
|
<view wx:for="{{eggBrands}}" wx:key="name" wx:for-item="brand" |
||||
|
class="option-item {{evaluateData.brand === brand.name ? 'selected' : ''}}" |
||||
|
bindtap="selectEggBrand" data-brand="{{brand.name}}"> |
||||
|
<view class="option-text"> |
||||
|
<text class="rank-number rank-{{brand.rank <= 3 ? brand.rank : ''}}">{{brand.rank}}</text> |
||||
|
{{brand.name}} |
||||
|
</view> |
||||
|
<view class="option-arrow">›</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 步骤4:选择具体型号 --> |
||||
|
<view wx:if="{{evaluateStep === 4}}" class="evaluate-step"> |
||||
|
<view class="step-content"> |
||||
|
<view class="step-title">选择具体型号</view> |
||||
|
<view class="step-subtitle">{{evaluateData.brand}} - 按每日成交单量排序</view> |
||||
|
|
||||
|
<view class="option-list"> |
||||
|
<view wx:for="{{eggModels}}" wx:key="name" wx:for-item="model" |
||||
|
class="option-item {{evaluateData.model === model.name ? 'selected' : ''}}" |
||||
|
bindtap="selectEggModel" data-model="{{model.name}}"> |
||||
|
<view class="option-text"> |
||||
|
<text class="rank-number rank-{{model.rank <= 3 ? model.rank : ''}}">{{model.rank}}</text> |
||||
|
{{model.name}} |
||||
|
</view> |
||||
|
<view class="option-arrow">›</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 步骤5:新鲜度选择 --> |
||||
|
<view wx:if="{{evaluateStep === 5}}" class="evaluate-step"> |
||||
|
<view class="step-content"> |
||||
|
<view class="step-title">新鲜程度</view> |
||||
|
<view class="step-subtitle">请选择鸡蛋的新鲜程度</view> |
||||
|
|
||||
|
<view class="condition-list"> |
||||
|
<view class="condition-item {{evaluateData.freshness === '非常新鲜' ? 'selected' : ''}}" |
||||
|
bindtap="selectCondition" data-type="freshness" data-value="非常新鲜"> |
||||
|
<view class="condition-info"> |
||||
|
<view class="condition-name">非常新鲜</view> |
||||
|
<view class="condition-desc">7天内产出的新鲜鸡蛋</view> |
||||
|
</view> |
||||
|
<view class="condition-check" wx:if="{{evaluateData.freshness === '非常新鲜'}}">✓</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="condition-item {{evaluateData.freshness === '较新鲜' ? 'selected' : ''}}" |
||||
|
bindtap="selectCondition" data-type="freshness" data-value="较新鲜"> |
||||
|
<view class="condition-info"> |
||||
|
<view class="condition-name">较新鲜</view> |
||||
|
<view class="condition-desc">15天内产出的鸡蛋</view> |
||||
|
</view> |
||||
|
<view class="condition-check" wx:if="{{evaluateData.freshness === '较新鲜'}}">✓</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="condition-item {{evaluateData.freshness === '一般' ? 'selected' : ''}}" |
||||
|
bindtap="selectCondition" data-type="freshness" data-value="一般"> |
||||
|
<view class="condition-info"> |
||||
|
<view class="condition-name">一般</view> |
||||
|
<view class="condition-desc">30天内产出的鸡蛋</view> |
||||
|
</view> |
||||
|
<view class="condition-check" wx:if="{{evaluateData.freshness === '一般'}}">✓</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="condition-item {{evaluateData.freshness === '不新鲜' ? 'selected' : ''}}" |
||||
|
bindtap="selectCondition" data-type="freshness" data-value="不新鲜"> |
||||
|
<view class="condition-info"> |
||||
|
<view class="condition-name">不新鲜</view> |
||||
|
<view class="condition-desc">30天以上的鸡蛋</view> |
||||
|
</view> |
||||
|
<view class="condition-check" wx:if="{{evaluateData.freshness === '不新鲜'}}">✓</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 步骤6:大小选择 --> |
||||
|
<view wx:if="{{evaluateStep === 6}}" class="evaluate-step"> |
||||
|
<view class="step-content"> |
||||
|
<view class="step-title">鸡蛋大小</view> |
||||
|
<view class="step-subtitle">请选择鸡蛋的大小规格</view> |
||||
|
|
||||
|
<view class="condition-list"> |
||||
|
<view class="condition-item {{evaluateData.size === '特大' ? 'selected' : ''}}" |
||||
|
bindtap="selectCondition" data-type="size" data-value="特大"> |
||||
|
<view class="condition-info"> |
||||
|
<view class="condition-name">特大</view> |
||||
|
<view class="condition-desc">单枚≥70g</view> |
||||
|
</view> |
||||
|
<view class="condition-check" wx:if="{{evaluateData.size === '特大'}}">✓</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="condition-item {{evaluateData.size === '大' ? 'selected' : ''}}" |
||||
|
bindtap="selectCondition" data-type="size" data-value="大"> |
||||
|
<view class="condition-info"> |
||||
|
<view class="condition-name">大</view> |
||||
|
<view class="condition-desc">单枚60-70g</view> |
||||
|
</view> |
||||
|
<view class="condition-check" wx:if="{{evaluateData.size === '大'}}">✓</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="condition-item {{evaluateData.size === '中' ? 'selected' : ''}}" |
||||
|
bindtap="selectCondition" data-type="size" data-value="中"> |
||||
|
<view class="condition-info"> |
||||
|
<view class="condition-name">中</view> |
||||
|
<view class="condition-desc">单枚50-60g</view> |
||||
|
</view> |
||||
|
<view class="condition-check" wx:if="{{evaluateData.size === '中'}}">✓</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="condition-item {{evaluateData.size === '小' ? 'selected' : ''}}" |
||||
|
bindtap="selectCondition" data-type="size" data-value="小"> |
||||
|
<view class="condition-info"> |
||||
|
<view class="condition-name">小</view> |
||||
|
<view class="condition-desc">单枚<50g</view> |
||||
|
</view> |
||||
|
<view class="condition-check" wx:if="{{evaluateData.size === '小'}}">✓</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 步骤7:包装情况 --> |
||||
|
<view wx:if="{{evaluateStep === 7}}" class="evaluate-step"> |
||||
|
<view class="step-content"> |
||||
|
<view class="step-title">包装情况</view> |
||||
|
<view class="step-subtitle">请选择鸡蛋的包装完好程度</view> |
||||
|
|
||||
|
<view class="condition-list"> |
||||
|
<view class="condition-item {{evaluateData.packaging === '原装完整' ? 'selected' : ''}}" |
||||
|
bindtap="selectCondition" data-type="packaging" data-value="原装完整"> |
||||
|
<view class="condition-info"> |
||||
|
<view class="condition-name">原装完整</view> |
||||
|
<view class="condition-desc">原包装完好无损</view> |
||||
|
</view> |
||||
|
<view class="condition-check" wx:if="{{evaluateData.packaging === '原装完整'}}">✓</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="condition-item {{evaluateData.packaging === '部分包装' ? 'selected' : ''}}" |
||||
|
bindtap="selectCondition" data-type="packaging" data-value="部分包装"> |
||||
|
<view class="condition-info"> |
||||
|
<view class="condition-name">部分包装</view> |
||||
|
<view class="condition-desc">包装有轻微破损</view> |
||||
|
</view> |
||||
|
<view class="condition-check" wx:if="{{evaluateData.packaging === '部分包装'}}">✓</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="condition-item {{evaluateData.packaging === '散装' ? 'selected' : ''}}" |
||||
|
bindtap="selectCondition" data-type="packaging" data-value="散装"> |
||||
|
<view class="condition-info"> |
||||
|
<view class="condition-name">散装</view> |
||||
|
<view class="condition-desc">无原包装</view> |
||||
|
</view> |
||||
|
<view class="condition-check" wx:if="{{evaluateData.packaging === '散装'}}">✓</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 步骤8:选择规格 --> |
||||
|
<view wx:if="{{evaluateStep === 8}}" class="evaluate-step"> |
||||
|
<view class="step-content"> |
||||
|
<view class="step-title">请选择规格(数量)</view> |
||||
|
<view class="step-subtitle">请选择鸡蛋的数量规格</view> |
||||
|
|
||||
|
<view class="option-list"> |
||||
|
<view class="option-item {{evaluateData.spec === '500' ? 'selected' : ''}}" bindtap="selectSpec" data-spec="500"> |
||||
|
<view class="option-text">500个</view> |
||||
|
<view class="option-arrow">›</view> |
||||
|
</view> |
||||
|
<view class="option-item {{evaluateData.spec === '1000' ? 'selected' : ''}}" bindtap="selectSpec" data-spec="1000"> |
||||
|
<view class="option-text">1000个</view> |
||||
|
<view class="option-arrow">›</view> |
||||
|
</view> |
||||
|
<view class="option-item {{evaluateData.spec === '2000' ? 'selected' : ''}}" bindtap="selectSpec" data-spec="2000"> |
||||
|
<view class="option-text">2000个</view> |
||||
|
<view class="option-arrow">›</view> |
||||
|
</view> |
||||
|
<view class="option-item {{evaluateData.spec === '10000' ? 'selected' : ''}}" bindtap="selectSpec" data-spec="10000"> |
||||
|
<view class="option-text">10000个</view> |
||||
|
<view class="option-arrow">›</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 获取报价按钮 --> |
||||
|
<view class="get-price-section"> |
||||
|
<button class="get-price-btn" bindtap="getQuote">获取报价</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 步骤9:估价结果 --> |
||||
|
<view wx:if="{{evaluateStep === 9}}" class="evaluate-step result-step"> |
||||
|
<view class="step-content"> |
||||
|
<view class="result-header"> |
||||
|
<view class="result-icon">💰</view> |
||||
|
<view class="result-title">估价完成</view> |
||||
|
<view class="result-subtitle">基于您选择的商品信息计算得出</view> |
||||
|
|
||||
|
<!-- 核心价格 --> |
||||
|
<view class="price-result"> |
||||
|
<view class="price-label">预估总价</view> |
||||
|
<view class="price-amount"> |
||||
|
<text class="price-symbol">¥</text> |
||||
|
<text class="price-number">{{evaluateResult.totalPrice}}</text> |
||||
|
</view> |
||||
|
<view class="price-unit">元({{evaluateData.spec}}个)</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="result-content"> |
||||
|
<!-- 商品信息 --> |
||||
|
<view class="product-info-card"> |
||||
|
<view class="product-type">{{evaluateData.type}}</view> |
||||
|
<view class="product-details"> |
||||
|
<view class="product-brand">{{evaluateData.brand}}</view> |
||||
|
<view class="product-model">{{evaluateData.model}}</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 商品状况 --> |
||||
|
<view class="condition-summary"> |
||||
|
<view class="summary-title">商品状况</view> |
||||
|
<view class="condition-items"> |
||||
|
<view class="condition-item"> |
||||
|
<view class="condition-label">新鲜度</view> |
||||
|
<view class="condition-value">{{evaluateData.freshness}}</view> |
||||
|
</view> |
||||
|
<view class="condition-item"> |
||||
|
<view class="condition-label">大小</view> |
||||
|
<view class="condition-value">{{evaluateData.size}}</view> |
||||
|
</view> |
||||
|
<view class="condition-item"> |
||||
|
<view class="condition-label">包装</view> |
||||
|
<view class="condition-value">{{evaluateData.packaging}}</view> |
||||
|
</view> |
||||
|
<view class="condition-item"> |
||||
|
<view class="condition-label">规格</view> |
||||
|
<view class="condition-value">{{evaluateData.spec}}个</view> |
||||
|
</view> |
||||
|
<view class="condition-item"> |
||||
|
<view class="condition-label">单价</view> |
||||
|
<view class="condition-value">{{evaluateResult.finalPrice}}元/斤</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 价格说明 --> |
||||
|
<view class="price-tips"> |
||||
|
<text class="tip-icon">💡</text> |
||||
|
<text class="tip-text">此价格仅供参考,实际成交价可能因市场波动有所差异</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 操作按钮 --> |
||||
|
<view class="result-actions"> |
||||
|
<button class="btn-secondary" bindtap="goToBuy">立即购买</button> |
||||
|
<button class="btn-primary" bindtap="goToSell">即刻上架</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
@ -0,0 +1,769 @@ |
|||||
|
/* pages/evaluate/index.wxss */ |
||||
|
.evaluate-page { |
||||
|
min-height: 100vh; |
||||
|
background: linear-gradient(135deg, #f9f9f9, #f5f5f5); |
||||
|
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', Arial, sans-serif; |
||||
|
} |
||||
|
|
||||
|
/* 顶部导航 */ |
||||
|
.evaluate-header { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: space-between; |
||||
|
padding: 30rpx 32rpx; |
||||
|
background: white; |
||||
|
border-bottom: 1rpx solid #f0f0f0; |
||||
|
position: sticky; |
||||
|
top: 0; |
||||
|
z-index: 100; |
||||
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.03); |
||||
|
} |
||||
|
|
||||
|
.header-back { |
||||
|
font-size: 48rpx; |
||||
|
color: #333; |
||||
|
width: 60rpx; |
||||
|
transition: color 0.2s ease; |
||||
|
} |
||||
|
|
||||
|
.header-back:active { |
||||
|
color: #FF6B00; |
||||
|
} |
||||
|
|
||||
|
.header-title { |
||||
|
font-size: 36rpx; |
||||
|
font-weight: 600; |
||||
|
color: #333; |
||||
|
} |
||||
|
|
||||
|
.header-placeholder { |
||||
|
width: 60rpx; |
||||
|
} |
||||
|
|
||||
|
/* 进度指示器 */ |
||||
|
.progress-indicator { |
||||
|
background: white; |
||||
|
padding: 30rpx 32rpx; |
||||
|
border-bottom: 1rpx solid #f0f0f0; |
||||
|
} |
||||
|
|
||||
|
.progress-dots { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
margin-bottom: 20rpx; |
||||
|
position: relative; |
||||
|
} |
||||
|
|
||||
|
.progress-dots::after { |
||||
|
content: ''; |
||||
|
position: absolute; |
||||
|
top: 14rpx; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
height: 8rpx; |
||||
|
background: #e0e0e0; |
||||
|
z-index: 1; |
||||
|
} |
||||
|
|
||||
|
.progress-dot { |
||||
|
width: 28rpx; |
||||
|
height: 28rpx; |
||||
|
background: #e0e0e0; |
||||
|
border-radius: 50%; |
||||
|
transition: all 0.3s ease; |
||||
|
position: relative; |
||||
|
z-index: 2; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
font-size: 16rpx; |
||||
|
color: transparent; |
||||
|
} |
||||
|
|
||||
|
.progress-dot.active { |
||||
|
background: #FF6B00; |
||||
|
color: white; |
||||
|
transform: scale(1.1); |
||||
|
box-shadow: 0 4rpx 12rpx rgba(255, 107, 0, 0.3); |
||||
|
} |
||||
|
|
||||
|
.progress-text { |
||||
|
font-size: 28rpx; |
||||
|
color: #FF6B00; |
||||
|
text-align: center; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
/* 步骤内容 */ |
||||
|
.evaluate-step { |
||||
|
padding: 60rpx 32rpx 40rpx; |
||||
|
display: block; |
||||
|
align-items: flex-start; |
||||
|
justify-content: flex-start; |
||||
|
} |
||||
|
|
||||
|
.step-content { |
||||
|
max-width: 100%; |
||||
|
display: block; |
||||
|
align-items: flex-start; |
||||
|
justify-content: flex-start; |
||||
|
} |
||||
|
|
||||
|
.step-title { |
||||
|
font-size: 48rpx; |
||||
|
font-weight: 700; |
||||
|
color: #333; |
||||
|
margin-bottom: 20rpx; |
||||
|
letter-spacing: 1rpx; |
||||
|
} |
||||
|
|
||||
|
.step-subtitle { |
||||
|
font-size: 28rpx; |
||||
|
color: #666; |
||||
|
margin-bottom: 60rpx; |
||||
|
line-height: 42rpx; |
||||
|
} |
||||
|
|
||||
|
/* 类别列表 */ |
||||
|
.category-list { |
||||
|
background: white; |
||||
|
border-radius: 20rpx; |
||||
|
overflow: hidden; |
||||
|
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.04); |
||||
|
} |
||||
|
|
||||
|
.category-item { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
padding: 36rpx; |
||||
|
border-bottom: 1rpx solid #f5f5f5; |
||||
|
transition: all 0.2s ease; |
||||
|
position: relative; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.category-item::before { |
||||
|
content: ''; |
||||
|
position: absolute; |
||||
|
left: 0; |
||||
|
top: 0; |
||||
|
bottom: 0; |
||||
|
width: 0; |
||||
|
background: linear-gradient(90deg, rgba(255, 107, 0, 0.05), transparent); |
||||
|
transition: width 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.category-item:active::before { |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.category-item:last-child { |
||||
|
border-bottom: none; |
||||
|
} |
||||
|
|
||||
|
.category-item.selected { |
||||
|
background: #fff8f0; |
||||
|
border-left: 8rpx solid #FF6B00; |
||||
|
} |
||||
|
|
||||
|
.category-icon { |
||||
|
font-size: 64rpx; |
||||
|
margin-right: 24rpx; |
||||
|
filter: drop-shadow(0 4rpx 8rpx rgba(0, 0, 0, 0.1)); |
||||
|
} |
||||
|
|
||||
|
.category-info { |
||||
|
flex: 1; |
||||
|
} |
||||
|
|
||||
|
.category-name { |
||||
|
font-size: 32rpx; |
||||
|
font-weight: 600; |
||||
|
color: #333; |
||||
|
margin-bottom: 8rpx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
flex-wrap: wrap; |
||||
|
} |
||||
|
|
||||
|
.rank-number { |
||||
|
display: inline-flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
width: 40rpx; |
||||
|
height: 40rpx; |
||||
|
background: #F5F5F5; |
||||
|
color: #666; |
||||
|
border-radius: 6rpx; |
||||
|
font-size: 26rpx; |
||||
|
font-weight: 600; |
||||
|
margin-right: 20rpx; |
||||
|
min-width: 40rpx; |
||||
|
line-height: 40rpx; |
||||
|
text-align: center; |
||||
|
transition: all 0.2s ease; |
||||
|
} |
||||
|
|
||||
|
/* 前三名的特殊样式 */ |
||||
|
.rank-number.rank-1 { |
||||
|
background: #FF6B00; |
||||
|
color: white; |
||||
|
transform: scale(1.05); |
||||
|
box-shadow: 0 4rpx 12rpx rgba(255, 107, 0, 0.25); |
||||
|
} |
||||
|
|
||||
|
.rank-number.rank-2 { |
||||
|
background: #FF9500; |
||||
|
color: white; |
||||
|
box-shadow: 0 4rpx 12rpx rgba(255, 149, 0, 0.2); |
||||
|
} |
||||
|
|
||||
|
.rank-number.rank-3 { |
||||
|
background: #FFB74D; |
||||
|
color: white; |
||||
|
box-shadow: 0 4rpx 12rpx rgba(255, 183, 77, 0.15); |
||||
|
} |
||||
|
|
||||
|
/* 销售标签样式 */ |
||||
|
.sales-tag { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
background: linear-gradient(135deg, #FF6B00, #FF9500); |
||||
|
padding: 6rpx 16rpx; |
||||
|
border-radius: 20rpx; |
||||
|
margin-left: 10rpx; |
||||
|
box-shadow: 0 4rpx 12rpx rgba(255, 107, 0, 0.2); |
||||
|
animation: pulse 2s infinite; |
||||
|
} |
||||
|
|
||||
|
@keyframes pulse { |
||||
|
0%, 100% { |
||||
|
transform: scale(1); |
||||
|
} |
||||
|
50% { |
||||
|
transform: scale(1.05); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.sales-icon { |
||||
|
font-size: 22rpx; |
||||
|
margin-right: 6rpx; |
||||
|
} |
||||
|
|
||||
|
.sales-count { |
||||
|
font-size: 22rpx; |
||||
|
color: #FFF; |
||||
|
font-weight: 600; |
||||
|
} |
||||
|
|
||||
|
.category-desc { |
||||
|
font-size: 24rpx; |
||||
|
color: #666; |
||||
|
} |
||||
|
|
||||
|
.category-arrow { |
||||
|
font-size: 36rpx; |
||||
|
color: #999; |
||||
|
} |
||||
|
|
||||
|
/* 选项列表 */ |
||||
|
.option-list { |
||||
|
background: white; |
||||
|
border-radius: 20rpx; |
||||
|
overflow: hidden; |
||||
|
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.04); |
||||
|
} |
||||
|
|
||||
|
.option-item { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: space-between; |
||||
|
padding: 36rpx; |
||||
|
border-bottom: 1rpx solid #f5f5f5; |
||||
|
transition: all 0.2s ease; |
||||
|
position: relative; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.option-item::before { |
||||
|
content: ''; |
||||
|
position: absolute; |
||||
|
left: 0; |
||||
|
top: 0; |
||||
|
bottom: 0; |
||||
|
width: 0; |
||||
|
background: linear-gradient(90deg, rgba(255, 107, 0, 0.05), transparent); |
||||
|
transition: width 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.option-item:active::before { |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.option-item:last-child { |
||||
|
border-bottom: none; |
||||
|
} |
||||
|
|
||||
|
.option-item.selected { |
||||
|
background: #fff8f0; |
||||
|
border-left: 8rpx solid #FF6B00; |
||||
|
} |
||||
|
|
||||
|
.option-text { |
||||
|
font-size: 32rpx; |
||||
|
color: #333; |
||||
|
font-weight: 500; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
flex-wrap: wrap; |
||||
|
flex: 1; |
||||
|
} |
||||
|
|
||||
|
/* 销售信息样式 */ |
||||
|
.sales-info { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
background: rgba(255, 107, 0, 0.1); |
||||
|
padding: 6rpx 16rpx; |
||||
|
border-radius: 20rpx; |
||||
|
margin-left: 15rpx; |
||||
|
transition: all 0.2s ease; |
||||
|
} |
||||
|
|
||||
|
.sales-info .sales-icon { |
||||
|
font-size: 22rpx; |
||||
|
margin-right: 6rpx; |
||||
|
} |
||||
|
|
||||
|
.sales-info .sales-count { |
||||
|
font-size: 22rpx; |
||||
|
color: #FF6B00; |
||||
|
font-weight: 600; |
||||
|
} |
||||
|
|
||||
|
.option-arrow { |
||||
|
font-size: 36rpx; |
||||
|
color: #999; |
||||
|
transition: transform 0.2s ease; |
||||
|
} |
||||
|
|
||||
|
.option-item:active .option-arrow { |
||||
|
transform: translateX(4rpx); |
||||
|
} |
||||
|
|
||||
|
/* 条件列表 */ |
||||
|
.condition-list { |
||||
|
background: white; |
||||
|
border-radius: 20rpx; |
||||
|
overflow: hidden; |
||||
|
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.04); |
||||
|
} |
||||
|
|
||||
|
.condition-item { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: space-between; |
||||
|
padding: 36rpx; |
||||
|
border-bottom: 1rpx solid #f5f5f5; |
||||
|
transition: all 0.3s ease; |
||||
|
position: relative; |
||||
|
overflow: hidden; |
||||
|
text-align: left; |
||||
|
} |
||||
|
|
||||
|
.condition-item::before { |
||||
|
content: ''; |
||||
|
position: absolute; |
||||
|
left: 0; |
||||
|
top: 0; |
||||
|
bottom: 0; |
||||
|
width: 0; |
||||
|
background: linear-gradient(90deg, rgba(255, 107, 0, 0.05), transparent); |
||||
|
transition: width 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.condition-item:active::before { |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.condition-item:last-child { |
||||
|
border-bottom: none; |
||||
|
} |
||||
|
|
||||
|
.condition-item.selected { |
||||
|
background: #fff8f0; |
||||
|
border-left: 8rpx solid #FF6B00; |
||||
|
} |
||||
|
|
||||
|
.condition-info { |
||||
|
flex: 1; |
||||
|
text-align: left; |
||||
|
align-self: flex-start; |
||||
|
} |
||||
|
|
||||
|
.condition-name { |
||||
|
font-size: 32rpx; |
||||
|
font-weight: 600; |
||||
|
color: #333; |
||||
|
margin-bottom: 12rpx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
text-align: left; |
||||
|
justify-content: flex-start; |
||||
|
align-self: flex-start; |
||||
|
} |
||||
|
|
||||
|
.condition-desc { |
||||
|
font-size: 26rpx; |
||||
|
color: #666; |
||||
|
line-height: 36rpx; |
||||
|
text-align: left; |
||||
|
align-self: flex-start; |
||||
|
} |
||||
|
|
||||
|
.condition-check { |
||||
|
color: #FF6B00; |
||||
|
font-size: 36rpx; |
||||
|
font-weight: bold; |
||||
|
background: white; |
||||
|
width: 50rpx; |
||||
|
height: 50rpx; |
||||
|
border-radius: 50%; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
box-shadow: 0 4rpx 12rpx rgba(255, 107, 0, 0.3); |
||||
|
} |
||||
|
|
||||
|
/* 获取报价按钮 */ |
||||
|
.get-price-section { |
||||
|
margin-top: 80rpx; |
||||
|
padding: 0 32rpx; |
||||
|
} |
||||
|
|
||||
|
.get-price-btn { |
||||
|
background: linear-gradient(135deg, #FF6B00, #FF9500); |
||||
|
color: white; |
||||
|
border: none; |
||||
|
border-radius: 50rpx; |
||||
|
padding: 32rpx 0; |
||||
|
font-size: 34rpx; |
||||
|
font-weight: 700; |
||||
|
width: 100%; |
||||
|
box-shadow: 0 10rpx 30rpx rgba(255, 107, 0, 0.35); |
||||
|
transition: all 0.3s ease; |
||||
|
letter-spacing: 2rpx; |
||||
|
} |
||||
|
|
||||
|
.get-price-btn:active { |
||||
|
transform: scale(0.98); |
||||
|
box-shadow: 0 6rpx 20rpx rgba(255, 107, 0, 0.3); |
||||
|
} |
||||
|
|
||||
|
.get-price-btn::after { |
||||
|
content: none; |
||||
|
} |
||||
|
|
||||
|
/* 结果页面 */ |
||||
|
.result-step { |
||||
|
background: linear-gradient(135deg, #fff8f0, #ffffff); |
||||
|
min-height: 100vh; |
||||
|
} |
||||
|
|
||||
|
.result-header { |
||||
|
text-align: center; |
||||
|
padding: 80rpx 0 30rpx; |
||||
|
background: white; |
||||
|
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.03); |
||||
|
animation: fadeIn 0.8s ease-out; |
||||
|
} |
||||
|
|
||||
|
.result-icon { |
||||
|
font-size: 140rpx; |
||||
|
margin-bottom: 30rpx; |
||||
|
animation: bounce 1s ease-out; |
||||
|
} |
||||
|
|
||||
|
@keyframes bounce { |
||||
|
0% { |
||||
|
transform: translateY(-30rpx) scale(0.8); |
||||
|
opacity: 0; |
||||
|
} |
||||
|
70% { |
||||
|
transform: translateY(10rpx) scale(1.1); |
||||
|
} |
||||
|
100% { |
||||
|
transform: translateY(0) scale(1); |
||||
|
opacity: 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.result-title { |
||||
|
font-size: 48rpx; |
||||
|
font-weight: 700; |
||||
|
color: #333; |
||||
|
margin-bottom: 20rpx; |
||||
|
letter-spacing: 2rpx; |
||||
|
animation: fadeUp 0.6s ease-out; |
||||
|
} |
||||
|
|
||||
|
.result-subtitle { |
||||
|
font-size: 28rpx; |
||||
|
color: #666; |
||||
|
animation: fadeUp 0.6s ease-out 0.2s both; |
||||
|
} |
||||
|
|
||||
|
@keyframes fadeUp { |
||||
|
from { |
||||
|
transform: translateY(20rpx); |
||||
|
opacity: 0; |
||||
|
} |
||||
|
to { |
||||
|
transform: translateY(0); |
||||
|
opacity: 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.result-content { |
||||
|
padding: 40rpx 32rpx; |
||||
|
} |
||||
|
|
||||
|
/* 商品信息卡片 */ |
||||
|
.product-info-card { |
||||
|
background: white; |
||||
|
border-radius: 20rpx; |
||||
|
padding: 40rpx; |
||||
|
margin-bottom: 50rpx; |
||||
|
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.04); |
||||
|
animation: slideUp 0.8s ease-out; |
||||
|
} |
||||
|
|
||||
|
@keyframes slideUp { |
||||
|
from { |
||||
|
transform: translateY(40rpx); |
||||
|
opacity: 0; |
||||
|
} |
||||
|
to { |
||||
|
transform: translateY(0); |
||||
|
opacity: 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.product-type { |
||||
|
font-size: 36rpx; |
||||
|
font-weight: 700; |
||||
|
color: #333; |
||||
|
margin-bottom: 20rpx; |
||||
|
letter-spacing: 1rpx; |
||||
|
} |
||||
|
|
||||
|
.product-details { |
||||
|
display: flex; |
||||
|
gap: 20rpx; |
||||
|
flex-wrap: wrap; |
||||
|
} |
||||
|
|
||||
|
.product-brand, .product-model { |
||||
|
font-size: 28rpx; |
||||
|
color: #666; |
||||
|
background: #f5f5f5; |
||||
|
padding: 14rpx 28rpx; |
||||
|
border-radius: 30rpx; |
||||
|
font-weight: 500; |
||||
|
transition: all 0.2s ease; |
||||
|
} |
||||
|
|
||||
|
.product-brand:active, .product-model:active { |
||||
|
background: #e8e8e8; |
||||
|
} |
||||
|
|
||||
|
/* 价格结果 */ |
||||
|
.price-result { |
||||
|
text-align: center; |
||||
|
margin-top: 30rpx; |
||||
|
margin-bottom: 0; |
||||
|
padding: 0 32rpx; |
||||
|
animation: pulseScale 1s ease-out; |
||||
|
} |
||||
|
|
||||
|
@keyframes pulseScale { |
||||
|
0% { |
||||
|
transform: scale(0.9); |
||||
|
opacity: 0; |
||||
|
} |
||||
|
50% { |
||||
|
transform: scale(1.05); |
||||
|
} |
||||
|
100% { |
||||
|
transform: scale(1); |
||||
|
opacity: 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.price-label { |
||||
|
font-size: 28rpx; |
||||
|
color: #666; |
||||
|
margin-bottom: 24rpx; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
.price-amount { |
||||
|
display: flex; |
||||
|
align-items: baseline; |
||||
|
justify-content: center; |
||||
|
gap: 12rpx; |
||||
|
margin-bottom: 16rpx; |
||||
|
} |
||||
|
|
||||
|
.price-symbol { |
||||
|
font-size: 44rpx; |
||||
|
color: #FF6B00; |
||||
|
font-weight: 700; |
||||
|
} |
||||
|
|
||||
|
.price-number { |
||||
|
font-size: 80rpx; |
||||
|
font-weight: bold; |
||||
|
color: #FF6B00; |
||||
|
line-height: 1; |
||||
|
text-shadow: 0 4rpx 12rpx rgba(255, 107, 0, 0.2); |
||||
|
} |
||||
|
|
||||
|
.price-unit { |
||||
|
font-size: 30rpx; |
||||
|
color: #666; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
/* 状况汇总 */ |
||||
|
.condition-summary { |
||||
|
background: white; |
||||
|
border-radius: 20rpx; |
||||
|
padding: 40rpx; |
||||
|
margin-bottom: 40rpx; |
||||
|
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.04); |
||||
|
animation: slideUp 0.8s ease-out 0.4s both; |
||||
|
} |
||||
|
|
||||
|
.summary-title { |
||||
|
font-size: 32rpx; |
||||
|
font-weight: 700; |
||||
|
color: #333; |
||||
|
margin-bottom: 24rpx; |
||||
|
letter-spacing: 1rpx; |
||||
|
} |
||||
|
|
||||
|
.condition-items { |
||||
|
display: flex; |
||||
|
flex-wrap: wrap; |
||||
|
gap: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.condition-item { |
||||
|
background: #f5f5f5; |
||||
|
padding: 20rpx 32rpx; |
||||
|
border-radius: 16rpx; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
gap: 12rpx; |
||||
|
flex: 1; |
||||
|
min-width: 200rpx; |
||||
|
transition: all 0.2s ease; |
||||
|
} |
||||
|
|
||||
|
.condition-item:active { |
||||
|
background: #e8e8e8; |
||||
|
} |
||||
|
|
||||
|
.condition-label { |
||||
|
font-size: 26rpx; |
||||
|
color: #666; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
.condition-value { |
||||
|
font-size: 32rpx; |
||||
|
font-weight: 700; |
||||
|
color: #333; |
||||
|
letter-spacing: 1rpx; |
||||
|
} |
||||
|
|
||||
|
/* 价格提示 */ |
||||
|
.price-tips { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
gap: 12rpx; |
||||
|
padding: 24rpx; |
||||
|
background: rgba(255, 107, 0, 0.05); |
||||
|
border-radius: 16rpx; |
||||
|
margin: 0 20rpx 60rpx; |
||||
|
animation: fadeUp 0.8s ease-out 0.6s both; |
||||
|
} |
||||
|
|
||||
|
.tip-icon { |
||||
|
font-size: 30rpx; |
||||
|
color: #FF6B00; |
||||
|
flex-shrink: 0; |
||||
|
margin-top: 4rpx; |
||||
|
} |
||||
|
|
||||
|
.tip-text { |
||||
|
font-size: 26rpx; |
||||
|
color: #666; |
||||
|
line-height: 38rpx; |
||||
|
flex: 1; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
/* 结果操作按钮 */ |
||||
|
.result-actions { |
||||
|
display: flex; |
||||
|
gap: 24rpx; |
||||
|
padding: 0 32rpx 60rpx; |
||||
|
animation: slideUp 0.8s ease-out 0.8s both; |
||||
|
} |
||||
|
|
||||
|
.btn-secondary { |
||||
|
flex: 1; |
||||
|
background: white; |
||||
|
color: #666; |
||||
|
border: 1rpx solid #e0e0e0; |
||||
|
border-radius: 50rpx; |
||||
|
padding: 32rpx 0; |
||||
|
font-size: 34rpx; |
||||
|
font-weight: 700; |
||||
|
transition: all 0.3s ease; |
||||
|
letter-spacing: 2rpx; |
||||
|
box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.04); |
||||
|
} |
||||
|
|
||||
|
.btn-secondary:active { |
||||
|
background: #f8f8f8; |
||||
|
transform: scale(0.98); |
||||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.03); |
||||
|
} |
||||
|
|
||||
|
.btn-primary { |
||||
|
flex: 1; |
||||
|
background: linear-gradient(135deg, #FF6B00, #FF9500); |
||||
|
color: white; |
||||
|
border: none; |
||||
|
border-radius: 50rpx; |
||||
|
padding: 32rpx 0; |
||||
|
font-size: 34rpx; |
||||
|
font-weight: 700; |
||||
|
transition: all 0.3s ease; |
||||
|
letter-spacing: 2rpx; |
||||
|
box-shadow: 0 10rpx 30rpx rgba(255, 107, 0, 0.35); |
||||
|
} |
||||
|
|
||||
|
.btn-primary:active { |
||||
|
transform: scale(0.98); |
||||
|
box-shadow: 0 6rpx 20rpx rgba(255, 107, 0, 0.3); |
||||
|
} |
||||
|
|
||||
|
.btn-secondary::after, .btn-primary::after { |
||||
|
content: none; |
||||
|
} |
||||
@ -0,0 +1,977 @@ |
|||||
|
// pages/index/index.js
|
||||
|
const API = require('../../utils/api.js'); |
||||
|
Page({ |
||||
|
data: { |
||||
|
currentUserType: null, |
||||
|
showUserInfoForm: false, |
||||
|
avatarUrl: 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0', |
||||
|
nickname: '', |
||||
|
showAuthModal: false, |
||||
|
showOneKeyLoginModal: false, |
||||
|
// 测试模式开关,用于在未完成微信认证时进行测试
|
||||
|
testMode: true |
||||
|
}, |
||||
|
|
||||
|
onLoad() { |
||||
|
console.log('首页初始化') |
||||
|
}, |
||||
|
|
||||
|
onShow: function () { |
||||
|
// 页面显示
|
||||
|
// 更新自定义tabBar状态
|
||||
|
if (typeof this.getTabBar === 'function' && this.getTabBar()) { |
||||
|
this.getTabBar().setData({ |
||||
|
selected: 0 |
||||
|
}); |
||||
|
} |
||||
|
// 更新全局tab状态
|
||||
|
const app = getApp(); |
||||
|
app.updateCurrentTab('index'); |
||||
|
}, |
||||
|
|
||||
|
// 选择买家身份
|
||||
|
async chooseBuyer() { |
||||
|
// 买家不需要登录验证,直接跳转到买家页面
|
||||
|
this.finishSetUserType('buyer'); |
||||
|
}, |
||||
|
|
||||
|
// 选择卖家身份
|
||||
|
async chooseSeller() { |
||||
|
// 先检查是否登录
|
||||
|
this.checkLoginAndProceed('seller'); |
||||
|
}, |
||||
|
|
||||
|
// 检查登录状态并继续操作
|
||||
|
checkLoginAndProceed(type) { |
||||
|
// 检查本地存储的登录信息
|
||||
|
const openid = wx.getStorageSync('openid'); |
||||
|
const userId = wx.getStorageSync('userId'); |
||||
|
const userInfo = wx.getStorageSync('userInfo'); |
||||
|
|
||||
|
if (openid && userId && userInfo) { |
||||
|
console.log('用户已登录,直接处理身份选择'); |
||||
|
// 用户已登录,直接处理
|
||||
|
if (type === 'buyer') { |
||||
|
this.finishSetUserType(type); |
||||
|
} else if (type === 'seller') { |
||||
|
this.handleSellerRoute(); |
||||
|
} |
||||
|
} else { |
||||
|
console.log('用户未登录,显示登录弹窗'); |
||||
|
// 用户未登录,显示一键登录弹窗
|
||||
|
this.setData({ |
||||
|
pendingUserType: type, |
||||
|
showOneKeyLoginModal: true |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 处理卖家路由逻辑
|
||||
|
async handleSellerRoute() { |
||||
|
try { |
||||
|
|
||||
|
// 查询用户信息获取partnerstatus字段
|
||||
|
const userInfo = await API.getUserInfo(); |
||||
|
if (userInfo && userInfo.data && userInfo.data.partnerstatus) { |
||||
|
// 将partnerstatus存储到本地
|
||||
|
wx.setStorageSync('partnerstatus', userInfo.data.partnerstatus); |
||||
|
|
||||
|
console.log('获取到的partnerstatus:', userInfo.data.partnerstatus); |
||||
|
|
||||
|
// 根据partnerstatus值控制路由跳转
|
||||
|
if (userInfo.data.partnerstatus === 'approved') { |
||||
|
// 如果为approved,则进入seller/index页面
|
||||
|
wx.switchTab({ url: '/pages/seller/index' }); |
||||
|
} else { |
||||
|
// 否则进入pages/settlement/index页面
|
||||
|
wx.navigateTo({ url: '/pages/settlement/index' }); |
||||
|
} |
||||
|
} else { |
||||
|
// 如果没有获取到partnerstatus,默认进入settlement页面
|
||||
|
console.log('未获取到partnerstatus字段'); |
||||
|
wx.navigateTo({ url: '/pages/settlement/index' }); |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.error('获取用户信息失败:', error); |
||||
|
// 出错时也进入settlement页面
|
||||
|
wx.navigateTo({ url: '/pages/settlement/index' }); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 跳转到估价页面
|
||||
|
|
||||
|
|
||||
|
// 显示一键登录弹窗
|
||||
|
showOneKeyLogin() { |
||||
|
this.setData({ |
||||
|
showAuthModal: false, |
||||
|
showOneKeyLoginModal: true |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 关闭未授权提示弹窗
|
||||
|
closeAuthModal() { |
||||
|
this.setData({ showAuthModal: false }) |
||||
|
}, |
||||
|
|
||||
|
// 关闭一键登录弹窗
|
||||
|
closeOneKeyLoginModal() { |
||||
|
this.setData({ showOneKeyLoginModal: false }) |
||||
|
}, |
||||
|
|
||||
|
// 处理手机号授权
|
||||
|
async onGetPhoneNumber(e) { |
||||
|
// 打印详细错误信息,方便调试
|
||||
|
console.log('getPhoneNumber响应:', e.detail) |
||||
|
|
||||
|
// 关闭手机号授权弹窗
|
||||
|
this.setData({ showOneKeyLoginModal: false }) |
||||
|
|
||||
|
// 用户点击拒绝授权
|
||||
|
if (e.detail.errMsg === 'getPhoneNumber:fail user deny') { |
||||
|
wx.showToast({ |
||||
|
title: '需要授权手机号才能使用', |
||||
|
icon: 'none', |
||||
|
duration: 2000 |
||||
|
}) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// 处理没有权限的情况
|
||||
|
if (e.detail.errMsg === 'getPhoneNumber:fail no permission') { |
||||
|
// 如果是测试模式,跳过真实授权流程
|
||||
|
if (this.data.testMode) { |
||||
|
console.log('进入测试模式,跳过真实手机号授权') |
||||
|
await this.simulateLoginForTest() |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
wx.showToast({ |
||||
|
title: '当前环境无法获取手机号权限', |
||||
|
icon: 'none', |
||||
|
duration: 3000 |
||||
|
}) |
||||
|
// 增加关于微信认证要求的说明
|
||||
|
console.warn('获取手机号权限失败: 请注意,微信小程序获取手机号功能需要满足以下条件:1. 小程序必须完成微信企业认证;2. 需要在小程序后台配置相应权限;3. 必须使用button组件的open-type="getPhoneNumber"触发。') |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// 检查是否已经登录,避免重复授权
|
||||
|
const existingOpenid = wx.getStorageSync('openid') |
||||
|
const existingUserId = wx.getStorageSync('userId') |
||||
|
const existingUserInfo = wx.getStorageSync('userInfo') |
||||
|
|
||||
|
if (existingOpenid && existingUserId && existingUserInfo && existingUserInfo.phoneNumber !== '13800138000') { |
||||
|
console.log('用户已登录且手机号有效,直接完成身份设置') |
||||
|
// 直接完成身份设置,跳过重复授权
|
||||
|
const currentUserType = this.data.pendingUserType || this.data.currentUserType || 'buyer' |
||||
|
this.finishSetUserType(currentUserType) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
wx.showLoading({ |
||||
|
title: '登录中...', |
||||
|
mask: true |
||||
|
}) |
||||
|
|
||||
|
// 引入API服务
|
||||
|
const API = require('../../utils/api.js') |
||||
|
|
||||
|
try { |
||||
|
if (e.detail.errMsg === 'getPhoneNumber:ok') { |
||||
|
// 用户同意授权,实际处理授权流程
|
||||
|
console.log('用户同意授权获取手机号') |
||||
|
|
||||
|
// 1. 先执行微信登录获取code
|
||||
|
const loginRes = await new Promise((resolve, reject) => { |
||||
|
wx.login({ |
||||
|
success: resolve, |
||||
|
fail: reject |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
if (!loginRes.code) { |
||||
|
throw new Error('获取登录code失败') |
||||
|
} |
||||
|
|
||||
|
console.log('获取登录code成功:', loginRes.code) |
||||
|
|
||||
|
// 2. 使用code换取openid
|
||||
|
const openidRes = await API.getOpenid(loginRes.code) |
||||
|
|
||||
|
// 改进错误处理逻辑,更宽容地处理服务器返回格式,增加详细日志
|
||||
|
let openid = null; |
||||
|
let userId = null; |
||||
|
console.log('openidRes完整响应:', JSON.stringify(openidRes)); |
||||
|
|
||||
|
if (openidRes && typeof openidRes === 'object') { |
||||
|
// 适配服务器返回格式:{success: true, code: 200, message: '获取openid成功', data: {openid, userId}}
|
||||
|
if (openidRes.data && typeof openidRes.data === 'object') { |
||||
|
console.log('识别到标准服务器返回格式,从data字段提取信息'); |
||||
|
openid = openidRes.data.openid || openidRes.data.OpenID || null; |
||||
|
userId = openidRes.data.userId || null; |
||||
|
} else { |
||||
|
// 尝试从响应对象中直接提取openid,适配其他可能的格式
|
||||
|
console.log('尝试从根对象直接提取openid'); |
||||
|
openid = openidRes.openid || openidRes.OpenID || null; |
||||
|
userId = openidRes.userId || null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (!openid) { |
||||
|
console.error('无法从服务器响应中提取openid,完整响应:', JSON.stringify(openidRes)); |
||||
|
// 增加更友好的错误信息,指导用户检查服务器配置
|
||||
|
throw new Error(`获取openid失败: 服务器返回数据格式可能不符合预期,请检查服务器配置。响应数据为: ${JSON.stringify(openidRes)}`); |
||||
|
} |
||||
|
|
||||
|
console.log('获取openid成功:', openid) |
||||
|
|
||||
|
// 3. 存储openid和session_key
|
||||
|
wx.setStorageSync('openid', openid) |
||||
|
|
||||
|
// 从服务器返回中获取session_key
|
||||
|
if (openidRes && openidRes.session_key) { |
||||
|
wx.setStorageSync('sessionKey', openidRes.session_key) |
||||
|
} else if (openidRes && openidRes.data && openidRes.data.session_key) { |
||||
|
wx.setStorageSync('sessionKey', openidRes.data.session_key) |
||||
|
} |
||||
|
|
||||
|
// 优先使用从服务器响应data字段中提取的userId
|
||||
|
if (userId) { |
||||
|
wx.setStorageSync('userId', userId) |
||||
|
console.log('使用从服务器data字段提取的userId:', userId) |
||||
|
} else if (openidRes && openidRes.userId) { |
||||
|
wx.setStorageSync('userId', openidRes.userId) |
||||
|
console.log('使用服务器根对象中的userId:', openidRes.userId) |
||||
|
} else { |
||||
|
// 生成临时userId
|
||||
|
const tempUserId = 'user_' + Date.now() |
||||
|
wx.setStorageSync('userId', tempUserId) |
||||
|
console.log('生成临时userId:', tempUserId) |
||||
|
} |
||||
|
|
||||
|
// 4. 上传手机号加密数据到服务器解密
|
||||
|
const phoneData = { |
||||
|
...e.detail, |
||||
|
openid: openid |
||||
|
} |
||||
|
|
||||
|
console.log('准备上传手机号加密数据到服务器') |
||||
|
const phoneRes = await API.uploadPhoneNumberData(phoneData) |
||||
|
|
||||
|
// 改进手机号解密结果的处理逻辑
|
||||
|
if (!phoneRes || (!phoneRes.success && !phoneRes.phoneNumber)) { |
||||
|
// 如果服务器返回格式不标准但包含手机号,也接受
|
||||
|
if (phoneRes && phoneRes.phoneNumber) { |
||||
|
console.warn('服务器返回格式可能不符合预期,但成功获取手机号'); |
||||
|
} else { |
||||
|
throw new Error('获取手机号失败: ' + (phoneRes && phoneRes.message ? phoneRes.message : '未知错误')) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 检查是否有手机号冲突
|
||||
|
const hasPhoneConflict = phoneRes.phoneNumberConflict || false |
||||
|
const isNewPhone = phoneRes.isNewPhone || true |
||||
|
const phoneNumber = phoneRes.phoneNumber || null |
||||
|
|
||||
|
// 如果有手机号冲突且没有返回手机号,使用临时手机号
|
||||
|
const finalPhoneNumber = hasPhoneConflict && !phoneNumber ? '13800138000' : phoneNumber |
||||
|
|
||||
|
console.log('手机号解密结果:', { |
||||
|
phoneNumber: finalPhoneNumber, |
||||
|
hasPhoneConflict: hasPhoneConflict, |
||||
|
isNewPhone: isNewPhone |
||||
|
}) |
||||
|
|
||||
|
// 5. 获取用户微信名称和头像
|
||||
|
let userProfile = null; |
||||
|
try { |
||||
|
userProfile = await new Promise((resolve, reject) => { |
||||
|
wx.getUserProfile({ |
||||
|
desc: '用于完善会员资料', |
||||
|
success: resolve, |
||||
|
fail: reject |
||||
|
}); |
||||
|
}); |
||||
|
console.log('获取用户信息成功:', userProfile); |
||||
|
} catch (err) { |
||||
|
console.warn('获取用户信息失败:', err); |
||||
|
// 如果获取失败,使用默认值
|
||||
|
} |
||||
|
|
||||
|
// 6. 创建用户信息
|
||||
|
const tempUserInfo = { |
||||
|
nickName: userProfile ? userProfile.userInfo.nickName : '微信用户', |
||||
|
avatarUrl: userProfile ? userProfile.userInfo.avatarUrl : this.data.avatarUrl, |
||||
|
gender: userProfile ? userProfile.userInfo.gender : 0, |
||||
|
country: userProfile ? userProfile.userInfo.country : '', |
||||
|
province: userProfile ? userProfile.userInfo.province : '', |
||||
|
city: userProfile ? userProfile.userInfo.city : '', |
||||
|
language: userProfile ? userProfile.userInfo.language : 'zh_CN', |
||||
|
phoneNumber: finalPhoneNumber |
||||
|
} |
||||
|
|
||||
|
// 从本地存储获取userId(使用已声明的变量)
|
||||
|
const storedUserId = wx.getStorageSync('userId') |
||||
|
// 优先使用用户之前选择的身份类型,如果没有则尝试获取已存储的或默认为买家
|
||||
|
const users = wx.getStorageSync('users') || {} |
||||
|
const currentUserType = this.data.pendingUserType || this.data.currentUserType || |
||||
|
(users[storedUserId] && users[storedUserId].type ? users[storedUserId].type : 'buyer') |
||||
|
|
||||
|
console.log('用户身份类型:', currentUserType) |
||||
|
|
||||
|
// 清除临时存储的身份类型
|
||||
|
if (this.data.pendingUserType) { |
||||
|
this.setData({ pendingUserType: null }) |
||||
|
} |
||||
|
|
||||
|
// 保存用户信息并等待上传完成
|
||||
|
console.log('开始保存用户信息并上传到服务器...') |
||||
|
const uploadResult = await this.saveUserInfo(tempUserInfo, currentUserType) |
||||
|
console.log('用户信息保存并上传完成') |
||||
|
|
||||
|
wx.hideLoading() |
||||
|
|
||||
|
// 根据服务器返回的结果显示不同的提示
|
||||
|
if (uploadResult && uploadResult.phoneNumberConflict) { |
||||
|
wx.showToast({ |
||||
|
title: '登录成功,但手机号已被其他账号绑定', |
||||
|
icon: 'none', |
||||
|
duration: 3000 |
||||
|
}) |
||||
|
} else { |
||||
|
wx.showToast({ |
||||
|
title: '登录成功,手机号已绑定', |
||||
|
icon: 'success', |
||||
|
duration: 2000 |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
// 完成设置并跳转
|
||||
|
this.finishSetUserType(currentUserType) |
||||
|
} else { |
||||
|
// 用户拒绝授权或其他情况
|
||||
|
console.log('手机号授权失败:', e.detail.errMsg) |
||||
|
// 不再抛出错误,而是显示友好的提示
|
||||
|
wx.hideLoading() |
||||
|
wx.showToast({ |
||||
|
title: '需要授权手机号才能使用', |
||||
|
icon: 'none', |
||||
|
duration: 2000 |
||||
|
}) |
||||
|
return |
||||
|
} |
||||
|
} catch (error) { |
||||
|
wx.hideLoading() |
||||
|
console.error('登录过程中发生错误:', error) |
||||
|
|
||||
|
// 更具体的错误提示
|
||||
|
let errorMsg = '登录失败,请重试' |
||||
|
if (error.message.includes('网络')) { |
||||
|
errorMsg = '网络连接失败,请检查网络后重试' |
||||
|
} else if (error.message.includes('服务器')) { |
||||
|
errorMsg = '服务器连接失败,请稍后重试' |
||||
|
} |
||||
|
|
||||
|
wx.showToast({ |
||||
|
title: errorMsg, |
||||
|
icon: 'none', |
||||
|
duration: 3000 |
||||
|
}) |
||||
|
|
||||
|
// 清除可能已经保存的不完整信息
|
||||
|
try { |
||||
|
wx.removeStorageSync('openid') |
||||
|
wx.removeStorageSync('sessionKey') |
||||
|
wx.removeStorageSync('userId') |
||||
|
} catch (e) { |
||||
|
console.error('清除临时登录信息失败:', e) |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 处理用户基本信息授权
|
||||
|
handleUserAuth(type) { |
||||
|
// 保存当前用户类型
|
||||
|
this.setData({ |
||||
|
currentUserType: type |
||||
|
}) |
||||
|
|
||||
|
// 先执行微信登录
|
||||
|
this.doWechatLogin(type) |
||||
|
}, |
||||
|
|
||||
|
// 测试模式下模拟登录流程
|
||||
|
async simulateLoginForTest() { |
||||
|
wx.showLoading({ |
||||
|
title: '测试模式登录中...', |
||||
|
mask: true |
||||
|
}) |
||||
|
|
||||
|
try { |
||||
|
// 1. 模拟微信登录,生成测试用的code
|
||||
|
const mockCode = 'test_code_' + Date.now() |
||||
|
console.log('模拟获取登录code:', mockCode) |
||||
|
|
||||
|
// 2. 模拟获取openid和userId
|
||||
|
const mockOpenid = 'test_openid_' + Date.now() |
||||
|
const mockUserId = 'test_user_' + Date.now() |
||||
|
|
||||
|
console.log('模拟获取openid:', mockOpenid) |
||||
|
console.log('模拟获取userId:', mockUserId) |
||||
|
|
||||
|
// 3. 存储测试数据
|
||||
|
wx.setStorageSync('openid', mockOpenid) |
||||
|
wx.setStorageSync('userId', mockUserId) |
||||
|
|
||||
|
// 4. 模拟手机号解密结果
|
||||
|
const mockPhoneNumber = '13800138000' |
||||
|
console.log('模拟手机号解密成功:', mockPhoneNumber) |
||||
|
|
||||
|
// 5. 创建模拟用户信息
|
||||
|
const mockUserInfo = { |
||||
|
nickName: '测试用户', |
||||
|
avatarUrl: this.data.avatarUrl, |
||||
|
gender: 0, |
||||
|
country: '测试国家', |
||||
|
province: '测试省份', |
||||
|
city: '测试城市', |
||||
|
language: 'zh_CN', |
||||
|
phoneNumber: mockPhoneNumber |
||||
|
} |
||||
|
|
||||
|
// 6. 获取用户身份类型(优先使用pendingUserType)
|
||||
|
const userId = wx.getStorageSync('userId') |
||||
|
const users = wx.getStorageSync('users') || {} |
||||
|
const currentUserType = this.data.pendingUserType || this.data.currentUserType || |
||||
|
(users[userId] && users[userId].type ? users[userId].type : 'buyer') |
||||
|
|
||||
|
console.log('测试模式用户身份类型:', currentUserType) |
||||
|
|
||||
|
// 7. 清除临时存储的身份类型
|
||||
|
if (this.data.pendingUserType) { |
||||
|
this.setData({ pendingUserType: null }) |
||||
|
} |
||||
|
|
||||
|
// 8. 保存用户信息并等待上传完成
|
||||
|
console.log('测试模式开始保存用户信息...') |
||||
|
// 在测试模式下也会上传用户信息到服务器,用于连通性测试
|
||||
|
await this.saveUserInfo(mockUserInfo, currentUserType) |
||||
|
console.log('测试模式用户信息保存完成') |
||||
|
|
||||
|
wx.hideLoading() |
||||
|
|
||||
|
// 9. 显示成功提示
|
||||
|
wx.showToast({ |
||||
|
title: '测试模式登录成功', |
||||
|
icon: 'success', |
||||
|
duration: 2000 |
||||
|
}) |
||||
|
|
||||
|
// 10. 完成设置并跳转
|
||||
|
this.finishSetUserType(currentUserType) |
||||
|
} catch (error) { |
||||
|
wx.hideLoading() |
||||
|
console.error('测试模式登录过程中发生错误:', error) |
||||
|
|
||||
|
wx.showToast({ |
||||
|
title: '测试模式登录失败', |
||||
|
icon: 'none', |
||||
|
duration: 2000 |
||||
|
}) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 执行微信登录并获取openid
|
||||
|
async doWechatLogin(type) { |
||||
|
// 显示加载提示
|
||||
|
wx.showLoading({ |
||||
|
title: '登录中...', |
||||
|
mask: true |
||||
|
}) |
||||
|
|
||||
|
try { |
||||
|
// 调用微信登录接口
|
||||
|
const loginRes = await new Promise((resolve, reject) => { |
||||
|
wx.login({ |
||||
|
success: resolve, |
||||
|
fail: reject |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
if (loginRes.code) { |
||||
|
console.log('微信登录成功,code:', loginRes.code) |
||||
|
|
||||
|
// 保存登录凭证
|
||||
|
try { |
||||
|
wx.setStorageSync('loginCode', loginRes.code) |
||||
|
} catch (e) { |
||||
|
console.error('保存登录凭证失败:', e) |
||||
|
} |
||||
|
|
||||
|
// 引入API服务
|
||||
|
const API = require('../../utils/api.js') |
||||
|
|
||||
|
// 发送code和用户类型到服务器换取openid和session_key
|
||||
|
try { |
||||
|
const openidRes = await API.getOpenid(loginRes.code, type) |
||||
|
console.log('获取openid响应:', openidRes) |
||||
|
|
||||
|
// 增强版响应处理逻辑,支持多种返回格式
|
||||
|
let openid = null; |
||||
|
let userId = null; |
||||
|
let sessionKey = null; |
||||
|
|
||||
|
// 优先从data字段获取数据
|
||||
|
if (openidRes && openidRes.data && typeof openidRes.data === 'object') { |
||||
|
openid = openidRes.data.openid || openidRes.data.OpenID || null; |
||||
|
userId = openidRes.data.userId || openidRes.data.userid || null; |
||||
|
sessionKey = openidRes.data.session_key || openidRes.data.sessionKey || null; |
||||
|
} |
||||
|
|
||||
|
// 如果data为空或不存在,尝试从响应对象直接获取
|
||||
|
if (!openid && openidRes && typeof openidRes === 'object') { |
||||
|
console.warn('服务器返回格式可能不符合预期,data字段为空或不存在,但尝试从根对象提取信息:', openidRes); |
||||
|
openid = openidRes.openid || openidRes.OpenID || null; |
||||
|
userId = openidRes.userId || openidRes.userid || null; |
||||
|
sessionKey = openidRes.session_key || openidRes.sessionKey || null; |
||||
|
} |
||||
|
|
||||
|
// 检查服务器状态信息
|
||||
|
const isSuccess = openidRes && (openidRes.success === true || openidRes.code === 200); |
||||
|
const serverMessage = openidRes && (openidRes.message || openidRes.msg); |
||||
|
|
||||
|
if (isSuccess && !openid) { |
||||
|
console.warn('服务器返回成功状态,但未包含有效的openid:', openidRes); |
||||
|
} |
||||
|
|
||||
|
// 打印获取到的信息,方便调试
|
||||
|
console.log('提取到的登录信息:', { openid, userId, sessionKey, serverMessage }); |
||||
|
|
||||
|
if (openid) { |
||||
|
// 存储openid和session_key
|
||||
|
wx.setStorageSync('openid', openid) |
||||
|
if (sessionKey) { |
||||
|
wx.setStorageSync('sessionKey', sessionKey) |
||||
|
} |
||||
|
|
||||
|
// 如果有userId,也存储起来
|
||||
|
if (userId) { |
||||
|
wx.setStorageSync('userId', userId) |
||||
|
} |
||||
|
console.log('获取openid成功并存储:', openid) |
||||
|
|
||||
|
// 验证登录状态并获取用户信息
|
||||
|
await this.validateLoginAndGetUserInfo(openid) |
||||
|
} else { |
||||
|
// 即使没有获取到openid,也要继续用户信息授权流程
|
||||
|
console.warn('未获取到有效的openid,但继续用户信息授权流程:', openidRes); |
||||
|
// 设置一个临时的openid以便继续流程
|
||||
|
wx.setStorageSync('openid', 'temp_' + Date.now()) |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.error('获取openid失败:', error) |
||||
|
// 即使获取openid失败,也继续用户信息授权流程
|
||||
|
} |
||||
|
|
||||
|
// 继续用户信息授权流程,等待完成
|
||||
|
await this.processUserInfoAuth(type) |
||||
|
} else { |
||||
|
wx.hideLoading() |
||||
|
console.error('微信登录失败:', loginRes) |
||||
|
wx.showToast({ |
||||
|
title: '登录失败,请重试', |
||||
|
icon: 'none', |
||||
|
duration: 2000 |
||||
|
}) |
||||
|
} |
||||
|
} catch (err) { |
||||
|
wx.hideLoading() |
||||
|
console.error('wx.login失败:', err) |
||||
|
wx.showToast({ |
||||
|
title: '获取登录状态失败', |
||||
|
icon: 'none', |
||||
|
duration: 2000 |
||||
|
}) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 验证登录状态并获取用户信息
|
||||
|
async validateLoginAndGetUserInfo(openid) { |
||||
|
try { |
||||
|
// 引入API服务
|
||||
|
const API = require('../../utils/api.js') |
||||
|
|
||||
|
// 调用服务器验证登录状态
|
||||
|
const validateRes = await API.validateUserLogin() |
||||
|
|
||||
|
if (validateRes.success && validateRes.userInfo) { |
||||
|
// 服务器返回了用户信息,同步到本地
|
||||
|
const app = getApp() |
||||
|
const userInfo = validateRes.userInfo |
||||
|
|
||||
|
// 更新全局用户信息
|
||||
|
app.globalData.userInfo = userInfo |
||||
|
|
||||
|
// 存储用户信息到本地
|
||||
|
wx.setStorageSync('userInfo', userInfo) |
||||
|
console.log('验证登录状态成功,用户信息已同步:', userInfo) |
||||
|
|
||||
|
// 检查是否为临时手机号,如果是则提示用户重新授权
|
||||
|
if (userInfo.phoneNumber === '13800138000') { |
||||
|
console.warn('检测到临时手机号,建议用户重新授权') |
||||
|
// 设置重新授权标志
|
||||
|
wx.setStorageSync('needPhoneAuth', true) |
||||
|
} else { |
||||
|
// 清除可能存在的重新授权标志
|
||||
|
wx.removeStorageSync('needPhoneAuth') |
||||
|
console.log('手机号验证通过:', userInfo.phoneNumber) |
||||
|
} |
||||
|
|
||||
|
return true |
||||
|
} else { |
||||
|
console.warn('服务器验证失败,可能是新用户或登录状态无效') |
||||
|
return false |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.error('验证登录状态失败:', error) |
||||
|
// 如果验证失败,清除可能存在的无效登录信息
|
||||
|
try { |
||||
|
wx.removeStorageSync('openid') |
||||
|
wx.removeStorageSync('userId') |
||||
|
wx.removeStorageSync('userInfo') |
||||
|
} catch (e) { |
||||
|
console.error('清除无效登录信息失败:', e) |
||||
|
} |
||||
|
return false |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 处理用户信息授权
|
||||
|
async processUserInfoAuth(type) { |
||||
|
const app = getApp() |
||||
|
|
||||
|
// 如果已经有用户信息,直接完成设置并跳转
|
||||
|
if (app.globalData.userInfo) { |
||||
|
wx.hideLoading() |
||||
|
this.finishSetUserType(type) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// 优化:首次登录时自动创建临时用户信息并完成登录,不再需要用户填写表单
|
||||
|
// 获取已存储的userId或生成新的
|
||||
|
let userId = wx.getStorageSync('userId') |
||||
|
if (!userId) { |
||||
|
userId = 'user_' + Date.now() |
||||
|
wx.setStorageSync('userId', userId) |
||||
|
} |
||||
|
|
||||
|
// 创建临时用户信息
|
||||
|
const tempUserInfo = { |
||||
|
nickName: '微信用户', |
||||
|
avatarUrl: this.data.avatarUrl, |
||||
|
gender: 0, |
||||
|
country: '', |
||||
|
province: '', |
||||
|
city: '', |
||||
|
language: 'zh_CN' |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
// 保存临时用户信息并完成登录,等待数据上传完成
|
||||
|
await this.saveUserInfo(tempUserInfo, type) |
||||
|
|
||||
|
// 隐藏加载提示
|
||||
|
wx.hideLoading() |
||||
|
|
||||
|
// 数据上传完成后再跳转
|
||||
|
this.finishSetUserType(type) |
||||
|
} catch (error) { |
||||
|
console.error('处理用户信息授权失败:', error) |
||||
|
wx.hideLoading() |
||||
|
wx.showToast({ |
||||
|
title: '登录失败,请重试', |
||||
|
icon: 'none', |
||||
|
duration: 2000 |
||||
|
}) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 保存用户信息
|
||||
|
async saveUserInfo(userInfo, type) { |
||||
|
// 确保userId存在
|
||||
|
let userId = wx.getStorageSync('userId') |
||||
|
if (!userId) { |
||||
|
userId = 'user_' + Date.now() |
||||
|
wx.setStorageSync('userId', userId) |
||||
|
} |
||||
|
|
||||
|
// 保存用户信息到本地存储 - 修复首次获取问题
|
||||
|
let users = wx.getStorageSync('users') |
||||
|
// 如果users不存在或不是对象,初始化为空对象
|
||||
|
if (!users || typeof users !== 'object') { |
||||
|
users = {} |
||||
|
} |
||||
|
|
||||
|
// 初始化用户信息
|
||||
|
users[userId] = users[userId] || {} |
||||
|
users[userId].info = userInfo |
||||
|
users[userId].type = type |
||||
|
|
||||
|
// 确保存储操作成功
|
||||
|
try { |
||||
|
wx.setStorageSync('users', users) |
||||
|
console.log('用户信息已成功保存到本地存储') |
||||
|
} catch (e) { |
||||
|
console.error('保存用户信息到本地存储失败:', e) |
||||
|
} |
||||
|
|
||||
|
// 保存用户信息到全局变量
|
||||
|
const app = getApp() |
||||
|
app.globalData.userInfo = userInfo |
||||
|
app.globalData.userType = type |
||||
|
console.log('用户信息已保存到全局变量:', userInfo) |
||||
|
|
||||
|
// 额外保存一份单独的userInfo到本地存储,便于checkPhoneAuthSetting方法检查
|
||||
|
try { |
||||
|
wx.setStorageSync('userInfo', userInfo) |
||||
|
console.log('单独的userInfo已保存') |
||||
|
} catch (e) { |
||||
|
console.error('保存单独的userInfo失败:', e) |
||||
|
} |
||||
|
|
||||
|
// 上传用户信息到服务器
|
||||
|
// 在测试模式下也上传用户信息,用于连通性测试
|
||||
|
console.log('准备上传用户信息到服务器进行测试...') |
||||
|
|
||||
|
// 确保测试数据包含服务器所需的所有字段
|
||||
|
const completeUserInfo = { |
||||
|
...userInfo, |
||||
|
// 确保包含服务器需要的必要字段
|
||||
|
nickName: userInfo.nickName || '测试用户', |
||||
|
phoneNumber: userInfo.phoneNumber || '13800138000' |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
const uploadResult = await this.uploadUserInfoToServer(completeUserInfo, userId, type) |
||||
|
console.log('用户信息上传到服务器成功') |
||||
|
return uploadResult // 返回上传结果
|
||||
|
} catch (error) { |
||||
|
console.error('用户信息上传到服务器失败:', error) |
||||
|
// 显示友好的提示,但不中断流程
|
||||
|
wx.showToast({ |
||||
|
title: '测试数据上传失败,不影响使用', |
||||
|
icon: 'none', |
||||
|
duration: 2000 |
||||
|
}) |
||||
|
// 不再抛出错误,而是返回默认成功结果,确保登录流程继续
|
||||
|
return { |
||||
|
success: true, |
||||
|
message: '本地登录成功,服务器连接失败' |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 处理头像选择
|
||||
|
onChooseAvatar(e) { |
||||
|
const { avatarUrl } = e.detail |
||||
|
this.setData({ |
||||
|
avatarUrl |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 处理昵称提交
|
||||
|
getUserName(e) { |
||||
|
const { nickname } = e.detail.value |
||||
|
const type = this.data.currentUserType |
||||
|
|
||||
|
if (!nickname) { |
||||
|
wx.showToast({ |
||||
|
title: '请输入昵称', |
||||
|
icon: 'none', |
||||
|
duration: 2000 |
||||
|
}) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// 创建用户信息对象
|
||||
|
const userInfo = { |
||||
|
nickName: nickname, |
||||
|
avatarUrl: this.data.avatarUrl, |
||||
|
// 其他可能需要的字段
|
||||
|
gender: 0, |
||||
|
country: '', |
||||
|
province: '', |
||||
|
city: '', |
||||
|
language: 'zh_CN' |
||||
|
} |
||||
|
|
||||
|
// 保存用户信息
|
||||
|
this.saveUserInfo(userInfo, type) |
||||
|
|
||||
|
// 隐藏表单
|
||||
|
this.setData({ |
||||
|
showUserInfoForm: false |
||||
|
}) |
||||
|
|
||||
|
// 完成设置并跳转
|
||||
|
this.finishSetUserType(type) |
||||
|
}, |
||||
|
|
||||
|
// 取消用户信息表单
|
||||
|
cancelUserInfoForm() { |
||||
|
this.setData({ |
||||
|
showUserInfoForm: false |
||||
|
}) |
||||
|
wx.hideLoading() |
||||
|
}, |
||||
|
|
||||
|
// 上传用户信息到服务器
|
||||
|
async uploadUserInfoToServer(userInfo, userId, type) { |
||||
|
// 引入API服务
|
||||
|
const API = require('../../utils/api.js') |
||||
|
|
||||
|
// 获取openid
|
||||
|
const openid = wx.getStorageSync('openid') |
||||
|
|
||||
|
// 构造上传数据(包含openid和session_key)
|
||||
|
const uploadData = { |
||||
|
userId: userId, |
||||
|
openid: openid, |
||||
|
...userInfo, |
||||
|
type: type, |
||||
|
timestamp: Date.now() |
||||
|
} |
||||
|
|
||||
|
// 调用API上传用户信息并返回Promise
|
||||
|
try { |
||||
|
const res = await API.uploadUserInfo(uploadData) |
||||
|
console.log('用户信息上传成功:', res) |
||||
|
return res |
||||
|
} catch (err) { |
||||
|
console.error('用户信息上传失败:', err) |
||||
|
// 不再抛出错误,而是返回默认成功结果,确保登录流程继续
|
||||
|
// 这样即使服务器连接失败,本地登录也能完成
|
||||
|
return { |
||||
|
success: true, |
||||
|
message: '本地登录成功,服务器连接失败' |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 处理手机号授权结果(已重命名为onPhoneNumberResult,此方法已废弃)
|
||||
|
processPhoneAuthResult: function () { |
||||
|
console.warn('processPhoneAuthResult方法已废弃,请使用onPhoneNumberResult方法') |
||||
|
}, |
||||
|
|
||||
|
// 手机号授权处理
|
||||
|
async onPhoneNumberResult(e) { |
||||
|
console.log('手机号授权结果:', e) |
||||
|
if (e.detail.errMsg === 'getPhoneNumber:ok') { |
||||
|
// 用户同意授权,获取加密数据
|
||||
|
const phoneData = e.detail |
||||
|
|
||||
|
wx.showLoading({ title: '获取手机号中...' }) |
||||
|
|
||||
|
try { |
||||
|
// 引入API服务
|
||||
|
const API = require('../../utils/api.js') |
||||
|
|
||||
|
// 上传到服务器解密
|
||||
|
const res = await API.uploadPhoneNumberData(phoneData) |
||||
|
|
||||
|
wx.hideLoading() |
||||
|
|
||||
|
if (res.success && res.phoneNumber) { |
||||
|
console.log('获取手机号成功:', res.phoneNumber) |
||||
|
|
||||
|
// 保存手机号到用户信息
|
||||
|
const app = getApp() |
||||
|
const userInfo = app.globalData.userInfo || wx.getStorageSync('userInfo') || {} |
||||
|
userInfo.phoneNumber = res.phoneNumber |
||||
|
|
||||
|
// 更新本地和全局用户信息
|
||||
|
app.globalData.userInfo = userInfo |
||||
|
wx.setStorageSync('userInfo', userInfo) |
||||
|
|
||||
|
// 获取userId
|
||||
|
const userId = wx.getStorageSync('userId') |
||||
|
const users = wx.getStorageSync('users') || {} |
||||
|
const currentUserType = users[userId] && users[userId].type ? users[userId].type : '' |
||||
|
|
||||
|
// 同时更新服务器用户信息,确保上传完成
|
||||
|
console.log('开始更新服务器用户信息...') |
||||
|
if (!this.data.testMode) { |
||||
|
await this.uploadUserInfoToServer(userInfo, userId, currentUserType) |
||||
|
console.log('服务器用户信息更新完成') |
||||
|
} else { |
||||
|
console.log('测试模式下跳过服务器用户信息更新') |
||||
|
} |
||||
|
|
||||
|
wx.showToast({ title: '手机号绑定成功', icon: 'success' }) |
||||
|
} else { |
||||
|
console.error('获取手机号失败:', res) |
||||
|
wx.showToast({ title: '获取手机号失败', icon: 'none' }) |
||||
|
} |
||||
|
} catch (err) { |
||||
|
wx.hideLoading() |
||||
|
console.error('获取手机号失败:', err) |
||||
|
wx.showToast({ title: '获取手机号失败', icon: 'none' }) |
||||
|
} |
||||
|
} else { |
||||
|
console.log('用户拒绝授权手机号') |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 完成用户类型设置并跳转
|
||||
|
finishSetUserType(type) { |
||||
|
const userId = wx.getStorageSync('userId') |
||||
|
|
||||
|
// 更新用户类型
|
||||
|
let users = wx.getStorageSync('users') |
||||
|
// 检查users是否为对象,如果不是则重新初始化为空对象
|
||||
|
if (typeof users !== 'object' || users === null) { |
||||
|
users = {} |
||||
|
} |
||||
|
// 确保userId对应的用户对象存在
|
||||
|
if (!users[userId]) { |
||||
|
users[userId] = {} |
||||
|
} |
||||
|
users[userId].type = type |
||||
|
wx.setStorageSync('users', users) |
||||
|
|
||||
|
// 打标签
|
||||
|
let tags = wx.getStorageSync('tags') |
||||
|
// 检查tags是否为对象,如果不是则重新初始化为空对象
|
||||
|
if (typeof tags !== 'object' || tags === null) { |
||||
|
tags = {} |
||||
|
} |
||||
|
// 确保userId对应的标签数组存在
|
||||
|
tags[userId] = tags[userId] || [] |
||||
|
// 移除已有的身份标签
|
||||
|
tags[userId] = tags[userId].filter(tag => !tag.startsWith('身份:')) |
||||
|
// 添加新的身份标签
|
||||
|
tags[userId].push(`身份:${type}`) |
||||
|
wx.setStorageSync('tags', tags) |
||||
|
|
||||
|
console.log('用户类型设置完成,准备跳转到', type === 'buyer' ? '买家页面' : '卖家页面') |
||||
|
|
||||
|
// 添加小延迟确保所有异步操作都完成后再跳转
|
||||
|
setTimeout(() => { |
||||
|
// 跳转到对应页面
|
||||
|
if (type === 'buyer') { |
||||
|
wx.switchTab({ url: '/pages/buyer/index' }) |
||||
|
} else { |
||||
|
// 卖家身份需要处理partnerstatus逻辑,调用专门的方法
|
||||
|
this.handleSellerRoute() |
||||
|
} |
||||
|
}, 500) |
||||
|
}, |
||||
|
|
||||
|
|
||||
|
|
||||
|
// 前往个人中心
|
||||
|
toProfile() { |
||||
|
wx.switchTab({ url: '/pages/profile/index' }) |
||||
|
} |
||||
|
}) |
||||
@ -0,0 +1,5 @@ |
|||||
|
{ |
||||
|
"usingComponents": { |
||||
|
"navigation-bar": "/components/navigation-bar/navigation-bar" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,94 @@ |
|||||
|
<view class="container"> |
||||
|
<image src="/images/生成鸡蛋贸易平台图片.png" style="width: 100%; height: 425rpx; margin: -280rpx auto 20rpx; display: block;"></image> |
||||
|
<view class="title" style="margin-top: 60rpx;">中国最专业的鸡蛋现货交易平台</view> |
||||
|
<view class="desc" style="margin: 30rpx 0; text-align: center; padding: 0 20rpx;"> |
||||
|
请选择您的需求,我们将为您提供专属服务 |
||||
|
</view> |
||||
|
|
||||
|
<!-- 身份选择 --> |
||||
|
<view style="text-align: center; margin-top: 30rpx; margin-bottom: 30rpx;"> |
||||
|
<text style="font-size: 28rpx; color: #666; display: block; margin-bottom: 15rpx;"></text> |
||||
|
|
||||
|
<button class="btn buyer-btn" bindtap="chooseBuyer"> |
||||
|
我要买蛋 |
||||
|
</button> |
||||
|
|
||||
|
<button class="btn seller-btn" bindtap="chooseSeller"> |
||||
|
我要卖蛋 |
||||
|
</button> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 未授权登录提示弹窗 --> |
||||
|
<view wx:if="{{showAuthModal}}" class="modal-overlay"> |
||||
|
<view class="modal-container"> |
||||
|
<view class="modal-title"> |
||||
|
<text>提示</text> |
||||
|
</view> |
||||
|
<view class="modal-content"> |
||||
|
<text>您还没有授权登录</text> |
||||
|
</view> |
||||
|
<view class="modal-buttons"> |
||||
|
<button class="primary-button" bindtap="showOneKeyLogin"> |
||||
|
一键登录 |
||||
|
</button> |
||||
|
<button class="cancel-button" bindtap="closeAuthModal">取消</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 一键登录弹窗 --> |
||||
|
<view wx:if="{{showOneKeyLoginModal}}" class="modal-overlay"> |
||||
|
<view class="modal-container"> |
||||
|
<view class="modal-title"> |
||||
|
<text>授权登录</text> |
||||
|
</view> |
||||
|
<view class="modal-content"> |
||||
|
<text>请授权获取您的手机号用于登录</text> |
||||
|
</view> |
||||
|
<view class="modal-buttons"> |
||||
|
<button class="primary-button" open-type="getPhoneNumber" bind:getphonenumber="onGetPhoneNumber"> |
||||
|
授权获取手机号 |
||||
|
</button> |
||||
|
<button class="cancel-button" bindtap="closeOneKeyLoginModal">取消</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 用户信息填写弹窗 --> |
||||
|
<view wx:if="{{showUserInfoForm}}" class="modal-overlay"> |
||||
|
<view class="modal-container"> |
||||
|
<view class="modal-title"> |
||||
|
<text>完善个人信息</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 头像选择 --> |
||||
|
<view class="avatar-section"> |
||||
|
<button class="avatar-wrapper" open-type="chooseAvatar" bind:chooseavatar="onChooseAvatar"> |
||||
|
<image class="avatar" src="{{avatarUrl}}"></image> |
||||
|
</button> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 昵称输入 --> |
||||
|
<form bindsubmit="getUserName"> |
||||
|
<view class="form-group"> |
||||
|
<view class="form-label">昵称</view> |
||||
|
<input placeholder="请输入昵称" type="nickname" name="nickname" maxlength="32" class="form-input"></input> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 提交按钮 --> |
||||
|
<view class="form-actions"> |
||||
|
<button form-type="submit" class="confirm-button">确定</button> |
||||
|
</view> |
||||
|
</form> |
||||
|
|
||||
|
<!-- 取消按钮 --> |
||||
|
<view class="modal-buttons"> |
||||
|
<button class="cancel-button" bindtap="cancelUserInfoForm">取消</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="login-hint"> |
||||
|
已有账号?<text class="link-text" bindtap="toProfile">进入我的页面</text> |
||||
|
</view> |
||||
|
</view> |
||||
@ -0,0 +1,243 @@ |
|||||
|
/**index.wxss**/ |
||||
|
page { |
||||
|
height: 100vh; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); |
||||
|
margin: 0; |
||||
|
padding: 0; |
||||
|
} |
||||
|
|
||||
|
.container { |
||||
|
padding: 0; |
||||
|
margin: 0; |
||||
|
width: 100%; |
||||
|
} |
||||
|
.scrollarea { |
||||
|
flex: 1; |
||||
|
overflow-y: hidden; |
||||
|
} |
||||
|
|
||||
|
/* 玻璃质感按钮样式 */ |
||||
|
.btn { |
||||
|
/* 基础样式重置 */ |
||||
|
border: none; |
||||
|
border-radius: 24rpx; |
||||
|
font-size: 32rpx; |
||||
|
font-weight: 600; |
||||
|
padding: 28rpx 0; |
||||
|
margin: 0 auto 24rpx; |
||||
|
width: 80%; |
||||
|
display: block; |
||||
|
text-align: center; |
||||
|
white-space: nowrap; |
||||
|
line-height: 1.5; |
||||
|
|
||||
|
/* 玻璃质感效果 */ |
||||
|
background: rgba(255, 255, 255, 0.15); |
||||
|
backdrop-filter: blur(12rpx); |
||||
|
-webkit-backdrop-filter: blur(12rpx); |
||||
|
border: 1rpx solid rgba(255, 255, 255, 0.3); |
||||
|
box-shadow: |
||||
|
0 8rpx 32rpx rgba(31, 38, 135, 0.2), |
||||
|
0 4rpx 16rpx rgba(0, 0, 0, 0.1), |
||||
|
inset 0 2rpx 4rpx rgba(255, 255, 255, 0.7), |
||||
|
inset 0 -2rpx 4rpx rgba(0, 0, 0, 0.1); |
||||
|
|
||||
|
/* 过渡效果 */ |
||||
|
transition: all 0.3s ease; |
||||
|
position: relative; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
/* 买家按钮样式 */ |
||||
|
.buyer-btn { |
||||
|
color: #07c160; |
||||
|
background: rgba(7, 193, 96, 0.15); |
||||
|
} |
||||
|
|
||||
|
/* 卖家按钮样式 */ |
||||
|
.seller-btn { |
||||
|
color: #1677ff; |
||||
|
background: rgba(22, 119, 255, 0.15); |
||||
|
} |
||||
|
|
||||
|
/* 估价按钮样式 */ |
||||
|
.evaluate-btn { |
||||
|
color: #4CAF50; |
||||
|
background: rgba(76, 175, 80, 0.15); |
||||
|
} |
||||
|
|
||||
|
/* 立即入驻按钮样式 */ |
||||
|
.settlement-btn { |
||||
|
color: #2196F3; |
||||
|
background: rgba(33, 150, 243, 0.15); |
||||
|
} |
||||
|
|
||||
|
/* 按钮点击效果 */ |
||||
|
.btn:active { |
||||
|
transform: scale(0.98); |
||||
|
box-shadow: |
||||
|
0 4rpx 16rpx rgba(31, 38, 135, 0.1), |
||||
|
0 2rpx 8rpx rgba(0, 0, 0, 0.05), |
||||
|
inset 0 1rpx 2rpx rgba(255, 255, 255, 0.5), |
||||
|
inset 0 -1rpx 2rpx rgba(0, 0, 0, 0.05); |
||||
|
} |
||||
|
|
||||
|
/* 按钮悬浮光晕效果 */ |
||||
|
.btn::after { |
||||
|
content: ''; |
||||
|
position: absolute; |
||||
|
top: -50%; |
||||
|
left: -50%; |
||||
|
width: 200%; |
||||
|
height: 200%; |
||||
|
background: linear-gradient(45deg, transparent, rgba(255, 255, 255, 0.3), transparent); |
||||
|
transform: rotate(45deg); |
||||
|
animation: shine 3s infinite; |
||||
|
opacity: 0; |
||||
|
} |
||||
|
|
||||
|
@keyframes shine { |
||||
|
0% { transform: translateX(-100%) rotate(45deg); opacity: 0; } |
||||
|
50% { opacity: 0.2; } |
||||
|
100% { transform: translateX(100%) rotate(45deg); opacity: 0; } |
||||
|
} |
||||
|
|
||||
|
.btn:active::after { |
||||
|
animation: none; |
||||
|
} |
||||
|
|
||||
|
/* 弹窗样式 */ |
||||
|
.modal-overlay { |
||||
|
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: 999; |
||||
|
} |
||||
|
|
||||
|
.modal-container { |
||||
|
background-color: white; |
||||
|
border-radius: 16rpx; |
||||
|
width: 80%; |
||||
|
padding: 40rpx; |
||||
|
} |
||||
|
|
||||
|
.modal-title { |
||||
|
text-align: center; |
||||
|
margin-bottom: 30rpx; |
||||
|
} |
||||
|
|
||||
|
.modal-title text { |
||||
|
font-size: 36rpx; |
||||
|
font-weight: bold; |
||||
|
} |
||||
|
|
||||
|
.modal-content { |
||||
|
text-align: center; |
||||
|
margin-bottom: 40rpx; |
||||
|
color: #666; |
||||
|
} |
||||
|
|
||||
|
.modal-content text { |
||||
|
font-size: 32rpx; |
||||
|
} |
||||
|
|
||||
|
.modal-buttons { |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.primary-button { |
||||
|
background-color: #1677ff; |
||||
|
color: white; |
||||
|
width: 100%; |
||||
|
border-radius: 8rpx; |
||||
|
margin-bottom: 20rpx; |
||||
|
border: none; |
||||
|
} |
||||
|
|
||||
|
.cancel-button { |
||||
|
background: none; |
||||
|
color: #666; |
||||
|
border: none; |
||||
|
} |
||||
|
|
||||
|
/* 头像选择样式 */ |
||||
|
.avatar-section { |
||||
|
text-align: center; |
||||
|
margin-bottom: 40rpx; |
||||
|
} |
||||
|
|
||||
|
.avatar-wrapper { |
||||
|
padding: 0; |
||||
|
background: none; |
||||
|
border: none; |
||||
|
} |
||||
|
|
||||
|
.avatar { |
||||
|
width: 160rpx; |
||||
|
height: 160rpx; |
||||
|
border-radius: 50%; |
||||
|
} |
||||
|
|
||||
|
/* 表单样式 */ |
||||
|
.form-group { |
||||
|
margin-bottom: 30rpx; |
||||
|
} |
||||
|
|
||||
|
.form-label { |
||||
|
font-size: 28rpx; |
||||
|
margin-bottom: 10rpx; |
||||
|
display: block; |
||||
|
} |
||||
|
|
||||
|
.form-input { |
||||
|
border: 1rpx solid #eee; |
||||
|
border-radius: 8rpx; |
||||
|
padding: 20rpx; |
||||
|
width: 100%; |
||||
|
max-width: 100%; |
||||
|
box-sizing: border-box; |
||||
|
font-size: 28rpx; |
||||
|
} |
||||
|
|
||||
|
.form-actions { |
||||
|
text-align: center; |
||||
|
margin-top: 40rpx; |
||||
|
} |
||||
|
|
||||
|
.confirm-button { |
||||
|
background-color: #07c160; |
||||
|
color: white; |
||||
|
width: 100%; |
||||
|
border-radius: 8rpx; |
||||
|
border: none; |
||||
|
} |
||||
|
|
||||
|
/* 登录提示样式 */ |
||||
|
.login-hint { |
||||
|
margin-top: 50rpx; |
||||
|
font-size: 28rpx; |
||||
|
color: #666; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.link-text { |
||||
|
color: #1677ff; |
||||
|
} |
||||
|
|
||||
|
/* 标题样式 */ |
||||
|
.title { |
||||
|
font-size: 40rpx; |
||||
|
font-weight: bold; |
||||
|
text-align: center; |
||||
|
color: #333; |
||||
|
margin-top: 20rpx; |
||||
|
margin-bottom: 10rpx; |
||||
|
} |
||||
@ -0,0 +1,64 @@ |
|||||
|
Page({ |
||||
|
/** |
||||
|
* 页面的初始数据 |
||||
|
*/ |
||||
|
data: { |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面加载 |
||||
|
*/ |
||||
|
onLoad: function (options) { |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面初次渲染完成 |
||||
|
*/ |
||||
|
onReady: function () { |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面显示 |
||||
|
*/ |
||||
|
onShow: function () { |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面隐藏 |
||||
|
*/ |
||||
|
onHide: function () { |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面卸载 |
||||
|
*/ |
||||
|
onUnload: function () { |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 页面相关事件处理函数--监听用户下拉动作 |
||||
|
*/ |
||||
|
onPullDownRefresh: function () { |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 页面上拉触底事件的处理函数 |
||||
|
*/ |
||||
|
onReachBottom: function () { |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 用户点击右上角分享 |
||||
|
*/ |
||||
|
onShareAppMessage: function () { |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 返回首页 |
||||
|
*/ |
||||
|
onBackTap: function () { |
||||
|
wx.switchTab({ |
||||
|
url: '/pages/index/index' |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
@ -0,0 +1,4 @@ |
|||||
|
{ |
||||
|
"navigationBarTitleText": "又鸟蛋平台", |
||||
|
"usingComponents": {} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
<view class="container"> |
||||
|
<view class="icon"> |
||||
|
<view class="lock-icon"></view> |
||||
|
</view> |
||||
|
<view class="title">功能暂未开放</view> |
||||
|
<view class="subtitle">该功能正在紧张开发中,敬请期待</view> |
||||
|
<button class="back-btn" bindtap="onBackTap">返回首页</button> |
||||
|
</view> |
||||
@ -0,0 +1,69 @@ |
|||||
|
.container { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
height: 100vh; |
||||
|
width: 100vw; |
||||
|
background-color: #ffffff; |
||||
|
padding: 40rpx; |
||||
|
box-sizing: border-box; |
||||
|
margin: 0; |
||||
|
} |
||||
|
|
||||
|
/* 重置页面默认样式 */ |
||||
|
page { |
||||
|
height: 100vh; |
||||
|
width: 100vw; |
||||
|
margin: 0; |
||||
|
padding: 0; |
||||
|
background-color: #ffffff; |
||||
|
} |
||||
|
|
||||
|
.icon { |
||||
|
margin-bottom: 60rpx; |
||||
|
} |
||||
|
|
||||
|
.lock-icon { |
||||
|
width: 120rpx; |
||||
|
height: 120rpx; |
||||
|
background-size: contain; |
||||
|
background-repeat: no-repeat; |
||||
|
background-position: center; |
||||
|
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23FFD93D"><path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z"/></svg>'); |
||||
|
} |
||||
|
|
||||
|
.title { |
||||
|
font-size: 36rpx; |
||||
|
font-weight: bold; |
||||
|
color: #333333; |
||||
|
margin-bottom: 24rpx; |
||||
|
} |
||||
|
|
||||
|
.subtitle { |
||||
|
font-size: 28rpx; |
||||
|
color: #999999; |
||||
|
margin-bottom: 80rpx; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.back-btn { |
||||
|
width: 320rpx; |
||||
|
height: 88rpx; |
||||
|
background-color: #1989fa; |
||||
|
color: white; |
||||
|
font-size: 32rpx; |
||||
|
border-radius: 44rpx; |
||||
|
box-shadow: 0 4rpx 16rpx rgba(25, 137, 250, 0.3); |
||||
|
border: none; |
||||
|
outline: none; |
||||
|
} |
||||
|
|
||||
|
.back-btn::after { |
||||
|
border: none; |
||||
|
} |
||||
|
|
||||
|
.back-btn:active { |
||||
|
background-color: #0c7ad9; |
||||
|
transform: scale(0.98); |
||||
|
} |
||||
@ -0,0 +1,652 @@ |
|||||
|
// pages/profile/index.js
|
||||
|
Page({ |
||||
|
data: { |
||||
|
userInfo: {}, |
||||
|
userType: '', |
||||
|
userTags: [], |
||||
|
needPhoneAuth: false // 是否需要重新授权手机号
|
||||
|
}, |
||||
|
|
||||
|
onLoad() { |
||||
|
this.loadUserInfo() |
||||
|
}, |
||||
|
|
||||
|
onShow() { |
||||
|
this.loadUserInfo() |
||||
|
// 更新自定义tabBar状态
|
||||
|
if (typeof this.getTabBar === 'function' && this.getTabBar()) { |
||||
|
this.getTabBar().setData({ |
||||
|
selected: 3 |
||||
|
}); |
||||
|
} |
||||
|
// 更新全局tab状态
|
||||
|
const app = getApp(); |
||||
|
app.updateCurrentTab('profile'); |
||||
|
}, |
||||
|
|
||||
|
// 加载用户信息
|
||||
|
loadUserInfo() { |
||||
|
console.log('开始加载用户信息') |
||||
|
const app = getApp() |
||||
|
|
||||
|
// 从本地存储获取用户信息
|
||||
|
const localUserInfo = wx.getStorageSync('userInfo') || {} |
||||
|
if (app.globalData.userInfo) { |
||||
|
this.setData({ userInfo: app.globalData.userInfo }) |
||||
|
} else { |
||||
|
app.globalData.userInfo = localUserInfo |
||||
|
this.setData({ |
||||
|
userInfo: localUserInfo, |
||||
|
needPhoneAuth: !localUserInfo.phoneNumber || localUserInfo.phoneNumber === '13800138000' |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
// 加载用户类型和标签
|
||||
|
const userId = wx.getStorageSync('userId') |
||||
|
const openid = wx.getStorageSync('openid') |
||||
|
console.log('加载用户信息 - userId:', userId, 'openid:', openid ? '已获取' : '未获取') |
||||
|
|
||||
|
if (userId && openid) { |
||||
|
// 从服务器获取最新的用户信息,确保手机号是最新的
|
||||
|
this.refreshUserInfoFromServer(openid, userId) |
||||
|
|
||||
|
// 确保users存储结构存在
|
||||
|
let users = wx.getStorageSync('users') |
||||
|
if (!users) { |
||||
|
users = {} |
||||
|
wx.setStorageSync('users', users) |
||||
|
} |
||||
|
|
||||
|
if (!users[userId]) { |
||||
|
users[userId] = { type: '' } |
||||
|
wx.setStorageSync('users', users) |
||||
|
} |
||||
|
|
||||
|
const user = users[userId] |
||||
|
const currentType = this.formatUserType(user.type) |
||||
|
this.setData({ userType: currentType }) |
||||
|
console.log('加载用户信息 - 当前用户类型:', currentType) |
||||
|
|
||||
|
// 确保tags存储结构存在
|
||||
|
let tags = wx.getStorageSync('tags') |
||||
|
if (!tags) { |
||||
|
tags = {} |
||||
|
wx.setStorageSync('tags', tags) |
||||
|
} |
||||
|
|
||||
|
if (!tags[userId]) { |
||||
|
tags[userId] = [] |
||||
|
wx.setStorageSync('tags', tags) |
||||
|
} |
||||
|
|
||||
|
const userTags = tags[userId] || [] |
||||
|
console.log('加载用户信息 - 原始标签:', userTags) |
||||
|
|
||||
|
// 使用indexOf替代includes以解决Babel兼容性问题
|
||||
|
let firstCategoryTag = [] |
||||
|
let identityTags = [] |
||||
|
|
||||
|
// 查找第一个偏好品类标签
|
||||
|
for (let i = 0; i < userTags.length; i++) { |
||||
|
if (userTags[i].indexOf('偏好品类') !== -1) { |
||||
|
firstCategoryTag = [userTags[i]] |
||||
|
break |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 合并保留的标签
|
||||
|
let filteredTags = [...firstCategoryTag] |
||||
|
|
||||
|
// 始终根据当前用户类型显示对应的身份标签,而不是使用存储的标签
|
||||
|
if (user.type && user.type !== '') { |
||||
|
let identityLabel = '身份:not_set' |
||||
|
switch (user.type) { |
||||
|
case 'buyer': identityLabel = '身份:buyer'; break |
||||
|
case 'seller': identityLabel = '身份:seller'; break |
||||
|
case 'both': identityLabel = '身份:buyer+seller'; break |
||||
|
} |
||||
|
filteredTags.push(identityLabel) |
||||
|
console.log('加载用户信息 - 根据当前用户类型显示身份标签:', identityLabel) |
||||
|
} else { |
||||
|
// 如果没有用户类型,但有存储的身份标签,显示第一个
|
||||
|
for (let i = 0; i < userTags.length; i++) { |
||||
|
if (userTags[i].indexOf('身份') !== -1) { |
||||
|
filteredTags.push(userTags[i]) |
||||
|
console.log('加载用户信息 - 显示存储的身份标签:', userTags[i]) |
||||
|
break |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
console.log('加载用户信息 - 过滤后的标签:', filteredTags) |
||||
|
this.setData({ userTags: filteredTags }) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 从服务器刷新用户信息
|
||||
|
refreshUserInfoFromServer(openid, userId) { |
||||
|
const API = require('../../utils/api.js') |
||||
|
|
||||
|
API.getUserInfo(openid).then(res => { |
||||
|
console.log('从服务器获取用户信息成功:', res) |
||||
|
|
||||
|
if (res.success && res.data) { |
||||
|
const serverUserInfo = res.data |
||||
|
|
||||
|
// 检查手机号是否是临时手机号
|
||||
|
if (serverUserInfo.phoneNumber === '13800138000') { |
||||
|
console.warn('服务器返回的仍是临时手机号,用户可能需要重新授权手机号') |
||||
|
// 可以在这里显示提示,让用户重新授权手机号
|
||||
|
this.setData({ |
||||
|
needPhoneAuth: true |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
// 更新本地用户信息
|
||||
|
const app = getApp() |
||||
|
const updatedUserInfo = { |
||||
|
...app.globalData.userInfo, |
||||
|
...serverUserInfo |
||||
|
} |
||||
|
|
||||
|
app.globalData.userInfo = updatedUserInfo |
||||
|
wx.setStorageSync('userInfo', updatedUserInfo) |
||||
|
this.setData({ userInfo: updatedUserInfo }) |
||||
|
|
||||
|
console.log('用户信息已更新,昵称:', updatedUserInfo.nickName, '手机号:', updatedUserInfo.phoneNumber) |
||||
|
} |
||||
|
}).catch(err => { |
||||
|
console.error('从服务器获取用户信息失败:', err) |
||||
|
// 如果getUserInfo失败,尝试使用validateUserLogin作为备选
|
||||
|
API.validateUserLogin().then(res => { |
||||
|
console.log('使用validateUserLogin获取用户信息成功:', res) |
||||
|
|
||||
|
if (res.success && res.data) { |
||||
|
const serverUserInfo = res.data |
||||
|
|
||||
|
// 检查手机号是否是临时手机号
|
||||
|
if (serverUserInfo.phoneNumber === '13800138000') { |
||||
|
console.warn('服务器返回的仍是临时手机号,用户可能需要重新授权手机号') |
||||
|
this.setData({ |
||||
|
needPhoneAuth: true |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
// 更新本地用户信息
|
||||
|
const app = getApp() |
||||
|
const updatedUserInfo = { |
||||
|
...app.globalData.userInfo, |
||||
|
...serverUserInfo |
||||
|
} |
||||
|
|
||||
|
app.globalData.userInfo = updatedUserInfo |
||||
|
wx.setStorageSync('userInfo', updatedUserInfo) |
||||
|
this.setData({ userInfo: updatedUserInfo }) |
||||
|
|
||||
|
console.log('用户信息已更新(备选方案):', updatedUserInfo) |
||||
|
} |
||||
|
}).catch(validateErr => { |
||||
|
console.error('从服务器获取用户信息失败(包括备选方案):', validateErr) |
||||
|
// 如果服务器请求失败,继续使用本地缓存的信息
|
||||
|
}) |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 格式化用户类型显示
|
||||
|
formatUserType(type) { |
||||
|
switch (type) { |
||||
|
case 'buyer': return '买家' |
||||
|
case 'seller': return '卖家' |
||||
|
case 'both': return '买家+卖家' |
||||
|
default: return '未设置' |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 设置为买家
|
||||
|
setAsBuyer() { |
||||
|
this.switchUserType('buyer', '买家') |
||||
|
}, |
||||
|
|
||||
|
// 设置为卖家
|
||||
|
setAsSeller() { |
||||
|
this.switchUserType('seller', '卖家') |
||||
|
}, |
||||
|
|
||||
|
// 切换用户类型的通用方法
|
||||
|
switchUserType(newType, typeName) { |
||||
|
const userId = wx.getStorageSync('userId') |
||||
|
const openid = wx.getStorageSync('openid') |
||||
|
const userInfo = wx.getStorageSync('userInfo') |
||||
|
|
||||
|
if (!userId || !openid) { |
||||
|
wx.navigateTo({ url: '/pages/index/index' }) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// 更新本地存储中的用户类型
|
||||
|
let users = wx.getStorageSync('users') || {} |
||||
|
if (!users[userId]) { |
||||
|
users[userId] = {} |
||||
|
} |
||||
|
users[userId].type = newType |
||||
|
wx.setStorageSync('users', users) |
||||
|
|
||||
|
// 更新全局数据
|
||||
|
const app = getApp() |
||||
|
app.globalData.userType = newType |
||||
|
|
||||
|
// 上传更新后的用户信息到服务器
|
||||
|
this.uploadUserTypeToServer(openid, userId, userInfo, newType) |
||||
|
|
||||
|
// 更新页面显示
|
||||
|
this.setData({ |
||||
|
userType: this.formatUserType(newType) |
||||
|
}) |
||||
|
|
||||
|
wx.showToast({ |
||||
|
title: `已切换为${typeName}`, |
||||
|
icon: 'success', |
||||
|
duration: 2000 |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 上传用户类型到服务器
|
||||
|
uploadUserTypeToServer(openid, userId, userInfo, type) { |
||||
|
// 引入API服务
|
||||
|
const API = require('../../utils/api.js') |
||||
|
|
||||
|
// 构造上传数据
|
||||
|
const uploadData = { |
||||
|
userId: userId, |
||||
|
openid: openid, |
||||
|
...userInfo, |
||||
|
type: type, |
||||
|
timestamp: Date.now() |
||||
|
} |
||||
|
|
||||
|
// 调用API上传用户信息
|
||||
|
API.uploadUserInfo(uploadData).then(res => { |
||||
|
console.log('用户类型更新成功:', res) |
||||
|
}).catch(err => { |
||||
|
console.error('用户类型更新失败:', err) |
||||
|
wx.showToast({ |
||||
|
title: '身份更新失败,请重试', |
||||
|
icon: 'none', |
||||
|
duration: 2000 |
||||
|
}) |
||||
|
}) |
||||
|
}, |
||||
|
// 处理手机号授权结果
|
||||
|
onPhoneNumberResult(e) { |
||||
|
console.log('手机号授权结果:', e) |
||||
|
|
||||
|
// 首先检查用户是否拒绝授权
|
||||
|
if (e.detail.errMsg !== 'getPhoneNumber:ok') { |
||||
|
console.log('用户拒绝授权手机号') |
||||
|
wx.showToast({ |
||||
|
title: '您已拒绝授权,操作已取消', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
// 直接返回,取消所有后续操作
|
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// 用户同意授权,继续执行后续操作
|
||||
|
// 检查是否有openid,如果没有则先登录
|
||||
|
const openid = wx.getStorageSync('openid') |
||||
|
if (!openid) { |
||||
|
console.log('未登录,执行登录流程') |
||||
|
// 显示登录loading提示
|
||||
|
wx.showLoading({ title: '登录中...' }) |
||||
|
// 调用微信登录接口
|
||||
|
wx.login({ |
||||
|
success: loginRes => { |
||||
|
if (loginRes.code) { |
||||
|
// 引入API服务
|
||||
|
const API = require('../../utils/api.js') |
||||
|
// 获取openid
|
||||
|
API.getOpenid(loginRes.code) |
||||
|
.then(openidRes => { |
||||
|
wx.hideLoading() |
||||
|
console.log('获取openid响应:', openidRes) |
||||
|
|
||||
|
// 增强版响应处理逻辑,支持多种返回格式
|
||||
|
let openid = null; |
||||
|
let userId = null; |
||||
|
let sessionKey = null; |
||||
|
|
||||
|
// 优先从data字段获取数据
|
||||
|
if (openidRes && openidRes.data && typeof openidRes.data === 'object') { |
||||
|
openid = openidRes.data.openid || openidRes.data.OpenID || null; |
||||
|
userId = openidRes.data.userId || openidRes.data.userid || null; |
||||
|
sessionKey = openidRes.data.session_key || openidRes.data.sessionKey || null; |
||||
|
} |
||||
|
|
||||
|
// 如果data为空或不存在,尝试从响应对象直接获取
|
||||
|
if (!openid && openidRes && typeof openidRes === 'object') { |
||||
|
console.warn('服务器返回格式可能不符合预期,data字段为空或不存在,但尝试从根对象提取信息:', openidRes); |
||||
|
openid = openidRes.openid || openidRes.OpenID || null; |
||||
|
userId = openidRes.userId || openidRes.userid || null; |
||||
|
sessionKey = openidRes.session_key || openidRes.sessionKey || null; |
||||
|
} |
||||
|
|
||||
|
// 检查服务器状态信息
|
||||
|
const isSuccess = openidRes && (openidRes.success === true || openidRes.code === 200); |
||||
|
|
||||
|
if (openid) { |
||||
|
// 存储openid和session_key
|
||||
|
wx.setStorageSync('openid', openid) |
||||
|
if (sessionKey) { |
||||
|
wx.setStorageSync('sessionKey', sessionKey) |
||||
|
} |
||||
|
|
||||
|
// 如果有userId,也存储起来
|
||||
|
if (userId) { |
||||
|
wx.setStorageSync('userId', userId) |
||||
|
} |
||||
|
|
||||
|
console.log('获取openid成功并存储:', openid) |
||||
|
|
||||
|
// 登录成功,显示提示并重新加载页面
|
||||
|
wx.showToast({ |
||||
|
title: '登录成功', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
|
||||
|
// 在登录成功后重新加载页面
|
||||
|
wx.reLaunch({ |
||||
|
url: '/pages/profile/index' |
||||
|
}) |
||||
|
} else { |
||||
|
console.error('获取openid失败,响应数据:', openidRes) |
||||
|
wx.showToast({ |
||||
|
title: '登录失败,请重试', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
.catch(err => { |
||||
|
wx.hideLoading() |
||||
|
console.error('获取openid失败:', err) |
||||
|
wx.showToast({ |
||||
|
title: '登录失败,请重试', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
}) |
||||
|
} else { |
||||
|
wx.hideLoading() |
||||
|
console.error('微信登录失败:', loginRes) |
||||
|
wx.showToast({ |
||||
|
title: '登录失败,请重试', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
} |
||||
|
}, |
||||
|
fail: err => { |
||||
|
wx.hideLoading() |
||||
|
console.error('wx.login失败:', err) |
||||
|
wx.showToast({ |
||||
|
title: '获取登录状态失败,操作已取消', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// 已登录且用户同意授权,获取加密数据
|
||||
|
const phoneData = e.detail |
||||
|
|
||||
|
wx.showLoading({ title: '获取手机号中...' }) |
||||
|
|
||||
|
// 引入API服务
|
||||
|
const API = require('../../utils/api.js') |
||||
|
|
||||
|
// 上传到服务器解密
|
||||
|
API.uploadPhoneNumberData(phoneData) |
||||
|
.then(res => { |
||||
|
wx.hideLoading() |
||||
|
if (res.success) { |
||||
|
console.log('获取手机号结果:', res) |
||||
|
|
||||
|
// 检查是否有手机号冲突
|
||||
|
const hasPhoneConflict = res.phoneNumberConflict || false |
||||
|
const isNewPhone = res.isNewPhone || true |
||||
|
const phoneNumber = res.phoneNumber || null |
||||
|
|
||||
|
// 如果有手机号冲突且没有返回手机号,使用临时手机号
|
||||
|
const finalPhoneNumber = hasPhoneConflict && !phoneNumber ? '13800138000' : phoneNumber |
||||
|
|
||||
|
if (finalPhoneNumber) { |
||||
|
// 保存手机号到用户信息
|
||||
|
const app = getApp() |
||||
|
const userInfo = app.globalData.userInfo || wx.getStorageSync('userInfo') || {} |
||||
|
userInfo.phoneNumber = finalPhoneNumber |
||||
|
|
||||
|
// 更新本地和全局用户信息
|
||||
|
app.globalData.userInfo = userInfo |
||||
|
wx.setStorageSync('userInfo', userInfo) |
||||
|
|
||||
|
// 获取userId
|
||||
|
const userId = wx.getStorageSync('userId') |
||||
|
const users = wx.getStorageSync('users') || {} |
||||
|
const currentUserType = users[userId] && users[userId].type ? users[userId].type : '' |
||||
|
|
||||
|
// 同时更新服务器用户信息
|
||||
|
this.uploadUserInfoToServer(userInfo, userId, currentUserType) |
||||
|
|
||||
|
// 更新页面状态
|
||||
|
this.setData({ |
||||
|
needPhoneAuth: finalPhoneNumber === '13800138000' |
||||
|
}) |
||||
|
|
||||
|
// 重新加载用户信息以更新UI
|
||||
|
this.loadUserInfo() |
||||
|
} |
||||
|
|
||||
|
// 根据服务器返回的结果显示不同的提示
|
||||
|
if (hasPhoneConflict) { |
||||
|
wx.showToast({ |
||||
|
title: '获取成功,但手机号已被其他账号绑定', |
||||
|
icon: 'none', |
||||
|
duration: 3000 |
||||
|
}) |
||||
|
} else { |
||||
|
wx.showToast({ |
||||
|
title: '手机号绑定成功', |
||||
|
icon: 'success' |
||||
|
}) |
||||
|
} |
||||
|
} else { |
||||
|
console.error('获取手机号失败:', res) |
||||
|
wx.showToast({ |
||||
|
title: '获取手机号失败', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
.catch(err => { |
||||
|
wx.hideLoading() |
||||
|
console.error('获取手机号失败:', err) |
||||
|
wx.showToast({ |
||||
|
title: '获取手机号失败', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 上传用户信息到服务器
|
||||
|
uploadUserInfoToServer(userInfo, userId, type) { |
||||
|
// 返回Promise以便调用者可以进行错误处理
|
||||
|
return new Promise((resolve, reject) => { |
||||
|
try { |
||||
|
// 引入API服务
|
||||
|
const API = require('../../utils/api.js') |
||||
|
|
||||
|
// 获取openid
|
||||
|
const openid = wx.getStorageSync('openid') |
||||
|
|
||||
|
// 验证必要参数
|
||||
|
if (!userId || !openid) { |
||||
|
const error = new Error('缺少必要的用户信息'); |
||||
|
console.error('用户信息上传失败:', error); |
||||
|
reject(error); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 构造上传数据(包含所有必要字段,包括phoneNumber)
|
||||
|
const uploadData = { |
||||
|
userId: userId, |
||||
|
openid: openid, |
||||
|
nickName: userInfo.nickName, |
||||
|
phoneNumber: userInfo.phoneNumber, // 添加phoneNumber字段,满足服务器要求
|
||||
|
type: type, |
||||
|
timestamp: Date.now() |
||||
|
} |
||||
|
|
||||
|
// 调用API上传用户信息
|
||||
|
API.uploadUserInfo(uploadData).then(res => { |
||||
|
console.log('用户信息上传成功:', res) |
||||
|
resolve(res); |
||||
|
}).catch(err => { |
||||
|
console.error('用户信息上传失败:', err) |
||||
|
reject(err); |
||||
|
}) |
||||
|
} catch (error) { |
||||
|
console.error('上传用户信息时发生异常:', error); |
||||
|
reject(error); |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 修改用户名称
|
||||
|
onEditNickName() { |
||||
|
const currentName = this.data.userInfo.nickName || '未登录'; |
||||
|
|
||||
|
wx.showModal({ |
||||
|
title: '修改用户名称', |
||||
|
editable: true, |
||||
|
placeholderText: '请输入新的名称最多10个字符', |
||||
|
confirmText: '确认', |
||||
|
cancelText: '取消', |
||||
|
success: (res) => { |
||||
|
if (res.confirm) { |
||||
|
// 移除首尾空格并过滤多余空格
|
||||
|
let newName = res.content.trim(); |
||||
|
newName = newName.replace(/\s+/g, ' '); |
||||
|
|
||||
|
// 验证昵称不能为空
|
||||
|
if (!newName) { |
||||
|
wx.showToast({ |
||||
|
title: '名称不能为空', |
||||
|
icon: 'none', |
||||
|
duration: 2000 |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 验证昵称长度
|
||||
|
if (newName.length > 10) { |
||||
|
wx.showToast({ |
||||
|
title: '名称长度不能超过10个字符', |
||||
|
icon: 'none', |
||||
|
duration: 2000 |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 检查名称是否变化
|
||||
|
if (newName === currentName) { |
||||
|
wx.showToast({ |
||||
|
title: '名称未变化', |
||||
|
icon: 'none', |
||||
|
duration: 2000 |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 显示加载提示
|
||||
|
wx.showLoading({ |
||||
|
title: '正在更新...', |
||||
|
mask: true |
||||
|
}); |
||||
|
|
||||
|
// 更新用户信息
|
||||
|
this.updateNickName(newName).finally(() => { |
||||
|
// 无论成功失败,都隐藏加载提示
|
||||
|
wx.hideLoading(); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 更新用户名称
|
||||
|
updateNickName(newName) { |
||||
|
return new Promise((resolve, reject) => { |
||||
|
try { |
||||
|
// 更新本地和全局用户信息
|
||||
|
const app = getApp(); |
||||
|
const updatedUserInfo = { |
||||
|
...this.data.userInfo, |
||||
|
nickName: newName |
||||
|
}; |
||||
|
|
||||
|
// 保存到本地存储和全局状态
|
||||
|
app.globalData.userInfo = updatedUserInfo; |
||||
|
wx.setStorageSync('userInfo', updatedUserInfo); |
||||
|
|
||||
|
// 更新页面显示
|
||||
|
this.setData({ |
||||
|
userInfo: updatedUserInfo |
||||
|
}); |
||||
|
|
||||
|
// 更新服务器信息
|
||||
|
const userId = wx.getStorageSync('userId'); |
||||
|
const currentUserType = this.data.userType; |
||||
|
|
||||
|
// 如果有用户ID,则上传到服务器
|
||||
|
if (userId) { |
||||
|
// 使用Promise链处理上传
|
||||
|
this.uploadUserInfoToServer(updatedUserInfo, userId, currentUserType) |
||||
|
.then(() => { |
||||
|
wx.showToast({ |
||||
|
title: '名称修改成功', |
||||
|
icon: 'success', |
||||
|
duration: 2000 |
||||
|
}); |
||||
|
resolve(); |
||||
|
}) |
||||
|
.catch((err) => { |
||||
|
console.error('服务器同步失败,但本地已更新:', err); |
||||
|
// 即使服务器同步失败,本地也已成功更新
|
||||
|
wx.showToast({ |
||||
|
title: '本地更新成功,服务器同步稍后进行', |
||||
|
icon: 'none', |
||||
|
duration: 3000 |
||||
|
}); |
||||
|
resolve(); // 即使服务器失败,也视为成功,因为本地已经更新
|
||||
|
}); |
||||
|
} else { |
||||
|
// 没有用户ID,只更新本地
|
||||
|
console.warn('没有用户ID,仅更新本地用户名称'); |
||||
|
wx.showToast({ |
||||
|
title: '名称修改成功', |
||||
|
icon: 'success', |
||||
|
duration: 2000 |
||||
|
}); |
||||
|
resolve(); |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.error('更新用户名称失败:', error); |
||||
|
wx.showToast({ |
||||
|
title: '更新失败,请稍后重试', |
||||
|
icon: 'none', |
||||
|
duration: 2000 |
||||
|
}); |
||||
|
reject(error); |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
}) |
||||
@ -0,0 +1,3 @@ |
|||||
|
{ |
||||
|
"usingComponents": {} |
||||
|
} |
||||
@ -0,0 +1,63 @@ |
|||||
|
<view class="container" style="align-items: flex-start; padding: 20rpx; width: 100%; max-width: 100vw; overflow-x: hidden; position: relative; box-sizing: border-box;"> |
||||
|
<view class="card" style="display: flex; align-items: center; justify-content: space-between;"> |
||||
|
<view style="display: flex; align-items: center;"> |
||||
|
<image |
||||
|
src="{{userInfo.avatarUrl || '/images/default-avatar.png'}}" |
||||
|
style="width: 100rpx; height: 100rpx; border-radius: 50%; margin-right: 20rpx;" |
||||
|
></image> |
||||
|
<view> |
||||
|
<view style="font-size: 32rpx; font-weight: bold;">{{userInfo.nickName || '未登录'}}</view> |
||||
|
<view style="font-size: 26rpx; color: #666;">当前身份: {{userType || '未设置'}}</view> |
||||
|
<view style="font-size: 26rpx; color: {{userInfo.phoneNumber === '13800138000' ? '#ff4d4f' : '#666'}};"> |
||||
|
手机号: {{userInfo.phoneNumber === '13800138000' ? '临时手机号,请重新授权' : (userInfo.phoneNumber || '未绑定')}} |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<button |
||||
|
class="edit-btn" |
||||
|
bindtap="onEditNickName" |
||||
|
style="background-color: #1677ff; color: white; padding: 10rpx 20rpx; border-radius: 20rpx; font-size: 26rpx;" |
||||
|
> |
||||
|
修改名称 |
||||
|
</button> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 手机号授权按钮 --> |
||||
|
<view class="card" wx:if="{{!userInfo.phoneNumber || userInfo.phoneNumber === '13800138000'}}"> |
||||
|
<button |
||||
|
open-type="getPhoneNumber" |
||||
|
bindgetphonenumber="onPhoneNumberResult" |
||||
|
type="primary" |
||||
|
style="margin: 20rpx 0;" |
||||
|
> |
||||
|
{{userInfo.phoneNumber === '13800138000' ? '重新授权手机号' : '授权手机号'}} |
||||
|
</button> |
||||
|
</view> |
||||
|
|
||||
|
<view class="card"> |
||||
|
<view class="title">我的标签</view> |
||||
|
<view style="flex-wrap: wrap; display: flex;"> |
||||
|
<view wx:for="{{userTags}}" wx:key="index" style="background-color: #f0f2f5; padding: 10rpx 20rpx; border-radius: 20rpx; margin: 10rpx; font-size: 26rpx;"> |
||||
|
{{item}} |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="card" wx:if="{{false}}"> |
||||
|
<view class="title">身份管理</view> |
||||
|
<button |
||||
|
class="btn" |
||||
|
style="background-color: {{userType === 'buyer' || userType === 'both' ? '#888' : '#07c160'}}; color: white;" |
||||
|
bindtap="setAsBuyer" |
||||
|
> |
||||
|
{{userType === 'buyer' || userType === 'both' ? '已设为买家' : '设为买家'}} |
||||
|
</button> |
||||
|
<button |
||||
|
class="btn" |
||||
|
style="background-color: {{userType === 'seller' || userType === 'both' ? '#888' : '#1677ff'}}; color: white;" |
||||
|
bindtap="setAsSeller" |
||||
|
> |
||||
|
{{userType === 'seller' || userType === 'both' ? '已设为卖家' : '设为卖家'}} |
||||
|
</button> |
||||
|
</view> |
||||
|
</view> |
||||
@ -0,0 +1 @@ |
|||||
|
/* pages/profile/index.wxss */ |
||||
@ -0,0 +1,588 @@ |
|||||
|
// pages/publish/index.js
|
||||
|
// 引入API工具
|
||||
|
const API = require('../../utils/api.js'); |
||||
|
|
||||
|
// 【终极修复】创建全局上传管理器,完全独立于页面生命周期
|
||||
|
if (!global.ImageUploadManager) { |
||||
|
global.ImageUploadManager = { |
||||
|
// 存储所有活动的上传任务
|
||||
|
activeTasks: {}, |
||||
|
// 深度克隆工具函数
|
||||
|
deepClone: function(obj) { |
||||
|
return JSON.parse(JSON.stringify(obj)); |
||||
|
}, |
||||
|
// 核心上传方法
|
||||
|
upload: function(formData, images, successCallback, failCallback) { |
||||
|
console.log('【全局上传管理器】开始上传,图片数量:', images.length); |
||||
|
|
||||
|
// 创建唯一的上传任务ID
|
||||
|
const taskId = `upload_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; |
||||
|
|
||||
|
// 创建深度克隆,完全隔离数据
|
||||
|
const clonedFormData = this.deepClone(formData); |
||||
|
const clonedImages = this.deepClone(images); |
||||
|
|
||||
|
// 将任务保存到全局状态中,防止页面重新编译时丢失
|
||||
|
this.activeTasks[taskId] = { |
||||
|
id: taskId, |
||||
|
formData: clonedFormData, |
||||
|
images: clonedImages, |
||||
|
status: 'started', |
||||
|
startTime: Date.now(), |
||||
|
uploadedCount: 0, |
||||
|
totalCount: clonedImages.length |
||||
|
}; |
||||
|
|
||||
|
console.log(`【全局上传管理器】创建任务 ${taskId},已保存到全局状态`); |
||||
|
|
||||
|
// 使用setTimeout完全隔离执行上下文,避免与页面生命周期耦合
|
||||
|
const self = this; |
||||
|
setTimeout(() => { |
||||
|
try { |
||||
|
console.log('【全局上传管理器】准备调用API.publishProduct'); |
||||
|
console.log('准备的商品数据:', clonedFormData); |
||||
|
console.log('准备的图片数量:', clonedImages.length); |
||||
|
|
||||
|
// 关键修改:使用API.publishProduct方法,这是正确的调用链
|
||||
|
// 包含所有必要字段
|
||||
|
const productData = { |
||||
|
...clonedFormData, |
||||
|
images: clonedImages, // 直接传递图片数组
|
||||
|
imageUrls: clonedImages, // 同时设置imageUrls字段
|
||||
|
// 生成会话ID,确保所有图片关联同一商品
|
||||
|
sessionId: taskId, |
||||
|
uploadSessionId: taskId |
||||
|
}; |
||||
|
|
||||
|
console.log('最终传递给publishProduct的数据:', Object.keys(productData)); |
||||
|
|
||||
|
API.publishProduct(productData) |
||||
|
.then(res => { |
||||
|
console.log(`【全局上传管理器】任务 ${taskId} 上传完成,响应:`, res); |
||||
|
|
||||
|
// 更新任务状态
|
||||
|
if (self.activeTasks[taskId]) { |
||||
|
self.activeTasks[taskId].status = 'completed'; |
||||
|
self.activeTasks[taskId].endTime = Date.now(); |
||||
|
self.activeTasks[taskId].result = res; |
||||
|
} |
||||
|
|
||||
|
// 使用setTimeout隔离成功回调的执行
|
||||
|
setTimeout(() => { |
||||
|
if (successCallback) { |
||||
|
successCallback(res); |
||||
|
} |
||||
|
}, 0); |
||||
|
}) |
||||
|
.catch(err => { |
||||
|
console.error(`【全局上传管理器】任务 ${taskId} 上传失败:`, err); |
||||
|
|
||||
|
// 更新任务状态
|
||||
|
if (self.activeTasks[taskId]) { |
||||
|
self.activeTasks[taskId].status = 'failed'; |
||||
|
self.activeTasks[taskId].error = err; |
||||
|
self.activeTasks[taskId].endTime = Date.now(); |
||||
|
} |
||||
|
|
||||
|
// 使用setTimeout隔离失败回调的执行
|
||||
|
setTimeout(() => { |
||||
|
if (failCallback) { |
||||
|
failCallback(err); |
||||
|
} |
||||
|
}, 0); |
||||
|
}) |
||||
|
.finally(() => { |
||||
|
// 延迟清理任务,确保所有操作完成
|
||||
|
setTimeout(() => { |
||||
|
if (self.activeTasks[taskId]) { |
||||
|
delete self.activeTasks[taskId]; |
||||
|
console.log(`【全局上传管理器】任务 ${taskId} 已清理`); |
||||
|
} |
||||
|
}, 10000); |
||||
|
}); |
||||
|
} catch (e) { |
||||
|
console.error(`【全局上传管理器】任务 ${taskId} 发生异常:`, e); |
||||
|
setTimeout(() => { |
||||
|
if (failCallback) { |
||||
|
failCallback(e); |
||||
|
} |
||||
|
}, 0); |
||||
|
} |
||||
|
}, 0); |
||||
|
|
||||
|
return taskId; |
||||
|
}, |
||||
|
|
||||
|
// 获取任务状态的方法
|
||||
|
getTaskStatus: function(taskId) { |
||||
|
return this.activeTasks[taskId] || null; |
||||
|
}, |
||||
|
|
||||
|
// 获取所有活动任务
|
||||
|
getActiveTasks: function() { |
||||
|
return Object.values(this.activeTasks); |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
Page({ |
||||
|
/** |
||||
|
* 页面的初始数据 |
||||
|
*/ |
||||
|
data: { |
||||
|
variety: '', // 品种
|
||||
|
price: '', |
||||
|
quantity: '', |
||||
|
grossWeight: '', |
||||
|
yolk: '', // 蛋黄
|
||||
|
specification: '', |
||||
|
images: [] // 新增图片数组
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面加载 |
||||
|
*/ |
||||
|
onLoad(options) { |
||||
|
// 检查用户是否已登录
|
||||
|
this.checkLoginStatus(); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 检查用户登录状态 |
||||
|
*/ |
||||
|
checkLoginStatus() { |
||||
|
const openid = wx.getStorageSync('openid'); |
||||
|
if (!openid) { |
||||
|
wx.showModal({ |
||||
|
title: '提示', |
||||
|
content: '请先登录后再发布商品', |
||||
|
showCancel: false, |
||||
|
success: (res) => { |
||||
|
if (res.confirm) { |
||||
|
wx.navigateTo({ url: '/pages/index/index' }); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 品种输入处理 |
||||
|
*/ |
||||
|
onVarietyInput(e) { |
||||
|
this.setData({ |
||||
|
variety: e.detail.value |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 蛋黄输入处理 |
||||
|
*/ |
||||
|
onYolkInput(e) { |
||||
|
this.setData({ |
||||
|
yolk: e.detail.value |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 价格输入处理 |
||||
|
*/ |
||||
|
onPriceInput(e) { |
||||
|
// 保存原始字符串值,不进行数字转换
|
||||
|
this.setData({ |
||||
|
price: e.detail.value |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 数量输入处理 |
||||
|
*/ |
||||
|
onQuantityInput(e) { |
||||
|
const value = parseFloat(e.detail.value); |
||||
|
this.setData({ |
||||
|
quantity: isNaN(value) ? '' : value |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 毛重输入处理 |
||||
|
*/ |
||||
|
onGrossWeightInput(e) { |
||||
|
// 直接保存原始字符串值,不进行数字转换
|
||||
|
this.setData({ |
||||
|
grossWeight: e.detail.value |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 规格输入处理 |
||||
|
*/ |
||||
|
onSpecificationInput(e) { |
||||
|
this.setData({ |
||||
|
specification: e.detail.value |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 表单验证 |
||||
|
*/ |
||||
|
validateForm() { |
||||
|
const { variety, price, quantity } = this.data; |
||||
|
|
||||
|
console.log('表单验证数据 - variety:', variety, 'price:', price, 'quantity:', quantity); |
||||
|
console.log('数据类型 - variety:', typeof variety, 'price:', typeof price, 'quantity:', typeof quantity); |
||||
|
|
||||
|
if (!variety || !variety.trim()) { |
||||
|
wx.showToast({ title: '请输入品种', icon: 'none' }); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (!price || price.trim() === '') { |
||||
|
wx.showToast({ title: '请输入有效价格', icon: 'none' }); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (quantity === '' || quantity === undefined || quantity === null || quantity <= 0) { |
||||
|
wx.showToast({ title: '请输入有效数量', icon: 'none' }); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
console.log('表单验证通过'); |
||||
|
return true; |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 发布商品按钮点击事件 |
||||
|
*/ |
||||
|
onPublishTap() { |
||||
|
console.log('发布按钮点击'); |
||||
|
|
||||
|
// 检查用户登录状态
|
||||
|
const openid = wx.getStorageSync('openid'); |
||||
|
const userInfo = wx.getStorageSync('userInfo'); |
||||
|
const userId = wx.getStorageSync('userId'); |
||||
|
|
||||
|
console.log('检查用户授权状态 - openid:', !!openid, 'userInfo:', !!userInfo, 'userId:', !!userId); |
||||
|
|
||||
|
if (!openid || !userId || !userInfo) { |
||||
|
console.log('用户未登录或未授权,引导重新登录'); |
||||
|
wx.showModal({ |
||||
|
title: '登录过期', |
||||
|
content: '请先授权登录后再发布商品', |
||||
|
showCancel: false, |
||||
|
confirmText: '去登录', |
||||
|
success: (res) => { |
||||
|
if (res.confirm) { |
||||
|
wx.navigateTo({ url: '/pages/index/index' }); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (!this.validateForm()) { |
||||
|
console.log('表单验证失败'); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const { variety, price, quantity, grossWeight, yolk, specification } = this.data; |
||||
|
const images = this.data.images; |
||||
|
|
||||
|
// 构建商品数据,确保价格和数量为字符串类型
|
||||
|
const productData = { |
||||
|
productName: variety.trim(), // 使用品种作为商品名称
|
||||
|
price: price.toString(), |
||||
|
quantity: quantity.toString(), |
||||
|
grossWeight: grossWeight !== '' && grossWeight !== null && grossWeight !== undefined ? grossWeight : "", |
||||
|
yolk: yolk || '', |
||||
|
specification: specification || '', |
||||
|
images: images, |
||||
|
imageUrls: images, |
||||
|
allImageUrls: images, |
||||
|
hasMultipleImages: images.length > 1, |
||||
|
totalImages: images.length |
||||
|
}; |
||||
|
|
||||
|
console.log('【关键日志】商品数据:', productData); |
||||
|
console.log('【关键日志】图片数量:', images.length); |
||||
|
|
||||
|
// 【终极修复】在上传开始前立即清空表单
|
||||
|
// 先深度克隆所有数据
|
||||
|
console.log('【上传前检查】准备克隆数据'); |
||||
|
const formDataCopy = JSON.parse(JSON.stringify(productData)); |
||||
|
const imagesCopy = JSON.parse(JSON.stringify(images)); |
||||
|
|
||||
|
console.log('【上传前检查】克隆后图片数量:', imagesCopy.length); |
||||
|
console.log('【上传前检查】克隆后图片数据:', imagesCopy); |
||||
|
|
||||
|
// 立即清空表单,避免任何状态变化触发重新编译
|
||||
|
console.log('【上传前检查】清空表单'); |
||||
|
this.setData({ |
||||
|
variety: '', |
||||
|
price: '', |
||||
|
quantity: '', |
||||
|
grossWeight: '', |
||||
|
yolk: '', |
||||
|
specification: '', |
||||
|
images: [] |
||||
|
}); |
||||
|
|
||||
|
// 显示加载提示
|
||||
|
wx.showLoading({ title: '正在上传图片...' }); |
||||
|
|
||||
|
// 【终极修复】使用全局上传管理器处理上传,完全脱离页面生命周期
|
||||
|
// 将所有数据存储到全局对象中,防止被回收
|
||||
|
console.log('【上传前检查】存储数据到全局对象'); |
||||
|
global.tempUploadData = { |
||||
|
formData: formDataCopy, |
||||
|
images: imagesCopy, |
||||
|
userId: userId, |
||||
|
timestamp: Date.now() |
||||
|
}; |
||||
|
|
||||
|
// 预先生成会话ID,确保所有图片关联同一个商品
|
||||
|
const uploadSessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; |
||||
|
formDataCopy.sessionId = uploadSessionId; |
||||
|
formDataCopy.uploadSessionId = uploadSessionId; |
||||
|
|
||||
|
console.log(`【关键修复】预先生成会话ID:`, uploadSessionId); |
||||
|
console.log(`【上传前检查】准备调用全局上传管理器,图片数量:`, imagesCopy.length); |
||||
|
console.log(`【上传前检查】传递的formData结构:`, Object.keys(formDataCopy)); |
||||
|
|
||||
|
// 【核心修复】直接使用wx.uploadFile API,确保与服务器端测试脚本格式一致
|
||||
|
console.log(`【核心修复】使用wx.uploadFile API直接上传`); |
||||
|
|
||||
|
// 预先生成会话ID
|
||||
|
const sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; |
||||
|
formDataCopy.sessionId = sessionId; |
||||
|
formDataCopy.uploadSessionId = sessionId; |
||||
|
|
||||
|
console.log(`【核心修复】使用会话ID:`, sessionId); |
||||
|
console.log(`【核心修复】上传图片数量:`, imagesCopy.length); |
||||
|
|
||||
|
// 使用Promise处理上传
|
||||
|
const uploadPromise = new Promise((resolve, reject) => { |
||||
|
// 构建formData,与服务器测试脚本一致
|
||||
|
const formData = { |
||||
|
productData: JSON.stringify(formDataCopy), |
||||
|
sessionId: sessionId, |
||||
|
uploadSessionId: sessionId, |
||||
|
totalImages: imagesCopy.length.toString(), |
||||
|
isSingleUpload: 'false' // 关键参数:标记为多图片上传
|
||||
|
}; |
||||
|
|
||||
|
console.log(`【核心修复】准备上传,formData结构:`, Object.keys(formData)); |
||||
|
console.log(`【核心修复】上传URL:`, API.BASE_URL + '/api/products/upload'); |
||||
|
|
||||
|
// 直接使用wx.uploadFile上传第一张图片
|
||||
|
wx.uploadFile({ |
||||
|
url: API.BASE_URL + '/api/products/upload', |
||||
|
filePath: imagesCopy[0], // 先上传第一张
|
||||
|
name: 'images', |
||||
|
formData: formData, |
||||
|
timeout: 180000, |
||||
|
success: (res) => { |
||||
|
console.log('【核心修复】上传成功,状态码:', res.statusCode); |
||||
|
console.log('【核心修复】原始响应:', res.data); |
||||
|
try { |
||||
|
const data = JSON.parse(res.data); |
||||
|
resolve(data); |
||||
|
} catch (e) { |
||||
|
resolve({data: res.data}); |
||||
|
} |
||||
|
}, |
||||
|
fail: (err) => { |
||||
|
console.error('【核心修复】上传失败:', err); |
||||
|
reject(err); |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
uploadPromise.then((res) => { |
||||
|
// 上传成功回调
|
||||
|
console.log('【核心修复】上传成功,响应:', res); |
||||
|
|
||||
|
// 使用setTimeout完全隔离回调执行上下文
|
||||
|
setTimeout(() => { |
||||
|
wx.hideLoading(); |
||||
|
|
||||
|
// 从全局临时存储获取数据
|
||||
|
const tempData = global.tempUploadData || {}; |
||||
|
const localFormData = tempData.formData; |
||||
|
const userId = tempData.userId; |
||||
|
|
||||
|
// 【关键修复】从多个来源提取图片URL,确保不丢失
|
||||
|
let allUploadedImageUrls = []; |
||||
|
|
||||
|
// 尝试从多个位置提取图片URLs
|
||||
|
if (res.imageUrls && Array.isArray(res.imageUrls) && res.imageUrls.length > 0) { |
||||
|
allUploadedImageUrls = [...res.imageUrls]; |
||||
|
console.log('【全局上传】从res.imageUrls提取到图片:', allUploadedImageUrls.length); |
||||
|
} |
||||
|
|
||||
|
if (res.product && res.product.imageUrls && Array.isArray(res.product.imageUrls) && res.product.imageUrls.length > 0) { |
||||
|
allUploadedImageUrls = [...res.product.imageUrls]; |
||||
|
console.log('【全局上传】从res.product.imageUrls提取到图片:', allUploadedImageUrls.length); |
||||
|
} |
||||
|
|
||||
|
if (res.data && res.data.imageUrls && Array.isArray(res.data.imageUrls) && res.data.imageUrls.length > 0) { |
||||
|
allUploadedImageUrls = [...res.data.imageUrls]; |
||||
|
console.log('【全局上传】从res.data.imageUrls提取到图片:', allUploadedImageUrls.length); |
||||
|
} |
||||
|
|
||||
|
// 去重处理,确保URL不重复
|
||||
|
allUploadedImageUrls = [...new Set(allUploadedImageUrls)]; |
||||
|
|
||||
|
console.log('【全局上传】最终去重后的图片URL列表:', allUploadedImageUrls); |
||||
|
console.log('【全局上传】最终图片数量:', allUploadedImageUrls.length); |
||||
|
|
||||
|
// 获取卖家信息
|
||||
|
const users = wx.getStorageSync('users') || {}; |
||||
|
const sellerName = users[userId] && users[userId].info && users[userId].info.nickName ? users[userId].info.nickName : '未知卖家'; |
||||
|
|
||||
|
// 保存到本地存储
|
||||
|
setTimeout(() => { |
||||
|
// 获取当前已有的货源列表
|
||||
|
const supplies = wx.getStorageSync('supplies') || []; |
||||
|
const newId = supplies.length > 0 ? Math.max(...supplies.map(s => s.id)) + 1 : 1; |
||||
|
const serverProductId = res.product && res.product.productId ? res.product.productId : ''; |
||||
|
|
||||
|
// 创建新的货源记录
|
||||
|
const newSupply = { |
||||
|
id: newId, |
||||
|
productId: serverProductId, |
||||
|
serverProductId: serverProductId, |
||||
|
name: localFormData.productName, |
||||
|
productName: localFormData.productName, |
||||
|
price: localFormData.price, |
||||
|
minOrder: localFormData.quantity, |
||||
|
yolk: localFormData.yolk, |
||||
|
spec: localFormData.specification, |
||||
|
grossWeight: localFormData.grossWeight !== null ? localFormData.grossWeight : '', |
||||
|
seller: sellerName, |
||||
|
status: res.product && res.product.status ? res.product.status : 'pending_review', |
||||
|
imageUrls: allUploadedImageUrls, |
||||
|
reservedCount: 0, |
||||
|
isReserved: false |
||||
|
}; |
||||
|
|
||||
|
// 保存到supplies和goods本地存储
|
||||
|
supplies.push(newSupply); |
||||
|
wx.setStorageSync('supplies', supplies); |
||||
|
|
||||
|
const goods = wx.getStorageSync('goods') || []; |
||||
|
const newGoodForBuyer = { |
||||
|
id: String(newId), |
||||
|
productId: String(serverProductId), |
||||
|
name: localFormData.productName, |
||||
|
productName: localFormData.productName, |
||||
|
price: localFormData.price, |
||||
|
minOrder: localFormData.quantity, |
||||
|
yolk: localFormData.yolk, |
||||
|
spec: localFormData.specification, |
||||
|
grossWeight: localFormData.grossWeight !== null ? localFormData.grossWeight : '', |
||||
|
displayGrossWeight: localFormData.grossWeight !== null ? localFormData.grossWeight : '', |
||||
|
seller: sellerName, |
||||
|
status: res.product && res.product.status ? res.product.status : 'pending_review', |
||||
|
imageUrls: allUploadedImageUrls, |
||||
|
reservedCount: 0, |
||||
|
isReserved: false |
||||
|
}; |
||||
|
goods.push(newGoodForBuyer); |
||||
|
wx.setStorageSync('goods', goods); |
||||
|
|
||||
|
// 显示成功提示
|
||||
|
setTimeout(() => { |
||||
|
wx.showModal({ |
||||
|
title: '发布成功', |
||||
|
content: `所有${allUploadedImageUrls.length}张图片已成功上传!\n请手动返回查看您的商品。\n\n重要:请勿关闭小程序,等待3-5秒确保所有数据处理完成。`, |
||||
|
showCancel: false, |
||||
|
confirmText: '我知道了', |
||||
|
success: function() { |
||||
|
// 延迟清理全局临时数据,确保所有操作完成
|
||||
|
setTimeout(() => { |
||||
|
if (global.tempUploadData) { |
||||
|
delete global.tempUploadData; |
||||
|
} |
||||
|
}, 5000); |
||||
|
} |
||||
|
}); |
||||
|
}, 500); |
||||
|
}, 500); |
||||
|
}, 100); |
||||
|
}) |
||||
|
.catch((err) => { |
||||
|
// 上传失败回调
|
||||
|
console.error('【核心修复】上传失败:', err); |
||||
|
|
||||
|
// 使用setTimeout隔离错误处理
|
||||
|
setTimeout(() => { |
||||
|
wx.hideLoading(); |
||||
|
|
||||
|
if (err.needRelogin) { |
||||
|
wx.showModal({ |
||||
|
title: '登录状态失效', |
||||
|
content: '请重新授权登录', |
||||
|
showCancel: false, |
||||
|
success: (res) => { |
||||
|
if (res.confirm) { |
||||
|
wx.removeStorageSync('openid'); |
||||
|
wx.removeStorageSync('userId'); |
||||
|
wx.navigateTo({ url: '/pages/login/index' }); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} else { |
||||
|
wx.showToast({ title: err.message || '发布失败,请重试', icon: 'none' }); |
||||
|
} |
||||
|
|
||||
|
// 清理全局临时数据
|
||||
|
if (global.tempUploadData) { |
||||
|
delete global.tempUploadData; |
||||
|
} |
||||
|
}, 100); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面显示 |
||||
|
*/ |
||||
|
onShow() { |
||||
|
// 页面显示时可以刷新数据
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 选择图片 - 修复版本 |
||||
|
*/ |
||||
|
chooseImage: function () { |
||||
|
const that = this; |
||||
|
wx.chooseMedia({ |
||||
|
count: 5 - that.data.images.length, |
||||
|
mediaType: ['image'], |
||||
|
sourceType: ['album', 'camera'], |
||||
|
success: function (res) { |
||||
|
console.log('选择图片成功,返回数据:', res); |
||||
|
const tempFiles = res.tempFiles.map(file => file.tempFilePath); |
||||
|
that.setData({ |
||||
|
images: [...that.data.images, ...tempFiles] |
||||
|
}); |
||||
|
console.log('更新后的图片数组:', that.data.images); |
||||
|
}, |
||||
|
fail: function (err) { |
||||
|
console.error('选择图片失败:', err); |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 删除图片 |
||||
|
*/ |
||||
|
deleteImage: function (e) { |
||||
|
const index = e.currentTarget.dataset.index; |
||||
|
const images = this.data.images; |
||||
|
images.splice(index, 1); |
||||
|
this.setData({ |
||||
|
images: images |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
@ -0,0 +1,3 @@ |
|||||
|
{ |
||||
|
"usingComponents": {} |
||||
|
} |
||||
@ -0,0 +1,54 @@ |
|||||
|
<!--pages/publish/index.wxml--> |
||||
|
<view class="publish-container"> |
||||
|
<view class="publish-header"> |
||||
|
<text class="header-title">发布新货源</text> |
||||
|
</view> |
||||
|
|
||||
|
<view class="form-container"> |
||||
|
<view class="form-item"> |
||||
|
<text class="label">品种 *</text> |
||||
|
<input class="input" type="text" placeholder="请输入品种" bindinput="onVarietyInput" value="{{variety}}" /> |
||||
|
</view> |
||||
|
|
||||
|
<view class="form-item"> |
||||
|
<text class="label">价格 (元/斤) *</text> |
||||
|
<input class="input" type="text" placeholder="请输入商品价格(支持文字描述)" bindinput="onPriceInput" value="{{price}}" /> |
||||
|
</view> |
||||
|
|
||||
|
<view class="form-item"> |
||||
|
<text class="label">数量 (斤) *</text> |
||||
|
<input class="input" type="digit" placeholder="请输入商品数量" bindinput="onQuantityInput" value="{{quantity}}" /> |
||||
|
</view> |
||||
|
|
||||
|
<view class="form-item"> |
||||
|
<text class="label">毛重 (斤)</text> |
||||
|
<input class="input" type="text" placeholder="请输入商品毛重(可输入文字,如:十斤)" bindinput="onGrossWeightInput" value="{{grossWeight}}" /> |
||||
|
</view> |
||||
|
|
||||
|
<view class="form-item"> |
||||
|
<text class="label">蛋黄</text> |
||||
|
<input class="input" type="text" placeholder="请输入蛋黄信息" bindinput="onYolkInput" value="{{yolk}}" /> |
||||
|
</view> |
||||
|
|
||||
|
<view class="form-item"> |
||||
|
<text class="label">规格</text> |
||||
|
<input class="input" type="text" placeholder="请输入商品规格" bindinput="onSpecificationInput" value="{{specification}}" /> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 新增图片上传区域 --> |
||||
|
<view class="image-upload-container"> |
||||
|
<text class="label">商品图片(最多5张)</text> |
||||
|
<view class="image-list"> |
||||
|
<view class="image-item" wx:for="{{images}}" wx:key="index"> |
||||
|
<image src="{{item}}" mode="aspectFill"></image> |
||||
|
<view class="image-delete" bindtap="deleteImage" data-index="{{index}}">×</view> |
||||
|
</view> |
||||
|
<view class="image-upload" wx:if="{{images.length < 5}}" bindtap="chooseImage"> |
||||
|
<text>+</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<button class="publish-btn" type="primary" bindtap="onPublishTap">发布商品</button> |
||||
|
</view> |
||||
|
</view> |
||||
@ -0,0 +1,117 @@ |
|||||
|
/* pages/publish/index.wxss */ |
||||
|
.publish-container { |
||||
|
padding: 20rpx; |
||||
|
background-color: #f8f8f8; |
||||
|
min-height: 100vh; |
||||
|
} |
||||
|
|
||||
|
.publish-header { |
||||
|
background-color: #fff; |
||||
|
padding: 30rpx; |
||||
|
border-radius: 10rpx; |
||||
|
margin-bottom: 20rpx; |
||||
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1); |
||||
|
} |
||||
|
|
||||
|
.header-title { |
||||
|
font-size: 36rpx; |
||||
|
font-weight: bold; |
||||
|
color: #333; |
||||
|
} |
||||
|
|
||||
|
.form-container { |
||||
|
background-color: #fff; |
||||
|
padding: 30rpx; |
||||
|
border-radius: 10rpx; |
||||
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1); |
||||
|
} |
||||
|
|
||||
|
.form-item { |
||||
|
margin-bottom: 30rpx; |
||||
|
} |
||||
|
|
||||
|
.label { |
||||
|
display: block; |
||||
|
font-size: 28rpx; |
||||
|
color: #666; |
||||
|
margin-bottom: 10rpx; |
||||
|
} |
||||
|
|
||||
|
.input { |
||||
|
width: 100%; |
||||
|
height: 80rpx; |
||||
|
border: 1rpx solid #ddd; |
||||
|
border-radius: 8rpx; |
||||
|
padding: 0 20rpx; |
||||
|
font-size: 28rpx; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
/* 图片上传样式 */ |
||||
|
.image-upload-container { |
||||
|
margin-bottom: 30rpx; |
||||
|
} |
||||
|
|
||||
|
.image-list { |
||||
|
display: flex; |
||||
|
flex-wrap: wrap; |
||||
|
gap: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.image-item { |
||||
|
width: 160rpx; |
||||
|
height: 160rpx; |
||||
|
position: relative; |
||||
|
border: 1rpx solid #ddd; |
||||
|
border-radius: 8rpx; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.image-item image { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
.image-delete { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
right: 0; |
||||
|
width: 40rpx; |
||||
|
height: 40rpx; |
||||
|
background-color: rgba(0, 0, 0, 0.5); |
||||
|
color: #fff; |
||||
|
text-align: center; |
||||
|
line-height: 40rpx; |
||||
|
font-size: 32rpx; |
||||
|
border-radius: 0 8rpx 0 20rpx; |
||||
|
} |
||||
|
|
||||
|
.image-upload { |
||||
|
width: 160rpx; |
||||
|
height: 160rpx; |
||||
|
border: 2rpx dashed #ddd; |
||||
|
border-radius: 8rpx; |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
background-color: #f8f8f8; |
||||
|
} |
||||
|
|
||||
|
.image-upload text { |
||||
|
font-size: 64rpx; |
||||
|
color: #999; |
||||
|
} |
||||
|
|
||||
|
.publish-btn { |
||||
|
margin-top: 40rpx; |
||||
|
background-color: #07c160; |
||||
|
color: #fff; |
||||
|
font-size: 32rpx; |
||||
|
height: 90rpx; |
||||
|
line-height: 90rpx; |
||||
|
border-radius: 45rpx; |
||||
|
} |
||||
|
|
||||
|
.publish-btn:active { |
||||
|
background-color: #06b356; |
||||
|
} |
||||
File diff suppressed because it is too large
@ -0,0 +1,5 @@ |
|||||
|
{ |
||||
|
"usingComponents": {}, |
||||
|
"enablePullDownRefresh": true, |
||||
|
"backgroundTextStyle": "dark" |
||||
|
} |
||||
@ -0,0 +1,833 @@ |
|||||
|
<view class="container {{pageScrollLock ? 'page-scroll-lock' : ''}}" |
||||
|
style="align-items: flex-start; padding: 20rpx; width: 100%; max-width: 100vw; overflow-x: hidden; position: relative; box-sizing: border-box;" |
||||
|
catchtouchmove="{{touchMoveBlocked ? 'preventTouchMove' : ''}}"> |
||||
|
<view style="display: flex; justify-content: space-between; align-items: center; width: 90%; margin-bottom: 20rpx;"> |
||||
|
<view class="title">我的鸡蛋货源</view> |
||||
|
<button |
||||
|
bindtap="contactCustomerService" |
||||
|
class="customer-service-btn" |
||||
|
> |
||||
|
联系客服 |
||||
|
</button> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 搜索框 --> |
||||
|
<view style="width: 100%; display: flex; justify-content: center; margin-bottom: 20rpx;"> |
||||
|
<view style="width: 90%; display: flex; border: 1rpx solid #ddd; border-radius: 40rpx; overflow: hidden;"> |
||||
|
<input |
||||
|
style="flex: 1; padding: 20rpx 30rpx;" |
||||
|
placeholder="搜索货源名称或品种" |
||||
|
bindinput="onSearchInput" |
||||
|
value="{{searchKeyword}}" |
||||
|
/> |
||||
|
<button |
||||
|
style="background-color: #52c41a; color: white; font-size: 26rpx; height: 80rpx; line-height: 80rpx; padding: 0 30rpx;" |
||||
|
bindtap="searchSupplies" |
||||
|
> |
||||
|
搜索 |
||||
|
</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<button |
||||
|
class="glass-btn primary-glass-btn" |
||||
|
bindtap="showAddSupply" |
||||
|
style="width: 90%;" |
||||
|
> |
||||
|
创建新货源 |
||||
|
</button> |
||||
|
|
||||
|
<!-- 已上架货源 --> |
||||
|
<view style="margin-top: 30rpx; width: 100%;"> |
||||
|
<view style="font-size: 28rpx; font-weight: bold; color: #52c41a; margin-bottom: 15rpx; display: flex; justify-content: space-between; align-items: center;"> |
||||
|
<text>已上架货源 ({{publishedSupplies.length}})</text> |
||||
|
<view bindtap="togglePublishedExpand" style="width: 40rpx; height: 40rpx; display: flex; align-items: center; justify-content: center;"> |
||||
|
<text wx:if="{{isPublishedExpanded}}" style="color: #52c41a; font-size: 28rpx;">▼</text> |
||||
|
<text wx:else style="color: #52c41a; font-size: 28rpx;">▲</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<block wx:if="{{isPublishedExpanded}}"> |
||||
|
<block wx:if="{{publishedSupplies.length > 0}}"> |
||||
|
<view wx:for="{{publishedSupplies}}" wx:key="id" class="card" style="width: 100%;"> |
||||
|
<!-- 图片和信息1:1比例并排显示 --> |
||||
|
<view style="display: flex; width: 100%; border-radius: 8rpx; overflow: hidden; background-color: #f5f5f5;"> |
||||
|
<!-- 左侧图片区域 50%宽度 --> |
||||
|
<view style="width: 50%; position: relative;"> |
||||
|
<!-- 第一张图片 --> |
||||
|
<view wx:if="{{item.imageUrls && item.imageUrls.length > 0}}" style="width: 100%; height: 100%;"> |
||||
|
<image src="{{item.imageUrls[0]}}" mode="aspectFill" style="width: 100%; height: 100%;" bindtap="previewImage" data-urls="{{item.imageUrls}}" data-index="0" binderror="imageError" bindload="imageLoad" |
||||
|
loading="lazy" |
||||
|
fallback-src="../../images/logo.svg"> |
||||
|
</image> |
||||
|
</view> |
||||
|
<view wx:else style="width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; color: #999;"> |
||||
|
<text>暂无图片</text> |
||||
|
</view> |
||||
|
<!-- 剩余图片可滑动区域 --> |
||||
|
<view wx:if="{{item.imageUrls && item.imageUrls.length > 1}}" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"> |
||||
|
<swiper |
||||
|
class="image-swiper" |
||||
|
style="width: 100%; height: 100%;" |
||||
|
current="{{item.currentImageIndex || 0}}" |
||||
|
bindchange="swiperChange" |
||||
|
data-id="{{item.id}}"> |
||||
|
<block wx:for="{{item.imageUrls}}" wx:for-item="img" wx:for-index="idx" wx:key="idx"> |
||||
|
<swiper-item> |
||||
|
<image src="{{img}}" mode="aspectFill" style="width: 100%; height: 100%;" bindtap="previewImage" data-urls="{{item.imageUrls}}" data-index="{{idx}}"></image> |
||||
|
</swiper-item> |
||||
|
</block> |
||||
|
</swiper> |
||||
|
<!-- 显示页码指示器 --> |
||||
|
<view style="position: absolute; bottom: 10rpx; right: 10rpx; background-color: rgba(0,0,0,0.5); color: white; padding: 5rpx 10rpx; border-radius: 15rpx; font-size: 20rpx;"> |
||||
|
{{(item.currentImageIndex || 0) + 1}}/{{item.imageUrls.length}} |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 右侧信息区域 50%宽度 --> |
||||
|
<view style="width: 50%; padding: 15rpx; display: flex; flex-direction: column; justify-content: space-between; background-color: white; border-left: 1rpx solid #f0f0f0;"> |
||||
|
<view> |
||||
|
<view style="font-size: 28rpx; font-weight: bold; word-break: break-word;">{{item.name}} |
||||
|
<view style="display: inline-block; margin-left: 10rpx; font-size: 18rpx; color: #fff; background-color: #52c41a; padding: 2rpx 8rpx; border-radius: 10rpx;">已上架</view> |
||||
|
</view> |
||||
|
<view style="font-size: 24rpx; color: #666; margin-top: 8rpx;">蛋黄: {{item.yolk || '无'}}</view> |
||||
|
<view style="font-size: 24rpx; color: #666; margin-top: 8rpx;">规格: {{item.spec || '无'}}</view> |
||||
|
<view style="color: #f5222d; font-size: 24rpx; margin-top: 8rpx;">件数: {{item.minOrder}}件</view> |
||||
|
<view style="color: #1677ff; font-size: 24rpx; margin-top: 8rpx;">斤重: {{item.grossWeight || ''}}斤</view> |
||||
|
<view style="color: #722ed1; font-size: 24rpx; margin-top: 8rpx;">地区: {{item.region || '未设置'}}</view> |
||||
|
<view style="font-size: 22rpx; color: #999; margin-top: 8rpx;">创建时间: {{item.formattedCreatedAt}}</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 按钮区域 --> |
||||
|
<view style="display: flex; justify-content: center; align-items: center; margin-top: 10rpx;"> |
||||
|
<button |
||||
|
style="background-color: #f5222d; color: white; font-size: 22rpx; padding: 0 20rpx; line-height: 60rpx;" |
||||
|
bindtap="unpublishSupply" |
||||
|
data-id="{{item.id}}" |
||||
|
> |
||||
|
下架 |
||||
|
</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 已上架货源加载更多 --> |
||||
|
<view class="load-more" wx:if="{{pagination.published.hasMore}}"> |
||||
|
<view class="loading-text" wx:if="{{pagination.published.loading}}"> |
||||
|
加载中... |
||||
|
</view> |
||||
|
<view class="load-more-text" wx:else bindtap="onReachPublishedBottom"> |
||||
|
点击加载更多已上架货源 |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="no-more" wx:if="{{!pagination.published.hasMore && publishedSupplies.length > 0}}"> |
||||
|
没有更多已上架货源了 |
||||
|
</view> |
||||
|
</block> |
||||
|
|
||||
|
<view wx:else style="text-align: center; color: #999; font-size: 24rpx; padding: 30rpx 0;"> |
||||
|
暂无已上架的货源 |
||||
|
</view> |
||||
|
</block> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 审核中的货源 --> |
||||
|
<view style="margin-top: 30rpx; width: 100%;"> |
||||
|
<view style="font-size: 28rpx; font-weight: bold; color: #1677ff; margin-bottom: 15rpx; display: flex; justify-content: space-between; align-items: center;"> |
||||
|
<text>审核中的货源 ({{pendingSupplies.length}})</text> |
||||
|
<view bindtap="togglePendingExpand" style="width: 40rpx; height: 40rpx; display: flex; align-items: center; justify-content: center;"> |
||||
|
<text wx:if="{{isPendingExpanded}}" style="color: #1677ff; font-size: 28rpx;">▼</text> |
||||
|
<text wx:else style="color: #1677ff; font-size: 28rpx;">▲</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<block wx:if="{{isPendingExpanded}}"> |
||||
|
<block wx:if="{{pendingSupplies.length > 0}}"> |
||||
|
<view wx:for="{{pendingSupplies}}" wx:key="id" class="card" style="width: 100%;"> |
||||
|
<!-- 图片和信息1:1比例并排显示 --> |
||||
|
<view style="display: flex; width: 100%; border-radius: 8rpx; overflow: hidden; background-color: #f5f5f5;"> |
||||
|
<!-- 左侧图片区域 50%宽度 --> |
||||
|
<view style="width: 50%; position: relative;"> |
||||
|
<!-- 第一张图片 --> |
||||
|
<view wx:if="{{item.imageUrls && item.imageUrls.length > 0}}" style="width: 100%; height: 100%;"> |
||||
|
<image src="{{item.imageUrls[0]}}" mode="aspectFill" style="width: 100%; height: 100%;" bindtap="previewImage" data-urls="{{item.imageUrls}}" data-index="0"></image> |
||||
|
</view> |
||||
|
<view wx:else style="width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; color: #999;"> |
||||
|
<text>暂无图片</text> |
||||
|
</view> |
||||
|
<!-- 剩余图片可滑动区域 --> |
||||
|
<view wx:if="{{item.imageUrls && item.imageUrls.length > 1}}" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"> |
||||
|
<swiper |
||||
|
class="image-swiper" |
||||
|
style="width: 100%; height: 100%;" |
||||
|
current="{{item.currentImageIndex || 0}}" |
||||
|
bindchange="swiperChange" |
||||
|
data-id="{{item.id}}"> |
||||
|
<block wx:for="{{item.imageUrls}}" wx:for-item="img" wx:for-index="idx" wx:key="idx"> |
||||
|
<swiper-item> |
||||
|
<image src="{{img}}" mode="aspectFill" style="width: 100%; height: 100%;" bindtap="previewImage" data-urls="{{item.imageUrls}}" data-index="{{idx}}"></image> |
||||
|
</swiper-item> |
||||
|
</block> |
||||
|
</swiper> |
||||
|
<!-- 显示页码指示器 --> |
||||
|
<view style="position: absolute; bottom: 10rpx; right: 10rpx; background-color: rgba(0,0,0,0.5); color: white; padding: 5rpx 10rpx; border-radius: 15rpx; font-size: 20rpx;"> |
||||
|
{{(item.currentImageIndex || 0) + 1}}/{{item.imageUrls.length}} |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 右侧信息区域 50%宽度 --> |
||||
|
<view style="width: 50%; padding: 15rpx; display: flex; flex-direction: column; justify-content: space-between; background-color: white; border-left: 1rpx solid #f0f0f0;"> |
||||
|
<view> |
||||
|
<view style="font-size: 28rpx; font-weight: bold; word-break: break-word;">{{item.name}} |
||||
|
<view style="display: inline-block; margin-left: 10rpx; font-size: 18rpx; color: #fff; background-color: #1677ff; padding: 2rpx 8rpx; border-radius: 10rpx;">审核中</view> |
||||
|
</view> |
||||
|
<view style="font-size: 24rpx; color: #666; margin-top: 8rpx;">蛋黄: {{item.yolk || '无'}}</view> |
||||
|
<view style="font-size: 24rpx; color: #666; margin-top: 8rpx;">规格: {{item.spec || '无'}}</view> |
||||
|
<view style="color: #f5222d; font-size: 24rpx; margin-top: 8rpx;">件数: {{item.minOrder}}件</view> |
||||
|
<view style="color: #1677ff; font-size: 24rpx; margin-top: 8rpx;">斤重: {{item.grossWeight || ''}}斤</view> |
||||
|
<view style="color: #722ed1; font-size: 24rpx; margin-top: 8rpx;">地区: {{item.region || '未设置'}}</view> |
||||
|
<view style="font-size: 22rpx; color: #999; margin-top: 8rpx;">创建时间: {{item.formattedCreatedAt}}</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 按钮区域 --> |
||||
|
<view style="display: flex; justify-content: space-around; margin-top: 10rpx; gap: 10rpx;"> |
||||
|
<button |
||||
|
style="background-color: #faad14; color: white; font-size: 22rpx; padding: 0 15rpx; line-height: 60rpx;" |
||||
|
bindtap="showEditSupply" |
||||
|
data-id="{{item.id}}" |
||||
|
> |
||||
|
编辑 |
||||
|
</button> |
||||
|
|
||||
|
<button |
||||
|
style="background-color: #f5222d; color: white; font-size: 22rpx; padding: 0 15rpx; line-height: 60rpx;" |
||||
|
bindtap="deleteSupply" |
||||
|
data-id="{{item.id}}" |
||||
|
> |
||||
|
删除 |
||||
|
</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 审核中货源加载更多 --> |
||||
|
<view class="load-more" wx:if="{{pagination.pending.hasMore}}"> |
||||
|
<view class="loading-text" wx:if="{{pagination.pending.loading}}"> |
||||
|
加载中... |
||||
|
</view> |
||||
|
<view class="load-more-text" wx:else bindtap="onReachPendingBottom"> |
||||
|
点击加载更多审核中货源 |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="no-more" wx:if="{{!pagination.pending.hasMore && pendingSupplies.length > 0}}"> |
||||
|
没有更多审核中货源了 |
||||
|
</view> |
||||
|
</block> |
||||
|
|
||||
|
<view wx:else style="text-align: center; color: #999; font-size: 24rpx; padding: 30rpx 0;"> |
||||
|
暂无审核中的货源 |
||||
|
</view> |
||||
|
</block> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 审核失败的货源 --> |
||||
|
<view style="margin-top: 30rpx; width: 100%;"> |
||||
|
<view style="font-size: 28rpx; font-weight: bold; color: #f5222d; margin-bottom: 15rpx; display: flex; justify-content: space-between; align-items: center;"> |
||||
|
<text>审核失败的货源 ({{rejectedSupplies.length}})</text> |
||||
|
<view bindtap="toggleRejectedExpand" style="width: 40rpx; height: 40rpx; display: flex; align-items: center; justify-content: center;"> |
||||
|
<text wx:if="{{isRejectedExpanded}}" style="color: #f5222d; font-size: 28rpx;">▼</text> |
||||
|
<text wx:else style="color: #f5222d; font-size: 28rpx;">▲</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<block wx:if="{{isRejectedExpanded}}"> |
||||
|
<block wx:if="{{rejectedSupplies.length > 0}}"> |
||||
|
<view wx:for="{{rejectedSupplies}}" wx:key="id" class="card" style="width: 100%;"> |
||||
|
<!-- 图片和信息1:1比例并排显示 --> |
||||
|
<view style="display: flex; width: 100%; border-radius: 8rpx; overflow: hidden; background-color: #f5f5f5;"> |
||||
|
<!-- 左侧图片区域 50%宽度 --> |
||||
|
<view style="width: 50%; position: relative;"> |
||||
|
<!-- 第一张图片 --> |
||||
|
<view wx:if="{{item.imageUrls && item.imageUrls.length > 0}}" style="width: 100%; height: 100%;"> |
||||
|
<image src="{{item.imageUrls[0]}}" mode="aspectFill" style="width: 100%; height: 100%;" bindtap="previewImage" data-urls="{{item.imageUrls}}" data-index="0"></image> |
||||
|
</view> |
||||
|
<view wx:else style="width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; color: #999;"> |
||||
|
<text>暂无图片</text> |
||||
|
</view> |
||||
|
<!-- 剩余图片可滑动区域 --> |
||||
|
<view wx:if="{{item.imageUrls && item.imageUrls.length > 1}}" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"> |
||||
|
<swiper |
||||
|
class="image-swiper" |
||||
|
style="width: 100%; height: 100%;" |
||||
|
current="{{item.currentImageIndex || 0}}" |
||||
|
bindchange="swiperChange" |
||||
|
data-id="{{item.id}}"> |
||||
|
<block wx:for="{{item.imageUrls}}" wx:for-item="img" wx:for-index="idx" wx:key="idx"> |
||||
|
<swiper-item> |
||||
|
<image src="{{img}}" mode="aspectFill" style="width: 100%; height: 100%;" bindtap="previewImage" data-urls="{{item.imageUrls}}" data-index="{{idx}}"></image> |
||||
|
</swiper-item> |
||||
|
</block> |
||||
|
</swiper> |
||||
|
<!-- 显示页码指示器 --> |
||||
|
<view style="position: absolute; bottom: 10rpx; right: 10rpx; background-color: rgba(0,0,0,0.5); color: white; padding: 5rpx 10rpx; border-radius: 15rpx; font-size: 20rpx;"> |
||||
|
{{(item.currentImageIndex || 0) + 1}}/{{item.imageUrls.length}} |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 右侧信息区域 50%宽度 --> |
||||
|
<view style="width: 50%; padding: 15rpx; display: flex; flex-direction: column; justify-content: space-between; background-color: white; border-left: 1rpx solid #f0f0f0;"> |
||||
|
<view> |
||||
|
<view style="font-size: 28rpx; font-weight: bold; word-break: break-word;">{{item.name}} |
||||
|
<view style="display: inline-block; margin-left: 10rpx; font-size: 18rpx; color: #fff; background-color: #f5222d; padding: 2rpx 8rpx; border-radius: 10rpx;">审核失败</view> |
||||
|
</view> |
||||
|
<view style="font-size: 24rpx; color: #666; margin-top: 8rpx;">蛋黄: {{item.yolk || '无'}}</view> |
||||
|
<view style="font-size: 24rpx; color: #666; margin-top: 8rpx;">规格: {{item.spec || '无'}}</view> |
||||
|
<view style="color: #f5222d; font-size: 24rpx; margin-top: 8rpx;">件数: {{item.minOrder}}件</view> |
||||
|
<view style="color: #1677ff; font-size: 24rpx; margin-top: 8rpx;">斤重: {{item.grossWeight || ''}}斤</view> |
||||
|
<view style="color: #722ed1; font-size: 24rpx; margin-top: 8rpx;">地区: {{item.region || '未设置'}}</view> |
||||
|
<view style="font-size: 22rpx; color: #999; margin-top: 8rpx;">创建时间: {{item.formattedCreatedAt}}</view> |
||||
|
<!-- 点击查看审核失败原因 --> |
||||
|
<view style="color: #f5222d; font-size: 24rpx; margin-top: 8rpx; text-decoration: underline;" bindtap="showRejectReason" data-id="{{item.id}}"> |
||||
|
审核失败原因:点击查看 |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 按钮区域 --> |
||||
|
<view style="display: flex; justify-content: space-around; margin-top: 10rpx; gap: 10rpx;"> |
||||
|
<button |
||||
|
style="background-color: #52c41a; color: white; font-size: 22rpx; padding: 0 15rpx; line-height: 60rpx;" |
||||
|
bindtap="preparePublishSupply" |
||||
|
data-id="{{item.id}}" |
||||
|
> |
||||
|
上架 |
||||
|
</button> |
||||
|
|
||||
|
<button |
||||
|
style="background-color: #f5222d; color: white; font-size: 22rpx; padding: 0 15rpx; line-height: 60rpx;" |
||||
|
bindtap="deleteSupply" |
||||
|
data-id="{{item.id}}" |
||||
|
> |
||||
|
删除 |
||||
|
</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 审核失败货源加载更多 --> |
||||
|
<view class="load-more" wx:if="{{pagination.rejected.hasMore}}"> |
||||
|
<view class="loading-text" wx:if="{{pagination.rejected.loading}}"> |
||||
|
加载中... |
||||
|
</view> |
||||
|
<view class="load-more-text" wx:else bindtap="onReachRejectedBottom"> |
||||
|
点击加载更多审核失败货源 |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="no-more" wx:if="{{!pagination.rejected.hasMore && rejectedSupplies.length > 0}}"> |
||||
|
没有更多审核失败货源了 |
||||
|
</view> |
||||
|
</block> |
||||
|
|
||||
|
<view wx:else style="text-align: center; color: #999; font-size: 24rpx; padding: 30rpx 0;"> |
||||
|
暂无审核失败的货源 |
||||
|
</view> |
||||
|
</block> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 草稿状态货源 --> |
||||
|
<view style="margin-top: 30rpx; width: 100%;"> |
||||
|
<view style="font-size: 28rpx; font-weight: bold; color: #999; margin-bottom: 15rpx; display: flex; justify-content: space-between; align-items: center;"> |
||||
|
<text>下架状态货源 ({{draftSupplies.length}})</text> |
||||
|
<view bindtap="toggleDraftExpand" style="width: 40rpx; height: 40rpx; display: flex; align-items: center; justify-content: center;"> |
||||
|
<text wx:if="{{isDraftExpanded}}" style="color: #999; font-size: 28rpx;">▼</text> |
||||
|
<text wx:else style="color: #999; font-size: 28rpx;">▲</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<block wx:if="{{isDraftExpanded}}"> |
||||
|
<block wx:if="{{draftSupplies.length > 0}}"> |
||||
|
<view wx:for="{{draftSupplies}}" wx:key="id" class="card" style="width: 100%;"> |
||||
|
<!-- 图片和信息1:1比例并排显示 --> |
||||
|
<view style="display: flex; width: 100%; border-radius: 8rpx; overflow: hidden; background-color: #f5f5f5;"> |
||||
|
<!-- 左侧图片区域 50%宽度 --> |
||||
|
<view style="width: 50%; position: relative;"> |
||||
|
<!-- 第一张图片 --> |
||||
|
<view wx:if="{{item.imageUrls && item.imageUrls.length > 0}}" style="width: 100%; height: 100%;"> |
||||
|
<image src="{{item.imageUrls[0]}}" mode="aspectFill" style="width: 100%; height: 100%;" bindtap="previewImage" data-urls="{{item.imageUrls}}" data-index="0"></image> |
||||
|
</view> |
||||
|
<view wx:else style="width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; color: #999;"> |
||||
|
<text>暂无图片</text> |
||||
|
</view> |
||||
|
<!-- 剩余图片可滑动区域 --> |
||||
|
<view wx:if="{{item.imageUrls && item.imageUrls.length > 1}}" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"> |
||||
|
<swiper |
||||
|
class="image-swiper" |
||||
|
style="width: 100%; height: 100%;" |
||||
|
current="{{item.currentImageIndex || 0}}" |
||||
|
bindchange="swiperChange" |
||||
|
data-id="{{item.id}}"> |
||||
|
<block wx:for="{{item.imageUrls}}" wx:for-item="img" wx:for-index="idx" wx:key="idx"> |
||||
|
<swiper-item> |
||||
|
<image src="{{img}}" mode="aspectFill" style="width: 100%; height: 100%;" bindtap="previewImage" data-urls="{{item.imageUrls}}" data-index="{{idx}}"></image> |
||||
|
</swiper-item> |
||||
|
</block> |
||||
|
</swiper> |
||||
|
<!-- 显示页码指示器 --> |
||||
|
<view style="position: absolute; bottom: 10rpx; right: 10rpx; background-color: rgba(0,0,0,0.5); color: white; padding: 5rpx 10rpx; border-radius: 15rpx; font-size: 20rpx;"> |
||||
|
{{(item.currentImageIndex || 0) + 1}}/{{item.imageUrls.length}} |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 右侧信息区域 50%宽度 --> |
||||
|
<view style="width: 50%; padding: 15rpx; display: flex; flex-direction: column; justify-content: space-between; background-color: white; border-left: 1rpx solid #f0f0f0;"> |
||||
|
<view> |
||||
|
<view style="font-size: 28rpx; font-weight: bold; word-break: break-word;">{{item.name}} |
||||
|
<view wx:if="{{item.status === 'hidden'}}" style="display: inline-block; margin-left: 10rpx; font-size: 18rpx; color: #fff; background-color: #8c8c8c; padding: 2rpx 8rpx; border-radius: 10rpx;">已隐藏</view> |
||||
|
<view wx:elif="{{item.status === 'sold_out' || item.status === 'Undercarriage'}}" style="display: inline-block; margin-left: 10rpx; font-size: 18rpx; color: #fff; background-color: #d9d9d9; padding: 2rpx 8rpx; border-radius: 10rpx;">已下架</view> |
||||
|
<view wx:else style="display: inline-block; margin-left: 10rpx; font-size: 18rpx; color: #fff; background-color: #999; padding: 2rpx 8rpx; border-radius: 10rpx;">草稿</view> |
||||
|
</view> |
||||
|
<view style="font-size: 24rpx; color: #666; margin-top: 8rpx;">蛋黄: {{item.yolk || '无'}}</view> |
||||
|
<view style="font-size: 24rpx; color: #666; margin-top: 8rpx;">规格: {{item.spec || '无'}}</view> |
||||
|
<view style="color: #f5222d; font-size: 24rpx; margin-top: 8rpx;">件数: {{item.minOrder}}件</view> |
||||
|
<view style="color: #1677ff; font-size: 24rpx; margin-top: 8rpx;">斤重: {{item.grossWeight || ''}}斤</view> |
||||
|
<view style="color: #722ed1; font-size: 24rpx; margin-top: 8rpx;">地区: {{item.region || '未设置'}}</view> |
||||
|
<view style="font-size: 22rpx; color: #999; margin-top: 8rpx;">创建时间: {{item.formattedCreatedAt}}</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 按钮区域 --> |
||||
|
<view style="display: flex; justify-content: space-around; margin-top: 10rpx; gap: 10rpx; flex-wrap: wrap;"> |
||||
|
<button |
||||
|
style="background-color: #1677ff; color: white; font-size: 22rpx; padding: 0 12rpx; line-height: 56rpx;" |
||||
|
bindtap="preparePublishSupply" |
||||
|
data-id="{{item.id}}" |
||||
|
> |
||||
|
上架 |
||||
|
</button> |
||||
|
|
||||
|
<!-- <button |
||||
|
style="background-color: #faad14; color: white; font-size: 22rpx; padding: 0 12rpx; line-height: 56rpx;" |
||||
|
bindtap="showEditSupply" |
||||
|
data-id="{{item.id}}" |
||||
|
> |
||||
|
编辑 |
||||
|
</button> --> |
||||
|
|
||||
|
<button |
||||
|
style="background-color: #f5222d; color: white; font-size: 22rpx; padding: 0 12rpx; line-height: 56rpx;" |
||||
|
bindtap="deleteSupply" |
||||
|
data-id="{{item.id}}" |
||||
|
> |
||||
|
删除 |
||||
|
</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 下架状态货源加载更多 --> |
||||
|
<view class="load-more" wx:if="{{pagination.draft.hasMore}}"> |
||||
|
<view class="loading-text" wx:if="{{pagination.draft.loading}}"> |
||||
|
加载中... |
||||
|
</view> |
||||
|
<view class="load-more-text" wx:else bindtap="onReachDraftBottom"> |
||||
|
点击加载更多下架状态货源 |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="no-more" wx:if="{{!pagination.draft.hasMore && draftSupplies.length > 0}}"> |
||||
|
没有更多下架状态货源了 |
||||
|
</view> |
||||
|
</block> |
||||
|
|
||||
|
<view wx:else style="text-align: center; color: #999; font-size: 24rpx; padding: 30rpx 0;"> |
||||
|
暂无下架状态的货源 |
||||
|
</view> |
||||
|
</block> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 创建货源弹窗 --> |
||||
|
<view class="modal" wx:if="{{showModal}}" style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); display: flex; justify-content: center; align-items: center; z-index: 999;" catchtouchmove="true" bindtouchstart="onModalTouchStart" bindtouchmove="onModalTouchMove"> |
||||
|
<view class="modal-content" style="width: 92%; max-width: 600rpx; background: white; padding: 40rpx; border-radius: 20rpx; max-height: 85vh; position: relative; box-shadow: 0 10rpx 40rpx rgba(0,0,0,0.15); transform: translateZ(0); -webkit-transform: translateZ(0);"> |
||||
|
<!-- 固定的关闭按钮 --> |
||||
|
<view style="position: absolute; top: 20rpx; right: 20rpx; background-color: #f5f5f5; color: #666; width: 60rpx; height: 60rpx; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 36rpx; z-index: 10;" bindtap="hideModal">×</view> |
||||
|
|
||||
|
<scroll-view scroll-y="true" style="height: 950rpx; overflow-y: scroll; -webkit-overflow-scrolling: touch; transform: translateZ(0); -webkit-transform: translateZ(0);" catchtouchmove="true" bindtouchstart="onModalTouchStart" bindtouchmove="onModalTouchMove"> |
||||
|
<view class="title" style="text-align: center; font-size: 36rpx; font-weight: bold; color: #333; margin-bottom: 30rpx; margin-top: 10rpx;">创建货源</view> |
||||
|
|
||||
|
<!-- 照片上传区域 --> |
||||
|
<view style="font-size: 28rpx; font-weight: 500; color: #333; margin-bottom: 12rpx; margin-top: 10rpx;">商品图片</view> |
||||
|
<view class="upload-area" style="width: 100%; margin: 0 auto; margin-bottom: 30rpx; border: 1rpx dashed #ddd; border-radius: 12rpx; padding: 24rpx;"> |
||||
|
<view style="display: flex; flex-wrap: wrap;"> |
||||
|
<!-- 已上传的图片 --> |
||||
|
<view wx:for="{{newSupply.imageUrls}}" wx:key="index" style="position: relative; width: 160rpx; height: 160rpx; margin: 10rpx; border-radius: 12rpx; overflow: hidden; box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.1);"> |
||||
|
<image src="{{item}}" mode="aspectFill" style="width: 100%; height: 100%;" bindtap="previewImage" data-urls="{{newSupply.imageUrls}}" data-index="{{index}}"></image> |
||||
|
<view class="delete-icon" style="position: absolute; top: 8rpx; right: 8rpx; background-color: rgba(0,0,0,0.6); color: white; width: 44rpx; height: 44rpx; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 28rpx;" bindtap="deleteImage" data-index="{{index}}" data-type="new">×</view> |
||||
|
</view> |
||||
|
<!-- 上传按钮 --> |
||||
|
<view wx:if="{{newSupply.imageUrls.length < 5}}" style="width: 160rpx; height: 160rpx; margin: 10rpx; border: 2rpx dashed #1677ff; border-radius: 12rpx; display: flex; align-items: center; justify-content: center; background-color: #f0f8ff;" bindtap="chooseImage" data-type="new"> |
||||
|
<text style="font-size: 60rpx; color: #1677ff;">+</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view style="font-size: 22rpx; color: #999; margin-top: 16rpx; text-align: center;">最多上传5张图片</view> |
||||
|
</view> |
||||
|
|
||||
|
<view style="font-size: 28rpx; font-weight: 500; color: #333; margin-bottom: 12rpx; margin-left: 10rpx;">商品名称</view> |
||||
|
<view |
||||
|
bindtap="openNameSelectModal" |
||||
|
style="width: 100%; height: 90rpx; line-height: 90rpx; padding: 0 24rpx; font-size: 30rpx; border: 2rpx solid #eee; border-radius: 12rpx; box-sizing: border-box; margin: 0 auto 30rpx; display: block; background: white; position: relative;"> |
||||
|
<view style="display: flex; justify-content: space-between; align-items: center;"> |
||||
|
<text>{{newSupply.name || '请选择商品名称'}}</text> |
||||
|
<text style="color: #999;">▼</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view style="font-size: 28rpx; font-weight: 500; color: #333; margin-bottom: 12rpx; margin-left: 10rpx;">蛋黄</view> |
||||
|
<view bindtap="openYolkSelectModal" style="width: 100%; height: 90rpx; line-height: 90rpx; padding: 0 24rpx; font-size: 30rpx; border: 2rpx solid #eee; border-radius: 12rpx; box-sizing: border-box; margin: 0 auto 30rpx; display: block; background: white; position: relative;"> |
||||
|
<view style="display: flex; justify-content: space-between; align-items: center;"> |
||||
|
<text>{{newSupply.yolk || '请选择蛋黄类型'}}</text> |
||||
|
<text style="color: #999;">▼</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view style="font-size: 28rpx; font-weight: 500; color: #333; margin-bottom: 12rpx; margin-left: 10rpx;">规格</view> |
||||
|
<!-- 修改为可点击的视图,点击后打开自定义弹窗 --> |
||||
|
<view bindtap="onSpecChange" style="width: 100%; height: 90rpx; line-height: 90rpx; padding: 0 24rpx; font-size: 30rpx; border: 2rpx solid #eee; border-radius: 12rpx; box-sizing: border-box; margin: 0 auto 30rpx; display: block; background: white; position: relative; z-index: 1;"> |
||||
|
<view style="display: flex; justify-content: space-between; align-items: center;"> |
||||
|
<text>{{newSupply.spec || '请选择规格'}}</text> |
||||
|
<text style="color: #999;">▼</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
<!-- 搜索功能已移至弹窗内 --> |
||||
|
<view style="font-size: 28rpx; font-weight: 500; color: #333; margin-bottom: 12rpx; margin-left: 10rpx;">价格</view> |
||||
|
<input class="input" type="text" placeholder="请输入价格" bindinput="onInput" data-field="price" value="{{newSupply.price}}" style="width: 100%; height: 90rpx; line-height: 90rpx; padding: 0 24rpx; font-size: 30rpx; border: 2rpx solid #eee; border-radius: 12rpx; box-sizing: border-box; margin: 0 auto 30rpx; display: block;" placeholder-style="font-size: 24rpx; color: #999; text-align: left;" catchtouchmove="true" bindtouchstart="onInputTouchStart" bindtouchmove="onInputTouchMove"></input> |
||||
|
|
||||
|
<view style="font-size: 28rpx; font-weight: 500; color: #333; margin-bottom: 12rpx; margin-left: 10rpx;">件数</view> |
||||
|
<input class="input" type="number" placeholder="请输入件数" bindinput="onInput" data-field="minOrder" value="{{newSupply.minOrder}}" style="width: 100%; height: 90rpx; line-height: 90rpx; padding: 0 24rpx; font-size: 30rpx; border: 2rpx solid #eee; border-radius: 12rpx; box-sizing: border-box; margin: 0 auto 30rpx; display: block;" placeholder-style="font-size: 24rpx; color: #999; text-align: left;" catchtouchmove="true" bindtouchstart="onInputTouchStart" bindtouchmove="onInputTouchMove"></input> |
||||
|
|
||||
|
<view style="font-size: 28rpx; font-weight: 500; color: #333; margin-bottom: 12rpx; margin-left: 10rpx;">斤重</view> |
||||
|
<input class="input" type="text" placeholder="请输入斤重" bindinput="onInput" data-field="grossWeight" value="{{newSupply.grossWeight || ''}}" style="width: 100%; height: 90rpx; line-height: 90rpx; padding: 0 24rpx; font-size: 30rpx; border: 2rpx solid #eee; border-radius: 12rpx; box-sizing: border-box; margin: 0 auto 30rpx; display: block;" placeholder-style="font-size: 24rpx; color: #999; text-align: left;" catchtouchmove="true" bindtouchstart="onInputTouchStart" bindtouchmove="onInputTouchMove"></input> |
||||
|
|
||||
|
|
||||
|
<view style="font-size: 28rpx; font-weight: 500; color: #333; margin-bottom: 12rpx; margin-left: 10rpx;">地区</view> |
||||
|
<input class="input" placeholder="请输入地区" bindinput="onInput" data-field="region" value="{{newSupply.region}}" style="width: 100%; height: 90rpx; line-height: 90rpx; padding: 0 24rpx; font-size: 30rpx; border: 2rpx solid #eee; border-radius: 12rpx; box-sizing: border-box; margin: 0 auto 30rpx; display: block;" placeholder-style="font-size: 24rpx; color: #999; text-align: left;" catchtouchmove="true" bindtouchstart="onInputTouchStart" bindtouchmove="onInputTouchMove"></input> |
||||
|
|
||||
|
<view style="display: flex; justify-content: space-between; margin-top: 20rpx; margin-bottom: 20rpx; gap: 20rpx;"> |
||||
|
<button bindtap="hideModal" style="flex: 1; height: 90rpx; line-height: 90rpx; background-color: #f5f5f5; color: #666; font-size: 30rpx; border-radius: 12rpx; margin: 0; display: flex; align-items: center; justify-content: center;">取消</button> |
||||
|
<button bindtap="addSupply" style="flex: 1; height: 90rpx; line-height: 90rpx; background-color: #07c160; color: white; font-size: 30rpx; border-radius: 12rpx; margin: 0; display: flex; align-items: center; justify-content: center;">创建</button> |
||||
|
</view> |
||||
|
</scroll-view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 编辑货源弹窗 --> |
||||
|
<view class="modal" wx:if="{{showEditModal}}" style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); display: flex; justify-content: center; align-items: center; z-index: 999;" catchtouchmove="true" bindtouchstart="onModalTouchStart" bindtouchmove="onModalTouchMove"> |
||||
|
<view class="modal-content" style="width: 92%; max-width: 600rpx; background: white; padding: 40rpx; border-radius: 20rpx; max-height: 85vh; position: relative; box-shadow: 0 10rpx 40rpx rgba(0,0,0,0.15); transform: translateZ(0); -webkit-transform: translateZ(0);"> |
||||
|
<!-- 固定的关闭按钮 --> |
||||
|
<view style="position: absolute; top: 20rpx; right: 20rpx; background-color: #f5f5f5; color: #666; width: 60rpx; height: 60rpx; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 36rpx; z-index: 10;" bindtap="hideEditModal">×</view> |
||||
|
|
||||
|
<scroll-view scroll-y="true" style="height: 950rpx; overflow-y: scroll; -webkit-overflow-scrolling: touch; transform: translateZ(0); -webkit-transform: translateZ(0);" catchtouchmove="true" bindtouchstart="onModalTouchStart" bindtouchmove="onModalTouchMove"> |
||||
|
<view class="title" style="text-align: center; font-size: 36rpx; font-weight: bold; color: #333; margin-bottom: 30rpx; margin-top: 10rpx;">编辑货源</view> |
||||
|
|
||||
|
<!-- 照片上传区域 --> |
||||
|
<view style="font-size: 28rpx; font-weight: 500; color: #333; margin-bottom: 12rpx; margin-top: 10rpx;">商品图片</view> |
||||
|
<view class="upload-area" style="width: 100%; margin: 0 auto; margin-bottom: 30rpx; border: 1rpx dashed #ddd; border-radius: 12rpx; padding: 24rpx;"> |
||||
|
<view style="display: flex; flex-wrap: wrap;"> |
||||
|
<!-- 已上传的图片 --> |
||||
|
<view wx:for="{{editSupply.imageUrls}}" wx:key="index" style="position: relative; width: 160rpx; height: 160rpx; margin: 10rpx; border-radius: 12rpx; overflow: hidden; box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.1);"> |
||||
|
<image src="{{item}}" mode="aspectFill" style="width: 100%; height: 100%;" bindtap="previewImage" data-urls="{{editSupply.imageUrls}}" data-index="{{index}}"></image> |
||||
|
<view class="delete-icon" style="position: absolute; top: 8rpx; right: 8rpx; background-color: rgba(0,0,0,0.6); color: white; width: 44rpx; height: 44rpx; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 28rpx;" bindtap="deleteImage" data-index="{{index}}" data-type="edit">×</view> |
||||
|
</view> |
||||
|
<!-- 上传按钮 --> |
||||
|
<view wx:if="{{editSupply.imageUrls.length < 5}}" style="width: 160rpx; height: 160rpx; margin: 10rpx; border: 2rpx dashed #1677ff; border-radius: 12rpx; display: flex; align-items: center; justify-content: center; background-color: #f0f8ff;" bindtap="chooseImage" data-type="edit"> |
||||
|
<text style="font-size: 60rpx; color: #1677ff;">+</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view style="font-size: 22rpx; color: #999; margin-top: 16rpx; text-align: center;">最多上传5张图片</view> |
||||
|
</view> |
||||
|
|
||||
|
<view style="font-size: 28rpx; font-weight: 500; color: #333; margin-bottom: 12rpx; margin-left: 10rpx;">商品名称</view> |
||||
|
<view |
||||
|
bindtap="openNameSelectModal" |
||||
|
style="width: 100%; height: 90rpx; line-height: 90rpx; padding: 0 24rpx; font-size: 30rpx; border: 2rpx solid #eee; border-radius: 12rpx; box-sizing: border-box; margin: 0 auto 30rpx; display: block; background: white; position: relative;"> |
||||
|
<view style="display: flex; justify-content: space-between; align-items: center;"> |
||||
|
<text>{{editSupply.name || '请选择商品名称'}}</text> |
||||
|
<text style="color: #999;">▼</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view style="font-size: 28rpx; font-weight: 500; color: #333; margin-bottom: 12rpx; margin-left: 10rpx;">蛋黄</view> |
||||
|
<view bindtap="openYolkSelectModal" style="width: 100%; height: 90rpx; line-height: 90rpx; padding: 0 24rpx; font-size: 30rpx; border: 2rpx solid #eee; border-radius: 12rpx; box-sizing: border-box; margin: 0 auto 30rpx; display: block; background: white; position: relative;"> |
||||
|
<view style="display: flex; justify-content: space-between; align-items: center;"> |
||||
|
<text>{{editSupply.yolk || '请选择蛋黄类型'}}</text> |
||||
|
<text style="color: #999;">▼</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view style="font-size: 28rpx; font-weight: 500; color: #333; margin-bottom: 12rpx; margin-left: 10rpx;">规格</view> |
||||
|
<!-- 修改为可点击的视图,点击后打开自定义弹窗 --> |
||||
|
<view bindtap="onEditSpecChange" style="width: 100%; height: 90rpx; line-height: 90rpx; padding: 0 24rpx; font-size: 30rpx; border: 2rpx solid #eee; border-radius: 12rpx; box-sizing: border-box; margin: 0 auto 30rpx; display: block; background: white; position: relative; z-index: 1;"> |
||||
|
<view style="display: flex; justify-content: space-between; align-items: center;"> |
||||
|
<text>{{editSupply.spec || '请选择规格'}}</text> |
||||
|
<text style="color: #999;">▼</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
<!-- 搜索功能已移至弹窗内 --> |
||||
|
|
||||
|
|
||||
|
<view style="font-size: 28rpx; font-weight: 500; color: #333; margin-bottom: 12rpx; margin-left: 10rpx;">价格</view> |
||||
|
<input class="input" type="text" placeholder="请输入价格" bindinput="onEditInput" data-field="price" value="{{editSupply.price}}" style="width: 100%; height: 90rpx; line-height: 90rpx; padding: 0 24rpx; font-size: 30rpx; border: 2rpx solid #eee; border-radius: 12rpx; box-sizing: border-box; margin: 0 auto 30rpx; display: block;" placeholder-style="font-size: 24rpx; color: #999; text-align: left;" catchtouchmove="true" bindtouchstart="onInputTouchStart" bindtouchmove="onInputTouchMove"></input> |
||||
|
|
||||
|
<view style="font-size: 28rpx; font-weight: 500; color: #333; margin-bottom: 12rpx; margin-left: 10rpx;">件数</view> |
||||
|
<input class="input" type="number" placeholder="请输入件数" bindinput="onEditInput" data-field="minOrder" value="{{editSupply.minOrder}}" style="width: 100%; height: 90rpx; line-height: 90rpx; padding: 0 24rpx; font-size: 30rpx; border: 2rpx solid #eee; border-radius: 12rpx; box-sizing: border-box; margin: 0 auto 30rpx; display: block;" placeholder-style="font-size: 24rpx; color: #999; text-align: left;" catchtouchmove="true" bindtouchstart="onInputTouchStart" bindtouchmove="onInputTouchMove"></input> |
||||
|
|
||||
|
<view style="font-size: 28rpx; font-weight: 500; color: #333; margin-bottom: 12rpx; margin-left: 10rpx;">斤重</view> |
||||
|
<input class="input" type="text" placeholder="请输入斤重" bindinput="onEditInput" data-field="grossWeight" value="{{editSupply.grossWeight || ''}}" style="width: 100%; height: 90rpx; line-height: 90rpx; padding: 0 24rpx; font-size: 30rpx; border: 2rpx solid #eee; border-radius: 12rpx; box-sizing: border-box; margin: 0 auto 30rpx; display: block;" placeholder-style="font-size: 24rpx; color: #999; text-align: left;" catchtouchmove="true" bindtouchstart="onInputTouchStart" bindtouchmove="onInputTouchMove"></input> |
||||
|
|
||||
|
|
||||
|
<view style="font-size: 28rpx; font-weight: 500; color: #333; margin-bottom: 12rpx; margin-left: 10rpx;">地区</view> |
||||
|
<input class="input" placeholder="请输入地区" bindinput="onEditInput" data-field="region" value="{{editSupply.region}}" style="width: 100%; height: 90rpx; line-height: 90rpx; padding: 0 24rpx; font-size: 30rpx; border: 2rpx solid #eee; border-radius: 12rpx; box-sizing: border-box; margin: 0 auto 30rpx; display: block;" placeholder-style="font-size: 24rpx; color: #999; text-align: left;" catchtouchmove="true" bindtouchstart="onInputTouchStart" bindtouchmove="onInputTouchMove"></input> |
||||
|
|
||||
|
<view style="display: flex; justify-content: space-between; margin-top: 20rpx; margin-bottom: 20rpx; gap: 20rpx;"> |
||||
|
<button bindtap="hideEditModal" style="flex: 1; height: 90rpx; line-height: 90rpx; background-color: #f5f5f5; color: #666; font-size: 30rpx; border-radius: 12rpx; margin: 0; display: flex; align-items: center; justify-content: center;">取消</button> |
||||
|
<button bindtap="saveEdit" style="flex: 1; height: 90rpx; line-height: 90rpx; background-color: #07c160; color: white; font-size: 30rpx; border-radius: 12rpx; margin: 0; display: flex; align-items: center; justify-content: center;">提交</button> |
||||
|
</view> |
||||
|
</scroll-view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 图片预览弹窗 --> |
||||
|
<view class="image-preview-mask" wx:if="{{showImagePreview}}" style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.9); display: flex; justify-content: center; align-items: center; z-index: 9999;" catchtouchmove="true" bindtap="closeImagePreview"> |
||||
|
<view style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center;"> |
||||
|
<swiper |
||||
|
style="width: 100%; height: 100%;" |
||||
|
current="{{previewImageIndex}}" |
||||
|
bindchange="onPreviewImageChange" |
||||
|
indicator-dots="true" |
||||
|
indicator-color="rgba(255,255,255,0.5)" |
||||
|
indicator-active-color="#fff"> |
||||
|
<block wx:for="{{previewImageUrls}}" wx:key="*this"> |
||||
|
<swiper-item> |
||||
|
<image |
||||
|
src="{{item}}" |
||||
|
mode="aspectFit" |
||||
|
style="width: 100%; height: 100%; transform: scale({{scale}}) translate({{offsetX}}px, {{offsetY}}px); transform-origin: center; transition: transform 0.1s;" |
||||
|
bindtap="handleImageTap" |
||||
|
bindtouchstart="handleTouchStart" |
||||
|
bindtouchmove="handleTouchMove" |
||||
|
bindtouchend="handleTouchEnd" |
||||
|
bindload="onPreviewImageLoad" |
||||
|
></image> |
||||
|
</swiper-item> |
||||
|
</block> |
||||
|
</swiper> |
||||
|
<view style="position: absolute; top: 40rpx; right: 40rpx; color: white; font-size: 40rpx;"> |
||||
|
<text bindtap="closeImagePreview" style="background: rgba(0,0,0,0.5); padding: 10rpx 20rpx; border-radius: 50%;">×</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 审核失败原因弹窗 --> |
||||
|
<view class="reject-reason-modal" wx:if="{{showRejectReasonModal}}" style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); display: flex; justify-content: center; align-items: center; z-index: 9999;" catchtouchmove="true"> |
||||
|
<view style="width: 80%; background: white; border-radius: 16rpx; overflow: hidden;"> |
||||
|
<!-- 弹窗标题和关闭按钮 --> |
||||
|
<view style="padding: 30rpx; border-bottom: 1rpx solid #eee; display: flex; justify-content: space-between; align-items: center;"> |
||||
|
<text style="font-size: 32rpx; font-weight: bold;">审核失败原因</text> |
||||
|
<view style="width: 60rpx; height: 60rpx; display: flex; align-items: center; justify-content: center; font-size: 40rpx; color: #999;" bindtap="closeRejectReasonModal">×</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 失败原因内容 --> |
||||
|
<view style="padding: 30rpx;"> |
||||
|
<view style="min-height: 200rpx; font-size: 28rpx; line-height: 48rpx; color: #666; white-space: pre-wrap; word-break: break-word;">{{rejectReason}}</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 操作按钮 --> |
||||
|
<view style="display: flex; border-top: 1rpx solid #eee;"> |
||||
|
<!-- <button style="flex: 1; background-color: #faad14; color: white; font-size: 28rpx; margin: 0; border-radius: 0; border-right: 1rpx solid #eee;" bindtap="editRejectedSupply">编辑</button> --> |
||||
|
<button style="flex: 1; background-color: #52c41a; color: white; font-size: 28rpx; margin: 0; border-radius: 0;" bindtap="resubmitRejectedSupply">重新提交</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 蛋黄选择弹窗 - 白色样式 --> |
||||
|
<view class="custom-select-modal" wx:if="{{showYolkSelectModal}}" style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); display: flex; justify-content: center; z-index: 9999;" catchtouchmove="true"> |
||||
|
<view style="position: fixed; bottom: 0; left: 0; right: 0; background: white; border-radius: 20rpx 20rpx 0 0; max-height: 80vh;"> |
||||
|
<!-- 顶部操作栏:取消和确定按钮 --> |
||||
|
<view style="padding: 20rpx; display: flex; justify-content: space-between; align-items: center; border-bottom: 1rpx solid #eee;"> |
||||
|
<view bindtap="closeYolkSelectModal" style="font-size: 32rpx; color: #333; padding: 10rpx 20rpx;">取消</view> |
||||
|
<view bindtap="confirmYolkSelection" style="font-size: 32rpx; color: #07c160; padding: 10rpx 20rpx;">确定</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 蛋黄列表 --> |
||||
|
<scroll-view |
||||
|
scroll-y="true" |
||||
|
style="max-height: 60vh; padding: 0; -webkit-overflow-scrolling: touch;" |
||||
|
enable-back-to-top="false" |
||||
|
> |
||||
|
<view |
||||
|
wx:for="{{yolkOptions}}" |
||||
|
wx:key="index" |
||||
|
class="select-item {{selectedYolkIndex === index ? 'selected' : ''}}" |
||||
|
bindtap="onYolkSelect" |
||||
|
data-index="{{index}}" |
||||
|
style="padding: 32rpx 40rpx; border-bottom: 1rpx solid #f0f0f0; font-size: 32rpx; color: {{selectedYolkIndex === index ? '#07c160' : '#131413'}}; text-align: center;" |
||||
|
> |
||||
|
{{item}} |
||||
|
</view> |
||||
|
</scroll-view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 商品名称选择弹窗 - 白色样式 --> |
||||
|
<view class="custom-select-modal" wx:if="{{showNameSelectModal}}" style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); display: flex; justify-content: center; z-index: 9999;" catchtouchmove="true"> |
||||
|
<view style="position: fixed; bottom: 0; left: 0; right: 0; background: white; border-radius: 20rpx 20rpx 0 0; max-height: 80vh;"> |
||||
|
<!-- 顶部操作栏:取消和确定按钮 --> |
||||
|
<view style="padding: 20rpx; display: flex; justify-content: space-between; align-items: center; border-bottom: 1rpx solid #eee;"> |
||||
|
<view bindtap="closeNameSelectModal" style="font-size: 32rpx; color: #333; padding: 10rpx 20rpx;">取消</view> |
||||
|
<view bindtap="confirmNameSelection" style="font-size: 32rpx; color: #07c160; padding: 10rpx 20rpx;">确定</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 商品名称列表 --> |
||||
|
<scroll-view |
||||
|
scroll-y="true" |
||||
|
style="max-height: 60vh; padding: 0; -webkit-overflow-scrolling: touch;" |
||||
|
enable-back-to-top="false" |
||||
|
> |
||||
|
<view |
||||
|
wx:for="{{productNameOptions}}" |
||||
|
wx:key="index" |
||||
|
class="select-item {{selectedNameIndex === index ? 'selected' : ''}}" |
||||
|
bindtap="onNameSelect" |
||||
|
data-index="{{index}}" |
||||
|
style="padding: 32rpx 40rpx; border-bottom: 1rpx solid #f0f0f0; font-size: 32rpx; color: {{selectedNameIndex === index ? '#07c160' : '#131413'}}; text-align: center;" |
||||
|
> |
||||
|
{{item}} |
||||
|
</view> |
||||
|
</scroll-view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 自定义规格选择弹窗 - 适配原生风格 --> |
||||
|
<view class="spec-select-modal" wx:if="{{showSpecSelectModal}}" style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); display: flex; justify-content: center; z-index: 9999;" catchtouchmove="true"> |
||||
|
<view style="position: fixed; bottom: 0; left: 0; right: 0; background: white; border-radius: 20rpx 20rpx 0 0; max-height: 80vh;"> |
||||
|
<!-- 顶部操作栏:取消和确定按钮 --> |
||||
|
<view style="padding: 20rpx; display: flex; justify-content: space-between; align-items: center; border-bottom: 1rpx solid #eee;"> |
||||
|
<view bindtap="closeSpecSelectModal" style="font-size: 32rpx; color: #333; padding: 10rpx 20rpx;">取消</view> |
||||
|
<view bindtap="confirmSpecSelection" style="font-size: 32rpx; color: #07c160; padding: 10rpx 20rpx;">确定</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 搜索框区域 --> |
||||
|
<view style="padding: 20rpx;"> |
||||
|
<view style="position: relative; background: #f5f5f5; border-radius: 40rpx; padding: 0 30rpx;"> |
||||
|
<input |
||||
|
type="text" |
||||
|
placeholder="搜索规格" |
||||
|
value="{{modalSpecSearchKeyword}}" |
||||
|
bindinput="onModalSpecSearchInput" |
||||
|
confirm-type="search" |
||||
|
style="width: 100%; height: 70rpx; line-height: 70rpx; font-size: 28rpx; background: transparent;" |
||||
|
/> |
||||
|
<view |
||||
|
wx:if="{{modalSpecSearchKeyword}}" |
||||
|
bindtap="clearModalSpecSearch" |
||||
|
style="position: absolute; right: 30rpx; top: 50%; transform: translateY(-50%); color: #999;" |
||||
|
> |
||||
|
✕ |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 规格列表 --> |
||||
|
<scroll-view |
||||
|
scroll-y="true" |
||||
|
style="max-height: 60vh; padding: 0; -webkit-overflow-scrolling: touch;" |
||||
|
enable-back-to-top="false" |
||||
|
> |
||||
|
<view |
||||
|
wx:for="{{filteredModalSpecOptions}}" |
||||
|
wx:key="index" |
||||
|
class="spec-item {{selectedModalSpecIndex === index ? 'selected' : ''}}" |
||||
|
bindtap="onModalSpecSelect" |
||||
|
data-index="{{index}}" |
||||
|
style="padding: 32rpx 40rpx; border-bottom: 1rpx solid #f0f0f0; font-size: 32rpx; color: {{selectedModalSpecIndex === index ? '#07c160' : '#131413'}}; text-align: center;" |
||||
|
> |
||||
|
{{item}} |
||||
|
</view> |
||||
|
</scroll-view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 未授权登录提示弹窗 --> |
||||
|
<view wx:if="{{showAuthModal}}" class="auth-modal-overlay"> |
||||
|
<view class="auth-modal-container"> |
||||
|
<view class="auth-modal-title"> |
||||
|
<text>提示</text> |
||||
|
</view> |
||||
|
<view class="auth-modal-content"> |
||||
|
<text>您还没有授权登录</text> |
||||
|
</view> |
||||
|
<view class="auth-modal-buttons"> |
||||
|
<button class="auth-primary-button" bindtap="showOneKeyLogin"> |
||||
|
一键登录 |
||||
|
</button> |
||||
|
<button class="auth-cancel-button" bindtap="closeAuthModal">取消</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 一键登录弹窗 --> |
||||
|
<view wx:if="{{showOneKeyLoginModal}}" class="auth-modal-overlay"> |
||||
|
<view class="auth-modal-container"> |
||||
|
<view class="auth-modal-title"> |
||||
|
<text>授权登录</text> |
||||
|
</view> |
||||
|
<view class="auth-modal-content"> |
||||
|
<text>请授权获取您的手机号用于登录</text> |
||||
|
</view> |
||||
|
<view class="auth-modal-buttons"> |
||||
|
<button class="auth-primary-button" open-type="getPhoneNumber" bind:getphonenumber="onGetPhoneNumber"> |
||||
|
授权获取手机号 |
||||
|
</button> |
||||
|
<button class="auth-cancel-button" bindtap="closeOneKeyLoginModal">取消</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 用户信息填写弹窗 --> |
||||
|
<view wx:if="{{showUserInfoForm}}" class="auth-modal-overlay"> |
||||
|
<view class="auth-modal-container"> |
||||
|
<view class="auth-modal-title"> |
||||
|
<text>完善个人信息</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 头像选择 --> |
||||
|
<view class="auth-avatar-section"> |
||||
|
<button class="auth-avatar-wrapper" open-type="chooseAvatar" bind:chooseavatar="onChooseAvatar"> |
||||
|
<image class="auth-avatar" src="{{avatarUrl}}"></image> |
||||
|
</button> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 昵称输入 --> |
||||
|
<form bindsubmit="getUserName"> |
||||
|
<view class="auth-form-group"> |
||||
|
<view class="auth-form-label">昵称</view> |
||||
|
<input placeholder="请输入昵称" type="nickname" name="nickname" maxlength="32" class="auth-form-input"></input> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 提交按钮 --> |
||||
|
<view class="auth-form-actions"> |
||||
|
<button form-type="submit" class="auth-confirm-button">确定</button> |
||||
|
</view> |
||||
|
</form> |
||||
|
|
||||
|
<!-- 取消按钮 --> |
||||
|
<view class="auth-modal-buttons"> |
||||
|
<button class="auth-cancel-button" bindtap="cancelUserInfoForm">取消</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
</view> |
||||
@ -0,0 +1,295 @@ |
|||||
|
/* pages/seller/index.wxss */ |
||||
|
/* 立体玻璃质感按钮基础样式 */ |
||||
|
.glass-btn { |
||||
|
position: relative; |
||||
|
overflow: hidden; |
||||
|
border: none; |
||||
|
padding: 28rpx 40rpx; |
||||
|
font-size: 32rpx; |
||||
|
font-weight: 600; |
||||
|
border-radius: 16rpx; |
||||
|
box-shadow: |
||||
|
0 8rpx 24rpx rgba(0, 0, 0, 0.15), |
||||
|
0 0 0 1rpx rgba(255, 255, 255, 0.3) inset, |
||||
|
0 1rpx 0 rgba(255, 255, 255, 0.2) inset; |
||||
|
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2); |
||||
|
transition: all 0.3s ease; |
||||
|
background: rgba(255, 255, 255, 0.8); |
||||
|
backdrop-filter: blur(10rpx); |
||||
|
-webkit-backdrop-filter: blur(10rpx); |
||||
|
white-space: nowrap; |
||||
|
width: auto; |
||||
|
min-width: 80%; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
/* 按压效果 */ |
||||
|
.glass-btn:active { |
||||
|
transform: translateY(2rpx); |
||||
|
box-shadow: |
||||
|
0 4rpx 12rpx rgba(0, 0, 0, 0.1), |
||||
|
0 0 0 1rpx rgba(255, 255, 255, 0.2) inset, |
||||
|
0 1rpx 0 rgba(255, 255, 255, 0.1) inset; |
||||
|
} |
||||
|
|
||||
|
/* 主按钮 - 蓝色玻璃效果 */ |
||||
|
.primary-glass-btn { |
||||
|
background: rgba(22, 119, 255, 0.7); |
||||
|
color: white; |
||||
|
box-shadow: |
||||
|
0 8rpx 24rpx rgba(22, 119, 255, 0.3), |
||||
|
0 0 0 1rpx rgba(255, 255, 255, 0.3) inset, |
||||
|
0 1rpx 0 rgba(255, 255, 255, 0.2) inset; |
||||
|
} |
||||
|
|
||||
|
/* 联系客服按钮样式 */ |
||||
|
.customer-service-btn { |
||||
|
background: #f0f0f0; |
||||
|
color: black; |
||||
|
font-size: 30rpx; |
||||
|
width: 70rpx; |
||||
|
height: 80rpx; |
||||
|
line-height: 70rpx; |
||||
|
padding: 0; |
||||
|
border-radius: 500rpx; |
||||
|
border: 1rpx solid #d9d9d9; |
||||
|
box-shadow: |
||||
|
0 4rpx 16rpx rgba(0, 0, 0, 0.1), |
||||
|
0 1rpx 0 rgba(255, 255, 255, 0.5) inset; |
||||
|
transition: all 0.3s ease; |
||||
|
white-space: nowrap; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.customer-service-btn:active { |
||||
|
transform: translateY(2rpx); |
||||
|
background: #f5f5f5; |
||||
|
box-shadow: |
||||
|
0 2rpx 8rpx rgba(0, 0, 0, 0.08), |
||||
|
0 1rpx 0 rgba(255, 255, 255, 0.3) inset; |
||||
|
} |
||||
|
|
||||
|
/* 登录授权弹窗样式 - 专门用于登录相关弹窗 */ |
||||
|
.auth-modal-overlay { |
||||
|
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: 999; |
||||
|
} |
||||
|
|
||||
|
.auth-modal-container { |
||||
|
background-color: white; |
||||
|
border-radius: 16rpx; |
||||
|
width: 80%; |
||||
|
padding: 40rpx; |
||||
|
} |
||||
|
|
||||
|
.auth-modal-title { |
||||
|
text-align: center; |
||||
|
margin-bottom: 30rpx; |
||||
|
} |
||||
|
|
||||
|
.auth-modal-title text { |
||||
|
font-size: 36rpx; |
||||
|
font-weight: bold; |
||||
|
} |
||||
|
|
||||
|
.auth-modal-content { |
||||
|
text-align: center; |
||||
|
margin-bottom: 40rpx; |
||||
|
color: #666; |
||||
|
} |
||||
|
|
||||
|
.auth-modal-content text { |
||||
|
font-size: 32rpx; |
||||
|
} |
||||
|
|
||||
|
.auth-modal-buttons { |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.auth-primary-button { |
||||
|
background-color: #1677ff; |
||||
|
color: white; |
||||
|
width: 100%; |
||||
|
border-radius: 8rpx; |
||||
|
margin-bottom: 20rpx; |
||||
|
border: none; |
||||
|
} |
||||
|
|
||||
|
.auth-cancel-button { |
||||
|
background: none; |
||||
|
color: #666; |
||||
|
border: none; |
||||
|
} |
||||
|
|
||||
|
/* 头像选择样式 */ |
||||
|
.auth-avatar-section { |
||||
|
text-align: center; |
||||
|
margin-bottom: 40rpx; |
||||
|
} |
||||
|
|
||||
|
.auth-avatar-wrapper { |
||||
|
padding: 0; |
||||
|
background: none; |
||||
|
border: none; |
||||
|
} |
||||
|
|
||||
|
.auth-avatar { |
||||
|
width: 160rpx; |
||||
|
height: 160rpx; |
||||
|
border-radius: 50%; |
||||
|
} |
||||
|
|
||||
|
/* 表单样式 */ |
||||
|
.auth-form-group { |
||||
|
margin-bottom: 30rpx; |
||||
|
} |
||||
|
|
||||
|
.auth-form-label { |
||||
|
font-size: 28rpx; |
||||
|
margin-bottom: 10rpx; |
||||
|
display: block; |
||||
|
} |
||||
|
|
||||
|
.auth-form-input { |
||||
|
border: 1rpx solid #eee; |
||||
|
border-radius: 8rpx; |
||||
|
padding: 20rpx; |
||||
|
width: 100%; |
||||
|
max-width: 100%; |
||||
|
box-sizing: border-box; |
||||
|
font-size: 28rpx; |
||||
|
} |
||||
|
|
||||
|
.auth-form-actions { |
||||
|
text-align: center; |
||||
|
margin-top: 40rpx; |
||||
|
} |
||||
|
|
||||
|
.auth-confirm-button { |
||||
|
background-color: #07c160; |
||||
|
color: white; |
||||
|
width: 100%; |
||||
|
border-radius: 8rpx; |
||||
|
border: none; |
||||
|
} |
||||
|
|
||||
|
/* 加载更多样式 */ |
||||
|
.load-more { |
||||
|
text-align: center; |
||||
|
padding: 30rpx; |
||||
|
color: #666; |
||||
|
background-color: #f9f9f9; |
||||
|
border-radius: 8rpx; |
||||
|
margin: 20rpx 0; |
||||
|
} |
||||
|
|
||||
|
.loading-text { |
||||
|
color: #999; |
||||
|
font-size: 26rpx; |
||||
|
} |
||||
|
|
||||
|
.load-more-text { |
||||
|
color: #1677ff; |
||||
|
font-size: 26rpx; |
||||
|
padding: 15rpx 30rpx; |
||||
|
border: 1rpx solid #1677ff; |
||||
|
border-radius: 8rpx; |
||||
|
display: inline-block; |
||||
|
background-color: white; |
||||
|
} |
||||
|
|
||||
|
.no-more { |
||||
|
text-align: center; |
||||
|
padding: 30rpx; |
||||
|
color: #999; |
||||
|
font-size: 26rpx; |
||||
|
background-color: #f9f9f9; |
||||
|
border-radius: 8rpx; |
||||
|
margin: 20rpx 0; |
||||
|
} |
||||
|
|
||||
|
/* 页面滚动锁定样式 */ |
||||
|
.page-scroll-lock { |
||||
|
position: fixed; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
overflow: hidden; |
||||
|
height: 100vh; |
||||
|
|
||||
|
/* iOS设备特殊锁定机制 */ |
||||
|
-webkit-overflow-scrolling: auto; |
||||
|
touch-action: none; |
||||
|
pointer-events: none; |
||||
|
|
||||
|
/* iOS设备硬件加速处理 */ |
||||
|
transform: translateZ(0); |
||||
|
-webkit-transform: translateZ(0); |
||||
|
} |
||||
|
|
||||
|
/* iOS设备子元素交互修复 */ |
||||
|
.page-scroll-lock view, |
||||
|
.page-scroll-lock text, |
||||
|
.page-scroll-lock image, |
||||
|
.page-scroll-lock button, |
||||
|
.page-scroll-lock input, |
||||
|
.page-scroll-lock textarea, |
||||
|
.page-scroll-lock scroll-view, |
||||
|
.page-scroll-lock swiper, |
||||
|
.page-scroll-lock navigator, |
||||
|
.page-scroll-lock picker, |
||||
|
.page-scroll-lock slider, |
||||
|
.page-scroll-lock switch { |
||||
|
pointer-events: auto; |
||||
|
} |
||||
|
|
||||
|
/* 弹窗输入框防抖动样式 */ |
||||
|
.modal-content .input { |
||||
|
-webkit-appearance: none; |
||||
|
appearance: none; |
||||
|
-webkit-tap-highlight-color: transparent; |
||||
|
tap-highlight-color: transparent; |
||||
|
outline: none; |
||||
|
-webkit-user-select: text; |
||||
|
user-select: text; |
||||
|
-webkit-touch-callout: none; |
||||
|
touch-callout: none; |
||||
|
} |
||||
|
|
||||
|
/* 弹窗容器硬件加速 */ |
||||
|
.modal-content { |
||||
|
will-change: transform; |
||||
|
-webkit-will-change: transform; |
||||
|
} |
||||
|
|
||||
|
/* 输入框容器稳定性增强 */ |
||||
|
.modal-content input, |
||||
|
.modal-content textarea { |
||||
|
transform: translateZ(0); |
||||
|
-webkit-transform: translateZ(0); |
||||
|
perspective: 1000px; |
||||
|
-webkit-perspective: 1000px; |
||||
|
} |
||||
|
|
||||
|
/* iOS输入框防抖优化 */ |
||||
|
.modal-content .input { |
||||
|
-webkit-appearance: none; |
||||
|
appearance: none; |
||||
|
-webkit-tap-highlight-color: transparent; |
||||
|
touch-action: manipulation; |
||||
|
-webkit-touch-callout: none; |
||||
|
-webkit-user-select: text; |
||||
|
user-select: text; |
||||
|
-webkit-transform: translateZ(0); |
||||
|
transform: translateZ(0); |
||||
|
will-change: transform; |
||||
|
} |
||||
File diff suppressed because it is too large
@ -0,0 +1,4 @@ |
|||||
|
{ |
||||
|
"usingComponents": {}, |
||||
|
"navigationBarTitleText": "立即入驻" |
||||
|
} |
||||
@ -0,0 +1,367 @@ |
|||||
|
<view class="container"> |
||||
|
<!-- 内容区域 --> |
||||
|
<view class="content"> |
||||
|
<!-- 引导页 --> |
||||
|
<view class="guide-page" wx:if="{{showGuidePage}}"> |
||||
|
<view class="guide-content"> |
||||
|
<view class="guide-title">成为供应商</view> |
||||
|
<view class="guide-description">完成入驻后即可发布货源,开展鸡蛋贸易</view> |
||||
|
<button class="guide-button btn btn-primary" bindtap="startSettlement">立即入驻</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 实际入驻流程内容 --> |
||||
|
<view wx:if="{{!showGuidePage}}"> |
||||
|
<!-- 步骤指示器 --> |
||||
|
<view class="step-indicator"> |
||||
|
<view class="step {{currentStep >= 0 ? 'active' : ''}}"> |
||||
|
<view class="step-circle">1</view> |
||||
|
<text>选择身份</text> |
||||
|
</view> |
||||
|
<view class="step-line"></view> |
||||
|
<view class="step {{currentStep >= 1 ? 'active' : ''}}"> |
||||
|
<view class="step-circle">2</view> |
||||
|
<text>基本信息</text> |
||||
|
</view> |
||||
|
<view class="step-line"></view> |
||||
|
<view class="step {{currentStep >= 2 ? 'active' : ''}}"> |
||||
|
<view class="step-circle">3</view> |
||||
|
<text>上传资料</text> |
||||
|
</view> |
||||
|
<view class="step-line"></view> |
||||
|
<view class="step {{currentStep >= 3 ? 'active' : ''}}"> |
||||
|
<view class="step-circle">4</view> |
||||
|
<text>审核状态</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 身份选择页面 --> |
||||
|
<view class="page" wx:if="{{currentStep === 0}}"> |
||||
|
<view class="section"> |
||||
|
<view class="section-title required">请选择您的身份</view> |
||||
|
<view class="identity-options"> |
||||
|
<view class="identity-option {{collaborationid === 'chicken' ? 'selected' : ''}}" |
||||
|
data-identity="chicken" bindtap="selectIdentity"> |
||||
|
<view class="identity-icon"> |
||||
|
<text class="icon">🐔</text> |
||||
|
</view> |
||||
|
<view class="identity-text"> |
||||
|
<view class="identity-title">鸡场</view> |
||||
|
<view class="identity-desc">养殖场主、生产商身份</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="identity-option {{collaborationid === 'trader' ? 'selected' : ''}}" |
||||
|
data-identity="trader" bindtap="selectIdentity"> |
||||
|
<view class="identity-icon"> |
||||
|
<text class="icon">💰</text> |
||||
|
</view> |
||||
|
<view class="identity-text"> |
||||
|
<view class="identity-title">贸易商</view> |
||||
|
<view class="identity-desc">经销商、批发商身份</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="error-message" wx:if="{{showIdentityError}}">请选择身份</view> |
||||
|
</view> |
||||
|
|
||||
|
<button class="btn btn-primary" bindtap="nextStep">下一步</button> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 基本信息页面 --> |
||||
|
<view class="page" wx:if="{{currentStep === 1}}"> |
||||
|
<view class="basic-info-form"> |
||||
|
<!-- 公司名称 --> |
||||
|
<view class="form-item"> |
||||
|
<view class="form-header"> |
||||
|
<text class="form-label required">公司名称</text> |
||||
|
</view> |
||||
|
<view class="input-wrapper"> |
||||
|
<input |
||||
|
class="form-input {{showCompanyNameError ? 'error' : ''}}" |
||||
|
placeholder="请输入公司名称" |
||||
|
placeholder-class="form-input-placeholder" |
||||
|
value="{{company}}" |
||||
|
bindinput="onCompanyNameInput" |
||||
|
bindblur="onCompanyNameBlur" |
||||
|
bindfocus="onCompanyNameFocus" |
||||
|
maxlength="50" |
||||
|
type="text" |
||||
|
confirm-type="next" |
||||
|
cursor-spacing="10" |
||||
|
adjust-position="{{true}}" |
||||
|
hold-keyboard="{{false}}" |
||||
|
/> |
||||
|
<text class="input-icon" wx:if="{{company && !showCompanyNameError}}">✓</text> |
||||
|
</view> |
||||
|
<text class="error-message" wx:if="{{showCompanyNameError}}">{{companyNameError || '请输入公司名称'}}</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 地址信息 --> |
||||
|
<view class="form-item"> |
||||
|
<view class="form-header"> |
||||
|
<text class="form-label required">所在地区</text> |
||||
|
</view> |
||||
|
<picker |
||||
|
mode="region" |
||||
|
value="{{[province, city, district]}}" |
||||
|
bindchange="onRegionChange" |
||||
|
class="region-picker {{showRegionError ? 'error' : ''}} {{!(province || city || district) ? 'placeholder' : ''}}" |
||||
|
> |
||||
|
<view class="picker-content"> |
||||
|
<text>{{province || city || district ? province + ' ' + city + ' ' + district : '请选择省市区'}}</text> |
||||
|
<view class="picker-arrow">▼</view> |
||||
|
</view> |
||||
|
</picker> |
||||
|
<text class="error-message" wx:if="{{showRegionError}}">{{regionError || '请选择所在地区'}}</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 详细地址 --> |
||||
|
<view class="form-item"> |
||||
|
<view class="form-header"> |
||||
|
<text class="form-label">详细地址</text> |
||||
|
</view> |
||||
|
<view class="input-wrapper"> |
||||
|
<input |
||||
|
class="form-input" |
||||
|
placeholder="请输入详细地址(选填)" |
||||
|
placeholder-class="form-input-placeholder" |
||||
|
value="{{detailedaddress}}" |
||||
|
bindinput="onDetailAddressInput" |
||||
|
bindfocus="onDetailAddressFocus" |
||||
|
maxlength="100" |
||||
|
type="text" |
||||
|
confirm-type="done" |
||||
|
cursor-spacing="10" |
||||
|
adjust-position="{{true}}" |
||||
|
hold-keyboard="{{false}}" |
||||
|
/> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 合作模式 --> |
||||
|
<view class="form-item"> |
||||
|
<view class="form-header"> |
||||
|
<text class="form-label required">合作模式</text> |
||||
|
</view> |
||||
|
<view class="cooperation-options"> |
||||
|
<view |
||||
|
class="cooperation-option {{cooperation === '货源委托' ? 'active' : ''}}" |
||||
|
bindtap="selectCooperation" |
||||
|
data-value="货源委托" |
||||
|
> |
||||
|
<view class="cooperation-icon"></view> |
||||
|
<text class="cooperation-text">货源委托</text> |
||||
|
</view> |
||||
|
<view |
||||
|
class="cooperation-option {{cooperation === '自主定价销售' ? 'active' : ''}}" |
||||
|
bindtap="selectCooperation" |
||||
|
data-value="自主定价销售" |
||||
|
> |
||||
|
<view class="cooperation-icon"></view> |
||||
|
<text class="cooperation-text">自主定价销售</text> |
||||
|
</view> |
||||
|
<view |
||||
|
class="cooperation-option {{cooperation === '区域包场合作' ? 'active' : ''}}" |
||||
|
bindtap="selectCooperation" |
||||
|
data-value="区域包场合作" |
||||
|
> |
||||
|
<view class="cooperation-icon"></view> |
||||
|
<text class="cooperation-text">区域包场合作</text> |
||||
|
</view> |
||||
|
<view |
||||
|
class="cooperation-option {{cooperation === '其他' ? 'active' : ''}}" |
||||
|
bindtap="selectCooperation" |
||||
|
data-value="其他" |
||||
|
> |
||||
|
<view class="cooperation-icon"></view> |
||||
|
<text class="cooperation-text">其他</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
<text class="error-message" wx:if="{{showCooperationError}}">{{cooperationError || '请选择合作模式'}}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 底部按钮 --> |
||||
|
<view class="button-group"> |
||||
|
<button class="btn btn-secondary" bindtap="prevStep">返回上一步</button> |
||||
|
<button class="btn btn-primary" bindtap="nextStep">下一步</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 上传资料页面 --> |
||||
|
<view class="page" wx:if="{{currentStep === 2}}"> |
||||
|
<!-- 营业执照上传 --> |
||||
|
<view class="section"> |
||||
|
<view class="section-title">{{collaborationid === 'chicken' ? '鸡场营业执照(选填)' : '贸易商营业执照(选填)'}}</view> |
||||
|
<view class="upload-area" bindtap="uploadBusinessLicense"> |
||||
|
<view class="upload-icon">+</view> |
||||
|
<view class="upload-text">点击上传营业执照</view> |
||||
|
<view class="upload-tip">支持jpg、png格式,大小不超过5M</view> |
||||
|
</view> |
||||
|
<view wx:if="{{businesslicenseurl}}"> |
||||
|
<view class="uploaded-file"> |
||||
|
<view class="file-icon">📄</view> |
||||
|
<view class="file-name">{{businesslicenseurl.name}}</view> |
||||
|
<view class="file-delete" bindtap="deleteBusinessLicense">删除</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 鸡场特有字段 --> |
||||
|
<view class="section" wx:if="{{collaborationid === 'chicken'}}"> |
||||
|
<view class="section-title">动物检疫合格证明(选填)</view> |
||||
|
<view class="upload-area" bindtap="uploadAnimalQuarantine"> |
||||
|
<view class="upload-icon">+</view> |
||||
|
<view class="upload-text">点击上传动物检疫合格证明</view> |
||||
|
<view class="upload-tip">支持jpg、png格式,大小不超过5M</view> |
||||
|
</view> |
||||
|
<view wx:if="{{proofurl}}"> |
||||
|
<view class="uploaded-file"> |
||||
|
<view class="file-icon">📄</view> |
||||
|
<view class="file-name">{{proofurl.name}}</view> |
||||
|
<view class="file-delete" bindtap="deleteAnimalQuarantine">删除</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 贸易商特有字段 --> |
||||
|
<view class="section" wx:if="{{collaborationid === 'trader'}}"> |
||||
|
<view class="section-title">法人身份证正反面(选填)</view> |
||||
|
<view class="upload-area" bindtap="uploadIdCard"> |
||||
|
<view class="upload-icon">+</view> |
||||
|
<view class="upload-text">点击上传法人身份证正反面</view> |
||||
|
<view class="upload-tip">支持jpg、png格式,大小不超过5M</view> |
||||
|
</view> |
||||
|
<view wx:if="{{idCardFile}}"> |
||||
|
<view class="uploaded-file"> |
||||
|
<view class="file-icon">📄</view> |
||||
|
<view class="file-name">{{idCardFile.name}}</view> |
||||
|
<view class="file-delete" bindtap="deleteIdCard">删除</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="section"> |
||||
|
<view class="section-title">品牌授权链文件</view> |
||||
|
<view class="upload-area" bindtap="uploadBrandAuth"> |
||||
|
<view class="upload-icon">+</view> |
||||
|
<view class="upload-text">点击上传品牌授权链文件</view> |
||||
|
<view class="upload-tip">支持jpg、png格式,大小不超过5M</view> |
||||
|
</view> |
||||
|
<view wx:if="{{brandurl}}"> |
||||
|
<view class="uploaded-file"> |
||||
|
<view class="file-icon">📄</view> |
||||
|
<view class="file-name">{{brandurl.name}}</view> |
||||
|
<view class="file-delete" bindtap="deleteBrandAuth">删除</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<button class="btn btn-primary" bindtap="submitApplication">提交申请</button> |
||||
|
<button class="btn btn-secondary" bindtap="prevStep">返回上一步</button> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 审核状态页面 --> |
||||
|
<view class="page" wx:if="{{currentStep === 3}}"> |
||||
|
<!-- 审核中状态 --> |
||||
|
<view class="audit-status" wx:if="{{partnerstatus === 'underreview'}}"> |
||||
|
<view class="audit-icon pending"> |
||||
|
<text class="icon">⏳</text> |
||||
|
</view> |
||||
|
<view class="audit-title">审核中</view> |
||||
|
<view class="audit-desc"> |
||||
|
你已成功提交小程序备案,请等待审核。<br /> |
||||
|
你可以撤回备案 |
||||
|
</view> |
||||
|
<button class="btn btn-outline" bindtap="withdrawApplication">撤回备案</button> |
||||
|
<button class="btn btn-primary btn-audit" bindtap="knowAudit">我知道了</button> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 审核失败状态 --> |
||||
|
<view class="audit-status" wx:if="{{partnerstatus === 'reviewfailed'}}"> |
||||
|
<view class="audit-icon failed"> |
||||
|
<text class="icon">❌</text> |
||||
|
</view> |
||||
|
<view class="audit-title">审核失败</view> |
||||
|
<view class="audit-desc"> |
||||
|
很抱歉,您的备案申请未通过审核 |
||||
|
</view> |
||||
|
|
||||
|
<view class="audit-reason"> |
||||
|
<view class="audit-reason-title">审核失败原因:</view> |
||||
|
<view class="audit-reason-content">{{auditFailedReason}}</view> |
||||
|
</view> |
||||
|
|
||||
|
<button class="btn btn-primary btn-audit" bindtap="resubmitApplication">重新提交备案</button> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 审核通过状态 --> |
||||
|
<view class="audit-status" wx:if="{{partnerstatus === 'approved'}}"> |
||||
|
<view class="audit-icon pending"> |
||||
|
<text class="icon">✅</text> |
||||
|
</view> |
||||
|
<view class="audit-title">审核通过</view> |
||||
|
<view class="audit-desc"> |
||||
|
恭喜!您的备案申请已通过审核。<br /> |
||||
|
我们将尽快与您联系后续事宜。 |
||||
|
</view> |
||||
|
<button class="btn btn-primary btn-audit" bindtap="completeApplication">完成</button> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 合作中状态 --> |
||||
|
<view class="audit-status" wx:if="{{partnerstatus === 'incooperation'}}"> |
||||
|
<view class="audit-icon success"> |
||||
|
<text class="icon">🤝</text> |
||||
|
</view> |
||||
|
<view class="audit-title">合作中</view> |
||||
|
<view class="audit-desc"> |
||||
|
您已成功成为我们的合作伙伴!<br /> |
||||
|
感谢您的信任与支持。 |
||||
|
</view> |
||||
|
<button class="btn btn-primary btn-audit" bindtap="completeApplication">继续合作</button> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 未合作状态 --> |
||||
|
<view class="audit-status" wx:if="{{partnerstatus === 'notcooperative'}}"> |
||||
|
<view class="audit-icon neutral"> |
||||
|
<text class="icon">📋</text> |
||||
|
</view> |
||||
|
<view class="audit-title">未合作</view> |
||||
|
<view class="audit-desc"> |
||||
|
感谢您的关注,期待未来有机会合作。<br /> |
||||
|
如有需要可重新申请。 |
||||
|
</view> |
||||
|
<button class="btn btn-outline" bindtap="resetApplication">重新申请</button> |
||||
|
<button class="btn btn-primary btn-audit" bindtap="knowAudit">我知道了</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
|
||||
|
|
||||
|
<!-- 登录授权弹窗 --> |
||||
|
<view class="auth-modal" wx:if="{{showAuthModal}}"> |
||||
|
<view class="auth-content"> |
||||
|
<view class="auth-header"> |
||||
|
<view class="auth-title">登录授权</view> |
||||
|
<view class="auth-close" bindtap="closeAuthModal">×</view> |
||||
|
</view> |
||||
|
<view class="auth-body"> |
||||
|
<view class="auth-icon">📱</view> |
||||
|
<view class="auth-text">为了提供更好的服务,需要获取您的手机号进行身份验证</view> |
||||
|
|
||||
|
<!-- 手机号授权按钮 --> |
||||
|
<button |
||||
|
class="auth-btn" |
||||
|
open-type="getPhoneNumber" |
||||
|
bindgetphonenumber="onGetPhoneNumber" |
||||
|
> |
||||
|
授权手机号 |
||||
|
</button> |
||||
|
|
||||
|
<view class="auth-tip">授权后即可完成入驻申请</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
File diff suppressed because it is too large
@ -0,0 +1,66 @@ |
|||||
|
// pages/test-tools/api-test.js
|
||||
|
Page({ |
||||
|
|
||||
|
/** |
||||
|
* 页面的初始数据 |
||||
|
*/ |
||||
|
data: { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面加载 |
||||
|
*/ |
||||
|
onLoad(options) { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面初次渲染完成 |
||||
|
*/ |
||||
|
onReady() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面显示 |
||||
|
*/ |
||||
|
onShow() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面隐藏 |
||||
|
*/ |
||||
|
onHide() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面卸载 |
||||
|
*/ |
||||
|
onUnload() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 页面相关事件处理函数--监听用户下拉动作 |
||||
|
*/ |
||||
|
onPullDownRefresh() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 页面上拉触底事件的处理函数 |
||||
|
*/ |
||||
|
onReachBottom() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 用户点击右上角分享 |
||||
|
*/ |
||||
|
onShareAppMessage() { |
||||
|
|
||||
|
} |
||||
|
}) |
||||
@ -0,0 +1,2 @@ |
|||||
|
<!--pages/test-tools/api-test.wxml--> |
||||
|
<text>pages/test-tools/api-test.wxml</text> |
||||
@ -0,0 +1,66 @@ |
|||||
|
// pages/test-tools/clear-storage.js
|
||||
|
Page({ |
||||
|
|
||||
|
/** |
||||
|
* 页面的初始数据 |
||||
|
*/ |
||||
|
data: { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面加载 |
||||
|
*/ |
||||
|
onLoad(options) { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面初次渲染完成 |
||||
|
*/ |
||||
|
onReady() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面显示 |
||||
|
*/ |
||||
|
onShow() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面隐藏 |
||||
|
*/ |
||||
|
onHide() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面卸载 |
||||
|
*/ |
||||
|
onUnload() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 页面相关事件处理函数--监听用户下拉动作 |
||||
|
*/ |
||||
|
onPullDownRefresh() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 页面上拉触底事件的处理函数 |
||||
|
*/ |
||||
|
onReachBottom() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 用户点击右上角分享 |
||||
|
*/ |
||||
|
onShareAppMessage() { |
||||
|
|
||||
|
} |
||||
|
}) |
||||
@ -0,0 +1,2 @@ |
|||||
|
<!--pages/test-tools/clear-storage.wxml--> |
||||
|
<text>pages/test-tools/clear-storage.wxml</text> |
||||
@ -0,0 +1,66 @@ |
|||||
|
// pages/test-tools/connection-test.js
|
||||
|
Page({ |
||||
|
|
||||
|
/** |
||||
|
* 页面的初始数据 |
||||
|
*/ |
||||
|
data: { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面加载 |
||||
|
*/ |
||||
|
onLoad(options) { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面初次渲染完成 |
||||
|
*/ |
||||
|
onReady() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面显示 |
||||
|
*/ |
||||
|
onShow() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面隐藏 |
||||
|
*/ |
||||
|
onHide() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面卸载 |
||||
|
*/ |
||||
|
onUnload() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 页面相关事件处理函数--监听用户下拉动作 |
||||
|
*/ |
||||
|
onPullDownRefresh() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 页面上拉触底事件的处理函数 |
||||
|
*/ |
||||
|
onReachBottom() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 用户点击右上角分享 |
||||
|
*/ |
||||
|
onShareAppMessage() { |
||||
|
|
||||
|
} |
||||
|
}) |
||||
@ -0,0 +1,2 @@ |
|||||
|
<!--pages/test-tools/connection-test.wxml--> |
||||
|
<text>pages/test-tools/connection-test.wxml</text> |
||||
@ -0,0 +1,66 @@ |
|||||
|
// pages/test-tools/fix-connection.js
|
||||
|
Page({ |
||||
|
|
||||
|
/** |
||||
|
* 页面的初始数据 |
||||
|
*/ |
||||
|
data: { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面加载 |
||||
|
*/ |
||||
|
onLoad(options) { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面初次渲染完成 |
||||
|
*/ |
||||
|
onReady() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面显示 |
||||
|
*/ |
||||
|
onShow() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面隐藏 |
||||
|
*/ |
||||
|
onHide() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面卸载 |
||||
|
*/ |
||||
|
onUnload() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 页面相关事件处理函数--监听用户下拉动作 |
||||
|
*/ |
||||
|
onPullDownRefresh() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 页面上拉触底事件的处理函数 |
||||
|
*/ |
||||
|
onReachBottom() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 用户点击右上角分享 |
||||
|
*/ |
||||
|
onShareAppMessage() { |
||||
|
|
||||
|
} |
||||
|
}) |
||||
@ -0,0 +1,2 @@ |
|||||
|
<!--pages/test-tools/fix-connection.wxml--> |
||||
|
<text>pages/test-tools/fix-connection.wxml</text> |
||||
@ -0,0 +1,66 @@ |
|||||
|
// pages/test-tools/gross-weight-tester.js
|
||||
|
Page({ |
||||
|
|
||||
|
/** |
||||
|
* 页面的初始数据 |
||||
|
*/ |
||||
|
data: { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面加载 |
||||
|
*/ |
||||
|
onLoad(options) { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面初次渲染完成 |
||||
|
*/ |
||||
|
onReady() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面显示 |
||||
|
*/ |
||||
|
onShow() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面隐藏 |
||||
|
*/ |
||||
|
onHide() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面卸载 |
||||
|
*/ |
||||
|
onUnload() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 页面相关事件处理函数--监听用户下拉动作 |
||||
|
*/ |
||||
|
onPullDownRefresh() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 页面上拉触底事件的处理函数 |
||||
|
*/ |
||||
|
onReachBottom() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 用户点击右上角分享 |
||||
|
*/ |
||||
|
onShareAppMessage() { |
||||
|
|
||||
|
} |
||||
|
}) |
||||
@ -0,0 +1,2 @@ |
|||||
|
<!--pages/test-tools/gross-weight-tester.wxml--> |
||||
|
<text>pages/test-tools/gross-weight-tester.wxml</text> |
||||
@ -0,0 +1,66 @@ |
|||||
|
// pages/test-tools/phone-test.js
|
||||
|
Page({ |
||||
|
|
||||
|
/** |
||||
|
* 页面的初始数据 |
||||
|
*/ |
||||
|
data: { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面加载 |
||||
|
*/ |
||||
|
onLoad(options) { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面初次渲染完成 |
||||
|
*/ |
||||
|
onReady() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面显示 |
||||
|
*/ |
||||
|
onShow() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面隐藏 |
||||
|
*/ |
||||
|
onHide() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面卸载 |
||||
|
*/ |
||||
|
onUnload() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 页面相关事件处理函数--监听用户下拉动作 |
||||
|
*/ |
||||
|
onPullDownRefresh() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 页面上拉触底事件的处理函数 |
||||
|
*/ |
||||
|
onReachBottom() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 用户点击右上角分享 |
||||
|
*/ |
||||
|
onShareAppMessage() { |
||||
|
|
||||
|
} |
||||
|
}) |
||||
@ -0,0 +1,2 @@ |
|||||
|
<!--pages/test-tools/phone-test.wxml--> |
||||
|
<text>pages/test-tools/phone-test.wxml</text> |
||||
@ -0,0 +1,66 @@ |
|||||
|
// pages/test-tools/test-mode-switch.js
|
||||
|
Page({ |
||||
|
|
||||
|
/** |
||||
|
* 页面的初始数据 |
||||
|
*/ |
||||
|
data: { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面加载 |
||||
|
*/ |
||||
|
onLoad(options) { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面初次渲染完成 |
||||
|
*/ |
||||
|
onReady() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面显示 |
||||
|
*/ |
||||
|
onShow() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面隐藏 |
||||
|
*/ |
||||
|
onHide() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面卸载 |
||||
|
*/ |
||||
|
onUnload() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 页面相关事件处理函数--监听用户下拉动作 |
||||
|
*/ |
||||
|
onPullDownRefresh() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 页面上拉触底事件的处理函数 |
||||
|
*/ |
||||
|
onReachBottom() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 用户点击右上角分享 |
||||
|
*/ |
||||
|
onShareAppMessage() { |
||||
|
|
||||
|
} |
||||
|
}) |
||||
@ -0,0 +1,2 @@ |
|||||
|
<!--pages/test-tools/test-mode-switch.wxml--> |
||||
|
<text>pages/test-tools/test-mode-switch.wxml</text> |
||||
@ -0,0 +1,66 @@ |
|||||
|
// pages/test/undercarriage-test.js
|
||||
|
Page({ |
||||
|
|
||||
|
/** |
||||
|
* 页面的初始数据 |
||||
|
*/ |
||||
|
data: { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面加载 |
||||
|
*/ |
||||
|
onLoad(options) { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面初次渲染完成 |
||||
|
*/ |
||||
|
onReady() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面显示 |
||||
|
*/ |
||||
|
onShow() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面隐藏 |
||||
|
*/ |
||||
|
onHide() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面卸载 |
||||
|
*/ |
||||
|
onUnload() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 页面相关事件处理函数--监听用户下拉动作 |
||||
|
*/ |
||||
|
onPullDownRefresh() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 页面上拉触底事件的处理函数 |
||||
|
*/ |
||||
|
onReachBottom() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 用户点击右上角分享 |
||||
|
*/ |
||||
|
onShareAppMessage() { |
||||
|
|
||||
|
} |
||||
|
}) |
||||
@ -0,0 +1,2 @@ |
|||||
|
<!--pages/test/undercarriage-test.wxml--> |
||||
|
<text>pages/test/undercarriage-test.wxml</text> |
||||
@ -0,0 +1,91 @@ |
|||||
|
{ |
||||
|
"setting": { |
||||
|
"es6": true, |
||||
|
"postcss": true, |
||||
|
"minified": true, |
||||
|
"uglifyFileName": false, |
||||
|
"enhance": true, |
||||
|
"sourceMap": false, |
||||
|
"packNpmRelationList": [], |
||||
|
"babelSetting": { |
||||
|
"ignore": [], |
||||
|
"disablePlugins": [], |
||||
|
"outputPath": "" |
||||
|
}, |
||||
|
"useCompilerPlugins": false, |
||||
|
"minifyWXML": true, |
||||
|
"requestDomain": [ |
||||
|
"http://localhost:3000", |
||||
|
"http://8.137.125.67:3000", |
||||
|
"http://localhost:3001", |
||||
|
"https://youniao.icu" |
||||
|
], |
||||
|
"compileWorklet": false, |
||||
|
"uploadWithSourceMap": true, |
||||
|
"packNpmManually": false, |
||||
|
"minifyWXSS": true, |
||||
|
"localPlugins": false, |
||||
|
"disableUseStrict": false, |
||||
|
"condition": false, |
||||
|
"swc": false, |
||||
|
"disableSWC": true |
||||
|
}, |
||||
|
"compileType": "miniprogram", |
||||
|
"simulatorPluginLibVersion": {}, |
||||
|
"packOptions": { |
||||
|
"ignore": [ |
||||
|
{ |
||||
|
"value": "server-example", |
||||
|
"type": "folder" |
||||
|
}, |
||||
|
{ |
||||
|
"value": "node_modules", |
||||
|
"type": "folder" |
||||
|
}, |
||||
|
{ |
||||
|
"value": ".md", |
||||
|
"type": "suffix" |
||||
|
}, |
||||
|
{ |
||||
|
"value": "test-api-fix.js", |
||||
|
"type": "file" |
||||
|
}, |
||||
|
{ |
||||
|
"value": "test-minimal.js", |
||||
|
"type": "file" |
||||
|
}, |
||||
|
{ |
||||
|
"value": "test-ports.js", |
||||
|
"type": "file" |
||||
|
}, |
||||
|
{ |
||||
|
"value": "test-server.js", |
||||
|
"type": "file" |
||||
|
}, |
||||
|
{ |
||||
|
"value": "verify-fix.js", |
||||
|
"type": "file" |
||||
|
}, |
||||
|
{ |
||||
|
"value": "verify-login.js", |
||||
|
"type": "file" |
||||
|
}, |
||||
|
{ |
||||
|
"value": "check-files.js", |
||||
|
"type": "file" |
||||
|
}, |
||||
|
{ |
||||
|
"value": "full-debug.js", |
||||
|
"type": "file" |
||||
|
}, |
||||
|
{ |
||||
|
"value": "get-openid.js", |
||||
|
"type": "file" |
||||
|
} |
||||
|
], |
||||
|
"include": [] |
||||
|
}, |
||||
|
"appid": "wx3da6ea0adf91cf0d", |
||||
|
"editorSetting": {}, |
||||
|
"libVersion": "3.10.3" |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
{ |
||||
|
"libVersion": "3.10.3", |
||||
|
"projectname": "miniprogram-x27", |
||||
|
"setting": { |
||||
|
"urlCheck": false, |
||||
|
"coverView": true, |
||||
|
"lazyloadPlaceholderEnable": false, |
||||
|
"skylineRenderEnable": false, |
||||
|
"preloadBackgroundData": false, |
||||
|
"autoAudits": false, |
||||
|
"showShadowRootInWxmlPanel": true, |
||||
|
"compileHotReLoad": true, |
||||
|
"useApiHook": true, |
||||
|
"useApiHostProcess": true, |
||||
|
"useStaticServer": false, |
||||
|
"useLanDebug": false, |
||||
|
"showES6CompileOption": false, |
||||
|
"checkInvalidKey": true, |
||||
|
"ignoreDevUnusedFiles": true, |
||||
|
"bigPackageSizeSupport": false |
||||
|
}, |
||||
|
"condition": {} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
# 微信小程序配置 |
||||
|
WECHAT_APPID=wx3da6ea0adf91cf0d |
||||
|
WECHAT_APPSECRET=78fd81bce5a2968a8e7c607ae68c4c0b |
||||
|
WECHAT_TOKEN=your-random-token |
||||
|
|
||||
|
# MySQL数据库配置(请根据您的实际环境修改) |
||||
|
# 如果是首次使用,可能需要先在MySQL中创建wechat_app数据库 |
||||
|
DB_HOST=1.95.162.61 |
||||
|
DB_PORT=3306 |
||||
|
DB_DATABASE=wechat_app |
||||
|
# 请使用您实际的MySQL用户名 |
||||
|
DB_USER=root |
||||
|
# 请使用您实际的MySQL密码 |
||||
|
# 如果MySQL的root用户有密码,请在此处填写 |
||||
|
# 如果没有密码,请保留为空字符串(DB_PASSWORD="") |
||||
|
DB_PASSWORD=schl@2025 |
||||
|
|
||||
|
# 服务器配置 |
||||
|
PORT=3003 |
||||
|
# 日志配置 |
||||
|
LOG_LEVEL=debug |
||||
|
NODE_ENV=development |
||||
|
# 详细日志记录,用于问题排查 |
||||
|
ENABLE_DETAILED_LOGGING=true |
||||
@ -0,0 +1,101 @@ |
|||||
|
# 微信小程序服务器环境变量配置示例(MySQL版本) |
||||
|
# 将此文件复制为 .env 文件并填写实际值 |
||||
|
|
||||
|
# ======================================================== |
||||
|
# 微信小程序配置 |
||||
|
# ======================================================== |
||||
|
# 从微信公众平台获取的AppID |
||||
|
WECHAT_APPID=wx1234567890abcdef |
||||
|
|
||||
|
# 从微信公众平台获取的AppSecret(请妥善保管,不要泄露) |
||||
|
WECHAT_APPSECRET=abcdef1234567890abcdef1234567890 |
||||
|
|
||||
|
# 微信消息校验Token(可选,用于消息验证) |
||||
|
WECHAT_TOKEN=your-random-token |
||||
|
|
||||
|
# ======================================================== |
||||
|
# MySQL数据库配置 |
||||
|
# ======================================================== |
||||
|
# MySQL主机地址(云数据库地址) |
||||
|
DB_HOST=your_mysql_host |
||||
|
|
||||
|
# MySQL端口(默认3306) |
||||
|
DB_PORT=3306 |
||||
|
|
||||
|
# MySQL数据库名(主数据库) |
||||
|
DB_DATABASE=wechat_app |
||||
|
|
||||
|
# MySQL用户名 |
||||
|
DB_USER=your_mysql_username |
||||
|
|
||||
|
# MySQL密码 |
||||
|
DB_PASSWORD=your_mysql_password |
||||
|
|
||||
|
# userlogin数据库名(如果需要连接) |
||||
|
DB_DATABASE_USERLOGIN=userlogin |
||||
|
|
||||
|
# MySQL连接池配置 |
||||
|
DB_MAX_CONNECTIONS=10 |
||||
|
DB_MIN_CONNECTIONS=0 |
||||
|
DB_CONNECTION_ACQUIRE_TIMEOUT=30000 |
||||
|
DB_CONNECTION_IDLE_TIMEOUT=10000 |
||||
|
|
||||
|
# ======================================================== |
||||
|
# 服务器配置 |
||||
|
# ======================================================== |
||||
|
# 服务器监听端口 |
||||
|
PORT=3000 |
||||
|
|
||||
|
# 运行环境(development/production/test) |
||||
|
NODE_ENV=development |
||||
|
|
||||
|
# 日志级别(debug/info/warn/error) |
||||
|
LOG_LEVEL=info |
||||
|
|
||||
|
# 允许的跨域来源(多个来源用逗号分隔) |
||||
|
CORS_ORIGINS=https://your-miniprogram-domain.com,http://localhost:8080 |
||||
|
|
||||
|
# ======================================================== |
||||
|
# 安全配置 |
||||
|
# ======================================================== |
||||
|
# JWT密钥(用于API认证,生成随机字符串) |
||||
|
JWT_SECRET=your-random-jwt-secret |
||||
|
|
||||
|
# JWT过期时间(秒) |
||||
|
JWT_EXPIRES_IN=86400 |
||||
|
|
||||
|
# 加密密钥(用于敏感数据加密) |
||||
|
ENCRYPTION_KEY=your-encryption-key-32-bytes-length |
||||
|
|
||||
|
# ======================================================== |
||||
|
# 微信接口配置 |
||||
|
# ======================================================== |
||||
|
# 微信API基础URL |
||||
|
WECHAT_API_BASE_URL=https://api.weixin.qq.com |
||||
|
|
||||
|
# 微信登录接口路径 |
||||
|
WECHAT_LOGIN_PATH=/sns/jscode2session |
||||
|
|
||||
|
# 微信接口请求超时时间(毫秒) |
||||
|
WECHAT_API_TIMEOUT=5000 |
||||
|
|
||||
|
# ======================================================== |
||||
|
# 开发环境配置 |
||||
|
# ======================================================== |
||||
|
# 在开发环境中启用详细错误信息 |
||||
|
DEVELOPMENT_SHOW_ERROR_DETAILS=true |
||||
|
|
||||
|
# 是否启用请求日志记录 |
||||
|
ENABLE_REQUEST_LOGGING=true |
||||
|
|
||||
|
# ======================================================== |
||||
|
# 生产环境配置 |
||||
|
# ======================================================== |
||||
|
# 是否启用Gzip压缩 |
||||
|
PRODUCTION_ENABLE_GZIP=true |
||||
|
|
||||
|
# 是否启用缓存 |
||||
|
PRODUCTION_ENABLE_CACHE=true |
||||
|
|
||||
|
# 缓存过期时间(秒) |
||||
|
PRODUCTION_CACHE_TTL=3600 |
||||
@ -0,0 +1,41 @@ |
|||||
|
const { Sequelize } = require('sequelize'); |
||||
|
require('dotenv').config(); |
||||
|
|
||||
|
// 创建数据库连接
|
||||
|
const sequelize = new Sequelize( |
||||
|
process.env.DB_DATABASE || 'wechat_app', |
||||
|
process.env.DB_USER || 'root', |
||||
|
process.env.DB_PASSWORD === undefined ? null : process.env.DB_PASSWORD, |
||||
|
{ |
||||
|
host: process.env.DB_HOST || 'localhost', |
||||
|
port: process.env.DB_PORT || 3306, |
||||
|
dialect: 'mysql', |
||||
|
timezone: '+08:00' |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
// 添加department字段到usermanagements表
|
||||
|
async function addDepartmentColumn() { |
||||
|
try { |
||||
|
// 连接数据库
|
||||
|
await sequelize.authenticate(); |
||||
|
console.log('✅ 数据库连接成功'); |
||||
|
|
||||
|
// 使用queryInterface添加字段
|
||||
|
await sequelize.getQueryInterface().addColumn('usermanagements', 'department', { |
||||
|
type: Sequelize.STRING(255), |
||||
|
defaultValue: null, |
||||
|
comment: '部门信息' |
||||
|
}); |
||||
|
|
||||
|
console.log('✅ 成功添加department字段到usermanagements表'); |
||||
|
} catch (error) { |
||||
|
console.error('❌ 添加字段失败:', error.message); |
||||
|
} finally { |
||||
|
// 关闭数据库连接
|
||||
|
await sequelize.close(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 执行函数
|
||||
|
addDepartmentColumn(); |
||||
@ -0,0 +1,143 @@ |
|||||
|
// 毛重字段(grossWeight)处理逻辑完整修复脚本
|
||||
|
// 此脚本用于统一所有API接口对grossWeight字段的处理逻辑
|
||||
|
|
||||
|
const fs = require('fs'); |
||||
|
const path = require('path'); |
||||
|
|
||||
|
// 定义配置
|
||||
|
const config = { |
||||
|
serverFilePath: path.join(__dirname, 'server-mysql.js'), |
||||
|
backupFilePath: path.join(__dirname, 'server-mysql.js.bak.final-fix-' + Date.now()), |
||||
|
logFilePath: path.join(__dirname, 'final-fix-gross-weight-log.txt') |
||||
|
}; |
||||
|
|
||||
|
// 日志函数
|
||||
|
function log(message) { |
||||
|
const timestamp = new Date().toISOString(); |
||||
|
const logMessage = '[' + timestamp + '] ' + message; |
||||
|
console.log(logMessage); |
||||
|
try { |
||||
|
fs.appendFileSync(config.logFilePath, logMessage + '\n'); |
||||
|
} catch (e) { |
||||
|
console.error('写入日志文件失败:', e.message); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 读取文件内容
|
||||
|
function readFile(filePath) { |
||||
|
try { |
||||
|
return fs.readFileSync(filePath, 'utf8'); |
||||
|
} catch (error) { |
||||
|
log('读取文件失败: ' + error.message); |
||||
|
throw error; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 写入文件内容
|
||||
|
function writeFile(filePath, content) { |
||||
|
try { |
||||
|
fs.writeFileSync(filePath, content, 'utf8'); |
||||
|
log('文件已成功写入: ' + filePath); |
||||
|
} catch (error) { |
||||
|
log('写入文件失败: ' + error.message); |
||||
|
throw error; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 创建备份文件
|
||||
|
function createBackup() { |
||||
|
try { |
||||
|
const content = readFile(config.serverFilePath); |
||||
|
writeFile(config.backupFilePath, content); |
||||
|
log('已创建备份文件: ' + config.backupFilePath); |
||||
|
} catch (error) { |
||||
|
log('创建备份文件失败: ' + error.message); |
||||
|
throw error; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 主函数
|
||||
|
function main() { |
||||
|
log('===== 开始执行毛重字段处理逻辑完整修复 ====='); |
||||
|
|
||||
|
try { |
||||
|
// 创建备份
|
||||
|
createBackup(); |
||||
|
|
||||
|
// 读取文件内容
|
||||
|
let content = readFile(config.serverFilePath); |
||||
|
|
||||
|
// 修复1: 统一中间件中的毛重处理逻辑,确保所有空值都设为5
|
||||
|
const searchPatterns = [ |
||||
|
'product.grossWeight = 0;', |
||||
|
'product.grossWeight = 0; // 空值设置为0' |
||||
|
]; |
||||
|
|
||||
|
let fixesApplied = 0; |
||||
|
searchPatterns.forEach(pattern => { |
||||
|
if (content.includes(pattern)) { |
||||
|
const originalCount = (content.match(new RegExp(pattern, 'g')) || []).length; |
||||
|
content = content.replace(new RegExp(pattern, 'g'), 'product.grossWeight = 5; // 空值设置为5'); |
||||
|
const fixedCount = (content.match(/product\.grossWeight = 5;/g) || []).length - originalCount; |
||||
|
fixesApplied += fixedCount; |
||||
|
log('修复中间件中的毛重默认值: 替换了' + fixedCount + '处'); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
if (fixesApplied > 0) { |
||||
|
log('修复1完成: 已统一所有中间件中的毛重默认值为5'); |
||||
|
} else { |
||||
|
log('修复1跳过: 所有中间件中的毛重默认值已经是5'); |
||||
|
} |
||||
|
|
||||
|
// 修复2: 在商品上传接口添加毛重处理逻辑
|
||||
|
const uploadApiSearch = 'app.post(\'/api/products/upload\', async (req, res) => {'; |
||||
|
if (content.includes(uploadApiSearch)) { |
||||
|
// 查找上传接口的位置
|
||||
|
const uploadApiStart = content.indexOf(uploadApiSearch); |
||||
|
const uploadApiEnd = content.indexOf('});', uploadApiStart) + 3; |
||||
|
const uploadApiContent = content.substring(uploadApiStart, uploadApiEnd); |
||||
|
|
||||
|
// 检查是否已经包含毛重处理逻辑
|
||||
|
if (uploadApiContent.includes('grossWeight') && uploadApiContent.includes('parseFloat')) { |
||||
|
log('修复2跳过: 商品上传接口已经包含毛重处理逻辑'); |
||||
|
} else { |
||||
|
// 查找商品数据处理的位置(在try块内)
|
||||
|
const tryBlockStart = uploadApiContent.indexOf('try {'); |
||||
|
const tryBlockEnd = uploadApiContent.lastIndexOf('} catch'); |
||||
|
|
||||
|
if (tryBlockStart !== -1 && tryBlockEnd !== -1) { |
||||
|
// 在try块开始处添加毛重处理逻辑
|
||||
|
const tryBlockContent = uploadApiContent.substring(tryBlockStart, tryBlockEnd); |
||||
|
const weightHandlingCode = `try {\n // 修复毛重字段处理逻辑\n if (req.body && req.body.productData) {\n let processedGrossWeight = 5; // 默认值为5\n if (req.body.productData.grossWeight !== null && req.body.productData.grossWeight !== undefined && req.body.productData.grossWeight !== \'\') {\n const numValue = parseFloat(req.body.productData.grossWeight);\n if (!isNaN(numValue) && isFinite(numValue)) {\n processedGrossWeight = numValue;\n }\n }\n req.body.productData.grossWeight = processedGrossWeight;\n console.log(\'修复后 - 毛重值处理: 原始值=\' + (req.body.productData.grossWeight || \'undefined\') + ', 处理后=\' + processedGrossWeight);\n }`; |
||||
|
|
||||
|
// 替换原代码
|
||||
|
const fixedUploadApiContent = uploadApiContent.replace(tryBlockContent, weightHandlingCode); |
||||
|
content = content.replace(uploadApiContent, fixedUploadApiContent); |
||||
|
log('修复2完成: 在商品上传接口添加了毛重处理逻辑'); |
||||
|
} else { |
||||
|
log('修复2失败: 无法在商品上传接口中找到try-catch块'); |
||||
|
} |
||||
|
} |
||||
|
} else { |
||||
|
log('修复2跳过: 未找到商品上传接口'); |
||||
|
} |
||||
|
|
||||
|
// 写入修复后的内容
|
||||
|
writeFile(config.serverFilePath, content); |
||||
|
|
||||
|
log('===== 毛重字段处理逻辑完整修复完成 ====='); |
||||
|
log('修复内容总结:'); |
||||
|
log('1. 统一了所有中间件中的毛重默认值为5'); |
||||
|
log('2. 在商品上传接口中添加了毛重处理逻辑,将空值设为5,有效数字转换为float类型'); |
||||
|
log('3. 创建了备份文件,以便需要时恢复'); |
||||
|
|
||||
|
} catch (error) { |
||||
|
log('修复过程中发生错误: ' + error.message); |
||||
|
log('===== 毛重字段处理逻辑完整修复失败 ====='); |
||||
|
process.exit(1); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 执行主函数
|
||||
|
main(); |
||||
@ -0,0 +1,123 @@ |
|||||
|
const fs = require('fs'); |
||||
|
const path = require('path'); |
||||
|
|
||||
|
// 读取server-mysql.js文件内容
|
||||
|
function readServerFile() { |
||||
|
return fs.readFileSync(path.join(__dirname, 'server-mysql.js'), 'utf8'); |
||||
|
} |
||||
|
|
||||
|
// 验证毛重字段处理逻辑
|
||||
|
function verifyGrossWeightHandling() { |
||||
|
try { |
||||
|
const fileContent = readServerFile(); |
||||
|
|
||||
|
// 初始化验证结果
|
||||
|
const verificationResult = { |
||||
|
totalIssues: 0, |
||||
|
successPoints: 0, |
||||
|
issues: [], |
||||
|
successDetails: [] |
||||
|
}; |
||||
|
|
||||
|
// 检查响应中间件中的/products/list接口
|
||||
|
const listMiddlewarePattern = /data\.products\s*=\s*data\.products\.map\(product\s*=>\s*\{[\s\S]*?product\.grossWeight\s*=\s*([^;]+);/; |
||||
|
const listMiddlewareMatch = fileContent.match(listMiddlewarePattern); |
||||
|
|
||||
|
if (listMiddlewareMatch && listMiddlewareMatch[1].includes('0')) { |
||||
|
verificationResult.successPoints++; |
||||
|
verificationResult.successDetails.push('✓ 响应中间件(/products/list)已正确设置空毛重默认值为0'); |
||||
|
} else { |
||||
|
verificationResult.totalIssues++; |
||||
|
verificationResult.issues.push('✗ 响应中间件(/products/list)未正确设置空毛重默认值'); |
||||
|
} |
||||
|
|
||||
|
// 检查响应中间件中的/data接口
|
||||
|
const dataMiddlewarePattern = /data\.data\.products\s*=\s*data\.data\.products\.map\(product\s*=>\s*\{[\s\S]*?product\.grossWeight\s*=\s*([^;]+);/; |
||||
|
const dataMiddlewareMatch = fileContent.match(dataMiddlewarePattern); |
||||
|
|
||||
|
if (dataMiddlewareMatch && dataMiddlewareMatch[1].includes('0')) { |
||||
|
verificationResult.successPoints++; |
||||
|
verificationResult.successDetails.push('✓ 响应中间件(/data)已正确设置空毛重默认值为0'); |
||||
|
} else { |
||||
|
verificationResult.totalIssues++; |
||||
|
verificationResult.issues.push('✗ 响应中间件(/data)未正确设置空毛重默认值'); |
||||
|
} |
||||
|
|
||||
|
// 检查商品上传接口
|
||||
|
const uploadApiPattern = /app\.post\('\/api\/products\/upload',[\s\S]*?let\s+processedGrossWeight\s*=\s*(\d+)/; |
||||
|
const uploadApiMatch = fileContent.match(uploadApiPattern); |
||||
|
|
||||
|
if (uploadApiMatch && uploadApiMatch[1] === '0') { |
||||
|
verificationResult.successPoints++; |
||||
|
verificationResult.successDetails.push('✓ 商品上传接口已正确设置空毛重默认值为0'); |
||||
|
} else { |
||||
|
verificationResult.totalIssues++; |
||||
|
verificationResult.issues.push('✗ 商品上传接口未正确设置空毛重默认值'); |
||||
|
} |
||||
|
|
||||
|
// 检查编辑商品API
|
||||
|
const editApiPattern = /parsedValue:\s*product\.grossWeight\s*===\s*''\s*\|\|\s*product\.grossWeight\s*===\s*null\s*\|\|\s*product\.grossWeight\s*===\s*undefined\s*\?\s*(\d+)/; |
||||
|
const editApiMatch = fileContent.match(editApiPattern); |
||||
|
|
||||
|
if (editApiMatch && editApiMatch[1] === '0') { |
||||
|
verificationResult.successPoints++; |
||||
|
verificationResult.successDetails.push('✓ 编辑商品API已正确设置空毛重默认值为0'); |
||||
|
} else { |
||||
|
verificationResult.totalIssues++; |
||||
|
verificationResult.issues.push('✗ 编辑商品API未正确设置空毛重默认值'); |
||||
|
} |
||||
|
|
||||
|
// 检查是否还有设置为5的地方
|
||||
|
const remaining5Pattern = /grossWeight\s*=\s*5/g; |
||||
|
const remaining5Matches = fileContent.match(remaining5Pattern); |
||||
|
|
||||
|
if (remaining5Matches && remaining5Matches.length > 0) { |
||||
|
verificationResult.totalIssues += remaining5Matches.length; |
||||
|
verificationResult.issues.push(`✗ 发现${remaining5Matches.length}处仍将毛重设置为5的地方`); |
||||
|
} else { |
||||
|
verificationResult.successPoints++; |
||||
|
verificationResult.successDetails.push('✓ 未发现仍将毛重设置为5的残留代码'); |
||||
|
} |
||||
|
|
||||
|
// 检查是否正确实现了空值返回0的逻辑
|
||||
|
const emptyValueHandlingPattern = /product\.grossWeight\s*===\s*null\s*\|\|\s*product\.grossWeight\s*===\s*undefined\s*\|\|\s*product\.grossWeight\s*===\s*''\s*\?\s*0/g; |
||||
|
const emptyValueMatches = fileContent.match(emptyValueHandlingPattern); |
||||
|
|
||||
|
if (emptyValueMatches && emptyValueMatches.length > 0) { |
||||
|
verificationResult.successPoints++; |
||||
|
verificationResult.successDetails.push(`✓ 发现${emptyValueMatches.length}处正确实现了空值返回0的逻辑`); |
||||
|
} |
||||
|
|
||||
|
// 输出验证结果
|
||||
|
console.log('\n======== 毛重字段处理逻辑全面验证结果 ========'); |
||||
|
console.log('\n成功项:'); |
||||
|
verificationResult.successDetails.forEach(detail => console.log(detail)); |
||||
|
|
||||
|
console.log('\n问题项:'); |
||||
|
if (verificationResult.issues.length === 0) { |
||||
|
console.log('✓ 未发现任何问题'); |
||||
|
} else { |
||||
|
verificationResult.issues.forEach(issue => console.log(issue)); |
||||
|
} |
||||
|
|
||||
|
console.log('\n总体评估:'); |
||||
|
if (verificationResult.totalIssues === 0) { |
||||
|
console.log('✅ 验证成功: 所有毛重字段处理逻辑已正确实现'); |
||||
|
console.log(' 已满足要求: 空值时小程序和数据库均返回0,非空值返回实际值'); |
||||
|
} else { |
||||
|
console.log(`❌ 验证失败: 发现${verificationResult.totalIssues}个问题需要修复`); |
||||
|
} |
||||
|
|
||||
|
console.log('=============================================='); |
||||
|
|
||||
|
// 设置退出码
|
||||
|
process.exit(verificationResult.totalIssues > 0 ? 1 : 0); |
||||
|
|
||||
|
} catch (error) { |
||||
|
console.error('验证过程中发生错误:', error); |
||||
|
process.exit(1); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 执行验证
|
||||
|
verifyGrossWeightHandling(); |
||||
@ -0,0 +1,155 @@ |
|||||
|
const { Sequelize } = require('sequelize'); |
||||
|
const fs = require('fs'); |
||||
|
const path = require('path'); |
||||
|
|
||||
|
// 读取环境变量
|
||||
|
const envPath = path.join(__dirname, '.env'); |
||||
|
if (fs.existsSync(envPath)) { |
||||
|
const envContent = fs.readFileSync(envPath, 'utf8'); |
||||
|
const envVars = envContent.split('\n').filter(line => line.trim() && !line.startsWith('#')); |
||||
|
envVars.forEach(line => { |
||||
|
const [key, value] = line.split('=').map(part => part.trim()); |
||||
|
process.env[key] = value; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// 数据库连接配置
|
||||
|
const sequelize = new Sequelize( |
||||
|
process.env.DB_NAME || 'wechat_app', |
||||
|
process.env.DB_USER || 'root', |
||||
|
process.env.DB_PASSWORD || '', |
||||
|
{ |
||||
|
host: process.env.DB_HOST || 'localhost', |
||||
|
port: process.env.DB_PORT || 3306, |
||||
|
dialect: 'mysql', |
||||
|
logging: false, |
||||
|
timezone: '+08:00' // 设置时区为UTC+8
|
||||
|
} |
||||
|
); |
||||
|
|
||||
|
// 定义模型 - 简化版
|
||||
|
const User = sequelize.define('User', { |
||||
|
userId: { |
||||
|
type: sequelize.Sequelize.STRING(100), |
||||
|
primaryKey: true, |
||||
|
allowNull: false |
||||
|
}, |
||||
|
nickName: sequelize.Sequelize.STRING(100), |
||||
|
phoneNumber: sequelize.Sequelize.STRING(20) |
||||
|
}, { |
||||
|
tableName: 'users', |
||||
|
timestamps: false |
||||
|
}); |
||||
|
|
||||
|
const Contact = sequelize.define('Contact', { |
||||
|
id: { |
||||
|
type: sequelize.Sequelize.INTEGER, |
||||
|
autoIncrement: true, |
||||
|
primaryKey: true |
||||
|
}, |
||||
|
userId: { |
||||
|
type: sequelize.Sequelize.STRING(100), |
||||
|
allowNull: false |
||||
|
}, |
||||
|
nickName: sequelize.Sequelize.STRING(100), |
||||
|
phoneNumber: sequelize.Sequelize.STRING(20) |
||||
|
}, { |
||||
|
tableName: 'contacts', |
||||
|
timestamps: false |
||||
|
}); |
||||
|
|
||||
|
const UserManagement = sequelize.define('UserManagement', { |
||||
|
id: { |
||||
|
type: sequelize.Sequelize.INTEGER, |
||||
|
autoIncrement: true, |
||||
|
primaryKey: true |
||||
|
}, |
||||
|
userId: { |
||||
|
type: sequelize.Sequelize.STRING(100), |
||||
|
allowNull: false |
||||
|
} |
||||
|
}, { |
||||
|
tableName: 'usermanagements', |
||||
|
timestamps: false |
||||
|
}); |
||||
|
|
||||
|
// 修复函数
|
||||
|
async function fixMissingAssociations() { |
||||
|
try { |
||||
|
console.log('========================================'); |
||||
|
console.log('开始修复用户关联表记录'); |
||||
|
console.log('========================================'); |
||||
|
|
||||
|
// 连接数据库
|
||||
|
await sequelize.authenticate(); |
||||
|
console.log('✅ 数据库连接成功'); |
||||
|
|
||||
|
// 获取所有用户
|
||||
|
const users = await User.findAll(); |
||||
|
console.log(`📊 共找到 ${users.length} 个用户记录`); |
||||
|
|
||||
|
let contactsCreated = 0; |
||||
|
let managementsCreated = 0; |
||||
|
|
||||
|
// 为每个用户检查并创建关联记录
|
||||
|
for (let i = 0; i < users.length; i++) { |
||||
|
const user = users[i]; |
||||
|
console.log(`\n🔄 处理用户 ${i + 1}/${users.length}: ${user.userId}`); |
||||
|
|
||||
|
// 检查并创建联系人记录
|
||||
|
try { |
||||
|
const existingContact = await Contact.findOne({ |
||||
|
where: { userId: user.userId } |
||||
|
}); |
||||
|
|
||||
|
if (!existingContact) { |
||||
|
await Contact.create({ |
||||
|
userId: user.userId, |
||||
|
nickName: user.nickName || '默认联系人', |
||||
|
phoneNumber: user.phoneNumber || '' |
||||
|
}); |
||||
|
console.log('✅ 创建了联系人记录'); |
||||
|
contactsCreated++; |
||||
|
} else { |
||||
|
console.log('✅ 联系人记录已存在'); |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.error('❌ 创建联系人记录失败:', error.message); |
||||
|
} |
||||
|
|
||||
|
// 检查并创建用户管理记录
|
||||
|
try { |
||||
|
const existingManagement = await UserManagement.findOne({ |
||||
|
where: { userId: user.userId } |
||||
|
}); |
||||
|
|
||||
|
if (!existingManagement) { |
||||
|
await UserManagement.create({ |
||||
|
userId: user.userId |
||||
|
}); |
||||
|
console.log('✅ 创建了用户管理记录'); |
||||
|
managementsCreated++; |
||||
|
} else { |
||||
|
console.log('✅ 用户管理记录已存在'); |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.error('❌ 创建用户管理记录失败:', error.message); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
console.log('\n========================================'); |
||||
|
console.log('修复完成!'); |
||||
|
console.log(`📈 共创建了 ${contactsCreated} 条联系人记录`); |
||||
|
console.log(`📈 共创建了 ${managementsCreated} 条用户管理记录`); |
||||
|
console.log('========================================'); |
||||
|
|
||||
|
} catch (error) { |
||||
|
console.error('❌ 修复过程中发生错误:', error); |
||||
|
} finally { |
||||
|
// 关闭数据库连接
|
||||
|
await sequelize.close(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 运行修复
|
||||
|
fixMissingAssociations(); |
||||
@ -0,0 +1,356 @@ |
|||||
|
// 注意:此文件是MongoDB版本的扩展实现,已被禁用
|
||||
|
// 数据库扩展 - 用于连接userlogin数据库并关联表
|
||||
|
const { Sequelize, DataTypes, Model } = require('sequelize'); |
||||
|
const path = require('path'); |
||||
|
require('dotenv').config({ path: path.resolve(__dirname, '.env') }); |
||||
|
|
||||
|
// 注意:不再直接导入User模型以避免循环依赖
|
||||
|
// User模型将通过setupAssociations函数的参数传入
|
||||
|
let User = null; |
||||
|
|
||||
|
// 创建到userlogin数据库的连接
|
||||
|
const sequelizeUserLogin = new Sequelize( |
||||
|
process.env.DB_DATABASE_USERLOGIN || 'userlogin', |
||||
|
process.env.DB_USER || 'root', |
||||
|
process.env.DB_PASSWORD, |
||||
|
{ |
||||
|
host: process.env.DB_HOST || 'localhost', |
||||
|
port: process.env.DB_PORT || 3306, |
||||
|
dialect: 'mysql', |
||||
|
pool: { |
||||
|
max: 10, |
||||
|
min: 0, |
||||
|
acquire: 30000, |
||||
|
idle: 10000 |
||||
|
}, |
||||
|
timezone: '+08:00' // 设置时区为UTC+8
|
||||
|
} |
||||
|
); |
||||
|
|
||||
|
// 测试userlogin数据库连接
|
||||
|
async function testUserLoginDbConnection() { |
||||
|
try { |
||||
|
await sequelizeUserLogin.authenticate(); |
||||
|
console.log('userlogin数据库连接成功'); |
||||
|
} catch (error) { |
||||
|
console.error('userlogin数据库连接失败:', error); |
||||
|
console.error('请注意:如果不需要使用userlogin数据库,可以忽略此错误'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 定义userlogin数据库中的表模型
|
||||
|
|
||||
|
// contact表模型
|
||||
|
class Contact extends Model { } |
||||
|
Contact.init({ |
||||
|
id: { |
||||
|
type: DataTypes.INTEGER, |
||||
|
autoIncrement: true, |
||||
|
primaryKey: true |
||||
|
}, |
||||
|
userId: { |
||||
|
type: DataTypes.STRING(100), |
||||
|
allowNull: false |
||||
|
}, |
||||
|
name: { |
||||
|
type: DataTypes.STRING(100), |
||||
|
allowNull: false |
||||
|
}, |
||||
|
phone: { |
||||
|
type: DataTypes.STRING(20), |
||||
|
allowNull: false |
||||
|
}, |
||||
|
email: { |
||||
|
type: DataTypes.STRING(100) |
||||
|
}, |
||||
|
address: { |
||||
|
type: DataTypes.TEXT |
||||
|
}, |
||||
|
created_at: { |
||||
|
type: DataTypes.DATE, |
||||
|
defaultValue: Sequelize.NOW |
||||
|
} |
||||
|
}, { |
||||
|
sequelize: sequelizeUserLogin, |
||||
|
modelName: 'Contact', |
||||
|
tableName: 'contact', |
||||
|
timestamps: false |
||||
|
}); |
||||
|
|
||||
|
// enterprise表模型
|
||||
|
class Enterprise extends Model { } |
||||
|
Enterprise.init({ |
||||
|
id: { |
||||
|
type: DataTypes.INTEGER, |
||||
|
autoIncrement: true, |
||||
|
primaryKey: true |
||||
|
}, |
||||
|
userId: { |
||||
|
type: DataTypes.STRING(100), |
||||
|
allowNull: false |
||||
|
}, |
||||
|
enterpriseName: { |
||||
|
type: DataTypes.STRING(255), |
||||
|
allowNull: false |
||||
|
}, |
||||
|
businessLicense: { |
||||
|
type: DataTypes.STRING(255) |
||||
|
}, |
||||
|
address: { |
||||
|
type: DataTypes.TEXT |
||||
|
}, |
||||
|
contactPerson: { |
||||
|
type: DataTypes.STRING(100) |
||||
|
}, |
||||
|
contactPhone: { |
||||
|
type: DataTypes.STRING(20) |
||||
|
}, |
||||
|
created_at: { |
||||
|
type: DataTypes.DATE, |
||||
|
defaultValue: Sequelize.NOW |
||||
|
} |
||||
|
}, { |
||||
|
sequelize: sequelizeUserLogin, |
||||
|
modelName: 'Enterprise', |
||||
|
tableName: 'enterprise', |
||||
|
timestamps: false |
||||
|
}); |
||||
|
|
||||
|
// managers表模型
|
||||
|
class Manager extends Model { } |
||||
|
Manager.init({ |
||||
|
id: { |
||||
|
type: DataTypes.INTEGER, |
||||
|
autoIncrement: true, |
||||
|
primaryKey: true |
||||
|
}, |
||||
|
userId: { |
||||
|
type: DataTypes.STRING(100), |
||||
|
allowNull: false |
||||
|
}, |
||||
|
managerName: { |
||||
|
type: DataTypes.STRING(100), |
||||
|
allowNull: false |
||||
|
}, |
||||
|
managerPhone: { |
||||
|
type: DataTypes.STRING(20), |
||||
|
allowNull: false |
||||
|
}, |
||||
|
role: { |
||||
|
type: DataTypes.STRING(50), |
||||
|
allowNull: false |
||||
|
}, |
||||
|
created_at: { |
||||
|
type: DataTypes.DATE, |
||||
|
defaultValue: Sequelize.NOW |
||||
|
} |
||||
|
}, { |
||||
|
sequelize: sequelizeUserLogin, |
||||
|
modelName: 'Manager', |
||||
|
tableName: 'managers', |
||||
|
timestamps: false |
||||
|
}); |
||||
|
|
||||
|
// publicseademand表模型
|
||||
|
class PublicSeaDemand extends Model { } |
||||
|
PublicSeaDemand.init({ |
||||
|
id: { |
||||
|
type: DataTypes.INTEGER, |
||||
|
autoIncrement: true, |
||||
|
primaryKey: true |
||||
|
}, |
||||
|
userId: { |
||||
|
type: DataTypes.STRING(100), |
||||
|
allowNull: false |
||||
|
}, |
||||
|
demandType: { |
||||
|
type: DataTypes.STRING(100), |
||||
|
allowNull: false |
||||
|
}, |
||||
|
description: { |
||||
|
type: DataTypes.TEXT |
||||
|
}, |
||||
|
status: { |
||||
|
type: DataTypes.STRING(50), |
||||
|
defaultValue: 'pending' |
||||
|
}, |
||||
|
created_at: { |
||||
|
type: DataTypes.DATE, |
||||
|
defaultValue: Sequelize.NOW |
||||
|
} |
||||
|
}, { |
||||
|
sequelize: sequelizeUserLogin, |
||||
|
modelName: 'PublicSeaDemand', |
||||
|
tableName: 'publicseademand', |
||||
|
timestamps: false |
||||
|
}); |
||||
|
|
||||
|
// rootdb表模型
|
||||
|
class RootDb extends Model { } |
||||
|
RootDb.init({ |
||||
|
id: { |
||||
|
type: DataTypes.INTEGER, |
||||
|
autoIncrement: true, |
||||
|
primaryKey: true |
||||
|
}, |
||||
|
userId: { |
||||
|
type: DataTypes.STRING(100), |
||||
|
allowNull: false |
||||
|
}, |
||||
|
dataKey: { |
||||
|
type: DataTypes.STRING(100), |
||||
|
allowNull: false |
||||
|
}, |
||||
|
dataValue: { |
||||
|
type: DataTypes.TEXT |
||||
|
}, |
||||
|
created_at: { |
||||
|
type: DataTypes.DATE, |
||||
|
defaultValue: Sequelize.NOW |
||||
|
} |
||||
|
}, { |
||||
|
sequelize: sequelizeUserLogin, |
||||
|
modelName: 'RootDb', |
||||
|
tableName: 'rootdb', |
||||
|
timestamps: false |
||||
|
}); |
||||
|
|
||||
|
// login表模型
|
||||
|
class Login extends Model { } |
||||
|
Login.init({ |
||||
|
id: { |
||||
|
type: DataTypes.INTEGER, |
||||
|
autoIncrement: true, |
||||
|
primaryKey: true |
||||
|
}, |
||||
|
userId: { |
||||
|
type: DataTypes.STRING(100), |
||||
|
allowNull: false |
||||
|
}, |
||||
|
loginTime: { |
||||
|
type: DataTypes.DATE, |
||||
|
defaultValue: Sequelize.NOW |
||||
|
}, |
||||
|
loginIp: { |
||||
|
type: DataTypes.STRING(50) |
||||
|
}, |
||||
|
deviceInfo: { |
||||
|
type: DataTypes.TEXT |
||||
|
}, |
||||
|
status: { |
||||
|
type: DataTypes.STRING(20), |
||||
|
defaultValue: 'success' |
||||
|
} |
||||
|
}, { |
||||
|
sequelize: sequelizeUserLogin, |
||||
|
modelName: 'Login', |
||||
|
tableName: 'login', |
||||
|
timestamps: false |
||||
|
}); |
||||
|
|
||||
|
// 设置模型关联关系
|
||||
|
function setupAssociations(mainUserModel) { |
||||
|
// 确保使用传入的User模型,不再依赖默认的User变量
|
||||
|
if (!mainUserModel) { |
||||
|
console.error('User模型未提供,无法设置关联关系'); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
// 关联User与Contact(一对多)
|
||||
|
// 使用唯一的别名userContacts以避免可能的冲突
|
||||
|
mainUserModel.hasMany(Contact, { |
||||
|
foreignKey: 'userId', |
||||
|
sourceKey: 'userId', |
||||
|
as: 'userContacts' |
||||
|
}); |
||||
|
|
||||
|
// 反向关联Contact与User
|
||||
|
Contact.belongsTo(mainUserModel, { |
||||
|
foreignKey: 'userId', |
||||
|
targetKey: 'userId', |
||||
|
as: 'user' |
||||
|
}); |
||||
|
|
||||
|
// 关联User与Enterprise(一对多)
|
||||
|
mainUserModel.hasMany(Enterprise, { |
||||
|
foreignKey: 'userId', |
||||
|
sourceKey: 'userId', |
||||
|
as: 'userEnterprises' |
||||
|
}); |
||||
|
|
||||
|
// 反向关联Enterprise与User
|
||||
|
Enterprise.belongsTo(mainUserModel, { |
||||
|
foreignKey: 'userId', |
||||
|
targetKey: 'userId', |
||||
|
as: 'user' |
||||
|
}); |
||||
|
|
||||
|
// 关联User与Manager(一对多)
|
||||
|
mainUserModel.hasMany(Manager, { |
||||
|
foreignKey: 'userId', |
||||
|
sourceKey: 'userId', |
||||
|
as: 'userManagers' |
||||
|
}); |
||||
|
|
||||
|
// 反向关联Manager与User
|
||||
|
Manager.belongsTo(mainUserModel, { |
||||
|
foreignKey: 'userId', |
||||
|
targetKey: 'userId', |
||||
|
as: 'user' |
||||
|
}); |
||||
|
|
||||
|
// 关联User与PublicSeaDemand(一对多)
|
||||
|
mainUserModel.hasMany(PublicSeaDemand, { |
||||
|
foreignKey: 'userId', |
||||
|
sourceKey: 'userId', |
||||
|
as: 'userPublicSeaDemands' |
||||
|
}); |
||||
|
|
||||
|
// 反向关联PublicSeaDemand与User
|
||||
|
PublicSeaDemand.belongsTo(mainUserModel, { |
||||
|
foreignKey: 'userId', |
||||
|
targetKey: 'userId', |
||||
|
as: 'user' |
||||
|
}); |
||||
|
|
||||
|
// 关联User与RootDb(一对多)
|
||||
|
mainUserModel.hasMany(RootDb, { |
||||
|
foreignKey: 'userId', |
||||
|
sourceKey: 'userId', |
||||
|
as: 'userRootDbs' |
||||
|
}); |
||||
|
|
||||
|
// 反向关联RootDb与User
|
||||
|
RootDb.belongsTo(mainUserModel, { |
||||
|
foreignKey: 'userId', |
||||
|
targetKey: 'userId', |
||||
|
as: 'user' |
||||
|
}); |
||||
|
|
||||
|
// 关联User与Login(一对多)
|
||||
|
mainUserModel.hasMany(Login, { |
||||
|
foreignKey: 'userId', |
||||
|
sourceKey: 'userId', |
||||
|
as: 'userLoginRecords' |
||||
|
}); |
||||
|
|
||||
|
console.log('已设置wechat_app数据库的User模型与userlogin数据库表的关联关系'); |
||||
|
} catch (error) { |
||||
|
console.error('设置模型关联关系时出错:', error); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 导出所有模型和连接
|
||||
|
module.exports = { |
||||
|
sequelizeUserLogin, |
||||
|
testUserLoginDbConnection, |
||||
|
Contact, |
||||
|
Enterprise, |
||||
|
Manager, |
||||
|
PublicSeaDemand, |
||||
|
RootDb, |
||||
|
Login, |
||||
|
setupAssociations, |
||||
|
User // 导出User模型(可能是实际的模型或临时模型)
|
||||
|
}; |
||||
@ -0,0 +1,175 @@ |
|||||
|
// 直接连接数据库检查productQuantity字段的脚本
|
||||
|
const Sequelize = require('sequelize'); |
||||
|
const mysql = require('mysql2/promise'); |
||||
|
|
||||
|
// 数据库连接配置
|
||||
|
const sequelize = new Sequelize( |
||||
|
'minishop', // 数据库名
|
||||
|
'root', // 用户名
|
||||
|
'password', // 密码
|
||||
|
{ |
||||
|
host: 'localhost', |
||||
|
dialect: 'mysql', |
||||
|
pool: { |
||||
|
max: 5, |
||||
|
min: 0, |
||||
|
acquire: 30000, |
||||
|
idle: 10000 |
||||
|
}, |
||||
|
timezone: '+08:00' // 设置时区为UTC+8
|
||||
|
} |
||||
|
); |
||||
|
|
||||
|
// 定义购物车模型 - 直接复制自server-mysql.js
|
||||
|
class CartItem extends Sequelize.Model {} |
||||
|
CartItem.init({ |
||||
|
id: { |
||||
|
type: Sequelize.DataTypes.INTEGER, |
||||
|
autoIncrement: true, |
||||
|
primaryKey: true |
||||
|
}, |
||||
|
userId: { |
||||
|
type: Sequelize.DataTypes.STRING(100), |
||||
|
allowNull: false |
||||
|
}, |
||||
|
productId: { |
||||
|
type: Sequelize.DataTypes.STRING(100), |
||||
|
allowNull: false |
||||
|
}, |
||||
|
productName: { |
||||
|
type: Sequelize.DataTypes.STRING(255), |
||||
|
allowNull: false |
||||
|
}, |
||||
|
specification: { |
||||
|
type: Sequelize.DataTypes.STRING(255) |
||||
|
}, |
||||
|
quantity: { |
||||
|
type: Sequelize.DataTypes.INTEGER, |
||||
|
allowNull: false, |
||||
|
defaultValue: 1 |
||||
|
}, |
||||
|
productQuantity: { |
||||
|
type: Sequelize.DataTypes.INTEGER, |
||||
|
allowNull: false, |
||||
|
defaultValue: 0 |
||||
|
}, |
||||
|
grossWeight: { |
||||
|
type: Sequelize.DataTypes.DECIMAL(10, 2) |
||||
|
}, |
||||
|
yolk: { |
||||
|
type: Sequelize.DataTypes.STRING(100) |
||||
|
}, |
||||
|
price: { |
||||
|
type: Sequelize.DataTypes.DECIMAL(10, 2) |
||||
|
}, |
||||
|
selected: { |
||||
|
type: Sequelize.DataTypes.BOOLEAN, |
||||
|
defaultValue: true |
||||
|
}, |
||||
|
added_at: { |
||||
|
type: Sequelize.DataTypes.DATE, |
||||
|
defaultValue: Sequelize.NOW |
||||
|
} |
||||
|
}, { |
||||
|
sequelize, |
||||
|
modelName: 'CartItem', |
||||
|
tableName: 'cart_items', |
||||
|
timestamps: false |
||||
|
}); |
||||
|
|
||||
|
// 检查数据库结构
|
||||
|
async function checkDatabaseStructure() { |
||||
|
console.log('开始直接检查数据库中的productQuantity字段...'); |
||||
|
|
||||
|
try { |
||||
|
// 检查连接
|
||||
|
await sequelize.authenticate(); |
||||
|
console.log('✅ 数据库连接成功'); |
||||
|
|
||||
|
// 1. 使用原始查询检查表结构
|
||||
|
console.log('\n1. 检查cart_items表结构...'); |
||||
|
const [fields, _] = await sequelize.query('DESCRIBE cart_items'); |
||||
|
|
||||
|
// 查找productQuantity字段
|
||||
|
const productQuantityField = fields.find(field => field.Field === 'productQuantity'); |
||||
|
|
||||
|
if (productQuantityField) { |
||||
|
console.log('✅ 数据库中存在productQuantity字段:'); |
||||
|
console.log(` - 类型: ${productQuantityField.Type}`); |
||||
|
console.log(` - 是否允许NULL: ${productQuantityField.Null === 'YES' ? '是' : '否'}`); |
||||
|
console.log(` - 默认值: ${productQuantityField.Default || '无'}`); |
||||
|
} else { |
||||
|
console.error('❌ 数据库中不存在productQuantity字段!'); |
||||
|
console.log('cart_items表中的所有字段:', fields.map(field => field.Field).join(', ')); |
||||
|
|
||||
|
// 如果不存在,尝试添加这个字段
|
||||
|
console.log('\n尝试添加productQuantity字段到cart_items表...'); |
||||
|
try { |
||||
|
await sequelize.query('ALTER TABLE cart_items ADD COLUMN productQuantity INT NOT NULL DEFAULT 0'); |
||||
|
console.log('✅ 成功添加productQuantity字段'); |
||||
|
} catch (addError) { |
||||
|
console.error('❌ 添加字段失败:', addError.message); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 2. 检查test_user_id的购物车数据
|
||||
|
console.log('\n2. 检查测试用户的购物车数据...'); |
||||
|
const cartItems = await CartItem.findAll({ |
||||
|
where: { |
||||
|
userId: 'test_user_id' |
||||
|
}, |
||||
|
// 明确指定返回所有字段
|
||||
|
attributes: { |
||||
|
exclude: [] // 不排除任何字段
|
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
console.log(`找到 ${cartItems.length} 条购物车记录`); |
||||
|
|
||||
|
if (cartItems.length > 0) { |
||||
|
// 显示第一条记录的所有字段
|
||||
|
console.log('\n第一条购物车记录的所有字段:'); |
||||
|
const firstItem = cartItems[0].toJSON(); |
||||
|
Object.keys(firstItem).forEach(key => { |
||||
|
console.log(` - ${key}: ${firstItem[key]}`); |
||||
|
}); |
||||
|
|
||||
|
// 特别检查productQuantity字段
|
||||
|
console.log('\nproductQuantity字段在数据中的状态:'); |
||||
|
cartItems.forEach((item, index) => { |
||||
|
const data = item.toJSON(); |
||||
|
console.log(` 记录 ${index + 1}: productQuantity = ${data.productQuantity !== undefined ? data.productQuantity : 'undefined'}`); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// 3. 尝试直接插入一条带productQuantity的记录
|
||||
|
console.log('\n3. 尝试直接插入一条带productQuantity的记录...'); |
||||
|
const testProductId = 'db_test_' + Date.now(); |
||||
|
const newItem = await CartItem.create({ |
||||
|
userId: 'test_user_id', |
||||
|
productId: testProductId, |
||||
|
productName: '数据库测试商品', |
||||
|
specification: '测试规格', |
||||
|
quantity: 2, |
||||
|
productQuantity: 10, |
||||
|
grossWeight: 1000, |
||||
|
yolk: '测试蛋黄', |
||||
|
price: 50, |
||||
|
selected: true, |
||||
|
added_at: new Date() |
||||
|
}); |
||||
|
|
||||
|
console.log('✅ 成功插入记录'); |
||||
|
console.log('插入的记录详情:', newItem.toJSON()); |
||||
|
|
||||
|
} catch (error) { |
||||
|
console.error('检查过程中发生错误:', error.message); |
||||
|
} finally { |
||||
|
// 关闭连接
|
||||
|
await sequelize.close(); |
||||
|
console.log('\n数据库连接已关闭'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 执行检查
|
||||
|
checkDatabaseStructure(); |
||||
@ -0,0 +1,86 @@ |
|||||
|
module.exports = { |
||||
|
apps: [ |
||||
|
{ |
||||
|
// 应用名称
|
||||
|
name: 'wechat-app', |
||||
|
// 要运行的脚本 - 修正为正确的启动脚本
|
||||
|
script: 'start-server.js', |
||||
|
// 运行模式 - 设置为fork模式以避免端口冲突
|
||||
|
exec_mode: 'fork', |
||||
|
// 实例数量
|
||||
|
instances: 1, |
||||
|
// 自动重启
|
||||
|
autorestart: true, |
||||
|
// 监听文件变化(开发环境可设置为true)
|
||||
|
watch: false, |
||||
|
// 内存限制,超过则重启
|
||||
|
max_memory_restart: '1G', |
||||
|
// 环境变量
|
||||
|
env: { |
||||
|
NODE_ENV: 'production', |
||||
|
PORT: 3002 |
||||
|
}, |
||||
|
env_development: { |
||||
|
NODE_ENV: 'development', |
||||
|
PORT: 3002 |
||||
|
}, |
||||
|
// 日志配置 - 重要:这些配置确保PM2正确捕获和显示所有console.log输出
|
||||
|
log_date_format: 'YYYY-MM-DD HH:mm:ss.SSS', |
||||
|
error_file: './logs/error.log', |
||||
|
out_file: './logs/output.log', |
||||
|
merge_logs: true, |
||||
|
combine_logs: false, // 确保不合并日志
|
||||
|
log_file_max_size: '10MB', |
||||
|
// 启用日志时间戳
|
||||
|
time: true, |
||||
|
// 自动重启策略
|
||||
|
min_uptime: '60s', |
||||
|
max_restarts: 10, |
||||
|
restart_delay: 1000, |
||||
|
// 环境变量文件加载
|
||||
|
// PM2会自动加载应用目录下的.env文件
|
||||
|
// 附加参数
|
||||
|
args: [], |
||||
|
// 启动超时时间
|
||||
|
kill_timeout: 5000, |
||||
|
// 启动前的钩子
|
||||
|
pre_start: "echo 'Starting wechat-miniprogram-server...'", |
||||
|
// 启动后的钩子
|
||||
|
post_start: "echo 'wechat-miniprogram-server started successfully'", |
||||
|
// 错误处理
|
||||
|
error: './logs/process-error.log', |
||||
|
// 合并stdout和stderr
|
||||
|
combine_logs: false |
||||
|
} |
||||
|
], |
||||
|
// 简化部署配置,用户可以根据实际情况修改
|
||||
|
deploy: { |
||||
|
production: { |
||||
|
user: 'your-username', |
||||
|
host: 'your-server-ip', |
||||
|
ref: 'origin/master', |
||||
|
repo: 'your-git-repository-url', |
||||
|
path: '/path/to/your/app', |
||||
|
'post-deploy': 'npm install && pm2 reload ecosystem.config.js --env production' |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
/* |
||||
|
使用说明: |
||||
|
1. 基本启动:pm2 start ecosystem.config.js |
||||
|
2. 开发环境启动:pm2 start ecosystem.config.js --env development |
||||
|
3. 查看状态:pm2 status |
||||
|
4. 查看日志:pm2 logs wechat-miniprogram-server |
||||
|
5. 监控应用:pm2 monit |
||||
|
6. 停止应用:pm2 stop wechat-miniprogram-server |
||||
|
7. 重启应用:pm2 restart wechat-miniprogram-server |
||||
|
8. 删除应用:pm2 delete wechat-miniprogram-server |
||||
|
9. 设置开机自启:pm2 startup && pm2 save |
||||
|
|
||||
|
故障排除提示: |
||||
|
- 如果应用启动失败,查看日志:pm2 logs wechat-miniprogram-server --lines 100 |
||||
|
- 检查端口占用:lsof -i :3001 或 netstat -ano | findstr :3001 |
||||
|
- 确保.env文件配置正确 |
||||
|
- 确保数据库服务正常运行 |
||||
|
*/ |
||||
@ -0,0 +1,61 @@ |
|||||
|
// 查询特定名称商品的创建者
|
||||
|
|
||||
|
const dotenv = require('dotenv'); |
||||
|
const mysql = require('mysql2/promise'); |
||||
|
const path = require('path'); |
||||
|
|
||||
|
// 加载环境变量
|
||||
|
dotenv.config({ path: path.resolve(__dirname, '.env') }); |
||||
|
|
||||
|
// 数据库连接配置
|
||||
|
const dbConfig = { |
||||
|
host: process.env.DB_HOST || 'localhost', |
||||
|
port: process.env.DB_PORT || 3306, |
||||
|
user: process.env.DB_USER || 'root', |
||||
|
password: process.env.DB_PASSWORD || '', |
||||
|
database: process.env.DB_NAME || 'wechat_app', |
||||
|
timezone: '+08:00' // 设置时区为UTC+8
|
||||
|
}; |
||||
|
|
||||
|
async function findProductCreator() { |
||||
|
let connection; |
||||
|
try { |
||||
|
// 连接数据库
|
||||
|
connection = await mysql.createConnection(dbConfig); |
||||
|
console.log('数据库连接成功'); |
||||
|
|
||||
|
// 查询名称为88888的商品及其创建者
|
||||
|
const [products] = await connection.query(` |
||||
|
SELECT p.productId, p.productName, p.sellerId, u.userId, u.nickName, u.phoneNumber |
||||
|
FROM products p |
||||
|
LEFT JOIN users u ON p.sellerId = u.userId |
||||
|
WHERE p.productName = '88888' |
||||
|
`);
|
||||
|
|
||||
|
if (products.length === 0) { |
||||
|
console.log('未找到名称为88888的商品'); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
console.log(`找到 ${products.length} 个名称为88888的商品:`); |
||||
|
products.forEach((product, index) => { |
||||
|
console.log(`\n商品 ${index + 1}:`); |
||||
|
console.log(` 商品ID: ${product.productId}`); |
||||
|
console.log(` 商品名称: ${product.productName}`); |
||||
|
console.log(` 创建者ID: ${product.sellerId}`); |
||||
|
console.log(` 创建者昵称: ${product.nickName || '未设置'}`); |
||||
|
console.log(` 创建者手机号: ${product.phoneNumber || '未设置'}`); |
||||
|
}); |
||||
|
|
||||
|
} catch (error) { |
||||
|
console.error('查询失败:', error.message); |
||||
|
} finally { |
||||
|
if (connection) { |
||||
|
await connection.end(); |
||||
|
console.log('\n数据库连接已关闭'); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 执行查询
|
||||
|
findProductCreator(); |
||||
@ -0,0 +1,85 @@ |
|||||
|
// 简单测试服务器 - 不连接数据库,专注于API接口测试和毛重字段处理
|
||||
|
const express = require('express'); |
||||
|
const bodyParser = require('body-parser'); |
||||
|
const path = require('path'); |
||||
|
|
||||
|
// 创建Express应用
|
||||
|
const app = express(); |
||||
|
const PORT = 3000; |
||||
|
|
||||
|
// 中间件
|
||||
|
app.use(bodyParser.json()); |
||||
|
|
||||
|
// 请求日志中间件
|
||||
|
app.use((req, res, next) => { |
||||
|
const now = new Date(); |
||||
|
console.log(`[${now.toISOString()}] 收到请求: ${req.method} ${req.url}`); |
||||
|
next(); |
||||
|
}); |
||||
|
|
||||
|
// 简单测试接口
|
||||
|
app.get('/api/test-connection', (req, res) => { |
||||
|
res.json({ |
||||
|
success: true, |
||||
|
message: '服务器连接测试成功', |
||||
|
timestamp: new Date().toISOString(), |
||||
|
serverInfo: { port: PORT } |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
// 商品发布接口(简化版,专注于毛重处理)
|
||||
|
app.post('/api/product/publish', (req, res) => { |
||||
|
try { |
||||
|
const { openid, product } = req.body; |
||||
|
console.log('收到商品发布请求:', { openid, product }); |
||||
|
|
||||
|
// 验证参数
|
||||
|
if (!openid || !product) { |
||||
|
return res.status(400).json({ success: false, message: '缺少必要参数' }); |
||||
|
} |
||||
|
|
||||
|
// 重点:毛重字段处理逻辑
|
||||
|
let grossWeightValue = product.grossWeight; |
||||
|
console.log('原始毛重值:', grossWeightValue, '类型:', typeof grossWeightValue); |
||||
|
|
||||
|
// 处理各种情况的毛重值
|
||||
|
if (grossWeightValue === '' || grossWeightValue === null || grossWeightValue === undefined || (typeof grossWeightValue === 'object' && grossWeightValue === null)) { |
||||
|
grossWeightValue = null; |
||||
|
console.log('毛重值为空或null,设置为null'); |
||||
|
} else { |
||||
|
// 转换为数字
|
||||
|
const numValue = Number(grossWeightValue); |
||||
|
if (!isNaN(numValue) && isFinite(numValue)) { |
||||
|
grossWeightValue = numValue; |
||||
|
console.log('毛重值成功转换为数字:', grossWeightValue); |
||||
|
} else { |
||||
|
grossWeightValue = null; |
||||
|
console.log('毛重值不是有效数字,设置为null'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 返回处理结果
|
||||
|
return res.json({ |
||||
|
success: true, |
||||
|
message: '商品发布处理成功(模拟)', |
||||
|
processedData: { |
||||
|
productName: product.productName, |
||||
|
price: product.price, |
||||
|
quantity: product.quantity, |
||||
|
grossWeight: grossWeightValue, // 返回处理后的毛重值
|
||||
|
grossWeightType: typeof grossWeightValue |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
} catch (error) { |
||||
|
console.error('发布商品失败:', error); |
||||
|
res.status(500).json({ success: false, message: '服务器错误', error: error.message }); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// 启动服务器
|
||||
|
app.listen(PORT, () => { |
||||
|
console.log(`修复版服务器运行在 http://localhost:${PORT}`); |
||||
|
console.log('测试接口: http://localhost:3000/api/test-connection'); |
||||
|
console.log('商品发布接口: POST http://localhost:3000/api/product/publish'); |
||||
|
}); |
||||
@ -0,0 +1,87 @@ |
|||||
|
// 检查并释放被占用的端口
|
||||
|
const { exec } = require('child_process'); |
||||
|
const os = require('os'); |
||||
|
|
||||
|
// 要检查的端口
|
||||
|
const PORT = 3001; |
||||
|
|
||||
|
function killProcessOnPort(port) { |
||||
|
console.log(`开始检查端口 ${port} 的占用情况...`); |
||||
|
|
||||
|
if (os.platform() === 'win32') { |
||||
|
// Windows系统
|
||||
|
exec(`netstat -ano | findstr :${port}`, (error, stdout) => { |
||||
|
if (error) { |
||||
|
console.log(`端口 ${port} 未被占用`); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const lines = stdout.trim().split('\n'); |
||||
|
if (lines.length > 0) { |
||||
|
// 提取PID
|
||||
|
const pid = lines[0].trim().split(/\s+/).pop(); |
||||
|
console.log(`发现进程 ${pid} 占用端口 ${port}`); |
||||
|
|
||||
|
// 杀死进程
|
||||
|
exec(`taskkill /F /PID ${pid}`, (killError) => { |
||||
|
if (killError) { |
||||
|
console.error(`杀死进程 ${pid} 失败:`, killError.message); |
||||
|
} else { |
||||
|
console.log(`成功杀死进程 ${pid},端口 ${port} 已释放`); |
||||
|
} |
||||
|
}); |
||||
|
} else { |
||||
|
console.log(`端口 ${port} 未被占用`); |
||||
|
} |
||||
|
}); |
||||
|
} else { |
||||
|
// Linux/Mac系统
|
||||
|
exec(`lsof -i :${port}`, (error, stdout) => { |
||||
|
if (error) { |
||||
|
console.log(`端口 ${port} 未被占用`); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const lines = stdout.trim().split('\n'); |
||||
|
if (lines.length > 1) { |
||||
|
// 提取PID
|
||||
|
const pid = lines[1].trim().split(/\s+/)[1]; |
||||
|
console.log(`发现进程 ${pid} 占用端口 ${port}`); |
||||
|
|
||||
|
// 杀死进程
|
||||
|
exec(`kill -9 ${pid}`, (killError) => { |
||||
|
if (killError) { |
||||
|
console.error(`杀死进程 ${pid} 失败:`, killError.message); |
||||
|
} else { |
||||
|
console.log(`成功杀死进程 ${pid},端口 ${port} 已释放`); |
||||
|
} |
||||
|
}); |
||||
|
} else { |
||||
|
console.log(`端口 ${port} 未被占用`); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 执行端口检查和释放
|
||||
|
killProcessOnPort(PORT); |
||||
|
|
||||
|
// 延迟2秒后再次启动服务器(如果是Windows系统)
|
||||
|
setTimeout(() => { |
||||
|
if (os.platform() === 'win32') { |
||||
|
console.log('\n正在尝试重新启动服务器...'); |
||||
|
const serverProcess = exec('node server-mysql.js'); |
||||
|
|
||||
|
serverProcess.stdout.on('data', (data) => { |
||||
|
console.log(`服务器输出: ${data}`); |
||||
|
}); |
||||
|
|
||||
|
serverProcess.stderr.on('data', (data) => { |
||||
|
console.error(`服务器错误: ${data}`); |
||||
|
}); |
||||
|
|
||||
|
serverProcess.on('close', (code) => { |
||||
|
console.log(`服务器进程退出,代码: ${code}`); |
||||
|
}); |
||||
|
} |
||||
|
}, 2000); |
||||
@ -0,0 +1,5 @@ |
|||||
|
{ |
||||
|
"timestamp": "2025-10-08T03:56:27.607Z", |
||||
|
"error": "Access denied for user 'root'@'218.88.54.38' (using password: YES)", |
||||
|
"stack": "Error: Access denied for user 'root'@'218.88.54.38' (using password: YES)\n at Object.createConnectionPromise [as createConnection] (D:\\WeichatAPP\\miniprogram-6\\server-example\\node_modules\\mysql2\\promise.js:19:31)\n at fixGrossWeightValues (D:\\WeichatAPP\\miniprogram-6\\server-example\\fix-gross-weight-values.js:26:30)\n at Object.<anonymous> (D:\\WeichatAPP\\miniprogram-6\\server-example\\fix-gross-weight-values.js:143:1)\n at Module._compile (node:internal/modules/cjs/loader:1688:14)\n at Object..js (node:internal/modules/cjs/loader:1820:10)\n at Module.load (node:internal/modules/cjs/loader:1423:32)\n at Function._load (node:internal/modules/cjs/loader:1246:12)\n at TracingChannel.traceSync (node:diagnostics_channel:322:14)\n at wrapModuleLoad (node:internal/modules/cjs/loader:235:24)\n at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:171:5)" |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
{ |
||||
|
"timestamp": "2025-10-08T03:57:52.452Z", |
||||
|
"modified": true, |
||||
|
"changes": [ |
||||
|
{ |
||||
|
"name": "商品列表API增强", |
||||
|
"applied": true |
||||
|
}, |
||||
|
{ |
||||
|
"name": "用户商品API增强", |
||||
|
"applied": false |
||||
|
}, |
||||
|
{ |
||||
|
"name": "添加毛重处理中间件", |
||||
|
"applied": true |
||||
|
} |
||||
|
], |
||||
|
"recommendations": [ |
||||
|
"重启服务器", |
||||
|
"检查前端页面使用的字段名", |
||||
|
"添加商品发布表单的毛重验证", |
||||
|
"检查前端数据处理逻辑" |
||||
|
] |
||||
|
} |
||||
@ -0,0 +1,135 @@ |
|||||
|
const fs = require('fs'); |
||||
|
const path = require('path'); |
||||
|
|
||||
|
// 日志文件路径
|
||||
|
const logFilePath = path.join(__dirname, 'logs', 'output.log'); |
||||
|
|
||||
|
// 读取并分析日志文件
|
||||
|
function analyzeGrossWeightLogs() { |
||||
|
try { |
||||
|
console.log(`正在分析日志文件: ${logFilePath}`); |
||||
|
console.log('搜索与grossWeight相关的日志记录...\n'); |
||||
|
|
||||
|
// 读取日志文件内容
|
||||
|
const logContent = fs.readFileSync(logFilePath, 'utf-8'); |
||||
|
const logLines = logContent.split('\n'); |
||||
|
|
||||
|
// 存储找到的毛重相关记录
|
||||
|
const grossWeightRecords = []; |
||||
|
const publishRequestRecords = []; |
||||
|
|
||||
|
// 搜索最近24小时的日志记录
|
||||
|
const twentyFourHoursAgo = new Date(Date.now() - 24 * 60 * 60 * 1000); |
||||
|
|
||||
|
// 遍历日志行
|
||||
|
for (let i = 0; i < logLines.length; i++) { |
||||
|
const line = logLines[i]; |
||||
|
|
||||
|
// 检查时间戳是否在最近24小时内
|
||||
|
const timestampMatch = line.match(/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})/); |
||||
|
if (timestampMatch) { |
||||
|
const logDate = new Date(timestampMatch[1]); |
||||
|
if (logDate < twentyFourHoursAgo) { |
||||
|
continue; // 跳过24小时前的日志
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 搜索与grossWeight相关的记录
|
||||
|
if (line.includes('grossWeight')) { |
||||
|
grossWeightRecords.push({ |
||||
|
line: i + 1, |
||||
|
content: line, |
||||
|
timestamp: timestampMatch ? timestampMatch[1] : '未知' |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// 搜索商品发布请求
|
||||
|
if (line.includes('/api/product/publish')) { |
||||
|
// 收集发布请求的上下文
|
||||
|
const contextLines = []; |
||||
|
// 向前收集5行
|
||||
|
for (let j = Math.max(0, i - 5); j < Math.min(logLines.length, i + 20); j++) { |
||||
|
contextLines.push(logLines[j]); |
||||
|
} |
||||
|
publishRequestRecords.push({ |
||||
|
line: i + 1, |
||||
|
content: contextLines.join('\n'), |
||||
|
timestamp: timestampMatch ? timestampMatch[1] : '未知' |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 输出分析结果
|
||||
|
console.log('===== 最近24小时毛重字段处理分析结果 =====\n'); |
||||
|
|
||||
|
console.log(`找到 ${grossWeightRecords.length} 条与grossWeight相关的日志记录\n`); |
||||
|
|
||||
|
// 显示最近的10条毛重记录
|
||||
|
console.log('最近的10条毛重处理记录:'); |
||||
|
grossWeightRecords.slice(-10).forEach((record, index) => { |
||||
|
console.log(`[${record.timestamp}] 第${record.line}行: ${record.content}`); |
||||
|
}); |
||||
|
|
||||
|
console.log('\n'); |
||||
|
|
||||
|
// 显示最近的商品发布请求及其毛重处理
|
||||
|
console.log(`找到 ${publishRequestRecords.length} 条商品发布请求记录`); |
||||
|
if (publishRequestRecords.length > 0) { |
||||
|
console.log('\n最近的商品发布请求及毛重处理详情:'); |
||||
|
const latestPublish = publishRequestRecords[publishRequestRecords.length - 1]; |
||||
|
console.log(`\n时间: ${latestPublish.timestamp}`); |
||||
|
console.log(`起始行号: ${latestPublish.line}`); |
||||
|
console.log('详细内容:'); |
||||
|
|
||||
|
// 解析请求体中的grossWeight值
|
||||
|
const requestBodyMatch = latestPublish.content.match(/请求体: \{([\s\S]*?)\}/); |
||||
|
if (requestBodyMatch) { |
||||
|
console.log(requestBodyMatch[0]); |
||||
|
|
||||
|
// 提取grossWeight值
|
||||
|
const grossWeightMatch = requestBodyMatch[0].match(/"grossWeight"\s*:\s*(null|\d+(\.\d+)?)/); |
||||
|
if (grossWeightMatch) { |
||||
|
console.log(`\n请求中的毛重值: ${grossWeightMatch[1]}`); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 查找毛重处理的相关日志
|
||||
|
const grossWeightProcessingMatch = latestPublish.content.match(/\[发布商品.*\] 原始毛重值:.*|毛重值.*设置为.*|最终处理的毛重值:.*|grossWeightStored:.*|毛重.*转换为数字/); |
||||
|
if (grossWeightProcessingMatch) { |
||||
|
console.log('\n毛重处理过程:'); |
||||
|
grossWeightProcessingMatch.forEach(processingLine => { |
||||
|
console.log(processingLine); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 生成总结
|
||||
|
console.log('\n===== 分析总结 ====='); |
||||
|
if (grossWeightRecords.length === 0) { |
||||
|
console.log('在最近24小时内没有找到与grossWeight相关的日志记录。'); |
||||
|
} else { |
||||
|
console.log(`在最近24小时内找到了 ${grossWeightRecords.length} 条与grossWeight相关的日志记录。`); |
||||
|
|
||||
|
// 简单统计
|
||||
|
const nullGrossWeightCount = grossWeightRecords.filter(r => r.content.includes('grossWeight: null')).length; |
||||
|
const zeroGrossWeightCount = grossWeightRecords.filter(r => r.content.includes('grossWeight: 0')).length; |
||||
|
const numericGrossWeightCount = grossWeightRecords.filter(r => /grossWeight:\s*\d+\.\d+/.test(r.content)).length; |
||||
|
|
||||
|
console.log(`- 毛重为null的记录数: ${nullGrossWeightCount}`); |
||||
|
console.log(`- 毛重为0的记录数: ${zeroGrossWeightCount}`); |
||||
|
console.log(`- 毛重为数字的记录数: ${numericGrossWeightCount}`); |
||||
|
} |
||||
|
|
||||
|
console.log('\n提示: 如果需要查看更多详细信息,建议直接查看日志文件或使用更专业的日志分析工具。'); |
||||
|
|
||||
|
} catch (error) { |
||||
|
console.error('分析日志时发生错误:', error.message); |
||||
|
console.log('\n建议手动查看日志文件:'); |
||||
|
console.log(`1. 打开文件: ${logFilePath}`); |
||||
|
console.log('2. 搜索关键词: grossWeight'); |
||||
|
console.log('3. 特别关注: 发布商品请求中的grossWeight值和处理过程'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 执行分析
|
||||
|
analyzeGrossWeightLogs(); |
||||
@ -0,0 +1,67 @@ |
|||||
|
require('dotenv').config(); |
||||
|
const mysql = require('mysql2/promise'); |
||||
|
|
||||
|
// 查询用户信息
|
||||
|
async function listUsers() { |
||||
|
let connection = null; |
||||
|
try { |
||||
|
console.log('连接数据库...'); |
||||
|
connection = await mysql.createConnection({ |
||||
|
host: process.env.DB_HOST || 'localhost', |
||||
|
user: process.env.DB_USER || 'root', |
||||
|
password: process.env.DB_PASSWORD === undefined ? null : process.env.DB_PASSWORD, |
||||
|
database: process.env.DB_DATABASE || 'wechat_app', |
||||
|
port: process.env.DB_PORT || 3306, |
||||
|
timezone: '+08:00' // 设置时区为UTC+8
|
||||
|
}); |
||||
|
console.log('数据库连接成功\n'); |
||||
|
|
||||
|
// 查询users表结构
|
||||
|
console.log('=== users表结构 ==='); |
||||
|
const [columns] = await connection.execute( |
||||
|
'SHOW COLUMNS FROM users' |
||||
|
); |
||||
|
console.log('字段列表:', columns.map(col => col.Field).join(', ')); |
||||
|
console.log(); |
||||
|
|
||||
|
// 查询前5个用户数据
|
||||
|
console.log('=== 用户数据 (前5个) ==='); |
||||
|
const [users] = await connection.execute( |
||||
|
'SELECT * FROM users LIMIT 5' |
||||
|
); |
||||
|
console.log(`数据库中有 ${users.length} 个用户记录`); |
||||
|
|
||||
|
if (users.length > 0) { |
||||
|
users.forEach((user, index) => { |
||||
|
console.log(`${index + 1}. 用户数据:`, user); |
||||
|
}); |
||||
|
|
||||
|
// 查找可能的openid字段
|
||||
|
const firstUser = users[0]; |
||||
|
const possibleOpenIdFields = Object.keys(firstUser).filter(key => |
||||
|
key.toLowerCase().includes('openid') || key.toLowerCase().includes('open_id') |
||||
|
); |
||||
|
|
||||
|
if (possibleOpenIdFields.length > 0) { |
||||
|
console.log('\n=== 可能的openid字段 ==='); |
||||
|
console.log('找到以下可能的openid字段:', possibleOpenIdFields.join(', ')); |
||||
|
console.log('请使用其中一个有效的字段更新测试脚本'); |
||||
|
} else { |
||||
|
console.log('\n未找到明显的openid字段,请检查users表结构后手动更新测试脚本'); |
||||
|
} |
||||
|
} else { |
||||
|
console.log('\n数据库中没有用户记录'); |
||||
|
} |
||||
|
|
||||
|
} catch (error) { |
||||
|
console.error('查询过程中发生错误:', error); |
||||
|
} finally { |
||||
|
if (connection) { |
||||
|
await connection.end(); |
||||
|
console.log('\n数据库连接已关闭'); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 运行查询
|
||||
|
listUsers(); |
||||
@ -0,0 +1,86 @@ |
|||||
|
// 日志记录模块,用于将控制台输出同时保存到文件
|
||||
|
const fs = require('fs'); |
||||
|
const path = require('path'); |
||||
|
|
||||
|
// 确保logs目录存在
|
||||
|
const logsDir = path.join(__dirname, 'logs'); |
||||
|
if (!fs.existsSync(logsDir)) { |
||||
|
fs.mkdirSync(logsDir); |
||||
|
} |
||||
|
|
||||
|
// 获取当前日期,用于日志文件名
|
||||
|
function getCurrentDate() { |
||||
|
const now = new Date(); |
||||
|
const year = now.getFullYear(); |
||||
|
const month = String(now.getMonth() + 1).padStart(2, '0'); |
||||
|
const day = String(now.getDate()).padStart(2, '0'); |
||||
|
return `${year}-${month}-${day}`; |
||||
|
} |
||||
|
|
||||
|
// 获取带时区的时间戳
|
||||
|
function getFormattedTimestamp() { |
||||
|
const now = new Date(); |
||||
|
const beijingTime = new Date(now.getTime() + 8 * 60 * 60 * 1000); |
||||
|
return beijingTime.toISOString().replace('Z', '+08:00'); |
||||
|
} |
||||
|
|
||||
|
// 写入日志到文件
|
||||
|
function writeLogToFile(level, message) { |
||||
|
const timestamp = getFormattedTimestamp(); |
||||
|
const logMessage = `[${timestamp}] [${level}] ${message}\n`; |
||||
|
const logFilePath = path.join(logsDir, `server-${getCurrentDate()}.log`); |
||||
|
|
||||
|
fs.appendFile(logFilePath, logMessage, (err) => { |
||||
|
if (err) { |
||||
|
console.error('写入日志文件失败:', err); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// 重写console.log,使其同时输出到控制台和文件
|
||||
|
const originalConsoleLog = console.log; |
||||
|
const originalConsoleError = console.error; |
||||
|
const originalConsoleWarn = console.warn; |
||||
|
const originalConsoleInfo = console.info; |
||||
|
|
||||
|
console.log = function (...args) { |
||||
|
const message = args.map(arg => |
||||
|
typeof arg === 'object' ? JSON.stringify(arg) : String(arg) |
||||
|
).join(' '); |
||||
|
|
||||
|
originalConsoleLog.apply(console, args); |
||||
|
writeLogToFile('INFO', message); |
||||
|
}; |
||||
|
|
||||
|
console.error = function (...args) { |
||||
|
const message = args.map(arg => |
||||
|
typeof arg === 'object' ? JSON.stringify(arg) : String(arg) |
||||
|
).join(' '); |
||||
|
|
||||
|
originalConsoleError.apply(console, args); |
||||
|
writeLogToFile('ERROR', message); |
||||
|
}; |
||||
|
|
||||
|
console.warn = function (...args) { |
||||
|
const message = args.map(arg => |
||||
|
typeof arg === 'object' ? JSON.stringify(arg) : String(arg) |
||||
|
).join(' '); |
||||
|
|
||||
|
originalConsoleWarn.apply(console, args); |
||||
|
writeLogToFile('WARN', message); |
||||
|
}; |
||||
|
|
||||
|
console.info = function (...args) { |
||||
|
const message = args.map(arg => |
||||
|
typeof arg === 'object' ? JSON.stringify(arg) : String(arg) |
||||
|
).join(' '); |
||||
|
|
||||
|
originalConsoleInfo.apply(console, args); |
||||
|
writeLogToFile('INFO', message); |
||||
|
}; |
||||
|
|
||||
|
module.exports = { |
||||
|
logsDir, |
||||
|
getFormattedTimestamp, |
||||
|
writeLogToFile |
||||
|
}; |
||||
@ -0,0 +1,8 @@ |
|||||
|
// 阿里云OSS配置
|
||||
|
module.exports = { |
||||
|
region: 'oss-cn-chengdu', // OSS区域,例如 'oss-cn-hangzhou'
|
||||
|
accessKeyId: 'LTAI5tRT6ReeHUdmqFpmLZi7', // 访问密钥ID
|
||||
|
accessKeySecret: 'zTnK27IAphwgCDMmyJzMUsHYxGsDBE', // 访问密钥Secret
|
||||
|
bucket: 'my-supplier-photos', // OSS存储桶名称
|
||||
|
endpoint: 'oss-cn-chengdu.aliyuncs.com' // 注意:不要在endpoint中包含bucket名称
|
||||
|
}; |
||||
@ -0,0 +1,319 @@ |
|||||
|
const fs = require('fs'); |
||||
|
const path = require('path'); |
||||
|
const { createHash } = require('crypto'); |
||||
|
const OSSClient = require('ali-oss'); |
||||
|
const ossConfig = require('./oss-config'); |
||||
|
|
||||
|
// 创建OSS客户端 - ali-oss 6.23.0版本的正确配置
|
||||
|
let client = null; |
||||
|
|
||||
|
// 初始化OSS客户端的函数
|
||||
|
function initOSSClient() { |
||||
|
try { |
||||
|
console.log('初始化OSS客户端配置:', { |
||||
|
region: ossConfig.region, |
||||
|
accessKeyId: ossConfig.accessKeyId ? '已配置' : '未配置', |
||||
|
accessKeySecret: ossConfig.accessKeySecret ? '已配置' : '未配置', |
||||
|
bucket: ossConfig.bucket, |
||||
|
endpoint: `https://${ossConfig.endpoint}` |
||||
|
}); |
||||
|
|
||||
|
client = new OSSClient({ |
||||
|
region: ossConfig.region, |
||||
|
accessKeyId: ossConfig.accessKeyId, |
||||
|
accessKeySecret: ossConfig.accessKeySecret, |
||||
|
bucket: ossConfig.bucket, |
||||
|
endpoint: `https://${ossConfig.endpoint}`, // 添加https协议前缀
|
||||
|
cname: false // 对于标准OSS域名,不需要启用cname模式
|
||||
|
}); |
||||
|
|
||||
|
return client; |
||||
|
} catch (error) { |
||||
|
console.error('初始化OSS客户端失败:', error); |
||||
|
throw error; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 延迟初始化,避免应用启动时就连接OSS
|
||||
|
function getOSSClient() { |
||||
|
if (!client) { |
||||
|
return initOSSClient(); |
||||
|
} |
||||
|
return client; |
||||
|
} |
||||
|
|
||||
|
class OssUploader { |
||||
|
/** |
||||
|
* 上传文件到OSS |
||||
|
* @param {String} filePath - 本地文件路径 |
||||
|
* @param {String} folder - OSS上的文件夹路径 |
||||
|
* @param {String} fileType - 文件类型,默认为'image' |
||||
|
* @returns {Promise<String>} - 上传后的文件URL |
||||
|
*/ |
||||
|
/** |
||||
|
* 计算文件的MD5哈希值 |
||||
|
* @param {String} filePath - 文件路径 |
||||
|
* @returns {Promise<String>} - MD5哈希值 |
||||
|
*/ |
||||
|
static async getFileHash(filePath) { |
||||
|
return new Promise((resolve, reject) => { |
||||
|
const hash = createHash('md5'); |
||||
|
const stream = fs.createReadStream(filePath); |
||||
|
|
||||
|
stream.on('error', reject); |
||||
|
stream.on('data', chunk => hash.update(chunk)); |
||||
|
stream.on('end', () => resolve(hash.digest('hex'))); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 计算缓冲区的MD5哈希值 |
||||
|
* @param {Buffer} buffer - 数据缓冲区 |
||||
|
* @returns {String} - MD5哈希值 |
||||
|
*/ |
||||
|
static getBufferHash(buffer) { |
||||
|
return createHash('md5').update(buffer).digest('hex'); |
||||
|
} |
||||
|
|
||||
|
static async uploadFile(filePath, folder = 'images', fileType = 'image') { |
||||
|
try { |
||||
|
console.log('【OSS上传】开始上传文件:', filePath, '到目录:', folder); |
||||
|
|
||||
|
// 确保文件存在
|
||||
|
const fileExists = await fs.promises.access(filePath).then(() => true).catch(() => false); |
||||
|
if (!fileExists) { |
||||
|
throw new Error(`文件不存在: ${filePath}`); |
||||
|
} |
||||
|
|
||||
|
// 获取文件扩展名
|
||||
|
const extname = path.extname(filePath).toLowerCase(); |
||||
|
if (!extname) { |
||||
|
throw new Error(`无法获取文件扩展名: ${filePath}`); |
||||
|
} |
||||
|
|
||||
|
// 基于文件内容计算MD5哈希值,实现文件级去重
|
||||
|
console.log('【文件去重】开始计算文件哈希值...'); |
||||
|
const fileHash = await this.getFileHash(filePath); |
||||
|
console.log(`【文件去重】文件哈希计算完成: ${fileHash}`); |
||||
|
|
||||
|
// 使用哈希值作为文件名,确保相同内容的文件生成相同的文件名
|
||||
|
const uniqueFilename = `${fileHash}${extname}`; |
||||
|
const ossFilePath = `${folder}/${fileType}/${uniqueFilename}`; |
||||
|
|
||||
|
console.log(`【文件去重】使用基于内容的文件名: ${uniqueFilename}`); |
||||
|
|
||||
|
// 获取OSS客户端,延迟初始化
|
||||
|
const ossClient = getOSSClient(); |
||||
|
|
||||
|
// 测试OSS连接
|
||||
|
try { |
||||
|
await ossClient.list({ max: 1 }); |
||||
|
console.log('OSS连接测试成功'); |
||||
|
} catch (connError) { |
||||
|
console.error('OSS连接测试失败,尝试重新初始化客户端:', connError.message); |
||||
|
// 尝试重新初始化客户端
|
||||
|
initOSSClient(); |
||||
|
} |
||||
|
|
||||
|
// 检查OSS客户端配置
|
||||
|
console.log('【OSS上传】OSS配置检查 - region:', ossClient.options.region, 'bucket:', ossClient.options.bucket); |
||||
|
|
||||
|
// 上传文件,明确设置为公共读权限
|
||||
|
console.log(`开始上传文件到OSS: ${filePath} -> ${ossFilePath}`); |
||||
|
const result = await ossClient.put(ossFilePath, filePath, { |
||||
|
headers: { |
||||
|
'x-oss-object-acl': 'public-read' // 确保文件可以公开访问
|
||||
|
}, |
||||
|
acl: 'public-read' // 额外设置ACL参数,确保文件公开可读
|
||||
|
}); |
||||
|
console.log(`文件上传成功: ${result.url}`); |
||||
|
console.log('已设置文件为公共读权限'); |
||||
|
|
||||
|
// 返回完整的文件URL
|
||||
|
return result.url; |
||||
|
} catch (error) { |
||||
|
console.error('【OSS上传】上传文件失败:', error); |
||||
|
console.error('【OSS上传】错误详情:', error.message); |
||||
|
console.error('【OSS上传】错误栈:', error.stack); |
||||
|
throw error; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 从缓冲区上传文件到OSS |
||||
|
* @param {Buffer} buffer - 文件数据缓冲区 |
||||
|
* @param {String} filename - 文件名 |
||||
|
* @param {String} folder - OSS上的文件夹路径 |
||||
|
* @param {String} fileType - 文件类型,默认为'image' |
||||
|
* @returns {Promise<String>} - 上传后的文件URL |
||||
|
*/ |
||||
|
static async uploadBuffer(buffer, filename, folder = 'images', fileType = 'image') { |
||||
|
try { |
||||
|
// 获取文件扩展名
|
||||
|
const extname = path.extname(filename).toLowerCase(); |
||||
|
if (!extname) { |
||||
|
throw new Error(`无法获取文件扩展名: ${filename}`); |
||||
|
} |
||||
|
|
||||
|
// 基于文件内容计算MD5哈希值,实现文件级去重
|
||||
|
console.log('【文件去重】开始计算缓冲区哈希值...'); |
||||
|
const bufferHash = this.getBufferHash(buffer); |
||||
|
console.log(`【文件去重】缓冲区哈希计算完成: ${bufferHash}`); |
||||
|
|
||||
|
// 使用哈希值作为文件名,确保相同内容的文件生成相同的文件名
|
||||
|
const uniqueFilename = `${bufferHash}${extname}`; |
||||
|
const ossFilePath = `${folder}/${fileType}/${uniqueFilename}`; |
||||
|
|
||||
|
console.log(`【文件去重】使用基于内容的文件名: ${uniqueFilename}`); |
||||
|
|
||||
|
// 获取OSS客户端,延迟初始化
|
||||
|
const ossClient = getOSSClient(); |
||||
|
|
||||
|
// 上传缓冲区,明确设置为公共读权限
|
||||
|
console.log(`开始上传缓冲区到OSS: ${ossFilePath}`); |
||||
|
const result = await ossClient.put(ossFilePath, buffer, { |
||||
|
headers: { |
||||
|
'x-oss-object-acl': 'public-read' // 确保文件可以公开访问
|
||||
|
}, |
||||
|
acl: 'public-read' // 额外设置ACL参数,确保文件公开可读
|
||||
|
}); |
||||
|
console.log(`缓冲区上传成功: ${result.url}`); |
||||
|
console.log('已设置文件为公共读权限'); |
||||
|
|
||||
|
// 返回完整的文件URL
|
||||
|
return result.url; |
||||
|
} catch (error) { |
||||
|
console.error('OSS缓冲区上传失败:', error); |
||||
|
console.error('OSS缓冲区上传错误详情:', error.message); |
||||
|
console.error('OSS缓冲区上传错误栈:', error.stack); |
||||
|
throw error; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 批量上传文件到OSS |
||||
|
* @param {Array<String>} filePaths - 本地文件路径数组 |
||||
|
* @param {String} folder - OSS上的文件夹路径 |
||||
|
* @param {String} fileType - 文件类型,默认为'image' |
||||
|
* @returns {Promise<Array<String>>} - 上传后的文件URL数组 |
||||
|
*/ |
||||
|
static async uploadFiles(filePaths, folder = 'images', fileType = 'image') { |
||||
|
try { |
||||
|
const uploadPromises = filePaths.map(filePath => |
||||
|
this.uploadFile(filePath, folder, fileType) |
||||
|
); |
||||
|
|
||||
|
const urls = await Promise.all(uploadPromises); |
||||
|
console.log(`批量上传完成,成功上传${urls.length}个文件`); |
||||
|
return urls; |
||||
|
} catch (error) { |
||||
|
console.error('OSS批量上传失败:', error); |
||||
|
throw error; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 删除OSS上的文件 |
||||
|
* @param {String} ossFilePath - OSS上的文件路径 |
||||
|
* @returns {Promise<Boolean>} - 删除是否成功 |
||||
|
*/ |
||||
|
static async deleteFile(ossFilePath) { |
||||
|
try { |
||||
|
console.log(`【OSS删除】开始删除OSS文件: ${ossFilePath}`); |
||||
|
const ossClient = getOSSClient(); |
||||
|
|
||||
|
// 【新增】记录OSS客户端配置信息(隐藏敏感信息)
|
||||
|
console.log(`【OSS删除】OSS客户端配置 - region: ${ossClient.options.region}, bucket: ${ossClient.options.bucket}`); |
||||
|
|
||||
|
const result = await ossClient.delete(ossFilePath); |
||||
|
console.log(`【OSS删除】OSS文件删除成功: ${ossFilePath}`, result); |
||||
|
return true; |
||||
|
} catch (error) { |
||||
|
console.error('【OSS删除】OSS文件删除失败:', ossFilePath, '错误:', error.message); |
||||
|
console.error('【OSS删除】错误详情:', error); |
||||
|
|
||||
|
// 【增强日志】详细分析错误类型
|
||||
|
console.log('【OSS删除】=== 错误详细分析开始 ==='); |
||||
|
console.log('【OSS删除】错误名称:', error.name); |
||||
|
console.log('【OSS删除】错误代码:', error.code); |
||||
|
console.log('【OSS删除】HTTP状态码:', error.status); |
||||
|
console.log('【OSS删除】请求ID:', error.requestId); |
||||
|
console.log('【OSS删除】主机ID:', error.hostId); |
||||
|
|
||||
|
// 【关键检查】判断是否为权限不足错误
|
||||
|
const isPermissionError = |
||||
|
error.code === 'AccessDenied' || |
||||
|
error.status === 403 || |
||||
|
error.message.includes('permission') || |
||||
|
error.message.includes('AccessDenied') || |
||||
|
error.message.includes('do not have write permission'); |
||||
|
|
||||
|
if (isPermissionError) { |
||||
|
console.error('【OSS删除】❌ 确认是权限不足错误!'); |
||||
|
console.error('【OSS删除】❌ 当前AccessKey缺少删除文件的权限'); |
||||
|
console.error('【OSS删除】❌ 请检查RAM策略是否包含 oss:DeleteObject 权限'); |
||||
|
console.error('【OSS删除】❌ 建议在RAM中授予 AliyunOSSFullAccess 或自定义删除权限'); |
||||
|
} |
||||
|
|
||||
|
console.log('【OSS删除】=== 错误详细分析结束 ==='); |
||||
|
|
||||
|
// 如果文件不存在,也算删除成功
|
||||
|
if (error.code === 'NoSuchKey' || error.status === 404) { |
||||
|
console.log(`【OSS删除】文件不存在,视为删除成功: ${ossFilePath}`); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
// 【新增】对于权限错误,提供更友好的错误信息
|
||||
|
if (isPermissionError) { |
||||
|
const permissionError = new Error(`OSS删除权限不足: ${error.message}`); |
||||
|
permissionError.code = 'OSS_ACCESS_DENIED'; |
||||
|
permissionError.originalError = error; |
||||
|
throw permissionError; |
||||
|
} |
||||
|
|
||||
|
throw error; |
||||
|
} |
||||
|
} |
||||
|
/** |
||||
|
* 获取OSS配置信息 |
||||
|
* @returns {Object} - OSS配置信息 |
||||
|
*/ |
||||
|
static getConfig() { |
||||
|
return { |
||||
|
region: ossConfig.region, |
||||
|
bucket: ossConfig.bucket, |
||||
|
endpoint: ossConfig.endpoint |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 测试OSS连接 |
||||
|
* @returns {Promise<Object>} - 连接测试结果 |
||||
|
*/ |
||||
|
static async testConnection() { |
||||
|
try { |
||||
|
console.log('【OSS连接测试】开始测试OSS连接...'); |
||||
|
const ossClient = getOSSClient(); |
||||
|
|
||||
|
// 执行简单的list操作来验证连接
|
||||
|
const result = await ossClient.list({ max: 1 }); |
||||
|
console.log('【OSS连接测试】连接成功,存储桶中有', result.objects.length, '个对象'); |
||||
|
|
||||
|
return { |
||||
|
success: true, |
||||
|
message: 'OSS连接成功', |
||||
|
region: ossClient.options.region, |
||||
|
bucket: ossClient.options.bucket |
||||
|
}; |
||||
|
} catch (error) { |
||||
|
console.error('【OSS连接测试】连接失败:', error.message); |
||||
|
return { |
||||
|
success: false, |
||||
|
message: `OSS连接失败: ${error.message}`, |
||||
|
error: error.message |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = OssUploader; |
||||
File diff suppressed because it is too large
@ -0,0 +1,35 @@ |
|||||
|
{ |
||||
|
"name": "wechat-miniprogram-server", |
||||
|
"version": "1.0.0", |
||||
|
"description": "微信小程序服务器示例", |
||||
|
"main": "server-mysql.js", |
||||
|
"scripts": { |
||||
|
"start": "node start-server.js", |
||||
|
"dev": "nodemon server-mysql.js", |
||||
|
"logs-pm2": "pm2 logs wechat-app", |
||||
|
"view-logs": "powershell \"Get-Content logs/server-*.log -Tail 100\"", |
||||
|
"view-output-log": "powershell \"Get-Content logs/output.log -Tail 100\"", |
||||
|
"monitor-logs": "powershell \"Get-Content logs/server-*.log -Wait -Tail 50\"", |
||||
|
"restart-pm2": "pm2 restart ecosystem.config.js", |
||||
|
"start-pm2": "pm2 start ecosystem.config.js", |
||||
|
"stop-pm2": "pm2 stop ecosystem.config.js", |
||||
|
"view-pm2-status": "pm2 status", |
||||
|
"install-pm2-startup": "npm install -g pm2-windows-startup && pm2-startup install", |
||||
|
"save-pm2-config": "pm2 save", |
||||
|
"setup-autostart": "npm run start-pm2 && npm run save-pm2-config" |
||||
|
}, |
||||
|
"dependencies": { |
||||
|
"ali-oss": "^6.23.0", |
||||
|
"axios": "^1.13.2", |
||||
|
"body-parser": "^1.20.2", |
||||
|
"dotenv": "^17.2.3", |
||||
|
"express": "^4.21.2", |
||||
|
"form-data": "^4.0.4", |
||||
|
"multer": "^2.0.2", |
||||
|
"mysql2": "^3.6.5", |
||||
|
"sequelize": "^6.35.2" |
||||
|
}, |
||||
|
"devDependencies": { |
||||
|
"nodemon": "^3.0.1" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,236 @@ |
|||||
|
#!/usr/bin/env node
|
||||
|
const { execSync, exec } = require('child_process'); |
||||
|
const fs = require('fs'); |
||||
|
const path = require('path'); |
||||
|
|
||||
|
// 检查端口是否被占用
|
||||
|
function isPortTaken(port) { |
||||
|
return new Promise((resolve) => { |
||||
|
// 在Linux上使用lsof检查端口占用
|
||||
|
try { |
||||
|
execSync(`lsof -i :${port}`, { stdio: 'ignore' }); |
||||
|
resolve(true); |
||||
|
} catch (error) { |
||||
|
resolve(false); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// 查找占用端口的进程ID
|
||||
|
function findProcessUsingPort(port) { |
||||
|
try { |
||||
|
const output = execSync(`lsof -i :${port} | grep LISTEN`, { encoding: 'utf8' }); |
||||
|
const lines = output.trim().split('\n'); |
||||
|
if (lines.length > 0) { |
||||
|
const parts = lines[0].trim().split(/\s+/); |
||||
|
return { pid: parts[1], process: parts[0], user: parts[2] }; |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.log(`未找到占用端口 ${port} 的进程`); |
||||
|
} |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
// 停止指定进程
|
||||
|
function stopProcess(pid) { |
||||
|
try { |
||||
|
execSync(`kill -9 ${pid}`); |
||||
|
console.log(`成功停止进程 ${pid}`); |
||||
|
return true; |
||||
|
} catch (error) { |
||||
|
console.error(`停止进程 ${pid} 失败:`, error.message); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 修改PM2配置文件中的端口
|
||||
|
function updatePM2ConfigFile(newPort) { |
||||
|
const configPath = path.join(__dirname, 'ecosystem.config.js'); |
||||
|
try { |
||||
|
let content = fs.readFileSync(configPath, 'utf8'); |
||||
|
|
||||
|
// 备份配置文件
|
||||
|
const backupPath = configPath + '.bak.' + Date.now(); |
||||
|
fs.writeFileSync(backupPath, content); |
||||
|
console.log(`已创建配置备份: ${backupPath}`); |
||||
|
|
||||
|
// 修改production环境端口
|
||||
|
content = content.replace(/PORT:\s*3001/g, `PORT: ${newPort}`); |
||||
|
|
||||
|
fs.writeFileSync(configPath, content); |
||||
|
console.log(`已将PM2配置中的端口修改为: ${newPort}`); |
||||
|
return true; |
||||
|
} catch (error) { |
||||
|
console.error('修改PM2配置文件失败:', error.message); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 重启PM2应用
|
||||
|
function restartPM2App() { |
||||
|
try { |
||||
|
execSync('pm2 restart ecosystem.config.js', { stdio: 'inherit' }); |
||||
|
return true; |
||||
|
} catch (error) { |
||||
|
console.error('重启PM2应用失败:', error.message); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 主函数
|
||||
|
async function main() { |
||||
|
console.log('=== 微信小程序后端服务 - 端口冲突修复工具 ===\n'); |
||||
|
|
||||
|
const originalPort = 3001; |
||||
|
let newPort = 3001; |
||||
|
|
||||
|
// 检查原始端口是否被占用
|
||||
|
const isOriginalPortTaken = await isPortTaken(originalPort); |
||||
|
|
||||
|
if (isOriginalPortTaken) { |
||||
|
console.log(`发现端口 ${originalPort} 被占用!`); |
||||
|
|
||||
|
// 查找占用进程
|
||||
|
const processInfo = findProcessUsingPort(originalPort); |
||||
|
if (processInfo) { |
||||
|
console.log(`占用端口的进程信息:`); |
||||
|
console.log(`- 进程ID: ${processInfo.pid}`); |
||||
|
console.log(`- 进程名称: ${processInfo.process}`); |
||||
|
console.log(`- 用户: ${processInfo.user}`); |
||||
|
|
||||
|
// 询问是否停止该进程
|
||||
|
const readline = require('readline').createInterface({ |
||||
|
input: process.stdin, |
||||
|
output: process.stdout |
||||
|
}); |
||||
|
|
||||
|
readline.question(`是否停止该进程以释放端口 ${originalPort}? (y/n): `, (answer) => { |
||||
|
readline.close(); |
||||
|
|
||||
|
if (answer.toLowerCase() === 'y') { |
||||
|
if (stopProcess(processInfo.pid)) { |
||||
|
console.log(`端口 ${originalPort} 已被释放,正在重启应用...`); |
||||
|
restartPM2App(); |
||||
|
} |
||||
|
} else { |
||||
|
// 查找可用端口
|
||||
|
console.log('正在查找可用端口...'); |
||||
|
let foundPort = false; |
||||
|
|
||||
|
for (let i = 3002; i <= 3100; i++) { |
||||
|
execSync(`lsof -i :${i}`, { stdio: 'ignore' }, (error) => { |
||||
|
if (error) { |
||||
|
newPort = i; |
||||
|
foundPort = true; |
||||
|
|
||||
|
// 修改PM2配置并重启
|
||||
|
if (updatePM2ConfigFile(newPort)) { |
||||
|
console.log(`已切换到可用端口: ${newPort}`); |
||||
|
console.log('正在重启PM2应用...'); |
||||
|
restartPM2App(); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
if (foundPort) break; |
||||
|
} |
||||
|
|
||||
|
if (!foundPort) { |
||||
|
console.error('未找到可用端口,请手动指定一个未被占用的端口。'); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} else { |
||||
|
console.error('无法确定占用端口的进程,请手动检查端口占用情况。'); |
||||
|
} |
||||
|
} else { |
||||
|
console.log(`端口 ${originalPort} 未被占用,检查应用状态...`); |
||||
|
|
||||
|
// 检查PM2应用状态
|
||||
|
try { |
||||
|
const statusOutput = execSync('pm2 status wechat-app', { encoding: 'utf8' }); |
||||
|
console.log(statusOutput); |
||||
|
|
||||
|
// 提示用户重启应用
|
||||
|
const readline = require('readline').createInterface({ |
||||
|
input: process.stdin, |
||||
|
output: process.stdout |
||||
|
}); |
||||
|
|
||||
|
readline.question('是否需要重启应用? (y/n): ', (answer) => { |
||||
|
readline.close(); |
||||
|
|
||||
|
if (answer.toLowerCase() === 'y') { |
||||
|
restartPM2App(); |
||||
|
} else { |
||||
|
console.log('操作已取消。'); |
||||
|
} |
||||
|
}); |
||||
|
} catch (error) { |
||||
|
console.error('检查PM2应用状态失败:', error.message); |
||||
|
console.log('建议尝试手动重启应用: pm2 restart wechat-app'); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 提供非交互式修复选项
|
||||
|
function provideNonInteractiveFix() { |
||||
|
console.log('\n=== 非交互式修复选项 ==='); |
||||
|
console.log('1. 强制释放3001端口并重启应用'); |
||||
|
console.log('2. 使用备用端口3004并更新配置'); |
||||
|
console.log('3. 查看当前端口占用情况'); |
||||
|
|
||||
|
const readline = require('readline').createInterface({ |
||||
|
input: process.stdin, |
||||
|
output: process.stdout |
||||
|
}); |
||||
|
|
||||
|
readline.question('请选择修复方式 (1-3): ', (answer) => { |
||||
|
readline.close(); |
||||
|
|
||||
|
switch (answer) { |
||||
|
case '1': |
||||
|
const processInfo = findProcessUsingPort(3001); |
||||
|
if (processInfo && stopProcess(processInfo.pid)) { |
||||
|
console.log('正在重启应用...'); |
||||
|
restartPM2App(); |
||||
|
} else { |
||||
|
console.log('端口未被占用或无法停止占用进程'); |
||||
|
} |
||||
|
break; |
||||
|
case '2': |
||||
|
if (updatePM2ConfigFile(3004)) { |
||||
|
console.log('正在重启应用...'); |
||||
|
restartPM2App(); |
||||
|
} |
||||
|
break; |
||||
|
case '3': |
||||
|
try { |
||||
|
console.log('端口占用情况:'); |
||||
|
execSync('netstat -tuln | grep 300', { stdio: 'inherit' }); |
||||
|
} catch (error) { |
||||
|
console.log('未找到相关端口占用信息'); |
||||
|
} |
||||
|
break; |
||||
|
default: |
||||
|
console.log('无效选项,操作已取消。'); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// 运行主程序
|
||||
|
main().catch(err => { |
||||
|
console.error('修复过程中发生错误:', err); |
||||
|
provideNonInteractiveFix(); |
||||
|
}); |
||||
|
|
||||
|
// 提供帮助信息
|
||||
|
setTimeout(() => { |
||||
|
console.log('\n如果自动修复失败,可以尝试以下手动解决方案:'); |
||||
|
console.log('1. 检查端口占用: lsof -i :3001 或 netstat -ano | findstr :3001'); |
||||
|
console.log('2. 停止占用进程: kill -9 [进程ID]'); |
||||
|
console.log('3. 或者修改PM2配置使用其他端口:'); |
||||
|
console.log(' - 编辑 ecosystem.config.js 文件'); |
||||
|
console.log(' - 将 PORT: 3001 修改为其他可用端口'); |
||||
|
console.log(' - 保存并运行: pm2 restart ecosystem.config.js'); |
||||
|
}, 1000); |
||||
@ -0,0 +1,71 @@ |
|||||
|
// 查询数据库中的用户和商品信息
|
||||
|
require('dotenv').config(); |
||||
|
const { Sequelize } = require('sequelize'); |
||||
|
|
||||
|
// 创建数据库连接
|
||||
|
const sequelize = new Sequelize( |
||||
|
process.env.DB_DATABASE || 'wechat_app', |
||||
|
process.env.DB_USER || 'root', |
||||
|
process.env.DB_PASSWORD === undefined ? null : process.env.DB_PASSWORD, |
||||
|
{ |
||||
|
host: process.env.DB_HOST || 'localhost', |
||||
|
port: process.env.DB_PORT || 3306, |
||||
|
dialect: 'mysql', |
||||
|
timezone: '+08:00' // 设置时区为UTC+8
|
||||
|
} |
||||
|
); |
||||
|
|
||||
|
// 执行查询
|
||||
|
async function queryDatabase() { |
||||
|
try { |
||||
|
// 测试连接
|
||||
|
await sequelize.authenticate(); |
||||
|
console.log('✅ 数据库连接成功'); |
||||
|
|
||||
|
// 查询用户信息
|
||||
|
const users = await sequelize.query('SELECT * FROM users LIMIT 10', { type: sequelize.QueryTypes.SELECT }); |
||||
|
console.log('\n👥 用户列表:'); |
||||
|
console.log(users.map(u => ({ |
||||
|
id: u.id, |
||||
|
openid: u.openid, |
||||
|
userId: u.userId, |
||||
|
type: u.type |
||||
|
}))); |
||||
|
|
||||
|
// 查询商品信息,特别是拒绝状态的商品
|
||||
|
const products = await sequelize.query( |
||||
|
'SELECT productId, sellerId, productName, status, rejectReason, created_at FROM products LIMIT 20', |
||||
|
{ type: sequelize.QueryTypes.SELECT } |
||||
|
); |
||||
|
|
||||
|
console.log('\n🛒 商品列表:'); |
||||
|
console.log(products.map(p => ({ |
||||
|
productId: p.productId, |
||||
|
sellerId: p.sellerId, |
||||
|
productName: p.productName, |
||||
|
status: p.status, |
||||
|
rejectReason: p.rejectReason, |
||||
|
created_at: p.created_at |
||||
|
}))); |
||||
|
|
||||
|
// 特别列出拒绝状态的商品
|
||||
|
const rejectedProducts = products.filter(p => p.status === 'rejected'); |
||||
|
console.log('\n❌ 审核拒绝的商品:'); |
||||
|
console.log(rejectedProducts.map(p => ({ |
||||
|
productId: p.productId, |
||||
|
sellerId: p.sellerId, |
||||
|
productName: p.productName, |
||||
|
status: p.status, |
||||
|
rejectReason: p.rejectReason |
||||
|
}))); |
||||
|
|
||||
|
} catch (error) { |
||||
|
console.error('❌ 查询失败:', error.message); |
||||
|
} finally { |
||||
|
// 关闭连接
|
||||
|
await sequelize.close(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 运行查询
|
||||
|
queryDatabase(); |
||||
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue