第一次提交:微信小程序前端最新完整代码
31
.eslintrc.js
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Eslint config file
|
||||
* Documentation: https://eslint.org/docs/user-guide/configuring/
|
||||
* Install the Eslint extension before using this feature.
|
||||
*/
|
||||
module.exports = {
|
||||
env: {
|
||||
es6: true,
|
||||
browser: true,
|
||||
node: true,
|
||||
},
|
||||
ecmaFeatures: {
|
||||
modules: true,
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018,
|
||||
sourceType: 'module',
|
||||
},
|
||||
globals: {
|
||||
wx: true,
|
||||
App: true,
|
||||
Page: true,
|
||||
getCurrentPages: true,
|
||||
getApp: true,
|
||||
Component: true,
|
||||
requirePlugin: true,
|
||||
requireMiniProgram: true,
|
||||
},
|
||||
// extends: 'eslint:recommended',
|
||||
rules: {},
|
||||
}
|
||||
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
node_modules/
|
||||
miniprogram_npm/
|
||||
project.private.config.json
|
||||
.DS_Store
|
||||
*.log
|
||||
3
.vs/ProjectSettings.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"CurrentProjectSetting": null
|
||||
}
|
||||
7
.vs/VSWorkspaceState.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"ExpandedNodes": [
|
||||
""
|
||||
],
|
||||
"SelectedNode": "\\app.js",
|
||||
"PreviewInSolutionExplorer": false
|
||||
}
|
||||
BIN
.vs/a_long_dianjing/v17/.wsuo
Normal file
37
.vs/a_long_dianjing/v17/DocumentLayout.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"Version": 1,
|
||||
"WorkspaceRootPath": "C:\\\u5FAE\u4FE1\u5F00\u53D1\u5DE5\u5177\\\u4EE3\u7801\\a_long_dianjing\\",
|
||||
"Documents": [
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\\u5FAE\u4FE1\u5F00\u53D1\u5DE5\u5177\\\u4EE3\u7801\\a_long_dianjing\\app.js||{14D17961-FE51-464D-9111-C4AF11D7D99A}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:app.js||{14D17961-FE51-464D-9111-C4AF11D7D99A}"
|
||||
}
|
||||
],
|
||||
"DocumentGroupContainers": [
|
||||
{
|
||||
"Orientation": 0,
|
||||
"VerticalTabListWidth": 256,
|
||||
"DocumentGroups": [
|
||||
{
|
||||
"DockedWidth": 200,
|
||||
"SelectedChildIndex": 0,
|
||||
"Children": [
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 0,
|
||||
"Title": "app.js",
|
||||
"DocumentMoniker": "C:\\\u5FAE\u4FE1\u5F00\u53D1\u5DE5\u5177\\\u4EE3\u7801\\a_long_dianjing\\app.js",
|
||||
"RelativeDocumentMoniker": "app.js",
|
||||
"ToolTip": "C:\\\u5FAE\u4FE1\u5F00\u53D1\u5DE5\u5177\\\u4EE3\u7801\\a_long_dianjing\\app.js",
|
||||
"RelativeToolTip": "app.js",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001646|",
|
||||
"WhenOpened": "2026-01-30T05:12:55.655Z",
|
||||
"EditorCaption": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
.vs/slnx.sqlite
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 GoEasy
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
44
_backup_pre_redesign/REVERT.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# 大改备份与恢复说明
|
||||
|
||||
改造日期:2026-06-09
|
||||
|
||||
## 如何一键恢复改造前(若已手动备份)
|
||||
|
||||
将 `_backup_pre_redesign/` 下对应文件夹覆盖回项目即可(见各子目录)。
|
||||
|
||||
## 本次改造改动文件清单
|
||||
|
||||
### 新增
|
||||
- `utils/primary-role.js` — 三端固定 primaryRole
|
||||
- `utils/avatar.js` — 统一默认头像
|
||||
|
||||
### TabBar
|
||||
- `custom-tab-bar/index.js`
|
||||
- `custom-tab-bar/index.wxml`
|
||||
- `custom-tab-bar/index.wxss`
|
||||
|
||||
### 打手合一中心(主战场)
|
||||
- `pages/dashouduan/dashouduan.js`
|
||||
- `pages/dashouduan/dashouduan.wxml`
|
||||
- `pages/dashouduan/dashouduan.wxss`
|
||||
- `pages/dashouduan/dashouduan.json`
|
||||
|
||||
### 启动与 IM
|
||||
- `app.js`
|
||||
- `utils/chat-core.js`
|
||||
|
||||
### 老板端 / 认证
|
||||
- `pages/wode/wode.wxml`
|
||||
- `pages/renzheng/renzheng.js`
|
||||
- `utils/role-tab-bar.js`
|
||||
|
||||
### 废弃独立 Tab(路由保留,自动跳转打手中心)
|
||||
- `pages/guanshiduan/guanshiduan.js` — onLoad redirect
|
||||
- `pages/zuzhangduan/zuzhangduan.js` — onLoad redirect
|
||||
|
||||
## 改造要点
|
||||
1. 去掉 TabBar「切换身份」
|
||||
2. 三端固定:normal / dashou / shangjia
|
||||
3. 管事+组长功能合并进 dashouduan
|
||||
4. 刷新按钮一次调用:dashouxinxi + guanshixinxi + zuzhangxinxi
|
||||
5. IM 打手端统一 Ds 前缀连接
|
||||
417
_backup_pre_redesign/app.js
Normal file
@@ -0,0 +1,417 @@
|
||||
// app.js - 稳健生产版本(修复 GoEasy 初始化错误 + 配置100%加载)
|
||||
import GoEasy from './static/lib/goeasy-2.13.24.esm.min';
|
||||
const ChatCore = require('./utils/chat-core');
|
||||
import { check } from './utils/phone-auth';
|
||||
|
||||
|
||||
// ===== 角色 → 默认首页(与 custom-tab-bar 完全一致) =====
|
||||
const roleDefaultPage = {
|
||||
normal: '/pages/index/index',
|
||||
dashou: '/pages/dashouduan/dashouduan',
|
||||
shangjia: '/pages/shangjiaduan/shangjiaduan',
|
||||
guanshi: '/pages/guanshiduan/guanshiduan',
|
||||
zuzhang: '/pages/zuzhangduan/zuzhangduan',
|
||||
kaoheguan: '/pages/kaoheguan/kaoheguan'
|
||||
};
|
||||
|
||||
App({
|
||||
globalData: {
|
||||
|
||||
hasShownPopupOnColdStart: false,
|
||||
|
||||
apiBaseUrl: 'https://www.abas.asia/hqhd',
|
||||
ossImageUrl: '',
|
||||
morentouxiang: '',
|
||||
dashouguize: '',
|
||||
|
||||
shangpinliebiao: [],
|
||||
shangpinzhuanqu: [],
|
||||
shangpinleixing: [],
|
||||
shangpinlunbo: [],
|
||||
shangpingonggao: '',
|
||||
lunbozhanwei: '/images/lunbozhanwei.jpg',
|
||||
|
||||
dingdanTiaoshu: {
|
||||
daifuwu: 0,
|
||||
fuwuzhong: 0,
|
||||
yiwancheng: 0,
|
||||
yituikuan: 0
|
||||
},
|
||||
|
||||
dashouqun: '',
|
||||
dashouqunid: '',
|
||||
guanshiqun: '',
|
||||
guanshiqunid: '',
|
||||
|
||||
appId:'wx0e4be86faac4a8d1',
|
||||
|
||||
shangjiastatus: 0,
|
||||
dashoustatus: 0,
|
||||
guanshistatus: 0,
|
||||
dashouNicheng: '',
|
||||
zhanghaoStatus: null,
|
||||
dashouzhuangtai: null,
|
||||
yongjin: null,
|
||||
zonge: null,
|
||||
yajin: null,
|
||||
chenghao: '',
|
||||
jinfen: null,
|
||||
clumber: [{
|
||||
huiyuanid: '',
|
||||
huiyuanming: '',
|
||||
daoqi: ''
|
||||
}],
|
||||
chengjiaoliang: null,
|
||||
zaixianZhuangtai: null,
|
||||
|
||||
guanshi: {
|
||||
nicheng: '',
|
||||
uid: '',
|
||||
touxiang: '',
|
||||
gszhstatus: '',
|
||||
yaoqingzongshu: 0,
|
||||
fenyongzonge: '0.00',
|
||||
fenyongtixian: '0.00'
|
||||
},
|
||||
|
||||
shangjia: {
|
||||
sjzhzhuangtai: '',
|
||||
sjyue: '',
|
||||
fadanzong: null,
|
||||
tuikuanzong: null,
|
||||
riliushui: '',
|
||||
yueliushui: ''
|
||||
},
|
||||
|
||||
liaotian_liebiao: [],
|
||||
xshenfen: 1,
|
||||
|
||||
goEasyConfig: null,
|
||||
kefuConfig: {
|
||||
link: '',
|
||||
enterpriseId: ''
|
||||
},
|
||||
|
||||
cosConfig: {
|
||||
bucket: '',
|
||||
region: 'ap-shanghai',
|
||||
uploadPathPrefix: 'order/'
|
||||
},
|
||||
|
||||
goEasyConnection: {
|
||||
status: 'disconnected',
|
||||
userId: '',
|
||||
identityType: '',
|
||||
lastConnectTime: 0,
|
||||
autoReconnect: true,
|
||||
reconnectAttempts: 0,
|
||||
maxReconnectAttempts: 5,
|
||||
heartbeatInterval: null,
|
||||
cacheKeys: {
|
||||
savedConnection: 'savedGoEasyConnection',
|
||||
userId: 'goEasyUserId',
|
||||
identityType: 'currentGoEasyIdentity',
|
||||
connectTime: 'goEasyConnectTime'
|
||||
},
|
||||
config: {
|
||||
heartbeatInterval: 20000,
|
||||
reconnectDelay: 3000,
|
||||
offlineTimeout: 30000,
|
||||
cacheValidityHours: 12
|
||||
}
|
||||
},
|
||||
|
||||
messageManager: {
|
||||
unreadTotal: 0,
|
||||
unreadMap: {},
|
||||
latestMessages: [],
|
||||
notificationQueue: [],
|
||||
notificationVisible: false,
|
||||
lastNotificationTime: 0,
|
||||
notificationCooldown: 3000,
|
||||
tabBarIndex: 2,
|
||||
showTabBarBadge: true,
|
||||
customTabBar: true,
|
||||
tabBarBadgeText: 0,
|
||||
soundEnabled: true,
|
||||
vibrationEnabled: true,
|
||||
doNotDisturb: false,
|
||||
doNotDisturbStart: '22:00',
|
||||
doNotDisturbEnd: '08:00',
|
||||
notificationStyle: {
|
||||
position: 'top',
|
||||
duration: 3000,
|
||||
backgroundColor: '#00f7ff',
|
||||
textColor: '#ffffff',
|
||||
borderRadius: '16rpx',
|
||||
boxShadow: '0 10rpx 30rpx rgba(0, 247, 255, 0.3)'
|
||||
},
|
||||
currentNotification: null,
|
||||
cacheKeys: {
|
||||
messageSettings: 'messageSettings',
|
||||
unreadTotal: 'messageUnreadTotal',
|
||||
latestMessages: 'latestMessages',
|
||||
notificationMuted: 'notificationMuted'
|
||||
}
|
||||
},
|
||||
|
||||
pageState: {
|
||||
currentPage: '',
|
||||
isInChatPage: false,
|
||||
currentChatId: '',
|
||||
lastPageUpdate: 0
|
||||
},
|
||||
|
||||
eventListeners: {},
|
||||
debugMode: false,
|
||||
currentUser: null,
|
||||
currentRole: 'normal'
|
||||
},
|
||||
|
||||
// ========== 核心启动流程 ==========
|
||||
async onLaunch() {
|
||||
// ---------- ① 立即隐藏“返回首页”按钮 ----------
|
||||
wx.hideHomeButton();
|
||||
wx.onAppRoute(() => {
|
||||
wx.hideHomeButton();
|
||||
});
|
||||
|
||||
// ---------- ② 恢复角色并跳转 ----------
|
||||
const savedRole = wx.getStorageSync('currentRole') || 'normal';
|
||||
this.globalData.currentRole = savedRole;
|
||||
|
||||
const targetPage = roleDefaultPage[savedRole] || '/pages/index/index';
|
||||
if (savedRole !== 'normal' || targetPage !== '/pages/index/index') {
|
||||
setTimeout(() => {
|
||||
wx.reLaunch({ url: targetPage });
|
||||
}, 0);
|
||||
}
|
||||
|
||||
// ---------- ③ 立刻应用本地缓存配置 ----------
|
||||
const cachedConfig = this.readConfigFromStorage();
|
||||
if (cachedConfig) {
|
||||
this.applyDynamicConfig(cachedConfig);
|
||||
}
|
||||
|
||||
// ---------- ④ 初始化基础设施 ----------
|
||||
this.initGoEasyWithConfig(); // 只有appkey有效时才真正初始化
|
||||
ChatCore.initGlobalMessageSystem(this);
|
||||
this.initCurrentUser();
|
||||
|
||||
// ---------- ⑤ 建立连接 ----------
|
||||
const saved = this.getSavedConnection();
|
||||
console.log('【启动】缓存身份:', saved ? saved.identityType : '无',
|
||||
'userId:', saved ? saved.userId : '无');
|
||||
setTimeout(() => {
|
||||
this.ensureConnection();
|
||||
}, 300);
|
||||
|
||||
// ---------- ⑥ 后台静默获取最新配置 ----------
|
||||
this.fetchConfigSafely()
|
||||
.then(latestConfig => {
|
||||
this.saveConfigToStorage(latestConfig);
|
||||
this.applyDynamicConfig(latestConfig);
|
||||
// 重新尝试初始化 GoEasy(如果之前因缺少appkey而跳过,现在会成功)
|
||||
this.initGoEasyWithConfig();
|
||||
console.log('远程配置更新完成');
|
||||
})
|
||||
.catch(err => {
|
||||
console.warn('远程配置获取失败,继续使用本地缓存', err);
|
||||
});
|
||||
|
||||
// ---------- ⑦ 手机号认证检查 ----------
|
||||
try {
|
||||
const needAuth = await check();
|
||||
if (needAuth) {
|
||||
wx.reLaunch({ url: '/pages/phone-auth/phone-auth' });
|
||||
return;
|
||||
}
|
||||
} catch (e) {}
|
||||
},
|
||||
|
||||
// ========== 连接管理方法 ==========
|
||||
getSavedConnection() {
|
||||
try {
|
||||
const saved = wx.getStorageSync(
|
||||
this.globalData.goEasyConnection.cacheKeys.savedConnection
|
||||
);
|
||||
return saved || null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
ensureConnection() {
|
||||
const conn = this.globalData.goEasyConnection;
|
||||
const userId = wx.getStorageSync(conn.cacheKeys.userId);
|
||||
if (userId && conn.status !== 'connected') {
|
||||
console.log('尝试建立GoEasy连接,userId:', userId);
|
||||
// 此处可调用实际的连接逻辑,目前仅做标记
|
||||
}
|
||||
},
|
||||
|
||||
// ========== 配置缓存读写 ==========
|
||||
CONFIG_CACHE_KEY: 'app_dynamic_config',
|
||||
|
||||
readConfigFromStorage() {
|
||||
try {
|
||||
const data = wx.getStorageSync(this.CONFIG_CACHE_KEY);
|
||||
if (data && typeof data === 'object') {
|
||||
return data;
|
||||
}
|
||||
} catch (e) {}
|
||||
return null;
|
||||
},
|
||||
|
||||
saveConfigToStorage(config) {
|
||||
try {
|
||||
if (config && typeof config === 'object') {
|
||||
wx.setStorageSync(this.CONFIG_CACHE_KEY, config);
|
||||
}
|
||||
} catch (e) {}
|
||||
},
|
||||
|
||||
// ========== 安全获取远程配置(永不报错) ==========
|
||||
fetchConfigSafely() {
|
||||
return new Promise((resolve) => {
|
||||
wx.request({
|
||||
url: this.globalData.apiBaseUrl + '/peizhi/qdpzhq',
|
||||
method: 'POST',
|
||||
header: { 'content-type': 'application/json' },
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200 && res.data && res.data.code === 0 && res.data.data) {
|
||||
resolve(res.data.data);
|
||||
} else {
|
||||
console.warn('远程配置接口返回异常,使用已有配置');
|
||||
resolve(this.readConfigFromStorage() || {});
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.warn('远程配置网络请求失败,使用已有配置', err);
|
||||
resolve(this.readConfigFromStorage() || {});
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// ========== 原有方法(保留,仅增强) ==========
|
||||
fetchDynamicConfig() {
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.request({
|
||||
url: this.globalData.apiBaseUrl + '/peizhi/qdpzhq',
|
||||
method: 'POST',
|
||||
header: { 'content-type': 'application/json' },
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200 && res.data && res.data.code === 0) {
|
||||
resolve(res.data.data);
|
||||
} else {
|
||||
reject(new Error(res.data?.msg || '接口返回错误'));
|
||||
}
|
||||
},
|
||||
fail: reject
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
applyDynamicConfig(config) {
|
||||
if (!config || typeof config !== 'object') return;
|
||||
|
||||
if (config.cos) {
|
||||
this.globalData.ossImageUrl = config.cos.ossImageUrl || '';
|
||||
this.globalData.cosConfig = {
|
||||
bucket: config.cos.bucket || '',
|
||||
region: config.cos.region || 'ap-shanghai',
|
||||
uploadPathPrefix: config.cos.uploadPathPrefix || 'order/'
|
||||
};
|
||||
}
|
||||
|
||||
if (config.goEasy) {
|
||||
this.globalData.goEasyConfig = {
|
||||
host: config.goEasy.host || 'hangzhou.goeasy.io',
|
||||
appkey: config.goEasy.appkey || ''
|
||||
};
|
||||
}
|
||||
|
||||
if (config.otherConfig) {
|
||||
this.globalData.morentouxiang = config.otherConfig.morentouxiang || '';
|
||||
this.globalData.dashouguize = config.otherConfig.dashouguize || '';
|
||||
}
|
||||
|
||||
if (config.kefu) {
|
||||
this.globalData.kefuConfig = {
|
||||
link: config.kefu.link || '',
|
||||
enterpriseId: config.kefu.enterpriseId || ''
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// 🔥 关键修复:只在有效 appkey 时初始化,且避免重复初始化
|
||||
initGoEasyWithConfig() {
|
||||
const cfg = this.globalData.goEasyConfig;
|
||||
if (!cfg || !cfg.appkey) {
|
||||
console.warn('GoEasy appkey 未配置,聊天功能将在配置就绪后自动恢复');
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果已经初始化过,就不再重复创建实例
|
||||
if (wx.goEasy && wx.GoEasy) {
|
||||
console.log('GoEasy 已初始化,跳过');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
wx.goEasy = GoEasy.getInstance({
|
||||
host: cfg.host || 'hangzhou.goeasy.io',
|
||||
appkey: cfg.appkey,
|
||||
modules: ['im', 'pubsub']
|
||||
});
|
||||
wx.GoEasy = GoEasy;
|
||||
console.log('GoEasy 初始化成功');
|
||||
} catch (error) {
|
||||
console.error('GoEasy 初始化失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
initCurrentUser() {
|
||||
const uid = wx.getStorageSync('uid');
|
||||
if (uid) {
|
||||
this.globalData.currentUser = {
|
||||
id: uid,
|
||||
name: '用户' + uid.substring(0, 6),
|
||||
avatar: this.globalData.ossImageUrl + this.globalData.morentouxiang
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
emitEvent(eventName, data) {
|
||||
if (this.globalData.eventListeners[eventName]) {
|
||||
this.globalData.eventListeners[eventName].forEach(callback => {
|
||||
try { callback(data); } catch (error) { console.error(error); }
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/** 切换当前角色并通知 custom-tab-bar 立即刷新(Storage + globalData + 事件) */
|
||||
setCurrentRole(role) {
|
||||
if (!role) return;
|
||||
this.globalData.currentRole = role;
|
||||
wx.setStorageSync('currentRole', role);
|
||||
this.emitEvent('currentRoleChanged', { role });
|
||||
},
|
||||
|
||||
on(eventName, callback) {
|
||||
if (!this.globalData.eventListeners[eventName]) {
|
||||
this.globalData.eventListeners[eventName] = [];
|
||||
}
|
||||
this.globalData.eventListeners[eventName].push(callback);
|
||||
},
|
||||
|
||||
off(eventName, callback) {
|
||||
if (this.globalData.eventListeners[eventName]) {
|
||||
const index = this.globalData.eventListeners[eventName].indexOf(callback);
|
||||
if (index > -1) {
|
||||
this.globalData.eventListeners[eventName].splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
268
_backup_pre_redesign/chat-core.js
Normal file
@@ -0,0 +1,268 @@
|
||||
// utils/chat-core.js - 最终角标稳定版
|
||||
import GoEasy from '../static/lib/goeasy-2.13.24.esm.min';
|
||||
import { jianquanxian } from './imAuth/jianquanxian';
|
||||
|
||||
let _convListener = null;
|
||||
let _privateListener = null;
|
||||
let _groupListener = null;
|
||||
let _notificationHandler = null;
|
||||
|
||||
function initGlobalMessageSystem(app) {
|
||||
app.ensureConnection = () => ensureConnection(app);
|
||||
app.loadConversations = () => loadConversations(app);
|
||||
app.disconnectGoEasy = () => disconnectGoEasy(app);
|
||||
app.switchRoleAndReconnect = (newRole) => switchRoleAndReconnect(app, newRole);
|
||||
app.updateUnreadCount = (count) => updateTabBarBadge(app, count || 0);
|
||||
|
||||
try {
|
||||
const muted = wx.getStorageSync('notificationMuted');
|
||||
if (muted !== undefined) app.globalData.messageManager.notificationMuted = muted;
|
||||
} catch(e) {}
|
||||
|
||||
initAppStateListener(app);
|
||||
initNetworkListener(app);
|
||||
ensureConnection(app);
|
||||
}
|
||||
|
||||
function getCurrentUserId() {
|
||||
return (wx.goEasy && wx.goEasy.im && wx.goEasy.im.userId) || null;
|
||||
}
|
||||
|
||||
async function ensureConnection(app) {
|
||||
const role = app.globalData.currentRole || 'normal';
|
||||
const uid = wx.getStorageSync('uid');
|
||||
if (!uid) return;
|
||||
|
||||
const prefixMap = { normal: 'Boss', dashou: 'Ds', shangjia: 'Sj', guanshi: 'Gs', zuzhang: 'Zz', kaoheguan: 'Kh' };
|
||||
const prefix = prefixMap[role] || 'Boss';
|
||||
const targetUserId = prefix + uid;
|
||||
|
||||
const status = wx.goEasy.getConnectionStatus ? wx.goEasy.getConnectionStatus() : 'disconnected';
|
||||
if (status === 'connected' || status === 'reconnected') {
|
||||
const currentUserId = getCurrentUserId();
|
||||
if (currentUserId === targetUserId) {
|
||||
registerGlobalListeners(app);
|
||||
await subscribeAllGroups(app);
|
||||
loadConversations(app);
|
||||
registerNotification(app);
|
||||
// 强制立即更新一次角标,避免切后台后不显示
|
||||
setTimeout(() => { loadConversations(app); }, 500);
|
||||
return;
|
||||
} else {
|
||||
wx.goEasy.disconnect();
|
||||
}
|
||||
}
|
||||
connectGoEasy(app, targetUserId, role);
|
||||
}
|
||||
|
||||
function connectGoEasy(app, userId, identityType) {
|
||||
const currentUser = app.globalData.currentUser || {
|
||||
id: wx.getStorageSync('uid'),
|
||||
name: '用户' + (wx.getStorageSync('uid') || '').substring(0, 6),
|
||||
avatar: app.globalData.ossImageUrl + app.globalData.morentouxiang
|
||||
};
|
||||
wx.goEasy.connect({
|
||||
id: userId,
|
||||
data: {
|
||||
name: currentUser.name,
|
||||
avatar: currentUser.avatar || (app.globalData.ossImageUrl + app.globalData.morentouxiang),
|
||||
identityType: identityType
|
||||
},
|
||||
onSuccess: async () => {
|
||||
wx.setStorageSync('savedGoEasyConnection', JSON.stringify({
|
||||
userId, identityType, lastConnectTime: Date.now(), status: 'connected'
|
||||
}));
|
||||
registerGlobalListeners(app);
|
||||
await subscribeAllGroups(app);
|
||||
loadConversations(app);
|
||||
registerNotification(app);
|
||||
},
|
||||
onFailed: (error) => {
|
||||
console.error('GoEasy连接失败', error);
|
||||
if (error.code === 408) {
|
||||
registerGlobalListeners(app);
|
||||
loadConversations(app);
|
||||
registerNotification(app);
|
||||
} else {
|
||||
wx.showToast({ title: '连接失败,请重试', icon: 'none' });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function subscribeAllGroups(app) {
|
||||
return new Promise((resolve) => {
|
||||
if (!wx.goEasy || !wx.goEasy.im) return resolve();
|
||||
wx.goEasy.im.latestConversations({
|
||||
onSuccess: (result) => {
|
||||
const conversations = result?.content?.conversations || result?.conversations || [];
|
||||
const groupIds = conversations.filter(c => c.type === 'group' && c.groupId).map(c => c.groupId);
|
||||
if (groupIds.length && wx.goEasy.im.subscribeGroup) {
|
||||
wx.goEasy.im.subscribeGroup({
|
||||
groupIds: groupIds,
|
||||
onSuccess: () => resolve(),
|
||||
onFailed: () => resolve()
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
onFailed: () => resolve()
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function registerGlobalListeners(app) {
|
||||
if (!wx.goEasy || !wx.goEasy.im) return;
|
||||
if (_convListener) {
|
||||
wx.goEasy.im.off(wx.GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, _convListener);
|
||||
}
|
||||
if (_privateListener) {
|
||||
wx.goEasy.im.off(wx.GoEasy.IM_EVENT.PRIVATE_MESSAGE_RECEIVED, _privateListener);
|
||||
}
|
||||
if (_groupListener) {
|
||||
wx.goEasy.im.off(wx.GoEasy.IM_EVENT.GROUP_MESSAGE_RECEIVED, _groupListener);
|
||||
}
|
||||
|
||||
_convListener = (result) => {
|
||||
const unreadTotal = result.unreadTotal || 0;
|
||||
updateTabBarBadge(app, unreadTotal);
|
||||
app.emitEvent('conversationsUpdated', result);
|
||||
};
|
||||
wx.goEasy.im.on(wx.GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, _convListener);
|
||||
|
||||
_privateListener = () => { loadConversations(app); };
|
||||
wx.goEasy.im.on(wx.GoEasy.IM_EVENT.PRIVATE_MESSAGE_RECEIVED, _privateListener);
|
||||
|
||||
_groupListener = () => { loadConversations(app); };
|
||||
wx.goEasy.im.on(wx.GoEasy.IM_EVENT.GROUP_MESSAGE_RECEIVED, _groupListener);
|
||||
}
|
||||
|
||||
function loadConversations(app) {
|
||||
if (!getCurrentUserId()) return;
|
||||
wx.goEasy.im.latestConversations({
|
||||
onSuccess: (result) => {
|
||||
const unreadTotal = result.unreadTotal || 0;
|
||||
updateTabBarBadge(app, unreadTotal);
|
||||
app.emitEvent('conversationsUpdated', result);
|
||||
subscribeAllGroups(app);
|
||||
},
|
||||
onFailed: (err) => console.error('加载会话失败', err)
|
||||
});
|
||||
}
|
||||
|
||||
function updateTabBarBadge(app, unreadCount) {
|
||||
const badgeText = unreadCount > 0 ? (unreadCount > 99 ? '99+' : String(unreadCount)) : '';
|
||||
app.globalData.messageManager.unreadTotal = unreadCount;
|
||||
app.emitEvent('tabBarBadgeChanged', { badgeText, showRedDot: unreadCount > 0 });
|
||||
// 直接更新组件,确保角标立即显示
|
||||
try {
|
||||
const pages = getCurrentPages();
|
||||
if (pages.length) {
|
||||
const tabBar = pages[pages.length-1].selectComponent('#custom-tab-bar');
|
||||
if (tabBar && tabBar.setData) tabBar.setData({ badgeText });
|
||||
// 兼容 getTabBar 方式
|
||||
const page = pages[pages.length-1];
|
||||
if (page.getTabBar) {
|
||||
const tabBarInstance = page.getTabBar();
|
||||
if (tabBarInstance && tabBarInstance.setData) tabBarInstance.setData({ badgeText });
|
||||
}
|
||||
}
|
||||
} catch(e) {}
|
||||
wx.setStorageSync('messageUnreadTotal', unreadCount);
|
||||
}
|
||||
|
||||
function registerNotification(app) {
|
||||
if (!wx.goEasy || !wx.goEasy.im) return;
|
||||
if (_notificationHandler) {
|
||||
wx.goEasy.im.off(wx.GoEasy.IM_EVENT.PRIVATE_MESSAGE_RECEIVED, _notificationHandler);
|
||||
wx.goEasy.im.off(wx.GoEasy.IM_EVENT.GROUP_MESSAGE_RECEIVED, _notificationHandler);
|
||||
}
|
||||
_notificationHandler = (message) => {
|
||||
if (app.globalData.messageManager.notificationMuted) return;
|
||||
const pages = getCurrentPages();
|
||||
if (pages.length) {
|
||||
const curPage = pages[pages.length - 1];
|
||||
if (curPage.route === 'pages/liaotian/liaotian' || curPage.route === 'pages/qunliaotian/qunliaotian') return;
|
||||
}
|
||||
const senderName = message.senderData?.name || (message.senderId || '用户');
|
||||
let content = '';
|
||||
if (message.type === 'text') content = message.payload?.text || '[文本]';
|
||||
else if (message.type === 'image') content = '[图片]';
|
||||
else if (message.type === 'audio') content = '[语音]';
|
||||
else content = '[新消息]';
|
||||
if (app.globalData.globalNotification && app.globalData.globalNotification.show) {
|
||||
app.globalData.globalNotification.show({
|
||||
senderName, content, message,
|
||||
notificationType: message.groupId ? 'group' : 'private',
|
||||
groupId: message.groupId, orderId: message.orderId
|
||||
});
|
||||
} else {
|
||||
wx.showToast({ title: `${senderName}: ${content}`, icon: 'none', duration: 2000 });
|
||||
}
|
||||
};
|
||||
wx.goEasy.im.on(wx.GoEasy.IM_EVENT.PRIVATE_MESSAGE_RECEIVED, _notificationHandler);
|
||||
wx.goEasy.im.on(wx.GoEasy.IM_EVENT.GROUP_MESSAGE_RECEIVED, _notificationHandler);
|
||||
}
|
||||
|
||||
function disconnectGoEasy(app) {
|
||||
if (_convListener) {
|
||||
wx.goEasy.im.off(wx.GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, _convListener);
|
||||
_convListener = null;
|
||||
}
|
||||
if (_privateListener) {
|
||||
wx.goEasy.im.off(wx.GoEasy.IM_EVENT.PRIVATE_MESSAGE_RECEIVED, _privateListener);
|
||||
_privateListener = null;
|
||||
}
|
||||
if (_groupListener) {
|
||||
wx.goEasy.im.off(wx.GoEasy.IM_EVENT.GROUP_MESSAGE_RECEIVED, _groupListener);
|
||||
_groupListener = null;
|
||||
}
|
||||
if (_notificationHandler) {
|
||||
wx.goEasy.im.off(wx.GoEasy.IM_EVENT.PRIVATE_MESSAGE_RECEIVED, _notificationHandler);
|
||||
wx.goEasy.im.off(wx.GoEasy.IM_EVENT.GROUP_MESSAGE_RECEIVED, _notificationHandler);
|
||||
_notificationHandler = null;
|
||||
}
|
||||
wx.goEasy.disconnect({ onSuccess: () => {}, onFailed: () => {} });
|
||||
wx.removeStorageSync('savedGoEasyConnection');
|
||||
}
|
||||
|
||||
async function switchRoleAndReconnect(app, newRole) {
|
||||
await disconnectGoEasy(app);
|
||||
app.globalData.currentRole = newRole;
|
||||
wx.setStorageSync('currentRole', newRole);
|
||||
ensureConnection(app);
|
||||
return true;
|
||||
}
|
||||
|
||||
function initAppStateListener(app) {
|
||||
let isActive = true;
|
||||
wx.onAppShow(() => {
|
||||
if (!isActive) return;
|
||||
const status = wx.goEasy.getConnectionStatus ? wx.goEasy.getConnectionStatus() : 'disconnected';
|
||||
if (status !== 'connected' && status !== 'reconnected') {
|
||||
ensureConnection(app);
|
||||
} else {
|
||||
// 连接存在但可能角标未更新,强制拉取并更新
|
||||
loadConversations(app);
|
||||
// 额外确保角标组件刷新
|
||||
setTimeout(() => {
|
||||
const unread = app.globalData.messageManager.unreadTotal || 0;
|
||||
updateTabBarBadge(app, unread);
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
wx.onAppHide(() => {});
|
||||
}
|
||||
|
||||
function initNetworkListener(app) {
|
||||
wx.onNetworkStatusChange((res) => {
|
||||
if (res.isConnected) {
|
||||
setTimeout(() => {
|
||||
ensureConnection(app);
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { initGlobalMessageSystem };
|
||||
220
_backup_pre_redesign/custom-tab-bar/index.js
Normal file
@@ -0,0 +1,220 @@
|
||||
// custom-tab-bar/index.js
|
||||
const app = getApp();
|
||||
|
||||
const roleCfg = {
|
||||
normal: {
|
||||
name: '点单老板',
|
||||
defaultPage: '/pages/index/index',
|
||||
tabList: [
|
||||
{ pagePath: 'pages/index/index', text: '商城', iconPath: '/images/zhuye.png', selectedIconPath: '/images/zhuye.png' },
|
||||
{ pagePath: 'pages/fenlei/fenlei', text: '分类', iconPath: '/images/fenlei.png', selectedIconPath: '/images/fenlei.png' },
|
||||
{ pagePath: 'pages/xiaoxi/xiaoxi', text: '消息', iconPath: '/images/xiaoxi.png', selectedIconPath: '/images/xiaoxi.png' },
|
||||
{ pagePath: 'pages/wode/wode', text: '我的', iconPath: '/images/wode.png', selectedIconPath: '/images/wode.png' }
|
||||
]
|
||||
},
|
||||
dashou: {
|
||||
name: '打手',
|
||||
defaultPage: '/pages/jiedan/jiedan',
|
||||
tabList: [
|
||||
{ pagePath: 'pages/jiedan/jiedan', text: '接单池', iconPath: '/images/jiedanchi.png', selectedIconPath: '/images/jiedanchi.png' },
|
||||
{ pagePath: 'pages/dashoudingdan/dashoudingdan', text: '订单', iconPath: '/images/dingdan.png', selectedIconPath: '/images/dingdan.png' },
|
||||
{ pagePath: 'pages/xiaoxi/xiaoxi', text: '消息', iconPath: '/images/xiaoxi.png', selectedIconPath: '/images/xiaoxi.png' },
|
||||
{ pagePath: 'pages/dashouduan/dashouduan', text: '我的', iconPath: '/images/wode.png', selectedIconPath: '/images/wode.png' }
|
||||
]
|
||||
},
|
||||
shangjia: {
|
||||
name: '商家',
|
||||
defaultPage: 'pages/sjdingdan/sjdingdan',
|
||||
tabList: [
|
||||
{ pagePath: 'pages/sjdingdan/sjdingdan', text: '订单', iconPath: '/images/dingdan.png', selectedIconPath: '/images/dingdan.png' },
|
||||
{ pagePath: 'pages/xiaoxi/xiaoxi', text: '消息', iconPath: '/images/xiaoxi.png', selectedIconPath: '/images/xiaoxi.png' },
|
||||
{ pagePath: 'pages/shangjiaduan/shangjiaduan', text: '我的', iconPath: '/images/wode.png', selectedIconPath: '/images/wode.png' }
|
||||
]
|
||||
},
|
||||
guanshi: {
|
||||
name: '管事',
|
||||
defaultPage: '/pages/guanshiduan/guanshiduan',
|
||||
tabList: [
|
||||
{ pagePath: 'pages/xiaoxi/xiaoxi', text: '消息', iconPath: '/images/xiaoxi.png', selectedIconPath: '/images/xiaoxi.png' },
|
||||
{ pagePath: 'pages/guanshiduan/guanshiduan', text: '我的', iconPath: '/images/wode.png', selectedIconPath: '/images/wode.png' }
|
||||
]
|
||||
},
|
||||
zuzhang: {
|
||||
name: '组长',
|
||||
defaultPage: '/pages/zuzhangduan/zuzhangduan',
|
||||
tabList: [
|
||||
{ pagePath: 'pages/xiaoxi/xiaoxi', text: '消息', iconPath: '/images/xiaoxi.png', selectedIconPath: '/images/xiaoxi.png' },
|
||||
{ pagePath: 'pages/zuzhangduan/zuzhangduan', text: '我的', iconPath: '/images/wode.png', selectedIconPath: '/images/wode.png' }
|
||||
]
|
||||
},
|
||||
kaoheguan: {
|
||||
name: '考核官',
|
||||
defaultPage: '/pages/kaoheguan/kaoheguan',
|
||||
tabList: [
|
||||
{ pagePath: 'pages/xiaoxi/xiaoxi', text: '消息', iconPath: '/images/xiaoxi.png', selectedIconPath: '/images/xiaoxi.png' },
|
||||
{ pagePath: 'pages/kaoheguan/kaoheguan', text: '我的', iconPath: '/images/wode.png', selectedIconPath: '/images/wode.png' }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
Component({
|
||||
data: {
|
||||
selectedIndex: 0,
|
||||
tabList: [],
|
||||
currentRole: 'normal',
|
||||
showRolePicker: false,
|
||||
availableRoles: [],
|
||||
badgeText: ''
|
||||
},
|
||||
lifetimes: {
|
||||
attached() {
|
||||
this.refresh();
|
||||
this._badgeListener = (data) => {
|
||||
if (data.badgeText !== undefined) {
|
||||
this.setData({ badgeText: data.badgeText });
|
||||
}
|
||||
};
|
||||
this._roleListener = () => {
|
||||
this.refresh();
|
||||
};
|
||||
app.on('tabBarBadgeChanged', this._badgeListener);
|
||||
app.on('currentRoleChanged', this._roleListener);
|
||||
},
|
||||
detached() {
|
||||
if (this._badgeListener) {
|
||||
app.off('tabBarBadgeChanged', this._badgeListener);
|
||||
}
|
||||
if (this._roleListener) {
|
||||
app.off('currentRoleChanged', this._roleListener);
|
||||
}
|
||||
}
|
||||
},
|
||||
pageLifetimes: {
|
||||
show() {
|
||||
this.refresh();
|
||||
const globalBadge = app.globalData.messageManager?.tabBarBadgeText || '';
|
||||
if (this.data.badgeText !== globalBadge) {
|
||||
this.setData({ badgeText: globalBadge });
|
||||
}
|
||||
this._syncUnreadWithDebounce();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
_syncUnreadWithDebounce() {
|
||||
if (this._syncTimer) clearTimeout(this._syncTimer);
|
||||
this._syncTimer = setTimeout(() => {
|
||||
if (app && app.loadConversations) {
|
||||
app.loadConversations();
|
||||
}
|
||||
}, 300);
|
||||
},
|
||||
|
||||
refresh() {
|
||||
const token = wx.getStorageSync('token');
|
||||
// 未登录时强制点单端,避免清除缓存后 TabBar 仍显示其他身份
|
||||
let role = wx.getStorageSync('currentRole') || app.globalData.currentRole || 'normal';
|
||||
if (!token) {
|
||||
role = 'normal';
|
||||
app.globalData.currentRole = 'normal';
|
||||
wx.setStorageSync('currentRole', 'normal');
|
||||
} else {
|
||||
app.globalData.currentRole = role;
|
||||
}
|
||||
let cfg = roleCfg[role];
|
||||
if (!cfg) return;
|
||||
|
||||
// 打手身份下,如果 isJinpai=1,悄悄把“我的”页面换成金牌页面
|
||||
if (role === 'dashou') {
|
||||
const isJinpai = wx.getStorageSync('isJinpai') === 1;
|
||||
if (isJinpai) {
|
||||
cfg = {
|
||||
...cfg,
|
||||
tabList: cfg.tabList.map(item => {
|
||||
if (item.pagePath === 'pages/dashouduan/dashouduan') {
|
||||
return { ...item, pagePath: 'pages/jinpaids/jinpaids' };
|
||||
}
|
||||
return { ...item };
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const list = cfg.tabList.map((v, i) => ({ ...v, index: i }));
|
||||
const pages = getCurrentPages();
|
||||
const curRoute = pages.length ? pages[pages.length - 1].route : '';
|
||||
let idx = list.findIndex(t => t.pagePath === curRoute);
|
||||
if (idx === -1) idx = 0;
|
||||
const avail = this._getAvail();
|
||||
this.setData({ currentRole: role, tabList: list, selectedIndex: idx, availableRoles: avail });
|
||||
},
|
||||
|
||||
_getAvail() {
|
||||
const token = wx.getStorageSync('token');
|
||||
if (!token) {
|
||||
return [{ key: 'normal', name: '点单老板' }];
|
||||
}
|
||||
const isActive = (key) => Number(wx.getStorageSync(key)) === 1;
|
||||
const arr = [{ key: 'normal', name: '点单老板' }];
|
||||
if (isActive('dashoustatus')) arr.push({ key: 'dashou', name: '打手' });
|
||||
if (isActive('shangjiastatus')) arr.push({ key: 'shangjia', name: '商家' });
|
||||
if (isActive('guanshistatus')) arr.push({ key: 'guanshi', name: '管事' });
|
||||
if (isActive('zuzhangstatus')) arr.push({ key: 'zuzhang', name: '组长' });
|
||||
if (isActive('kaoheguanstatus')) arr.push({ key: 'kaoheguan', name: '考核官' });
|
||||
return arr;
|
||||
},
|
||||
|
||||
toggleRolePicker() {
|
||||
this.setData({ showRolePicker: !this.data.showRolePicker });
|
||||
},
|
||||
|
||||
async selectRole(e) {
|
||||
const newRole = e.currentTarget.dataset.role;
|
||||
if (newRole === this.data.currentRole) {
|
||||
this.setData({ showRolePicker: false });
|
||||
return;
|
||||
}
|
||||
|
||||
this.setData({ showRolePicker: false });
|
||||
|
||||
// 1. 立即更新角色并保存
|
||||
if (app.setCurrentRole) {
|
||||
app.setCurrentRole(newRole);
|
||||
} else {
|
||||
app.globalData.currentRole = newRole;
|
||||
wx.setStorageSync('currentRole', newRole);
|
||||
app.emitEvent('currentRoleChanged', { role: newRole });
|
||||
}
|
||||
|
||||
// 2. 🔥 确定跳转页面:所有角色都去“我的”页面
|
||||
let targetPage = '';
|
||||
if (newRole === 'dashou') {
|
||||
// 打手需要根据金牌状态决定跳转
|
||||
const isJinpai = wx.getStorageSync('isJinpai') === 1;
|
||||
targetPage = isJinpai ? '/pages/jinpaids/jinpaids' : '/pages/dashouduan/dashouduan';
|
||||
} else {
|
||||
// 其他角色直接取 tabList 最后一个页面
|
||||
const cfg = roleCfg[newRole];
|
||||
if (cfg && cfg.tabList.length > 0) {
|
||||
targetPage = '/' + cfg.tabList[cfg.tabList.length - 1].pagePath;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 跳转
|
||||
if (targetPage) {
|
||||
wx.reLaunch({ url: targetPage });
|
||||
}
|
||||
|
||||
// 4. 后台异步切换连接
|
||||
app.switchRoleAndReconnect(newRole).catch(err => {
|
||||
console.error('后台切换连接失败:', err);
|
||||
});
|
||||
},
|
||||
|
||||
switchTab(e) {
|
||||
const path = e.currentTarget.dataset.path;
|
||||
const idx = e.currentTarget.dataset.index;
|
||||
if (idx === this.data.selectedIndex) return;
|
||||
wx.redirectTo({ url: '/' + path });
|
||||
}
|
||||
}
|
||||
});
|
||||
4
_backup_pre_redesign/custom-tab-bar/index.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {}
|
||||
}
|
||||
42
_backup_pre_redesign/custom-tab-bar/index.wxml
Normal file
@@ -0,0 +1,42 @@
|
||||
<view class="custom-tab-bar">
|
||||
<view class="bar-row">
|
||||
<!-- 左侧椭圆身份按钮 -->
|
||||
<view class="role-switch-btn" catchtap="toggleRolePicker">
|
||||
<image class="role-switch-icon" src="/images/role-switch.png" mode="aspectFit" />
|
||||
<text class="role-switch-text">切换身份</text>
|
||||
</view>
|
||||
|
||||
<!-- 右侧纯黑闪光胶囊 -->
|
||||
<view class="tab-capsule">
|
||||
<block wx:for="{{tabList}}" wx:key="index">
|
||||
<view class="tab-item {{selectedIndex === index ? 'active' : ''}}"
|
||||
data-path="{{item.pagePath}}"
|
||||
data-index="{{index}}"
|
||||
catchtap="switchTab">
|
||||
<!-- 🔥 根据页面路径判断是否显示角标,不再写死 index -->
|
||||
<view class="badge-container" wx:if="{{item.pagePath === 'pages/xiaoxi/xiaoxi' && badgeText}}">
|
||||
<view class="tab-badge">{{badgeText}}</view>
|
||||
</view>
|
||||
<image class="tab-icon" src="{{selectedIndex === index ? item.selectedIconPath : item.iconPath}}" mode="aspectFit" />
|
||||
<text class="tab-text">{{item.text}}</text>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 身份选择弹窗 -->
|
||||
<view wx:if="{{showRolePicker}}" class="picker-mask" catchtap="toggleRolePicker"></view>
|
||||
<view wx:if="{{showRolePicker}}" class="picker-panel">
|
||||
<view class="picker-header">
|
||||
<text class="picker-title">切换身份</text>
|
||||
<text class="picker-close" catchtap="toggleRolePicker">✕</text>
|
||||
</view>
|
||||
<block wx:for="{{availableRoles}}" wx:key="key">
|
||||
<view class="role-option {{currentRole === item.key ? 'active' : ''}}"
|
||||
data-role="{{item.key}}" catchtap="selectRole">
|
||||
<text>{{item.name}}</text>
|
||||
<text wx:if="{{currentRole === item.key}}" class="check-mark">✓</text>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
165
_backup_pre_redesign/custom-tab-bar/index.wxss
Normal file
@@ -0,0 +1,165 @@
|
||||
/* custom-tab-bar/index.wxss - 角标浮于右上角,可超出胶囊 */
|
||||
|
||||
.custom-tab-bar {
|
||||
position: fixed;
|
||||
bottom: 0; left: 0; right: 0;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-end;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.bar-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 15rpx;
|
||||
padding: 0 20rpx;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 左侧椭圆身份按钮 */
|
||||
.role-switch-btn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 110rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 50rpx;
|
||||
background: #000000;
|
||||
border: 1px solid rgba(255,255,255,0.15);
|
||||
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.8);
|
||||
margin-right: 20rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.role-switch-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
margin-bottom: 2rpx;
|
||||
}
|
||||
|
||||
.role-switch-text {
|
||||
font-size: 22rpx;
|
||||
font-weight: 600;
|
||||
color: #00f7ff;
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
|
||||
/* 纯黑闪光胶囊,移除overflow保证角标可见 */
|
||||
.tab-capsule {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
height: 100rpx;
|
||||
background: #000000;
|
||||
border-radius: 50rpx;
|
||||
border: 1px solid rgba(255,255,255,0.15);
|
||||
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.8);
|
||||
padding: 0 20rpx;
|
||||
position: relative;
|
||||
}
|
||||
.tab-capsule::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0; left: -100%;
|
||||
width: 60%; height: 100%;
|
||||
background: linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.25) 20%, rgba(255,255,255,0.35) 50%, rgba(255,255,255,0.25) 80%, transparent 100%);
|
||||
animation: shine 3s infinite;
|
||||
z-index: 1;
|
||||
}
|
||||
@keyframes shine {
|
||||
0% { left: -100%; }
|
||||
25% { left: 150%; }
|
||||
100% { left: 150%; }
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
position: relative; /* 角标的定位参考 */
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 70rpx;
|
||||
}
|
||||
.tab-icon {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
margin-bottom: 4rpx;
|
||||
filter: brightness(0) invert(1);
|
||||
opacity: 0.9;
|
||||
}
|
||||
.tab-text {
|
||||
font-size: 20rpx;
|
||||
color: #aaa;
|
||||
z-index: 2;
|
||||
}
|
||||
.active .tab-icon {
|
||||
opacity: 1;
|
||||
filter: brightness(0) invert(1) drop-shadow(0 0 8rpx #00aaff);
|
||||
}
|
||||
.active .tab-text {
|
||||
color: #00aaff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 🔴 角标容器:绝对定位到图标右上角,允许超出 */
|
||||
.badge-container {
|
||||
position: absolute;
|
||||
top: -10rpx; /* 往上提,更接近图标右上角 */
|
||||
right: -15rpx; /* 往右伸,超出tab-item */
|
||||
z-index: 100;
|
||||
pointer-events: none; /* 不阻挡点击 */
|
||||
}
|
||||
.tab-badge {
|
||||
min-width: 36rpx;
|
||||
height: 36rpx;
|
||||
line-height: 36rpx;
|
||||
padding: 0 10rpx;
|
||||
background: #ff3b30;
|
||||
color: #fff;
|
||||
font-size: 22rpx;
|
||||
font-weight: bold;
|
||||
border-radius: 36rpx;
|
||||
text-align: center;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.3);
|
||||
border: 1rpx solid rgba(255,255,255,0.4);
|
||||
}
|
||||
|
||||
/* 弹窗部分保持不变 */
|
||||
.picker-mask {
|
||||
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: rgba(0,0,0,0.6);
|
||||
z-index: 10000;
|
||||
}
|
||||
.picker-panel {
|
||||
position: fixed; bottom: 0; left: 0; right: 0;
|
||||
background: #000000;
|
||||
border-radius: 30rpx 30rpx 0 0;
|
||||
z-index: 10001;
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.picker-header {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
padding: 30rpx 40rpx 20rpx;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.1);
|
||||
}
|
||||
.picker-title { font-size: 32rpx; color: #00f7ff; }
|
||||
.picker-close { font-size: 36rpx; color: #aaa; padding: 0 10rpx; }
|
||||
.role-option {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
padding: 28rpx 40rpx;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.05);
|
||||
color: #ccc; font-size: 28rpx;
|
||||
}
|
||||
.role-option.active { color: #00f7ff; }
|
||||
.check-mark { font-size: 32rpx; color: #00f7ff; }
|
||||
|
||||
page {
|
||||
padding-bottom: 100rpx;
|
||||
}
|
||||
415
app.js
Normal file
@@ -0,0 +1,415 @@
|
||||
// app.js - 稳健生产版本(修复 GoEasy 初始化错误 + 配置100%加载)
|
||||
import GoEasy from './static/lib/goeasy-2.13.24.esm.min';
|
||||
const ChatCore = require('./utils/chat-core');
|
||||
import { check } from './utils/phone-auth';
|
||||
import { getPrimaryRole, lockPrimaryRole, migrateLegacyCenterRole, PRIMARY_DEFAULT_PAGES } from './utils/primary-role';
|
||||
|
||||
|
||||
// ===== 三端固定首页(与 custom-tab-bar 一致) =====
|
||||
const roleDefaultPage = PRIMARY_DEFAULT_PAGES;
|
||||
|
||||
App({
|
||||
globalData: {
|
||||
|
||||
hasShownPopupOnColdStart: false,
|
||||
|
||||
apiBaseUrl: 'https://www.abas.asia/hqhd',
|
||||
ossImageUrl: '',
|
||||
morentouxiang: '',
|
||||
dashouguize: '',
|
||||
|
||||
shangpinliebiao: [],
|
||||
shangpinzhuanqu: [],
|
||||
shangpinleixing: [],
|
||||
shangpinlunbo: [],
|
||||
shangpingonggao: '',
|
||||
lunbozhanwei: '/images/lunbozhanwei.jpg',
|
||||
|
||||
dingdanTiaoshu: {
|
||||
daifuwu: 0,
|
||||
fuwuzhong: 0,
|
||||
yiwancheng: 0,
|
||||
yituikuan: 0
|
||||
},
|
||||
|
||||
dashouqun: '',
|
||||
dashouqunid: '',
|
||||
guanshiqun: '',
|
||||
guanshiqunid: '',
|
||||
|
||||
appId:'wx0e4be86faac4a8d1',
|
||||
|
||||
shangjiastatus: 0,
|
||||
dashoustatus: 0,
|
||||
guanshistatus: 0,
|
||||
dashouNicheng: '',
|
||||
zhanghaoStatus: null,
|
||||
dashouzhuangtai: null,
|
||||
yongjin: null,
|
||||
zonge: null,
|
||||
yajin: null,
|
||||
chenghao: '',
|
||||
jinfen: null,
|
||||
clumber: [{
|
||||
huiyuanid: '',
|
||||
huiyuanming: '',
|
||||
daoqi: ''
|
||||
}],
|
||||
chengjiaoliang: null,
|
||||
zaixianZhuangtai: null,
|
||||
|
||||
guanshi: {
|
||||
nicheng: '',
|
||||
uid: '',
|
||||
touxiang: '',
|
||||
gszhstatus: '',
|
||||
yaoqingzongshu: 0,
|
||||
fenyongzonge: '0.00',
|
||||
fenyongtixian: '0.00'
|
||||
},
|
||||
|
||||
shangjia: {
|
||||
sjzhzhuangtai: '',
|
||||
sjyue: '',
|
||||
fadanzong: null,
|
||||
tuikuanzong: null,
|
||||
riliushui: '',
|
||||
yueliushui: ''
|
||||
},
|
||||
|
||||
liaotian_liebiao: [],
|
||||
xshenfen: 1,
|
||||
|
||||
goEasyConfig: null,
|
||||
kefuConfig: {
|
||||
link: '',
|
||||
enterpriseId: ''
|
||||
},
|
||||
|
||||
cosConfig: {
|
||||
bucket: '',
|
||||
region: 'ap-shanghai',
|
||||
uploadPathPrefix: 'order/'
|
||||
},
|
||||
|
||||
goEasyConnection: {
|
||||
status: 'disconnected',
|
||||
userId: '',
|
||||
identityType: '',
|
||||
lastConnectTime: 0,
|
||||
autoReconnect: true,
|
||||
reconnectAttempts: 0,
|
||||
maxReconnectAttempts: 5,
|
||||
heartbeatInterval: null,
|
||||
cacheKeys: {
|
||||
savedConnection: 'savedGoEasyConnection',
|
||||
userId: 'goEasyUserId',
|
||||
identityType: 'currentGoEasyIdentity',
|
||||
connectTime: 'goEasyConnectTime'
|
||||
},
|
||||
config: {
|
||||
heartbeatInterval: 20000,
|
||||
reconnectDelay: 3000,
|
||||
offlineTimeout: 30000,
|
||||
cacheValidityHours: 12
|
||||
}
|
||||
},
|
||||
|
||||
messageManager: {
|
||||
unreadTotal: 0,
|
||||
unreadMap: {},
|
||||
latestMessages: [],
|
||||
notificationQueue: [],
|
||||
notificationVisible: false,
|
||||
lastNotificationTime: 0,
|
||||
notificationCooldown: 3000,
|
||||
tabBarIndex: 2,
|
||||
showTabBarBadge: true,
|
||||
customTabBar: true,
|
||||
tabBarBadgeText: 0,
|
||||
soundEnabled: true,
|
||||
vibrationEnabled: true,
|
||||
doNotDisturb: false,
|
||||
doNotDisturbStart: '22:00',
|
||||
doNotDisturbEnd: '08:00',
|
||||
notificationStyle: {
|
||||
position: 'top',
|
||||
duration: 3000,
|
||||
backgroundColor: '#00f7ff',
|
||||
textColor: '#ffffff',
|
||||
borderRadius: '16rpx',
|
||||
boxShadow: '0 10rpx 30rpx rgba(0, 247, 255, 0.3)'
|
||||
},
|
||||
currentNotification: null,
|
||||
cacheKeys: {
|
||||
messageSettings: 'messageSettings',
|
||||
unreadTotal: 'messageUnreadTotal',
|
||||
latestMessages: 'latestMessages',
|
||||
notificationMuted: 'notificationMuted'
|
||||
}
|
||||
},
|
||||
|
||||
pageState: {
|
||||
currentPage: '',
|
||||
isInChatPage: false,
|
||||
currentChatId: '',
|
||||
lastPageUpdate: 0
|
||||
},
|
||||
|
||||
eventListeners: {},
|
||||
debugMode: false,
|
||||
currentUser: null,
|
||||
currentRole: 'normal',
|
||||
primaryRole: 'normal'
|
||||
},
|
||||
|
||||
// ========== 核心启动流程 ==========
|
||||
async onLaunch() {
|
||||
// ---------- ① 立即隐藏“返回首页”按钮 ----------
|
||||
wx.hideHomeButton();
|
||||
wx.onAppRoute((res) => {
|
||||
wx.hideHomeButton();
|
||||
const path = (res && res.path) || '';
|
||||
if (path === 'pages/guanshiduan/guanshiduan' || path === 'pages/zuzhangduan/zuzhangduan') {
|
||||
migrateLegacyCenterRole(this);
|
||||
lockPrimaryRole('dashou', this);
|
||||
}
|
||||
});
|
||||
|
||||
// ---------- ② 恢复主端并跳转(旧管事/组长标识自动归打手端) ----------
|
||||
migrateLegacyCenterRole(this);
|
||||
const savedRole = getPrimaryRole(this);
|
||||
lockPrimaryRole(savedRole, this);
|
||||
|
||||
const targetPage = roleDefaultPage[savedRole] || '/pages/index/index';
|
||||
if (savedRole !== 'normal') {
|
||||
setTimeout(() => {
|
||||
wx.reLaunch({ url: targetPage });
|
||||
}, 0);
|
||||
}
|
||||
|
||||
// ---------- ③ 立刻应用本地缓存配置 ----------
|
||||
const cachedConfig = this.readConfigFromStorage();
|
||||
if (cachedConfig) {
|
||||
this.applyDynamicConfig(cachedConfig);
|
||||
}
|
||||
|
||||
// ---------- ④ 初始化基础设施 ----------
|
||||
this.initGoEasyWithConfig(); // 只有appkey有效时才真正初始化
|
||||
ChatCore.initGlobalMessageSystem(this);
|
||||
this.initCurrentUser();
|
||||
|
||||
// ---------- ⑤ 建立连接 ----------
|
||||
const saved = this.getSavedConnection();
|
||||
console.log('【启动】缓存身份:', saved ? saved.identityType : '无',
|
||||
'userId:', saved ? saved.userId : '无');
|
||||
setTimeout(() => {
|
||||
if (this.startImWhenReady) this.startImWhenReady();
|
||||
else if (this.ensureConnection) this.ensureConnection();
|
||||
}, 300);
|
||||
|
||||
// ---------- ⑥ 后台静默获取最新配置 ----------
|
||||
this.fetchConfigSafely()
|
||||
.then(latestConfig => {
|
||||
this.saveConfigToStorage(latestConfig);
|
||||
this.applyDynamicConfig(latestConfig);
|
||||
this.initGoEasyWithConfig();
|
||||
this.initCurrentUser();
|
||||
if (this.startImWhenReady) this.startImWhenReady();
|
||||
else if (this.connectForCurrentRole) this.connectForCurrentRole();
|
||||
console.log('远程配置更新完成');
|
||||
})
|
||||
.catch(err => {
|
||||
console.warn('远程配置获取失败,继续使用本地缓存', err);
|
||||
});
|
||||
|
||||
// ---------- ⑦ 手机号认证检查 ----------
|
||||
try {
|
||||
const needAuth = await check();
|
||||
if (needAuth) {
|
||||
wx.reLaunch({ url: '/pages/phone-auth/phone-auth' });
|
||||
return;
|
||||
}
|
||||
} catch (e) {}
|
||||
},
|
||||
|
||||
// ========== 连接管理方法 ==========
|
||||
getSavedConnection() {
|
||||
try {
|
||||
const saved = wx.getStorageSync(
|
||||
this.globalData.goEasyConnection.cacheKeys.savedConnection
|
||||
);
|
||||
return saved || null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
// ========== 配置缓存读写 ==========
|
||||
CONFIG_CACHE_KEY: 'app_dynamic_config',
|
||||
|
||||
readConfigFromStorage() {
|
||||
try {
|
||||
const data = wx.getStorageSync(this.CONFIG_CACHE_KEY);
|
||||
if (data && typeof data === 'object') {
|
||||
return data;
|
||||
}
|
||||
} catch (e) {}
|
||||
return null;
|
||||
},
|
||||
|
||||
saveConfigToStorage(config) {
|
||||
try {
|
||||
if (config && typeof config === 'object') {
|
||||
wx.setStorageSync(this.CONFIG_CACHE_KEY, config);
|
||||
}
|
||||
} catch (e) {}
|
||||
},
|
||||
|
||||
// ========== 安全获取远程配置(永不报错) ==========
|
||||
fetchConfigSafely() {
|
||||
return new Promise((resolve) => {
|
||||
wx.request({
|
||||
url: this.globalData.apiBaseUrl + '/peizhi/qdpzhq',
|
||||
method: 'POST',
|
||||
header: { 'content-type': 'application/json' },
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200 && res.data && res.data.code === 0 && res.data.data) {
|
||||
resolve(res.data.data);
|
||||
} else {
|
||||
console.warn('远程配置接口返回异常,使用已有配置');
|
||||
resolve(this.readConfigFromStorage() || {});
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.warn('远程配置网络请求失败,使用已有配置', err);
|
||||
resolve(this.readConfigFromStorage() || {});
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// ========== 原有方法(保留,仅增强) ==========
|
||||
fetchDynamicConfig() {
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.request({
|
||||
url: this.globalData.apiBaseUrl + '/peizhi/qdpzhq',
|
||||
method: 'POST',
|
||||
header: { 'content-type': 'application/json' },
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200 && res.data && res.data.code === 0) {
|
||||
resolve(res.data.data);
|
||||
} else {
|
||||
reject(new Error(res.data?.msg || '接口返回错误'));
|
||||
}
|
||||
},
|
||||
fail: reject
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
applyDynamicConfig(config) {
|
||||
if (!config || typeof config !== 'object') return;
|
||||
|
||||
if (config.cos) {
|
||||
this.globalData.ossImageUrl = config.cos.ossImageUrl || '';
|
||||
this.globalData.cosConfig = {
|
||||
bucket: config.cos.bucket || '',
|
||||
region: config.cos.region || 'ap-shanghai',
|
||||
uploadPathPrefix: config.cos.uploadPathPrefix || 'order/'
|
||||
};
|
||||
}
|
||||
|
||||
if (config.goEasy) {
|
||||
this.globalData.goEasyConfig = {
|
||||
host: config.goEasy.host || 'hangzhou.goeasy.io',
|
||||
appkey: config.goEasy.appkey || ''
|
||||
};
|
||||
}
|
||||
|
||||
if (config.otherConfig) {
|
||||
this.globalData.morentouxiang = config.otherConfig.morentouxiang || '';
|
||||
this.globalData.dashouguize = config.otherConfig.dashouguize || '';
|
||||
}
|
||||
|
||||
if (config.kefu) {
|
||||
this.globalData.kefuConfig = {
|
||||
link: config.kefu.link || '',
|
||||
enterpriseId: config.kefu.enterpriseId || ''
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// 🔥 关键修复:只在有效 appkey 时初始化,且避免重复初始化
|
||||
initGoEasyWithConfig() {
|
||||
const cfg = this.globalData.goEasyConfig;
|
||||
if (!cfg || !cfg.appkey) {
|
||||
console.warn('GoEasy appkey 未配置,聊天功能将在配置就绪后自动恢复');
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果已经初始化过,就不再重复创建实例
|
||||
if (wx.goEasy && wx.GoEasy) {
|
||||
console.log('GoEasy 已初始化,跳过');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
wx.goEasy = GoEasy.getInstance({
|
||||
host: cfg.host || 'hangzhou.goeasy.io',
|
||||
appkey: cfg.appkey,
|
||||
modules: ['im', 'pubsub']
|
||||
});
|
||||
wx.GoEasy = GoEasy;
|
||||
console.log('GoEasy 初始化成功');
|
||||
if (this.startImWhenReady && wx.getStorageSync('uid')) {
|
||||
setTimeout(() => this.startImWhenReady(), 100);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('GoEasy 初始化失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
initCurrentUser() {
|
||||
const uid = wx.getStorageSync('uid');
|
||||
if (uid) {
|
||||
this.globalData.currentUser = {
|
||||
id: uid,
|
||||
name: '用户' + uid.substring(0, 6),
|
||||
avatar: this.globalData.ossImageUrl + this.globalData.morentouxiang
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
emitEvent(eventName, data) {
|
||||
if (this.globalData.eventListeners[eventName]) {
|
||||
this.globalData.eventListeners[eventName].forEach(callback => {
|
||||
try { callback(data); } catch (error) { console.error(error); }
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/** 切换当前角色并通知 custom-tab-bar 立即刷新(Storage + globalData + 事件) */
|
||||
setCurrentRole(role) {
|
||||
if (!role) return;
|
||||
this.globalData.currentRole = role;
|
||||
wx.setStorageSync('currentRole', role);
|
||||
this.emitEvent('currentRoleChanged', { role });
|
||||
},
|
||||
|
||||
on(eventName, callback) {
|
||||
if (!this.globalData.eventListeners[eventName]) {
|
||||
this.globalData.eventListeners[eventName] = [];
|
||||
}
|
||||
this.globalData.eventListeners[eventName].push(callback);
|
||||
},
|
||||
|
||||
off(eventName, callback) {
|
||||
if (this.globalData.eventListeners[eventName]) {
|
||||
const index = this.globalData.eventListeners[eventName].indexOf(callback);
|
||||
if (index > -1) {
|
||||
this.globalData.eventListeners[eventName].splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
107
app.json
Normal file
@@ -0,0 +1,107 @@
|
||||
{
|
||||
"pages": [
|
||||
"pages/index/index",
|
||||
"pages/fenlei/fenlei",
|
||||
"pages/xuanren/xuanren",
|
||||
"pages/xiaoxi/xiaoxi",
|
||||
"pages/wode/wode",
|
||||
"pages/shangpinxiangqing/shangpinxiangqing",
|
||||
"pages/tijiao/tijiao",
|
||||
"pages/xiugai/xiugai",
|
||||
"pages/dashouduan/dashouduan",
|
||||
"pages/shangjiaduan/shangjiaduan",
|
||||
"pages/guanshiduan/guanshiduan",
|
||||
"pages/dingdan/dingdan",
|
||||
"pages/dingdanxiangqing/dingdanxiangqing",
|
||||
"pages/dashouguize/dashouguize",
|
||||
"pages/dashouxiugai/dashouxiugai",
|
||||
"pages/dashoupaihang/dashoupaihang",
|
||||
"pages/dashouchongzhi/dashouchongzhi",
|
||||
"pages/dashouxiaoxi/dashouxiaoxi",
|
||||
"pages/dashoudingdan/dashoudingdan",
|
||||
"pages/jiedan/jiedan",
|
||||
"pages/tixian/tixian",
|
||||
"pages/yaoqingdashou/yaoqingdashou",
|
||||
"pages/wodedashou/wodedashou",
|
||||
"pages/czjilu/czjilu",
|
||||
"pages/guanshipaihang/guanshipaihang",
|
||||
"pages/sjpaidan/sjpaidan",
|
||||
"pages/sjdingdan/sjdingdan",
|
||||
"pages/sjxiaoxi/sjxiaoxi",
|
||||
"pages/sjpaihang/sjpaihang",
|
||||
"pages/sjchongzhi/sjchongzhi",
|
||||
"pages/sjddxq/sjddxq",
|
||||
"pages/dsddxq/dsddxq",
|
||||
"pages/liaotian/liaotian",
|
||||
"pages/jisufd/jisufd",
|
||||
"pages/jiedanchi/jiedanchi",
|
||||
"pages/haibao/haibao",
|
||||
"pages/jinpaids/jinpaids",
|
||||
"pages/zuzhangduan/zuzhangduan",
|
||||
"pages/guanzhual/guanzhual",
|
||||
"pages/zzfhjilu/zzfhjilu",
|
||||
"pages/yqguanshi/yqguanshi",
|
||||
"pages/jiedanchi2/jiedanchi2",
|
||||
"pages/qunliaotian/qunliaotian",
|
||||
"pages/kefuliaotian/kefuliaotian",
|
||||
"pages/renzheng/renzheng",
|
||||
"components/global-notification/global-notification",
|
||||
"components/popup-notice/popup-notice",
|
||||
"components/order-card/order-card",
|
||||
"components/order-sender/order-sender",
|
||||
"pages/cfss/cfss/cfss",
|
||||
"pages/cfss/components/fakuan-list/fakuan-list",
|
||||
"pages/cfss/components/jifen-list/jifen-list",
|
||||
"pages/cfss/components/fakuan-pay/fakuan-pay",
|
||||
"pages/phone-auth/phone-auth",
|
||||
"components/chenghao-tag/chenghao-tag",
|
||||
"pages/kaoheguan/kaoheguan",
|
||||
"pages/kaohe_dafen/kaohe_dafen",
|
||||
"pages/kaohe_jilu/kaohe_jilu",
|
||||
"pages/kaohe_zhongxin/kaohe_zhongxin",
|
||||
"pages/kaohe_jinpai/kaohe_jinpai",
|
||||
"pages/peihuDingdan/peihuDingdan",
|
||||
"pages/tixian/components/mode1/mode1",
|
||||
"pages/tixian/components/mode2/mode2"
|
||||
|
||||
|
||||
],
|
||||
|
||||
"usingComponents": {
|
||||
"popup-notice": "/components/popup-notice/popup-notice",
|
||||
"custom-tab-bar": "/custom-tab-bar/index"
|
||||
},
|
||||
|
||||
"window": {
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationStyle": "default",
|
||||
"backgroundTextStyle": "light",
|
||||
"navigationBarBackgroundColor": "#fff",
|
||||
"navigationBarTitleText": "星阙网络"
|
||||
},
|
||||
"tabBar": {
|
||||
"custom": true,
|
||||
"list": [
|
||||
|
||||
|
||||
{
|
||||
"pagePath": "pages/jiedanchi/jiedanchi",
|
||||
"text": "接单池",
|
||||
"iconPath": "/images/jiedanchi.png",
|
||||
"selectedIconPath": "/images/jiedanchi.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/jiedanchi2/jiedanchi2",
|
||||
"text": "接单池",
|
||||
"iconPath": "/images/jiedanchi.png",
|
||||
"selectedIconPath": "/images/jiedanchi.png"
|
||||
}
|
||||
|
||||
]
|
||||
},
|
||||
|
||||
|
||||
"style": "v2",
|
||||
"sitemapLocation": "sitemap.json",
|
||||
"lazyCodeLoading": "requiredComponents"
|
||||
}
|
||||
10
app.wxss
Normal file
@@ -0,0 +1,10 @@
|
||||
/**app.wxss**/
|
||||
.container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 200rpx 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
73
components/chenghao-tag/chenghao-tag.js
Normal file
@@ -0,0 +1,73 @@
|
||||
// components/chenghao-tag/chenghao-tag.js
|
||||
const app = getApp();
|
||||
|
||||
Component({
|
||||
properties: {
|
||||
mingcheng: { type: String, value: '' },
|
||||
texiaoJson: { type: Object, value: {} }
|
||||
},
|
||||
data: {
|
||||
// 默认值:宽152,高52,六边形,无背景图,无动画,白色字
|
||||
width: 152,
|
||||
height: 52,
|
||||
shapeClass: 'liubianxing', // 保证至少不是矩形
|
||||
animationClass: '',
|
||||
bgStyle: '',
|
||||
textColor: '#FFFFFF',
|
||||
textSize: 22,
|
||||
imageUrl: ''
|
||||
},
|
||||
lifetimes: {
|
||||
attached() {
|
||||
const cfg = this.properties.texiaoJson || {};
|
||||
|
||||
// 1. 尺寸(稍大一些,一行能放3~4个)
|
||||
const width = cfg.width || 152;
|
||||
const height = cfg.height || 52;
|
||||
|
||||
// 2. 背景处理:优先背景图,否则用渐变/纯色
|
||||
let bgStyle = '';
|
||||
let imageUrl = '';
|
||||
if (cfg.image_url) {
|
||||
const ossImageUrl = app.globalData.ossImageUrl || '';
|
||||
imageUrl = cfg.image_url.startsWith('http')
|
||||
? cfg.image_url
|
||||
: ossImageUrl + cfg.image_url;
|
||||
} else if (cfg.bg_gradient) {
|
||||
bgStyle = `background: ${cfg.bg_gradient};`;
|
||||
} else if (cfg.bg_color) {
|
||||
bgStyle = `background: ${cfg.bg_color};`;
|
||||
} else {
|
||||
// 默认金橙渐变
|
||||
bgStyle = `background: linear-gradient(135deg, #FFD700, #FF8C00);`;
|
||||
}
|
||||
|
||||
// 3. 形状(后端传入 shape 字段,默认 liubianxing)
|
||||
const shapeClass = cfg.shape || 'liubianxing';
|
||||
|
||||
// 4. 动画
|
||||
const animationClass = cfg.animation || '';
|
||||
|
||||
// 5. 文字
|
||||
const textColor = cfg.text_color || '#FFFFFF';
|
||||
const textSize = cfg.text_size || 22;
|
||||
|
||||
this.setData({
|
||||
width, height,
|
||||
bgStyle, imageUrl,
|
||||
shapeClass, animationClass,
|
||||
textColor, textSize
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 背景图加载失败时回退为纯色背景
|
||||
onImageError() {
|
||||
this.setData({ imageUrl: '' });
|
||||
// 如果 bgStyle 为空,给个默认背景
|
||||
if (!this.data.bgStyle) {
|
||||
this.setData({ bgStyle: 'background: linear-gradient(135deg, #FFD700, #FF8C00);' });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
4
components/chenghao-tag/chenghao-tag.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {}
|
||||
}
|
||||
20
components/chenghao-tag/chenghao-tag.wxml
Normal file
@@ -0,0 +1,20 @@
|
||||
<!-- components/chenghao-tag/chenghao-tag.wxml -->
|
||||
<view
|
||||
class="tag-root {{shapeClass}} {{animationClass}}"
|
||||
style="width: {{width}}rpx; height: {{height}}rpx; {{bgStyle}}"
|
||||
>
|
||||
<!-- 背景图(如果有) -->
|
||||
<image
|
||||
wx:if="{{imageUrl}}"
|
||||
class="tag-bg-image"
|
||||
src="{{imageUrl}}"
|
||||
mode="aspectFill"
|
||||
binderror="onImageError"
|
||||
></image>
|
||||
|
||||
<!-- 文字层 -->
|
||||
<text
|
||||
class="tag-text"
|
||||
style="color: {{textColor}}; font-size: {{textSize}}rpx;"
|
||||
>{{mingcheng}}</text>
|
||||
</view>
|
||||
90
components/chenghao-tag/chenghao-tag.wxss
Normal file
@@ -0,0 +1,90 @@
|
||||
/* components/chenghao-tag/chenghao-tag.wxss */
|
||||
|
||||
/* ========== 根容器 ========== */
|
||||
.tag-root {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
border-radius: 4rpx;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
margin: 4rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* ========== 背景图 ========== */
|
||||
.tag-bg-image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 0;
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
/* ========== 文字层 ========== */
|
||||
.tag-text {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.5);
|
||||
padding: 0 10rpx;
|
||||
}
|
||||
|
||||
/* ========== 多边形形状 ========== */
|
||||
.tag-root.liubianxing {
|
||||
clip-path: polygon(12% 0%, 88% 0%, 100% 50%, 88% 100%, 12% 100%, 0% 50%);
|
||||
}
|
||||
.tag-root.lingxing {
|
||||
clip-path: polygon(50% 0%, 85% 12%, 100% 50%, 85% 88%, 50% 100%, 15% 88%, 0% 50%, 15% 12%);
|
||||
}
|
||||
.tag-root.wujiaoxing {
|
||||
clip-path: polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%);
|
||||
}
|
||||
.tag-root.dunpai {
|
||||
clip-path: polygon(18% 0%, 82% 0%, 100% 18%, 95% 50%, 100% 82%, 82% 100%, 18% 100%, 0% 82%, 5% 50%, 0% 18%);
|
||||
}
|
||||
.tag-root.jiantou {
|
||||
clip-path: polygon(0% 0%, 85% 0%, 100% 50%, 85% 100%, 0% 100%, 12% 50%);
|
||||
}
|
||||
|
||||
/* ========== 动画 ========== */
|
||||
/* 流光扫描 */
|
||||
.tag-root.shine::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 60%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.5), transparent);
|
||||
transform: skewX(-20deg);
|
||||
animation: shine-anim 2s infinite;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
@keyframes shine-anim {
|
||||
0% { left: -100%; }
|
||||
100% { left: 200%; }
|
||||
}
|
||||
|
||||
/* 呼吸光晕 */
|
||||
.tag-root.pulse {
|
||||
animation: pulse-anim 1.5s ease-in-out infinite;
|
||||
}
|
||||
@keyframes pulse-anim {
|
||||
0%, 100% { filter: brightness(1); transform: scale(1); }
|
||||
50% { filter: brightness(1.2); transform: scale(1.04); }
|
||||
}
|
||||
|
||||
/* 外发光闪烁 */
|
||||
.tag-root.glow {
|
||||
animation: glow-anim 2s ease-in-out infinite;
|
||||
}
|
||||
@keyframes glow-anim {
|
||||
0%, 100% { box-shadow: 0 0 8rpx rgba(255, 215, 0, 0.4); }
|
||||
50% { box-shadow: 0 0 20rpx rgba(255, 215, 0, 0.8), 0 0 40rpx rgba(255, 215, 0, 0.4); }
|
||||
}
|
||||
368
components/global-notification/global-notification.js
Normal file
@@ -0,0 +1,368 @@
|
||||
const { resolveAvatarUrl } = require('../../utils/avatar.js');
|
||||
|
||||
Component({
|
||||
properties: {
|
||||
position: {
|
||||
type: String,
|
||||
value: 'top'
|
||||
},
|
||||
duration: {
|
||||
type: Number,
|
||||
value: 3000
|
||||
},
|
||||
showProgress: {
|
||||
type: Boolean,
|
||||
value: true
|
||||
},
|
||||
showMuteButton: {
|
||||
type: Boolean,
|
||||
value: true
|
||||
},
|
||||
showTime: {
|
||||
type: Boolean,
|
||||
value: true
|
||||
},
|
||||
swipeToClose: {
|
||||
type: Boolean,
|
||||
value: true
|
||||
},
|
||||
swipeThreshold: {
|
||||
type: Number,
|
||||
value: 50
|
||||
}
|
||||
},
|
||||
|
||||
data: {
|
||||
show: false,
|
||||
title: '',
|
||||
message: '',
|
||||
avatar: '',
|
||||
notificationData: null,
|
||||
progress: 100,
|
||||
progressInterval: null,
|
||||
positionClass: 'top',
|
||||
|
||||
// 滑动相关
|
||||
touchStartY: 0,
|
||||
touchStartX: 0,
|
||||
currentY: 0,
|
||||
currentX: 0,
|
||||
isSwiping: false,
|
||||
swipingClass: '',
|
||||
swipeStyle: '',
|
||||
swipeDistance: 0,
|
||||
|
||||
// 静音状态
|
||||
isMuted: false,
|
||||
|
||||
// 自动隐藏计时器
|
||||
autoHideTimeout: null
|
||||
},
|
||||
|
||||
lifetimes: {
|
||||
attached() {
|
||||
this.loadMuteSetting();
|
||||
this.setData({
|
||||
positionClass: this.properties.position
|
||||
});
|
||||
this.setupDirectEventListeners();
|
||||
},
|
||||
|
||||
detached() {
|
||||
this.clearTimers();
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
setupDirectEventListeners() {
|
||||
const app = getApp();
|
||||
if (app) {
|
||||
const self = this;
|
||||
const globalNotification = {
|
||||
show: (data) => {
|
||||
if (self && self.showNotification) {
|
||||
self.showNotification(data);
|
||||
}
|
||||
},
|
||||
hide: () => {
|
||||
if (self && self.hideNotification) {
|
||||
self.hideNotification();
|
||||
}
|
||||
},
|
||||
isMuted: () => {
|
||||
return self.data.isMuted;
|
||||
}
|
||||
};
|
||||
app.globalData.globalNotification = globalNotification;
|
||||
}
|
||||
},
|
||||
|
||||
loadMuteSetting() {
|
||||
try {
|
||||
const isMuted = wx.getStorageSync('notificationMuted') || false;
|
||||
this.setData({ isMuted });
|
||||
} catch (error) {
|
||||
console.warn('加载静音设置失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
saveMuteSetting(isMuted) {
|
||||
try {
|
||||
wx.setStorageSync('notificationMuted', isMuted);
|
||||
} catch (error) {
|
||||
console.warn('保存静音设置失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
showNotification(data) {
|
||||
if (!data) return;
|
||||
if (this.data.isMuted) return;
|
||||
|
||||
this.clearTimers();
|
||||
|
||||
const app = getApp();
|
||||
let avatar = resolveAvatarUrl(
|
||||
data.avatar || data.message?.senderData?.avatar,
|
||||
app
|
||||
);
|
||||
|
||||
this.setData({
|
||||
show: true,
|
||||
title: data.senderName || '新消息',
|
||||
message: data.content || '[消息内容]',
|
||||
avatar: avatar,
|
||||
notificationData: data,
|
||||
progress: 100,
|
||||
swipingClass: '',
|
||||
swipeStyle: ''
|
||||
});
|
||||
|
||||
if (this.properties.showProgress) {
|
||||
this.startProgressBar(this.properties.duration);
|
||||
}
|
||||
|
||||
this.data.autoHideTimeout = setTimeout(() => {
|
||||
this.hideNotification();
|
||||
}, this.properties.duration);
|
||||
},
|
||||
|
||||
hideNotification() {
|
||||
this.clearTimers();
|
||||
this.setData({
|
||||
show: false,
|
||||
progress: 100,
|
||||
swipingClass: '',
|
||||
swipeStyle: ''
|
||||
});
|
||||
this.triggerEvent('hide');
|
||||
},
|
||||
|
||||
startProgressBar(duration) {
|
||||
const interval = 100;
|
||||
const steps = duration / interval;
|
||||
const stepValue = 100 / steps;
|
||||
|
||||
let currentProgress = 100;
|
||||
this.data.progressInterval = setInterval(() => {
|
||||
currentProgress -= stepValue;
|
||||
if (currentProgress <= 0) {
|
||||
currentProgress = 0;
|
||||
this.setData({ progress: currentProgress });
|
||||
clearInterval(this.data.progressInterval);
|
||||
} else {
|
||||
this.setData({ progress: currentProgress });
|
||||
}
|
||||
}, interval);
|
||||
},
|
||||
|
||||
clearTimers() {
|
||||
if (this.data.autoHideTimeout) {
|
||||
clearTimeout(this.data.autoHideTimeout);
|
||||
this.data.autoHideTimeout = null;
|
||||
}
|
||||
if (this.data.progressInterval) {
|
||||
clearInterval(this.data.progressInterval);
|
||||
this.data.progressInterval = null;
|
||||
}
|
||||
},
|
||||
|
||||
onTouchStart(e) {
|
||||
if (!this.properties.swipeToClose) return;
|
||||
const touch = e.touches[0];
|
||||
this.setData({
|
||||
touchStartY: touch.clientY,
|
||||
touchStartX: touch.clientX,
|
||||
currentY: touch.clientY,
|
||||
currentX: touch.clientX,
|
||||
isSwiping: false
|
||||
});
|
||||
this.clearTimers();
|
||||
},
|
||||
|
||||
onTouchMove(e) {
|
||||
if (!this.properties.swipeToClose) return;
|
||||
const touch = e.touches[0];
|
||||
const deltaY = touch.clientY - this.data.touchStartY;
|
||||
const deltaX = touch.clientX - this.data.touchStartX;
|
||||
const absDeltaY = Math.abs(deltaY);
|
||||
const absDeltaX = Math.abs(deltaX);
|
||||
|
||||
if (absDeltaY > 10 && absDeltaY > absDeltaX) {
|
||||
this.setData({
|
||||
isSwiping: true,
|
||||
currentY: touch.clientY,
|
||||
swipingClass: deltaY < 0 ? 'swiping-up' : 'swiping',
|
||||
swipeDistance: Math.abs(deltaY),
|
||||
swipeStyle: `--swipe-distance: ${Math.abs(deltaY)}rpx;`
|
||||
});
|
||||
e.stopPropagation();
|
||||
}
|
||||
},
|
||||
|
||||
onTouchEnd(e) {
|
||||
if (!this.properties.swipeToClose || !this.data.isSwiping) {
|
||||
this.restartAutoHide();
|
||||
return;
|
||||
}
|
||||
const deltaY = this.data.currentY - this.data.touchStartY;
|
||||
const absDeltaY = Math.abs(deltaY);
|
||||
if (absDeltaY > this.properties.swipeThreshold) {
|
||||
this.hideNotification();
|
||||
} else {
|
||||
this.setData({
|
||||
swipingClass: '',
|
||||
swipeStyle: 'transition: transform 0.3s ease; transform: translateY(0);'
|
||||
});
|
||||
setTimeout(() => {
|
||||
this.setData({
|
||||
swipingClass: '',
|
||||
swipeStyle: ''
|
||||
});
|
||||
}, 300);
|
||||
this.restartAutoHide();
|
||||
}
|
||||
this.setData({ isSwiping: false });
|
||||
},
|
||||
|
||||
restartAutoHide() {
|
||||
const remainingTime = (this.data.progress / 100) * this.properties.duration;
|
||||
if (remainingTime > 0) {
|
||||
this.data.autoHideTimeout = setTimeout(() => {
|
||||
this.hideNotification();
|
||||
}, remainingTime);
|
||||
}
|
||||
},
|
||||
|
||||
onTap() {
|
||||
const { notificationData } = this.data;
|
||||
if (!notificationData) return;
|
||||
this.triggerEvent('tap', notificationData);
|
||||
this.hideNotification();
|
||||
this.navigateToChatPage(notificationData);
|
||||
},
|
||||
|
||||
onClose(e) {
|
||||
e.stopPropagation();
|
||||
this.triggerEvent('close');
|
||||
this.hideNotification();
|
||||
},
|
||||
|
||||
onMute(e) {
|
||||
e.stopPropagation();
|
||||
const newMutedState = !this.data.isMuted;
|
||||
this.setData({ isMuted: newMutedState });
|
||||
this.saveMuteSetting(newMutedState);
|
||||
|
||||
const app = getApp();
|
||||
if (app && app.toggleDoNotDisturb) {
|
||||
app.toggleDoNotDisturb(newMutedState);
|
||||
}
|
||||
this.triggerEvent('mute', { muted: newMutedState });
|
||||
wx.showToast({
|
||||
title: newMutedState ? '已开启免打扰' : '已关闭免打扰',
|
||||
icon: 'success',
|
||||
duration: 1500
|
||||
});
|
||||
|
||||
if (newMutedState) {
|
||||
this.hideNotification();
|
||||
}
|
||||
},
|
||||
|
||||
formatTime(timestamp) {
|
||||
if (!timestamp) return '';
|
||||
const date = new Date(timestamp);
|
||||
const now = new Date();
|
||||
const diff = now - date;
|
||||
if (diff < 60 * 1000) return '刚刚';
|
||||
if (diff < 60 * 60 * 1000) return Math.floor(diff / (60 * 1000)) + '分钟前';
|
||||
if (date.toDateString() === now.toDateString()) {
|
||||
return date.getHours().toString().padStart(2, '0') + ':' + date.getMinutes().toString().padStart(2, '0');
|
||||
}
|
||||
const yesterday = new Date(now);
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
if (date.toDateString() === yesterday.toDateString()) {
|
||||
return '昨天 ' + date.getHours().toString().padStart(2, '0') + ':' + date.getMinutes().toString().padStart(2, '0');
|
||||
}
|
||||
return (date.getMonth() + 1) + '月' + date.getDate() + '日';
|
||||
},
|
||||
|
||||
navigateToChatPage(data) {
|
||||
if (!data || !data.message) {
|
||||
console.warn('通知数据无效,无法跳转');
|
||||
return;
|
||||
}
|
||||
|
||||
const msg = data.message;
|
||||
let path = '';
|
||||
let queryParam = null;
|
||||
|
||||
// 根据 notificationType 或消息字段判断
|
||||
if (data.notificationType === 'cs' || msg.teamId) {
|
||||
// 客服消息
|
||||
const teamId = data.teamId || msg.teamId;
|
||||
path = '/pages/kefuliaotian/kefuliaotian';
|
||||
queryParam = { teamId: teamId };
|
||||
} else if (data.notificationType === 'group' || msg.groupId) {
|
||||
// 群聊消息
|
||||
const groupId = data.groupId || msg.groupId;
|
||||
const orderId = data.orderId || msg.orderId || '';
|
||||
const groupName = data.groupName || '订单群聊';
|
||||
const groupAvatar = data.groupAvatar || '';
|
||||
const isCross = data.isCross || msg.isCross || 0;
|
||||
path = '/pages/qunliaotian/qunliaotian';
|
||||
queryParam = {
|
||||
groupId,
|
||||
orderId,
|
||||
groupName,
|
||||
groupAvatar,
|
||||
isCross
|
||||
};
|
||||
} else {
|
||||
// 默认私聊
|
||||
const toUserId = data.senderId || msg.senderId;
|
||||
const toName = data.senderName || '用户';
|
||||
const toAvatar = data.avatar || '';
|
||||
path = '/pages/liaotian/liaotian';
|
||||
queryParam = {
|
||||
toUserId,
|
||||
toName,
|
||||
toAvatar
|
||||
};
|
||||
}
|
||||
|
||||
if (path && queryParam) {
|
||||
const encoded = encodeURIComponent(JSON.stringify(queryParam));
|
||||
wx.navigateTo({
|
||||
url: `${path}?data=${encoded}`,
|
||||
fail: (err) => {
|
||||
console.error('通知跳转失败:', err);
|
||||
wx.switchTab({ url: '/pages/xiaoxi/xiaoxi' });
|
||||
}
|
||||
});
|
||||
} else {
|
||||
wx.switchTab({ url: '/pages/xiaoxi/xiaoxi' });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
4
components/global-notification/global-notification.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {}
|
||||
}
|
||||
49
components/global-notification/global-notification.wxml
Normal file
@@ -0,0 +1,49 @@
|
||||
<view
|
||||
class="global-notification {{show ? 'show' : ''}} {{positionClass}} {{swipingClass}}"
|
||||
bindtap="onTap"
|
||||
bindtouchstart="onTouchStart"
|
||||
bindtouchmove="onTouchMove"
|
||||
bindtouchend="onTouchEnd"
|
||||
style="{{swipeStyle}}"
|
||||
data-notification-id="{{notificationData && notificationData.id ? notificationData.id : ''}}">
|
||||
|
||||
<!-- 滑动关闭指示器 -->
|
||||
<view class="notification-swipe-indicator {{isSwiping ? 'active' : ''}}">
|
||||
<view class="swipe-line"></view>
|
||||
</view>
|
||||
|
||||
<!-- 通知内容 -->
|
||||
<view class="notification-content">
|
||||
<!-- 左侧头像 -->
|
||||
<image class="notification-avatar" src="{{avatar}}" mode="aspectFill"></image>
|
||||
|
||||
<!-- 中间内容区域 -->
|
||||
<view class="notification-body">
|
||||
<view class="notification-title">{{title}}</view>
|
||||
<view class="notification-message">{{message}}</view>
|
||||
<view class="notification-time" wx:if="{{showTime}}">
|
||||
{{formatTime(notificationData ? notificationData.timestamp : '')}}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 右侧操作区域 -->
|
||||
<view class="notification-actions">
|
||||
<!-- 关闭按钮 -->
|
||||
<view class="notification-close" catchtap="onClose">
|
||||
<text class="close-icon">×</text>
|
||||
</view>
|
||||
|
||||
<!-- 免打扰按钮 -->
|
||||
<view class="notification-mute {{isMuted ? 'muted' : ''}}" catchtap="onMute" wx:if="{{showMuteButton}}">
|
||||
<text class="mute-icon">
|
||||
{{isMuted ? '🔕' : '🔔'}}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 进度条 -->
|
||||
<view class="notification-progress" wx:if="{{showProgress}}">
|
||||
<view class="progress-bar {{progress < 30 ? 'warning' : ''}}"></view>
|
||||
</view>
|
||||
</view>
|
||||
215
components/global-notification/global-notification.wxss
Normal file
@@ -0,0 +1,215 @@
|
||||
.global-notification {
|
||||
position: fixed;
|
||||
left: 20rpx;
|
||||
right: 20rpx;
|
||||
top: 0rpx;
|
||||
z-index: 99999;
|
||||
opacity: 0;
|
||||
transform: translateY(-120%);
|
||||
transition: all 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
border-radius: 24rpx;
|
||||
background: linear-gradient(135deg,
|
||||
rgba(41, 47, 61, 0.98) 0%,
|
||||
rgba(31, 36, 48, 0.98) 100%);
|
||||
backdrop-filter: blur(30rpx) saturate(180%);
|
||||
-webkit-backdrop-filter: blur(30rpx) saturate(180%);
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.08);
|
||||
box-shadow:
|
||||
0 20rpx 60rpx rgba(0, 0, 0, 0.4),
|
||||
0 0 0 1rpx rgba(255, 255, 255, 0.03),
|
||||
inset 0 1rpx 0 rgba(255, 255, 255, 0.1);
|
||||
min-height: 140rpx;
|
||||
touch-action: pan-y;
|
||||
}
|
||||
|
||||
.global-notification.show {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.notification-swipe-indicator {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.swipe-line {
|
||||
width: 40rpx;
|
||||
height: 4rpx;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border-radius: 2rpx;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.notification-swipe-indicator.active .swipe-line {
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
transform: scaleX(1.2);
|
||||
}
|
||||
|
||||
.notification-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 32rpx;
|
||||
min-height: 140rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.notification-avatar {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
border-radius: 44rpx;
|
||||
margin-right: 24rpx;
|
||||
flex-shrink: 0;
|
||||
border: 3rpx solid rgba(212, 175, 55, 0.6);
|
||||
box-shadow:
|
||||
0 8rpx 24rpx rgba(212, 175, 55, 0.2),
|
||||
inset 0 1rpx 0 rgba(255, 255, 255, 0.3);
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.notification-body {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.notification-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
background: linear-gradient(135deg, #D4AF37 0%, #FFD700 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.notification-message {
|
||||
font-size: 28rpx;
|
||||
line-height: 1.4;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
color: rgba(255, 255, 255, 0.95);
|
||||
text-shadow: 0 1rpx 2rpx rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.notification-time {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
margin-top: 4rpx;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.notification-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 20rpx;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.notification-close,
|
||||
.notification-mute {
|
||||
width: 56rpx;
|
||||
height: 56rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 28rpx;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.1);
|
||||
transition: all 0.3s;
|
||||
flex-shrink: 0;
|
||||
box-shadow:
|
||||
0 4rpx 12rpx rgba(0, 0, 0, 0.2),
|
||||
inset 0 1rpx 0 rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.notification-close:active,
|
||||
.notification-mute:active {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
transform: scale(0.92);
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
font-size: 32rpx;
|
||||
font-weight: 300;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.mute-icon {
|
||||
font-size: 28rpx;
|
||||
line-height: 0;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.notification-mute.muted .mute-icon {
|
||||
color: #D4AF37;
|
||||
}
|
||||
|
||||
.notification-progress {
|
||||
height: 6rpx;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
overflow: hidden;
|
||||
border-radius: 0 0 24rpx 24rpx;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: linear-gradient(90deg,
|
||||
#D4AF37 0%,
|
||||
#FFD700 50%,
|
||||
#D4AF37 100%);
|
||||
background-size: 200% 100%;
|
||||
transition: width 0.1s linear;
|
||||
box-shadow: 0 0 20rpx rgba(212, 175, 55, 0.4);
|
||||
animation: progressShine 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes progressShine {
|
||||
0% { background-position: 200% 0; }
|
||||
100% { background-position: -200% 0; }
|
||||
}
|
||||
|
||||
.progress-bar.warning {
|
||||
background: linear-gradient(90deg,
|
||||
#ff3b30 0%,
|
||||
#ff6b6b 50%,
|
||||
#ff3b30 100%);
|
||||
animation: progressShine 1s linear infinite;
|
||||
}
|
||||
|
||||
.global-notification.swiping {
|
||||
opacity: 0.7;
|
||||
transform: translateY(calc(var(--swipe-distance, 0) * -1));
|
||||
}
|
||||
|
||||
.global-notification.swiping-up {
|
||||
opacity: 0.3;
|
||||
transform: translateY(calc(var(--swipe-distance, 0) * -1));
|
||||
}
|
||||
|
||||
.global-notification:active:not(.swiping) {
|
||||
transform: scale(0.995) translateY(0);
|
||||
transition: transform 0.1s;
|
||||
}
|
||||
102
components/navigation-bar/navigation-bar.js
Normal file
@@ -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 }, {})
|
||||
}
|
||||
},
|
||||
})
|
||||
5
components/navigation-bar/navigation-bar.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"component": true,
|
||||
"styleIsolation": "apply-shared",
|
||||
"usingComponents": {}
|
||||
}
|
||||
64
components/navigation-bar/navigation-bar.wxml
Normal file
@@ -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>
|
||||
96
components/navigation-bar/navigation-bar.wxss
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
102
components/order-card/order-card.js
Normal file
@@ -0,0 +1,102 @@
|
||||
const app = getApp();
|
||||
|
||||
Component({
|
||||
properties: {
|
||||
orderId: { type: String, value: '' }
|
||||
},
|
||||
|
||||
data: {
|
||||
order: null,
|
||||
loading: false,
|
||||
errorMsg: '',
|
||||
// 跨平台字段
|
||||
isCross: false, // 是否跨平台订单
|
||||
dispatchType: 0, // 1-我方派单,2-对方派单
|
||||
partnerClubId: '', // 对方俱乐部ID
|
||||
},
|
||||
|
||||
lifetimes: {
|
||||
attached() {
|
||||
if (this.properties.orderId) {
|
||||
this.fetchOrderDetail();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 获取订单详情(含跨平台字段)
|
||||
fetchOrderDetail() {
|
||||
if (!this.properties.orderId) return;
|
||||
this.setData({ loading: true, errorMsg: '' });
|
||||
const baseUrl = app.globalData.apiBaseUrl;
|
||||
wx.request({
|
||||
url: `${baseUrl}/dingdan/order_detail/`, // 需后端返回 is_cross, dispatch_type, partner_club_id
|
||||
method: 'POST',
|
||||
data: { order_id: this.properties.orderId },
|
||||
header: { 'content-type': 'application/json' },
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200 && res.data && res.data.code === 0) {
|
||||
const order = res.data.data;
|
||||
// 处理图片URL
|
||||
if (order.tupian && !order.tupian.startsWith('http')) {
|
||||
order.tupian = app.globalData.ossImageUrl + order.tupian;
|
||||
}
|
||||
this.setData({
|
||||
order,
|
||||
loading: false,
|
||||
isCross: order.is_cross === 1,
|
||||
dispatchType: order.dispatch_type || 0,
|
||||
partnerClubId: order.partner_club_id || '',
|
||||
});
|
||||
// 通知父页面状态
|
||||
this.triggerEvent('statusready', {
|
||||
isCross: this.data.isCross,
|
||||
dispatchType: this.data.dispatchType,
|
||||
partnerClubId: this.data.partnerClubId
|
||||
});
|
||||
} else {
|
||||
this.setData({ errorMsg: res.data?.msg || '获取订单失败', loading: false });
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
this.setData({ errorMsg: '网络请求失败', loading: false });
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 发送订单卡片消息(原有)
|
||||
sendOrderMessage() {
|
||||
if (!this.data.order) return;
|
||||
this.triggerEvent('sendorder', { order: this.data.order });
|
||||
},
|
||||
|
||||
// 跨平台消息转发(供父页面调用)
|
||||
forwardMessage(payload) {
|
||||
const that = this;
|
||||
const baseUrl = app.globalData.apiBaseUrl;
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.request({
|
||||
url: `${baseUrl}/dingdan/kptxxts`,
|
||||
method: 'POST',
|
||||
data: {
|
||||
order_id: that.properties.orderId,
|
||||
message_type: payload.type || 'text',
|
||||
content: payload.content || '',
|
||||
image_url: payload.imageUrl || '', // 图片等
|
||||
},
|
||||
header: { 'content-type': 'application/json' },
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200 && res.data && res.data.code === 0) {
|
||||
resolve(res.data);
|
||||
} else {
|
||||
reject(res.data);
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
4
components/order-card/order-card.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {}
|
||||
}
|
||||
20
components/order-card/order-card.wxml
Normal file
@@ -0,0 +1,20 @@
|
||||
<view class="order-card" wx:if="{{order}}">
|
||||
<view class="card-header">
|
||||
<text class="card-title">关联订单</text>
|
||||
<text class="card-id">{{order.dingdan_id}}</text>
|
||||
<!-- 跨平台标记 -->
|
||||
<text class="cross-tag" wx:if="{{isCross}}">跨平台</text>
|
||||
</view>
|
||||
<view class="card-body" bindtap="sendOrderMessage">
|
||||
<image class="card-img" src="{{order.tupian || '/images/default-goods.png'}}" mode="aspectFill" />
|
||||
<view class="card-info">
|
||||
<text class="card-jieshao">{{order.jieshao || '暂无介绍'}}</text>
|
||||
<text class="card-jine">¥{{order.jine}}</text>
|
||||
</view>
|
||||
<view class="card-send-btn">发送</view>
|
||||
</view>
|
||||
<view wx:if="{{errorMsg}}" class="card-error">{{errorMsg}}</view>
|
||||
</view>
|
||||
<view class="order-card" wx:elif="{{loading}}">
|
||||
<text class="loading-text">加载订单信息...</text>
|
||||
</view>
|
||||
39
components/order-card/order-card.wxss
Normal file
@@ -0,0 +1,39 @@
|
||||
.order-card {
|
||||
background: #fff;
|
||||
margin: 15rpx 20rpx;
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.05);
|
||||
}
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16rpx 20rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
.card-title { font-size: 26rpx; color: #666; }
|
||||
.card-id { font-size: 24rpx; color: #999; margin-left: 15rpx; }
|
||||
.cross-tag {
|
||||
margin-left: auto;
|
||||
background: #ff9900;
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
padding: 2rpx 12rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
.card-body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
}
|
||||
.card-img { width: 100rpx; height: 100rpx; border-radius: 8rpx; margin-right: 15rpx; }
|
||||
.card-info { flex: 1; }
|
||||
.card-jieshao { font-size: 28rpx; color: #333; }
|
||||
.card-jine { font-size: 26rpx; color: #e74c3c; }
|
||||
.card-send-btn {
|
||||
width: 80rpx; height: 50rpx; line-height: 50rpx;
|
||||
text-align: center; background: #07c160; color: #fff;
|
||||
border-radius: 8rpx; font-size: 24rpx;
|
||||
}
|
||||
.card-error { padding: 20rpx; color: #e74c3c; text-align: center; }
|
||||
.loading-text { display: block; padding: 20rpx; text-align: center; color: #999; }
|
||||
16
components/order-sender/order-sender.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const app = getApp();
|
||||
|
||||
Component({
|
||||
properties: {
|
||||
visible: Boolean,
|
||||
showDetail: { type: Boolean, value: false }
|
||||
},
|
||||
|
||||
data: {},
|
||||
|
||||
methods: {
|
||||
close() {
|
||||
this.triggerEvent('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
4
components/order-sender/order-sender.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {}
|
||||
}
|
||||
12
components/order-sender/order-sender.wxml
Normal file
@@ -0,0 +1,12 @@
|
||||
<view class="order-sender-mask" wx:if="{{visible}}" catchtap="close"></view>
|
||||
<view class="order-sender {{visible ? 'show' : ''}}">
|
||||
<view class="header">
|
||||
<text class="title">提示</text>
|
||||
<text class="close" bindtap="close">✕</text>
|
||||
</view>
|
||||
<view class="content">
|
||||
<view class="icon">📋</view>
|
||||
<text class="notice">此功能暂未开放</text>
|
||||
<text class="sub">敬请期待</text>
|
||||
</view>
|
||||
</view>
|
||||
45
components/order-sender/order-sender.wxss
Normal file
@@ -0,0 +1,45 @@
|
||||
.order-sender-mask {
|
||||
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: rgba(0,0,0,0.5);
|
||||
z-index: 2000;
|
||||
}
|
||||
.order-sender {
|
||||
position: fixed; bottom: 0; left: 0; right: 0;
|
||||
height: 360rpx;
|
||||
background: #fff;
|
||||
border-radius: 24rpx 24rpx 0 0;
|
||||
z-index: 2001;
|
||||
transform: translateY(100%);
|
||||
transition: 0.25s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.order-sender.show { transform: translateY(0); }
|
||||
|
||||
.header {
|
||||
display: flex; justify-content: space-between;
|
||||
padding: 24rpx 30rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
.title { font-size: 32rpx; font-weight: 600; }
|
||||
.close { font-size: 44rpx; color: #999; padding: 0 12rpx; }
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
.icon { font-size: 80rpx; margin-bottom: 20rpx; }
|
||||
.notice {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
.sub {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
325
components/popup-notice/popup-notice.js
Normal file
@@ -0,0 +1,325 @@
|
||||
// components/popup-notice/popup-notice.js
|
||||
import PopupService from '../../services/popupService.js';
|
||||
|
||||
Component({
|
||||
data: {
|
||||
// 原有全屏弹窗数据
|
||||
visible: false,
|
||||
title: '',
|
||||
images: [],
|
||||
processedImages: [],
|
||||
hasImages: false,
|
||||
hasTextOnly: false,
|
||||
textContent: '',
|
||||
duration: 0,
|
||||
remainingSeconds: 0,
|
||||
muteChecked: false,
|
||||
showMuteCheckbox: false,
|
||||
isFullscreen: false,
|
||||
timer: null,
|
||||
|
||||
// 悬浮窗相关数据
|
||||
isMinimized: false,
|
||||
floatX: 20,
|
||||
floatY: 200,
|
||||
savedRemainingSeconds: 0,
|
||||
floatAvatarUrl: '',
|
||||
|
||||
// 🆕 自定义预览数据
|
||||
showCustomPreview: false, // 是否显示自定义预览遮罩
|
||||
previewUrls: [], // 当前预览的图片URL列表
|
||||
currentPreviewIndex: 0, // 当前预览图片索引
|
||||
},
|
||||
|
||||
lifetimes: {
|
||||
detached() {
|
||||
this.clearTimer();
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// ========== 原有所有方法(完全不变,一字不漏) ==========
|
||||
async show(config, onCloseCallback) {
|
||||
const { title, images, duration, showMuteCheckbox, serverTime } = config;
|
||||
this.onCloseCallback = onCloseCallback;
|
||||
|
||||
let safeImages = JSON.parse(JSON.stringify(images || []));
|
||||
const hasImages = safeImages.length > 0;
|
||||
let hasTextOnly = false, textContent = '';
|
||||
if (!hasImages) {
|
||||
hasTextOnly = true;
|
||||
textContent = config.text || '暂无内容';
|
||||
}
|
||||
|
||||
this.setData({
|
||||
visible: true,
|
||||
isMinimized: false,
|
||||
title: title || '',
|
||||
images: safeImages,
|
||||
hasImages,
|
||||
hasTextOnly,
|
||||
textContent,
|
||||
duration: duration || 0,
|
||||
remainingSeconds: duration || 0,
|
||||
muteChecked: false,
|
||||
showMuteCheckbox: showMuteCheckbox || false,
|
||||
isFullscreen: false,
|
||||
processedImages: [],
|
||||
});
|
||||
|
||||
if (duration > 0) {
|
||||
this.startCountdown();
|
||||
}
|
||||
|
||||
if (hasImages) {
|
||||
const processed = [];
|
||||
const screenWidth = wx.getSystemInfoSync().windowWidth;
|
||||
const containerWidth = screenWidth * 0.9;
|
||||
const imageWidth = containerWidth - 60;
|
||||
for (let i = 0; i < safeImages.length; i++) {
|
||||
const img = safeImages[i];
|
||||
const fullUrl = this.getFullImageUrl(img.url);
|
||||
let imageHeight = 200;
|
||||
try {
|
||||
const res = await this.getImageInfoPromise(fullUrl);
|
||||
const realHeight = (imageWidth * res.height) / res.width;
|
||||
imageHeight = realHeight;
|
||||
} catch (e) {
|
||||
console.error('获取图片信息失败', fullUrl, e);
|
||||
}
|
||||
processed.push({
|
||||
url: fullUrl,
|
||||
text: img.text,
|
||||
height: imageHeight,
|
||||
});
|
||||
}
|
||||
this.setData({ processedImages: processed });
|
||||
}
|
||||
},
|
||||
|
||||
getImageInfoPromise(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.getImageInfo({
|
||||
src: url,
|
||||
success: resolve,
|
||||
fail: reject,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
hide(muteToday = false) {
|
||||
this.clearTimer();
|
||||
this.setData({
|
||||
visible: false,
|
||||
isMinimized: false
|
||||
});
|
||||
if (this.onCloseCallback) {
|
||||
this.onCloseCallback({ muteToday });
|
||||
}
|
||||
},
|
||||
|
||||
startCountdown() {
|
||||
this.clearTimer();
|
||||
this.timer = setInterval(() => {
|
||||
let sec = this.data.remainingSeconds;
|
||||
if (sec <= 1) {
|
||||
this.clearTimer();
|
||||
this.setData({ remainingSeconds: 0 });
|
||||
} else {
|
||||
this.setData({ remainingSeconds: sec - 1 });
|
||||
}
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
clearTimer() {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
},
|
||||
|
||||
onCloseTap() {
|
||||
if (this.data.remainingSeconds > 0) {
|
||||
wx.showToast({
|
||||
title: `请阅读${this.data.remainingSeconds}秒后再关闭`,
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (this.data.showMuteCheckbox && this.data.muteChecked) {
|
||||
wx.showModal({
|
||||
title: '提示',
|
||||
content: '今天将不再收到本页面的任何公告,确认吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) this.hide(true);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
this.hide(false);
|
||||
}
|
||||
},
|
||||
|
||||
onMuteChange(e) {
|
||||
this.setData({ muteChecked: e.detail.value.length > 0 });
|
||||
},
|
||||
|
||||
// 🆕 替换原有的 onPreviewImage,改用自定义预览(不触发 onShow)
|
||||
onCustomPreview(e) {
|
||||
const index = e.currentTarget.dataset.index;
|
||||
const urls = this.data.processedImages.map(img => img.url);
|
||||
this.setData({
|
||||
showCustomPreview: true,
|
||||
previewUrls: urls,
|
||||
currentPreviewIndex: index !== undefined ? index : 0
|
||||
});
|
||||
},
|
||||
|
||||
// 🆕 关闭自定义预览
|
||||
onCloseCustomPreview() {
|
||||
this.setData({
|
||||
showCustomPreview: false,
|
||||
previewUrls: [],
|
||||
currentPreviewIndex: 0
|
||||
});
|
||||
},
|
||||
|
||||
// 🆕 swiper 切换事件
|
||||
onPreviewSwiperChange(e) {
|
||||
this.setData({
|
||||
currentPreviewIndex: e.detail.current
|
||||
});
|
||||
},
|
||||
|
||||
// 保留原 onPreviewImage 方法以防外部调用,但不再使用
|
||||
onPreviewImage(e) {
|
||||
// 重定向到自定义预览
|
||||
this.onCustomPreview(e);
|
||||
},
|
||||
|
||||
onImageError(e) {
|
||||
console.error('图片加载失败:', e.currentTarget.dataset.url);
|
||||
},
|
||||
|
||||
toggleFullscreen() {
|
||||
this.setData({ isFullscreen: !this.data.isFullscreen });
|
||||
},
|
||||
|
||||
getFullImageUrl(relativeUrl) {
|
||||
if (!relativeUrl) return '';
|
||||
if (relativeUrl.startsWith('http')) return relativeUrl;
|
||||
const app = getApp();
|
||||
let base = app.globalData.ossImageUrl || '';
|
||||
base = base.replace(/\/+$/, '');
|
||||
const clean = relativeUrl.replace(/^\/+/, '');
|
||||
return base ? base + '/' + clean : clean;
|
||||
},
|
||||
|
||||
preventMove() {},
|
||||
|
||||
onMinimize() {
|
||||
const remaining = this.data.remainingSeconds;
|
||||
this.clearTimer();
|
||||
|
||||
const app = getApp();
|
||||
let avatarUrl = '';
|
||||
if (app.globalData.morentouxiang) {
|
||||
avatarUrl = app.globalData.morentouxiang;
|
||||
} else {
|
||||
// 使用内嵌的默认头像 base64(灰色圆形),确保绝对显示,不依赖外部图片
|
||||
avatarUrl = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48Y2lyY2xlIGN4PSI1MCIgY3k9IjUwIiByPSI1MCIgZmlsbD0iI2NjY2NjYyIvPjxwYXRoIGQ9Ik01MCAyNWMtMTMuOCAwLTI1IDExLjItMjUgMjUgMCAzLjIgMS4yIDYuMSAzLjIgOC4zQzM0LjQgNzAuNyA0Mi4xIDc1IDUwIDc1czE1LjYtNC4zIDIxLjgtMTEuN0M3My44IDU2LjEgNzUgNTMuMiA3NSA1MGMwLTEzLjgtMTEuMi0yNS0yNS0yNXoiIGZpbGw9IiNmZmZmZmYiLz48L3N2Zz4=';
|
||||
}
|
||||
|
||||
if (avatarUrl && !avatarUrl.startsWith('http') && !avatarUrl.startsWith('data:')) {
|
||||
const ossBase = app.globalData.ossImageUrl || '';
|
||||
avatarUrl = ossBase.replace(/\/+$/, '') + '/' + avatarUrl.replace(/^\/+/, '');
|
||||
}
|
||||
|
||||
this.setData({
|
||||
visible: false,
|
||||
isMinimized: true,
|
||||
savedRemainingSeconds: remaining,
|
||||
floatX: 20,
|
||||
floatY: 200,
|
||||
floatAvatarUrl: avatarUrl
|
||||
});
|
||||
},
|
||||
|
||||
onFloatChange(e) {
|
||||
this.setData({
|
||||
floatX: e.detail.x,
|
||||
floatY: e.detail.y
|
||||
});
|
||||
},
|
||||
|
||||
onFloatTap() {
|
||||
this.setData({
|
||||
visible: true,
|
||||
isMinimized: false,
|
||||
remainingSeconds: this.data.savedRemainingSeconds
|
||||
});
|
||||
|
||||
if (this.data.duration > 0 && this.data.savedRemainingSeconds > 0) {
|
||||
this.startCountdown();
|
||||
}
|
||||
},
|
||||
|
||||
onFloatAvatarError() {
|
||||
this.setData({
|
||||
floatAvatarUrl: '/images/avatar-placeholder.png'
|
||||
});
|
||||
},
|
||||
|
||||
cleanup() {
|
||||
this.clearTimer();
|
||||
this.setData({
|
||||
visible: false,
|
||||
isMinimized: false
|
||||
});
|
||||
PopupService.reset();
|
||||
},
|
||||
|
||||
onFloatTouchStart(e) {
|
||||
const touch = e.touches[0];
|
||||
this.touchStartX = touch.clientX;
|
||||
this.touchStartY = touch.clientY;
|
||||
this.startFloatX = this.data.floatX;
|
||||
this.startFloatY = this.data.floatY;
|
||||
this.hasMoved = false;
|
||||
return false;
|
||||
},
|
||||
|
||||
onFloatTouchMove(e) {
|
||||
const touch = e.touches[0];
|
||||
const deltaX = touch.clientX - this.touchStartX;
|
||||
const deltaY = touch.clientY - this.touchStartY;
|
||||
|
||||
if (Math.abs(deltaX) > 5 || Math.abs(deltaY) > 5) {
|
||||
this.hasMoved = true;
|
||||
}
|
||||
|
||||
let newX = this.startFloatX + deltaX;
|
||||
let newY = this.startFloatY + deltaY;
|
||||
|
||||
const systemInfo = wx.getSystemInfoSync();
|
||||
const screenWidth = systemInfo.windowWidth;
|
||||
const screenHeight = systemInfo.windowHeight;
|
||||
const estimatedSize = 60;
|
||||
|
||||
newX = Math.max(0, Math.min(newX, screenWidth - estimatedSize));
|
||||
newY = Math.max(0, Math.min(newY, screenHeight - estimatedSize));
|
||||
this.setData({ floatX: newX, floatY: newY });
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
onFloatTouchEnd(e) {
|
||||
if (!this.hasMoved) {
|
||||
this.onFloatTap();
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
// 🆕 阻止事件冒泡(用于预览遮罩内部点击不关闭)
|
||||
stopPropagation() {}
|
||||
}
|
||||
});
|
||||
4
components/popup-notice/popup-notice.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {}
|
||||
}
|
||||
111
components/popup-notice/popup-notice.wxml
Normal file
@@ -0,0 +1,111 @@
|
||||
<!-- components/popup-notice/popup-notice.wxml -->
|
||||
|
||||
<!-- 全屏弹窗遮罩层(完全不变) -->
|
||||
<view class="popup-mask" wx:if="{{visible}}" catchtouchmove="preventMove">
|
||||
<view class="popup-container {{isFullscreen ? 'fullscreen' : ''}}">
|
||||
<!-- 右上角操作按钮组 -->
|
||||
<view class="action-buttons">
|
||||
<!-- 最小化按钮 -->
|
||||
<view class="minimize-btn" bindtap="onMinimize">
|
||||
<text class="btn-icon">−</text>
|
||||
</view>
|
||||
<!-- 全屏切换按钮 -->
|
||||
<view class="fullscreen-btn" bindtap="toggleFullscreen">
|
||||
<text class="btn-icon">{{isFullscreen ? '✕' : '⛶'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 标题 -->
|
||||
<view wx:if="{{title}}" class="popup-title">{{title}}</view>
|
||||
|
||||
<!-- 可滚动内容区 -->
|
||||
<view class="scroll-wrapper">
|
||||
<scroll-view scroll-y class="scroll-view" enhanced show-scrollbar="{{true}}">
|
||||
<view wx:if="{{hasTextOnly}}" class="text-content">{{textContent}}</view>
|
||||
<view wx:else>
|
||||
<view wx:for="{{processedImages}}" wx:key="index" class="image-block">
|
||||
<view wx:if="{{item.text}}" class="block-text">{{item.text}}</view>
|
||||
<view class="image-padding">
|
||||
<image
|
||||
class="block-image"
|
||||
src="{{item.url}}"
|
||||
style="width:100%; height:{{item.height}}px; display:block;"
|
||||
data-index="{{index}}"
|
||||
data-url="{{item.url}}"
|
||||
bindtap="onCustomPreview"
|
||||
binderror="onImageError"
|
||||
lazy-load="{{false}}"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 底部固定区 -->
|
||||
<view class="bottom-fixed">
|
||||
<view wx:if="{{remainingSeconds > 0}}" class="countdown-tip">
|
||||
请阅读 {{remainingSeconds}} 秒后关闭
|
||||
</view>
|
||||
<view wx:if="{{remainingSeconds === 0 && showMuteCheckbox}}" class="mute-row">
|
||||
<checkbox-group bindchange="onMuteChange">
|
||||
<checkbox value="mute" color="#000" />
|
||||
<text class="mute-label">今日不再提示</text>
|
||||
</checkbox-group>
|
||||
</view>
|
||||
<!-- 关闭按钮 -->
|
||||
<view
|
||||
class="close-btn {{remainingSeconds > 0 ? 'close-btn-disabled' : ''}}"
|
||||
bindtap="{{remainingSeconds > 0 ? '' : 'onCloseTap'}}"
|
||||
>
|
||||
<text class="close-btn-text">{{remainingSeconds > 0 ? '阅读中...' : '关闭'}}</text>
|
||||
<view class="close-btn-sweep" wx:if="{{remainingSeconds === 0}}"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 悬浮窗(自定义触摸拖动) -->
|
||||
<view
|
||||
class="floating-avatar-wrapper"
|
||||
wx:if="{{isMinimized}}"
|
||||
style="left: {{floatX}}px; top: {{floatY}}px;"
|
||||
catchtouchstart="onFloatTouchStart"
|
||||
catchtouchmove="onFloatTouchMove"
|
||||
catchtouchend="onFloatTouchEnd"
|
||||
>
|
||||
<image
|
||||
class="floating-avatar"
|
||||
src="{{floatAvatarUrl}}"
|
||||
mode="aspectFill"
|
||||
binderror="onFloatAvatarError"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 🆕 自定义图片预览遮罩(全屏,不触发页面生命周期) -->
|
||||
<view
|
||||
class="custom-preview-mask"
|
||||
wx:if="{{showCustomPreview}}"
|
||||
catchtouchmove="preventMove"
|
||||
bindtap="onCloseCustomPreview"
|
||||
>
|
||||
<view class="preview-header">
|
||||
<text class="preview-index">{{currentPreviewIndex + 1}} / {{previewUrls.length}}</text>
|
||||
<view class="preview-close" bindtap="onCloseCustomPreview">×</view>
|
||||
</view>
|
||||
<swiper
|
||||
class="preview-swiper"
|
||||
current="{{currentPreviewIndex}}"
|
||||
bindchange="onPreviewSwiperChange"
|
||||
indicator-dots="{{false}}"
|
||||
>
|
||||
<swiper-item wx:for="{{previewUrls}}" wx:key="index">
|
||||
<image
|
||||
src="{{item}}"
|
||||
mode="aspectFit"
|
||||
class="preview-image"
|
||||
catchtap="stopPropagation"
|
||||
/>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
295
components/popup-notice/popup-notice.wxss
Normal file
@@ -0,0 +1,295 @@
|
||||
/* components/popup-notice/popup-notice.wxss */
|
||||
|
||||
/* ========== 全屏弹窗样式(原有,完全不变) ========== */
|
||||
.popup-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
.popup-container {
|
||||
width: 90%;
|
||||
height: 85vh;
|
||||
background: white;
|
||||
border-radius: 32rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.popup-container.fullscreen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
position: absolute;
|
||||
top: 20rpx;
|
||||
right: 20rpx;
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.minimize-btn,
|
||||
.fullscreen-btn {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
backdrop-filter: blur(4rpx);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.minimize-btn:active,
|
||||
.fullscreen-btn:active {
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
font-size: 36rpx;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.popup-title {
|
||||
font-size: 40rpx;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
padding: 30rpx 30rpx 20rpx;
|
||||
border-bottom: 2rpx solid #eee;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.scroll-wrapper {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.scroll-view {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.text-content {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
line-height: 1.6;
|
||||
padding: 30rpx;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.image-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.block-text {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
padding: 16rpx 30rpx 4rpx 30rpx;
|
||||
text-align: left;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.image-padding {
|
||||
padding: 0 30rpx;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.block-image {
|
||||
width: 100%;
|
||||
display: block;
|
||||
background: #f5f5f5;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.bottom-fixed {
|
||||
background: white;
|
||||
border-top: 1rpx solid #eee;
|
||||
padding: 20rpx 0 30rpx 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.countdown-tip {
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
color: #ff6600;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.mute-row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.mute-label {
|
||||
font-size: 28rpx;
|
||||
color: #555;
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
|
||||
/* 🔥 关闭按钮(替换原有 button,增加扫光) */
|
||||
.close-btn {
|
||||
width: 600rpx;
|
||||
height: 100rpx;
|
||||
background: #1a1a1a;
|
||||
border-radius: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.close-btn:active {
|
||||
opacity: 0.85;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.close-btn-disabled {
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.close-btn-text {
|
||||
color: white;
|
||||
font-size: 40rpx;
|
||||
font-weight: bold;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.close-btn-sweep {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.5), transparent);
|
||||
transform: skewX(-20deg);
|
||||
animation: sweep 2.5s infinite ease-in-out;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@keyframes sweep {
|
||||
0% { left: -100%; }
|
||||
40% { left: 100%; }
|
||||
100% { left: 100%; }
|
||||
}
|
||||
|
||||
/* ========== 悬浮窗样式(纯圆形,适配自定义触摸拖动) ========== */
|
||||
.floating-avatar-wrapper {
|
||||
position: fixed;
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
z-index: 10001;
|
||||
transform: translateZ(0);
|
||||
will-change: left, top;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.floating-avatar {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.3);
|
||||
border: 2rpx solid rgba(255, 215, 0, 0.6);
|
||||
background: #f0f0f0;
|
||||
display: block;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
/* 以下原有 movable-view 相关样式已弃用,但保留以维持代码完整(用户要求不删代码) */
|
||||
.floating-area {
|
||||
display: none;
|
||||
}
|
||||
.floating-view {
|
||||
display: none;
|
||||
}
|
||||
.float-avatar-wrapper {
|
||||
display: none;
|
||||
}
|
||||
.float-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ========== 自定义图片预览遮罩样式(全屏,不触发 onShow) ========== */
|
||||
.custom-preview-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.95);
|
||||
z-index: 100000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.preview-header {
|
||||
height: 100rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 30rpx;
|
||||
color: white;
|
||||
font-size: 32rpx;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.preview-index {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.preview-close {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 48rpx;
|
||||
color: white;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.preview-swiper {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.preview-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
109
custom-tab-bar/index.js
Normal file
@@ -0,0 +1,109 @@
|
||||
// custom-tab-bar/index.js — 三端固定,无身份切换
|
||||
const app = getApp();
|
||||
import { getTabRole } from '../utils/primary-role.js';
|
||||
|
||||
const roleCfg = {
|
||||
normal: {
|
||||
name: '点单老板',
|
||||
tabList: [
|
||||
{ pagePath: 'pages/index/index', text: '商城', iconPath: '/images/zhuye.png', selectedIconPath: '/images/zhuye.png' },
|
||||
{ pagePath: 'pages/fenlei/fenlei', text: '分类', iconPath: '/images/fenlei.png', selectedIconPath: '/images/fenlei.png' },
|
||||
{ pagePath: 'pages/xiaoxi/xiaoxi', text: '消息', iconPath: '/images/xiaoxi.png', selectedIconPath: '/images/xiaoxi.png' },
|
||||
{ pagePath: 'pages/wode/wode', text: '我的', iconPath: '/images/wode.png', selectedIconPath: '/images/wode.png' },
|
||||
],
|
||||
},
|
||||
dashou: {
|
||||
name: '打手',
|
||||
tabList: [
|
||||
{ pagePath: 'pages/jiedan/jiedan', text: '接单池', iconPath: '/images/jiedanchi.png', selectedIconPath: '/images/jiedanchi.png' },
|
||||
{ pagePath: 'pages/dashoudingdan/dashoudingdan', text: '订单', iconPath: '/images/dingdan.png', selectedIconPath: '/images/dingdan.png' },
|
||||
{ pagePath: 'pages/xiaoxi/xiaoxi', text: '消息', iconPath: '/images/xiaoxi.png', selectedIconPath: '/images/xiaoxi.png' },
|
||||
{ pagePath: 'pages/dashouduan/dashouduan', text: '我的', iconPath: '/images/wode.png', selectedIconPath: '/images/wode.png' },
|
||||
],
|
||||
},
|
||||
shangjia: {
|
||||
name: '商家',
|
||||
tabList: [
|
||||
{ pagePath: 'pages/sjdingdan/sjdingdan', text: '订单', iconPath: '/images/dingdan.png', selectedIconPath: '/images/dingdan.png' },
|
||||
{ pagePath: 'pages/xiaoxi/xiaoxi', text: '消息', iconPath: '/images/xiaoxi.png', selectedIconPath: '/images/xiaoxi.png' },
|
||||
{ pagePath: 'pages/shangjiaduan/shangjiaduan', text: '我的', iconPath: '/images/wode.png', selectedIconPath: '/images/wode.png' },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
Component({
|
||||
data: {
|
||||
selectedIndex: 0,
|
||||
tabList: [],
|
||||
currentRole: 'normal',
|
||||
badgeText: '',
|
||||
},
|
||||
|
||||
lifetimes: {
|
||||
attached() {
|
||||
this.refresh();
|
||||
this._badgeListener = (data) => {
|
||||
if (data.badgeText !== undefined) {
|
||||
this.setData({ badgeText: data.badgeText });
|
||||
}
|
||||
};
|
||||
this._roleListener = () => this.refresh();
|
||||
app.on('tabBarBadgeChanged', this._badgeListener);
|
||||
app.on('currentRoleChanged', this._roleListener);
|
||||
},
|
||||
detached() {
|
||||
if (this._badgeListener) app.off('tabBarBadgeChanged', this._badgeListener);
|
||||
if (this._roleListener) app.off('currentRoleChanged', this._roleListener);
|
||||
},
|
||||
},
|
||||
|
||||
pageLifetimes: {
|
||||
show() {
|
||||
this.refresh();
|
||||
const globalBadge = app.globalData.messageManager?.tabBarBadgeText || '';
|
||||
if (this.data.badgeText !== globalBadge) {
|
||||
this.setData({ badgeText: globalBadge });
|
||||
}
|
||||
this._syncUnreadWithDebounce();
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
_syncUnreadWithDebounce() {
|
||||
if (this._syncTimer) clearTimeout(this._syncTimer);
|
||||
this._syncTimer = setTimeout(() => {
|
||||
if (app && app.loadConversations) app.loadConversations();
|
||||
}, 300);
|
||||
},
|
||||
|
||||
refresh() {
|
||||
const token = wx.getStorageSync('token');
|
||||
let role = getTabRole(app);
|
||||
if (!token) {
|
||||
role = 'normal';
|
||||
}
|
||||
app.globalData.currentRole = role;
|
||||
wx.setStorageSync('currentRole', role);
|
||||
|
||||
let cfg = roleCfg[role];
|
||||
if (!cfg) {
|
||||
cfg = roleCfg.normal;
|
||||
role = 'normal';
|
||||
}
|
||||
|
||||
const list = cfg.tabList.map((v, i) => ({ ...v, index: i }));
|
||||
const pages = getCurrentPages();
|
||||
const curRoute = pages.length ? pages[pages.length - 1].route : '';
|
||||
let idx = list.findIndex((t) => t.pagePath === curRoute);
|
||||
if (idx === -1) idx = 0;
|
||||
this.setData({ currentRole: role, tabList: list, selectedIndex: idx });
|
||||
},
|
||||
|
||||
switchTab(e) {
|
||||
const path = e.currentTarget.dataset.path;
|
||||
const idx = e.currentTarget.dataset.index;
|
||||
if (idx === this.data.selectedIndex) return;
|
||||
wx.redirectTo({ url: '/' + path });
|
||||
},
|
||||
},
|
||||
});
|
||||
4
custom-tab-bar/index.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {}
|
||||
}
|
||||
24
custom-tab-bar/index.wxml
Normal file
@@ -0,0 +1,24 @@
|
||||
<view class="custom-tab-bar">
|
||||
<view class="bar-row">
|
||||
<view class="tab-capsule">
|
||||
<block wx:for="{{tabList}}" wx:key="index">
|
||||
<view
|
||||
class="tab-item {{selectedIndex === index ? 'active' : ''}}"
|
||||
data-path="{{item.pagePath}}"
|
||||
data-index="{{index}}"
|
||||
catchtap="switchTab"
|
||||
>
|
||||
<view class="badge-container" wx:if="{{item.pagePath === 'pages/xiaoxi/xiaoxi' && badgeText}}">
|
||||
<view class="tab-badge">{{badgeText}}</view>
|
||||
</view>
|
||||
<image
|
||||
class="tab-icon"
|
||||
src="{{selectedIndex === index ? item.selectedIconPath : item.iconPath}}"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<text class="tab-text">{{item.text}}</text>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
123
custom-tab-bar/index.wxss
Normal file
@@ -0,0 +1,123 @@
|
||||
/* custom-tab-bar — 三端固定,全宽 Tab 胶囊 */
|
||||
|
||||
.custom-tab-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-end;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.bar-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 15rpx;
|
||||
padding: 0 24rpx;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tab-capsule {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
height: 100rpx;
|
||||
background: #000000;
|
||||
border-radius: 50rpx;
|
||||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.8);
|
||||
padding: 0 20rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tab-capsule::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 60%;
|
||||
height: 100%;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent 0%,
|
||||
rgba(255, 255, 255, 0.25) 20%,
|
||||
rgba(255, 255, 255, 0.35) 50%,
|
||||
rgba(255, 255, 255, 0.25) 80%,
|
||||
transparent 100%
|
||||
);
|
||||
animation: shine 3s infinite;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@keyframes shine {
|
||||
0% { left: -100%; }
|
||||
25% { left: 150%; }
|
||||
100% { left: 150%; }
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 70rpx;
|
||||
}
|
||||
|
||||
.tab-icon {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
margin-bottom: 4rpx;
|
||||
filter: brightness(0) invert(1);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.tab-text {
|
||||
font-size: 20rpx;
|
||||
color: #aaa;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.active .tab-icon {
|
||||
opacity: 1;
|
||||
filter: brightness(0) invert(1) drop-shadow(0 0 8rpx #00aaff);
|
||||
}
|
||||
|
||||
.active .tab-text {
|
||||
color: #00aaff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.badge-container {
|
||||
position: absolute;
|
||||
top: -10rpx;
|
||||
right: -15rpx;
|
||||
z-index: 100;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tab-badge {
|
||||
min-width: 36rpx;
|
||||
height: 36rpx;
|
||||
line-height: 36rpx;
|
||||
padding: 0 10rpx;
|
||||
background: #ff3b30;
|
||||
color: #fff;
|
||||
font-size: 22rpx;
|
||||
font-weight: bold;
|
||||
border-radius: 36rpx;
|
||||
text-align: center;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
page {
|
||||
padding-bottom: 100rpx;
|
||||
}
|
||||
4
images/clouds/cloud-btn-br.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg viewBox="0 0 60 40" fill="none" stroke="rgba(255,255,255,0.6)" stroke-width="1">
|
||||
<path d="M60 20 Q60 35 45 32 Q35 40 20 30 Q10 35 0 25" />
|
||||
<path d="M20 30 Q16 34 12 30 Q16 26 20 30" fill="rgba(255,255,255,0.15)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 229 B |
4
images/clouds/cloud-btn-tl.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg viewBox="0 0 60 40" fill="none" stroke="rgba(255,255,255,0.6)" stroke-width="1">
|
||||
<path d="M0 20 Q0 5 15 8 Q25 0 40 10 Q50 5 60 15" />
|
||||
<path d="M40 10 Q44 6 48 10 Q44 14 40 10" fill="rgba(255,255,255,0.15)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 223 B |
6
images/clouds/cloud-card-br.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg viewBox="0 0 100 80" fill="none" stroke="#b8943a" stroke-width="1.3">
|
||||
<path d="M100 40 Q100 65 82 68 Q70 80 45 72 Q30 80 15 65 Q0 60 0 40" />
|
||||
<path d="M45 72 Q40 77 35 72 Q40 67 45 72" fill="rgba(184,148,58,0.1)"/>
|
||||
<path d="M82 68 Q78 72 74 68 Q78 64 82 68" fill="rgba(184,148,58,0.1)"/>
|
||||
<path d="M95 45 Q95 58 88 62" stroke-width="0.8" opacity="0.4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 372 B |
6
images/clouds/cloud-card-tl.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg viewBox="0 0 100 80" fill="none" stroke="#b8943a" stroke-width="1.3">
|
||||
<path d="M0 40 Q0 15 18 12 Q30 0 55 8 Q70 0 85 15 Q100 20 100 40" />
|
||||
<path d="M55 8 Q60 3 65 8 Q60 13 55 8" fill="rgba(184,148,58,0.1)"/>
|
||||
<path d="M18 12 Q22 8 26 12 Q22 16 18 12" fill="rgba(184,148,58,0.1)"/>
|
||||
<path d="M5 35 Q5 22 12 18" stroke-width="0.8" opacity="0.4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 362 B |
5
images/clouds/cloud-top-l.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg viewBox="0 0 120 60" fill="none" stroke="#b8943a" stroke-width="1.2">
|
||||
<path d="M0 30 Q0 10 20 8 Q30 0 50 5 Q70 0 85 10 Q100 8 110 20 Q120 15 120 35" />
|
||||
<path d="M20 8 Q25 3 30 8 Q25 13 20 8" fill="rgba(184,148,58,0.12)"/>
|
||||
<path d="M85 10 Q90 5 95 10 Q90 15 85 10" fill="rgba(184,148,58,0.12)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 312 B |
5
images/clouds/cloud-top-r.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg viewBox="0 0 120 60" fill="none" stroke="#b8943a" stroke-width="1.2">
|
||||
<path d="M120 30 Q120 10 100 8 Q90 0 70 5 Q50 0 35 10 Q20 8 10 20 Q0 15 0 35" />
|
||||
<path d="M100 8 Q95 3 90 8 Q95 13 100 8" fill="rgba(184,148,58,0.12)"/>
|
||||
<path d="M35 10 Q30 5 25 10 Q30 15 35 10" fill="rgba(184,148,58,0.12)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 313 B |
BIN
images/dingdan.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
images/fenlei.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
images/gonggao.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
images/goumai.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
images/jiedanchi.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
images/jieshao.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
images/jinpaidashou.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
images/role-switch.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
images/shuaxin.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
images/sousuo.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
images/tiaoxuan.png
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
BIN
images/tuichu.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
images/weixinzhifu.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
images/wode.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
images/xiaoxi.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
images/yituikuan.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
images/yiwancheng.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
images/zhuanqu.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
images/zhuye.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
100
pages/cfss/cfss/cfss.js
Normal file
@@ -0,0 +1,100 @@
|
||||
// pages/cfss/cfss.js
|
||||
import request from '../../../utils/request.js'; // 向上4级就是根目录
|
||||
// 如果有 COS:
|
||||
|
||||
const app = getApp();
|
||||
|
||||
Page({
|
||||
data: {
|
||||
// 统计数字(由接口 /yonghu/cffktjhq 返回)
|
||||
stats: {
|
||||
fakuan: { total: 0, daijiaona: 0, shensuzhong: 0, yijiaona: 0, yibohui: 0 },
|
||||
jifen: { total: 0, daichuli: 0, yichuli: 0, daichuli_0: 0, shensuzhong_3: 0, yichufa_1: 0, yibohui_2: 0 }
|
||||
},
|
||||
|
||||
// 当前选中的主标签:'fakuan' 或 'jifen'
|
||||
activeTab: 'fakuan',
|
||||
|
||||
// 子标签状态值
|
||||
activeFakuanStatus: 1, // 罚款默认展示“待缴纳”
|
||||
activeJifenStatus: 0, // 积分默认展示“待处理”
|
||||
|
||||
// 搜索关键词(订单号)
|
||||
searchDingdan: '',
|
||||
|
||||
// 刷新触发器,传给子组件
|
||||
trigger: {
|
||||
fakuan: 0,
|
||||
jifen: 0
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.fetchTongji();
|
||||
},
|
||||
|
||||
onShow() {
|
||||
// 每次显示页面都重新拉取统计,确保数字最新
|
||||
this.fetchTongji();
|
||||
},
|
||||
|
||||
// 获取综合统计
|
||||
async fetchTongji() {
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/yonghu/cffktjhq',
|
||||
method: 'POST'
|
||||
});
|
||||
if (res.statusCode === 200 && res.data.code === 0) {
|
||||
this.setData({ stats: res.data.data });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取处罚统计失败', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 切换主Tab(罚款 / 积分)
|
||||
switchMainTab(e) {
|
||||
const tab = e.currentTarget.dataset.tab;
|
||||
if (tab === this.data.activeTab) return;
|
||||
this.setData({ activeTab: tab });
|
||||
},
|
||||
|
||||
// 切换罚款状态子标签
|
||||
switchFakuanStatus(e) {
|
||||
const status = Number(e.currentTarget.dataset.status);
|
||||
if (status === this.data.activeFakuanStatus) return;
|
||||
this.setData({ activeFakuanStatus: status });
|
||||
this.bumpTrigger('fakuan'); // 通知罚款列表组件刷新
|
||||
},
|
||||
|
||||
// 切换积分状态子标签
|
||||
switchJifenStatus(e) {
|
||||
const status = Number(e.currentTarget.dataset.status);
|
||||
if (status === this.data.activeJifenStatus) return;
|
||||
this.setData({ activeJifenStatus: status });
|
||||
this.bumpTrigger('jifen'); // 通知积分列表组件刷新
|
||||
},
|
||||
|
||||
// 搜索输入
|
||||
onSearchInput(e) {
|
||||
this.setData({ searchDingdan: e.detail.value });
|
||||
},
|
||||
|
||||
// 执行搜索(按键盘搜索按钮或点击搜索图标)
|
||||
onSearchConfirm() {
|
||||
this.bumpTrigger(this.data.activeTab);
|
||||
},
|
||||
|
||||
// 清空搜索关键字
|
||||
clearSearch() {
|
||||
this.setData({ searchDingdan: '' });
|
||||
this.bumpTrigger(this.data.activeTab);
|
||||
},
|
||||
|
||||
// 递增触发器,子组件通过 observer 响应
|
||||
bumpTrigger(tab) {
|
||||
const key = `trigger.${tab}`;
|
||||
this.setData({ [key]: this.data.trigger[tab] + 1 });
|
||||
}
|
||||
});
|
||||
10
pages/cfss/cfss/cfss.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
"fakuan-list": "/pages/cfss/components/fakuan-list/fakuan-list",
|
||||
"jifen-list": "/pages/cfss/components/jifen-list/jifen-list"
|
||||
},
|
||||
"navigationBarTitleText": "处罚中心",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"navigationBarTextStyle": "black",
|
||||
"backgroundColor": "#f7f8fa"
|
||||
}
|
||||
68
pages/cfss/cfss/cfss.wxml
Normal file
@@ -0,0 +1,68 @@
|
||||
<!-- pages/cfss/cfss.wxml -->
|
||||
<view class="page-container">
|
||||
<!-- 统计卡片 -->
|
||||
<view class="stats-card">
|
||||
<view class="stats-number-row">
|
||||
<view class="number-block">
|
||||
<text class="big-num green">{{ stats.fakuan.daijiaona }}</text>
|
||||
<text class="num-label">待缴罚款</text>
|
||||
</view>
|
||||
<view class="number-block">
|
||||
<text class="big-num orange">{{ stats.jifen.daichuli }}</text>
|
||||
<text class="num-label">待处理积分</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="stats-detail-row">
|
||||
<text>罚款:已缴{{ stats.fakuan.yijiaona }} · 申诉中{{ stats.fakuan.shensuzhong }} · 成功{{ stats.fakuan.yibohui }}</text>
|
||||
<text>积分:已处理{{ stats.jifen.yichuli }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 主Tab -->
|
||||
<view class="tab-row">
|
||||
<view class="tab-item {{ activeTab === 'fakuan' ? 'active' : '' }}" bindtap="switchMainTab" data-tab="fakuan">
|
||||
罚款
|
||||
</view>
|
||||
<view class="tab-item {{ activeTab === 'jifen' ? 'active' : '' }}" bindtap="switchMainTab" data-tab="jifen">
|
||||
积分处罚
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 子状态标签(横向滑动) -->
|
||||
<block wx:if="{{ activeTab === 'fakuan' }}">
|
||||
<scroll-view class="sub-tabs" scroll-x enable-flex>
|
||||
<view class="sub-tag {{ activeFakuanStatus === 1 ? 'sub-active' : '' }}" bindtap="switchFakuanStatus" data-status="1">待缴纳 {{ stats.fakuan.daijiaona }}</view>
|
||||
<view class="sub-tag {{ activeFakuanStatus === 3 ? 'sub-active' : '' }}" bindtap="switchFakuanStatus" data-status="3">申诉中 {{ stats.fakuan.shensuzhong }}</view>
|
||||
<view class="sub-tag {{ activeFakuanStatus === 2 ? 'sub-active' : '' }}" bindtap="switchFakuanStatus" data-status="2">已缴纳 {{ stats.fakuan.yijiaona }}</view>
|
||||
<view class="sub-tag {{ activeFakuanStatus === 4 ? 'sub-active' : '' }}" bindtap="switchFakuanStatus" data-status="4">申诉成功 {{ stats.fakuan.yibohui }}</view>
|
||||
</scroll-view>
|
||||
</block>
|
||||
<block wx:else>
|
||||
<scroll-view class="sub-tabs" scroll-x enable-flex>
|
||||
<view class="sub-tag {{ activeJifenStatus === 0 ? 'sub-active' : '' }}" bindtap="switchJifenStatus" data-status="0">待处理 {{ stats.jifen.daichuli }}</view>
|
||||
<view class="sub-tag {{ activeJifenStatus === 1 ? 'sub-active' : '' }}" bindtap="switchJifenStatus" data-status="1">已处理 {{ stats.jifen.yichuli }}</view>
|
||||
</scroll-view>
|
||||
</block>
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-bar">
|
||||
<input class="search-input" placeholder="输入订单号搜索" value="{{ searchDingdan }}" bindinput="onSearchInput" bindconfirm="onSearchConfirm" confirm-type="search"/>
|
||||
<view class="search-btn" bindtap="onSearchConfirm">搜索</view>
|
||||
<view wx:if="{{ searchDingdan }}" class="clear-btn" bindtap="clearSearch">✕</view>
|
||||
</view>
|
||||
|
||||
<!-- 列表区域(动态显示对应组件) -->
|
||||
<view wx:if="{{ activeTab === 'fakuan' }}" class="list-area">
|
||||
<fakuan-list
|
||||
status="{{ activeFakuanStatus }}"
|
||||
searchDingdan="{{ searchDingdan }}"
|
||||
trigger="{{ trigger.fakuan }}"
|
||||
/>
|
||||
</view>
|
||||
<view wx:else class="list-area">
|
||||
<jifen-list
|
||||
status="{{ activeJifenStatus }}"
|
||||
trigger="{{ trigger.jifen }}"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
121
pages/cfss/cfss/cfss.wxss
Normal file
@@ -0,0 +1,121 @@
|
||||
/* pages/cfss/cfss.wxss */
|
||||
page { background: #f7f8fa; font-family: -apple-system, BlinkMacSystemFont, sans-serif; }
|
||||
|
||||
.page-container { padding-bottom: 30rpx; }
|
||||
|
||||
/* 统计卡片 */
|
||||
.stats-card {
|
||||
margin: 24rpx 28rpx;
|
||||
padding: 30rpx 24rpx;
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.04);
|
||||
}
|
||||
.stats-number-row {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
.number-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.big-num {
|
||||
font-size: 56rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.green { color: #2E7D32; }
|
||||
.orange { color: #E67E22; }
|
||||
.num-label {
|
||||
font-size: 24rpx;
|
||||
color: #888;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
.stats-detail-row {
|
||||
border-top: 1rpx solid #eee;
|
||||
padding-top: 18rpx;
|
||||
}
|
||||
.stats-detail-row text {
|
||||
display: block;
|
||||
font-size: 22rpx;
|
||||
color: #aaa;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
/* 主Tab */
|
||||
.tab-row {
|
||||
display: flex;
|
||||
margin: 16rpx 28rpx;
|
||||
background: #fff;
|
||||
border-radius: 18rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 6rpx rgba(0,0,0,0.02);
|
||||
}
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 24rpx 0;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
transition: 0.2s;
|
||||
}
|
||||
.tab-item.active {
|
||||
color: #2E7D32;
|
||||
font-weight: 600;
|
||||
background: #F0F9F0;
|
||||
}
|
||||
|
||||
/* 子状态标签 */
|
||||
.sub-tabs {
|
||||
white-space: nowrap;
|
||||
margin: 8rpx 28rpx 16rpx;
|
||||
}
|
||||
.sub-tag {
|
||||
display: inline-block;
|
||||
padding: 10rpx 26rpx;
|
||||
margin-right: 14rpx;
|
||||
background: #fff;
|
||||
border-radius: 30rpx;
|
||||
font-size: 24rpx;
|
||||
color: #888;
|
||||
box-shadow: 0 1rpx 4rpx rgba(0,0,0,0.02);
|
||||
}
|
||||
.sub-tag.sub-active {
|
||||
background: #E8F5E9;
|
||||
color: #2E7D32;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 搜索栏 */
|
||||
.search-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 16rpx 28rpx;
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 12rpx 22rpx;
|
||||
box-shadow: 0 1rpx 6rpx rgba(0,0,0,0.02);
|
||||
}
|
||||
.search-input {
|
||||
flex: 1;
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
}
|
||||
.search-btn {
|
||||
margin-left: 18rpx;
|
||||
padding: 12rpx 30rpx;
|
||||
background: #2E7D32;
|
||||
color: #fff;
|
||||
border-radius: 16rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
.clear-btn {
|
||||
margin-left: 16rpx;
|
||||
font-size: 30rpx;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.list-area {
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
317
pages/cfss/components/fakuan-list/fakuan-list.js
Normal file
@@ -0,0 +1,317 @@
|
||||
// components/fakuan-list/fakuan-list.js
|
||||
import request from '../../../../utils/request.js';
|
||||
|
||||
const app = getApp();
|
||||
const MEIYE_TIAOSHU = 5;
|
||||
|
||||
Component({
|
||||
properties: {
|
||||
status: { type: Number, value: 1, observer: 'reload' },
|
||||
searchDingdan: { type: String, value: '', observer: 'reload' },
|
||||
trigger: { type: Number, value: 0, observer: 'reload' }
|
||||
},
|
||||
|
||||
data: {
|
||||
list: [],
|
||||
page: 1,
|
||||
hasMore: true,
|
||||
loading: false,
|
||||
loadingMore: false,
|
||||
ossImageUrl: app.globalData.ossImageUrl || '',
|
||||
|
||||
// 详情弹窗
|
||||
showDetail: false,
|
||||
detailItem: null,
|
||||
|
||||
// 申诉弹窗
|
||||
showShensu: false,
|
||||
shensuLiyou: '',
|
||||
shensuTupian: [],
|
||||
uploading: false,
|
||||
uploadProgress: { current: 0, total: 0 },
|
||||
uploadProgressWidth: '0%',
|
||||
|
||||
// 支付弹窗
|
||||
showPay: false,
|
||||
payFadan: null
|
||||
},
|
||||
|
||||
lifetimes: {
|
||||
attached() { this.reload(); }
|
||||
},
|
||||
|
||||
methods: {
|
||||
// ==================== 数据加载 ====================
|
||||
reload() {
|
||||
this.setData({ page: 1, list: [], hasMore: true });
|
||||
this.loadData();
|
||||
},
|
||||
|
||||
async loadData(isLoadMore = false) {
|
||||
// 防止重复请求
|
||||
if (this.data.loading || this.data.loadingMore) return;
|
||||
|
||||
if (!isLoadMore) {
|
||||
this.setData({ loading: true });
|
||||
} else {
|
||||
// 加载更多时,如果后端已经告知没有更多数据,不发送请求
|
||||
if (!this.data.hasMore) {
|
||||
wx.showToast({ title: '没有更多了', icon: 'none', duration: 1000 });
|
||||
return;
|
||||
}
|
||||
this.setData({ loadingMore: true });
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/yonghu/dsfklbhq',
|
||||
method: 'POST',
|
||||
data: {
|
||||
page: this.data.page,
|
||||
page_size: MEIYE_TIAOSHU,
|
||||
zhuangtai: this.data.status,
|
||||
sousuo_dingdan_id: this.data.searchDingdan
|
||||
}
|
||||
});
|
||||
if (res.statusCode === 200 && res.data.code === 0) {
|
||||
const data = res.data.data;
|
||||
const newList = isLoadMore ? this.data.list.concat(data.list || []) : (data.list || []);
|
||||
this.setData({
|
||||
list: newList,
|
||||
hasMore: data.has_more || false, // 以后端返回为准
|
||||
page: isLoadMore ? this.data.page + 1 : 2, // 首次加载后页码为2
|
||||
loading: false,
|
||||
loadingMore: false
|
||||
});
|
||||
} else {
|
||||
wx.showToast({ title: res.data?.msg || '加载失败', icon: 'none' });
|
||||
this.setData({ loading: false, loadingMore: false });
|
||||
}
|
||||
} catch (e) {
|
||||
wx.showToast({ title: '网络请求失败', icon: 'none' });
|
||||
this.setData({ loading: false, loadingMore: false });
|
||||
}
|
||||
},
|
||||
|
||||
loadMore() {
|
||||
if (this.data.list.length === 0) return;
|
||||
this.loadData(true);
|
||||
},
|
||||
|
||||
// ==================== 详情弹窗 ====================
|
||||
openDetail(e) {
|
||||
const item = e.currentTarget.dataset.item;
|
||||
this.setData({ showDetail: true, detailItem: item });
|
||||
},
|
||||
closeDetail() {
|
||||
this.setData({ showDetail: false, detailItem: null });
|
||||
},
|
||||
|
||||
// ==================== 申诉逻辑 ====================
|
||||
openShensu() {
|
||||
const item = this.data.detailItem;
|
||||
if (item.bohuiliyou) {
|
||||
wx.showToast({ title: '该罚单申诉已被驳回', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
const existingImgs = (item.shensu_tupian || []).map(url => this.getFullUrl(url));
|
||||
this.setData({
|
||||
showShensu: true,
|
||||
shensuLiyou: item.shensuliyou || '',
|
||||
shensuTupian: existingImgs,
|
||||
uploading: false,
|
||||
uploadProgress: { current: 0, total: 0 },
|
||||
uploadProgressWidth: '0%'
|
||||
});
|
||||
},
|
||||
closeShensu() {
|
||||
this.setData({ showShensu: false });
|
||||
},
|
||||
onShensuInput(e) {
|
||||
this.setData({ shensuLiyou: e.detail.value.slice(0, 500) });
|
||||
},
|
||||
|
||||
chooseImage() {
|
||||
const remain = 9 - this.data.shensuTupian.length;
|
||||
if (remain <= 0) return;
|
||||
wx.chooseMedia({
|
||||
count: remain,
|
||||
mediaType: ['image'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: (res) => {
|
||||
const newImgs = [...this.data.shensuTupian, ...res.tempFiles.map(f => f.tempFilePath)];
|
||||
this.setData({ shensuTupian: newImgs.slice(0, 9) });
|
||||
}
|
||||
});
|
||||
},
|
||||
deleteShensuImg(e) {
|
||||
const arr = this.data.shensuTupian;
|
||||
arr.splice(e.currentTarget.dataset.index, 1);
|
||||
this.setData({ shensuTupian: arr });
|
||||
},
|
||||
previewImage(e) {
|
||||
wx.previewImage({ current: e.currentTarget.dataset.url, urls: [e.currentTarget.dataset.url] });
|
||||
},
|
||||
previewLocalImg(e) {
|
||||
const idx = e.currentTarget.dataset.index;
|
||||
wx.previewImage({ current: this.data.shensuTupian[idx], urls: this.data.shensuTupian });
|
||||
},
|
||||
|
||||
// ==================== 提交流程 ====================
|
||||
async submitShensu() {
|
||||
if (!this.data.shensuLiyou.trim()) {
|
||||
wx.showToast({ title: '请输入申诉理由', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
wx.showLoading({ title: '提交中...' });
|
||||
try {
|
||||
const newLocalPaths = this.data.shensuTupian.filter(path => !path.startsWith('http'));
|
||||
let finalUrls = [];
|
||||
if (newLocalPaths.length > 0) {
|
||||
finalUrls = await this.piliangShangchuanShensuTupian(newLocalPaths);
|
||||
if (!finalUrls) {
|
||||
wx.hideLoading();
|
||||
return;
|
||||
}
|
||||
}
|
||||
const oldRemoteRelative = this.data.shensuTupian
|
||||
.filter(path => path.startsWith('http'))
|
||||
.map(url => url.replace(this.data.ossImageUrl, '').replace(/^\//, ''));
|
||||
const allUrls = oldRemoteRelative.concat(finalUrls);
|
||||
await this.doSubmitShensu(allUrls);
|
||||
} catch (err) {
|
||||
console.error('提交申诉失败', err);
|
||||
wx.showToast({ title: err.message || '提交失败', icon: 'none' });
|
||||
} finally {
|
||||
wx.hideLoading();
|
||||
}
|
||||
},
|
||||
|
||||
async doSubmitShensu(tupianUrls) {
|
||||
const res = await request({
|
||||
url: '/yonghu/fkss',
|
||||
method: 'POST',
|
||||
data: {
|
||||
fadan_id: this.data.detailItem.id,
|
||||
shensu_liyou: this.data.shensuLiyou,
|
||||
shensu_tupian_urls: tupianUrls
|
||||
}
|
||||
});
|
||||
if (res.statusCode === 200 && res.data.code === 0) {
|
||||
wx.showToast({ title: '申诉已提交', icon: 'success' });
|
||||
this.setData({ showShensu: false, showDetail: false });
|
||||
this.reload();
|
||||
} else {
|
||||
throw new Error(res.data?.msg || '提交失败');
|
||||
}
|
||||
},
|
||||
|
||||
// ==================== 图片上传到COS ====================
|
||||
async piliangShangchuanShensuTupian(localPaths) {
|
||||
const total = localPaths.length;
|
||||
if (!total) return [];
|
||||
this.setData({ uploading: true, uploadProgress: { current: 0, total: total }, uploadProgressWidth: '0%' });
|
||||
|
||||
let tokenData;
|
||||
try {
|
||||
const credRes = await request({
|
||||
url: '/dingdan/dsscpz',
|
||||
method: 'POST',
|
||||
data: {
|
||||
dingdan_id: this.data.detailItem.guanliandingdan_id || '',
|
||||
fadan_id: this.data.detailItem.id,
|
||||
yongtu: 'fakuan'
|
||||
}
|
||||
});
|
||||
if (credRes.data.code !== 0) throw new Error(credRes.data.msg || '凭证获取失败');
|
||||
tokenData = credRes.data.data;
|
||||
} catch (e) {
|
||||
wx.showToast({ title: '获取上传凭证失败', icon: 'none' });
|
||||
this.setData({ uploading: false });
|
||||
return null;
|
||||
}
|
||||
|
||||
const COS = require('../../../../utils/cos-wx-sdk-v5.js');
|
||||
const credentials = tokenData.credentials || tokenData;
|
||||
const cos = new COS({
|
||||
SimpleUploadMethod: 'putObject',
|
||||
getAuthorization: (options, callback) => {
|
||||
callback({
|
||||
TmpSecretId: credentials.tmpSecretId,
|
||||
TmpSecretKey: credentials.tmpSecretKey,
|
||||
SecurityToken: credentials.sessionToken || '',
|
||||
StartTime: tokenData.startTime,
|
||||
ExpiredTime: tokenData.expiredTime
|
||||
});
|
||||
}
|
||||
});
|
||||
const bucket = tokenData.bucket || 'julebu-1361527063';
|
||||
const region = tokenData.region || 'ap-shanghai';
|
||||
|
||||
const yonghuid = app.globalData.userInfo?.yonghuid || wx.getStorageSync('uid') || '0000000';
|
||||
const fadanId = this.data.detailItem.id;
|
||||
const keys = [];
|
||||
for (let i = 0; i < total; i++) {
|
||||
keys.push(`fakuan/dashoufakuan/dashoufakuanshensu/${yonghuid}_${fadanId}_${Date.now() + i}.jpg`);
|
||||
}
|
||||
|
||||
for (let i = 0; i < total; i++) {
|
||||
try {
|
||||
await new Promise((resolve, reject) => {
|
||||
const timeoutId = setTimeout(() => resolve(), 2000);
|
||||
cos.uploadFile({
|
||||
Bucket: bucket,
|
||||
Region: region,
|
||||
Key: keys[i],
|
||||
FilePath: localPaths[i],
|
||||
onProgress: (info) => {
|
||||
if (Math.round(info.percent * 100) === 100) {
|
||||
clearTimeout(timeoutId);
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
}, (err) => {
|
||||
clearTimeout(timeoutId);
|
||||
if (err) reject(err);
|
||||
else resolve();
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
wx.showToast({ title: `第 ${i + 1} 张上传失败`, icon: 'none' });
|
||||
this.setData({ uploading: false });
|
||||
return null;
|
||||
}
|
||||
const done = i + 1;
|
||||
this.setData({
|
||||
uploadProgress: { current: done, total: total },
|
||||
uploadProgressWidth: `${((done / total) * 100).toFixed(0)}%`
|
||||
});
|
||||
}
|
||||
this.setData({ uploading: false });
|
||||
return keys;
|
||||
},
|
||||
|
||||
// ==================== 缴纳支付 ====================
|
||||
startPay() {
|
||||
this.setData({
|
||||
showDetail: false,
|
||||
showPay: true,
|
||||
payFadan: this.data.detailItem
|
||||
});
|
||||
},
|
||||
onPayClose() {
|
||||
this.setData({ showPay: false, payFadan: null });
|
||||
},
|
||||
onPaySuccess() {
|
||||
this.setData({ showPay: false, payFadan: null });
|
||||
this.reload();
|
||||
},
|
||||
|
||||
// ==================== 工具方法 ====================
|
||||
getFullUrl(url) {
|
||||
if (!url) return '';
|
||||
if (url.startsWith('http')) return url;
|
||||
return this.data.ossImageUrl + url;
|
||||
}
|
||||
}
|
||||
});
|
||||
6
pages/cfss/components/fakuan-list/fakuan-list.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {
|
||||
"fakuan-pay": "/pages/cfss/components/fakuan-pay/fakuan-pay"
|
||||
}
|
||||
}
|
||||
116
pages/cfss/components/fakuan-list/fakuan-list.wxml
Normal file
@@ -0,0 +1,116 @@
|
||||
<!-- components/fakuan-list/fakuan-list.wxml -->
|
||||
<view class="container">
|
||||
<scroll-view scroll-y class="scroll" bindscrolltolower="loadMore">
|
||||
<view wx:if="{{ loading && list.length === 0 }}" class="tip">加载中...</view>
|
||||
<block wx:for="{{ list }}" wx:key="id">
|
||||
<view class="card" bindtap="openDetail" data-item="{{ item }}">
|
||||
<view class="card-head">
|
||||
<text class="time">{{ item.create_time }}</text>
|
||||
<text class="tag {{ item.status_class }}">{{ item.display_status }}</text>
|
||||
</view>
|
||||
<text class="info">金额:¥{{ item.fakuanjine }}</text>
|
||||
<text class="info">理由:{{ item.chufaliyou }}</text>
|
||||
<text class="info" wx:if="{{ item.guanliandingdan_id }}">订单:{{ item.guanliandingdan_id }}</text>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- ✅ 按钮始终显示,只要列表不为空且没有正在加载 -->
|
||||
<view wx:if="{{ list.length > 0 && !loadingMore }}" class="load-more-btn" bindtap="loadMore">
|
||||
<text>点击获取更多</text>
|
||||
</view>
|
||||
<view wx:if="{{ loadingMore }}" class="tip">加载中...</view>
|
||||
<view wx:if="{{ !hasMore && list.length > 0 }}" class="tip">—— 没有更多了 ——</view>
|
||||
<view wx:if="{{ !loading && list.length === 0 }}" class="empty">暂无罚款记录</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 详情弹窗 -->
|
||||
<view wx:if="{{ showDetail }}" class="modal-mask">
|
||||
<view class="modal-panel">
|
||||
<view class="modal-head">
|
||||
<text class="modal-title">罚款详情</text>
|
||||
<view class="modal-close" bindtap="closeDetail">✕</view>
|
||||
</view>
|
||||
<scroll-view scroll-y class="modal-body">
|
||||
<view class="row"><text class="lbl">罚款金额</text><text class="val strong">¥{{ detailItem.fakuanjine }}</text></view>
|
||||
<view class="row"><text class="lbl">当前状态</text><text class="tag {{ detailItem.status_class }}">{{ detailItem.display_status }}</text></view>
|
||||
<view class="row"><text class="lbl">处罚理由</text><text class="val">{{ detailItem.chufaliyou }}</text></view>
|
||||
<view wx:if="{{ detailItem.guanliandingdan_id }}" class="row"><text class="lbl">关联订单</text><text class="val">{{ detailItem.guanliandingdan_id }}</text></view>
|
||||
<view wx:if="{{ detailItem.bohuiliyou }}" class="row"><text class="lbl">驳回理由</text><text class="val">{{ detailItem.bohuiliyou }}</text></view>
|
||||
<view wx:if="{{ detailItem.shensuliyou }}" class="row"><text class="lbl">申诉理由</text><text class="val">{{ detailItem.shensuliyou }}</text></view>
|
||||
|
||||
<!-- 证据图片 -->
|
||||
<view wx:if="{{ detailItem.zhengju_tupian && detailItem.zhengju_tupian.length }}" class="section">
|
||||
<text class="sec-title">证据图片</text>
|
||||
<view class="img-grid">
|
||||
<block wx:for="{{ detailItem.zhengju_tupian }}" wx:key="index">
|
||||
<view class="img-box" bindtap="previewImage" data-url="{{ ossImageUrl + item }}">
|
||||
<image class="img" src="{{ ossImageUrl + item }}" mode="aspectFill" />
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 申诉图片 -->
|
||||
<view wx:if="{{ detailItem.shensu_tupian && detailItem.shensu_tupian.length }}" class="section">
|
||||
<text class="sec-title">申诉图片</text>
|
||||
<view class="img-grid">
|
||||
<block wx:for="{{ detailItem.shensu_tupian }}" wx:key="index">
|
||||
<view class="img-box" bindtap="previewImage" data-url="{{ ossImageUrl + item }}">
|
||||
<image class="img" src="{{ ossImageUrl + item }}" mode="aspectFill" />
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="modal-foot">
|
||||
<view wx:if="{{ detailItem.zhuangtai === 1 && !detailItem.bohuiliyou }}" class="btn primary" bindtap="openShensu">申诉</view>
|
||||
<view wx:if="{{ detailItem.zhuangtai === 1 || detailItem.zhuangtai === 3 }}" class="btn primary" bindtap="startPay">立即缴纳</view>
|
||||
<view class="btn default" bindtap="closeDetail">关闭</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 申诉弹窗 -->
|
||||
<view wx:if="{{ showShensu }}" class="modal-mask">
|
||||
<view class="modal-panel">
|
||||
<view class="modal-head">
|
||||
<text class="modal-title">提交申诉</text>
|
||||
<view class="modal-close" bindtap="closeShensu">✕</view>
|
||||
</view>
|
||||
<scroll-view scroll-y class="modal-body">
|
||||
<view class="form-group">
|
||||
<text class="sec-title">申诉理由</text>
|
||||
<textarea class="textarea" placeholder="请输入申诉理由" value="{{ shensuLiyou }}" bindinput="onShensuInput" maxlength="500" />
|
||||
<text class="word-count">{{ shensuLiyou.length }}/500</text>
|
||||
</view>
|
||||
<view class="form-group">
|
||||
<text class="sec-title">上传凭证(最多9张)</text>
|
||||
<view class="img-grid">
|
||||
<block wx:for="{{ shensuTupian }}" wx:key="index">
|
||||
<view class="img-box">
|
||||
<image class="img" src="{{ item }}" mode="aspectFill" bindtap="previewLocalImg" data-index="{{ index }}" />
|
||||
<view class="img-del" catchtap="deleteShensuImg" data-index="{{ index }}">✕</view>
|
||||
</view>
|
||||
</block>
|
||||
<view wx:if="{{ shensuTupian.length < 9 }}" class="img-add" bindtap="chooseImage">+</view>
|
||||
</view>
|
||||
<view wx:if="{{ uploading }}" class="progress-box">
|
||||
<view class="progress-bar"><view class="progress-inner" style="width: {{ uploadProgressWidth }}"></view></view>
|
||||
<text class="progress-text">{{ uploadProgress.current }}/{{ uploadProgress.total }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="modal-foot">
|
||||
<view class="btn default" bindtap="closeShensu">取消</view>
|
||||
<view class="btn primary" bindtap="submitShensu">提交申诉</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 支付组件(缴纳罚款) -->
|
||||
<fakuan-pay
|
||||
visible="{{ showPay }}"
|
||||
fadan="{{ payFadan }}"
|
||||
bind:close="onPayClose"
|
||||
bind:success="onPaySuccess"
|
||||
/>
|
||||
</view>
|
||||
77
pages/cfss/components/fakuan-list/fakuan-list.wxss
Normal file
@@ -0,0 +1,77 @@
|
||||
/* 完全复用积分组件的样式 */
|
||||
page { background: #f5f5f5; }
|
||||
|
||||
.container { width: 100%; box-sizing: border-box; display: flex; flex-direction: column; }
|
||||
|
||||
.scroll { width: 100%; box-sizing: border-box; padding: 20rpx 24rpx; }
|
||||
|
||||
.tip { text-align: center; color: #999; font-size: 24rpx; padding: 30rpx 0; }
|
||||
.empty { display: flex; flex-direction: column; align-items: center; padding-top: 200rpx; color: #999; font-size: 28rpx; }
|
||||
|
||||
.card { width: 100%; box-sizing: border-box; background: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 16rpx; }
|
||||
.card-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12rpx; }
|
||||
.time { font-size: 24rpx; color: #999; }
|
||||
.tag { font-size: 20rpx; padding: 2rpx 14rpx; border-radius: 16rpx; display: inline-block; }
|
||||
|
||||
/* 状态标签颜色(与积分组件完全相同) */
|
||||
.zhuangtai-daijiaona { background: #FFF3E0; color: #E65100; } /* 待缴纳 */
|
||||
.zhuangtai-yijiaona { background: #E8F5E9; color: #2E7D32; } /* 已缴纳 */
|
||||
.zhuangtai-shensuzhong { background: #E3F2FD; color: #1565C0; } /* 申诉中 */
|
||||
.zhuangtai-yibohui { background: #F3E5F5; color: #7B1FA2; } /* 申诉成功 */
|
||||
|
||||
.info { display: block; font-size: 26rpx; color: #333; margin-top: 8rpx; }
|
||||
|
||||
.modal-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 999; display: flex; align-items: flex-end; justify-content: center; }
|
||||
.modal-panel { width: 100%; box-sizing: border-box; max-height: 85vh; background: #fff; border-radius: 24rpx 24rpx 0 0; display: flex; flex-direction: column; overflow: hidden; }
|
||||
.modal-head { display: flex; justify-content: space-between; align-items: center; padding: 28rpx 30rpx 16rpx; border-bottom: 1px solid #eee; }
|
||||
.modal-title { font-size: 32rpx; font-weight: 600; }
|
||||
.modal-close { width: 44rpx; height: 44rpx; border-radius: 50%; background: #f5f5f5; display: flex; align-items: center; justify-content: center; font-size: 26rpx; color: #888; }
|
||||
|
||||
.modal-body { width: 100%; box-sizing: border-box; padding: 20rpx 30rpx; overflow-y: auto; }
|
||||
.row { display: flex; margin-bottom: 16rpx; }
|
||||
.lbl { width: 150rpx; font-size: 26rpx; color: #888; flex-shrink: 0; }
|
||||
.val { flex: 1; font-size: 26rpx; color: #333; word-break: break-all; }
|
||||
.strong { font-weight: 600; color: #E53935; }
|
||||
.section { margin: 20rpx 0; }
|
||||
.sec-title { font-size: 26rpx; color: #888; margin-bottom: 12rpx; display: block; }
|
||||
|
||||
.img-grid { width: 100%; box-sizing: border-box; display: flex; flex-wrap: wrap; gap: 12rpx; }
|
||||
.img-box { position: relative; width: calc((100% - 24rpx) / 3); height: 0; padding-bottom: calc((100% - 24rpx) / 3); }
|
||||
.img-box .img { position: absolute; top: 0; left: 0; width: 100%; height: 100%; border-radius: 10rpx; background: #f0f0f0; }
|
||||
.img-del { position: absolute; top: -6rpx; right: -6rpx; width: 34rpx; height: 34rpx; background: #e53935; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: #fff; font-size: 18rpx; z-index: 2; }
|
||||
.img-add { width: calc((100% - 24rpx) / 3); height: 0; padding-bottom: calc((100% - 24rpx) / 3); border: 2rpx dashed #ccc; border-radius: 10rpx; display: flex; align-items: center; justify-content: center; font-size: 48rpx; color: #aaa; position: relative; box-sizing: border-box; }
|
||||
|
||||
.form-group { margin-bottom: 24rpx; }
|
||||
.textarea { width: 100%; box-sizing: border-box; height: 180rpx; background: #f9f9f9; border-radius: 10rpx; padding: 16rpx; font-size: 26rpx; border: 1px solid #eee; }
|
||||
.word-count { text-align: right; font-size: 22rpx; color: #bbb; margin-top: 6rpx; }
|
||||
.progress-box { margin-top: 12rpx; }
|
||||
.progress-bar { height: 10rpx; background: #eee; border-radius: 5rpx; }
|
||||
.progress-inner { height: 100%; background: #2e7d32; border-radius: 5rpx; }
|
||||
.progress-text { font-size: 22rpx; color: #2e7d32; text-align: right; margin-top: 4rpx; }
|
||||
|
||||
.modal-foot { width: 100%; box-sizing: border-box; display: flex; padding: 16rpx 30rpx 30rpx; gap: 20rpx; border-top: 1px solid #eee; }
|
||||
.btn { flex: 1; text-align: center; padding: 22rpx 0; border-radius: 16rpx; font-size: 28rpx; font-weight: 600; }
|
||||
.primary { background: #2e7d32; color: #fff; }
|
||||
.default { background: #f0f0f0; color: #555; }
|
||||
.btn:active { opacity: 0.8; }
|
||||
|
||||
|
||||
/* ===== 获取更多按钮 ===== */
|
||||
.load-more-btn {
|
||||
width: 80%;
|
||||
margin: 20rpx auto 30rpx;
|
||||
padding: 22rpx 0;
|
||||
text-align: center;
|
||||
background: #ffffff;
|
||||
border: 2rpx solid #e0e0e0;
|
||||
border-radius: 20rpx;
|
||||
color: #555;
|
||||
font-size: 26rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.02);
|
||||
transition: all 0.15s;
|
||||
}
|
||||
.load-more-btn:active {
|
||||
background: #f5f5f5;
|
||||
border-color: #2e7d32;
|
||||
color: #2e7d32;
|
||||
}
|
||||
178
pages/cfss/components/fakuan-pay/fakuan-pay.js
Normal file
@@ -0,0 +1,178 @@
|
||||
// components/fakuan-list/fakuan-pay/fakuan-pay.js
|
||||
import request from '../../../../utils/request.js'; // 路径保持您原来的样子
|
||||
|
||||
Component({
|
||||
properties: {
|
||||
visible: { type: Boolean, value: false, observer: 'onVisibleChange' },
|
||||
fadan: { type: Object, value: null } // { id, fakuanjine }
|
||||
},
|
||||
|
||||
data: {
|
||||
step: 'method', // method / balance / confirm / loading
|
||||
balanceOptions: [],
|
||||
selectedBalance: null,
|
||||
loadingText: '',
|
||||
payLoading: false,
|
||||
dingdanid: '' // 微信支付内部订单ID
|
||||
},
|
||||
|
||||
methods: {
|
||||
onVisibleChange(val) {
|
||||
if (val) {
|
||||
// 每次打开重置为支付方式选择
|
||||
this.setData({ step: 'method', balanceOptions: [], selectedBalance: null });
|
||||
}
|
||||
},
|
||||
|
||||
close() {
|
||||
this.setData({ visible: false });
|
||||
this.triggerEvent('close');
|
||||
},
|
||||
|
||||
// 选择支付方式
|
||||
async chooseMethod(e) {
|
||||
const method = e.currentTarget.dataset.method;
|
||||
if (method === 'wx') {
|
||||
this.wxPay();
|
||||
} else if (method === 'balance') {
|
||||
this.fetchBalanceOptions();
|
||||
}
|
||||
},
|
||||
|
||||
// ========== 微信支付流程 ==========
|
||||
async wxPay() {
|
||||
this.setData({ step: 'loading', loadingText: '发起支付中...' });
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/yonghu/fkjn',
|
||||
method: 'POST',
|
||||
data: { fadan_id: this.properties.fadan.id }
|
||||
});
|
||||
if (res.data.code !== 200) throw new Error(res.data.msg || '下单失败');
|
||||
|
||||
// 🔥 唯一修改点:后端返回格式 { code:200, payParams:{...}, dingdanid:'...' }
|
||||
const payParams = res.data.payParams;
|
||||
const dingdanid = res.data.dingdanid;
|
||||
|
||||
this.setData({ dingdanid });
|
||||
await this.requestWxPayment(payParams);
|
||||
} catch (e) {
|
||||
this.setData({ step: 'method' });
|
||||
wx.showToast({ title: e.message, icon: 'none' });
|
||||
}
|
||||
},
|
||||
|
||||
async requestWxPayment(payParams) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!payParams || !payParams.timeStamp || !payParams.paySign) {
|
||||
wx.showToast({ title: '支付参数错误', icon: 'none' });
|
||||
reject(new Error('支付参数错误'));
|
||||
return;
|
||||
}
|
||||
wx.requestPayment({
|
||||
timeStamp: payParams.timeStamp,
|
||||
nonceStr: payParams.nonceStr,
|
||||
package: payParams.package,
|
||||
signType: payParams.signType || 'MD5',
|
||||
paySign: payParams.paySign,
|
||||
success: () => {
|
||||
this.pollPayStatus();
|
||||
resolve();
|
||||
},
|
||||
fail: (err) => {
|
||||
this.setData({ step: 'method' });
|
||||
this.notifyPayFail();
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
async pollPayStatus() {
|
||||
this.setData({ step: 'loading', loadingText: '确认支付结果...' });
|
||||
for (let i = 0; i < 10; i++) {
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/yonghu/fkzffc',
|
||||
method: 'POST',
|
||||
data: { dingdanid: this.data.dingdanid }
|
||||
});
|
||||
if (res.data.code === 200) {
|
||||
this.triggerEvent('success');
|
||||
this.close();
|
||||
wx.showToast({ title: '缴纳成功', icon: 'success' });
|
||||
return;
|
||||
}
|
||||
} catch (e) {}
|
||||
await new Promise(r => setTimeout(r, 2000));
|
||||
}
|
||||
this.setData({ step: 'method' });
|
||||
wx.showToast({ title: '支付确认超时', icon: 'none' });
|
||||
},
|
||||
|
||||
async notifyPayFail() {
|
||||
try {
|
||||
await request({
|
||||
url: '/yonghu/fkzfsb',
|
||||
method: 'POST',
|
||||
data: { dingdanid: this.data.dingdanid }
|
||||
});
|
||||
} catch (e) {}
|
||||
},
|
||||
|
||||
// ========== 佣金抵扣流程 ==========
|
||||
async fetchBalanceOptions() {
|
||||
this.setData({ step: 'loading', loadingText: '获取可抵扣身份...' });
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/shangpin/czhqdy',
|
||||
method: 'POST',
|
||||
data: {
|
||||
leixing: 4,
|
||||
fadan_id: this.properties.fadan.id,
|
||||
jine: this.properties.fadan.fakuanjine
|
||||
}
|
||||
});
|
||||
if (res.data.code !== 200) throw new Error(res.data.msg || '获取失败');
|
||||
this.setData({ balanceOptions: res.data.data, step: 'balance' });
|
||||
} catch (e) {
|
||||
this.setData({ step: 'method' });
|
||||
wx.showToast({ title: e.message, icon: 'none' });
|
||||
}
|
||||
},
|
||||
|
||||
selectBalance(e) {
|
||||
const id = e.currentTarget.dataset.id;
|
||||
const info = this.data.balanceOptions.find(opt => opt.id === id);
|
||||
if (!info) return;
|
||||
this.setData({ selectedBalance: info, step: 'confirm' });
|
||||
},
|
||||
|
||||
backToMethod() {
|
||||
this.setData({ step: 'method' });
|
||||
},
|
||||
|
||||
async confirmBalance() {
|
||||
if (!this.data.selectedBalance) return;
|
||||
this.setData({ step: 'loading', loadingText: '抵扣扣款中...' });
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/shangpin/dsqrgmdh',
|
||||
method: 'POST',
|
||||
data: {
|
||||
leixing: 4,
|
||||
shenfen_id: this.data.selectedBalance.id,
|
||||
fadan_id: this.properties.fadan.id
|
||||
}
|
||||
});
|
||||
if (res.data.code !== 200) throw new Error(res.data.msg || '抵扣失败');
|
||||
this.triggerEvent('success');
|
||||
this.close();
|
||||
wx.showToast({ title: '缴纳成功', icon: 'success' });
|
||||
} catch (e) {
|
||||
this.setData({ step: 'method' });
|
||||
wx.showToast({ title: e.message, icon: 'none' });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
3
pages/cfss/components/fakuan-pay/fakuan-pay.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"component": true
|
||||
}
|
||||
61
pages/cfss/components/fakuan-pay/fakuan-pay.wxml
Normal file
@@ -0,0 +1,61 @@
|
||||
<!-- 支付组件模板 -->
|
||||
<view wx:if="{{ visible }}" class="pay-mask" catchtouchmove="prevent">
|
||||
<!-- 支付方式选择 -->
|
||||
<view wx:if="{{ step === 'method' }}" class="pay-panel">
|
||||
<view class="pay-head">
|
||||
<text class="pay-title">选择支付方式</text>
|
||||
<view class="pay-close" bindtap="close">✕</view>
|
||||
</view>
|
||||
<view class="pay-body">
|
||||
<view class="method-item" bindtap="chooseMethod" data-method="wx">
|
||||
<text class="method-icon">💳</text>
|
||||
<text class="method-name">微信支付</text>
|
||||
</view>
|
||||
<view class="method-item" bindtap="chooseMethod" data-method="balance">
|
||||
<text class="method-icon">💰</text>
|
||||
<text class="method-name">佣金抵扣</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 余额抵扣身份选择 -->
|
||||
<view wx:if="{{ step === 'balance' }}" class="pay-panel">
|
||||
<view class="pay-head">
|
||||
<text class="pay-title">选择抵扣身份</text>
|
||||
<view class="pay-close" bindtap="close">✕</view>
|
||||
</view>
|
||||
<scroll-view scroll-y class="pay-body">
|
||||
<view wx:for="{{ balanceOptions }}" wx:key="id" class="balance-item" bindtap="selectBalance" data-id="{{ item.id }}">
|
||||
<text class="balance-name">{{ item.jieshao }}</text>
|
||||
<text class="balance-amount">需¥{{ item.xuyao }}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 余额抵扣确认 -->
|
||||
<view wx:if="{{ step === 'confirm' }}" class="pay-panel">
|
||||
<view class="pay-head">
|
||||
<text class="pay-title">确认抵扣</text>
|
||||
<view class="pay-close" bindtap="close">✕</view>
|
||||
</view>
|
||||
<view class="pay-body">
|
||||
<view class="confirm-row">
|
||||
<text>抵扣身份:{{ selectedBalance.jieshao }}</text>
|
||||
</view>
|
||||
<view class="confirm-row">
|
||||
<text>需扣除:¥{{ selectedBalance.xuyao }}</text>
|
||||
</view>
|
||||
<text class="confirm-tip">确认后将从您的佣金/分红中扣除</text>
|
||||
</view>
|
||||
<view class="pay-foot">
|
||||
<view class="btn default" bindtap="backToMethod">取消</view>
|
||||
<view class="btn primary" bindtap="confirmBalance">确认</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 支付加载动画 -->
|
||||
<view wx:if="{{ step === 'loading' }}" class="pay-mask loading-box">
|
||||
<view class="spinner"></view>
|
||||
<text class="loading-text">{{ loadingText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
59
pages/cfss/components/fakuan-pay/fakuan-pay.wxss
Normal file
@@ -0,0 +1,59 @@
|
||||
/* 支付组件样式,与罚款列表统一 */
|
||||
.pay-mask {
|
||||
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: rgba(0,0,0,0.5); z-index: 2000;
|
||||
display: flex; align-items: flex-end; justify-content: center;
|
||||
}
|
||||
.pay-panel {
|
||||
width: 100%; max-height: 85vh; background: #fff;
|
||||
border-radius: 24rpx 24rpx 0 0; display: flex; flex-direction: column; overflow: hidden;
|
||||
}
|
||||
.pay-head {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
padding: 28rpx 30rpx 16rpx; border-bottom: 1px solid #eee;
|
||||
}
|
||||
.pay-title { font-size: 32rpx; font-weight: 600; }
|
||||
.pay-close {
|
||||
width: 44rpx; height: 44rpx; background: #f5f5f5;
|
||||
border-radius: 50%; display: flex; align-items: center; justify-content: center;
|
||||
font-size: 26rpx; color: #888;
|
||||
}
|
||||
.pay-body { padding: 20rpx 30rpx; flex: 1; overflow-y: auto; }
|
||||
.pay-foot {
|
||||
display: flex; padding: 16rpx 30rpx 30rpx; gap: 20rpx; border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
.method-item {
|
||||
display: flex; flex-direction: column; align-items: center;
|
||||
padding: 40rpx 30rpx; background: #f9f9f9; border-radius: 20rpx; margin-bottom: 20rpx;
|
||||
}
|
||||
.method-icon { font-size: 48rpx; margin-bottom: 12rpx; }
|
||||
.method-name { font-size: 28rpx; color: #333; }
|
||||
|
||||
.balance-item {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
padding: 24rpx 0; border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
.balance-name { font-size: 28rpx; color: #333; }
|
||||
.balance-amount { font-size: 28rpx; color: #E53935; }
|
||||
|
||||
.confirm-row { font-size: 28rpx; margin-bottom: 16rpx; }
|
||||
.confirm-tip { font-size: 24rpx; color: #888; display: block; margin-top: 20rpx; }
|
||||
|
||||
.loading-box {
|
||||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
background: rgba(0,0,0,0.6);
|
||||
}
|
||||
.spinner {
|
||||
width: 60rpx; height: 60rpx;
|
||||
border: 4rpx solid rgba(255,255,255,0.3);
|
||||
border-top-color: #fff; border-radius: 50%;
|
||||
animation: spin 1s linear infinite; margin-bottom: 20rpx;
|
||||
}
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
.loading-text { color: #fff; font-size: 26rpx; }
|
||||
|
||||
.btn { flex: 1; text-align: center; padding: 22rpx 0; border-radius: 16rpx; font-size: 28rpx; font-weight: 600; }
|
||||
.primary { background: #2e7d32; color: #fff; }
|
||||
.default { background: #f0f0f0; color: #555; }
|
||||
.btn:active { opacity: 0.8; }
|
||||
220
pages/cfss/components/jifen-list/jifen-list.js
Normal file
@@ -0,0 +1,220 @@
|
||||
// components/jifen-list/jifen-list.js
|
||||
import request from '../../../../utils/request.js';
|
||||
const app = getApp();
|
||||
const MEIYE_TIAOSHU = 5;
|
||||
|
||||
Component({
|
||||
properties: {
|
||||
status: { type: Number, value: 0, observer: 'reload' },
|
||||
trigger: { type: Number, value: 0, observer: 'reload' }
|
||||
},
|
||||
|
||||
data: {
|
||||
chufaList: [],
|
||||
dangqianye: 1,
|
||||
haiyougengduo: true,
|
||||
jiazhaozhong: false,
|
||||
jiazhaigengduo: false,
|
||||
ossImageUrl: app.globalData.ossImageUrl || '',
|
||||
|
||||
showXiangqing: false,
|
||||
xuanzhongChufa: {},
|
||||
|
||||
showShensuModal: false,
|
||||
shensuLiyou: '',
|
||||
shensuTupian: [],
|
||||
shangchuanJindu: 0,
|
||||
shangchuanZongshu: 0,
|
||||
jinduWidth: '0%',
|
||||
isShangchuanzhong: false,
|
||||
fangdouTimer: null
|
||||
},
|
||||
|
||||
lifetimes: {
|
||||
attached() { this.reload(); },
|
||||
detached() { if (this.data.fangdouTimer) clearTimeout(this.data.fangdouTimer); }
|
||||
},
|
||||
|
||||
methods: {
|
||||
reload() {
|
||||
this.setData({ dangqianye: 1, chufaList: [], haiyougengduo: true });
|
||||
this.loadJifenList();
|
||||
},
|
||||
|
||||
async loadJifenList(isLoadMore = false) {
|
||||
if (this.data.jiazhaozhong || this.data.jiazhaigengduo) return;
|
||||
if (isLoadMore && !this.data.haiyougengduo) return;
|
||||
if (isLoadMore) this.setData({ jiazhaigengduo: true });
|
||||
else this.setData({ jiazhaozhong: true });
|
||||
|
||||
try {
|
||||
const params = { page: this.data.dangqianye, page_size: MEIYE_TIAOSHU, zhuangtai: this.data.status };
|
||||
const res = await request({ url: '/yonghu/dshqcfjl', method: 'POST', data: params });
|
||||
if (res.statusCode === 200 && res.data.code === 0) {
|
||||
const data = res.data.data;
|
||||
if (data.list && Array.isArray(data.list)) {
|
||||
const processedList = data.list.map(item => {
|
||||
let displayTime = '--';
|
||||
if (item.create_time) displayTime = item.create_time;
|
||||
let displayStatus = '未知状态', statusClass = 'zhuangtai-weizhi';
|
||||
const n = Number(item.sqzhuangtai);
|
||||
if (!isNaN(n)) {
|
||||
if (n === 0) { displayStatus = '待处理'; statusClass = 'zhuangtai-daichuli'; }
|
||||
else if (n === 1) { displayStatus = '已处罚'; statusClass = 'zhuangtai-yichufa'; }
|
||||
else if (n === 2) { displayStatus = '处罚已撤销'; statusClass = 'zhuangtai-yibohui'; }
|
||||
else if (n === 3) { displayStatus = '申诉中'; statusClass = 'zhuangtai-shensuzhong'; }
|
||||
}
|
||||
return {
|
||||
...item,
|
||||
display_time: displayTime, display_status: displayStatus, status_class: statusClass,
|
||||
full_zhengju_tupian: (item.zhengju_tupian || []).map(url => this.getFullImageUrl(url)),
|
||||
full_shensu_tupian: (item.shensu_tupian || []).map(url => this.getFullImageUrl(url))
|
||||
};
|
||||
});
|
||||
const newList = isLoadMore ? [...this.data.chufaList, ...processedList] : processedList;
|
||||
this.setData({ chufaList: newList, haiyougengduo: data.has_more === true, dangqianye: this.data.dangqianye + 1 });
|
||||
} else this.setData({ haiyougengduo: false });
|
||||
}
|
||||
} catch (e) { wx.showToast({ title: '加载失败', icon: 'none' }); this.setData({ haiyougengduo: false }); }
|
||||
finally { if (isLoadMore) this.setData({ jiazhaigengduo: false }); else this.setData({ jiazhaozhong: false }); }
|
||||
},
|
||||
|
||||
onReachBottom() {
|
||||
if (this.data.fangdouTimer) clearTimeout(this.data.fangdouTimer);
|
||||
const timer = setTimeout(() => {
|
||||
if (this.data.jiazhaozhong || this.data.jiazhaigengduo) return;
|
||||
if (!this.data.haiyougengduo) return;
|
||||
this.loadJifenList(true);
|
||||
}, 300);
|
||||
this.setData({ fangdouTimer: timer });
|
||||
},
|
||||
|
||||
chakanXiangqing(e) { this.setData({ showXiangqing: true, xuanzhongChufa: e.currentTarget.dataset.item }); },
|
||||
guanbiXiangqing() { this.setData({ showXiangqing: false, xuanzhongChufa: {} }); },
|
||||
|
||||
openShensuModal() {
|
||||
const { xuanzhongChufa } = this.data;
|
||||
if (xuanzhongChufa.ssliyou || (xuanzhongChufa.shensu_tupian && xuanzhongChufa.shensu_tupian.length > 0)) {
|
||||
wx.showToast({ title: '您已经提交过申诉,请等待处理结果', icon: 'none' }); return;
|
||||
}
|
||||
this.setData({ showShensuModal: true, shensuLiyou: '', shensuTupian: [], shangchuanJindu: 0, shangchuanZongshu: 0, jinduWidth: '0%', isShangchuanzhong: false });
|
||||
},
|
||||
closeShensuModal() { this.setData({ showShensuModal: false }); },
|
||||
|
||||
onShensuLiyouInput(e) { this.setData({ shensuLiyou: e.detail.value.slice(0, 500) }); },
|
||||
|
||||
chooseShensuTupian() {
|
||||
if (this.data.isShangchuanzhong) return;
|
||||
const shengyu = 9 - this.data.shensuTupian.length;
|
||||
if (shengyu <= 0) { wx.showToast({ title: '最多只能上传9张图片', icon: 'none' }); return; }
|
||||
wx.chooseMedia({ count: shengyu, mediaType: ['image'], sourceType: ['album', 'camera'], success: (res) => {
|
||||
const newTupian = [...this.data.shensuTupian, ...res.tempFiles.map(f => f.tempFilePath)];
|
||||
this.setData({ shensuTupian: newTupian.slice(0, 9) });
|
||||
}});
|
||||
},
|
||||
deleteShensuTupian(e) { const arr = [...this.data.shensuTupian]; arr.splice(e.currentTarget.dataset.index, 1); this.setData({ shensuTupian: arr }); },
|
||||
yulanShensuTupian(e) {
|
||||
const idx = e.currentTarget.dataset.index;
|
||||
const urls = this.data.shensuTupian;
|
||||
if (!urls[idx]) return;
|
||||
wx.previewImage({ current: urls[idx], urls });
|
||||
},
|
||||
yulanTupian(e) {
|
||||
const current = e.currentTarget.dataset.url;
|
||||
const urls = e.currentTarget.dataset.urls || [];
|
||||
const fullCurrent = current.startsWith('http') ? current : this.getFullImageUrl(current);
|
||||
const fullUrls = urls.map(url => url.startsWith('http') ? url : this.getFullImageUrl(url));
|
||||
wx.previewImage({ current: fullCurrent, urls: fullUrls.length ? fullUrls : [fullCurrent] });
|
||||
},
|
||||
|
||||
/* ========== 图片上传(完全照抄原 cfss.js) ========== */
|
||||
// 【仅替换此方法,其余代码一个字别动】
|
||||
getFullImageUrl(relativeUrl) {
|
||||
if (!relativeUrl) return '';
|
||||
if (relativeUrl.startsWith('http')) return relativeUrl;
|
||||
const ossUrl = this.data.ossImageUrl;
|
||||
if (!ossUrl) return relativeUrl;
|
||||
// 确保拼接时只有一个斜杠
|
||||
const base = ossUrl.replace(/\/+$/, '');
|
||||
const rel = relativeUrl.replace(/^\/+/, '');
|
||||
return base + '/' + rel;
|
||||
},
|
||||
getFileExtension(filePath) {
|
||||
const lastDot = filePath.lastIndexOf('.'); if (lastDot === -1) return '.jpg';
|
||||
const ext = filePath.substring(lastDot).toLowerCase();
|
||||
return ['.jpg','.jpeg','.png','.gif','.bmp','.webp'].includes(ext) ? ext : '.jpg';
|
||||
},
|
||||
async getCOSZhengshu() {
|
||||
const res = await request({ url: '/dingdan/dsscpz', method: 'POST', data: { dingdan_id: this.data.xuanzhongChufa.dingdan_id || '', yongtu: 'chufa' } });
|
||||
if (res.statusCode === 200 && res.data.code === 0 && res.data.data) return res.data.data;
|
||||
throw new Error(res?.data?.msg || '获取上传凭证失败');
|
||||
},
|
||||
initCOSClient(tokenData) {
|
||||
const COS = require('../../../../utils/cos-wx-sdk-v5.js');
|
||||
const credentials = tokenData.credentials || tokenData;
|
||||
const bucket = tokenData.bucket || 'julebu-1361527063';
|
||||
const region = tokenData.region || 'ap-shanghai';
|
||||
const cos = new COS({ SimpleUploadMethod: 'putObject', getAuthorization: (options, callback) => { callback({ TmpSecretId: credentials.tmpSecretId, TmpSecretKey: credentials.tmpSecretKey, SecurityToken: credentials.sessionToken || '', StartTime: tokenData.startTime || Math.floor(Date.now() / 1000), ExpiredTime: tokenData.expiredTime || (Math.floor(Date.now() / 1000) + 7200) }); } });
|
||||
return { cos, bucket, region };
|
||||
},
|
||||
shangchuanDanZhangTupian(filePath, cosKey, cosClient, bucket, region) {
|
||||
return new Promise(resolve => {
|
||||
const timeoutId = setTimeout(() => resolve({ status: 'optimistic_success', key: cosKey }), 2000);
|
||||
cosClient.uploadFile({ Bucket: bucket, Region: region, Key: cosKey, FilePath: filePath, onProgress: (info) => { if (Math.round(info.percent * 100) === 100) { clearTimeout(timeoutId); resolve({ status: 'optimistic_success', key: cosKey }); } } });
|
||||
});
|
||||
},
|
||||
async piliangShangchuanShensuTupian() {
|
||||
const total = this.data.shensuTupian.length;
|
||||
if (total === 0) return [];
|
||||
this.setData({ shangchuanZongshu: total, shangchuanJindu: 0, jinduWidth: '0%', isShangchuanzhong: true });
|
||||
|
||||
let yonghuid = '';
|
||||
const uid = wx.getStorageSync('uid'); if (uid) yonghuid = String(uid).padStart(7, '0');
|
||||
else if (app.globalData.userInfo?.yonghuid) yonghuid = String(app.globalData.userInfo.yonghuid).padStart(7, '0');
|
||||
if (!yonghuid) { wx.showToast({ title: '请先登录', icon: 'none' }); this.setData({ isShangchuanzhong: false }); return []; }
|
||||
|
||||
const preGeneratedUrls = [];
|
||||
for (let i = 0; i < total; i++) {
|
||||
const timestamp = Date.now() + i, random = Math.floor(Math.random() * 10000);
|
||||
preGeneratedUrls.push(`a_long/chfajltp/dssstp/${yonghuid}_${timestamp}_${random}${this.getFileExtension(this.data.shensuTupian[i])}`);
|
||||
}
|
||||
|
||||
let cosClient, bucket, region;
|
||||
try { const tokenData = await this.getCOSZhengshu(); const c = this.initCOSClient(tokenData); cosClient = c.cos; bucket = c.bucket; region = c.region; }
|
||||
catch (e) { wx.showToast({ title: '上传初始化失败', icon: 'none' }); this.setData({ isShangchuanzhong: false }); return []; }
|
||||
|
||||
for (let i = 0; i < total; i++) {
|
||||
await this.shangchuanDanZhangTupian(this.data.shensuTupian[i], preGeneratedUrls[i], cosClient, bucket, region);
|
||||
const done = i + 1;
|
||||
this.setData({ shangchuanJindu: done, jinduWidth: `${((done / total) * 100).toFixed(0)}%` });
|
||||
}
|
||||
this.setData({ isShangchuanzhong: false });
|
||||
return preGeneratedUrls;
|
||||
},
|
||||
|
||||
async submitShensu() {
|
||||
if (!this.data.shensuLiyou.trim()) { wx.showToast({ title: '请输入申诉理由', icon: 'none' }); return; }
|
||||
if (this.data.shensuTupian.length === 0) { wx.showModal({ title: '提示', content: '未上传任何图片,确定要提交申诉吗?', success: async (res) => { if (res.confirm) await this.tijiaoShensuData([]); } }); return; }
|
||||
wx.showToast({ title: '开始上传图片...', icon: 'none' });
|
||||
try {
|
||||
const urls = await this.piliangShangchuanShensuTupian();
|
||||
if (!urls || urls.length === 0) throw new Error('图片上传失败');
|
||||
await this.tijiaoShensuData(urls);
|
||||
} catch (e) { wx.showToast({ title: e.message || '提交失败', icon: 'none' }); }
|
||||
},
|
||||
async tijiaoShensuData(tupianUrls) {
|
||||
wx.showToast({ title: '提交申诉中...', icon: 'none' });
|
||||
try {
|
||||
const res = await request({ url: '/yonghu/dscfss', method: 'POST', data: { chufa_id: this.data.xuanzhongChufa.id, shensu_liyou: this.data.shensuLiyou, shensu_tupian_urls: tupianUrls } });
|
||||
if (res.statusCode === 200 && res.data.code === 0) {
|
||||
const newList = this.data.chufaList.map(item => {
|
||||
if (item.id === this.data.xuanzhongChufa.id) { return { ...item, sqzhuangtai: 3, ssliyou: this.data.shensuLiyou, display_status: '申诉中', status_class: 'zhuangtai-shensuzhong', full_shensu_tupian: tupianUrls.map(url => this.getFullImageUrl(url)) }; }
|
||||
return item;
|
||||
});
|
||||
this.setData({ chufaList: newList, showShensuModal: false, showXiangqing: false });
|
||||
wx.showToast({ title: '申诉提交成功,请等待处理', icon: 'success' });
|
||||
} else throw new Error(res.data?.msg || '提交申诉失败');
|
||||
} catch (e) { wx.showToast({ title: e.message || '提交失败', icon: 'none' }); }
|
||||
}
|
||||
}
|
||||
});
|
||||
3
pages/cfss/components/jifen-list/jifen-list.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"component": true
|
||||
}
|
||||
105
pages/cfss/components/jifen-list/jifen-list.wxml
Normal file
@@ -0,0 +1,105 @@
|
||||
<view class="container">
|
||||
<scroll-view scroll-y class="scroll" bindscrolltolower="onReachBottom">
|
||||
<view wx:if="{{ jiazhaozhong && chufaList.length === 0 }}" class="tip">加载中...</view>
|
||||
<block wx:for="{{ chufaList }}" wx:key="id">
|
||||
<view class="card" bindtap="chakanXiangqing" data-item="{{ item }}">
|
||||
<view class="card-head">
|
||||
<text class="time">{{ item.display_time }}</text>
|
||||
<text class="tag {{ item.status_class }}">{{ item.display_status }}</text>
|
||||
</view>
|
||||
<text class="info">申请人:{{ item.qingqiuid || '--' }}</text>
|
||||
<text class="info">理由:{{ item.cfliyou || '无' }}</text>
|
||||
<text class="info" wx:if="{{ item.dingdan_id }}">订单:{{ item.dingdan_id }}</text>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- ========== 底部:按钮 / 加载中 / 没有更多 ========== -->
|
||||
<view wx:if="{{ haiyougengduo && !jiazhaigengduo }}" class="load-more-btn" bindtap="onReachBottom">
|
||||
<text>点击获取更多</text>
|
||||
</view>
|
||||
<view wx:if="{{ jiazhaigengduo }}" class="tip">加载中...</view>
|
||||
<view wx:if="{{ !haiyougengduo && chufaList.length > 0 }}" class="tip">—— 没有更多了 ——</view>
|
||||
<view wx:if="{{ !jiazhaozhong && chufaList.length === 0 }}" class="empty">暂无积分处罚记录</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 详情弹窗(不动) -->
|
||||
<view wx:if="{{ showXiangqing }}" class="modal-mask">
|
||||
<view class="modal-panel">
|
||||
<view class="modal-head">
|
||||
<text class="modal-title">处罚详情</text>
|
||||
<view class="modal-close" bindtap="guanbiXiangqing">✕</view>
|
||||
</view>
|
||||
<scroll-view scroll-y class="modal-body">
|
||||
<view class="row"><text class="lbl">订单编号</text><text class="val">{{ xuanzhongChufa.dingdan_id || '--' }}</text></view>
|
||||
<view class="row"><text class="lbl">申请人ID</text><text class="val">{{ xuanzhongChufa.qingqiuid || '--' }}</text></view>
|
||||
<view class="row"><text class="lbl">时间</text><text class="val">{{ xuanzhongChufa.display_time }}</text></view>
|
||||
<view class="row"><text class="lbl">状态</text><text class="val"><text class="tag {{ xuanzhongChufa.status_class }}">{{ xuanzhongChufa.display_status }}</text></text></view>
|
||||
<view class="row"><text class="lbl">处罚理由</text><text class="val">{{ xuanzhongChufa.cfliyou || '无' }}</text></view>
|
||||
|
||||
<view wx:if="{{ xuanzhongChufa.full_zhengju_tupian.length }}" class="section">
|
||||
<text class="sec-title">证据图片</text>
|
||||
<view class="img-grid">
|
||||
<block wx:for="{{ xuanzhongChufa.full_zhengju_tupian }}" wx:key="index">
|
||||
<view class="img-box" bindtap="yulanTupian" data-url="{{ item }}" data-urls="{{ xuanzhongChufa.full_zhengju_tupian }}">
|
||||
<image class="img" src="{{ item }}" mode="aspectFill" />
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{ xuanzhongChufa.ssliyou || xuanzhongChufa.full_shensu_tupian.length }}" class="section">
|
||||
<text class="sec-title">我的申诉</text>
|
||||
<view wx:if="{{ xuanzhongChufa.ssliyou }}" class="row"><text class="lbl">理由</text><text class="val">{{ xuanzhongChufa.ssliyou }}</text></view>
|
||||
<view wx:if="{{ xuanzhongChufa.full_shensu_tupian.length }}" class="img-grid">
|
||||
<block wx:for="{{ xuanzhongChufa.full_shensu_tupian }}" wx:key="index">
|
||||
<view class="img-box" bindtap="yulanTupian" data-url="{{ item }}" data-urls="{{ xuanzhongChufa.full_shensu_tupian }}">
|
||||
<image class="img" src="{{ item }}" mode="aspectFill" />
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="modal-foot">
|
||||
<view wx:if="{{ xuanzhongChufa.sqzhuangtai === 0 }}" class="btn primary" bindtap="openShensuModal">申诉</view>
|
||||
<view class="btn default" bindtap="guanbiXiangqing">关闭</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 申诉弹窗(不动) -->
|
||||
<view wx:if="{{ showShensuModal }}" class="modal-mask">
|
||||
<view class="modal-panel">
|
||||
<view class="modal-head">
|
||||
<text class="modal-title">提交申诉</text>
|
||||
<view class="modal-close" bindtap="closeShensuModal">✕</view>
|
||||
</view>
|
||||
<scroll-view scroll-y class="modal-body">
|
||||
<view class="form-group">
|
||||
<text class="sec-title">申诉理由</text>
|
||||
<textarea class="textarea" placeholder="请输入申诉理由(最多500字)" value="{{ shensuLiyou }}" bindinput="onShensuLiyouInput" maxlength="500" />
|
||||
<text class="word-count">{{ shensuLiyou.length }}/500</text>
|
||||
</view>
|
||||
<view class="form-group">
|
||||
<text class="sec-title">上传凭证(最多9张)</text>
|
||||
<view class="img-grid">
|
||||
<block wx:for="{{ shensuTupian }}" wx:key="index">
|
||||
<view class="img-box">
|
||||
<image class="img" src="{{ item }}" mode="aspectFill" bindtap="yulanShensuTupian" data-index="{{ index }}" />
|
||||
<view class="img-del" catchtap="deleteShensuTupian" data-index="{{ index }}">✕</view>
|
||||
</view>
|
||||
</block>
|
||||
<view wx:if="{{ shensuTupian.length < 9 }}" class="img-add" bindtap="chooseShensuTupian">+</view>
|
||||
</view>
|
||||
<view wx:if="{{ shangchuanZongshu > 0 }}" class="progress-box">
|
||||
<view class="progress-bar"><view class="progress-inner" style="width: {{ jinduWidth }}"></view></view>
|
||||
<text class="progress-text">{{ shangchuanJindu }}/{{ shangchuanZongshu }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="modal-foot">
|
||||
<view class="btn default" bindtap="closeShensuModal">取消</view>
|
||||
<view class="btn primary" bindtap="submitShensu">提交申诉</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
86
pages/cfss/components/jifen-list/jifen-list.wxss
Normal file
@@ -0,0 +1,86 @@
|
||||
/* 页面与容器 */
|
||||
page { background: #f5f5f5; }
|
||||
|
||||
/* 🔥 组件自身必须占满宽度,且 padding 左右严格相等 */
|
||||
.container { width: 100%; box-sizing: border-box; display: flex; flex-direction: column; }
|
||||
|
||||
/* 🔥 滚动区 padding 左右必须相等,且容器用 box-sizing */
|
||||
.scroll { width: 100%; box-sizing: border-box; padding: 20rpx 24rpx; }
|
||||
|
||||
/* 提示文字 */
|
||||
.tip { text-align: center; color: #999; font-size: 24rpx; padding: 30rpx 0; }
|
||||
.empty { display: flex; flex-direction: column; align-items: center; padding-top: 200rpx; color: #999; font-size: 28rpx; }
|
||||
|
||||
/* 🔥 卡片 width:100% + box-sizing 使左右边距完全由父容器决定 */
|
||||
.card { width: 100%; box-sizing: border-box; background: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 16rpx; }
|
||||
.card-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12rpx; }
|
||||
.time { font-size: 24rpx; color: #999; }
|
||||
.tag { font-size: 20rpx; padding: 2rpx 14rpx; border-radius: 16rpx; display: inline-block; }
|
||||
.zhuangtai-daichuli { background: #fff3e0; color: #e65100; }
|
||||
.zhuangtai-yichufa { background: #e8f5e9; color: #2e7d32; }
|
||||
.zhuangtai-yibohui { background: #f3e5f5; color: #7b1fa2; }
|
||||
.zhuangtai-shensuzhong { background: #e3f2fd; color: #1565c0; }
|
||||
.zhuangtai-weizhi { background: #f0f0f0; color: #888; }
|
||||
.info { display: block; font-size: 26rpx; color: #333; margin-top: 8rpx; }
|
||||
|
||||
/* 弹窗遮罩 */
|
||||
.modal-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 999; display: flex; align-items: flex-end; justify-content: center; }
|
||||
|
||||
/* 🔥 弹窗面板 width:100% + box-sizing */
|
||||
.modal-panel { width: 100%; box-sizing: border-box; max-height: 85vh; background: #fff; border-radius: 24rpx 24rpx 0 0; display: flex; flex-direction: column; overflow: hidden; }
|
||||
.modal-head { display: flex; justify-content: space-between; align-items: center; padding: 28rpx 30rpx 16rpx; border-bottom: 1px solid #eee; }
|
||||
.modal-title { font-size: 32rpx; font-weight: 600; }
|
||||
.modal-close { width: 44rpx; height: 44rpx; border-radius: 50%; background: #f5f5f5; display: flex; align-items: center; justify-content: center; font-size: 26rpx; color: #888; }
|
||||
|
||||
/* 🔥 内容区左右 padding 严格相等 */
|
||||
.modal-body { width: 100%; box-sizing: border-box; padding: 20rpx 30rpx; overflow-y: auto; }
|
||||
.row { display: flex; margin-bottom: 16rpx; }
|
||||
.lbl { width: 150rpx; font-size: 26rpx; color: #888; flex-shrink: 0; }
|
||||
.val { flex: 1; font-size: 26rpx; color: #333; word-break: break-all; }
|
||||
.section { margin: 20rpx 0; }
|
||||
.sec-title { font-size: 26rpx; color: #888; margin-bottom: 12rpx; display: block; }
|
||||
|
||||
/* 🔥 图片网格:gap 均等,无任何额外水平偏移 */
|
||||
.img-grid { width: 100%; box-sizing: border-box; display: flex; flex-wrap: wrap; gap: 12rpx; }
|
||||
.img { width: calc((100% - 24rpx) / 3); height: 0; padding-bottom: calc((100% - 24rpx) / 3); border-radius: 10rpx; background: #f0f0f0; display: block; position: relative; }
|
||||
.img-box { position: relative; width: calc((100% - 24rpx) / 3); height: 0; padding-bottom: calc((100% - 24rpx) / 3); }
|
||||
.img-box .img { position: absolute; top: 0; left: 0; width: 100%; height: 100%; padding-bottom: 0; }
|
||||
.img-del { position: absolute; top: -6rpx; right: -6rpx; width: 34rpx; height: 34rpx; background: #e53935; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: #fff; font-size: 18rpx; z-index: 2; }
|
||||
.img-add { width: calc((100% - 24rpx) / 3); height: 0; padding-bottom: calc((100% - 24rpx) / 3); border: 2rpx dashed #ccc; border-radius: 10rpx; display: flex; align-items: center; justify-content: center; font-size: 52rpx; color: #aaa; box-sizing: border-box; position: relative; }
|
||||
|
||||
/* 表单 */
|
||||
.form-group { margin-bottom: 24rpx; }
|
||||
.textarea { width: 100%; box-sizing: border-box; height: 180rpx; background: #f9f9f9; border-radius: 10rpx; padding: 16rpx; font-size: 26rpx; border: 1px solid #eee; }
|
||||
.word-count { text-align: right; font-size: 22rpx; color: #bbb; margin-top: 6rpx; }
|
||||
.progress-box { margin-top: 12rpx; }
|
||||
.progress-bar { height: 10rpx; background: #eee; border-radius: 5rpx; }
|
||||
.progress-inner { height: 100%; background: #2e7d32; border-radius: 5rpx; }
|
||||
.progress-text { font-size: 22rpx; color: #2e7d32; text-align: right; margin-top: 4rpx; }
|
||||
|
||||
/* 🔥 底部按钮区左右 padding 严格均等 */
|
||||
.modal-foot { width: 100%; box-sizing: border-box; display: flex; padding: 16rpx 30rpx 30rpx; gap: 20rpx; border-top: 1px solid #eee; }
|
||||
.btn { flex: 1; text-align: center; padding: 22rpx 0; border-radius: 16rpx; font-size: 28rpx; font-weight: 600; }
|
||||
.primary { background: #2e7d32; color: #fff; }
|
||||
.default { background: #f0f0f0; color: #555; }
|
||||
.btn:active { opacity: 0.8; }
|
||||
|
||||
|
||||
/* ===== 获取更多按钮 ===== */
|
||||
.load-more-btn {
|
||||
width: 80%;
|
||||
margin: 20rpx auto 30rpx;
|
||||
padding: 22rpx 0;
|
||||
text-align: center;
|
||||
background: #ffffff;
|
||||
border: 2rpx solid #e0e0e0;
|
||||
border-radius: 20rpx;
|
||||
color: #555;
|
||||
font-size: 26rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.02);
|
||||
transition: all 0.15s;
|
||||
}
|
||||
.load-more-btn:active {
|
||||
background: #f5f5f5;
|
||||
border-color: #2e7d32;
|
||||
color: #2e7d32;
|
||||
}
|
||||
288
pages/czjilu/czjilu.js
Normal file
@@ -0,0 +1,288 @@
|
||||
// 管事会员记录页面逻辑
|
||||
import request from '../../utils/request.js';
|
||||
|
||||
// 每页数据条数常量 - 统一管理,方便修改
|
||||
const MEIYE_TIAOSHU = 50;
|
||||
|
||||
Page({
|
||||
data: {
|
||||
// 从全局变量获取的数据
|
||||
yaoqingzongshu: 0, // 邀请打手总数
|
||||
fenyongzonge: '0.00', // 分成总额
|
||||
|
||||
// 从接口获取的数据
|
||||
chongzhiDashouShuliang: 0, // 充值打手数量
|
||||
dashouList: [], // 打手列表数据
|
||||
zongTiaoshu: 0, // 总条数
|
||||
|
||||
// 分页相关
|
||||
dangqianYe: 1, // 当前页
|
||||
meiyouGengduo: false, // 没有更多数据
|
||||
jiazaiZhong: false, // 加载中状态
|
||||
jiazaiGengduo: false, // 加载更多状态
|
||||
|
||||
// UI状态
|
||||
cuowuTishi: '', // 错误提示
|
||||
shuaxinKezhi: false, // 刷新按钮是否可点击
|
||||
shuaxinCd: 2000, // 刷新冷却时间(毫秒)
|
||||
|
||||
// 常量(用于模板)
|
||||
meiyeTiaoshu: MEIYE_TIAOSHU // 每页条数
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
// 从全局变量获取管事数据
|
||||
this.huoquQuanjubianliang();
|
||||
|
||||
// 加载第一页数据
|
||||
this.jiazaiShuju(true);
|
||||
},
|
||||
|
||||
onShow() {
|
||||
// 页面显示时检查全局变量是否有更新
|
||||
this.registerNotificationComponent();
|
||||
this.huoquQuanjubianliang();
|
||||
},
|
||||
// 🆕 新增:注册通知组件
|
||||
registerNotificationComponent() {
|
||||
const app = getApp();
|
||||
const notificationComp = this.selectComponent('#global-notification');
|
||||
|
||||
if (notificationComp && notificationComp.showNotification) {
|
||||
console.log('🏪 商城页面注册通知组件');
|
||||
app.globalData.globalNotification = {
|
||||
show: (data) => notificationComp.showNotification(data),
|
||||
hide: () => notificationComp.hideNotification()
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// 获取全局变量数据
|
||||
huoquQuanjubianliang() {
|
||||
const app = getApp();
|
||||
const guanshiData = app.globalData.guanshi;
|
||||
|
||||
if (guanshiData) {
|
||||
this.setData({
|
||||
yaoqingzongshu: guanshiData.yaoqingzongshu || 0,
|
||||
fenyongzonge: guanshiData.fenyongzonge || '0.00'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 加载数据核心方法
|
||||
async jiazaiShuju(chushihua = false) {
|
||||
// 防止重复请求
|
||||
if (this.data.jiazaiZhong) return;
|
||||
|
||||
// 如果是初始化加载,重置分页
|
||||
if (chushihua) {
|
||||
this.setData({
|
||||
dangqianYe: 1,
|
||||
meiyouGengduo: false,
|
||||
dashouList: [],
|
||||
zongTiaoshu: 0,
|
||||
chongzhiDashouShuliang: 0
|
||||
});
|
||||
} else {
|
||||
// 上拉加载更多时,检查是否还有更多数据
|
||||
if (this.data.meiyouGengduo) return;
|
||||
|
||||
// 如果有数据但不足一页,说明后端已经没数据了
|
||||
if (this.data.dashouList.length > 0 && this.data.dashouList.length < MEIYE_TIAOSHU) {
|
||||
this.setData({ meiyouGengduo: true });
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果数据量已经达到总条数,则不请求
|
||||
if (this.data.dashouList.length >= this.data.zongTiaoshu && this.data.zongTiaoshu > 0) {
|
||||
this.setData({ meiyouGengduo: true });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 设置加载状态
|
||||
this.setData({
|
||||
jiazaiZhong: chushihua ? true : false,
|
||||
jiazaiGengduo: !chushihua ? true : false,
|
||||
cuowuTishi: ''
|
||||
});
|
||||
|
||||
try {
|
||||
const { dangqianYe } = this.data;
|
||||
|
||||
// 严格按照您的request规范调用
|
||||
const res = await request({
|
||||
url: '/shangpin/fenhonghq',
|
||||
method: 'POST',
|
||||
data: {
|
||||
page: dangqianYe,
|
||||
page_size: MEIYE_TIAOSHU // 使用统一常量
|
||||
}
|
||||
});
|
||||
|
||||
// 处理返回数据
|
||||
if (res.data.code === 200) {
|
||||
const data = res.data.data || {};
|
||||
const newList = data.list || [];
|
||||
const total = data.total || 0;
|
||||
|
||||
// 处理头像URL拼接
|
||||
const processedList = this.chuliTouxiangURL(newList);
|
||||
|
||||
// 计算是否有更多数据
|
||||
const hasMoreData = processedList.length === MEIYE_TIAOSHU;
|
||||
|
||||
// 更新数据
|
||||
this.setData({
|
||||
dashouList: chushihua ? processedList : [...this.data.dashouList, ...processedList],
|
||||
zongTiaoshu: total,
|
||||
chongzhiDashouShuliang: data.chongzhi_dashou_shuliang || 0,
|
||||
meiyouGengduo: !hasMoreData,
|
||||
dangqianYe: chushihua ? 2 : this.data.dangqianYe + 1
|
||||
});
|
||||
|
||||
// 如果数据为空,显示空状态
|
||||
if (chushihua && processedList.length === 0) {
|
||||
wx.showToast({
|
||||
title: '暂无数据',
|
||||
icon: 'none',
|
||||
duration: 1500
|
||||
});
|
||||
}
|
||||
|
||||
// 如果加载了数据,给个提示
|
||||
if (processedList.length > 0) {
|
||||
if (chushihua) {
|
||||
wx.showToast({
|
||||
title: `已加载${processedList.length}条记录`,
|
||||
icon: 'success',
|
||||
duration: 1000
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.setData({
|
||||
cuowuTishi: res.data.msg || '数据加载失败'
|
||||
});
|
||||
wx.showToast({
|
||||
title: res.data.msg || '加载失败',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载数据失败:', error);
|
||||
this.setData({
|
||||
cuowuTishi: '网络错误,请检查网络连接'
|
||||
});
|
||||
wx.showToast({
|
||||
title: '网络错误',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
} finally {
|
||||
this.setData({
|
||||
jiazaiZhong: false,
|
||||
jiazaiGengduo: false
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 处理头像URL拼接
|
||||
chuliTouxiangURL(list) {
|
||||
const app = getApp();
|
||||
const ossUrl = app.globalData.ossImageUrl || '';
|
||||
|
||||
return list.map(item => {
|
||||
// 确保有头像URL,如果没有则使用默认头像
|
||||
let touxiangUrl = item.avatar || '';
|
||||
if (touxiangUrl && !touxiangUrl.startsWith('http')) {
|
||||
touxiangUrl = ossUrl + touxiangUrl;
|
||||
}
|
||||
|
||||
// 格式化时间
|
||||
let shijian = item.create_time || '';
|
||||
if (shijian) {
|
||||
// 如果是时间戳,转换为日期格式
|
||||
if (/^\d+$/.test(shijian)) {
|
||||
const date = new Date(parseInt(shijian));
|
||||
shijian = `${date.getMonth() + 1}月${date.getDate()}日 ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
|
||||
} else {
|
||||
// 如果是字符串,提取日期和时间
|
||||
try {
|
||||
const dateObj = new Date(shijian);
|
||||
shijian = `${dateObj.getMonth() + 1}月${dateObj.getDate()}日 ${dateObj.getHours().toString().padStart(2, '0')}:${dateObj.getMinutes().toString().padStart(2, '0')}`;
|
||||
} catch (e) {
|
||||
shijian = shijian.substring(0, 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...item,
|
||||
touxiangUrl: touxiangUrl || '/images/default-avatar.png',
|
||||
shijian: shijian,
|
||||
fenhong: parseFloat(item.fenhong || 0).toFixed(2)
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
// 刷新数据(带防重复点击)
|
||||
shuaxinShuju() {
|
||||
// 检查是否在冷却中
|
||||
if (this.data.shuaxinKezhi) {
|
||||
wx.showToast({
|
||||
title: `请等待${this.data.shuaxinCd/1000}秒`,
|
||||
icon: 'none',
|
||||
duration: 1000
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置冷却状态
|
||||
this.setData({ shuaxinKezhi: true });
|
||||
|
||||
// 显示刷新动画
|
||||
this.setData({
|
||||
jiazaiZhong: true
|
||||
});
|
||||
|
||||
// 刷新数据
|
||||
this.jiazaiShuju(true).finally(() => {
|
||||
// 2秒后恢复按钮可点击
|
||||
setTimeout(() => {
|
||||
this.setData({ shuaxinKezhi: false });
|
||||
}, this.data.shuaxinCd);
|
||||
});
|
||||
},
|
||||
|
||||
// 上拉加载更多
|
||||
shanglaJiazai() {
|
||||
// 没有更多数据时不再请求
|
||||
if (this.data.meiyouGengduo) {
|
||||
wx.showToast({
|
||||
title: '已经到底了',
|
||||
icon: 'none',
|
||||
duration: 1000
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果正在加载,不再请求
|
||||
if (this.data.jiazaiGengduo || this.data.jiazaiZhong) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.jiazaiShuju(false);
|
||||
},
|
||||
|
||||
// 重试加载
|
||||
chongshiJiazai() {
|
||||
this.setData({
|
||||
cuowuTishi: '',
|
||||
meiyouGengduo: false
|
||||
});
|
||||
this.jiazaiShuju(true);
|
||||
}
|
||||
});
|
||||
11
pages/czjilu/czjilu.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
"global-notification": "/components/global-notification/global-notification"
|
||||
},
|
||||
"navigationBarTitleText": "会员记录",
|
||||
"navigationBarBackgroundColor": "#0a0a0f",
|
||||
"navigationBarTextStyle": "white",
|
||||
"backgroundColor": "#0a0a0f",
|
||||
"backgroundTextStyle": "light",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
119
pages/czjilu/czjilu.wxml
Normal file
@@ -0,0 +1,119 @@
|
||||
<!-- 管事会员记录页面 -->
|
||||
<view class="page-container">
|
||||
<!-- 固定顶部区域 - 不动 -->
|
||||
<view class="fixed-top">
|
||||
<!-- 顶部统计区域 -->
|
||||
<view class="tongji-quyu">
|
||||
<!-- 分红总额 -->
|
||||
<view class="fenyong-zonge">
|
||||
<view class="zonge-label">分红总额</view>
|
||||
<view class="zonge-value">¥{{fenyongzonge}}</view>
|
||||
<view class="zonge-tip">累计收益</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部统计信息 -->
|
||||
<view class="tongji-bottom">
|
||||
<view class="tongji-item">
|
||||
<view class="tongji-number">{{yaoqingzongshu}}</view>
|
||||
<view class="tongji-label">邀请打手</view>
|
||||
</view>
|
||||
|
||||
<view class="tongji-divider"></view>
|
||||
|
||||
<view class="tongji-item">
|
||||
<view class="tongji-number">{{chongzhiDashouShuliang}}</view>
|
||||
<view class="tongji-label">充值打手</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 刷新按钮 -->
|
||||
<view class="shuaxin-anniu" bindtap="shuaxinShuju">
|
||||
<text class="shuaxin-icon {{shuaxinKezhi ? 'shuaxin-disabled' : ''}}">↻</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 滚动区域 - 只包裹列表部分 -->
|
||||
<scroll-view
|
||||
class="scroll-area"
|
||||
scroll-y
|
||||
enable-back-to-top
|
||||
bindscrolltolower="shanglaJiazai">
|
||||
|
||||
<!-- 全局加载状态 -->
|
||||
<view wx:if="{{jiazaiZhong && dashouList.length === 0}}" class="jiazai-quanju">
|
||||
<view class="jiazai-spinner"></view>
|
||||
<view class="jiazai-text">加载中...</view>
|
||||
</view>
|
||||
|
||||
<!-- 空数据提示 -->
|
||||
<view wx:if="{{!jiazaiZhong && dashouList.length === 0}}" class="kong-shuju">
|
||||
<view class="kong-icon">🚀</view>
|
||||
<view class="kong-text">暂无会员记录</view>
|
||||
<view class="kong-tip">快去邀请打手吧</view>
|
||||
<view class="kong-btn" bindtap="shuaxinShuju">点击刷新</view>
|
||||
</view>
|
||||
|
||||
<!-- 会员记录卡片列表 -->
|
||||
<block wx:if="{{dashouList.length > 0}}">
|
||||
<!-- 列表标题 -->
|
||||
<view class="list-title">
|
||||
<text class="title-text">充值记录</text>
|
||||
<text class="title-count">({{dashouList.length}}/{{zongTiaoshu}})</text>
|
||||
</view>
|
||||
|
||||
<!-- 卡片列表 -->
|
||||
<view class="card-list">
|
||||
<block wx:for="{{dashouList}}" wx:key="index">
|
||||
<view class="dashou-card">
|
||||
<!-- 圆形头像区域 -->
|
||||
<view class="touxiang-quyu">
|
||||
<image
|
||||
class="dashou-touxiang"
|
||||
src="{{item.touxiangUrl}}"
|
||||
mode="aspectFill">
|
||||
</image>
|
||||
<!-- 头像边框效果 -->
|
||||
<view class="touxiang-border"></view>
|
||||
</view>
|
||||
|
||||
<!-- 信息区域 -->
|
||||
<view class="xinxi-quyu">
|
||||
<view class="nicheng">{{item.nicheng}}</view>
|
||||
<view class="dashou-id">ID: {{item.yonghuid}}</view>
|
||||
</view>
|
||||
|
||||
<!-- 分红金额区域 -->
|
||||
<view class="fenhong-quyu">
|
||||
<view class="fenhong-value">
|
||||
<text class="fenhong-jiahao">+</text>
|
||||
<text class="fenhong-shuzi">¥{{item.fenhong}}</text>
|
||||
</view>
|
||||
<view class="shijian">{{item.shijian}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多提示 -->
|
||||
<view wx:if="{{jiazaiGengduo}}" class="jiazai-gengduo">
|
||||
<view class="gengduo-spinner"></view>
|
||||
<text class="gengduo-text">加载更多中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 没有更多数据 -->
|
||||
<view wx:if="{{meiyouGengduo && dashouList.length > 0}}" class="meiyou-gengduo">
|
||||
<view class="meiyou-icon">✓</view>
|
||||
<text class="meiyou-text">已经到底了,共{{dashouList.length}}条记录</text>
|
||||
</view>
|
||||
</block>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 错误提示 -->
|
||||
<view wx:if="{{cuowuTishi}}" class="cuowu-quyu">
|
||||
<view class="cuowu-icon">⚠️</view>
|
||||
<view class="cuowu-text">{{cuowuTishi}}</view>
|
||||
<view class="cuowu-btn" bindtap="chongshiJiazai">重试加载</view>
|
||||
</view>
|
||||
</view>
|
||||
<global-notification id="global-notification" />
|
||||
506
pages/czjilu/czjilu.wxss
Normal file
@@ -0,0 +1,506 @@
|
||||
/* 管事会员记录页面样式 - 赛博风格 */
|
||||
|
||||
/* 页面容器 */
|
||||
page {
|
||||
height: 100%;
|
||||
background: #0a0a0f;
|
||||
}
|
||||
|
||||
.page-container {
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #0a0a0f 0%, #1a1a2e 50%, #16213e 100%);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 固定顶部区域 */
|
||||
.fixed-top {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
background: linear-gradient(135deg, #0a0a0f 0%, #1a1a2e 100%);
|
||||
padding-bottom: 20rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* 顶部统计区域 - 赛博卡片设计 */
|
||||
.tongji-quyu {
|
||||
position: relative;
|
||||
margin: 20rpx 30rpx 0;
|
||||
padding: 40rpx 30rpx;
|
||||
background: rgba(20, 20, 35, 0.9);
|
||||
border-radius: 24rpx;
|
||||
border: 1px solid rgba(100, 255, 255, 0.1);
|
||||
box-shadow:
|
||||
0 10rpx 30rpx rgba(0, 0, 0, 0.4),
|
||||
0 0 20rpx rgba(100, 255, 255, 0.05) inset,
|
||||
0 0 0 1px rgba(100, 255, 255, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tongji-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background:
|
||||
linear-gradient(45deg,
|
||||
rgba(100, 255, 255, 0.03) 0%,
|
||||
rgba(255, 100, 255, 0.03) 100%);
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
/* 分红总额样式 - 赛博霓虹效果 */
|
||||
.fenyong-zonge {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
padding: 20rpx 0 30rpx;
|
||||
margin-bottom: 25rpx;
|
||||
border-bottom: 1px solid rgba(100, 255, 255, 0.1);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.zonge-label {
|
||||
font-size: 26rpx;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
margin-bottom: 15rpx;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2rpx;
|
||||
}
|
||||
|
||||
.zonge-value {
|
||||
font-size: 60rpx;
|
||||
font-weight: 800;
|
||||
color: #00ffea;
|
||||
margin-bottom: 8rpx;
|
||||
text-shadow:
|
||||
0 0 10rpx rgba(0, 255, 234, 0.5),
|
||||
0 0 20rpx rgba(0, 255, 234, 0.3);
|
||||
font-family: 'Arial', sans-serif;
|
||||
}
|
||||
|
||||
.zonge-tip {
|
||||
font-size: 22rpx;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
/* 底部统计信息 */
|
||||
.tongji-bottom {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tongji-item {
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.tongji-number {
|
||||
font-size: 40rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
margin-bottom: 6rpx;
|
||||
text-shadow: 0 0 10rpx rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.tongji-label {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.tongji-divider {
|
||||
width: 1px;
|
||||
height: 45rpx;
|
||||
background: linear-gradient(to bottom,
|
||||
transparent 0%,
|
||||
rgba(100, 255, 255, 0.3) 50%,
|
||||
transparent 100%);
|
||||
}
|
||||
|
||||
/* 刷新按钮 - 固定在右上角 */
|
||||
.shuaxin-anniu {
|
||||
position: absolute;
|
||||
top: 60rpx;
|
||||
right: 60rpx;
|
||||
z-index: 200;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 70rpx;
|
||||
height: 70rpx;
|
||||
background: rgba(20, 20, 35, 0.95);
|
||||
border-radius: 50%;
|
||||
border: 1px solid rgba(100, 255, 255, 0.2);
|
||||
box-shadow:
|
||||
0 8rpx 20rpx rgba(0, 0, 0, 0.4),
|
||||
0 0 20rpx rgba(100, 255, 255, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.shuaxin-anniu:active {
|
||||
transform: scale(0.95);
|
||||
background: rgba(100, 255, 255, 0.1);
|
||||
box-shadow:
|
||||
0 4rpx 10rpx rgba(0, 0, 0, 0.3),
|
||||
0 0 30rpx rgba(100, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.shuaxin-icon {
|
||||
font-size: 32rpx;
|
||||
color: #00ffea;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.shuaxin-disabled {
|
||||
color: rgba(255, 255, 255, 0.3);
|
||||
animation: rotate 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 滚动区域 - 只包裹列表部分 */
|
||||
.scroll-area {
|
||||
position: fixed;
|
||||
top: 340rpx; /* 根据固定区域高度调整 */
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
/* 全局加载状态 */
|
||||
.jiazai-quanju {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 120rpx 0;
|
||||
}
|
||||
|
||||
.jiazai-spinner {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border: 4rpx solid rgba(100, 255, 255, 0.1);
|
||||
border-top: 4rpx solid #00ffea;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 30rpx;
|
||||
box-shadow: 0 0 20rpx rgba(0, 255, 234, 0.3);
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.jiazai-text {
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
/* 空数据提示 */
|
||||
.kong-shuju {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 120rpx 30rpx;
|
||||
background: rgba(20, 20, 35, 0.5);
|
||||
border-radius: 24rpx;
|
||||
border: 1px solid rgba(100, 255, 255, 0.1);
|
||||
margin: 40rpx 30rpx 0;
|
||||
}
|
||||
|
||||
.kong-icon {
|
||||
font-size: 100rpx;
|
||||
margin-bottom: 30rpx;
|
||||
color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.kong-text {
|
||||
font-size: 32rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
margin-bottom: 10rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.kong-tip {
|
||||
font-size: 26rpx;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
margin-bottom: 50rpx;
|
||||
}
|
||||
|
||||
.kong-btn {
|
||||
padding: 20rpx 50rpx;
|
||||
background: linear-gradient(90deg, #00ffea 0%, #0088ff 100%);
|
||||
color: #000;
|
||||
border-radius: 50rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 8rpx 20rpx rgba(0, 255, 234, 0.3);
|
||||
}
|
||||
|
||||
.kong-btn:active {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 4rpx 10rpx rgba(0, 255, 234, 0.2);
|
||||
}
|
||||
|
||||
/* 列表标题 */
|
||||
.list-title {
|
||||
margin: 0 30rpx 20rpx;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.title-text {
|
||||
font-size: 32rpx;
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
margin-right: 10rpx;
|
||||
text-shadow: 0 0 10rpx rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.title-count {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
/* 卡片列表 */
|
||||
.card-list {
|
||||
margin: 0 30rpx;
|
||||
}
|
||||
|
||||
/* 打手卡片 - 赛博风格设计 */
|
||||
.dashou-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
background: rgba(20, 20, 35, 0.7);
|
||||
border-radius: 20rpx;
|
||||
border: 1px solid rgba(100, 255, 255, 0.1);
|
||||
box-shadow:
|
||||
0 8rpx 24rpx rgba(0, 0, 0, 0.3),
|
||||
0 0 0 1px rgba(100, 255, 255, 0.05);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.dashou-card:active {
|
||||
transform: translateY(-2rpx);
|
||||
background: rgba(30, 30, 50, 0.8);
|
||||
box-shadow:
|
||||
0 12rpx 30rpx rgba(0, 0, 0, 0.4),
|
||||
0 0 20rpx rgba(100, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* 头像区域 - 纯圆形 */
|
||||
.touxiang-quyu {
|
||||
position: relative;
|
||||
margin-right: 30rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.dashou-touxiang {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 50%; /* 确保是纯圆形 */
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.touxiang-border {
|
||||
position: absolute;
|
||||
top: -4rpx;
|
||||
left: -4rpx;
|
||||
width: 108rpx;
|
||||
height: 108rpx;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(45deg, #00ffea, #ff00ff);
|
||||
z-index: 1;
|
||||
opacity: 0.3;
|
||||
animation: borderGlow 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes borderGlow {
|
||||
0%, 100% { opacity: 0.3; }
|
||||
50% { opacity: 0.6; }
|
||||
}
|
||||
|
||||
/* 信息区域 */
|
||||
.xinxi-quyu {
|
||||
flex: 1;
|
||||
min-width: 0; /* 防止内容撑开 */
|
||||
}
|
||||
|
||||
.nicheng {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
margin-bottom: 10rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
text-shadow: 0 0 10rpx rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.dashou-id {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
|
||||
/* 分红金额区域 */
|
||||
.fenhong-quyu {
|
||||
text-align: right;
|
||||
flex-shrink: 0;
|
||||
min-width: 180rpx;
|
||||
}
|
||||
|
||||
.fenhong-value {
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.fenhong-jiahao {
|
||||
font-size: 26rpx;
|
||||
color: #00ff00;
|
||||
margin-right: 4rpx;
|
||||
}
|
||||
|
||||
.fenhong-shuzi {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #00ff00;
|
||||
text-shadow: 0 0 10rpx rgba(0, 255, 0, 0.5);
|
||||
}
|
||||
|
||||
.shijian {
|
||||
font-size: 22rpx;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
/* 加载更多提示 */
|
||||
.jiazai-gengduo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 40rpx 0 60rpx;
|
||||
margin: 0 30rpx;
|
||||
}
|
||||
|
||||
.gengduo-spinner {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border: 3rpx solid rgba(100, 255, 255, 0.1);
|
||||
border-top: 3rpx solid #00ffea;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.gengduo-text {
|
||||
font-size: 26rpx;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
/* 没有更多数据 */
|
||||
.meiyou-gengduo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 50rpx 0 80rpx;
|
||||
margin: 0 30rpx;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.meiyou-icon {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba(0, 255, 234, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 32rpx;
|
||||
color: #00ffea;
|
||||
margin-bottom: 20rpx;
|
||||
border: 1px solid rgba(0, 255, 234, 0.2);
|
||||
}
|
||||
|
||||
.meiyou-text {
|
||||
font-size: 26rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 错误提示 */
|
||||
.cuowu-quyu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 30rpx;
|
||||
background: rgba(40, 20, 20, 0.8);
|
||||
border-radius: 24rpx;
|
||||
border: 1px solid rgba(255, 100, 100, 0.2);
|
||||
margin: 40rpx 30rpx 0;
|
||||
}
|
||||
|
||||
.cuowu-icon {
|
||||
font-size: 80rpx;
|
||||
color: #ff5555;
|
||||
margin-bottom: 30rpx;
|
||||
text-shadow: 0 0 20rpx rgba(255, 85, 85, 0.5);
|
||||
}
|
||||
|
||||
.cuowu-text {
|
||||
font-size: 30rpx;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
margin-bottom: 40rpx;
|
||||
text-align: center;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.cuowu-btn {
|
||||
padding: 20rpx 50rpx;
|
||||
background: linear-gradient(90deg, #ff5555 0%, #ff8800 100%);
|
||||
color: #fff;
|
||||
border-radius: 50rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 8rpx 20rpx rgba(255, 85, 85, 0.3);
|
||||
}
|
||||
|
||||
.cuowu-btn:active {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 4rpx 10rpx rgba(255, 85, 85, 0.2);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* 调整滚动区域的顶部距离,增加间隔 */
|
||||
.scroll-area {
|
||||
position: fixed;
|
||||
top: 400rpx; /* 从 340rpx 增加到 360rpx,增加20rpx的间隔 */
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
/* 如果觉得还不够,可以再增加列表标题的上边距 */
|
||||
.list-title {
|
||||
margin: 10rpx 30rpx 20rpx; /* 从 0 30rpx 20rpx 改为 10rpx 30rpx 20rpx */
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
975
pages/dashouchongzhi/dashouchongzhi.js
Normal file
@@ -0,0 +1,975 @@
|
||||
// pages/dashou-chongzhi/index.js
|
||||
import request from '../../utils/request';
|
||||
// 引入弹窗服务(路径根据实际调整)
|
||||
import PopupService from '../../services/popupService.js';
|
||||
|
||||
Page({
|
||||
data: {
|
||||
// ========== 全局数据 ==========
|
||||
clubmber: [], // 会员列表(注意:全局变量中是 clumber,这里保持原样)
|
||||
yajin: 0, // 押金
|
||||
jifen: 0, // 积分(注意:全局变量中是 jinfen)
|
||||
jifenProgress: 0, // 积分进度条角度
|
||||
|
||||
// ========== 会员商品列表 ==========
|
||||
huiyuanList: [], // 从后端获取的会员列表
|
||||
currentHuiyuanIndex: 0, // 当前选中的会员索引
|
||||
currentHuiyuan: {}, // 当前选中的会员详情
|
||||
|
||||
// ========== 押金充值相关 ==========
|
||||
showYajinModal: false, // 显示押金弹窗
|
||||
yajinAmount: '100', // 押金金额
|
||||
|
||||
// ========== 支付相关 ==========
|
||||
payLoading: false, // 支付加载状态
|
||||
loadingText: '支付中...',
|
||||
|
||||
// ========== 订单ID记录 ==========
|
||||
currentDingdanid: '', // 当前操作的订单ID
|
||||
|
||||
// ========== 计算高度 ==========
|
||||
huiyuanListHeight: 120,
|
||||
|
||||
// ========== 会员详情规则 ==========
|
||||
detailRules: [
|
||||
'会员有效期为30天,从购买当天开始计算',
|
||||
'会员期间享受优先接单特权',
|
||||
'可享受专属客服服务',
|
||||
'会员到期前3天会有提醒',
|
||||
'支持随时续费,续费天数叠加'
|
||||
],
|
||||
|
||||
// ========== 新增:跳转参数处理 ==========
|
||||
// 用于接收从其他页面跳转过来的参数,决定是否自动滚动
|
||||
jumpParams: {},
|
||||
|
||||
// ========== 新增:支付方式选择 ==========
|
||||
showPayMethodModal: false, // 支付方式选择弹窗
|
||||
currentBuyType: null, // 当前购买类型:1会员 2押金 3积分
|
||||
currentHuiyuanId: null, // 当前选中的会员ID(用于会员购买)
|
||||
currentYajinAmount: null, // 当前押金金额(用于押金购买)
|
||||
|
||||
// ========== 新增:余额抵扣身份选择 ==========
|
||||
showBalanceModal: false, // 余额抵扣身份选择弹窗
|
||||
balanceOptions: [], // 后端返回的身份列表
|
||||
selectedBalanceId: null, // 选中的身份ID
|
||||
|
||||
// ========== 新增:余额抵扣确认弹窗 ==========
|
||||
showConfirmModal: false, // 余额抵扣确认弹窗
|
||||
selectedBalanceInfo: null, // 选中的身份详情(用于确认弹窗)
|
||||
|
||||
// ========== 新增:会员详情弹窗 ==========
|
||||
showMemberModal: false,
|
||||
currentMemberDetail: null,
|
||||
},
|
||||
|
||||
// ========== 生命周期函数 ==========
|
||||
onLoad(options) {
|
||||
//console.log('页面加载,跳转参数:', options);
|
||||
|
||||
// ✅ 新增:保存跳转参数
|
||||
this.setData({
|
||||
jumpParams: options || {}
|
||||
});
|
||||
|
||||
this.initPage();
|
||||
},
|
||||
|
||||
// pages/tijiao/tijiao.js 中添加 onHide 方法(如果已有则合并内容)
|
||||
onHide() {
|
||||
const popupComp = this.selectComponent('#popupNotice');
|
||||
if (popupComp && popupComp.cleanup) {
|
||||
popupComp.cleanup();
|
||||
}
|
||||
},
|
||||
|
||||
onShow() {
|
||||
// 每次显示页面都刷新数据
|
||||
this.registerNotificationComponent();
|
||||
this.loadFromGlobalData();
|
||||
this.checkAndRefreshData();
|
||||
|
||||
// 调用统一弹窗组件检查并展示公告
|
||||
PopupService.checkAndShow(this, 'dashouchongzhi');
|
||||
},
|
||||
|
||||
// 注册通知组件(原有,保持不变)
|
||||
registerNotificationComponent() {
|
||||
const app = getApp();
|
||||
const notificationComp = this.selectComponent('#global-notification');
|
||||
|
||||
if (notificationComp && notificationComp.showNotification) {
|
||||
|
||||
app.globalData.globalNotification = {
|
||||
show: (data) => notificationComp.showNotification(data),
|
||||
hide: () => notificationComp.hideNotification()
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
onReady() {
|
||||
// ✅ 新增:页面渲染完成后处理自动滚动
|
||||
this.handleAutoScroll();
|
||||
},
|
||||
|
||||
// ========== 初始化页面 ==========
|
||||
async initPage() {
|
||||
// 从全局数据加载
|
||||
this.loadFromGlobalData();
|
||||
|
||||
// 获取会员商品列表
|
||||
await this.fetchHuiyuanList();
|
||||
|
||||
// 计算会员列表高度
|
||||
this.calculateHuiyuanHeight();
|
||||
},
|
||||
|
||||
// ========== 从全局变量加载数据 ==========
|
||||
loadFromGlobalData() {
|
||||
const app = getApp();
|
||||
const globalData = app.globalData || {};
|
||||
|
||||
// ✅ 重要:保持字段对应关系
|
||||
// 全局变量中是 clumber,页面中是 clubmber
|
||||
// 全局变量中是 jinfen,页面中是 jifen
|
||||
this.setData({
|
||||
clubmber: globalData.clumber || [],
|
||||
yajin: globalData.yajin || 0,
|
||||
jifen: globalData.jinfen || 0 // ✅ 注意:这里从 jinfen 读取
|
||||
});
|
||||
|
||||
// 计算积分进度条
|
||||
this.calculateJifenProgress();
|
||||
},
|
||||
|
||||
// ========== 计算积分进度条角度 ==========
|
||||
calculateJifenProgress() {
|
||||
const jifen = this.data.jifen || 0;
|
||||
const progress = (jifen / 10) * 360; // 满分10分
|
||||
this.setData({
|
||||
jifenProgress: progress
|
||||
});
|
||||
},
|
||||
|
||||
// ========== 计算会员列表高度 ==========
|
||||
calculateHuiyuanHeight() {
|
||||
const clubmber = this.data.clubmber || [];
|
||||
|
||||
const itemHeight = 100; // 每个会员标签高度
|
||||
const maxHeight = 300; // 最大高度(3个会员)
|
||||
|
||||
let height = clubmber.length * itemHeight;
|
||||
height = Math.min(height, maxHeight);
|
||||
|
||||
this.setData({
|
||||
huiyuanListHeight: height
|
||||
});
|
||||
},
|
||||
|
||||
// ========== 获取会员商品列表 ==========
|
||||
async fetchHuiyuanList() {
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/shangpin/dshyhq',
|
||||
method: 'POST',
|
||||
data: {}
|
||||
});
|
||||
|
||||
if (res.data.code === 200) {
|
||||
const huiyuanList = res.data.data || [];
|
||||
|
||||
// 处理数据,标记已购买的会员
|
||||
const processedList = this.processHuiyuanList(huiyuanList);
|
||||
|
||||
this.setData({
|
||||
huiyuanList: processedList,
|
||||
currentHuiyuan: processedList[0] || {}
|
||||
});
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: res.data.message || '获取会员列表失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取会员列表失败:', error);
|
||||
wx.showToast({
|
||||
title: '网络错误,请重试',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// ========== 处理会员列表,标记已购买的会员 ==========
|
||||
processHuiyuanList(huiyuanList) {
|
||||
const clubmber = this.data.clubmber || [];
|
||||
|
||||
return huiyuanList.map(item => {
|
||||
// 检查是否已购买(注意:会员ID字段是 id)
|
||||
const boughtItem = clubmber.find(c => c.huiyuanid === item.id);
|
||||
|
||||
return {
|
||||
...item,
|
||||
isBought: !!boughtItem,
|
||||
buyInfo: boughtItem || null
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
// ========== Swiper切换事件 ==========
|
||||
onSwiperChange(e) {
|
||||
const current = e.detail.current;
|
||||
const huiyuanList = this.data.huiyuanList || [];
|
||||
|
||||
this.setData({
|
||||
currentHuiyuanIndex: current,
|
||||
currentHuiyuan: huiyuanList[current] || {}
|
||||
});
|
||||
},
|
||||
|
||||
// ========== 手动选择会员 ==========
|
||||
selectHuiyuan(e) {
|
||||
const index = e.currentTarget.dataset.index;
|
||||
const huiyuanList = this.data.huiyuanList || [];
|
||||
|
||||
this.setData({
|
||||
currentHuiyuanIndex: index,
|
||||
currentHuiyuan: huiyuanList[index] || {}
|
||||
});
|
||||
},
|
||||
|
||||
// ========== 格式化到期时间 ==========
|
||||
formatDaoqiTime(daoqi) {
|
||||
if (!daoqi) return '';
|
||||
|
||||
try {
|
||||
// 如果是标准日期格式,格式化为 YYYY-MM-DD
|
||||
if (daoqi.includes(' ')) {
|
||||
const dateStr = daoqi.split(' ')[0];
|
||||
const dateParts = dateStr.split('-');
|
||||
if (dateParts.length === 3) {
|
||||
return `${dateParts[0]}-${dateParts[1]}-${dateParts[2]}`;
|
||||
}
|
||||
}
|
||||
return daoqi;
|
||||
} catch (error) {
|
||||
console.error('格式化到期时间错误:', error);
|
||||
return daoqi;
|
||||
}
|
||||
},
|
||||
|
||||
// ========== 计算剩余时间 ==========
|
||||
formatRemainTime(daoqi) {
|
||||
if (!daoqi) return '';
|
||||
|
||||
try {
|
||||
const now = new Date();
|
||||
const end = new Date(daoqi);
|
||||
const diff = end - now;
|
||||
|
||||
if (diff <= 0) return '已过期';
|
||||
|
||||
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
|
||||
if (days > 0) {
|
||||
return `${days}天`;
|
||||
}
|
||||
|
||||
const hours = Math.floor(diff / (1000 * 60 * 60));
|
||||
if (hours > 0) {
|
||||
return `${hours}小时`;
|
||||
}
|
||||
|
||||
return '即将到期';
|
||||
} catch (error) {
|
||||
console.error('计算剩余时间错误:', error);
|
||||
return '时间错误';
|
||||
}
|
||||
},
|
||||
|
||||
// ========== 处理自动滚动 ==========
|
||||
handleAutoScroll() {
|
||||
const { jumpParams } = this.data;
|
||||
|
||||
if (!jumpParams || !jumpParams.needScroll) return;
|
||||
|
||||
setTimeout(() => {
|
||||
let scrollTop = 0;
|
||||
|
||||
if (jumpParams.scrollTo === 'bottom') {
|
||||
scrollTop = 2000;
|
||||
} else if (jumpParams.scrollTo === 'member') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scrollTop > 0) {
|
||||
wx.pageScrollTo({
|
||||
scrollTop: scrollTop,
|
||||
duration: 300
|
||||
});
|
||||
}
|
||||
}, 800);
|
||||
},
|
||||
|
||||
// ========== 押金充值相关 ==========
|
||||
showYajinModal() {
|
||||
this.setData({
|
||||
showYajinModal: true,
|
||||
yajinAmount: '100'
|
||||
});
|
||||
},
|
||||
|
||||
hideYajinModal() {
|
||||
this.setData({
|
||||
showYajinModal: false
|
||||
});
|
||||
},
|
||||
|
||||
onYajinInput(e) {
|
||||
let value = e.detail.value;
|
||||
value = value.replace(/[^\d]/g, '');
|
||||
|
||||
if (value) {
|
||||
const num = parseInt(value);
|
||||
if (num < 1) value = '1';
|
||||
if (num > 10000) value = '10000';
|
||||
}
|
||||
|
||||
this.setData({
|
||||
yajinAmount: value
|
||||
});
|
||||
},
|
||||
|
||||
setYajinAmount(e) {
|
||||
const amount = e.currentTarget.dataset.amount;
|
||||
this.setData({
|
||||
yajinAmount: amount
|
||||
});
|
||||
},
|
||||
|
||||
async handleYajinPay() {
|
||||
const amount = this.data.yajinAmount;
|
||||
|
||||
if (!amount || amount < 1 || amount > 10000) {
|
||||
wx.showToast({
|
||||
title: '请输入1-10000元的金额',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.showLoading('发起支付中...');
|
||||
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/shangpin/yajingoumai',
|
||||
method: 'POST',
|
||||
data: {
|
||||
jine: parseInt(amount)
|
||||
}
|
||||
});
|
||||
|
||||
if (res.data.code === 200) {
|
||||
const payParams = res.data.payParams;
|
||||
const dingdanid = res.data.dingdanid;
|
||||
|
||||
this.setData({
|
||||
currentDingdanid: dingdanid
|
||||
});
|
||||
|
||||
await this.wechatPay(payParams, 'yajin');
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: res.data.message || '支付发起失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('押金支付失败:', error);
|
||||
wx.showToast({
|
||||
title: '支付失败,请重试',
|
||||
icon: 'none'
|
||||
});
|
||||
} finally {
|
||||
this.hideLoading();
|
||||
}
|
||||
},
|
||||
|
||||
// ========== 积分充值相关 ==========
|
||||
// 🔥 修改点1:增加积分已满的硬拦截,不弹任何提示,直接返回
|
||||
onJifenClick(e) {
|
||||
// 优先判断积分是否已满(10分为满分)
|
||||
if (this.data.jifen >= 10) {
|
||||
// 积分已满,不弹窗,不提示,静默返回
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否有会员
|
||||
const clubmber = this.data.clubmber || [];
|
||||
if (clubmber.length === 0) {
|
||||
wx.showToast({
|
||||
title: '请先购买会员才能充值积分',
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 积分未满,正常打开支付方式选择
|
||||
this.setData({
|
||||
currentBuyType: 3,
|
||||
showPayMethodModal: true
|
||||
});
|
||||
},
|
||||
|
||||
// ========== 会员购买相关 ==========
|
||||
async handleHuiyuanBuy(e) {
|
||||
const huiyuanId = e.currentTarget.dataset.id;
|
||||
|
||||
if (!huiyuanId) {
|
||||
wx.showToast({
|
||||
title: '会员信息错误',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.showLoading('发起会员支付中...');
|
||||
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/shangpin/huiyuangm',
|
||||
method: 'POST',
|
||||
data: {
|
||||
huiyuanid: huiyuanId
|
||||
}
|
||||
});
|
||||
|
||||
if (res.data.code === 200) {
|
||||
const payParams = res.data.payParams;
|
||||
const dingdanid = res.data.dingdanid;
|
||||
|
||||
this.setData({
|
||||
currentDingdanid: dingdanid
|
||||
});
|
||||
|
||||
await this.wechatPay(payParams, 'huiyuan', huiyuanId);
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: res.data.message || '会员支付发起失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('会员支付失败:', error);
|
||||
wx.showToast({
|
||||
title: '支付失败,请重试',
|
||||
icon: 'none'
|
||||
});
|
||||
} finally {
|
||||
this.hideLoading();
|
||||
}
|
||||
},
|
||||
|
||||
// ========== 积分购买(微信支付)—— 已替换为正确的后端接口 ==========
|
||||
async handleJifenBuy(e) {
|
||||
const disabled = e.currentTarget.dataset.disabled;
|
||||
|
||||
// 检查积分是否已满
|
||||
if (disabled === 'true') {
|
||||
wx.showToast({ title: '积分已满,无需补充', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否有会员
|
||||
const clubmber = this.data.clubmber || [];
|
||||
if (clubmber.length === 0) {
|
||||
wx.showToast({ title: '请先购买会员才能充值积分', icon: 'none', duration: 3000 });
|
||||
return;
|
||||
}
|
||||
|
||||
this.showLoading('发起积分支付中...');
|
||||
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/shangpin/jifenbc', // 正确的积分下单接口
|
||||
method: 'POST',
|
||||
data: {}
|
||||
});
|
||||
|
||||
if (res.data.code === 200) {
|
||||
const payParams = res.data.payParams;
|
||||
const dingdanid = res.data.dingdanid;
|
||||
|
||||
this.setData({ currentDingdanid: dingdanid });
|
||||
await this.wechatPay(payParams, 'jifen');
|
||||
} else {
|
||||
wx.showToast({ title: res.data.message || '积分支付发起失败', icon: 'none' });
|
||||
}
|
||||
} catch (error) {
|
||||
wx.showToast({ title: '支付失败,请重试', icon: 'none' });
|
||||
} finally {
|
||||
this.hideLoading();
|
||||
}
|
||||
},
|
||||
|
||||
// ========== 微信支付封装 ==========
|
||||
async wechatPay(payParams, payType, extraData = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!payParams || !payParams.timeStamp || !payParams.paySign) {
|
||||
wx.showToast({
|
||||
title: '支付参数错误',
|
||||
icon: 'none'
|
||||
});
|
||||
reject(new Error('支付参数错误'));
|
||||
return;
|
||||
}
|
||||
|
||||
this.showLoading('调起支付中...');
|
||||
|
||||
wx.requestPayment({
|
||||
timeStamp: payParams.timeStamp,
|
||||
nonceStr: payParams.nonceStr,
|
||||
package: payParams.package,
|
||||
signType: payParams.signType || 'MD5',
|
||||
paySign: payParams.paySign,
|
||||
success: async () => {
|
||||
this.hideLoading();
|
||||
|
||||
wx.showToast({
|
||||
title: '支付成功!确认中...',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
|
||||
try {
|
||||
await this.startPolling(payType, extraData);
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
},
|
||||
fail: async (res) => {
|
||||
this.hideLoading();
|
||||
|
||||
let errorMsg = '支付失败';
|
||||
if (res.errMsg.includes('cancel')) {
|
||||
errorMsg = '您取消了支付';
|
||||
} else if (res.errMsg.includes('fail')) {
|
||||
errorMsg = '支付失败,请重试';
|
||||
}
|
||||
|
||||
wx.showToast({
|
||||
title: errorMsg,
|
||||
icon: 'none'
|
||||
});
|
||||
|
||||
try {
|
||||
await this.handlePayFailure(payType, extraData);
|
||||
} catch (error) {
|
||||
console.error('支付失败通知失败:', error);
|
||||
}
|
||||
|
||||
reject(new Error(res.errMsg));
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// ========== 开始轮询支付结果 ==========
|
||||
async startPolling(payType, extraData = null) {
|
||||
const maxRetries = 10;
|
||||
const interval = 2000;
|
||||
const dingdanid = this.data.currentDingdanid;
|
||||
|
||||
if (!dingdanid) {
|
||||
wx.showToast({
|
||||
title: '订单号缺失',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.showLoading('确认支付结果中...');
|
||||
|
||||
for (let i = 0; i < maxRetries; i++) {
|
||||
try {
|
||||
const pollResult = await this.checkPayStatus(payType, dingdanid, extraData);
|
||||
|
||||
if (pollResult.success) {
|
||||
this.hideLoading();
|
||||
await this.updateAfterPayment(payType, extraData, pollResult.data);
|
||||
|
||||
wx.showToast({
|
||||
title: this.getSuccessMessage(payType),
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await this.sleep(interval);
|
||||
} catch (error) {
|
||||
console.error(`轮询失败第${i + 1}次:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
this.hideLoading();
|
||||
wx.showToast({
|
||||
title: '支付确认超时,请联系客服',
|
||||
icon: 'none'
|
||||
});
|
||||
},
|
||||
|
||||
// ========== 检查支付状态 ==========
|
||||
async checkPayStatus(payType, dingdanid, extraData = null) {
|
||||
let url = '';
|
||||
let data = { dingdanid: dingdanid };
|
||||
|
||||
switch (payType) {
|
||||
case 'yajin':
|
||||
url = '/shangpin/yajinlunxun';
|
||||
break;
|
||||
case 'jifen':
|
||||
url = '/shangpin/jifenlunxun';
|
||||
break;
|
||||
case 'huiyuan':
|
||||
url = '/shangpin/huiyuanlx';
|
||||
data.huiyuanid = extraData;
|
||||
break;
|
||||
default:
|
||||
throw new Error('未知的支付类型');
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await request({
|
||||
url,
|
||||
method: 'POST',
|
||||
data
|
||||
});
|
||||
|
||||
return {
|
||||
success: res.data.code === 200,
|
||||
data: res.data
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('检查支付状态失败:', error);
|
||||
return {
|
||||
success: false,
|
||||
data: null
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// ========== 支付失败处理 ==========
|
||||
async handlePayFailure(payType, extraData = null) {
|
||||
let url = '';
|
||||
let data = { dingdanid: this.data.currentDingdanid };
|
||||
|
||||
switch (payType) {
|
||||
case 'yajin':
|
||||
url = '/shangpin/yajinshibai';
|
||||
break;
|
||||
case 'jifen':
|
||||
url = '/shangpin/jifenshibai';
|
||||
break;
|
||||
case 'huiyuan':
|
||||
url = '/shangpin/huiyuanshibai';
|
||||
data.huiyuanid = extraData;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await request({
|
||||
url,
|
||||
method: 'POST',
|
||||
data
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('支付失败通知失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// ========== 支付成功后更新数据 ==========
|
||||
async updateAfterPayment(payType, extraData = null, responseData = null) {
|
||||
const app = getApp();
|
||||
|
||||
switch (payType) {
|
||||
case 'yajin':
|
||||
if (responseData && responseData.yajin !== undefined) {
|
||||
app.globalData.yajin = responseData.yajin;
|
||||
}
|
||||
await this.refreshYajinData();
|
||||
break;
|
||||
|
||||
case 'jifen':
|
||||
if (responseData && responseData.jifen !== undefined) {
|
||||
app.globalData.jinfen = responseData.jifen;
|
||||
}
|
||||
await this.refreshJifenData();
|
||||
break;
|
||||
|
||||
case 'huiyuan':
|
||||
if (responseData && responseData.huiyuan) {
|
||||
app.globalData.jinfen = responseData.jifen;
|
||||
await this.updateClubmberData(responseData.huiyuan);
|
||||
}
|
||||
await this.refreshHuiyuanData(extraData);
|
||||
break;
|
||||
}
|
||||
|
||||
this.loadFromGlobalData();
|
||||
},
|
||||
|
||||
// ========== 刷新押金数据 ==========
|
||||
async refreshYajinData() {
|
||||
this.loadFromGlobalData();
|
||||
},
|
||||
|
||||
// ========== 刷新积分数据 ==========
|
||||
async refreshJifenData() {
|
||||
this.loadFromGlobalData();
|
||||
},
|
||||
|
||||
// ========== 刷新会员数据 ==========
|
||||
async refreshHuiyuanData(huiyuanId) {
|
||||
this.loadFromGlobalData();
|
||||
await this.fetchHuiyuanList();
|
||||
},
|
||||
|
||||
// ========== 更新会员数据到全局变量 ==========
|
||||
async updateClubmberData(huiyuanData) {
|
||||
const app = getApp();
|
||||
const globalClubmber = app.globalData.clumber || [];
|
||||
|
||||
const { huiyuanid, huiyuanming, daoqi } = huiyuanData;
|
||||
|
||||
const index = globalClubmber.findIndex(item => item.huiyuanid === huiyuanid);
|
||||
|
||||
if (index >= 0) {
|
||||
globalClubmber[index].daoqi = daoqi;
|
||||
} else {
|
||||
globalClubmber.push({
|
||||
huiyuanid: huiyuanid,
|
||||
huiyuanming: huiyuanming,
|
||||
daoqi: daoqi
|
||||
});
|
||||
}
|
||||
|
||||
app.globalData.clumber = globalClubmber;
|
||||
|
||||
this.setData({
|
||||
clubmber: [...globalClubmber]
|
||||
});
|
||||
},
|
||||
|
||||
// ========== 检查并刷新数据 ==========
|
||||
async checkAndRefreshData() {
|
||||
this.loadFromGlobalData();
|
||||
},
|
||||
|
||||
// ========== 工具方法 ==========
|
||||
getSuccessMessage(payType) {
|
||||
switch (payType) {
|
||||
case 'yajin': return '押金充值成功';
|
||||
case 'jifen': return '积分补充成功';
|
||||
case 'huiyuan': return '会员购买成功';
|
||||
default: return '支付成功';
|
||||
}
|
||||
},
|
||||
|
||||
showLoading(text = '加载中...') {
|
||||
this.setData({
|
||||
payLoading: true,
|
||||
loadingText: text
|
||||
});
|
||||
},
|
||||
|
||||
hideLoading() {
|
||||
this.setData({
|
||||
payLoading: false
|
||||
});
|
||||
},
|
||||
|
||||
sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
},
|
||||
|
||||
// ========== 新增功能(余额抵扣等) ==========
|
||||
onBuyHuiyuanClick(e) {
|
||||
const huiyuanId = e.currentTarget.dataset.id;
|
||||
this.setData({
|
||||
currentBuyType: 1,
|
||||
currentHuiyuanId: huiyuanId,
|
||||
showPayMethodModal: true
|
||||
});
|
||||
},
|
||||
|
||||
onYajinClick() {
|
||||
this.showYajinModal();
|
||||
},
|
||||
|
||||
onYajinConfirm() {
|
||||
const amount = this.data.yajinAmount;
|
||||
if (!amount || amount < 1 || amount > 10000) {
|
||||
wx.showToast({ title: '请输入1-10000元的金额', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
this.hideYajinModal();
|
||||
this.setData({
|
||||
currentBuyType: 2,
|
||||
currentYajinAmount: amount,
|
||||
showPayMethodModal: true
|
||||
});
|
||||
},
|
||||
|
||||
hidePayMethodModal() {
|
||||
this.setData({ showPayMethodModal: false });
|
||||
},
|
||||
|
||||
// 🔥 修改点2:选择支付方式时再次校验积分状态(防止异步过程中积分变化)
|
||||
onPayMethodSelect(e) {
|
||||
const method = e.currentTarget.dataset.method;
|
||||
const { currentBuyType, currentHuiyuanId, currentYajinAmount, jifen } = this.data;
|
||||
this.hidePayMethodModal();
|
||||
|
||||
// 如果是积分购买,且积分已满,直接拒绝
|
||||
if (currentBuyType === 3 && jifen >= 10) {
|
||||
wx.showToast({
|
||||
title: '积分已满,无需补充',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (method === 'wx') {
|
||||
if (currentBuyType === 1) {
|
||||
this.handleHuiyuanBuy({ currentTarget: { dataset: { id: currentHuiyuanId } } });
|
||||
} else if (currentBuyType === 2) {
|
||||
this.setData({ yajinAmount: currentYajinAmount }, () => {
|
||||
this.handleYajinPay();
|
||||
});
|
||||
} else if (currentBuyType === 3) {
|
||||
// ✅ 调用已修正的积分支付函数
|
||||
this.handleJifenBuy({ currentTarget: { dataset: { disabled: 'false' } } });
|
||||
}
|
||||
} else if (method === 'balance') {
|
||||
this.fetchBalanceOptions();
|
||||
}
|
||||
},
|
||||
|
||||
// 🔥 修改点3:请求余额抵扣选项前校验积分状态
|
||||
async fetchBalanceOptions() {
|
||||
const { currentBuyType, currentHuiyuanId, currentYajinAmount, jifen } = this.data;
|
||||
|
||||
// 如果是积分购买,且积分已满,拒绝发起余额抵扣
|
||||
if (currentBuyType === 3 && jifen >= 10) {
|
||||
wx.showToast({
|
||||
title: '积分已满,无需补充',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.showLoading('获取抵扣信息...');
|
||||
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/shangpin/czhqdy',
|
||||
method: 'POST',
|
||||
data: {
|
||||
leixing: currentBuyType,
|
||||
huiyuan_id: currentBuyType === 1 ? currentHuiyuanId : undefined,
|
||||
yajin_jine: currentBuyType === 2 ? parseInt(currentYajinAmount) : undefined,
|
||||
}
|
||||
});
|
||||
if (res.data.code === 200) {
|
||||
const options = res.data.data || [];
|
||||
this.setData({
|
||||
balanceOptions: options,
|
||||
showBalanceModal: true
|
||||
});
|
||||
} else {
|
||||
wx.showToast({ title: res.data.message || '获取失败', icon: 'none' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取抵扣身份失败:', error);
|
||||
wx.showToast({ title: '网络错误', icon: 'none' });
|
||||
} finally {
|
||||
this.hideLoading();
|
||||
}
|
||||
},
|
||||
|
||||
hideBalanceModal() {
|
||||
this.setData({ showBalanceModal: false });
|
||||
},
|
||||
|
||||
onBalanceSelect(e) {
|
||||
const identityId = e.currentTarget.dataset.id;
|
||||
const selected = this.data.balanceOptions.find(opt => opt.id === identityId);
|
||||
if (!selected) return;
|
||||
|
||||
this.setData({
|
||||
selectedBalanceId: identityId,
|
||||
selectedBalanceInfo: selected,
|
||||
showConfirmModal: true,
|
||||
showBalanceModal: false
|
||||
});
|
||||
},
|
||||
|
||||
hideConfirmModal() {
|
||||
this.setData({ showConfirmModal: false });
|
||||
},
|
||||
|
||||
async confirmBalancePay() {
|
||||
const { selectedBalanceId, currentBuyType, currentHuiyuanId, currentYajinAmount } = this.data;
|
||||
if (!selectedBalanceId) return;
|
||||
|
||||
this.hideConfirmModal();
|
||||
this.showLoading('抵扣支付中...');
|
||||
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/shangpin/dsqrgmdh',
|
||||
method: 'POST',
|
||||
data: {
|
||||
leixing: currentBuyType,
|
||||
shenfen_id: selectedBalanceId,
|
||||
huiyuan_id: currentBuyType === 1 ? currentHuiyuanId : undefined,
|
||||
yajin_jine: currentBuyType === 2 ? parseInt(currentYajinAmount) : undefined,
|
||||
}
|
||||
});
|
||||
|
||||
if (res.data.code === 200) {
|
||||
const responseData = res.data.data || {};
|
||||
await this.updateAfterPayment(
|
||||
currentBuyType === 1 ? 'huiyuan' : (currentBuyType === 2 ? 'yajin' : 'jifen'),
|
||||
currentBuyType === 1 ? currentHuiyuanId : null,
|
||||
responseData
|
||||
);
|
||||
wx.showToast({ title: '购买成功', icon: 'success' });
|
||||
} else {
|
||||
wx.showToast({ title: res.data.message || '购买失败', icon: 'none' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('抵扣支付失败:', error);
|
||||
wx.showToast({ title: '网络错误', icon: 'none' });
|
||||
} finally {
|
||||
this.hideLoading();
|
||||
}
|
||||
},
|
||||
|
||||
showMemberDetail(e) {
|
||||
const item = e.currentTarget.dataset.item;
|
||||
this.setData({
|
||||
currentMemberDetail: item,
|
||||
showMemberModal: true
|
||||
});
|
||||
},
|
||||
|
||||
hideMemberModal() {
|
||||
this.setData({ showMemberModal: false });
|
||||
},
|
||||
});
|
||||
12
pages/dashouchongzhi/dashouchongzhi.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
"global-notification": "/components/global-notification/global-notification"
|
||||
},
|
||||
"navigationBarTitleText": "充值中心",
|
||||
"navigationBarBackgroundColor": "#0a0e2a",
|
||||
"navigationBarTextStyle": "white",
|
||||
"backgroundColor": "#0a0e2a",
|
||||
"backgroundTextStyle": "light",
|
||||
"enablePullDownRefresh": false,
|
||||
"onReachBottomDistance": 50
|
||||
}
|
||||
514
pages/dashouchongzhi/dashouchongzhi.wxml
Normal file
@@ -0,0 +1,514 @@
|
||||
<!--pages/dashou-chongzhi/index.wxml-->
|
||||
<view class="page-container">
|
||||
<!-- ❌ 删除自定义导航栏区块 -->
|
||||
|
||||
<!-- ======================= -->
|
||||
<!-- VIP会员区域 - 放在上面 -->
|
||||
<!-- ======================= -->
|
||||
<view class="vip-section">
|
||||
<view class="section-header">
|
||||
<view class="header-decoration">
|
||||
<view class="dec-line"></view>
|
||||
<view class="dec-diamond"></view>
|
||||
<text class="dec-text">VIP会员充值</text>
|
||||
<view class="dec-diamond"></view>
|
||||
<view class="dec-line"></view>
|
||||
</view>
|
||||
<text class="section-subtitle">选择你的会员</text>
|
||||
</view>
|
||||
|
||||
<!-- 会员卡片滑动区域 -->
|
||||
<swiper
|
||||
class="vip-swiper"
|
||||
current="{{currentHuiyuanIndex}}"
|
||||
circular="{{true}}"
|
||||
previous-margin="120rpx"
|
||||
next-margin="120rpx"
|
||||
duration="500"
|
||||
easing-function="easeOutCubic"
|
||||
bindchange="onSwiperChange">
|
||||
<swiper-item wx:for="{{huiyuanList}}" wx:key="id">
|
||||
<view class="vip-card {{index === currentHuiyuanIndex ? 'active' : ''}}"
|
||||
data-index="{{index}}" bindtap="selectHuiyuan">
|
||||
<!-- 机甲风格边框 -->
|
||||
<view class="card-frame">
|
||||
<view class="frame-corner tl"></view>
|
||||
<view class="frame-corner tr"></view>
|
||||
<view class="frame-corner bl"></view>
|
||||
<view class="frame-corner br"></view>
|
||||
<view class="frame-line top"></view>
|
||||
<view class="frame-line right"></view>
|
||||
<view class="frame-line bottom"></view>
|
||||
<view class="frame-line left"></view>
|
||||
</view>
|
||||
|
||||
<!-- 卡片内容 -->
|
||||
<view class="card-content">
|
||||
<view class="vip-badge" wx:if="{{item.isBought}}">
|
||||
<text class="badge-text">已拥有</text>
|
||||
</view>
|
||||
|
||||
<view class="vip-title">
|
||||
<text class="title-text">{{item.mingzi}}</text>
|
||||
<view class="title-underline"></view>
|
||||
</view>
|
||||
|
||||
<view class="vip-price">
|
||||
<text class="price-symbol">¥</text>
|
||||
<text class="price-number">{{item.jiage}}</text>
|
||||
<text class="price-unit">/30天</text>
|
||||
</view>
|
||||
|
||||
<view class="vip-features">
|
||||
<view class="feature-item" wx:for="{{item.features || ['特权功能', '专属标识', '优先接单']}}"
|
||||
wx:key="index">
|
||||
<image class="feature-icon" src="/static/images/check-tech.png"></image>
|
||||
<text class="feature-text">{{item}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 选中状态指示器 -->
|
||||
<view class="card-indicator" wx:if="{{index === currentHuiyuanIndex}}">
|
||||
<view class="indicator-triangle"></view>
|
||||
<view class="indicator-glow"></view>
|
||||
</view>
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
|
||||
<!-- 会员详细介绍 -->
|
||||
<view class="vip-detail">
|
||||
<view class="detail-header">
|
||||
<view class="detail-title">
|
||||
<image class="detail-icon" src="/static/images/info-tech.png"></image>
|
||||
<text class="title-text">{{currentHuiyuan.mingzi}} - 会员详情</text>
|
||||
</view>
|
||||
<view class="detail-tech"></view>
|
||||
</view>
|
||||
|
||||
<view class="detail-content">
|
||||
<scroll-view class="detail-scroll" scroll-y>
|
||||
<text class="detail-text">{{currentHuiyuan.jieshao || '加载中...'}}</text>
|
||||
|
||||
<view class="detail-rules">
|
||||
<view class="rule-item" wx:for="{{detailRules}}" wx:key="index">
|
||||
<view class="rule-number">{{index + 1}}</view>
|
||||
<text class="rule-text">{{item}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 购买区域 - 点击按钮先弹出支付方式选择 -->
|
||||
<view class="buy-section">
|
||||
<view class="buy-info">
|
||||
<view class="price-display">
|
||||
<!-- 🔥 将“机甲能源”改为“价格” -->
|
||||
<text class="price-label">价格:</text>
|
||||
<text class="price-value">¥{{currentHuiyuan.jiage}}</text>
|
||||
</view>
|
||||
<text class="buy-desc" wx:if="{{currentHuiyuan.isBought && currentHuiyuan.buyInfo}}">
|
||||
已激活,<text class="expire-date">{{currentHuiyuan.buyInfo.daoqi}}</text>后到期
|
||||
</text>
|
||||
<text class="buy-desc" wx:else>激活会员特权</text>
|
||||
</view>
|
||||
|
||||
<view class="buy-action">
|
||||
<view class="tech-buy-btn {{currentHuiyuan.isBought ? 'renew' : ''}}"
|
||||
bindtap="onBuyHuiyuanClick" data-id="{{currentHuiyuan.id}}">
|
||||
<view class="buy-btn-bg"></view>
|
||||
<view class="buy-btn-glow"></view>
|
||||
<text class="buy-btn-text">
|
||||
{{currentHuiyuan.isBought ? '立即续费' : '立即激活'}}
|
||||
</text>
|
||||
<view class="buy-btn-energy">
|
||||
<view class="energy-dot"></view>
|
||||
<view class="energy-dot"></view>
|
||||
<view class="energy-dot"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- ======================= -->
|
||||
<!-- 三卡片区域 - 放在下面 -->
|
||||
<!-- ======================= -->
|
||||
<view class="tech-grid">
|
||||
<!-- 会员区域 - 高度限制最多3个,超出可滚动 -->
|
||||
<view class="tech-card huiyuan-card">
|
||||
<view class="card-header">
|
||||
<view class="card-title">
|
||||
<text class="title-text">我的会员</text>
|
||||
<view class="title-text">👑</view>
|
||||
</view>
|
||||
<view class="card-badge">{{clubmber.length}}</view>
|
||||
</view>
|
||||
|
||||
<scroll-view
|
||||
class="huiyuan-scroll"
|
||||
scroll-y
|
||||
enable-flex
|
||||
style="height: {{huiyuanListHeight}}rpx">
|
||||
<view wx:for="{{clubmber}}" wx:key="huiyuanid" class="huiyuan-item" data-item="{{item}}" bindtap="showMemberDetail">
|
||||
<view class="huiyuan-tag tech-tag">
|
||||
<view class="tag-triangle"></view>
|
||||
<text class="tag-name">{{item.huiyuanming}}</text>
|
||||
<text class="tag-time">到期: {{formatDaoqiTime(item.daoqi)}}</text>
|
||||
<view class="tag-glow"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view wx:if="{{clubmber.length === 0}}" class="empty-huiyuan">
|
||||
<image class="empty-icon" src="/static/images/empty-member.png"></image>
|
||||
<text class="empty-text">暂无会员</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="card-footer">
|
||||
<text class="footer-text">共{{clubmber.length}}个会员</text>
|
||||
<view class="footer-line"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 押金区域 - 点击充值按钮弹出押金输入弹窗 -->
|
||||
<view class="tech-card yajin-card">
|
||||
<view class="card-header">
|
||||
<view class="card-title">
|
||||
<text class="title-text">我的押金</text>
|
||||
<view class="title-text">💰</view>
|
||||
</view>
|
||||
<view class="card-corner"></view>
|
||||
</view>
|
||||
|
||||
<view class="yajin-display">
|
||||
<view class="amount-container">
|
||||
<text class="amount-symbol">¥</text>
|
||||
<text class="amount-number">{{yajin}}</text>
|
||||
</view>
|
||||
<text class="amount-unit">可用余额</text>
|
||||
</view>
|
||||
|
||||
<view class="tech-line"></view>
|
||||
|
||||
<view class="action-container">
|
||||
<view class="tech-btn yajin-btn" bindtap="onYajinClick">
|
||||
<view class="btn-bg"></view>
|
||||
<text class="btn-text">立即充值</text>
|
||||
<view class="btn-arrow" style="display:none;">➤</view>
|
||||
<view class="btn-glow"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="card-corner corner-bottom"></view>
|
||||
</view>
|
||||
|
||||
<!-- 积分区域 - 点击补充按钮弹出支付方式选择 -->
|
||||
<view class="tech-card jifen-card">
|
||||
<view class="card-header">
|
||||
<view class="card-title">
|
||||
<text class="title-text">我的积分</text>
|
||||
<view class="title-text">⭐</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="jifen-display">
|
||||
<view class="circle-progress">
|
||||
<view class="circle-bg"></view>
|
||||
<view class="circle-mask" style="transform: rotate({{jifenProgress}}deg);"></view>
|
||||
<view class="circle-fill" style="transform: rotate({{jifenProgress}}deg);"></view>
|
||||
<view class="circle-center">
|
||||
<text class="jifen-value">{{jifen}}</text>
|
||||
<text class="jifen-label">积分</text>
|
||||
</view>
|
||||
<view class="circle-dots">
|
||||
<view class="circle-dot" wx:for="{{10}}" wx:key="index"
|
||||
style="transform: rotate({{index * 36}}deg);"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="jifen-status">
|
||||
<text class="status-text" wx:if="{{jifen < 10}}">积分不足,影响接单,提现权限</text>
|
||||
<text class="status-text full" wx:else>积分充足,无需补充</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="action-container">
|
||||
<view class="tech-btn jifen-btn" bindtap="onJifenClick" data-disabled="{{jifen >= 10}}">
|
||||
<view class="btn-bg"></view>
|
||||
<text class="btn-text">{{jifen >= 10 ? '积分已满' : '立即补充'}}</text>
|
||||
<view class="btn-energy" wx:if="{{jifen < 10}}"></view>
|
||||
<view class="btn-glow" wx:if="{{jifen < 10}}"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- ======================= -->
|
||||
<!-- 押金充值弹窗(原有,确认事件改为 onYajinConfirm) -->
|
||||
<!-- ======================= -->
|
||||
<view class="tech-modal" wx:if="{{showYajinModal}}">
|
||||
<view class="modal-backdrop" bindtap="hideYajinModal"></view>
|
||||
<view class="modal-container">
|
||||
<view class="modal-corner tl"></view>
|
||||
<view class="modal-corner tr"></view>
|
||||
<view class="modal-corner bl"></view>
|
||||
<view class="modal-corner br"></view>
|
||||
|
||||
<view class="modal-header">
|
||||
<text class="modal-title">押金充值</text>
|
||||
<text class="modal-subtitle">充值保证金</text>
|
||||
<view class="modal-close" bindtap="hideYajinModal">
|
||||
<text class="close-text">✕</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="modal-body">
|
||||
<view class="input-section">
|
||||
<view class="input-label">
|
||||
<image class="label-icon" src="/static/images/money-tech.png"></image>
|
||||
<text class="label-text">充值金额 (1-10000元)</text>
|
||||
</view>
|
||||
|
||||
<view class="input-container">
|
||||
<view class="input-prefix">¥</view>
|
||||
<input
|
||||
class="yajin-input"
|
||||
type="digit"
|
||||
placeholder="输入充值金额"
|
||||
placeholder-class="input-placeholder"
|
||||
value="{{yajinAmount}}"
|
||||
bindinput="onYajinInput"
|
||||
maxlength="5"
|
||||
focus="{{showYajinModal}}"/>
|
||||
<view class="input-underline"></view>
|
||||
</view>
|
||||
|
||||
<text class="input-tip">支持1-10000元整数充值</text>
|
||||
</view>
|
||||
|
||||
<view class="quick-section">
|
||||
<text class="quick-title">快速选择</text>
|
||||
<view class="quick-buttons">
|
||||
<view
|
||||
class="quick-btn {{yajinAmount == '100' ? 'active' : ''}}"
|
||||
bindtap="setYajinAmount"
|
||||
data-amount="100">
|
||||
<text class="quick-text">100元</text>
|
||||
</view>
|
||||
<view
|
||||
class="quick-btn {{yajinAmount == '500' ? 'active' : ''}}"
|
||||
bindtap="setYajinAmount"
|
||||
data-amount="500">
|
||||
<text class="quick-text">500元</text>
|
||||
</view>
|
||||
<view
|
||||
class="quick-btn {{yajinAmount == '1000' ? 'active' : ''}}"
|
||||
bindtap="setYajinAmount"
|
||||
data-amount="1000">
|
||||
<text class="quick-text">1000元</text>
|
||||
</view>
|
||||
<view
|
||||
class="quick-btn {{yajinAmount == '5000' ? 'active' : ''}}"
|
||||
bindtap="setYajinAmount"
|
||||
data-amount="5000">
|
||||
<text class="quick-text">5000元</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="modal-footer">
|
||||
<view class="modal-btn cancel-btn" bindtap="hideYajinModal">
|
||||
<text class="btn-text">取消</text>
|
||||
</view>
|
||||
<view class="modal-divider"></view>
|
||||
<view class="modal-btn confirm-btn" bindtap="onYajinConfirm">
|
||||
<text class="btn-text">确认支付</text>
|
||||
<view class="btn-sparkle"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- ====== 支付方式选择弹窗 ====== -->
|
||||
<view class="tech-modal" wx:if="{{showPayMethodModal}}">
|
||||
<view class="modal-backdrop" bindtap="hidePayMethodModal"></view>
|
||||
<view class="modal-container small">
|
||||
<view class="modal-corner tl"></view>
|
||||
<view class="modal-corner tr"></view>
|
||||
<view class="modal-corner bl"></view>
|
||||
<view class="modal-corner br"></view>
|
||||
|
||||
<view class="modal-header">
|
||||
<text class="modal-title">选择支付方式</text>
|
||||
<view class="modal-close" bindtap="hidePayMethodModal">
|
||||
<text class="close-text">✕</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="modal-body">
|
||||
<view class="pay-method-list">
|
||||
<view class="pay-method-item" bindtap="onPayMethodSelect" data-method="wx">
|
||||
<image class="method-icon" src="/static/images/wechat-pay.png"></image>
|
||||
<text class="method-name">微信支付</text>
|
||||
<text class="method-desc">安全快捷</text>
|
||||
</view>
|
||||
<view class="pay-method-item" bindtap="onPayMethodSelect" data-method="balance">
|
||||
<image class="method-icon" src="/static/images/balance-pay.png"></image>
|
||||
<text class="method-name">余额抵扣</text>
|
||||
<text class="method-desc">使用佣金或分红</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- ====== 余额抵扣身份选择弹窗 ====== -->
|
||||
<view class="tech-modal" wx:if="{{showBalanceModal}}">
|
||||
<view class="modal-backdrop" bindtap="hideBalanceModal"></view>
|
||||
<view class="modal-container">
|
||||
<view class="modal-corner tl"></view>
|
||||
<view class="modal-corner tr"></view>
|
||||
<view class="modal-corner bl"></view>
|
||||
<view class="modal-corner br"></view>
|
||||
|
||||
<view class="modal-header">
|
||||
<text class="modal-title">选择抵扣身份</text>
|
||||
<view class="modal-close" bindtap="hideBalanceModal">
|
||||
<text class="close-text">✕</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="modal-body">
|
||||
<view class="balance-list">
|
||||
<view wx:for="{{balanceOptions}}" wx:key="id" class="balance-item"
|
||||
data-id="{{item.id}}" bindtap="onBalanceSelect">
|
||||
<view class="balance-info">
|
||||
<text class="balance-name">{{item.jieshao}}</text>
|
||||
<text class="balance-amount">需¥{{item.xuyao}}</text>
|
||||
</view>
|
||||
<view class="balance-arrow">➤</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- ====== 余额抵扣确认弹窗 ====== -->
|
||||
<view class="tech-modal" wx:if="{{showConfirmModal}}">
|
||||
<view class="modal-backdrop" bindtap="hideConfirmModal"></view>
|
||||
<view class="modal-container">
|
||||
<view class="modal-corner tl"></view>
|
||||
<view class="modal-corner tr"></view>
|
||||
<view class="modal-corner bl"></view>
|
||||
<view class="modal-corner br"></view>
|
||||
|
||||
<view class="modal-header">
|
||||
<text class="modal-title">确认抵扣</text>
|
||||
<view class="modal-close" bindtap="hideConfirmModal">
|
||||
<text class="close-text">✕</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="modal-body">
|
||||
<view class="confirm-detail">
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">抵扣身份:</text>
|
||||
<text class="detail-value">{{selectedBalanceInfo && selectedBalanceInfo.jieshao}}</text>
|
||||
</view>
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">所需金额:</text>
|
||||
<text class="detail-value">¥{{selectedBalanceInfo && selectedBalanceInfo.xuyao}}</text>
|
||||
</view>
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">实际扣除:</text>
|
||||
<text class="detail-value">¥{{selectedBalanceInfo && selectedBalanceInfo.xuyao}}</text>
|
||||
</view>
|
||||
<view class="detail-tip">确认后将从您的余额中扣除</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="modal-footer">
|
||||
<view class="modal-btn cancel-btn" bindtap="hideConfirmModal">
|
||||
<text class="btn-text">取消</text>
|
||||
</view>
|
||||
<view class="modal-divider"></view>
|
||||
<view class="modal-btn confirm-btn" bindtap="confirmBalancePay">
|
||||
<text class="btn-text">确认支付</text>
|
||||
<view class="btn-sparkle"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- ====== 会员详情弹窗 ====== -->
|
||||
<view class="tech-modal" wx:if="{{showMemberModal}}">
|
||||
<view class="modal-backdrop" bindtap="hideMemberModal"></view>
|
||||
<view class="modal-container">
|
||||
<view class="modal-corner tl"></view>
|
||||
<view class="modal-corner tr"></view>
|
||||
<view class="modal-corner bl"></view>
|
||||
<view class="modal-corner br"></view>
|
||||
|
||||
<view class="modal-header">
|
||||
<text class="modal-title">会员详情</text>
|
||||
<view class="modal-close" bindtap="hideMemberModal">
|
||||
<text class="close-text">✕</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="modal-body">
|
||||
<view class="member-detail">
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">会员名称:</text>
|
||||
<text class="detail-value">{{currentMemberDetail && currentMemberDetail.huiyuanming}}</text>
|
||||
</view>
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">到期时间:</text>
|
||||
<text class="detail-value">{{currentMemberDetail && currentMemberDetail.daoqi}}</text>
|
||||
</view>
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">剩余天数:</text>
|
||||
<text class="detail-value">{{formatRemainTime(currentMemberDetail && currentMemberDetail.daoqi)}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- ❌ 删除“进入提示弹窗”整个区块 -->
|
||||
|
||||
<!-- ======================= -->
|
||||
<!-- 支付加载动画(原有) -->
|
||||
<!-- ======================= -->
|
||||
<view class="tech-loading" wx:if="{{payLoading}}">
|
||||
<view class="loading-backdrop"></view>
|
||||
<view class="loading-content">
|
||||
<view class="loading-spinner">
|
||||
<view class="spinner-ring ring-1"></view>
|
||||
<view class="spinner-ring ring-2"></view>
|
||||
<view class="spinner-ring ring-3"></view>
|
||||
<view class="spinner-core"></view>
|
||||
<view class="spinner-energy"></view>
|
||||
</view>
|
||||
<text class="loading-text">{{loadingText}}</text>
|
||||
<text class="loading-subtext">正在连接支付系统...</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- ======================= -->
|
||||
<!-- 底部机甲装饰 -->
|
||||
<!-- ======================= -->
|
||||
<view class="tech-footer">
|
||||
<view class="footer-line"></view>
|
||||
<view class="footer-text">星阙网络 © 7891 科技</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 通知组件 -->
|
||||
<global-notification id="global-notification" />
|
||||
|
||||
<!-- 🆕 统一弹窗组件 -->
|
||||
<popup-notice id="popupNotice" />
|
||||
1705
pages/dashouchongzhi/dashouchongzhi.wxss
Normal file
307
pages/dashoudingdan/dashoudingdan.js
Normal file
@@ -0,0 +1,307 @@
|
||||
const app = getApp();
|
||||
import request from '../../utils/request.js';
|
||||
import { reconnectForRole } from '../../utils/role-tab-bar.js';
|
||||
|
||||
Page({
|
||||
data: {
|
||||
// 商品类型
|
||||
shangpinleixing: [],
|
||||
xuanzhongLeixingId: null,
|
||||
|
||||
// 订单类型: normal 普通, peihu 陪护
|
||||
orderType: 'normal',
|
||||
|
||||
// 搜索关键字
|
||||
searchKeyword: '',
|
||||
|
||||
// 状态列表(增加全部)
|
||||
statusList: [
|
||||
{ name: '全部', key: 'all', zhuangtaiList: [], color: '#333333' },
|
||||
{ name: '进行中', key: 'jinxingzhong', zhuangtaiList: [2], color: '#2196F3' },
|
||||
{ name: '退款中', key: 'tuikuanzhong', zhuangtaiList: [4], color: '#FF9800' },
|
||||
{ name: '已退款', key: 'yituikuan', zhuangtaiList: [5], color: '#9E9E9E' },
|
||||
{ name: '退款失败', key: 'tuikuanshibai', zhuangtaiList: [6], color: '#F44336' },
|
||||
{ name: '已完成', key: 'yiwancheng', zhuangtaiList: [3], color: '#4CAF50' },
|
||||
{ name: '结算中', key: 'jiesuanzhong', zhuangtaiList: [8], color: '#FF5722' }
|
||||
],
|
||||
currentStatusKey: 'all',
|
||||
|
||||
// 每个状态独立的数据集
|
||||
dsDingdanShuju: {
|
||||
all: { list: [], page: 1, hasMore: true, isLoading: false },
|
||||
jinxingzhong: { list: [], page: 1, hasMore: true, isLoading: false },
|
||||
tuikuanzhong: { list: [], page: 1, hasMore: true, isLoading: false },
|
||||
yituikuan: { list: [], page: 1, hasMore: true, isLoading: false },
|
||||
tuikuanshibai: { list: [], page: 1, hasMore: true, isLoading: false },
|
||||
yiwancheng: { list: [], page: 1, hasMore: true, isLoading: false },
|
||||
jiesuanzhong: { list: [], page: 1, hasMore: true, isLoading: false }
|
||||
},
|
||||
|
||||
currentList: [],
|
||||
hasMore: true,
|
||||
isLoading: false,
|
||||
isLoadingMore: false,
|
||||
scrollViewRefreshing: false,
|
||||
defaultImg: '/images/default-order.png',
|
||||
ossImageUrl: app.globalData.ossImageUrl || '',
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
wx.setNavigationBarTitle({ title: '我的接单' });
|
||||
this.loadShangpinLeixing();
|
||||
this.registerNotificationComponent();
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.registerNotificationComponent();
|
||||
if (wx.getStorageSync('uid')) {
|
||||
reconnectForRole('dashou');
|
||||
if (app.startImWhenReady) app.startImWhenReady();
|
||||
}
|
||||
if (this.data.currentStatusKey && this.data.xuanzhongLeixingId) {
|
||||
this.loadCurrentStatusOrders(true);
|
||||
}
|
||||
},
|
||||
|
||||
registerNotificationComponent() {
|
||||
const notificationComp = this.selectComponent('#global-notification');
|
||||
if (notificationComp && notificationComp.showNotification) {
|
||||
app.globalData.globalNotification = {
|
||||
show: (data) => notificationComp.showNotification(data),
|
||||
hide: () => notificationComp.hideNotification()
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// 加载商品类型(图片拼接)
|
||||
async loadShangpinLeixing() {
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/dingdan/dsqdhqddlx',
|
||||
method: 'POST',
|
||||
header: { 'content-type': 'application/json' }
|
||||
});
|
||||
if (res.data && (res.data.code === 200 || res.data.code === 0)) {
|
||||
let list = res.data.data.list || res.data.data || [];
|
||||
if (!Array.isArray(list)) list = [];
|
||||
const oss = this.data.ossImageUrl;
|
||||
const processed = list.map(item => ({
|
||||
...item,
|
||||
full_tupian_url: item.tupian_url && !item.tupian_url.startsWith('http')
|
||||
? oss + item.tupian_url
|
||||
: (item.tupian_url || '/images/default-type.png')
|
||||
}));
|
||||
this.setData({
|
||||
shangpinleixing: processed,
|
||||
xuanzhongLeixingId: processed[0]?.id || null
|
||||
});
|
||||
this.loadCurrentStatusOrders(true);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载商品类型失败', e);
|
||||
}
|
||||
},
|
||||
|
||||
selectLeixing(e) {
|
||||
const id = e.currentTarget.dataset.id;
|
||||
if (id === this.data.xuanzhongLeixingId) return;
|
||||
this.setData({ xuanzhongLeixingId: id });
|
||||
this.resetCurrentStatusData();
|
||||
this.loadCurrentStatusOrders(true);
|
||||
},
|
||||
|
||||
switchOrderType(e) {
|
||||
const type = e.currentTarget.dataset.type;
|
||||
if (type === this.data.orderType) return;
|
||||
this.setData({ orderType: type });
|
||||
this.resetCurrentStatusData();
|
||||
this.loadCurrentStatusOrders(true);
|
||||
},
|
||||
|
||||
onSearchInput(e) {
|
||||
this.setData({ searchKeyword: e.detail.value });
|
||||
if (this._searchTimer) clearTimeout(this._searchTimer);
|
||||
this._searchTimer = setTimeout(() => {
|
||||
this.onSearchConfirm();
|
||||
}, 500);
|
||||
},
|
||||
|
||||
onSearchConfirm() {
|
||||
this.resetCurrentStatusData();
|
||||
this.loadCurrentStatusOrders(true);
|
||||
},
|
||||
|
||||
clearSearch() {
|
||||
this.setData({ searchKeyword: '' });
|
||||
this.resetCurrentStatusData();
|
||||
this.loadCurrentStatusOrders(true);
|
||||
},
|
||||
|
||||
switchStatus(e) {
|
||||
const key = e.currentTarget.dataset.key;
|
||||
if (key === this.data.currentStatusKey) return;
|
||||
this.setData({ currentStatusKey: key });
|
||||
const tabData = this.data.dsDingdanShuju[key];
|
||||
if (tabData.list.length === 0 && tabData.hasMore && !tabData.isLoading) {
|
||||
this.loadCurrentStatusOrders(true);
|
||||
} else {
|
||||
this.refreshCurrentListView();
|
||||
}
|
||||
},
|
||||
|
||||
// 加载订单(核心)
|
||||
async loadCurrentStatusOrders(isRefresh = false) {
|
||||
const key = this.data.currentStatusKey;
|
||||
const tabData = this.data.dsDingdanShuju[key];
|
||||
if (tabData.isLoading) return;
|
||||
const page = isRefresh ? 1 : tabData.page;
|
||||
|
||||
this.setData({
|
||||
[`dsDingdanShuju.${key}.isLoading`]: true,
|
||||
isLoading: true,
|
||||
isLoadingMore: !isRefresh
|
||||
});
|
||||
|
||||
try {
|
||||
const apiUrl = this.data.orderType === 'peihu'
|
||||
? '/dingdan/phddlbhq'
|
||||
: '/dingdan/dshqdingdan';
|
||||
|
||||
const statusItem = this.data.statusList.find(s => s.key === key);
|
||||
const zhuangtaiList = statusItem.zhuangtaiList.length > 0 ? statusItem.zhuangtaiList : undefined;
|
||||
const params = {
|
||||
zhuangtai_list: zhuangtaiList,
|
||||
page: page,
|
||||
page_size: 5,
|
||||
leixing_id: this.data.xuanzhongLeixingId,
|
||||
keyword: this.data.searchKeyword || undefined
|
||||
};
|
||||
|
||||
const res = await request({
|
||||
url: apiUrl,
|
||||
method: 'POST',
|
||||
data: params,
|
||||
header: { 'content-type': 'application/json' }
|
||||
});
|
||||
|
||||
const code = res.data.code;
|
||||
if (code === 200 || code === 0) {
|
||||
const newList = res.data.data.list || [];
|
||||
const hasMore = res.data.data.has_more || false;
|
||||
|
||||
// ✅ 图片URL拼接(核心)
|
||||
const processed = newList.map(item => ({
|
||||
...item,
|
||||
zhuangtaiZh: this.getZhuangtaiZh(item.zhuangtai),
|
||||
zhuangtaiColor: statusItem.color,
|
||||
tupian: item.tupian
|
||||
? (item.tupian.startsWith('http') ? item.tupian : this.data.ossImageUrl + item.tupian)
|
||||
: this.data.defaultImg
|
||||
}));
|
||||
|
||||
const currentList = this.data.dsDingdanShuju[key].list;
|
||||
const updatedList = isRefresh ? processed : [...currentList, ...processed];
|
||||
|
||||
this.setData({
|
||||
[`dsDingdanShuju.${key}.list`]: updatedList,
|
||||
[`dsDingdanShuju.${key}.page`]: page,
|
||||
[`dsDingdanShuju.${key}.hasMore`]: hasMore,
|
||||
[`dsDingdanShuju.${key}.isLoading`]: false,
|
||||
isLoading: false,
|
||||
isLoadingMore: false,
|
||||
scrollViewRefreshing: false
|
||||
});
|
||||
this.refreshCurrentListView();
|
||||
} else {
|
||||
throw new Error(res.data.msg || '加载失败');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('加载订单失败', err);
|
||||
wx.showToast({ title: err.message || '加载失败', icon: 'none' });
|
||||
this.setData({
|
||||
[`dsDingdanShuju.${key}.isLoading`]: false,
|
||||
isLoading: false,
|
||||
isLoadingMore: false,
|
||||
scrollViewRefreshing: false
|
||||
});
|
||||
this.refreshCurrentListView();
|
||||
}
|
||||
},
|
||||
|
||||
refreshCurrentListView() {
|
||||
const key = this.data.currentStatusKey;
|
||||
const tabData = this.data.dsDingdanShuju[key];
|
||||
this.setData({
|
||||
currentList: tabData.list,
|
||||
hasMore: tabData.hasMore,
|
||||
isLoading: tabData.isLoading
|
||||
});
|
||||
},
|
||||
|
||||
resetCurrentStatusData() {
|
||||
const key = this.data.currentStatusKey;
|
||||
this.setData({
|
||||
[`dsDingdanShuju.${key}.list`]: [],
|
||||
[`dsDingdanShuju.${key}.page`]: 1,
|
||||
[`dsDingdanShuju.${key}.hasMore`]: true,
|
||||
[`dsDingdanShuju.${key}.isLoading`]: false
|
||||
});
|
||||
this.refreshCurrentListView();
|
||||
},
|
||||
|
||||
// 下拉刷新(由 scroll-view 触发)
|
||||
onPullDownRefresh() {
|
||||
if (this.data.isLoading) {
|
||||
this.setData({ scrollViewRefreshing: false });
|
||||
return;
|
||||
}
|
||||
this.setData({ scrollViewRefreshing: true });
|
||||
this.resetCurrentStatusData();
|
||||
this.loadCurrentStatusOrders(true);
|
||||
},
|
||||
|
||||
// 上拉加载更多(由 scroll-view 触发)
|
||||
onReachBottom() {
|
||||
const key = this.data.currentStatusKey;
|
||||
const tabData = this.data.dsDingdanShuju[key];
|
||||
if (tabData.isLoading || !tabData.hasMore) return;
|
||||
this.setData({ [`dsDingdanShuju.${key}.page`]: tabData.page + 1 });
|
||||
this.loadCurrentStatusOrders(false);
|
||||
},
|
||||
|
||||
// ✅ 手动点击“加载更多”按钮
|
||||
onLoadMoreTap() {
|
||||
const key = this.data.currentStatusKey;
|
||||
const tabData = this.data.dsDingdanShuju[key];
|
||||
if (tabData.isLoading || !tabData.hasMore) return;
|
||||
this.setData({ [`dsDingdanShuju.${key}.page`]: tabData.page + 1 });
|
||||
this.loadCurrentStatusOrders(false);
|
||||
},
|
||||
|
||||
getZhuangtaiZh(zhuangtai) {
|
||||
const map = {
|
||||
1: '已下单', 2: '进行中', 3: '已完成',
|
||||
4: '退款中', 5: '已退款', 6: '退款失败',
|
||||
7: '指定中', 8: '结算中'
|
||||
};
|
||||
return map[zhuangtai] || '未知状态';
|
||||
},
|
||||
|
||||
goToDsDingdanXiangqing(e) {
|
||||
const item = e.currentTarget.dataset.item;
|
||||
if (!item) return;
|
||||
const dataStr = encodeURIComponent(JSON.stringify(item));
|
||||
|
||||
if (this.data.orderType === 'peihu') {
|
||||
wx.navigateTo({
|
||||
url: `/pages/peihuDingdan/peihuDingdan?dingdanData=${dataStr}`
|
||||
});
|
||||
} else {
|
||||
wx.navigateTo({
|
||||
url: `/pages/dsddxq/dsddxq?dingdanData=${dataStr}`
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
onImageError() {}
|
||||
});
|
||||
8
pages/dashoudingdan/dashoudingdan.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
"global-notification": "/components/global-notification/global-notification"
|
||||
},
|
||||
"navigationBarTitleText": "我的接单",
|
||||
"enablePullDownRefresh": false,
|
||||
"onReachBottomDistance": 50
|
||||
}
|
||||
112
pages/dashoudingdan/dashoudingdan.wxml
Normal file
@@ -0,0 +1,112 @@
|
||||
<view class="ds-page">
|
||||
<!-- 顶部商品类型滑动 -->
|
||||
<view class="leixing-area">
|
||||
<scroll-view class="leixing-scroll" scroll-x enhanced show-scrollbar="{{false}}">
|
||||
<view class="leixing-container">
|
||||
<block wx:for="{{shangpinleixing}}" wx:key="id">
|
||||
<view
|
||||
class="leixing-item {{xuanzhongLeixingId == item.id ? 'leixing-active' : ''}}"
|
||||
data-id="{{item.id}}"
|
||||
bindtap="selectLeixing"
|
||||
>
|
||||
<image class="leixing-img" src="{{item.full_tupian_url}}" mode="aspectFill"/>
|
||||
<text class="leixing-name">{{item.jieshao || '类型'}}</text>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 筛选行 -->
|
||||
<view class="filter-row">
|
||||
<view class="type-switch">
|
||||
<view class="type-btn {{orderType == 'normal' ? 'type-active' : ''}}" data-type="normal" bindtap="switchOrderType">普通订单</view>
|
||||
<view class="type-btn {{orderType == 'peihu' ? 'type-active' : ''}}" data-type="peihu" bindtap="switchOrderType">陪护订单</view>
|
||||
</view>
|
||||
<view class="search-box">
|
||||
<image class="search-icon" src="/images/icon_search.svg" mode="aspectFit"/>
|
||||
<input class="search-input" placeholder="搜索订单ID/介绍" value="{{searchKeyword}}" bindinput="onSearchInput" bindconfirm="onSearchConfirm" confirm-type="search"/>
|
||||
<view class="search-clear" wx:if="{{searchKeyword}}" bindtap="clearSearch"><text>✕</text></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 主体:左右布局 -->
|
||||
<view class="main-container">
|
||||
<!-- 左侧状态栏(固定) -->
|
||||
<view class="left-status">
|
||||
<block wx:for="{{statusList}}" wx:key="key">
|
||||
<view class="status-item {{currentStatusKey == item.key ? 'status-active' : ''}}" data-key="{{item.key}}" bindtap="switchStatus">
|
||||
<text class="status-name">{{item.name}}</text>
|
||||
<view class="status-dot" wx:if="{{currentStatusKey == item.key}}"></view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
<!-- 右侧包裹:撑出右边距,里面是 scroll-view -->
|
||||
<view class="right-wrapper">
|
||||
<scroll-view
|
||||
class="right-list"
|
||||
scroll-y enhanced show-scrollbar="{{false}}"
|
||||
refresher-enabled="{{true}}"
|
||||
refresher-threshold="80"
|
||||
refresher-default-style="black"
|
||||
refresher-background="#f5f6fa"
|
||||
refresher-triggered="{{scrollViewRefreshing}}"
|
||||
bindrefresherrefresh="onPullDownRefresh"
|
||||
bindscrolltolower="onReachBottom"
|
||||
lower-threshold="50"
|
||||
>
|
||||
<view class="refresher-slot" slot="refresher">
|
||||
<text wx:if="{{scrollViewRefreshing}}" class="refreshing-text">⚡ 正在刷新...</text>
|
||||
<text wx:else class="pull-text">▼ 下拉刷新</text>
|
||||
</view>
|
||||
|
||||
<!-- 内容容器:仅设置左侧 padding,右侧间距由 wrapper 控制 -->
|
||||
<view class="list-inner">
|
||||
<view wx:if="{{isLoading && currentList.length === 0}}" class="loading-state">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="loading-tip">加载订单中...</text>
|
||||
</view>
|
||||
|
||||
<view wx:elif="{{!isLoading && currentList.length === 0}}" class="empty-state">
|
||||
<image class="empty-img" src="/images/empty-order.png" mode="aspectFit"/>
|
||||
<text class="empty-text">暂无订单</text>
|
||||
</view>
|
||||
|
||||
<block wx:for="{{currentList}}" wx:key="dingdan_id">
|
||||
<view class="order-card" data-item="{{item}}" bindtap="goToDsDingdanXiangqing">
|
||||
<image class="card-img" src="{{item.tupian}}" mode="aspectFill" binderror="onImageError"/>
|
||||
<view class="card-info">
|
||||
<view class="card-title">{{item.jieshao || '暂无描述'}}</view>
|
||||
<view class="card-id" wx:if="{{item.dingdan_id}}">ID: {{item.dingdan_id}}</view>
|
||||
</view>
|
||||
<view class="card-right">
|
||||
<view class="card-status" style="color: {{item.zhuangtaiColor}};">{{item.zhuangtaiZh}}</view>
|
||||
<view class="card-price">¥{{item.jine || '0.00'}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<view class="load-more" wx:if="{{hasMore && currentList.length > 0 && !isLoadingMore}}">
|
||||
<text class="load-more-text">上拉加载更多</text>
|
||||
</view>
|
||||
|
||||
<view class="loading-more-state" wx:if="{{isLoadingMore}}">
|
||||
<view class="mini-spinner"></view>
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
|
||||
<view class="manual-load-more" wx:if="{{hasMore && currentList.length > 0 && !isLoadingMore}}">
|
||||
<button class="load-more-btn" bindtap="onLoadMoreTap" loading="{{false}}">点击加载更多</button>
|
||||
</view>
|
||||
|
||||
<view class="no-more" wx:if="{{!hasMore && currentList.length > 0}}">
|
||||
<text>—— 没有更多了 ——</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<global-notification id="global-notification" />
|
||||
<custom-tab-bar />
|
||||
349
pages/dashoudingdan/dashoudingdan.wxss
Normal file
@@ -0,0 +1,349 @@
|
||||
/* 页面容器:禁止整体滚动 */
|
||||
page {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.ds-page {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #f5f6fa;
|
||||
}
|
||||
|
||||
/* 商品类型区 */
|
||||
.leixing-area {
|
||||
background: #fff;
|
||||
padding: 24rpx 0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.leixing-scroll {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.leixing-container {
|
||||
display: inline-flex;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
.leixing-item {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-right: 32rpx;
|
||||
padding: 12rpx 20rpx;
|
||||
border-radius: 28rpx;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.leixing-item.leixing-active {
|
||||
background: linear-gradient(135deg, #e8f0fe, #d4e4ff);
|
||||
box-shadow: 0 4rpx 12rpx rgba(33,150,243,0.15);
|
||||
}
|
||||
.leixing-img {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 50%;
|
||||
background: #f0f0f0;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
.leixing-name {
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
max-width: 100rpx;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 筛选行 */
|
||||
.filter-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx 24rpx;
|
||||
background: #fff;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.type-switch {
|
||||
display: flex;
|
||||
background: #f0f2f5;
|
||||
border-radius: 36rpx;
|
||||
padding: 4rpx;
|
||||
margin-right: 24rpx;
|
||||
}
|
||||
.type-btn {
|
||||
padding: 12rpx 32rpx;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
border-radius: 32rpx;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.type-btn.type-active {
|
||||
background: #fff;
|
||||
color: #1976D2;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.06);
|
||||
}
|
||||
.search-box {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #f0f2f5;
|
||||
border-radius: 36rpx;
|
||||
padding: 0 20rpx;
|
||||
height: 68rpx;
|
||||
}
|
||||
.search-icon {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
margin-right: 12rpx;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.search-input {
|
||||
flex: 1;
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
}
|
||||
.search-clear {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #ccc;
|
||||
border-radius: 50%;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
.search-clear text {
|
||||
font-size: 24rpx;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 主体布局 */
|
||||
.main-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 左侧状态栏(固定) */
|
||||
.left-status {
|
||||
width: 180rpx;
|
||||
background: #fff;
|
||||
border-right: 1rpx solid #f0f0f0;
|
||||
padding: 28rpx 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.status-item {
|
||||
padding: 28rpx 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.status-item.status-active {
|
||||
background: linear-gradient(to right, #e3f2fd, #fff);
|
||||
}
|
||||
.status-name {
|
||||
font-size: 28rpx;
|
||||
color: #555;
|
||||
}
|
||||
.status-active .status-name {
|
||||
color: #1976D2;
|
||||
font-weight: 600;
|
||||
}
|
||||
.status-dot {
|
||||
width: 14rpx;
|
||||
height: 14rpx;
|
||||
border-radius: 50%;
|
||||
background: #1976D2;
|
||||
}
|
||||
|
||||
/* ✅ 右侧包裹:通过 padding-right 制造右侧间距 */
|
||||
.right-wrapper {
|
||||
flex: 1;
|
||||
padding-right: 30rpx; /* 卡片距离屏幕右侧 30rpx */
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 右侧滚动区 */
|
||||
.right-list {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #f5f6fa;
|
||||
}
|
||||
.refresher-slot {
|
||||
text-align: center;
|
||||
padding: 14rpx 0;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 内容容器:仅左侧 padding,右侧间距由 wrapper 负责 */
|
||||
.list-inner {
|
||||
padding: 24rpx 0 200rpx 20rpx; /* 上、右、下、左 —— 右边无 padding */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 订单卡片(严格限制宽度) */
|
||||
.order-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 6rpx 20rpx rgba(0,0,0,0.04);
|
||||
box-sizing: border-box;
|
||||
width: 100%; /* 占满父容器宽度 */
|
||||
}
|
||||
.order-card:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
.card-img {
|
||||
width: 130rpx;
|
||||
height: 130rpx;
|
||||
border-radius: 18rpx;
|
||||
background: #f0f0f0;
|
||||
margin-right: 24rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.card-info {
|
||||
flex: 1;
|
||||
height: 130rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
min-width: 0;
|
||||
}
|
||||
.card-title {
|
||||
font-size: 28rpx;
|
||||
color: #222;
|
||||
line-height: 1.5;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
.card-id {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
/* ✅ 状态和价格区:压缩宽度,强制不换行 */
|
||||
.card-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
height: 130rpx;
|
||||
margin-left: 20rpx;
|
||||
flex-shrink: 0;
|
||||
max-width: 160rpx; /* 限制最大宽度 */
|
||||
}
|
||||
.card-status {
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
padding: 4rpx 12rpx;
|
||||
background: #f5f5f5;
|
||||
border-radius: 20rpx;
|
||||
margin-bottom: 8rpx;
|
||||
white-space: nowrap;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.card-price {
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
white-space: nowrap;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* 加载与空状态 */
|
||||
.loading-state, .empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 120rpx 0;
|
||||
}
|
||||
.loading-spinner {
|
||||
width: 52rpx;
|
||||
height: 52rpx;
|
||||
border: 4rpx solid #e0e0e0;
|
||||
border-top-color: #1976D2;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.empty-img {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
opacity: 0.4;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
.empty-text, .loading-tip {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.load-more {
|
||||
text-align: center;
|
||||
padding: 36rpx 0 20rpx 0;
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
.loading-more-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20rpx 0;
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
.mini-spinner {
|
||||
display: inline-block;
|
||||
width: 26rpx;
|
||||
height: 26rpx;
|
||||
border: 3rpx solid #ccc;
|
||||
border-top-color: #1976D2;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.6s linear infinite;
|
||||
vertical-align: middle;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.manual-load-more {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 30rpx 0 10rpx 0;
|
||||
}
|
||||
.load-more-btn {
|
||||
background: #ffffff;
|
||||
color: #1976D2;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
border: 2rpx solid #1976D2;
|
||||
border-radius: 40rpx;
|
||||
padding: 16rpx 60rpx;
|
||||
text-align: center;
|
||||
box-shadow: 0 4rpx 12rpx rgba(33,150,243,0.1);
|
||||
}
|
||||
.load-more-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.no-more {
|
||||
text-align: center;
|
||||
padding: 40rpx 0;
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||