diff --git a/miniprogram/app.js b/miniprogram/app.js
index cd75c7c..63d2a6c 100644
--- a/miniprogram/app.js
+++ b/miniprogram/app.js
@@ -310,6 +310,7 @@ App({
enterpriseId: config.kefu.enterpriseId || ''
};
}
+ this.emitEvent('configReady', config);
},
initGoEasyWithConfig() {
diff --git a/miniprogram/app.json b/miniprogram/app.json
new file mode 100644
index 0000000..927550b
--- /dev/null
+++ b/miniprogram/app.json
@@ -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",
+ "components/global-notification/global-notification",
+ "components/popup-notice/popup-notice",
+ "pages/tixian/components/mode1/mode1",
+ "pages/tixian/components/mode2/mode2",
+
+ "pages/jiedanchi2/jiedanchi2",
+ "pages/qunliaotian/qunliaotian",
+ "pages/kefuliaotian/kefuliaotian",
+ "pages/renzheng/renzheng",
+
+ "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"
+
+ ],
+
+ "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": "二H电竞"
+ },
+ "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"
+}
\ No newline at end of file
diff --git a/miniprogram/app.wxss b/miniprogram/app.wxss
new file mode 100644
index 0000000..06c6fc9
--- /dev/null
+++ b/miniprogram/app.wxss
@@ -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;
+}
diff --git a/miniprogram/components/chenghao-tag/chenghao-tag.js b/miniprogram/components/chenghao-tag/chenghao-tag.js
new file mode 100644
index 0000000..ae79133
--- /dev/null
+++ b/miniprogram/components/chenghao-tag/chenghao-tag.js
@@ -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);' });
+ }
+ }
+ }
+});
\ No newline at end of file
diff --git a/miniprogram/components/chenghao-tag/chenghao-tag.json b/miniprogram/components/chenghao-tag/chenghao-tag.json
new file mode 100644
index 0000000..62389a9
--- /dev/null
+++ b/miniprogram/components/chenghao-tag/chenghao-tag.json
@@ -0,0 +1,4 @@
+{
+ "component": true,
+ "usingComponents": {}
+ }
\ No newline at end of file
diff --git a/miniprogram/components/chenghao-tag/chenghao-tag.wxml b/miniprogram/components/chenghao-tag/chenghao-tag.wxml
new file mode 100644
index 0000000..e9c44f3
--- /dev/null
+++ b/miniprogram/components/chenghao-tag/chenghao-tag.wxml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+ {{mingcheng}}
+
\ No newline at end of file
diff --git a/miniprogram/components/chenghao-tag/chenghao-tag.wxss b/miniprogram/components/chenghao-tag/chenghao-tag.wxss
new file mode 100644
index 0000000..d142188
--- /dev/null
+++ b/miniprogram/components/chenghao-tag/chenghao-tag.wxss
@@ -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); }
+ }
\ No newline at end of file
diff --git a/miniprogram/components/global-notification/global-notification.js b/miniprogram/components/global-notification/global-notification.js
new file mode 100644
index 0000000..bd88ab6
--- /dev/null
+++ b/miniprogram/components/global-notification/global-notification.js
@@ -0,0 +1,366 @@
+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 = data.avatar;
+ if (!avatar && app && app.globalData) {
+ avatar = app.globalData.ossImageUrl + app.globalData.morentouxiang;
+ }
+
+ 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' });
+ }
+ }
+ }
+ });
\ No newline at end of file
diff --git a/miniprogram/components/global-notification/global-notification.json b/miniprogram/components/global-notification/global-notification.json
new file mode 100644
index 0000000..7e37c03
--- /dev/null
+++ b/miniprogram/components/global-notification/global-notification.json
@@ -0,0 +1,4 @@
+{
+ "component": true,
+ "usingComponents": {}
+}
\ No newline at end of file
diff --git a/miniprogram/components/global-notification/global-notification.wxml b/miniprogram/components/global-notification/global-notification.wxml
new file mode 100644
index 0000000..d76b58c
--- /dev/null
+++ b/miniprogram/components/global-notification/global-notification.wxml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{title}}
+ {{message}}
+
+ {{formatTime(notificationData ? notificationData.timestamp : '')}}
+
+
+
+
+
+
+
+ ×
+
+
+
+
+
+ {{isMuted ? '🔕' : '🔔'}}
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/components/global-notification/global-notification.wxss b/miniprogram/components/global-notification/global-notification.wxss
new file mode 100644
index 0000000..fc67be6
--- /dev/null
+++ b/miniprogram/components/global-notification/global-notification.wxss
@@ -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;
+}
\ No newline at end of file
diff --git a/miniprogram/components/navigation-bar/navigation-bar.js b/miniprogram/components/navigation-bar/navigation-bar.js
new file mode 100644
index 0000000..eb1770e
--- /dev/null
+++ b/miniprogram/components/navigation-bar/navigation-bar.js
@@ -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 }, {})
+ }
+ },
+})
diff --git a/miniprogram/components/navigation-bar/navigation-bar.json b/miniprogram/components/navigation-bar/navigation-bar.json
new file mode 100644
index 0000000..4a20f17
--- /dev/null
+++ b/miniprogram/components/navigation-bar/navigation-bar.json
@@ -0,0 +1,5 @@
+{
+ "component": true,
+ "styleIsolation": "apply-shared",
+ "usingComponents": {}
+}
\ No newline at end of file
diff --git a/miniprogram/components/navigation-bar/navigation-bar.wxml b/miniprogram/components/navigation-bar/navigation-bar.wxml
new file mode 100644
index 0000000..2bfe519
--- /dev/null
+++ b/miniprogram/components/navigation-bar/navigation-bar.wxml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{title}}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/miniprogram/components/navigation-bar/navigation-bar.wxss b/miniprogram/components/navigation-bar/navigation-bar.wxss
new file mode 100644
index 0000000..8bd379e
--- /dev/null
+++ b/miniprogram/components/navigation-bar/navigation-bar.wxss
@@ -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);
+ }
+}
diff --git a/miniprogram/components/order-card/order-card.js b/miniprogram/components/order-card/order-card.js
new file mode 100644
index 0000000..b94bb6c
--- /dev/null
+++ b/miniprogram/components/order-card/order-card.js
@@ -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);
+ }
+ });
+ });
+ }
+ }
+});
\ No newline at end of file
diff --git a/miniprogram/components/order-card/order-card.json b/miniprogram/components/order-card/order-card.json
new file mode 100644
index 0000000..7e37c03
--- /dev/null
+++ b/miniprogram/components/order-card/order-card.json
@@ -0,0 +1,4 @@
+{
+ "component": true,
+ "usingComponents": {}
+}
\ No newline at end of file
diff --git a/miniprogram/components/order-card/order-card.wxml b/miniprogram/components/order-card/order-card.wxml
new file mode 100644
index 0000000..c87ad17
--- /dev/null
+++ b/miniprogram/components/order-card/order-card.wxml
@@ -0,0 +1,20 @@
+
+
+
+
+
+ {{order.jieshao || '暂无介绍'}}
+ ¥{{order.jine}}
+
+ 发送
+
+ {{errorMsg}}
+
+
+ 加载订单信息...
+
\ No newline at end of file
diff --git a/miniprogram/components/order-card/order-card.wxss b/miniprogram/components/order-card/order-card.wxss
new file mode 100644
index 0000000..62c2737
--- /dev/null
+++ b/miniprogram/components/order-card/order-card.wxss
@@ -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; }
\ No newline at end of file
diff --git a/miniprogram/components/order-sender/order-sender.js b/miniprogram/components/order-sender/order-sender.js
new file mode 100644
index 0000000..f4c9444
--- /dev/null
+++ b/miniprogram/components/order-sender/order-sender.js
@@ -0,0 +1,16 @@
+const app = getApp();
+
+Component({
+ properties: {
+ visible: Boolean,
+ showDetail: { type: Boolean, value: false }
+ },
+
+ data: {},
+
+ methods: {
+ close() {
+ this.triggerEvent('close');
+ }
+ }
+});
\ No newline at end of file
diff --git a/miniprogram/components/order-sender/order-sender.json b/miniprogram/components/order-sender/order-sender.json
new file mode 100644
index 0000000..62389a9
--- /dev/null
+++ b/miniprogram/components/order-sender/order-sender.json
@@ -0,0 +1,4 @@
+{
+ "component": true,
+ "usingComponents": {}
+ }
\ No newline at end of file
diff --git a/miniprogram/components/order-sender/order-sender.wxml b/miniprogram/components/order-sender/order-sender.wxml
new file mode 100644
index 0000000..ae53ed5
--- /dev/null
+++ b/miniprogram/components/order-sender/order-sender.wxml
@@ -0,0 +1,12 @@
+
+
+
+
+ 📋
+ 此功能暂未开放
+ 敬请期待
+
+
\ No newline at end of file
diff --git a/miniprogram/components/order-sender/order-sender.wxss b/miniprogram/components/order-sender/order-sender.wxss
new file mode 100644
index 0000000..c0ffc48
--- /dev/null
+++ b/miniprogram/components/order-sender/order-sender.wxss
@@ -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;
+ }
\ No newline at end of file
diff --git a/miniprogram/components/popup-notice/popup-notice.js b/miniprogram/components/popup-notice/popup-notice.js
new file mode 100644
index 0000000..64589db
--- /dev/null
+++ b/miniprogram/components/popup-notice/popup-notice.js
@@ -0,0 +1,324 @@
+// 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 {
+ avatarUrl = '/images/default-avatar.png';
+ }
+
+ if (avatarUrl && !avatarUrl.startsWith('http')) {
+ 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() {}
+ }
+ });
\ No newline at end of file
diff --git a/miniprogram/components/popup-notice/popup-notice.json b/miniprogram/components/popup-notice/popup-notice.json
new file mode 100644
index 0000000..62389a9
--- /dev/null
+++ b/miniprogram/components/popup-notice/popup-notice.json
@@ -0,0 +1,4 @@
+{
+ "component": true,
+ "usingComponents": {}
+ }
\ No newline at end of file
diff --git a/miniprogram/components/popup-notice/popup-notice.wxml b/miniprogram/components/popup-notice/popup-notice.wxml
new file mode 100644
index 0000000..8e682cb
--- /dev/null
+++ b/miniprogram/components/popup-notice/popup-notice.wxml
@@ -0,0 +1,110 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/components/popup-notice/popup-notice.wxss b/miniprogram/components/popup-notice/popup-notice.wxss
new file mode 100644
index 0000000..ee4873b
--- /dev/null
+++ b/miniprogram/components/popup-notice/popup-notice.wxss
@@ -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%;
+}
\ No newline at end of file
diff --git a/miniprogram/config/roles.js b/miniprogram/config/roles.js
new file mode 100644
index 0000000..95c04dc
--- /dev/null
+++ b/miniprogram/config/roles.js
@@ -0,0 +1,144 @@
+/*
+ * 角色配置表 —— 用于动态切换底部 TabBar 展示内容
+ * 每个角色的 tabList 数组元素顺序决定 TabBar 显示顺序
+ * 字段说明:
+ * pagePath : 页面路径(以 pages/ 开头)
+ * text : TabBar 文字
+ * iconPath : 未选中图标(相对路径)
+ * selectedIconPath: 选中图标
+ */
+const roles = {
+ // 普通老板(默认)
+ 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/jiedanchi/jiedanchi',
+ tabList: [
+ {
+ pagePath: 'pages/jiedanchi/jiedanchi',
+ text: '接单池',
+ iconPath: '/images/jiedanchi.png',
+ selectedIconPath: '/images/jiedanchi.png'
+ },
+ {
+ pagePath: 'pages/xiaoxi/xiaoxi',
+ text: '消息',
+ iconPath: '/images/xiaoxi.png',
+ selectedIconPath: '/images/xiaoxi.png'
+ },
+ {
+ pagePath: 'pages/dashoudingdan/dashoudingdan',
+ text: '订单',
+ iconPath: '/images/dashoudingdan_icon.png', // 请替换为实际图标
+ selectedIconPath: '/images/dashoudingdan_icon.png'
+ },
+ {
+ pagePath: 'pages/dashouduan/dashouduan',
+ text: '我的',
+ iconPath: '/images/dashouduan_icon.png',
+ selectedIconPath: '/images/dashouduan_icon.png'
+ }
+ ]
+ },
+ // 商家
+ shangjia: {
+ name: '商家',
+ defaultPage: 'pages/index/index',
+ tabList: [
+ {
+ pagePath: 'pages/index/index',
+ text: '首页',
+ iconPath: '/images/zhuye.png',
+ selectedIconPath: '/images/zhuye.png'
+ },
+ {
+ pagePath: 'pages/sjdingdan/sjdingdan',
+ text: '订单',
+ iconPath: '/images/sjdingdan_icon.png', // 请替换为实际图标
+ selectedIconPath: '/images/sjdingdan_icon.png'
+ },
+ {
+ pagePath: 'pages/xiaoxi/xiaoxi',
+ text: '消息',
+ iconPath: '/images/xiaoxi.png',
+ selectedIconPath: '/images/xiaoxi.png'
+ },
+ {
+ pagePath: 'pages/shangjiaduan/shangjiaduan',
+ text: '我的',
+ iconPath: '/images/shangjiaduan_icon.png',
+ selectedIconPath: '/images/shangjiaduan_icon.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/guanshiduan_icon.png',
+ selectedIconPath: '/images/guanshiduan_icon.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/zuzhangduan_icon.png',
+ selectedIconPath: '/images/zuzhangduan_icon.png'
+ }
+ ]
+ }
+ };
+
+ module.exports = { roles };
\ No newline at end of file
diff --git a/miniprogram/custom-tab-bar/index.js b/miniprogram/custom-tab-bar/index.js
new file mode 100644
index 0000000..53ab079
--- /dev/null
+++ b/miniprogram/custom-tab-bar/index.js
@@ -0,0 +1,194 @@
+// 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 });
+ }
+ };
+ app.on('tabBarBadgeChanged', this._badgeListener);
+ },
+ detached() {
+ if (this._badgeListener) {
+ app.off('tabBarBadgeChanged', this._badgeListener);
+ }
+ }
+ },
+ 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 role = app.globalData.currentRole || 'normal';
+ 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 arr = [{ key: 'normal', name: '点单老板' }];
+ if (wx.getStorageSync('dashoustatus') === 1) arr.push({ key: 'dashou', name: '打手' });
+ if (wx.getStorageSync('shangjiastatus') === 1) arr.push({ key: 'shangjia', name: '商家' });
+ if (wx.getStorageSync('guanshistatus') === 1) arr.push({ key: 'guanshi', name: '管事' });
+ if (wx.getStorageSync('zuzhangstatus') === 1) arr.push({ key: 'zuzhang', name: '组长' });
+ if (wx.getStorageSync('kaoheguanstatus') === 1) 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. 立即更新角色并保存
+ app.globalData.currentRole = newRole;
+ wx.setStorageSync('currentRole', 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 });
+ }
+ }
+});
\ No newline at end of file
diff --git a/miniprogram/custom-tab-bar/index.json b/miniprogram/custom-tab-bar/index.json
new file mode 100644
index 0000000..62389a9
--- /dev/null
+++ b/miniprogram/custom-tab-bar/index.json
@@ -0,0 +1,4 @@
+{
+ "component": true,
+ "usingComponents": {}
+ }
\ No newline at end of file
diff --git a/miniprogram/custom-tab-bar/index.wxml b/miniprogram/custom-tab-bar/index.wxml
new file mode 100644
index 0000000..93b07a6
--- /dev/null
+++ b/miniprogram/custom-tab-bar/index.wxml
@@ -0,0 +1,42 @@
+
+
+
+
+
+ 切换
+
+
+
+
+
+
+
+
+ {{badgeText}}
+
+
+ {{item.text}}
+
+
+
+
+
+
+
+
+
+
+
+ {{item.name}}
+ ✓
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/custom-tab-bar/index.wxss b/miniprogram/custom-tab-bar/index.wxss
new file mode 100644
index 0000000..7a957fa
--- /dev/null
+++ b/miniprogram/custom-tab-bar/index.wxss
@@ -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;
+ }
\ No newline at end of file
diff --git a/miniprogram/images/dingdan.png b/miniprogram/images/dingdan.png
new file mode 100644
index 0000000..b9a3a9c
Binary files /dev/null and b/miniprogram/images/dingdan.png differ
diff --git a/miniprogram/images/fenlei.png b/miniprogram/images/fenlei.png
new file mode 100644
index 0000000..4753795
Binary files /dev/null and b/miniprogram/images/fenlei.png differ
diff --git a/miniprogram/images/gonggao.png b/miniprogram/images/gonggao.png
new file mode 100644
index 0000000..19cc52c
Binary files /dev/null and b/miniprogram/images/gonggao.png differ
diff --git a/miniprogram/images/goumai.png b/miniprogram/images/goumai.png
new file mode 100644
index 0000000..a2ec8ed
Binary files /dev/null and b/miniprogram/images/goumai.png differ
diff --git a/miniprogram/images/jiedanchi.png b/miniprogram/images/jiedanchi.png
new file mode 100644
index 0000000..16e2b9f
Binary files /dev/null and b/miniprogram/images/jiedanchi.png differ
diff --git a/miniprogram/images/jieshao.png b/miniprogram/images/jieshao.png
new file mode 100644
index 0000000..c713f53
Binary files /dev/null and b/miniprogram/images/jieshao.png differ
diff --git a/miniprogram/images/jinpaidashou.png b/miniprogram/images/jinpaidashou.png
new file mode 100644
index 0000000..b54a971
Binary files /dev/null and b/miniprogram/images/jinpaidashou.png differ
diff --git a/miniprogram/images/role-switch.png b/miniprogram/images/role-switch.png
new file mode 100644
index 0000000..bbd4afe
Binary files /dev/null and b/miniprogram/images/role-switch.png differ
diff --git a/miniprogram/images/shuaxin.png b/miniprogram/images/shuaxin.png
new file mode 100644
index 0000000..15ad08d
Binary files /dev/null and b/miniprogram/images/shuaxin.png differ
diff --git a/miniprogram/images/sousuo.png b/miniprogram/images/sousuo.png
new file mode 100644
index 0000000..2a8f235
Binary files /dev/null and b/miniprogram/images/sousuo.png differ
diff --git a/miniprogram/images/tiaoxuan.png b/miniprogram/images/tiaoxuan.png
new file mode 100644
index 0000000..ffcf298
Binary files /dev/null and b/miniprogram/images/tiaoxuan.png differ
diff --git a/miniprogram/images/tuichu.png b/miniprogram/images/tuichu.png
new file mode 100644
index 0000000..01553b5
Binary files /dev/null and b/miniprogram/images/tuichu.png differ
diff --git a/miniprogram/images/weixinzhifu.png b/miniprogram/images/weixinzhifu.png
new file mode 100644
index 0000000..f2551fd
Binary files /dev/null and b/miniprogram/images/weixinzhifu.png differ
diff --git a/miniprogram/images/wode.png b/miniprogram/images/wode.png
new file mode 100644
index 0000000..1fec6b3
Binary files /dev/null and b/miniprogram/images/wode.png differ
diff --git a/miniprogram/images/xiaoxi.png b/miniprogram/images/xiaoxi.png
new file mode 100644
index 0000000..e6cda27
Binary files /dev/null and b/miniprogram/images/xiaoxi.png differ
diff --git a/miniprogram/images/yituikuan.png b/miniprogram/images/yituikuan.png
new file mode 100644
index 0000000..62c8fee
Binary files /dev/null and b/miniprogram/images/yituikuan.png differ
diff --git a/miniprogram/images/yiwancheng.png b/miniprogram/images/yiwancheng.png
new file mode 100644
index 0000000..e1c333e
Binary files /dev/null and b/miniprogram/images/yiwancheng.png differ
diff --git a/miniprogram/images/zhuanqu.png b/miniprogram/images/zhuanqu.png
new file mode 100644
index 0000000..2cb7945
Binary files /dev/null and b/miniprogram/images/zhuanqu.png differ
diff --git a/miniprogram/images/zhuye.png b/miniprogram/images/zhuye.png
new file mode 100644
index 0000000..b2fe9ae
Binary files /dev/null and b/miniprogram/images/zhuye.png differ
diff --git a/miniprogram/pages/cfss/cfss/cfss.js b/miniprogram/pages/cfss/cfss/cfss.js
new file mode 100644
index 0000000..abdec80
--- /dev/null
+++ b/miniprogram/pages/cfss/cfss/cfss.js
@@ -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 });
+ }
+});
\ No newline at end of file
diff --git a/miniprogram/pages/cfss/cfss/cfss.json b/miniprogram/pages/cfss/cfss/cfss.json
new file mode 100644
index 0000000..4ca406b
--- /dev/null
+++ b/miniprogram/pages/cfss/cfss/cfss.json
@@ -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"
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/cfss/cfss/cfss.wxml b/miniprogram/pages/cfss/cfss/cfss.wxml
new file mode 100644
index 0000000..03e8517
--- /dev/null
+++ b/miniprogram/pages/cfss/cfss/cfss.wxml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+ {{ stats.fakuan.daijiaona }}
+ 待缴罚款
+
+
+ {{ stats.jifen.daichuli }}
+ 待处理积分
+
+
+
+ 罚款:已缴{{ stats.fakuan.yijiaona }} · 申诉中{{ stats.fakuan.shensuzhong }} · 成功{{ stats.fakuan.yibohui }}
+ 积分:已处理{{ stats.jifen.yichuli }}
+
+
+
+
+
+
+ 罚款
+
+
+ 积分处罚
+
+
+
+
+
+
+ 待缴纳 {{ stats.fakuan.daijiaona }}
+ 申诉中 {{ stats.fakuan.shensuzhong }}
+ 已缴纳 {{ stats.fakuan.yijiaona }}
+ 申诉成功 {{ stats.fakuan.yibohui }}
+
+
+
+
+ 待处理 {{ stats.jifen.daichuli }}
+ 已处理 {{ stats.jifen.yichuli }}
+
+
+
+
+
+
+ 搜索
+ ✕
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/cfss/cfss/cfss.wxss b/miniprogram/pages/cfss/cfss/cfss.wxss
new file mode 100644
index 0000000..4cd5d19
--- /dev/null
+++ b/miniprogram/pages/cfss/cfss/cfss.wxss
@@ -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;
+}
\ No newline at end of file
diff --git a/miniprogram/pages/cfss/components/fakuan-list/fakuan-list.js b/miniprogram/pages/cfss/components/fakuan-list/fakuan-list.js
new file mode 100644
index 0000000..cf45977
--- /dev/null
+++ b/miniprogram/pages/cfss/components/fakuan-list/fakuan-list.js
@@ -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;
+ }
+ }
+});
\ No newline at end of file
diff --git a/miniprogram/pages/cfss/components/fakuan-list/fakuan-list.json b/miniprogram/pages/cfss/components/fakuan-list/fakuan-list.json
new file mode 100644
index 0000000..d192c45
--- /dev/null
+++ b/miniprogram/pages/cfss/components/fakuan-list/fakuan-list.json
@@ -0,0 +1,6 @@
+{
+ "component": true,
+ "usingComponents": {
+ "fakuan-pay": "/pages/cfss/components/fakuan-pay/fakuan-pay"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/pages/cfss/components/fakuan-list/fakuan-list.wxml b/miniprogram/pages/cfss/components/fakuan-list/fakuan-list.wxml
new file mode 100644
index 0000000..257802a
--- /dev/null
+++ b/miniprogram/pages/cfss/components/fakuan-list/fakuan-list.wxml
@@ -0,0 +1,116 @@
+
+
+
+ 加载中...
+
+
+
+ {{ item.create_time }}
+ {{ item.display_status }}
+
+ 金额:¥{{ item.fakuanjine }}
+ 理由:{{ item.chufaliyou }}
+ 订单:{{ item.guanliandingdan_id }}
+
+
+
+
+
+ 点击获取更多
+
+ 加载中...
+ —— 没有更多了 ——
+ 暂无罚款记录
+
+
+
+
+
+
+ 罚款详情
+ ✕
+
+
+ 罚款金额¥{{ detailItem.fakuanjine }}
+ 当前状态{{ detailItem.display_status }}
+ 处罚理由{{ detailItem.chufaliyou }}
+ 关联订单{{ detailItem.guanliandingdan_id }}
+ 驳回理由{{ detailItem.bohuiliyou }}
+ 申诉理由{{ detailItem.shensuliyou }}
+
+
+
+ 证据图片
+
+
+
+
+
+
+
+
+
+
+ 申诉图片
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 提交申诉
+ ✕
+
+
+
+ 申诉理由
+
+ {{ shensuLiyou.length }}/500
+
+
+ 上传凭证(最多9张)
+
+
+
+
+ ✕
+
+
+ +
+
+
+
+ {{ uploadProgress.current }}/{{ uploadProgress.total }}
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/cfss/components/fakuan-list/fakuan-list.wxss b/miniprogram/pages/cfss/components/fakuan-list/fakuan-list.wxss
new file mode 100644
index 0000000..3e02bb2
--- /dev/null
+++ b/miniprogram/pages/cfss/components/fakuan-list/fakuan-list.wxss
@@ -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;
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/cfss/components/fakuan-pay/fakuan-pay.js b/miniprogram/pages/cfss/components/fakuan-pay/fakuan-pay.js
new file mode 100644
index 0000000..53d2f89
--- /dev/null
+++ b/miniprogram/pages/cfss/components/fakuan-pay/fakuan-pay.js
@@ -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' });
+ }
+ }
+ }
+});
\ No newline at end of file
diff --git a/miniprogram/pages/cfss/components/fakuan-pay/fakuan-pay.json b/miniprogram/pages/cfss/components/fakuan-pay/fakuan-pay.json
new file mode 100644
index 0000000..fba482a
--- /dev/null
+++ b/miniprogram/pages/cfss/components/fakuan-pay/fakuan-pay.json
@@ -0,0 +1,3 @@
+{
+ "component": true
+}
\ No newline at end of file
diff --git a/miniprogram/pages/cfss/components/fakuan-pay/fakuan-pay.wxml b/miniprogram/pages/cfss/components/fakuan-pay/fakuan-pay.wxml
new file mode 100644
index 0000000..d3a9c3a
--- /dev/null
+++ b/miniprogram/pages/cfss/components/fakuan-pay/fakuan-pay.wxml
@@ -0,0 +1,61 @@
+
+
+
+
+
+ 选择支付方式
+ ✕
+
+
+
+ 💳
+ 微信支付
+
+
+ 💰
+ 佣金抵扣
+
+
+
+
+
+
+
+ 选择抵扣身份
+ ✕
+
+
+
+ {{ item.jieshao }}
+ 需¥{{ item.xuyao }}
+
+
+
+
+
+
+
+ 确认抵扣
+ ✕
+
+
+
+ 抵扣身份:{{ selectedBalance.jieshao }}
+
+
+ 需扣除:¥{{ selectedBalance.xuyao }}
+
+ 确认后将从您的佣金/分红中扣除
+
+
+
+
+
+
+
+ {{ loadingText }}
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/cfss/components/fakuan-pay/fakuan-pay.wxss b/miniprogram/pages/cfss/components/fakuan-pay/fakuan-pay.wxss
new file mode 100644
index 0000000..1f7f292
--- /dev/null
+++ b/miniprogram/pages/cfss/components/fakuan-pay/fakuan-pay.wxss
@@ -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; }
\ No newline at end of file
diff --git a/miniprogram/pages/cfss/components/jifen-list/jifen-list.js b/miniprogram/pages/cfss/components/jifen-list/jifen-list.js
new file mode 100644
index 0000000..487c7d9
--- /dev/null
+++ b/miniprogram/pages/cfss/components/jifen-list/jifen-list.js
@@ -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' }); }
+ }
+ }
+});
\ No newline at end of file
diff --git a/miniprogram/pages/cfss/components/jifen-list/jifen-list.json b/miniprogram/pages/cfss/components/jifen-list/jifen-list.json
new file mode 100644
index 0000000..bbde8dc
--- /dev/null
+++ b/miniprogram/pages/cfss/components/jifen-list/jifen-list.json
@@ -0,0 +1,3 @@
+{
+ "component": true
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/cfss/components/jifen-list/jifen-list.wxml b/miniprogram/pages/cfss/components/jifen-list/jifen-list.wxml
new file mode 100644
index 0000000..ccef2b0
--- /dev/null
+++ b/miniprogram/pages/cfss/components/jifen-list/jifen-list.wxml
@@ -0,0 +1,105 @@
+
+
+ 加载中...
+
+
+
+ {{ item.display_time }}
+ {{ item.display_status }}
+
+ 申请人:{{ item.qingqiuid || '--' }}
+ 理由:{{ item.cfliyou || '无' }}
+ 订单:{{ item.dingdan_id }}
+
+
+
+
+
+ 点击获取更多
+
+ 加载中...
+ —— 没有更多了 ——
+ 暂无积分处罚记录
+
+
+
+
+
+
+ 处罚详情
+ ✕
+
+
+ 订单编号{{ xuanzhongChufa.dingdan_id || '--' }}
+ 申请人ID{{ xuanzhongChufa.qingqiuid || '--' }}
+ 时间{{ xuanzhongChufa.display_time }}
+ 状态{{ xuanzhongChufa.display_status }}
+ 处罚理由{{ xuanzhongChufa.cfliyou || '无' }}
+
+
+ 证据图片
+
+
+
+
+
+
+
+
+
+
+ 我的申诉
+ 理由{{ xuanzhongChufa.ssliyou }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 提交申诉
+ ✕
+
+
+
+ 申诉理由
+
+ {{ shensuLiyou.length }}/500
+
+
+ 上传凭证(最多9张)
+
+
+
+
+ ✕
+
+
+ +
+
+
+
+ {{ shangchuanJindu }}/{{ shangchuanZongshu }}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/cfss/components/jifen-list/jifen-list.wxss b/miniprogram/pages/cfss/components/jifen-list/jifen-list.wxss
new file mode 100644
index 0000000..5cf6c07
--- /dev/null
+++ b/miniprogram/pages/cfss/components/jifen-list/jifen-list.wxss
@@ -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;
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/czjilu/czjilu.js b/miniprogram/pages/czjilu/czjilu.js
new file mode 100644
index 0000000..a2a592d
--- /dev/null
+++ b/miniprogram/pages/czjilu/czjilu.js
@@ -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);
+ }
+});
\ No newline at end of file
diff --git a/miniprogram/pages/czjilu/czjilu.json b/miniprogram/pages/czjilu/czjilu.json
new file mode 100644
index 0000000..ea59dc7
--- /dev/null
+++ b/miniprogram/pages/czjilu/czjilu.json
@@ -0,0 +1,11 @@
+{
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification"
+ },
+ "navigationBarTitleText": "会员记录",
+ "navigationBarBackgroundColor": "#0a0a0f",
+ "navigationBarTextStyle": "white",
+ "backgroundColor": "#0a0a0f",
+ "backgroundTextStyle": "light",
+ "enablePullDownRefresh": false
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/czjilu/czjilu.wxml b/miniprogram/pages/czjilu/czjilu.wxml
new file mode 100644
index 0000000..46fcd82
--- /dev/null
+++ b/miniprogram/pages/czjilu/czjilu.wxml
@@ -0,0 +1,119 @@
+
+
+
+
+
+
+
+
+ 分红总额
+ ¥{{fenyongzonge}}
+ 累计收益
+
+
+
+
+
+ {{yaoqingzongshu}}
+ 邀请打手
+
+
+
+
+
+ {{chongzhiDashouShuliang}}
+ 充值打手
+
+
+
+
+
+
+ ↻
+
+
+
+
+
+
+
+
+
+ 加载中...
+
+
+
+
+ 🚀
+ 暂无会员记录
+ 快去邀请打手吧
+ 点击刷新
+
+
+
+
+
+
+ 充值记录
+ ({{dashouList.length}}/{{zongTiaoshu}})
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{item.nicheng}}
+ ID: {{item.yonghuid}}
+
+
+
+
+
+ +
+ ¥{{item.fenhong}}
+
+ {{item.shijian}}
+
+
+
+
+
+
+
+
+ 加载更多中...
+
+
+
+
+ ✓
+ 已经到底了,共{{dashouList.length}}条记录
+
+
+
+
+
+
+ ⚠️
+ {{cuowuTishi}}
+ 重试加载
+
+
+
diff --git a/miniprogram/pages/czjilu/czjilu.wxss b/miniprogram/pages/czjilu/czjilu.wxss
new file mode 100644
index 0000000..e239ef6
--- /dev/null
+++ b/miniprogram/pages/czjilu/czjilu.wxss
@@ -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;
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/dashouchongzhi/dashouchongzhi.js b/miniprogram/pages/dashouchongzhi/dashouchongzhi.js
new file mode 100644
index 0000000..030a40e
--- /dev/null
+++ b/miniprogram/pages/dashouchongzhi/dashouchongzhi.js
@@ -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 });
+ },
+});
\ No newline at end of file
diff --git a/miniprogram/pages/dashouchongzhi/dashouchongzhi.json b/miniprogram/pages/dashouchongzhi/dashouchongzhi.json
new file mode 100644
index 0000000..90e50da
--- /dev/null
+++ b/miniprogram/pages/dashouchongzhi/dashouchongzhi.json
@@ -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
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/dashouchongzhi/dashouchongzhi.wxml b/miniprogram/pages/dashouchongzhi/dashouchongzhi.wxml
new file mode 100644
index 0000000..04f074e
--- /dev/null
+++ b/miniprogram/pages/dashouchongzhi/dashouchongzhi.wxml
@@ -0,0 +1,514 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 已拥有
+
+
+
+ {{item.mingzi}}
+
+
+
+
+ ¥
+ {{item.jiage}}
+ /30天
+
+
+
+
+
+ {{item}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{currentHuiyuan.jieshao || '加载中...'}}
+
+
+
+ {{index + 1}}
+ {{item}}
+
+
+
+
+
+
+
+
+
+
+ 价格:
+ ¥{{currentHuiyuan.jiage}}
+
+
+ 已激活,{{currentHuiyuan.buyInfo.daoqi}}后到期
+
+ 激活会员特权
+
+
+
+
+
+
+
+ {{currentHuiyuan.isBought ? '立即续费' : '立即激活'}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{item.huiyuanming}}
+ 到期: {{formatDaoqiTime(item.daoqi)}}
+
+
+
+
+
+ 暂无会员
+
+
+
+
+
+
+
+
+
+
+
+
+ ¥
+ {{yajin}}
+
+ 可用余额
+
+
+
+
+
+
+
+ 立即充值
+ ➤
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{jifen}}
+ 积分
+
+
+
+
+
+
+
+ 积分不足,影响接单,提现权限
+ 积分充足,无需补充
+
+
+
+
+
+
+ {{jifen >= 10 ? '积分已满' : '立即补充'}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 充值金额 (1-10000元)
+
+
+
+ ¥
+
+
+
+
+ 支持1-10000元整数充值
+
+
+
+ 快速选择
+
+
+ 100元
+
+
+ 500元
+
+
+ 1000元
+
+
+ 5000元
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 微信支付
+ 安全快捷
+
+
+
+ 余额抵扣
+ 使用佣金或分红
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{item.jieshao}}
+ 需¥{{item.xuyao}}
+
+ ➤
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 抵扣身份:
+ {{selectedBalanceInfo && selectedBalanceInfo.jieshao}}
+
+
+ 所需金额:
+ ¥{{selectedBalanceInfo && selectedBalanceInfo.xuyao}}
+
+
+ 实际扣除:
+ ¥{{selectedBalanceInfo && selectedBalanceInfo.xuyao}}
+
+ 确认后将从您的余额中扣除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 会员名称:
+ {{currentMemberDetail && currentMemberDetail.huiyuanming}}
+
+
+ 到期时间:
+ {{currentMemberDetail && currentMemberDetail.daoqi}}
+
+
+ 剩余天数:
+ {{formatRemainTime(currentMemberDetail && currentMemberDetail.daoqi)}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{loadingText}}
+ 正在连接支付系统...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/dashouchongzhi/dashouchongzhi.wxss b/miniprogram/pages/dashouchongzhi/dashouchongzhi.wxss
new file mode 100644
index 0000000..e2536bf
--- /dev/null
+++ b/miniprogram/pages/dashouchongzhi/dashouchongzhi.wxss
@@ -0,0 +1,1705 @@
+/* pages/dashou-chongzhi/index.wxss - 完整保留原有样式,仅删除导航栏相关,调整间距和字体 */
+
+/* ==================== 基础样式 ==================== */
+.page-container {
+ min-height: 100vh;
+ background: linear-gradient(135deg, #0a0e2a 0%, #1a1f4e 30%, #0a0e2a 100%);
+ position: relative;
+ overflow-x: hidden;
+ padding-bottom: 40rpx;
+ }
+
+ .page-container::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background:
+ radial-gradient(circle at 20% 30%, rgba(64, 156, 255, 0.1) 0%, transparent 50%),
+ radial-gradient(circle at 80% 70%, rgba(138, 43, 226, 0.08) 0%, transparent 50%),
+ linear-gradient(45deg, transparent 49%, rgba(64, 156, 255, 0.05) 50%, transparent 51%),
+ linear-gradient(-45deg, transparent 49%, rgba(64, 156, 255, 0.05) 50%, transparent 51%);
+ background-size: 100% 100%, 100% 100%, 60rpx 60rpx, 60rpx 60rpx;
+ z-index: 0;
+ pointer-events: none;
+ }
+
+ /* ❌ 删除 .custom-navbar 及其所有子样式 */
+
+ /* 顶部三区域网格 */
+ .tech-grid {
+ display: flex;
+ justify-content: space-between;
+ padding: 20rpx 30rpx;
+ gap: 20rpx;
+ position: relative;
+ z-index: 1;
+ }
+
+ /* 科技卡片 */
+ .tech-card {
+ flex: 1;
+ background: rgba(16, 20, 48, 0.8);
+ border-radius: 20rpx;
+ padding: 20rpx 25rpx;
+ position: relative;
+ overflow: hidden;
+ border: 1px solid rgba(64, 156, 255, 0.2);
+ box-shadow:
+ 0 10rpx 30rpx rgba(0, 10, 30, 0.5),
+ inset 0 1px 0 rgba(255, 255, 255, 0.1);
+ min-height: 260rpx;
+ display: flex;
+ flex-direction: column;
+ }
+
+ .tech-card::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ border-radius: 20rpx;
+ padding: 2rpx;
+ background: linear-gradient(135deg,
+ rgba(64, 156, 255, 0.4) 0%,
+ rgba(64, 156, 255, 0.1) 50%,
+ rgba(64, 156, 255, 0.4) 100%);
+ -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
+ mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
+ -webkit-mask-composite: xor;
+ mask-composite: exclude;
+ pointer-events: none;
+ }
+
+ /* 卡片头部 */
+ .card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20rpx;
+ padding: 0 5rpx;
+ }
+
+ .card-title {
+ display: flex;
+ align-items: center;
+ gap: 12rpx;
+ flex: 1;
+ white-space: nowrap;
+ }
+
+ .title-icon {
+ width: 32rpx;
+ height: 32rpx;
+ filter: drop-shadow(0 0 8rpx rgba(64, 156, 255, 0.6));
+ }
+
+ .title-text {
+ font-size: 26rpx;
+ font-weight: bold;
+ color: #e0f0ff;
+ text-shadow: 0 0 10rpx rgba(64, 156, 255, 0.5);
+ white-space: nowrap;
+ }
+
+ /* ===== 会员区域(优化后) ===== */
+ .huiyuan-scroll {
+ flex: 1;
+ margin-bottom: 15rpx;
+ overflow-y: scroll;
+ }
+
+ .huiyuan-item {
+ margin-bottom: 12rpx;
+ }
+
+ .huiyuan-tag {
+ background: linear-gradient(90deg,
+ rgba(64, 156, 255, 0.15),
+ rgba(64, 156, 255, 0.08));
+ border: 1px solid rgba(64, 156, 255, 0.3);
+ border-radius: 16rpx;
+ padding: 15rpx 20rpx;
+ position: relative;
+ overflow: hidden;
+ min-height: 70rpx;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ }
+
+ .tag-triangle {
+ position: absolute;
+ top: 10rpx;
+ left: 10rpx;
+ width: 0;
+ height: 0;
+ border-top: 10rpx solid #409cff;
+ border-right: 10rpx solid transparent;
+ }
+
+ .tag-name {
+ display: block;
+ font-size: 24rpx;
+ color: #fff;
+ font-weight: bold;
+ margin-bottom: 5rpx;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .tag-time {
+ display: block;
+ font-size: 20rpx;
+ color: #ffd166;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .tag-glow {
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg,
+ transparent,
+ rgba(255, 255, 255, 0.15),
+ transparent);
+ animation: techGlide 3s infinite;
+ }
+
+ .empty-huiyuan {
+ height: 100rpx;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .empty-icon {
+ width: 50rpx;
+ height: 50rpx;
+ opacity: 0.5;
+ margin-bottom: 8rpx;
+ }
+
+ .empty-text {
+ color: #6688cc;
+ font-size: 22rpx;
+ }
+
+ .card-footer {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-top: auto;
+ padding-top: 10rpx;
+ }
+
+ .footer-text {
+ font-size: 20rpx;
+ color: #a0c8ff;
+ }
+
+ .footer-line {
+ flex: 1;
+ height: 1px;
+ background: linear-gradient(90deg,
+ rgba(64, 156, 255, 0.3),
+ transparent);
+ margin-left: 15rpx;
+ }
+
+ /* 押金区域 */
+ .yajin-display {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ margin: 15rpx 0;
+ }
+
+ .amount-container {
+ display: flex;
+ align-items: baseline;
+ margin-bottom: 8rpx;
+ }
+
+ .amount-symbol {
+ font-size: 28rpx;
+ color: #ffd166;
+ margin-right: 6rpx;
+ }
+
+ .amount-number {
+ font-size: 56rpx;
+ font-weight: bold;
+ background: linear-gradient(90deg, #40a9ff, #36cfc9);
+ -webkit-background-clip: text;
+ background-clip: text;
+ color: transparent;
+ text-shadow: 0 0 20rpx rgba(64, 169, 255, 0.4);
+ }
+
+ .amount-unit {
+ font-size: 22rpx;
+ color: #a0c8ff;
+ }
+
+ .tech-line {
+ height: 2rpx;
+ background: linear-gradient(90deg,
+ transparent,
+ rgba(64, 156, 255, 0.5),
+ transparent);
+ margin: 15rpx 0;
+ }
+
+ /* 积分区域 */
+ .jifen-card .card-title {
+ flex-direction: row;
+ }
+
+ .score-badge {
+ background: rgba(64, 156, 255, 0.2);
+ border: 1px solid rgba(64, 156, 255, 0.5);
+ padding: 5rpx 14rpx;
+ border-radius: 20rpx;
+ font-size: 22rpx;
+ color: #409cff;
+ font-weight: bold;
+ }
+
+ .jifen-display {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ margin: 10rpx 0;
+ }
+
+ .circle-progress {
+ width: 140rpx;
+ height: 140rpx;
+ position: relative;
+ margin-bottom: 20rpx;
+ }
+
+ .circle-bg {
+ width: 100%;
+ height: 100%;
+ border: 6rpx solid rgba(255, 255, 255, 0.05);
+ border-radius: 50%;
+ box-sizing: border-box;
+ }
+
+ .circle-mask {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ border: 6rpx solid transparent;
+ border-top-color: #409cff;
+ border-right-color: #409cff;
+ border-radius: 50%;
+ box-sizing: border-box;
+ transition: transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
+ }
+
+ .circle-fill {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ border: 6rpx solid transparent;
+ border-top-color: #36cfc9;
+ border-right-color: #36cfc9;
+ border-radius: 50%;
+ box-sizing: border-box;
+ transition: transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
+ filter: drop-shadow(0 0 15rpx rgba(54, 207, 201, 0.6));
+ }
+
+ .circle-center {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ text-align: center;
+ }
+
+ .jifen-value {
+ display: block;
+ font-size: 42rpx;
+ font-weight: bold;
+ color: #fff;
+ text-shadow: 0 0 10rpx rgba(64, 156, 255, 0.8);
+ }
+
+ .jifen-label {
+ display: block;
+ font-size: 20rpx;
+ color: #a0c8ff;
+ margin-top: 5rpx;
+ }
+
+ .circle-dots {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ }
+
+ .circle-dot {
+ position: absolute;
+ top: 0;
+ left: 50%;
+ width: 5rpx;
+ height: 5rpx;
+ background: rgba(64, 156, 255, 0.6);
+ border-radius: 50%;
+ transform-origin: 0 70rpx;
+ }
+
+ .jifen-status {
+ text-align: center;
+ }
+
+ .status-text {
+ font-size: 22rpx;
+ color: #ff9d66;
+ }
+
+ .status-text.full {
+ color: #36cfc9;
+ }
+
+ /* 科技按钮 */
+ .action-container {
+ margin-top: auto;
+ }
+
+ .tech-btn {
+ height: 70rpx;
+ border-radius: 35rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ overflow: hidden;
+ font-size: 26rpx;
+ font-weight: bold;
+ color: #fff;
+ transition: all 0.3s ease;
+ line-height: 1;
+ }
+
+ .tech-btn:active {
+ transform: scale(0.98);
+ }
+
+ .btn-bg {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ border-radius: 35rpx;
+ }
+
+ .yajin-btn .btn-bg {
+ background: linear-gradient(135deg, #1e4b8f, #409cff);
+ }
+
+ .jifen-btn .btn-bg {
+ background: linear-gradient(135deg, #0f2c5c, #36cfc9);
+ }
+
+ .jifen-btn[data-disabled="true"] .btn-bg {
+ background: linear-gradient(135deg, #333, #666);
+ opacity: 0.7;
+ }
+
+ .btn-text {
+ position: relative;
+ z-index: 1;
+ display: flex;
+ align-items: center;
+ gap: 8rpx;
+ text-align: center;
+ width: 100%;
+ justify-content: center;
+ height: 100%;
+ }
+
+ .btn-arrow {
+ font-size: 22rpx;
+ margin-left: 6rpx;
+ color: #fff;
+ }
+
+ .btn-energy {
+ position: absolute;
+ top: 50%;
+ right: 25rpx;
+ transform: translateY(-50%);
+ display: flex;
+ gap: 4rpx;
+ }
+
+ .btn-energy .energy-dot {
+ width: 6rpx;
+ height: 6rpx;
+ background: #ffd166;
+ border-radius: 50%;
+ animation: energyPulse 1s infinite;
+ }
+
+ .btn-energy .energy-dot:nth-child(2) { animation-delay: 0.2s; }
+ .btn-energy .energy-dot:nth-child(3) { animation-delay: 0.4s; }
+
+ .btn-glow {
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg,
+ transparent,
+ rgba(255, 255, 255, 0.3),
+ transparent);
+ animation: techShine 2s infinite;
+ }
+
+ /* VIP会员区域 */
+ .vip-section {
+ margin: 30rpx 30rpx; /* 🔥 移除导航栏后,无需额外 margin-top */
+ background: rgba(16, 20, 48, 0.8);
+ border-radius: 30rpx;
+ padding: 30rpx;
+ border: 1px solid rgba(64, 156, 255, 0.2);
+ box-shadow:
+ 0 20rpx 40rpx rgba(0, 10, 30, 0.6),
+ inset 0 1px 0 rgba(255, 255, 255, 0.1);
+ position: relative;
+ z-index: 1;
+ }
+
+ .section-header {
+ text-align: center;
+ margin-bottom: 40rpx;
+ }
+
+ .header-decoration {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 15rpx;
+ margin-bottom: 12rpx;
+ }
+
+ .dec-line {
+ width: 50rpx;
+ height: 2rpx;
+ background: linear-gradient(90deg,
+ rgba(64, 156, 255, 0.5),
+ rgba(64, 156, 255, 0.1));
+ }
+
+ .dec-diamond {
+ width: 14rpx;
+ height: 14rpx;
+ background: #409cff;
+ transform: rotate(45deg);
+ box-shadow: 0 0 10rpx rgba(64, 156, 255, 0.6);
+ }
+
+ .dec-text {
+ font-size: 32rpx;
+ font-weight: bold;
+ background: linear-gradient(90deg, #40a9ff, #36cfc9);
+ -webkit-background-clip: text;
+ background-clip: text;
+ color: transparent;
+ text-shadow: 0 0 20rpx rgba(64, 169, 255, 0.4);
+ }
+
+ .section-subtitle {
+ font-size: 22rpx;
+ color: #a0c8ff;
+ letter-spacing: 4rpx;
+ }
+
+ /* 会员卡片滑动区域 */
+ .vip-swiper {
+ height: 350rpx;
+ margin-bottom: 40rpx;
+ }
+
+ .vip-card {
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
+ position: relative;
+ }
+
+ .vip-card.active {
+ transform: scale(1.08);
+ z-index: 10;
+ }
+
+ .vip-card:not(.active) {
+ opacity: 0.6;
+ transform: scale(0.85);
+ }
+
+ /* 机甲风格卡片边框 */
+ .card-frame {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ }
+
+ .frame-corner {
+ position: absolute;
+ width: 40rpx;
+ height: 40rpx;
+ }
+
+ .frame-corner.tl {
+ top: 0;
+ left: 0;
+ border-top: 4rpx solid #409cff;
+ border-left: 4rpx solid #409cff;
+ border-top-left-radius: 20rpx;
+ }
+
+ .frame-corner.tr {
+ top: 0;
+ right: 0;
+ border-top: 4rpx solid #409cff;
+ border-right: 4rpx solid #409cff;
+ border-top-right-radius: 20rpx;
+ }
+
+ .frame-corner.bl {
+ bottom: 0;
+ left: 0;
+ border-bottom: 4rpx solid #409cff;
+ border-left: 4rpx solid #409cff;
+ border-bottom-left-radius: 20rpx;
+ }
+
+ .frame-corner.br {
+ bottom: 0;
+ right: 0;
+ border-bottom: 4rpx solid #409cff;
+ border-right: 4rpx solid #409cff;
+ border-bottom-right-radius: 20rpx;
+ }
+
+ .frame-line {
+ position: absolute;
+ background: rgba(64, 156, 255, 0.3);
+ }
+
+ .frame-line.top {
+ top: 0;
+ left: 40rpx;
+ right: 40rpx;
+ height: 2rpx;
+ }
+
+ .frame-line.right {
+ right: 0;
+ top: 40rpx;
+ bottom: 40rpx;
+ width: 2rpx;
+ }
+
+ .frame-line.bottom {
+ bottom: 0;
+ left: 40rpx;
+ right: 40rpx;
+ height: 2rpx;
+ }
+
+ .frame-line.left {
+ left: 0;
+ top: 40rpx;
+ bottom: 40rpx;
+ width: 2rpx;
+ }
+
+ .vip-card.active .frame-corner {
+ border-color: #36cfc9;
+ }
+
+ .vip-card.active .frame-line {
+ background: rgba(54, 207, 201, 0.5);
+ }
+
+ .card-content {
+ position: relative;
+ z-index: 1;
+ text-align: center;
+ padding: 30rpx 25rpx;
+ width: 100%;
+ }
+
+ .vip-badge {
+ position: absolute;
+ top: 25rpx;
+ right: 25rpx;
+ background: linear-gradient(135deg, #ff6b9d, #ff8e53);
+ padding: 5rpx 14rpx;
+ border-radius: 20rpx;
+ font-size: 20rpx;
+ color: #fff;
+ font-weight: bold;
+ box-shadow: 0 0 15rpx rgba(255, 107, 157, 0.4);
+ z-index: 20;
+ }
+
+ .vip-title {
+ margin-bottom: 25rpx;
+ }
+
+ .title-text {
+ display: block;
+ font-size: 32rpx;
+ font-weight: bold;
+ color: #fff;
+ margin-bottom: 8rpx;
+ text-shadow: 0 0 10rpx rgba(64, 156, 255, 0.8);
+ }
+
+ .title-underline {
+ width: 50rpx;
+ height: 4rpx;
+ background: linear-gradient(90deg, #409cff, #36cfc9);
+ margin: 0 auto;
+ border-radius: 2rpx;
+ }
+
+ .vip-price {
+ margin-bottom: 30rpx;
+ }
+
+ .price-symbol {
+ font-size: 26rpx;
+ color: #ffd166;
+ margin-right: 5rpx;
+ }
+
+ .price-number {
+ font-size: 50rpx;
+ font-weight: bold;
+ color: #ffd166;
+ text-shadow: 0 0 15rpx rgba(255, 209, 102, 0.6);
+ }
+
+ .price-unit {
+ font-size: 22rpx;
+ color: #a0c8ff;
+ margin-left: 6rpx;
+ }
+
+ .vip-features {
+ margin-top: 25rpx;
+ }
+
+ .feature-item {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8rpx;
+ margin-bottom: 12rpx;
+ }
+
+ .feature-icon {
+ width: 20rpx;
+ height: 20rpx;
+ }
+
+ .feature-text {
+ font-size: 22rpx;
+ color: #e0f0ff;
+ }
+
+ .card-indicator {
+ position: absolute;
+ bottom: -35rpx;
+ left: 50%;
+ transform: translateX(-50%);
+ }
+
+ .indicator-triangle {
+ width: 0;
+ height: 0;
+ border-left: 15rpx solid transparent;
+ border-right: 15rpx solid transparent;
+ border-top: 25rpx solid #409cff;
+ filter: drop-shadow(0 5rpx 5rpx rgba(0, 0, 0, 0.3));
+ }
+
+ .indicator-glow {
+ position: absolute;
+ top: -25rpx;
+ left: 50%;
+ transform: translateX(-50%);
+ width: 30rpx;
+ height: 30rpx;
+ background: radial-gradient(circle, rgba(64, 156, 255, 0.6), transparent 70%);
+ border-radius: 50%;
+ animation: pulseGlow 2s infinite;
+ }
+
+ /* 会员详细介绍 */
+ .vip-detail {
+ background: rgba(10, 14, 42, 0.9);
+ border-radius: 20rpx;
+ padding: 25rpx;
+ border: 1px solid rgba(64, 156, 255, 0.3);
+ }
+
+ .detail-header {
+ display: flex;
+ align-items: center;
+ gap: 15rpx;
+ margin-bottom: 25rpx;
+ }
+
+ .detail-icon {
+ width: 36rpx;
+ height: 36rpx;
+ filter: drop-shadow(0 0 8rpx rgba(64, 156, 255, 0.6));
+ }
+
+ .detail-title .title-text {
+ font-size: 28rpx;
+ color: #e0f0ff;
+ text-shadow: 0 0 10rpx rgba(64, 156, 255, 0.5);
+ }
+
+ .detail-tech {
+ flex: 1;
+ height: 2rpx;
+ background: linear-gradient(90deg, rgba(64, 156, 255, 0.5), transparent);
+ }
+
+ .detail-content {
+ margin-bottom: 30rpx;
+ }
+
+ .detail-scroll {
+ max-height: 180rpx;
+ }
+
+ .detail-text {
+ font-size: 24rpx;
+ color: #b8d4ff;
+ line-height: 1.5;
+ margin-bottom: 25rpx;
+ }
+
+ .detail-rules {
+ margin-top: 25rpx;
+ }
+
+ .rule-item {
+ display: flex;
+ align-items: flex-start;
+ gap: 15rpx;
+ margin-bottom: 15rpx;
+ }
+
+ .rule-number {
+ width: 32rpx;
+ height: 32rpx;
+ background: rgba(64, 156, 255, 0.2);
+ border: 1px solid rgba(64, 156, 255, 0.5);
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 20rpx;
+ color: #409cff;
+ flex-shrink: 0;
+ font-weight: bold;
+ }
+
+ .rule-text {
+ font-size: 22rpx;
+ color: #a0c8ff;
+ line-height: 1.4;
+ flex: 1;
+ }
+
+ /* 购买区域 */
+ .buy-section {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding-top: 25rpx;
+ border-top: 1px solid rgba(64, 156, 255, 0.2);
+ }
+
+ .buy-info {
+ display: flex;
+ flex-direction: column;
+ }
+
+ .price-display {
+ display: flex;
+ align-items: baseline;
+ gap: 8rpx;
+ margin-bottom: 8rpx;
+ }
+
+ .price-label {
+ font-size: 24rpx;
+ color: #a0c8ff;
+ }
+
+ .price-value {
+ font-size: 32rpx;
+ font-weight: bold;
+ color: #ffd166;
+ text-shadow: 0 0 10rpx rgba(255, 209, 102, 0.4);
+ }
+
+ .buy-desc {
+ font-size: 20rpx;
+ color: #6688cc;
+ }
+
+ /* 🔥 新增:到期日期文字加大 */
+ .expire-date {
+ font-size: 26rpx;
+ color: #ffd166;
+ font-weight: bold;
+ }
+
+ /* 购买按钮 */
+ .tech-buy-btn {
+ width: 220rpx;
+ height: 80rpx;
+ border-radius: 40rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ overflow: hidden;
+ font-size: 28rpx;
+ font-weight: bold;
+ color: #fff;
+ transition: all 0.3s ease;
+ line-height: 1;
+ }
+
+ .tech-buy-btn:active {
+ transform: scale(0.98);
+ }
+
+ .buy-btn-bg {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ border-radius: 40rpx;
+ background: linear-gradient(135deg, #1e4b8f, #409cff);
+ }
+
+ .tech-buy-btn.renew .buy-btn-bg {
+ background: linear-gradient(135deg, #0f2c5c, #36cfc9);
+ }
+
+ .buy-btn-glow {
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg,
+ transparent,
+ rgba(255, 255, 255, 0.3),
+ transparent);
+ animation: techShine 2s infinite;
+ }
+
+ .buy-btn-text {
+ position: relative;
+ z-index: 1;
+ display: flex;
+ align-items: center;
+ gap: 10rpx;
+ text-align: center;
+ width: 100%;
+ justify-content: center;
+ height: 100%;
+ }
+
+ .buy-btn-energy {
+ position: absolute;
+ top: 50%;
+ right: 25rpx;
+ transform: translateY(-50%);
+ display: flex;
+ flex-direction: column;
+ gap: 3rpx;
+ }
+
+ .buy-btn-energy .energy-dot {
+ width: 5rpx;
+ height: 5rpx;
+ background: #ffd166;
+ border-radius: 50%;
+ animation: energyPulse 1s infinite;
+ }
+
+ .buy-btn-energy .energy-dot:nth-child(2) { animation-delay: 0.2s; }
+ .buy-btn-energy .energy-dot:nth-child(3) { animation-delay: 0.4s; }
+
+ /* ==================== 弹窗样式(保留原有,删除 tip-content 相关) ==================== */
+ .tech-modal {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 9999;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .modal-backdrop {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 5, 20, 0.85);
+ backdrop-filter: blur(10rpx);
+ }
+
+ .modal-container {
+ width: 650rpx;
+ background: rgba(16, 20, 48, 0.95);
+ border-radius: 30rpx;
+ border: 2px solid rgba(64, 156, 255, 0.4);
+ box-shadow:
+ 0 30rpx 80rpx rgba(0, 10, 30, 0.8),
+ 0 0 0 1px rgba(64, 156, 255, 0.2) inset;
+ position: relative;
+ z-index: 1;
+ overflow: hidden;
+ }
+
+ .modal-header {
+ padding: 40rpx 40rpx 30rpx;
+ text-align: center;
+ position: relative;
+ }
+
+ .modal-corner {
+ position: absolute;
+ width: 30rpx;
+ height: 30rpx;
+ }
+
+ .modal-corner.tl {
+ top: 10rpx;
+ left: 10rpx;
+ border-top: 3rpx solid #409cff;
+ border-left: 3rpx solid #409cff;
+ border-top-left-radius: 10rpx;
+ }
+
+ .modal-corner.tr {
+ top: 10rpx;
+ right: 10rpx;
+ border-top: 3rpx solid #409cff;
+ border-right: 3rpx solid #409cff;
+ border-top-right-radius: 10rpx;
+ }
+
+ .modal-corner.bl {
+ bottom: 10rpx;
+ left: 10rpx;
+ border-bottom: 3rpx solid #409cff;
+ border-left: 3rpx solid #409cff;
+ border-bottom-left-radius: 10rpx;
+ }
+
+ .modal-corner.br {
+ bottom: 10rpx;
+ right: 10rpx;
+ border-bottom: 3rpx solid #409cff;
+ border-right: 3rpx solid #409cff;
+ border-bottom-left-radius: 10rpx;
+ }
+
+ .modal-title {
+ display: block;
+ font-size: 36rpx;
+ font-weight: bold;
+ background: linear-gradient(90deg, #40a9ff, #36cfc9);
+ -webkit-background-clip: text;
+ background-clip: text;
+ color: transparent;
+ margin-bottom: 10rpx;
+ text-shadow: 0 0 20rpx rgba(64, 169, 255, 0.4);
+ }
+
+ .modal-subtitle {
+ display: block;
+ font-size: 22rpx;
+ color: #a0c8ff;
+ letter-spacing: 4rpx;
+ }
+
+ .modal-close {
+ position: absolute;
+ top: 30rpx;
+ right: 30rpx;
+ width: 60rpx;
+ height: 60rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 32rpx;
+ color: #6688cc;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ }
+
+ .modal-close:active {
+ transform: scale(0.9);
+ }
+
+ .modal-body {
+ padding: 0 40rpx 40rpx;
+ }
+
+ /* 输入框区域(押金弹窗) */
+ .input-section {
+ margin-bottom: 40rpx;
+ }
+
+ .input-label {
+ display: flex;
+ align-items: center;
+ gap: 15rpx;
+ margin-bottom: 25rpx;
+ }
+
+ .label-icon {
+ width: 32rpx;
+ height: 32rpx;
+ filter: drop-shadow(0 0 8rpx rgba(64, 156, 255, 0.6));
+ }
+
+ .label-text {
+ font-size: 28rpx;
+ color: #e0f0ff;
+ font-weight: bold;
+ }
+
+ .input-container {
+ display: flex;
+ align-items: center;
+ background: rgba(10, 14, 42, 0.8);
+ border-radius: 20rpx;
+ border: 1px solid rgba(64, 156, 255, 0.5);
+ padding: 25rpx 30rpx;
+ margin-bottom: 20rpx;
+ position: relative;
+ }
+
+ .input-prefix {
+ font-size: 40rpx;
+ color: #ffd166;
+ margin-right: 20rpx;
+ font-weight: bold;
+ }
+
+ .yajin-input {
+ flex: 1;
+ font-size: 40rpx;
+ color: #fff;
+ min-height: 60rpx;
+ }
+
+ .input-placeholder {
+ color: #6688cc;
+ font-size: 28rpx;
+ }
+
+ .input-underline {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: 2rpx;
+ background: linear-gradient(90deg, #409cff, #36cfc9);
+ transform: scaleX(0);
+ transition: transform 0.3s ease;
+ }
+
+ .yajin-input:focus + .input-underline {
+ transform: scaleX(1);
+ }
+
+ .input-tip {
+ display: block;
+ font-size: 22rpx;
+ color: #a0c8ff;
+ text-align: right;
+ }
+
+ .quick-section {
+ margin-top: 40rpx;
+ }
+
+ .quick-title {
+ display: block;
+ font-size: 26rpx;
+ color: #e0f0ff;
+ margin-bottom: 25rpx;
+ font-weight: bold;
+ }
+
+ .quick-buttons {
+ display: flex;
+ gap: 20rpx;
+ }
+
+ .quick-btn {
+ flex: 1;
+ height: 80rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: rgba(10, 14, 42, 0.8);
+ border: 1px solid rgba(64, 156, 255, 0.3);
+ border-radius: 15rpx;
+ font-size: 26rpx;
+ color: #a0c8ff;
+ transition: all 0.3s ease;
+ }
+
+ .quick-btn.active {
+ background: linear-gradient(135deg, #1e4b8f, #409cff);
+ color: #fff;
+ border-color: rgba(64, 156, 255, 0.8);
+ box-shadow: 0 0 20rpx rgba(64, 156, 255, 0.4);
+ transform: translateY(-2rpx);
+ }
+
+ .quick-text {
+ font-weight: bold;
+ }
+
+ .modal-footer {
+ display: flex;
+ border-top: 1px solid rgba(64, 156, 255, 0.3);
+ }
+
+ .modal-btn {
+ flex: 1;
+ height: 100rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 30rpx;
+ font-weight: bold;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ position: relative;
+ overflow: hidden;
+ }
+
+ .cancel-btn {
+ color: #a0c8ff;
+ background: rgba(10, 14, 42, 0.5);
+ }
+
+ .cancel-btn:active {
+ background: rgba(10, 14, 42, 0.8);
+ }
+
+ .modal-divider {
+ width: 1px;
+ background: rgba(64, 156, 255, 0.3);
+ }
+
+ .confirm-btn {
+ background: linear-gradient(135deg, #1e4b8f, #409cff);
+ color: #fff;
+ }
+
+ .confirm-btn:active {
+ background: linear-gradient(135deg, #0f2c5c, #3080e0);
+ }
+
+ .btn-sparkle {
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg,
+ transparent,
+ rgba(255, 255, 255, 0.2),
+ transparent);
+ animation: techShine 2s infinite;
+ }
+
+ /* 支付方式选择弹窗(小尺寸) */
+ .modal-container.small {
+ width: 550rpx;
+ }
+
+ .pay-method-list {
+ padding: 20rpx 0;
+ }
+
+ .pay-method-item {
+ display: flex;
+ align-items: center;
+ padding: 30rpx 20rpx;
+ background: rgba(10, 14, 42, 0.5);
+ border: 1px solid rgba(64, 156, 255, 0.2);
+ border-radius: 20rpx;
+ margin-bottom: 20rpx;
+ transition: all 0.2s;
+ }
+
+ .pay-method-item:active {
+ background: rgba(64, 156, 255, 0.1);
+ border-color: rgba(64, 156, 255, 0.5);
+ transform: scale(0.98);
+ }
+
+ .method-icon {
+ width: 60rpx;
+ height: 60rpx;
+ margin-right: 20rpx;
+ }
+
+ .method-name {
+ flex: 1;
+ font-size: 30rpx;
+ color: #e0f0ff;
+ font-weight: bold;
+ }
+
+ .method-desc {
+ font-size: 24rpx;
+ color: #a0c8ff;
+ margin-right: 10rpx;
+ }
+
+ /* 余额抵扣身份列表 */
+ .balance-list {
+ max-height: 600rpx;
+ overflow-y: auto;
+ }
+
+ .balance-item {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 30rpx 20rpx;
+ background: rgba(10, 14, 42, 0.5);
+ border: 1px solid rgba(64, 156, 255, 0.2);
+ border-radius: 20rpx;
+ margin-bottom: 20rpx;
+ transition: all 0.2s;
+ }
+
+ .balance-item:active {
+ background: rgba(64, 156, 255, 0.1);
+ border-color: rgba(64, 156, 255, 0.5);
+ transform: scale(0.98);
+ }
+
+ .balance-info {
+ display: flex;
+ flex-direction: column;
+ }
+
+ .balance-name {
+ font-size: 28rpx;
+ color: #e0f0ff;
+ font-weight: bold;
+ margin-bottom: 8rpx;
+ }
+
+ .balance-amount {
+ font-size: 24rpx;
+ color: #ffd166;
+ }
+
+ .balance-arrow {
+ font-size: 32rpx;
+ color: #409cff;
+ transform: scaleX(1.2);
+ }
+
+ /* 会员详情弹窗 */
+ .member-detail {
+ padding: 30rpx 20rpx;
+ }
+
+ .detail-row {
+ display: flex;
+ align-items: baseline;
+ margin-bottom: 25rpx;
+ padding-bottom: 15rpx;
+ border-bottom: 1px solid rgba(64, 156, 255, 0.2);
+ }
+
+ .detail-row:last-child {
+ border-bottom: none;
+ margin-bottom: 0;
+ padding-bottom: 0;
+ }
+
+ .detail-label {
+ width: 150rpx;
+ font-size: 28rpx;
+ color: #a0c8ff;
+ }
+
+ .detail-value {
+ flex: 1;
+ font-size: 30rpx;
+ color: #ffd166;
+ font-weight: bold;
+ }
+
+ /* ❌ 删除 .tip-content 等相关样式 */
+
+ /* ==================== 加载动画 ==================== */
+ .tech-loading {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 10000;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .loading-backdrop {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 5, 20, 0.9);
+ backdrop-filter: blur(20rpx);
+ }
+
+ .loading-content {
+ position: relative;
+ z-index: 1;
+ text-align: center;
+ }
+
+ .loading-spinner {
+ width: 150rpx;
+ height: 150rpx;
+ position: relative;
+ margin: 0 auto 40rpx;
+ }
+
+ .spinner-ring {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ border: 8rpx solid transparent;
+ border-radius: 50%;
+ animation: spinRing 2s linear infinite;
+ }
+
+ .spinner-ring.ring-1 {
+ border-top-color: #409cff;
+ animation-delay: 0s;
+ }
+
+ .spinner-ring.ring-2 {
+ border-right-color: #36cfc9;
+ animation-delay: 0.5s;
+ }
+
+ .spinner-ring.ring-3 {
+ border-bottom-color: #ffd166;
+ animation-delay: 1s;
+ }
+
+ .spinner-core {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: 40rpx;
+ height: 40rpx;
+ background: radial-gradient(circle, #409cff, #1e4b8f);
+ border-radius: 50%;
+ box-shadow: 0 0 30rpx rgba(64, 156, 255, 0.8);
+ }
+
+ .spinner-energy {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: 60rpx;
+ height: 60rpx;
+ border: 4rpx solid rgba(64, 156, 255, 0.3);
+ border-radius: 50%;
+ animation: pulseEnergy 1.5s ease-in-out infinite;
+ }
+
+ .loading-text {
+ display: block;
+ font-size: 32rpx;
+ color: #fff;
+ margin-bottom: 15rpx;
+ font-weight: bold;
+ text-shadow: 0 0 10rpx rgba(64, 156, 255, 0.8);
+ }
+
+ .loading-subtext {
+ display: block;
+ font-size: 22rpx;
+ color: #a0c8ff;
+ }
+
+ /* ==================== 底部装饰 ==================== */
+ .tech-footer {
+ text-align: center;
+ margin-top: 40rpx;
+ padding: 0 30rpx;
+ }
+
+ .footer-line {
+ height: 1px;
+ background: linear-gradient(90deg,
+ transparent,
+ rgba(64, 156, 255, 0.3),
+ transparent);
+ margin-bottom: 20rpx;
+ }
+
+ .footer-text {
+ font-size: 20rpx;
+ color: #6688cc;
+ letter-spacing: 4rpx;
+ }
+
+ /* ==================== 动画定义 ==================== */
+ @keyframes techGlide {
+ 0% { left: -100%; }
+ 50% { left: 100%; }
+ 100% { left: 100%; }
+ }
+
+ @keyframes techShine {
+ 0% { left: -100%; }
+ 20% { left: 100%; }
+ 100% { left: 100%; }
+ }
+
+ @keyframes spinRing {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+ }
+
+ @keyframes pulseEnergy {
+ 0% {
+ width: 60rpx;
+ height: 60rpx;
+ opacity: 0.5;
+ }
+ 50% {
+ width: 80rpx;
+ height: 80rpx;
+ opacity: 0.8;
+ }
+ 100% {
+ width: 60rpx;
+ height: 60rpx;
+ opacity: 0.5;
+ }
+ }
+
+ @keyframes dotPulse {
+ 0%, 100% { opacity: 0.3; }
+ 50% { opacity: 1; }
+ }
+
+ @keyframes energyPulse {
+ 0%, 100% { opacity: 0.3; transform: scale(0.8); }
+ 50% { opacity: 1; transform: scale(1.2); }
+ }
+
+ @keyframes pulseGlow {
+ 0%, 100% { opacity: 0.5; }
+ 50% { opacity: 1; }
+ }
+
+ /* ==================== 响应式调整 ==================== */
+ @media screen and (max-width: 750rpx) {
+ .tech-grid {
+ flex-direction: column;
+ gap: 20rpx;
+ }
+
+ .tech-card {
+ width: 100%;
+ }
+
+ .vip-swiper {
+ height: 320rpx;
+ }
+
+ .modal-container {
+ width: 90%;
+ margin: 0 5%;
+ }
+
+ .quick-buttons {
+ flex-wrap: wrap;
+ }
+
+ .quick-btn {
+ flex: 0 0 calc(50% - 10rpx);
+ }
+ }
+
+ /* ==================== 按钮垂直居中最终修复(覆盖) ==================== */
+ .tech-btn {
+ height: 70rpx;
+ border-radius: 35rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ overflow: hidden;
+ font-size: 26rpx;
+ font-weight: bold;
+ color: #fff;
+ transition: all 0.3s ease;
+ }
+
+ .tech-btn .btn-text {
+ position: relative;
+ z-index: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ height: 100%;
+ padding: 0;
+ margin: 0;
+ }
+
+ .yajin-btn .btn-arrow {
+ display: none !important;
+ }
+
+ .tech-buy-btn {
+ width: 220rpx;
+ height: 80rpx;
+ border-radius: 40rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ overflow: hidden;
+ font-size: 28rpx;
+ font-weight: bold;
+ color: #fff;
+ transition: all 0.3s ease;
+ }
+
+ .tech-buy-btn .buy-btn-text {
+ position: relative;
+ z-index: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ height: 100%;
+ padding: 0;
+ margin: 0;
+ }
+
+ .tech-btn .btn-bg,
+ .tech-btn .btn-glow,
+ .tech-buy-btn .buy-btn-bg,
+ .tech-buy-btn .buy-btn-glow {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 0;
+ pointer-events: none;
+ }
+
+ .buy-btn-energy {
+ position: absolute;
+ right: 20rpx;
+ top: 50%;
+ transform: translateY(-50%);
+ z-index: 2;
+ }
+
+ .btn-text, .buy-btn-text {
+ display: flex !important;
+ align-items: center !important;
+ justify-content: center !important;
+ height: 100% !important;
+ }
+
+ .tech-buy-btn .buy-btn-text {
+ display: flex !important;
+ align-items: center !important;
+ justify-content: center !important;
+ width: 100% !important;
+ height: 100% !important;
+ line-height: 0.4 !important;
+ }
+
+ .tech-buy-btn {
+ display: flex !important;
+ align-items: center !important;
+ justify-content: center !important;
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/dashoudingdan/dashoudingdan.js b/miniprogram/pages/dashoudingdan/dashoudingdan.js
new file mode 100644
index 0000000..a4d0866
--- /dev/null
+++ b/miniprogram/pages/dashoudingdan/dashoudingdan.js
@@ -0,0 +1,344 @@
+const app = getApp()
+import request from '../../utils/request.js'
+
+Page({
+ onShow() {
+ // 原有代码...
+
+ // 🆕 注册通知组件
+ this.registerNotificationComponent();
+ },
+
+ // 🆕 新增:注册通知组件
+ 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()
+ };
+ }
+ },
+
+
+
+
+ data: {
+ // 打手订单状态tab配置(去掉已下单,其他保留)
+ dsDingdanZhuangtaiTabs: [
+ { name: '进行中', key: 'jinxingzhong', zhuangtaiList: [2] },
+ { name: '退款中', key: 'tuikuanzhong', zhuangtaiList: [4] },
+ { name: '已退款', key: 'yituikuan', zhuangtaiList: [5] },
+ { name: '退款失败', key: 'tuikuanshibai', zhuangtaiList: [6] },
+ { name: '已完成', key: 'yiwancheng', zhuangtaiList: [3] },
+ { name: '结算中', key: 'jiesuanzhong', zhuangtaiList: [8] }
+ ],
+
+ // 当前选中的tab索引(默认0=进行中)
+ xuanzhongTabIndex: 0,
+ // 当前选中的tab的key
+ currentTabKey: 'jinxingzhong',
+
+ // 防抖标志:是否允许切换tab
+ allowSwitchTab: true,
+ // 防抖时间(毫秒)
+ switchDebounceTime: 2000,
+
+ // 每个tab对应的订单数据
+ dsDingdanShuju: {
+ 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 }
+ },
+
+ // 打手订单图片URL(从缓存或全局变量获取后拼接)
+ dsDingdanTupianUrl: '',
+
+ // 页面加载状态
+ isLoading: true,
+
+ // 是否显示加载更多
+ xianshiJiazaigengduo: false
+ },
+
+ onLoad(options) {
+ // 设置页面标题
+ wx.setNavigationBarTitle({
+ title: '我的接单'
+ })
+
+ // 初始化:获取图片URL并拼接
+ this.getAndSetTupianUrl()
+
+ // 加载初始数据(默认进行中)
+ this.setData({
+ isLoading: false
+ })
+
+ this.jiazaiDsDingdanShuju(0, false)
+ },
+
+ // 获取并设置图片URL
+ getAndSetTupianUrl() {
+ const app = getApp()
+
+ // 1. 先从缓存获取touxiang
+ const cacheTouxiang = wx.getStorageSync('touxiang') || ''
+
+ // 2. 如果缓存没有,从全局变量获取morentouxiang
+ let tupianPath = cacheTouxiang || app.globalData.morentouxiang || ''
+
+ // 3. 拼接完整的图片URL
+ const ossImageUrl = app.globalData.ossImageUrl || ''
+ let fullTupianUrl = ''
+
+ if (tupianPath && ossImageUrl) {
+ // 确保路径不以斜杠开头(防止双斜杠)
+ if (tupianPath.startsWith('/')) {
+ tupianPath = tupianPath.substring(1)
+ }
+ fullTupianUrl = ossImageUrl + tupianPath
+ }
+
+ this.setData({
+ dsDingdanTupianUrl: fullTupianUrl
+ })
+ },
+
+ // 切换订单tab(增加防抖)
+ switchDsDingdanTab(e) {
+ const tabIndex = e.currentTarget.dataset.index
+
+ // 如果当前正在加载,不允许切换
+ const currentTabKey = this.data.currentTabKey
+ const currentTabData = this.data.dsDingdanShuju[currentTabKey]
+ if (currentTabData.isLoading) {
+ console.log('当前正在加载,禁止切换')
+ return
+ }
+
+ // 防抖:检查是否允许切换
+ if (!this.data.allowSwitchTab) {
+
+ wx.showToast({
+ title: '切换太频繁,请稍后',
+ icon: 'none',
+ duration: 1000
+ })
+ return
+ }
+
+ // 如果点击的是当前选中的tab,不处理
+ if (tabIndex === this.data.xuanzhongTabIndex) {
+ return
+ }
+
+ const tabKey = this.data.dsDingdanZhuangtaiTabs[tabIndex].key
+
+ // 设置防抖标志,2秒内不允许再次切换
+ this.setData({
+ allowSwitchTab: false
+ })
+
+ // 设置定时器,2秒后恢复允许切换
+ setTimeout(() => {
+ this.setData({
+ allowSwitchTab: true
+ })
+ }, this.data.switchDebounceTime)
+
+ // 更新选中的tab
+ this.setData({
+ xuanzhongTabIndex: tabIndex,
+ currentTabKey: tabKey
+ })
+
+ // 加载对应tab的数据
+ this.jiazaiDsDingdanShuju(tabIndex, false)
+ },
+
+ // 加载打手订单数据
+ async jiazaiDsDingdanShuju(tabIndex, isLoadMore = false) {
+ const tab = this.data.dsDingdanZhuangtaiTabs[tabIndex]
+ const tabKey = tab.key
+
+ const tabData = this.data.dsDingdanShuju[tabKey]
+
+ const page = isLoadMore ? tabData.page + 1 : 1
+
+ // 检查是否正在加载
+ if (tabData.isLoading) {
+
+ return
+ }
+
+ // 如果不是加载更多,清空已有数据
+ if (!isLoadMore) {
+ this.setData({
+ [`dsDingdanShuju.${tabKey}.list`]: [],
+ [`dsDingdanShuju.${tabKey}.page`]: 1
+ })
+ }
+
+ // 设置加载状态
+ this.setData({
+ [`dsDingdanShuju.${tabKey}.isLoading`]: true
+ })
+
+ // 显示加载提示(非加载更多时显示)
+ if (!isLoadMore) {
+ wx.showLoading({
+ title: '加载订单...',
+ mask: true
+ })
+ }
+
+ try {
+ // 准备请求数据
+ const requestData = {
+ zhuangtai_list: tab.zhuangtaiList,
+ page: page,
+ page_size: 10
+ }
+
+
+
+ const res = await request({
+ url: '/dingdan/dshqdingdan',
+ method: 'POST',
+ data: requestData,
+ header: {
+ 'content-type': 'application/json'
+ }
+ })
+
+ // 隐藏加载提示
+ if (!isLoadMore) {
+ wx.hideLoading()
+ }
+
+ // 处理返回数据
+ if (res && res.data.code === 0) {
+ const newDingdanList = res.data.data.list || []
+ const hasMore = res.data.data.has_more || false
+
+
+
+ // 为每个订单添加状态中文描述和统一的图片URL
+ const processedList = newDingdanList.map(item => {
+ const zhuangtaiZh = this.getZhuangtaiZhongwen(item.zhuangtai)
+
+ return {
+ ...item,
+ tupian: this.data.dsDingdanTupianUrl, // 使用统一的图片URL
+ zhuangtaiZh: zhuangtaiZh
+ }
+ })
+
+ const currentList = this.data.dsDingdanShuju[tabKey].list
+ const updatedList = isLoadMore
+ ? [...currentList, ...processedList]
+ : processedList
+
+ // 更新数据
+ this.setData({
+ [`dsDingdanShuju.${tabKey}.list`]: updatedList,
+ [`dsDingdanShuju.${tabKey}.page`]: page,
+ [`dsDingdanShuju.${tabKey}.hasMore`]: hasMore,
+ [`dsDingdanShuju.${tabKey}.isLoading`]: false,
+ xianshiJiazaigengduo: hasMore
+ })
+
+ } else {
+ // 请求失败处理
+ const errorMsg = res ? (res.data.msg || '加载失败') : '请求失败'
+
+ if (!isLoadMore) {
+ wx.showToast({
+ title: errorMsg,
+ icon: 'none',
+ duration: 2000
+ })
+ }
+
+ this.setData({
+ [`dsDingdanShuju.${tabKey}.isLoading`]: false
+ })
+ }
+
+ } catch (error) {
+
+
+ if (!isLoadMore) {
+ wx.hideLoading()
+ wx.showToast({
+ title: '网络请求失败,请重试',
+ icon: 'none',
+ duration: 2000
+ })
+ }
+
+ this.setData({
+ [`dsDingdanShuju.${tabKey}.isLoading`]: false
+ })
+ }
+ },
+
+ // 获取状态中文描述
+ getZhuangtaiZhongwen(zhuangtai) {
+ const zhuangtaiMap = {
+ 1: '已下单', // 打手看不到这个状态
+ 2: '进行中',
+ 3: '已完成',
+ 4: '退款中',
+ 5: '已退款',
+ 6: '退款失败',
+ 7: '指定中', // 打手看不到这个状态
+ 8: '结算中'
+ }
+
+ return zhuangtaiMap[zhuangtai] || '未知状态'
+ },
+
+ // 上拉加载更多
+ onReachBottom() {
+ const tabKey = this.data.currentTabKey
+ const tabData = this.data.dsDingdanShuju[tabKey]
+
+ // 检查是否还有更多数据并且当前没有正在加载
+ if (tabData.hasMore && !tabData.isLoading) {
+ const tabIndex = this.data.xuanzhongTabIndex
+ this.jiazaiDsDingdanShuju(tabIndex, true)
+ }
+ },
+
+ // 跳转到订单详情页面
+ goToDsDingdanXiangqing(e) {
+ const dingdanItem = e.currentTarget.dataset.item
+ if (!dingdanItem) return
+
+ // 编码订单数据
+ const dingdanDataStr = encodeURIComponent(JSON.stringify(dingdanItem))
+
+ // 跳转到打手订单详情页
+ wx.navigateTo({
+ url: `/pages/dsddxq/dsddxq?dingdanData=${dingdanDataStr}`
+ })
+ },
+
+ // 图片加载失败处理
+ onImageError(e) {
+ // 如果统一图片加载失败,使用默认图片
+ const defaultImage = app.globalData.lunbozhanwei || '/images/default.jpg'
+
+ this.setData({
+ dsDingdanTupianUrl: defaultImage
+ })
+ }
+})
\ No newline at end of file
diff --git a/miniprogram/pages/dashoudingdan/dashoudingdan.json b/miniprogram/pages/dashoudingdan/dashoudingdan.json
new file mode 100644
index 0000000..60b86d2
--- /dev/null
+++ b/miniprogram/pages/dashoudingdan/dashoudingdan.json
@@ -0,0 +1,8 @@
+{
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification"
+ },
+ "navigationBarTitleText": "我的接单",
+ "enablePullDownRefresh": false,
+ "onReachBottomDistance": 50
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/dashoudingdan/dashoudingdan.wxml b/miniprogram/pages/dashoudingdan/dashoudingdan.wxml
new file mode 100644
index 0000000..cec5582
--- /dev/null
+++ b/miniprogram/pages/dashoudingdan/dashoudingdan.wxml
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+ {{item.name}}
+
+
+
+
+
+
+
+
+
+
+
+
+ 加载中...
+
+
+
+
+
+ 暂无订单
+ 快去接单吧
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{item.jieshao || '暂无订单描述'}}
+
+
+
+
+
+
+
+ {{item.zhuangtaiZh || '未知状态'}}
+
+
+
+
+ ¥
+ {{item.jine || '0.00'}}
+
+
+
+
+
+
+
+
+
+ 上拉加载更多
+
+
+
+
+
+ 没有更多订单了
+
+
+
+
+
+
+
diff --git a/miniprogram/pages/dashoudingdan/dashoudingdan.wxss b/miniprogram/pages/dashoudingdan/dashoudingdan.wxss
new file mode 100644
index 0000000..eddb631
--- /dev/null
+++ b/miniprogram/pages/dashoudingdan/dashoudingdan.wxss
@@ -0,0 +1,273 @@
+/* pages/dashouDingdan/dashouDingdan.wxss */
+
+.ds-dingdan-page {
+ min-height: 100vh;
+ background-color: #f8f9fa;
+ padding-bottom: 20rpx;
+ }
+
+ .ds-dingdan-tabs-scroll {
+ width: 100%;
+ height: 90rpx;
+ white-space: nowrap;
+ background-color: #ffffff;
+ border-bottom: 1rpx solid #f0f0f0;
+ box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.03);
+ }
+
+ .ds-dingdan-tabs-container {
+ display: flex;
+ align-items: center;
+ height: 100%;
+ padding: 0 20rpx;
+ }
+
+ .ds-dingdan-tab-item {
+ display: inline-flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 60rpx;
+ padding: 0 30rpx;
+ margin-right: 20rpx;
+ border-radius: 30rpx;
+ background-color: #f5f5f5;
+ color: #666666;
+ font-size: 28rpx;
+ transition: all 0.3s ease;
+ position: relative;
+ flex-shrink: 0;
+ }
+
+ .ds-dingdan-tab-item:last-child {
+ margin-right: 0;
+ }
+
+ .ds-dingdan-tab-item.ds-tab-active {
+ background-color: #e3f2fd;
+ color: #1565c0;
+ font-weight: 500;
+ box-shadow: 0 4rpx 12rpx rgba(21, 101, 192, 0.1);
+ }
+
+ .ds-tab-active-line {
+ width: 40rpx;
+ height: 4rpx;
+ background-color: #2196f3;
+ border-radius: 2rpx;
+ margin-top: 6rpx;
+ animation: ds-fadeIn 0.3s ease;
+ }
+
+ @keyframes ds-fadeIn {
+ from { opacity: 0; transform: scale(0.8); }
+ to { opacity: 1; transform: scale(1); }
+ }
+
+ .ds-tab-text {
+ font-weight: 400;
+ }
+
+ .ds-dingdan-list-container {
+ padding: 20rpx;
+ }
+
+ .ds-loading-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 400rpx;
+ }
+
+ .ds-loading-spinner {
+ width: 60rpx;
+ height: 60rpx;
+ border: 6rpx solid #e0e0e0;
+ border-top-color: #2196f3;
+ border-radius: 50%;
+ animation: ds-spin 1s linear infinite;
+ margin-bottom: 20rpx;
+ }
+
+ @keyframes ds-spin {
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
+ }
+
+ .ds-loading-text {
+ font-size: 28rpx;
+ color: #999999;
+ }
+
+ .ds-empty-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 500rpx;
+ padding-top: 80rpx;
+ }
+
+ .ds-empty-image {
+ width: 200rpx;
+ height: 200rpx;
+ margin-bottom: 30rpx;
+ opacity: 0.6;
+ }
+
+ .ds-empty-text {
+ font-size: 32rpx;
+ color: #999999;
+ margin-bottom: 10rpx;
+ }
+
+ .ds-empty-subtext {
+ font-size: 26rpx;
+ color: #cccccc;
+ }
+
+ .ds-dingdan-card {
+ display: flex;
+ align-items: center;
+ background-color: #ffffff;
+ border-radius: 16rpx;
+ padding: 24rpx;
+ margin-bottom: 20rpx;
+ box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
+ }
+
+ .ds-dingdan-card:active {
+ transform: translateY(-2rpx);
+ box-shadow: 0 6rpx 24rpx rgba(0, 0, 0, 0.08);
+ }
+
+ .ds-dingdan-card-left {
+ flex-shrink: 0;
+ margin-right: 24rpx;
+ }
+
+ .ds-dingdan-image {
+ width: 160rpx;
+ height: 160rpx;
+ border-radius: 12rpx;
+ background-color: #f5f5f5;
+ }
+
+ .ds-dingdan-card-center {
+ flex: 1;
+ height: 160rpx;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ }
+
+ /* 关键不同点:订单介绍显示三行 */
+ .ds-dingdan-jieshao.ds-jieshao-three-line {
+ font-size: 28rpx;
+ line-height: 36rpx;
+ color: #333333;
+ font-weight: 400;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 3; /* 改为3行 */
+ overflow: hidden;
+ text-overflow: ellipsis;
+ margin-bottom: 8rpx;
+ min-height: 108rpx; /* 3 * 36rpx */
+ }
+
+ .ds-dingdan-card-right {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ justify-content: space-between;
+ height: 160rpx;
+ margin-left: 20rpx;
+ }
+
+ .ds-dingdan-zhuangtai {
+ font-size: 24rpx;
+ color: #ff6b6b;
+ font-weight: 500;
+ padding: 6rpx 16rpx;
+ border-radius: 20rpx;
+ background-color: #fff5f5;
+ white-space: nowrap;
+ text-align: center;
+ min-width: 100rpx;
+ }
+
+ .ds-dingdan-jine {
+ display: flex;
+ align-items: center;
+ }
+
+ .ds-jine-symbol {
+ font-size: 28rpx;
+ color: #333333;
+ font-weight: 500;
+ margin-right: 4rpx;
+ }
+
+ .ds-jine-number {
+ font-size: 32rpx;
+ color: #333333;
+ font-weight: 600;
+ }
+
+ .ds-load-more-container {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 40rpx 0;
+ }
+
+ .ds-load-more-line {
+ width: 80rpx;
+ height: 1rpx;
+ background-color: #e0e0e0;
+ margin: 0 20rpx;
+ }
+
+ .ds-load-more-text {
+ font-size: 26rpx;
+ color: #999999;
+ white-space: nowrap;
+ }
+
+ .ds-no-more-container {
+ text-align: center;
+ padding: 40rpx 0;
+ }
+
+ .ds-no-more-text {
+ font-size: 26rpx;
+ color: #cccccc;
+ }
+
+ @media (max-width: 375px) {
+ .ds-dingdan-image {
+ width: 140rpx;
+ height: 140rpx;
+ }
+
+ .ds-dingdan-card-center {
+ height: 140rpx;
+ }
+
+ .ds-dingdan-card-right {
+ height: 140rpx;
+ }
+
+ .ds-jine-number {
+ font-size: 30rpx;
+ }
+
+ .ds-dingdan-jieshao.ds-jieshao-three-line {
+ min-height: 96rpx; /* 调整小屏幕下的最小高度 */
+ font-size: 26rpx;
+ line-height: 32rpx;
+ }
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/dashouduan/dashouduan.js b/miniprogram/pages/dashouduan/dashouduan.js
new file mode 100644
index 0000000..7a4a87c
--- /dev/null
+++ b/miniprogram/pages/dashouduan/dashouduan.js
@@ -0,0 +1,543 @@
+// pages/dashouzhongxin/dashouzhongxin.js
+import request from '../../utils/request.js'
+import popupService from '../../services/popupService.js'
+
+const PLATFORM_INVITE_CODE = '0000008RffVgKHMj7kQC'
+
+Page({
+ data: {
+ isDashou: false,
+ isLoading: false,
+ inviteCode: '',
+ uid: '',
+ touxiang: '',
+ avatarUrl: '',
+ dashouNicheng: '',
+ zhanghaoStatus: '',
+ yongjin: '',
+ zonge: '',
+ yajin: '',
+ chenghao: '',
+ jinfen: '',
+ clumber: [],
+ chengjiaoliang: '',
+ zaixianZhuangtai: 0,
+ lastRefreshTime: 0,
+ canRefresh: true,
+ isAutoRegistering: false,
+ inviterCache: null,
+ // 🆕 称号标签列表(从独立接口获取)
+ chenghaoList: []
+ },
+
+ onLoad(options) {
+ if (options.scene) {
+ try {
+ const scene = decodeURIComponent(options.scene);
+ options.inviteCode = scene;
+ } catch (e) {
+ console.error('scene解码失败', e);
+ }
+ }
+ this.checkDashouStatus();
+ const { inviteCode } = options || {};
+ if (inviteCode) {
+ this.setData({ inviteCode: inviteCode });
+ setTimeout(() => {
+ this.handleInviteCodeWithLoginCheck(inviteCode);
+ }, 500);
+ }
+ const app = getApp();
+ if (!app.globalData.hasShownPopupOnColdStart) {
+ app.globalData.hasShownPopupOnColdStart = true;
+ setTimeout(() => {
+ popupService.checkAndShow(this, 'dashouduan');
+ }, 300);
+ }
+ },
+
+ onHide() {
+ const popupComp = this.selectComponent('#popupNotice');
+ if (popupComp && popupComp.cleanup) {
+ popupComp.cleanup();
+ }
+ },
+
+ onShow() {
+ // 从缓存读取邀请人信息
+ const inviterCache = wx.getStorageSync('inviterCache') || null;
+ this.setData({ inviterCache });
+
+ this.registerNotificationComponent();
+ this.checkAndRedirectToJinpai();
+ if (this.data.isDashou) {
+ setTimeout(() => {
+ this.refreshDashouInfo();
+ this.fetchChenghaoList(); // 🆕 获取标签
+ }, 300);
+ }
+ const pages = getCurrentPages();
+ const currentPage = pages[pages.length - 1];
+ const options = currentPage.options || {};
+ if (options.inviteCode && this.data.isDashou === false) {
+ const { inviteCode } = options;
+ setTimeout(() => {
+ this.handleInviteCodeWithLoginCheck(inviteCode);
+ }, 300);
+ }
+ },
+
+ // ==================== 🆕 获取称号标签列表 ====================
+ async fetchChenghaoList() {
+ try {
+ const res = await request({
+ url: '/dengji/dsbqhq',
+ method: 'POST'
+ });
+ if (res && res.data.code === 0 && res.data.data) {
+ const list = res.data.data.chenghao_list || [];
+ this.setData({ chenghaoList: list });
+ // 可选:也存入全局,方便其他页面使用
+ const app = getApp();
+ app.globalData.chenghaoList = list;
+ } else {
+ this.setData({ chenghaoList: [] });
+ }
+ } catch (e) {
+ console.error('获取称号标签失败', e);
+ // 失败不影响主流程
+ }
+ },
+ // ========================================================
+
+ // 联系邀请人(保持不变)
+ async contactInviter() {
+ wx.showLoading({ title: '获取邀请人...', mask: true });
+ try {
+ const res = await request({ url: '/yonghu/hqwdyqr', method: 'POST' });
+ if (res && res.data.code === 200) {
+ const inviter = res.data.data;
+ const ossImageUrl = getApp().globalData.ossImageUrl || '';
+ const fullAvatar = inviter.touxiang
+ ? (inviter.touxiang.startsWith('http') ? inviter.touxiang : ossImageUrl + inviter.touxiang)
+ : '';
+
+ const cacheData = {
+ uid: inviter.uid,
+ nicheng: inviter.nicheng || '',
+ avatar: fullAvatar
+ };
+ wx.setStorageSync('inviterCache', cacheData);
+ this.setData({ inviterCache: cacheData });
+
+ const app = getApp();
+ const myUid = wx.getStorageSync('uid');
+ if (app.globalData.currentRole !== 'dashou') {
+ app.globalData.currentRole = 'dashou';
+ wx.setStorageSync('currentRole', 'dashou');
+ if (app.switchRoleAndReconnect) {
+ await app.switchRoleAndReconnect('dashou');
+ }
+ } else {
+ if (app.connectForCurrentRole) app.connectForCurrentRole();
+ }
+
+ const param = {
+ toUserId: 'Gs' + inviter.uid,
+ toName: inviter.nicheng || '邀请人',
+ toAvatar: fullAvatar
+ };
+ wx.navigateTo({
+ url: '/pages/liaotian/liaotian?data=' + encodeURIComponent(JSON.stringify(param))
+ });
+ } else {
+ wx.showToast({ title: res?.data?.msg || '获取失败', icon: 'none' });
+ }
+ } catch (e) {
+ wx.showToast({ title: '网络错误', icon: 'none' });
+ } finally {
+ wx.hideLoading();
+ }
+ },
+
+ handleInviteCodeWithLoginCheck(inviteCode) {
+ const that = this;
+ const token = wx.getStorageSync('token');
+ const uid = wx.getStorageSync('uid');
+ if (!token || !uid) {
+ this.loginAndRegisterWithInviteCode(inviteCode);
+ } else {
+ if (!that.data.isDashou) {
+ wx.showLoading({ title: '自动注册中...', mask: true });
+ that.setData({ isAutoRegistering: true });
+ that.onRegister();
+ } else {
+ wx.showToast({ title: '您已是打手', icon: 'success', duration: 2000 });
+ }
+ }
+ },
+
+ loginAndRegisterWithInviteCode(inviteCode) {
+ const that = this;
+ wx.showLoading({ title: '登录注册中...', mask: true });
+ wx.login({
+ success: (loginRes) => {
+ if (loginRes.code) {
+ that.callLoginRegisterApi(loginRes.code, inviteCode);
+ } else {
+ wx.hideLoading();
+ wx.showToast({ title: '微信登录失败', icon: 'none' });
+ }
+ },
+ fail: () => {
+ wx.hideLoading();
+ console.error('微信登录失败');
+ wx.reLaunch({ url: '/pages/gerenzhongxin/gerenzhongxin' });
+ }
+ });
+ },
+
+ callLoginRegisterApi(code, inviteCode) {
+ const that = this;
+ const app = getApp();
+ const apiBaseUrl = app.globalData.apiBaseUrl;
+ if (!apiBaseUrl) {
+ wx.hideLoading();
+ wx.showToast({ title: '服务器配置错误', icon: 'none' });
+ return;
+ }
+ wx.request({
+ url: apiBaseUrl + '/yonghu/wdlyhdl',
+ method: 'POST',
+ data: { code: code, inviteCode: inviteCode },
+ header: { 'content-type': 'application/json' },
+ success: (res) => {
+ if (res.statusCode === 200 && res.data && res.data.code === 0 && res.data.data) {
+ const userData = res.data.data;
+ if (userData.token) wx.setStorageSync('token', userData.token);
+ const identityFields = ['nicheng', 'uid', 'touxiang', 'dashoustatus', 'guanshistatus', 'shangjiastatus', 'zuzhangstatus'];
+ identityFields.forEach(field => {
+ if (userData[field] !== undefined) {
+ wx.setStorageSync(field, userData[field]);
+ app.globalData[field] = userData[field];
+ }
+ });
+ const groupFields = ['dashouqun', 'dashouqunid', 'guanshiqun', 'guanshiqunid'];
+ groupFields.forEach(field => {
+ if (userData[field] !== undefined) {
+ wx.setStorageSync(field, userData[field]);
+ app.globalData[field] = userData[field];
+ }
+ });
+ if (userData.dashounicheng || userData.yongjin) {
+ const updateData = {
+ dashouNicheng: userData.dashounicheng || '',
+ zhanghaoStatus: userData.zhanghaostatus || '',
+ yongjin: userData.yongjin || '0.00',
+ zonge: userData.zonge || '0.00',
+ yajin: userData.yajin || '0.00',
+ chenghao: userData.chenghao || '',
+ jinfen: userData.jifen || userData.jinfen || '0',
+ clumber: userData.clumber || [],
+ chengjiaoliang: userData.chengjiaoliang || '0',
+ zaixianZhuangtai: userData.zaixianzhuangtai || 0,
+ dashouzhuangtai: userData.dashouzhuangtai || '',
+ // 🔥 不再从这里取 chenghaoList,单独调用 fetchChenghaoList
+ };
+ Object.assign(app.globalData, updateData);
+ }
+ that.setData({
+ isDashou: true,
+ uid: userData.uid || '',
+ touxiang: userData.touxiang || '',
+ isAutoRegistering: false
+ // 不设置 chenghaoList,稍后在 onShow 中自动获取
+ });
+ that.loadAvatar();
+ that.getDashouInfo();
+ that.switchToDashouRole();
+ wx.hideLoading();
+ wx.showToast({ title: '登录注册成功!', icon: 'success', duration: 2000 });
+ } else {
+ wx.hideLoading();
+ wx.showToast({ title: (res.data && res.data.msg) || '登录注册失败', icon: 'none' });
+ }
+ },
+ fail: () => {
+ wx.hideLoading();
+ wx.showToast({ title: '网络请求失败', icon: 'none' });
+ }
+ });
+ },
+
+ 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()
+ };
+ }
+ },
+
+ checkDashouStatus() {
+ try {
+ const dashoustatus = wx.getStorageSync('dashoustatus');
+ const uid = wx.getStorageSync('uid');
+ const touxiang = wx.getStorageSync('touxiang');
+ if (dashoustatus === 1) {
+ this.setData({ isDashou: true, uid: uid || '', touxiang: touxiang || '' });
+ this.loadAvatar();
+ this.checkGlobalData();
+ } else {
+ this.setData({ isDashou: false });
+ }
+ } catch (error) {
+ console.error('读取缓存失败:', error);
+ this.setData({ isDashou: false });
+ }
+ },
+
+ loadAvatar() {
+ const app = getApp();
+ const { ossImageUrl = '', morentouxiang = 'avatar/default.jpg' } = app.globalData || {};
+ const { touxiang } = this.data;
+ let avatarUrl = '';
+ if (touxiang) {
+ avatarUrl = ossImageUrl + (touxiang.startsWith('/') ? touxiang : '/' + touxiang);
+ } else {
+ avatarUrl = ossImageUrl + (morentouxiang.startsWith('/') ? morentouxiang : '/' + morentouxiang);
+ }
+ this.setData({ avatarUrl });
+ },
+
+ checkGlobalData() {
+ const app = getApp();
+ const globalData = app.globalData || {};
+ const needRefresh = !globalData.dashouNicheng || globalData.dashouNicheng === '' || !globalData.yongjin;
+ if (needRefresh) {
+ this.getDashouInfo();
+ } else {
+ this.setDataFromGlobalData();
+ }
+ },
+
+ setDataFromGlobalData() {
+ const app = getApp();
+ const {
+ dashouNicheng = '', zhanghaoStatus = '', yongjin = '0.00', zonge = '0.00',
+ yajin = '0.00', chenghao = '', jinfen = '0', clumber = [],
+ chengjiaoliang = '0', zaixianZhuangtai = 0
+ // 🔥 chenghaoList 不再从全局取,避免旧数据干扰
+ } = app.globalData || {};
+ this.setData({
+ dashouNicheng, zhanghaoStatus, yongjin, zonge, yajin,
+ chenghao, jinfen,
+ clumber: Array.isArray(clumber) ? clumber : [],
+ chengjiaoliang,
+ zaixianZhuangtai: zaixianZhuangtai === 1 ? 1 : 0
+ });
+ },
+
+ async getDashouInfo() {
+ this.setData({ isLoading: true });
+ try {
+ const res = await request({ url: '/yonghu/dashouxinxi', method: 'POST' });
+ if (res && res.data.code == 200) {
+ const data = res.data.data;
+ const app = getApp();
+ const updateData = {
+ dashouNicheng: data.dashounicheng || '',
+ zhanghaoStatus: data.zhanghaostatus || '',
+ yongjin: data.yongjin || '0.00',
+ zonge: data.zonge || '0.00',
+ yajin: data.yajin || '0.00',
+ chenghao: data.chenghao || '',
+ jinfen: data.jifen || '0',
+ clumber: data.clumber || [],
+ chengjiaoliang: data.chengjiaoliang || '0',
+ zaixianZhuangtai: data.zaixianzhuangtai || 0,
+ dashouzhuangtai: data.dashouzhuangtai || '',
+ // 🔥 不再包含 chenghaoList
+ };
+ Object.assign(app.globalData, updateData);
+ if (data.dashoustatus !== undefined) {
+ wx.setStorageSync('dashoustatus', data.dashoustatus);
+ app.globalData.dashoustatus = data.dashoustatus;
+ }
+ if (data.guanshistatus !== undefined) {
+ wx.setStorageSync('guanshistatus', data.guanshistatus);
+ app.globalData.guanshistatus = data.guanshistatus;
+ }
+ this.setData({ ...updateData, isLoading: false });
+ if (this.data.isAutoRegistering) {
+ this.setData({ isAutoRegistering: false });
+ wx.showToast({ title: '注册成功!', icon: 'success', duration: 2000 });
+ } else {
+ wx.showToast({ title: '信息已更新', icon: 'success', duration: 1500 });
+ }
+ this.handleJinpaiStatus(data.chenghao);
+ }
+ } catch (error) {
+ console.error('获取打手信息失败:', error);
+ this.setData({ isLoading: false, isAutoRegistering: false });
+ wx.showToast({ title: '获取信息失败', icon: 'error', duration: 2000 });
+ }
+ },
+
+ refreshDashouInfo() {
+ const now = Date.now();
+ if (!this.data.canRefresh || (now - this.data.lastRefreshTime < 3000)) return;
+ this.setData({ lastRefreshTime: now, canRefresh: false });
+ this.getDashouInfo();
+ this.fetchChenghaoList(); // 🆕 同时刷新标签
+ setTimeout(() => this.setData({ canRefresh: true }), 3000);
+ },
+
+ onInviteCodeInput(e) {
+ this.setData({ inviteCode: e.detail.value.trim() });
+ },
+
+ async onRegister() {
+ const { inviteCode } = this.data;
+ if (!inviteCode) {
+ wx.showToast({ title: '请输入邀请码', icon: 'none', duration: 2000 });
+ return;
+ }
+ if (inviteCode.length > 100) {
+ wx.showToast({ title: '邀请码不能超过100字符', icon: 'none', duration: 2000 });
+ return;
+ }
+ this.setData({ isLoading: true });
+ try {
+ const res = await request({
+ url: '/yonghu/dashouzhuce',
+ method: 'POST',
+ data: { inviteCode: inviteCode }
+ });
+ if (res && res.data.code == 200) {
+ const data = res.data.data;
+ const app = getApp();
+ const identityFields = ['dashoustatus', 'guanshistatus', 'shangjiastatus', 'zuzhangstatus'];
+ identityFields.forEach(field => {
+ if (data[field] !== undefined) {
+ wx.setStorageSync(field, data[field]);
+ app.globalData[field] = data[field];
+ }
+ });
+ if (data.dashoustatus === undefined) {
+ wx.setStorageSync('dashoustatus', 1);
+ app.globalData.dashoustatus = 1;
+ }
+ const updateData = {
+ dashouNicheng: data.dashounicheng || '',
+ zhanghaoStatus: data.zhanghaostatus || '',
+ yongjin: data.yongjin || '0.00',
+ zonge: data.zonge || '0.00',
+ yajin: data.yajin || '0.00',
+ chenghao: data.chenghao || '',
+ jinfen: data.jinfen || '0',
+ clumber: data.clumber || [],
+ chengjiaoliang: data.chengjiaoliang || '0',
+ zaixianZhuangtai: data.zaixianzhuangtai || 0,
+ dashouzhuangtai: data.dashouzhuangtai || '',
+ // chenghaoList 不从这取
+ };
+ Object.assign(app.globalData, updateData);
+ this.setData({
+ isDashou: true,
+ ...updateData,
+ isLoading: false,
+ isAutoRegistering: false
+ });
+ this.loadAvatar();
+ if (this.data.isAutoRegistering || !wx.getStorageSync('token')) {
+ this.switchToDashouRole();
+ }
+ wx.showToast({ title: '注册成功!', icon: 'success', duration: 2000 });
+ this.handleJinpaiStatus(data.chenghao);
+ } else {
+ this.setData({ isLoading: false, isAutoRegistering: false });
+ wx.showToast({ title: (res.data && res.data.message) || '注册失败', icon: 'error', duration: 2000 });
+ }
+ } catch (error) {
+ console.error('注册失败:', error);
+ this.setData({ isLoading: false, isAutoRegistering: false });
+ wx.showToast({ title: error.message || '注册失败', icon: 'error', duration: 2000 });
+ }
+ },
+
+ switchToDashouRole() {
+ const app = getApp();
+ app.globalData.currentRole = 'dashou';
+ wx.setStorageSync('currentRole', 'dashou');
+ const tabBar = this.getTabBar();
+ if (tabBar && tabBar.refresh) tabBar.refresh();
+ if (app.connectForCurrentRole) {
+ app.connectForCurrentRole();
+ } else if (app.ensureConnection) {
+ app.ensureConnection();
+ }
+ },
+
+ copyUid() {
+ const { uid } = this.data;
+ if (!uid) {
+ wx.showToast({ title: 'UID不存在', icon: 'none', duration: 1500 });
+ return;
+ }
+ wx.setClipboardData({
+ data: uid,
+ success: () => wx.showToast({ title: '已复制UID', icon: 'success', duration: 1500 }),
+ fail: () => wx.showToast({ title: '复制失败', icon: 'error', duration: 1500 })
+ });
+ },
+
+ previewAvatar() {
+ const { avatarUrl } = this.data;
+ if (avatarUrl) wx.previewImage({ urls: [avatarUrl] });
+ },
+
+ goToWithdraw() { wx.navigateTo({ url: '/pages/tixian/tixian' }) },
+ goToReceiveOrder() { wx.navigateTo({ url: '/pages/jiedan/jiedan' }) },
+ goToMyOrders() { wx.navigateTo({ url: '/pages/dashoudingdan/dashoudingdan' }) },
+ goToMyPunishment() { wx.navigateTo({ url: '/pages/cfss/cfss/cfss' }) },
+ goToRecharge() { wx.navigateTo({ url: '/pages/dashouchongzhi/dashouchongzhi' }) },
+ goToRanking() { wx.navigateTo({ url: '/pages/dashoupaihang/dashoupaihang' }) },
+ goToModifyInfo() { wx.navigateTo({ url: '/pages/dashouxiugai/dashouxiugai' }) },
+ goToRules() { wx.navigateTo({ url: '/pages/dashouguize/dashouguize' }) },
+ // 跳转到考核金牌
+goToKaohe() {
+ wx.navigateTo({ url: '/pages/kaohe_jinpai/kaohe_jinpai' });
+ },
+
+ quickRegisterAsPlatform() {
+ const that = this;
+ wx.showModal({
+ title: '提示',
+ content: '使用此功能后,您将不属于任何管事,确认注册吗?',
+ success: (res) => {
+ if (res.confirm) {
+ that.setData({ inviteCode: PLATFORM_INVITE_CODE });
+ that.onRegister();
+ }
+ }
+ });
+ },
+
+ checkAndRedirectToJinpai() {
+ const isJinpai = wx.getStorageSync('isJinpai');
+ if (isJinpai === 1) {
+ wx.redirectTo({ url: '/pages/jinpaids/jinpaids' });
+ }
+ },
+
+ handleJinpaiStatus(chenghao) {
+ const isJinpai = (chenghao === '金牌选手') ? 1 : 0;
+ wx.setStorageSync('isJinpai', isJinpai);
+ if (isJinpai) {
+ wx.redirectTo({ url: '/pages/jinpaids/jinpaids' });
+ }
+ }
+})
\ No newline at end of file
diff --git a/miniprogram/pages/dashouduan/dashouduan.json b/miniprogram/pages/dashouduan/dashouduan.json
new file mode 100644
index 0000000..e0bbde6
--- /dev/null
+++ b/miniprogram/pages/dashouduan/dashouduan.json
@@ -0,0 +1,14 @@
+{
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification",
+ "chenghao-tag": "/components/chenghao-tag/chenghao-tag"
+
+ },
+ "navigationBarBackgroundColor": "#0F0A1F",
+ "navigationBarTextStyle": "white",
+ "navigationBarTitleText": "服务中心",
+ "backgroundColor": "#0F0A1F",
+ "backgroundTextStyle": "light",
+ "enablePullDownRefresh": false,
+ "onReachBottomDistance": 50
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/dashouduan/dashouduan.wxml b/miniprogram/pages/dashouduan/dashouduan.wxml
new file mode 100644
index 0000000..38433d1
--- /dev/null
+++ b/miniprogram/pages/dashouduan/dashouduan.wxml
@@ -0,0 +1,212 @@
+
+
+
+ 您还未解锁此功能,请输入邀请码解锁
+
+
+
+
+
+
+ 输入邀请码注册
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ↻
+
+
+
+
+
+
+
+
+
+ {{dashouNicheng || '打手昵称'}}
+
+
+
+
+ UID:
+ {{uid || '未获取'}}
+
+ ⎘
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 我的佣金
+
+ {{yongjin || '0.00'}}
+ 元
+
+
+
+
+ 提现
+
+
+
+
+
+
+ 积分
+ {{jinfen || '0'}}
+
+
+
+
+
+ 保证金
+ {{yajin || '0.00'}}元
+
+
+
+
+
+
+
+
+ 🎮
+ 抢单大厅
+
+
+
+
+
+ 💳
+ 充值会员保证金
+
+
+
+ 📦
+ 我的订单
+
+
+
+
+
+ ⚖️
+ 我的处罚
+
+
+
+ 🏆
+ 排行榜
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 邀请人:{{inviterCache.nicheng || '未知'}}
+ ID: {{inviterCache.uid}}
+
+
+ 联系
+
+
+
+
+ 🤝
+ 联系邀请人
+
+ ›
+
+
+
+
+
+ 🏅
+ 考核金牌
+
+ ›
+
+
+
+
+ ✏️
+ 修改个人信息
+
+ ›
+
+
+
+
+ 📖
+ 用户规则
+
+ ›
+
+
+
+
+
+
+
+
+
+ 加载中...
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/dashouduan/dashouduan.wxss b/miniprogram/pages/dashouduan/dashouduan.wxss
new file mode 100644
index 0000000..3429296
--- /dev/null
+++ b/miniprogram/pages/dashouduan/dashouduan.wxss
@@ -0,0 +1,860 @@
+/* pages/dashouzhongxin/dashouzhongxin.wxss */
+
+/* =========== 全局样式 =========== */
+page {
+ background: linear-gradient(135deg, #121212 0%, #1a1a2e 30%, #16213e 70%, #0f3460 100%);
+ min-height: 100vh;
+ color: #FFFFFF;
+ font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif;
+ background-attachment: fixed;
+ background-size: cover;
+}
+
+.page-container {
+ min-height: 100vh;
+ position: relative;
+ box-sizing: border-box;
+}
+
+/* =========== 未注册状态页面 =========== */
+.unregistered-page {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding-top: 200rpx;
+ min-height: 100vh;
+ box-sizing: border-box;
+}
+
+.status-tip {
+ font-size: 32rpx;
+ color: #FFFFFF;
+ margin-bottom: 80rpx;
+ font-weight: 500;
+ text-align: center;
+ opacity: 0;
+ animation: fadeIn 1s ease-out forwards;
+ line-height: 1.5;
+ padding: 0 40rpx;
+}
+
+.input-container {
+ width: 85%;
+ margin-bottom: 60rpx;
+ box-sizing: border-box;
+}
+
+.invite-input {
+ width: 100%;
+ height: 90rpx;
+ background: rgba(255, 255, 255, 0.1);
+ border-radius: 20rpx;
+ padding: 0 30rpx;
+ font-size: 30rpx;
+ color: #FFFFFF;
+ box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.3);
+ border: 1rpx solid rgba(255, 215, 0, 0.3);
+ transition: all 0.3s;
+ box-sizing: border-box;
+}
+
+.invite-input:focus {
+ border-color: #FFD700;
+ box-shadow: 0 15rpx 40rpx rgba(255, 215, 0, 0.25);
+ transform: translateY(-2rpx);
+}
+
+.placeholder {
+ color: rgba(255, 255, 255, 0.5);
+ font-size: 28rpx;
+}
+
+.register-view-btn {
+ width: 85%;
+ height: 80rpx;
+ background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
+ border-radius: 40rpx;
+ position: relative;
+ overflow: hidden;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-shadow: 0 10rpx 30rpx rgba(255, 215, 0, 0.3);
+ transition: all 0.3s ease;
+ margin-top: 20rpx;
+}
+
+.register-view-btn:active {
+ transform: scale(0.98);
+}
+
+.register-text {
+ font-size: 34rpx;
+ font-weight: bold;
+ color: #333;
+ letter-spacing: 4rpx;
+ position: relative;
+ z-index: 2;
+ text-shadow: 0 2rpx 4rpx rgba(255, 255, 255, 0.2);
+ line-height: 80rpx;
+}
+
+.register-glow {
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg,
+ transparent,
+ rgba(255, 255, 255, 0.4),
+ transparent);
+ animation: shine 2s infinite;
+}
+
+/* 一键注册按钮样式 */
+.quick-register-btn {
+ width: 85%;
+ height: 80rpx;
+ background: linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%);
+ border-radius: 40rpx;
+ position: relative;
+ overflow: hidden;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-shadow: 0 10rpx 30rpx rgba(76, 175, 80, 0.4);
+ transition: all 0.3s ease;
+ margin-top: 20rpx;
+}
+
+.quick-register-btn:active {
+ transform: scale(0.98);
+}
+
+.quick-register-text {
+ font-size: 34rpx;
+ font-weight: bold;
+ color: white;
+ letter-spacing: 4rpx;
+ position: relative;
+ z-index: 2;
+ text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2);
+ line-height: 80rpx;
+}
+
+.quick-register-glow {
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg,
+ transparent,
+ rgba(255, 255, 255, 0.3),
+ transparent);
+ animation: shine 2s infinite;
+}
+
+/* =========== 已注册状态页面 =========== */
+.registered-page {
+ padding: 30rpx 25rpx;
+ box-sizing: border-box;
+}
+
+.refresh-btn {
+ position: absolute;
+ top: 30rpx;
+ right: 25rpx;
+ width: 60rpx;
+ height: 60rpx;
+ background: rgba(255, 255, 255, 0.08);
+ border-radius: 50%;
+ box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.25);
+ z-index: 10;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border: 1rpx solid rgba(255, 255, 255, 0.1);
+ transition: all 0.3s ease;
+}
+
+.refresh-btn:active {
+ transform: rotate(180deg);
+ background: rgba(255, 255, 255, 0.12);
+}
+
+.refresh-icon {
+ font-size: 32rpx;
+ color: #FFD700;
+ font-weight: bold;
+}
+
+/* 头像区域 */
+.avatar-section {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 30rpx;
+ margin-top: 40rpx;
+ position: relative;
+}
+
+.avatar-info {
+ display: flex;
+ align-items: center;
+ flex: 1;
+}
+
+.avatar-img {
+ width: 120rpx;
+ height: 120rpx;
+ border-radius: 50%;
+ box-shadow: 0 0 0 3rpx rgba(255, 215, 0, 0.5),
+ 0 8rpx 25rpx rgba(0, 0, 0, 0.4);
+ margin-right: 25rpx;
+ transition: transform 0.3s ease;
+ background: linear-gradient(45deg, #2a2a2a, #1a1a1a);
+}
+
+.avatar-img:active {
+ transform: scale(0.95);
+}
+
+.user-info {
+ margin-left: 25rpx;
+ flex: 1;
+}
+
+.nickname-row {
+ display: flex;
+ align-items: center;
+ margin-bottom: 15rpx;
+ flex-wrap: wrap;
+}
+
+.dashou-nicheng {
+ font-size: 36rpx;
+ font-weight: bold;
+ color: #FFFFFF;
+ margin-right: 15rpx;
+ max-width: 200rpx;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.5);
+}
+
+.chenghao-tag {
+ background: linear-gradient(135deg,
+ rgba(255, 215, 0, 0.9) 0%,
+ rgba(255, 165, 0, 0.9) 100%);
+ padding: 4rpx 16rpx;
+ border-radius: 20rpx;
+ height: 40rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border: 1rpx solid rgba(255, 215, 0, 0.5);
+ box-shadow: 0 4rpx 12rpx rgba(255, 215, 0, 0.3);
+}
+
+.chenghao-text {
+ font-size: 22rpx;
+ font-weight: bold;
+ color: #333;
+ text-shadow: 0 1rpx 1rpx rgba(255, 255, 255, 0.8);
+ letter-spacing: 1rpx;
+}
+
+.uid-row {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+}
+
+.uid-label {
+ font-size: 26rpx;
+ color: rgba(255, 255, 255, 0.7);
+ margin-right: 10rpx;
+ font-weight: 500;
+}
+
+.uid-value {
+ font-size: 26rpx;
+ color: #FFFFFF;
+ font-family: 'Courier New', monospace;
+ margin-right: 15rpx;
+ background: rgba(255, 255, 255, 0.08);
+ padding: 6rpx 12rpx;
+ border-radius: 8rpx;
+ border: 1rpx solid rgba(255, 255, 255, 0.1);
+}
+
+.copy-btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.3s ease;
+ width: 50rpx;
+ height: 50rpx;
+ border-radius: 50%;
+ background: rgba(255, 215, 0, 0.1);
+ border: 1rpx solid rgba(255, 215, 0, 0.3);
+ box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.2);
+}
+
+.copy-btn:active {
+ transform: scale(0.95);
+ background: rgba(255, 215, 0, 0.2);
+}
+
+.copy-icon {
+ font-size: 28rpx;
+ color: #FFD700;
+ font-weight: 500;
+}
+
+/* 资产区域 */
+.assets-section {
+ position: relative;
+ margin-bottom: 30rpx;
+}
+
+.black-card {
+ background: linear-gradient(135deg,
+ rgba(30, 30, 46, 0.9) 0%,
+ rgba(22, 33, 62, 0.9) 100%);
+ border-radius: 20rpx;
+ padding: 30rpx;
+ position: relative;
+ box-shadow:
+ 0 15rpx 40rpx rgba(0, 0, 0, 0.4);
+ overflow: hidden;
+ border: 1rpx solid rgba(255, 215, 0, 0.15);
+ min-height: 180rpx;
+}
+
+.card-header {
+ margin-bottom: 25rpx;
+}
+
+.card-title {
+ font-size: 28rpx;
+ color: #FFD700;
+ letter-spacing: 2rpx;
+ font-weight: bold;
+}
+
+.yongjin-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 25rpx;
+}
+
+.yongjin-info {
+ flex: 1;
+}
+
+.asset-label {
+ font-size: 24rpx;
+ color: rgba(255, 255, 255, 0.7);
+ font-weight: 400;
+ margin-bottom: 8rpx;
+ display: block;
+}
+
+.asset-value-container {
+ display: flex;
+ align-items: baseline;
+}
+
+.asset-value {
+ font-size: 48rpx;
+ font-weight: bold;
+ color: #FFD700;
+ font-family: 'Arial', sans-serif;
+ letter-spacing: 1rpx;
+}
+
+.asset-unit {
+ font-size: 28rpx;
+ color: #FFD700;
+ margin-left: 8rpx;
+ font-weight: 500;
+}
+
+.withdraw-view-btn {
+ width: 120rpx;
+ height: 55rpx;
+ background: linear-gradient(135deg,
+ rgba(255, 215, 0, 0.9) 0%,
+ rgba(255, 165, 0, 0.9) 100%);
+ border-radius: 10rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-shadow:
+ 0 6rpx 20rpx rgba(255, 215, 0, 0.4);
+ transition: all 0.3s ease;
+ border: 1rpx solid rgba(255, 215, 0, 0.3);
+ position: relative;
+ overflow: hidden;
+}
+
+.withdraw-view-btn:active {
+ transform: scale(0.95);
+}
+
+.withdraw-text {
+ font-size: 26rpx;
+ font-weight: bold;
+ color: #333;
+ letter-spacing: 2rpx;
+ position: relative;
+ z-index: 2;
+ text-shadow: 0 1rpx 2rpx rgba(255, 255, 255, 0.5);
+}
+
+.assets-bottom {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding-top: 25rpx;
+ border-top: 1rpx solid rgba(255, 255, 255, 0.1);
+}
+
+.bottom-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ flex: 1;
+}
+
+.bottom-label {
+ font-size: 22rpx;
+ color: rgba(255, 255, 255, 0.7);
+ margin-bottom: 8rpx;
+ font-weight: 400;
+}
+
+.bottom-value {
+ font-size: 30rpx;
+ font-weight: bold;
+ color: #FFD700;
+}
+
+.vertical-divider {
+ width: 1rpx;
+ height: 40rpx;
+ background: linear-gradient(to bottom,
+ transparent,
+ rgba(255, 215, 0, 0.5) 50%,
+ transparent);
+}
+
+/* 功能区域 */
+.function-section {
+ background: rgba(255, 255, 255, 0.05);
+ border-radius: 20rpx;
+ padding: 30rpx 25rpx;
+ margin-bottom: 30rpx;
+ box-shadow:
+ 0 10rpx 25rpx rgba(0, 0, 0, 0.2);
+ border: 1rpx solid rgba(255, 215, 0, 0.1);
+}
+
+/* 抢单大厅 */
+.main-function {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 35rpx 0;
+ border-radius: 20rpx;
+ background: linear-gradient(135deg,
+ rgba(0, 150, 255, 0.2) 0%,
+ rgba(0, 100, 200, 0.2) 100%);
+ transition: all 0.3s ease;
+ position: relative;
+ overflow: hidden;
+ margin-bottom: 25rpx;
+ border: 1rpx solid rgba(0, 150, 255, 0.3);
+}
+
+.main-function:active {
+ transform: scale(0.98);
+ background: linear-gradient(135deg,
+ rgba(0, 150, 255, 0.3) 0%,
+ rgba(0, 100, 200, 0.3) 100%);
+}
+
+.main-function-icon {
+ font-size: 60rpx;
+ margin-bottom: 20rpx;
+}
+
+.main-function-text {
+ font-size: 32rpx;
+ color: #FFFFFF;
+ font-weight: bold;
+ text-align: center;
+ position: relative;
+ z-index: 2;
+ letter-spacing: 2rpx;
+}
+
+.main-function-glow {
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg,
+ transparent,
+ rgba(255, 255, 255, 0.2),
+ transparent);
+ animation: shine 3s infinite;
+}
+
+/* 第二行:充值和订单 */
+.function-grid-second {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 20rpx;
+}
+
+.function-grid-second .function-item-second:nth-child(1) {
+ position: relative;
+ overflow: hidden;
+ background: linear-gradient(135deg,
+ rgba(255, 215, 0, 0.2) 0%,
+ rgba(255, 165, 0, 0.2) 100%);
+ border: 1rpx solid rgba(255, 215, 0, 0.4);
+ box-shadow:
+ 0 8rpx 25rpx rgba(255, 215, 0, 0.3),
+ 0 0 15rpx rgba(255, 215, 0, 0.2);
+}
+
+.function-grid-second .function-item-second:nth-child(1):active {
+ transform: scale(0.96);
+ background: linear-gradient(135deg,
+ rgba(255, 215, 0, 0.3) 0%,
+ rgba(255, 165, 0, 0.3) 100%);
+}
+
+.function-grid-second .function-item-second:nth-child(1) .function-icon-second {
+ font-size: 48rpx;
+ color: #FFD700;
+ text-shadow:
+ 0 2rpx 8rpx rgba(255, 215, 0, 0.5),
+ 0 0 15rpx rgba(255, 215, 0, 0.3);
+ animation: goldPulse 2s infinite ease-in-out;
+}
+
+.function-grid-second .function-item-second:nth-child(1) .function-text-second {
+ font-size: 26rpx;
+ font-weight: bold;
+ color: #FFD700;
+ text-shadow: 0 1rpx 3rpx rgba(0, 0, 0, 0.5);
+ letter-spacing: 1rpx;
+}
+
+.function-grid-second .function-item-second:nth-child(1)::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg,
+ transparent,
+ rgba(255, 255, 255, 0.3),
+ transparent);
+ animation: shine 2.5s infinite;
+}
+
+.function-item-second {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 25rpx 0;
+ border-radius: 16rpx;
+ background: rgba(255, 255, 255, 0.08);
+ transition: all 0.3s ease;
+ position: relative;
+ overflow: hidden;
+ box-shadow:
+ 0 5rpx 15rpx rgba(0, 0, 0, 0.2);
+ width: 48%;
+}
+
+.function-item-second:active {
+ transform: scale(0.96);
+ background: rgba(255, 255, 255, 0.12);
+}
+
+.function-icon-second {
+ font-size: 44rpx;
+ margin-bottom: 15rpx;
+}
+
+.function-text-second {
+ font-size: 26rpx;
+ color: #FFFFFF;
+ font-weight: 500;
+ text-align: center;
+ position: relative;
+ z-index: 2;
+}
+
+/* 第三行:我的处罚和排行 */
+.function-grid-third {
+ display: flex;
+ justify-content: space-between;
+}
+
+.function-item-third {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 22rpx 0;
+ border-radius: 16rpx;
+ background: rgba(255, 255, 255, 0.06);
+ transition: all 0.3s ease;
+ position: relative;
+ overflow: hidden;
+ box-shadow:
+ 0 4rpx 12rpx rgba(0, 0, 0, 0.2);
+ width: 48%;
+}
+
+.function-item-third:active {
+ transform: scale(0.96);
+ background: rgba(255, 255, 255, 0.09);
+}
+
+.function-icon-third {
+ font-size: 40rpx;
+ margin-bottom: 12rpx;
+}
+
+.function-text-third {
+ font-size: 24rpx;
+ color: rgba(255, 255, 255, 0.9);
+ font-weight: 500;
+ text-align: center;
+ position: relative;
+ z-index: 2;
+}
+
+/* 其他功能区域 */
+.other-section {
+ background: rgba(255, 255, 255, 0.05);
+ border-radius: 20rpx;
+ padding: 0 25rpx;
+ box-shadow:
+ 0 10rpx 25rpx rgba(0, 0, 0, 0.2);
+ border: 1rpx solid rgba(255, 215, 0, 0.1);
+}
+
+.other-list {
+ padding: 20rpx 0;
+}
+
+.other-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 28rpx 0;
+ border-bottom: 1rpx solid rgba(255, 255, 255, 0.05);
+ transition: all 0.3s ease;
+}
+
+.other-item:active {
+ background: rgba(255, 215, 0, 0.05);
+ transform: translateX(5rpx);
+}
+
+.other-item:last-child {
+ border-bottom: none;
+}
+
+.other-item-left {
+ display: flex;
+ align-items: center;
+}
+
+.other-icon {
+ font-size: 36rpx;
+ margin-right: 20rpx;
+}
+
+.other-text {
+ font-size: 28rpx;
+ color: #FFFFFF;
+ font-weight: 500;
+}
+
+.arrow-icon {
+ font-size: 36rpx;
+ color: rgba(255, 255, 255, 0.5);
+ font-weight: bold;
+ transition: all 0.3s ease;
+}
+
+.other-item:active .arrow-icon {
+ color: #FFD700;
+ transform: translateX(5rpx);
+}
+
+/* 邀请人专属样式(新增) */
+.inviter-avatar {
+ width: 70rpx;
+ height: 70rpx;
+ border-radius: 50%;
+ margin-right: 20rpx;
+ border: 2rpx solid rgba(255, 215, 0, 0.5);
+ box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.4);
+}
+
+.inviter-text-info {
+ display: flex;
+ flex-direction: column;
+}
+
+.inviter-uid-text {
+ font-size: 22rpx;
+ color: rgba(255, 255, 255, 0.5);
+ margin-top: 6rpx;
+}
+
+.contact-badge {
+ background: rgba(255, 215, 0, 0.2);
+ border: 1rpx solid rgba(255, 215, 0, 0.5);
+ color: #FFD700;
+ font-size: 24rpx;
+ padding: 8rpx 20rpx;
+ border-radius: 20rpx;
+ font-weight: 500;
+ transition: all 0.3s;
+}
+
+.contact-badge:active {
+ background: rgba(255, 215, 0, 0.4);
+}
+
+/* 加载遮罩 */
+.loading-mask {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(18, 18, 18, 0.9);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 9999;
+}
+
+.loading-content {
+ background: rgba(30, 30, 46, 0.95);
+ padding: 60rpx 80rpx;
+ border-radius: 30rpx;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.5);
+ border: 1rpx solid rgba(255, 215, 0, 0.2);
+}
+
+.loading-spinner {
+ width: 80rpx;
+ height: 80rpx;
+ border: 6rpx solid rgba(255, 215, 0, 0.2);
+ border-top: 6rpx solid #FFD700;
+ border-radius: 50%;
+ animation: spin 1s linear infinite;
+ margin-bottom: 30rpx;
+}
+
+.loading-text {
+ font-size: 28rpx;
+ color: #FFFFFF;
+ font-weight: 500;
+}
+
+/* =========== 动画定义 =========== */
+@keyframes fadeIn {
+ from { opacity: 0; transform: translateY(30rpx); }
+ to { opacity: 1; transform: translateY(0); }
+}
+
+@keyframes shine {
+ 0% { left: -100%; }
+ 100% { left: 100%; }
+}
+
+@keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+}
+
+@keyframes goldPulse {
+ 0%, 100% { transform: scale(1); opacity: 0.9; }
+ 50% { transform: scale(1.05); opacity: 1; }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/* ========== 🆕 称号标签展示区 ========== */
+/* ========== 🆕 称号标签展示区 ========== */
+.badge-area {
+ width: 100%;
+ margin: 20rpx 0 30rpx 0;
+ padding: 0 20rpx;
+ box-sizing: border-box;
+ }
+
+ .badge-scroll {
+ width: 100%;
+ max-height: 140rpx; /* 最多两行高度(52*2=104 + 间距36rpx) */
+ overflow-y: auto; /* 超出自动滚动 */
+ background: transparent;
+ }
+
+ .badge-grid {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8rpx;
+ padding: 6rpx 0;
+ align-items: center;
+ justify-content: flex-start;
+ }
+
+ .badge-item {
+ flex-shrink: 0;
+ /* 不设宽度限制,由组件自身宽度决定 */
+ }
+
+ .other-icon-img {
+ width: 36rpx;
+ height: 36rpx;
+ margin-right: 20rpx;
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/dashouguize/dashouguize.js b/miniprogram/pages/dashouguize/dashouguize.js
new file mode 100644
index 0000000..28163aa
--- /dev/null
+++ b/miniprogram/pages/dashouguize/dashouguize.js
@@ -0,0 +1,103 @@
+Page({
+ onShow() {
+ // 原有代码...
+
+ // 🆕 注册通知组件
+ this.registerNotificationComponent();
+ },
+
+ // 🆕 新增:注册通知组件
+ 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()
+ };
+ }
+ },
+ data: {
+ // 图片URL(带时间戳避免缓存)
+ imageUrl: '',
+ // 容器高度(初始为100vh,加载后调整为图片高度)
+ containerHeight: 0
+ },
+
+ onLoad() {
+ // 初始设置容器高度为屏幕高度
+ this.getSystemInfo()
+ // 构建图片URL(带时间戳)
+ this.buildImageUrl()
+ },
+
+ // 获取系统信息
+ getSystemInfo() {
+ const res = wx.getSystemInfoSync()
+ this.setData({
+ containerHeight: res.windowHeight
+ })
+ },
+
+ // 构建图片URL(关键:加时间戳避免缓存)
+ buildImageUrl() {
+ const app = getApp()
+ const ossImageUrl = app.globalData.ossImageUrl || ''
+ const dashouguize = app.globalData.dashouguize || ''
+
+ // 加上时间戳参数,强制每次重新加载
+ const timestamp = new Date().getTime()
+ const fullImageUrl = ossImageUrl + dashouguize + '?t=' + timestamp
+
+ //console.log('规则图片URL(带时间戳):', fullImageUrl)
+
+ this.setData({
+ imageUrl: fullImageUrl
+ })
+ },
+
+ // 图片加载成功
+ onImageLoad(e) {
+ //console.log('图片加载成功,原始尺寸:', e.detail.width, 'x', e.detail.height)
+
+ // 根据图片实际高度设置容器高度
+ // 图片宽度为屏幕宽度,高度按比例计算
+ const systemInfo = wx.getSystemInfoSync()
+ const screenWidth = systemInfo.screenWidth // 单位:px
+
+ // 图片的实际像素尺寸
+ const imageWidth = e.detail.width
+ const imageHeight = e.detail.height
+
+ // 计算在小程序中显示的高度(保持比例)
+ // 图片在小程序中宽度为100%屏幕宽度,高度按比例缩放
+ const displayHeight = (imageHeight / imageWidth) * screenWidth
+
+ //console.log('计算后显示高度:', displayHeight, 'px')
+
+ // 设置容器高度为图片高度
+ this.setData({
+ containerHeight: displayHeight
+ })
+ },
+
+ // 图片加载失败
+ onImageError() {
+ console.error('图片加载失败')
+ wx.showToast({
+ title: '图片加载失败',
+ icon: 'none',
+ duration: 2000
+ })
+ },
+
+ // 下拉刷新
+ onPullDownRefresh() {
+ // 重新构建图片URL(带新时间戳)
+ this.buildImageUrl()
+ // 停止下拉刷新
+ wx.stopPullDownRefresh()
+ }
+ })
\ No newline at end of file
diff --git a/miniprogram/pages/dashouguize/dashouguize.json b/miniprogram/pages/dashouguize/dashouguize.json
new file mode 100644
index 0000000..62ab6de
--- /dev/null
+++ b/miniprogram/pages/dashouguize/dashouguize.json
@@ -0,0 +1,11 @@
+{
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification"
+ },
+ "navigationBarTitleText": "用户规则",
+ "navigationBarBackgroundColor": "#ffffff",
+ "navigationBarTextStyle": "black",
+ "backgroundColor": "#ffffff",
+ "enablePullDownRefresh": false,
+ "backgroundTextStyle": "dark"
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/dashouguize/dashouguize.wxml b/miniprogram/pages/dashouguize/dashouguize.wxml
new file mode 100644
index 0000000..5b0e643
--- /dev/null
+++ b/miniprogram/pages/dashouguize/dashouguize.wxml
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/miniprogram/pages/dashouguize/dashouguize.wxss b/miniprogram/pages/dashouguize/dashouguize.wxss
new file mode 100644
index 0000000..f72837c
--- /dev/null
+++ b/miniprogram/pages/dashouguize/dashouguize.wxss
@@ -0,0 +1,15 @@
+/* 页面容器 */
+.container {
+ width: 100%;
+ position: relative;
+ overflow-y: auto;
+ background-color: #ffffff;
+ }
+
+ /* 规则图片样式 - 绝对定位,从顶部开始 */
+ .rule-image {
+ position: absolute;
+ top: 0;
+ left: 0;
+ display: block;
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/dashoupaihang/dashoupaihang.js b/miniprogram/pages/dashoupaihang/dashoupaihang.js
index de9b858..8213500 100644
--- a/miniprogram/pages/dashoupaihang/dashoupaihang.js
+++ b/miniprogram/pages/dashoupaihang/dashoupaihang.js
@@ -1,9 +1,10 @@
/**
- * 统一排行榜(打手/管事/组长/商家)
+ * 统一排行榜(打手/管事/组长/商家)— 对齐星雀 fighter-rank,排序按收益/流水
* POST /yonghu/phbhqsj
*/
import request from '../../utils/request.js'
import { resolveAvatarUrl, getDefaultAvatarUrl } from '../../utils/avatar.js'
+import { getPaihangImgUrls } from '../../utils/page-assets.js'
const RANK_API = '/yonghu/phbhqsj'
@@ -32,8 +33,8 @@ const ROLE_META = {
mainType: 'money',
metrics: [
{ field: 'jiedan_zongliang', label: '接单量', type: 'int' },
- { field: 'chengjiao_zongliang', label: '成交量', type: 'int' },
{ field: 'jiedan_zonge', label: '接单额', type: 'money' },
+ { field: 'chengjiao_zongliang', label: '成交量', type: 'int' },
],
},
guanshi: {
@@ -71,7 +72,7 @@ const ROLE_META = {
function buildAvatar(path, app) {
const def = getDefaultAvatarUrl(app)
- if (!path) return def
+ if (!path || path === 'null' || path === 'undefined') return def
return resolveAvatarUrl(path, app) || def
}
@@ -83,7 +84,7 @@ Page({
currentRole: 'dashou',
currentDate: '今日',
roleMeta: ROLE_META.dashou,
- sortLabel: '成交总额',
+ sortLabel: '收益',
isLoading: true,
rankList: [],
topThree: [],
@@ -95,34 +96,34 @@ Page({
onLoad(options) {
const type = options.type || options.rankType || 'dashou'
const validRole = ROLE_META[type] ? type : 'dashou'
+ this._onConfigReady = () => this.initPageAssets()
+ getApp().on('configReady', this._onConfigReady)
this.initPageAssets()
this.applyRole(validRole, false)
this.fetchRankList()
},
+ onUnload() {
+ if (this._onConfigReady) {
+ getApp().off('configReady', this._onConfigReady)
+ }
+ },
+
onPullDownRefresh() {
+ this.initPageAssets()
this.fetchRankList().finally(() => wx.stopPullDownRefresh())
},
initPageAssets() {
this.syncDefaultAvatar()
- const g = getApp().globalData
- const oss = (g.ossImageUrl || '').replace(/\/$/, '')
- const assets = g.paihangAssets || {}
- const url = (p) => (p ? `${oss}/${String(p).replace(/^\//, '')}` : '')
- this.setData({
- imgUrls: {
- pageBg: url(assets.pageBg) || `${oss}/beijing/paihangbang/page-bg.jpg`,
- cardBg: url(assets.cardBg) || `${oss}/beijing/paihangbang/list-card-bg.png`,
- emptyIcon: url(assets.emptyIcon) || `${oss}/beijing/paihangbang/empty.png`,
- refreshIcon: url(assets.refreshIcon) || `${oss}/beijing/paihangbang/icon-refresh.png`,
- },
- })
+ this.setData({ imgUrls: getPaihangImgUrls() })
},
syncDefaultAvatar() {
const def = getDefaultAvatarUrl()
- if (def) this.setData({ defaultAvatar: def })
+ if (def && def !== this.data.defaultAvatar) {
+ this.setData({ defaultAvatar: def })
+ }
},
ensureAvatars() {
@@ -134,7 +135,8 @@ Page({
this.setData({ defaultAvatar: def })
return
}
- const isValid = (url) => url && typeof url === 'string' && (url.startsWith('http') || (oss && url.indexOf(oss) === 0))
+ const isValid = (url) =>
+ url && typeof url === 'string' && (url.startsWith('http') || (oss && url.indexOf(oss) === 0))
const fix = (item) => ({ ...item, avatar: isValid(item.avatar) ? item.avatar : def })
this.setData({
defaultAvatar: def,
@@ -165,6 +167,7 @@ Page({
},
onRefreshTap() {
+ this.initPageAssets()
this.fetchRankList()
},
@@ -256,6 +259,7 @@ Page({
},
onShow() {
+ this.initPageAssets()
this.syncDefaultAvatar()
this.ensureAvatars()
this.registerNotificationComponent()
diff --git a/miniprogram/pages/dashoupaihang/dashoupaihang.wxss b/miniprogram/pages/dashoupaihang/dashoupaihang.wxss
index 748f24e..227822b 100644
--- a/miniprogram/pages/dashoupaihang/dashoupaihang.wxss
+++ b/miniprogram/pages/dashoupaihang/dashoupaihang.wxss
@@ -75,6 +75,7 @@ page {
font-weight: 600;
background: rgba(107, 163, 247, 0.45);
border-color: rgba(107, 163, 247, 0.65);
+ box-shadow: 0 0 20rpx rgba(107, 163, 247, 0.2);
}
.date-grid {
@@ -183,16 +184,19 @@ page {
.badge-first {
color: #fff;
background: linear-gradient(145deg, #6BA3F7, #4A7FD4);
+ box-shadow: 0 4rpx 16rpx rgba(107, 163, 247, 0.4);
}
.badge-silver {
color: #fff;
background: linear-gradient(145deg, #A8B4C4, #7A8899);
+ box-shadow: 0 4rpx 12rpx rgba(168, 180, 196, 0.25);
}
.badge-bronze {
color: #fff;
background: linear-gradient(145deg, #C4A882, #9A7858);
+ box-shadow: 0 4rpx 12rpx rgba(196, 168, 130, 0.25);
}
.p-avatar-wrap {
@@ -201,7 +205,12 @@ page {
margin-bottom: 12rpx;
}
-.wrap-first { padding: 5rpx; background: linear-gradient(145deg, #8EB8FF, #5B8FD8); }
+.wrap-first {
+ padding: 5rpx;
+ background: linear-gradient(145deg, #8EB8FF, #5B8FD8);
+ box-shadow: 0 6rpx 24rpx rgba(107, 163, 247, 0.3);
+}
+
.wrap-silver { background: linear-gradient(145deg, #C8D0DA, #909AA8); }
.wrap-bronze { background: linear-gradient(145deg, #D4B896, #A88860); }
@@ -222,6 +231,7 @@ page {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
+ text-align: center;
}
.p-name-first { font-size: 28rpx; color: #fff; }
@@ -270,6 +280,16 @@ page {
.p-pedestal {
width: 88%;
border-radius: 20rpx 20rpx 0 0;
+ position: relative;
+}
+
+.p-pedestal::before {
+ content: '';
+ position: absolute;
+ top: 0; left: 10%; right: 10%;
+ height: 2rpx;
+ background: rgba(255,255,255,0.15);
+ border-radius: 2rpx;
}
.pedestal-first {
@@ -335,6 +355,7 @@ page {
align-items: center;
padding: 20rpx 22rpx;
background: rgba(12, 16, 28, 0.92);
+ backdrop-filter: blur(8px);
}
.lc-rank {
diff --git a/miniprogram/pages/dashouxiaoxi/dashouxiaoxi.js b/miniprogram/pages/dashouxiaoxi/dashouxiaoxi.js
new file mode 100644
index 0000000..3b7e862
--- /dev/null
+++ b/miniprogram/pages/dashouxiaoxi/dashouxiaoxi.js
@@ -0,0 +1,684 @@
+// pages/cfss/cfss.js
+import request from '../../utils/request.js'
+const app = getApp()
+const MEIYE_TIAOSHU = 5
+
+Page({
+ data: {
+ zongshu: 0,
+ daichuli: 0,
+ yichuli: 0,
+ shenfen: 0,
+ chufaList: [],
+ dangqianye: 1,
+ haiyougengduo: true,
+ jiazhaozhong: false,
+ jiazhaigengduo: false,
+ scrollHeight: 0,
+ showXiangqing: false,
+ xuanzhongChufa: {},
+ showShensuModal: false,
+ shensuLiyou: '',
+ shensuTupian: [],
+ shensuTupianUrls: [],
+ shangchuanJindu: 0,
+ shangchuanZongshu: 0,
+ jinduWidth: '0%',
+ isShangchuanzhong: false,
+ ossImageUrl: '',
+ fangdouTimer: null
+ },
+
+ onLoad(options) {
+ this.registerNotificationComponent()
+ this.initOSSUrl()
+ this.jisuanGaodu()
+ this.chushihuaShuju()
+ },
+
+
+
+
+
+
+
+ onShow() {
+ this.registerNotificationComponent()
+ },
+
+ onUnload() {
+ if (this.data.fangdouTimer) clearTimeout(this.data.fangdouTimer)
+ },
+
+ onReachBottom() {
+ this.shanglaShuaxin()
+ },
+
+ registerNotificationComponent() {
+ const notificationComp = this.selectComponent('#global-notification')
+ if (notificationComp && notificationComp.showNotification) {
+ app.globalData.globalNotification = {
+ show: (data) => notificationComp.showNotification(data),
+ hide: () => notificationComp.hideNotification()
+ }
+ }
+ },
+
+ initOSSUrl() {
+ const ossImageUrl = app.globalData.ossImageUrl || ''
+ this.setData({ ossImageUrl })
+ },
+
+ jisuanGaodu() {
+ const systemInfo = wx.getSystemInfoSync()
+ const windowHeight = systemInfo.windowHeight
+ const height = windowHeight - 200 - 100 - 40
+ this.setData({
+ scrollHeight: height > 0 ? height : 400
+ })
+ },
+
+ chushihuaShuju() {
+ this.setData({
+ dangqianye: 1,
+ chufaList: [],
+ haiyougengduo: true
+ })
+ this.jiazhuoquTongji()
+ this.jiazhuoquChufaList()
+ },
+
+ async jiazhuoquTongji() {
+ try {
+ const res = await request({
+ url: '/yonghu/dshqcfjl',
+ method: 'POST',
+ data: { qingqiu_tongji: true }
+ })
+ if (res.statusCode === 200 && res.data.code === 0) {
+ const data = res.data.data
+ this.setData({
+ zongshu: data.zongshu || 0,
+ daichuli: data.daichuli || 0,
+ yichuli: data.yichuli || 0
+ })
+ }
+ } catch (error) {
+ console.error('获取统计信息失败:', error)
+ }
+ },
+
+ async jiazhuoquChufaList(isLoadMore = false) {
+ if (this.data.jiazhaozhong || this.data.jiazhaigengduo) return
+ if (isLoadMore) {
+ if (!this.data.haiyougengduo) return
+ this.setData({ jiazhaigengduo: true })
+ } else {
+ this.setData({ jiazhaozhong: true })
+ }
+
+ try {
+ const params = {
+ page: this.data.dangqianye,
+ page_size: MEIYE_TIAOSHU
+ }
+ if (this.data.shenfen === 0) {
+ params.zhuangtai = 0
+ } else {
+ params.zhuangtai = 1
+ }
+
+ const res = await request({
+ url: '/yonghu/dshqcfjl',
+ method: 'POST',
+ data: params
+ })
+
+ //console.log('🔴【后端返回的数据】:', res.data)
+
+ 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) {
+ if (typeof item.create_time === 'number') {
+ displayTime = String(item.create_time)
+ } else if (typeof item.create_time === 'string') {
+ displayTime = item.create_time
+ } else {
+ displayTime = String(item.create_time)
+ }
+ }
+
+ // 处理显示状态
+ let displayStatus = '未知状态'
+ let statusClass = 'zhuangtai-weizhi'
+ const statusNum = Number(item.sqzhuangtai)
+
+ if (!isNaN(statusNum)) {
+ if (statusNum === 0) {
+ displayStatus = '待处罚'
+ statusClass = 'zhuangtai-daichuli'
+ } else if (statusNum === 1) {
+ displayStatus = '已处罚'
+ statusClass = 'zhuangtai-yichufa'
+ } else if (statusNum === 2) {
+ displayStatus = '已驳回'
+ statusClass = 'zhuangtai-yibohui'
+ } else if (statusNum === 3) {
+ displayStatus = '申诉中'
+ statusClass = 'zhuangtai-shensuzhong'
+ }
+ }
+
+ // 处理图片URL(在数据加载时就拼接完整URL)
+ const fullZhengjuTupian = (item.zhengju_tupian || []).map(url => this.getFullImageUrl(url))
+ const fullShensuTupian = (item.shensu_tupian || []).map(url => this.getFullImageUrl(url))
+
+ return {
+ ...item,
+ display_time: displayTime, // 🔴 处理好的显示时间
+ display_status: displayStatus, // 🔴 处理好的显示状态
+ status_class: statusClass, // 🔴 处理好的状态样式类
+ zhengju_tupian: item.zhengju_tupian || [],
+ shensu_tupian: item.shensu_tupian || [],
+ full_zhengju_tupian: fullZhengjuTupian, // 🔴 完整图片URL
+ full_shensu_tupian: fullShensuTupian // 🔴 完整图片URL
+ }
+ })
+
+ //console.log('🔴【处理后的数据】:', processedList)
+
+ let newList
+ if (isLoadMore) {
+ newList = [...this.data.chufaList, ...processedList]
+ } else {
+ newList = processedList
+ }
+
+ const hasMore = data.has_more === true
+ this.setData({
+ chufaList: newList,
+ haiyougengduo: hasMore,
+ dangqianye: this.data.dangqianye + 1
+ })
+ } else {
+ this.setData({ haiyougengduo: false })
+ }
+ } else {
+ throw new Error(res.data?.msg || '加载处罚记录失败')
+ }
+ } catch (error) {
+ console.error('加载处罚记录失败:', error)
+ wx.showToast({
+ title: error.message || '加载失败',
+ icon: 'none'
+ })
+ this.setData({ haiyougengduo: false })
+ } finally {
+ if (isLoadMore) {
+ this.setData({ jiazhaigengduo: false })
+ } else {
+ this.setData({ jiazhaozhong: false })
+ }
+ }
+ },
+
+ qiehuanShenfen(e) {
+ const type = parseInt(e.currentTarget.dataset.type)
+ if (this.data.shenfen === type) return
+ this.setData({
+ shenfen: type,
+ dangqianye: 1,
+ chufaList: [],
+ haiyougengduo: true
+ })
+ this.jiazhuoquChufaList()
+ },
+
+ shanglaShuaxin() {
+ 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.jiazhuoquChufaList(true)
+ }, 300)
+ this.setData({ fangdouTimer: timer })
+ },
+
+ chakanXiangqing(e) {
+ const item = e.currentTarget.dataset.item
+ this.setData({
+ xuanzhongChufa: item,
+ showXiangqing: true
+ })
+ },
+
+ guanbiXiangqing() {
+ this.setData({
+ showXiangqing: false,
+ xuanzhongChufa: {}
+ })
+ },
+
+ openShensuModal() {
+ if (this.data.xuanzhongChufa.ssliyou ||
+ (this.data.xuanzhongChufa.shensu_tupian && this.data.xuanzhongChufa.shensu_tupian.length > 0)) {
+ wx.showToast({
+ title: '您已经提交过申诉,请等待处理结果',
+ icon: 'none'
+ })
+ return
+ }
+ this.setData({
+ showShensuModal: true,
+ shensuLiyou: '',
+ shensuTupian: [],
+ shensuTupianUrls: [],
+ shangchuanJindu: 0,
+ shangchuanZongshu: 0,
+ jinduWidth: '0%'
+ })
+ },
+
+ closeShensuModal() {
+ this.setData({
+ showShensuModal: false,
+ shensuLiyou: '',
+ shensuTupian: [],
+ shensuTupianUrls: []
+ })
+ },
+
+ onShensuLiyouInput(e) {
+ const value = e.detail.value.slice(0, 500)
+ this.setData({ shensuLiyou: value })
+ },
+
+ 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(file => file.tempFilePath)]
+ this.setData({ shensuTupian: newTupian.slice(0, 9) })
+ }
+ })
+ },
+
+ deleteShensuTupian(e) {
+ const index = e.currentTarget.dataset.index
+ const newTupian = [...this.data.shensuTupian]
+ newTupian.splice(index, 1)
+ this.setData({ shensuTupian: newTupian })
+ },
+
+ yulanShensuTupian(e) {
+ const index = e.currentTarget.dataset.index
+ const urls = this.data.shensuTupian
+ if (!urls[index]) return
+ wx.previewImage({
+ current: urls[index],
+ urls: urls
+ })
+ },
+
+ lianxiKefu() {
+ const kefuLink = app.globalData.kefuConfig?.link || '';
+ const corpId = app.globalData.kefuConfig?.enterpriseId || '';
+
+ if (!kefuLink || !corpId) {
+ wx.showToast({
+ title: '客服配置未加载',
+ icon: 'none'
+ });
+ return;
+ }
+
+ wx.openCustomerServiceChat({
+ extInfo: { url: kefuLink },
+ corpId: corpId,
+ success: (res) => {
+ console.log('跳转客服成功', res);
+ },
+ fail: (err) => {
+ console.error('跳转客服失败', err);
+ wx.showToast({
+ title: '暂时无法连接客服',
+ icon: 'none'
+ });
+ }
+ });
+ },
+
+ async getCOSZhengshu() {
+ try {
+ 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
+ } else {
+ throw new Error(res?.data?.msg || '获取上传凭证失败')
+ }
+ } catch (error) {
+ console.error('获取COS凭证失败:', error)
+ throw error
+ }
+ },
+
+ 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: async (options, callback) => {
+ const authParams = {
+ 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)
+ }
+ callback(authParams)
+ }
+ })
+ return { cos, bucket, region }
+ },
+
+ async shangchuanDanZhangTupian(filePath, cosKey, index, cosInstance, bucket, region) {
+ return new Promise((resolve) => {
+ const timeoutId = setTimeout(() => {
+ resolve({ status: 'optimistic_success', key: cosKey })
+ }, 2000)
+ cosInstance.uploadFile({
+ Bucket: bucket,
+ Region: region,
+ Key: cosKey,
+ FilePath: filePath,
+ onProgress: (progressInfo) => {
+ const percent = Math.round(progressInfo.percent * 100)
+ if (percent === 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 = ''
+
+ // 方案1:直接从全局缓存获取
+ const uid = wx.getStorageSync('uid')
+ if (uid) {
+ yonghuid = String(uid).padStart(7, '0')
+ //console.log('🔴【从uid获取yonghuid】', yonghuid)
+ }
+
+ // 方案2:如果还没有,从用户信息获取
+ if (!yonghuid && app.globalData.userInfo && app.globalData.userInfo.yonghuid) {
+ yonghuid = String(app.globalData.userInfo.yonghuid).padStart(7, '0')
+ //console.log('🔴【从userInfo获取yonghuid】', yonghuid)
+ }
+
+ // 方案3:如果还没有,直接提示并返回
+ 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
+ const random = Math.floor(Math.random() * 10000)
+ const fileExt = this.getFileExtension(this.data.shensuTupian[i])
+ const fileName = `${yonghuid}_${timestamp}_${random}${fileExt}`
+ const cosKey = `a_long/chfajltp/dssstp/${fileName}`
+ preGeneratedUrls.push(cosKey)
+ }
+
+ let cosClient, bucket, region
+ try {
+ const tokenData = await this.getCOSZhengshu()
+ const { cos, bucket: cosBucket, region: cosRegion } = this.initCOSClient(tokenData)
+ cosClient = cos
+ bucket = cosBucket
+ region = cosRegion
+ } catch (error) {
+ wx.showToast({ title: '上传初始化失败', icon: 'none' })
+ this.setData({ isShangchuanzhong: false })
+ return []
+ }
+
+ const uploadTasks = []
+ for (let i = 0; i < total; i++) {
+ const task = this.shangchuanDanZhangTupian(
+ this.data.shensuTupian[i],
+ preGeneratedUrls[i],
+ i,
+ cosClient,
+ bucket,
+ region
+ ).then((result) => {
+ const currentDone = i + 1
+ const jinduPercent = ((currentDone / total) * 100).toFixed(0)
+ this.setData({
+ shangchuanJindu: currentDone,
+ jinduWidth: `${jinduPercent}%`
+ })
+ return result
+ })
+ uploadTasks.push(task)
+ }
+ await Promise.all(uploadTasks)
+ this.setData({ isShangchuanzhong: false })
+ return preGeneratedUrls
+ },
+
+ getFileExtension(filePath) {
+ const lastDotIndex = filePath.lastIndexOf('.')
+ if (lastDotIndex === -1) return '.jpg'
+ const ext = filePath.substring(lastDotIndex).toLowerCase()
+ const allowedExts = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp']
+ return allowedExts.includes(ext) ? ext : '.jpg'
+ },
+
+ 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 tupianUrls = await this.piliangShangchuanShensuTupian()
+ if (!tupianUrls || tupianUrls.length === 0) throw new Error('图片上传失败')
+ await this.tijiaoShensuData(tupianUrls)
+ } catch (error) {
+ console.error('提交申诉失败:', error)
+ wx.showToast({ title: error.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) {
+ this.updateChufaRecord({
+ sqzhuangtai: 3,
+ ssliyou: this.data.shensuLiyou,
+ shensu_tupian: tupianUrls
+ })
+ this.setData({
+ showShensuModal: false,
+ showXiangqing: false,
+ shensuLiyou: '',
+ shensuTupian: [],
+ xuanzhongChufa: {}
+ })
+ wx.showToast({ title: '申诉提交成功,请等待处理', icon: 'success' })
+ setTimeout(() => { this.chushihuaShuju() }, 500)
+ } else {
+ throw new Error(res.data?.msg || '提交申诉失败')
+ }
+ } catch (error) {
+ console.error('提交申诉数据失败:', error)
+ wx.showToast({ title: error.message || '提交失败', icon: 'none' })
+ }
+ },
+
+ updateChufaRecord(newData) {
+ const newList = this.data.chufaList.map(item => {
+ if (item.id === this.data.xuanzhongChufa.id) {
+ const updatedItem = { ...item, ...newData }
+ // 🔴 更新显示字段
+ if (newData.sqzhuangtai === 3) {
+ updatedItem.display_status = '申诉中'
+ updatedItem.status_class = 'zhuangtai-shensuzhong'
+ }
+ if (newData.shensu_tupian) {
+ updatedItem.full_shensu_tupian = newData.shensu_tupian.map(url => this.getFullImageUrl(url))
+ }
+ return updatedItem
+ }
+ return item
+ })
+ this.setData({ chufaList: newList })
+ },
+
+ // 🔴【图片URL拼接 - 确保返回完整URL】
+ getFullImageUrl(relativeUrl) {
+ //console.log('🔴【图片拼接】输入:', relativeUrl)
+
+ if (!relativeUrl) return ''
+ if (relativeUrl.startsWith('http')) {
+ //console.log('🔴【已经是完整URL】返回:', relativeUrl)
+ return relativeUrl
+ }
+
+ const ossUrl = this.data.ossImageUrl
+ if (!ossUrl) {
+ //console.log('🔴【OSS地址为空】返回原始URL:', relativeUrl)
+ return relativeUrl
+ }
+
+ let fullUrl
+ if (ossUrl.endsWith('/') && relativeUrl.startsWith('/')) {
+ fullUrl = ossUrl + relativeUrl.substring(1)
+ } else if (!ossUrl.endsWith('/') && !relativeUrl.startsWith('/')) {
+ fullUrl = ossUrl + '/' + relativeUrl
+ } else {
+ fullUrl = ossUrl + relativeUrl
+ }
+
+ // console.log('🔴【拼接后URL】:', fullUrl)
+ return fullUrl
+ },
+
+ yulanTupian(e) {
+ const currentUrl = e.currentTarget.dataset.url
+ const urls = e.currentTarget.dataset.urls || []
+ if (!currentUrl) return
+
+ const fullCurrentUrl = currentUrl.startsWith('http') ? currentUrl : this.getFullImageUrl(currentUrl)
+ const fullUrls = urls.map(url => url.startsWith('http') ? url : this.getFullImageUrl(url))
+
+ wx.previewImage({
+ current: fullCurrentUrl,
+ urls: fullUrls.length > 0 ? fullUrls : [fullCurrentUrl]
+ })
+ },
+
+ fuzhiWenben(e) {
+ const text = e.currentTarget.dataset.text
+ if (!text) return
+
+ wx.setClipboardData({
+ data: text,
+ success: () => {
+ wx.showToast({ title: '复制成功', icon: 'success' })
+ },
+ fail: () => {
+ wx.showToast({ title: '复制失败', icon: 'none' })
+ }
+ })
+ },
+
+ // 🔴【保留原函数但已不再使用(用于向后兼容)】
+ formatShijian(shijian) {
+ if (!shijian) return '--'
+ return String(shijian)
+ },
+
+ getZhuangtaiText(zhuangtai) {
+ const statusNum = Number(zhuangtai)
+ if (statusNum === 0) return '待处罚'
+ if (statusNum === 1) return '已处罚'
+ if (statusNum === 2) return '已驳回'
+ if (statusNum === 3) return '申诉中'
+ return '未知状态'
+ },
+
+ getZhuangtaiClass(zhuangtai) {
+ const statusNum = Number(zhuangtai)
+ if (statusNum === 0) return 'zhuangtai-daichuli'
+ if (statusNum === 1) return 'zhuangtai-yichufa'
+ if (statusNum === 2) return 'zhuangtai-yibohui'
+ if (statusNum === 3) return 'zhuangtai-shensuzhong'
+ return 'zhuangtai-weizhi'
+ }
+})
\ No newline at end of file
diff --git a/miniprogram/pages/dashouxiaoxi/dashouxiaoxi.json b/miniprogram/pages/dashouxiaoxi/dashouxiaoxi.json
new file mode 100644
index 0000000..e3c4071
--- /dev/null
+++ b/miniprogram/pages/dashouxiaoxi/dashouxiaoxi.json
@@ -0,0 +1,10 @@
+{
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification"
+ },
+ "navigationBarTitleText": "处罚记录",
+ "navigationBarBackgroundColor": "#0a0a12",
+ "navigationBarTextStyle": "white",
+ "backgroundColor": "#0a0a12",
+ "enablePullDownRefresh": false
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/dashouxiaoxi/dashouxiaoxi.wxml b/miniprogram/pages/dashouxiaoxi/dashouxiaoxi.wxml
new file mode 100644
index 0000000..a1e1265
--- /dev/null
+++ b/miniprogram/pages/dashouxiaoxi/dashouxiaoxi.wxml
@@ -0,0 +1,316 @@
+
+
+
+
+ 处罚统计
+
+
+ 总记录
+ {{ zongshu }}
+
+
+ 待处理
+ {{ daichuli }}
+
+
+ 已处理
+ {{ yichuli }}
+
+
+
+
+
+
+
+ 待处理
+ {{ daichuli }}
+
+
+ 已处理
+ {{ yichuli }}
+
+
+
+
+
+
+
+
+
+
+
+
+ 加载中...
+
+
+
+
+
+
+
+
+ {{ item.display_time }}
+
+
+ {{ item.display_status }}
+
+
+
+
+
+ 申请人:
+ {{ item.qingqiuid || '--' }}
+
+
+
+
+ 处罚理由:
+ {{ item.cfliyou || '无' }}
+
+
+
+
+ 订单编号:
+ {{ item.dingdan_id || '--' }}
+
+
+
+
+
+
+
+
+
+
+
+ 加载更多...
+
+
+
+
+ —— 没有更多记录了 ——
+
+
+
+
+ ⚡
+ 暂无处罚记录
+ 所有事务已处理完毕
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 基本信息
+
+ 订单编号:
+
+ {{ xuanzhongChufa.dingdan_id || '--' }}
+ 复制
+
+
+
+ 申请人ID:
+
+ {{ xuanzhongChufa.qingqiuid || '--' }}
+ 复制
+
+
+
+
+ 创建时间:
+ {{ xuanzhongChufa.display_time || '--' }}
+
+
+
+
+
+ 处罚信息
+
+ 处罚状态:
+
+
+ {{ xuanzhongChufa.display_status }}
+
+
+
+ 处罚理由:
+ {{ xuanzhongChufa.cfliyou || '无' }}
+
+
+
+
+ 证据图片:
+
+
+
+
+
+
+
+
+
+
+
+
+ 申诉信息
+
+ 申诉理由:
+ {{ xuanzhongChufa.ssliyou }}
+
+
+
+ 申诉图片:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 申诉理由
+
+ {{ shensuLiyou.length }}/500
+
+
+
+
+ 申诉图片
+ 最多可上传9张图片
+
+
+
+
+
+
+
+
+ ×
+
+
+
+
+
+
+ +
+ 添加图片
+
+
+
+
+
+
+ 上传进度
+
+
+
+ {{ shangchuanJindu }}/{{ shangchuanZongshu }}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/dashouxiaoxi/dashouxiaoxi.wxss b/miniprogram/pages/dashouxiaoxi/dashouxiaoxi.wxss
new file mode 100644
index 0000000..c9a6d32
--- /dev/null
+++ b/miniprogram/pages/dashouxiaoxi/dashouxiaoxi.wxss
@@ -0,0 +1,963 @@
+/* pages/cfss/cfss.wxss */
+/* 🔴【恢复您原来的深色背景,但调淡一点】 */
+
+:root {
+ /* 🔴【颜色调淡方案】 */
+ --cyber-blue: rgba(0, 243, 255, 0.7); /* 蓝色调淡70% */
+ --cyber-pink: rgba(255, 0, 255, 0.7); /* 粉色调淡70% */
+ --cyber-purple: rgba(157, 0, 255, 0.7); /* 紫色调淡70% */
+ --cyber-green: rgba(0, 255, 157, 0.7); /* 绿色调淡70% */
+ --cyber-red: rgba(255, 0, 102, 0.7); /* 红色调淡70% */
+
+ /* 🔴【背景改为淡灰色,不是深色,也不是白色】 */
+ --cyber-bg: #2a2a3a; /* 深灰色背景,比原来的淡 */
+ --cyber-card: #333344; /* 卡片:比背景稍深的灰色 */
+ --cyber-border: rgba(255, 255, 255, 0.15); /* 边框:半透明白色 */
+ --cyber-glow: rgba(0, 243, 255, 0.2); /* 光效:调淡 */
+
+ /* 文字颜色保持不变 */
+ --cyber-text: #ffffff;
+ --cyber-text-light: #e6e6e6;
+ --cyber-text-lighter: #cccccc;
+}
+
+.cfss-container {
+ padding: 30rpx;
+ background: var(--cyber-bg);
+ min-height: 100vh;
+ color: var(--cyber-text) !important;
+ position: relative;
+ overflow: hidden;
+ box-sizing: border-box;
+}
+
+/* 恢复网格背景 */
+.cfss-container::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-image:
+ linear-gradient(rgba(0, 243, 255, 0.05) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(0, 243, 255, 0.05) 1px, transparent 1px);
+ background-size: 40rpx 40rpx;
+ pointer-events: none;
+ opacity: 0.15;
+ z-index: 0;
+}
+
+/* ====== 统计卡片 ====== */
+.tongji-card {
+ background: rgba(255, 0, 102, 0.3); /* 🔴 调淡为30%透明度 */
+ border-radius: 20rpx;
+ padding: 30rpx;
+ margin-bottom: 30rpx;
+ border: 1rpx solid var(--cyber-border);
+ box-shadow:
+ 0 0 20rpx rgba(255, 0, 102, 0.2),
+ inset 0 0 10rpx rgba(0, 243, 255, 0.05);
+ position: relative;
+ overflow: hidden;
+ z-index: 1;
+}
+
+.tongji-title {
+ color: var(--cyber-blue);
+ font-size: 34rpx;
+ font-weight: bold;
+ margin-bottom: 30rpx;
+ text-shadow: 0 0 8rpx var(--cyber-blue);
+ letter-spacing: 2rpx;
+}
+
+.tongji-content {
+ display: flex;
+ justify-content: space-between;
+}
+
+.tongji-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ flex: 1;
+}
+
+.tongji-label {
+ color: var(--cyber-text-light) !important;
+ font-size: 26rpx;
+ margin-bottom: 15rpx;
+ opacity: 0.9;
+}
+
+.tongji-value {
+ color: var(--cyber-text) !important;
+ font-size: 44rpx;
+ font-weight: bold;
+ text-shadow: 0 0 8rpx currentColor;
+}
+
+.daichuli-value {
+ color: var(--cyber-red) !important;
+ text-shadow: 0 0 8rpx rgba(255, 0, 102, 0.3);
+}
+
+.yichuli-value {
+ color: var(--cyber-green) !important;
+ text-shadow: 0 0 8rpx rgba(0, 255, 157, 0.3);
+}
+
+/* ====== 类型切换 ====== */
+.leixing-qiehuan {
+ display: flex;
+ background: rgba(255, 0, 102, 0.3); /* 🔴 调淡为30%透明度 */
+ border-radius: 16rpx;
+ padding: 10rpx;
+ margin-bottom: 30rpx;
+ border: 1rpx solid var(--cyber-border);
+ box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.2);
+ z-index: 1;
+}
+
+.leixing-item {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 25rpx 0;
+ border-radius: 12rpx;
+ transition: all 0.3s;
+}
+
+.leixing-item:active {
+ transform: scale(0.98);
+}
+
+.leixing-active {
+ background: linear-gradient(135deg, rgba(0, 243, 255, 0.15), rgba(157, 0, 255, 0.15));
+ box-shadow: 0 0 15rpx rgba(0, 243, 255, 0.2);
+}
+
+.leixing-text {
+ font-size: 28rpx;
+ font-weight: bold;
+ margin-bottom: 8rpx;
+ color: var(--cyber-text-light) !important;
+}
+
+.leixing-active .leixing-text {
+ color: var(--cyber-text) !important;
+ text-shadow: 0 0 8rpx var(--cyber-blue);
+}
+
+.leixing-count {
+ font-size: 24rpx;
+ color: var(--cyber-text-lighter) !important;
+ opacity: 0.9;
+}
+
+.leixing-active .leixing-count {
+ color: rgba(255, 255, 255, 0.9) !important;
+}
+
+/* ====== 处罚列表 ====== */
+.chufa-list {
+ margin-bottom: 0;
+ padding-bottom: 30rpx;
+ z-index: 1;
+}
+
+/* 加载动画 */
+.jiazai-zhong {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 60rpx 0;
+ color: var(--cyber-blue);
+}
+
+.loading-animation {
+ display: flex;
+ justify-content: center;
+ margin-bottom: 20rpx;
+}
+
+.loading-dot {
+ width: 20rpx;
+ height: 20rpx;
+ border-radius: 50%;
+ background-color: var(--cyber-blue);
+ margin: 0 10rpx;
+ animation: pulse 1.5s infinite ease-in-out;
+}
+
+.loading-dot:nth-child(2) {
+ animation-delay: 0.2s;
+}
+
+.loading-dot:nth-child(3) {
+ animation-delay: 0.4s;
+}
+
+@keyframes pulse {
+ 0%, 100% {
+ transform: scale(0.8);
+ opacity: 0.5;
+ }
+ 50% {
+ transform: scale(1.2);
+ opacity: 1;
+ }
+}
+
+/* 🔴【关键:处罚卡片背景改为淡灰色】 */
+.chufa-card {
+ background: rgba(50, 50, 65, 0.8); /* 🔴 淡灰色卡片,半透明 */
+ border-radius: 20rpx;
+ padding: 30rpx;
+ margin-bottom: 30rpx;
+ border: 1rpx solid var(--cyber-border);
+ position: relative;
+ overflow: hidden;
+ transition: all 0.3s;
+ z-index: 1;
+}
+
+.chufa-card:active {
+ transform: translateY(-4rpx);
+ box-shadow:
+ 0 10rpx 30rpx rgba(0, 0, 0, 0.3),
+ 0 0 25rpx var(--cyber-glow);
+}
+
+.card-border {
+ position: absolute;
+ top: -1px;
+ left: -1px;
+ right: -1px;
+ bottom: -1px;
+ border-radius: 21rpx;
+ background: linear-gradient(45deg, rgba(255, 0, 255, 0.3), rgba(0, 243, 255, 0.3), rgba(255, 0, 255, 0.3), rgba(0, 243, 255, 0.3));
+ z-index: -1;
+ opacity: 0;
+ transition: opacity 0.3s ease;
+}
+
+.chufa-card:active .card-border {
+ opacity: 0.3;
+}
+
+.card-top {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 25rpx;
+}
+
+/* 🔴【时间必须显示】 */
+.shijian {
+ color: var(--cyber-text-light) !important;
+ font-size: 26rpx;
+ font-family: 'Courier New', monospace;
+ letter-spacing: 1rpx;
+ opacity: 0.9;
+}
+
+/* 🔴【状态标签 - 必须显示出来】 */
+.zhuangtai-badge {
+ padding: 8rpx 25rpx;
+ border-radius: 20rpx;
+ font-size: 24rpx;
+ font-weight: bold;
+ text-transform: uppercase;
+ letter-spacing: 2rpx;
+ border: 1rpx solid;
+ box-shadow: 0 0 8rpx;
+ color: #ffffff !important;
+}
+
+.zhuangtai-daichuli {
+ background: rgba(255, 0, 102, 0.2);
+ border-color: var(--cyber-red);
+ box-shadow: 0 0 8rpx rgba(255, 0, 102, 0.2);
+}
+
+.zhuangtai-yichufa {
+ background: rgba(0, 255, 157, 0.2);
+ border-color: var(--cyber-green);
+ box-shadow: 0 0 8rpx rgba(0, 255, 157, 0.2);
+}
+
+.zhuangtai-yibohui {
+ background: rgba(102, 102, 102, 0.2);
+ border-color: #666;
+ box-shadow: 0 0 8rpx rgba(102, 102, 102, 0.2);
+}
+
+.zhuangtai-shensuzhong {
+ background: rgba(255, 165, 0, 0.2);
+ border-color: #FFA500;
+ box-shadow: 0 0 8rpx rgba(255, 165, 0, 0.2);
+}
+
+.zhuangtai-weizhi {
+ background: rgba(128, 0, 255, 0.2); /* 未知状态:紫色 */
+ border-color: rgba(128, 0, 255, 0.5);
+ box-shadow: 0 0 8rpx rgba(128, 0, 255, 0.2);
+}
+
+/* 其他文字内容 */
+.qingqiu-row, .liyou-row, .dingdan-row {
+ display: flex;
+ align-items: flex-start;
+ margin-bottom: 20rpx;
+}
+
+.qingqiu-label, .liyou-label, .dingdan-label {
+ color: var(--cyber-text-light) !important;
+ font-size: 26rpx;
+ font-weight: bold;
+ margin-right: 15rpx;
+ opacity: 0.9;
+ flex-shrink: 0;
+}
+
+.qingqiu-value, .liyou-value, .dingdan-value {
+ color: var(--cyber-text) !important;
+ font-size: 26rpx;
+ flex: 1;
+ word-break: break-all;
+}
+
+.liyou-value {
+ line-height: 1.5;
+}
+
+.dingdan-value {
+ font-family: 'Courier New', monospace;
+ letter-spacing: 1rpx;
+}
+
+/* 加载更多 */
+.jiazai-gengduo {
+ text-align: center;
+ padding: 40rpx 0;
+ color: var(--cyber-blue);
+ position: relative;
+ overflow: hidden;
+}
+
+.scan-line {
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 2rpx;
+ background: linear-gradient(90deg, transparent, var(--cyber-blue), transparent);
+ animation: scan 2s linear infinite;
+}
+
+@keyframes scan {
+ 0% { left: -100%; }
+ 100% { left: 100%; }
+}
+
+/* 没有更多数据 */
+.meiyou-gengduo {
+ text-align: center;
+ padding: 40rpx 0;
+ color: var(--cyber-green);
+ font-size: 28rpx;
+ opacity: 0.9;
+}
+
+/* 空状态 */
+.kong-zhuangtai {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 100rpx 40rpx;
+ text-align: center;
+ z-index: 1;
+}
+
+.empty-icon {
+ font-size: 120rpx;
+ color: var(--cyber-blue);
+ margin-bottom: 30rpx;
+ text-shadow: 0 0 15rpx var(--cyber-blue);
+ animation: pulse 2s infinite;
+}
+
+.empty-text {
+ font-size: 34rpx;
+ color: var(--cyber-text) !important;
+ margin-bottom: 15rpx;
+ font-weight: bold;
+}
+
+.empty-subtext {
+ font-size: 28rpx;
+ color: var(--cyber-text-light) !important;
+ opacity: 0.8;
+}
+
+/* ====== 弹窗样式 ====== */
+.xiangqing-modal,
+.shensu-modal {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 1000;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.modal-mask {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.7);
+}
+
+/* 🔴【关键:弹窗背景改为不透明的淡灰色】 */
+.modal-content {
+ position: relative;
+ background: #3a3a4a; /* 🔴 淡灰色,不透明 */
+ border-radius: 30rpx;
+ width: 90%;
+ max-height: 80vh;
+ display: flex;
+ flex-direction: column;
+ border: 1rpx solid var(--cyber-border);
+ box-shadow: 0 0 35rpx rgba(0, 243, 255, 0.3);
+ z-index: 1001;
+ backdrop-filter: blur(0px); /* 去掉模糊效果 */
+}
+
+.small-modal {
+ max-height: 85vh;
+ width: 85%;
+}
+
+.modal-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 40rpx 30rpx 30rpx 30rpx;
+ border-bottom: 1rpx solid rgba(0, 243, 255, 0.2);
+ flex-shrink: 0;
+}
+
+.modal-title {
+ font-size: 36rpx;
+ font-weight: bold;
+ color: var(--cyber-blue);
+ text-shadow: 0 0 8rpx var(--cyber-blue);
+}
+
+.modal-close {
+ width: 60rpx;
+ height: 60rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 50%;
+ background: rgba(0, 243, 255, 0.1);
+ border: 1rpx solid rgba(0, 243, 255, 0.3);
+}
+
+.modal-close:active {
+ background: rgba(0, 243, 255, 0.2);
+ transform: scale(0.95);
+}
+
+.close-icon {
+ font-size: 40rpx;
+ color: var(--cyber-blue);
+ line-height: 1;
+}
+
+/* 弹窗主体 */
+.modal-body {
+ flex: 1;
+ padding: 30rpx;
+ overflow-y: auto;
+ min-height: 200rpx;
+ box-sizing: border-box;
+}
+
+/* 详情区域 */
+.detail-section {
+ margin-bottom: 40rpx;
+}
+
+.section-title {
+ font-size: 32rpx;
+ font-weight: bold;
+ color: var(--cyber-blue);
+ margin-bottom: 25rpx;
+ padding-bottom: 15rpx;
+ border-bottom: 1rpx solid rgba(0, 243, 255, 0.2);
+ text-shadow: 0 0 8rpx var(--cyber-blue);
+}
+
+.detail-row {
+ display: flex;
+ margin-bottom: 25rpx;
+ align-items: center;
+}
+
+.detail-label {
+ color: var(--cyber-text-light) !important;
+ font-size: 30rpx;
+ min-width: 180rpx;
+ margin-right: 20rpx;
+ line-height: 1.5;
+ opacity: 0.9;
+ flex-shrink: 0;
+}
+
+.detail-value-container {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.detail-value {
+ color: var(--cyber-text) !important;
+ font-size: 30rpx;
+ line-height: 1.5;
+ word-break: break-all;
+ opacity: 0.95;
+ flex: 1;
+ margin-right: 20rpx;
+}
+
+.fuzhi-btn {
+ color: var(--cyber-blue);
+ font-size: 24rpx;
+ padding: 8rpx 20rpx;
+ border-radius: 8rpx;
+ background: rgba(0, 243, 255, 0.1);
+ border: 1rpx solid rgba(0, 243, 255, 0.3);
+ flex-shrink: 0;
+}
+
+.fuzhi-btn:active {
+ background: rgba(0, 243, 255, 0.2);
+}
+
+.full-row {
+ flex-direction: column;
+ align-items: flex-start;
+}
+
+.full-row .detail-label {
+ margin-bottom: 10rpx;
+ min-width: auto;
+}
+
+/* 弹窗内的状态标签 */
+.zhuangtai-badge-inline {
+ padding: 8rpx 24rpx;
+ border-radius: 20rpx;
+ font-size: 26rpx;
+ font-weight: bold;
+ text-transform: uppercase;
+ letter-spacing: 1rpx;
+ border: 1rpx solid;
+ min-width: 120rpx;
+ text-align: center;
+ color: #ffffff !important;
+}
+
+/* 理由方框 */
+.liyou-full {
+ background: rgba(255, 255, 255, 0.05);
+ padding: 25rpx 30rpx;
+ border-radius: 15rpx;
+ line-height: 1.6;
+ border: 1rpx solid rgba(255, 255, 255, 0.1);
+ color: var(--cyber-text) !important;
+ width: 100%;
+ box-sizing: border-box;
+ margin: 0;
+ display: block;
+ opacity: 0.95;
+}
+
+/* 图片网格样式 */
+.tupian-grid {
+ display: flex;
+ flex-wrap: wrap;
+ margin-top: 10rpx;
+ margin-left: -5rpx;
+ margin-right: -5rpx;
+}
+
+.tupian-item {
+ width: calc(33.333% - 10rpx);
+ margin: 5rpx;
+ aspect-ratio: 1;
+ border-radius: 10rpx;
+ overflow: hidden;
+ position: relative;
+ border: 1rpx solid rgba(0, 243, 255, 0.2);
+}
+
+.tupian-image {
+ width: 100%;
+ height: 100%;
+}
+
+/* 弹窗底部按钮 */
+.modal-footer {
+ padding: 30rpx;
+ border-top: 1rpx solid rgba(0, 243, 255, 0.2);
+ flex-shrink: 0;
+}
+
+.btn-group {
+ display: flex;
+ gap: 20rpx;
+}
+
+.btn {
+ flex: 1;
+ height: 90rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 15rpx;
+ font-size: 32rpx;
+ font-weight: bold;
+ transition: all 0.3s;
+ border: 1rpx solid;
+ box-shadow: 0 0 12rpx;
+ color: var(--cyber-text) !important;
+}
+
+.btn:active {
+ transform: scale(0.98);
+}
+
+/* 按钮样式 */
+.btn-kefu {
+ background: rgba(0, 243, 255, 0.1);
+ border-color: var(--cyber-blue);
+ box-shadow: 0 0 12rpx rgba(0, 243, 255, 0.2);
+}
+
+.btn-shensu {
+ background: rgba(255, 165, 0, 0.1);
+ border-color: #FFA500;
+ box-shadow: 0 0 12rpx rgba(255, 165, 0, 0.2);
+}
+
+.btn-shensu-disabled {
+ background: rgba(102, 102, 102, 0.1);
+ border-color: #666;
+ box-shadow: 0 0 12rpx rgba(102, 102, 102, 0.2);
+ opacity: 0.5;
+}
+
+.btn-quxiao {
+ background: rgba(255, 255, 255, 0.07);
+ border-color: rgba(255, 255, 255, 0.2);
+ box-shadow: 0 0 12rpx rgba(255, 255, 255, 0.1);
+}
+
+.btn-queren {
+ background: rgba(0, 255, 157, 0.1);
+ border-color: var(--cyber-green);
+ box-shadow: 0 0 12rpx rgba(0, 255, 157, 0.2);
+}
+
+/* 申诉弹窗输入样式 */
+.input-group {
+ margin-bottom: 40rpx;
+}
+
+.input-label {
+ color: var(--cyber-text-light) !important;
+ font-size: 30rpx;
+ margin-bottom: 15rpx;
+ font-weight: bold;
+}
+
+.shensu-textarea {
+ width: 100%;
+ min-height: 200rpx;
+ padding: 25rpx 30rpx;
+ background: rgba(255, 255, 255, 0.05);
+ border-radius: 15rpx;
+ font-size: 30rpx;
+ line-height: 1.5;
+ color: var(--cyber-text) !important;
+ border: 1rpx solid rgba(0, 243, 255, 0.2);
+ box-sizing: border-box;
+}
+
+.shensu-textarea::placeholder {
+ color: var(--cyber-text-lighter) !important;
+ opacity: 0.6;
+}
+
+.word-count {
+ text-align: right;
+ color: var(--cyber-text-lighter) !important;
+ font-size: 24rpx;
+ margin-top: 10rpx;
+ opacity: 0.7;
+}
+
+.upload-tip {
+ color: var(--cyber-text-lighter) !important;
+ font-size: 26rpx;
+ margin-bottom: 20rpx;
+ opacity: 0.8;
+}
+
+/* 上传图片网格 */
+.tupian-grid-upload {
+ display: flex;
+ flex-wrap: wrap;
+ margin-left: -5rpx;
+ margin-right: -5rpx;
+}
+
+.tupian-item-upload {
+ width: calc(33.333% - 10rpx);
+ margin: 5rpx;
+ aspect-ratio: 1;
+ border-radius: 10rpx;
+ overflow: hidden;
+ position: relative;
+ border: 1rpx solid rgba(0, 243, 255, 0.2);
+}
+
+.tupian-image-upload {
+ width: 100%;
+ height: 100%;
+}
+
+.tupian-delete {
+ position: absolute;
+ top: 5rpx;
+ right: 5rpx;
+ width: 40rpx;
+ height: 40rpx;
+ border-radius: 50%;
+ background: rgba(255, 0, 102, 0.7);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: white;
+ font-size: 30rpx;
+ font-weight: bold;
+}
+
+.tupian-add-btn {
+ width: calc(33.333% - 10rpx);
+ margin: 5rpx;
+ aspect-ratio: 1;
+ border-radius: 10rpx;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ border: 2rpx dashed rgba(0, 243, 255, 0.4);
+ color: var(--cyber-blue);
+}
+
+.tupian-add-btn:active {
+ border-color: var(--cyber-blue);
+ background: rgba(0, 243, 255, 0.07);
+}
+
+.add-icon {
+ font-size: 60rpx;
+ margin-bottom: 10rpx;
+}
+
+.add-text {
+ font-size: 24rpx;
+}
+
+/* 上传进度 */
+.upload-progress {
+ margin-top: 30rpx;
+ padding: 25rpx 30rpx;
+ background: rgba(255, 255, 255, 0.05);
+ border-radius: 15rpx;
+ border: 1rpx solid rgba(0, 243, 255, 0.2);
+}
+
+.progress-title {
+ color: var(--cyber-blue);
+ font-size: 28rpx;
+ margin-bottom: 15rpx;
+ font-weight: bold;
+}
+
+.progress-bar {
+ height: 20rpx;
+ background: rgba(255, 255, 255, 0.07);
+ border-radius: 10rpx;
+ overflow: hidden;
+ margin-bottom: 10rpx;
+}
+
+.progress-inner {
+ height: 100%;
+ background: linear-gradient(90deg, var(--cyber-blue), var(--cyber-purple));
+ border-radius: 10rpx;
+ transition: width 0.3s ease;
+}
+
+.progress-text {
+ text-align: center;
+ color: var(--cyber-text-light) !important;
+ font-size: 26rpx;
+ opacity: 0.9;
+}
+
+/* 响应式调整 */
+@media (max-width: 375px) {
+ .tongji-value {
+ font-size: 36rpx;
+ }
+
+ .shijian,
+ .qingqiu-label,
+ .liyou-label,
+ .dingdan-label {
+ font-size: 24rpx;
+ }
+
+ .qingqiu-value,
+ .liyou-value,
+ .dingdan-value {
+ font-size: 24rpx;
+ }
+
+ .modal-content {
+ width: 95%;
+ }
+}
+
+
+/* 🔴【颜色调整】卡片和弹窗背景色稍微调淡一点 */
+:root {
+ --cyber-bg: #2c2c3c; /* 从#2a2a3a调亮一点 */
+ --cyber-card: #363648; /* 从#333344调亮一点 */
+}
+
+/* 🔴【卡片背景色调整 - 更淡一点】 */
+.chufa-card {
+ background: rgba(130, 141, 146, 0.8); /* 从rgba(50,50,65,0.8)调亮 */
+}
+
+/* 🔴【弹窗背景色调整 - 更淡一点】 */
+.modal-content {
+
+ background: #acacda; /* 从#3a3a4a调亮 */
+}
+
+/* 🔴【增加弹窗高度】 */
+.modal-content {
+ max-height: 88vh; /* 从80vh增加 */
+}
+
+.small-modal {
+ max-height: 88vh; /* 从85vh调整 */
+}
+
+/* 🔴【增加弹窗内部滚动区域高度】 */
+/* 修改wxml中对应的style */
+/* 需要将: */
+/* 改为: */
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/* pages/cfss/cfss.wxss 中新增样式 */
+
+/* 🔴【关键修改】图片网格和图片样式 */
+.tupian-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 12rpx;
+ margin-top: 10rpx;
+ width: 100%;
+ }
+
+ .tupian-item {
+ position: relative;
+ width: 100%;
+ aspect-ratio: 1;
+ border-radius: 12rpx;
+ overflow: hidden;
+ border: 1rpx solid rgba(0, 243, 255, 0.3);
+ background-color: rgba(0, 0, 0, 0.2);
+ }
+
+ .tupian-image {
+ width: 100%;
+ height: 100%;
+ display: block;
+ object-fit: cover;
+ }
+
+ /* 图片预览遮罩 */
+ .tupian-preview {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.3);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ opacity: 0;
+ transition: opacity 0.3s ease;
+ }
+
+ .tupian-item:hover .tupian-preview {
+ opacity: 1;
+ }
+
+ .preview-text {
+ color: white;
+ font-size: 24rpx;
+ background: rgba(0, 243, 255, 0.7);
+ padding: 8rpx 16rpx;
+ border-radius: 8rpx;
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/dashouxiugai/dashouxiugai.js b/miniprogram/pages/dashouxiugai/dashouxiugai.js
new file mode 100644
index 0000000..9b50cb3
--- /dev/null
+++ b/miniprogram/pages/dashouxiugai/dashouxiugai.js
@@ -0,0 +1,422 @@
+import request from '../../utils/request.js'
+
+Page({
+ data: {
+ // 头像相关
+ touxiangUrl: '', // 完整头像URL
+ touxiangRelative: '', // 头像相对URL(用于上传后更新)
+
+ // 打手信息字段
+ dashouUid: '', // UID(不可修改)
+ dashouNicheng: '', // 昵称
+ dashouJieshao: '', // 个人介绍
+ dashouDianhua: '', // 电话
+ dashouWeixin: '', // 微信
+
+ // 页面状态
+ isLoading: false, // 是否正在加载
+ isDataFromApi: false, // 数据是否来自API(用于显示提示)
+ isSubmitting: false, // 是否正在提交
+ hasLoadedFromCache: false, // 是否已从缓存加载
+
+ // 验证状态
+ nichengValid: true,
+ dianhuaValid: true,
+ weixinValid: true,
+ jieshaoValid: true
+ },
+
+ onLoad() {
+ // 页面加载时初始化数据
+ this.initPageData()
+ },
+
+ onShow() {
+ // 每次页面显示时检查头像是否需要更新
+ this.registerNotificationComponent();
+ this.checkAvatarUpdate()
+ },
+ // 🆕 新增:注册通知组件
+ 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()
+ };
+ }
+},
+
+ // 初始化页面数据
+ async initPageData() {
+ this.setData({ isLoading: true })
+
+ try {
+ // 1. 从缓存加载基本信息
+ const cachedData = this.loadFromCache()
+
+ // 2. 检查是否需要从API加载
+ const needApiLoad = this.checkNeedApiLoad(cachedData)
+
+ if (needApiLoad) {
+ // 从API加载数据
+ await this.loadFromApi()
+ this.setData({ isDataFromApi: true })
+ } else {
+ // 使用缓存数据
+ this.setDataFromCache(cachedData)
+ this.setData({ isDataFromApi: false })
+ }
+
+ // 3. 构建完整头像URL
+ this.buildAvatarUrl()
+
+ } catch (error) {
+ console.error('初始化页面数据失败:', error)
+ wx.showToast({
+ title: '数据加载失败',
+ icon: 'error',
+ duration: 2000
+ })
+ } finally {
+ this.setData({ isLoading: false, hasLoadedFromCache: true })
+ }
+ },
+
+ // 从缓存加载数据
+ loadFromCache() {
+ const app = getApp()
+
+ return {
+ // 从缓存获取头像相对URL
+ touxiang: wx.getStorageSync('touxiang') || '',
+
+ // 从缓存获取用户ID
+ uid: wx.getStorageSync('uid') || '',
+
+ // 从缓存获取打手信息
+ dsnc: wx.getStorageSync('dsnc') || '',
+ dsjieshao: wx.getStorageSync('dsjieshao') || '',
+ dsdianhua: wx.getStorageSync('dsdianhua') || '',
+ dsweixin: wx.getStorageSync('dsweixin') || ''
+ }
+ },
+
+ // 检查是否需要从API加载数据
+ checkNeedApiLoad(cachedData) {
+ // 如果昵称或介绍有一个为空,则需要从API加载
+ return !cachedData.dsnc || !cachedData.dsjieshao
+ },
+
+ // 设置缓存数据到页面
+ setDataFromCache(cachedData) {
+ const app = getApp()
+
+ // 处理空值显示
+ const formatEmptyValue = (value) => {
+ return value && value.trim() !== '' ? value : '暂未填写'
+ }
+
+ this.setData({
+ dashouUid: cachedData.uid || '',
+ dashouNicheng: cachedData.dsnc || '',
+ dashouJieshao: cachedData.dsjieshao || '',
+ dashouDianhua: formatEmptyValue(cachedData.dsdianhua),
+ dashouWeixin: formatEmptyValue(cachedData.dsweixin),
+ touxiangRelative: cachedData.touxiang || ''
+ })
+ },
+
+ // 从API加载数据
+ async loadFromApi() {
+ try {
+ const res = await request({
+ url: '/yonghu/dszjxxhq',
+ method: 'POST'
+ })
+
+ if (res && res.data.code === 200) {
+ const data = res.data.data || {}
+
+ // 格式化空值
+ const formatValue = (value) => {
+ if (value === null || value === undefined || value.trim() === '') {
+ return '暂未填写'
+ }
+ return value
+ }
+
+ // 更新页面数据
+ this.setData({
+ dashouNicheng: formatValue(data.nicheng),
+ dashouJieshao: formatValue(data.jieshao),
+ dashouDianhua: formatValue(data.dianhua),
+ dashouWeixin: formatValue(data.wechat)
+ })
+
+ // 更新缓存(即使为空也存储,避免重复请求)
+ this.updateCache(data)
+
+ } else {
+ throw new Error(res?.data?.msg || '获取信息失败')
+ }
+ } catch (error) {
+ console.error('API加载失败:', error)
+ throw error
+ }
+ },
+
+ // 更新缓存
+ updateCache(data) {
+ // 存储到缓存,确保字段存在(即使为空)
+ wx.setStorageSync('dsnc', data.nicheng || '')
+ wx.setStorageSync('dsjieshao', data.jieshao || '')
+ wx.setStorageSync('dsdianhua', data.dianhua || '')
+ wx.setStorageSync('dsweixin', data.wechat || '')
+ },
+
+ // 构建完整头像URL
+ buildAvatarUrl() {
+ const app = getApp()
+
+ // 获取头像相对路径
+ const touxiangRelative = this.data.touxiangRelative || ''
+
+ // 获取全局配置
+ const ossImageUrl = app.globalData.ossImageUrl || ''
+ const morentouxiang = app.globalData.morentouxiang || ''
+
+ // 构建完整URL
+ let fullUrl = ''
+
+ if (touxiangRelative && touxiangRelative.trim() !== '') {
+ fullUrl = ossImageUrl + touxiangRelative
+ } else {
+ fullUrl = ossImageUrl + morentouxiang
+ }
+
+ this.setData({ touxiangUrl: fullUrl })
+ },
+
+ // 检查头像是否需要更新
+ checkAvatarUpdate() {
+ const app = getApp()
+ const currentAvatar = wx.getStorageSync('touxiang') || ''
+
+ // 如果缓存中的头像与当前显示的不同,重新构建URL
+ if (currentAvatar !== this.data.touxiangRelative) {
+ this.setData({ touxiangRelative: currentAvatar })
+ this.buildAvatarUrl()
+ }
+ },
+
+ // 事件处理函数
+ onAvatarTap() {
+ // 头像点击事件(后续可扩展为更换头像)
+ wx.showToast({
+ title: '头像上传功能待实现',
+ icon: 'none',
+ duration: 2000
+ })
+ },
+
+ onNichengInput(e) {
+ const value = e.detail.value
+ this.setData({
+ dashouNicheng: value,
+ nichengValid: value.length <= 20
+ })
+ },
+
+ onDianhuaInput(e) {
+ const value = e.detail.value
+ // 简单的手机号验证
+ const isValid = /^1[3-9]\d{9}$/.test(value) || value === '' || value === '暂未填写'
+ this.setData({
+ dashouDianhua: value,
+ dianhuaValid: isValid
+ })
+ },
+
+ onWeixinInput(e) {
+ const value = e.detail.value
+ this.setData({
+ dashouWeixin: value,
+ weixinValid: value.length <= 30
+ })
+ },
+
+ onJieshaoInput(e) {
+ const value = e.detail.value
+ this.setData({
+ dashouJieshao: value,
+ jieshaoValid: value.length <= 200
+ })
+ },
+
+ // 表单验证
+ validateForm() {
+ const {
+ dashouNicheng,
+ dashouDianhua,
+ dashouWeixin,
+ dashouJieshao,
+ nichengValid,
+ dianhuaValid,
+ weixinValid,
+ jieshaoValid
+ } = this.data
+
+ // 1. 验证昵称
+ if (!dashouNicheng || dashouNicheng.trim() === '') {
+ wx.showToast({
+ title: '昵称不能为空',
+ icon: 'none',
+ duration: 2000
+ })
+ return false
+ }
+
+ if (dashouNicheng.length > 20) {
+ wx.showToast({
+ title: '昵称不能超过20字',
+ icon: 'none',
+ duration: 2000
+ })
+ return false
+ }
+
+ // 2. 验证手机号(非必填,但填写时必须正确)
+ if (dashouDianhua && dashouDianhua !== '暂未填写') {
+ if (!/^1[3-9]\d{9}$/.test(dashouDianhua)) {
+ wx.showToast({
+ title: '手机号格式不正确',
+ icon: 'none',
+ duration: 2000
+ })
+ return false
+ }
+ }
+
+ // 3. 验证微信号(非必填)
+ if (dashouWeixin && dashouWeixin.length > 30) {
+ wx.showToast({
+ title: '微信号不能超过30字符',
+ icon: 'none',
+ duration: 2000
+ })
+ return false
+ }
+
+ // 4. 验证个人介绍
+ if (dashouJieshao && dashouJieshao.length > 200) {
+ wx.showToast({
+ title: '介绍不能超过200字',
+ icon: 'none',
+ duration: 2000
+ })
+ return false
+ }
+
+ return true
+ },
+
+ // 格式化提交数据
+ formatSubmitData() {
+ const {
+ dashouNicheng,
+ dashouDianhua,
+ dashouWeixin,
+ dashouJieshao
+ } = this.data
+
+ // 将"暂未填写"转换为空字符串
+ const formatValue = (value) => {
+ return value === '暂未填写' ? '' : value.trim()
+ }
+
+ return {
+ nicheng: formatValue(dashouNicheng),
+ dianhua: formatValue(dashouDianhua),
+ wechat: formatValue(dashouWeixin),
+ jieshao: formatValue(dashouJieshao)
+ }
+ },
+
+ // 提交修改
+ async onSubmit() {
+ // 防止重复提交
+ if (this.data.isSubmitting) return
+
+ // 表单验证
+ if (!this.validateForm()) return
+
+ this.setData({ isSubmitting: true })
+
+ // 准备提交数据
+ const postData = this.formatSubmitData()
+
+ try {
+ const res = await request({
+ url: '/yonghu/dsgxxx',
+ method: 'POST',
+ data: postData
+ })
+
+ if (res && res.data.code === 200) {
+ // 更新成功
+ wx.showToast({
+ title: '修改成功',
+ icon: 'success',
+ duration: 2000
+ })
+
+ // 更新本地缓存
+ this.updateLocalCache(postData)
+
+ // 延迟返回上一页
+ setTimeout(() => {
+ wx.navigateBack()
+ }, 1500)
+
+ } else {
+ // 更新失败
+ const errorMsg = res?.data?.msg || '修改失败,请重试'
+ wx.showModal({
+ title: '修改失败',
+ content: errorMsg,
+ showCancel: false
+ })
+ this.setData({ isSubmitting: false })
+ }
+
+ } catch (error) {
+ console.error('提交修改失败:', error)
+ wx.showModal({
+ title: '请求失败',
+ content: error.message || '网络错误,请检查连接',
+ showCancel: false
+ })
+ this.setData({ isSubmitting: false })
+ }
+ },
+
+ // 更新本地缓存
+ updateLocalCache(data) {
+ if (data.nicheng !== undefined) {
+ wx.setStorageSync('dsnc', data.nicheng)
+ }
+ if (data.jieshao !== undefined) {
+ wx.setStorageSync('dsjieshao', data.jieshao)
+ }
+ if (data.dianhua !== undefined) {
+ wx.setStorageSync('dsdianhua', data.dianhua)
+ }
+ if (data.wechat !== undefined) {
+ wx.setStorageSync('dsweixin', data.wechat)
+ }
+ }
+})
\ No newline at end of file
diff --git a/miniprogram/pages/dashouxiugai/dashouxiugai.json b/miniprogram/pages/dashouxiugai/dashouxiugai.json
new file mode 100644
index 0000000..8eb3bea
--- /dev/null
+++ b/miniprogram/pages/dashouxiugai/dashouxiugai.json
@@ -0,0 +1,11 @@
+{
+
+ "navigationBarTitleText": "服务者信息修改",
+ "navigationBarBackgroundColor": "#ffffff",
+ "navigationBarTextStyle": "black",
+ "backgroundColor": "#f8f9fa",
+ "enablePullDownRefresh": false,
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification"
+ }
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/dashouxiugai/dashouxiugai.wxml b/miniprogram/pages/dashouxiugai/dashouxiugai.wxml
new file mode 100644
index 0000000..5861902
--- /dev/null
+++ b/miniprogram/pages/dashouxiugai/dashouxiugai.wxml
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+ 点击更换头像
+
+
+
+
+
+
+
+ 服务者UID
+ {{ dashouUid }}
+
+
+
+
+ 服务昵称
+
+ {{ dashouNicheng.length || 0 }}/20
+
+
+
+
+ 联系电话
+
+
+
+
+
+ 微信号
+
+ {{ dashouWeixin.length || 0 }}/30
+
+
+
+
+ 个人介绍
+
+ {{ dashouJieshao.length || 0 }}/200
+
+
+
+
+
+ 提示:已从服务器加载最新信息
+
+
+
+
+
+
+
+
+
+
+
+ 加载中...
+
+
+
+
diff --git a/miniprogram/pages/dashouxiugai/dashouxiugai.wxss b/miniprogram/pages/dashouxiugai/dashouxiugai.wxss
new file mode 100644
index 0000000..653e8db
--- /dev/null
+++ b/miniprogram/pages/dashouxiugai/dashouxiugai.wxss
@@ -0,0 +1,197 @@
+/* 页面容器 */
+.container {
+ min-height: 100vh;
+ background: linear-gradient(135deg, #fffacd 0%, #e6f7ff 100%);
+ padding: 20rpx;
+ box-sizing: border-box;
+ }
+
+ /* 头像区域 */
+ .avatar-section {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding: 40rpx 0;
+ position: relative;
+ }
+
+ .avatar-wrapper {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ }
+
+ .avatar {
+ width: 180rpx;
+ height: 180rpx;
+ border-radius: 50%;
+ border: 6rpx solid #ffffff;
+ box-shadow: 0 0 30rpx rgba(255, 215, 0, 0.4);
+ z-index: 2;
+ position: relative;
+ background-color: #f5f5f5;
+ }
+
+ .avatar-halo {
+ position: absolute;
+ width: 220rpx;
+ height: 220rpx;
+ border-radius: 50%;
+ background: radial-gradient(circle, rgba(255, 215, 0, 0.2) 0%, transparent 70%);
+ z-index: 1;
+ animation: halo-pulse 2s infinite ease-in-out;
+ }
+
+ @keyframes halo-pulse {
+ 0%, 100% { opacity: 0.6; transform: scale(1); }
+ 50% { opacity: 0.8; transform: scale(1.05); }
+ }
+
+ .avatar-hint {
+ margin-top: 20rpx;
+ font-size: 26rpx;
+ color: #666;
+ text-align: center;
+ }
+
+ /* 信息区域 */
+ .info-section {
+ background: rgba(255, 255, 255, 0.9);
+ border-radius: 20rpx;
+ padding: 30rpx;
+ margin-bottom: 40rpx;
+ box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.05);
+ }
+
+ .info-item {
+ display: flex;
+ flex-direction: column;
+ margin-bottom: 40rpx;
+ position: relative;
+ padding-bottom: 30rpx;
+ border-bottom: 1rpx solid #f0f0f0;
+ }
+
+ .info-item:last-child {
+ border-bottom: none;
+ }
+
+ .info-label {
+ font-size: 28rpx;
+ color: #333;
+ font-weight: 600;
+ margin-bottom: 15rpx;
+ }
+
+ .info-value-disabled {
+ font-size: 32rpx;
+ color: #999;
+ font-weight: bold;
+ background: #f9f9f9;
+ padding: 20rpx;
+ border-radius: 10rpx;
+ }
+
+ .info-input {
+ font-size: 32rpx;
+ color: #333;
+ padding: 20rpx;
+ background: #f9f9f9;
+ border-radius: 10rpx;
+ border: 1rpx solid #e0e0e0;
+ }
+
+ .info-textarea {
+ font-size: 30rpx;
+ color: #333;
+ padding: 20rpx;
+ background: #f9f9f9;
+ border-radius: 10rpx;
+ border: 1rpx solid #e0e0e0;
+ min-height: 200rpx;
+ line-height: 1.5;
+ }
+
+ .info-item-textarea {
+ padding-bottom: 20rpx;
+ }
+
+ .char-count {
+ position: absolute;
+ right: 0;
+ bottom: 5rpx;
+ font-size: 24rpx;
+ color: #999;
+ }
+
+ /* 状态提示 */
+ .status-section {
+ background: #e8f4fd;
+ border-radius: 10rpx;
+ padding: 20rpx;
+ margin-bottom: 30rpx;
+ border-left: 6rpx solid #1890ff;
+ }
+
+ .status-text {
+ font-size: 26rpx;
+ color: #1890ff;
+ }
+
+ /* 按钮区域 */
+ .button-section {
+ padding: 20rpx 0 40rpx;
+ }
+
+ .submit-btn {
+ background: linear-gradient(90deg, #52c41a, #73d13d);
+ color: white;
+ font-size: 32rpx;
+ font-weight: 600;
+ height: 90rpx;
+ line-height: 70rpx;
+ border-radius: 45rpx;
+ border: none;
+ box-shadow: 0 10rpx 20rpx rgba(82, 196, 26, 0.2);
+ }
+
+ .submit-btn:disabled {
+ background: #cccccc;
+ box-shadow: none;
+ }
+
+ .submit-btn::after {
+ border: none;
+ }
+
+ /* 加载中 */
+ .loading-wrapper {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(255, 255, 255, 0.9);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ z-index: 1000;
+ }
+
+ .loading-content {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ }
+
+ .loading-icon {
+ width: 120rpx;
+ height: 120rpx;
+ margin-bottom: 30rpx;
+ }
+
+ .loading-text {
+ font-size: 28rpx;
+ color: #666;
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/dingdan/dingdan.js b/miniprogram/pages/dingdan/dingdan.js
new file mode 100644
index 0000000..9e31b53
--- /dev/null
+++ b/miniprogram/pages/dingdan/dingdan.js
@@ -0,0 +1,287 @@
+// pages/dingdan/dingdan.js
+const app = getApp()
+import request from '../../utils/request.js'
+
+Page({
+ data: {
+ // 订单状态tab配置
+ dingdanZhuangtaiTabs: [
+ { name: '全部', key: 'all', zhuangtaiList: [1, 2, 3, 4, 5, 6, 7, 8] },
+ { name: '已下单', key: 'yixiadan', zhuangtaiList: [1, 7] },
+ { name: '进行中', key: 'jinxingzhong', zhuangtaiList: [2] },
+ { name: '争议订单', key: 'zhengyi', zhuangtaiList: [4, 5, 6] },
+ { name: '已完成', key: 'yiwancheng', zhuangtaiList: [3] },
+ { name: '结算中', key: 'jiesuanzhong', zhuangtaiList: [8] }
+ ],
+
+ // 当前选中的tab索引
+ xuanzhongTabIndex: 0,
+ // 当前选中的tab的key
+ currentTabKey: 'all',
+
+ // 每个tab对应的订单数据
+ dingdanShuju: {
+ all: { list: [], page: 1, hasMore: true, isLoading: false },
+ yixiadan: { list: [], page: 1, hasMore: true, isLoading: false },
+ jinxingzhong: { list: [], page: 1, hasMore: true, isLoading: false },
+ zhengyi: { list: [], page: 1, hasMore: true, isLoading: false },
+ yiwancheng: { list: [], page: 1, hasMore: true, isLoading: false },
+ jiesuanzhong: { list: [], page: 1, hasMore: true, isLoading: false }
+ },
+
+ // 个人中心传递过来的type参数
+ gerenzhongxinType: '',
+
+ // 是否显示加载更多
+ xianshiJiazaigengduo: false,
+
+ // 页面加载状态
+ isLoading: true
+ },
+
+ onLoad(options) {
+ const type = options.type || ''
+
+ wx.setNavigationBarTitle({
+ title: '我的订单'
+ })
+
+ this.qingkongGerenzhongxinJishu(type)
+
+ const tabIndex = this.yingsheTypeToTabIndex(type)
+ const tabKey = this.data.dingdanZhuangtaiTabs[tabIndex].key
+
+ this.setData({
+ gerenzhongxinType: type,
+ xuanzhongTabIndex: tabIndex,
+ currentTabKey: tabKey,
+ isLoading: false
+ })
+
+ this.jiazaiDingdanShuju(tabIndex)
+ },
+ onShow() {
+ // 原有代码...
+
+ // 🆕 注册通知组件
+ this.registerNotificationComponent();
+},
+
+// 🆕 新增:注册通知组件
+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()
+ };
+ }
+},
+
+
+ qingkongGerenzhongxinJishu(type) {
+ const jishuMap = {
+ 'daifuwu': 'daifuwu',
+ 'fuwuzhong': 'fuwuzhong',
+ 'yiwancheng': 'yiwancheng',
+ 'yituikuan': 'yituikuan'
+ }
+
+ const jishuKey = jishuMap[type]
+ if (jishuKey && app.globalData.dingdanTiaoshu) {
+ app.globalData.dingdanTiaoshu[jishuKey] = 0
+ }
+ },
+
+ yingsheTypeToTabIndex(type) {
+ const yingsheMap = {
+ 'daifuwu': 1,
+ 'fuwuzhong': 2,
+ 'yiwancheng': 4,
+ 'yituikuan': 3
+ }
+
+ return yingsheMap[type] || 0
+ },
+
+ switchDingdanTab(e) {
+ const tabIndex = e.currentTarget.dataset.index
+ if (tabIndex === this.data.xuanzhongTabIndex) return
+
+ const tabKey = this.data.dingdanZhuangtaiTabs[tabIndex].key
+
+ this.setData({
+ xuanzhongTabIndex: tabIndex,
+ currentTabKey: tabKey
+ })
+
+ this.jiazaiDingdanShuju(tabIndex)
+ },
+
+ async jiazaiDingdanShuju(tabIndex, isLoadMore = false) {
+ const tab = this.data.dingdanZhuangtaiTabs[tabIndex]
+ const tabKey = tab.key
+
+ const tabData = this.data.dingdanShuju[tabKey]
+
+ const page = isLoadMore ? tabData.page + 1 : 1
+
+ if (tabData.isLoading) return
+
+ // 重置逻辑:如果不是加载更多,清空已有数据
+ if (!isLoadMore) {
+ this.setData({
+ [`dingdanShuju.${tabKey}.list`]: [],
+ [`dingdanShuju.${tabKey}.page`]: 1
+ })
+ }
+
+ this.setData({
+ [`dingdanShuju.${tabKey}.isLoading`]: true
+ })
+
+ wx.showLoading({
+ title: isLoadMore ? '加载更多...' : '加载订单...',
+ mask: true
+ })
+
+ try {
+ const requestData = {
+ zhuangtai_list: tab.zhuangtaiList,
+ page: page,
+ page_size: 10
+ }
+
+
+
+ const res = await request({
+ url: '/dingdan/dingdanhuoqu',
+ method: 'POST',
+ data: requestData,
+ header: {
+ 'content-type': 'application/json'
+ }
+ })
+
+
+
+ wx.hideLoading()
+
+ // ✅ 关键修正:res是直接返回的数据对象,不是res.data
+ if (res && res.data.code === 0) {
+ // ✅ 关键修正:直接使用res.data.list
+ const newDingdanList = res.data.data.list || []
+ const hasMore = res.data.data.has_more || false
+
+
+
+ // 处理图片URL,拼接完整路径
+ const processedList = newDingdanList.map(item => {
+ let tupianUrl = item.tupian || ''
+
+ // 如果图片URL不是完整路径,拼接ossImageUrl
+ if (tupianUrl && !tupianUrl.startsWith('http')) {
+ const ossImageUrl = app.globalData.ossImageUrl || ''
+ tupianUrl = ossImageUrl + tupianUrl
+ }
+
+ const zhuangtaiZh = this.getZhuangtaiZhongwen(item.zhuangtai)
+
+
+ return {
+ ...item,
+ tupian: tupianUrl,
+ zhuangtaiZh: zhuangtaiZh
+ }
+ })
+
+ const currentList = this.data.dingdanShuju[tabKey].list
+ const updatedList = isLoadMore
+ ? [...currentList, ...processedList]
+ : processedList
+
+ this.setData({
+ [`dingdanShuju.${tabKey}.list`]: updatedList,
+ [`dingdanShuju.${tabKey}.page`]: page,
+ [`dingdanShuju.${tabKey}.hasMore`]: hasMore,
+ [`dingdanShuju.${tabKey}.isLoading`]: false,
+ xianshiJiazaigengduo: hasMore
+ })
+
+ } else {
+ wx.showToast({
+ title: res ? (res.msg || '加载失败') : '请求失败',
+ icon: 'none',
+ duration: 2000
+ })
+
+ this.setData({
+ [`dingdanShuju.${tabKey}.isLoading`]: false
+ })
+ }
+
+ } catch (error) {
+ console.error('加载订单数据失败:', error)
+ wx.hideLoading()
+ wx.showToast({
+ title: '网络请求失败,请重试',
+ icon: 'none',
+ duration: 2000
+ })
+
+ this.setData({
+ [`dingdanShuju.${tabKey}.isLoading`]: false
+ })
+ }
+ },
+
+ getZhuangtaiZhongwen(zhuangtai) {
+ const zhuangtaiMap = {
+ 1: '已下单',
+ 2: '进行中',
+ 3: '已完成',
+ 4: '退款中',
+ 5: '已退款',
+ 6: '退款失败',
+ 7: '指定中',
+ 8: '结算中'
+ }
+
+ return zhuangtaiMap[zhuangtai] || '未知状态'
+ },
+
+ onReachBottom() {
+ const tabKey = this.data.currentTabKey
+ const tabData = this.data.dingdanShuju[tabKey]
+
+ if (tabData.hasMore && !tabData.isLoading) {
+ const tabIndex = this.data.xuanzhongTabIndex
+ this.jiazaiDingdanShuju(tabIndex, true)
+ }
+ },
+
+ goToDingdanXiangqing(e) {
+ const dingdanItem = e.currentTarget.dataset.item
+ if (!dingdanItem) return
+
+ const dingdanDataStr = encodeURIComponent(JSON.stringify(dingdanItem))
+
+ wx.navigateTo({
+ url: `/pages/dingdanxiangqing/dingdanxiangqing?dingdanData=${dingdanDataStr}`
+ })
+ },
+
+ onImageError(e) {
+ const index = e.currentTarget.dataset.index
+ const tabKey = this.data.currentTabKey
+
+ const defaultImage = app.globalData.lunbozhanwei || '/images/default.jpg'
+
+ this.setData({
+ [`dingdanShuju.${tabKey}.list[${index}].tupian`]: defaultImage
+ })
+ }
+})
\ No newline at end of file
diff --git a/miniprogram/pages/dingdan/dingdan.json b/miniprogram/pages/dingdan/dingdan.json
new file mode 100644
index 0000000..837f016
--- /dev/null
+++ b/miniprogram/pages/dingdan/dingdan.json
@@ -0,0 +1,5 @@
+{
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/pages/dingdan/dingdan.wxml b/miniprogram/pages/dingdan/dingdan.wxml
new file mode 100644
index 0000000..9d64425
--- /dev/null
+++ b/miniprogram/pages/dingdan/dingdan.wxml
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+ {{item.name}}
+
+
+
+
+
+
+
+
+
+
+
+
+ 加载中...
+
+
+
+
+
+ 暂无订单
+ 快去下单体验服务吧
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{item.jieshao || '暂无描述'}}
+
+
+
+
+
+
+
+ {{item.zhuangtaiZh || '未知状态'}}
+
+
+
+
+ ¥
+ {{item.jine || '0.00'}}
+
+
+
+
+
+
+
+
+
+ 上拉加载更多
+
+
+
+
+
+ 没有更多订单了
+
+
+
+
+
+
diff --git a/miniprogram/pages/dingdan/dingdan.wxss b/miniprogram/pages/dingdan/dingdan.wxss
new file mode 100644
index 0000000..afb2316
--- /dev/null
+++ b/miniprogram/pages/dingdan/dingdan.wxss
@@ -0,0 +1,262 @@
+/* pages/dingdan/dingdan.wxss */
+
+.dingdan-page {
+ min-height: 100vh;
+ background-color: #f8f9fa;
+ padding-bottom: 20rpx;
+ }
+
+ .dingdan-tabs-scroll {
+ width: 100%;
+ height: 90rpx;
+ white-space: nowrap;
+ background-color: #ffffff;
+ border-bottom: 1rpx solid #f0f0f0;
+ box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.03);
+ }
+
+ .dingdan-tabs-container {
+ display: flex;
+ align-items: center;
+ height: 100%;
+ padding: 0 20rpx;
+ }
+
+ .dingdan-tab-item {
+ display: inline-flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 60rpx;
+ padding: 0 30rpx;
+ margin-right: 20rpx;
+ border-radius: 30rpx;
+ background-color: #f5f5f5;
+ color: #666666;
+ font-size: 28rpx;
+ transition: all 0.3s ease;
+ position: relative;
+ flex-shrink: 0;
+ }
+
+ .dingdan-tab-item:last-child {
+ margin-right: 0;
+ }
+
+ .dingdan-tab-item.tab-active {
+ background-color: #e8f5e9;
+ color: #2e7d32;
+ font-weight: 500;
+ box-shadow: 0 4rpx 12rpx rgba(46, 125, 50, 0.1);
+ }
+
+ .tab-active-line {
+ width: 40rpx;
+ height: 4rpx;
+ background-color: #4caf50;
+ border-radius: 2rpx;
+ margin-top: 6rpx;
+ animation: fadeIn 0.3s ease;
+ }
+
+ @keyframes fadeIn {
+ from { opacity: 0; transform: scale(0.8); }
+ to { opacity: 1; transform: scale(1); }
+ }
+
+ .tab-text {
+ font-weight: 400;
+ }
+
+ .dingdan-list-container {
+ padding: 20rpx;
+ }
+
+ .loading-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 400rpx;
+ }
+
+ .loading-spinner {
+ width: 60rpx;
+ height: 60rpx;
+ border: 6rpx solid #e0e0e0;
+ border-top-color: #4caf50;
+ border-radius: 50%;
+ animation: spin 1s linear infinite;
+ margin-bottom: 20rpx;
+ }
+
+ @keyframes spin {
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
+ }
+
+ .loading-text {
+ font-size: 28rpx;
+ color: #999999;
+ }
+
+ .empty-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 500rpx;
+ padding-top: 80rpx;
+ }
+
+ .empty-image {
+ width: 200rpx;
+ height: 200rpx;
+ margin-bottom: 30rpx;
+ opacity: 0.6;
+ }
+
+ .empty-text {
+ font-size: 32rpx;
+ color: #999999;
+ margin-bottom: 10rpx;
+ }
+
+ .empty-subtext {
+ font-size: 26rpx;
+ color: #cccccc;
+ }
+
+ .dingdan-card {
+ display: flex;
+ align-items: center;
+ background-color: #ffffff;
+ border-radius: 16rpx;
+ padding: 24rpx;
+ margin-bottom: 20rpx;
+ box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
+ }
+
+ .dingdan-card:active {
+ transform: translateY(-2rpx);
+ box-shadow: 0 6rpx 24rpx rgba(0, 0, 0, 0.08);
+ }
+
+ .dingdan-card-left {
+ flex-shrink: 0;
+ margin-right: 24rpx;
+ }
+
+ .dingdan-image {
+ width: 160rpx;
+ height: 160rpx;
+ border-radius: 12rpx;
+ background-color: #f5f5f5;
+ }
+
+ .dingdan-card-center {
+ flex: 1;
+ height: 160rpx;
+ display: flex;
+ align-items: flex-start;
+ }
+
+ .dingdan-jieshao {
+ font-size: 28rpx;
+ line-height: 40rpx;
+ color: #333333;
+ font-weight: 400;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 3;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .dingdan-card-right {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ justify-content: space-between;
+ height: 160rpx;
+ margin-left: 20rpx;
+ }
+
+ .dingdan-zhuangtai {
+ font-size: 24rpx;
+ color: #ff6b6b;
+ font-weight: 500;
+ padding: 6rpx 16rpx;
+ border-radius: 20rpx;
+ background-color: #fff5f5;
+ white-space: nowrap;
+ }
+
+ /* ✅ 修正后的价格样式 - 简单明了 */
+ .dingdan-jine {
+ display: flex;
+ align-items: center; /* 垂直居中 */
+ }
+
+ .jine-symbol {
+ font-size: 28rpx;
+ color: #333333;
+ font-weight: 500;
+ margin-right: 4rpx; /* 添加右边距,让符号和数字有间隔 */
+ }
+
+ .jine-number {
+ font-size: 32rpx; /* 整数和小数一样大 */
+ color: #333333;
+ font-weight: 600;
+ }
+
+ .load-more-container {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 40rpx 0;
+ }
+
+ .load-more-line {
+ width: 80rpx;
+ height: 1rpx;
+ background-color: #e0e0e0;
+ margin: 0 20rpx;
+ }
+
+ .load-more-text {
+ font-size: 26rpx;
+ color: #999999;
+ white-space: nowrap;
+ }
+
+ .no-more-container {
+ text-align: center;
+ padding: 40rpx 0;
+ }
+
+ .no-more-text {
+ font-size: 26rpx;
+ color: #cccccc;
+ }
+
+ @media (max-width: 375px) {
+ .dingdan-image {
+ width: 140rpx;
+ height: 140rpx;
+ }
+
+ .dingdan-card-center {
+ height: 140rpx;
+ }
+
+ .dingdan-card-right {
+ height: 140rpx;
+ }
+
+ .jine-number {
+ font-size: 30rpx;
+ }
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/dingdanxiangqing/dingdanxiangqing.js b/miniprogram/pages/dingdanxiangqing/dingdanxiangqing.js
new file mode 100644
index 0000000..9e5fa60
--- /dev/null
+++ b/miniprogram/pages/dingdanxiangqing/dingdanxiangqing.js
@@ -0,0 +1,412 @@
+// pages/dingdanxiangqing/dingdanxiangqing.js
+const app = getApp()
+import request from '../../utils/request.js'
+
+Page({
+ data: {
+ // 从跳转参数获取的基础数据
+ jibenShuju: {
+ dingdanId: '',
+ tupian: '',
+ jieshao: '',
+ chuangjianshijian: '',
+ jine: '',
+ nicheng: '',
+ beizhu: '',
+ zhuangtai: 0
+ },
+
+ // 从后端获取的详细数据
+ xiangxiShuju: {
+ zhiding: '', // 指定打手
+ dashou: '', // 服务打手
+ dashoujiaofu: [], // 打手交付图片列表
+ dashouliuyan: '' // 打手留言
+ },
+
+ // 结算相关数据
+ jiesuanShuju: {
+ pingfen: 0, // 评分 0-5
+ liuyan: '' // 留言
+ },
+
+ // 页面状态
+ isLoading: true, // 页面加载中
+ isJiesuanLoading: false, // 结单请求中
+
+ // 状态颜色映射
+ zhuangtaiYanseMap: {
+ 1: '#E8F5E9', // 已下单 - 浅绿色
+ 2: '#E3F2FD', // 进行中 - 浅蓝色
+ 3: '#F3E5F5', // 已完成 - 浅紫色
+ 4: '#FFF3E0', // 退款中 - 浅橙色
+ 5: '#FFEBEE', // 已退款 - 浅红色
+ 6: '#EFEBE9', // 退款失败 - 浅棕色
+ 7: '#E0F7FA', // 指定中 - 浅青色
+ 8: '#FFF8E1' // 结算中 - 浅黄色
+ },
+
+ // 状态文字映射
+ zhuangtaiWenziMap: {
+ 1: '已下单',
+ 2: '进行中',
+ 3: '已完成',
+ 4: '退款中',
+ 5: '已退款',
+ 6: '退款失败',
+ 7: '指定中',
+ 8: '结算中'
+ },
+
+ // 星星评分数据
+ xingxingData: [
+ { id: 1, active: false },
+ { id: 2, active: false },
+ { id: 3, active: false },
+ { id: 4, active: false },
+ { id: 5, active: false }
+ ]
+ },
+
+ onLoad(options) {
+ // 设置页面标题
+ wx.setNavigationBarTitle({
+ title: '订单详情'
+ })
+
+ // 解析跳转参数
+ this.jiexiTiaozhuanCanshu(options)
+ },
+ onShow() {
+ // 原有代码...
+
+ // 🆕 注册通知组件
+ this.registerNotificationComponent();
+},
+
+// 🆕 新增:注册通知组件
+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()
+ };
+ }
+},
+
+ /**
+ * 解析跳转参数
+ */
+ jiexiTiaozhuanCanshu(options) {
+ try {
+ const dingdanDataStr = options.dingdanData || ''
+ if (!dingdanDataStr) {
+ wx.showToast({
+ title: '订单数据错误',
+ icon: 'none'
+ })
+ setTimeout(() => {
+ wx.navigateBack()
+ }, 1500)
+ return
+ }
+
+ // 解析订单数据
+ const dingdanData = JSON.parse(decodeURIComponent(dingdanDataStr))
+
+
+
+ // 处理图片URL(拼接完整路径)
+ let tupianUrl = dingdanData.tupian || ''
+ if (tupianUrl && !tupianUrl.startsWith('http')) {
+ const ossImageUrl = app.globalData.ossImageUrl || ''
+ tupianUrl = ossImageUrl + tupianUrl
+ }
+
+ // 格式化价格
+ let jine = dingdanData.jine || 0
+ jine = parseFloat(jine)
+ if (isNaN(jine)) {
+ jine = 0
+ }
+ const formattedJine = jine.toFixed(2)
+
+ // 设置基础数据(状态先用跳转的,等后端返回后更新)
+ this.setData({
+ 'jibenShuju.dingdanId': dingdanData.dingdanId || '',
+ 'jibenShuju.tupian': tupianUrl,
+ 'jibenShuju.jieshao': dingdanData.jieshao || '',
+ 'jibenShuju.chuangjianshijian': dingdanData.chuangjianshijian || '',
+ 'jibenShuju.jine': formattedJine,
+ 'jibenShuju.nicheng': dingdanData.nicheng || '',
+ 'jibenShuju.beizhu': dingdanData.beizhu || '',
+ // 这里先使用跳转的状态,等后端返回后会被覆盖
+ 'jibenShuju.zhuangtai': dingdanData.zhuangtai || 0,
+ isLoading: false
+ })
+
+
+
+ // 加载详细数据
+ this.jiazaiXiangxiShuju(dingdanData.dingdanId)
+
+ } catch (error) {
+ console.error('解析订单数据失败:', error)
+ wx.showToast({
+ title: '数据解析失败',
+ icon: 'none'
+ })
+ setTimeout(() => {
+ wx.navigateBack()
+ }, 1500)
+ }
+ },
+
+ /**
+ * 加载详细数据
+ */
+ async jiazaiXiangxiShuju(dingdanId) {
+ if (!dingdanId) return
+
+
+
+ try {
+ const res = await request({
+ url: '/dingdan/dingdanxiangqing',
+ method: 'POST',
+ data: { dingdanId: dingdanId },
+ header: { 'content-type': 'application/json' }
+ })
+
+
+
+ // 关键修正:使用正确的数据访问路径
+ if (res && res.data.code === 0 && res.data.data) {
+ const data = res.data.data
+
+
+
+ // 处理打手交付图片(拼接完整URL)
+ let dashoujiaofuList = data.dashoujiaofu || []
+ dashoujiaofuList = dashoujiaofuList.map(imgUrl => {
+ if (imgUrl && !imgUrl.startsWith('http')) {
+ const ossImageUrl = app.globalData.ossImageUrl || ''
+ return ossImageUrl + imgUrl
+ }
+ return imgUrl
+ })
+
+ // 核心修正:使用后端返回的订单状态,覆盖跳转时的状态
+ this.setData({
+ 'xiangxiShuju.zhiding': data.zhiding || '',
+ 'xiangxiShuju.dashou': data.dashou || '',
+ 'xiangxiShuju.dashoujiaofu': dashoujiaofuList,
+ 'xiangxiShuju.dashouliuyan': data.dashouliuyan || '',
+ // 关键:使用后端返回的状态覆盖跳转的状态
+ 'jibenShuju.zhuangtai': data.zhuangtai || 0
+ })
+
+
+
+ } else {
+ console.warn('获取详细数据失败:', res?.msg || '未知错误')
+ wx.showToast({
+ title: res?.msg || '获取订单详情失败',
+ icon: 'none',
+ duration: 2000
+ })
+ }
+
+ } catch (error) {
+ console.error('加载详细数据失败:', error)
+ wx.showToast({
+ title: '加载数据失败,请重试',
+ icon: 'none',
+ duration: 2000
+ })
+ }
+ },
+
+ /**
+ * 复制文本到剪贴板
+ */
+ async fuzhiWenben(e) {
+ const text = e.currentTarget.dataset.text
+ if (!text) return
+
+
+
+ try {
+ await wx.setClipboardData({
+ data: text
+ })
+
+ wx.showToast({
+ title: '复制成功',
+ icon: 'success',
+ duration: 1500
+ })
+
+ } catch (error) {
+ console.error('复制失败:', error)
+ wx.showToast({
+ title: '复制失败',
+ icon: 'none'
+ })
+ }
+ },
+
+ /**
+ * 预览图片
+ */
+ yulanTupian(e) {
+ const currentUrl = e.currentTarget.dataset.url
+ const urls = e.currentTarget.dataset.urls || []
+
+ if (!currentUrl) return
+
+ wx.previewImage({
+ current: currentUrl,
+ urls: urls.length > 0 ? urls : [currentUrl]
+ })
+ },
+
+ /**
+ * 点击星星评分
+ */
+ dianjiXingxing(e) {
+ const pingfen = e.currentTarget.dataset.index
+
+ // 更新星星状态
+ const xingxingData = this.data.xingxingData.map((item, index) => ({
+ ...item,
+ active: index < pingfen
+ }))
+
+ this.setData({
+ xingxingData: xingxingData,
+ 'jiesuanShuju.pingfen': pingfen
+ })
+
+
+ },
+
+ /**
+ * 输入留言
+ */
+ shuruLiuyan(e) {
+ const liuyan = e.detail.value.slice(0, 50) // 限制50字
+ this.setData({
+ 'jiesuanShuju.liuyan': liuyan
+ })
+ },
+
+ /**
+ * 提交结单
+ */
+ async tijiaoJiedan() {
+ const { jibenShuju, jiesuanShuju } = this.data
+
+ console.log('提交结单,数据:', {
+ dingdanId: jibenShuju.dingdanId,
+ pingfen: jiesuanShuju.pingfen,
+ liuyan: jiesuanShuju.liuyan
+ })
+
+ // 验证订单状态
+ if (jibenShuju.zhuangtai !== 8) {
+ wx.showToast({
+ title: '订单不在结算状态',
+ icon: 'none'
+ })
+ return
+ }
+
+ // 验证评分范围
+ if (jiesuanShuju.pingfen < 0 || jiesuanShuju.pingfen > 5) {
+ wx.showToast({
+ title: '请选择0-5分评分',
+ icon: 'none'
+ })
+ return
+ }
+
+ // 验证订单ID
+ if (!jibenShuju.dingdanId) {
+ wx.showToast({
+ title: '订单信息错误',
+ icon: 'none'
+ })
+ return
+ }
+
+ // 显示加载中
+ this.setData({ isJiesuanLoading: true })
+ wx.showLoading({
+ title: '提交中...',
+ mask: true
+ })
+
+ try {
+ const res = await request({
+ url: '/dingdan/jiedan',
+ method: 'POST',
+ data: {
+ dingdanId: jibenShuju.dingdanId,
+ pingfen: jiesuanShuju.pingfen,
+ liuyan: jiesuanShuju.liuyan
+ },
+ header: { 'content-type': 'application/json' }
+ })
+
+
+
+ wx.hideLoading()
+ this.setData({ isJiesuanLoading: false })
+
+ // 使用正确的数据访问路径
+ if (res && res.data.code === 0) {
+ // 结单成功,更新订单状态
+ this.setData({
+ 'jibenShuju.zhuangtai': 3, // 变为已完成状态
+ 'jiesuanShuju.pingfen': 0,
+ 'jiesuanShuju.liuyan': ''
+ })
+
+ // 重置星星
+ const resetXingxingData = this.data.xingxingData.map(item => ({
+ ...item,
+ active: false
+ }))
+ this.setData({ xingxingData: resetXingxingData })
+
+ wx.showToast({
+ title: '结单成功',
+ icon: 'success',
+ duration: 2000
+ })
+
+ } else {
+ wx.showToast({
+ title: res?.msg || '结单失败',
+ icon: 'none'
+ })
+ }
+
+ } catch (error) {
+ console.error('结单失败:', error)
+ wx.hideLoading()
+ this.setData({ isJiesuanLoading: false })
+ wx.showToast({
+ title: '网络错误,请重试',
+ icon: 'none'
+ })
+ }
+ },
+
+
+})
\ No newline at end of file
diff --git a/miniprogram/pages/dingdanxiangqing/dingdanxiangqing.json b/miniprogram/pages/dingdanxiangqing/dingdanxiangqing.json
new file mode 100644
index 0000000..837f016
--- /dev/null
+++ b/miniprogram/pages/dingdanxiangqing/dingdanxiangqing.json
@@ -0,0 +1,5 @@
+{
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/pages/dingdanxiangqing/dingdanxiangqing.wxml b/miniprogram/pages/dingdanxiangqing/dingdanxiangqing.wxml
new file mode 100644
index 0000000..d0a1ab8
--- /dev/null
+++ b/miniprogram/pages/dingdanxiangqing/dingdanxiangqing.wxml
@@ -0,0 +1,202 @@
+
+
+
+
+
+
+ 加载订单详情...
+
+
+
+
+
+
+
+
+
+
+
+ {{jibenShuju.jieshao || '暂无描述'}}
+
+
+
+
+
+ 订单信息
+
+
+
+ 游戏昵称:
+ {{jibenShuju.nicheng}}
+
+
+
+
+ 备注信息:
+ {{jibenShuju.beizhu}}
+
+
+
+
+ 指定打手:
+ {{xiangxiShuju.zhiding}}
+
+
+
+
+ 订单ID:
+
+ {{jibenShuju.dingdanId}}
+
+ 📋
+ 复制
+
+
+
+
+
+
+ 创建时间:
+ {{jibenShuju.chuangjianshijian}}
+
+
+
+
+ 订单价格:
+ ¥{{jibenShuju.jine}}
+
+
+
+
+ 订单状态:
+ {{zhuangtaiWenziMap[jibenShuju.zhuangtai] || '未知状态'}}
+
+
+
+
+
+ 服务详情
+
+
+
+ 服务打手:
+
+ {{xiangxiShuju.dashou}}
+
+ 📋
+ 复制
+
+
+
+
+
+
+ 打手交付
+
+
+
+
+
+
+
+
+
+
+
+ 打手留言
+
+ {{xiangxiShuju.dashouliuyan}}
+
+
+
+
+
+
+ 订单结算
+
+
+
+
+ 打星评分:
+
+
+
+ {{item.active ? '★' : '☆'}}
+
+
+
+
+
+
+
+
+ 订单留言(可选)
+
+
+ 已输入 {{jiesuanShuju.liuyan.length}}/50 字
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/miniprogram/pages/dingdanxiangqing/dingdanxiangqing.wxss b/miniprogram/pages/dingdanxiangqing/dingdanxiangqing.wxss
new file mode 100644
index 0000000..686128a
--- /dev/null
+++ b/miniprogram/pages/dingdanxiangqing/dingdanxiangqing.wxss
@@ -0,0 +1,422 @@
+/* pages/dingdanxiangqing/dingdanxiangqing.wxss */
+
+/* 页面基础样式 */
+.dingdanxiangqing-page {
+ min-height: 100vh;
+ background-color: #f8f9fa;
+ padding-bottom: 140rpx;
+ }
+
+ /* 加载中状态 */
+ .loading-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 60vh;
+ }
+
+ .loading-spinner {
+ width: 80rpx;
+ height: 80rpx;
+ border: 8rpx solid #e0e0e0;
+ border-top-color: #4caf50;
+ border-radius: 50%;
+ animation: spin 1.2s linear infinite;
+ margin-bottom: 30rpx;
+ }
+
+ @keyframes spin {
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
+ }
+
+ .loading-text {
+ font-size: 30rpx;
+ color: #666666;
+ }
+
+ /* 内容区域 */
+ .content-container {
+ padding: 25rpx;
+ }
+
+ /* 商品图片和介绍区域 */
+ .shangpin-area {
+ display: flex;
+ background-color: #ffffff;
+ border-radius: 20rpx;
+ padding: 25rpx;
+ margin-bottom: 25rpx;
+ box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
+ }
+
+ .shangpin-image-container {
+ flex-shrink: 0;
+ width: 180rpx;
+ height: 180rpx;
+ margin-right: 25rpx;
+ }
+
+ .shangpin-image {
+ width: 100%;
+ height: 100%;
+ border-radius: 12rpx;
+ background-color: #f5f5f5;
+ }
+
+ .shangpin-jieshao {
+ flex: 1;
+ display: flex;
+ align-items: flex-start;
+ }
+
+ .jieshao-text {
+ font-size: 28rpx;
+ line-height: 42rpx;
+ color: #333333;
+ font-weight: 400;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 4;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ /* 通用区域样式 */
+ .info-title {
+ font-size: 32rpx;
+ color: #333333;
+ font-weight: 600;
+ margin-bottom: 20rpx;
+ padding-bottom: 12rpx;
+ border-bottom: 2rpx solid rgba(0, 0, 0, 0.08);
+ }
+
+ .section-subtitle {
+ font-size: 28rpx;
+ color: #555555;
+ font-weight: 500;
+ display: block;
+ }
+
+ /* 左右布局的订单信息项 */
+ .dingdan-info-area {
+ background-color: #ffffff;
+ border-radius: 20rpx;
+ padding: 25rpx;
+ margin-bottom: 25rpx;
+ box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
+ }
+
+ .info-item-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 18rpx;
+ padding: 12rpx 0;
+ }
+
+ .info-item-row:last-child {
+ margin-bottom: 0;
+ }
+
+ .item-label {
+ font-size: 28rpx;
+ color: #666666;
+ font-weight: 400;
+ flex-shrink: 0;
+ min-width: 150rpx;
+ }
+
+ /* 右侧区域容器 */
+ .item-right-area {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ }
+
+ .item-value {
+ font-size: 28rpx;
+ color: #333333;
+ font-weight: 500;
+ word-break: break-all;
+ text-align: right;
+ margin-right: 15rpx;
+ }
+
+ /* 特殊样式 */
+ .jine-red {
+ color: #ff4444 !important;
+ font-weight: 600 !important;
+ font-size: 32rpx !important;
+ }
+
+ .zhuangtai-text {
+ font-weight: 600 !important;
+ font-size: 30rpx !important;
+ color: #333333 !important;
+ }
+
+ /* 复制按钮 */
+ .copy-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 24rpx;
+ color: #4caf50;
+ background-color: #e8f5e9;
+ padding: 6rpx 16rpx;
+ border-radius: 20rpx;
+ border: 1rpx solid #4caf50;
+ transition: all 0.2s ease;
+ white-space: nowrap;
+ flex-shrink: 0;
+ }
+
+ .copy-btn-hover {
+ background-color: #c8e6c9;
+ transform: scale(0.95);
+ }
+
+ .copy-icon {
+ font-size: 20rpx;
+ margin-right: 6rpx;
+ }
+
+ .copy-text {
+ font-weight: 500;
+ }
+
+ /* 服务区域 */
+ .fuwu-area {
+ background-color: #ffffff;
+ border-radius: 20rpx;
+ padding: 25rpx;
+ margin-bottom: 25rpx;
+ box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
+ }
+
+ /* 打手交付图片网格 */
+ .dashou-jiaofu-container {
+ margin-bottom: 20rpx;
+ margin-top: 15rpx;
+ }
+
+ .jiaofu-grid {
+ display: flex;
+ flex-wrap: wrap;
+ margin: -5rpx;
+ }
+
+ .jiaofu-image-item {
+ width: calc(33.333% - 10rpx);
+ margin: 5rpx;
+ aspect-ratio: 1/1;
+ }
+
+ .jiaofu-image {
+ width: 100%;
+ height: 100%;
+ border-radius: 12rpx;
+ background-color: #f5f5f5;
+ transition: transform 0.2s ease;
+ }
+
+ .jiaofu-image:active {
+ transform: scale(0.95);
+ }
+
+ /* 打手留言 */
+ .dashou-liuyan-container {
+ margin-top: 15rpx;
+ }
+
+ .liuyan-content {
+ font-size: 26rpx;
+ line-height: 40rpx;
+ color: #555555;
+ padding: 15rpx;
+ background-color: #f9f9f9;
+ border-radius: 12rpx;
+ border-left: 4rpx solid #4caf50;
+ }
+
+ /* 结算区域 */
+ .jiesuan-area {
+ background-color: #ffffff;
+ border-radius: 20rpx;
+ padding: 25rpx;
+ margin-bottom: 25rpx;
+ box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
+ }
+
+ /* 星星评分布局修正 */
+ .pingfen-container {
+ margin-bottom: 25rpx;
+ }
+
+ .xingxing-layout {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-top: 10rpx;
+ }
+
+ .xingxing-wrapper {
+ display: flex;
+ align-items: center;
+ }
+
+ .xingxing-icon {
+ font-size: 52rpx;
+ color: #cccccc;
+ margin-right: 10rpx;
+ transition: all 0.3s ease;
+ cursor: pointer;
+ /* 去掉点击时的蓝色矩形框 */
+ user-select: none;
+ -webkit-tap-highlight-color: transparent;
+ outline: none;
+ border: none;
+ background: none;
+ }
+
+ .xingxing-icon:last-child {
+ margin-right: 0;
+ }
+
+ .xingxing-active {
+ color: #FFD700;
+ text-shadow: 0 0 10rpx rgba(255, 215, 0, 0.5);
+ transform: scale(1.1);
+ }
+
+ .xingxing-icon:active {
+ transform: scale(0.9);
+ }
+
+ /* 留言输入 */
+ .liuyan-input-container {
+ margin-top: 15rpx;
+ }
+
+ .liuyan-textarea {
+ width: 100%;
+ min-height: 120rpx;
+ font-size: 28rpx;
+ color: #333333;
+ background-color: #f9f9f9;
+ border-radius: 12rpx;
+ padding: 20rpx;
+ margin-top: 10rpx;
+ box-sizing: border-box;
+ }
+
+ .zi-shu {
+ display: block;
+ text-align: right;
+ font-size: 24rpx;
+ color: #999999;
+ margin-top: 8rpx;
+ }
+
+ /* 底部安全区域 */
+ .safe-area {
+ height: 40rpx;
+ }
+
+ /* 结单按钮容器修正 */
+ .jiedan-btn-container {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background: linear-gradient(to top, rgba(255,255,255,0.9), transparent);
+ padding: 20rpx 30rpx;
+ backdrop-filter: blur(10rpx);
+ z-index: 100;
+ }
+
+ .jiedan-btn {
+ background: linear-gradient(135deg, #4caf50, #2e7d32);
+ color: #ffffff;
+ border: none;
+ border-radius: 50rpx;
+ height: 90rpx;
+ font-size: 32rpx;
+ font-weight: 600;
+ position: relative;
+ overflow: hidden;
+ box-shadow: 0 6rpx 20rpx rgba(76, 175, 80, 0.3);
+ transition: all 0.3s ease;
+ width: 100%;
+ /* 垂直居中 */
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .jiedan-btn::after {
+ border: none;
+ }
+
+ .btn-hover {
+ transform: translateY(-2rpx);
+ box-shadow: 0 8rpx 25rpx rgba(76, 175, 80, 0.4);
+ }
+
+ .btn-text {
+ position: relative;
+ z-index: 2;
+ /* 确保文字垂直居中 */
+ line-height: 1;
+ }
+
+ /* 按钮闪光特效 */
+ .btn-sparkle {
+ position: absolute;
+ top: -50%;
+ left: -50%;
+ width: 200%;
+ height: 200%;
+ background: radial-gradient(circle, rgba(255,255,255,0.3) 1%, transparent 10%);
+ animation: sparkle 2s infinite linear;
+ z-index: 1;
+ }
+
+ @keyframes sparkle {
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
+ }
+
+ /* 响应式调整 */
+ @media (max-width: 375px) {
+ .shangpin-image-container {
+ width: 160rpx;
+ height: 160rpx;
+ }
+
+ .xingxing-icon {
+ font-size: 48rpx;
+ }
+
+ .jiedan-btn {
+ height: 85rpx;
+ font-size: 30rpx;
+ }
+
+ .item-label {
+ min-width: 140rpx;
+ font-size: 26rpx;
+ }
+
+ .item-value {
+ font-size: 26rpx;
+ }
+
+ .jiedan-btn-container {
+ padding: 20rpx 25rpx;
+ }
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/dsddxq/dsddxq.js b/miniprogram/pages/dsddxq/dsddxq.js
new file mode 100644
index 0000000..7e53d26
--- /dev/null
+++ b/miniprogram/pages/dsddxq/dsddxq.js
@@ -0,0 +1,730 @@
+// pages/dsddxq/dsddxq.js - 【最终版:提交前公告弹窗 + 修改图片至少保留一张】
+const app = getApp()
+import request from '../../utils/request.js'
+const COS = require('../../utils/cos-wx-sdk-v5.js')
+import connectionManager from '../../utils/xiaoxilj.js'
+
+Page({
+ data: {
+ jibenShuju: {
+ dingdan_id: '',
+ tupian: '',
+ jieshao: '',
+ create_time: '',
+ jine: '',
+ nicheng: '',
+ beizhu: '',
+ zhuangtai: 0,
+ fadanpingtai: 0,
+ is_cross: 0
+ },
+ xiangxiShuju: {
+ shangjia_id: '',
+ shangjia_nicheng: '',
+ shangjia_liuyan: '',
+ tuikuan_liyou: '',
+ chufa_liyou: '',
+ chufa_zhuangtai: 0,
+ chufa_jieguo: '',
+ bohui_liyou: '',
+ dashou_jiaofu: [],
+ dashou_liuyan: '',
+ laoban_id: ''
+ },
+ zhuangtaiYanseMap: {
+ 1: '#E8F5E9', 2: '#E3F2FD', 3: '#F3E5F5', 4: '#FFF3E0',
+ 5: '#FFEBEE', 6: '#EFEBE9', 7: '#E0F7FA', 8: '#FFF8E1'
+ },
+ zhuangtaiWenziMap: {
+ 1: '已下单', 2: '进行中', 3: '已完成', 4: '退款中',
+ 5: '已退款', 6: '退款失败', 7: '指定中', 8: '结算中'
+ },
+ isLoading: true,
+ isSubmitting: false,
+ liuyan: '',
+ xuanzhongTupian: [],
+ shangchuanJindu: 0,
+ shangchuanZongshu: 0,
+ jinduWidth: '0%',
+ showWanzhengJieshao: false,
+ wanzhengJieshao: '',
+ showWanzhengBeizhu: false,
+ wanzhengBeizhu: '',
+ ossImageUrl: '',
+ morentouxiang: '',
+ showRefundInfo: false,
+
+ // 修改功能所需字段
+ isModifying: false,
+ originalDashouJiaofu: [],
+ originalDashouLiuyan: '',
+ remainingOriginals: [],
+ modifiedLiuyan: '',
+ deletedImages: [],
+ newImages: [],
+ isConfirming: false,
+ modifyUploadProgress: { total: 0, current: 0, width: '0%' }
+ },
+
+ onLoad(options) {
+ wx.setNavigationBarTitle({ title: '订单详情' })
+ this.initGlobalData()
+ this.jiexiTiaozhuanCanshu(options)
+ },
+ onHide() {
+ const popupComp = this.selectComponent('#popupNotice');
+ if (popupComp && popupComp.cleanup) {
+ popupComp.cleanup();
+ }
+ },
+
+ onShow() {
+ this.registerNotificationComponent();
+ },
+
+ 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()
+ };
+ }
+ },
+
+ initGlobalData() {
+ const app = getApp()
+ const ossImageUrl = app.globalData.ossImageUrl || 'https://your-oss-domain.com/'
+ const morentouxiang = app.globalData.morentouxiang || '/images/default-avatar.png'
+ this.setData({ ossImageUrl, morentouxiang })
+ },
+
+ jiexiTiaozhuanCanshu(options) {
+ try {
+ const dingdanDataStr = options.dingdanData || ''
+ if (!dingdanDataStr) {
+ wx.showToast({ title: '订单数据错误', icon: 'none' })
+ setTimeout(() => wx.navigateBack(), 1500)
+ return
+ }
+ const dingdanData = JSON.parse(decodeURIComponent(dingdanDataStr))
+ let tupianUrl = dingdanData.tupian || ''
+ if (!tupianUrl) tupianUrl = this.getTupianUrl()
+ let jine = parseFloat(dingdanData.jine || 0)
+ if (isNaN(jine)) jine = 0
+ const formattedJine = jine.toFixed(2)
+ this.setData({
+ 'jibenShuju.dingdan_id': dingdanData.dingdan_id || '',
+ 'jibenShuju.tupian': tupianUrl,
+ 'jibenShuju.jieshao': dingdanData.jieshao || '',
+ 'jibenShuju.create_time': dingdanData.create_time || '',
+ 'jibenShuju.jine': formattedJine,
+ 'jibenShuju.nicheng': dingdanData.nicheng || '',
+ 'jibenShuju.beizhu': dingdanData.beizhu || '',
+ 'jibenShuju.zhuangtai': dingdanData.zhuangtai || 0,
+ 'jibenShuju.fadanpingtai': dingdanData.fadanpingtai || 0,
+ isLoading: false
+ })
+ this.jiazaiXiangxiShuju(dingdanData.dingdan_id)
+ } catch (error) {
+ console.error('解析订单数据失败:', error)
+ wx.showToast({ title: '数据解析失败', icon: 'none' })
+ setTimeout(() => wx.navigateBack(), 1500)
+ }
+ },
+
+ getTupianUrl() {
+ const cacheTouxiang = wx.getStorageSync('touxiang') || ''
+ let tupianPath = cacheTouxiang || app.globalData.morentouxiang || ''
+ const ossImageUrl = app.globalData.ossImageUrl || ''
+ if (tupianPath && ossImageUrl) {
+ if (tupianPath.startsWith('/')) tupianPath = tupianPath.substring(1)
+ return ossImageUrl + tupianPath
+ }
+ return ''
+ },
+
+ async jiazaiXiangxiShuju(dingdanId) {
+ if (!dingdanId) return
+ wx.showLoading({ title: '加载中...', mask: true })
+ try {
+ const res = await request({
+ url: '/dingdan/dsddxq',
+ method: 'POST',
+ data: { dingdan_id: dingdanId },
+ header: { 'content-type': 'application/json' }
+ })
+ wx.hideLoading()
+ if (res && res.data.code === 0 && res.data.data) {
+ const data = res.data.data
+ let dashouJiaofuList = data.dashou_jiaofu || []
+ dashouJiaofuList = dashouJiaofuList.map(imgUrl => {
+ if (imgUrl && !imgUrl.startsWith('http')) {
+ const ossImageUrl = app.globalData.ossImageUrl || ''
+ if (imgUrl.startsWith('/')) imgUrl = imgUrl.substring(1)
+ return ossImageUrl + imgUrl
+ }
+ return imgUrl
+ })
+ this.setData({
+ 'xiangxiShuju.shangjia_id': data.shangjia_id || '',
+ 'xiangxiShuju.shangjia_nicheng': data.shangjia_nicheng || '',
+ 'xiangxiShuju.shangjia_liuyan': data.shangjia_liuyan || '',
+ 'xiangxiShuju.tuikuan_liyou': data.tuikuan_liyou || '',
+ 'xiangxiShuju.chufa_liyou': data.chufa_liyou || '',
+ 'xiangxiShuju.chufa_zhuangtai': data.chufa_zhuangtai || 0,
+ 'xiangxiShuju.chufa_jieguo': data.chufa_jieguo || '',
+ 'xiangxiShuju.bohui_liyou': data.bohui_liyou || '',
+ 'xiangxiShuju.dashou_jiaofu': dashouJiaofuList,
+ 'xiangxiShuju.dashou_liuyan': data.dashou_liuyan || '',
+ 'xiangxiShuju.laoban_id': data.laoban_id || '',
+ 'jibenShuju.zhuangtai': data.zhuangtai || 0,
+ 'jibenShuju.beizhu': data.beizhu || '',
+ 'liuyan': data.dashou_liuyan || '',
+ 'jibenShuju.fadanpingtai': data.fadanpingtai || 0,
+ 'jibenShuju.is_cross': data.is_cross || 0,
+ })
+ const currentStatus = data.zhuangtai || 0
+ const showRefund = [4,5,6].includes(currentStatus)
+ this.setData({ showRefundInfo: showRefund })
+ } else {
+ const errorMsg = res?.data?.msg || '获取订单详情失败'
+ wx.showToast({ title: errorMsg, icon: 'none', duration: 2000 })
+ }
+ } catch (error) {
+ console.error('加载详细数据失败:', error)
+ wx.hideLoading()
+ wx.showToast({ title: '加载数据失败,请重试', icon: 'none', duration: 2000 })
+ }
+ },
+
+ // ========== 原有提交功能(部分修改) ==========
+ xuanzeTupian() {
+ if (this.data.isSubmitting) return
+ const shengyu = 9 - this.data.xuanzhongTupian.length
+ if (shengyu <= 0) {
+ wx.showToast({ title: '最多只能选择9张图片', icon: 'none' })
+ return
+ }
+ wx.chooseImage({
+ count: shengyu,
+ sizeType: ['compressed'],
+ sourceType: ['album', 'camera'],
+ success: (res) => {
+ const newTupian = [...this.data.xuanzhongTupian, ...res.tempFilePaths]
+ this.setData({ xuanzhongTupian: newTupian.slice(0, 9) })
+ }
+ })
+ },
+ shanchuTupian(e) {
+ const index = e.currentTarget.dataset.index
+ const newTupian = [...this.data.xuanzhongTupian]
+ newTupian.splice(index, 1)
+ this.setData({ xuanzhongTupian: newTupian })
+ },
+ yulanTupian(e) {
+ const currentUrl = e.currentTarget.dataset.url
+ const urls = e.currentTarget.dataset.urls || []
+ if (!currentUrl) return
+ wx.previewImage({ current: currentUrl, urls: urls.length > 0 ? urls : [currentUrl] })
+ },
+ getFileExtension(filePath) {
+ const lastDotIndex = filePath.lastIndexOf('.')
+ if (lastDotIndex === -1) return '.jpg'
+ const ext = filePath.substring(lastDotIndex).toLowerCase()
+ const allowedExts = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp']
+ return allowedExts.includes(ext) ? ext : '.jpg'
+ },
+ shuruLiuyan(e) {
+ const liuyan = e.detail.value.slice(0, 50)
+ this.setData({ liuyan })
+ },
+ chakanWanzhengJieshao() {
+ this.setData({ showWanzhengJieshao: true, wanzhengJieshao: this.data.jibenShuju.jieshao || '无' })
+ },
+ guanbiWanzhengJieshao() { this.setData({ showWanzhengJieshao: false }) },
+ chakanWanzhengBeizhu() {
+ this.setData({ showWanzhengBeizhu: true, wanzhengBeizhu: this.data.jibenShuju.beizhu || '无' })
+ },
+ guanbiWanzhengBeizhu() { this.setData({ showWanzhengBeizhu: false }) },
+ async fuzhiWenben(e) {
+ const text = e.currentTarget.dataset.text
+ if (!text) return
+ try {
+ await wx.setClipboardData({ data: text })
+ wx.showToast({ title: '复制成功', icon: 'success', duration: 1500 })
+ } catch (error) {
+ console.error('复制失败:', error)
+ wx.showToast({ title: '复制失败', icon: 'none' })
+ }
+ },
+ goToKefu() {
+ const kefuLink = app.globalData.kefuConfig?.link || '';
+ const corpId = app.globalData.kefuConfig?.enterpriseId || '';
+
+ if (!kefuLink || !corpId) {
+ wx.showToast({
+ title: '客服配置未加载',
+ icon: 'none'
+ });
+ return;
+ }
+
+ wx.openCustomerServiceChat({
+ extInfo: { url: kefuLink },
+ corpId: corpId,
+ success: (res) => {
+ console.log('跳转客服成功', res);
+ },
+ fail: (err) => {
+ console.error('跳转客服失败', err);
+ wx.showToast({
+ title: '暂时无法连接客服',
+ icon: 'none'
+ });
+ }
+ });
+ },
+
+ // ===== 联系老板按钮(改为跳转订单群聊) =====
+ // ===== 联系老板按钮(智能查找订单群聊) =====
+// ===== 联系老板按钮(智能查找订单群聊) =====
+goToChatWithBoss() {
+ const uid = wx.getStorageSync('uid')
+ if (!uid) return
+
+ const orderId = this.data.jibenShuju.dingdan_id
+ if (!orderId) {
+ wx.showToast({ title: '订单ID缺失', icon: 'none' })
+ return
+ }
+
+ const identityType = 'dashou'
+ const userId = 'Ds' + uid
+
+ const fadanPingtai = this.data.jibenShuju.fadanpingtai || 0
+ const partnerUid = fadanPingtai === 2
+ ? (this.data.xiangxiShuju.shangjia_id || '')
+ : (this.data.xiangxiShuju.laoban_id || '')
+
+ connectionManager.connectToGroupChat({
+ identityType,
+ userId,
+ orderId,
+ partnerUid,
+ fadanPingtai,
+ groupName: this.data.jibenShuju.nicheng + '的订单群',
+ isCross: this.data.jibenShuju.is_cross || 0
+ }).catch(err => {
+ console.error('跳转群聊失败', err)
+ })
+ },
+
+ async getCOSZhengshu() {
+ try {
+ const res = await request({
+ url: '/dingdan/dsscpz',
+ method: 'POST',
+ data: { dingdan_id: this.data.jibenShuju.dingdan_id }
+ })
+ if (res && res.data.code === 0 && res.data.data) return res.data.data
+ else throw new Error(res?.data?.msg || '获取上传凭证失败')
+ } catch (error) {
+ console.error('获取COS凭证失败:', error)
+ throw error
+ }
+ },
+
+ initCOSClient(tokenData) {
+ const credentials = tokenData.credentials || tokenData
+ const bucket = tokenData.bucket || 'julebu-1361527063'
+ const region = tokenData.region || 'ap-shanghai'
+ const cos = new COS({
+ SimpleUploadMethod: 'putObject',
+ getAuthorization: async (options, callback) => {
+ const authParams = {
+ 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)
+ }
+ callback(authParams)
+ }
+ })
+ return { cos, bucket, region }
+ },
+
+ async shangchuanDanZhangTupian(filePath, cosKey, index, cosInstance, bucket, region) {
+ return new Promise((resolve) => {
+ const timeoutId = setTimeout(() => resolve({ status: 'optimistic_success', key: cosKey }), 2000)
+ cosInstance.uploadFile({
+ Bucket: bucket,
+ Region: region,
+ Key: cosKey,
+ FilePath: filePath,
+ onProgress: (progressInfo) => {
+ const percent = Math.round(progressInfo.percent * 100)
+ if (percent === 100) {
+ clearTimeout(timeoutId)
+ resolve({ status: 'optimistic_success', key: cosKey })
+ }
+ }
+ })
+ })
+ },
+
+ async piliangShangchuanTupian() {
+ const total = this.data.xuanzhongTupian.length
+ this.setData({ shangchuanZongshu: total, shangchuanJindu: 0, jinduWidth: '0%' })
+ wx.showLoading({ title: '正在准备...', mask: true })
+ const preGeneratedUrls = []
+ for (let i = 0; i < total; i++) {
+ const timestamp = Date.now() + i
+ const random = Math.floor(Math.random() * 10000)
+ const fileExt = this.getFileExtension(this.data.xuanzhongTupian[i])
+ const fileName = `${timestamp}_${random}${fileExt}`
+ const cosKey = `order/${this.data.jibenShuju.dingdan_id}/${fileName}`
+ preGeneratedUrls.push(cosKey)
+ }
+ let cosClient, bucket, region
+ try {
+ const tokenData = await this.getCOSZhengshu()
+ const { cos, bucket: cosBucket, region: cosRegion } = this.initCOSClient(tokenData)
+ cosClient = cos
+ bucket = cosBucket
+ region = cosRegion
+ } catch (error) {
+ wx.hideLoading()
+ wx.showToast({ title: '上传初始化失败', icon: 'none' })
+ return []
+ }
+ const uploadTasks = []
+ for (let i = 0; i < total; i++) {
+ const task = this.shangchuanDanZhangTupian(
+ this.data.xuanzhongTupian[i],
+ preGeneratedUrls[i],
+ i,
+ cosClient,
+ bucket,
+ region
+ ).then((result) => {
+ const currentDone = i + 1
+ const jinduPercent = ((currentDone / total) * 100).toFixed(0)
+ this.setData({ shangchuanJindu: currentDone, jinduWidth: `${jinduPercent}%` })
+ return result
+ })
+ uploadTasks.push(task)
+ }
+ wx.showLoading({ title: `上传中...`, mask: true })
+ await Promise.all(uploadTasks)
+ wx.hideLoading()
+ return preGeneratedUrls
+ },
+
+ tijiaoDingdan() {
+ this.showAnnouncementAndConfirm()
+ },
+
+ async showAnnouncementAndConfirm() {
+ try {
+ const res = await request({
+ url: '/peizhi/tanchuang',
+ method: 'POST',
+ data: { pageKey: 'dsddxq' }
+ })
+ if (res.statusCode !== 200 || res.data.code !== 0) {
+ this.showOriginalConfirmAndSubmit()
+ return
+ }
+ const { popups = [], serverTime } = res.data.data
+ if (!popups.length) {
+ this.showOriginalConfirmAndSubmit()
+ return
+ }
+ const popup = popups[0]
+ const popupComp = this.selectComponent('#popupNotice')
+ if (!popupComp) {
+ console.error('未找到弹窗组件 #popupNotice')
+ this.showOriginalConfirmAndSubmit()
+ return
+ }
+ const showData = {
+ popupId: popup.popup_id,
+ title: popup.title || '',
+ duration: popup.duration || 0,
+ images: popup.images || [],
+ showMuteCheckbox: false,
+ serverTime: serverTime
+ }
+ popupComp.show(showData, (result) => {
+ this.showOriginalConfirmAndSubmit()
+ })
+ } catch (error) {
+ console.error('获取公告配置失败', error)
+ this.showOriginalConfirmAndSubmit()
+ }
+ },
+
+ showOriginalConfirmAndSubmit() {
+ if (this.data.isSubmitting) {
+ wx.showToast({ title: '正在提交中', icon: 'none' })
+ return
+ }
+ if (this.data.jibenShuju.zhuangtai !== 2) {
+ wx.showToast({ title: '订单状态不可提交', icon: 'none' })
+ return
+ }
+ if (this.data.xuanzhongTupian.length === 0) {
+ wx.showToast({ title: '请至少上传一张图片', icon: 'none' })
+ return
+ }
+ wx.showModal({
+ title: '⚠️ 提交确认',
+ content: '在提交订单之前,请确保您已在打手端页面认真阅读打手规则。如有漏交图片,订单被退款,概不申诉。',
+ confirmText: '我已阅读',
+ cancelText: '我再想想',
+ success: (res) => {
+ if (res.confirm) this._doSubmit()
+ },
+ fail: (err) => {
+ console.error('弹窗失败', err)
+ wx.showToast({ title: '系统异常,请重试', icon: 'none' })
+ }
+ })
+ },
+
+ async _doSubmit() {
+ this.setData({ isSubmitting: true })
+ wx.showLoading({ title: '开始提交...', mask: true })
+ try {
+ const tupianUrls = await this.piliangShangchuanTupian()
+ if (!tupianUrls || tupianUrls.length === 0) throw new Error('无法生成上传路径')
+ wx.showLoading({ title: '提交订单数据...', mask: true })
+ const res = await request({
+ url: '/dingdan/dstijiao',
+ method: 'POST',
+ data: {
+ dingdan_id: this.data.jibenShuju.dingdan_id,
+ liuyan: this.data.liuyan || '',
+ jiaofu_tupian_urls: tupianUrls
+ },
+ header: { 'content-type': 'application/json' }
+ })
+ wx.hideLoading()
+ this.setData({ isSubmitting: false })
+ if (res && res.data.code === 0) {
+ const ossImageUrl = app.globalData.ossImageUrl || ''
+ const fullUrls = tupianUrls.map(url => {
+ if (url && !url.startsWith('http')) {
+ if (url.startsWith('/')) url = url.substring(1)
+ return ossImageUrl + url
+ }
+ return url
+ })
+ this.setData({
+ 'jibenShuju.zhuangtai': 8,
+ 'xiangxiShuju.dashou_liuyan': this.data.liuyan || '',
+ 'xiangxiShuju.dashou_jiaofu': fullUrls,
+ xuanzhongTupian: [],
+ liuyan: '',
+ shangchuanJindu: 0,
+ shangchuanZongshu: 0,
+ jinduWidth: '0%',
+ })
+ if (app.globalData.dashouzhuangtai != 1) app.globalData.dashouzhuangtai = 1
+ wx.showToast({ title: '提交成功!', icon: 'success', duration: 2000 })
+ setTimeout(() => wx.navigateBack(), 1500)
+ } else {
+ const errorMsg = res?.data?.msg || '提交失败'
+ wx.showToast({ title: errorMsg, icon: 'none' })
+ }
+ } catch (error) {
+ console.error('提交失败:', error)
+ wx.hideLoading()
+ this.setData({ isSubmitting: false })
+ wx.showToast({ title: error.message || '提交失败', icon: 'none' })
+ }
+ },
+
+ // ========== 修改功能(增加最少一张图片校验) ==========
+ extractRelativePath(fullUrl) {
+ const ossImageUrl = this.data.ossImageUrl
+ if (fullUrl.startsWith(ossImageUrl)) return fullUrl.substring(ossImageUrl.length)
+ return fullUrl
+ },
+ enterModify() {
+ const { dashou_jiaofu, dashou_liuyan } = this.data.xiangxiShuju
+ this.setData({
+ isModifying: true,
+ originalDashouJiaofu: dashou_jiaofu.slice(),
+ remainingOriginals: dashou_jiaofu.slice(),
+ originalDashouLiuyan: dashou_liuyan || '',
+ modifiedLiuyan: dashou_liuyan || '',
+ deletedImages: [],
+ newImages: []
+ })
+ },
+ cancelModify() {
+ this.setData({
+ isModifying: false,
+ originalDashouJiaofu: [],
+ remainingOriginals: [],
+ originalDashouLiuyan: '',
+ modifiedLiuyan: '',
+ deletedImages: [],
+ newImages: []
+ })
+ },
+ handleDeleteImage(e) {
+ const fullUrl = e.currentTarget.dataset.url
+ const { originalDashouJiaofu, remainingOriginals, deletedImages, newImages } = this.data
+ if (originalDashouJiaofu.includes(fullUrl)) {
+ const newRemaining = remainingOriginals.filter(url => url !== fullUrl)
+ this.setData({
+ remainingOriginals: newRemaining,
+ deletedImages: [...deletedImages, fullUrl]
+ })
+ } else {
+ const index = newImages.indexOf(fullUrl)
+ if (index > -1) {
+ const newImagesCopy = [...newImages]
+ newImagesCopy.splice(index, 1)
+ this.setData({ newImages: newImagesCopy })
+ }
+ }
+ },
+ handleAddImage() {
+ if (this.data.isConfirming) return
+ const currentCount = this.getCurrentImageCount()
+ const maxCount = 9 - currentCount
+ if (maxCount <= 0) {
+ wx.showToast({ title: '最多9张图片', icon: 'none' })
+ return
+ }
+ wx.chooseImage({
+ count: maxCount,
+ sizeType: ['compressed'],
+ sourceType: ['album', 'camera'],
+ success: (res) => {
+ const newImages = [...this.data.newImages, ...res.tempFilePaths]
+ this.setData({ newImages: newImages.slice(0, 9) })
+ }
+ })
+ },
+ getCurrentImageCount() {
+ const { remainingOriginals, newImages } = this.data
+ return remainingOriginals.length + newImages.length
+ },
+ onModifyLiuyanInput(e) {
+ this.setData({ modifiedLiuyan: e.detail.value.slice(0, 50) })
+ },
+
+ async confirmModify() {
+ if (this.data.isConfirming) return
+ const { dingdan_id } = this.data.jibenShuju
+ const { remainingOriginals, deletedImages, newImages, modifiedLiuyan, originalDashouLiuyan } = this.data
+ const hasLiuyanChange = modifiedLiuyan !== originalDashouLiuyan
+ const hasDelete = deletedImages.length > 0
+ const hasAdd = newImages.length > 0
+
+ if (!hasLiuyanChange && !hasDelete && !hasAdd) {
+ wx.showToast({ title: '没有修改内容', icon: 'none' })
+ return
+ }
+
+ const finalImageCount = remainingOriginals.length + newImages.length
+ if (finalImageCount === 0) {
+ wx.showToast({ title: '至少保留一张图片', icon: 'none' })
+ return
+ }
+
+ this.setData({ isConfirming: true })
+ wx.showLoading({ title: '提交修改...', mask: true })
+ try {
+ const tokenData = await this.getCOSZhengshu()
+ const { cos, bucket, region } = this.initCOSClient(tokenData)
+
+ let newImageRelativePaths = []
+ if (hasAdd) {
+ const total = newImages.length
+ this.setData({ 'modifyUploadProgress.total': total, 'modifyUploadProgress.current': 0, 'modifyUploadProgress.width': '0%' })
+ const preGeneratedKeys = []
+ for (let i = 0; i < total; i++) {
+ const timestamp = Date.now() + i
+ const random = Math.floor(Math.random() * 10000)
+ const fileExt = this.getFileExtension(newImages[i])
+ const fileName = `${timestamp}_${random}${fileExt}`
+ const cosKey = `order/${this.data.jibenShuju.dingdan_id}/${fileName}`
+ preGeneratedKeys.push(cosKey)
+ }
+ const uploadTasks = []
+ for (let i = 0; i < total; i++) {
+ const task = this.shangchuanDanZhangTupian(
+ newImages[i],
+ preGeneratedKeys[i],
+ i,
+ cos,
+ bucket,
+ region
+ ).then((result) => {
+ const currentDone = i + 1
+ const jinduPercent = ((currentDone / total) * 100).toFixed(0)
+ this.setData({
+ 'modifyUploadProgress.current': currentDone,
+ 'modifyUploadProgress.width': `${jinduPercent}%`
+ })
+ return result.key
+ })
+ uploadTasks.push(task)
+ }
+ wx.showLoading({ title: `上传中...`, mask: true })
+ newImageRelativePaths = await Promise.all(uploadTasks)
+ wx.hideLoading()
+ this.setData({ 'modifyUploadProgress.total': 0, 'modifyUploadProgress.current': 0, 'modifyUploadProgress.width': '0%' })
+ }
+
+ const deletedRelativePaths = deletedImages.map(fullUrl => this.extractRelativePath(fullUrl))
+
+ const res = await request({
+ url: '/dingdan/dsxiugaidd',
+ method: 'POST',
+ data: {
+ dingdan_id,
+ liuyan: modifiedLiuyan,
+ deleted_images: deletedRelativePaths,
+ new_images: newImageRelativePaths
+ },
+ header: { 'content-type': 'application/json' }
+ })
+ wx.hideLoading()
+ this.setData({ isConfirming: false })
+ if (res && res.data.code === 0) {
+ const ossImageUrl = this.data.ossImageUrl
+ const newFullUrls = newImageRelativePaths.map(rel => ossImageUrl + rel)
+ const newDashouJiaofu = [...remainingOriginals, ...newFullUrls]
+ this.setData({
+ 'xiangxiShuju.dashou_jiaofu': newDashouJiaofu,
+ 'xiangxiShuju.dashou_liuyan': modifiedLiuyan,
+ isModifying: false,
+ originalDashouJiaofu: [],
+ remainingOriginals: [],
+ originalDashouLiuyan: '',
+ deletedImages: [],
+ newImages: [],
+ modifiedLiuyan: ''
+ })
+ wx.showToast({ title: '修改成功', icon: 'success' })
+ } else {
+ const errorMsg = res?.data?.msg || '修改失败'
+ wx.showToast({ title: errorMsg, icon: 'none' })
+ }
+ } catch (error) {
+ console.error('修改失败:', error)
+ wx.hideLoading()
+ this.setData({ isConfirming: false })
+ wx.showToast({ title: error.message || '修改失败', icon: 'none' })
+ }
+ }
+})
\ No newline at end of file
diff --git a/miniprogram/pages/dsddxq/dsddxq.json b/miniprogram/pages/dsddxq/dsddxq.json
new file mode 100644
index 0000000..837f016
--- /dev/null
+++ b/miniprogram/pages/dsddxq/dsddxq.json
@@ -0,0 +1,5 @@
+{
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/pages/dsddxq/dsddxq.wxml b/miniprogram/pages/dsddxq/dsddxq.wxml
new file mode 100644
index 0000000..7a715d6
--- /dev/null
+++ b/miniprogram/pages/dsddxq/dsddxq.wxml
@@ -0,0 +1,478 @@
+
+
+
+
+
+ 加载订单详情...
+
+
+
+
+
+
+
+
+
+
+
+
+ {{jibenShuju.jieshao || '暂无描述'}}
+
+
+ 点击上面字体查看完整介绍
+
+
+
+
+
+
+ 订单信息
+
+
+
+ 游戏昵称:
+ {{jibenShuju.nicheng}}
+
+ 📋
+ 复制
+
+
+
+
+
+ 备注信息:
+
+ {{jibenShuju.beizhu.length > 20 ? jibenShuju.beizhu.substring(0, 20) + '...' : jibenShuju.beizhu}}
+
+ 查看完整
+
+
+
+
+
+
+ 订单ID:
+
+ {{jibenShuju.dingdan_id}}
+
+ 📋
+ 复制
+
+
+
+
+
+
+ 创建时间:
+ {{jibenShuju.create_time}}
+
+
+
+
+ 订单价格:
+ ¥{{jibenShuju.jine}}
+
+
+
+
+ 订单状态:
+ {{zhuangtaiWenziMap[jibenShuju.zhuangtai] || '未知状态'}}
+
+
+
+
+ 发单平台:
+
+ {{jibenShuju.fadanpingtai == 1 ? '平台发单' : (jibenShuju.fadanpingtai == 2 ? '商家发单' : '未知')}}
+
+
+
+
+
+
+ 商家信息
+
+
+
+ 商家ID:
+
+ {{xiangxiShuju.shangjia_id}}
+
+ 📋
+ 复制
+
+
+
+
+
+
+ 商家昵称:
+ {{xiangxiShuju.shangjia_nicheng}}
+
+
+
+
+ 商家留言
+
+ {{xiangxiShuju.shangjia_liuyan}}
+
+
+
+
+
+
+ 退款信息
+
+
+ 退款理由:
+ {{xiangxiShuju.tuikuan_liyou}}
+
+
+
+
+
+ 处罚申请
+
+
+
+ 申请原因:
+ {{xiangxiShuju.chufa_liyou}}
+
+
+
+
+ 申请状态:
+
+ {{xiangxiShuju.chufa_zhuangtai == 0 ? '审核中' : (xiangxiShuju.chufa_zhuangtai == 1 ? '处罚成功' : '处罚驳回')}}
+
+
+
+
+
+ 处罚结果:
+ {{xiangxiShuju.chufa_jieguo}}
+
+
+
+
+ 驳回理由:
+ {{xiangxiShuju.bohui_liyou}}
+
+
+
+
+
+ 我的交付
+
+
+
+ 交付图片
+
+
+
+
+
+
+
+
+
+
+
+ 我的留言
+
+ {{xiangxiShuju.dashou_liuyan}}
+
+
+
+
+
+
+ 修改交付
+
+
+
+ 交付图片(点击叉号删除)
+
+
+
+
+
+
+
+
+ ×
+
+
+
+
+
+
+
+
+
+ ×
+
+
+
+
+
+
+ +
+ 添加图片
+ ({{9 - (remainingOriginals.length + newImages.length)}}/9)
+
+
+
+
+
+
+ 留言(可选,最多50字)
+
+
+ 已输入 {{modifiedLiuyan.length}}/50 字
+
+
+
+
+
+
+ 提交订单
+
+
+
+ 选择交付图片(最多9张)
+
+
+
+
+
+
+
+ ×
+
+
+
+
+
+
+ +
+ 添加图片
+ ({{9 - xuanzhongTupian.length}}/9)
+
+
+
+
+
+
+
+
+
+ 上传中: {{shangchuanJindu}}/{{shangchuanZongshu}}
+
+
+
+
+ 留言(可选,最多50字)
+
+
+ 已输入 {{liuyan.length}}/50 字
+
+
+
+
+
+
+
+
+
+
+
+
+ 取消修改
+
+
+ {{isConfirming ? '提交中...' : '确认修改'}}
+
+
+
+
+
+
+
+
+
+ 联系客服
+
+
+ 联系老板
+
+
+
+
+
+ 联系客服
+
+
+
+
+
+
+ 修改交付
+
+
+
+
+
+ {{isSubmitting ? '提交中...' : '立即提交'}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{wanzhengJieshao}}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{wanzhengBeizhu}}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/dsddxq/dsddxq.wxss b/miniprogram/pages/dsddxq/dsddxq.wxss
new file mode 100644
index 0000000..7e9a205
--- /dev/null
+++ b/miniprogram/pages/dsddxq/dsddxq.wxss
@@ -0,0 +1,589 @@
+/* pages/dsddxq/dsddxq.wxss */
+
+/* 页面基础样式 */
+.dsddxq-page {
+ min-height: 100vh;
+ background-color: #f8f9fa;
+ position: relative;
+ box-sizing: border-box;
+}
+
+/* 加载中状态 */
+.loading-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 60vh;
+}
+
+.loading-spinner {
+ width: 80rpx;
+ height: 80rpx;
+ border: 8rpx solid #e0e0e0;
+ border-top-color: #2196f3;
+ border-radius: 50%;
+ animation: spin 1.2s linear infinite;
+ margin-bottom: 30rpx;
+}
+
+@keyframes spin {
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
+}
+
+.loading-text {
+ font-size: 30rpx;
+ color: #666666;
+}
+
+/* 内容区域 */
+.content-container {
+ padding: 25rpx 25rpx 220rpx; /* 减少底部内边距,避免过多空白 */
+}
+
+/* 商品图片和介绍区域 */
+.shangpin-area {
+ display: flex;
+ background-color: #ffffff;
+ border-radius: 20rpx;
+ padding: 25rpx;
+ margin-bottom: 25rpx;
+ box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
+}
+
+.shangpin-image-container {
+ flex-shrink: 0;
+ width: 180rpx;
+ height: 180rpx;
+ margin-right: 25rpx;
+}
+
+.shangpin-image {
+ width: 100%;
+ height: 100%;
+ border-radius: 12rpx;
+ background-color: #f5f5f5;
+}
+
+.shangpin-jieshao {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+}
+
+.jieshao-text {
+ font-size: 28rpx;
+ line-height: 42rpx;
+ color: #333333;
+ font-weight: 400;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 4;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.chakan-wanzheng {
+ margin-top: 10rpx;
+}
+
+.chakan-wanzheng text {
+ font-size: 24rpx;
+ color: #2196f3;
+ text-decoration: underline;
+}
+
+/* 通用区域样式 */
+.info-title {
+ font-size: 32rpx;
+ color: #333333;
+ font-weight: 600;
+ margin-bottom: 20rpx;
+ padding-bottom: 12rpx;
+ border-bottom: 2rpx solid rgba(0, 0, 0, 0.08);
+}
+
+.section-subtitle {
+ font-size: 28rpx;
+ color: #555555;
+ font-weight: 500;
+ display: block;
+ margin: 15rpx 0 10rpx;
+}
+
+/* 订单信息区域 */
+.dingdan-info-area,
+.shangjia-area,
+.tuikuan-area,
+.chufa-area,
+.dashou-jiaofu-area,
+.tijiao-area {
+ background-color: #ffffff;
+ border-radius: 20rpx;
+ padding: 25rpx;
+ margin-bottom: 25rpx;
+ box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
+}
+
+.info-item-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 18rpx;
+ padding: 12rpx 0;
+}
+
+.info-item-row:last-child {
+ margin-bottom: 0;
+}
+
+.item-label {
+ font-size: 28rpx;
+ color: #666666;
+ font-weight: 400;
+ flex-shrink: 0;
+ min-width: 150rpx;
+}
+
+/* 右侧区域容器 */
+.item-right-area {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+}
+
+.item-value {
+ font-size: 28rpx;
+ color: #333333;
+ font-weight: 500;
+ word-break: break-all;
+ text-align: right;
+ margin-right: 15rpx;
+}
+
+/* 特殊样式 */
+.jine-green {
+ color: #4caf50 !important;
+ font-weight: 600 !important;
+ font-size: 32rpx !important;
+}
+
+.zhuangtai-text {
+ font-weight: 600 !important;
+ font-size: 30rpx !important;
+ color: #333333 !important;
+}
+
+/* 按钮样式(复制、查看) */
+.copy-btn,
+.chakan-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 24rpx;
+ color: #4caf50;
+ background-color: #e8f5e9;
+ padding: 6rpx 16rpx;
+ border-radius: 20rpx;
+ border: 1rpx solid #4caf50;
+ transition: all 0.2s ease;
+ white-space: nowrap;
+ flex-shrink: 0;
+}
+
+.chakan-btn {
+ color: #2196f3;
+ background-color: #e3f2fd;
+ border-color: #2196f3;
+ margin-left: 10rpx;
+}
+
+.copy-btn-hover,
+.chakan-btn:active {
+ background-color: #c8e6c9;
+ transform: scale(0.95);
+}
+
+.copy-icon {
+ font-size: 20rpx;
+ margin-right: 6rpx;
+}
+
+.copy-text {
+ font-weight: 500;
+}
+
+/* 打手交付图片网格 */
+.jiaofu-grid {
+ display: flex;
+ flex-wrap: wrap;
+ margin: -5rpx;
+}
+
+.jiaofu-image-item {
+ width: calc(33.333% - 10rpx);
+ margin: 5rpx;
+ aspect-ratio: 1/1;
+}
+
+.jiaofu-image {
+ width: 100%;
+ height: 100%;
+ border-radius: 12rpx;
+ background-color: #f5f5f5;
+ transition: transform 0.2s ease;
+}
+
+.jiaofu-image:active {
+ transform: scale(0.95);
+}
+
+/* 留言内容样式 */
+.liuyan-content {
+ font-size: 26rpx;
+ line-height: 40rpx;
+ color: #555555;
+ padding: 15rpx;
+ background-color: #f9f9f9;
+ border-radius: 12rpx;
+ margin-top: 8rpx;
+}
+
+/* 选择图片区域 */
+.yixuan-tupian-grid {
+ display: flex;
+ flex-wrap: wrap;
+ margin: -8rpx;
+}
+
+.yixuan-tupian-item {
+ width: calc(33.333% - 16rpx);
+ margin: 8rpx;
+ aspect-ratio: 1/1;
+ position: relative;
+}
+
+.yixuan-tupian {
+ width: 100%;
+ height: 100%;
+ border-radius: 12rpx;
+ background-color: #f5f5f5;
+}
+
+.shanchu-btn {
+ position: absolute;
+ top: -10rpx;
+ right: -10rpx;
+ width: 40rpx;
+ height: 40rpx;
+ background-color: #ff4444;
+ color: white;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 24rpx;
+ font-weight: bold;
+ z-index: 10;
+}
+
+.tianjia-btn {
+ width: calc(33.333% - 16rpx);
+ margin: 8rpx;
+ aspect-ratio: 1/1;
+ background-color: #f5f5f5;
+ border-radius: 12rpx;
+ border: 2rpx dashed #cccccc;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ color: #666666;
+}
+
+.tianjia-icon {
+ font-size: 48rpx;
+ font-weight: 300;
+ margin-bottom: 10rpx;
+}
+
+.tianjia-text {
+ font-size: 24rpx;
+ margin-bottom: 4rpx;
+}
+
+.tianjia-tip {
+ font-size: 20rpx;
+ color: #999999;
+}
+
+/* 上传进度 */
+.shangchuan-jindu-container {
+ margin-top: 20rpx;
+}
+
+.jindu-bar {
+ width: 100%;
+ height: 20rpx;
+ background-color: #f0f0f0;
+ border-radius: 10rpx;
+ overflow: hidden;
+ margin-bottom: 10rpx;
+}
+
+.jindu-fill {
+ height: 100%;
+ background-color: #4caf50;
+ border-radius: 10rpx;
+ transition: width 0.3s ease;
+}
+
+.jindu-text {
+ font-size: 24rpx;
+ color: #666666;
+ text-align: center;
+ display: block;
+}
+
+/* 留言输入 */
+.liuyan-textarea {
+ width: 100%;
+ min-height: 120rpx;
+ font-size: 28rpx;
+ color: #333333;
+ background-color: #f9f9f9;
+ border-radius: 12rpx;
+ padding: 20rpx;
+ margin-top: 10rpx;
+ box-sizing: border-box;
+}
+
+.zi-shu {
+ display: block;
+ text-align: right;
+ font-size: 24rpx;
+ color: #999999;
+ margin-top: 8rpx;
+}
+
+/* ==================== 底部按钮区域 ==================== */
+.bottom-btns-container {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background-color: #ffffff;
+ padding: 20rpx 30rpx;
+ border-top: 1rpx solid #f0f0f0;
+ box-shadow: 0 -2rpx 20rpx rgba(0, 0, 0, 0.08);
+ z-index: 100;
+ display: flex;
+ flex-direction: column;
+ gap: 10rpx; /* 减小按钮间距 */
+}
+
+/* 🆕 新增:按钮行布局(用于并排两个按钮) */
+.btn-row {
+ display: flex;
+ justify-content: space-between;
+ width: 100%;
+}
+
+/* 🆕 当有两个按钮时,各占一半(减去gap的一半) */
+.btn-row .bottom-btn {
+ width: calc(50% - 10rpx);
+}
+
+/* 🆕 当只有一个按钮时,占满 */
+.btn-row .full-width {
+ width: 100%;
+}
+
+/* 统一底部按钮样式 */
+.bottom-btn {
+ width: 100%;
+ height: 88rpx; /* 统一高度 */
+ border-radius: 44rpx; /* 圆角 */
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 32rpx;
+ font-weight: 600;
+ transition: all 0.3s ease;
+ position: relative;
+ overflow: hidden;
+ border: none;
+ box-sizing: border-box;
+}
+
+.btn-hover {
+ opacity: 0.9;
+ transform: scale(0.98);
+}
+
+/* 联系客服按钮 */
+.contact-btn {
+ background-color: #2196F3;
+ color: #ffffff;
+ box-shadow: 0 4rpx 15rpx rgba(33, 150, 243, 0.3);
+}
+
+/* 🆕 新增:联系老板按钮 */
+.boss-btn {
+ background-color: #ff9800;
+ color: #ffffff;
+ box-shadow: 0 4rpx 15rpx rgba(255, 152, 0, 0.3);
+}
+
+/* 🆕 新增:修改按钮(黑色) */
+.modify-btn {
+ background-color: #333333;
+ color: #ffffff;
+ box-shadow: 0 4rpx 15rpx rgba(51, 51, 51, 0.3);
+}
+
+/* 🆕 新增:取消修改按钮 */
+.cancel-modify-btn {
+ background-color: #9e9e9e;
+ color: #ffffff;
+ box-shadow: 0 4rpx 15rpx rgba(158, 158, 158, 0.3);
+}
+
+/* 🆕 新增:确认修改按钮 */
+.confirm-modify-btn {
+ background-color: #4caf50;
+ color: #ffffff;
+ box-shadow: 0 4rpx 15rpx rgba(76, 175, 80, 0.3);
+}
+
+/* 提交按钮 */
+.submit-btn {
+ background: linear-gradient(135deg, #4caf50 0%, #2e7d32 100%);
+ color: #ffffff;
+ box-shadow: 0 4rpx 15rpx rgba(76, 175, 80, 0.3);
+}
+
+/* 提交按钮闪光特效 */
+.submit-btn::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
+ animation: flash 2s infinite;
+}
+
+@keyframes flash {
+ 0% { left: -100%; }
+ 100% { left: 100%; }
+}
+
+.btn-text {
+ position: relative;
+ z-index: 1;
+}
+
+/* ==================== 弹窗样式 ==================== */
+.modal-container {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 1000;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.modal-mask {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.5);
+}
+
+.modal-content {
+ position: relative;
+ width: 600rpx;
+ max-height: 70vh;
+ background-color: #ffffff;
+ border-radius: 20rpx;
+ overflow: hidden;
+ box-shadow: 0 10rpx 40rpx rgba(0, 0, 0, 0.15);
+ z-index: 1001;
+}
+
+.modal-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 30rpx;
+ border-bottom: 1rpx solid #f0f0f0;
+}
+
+.modal-title {
+ font-size: 34rpx;
+ color: #333333;
+ font-weight: 600;
+}
+
+.modal-close {
+ font-size: 40rpx;
+ color: #999999;
+ width: 40rpx;
+ height: 40rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.modal-body {
+ padding: 30rpx;
+ max-height: 50vh;
+ overflow: hidden;
+}
+
+.modal-scroll {
+ height: 100%;
+ max-height: 50vh;
+}
+
+.modal-content-text {
+ font-size: 28rpx;
+ line-height: 44rpx;
+ color: #333333;
+ white-space: pre-wrap;
+}
+
+/* 响应式调整 */
+@media (max-width: 375px) {
+ .shangpin-image-container {
+ width: 160rpx;
+ height: 160rpx;
+ }
+
+ .bottom-btn {
+ height: 80rpx;
+ font-size: 30rpx;
+ }
+
+ .item-label {
+ min-width: 140rpx;
+ font-size: 26rpx;
+ }
+
+ .item-value {
+ font-size: 26rpx;
+ }
+
+ .modal-content {
+ width: 560rpx;
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/pages/fenlei/fenlei.js b/miniprogram/pages/fenlei/fenlei.js
new file mode 100644
index 0000000..9cbf998
--- /dev/null
+++ b/miniprogram/pages/fenlei/fenlei.js
@@ -0,0 +1,452 @@
+// pages/fenlei/fenlei.js
+const app = getApp()
+
+Page({
+ /**
+ * 页面的初始数据
+ */
+ data: {
+ // 全局变量引用
+ ossImageUrl: '',
+
+ // 搜索相关
+ searchText: '',
+ showSearchResult: false,
+ searchResults: [],
+ isSearching: false,
+
+ // 页面数据
+ shangpinleixing: [],
+ shangpinzhuanqu: [],
+ shangpinliebiao: [],
+
+ // 状态控制
+ selectedLeixingId: null,
+ selectedZhuanquId: null,
+ filteredZhuanquList: [], // 当前类型下的专区列表
+ filteredShangpinList: [], // 当前专区下的商品列表
+ zhuanquByLeixing: {}, // 类型->专区映射对象
+ shangpinByZhuanqu: {}, // 专区->商品映射对象
+ zhuanquGoodsCount: {}, // 专区商品数量统计
+
+ // UI状态
+ isLoading: false,
+ hasInitialized: false
+ },
+
+ /**
+ * 生命周期函数--监听页面加载
+ */
+ onLoad(options) {
+ // 获取全局变量
+ this.setData({
+ ossImageUrl: app.globalData.ossImageUrl
+ })
+
+ // 初始化数据
+ this.initPageData()
+ },
+ onShow() {
+ // 原有代码...
+
+ // 🆕 注册通知组件
+ this.registerNotificationComponent();
+},
+
+// 🆕 新增:注册通知组件
+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()
+ };
+ }
+},
+
+ /**
+ * 初始化页面数据
+ */
+ initPageData() {
+ const that = this
+ this.setData({ isLoading: true })
+
+ // 检查全局变量是否已有数据
+ if (app.globalData.shangpinleixing && app.globalData.shangpinleixing.length > 0) {
+ this.initFromGlobalData()
+ } else {
+ this.loadAllShangpinData()
+ }
+ },
+
+ /**
+ * 从全局变量初始化数据
+ */
+ initFromGlobalData() {
+ const leixingList = app.globalData.shangpinleixing || []
+ const zhuanquList = app.globalData.shangpinzhuanqu || []
+ const shangpinList = app.globalData.shangpinliebiao || []
+
+ // 构建映射关系对象
+ const zhuanquByLeixing = {}
+ const shangpinByZhuanqu = {}
+ const zhuanquGoodsCount = {}
+
+ // 1. 构建专区映射 (按类型分组)
+ zhuanquList.forEach(zhuanqu => {
+ const leixingId = zhuanqu.leixing_id
+ if (!zhuanquByLeixing[leixingId]) {
+ zhuanquByLeixing[leixingId] = []
+ }
+ zhuanquByLeixing[leixingId].push(zhuanqu)
+ })
+
+ // 2. 构建商品映射 (按专区分组)并统计数量
+ shangpinList.forEach(shangpin => {
+ const zhuanquId = shangpin.zhuanqu_id
+ if (zhuanquId) {
+ if (!shangpinByZhuanqu[zhuanquId]) {
+ shangpinByZhuanqu[zhuanquId] = []
+ zhuanquGoodsCount[zhuanquId] = 0
+ }
+
+ // 处理价格显示
+ const jiage = parseFloat(shangpin.jiage) || 0
+ const priceInteger = Math.floor(jiage)
+ let priceDecimal = '00'
+
+ const decimalPart = (jiage - priceInteger).toFixed(2)
+ if (decimalPart > 0) {
+ priceDecimal = decimalPart.toString().split('.')[1]
+ }
+
+ shangpinByZhuanqu[zhuanquId].push({
+ id: shangpin.id,
+ biaoqian: shangpin.biaoqian,
+ jiage: jiage,
+ priceInteger: priceInteger,
+ priceDecimal: priceDecimal,
+ tupian_url: shangpin.tupian_url,
+ duiwai_xiaoliang: shangpin.duiwai_xiaoliang || 0
+ })
+
+ zhuanquGoodsCount[zhuanquId] += 1
+ }
+ })
+
+ // 设置初始选中状态(第一个类型和第一个专区)
+ const firstLeixing = leixingList[0]
+ let firstZhuanquId = null
+
+ if (firstLeixing && zhuanquByLeixing[firstLeixing.id]) {
+ firstZhuanquId = zhuanquByLeixing[firstLeixing.id][0]?.id || null
+ }
+
+ this.setData({
+ shangpinleixing: leixingList,
+ shangpinzhuanqu: zhuanquList,
+ shangpinliebiao: shangpinList,
+ zhuanquByLeixing: zhuanquByLeixing,
+ shangpinByZhuanqu: shangpinByZhuanqu,
+ zhuanquGoodsCount: zhuanquGoodsCount,
+ selectedLeixingId: firstLeixing?.id || null,
+ selectedZhuanquId: firstZhuanquId
+ }, () => {
+ // 筛选数据
+ this.filterData()
+ this.setData({ isLoading: false, hasInitialized: true })
+ })
+ },
+
+ /**
+ * 加载所有商品数据
+ */
+ loadAllShangpinData() {
+ const that = this
+ wx.request({
+ url: app.globalData.apiBaseUrl + '/shangpin/shangpinhuoqu/',
+ method: 'POST',
+ header: {
+ 'content-type': 'application/json'
+ },
+ success(res) {
+ if (res.statusCode === 200 && res.data) {
+ const data = res.data
+
+ // 存储到全局变量
+ app.globalData.shangpinleixing = data.shangpinleixing || []
+ app.globalData.shangpinzhuanqu = data.shangpinzhuanqu || []
+ app.globalData.shangpinliebiao = data.shangpinliebiao || []
+
+ // 从全局变量初始化
+ that.initFromGlobalData()
+ } else {
+ that.handleLoadError()
+ }
+ },
+ fail(error) {
+ console.error('加载商品数据失败:', error)
+ that.handleLoadError()
+ }
+ })
+ },
+
+ /**
+ * 处理加载错误
+ */
+ handleLoadError() {
+ wx.showToast({
+ title: '加载失败,请重试',
+ icon: 'none'
+ })
+ this.setData({ isLoading: false })
+ },
+
+ /**
+ * 筛选数据
+ */
+ filterData() {
+ const { selectedLeixingId, selectedZhuanquId, zhuanquByLeixing, shangpinByZhuanqu } = this.data
+
+ // 1. 筛选专区列表
+ const filteredZhuanquList = zhuanquByLeixing[selectedLeixingId] || []
+
+ // 2. 筛选商品列表
+ let filteredShangpinList = []
+ if (selectedZhuanquId) {
+ filteredShangpinList = shangpinByZhuanqu[selectedZhuanquId] || []
+ } else if (filteredZhuanquList.length > 0) {
+ // 如果没有选中的专区,默认显示第一个专区的商品
+ const firstZhuanquId = filteredZhuanquList[0].id
+ filteredShangpinList = shangpinByZhuanqu[firstZhuanquId] || []
+ this.setData({ selectedZhuanquId: firstZhuanquId })
+ }
+
+ this.setData({
+ filteredZhuanquList: filteredZhuanquList,
+ filteredShangpinList: filteredShangpinList
+ })
+ },
+
+ /**
+ * 选择商品类型
+ */
+ selectLeixing(e) {
+ const leixingId = e.currentTarget.dataset.id
+
+ if (this.data.selectedLeixingId === leixingId) {
+ return
+ }
+
+ // 获取该类型下的第一个专区
+ const zhuanquList = this.data.zhuanquByLeixing[leixingId] || []
+ const firstZhuanquId = zhuanquList.length > 0 ? zhuanquList[0].id : null
+
+ this.setData({
+ selectedLeixingId: leixingId,
+ selectedZhuanquId: firstZhuanquId
+ }, () => {
+ this.filterData()
+ })
+ },
+
+ /**
+ * 选择商品专区
+ */
+ selectZhuanqu(e) {
+ const zhuanquId = e.currentTarget.dataset.id
+
+ if (this.data.selectedZhuanquId === zhuanquId) {
+ return
+ }
+
+ this.setData({
+ selectedZhuanquId: zhuanquId
+ }, () => {
+ // 筛选商品列表
+ const filteredShangpinList = this.data.shangpinByZhuanqu[zhuanquId] || []
+ this.setData({
+ filteredShangpinList: filteredShangpinList
+ })
+ })
+ },
+
+ /**
+ * 搜索框输入事件
+ */
+ onSearchInput(e) {
+ this.setData({
+ searchText: e.detail.value
+ })
+ },
+
+ /**
+ * 点击搜索按钮
+ */
+ onSearchTap() {
+ const searchText = this.data.searchText.trim()
+ if (!searchText) {
+ wx.showToast({
+ title: '请输入搜索内容',
+ icon: 'none'
+ })
+ return
+ }
+
+ // 开始搜索
+ this.startSearch(searchText)
+ },
+
+ /**
+ * 键盘搜索事件(回车键)
+ */
+ onSearchConfirm(e) {
+ const searchText = e.detail.value.trim()
+ if (!searchText) {
+ wx.showToast({
+ title: '请输入搜索内容',
+ icon: 'none'
+ })
+ return
+ }
+
+ this.setData({
+ searchText: searchText
+ })
+
+ // 开始搜索
+ this.startSearch(searchText)
+ },
+
+ /**
+ * 执行搜索
+ */
+ startSearch(searchText) {
+ this.setData({
+ isSearching: true,
+ showSearchResult: true
+ })
+
+ // 本地搜索
+ const allShangpin = this.data.shangpinliebiao
+ const results = []
+
+ for (const item of allShangpin) {
+ if (item.biaoqian && item.biaoqian.toLowerCase().includes(searchText.toLowerCase())) {
+ // 处理价格显示
+ const jiage = parseFloat(item.jiage) || 0
+ const priceInteger = Math.floor(jiage)
+ let priceDecimal = '00'
+
+ const decimalPart = (jiage - priceInteger).toFixed(2)
+ if (decimalPart > 0) {
+ priceDecimal = decimalPart.toString().split('.')[1]
+ }
+
+ results.push({
+ id: item.id,
+ biaoqian: item.biaoqian,
+ jiage: jiage,
+ priceInteger: priceInteger,
+ priceDecimal: priceDecimal,
+ tupian_url: item.tupian_url,
+ leixing_name: this.getLeixingName(item.leixing_id),
+ zhuanqu_name: this.getZhuanquName(item.zhuanqu_id)
+ })
+ }
+ }
+
+ this.setData({
+ searchResults: results,
+ isSearching: false
+ })
+ },
+
+ /**
+ * 清空搜索
+ */
+ onClearSearch() {
+ this.setData({
+ searchText: '',
+ showSearchResult: false,
+ searchResults: []
+ })
+ },
+
+ /**
+ * 根据类型ID获取类型名称
+ */
+ getLeixingName(leixingId) {
+ const leixingList = this.data.shangpinleixing
+ for (const item of leixingList) {
+ if (item.id === leixingId) {
+ return item.jieshao
+ }
+ }
+ return ''
+ },
+
+ /**
+ * 根据专区ID获取专区名称
+ */
+ getZhuanquName(zhuanquId) {
+ const zhuanquList = this.data.shangpinzhuanqu
+ for (const item of zhuanquList) {
+ if (item.id === zhuanquId) {
+ return item.mingzi
+ }
+ }
+ return ''
+ },
+
+ /**
+ * 跳转到商品详情页
+ */
+ goToDetail(e) {
+ const shangpinId = e.currentTarget.dataset.id
+ if (!shangpinId) return
+
+ wx.navigateTo({
+ url: `/pages/shangpinxiangqing/shangpinxiangqing?id=${shangpinId}`
+ })
+ },
+
+ /**
+ * 刷新所有数据
+ */
+ onRefresh() {
+ wx.showLoading({
+ title: '刷新中...',
+ mask: true
+ })
+
+ // 清空全局变量
+ app.globalData.shangpinleixing = []
+ app.globalData.shangpinzhuanqu = []
+ app.globalData.shangpinliebiao = []
+
+ this.loadAllShangpinData()
+
+ setTimeout(() => {
+ wx.hideLoading()
+ wx.showToast({
+ title: '刷新成功',
+ icon: 'success',
+ duration: 1500
+ })
+ }, 1000)
+ },
+
+ /**
+ * 用户点击右上角分享
+ */
+ onShareAppMessage() {
+ return {
+ title: '阿龙电竞 - 商品分类',
+ path: '/pages/fenlei/fenlei'
+ }
+ }
+})
\ No newline at end of file
diff --git a/miniprogram/pages/fenlei/fenlei.json b/miniprogram/pages/fenlei/fenlei.json
new file mode 100644
index 0000000..4af9065
--- /dev/null
+++ b/miniprogram/pages/fenlei/fenlei.json
@@ -0,0 +1,6 @@
+{
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification",
+ "custom-tab-bar": "/custom-tab-bar/index"
+ }
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/fenlei/fenlei.wxml b/miniprogram/pages/fenlei/fenlei.wxml
new file mode 100644
index 0000000..6f015fa
--- /dev/null
+++ b/miniprogram/pages/fenlei/fenlei.wxml
@@ -0,0 +1,217 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{item.jieshao}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{item.mingzi}}
+ ({{zhuanquGoodsCount[item.id] || 0}})
+
+
+
+
+
+
+
+ 暂无专区
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{item.biaoqian}}
+
+
+
+
+
+
+
+
+
+
+
+ 该专区暂无商品
+
+
+
+
+ — 已加载全部商品 —
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{item.biaoqian}}
+
+ {{item.leixing_name}}
+ ·
+ {{item.zhuanqu_name}}
+
+
+ ¥
+ {{item.priceInteger}}
+ .{{item.priceDecimal}}
+
+
+
+
+
+
+
+ 未找到相关商品
+
+
+
+
+
+
+ 搜索中...
+
+
+
+
+
+
+
+
+
+
+
+
+ 加载中...
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/fenlei/fenlei.wxss b/miniprogram/pages/fenlei/fenlei.wxss
new file mode 100644
index 0000000..d4a3420
--- /dev/null
+++ b/miniprogram/pages/fenlei/fenlei.wxss
@@ -0,0 +1,1215 @@
+/* pages/fenlei/fenlei.wxss */
+.fenlei-page {
+ width: 100%;
+ height: 100vh;
+ background: linear-gradient(135deg, #f8f9ff 0%, #f0f2ff 100%);
+ display: flex;
+ flex-direction: column;
+ position: relative;
+ overflow: hidden;
+ }
+
+ /* 1. 毛玻璃搜索框区域(固定) */
+ .search-container {
+ padding: 24rpx 32rpx;
+ background: transparent;
+ flex-shrink: 0;
+ position: relative;
+ z-index: 100;
+ }
+
+ .search-box {
+ display: flex;
+ align-items: center;
+ background: rgba(255, 255, 255, 0.85);
+ backdrop-filter: blur(30rpx);
+ -webkit-backdrop-filter: blur(30rpx);
+ border-radius: 50rpx;
+ padding: 0 28rpx;
+ height: 84rpx;
+ box-shadow:
+ 0 8rpx 32rpx rgba(31, 38, 135, 0.1),
+ inset 0 1rpx 0 rgba(255, 255, 255, 0.8);
+ border: 1rpx solid rgba(255, 255, 255, 0.3);
+ position: relative;
+ overflow: hidden;
+ }
+
+ .search-box::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0.1));
+ border-radius: 50rpx;
+ z-index: -1;
+ }
+
+ .search-icon {
+ width: 34rpx;
+ height: 34rpx;
+ margin-right: 18rpx;
+ opacity: 0.6;
+ }
+
+ .search-input {
+ flex: 1;
+ height: 100%;
+ font-size: 28rpx;
+ color: #222;
+ font-weight: 400;
+ }
+
+ .search-placeholder {
+ color: #aaa;
+ font-size: 28rpx;
+ }
+
+ .search-btn {
+ background: linear-gradient(135deg, #ffcc00, #ffaa00);
+ border-radius: 40rpx;
+ padding: 14rpx 32rpx;
+ margin-left: 20rpx;
+ position: relative;
+ overflow: hidden;
+ box-shadow: 0 6rpx 20rpx rgba(255, 170, 0, 0.3);
+ transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
+ }
+
+ .search-btn:active {
+ transform: scale(0.95);
+ box-shadow: 0 4rpx 15rpx rgba(255, 170, 0, 0.4);
+ }
+
+ .search-btn-text {
+ font-size: 26rpx;
+ color: white;
+ font-weight: 600;
+ position: relative;
+ z-index: 1;
+ }
+
+ .search-btn-glow {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent);
+ transform: translateX(-100%);
+ transition: transform 0.6s;
+ }
+
+ .search-btn:active .search-btn-glow {
+ transform: translateX(100%);
+ }
+
+ .search-clear {
+ width: 36rpx;
+ height: 36rpx;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-left: 16rpx;
+ opacity: 0.6;
+ transition: opacity 0.3s;
+ }
+
+ .search-clear:active {
+ opacity: 1;
+ }
+
+ .clear-icon {
+ width: 24rpx;
+ height: 24rpx;
+ }
+
+ /* 2. 商品类型选择区域(固定)- 恢复原大小 */
+ .leixing-container {
+ padding: 0 32rpx 24rpx;
+ flex-shrink: 0;
+ position: relative;
+ }
+
+ .leixing-bg-glow {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: 300rpx;
+ height: 300rpx;
+ background: radial-gradient(circle, rgba(255, 204, 0, 0.1) 0%, transparent 70%);
+ z-index: -1;
+ }
+
+ .leixing-scroll {
+ white-space: nowrap;
+ height: 180rpx; /* 恢复原大小 */
+ }
+
+ .leixing-list {
+ display: inline-flex;
+ padding-right: 32rpx;
+ }
+
+ .leixing-item {
+ display: inline-flex;
+ flex-direction: column;
+ align-items: center;
+ margin-right: 48rpx; /* 增加间距 */
+ position: relative;
+ padding: 12rpx;
+ }
+
+ .leixing-image-box {
+ width: 100rpx; /* 恢复原大小 */
+ height: 100rpx; /* 恢复原大小 */
+ border-radius: 50%;
+ position: relative;
+ margin-bottom: 16rpx;
+ overflow: hidden;
+ background: white;
+ box-shadow:
+ 0 12rpx 30rpx rgba(0, 0, 0, 0.12),
+ 0 4rpx 8rpx rgba(0, 0, 0, 0.06);
+ transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
+ }
+
+ .leixing-active .leixing-image-box {
+ transform: scale(1.12);
+ box-shadow:
+ 0 18rpx 40rpx rgba(255, 204, 0, 0.3),
+ 0 8rpx 16rpx rgba(255, 170, 0, 0.2);
+ }
+
+ .leixing-image {
+ width: 100%;
+ height: 100%;
+ border-radius: 50%;
+ object-fit: cover;
+ }
+
+ .leixing-glow {
+ position: absolute;
+ top: -3rpx;
+ left: -3rpx;
+ right: -3rpx;
+ bottom: -3rpx;
+ border-radius: 50%;
+ background: linear-gradient(135deg, #ffcc00, #ffaa00, #ff8800);
+ opacity: 0;
+ transition: opacity 0.4s;
+ z-index: -1;
+ filter: blur(10rpx);
+ }
+
+ .leixing-glow-active {
+ opacity: 0.6;
+ animation: glowPulse 2s infinite;
+ }
+
+ .leixing-sparkle {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 20rpx;
+ height: 20rpx;
+ background: white;
+ border-radius: 50%;
+ transform: translate(-50%, -50%) scale(0);
+ opacity: 0;
+ pointer-events: none;
+ }
+
+ .sparkle-animation {
+ animation: sparkleFlash 1.5s infinite;
+ }
+
+ .leixing-text {
+ font-size: 24rpx;
+ color: #555; /* 颜色加深 */
+ font-weight: 500;
+ transition: all 0.3s;
+ white-space: nowrap;
+ }
+
+ .leixing-active .leixing-text {
+ color: #ff9900;
+ font-weight: 600;
+ transform: translateY(-2rpx);
+ text-shadow: 0 2rpx 4rpx rgba(255, 153, 0, 0.2);
+ }
+
+ .leixing-active-bar {
+ position: absolute;
+ bottom: -4rpx;
+ width: 44rpx;
+ height: 4rpx;
+ background: linear-gradient(90deg, #ffcc00, #ffaa00);
+ border-radius: 2rpx;
+ opacity: 0;
+ transform: scaleX(0);
+ transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
+ box-shadow: 0 2rpx 8rpx rgba(255, 170, 0, 0.4);
+ }
+
+ .leixing-active-bar-show {
+ opacity: 1;
+ transform: scaleX(1);
+ }
+
+ .leixing-border-effect {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ border-radius: 50%;
+ border: 2rpx solid transparent;
+ opacity: 0;
+ pointer-events: none;
+ }
+
+ .border-effect-active {
+ opacity: 1;
+ border-color: rgba(255, 204, 0, 0.5);
+ animation: borderGlow 2s infinite;
+ }
+
+ /* 3. 左右分栏布局(可滚动区域) */
+ .main-container {
+ flex: 1;
+ display: flex;
+ height: calc(100vh - 300rpx);
+ overflow: hidden;
+ }
+
+ /* 左边:商品专区列表 */
+ .zhuanqu-left {
+ width: 200rpx;
+ background: rgba(255, 255, 255, 0.7);
+ backdrop-filter: blur(10rpx);
+ border-right: 1rpx solid rgba(0, 0, 0, 0.08);
+ flex-shrink: 0;
+ position: relative;
+ }
+
+ .zhuanqu-list {
+ padding: 0;
+ }
+
+ .zhuanqu-item {
+ position: relative;
+ padding: 32rpx 0;
+ border-bottom: 1rpx solid rgba(0, 0, 0, 0.05);
+ transition: all 0.3s;
+ }
+
+ .zhuanqu-item:active {
+ background: rgba(255, 204, 0, 0.05);
+ }
+
+ .zhuanqu-item-content {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 0 20rpx;
+ }
+
+ .zhuanqu-name {
+ font-size: 26rpx;
+ color: #444; /* 颜色加深 */
+ text-align: center;
+ line-height: 1.4;
+ margin-bottom: 8rpx;
+ transition: all 0.3s;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ overflow: hidden;
+ font-weight: 500;
+ }
+
+ .zhuanqu-active .zhuanqu-name {
+ color: #ff9900;
+ font-weight: 600;
+ text-shadow: 0 2rpx 4rpx rgba(255, 153, 0, 0.2);
+ }
+
+ .zhuanqu-count {
+ font-size: 22rpx;
+ color: #888;
+ transition: all 0.3s;
+ }
+
+ .zhuanqu-active .zhuanqu-count {
+ color: #ffaa00;
+ font-weight: 500;
+ }
+
+ .zhuanqu-active-indicator {
+ position: absolute;
+ right: 0;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 6rpx;
+ height: 44rpx;
+ background: linear-gradient(180deg, #ffcc00, #ffaa00);
+ border-radius: 3rpx 0 0 3rpx;
+ opacity: 0;
+ transition: all 0.3s;
+ overflow: hidden;
+ }
+
+ .zhuanqu-indicator-glow {
+ opacity: 1;
+ animation: indicatorGlow 2s infinite;
+ box-shadow: 0 0 20rpx rgba(255, 204, 0, 0.5);
+ }
+
+ .indicator-sparkle {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 10rpx;
+ background: rgba(255, 255, 255, 0.6);
+ animation: sparkleMove 1.5s infinite;
+ }
+
+ .zhuanqu-hover-glow {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: linear-gradient(90deg, transparent, rgba(255, 204, 0, 0.05), transparent);
+ opacity: 0;
+ transition: opacity 0.3s;
+ pointer-events: none;
+ }
+
+ .zhuanqu-item:active .zhuanqu-hover-glow {
+ opacity: 1;
+ }
+
+ .zhuanqu-click-effect {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 0;
+ height: 0;
+ border-radius: 50%;
+ background: rgba(255, 204, 0, 0.1);
+ transform: translate(-50%, -50%);
+ opacity: 0;
+ pointer-events: none;
+ }
+
+ .zhuanqu-item:active .zhuanqu-click-effect {
+ animation: rippleEffect 0.6s ease-out;
+ }
+
+ .zhuanqu-empty {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 120rpx 0;
+ }
+
+ .zhuanqu-empty-icon {
+ width: 120rpx;
+ height: 120rpx;
+ opacity: 0.2;
+ margin-bottom: 28rpx;
+ }
+
+ .zhuanqu-empty-text {
+ font-size: 26rpx;
+ color: #999;
+ }
+
+ /* 右边:商品列表(紧凑布局) */
+ .shangpin-right {
+ flex: 1;
+ background: #fafafa;
+ }
+
+ .shangpin-list {
+ padding: 0;
+ }
+
+ .shangpin-item {
+ background: white;
+ padding: 28rpx 30rpx 22rpx; /* 底部距离缩小 */
+ position: relative;
+ display: flex;
+ align-items: flex-start;
+ min-height: 140rpx;
+ overflow: hidden;
+ }
+
+ .shangpin-item:active {
+ background: rgba(0, 0, 0, 0.02);
+ }
+
+ .shangpin-image-box {
+ margin-right: 24rpx;
+ flex-shrink: 0;
+ position: relative;
+ }
+
+ .shangpin-image {
+ width: 120rpx;
+ height: 120rpx;
+ border-radius: 14rpx;
+ background: #f5f5f5;
+ object-fit: cover;
+ }
+
+ .shangpin-image-glow {
+ position: absolute;
+ top: -4rpx;
+ left: -4rpx;
+ right: -4rpx;
+ bottom: -4rpx;
+ border-radius: 18rpx;
+ background: linear-gradient(135deg, rgba(255, 204, 0, 0.2), transparent);
+ opacity: 0;
+ transition: opacity 0.3s;
+ pointer-events: none;
+ }
+
+ .shangpin-item:active .shangpin-image-glow {
+ opacity: 0.5;
+ }
+
+ .image-corner {
+ position: absolute;
+ top: 0;
+ right: 0;
+ width: 20rpx;
+ height: 20rpx;
+ background: linear-gradient(135deg, #ffcc00, transparent 70%);
+ border-radius: 0 14rpx 0 0;
+ }
+
+ .shangpin-info {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ height: 120rpx;
+ justify-content: space-between;
+ padding-top: 2rpx; /* 标题与图片顶部对齐 */
+ }
+
+ .shangpin-title {
+ margin-bottom: 0;
+ position: relative;
+ }
+
+ .shangpin-title-text {
+ font-size: 30rpx;
+ font-weight: 500;
+ color: #222; /* 黑色加深 */
+ line-height: 1.4;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 1;
+ overflow: hidden;
+ }
+
+ .title-underline {
+ position: absolute;
+ bottom: -4rpx;
+ left: 0;
+ width: 0;
+ height: 2rpx;
+ background: linear-gradient(90deg, #ffcc00, #ffaa00);
+ transition: width 0.3s;
+ }
+
+ .shangpin-item:active .title-underline {
+ width: 40rpx;
+ }
+
+ .shangpin-footer {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-top: auto;
+ }
+
+ .shangpin-price {
+ display: flex;
+ align-items: baseline;
+ position: relative;
+ }
+
+ .price-icon {
+ font-size: 24rpx;
+ color: #ff6600;
+ font-weight: 500;
+ }
+
+ .price-integer {
+ font-size: 36rpx; /* 适当调整大小 */
+ color: #ff6600;
+ font-weight: 600;
+ margin-left: 2rpx;
+ }
+
+ .price-decimal {
+ font-size: 24rpx;
+ color: #ff6600;
+ font-weight: 500;
+ }
+
+ .price-glow {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 80rpx;
+ height: 80rpx;
+ background: radial-gradient(circle, rgba(255, 102, 0, 0.1) 0%, transparent 70%);
+ transform: translate(-50%, -50%);
+ opacity: 0;
+ pointer-events: none;
+ }
+
+ .shangpin-item:active .price-glow {
+ animation: priceGlow 0.6s;
+ }
+
+ .detail-btn {
+ position: relative;
+ padding: 12rpx 28rpx;
+ background: linear-gradient(135deg, #ffcc00, #ffaa00);
+ border-radius: 40rpx;
+ transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
+ overflow: hidden;
+ box-shadow: 0 6rpx 20rpx rgba(255, 170, 0, 0.2);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 120rpx;
+ }
+
+ .detail-btn:active {
+ transform: scale(0.95);
+ box-shadow: 0 4rpx 15rpx rgba(255, 170, 0, 0.4);
+ }
+
+ .detail-btn-text {
+ font-size: 24rpx;
+ color: white;
+ font-weight: 500;
+ position: relative;
+ z-index: 2;
+ line-height: 1;
+ }
+
+ .btn-glow-effect {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent);
+ transform: translateX(-100%);
+ transition: transform 0.6s;
+ }
+
+ .detail-btn:active .btn-glow-effect {
+ transform: translateX(100%);
+ }
+
+ .btn-halo {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 0;
+ height: 0;
+ border-radius: 50%;
+ background: rgba(255, 255, 255, 0.3);
+ transform: translate(-50%, -50%);
+ opacity: 0;
+ pointer-events: none;
+ }
+
+ .detail-btn:active .btn-halo {
+ animation: buttonHalo 0.6s ease-out;
+ }
+
+ .btn-star {
+ position: absolute;
+ width: 6rpx;
+ height: 6rpx;
+ background: white;
+ border-radius: 50%;
+ opacity: 0;
+ pointer-events: none;
+ }
+
+ .detail-btn:active .btn-star {
+ animation: starSparkle 0.8s ease-out;
+ }
+
+ .item-divider {
+ position: absolute;
+ left: 30rpx;
+ right: 30rpx;
+ bottom: 0;
+ height: 1rpx;
+ background: linear-gradient(90deg, rgba(0, 0, 0, 0.08), rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0.08));
+ }
+
+ .item-hover-glow {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: linear-gradient(135deg, transparent, rgba(255, 204, 0, 0.03), transparent);
+ opacity: 0;
+ transition: opacity 0.3s;
+ pointer-events: none;
+ }
+
+ .shangpin-item:active .item-hover-glow {
+ opacity: 1;
+ }
+
+ .item-ripple-effect {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 0;
+ height: 0;
+ border-radius: 50%;
+ background: rgba(255, 204, 0, 0.1);
+ transform: translate(-50%, -50%);
+ opacity: 0;
+ pointer-events: none;
+ }
+
+ .shangpin-item:active .item-ripple-effect {
+ animation: rippleEffect 0.6s ease-out;
+ }
+
+ .shangpin-empty {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 150rpx 0;
+ }
+
+ .shangpin-empty-icon {
+ width: 180rpx;
+ height: 180rpx;
+ opacity: 0.15;
+ margin-bottom: 36rpx;
+ }
+
+ .shangpin-empty-text {
+ font-size: 28rpx;
+ color: #999;
+ margin-bottom: 40rpx;
+ }
+
+ .shangpin-empty-btn {
+ background: linear-gradient(135deg, #ffcc00, #ffaa00);
+ color: white;
+ font-size: 26rpx;
+ font-weight: 500;
+ padding: 16rpx 44rpx;
+ border-radius: 40rpx;
+ box-shadow: 0 8rpx 24rpx rgba(255, 170, 0, 0.3);
+ transition: all 0.3s;
+ }
+
+ .shangpin-empty-btn:active {
+ transform: scale(0.95);
+ box-shadow: 0 4rpx 16rpx rgba(255, 170, 0, 0.4);
+ }
+
+ .load-more {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 40rpx 0;
+ }
+
+ .load-more-line {
+ width: 60rpx;
+ height: 1rpx;
+ background: linear-gradient(90deg, transparent, rgba(0, 0, 0, 0.1), transparent);
+ }
+
+ .load-more-text {
+ font-size: 24rpx;
+ color: #bbb;
+ margin: 0 20rpx;
+ }
+
+ /* 搜索结果弹窗 */
+ .search-results-container {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 2000;
+ display: flex;
+ }
+
+ .search-mask {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.5);
+ backdrop-filter: blur(5rpx);
+ }
+
+ .search-results-content {
+ position: absolute;
+ top: 100rpx;
+ left: 32rpx;
+ right: 32rpx;
+ bottom: 100rpx;
+ background: rgba(255, 255, 255, 0.95);
+ backdrop-filter: blur(30rpx);
+ border-radius: 24rpx;
+ box-shadow:
+ 0 20rpx 60rpx rgba(0, 0, 0, 0.2),
+ inset 0 1rpx 0 rgba(255, 255, 255, 0.8);
+ border: 1rpx solid rgba(255, 255, 255, 0.3);
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ }
+
+ .search-results-header {
+ padding: 32rpx;
+ border-bottom: 1rpx solid rgba(0, 0, 0, 0.06);
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ flex-shrink: 0;
+ }
+
+ .search-header-left {
+ display: flex;
+ align-items: baseline;
+ }
+
+ .search-results-title {
+ font-size: 34rpx;
+ font-weight: 600;
+ color: #333;
+ }
+
+ .search-results-count {
+ font-size: 24rpx;
+ color: #999;
+ margin-left: 12rpx;
+ }
+
+ .search-close {
+ width: 44rpx;
+ height: 44rpx;
+ border-radius: 50%;
+ background: rgba(0, 0, 0, 0.05);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.3s;
+ }
+
+ .search-close:active {
+ background: rgba(0, 0, 0, 0.1);
+ transform: scale(0.9);
+ }
+
+ .close-icon {
+ width: 20rpx;
+ height: 20rpx;
+ opacity: 0.6;
+ }
+
+ .search-results-list {
+ flex: 1;
+ height: 100%; /* 新增:修复滚动问题的关键代码 */
+ }
+
+ .search-result-item {
+ display: flex;
+ padding: 28rpx 32rpx;
+ border-bottom: 1rpx solid rgba(0, 0, 0, 0.04);
+ position: relative;
+ transition: all 0.3s;
+ }
+
+ .search-result-item:active {
+ background: rgba(0, 0, 0, 0.02);
+ transform: translateY(-2rpx);
+ }
+
+ .search-result-left {
+ margin-right: 24rpx;
+ position: relative;
+ }
+
+ .search-result-image {
+ width: 120rpx;
+ height: 120rpx;
+ border-radius: 16rpx;
+ background: #f5f5f5;
+ object-fit: cover;
+ }
+
+ .search-image-glow {
+ position: absolute;
+ top: -4rpx;
+ left: -4rpx;
+ right: -4rpx;
+ bottom: -4rpx;
+ border-radius: 20rpx;
+ background: linear-gradient(135deg, rgba(255, 204, 0, 0.2), transparent);
+ opacity: 0;
+ transition: opacity 0.3s;
+ pointer-events: none;
+ }
+
+ .search-result-item:active .search-image-glow {
+ opacity: 0.5;
+ }
+
+ .search-result-right {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ }
+
+ .search-result-title {
+ font-size: 28rpx;
+ font-weight: 500;
+ color: #333;
+ line-height: 1.4;
+ margin-bottom: 16rpx;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ overflow: hidden;
+ }
+
+ .search-result-info {
+ display: flex;
+ gap: 12rpx;
+ margin-bottom: 20rpx;
+ }
+
+ .info-tag {
+ padding: 6rpx 12rpx;
+ background: rgba(255, 204, 0, 0.1);
+ border-radius: 6rpx;
+ }
+
+ .search-result-category {
+ font-size: 22rpx;
+ color: #666;
+ }
+
+ .search-result-zhuanqu {
+ font-size: 22rpx;
+ color: #888;
+ }
+
+ .search-result-price {
+ display: flex;
+ align-items: baseline;
+ }
+
+ .search-item-glow {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: linear-gradient(135deg, transparent, rgba(255, 204, 0, 0.05), transparent);
+ opacity: 0;
+ transition: opacity 0.3s;
+ pointer-events: none;
+ }
+
+ .search-result-item:active .search-item-glow {
+ opacity: 1;
+ }
+
+ .search-empty {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 160rpx 0;
+ }
+
+ .search-empty-icon {
+ width: 160rpx;
+ height: 160rpx;
+ opacity: 0.2;
+ margin-bottom: 36rpx;
+ }
+
+ .search-empty-text {
+ font-size: 30rpx;
+ color: #999;
+ margin-bottom: 16rpx;
+ }
+
+ .search-empty-tip {
+ font-size: 24rpx;
+ color: #bbb;
+ }
+
+ .search-loading {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 160rpx 0;
+ }
+
+ .search-spinner {
+ width: 60rpx;
+ height: 60rpx;
+ position: relative;
+ margin-bottom: 24rpx;
+ }
+
+ .spinner-circle {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ border: 4rpx solid transparent;
+ border-top-color: #ffcc00;
+ border-radius: 50%;
+ animation: spin 1s linear infinite;
+ }
+
+ .search-loading-text {
+ font-size: 26rpx;
+ color: #999;
+ }
+
+ /* 全局加载状态 */
+ .loading-toast {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.7);
+ backdrop-filter: blur(10rpx);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 3000;
+ }
+
+ .loading-content {
+ background: rgba(255, 255, 255, 0.1);
+ backdrop-filter: blur(20rpx);
+ padding: 60rpx 80rpx;
+ border-radius: 24rpx;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ position: relative;
+ border: 1rpx solid rgba(255, 255, 255, 0.1);
+ }
+
+ .loading-spinner {
+ width: 80rpx;
+ height: 80rpx;
+ position: relative;
+ margin-bottom: 30rpx;
+ }
+
+ .spinner-ring {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ border: 4rpx solid rgba(255, 255, 255, 0.2);
+ border-radius: 50%;
+ }
+
+ .spinner-dot {
+ position: absolute;
+ top: 0;
+ left: 50%;
+ width: 12rpx;
+ height: 12rpx;
+ background: #ffcc00;
+ border-radius: 50%;
+ transform: translateX(-50%);
+ animation: spin 1s linear infinite;
+ }
+
+ .loading-text {
+ font-size: 28rpx;
+ color: white;
+ font-weight: 500;
+ }
+
+ .loading-glow {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 200rpx;
+ height: 200rpx;
+ background: radial-gradient(circle, rgba(255, 204, 0, 0.2) 0%, transparent 70%);
+ transform: translate(-50%, -50%);
+ z-index: -1;
+ }
+
+ /* 动画定义 */
+ @keyframes glowPulse {
+ 0%, 100% {
+ opacity: 0.4;
+ transform: scale(1);
+ }
+ 50% {
+ opacity: 0.8;
+ transform: scale(1.05);
+ }
+ }
+
+ @keyframes sparkleFlash {
+ 0%, 100% {
+ opacity: 0;
+ transform: translate(-50%, -50%) scale(0);
+ }
+ 50% {
+ opacity: 1;
+ transform: translate(-50%, -50%) scale(1);
+ filter: blur(0);
+ }
+ }
+
+ @keyframes borderGlow {
+ 0%, 100% {
+ border-color: rgba(255, 204, 0, 0.3);
+ }
+ 50% {
+ border-color: rgba(255, 204, 0, 0.8);
+ }
+ }
+
+ @keyframes indicatorGlow {
+ 0%, 100% {
+ box-shadow: 0 0 20rpx rgba(255, 204, 0, 0.3);
+ }
+ 50% {
+ box-shadow: 0 0 30rpx rgba(255, 204, 0, 0.8);
+ }
+ }
+
+ @keyframes sparkleMove {
+ 0% {
+ transform: translateY(-100%);
+ opacity: 0;
+ }
+ 50% {
+ opacity: 1;
+ }
+ 100% {
+ transform: translateY(400%);
+ opacity: 0;
+ }
+ }
+
+ @keyframes rippleEffect {
+ 0% {
+ width: 0;
+ height: 0;
+ opacity: 0.5;
+ }
+ 100% {
+ width: 200rpx;
+ height: 200rpx;
+ opacity: 0;
+ }
+ }
+
+ @keyframes priceGlow {
+ 0% {
+ opacity: 0;
+ transform: translate(-50%, -50%) scale(0.5);
+ }
+ 50% {
+ opacity: 0.6;
+ transform: translate(-50%, -50%) scale(1);
+ }
+ 100% {
+ opacity: 0;
+ transform: translate(-50%, -50%) scale(1.5);
+ }
+ }
+
+ @keyframes buttonHalo {
+ 0% {
+ width: 0;
+ height: 0;
+ opacity: 0.6;
+ }
+ 100% {
+ width: 200rpx;
+ height: 200rpx;
+ opacity: 0;
+ }
+ }
+
+ @keyframes starSparkle {
+ 0% {
+ opacity: 0;
+ transform: translate(0, 0) scale(0);
+ }
+ 20% {
+ opacity: 1;
+ transform: translate(-40rpx, -30rpx) scale(1);
+ }
+ 40% {
+ opacity: 1;
+ transform: translate(30rpx, -20rpx) scale(1);
+ }
+ 60% {
+ opacity: 1;
+ transform: translate(-20rpx, 40rpx) scale(1);
+ }
+ 80% {
+ opacity: 1;
+ transform: translate(40rpx, 20rpx) scale(1);
+ }
+ 100% {
+ opacity: 0;
+ transform: translate(0, 0) scale(0);
+ }
+ }
+
+ @keyframes spin {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/guanshiduan/guanshiduan.js b/miniprogram/pages/guanshiduan/guanshiduan.js
new file mode 100644
index 0000000..2aba158
--- /dev/null
+++ b/miniprogram/pages/guanshiduan/guanshiduan.js
@@ -0,0 +1,548 @@
+// pages/guanshizhongxin/guanshizhongxin.js
+import request from '../../utils/request.js';
+import popupService from '../../services/popupService.js';
+import { getDefaultInviteCode } from '../../utils/default-invite-code.js';
+import { getGroupImgUrls, GUANSHIDUAN_DEFAULTS, buildOssUrl } from '../../utils/page-assets.js';
+
+Page({
+ data: {
+ // 图片路径对象(逻辑层拼接完整URL)
+ imgUrls: {},
+
+ isGuanshi: false,
+ isLoading: false,
+ inviteCode: '',
+ uid: '',
+ nicheng: '',
+ avatarUrl: '',
+
+ // 管事信息
+ gszhstatus: '',
+ yaoqingzongshu: 0,
+ fenyongzonge: '0.00',
+ fenyongtixian: '0.00',
+ yichongzhiDashou: 0,
+ zhuanhualv: 0,
+
+ // 称号列表
+ chenghaoList: [],
+
+ // 邀请人(组长)缓存
+ inviterCache: null,
+ },
+
+ onLoad(options) {
+ const app = getApp();
+ this._onConfigReady = () => this.initImageUrls();
+ app.on('configReady', this._onConfigReady);
+ this.initImageUrls();
+
+ // 处理扫码 scene
+ if (options.scene) {
+ try {
+ const scene = decodeURIComponent(options.scene);
+ options.inviteCode = scene;
+ } catch (e) {
+ console.error('scene解码失败', e);
+ }
+ }
+
+ this.checkGuanshiStatus();
+
+ const { inviteCode } = options || {};
+ if (inviteCode) {
+ this.setData({ inviteCode });
+ setTimeout(() => {
+ this.handleInviteCodeWithLoginCheck(inviteCode);
+ }, 500);
+ }
+
+ if (!app.globalData.hasShownPopupOnColdStart) {
+ app.globalData.hasShownPopupOnColdStart = true;
+ setTimeout(() => {
+ popupService.checkAndShow(this, 'guanshiduan');
+ }, 300);
+ }
+ },
+
+ onHide() {
+ const popupComp = this.selectComponent('#popupNotice');
+ if (popupComp && popupComp.cleanup) {
+ popupComp.cleanup();
+ }
+ },
+
+ onUnload() {
+ if (this._onConfigReady) {
+ getApp().off('configReady', this._onConfigReady);
+ }
+ },
+
+ initImageUrls() {
+ const urls = getGroupImgUrls('guanshiduan', GUANSHIDUAN_DEFAULTS);
+ const g = getApp().globalData;
+ urls.avatarDefault = buildOssUrl(g.morentouxiang || GUANSHIDUAN_DEFAULTS.avatarDefault);
+ this.setData({ imgUrls: urls });
+ },
+
+ onShow() {
+ this.initImageUrls();
+ const inviterCache = wx.getStorageSync('inviterCache') || null;
+ this.setData({ inviterCache });
+
+ this.registerNotificationComponent();
+
+ if (this.data.isGuanshi) {
+ setTimeout(() => {
+ this.getGuanshiInfo();
+ this.fetchChenghaoList();
+ }, 300);
+ }
+
+ const pages = getCurrentPages();
+ const currentPage = pages[pages.length - 1];
+ const options = currentPage.options || {};
+ if (options.inviteCode && !this.data.isGuanshi) {
+ setTimeout(() => {
+ this.handleInviteCodeWithLoginCheck(options.inviteCode);
+ }, 300);
+ }
+ },
+
+ // ========== 全局通知注册 ==========
+ registerNotificationComponent() {
+ const app = getApp();
+ const comp = this.selectComponent('#global-notification');
+ if (comp && comp.showNotification) {
+ app.globalData.globalNotification = {
+ show: (data) => comp.showNotification(data),
+ hide: () => comp.hideNotification()
+ };
+ }
+ },
+
+ // ========== 称号列表(新接口,区分管事) ==========
+ async fetchChenghaoList() {
+ try {
+ const res = await request({
+ url: '/dengji/chenghao',
+ method: 'POST',
+ data: { role: 'guanshi' }
+ });
+ if (res && res.data.code === 0 && res.data.data) {
+ this.setData({ chenghaoList: res.data.data.chenghao_list || [] });
+ }
+ } catch (e) {
+ console.error('获取称号失败', e);
+ }
+ },
+
+ // ========== 联系组长(新接口) ==========
+ // 管事联系组长 —— 获取组长信息并跳转聊天
+ // 管事联系组长
+async contactInviter() {
+ wx.showLoading({ title: '获取组长信息...', mask: true });
+ try {
+ const res = await request({
+ url: '/yonghu/guanshi_contact',
+ method: 'POST'
+ });
+
+ if (res && res.data.code === 200 && res.data.data) {
+ const inviter = res.data.data;
+ const ossImageUrl = getApp().globalData.ossImageUrl || '';
+ const fullAvatar = inviter.touxiang
+ ? (inviter.touxiang.startsWith('http') ? inviter.touxiang : ossImageUrl + inviter.touxiang)
+ : '';
+
+ // 缓存邀请人信息
+ const cacheData = {
+ uid: inviter.uid,
+ nicheng: inviter.nicheng || '',
+ avatar: fullAvatar
+ };
+ wx.setStorageSync('inviterCache', cacheData);
+ this.setData({ inviterCache: cacheData });
+
+ // 跳转聊天(组长前缀请确认,默认 'Zz')
+ const param = {
+ toUserId: 'Zz' + inviter.uid,
+ toName: inviter.nicheng || '组长',
+ toAvatar: fullAvatar
+ };
+ wx.navigateTo({
+ url: '/pages/liaotian/liaotian?data=' + encodeURIComponent(JSON.stringify(param))
+ });
+ } else {
+ wx.showToast({ title: '未绑定邀请人', icon: 'none' });
+ }
+ } catch (e) {
+ wx.showToast({ title: '网络错误', icon: 'none' });
+ } finally {
+ wx.hideLoading();
+ }
+},
+ // ========== 邀请码处理(完全照搬打手端) ==========
+ handleInviteCodeWithLoginCheck(inviteCode) {
+ const token = wx.getStorageSync('token');
+ const uid = wx.getStorageSync('uid');
+ if (!token || !uid) {
+ this.loginAndRegisterWithInviteCode(inviteCode);
+ } else {
+ if (!this.data.isGuanshi) {
+ wx.showLoading({ title: '自动注册中...', mask: true });
+ this.callRegisterApi(inviteCode);
+ } else {
+ wx.showToast({ title: '您已是管事', icon: 'success' });
+ }
+ }
+ },
+
+ loginAndRegisterWithInviteCode(inviteCode) {
+ const that = this;
+ wx.showLoading({ title: '登录注册中...', mask: true });
+ wx.login({
+ success: (loginRes) => {
+ if (loginRes.code) {
+ that.callLoginRegisterApi(loginRes.code, inviteCode);
+ } else {
+ wx.hideLoading();
+ wx.showToast({ title: '微信登录失败', icon: 'none' });
+ }
+ },
+ fail: () => {
+ wx.hideLoading();
+ wx.reLaunch({ url: '/pages/gerenzhongxin/gerenzhongxin' });
+ }
+ });
+ },
+
+ callLoginRegisterApi(code, inviteCode) {
+ const that = this;
+ const app = getApp();
+ const apiBaseUrl = app.globalData.apiBaseUrl;
+ if (!apiBaseUrl) {
+ wx.hideLoading();
+ wx.showToast({ title: '服务器配置错误', icon: 'none' });
+ return;
+ }
+ wx.request({
+ url: apiBaseUrl + '/yonghu/zuzhangyqmzc',
+ method: 'POST',
+ data: { code: code, inviteCode: inviteCode },
+ header: { 'content-type': 'application/json' },
+ success: (res) => {
+ if (res.statusCode === 200 && res.data && res.data.code === 200 && res.data.data) {
+ const userData = res.data.data;
+ if (userData.token) wx.setStorageSync('token', userData.token);
+ const identityFields = ['nicheng', 'uid', 'touxiang', 'dashoustatus', 'guanshistatus', 'shangjiastatus', 'zuzhangstatus'];
+ identityFields.forEach(field => {
+ if (userData[field] !== undefined) {
+ wx.setStorageSync(field, userData[field]);
+ app.globalData[field] = userData[field];
+ }
+ });
+ if (userData.guanshistatus === undefined) {
+ wx.setStorageSync('guanshistatus', 1);
+ app.globalData.guanshistatus = 1;
+ }
+ const groupFields = ['dashouqun', 'dashouqunid', 'guanshiqun', 'guanshiqunid'];
+ groupFields.forEach(field => {
+ if (userData[field] !== undefined) {
+ wx.setStorageSync(field, userData[field]);
+ app.globalData[field] = userData[field];
+ }
+ });
+ const guanshiData = {
+ gszhstatus: userData.gszhstatus || '',
+ yaoqingzongshu: userData.yaoqingzongshu || 0,
+ fenyongzonge: userData.fenyongzonge || '0.00',
+ fenyongtixian: userData.fenyongtixian || '0.00',
+ yichongzhiDashou: userData.yichongzhiDashou || 0
+ };
+ if (!app.globalData.guanshi) app.globalData.guanshi = {};
+ Object.assign(app.globalData.guanshi, guanshiData);
+ const zhuanhualv = guanshiData.yaoqingzongshu > 0
+ ? (guanshiData.yichongzhiDashou / guanshiData.yaoqingzongshu * 100).toFixed(1)
+ : 0;
+ that.setData({
+ isGuanshi: true,
+ uid: userData.uid || '',
+ nicheng: userData.nicheng || '',
+ ...guanshiData,
+ zhuanhualv,
+ isLoading: false
+ });
+ that.loadUserInfo();
+ that.switchToGuanshiRole();
+ wx.hideLoading();
+ wx.showToast({ title: '注册成功!', icon: 'success', duration: 2000 });
+ } else {
+ wx.hideLoading();
+ wx.showToast({ title: (res.data && res.data.msg) || '注册失败', icon: 'none' });
+ }
+ },
+ fail: () => {
+ wx.hideLoading();
+ wx.showToast({ title: '网络请求失败', icon: 'none' });
+ }
+ });
+ },
+
+ async callRegisterApi(inviteCode) {
+ try {
+ const res = await request({
+ url: '/yonghu/guanshizhuce',
+ method: 'POST',
+ data: { inviteCode }
+ });
+ if (res && res.data.code === 200 && res.data.data) {
+ const userData = res.data.data;
+ const identityFields = ['nicheng', 'uid', 'touxiang', 'dashoustatus', 'guanshistatus', 'shangjiastatus', 'zuzhangstatus'];
+ const app = getApp();
+ identityFields.forEach(field => {
+ if (userData[field] !== undefined) {
+ wx.setStorageSync(field, userData[field]);
+ app.globalData[field] = userData[field];
+ }
+ });
+ if (userData.guanshistatus === undefined) {
+ wx.setStorageSync('guanshistatus', 1);
+ app.globalData.guanshistatus = 1;
+ }
+ const guanshiData = {
+ gszhstatus: userData.gszhstatus || '',
+ yaoqingzongshu: userData.yaoqingzongshu || 0,
+ fenyongzonge: userData.fenyongzonge || '0.00',
+ fenyongtixian: userData.fenyongtixian || '0.00',
+ yichongzhiDashou: userData.yichongzhiDashou || 0
+ };
+ if (!app.globalData.guanshi) app.globalData.guanshi = {};
+ Object.assign(app.globalData.guanshi, guanshiData);
+ const zhuanhualv = guanshiData.yaoqingzongshu > 0
+ ? (guanshiData.yichongzhiDashou / guanshiData.yaoqingzongshu * 100).toFixed(1)
+ : 0;
+ this.setData({
+ isGuanshi: true,
+ uid: userData.uid || '',
+ nicheng: userData.nicheng || '',
+ ...guanshiData,
+ zhuanhualv,
+ isLoading: false
+ });
+ this.loadUserInfo();
+ this.switchToGuanshiRole();
+ wx.hideLoading();
+ wx.showToast({ title: '注册成功!', icon: 'success', duration: 2000 });
+ } else {
+ wx.hideLoading();
+ wx.showToast({ title: res?.data?.msg || '注册失败', icon: 'none' });
+ }
+ } catch (e) {
+ console.error('注册失败:', e);
+ wx.hideLoading();
+ wx.showToast({ title: e.message || '注册失败', icon: 'error' });
+ }
+ },
+
+ switchToGuanshiRole() {
+ const app = getApp();
+ app.globalData.currentRole = 'guanshi';
+ wx.setStorageSync('currentRole', 'guanshi');
+ const tabBar = this.getTabBar();
+ if (tabBar && tabBar.refresh) tabBar.refresh();
+ if (app.connectForCurrentRole) {
+ app.connectForCurrentRole();
+ } else if (app.ensureConnection) {
+ app.ensureConnection();
+ }
+ },
+
+ checkGuanshiStatus() {
+ try {
+ const status = wx.getStorageSync('guanshistatus');
+ if (status === 1) {
+ this.setData({ isGuanshi: true });
+ this.loadUserInfo();
+ this.checkGlobalData();
+ } else {
+ this.setData({ isGuanshi: false });
+ }
+ } catch (e) {
+ this.setData({ isGuanshi: false });
+ }
+ },
+
+ loadUserInfo() {
+ const app = getApp();
+ const globalData = app.globalData || {};
+ const touxiang = wx.getStorageSync('touxiang');
+ const ossImageUrl = globalData.ossImageUrl || '';
+ const morentouxiang = 'avatar/default.jpg';
+ let avatarUrl = '';
+ if (touxiang) {
+ avatarUrl = ossImageUrl + (touxiang.startsWith('/') ? touxiang : '/' + touxiang);
+ } else {
+ avatarUrl = ossImageUrl + (morentouxiang.startsWith('/') ? morentouxiang : '/' + morentouxiang);
+ }
+ this.setData({
+ avatarUrl,
+ uid: wx.getStorageSync('uid') || '',
+ nicheng: globalData.nicheng || ''
+ });
+ },
+
+ checkGlobalData() {
+ const app = getApp();
+ const gd = app.globalData.guanshi || {};
+ const needRefresh = !gd.yaoqingzongshu && !gd.fenyongzonge && !gd.fenyongtixian;
+ if (needRefresh) {
+ this.getGuanshiInfo();
+ } else {
+ this.setDataFromGlobalData();
+ }
+ },
+
+ setDataFromGlobalData() {
+ const gd = getApp().globalData.guanshi || {};
+ const zhuanhualv = gd.yaoqingzongshu > 0
+ ? (gd.yichongzhiDashou / gd.yaoqingzongshu * 100).toFixed(1)
+ : 0;
+ this.setData({
+ gszhstatus: gd.gszhstatus || '',
+ yaoqingzongshu: gd.yaoqingzongshu || 0,
+ fenyongzonge: gd.fenyongzonge || '0.00',
+ fenyongtixian: gd.fenyongtixian || '0.00',
+ yichongzhiDashou: gd.yichongzhiDashou || 0,
+ zhuanhualv,
+ isLoading: false
+ });
+ },
+
+ async getGuanshiInfo() {
+ this.setData({ isLoading: true });
+ try {
+ const res = await request({
+ url: '/yonghu/guanshixinxi',
+ method: 'POST'
+ });
+ if (res && res.data.code === 200) {
+ const data = res.data.data;
+ const app = getApp();
+ if (!app.globalData.guanshi) app.globalData.guanshi = {};
+ const updateData = {
+ gszhstatus: data.gszhstatus || '',
+ yaoqingzongshu: data.yaoqingzongshu || 0,
+ fenyongzonge: data.fenyongzonge || '0.00',
+ fenyongtixian: data.fenyongtixian || '0.00',
+ yichongzhiDashou: data.yichongzhiDashou || 0
+ };
+ Object.assign(app.globalData.guanshi, updateData);
+ const zhuanhualv = updateData.yaoqingzongshu > 0
+ ? (updateData.yichongzhiDashou / updateData.yaoqingzongshu * 100).toFixed(1)
+ : 0;
+ this.setData({
+ ...updateData,
+ zhuanhualv,
+ isLoading: false
+ });
+ wx.showToast({ title: '信息已更新', icon: 'success', duration: 1500 });
+ }
+ } catch (e) {
+ this.setData({ isLoading: false });
+ wx.showToast({ title: e.message || '获取失败', icon: 'error' });
+ }
+ },
+
+ refreshGuanshiInfo() {
+ this.getGuanshiInfo();
+ this.fetchChenghaoList();
+ },
+
+ onInviteCodeInput(e) {
+ this.setData({ inviteCode: e.detail.value.trim() });
+ },
+
+ async onRegister() {
+ const { inviteCode } = this.data;
+ if (!inviteCode) {
+ wx.showToast({ title: '请输入邀请码', icon: 'none' });
+ return;
+ }
+ if (inviteCode.length > 100) {
+ wx.showToast({ title: '邀请码过长', icon: 'none' });
+ return;
+ }
+ this.setData({ isLoading: true });
+ try {
+ const res = await request({
+ url: '/yonghu/guanshizhuce',
+ method: 'POST',
+ data: { inviteCode }
+ });
+ if (res && res.data.code === 200) {
+ const userData = res.data.data;
+ const identityFields = ['dashoustatus', 'guanshistatus', 'shangjiastatus', 'zuzhangstatus'];
+ const app = getApp();
+ identityFields.forEach(field => {
+ if (userData[field] !== undefined) {
+ wx.setStorageSync(field, userData[field]);
+ app.globalData[field] = userData[field];
+ }
+ });
+ if (userData.guanshistatus === undefined) {
+ wx.setStorageSync('guanshistatus', 1);
+ app.globalData.guanshistatus = 1;
+ }
+ if (!app.globalData.guanshi) app.globalData.guanshi = {};
+ const updateData = {
+ gszhstatus: userData.gszhstatus || '',
+ yaoqingzongshu: userData.yaoqingzongshu || 0,
+ fenyongzonge: userData.fenyongzonge || '0.00',
+ fenyongtixian: userData.fenyongtixian || '0.00',
+ yichongzhiDashou: userData.yichongzhiDashou || 0
+ };
+ Object.assign(app.globalData.guanshi, updateData);
+ this.setData({
+ isGuanshi: true,
+ ...updateData,
+ isLoading: false
+ });
+ this.loadUserInfo();
+ wx.showToast({ title: '注册成功!', icon: 'success', duration: 2000 });
+ }
+ } catch (e) {
+ this.setData({ isLoading: false });
+ wx.showToast({ title: e.message || '注册失败', icon: 'error' });
+ }
+ },
+
+ onQuickRegister() {
+ const code = getDefaultInviteCode('guanshi');
+ if (!code) {
+ wx.showToast({ title: '暂不支持无邀请码注册', icon: 'none' });
+ return;
+ }
+ this.setData({ inviteCode: code });
+ this.onRegister();
+ },
+
+ copyUid() {
+ const { uid } = this.data;
+ if (!uid) return;
+ wx.setClipboardData({
+ data: uid,
+ success: () => wx.showToast({ title: '已复制', icon: 'success' })
+ });
+ },
+
+ // ========== 页面跳转 ==========
+ goToWithdraw() { wx.navigateTo({ url: '/pages/tixian/tixian' }); },
+ goToInviteUser() { wx.navigateTo({ url: '/pages/yaoqingdashou/yaoqingdashou' }); },
+ goToMySubordinate() { wx.navigateTo({ url: '/pages/wodedashou/wodedashou' }); },
+ goToRechargeRecord() { wx.navigateTo({ url: '/pages/czjilu/czjilu' }); },
+ goToGuanshiRanking() {
+ wx.navigateTo({ url: '/pages/dashoupaihang/dashoupaihang?type=guanshi' })
+ },
+ goToPromoPoster() { wx.navigateTo({ url: '/pages/haibao/haibao' }); }
+});
\ No newline at end of file
diff --git a/miniprogram/pages/guanshiduan/guanshiduan.json b/miniprogram/pages/guanshiduan/guanshiduan.json
new file mode 100644
index 0000000..9aca2d3
--- /dev/null
+++ b/miniprogram/pages/guanshiduan/guanshiduan.json
@@ -0,0 +1,11 @@
+{
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification",
+ "chenghao-tag": "/components/chenghao-tag/chenghao-tag"
+ },
+ "navigationBarBackgroundColor": "#f0f4f8",
+ "navigationBarTextStyle": "black",
+ "navigationBarTitleText": "管事中心",
+ "backgroundColor": "#f0f4f8",
+ "enablePullDownRefresh": false
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/guanshiduan/guanshiduan.wxml b/miniprogram/pages/guanshiduan/guanshiduan.wxml
new file mode 100644
index 0000000..3ba8dbc
--- /dev/null
+++ b/miniprogram/pages/guanshiduan/guanshiduan.wxml
@@ -0,0 +1,122 @@
+
+
+
+
+
+
+ ✦ 管事认证 ✦
+
+
+ 立即注册
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{nicheng || '管事'}}
+
+ UID: {{uid || '--'}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 可提现分成
+
+ {{fenyongtixian}}
+ 元
+
+
+
+
+ {{yaoqingzongshu}}
+ 邀请总数
+
+
+ {{yichongzhiDashou}}
+ 已充值
+
+
+ {{fenyongzonge}}
+ 分成总额
+
+
+ {{zhuanhualv}}%
+ 有效率
+
+
+
+
+
+ 提现
+
+
+
+
+
+
+
+ 邀请用户
+
+
+
+ 我的下级
+
+
+
+ 会员记录
+
+
+
+ 管事排行
+
+
+
+ 推广海报
+
+
+
+ 联系组长
+
+
+
+
+
+
+
+
+ {{inviterCache.nicheng || '联系组长'}}
+ ID: {{inviterCache.uid}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/guanshiduan/guanshiduan.wxss b/miniprogram/pages/guanshiduan/guanshiduan.wxss
new file mode 100644
index 0000000..3cf8113
--- /dev/null
+++ b/miniprogram/pages/guanshiduan/guanshiduan.wxss
@@ -0,0 +1,367 @@
+/* ========== 全局背景 ========== */
+page {
+ background: #F8FAFE;
+ font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', sans-serif;
+ color: #1A1A1A;
+ }
+
+ /* 背景图清晰可见 */
+ .page-bg {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ opacity: 0.4;
+ z-index: 0;
+ pointer-events: none;
+ }
+
+ /* ========== 未注册区域 ========== */
+ .unreg-area {
+ position: relative;
+ z-index: 2;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-height: 100vh;
+ padding: 0 50rpx;
+ }
+
+ .unreg-card {
+ width: 100%;
+ background: rgba(255, 255, 255, 0.75);
+ backdrop-filter: blur(25rpx);
+ -webkit-backdrop-filter: blur(25rpx);
+ border-radius: 48rpx;
+ padding: 60rpx 50rpx;
+ text-align: center;
+ box-shadow: 0 20rpx 50rpx rgba(0,0,0,0.06);
+ border: 1rpx solid rgba(255,255,255,0.5);
+ }
+
+ .unreg-title {
+ font-size: 40rpx;
+ font-weight: 700;
+ color: #1A1A1A;
+ display: block;
+ margin-bottom: 50rpx;
+ }
+
+ .unreg-input {
+ width: 100%;
+ height: 96rpx;
+ background: #FFFFFF;
+ border-radius: 48rpx;
+ padding: 0 40rpx;
+ font-size: 30rpx;
+ color: #1A1A1A;
+ margin-bottom: 40rpx;
+ box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.03);
+ }
+
+ .ph {
+ color: #999999;
+ }
+
+ .unreg-btn {
+ width: 100%;
+ height: 96rpx;
+ background: rgba(255,255,255,0.6);
+ backdrop-filter: blur(15rpx);
+ border-radius: 48rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border: 2rpx solid rgba(0,0,0,0.08);
+ box-shadow: 0 12rpx 30rpx rgba(0,0,0,0.05);
+ }
+ .unreg-btn:active { opacity: 0.8; }
+ .unreg-btn-text {
+ color: #1A1A1A;
+ font-size: 34rpx;
+ font-weight: 600;
+ }
+
+ /* ========== 已注册主区域 ========== */
+ .main-body {
+ position: relative;
+ z-index: 2;
+ padding: 0 30rpx 40rpx;
+ }
+
+ /* 刷新按钮 */
+ .refresh-btn {
+ position: absolute;
+ top: 40rpx;
+ right: 40rpx;
+ width: 68rpx;
+ height: 68rpx;
+ background: rgba(255,255,255,0.75);
+ backdrop-filter: blur(12rpx);
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 20;
+ border: 1rpx solid rgba(255,255,255,0.6);
+ box-shadow: 0 8rpx 20rpx rgba(0,0,0,0.06);
+ }
+ .refresh-icon {
+ width: 38rpx;
+ height: 38rpx;
+ }
+
+ /* 用户卡片 */
+ .user-card {
+ margin-top: 60rpx;
+ margin-bottom: 30rpx;
+ background: rgba(255,255,255,0.7);
+ backdrop-filter: blur(20rpx);
+ -webkit-backdrop-filter: blur(20rpx);
+ border-radius: 36rpx;
+ padding: 28rpx 30rpx;
+ display: flex;
+ align-items: center;
+ border: 1rpx solid rgba(255,255,255,0.5);
+ box-shadow: 0 12rpx 35rpx rgba(0,0,0,0.05);
+ }
+
+ .user-avatar {
+ width: 100rpx;
+ height: 100rpx;
+ border-radius: 50%;
+ border: 3rpx solid #FFFFFF;
+ margin-right: 24rpx;
+ }
+
+ .user-name {
+ font-size: 38rpx;
+ font-weight: 700;
+ color: #1A1A1A;
+ display: block;
+ margin-bottom: 8rpx;
+ }
+
+ .uid-row {
+ display: inline-flex;
+ align-items: center;
+ background: rgba(0,0,0,0.03);
+ border-radius: 20rpx;
+ padding: 6rpx 18rpx;
+ }
+
+ .uid-text {
+ font-size: 26rpx;
+ color: #1A1A1A;
+ margin-right: 10rpx;
+ }
+
+ .copy-icon {
+ width: 28rpx;
+ height: 28rpx;
+ }
+
+ /* 称号区域 */
+ .badge-zone {
+ margin-bottom: 24rpx;
+ }
+ .badge-scroll {
+ white-space: nowrap;
+ }
+
+ /* 资产卡片(纯玻璃质感) */
+ .asset-card {
+ background: rgba(255,255,255,0.7);
+ backdrop-filter: blur(20rpx);
+ -webkit-backdrop-filter: blur(20rpx);
+ border-radius: 36rpx;
+ padding: 28rpx 30rpx;
+ margin-bottom: 28rpx;
+ border: 1rpx solid rgba(255,255,255,0.5);
+ box-shadow: 0 12rpx 35rpx rgba(0,0,0,0.05);
+ }
+
+ .asset-title {
+ font-size: 26rpx;
+ color: #1A1A1A;
+ font-weight: 500;
+ display: block;
+ }
+
+ .asset-amount {
+ display: flex;
+ align-items: baseline;
+ margin: 10rpx 0 22rpx;
+ }
+
+ .amount-number {
+ font-size: 74rpx;
+ font-weight: 800;
+ color: #1A1A1A;
+ margin-right: 8rpx;
+ }
+
+ .amount-unit {
+ font-size: 32rpx;
+ color: #1A1A1A;
+ font-weight: 600;
+ }
+
+ /* 四列数据统计 */
+ .four-stats {
+ display: flex;
+ align-items: center;
+ justify-content: space-around;
+ padding: 20rpx 0;
+ border-top: 1rpx solid rgba(0,0,0,0.06);
+ border-bottom: 1rpx solid rgba(0,0,0,0.06);
+ margin-bottom: 22rpx;
+ }
+
+ .stat-item {
+ text-align: center;
+ flex: 1;
+ }
+
+ .stat-val {
+ font-size: 28rpx;
+ font-weight: 700;
+ color: #1A1A1A;
+ display: block;
+ margin-bottom: 6rpx;
+ }
+
+ .stat-label {
+ font-size: 20rpx;
+ color: #1A1A1A;
+ font-weight: 500;
+ }
+
+ /* 提现按钮 */
+ .withdraw-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: rgba(255,255,255,0.55);
+ backdrop-filter: blur(12rpx);
+ border-radius: 48rpx;
+ padding: 16rpx 0;
+ border: 2rpx solid rgba(0,0,0,0.06);
+ box-shadow: 0 8rpx 20rpx rgba(0,0,0,0.04);
+ }
+
+ .withdraw-icon {
+ width: 36rpx;
+ height: 36rpx;
+ margin-right: 12rpx;
+ }
+
+ .withdraw-text {
+ font-size: 30rpx;
+ font-weight: 600;
+ color: #1A1A1A;
+ }
+
+ /* 功能入口三列布局 */
+ .func-grid {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ margin-bottom: 28rpx;
+ }
+
+ .func-item {
+ width: 31%;
+ aspect-ratio: 1 / 1;
+ background: rgba(255,255,255,0.7);
+ backdrop-filter: blur(16rpx);
+ -webkit-backdrop-filter: blur(16rpx);
+ border-radius: 28rpx;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ margin-bottom: 20rpx;
+ border: 1rpx solid rgba(255,255,255,0.5);
+ box-shadow: 0 8rpx 22rpx rgba(0,0,0,0.04);
+ }
+
+ .func-icon {
+ width: 56rpx;
+ height: 56rpx;
+ margin-bottom: 12rpx;
+ }
+
+ .func-label {
+ font-size: 26rpx;
+ font-weight: 500;
+ color: #1A1A1A;
+ text-align: center;
+ }
+
+ /* 联系组长卡片(底部横条) */
+ .contact-card {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ background: rgba(255,255,255,0.7);
+ backdrop-filter: blur(20rpx);
+ -webkit-backdrop-filter: blur(20rpx);
+ border-radius: 36rpx;
+ padding: 20rpx 30rpx;
+ margin-bottom: 40rpx;
+ border: 1rpx solid rgba(255,255,255,0.5);
+ box-shadow: 0 10rpx 25rpx rgba(0,0,0,0.04);
+ }
+
+ .contact-left {
+ display: flex;
+ align-items: center;
+ flex: 1;
+ }
+
+ .contact-avatar {
+ width: 60rpx;
+ height: 60rpx;
+ border-radius: 50%;
+ margin-right: 18rpx;
+ border: 2rpx solid #FFFFFF;
+ }
+
+ .contact-name {
+ font-size: 28rpx;
+ font-weight: 500;
+ color: #1A1A1A;
+ }
+
+ .contact-uid {
+ font-size: 22rpx;
+ color: #1A1A1A;
+ margin-top: 4rpx;
+ }
+
+ .contact-arrow {
+ width: 24rpx;
+ height: 24rpx;
+ }
+
+ /* 加载遮罩 */
+ .loading-mask {
+ position: fixed;
+ top:0; left:0; right:0; bottom:0;
+ background: rgba(255,255,255,0.85);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 9999;
+ }
+ .loading-spinner {
+ width: 60rpx;
+ height: 60rpx;
+ border: 4rpx solid rgba(0,0,0,0.1);
+ border-top-color: #1A1A1A;
+ border-radius: 50%;
+ animation: spin 0.8s linear infinite;
+ }
+ @keyframes spin { to { transform: rotate(360deg); } }
\ No newline at end of file
diff --git a/miniprogram/pages/guanshipaihang/guanshipaihang.json b/miniprogram/pages/guanshipaihang/guanshipaihang.json
new file mode 100644
index 0000000..f6237dd
--- /dev/null
+++ b/miniprogram/pages/guanshipaihang/guanshipaihang.json
@@ -0,0 +1,12 @@
+{
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification"
+ },
+ "navigationBarTitleText": "管事排行榜",
+ "navigationBarBackgroundColor": "#000000",
+ "navigationBarTextStyle": "white",
+ "enablePullDownRefresh": false,
+ "backgroundColor": "#000000",
+ "backgroundTextStyle": "dark",
+ "onReachBottomDistance": 50
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/guanshipaihang/guanshipaihang.wxml b/miniprogram/pages/guanshipaihang/guanshipaihang.wxml
new file mode 100644
index 0000000..8a45da9
--- /dev/null
+++ b/miniprogram/pages/guanshipaihang/guanshipaihang.wxml
@@ -0,0 +1,2 @@
+
+正在打开排行榜...
diff --git a/miniprogram/pages/guanshipaihang/guanshipaihang.wxss b/miniprogram/pages/guanshipaihang/guanshipaihang.wxss
new file mode 100644
index 0000000..0ac9ca5
--- /dev/null
+++ b/miniprogram/pages/guanshipaihang/guanshipaihang.wxss
@@ -0,0 +1,5 @@
+.redirect-hint {
+ padding: 80rpx;
+ text-align: center;
+ color: #999;
+}
diff --git a/miniprogram/pages/guanzhual/guanzhual.js b/miniprogram/pages/guanzhual/guanzhual.js
new file mode 100644
index 0000000..ea8dbfd
--- /dev/null
+++ b/miniprogram/pages/guanzhual/guanzhual.js
@@ -0,0 +1,90 @@
+// pages/guanzhual/guanzhual.js
+const app = getApp()
+import request from '../../utils/request.js'
+
+Page({
+ data: {
+ images: [], // 图片完整URL列表
+ lastUpdate: '', // 最新更新时间
+ visitCount: 0, // 浏览次数
+ loading: true, // 加载中
+ ossImageUrl: '' // OSS基础地址
+ },
+
+ onLoad() {
+ // 获取OSS地址
+ this.setData({
+ ossImageUrl: app.globalData.ossImageUrl || ''
+ })
+ this.loadData()
+ },
+
+ onShow() {
+ this.registerNotificationComponent()
+ },
+
+ // 注册全局通知组件
+ registerNotificationComponent() {
+ const notificationComp = this.selectComponent('#global-notification')
+ if (notificationComp && notificationComp.showNotification) {
+ app.globalData.globalNotification = {
+ show: (data) => notificationComp.showNotification(data),
+ hide: () => notificationComp.hideNotification()
+ }
+ }
+ },
+
+ // 加载数据
+ loadData() {
+ this.setData({ loading: true })
+
+ request({
+ url: '/peizhi/gzal',
+ method: 'POST',
+ header: { 'content-type': 'application/json' }
+ })
+ .then(res => {
+ if (res && res.data && res.data.code === 0) {
+ const data = res.data.data || {}
+ const ossImageUrl = this.data.ossImageUrl
+ // 拼接完整URL
+ const fullImages = (data.images || []).map(url => {
+ if (url && !url.startsWith('http')) {
+ return ossImageUrl + url
+ }
+ return url
+ })
+ this.setData({
+ images: fullImages,
+ lastUpdate: data.last_update || '',
+ visitCount: data.visit_count || 0,
+ loading: false
+ })
+ } else {
+ wx.showToast({
+ title: res?.data?.msg || '加载失败',
+ icon: 'none'
+ })
+ this.setData({ loading: false })
+ }
+ })
+ .catch(err => {
+ console.error('加载关注阿龙数据失败', err)
+ wx.showToast({
+ title: '网络错误',
+ icon: 'none'
+ })
+ this.setData({ loading: false })
+ })
+ },
+
+ // 图片加载成功(可选)
+ onImageLoad(e) {
+ // 可忽略
+ },
+
+ // 图片加载失败
+ onImageError(e) {
+ console.warn('图片加载失败', e)
+ }
+})
\ No newline at end of file
diff --git a/miniprogram/pages/guanzhual/guanzhual.json b/miniprogram/pages/guanzhual/guanzhual.json
new file mode 100644
index 0000000..ac80b22
--- /dev/null
+++ b/miniprogram/pages/guanzhual/guanzhual.json
@@ -0,0 +1,8 @@
+{
+ "navigationBarTitleText": "关注二H",
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification"
+ },
+ "enablePullDownRefresh": false,
+ "backgroundTextStyle": "dark"
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/guanzhual/guanzhual.wxml b/miniprogram/pages/guanzhual/guanzhual.wxml
new file mode 100644
index 0000000..5394ae8
--- /dev/null
+++ b/miniprogram/pages/guanzhual/guanzhual.wxml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ 暂无内容
+
+
+
+
+
+
+ 加载中...
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/guanzhual/guanzhual.wxss b/miniprogram/pages/guanzhual/guanzhual.wxss
new file mode 100644
index 0000000..675b458
--- /dev/null
+++ b/miniprogram/pages/guanzhual/guanzhual.wxss
@@ -0,0 +1,95 @@
+/* pages/guanzhual/guanzhual.wxss */
+page {
+ background-color: #ffffff;
+ min-height: 100vh;
+ }
+
+ .guanzhual-page {
+ padding-bottom: 30rpx;
+ box-sizing: border-box;
+ }
+
+ /* 头部信息 */
+ .header-info {
+ padding: 30rpx 20rpx 20rpx 20rpx;
+ border-bottom: 2rpx solid #f5f5f5;
+ }
+
+ .title-section {
+ display: flex;
+ justify-content: space-between;
+ align-items: baseline;
+ margin-bottom: 12rpx;
+ }
+
+ .main-title {
+ font-size: 40rpx;
+ font-weight: 600;
+ color: #333333;
+ }
+
+ .visit-count {
+ font-size: 28rpx;
+ color: #999999;
+ }
+
+ .sub-info {
+ display: flex;
+ justify-content: space-between;
+ font-size: 26rpx;
+ color: #aaaaaa;
+ }
+
+ .update-time {
+ color: #888888;
+ }
+
+ /* 图片列表 */
+ .image-list {
+ padding: 0 8rpx; /* 非常微小的左右边距 */
+ }
+
+ .image-item {
+ width: 100%;
+ margin-bottom: 6rpx; /* 极小的上下间距 */
+ background-color: #fafafa;
+ overflow: hidden;
+ border-radius: 4rpx;
+ }
+
+ .image-item image {
+ width: 100%;
+ display: block;
+ }
+
+ /* 空状态提示 */
+ .empty-tip {
+ padding: 100rpx 0;
+ text-align: center;
+ color: #999999;
+ font-size: 28rpx;
+ }
+
+ /* 加载状态 */
+ .loading-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 100rpx 0;
+ }
+
+ .loading-spinner {
+ width: 60rpx;
+ height: 60rpx;
+ border: 4rpx solid #e0e0e0;
+ border-top-color: #00C853;
+ border-radius: 50%;
+ animation: spin 1s linear infinite;
+ margin-bottom: 20rpx;
+ }
+
+ @keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/haibao/haibao.js b/miniprogram/pages/haibao/haibao.js
new file mode 100644
index 0000000..3985d30
--- /dev/null
+++ b/miniprogram/pages/haibao/haibao.js
@@ -0,0 +1,281 @@
+// pages/haibao/haibao.js
+import request from '../../utils/request.js';
+
+const app = getApp();
+
+Page({
+ data: {
+ qrcodeUrl: '',
+ isLoading: false,
+ showPosterCanvas: false,
+ posterImageTemp: '',
+ avatarUrl: '',
+ uid: '',
+ nickName: '',
+ },
+
+ onLoad() {
+ this.loadQRCodeFromCache();
+ this.loadUserInfo();
+ },
+
+ onShow() {
+ this.registerNotificationComponent();
+ },
+
+ registerNotificationComponent() {
+ const notificationComp = this.selectComponent('#global-notification');
+ if (notificationComp && notificationComp.showNotification) {
+ app.globalData.globalNotification = {
+ show: (data) => notificationComp.showNotification(data),
+ hide: () => notificationComp.hideNotification()
+ };
+ }
+ },
+
+ loadQRCodeFromCache() {
+ try {
+ const relativeUrl = wx.getStorageSync('haibao_url');
+ if (relativeUrl) {
+ const fullUrl = this.getFullImageUrl(relativeUrl);
+ this.setData({ qrcodeUrl: fullUrl });
+ } else {
+ this.setData({ qrcodeUrl: '' });
+ }
+ } catch (error) {
+ console.error('读取缓存失败', error);
+ }
+ },
+
+ loadUserInfo() {
+ const touxiang = wx.getStorageSync('touxiang');
+ const ossUrl = app.globalData.ossImageUrl || '';
+ const defaultAvatar = ossUrl + (app.globalData.morentouxiang || 'avatar/default.jpg');
+ let avatarUrl = defaultAvatar;
+ if (touxiang && typeof touxiang === 'string' && touxiang.trim() !== '') {
+ avatarUrl = this.getFullImageUrl(touxiang);
+ }
+ const uid = wx.getStorageSync('uid') || '';
+ let nickName = app.globalData.nicheng || wx.getStorageSync('nicheng') || '管事';
+ this.setData({ avatarUrl, uid, nickName });
+ },
+
+ getFullImageUrl(url) {
+ if (!url) return '';
+ const ossUrl = app.globalData.ossImageUrl || '';
+ return url.startsWith('http') ? url : ossUrl + url;
+ },
+
+ refreshQRCode() {
+ if (this.data.isLoading) return;
+ this.setData({ isLoading: true });
+ request({
+ url: '/peizhi/guanshiewm',
+ method: 'POST',
+ }).then(res => {
+ if (res.data && res.data.code === 0) {
+ const relativeUrl = res.data.data.url;
+ if (relativeUrl) {
+ wx.setStorageSync('haibao_url', relativeUrl);
+ const fullUrl = this.getFullImageUrl(relativeUrl);
+ this.setData({ qrcodeUrl: fullUrl });
+ wx.showToast({ title: '获取成功', icon: 'success' });
+ } else {
+ wx.showToast({ title: '返回数据异常', icon: 'none' });
+ }
+ } else {
+ wx.showToast({ title: res.data.msg || '获取失败', icon: 'none' });
+ }
+ }).catch(err => {
+ console.error('获取海报失败', err);
+ wx.showToast({ title: '网络错误', icon: 'none' });
+ }).finally(() => {
+ this.setData({ isLoading: false });
+ });
+ },
+
+ // 🔥【修改】保存二维码到相册(先下载网络图片)
+ saveToAlbum() {
+ if (!this.data.qrcodeUrl) {
+ wx.showToast({ title: '暂无二维码', icon: 'none' });
+ return;
+ }
+
+ wx.showLoading({ title: '保存中...', mask: true });
+ wx.downloadFile({
+ url: this.data.qrcodeUrl,
+ success: (res) => {
+ wx.hideLoading();
+ if (res.statusCode === 200) {
+ // 调用已有的保存函数(传入临时路径)
+ this.downloadAndSave(res.tempFilePath);
+ } else {
+ wx.showToast({ title: '下载失败', icon: 'none' });
+ }
+ },
+ fail: (err) => {
+ wx.hideLoading();
+ console.error('下载二维码失败', err);
+ wx.showToast({ title: '下载失败', icon: 'none' });
+ }
+ });
+ },
+
+ generateMyPoster() {
+ if (!this.data.qrcodeUrl) {
+ wx.showToast({ title: '请先生成二维码', icon: 'none' });
+ return;
+ }
+
+ wx.showLoading({ title: '生成海报中...', mask: true });
+
+ const ossUrl = app.globalData.ossImageUrl || '';
+ const bgUrl = ossUrl + 'beijing/haibaobeijing.jpg';
+ const urls = [bgUrl, this.data.qrcodeUrl, this.data.avatarUrl];
+
+ const promises = urls.map(url => this.loadImage(url));
+ Promise.all(promises)
+ .then((images) => {
+ wx.hideLoading();
+ this.setData({ showPosterCanvas: true }, () => {
+ setTimeout(() => {
+ this.drawPoster(images[0], images[1], images[2]);
+ }, 200);
+ });
+ })
+ .catch((err) => {
+ wx.hideLoading();
+ wx.showToast({ title: '图片加载失败', icon: 'none' });
+ console.error('图片加载失败', err);
+ });
+ },
+
+ loadImage(url) {
+ return new Promise((resolve, reject) => {
+ wx.getImageInfo({
+ src: url,
+ success: (res) => resolve(res.path),
+ fail: reject
+ });
+ });
+ },
+
+ drawPoster(bgPath, qrPath, avatarPath) {
+ const ctx = wx.createCanvasContext('posterCanvas', this);
+ const query = wx.createSelectorQuery().in(this);
+ query.select('.poster-canvas').boundingClientRect(rect => {
+ if (!rect) {
+ wx.showToast({ title: '画布获取失败', icon: 'none' });
+ return;
+ }
+ const canvasWidth = rect.width;
+ const canvasHeight = rect.height;
+
+ // 1. 绘制背景
+ ctx.drawImage(bgPath, 0, 0, canvasWidth, canvasHeight);
+
+ const qrSize = 100;
+ const margin = 5;
+ const qrX = canvasWidth - qrSize - margin;
+ const qrY = canvasHeight - qrSize - margin;
+ ctx.drawImage(qrPath, qrX, qrY, qrSize, qrSize);
+
+ const avatarSize = 60;
+ const avatarX = margin + 20;
+ const avatarY = canvasHeight - avatarSize - margin;
+ ctx.save();
+ ctx.beginPath();
+ ctx.arc(avatarX + avatarSize/2, avatarY + avatarSize/2, avatarSize/2, 0, 2 * Math.PI);
+ ctx.clip();
+ ctx.drawImage(avatarPath, avatarX, avatarY, avatarSize, avatarSize);
+ ctx.restore();
+
+ ctx.setFontSize(16);
+ ctx.setFillStyle('#333333');
+ ctx.setTextAlign('left');
+
+ let nick = this.data.nickName || '管事';
+ if (nick.length > 3) {
+ nick = nick.substring(0, 3) + '...';
+ }
+ const nameText = nick;
+
+ const textX = avatarX + avatarSize + 8;
+ const textY = avatarY + avatarSize/2 + 6;
+ ctx.fillText(nameText, textX, textY);
+
+ ctx.setFontSize(18);
+ ctx.setFillStyle('#ffaa00');
+ ctx.setTextAlign('left');
+ const slogan = '快来加入我们 ✦';
+ const sloganX = avatarX;
+ const sloganY = avatarY - 12;
+ ctx.fillText(slogan, sloganX, sloganY);
+
+ ctx.draw(false, () => {
+ wx.canvasToTempFilePath({
+ canvasId: 'posterCanvas',
+ success: (res) => {
+ this.setData({ posterImageTemp: res.tempFilePath });
+ },
+ fail: (err) => {
+ console.error('生成海报临时文件失败', err);
+ }
+ }, this);
+ });
+ }).exec();
+ },
+
+ savePoster() {
+ if (!this.data.posterImageTemp) {
+ wx.showToast({ title: '海报尚未生成', icon: 'none' });
+ return;
+ }
+ this.downloadAndSave(this.data.posterImageTemp);
+ },
+
+ hidePoster() {
+ this.setData({ showPosterCanvas: false, posterImageTemp: '' });
+ },
+
+ downloadAndSave(filePath) {
+ wx.getSetting({
+ success: (res) => {
+ if (!res.authSetting['scope.writePhotosAlbum']) {
+ wx.authorize({
+ scope: 'scope.writePhotosAlbum',
+ success: () => {
+ this._saveImage(filePath);
+ },
+ fail: () => {
+ wx.showModal({
+ title: '提示',
+ content: '需要您授权保存到相册',
+ success: (modalRes) => {
+ if (modalRes.confirm) {
+ wx.openSetting();
+ }
+ }
+ });
+ }
+ });
+ } else {
+ this._saveImage(filePath);
+ }
+ }
+ });
+ },
+
+ _saveImage(filePath) {
+ wx.saveImageToPhotosAlbum({
+ filePath: filePath,
+ success: () => {
+ wx.showToast({ title: '保存成功', icon: 'success' });
+ },
+ fail: (err) => {
+ console.error('保存失败', err);
+ wx.showToast({ title: '保存失败', icon: 'none' });
+ }
+ });
+ },
+});
\ No newline at end of file
diff --git a/miniprogram/pages/haibao/haibao.json b/miniprogram/pages/haibao/haibao.json
new file mode 100644
index 0000000..8f6fba7
--- /dev/null
+++ b/miniprogram/pages/haibao/haibao.json
@@ -0,0 +1,8 @@
+{
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification"
+ },
+ "navigationBarTitleText": "推广海报",
+ "navigationBarBackgroundColor": "#0a0e17",
+ "navigationBarTextStyle": "white"
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/haibao/haibao.wxml b/miniprogram/pages/haibao/haibao.wxml
new file mode 100644
index 0000000..aa2c48d
--- /dev/null
+++ b/miniprogram/pages/haibao/haibao.wxml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+ ⚡ 推广海报 ⚡
+ 邀请打手扫码注册,自动填充邀请码
+
+
+
+
+
+
+
+
+
+ ⚡
+ 点击下方按钮生成您的专属二维码
+
+
+
+
+
+
+
+ 📥
+ 保存二维码
+
+
+ ⟳
+ {{qrcodeUrl ? '重新获取' : '获取二维码'}}
+
+
+
+ 生成中...
+
+
+
+ 🎨
+ 生成我的专属海报
+
+
+
+
+
+
+
+
+
+ 📥
+ 保存海报
+
+
+ ↩️
+ 返回
+
+
+
+
+
+
+ * 打手扫码后自动进入注册页面,邀请码将自动填充
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/haibao/haibao.wxss b/miniprogram/pages/haibao/haibao.wxss
new file mode 100644
index 0000000..e52db1f
--- /dev/null
+++ b/miniprogram/pages/haibao/haibao.wxss
@@ -0,0 +1,269 @@
+/* 机甲风格推广海报页面(含新增专属海报样式) */
+
+page {
+ background: #0a0e17;
+ min-height: 100vh;
+ font-family: 'Avenir', 'PingFang SC', 'Helvetica Neue', sans-serif;
+ color: #fff;
+ }
+
+ /* 背景网格 */
+ .grid-bg {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-image:
+ linear-gradient(rgba(0, 160, 255, 0.05) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(0, 160, 255, 0.05) 1px, transparent 1px);
+ background-size: 50rpx 50rpx;
+ pointer-events: none;
+ z-index: 0;
+ }
+
+ .page-container {
+ position: relative;
+ z-index: 2;
+ min-height: 100vh;
+ padding: 30rpx 25rpx;
+ box-sizing: border-box;
+ }
+
+ /* 机械装饰线条 */
+ .mech-line {
+ position: absolute;
+ pointer-events: none;
+ z-index: 1;
+ }
+ .line-h {
+ top: 200rpx;
+ left: 0;
+ width: 100%;
+ height: 2rpx;
+ background: linear-gradient(90deg, transparent, #00a6ff, transparent);
+ }
+ .line-v {
+ right: 50rpx;
+ top: 100rpx;
+ width: 2rpx;
+ height: 300rpx;
+ background: linear-gradient(180deg, transparent, #00a6ff, transparent);
+ }
+
+ .content {
+ position: relative;
+ z-index: 3;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ min-height: 100vh;
+ padding: 40rpx 0;
+ }
+
+ /* 标题区域 */
+ .title-section {
+ text-align: center;
+ margin-bottom: 60rpx;
+ }
+ .title {
+ font-size: 44rpx;
+ font-weight: bold;
+ color: #fff;
+ text-shadow: 0 0 30rpx #00a6ff;
+ letter-spacing: 2rpx;
+ display: block;
+ margin-bottom: 20rpx;
+ }
+ .subtitle {
+ font-size: 28rpx;
+ color: #9cf;
+ opacity: 0.9;
+ letter-spacing: 1rpx;
+ }
+
+ /* 二维码区域(方形,无斜切) */
+ .qrcode-section {
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-bottom: 60rpx;
+ }
+ .qrcode-wrapper {
+ position: relative;
+ width: 400rpx;
+ height: 400rpx;
+ background: rgba(0, 20, 40, 0.8);
+ border: 4rpx solid #00a6ff;
+ /* 移除 clip-path,保持方形 */
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-shadow: 0 0 80rpx rgba(0, 166, 255, 0.4);
+ animation: qrcodePulse 2s infinite alternate;
+ }
+ .qrcode-img {
+ width: 90%;
+ height: 90%;
+ object-fit: contain;
+ }
+ .qrcode-glow {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: radial-gradient(circle at 30% 30%, rgba(0, 166, 255, 0.2), transparent 70%);
+ pointer-events: none;
+ }
+ .empty-qrcode {
+ width: 400rpx;
+ height: 400rpx;
+ background: rgba(0, 20, 40, 0.8);
+ border: 4rpx dashed #00a6ff;
+ /* 方形 */
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ }
+ .empty-icon {
+ font-size: 80rpx;
+ color: #00a6ff;
+ margin-bottom: 20rpx;
+ opacity: 0.5;
+ }
+ .empty-text {
+ font-size: 26rpx;
+ color: #9cf;
+ text-align: center;
+ padding: 0 40rpx;
+ }
+
+ /* 按钮区域(机甲斜切风格) */
+ .button-section {
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ margin-bottom: 40rpx;
+ }
+ .btn-group {
+ display: flex;
+ gap: 20rpx;
+ flex-wrap: wrap;
+ justify-content: center;
+ }
+ .btn {
+ width: 220rpx;
+ height: 80rpx;
+ background: rgba(0, 30, 60, 0.8);
+ border: 2rpx solid #00a6ff;
+ border-right: none;
+ border-bottom: none;
+ clip-path: polygon(0 0, 100% 0, 90% 100%, 0 100%);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ transition: 0.2s;
+ cursor: pointer;
+ }
+ .btn:active {
+ transform: scale(0.96);
+ box-shadow: 0 0 40rpx #00a6ff;
+ }
+ .save-btn {
+ border-color: #00a6ff;
+ }
+ .refresh-btn {
+ border-color: #ffaa00;
+ }
+ .refresh-btn .btn-icon {
+ color: #ffaa00;
+ }
+ /* 🔥【新增】生成海报按钮样式 */
+ .poster-btn {
+ border-color: #ffaa00;
+ }
+ .poster-btn .btn-icon {
+ color: #ffaa00;
+ }
+ .loading-btn {
+ opacity: 0.7;
+ pointer-events: none;
+ }
+ .btn-icon {
+ font-size: 36rpx;
+ margin-right: 8rpx;
+ }
+ .btn-text {
+ font-size: 26rpx;
+ font-weight: 500;
+ color: #fff;
+ }
+ .loading-spinner-small {
+ width: 30rpx;
+ height: 30rpx;
+ border: 4rpx solid rgba(255,255,255,0.2);
+ border-top: 4rpx solid #fff;
+ border-radius: 50%;
+ animation: spin 1s linear infinite;
+ margin-right: 10rpx;
+ }
+
+ /* 🔥【新增】专属海报展示区 */
+ .poster-section {
+ margin-top: 40rpx;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ width: 100%;
+ }
+ .poster-canvas {
+ width: 600rpx;
+ height: 900rpx;
+ background: #1a1f2e; /* 占位色 */
+ border: 4rpx solid #00a6ff;
+ box-shadow: 0 0 60rpx #00a6ff;
+ /* 保持方形,无斜切 */
+ }
+ .poster-actions {
+ display: flex;
+ gap: 30rpx;
+ margin-top: 30rpx;
+ justify-content: center;
+ }
+ .save-poster-btn {
+ background: linear-gradient(45deg, #0066cc, #00a6ff);
+ border-color: #00a6ff;
+ }
+ .back-btn {
+ background: rgba(0, 30, 60, 0.8);
+ border-color: #00a6ff;
+ }
+
+ /* 使用说明(保留) */
+ .instruction {
+ margin-top: 30rpx;
+ padding: 20rpx 40rpx;
+ background: rgba(0, 30, 50, 0.5);
+ border-left: 4rpx solid #00a6ff;
+ clip-path: polygon(0 0, 100% 0, 98% 100%, 0 100%);
+ }
+ .instruction-text {
+ font-size: 24rpx;
+ color: #9cf;
+ line-height: 1.6;
+ }
+
+ /* 动画 */
+ @keyframes qrcodePulse {
+ 0% { box-shadow: 0 0 40rpx rgba(0, 166, 255, 0.3); }
+ 100% { box-shadow: 0 0 100rpx rgba(0, 166, 255, 0.6); }
+ }
+ @keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/index/index.js b/miniprogram/pages/index/index.js
new file mode 100644
index 0000000..d89bade
--- /dev/null
+++ b/miniprogram/pages/index/index.js
@@ -0,0 +1,494 @@
+// pages/shangpin/shangpin.js
+const app = getApp()
+
+Page({
+ /**
+ * 页面的初始数据
+ */
+ data: {
+ // 全局变量引用
+ ossImageUrl: '',
+ lunbozhanwei: '',
+
+ // 页面数据
+ shangpingonggao: '',
+ lunboList: [],
+ shangpinleixing: [],
+ shangpinzhuanqu: [],
+ shangpinliebiao: [],
+
+ // 状态控制
+ selectedLeixingId: null,
+ filteredData: [],
+ showGonggaoModal: false,
+ gonggaoAnim: true,
+ isLoading: false,
+ isRefreshing: false,
+ hasInitialized: false,
+
+ // 🔴【新增】当前时间(用于弹窗显示)
+ currentTime: ''
+ },
+
+ /**
+ * 生命周期函数--监听页面加载
+ */
+ onLoad(options) {
+ // 🔥 等待全局配置加载完成
+ this.waitForConfigAndInit();
+ // 🆕 修改:仅在已登录时检查弹窗,避免未登录时发起请求导致 401 弹窗
+ const token = wx.getStorageSync('token');
+ if (token) {
+ PopupService.checkAndShow(this, 'index');
+ }
+ },
+ // 🆕 页面隐藏时清理弹窗视图
+ onHide: function () {
+ const popupComp = this.selectComponent('#popupNotice');
+ if (popupComp && popupComp.cleanup) {
+ popupComp.cleanup();
+ }
+},
+
+
+ /**
+ * 🔥 新增:等待配置加载完成再初始化
+ */
+ waitForConfigAndInit() {
+ if (app.globalData.ossImageUrl) {
+ // 配置已就绪,直接初始化
+ this.setData({
+ ossImageUrl: app.globalData.ossImageUrl,
+ lunbozhanwei: app.globalData.lunbozhanwei,
+ currentTime: this.getCurrentTime()
+ });
+ this.initPageData();
+ } else {
+ // 配置未就绪,延迟重试(最多等待3秒,每100ms检查一次)
+
+ setTimeout(() => this.waitForConfigAndInit(), 100);
+ }
+ },
+
+ /**
+ * 生命周期函数--监听页面显示
+ */
+ onShow() {
+ // 如果已经初始化过,只更新时间;否则等待配置
+ if (this.data.ossImageUrl) {
+ this.setData({
+ gonggaoAnim: true,
+ currentTime: this.getCurrentTime()
+ });
+ }
+ this.registerNotificationComponent();
+ },
+
+ // 注册通知组件
+ 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()
+ };
+ }
+ },
+
+ /**
+ * 🔴【新增】获取当前时间(格式化)
+ */
+ getCurrentTime() {
+ const now = new Date();
+ const year = now.getFullYear();
+ const month = (now.getMonth() + 1).toString().padStart(2, '0');
+ const day = now.getDate().toString().padStart(2, '0');
+ const hour = now.getHours().toString().padStart(2, '0');
+ const minute = now.getMinutes().toString().padStart(2, '0');
+ return `${year}-${month}-${day} ${hour}:${minute}`;
+ },
+
+ /**
+ * 🔴【新增】按paixu字段排序函数
+ * 规则:1. paixu大的在前 2. paixu相同时id小的在前 3. 没有paixu的按id排序
+ */
+ sortByPaixu(list) {
+ if (!list || !Array.isArray(list)) return []
+
+ return [...list].sort((a, b) => {
+ // 获取paixu值,如果没有则默认为0
+ const paixuA = a.paixu || 0
+ const paixuB = b.paixu || 0
+
+ // 1. 先按paixu降序(大的在前)
+ if (paixuB !== paixuA) {
+ return paixuB - paixuA
+ }
+
+ // 2. paixu相同时,按id升序(小的在前)
+ return a.id - b.id
+ })
+ },
+
+ /**
+ * 初始化页面数据
+ */
+ initPageData() {
+ const that = this
+ this.setData({ isLoading: true })
+
+ const needLoad = []
+
+ // 1. 检查公告和轮播图
+ if (!app.globalData.shangpingonggao || app.globalData.shangpinlunbo.length === 0) {
+ needLoad.push(this.loadGonggaoAndLunbo())
+ } else {
+ this.setData({
+ shangpingonggao: app.globalData.shangpingonggao,
+ lunboList: this.processImageUrls(app.globalData.shangpinlunbo)
+ })
+ }
+
+ // 2. 检查商品数据
+ if (app.globalData.shangpinleixing.length === 0) {
+ needLoad.push(this.loadAllShangpinData())
+ } else {
+ const leixingList = app.globalData.shangpinleixing
+
+ // 🔴【修改】对商品类型进行排序
+ const sortedLeixing = this.sortByPaixu(leixingList)
+
+ this.setData({
+ shangpinleixing: leixingList,
+ shangpinzhuanqu: app.globalData.shangpinzhuanqu || [],
+ shangpinliebiao: app.globalData.shangpinliebiao || [],
+ selectedLeixingId: sortedLeixing[0]?.id || null
+ }, () => {
+ this.filterShangpinData()
+ })
+ }
+
+ if (needLoad.length > 0) {
+ Promise.all(needLoad)
+ .then(() => {
+ that.setData({ isLoading: false })
+ })
+ .catch(error => {
+ console.error('数据加载失败:', error)
+ that.setData({ isLoading: false })
+ wx.showToast({
+ title: '加载失败,请重试',
+ icon: 'none'
+ })
+ })
+ } else {
+ this.setData({ isLoading: false })
+ }
+ },
+
+ /**
+ * 加载公告和轮播图数据
+ */
+ loadGonggaoAndLunbo() {
+ const that = this
+ return new Promise((resolve, reject) => {
+ wx.request({
+ url: app.globalData.apiBaseUrl + '/peizhi/shangpingonggao/',
+ method: 'POST',
+ header: {
+ 'content-type': 'application/json'
+ },
+ success(res) {
+ if (res.statusCode === 200 && res.data) {
+ const data = res.data
+
+ app.globalData.shangpingonggao = data.shangpingonggao || ''
+ app.globalData.shangpinlunbo = data.shangpinlunbo || []
+
+ that.setData({
+ shangpingonggao: app.globalData.shangpingonggao,
+ lunboList: that.processImageUrls(app.globalData.shangpinlunbo)
+ })
+
+ resolve()
+ } else {
+ reject(new Error('请求失败'))
+ }
+ },
+ fail(error) {
+ console.error('加载公告失败:', error)
+ reject(error)
+ }
+ })
+ })
+ },
+
+ /**
+ * 加载所有商品数据
+ */
+ loadAllShangpinData() {
+ const that = this
+ return new Promise((resolve, reject) => {
+ wx.request({
+ url: app.globalData.apiBaseUrl + '/shangpin/shangpinhuoqu/',
+ method: 'POST',
+ header: {
+ 'content-type': 'application/json'
+ },
+ success(res) {
+ if (res.statusCode === 200 && res.data) {
+ const data = res.data
+
+ // 🔴【修改】对返回的数据进行排序
+ const sortedLeixing = that.sortByPaixu(data.shangpinleixing || [])
+ const sortedZhuanqu = that.sortByPaixu(data.shangpinzhuanqu || [])
+ const sortedShangpin = that.sortByPaixu(data.shangpinliebiao || [])
+
+ app.globalData.shangpinleixing = sortedLeixing
+ app.globalData.shangpinzhuanqu = sortedZhuanqu
+ app.globalData.shangpinliebiao = sortedShangpin
+
+ that.setData({
+ shangpinleixing: sortedLeixing,
+ shangpinzhuanqu: sortedZhuanqu,
+ shangpinliebiao: sortedShangpin,
+ selectedLeixingId: sortedLeixing[0]?.id || null
+ }, () => {
+ that.filterShangpinData()
+ resolve()
+ })
+ } else {
+ reject(new Error('请求失败'))
+ }
+ },
+ fail(error) {
+ console.error('加载商品数据失败:', error)
+ reject(error)
+ }
+ })
+ })
+ },
+
+ /**
+ * 处理图片URL(拼接OSS域名)
+ */
+ processImageUrls(urlList) {
+ if (!urlList || !Array.isArray(urlList)) return []
+
+ return urlList.map(url => {
+ if (url.startsWith('http')) {
+ return url
+ } else {
+ return this.data.ossImageUrl + url
+ }
+ })
+ },
+
+ /**
+ * 筛选商品数据
+ */
+ filterShangpinData() {
+ const { selectedLeixingId, shangpinzhuanqu, shangpinliebiao } = this.data
+
+ if (!selectedLeixingId || !shangpinzhuanqu || !shangpinliebiao) {
+ this.setData({ filteredData: [] })
+ return
+ }
+
+ // 1. 构建专区Map(按类型分组,但只处理当前选中的类型)
+ const zhuanquByLeixing = new Map()
+
+ shangpinzhuanqu.forEach(zhuanqu => {
+ if (zhuanqu.leixing_id === selectedLeixingId) {
+ if (!zhuanquByLeixing.has(selectedLeixingId)) {
+ zhuanquByLeixing.set(selectedLeixingId, [])
+ }
+ zhuanquByLeixing.get(selectedLeixingId).push(zhuanqu)
+ }
+ })
+
+ // 2. 构建商品Map(按专区分组)
+ const shangpinByZhuanqu = new Map()
+
+ shangpinliebiao.forEach(shangpin => {
+ if (shangpin.leixing_id === selectedLeixingId && shangpin.zhuanqu_id) {
+ const zhuanquId = shangpin.zhuanqu_id
+
+ if (!shangpinByZhuanqu.has(zhuanquId)) {
+ shangpinByZhuanqu.set(zhuanquId, [])
+ }
+
+ // 处理价格显示
+ const jiage = parseFloat(shangpin.jiage) || 0
+ const priceInteger = Math.floor(jiage)
+ let priceDecimal = '00'
+
+ const decimalPart = (jiage - priceInteger).toFixed(2)
+ if (decimalPart > 0) {
+ priceDecimal = decimalPart.toString().split('.')[1]
+ }
+
+ shangpinByZhuanqu.get(zhuanquId).push({
+ id: shangpin.id,
+ biaoqian: shangpin.biaoqian,
+ jiage: jiage,
+ priceInteger: priceInteger,
+ priceDecimal: priceDecimal,
+ tupian_url: shangpin.tupian_url,
+ duiwai_xiaoliang: shangpin.duiwai_xiaoliang || 0,
+ paixu: shangpin.paixu || 0 // 🔴 保留排序字段
+ })
+ }
+ })
+
+ // 3. 构建最终数据结构
+ const filteredData = []
+ const currentZhuanquList = zhuanquByLeixing.get(selectedLeixingId) || []
+
+ currentZhuanquList.forEach(zhuanqu => {
+ const shangpinList = shangpinByZhuanqu.get(zhuanqu.id) || []
+
+ if (shangpinList.length > 0) {
+ filteredData.push({
+ zhuanqu: {
+ id: zhuanqu.id,
+ mingzi: zhuanqu.mingzi,
+ paixu: zhuanqu.paixu || 0 // 🔴 保留排序字段
+ },
+ shangpinList: shangpinList
+ })
+ }
+ })
+
+ // 🔴【修改】对最终数据进行排序(确保专区顺序)
+ const sortedFilteredData = filteredData.sort((a, b) => {
+ // 按专区paixu降序
+ const paixuA = a.zhuanqu.paixu || 0
+ const paixuB = b.zhuanqu.paixu || 0
+ if (paixuB !== paixuA) return paixuB - paixuA
+ // paixu相同时按专区id升序
+ return a.zhuanqu.id - b.zhuanqu.id
+ })
+
+ this.setData({ filteredData: sortedFilteredData })
+ },
+
+ /**
+ * 选择商品类型
+ */
+ selectLeixing(e) {
+ const leixingId = e.currentTarget.dataset.id
+
+ if (this.data.selectedLeixingId === leixingId) {
+ return
+ }
+
+ this.setData({
+ selectedLeixingId: leixingId
+ }, () => {
+ this.filterShangpinData()
+ })
+ },
+
+ /**
+ * 显示公告详情
+ */
+ showGonggaoDetail() {
+ this.setData({
+ showGonggaoModal: true,
+ gonggaoAnim: false,
+ currentTime: this.getCurrentTime() // 🔴 更新弹窗时间
+ })
+ },
+
+ /**
+ * 隐藏公告详情
+ */
+ hideGonggaoDetail() {
+ this.setData({
+ showGonggaoModal: false,
+ gonggaoAnim: true
+ })
+ },
+
+ /**
+ * 刷新所有数据
+ */
+ refreshAllData() {
+ const that = this
+ this.setData({ isRefreshing: true })
+
+ wx.showLoading({
+ title: '刷新中...',
+ mask: true
+ })
+
+ // 清空全局变量
+ app.globalData.shangpingonggao = ''
+ app.globalData.shangpinlunbo = []
+ app.globalData.shangpinleixing = []
+ app.globalData.shangpinzhuanqu = []
+ app.globalData.shangpinliebiao = []
+
+ Promise.all([
+ that.loadGonggaoAndLunbo(),
+ that.loadAllShangpinData()
+ ])
+ .then(() => {
+ wx.hideLoading()
+ that.setData({ isRefreshing: false })
+ wx.showToast({
+ title: '刷新成功',
+ icon: 'success',
+ duration: 1500
+ })
+ })
+ .catch(error => {
+ console.error('刷新失败:', error)
+ wx.hideLoading()
+ that.setData({ isRefreshing: false })
+ wx.showToast({
+ title: '刷新失败',
+ icon: 'none'
+ })
+ })
+ },
+
+ /**
+ * 跳转到商品详情页
+ */
+ goToDetail(e) {
+ const shangpinId = e.currentTarget.dataset.id
+ if (!shangpinId) return
+
+ wx.navigateTo({
+ url: `/pages/shangpinxiangqing/shangpinxiangqing?id=${shangpinId}`
+ })
+ },
+
+ /**
+ * 页面上拉触底事件的处理函数
+ */
+ onReachBottom() {
+ // 可以在这里实现分页加载
+ },
+
+ /**
+ * 用户点击右上角分享
+ */
+ onShareAppMessage() {
+ return {
+ title: '阿龙电竞',
+ path: '/pages/shangpin/shangpin'
+ }
+ },
+
+ /**
+ * 页面卸载时清理
+ */
+ onUnload() {
+ this.setData({ gonggaoAnim: false })
+ }
+})
\ No newline at end of file
diff --git a/miniprogram/pages/index/index.json b/miniprogram/pages/index/index.json
new file mode 100644
index 0000000..06f55f5
--- /dev/null
+++ b/miniprogram/pages/index/index.json
@@ -0,0 +1,12 @@
+{
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification",
+ "custom-tab-bar": "/custom-tab-bar/index"
+ },
+
+ "navigationBarTitleText": "商城",
+ "navigationBarBackgroundColor": "#ffffff",
+ "navigationBarTextStyle": "black",
+
+ "backgroundColor": "#f5faff"
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/index/index.wxml b/miniprogram/pages/index/index.wxml
new file mode 100644
index 0000000..d5fff6b
--- /dev/null
+++ b/miniprogram/pages/index/index.wxml
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+
+
+
+ {{shangpingonggao || '暂无公告'}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{item.jieshao}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{item.zhuanqu.mingzi}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{item.biaoqian}}
+
+
+
+
+
+
+
+
+
+
+
+
+ 暂无商品
+
+
+
+
+
+
+
+
+
+
+
+ {{shangpingonggao || '暂无公告内容'}}
+
+
+
+
+
+
+
+ 加载中...
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/index/index.wxss b/miniprogram/pages/index/index.wxss
new file mode 100644
index 0000000..beb4936
--- /dev/null
+++ b/miniprogram/pages/index/index.wxss
@@ -0,0 +1,585 @@
+/* ===== 1. 全局样式 ===== */
+.shangpin-page {
+ min-height: 100vh;
+ background: linear-gradient(180deg, #f5faff 0%, #f0f8ff 100%);
+ }
+
+/* ===== 2. 公告栏样式 - 物理上移方案 ===== */
+.gonggao-box {
+ margin: 20rpx 30rpx;
+ height: 80rpx;
+ border-radius: 40rpx;
+ background: linear-gradient(135deg,
+ rgba(220, 255, 220, 0.85) 0%,
+ rgba(255, 255, 200, 0.85) 100%);
+ backdrop-filter: blur(10px);
+ display: flex;
+ align-items: center;
+ padding: 0 25rpx;
+ box-shadow: 0 4rpx 15rpx rgba(0, 100, 0, 0.08);
+ border: 1rpx solid rgba(255, 255, 255, 0.3);
+ position: relative;
+ overflow: hidden;
+ }
+
+ .gonggao-icon {
+ width: 40rpx;
+ height: 40rpx;
+ margin-right: 20rpx;
+ flex-shrink: 0;
+ display: flex;
+ align-items: center;
+ }
+
+ .gonggao-icon image {
+ width: 100%;
+ height: 100%;
+ }
+
+ .gonggao-content {
+ flex: 1;
+ overflow: hidden;
+ height: 100%;
+ position: relative;
+ }
+
+ /* 关键修改:使用物理上移 */
+ .gonggao-text {
+ font-size: 26rpx;
+ color: #333;
+ white-space: nowrap;
+ padding-right: 50rpx;
+
+ /* 物理上移方法1:直接调整位置 */
+ position: absolute;
+ top: -5rpx; /* 从顶部开始计算位置 */
+ left: 0;
+
+ /* 或者使用方法2:负margin上移 */
+ /* position: relative;
+ top: -10rpx; */
+
+ /* 或者使用方法3:translate上移 */
+ /* transform: translateY(-5rpx); */
+ }
+
+ /* 尝试不同的上移值,直到居中为止 */
+ /* 可以尝试的值:5rpx, 8rpx, 10rpx, 12rpx, 15rpx, 18rpx, 20rpx */
+
+ .gonggao-anim {
+ animation: marquee 10s linear infinite; /* 加快速度到10秒 */
+ animation-delay: 0.5s;
+ }
+
+ @keyframes marquee {
+ 0% {
+ transform: translateX(100%);
+ }
+ 100% {
+ transform: translateX(-100%);
+ }
+ }
+
+ /* ===== 3. 轮播图样式 ===== */
+ .lunbo-container {
+ margin: 0 30rpx 30rpx;
+ height: 350rpx;
+ border-radius: 25rpx;
+ overflow: hidden;
+ position: relative;
+ box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.1);
+ }
+
+ .lunbo-swiper {
+ width: 100%;
+ height: 100%;
+ }
+
+ .lunbo-image {
+ width: 100%;
+ height: 100%;
+ }
+
+ .lunbo-placeholder {
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ }
+
+ .lunbo-placeholder image {
+ width: 100%;
+ height: 100%;
+ opacity: 0.8;
+ }
+
+ /* 刷新按钮 */
+ .refresh-btn {
+ position: absolute;
+ top: 20rpx;
+ right: 20rpx;
+ width: 80rpx;
+ height: 80rpx;
+ z-index: 10;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ }
+
+ .refresh-icon {
+ width: 60rpx;
+ height: 60rpx;
+ transition: transform 0.3s;
+ filter: drop-shadow(0 4rpx 8rpx rgba(0, 0, 0, 0.2));
+ }
+
+ .refreshing {
+ animation: rotate360 1s linear infinite;
+ }
+
+ @keyframes rotate360 {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+ }
+
+ .refresh-dot {
+ position: absolute;
+ width: 12rpx;
+ height: 12rpx;
+ background: #ffcc00;
+ border-radius: 50%;
+ top: 15rpx;
+ right: 15rpx;
+ animation: pulse 2s infinite;
+ }
+
+ @keyframes pulse {
+ 0%, 100% {
+ opacity: 0.8;
+ transform: scale(1);
+ }
+ 50% {
+ opacity: 1;
+ transform: scale(1.2);
+ }
+ }
+
+ /* ===== 4. 商品展示区域 ===== */
+ .shangpin-container {
+ background: rgba(255, 255, 255, 0.95);
+ border-radius: 40rpx 40rpx 0 0;
+ margin-top: -20rpx;
+ padding: 40rpx 30rpx 100rpx;
+ min-height: 800rpx;
+ box-shadow: 0 -10rpx 30rpx rgba(0, 0, 0, 0.05);
+ }
+
+ /* 商品类型选择 - 完全重新设计,确保左对齐且间距协调 */
+ .leixing-scroll {
+ width: 100%;
+ white-space: nowrap;
+ margin-bottom: 50rpx;
+ }
+
+ .leixing-list {
+ display: inline-flex;
+ padding-left: 0; /* 完全去掉左内边距,100%左对齐 */
+ padding-right: 30rpx; /* 只保留右内边距给最后一个元素一点空间 */
+ padding-top: 10rpx;
+ padding-bottom: 10rpx;
+ }
+
+ /* 关键:完全协调的间距 */
+ .leixing-item {
+ display: inline-flex;
+ flex-direction: column;
+ align-items: center;
+ margin-right: 30rpx; /* 合适的间距,不会太大也不会太小 */
+ transition: all 0.3s;
+ position: relative;
+ flex-shrink: 0;
+ width: 130rpx; /* 固定宽度,确保四个看起来协调 */
+ }
+
+ .leixing-item:last-child {
+ margin-right: 0;
+ }
+
+ .leixing-image-box {
+ width: 110rpx; /* 稍微减小,让整体更紧凑 */
+ height: 110rpx;
+ border-radius: 25rpx;
+ overflow: hidden;
+ position: relative;
+ margin-bottom: 12rpx;
+ transition: all 0.3s;
+ box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.1);
+ }
+
+ .leixing-image {
+ width: 100%;
+ height: 100%;
+ }
+
+ .leixing-glow {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ border-radius: 25rpx;
+ border: 4rpx solid transparent;
+ transition: all 0.3s;
+ }
+
+ .leixing-active .leixing-glow {
+ border-color: #ffcc00;
+ box-shadow: 0 0 20rpx rgba(255, 204, 0, 0.5);
+ }
+
+ .leixing-active .leixing-image-box {
+ transform: translateY(-8rpx);
+ }
+
+ .leixing-text {
+ font-size: 24rpx;
+ color: #666;
+ white-space: nowrap;
+ transition: all 0.3s;
+ text-align: center;
+ width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .leixing-active .leixing-text {
+ color: #333;
+ font-weight: bold;
+ transform: scale(1.05);
+ }
+
+ /* 专区标题 */
+ .zhuanqu-title {
+ display: flex;
+ align-items: center;
+ margin: 40rpx 0 15rpx;
+ padding-left: 0;
+ position: relative;
+ }
+
+ .zhuanqu-icon {
+ width: 40rpx;
+ height: 40rpx;
+ margin-right: 20rpx;
+ position: relative;
+ }
+
+ .zhuanqu-icon-image {
+ width: 100%;
+ height: 100%;
+ filter: drop-shadow(0 2rpx 4rpx rgba(255, 204, 0, 0.3));
+ }
+
+ .zhuanqu-sparkle {
+ animation: sparkle 2s infinite;
+ }
+
+ @keyframes sparkle {
+ 0%, 100% {
+ transform: scale(1) rotate(0deg);
+ filter: drop-shadow(0 2rpx 4rpx rgba(255, 204, 0, 0.3));
+ }
+ 50% {
+ transform: scale(1.1) rotate(10deg);
+ filter: drop-shadow(0 4rpx 8rpx rgba(255, 204, 0, 0.6));
+ }
+ }
+
+ .zhuanqu-text {
+ font-size: 32rpx;
+ font-weight: bold;
+ color: #333;
+ position: relative;
+ }
+
+ .zhuanqu-text::after {
+ content: '';
+ position: absolute;
+ left: 0;
+ bottom: -8rpx;
+ width: 60rpx;
+ height: 4rpx;
+ background: linear-gradient(90deg, #ffcc00, transparent);
+ border-radius: 2rpx;
+ }
+
+ /* 商品网格 */
+ .shangpin-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 25rpx;
+ margin-bottom: 40rpx;
+ }
+
+ .shangpin-card {
+ background: #ffffff;
+ border-radius: 20rpx;
+ overflow: hidden;
+ box-shadow: 0 8rpx 25rpx rgba(0, 0, 0, 0.06);
+ transition: all 0.3s;
+ position: relative;
+ }
+
+ .shangpin-card:active {
+ transform: scale(0.98);
+ box-shadow: 0 4rpx 15rpx rgba(0, 0, 0, 0.1);
+ }
+
+ .shangpin-image-box {
+ width: 100%;
+ height: 220rpx;
+ position: relative;
+ overflow: hidden;
+ }
+
+ .shangpin-image {
+ width: 100%;
+ height: 100%;
+ transition: transform 0.5s;
+ }
+
+ /* 商品标题区域 - 协调紧凑 */
+ .shangpin-title {
+ height: 75rpx;
+ padding: 6rpx 12rpx 2rpx 12rpx;
+ display: flex;
+ align-items: flex-start;
+ overflow: hidden;
+ }
+
+ .title-text {
+ font-size: 26rpx;
+ color: #000;
+ line-height: 34rpx;
+ font-weight: 450; /* 微调粗细,更加协调 */
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ /* 价格和购买按钮 - 协调紧凑 */
+ .shangpin-footer {
+ height: 50rpx;
+ padding: 0 12rpx 8rpx 12rpx;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ }
+
+ .price-box {
+ display: flex;
+ align-items: baseline;
+ }
+
+ .price-icon {
+ font-size: 24rpx;
+ color: #ff4444;
+ font-weight: bold;
+ margin-right: 2rpx;
+ }
+
+ .price-integer {
+ font-size: 32rpx;
+ color: #ff4444;
+ font-weight: bold;
+ }
+
+ .price-decimal {
+ font-size: 24rpx;
+ color: #ff4444;
+ font-weight: bold;
+ }
+
+ .buy-btn {
+ width: 48rpx;
+ height: 48rpx;
+ border-radius: 50%;
+ background: linear-gradient(135deg, #ff4444, #ff6666);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ box-shadow: 0 4rpx 12rpx rgba(255, 68, 68, 0.3);
+ transition: all 0.3s;
+ }
+
+ .buy-btn:active {
+ transform: scale(0.9);
+ }
+
+ .buy-icon {
+ width: 26rpx;
+ height: 26rpx;
+ filter: brightness(0) invert(1);
+ }
+
+ /* ===== 5. 空状态 ===== */
+ .empty-state {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 100rpx 0;
+ }
+
+ .empty-image {
+ width: 200rpx;
+ height: 200rpx;
+ margin-bottom: 40rpx;
+ opacity: 0.7;
+ }
+
+ .empty-text {
+ font-size: 30rpx;
+ color: #999;
+ margin-bottom: 40rpx;
+ }
+
+ .empty-btn {
+ background: linear-gradient(135deg, #667eea, #764ba2);
+ color: white;
+ font-size: 28rpx;
+ padding: 20rpx 60rpx;
+ border-radius: 50rpx;
+ box-shadow: 0 8rpx 25rpx rgba(102, 126, 234, 0.3);
+ }
+
+ /* ===== 6. 弹窗样式 ===== */
+ .gonggao-modal {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 1000;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ }
+
+ .modal-mask {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.5);
+ backdrop-filter: blur(5px);
+ }
+
+ .modal-content {
+ width: 600rpx;
+ background: white;
+ border-radius: 30rpx;
+ overflow: hidden;
+ box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.2);
+ z-index: 1001;
+ animation: modalShow 0.3s;
+ }
+
+ @keyframes modalShow {
+ from {
+ opacity: 0;
+ transform: scale(0.9);
+ }
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+ }
+
+ .modal-header {
+ padding: 40rpx;
+ border-bottom: 2rpx solid #f0f0f0;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ }
+
+ .modal-title {
+ font-size: 32rpx;
+ font-weight: bold;
+ color: #333;
+ }
+
+ .modal-close {
+ font-size: 40rpx;
+ color: #999;
+ width: 60rpx;
+ height: 60rpx;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border-radius: 50%;
+ }
+
+ .modal-close:active {
+ background: #f5f5f5;
+ }
+
+ .modal-body {
+ max-height: 600rpx;
+ padding: 40rpx;
+ }
+
+ .modal-text {
+ font-size: 28rpx;
+ color: #333;
+ line-height: 1.6;
+ white-space: normal;
+ word-break: break-word;
+ }
+
+ /* ===== 7. 加载提示 ===== */
+ .loading-toast {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background: rgba(0, 0, 0, 0.7);
+ backdrop-filter: blur(10px);
+ padding: 30rpx 50rpx;
+ border-radius: 25rpx;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ z-index: 999;
+ }
+
+ .loading-spinner {
+ width: 60rpx;
+ height: 60rpx;
+ border: 6rpx solid rgba(255, 255, 255, 0.3);
+ border-top-color: white;
+ border-radius: 50%;
+ animation: spin 1s linear infinite;
+ margin-bottom: 20rpx;
+ }
+
+ @keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+ }
+
+ .loading-text {
+ font-size: 26rpx;
+ color: white;
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/jiedan/jiedan.js b/miniprogram/pages/jiedan/jiedan.js
new file mode 100644
index 0000000..f0e4cbb
--- /dev/null
+++ b/miniprogram/pages/jiedan/jiedan.js
@@ -0,0 +1,528 @@
+// pages/qiangdan/qiangdan.js
+const app = getApp();
+import request from '../../utils/request.js';
+import PopupService from '../../services/popupService.js';
+
+Page({
+ data: {
+ // 1. 商品类型相关
+ shangpinleixing: [],
+ xuanzhongLeixingId: null,
+
+ // 2. 订单列表与分页相关
+ dingdanList: [],
+ page: 1,
+ pageSize: 5,
+ hasMore: true,
+ isLoading: false,
+
+ // 3. 全局状态字段
+ dashoustatus: null,
+ zhuanghaoStatus: null,
+ dashouzhuangtai: null,
+ uid: null,
+ huiyuanList: [],
+ yajin: 0,
+ jifen: 0,
+
+ // 4. 刷新控制字段
+ scrollViewRefreshing: false,
+ lastRefreshTime: 0,
+ refreshCooldown: 2000,
+ isLoadingMore: false,
+
+ // 5. 切换类型冷却
+ lastSwitchTime: 0,
+ switchCooldown: 1500,
+ isSwitching: false,
+
+ // 6. OSS地址
+ ossImageUrl: app.globalData.ossImageUrl || '',
+
+ // 🆕 7. 标签筛选相关
+ bankuaiBiaoqian: [], // 当前商品类型所属板块的全部标签
+ xuanzhongBiaoqianId: 0, // 0=全部
+ },
+
+ async onLoad(options) {
+ this.loadGlobalStatus();
+ await this.loadShangpinLeixing();
+ PopupService.checkAndShow(this, 'jiedan');
+ },
+
+ onHide: function () {
+ const popupComp = this.selectComponent('#popupNotice');
+ if (popupComp && popupComp.cleanup) {
+ popupComp.cleanup();
+ }
+ },
+
+ onShow() {
+ this.registerNotificationComponent();
+ // 重新加载全局状态
+ this.loadGlobalStatus();
+ // 如果有选中类型则刷新订单
+ if (this.data.xuanzhongLeixingId) {
+ this.loadDingdanList(true);
+ }
+ },
+
+ registerNotificationComponent() {
+ const notificationComp = this.selectComponent('#global-notification');
+ if (notificationComp && notificationComp.showNotification) {
+ app.globalData.globalNotification = {
+ show: (data) => notificationComp.showNotification(data),
+ hide: () => notificationComp.hideNotification()
+ };
+ }
+ },
+
+ loadGlobalStatus() {
+ const globalData = app.globalData;
+ this.setData({
+ dashoustatus: wx.getStorageSync('dashoustatus'),
+ zhuanghaoStatus: globalData.zhanghaoStatus,
+ dashouzhuangtai: globalData.dashouzhuangtai,
+ uid: wx.getStorageSync('uid'),
+ huiyuanList: globalData.clumber || [],
+ yajin: globalData.yajin || 0,
+ jifen: globalData.jinfen || 0
+ });
+ },
+
+ // 加载商品类型
+ async loadShangpinLeixing() {
+ wx.showLoading({ title: '加载商品类型...' });
+ try {
+ const res = await request({
+ url: '/dingdan/dsqdhqddlx',
+ method: 'POST',
+ header: { 'content-type': 'application/json' }
+ });
+ if (res.statusCode === 200 && res.data) {
+ const data = res.data;
+ let list = [];
+ if (data.code === 200 || data.code === 0) {
+ if (data.data && Array.isArray(data.data.list)) list = data.data.list;
+ else if (data.data && Array.isArray(data.data)) list = data.data;
+ else if (Array.isArray(data.list)) list = data.list;
+ else if (Array.isArray(data)) list = data;
+ }
+ const processedList = this.processTupianUrl(list);
+ this.setData({
+ shangpinleixing: processedList,
+ xuanzhongLeixingId: processedList[0]?.id || null
+ });
+ if (this.data.xuanzhongLeixingId) {
+ this.loadDingdanList(true);
+ // 🆕 加载板块标签
+ this.loadBankuaiBiaoqian();
+ }
+ } else {
+ wx.showToast({ title: res.data?.msg || '加载商品类型失败', icon: 'none' });
+ }
+ } catch (error) {
+ wx.showToast({ title: '网络错误,加载失败', icon: 'none' });
+ } finally {
+ wx.hideLoading();
+ }
+ },
+
+ // 处理图片URL
+ processTupianUrl(list) {
+ const ossUrl = app.globalData.ossImageUrl || '';
+ return list.map(item => ({
+ ...item,
+ full_tupian_url: item.tupian_url && !item.tupian_url.startsWith('http')
+ ? ossUrl + item.tupian_url
+ : (item.tupian_url || '/images/default-type.png')
+ }));
+ },
+
+ // 🆕 加载板块标签
+ async loadBankuaiBiaoqian() {
+ const leixingId = this.data.xuanzhongLeixingId;
+ if (!leixingId) return;
+ try {
+ const res = await request({
+ url: '/dengji/bkqhbq',
+ method: 'POST',
+ data: { leixing_id: leixingId }
+ });
+ if (res && res.data.code === 0) {
+ this.setData({
+ bankuaiBiaoqian: res.data.data.biaoqian_list || [],
+ xuanzhongBiaoqianId: 0
+ });
+ }
+ } catch (e) {
+ // 标签加载失败不影响主流程
+ }
+ },
+
+ // 选择商品类型
+ async selectLeixing(e) {
+ const now = Date.now();
+ const lastSwitchTime = this.data.lastSwitchTime;
+ const switchCooldown = this.data.switchCooldown;
+
+ if (now - lastSwitchTime < switchCooldown) {
+ wx.showToast({
+ title: `操作太快,请${Math.ceil((switchCooldown - (now - lastSwitchTime)) / 1000)}秒后再试`,
+ icon: 'none',
+ duration: 1500
+ });
+ return;
+ }
+ if (this.data.isSwitching) return;
+
+ const leixingId = e.currentTarget.dataset.id;
+ if (leixingId === this.data.xuanzhongLeixingId) return;
+
+ this.setData({ isSwitching: true, lastSwitchTime: now });
+ wx.showLoading({ title: '切换中...' });
+
+ try {
+ this.setData({
+ xuanzhongLeixingId: leixingId,
+ dingdanList: [],
+ page: 1,
+ hasMore: true,
+ xuanzhongBiaoqianId: 0 // 重置标签筛选
+ });
+ await this.loadDingdanList(true);
+ // 🆕 切换类型后加载新标签
+ await this.loadBankuaiBiaoqian();
+ } catch (error) {
+ console.error('切换类型失败:', error);
+ } finally {
+ this.setData({ isSwitching: false });
+ wx.hideLoading();
+ }
+ },
+
+ // 🆕 选择标签筛选
+ async selectBiaoqian(e) {
+ const id = e.currentTarget.dataset.id;
+ if (id === this.data.xuanzhongBiaoqianId) return;
+ this.setData({
+ xuanzhongBiaoqianId: id,
+ dingdanList: [],
+ page: 1,
+ hasMore: true
+ });
+ await this.loadDingdanList(true);
+ },
+
+ // 加载订单列表
+ async loadDingdanList(isRefresh = false) {
+ if (this.data.isLoading || !this.data.xuanzhongLeixingId) return;
+
+ const loadPage = isRefresh ? 1 : this.data.page;
+ if (!isRefresh && !this.data.hasMore) return;
+
+ this.setData({
+ isLoading: true,
+ isLoadingMore: !isRefresh
+ });
+
+ try {
+ const res = await request({
+ url: '/dingdan/ddhq',
+ method: 'POST',
+ data: {
+ leixing_id: this.data.xuanzhongLeixingId,
+ page: loadPage,
+ page_size: this.data.pageSize,
+ // 🆕 传递标签筛选参数(0时不传)
+ biaoqian_id: this.data.xuanzhongBiaoqianId || undefined
+ }
+ });
+
+ this.setData({
+ isLoading: false,
+ isLoadingMore: false,
+ scrollViewRefreshing: false
+ });
+
+ if (res.data.code === 200 || res.data.code === 0) {
+ const newList = res.data.data.list || [];
+ const hasMore = res.data.data.has_more || false;
+
+ const processedList = newList.map(item => this.processDingdanItem(item));
+ const updatedList = isRefresh ? processedList : [...this.data.dingdanList, ...processedList];
+ updatedList.sort((a, b) => (b.creat_time || '').localeCompare(a.creat_time || ''));
+
+ this.setData({
+ dingdanList: updatedList,
+ page: loadPage,
+ hasMore: hasMore
+ });
+
+ if (isRefresh) {
+ this.setData({ lastRefreshTime: Date.now() });
+ }
+ } else {
+ wx.showToast({ title: res.data.msg || '加载失败', icon: 'none' });
+ }
+ } catch (error) {
+ console.error('加载订单失败:', error);
+ this.setData({
+ isLoading: false,
+ isLoadingMore: false,
+ scrollViewRefreshing: false
+ });
+ wx.showToast({ title: '网络请求失败', icon: 'none' });
+ }
+ },
+
+ // 处理单条订单数据(包含标签)
+ processDingdanItem(item) {
+ const ossUrl = app.globalData.ossImageUrl || '';
+ let fullTupianUrl = '';
+
+ if (item.tupian) {
+ fullTupianUrl = !item.tupian.startsWith('http') ? ossUrl + item.tupian : item.tupian;
+ } else {
+ const leixing = this.data.shangpinleixing.find(l => l.id === item.leixing_id);
+ fullTupianUrl = leixing ? leixing.full_tupian_url : '/images/default-order.png';
+ }
+
+ const isZhiding = item.zhuangtai === 7;
+
+ let zhidingAvatar = '';
+ if (item.zhiding_avatar) {
+ zhidingAvatar = !item.zhiding_avatar.startsWith('http')
+ ? ossUrl + item.zhiding_avatar
+ : item.zhiding_avatar;
+ }
+ const zhidingNicheng = item.zhiding_nicheng || '';
+
+ return {
+ ...item,
+ full_tupian_url: fullTupianUrl,
+ isZhiding: isZhiding,
+ isPingtai: item.pingtai == 1,
+ isShangjia: item.pingtai == 2,
+ zhiding_avatar_full: zhidingAvatar,
+ zhiding_nicheng: zhidingNicheng,
+ // 🆕 三层标签(后端返回,直接保留)
+ xuqiu_biaoqian: item.xuqiu_biaoqian || [],
+ dashou_biaoqian: item.dashou_biaoqian || [],
+ shangjia_biaoqian: item.shangjia_biaoqian || [],
+ };
+ },
+
+ // 跳转到充值页面(原封不动)
+ goToChongzhiPage(failureType, huiyuanId = null, requiredYajin = 0) {
+ let url = '/pages/dashouchongzhi/dashouchongzhi';
+ let params = {};
+
+ switch (failureType) {
+ case 'huiyuan':
+ params = { needScroll: '0', scrollTo: 'member' };
+ break;
+ case 'yajin':
+ params = { needScroll: '1', scrollTo: 'bottom', requiredYajin: requiredYajin };
+ break;
+ case 'jifen':
+ params = { needScroll: '1', scrollTo: 'bottom' };
+ break;
+ }
+
+ if (huiyuanId) params.huiyuanId = huiyuanId;
+
+ const queryString = Object.keys(params)
+ .map(key => `${key}=${encodeURIComponent(params[key])}`)
+ .join('&');
+
+ wx.navigateTo({
+ url: `${url}?${queryString}`,
+ fail: (err) => {
+ console.error('跳转失败:', err);
+ wx.showToast({ title: '跳转失败,请稍后重试', icon: 'none' });
+ }
+ });
+ },
+
+ // 抢单按钮(原封不动)
+ async onQiangdanTap(e) {
+ const dingdanItem = e.currentTarget.dataset.item;
+ if (!dingdanItem) return;
+
+ // 校验1: 打手身份
+ if (!this.data.dashoustatus || this.data.dashoustatus != 1) {
+ wx.showToast({ title: '请先开启打手身份', icon: 'none' });
+ return;
+ }
+ // 校验2: 账号状态
+ if (this.data.zhuanghaoStatus != 1) {
+ wx.showToast({ title: '账号异常,无法接单', icon: 'none' });
+ return;
+ }
+ // 校验3: 打手接单状态
+ if (this.data.dashouzhuangtai != 1) {
+ wx.showToast({ title: '您有订单正在接待中', icon: 'none' });
+ return;
+ }
+ // 校验4: 指定订单
+ if (dingdanItem.isZhiding) {
+ if (!this.data.uid || this.data.uid != dingdanItem.zhiding_uid) {
+ wx.showToast({ title: '此订单为指定单', icon: 'none' });
+ return;
+ }
+ }
+ // 校验5: 会员要求
+ if (dingdanItem.yaoqiuleixing == 1) {
+ const hasHuiyuan = this.data.huiyuanList.some(h => h.huiyuanid == dingdanItem.huiyuan_id);
+ if (!hasHuiyuan) {
+ wx.showToast({
+ title: '未开通对应会员,无法抢单',
+ icon: 'none',
+ duration: 2000
+ });
+ setTimeout(() => {
+ this.goToChongzhiPage('huiyuan', dingdanItem.huiyuan_id);
+ }, 500);
+ return;
+ }
+ }
+ // 校验6: 押金要求
+ if (dingdanItem.yaoqiuleixing == 2) {
+ if (this.data.yajin < dingdanItem.yajin) {
+ wx.showToast({
+ title: `押金不足,需${dingdanItem.yajin}`,
+ icon: 'none',
+ duration: 2000
+ });
+ setTimeout(() => {
+ this.goToChongzhiPage('yajin', null, dingdanItem.yajin);
+ }, 500);
+ return;
+ }
+ }
+ // 校验7: 积分要求
+ if (this.data.jifen < 5) {
+ wx.showToast({
+ title: '积分不足,至少需要5积分',
+ icon: 'none',
+ duration: 2000
+ });
+ setTimeout(() => {
+ this.goToChongzhiPage('jifen');
+ }, 500);
+ return;
+ }
+
+ // 确认框
+ const that = this;
+ wx.showModal({
+ title: '确认抢单',
+ content: `确定要抢此订单吗?${dingdanItem.isZhiding ? '(指定订单)' : ''}`,
+ success: async function (res) {
+ if (res.confirm) {
+ wx.showLoading({ title: '抢单中...', mask: true });
+ try {
+ const qiangdanRes = await request({
+ url: '/dingdan/qiangdan',
+ method: 'POST',
+ data: { dingdan_id: dingdanItem.dingdan_id }
+ });
+ wx.hideLoading();
+
+ if (qiangdanRes.data.code === 200 || qiangdanRes.data.code === 0) {
+ that.showQiangdanSuccessEffect();
+ if (app.globalData) {
+ app.globalData.dashouzhuangtai = 0;
+ that.setData({ dashouzhuangtai: 0 });
+ }
+ const newList = that.data.dingdanList.filter(
+ item => item.dingdan_id !== dingdanItem.dingdan_id
+ );
+ that.setData({ dingdanList: newList });
+ } else {
+ wx.showToast({
+ title: qiangdanRes.data.msg || '抢单失败',
+ icon: 'none'
+ });
+ }
+ } catch (error) {
+ wx.hideLoading();
+ console.error('抢单请求失败:', error);
+ wx.showToast({ title: '网络错误,抢单失败', icon: 'none' });
+ }
+ }
+ }
+ });
+ },
+
+ showQiangdanSuccessEffect() {
+ wx.showToast({ title: '抢单成功!', icon: 'success', duration: 2000 });
+ },
+
+ onViewDetail(e) {
+ const { type, content } = e.currentTarget.dataset;
+ wx.showModal({
+ title: type === 'jieshao' ? '订单介绍' : '订单备注',
+ content: content,
+ showCancel: false,
+ confirmText: '知道了'
+ });
+ },
+
+ onReachBottom() {
+ const now = Date.now();
+ const lastTime = this.data.lastRefreshTime;
+ const cooldown = this.data.refreshCooldown;
+
+ if (now - lastTime < cooldown) {
+ wx.showToast({
+ title: `操作太快,请${Math.ceil((cooldown - (now - lastTime)) / 1000)}秒后再试`,
+ icon: 'none',
+ duration: 1500
+ });
+ return;
+ }
+
+ if (this.data.isLoading || this.data.isLoadingMore) return;
+
+ if (this.data.hasMore && !this.data.isLoading && this.data.xuanzhongLeixingId) {
+ this.setData({ page: this.data.page + 1 });
+ this.loadDingdanList(false);
+ }
+ },
+
+ onPullDownRefresh() {
+ const now = Date.now();
+ const lastTime = this.data.lastRefreshTime;
+ const cooldown = this.data.refreshCooldown;
+
+ if (now - lastTime < cooldown) {
+ this.setData({ scrollViewRefreshing: false });
+ wx.showToast({
+ title: `操作太快,请${Math.ceil((cooldown - (now - lastTime)) / 1000)}秒后再试`,
+ icon: 'none',
+ duration: 1500
+ });
+ return;
+ }
+
+ if (this.data.isLoading) {
+ this.setData({ scrollViewRefreshing: false });
+ return;
+ }
+
+ this.setData({
+ scrollViewRefreshing: true,
+ lastRefreshTime: now,
+ page: 1,
+ hasMore: true
+ });
+
+ if (this.data.xuanzhongLeixingId) {
+ this.loadDingdanList(true);
+ } else {
+ this.setData({ scrollViewRefreshing: false });
+ }
+ }
+});
\ No newline at end of file
diff --git a/miniprogram/pages/jiedan/jiedan.json b/miniprogram/pages/jiedan/jiedan.json
new file mode 100644
index 0000000..eee9b81
--- /dev/null
+++ b/miniprogram/pages/jiedan/jiedan.json
@@ -0,0 +1,12 @@
+{
+ "navigationBarTitleText": "抢单大厅",
+ "navigationBarBackgroundColor": "#0b0f1a",
+ "navigationBarTextStyle": "white",
+ "enablePullDownRefresh": false,
+ "backgroundTextStyle": "dark",
+ "backgroundColor": "#0b0f1a",
+ "usingComponents": {
+ "chenghao-tag": "/components/chenghao-tag/chenghao-tag",
+ "global-notification": "/components/global-notification/global-notification"
+ }
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/jiedan/jiedan.wxml b/miniprogram/pages/jiedan/jiedan.wxml
new file mode 100644
index 0000000..6450749
--- /dev/null
+++ b/miniprogram/pages/jiedan/jiedan.wxml
@@ -0,0 +1,208 @@
+
+
+
+
+
+
+
+
+
+
+ {{item.jieshao}}
+
+
+
+
+
+
+
+
+
+
+
+ 全部
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ⚡️ 正在刷新...
+ ▼ 下拉刷新订单
+
+
+
+
+
+ 请先选择上方订单类型
+
+
+
+ 当前类型暂无待抢订单
+
+
+
+
+
+
+ {{item.creat_time}}
+
+ ⚙️ 平台订单
+
+
+
+
+ 指定
+
+ {{item.zhiding_nicheng || '指定打手'}}
+ UID: {{item.zhiding_uid || '未知'}}
+
+
+
+
+
+
+
+
+
+ 介绍:
+ {{item.jieshao || '暂无介绍'}}
+ [点击查看]
+
+
+
+
+ 备注:
+ {{item.beizhu}}
+ [点击查看]
+
+
+
+
+ 需求标签:
+
+
+
+
+
+
+
+ 💰
+ 分佣
+ {{item.dashou_fencheng || 0}}
+ 元
+
+
+ ⚡ 立即抢单
+
+
+
+
+
+
+
+
+
+
+ {{item.creat_time}}
+
+ 🏪 商家发单
+
+
+
+
+ 指定
+
+ {{item.zhiding_nicheng || '指定打手'}}
+ UID: {{item.zhiding_uid || '未知'}}
+
+
+
+
+
+
+
+
+ {{item.sjnicheng || '未知商家'}}
+
+
+
+
+
+
+
+ 订单介绍:
+ {{item.jieshao || '暂无介绍'}}
+ [点击查看]
+
+
+
+ 商家备注:
+ {{item.beizhu}}
+ [点击查看]
+
+
+
+
+ 需求标签:
+
+
+
+
+
+
+
+ 💰
+ 分佣
+ {{item.dashou_fencheng || 0}}
+ 元
+
+
+ ⚡ 立即抢单
+
+
+
+
+
+
+
+
+ ⚙️ 加载更多订单中...
+ —— 没有更多订单了 ——
+
+
+
+
+
+ ⚡ 订单加载中...
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/jiedan/jiedan.wxss b/miniprogram/pages/jiedan/jiedan.wxss
new file mode 100644
index 0000000..52ed1a3
--- /dev/null
+++ b/miniprogram/pages/jiedan/jiedan.wxss
@@ -0,0 +1,762 @@
+/* pages/qiangdan/qiangdan.wxss - 高级机甲风格重构版(对称优化) */
+.qiangdan-page {
+ height: 100vh;
+ display: flex;
+ flex-direction: column;
+ background: radial-gradient(ellipse at top, #1a1f30, #0b0f1a);
+ overflow: hidden;
+ position: relative;
+ }
+
+ /* 背景动态纹理,增加层次感 */
+ .qiangdan-page::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-image:
+ linear-gradient(45deg, rgba(0, 247, 255, 0.02) 0%, transparent 30%),
+ radial-gradient(circle at 30% 50%, rgba(255, 77, 109, 0.03) 0%, transparent 50%),
+ repeating-linear-gradient(45deg, rgba(255,255,255,0.01) 0px, rgba(255,255,255,0.01) 1px, transparent 1px, transparent 6px);
+ pointer-events: none;
+ z-index: 0;
+ }
+
+ /* ====== 1. 商品类型区域 ====== */
+ .leixing-quyu {
+ padding: 30rpx 0 20rpx;
+ background: rgba(18, 22, 38, 0.9);
+ backdrop-filter: blur(20rpx);
+ border-bottom: 1rpx solid rgba(255, 255, 255, 0.05);
+ position: relative;
+ z-index: 10;
+ flex-shrink: 0;
+ box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.5);
+ }
+
+ .leixing-scroll {
+ width: 100%;
+ white-space: nowrap;
+ height: 180rpx;
+ }
+
+ .leixing-container {
+ display: inline-flex;
+ padding: 0 30rpx;
+ gap: 20rpx;
+ }
+
+ .leixing-item {
+ display: inline-flex;
+ flex-direction: column;
+ align-items: center;
+ width: 160rpx;
+ position: relative;
+ transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
+ }
+
+ .leixing-active {
+ transform: translateY(-8rpx);
+ }
+
+ .guangyun-effect {
+ position: absolute;
+ top: -15rpx;
+ left: -15rpx;
+ right: -15rpx;
+ bottom: -15rpx;
+ background: radial-gradient(circle at center, rgba(0, 200, 255, 0.3) 0%, transparent 70%);
+ border-radius: 50rpx;
+ z-index: 0;
+ animation: guangyun-pulse 2s infinite ease-in-out;
+ }
+
+ @keyframes guangyun-pulse {
+ 0%, 100% { opacity: 0.5; transform: scale(1); }
+ 50% { opacity: 1; transform: scale(1.1); }
+ }
+
+ .leixing-tupian {
+ width: 120rpx;
+ height: 120rpx;
+ border-radius: 32rpx;
+ z-index: 1;
+ box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.6);
+ transition: all 0.3s ease;
+ border: 2rpx solid rgba(0, 200, 255, 0.4);
+ }
+
+ .leixing-active .leixing-tupian {
+ box-shadow: 0 16rpx 40rpx rgba(0, 200, 255, 0.5);
+ border: 2rpx solid #00c8ff;
+ }
+
+ .leixing-jieshao {
+ margin-top: 16rpx;
+ font-size: 24rpx;
+ color: #b0c0e0;
+ max-width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ text-align: center;
+ z-index: 1;
+ font-weight: 500;
+ }
+
+ .leixing-active .leixing-jieshao {
+ color: #00c8ff;
+ text-shadow: 0 0 8px rgba(0,200,255,0.5);
+ }
+
+ /* ====== 2. 分割线 ====== */
+ .fenge-xian {
+ height: 2rpx;
+ background: linear-gradient(90deg, transparent, #00c8ff, #ff4d6d, transparent);
+ flex-shrink: 0;
+ margin: 0 30rpx;
+ opacity: 0.5;
+ }
+
+ /* ====== 3. 订单列表容器 ====== */
+ .dingdan-container {
+ flex: 1;
+ overflow: hidden;
+ position: relative;
+ z-index: 1;
+ padding: 0 20rpx; /* 统一左右内边距 */
+ }
+
+ .dingdan-scroll-view {
+ height: 100%;
+ -webkit-overflow-scrolling: touch;
+ }
+
+ .refresher-container {
+ height: 100rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .refreshing-text,
+ .pull-down-text {
+ font-size: 26rpx;
+ color: #6a9cff;
+ text-shadow: 0 0 8px rgba(0,200,255,0.3);
+ }
+
+ /* ====== 4. 空状态提示 ====== */
+ .empty-tip {
+ padding: 160rpx 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .tip-icon {
+ width: 180rpx;
+ height: 180rpx;
+ opacity: 0.5;
+ margin-bottom: 30rpx;
+ filter: drop-shadow(0 0 20px rgba(0,200,255,0.3));
+ }
+
+ .tip-text {
+ font-size: 28rpx;
+ color: #8a9ac0;
+ letter-spacing: 2rpx;
+ }
+
+ /* ====== 5. 订单卡片通用样式(移除左右边框,改用顶部标识)====== */
+ .dingdan-card {
+ margin: 20rpx 0; /* 左右由容器控制 */
+ border-radius: 36rpx;
+ padding: 28rpx 24rpx;
+ position: relative;
+ overflow: hidden;
+ box-shadow: 0 20rpx 40rpx rgba(0, 0, 0, 0.6);
+ transition: all 0.3s ease;
+ border: 1rpx solid rgba(255, 255, 255, 0.03);
+ backdrop-filter: blur(30rpx);
+ }
+
+ .dingdan-card:active {
+ transform: scale(0.998);
+ box-shadow: 0 10rpx 20rpx rgba(0, 0, 0, 0.8);
+ }
+
+ /* 平台订单 - 冷色调,顶部光效 */
+ .pingtai-card {
+ background: linear-gradient(145deg, rgba(18, 28, 48, 0.95), rgba(8, 18, 35, 0.98));
+ }
+
+ .pingtai-card::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 10%;
+ width: 80%;
+ height: 4rpx;
+ background: linear-gradient(90deg, transparent, #00c8ff, transparent);
+ border-radius: 2rpx;
+ opacity: 0.7;
+ }
+
+ /* 商家订单 - 暖色调,顶部光效 */
+ .shangjia-card {
+ background: linear-gradient(145deg, rgba(38, 18, 48, 0.95), rgba(25, 8, 35, 0.98));
+ }
+
+ .shangjia-card::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 10%;
+ width: 80%;
+ height: 4rpx;
+ background: linear-gradient(90deg, transparent, #ff4d6d, transparent);
+ border-radius: 2rpx;
+ opacity: 0.7;
+ }
+
+ /* 卡片顶部 */
+ .card-top {
+ display: flex;
+ align-items: center;
+ margin-bottom: 20rpx;
+ padding-bottom: 16rpx;
+ border-bottom: 1rpx dashed rgba(255, 255, 255, 0.08);
+ }
+
+ .creat-time {
+ font-size: 26rpx;
+ color: #c0d0ff;
+ font-weight: 500;
+ text-shadow: 0 0 5px rgba(0,200,255,0.3);
+ }
+
+ .top-line {
+ flex: 1;
+ height: 1rpx;
+ background: linear-gradient(to right, transparent, rgba(255, 255, 255, 0.2), transparent);
+ margin: 0 20rpx;
+ }
+
+ .pingtai-tag {
+ font-size: 24rpx;
+ color: #aac8ff;
+ padding: 6rpx 18rpx;
+ background: rgba(0,200,255,0.08);
+ border-radius: 40rpx;
+ border: 1rpx solid rgba(0,200,255,0.3);
+ box-shadow: 0 0 10rpx rgba(0,200,255,0.2);
+ }
+
+ /* ====== 6. 指定单标识行(新设计)====== */
+ .zhiding-row {
+ display: flex;
+ align-items: center;
+ margin-bottom: 24rpx;
+ padding: 12rpx 20rpx;
+ background: rgba(255, 77, 109, 0.1);
+ border-radius: 60rpx;
+ border: 1rpx solid rgba(255, 77, 109, 0.3);
+ box-shadow: 0 0 20rpx rgba(255, 77, 109, 0.2);
+ backdrop-filter: blur(10rpx);
+ gap: 12rpx;
+ }
+
+ .zhiding-icon {
+ font-size: 28rpx;
+ color: #ff4d6d;
+ font-weight: 700;
+ background: rgba(255, 77, 109, 0.2);
+ padding: 4rpx 16rpx;
+ border-radius: 40rpx;
+ text-shadow: 0 0 8px #ff4d6d;
+ letter-spacing: 2rpx;
+ }
+
+ .zhiding-avatar {
+ width: 48rpx;
+ height: 48rpx;
+ border-radius: 50%;
+ border: 2rpx solid #ff4d6d;
+ box-shadow: 0 0 15rpx #ff4d6d;
+ object-fit: cover;
+ }
+
+ .zhiding-nicheng {
+ font-size: 26rpx;
+ color: #ffc0d0;
+ font-weight: 500;
+ max-width: 160rpx;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ .zhiding-uid {
+ font-size: 24rpx;
+ color: #ff9aab;
+ flex: 1;
+ text-align: right;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ font-family: monospace;
+ }
+
+ /* ====== 7. 平台订单内容区 ====== */
+ .card-content {
+ display: flex;
+ margin-bottom: 24rpx;
+ align-items: center;
+ }
+
+ .neirong-tupian {
+ width: 140rpx;
+ height: 140rpx;
+ border-radius: 50%;
+ margin-right: 24rpx;
+ flex-shrink: 0;
+ box-shadow: 0 10rpx 30rpx rgba(0,200,255,0.3);
+ border: 2rpx solid #00c8ff;
+ object-fit: cover;
+ }
+
+ .jieshao-box {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ min-height: 140rpx;
+ padding: 8rpx 0;
+ }
+
+ .jieshao-label {
+ font-size: 26rpx;
+ color: #d0e0ff;
+ font-weight: 600;
+ margin-bottom: 6rpx;
+ text-shadow: 0 0 5px rgba(0,200,255,0.3);
+ }
+
+ .jieshao-text {
+ font-size: 26rpx;
+ color: #b0c8f0;
+ line-height: 1.4;
+ word-break: break-word;
+ }
+
+ .jieshao-ellipsis .jieshao-text {
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 3;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ /* ====== 8. 商家订单特有样式 ====== */
+ .shangjia-info {
+ display: flex;
+ align-items: center;
+ margin-bottom: 20rpx;
+ }
+
+ .shangjia-touxiang {
+ width: 90rpx;
+ height: 90rpx;
+ border-radius: 50%;
+ margin-right: 20rpx;
+ border: 2rpx solid #ff4d6d;
+ box-shadow: 0 6rpx 18rpx rgba(255, 77, 109, 0.5);
+ object-fit: cover;
+ }
+
+ .shangjia-nicheng {
+ font-size: 30rpx;
+ color: #ffb3c6;
+ font-weight: 600;
+ text-shadow: 0 0 8px rgba(255, 77, 109, 0.4);
+ }
+
+ .shangjia-jieshao,
+ .shangjia-beizhu {
+ margin-bottom: 16rpx;
+ padding: 14rpx 18rpx;
+ background: rgba(255, 255, 255, 0.02);
+ border-radius: 24rpx;
+ border: 1rpx solid rgba(255, 77, 109, 0.2);
+ }
+
+ .shangjia-jieshao .jieshao-label,
+ .shangjia-beizhu .beizhu-label {
+ display: block;
+ margin-bottom: 6rpx;
+ font-size: 24rpx;
+ color: #ff9aab;
+ }
+
+ .shangjia-jieshao .jieshao-text,
+ .shangjia-beizhu .beizhu-text {
+ font-size: 24rpx;
+ color: #e0c8ff;
+ line-height: 1.4;
+ }
+
+ .shangjia-jieshao.jieshao-ellipsis .jieshao-text {
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .shangjia-beizhu.beizhu-ellipsis .beizhu-text {
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ /* ====== 9. 通用备注区 ====== */
+ .beizhu-box {
+ margin-bottom: 20rpx;
+ padding: 14rpx 18rpx;
+ background: rgba(255, 255, 255, 0.02);
+ border-radius: 24rpx;
+ border: 1rpx solid rgba(0,200,255,0.2);
+ }
+
+ .beizhu-label {
+ font-size: 24rpx;
+ color: #80d0ff;
+ font-weight: 600;
+ margin-bottom: 6rpx;
+ display: block;
+ }
+
+ .beizhu-text {
+ font-size: 24rpx;
+ color: #b0c8f0;
+ line-height: 1.4;
+ }
+
+ .beizhu-ellipsis .beizhu-text {
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .view-more {
+ display: block;
+ font-size: 22rpx;
+ color: #00c8ff;
+ margin-top: 6rpx;
+ text-align: right;
+ opacity: 0.8;
+ text-shadow: 0 0 5px #00c8ff;
+ }
+
+ /* ====== 10. 卡片底部:分佣 + 按钮 ====== */
+ .card-bottom {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding-top: 20rpx;
+ border-top: 1rpx solid rgba(255, 255, 255, 0.06);
+ }
+
+ /* 分佣容器(图标占位,用户可自定义) */
+ .fenyong-box {
+ display: flex;
+ align-items: baseline;
+ background: rgba(0, 0, 0, 0.3);
+ padding: 8rpx 20rpx;
+ border-radius: 60rpx;
+ border: 1rpx solid rgba(255, 215, 0, 0.3);
+ }
+
+ /* 分佣图标占位符,用户可替换为自己图标 */
+ .fenyong-icon {
+ font-size: 32rpx;
+ margin-right: 10rpx;
+ filter: drop-shadow(0 0 8px gold);
+ }
+
+ .fenyong-text {
+ font-size: 24rpx;
+ color: #c0b0a0;
+ margin-right: 8rpx;
+ }
+
+ .fenyong-price {
+ font-size: 44rpx;
+ color: #ffd700;
+ font-weight: 800;
+ margin-right: 4rpx;
+ text-shadow: 0 0 20px rgba(255, 215, 0, 0.8);
+ }
+
+ .fenyong-unit {
+ font-size: 24rpx;
+ color: #a0a0b0;
+ }
+
+ /* ====== 11. 机甲风格抢单按钮(对称优化)====== */
+ .qiangdan-btn {
+ position: relative;
+ padding: 16rpx 44rpx;
+ border: none;
+ overflow: hidden;
+ transition: all 0.2s ease;
+ cursor: pointer;
+ z-index: 1;
+ /* 多边形形状,保证上下对称 */
+ clip-path: polygon(10% 0%, 90% 0%, 100% 25%, 100% 75%, 90% 100%, 10% 100%, 0% 75%, 0% 25%);
+ }
+
+ /* 按钮统一基础样式,无上下不对称阴影 */
+ .mecha-btn {
+ background: linear-gradient(145deg, #2a3050, #181e38);
+ box-shadow:
+ 0 8rpx 0 #0a0a1a,
+ 0 12rpx 28rpx rgba(0, 0, 0, 0.7),
+ inset 0 2rpx 6rpx rgba(255, 255, 255, 0.2),
+ inset 0 -2rpx 4rpx rgba(0, 0, 0, 0.3);
+ border: 1rpx solid rgba(255, 255, 255, 0.1);
+ }
+
+ /* 平台按钮冷光 */
+ .pingtai-btn.mecha-btn {
+ background: linear-gradient(145deg, #1a2a48, #0e1a30);
+ box-shadow:
+ 0 8rpx 0 #0a1525,
+ 0 12rpx 28rpx rgba(0, 0, 0, 0.7),
+ inset 0 2rpx 6rpx #00c8ff,
+ inset 0 -2rpx 4rpx rgba(0, 0, 0, 0.3);
+ }
+
+ /* 商家按钮暖光 */
+ .shangjia-btn.mecha-btn {
+ background: linear-gradient(145deg, #3a2048, #251030);
+ box-shadow:
+ 0 8rpx 0 #2a0a1a,
+ 0 12rpx 28rpx rgba(0, 0, 0, 0.7),
+ inset 0 2rpx 6rpx #ff4d6d,
+ inset 0 -2rpx 4rpx rgba(0, 0, 0, 0.3);
+ }
+
+ .btn-text {
+ font-size: 32rpx;
+ color: #fff;
+ font-weight: 700;
+ letter-spacing: 3rpx;
+ position: relative;
+ z-index: 3;
+ text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.7), 0 0 15rpx currentColor;
+ }
+
+ /* 扫光效果 */
+ .btn-shine {
+ position: absolute;
+ top: -50%;
+ left: -50%;
+ width: 200%;
+ height: 200%;
+ background: linear-gradient(
+ to right,
+ rgba(255, 255, 255, 0) 0%,
+ rgba(255, 255, 255, 0.25) 50%,
+ rgba(255, 255, 255, 0) 100%
+ );
+ transform: rotate(30deg);
+ animation: btn-shimmer 2.8s infinite linear;
+ z-index: 2;
+ }
+
+ /* 内发光,只在悬停/按下时出现,保证对称 */
+ .btn-glow {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: radial-gradient(circle at 30% 30%, rgba(255, 255, 255, 0.5), transparent 70%);
+ opacity: 0;
+ transition: opacity 0.3s;
+ z-index: 1;
+ }
+
+ .mecha-btn:hover .btn-glow,
+ .mecha-btn:active .btn-glow {
+ opacity: 0.3;
+ }
+
+ /* 按下效果,保持对称 */
+ .mecha-btn:active {
+ transform: translateY(4rpx) scale(0.98);
+ box-shadow:
+ 0 4rpx 0 #0a0a1a,
+ 0 8rpx 20rpx rgba(0, 0, 0, 0.7),
+ inset 0 2rpx 6rpx rgba(255, 255, 255, 0.2),
+ inset 0 -2rpx 4rpx rgba(0, 0, 0, 0.3);
+ }
+
+ /* 机械纹理,保持视觉丰富但不过分 */
+ .mecha-overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-image:
+ repeating-linear-gradient(45deg, rgba(255, 255, 255, 0.02) 0px, rgba(255, 255, 255, 0.02) 2px, transparent 2px, transparent 8px);
+ pointer-events: none;
+ z-index: 1;
+ opacity: 0.5;
+ }
+
+ @keyframes btn-shimmer {
+ 0% { transform: translateX(-100%) rotate(30deg); }
+ 100% { transform: translateX(100%) rotate(30deg); }
+ }
+
+ /* ====== 12. 加载状态 ====== */
+ .loading-more,
+ .no-more {
+ padding: 40rpx 0;
+ text-align: center;
+ }
+
+ .loading-text {
+ font-size: 26rpx;
+ color: #6a9cff;
+ text-shadow: 0 0 10px rgba(0,200,255,0.5);
+ animation: pulse 1.5s infinite;
+ }
+
+ .no-more-text {
+ font-size: 26rpx;
+ color: #4a5a8a;
+ letter-spacing: 3rpx;
+ }
+
+ @keyframes pulse {
+ 0%, 100% { opacity: 0.7; }
+ 50% { opacity: 1; }
+ }
+
+ .loading-mask {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(5, 8, 18, 0.95);
+ backdrop-filter: blur(30rpx);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ z-index: 9999;
+ }
+
+ .loading-spinner {
+ width: 90rpx;
+ height: 90rpx;
+ border: 6rpx solid rgba(0,200,255,0.2);
+ border-top: 6rpx solid #00c8ff;
+ border-right: 6rpx solid #ff4d6d;
+ border-radius: 50%;
+ animation: spin 1.2s cubic-bezier(0.68, -0.55, 0.27, 1.55) infinite;
+ margin-bottom: 30rpx;
+ box-shadow: 0 0 50rpx #00c8ff;
+ }
+
+ @keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+ }
+
+ .loading-mask-text {
+ font-size: 30rpx;
+ color: #c0d0ff;
+ text-shadow: 0 0 15px rgba(0,200,255,0.7);
+ letter-spacing: 4rpx;
+ }
+
+
+
+
+
+/* 🆕 标签筛选区 */
+.biaoqian-quyu {
+ padding: 12rpx 20rpx;
+ background: rgba(18, 22, 38, 0.7);
+ backdrop-filter: blur(10rpx);
+ }
+ .biaoqian-scroll {
+ white-space: nowrap;
+ height: 80rpx;
+ }
+ .biaoqian-container {
+ display: inline-flex;
+ gap: 16rpx;
+ padding-right: 20rpx;
+ }
+ .biaoqian-item {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0 12rpx;
+ border-radius: 20rpx;
+ background: rgba(255,255,255,0.05);
+ transition: all 0.2s;
+ }
+ .biaoqian-active {
+ background: rgba(0,200,255,0.2);
+ border: 1rpx solid #00c8ff;
+ }
+ .quanbu-text {
+ font-size: 26rpx;
+ color: #ccc;
+ padding: 0 12rpx;
+ }
+
+ /* 订单卡片内标签滚动 */
+ .xuqiu-biaoqian-area {
+ display: flex;
+ align-items: center;
+ margin-top: 16rpx;
+ padding: 8rpx 0;
+ }
+ .xuqiu-label {
+ font-size: 24rpx;
+ color: #80d0ff;
+ margin-right: 12rpx;
+ flex-shrink: 0;
+ }
+ .xuqiu-biaoqian-scroll {
+ flex: 1;
+ white-space: nowrap;
+ }
+ .biaoqian-inline-scroll {
+ flex: 1;
+ white-space: nowrap;
+ margin-left: 10rpx;
+ }
+ /* 调整指定打手行内的标签滚动 */
+ .zhiding-row .biaoqian-inline-scroll {
+ margin-left: auto;
+ max-width: 50%;
+ }
+
diff --git a/miniprogram/pages/jiedanchi/jiedanchi.js b/miniprogram/pages/jiedanchi/jiedanchi.js
new file mode 100644
index 0000000..a081b4e
--- /dev/null
+++ b/miniprogram/pages/jiedanchi/jiedanchi.js
@@ -0,0 +1,467 @@
+// pages/jiedanchi/jiedanchi.js
+const app = getApp();
+import request from '../../utils/request.js';
+import PopupService from '../../services/popupService.js';
+
+Page({
+ data: {
+ // 1. 商品类型相关
+ shangpinleixing: [], // 商品类型列表 {id, jieshao, tupian_url}
+ xuanzhongLeixingId: null, // 当前选中的类型ID
+
+ // 2. 订单列表与分页相关
+ dingdanList: [], // 当前展示的订单列表
+ page: 1,
+ pageSize: 10,
+ hasMore: true,
+ isLoading: false,
+
+ // 3. 全局状态字段
+ dashoustatus: null,
+ zhuanghaoStatus: null, // 注意变量名:zhuanghaoStatus
+ dashouzhuangtai: null,
+ uid: null,
+ huiyuanList: [], // clumber 列表
+ yajin: 0,
+ jifen: 0,
+
+ // 4. 刷新控制字段
+ scrollViewRefreshing: false,
+ lastRefreshTime: 0,
+ refreshCooldown: 2000,
+ isLoadingMore: false,
+
+ // 5. 切换类型冷却时间控制
+ lastSwitchTime: 0,
+ switchCooldown: 1500,
+ isSwitching: false,
+
+ // 6. 全局OSS地址
+ ossImageUrl: app.globalData.ossImageUrl || '',
+
+ // 7. 授权状态控制
+ showUnauthorized: false,
+ unauthorizedMsg: '您尚未开启此功能',
+ },
+
+ async onLoad(options) {
+ this.loadGlobalStatus();
+ this.checkAuthorization();
+
+ },
+ // 🆕 页面隐藏时清理弹窗视图
+ onHide: function () {
+ const popupComp = this.selectComponent('#popupNotice');
+ if (popupComp && popupComp.cleanup) {
+ popupComp.cleanup();
+ }
+},
+
+ onShow() {
+ this.registerNotificationComponent();
+ this.loadGlobalStatus();
+ this.checkAuthorization();
+ },
+
+ registerNotificationComponent() {
+ const notificationComp = this.selectComponent('#global-notification');
+ if (notificationComp && notificationComp.showNotification) {
+ app.globalData.globalNotification = {
+ show: (data) => notificationComp.showNotification(data),
+ hide: () => notificationComp.hideNotification()
+ };
+ }
+ },
+
+ loadGlobalStatus() {
+ const globalData = app.globalData;
+ // 从缓存读取,确保与抢单时使用的数据源一致
+ const dashoustatusCache = wx.getStorageSync('dashoustatus');
+ const dashoustatus = dashoustatusCache !== '' ? dashoustatusCache : null;
+
+ // 从 globalData 或缓存获取 zhuanghaoStatus(注意字段名可能有差异)
+ let zhuanghaoStatus = globalData.zhanghaoStatus;
+ if (zhuanghaoStatus === undefined || zhuanghaoStatus === null) {
+ zhuanghaoStatus = wx.getStorageSync('zhanghaoStatus');
+ }
+
+ this.setData({
+ dashoustatus: dashoustatus,
+ zhuanghaoStatus: zhuanghaoStatus,
+ dashouzhuangtai: globalData.dashouzhuangtai,
+ uid: wx.getStorageSync('uid'),
+ huiyuanList: globalData.clumber || [],
+ yajin: globalData.yajin || 0,
+ jifen: globalData.jinfen || 0
+ });
+
+ // 打印当前值,方便调试
+ //console.log('loadGlobalStatus -> dashoustatus:', dashoustatus, 'zhuanghaoStatus:', zhuanghaoStatus);
+ },
+
+ checkAuthorization() {
+ const { dashoustatus, zhuanghaoStatus } = this.data;
+ //console.log('checkAuthorization -> dashoustatus:', dashoustatus, 'zhuanghaoStatus:', zhuanghaoStatus);
+
+ // 完全复制抢单时的判断逻辑
+ const isDashouValid = (dashoustatus && dashoustatus == 1) || false; // 存在且等于1
+ const isZhuanghaoValid = (zhuanghaoStatus == 1); // 宽松比较,null/undefined/0 均视为 false
+
+ const isAuthorized = isDashouValid && isZhuanghaoValid;
+
+ if (!isAuthorized) {
+ this.setData({ showUnauthorized: true });
+ let msg = '';
+ if (!isDashouValid) msg = '您尚未开启身份';
+ //else if (!isZhuanghaoValid) msg = '您的账号状态异常';
+ this.setData({ unauthorizedMsg: msg || '无法使用抢单功能' });
+ } else {
+ this.setData({ showUnauthorized: false });
+ if (!this.data.shangpinleixing.length) {
+ this.loadShangpinLeixing();
+ } else if (this.data.xuanzhongLeixingId) {
+ this.loadDingdanList(true);
+ }
+ PopupService.checkAndShow(this, 'jiedanchi');
+ }
+ },
+
+ async loadShangpinLeixing() {
+ wx.showLoading({ title: '加载商品类型...' });
+ try {
+ const res = await request({
+ url: '/dingdan/dsqdhqddlx',
+ method: 'POST',
+ header: { 'content-type': 'application/json' }
+ });
+
+ if (res.statusCode === 200 && res.data) {
+ const data = res.data;
+ let list = [];
+ if (data.code === 200 || data.code === 0) {
+ if (data.data && Array.isArray(data.data.list)) {
+ list = data.data.list;
+ } else if (data.data && Array.isArray(data.data)) {
+ list = data.data;
+ } else if (Array.isArray(data.list)) {
+ list = data.list;
+ } else if (Array.isArray(data)) {
+ list = data;
+ }
+ }
+ const processedList = this.processTupianUrl(list);
+ this.setData({
+ shangpinleixing: processedList,
+ xuanzhongLeixingId: processedList[0]?.id || null
+ });
+ if (this.data.xuanzhongLeixingId) {
+ this.loadDingdanList(true);
+ }
+ } else {
+ wx.showToast({ title: res.data?.msg || '加载商品类型失败', icon: 'none', duration: 2000 });
+ }
+ } catch (error) {
+ console.error('加载商品类型失败:', error);
+ wx.showToast({ title: '网络错误,加载失败', icon: 'none', duration: 2000 });
+ } finally {
+ wx.hideLoading();
+ }
+ },
+
+ processTupianUrl(list) {
+ const ossUrl = app.globalData.ossImageUrl || '';
+ return list.map(item => ({
+ ...item,
+ full_tupian_url: item.tupian_url && !item.tupian_url.startsWith('http') ? ossUrl + item.tupian_url : (item.tupian_url || '/images/default-type.png')
+ }));
+ },
+
+ async selectLeixing(e) {
+ const now = Date.now();
+ if (now - this.data.lastSwitchTime < this.data.switchCooldown) {
+ wx.showToast({
+ title: `操作太快,请${Math.ceil((this.data.switchCooldown - (now - this.data.lastSwitchTime)) / 1000)}秒后再试`,
+ icon: 'none',
+ duration: 1500
+ });
+ return;
+ }
+ if (this.data.isSwitching) return;
+ const leixingId = e.currentTarget.dataset.id;
+ if (leixingId === this.data.xuanzhongLeixingId) return;
+ this.setData({ isSwitching: true, lastSwitchTime: now });
+ wx.showLoading({ title: '切换中...' });
+ try {
+ this.setData({
+ xuanzhongLeixingId: leixingId,
+ dingdanList: [],
+ page: 1,
+ hasMore: true
+ });
+ await this.loadDingdanList(true);
+ } catch (error) {
+ console.error('切换类型失败:', error);
+ } finally {
+ this.setData({ isSwitching: false });
+ wx.hideLoading();
+ }
+ },
+
+ async loadDingdanList(isRefresh = false) {
+ if (this.data.isLoading || !this.data.xuanzhongLeixingId) return;
+
+ const loadPage = isRefresh ? 1 : this.data.page;
+ if (!isRefresh && !this.data.hasMore) return;
+
+ this.setData({
+ isLoading: true,
+ isLoadingMore: !isRefresh
+ });
+
+ try {
+ const res = await request({
+ url: '/dingdan/ddhq',
+ method: 'POST',
+ data: {
+ leixing_id: this.data.xuanzhongLeixingId,
+ page: loadPage,
+ page_size: this.data.pageSize
+ }
+ });
+
+ this.setData({
+ isLoading: false,
+ isLoadingMore: false,
+ scrollViewRefreshing: false
+ });
+
+ if (res.data.code === 200 || res.data.code === 0) {
+ const newList = res.data.data.list || [];
+ const hasMore = res.data.data.has_more || false;
+
+ const processedList = newList.map(item => this.processDingdanItem(item));
+
+ const updatedList = isRefresh ? processedList : [...this.data.dingdanList, ...processedList];
+
+ updatedList.sort((a, b) => (b.creat_time || '').localeCompare(a.creat_time || ''));
+
+ this.setData({
+ dingdanList: updatedList,
+ page: loadPage,
+ hasMore: hasMore
+ });
+
+ if (isRefresh) {
+ this.setData({ lastRefreshTime: Date.now() });
+ }
+ } else {
+ wx.showToast({ title: res.data.msg || '加载失败', icon: 'none' });
+ }
+ } catch (error) {
+ console.error('加载订单失败:', error);
+ this.setData({
+ isLoading: false,
+ isLoadingMore: false,
+ scrollViewRefreshing: false
+ });
+ wx.showToast({ title: '网络请求失败', icon: 'none' });
+ }
+ },
+
+ processDingdanItem(item) {
+ const ossUrl = app.globalData.ossImageUrl || '';
+ let fullTupianUrl = '';
+ if (item.tupian) {
+ fullTupianUrl = !item.tupian.startsWith('http') ? ossUrl + item.tupian : item.tupian;
+ } else {
+ const leixing = this.data.shangpinleixing.find(l => l.id === item.leixing_id);
+ fullTupianUrl = leixing ? leixing.full_tupian_url : '/images/default-order.png';
+ }
+
+ const isZhiding = item.zhuangtai === 7;
+
+ let zhidingAvatar = '';
+ if (item.zhiding_avatar) {
+ zhidingAvatar = !item.zhiding_avatar.startsWith('http') ? ossUrl + item.zhiding_avatar : item.zhiding_avatar;
+ }
+ const zhidingNicheng = item.zhiding_nicheng || '';
+
+ return {
+ ...item,
+ full_tupian_url: fullTupianUrl,
+ isZhiding: isZhiding,
+ isPingtai: item.pingtai == 1,
+ isShangjia: item.pingtai == 2,
+ zhiding_avatar_full: zhidingAvatar,
+ zhiding_nicheng: zhidingNicheng
+ };
+ },
+
+ goToChongzhiPage(failureType, huiyuanId = null, requiredYajin = 0) {
+ let url = '/pages/dashouchongzhi/dashouchongzhi';
+ let params = {};
+ switch (failureType) {
+ case 'huiyuan':
+ params = { needScroll: '0', scrollTo: 'member' };
+ break;
+ case 'yajin':
+ params = { needScroll: '1', scrollTo: 'bottom', requiredYajin: requiredYajin };
+ break;
+ case 'jifen':
+ params = { needScroll: '1', scrollTo: 'bottom' };
+ break;
+ }
+ if (huiyuanId) params.huiyuanId = huiyuanId;
+ const queryString = Object.keys(params).map(key => `${key}=${encodeURIComponent(params[key])}`).join('&');
+ const fullUrl = `${url}?${queryString}`;
+ wx.navigateTo({
+ url: fullUrl,
+ fail: (err) => {
+ console.error('跳转失败:', err);
+ wx.showToast({ title: '跳转失败,请稍后重试', icon: 'none' });
+ }
+ });
+ },
+
+ async onQiangdanTap(e) {
+ const dingdanItem = e.currentTarget.dataset.item;
+ if (!dingdanItem) return;
+
+ if (!this.data.dashoustatus || this.data.dashoustatus != 1) {
+ wx.showToast({ title: '请先开启打手身份', icon: 'none' });
+ return;
+ }
+ if (this.data.zhuanghaoStatus != 1) {
+ wx.showToast({ title: '账号异常,无法接单', icon: 'none' });
+ return;
+ }
+ if (this.data.dashouzhuangtai != 1) {
+ wx.showToast({ title: '您有订单正在接待中', icon: 'none' });
+ return;
+ }
+ if (dingdanItem.isZhiding) {
+ if (!this.data.uid || this.data.uid != dingdanItem.zhiding_uid) {
+ wx.showToast({ title: '此订单为指定单', icon: 'none' });
+ return;
+ }
+ }
+ if (dingdanItem.yaoqiuleixing == 1) {
+ const hasHuiyuan = this.data.huiyuanList.some(h => h.huiyuanid == dingdanItem.huiyuan_id);
+ if (!hasHuiyuan) {
+ wx.showToast({ title: '未开通对应会员,无法抢单', icon: 'none', duration: 2000 });
+ setTimeout(() => this.goToChongzhiPage('huiyuan', dingdanItem.huiyuan_id), 500);
+ return;
+ }
+ }
+ if (dingdanItem.yaoqiuleixing == 2) {
+ if (this.data.yajin < dingdanItem.yajin) {
+ wx.showToast({ title: `押金不足,需${dingdanItem.yajin}`, icon: 'none', duration: 2000 });
+ setTimeout(() => this.goToChongzhiPage('yajin', null, dingdanItem.yajin), 500);
+ return;
+ }
+ }
+ if (this.data.jifen < 5) {
+ wx.showToast({ title: '积分不足,至少需要5积分', icon: 'none', duration: 2000 });
+ setTimeout(() => this.goToChongzhiPage('jifen'), 500);
+ return;
+ }
+
+ const that = this;
+ wx.showModal({
+ title: '确认抢单',
+ content: `确定要抢此订单吗?${dingdanItem.isZhiding ? '(指定订单)' : ''}`,
+ success: async function (res) {
+ if (res.confirm) {
+ wx.showLoading({ title: '抢单中...', mask: true });
+ try {
+ const qiangdanRes = await request({
+ url: '/dingdan/qiangdan',
+ method: 'POST',
+ data: { dingdan_id: dingdanItem.dingdan_id }
+ });
+ wx.hideLoading();
+ if (qiangdanRes.data.code === 200 || qiangdanRes.data.code === 0) {
+ that.showQiangdanSuccessEffect();
+ if (app.globalData) {
+ app.globalData.dashouzhuangtai = 0;
+ that.setData({ dashouzhuangtai: 0 });
+ }
+ const newList = that.data.dingdanList.filter(item => item.dingdan_id !== dingdanItem.dingdan_id);
+ that.setData({ dingdanList: newList });
+ } else {
+ wx.showToast({ title: qiangdanRes.data.msg || '抢单失败', icon: 'none' });
+ }
+ } catch (error) {
+ wx.hideLoading();
+ console.error('抢单请求失败:', error);
+ wx.showToast({ title: '网络错误,抢单失败', icon: 'none' });
+ }
+ }
+ }
+ });
+ },
+
+ showQiangdanSuccessEffect() {
+ wx.showToast({ title: '抢单成功!', icon: 'success', duration: 2000 });
+ },
+
+ onViewDetail(e) {
+ const { type, content } = e.currentTarget.dataset;
+ wx.showModal({
+ title: type === 'jieshao' ? '订单介绍' : '订单备注',
+ content: content,
+ showCancel: false,
+ confirmText: '知道了'
+ });
+ },
+
+ onReachBottom() {
+ const now = Date.now();
+ if (now - this.data.lastRefreshTime < this.data.refreshCooldown) {
+ wx.showToast({
+ title: `操作太快,请${Math.ceil((this.data.refreshCooldown - (now - this.data.lastRefreshTime)) / 1000)}秒后再试`,
+ icon: 'none',
+ duration: 1500
+ });
+ return;
+ }
+ if (this.data.isLoading || this.data.isLoadingMore) return;
+ if (this.data.hasMore && !this.data.isLoading && this.data.xuanzhongLeixingId) {
+ this.setData({ page: this.data.page + 1 });
+ this.loadDingdanList(false);
+ }
+ },
+
+ onPullDownRefresh() {
+ const now = Date.now();
+ if (now - this.data.lastRefreshTime < this.data.refreshCooldown) {
+ this.setData({ scrollViewRefreshing: false });
+ wx.showToast({
+ title: `操作太快,请${Math.ceil((this.data.refreshCooldown - (now - this.data.lastRefreshTime)) / 1000)}秒后再试`,
+ icon: 'none',
+ duration: 1500
+ });
+ return;
+ }
+ if (this.data.isLoading) {
+ this.setData({ scrollViewRefreshing: false });
+ return;
+ }
+ this.setData({
+ scrollViewRefreshing: true,
+ lastRefreshTime: now
+ });
+ if (this.data.xuanzhongLeixingId) {
+ this.setData({ page: 1, hasMore: true });
+ this.loadDingdanList(true);
+ } else {
+ this.setData({ scrollViewRefreshing: false });
+ }
+ },
+
+ goToDashouRegister() {
+ wx.navigateTo({
+ url: '/pages/dashouduan/dashouduan'
+ });
+ }
+});
\ No newline at end of file
diff --git a/miniprogram/pages/jiedanchi/jiedanchi.json b/miniprogram/pages/jiedanchi/jiedanchi.json
new file mode 100644
index 0000000..ea51a5d
--- /dev/null
+++ b/miniprogram/pages/jiedanchi/jiedanchi.json
@@ -0,0 +1,9 @@
+{
+ "navigationBarTitleText": "接单池",
+ "enablePullDownRefresh": false,
+ "backgroundTextStyle": "dark",
+ "backgroundColor": "#f5f7fa",
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification"
+ }
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/jiedanchi/jiedanchi.wxml b/miniprogram/pages/jiedanchi/jiedanchi.wxml
new file mode 100644
index 0000000..027df01
--- /dev/null
+++ b/miniprogram/pages/jiedanchi/jiedanchi.wxml
@@ -0,0 +1,263 @@
+
+
+
+
+
+
+
+ 🔒
+ 功能未开启
+ {{unauthorizedMsg}}
+ 请先注册为服务者,即可使用抢单功能
+
+ 立即注册
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{item.jieshao}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 正在刷新...
+ 下拉刷新订单
+
+
+
+
+
+
+
+ 请先选择上方订单类型
+
+
+
+
+ 当前类型暂无待抢订单
+
+
+
+
+
+
+
+
+ {{item.creat_time}}
+
+ 平台订单
+
+
+
+
+ 指定
+
+ {{item.zhiding_nicheng || '指定打手'}}
+ UID: {{item.zhiding_uid || '未知'}}
+
+
+
+
+
+
+ 介绍:
+ {{item.jieshao || '暂无介绍'}}
+ [点击查看]
+
+
+
+
+
+ 备注:
+ {{item.beizhu}}
+ [点击查看]
+
+
+
+
+
+ 💰
+ 分佣
+ {{item.dashou_fencheng || 0}}
+ 元
+
+
+ 立即抢单
+
+
+
+
+
+
+
+
+
+ {{item.creat_time}}
+
+ 商家发单
+
+
+
+
+ 指定
+
+ {{item.zhiding_nicheng || '指定打手'}}
+ UID: {{item.zhiding_uid || '未知'}}
+
+
+
+
+
+ {{item.sjnicheng || '未知商家'}}
+
+
+
+
+ 订单介绍:
+ {{item.jieshao || '暂无介绍'}}
+ [点击查看]
+
+
+
+
+ 商家备注:
+ {{item.beizhu}}
+ [点击查看]
+
+
+
+
+
+ 💰
+ 分佣
+ {{item.dashou_fencheng || 0}}
+ 元
+
+
+ 立即抢单
+
+
+
+
+
+
+
+
+ 加载更多订单中...
+
+
+ 没有更多订单了
+
+
+
+
+
+
+
+ 订单加载中...
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/jiedanchi/jiedanchi.wxss b/miniprogram/pages/jiedanchi/jiedanchi.wxss
new file mode 100644
index 0000000..5784b03
--- /dev/null
+++ b/miniprogram/pages/jiedanchi/jiedanchi.wxss
@@ -0,0 +1,655 @@
+/* pages/qiangdan/qiangdan.wxss */
+.qiangdan-page {
+ height: 100vh;
+ display: flex;
+ flex-direction: column;
+ background-color: #f5f7fa;
+ overflow: hidden;
+ position: relative;
+ }
+
+ /* ====== 授权提示页(机甲风格)====== */
+ .unauthorized-container {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: radial-gradient(ellipse at top, #0b0f1a, #03050a);
+ z-index: 100;
+ }
+
+ .unauthorized-card {
+ width: 600rpx;
+ padding: 60rpx 40rpx;
+ background: linear-gradient(145deg, #1a1f30, #0f1422);
+ border-radius: 60rpx;
+ position: relative;
+ overflow: hidden;
+ border: 2rpx solid rgba(0, 247, 255, 0.3);
+ box-shadow: 0 30rpx 60rpx rgba(0, 0, 0, 0.8), 0 0 30rpx rgba(0, 247, 255, 0.2);
+ }
+
+ .card-glow {
+ position: absolute;
+ top: -20%;
+ left: -20%;
+ width: 140%;
+ height: 140%;
+ background: radial-gradient(circle at 30% 30%, rgba(0, 247, 255, 0.2), transparent 60%);
+ animation: rotateGlow 10s linear infinite;
+ z-index: 0;
+ }
+
+ @keyframes rotateGlow {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+ }
+
+ .card-content-unauth {
+ position: relative;
+ z-index: 2;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ color: #ffffff;
+ }
+
+ .icon-lock {
+ font-size: 100rpx;
+ margin-bottom: 30rpx;
+ background: rgba(255, 255, 255, 0.1);
+ width: 160rpx;
+ height: 160rpx;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border: 4rpx solid #00f7ff;
+ box-shadow: 0 0 50rpx #00f7ff;
+ text-shadow: 0 0 20rpx #00f7ff;
+ }
+
+ .title {
+ font-size: 48rpx;
+ font-weight: 700;
+ letter-spacing: 4rpx;
+ margin-bottom: 20rpx;
+ text-shadow: 0 0 15rpx #00f7ff;
+ background: linear-gradient(135deg, #ffffff, #aaddff);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ }
+
+ .message {
+ font-size: 32rpx;
+ color: #ffaa00;
+ margin-bottom: 16rpx;
+ font-weight: 600;
+ text-shadow: 0 0 10rpx #ffaa00;
+ }
+
+ .description {
+ font-size: 28rpx;
+ color: #a0b0d0;
+ margin-bottom: 50rpx;
+ text-align: center;
+ }
+
+ .btn-register {
+ position: relative;
+ padding: 24rpx 80rpx;
+ background: linear-gradient(145deg, #2a3050, #181e38);
+ border-radius: 60rpx;
+ overflow: hidden;
+ border: 2rpx solid #00f7ff;
+ box-shadow: 0 10rpx 30rpx rgba(0, 247, 255, 0.4);
+ transition: transform 0.2s;
+ }
+
+ .btn-register:active {
+ transform: scale(0.96);
+ }
+
+ .btn-text {
+ font-size: 32rpx;
+ color: #fff;
+ font-weight: 700;
+ letter-spacing: 4rpx;
+ position: relative;
+ z-index: 2;
+ text-shadow: 0 0 10rpx #00f7ff;
+ }
+
+ .btn-shine {
+ position: absolute;
+ top: -50%;
+ left: -50%;
+ width: 200%;
+ height: 200%;
+ background: linear-gradient(
+ to right,
+ rgba(255, 255, 255, 0) 0%,
+ rgba(255, 255, 255, 0.2) 50%,
+ rgba(255, 255, 255, 0) 100%
+ );
+ transform: rotate(30deg);
+ animation: btn-shimmer 2.8s infinite linear;
+ z-index: 1;
+ }
+
+ @keyframes btn-shimmer {
+ 0% { transform: translateX(-100%) rotate(30deg); }
+ 100% { transform: translateX(100%) rotate(30deg); }
+ }
+
+ /* ====== 原有样式(保持不变,仅略作调整以兼容提示页)====== */
+ .leixing-quyu {
+ padding: 30rpx 0 20rpx;
+ background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
+ border-bottom: 1rpx solid #eaeaea;
+ position: relative;
+ z-index: 10;
+ flex-shrink: 0;
+ }
+
+ .leixing-scroll {
+ width: 100%;
+ white-space: nowrap;
+ height: 180rpx;
+ }
+
+ .leixing-container {
+ display: inline-flex;
+ padding: 0 30rpx;
+ }
+
+ .leixing-item {
+ display: inline-flex;
+ flex-direction: column;
+ align-items: center;
+ width: 160rpx;
+ margin-right: 24rpx;
+ position: relative;
+ transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
+ }
+
+ .leixing-active {
+ transform: translateY(-6rpx);
+ }
+
+ .guangyun-effect {
+ position: absolute;
+ top: -12rpx;
+ left: -12rpx;
+ right: -12rpx;
+ bottom: -12rpx;
+ background: radial-gradient(circle at center, rgba(82, 196, 26, 0.15) 0%, transparent 70%);
+ border-radius: 40rpx;
+ z-index: 0;
+ animation: guangyun-pulse 2s infinite ease-in-out;
+ }
+
+ @keyframes guangyun-pulse {
+ 0%, 100% { opacity: 0.6; transform: scale(1); }
+ 50% { opacity: 0.9; transform: scale(1.05); }
+ }
+
+ .leixing-tupian {
+ width: 120rpx;
+ height: 120rpx;
+ border-radius: 28rpx;
+ z-index: 1;
+ box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.08);
+ transition: all 0.3s ease;
+ }
+
+ .leixing-active .leixing-tupian {
+ box-shadow: 0 16rpx 40rpx rgba(82, 196, 26, 0.3);
+ border: 4rpx solid #52c41a;
+ }
+
+ .leixing-jieshao {
+ margin-top: 16rpx;
+ font-size: 24rpx;
+ color: #666;
+ max-width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ text-align: center;
+ z-index: 1;
+ }
+
+ .leixing-active .leixing-jieshao {
+ color: #52c41a;
+ font-weight: 600;
+ }
+
+ .fenge-xian {
+ height: 16rpx;
+ background: linear-gradient(to bottom, #f0f2f5, #f8f9fa);
+ flex-shrink: 0;
+ }
+
+ .dingdan-container {
+ flex: 1;
+ overflow: hidden;
+ }
+
+ .dingdan-scroll-view {
+ height: 100%;
+ -webkit-overflow-scrolling: touch;
+ }
+
+ .refresher-container {
+ height: 100rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .refreshing-text,
+ .pull-down-text {
+ font-size: 26rpx;
+ color: #999;
+ }
+
+ .empty-tip {
+ padding: 120rpx 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .tip-icon {
+ width: 160rpx;
+ height: 160rpx;
+ opacity: 0.4;
+ margin-bottom: 30rpx;
+ }
+
+ .tip-text {
+ font-size: 28rpx;
+ color: #aaa;
+ }
+
+ .dingdan-card {
+ margin: 20rpx 24rpx;
+ border-radius: 20rpx;
+ padding: 28rpx;
+ position: relative;
+ overflow: hidden;
+ box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.06);
+ transition: all 0.2s ease;
+ border: 1rpx solid rgba(0, 0, 0, 0.08);
+ }
+
+ .dingdan-card:active {
+ transform: scale(0.995);
+ }
+
+ .pingtai-card {
+ background: linear-gradient(145deg, #f8fff8 0%, #f0f9f0 100%);
+ }
+
+ .shangjia-card {
+ background: linear-gradient(145deg, #f9f0ff 0%, #f3e8ff 100%);
+ }
+
+ .card-top {
+ display: flex;
+ align-items: center;
+ margin-bottom: 20rpx;
+ padding-bottom: 16rpx;
+ border-bottom: 1rpx dashed #e0e0e0;
+ }
+
+ .creat-time {
+ font-size: 26rpx;
+ color: #555;
+ font-weight: 500;
+ }
+
+ .top-line {
+ flex: 1;
+ height: 1rpx;
+ background: linear-gradient(to right, transparent, #ddd, transparent);
+ margin: 0 20rpx;
+ }
+
+ .pingtai-tag {
+ font-size: 24rpx;
+ color: #666;
+ padding: 6rpx 16rpx;
+ background: rgba(0, 0, 0, 0.04);
+ border-radius: 20rpx;
+ }
+
+ .zhiding-row {
+ display: flex;
+ align-items: center;
+ margin-bottom: 20rpx;
+ padding: 10rpx 16rpx;
+ background: rgba(255, 77, 79, 0.08);
+ border-radius: 12rpx;
+ border-left: 4rpx solid #ff4d4f;
+ gap: 10rpx;
+ }
+
+ .zhiding-icon {
+ font-size: 26rpx;
+ margin-right: 4rpx;
+ }
+
+ .zhiding-avatar {
+ width: 48rpx;
+ height: 48rpx;
+ border-radius: 50%;
+ border: 2rpx solid #ff4d4f;
+ object-fit: cover;
+ }
+
+ .zhiding-nicheng {
+ font-size: 24rpx;
+ color: #ff4d4f;
+ font-weight: 500;
+ max-width: 120rpx;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ .zhiding-uid {
+ font-size: 24rpx;
+ color: #ff4d4f;
+ font-weight: 400;
+ flex: 1;
+ text-align: right;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ .card-content {
+ display: flex;
+ margin-bottom: 20rpx;
+ }
+
+ .neirong-tupian {
+ width: 140rpx;
+ height: 140rpx;
+ border-radius: 50%;
+ margin-right: 24rpx;
+ flex-shrink: 0;
+ box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.08);
+ object-fit: cover;
+ }
+
+ .jieshao-box {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ min-height: 140rpx;
+ padding: 16rpx 0;
+ }
+
+ .jieshao-label {
+ font-size: 26rpx;
+ color: #333;
+ font-weight: 600;
+ margin-bottom: 8rpx;
+ }
+
+ .jieshao-text {
+ font-size: 26rpx;
+ color: #666;
+ line-height: 1.4;
+ }
+
+ .jieshao-ellipsis .jieshao-text {
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 3;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .shangjia-info {
+ display: flex;
+ align-items: center;
+ margin-bottom: 20rpx;
+ }
+
+ .shangjia-touxiang {
+ width: 90rpx;
+ height: 90rpx;
+ border-radius: 50%;
+ margin-right: 20rpx;
+ border: 2rpx solid rgba(114, 46, 209, 0.15);
+ box-shadow: 0 6rpx 18rpx rgba(114, 46, 209, 0.12);
+ object-fit: cover;
+ }
+
+ .shangjia-nicheng {
+ font-size: 28rpx;
+ color: #333;
+ font-weight: 600;
+ }
+
+ .shangjia-jieshao,
+ .shangjia-beizhu {
+ margin-bottom: 16rpx;
+ padding: 14rpx 18rpx;
+ background: rgba(255, 255, 255, 0.6);
+ border-radius: 12rpx;
+ }
+
+ .shangjia-jieshao .jieshao-label,
+ .shangjia-beizhu .beizhu-label {
+ display: block;
+ margin-bottom: 6rpx;
+ font-size: 24rpx;
+ }
+
+ .shangjia-jieshao .jieshao-text,
+ .shangjia-beizhu .beizhu-text {
+ font-size: 24rpx;
+ color: #555;
+ line-height: 1.4;
+ }
+
+ .shangjia-jieshao.jieshao-ellipsis .jieshao-text {
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .shangjia-beizhu.beizhu-ellipsis .beizhu-text {
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .beizhu-box {
+ margin-bottom: 20rpx;
+ padding: 14rpx 18rpx;
+ background: rgba(255, 255, 255, 0.6);
+ border-radius: 12rpx;
+ }
+
+ .beizhu-label {
+ font-size: 24rpx;
+ color: #333;
+ font-weight: 600;
+ margin-bottom: 6rpx;
+ display: block;
+ }
+
+ .beizhu-text {
+ font-size: 24rpx;
+ color: #666;
+ line-height: 1.4;
+ }
+
+ .beizhu-ellipsis .beizhu-text {
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .view-more {
+ display: block;
+ font-size: 22rpx;
+ color: #1890ff;
+ margin-top: 6rpx;
+ text-align: right;
+ }
+
+ .card-bottom {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding-top: 20rpx;
+ border-top: 1rpx solid rgba(0, 0, 0, 0.06);
+ }
+
+ .fenyong-box {
+ display: flex;
+ align-items: baseline;
+ }
+
+ .fenyong-icon {
+ font-size: 30rpx;
+ margin-right: 10rpx;
+ }
+
+ .fenyong-text {
+ font-size: 24rpx;
+ color: #999;
+ margin-right: 6rpx;
+ }
+
+ .fenyong-price {
+ font-size: 40rpx;
+ color: #ff4d4f;
+ font-weight: 700;
+ margin-right: 4rpx;
+ }
+
+ .fenyong-unit {
+ font-size: 24rpx;
+ color: #999;
+ }
+
+ .qiangdan-btn {
+ position: relative;
+ padding: 18rpx 40rpx;
+ border-radius: 50rpx;
+ overflow: hidden;
+ box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.12);
+ }
+
+ .pingtai-btn {
+ background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%);
+ }
+
+ .shangjia-btn {
+ background: linear-gradient(135deg, #722ed1 0%, #9254de 100%);
+ }
+
+ .btn-text {
+ font-size: 28rpx;
+ color: white;
+ font-weight: 600;
+ letter-spacing: 1rpx;
+ position: relative;
+ z-index: 2;
+ }
+
+ .btn-shine {
+ position: absolute;
+ top: -50%;
+ left: -50%;
+ width: 200%;
+ height: 200%;
+ background: linear-gradient(
+ to right,
+ rgba(255, 255, 255, 0) 0%,
+ rgba(255, 255, 255, 0.3) 50%,
+ rgba(255, 255, 255, 0) 100%
+ );
+ transform: rotate(30deg);
+ animation: btn-shimmer 3s infinite linear;
+ z-index: 1;
+ }
+
+ @keyframes btn-shimmer {
+ 0% { transform: translateX(-100%) rotate(30deg); }
+ 100% { transform: translateX(100%) rotate(30deg); }
+ }
+
+ .qiangdan-btn:active {
+ transform: scale(0.96);
+ }
+
+ .loading-more,
+ .no-more {
+ padding: 30rpx 0;
+ text-align: center;
+ }
+
+ .loading-text {
+ font-size: 26rpx;
+ color: #999;
+ }
+
+ .no-more-text {
+ font-size: 26rpx;
+ color: #ccc;
+ }
+
+ .loading-mask {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(255, 255, 255, 0.9);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ z-index: 9999;
+ }
+
+ .loading-spinner {
+ width: 70rpx;
+ height: 70rpx;
+ border: 6rpx solid #f0f0f0;
+ border-top: 6rpx solid #52c41a;
+ border-radius: 50%;
+ animation: spin 1s linear infinite;
+ margin-bottom: 24rpx;
+ }
+
+ @keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+ }
+
+ .loading-mask-text {
+ font-size: 28rpx;
+ color: #666;
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/jiedanchi2/jiedanchi2.js b/miniprogram/pages/jiedanchi2/jiedanchi2.js
new file mode 100644
index 0000000..0e33db9
--- /dev/null
+++ b/miniprogram/pages/jiedanchi2/jiedanchi2.js
@@ -0,0 +1,66 @@
+// pages/jiedanchi2/jiedanchi2.js
+Page({
+
+ /**
+ * 页面的初始数据
+ */
+ data: {
+
+ },
+
+ /**
+ * 生命周期函数--监听页面加载
+ */
+ onLoad(options) {
+
+ },
+
+ /**
+ * 生命周期函数--监听页面初次渲染完成
+ */
+ onReady() {
+
+ },
+
+ /**
+ * 生命周期函数--监听页面显示
+ */
+ onShow() {
+
+ },
+
+ /**
+ * 生命周期函数--监听页面隐藏
+ */
+ onHide() {
+
+ },
+
+ /**
+ * 生命周期函数--监听页面卸载
+ */
+ onUnload() {
+
+ },
+
+ /**
+ * 页面相关事件处理函数--监听用户下拉动作
+ */
+ onPullDownRefresh() {
+
+ },
+
+ /**
+ * 页面上拉触底事件的处理函数
+ */
+ onReachBottom() {
+
+ },
+
+ /**
+ * 用户点击右上角分享
+ */
+ onShareAppMessage() {
+
+ }
+})
\ No newline at end of file
diff --git a/miniprogram/pages/jiedanchi2/jiedanchi2.json b/miniprogram/pages/jiedanchi2/jiedanchi2.json
new file mode 100644
index 0000000..3928faa
--- /dev/null
+++ b/miniprogram/pages/jiedanchi2/jiedanchi2.json
@@ -0,0 +1,3 @@
+{
+ "usingComponents": {}
+}
\ No newline at end of file
diff --git a/miniprogram/pages/jiedanchi2/jiedanchi2.wxml b/miniprogram/pages/jiedanchi2/jiedanchi2.wxml
new file mode 100644
index 0000000..fdd9cd8
--- /dev/null
+++ b/miniprogram/pages/jiedanchi2/jiedanchi2.wxml
@@ -0,0 +1,2 @@
+
+pages/jiedanchi2/jiedanchi2.wxml
\ No newline at end of file
diff --git a/miniprogram/pages/jiedanchi2/jiedanchi2.wxss b/miniprogram/pages/jiedanchi2/jiedanchi2.wxss
new file mode 100644
index 0000000..fae36a0
--- /dev/null
+++ b/miniprogram/pages/jiedanchi2/jiedanchi2.wxss
@@ -0,0 +1 @@
+/* pages/jiedanchi2/jiedanchi2.wxss */
\ No newline at end of file
diff --git a/miniprogram/pages/jinpaids/jinpaids.js b/miniprogram/pages/jinpaids/jinpaids.js
new file mode 100644
index 0000000..aef8cef
--- /dev/null
+++ b/miniprogram/pages/jinpaids/jinpaids.js
@@ -0,0 +1,346 @@
+// pages/jinpaids/jinpaids.js
+import request from '../../utils/request.js'
+import popupService from '../../services/popupService.js'
+
+const app = getApp()
+
+Page({
+ data: {
+ // OSS地址
+ ossImageUrl: '',
+
+ // 图标路径
+ icons: {
+ jinpai: '',
+ refresh: '',
+ copy: '',
+ qiangdan: '',
+ chongzhi: '',
+ dingdan: '',
+ chufa: '',
+ paihang: '',
+ xiugai: '',
+ tixian: '',
+ rightArrow: ''
+ },
+
+ // 用户基本信息
+ uid: '',
+ touxiang: '',
+ avatarUrl: '',
+ dashouNicheng: '',
+
+ // 打手核心数据
+ chenghao: '',
+ yongjin: '0.00',
+ zonge: '0.00',
+ yajin: '0.00',
+ jinfen: '0',
+ chengjiaoliang: '0',
+ zaixianZhuangtai: 0,
+ dashouzhuangtai: '',
+ clumber: [],
+
+ // 金牌专属数据
+ jiedanZongliang: '0',
+ jinriJiedan: '0.00',
+ zuijinTixian: '0.00',
+
+ // 页面状态
+ isLoading: false,
+ lastRefreshTime: 0,
+ canRefresh: true,
+
+ // 计算数据
+ chengjiaoRate: '0%',
+ chengjiaoAngle: 0,
+ tixianProgress: 0,
+
+ // 邀请人缓存(管事)
+ inviterCache: null,
+ chenghaoList: [] // 新增
+ },
+
+ onLoad() {
+ const ossUrl = app.globalData.ossImageUrl || ''
+ const iconBase = 'beijing/tubiao/'
+
+ this.setData({
+ ossImageUrl: ossUrl,
+ 'icons.jinpai': ossUrl + iconBase + 'jinpai.png',
+ 'icons.refresh': ossUrl + iconBase + 'shuaxin.png',
+ 'icons.copy': ossUrl + iconBase + 'fuzhi.png',
+ 'icons.qiangdan': ossUrl + iconBase + 'qiangdan.png',
+ 'icons.chongzhi': ossUrl + iconBase + 'chongzhi.png',
+ 'icons.dingdan': ossUrl + iconBase + 'dingdan.png',
+ 'icons.chufa': ossUrl + iconBase + 'chufa.png',
+ 'icons.paihang': ossUrl + iconBase + 'paihang.png',
+ 'icons.xiugai': ossUrl + iconBase + 'xiugai.png',
+ 'icons.tixian': ossUrl + iconBase + 'tixian.png',
+ 'icons.rightArrow': ossUrl + iconBase + 'jiantou.png',
+ 'icons.guize': ossUrl + iconBase + 'guize.png',
+ 'icons.kaohe': ossUrl + iconBase + 'kaohe.png',
+ })
+
+ this.loadUserInfo()
+ this.getJinpaiInfo()
+
+ if (!app.globalData.hasShownPopupOnColdStart) {
+ app.globalData.hasShownPopupOnColdStart = true;
+ setTimeout(() => {
+ popupService.checkAndShow(this, 'jinpaids');
+ }, 300);
+ }
+ },
+
+ onHide() {
+ const popupComp = this.selectComponent('#popupNotice');
+ if (popupComp && popupComp.cleanup) {
+ popupComp.cleanup();
+ }
+ },
+
+ onShow() {
+ this.registerNotificationComponent()
+ this.refreshJinpaiInfo()
+ this.fetchChenghaoList() // 新增
+ const inviterCache = wx.getStorageSync('inviterCache') || null
+ this.setData({ inviterCache })
+ },
+
+ registerNotificationComponent() {
+ const notificationComp = this.selectComponent('#global-notification')
+ if (notificationComp && notificationComp.showNotification) {
+ app.globalData.globalNotification = {
+ show: (data) => notificationComp.showNotification(data),
+ hide: () => notificationComp.hideNotification()
+ }
+ }
+ },
+
+ loadUserInfo() {
+ const uid = wx.getStorageSync('uid') || ''
+ const touxiang = wx.getStorageSync('touxiang') || ''
+ const ossUrl = app.globalData.ossImageUrl || ''
+ const defaultAvatar = ossUrl + (app.globalData.morentouxiang || 'avatar/default.jpg')
+
+ let avatarUrl = defaultAvatar
+ if (touxiang) {
+ avatarUrl = ossUrl + (touxiang.startsWith('/') ? touxiang : '/' + touxiang)
+ }
+
+ const dashouNicheng = app.globalData.dashouNicheng || wx.getStorageSync('nicheng') || '金牌选手'
+
+ this.setData({ uid, touxiang, avatarUrl, dashouNicheng })
+ },
+
+ async getJinpaiInfo() {
+ this.setData({ isLoading: true })
+
+ try {
+ const res = await request({
+ url: '/yonghu/dashouxinxi',
+ method: 'POST'
+ })
+
+ if (res && res.data.code === 200) {
+ const data = res.data.data
+
+ const updateData = {
+ dashouNicheng: data.dashounicheng || '',
+ zhanghaoStatus: data.zhanghaostatus || '',
+ yongjin: data.yongjin || '0.00',
+ zonge: data.zonge || '0.00',
+ yajin: data.yajin || '0.00',
+ chenghao: data.chenghao || '',
+ jinfen: data.jifen || '0',
+ chengjiaoliang: data.chengjiaoliang || '0',
+ zaixianZhuangtai: data.zaixianzhuangtai || 0,
+ dashouzhuangtai: data.dashouzhuangtai || '',
+ clumber: data.clumber || [],
+ jiedanZongliang: data.jiedanZongliang || '0',
+ jinriJiedan: data.jinriJiedan || '0.00',
+ zuijinTixian: data.zuijinTixian || '0.00',
+ }
+
+ const jiedanTotal = parseFloat(updateData.jiedanZongliang) || 0
+ const chengjiaoTotal = parseFloat(updateData.chengjiaoliang) || 0
+ const rateNumber = jiedanTotal > 0 ? (chengjiaoTotal / jiedanTotal) * 100 : 0
+ const chengjiaoRate = rateNumber.toFixed(1) + '%'
+ const chengjiaoAngle = rateNumber * 3.6
+
+ const zuijin = parseFloat(updateData.zuijinTixian) || 0
+ const zong = parseFloat(updateData.jinriJiedan) || 0
+ const tixianProgress = zong > 0 ? Math.min((zuijin / zong) * 100, 100) : 0
+
+ const globalUpdate = {
+ dashouNicheng: updateData.dashouNicheng,
+ zhanghaoStatus: updateData.zhanghaoStatus,
+ yongjin: updateData.yongjin,
+ zonge: updateData.zonge,
+ yajin: updateData.yajin,
+ chenghao: updateData.chenghao,
+ jinfen: updateData.jinfen,
+ chengjiaoliang: updateData.chengjiaoliang,
+ zaixianZhuangtai: updateData.zaixianZhuangtai,
+ dashouzhuangtai: updateData.dashouzhuangtai,
+ clumber: updateData.clumber,
+ }
+ Object.assign(app.globalData, globalUpdate)
+
+ wx.setStorageSync('nicheng', updateData.dashouNicheng)
+ if (data.touxiang) {
+ wx.setStorageSync('touxiang', data.touxiang)
+ }
+
+ this.setData({
+ ...updateData,
+ chengjiaoRate,
+ chengjiaoAngle,
+ tixianProgress: Math.round(tixianProgress),
+ isLoading: false
+ })
+
+ this.checkJinpaiAndRedirect(updateData.chenghao)
+
+ } else {
+ wx.showToast({ title: res?.data?.msg || '获取信息失败', icon: 'none' })
+ this.setData({ isLoading: false })
+ }
+ } catch (error) {
+ console.error('获取金牌信息失败:', error)
+ wx.showToast({ title: '网络错误', icon: 'none' })
+ this.setData({ isLoading: false })
+ }
+ },
+
+ refreshJinpaiInfo() {
+ const now = Date.now()
+ const lastTime = this.data.lastRefreshTime
+ const canRefresh = this.data.canRefresh
+ if (!canRefresh || (now - lastTime < 3000)) return
+
+
+ this.setData({ lastRefreshTime: now, canRefresh: false })
+ this.getJinpaiInfo()
+ this.fetchChenghaoList() // 新增
+ setTimeout(() => this.setData({ canRefresh: true }), 3000)
+ },
+
+ checkJinpaiAndRedirect(chenghao) {
+ if (chenghao !== '金牌选手') {
+ wx.setStorageSync('isJinpai', 0)
+ wx.redirectTo({ url: '/pages/dashouduan/dashouduan' })
+ } else {
+ wx.setStorageSync('isJinpai', 1)
+ }
+ },
+
+ // 联系管事(与打手端完全一致,增加容错)
+ async contactInviter() {
+ wx.showLoading({ title: '获取管事信息...', mask: true })
+ try {
+ const res = await request({ url: '/yonghu/hqwdyqr', method: 'POST' })
+ if (res && res.data.code === 200) {
+ const inviter = res.data.data
+ const ossImageUrl = getApp().globalData.ossImageUrl || ''
+ const fullAvatar = inviter.touxiang
+ ? (inviter.touxiang.startsWith('http') ? inviter.touxiang : ossImageUrl + inviter.touxiang)
+ : ''
+
+ // 缓存邀请人信息
+ const cacheData = {
+ uid: inviter.uid,
+ nicheng: inviter.nicheng || '',
+ avatar: fullAvatar
+ }
+ wx.setStorageSync('inviterCache', cacheData)
+ this.setData({ inviterCache: cacheData })
+
+ // 确保打手身份并连接
+ const app = getApp()
+ const myUid = wx.getStorageSync('uid')
+
+ // 如果当前不是打手身份,则切换并重连
+ if (app.globalData.currentRole !== 'dashou') {
+ app.globalData.currentRole = 'dashou'
+ wx.setStorageSync('currentRole', 'dashou')
+ if (app.switchRoleAndReconnect) {
+ await app.switchRoleAndReconnect('dashou')
+ } else if (app.connectWithIdentity) {
+ // 降级方案:直接用打手身份连接
+ await app.connectWithIdentity('dashou', 'Ds' + myUid, false)
+ }
+ } else {
+ // 已为打手,确保连接存活
+ if (app.connectForCurrentRole) {
+ app.connectForCurrentRole()
+ } else if (app.ensureConnection) {
+ app.ensureConnection()
+ }
+ }
+
+ // 构造私聊参数
+ const param = {
+ toUserId: 'Gs' + inviter.uid,
+ toName: inviter.nicheng || '管事',
+ toAvatar: fullAvatar
+ }
+ wx.navigateTo({
+ url: '/pages/liaotian/liaotian?data=' + encodeURIComponent(JSON.stringify(param)),
+ fail: (err) => {
+ console.error('跳转私聊失败', err)
+ wx.showToast({ title: '打开聊天失败', icon: 'none' })
+ }
+ })
+ } else {
+ wx.showToast({ title: res?.data?.msg || '获取失败', icon: 'none' })
+ }
+ } catch (e) {
+ console.error('联系管事出错:', e)
+ wx.showToast({ title: '网络错误', icon: 'none' })
+ } finally {
+ wx.hideLoading()
+ }
+ },
+
+ copyUid() {
+ const { uid } = this.data
+ if (!uid) return wx.showToast({ title: 'UID不存在', icon: 'none' })
+ wx.setClipboardData({
+ data: uid,
+ success: () => wx.showToast({ title: '已复制UID', icon: 'success' })
+ })
+ },
+
+ previewAvatar() {
+ if (this.data.avatarUrl) wx.previewImage({ urls: [this.data.avatarUrl] })
+ },
+
+ goToWithdraw() { wx.navigateTo({ url: '/pages/tixian/tixian' }) },
+ goToReceiveOrder() { wx.navigateTo({ url: '/pages/jiedan/jiedan' }) },
+ goToRecharge() { wx.navigateTo({ url: '/pages/dashouchongzhi/dashouchongzhi' }) },
+ goToMyOrders() { wx.navigateTo({ url: '/pages/dashoudingdan/dashoudingdan' }) },
+ goToMyPunishment() { wx.navigateTo({ url: '/pages/cfss/cfss/cfss' }) },
+ goToRanking() { wx.navigateTo({ url: '/pages/dashoupaihang/dashoupaihang' }) },
+ goToModifyInfo() { wx.navigateTo({ url: '/pages/dashouxiugai/dashouxiugai' }) },
+ goToRules() { wx.navigateTo({ url: '/pages/dashouguize/dashouguize' }) },
+ // 跳转到考核金牌
+goToKaohe() {
+ wx.navigateTo({ url: '/pages/kaohe_jinpai/kaohe_jinpai' });
+ },
+
+ async fetchChenghaoList() {
+ try {
+ const res = await request({
+ url: '/dengji/dsbqhq',
+ method: 'POST'
+ });
+ if (res && res.data.code === 0 && res.data.data) {
+ this.setData({ chenghaoList: res.data.data.chenghao_list || [] });
+ }
+ } catch (e) {
+ console.error('获取称号标签失败', e);
+ }
+},
+})
\ No newline at end of file
diff --git a/miniprogram/pages/jinpaids/jinpaids.json b/miniprogram/pages/jinpaids/jinpaids.json
new file mode 100644
index 0000000..f3d656c
--- /dev/null
+++ b/miniprogram/pages/jinpaids/jinpaids.json
@@ -0,0 +1,9 @@
+{
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification",
+ "chenghao-tag": "/components/chenghao-tag/chenghao-tag"
+ },
+ "navigationBarTitleText": "金牌用户",
+ "navigationBarBackgroundColor": "#0B2A4A",
+ "navigationBarTextStyle": "white"
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/jinpaids/jinpaids.wxml b/miniprogram/pages/jinpaids/jinpaids.wxml
new file mode 100644
index 0000000..5912b19
--- /dev/null
+++ b/miniprogram/pages/jinpaids/jinpaids.wxml
@@ -0,0 +1,193 @@
+
+
+
+
+
+
+
+
+
+ 金牌选手
+
+
+
+ 刷新
+
+
+
+
+
+
+
+
+
+
+
+ {{dashouNicheng}}
+
+ {{zaixianZhuangtai == 1 ? '在线' : '离线'}}
+
+
+
+ UID
+ {{uid}}
+
+
+
+ 接单状态:
+
+ {{dashouzhuangtai == '1' ? '可接单' : '暂停接单'}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 可提现金额
+
+ {{yongjin}}
+ 元
+
+
+
+
+ 立即提现
+
+
+
+
+
+
+
+ 总赚取
+ {{zonge}}元
+
+
+ 保证金
+ {{yajin}}元
+
+
+ 积分
+ {{jinfen}}
+
+
+ 接单总量
+ {{jiedanZongliang}}
+
+
+ 成交总量
+ {{chengjiaoliang}}
+
+
+ 今日接单
+ {{jinriJiedan}}元
+
+
+
+
+
+
+ 成交率
+
+
+ {{chengjiaoRate}}
+
+
+
+ 最近提现占比
+
+
+ {{tixianProgress}}%
+
+
+ 最近提现:{{zuijinTixian}}元
+ 今日赚取:{{jinriJiedan}}元
+
+
+
+
+
+
+
+
+
+ {{inviterCache.nicheng || '我的管事'}}
+ ID: {{inviterCache.uid}}
+
+
+ 联系我的邀请人
+
+
+
+
+
+ 我的管事
+ 点击联系
+
+
+ 联系我的邀请人
+
+
+
+
+
+
+
+ 抢单大厅
+
+
+
+ 充值会员保证金
+
+
+
+ 我的订单
+
+
+
+ 我的处罚
+
+
+
+ 排行榜
+
+
+
+ 修改资料
+
+
+
+ 用户规则
+
+
+
+ 考核金牌
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/jinpaids/jinpaids.wxss b/miniprogram/pages/jinpaids/jinpaids.wxss
new file mode 100644
index 0000000..162eae6
--- /dev/null
+++ b/miniprogram/pages/jinpaids/jinpaids.wxss
@@ -0,0 +1,610 @@
+/* pages/jinpaids/jinpaids.wxss - 金牌打手机甲风格(CSS变量版) */
+
+/* ========== 全局 ========== */
+page {
+ background: #0B1A2F;
+ min-height: 100vh;
+ font-family: 'Avenir', 'PingFang SC', 'Helvetica Neue', sans-serif;
+ color: #fff;
+ }
+
+ .bg-grid {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-image:
+ linear-gradient(rgba(0, 160, 255, 0.1) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(0, 160, 255, 0.1) 1px, transparent 1px);
+ background-size: 50rpx 50rpx;
+ pointer-events: none;
+ z-index: 0;
+ }
+
+ .bg-energy {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: radial-gradient(circle at 20% 30%, rgba(0,200,255,0.1) 0%, transparent 40%),
+ radial-gradient(circle at 80% 70%, rgba(0,100,255,0.1) 0%, transparent 40%);
+ animation: energyFlow 20s linear infinite;
+ pointer-events: none;
+ z-index: 0;
+ }
+
+ @keyframes energyFlow {
+ 0% { opacity: 0.3; }
+ 50% { opacity: 0.6; }
+ 100% { opacity: 0.3; }
+ }
+
+ .page-container {
+ position: relative;
+ z-index: 2;
+ min-height: 100vh;
+ padding: 30rpx 25rpx;
+ box-sizing: border-box;
+ }
+
+ /* ========== 顶部栏 ========== */
+ .top-bar {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 30rpx;
+ margin-top: 20rpx;
+ }
+ .jinpai-badge {
+ display: flex;
+ align-items: center;
+ }
+ .jinpai-icon {
+ width: 60rpx;
+ height: 60rpx;
+ margin-right: 15rpx;
+ }
+ /* 火苗图标燃烧动画 */
+ .fire-icon {
+ animation: fireBurn 0.8s infinite alternate ease-in-out;
+ filter: drop-shadow(0 0 10rpx #ffaa00);
+ }
+ @keyframes fireBurn {
+ 0% { transform: scale(1) rotate(-3deg); filter: drop-shadow(0 0 10rpx #ffaa00); }
+ 25% { transform: scale(1.1) rotate(2deg); filter: drop-shadow(0 0 20rpx #ff5500); }
+ 50% { transform: scale(1.05) rotate(-2deg); filter: drop-shadow(0 0 30rpx #ffaa00); }
+ 75% { transform: scale(1.15) rotate(3deg); filter: drop-shadow(0 0 40rpx #ffaa00); }
+ 100% { transform: scale(1) rotate(-3deg); filter: drop-shadow(0 0 10rpx #ffaa00); }
+ }
+ .jinpai-title {
+ font-size: 40rpx;
+ font-weight: bold;
+ color: #ffaa00;
+ text-shadow: 0 0 30rpx #ffaa00, 0 0 60rpx #ffaa00;
+ letter-spacing: 2rpx;
+ }
+ .refresh-btn {
+ display: flex;
+ align-items: center;
+ background: rgba(0,40,70,0.8);
+ border: 2rpx solid #00a6ff;
+ clip-path: polygon(0 0, 100% 0, 90% 100%, 0 100%);
+ padding: 8rpx 25rpx;
+ }
+ .refresh-icon {
+ width: 40rpx;
+ height: 40rpx;
+ margin-right: 10rpx;
+ }
+ .refresh-text {
+ font-size: 26rpx;
+ color: #00a6ff;
+ }
+ .refresh-btn:active {
+ opacity: 0.8;
+ }
+
+ /* ========== 用户信息 ========== */
+ .user-section {
+ display: flex;
+ background: rgba(0,30,50,0.5);
+ backdrop-filter: blur(20px);
+ border: 2rpx solid #00a6ff;
+ clip-path: polygon(0 0, 100% 0, 95% 100%, 0 100%);
+ padding: 30rpx;
+ margin-bottom: 40rpx;
+ }
+ .avatar-wrap {
+ position: relative;
+ width: 120rpx;
+ height: 120rpx;
+ margin-right: 30rpx;
+ }
+ .avatar {
+ width: 100%;
+ height: 100%;
+ border-radius: 50%;
+ border: 4rpx solid #00a6ff;
+ box-shadow: 0 0 50rpx #00a6ff;
+ object-fit: cover;
+ }
+ .avatar-ring {
+ position: absolute;
+ top: -10rpx;
+ left: -10rpx;
+ right: -10rpx;
+ bottom: -10rpx;
+ border-radius: 50%;
+ border: 2rpx solid #00a6ff;
+ border-top-color: transparent;
+ border-bottom-color: transparent;
+ animation: ringRotate 6s linear infinite;
+ }
+ @keyframes ringRotate {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+ }
+ .user-info {
+ flex: 1;
+ }
+ .name-row {
+ display: flex;
+ align-items: center;
+ margin-bottom: 10rpx;
+ }
+ .nickname {
+ font-size: 40rpx;
+ font-weight: bold;
+ color: #fff;
+ margin-right: 15rpx;
+ max-width: 250rpx;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+ .status-tag {
+ font-size: 22rpx;
+ padding: 4rpx 16rpx;
+ border-radius: 30rpx;
+ background: rgba(255,255,255,0.1);
+ border: 1rpx solid currentColor;
+ }
+ .status-tag.online { color: #00ff80; border-color: #00ff80; }
+ .status-tag.offline { color: #aaa; border-color: #aaa; }
+ .uid-row {
+ display: flex;
+ align-items: center;
+ background: rgba(0,50,80,0.6);
+ border-left: 4rpx solid #00a6ff;
+ padding: 8rpx 20rpx;
+ margin-bottom: 10rpx;
+ width: fit-content;
+ }
+ .uid-label {
+ font-size: 24rpx;
+ color: #9cf;
+ margin-right: 10rpx;
+ }
+ .uid-value {
+ font-size: 24rpx;
+ color: #fff;
+ font-family: monospace;
+ margin-right: 15rpx;
+ }
+ .copy-icon {
+ width: 30rpx;
+ height: 30rpx;
+ }
+ .order-status {
+ display: flex;
+ align-items: center;
+ }
+ .status-label {
+ font-size: 24rpx;
+ color: #9cf;
+ margin-right: 8rpx;
+ }
+ .status-value {
+ font-size: 24rpx;
+ color: #aaa;
+ }
+ .status-value.active {
+ color: #00ff80;
+ text-shadow: 0 0 10rpx #00ff80;
+ }
+
+ /* ========== 核心资产卡 ========== */
+ .asset-card {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ background: linear-gradient(135deg, #0A1F3A, #0B2A4A);
+ border: 2rpx solid #00a6ff;
+ clip-path: polygon(0 0, 100% 0, 95% 100%, 0 100%);
+ padding: 30rpx 30rpx;
+ margin-bottom: 30rpx;
+ box-shadow: 0 0 40rpx #00a6ff;
+ }
+ .asset-left {
+ flex: 1;
+ }
+ .asset-label {
+ font-size: 28rpx;
+ color: #9cf;
+ display: block;
+ margin-bottom: 10rpx;
+ }
+ .asset-value-group {
+ display: flex;
+ align-items: baseline;
+ }
+ .asset-number {
+ font-size: 68rpx;
+ font-weight: bold;
+ color: #fff;
+ text-shadow: 0 0 30rpx #00a6ff, 0 0 60rpx #00a6ff;
+ }
+ .asset-unit {
+ font-size: 32rpx;
+ color: #00a6ff;
+ margin-left: 10rpx;
+ }
+ .asset-right {
+ display: flex;
+ align-items: center;
+ background: rgba(255,255,255,0.1);
+ border: 2rpx solid #ffaa00;
+ clip-path: polygon(0 0, 100% 0, 90% 100%, 0 100%);
+ padding: 15rpx 30rpx;
+ position: relative;
+ overflow: hidden;
+ }
+ .withdraw-icon {
+ width: 50rpx;
+ height: 50rpx;
+ margin-right: 10rpx;
+ }
+ .withdraw-text {
+ font-size: 32rpx;
+ font-weight: bold;
+ color: #ffaa00;
+ }
+ .withdraw-glow {
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
+ animation: scan 2s infinite;
+ }
+ @keyframes scan {
+ 0% { left: -100%; }
+ 100% { left: 100%; }
+ }
+ .asset-right:active {
+ transform: scale(0.96);
+ }
+
+ /* ========== 统计网格 ========== */
+ .stats-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 20rpx;
+ margin-bottom: 40rpx;
+ }
+ .stat-item {
+ background: rgba(0,30,50,0.7);
+ border: 1rpx solid #00a6ff;
+ clip-path: polygon(0 0, 100% 0, 90% 100%, 0 100%);
+ padding: 20rpx 10rpx;
+ text-align: center;
+ }
+ .stat-label {
+ font-size: 24rpx;
+ color: #9cf;
+ display: block;
+ margin-bottom: 10rpx;
+ }
+ .stat-value {
+ font-size: 32rpx;
+ font-weight: bold;
+ color: #fff;
+ }
+
+ /* ========== 图形化数据(使用CSS变量) ========== */
+ .graphic-panel {
+ background: rgba(0,30,50,0.6);
+ backdrop-filter: blur(15px);
+ border: 2rpx solid #00a6ff;
+ clip-path: polygon(0 0, 100% 0, 98% 100%, 0 100%);
+ padding: 30rpx;
+ margin-bottom: 40rpx;
+ display: flex;
+ justify-content: space-around;
+ flex-wrap: wrap;
+ }
+ .chart-item {
+ width: 45%;
+ text-align: center;
+ }
+ .chart-title {
+ font-size: 28rpx;
+ color: #9cf;
+ margin-bottom: 15rpx;
+ text-transform: uppercase;
+ border-left: 4rpx solid #00a6ff;
+ padding-left: 15rpx;
+ text-align: left;
+ }
+
+ /* 环形进度条(使用CSS变量 --progress-angle) */
+ .ring-chart {
+ position: relative;
+ width: 200rpx;
+ height: 200rpx;
+ margin: 0 auto;
+ border-radius: 50%;
+ overflow: hidden;
+ box-shadow: 0 0 40rpx #00a6ff;
+ }
+ .ring-progress {
+ width: 100%;
+ height: 100%;
+ background: conic-gradient(#00a6ff 0deg, #00a6ff var(--progress-angle), #1a3a5a var(--progress-angle), #1a3a5a 360deg);
+ border-radius: 50%;
+ }
+ .ring-inner {
+ position: absolute;
+ top: 15rpx;
+ left: 15rpx;
+ right: 15rpx;
+ bottom: 15rpx;
+ background: #0a1a2a;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 40rpx;
+ font-weight: bold;
+ color: #00a6ff;
+ box-shadow: inset 0 0 20rpx rgba(0,0,0,0.5);
+ }
+
+ /* 提现进度条(使用CSS变量 --progress-width) */
+ .progress-container {
+ height: 30rpx;
+ background: #1a3a5a;
+ border-radius: 15rpx;
+ position: relative;
+ overflow: hidden;
+ width: 100%;
+ margin-top: 20rpx;
+ }
+ .progress-bar {
+ height: 100%;
+ background: linear-gradient(90deg, #00a6ff, #80d4ff);
+ border-radius: 15rpx;
+ width: var(--progress-width);
+ position: relative;
+ }
+ .flow-bar::after {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,0.6), transparent);
+ animation: flow 1.5s infinite;
+ }
+ @keyframes flow {
+ 0% { left: -100%; }
+ 100% { left: 100%; }
+ }
+ .progress-label {
+ position: absolute;
+ right: 20rpx;
+ top: -30rpx;
+ font-size: 24rpx;
+ color: #00a6ff;
+ }
+ .progress-stats {
+ display: flex;
+ justify-content: space-between;
+ font-size: 24rpx;
+ color: #9cf;
+ margin-top: 15rpx;
+ }
+
+ /* ========== 功能按钮(图标强光效果) ========== */
+ .function-panel {
+ margin-bottom: 40rpx;
+ }
+ .function-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 20rpx;
+ }
+ .func-item {
+ background: rgba(0,20,40,0.8);
+ border: 1rpx solid #00a6ff;
+ clip-path: polygon(0 0, 100% 0, 90% 100%, 0 100%);
+ padding: 25rpx 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ transition: 0.2s;
+ position: relative;
+ overflow: hidden;
+ }
+ .func-item:active {
+ transform: scale(0.96);
+ box-shadow: 0 0 40rpx #00a6ff;
+ }
+ /* 图标超强发光 */
+ .super-glow {
+ width: 60rpx;
+ height: 60rpx;
+ margin-bottom: 15rpx;
+ filter: drop-shadow(0 0 25rpx #00a6ff) drop-shadow(0 0 50rpx #00a6ff) drop-shadow(0 0 80rpx #00a6ff);
+ animation: superGlow 1s infinite alternate;
+ }
+ @keyframes superGlow {
+ 0% { filter: drop-shadow(0 0 20rpx #00a6ff) drop-shadow(0 0 40rpx #00a6ff) drop-shadow(0 0 60rpx #00a6ff); }
+ 100% { filter: drop-shadow(0 0 30rpx #00a6ff) drop-shadow(0 0 60rpx #00a6ff) drop-shadow(0 0 100rpx #00a6ff); }
+ }
+ .func-label {
+ font-size: 26rpx;
+ color: #fff;
+ z-index: 1;
+ }
+
+ /* ========== 加载遮罩 ========== */
+ .loading-mask {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(2,6,12,0.9);
+ backdrop-filter: blur(20px);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 9999;
+ }
+ .loading-core {
+ width: 200rpx;
+ height: 200rpx;
+ border: 4rpx solid #00a6ff;
+ border-radius: 50%;
+ border-top-color: transparent;
+ animation: spin 1s linear infinite;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+ .loading-core::after {
+ content: '';
+ width: 100rpx;
+ height: 100rpx;
+ border: 4rpx solid #ffaa00;
+ border-radius: 50%;
+ border-bottom-color: transparent;
+ animation: spin 1.5s reverse infinite;
+ }
+ @keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+ }
+
+
+
+
+
+ /* ========== 原有样式(完全不变) ========== */
+/* ... 此处省略,切勿修改 ... */
+
+/* ========== 联系管事胶囊(新增,适配机甲风格) ========== */
+.inviter-capsule {
+ margin: 30rpx 25rpx 30rpx;
+ background: rgba(0, 20, 40, 0.85);
+ backdrop-filter: blur(20px);
+ border: 2rpx solid #00a6ff;
+ clip-path: polygon(5% 0, 100% 0, 95% 100%, 0 100%);
+ padding: 20rpx 30rpx;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ box-shadow: 0 0 30rpx rgba(0, 166, 255, 0.4);
+ position: relative;
+ z-index: 1;
+ }
+
+ .capsule-body {
+ display: flex;
+ align-items: center;
+ flex: 1;
+ }
+
+ .capsule-avatar {
+ width: 80rpx;
+ height: 80rpx;
+ border-radius: 50%;
+ border: 3rpx solid #00a6ff;
+ box-shadow: 0 0 20rpx #00a6ff;
+ margin-right: 20rpx;
+ background: #0a1a2a;
+ }
+
+ .capsule-info {
+ display: flex;
+ flex-direction: column;
+ }
+
+ .capsule-name {
+ font-size: 30rpx;
+ font-weight: bold;
+ color: #fff;
+ text-shadow: 0 0 15rpx #00a6ff;
+ margin-bottom: 6rpx;
+ }
+
+ .capsule-uid {
+ font-size: 24rpx;
+ color: #9cf;
+ }
+
+ .capsule-contact {
+ background: linear-gradient(135deg, #00a6ff, #0080ff);
+ border-radius: 30rpx;
+ padding: 12rpx 30rpx;
+ font-size: 26rpx;
+ font-weight: bold;
+ color: #fff;
+ box-shadow: 0 0 20rpx #00a6ff;
+ letter-spacing: 2rpx;
+ transition: all 0.3s;
+ }
+
+ .capsule-contact:active {
+ transform: scale(0.95);
+ box-shadow: 0 0 40rpx #00a6ff;
+ }
+
+
+
+
+
+
+
+ /* ========== 🆕 称号标签展示区 ========== */
+.badge-area {
+ width: 100%;
+ margin: 20rpx 0 30rpx 0;
+ padding: 0 20rpx;
+ box-sizing: border-box;
+ }
+
+ .badge-scroll {
+ width: 100%;
+ max-height: 140rpx;
+ overflow-y: auto;
+ background: transparent;
+ }
+
+ .badge-grid {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8rpx;
+ padding: 6rpx 0;
+ align-items: center;
+ justify-content: flex-start;
+ }
+
+ .badge-item {
+ flex-shrink: 0;
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/jisufd/jisufd.js b/miniprogram/pages/jisufd/jisufd.js
new file mode 100644
index 0000000..49ae9cb
--- /dev/null
+++ b/miniprogram/pages/jisufd/jisufd.js
@@ -0,0 +1,618 @@
+// pages/jisufd/jisufd.js
+import request from '../../utils/request.js'
+
+const PAGE_SIZE = 50
+const LINK_PAGE_SIZE = 5
+
+Page({
+ data: {
+ sjyue: '0.00',
+ shangpinList: [], // 最终展示的商品类型列表(已倒序)
+ selectedTypeId: null,
+ selectedHuiyuanId: null,
+ selectedYaoqiuleixing: null,
+ currentTypeChenghaoList: [],
+
+ searchParams: { keyword: '', minPrice: '', labelId: '' },
+ searchLabelName: '',
+ isSearchMode: false,
+
+ templateList: [],
+ currentPage: 1,
+ hasMoreData: true,
+ isLoadingMore: false,
+
+ linkMap: {},
+
+ showDetailModal: false,
+ showAddModal: false,
+ showDeleteConfirm: false,
+ showNewLinkConfirm: false,
+
+ currentTemplate: null,
+ currentTemplateIndex: -1,
+
+ isEditing: false,
+ editJieshao: '',
+ editJiage: '',
+ editLabelId: '',
+ editLabelName: '',
+ editCommissionEnabled: false,
+ editCommissionValue: '',
+
+ addJieshao: '',
+ addJiage: '',
+ addLabelId: '',
+ addLabelName: '',
+ addCommissionEnabled: false,
+ addCommissionValue: '',
+
+ detailLinkFilter: 0,
+ detailLinkList: [],
+ detailLinkPage: 1,
+ detailLinkHasMore: true,
+ isLoadingLinks: false,
+
+ isLoading: false
+ },
+
+ onLoad() {
+ this.loadShangjiaYue()
+ this.loadShangpinTypes()
+ },
+
+ onShow() {
+ this.registerNotificationComponent()
+ this.loadShangjiaYue()
+ },
+
+ registerNotificationComponent() {
+ const app = getApp()
+ const comp = this.selectComponent('#global-notification')
+ if (comp && comp.showNotification) {
+ app.globalData.globalNotification = {
+ show: data => comp.showNotification(data),
+ hide: () => comp.hideNotification()
+ }
+ }
+ },
+
+ loadShangjiaYue() {
+ const app = getApp()
+ const data = app.globalData.shangjia || {}
+ this.setData({ sjyue: data.sjyue || '0.00' })
+ },
+
+ async loadShangpinTypes() {
+ this.setData({ isLoading: true })
+ try {
+ const res = await request({ url: '/dingdan/sjspleixing', method: 'POST' })
+ if (res && res.data.code === 200) {
+ let list = res.data.data || []
+ // 强制倒序排列
+ list.reverse()
+ this.setData({ shangpinList: list, isLoading: false })
+ if (list.length > 0) {
+ this.setSelectedType(list[0])
+ }
+ }
+ } catch (e) {
+ console.error(e)
+ this.setData({ isLoading: false })
+ wx.showToast({ title: '加载类型失败', icon: 'error' })
+ }
+ },
+
+ setSelectedType(item) {
+ this.setData({
+ selectedTypeId: item.id,
+ selectedHuiyuanId: item.huiyuan_id,
+ selectedYaoqiuleixing: item.yaoqiuleixing,
+ currentTypeChenghaoList: item.chenghaoList || []
+ }, () => {
+ this.resetAndLoadTemplates()
+ })
+ },
+
+ resetAndLoadTemplates() {
+ this.setData({
+ templateList: [],
+ currentPage: 1,
+ hasMoreData: true,
+ isSearchMode: false,
+ searchParams: { keyword: '', minPrice: '', labelId: '' },
+ searchLabelName: ''
+ })
+ this.loadTemplates()
+ },
+
+ async loadTemplates() {
+ const { selectedTypeId, currentPage, isSearchMode, searchParams, linkMap } = this.data
+ if (!selectedTypeId) {
+ wx.showToast({ title: '请先选择订单类型', icon: 'none' })
+ return
+ }
+ if (currentPage === 1) this.setData({ isLoading: true })
+ else this.setData({ isLoadingMore: true })
+
+ try {
+ const postData = {
+ shangpinTypeId: selectedTypeId,
+ page: currentPage,
+ pageSize: PAGE_SIZE,
+ getType: isSearchMode ? 1 : 2
+ }
+ if (isSearchMode) {
+ if (searchParams.keyword) postData.keyword = searchParams.keyword.trim()
+ if (searchParams.minPrice) postData.minPrice = searchParams.minPrice
+ if (searchParams.labelId) postData.labelId = searchParams.labelId
+ }
+
+ const res = await request({ url: '/peizhi/sjddmblb', method: 'POST', data: postData })
+ if (res && res.data.code === 200) {
+ const data = res.data.data || {}
+ const newTemplates = data.list || []
+ const existingIds = new Set(this.data.templateList.map(item => item.mobanId))
+ const uniqueNew = newTemplates.filter(item => !existingIds.has(item.mobanId))
+
+ const processed = uniqueNew.map(item => {
+ const mobanId = item.mobanId || item.id
+ const cached = linkMap[mobanId] || {}
+ return {
+ ...item,
+ hasGenerated: cached.hasGenerated || !!item.linkUrl,
+ isCopied: cached.isCopied || false,
+ linkUrl: cached.linkUrl || item.linkUrl || '',
+ mobanId: mobanId,
+ jieshao: item.jieshao || item.moban_jieshao,
+ jiage: item.jiage || item.price,
+ fabushuliang: item.fabushuliang || item.fabu_shuliang || 0,
+ labelId: item.labelId,
+ labelName: item.labelName || '',
+ commissionEnabled: item.commissionEnabled,
+ commissionValue: item.commissionValue
+ }
+ })
+
+ this.setData({
+ templateList: currentPage === 1 ? processed : [...this.data.templateList, ...processed],
+ hasMoreData: data.hasMore !== false && processed.length === PAGE_SIZE,
+ isLoading: false,
+ isLoadingMore: false
+ })
+ } else {
+ this.setData({ isLoading: false, isLoadingMore: false, hasMoreData: false })
+ }
+ } catch (e) {
+ console.error(e)
+ this.setData({ isLoading: false, isLoadingMore: false })
+ wx.showToast({ title: '加载模板失败', icon: 'error' })
+ }
+ },
+
+ // ---------- 搜索相关 ----------
+ onSearchKeywordInput(e) { this.setData({ 'searchParams.keyword': e.detail.value }) },
+ onSearchMinPriceInput(e) { this.setData({ 'searchParams.minPrice': e.detail.value }) },
+ onSearchLabelChange(e) {
+ const idx = e.detail.value
+ const label = this.data.currentTypeChenghaoList[idx]
+ this.setData({
+ 'searchParams.labelId': label ? label.id : '',
+ searchLabelName: label ? label.mingcheng : ''
+ })
+ },
+ onSearchConfirm() {
+ const { searchParams } = this.data
+ if (!searchParams.keyword && !searchParams.minPrice && !searchParams.labelId) {
+ this.resetAndLoadTemplates()
+ return
+ }
+ this.setData({
+ isSearchMode: true,
+ currentPage: 1,
+ templateList: [],
+ hasMoreData: true
+ }, () => this.loadTemplates())
+ },
+ onClearSearch() { this.resetAndLoadTemplates() },
+
+ // ---------- 卡片生成链接 ----------
+ async onGenerateLink(e) {
+ const { id } = e.currentTarget.dataset
+ const idx = this.data.templateList.findIndex(item => item.mobanId === id)
+ if (idx === -1) return
+ await this.generateLinkForTemplate(this.data.templateList[idx], idx)
+ },
+
+ onCopyLink(e) {
+ const { id } = e.currentTarget.dataset
+ const idx = this.data.templateList.findIndex(item => item.mobanId === id)
+ if (idx === -1) return
+ const template = this.data.templateList[idx]
+ if (!template.linkUrl) {
+ wx.showToast({ title: '请先生成链接', icon: 'none' })
+ return
+ }
+ wx.setClipboardData({
+ data: template.linkUrl,
+ success: () => {
+ const { linkMap } = this.data
+ const mobanId = template.mobanId
+ const updatedLinkMap = { ...linkMap }
+ if (updatedLinkMap[mobanId]) updatedLinkMap[mobanId].isCopied = true
+ const updatedList = [...this.data.templateList]
+ updatedList[idx].isCopied = true
+ this.setData({ linkMap: updatedLinkMap, templateList: updatedList })
+ wx.showToast({ title: '链接已复制', icon: 'success' })
+ }
+ })
+ },
+
+ async generateLinkForTemplate(template, templateIndex) {
+ const { selectedTypeId, linkMap } = this.data
+ if (!selectedTypeId) {
+ wx.showToast({ title: '请选择订单类型', icon: 'none' })
+ return
+ }
+ wx.showLoading({ title: '生成中...', mask: true })
+ try {
+ const res = await request({
+ url: '/peizhi/sjljhq',
+ method: 'POST',
+ data: { mobanId: template.mobanId, shangpinTypeId: selectedTypeId }
+ })
+ if (res && res.data.code === 200) {
+ const data = res.data.data || {}
+ const mobanId = template.mobanId
+ const updatedLinkMap = { ...linkMap }
+ updatedLinkMap[mobanId] = {
+ hasGenerated: true,
+ linkUrl: data.linkUrl || '',
+ isCopied: false,
+ newBalance: data.newBalance
+ }
+ const updatedList = [...this.data.templateList]
+ updatedList[templateIndex] = {
+ ...updatedList[templateIndex],
+ hasGenerated: true,
+ linkUrl: data.linkUrl || '',
+ isCopied: false,
+ newBalance: data.newBalance
+ }
+ if (data.newBalance) {
+ const app = getApp()
+ if (app.globalData.shangjia) app.globalData.shangjia.sjyue = data.newBalance
+ this.setData({ sjyue: data.newBalance })
+ }
+ this.setData({ linkMap: updatedLinkMap, templateList: updatedList })
+ wx.showToast({ title: '链接生成成功', icon: 'success' })
+ } else {
+ wx.showToast({ title: res.data.msg || '生成失败', icon: 'error' })
+ }
+ } catch (e) {
+ wx.showToast({ title: '生成失败', icon: 'error' })
+ } finally {
+ wx.hideLoading()
+ }
+ },
+
+ onCardTap(e) {
+ const { target } = e
+ if (target.className && (target.className.includes('btn') || target.className.includes('action'))) return
+ const { item } = e.currentTarget.dataset
+ if (!item) return
+ const idx = this.data.templateList.findIndex(t => t.mobanId === item.mobanId)
+ this.setData({
+ currentTemplate: item,
+ currentTemplateIndex: idx,
+ showDetailModal: true,
+ isEditing: false,
+ editJieshao: item.jieshao || '',
+ editJiage: item.jiage || '',
+ editLabelId: item.labelId || '',
+ editLabelName: item.labelName || '',
+ editCommissionEnabled: item.commissionEnabled || false,
+ editCommissionValue: item.commissionValue || '',
+ detailLinkFilter: 0,
+ detailLinkList: [],
+ detailLinkPage: 1,
+ detailLinkHasMore: true
+ })
+ this.loadDetailLinks()
+ },
+
+ hideDetailModal() { this.setData({ showDetailModal: false, isEditing: false }) },
+ showAddModal() {
+ this.setData({
+ showAddModal: true,
+ addJieshao: '', addJiage: '', addLabelId: '', addLabelName: '',
+ addCommissionEnabled: false, addCommissionValue: ''
+ })
+ },
+ hideAddModal() { this.setData({ showAddModal: false }) },
+ hideDeleteConfirm() { this.setData({ showDeleteConfirm: false }) },
+ hideNewLinkConfirm() { this.setData({ showNewLinkConfirm: false }) },
+
+ // ---------- 链接筛选与加载 ----------
+ onToggleLinkFilter() {
+ const newFilter = this.data.detailLinkFilter === 0 ? 1 : 0
+ this.setData({
+ detailLinkFilter: newFilter,
+ detailLinkList: [],
+ detailLinkPage: 1,
+ detailLinkHasMore: true
+ }, () => this.loadDetailLinks())
+ },
+
+ async loadDetailLinks() {
+ const { currentTemplate, detailLinkFilter, detailLinkPage, isLoadingLinks } = this.data
+ if (!currentTemplate || isLoadingLinks) return
+ this.setData({ isLoadingLinks: true })
+ try {
+ const res = await request({
+ url: '/peizhi/hqsjlslj',
+ method: 'POST',
+ data: {
+ mobanId: currentTemplate.mobanId,
+ used: detailLinkFilter,
+ page: detailLinkPage,
+ pageSize: LINK_PAGE_SIZE
+ }
+ })
+ if (res && res.data.code === 200) {
+ const data = res.data.data || {}
+ const newLinks = data.list || []
+ const merged = detailLinkPage === 1 ? newLinks : [...this.data.detailLinkList, ...newLinks]
+ this.setData({
+ detailLinkList: merged,
+ detailLinkHasMore: newLinks.length === LINK_PAGE_SIZE,
+ isLoadingLinks: false
+ })
+ } else {
+ this.setData({ isLoadingLinks: false })
+ }
+ } catch (e) {
+ this.setData({ isLoadingLinks: false })
+ wx.showToast({ title: '加载链接失败', icon: 'error' })
+ }
+ },
+
+ onLoadMoreLinks() {
+ if (!this.data.detailLinkHasMore || this.data.isLoadingLinks) return
+ this.setData({ detailLinkPage: this.data.detailLinkPage + 1 }, () => this.loadDetailLinks())
+ },
+
+ onCopyLinkItem(e) {
+ const url = e.currentTarget.dataset.url
+ if (!url) return
+ wx.setClipboardData({ data: url, success: () => wx.showToast({ title: '已复制', icon: 'success' }) })
+ },
+
+ // ---------- 弹窗内操作 ----------
+ onGenerateLinkModal() {
+ const { currentTemplate, currentTemplateIndex } = this.data
+ if (!currentTemplate) return
+ if (currentTemplate.hasGenerated && !currentTemplate.isCopied) {
+ this.setData({ showNewLinkConfirm: true })
+ return
+ }
+ this.generateLinkForTemplate(currentTemplate, currentTemplateIndex)
+ },
+
+ onCopyLinkModal() {
+ const { currentTemplate, linkMap } = this.data
+ if (!currentTemplate || !currentTemplate.linkUrl) {
+ wx.showToast({ title: '请先生成链接', icon: 'none' })
+ return
+ }
+ wx.setClipboardData({
+ data: currentTemplate.linkUrl,
+ success: () => {
+ const mobanId = currentTemplate.mobanId
+ const updatedLinkMap = { ...linkMap }
+ if (updatedLinkMap[mobanId]) updatedLinkMap[mobanId].isCopied = true
+ const updatedList = [...this.data.templateList]
+ if (this.data.currentTemplateIndex !== -1) {
+ updatedList[this.data.currentTemplateIndex].isCopied = true
+ }
+ const updatedCurrent = { ...currentTemplate, isCopied: true }
+ this.setData({ linkMap: updatedLinkMap, templateList: updatedList, currentTemplate: updatedCurrent })
+ wx.showToast({ title: '链接已复制', icon: 'success' })
+ }
+ })
+ },
+
+ // ---------- 编辑 ----------
+ onEditTemplate() {
+ if (!this.data.isEditing) this.setData({ isEditing: true })
+ else this.saveTemplateEdit()
+ },
+ onEditJieshaoInput(e) { this.setData({ editJieshao: e.detail.value }) },
+ onEditJiageInput(e) { this.setData({ editJiage: e.detail.value }) },
+ onEditLabelChange(e) {
+ const idx = e.detail.value
+ const label = this.data.currentTypeChenghaoList[idx]
+ this.setData({ editLabelId: label ? label.id : '', editLabelName: label ? label.mingcheng : '' })
+ },
+ onEditCommissionToggle() { this.setData({ editCommissionEnabled: !this.data.editCommissionEnabled }) },
+ onEditCommissionValueInput(e) { this.setData({ editCommissionValue: e.detail.value }) },
+
+ async saveTemplateEdit() {
+ const { currentTemplate, selectedTypeId, editJieshao, editJiage, editLabelId, editCommissionEnabled, editCommissionValue } = this.data
+ if (!currentTemplate || !selectedTypeId) return
+ if (editCommissionEnabled && (!editCommissionValue || isNaN(parseFloat(editCommissionValue)))) {
+ wx.showToast({ title: '请输入有效佣金金额', icon: 'none' })
+ return
+ }
+ wx.showLoading({ title: '保存中...', mask: true })
+ try {
+ const res = await request({
+ url: '/peizhi/sjddmbgx',
+ method: 'POST',
+ data: {
+ mobanId: currentTemplate.mobanId,
+ shangpinTypeId: selectedTypeId,
+ jieshao: editJieshao,
+ jiage: editJiage,
+ labelId: editLabelId || '',
+ commissionEnabled: editCommissionEnabled,
+ commissionValue: editCommissionValue
+ }
+ })
+ if (res && res.data.code === 200) {
+ const updatedList = [...this.data.templateList]
+ const idx = this.data.currentTemplateIndex
+ if (idx !== -1) {
+ updatedList[idx] = {
+ ...updatedList[idx],
+ jieshao: editJieshao,
+ jiage: editJiage,
+ labelId: editLabelId,
+ labelName: this.getLabelNameById(editLabelId),
+ commissionEnabled: editCommissionEnabled,
+ commissionValue: editCommissionValue
+ }
+ this.setData({ templateList: updatedList, currentTemplate: updatedList[idx], isEditing: false })
+ }
+ wx.showToast({ title: '修改成功', icon: 'success' })
+ } else {
+ wx.showToast({ title: res.data.msg || '修改失败', icon: 'error' })
+ }
+ } catch (e) {
+ wx.showToast({ title: '修改失败', icon: 'error' })
+ } finally { wx.hideLoading() }
+ },
+
+ onDeleteTemplate() { this.setData({ showDeleteConfirm: true }) },
+
+ async confirmDeleteTemplate() {
+ const { currentTemplate, selectedTypeId, linkMap } = this.data
+ if (!currentTemplate || !selectedTypeId) return
+ wx.showLoading({ title: '删除中...', mask: true })
+ try {
+ const res = await request({
+ url: '/peizhi/sjddmbsc',
+ method: 'POST',
+ data: { mobanId: currentTemplate.mobanId, shangpinTypeId: selectedTypeId }
+ })
+ if (res && res.data.code === 200) {
+ const updatedLinkMap = { ...linkMap }
+ delete updatedLinkMap[currentTemplate.mobanId]
+ const updatedList = [...this.data.templateList]
+ updatedList.splice(this.data.currentTemplateIndex, 1)
+ this.setData({
+ linkMap: updatedLinkMap,
+ templateList: updatedList,
+ showDetailModal: false,
+ showDeleteConfirm: false,
+ currentTemplate: null,
+ currentTemplateIndex: -1
+ })
+ wx.showToast({ title: '删除成功', icon: 'success' })
+ } else {
+ wx.showToast({ title: res.data.msg || '删除失败', icon: 'error' })
+ }
+ } catch (e) {
+ wx.showToast({ title: '删除失败', icon: 'error' })
+ } finally { wx.hideLoading() }
+ },
+
+ async confirmNewLink() {
+ const { currentTemplate, currentTemplateIndex } = this.data
+ if (!currentTemplate) return
+ this.setData({ showNewLinkConfirm: false })
+ await this.generateLinkForTemplate(currentTemplate, currentTemplateIndex)
+ },
+
+ // ---------- 添加模板 ----------
+ onAddJieshaoInput(e) { this.setData({ addJieshao: e.detail.value }) },
+ onAddJiageInput(e) { this.setData({ addJiage: e.detail.value }) },
+ onAddLabelChange(e) {
+ const idx = e.detail.value
+ const label = this.data.currentTypeChenghaoList[idx]
+ this.setData({ addLabelId: label ? label.id : '', addLabelName: label ? label.mingcheng : '' })
+ },
+ onAddCommissionToggle() { this.setData({ addCommissionEnabled: !this.data.addCommissionEnabled }) },
+ onAddCommissionValueInput(e) { this.setData({ addCommissionValue: e.detail.value }) },
+
+ async onAddTemplate() {
+ const { selectedTypeId, addJieshao, addJiage, addLabelId, addCommissionEnabled, addCommissionValue } = this.data
+ if (!selectedTypeId) {
+ wx.showToast({ title: '请选择订单类型', icon: 'none' })
+ return
+ }
+ if (!addJieshao.trim()) {
+ wx.showToast({ title: '请输入订单介绍', icon: 'none' })
+ return
+ }
+ if (!addJiage || isNaN(parseFloat(addJiage)) || parseFloat(addJiage) <= 0) {
+ wx.showToast({ title: '请输入有效的价格', icon: 'none' })
+ return
+ }
+ if (addCommissionEnabled && (!addCommissionValue || isNaN(parseFloat(addCommissionValue)))) {
+ wx.showToast({ title: '请输入有效的佣金金额', icon: 'none' })
+ return
+ }
+
+ wx.showLoading({ title: '添加中...', mask: true })
+ try {
+ const res = await request({
+ url: '/peizhi/sjtjddmb',
+ method: 'POST',
+ data: {
+ shangpinTypeId: selectedTypeId,
+ jieshao: addJieshao.trim(),
+ jiage: addJiage,
+ labelId: addLabelId || '',
+ commissionEnabled: addCommissionEnabled,
+ commissionValue: addCommissionValue
+ }
+ })
+ if (res && res.data.code === 200) {
+ const data = res.data.data || {}
+ const newTemplate = {
+ mobanId: data.mobanId,
+ jieshao: addJieshao,
+ jiage: addJiage,
+ fabushuliang: 0,
+ hasGenerated: false,
+ isCopied: false,
+ linkUrl: '',
+ labelId: addLabelId,
+ labelName: this.getLabelNameById(addLabelId),
+ commissionEnabled: addCommissionEnabled,
+ commissionValue: addCommissionValue
+ }
+ this.setData({
+ templateList: [newTemplate, ...this.data.templateList],
+ showAddModal: false,
+ addJieshao: '', addJiage: '', addLabelId: '', addLabelName: '',
+ addCommissionEnabled: false, addCommissionValue: ''
+ })
+ wx.showToast({ title: '添加成功', icon: 'success' })
+ } else {
+ wx.showToast({ title: res.data.msg || '添加失败', icon: 'error' })
+ }
+ } catch (e) {
+ wx.showToast({ title: '添加失败', icon: 'error' })
+ } finally { wx.hideLoading() }
+ },
+
+ getLabelNameById(labelId) {
+ if (!labelId) return ''
+ const list = this.data.currentTypeChenghaoList
+ const found = list.find(item => item.id == labelId)
+ return found ? found.mingcheng : ''
+ },
+
+ onScrollToLower() {
+ const { hasMoreData, isLoadingMore, isSearchMode } = this.data
+ if (!hasMoreData || isLoadingMore || isSearchMode) return
+ this.setData({ currentPage: this.data.currentPage + 1 }, () => this.loadTemplates())
+ },
+
+ onSelectType(e) {
+ const { id, item } = e.currentTarget.dataset
+ this.setSelectedType(item)
+ }
+})
\ No newline at end of file
diff --git a/miniprogram/pages/jisufd/jisufd.json b/miniprogram/pages/jisufd/jisufd.json
new file mode 100644
index 0000000..e0c0695
--- /dev/null
+++ b/miniprogram/pages/jisufd/jisufd.json
@@ -0,0 +1,12 @@
+{
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification",
+ "chenghao-tag": "/components/chenghao-tag/chenghao-tag"
+ },
+ "backgroundTextStyle": "dark",
+ "navigationBarBackgroundColor": "#0a0a0f",
+ "navigationBarTitleText": "极速派单",
+ "navigationBarTextStyle": "white",
+ "backgroundColor": "#0a0a0f",
+ "enablePullDownRefresh": false
+}
\ No newline at end of file
diff --git a/miniprogram/pages/jisufd/jisufd.wxml b/miniprogram/pages/jisufd/jisufd.wxml
new file mode 100644
index 0000000..5d0fc2a
--- /dev/null
+++ b/miniprogram/pages/jisufd/jisufd.wxml
@@ -0,0 +1,230 @@
+
+
+
+
+ 可用余额
+
+ {{sjyue}}
+ 元
+
+ 生成链接将从此余额扣除
+
+
+
+
+ 选择订单类型
+
+
+
+ {{item.jieshao}}
+
+
+
+
+
+
+
+
+ 🔍
+
+
+ 搜索
+
+
+
+
+ 金额≥
+
+
+
+ 标签
+
+
+ {{searchLabelName || '全部'}}
+
+
+
+ 清空
+
+
+
+
+
+
+
+
+
+
+ {{item.jieshao || '无介绍'}}
+
+
+
+
+
+ 价格:
+ {{item.jiage || '0.00'}}元
+
+
+
+ {{item.hasGenerated ? '获取新链接' : '生成链接'}}
+
+
+ {{item.isCopied ? '已复制' : '一键复制'}}
+
+
+
+
+
+ ➕
+ 添加模板
+ 点击创建新模板
+
+
+ ...
+ 没有更多模板了
+ 暂无模板
+
+
+
+
+
+
+
+ ×
+
+
+
+ 订单介绍
+ {{currentTemplate.jieshao || '无介绍'}}
+
+
+ 关联标签
+
+
+
+ 价格{{currentTemplate.jiage || '0.00'}}元
+ |
+ 发布数量{{currentTemplate.fabushuliang || 0}}次
+ |
+ 模板ID{{currentTemplate.mobanId || 'N/A'}}
+
+
+ 佣金要求
+ {{currentTemplate.commissionValue}}元(建议≤50)
+
+
+
+ 链接状态:
+ 未使用
+ 已使用
+
+
+
+
+ {{item.lianjie}}
+ {{item.is_used ? '已使用' : '未使用'}}
+ 复制
+
+
+
+ 获取更多链接
+
+ 加载中...
+ 暂无链接
+
+
+
+ {{currentTemplate.hasGenerated ? '获取新链接' : '生成链接'}}
+ {{currentTemplate.isCopied ? '已复制' : '复制链接'}}
+ {{isEditing ? '保存修改' : '立即修改'}}
+ 删除模板
+
+
+
+
+ 订单介绍
+
+ {{editJieshao.length}}/100
+
+
+ 价格(元)
+
+
+
+ 标签
+
+ {{editLabelName || '无'}}
+
+
+
+ 佣金要求(建议≤50)
+
+
+
+
+
+
+
+
+
+
+ ×
+
+
+
+ 订单介绍
+
+ {{addJieshao.length}}/100
+
+
+ 价格(元)
+
+
+
+ 标签
+
+ {{addLabelName || '可选'}}
+
+
+
+ 佣金要求(建议≤50)
+
+
+
+
+
+ 取消
+ 确认添加
+
+
+
+
+
+
+
+ 确认删除
+ 确定要删除这个模板吗?此操作不可恢复。
+
+ 取消
+ 确定删除
+
+
+
+
+
+
+
+ 获取新链接
+ {{currentTemplate.isCopied ? '确定要获取新链接吗?旧链接将失效。' : '您还没有复制当前链接,确定要获取新链接吗?'}}
+
+ 取消
+ 确定
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/jisufd/jisufd.wxss b/miniprogram/pages/jisufd/jisufd.wxss
new file mode 100644
index 0000000..dc4d7e6
--- /dev/null
+++ b/miniprogram/pages/jisufd/jisufd.wxss
@@ -0,0 +1,222 @@
+/* pages/jisufd/jisufd.wxss - 增强版,包含标签展示 */
+.page-container {
+ min-height: 100vh;
+ background: linear-gradient(135deg, #0a0a0f 0%, #1a1a2e 50%, #16213e 100%);
+ padding: 30rpx;
+ box-sizing: border-box;
+ padding-bottom: 40rpx;
+ font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif;
+ }
+ .balance-card {
+ background: linear-gradient(135deg, rgba(20,20,30,0.9), rgba(30,30,50,0.9));
+ border-radius: 24rpx;
+ padding: 36rpx;
+ margin-bottom: 40rpx;
+ color: #fff;
+ box-shadow: 0 15rpx 35rpx rgba(0,0,0,0.4), 0 0 40rpx rgba(0,150,255,0.15);
+ text-align: center;
+ border: 1.5rpx solid rgba(0,180,255,0.2);
+ backdrop-filter: blur(10rpx);
+ position: relative;
+ overflow: hidden;
+ }
+ .balance-card::before {
+ content: '';
+ position: absolute;
+ top: -50%; left: -50%;
+ width: 200%; height: 200%;
+ background: linear-gradient(45deg, transparent 30%, rgba(0,180,255,0.1) 50%, transparent 70%);
+ animation: cardShine 6s infinite linear;
+ z-index: 1;
+ }
+ @keyframes cardShine {
+ 0% { transform: translateX(-100%) translateY(-100%) rotate(45deg); }
+ 100% { transform: translateX(100%) translateY(100%) rotate(45deg); }
+ }
+ .balance-label { font-size: 28rpx; color: rgba(255,255,255,0.8); margin-bottom: 18rpx; font-weight: 500; position: relative; z-index: 2; }
+ .balance-amount { display: flex; align-items: baseline; justify-content: center; margin-bottom: 15rpx; position: relative; z-index: 2; }
+ .amount-value { font-size: 64rpx; font-weight: bold; color: #00B4FF; text-shadow: 0 0 20rpx rgba(0,180,255,0.7), 0 0 40rpx rgba(0,180,255,0.4); animation: amountGlow 3s infinite alternate; }
+ @keyframes amountGlow {
+ 0% { color: #00B4FF; text-shadow: 0 0 15rpx rgba(0,180,255,0.6); }
+ 100% { color: #00E0FF; text-shadow: 0 0 25rpx rgba(0,180,255,0.8), 0 0 50rpx rgba(0,180,255,0.5); }
+ }
+ .amount-unit { font-size: 36rpx; color: #00B4FF; margin-left: 12rpx; font-weight: 500; z-index: 2; }
+ .balance-tip { font-size: 24rpx; color: rgba(255,255,255,0.6); z-index: 2; }
+
+ .section { margin-bottom: 35rpx; }
+ .section-title { font-size: 32rpx; font-weight: 700; color: #FFFFFF; margin-bottom: 25rpx; padding-left: 12rpx; border-left: 6rpx solid #9C27B0; text-shadow: 0 2rpx 4rpx rgba(0,0,0,0.3); }
+
+ .type-scroll { white-space: nowrap; display: flex; height: 90rpx; }
+ .type-item {
+ display: inline-flex; flex-shrink: 0; align-items: center; justify-content: center;
+ height: 85rpx; padding: 0 40rpx; margin-right: 20rpx;
+ background: rgba(255,255,255,0.08); border-radius: 45rpx; border: 2rpx solid rgba(255,255,255,0.15);
+ font-size: 28rpx; color: rgba(255,255,255,0.8);
+ transition: all 0.3s cubic-bezier(0.175,0.885,0.32,1.275);
+ backdrop-filter: blur(10rpx);
+ }
+ .type-active {
+ background: linear-gradient(135deg, #9C27B0 0%, #673AB7 100%);
+ border-color: #9C27B0; color: white; transform: scale(1.05);
+ box-shadow: 0 10rpx 25rpx rgba(156,39,176,0.4), 0 0 30rpx rgba(156,39,176,0.3);
+ }
+ .type-text { max-width: 220rpx; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; }
+
+ /* 搜索区域 */
+ .search-section { margin-bottom: 35rpx; }
+ .search-box {
+ display: flex; align-items: center;
+ background: rgba(255,255,255,0.1); border-radius: 50rpx;
+ padding: 20rpx 30rpx; border: 2rpx solid rgba(255,255,255,0.2);
+ backdrop-filter: blur(10rpx); transition: all 0.3s ease;
+ }
+ .search-box:focus-within { border-color: #9C27B0; background: rgba(255,255,255,0.15); box-shadow: 0 0 25rpx rgba(156,39,176,0.3); }
+ .search-icon { font-size: 32rpx; color: rgba(255,255,255,0.7); margin-right: 20rpx; flex-shrink: 0; }
+ .search-input { flex: 1; font-size: 28rpx; color: #FFFFFF; background: transparent; border: none; outline: none; min-height: 40rpx; }
+ .search-input::placeholder { color: rgba(255,255,255,0.4); }
+ .search-btn { background: linear-gradient(135deg, #9C27B0 0%, #673AB7 100%); border-radius: 40rpx; padding: 15rpx 30rpx; margin-left: 20rpx; flex-shrink: 0; box-shadow: 0 8rpx 20rpx rgba(156,39,176,0.3); }
+ .search-btn:active { transform: scale(0.95); }
+ .search-btn-text { font-size: 26rpx; color: white; font-weight: 600; }
+
+ .filter-row { display: flex; align-items: center; margin-top: 20rpx; gap: 15rpx; }
+ .filter-item { display: flex; align-items: center; background: rgba(255,255,255,0.08); border-radius: 30rpx; padding: 10rpx 20rpx; border: 1rpx solid rgba(255,255,255,0.15); }
+ .filter-label { font-size: 24rpx; color: rgba(255,255,255,0.7); margin-right: 10rpx; }
+ .filter-input { width: 120rpx; height: 50rpx; background: transparent; color: #fff; font-size: 26rpx; }
+ .picker-view { padding: 10rpx 0; color: rgba(255,255,255,0.9); font-size: 26rpx; max-width: 160rpx; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+ .clear-search { margin-left: auto; padding: 10rpx 20rpx; background: rgba(255,255,255,0.1); border-radius: 20rpx; color: rgba(255,255,255,0.7); font-size: 24rpx; }
+
+ /* 模板区域 */
+ .template-section { height: calc(100vh - 600rpx); position: relative; }
+ .template-scroll { height: 100%; }
+ .template-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 25rpx; padding-bottom: 30rpx; }
+ .template-card {
+ background: linear-gradient(145deg, rgba(30,30,45,0.9), rgba(40,40,60,0.9));
+ border-radius: 20rpx; padding: 25rpx;
+ border: 1.5rpx solid rgba(255,255,255,0.1);
+ box-shadow: 0 10rpx 30rpx rgba(0,0,0,0.3), 0 0 25rpx rgba(0,180,255,0.1);
+ transition: all 0.3s ease; backdrop-filter: blur(10rpx);
+ display: flex; flex-direction: column; min-height: 280rpx; position: relative; overflow: hidden;
+ }
+ .template-card::before { content: ''; position: absolute; top:0; left:0; right:0; height:4rpx; background: linear-gradient(90deg, #9C27B0, #00B4FF, #9C27B0); opacity:0.6; }
+ .template-intro { flex:1; margin-bottom:10rpx; min-height:70rpx; overflow:hidden; }
+ .intro-text { font-size:24rpx; line-height:1.4; color:rgba(255,255,255,0.9); display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:3; overflow:hidden; font-weight:500; }
+ .template-label { margin-bottom: 10rpx; }
+ .template-price-row { display:flex; align-items:center; margin-bottom:20rpx; padding:10rpx 0; border-bottom:1rpx solid rgba(255,255,255,0.1); }
+ .template-price-label { font-size:22rpx; color:rgba(255,255,255,0.7); margin-right:10rpx; }
+ .template-price-value { font-size:26rpx; font-weight:bold; color:#FFD700; text-shadow:0 0 10rpx rgba(255,215,0,0.3); flex:1; }
+ .template-actions-grid { display:flex; flex-direction:column; gap:12rpx; margin-top:10rpx; }
+ .action-grid-btn {
+ height:65rpx; border-radius:32rpx; font-size:24rpx; font-weight:600; transition:all 0.3s ease;
+ border:none; padding:0 20rpx; box-shadow:0 4rpx 12rpx rgba(0,0,0,0.2), inset 0 1rpx 0 rgba(255,255,255,0.1);
+ display:flex !important; align-items:center !important; justify-content:center !important; text-align:center !important;
+ }
+ .generate-grid-btn { background:linear-gradient(135deg, #00B4FF, #0091EA); color:white; }
+ .generate-grid-btn.generated { background:linear-gradient(135deg, #4CAF50, #2E7D32); }
+ .copy-grid-btn { background:linear-gradient(135deg, #9C27B0, #673AB7); color:white; }
+ .copy-grid-btn.copied { background:linear-gradient(135deg, #FF9800, #F57C00); }
+ .add-card { background: linear-gradient(145deg, rgba(40,40,60,0.7), rgba(60,60,80,0.7)); border:2rpx dashed rgba(255,255,255,0.3); display:flex; flex-direction:column; align-items:center; justify-content:center; min-height:280rpx; }
+ .add-icon { font-size:60rpx; color:rgba(255,255,255,0.6); margin-bottom:20rpx; }
+ .add-text { font-size:30rpx; color:rgba(255,255,255,0.8); font-weight:600; margin-bottom:8rpx; }
+ .add-subtext { font-size:24rpx; color:rgba(255,255,255,0.5); }
+
+ /* 弹窗通用 */
+ .modal-overlay { position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.75); display:flex; align-items:center; justify-content:center; z-index:9999; padding:30rpx; backdrop-filter:blur(10rpx); animation:fadeIn 0.3s ease; }
+ @keyframes fadeIn { from{opacity:0;} to{opacity:1;} }
+ .modal-content {
+ background:linear-gradient(145deg, rgba(30,30,45,0.95), rgba(40,40,60,0.95));
+ border-radius:30rpx; width:100%; max-width:700rpx; max-height:85vh; overflow-y:auto;
+ border:1.5rpx solid rgba(255,255,255,0.15);
+ box-shadow:0 25rpx 80rpx rgba(0,0,0,0.6), 0 0 60rpx rgba(0,180,255,0.2);
+ animation:slideUp 0.4s cubic-bezier(0.175,0.885,0.32,1.275);
+ position:relative;
+ }
+ @keyframes slideUp { from{transform:translateY(100rpx) scale(0.9); opacity:0;} to{transform:translateY(0) scale(1); opacity:1;} }
+ .modal-close { position:absolute; top:25rpx; right:25rpx; width:60rpx; height:60rpx; border-radius:50%; background:rgba(255,255,255,0.1); display:flex; align-items:center; justify-content:center; font-size:40rpx; color:rgba(255,255,255,0.7); z-index:10; }
+ .modal-header { padding:40rpx 40rpx 20rpx; text-align:center; }
+ .modal-title { font-size:36rpx; font-weight:bold; color:#FFFFFF; }
+
+ .detail-info { padding:30rpx 40rpx; }
+ .detail-item { margin-bottom:30rpx; }
+ .detail-label { font-size:26rpx; color:rgba(255,255,255,0.7); margin-bottom:10rpx; font-weight:500; }
+ .detail-value { font-size:28rpx; color:#FFFFFF; word-break:break-word; }
+ .intro-value { background:rgba(255,255,255,0.05); border-radius:15rpx; padding:20rpx; border:1rpx solid rgba(255,255,255,0.1); max-height:200rpx; overflow-y:auto; }
+ .detail-meta-row { display:flex; align-items:center; justify-content:space-between; padding:25rpx 0; margin:20rpx 0; border-top:1rpx solid rgba(255,255,255,0.1); border-bottom:1rpx solid rgba(255,255,255,0.1); }
+ .meta-item { display:flex; flex-direction:column; align-items:center; flex:1; }
+ .meta-label { font-size:22rpx; color:rgba(255,255,255,0.6); margin-bottom:8rpx; }
+ .meta-value { font-size:26rpx; font-weight:600; }
+ .price-meta { color:#FFD700; text-shadow:0 0 10rpx rgba(255,215,0,0.3); }
+ .count-meta { color:#00B4FF; }
+ .id-meta { color:#4CAF50; font-family:'Courier New',monospace; font-size:24rpx; }
+ .meta-divider { color:rgba(255,255,255,0.2); font-size:24rpx; margin:0 15rpx; }
+
+ .link-filter-bar { display:flex; align-items:center; margin-top:30rpx; margin-bottom:20rpx; }
+ .filter-switch {
+ padding:10rpx 25rpx; margin-left:10rpx; border-radius:20rpx; background:rgba(255,255,255,0.1);
+ font-size:24rpx; color:rgba(255,255,255,0.7); transition:all 0.3s;
+ }
+ .filter-switch.active { background:linear-gradient(135deg, #00B4FF, #0091EA); color:white; }
+ .link-list { background:rgba(0,0,0,0.2); border-radius:15rpx; padding:15rpx; }
+ .link-item { display:flex; align-items:center; justify-content:space-between; padding:15rpx 0; border-bottom:1rpx solid rgba(255,255,255,0.1); }
+ .link-url { flex:1; font-size:22rpx; color:rgba(255,255,255,0.8); word-break:break-all; margin-right:15rpx; }
+ .link-status { font-size:20rpx; color:rgba(255,255,255,0.5); margin-right:15rpx; }
+ .copy-link-btn { padding:8rpx 20rpx; background:#00B4FF; color:white; border-radius:15rpx; font-size:22rpx; }
+ .load-more-links { text-align:center; padding:20rpx 0; }
+ .load-more-btn { display:inline-block; padding:15rpx 40rpx; background:linear-gradient(135deg, #9C27B0, #673AB7); border-radius:30rpx; color:white; font-size:26rpx; }
+ .link-loading, .link-empty { text-align:center; padding:30rpx; color:rgba(255,255,255,0.5); font-size:24rpx; }
+
+ /* 弹窗按钮 */
+ .modal-actions-grid { display:grid; grid-template-columns:repeat(2,1fr); gap:18rpx; padding:30rpx 40rpx 40rpx; border-top:1rpx solid rgba(255,255,255,0.1); }
+ .modal-grid-btn {
+ height:85rpx; border-radius:42rpx; font-size:28rpx; font-weight:600; transition:all 0.3s;
+ border:none; padding:0 20rpx; box-shadow:0 8rpx 25rpx rgba(0,0,0,0.3), inset 0 1rpx 0 rgba(255,255,255,0.2);
+ display:flex !important; align-items:center !important; justify-content:center !important;
+ }
+ .generate-modal-grid-btn { background:linear-gradient(145deg, #00B4FF, #0091EA); color:white; }
+ .copy-modal-grid-btn { background:linear-gradient(145deg, #9C27B0, #673AB7); color:white; }
+ .copy-modal-grid-btn.copied { background:linear-gradient(145deg, #FF9800, #F57C00); }
+ .edit-modal-grid-btn { background:linear-gradient(145deg, #4CAF50, #2E7D32); color:white; }
+ .delete-modal-grid-btn { background:linear-gradient(145deg, #FF4444, #CC0000); color:white; }
+
+ /* 编辑区 */
+ .edit-section { padding:30rpx 40rpx 20rpx; border-top:1rpx solid rgba(255,255,255,0.1); margin-top:20rpx; }
+ .edit-item { margin-bottom:30rpx; }
+ .edit-label { font-size:26rpx; color:rgba(255,255,255,0.8); margin-bottom:15rpx; font-weight:600; }
+ .edit-input { width:100%; background:rgba(255,255,255,0.08); border:1.5rpx solid rgba(255,255,255,0.2); border-radius:15rpx; padding:25rpx; font-size:28rpx; color:#FFFFFF; }
+ .intro-edit { min-height:150rpx; }
+ .price-edit { text-align:center; font-size:32rpx; font-weight:600; height:90rpx; line-height:90rpx; }
+ .picker-view { padding:25rpx; background:rgba(255,255,255,0.08); border-radius:15rpx; border:1.5rpx solid rgba(255,255,255,0.2); color:#fff; }
+ .commission-item { display:flex; align-items:center; gap:20rpx; }
+ .commission-input { flex:1; background:rgba(255,255,255,0.08); border-radius:15rpx; padding:20rpx; color:#fff; }
+
+ /* 添加表单 */
+ .add-form { padding:30rpx 40rpx 20rpx; }
+ .form-item { margin-bottom:35rpx; }
+ .form-label { font-size:28rpx; color:rgba(255,255,255,0.8); margin-bottom:18rpx; font-weight:600; }
+ .intro-input, .price-input { width:100%; background:rgba(255,255,255,0.08); border:1.5rpx solid rgba(255,255,255,0.2); border-radius:15rpx; padding:25rpx; font-size:28rpx; color:#FFFFFF; }
+ .intro-input { min-height:180rpx; }
+ .price-input { height:90rpx; line-height:90rpx; text-align:center; font-weight:600; }
+ .add-actions-grid { display:grid; grid-template-columns:repeat(2,1fr); gap:25rpx; padding:30rpx 40rpx 40rpx; border-top:1rpx solid rgba(255,255,255,0.1); }
+ .cancel-grid-btn { background:rgba(255,255,255,0.1); color:rgba(255,255,255,0.8); border:1.5rpx solid rgba(255,255,255,0.2); }
+ .confirm-add-grid-btn { background:linear-gradient(145deg, #9C27B0, #673AB7); color:white; }
+
+ /* 确认弹窗 */
+ .confirm-content {
+ background:linear-gradient(145deg, rgba(40,40,60,0.95), rgba(50,50,70,0.95));
+ border-radius:25rpx; padding:50rpx 40rpx 40rpx; width:85%; max-width:550rpx;
+ border:1.5rpx solid rgba(255,255,255,0.15); box-shadow:0 30rpx 90rpx rgba(0,0,0,0.7); text-align:center;
+ animation:scaleIn 0.3s ease;
+ }
+ @keyframes scaleIn { from{transform:scale(0.8); opacity:0;} to{transform:scale(1); opacity:1;} }
+ .confirm-title { font-size:34rpx; font-weight:bold; color:#FFFFFF; margin-bottom:25rpx; }
+ .confirm-text { font-size:28rpx; color:rgba(255,255,255,0.8); margin-bottom:40rpx; }
+ .confirm-actions-grid { display:grid; grid-template-columns:repeat(2,1fr); gap:25rpx; }
+ .confirm-grid-btn { height:85rpx; border-radius:42rpx; font-size:28rpx; font-weight:600; display:flex; align-items:center; justify-content:center; }
+ .cancel-confirm-grid-btn { background:rgba(255,255,255,0.1); color:rgba(255,255,255,0.8); border:1.5rpx solid rgba(255,255,255,0.2); }
+ .delete-confirm-grid-btn { background:linear-gradient(145deg, #FF4444, #CC0000); color:white; }
+ .confirm-new-link-grid-btn { background:linear-gradient(145deg, #00B4FF, #0091EA); color:white; }
+
+ .loading-mask { position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(18,18,24,0.95); display:flex; align-items:center; justify-content:center; z-index:10000; }
+ .loading-content { background:rgba(40,40,60,0.95); padding:60rpx 80rpx; border-radius:30rpx; display:flex; flex-direction:column; align-items:center; box-shadow:0 25rpx 70rpx rgba(0,0,0,0.6); }
+ .loading-spinner { width:90rpx; height:90rpx; border:8rpx solid rgba(0,180,255,0.2); border-top:8rpx solid #00B4FF; border-radius:50%; animation:spin 1.2s linear infinite; margin-bottom:35rpx; }
+ .loading-text { font-size:30rpx; color:#00B4FF; font-weight:600; }
+ @keyframes spin { 0%{transform:rotate(0deg);} 100%{transform:rotate(360deg);} }
\ No newline at end of file
diff --git a/miniprogram/pages/kaohe_dafen/kaohe_dafen.js b/miniprogram/pages/kaohe_dafen/kaohe_dafen.js
new file mode 100644
index 0000000..c70b1eb
--- /dev/null
+++ b/miniprogram/pages/kaohe_dafen/kaohe_dafen.js
@@ -0,0 +1,237 @@
+// pages/kaohe_dafen/kaohe_dafen.js
+const app = getApp();
+import request from '../../utils/request.js';
+
+Page({
+ data: {
+ recordList: [],
+ page: 1,
+ pageSize: 10,
+ hasMore: false,
+ isLoading: false,
+ ossImageUrl: app.globalData.ossImageUrl || '',
+
+ // 操作弹窗
+ showAgreeModal: false,
+ showRejectModal: false,
+ showTransferModal: false,
+ currentItem: null,
+ rejectReason: '',
+
+ // 倒计时
+ countdown: 2,
+ counting: false,
+ countdownTimer: null,
+ },
+
+ onLoad() {
+ this.loadRecords(true);
+ },
+
+ // 加载考核记录
+ async loadRecords(isRefresh = false) {
+ if (this.data.isLoading) return;
+ const page = isRefresh ? 1 : this.data.page;
+ this.setData({ isLoading: true });
+ try {
+ const res = await request({
+ url: '/dengji/hqkhxq',
+ method: 'POST',
+ data: { page, page_size: this.data.pageSize }
+ });
+ if (res && res.data.code === 0) {
+ const list = res.data.data.list || [];
+ const hasMore = res.data.data.has_more;
+ const oss = this.data.ossImageUrl;
+ const processed = list.map(item => ({
+ ...item,
+ shenqingren_avatar: item.shenqingren_avatar && !item.shenqingren_avatar.startsWith('http')
+ ? oss + item.shenqingren_avatar
+ : item.shenqingren_avatar || '/images/default-avatar.png'
+ }));
+ const newList = isRefresh ? processed : [...this.data.recordList, ...processed];
+ this.setData({ recordList: newList, page: page + 1, hasMore, isLoading: false });
+ } else {
+ wx.showToast({ title: '加载失败', icon: 'none' });
+ this.setData({ isLoading: false });
+ }
+ } catch (e) {
+ wx.showToast({ title: '网络错误', icon: 'none' });
+ this.setData({ isLoading: false });
+ }
+ },
+
+ onReachBottom() {
+ if (this.data.hasMore && !this.data.isLoading) {
+ this.loadRecords(false);
+ }
+ },
+
+ // 联系打手(私聊)—— 照搬金牌页面逻辑
+ contactDashou(e) {
+ const item = e.currentTarget.dataset.item;
+ if (!item) return;
+ if (!item.shenqingren_id) {
+ wx.showToast({ title: '无法获取打手ID', icon: 'none' });
+ return;
+ }
+
+ const uid = wx.getStorageSync('uid');
+ if (!uid) {
+ wx.showToast({ title: '用户信息缺失', icon: 'none' });
+ return;
+ }
+
+ // 1. 设置当前角色为考核官
+ if (app.globalData.currentRole !== 'kaoheguan') {
+ app.globalData.currentRole = 'kaoheguan';
+ wx.setStorageSync('currentRole', 'kaoheguan');
+ }
+
+ // 2. 确保 IM 连接存活
+ if (app.connectForCurrentRole) {
+ app.connectForCurrentRole();
+ } else if (app.ensureConnection) {
+ app.ensureConnection();
+ }
+
+ // 3. 构造私聊参数
+ const param = {
+ toUserId: 'Ds' + item.shenqingren_id,
+ toName: '打手' + item.shenqingren_id,
+ toAvatar: item.shenqingren_avatar || ''
+ };
+
+ // 4. 直接跳转聊天页面
+ wx.navigateTo({
+ url: '/pages/liaotian/liaotian?data=' + encodeURIComponent(JSON.stringify(param)),
+ fail: (err) => {
+ console.error('跳转私聊失败', err);
+ wx.showToast({ title: '打开聊天失败', icon: 'none' });
+ }
+ });
+ },
+
+ // 修改游戏昵称
+ async modifyNick(e) {
+ const { id } = e.currentTarget.dataset;
+ const item = this.data.recordList.find(r => r.jilu_id === id);
+ if (!item) return;
+ const newNick = item.shenheguan_youxi_id || '';
+ try {
+ const res = await request({
+ url: '/dengji/khgxgkh',
+ method: 'POST',
+ data: { jilu_id: id, caozuo: 'modify_nick', youxi_id: newNick }
+ });
+ if (res && res.data.code === 0) {
+ wx.showToast({ title: '修改成功', icon: 'success' });
+ } else {
+ wx.showToast({ title: res.data?.msg || '修改失败', icon: 'none' });
+ }
+ } catch (e) {
+ wx.showToast({ title: '网络错误', icon: 'none' });
+ }
+ },
+
+ onNickInput(e) {
+ const id = e.currentTarget.dataset.id;
+ const value = e.detail.value;
+ const list = this.data.recordList.map(item => {
+ if (item.jilu_id === id) {
+ return { ...item, shenheguan_youxi_id: value };
+ }
+ return item;
+ });
+ this.setData({ recordList: list });
+ },
+
+ // 🆕 实时更新拒绝原因
+ onRejectInput(e) {
+ this.setData({ rejectReason: e.detail.value });
+ },
+
+ openAgreeModal(e) {
+ const item = e.currentTarget.dataset.item;
+ this.setData({ showAgreeModal: true, currentItem: item });
+ this.startCountdown();
+ },
+ openRejectModal(e) {
+ const item = e.currentTarget.dataset.item;
+ this.setData({ showRejectModal: true, currentItem: item, rejectReason: '' });
+ this.startCountdown();
+ },
+ openTransferModal(e) {
+ const item = e.currentTarget.dataset.item;
+ this.setData({ showTransferModal: true, currentItem: item });
+ this.startCountdown();
+ },
+
+ closeAllModals() {
+ this.stopCountdown();
+ this.setData({
+ showAgreeModal: false,
+ showRejectModal: false,
+ showTransferModal: false,
+ currentItem: null,
+ rejectReason: ''
+ });
+ },
+
+ startCountdown() {
+ this.stopCountdown();
+ this.setData({ counting: true, countdown: 2 });
+ this.data.countdownTimer = setInterval(() => {
+ if (this.data.countdown > 1) {
+ this.setData({ countdown: this.data.countdown - 1 });
+ } else {
+ this.stopCountdown();
+ this.setData({ counting: false, countdown: 0 });
+ }
+ }, 1000);
+ },
+ stopCountdown() {
+ if (this.data.countdownTimer) {
+ clearInterval(this.data.countdownTimer);
+ this.data.countdownTimer = null;
+ }
+ },
+
+ // 执行操作(从事件对象中获取操作类型)
+ async executeAction(e) {
+ const actionType = e.currentTarget.dataset.action;
+ if (!actionType) {
+ wx.showToast({ title: '操作类型异常', icon: 'none' });
+ return;
+ }
+
+ const item = this.data.currentItem;
+ if (!item) return;
+
+ const params = { jilu_id: item.jilu_id, caozuo: actionType };
+
+ if (actionType === 'reject') {
+ if (!this.data.rejectReason.trim()) {
+ wx.showToast({ title: '请填写拒绝原因', icon: 'none' });
+ return;
+ }
+ params.yuanyin = this.data.rejectReason;
+ }
+
+ try {
+ const res = await request({ url: '/dengji/khgxgkh', method: 'POST', data: params });
+ if (res && res.data.code === 0) {
+ wx.showToast({ title: '操作成功', icon: 'success' });
+ this.closeAllModals();
+ const newList = this.data.recordList.filter(r => r.jilu_id !== item.jilu_id);
+ this.setData({ recordList: newList });
+ } else {
+ wx.showToast({ title: res.data?.msg || '操作失败', icon: 'none' });
+ this.closeAllModals();
+ }
+ } catch (e) {
+ wx.showToast({ title: '网络错误', icon: 'none' });
+ this.closeAllModals();
+ }
+ },
+});
\ No newline at end of file
diff --git a/miniprogram/pages/kaohe_dafen/kaohe_dafen.json b/miniprogram/pages/kaohe_dafen/kaohe_dafen.json
new file mode 100644
index 0000000..f315be3
--- /dev/null
+++ b/miniprogram/pages/kaohe_dafen/kaohe_dafen.json
@@ -0,0 +1,10 @@
+{
+ "usingComponents": {
+ "chenghao-tag": "/components/chenghao-tag/chenghao-tag",
+ "global-notification": "/components/global-notification/global-notification",
+ "popup-notice": "/components/popup-notice/popup-notice"
+ },
+ "navigationBarTitleText": "考核打分",
+ "navigationBarBackgroundColor": "#0b0c12",
+ "navigationBarTextStyle": "white"
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/kaohe_dafen/kaohe_dafen.wxml b/miniprogram/pages/kaohe_dafen/kaohe_dafen.wxml
new file mode 100644
index 0000000..3094b15
--- /dev/null
+++ b/miniprogram/pages/kaohe_dafen/kaohe_dafen.wxml
@@ -0,0 +1,118 @@
+
+
+
+
+
+
+
+
+
+
+ UID {{item.shenqingren_id}}
+ {{item.create_time}}
+
+ 联系
+
+
+
+
+
+ 板块
+ {{item.bankuai_mingcheng}}
+
+
+ 标签
+
+ (开启金牌)
+
+
+ 规则
+ {{item.guize_neirong}}
+
+
+
+ 打手游戏昵称
+ {{item.dashou_youxi_id}}
+
+
+ 备注
+ {{item.dashou_beizhu}}
+
+
+ 次数
+ {{item.cishu}}
+
+
+ 待收考核费
+ ¥{{item.daishou_fei}}
+
+
+
+
+
+ 上次拒绝原因:
+ {{item.last_fail_reason}}
+
+
+
+
+ 我的游戏昵称
+
+ 保存
+
+
+
+
+ 合格
+ 不合格
+ 转移
+
+
+
+ 加载中...
+ —— 已加载全部 ——
+ 暂无待考核记录
+
+
+
+
+
+
+
+
+ 通过后,该用户将获得对应标签,请认真审核后操作。
+ {{countdown}}秒后可确认
+
+
+
+
+
+
+
+
+
+ {{countdown}}秒后可确认
+
+
+
+
+
+
+
+
+ 转移后,您将无法继续考核此订单,可在考核中心重新接待。
+ {{countdown}}秒后可确认
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/kaohe_dafen/kaohe_dafen.wxss b/miniprogram/pages/kaohe_dafen/kaohe_dafen.wxss
new file mode 100644
index 0000000..f736716
--- /dev/null
+++ b/miniprogram/pages/kaohe_dafen/kaohe_dafen.wxss
@@ -0,0 +1,170 @@
+/* 根容器,全屏且底部固定 */
+.root-page {
+ width: 100vw;
+ height: 100vh;
+ background: radial-gradient(ellipse at 20% 20%, #1a1e2b 0%, #0b0c12 100%);
+ display: flex;
+ flex-direction: column;
+ }
+
+ /* 滚动区域 */
+ .scroll-container {
+ flex: 1;
+ overflow-y: auto;
+ padding: 30rpx 30rpx 0;
+ padding-bottom: constant(safe-area-inset-bottom);
+ padding-bottom: env(safe-area-inset-bottom);
+ }
+
+ /* 卡片 */
+ .card {
+ background: rgba(25, 27, 36, 0.8);
+ backdrop-filter: blur(20px);
+ border: 1rpx solid rgba(201, 169, 88, 0.15);
+ border-radius: 24rpx;
+ padding: 30rpx;
+ margin-bottom: 30rpx;
+ box-shadow: 0 8rpx 30rpx rgba(0,0,0,0.5);
+ }
+
+ /* 用户信息 */
+ .user-row {
+ display: flex;
+ align-items: center;
+ margin-bottom: 30rpx;
+ }
+ .avatar {
+ width: 80rpx; height: 80rpx;
+ border-radius: 50%;
+ border: 2rpx solid rgba(201,169,88,0.4);
+ margin-right: 20rpx;
+ }
+ .info { flex: 1; }
+ .uid { font-size: 30rpx; color: #f0f2ff; font-weight: 600; }
+ .time { font-size: 22rpx; color: #6a7090; margin-top: 4rpx; }
+ .contact-btn {
+ background: rgba(201,169,88,0.15);
+ border: 1rpx solid rgba(201,169,88,0.3);
+ border-radius: 20rpx;
+ padding: 8rpx 24rpx;
+ font-size: 24rpx;
+ color: #c9a558;
+ }
+
+ /* 详情网格 */
+ .detail-grid {
+ border-bottom: 1rpx solid rgba(255,255,255,0.06);
+ margin-bottom: 20rpx;
+ }
+ .detail-item {
+ display: flex;
+ align-items: center;
+ padding: 16rpx 0;
+ border-bottom: 1rpx solid rgba(255,255,255,0.04);
+ }
+ .detail-item:last-child { border-bottom: none; }
+ .label { font-size: 26rpx; color: #8a90b0; width: 160rpx; flex-shrink: 0; }
+ .value { font-size: 26rpx; color: #d0d4ff; font-weight: 500; }
+ .rule-content { white-space: pre-wrap; line-height: 1.5; }
+ .jinpai-note { font-size: 24rpx; color: #c9a558; font-weight: 600; margin-left: 10rpx; }
+
+ /* 失败原因 */
+ .fail-box {
+ background: rgba(255, 70, 70, 0.1);
+ border-left: 4rpx solid #ff5e5e;
+ padding: 16rpx;
+ border-radius: 8rpx;
+ margin-bottom: 20rpx;
+ }
+ .fail-label { font-size: 24rpx; color: #ff8a8a; }
+ .fail-text { font-size: 24rpx; color: #ffd0d0; }
+
+ /* 昵称修改 */
+ .nick-row {
+ display: flex;
+ align-items: center;
+ margin-bottom: 30rpx;
+ }
+ .nick-input {
+ flex: 1;
+ background: rgba(255,255,255,0.06);
+ border: 1rpx solid rgba(255,255,255,0.1);
+ border-radius: 12rpx;
+ padding: 12rpx 20rpx;
+ color: #fff;
+ font-size: 26rpx;
+ margin-right: 20rpx;
+ }
+ .save-nick-btn {
+ background: #c9a558;
+ color: #0b0c12;
+ padding: 12rpx 30rpx;
+ border-radius: 30rpx;
+ font-size: 26rpx;
+ font-weight: 600;
+ }
+
+ /* 操作按钮 */
+ .action-row {
+ display: flex;
+ gap: 20rpx;
+ margin-top: 20rpx;
+ }
+ .action-btn {
+ flex: 1;
+ height: 80rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 40rpx;
+ font-size: 28rpx;
+ font-weight: 600;
+ color: #fff;
+ }
+ .agree { background: linear-gradient(135deg, #c9a558, #9e7d2e); }
+ .reject { background: linear-gradient(135deg, #a85050, #802a2a); }
+ .transfer { background: linear-gradient(135deg, #4a6090, #2a3a60); }
+
+ /* 加载/空状态 */
+ .loading, .no-more, .empty { text-align: center; padding: 30rpx; color: #6a7090; font-size: 28rpx; }
+
+ /* 弹窗 */
+ .modal-mask {
+ position: fixed;
+ top: 0; left: 0; right: 0; bottom: 0;
+ background: rgba(0,0,0,0.7);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+ }
+ .modal-box {
+ width: 80%;
+ background: #1e2030;
+ border-radius: 24rpx;
+ padding: 30rpx;
+ border: 1rpx solid rgba(201,169,88,0.2);
+ }
+ .modal-header { font-size: 32rpx; color: #f0f2ff; font-weight: 600; margin-bottom: 20rpx; }
+ .modal-body { font-size: 26rpx; color: #b0b8d0; margin-bottom: 20rpx; }
+ .reason-input {
+ width: 100%;
+ height: 200rpx;
+ background: rgba(255,255,255,0.05);
+ border-radius: 12rpx;
+ padding: 15rpx;
+ color: #fff;
+ font-size: 26rpx;
+ box-sizing: border-box;
+ margin-bottom: 20rpx;
+ }
+ .countdown-text { text-align: center; font-size: 28rpx; color: #c9a558; margin-bottom: 20rpx; }
+ .modal-footer { display: flex; gap: 20rpx; }
+ .btn {
+ flex: 1; height: 80rpx; border-radius: 40rpx;
+ display: flex; align-items: center; justify-content: center;
+ font-size: 28rpx; font-weight: 600;
+ }
+ .cancel { background: #2a2f3e; color: #fff; }
+ .confirm { background: linear-gradient(135deg, #c9a558, #9e7d2e); color: #0b0c12; }
+ .confirm.disabled { opacity: 0.4; pointer-events: none; }
\ No newline at end of file
diff --git a/miniprogram/pages/kaohe_jilu/kaohe_jilu.js b/miniprogram/pages/kaohe_jilu/kaohe_jilu.js
new file mode 100644
index 0000000..545e567
--- /dev/null
+++ b/miniprogram/pages/kaohe_jilu/kaohe_jilu.js
@@ -0,0 +1,149 @@
+// pages/kaohe_jilu/kaohe_jilu.js
+const app = getApp();
+import request from '../../utils/request.js';
+
+Page({
+ data: {
+ // 板块筛选
+ moduleList: [],
+ xuanzhongBankuaiId: 0, // 0代表全部
+ // 状态筛选:0-全部 1-审核中 2-已通过 3-未通过
+ statusFilter: 0,
+ // 记录列表
+ recordList: [],
+ page: 1,
+ pageSize: 5,
+ hasMore: true,
+ isLoading: false,
+ isLoadingMore: false,
+ // 展开的记录ID集合(字符串数组)
+ expandedIds: [],
+ // OSS地址
+ ossImageUrl: app.globalData.ossImageUrl || '',
+ },
+
+ onLoad() {
+ this.loadModules();
+ },
+
+ // 加载板块(复用审核大厅接口)
+ async loadModules() {
+ try {
+ const res = await request({ url: '/dengji/shdthqmk', method: 'POST' });
+ if (res && res.data.code === 0) {
+ const modules = res.data.data.modules || [];
+ this.setData({ moduleList: modules, xuanzhongBankuaiId: 0 });
+ this.loadRecords(true);
+ } else {
+ wx.showToast({ title: '加载模块失败', icon: 'none' });
+ }
+ } catch (e) {
+ wx.showToast({ title: '网络错误', icon: 'none' });
+ }
+ },
+
+ // 选择板块
+ selectModule(e) {
+ const bankuaiId = e.currentTarget.dataset.id;
+ if (bankuaiId === this.data.xuanzhongBankuaiId) return;
+ this.setData({
+ xuanzhongBankuaiId: bankuaiId,
+ recordList: [],
+ page: 1,
+ hasMore: true,
+ expandedIds: []
+ });
+ this.loadRecords(true);
+ },
+
+ // 选择状态
+ selectStatus(e) {
+ const status = e.currentTarget.dataset.status;
+ if (status === this.data.statusFilter) return;
+ this.setData({
+ statusFilter: status,
+ recordList: [],
+ page: 1,
+ hasMore: true,
+ expandedIds: []
+ });
+ this.loadRecords(true);
+ },
+
+ // 加载历史记录
+ async loadRecords(isRefresh = false) {
+ if (this.data.isLoading) return;
+ const page = isRefresh ? 1 : this.data.page;
+ if (!isRefresh && !this.data.hasMore) return;
+
+ this.setData({ isLoading: true, isLoadingMore: !isRefresh });
+
+ try {
+ const res = await request({
+ url: '/dengji/khghqkhjl',
+ method: 'POST',
+ data: {
+ bankuai_id: this.data.xuanzhongBankuaiId || undefined,
+ status: this.data.statusFilter || undefined,
+ page: page,
+ page_size: this.data.pageSize
+ }
+ });
+
+ this.setData({ isLoading: false, isLoadingMore: false });
+
+ if (res && res.data.code === 0) {
+ const list = res.data.data.list || [];
+ const hasMore = res.data.data.has_more;
+ // 拼接头像
+ const oss = this.data.ossImageUrl;
+ const processedList = list.map(item => {
+ let avatar = item.shenqingren_avatar || '';
+ if (avatar && !avatar.startsWith('http')) {
+ avatar = oss + avatar;
+ }
+ return { ...item, shenqingren_avatar: avatar };
+ });
+ const newList = isRefresh ? processedList : [...this.data.recordList, ...processedList];
+ this.setData({ recordList: newList, page: page + 1, hasMore: hasMore });
+ } else {
+ wx.showToast({ title: '加载记录失败', icon: 'none' });
+ }
+ } catch (e) {
+ this.setData({ isLoading: false, isLoadingMore: false });
+ wx.showToast({ title: '网络错误', icon: 'none' });
+ }
+ },
+
+ // 上拉加载更多
+ onReachBottom() {
+ if (this.data.hasMore && !this.data.isLoading && !this.data.isLoadingMore) {
+ this.loadRecords(false);
+ }
+ },
+
+ // 展开/收起详情
+ toggleExpand(e) {
+ const jiluId = e.currentTarget.dataset.id;
+ // 确保 jiluId 是字符串
+ const id = String(jiluId);
+ const expandedIds = this.data.expandedIds.slice(); // 拷贝
+ const index = expandedIds.indexOf(id);
+ if (index > -1) {
+ expandedIds.splice(index, 1);
+ } else {
+ expandedIds.push(id);
+ }
+ this.setData({ expandedIds });
+ },
+
+ // 去评判(跳转考核打分页)
+ goJudge(e) {
+ const item = e.currentTarget.dataset.item;
+ if (!item) return;
+ wx.navigateTo({ url: '/pages/kaohe_dafen/kaohe_dafen?jilu_id=' + item.jilu_id });
+ },
+
+ // 阻止冒泡
+ noop() {}
+});
\ No newline at end of file
diff --git a/miniprogram/pages/kaohe_jilu/kaohe_jilu.json b/miniprogram/pages/kaohe_jilu/kaohe_jilu.json
new file mode 100644
index 0000000..f6a68c8
--- /dev/null
+++ b/miniprogram/pages/kaohe_jilu/kaohe_jilu.json
@@ -0,0 +1,9 @@
+{
+ "usingComponents": {
+ "chenghao-tag": "/components/chenghao-tag/chenghao-tag",
+ "global-notification": "/components/global-notification/global-notification"
+ },
+ "navigationBarTitleText": "考核记录",
+ "navigationBarBackgroundColor": "#FDFBF7",
+ "navigationBarTextStyle": "black"
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/kaohe_jilu/kaohe_jilu.wxml b/miniprogram/pages/kaohe_jilu/kaohe_jilu.wxml
new file mode 100644
index 0000000..b8ac9ff
--- /dev/null
+++ b/miniprogram/pages/kaohe_jilu/kaohe_jilu.wxml
@@ -0,0 +1,130 @@
+
+
+
+
+
+
+
+
+ 全部板块
+
+
+
+ {{item.mingcheng}}
+
+
+
+
+
+
+ 全部
+ 审核中
+ 已通过
+ 未通过
+
+
+
+
+
+
+
+ 暂无考核记录
+
+
+
+
+
+
+
+
+
+ 板块
+ {{item.bankuai_mingcheng}}
+
+
+ 标签
+
+
+
+ 次数
+ {{item.cishu}}次
+
+
+ 总费用
+ ¥{{item.total_fee}}
+
+
+
+
+
+
+ {{expandedIds.indexOf(item.jilu_id) > -1 ? '收起详情' : '展开详情'}}
+
+
+ 去评判
+
+
+
+
+
+
+ 考核规则
+ {{item.guize_neirong}}
+
+
+ 缴纳记录
+
+ 第{{item.cishu}}次:¥{{item.jine}}
+
+ 暂无缴纳记录
+
+
+ 拒绝记录
+
+
+ {{item.create_time}}
+ 第{{item.cishu}}次拒绝:{{item.yuanyin}}
+
+
+ 暂无拒绝记录
+
+
+ 其他信息
+
+ 打手游戏ID:{{item.dashou_youxi_id || '无'}}
+ 打手备注:{{item.dashou_beizhu || '无'}}
+ 审核官游戏ID:{{item.shenheguan_youxi_id || '无'}}
+ 创建时间:{{item.create_time}}
+ 更新时间:{{item.update_time}}
+
+
+
+
+
+
+ 加载更多...
+ —— 没有更多了 ——
+
+
+
+
+
+
+
+ 加载中...
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/kaohe_jilu/kaohe_jilu.wxss b/miniprogram/pages/kaohe_jilu/kaohe_jilu.wxss
new file mode 100644
index 0000000..bf37003
--- /dev/null
+++ b/miniprogram/pages/kaohe_jilu/kaohe_jilu.wxss
@@ -0,0 +1,263 @@
+/* pages/kaohe_jilu/kaohe_jilu.wxss - 晨露微光风格(左右均等边距) */
+page {
+ background: linear-gradient(135deg, #FDFBF7 0%, #F2EFE9 100%);
+ color: #4A4A4A;
+ font-family: 'SF Pro Display', 'PingFang SC', system-ui;
+ height: 100vh;
+ overflow: hidden;
+ }
+
+ .page-root {
+ height: 100vh;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ }
+
+ /* 筛选栏 */
+ .filter-bar {
+ background: rgba(255, 255, 255, 0.8);
+ backdrop-filter: blur(20px);
+ border-bottom: 1rpx solid rgba(0,0,0,0.04);
+ flex-shrink: 0;
+ padding: 20rpx 0 0;
+ }
+
+ .module-scroll {
+ white-space: nowrap;
+ height: 70rpx;
+ padding: 0 30rpx;
+ }
+ .module-container {
+ display: inline-flex;
+ gap: 20rpx;
+ align-items: center;
+ height: 70rpx;
+ }
+
+ .filter-item {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0 30rpx;
+ height: 56rpx;
+ border-radius: 28rpx;
+ background: #F0EFEC;
+ font-size: 26rpx;
+ color: #6B6B6B;
+ transition: all 0.2s;
+ }
+ .filter-item.active {
+ background: #C8A27A;
+ color: #FFFFFF;
+ font-weight: 600;
+ box-shadow: 0 4rpx 12rpx rgba(200, 162, 122, 0.3);
+ }
+
+ .status-row {
+ display: flex;
+ padding: 15rpx 30rpx 20rpx;
+ gap: 30rpx;
+ }
+ .status-item {
+ font-size: 26rpx;
+ color: #8B8B8B;
+ padding-bottom: 8rpx;
+ border-bottom: 4rpx solid transparent;
+ transition: all 0.2s;
+ }
+ .status-item.active {
+ color: #C8A27A;
+ border-bottom-color: #C8A27A;
+ font-weight: 600;
+ }
+
+ /* 列表容器 */
+ .list-container {
+ flex: 1;
+ overflow: hidden;
+ }
+ .list-scroll {
+ height: 100%;
+ padding: 20rpx 0; /* 上下内边距 */
+ }
+
+ .empty-tip {
+ text-align: center;
+ color: #A0A0A0;
+ font-size: 28rpx;
+ padding-top: 200rpx;
+ }
+
+ /* 卡片:统一左右边距 30rpx */
+ .card {
+ background: #FFFFFF;
+ border-radius: 28rpx;
+ padding: 30rpx;
+ margin: 0 30rpx 24rpx 30rpx; /* 左右严格相等 */
+ box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.04);
+ transition: all 0.3s;
+ }
+
+ .card-header {
+ display: flex;
+ align-items: center;
+ margin-bottom: 24rpx;
+ }
+ .avatar {
+ width: 72rpx;
+ height: 72rpx;
+ border-radius: 50%;
+ margin-right: 18rpx;
+ border: 2rpx solid #F0EDE8;
+ background: #F7F5F2;
+ }
+ .user-info {
+ flex: 1;
+ }
+ .nickname {
+ font-size: 30rpx;
+ font-weight: 600;
+ color: #3A3A3A;
+ display: block;
+ }
+ .uid {
+ font-size: 24rpx;
+ color: #9A9A9A;
+ margin-top: 4rpx;
+ }
+ .status-tag {
+ font-size: 24rpx;
+ padding: 6rpx 18rpx;
+ border-radius: 20rpx;
+ font-weight: 500;
+ }
+ .status-0 { background: #FFF3E0; color: #EF6C00; }
+ .status-1 { background: #E8F5E9; color: #2E7D32; }
+ .status-2 { background: #FFEBEE; color: #C62828; }
+ .status-3 { background: #E3F2FD; color: #1565C0; }
+
+ .card-summary {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 16rpx 24rpx;
+ margin-bottom: 20rpx;
+ }
+ .summary-item {
+ display: flex;
+ align-items: center;
+ font-size: 26rpx;
+ }
+ .summary-item .label {
+ color: #8B8B8B;
+ margin-right: 12rpx;
+ }
+ .summary-item .value {
+ color: #3A3A3A;
+ font-weight: 500;
+ }
+
+ .card-actions {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding-top: 20rpx;
+ border-top: 1rpx solid #F5F2ED;
+ }
+ .expand-btn {
+ font-size: 26rpx;
+ color: #C8A27A;
+ font-weight: 500;
+ }
+ .judge-btn {
+ font-size: 26rpx;
+ background: #C8A27A;
+ color: #FFFFFF;
+ padding: 8rpx 24rpx;
+ border-radius: 24rpx;
+ font-weight: 500;
+ }
+
+ /* 展开详情(左右边距继承卡片,无需额外设置) */
+ .card-detail {
+ margin-top: 20rpx;
+ padding-top: 20rpx;
+ border-top: 1rpx solid #F5F2ED;
+ }
+ .detail-section {
+ margin-bottom: 20rpx;
+ }
+ .detail-title {
+ font-size: 26rpx;
+ font-weight: 600;
+ color: #5A5A5A;
+ margin-bottom: 10rpx;
+ display: block;
+ }
+ .detail-text {
+ font-size: 24rpx;
+ color: #6B6B6B;
+ line-height: 1.6;
+ }
+ .fee-row, .reject-row {
+ font-size: 24rpx;
+ color: #6B6B6B;
+ padding: 6rpx 0;
+ }
+ .reject-item {
+ display: flex;
+ flex-direction: column;
+ background: #FFF9F5;
+ padding: 12rpx 16rpx;
+ border-radius: 12rpx;
+ margin-bottom: 12rpx;
+ }
+ .reject-time {
+ font-size: 22rpx;
+ color: #B0A090;
+ margin-bottom: 4rpx;
+ }
+ .reject-reason {
+ font-size: 24rpx;
+ color: #7A5A4A;
+ }
+ .detail-empty {
+ font-size: 24rpx;
+ color: #A0A0A0;
+ padding: 8rpx 0;
+ }
+ .info-grid {
+ display: flex;
+ flex-direction: column;
+ gap: 8rpx;
+ font-size: 24rpx;
+ color: #6B6B6B;
+ }
+
+ /* 加载 */
+ .loading-more, .no-more {
+ text-align: center;
+ padding: 30rpx;
+ font-size: 24rpx;
+ color: #A0A0A0;
+ }
+ .loading-mask {
+ position: fixed;
+ top: 0; left: 0; right: 0; bottom: 0;
+ background: rgba(253, 251, 247, 0.8);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ z-index: 999;
+ }
+ .loading-spinner {
+ width: 60rpx;
+ height: 60rpx;
+ border: 4rpx solid #E0DDD5;
+ border-top-color: #C8A27A;
+ border-radius: 50%;
+ animation: spin 0.8s linear infinite;
+ margin-bottom: 20rpx;
+ }
+ @keyframes spin { to { transform: rotate(360deg); } }
\ No newline at end of file
diff --git a/miniprogram/pages/kaohe_jinpai/kaohe_jinpai.js b/miniprogram/pages/kaohe_jinpai/kaohe_jinpai.js
new file mode 100644
index 0000000..ed6fcb4
--- /dev/null
+++ b/miniprogram/pages/kaohe_jinpai/kaohe_jinpai.js
@@ -0,0 +1,461 @@
+// pages/kaohe_jinpai/kaohe_jinpai.js
+const app = getApp();
+import request from '../../utils/request.js';
+
+Page({
+ data: {
+ activeTab: 'all_tags',
+ plateList: [], // 第一个接口返回的所有板块及标签(含 fee_list)
+ myTags: [],
+ recordList: [],
+ recordPage: 1,
+ recordHasMore: true,
+ recordLoading: false,
+ recordFilter: 'pending',
+ showApplyModal: false,
+ selectedTag: null,
+ applyGameNick: '',
+ applyRemark: '',
+ applyFee: 0,
+ applyJiluId: '',
+ isReapply: false,
+ payLoading: false,
+ ossImageUrl: app.globalData.ossImageUrl || '',
+
+ // 新增:考核官相关
+ kaoheguanList: [], // 所有可选的考核官
+ showKaoheguanModal: false, // 考核官选择弹窗
+ selectedKaoheguanId: '', // 已选择的考核官ID
+ selectedKaoheguanInfo: null, // 已选择的考核官信息(用于展示)
+ searchKeyword: '', // 搜索关键词
+ filteredKaoheguanList: [], // 搜索过滤后的列表
+ },
+
+ onLoad() {
+ this.fetchAllTags();
+ this.fetchMyRecords(true);
+ },
+
+ switchTab(e) {
+ const tab = e.currentTarget.dataset.tab;
+ if (tab === this.data.activeTab) return;
+ this.setData({ activeTab: tab });
+ if (tab === 'my_records') this.fetchMyRecords(true);
+ },
+
+ // 获取所有板块及标签(含费用列表),缓存到 plateList,同时获取考核官列表
+ async fetchAllTags() {
+ try {
+ const res = await request({ url: '/dengji/dskhhq', method: 'POST' });
+ if (res.data.code === 0) {
+ const { plate_list, my_tags, kaoheguan_list } = res.data.data;
+ // 拼接考核官头像为完整URL
+ const ossUrl = this.data.ossImageUrl;
+ const processedKgList = (kaoheguan_list || []).map(kg => ({
+ ...kg,
+ avatar: kg.avatar && !kg.avatar.startsWith('http') ? ossUrl + kg.avatar : kg.avatar,
+ }));
+ this.setData({
+ plateList: plate_list || [],
+ myTags: my_tags || [],
+ kaoheguanList: processedKgList // 使用拼接后的列表
+ });
+ }
+ } catch (e) {}
+ },
+
+ // 获取我的考核记录,并根据 plateList 计算本次/预计下次费用
+ async fetchMyRecords(isRefresh = false) {
+ if (this.data.recordLoading) return;
+ const page = isRefresh ? 1 : this.data.recordPage;
+ this.setData({ recordLoading: true });
+ try {
+ const res = await request({
+ url: '/dengji/dskhjl',
+ method: 'POST',
+ data: { status: this.data.recordFilter, page, page_size: 5 }
+ });
+ if (res.data.code === 0) {
+ const { list, has_more } = res.data.data;
+
+ // 从 plateList 构建标签ID -> 完整标签对象的映射(用于精确匹配费用)
+ const tagMap = {};
+ this.data.plateList.forEach(plate => {
+ (plate.tags || []).forEach(tag => {
+ tagMap[tag.id] = tag; // tag 包含 fee_list
+ });
+ });
+
+ // 处理每条记录:优先使用 tag_obj.id 匹配,其次 chenghao_id
+ const processed = list.map(item => {
+ // 尝试多种方式获取标签ID
+ const tagId = (item.tag_obj && item.tag_obj.id) || item.chenghao_id;
+ const tag = tagMap[tagId] || item.tag_obj || {};
+ const feeList = tag.fee_list || [];
+ let currentFee = 0;
+ let nextFee = 0;
+ if (feeList.length > 0) {
+ const cishu = item.cishu;
+ const cur = feeList.find(f => f.cishu === cishu);
+ currentFee = cur ? cur.feiyong : feeList[feeList.length - 1].feiyong;
+ const nextCishu = cishu + 1;
+ const nxt = feeList.find(f => f.cishu === nextCishu);
+ nextFee = nxt ? nxt.feiyong : feeList[feeList.length - 1].feiyong;
+ }
+ return {
+ ...item,
+ current_fee: currentFee,
+ next_fee: nextFee,
+ tag_obj: tag // 使用匹配到的完整标签对象,保证后续“再次考核”能拿到费用
+ };
+ });
+
+ const newList = isRefresh ? processed : [...this.data.recordList, ...processed];
+ this.setData({
+ recordList: newList,
+ recordPage: page + 1,
+ recordHasMore: has_more,
+ recordLoading: false
+ });
+ } else {
+ this.setData({ recordLoading: false });
+ }
+ } catch (e) {
+ this.setData({ recordLoading: false });
+ }
+ },
+
+ filterRecord(e) {
+ const filter = e.currentTarget.dataset.filter;
+ if (filter === this.data.recordFilter) return;
+ this.setData({ recordFilter: filter, recordList: [], recordPage: 1, recordHasMore: true });
+ this.fetchMyRecords(true);
+ },
+
+ onReachBottom() {
+ if (this.data.activeTab === 'my_records' && this.data.recordHasMore) {
+ this.fetchMyRecords(false);
+ }
+ },
+
+ // 打开申请考核弹窗,费用完全基于 plateList 中的标签数据
+ openApplyModal(e) {
+ const { tag, jiluid, reapply } = e.currentTarget.dataset;
+ // 查找完整标签对象,保证 fee_list 存在
+ let fullTag = tag;
+ if (fullTag && fullTag.fee_list === undefined) {
+ const found = this.findTagInPlateList(fullTag.id);
+ if (found) fullTag = found;
+ }
+ if (!fullTag) {
+ const found = this.findTagInPlateList(tag);
+ if (found) fullTag = found;
+ }
+
+ let fee = 0;
+ if (fullTag) {
+ let nextCishu = 0;
+ // 再次申请时,基于原记录次数计算下一次考核次数
+ if (reapply && jiluid) {
+ const record = this.data.recordList.find(r => r.jilu_id === jiluid);
+ if (record) {
+ nextCishu = record.cishu + 1;
+ } else {
+ nextCishu = (fullTag.my_cishu || 0) + 1;
+ }
+ } else {
+ nextCishu = (fullTag.my_cishu || 0) + 1;
+ }
+ const feeList = fullTag.fee_list || [];
+ const exact = feeList.find(f => f.cishu === nextCishu);
+ if (exact) {
+ fee = exact.feiyong;
+ } else if (feeList.length > 0) {
+ fee = feeList[feeList.length - 1].feiyong;
+ }
+ }
+ // 检查再次申请时原记录是否有考核官,若有则不允许指定
+ let canSpecify = true;
+ let originalShenheguanId = '';
+ if (reapply && jiluid) {
+ const record = this.data.recordList.find(r => r.jilu_id === jiluid);
+ if (record && record.shenheguan_id) {
+ canSpecify = false; // 已有考核官,不能指定新的
+ originalShenheguanId = record.shenheguan_id;
+ }
+ }
+ this.setData({
+ showApplyModal: true,
+ selectedTag: fullTag || null,
+ applyGameNick: '',
+ applyRemark: '',
+ applyFee: fee,
+ applyJiluId: jiluid || '',
+ isReapply: reapply || false,
+ selectedKaoheguanId: '', // 重置
+ selectedKaoheguanInfo: null,
+ canSpecify: canSpecify, // 新增
+ originalShenheguanId: originalShenheguanId, // 新增
+ });
+ },
+
+ // 辅助方法:从 plateList 查找指定 id 的标签
+ findTagInPlateList(tagId) {
+ if (!tagId) return null;
+ for (const plate of this.data.plateList) {
+ for (const t of (plate.tags || [])) {
+ if (t.id === tagId) return t;
+ }
+ }
+ return null;
+ },
+
+ closeApplyModal() {
+ this.setData({ showApplyModal: false });
+ },
+
+ // 实时更新游戏昵称
+ onNickInput(e) {
+ this.setData({ applyGameNick: e.detail.value });
+ },
+
+ // 实时更新备注
+ onRemarkInput(e) {
+ this.setData({ applyRemark: e.detail.value });
+ },
+
+ // 新增:打开考核官选择弹窗
+ openKaoheguanModal() {
+ if (!this.data.canSpecify) {
+ wx.showToast({ title: '已有考核官,无法重新指定', icon: 'none' });
+ return;
+ }
+ // 根据费用筛选可选考核官
+ let all = [...this.data.kaoheguanList];
+ if (this.data.applyFee > 0) {
+ all = all.filter(kg => kg.is_renzheng);
+ }
+ this.setData({
+ showKaoheguanModal: true,
+ filteredKaoheguanList: all,
+ searchKeyword: '',
+ });
+ },
+
+ // 关闭考核官选择弹窗
+ closeKaoheguanModal() {
+ this.setData({ showKaoheguanModal: false });
+ },
+
+ // 搜索输入
+ onSearchInput(e) {
+ const value = e.detail.value.trim();
+ this.setData({ searchKeyword: value });
+ this.filterKaoheguanList(value);
+ },
+
+ // 过滤考核官列表
+ filterKaoheguanList(keyword) {
+ let all = [...this.data.kaoheguanList];
+ if (this.data.applyFee > 0) {
+ all = all.filter(kg => kg.is_renzheng);
+ }
+ if (keyword) {
+ all = all.filter(kg =>
+ kg.yonghuid.includes(keyword) ||
+ (kg.dashou_nick && kg.dashou_nick.includes(keyword))
+ );
+ }
+ this.setData({ filteredKaoheguanList: all });
+ },
+
+ // 选择一个考核官
+ selectKaoheguan(e) {
+ const kg = e.currentTarget.dataset.kg;
+ if (kg.shenhe_zhuangtai !== 0) {
+ wx.showToast({ title: '该考核官正忙,无法指定', icon: 'none' });
+ return;
+ }
+ this.setData({
+ selectedKaoheguanId: kg.yonghuid,
+ selectedKaoheguanInfo: kg,
+ showKaoheguanModal: false,
+ });
+ },
+
+ // 取消已选择的考核官
+ clearSelectedKaoheguan() {
+ this.setData({
+ selectedKaoheguanId: '',
+ selectedKaoheguanInfo: null,
+ });
+ },
+
+ // 提交申请
+ async submitApply() {
+ const { applyGameNick, applyRemark, applyFee, selectedTag, isReapply, applyJiluId, selectedKaoheguanId } = this.data;
+ if (!applyGameNick || !applyGameNick.trim()) {
+ wx.showToast({ title: '请输入游戏昵称', icon: 'none' });
+ return;
+ }
+ if (!applyRemark || !applyRemark.trim()) {
+ wx.showToast({ title: '请填写备注', icon: 'none' });
+ return;
+ }
+ if (applyFee > 0) {
+ await this.startPayProcess();
+ } else {
+ await this.doFreeApply();
+ }
+ },
+
+ // 支付流程
+ async startPayProcess() {
+ this.setData({ payLoading: true });
+ try {
+ const res = await request({
+ url: '/yonghu/khzf',
+ method: 'POST',
+ data: {
+ tag_id: this.data.selectedTag.id,
+ game_nick: this.data.applyGameNick,
+ remark: this.data.applyRemark,
+ reapply: this.data.isReapply,
+ jilu_id: this.data.applyJiluId,
+ shenheguan_id: this.data.selectedKaoheguanId, // 新增
+ }
+ });
+ if (res.data.code !== 200) throw new Error(res.data.message || '下单失败');
+ const payParams = res.data.payParams;
+ const dingdanid = res.data.dingdanid;
+ wx.requestPayment({
+ timeStamp: payParams.timeStamp,
+ nonceStr: payParams.nonceStr,
+ package: payParams.package,
+ signType: payParams.signType || 'MD5',
+ paySign: payParams.paySign,
+ success: async () => {
+ await this.pollPayStatus(dingdanid);
+ },
+ fail: () => {
+ wx.showToast({ title: '支付取消或失败', icon: 'none' });
+ request({ url: '/yonghu/khzfsb', method: 'POST', data: { dingdanid } }).catch(()=>{});
+ }
+ });
+ } catch (e) {
+ wx.showToast({ title: e.message || '支付异常', icon: 'none' });
+ } finally {
+ this.setData({ payLoading: false });
+ }
+ },
+
+ // 轮询支付结果
+ async pollPayStatus(dingdanid) {
+ for (let i = 0; i < 10; i++) {
+ try {
+ const res = await request({ url: '/yonghu/khzffc', method: 'POST', data: { dingdanid } });
+ if (res.data.code === 200) {
+ wx.showToast({ title: '申请成功', icon: 'success' });
+ this.closeApplyModal();
+ this.fetchAllTags();
+ this.setData({ recordList: [], recordPage: 1 });
+ this.fetchMyRecords(true);
+ return;
+ }
+ } catch (e) {}
+ await new Promise(r => setTimeout(r, 2000));
+ }
+ wx.showToast({ title: '支付确认超时', icon: 'none' });
+ },
+
+ // 免费申请
+ async doFreeApply() {
+ try {
+ const res = await request({
+ url: '/dengji/dssqkh',
+ method: 'POST',
+ data: {
+ tag_id: this.data.selectedTag.id,
+ game_nick: this.data.applyGameNick,
+ remark: this.data.applyRemark,
+ reapply: this.data.isReapply,
+ jilu_id: this.data.applyJiluId,
+ shenheguan_id: this.data.selectedKaoheguanId, // 新增
+ }
+ });
+ if (res.data.code === 0) {
+ wx.showToast({ title: '申请成功', icon: 'success' });
+ this.closeApplyModal();
+ this.fetchAllTags();
+ this.setData({ recordList: [], recordPage: 1 });
+ this.fetchMyRecords(true);
+ } else {
+ wx.showToast({ title: res.data.msg || '申请失败', icon: 'none' });
+ }
+ } catch (e) {
+ wx.showToast({ title: '网络错误', icon: 'none' });
+ }
+ },
+
+ // 联系考核官(私聊)
+ contactKaoheguan(e) {
+ const item = e.currentTarget.dataset.item;
+ if (!item) return;
+ if (!item.shenheguan_id) {
+ wx.showToast({ title: '暂无考核官信息', icon: 'none' });
+ return;
+ }
+ const uid = wx.getStorageSync('uid');
+ if (!uid) {
+ wx.showToast({ title: '用户信息缺失', icon: 'none' });
+ return;
+ }
+ if (app.globalData.currentRole !== 'dashou') {
+ app.globalData.currentRole = 'dashou';
+ wx.setStorageSync('currentRole', 'dashou');
+ }
+ if (app.connectForCurrentRole) {
+ app.connectForCurrentRole();
+ } else if (app.ensureConnection) {
+ app.ensureConnection();
+ }
+ const ossImageUrl = this.data.ossImageUrl || app.globalData.ossImageUrl || '';
+ let fullAvatar = item.shenheguan_avatar || '';
+ if (fullAvatar && !fullAvatar.startsWith('http')) {
+ fullAvatar = ossImageUrl + fullAvatar;
+ }
+ if (!fullAvatar) {
+ fullAvatar = ossImageUrl + (app.globalData.morentouxiang || 'avatar/default.jpg');
+ }
+ const param = {
+ toUserId: 'Kh' + item.shenheguan_id,
+ toName: '考核官' + item.shenheguan_id,
+ toAvatar: fullAvatar
+ };
+ wx.navigateTo({
+ url: '/pages/liaotian/liaotian?data=' + encodeURIComponent(JSON.stringify(param)),
+ fail: (err) => {
+ wx.showToast({ title: '打开聊天失败', icon: 'none' });
+ }
+ });
+ },
+
+ showTagDetail(e) {
+ const tag = e.currentTarget.dataset.tag;
+ const feeStr = this.formatFeeList(tag.fee_list);
+ wx.showModal({
+ title: tag.name,
+ content: `规则:${tag.rule || '暂无'}\n${tag.jinpai ? '【可同时开启金牌】' : ''}\n费用:${feeStr}`,
+ showCancel: false,
+ confirmText: '知道了'
+ });
+ },
+
+ formatFeeList(feeList) {
+ if (!feeList || feeList.length === 0) return '暂无';
+ let str = feeList.map(f => `第${f.cishu}次:¥${f.feiyong}`).join(';');
+ const last = feeList[feeList.length - 1];
+ str += `;第${last.cishu}次及以上:¥${last.feiyong}`;
+ return str;
+ },
+});
\ No newline at end of file
diff --git a/miniprogram/pages/kaohe_jinpai/kaohe_jinpai.json b/miniprogram/pages/kaohe_jinpai/kaohe_jinpai.json
new file mode 100644
index 0000000..fa331e5
--- /dev/null
+++ b/miniprogram/pages/kaohe_jinpai/kaohe_jinpai.json
@@ -0,0 +1,9 @@
+{
+ "usingComponents": {
+ "chenghao-tag": "/components/chenghao-tag/chenghao-tag",
+ "global-notification": "/components/global-notification/global-notification"
+ },
+ "navigationBarTitleText": "考核金牌",
+ "navigationBarBackgroundColor": "#0F0A1E",
+ "navigationBarTextStyle": "white"
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/kaohe_jinpai/kaohe_jinpai.wxml b/miniprogram/pages/kaohe_jinpai/kaohe_jinpai.wxml
new file mode 100644
index 0000000..6d89494
--- /dev/null
+++ b/miniprogram/pages/kaohe_jinpai/kaohe_jinpai.wxml
@@ -0,0 +1,135 @@
+
+
+
+
+ 考核标签
+ 我的考核
+
+
+
+
+
+
+ {{item.mingcheng}}
+
+
+
+ {{item.cishu}}次 ¥{{item.feiyong}}
+ ≥{{item.fee_list.length}}次 ¥{{item.fee_list[item.fee_list.length-1].feiyong}}
+
+ 申请考核
+ 已拥有
+
+
+
+
+
+
+
+
+ 进行中/未通过
+ 已通过
+
+
+
+
+ {{item.chenghao_name}}
+ {{item.statusText}}
+
+
+ 板块:{{item.bankuai_name}}
+ 考核官:{{item.shenheguan_id || '待分配'}}
+
+ 联系考核官
+
+ 游戏ID:{{item.dashou_youxi_id}}
+ 备注:{{item.dashou_beizhu}}
+ 第{{item.cishu}}次考核 · 共缴 ¥{{item.total_fee}}
+
+
+
+ 本次考核费用:¥{{item.current_fee}}
+ 预计下次费用:¥{{item.next_fee}}
+
+
+ 上次拒绝原因:{{item.last_fail_reason}}
+
+
+ 再次考核
+
+ 加载中...
+ —— 没有更多了 ——
+
+
+
+
+
+
+
+
+ 游戏昵称
+
+
+
+ 备注
+
+
+
+
+
+ 指定考核官(可选)
+
+ 已有考核官:{{originalShenheguanId}},不可重新指定
+
+
+ 选择考核官
+
+
+
+ UID {{selectedKaoheguanInfo.yonghuid}}
+ {{selectedKaoheguanInfo.dashou_nick}}
+ {{selectedKaoheguanInfo.is_renzheng ? '已认证' : '未认证'}}
+
+ 取消
+
+
+
+
+ 本次需缴纳:¥{{applyFee}}
+
+ 取消
+ 确认申请{{applyFee > 0 ? '并支付' : ''}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ UID {{item.yonghuid}}
+ {{item.dashou_nick}}
+ {{item.is_renzheng ? '已认证' : '未认证'}}
+ {{item.shenhe_zhuangtai === 0 ? '空闲' : '忙碌'}}
+
+
+ 暂无符合条件的考核官
+
+
+ 取消
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/kaohe_jinpai/kaohe_jinpai.wxss b/miniprogram/pages/kaohe_jinpai/kaohe_jinpai.wxss
new file mode 100644
index 0000000..854c33b
--- /dev/null
+++ b/miniprogram/pages/kaohe_jinpai/kaohe_jinpai.wxss
@@ -0,0 +1,385 @@
+/* pages/kaohe_jinpai/kaohe_jinpai.wxss - 晨雾微光风格 */
+.page-root {
+ min-height: 100vh;
+ background: linear-gradient(135deg, #F5F7FA 0%, #E4E8F0 100%);
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ }
+
+ .tab-bar {
+ display: flex;
+ padding: 30rpx 50rpx;
+ gap: 60rpx;
+ background: rgba(255, 255, 255, 0.65);
+ backdrop-filter: blur(15px);
+ border-bottom: 1rpx solid rgba(0, 0, 0, 0.04);
+ }
+ .tab-item {
+ font-size: 32rpx;
+ color: #6B7280;
+ padding-bottom: 10rpx;
+ border-bottom: 4rpx solid transparent;
+ transition: all 0.3s;
+ }
+ .tab-item.active {
+ color: #4C5BF0;
+ border-bottom-color: #4C5BF0;
+ font-weight: 600;
+ }
+
+ .content-area {
+ flex: 1;
+ overflow: hidden;
+ }
+ .scroll-view {
+ height: 100%;
+ padding: 30rpx 0;
+ }
+
+ .plate-group {
+ margin-bottom: 50rpx;
+ }
+ .plate-title {
+ font-size: 34rpx;
+ font-weight: 700;
+ color: #1F2937;
+ margin-bottom: 20rpx;
+ padding-left: 30rpx;
+ border-left: 6rpx solid #4C5BF0;
+ margin-left: 30rpx;
+ margin-right: 30rpx;
+ }
+
+ .tag-card {
+ background: #FFFFFF;
+ border-radius: 28rpx;
+ padding: 30rpx;
+ margin: 0 30rpx 20rpx 30rpx;
+ box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.03);
+ border: 1rpx solid rgba(0, 0, 0, 0.04);
+ }
+ .tag-header {
+ display: flex;
+ align-items: center;
+ gap: 16rpx;
+ margin-bottom: 18rpx;
+ }
+ .jinpai-badge {
+ font-size: 22rpx;
+ color: #F59E0B;
+ background: rgba(245, 158, 11, 0.1);
+ padding: 4rpx 14rpx;
+ border-radius: 20rpx;
+ font-weight: 600;
+ }
+ .fee-line {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 16rpx;
+ margin-bottom: 20rpx;
+ }
+ .fee-item {
+ font-size: 24rpx;
+ color: #4B5563;
+ background: #F3F4F6;
+ padding: 6rpx 16rpx;
+ border-radius: 20rpx;
+ }
+
+ .apply-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 80rpx;
+ background: linear-gradient(135deg, #4C5BF0, #6D7AF5);
+ color: #FFFFFF;
+ border-radius: 34rpx;
+ font-size: 28rpx;
+ font-weight: 600;
+ letter-spacing: 2rpx;
+ transition: all 0.2s;
+ }
+ .apply-btn:active {
+ opacity: 0.85;
+ transform: scale(0.98);
+ }
+ .owned-tip {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 80rpx;
+ color: #10B981;
+ font-size: 28rpx;
+ font-weight: 600;
+ }
+
+ /* 记录卡片 */
+ .record-card {
+ background: #FFFFFF;
+ border-radius: 28rpx;
+ padding: 30rpx;
+ margin: 0 30rpx 20rpx 30rpx;
+ box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.03);
+ border: 1rpx solid rgba(0, 0, 0, 0.04);
+ }
+ .card-top {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 16rpx;
+ }
+ .tag-name { font-size: 32rpx; font-weight: 600; color: #1F2937; }
+ .status-text { font-size: 26rpx; }
+ .card-info {
+ display: flex;
+ flex-direction: column;
+ gap: 10rpx;
+ font-size: 26rpx;
+ color: #4B5563;
+ }
+
+ /* 联系考核官:蓝色下划线 */
+ .contact-text-link {
+ color: #4C5BF0;
+ text-decoration: underline;
+ font-size: 26rpx;
+ }
+
+ .fee-predict {
+ background: #F3F4F6;
+ padding: 12rpx 16rpx;
+ border-radius: 12rpx;
+ margin-top: 10rpx;
+ display: flex;
+ flex-direction: column;
+ gap: 6rpx;
+ font-size: 24rpx;
+ color: #4B5563;
+ }
+
+ .reapply-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 80rpx;
+ margin-top: 20rpx;
+ background: #F59E0B;
+ color: #FFFFFF;
+ border-radius: 34rpx;
+ font-size: 28rpx;
+ font-weight: 600;
+ }
+ .reapply-btn:active {
+ opacity: 0.85;
+ transform: scale(0.98);
+ }
+
+ .filter-row {
+ display: flex;
+ padding: 20rpx 30rpx;
+ gap: 40rpx;
+ }
+ .filter-tag {
+ font-size: 28rpx;
+ color: #6B7280;
+ padding-bottom: 8rpx;
+ border-bottom: 3rpx solid transparent;
+ }
+ .filter-tag.active {
+ color: #4C5BF0;
+ border-bottom-color: #4C5BF0;
+ }
+
+ /* 弹窗 */
+ .modal-mask {
+ position: fixed; top:0; left:0; width:100%; height:100%;
+ background: rgba(0, 0, 0, 0.4);
+ display: flex; align-items: center; justify-content: center;
+ z-index: 1000;
+ }
+ .modal-dialog {
+ width: 85%;
+ background: #FFFFFF;
+ border-radius: 36rpx;
+ padding: 40rpx;
+ box-shadow: 0 20rpx 40rpx rgba(0, 0, 0, 0.15);
+ }
+ .modal-header { font-size: 34rpx; font-weight: 700; color: #1F2937; margin-bottom: 30rpx; }
+ .form-group { margin-bottom: 25rpx; }
+ .form-label { font-size: 28rpx; color: #4B5563; margin-bottom: 10rpx; display: block; }
+ .form-input, .form-textarea {
+ width: 100%;
+ background: #F9FAFB;
+ border-radius: 14rpx;
+ padding: 16rpx;
+ font-size: 28rpx;
+ color: #1F2937;
+ border: 1rpx solid #E5E7EB;
+ }
+ .form-textarea { height: 150rpx; }
+ .fee-info { text-align: center; font-size: 30rpx; margin: 20rpx 0; color: #F59E0B; }
+ .fee-amount { font-weight: 700; }
+ .modal-actions { display: flex; gap: 20rpx; margin-top: 30rpx; }
+ .btn-cancel, .btn-confirm {
+ flex: 1;
+ height: 80rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 40rpx;
+ font-size: 30rpx;
+ font-weight: 600;
+ transition: all 0.2s;
+ }
+ .btn-cancel { background: #F3F4F6; color: #4B5563; }
+ .btn-cancel:active { background: #E5E7EB; }
+ .btn-confirm { background: linear-gradient(135deg, #4C5BF0, #6D7AF5); color: #FFFFFF; }
+ .btn-confirm:active { opacity: 0.85; transform: scale(0.98); }
+
+ .loading-more, .no-more { text-align: center; padding: 30rpx; color: #9CA3AF; font-size: 26rpx; }
+
+ .kg-avatar {
+ width: 40rpx;
+ height: 40rpx;
+ border-radius: 50%;
+ margin-right: 8rpx;
+ vertical-align: middle;
+ }
+
+
+
+
+
+ /* 原有样式保留,以下是新增样式 */
+
+/* 指定考核官选择按钮 */
+.select-kg-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 80rpx;
+ background: #F3F4F6;
+ color: #4B5563;
+ border-radius: 34rpx;
+ font-size: 28rpx;
+ margin-top: 10rpx;
+ }
+ .select-kg-btn:active {
+ background: #E5E7EB;
+ }
+
+ /* 已选择的考核官卡片 */
+ .selected-kg-card {
+ display: flex;
+ align-items: center;
+ background: #F9FAFB;
+ border-radius: 20rpx;
+ padding: 20rpx;
+ margin-top: 10rpx;
+ border: 1rpx solid #E5E7EB;
+ }
+ .kg-avatar {
+ width: 64rpx;
+ height: 64rpx;
+ border-radius: 50%;
+ margin-right: 16rpx;
+ }
+ .kg-info {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 4rpx;
+ }
+ .kg-uid {
+ font-size: 28rpx;
+ font-weight: 600;
+ color: #1F2937;
+ }
+ .kg-nick {
+ font-size: 24rpx;
+ color: #6B7280;
+ }
+ .kg-status {
+ font-size: 22rpx;
+ padding: 2rpx 12rpx;
+ border-radius: 20rpx;
+ display: inline-block;
+ width: fit-content;
+ }
+ .kg-status.renzheng {
+ background: rgba(16, 185, 129, 0.1);
+ color: #10B981;
+ }
+ .kg-status.not-renzheng {
+ background: rgba(245, 158, 11, 0.1);
+ color: #F59E0B;
+ }
+ .clear-kg-btn {
+ color: #EF4444;
+ font-size: 26rpx;
+ padding: 0 10rpx;
+ }
+ .clear-kg-btn:active {
+ opacity: 0.7;
+ }
+
+ /* 禁用指定提示 */
+ .kg-disabled-tip {
+ background: #FEF3C7;
+ padding: 16rpx;
+ border-radius: 12rpx;
+ font-size: 24rpx;
+ color: #92400E;
+ margin-top: 10rpx;
+ }
+
+ /* 考核官选择弹窗内搜索 */
+ .search-box {
+ margin-bottom: 20rpx;
+ }
+ .search-input {
+ width: 100%;
+ background: #F9FAFB;
+ border-radius: 14rpx;
+ padding: 16rpx;
+ font-size: 28rpx;
+ border: 1rpx solid #E5E7EB;
+ }
+
+ /* 考核官滚动列表 */
+ .kg-scroll {
+ max-height: 500rpx;
+ margin-bottom: 20rpx;
+ }
+
+ /* 单个考核官项 */
+ .kg-item {
+ display: flex;
+ align-items: center;
+ padding: 20rpx;
+ background: #FFFFFF;
+ border-radius: 16rpx;
+ margin-bottom: 12rpx;
+ border: 1rpx solid #F3F4F6;
+ }
+ .kg-item:active {
+ background: #F9FAFB;
+ }
+ .kg-item .kg-state {
+ font-size: 22rpx;
+ margin-top: 4rpx;
+ }
+ .kg-state.free {
+ color: #10B981;
+ }
+ .kg-state.busy {
+ color: #EF4444;
+ }
+
+ .empty-tip {
+ text-align: center;
+ padding: 30rpx;
+ color: #9CA3AF;
+ font-size: 26rpx;
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/kaohe_zhongxin/kaohe_zhongxin.js b/miniprogram/pages/kaohe_zhongxin/kaohe_zhongxin.js
new file mode 100644
index 0000000..9f45455
--- /dev/null
+++ b/miniprogram/pages/kaohe_zhongxin/kaohe_zhongxin.js
@@ -0,0 +1,240 @@
+// pages/shenhedating/shenhedating.js
+const app = getApp();
+import request from '../../utils/request.js';
+
+Page({
+ data: {
+ // 模块列表 [{bankuai_id, mingcheng, count, is_related}]
+ moduleList: [],
+ xuanzhongBankuaiId: 0, // 0代表全部
+ // 审核记录列表
+ recordList: [],
+ page: 1,
+ pageSize: 5,
+ hasMore: true,
+ isLoading: false,
+ isLoadingMore: false,
+ scrollViewRefreshing: false,
+ lastRefreshTime: 0,
+ refreshCooldown: 2000,
+ // 弹窗控制
+ showRuleModal: false,
+ currentRuleContent: '',
+ showBeizhuModal: false,
+ currentBeizhuContent: '',
+ // 加载状态
+ loadingModules: false
+ },
+
+ onLoad() {
+ this.loadModules();
+ },
+
+ onShow() {
+ this.registerNotificationComponent();
+ },
+
+ registerNotificationComponent() {
+ const notificationComp = this.selectComponent('#global-notification');
+ if (notificationComp && notificationComp.showNotification) {
+ app.globalData.globalNotification = {
+ show: (data) => notificationComp.showNotification(data),
+ hide: () => notificationComp.hideNotification()
+ };
+ }
+ },
+
+ // 加载板块模块
+ async loadModules() {
+ this.setData({ loadingModules: true });
+ try {
+ const res = await request({ url: '/dengji/shdthqmk', method: 'POST' });
+ if (res && res.data.code === 0) {
+ const modules = res.data.data.modules || [];
+ this.setData({ moduleList: modules, xuanzhongBankuaiId: 0 });
+ this.loadRecords(true);
+ } else {
+ wx.showToast({ title: '加载模块失败', icon: 'none' });
+ }
+ } catch (e) {
+ wx.showToast({ title: '网络错误', icon: 'none' });
+ } finally {
+ this.setData({ loadingModules: false });
+ }
+ },
+
+ // 选择模块(含全部)
+ selectModule(e) {
+ const bankuaiId = e.currentTarget.dataset.id;
+ if (bankuaiId === this.data.xuanzhongBankuaiId) return;
+ this.setData({
+ xuanzhongBankuaiId: bankuaiId,
+ recordList: [],
+ page: 1,
+ hasMore: true
+ });
+ this.loadRecords(true);
+ },
+
+ // 加载审核记录
+ async loadRecords(isRefresh = false) {
+ if (this.data.isLoading) return;
+ const page = isRefresh ? 1 : this.data.page;
+ if (!isRefresh && !this.data.hasMore) return;
+
+ this.setData({ isLoading: true, isLoadingMore: !isRefresh });
+
+ try {
+ const res = await request({
+ url: '/dengji/hqshsj',
+ method: 'POST',
+ data: {
+ bankuai_id: this.data.xuanzhongBankuaiId || undefined,
+ page: page,
+ page_size: this.data.pageSize
+ }
+ });
+
+ this.setData({ isLoading: false, isLoadingMore: false, scrollViewRefreshing: false });
+
+ if (res && res.data.code === 0) {
+ const list = res.data.data.list || [];
+ const hasMore = res.data.data.has_more;
+ const ossUrl = app.globalData.ossImageUrl || '';
+ const processedList = list.map(item => {
+ let avatar = item.shenqingren_avatar || '';
+ if (avatar && !avatar.startsWith('http')) {
+ avatar = ossUrl + avatar;
+ }
+ return { ...item, shenqingren_avatar: avatar };
+ });
+ const newList = isRefresh ? processedList : [...this.data.recordList, ...processedList];
+ this.setData({ recordList: newList, page: page + 1, hasMore: hasMore });
+ if (isRefresh) this.setData({ lastRefreshTime: Date.now() });
+ } else {
+ wx.showToast({ title: '加载记录失败', icon: 'none' });
+ }
+ } catch (e) {
+ this.setData({ isLoading: false, isLoadingMore: false, scrollViewRefreshing: false });
+ wx.showToast({ title: '网络错误', icon: 'none' });
+ }
+ },
+
+ // 上拉加载更多
+ onReachBottom() {
+ const now = Date.now();
+ if (now - this.data.lastRefreshTime < this.data.refreshCooldown ||
+ this.data.isLoading || this.data.isLoadingMore) return;
+ if (this.data.hasMore) {
+ this.loadRecords(false);
+ }
+ },
+
+ // 下拉刷新
+ onPullDownRefresh() {
+ const now = Date.now();
+ if (now - this.data.lastRefreshTime < this.data.refreshCooldown || this.data.isLoading) {
+ this.setData({ scrollViewRefreshing: false });
+ return;
+ }
+ this.setData({ scrollViewRefreshing: true, lastRefreshTime: now, page: 1, hasMore: true });
+ this.loadRecords(true);
+ },
+
+ // 查看规则详情
+ showRuleDetail(e) {
+ const content = e.currentTarget.dataset.content;
+ this.setData({ showRuleModal: true, currentRuleContent: content });
+ },
+ hideRuleDetail() {
+ this.setData({ showRuleModal: false, currentRuleContent: '' });
+ },
+
+ // 查看备注详情
+ showBeizhuDetail(e) {
+ const content = e.currentTarget.dataset.content;
+ this.setData({ showBeizhuModal: true, currentBeizhuContent: content || '无' });
+ },
+ hideBeizhuDetail() {
+ this.setData({ showBeizhuModal: false, currentBeizhuContent: '' });
+ },
+
+ // 抢单
+ async onShouShen(e) {
+ const jiluId = e.currentTarget.dataset.jiluid;
+ const bankuaiId = e.currentTarget.dataset.bankuaiid;
+ if (!jiluId) {
+ wx.showToast({ title: '记录ID异常,无法接单', icon: 'none' });
+ return;
+ }
+
+ const that = this;
+ wx.showModal({
+ title: '确认审核',
+ content: '确定要审核此用户吗?审核后请前往游戏进行审核,并严格评分。',
+ confirmColor: '#c9a558',
+ success: async (res) => {
+ if (res.confirm) {
+ wx.showLoading({ title: '接单中...', mask: true });
+ try {
+ const qdRes = await request({
+ url: '/dengji/shqd',
+ method: 'POST',
+ data: { jilu_id: jiluId }
+ });
+ if (qdRes && qdRes.data.code === 0) {
+ // 抢单成功,弹出联系打手弹窗
+ wx.showModal({
+ title: '接待成功',
+ content: '请认真考核,点击联系他查看打手信息并打分。',
+ showCancel: false,
+ confirmText: '联系他',
+ confirmColor: '#c9a558',
+ success: (modalRes) => {
+ if (modalRes.confirm) {
+ wx.navigateTo({
+ url: '/pages/kaohe_dafen/kaohe_dafen?jilu_id=' + jiluId
+ });
+ }
+ }
+ });
+ const newList = that.data.recordList.filter(r => r.jilu_id !== jiluId);
+ that.setData({ recordList: newList });
+ // 对应板块待审核数减1
+ const bkId = qdRes.data.data ? qdRes.data.data.bankuai_id : bankuaiId;
+ if (bkId) {
+ const newModules = that.data.moduleList.map(m => {
+ if (m.bankuai_id === bkId && m.count > 0) {
+ return { ...m, count: m.count - 1 };
+ }
+ return m;
+ });
+ that.setData({ moduleList: newModules });
+ }
+ } else {
+ const errMsg = qdRes?.data?.msg || '接单失败,请重试';
+ wx.showModal({
+ title: '接单失败',
+ content: errMsg,
+ showCancel: false,
+ confirmText: '知道了',
+ confirmColor: '#c9a558'
+ });
+ }
+ } catch (e) {
+ wx.showModal({
+ title: '提示',
+ content: '网络错误,请重试',
+ showCancel: false,
+ confirmText: '知道了'
+ });
+ } finally {
+ wx.hideLoading();
+ }
+ }
+ }
+ });
+ },
+
+ noop() {}
+});
\ No newline at end of file
diff --git a/miniprogram/pages/kaohe_zhongxin/kaohe_zhongxin.json b/miniprogram/pages/kaohe_zhongxin/kaohe_zhongxin.json
new file mode 100644
index 0000000..37a74a1
--- /dev/null
+++ b/miniprogram/pages/kaohe_zhongxin/kaohe_zhongxin.json
@@ -0,0 +1,10 @@
+{
+ "usingComponents": {
+ "chenghao-tag": "/components/chenghao-tag/chenghao-tag",
+ "global-notification": "/components/global-notification/global-notification",
+ "popup-notice": "/components/popup-notice/popup-notice"
+ },
+ "navigationBarTitleText": "审核大厅",
+ "navigationBarBackgroundColor": "#0b0c12",
+ "navigationBarTextStyle": "white"
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/kaohe_zhongxin/kaohe_zhongxin.wxml b/miniprogram/pages/kaohe_zhongxin/kaohe_zhongxin.wxml
new file mode 100644
index 0000000..08e7ebb
--- /dev/null
+++ b/miniprogram/pages/kaohe_zhongxin/kaohe_zhongxin.wxml
@@ -0,0 +1,144 @@
+
+
+
+
+
+
+
+ 全部
+
+
+
+ {{item.mingcheng}}
+ {{item.count}}
+
+
+
+
+
+
+
+
+
+
+ ⚡️ 正在刷新...
+ ▼ 下拉刷新
+
+
+
+ 模块加载中...
+ 当前暂无待审核记录
+
+
+
+
+
+
+
+ UID {{item.shenqingren_id}}
+ {{item.create_time}}
+
+
+
+
+
+ 所属板块
+ {{item.bankuai_mingcheng}}
+
+
+
+
+ 考核标签
+
+
+
+
+
+
+
+ {{item.guize_neirong.length > 50 ? item.guize_neirong.substring(0, 50) + '...' : item.guize_neirong}}
+
+
+
+
+
+
+
+ {{item.dashou_beizhu.length > 40 ? item.dashou_beizhu.substring(0, 40) + '...' : item.dashou_beizhu}}
+
+
+
+
+
+ 本次考核费用:
+ 免费
+ {{item.kaohe_feiyong}}元
+ (任何考核官可接)
+ (需认证考核官)
+
+
+
+
+
+
+
+ 加载更多...
+ —— 没有更多了 ——
+
+
+
+
+
+ 加载中...
+
+
+
+
+
+
+
+ {{currentRuleContent}}
+
+
+
+
+
+
+ {{currentBeizhuContent}}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/kaohe_zhongxin/kaohe_zhongxin.wxss b/miniprogram/pages/kaohe_zhongxin/kaohe_zhongxin.wxss
new file mode 100644
index 0000000..586c429
--- /dev/null
+++ b/miniprogram/pages/kaohe_zhongxin/kaohe_zhongxin.wxss
@@ -0,0 +1,288 @@
+/* pages/shenhedating/shenhedating.wxss - 暗夜鎏金主题 */
+
+/* 根容器:深邃渐变背景 */
+.shenhe-page {
+ width: 100vw;
+ height: 100vh;
+ display: flex;
+ flex-direction: column;
+ background: radial-gradient(ellipse at 20% 20%, #1a1e2b 0%, #0b0c12 100%);
+ overflow: hidden;
+ }
+
+ /* ========== 顶部固定模块区 ========== */
+ .module-quyu {
+ width: 100%;
+ background: rgba(20, 22, 30, 0.8);
+ backdrop-filter: blur(25px);
+ padding: 20rpx 0;
+ border-bottom: 1rpx solid rgba(201, 169, 88, 0.15);
+ flex-shrink: 0;
+ position: sticky;
+ top: 0;
+ z-index: 10;
+ }
+ .module-scroll {
+ white-space: nowrap;
+ height: 80rpx;
+ width: 100%;
+ }
+ .module-container {
+ display: inline-flex;
+ padding: 0 24rpx;
+ gap: 20rpx;
+ height: 80rpx;
+ align-items: center;
+ }
+ .module-item {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0 36rpx;
+ height: 64rpx;
+ border-radius: 32rpx;
+ background: rgba(255, 255, 255, 0.06);
+ transition: all 0.25s ease;
+ min-width: 100rpx;
+ position: relative;
+ flex-shrink: 0;
+ border: 1rpx solid rgba(255, 255, 255, 0.04);
+ }
+ .module-active {
+ background: linear-gradient(135deg, #c9a558, #b28b3a);
+ border-color: transparent;
+ box-shadow: 0 4rpx 20rpx rgba(201, 169, 88, 0.35);
+ }
+ .module-active .module-name {
+ color: #0b0c12;
+ font-weight: 700;
+ }
+ .module-name {
+ font-size: 26rpx;
+ color: #a2a8ba;
+ font-weight: 500;
+ }
+ .module-count {
+ position: absolute;
+ top: -10rpx;
+ right: -6rpx;
+ background: #d43b3b;
+ color: #ffffff;
+ border-radius: 14rpx;
+ padding: 2rpx 12rpx;
+ font-size: 20rpx;
+ font-weight: 600;
+ }
+
+ /* ========== 列表容器 ========== */
+ .record-container {
+ flex: 1;
+ overflow: hidden;
+ width: 100%;
+ }
+ .record-scroll {
+ height: 100%;
+ width: 100%;
+ }
+ .refresher-container {
+ height: 80rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+ .refreshing-text, .pull-down-text {
+ font-size: 24rpx;
+ color: #6a7090;
+ }
+
+ .empty-tip {
+ padding: 200rpx 0;
+ text-align: center;
+ color: #6a7090;
+ font-size: 28rpx;
+ }
+
+ /* ========== 审核卡片:玻璃态 ========== */
+ .record-card {
+ background: rgba(25, 27, 36, 0.7);
+ backdrop-filter: blur(15px);
+ margin: 20rpx 24rpx;
+ border-radius: 24rpx;
+ padding: 28rpx;
+ border: 1rpx solid rgba(201, 169, 88, 0.1);
+ box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.5);
+ transition: transform 0.15s;
+ }
+ .record-card:active {
+ transform: scale(0.995);
+ }
+ .user-row {
+ display: flex;
+ align-items: center;
+ margin-bottom: 24rpx;
+ }
+ .user-avatar {
+ width: 80rpx;
+ height: 80rpx;
+ border-radius: 50%;
+ margin-right: 20rpx;
+ background: #2a2d3a;
+ border: 2rpx solid rgba(201, 169, 88, 0.3);
+ box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.4);
+ }
+ .user-info {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ }
+ .user-uid {
+ font-size: 30rpx;
+ color: #f0f2ff;
+ font-weight: 600;
+ margin-bottom: 6rpx;
+ }
+ .user-time {
+ font-size: 22rpx;
+ color: #6a7090;
+ }
+
+ .detail-row {
+ display: flex;
+ align-items: center;
+ padding: 16rpx 0;
+ border-bottom: 1rpx solid rgba(255, 255, 255, 0.05);
+ gap: 20rpx;
+ }
+ .detail-row:last-of-type {
+ border-bottom: none;
+ }
+ .detail-label {
+ font-size: 26rpx;
+ color: #8a90b0;
+ flex-shrink: 0;
+ width: 120rpx;
+ }
+ .detail-value {
+ font-size: 26rpx;
+ color: #d0d4ff;
+ font-weight: 500;
+ }
+
+ .rule-row, .beizhu-row {
+ padding: 16rpx 0;
+ border-bottom: 1rpx solid rgba(255, 255, 255, 0.05);
+ }
+ .rule-header, .beizhu-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 12rpx;
+ }
+ .rule-label, .beizhu-label {
+ font-size: 26rpx;
+ color: #8a90b0;
+ }
+ .rule-more, .beizhu-more {
+ font-size: 24rpx;
+ color: #c9a558;
+ font-weight: 500;
+ }
+ .rule-content, .beizhu-content {
+ background: rgba(255, 255, 255, 0.05);
+ padding: 18rpx;
+ border-radius: 14rpx;
+ border: 1rpx solid rgba(255, 255, 255, 0.06);
+ }
+ .rule-text, .beizhu-text {
+ font-size: 24rpx;
+ color: #b0b8d0;
+ line-height: 1.6;
+ }
+
+ /* 底部按钮 */
+ .card-footer {
+ display: flex;
+ justify-content: flex-end;
+ margin-top: 24rpx;
+ }
+ .shoushen-btn {
+ background: linear-gradient(135deg, #c9a558, #9e7d2e);
+ color: #0b0c12;
+ padding: 14rpx 48rpx;
+ border-radius: 40rpx;
+ font-size: 28rpx;
+ font-weight: 700;
+ box-shadow: 0 6rpx 18rpx rgba(201, 169, 88, 0.3);
+ transition: all 0.2s;
+ }
+ .shoushen-btn:active {
+ transform: scale(0.97);
+ opacity: 0.9;
+ }
+
+ /* 加载 */
+ .loading-more, .no-more {
+ text-align: center;
+ padding: 30rpx;
+ font-size: 24rpx;
+ color: #6a7090;
+ }
+ .loading-mask {
+ position: fixed;
+ top: 0; left: 0; right: 0; bottom: 0;
+ background: rgba(11, 12, 18, 0.85);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ }
+ .loading-spinner {
+ width: 60rpx;
+ height: 60rpx;
+ border: 4rpx solid rgba(255,255,255,0.1);
+ border-top-color: #c9a558;
+ border-radius: 50%;
+ animation: spin 0.8s linear infinite;
+ margin-bottom: 20rpx;
+ }
+ @keyframes spin { to { transform: rotate(360deg); } }
+
+ /* 弹窗 */
+ .modal-mask {
+ position: fixed;
+ top: 0; left: 0; right: 0; bottom: 0;
+ background: rgba(0, 0, 0, 0.7);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+ }
+ .modal-content {
+ width: 85%;
+ max-height: 70vh;
+ background: #1e2030;
+ border-radius: 24rpx;
+ padding: 30rpx;
+ border: 1rpx solid rgba(201, 169, 88, 0.2);
+ box-shadow: 0 20rpx 40rpx rgba(0,0,0,0.8);
+ }
+ .modal-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ font-size: 32rpx;
+ font-weight: 600;
+ color: #f0f2ff;
+ margin-bottom: 20rpx;
+ }
+ .modal-close {
+ font-size: 40rpx;
+ color: #8a90b0;
+ padding: 0 10rpx;
+ }
+ .modal-body {
+ max-height: 50vh;
+ font-size: 28rpx;
+ line-height: 1.6;
+ color: #c0c8e0;
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/kaoheguan/kaoheguan.js b/miniprogram/pages/kaoheguan/kaoheguan.js
new file mode 100644
index 0000000..5f5715a
--- /dev/null
+++ b/miniprogram/pages/kaoheguan/kaoheguan.js
@@ -0,0 +1,195 @@
+// pages/kaoheguan/kaoheguan.js
+// 只新增了两个字段的接收,其余所有代码未变
+import request from '../../utils/request.js';
+const app = getApp();
+
+Page({
+ data: {
+ isKaoheguan: false,
+ isLoading: false,
+ inviteCode: '',
+ statusText: '用户尚未注册或账号已被封禁',
+ uid: '',
+ avatarUrl: '',
+ icons: {
+ refresh: '',
+ tixian: '',
+ daofen: '',
+ jilu: '',
+ zhongxin: '',
+ bgImage: ''
+ },
+ kaoheData: {
+ shenheZongshu: 0,
+ tongguoZongshu: 0,
+ yue: '0.00',
+ zonge: '0.00',
+ shenheZhuangtai: 0,
+ bankuaiList: [],
+ isRenzheng: false, // 🆕 是否认证
+ pendingShenheCount: 0 // 🆕 审核中数量
+ },
+ lastRefreshTime: 0,
+ canRefresh: true,
+ planetPaused: {
+ top: false,
+ bottom: false,
+ left: false,
+ right: false
+ }
+ },
+
+ _planetTimers: {},
+
+ onLoad() {
+ const ossUrl = app.globalData.ossImageUrl || '';
+ const iconBase = 'beijing/kaohe/';
+ this.setData({
+ 'icons.refresh': ossUrl + iconBase + 'shuaxin.png',
+ 'icons.tixian': ossUrl + iconBase + 'tixian.png',
+ 'icons.daofen': ossUrl + iconBase + 'daofen.png',
+ 'icons.jilu': ossUrl + iconBase + 'jilu.png',
+ 'icons.zhongxin': ossUrl + iconBase + 'zhongxin.png',
+ 'icons.bgImage': ossUrl + iconBase + 'top_bg.jpg'
+ });
+
+ const kaoheguanstatus = wx.getStorageSync('kaoheguanstatus');
+ if (kaoheguanstatus === 1 || kaoheguanstatus === '1') {
+ this.loadUserInfo();
+ this.setData({ isKaoheguan: true });
+ this.getKaoheguanInfo();
+ } else {
+ this.setData({ isKaoheguan: false });
+ }
+ },
+
+ onShow() {
+ if (this.data.isKaoheguan) {
+ this.refreshKaoheInfo();
+ }
+ },
+
+ loadUserInfo() {
+ const uid = wx.getStorageSync('uid') || '';
+ const touxiang = wx.getStorageSync('touxiang') || '';
+ const ossUrl = app.globalData.ossImageUrl || '';
+ const defaultAvatar = ossUrl + (app.globalData.morentouxiang || 'avatar/default.jpg');
+ let avatarUrl = defaultAvatar;
+ if (touxiang) {
+ avatarUrl = ossUrl + (touxiang.startsWith('/') ? touxiang : '/' + touxiang);
+ }
+ this.setData({ uid, avatarUrl });
+ },
+
+ async getKaoheguanInfo() {
+ this.setData({ isLoading: true });
+ try {
+ const res = await request({ url: '/dengji/khgsx', method: 'POST' });
+ if (res && res.data.code === 200) {
+ const data = res.data.data;
+ if (data.zhuangtai === 0 || data.zhuangtai === 2) {
+ wx.setStorageSync('kaoheguanstatus', 0);
+ this.setData({ isKaoheguan: false, isLoading: false, statusText: '用户尚未注册或账号已被封禁' });
+ return;
+ }
+ wx.setStorageSync('kaoheguanstatus', 1);
+ this.setData({
+ isKaoheguan: true,
+ isLoading: false,
+ kaoheData: {
+ shenheZongshu: data.shenhe_zongshu || 0,
+ tongguoZongshu: data.tongguo_zongshu || 0,
+ yue: data.yue || '0.00',
+ zonge: data.zonge || '0.00',
+ shenheZhuangtai: data.shenhe_zhuangtai || 0,
+ bankuaiList: data.bankuai_list || [],
+ isRenzheng: data.is_renzheng || false, // 🆕
+ pendingShenheCount: data.pending_shenhe_count || 0 // 🆕
+ }
+ });
+ } else {
+ wx.showToast({ title: res?.data?.msg || '获取信息失败', icon: 'none' });
+ this.setData({ isLoading: false });
+ }
+ } catch (error) {
+ console.error('获取考核官信息失败:', error);
+ wx.showToast({ title: '网络错误', icon: 'none' });
+ this.setData({ isLoading: false });
+ }
+ },
+
+ onInviteCodeInput(e) {
+ this.setData({ inviteCode: e.detail.value.trim() });
+ },
+
+ async onRegister() {
+ const { inviteCode } = this.data;
+ if (!inviteCode) {
+ wx.showToast({ title: '请输入邀请码', icon: 'none', duration: 2000 });
+ return;
+ }
+ if (inviteCode.length > 100) {
+ wx.showToast({ title: '邀请码不能超过100位', icon: 'none', duration: 2000 });
+ return;
+ }
+ this.setData({ isLoading: true });
+ try {
+ const res = await request({
+ url: '/dengji/khgzc',
+ method: 'POST',
+ data: { inviteCode: inviteCode }
+ });
+ if (res && res.data.code === 200) {
+ wx.setStorageSync('kaoheguanstatus', 1);
+ wx.showToast({ title: '注册成功', icon: 'success', duration: 2000 });
+ this.loadUserInfo();
+ await this.getKaoheguanInfo();
+ } else {
+ wx.showToast({ title: res?.data?.msg || '注册失败', icon: 'none', duration: 2000 });
+ this.setData({ isLoading: false });
+ }
+ } catch (error) {
+ console.error('注册失败:', error);
+ wx.showToast({ title: '网络错误', icon: 'none' });
+ this.setData({ isLoading: false });
+ }
+ },
+
+ refreshKaoheInfo() {
+ const now = Date.now();
+ const lastTime = this.data.lastRefreshTime;
+ const canRefresh = this.data.canRefresh;
+ if (!canRefresh || (now - lastTime < 3000)) return;
+ this.setData({ lastRefreshTime: now, canRefresh: false });
+ this.getKaoheguanInfo();
+ setTimeout(() => this.setData({ canRefresh: true }), 3000);
+ },
+
+ goToWithdraw() {
+ const yue = this.data.kaoheData.yue || 0;
+ wx.navigateTo({
+ url: `/pages/tixian/tixian?amount=${yue}&from=kaoheguan`
+ });
+ },
+ goToDafen() { wx.navigateTo({ url: '/pages/kaohe_dafen/kaohe_dafen' }); },
+ goToJilu() { wx.navigateTo({ url: '/pages/kaohe_jilu/kaohe_jilu' }); },
+ goToZhongxin() { wx.navigateTo({ url: '/pages/kaohe_zhongxin/kaohe_zhongxin' }); },
+
+ onPlanetTouch(e) {
+ const id = e.currentTarget.dataset.id;
+ if (this._planetTimers[id]) {
+ clearTimeout(this._planetTimers[id]);
+ this._planetTimers[id] = null;
+ }
+ const paused = `planetPaused.${id}`;
+ this.setData({ [paused]: true });
+ },
+
+ onPlanetLeave(e) {
+ const id = e.currentTarget.dataset.id;
+ this._planetTimers[id] = setTimeout(() => {
+ const paused = `planetPaused.${id}`;
+ this.setData({ [paused]: false });
+ }, 5000);
+ }
+});
\ No newline at end of file
diff --git a/miniprogram/pages/kaoheguan/kaoheguan.json b/miniprogram/pages/kaoheguan/kaoheguan.json
new file mode 100644
index 0000000..78582c0
--- /dev/null
+++ b/miniprogram/pages/kaoheguan/kaoheguan.json
@@ -0,0 +1,11 @@
+{
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification",
+ "custom-tab-bar": "/custom-tab-bar/index",
+ "popup-notice": "/components/popup-notice/popup-notice"
+ },
+ "navigationBarTitleText": "考核官",
+ "navigationBarBackgroundColor": "#0a0c14",
+ "navigationBarTextStyle": "white",
+ "navigationStyle": "custom"
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/kaoheguan/kaoheguan.wxml b/miniprogram/pages/kaoheguan/kaoheguan.wxml
new file mode 100644
index 0000000..0c1d239
--- /dev/null
+++ b/miniprogram/pages/kaoheguan/kaoheguan.wxml
@@ -0,0 +1,128 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 考核官
+ {{statusText}}
+
+
+ 即刻入驻
+
+
+
+
+
+
+
+
+
+
+ 考核官
+
+ {{uid}}
+
+
+
+
+ {{kaoheData.isRenzheng ? '已认证' : '未认证'}}
+
+
+
+
+
+
+
+
+
+
+ {{item.mingcheng}}
+
+
+
+
+
+
+
+ {{kaoheData.yue}}元
+ 可提现余额
+
+
+
+
+
+
+
+ {{kaoheData.shenheZongshu}}
+ 审核总数
+
+
+
+ {{kaoheData.tongguoZongshu}}
+ 通过总数
+
+
+
+ {{kaoheData.zonge}}元
+ 累计赚取
+
+
+
+
+
+
+
+
+
+ 考核中心
+
+
+
+
+
+ 考核打分
+
+ {{kaoheData.pendingShenheCount}}
+
+
+
+
+
+ 考核记录
+
+
+
+
+
+ 立即提现
+
+
+
+
+
+
+
+
+
+ 加载中...
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/kaoheguan/kaoheguan.wxss b/miniprogram/pages/kaoheguan/kaoheguan.wxss
new file mode 100644
index 0000000..b1f152c
--- /dev/null
+++ b/miniprogram/pages/kaoheguan/kaoheguan.wxss
@@ -0,0 +1,512 @@
+/* pages/kaoheguan/kaoheguan.wxss — 完整版:适度背景 + 整体下移 */
+
+page {
+ background: #0a0c14;
+ color: #e0e6f0;
+ font-family: 'SF Pro Display', 'PingFang SC', system-ui;
+ height: 100vh;
+ overflow: hidden;
+ }
+
+ /* 根容器:让出自定义导航栏与底部 TabBar 空间 */
+ .universe-root {
+ position: relative;
+ width: 100%;
+ height: 100vh;
+ display: flex;
+ flex-direction: column;
+ overflow-y: auto;
+ overflow-x: hidden;
+ padding-top: calc(88rpx + env(safe-area-inset-top));
+ padding-bottom: calc(120rpx + env(safe-area-inset-bottom));
+ box-sizing: border-box;
+ z-index: 1;
+ }
+
+ /* ========== 全屏背景图(适度弱化) ========== */
+ .full-bg {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+ z-index: 0;
+ opacity: 1; /* 从0.15调整到0.3,若隐若现但不抢夺内容 */
+ pointer-events: none;
+ object-fit: cover;
+ filter: blur(1px); /* 轻微模糊,增加梦幻感 */
+ }
+
+ /* ========== 动态星空(Canvas) ========== */
+ .starfield-canvas {
+ position: fixed;
+ top: 0; left: 0;
+ width: 100vw; height: 100vh;
+ pointer-events: none;
+ z-index: 0;
+ opacity: 0.35;
+ }
+
+ /* ========== 极光飘带 ========== */
+ .aurora-ribbon {
+ position: fixed;
+ top: -10%;
+ left: -30%;
+ width: 160%;
+ height: 60%;
+ background: radial-gradient(ellipse at 50% 50%, rgba(100, 140, 255, 0.12) 0%, transparent 70%);
+ pointer-events: none;
+ z-index: 0;
+ animation: drift 20s ease-in-out infinite alternate;
+ }
+
+ @keyframes drift {
+ 0% { transform: translate(0, 0) rotate(0deg); }
+ 100% { transform: translate(10%, 5%) rotate(3deg); }
+ }
+
+ /* ========== 舒缓流星 ========== */
+ /* 流星1:右上 → 左下,缓慢,间隔10秒 */
+ .universe-root::before {
+ content: "";
+ position: fixed;
+ top: 5%;
+ right: -2%;
+ width: 2rpx;
+ height: 85rpx;
+ background: linear-gradient(to bottom, rgba(255, 255, 255, 0.6), transparent);
+ transform: rotate(-35deg);
+ animation: meteor1 10s 0s linear infinite;
+ pointer-events: none;
+ z-index: 1;
+ opacity: 0;
+ }
+
+ /* 流星2:稍有不同的角度与延迟 */
+ .universe-root::after {
+ content: "";
+ position: fixed;
+ top: 10%;
+ right: -5%;
+ width: 2rpx;
+ height: 70rpx;
+ background: linear-gradient(to bottom, rgba(200, 210, 255, 0.55), transparent);
+ transform: rotate(-28deg);
+ animation: meteor2 11s 4s linear infinite;
+ pointer-events: none;
+ z-index: 1;
+ opacity: 0;
+ }
+
+ @keyframes meteor1 {
+ 0% {
+ opacity: 0;
+ transform: translate(0, 0) rotate(-35deg);
+ }
+ 10% {
+ opacity: 0.5;
+ }
+ 28% {
+ opacity: 0;
+ transform: translate(-500rpx, 450rpx) rotate(-35deg);
+ }
+ 100% {
+ opacity: 0;
+ transform: translate(-500rpx, 450rpx) rotate(-35deg);
+ }
+ }
+
+ @keyframes meteor2 {
+ 0% {
+ opacity: 0;
+ transform: translate(0, 0) rotate(-28deg);
+ }
+ 8% {
+ opacity: 0.45;
+ }
+ 25% {
+ opacity: 0;
+ transform: translate(-480rpx, 420rpx) rotate(-28deg);
+ }
+ 100% {
+ opacity: 0;
+ transform: translate(-480rpx, 420rpx) rotate(-28deg);
+ }
+ }
+
+ /* ========== 未注册状态 ========== */
+ .unreg-floating {
+ position: relative;
+ z-index: 10;
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+ .unreg-core {
+ width: 80%;
+ max-width: 640rpx;
+ background: rgba(255,255,255,0.04);
+ backdrop-filter: blur(30px);
+ border-radius: 50rpx;
+ padding: 80rpx 50rpx 60rpx;
+ border: 1rpx solid rgba(255,255,255,0.1);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ }
+ .unreg-icon { width: 100rpx; height: 100rpx; margin-bottom: 20rpx; }
+ .unreg-title { font-size: 42rpx; font-weight: 700; color: #fff; margin-bottom: 12rpx; }
+ .unreg-desc { font-size: 28rpx; color: #8590b0; margin-bottom: 50rpx; text-align: center; }
+ .unreg-input { width: 100%; height: 90rpx; background: rgba(255,255,255,0.08); border-radius: 30rpx; padding: 0 30rpx; color: #fff; font-size: 30rpx; box-sizing: border-box; margin-bottom: 30rpx; }
+ .unreg-placeholder { color: rgba(255,255,255,0.3); }
+ .unreg-btn { width: 100%; height: 90rpx; background: linear-gradient(135deg, #4f6ef6, #8b5cf6); border-radius: 45rpx; display: flex; align-items: center; justify-content: center; transition: transform 0.2s; }
+ .unreg-btn:active { transform: scale(0.98); }
+ .unreg-btn-text { font-size: 34rpx; font-weight: 600; color: #fff; }
+
+ /* ========== 已注册状态(整体下移) ========== */
+ .orbit-zone {
+ position: relative;
+ z-index: 10;
+ flex: 1;
+ padding: 80rpx 30rpx 0; /* 顶部从40rpx增加到60rpx,整体下移 */
+ display: flex;
+ flex-direction: column;
+ }
+
+ /* 身份条 */
+ .top-identity {
+ display: flex;
+ align-items: center;
+ margin-bottom: 40rpx;
+ position: relative;
+ z-index: 2;
+ }
+ .identity-avatar {
+ width: 90rpx; height: 90rpx; border-radius: 50%;
+ border: 2rpx solid rgba(255,255,255,0.2);
+ margin-right: 20rpx;
+ }
+ .identity-info { flex: 1; }
+ .identity-name { font-size: 36rpx; font-weight: 700; color: #fff; }
+ .identity-uid {
+ display: flex; align-items: center; margin-top: 8rpx;
+ }
+ .uid-txt { font-size: 24rpx; color: #9098b0; font-family: monospace; }
+ .status-indicator {
+ width: 12rpx; height: 12rpx; border-radius: 50%;
+ background: #4ade80; margin-left: 12rpx;
+ transition: background 0.3s;
+ }
+ .status-indicator.active { background: #fbbf24; }
+ .refresh-touch {
+ width: 60rpx; height: 60rpx;
+ background: rgba(255,255,255,0.06);
+ border-radius: 20rpx;
+ display: flex; align-items: center; justify-content: center;
+ transition: background 0.2s;
+ }
+ .refresh-touch:active { background: rgba(255,255,255,0.15); }
+ .refresh-icon { width: 36rpx; height: 36rpx; }
+
+ /* 板块标签云 */
+ .plate-cloud { margin-bottom: 30rpx; }
+ .plate-cloud-scroll { white-space: nowrap; }
+ .plate-chip {
+ display: inline-block;
+ padding: 8rpx 24rpx;
+ border-radius: 30rpx;
+ background: rgba(79,110,246,0.1);
+ color: #a5b4fc;
+ font-size: 26rpx;
+ margin-right: 16rpx;
+ border: 1rpx solid rgba(79,110,246,0.3);
+ box-shadow: 0 0 12rpx rgba(79,110,246,0.1);
+ transition: all 0.3s;
+ }
+ .plate-chip:active { transform: scale(0.95); background: rgba(79,110,246,0.2); }
+
+ /* 中央余额星球 */
+ .balance-planet {
+ position: relative;
+ width: 280rpx;
+ height: 280rpx;
+ margin: 20rpx auto 30rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+ .planet-ring {
+ position: absolute;
+ width: 280rpx;
+ height: 280rpx;
+ border-radius: 50%;
+ border: 2rpx solid rgba(79,110,246,0.4);
+ animation: ringRotate 12s linear infinite;
+ }
+ @keyframes ringRotate { to { transform: rotate(360deg); } }
+ .planet-body {
+ width: 210rpx;
+ height: 210rpx;
+ border-radius: 50%;
+ background: radial-gradient(circle at 30% 30%, rgba(79,110,246,0.3), rgba(15,15,30,0.8));
+ backdrop-filter: blur(10px);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ border: 1rpx solid rgba(255,255,255,0.1);
+ transition: transform 0.3s;
+ }
+ .balance-planet:active .planet-body { transform: scale(0.95); }
+ .planet-num {
+ font-size: 52rpx;
+ font-weight: 900;
+ color: #fff;
+ text-shadow: 0 0 30rpx rgba(79,110,246,0.8);
+ animation: numGlow 2s ease-in-out infinite alternate;
+ }
+ @keyframes numGlow {
+ from { text-shadow: 0 0 20rpx rgba(79,110,246,0.6); }
+ to { text-shadow: 0 0 40rpx rgba(79,110,246,1); }
+ }
+ .planet-label {
+ font-size: 24rpx;
+ color: #a5b4fc;
+ margin-top: 8rpx;
+ }
+ .planet-glow {
+ position: absolute;
+ width: 300rpx;
+ height: 300rpx;
+ border-radius: 50%;
+ background: radial-gradient(circle, rgba(79,110,246,0.2), transparent 70%);
+ animation: planetPulse 4s ease-in-out infinite;
+ pointer-events: none;
+ }
+ @keyframes planetPulse {
+ 0%, 100% { opacity: 0.3; transform: scale(1); }
+ 50% { opacity: 0.8; transform: scale(1.08); }
+ }
+
+ /* 流星数据条 */
+ .meteor-stats {
+ display: flex;
+ justify-content: space-around;
+ align-items: center;
+ margin-bottom: 30rpx;
+ padding: 20rpx 0;
+ position: relative;
+ }
+ .meteor-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ flex: 1;
+ }
+ .meteor-num {
+ font-size: 36rpx;
+ font-weight: 800;
+ background: linear-gradient(135deg, #a5b4fc, #4f6ef6);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ transition: all 0.3s;
+ }
+ .meteor-item:active .meteor-num { transform: scale(1.1); }
+ .meteor-label {
+ font-size: 22rpx;
+ color: #64748b;
+ margin-top: 10rpx;
+ }
+ .meteor-divider {
+ width: 2rpx;
+ height: 40rpx;
+ background: linear-gradient(to bottom, transparent, rgba(255,255,255,0.15), transparent);
+ }
+
+ /* ========== 十字星阵功能区 ========== */
+ .star-cross {
+ position: relative;
+ width: 520rpx;
+ height: 520rpx;
+ margin: 40rpx auto 50rpx;
+ }
+
+ /* 按钮公共样式 */
+ .cross-item {
+ position: absolute;
+ width: 150rpx;
+ height: 150rpx;
+ background: radial-gradient(circle at 30% 30%, rgba(79,110,246,0.25), rgba(0,0,0,0.25));
+ border-radius: 50%;
+ border: 1rpx solid rgba(255,255,255,0.12);
+ backdrop-filter: blur(10px);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ transition: transform 0.2s, box-shadow 0.2s;
+ z-index: 5;
+ overflow: visible;
+ }
+
+ /* 行星光环 */
+ .orbit-ring {
+ position: absolute;
+ top: -8rpx;
+ left: -8rpx;
+ right: -8rpx;
+ bottom: -8rpx;
+ border-radius: 50%;
+ border: 2rpx solid rgba(79,110,246,0.5);
+ border-top-color: transparent;
+ border-bottom-color: transparent;
+ animation: spinRing 4s linear infinite;
+ pointer-events: none;
+ }
+ .pause-spin .orbit-ring {
+ animation-play-state: paused;
+ }
+
+ @keyframes spinRing {
+ to { transform: rotate(360deg); }
+ }
+
+ .cross-item:active {
+ transform: scale(0.95);
+ box-shadow: 0 0 30rpx rgba(79,110,246,0.6);
+ }
+
+ /* 图标强化 */
+ .cross-icon {
+ width: 58rpx;
+ height: 58rpx;
+ margin-bottom: 8rpx;
+ filter: drop-shadow(0 0 14rpx rgba(79,110,246,0.9))
+ drop-shadow(0 0 28rpx rgba(99,102,241,0.7));
+ animation: iconFloat 3s ease-in-out infinite;
+ z-index: 2;
+ }
+
+ @keyframes iconFloat {
+ 0%, 100% { transform: translateY(0); }
+ 50% { transform: translateY(-3rpx); }
+ }
+
+ .cross-text {
+ font-size: 24rpx;
+ color: #e0e6f0;
+ font-weight: 600;
+ z-index: 2;
+ }
+
+ /* 上 */
+ .cross-top { top: 0; left: 50%; transform: translateX(-50%); }
+ /* 下 */
+ .cross-bottom { bottom: 0; left: 50%; transform: translateX(-50%); }
+ /* 左 */
+ .cross-left { left: 0; top: 50%; transform: translateY(-50%); }
+ /* 右 */
+ .cross-right { right: 0; top: 50%; transform: translateY(-50%); }
+
+ /* 中央五角星(固定,仅呼吸) */
+ .pentagram {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 0; height: 0;
+ transform: translate(-50%, -50%);
+ border-left: 36rpx solid transparent;
+ border-right: 36rpx solid transparent;
+ border-bottom: 84rpx solid rgba(79,110,246,0.85);
+ filter: drop-shadow(0 0 18rpx rgba(79,110,246,0.7));
+ animation: star-pulse 2s ease-in-out infinite;
+ }
+ .pentagram::before {
+ content: "";
+ position: absolute;
+ top: 36rpx;
+ left: -36rpx;
+ width: 0; height: 0;
+ border-left: 36rpx solid transparent;
+ border-right: 36rpx solid transparent;
+ border-bottom: 84rpx solid rgba(79,110,246,0.85);
+ transform: rotate(72deg);
+ }
+ .pentagram::after {
+ content: "";
+ position: absolute;
+ top: 36rpx;
+ left: -36rpx;
+ width: 0; height: 0;
+ border-left: 36rpx solid transparent;
+ border-right: 36rpx solid transparent;
+ border-bottom: 84rpx solid rgba(79,110,246,0.85);
+ transform: rotate(-72deg);
+ }
+
+ @keyframes star-pulse {
+ 0%, 100% { opacity: 0.8; }
+ 50% { opacity: 1; }
+ }
+
+ /* ========== 加载星门 ========== */
+ .loading-portal {
+ position: fixed; top: 0; left: 0; right: 0; bottom: 0;
+ background: rgba(10,12,20,0.95);
+ backdrop-filter: blur(15px);
+ display: flex; flex-direction: column;
+ align-items: center; justify-content: center;
+ z-index: 999;
+ }
+ .portal-ring {
+ width: 100rpx; height: 100rpx;
+ border-radius: 50%;
+ border: 3rpx solid rgba(79,110,246,0.2);
+ border-top-color: #4f6ef6;
+ animation: portalSpin 1s linear infinite;
+ margin-bottom: 30rpx;
+ }
+ @keyframes portalSpin { to { transform: rotate(360deg); } }
+ .portal-text { font-size: 28rpx; color: #64748b; }
+
+/* ========== 🆕 以下为新增样式,绝无任何修改 ========== */
+
+/* 认证状态标签 */
+.renzheng-tag {
+ font-size: 22rpx;
+ padding: 4rpx 12rpx;
+ border-radius: 20rpx;
+ margin-top: 8rpx;
+ display: inline-block;
+}
+.renzheng-tag.authed {
+ background: rgba(76, 175, 80, 0.15);
+ color: #4caf50;
+ border: 1rpx solid rgba(76, 175, 80, 0.4);
+}
+.renzheng-tag.unauth {
+ background: rgba(244, 67, 54, 0.1);
+ color: #f44336;
+ border: 1rpx solid rgba(244, 67, 54, 0.4);
+}
+
+/* 红色角标 */
+.badge {
+ position: absolute;
+ top: -4rpx;
+ right: -8rpx;
+ min-width: 36rpx;
+ height: 36rpx;
+ background: #f44336;
+ color: #fff;
+ font-size: 22rpx;
+ font-weight: 600;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0 8rpx;
+ box-shadow: 0 2rpx 8rpx rgba(244, 67, 54, 0.4);
+ z-index: 10;
+}
\ No newline at end of file
diff --git a/miniprogram/pages/kefuliaotian/kefuliaotian.js b/miniprogram/pages/kefuliaotian/kefuliaotian.js
new file mode 100644
index 0000000..bc7e394
--- /dev/null
+++ b/miniprogram/pages/kefuliaotian/kefuliaotian.js
@@ -0,0 +1,526 @@
+const app = getApp();
+import { formatDate } from '../../static/lib/utils';
+import { showConfirmByScene, matchAutoReply, getWelcomeText } from '../../utils/scriptService.js';
+
+Page({
+ data: {
+ teamId: '',
+ teamName: '客服',
+ teamAvatar: '',
+ currentUser: null,
+ messages: [],
+ inputText: '',
+ showEmojiPanel: false,
+ showPlusPanel: false,
+ showOrderSender: false,
+ loadingHistory: false,
+ isRefreshing: false,
+ lastTimestamp: null,
+ hasMore: true,
+ scrollToView: '',
+ detailText: '',
+ showDetailModal: false,
+ lastTapTime: 0,
+ lastTapMsgId: '',
+ showOrderDetail: false,
+ detailOrder: null,
+ emojiList: ['😀','😁','😂','🤣','😃','😄','😅','😆','😉','😊','😋','😎','😍','😘','😗','😙','😚','🙂','🤗','🤩','🤔','🤨','😐','😑','😶','🙄','😏','😣','😥','😮','🤐','😯','😪','😫','😴','😌','😛','😜','😝','🤤','😒','😓','😔','😕','🙃','🤑','😲','☹','🙁','😖','😞','😟','😤','😢','😭','😦','😧','😨','😩','🤯','😬','😰','😱','🥵','🥶','😳','🤪','😵','😡','😠','🤬','😷','🤒','🤕','🤢','🤮','🤧','😇','🤠','🤡','🤥','🤫','🤭','🧐','🤓','😈','👿','👹','👺','💀','👻','👽','🤖','💩','😺','😸','😹','😻','😼','😽','🙀','😿','😾'],
+ pendingImage: '',
+ keyboardHeight: 0,
+ bottomSafeHeight: 10,
+ welcomeText: '你好,请问有什么可以帮到您的?'
+ },
+
+ onLoad(options) {
+ let teamId = '', teamName = '客服', teamAvatar = '';
+ if (options.data) {
+ try {
+ const p = JSON.parse(decodeURIComponent(options.data));
+ teamId = p.teamId || '';
+ teamName = p.teamName || '客服';
+ teamAvatar = p.teamAvatar || '';
+ } catch (e) {
+ console.error('解析客服页面参数失败', e);
+ }
+ }
+ // 默认头像使用全局配置
+ if (!teamAvatar) {
+ teamAvatar = app.globalData.ossImageUrl + app.globalData.morentouxiang;
+ }
+ this.setData({ teamId, teamName, teamAvatar });
+ wx.setNavigationBarTitle({ title: teamName });
+ this.initCurrentUser();
+ this.loadWelcomeScript();
+ },
+
+ async loadWelcomeScript() {
+ try {
+ const text = await getWelcomeText();
+ if (text) {
+ this.setData({ welcomeText: text });
+ const msgs = this.data.messages;
+ const idx = msgs.findIndex((m) => m.messageId === 'welcome-cs');
+ if (idx >= 0) {
+ const updated = [...msgs];
+ updated[idx] = { ...updated[idx], payload: { ...updated[idx].payload, text } };
+ this.setData({ messages: updated });
+ }
+ }
+ } catch (e) {
+ console.warn('loadWelcomeScript failed', e);
+ }
+ },
+
+ onShow() {
+ this.autoConnect();
+ },
+
+ onHide() {
+ this.clearAllListeners();
+ },
+
+ autoConnect() {
+ const role = app.globalData.currentRole || 'normal';
+ const uid = wx.getStorageSync('uid');
+ const prefixMap = { normal:'Boss', dashou:'Ds', shangjia:'Sj', guanshi:'Gs', zuzhang:'Zz' };
+ 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 = wx.goEasy.im ? wx.goEasy.im.userId : null;
+ if (currentUserId === targetUserId) {
+ this.setupAllListeners();
+ this.loadHistory(true);
+ return;
+ } else {
+ wx.goEasy.disconnect();
+ }
+ }
+ this.connectGoEasy(targetUserId);
+ },
+
+ connectGoEasy(userId) {
+ const { currentUser } = this.data;
+ if (!currentUser) return;
+ wx.goEasy.connect({
+ id: userId,
+ data: {
+ name: currentUser.name,
+ avatar: currentUser.avatar
+ },
+ onSuccess: () => {
+ this.setupAllListeners();
+ this.loadHistory(true);
+ },
+ onFailed: (error) => {
+ if (error.code === 408) {
+ this.setupAllListeners();
+ this.loadHistory(true);
+ } else {
+ wx.showToast({ title: '连接失败,请重试', icon: 'none' });
+ }
+ }
+ });
+ },
+
+ setupAllListeners() {
+ this.clearAllListeners();
+ this.listenNewMsg();
+ this.listenRecall();
+ this.listenDelete();
+ this.listenRead();
+ },
+
+ clearAllListeners() {
+ if (this._msgHandler) { wx.goEasy.im.off(wx.GoEasy.IM_EVENT.CS_MESSAGE_RECEIVED, this._msgHandler); this._msgHandler = null; }
+ if (this._recallHandler) { wx.goEasy.im.off(wx.GoEasy.IM_EVENT.MESSAGE_RECALLED, this._recallHandler); this._recallHandler = null; }
+ if (this._deleteHandler) { wx.goEasy.im.off(wx.GoEasy.IM_EVENT.MESSAGE_DELETED, this._deleteHandler); this._deleteHandler = null; }
+ if (this._readHandler) { wx.goEasy.im.off(wx.GoEasy.IM_EVENT.MESSAGE_READ, this._readHandler); this._readHandler = null; }
+ },
+
+ initCurrentUser() {
+ const uid = wx.getStorageSync('uid');
+ const role = app.globalData.currentRole || 'normal';
+ const prefix = { normal:'Boss', dashou:'Ds', shangjia:'Sj', guanshi:'Gs', zuzhang:'Zz' }[role] || 'Boss';
+ const avatar = this.fixAvatar(wx.getStorageSync('touxiang'));
+ this.setData({
+ currentUser: {
+ id: prefix + uid,
+ name: app.globalData.currentUser?.name || `用户${uid}`,
+ avatar: avatar
+ }
+ });
+ },
+
+ fixAvatar(url) {
+ if (!url) return app.globalData.ossImageUrl + app.globalData.morentouxiang;
+ if (url.startsWith('http')) return url;
+ return app.globalData.ossImageUrl + url;
+ },
+
+ // ---------- 历史消息 + 永久欢迎语 ----------
+ loadHistory(refresh) {
+ if (!refresh && !this.data.hasMore) return Promise.resolve();
+ return new Promise((resolve) => {
+ this.setData({ loadingHistory: true });
+ const { teamId, lastTimestamp } = this.data;
+ const ts = refresh ? null : lastTimestamp;
+ wx.goEasy.im.history({
+ type: wx.GoEasy.IM_SCENE.CS,
+ id: teamId,
+ lastTimestamp: ts,
+ limit: 20,
+ onSuccess: (res) => {
+ let list = res.content || [];
+ list.sort((a, b) => a.timestamp - b.timestamp);
+ list.forEach((m, idx) => {
+ m.formattedTime = formatDate(m.timestamp);
+ if (idx === 0) m.showTime = true;
+ else m.showTime = (m.timestamp - list[idx-1].timestamp) / 60000 > 5;
+ if (m.senderId === this.data.currentUser.id) {
+ m.senderData = { name: this.data.currentUser.name, avatar: this.data.currentUser.avatar };
+ } else {
+ m.senderData = { name: this.data.teamName, avatar: this.data.teamAvatar };
+ }
+ });
+ let final = refresh ? list : [...list, ...this.data.messages];
+ if (!refresh) {
+ for (let i=0; i 5;
+ }
+ }
+ const ids = new Set();
+ const unique = [];
+ for (const m of final) { if (!ids.has(m.messageId)) { ids.add(m.messageId); unique.push(m); } }
+
+ // 强制在第一行插入欢迎语(如果不存在)
+ const welcomeId = 'welcome-cs';
+ if (!unique.some(m => m.messageId === welcomeId)) {
+ const welcome = this.createWelcomeMessage();
+ welcome.messageId = welcomeId;
+ welcome.showTime = true;
+ unique.unshift(welcome);
+ }
+
+ const update = {
+ messages: unique,
+ hasMore: list.length >= 20,
+ lastTimestamp: unique.length ? unique[0].timestamp : lastTimestamp,
+ loadingHistory: false
+ };
+ if (refresh) update.scrollToView = 'msg-bottom';
+ this.setData(update);
+ resolve();
+ },
+ onFailed: () => { this.setData({ loadingHistory: false }); resolve(); }
+ });
+ });
+ },
+
+ createWelcomeMessage() {
+ return {
+ messageId: 'welcome-cs',
+ type: 'text',
+ timestamp: Date.now(),
+ senderId: 'system',
+ receiverId: this.data.currentUser?.id,
+ senderData: { name: '系统消息', avatar: '' },
+ payload: { text: this.data.welcomeText || '你好,请问有什么可以帮到您的?' },
+ status: 'success',
+ read: false,
+ formattedTime: formatDate(Date.now()),
+ showTime: true
+ };
+ },
+
+ // ---------- 接收新客服消息 ----------
+ listenNewMsg() {
+ if (this._msgHandler) wx.goEasy.im.off(wx.GoEasy.IM_EVENT.CS_MESSAGE_RECEIVED, this._msgHandler);
+ this._msgHandler = (msg) => {
+ if (msg.teamId !== this.data.teamId && msg.to !== this.data.teamId) return;
+ if (msg.senderId === this.data.currentUser.id) return;
+ msg.formattedTime = formatDate(msg.timestamp);
+ msg.senderData = { name: this.data.teamName, avatar: this.data.teamAvatar };
+ const msgs = this.data.messages;
+ const last = msgs.length ? msgs[msgs.length-1] : null;
+ msg.showTime = last ? (msg.timestamp - last.timestamp) / 60000 > 5 : true;
+ if (msgs.some(m => m.messageId === msg.messageId)) return;
+ this.setData({ messages: [...msgs, msg], scrollToView: 'msg-bottom' });
+ this.markCsMessageAsRead();
+ };
+ wx.goEasy.im.on(wx.GoEasy.IM_EVENT.CS_MESSAGE_RECEIVED, this._msgHandler);
+ },
+
+ markCsMessageAsRead() {
+ if (!this.data.teamId) return;
+ wx.goEasy.im.markMessageAsRead({
+ type: wx.GoEasy.IM_SCENE.CS,
+ id: this.data.teamId
+ });
+ },
+
+ // 已读回执监听
+ listenRead() {
+ if (this._readHandler) wx.goEasy.im.off(wx.GoEasy.IM_EVENT.MESSAGE_READ, this._readHandler);
+ this._readHandler = (msgs) => {
+ if (!msgs || !msgs.length) return;
+ const ids = new Set(msgs.map(m => m.messageId));
+ const newMsgs = this.data.messages.map(m => ids.has(m.messageId) ? { ...m, read: true } : m);
+ this.setData({ messages: newMsgs });
+ };
+ wx.goEasy.im.on(wx.GoEasy.IM_EVENT.MESSAGE_READ, this._readHandler);
+ },
+
+ // 撤回监听
+ listenRecall() {
+ if (this._recallHandler) wx.goEasy.im.off(wx.GoEasy.IM_EVENT.MESSAGE_RECALLED, this._recallHandler);
+ this._recallHandler = (msgs) => {
+ if (!msgs || !msgs.length) return;
+ const ids = new Set(msgs.map(m => m.messageId));
+ this.setData({ messages: this.data.messages.filter(m => !ids.has(m.messageId)) });
+ };
+ wx.goEasy.im.on(wx.GoEasy.IM_EVENT.MESSAGE_RECALLED, this._recallHandler);
+ },
+
+ // 删除监听
+ listenDelete() {
+ if (this._deleteHandler) wx.goEasy.im.off(wx.GoEasy.IM_EVENT.MESSAGE_DELETED, this._deleteHandler);
+ this._deleteHandler = (msgs) => {
+ if (!msgs || !msgs.length) return;
+ const ids = new Set(msgs.map(m => m.messageId));
+ this.setData({ messages: this.data.messages.filter(m => !ids.has(m.messageId)) });
+ };
+ wx.goEasy.im.on(wx.GoEasy.IM_EVENT.MESSAGE_DELETED, this._deleteHandler);
+ },
+
+ // ---------- 面板控制 ----------
+ togglePlusPanel() { this.setData({ showPlusPanel: !this.data.showPlusPanel, showEmojiPanel: false }); },
+ closePlusPanel() { this.setData({ showPlusPanel: false }); },
+ openEmojiFromPlus() { this.setData({ showPlusPanel: false, showEmojiPanel: true }); },
+ toggleEmoji() { this.setData({ showEmojiPanel: !this.data.showEmojiPanel }); },
+ insertEmoji(e) { this.setData({ inputText: this.data.inputText + e.currentTarget.dataset.emoji }); },
+ closeEmojiPanel() { this.setData({ showEmojiPanel: false }); },
+
+ // ---------- 发送消息 ----------
+ onInput(e) { this.setData({ inputText: e.detail.value }); },
+ chooseImage() {
+ const that = this;
+ wx.chooseImage({
+ count: 1, sizeType: ['compressed'], sourceType: ['album', 'camera'],
+ success(res) { that.setData({ pendingImage: res.tempFilePaths[0] }); }
+ });
+ },
+ clearPendingImage() { this.setData({ pendingImage: '' }); },
+
+ sendMessage() {
+ if (this.data.pendingImage) {
+ const file = this.data.pendingImage;
+ showConfirmByScene('chat_image_confirm', () => {
+ this.sendImageMsg(file);
+ this.setData({ pendingImage: '' });
+ });
+ return;
+ }
+ const text = this.data.inputText.trim();
+ if (!text) return;
+ this.sendText(text);
+ this.setData({ inputText: '' });
+ },
+
+ sendText(text) {
+ const { currentUser, teamId, teamName, teamAvatar } = this.data;
+ if (!teamId || !currentUser) return;
+ const id = 'local-'+Date.now();
+ const local = {
+ messageId:id, type:'text', timestamp:Date.now(),
+ senderId:currentUser.id, receiverId:teamId,
+ senderData: { name: currentUser.name, avatar: currentUser.avatar },
+ payload:{text}, status:'sending', read:false,
+ formattedTime:formatDate(Date.now()), showTime:this.needShow(Date.now())
+ };
+ this.setData({ messages: [...this.data.messages, local], scrollToView: 'msg-bottom' });
+ const msg = wx.goEasy.im.createTextMessage({
+ text,
+ to:{ type:wx.GoEasy.IM_SCENE.CS, id:teamId, data:{ name:teamName, avatar:teamAvatar } }
+ });
+ wx.goEasy.im.sendMessage({
+ message:msg,
+ onSuccess:() => {
+ this.updateMsg(id, 'success');
+ this.tryAutoReply(text);
+ },
+ onFailed:() => this.updateMsg(id, 'failed')
+ });
+ },
+
+ async tryAutoReply(userText) {
+ try {
+ const result = await matchAutoReply(userText);
+ if (result && result.matched && result.content) {
+ this.insertCsAutoReply(result.content);
+ }
+ } catch (e) {
+ console.warn('tryAutoReply failed', e);
+ }
+ },
+
+ insertCsAutoReply(content) {
+ const msg = {
+ messageId: 'auto-reply-' + Date.now(),
+ type: 'text',
+ timestamp: Date.now(),
+ senderId: this.data.teamId,
+ receiverId: this.data.currentUser?.id,
+ senderData: { name: this.data.teamName, avatar: this.data.teamAvatar },
+ payload: { text: content },
+ status: 'success',
+ read: false,
+ formattedTime: formatDate(Date.now()),
+ showTime: this.needShow(Date.now()),
+ };
+ this.setData({
+ messages: [...this.data.messages, msg],
+ scrollToView: 'msg-bottom',
+ });
+ },
+
+ sendImageMsg(file) {
+ const { currentUser, teamId, teamName, teamAvatar } = this.data;
+ if (!teamId || !currentUser) return;
+ const id = 'img-'+Date.now();
+ const local = {
+ messageId:id, type:'image', timestamp:Date.now(),
+ senderId:currentUser.id, receiverId:teamId,
+ payload:{url:file}, status:'sending', read:false,
+ formattedTime:formatDate(Date.now()), showTime:this.needShow(Date.now())
+ };
+ this.setData({ messages: [...this.data.messages, local], scrollToView: 'msg-bottom' });
+ const imgMsg = wx.goEasy.im.createImageMessage({
+ file,
+ to:{ type:wx.GoEasy.IM_SCENE.CS, id:teamId, data:{ name:teamName, avatar:teamAvatar } }
+ });
+ wx.goEasy.im.sendMessage({ message:imgMsg, onSuccess:()=>this.updateMsg(id,'success'), onFailed:()=>{ this.updateMsg(id,'failed'); } });
+ },
+
+ // ---------- 订单发送 ----------
+ openOrderSender() { this.setData({ showOrderSender: true, showPlusPanel: false }); },
+ closeOrderSender() { this.setData({ showOrderSender: false }); },
+ onSendOrder(e) {
+ const order = e.detail.order;
+ if (!order) return;
+ this.sendOrderMessage(order);
+ },
+
+ sendOrderMessage(order) {
+ const { currentUser, teamId, teamName, teamAvatar } = this.data;
+ if (!teamId || !currentUser) return;
+ const id = 'order-'+Date.now();
+ const local = {
+ messageId:id, type:'order', timestamp:Date.now(),
+ senderId:currentUser.id, receiverId:teamId,
+ senderData: { name: currentUser.name, avatar: currentUser.avatar },
+ payload: order, status:'sending', read:false,
+ formattedTime:formatDate(Date.now()), showTime:this.needShow(Date.now())
+ };
+ this.setData({ messages: [...this.data.messages, local], scrollToView: 'msg-bottom' });
+ const customMsg = wx.goEasy.im.createCustomMessage({
+ type:'order',
+ payload: order,
+ to:{ type:wx.GoEasy.IM_SCENE.CS, id:teamId, data:{ name:teamName, avatar:teamAvatar } }
+ });
+ wx.goEasy.im.sendMessage({
+ message:customMsg,
+ onSuccess:() => this.updateMsg(id, 'success'),
+ onFailed:() => this.updateMsg(id, 'failed')
+ });
+ },
+
+ updateMsg(id, status) { this.setData({ messages: this.data.messages.map(m => m.messageId === id ? { ...m, status } : m) }); },
+ needShow(ts) { const ms = this.data.messages; if (!ms.length) return true; const last = ms[ms.length-1]; return (ts - last.timestamp) / 60000 > 5; },
+
+ // ---------- 消息操作 ----------
+ onBubbleTap(e) {
+ const msgId = e.currentTarget.dataset.messageid;
+ const text = e.currentTarget.dataset.text;
+ const now = Date.now();
+ if (msgId === this.data.lastTapMsgId && now - this.data.lastTapTime < 350) {
+ this.setData({ detailText: text, showDetailModal: true, lastTapTime:0, lastTapMsgId:'' });
+ } else {
+ this.data.lastTapTime = now;
+ this.data.lastTapMsgId = msgId;
+ }
+ },
+ hideDetail() { this.setData({ showDetailModal: false }); },
+ copyDetail() { wx.setClipboardData({ data: this.data.detailText }); },
+
+ showAction(e) {
+ const msgId = e.currentTarget.dataset.messageid;
+ if (!msgId) return;
+ const msg = this.data.messages.find(m => m.messageId === msgId);
+ if (!msg || msg.messageId.startsWith('local-')) return;
+ const itemList = ['复制', '删除'];
+ if (msg.senderId === this.data.currentUser.id && (Date.now() - msg.timestamp < 120000)) {
+ itemList.push('撤回');
+ }
+ wx.showActionSheet({
+ itemList,
+ success: (res) => {
+ if (itemList[res.tapIndex] === '复制') { if (msg.type === 'text') wx.setClipboardData({ data: msg.payload.text }); }
+ else if (itemList[res.tapIndex] === '删除') { this.deleteMsg(msg); }
+ else if (itemList[res.tapIndex] === '撤回') { this.recallMsg(msg); }
+ }
+ });
+ },
+
+ deleteMsg(msg) {
+ wx.showModal({
+ title: '提示', content: '仅自己看不到',
+ success: (res) => {
+ if (res.confirm) {
+ wx.goEasy.im.deleteMessage({ messages: [msg] });
+ this.setData({ messages: this.data.messages.filter(m => m.messageId !== msg.messageId) });
+ }
+ }
+ });
+ },
+
+ recallMsg(msg) {
+ wx.showModal({
+ title: '提示', content: '撤回后对方也看不到',
+ success: (res) => {
+ if (res.confirm) {
+ wx.goEasy.im.recallMessage({ messages: [msg] });
+ const newMsgs = this.data.messages.map(m => {
+ if (m.messageId === msg.messageId) return { ...m, type:'text', payload:{text:'你撤回了一条消息'}, recalled:true };
+ return m;
+ });
+ this.setData({ messages: newMsgs });
+ }
+ }
+ });
+ },
+
+ previewImage(e) {
+ const url = e.currentTarget.dataset.url;
+ if (url) wx.previewImage({ urls: [url], current: url });
+ },
+
+ // 订单详情弹窗
+ showOrderDetailPopup(e) {
+ const order = e.currentTarget.dataset.order;
+ this.setData({ showOrderDetail: true, detailOrder: order });
+ },
+ hideOrderDetail() {
+ this.setData({ showOrderDetail: false, detailOrder: null });
+ },
+
+ onKeyboardHeightChange(e) { this.setData({ keyboardHeight: e.detail.height }); },
+
+ onPullDownRefresh() {
+ if (this.data.loadingHistory) return;
+ this.setData({ isRefreshing: true });
+ this.loadHistory(false).finally(() => { this.setData({ isRefreshing: false }); wx.stopPullDownRefresh(); });
+ }
+});
\ No newline at end of file
diff --git a/miniprogram/pages/kefuliaotian/kefuliaotian.json b/miniprogram/pages/kefuliaotian/kefuliaotian.json
new file mode 100644
index 0000000..23f400e
--- /dev/null
+++ b/miniprogram/pages/kefuliaotian/kefuliaotian.json
@@ -0,0 +1,7 @@
+{
+ "navigationBarTitleText": "聊天",
+ "usingComponents": {
+ "order-card": "/components/order-card/order-card",
+ "order-sender": "/components/order-sender/order-sender"
+ }
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/kefuliaotian/kefuliaotian.wxml b/miniprogram/pages/kefuliaotian/kefuliaotian.wxml
new file mode 100644
index 0000000..0b64b0c
--- /dev/null
+++ b/miniprogram/pages/kefuliaotian/kefuliaotian.wxml
@@ -0,0 +1,109 @@
+
+
+ 加载更早消息...
+
+
+ {{item.formattedTime}}
+
+
+
+
+ {{item.payload.text}}
+
+
+ 📋 订单消息
+
+ 订单号:{{item.payload.dingdan_id || '无'}}
+ 服务:{{item.payload.jieshao || ''}}
+ ¥{{item.payload.jine}}
+
+
+
+ {{item.payload.text || '[未知消息]'}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 😀
+ 表情
+
+
+ 📷
+ 图片
+
+
+ 📋
+ 订单
+
+
+
+
+
+
+
+
+
+ {{item}}
+
+
+
+
+
+
+
+ ✕
+
+
+
+
+ 发送
+
+ ➕
+
+
+
+
+
+
+
+ {{detailText}}
+
+
+
+
+
+
+
+ 订单详情
+ 订单号:{{detailOrder.dingdan_id}}
+ 服务描述:{{detailOrder.jieshao}}
+ 金额:¥{{detailOrder.jine}}
+ 类型:{{detailOrder.leixing || '无'}}
+ 时间:{{detailOrder.shijian || '无'}}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/kefuliaotian/kefuliaotian.wxss b/miniprogram/pages/kefuliaotian/kefuliaotian.wxss
new file mode 100644
index 0000000..00e37b0
--- /dev/null
+++ b/miniprogram/pages/kefuliaotian/kefuliaotian.wxss
@@ -0,0 +1,67 @@
+.chat-page { width:100%; height:100vh; background:#f5f5f5; }
+.msg-list { position:absolute; left:0; right:0; padding:0 20rpx; box-sizing:border-box; overflow-y:auto; }
+.loading-more { text-align:center; font-size:24rpx; color:#999; padding:20rpx 0; }
+.time-tag { text-align:center; margin:28rpx 0; font-size:24rpx; color:#b0b0b0; }
+.msg-wrapper { margin-bottom:12rpx; }
+.msg-row { display:flex; align-items:flex-start; margin-bottom:4rpx; }
+.right { justify-content:flex-end; }
+.left { justify-content:flex-start; }
+.avatar { width:76rpx; height:76rpx; border-radius:50%; background:#e0e0e0; margin:0 12rpx; flex-shrink:0; }
+.bubble { max-width:70%; padding:16rpx 20rpx; border-radius:16rpx; position:relative; word-break:break-all; font-size:30rpx; line-height:1.5; }
+.bubble-left { background:#fff; border-top-left-radius:4rpx; }
+.bubble-right { background:#00aaff; color:#fff; border-top-right-radius:4rpx; }
+.msg-text { color:inherit; }
+.msg-image { max-width:240rpx; border-radius:10rpx; }
+
+/* 已读/未读标签:靠右显示 */
+.read-tag {
+ display: flex;
+ justify-content: flex-end;
+ font-size: 20rpx;
+ color: #888;
+ padding: 4rpx 12rpx 0 0;
+ margin-top: 2rpx;
+}
+
+/* 订单气泡 */
+.order-bubble { background:#fff7e0; padding:12rpx; border-radius:8rpx; }
+.order-label { font-size:24rpx; color:#e6a23c; font-weight:600; margin-bottom:8rpx; }
+.order-brief { font-size:24rpx; color:#333; line-height:1.6; }
+.order-price { color:#e6a23c; font-weight:bold; }
+.order-detail-btn { margin-top:10rpx; background:#e6a23c; color:#fff; border-radius:8rpx; }
+
+/* 订单详情弹窗 */
+.order-detail-mask { position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.6); z-index:999; display:flex; justify-content:center; align-items:center; }
+.order-detail-panel { width:80%; background:#fff; border-radius:20rpx; padding:30rpx; }
+.order-detail-title { font-size:34rpx; font-weight:bold; margin-bottom:20rpx; color:#333; }
+.order-detail-item { font-size:28rpx; margin:16rpx 0; color:#555; }
+.order-detail-close-btn { width:100%; height:80rpx; background:#00aaff; color:#fff; border-radius:12rpx; margin-top:30rpx; font-size:30rpx; }
+
+/* 底部输入区域 */
+.input-area { position:fixed; left:0; right:0; background:#f7f7f7; border-top:1rpx solid #ddd; z-index:10; }
+.plus-panel { position:absolute; bottom:100%; left:0; right:0; background:#fff; border-top:1rpx solid #e0e0e0; padding:20rpx; transform:translateY(100%); opacity:0; transition:0.3s; pointer-events:none; z-index:9; }
+.plus-panel.show { transform:translateY(0); opacity:1; pointer-events:auto; }
+.plus-grid { display:flex; justify-content:space-around; }
+.plus-item { display:flex; flex-direction:column; align-items:center; width:120rpx; }
+.plus-icon { font-size:60rpx; }
+.plus-label { font-size:24rpx; color:#666; margin-top:8rpx; }
+.emoji-mask { position:fixed; top:0; left:0; right:0; bottom:0; background:transparent; z-index:8; }
+.emoji-panel { position:absolute; bottom:100%; left:0; right:0; background:#fff; border-top:1rpx solid #e0e0e0; height:380rpx; transform:translateY(100%); opacity:0; transition:0.3s; pointer-events:none; z-index:9; }
+.emoji-panel.show { transform:translateY(0); opacity:1; pointer-events:auto; }
+.emoji-scroll { height:100%; }
+.emoji-grid { display:flex; flex-wrap:wrap; padding:20rpx 10rpx; }
+.emoji-item { width:14.28%; text-align:center; padding:16rpx 0; font-size:44rpx; }
+.pending-image { display:flex; align-items:center; padding:10rpx 20rpx; background:#fff; }
+.pending-thumb { width:80rpx; height:80rpx; border-radius:8rpx; }
+.pending-remove { font-size:30rpx; color:#999; margin-left:10rpx; }
+.write-row { display:flex; padding:0 20rpx 10rpx; align-items:center; }
+.text-input { flex:1; height:76rpx; background:#fff; border-radius:12rpx; padding:0 20rpx; font-size:30rpx; margin-right:12rpx; }
+.send-btn { width:120rpx; height:76rpx; background:#00aaff; color:#fff; font-size:28rpx; font-weight:500; border-radius:12rpx; display:flex; align-items:center; justify-content:center; }
+.plus-btn { width:56rpx; height:56rpx; display:flex; align-items:center; justify-content:center; margin-left:10rpx; }
+.plus-icon-single { font-size:46rpx; color:#00aaff; }
+
+/* 双击放大文本弹窗 */
+.detail-modal { position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.7); z-index:999; display:flex; align-items:center; justify-content:center; }
+.detail-content { width:80%; max-height:80%; background:#fff; border-radius:16rpx; padding:40rpx; }
+.detail-text { font-size:32rpx; line-height:1.6; word-break:break-all; overflow-y:auto; }
+.copy-btn { width:100%; height:80rpx; background:#00aaff; color:#fff; border-radius:12rpx; margin-top:20rpx; font-size:30rpx; }
\ No newline at end of file
diff --git a/miniprogram/pages/liaotian/liaotian.js b/miniprogram/pages/liaotian/liaotian.js
new file mode 100644
index 0000000..d69e431
--- /dev/null
+++ b/miniprogram/pages/liaotian/liaotian.js
@@ -0,0 +1,452 @@
+const app = getApp();
+import { formatDate, normalizeChatMessage, getChatImageUrl } from '../../static/lib/utils';
+import { showConfirmByScene } from '../../utils/scriptService.js';
+import { sendGoEasyImage } from '../../utils/chatImageSend.js';
+
+Page({
+ data: {
+ toUserId: '', toName: '', toAvatar: '',
+ currentUser: null,
+ messages: [],
+ inputText: '',
+ showEmojiPanel: false,
+ showPlusPanel: false,
+ showOrderSender: false,
+ loadingHistory: false,
+ isRefreshing: false,
+ lastTimestamp: null,
+ hasMore: true,
+ scrollToView: '',
+ detailText: '',
+ showDetailModal: false,
+ lastTapTime: 0,
+ lastTapMsgId: '',
+ emojiList: ['😀','😁','😂','🤣','😃','😄','😅','😆','😉','😊','😋','😎','😍','😘','😗','😙','😚','🙂','🤗','🤩','🤔','🤨','😐','😑','😶','🙄','😏','😣','😥','😮','🤐','😯','😪','😫','😴','😌','😛','😜','😝','🤤','😒','😓','😔','😕','🙃','🤑','😲','☹','🙁','😖','😞','😟','😤','😢','😭','😦','😧','😨','😩','🤯','😬','😰','😱','🥵','🥶','😳','🤪','😵','😡','😠','🤬','😷','🤒','🤕','🤢','🤮','🤧','😇','🤠','🤡','🤥','🤫','🤭','🧐','🤓','😈','👿','👹','👺','💀','👻','👽','🤖','💩','😺','😸','😹','😻','😼','😽','🙀','😿','😾'],
+ pendingImage: '',
+ pendingImageFile: null,
+ keyboardHeight: 0,
+ bottomSafeHeight: 10
+ },
+
+ onLoad(options) {
+ if (options.data) {
+ try {
+ const p = JSON.parse(decodeURIComponent(options.data));
+ const toId = p.toUserId || (p.to && p.to.id) || '';
+ const toName = p.toName || (p.to && p.to.name) || '聊天';
+ const toAvatar = p.toAvatar || (p.to && p.to.avatar) || '';
+ this.setData({ toUserId: toId, toName, toAvatar });
+ } catch (e) {}
+ }
+ wx.setNavigationBarTitle({ title: this.data.toName });
+ this.initCurrentUser();
+ },
+
+ onShow() {
+ app.globalData.pageState.isInChatPage = true;
+ app.globalData.pageState.currentChatId = this.data.toUserId;
+ this.autoConnect();
+ },
+
+ onHide() {
+ app.globalData.pageState.isInChatPage = false;
+ app.globalData.pageState.currentChatId = '';
+ this.clearAllListeners();
+ },
+
+ autoConnect() {
+ const role = app.globalData.currentRole || 'normal';
+ const uid = wx.getStorageSync('uid');
+ 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 = wx.goEasy.im ? wx.goEasy.im.userId : null;
+ if (currentUserId === targetUserId) {
+ this.setupAllListeners();
+ this.loadHistory(true);
+ return;
+ } else {
+ wx.goEasy.disconnect();
+ }
+ }
+ this.connectGoEasy(targetUserId);
+ },
+
+ connectGoEasy(userId) {
+ const { currentUser } = this.data;
+ if (!currentUser) return;
+ wx.goEasy.connect({
+ id: userId,
+ data: {
+ name: currentUser.name,
+ avatar: currentUser.avatar || (app.globalData.ossImageUrl + app.globalData.morentouxiang)
+ },
+ onSuccess: () => {
+ this.setupAllListeners();
+ this.loadHistory(true);
+ },
+ onFailed: (error) => {
+ if (error.code === 408) {
+ this.setupAllListeners();
+ this.loadHistory(true);
+ } else {
+ wx.showToast({ title: '连接失败,请重试', icon: 'none' });
+ }
+ }
+ });
+ },
+
+ setupAllListeners() {
+ this.clearAllListeners();
+ this.listenNewMsg();
+ this.listenRecall();
+ this.listenDelete();
+ this.listenRead();
+ },
+
+ clearAllListeners() {
+ if (this._msgHandler) { wx.goEasy.im.off(wx.GoEasy.IM_EVENT.PRIVATE_MESSAGE_RECEIVED, this._msgHandler); this._msgHandler = null; }
+ if (this._recallHandler) { wx.goEasy.im.off(wx.GoEasy.IM_EVENT.MESSAGE_RECALLED, this._recallHandler); this._recallHandler = null; }
+ if (this._deleteHandler) { wx.goEasy.im.off(wx.GoEasy.IM_EVENT.MESSAGE_DELETED, this._deleteHandler); this._deleteHandler = null; }
+ if (this._readHandler) { wx.goEasy.im.off(wx.GoEasy.IM_EVENT.MESSAGE_READ, this._readHandler); this._readHandler = null; }
+ },
+
+ initCurrentUser() {
+ const uid = wx.getStorageSync('uid');
+ const role = app.globalData.currentRole || 'normal';
+ const prefix = { normal:'Boss', dashou:'Ds', shangjia:'Sj', guanshi:'Gs', zuzhang:'Zz',kaoheguan:'Kh' }[role] || 'Boss';
+ this.setData({ currentUser: {
+ id: prefix + uid,
+ name: app.globalData.currentUser?.name || `用户${uid}`,
+ avatar: this.fixAvatar(wx.getStorageSync('touxiang'))
+ }});
+ },
+ fixAvatar(url) {
+ if (!url) return app.globalData.ossImageUrl + app.globalData.morentouxiang;
+ if (url.startsWith('http')) return url;
+ return app.globalData.ossImageUrl + url;
+ },
+ isConnected() {
+ const s = wx.goEasy.getConnectionStatus ? wx.goEasy.getConnectionStatus() : 'disconnected';
+ return s === 'connected' || s === 'reconnected';
+ },
+
+ onPullDownRefresh() {
+ if (this.data.loadingHistory) return;
+ this.setData({ isRefreshing: true });
+ this.loadHistory(false).finally(() => { this.setData({ isRefreshing: false }); wx.stopPullDownRefresh(); });
+ },
+
+ loadHistory(refresh) {
+ if (!refresh && !this.data.hasMore) return Promise.resolve();
+ return new Promise((resolve) => {
+ this.setData({ loadingHistory: true });
+ const { toUserId, lastTimestamp } = this.data;
+ const ts = refresh ? null : lastTimestamp;
+ wx.goEasy.im.history({
+ type: wx.GoEasy.IM_SCENE.PRIVATE, id: toUserId, lastTimestamp: ts, limit: 20,
+ onSuccess: (res) => {
+ let list = res.content || [];
+ list.sort((a, b) => a.timestamp - b.timestamp);
+ list.forEach((m, idx) => {
+ normalizeChatMessage(m);
+ m.formattedTime = formatDate(m.timestamp);
+ if (idx === 0) m.showTime = true;
+ else m.showTime = (m.timestamp - list[idx-1].timestamp) / 60000 > 5;
+ if (m.senderId === this.data.currentUser.id) {
+ m.senderData = { name: this.data.currentUser.name, avatar: this.data.currentUser.avatar };
+ } else {
+ m.senderData = { name: this.data.toName, avatar: this.data.toAvatar };
+ }
+ });
+ let final = refresh ? list : [...list, ...this.data.messages];
+ if (!refresh) {
+ for (let i=0; i 5;
+ }
+ }
+ const ids = new Set();
+ const unique = [];
+ for (const m of final) { if (!ids.has(m.messageId)) { ids.add(m.messageId); unique.push(m); } }
+ const update = {
+ messages: unique,
+ hasMore: list.length >= 20,
+ lastTimestamp: unique.length ? unique[0].timestamp : lastTimestamp,
+ loadingHistory: false
+ };
+ if (refresh) update.scrollToView = 'msg-bottom';
+ this.setData(update);
+ resolve();
+ },
+ onFailed: () => { this.setData({ loadingHistory: false }); resolve(); }
+ });
+ });
+ },
+
+ listenNewMsg() {
+ if (this._msgHandler) wx.goEasy.im.off(wx.GoEasy.IM_EVENT.PRIVATE_MESSAGE_RECEIVED, this._msgHandler);
+ this._msgHandler = (msg) => {
+ if (msg.receiverId !== this.data.currentUser.id && msg.senderId !== this.data.toUserId) return;
+ normalizeChatMessage(msg);
+ msg.formattedTime = formatDate(msg.timestamp);
+ if (msg.senderId === this.data.currentUser.id) {
+ msg.senderData = { name: this.data.currentUser.name, avatar: this.data.currentUser.avatar };
+ } else {
+ msg.senderData = { name: this.data.toName, avatar: this.data.toAvatar };
+ }
+ const msgs = this.data.messages;
+ const last = msgs.length ? msgs[msgs.length-1] : null;
+ msg.showTime = last ? (msg.timestamp - last.timestamp) / 60000 > 5 : true;
+ this.setData({ messages: [...msgs, msg], scrollToView: 'msg-bottom' });
+ this.markPrivateMessageAsRead();
+ };
+ wx.goEasy.im.on(wx.GoEasy.IM_EVENT.PRIVATE_MESSAGE_RECEIVED, this._msgHandler);
+ },
+
+ markPrivateMessageAsRead() {
+ if (!this.data.toUserId) return;
+ wx.goEasy.im.markMessageAsRead({
+ type: wx.GoEasy.IM_SCENE.PRIVATE,
+ id: this.data.toUserId
+ });
+ },
+
+ listenRead() {
+ if (this._readHandler) wx.goEasy.im.off(wx.GoEasy.IM_EVENT.MESSAGE_READ, this._readHandler);
+ this._readHandler = (msgs) => {
+ if (!msgs || !msgs.length) return;
+ const ids = new Set(msgs.map(m => m.messageId));
+ const newMsgs = this.data.messages.map(m => ids.has(m.messageId) ? { ...m, read: true } : m);
+ this.setData({ messages: newMsgs });
+ };
+ wx.goEasy.im.on(wx.GoEasy.IM_EVENT.MESSAGE_READ, this._readHandler);
+ },
+
+ listenRecall() {
+ if (this._recallHandler) wx.goEasy.im.off(wx.GoEasy.IM_EVENT.MESSAGE_RECALLED, this._recallHandler);
+ this._recallHandler = (msgs) => {
+ if (!msgs || !msgs.length) return;
+ const ids = new Set(msgs.map(m => m.messageId));
+ this.setData({ messages: this.data.messages.filter(m => !ids.has(m.messageId)) });
+ };
+ wx.goEasy.im.on(wx.GoEasy.IM_EVENT.MESSAGE_RECALLED, this._recallHandler);
+ },
+
+ listenDelete() {
+ if (this._deleteHandler) wx.goEasy.im.off(wx.GoEasy.IM_EVENT.MESSAGE_DELETED, this._deleteHandler);
+ this._deleteHandler = (msgs) => {
+ if (!msgs || !msgs.length) return;
+ const ids = new Set(msgs.map(m => m.messageId));
+ this.setData({ messages: this.data.messages.filter(m => !ids.has(m.messageId)) });
+ };
+ wx.goEasy.im.on(wx.GoEasy.IM_EVENT.MESSAGE_DELETED, this._deleteHandler);
+ },
+
+ onInput(e) { this.setData({ inputText: e.detail.value }); },
+ chooseImage() {
+ const that = this;
+ const onPick = (file) => {
+ if (!file) return;
+ const path = typeof file === 'string' ? file : (file.tempFilePath || file.path);
+ if (!path) return;
+ that.setData({
+ pendingImage: path,
+ pendingImageFile: typeof file === 'string' ? { tempFilePath: path, path } : file,
+ showPlusPanel: false,
+ });
+ };
+ if (wx.chooseMedia) {
+ wx.chooseMedia({
+ count: 1,
+ mediaType: ['image'],
+ sourceType: ['album', 'camera'],
+ sizeType: ['compressed'],
+ success(res) { onPick(res.tempFiles[0]); }
+ });
+ return;
+ }
+ wx.chooseImage({
+ count: 1, sizeType: ['compressed'], sourceType: ['album', 'camera'],
+ success(res) {
+ const p = res.tempFilePaths[0];
+ onPick(res.tempFiles && res.tempFiles[0] || { tempFilePath: p, path: p });
+ }
+ });
+ },
+ clearPendingImage() { this.setData({ pendingImage: '', pendingImageFile: null }); },
+ sendMessage() {
+ if (this.data.pendingImage) {
+ const file = this.data.pendingImageFile || this.data.pendingImage;
+ showConfirmByScene('chat_image_confirm', () => {
+ this.sendImageMsg(file);
+ this.setData({ pendingImage: '', pendingImageFile: null });
+ });
+ return;
+ }
+ const text = this.data.inputText.trim();
+ if (!text) return;
+ this.sendText(text);
+ this.setData({ inputText: '' });
+ },
+
+ sendText(text) {
+ const { currentUser, toUserId } = this.data;
+ if (!toUserId || !currentUser) return;
+ const id = 'local-'+Date.now();
+ const local = {
+ messageId:id, type:'text', timestamp:Date.now(),
+ senderId:currentUser.id, receiverId:toUserId,
+ senderData: { name: currentUser.name, avatar: currentUser.avatar },
+ payload:{text}, status:'sending', read:false,
+ formattedTime:formatDate(Date.now()), showTime:this.needShow(Date.now())
+ };
+ this.setData({ messages: [...this.data.messages, local], scrollToView: 'msg-bottom' });
+ const msg = wx.goEasy.im.createTextMessage({
+ text,
+ to:{ type:wx.GoEasy.IM_SCENE.PRIVATE, id:toUserId, data:{ avatar:currentUser.avatar, name:currentUser.name } }
+ });
+ wx.goEasy.im.sendMessage({
+ message:msg,
+ onSuccess:() => this.updateMsg(id, 'success'),
+ onFailed:() => this.updateMsg(id, 'failed')
+ });
+ },
+ sendImageMsg(file) {
+ const { currentUser, toUserId } = this.data;
+ if (!toUserId || !currentUser) return;
+ const preview = typeof file === 'string' ? file : (file.tempFilePath || file.path);
+ const id = 'img-'+Date.now();
+ const local = {
+ messageId:id, type:'image', timestamp:Date.now(),
+ senderId:currentUser.id, receiverId:toUserId,
+ senderData: { name: currentUser.name, avatar: currentUser.avatar },
+ payload:{url:preview}, imageUrl:preview, status:'sending', read:false,
+ formattedTime:formatDate(Date.now()), showTime:this.needShow(Date.now())
+ };
+ this.setData({ messages: [...this.data.messages, local], scrollToView: 'msg-bottom' });
+ const to = { type: wx.GoEasy.IM_SCENE.PRIVATE, id: toUserId, data: { avatar: currentUser.avatar, name: currentUser.name } };
+ sendGoEasyImage({
+ file,
+ to,
+ onSuccess: (sentMsg) => this.onImageSendSuccess(id, sentMsg, preview),
+ onFailed: () => this.updateMsg(id, 'failed'),
+ });
+ },
+ onImageSendSuccess(localId, sentMsg, localFile) {
+ const normalized = normalizeChatMessage(sentMsg || {});
+ const url = getChatImageUrl(normalized) || localFile;
+ this.setData({
+ messages: this.data.messages.map(m => {
+ if (m.messageId !== localId) return m;
+ return {
+ ...m,
+ ...normalized,
+ messageId: normalized.messageId || m.messageId,
+ payload: { ...(m.payload || {}), url },
+ imageUrl: url,
+ status: 'success'
+ };
+ })
+ });
+ },
+ updateMsg(id, status) { this.setData({ messages: this.data.messages.map(m => m.messageId === id ? { ...m, status } : m) }); },
+ needShow(ts) { const ms = this.data.messages; if (!ms.length) return true; const last = ms[ms.length-1]; return (ts - last.timestamp) / 60000 > 5; },
+
+ onBubbleTap(e) {
+ const msgId = e.currentTarget.dataset.messageid;
+ const text = e.currentTarget.dataset.text;
+ const now = Date.now();
+ if (msgId === this.data.lastTapMsgId && now - this.data.lastTapTime < 350) {
+ this.setData({ detailText: text, showDetailModal: true, lastTapTime:0, lastTapMsgId:'' });
+ } else {
+ this.data.lastTapTime = now;
+ this.data.lastTapMsgId = msgId;
+ }
+ },
+ hideDetail() { this.setData({ showDetailModal: false }); },
+ copyDetail() { wx.setClipboardData({ data: this.data.detailText }); },
+
+ showAction(e) {
+ const msgId = e.currentTarget.dataset.messageid;
+ if (!msgId) return;
+ const msg = this.data.messages.find(m => m.messageId === msgId);
+ if (!msg || msg.messageId.startsWith('local-')) return;
+ const itemList = ['复制', '删除'];
+ if (msg.senderId === this.data.currentUser.id && (Date.now() - msg.timestamp < 120000)) {
+ itemList.push('撤回');
+ }
+ wx.showActionSheet({
+ itemList,
+ success: (res) => {
+ if (itemList[res.tapIndex] === '复制') { if (msg.type === 'text') wx.setClipboardData({ data: msg.payload.text }); }
+ else if (itemList[res.tapIndex] === '删除') { this.deleteMsg(msg); }
+ else if (itemList[res.tapIndex] === '撤回') { this.recallMsg(msg); }
+ }
+ });
+ },
+
+ deleteMsg(msg) {
+ wx.showModal({
+ title: '提示', content: '仅自己看不到',
+ success: (res) => {
+ if (res.confirm) {
+ wx.goEasy.im.deleteMessage({
+ messages: [msg],
+ onSuccess: () => { this.setData({ messages: this.data.messages.filter(m => m.messageId !== msg.messageId) }); },
+ onFailed: () => { this.setData({ messages: this.data.messages.filter(m => m.messageId !== msg.messageId) }); }
+ });
+ }
+ }
+ });
+ },
+
+ recallMsg(msg) {
+ wx.showModal({
+ title: '提示', content: '撤回后对方也看不到',
+ success: (res) => {
+ if (res.confirm) {
+ const newMsgs = this.data.messages.map(m => {
+ if (m.messageId === msg.messageId) return { ...m, type:'text', payload:{text:'你撤回了一条消息'}, recalled:true };
+ return m;
+ });
+ this.setData({ messages: newMsgs });
+ wx.goEasy.im.recallMessage({ messages: [msg], onFailed:()=>{} });
+ }
+ }
+ });
+ },
+
+ previewImage(e) {
+ const url = e.currentTarget.dataset.url;
+ if (!url) return;
+ const urls = this.data.messages
+ .filter(m => m.type === 'image')
+ .map(m => m.imageUrl || getChatImageUrl(m))
+ .filter(Boolean);
+ wx.previewImage({ urls: urls.length ? urls : [url], current: url });
+ },
+
+ togglePlusPanel() { this.setData({ showPlusPanel: !this.data.showPlusPanel, showEmojiPanel: false }); },
+ closePlusPanel() { this.setData({ showPlusPanel: false }); },
+
+ openEmojiFromPlus() { this.setData({ showPlusPanel: false, showEmojiPanel: true }); },
+ toggleEmoji() { this.setData({ showEmojiPanel: !this.data.showEmojiPanel }); },
+ insertEmoji(e) { this.setData({ inputText: this.data.inputText + e.currentTarget.dataset.emoji }); },
+ closeEmojiPanel() { this.setData({ showEmojiPanel: false }); },
+
+ openOrderSender() { this.setData({ showOrderSender: true, showPlusPanel: false }); },
+ closeOrderSender() { this.setData({ showOrderSender: false }); },
+ onSendOrder(e) {
+ const order = e.detail.order;
+ if (!order) return;
+ const text = `[订单]${order.dingdan_id}\n${order.jieshao}\n¥${order.jine}`;
+ this.setData({ inputText: text });
+ this.sendMessage();
+ },
+
+ onKeyboardHeightChange(e) { this.setData({ keyboardHeight: e.detail.height }); }
+});
\ No newline at end of file
diff --git a/miniprogram/pages/liaotian/liaotian.json b/miniprogram/pages/liaotian/liaotian.json
new file mode 100644
index 0000000..23f400e
--- /dev/null
+++ b/miniprogram/pages/liaotian/liaotian.json
@@ -0,0 +1,7 @@
+{
+ "navigationBarTitleText": "聊天",
+ "usingComponents": {
+ "order-card": "/components/order-card/order-card",
+ "order-sender": "/components/order-sender/order-sender"
+ }
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/liaotian/liaotian.wxml b/miniprogram/pages/liaotian/liaotian.wxml
new file mode 100644
index 0000000..db692d2
--- /dev/null
+++ b/miniprogram/pages/liaotian/liaotian.wxml
@@ -0,0 +1,87 @@
+
+
+ 加载更早消息...
+
+
+ {{item.formattedTime}}
+
+
+
+ {{item.payload.text}}
+
+
+ [订单]
+ ID: {{item.payload.orderId}}
+
+
+
+
+
+ {{item.read ? '已读' : '未读'}}
+
+
+
+
+
+
+
+
+
+
+ 😀
+ 表情
+
+
+ 📷
+ 图片
+
+
+ 📋
+ 订单
+
+
+
+
+
+
+
+
+
+ {{item}}
+
+
+
+
+
+
+
+ ✕
+
+
+
+
+ 发送
+
+ ➕
+
+
+
+
+
+
+ {{detailText}}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/liaotian/liaotian.wxss b/miniprogram/pages/liaotian/liaotian.wxss
new file mode 100644
index 0000000..21a4b4e
--- /dev/null
+++ b/miniprogram/pages/liaotian/liaotian.wxss
@@ -0,0 +1,98 @@
+.chat-page { width:100%; height:100vh; background:#f5f5f5; }
+
+.msg-list {
+ position: absolute;
+ left:0; right:0;
+ padding: 0 20rpx;
+ box-sizing: border-box;
+ overflow-y: auto;
+}
+
+.loading-more { text-align:center; font-size:24rpx; color:#999; padding:20rpx 0; }
+.time-tag { text-align:center; margin:28rpx 0; font-size:24rpx; color:#b0b0b0; }
+.msg-wrapper { margin-bottom:12rpx; }
+.msg-row { display:flex; align-items:flex-start; margin-bottom:4rpx; }
+.right { justify-content:flex-end; }
+.left { justify-content:flex-start; }
+.avatar { width:76rpx; height:76rpx; border-radius:50%; background:#e0e0e0; margin:0 12rpx; flex-shrink:0; }
+.bubble { max-width:70%; padding:16rpx 20rpx; border-radius:16rpx; position:relative; word-break:break-all; font-size:30rpx; line-height:1.5; }
+.bubble-left { background:#fff; border-top-left-radius:4rpx; }
+.bubble-right { background:#00aaff; color:#fff; border-top-right-radius:4rpx; }
+.msg-text { color:inherit; }
+.msg-image { max-width:240rpx; border-radius:10rpx; }
+.order-bubble { background:#fff7e0; padding:12rpx; border-radius:8rpx; }
+.order-label { font-size:24rpx; color:#e6a23c; font-weight:600; }
+.order-info { font-size:24rpx; color:#333; margin:6rpx 0; }
+
+.read-tag {
+ font-size:20rpx;
+ color:#888;
+ text-align:right;
+ padding:4rpx 12rpx 0 0;
+ margin-top: -4rpx;
+}
+
+.input-area {
+ position: fixed;
+ left:0; right:0;
+ background:#f7f7f7;
+ border-top:1rpx solid #ddd;
+ z-index:10;
+}
+
+.plus-panel {
+ position: absolute;
+ bottom:100%; left:0; right:0;
+ background:#fff;
+ border-top:1rpx solid #e0e0e0;
+ padding: 20rpx;
+ transform: translateY(100%);
+ opacity:0;
+ transition: transform 0.3s, opacity 0.3s;
+ pointer-events:none;
+ z-index:9;
+}
+.plus-panel.show { transform: translateY(0); opacity:1; pointer-events:auto; }
+.plus-grid { display: flex; justify-content: space-around; }
+.plus-item { display: flex; flex-direction: column; align-items: center; width: 120rpx; }
+.plus-icon { font-size: 60rpx; }
+.plus-label { font-size: 24rpx; color: #666; margin-top: 8rpx; }
+
+.emoji-mask { position:fixed; top:0; left:0; right:0; bottom:0; background:transparent; z-index:8; }
+.emoji-panel {
+ position: absolute; bottom:100%; left:0; right:0;
+ background:#fff; border-top:1rpx solid #e0e0e0;
+ height:380rpx;
+ transform: translateY(100%); opacity:0; transition:0.3s;
+ pointer-events:none; z-index:9;
+}
+.emoji-panel.show { transform:translateY(0); opacity:1; pointer-events:auto; }
+.emoji-scroll { height:100%; }
+.emoji-grid { display:flex; flex-wrap:wrap; padding:20rpx 10rpx; }
+.emoji-item { width:14.28%; text-align:center; padding:16rpx 0; font-size:44rpx; }
+
+.pending-image { display:flex; align-items:center; padding:10rpx 20rpx; background:#fff; }
+.pending-thumb { width:80rpx; height:80rpx; border-radius:8rpx; }
+.pending-remove { font-size:30rpx; color:#999; margin-left:10rpx; }
+
+.write-row { display:flex; padding:0 20rpx 10rpx; align-items:center; }
+.text-input {
+ flex:1; height:76rpx; background:#fff; border-radius:12rpx;
+ padding:0 20rpx; font-size:30rpx; margin-right:12rpx;
+}
+.send-btn {
+ width:120rpx; height:76rpx; background:#00aaff; color:#fff;
+ font-size:28rpx; font-weight:500; border-radius:12rpx;
+ display:flex; align-items:center; justify-content:center;
+}
+.plus-btn {
+ width:56rpx; height:56rpx;
+ display:flex; align-items:center; justify-content:center;
+ margin-left: 10rpx;
+}
+.plus-icon-single { font-size:46rpx; color:#00aaff; }
+
+.detail-modal { position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.7); z-index:999; display:flex; align-items:center; justify-content:center; }
+.detail-content { width:80%; max-height:80%; background:#fff; border-radius:16rpx; padding:40rpx; }
+.detail-text { font-size:32rpx; line-height:1.6; word-break:break-all; overflow-y:auto; }
+.copy-btn { width:100%; height:80rpx; background:#00aaff; color:#fff; border-radius:12rpx; margin-top:20rpx; font-size:30rpx; }
\ No newline at end of file
diff --git a/miniprogram/pages/phone-auth/phone-auth.js b/miniprogram/pages/phone-auth/phone-auth.js
new file mode 100644
index 0000000..d61714f
--- /dev/null
+++ b/miniprogram/pages/phone-auth/phone-auth.js
@@ -0,0 +1,40 @@
+import { submit } from '../../utils/phone-auth';
+
+Page({
+ data: {
+ show: false,
+ avatar: '',
+ avatarFile: null,
+ phoneCode: '',
+ phoneGot: false
+ },
+ onLoad() {
+ setTimeout(() => this.setData({ show: true }), 100);
+ },
+ onAvatar(e) {
+ const p = e.detail.avatarUrl;
+ this.setData({ avatar: p, avatarFile: p });
+ },
+ onPhone(e) {
+ if (e.detail.code) {
+ this.setData({ phoneCode: e.detail.code, phoneGot: true });
+ }
+ },
+ async submit() {
+ const { phoneCode, avatarFile } = this.data;
+ if (!avatarFile || !phoneCode) return;
+ try {
+ wx.showLoading({ title: '认证中...', mask: true });
+ await submit(phoneCode, avatarFile);
+ wx.hideLoading();
+ // 成功,重启小程序,此时check返回false,用户无感进入
+ wx.reLaunch({ url: '/pages/index/index' });
+ } catch (e) {
+ wx.hideLoading();
+ wx.showToast({ title: e.message, icon: 'none' });
+ }
+ },
+ exit() {
+ wx.exitMiniProgram();
+ }
+});
\ No newline at end of file
diff --git a/miniprogram/pages/phone-auth/phone-auth.json b/miniprogram/pages/phone-auth/phone-auth.json
new file mode 100644
index 0000000..34bbf75
--- /dev/null
+++ b/miniprogram/pages/phone-auth/phone-auth.json
@@ -0,0 +1,3 @@
+{
+ "navigationStyle": "custom"
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/phone-auth/phone-auth.wxml b/miniprogram/pages/phone-auth/phone-auth.wxml
new file mode 100644
index 0000000..512af56
--- /dev/null
+++ b/miniprogram/pages/phone-auth/phone-auth.wxml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+ 完善资料
+ 仅需两步,即可开始
+
+
+
+ 1
+ 设置你的头像(一定要选哦)
+
+
+
+
+
+ 2
+ 绑定微信手机号
+
+
+
+
+
+
+
+ 暂不认证,退出
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/phone-auth/phone-auth.wxss b/miniprogram/pages/phone-auth/phone-auth.wxss
new file mode 100644
index 0000000..2f24905
--- /dev/null
+++ b/miniprogram/pages/phone-auth/phone-auth.wxss
@@ -0,0 +1,169 @@
+/* 极简居中卡片设计 - 世界级UI */
+
+/* 遮罩 */
+.mask {
+ position: fixed;
+ top: 0; left: 0; right: 0; bottom: 0;
+ background: rgba(0,0,0,0.45);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 9999;
+ backdrop-filter: blur(6rpx);
+ }
+
+ /* 卡片 */
+ .card {
+ width: 640rpx;
+ background: #ffffff;
+ border-radius: 32rpx;
+ padding: 60rpx 48rpx 48rpx;
+ box-shadow: 0 20rpx 60rpx rgba(0,0,0,0.08);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ animation: fadeIn 0.3s ease;
+ }
+
+ @keyframes fadeIn {
+ from { opacity: 0; transform: scale(0.95); }
+ to { opacity: 1; transform: scale(1); }
+ }
+
+ /* 图标区域 */
+ .icon-box {
+ width: 96rpx;
+ height: 96rpx;
+ border-radius: 50%;
+ background: #f0f4ff;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-bottom: 24rpx;
+ }
+
+ .icon {
+ width: 52rpx;
+ height: 52rpx;
+ }
+
+ /* 标题 */
+ .title {
+ font-size: 40rpx;
+ font-weight: 600;
+ color: #1a1a1a;
+ margin-bottom: 8rpx;
+ }
+
+ .subtitle {
+ font-size: 26rpx;
+ color: #999;
+ margin-bottom: 40rpx;
+ }
+
+ /* 步骤 */
+ .step {
+ display: flex;
+ align-items: center;
+ width: 100%;
+ margin-bottom: 16rpx;
+ }
+
+ .step-number {
+ width: 40rpx;
+ height: 40rpx;
+ border-radius: 50%;
+ background: #f0f4ff;
+ color: #4a6cf7;
+ font-size: 24rpx;
+ font-weight: 600;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-right: 16rpx;
+ flex-shrink: 0;
+ }
+
+ .step-text {
+ font-size: 28rpx;
+ color: #444;
+ }
+
+ /* 头像按钮 */
+ .avatar-btn {
+ display: flex;
+ align-items: center;
+ width: 100%;
+ height: 100rpx;
+ background: #f8f9ff;
+ border: 2rpx solid #eef0f6;
+ border-radius: 20rpx;
+ padding: 0 24rpx;
+ margin-bottom: 32rpx;
+ box-sizing: border-box;
+ text-align: left;
+ }
+
+ .avatar-img {
+ width: 68rpx;
+ height: 68rpx;
+ border-radius: 50%;
+ background: #eee;
+ flex-shrink: 0;
+ }
+
+ .avatar-tip {
+ margin-left: 20rpx;
+ font-size: 26rpx;
+ color: #666;
+ }
+
+ /* 手机号按钮 */
+ .phone-btn {
+ display: flex;
+ align-items: center;
+ width: 100%;
+ height: 96rpx;
+ background: #4a6cf7;
+ border-radius: 48rpx;
+ justify-content: center;
+ margin-bottom: 40rpx;
+ padding: 0;
+ border: none;
+ color: #fff;
+ font-size: 30rpx;
+ font-weight: 500;
+ }
+
+ .phone-icon {
+ margin-right: 12rpx;
+ font-size: 32rpx;
+ }
+
+ /* 提交按钮 */
+ .submit-btn {
+ width: 100%;
+ height: 96rpx;
+ background: #1a1a1a;
+ border-radius: 48rpx;
+ color: #fff;
+ font-size: 32rpx;
+ font-weight: 600;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border: none;
+ margin-bottom: 24rpx;
+ }
+
+ .submit-btn[disabled] {
+ background: #ccc;
+ color: #fff;
+ }
+
+ /* 退出 */
+ .exit {
+ font-size: 26rpx;
+ color: #aaa;
+ padding: 8rpx 0;
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/qunliaotian/qunliaotian.json b/miniprogram/pages/qunliaotian/qunliaotian.json
new file mode 100644
index 0000000..d5bfa0e
--- /dev/null
+++ b/miniprogram/pages/qunliaotian/qunliaotian.json
@@ -0,0 +1,6 @@
+{
+ "navigationBarTitleText": "群聊",
+ "usingComponents": {
+ "order-card": "/components/order-card/order-card"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/pages/qunliaotian/qunliaotian.wxml b/miniprogram/pages/qunliaotian/qunliaotian.wxml
new file mode 100644
index 0000000..988a577
--- /dev/null
+++ b/miniprogram/pages/qunliaotian/qunliaotian.wxml
@@ -0,0 +1,99 @@
+
+
+
+ 订单 {{orderId}}
+ {{orderZhuangtaiText}}
+
+ {{orderJieshao}}
+
+
+ 加载更早消息...
+
+
+ {{item.formattedTime}}
+
+
+
+
+ {{item.senderData.name || item.senderId}}
+
+
+ {{item.payload.text}}
+
+
+ [订单]
+ ID: {{item.payload.orderId}}
+ 状态: {{item.payload.zhuangtaiText || item.payload.zhuangtai}}
+ 内容: {{item.payload.jieshao}}
+ 金额: ¥{{item.payload.jine}}
+ 点击查看详情
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 😀
+ 表情
+
+
+ 📷
+ 图片
+
+
+ 📋
+ 订单
+
+
+
+
+
+
+
+
+
+ {{item}}
+
+
+
+
+
+
+
+ ✕
+
+
+
+
+ 发送
+
+ ➕
+
+
+
+
+
+
+ {{detailText}}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/qunliaotian/qunliaotian.wxss b/miniprogram/pages/qunliaotian/qunliaotian.wxss
new file mode 100644
index 0000000..f8a8064
--- /dev/null
+++ b/miniprogram/pages/qunliaotian/qunliaotian.wxss
@@ -0,0 +1,133 @@
+.chat-page { width:100%; height:100vh; background:#f5f5f5; }
+
+.order-status-bar {
+ position: fixed;
+ top: 0; left: 0; right: 0;
+ z-index: 11;
+ background: #fff;
+ padding: 16rpx 24rpx;
+ border-bottom: 1rpx solid #e8e8e8;
+ box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.04);
+}
+.order-status-main { display: flex; align-items: center; justify-content: space-between; }
+.order-status-id { font-size: 26rpx; color: #333; font-weight: 600; }
+.order-status-tag {
+ font-size: 22rpx; color: #07c160; background: #e8f8ee;
+ padding: 4rpx 16rpx; border-radius: 20rpx;
+}
+.order-status-desc {
+ font-size: 24rpx; color: #888; margin-top: 8rpx;
+ display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
+}
+
+.msg-list {
+ position: absolute;
+ left:0; right:0;
+ top: 100rpx;
+ padding: 0 20rpx;
+ box-sizing: border-box;
+ overflow-y: auto;
+}
+
+.loading-more { text-align:center; font-size:24rpx; color:#999; padding:20rpx 0; }
+.time-tag { text-align:center; margin:28rpx 0; font-size:24rpx; color:#b0b0b0; }
+.msg-wrapper { margin-bottom:12rpx; }
+.msg-row { display:flex; align-items:flex-start; margin-bottom:4rpx; }
+.right { justify-content:flex-end; }
+.left { justify-content:flex-start; }
+.avatar { width:76rpx; height:76rpx; border-radius:50%; background:#e0e0e0; margin:0 12rpx; flex-shrink:0; }
+.bubble { max-width:70%; padding:16rpx 20rpx; border-radius:16rpx; position:relative; word-break:break-all; font-size:30rpx; line-height:1.5; }
+.bubble-left { background:#fff; border-top-left-radius:4rpx; }
+.bubble-right { background:#00aaff; color:#fff; border-top-right-radius:4rpx; }
+.bubble-image { padding:0; background:transparent !important; overflow:hidden; }
+.bubble-image.bubble-right { color:inherit; }
+.msg-text { color:inherit; }
+.msg-image { max-width:360rpx; min-width:120rpx; display:block; border-radius:10rpx; }
+
+.order-bubble { background:#fff7e0; padding:12rpx; border-radius:8rpx; }
+.order-label { font-size:24rpx; color:#e6a23c; font-weight:600; }
+.order-info { font-size:24rpx; color:#333; margin:6rpx 0; }
+.order-status-inline { color: #07c160; font-weight: 600; }
+.order-detail-link { color:#007aff; font-size:24rpx; margin-top:8rpx; }
+
+.input-area {
+ position: fixed;
+ left:0; right:0;
+ background:#f7f7f7;
+ border-top:1rpx solid #ddd;
+ z-index:10;
+}
+
+.plus-panel {
+ position: absolute;
+ bottom:100%; left:0; right:0;
+ background:#fff;
+ border-top:1rpx solid #e0e0e0;
+ padding: 20rpx;
+ transform: translateY(100%);
+ opacity:0;
+ transition: transform 0.3s, opacity 0.3s;
+ pointer-events:none;
+ z-index:9;
+}
+.plus-panel.show { transform: translateY(0); opacity:1; pointer-events:auto; }
+.plus-grid { display: flex; justify-content: space-around; }
+.plus-item { display: flex; flex-direction: column; align-items: center; width: 120rpx; }
+.plus-icon { font-size: 60rpx; }
+.plus-label { font-size: 24rpx; color: #666; margin-top: 8rpx; }
+
+.emoji-mask { position:fixed; top:0; left:0; right:0; bottom:0; background:transparent; z-index:8; }
+.emoji-panel {
+ position: absolute; bottom:100%; left:0; right:0;
+ background:#fff; border-top:1rpx solid #e0e0e0;
+ height:380rpx;
+ transform: translateY(100%); opacity:0; transition:0.3s;
+ pointer-events:none; z-index:9;
+}
+.emoji-panel.show { transform:translateY(0); opacity:1; pointer-events:auto; }
+.emoji-scroll { height:100%; }
+.emoji-grid { display:flex; flex-wrap:wrap; padding:20rpx 10rpx; }
+.emoji-item { width:14.28%; text-align:center; padding:16rpx 0; font-size:44rpx; }
+
+.pending-image { display:flex; align-items:center; padding:10rpx 20rpx; background:#fff; }
+.pending-thumb { width:80rpx; height:80rpx; border-radius:8rpx; }
+.pending-remove { font-size:30rpx; color:#999; margin-left:10rpx; }
+
+.write-row { display:flex; padding:0 20rpx 10rpx; align-items:center; }
+.text-input {
+ flex:1; height:76rpx; background:#fff; border-radius:12rpx;
+ padding:0 20rpx; font-size:30rpx; margin-right:12rpx;
+}
+.send-btn {
+ width:120rpx; height:76rpx; background:#00aaff; color:#fff;
+ font-size:28rpx; font-weight:500; border-radius:12rpx;
+ display:flex; align-items:center; justify-content:center;
+}
+.plus-btn {
+ width:56rpx; height:56rpx;
+ display:flex; align-items:center; justify-content:center;
+ margin-left: 10rpx;
+}
+.plus-icon-single { font-size:46rpx; color:#00aaff; }
+
+.detail-modal { position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.7); z-index:999; display:flex; align-items:center; justify-content:center; }
+.detail-content { width:80%; max-height:80%; background:#fff; border-radius:16rpx; padding:40rpx; }
+.detail-text { font-size:32rpx; line-height:1.6; word-break:break-all; overflow-y:auto; }
+.copy-btn { width:100%; height:80rpx; background:#00aaff; color:#fff; border-radius:12rpx; margin-top:20rpx; font-size:30rpx; }
+
+/* 发送者昵称样式 */
+.sender-info {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ margin-right: 10rpx;
+}
+.sender-name {
+ font-size: 20rpx;
+ color: #888;
+ margin-top: 4rpx;
+ max-width: 100rpx;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
\ No newline at end of file
diff --git a/miniprogram/pages/renzheng/renzheng.js b/miniprogram/pages/renzheng/renzheng.js
new file mode 100644
index 0000000..84420a9
--- /dev/null
+++ b/miniprogram/pages/renzheng/renzheng.js
@@ -0,0 +1,155 @@
+// pages/renzheng/renzheng.js
+import request from '../../utils/request.js'
+
+const API_MAP = {
+ dashou: '/yonghu/dashouzhuce',
+ shangjia: '/yonghu/shangjiahuce',
+ guanshi: '/yonghu/guanshizhuce',
+ zuzhang: '/yonghu/zuzhangzhuce',
+ kaoheguan: '/dengji/khgzc' // 🆕 考核官注册接口
+}
+
+const STATUS_KEY_MAP = {
+ dashou: 'dashoustatus',
+ shangjia: 'shangjiastatus',
+ guanshi: 'guanshistatus',
+ zuzhang: 'zuzhangstatus',
+ kaoheguan: 'kaoheguanstatus' // 🆕 考核官缓存键
+}
+
+const NAME_MAP = {
+ dashou: '接单员',
+ shangjia: '商家',
+ guanshi: '管事',
+ zuzhang: '组长',
+ kaoheguan: '考核官' // 🆕 考核官中文名
+}
+
+Page({
+ data: {
+ authType: '',
+ authName: '',
+ pageState: 'need_register',
+ inviteCode: '',
+ isLoading: false,
+ },
+
+ onLoad(options) {
+ const type = options.type || ''
+ if (!API_MAP[type]) {
+ wx.showToast({ title: '认证类型错误', icon: 'none' })
+ setTimeout(() => wx.navigateBack(), 1500)
+ return
+ }
+ this.setData({ authType: type, authName: NAME_MAP[type] })
+ this.checkStatus()
+ },
+
+ onShow() {
+ if (this.data.authType) this.checkStatus()
+ },
+
+ checkStatus() {
+ const key = STATUS_KEY_MAP[this.data.authType]
+ const val = wx.getStorageSync(key)
+ if (val === 1) {
+ this.setData({ pageState: 'done' })
+ } else {
+ this.setData({ pageState: 'need_register' })
+ }
+ },
+
+ onInviteCodeInput(e) {
+ this.setData({ inviteCode: e.detail.value.trim() })
+ },
+
+ async onRegister() {
+ const { inviteCode, authType } = this.data
+ if (!inviteCode) {
+ wx.showToast({ title: '请输入邀请码', icon: 'none' })
+ return
+ }
+ if (inviteCode.length > 100) {
+ wx.showToast({ title: '邀请码不能超过100字符', icon: 'none' })
+ return
+ }
+
+ this.setData({ isLoading: true })
+ wx.showLoading({ title: '注册中...', mask: true })
+
+ try {
+ const res = await request({
+ url: API_MAP[authType],
+ method: 'POST',
+ data: { inviteCode }
+ })
+
+ if (res && res.data.code === 200) {
+ const data = res.data.data || res.data
+ const app = getApp()
+
+ // ----- 缓存身份状态(完全由后端决定) -----
+ const key = STATUS_KEY_MAP[authType]
+ if (data[key] !== undefined) {
+ wx.setStorageSync(key, data[key])
+ app.globalData[key] = data[key]
+ } else {
+ wx.setStorageSync(key, 1)
+ app.globalData[key] = 1
+ }
+
+ // 更新其他可能返回的身份状态字段
+ const allKeys = ['dashoustatus', 'shangjiastatus', 'guanshistatus', 'zuzhangstatus', 'kaoheguanstatus'] // 🆕 新增
+ allKeys.forEach(k => {
+ if (data[k] !== undefined && k !== key) {
+ wx.setStorageSync(k, data[k])
+ app.globalData[k] = data[k]
+ }
+ })
+
+ // 保存各身份详细信息(参照各注册页面的处理)
+ if (authType === 'dashou') {
+ const fields = ['dashounicheng','zhanghaostatus','yongjin','zonge','yajin','chenghao','jifen','clumber','chengjiaoliang','zaixianzhuangtai']
+ fields.forEach(f => { if (data[f] !== undefined) app.globalData[f] = data[f] })
+ } else if (authType === 'shangjia') {
+ app.globalData.shangjia = app.globalData.shangjia || {}
+ const sf = ['nicheng','sjyue','fabu','chengjiao','tuikuan','jinridingdan','jinriliushui','jinyuedingdan','jinyueliushui','zhuangtai']
+ sf.forEach(f => { if (data[f] !== undefined) app.globalData.shangjia[f] = data[f] })
+ } else if (authType === 'guanshi') {
+ app.globalData.guanshi = app.globalData.guanshi || {}
+ const gf = ['gszhstatus','yaoqingzongshu','fenyongzonge','fenyongtixian','yichongzhiDashou']
+ gf.forEach(f => { if (data[f] !== undefined) app.globalData.guanshi[f] = data[f] })
+ } else if (authType === 'zuzhang') {
+ const zf = ['ketixian','guanshiCount','fenhongZonge']
+ zf.forEach(f => { if (data[f] !== undefined) app.globalData[f] = data[f] })
+ } else if (authType === 'kaoheguan') { // 🆕 考核官数据更新
+ app.globalData.kaoheguan = app.globalData.kaoheguan || {}
+ const kf = ['shenhe_zongshu','tongguo_zongshu','yue','zonge','shenhe_zhuangtai','bankuai_list']
+ kf.forEach(f => { if (data[f] !== undefined) app.globalData.kaoheguan[f] = data[f] })
+ }
+
+ wx.hideLoading()
+ this.setData({ isLoading: false, pageState: 'done' })
+ wx.showToast({ title: '认证成功', icon: 'success' })
+ } else {
+ wx.hideLoading()
+ this.setData({ isLoading: false })
+ wx.showToast({ title: res?.data?.msg || '注册失败', icon: 'none' })
+ }
+ } catch (err) {
+ wx.hideLoading()
+ this.setData({ isLoading: false })
+ wx.showToast({ title: err.message || '注册失败', icon: 'none' })
+ }
+ },
+
+ goToKefu() {
+ const app = getApp()
+ const { link, enterpriseId } = app.globalData.kefuConfig || {}
+ if (!link || !enterpriseId) {
+ wx.showToast({ title: '客服配置未加载', icon: 'none' })
+ return
+ }
+ wx.openCustomerServiceChat({ extInfo: { url: link }, corpId: enterpriseId })
+ }
+})
\ No newline at end of file
diff --git a/miniprogram/pages/renzheng/renzheng.json b/miniprogram/pages/renzheng/renzheng.json
new file mode 100644
index 0000000..ba70bf8
--- /dev/null
+++ b/miniprogram/pages/renzheng/renzheng.json
@@ -0,0 +1,8 @@
+{
+ "usingComponents": {},
+ "navigationBarTitleText": "身份认证",
+ "navigationBarBackgroundColor": "#F7F3ED",
+ "navigationBarTextStyle": "black",
+ "backgroundColor": "#F7F3ED",
+ "enablePullDownRefresh": false
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/renzheng/renzheng.wxml b/miniprogram/pages/renzheng/renzheng.wxml
new file mode 100644
index 0000000..5b4b678
--- /dev/null
+++ b/miniprogram/pages/renzheng/renzheng.wxml
@@ -0,0 +1,29 @@
+
+
+
+
+ {{authName}}认证
+ 您尚未注册此角色或账号已被封禁,请输入邀请码注册或联系客服
+
+
+
+
+ {{isLoading ? '注册中…' : '立即注册'}}
+
+ 联系客服
+
+
+
+
+ ✓
+ 认证成功
+
+ 您已是{{authName}}身份,
+ 请退出此页面点击底部导航栏左侧的
+ 「切换」
+ 按钮,选择
+ 「{{authName}}」
+ 即可开始使用。
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/renzheng/renzheng.wxss b/miniprogram/pages/renzheng/renzheng.wxss
new file mode 100644
index 0000000..1dbbb97
--- /dev/null
+++ b/miniprogram/pages/renzheng/renzheng.wxss
@@ -0,0 +1,95 @@
+page {
+ background: #F7F3ED;
+ height: 100vh;
+ }
+
+ .page-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 100rpx 40rpx;
+ }
+
+ .card {
+ width: 100%;
+ background: #fff;
+ border-radius: 36rpx;
+ padding: 60rpx 40rpx;
+ box-shadow: 0 20rpx 40rpx rgba(0,0,0,0.04);
+ text-align: center;
+ }
+
+ .card-title {
+ font-size: 40rpx;
+ font-weight: 700;
+ color: #1A1A1A;
+ margin-bottom: 30rpx;
+ }
+
+ .card-desc {
+ font-size: 28rpx;
+ color: #7A7A7A;
+ margin-bottom: 50rpx;
+ line-height: 1.6;
+ }
+
+ .input-box {
+ background: #F7F3ED;
+ border-radius: 24rpx;
+ padding: 0 24rpx;
+ margin-bottom: 40rpx;
+ }
+
+ .invite-input {
+ height: 90rpx;
+ font-size: 30rpx;
+ color: #1A1A1A;
+ }
+
+ .btn-primary {
+ width: 100%;
+ height: 96rpx;
+ line-height: 96rpx;
+ background: linear-gradient(135deg, #C9A962, #D4B56A);
+ color: #fff;
+ font-size: 34rpx;
+ font-weight: 600;
+ border-radius: 48rpx;
+ text-align: center;
+ }
+
+ .kefu-link {
+ margin-top: 30rpx;
+ font-size: 28rpx;
+ color: #C9A962;
+ text-decoration: underline;
+ }
+
+ /* 已认证专用 */
+ .done-card {
+ padding: 80rpx 40rpx;
+ }
+
+ .done-icon {
+ font-size: 80rpx;
+ color: #C9A962;
+ margin-bottom: 30rpx;
+ font-weight: bold;
+ }
+
+ .done-tip {
+ font-size: 34rpx;
+ color: #1A1A1A;
+ line-height: 1.8;
+ font-weight: 500;
+ background: rgba(201,169,98,0.08);
+ border-radius: 20rpx;
+ padding: 30rpx 24rpx;
+ margin-top: 40rpx;
+ }
+
+ .highlight {
+ color: #C9A962;
+ font-weight: 700;
+ font-size: 36rpx;
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/shangjiaduan/shangjiaduan.js b/miniprogram/pages/shangjiaduan/shangjiaduan.js
index 47f539f..21e3109 100644
--- a/miniprogram/pages/shangjiaduan/shangjiaduan.js
+++ b/miniprogram/pages/shangjiaduan/shangjiaduan.js
@@ -4,6 +4,7 @@
import request from '../../utils/request.js';
import popupService from '../../services/popupService.js';
import { getDefaultInviteCode } from '../../utils/default-invite-code.js';
+import { getGroupImgUrls, SHANGJIADUAN_DEFAULTS } from '../../utils/page-assets.js';
const app = getApp();
@@ -58,6 +59,8 @@ Page({
},
onLoad(options) {
+ this._onConfigReady = () => this.initImageUrls();
+ app.on('configReady', this._onConfigReady);
this.initImageUrls();
this.checkShangjiaStatus();
@@ -74,6 +77,7 @@ Page({
},
onShow() {
+ this.initImageUrls();
this.registerNotificationComponent();
if (this.data.isShangjia) {
this.refreshShangjiaInfo();
@@ -85,37 +89,15 @@ Page({
popupComp?.cleanup?.();
},
+ onUnload() {
+ if (this._onConfigReady) {
+ getApp().off('configReady', this._onConfigReady);
+ }
+ },
+
// ========== 图片路径初始化 ==========
initImageUrls() {
- const ossBase = (app.globalData.ossImageUrl || '').replace(/\/$/, '');
- const cfg = app.globalData.pageAssets?.shangjiaduan || {};
- const url = (key, fallback) => {
- const p = cfg[key];
- if (p) return `${ossBase}/${String(p).replace(/^\//, '')}`;
- return `${ossBase}/beijing/shangjiaduan/${fallback}`;
- };
- this.setData({
- imgUrls: {
- pageBg: url('pageBg', 'page_bg.png'),
- avatarCardBg: url('avatarCardBg', 'avatar_card_bg.png'),
- assetCardBg: url('assetCardBg', 'asset_card_bg.png'),
- statCardBg: url('statCardBg', 'stat_card_bg.png'),
- funcItemBg: url('funcItemBg', 'func_item_bg.png'),
- primaryFuncBg: url('primaryFuncBg', 'primary_func_bg.png'),
- highlightFuncBg: url('highlightFuncBg', 'highlight_func_bg.png'),
- iconRelease: url('iconRelease', 'icon_release.png'),
- iconFast: url('iconFast', 'icon_fast.png'),
- iconOrders: url('iconOrders', 'icon_orders.png'),
- iconService: url('iconService', 'icon_service.png'),
- iconPunish: url('iconPunish', 'icon_punish.png'),
- iconRank: url('iconRank', 'icon_rank.png'),
- iconRecharge: url('iconRecharge', 'icon_recharge.png'),
- iconWithdraw: url('iconWithdraw', 'icon_withdraw.png'),
- iconRefresh: url('iconRefresh', 'icon_refresh.png'),
- iconCopy: url('iconCopy', 'icon_copy.png'),
- avatarFrame: url('avatarFrame', 'avatar_frame.png'),
- }
- });
+ this.setData({ imgUrls: getGroupImgUrls('shangjiaduan', SHANGJIADUAN_DEFAULTS) });
},
// ========== 商家状态检查 ==========
@@ -320,7 +302,9 @@ Page({
goToMyOrders() { wx.navigateTo({ url: '/pages/sjdingdan/sjdingdan' }); },
goToRecharge() { wx.navigateTo({ url: '/pages/sjchongzhi/sjchongzhi' }); },
goToWithdraw() { wx.navigateTo({ url: '/pages/tixian/tixian' }); },
- goToRanking() { wx.navigateTo({ url: '/pages/sjpaihang/sjpaihang' }); },
+ goToRanking() {
+ wx.navigateTo({ url: '/pages/dashoupaihang/dashoupaihang?type=shangjia' })
+ },
goToMessages() { wx.navigateTo({ url: '/pages/sjxiaoxi/sjxiaoxi' }); },
goToKefu() {
const { kefuConfig } = app.globalData;
diff --git a/miniprogram/pages/shangjiaduan/shangjiaduan.json b/miniprogram/pages/shangjiaduan/shangjiaduan.json
new file mode 100644
index 0000000..c00917d
--- /dev/null
+++ b/miniprogram/pages/shangjiaduan/shangjiaduan.json
@@ -0,0 +1,14 @@
+{
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification",
+ "popup-notice": "/components/popup-notice/popup-notice",
+ "custom-tab-bar": "/custom-tab-bar/index",
+ "chenghao-tag": "/components/chenghao-tag/chenghao-tag"
+ },
+ "navigationBarTitleText": "商家中心",
+ "navigationBarBackgroundColor": "#ffffff",
+ "navigationBarTextStyle": "black",
+ "backgroundColor": "#f8f9fc",
+ "backgroundTextStyle": "light",
+ "enablePullDownRefresh": false
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/shangjiaduan/shangjiaduan.wxml b/miniprogram/pages/shangjiaduan/shangjiaduan.wxml
new file mode 100644
index 0000000..32c2292
--- /dev/null
+++ b/miniprogram/pages/shangjiaduan/shangjiaduan.wxml
@@ -0,0 +1,178 @@
+
+
+
+
+
+
+
+
+
+
+ 商家入驻
+ 输入邀请码,解锁专属商家权益
+
+
+
+
+ 已有邀请码?点击上方注册
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{nicheng || '商家'}}
+
+ UID
+ {{uid || '------'}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 账户余额(元)
+
+ {{sjyue}}
+ ¥
+
+
+
+
+ 充值
+
+
+
+ 提现
+
+
+
+
+
+
+ 发单总量
+ {{fabu}}
+
+
+ 退款总量
+ {{tuikuan}}
+
+
+ 今日流水
+ {{jinriliushui}}
+
+
+ 今月流水
+ {{jinyueliushui}}
+
+
+
+
+
+
+
+
+
+
+ {{jinridingdan}}
+ 今日派单
+
+
+
+
+
+ {{jinrituikuan}}
+ 今日退款
+
+
+
+
+
+
+
+
+
+ 发布订单
+ 快速派单
+
+
+
+
+
+ 极速派单
+ 闪电体验
+
+
+
+
+
+ 我的订单
+ 管理派单
+
+
+
+
+
+ 处罚记录
+ 违规查询
+
+
+
+
+
+ 商家排行
+ 业绩排名
+
+
+
+
+
+ 在线客服
+ 帮助中心
+
+
+
+
+
+
+
+
+
+
+
+ 加载中...
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/shangjiaduan/shangjiaduan.wxss b/miniprogram/pages/shangjiaduan/shangjiaduan.wxss
new file mode 100644
index 0000000..ffcc46a
--- /dev/null
+++ b/miniprogram/pages/shangjiaduan/shangjiaduan.wxss
@@ -0,0 +1,422 @@
+/* pages/shangjiazhongxin/shangjiazhongxin.wxss */
+/* 最终优化:指标卡片缩小,图标缩小,资产卡片微缩,背景模糊 */
+
+page {
+ background-color: #f5f7fb;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
+ }
+
+ /* 页面背景纹理:添加模糊效果,降低对比 */
+ .page-bg {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 0;
+ opacity: 0.15; /* 降低不透明度,更柔和 */
+ filter: blur(8rpx); /* 轻微模糊,不刺眼 */
+ pointer-events: none;
+ }
+
+ .page-container {
+ position: relative;
+ z-index: 1;
+ min-height: 100vh;
+ padding: 0 32rpx;
+ box-sizing: border-box;
+ background-color: transparent;
+ }
+
+ /* ========== 未注册卡片 ========== */
+ .unregistered {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding-top: 200rpx;
+ }
+ .unreg-card {
+ width: 90%;
+ border-radius: 48rpx;
+ overflow: hidden;
+ box-shadow: 0 8rpx 20rpx rgba(0,0,0,0.2);
+ background-color: transparent;
+ }
+ .unreg-content {
+ padding: 60rpx 40rpx;
+ text-align: center;
+ background: rgba(0,0,0,0.65);
+ backdrop-filter: blur(16rpx);
+ border-radius: 48rpx;
+ }
+ .status-title {
+ font-size: 44rpx;
+ font-weight: 700;
+ color: #fff;
+ margin-bottom: 16rpx;
+ }
+ .status-desc {
+ font-size: 28rpx;
+ color: #ddd;
+ margin-bottom: 50rpx;
+ }
+ .input-group {
+ margin-bottom: 40rpx;
+ }
+ .invite-input {
+ width: 100%;
+ height: 96rpx;
+ background: rgba(255,255,255,0.2);
+ border-radius: 48rpx;
+ padding: 0 32rpx;
+ font-size: 30rpx;
+ color: #fff;
+ border: 1rpx solid rgba(255,255,255,0.3);
+ box-sizing: border-box;
+ }
+ .register-btn {
+ width: 100%;
+ height: 96rpx;
+ background: linear-gradient(135deg, #ffb347, #ff8c1a);
+ border-radius: 48rpx;
+ font-size: 34rpx;
+ font-weight: 600;
+ color: white;
+ box-shadow: 0 8rpx 20rpx rgba(255,140,26,0.3);
+ border: none;
+ line-height: 96rpx;
+ }
+ .register-tip {
+ margin-top: 32rpx;
+ font-size: 24rpx;
+ color: #ddd;
+ }
+
+ /* ========== 已注册页面 ========== */
+ .registered {
+ padding-top: 30rpx;
+ padding-bottom: 40rpx;
+ }
+
+ /* 刷新按钮 */
+ .refresh-btn {
+ position: absolute;
+ top: 30rpx;
+ right: 32rpx;
+ width: 72rpx;
+ height: 72rpx;
+ background: rgba(0,0,0,0.5);
+ border-radius: 36rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 20;
+ backdrop-filter: blur(10rpx);
+ }
+ .refresh-icon {
+ width: 40rpx;
+ height: 40rpx;
+ filter: brightness(0) invert(1);
+ }
+
+ /* ========== 头像卡片 ========== */
+ .avatar-card {
+ border-radius: 60rpx;
+ overflow: hidden;
+ margin-bottom: 36rpx;
+ box-shadow: 0 6rpx 18rpx rgba(0,0,0,0.2);
+ background-color: transparent;
+ }
+ .avatar-content {
+ display: flex;
+ align-items: center;
+ padding: 28rpx 32rpx;
+ background: rgba(0,0,0,0.55);
+ backdrop-filter: blur(20rpx);
+ }
+ .avatar-wrapper {
+ position: relative;
+ width: 110rpx;
+ height: 110rpx;
+ margin-right: 28rpx;
+ }
+ .avatar-img {
+ width: 100%;
+ height: 100%;
+ border-radius: 50%;
+ object-fit: cover;
+ border: 2rpx solid rgba(255,255,255,0.3);
+ }
+ .avatar-frame {
+ position: absolute;
+ top: -8rpx;
+ left: -8rpx;
+ width: 126rpx;
+ height: 126rpx;
+ pointer-events: none;
+ }
+ .user-info {
+ flex: 1;
+ }
+ .nicheng {
+ font-size: 40rpx;
+ font-weight: 700;
+ color: #fff;
+ margin-bottom: 16rpx;
+ text-shadow: 0 2rpx 6rpx rgba(0,0,0,0.5);
+ background: rgba(0,0,0,0.6);
+ display: inline-block;
+ padding: 6rpx 20rpx;
+ border-radius: 40rpx;
+ }
+ .uid-area {
+ display: flex;
+ align-items: center;
+ }
+ .uid-label {
+ font-size: 26rpx;
+ color: #fff;
+ background: rgba(0,0,0,0.7);
+ padding: 6rpx 16rpx;
+ border-radius: 28rpx;
+ margin-right: 16rpx;
+ backdrop-filter: blur(4rpx);
+ }
+ .uid-value {
+ font-size: 28rpx;
+ color: #fff;
+ font-family: monospace;
+ background: rgba(0,0,0,0.6);
+ padding: 6rpx 16rpx;
+ border-radius: 28rpx;
+ }
+ .copy-icon {
+ width: 40rpx;
+ height: 40rpx;
+ margin-left: 20rpx;
+ opacity: 0.9;
+ filter: drop-shadow(0 2rpx 2rpx rgba(0,0,0,0.5));
+ }
+
+ /* 荣誉标签 */
+ .badge-area {
+ margin-bottom: 36rpx;
+ }
+ .badge-scroll {
+ white-space: nowrap;
+ }
+ .badge-row {
+ display: inline-flex;
+ gap: 16rpx;
+ padding: 8rpx 0;
+ }
+
+ /* ========== 资产卡片(稍微缩小) ========== */
+ .asset-card {
+ border-radius: 48rpx;
+ overflow: hidden;
+ margin-bottom: 36rpx;
+ box-shadow: 0 10rpx 24rpx rgba(0,0,0,0.2);
+ background-color: transparent;
+ }
+ .glass-effect {
+ padding: 44rpx 36rpx; /* 原 48rpx 36rpx,稍微缩小 */
+ background: rgba(0,0,0,0.55);
+ backdrop-filter: blur(20rpx);
+ }
+ .balance-area {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ flex-wrap: wrap;
+ margin-bottom: 32rpx;
+ }
+ .balance-label {
+ font-size: 28rpx;
+ color: #ffdebc;
+ font-weight: 500;
+ }
+ .balance-value {
+ display: flex;
+ align-items: baseline;
+ }
+ .value-num {
+ font-size: 64rpx; /* 原 68rpx,稍微缩小 */
+ font-weight: 800;
+ color: #ffd966;
+ font-family: 'DIN', monospace;
+ }
+ .value-unit {
+ font-size: 32rpx;
+ color: #ffdebc;
+ margin-left: 6rpx;
+ }
+ .action-buttons {
+ display: flex;
+ gap: 24rpx;
+ }
+ .action-btn {
+ display: flex;
+ align-items: center;
+ gap: 10rpx;
+ background: rgba(255,255,255,0.9);
+ padding: 12rpx 28rpx;
+ border-radius: 60rpx;
+ font-size: 26rpx;
+ font-weight: 500;
+ }
+ .action-btn.recharge {
+ color: #ff8c1a;
+ }
+ .action-btn.withdraw {
+ color: #2d3e6e;
+ }
+ .btn-icon {
+ width: 32rpx;
+ height: 32rpx;
+ }
+ .divider {
+ height: 2rpx;
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,0.5), transparent);
+ margin: 24rpx 0;
+ }
+ .stats-row {
+ display: flex;
+ justify-content: space-between;
+ flex-wrap: wrap;
+ }
+ .stat-item {
+ flex: 1;
+ text-align: center;
+ }
+ .stat-label {
+ font-size: 24rpx;
+ color: #ddd;
+ margin-bottom: 12rpx;
+ display: block;
+ }
+ .stat-number {
+ font-size: 34rpx; /* 原 38rpx,稍微缩小 */
+ font-weight: 700;
+ color: #fff;
+ }
+
+ /* ========== 核心指标卡片组(缩小版) ========== */
+ .metrics-section {
+ display: flex;
+ gap: 24rpx;
+ margin-bottom: 40rpx;
+ }
+ .metric-card {
+ flex: 1;
+ border-radius: 36rpx;
+ overflow: hidden;
+ box-shadow: 0 6rpx 16rpx rgba(0,0,0,0.2);
+ background-color: transparent;
+ }
+ .metric-content {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 24rpx 0; /* 原 36rpx 0,缩小 */
+ background: rgba(0,0,0,0.55);
+ backdrop-filter: blur(12rpx);
+ }
+ .metric-icon {
+ width: 48rpx; /* 原 60rpx,缩小 */
+ height: 48rpx;
+ margin-bottom: 16rpx;
+ filter: brightness(0) invert(1);
+ }
+ .metric-value {
+ font-size: 44rpx; /* 原 52rpx,缩小 */
+ font-weight: 800;
+ color: #ffd966;
+ margin-bottom: 8rpx;
+ }
+ .metric-label {
+ font-size: 24rpx; /* 原 28rpx,缩小 */
+ color: #eee;
+ }
+
+ /* ========== 功能宫格(图标缩小) ========== */
+ .function-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 24rpx;
+ margin-top: 12rpx;
+ }
+ .func-item {
+ border-radius: 32rpx;
+ overflow: hidden;
+ box-shadow: 0 6rpx 14rpx rgba(0,0,0,0.2);
+ background-color: transparent;
+ }
+ .func-content {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 32rpx 12rpx 28rpx; /* 原 40rpx 16rpx 36rpx,稍微缩小 */
+ background: rgba(0,0,0,0.55);
+ backdrop-filter: blur(12rpx);
+ transition: all 0.2s ease;
+ }
+ .func-icon {
+ width: 64rpx; /* 原 72rpx,缩小 */
+ height: 64rpx;
+ margin-bottom: 20rpx;
+ filter: brightness(0) invert(1);
+ }
+ .func-name {
+ font-size: 28rpx; /* 原 32rpx,缩小 */
+ font-weight: 600;
+ color: #fff;
+ margin-bottom: 8rpx;
+ }
+ .func-desc {
+ font-size: 22rpx; /* 原 24rpx,缩小 */
+ color: #ddd;
+ }
+
+ /* 底部安全区 */
+ .safe-bottom {
+ height: 40rpx;
+ }
+
+ /* 加载遮罩 */
+ .loading-mask {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0,0,0,0.7);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 9999;
+ }
+ .loading-content {
+ background: rgba(0,0,0,0.8);
+ padding: 48rpx 64rpx;
+ border-radius: 32rpx;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ }
+ .loading-spinner {
+ width: 72rpx;
+ height: 72rpx;
+ border: 6rpx solid rgba(255,255,255,0.3);
+ border-top-color: #ffd966;
+ border-radius: 50%;
+ animation: spin 0.8s linear infinite;
+ margin-bottom: 24rpx;
+ }
+ .loading-text {
+ font-size: 28rpx;
+ color: #fff;
+ }
+ @keyframes spin {
+ to { transform: rotate(360deg); }
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/shangpinxiangqing/shangpinxiangqing.js b/miniprogram/pages/shangpinxiangqing/shangpinxiangqing.js
new file mode 100644
index 0000000..cf61147
--- /dev/null
+++ b/miniprogram/pages/shangpinxiangqing/shangpinxiangqing.js
@@ -0,0 +1,296 @@
+// pages/shangpinxiangqing/shangpinxiangqing.js
+const app = getApp()
+
+Page({
+ /**
+ * 页面的初始数据
+ */
+ data: {
+ // 全局变量引用
+ ossImageUrl: '',
+ apiBaseUrl: '',
+
+ // 从上级页面传入的参数
+ shangpinId: '', // 商品ID
+ shangpinTitle: '', // 商品标题(可选,用于SEO)
+ shangpinPrice: '', // 商品价格(可选)
+
+ // 商品详情数据
+ shangpinData: null, // 商品详情数据对象
+ isLoading: true, // 加载状态
+ loadError: false, // 加载错误
+
+ // 页面字段映射(后端返回字段名)
+ fieldMapping: {
+ tupian: 'tupian', // 商品图片URL
+ biaoti: 'biaoti', // 商品标题
+ jiage: 'jiage', // 商品价格
+ xiangqing: 'xiangqing', // 商品详细介绍
+ xiadanxuzhi: 'xiadanxuzhi', // 下单须知
+ guize: 'guize', // 规则图片URL
+ xiaoliang: 'xiaoliang', // 商品销量
+ kucun: 'kucun', // 商品库存
+ fenlei: 'fenlei', // 商品分类(可能有用)
+ pingfen: 'pingfen' // 商品评分(可能有用)
+ }
+ },
+
+ /**
+ * 生命周期函数--监听页面加载
+ */
+ onLoad(options) {
+
+
+ // 获取全局变量
+ this.setData({
+ ossImageUrl: app.globalData.ossImageUrl,
+ apiBaseUrl: app.globalData.apiBaseUrl,
+ shangpinId: options.id || '',
+ shangpinTitle: options.title || '',
+ shangpinPrice: options.price || ''
+ })
+
+ // 验证商品ID
+ if (!this.data.shangpinId) {
+ wx.showToast({
+ title: '商品不存在',
+ icon: 'none'
+ })
+ setTimeout(() => {
+ wx.navigateBack()
+ }, 1500)
+ return
+ }
+
+ // 加载商品详情数据
+ this.loadShangpinDetail()
+ },
+ onShow() {
+ // 原有代码...
+
+ // 🆕 注册通知组件
+ this.registerNotificationComponent();
+},
+
+// 🆕 新增:注册通知组件
+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()
+ };
+ }
+},
+
+ /**
+ * 加载商品详情数据
+ */
+ loadShangpinDetail() {
+ const that = this
+ const { shangpinId, apiBaseUrl } = this.data
+
+ this.setData({ isLoading: true, loadError: false })
+
+ wx.request({
+ url: apiBaseUrl + '/shangpin/xiangqinghuoqu/',
+ method: 'POST',
+ header: {
+ 'content-type': 'application/json'
+ },
+ data: {
+ shangpin_id: shangpinId
+ },
+ success(res) {
+
+
+ if (res.statusCode === 200 && res.data && res.data.code === 0) {
+ // 获取商品数据
+ const shangpinData = res.data.data || {}
+
+ // 处理价格显示
+ if (shangpinData.jiage) {
+ const jiage = parseFloat(shangpinData.jiage) || 0
+ const priceInteger = Math.floor(jiage)
+ let priceDecimal = '00'
+
+ const decimalPart = (jiage - priceInteger).toFixed(2)
+ if (decimalPart > 0) {
+ priceDecimal = decimalPart.toString().split('.')[1]
+ }
+
+ shangpinData.priceInteger = priceInteger
+ shangpinData.priceDecimal = priceDecimal
+ }
+
+ // 处理图片URL(确保完整URL)
+ if (shangpinData.tupian && !shangpinData.tupian.startsWith('http')) {
+ shangpinData.tupian = that.data.ossImageUrl + shangpinData.tupian
+ }
+
+ // 处理规则图片URL
+ if (shangpinData.guize && !shangpinData.guize.startsWith('http')) {
+ shangpinData.guize = that.data.ossImageUrl + shangpinData.guize
+ }
+
+ // 处理销量显示
+ if (shangpinData.xiaoliang) {
+ shangpinData.xiaoliangText = that.formatXiaoliang(shangpinData.xiaoliang)
+ }
+
+ // 处理库存显示
+ if (shangpinData.kucun) {
+ shangpinData.kucunText = shangpinData.kucun + '件'
+ shangpinData.kucunClass = shangpinData.kucun > 0 ? 'kucun-normal' : 'kucun-none'
+ }
+
+ // 设置页面标题
+ if (shangpinData.biaoti) {
+ wx.setNavigationBarTitle({
+ title: '商品详情'
+ })
+ }
+
+ that.setData({
+ shangpinData: shangpinData,
+ isLoading: false
+ })
+ } else {
+ that.handleLoadError(res.data?.msg || '加载失败')
+ }
+ },
+ fail(error) {
+ console.error('商品详情加载失败:', error)
+ that.handleLoadError('网络错误,请重试')
+ }
+ })
+ },
+
+ /**
+ * 格式化销量显示
+ */
+ formatXiaoliang(xiaoliang) {
+ const num = parseInt(xiaoliang) || 0
+
+ if (num >= 10000) {
+ return (num / 10000).toFixed(1) + '万+'
+ } else if (num >= 1000) {
+ return (num / 1000).toFixed(1) + '千+'
+ } else {
+ return num + ''
+ }
+ },
+
+ /**
+ * 处理加载错误
+ */
+ handleLoadError(errorMsg) {
+ this.setData({
+ isLoading: false,
+ loadError: true
+ })
+
+ wx.showToast({
+ title: errorMsg || '加载失败',
+ icon: 'none',
+ duration: 2000
+ })
+
+ // 3秒后自动返回
+ setTimeout(() => {
+ wx.navigateBack()
+ }, 3000)
+ },
+
+ /**
+ * 预览商品图片
+ */
+ previewImage() {
+ const { shangpinData } = this.data
+ if (!shangpinData || !shangpinData.tupian) return
+
+ wx.previewImage({
+ current: shangpinData.tupian,
+ urls: [shangpinData.tupian]
+ })
+ },
+
+ /**
+ * 点击立即下单按钮
+ */
+ onOrderClick() {
+ const { shangpinData, shangpinId } = this.data
+
+ // 检查库存
+ if (shangpinData && shangpinData.kucun <= 0) {
+ wx.showToast({
+ title: '商品已售罄',
+ icon: 'none'
+ })
+ return
+ }
+
+ // 验证必要数据
+ if (!shangpinData || !shangpinData.biaoti) {
+ wx.showToast({
+ title: '商品信息不完整',
+ icon: 'none'
+ })
+ return
+ }
+
+ // 准备跳转数据
+ const orderData = {
+ id: shangpinId, // 商品ID
+ title: shangpinData.biaoti, // 商品标题
+ price: shangpinData.jiage, // 商品价格(原价)
+ priceInteger: shangpinData.priceInteger, // 价格整数部分
+ priceDecimal: shangpinData.priceDecimal, // 价格小数部分
+ image: shangpinData.tupian, // 商品图片
+ kucun: shangpinData.kucun || 0, // 商品库存
+ xiaoliang: shangpinData.xiaoliang || 0 // 商品销量
+ }
+
+ // 跳转到提交订单页面
+ wx.navigateTo({
+ url: `/pages/tijiao/tijiao?data=${encodeURIComponent(JSON.stringify(orderData))}`
+ })
+ },
+
+ /**
+ * 刷新页面数据
+ */
+ onRefresh() {
+ this.loadShangpinDetail()
+ },
+
+ /**
+ * 用户点击右上角分享
+ */
+ onShareAppMessage() {
+ const { shangpinData, shangpinId } = this.data
+
+ return {
+ title: shangpinData?.biaoti || '阿龙电竞 - 商品详情',
+ path: `/pages/shangpinxiangqing/shangpinxiangqing?id=${shangpinId}`,
+ imageUrl: shangpinData?.tupian || ''
+ }
+ },
+
+ /**
+ * 生命周期函数--监听页面显示
+ */
+ onShow() {
+ // 页面显示时,可以检查是否需要刷新数据
+ },
+
+ /**
+ * 生命周期函数--监听页面卸载
+ */
+ onUnload() {
+ // 清理工作
+ }
+})
\ No newline at end of file
diff --git a/miniprogram/pages/shangpinxiangqing/shangpinxiangqing.json b/miniprogram/pages/shangpinxiangqing/shangpinxiangqing.json
new file mode 100644
index 0000000..837f016
--- /dev/null
+++ b/miniprogram/pages/shangpinxiangqing/shangpinxiangqing.json
@@ -0,0 +1,5 @@
+{
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/pages/shangpinxiangqing/shangpinxiangqing.wxml b/miniprogram/pages/shangpinxiangqing/shangpinxiangqing.wxml
new file mode 100644
index 0000000..595ed1f
--- /dev/null
+++ b/miniprogram/pages/shangpinxiangqing/shangpinxiangqing.wxml
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+
+
+
+ 加载中...
+
+
+
+
+
+
+ 加载失败
+
+
+
+
+
+
+
+
+
+
+
+ {{shangpinData.kucunText || '库存充足'}}
+
+
+
+
+
+
+
+
+
+ {{shangpinData.biaoti || ''}}
+
+
+
+
+
+ ¥
+ {{shangpinData.priceInteger || '0'}}
+ .{{shangpinData.priceDecimal || '00'}}
+
+
+
+
+
+ {{shangpinData.xiaoliangText || '0'}}次购买
+
+
+
+
+
+
+
+
+ {{shangpinData.xiangqing}}
+
+
+
+
+
+
+
+ {{shangpinData.xiadanxuzhi}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 立即预约
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/shangpinxiangqing/shangpinxiangqing.wxss b/miniprogram/pages/shangpinxiangqing/shangpinxiangqing.wxss
new file mode 100644
index 0000000..c94d2b8
--- /dev/null
+++ b/miniprogram/pages/shangpinxiangqing/shangpinxiangqing.wxss
@@ -0,0 +1,386 @@
+/* pages/shangpinxiangqing/shangpinxiangqing.wxss */
+.detail-page {
+ width: 100%;
+ min-height: 100vh;
+ background: #f5f5f5;
+ position: relative;
+ }
+
+ /* 加载状态 */
+ .loading-container {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: #f5f5f5;
+ z-index: 100;
+ }
+
+ .loading-content {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ }
+
+ .loading-spinner {
+ width: 80rpx;
+ height: 80rpx;
+ position: relative;
+ margin-bottom: 30rpx;
+ }
+
+ .spinner-ring {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ border: 4rpx solid rgba(82, 196, 26, 0.2);
+ border-radius: 50%;
+ }
+
+ .spinner-dot {
+ position: absolute;
+ top: 0;
+ left: 50%;
+ width: 12rpx;
+ height: 12rpx;
+ background: #52c41a;
+ border-radius: 50%;
+ transform: translateX(-50%);
+ animation: spin 1s linear infinite;
+ }
+
+ .loading-text {
+ font-size: 28rpx;
+ color: #666;
+ font-weight: 500;
+ }
+
+ /* 错误状态 */
+ .error-container {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ background: #f5f5f5;
+ z-index: 100;
+ }
+
+ .error-icon {
+ width: 180rpx;
+ height: 180rpx;
+ opacity: 0.3;
+ margin-bottom: 30rpx;
+ }
+
+ .error-text {
+ font-size: 30rpx;
+ color: #999;
+ margin-bottom: 40rpx;
+ }
+
+ .error-btn {
+ background: linear-gradient(135deg, #52c41a, #389e0d);
+ color: white;
+ font-size: 26rpx;
+ font-weight: 500;
+ padding: 20rpx 50rpx;
+ border-radius: 50rpx;
+ box-shadow: 0 8rpx 24rpx rgba(82, 196, 26, 0.3);
+ }
+
+ /* 商品详情内容 */
+ .detail-content {
+ padding-bottom: 140rpx; /* 给底部按钮留出空间 */
+ }
+
+ /* 商品图片区域(占满屏幕) */
+ .image-section {
+ width: 100%;
+ background: white;
+ }
+
+ .image-container {
+ width: 100%;
+ height: 750rpx; /* 正方形,占满屏幕宽度 */
+ position: relative;
+ overflow: hidden;
+ background: #f5f5f5;
+ }
+
+ .product-image {
+ width: 100%;
+ height: 100%;
+ }
+
+ /* 库存标签 */
+ .kucun-tag {
+ position: absolute;
+ bottom: 30rpx;
+ right: 30rpx;
+ padding: 12rpx 24rpx;
+ border-radius: 30rpx;
+ backdrop-filter: blur(10rpx);
+ z-index: 10;
+ }
+
+ .kucun-normal {
+ background: rgba(0, 0, 0, 0.6);
+ color: white;
+ }
+
+ .kucun-none {
+ background: rgba(255, 77, 79, 0.8);
+ color: white;
+ }
+
+ .kucun-text {
+ font-size: 24rpx;
+ font-weight: 500;
+ }
+
+ /* 下方内容区域(顶部有圆角,与图片重叠一部分) */
+ .content-area {
+ position: relative;
+ background: white;
+ border-radius: 40rpx 40rpx 0 0;
+ margin-top: -40rpx; /* 与图片重叠一部分,实现平滑过渡 */
+ padding: 40rpx 30rpx 0;
+ min-height: 400rpx;
+ }
+
+ /* 标题和价格区域 */
+ .title-price-section {
+ margin-bottom: 40rpx;
+ }
+
+ .title-row {
+ margin-bottom: 30rpx;
+ }
+
+ .product-title {
+ font-size: 38rpx;
+ font-weight: 700;
+ color: #222;
+ line-height: 1.5;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ overflow: hidden;
+ }
+
+ /* 价格和销量行 */
+ .price-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ }
+
+ /* 价格部分 */
+ .price-section {
+ display: flex;
+ align-items: baseline;
+ }
+
+ .price-icon {
+ font-size: 34rpx;
+ color: #ff4d4f;
+ font-weight: 600;
+ }
+
+ .price-integer {
+ font-size: 58rpx;
+ color: #ff4d4f;
+ font-weight: 700;
+ margin-left: 4rpx;
+ }
+
+ .price-decimal {
+ font-size: 34rpx;
+ color: #ff4d4f;
+ font-weight: 600;
+ }
+
+ /* 销量部分 */
+ .sales-section {
+ display: flex;
+ align-items: center;
+ }
+
+ .sales-icon {
+ width: 30rpx;
+ height: 30rpx;
+ margin-right: 12rpx;
+ opacity: 0.6;
+ }
+
+ .sales-text {
+ font-size: 28rpx;
+ color: #999;
+ font-weight: 400;
+ }
+
+ /* 信息区域(商品介绍、购买须知) */
+ .info-section {
+ margin-bottom: 40rpx;
+ padding-top: 30rpx;
+ border-top: 1rpx solid #f0f0f0;
+ }
+
+ .section-header {
+ display: flex;
+ align-items: center;
+ margin-bottom: 20rpx;
+ }
+
+ .section-icon {
+ width: 36rpx;
+ height: 36rpx;
+ margin-right: 16rpx;
+ }
+
+ .section-title {
+ font-size: 32rpx;
+ font-weight: 600;
+ color: #52c41a; /* 绿色 */
+ }
+
+ .section-content {
+ line-height: 1.8;
+ padding-left: 52rpx; /* 与图标对齐 */
+ }
+
+ .content-text {
+ font-size: 28rpx;
+ color: #333;
+ font-weight: 400;
+ white-space: pre-line;
+ line-height: 1.8;
+ }
+
+ /* 规则图片区域 */
+ .rule-section {
+ margin: 40rpx 0;
+ padding-top: 30rpx;
+ border-top: 1rpx solid #f0f0f0;
+ }
+
+ .rule-image {
+ width: 100%;
+ border-radius: 16rpx;
+ display: block;
+ box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
+ }
+
+ /* 占位区域 */
+ .placeholder {
+ height: 140rpx;
+ }
+
+ /* 立即下单按钮容器(固定底部) */
+ .order-btn-container {
+ position: fixed;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ padding: 20rpx 30rpx;
+ background: linear-gradient(transparent, rgba(255, 255, 255, 0.9) 70%);
+ z-index: 100;
+ }
+
+ /* 立即下单按钮 */
+ .order-btn {
+ background: linear-gradient(135deg, #52c41a, #389e0d);
+ border-radius: 50rpx;
+ height: 100rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ overflow: hidden;
+ box-shadow:
+ 0 12rpx 36rpx rgba(82, 196, 26, 0.4),
+ 0 4rpx 12rpx rgba(82, 196, 26, 0.3);
+ transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
+ }
+
+ .order-btn:active {
+ transform: scale(0.98);
+ box-shadow:
+ 0 6rpx 24rpx rgba(82, 196, 26, 0.5),
+ 0 2rpx 8rpx rgba(82, 196, 26, 0.4);
+ }
+
+ /* 按钮发光特效 */
+ .btn-glow {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(255, 255, 255, 0.1);
+ animation: glowPulse 2s infinite;
+ }
+
+ /* 按钮流光特效 */
+ .btn-streamer {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100rpx;
+ height: 100%;
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
+ transform: translateX(-100%);
+ animation: streamerFlow 3s infinite;
+ }
+
+ /* 按钮文字 */
+ .btn-text {
+ font-size: 36rpx;
+ color: white;
+ font-weight: 600;
+ letter-spacing: 2rpx;
+ position: relative;
+ z-index: 2;
+ }
+
+ /* 动画定义 */
+ @keyframes spin {
+ 0% {
+ transform: translateX(-50%) rotate(0deg);
+ }
+ 100% {
+ transform: translateX(-50%) rotate(360deg);
+ }
+ }
+
+ @keyframes glowPulse {
+ 0%, 100% {
+ opacity: 0.5;
+ }
+ 50% {
+ opacity: 0.8;
+ }
+ }
+
+ @keyframes streamerFlow {
+ 0% {
+ transform: translateX(-100%);
+ }
+ 50% {
+ transform: translateX(100%);
+ }
+ 100% {
+ transform: translateX(100%);
+ }
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/sjchongzhi/sjchongzhi.js b/miniprogram/pages/sjchongzhi/sjchongzhi.js
new file mode 100644
index 0000000..889cfee
--- /dev/null
+++ b/miniprogram/pages/sjchongzhi/sjchongzhi.js
@@ -0,0 +1,473 @@
+// pages/shangjia-chongzhi/index.js
+import request from '../../utils/request';
+
+Page({
+ data: {
+ // 商家余额
+ sjyue: '0.00',
+
+ // 充值金额相关
+ quickAmounts: [50, 100, 300, 500, 1000, 2000],
+ selectedAmount: 50, // 默认选择第一个
+ customAmount: '',
+ inputFocus: false,
+
+ // 金额限制
+ minAmount: 1,
+ maxAmount: 10000,
+
+ // 支付状态
+ canPay: false,
+ isLoading: false,
+ loadingText: '',
+
+ // 支付状态提示
+ showStatus: false,
+ statusText: '',
+ statusSubtext: '',
+ showAction: false,
+ actionText: '',
+
+ // 订单ID
+ currentDingdanid: '',
+
+ // 轮询相关
+ pollingCount: 0,
+ maxPollingCount: 15
+ },
+
+ onLoad() {
+ this.initPage();
+ },
+
+ onShow() {
+ this.loadShangjiaBalance();
+ this.registerNotificationComponent();
+ },
+ 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()
+ };
+ }
+},
+
+ // 初始化页面
+ initPage() {
+ this.loadShangjiaBalance();
+ this.checkPayCondition();
+ },
+
+ // 从全局变量加载商家余额
+ loadShangjiaBalance() {
+ const app = getApp();
+ const globalData = app.globalData || {};
+ const shangjiaData = globalData.shangjia || {};
+
+ this.setData({
+ sjyue: shangjiaData.sjyue || '0.00'
+ });
+ },
+
+ // 选择快捷金额
+ selectAmount(e) {
+ const amount = parseInt(e.currentTarget.dataset.amount);
+
+ this.setData({
+ selectedAmount: amount,
+ customAmount: '',
+ inputFocus: false
+ }, () => {
+ this.checkPayCondition();
+ });
+ },
+
+ // 自定义金额输入
+ onCustomInput(e) {
+ const value = e.detail.value;
+
+ // 只允许输入数字和小数点
+ let cleanedValue = value.replace(/[^\d.]/g, '');
+
+ // 只允许一个小数点
+ const dotCount = cleanedValue.split('.').length - 1;
+ if (dotCount > 1) {
+ cleanedValue = cleanedValue.substring(0, cleanedValue.lastIndexOf('.'));
+ }
+
+ // 限制小数点后两位
+ if (cleanedValue.includes('.')) {
+ const parts = cleanedValue.split('.');
+ if (parts[1].length > 2) {
+ cleanedValue = parts[0] + '.' + parts[1].substring(0, 2);
+ }
+ }
+
+ this.setData({
+ customAmount: cleanedValue,
+ selectedAmount: null
+ }, () => {
+ this.checkPayCondition();
+ });
+ },
+
+ // 输入框聚焦
+ onInputFocus() {
+ this.setData({
+ inputFocus: true,
+ selectedAmount: null
+ });
+ },
+
+ // 输入框失焦
+ onInputBlur() {
+ this.setData({
+ inputFocus: false
+ }, () => {
+ this.checkPayCondition();
+ });
+ },
+
+ // 检查金额是否有效
+ checkAmountValid() {
+ const amount = this.getSelectedAmount();
+
+ if (!amount || amount === '') {
+ return false;
+ }
+
+ const numAmount = parseFloat(amount);
+ const min = this.data.minAmount;
+ const max = this.data.maxAmount;
+
+ if (numAmount < min || numAmount > max || isNaN(numAmount)) {
+ return false;
+ }
+
+ return true;
+ },
+
+ // 获取选中的金额
+ getSelectedAmount() {
+ if (this.data.selectedAmount !== null && this.data.selectedAmount !== undefined) {
+ return this.data.selectedAmount.toString();
+ } else if (this.data.customAmount) {
+ return this.data.customAmount;
+ }
+ return null;
+ },
+
+ // 检查支付条件
+ checkPayCondition() {
+ const validAmount = this.checkAmountValid();
+
+ this.setData({
+ canPay: validAmount
+ });
+ },
+
+ // ========== 支付逻辑 ==========
+
+ // 处理支付
+ handlePay() {
+ if (!this.data.canPay) {
+ wx.showToast({
+ title: '请选择有效金额',
+ icon: 'none'
+ });
+ return;
+ }
+
+ const amount = this.getSelectedAmount();
+ const numAmount = parseFloat(amount);
+
+ // 确认支付
+ wx.showModal({
+ title: '确认充值',
+ content: `是否充值 ¥${numAmount.toFixed(2)}?`,
+ success: (res) => {
+ if (res.confirm) {
+ this.startPayment(numAmount);
+ }
+ }
+ });
+ },
+
+ // 开始支付流程
+ async startPayment(amount) {
+ this.showLoading('发起支付中...');
+
+ try {
+ const res = await request({
+ url: '/shangpin/sjcz',
+ method: 'POST',
+ data: {
+ jine: amount // 关键字段:后端接口要求"jine"
+ }
+ });
+
+ if (res.data.code === 200) {
+ const payParams = res.data.payParams;
+ const dingdanid = res.data.dingdanid;
+
+ this.setData({
+ currentDingdanid: dingdanid,
+ pollingCount: 0
+ });
+
+ // 调起微信支付
+ await this.wechatPay(payParams);
+ } else {
+ this.hideLoading();
+ wx.showToast({
+ title: res.data.message || '支付发起失败',
+ icon: 'none'
+ });
+ }
+ } catch (error) {
+ console.error('发起支付失败:', error);
+ this.hideLoading();
+ wx.showToast({
+ title: '网络错误,请重试',
+ icon: 'none'
+ });
+ }
+ },
+
+ // 微信支付
+ async wechatPay(payParams) {
+ this.showLoading('调起支付中...');
+
+ return new Promise((resolve, reject) => {
+ if (!payParams || !payParams.timeStamp || !payParams.paySign) {
+ this.hideLoading();
+ 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: async () => {
+ this.hideLoading();
+ this.showStatusMessage('支付成功', '正在确认到账...', false);
+
+ try {
+ await this.startPolling();
+ resolve();
+ } catch (error) {
+ reject(error);
+ }
+ },
+ fail: async (res) => {
+ this.hideLoading();
+
+ let errorMsg = '支付失败';
+ if (res.errMsg.includes('cancel')) {
+ errorMsg = '您取消了支付';
+ }
+
+ this.showStatusMessage(errorMsg, '请重新尝试', true, '重新支付');
+
+ // 通知后端支付失败
+ try {
+ await this.notifyPaymentFailure();
+ } catch (error) {
+ console.error('支付失败通知失败:', error);
+ }
+
+ reject(new Error(res.errMsg));
+ }
+ });
+ });
+ },
+
+ // 开始轮询支付结果
+ async startPolling() {
+ this.setData({ pollingCount: 0 });
+
+ const poll = async () => {
+ if (this.data.pollingCount >= this.data.maxPollingCount) {
+ this.showStatusMessage('确认超时', '请联系客服确认到账情况', true, '联系客服');
+ return;
+ }
+
+ try {
+ // 🟢 修改开始:修改checkPaymentStatus的调用和返回值处理
+ const result = await this.checkPaymentStatus();
+
+ if (result.success) {
+ // 🟢 修改:更新全局变量中的商家余额
+ const app = getApp();
+ if (app.globalData && app.globalData.shangjia) {
+ // 🟢 注意:后端返回yue,前端全局变量使用sjyue,保留两位小数
+ app.globalData.shangjia.sjyue = result.yue.toFixed(2);
+ console.log('🟢 全局变量商家余额已更新:', app.globalData.shangjia.sjyue);
+ }
+
+ this.showStatusMessage('充值成功', '余额已到账', true, '完成');
+ this.refreshBalance();
+ return;
+ }
+ // 🟢 修改结束
+
+ this.setData({
+ pollingCount: this.data.pollingCount + 1
+ });
+
+ const delay = this.calculatePollingDelay();
+ setTimeout(poll, delay);
+ } catch (error) {
+ console.error('轮询失败:', error);
+ this.setData({
+ pollingCount: this.data.pollingCount + 1
+ });
+ setTimeout(poll, 2000);
+ }
+ };
+
+ poll();
+ },
+
+ // 计算轮询延迟
+ calculatePollingDelay() {
+ const count = this.data.pollingCount;
+ if (count < 3) return 1000;
+ if (count < 6) return 2000;
+ if (count < 9) return 3000;
+ return 5000;
+ },
+
+ // 检查支付状态
+ async checkPaymentStatus() {
+ const dingdanid = this.data.currentDingdanid;
+
+ if (!dingdanid) {
+ throw new Error('订单ID缺失');
+ }
+
+ try {
+ const res = await request({
+ url: '/shangpin/sjcg',
+ method: 'POST',
+ data: {
+ dingdanid: dingdanid // 后端接口要求"dingdanid"
+ }
+ });
+
+ // 🟢 修改开始:返回对象,包含成功状态和余额
+ if (res.data.code === 200) {
+ return {
+ success: true,
+ yue: res.data.yue // 🟢 注意:后端返回字段是yue
+ };
+ } else {
+ return { success: false };
+ }
+ // 🟢 修改结束
+ } catch (error) {
+ console.error('检查支付状态失败:', error);
+ return { success: false };
+ }
+ },
+
+ // 通知支付失败
+ async notifyPaymentFailure() {
+ const dingdanid = this.data.currentDingdanid;
+
+ if (!dingdanid) return;
+
+ try {
+ await request({
+ url: '/shangpin/sjshibai',
+ method: 'POST',
+ data: {
+ dingdanid: dingdanid // 后端接口要求"dingdanid"
+ }
+ });
+ } catch (error) {
+ console.error('支付失败通知失败:', error);
+ }
+ },
+
+ // 刷新余额
+ async refreshBalance() {
+ try {
+ this.loadShangjiaBalance();
+ } catch (error) {
+ console.error('刷新余额失败:', error);
+ }
+ },
+
+ // ========== 状态提示 ==========
+
+ // 显示状态消息
+ showStatusMessage(title, desc, showAction = false, actionBtnText = '') {
+ this.setData({
+ showStatus: true,
+ statusText: title,
+ statusSubtext: desc,
+ showAction: showAction,
+ actionText: actionBtnText
+ });
+ },
+
+ // 处理状态按钮点击
+ handleStatusAction() {
+ const actionText = this.data.actionText;
+
+ if (actionText === '重新支付' || actionText === '重试') {
+ this.setData({ showStatus: false });
+ setTimeout(() => {
+ this.handlePay();
+ }, 300);
+ } else if (actionText === '联系客服') {
+ wx.showModal({
+ title: '联系客服',
+ content: '联系客服',
+ showCancel: false,
+ confirmText: '我知道了'
+ });
+ } else if (actionText === '完成') {
+ this.setData({ showStatus: false });
+ this.refreshBalance();
+ }
+ },
+
+ // ========== 工具方法 ==========
+
+ // 显示加载
+ showLoading(text = '加载中...') {
+ this.setData({
+ isLoading: true,
+ loadingText: text
+ });
+ },
+
+ // 隐藏加载
+ hideLoading() {
+ this.setData({
+ isLoading: false
+ });
+ },
+
+ // 页面卸载时清理
+ onUnload() {
+ this.setData({
+ showStatus: false,
+ isLoading: false
+ });
+ }
+});
\ No newline at end of file
diff --git a/miniprogram/pages/sjchongzhi/sjchongzhi.json b/miniprogram/pages/sjchongzhi/sjchongzhi.json
new file mode 100644
index 0000000..85fce31
--- /dev/null
+++ b/miniprogram/pages/sjchongzhi/sjchongzhi.json
@@ -0,0 +1,13 @@
+{
+ "navigationBarTitleText": "商家充值",
+ "navigationBarBackgroundColor": "#0a0e17",
+ "navigationBarTextStyle": "white",
+ "backgroundColor": "#0a0e17",
+ "backgroundTextStyle": "light",
+ "enablePullDownRefresh": false,
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification"
+ },
+ "navigationStyle": "default",
+ "disableScroll": true
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/sjchongzhi/sjchongzhi.wxml b/miniprogram/pages/sjchongzhi/sjchongzhi.wxml
new file mode 100644
index 0000000..e0916af
--- /dev/null
+++ b/miniprogram/pages/sjchongzhi/sjchongzhi.wxml
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+ ¥
+ {{sjyue}}
+ 元
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ¥{{item}}
+
+
+
+
+
+
+
+
+ ¥
+
+
+
+
+ 单次充值范围:¥{{minAmount}} - ¥{{maxAmount}}
+
+
+
+
+
+
+
+
+
+ 立即充值
+
+
+
+
+
+
+
+
+
+
+ {{statusText}}
+ {{statusSubtext}}
+
+
+
+ {{actionText}}
+
+
+
+
+
+
+
+
+
+ {{loadingText}}
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/sjchongzhi/sjchongzhi.wxss b/miniprogram/pages/sjchongzhi/sjchongzhi.wxss
new file mode 100644
index 0000000..f788fce
--- /dev/null
+++ b/miniprogram/pages/sjchongzhi/sjchongzhi.wxss
@@ -0,0 +1,1024 @@
+/* ========================
+ 阿龙电竞 - 商家充值页面
+ 赛博科技风主题 - 完全修复版
+ v4.0 - 重点修复文字可见性和整体协调性
+======================== */
+
+/* 科技主题色 - 增强对比度 */
+:root {
+ --primary-color: #00f3ff; /* 主科技蓝 */
+ --primary-light: #4dfbff; /* 亮科技蓝 */
+ --primary-dark: #00a0b0; /* 暗科技蓝 */
+ --secondary-color: #ff2a6d; /* 科技粉红 */
+ --accent-color: #ffd700; /* 科技金 */
+ --accent-purple: #9d4edd; /* 科技紫 */
+ --dark-bg: #0a0e17; /* 深色背景 */
+ --medium-bg: #13182b; /* 中色背景 */
+ --light-bg: #1c2238; /* 浅色背景 */
+ --card-bg: rgba(20, 25, 40, 0.85); /* 卡片背景 - 提高透明度 */
+ --border-glow: rgba(0, 243, 255, 0.4);
+ --text-primary: #ffffff; /* 主要文字 */
+ --text-secondary: #a0aec0; /* 次要文字 */
+ --text-highlight: #ffffff; /* 高亮文字 */
+ --text-glow: #00f3ff; /* 发光文字 */
+ --shadow-tech: 0 8rpx 24rpx rgba(0, 0, 0, 0.5);
+ --shadow-glow: 0 0 20rpx rgba(0, 243, 255, 0.25);
+ }
+
+ /* 全局页面样式 - 修复背景 */
+ .shangjia-chongzhi-page {
+ padding: 20rpx 20rpx 100rpx;
+ color: var(--text-primary);
+ min-height: 100vh;
+ background: linear-gradient(145deg,
+ var(--dark-bg) 0%,
+ var(--medium-bg) 50%,
+ var(--light-bg) 100%
+ ) !important;
+ position: relative;
+ overflow-x: hidden;
+ }
+
+ /* 移除过强的毛玻璃效果,只保留轻微模糊 */
+ .shangjia-chongzhi-page::before {
+ display: none; /* 移除网格背景 */
+ }
+
+ /* ========== 余额卡片样式 - 完全重写 ========== */
+ .balance-card {
+ background: rgba(20, 25, 40, 0.9); /* 降低透明度,提高文字可见性 */
+ border-radius: 25rpx;
+ padding: 35rpx 25rpx;
+ margin: 0 0 35rpx;
+ position: relative;
+ overflow: hidden;
+ border: 1.5rpx solid rgba(0, 243, 255, 0.3);
+ box-shadow:
+ var(--shadow-tech),
+ var(--shadow-glow),
+ inset 0 0 15rpx rgba(0, 243, 255, 0.08);
+ z-index: 1;
+ /* 移除或大幅降低毛玻璃效果 */
+ backdrop-filter: blur(2px);
+ }
+
+ /* 余额头部 */
+ .balance-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 35rpx;
+ }
+
+ /* 余额标题 - 确保文字在最上层,完全可见 */
+ .balance-title {
+ position: relative;
+ z-index: 10; /* 确保在最上层 */
+ }
+
+ .title-text {
+ font-size: 38rpx;
+ font-weight: 800;
+ letter-spacing: 1.5rpx;
+ color: var(--text-primary) !important; /* 强制使用白色,确保可见 */
+ text-shadow:
+ 0 0 15rpx rgba(0, 243, 255, 0.8),
+ 0 0 30rpx rgba(0, 243, 255, 0.4),
+ 0 0 45rpx rgba(0, 243, 255, 0.2);
+ position: relative;
+ display: inline-block;
+ /* 移除背景渐变,直接用纯色加阴影 */
+ background: none;
+ -webkit-background-clip: initial;
+ background-clip: initial;
+ filter: brightness(1.2);
+ }
+
+ /* 标题下划线装饰 */
+ .title-decoration {
+ display: flex;
+ align-items: center;
+ margin-top: 12rpx;
+ }
+
+ .decoration-line {
+ width: 50rpx;
+ height: 3rpx;
+ background: linear-gradient(90deg,
+ transparent,
+ var(--primary-color),
+ transparent
+ );
+ border-radius: 2rpx;
+ animation: lineFlow 2s ease-in-out infinite;
+ }
+
+ .decoration-dot {
+ width: 8rpx;
+ height: 8rpx;
+ background: var(--secondary-color);
+ border-radius: 50%;
+ margin: 0 12rpx;
+ animation: pulse 1.5s infinite;
+ box-shadow: 0 0 8rpx var(--secondary-color);
+ }
+
+ @keyframes pulse {
+ 0%, 100% {
+ transform: scale(1);
+ opacity: 1;
+ }
+ 50% {
+ transform: scale(1.3);
+ opacity: 0.8;
+ }
+ }
+
+ @keyframes lineFlow {
+ 0% { background-position: -100% 0; }
+ 100% { background-position: 200% 0; }
+ }
+
+ /* VIP商家标签 */
+ .balance-chip {
+ background: linear-gradient(90deg,
+ rgba(0, 243, 255, 0.15),
+ rgba(255, 42, 109, 0.15)
+ );
+ border: 1rpx solid rgba(0, 243, 255, 0.3);
+ border-radius: 20rpx;
+ padding: 10rpx 20rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 44rpx;
+ min-width: 180rpx;
+ box-shadow:
+ 0 4rpx 12rpx rgba(0, 243, 255, 0.2),
+ inset 0 0 8rpx rgba(0, 243, 255, 0.1);
+ position: relative;
+ z-index: 10; /* 确保在最上层 */
+ }
+
+ .chip-text {
+ font-size: 24rpx;
+ font-weight: 600;
+ color: var(--text-primary) !important;
+ text-align: center;
+ letter-spacing: 1rpx;
+ position: relative;
+ z-index: 2;
+ }
+
+ /* 余额金额展示 - 完全重写,确保数字可见 */
+ .balance-amount {
+ text-align: center;
+ margin: 35rpx 0;
+ position: relative;
+ z-index: 10; /* 确保在最上层 */
+ }
+
+ .currency {
+ font-size: 44rpx;
+ font-weight: 800;
+ color: var(--accent-color) !important; /* 使用金色,确保可见 */
+ margin-right: 8rpx;
+ text-shadow:
+ 0 0 20rpx rgba(255, 215, 0, 0.7),
+ 0 0 40rpx rgba(255, 215, 0, 0.4);
+ display: inline-block;
+ vertical-align: middle;
+ line-height: 1;
+ /* 移除渐变背景,直接用纯色 */
+ background: none;
+ -webkit-background-clip: initial;
+ background-clip: initial;
+ }
+
+ .amount {
+ font-size: 76rpx;
+ font-weight: 900;
+ color: var(--text-primary) !important; /* 强制使用白色 */
+ line-height: 1;
+ text-shadow:
+ 0 0 20rpx rgba(0, 243, 255, 0.8),
+ 0 0 40rpx rgba(0, 243, 255, 0.5),
+ 0 0 60rpx rgba(0, 243, 255, 0.3);
+ letter-spacing: -1.5rpx;
+ display: inline-block;
+ position: relative;
+ vertical-align: middle;
+ margin: 0 5rpx;
+ /* 移除渐变背景,直接用纯色加阴影 */
+ background: none;
+ -webkit-background-clip: initial;
+ background-clip: initial;
+ }
+
+ .amount::after {
+ display: none; /* 移除模糊效果 */
+ }
+
+ .unit {
+ font-size: 30rpx;
+ color: var(--text-secondary);
+ margin-left: 8rpx;
+ font-weight: 500;
+ vertical-align: middle;
+ line-height: 1;
+ }
+
+ /* 余额底部标签 */
+ .balance-footer {
+ display: flex;
+ justify-content: center;
+ gap: 30rpx;
+ margin-top: 30rpx;
+ position: relative;
+ z-index: 10;
+ }
+
+ .balance-tag {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: rgba(0, 243, 255, 0.08);
+ padding: 10rpx 22rpx;
+ border-radius: 18rpx;
+ border: 1rpx solid rgba(0, 243, 255, 0.2);
+ min-width: 150rpx;
+ }
+
+ .tag-text {
+ font-size: 24rpx;
+ font-weight: 600;
+ color: var(--primary-light) !important;
+ text-align: center;
+ letter-spacing: 1rpx;
+ }
+
+ /* 机甲装饰元素 - 调低透明度,不干扰文字 */
+ .mech-decoration {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ pointer-events: none;
+ z-index: 1; /* 在背景层,不在文字上层 */
+ opacity: 0.3;
+ }
+
+ .mech-line {
+ position: absolute;
+ background: linear-gradient(90deg,
+ transparent 0%,
+ var(--primary-color) 50%,
+ transparent 100%
+ );
+ opacity: 0.15;
+ }
+
+ .line-1 {
+ top: 15rpx;
+ left: 15rpx;
+ right: 15rpx;
+ height: 1.5rpx;
+ animation: lineFlow 3s ease-in-out infinite;
+ }
+
+ .line-2 {
+ bottom: 15rpx;
+ left: 15rpx;
+ right: 15rpx;
+ height: 1.5rpx;
+ animation: lineFlow 3s ease-in-out infinite reverse;
+ }
+
+ .mech-corner {
+ position: absolute;
+ width: 18rpx;
+ height: 18rpx;
+ border: 1.5rpx solid var(--primary-color);
+ opacity: 0.2;
+ }
+
+ .corner-1 {
+ top: 15rpx;
+ left: 15rpx;
+ border-right: none;
+ border-bottom: none;
+ border-top-left-radius: 6rpx;
+ }
+
+ .corner-2 {
+ top: 15rpx;
+ right: 15rpx;
+ border-left: none;
+ border-bottom: none;
+ border-top-right-radius: 6rpx;
+ }
+
+ .corner-3 {
+ bottom: 15rpx;
+ left: 15rpx;
+ border-right: none;
+ border-top: none;
+ border-bottom-left-radius: 6rpx;
+ }
+
+ .corner-4 {
+ bottom: 15rpx;
+ right: 15rpx;
+ border-left: none;
+ border-top: none;
+ border-bottom-right-radius: 6rpx;
+ }
+
+ /* ========== 充值金额区域 ========== */
+ .amount-section {
+ background: rgba(25, 30, 48, 0.9);
+ border-radius: 25rpx;
+ padding: 35rpx 25rpx;
+ margin-bottom: 35rpx;
+ border: 1rpx solid rgba(255, 255, 255, 0.08);
+ backdrop-filter: blur(4px);
+ box-shadow:
+ var(--shadow-tech),
+ inset 0 0 15rpx rgba(0, 243, 255, 0.05);
+ position: relative;
+ z-index: 1;
+ }
+
+ .section-header {
+ margin-bottom: 35rpx;
+ }
+
+ .section-title {
+ font-size: 34rpx;
+ font-weight: 700;
+ color: var(--text-primary) !important;
+ display: block;
+ margin-bottom: 12rpx;
+ letter-spacing: 1rpx;
+ text-shadow: 0 0 10rpx rgba(255, 255, 255, 0.2);
+ position: relative;
+ z-index: 10;
+ }
+
+ .section-subtitle {
+ display: flex;
+ align-items: center;
+ }
+
+ .subtitle-text {
+ font-size: 26rpx;
+ color: var(--text-secondary);
+ position: relative;
+ z-index: 10;
+ }
+
+ /* 快捷金额网格 */
+ .quick-amount-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 18rpx;
+ margin-bottom: 35rpx;
+ }
+
+ .amount-item {
+ background: rgba(255, 255, 255, 0.05);
+ border: 2rpx solid rgba(255, 255, 255, 0.12);
+ border-radius: 18rpx;
+ padding: 26rpx 10rpx;
+ text-align: center;
+ position: relative;
+ transition: all 0.2s ease;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-height: 85rpx;
+ overflow: hidden;
+ }
+
+ .amount-item:active {
+ transform: scale(0.96);
+ }
+
+ .amount-item.active {
+ background: linear-gradient(135deg,
+ rgba(0, 243, 255, 0.15),
+ rgba(157, 78, 221, 0.15)
+ );
+ border-color: var(--primary-color);
+ transform: translateY(-3rpx);
+ box-shadow:
+ 0 10rpx 25rpx rgba(0, 243, 255, 0.2),
+ 0 0 30rpx rgba(0, 243, 255, 0.1),
+ inset 0 0 12rpx rgba(0, 243, 255, 0.1);
+ }
+
+ .amount-text {
+ font-size: 32rpx;
+ font-weight: 600;
+ color: var(--text-primary) !important;
+ position: relative;
+ z-index: 10;
+ transition: all 0.3s;
+ }
+
+ .amount-item.active .amount-text {
+ color: var(--text-highlight) !important;
+ text-shadow: 0 0 10rpx var(--primary-color);
+ }
+
+ .amount-halo {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: 90%;
+ height: 90%;
+ background: radial-gradient(
+ circle at center,
+ rgba(0, 243, 255, 0.2) 0%,
+ transparent 70%
+ );
+ border-radius: 50%;
+ animation: haloPulse 2s ease-in-out infinite;
+ opacity: 0;
+ z-index: 1; /* 在文字下面 */
+ }
+
+ .amount-item.active .amount-halo {
+ opacity: 1;
+ }
+
+ @keyframes haloPulse {
+ 0%, 100% {
+ opacity: 0.4;
+ transform: translate(-50%, -50%) scale(1);
+ }
+ 50% {
+ opacity: 0.6;
+ transform: translate(-50%, -50%) scale(1.1);
+ }
+ }
+
+ /* 自定义金额输入 - 改为线条样式 */
+ .custom-amount {
+ background: transparent;
+ border-radius: 0;
+ padding: 0;
+ border: none;
+ width: 100%;
+ box-sizing: border-box;
+ }
+
+ .custom-input-wrapper {
+ position: relative;
+ margin-bottom: 20rpx;
+ width: 100%;
+ padding-bottom: 10rpx;
+ }
+
+ .input-label {
+ position: absolute;
+ left: 0;
+ top: 50%;
+ transform: translateY(-50%);
+ font-size: 42rpx;
+ font-weight: 800;
+ color: var(--accent-color) !important;
+ text-shadow: 0 0 10rpx rgba(255, 215, 0, 0.5);
+ z-index: 10;
+ padding-left: 10rpx;
+ }
+
+ .custom-input {
+ background: transparent;
+ border: none;
+ padding: 30rpx 20rpx 30rpx 60rpx;
+ font-size: 42rpx;
+ font-weight: 800;
+ color: var(--text-primary) !important;
+ width: 100%;
+ text-align: left;
+ caret-color: var(--primary-color);
+ border-radius: 0;
+ border-bottom: 2rpx solid rgba(255, 255, 255, 0.15);
+ transition: all 0.3s ease;
+ box-sizing: border-box;
+ height: 80rpx;
+ }
+
+ .custom-input:focus {
+ border-bottom-color: var(--primary-color);
+ box-shadow: 0 0 15rpx rgba(0, 243, 255, 0.3);
+ }
+
+ .placeholder {
+ color: rgba(160, 174, 192, 0.6) !important;
+ font-size: 34rpx;
+ font-weight: 500;
+ }
+
+ .input-line {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: 3rpx;
+ background: rgba(255, 255, 255, 0.1);
+ transition: all 0.3s ease;
+ }
+
+ .input-line.active {
+ background: linear-gradient(90deg,
+ var(--primary-color),
+ var(--secondary-color),
+ var(--primary-color)
+ );
+ height: 3rpx;
+ box-shadow: 0 0 15rpx var(--primary-color);
+ animation: lineFlash 1.5s ease-in-out infinite;
+ }
+
+ @keyframes lineFlash {
+ 0%, 100% { opacity: 0.8; }
+ 50% { opacity: 1; }
+ }
+
+ .amount-hint {
+ font-size: 24rpx;
+ color: var(--text-secondary);
+ text-align: center;
+ display: block;
+ margin-top: 15rpx;
+ }
+
+ /* ========== 支付按钮区域 ========== */
+ .pay-section {
+ margin-top: 40rpx;
+ }
+
+ .pay-button-wrapper {
+ width: 100%;
+ }
+
+ /* 支付按钮 - 修复颜色问题 */
+ .pay-button {
+ width: 100%;
+ height: 100rpx;
+ background: linear-gradient(135deg,
+ rgba(100, 116, 139, 0.8),
+ rgba(74, 85, 104, 0.8)
+ );
+ border-radius: 22rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ overflow: hidden;
+ transition: all 0.3s ease;
+ border: 1.5rpx solid rgba(255, 255, 255, 0.1);
+ box-shadow: 0 10rpx 25rpx rgba(0, 0, 0, 0.4);
+ }
+
+ /* 激活状态 - 修复为科技蓝渐变 */
+ .pay-button.active {
+ background: linear-gradient(135deg,
+ #008ca3, /* 深蓝 */
+ #00b8d4, /* 中蓝 */
+ #00f3ff /* 亮蓝 */
+ );
+ border: 1.5rpx solid rgba(0, 243, 255, 0.6);
+ box-shadow:
+ 0 12rpx 35rpx rgba(0, 243, 255, 0.4),
+ 0 0 50rpx rgba(0, 243, 255, 0.3),
+ inset 0 0 20rpx rgba(0, 243, 255, 0.15);
+ }
+
+ .pay-button.active:active {
+ transform: scale(0.98);
+ box-shadow:
+ 0 8rpx 25rpx rgba(0, 243, 255, 0.4),
+ 0 0 35rpx rgba(0, 243, 255, 0.3),
+ inset 0 0 15rpx rgba(0, 243, 255, 0.15);
+ }
+
+ /* 按钮闪光特效 */
+ .pay-button-shine {
+ position: absolute;
+ top: -50%;
+ left: -50%;
+ width: 200%;
+ height: 200%;
+ background: linear-gradient(45deg,
+ transparent 30%,
+ rgba(255, 255, 255, 0.2) 50%,
+ transparent 70%
+ );
+ animation: shine 3s infinite linear;
+ z-index: 1;
+ pointer-events: none;
+ opacity: 0;
+ }
+
+ .pay-button.active .pay-button-shine {
+ opacity: 1;
+ }
+
+ @keyframes shine {
+ 0% { transform: translateX(-100%) translateY(-100%) rotate(45deg); }
+ 100% { transform: translateX(100%) translateY(100%) rotate(45deg); }
+ }
+
+ /* 按钮内容 */
+ .button-content {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ position: relative;
+ z-index: 10; /* 确保按钮文字在最上层 */
+ width: 100%;
+ height: 100%;
+ }
+
+ .button-text {
+ font-size: 38rpx;
+ font-weight: 800;
+ color: rgba(255, 255, 255, 0.9);
+ letter-spacing: 2rpx;
+ text-shadow: 0 0 5rpx rgba(0, 0, 0, 0.5);
+ }
+
+ .pay-button.active .button-text {
+ color: white !important;
+ text-shadow:
+ 0 0 12rpx rgba(255, 255, 255, 0.8),
+ 0 0 25rpx rgba(0, 243, 255, 0.6);
+ }
+
+ /* ========== 支付状态提示 ========== */
+ .payment-status {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(10, 14, 23, 0.95);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ z-index: 1000;
+ backdrop-filter: blur(10rpx);
+ }
+
+ .status-content {
+ background: linear-gradient(135deg,
+ rgba(25, 30, 48, 0.95),
+ rgba(40, 47, 75, 0.95)
+ );
+ border-radius: 28rpx;
+ padding: 60rpx 40rpx;
+ text-align: center;
+ max-width: 500rpx;
+ width: 85%;
+ border: 2rpx solid var(--primary-color);
+ box-shadow:
+ 0 20rpx 50rpx rgba(0, 0, 0, 0.6),
+ 0 0 50rpx rgba(0, 243, 255, 0.4);
+ position: relative;
+ overflow: hidden;
+ animation: statusAppear 0.3s ease-out;
+ }
+
+ @keyframes statusAppear {
+ 0% { opacity: 0; transform: scale(0.9); }
+ 100% { opacity: 1; transform: scale(1); }
+ }
+
+ .status-text {
+ font-size: 36rpx;
+ font-weight: 800;
+ color: var(--text-primary) !important;
+ display: block;
+ margin-bottom: 15rpx;
+ text-shadow: 0 0 10rpx rgba(0, 243, 255, 0.6);
+ position: relative;
+ z-index: 10;
+ }
+
+ .status-subtext {
+ font-size: 28rpx;
+ color: var(--text-secondary);
+ display: block;
+ margin-bottom: 40rpx;
+ line-height: 1.4;
+ position: relative;
+ z-index: 10;
+ }
+
+ .status-actions {
+ display: flex;
+ justify-content: center;
+ position: relative;
+ z-index: 10;
+ }
+
+ .action-button {
+ background: linear-gradient(135deg, #008ca3, #00f3ff);
+ border-radius: 20rpx;
+ padding: 20rpx 40rpx;
+ min-width: 200rpx;
+ box-shadow:
+ 0 8rpx 24rpx rgba(0, 243, 255, 0.4),
+ 0 0 16rpx rgba(0, 243, 255, 0.3);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.2s ease;
+ }
+
+ .action-button:active {
+ transform: scale(0.95);
+ box-shadow:
+ 0 4rpx 12rpx rgba(0, 243, 255, 0.4),
+ 0 0 8rpx rgba(0, 243, 255, 0.3);
+ }
+
+ .action-text {
+ font-size: 30rpx;
+ font-weight: bold;
+ color: white;
+ text-align: center;
+ letter-spacing: 1rpx;
+ }
+
+ /* ========== 加载遮罩 ========== */
+ .loading-mask {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(10, 14, 23, 0.92);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ z-index: 1001;
+ backdrop-filter: blur(10rpx);
+ }
+
+ .loading-content {
+ text-align: center;
+ animation: loadingFloat 2s ease-in-out infinite;
+ }
+
+ @keyframes loadingFloat {
+ 0%, 100% { transform: translateY(0); }
+ 50% { transform: translateY(-12rpx); }
+ }
+
+ .loading-spinner {
+ width: 110rpx;
+ height: 110rpx;
+ border: 7rpx solid transparent;
+ border-top: 7rpx solid var(--primary-color);
+ border-right: 7rpx solid var(--secondary-color);
+ border-radius: 50%;
+ animation: spin 1.5s linear infinite;
+ margin: 0 auto 30rpx;
+ box-shadow:
+ 0 0 20rpx var(--primary-color),
+ inset 0 0 12rpx rgba(0, 243, 255, 0.2);
+ position: relative;
+ }
+
+ @keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+ }
+
+ .loading-text {
+ font-size: 28rpx;
+ color: var(--text-primary);
+ text-shadow: 0 0 10rpx rgba(0, 243, 255, 0.6);
+ letter-spacing: 1rpx;
+ }
+
+
+
+
+/* ========== 修复背景颜色和间距 ========== */
+
+/* 1. 调整背景颜色为更亮的科技风 */
+page {
+ background: linear-gradient(135deg,
+ #0e1424 0%, /* 调亮深色部分 */
+ #1a2238 50%,
+ #242d45 100%
+ ) !important;
+ height: 100%;
+ }
+
+ /* 2. 调整页面整体间距 */
+ .shangjia-chongzhi-page {
+ padding: 20rpx 20rpx 100rpx !important; /* 稍微增加边距 */
+ background: transparent !important;
+ min-height: 100vh;
+ }
+
+ /* 3. 调整余额卡片 */
+ .balance-card {
+ margin: 0 0 30rpx !important; /* 增加下方间距 */
+ padding: 35rpx 25rpx !important; /* 增加内边距 */
+ border-radius: 25rpx !important;
+ background: rgba(25, 30, 48, 0.9) !important; /* 调亮背景 */
+ border: 1.5rpx solid rgba(0, 243, 255, 0.4) !important; /* 加强边框 */
+ }
+
+ /* 4. 调整余额字体和颜色 - 确保可见 */
+ .balance-amount {
+ margin: 30rpx 0 !important;
+ }
+
+ .currency {
+ font-size: 42rpx !important;
+ color: #ffd700 !important; /* 使用更亮的金色 */
+ text-shadow: 0 0 20rpx rgba(255, 215, 0, 0.8) !important;
+ }
+
+ .amount {
+ font-size: 72rpx !important; /* 稍微调大 */
+ color: #ffffff !important;
+ text-shadow:
+ 0 0 25rpx rgba(0, 243, 255, 0.8),
+ 0 0 50rpx rgba(0, 243, 255, 0.5) !important;
+ }
+
+ .unit {
+ font-size: 30rpx !important;
+ color: #a0aec0 !important; /* 调亮灰色 */
+ }
+
+ /* 5. 调整充值金额区域 */
+ .amount-section {
+ margin-bottom: 30rpx !important;
+ padding: 30rpx 25rpx !important;
+ border-radius: 25rpx !important;
+ background: rgba(30, 35, 55, 0.9) !important; /* 调亮背景 */
+ }
+
+ /* 6. 调整快捷金额网格 */
+ .quick-amount-grid {
+ gap: 18rpx !important;
+ margin-bottom: 30rpx !important;
+ }
+
+ .amount-item {
+ min-height: 125rpx !important; /* 增加高度 */
+ padding: 25rpx 10rpx !important;
+ }
+
+ .amount-text {
+ font-size: 32rpx !important;
+ color: #ffffff !important;
+ }
+
+ /* 7. 调整输入区域 - 修复占位符溢出问题 */
+ .custom-amount {
+ margin-top: 20rpx !important; /* 增加上方间距 */
+ }
+
+ .custom-input-wrapper {
+ margin-bottom: 20rpx !important;
+ padding-bottom: 15rpx !important; /* 增加底部间距 */
+ }
+
+ .custom-input {
+ padding: 28rpx 20rpx 28rpx 60rpx !important;
+ height: 145rpx !important; /* 增加高度,让内容有足够空间 */
+ font-size: 40rpx !important;
+ color: #ffffff !important;
+ border-bottom: 2rpx solid rgba(255, 255, 255, 0.2) !important;
+ }
+
+ .custom-input:focus {
+ border-bottom-color: #00f3ff !important;
+ box-shadow: 0 0 20rpx rgba(0, 243, 255, 0.4) !important;
+ }
+
+ .input-label {
+ font-size: 40rpx !important;
+ color: #ffd700 !important; /* 使用金色 */
+ text-shadow: 0 0 15rpx rgba(255, 215, 0, 0.6) !important;
+ top: 50% !important;
+ transform: translateY(-50%) !important;
+ }
+
+ .placeholder {
+ color: rgba(160, 174, 192, 0.8) !important; /* 调亮占位符 */
+ font-size: 34rpx !important;
+ font-weight: 500 !important;
+ }
+
+ /* 8. 调整按钮区域 */
+ .pay-section {
+ margin-top: 65rpx !important;
+ }
+
+ .pay-button {
+ height: 100rpx !important; /* 恢复适当高度 */
+ border-radius: 22rpx !important;
+ }
+
+ .pay-button.active {
+ background: linear-gradient(135deg,
+ #0099b0, /* 更亮的蓝 */
+ #00ccff, /* 亮蓝 */
+ #00f3ff /* 最亮蓝 */
+ ) !important;
+ border: 1.5rpx solid rgba(0, 243, 255, 0.8) !important;
+ }
+
+ .button-text {
+ font-size: 36rpx !important;
+ color: #ffffff !important;
+ text-shadow: 0 0 10rpx rgba(0, 243, 255, 0.8) !important;
+ }
+
+ /* 9. 调整标题文字颜色 - 确保清晰可见 */
+ .title-text {
+ font-size: 36rpx !important;
+ color: #ffffff !important;
+ text-shadow:
+ 0 0 20rpx rgba(0, 243, 255, 0.8),
+ 0 0 40rpx rgba(0, 243, 255, 0.4) !important;
+ }
+
+ .section-title {
+ font-size: 34rpx !important;
+ color: #ffffff !important;
+ text-shadow: 0 0 15rpx rgba(255, 255, 255, 0.3) !important;
+ }
+
+ .subtitle-text,
+ .amount-hint {
+ color: #b8c2cc !important; /* 调亮灰色文字 */
+ font-size: 26rpx !important;
+ }
+
+ /* 10. 调整卡片文字颜色 */
+ .chip-text {
+ color: #ffffff !important;
+ text-shadow: 0 0 8rpx rgba(0, 243, 255, 0.6) !important;
+ }
+
+ .tag-text {
+ color: #4dfbff !important; /* 使用亮科技蓝 */
+ text-shadow: 0 0 8rpx rgba(0, 243, 255, 0.4) !important;
+ }
+
+ /* 11. 增强装饰元素可见性(但不干扰文字) */
+ .mech-line {
+ opacity: 0.25 !important; /* 稍微增强可见性 */
+ }
+
+ .mech-corner {
+ opacity: 0.3 !important; /* 稍微增强可见性 */
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/sjddxq/sjddxq.js b/miniprogram/pages/sjddxq/sjddxq.js
new file mode 100644
index 0000000..fdcf817
--- /dev/null
+++ b/miniprogram/pages/sjddxq/sjddxq.js
@@ -0,0 +1,707 @@
+// pages/sjddxq/sjddxq.js - 商家订单详情(完整版,已添加罚款/退款操作及教程按钮)
+const app = getApp()
+import request from '../../utils/request.js'
+import connectionManager from '../../utils/xiaoxilj.js'
+import PopupService from '../../services/popupService.js' // 新增:导入弹窗服务
+
+Page({
+ data: {
+ jibenShuju: {
+ dingdan_id: '', tupian: '', jieshao: '', create_time: '',
+ jine: '', nicheng: '', beizhu: '', zhuangtai: 0, fadanpingtai: 0, is_cross: 0
+ },
+ dashouInfo: { yonghuid: '', nicheng: '', avatar: '' },
+ xiangxiShuju: {
+ dashou_jiaofu: [], dashou_liuyan: '', tuikuan_liyou: '', shangjia_liuyan: ''
+ },
+ fadanData: null,
+ fenhongLilv: 0,
+ xinDingdanId: '',
+ zhuangtaiYanseMap: {
+ 1: '#E8F5E9', 2: '#E3F2FD', 3: '#F3E5F5', 4: '#FFF3E0',
+ 5: '#FFEBEE', 6: '#EFEBE9', 7: '#E0F7FA', 8: '#FFF8E1'
+ },
+ zhuangtaiWenziMap: {
+ 1: '已下单', 2: '进行中', 3: '已完成', 4: '退款中',
+ 5: '已退款', 6: '退款失败', 7: '指定中', 8: '结算中'
+ },
+ isLoading: true,
+ showChexiaoModal: false, chexiaoLiyou: '', isChexiaoLoading: false,
+ showTuikuanModal: false, tuikuanLiyou: '', isTuikuanLoading: false,
+ showFakuanModal: false, fakuanLiyou: '', fakuanJine: '', yingxiangQiangdan: true, isFakuanLoading: false,
+ showGhDashouModal: false, isGhDashouLoading: false,
+ showJiesuanConfirm: false, isJiesuanLoading: false,
+ jiesuanPingfen: 0, jiesuanLiuyan: '',
+ xingxingData: [
+ { id: 1, active: false }, { id: 2, active: false }, { id: 3, active: false },
+ { id: 4, active: false }, { id: 5, active: false }
+ ],
+ currentIMUserId: '',
+
+ // ========== 新增:罚单/订单操作弹窗控制 ==========
+ showModifyFakuanModal: false, // 修改罚款弹窗
+ modifyFakuanLiyou: '', // 修改后的罚款理由
+ modifyFakuanJine: '', // 修改后的罚款金额
+ showCancelFakuanModal: false, // 撤销罚款弹窗
+ cancelFakuanLiyou: '', // 撤销驳回理由
+ showResubmitFakuanModal: false, // 再次申请罚款弹窗
+ resubmitFakuanLiyou: '', // 再次申请的理由
+ resubmitFakuanJine: '', // 再次申请的金额
+ showCancelTuikuanModal: false, // 撤销退款申请弹窗
+ cancelTuikuanLiyou: '', // 撤销退款理由
+ showModifyTuikuanModal: false, // 修改退款理由弹窗
+ modifyTuikuanLiyou: '', // 修改后的退款理由
+ // 教程按钮状态
+ tutorialBtnHidden: false
+ },
+
+ onLoad(options) {
+ wx.setNavigationBarTitle({ title: '订单详情' })
+ this.ensureIMConnection()
+ this.jiexiTiaozhuanCanshu(options)
+ // 新增:启动教程按钮定时器
+ this.startTutorialBtnTimer()
+ },
+
+ onShow() {
+ this.registerNotificationComponent()
+ this.ensureIMConnection()
+ },
+
+ onHide() {
+ // 页面隐藏时重置弹窗服务状态,避免影响其他页面
+ PopupService.reset()
+ },
+
+ onUnload() {
+ // 清除缩进定时器,避免内存泄漏
+ if (this.tutorialTimer) {
+ clearTimeout(this.tutorialTimer)
+ }
+ // 页面卸载时重置弹窗服务
+ PopupService.reset()
+ },
+
+ registerNotificationComponent() {
+ const notificationComp = this.selectComponent('#global-notification')
+ if (notificationComp && notificationComp.showNotification) {
+ app.globalData.globalNotification = {
+ show: (data) => notificationComp.showNotification(data),
+ hide: () => notificationComp.hideNotification()
+ }
+ }
+ },
+
+ ensureIMConnection() {
+ const uid = wx.getStorageSync('uid')
+ if (!uid) return
+ app.globalData.currentRole = 'shangjia'
+ wx.setStorageSync('currentRole', 'shangjia')
+ const targetUserId = 'Sj' + uid
+ const status = wx.goEasy?.getConnectionStatus?.() || 'disconnected'
+ const currentUserId = wx.goEasy?.im?.userId
+ if ((status === 'connected' || status === 'reconnected') && currentUserId === targetUserId) {
+ this.setData({ currentIMUserId: targetUserId })
+ return
+ }
+ if (app.ensureConnection) app.ensureConnection()
+ this.setData({ currentIMUserId: targetUserId })
+ },
+
+ jiexiTiaozhuanCanshu(options) {
+ try {
+ const dingdanDataStr = options.dingdanData || ''
+ if (!dingdanDataStr) {
+ wx.showToast({ title: '订单数据错误', icon: 'none' })
+ setTimeout(() => wx.navigateBack(), 1500)
+ return
+ }
+ const dingdanData = JSON.parse(decodeURIComponent(dingdanDataStr))
+ let tupianUrl = dingdanData.tupian || ''
+ if (!tupianUrl) tupianUrl = this.getTupianUrl()
+ let jine = parseFloat(dingdanData.jine || 0)
+ if (isNaN(jine)) jine = 0
+ this.setData({
+ 'jibenShuju.dingdan_id': dingdanData.dingdan_id || '',
+ 'jibenShuju.tupian': tupianUrl,
+ 'jibenShuju.jieshao': dingdanData.jieshao || '',
+ 'jibenShuju.create_time': dingdanData.create_time || '',
+ 'jibenShuju.jine': jine.toFixed(2),
+ 'jibenShuju.nicheng': dingdanData.nicheng || '',
+ 'jibenShuju.beizhu': dingdanData.beizhu || '',
+ 'jibenShuju.zhuangtai': dingdanData.zhuangtai || 0,
+ 'jibenShuju.fadanpingtai': dingdanData.fadanpingtai || 0,
+ 'jibenShuju.is_cross': dingdanData.is_cross || 0,
+ isLoading: false
+ })
+ this.jiazaiXiangxiShuju(dingdanData.dingdan_id)
+ } catch (error) {
+ console.error('解析订单数据失败', error)
+ wx.showToast({ title: '数据解析失败', icon: 'none' })
+ setTimeout(() => wx.navigateBack(), 1500)
+ }
+ },
+
+ getTupianUrl() {
+ const cacheTouxiang = wx.getStorageSync('touxiang') || ''
+ let tupianPath = cacheTouxiang || app.globalData.morentouxiang || ''
+ const ossImageUrl = app.globalData.ossImageUrl || ''
+ if (tupianPath && ossImageUrl) {
+ if (tupianPath.startsWith('/')) tupianPath = tupianPath.substring(1)
+ return ossImageUrl + tupianPath
+ }
+ return ''
+ },
+
+ async jiazaiXiangxiShuju(dingdanId) {
+ if (!dingdanId) return
+ try {
+ const res = await request({
+ url: '/dingdan/sjddxq',
+ method: 'POST',
+ data: { dingdan_id: dingdanId },
+ header: { 'content-type': 'application/json' }
+ })
+ if (res && res.data.code === 0 && res.data.data) {
+ const d = res.data.data
+ let jiaofuList = d.dashou_jiaofu || []
+ jiaofuList = jiaofuList.map(url => {
+ if (url && !url.startsWith('http')) {
+ const oss = app.globalData.ossImageUrl || ''
+ return oss + url
+ }
+ return url
+ })
+ let dashouAvatar = d.dashou_avatar || ''
+ if (dashouAvatar && !dashouAvatar.startsWith('http')) {
+ dashouAvatar = (app.globalData.ossImageUrl || '') + dashouAvatar
+ }
+ this.setData({
+ 'dashouInfo.yonghuid': d.dashou_yonghuid || '',
+ 'dashouInfo.nicheng': d.dashou_nicheng || '',
+ 'dashouInfo.avatar': dashouAvatar,
+ 'xiangxiShuju.dashou_jiaofu': jiaofuList,
+ 'xiangxiShuju.dashou_liuyan': d.dashou_liuyan || '',
+ 'xiangxiShuju.tuikuan_liyou': d.tuikuan_liyou || '',
+ 'xiangxiShuju.shangjia_liuyan': d.shangjia_liuyan || '',
+ fadanData: d.fadan || null,
+ fenhongLilv: parseFloat(d.fenhong_lilv) || 0,
+ xinDingdanId: d.xin_dingdan_id || '',
+ 'jibenShuju.zhuangtai': d.zhuangtai || this.data.jibenShuju.zhuangtai,
+ 'jibenShuju.is_cross': d.is_cross != null ? d.is_cross : this.data.jibenShuju.is_cross,
+ 'jibenShuju.fadanpingtai': d.fadanpingtai != null ? d.fadanpingtai : this.data.jibenShuju.fadanpingtai,
+ })
+ } else {
+ wx.showToast({ title: res?.data?.msg || '获取详情失败', icon: 'none' })
+ }
+ } catch (err) {
+ console.error('加载详情失败', err)
+ wx.showToast({ title: '网络错误', icon: 'none' })
+ }
+ },
+
+ fuzhiWenben(e) {
+ const text = e.currentTarget.dataset.text
+ if (!text) return
+ wx.setClipboardData({ data: text })
+ wx.showToast({ title: '已复制', icon: 'success' })
+ },
+
+ yulanTupian(e) {
+ const current = e.currentTarget.dataset.url
+ const urls = e.currentTarget.dataset.urls || [current]
+ wx.previewImage({ current, urls })
+ },
+
+ goToKefu() {
+ const kefuLink = app.globalData.kefuConfig?.link || ''
+ const corpId = app.globalData.kefuConfig?.enterpriseId || ''
+ if (!kefuLink || !corpId) {
+ wx.showToast({ title: '客服配置未加载', icon: 'none' })
+ return
+ }
+ wx.openCustomerServiceChat({
+ extInfo: { url: kefuLink },
+ corpId: corpId,
+ success: (res) => console.log('跳转客服成功', res),
+ fail: (err) => {
+ console.error('跳转客服失败', err)
+ wx.showToast({ title: '暂时无法连接客服', icon: 'none' })
+ }
+ })
+ },
+
+ // ===== 联系打手(后端准备配对群 + 稳定跳转) =====
+ goToChatWithDashou() {
+ const uid = wx.getStorageSync('uid')
+ if (!uid) return
+
+ const orderId = this.data.jibenShuju.dingdan_id
+ if (!orderId) {
+ wx.showToast({ title: '订单ID缺失', icon: 'none' })
+ return
+ }
+
+ connectionManager.connectToGroupChat({
+ identityType: 'shangjia',
+ userId: 'Sj' + uid,
+ orderId,
+ partnerUid: this.data.dashouInfo.yonghuid,
+ fadanPingtai: this.data.jibenShuju.fadanpingtai || 0,
+ groupName: (this.data.dashouInfo.nicheng || this.data.jibenShuju.nicheng) + '的订单群',
+ isCross: this.data.jibenShuju.is_cross || 0
+ }).catch(err => {
+ console.error('跳转群聊失败', err)
+ })
+ },
+
+ // ========== 以下所有业务功能完全保持不变 ==========
+ showGhDashouConfirm() { this.setData({ showGhDashouModal: true }) },
+ closeGhDashouModal() { this.setData({ showGhDashouModal: false }) },
+ async tijiaoGhDashou() {
+ if (this.data.isGhDashouLoading) return
+ this.setData({ isGhDashouLoading: true })
+ wx.showLoading({ title: '更换中...', mask: true })
+ try {
+ const res = await request({
+ url: '/dingdan/zxsjghds',
+ method: 'POST',
+ data: { dingdan_id: this.data.jibenShuju.dingdan_id },
+ header: { 'content-type': 'application/json' }
+ })
+ wx.hideLoading()
+ this.setData({ isGhDashouLoading: false, showGhDashouModal: false })
+ if (res && res.data.code === 0) {
+ this.setData({ xinDingdanId: res.data.data?.xin_dingdan_id || '' })
+ wx.showToast({ title: '更换成功', icon: 'success' })
+ this.jiazaiXiangxiShuju(this.data.jibenShuju.dingdan_id)
+ } else {
+ wx.showToast({ title: res?.data?.msg || '更换失败', icon: 'none' })
+ }
+ } catch (err) {
+ wx.hideLoading()
+ this.setData({ isGhDashouLoading: false, showGhDashouModal: false })
+ wx.showToast({ title: '网络错误', icon: 'none' })
+ }
+ },
+
+ showFakuanModal() { this.setData({ showFakuanModal: true, fakuanLiyou: '', fakuanJine: '', yingxiangQiangdan: true }) },
+ closeFakuanModal() { this.setData({ showFakuanModal: false }) },
+ inputFakuanLiyou(e) { this.setData({ fakuanLiyou: e.detail.value.slice(0, 50) }) },
+ inputFakuanJine(e) { this.setData({ fakuanJine: e.detail.value }) },
+ toggleQiangdan() { this.setData({ yingxiangQiangdan: !this.data.yingxiangQiangdan }) },
+ jisuanFenhong() {
+ const jine = parseFloat(this.data.fakuanJine)
+ if (isNaN(jine) || jine <= 0) return '0.00'
+ return (jine * this.data.fenhongLilv).toFixed(2)
+ },
+ async tijiaoFakuan() {
+ const { fakuanLiyou, fakuanJine, jibenShuju, dashouInfo } = this.data
+ if (!fakuanLiyou.trim()) { wx.showToast({ title: '请输入罚款原因', icon: 'none' }); return }
+ const jineNum = parseFloat(fakuanJine)
+ if (isNaN(jineNum) || jineNum <= 0) { wx.showToast({ title: '请输入正确的罚款金额', icon: 'none' }); return }
+ if (this.data.isFakuanLoading) return
+ this.setData({ isFakuanLoading: true })
+ wx.showLoading({ title: '提交中...', mask: true })
+ try {
+ const res = await request({
+ url: '/dingdan/sjfksq',
+ method: 'POST',
+ data: {
+ dingdan_id: jibenShuju.dingdan_id,
+ chufa_liyou: fakuanLiyou,
+ fakuanjine: jineNum.toFixed(2),
+ yingxiang_qiangdan: this.data.yingxiangQiangdan ? 1 : 0,
+ dashou_id: dashouInfo.yonghuid
+ },
+ header: { 'content-type': 'application/json' }
+ })
+ wx.hideLoading()
+ this.setData({ isFakuanLoading: false })
+ if (res && res.data.code === 0) {
+ wx.showToast({ title: '罚款申请已提交', icon: 'success' })
+ this.setData({ showFakuanModal: false })
+ this.jiazaiXiangxiShuju(jibenShuju.dingdan_id)
+ } else {
+ wx.showToast({ title: res?.data?.msg || '提交失败', icon: 'none' })
+ }
+ } catch (err) {
+ wx.hideLoading()
+ this.setData({ isFakuanLoading: false })
+ wx.showToast({ title: '网络错误', icon: 'none' })
+ }
+ },
+
+ showChexiaoBtn() { this.setData({ showChexiaoModal: true, chexiaoLiyou: '' }) },
+ closeChexiaoModal() { this.setData({ showChexiaoModal: false }) },
+ shuruChexiaoLiyou(e) { this.setData({ chexiaoLiyou: e.detail.value.slice(0, 50) }) },
+ async tijiaoChexiao() {
+ if (this.data.isChexiaoLoading) return
+ const { chexiaoLiyou, jibenShuju } = this.data
+ if (!chexiaoLiyou.trim()) { wx.showToast({ title: '请输入撤销原因', icon: 'none' }); return }
+ this.setData({ isChexiaoLoading: true })
+ wx.showLoading({ title: '撤销中...', mask: true })
+ try {
+ const res = await request({
+ url: '/dingdan/sjchexiao',
+ method: 'POST',
+ data: { dingdan_id: jibenShuju.dingdan_id, chexiao_liyou: chexiaoLiyou },
+ header: { 'content-type': 'application/json' }
+ })
+ wx.hideLoading()
+ this.setData({ isChexiaoLoading: false })
+ if (res && res.data.code === 0) {
+ this.setData({ 'jibenShuju.zhuangtai': 5, showChexiaoModal: false })
+ wx.showToast({ title: '撤销成功', icon: 'success' })
+ } else {
+ wx.showToast({ title: res?.data?.msg || '撤销失败', icon: 'none' })
+ }
+ } catch (err) {
+ wx.hideLoading()
+ this.setData({ isChexiaoLoading: false })
+ wx.showToast({ title: '网络错误', icon: 'none' })
+ }
+ },
+
+ showTuikuanBtn() { this.setData({ showTuikuanModal: true, tuikuanLiyou: '' }) },
+ closeTuikuanModal() { this.setData({ showTuikuanModal: false }) },
+ shuruTuikuanLiyou(e) { this.setData({ tuikuanLiyou: e.detail.value.slice(0, 50) }) },
+ async tijiaoTuikuan() {
+ if (this.data.isTuikuanLoading) return
+ const { tuikuanLiyou, jibenShuju } = this.data
+ if (!tuikuanLiyou.trim()) { wx.showToast({ title: '请输入退款原因', icon: 'none' }); return }
+ this.setData({ isTuikuanLoading: true })
+ wx.showLoading({ title: '提交中...', mask: true })
+ try {
+ const res = await request({
+ url: '/dingdan/sjtkshenqing',
+ method: 'POST',
+ data: { dingdan_id: jibenShuju.dingdan_id, tuikuan_liyou: tuikuanLiyou },
+ header: { 'content-type': 'application/json' }
+ })
+ wx.hideLoading()
+ this.setData({ isTuikuanLoading: false })
+ if (res && res.data.code === 0) {
+ this.setData({ 'jibenShuju.zhuangtai': 4, showTuikuanModal: false })
+ wx.showToast({ title: '退款申请已提交', icon: 'success' })
+ } else {
+ wx.showToast({ title: res?.data?.msg || '失败', icon: 'none' })
+ }
+ } catch (err) {
+ wx.hideLoading()
+ this.setData({ isTuikuanLoading: false })
+ wx.showToast({ title: '网络错误', icon: 'none' })
+ }
+ },
+
+ dianjiXingxing(e) {
+ const pf = e.currentTarget.dataset.index
+ const newStars = this.data.xingxingData.map((item, idx) => ({ ...item, active: idx < pf }))
+ this.setData({ xingxingData: newStars, jiesuanPingfen: pf })
+ },
+ shuruLiuyan(e) { this.setData({ jiesuanLiuyan: e.detail.value.slice(0, 50) }) },
+ showJiesuanConfirm() { this.setData({ showJiesuanConfirm: true }) },
+ closeJiesuanConfirm() { this.setData({ showJiesuanConfirm: false }) },
+ async tijiaoJiesuan() {
+ if (this.data.isJiesuanLoading) return
+ this.setData({ isJiesuanLoading: true })
+ wx.showLoading({ title: '结算中...', mask: true })
+ try {
+ const res = await request({
+ url: '/dingdan/sjjiesuan',
+ method: 'POST',
+ data: {
+ dingdan_id: this.data.jibenShuju.dingdan_id,
+ pingfen: this.data.jiesuanPingfen,
+ liuyan: this.data.jiesuanLiuyan
+ },
+ header: { 'content-type': 'application/json' }
+ })
+ wx.hideLoading()
+ this.setData({ isJiesuanLoading: false, showJiesuanConfirm: false })
+ if (res && res.data.code === 0) {
+ this.setData({ 'jibenShuju.zhuangtai': 3 })
+ wx.showToast({ title: '结算成功', icon: 'success' })
+ } else {
+ wx.showToast({ title: res?.data?.msg || '结算失败', icon: 'none' })
+ }
+ } catch (err) {
+ wx.hideLoading()
+ this.setData({ isJiesuanLoading: false })
+ wx.showToast({ title: '网络错误', icon: 'none' })
+ }
+ },
+
+ // ========== 新增:罚款操作(修改/撤销/再次申请) ==========
+ // 显示修改罚款弹窗(仅当状态为待缴纳 或 申诉中 时可用)
+ showModifyFakuan() {
+ const status = this.data.fadanData?.zhuangtai;
+ if (status !== 1 && status !== 3) {
+ wx.showToast({ title: '当前状态不可修改罚款', icon: 'none' });
+ return;
+ }
+ this.setData({
+ showModifyFakuanModal: true,
+ modifyFakuanLiyou: this.data.fadanData?.chufaliyou || '',
+ modifyFakuanJine: this.data.fadanData?.fakuanjine || ''
+ });
+ },
+ closeModifyFakuanModal() {
+ this.setData({ showModifyFakuanModal: false });
+ },
+ inputModifyFakuanLiyou(e) {
+ this.setData({ modifyFakuanLiyou: e.detail.value.slice(0, 100) });
+ },
+ inputModifyFakuanJine(e) {
+ this.setData({ modifyFakuanJine: e.detail.value });
+ },
+ async submitModifyFakuan() {
+ const { modifyFakuanLiyou, modifyFakuanJine, fadanData, jibenShuju } = this.data;
+ if (!modifyFakuanLiyou.trim()) {
+ wx.showToast({ title: '请输入罚款理由', icon: 'none' }); return;
+ }
+ const jine = parseFloat(modifyFakuanJine);
+ if (isNaN(jine) || jine <= 0) {
+ wx.showToast({ title: '请输入正确的罚款金额', icon: 'none' }); return;
+ }
+ wx.showLoading({ title: '提交中...', mask: true });
+ try {
+ const res = await request({
+ url: '/dingdan/sjfkxgycx',
+ method: 'POST',
+ data: {
+ operation: 'modify_penalty',
+ fadan_id: fadanData.id,
+ dingdan_id: jibenShuju.dingdan_id,
+ liyou: modifyFakuanLiyou,
+ jine: jine.toFixed(2)
+ }
+ });
+ wx.hideLoading();
+ if (res && res.data.code === 0) {
+ wx.showToast({ title: '修改成功', icon: 'success' });
+ this.setData({ showModifyFakuanModal: false });
+ this.jiazaiXiangxiShuju(jibenShuju.dingdan_id); // 刷新数据
+ } else {
+ wx.showToast({ title: res?.data?.msg || '修改失败', icon: 'none' });
+ }
+ } catch (err) {
+ wx.hideLoading();
+ wx.showToast({ title: '网络错误', icon: 'none' });
+ }
+ },
+
+ // 显示撤销罚款弹窗(待缴纳/申诉中 可撤销,变为已驳回)
+ showCancelFakuan() {
+ const status = this.data.fadanData?.zhuangtai;
+ if (status !== 1 && status !== 3) {
+ wx.showToast({ title: '当前状态不可撤销罚款', icon: 'none' });
+ return;
+ }
+ this.setData({ showCancelFakuanModal: true, cancelFakuanLiyou: '' });
+ },
+ closeCancelFakuanModal() {
+ this.setData({ showCancelFakuanModal: false });
+ },
+ inputCancelFakuanLiyou(e) {
+ this.setData({ cancelFakuanLiyou: e.detail.value.slice(0, 100) });
+ },
+ async submitCancelFakuan() {
+ const { cancelFakuanLiyou, fadanData, jibenShuju } = this.data;
+ if (!cancelFakuanLiyou.trim()) {
+ wx.showToast({ title: '请输入驳回理由', icon: 'none' }); return;
+ }
+ wx.showLoading({ title: '提交中...', mask: true });
+ try {
+ const res = await request({
+ url: '/dingdan/sjfkxgycx',
+ method: 'POST',
+ data: {
+ operation: 'cancel_penalty',
+ fadan_id: fadanData.id,
+ dingdan_id: jibenShuju.dingdan_id,
+ liyou: cancelFakuanLiyou
+ }
+ });
+ wx.hideLoading();
+ if (res && res.data.code === 0) {
+ wx.showToast({ title: '已撤销', icon: 'success' });
+ this.setData({ showCancelFakuanModal: false });
+ this.jiazaiXiangxiShuju(jibenShuju.dingdan_id);
+ } else {
+ wx.showToast({ title: res?.data?.msg || '撤销失败', icon: 'none' });
+ }
+ } catch (err) {
+ wx.hideLoading();
+ wx.showToast({ title: '网络错误', icon: 'none' });
+ }
+ },
+
+ // 显示再次申请罚款弹窗(仅当状态为已驳回时可用)
+ showResubmitFakuan() {
+ if (this.data.fadanData?.zhuangtai !== 4) {
+ wx.showToast({ title: '当前状态不可再次申请', icon: 'none' });
+ return;
+ }
+ this.setData({
+ showResubmitFakuanModal: true,
+ resubmitFakuanLiyou: '',
+ resubmitFakuanJine: ''
+ });
+ },
+ closeResubmitFakuanModal() {
+ this.setData({ showResubmitFakuanModal: false });
+ },
+ inputResubmitFakuanLiyou(e) {
+ this.setData({ resubmitFakuanLiyou: e.detail.value.slice(0, 100) });
+ },
+ inputResubmitFakuanJine(e) {
+ this.setData({ resubmitFakuanJine: e.detail.value });
+ },
+ async submitResubmitFakuan() {
+ const { resubmitFakuanLiyou, resubmitFakuanJine, fadanData, jibenShuju } = this.data;
+ if (!resubmitFakuanLiyou.trim()) {
+ wx.showToast({ title: '请输入罚款理由', icon: 'none' }); return;
+ }
+ const jine = parseFloat(resubmitFakuanJine);
+ if (isNaN(jine) || jine <= 0) {
+ wx.showToast({ title: '请输入正确的罚款金额', icon: 'none' }); return;
+ }
+ wx.showLoading({ title: '提交中...', mask: true });
+ try {
+ const res = await request({
+ url: '/dingdan/sjfkxgycx',
+ method: 'POST',
+ data: {
+ operation: 'resubmit_penalty',
+ fadan_id: fadanData.id,
+ dingdan_id: jibenShuju.dingdan_id,
+ liyou: resubmitFakuanLiyou,
+ jine: jine.toFixed(2)
+ }
+ });
+ wx.hideLoading();
+ if (res && res.data.code === 0) {
+ wx.showToast({ title: '申请成功', icon: 'success' });
+ this.setData({ showResubmitFakuanModal: false });
+ this.jiazaiXiangxiShuju(jibenShuju.dingdan_id);
+ } else {
+ wx.showToast({ title: res?.data?.msg || '申请失败', icon: 'none' });
+ }
+ } catch (err) {
+ wx.hideLoading();
+ wx.showToast({ title: '网络错误', icon: 'none' });
+ }
+ },
+
+ // ========== 新增:订单退款操作(撤销/修改理由) ==========
+ showCancelTuikuan() {
+ if (this.data.jibenShuju.zhuangtai !== 4) {
+ wx.showToast({ title: '只有退款中的订单可撤销', icon: 'none' });
+ return;
+ }
+ this.setData({ showCancelTuikuanModal: true, cancelTuikuanLiyou: '' });
+ },
+ closeCancelTuikuanModal() {
+ this.setData({ showCancelTuikuanModal: false });
+ },
+ inputCancelTuikuanLiyou(e) {
+ this.setData({ cancelTuikuanLiyou: e.detail.value.slice(0, 100) });
+ },
+ async submitCancelTuikuan() {
+ const { cancelTuikuanLiyou, jibenShuju } = this.data;
+ if (!cancelTuikuanLiyou.trim()) {
+ wx.showToast({ title: '请输入撤销理由', icon: 'none' }); return;
+ }
+ wx.showLoading({ title: '撤销中...', mask: true });
+ try {
+ const res = await request({
+ url: '/dingdan/sjfkxgycx',
+ method: 'POST',
+ data: {
+ operation: 'cancel_refund',
+ dingdan_id: jibenShuju.dingdan_id,
+ liyou: cancelTuikuanLiyou
+ }
+ });
+ wx.hideLoading();
+ if (res && res.data.code === 0) {
+ wx.showToast({ title: '已撤销退款申请', icon: 'success' });
+ this.setData({ showCancelTuikuanModal: false });
+ this.jiazaiXiangxiShuju(jibenShuju.dingdan_id);
+ } else {
+ wx.showToast({ title: res?.data?.msg || '撤销失败', icon: 'none' });
+ }
+ } catch (err) {
+ wx.hideLoading();
+ wx.showToast({ title: '网络错误', icon: 'none' });
+ }
+ },
+
+ showModifyTuikuan() {
+ if (this.data.jibenShuju.zhuangtai !== 4) {
+ wx.showToast({ title: '只有退款中的订单可修改理由', icon: 'none' });
+ return;
+ }
+ this.setData({
+ showModifyTuikuanModal: true,
+ modifyTuikuanLiyou: this.data.xiangxiShuju.tuikuan_liyou || ''
+ });
+ },
+ closeModifyTuikuanModal() {
+ this.setData({ showModifyTuikuanModal: false });
+ },
+ inputModifyTuikuanLiyou(e) {
+ this.setData({ modifyTuikuanLiyou: e.detail.value.slice(0, 100) });
+ },
+ async submitModifyTuikuan() {
+ const { modifyTuikuanLiyou, jibenShuju } = this.data;
+ if (!modifyTuikuanLiyou.trim()) {
+ wx.showToast({ title: '请输入退款理由', icon: 'none' }); return;
+ }
+ wx.showLoading({ title: '修改中...', mask: true });
+ try {
+ const res = await request({
+ url: '/dingdan/sjfkxgycx',
+ method: 'POST',
+ data: {
+ operation: 'modify_refund_reason',
+ dingdan_id: jibenShuju.dingdan_id,
+ liyou: modifyTuikuanLiyou
+ }
+ });
+ wx.hideLoading();
+ if (res && res.data.code === 0) {
+ wx.showToast({ title: '修改成功', icon: 'success' });
+ this.setData({ showModifyTuikuanModal: false });
+ this.jiazaiXiangxiShuju(jibenShuju.dingdan_id);
+ } else {
+ wx.showToast({ title: res?.data?.msg || '修改失败', icon: 'none' });
+ }
+ } catch (err) {
+ wx.hideLoading();
+ wx.showToast({ title: '网络错误', icon: 'none' });
+ }
+ },
+
+ // ========== 新增:教程按钮 ==========
+ startTutorialBtnTimer() {
+ if (this.tutorialTimer) clearTimeout(this.tutorialTimer);
+ this.tutorialTimer = setTimeout(() => {
+ this.setData({ tutorialBtnHidden: true });
+ }, 5000);
+ },
+ resetTutorialBtn() {
+ if (this.data.tutorialBtnHidden) {
+ this.setData({ tutorialBtnHidden: false });
+ }
+ if (this.tutorialTimer) clearTimeout(this.tutorialTimer);
+ this.tutorialTimer = setTimeout(() => {
+ this.setData({ tutorialBtnHidden: true });
+ }, 5000);
+ },
+ onTutorialBtnTap() {
+ this.resetTutorialBtn();
+ PopupService.checkAndShow(this, 'sjddxq1');
+ }
+})
\ No newline at end of file
diff --git a/miniprogram/pages/sjddxq/sjddxq.json b/miniprogram/pages/sjddxq/sjddxq.json
new file mode 100644
index 0000000..837f016
--- /dev/null
+++ b/miniprogram/pages/sjddxq/sjddxq.json
@@ -0,0 +1,5 @@
+{
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/pages/sjddxq/sjddxq.wxml b/miniprogram/pages/sjddxq/sjddxq.wxml
new file mode 100644
index 0000000..d952ee0
--- /dev/null
+++ b/miniprogram/pages/sjddxq/sjddxq.wxml
@@ -0,0 +1,354 @@
+
+
+
+
+
+
+
+
+ 加载中...
+
+
+
+
+
+
+
+
+
+
+
+ {{jibenShuju.jieshao || '无描述'}}
+
+
+
+
+
+ 订单信息
+
+ 游戏昵称{{jibenShuju.nicheng}}
+
+ 备注{{jibenShuju.beizhu}}
+
+ 订单ID{{jibenShuju.dingdan_id}}📋
+
+ 创建时间{{jibenShuju.create_time}}
+
+ 订单金额¥{{jibenShuju.jine}}
+
+ 状态{{zhuangtaiWenziMap[jibenShuju.zhuangtai]}}
+
+
+ 撤销退款申请
+ 修改退款理由
+
+
+
+
+
+ 服务打手
+
+
+
+
+
+ 服务详情
+
+
+ 交付图片
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 打手留言
+ {{xiangxiShuju.dashou_liuyan}}
+
+
+
+ 退款理由
+ {{xiangxiShuju.tuikuan_liyou}}
+
+
+
+ 商家留言
+ {{xiangxiShuju.shangjia_liuyan}}
+
+
+
+
+
+ 罚款信息
+
+ 罚款金额¥{{fadanData.fakuanjine || '0.00'}}
+
+ 罚款理由{{fadanData.chufaliyou || '-'}}
+
+ 是否影响抢单{{fadanData.yingxiang_qiangdan == 1 ? '是' : '否'}}
+
+ 罚单状态{{fadanData.zhuangtai == 1 ? '待缴纳' : fadanData.zhuangtai == 2 ? '已缴纳' : fadanData.zhuangtai == 3 ? '申诉中' : '已驳回'}}
+
+ 申诉理由{{fadanData.shensuliyou}}
+
+ 同意处罚理由{{fadanData.tongyi_chufaliyou}}
+
+ 驳回理由{{fadanData.bohuiliyou}}
+
+ 创建时间{{fadanData.create_time}}
+
+ 更新时间{{fadanData.update_time}}
+
+
+
+
+ 修改罚款
+ 撤销罚款
+
+
+
+ 再次申请罚款
+
+
+
+
+
+
+ 更换打手成功
+ 新订单ID{{xinDingdanId}}📋
+
+
+
+
+ 订单结算
+
+ 打星评分
+
+
+
+
+ {{item.active ? '★' : '☆'}}
+
+
+
+
+
+
+ 立即结算
+
+
+
+
+
+
+
+ 联系客服
+ 联系打手
+
+
+
+
+ 撤销订单
+
+
+
+ 申请退款
+
+
+
+
+
+
+
+ 撤销订单×
+
+
+ 取消
+ {{isChexiaoLoading ? '处理中' : '确认撤销'}}
+
+
+
+
+
+
+
+
+ 申请退款×
+
+
+ 取消
+ {{isTuikuanLoading ? '处理中' : '确认申请'}}
+
+
+
+
+
+
+
+
+ 更换打手×
+
+ 确认更换打手吗?当前订单将被撤销,并生成一条相同内容的新订单(打手清空)。
+
+
+ 取消
+ {{isGhDashouLoading ? '处理中' : '确认更换'}}
+
+
+
+
+
+
+
+
+ 申请罚款×
+
+ 罚款原因
+
+ 罚款金额
+
+
+ 是否影响抢单
+
+
+
+
+ 此次罚款应得分红:¥{{jisuanFenhong()}}(利率 {{fenhongLilv*100}}%)
+
+
+ 取消
+ {{isFakuanLoading ? '处理中' : '确认申请'}}
+
+
+
+
+
+
+
+
+ 确认结算×
+
+ 确认结算该订单吗?结算后状态变为“已完成”。
+
+
+ 取消
+ {{isJiesuanLoading ? '处理中' : '确认结算'}}
+
+
+
+
+
+
+
+
+ 修改罚款×
+
+ 罚款理由
+
+ 罚款金额
+
+
+
+ 取消
+ 确认修改
+
+
+
+
+
+
+
+
+ 撤销罚款×
+
+ 驳回理由
+
+
+
+ 取消
+ 确认撤销
+
+
+
+
+
+
+
+
+ 再次申请罚款×
+
+ 罚款理由
+
+ 罚款金额
+
+
+
+ 取消
+ 确认申请
+
+
+
+
+
+
+
+
+ 撤销退款申请×
+
+ 撤销理由
+
+
+
+ 取消
+ 确认撤销
+
+
+
+
+
+
+
+
+ 修改退款理由×
+
+ 退款理由
+
+
+
+ 取消
+ 确认修改
+
+
+
+
+
+
+
+
+
+ 教程
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/sjddxq/sjddxq.wxss b/miniprogram/pages/sjddxq/sjddxq.wxss
new file mode 100644
index 0000000..3f8b6b0
--- /dev/null
+++ b/miniprogram/pages/sjddxq/sjddxq.wxss
@@ -0,0 +1,185 @@
+/* pages/sjddxq/sjddxq.wxss - 商家订单详情页样式(正方形九宫格,圆形头像) */
+/* 页面全局背景 */
+page { background: #f5f7fa; }
+/* 页面底部留出固定栏的空间 */
+.sjddxq-page { padding-bottom: 160rpx; }
+
+/* 加载状态居中布局 */
+.loading-container { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 60vh; }
+/* 加载旋转动画图标 */
+.loading-spinner { width: 64rpx; height: 64rpx; border: 6rpx solid #e0e0e0; border-top-color: #4caf50; border-radius: 50%; animation: spin 1s linear infinite; margin-bottom: 20rpx; }
+@keyframes spin { to { transform: rotate(360deg); } }
+/* 加载文字 */
+.loading-text { color: #666; font-size: 28rpx; }
+
+/* 内容区域的内边距 */
+.content-container { padding: 24rpx; }
+
+/* 通用卡片样式 */
+.card { background: #fff; border-radius: 24rpx; padding: 28rpx; margin-bottom: 24rpx; box-shadow: 0 8rpx 24rpx rgba(0,0,0,0.04); }
+/* 卡片标题样式 */
+.card-title { font-size: 32rpx; font-weight: 600; color: #1a1a1a; margin-bottom: 20rpx; padding-bottom: 12rpx; border-bottom: 1rpx solid #f0f0f0; }
+
+/* 商品卡片:左右布局 */
+.shangpin-card { display: flex; align-items: center; }
+/* 商品图片:固定宽高为正方形 */
+.shangpin-img { width: 180rpx; height: 180rpx; border-radius: 16rpx; background: #f5f5f5; margin-right: 24rpx; flex-shrink: 0; }
+.shangpin-text { flex: 1; }
+.jieshao-text { font-size: 28rpx; color: #333; line-height: 1.6; }
+
+/* 信息行:左右分布,底部虚线 */
+.info-row { display: flex; justify-content: space-between; align-items: center; padding: 14rpx 0; border-bottom: 1rpx dashed #f5f5f5; }
+.info-row:last-child { border-bottom: none; }
+.label { font-size: 28rpx; color: #888; min-width: 140rpx; }
+.value { font-size: 28rpx; color: #333; text-align: right; word-break: break-all; }
+.value-with-copy { display: flex; align-items: center; }
+/* 复制按钮 */
+.copy-btn { margin-left: 12rpx; color: #4caf50; font-size: 26rpx; padding: 4rpx 12rpx; background: #e8f5e9; border-radius: 16rpx; }
+/* 红色价格 */
+.price-red { color: #ff4444; font-weight: 600; }
+.zhuangtai-text { font-weight: 600; }
+
+/* 打手卡片头部 */
+.dashou-header { display: flex; align-items: center; }
+/* 打手头像:固定宽高,圆形 */
+.dashou-avatar { width: 88rpx; height: 88rpx; border-radius: 50%; margin-right: 20rpx; }
+.dashou-detail { flex: 1; }
+.dashou-nicheng { font-size: 30rpx; font-weight: 500; margin-bottom: 6rpx; }
+.dashou-id-row { display: flex; align-items: center; font-size: 26rpx; }
+
+/* 服务详情子标题 */
+.sub-title { font-size: 28rpx; color: #555; margin: 16rpx 0 8rpx; }
+/* 内容框(留言、理由等) */
+.content-box { background: #f9f9f9; border-radius: 12rpx; padding: 16rpx; font-size: 26rpx; color: #444; line-height: 1.5; margin-bottom: 16rpx; }
+
+/* ========= 核心:交付图片九宫格,强制正方形 ========= */
+.image-grid { display: flex; flex-wrap: wrap; margin-bottom: 16rpx; }
+/* 每个图片的外层容器 */
+.grid-img-wrapper {
+ width: calc(33.33% - 8rpx); /* 每行三个,减去间距 */
+ margin: 4rpx;
+ position: relative; /* 为绝对定位的图片提供锚点 */
+ overflow: hidden;
+ border-radius: 12rpx;
+}
+/* 使用伪元素撑出 1:1 的正方形高度 */
+.grid-img-wrapper::after {
+ content: '';
+ display: block;
+ padding-bottom: 100%; /* 100% 相对于自身宽度,实现正方形 */
+}
+/* 图片绝对定位填满整个容器 */
+.grid-img {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ object-fit: cover; /* 保持比例裁剪,不变形 */
+}
+
+/* 结算区域:星星评分 */
+.star-row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 20rpx; }
+.stars { display: flex; }
+.star { font-size: 48rpx; color: #ccc; margin-left: 8rpx; transition: all 0.2s; }
+.star-active { color: #ffc107; transform: scale(1.1); }
+/* 留言输入框 */
+.liuyan-input { width: 100%; height: 120rpx; background: #f9f9f9; border-radius: 12rpx; padding: 16rpx; font-size: 28rpx; margin-bottom: 20rpx; }
+/* 结算按钮 */
+.jiesuan-btn { background: #4caf50; color: #fff; text-align: center; padding: 20rpx; border-radius: 48rpx; font-size: 30rpx; font-weight: 600; margin-top: 10rpx; }
+
+/* 底部固定操作栏 */
+.bottom-bar { position: fixed; bottom: 0; left: 0; right: 0; background: #fff; padding: 16rpx 24rpx; box-shadow: 0 -4rpx 20rpx rgba(0,0,0,0.06); display: flex; flex-direction: column; gap: 12rpx; z-index: 100; }
+.bottom-btn-group { display: flex; gap: 16rpx; }
+.btn-outline { flex: 1; text-align: center; padding: 18rpx 0; border-radius: 44rpx; font-size: 28rpx; font-weight: 500; border: 2rpx solid #1976D2; color: #1976D2; background: #fff; }
+.btn-primary { background: #2196F3; color: #fff; flex: 1; text-align: center; padding: 18rpx 0; border-radius: 44rpx; font-weight: 600; font-size: 28rpx; }
+.btn-warn { background: #ff9800; color: #fff; flex: 1; text-align: center; padding: 18rpx 0; border-radius: 44rpx; font-weight: 600; font-size: 28rpx; }
+.btn-danger { background: #f44336; color: #fff; flex: 1; text-align: center; padding: 18rpx 0; border-radius: 44rpx; font-weight: 600; font-size: 28rpx; }
+
+/* 弹窗通用样式 */
+.modal { position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 1000; display: flex; align-items: center; justify-content: center; }
+.modal-mask { position: absolute; width: 100%; height: 100%; background: rgba(0,0,0,0.45); }
+.modal-content { position: relative; width: 620rpx; background: #fff; border-radius: 32rpx; overflow: hidden; }
+.modal-hd { padding: 32rpx; font-size: 34rpx; font-weight: 600; display: flex; justify-content: space-between; border-bottom: 1rpx solid #f0f0f0; }
+.modal-close { color: #999; font-size: 40rpx; }
+.modal-body { padding: 32rpx; }
+.modal-tip { font-size: 28rpx; color: #555; margin-bottom: 12rpx; }
+.modal-input { width: 100%; height: 140rpx; background: #f5f5f5; border-radius: 12rpx; padding: 16rpx; font-size: 28rpx; margin-bottom: 20rpx; }
+.modal-input-num { width: 100%; height: 80rpx; background: #f5f5f5; border-radius: 12rpx; padding: 0 20rpx; font-size: 32rpx; margin-bottom: 20rpx; }
+.toggle-row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20rpx; }
+.toggle { width: 100rpx; height: 50rpx; border-radius: 50rpx; background: #e0e0e0; display: flex; align-items: center; padding: 0 4rpx; transition: background 0.2s; }
+.toggle-on { background: #4caf50; }
+.toggle-knob { width: 42rpx; height: 42rpx; border-radius: 50%; background: #fff; transition: transform 0.2s; }
+.toggle-on .toggle-knob { transform: translateX(50rpx); }
+.fenhong-tip { text-align: center; font-size: 28rpx; color: #ff9800; margin: 16rpx 0 0; }
+.modal-btns { display: flex; border-top: 1rpx solid #f0f0f0; }
+.modal-btn { flex: 1; text-align: center; padding: 28rpx; font-size: 30rpx; font-weight: 500; }
+.modal-btn.cancel { color: #888; background: #f5f5f5; }
+.modal-btn.confirm { color: #4caf50; background: #e8f5e9; }
+
+
+
+
+/* ========== 新增:罚单/订单操作按钮样式 ========== */
+.fakuan-actions, .refund-actions {
+ display: flex;
+ gap: 20rpx;
+ margin-top: 24rpx;
+ padding-top: 16rpx;
+ border-top: 1rpx solid #f0f0f0;
+}
+.action-btn {
+ flex: 1;
+ text-align: center;
+ padding: 16rpx 0;
+ border-radius: 44rpx;
+ font-size: 28rpx;
+ font-weight: 500;
+}
+.action-modify {
+ background: #2196F3;
+ color: #fff;
+}
+.action-cancel {
+ background: #ff9800;
+ color: #fff;
+}
+.action-resubmit {
+ background: #4caf50;
+ color: #fff;
+}
+
+/* ========== 新增:教程按钮样式 ========== */
+.tutorial-btn {
+ position: fixed;
+ right: 0;
+ top: 20%;
+ transform: translateY(-50%);
+ width: 120rpx;
+ height: 120rpx;
+ background: rgba(0, 0, 0, 0.75);
+ backdrop-filter: blur(15rpx);
+ border-radius: 60rpx 0 0 60rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 10001;
+ box-shadow: -8rpx 0 20rpx rgba(0,0,0,0.3);
+ transition: right 0.3s ease-out;
+ border: 2rpx solid rgba(255,215,0,0.5);
+ border-right: none;
+}
+.tutorial-btn-text {
+ color: #FFD700;
+ font-size: 36rpx;
+ font-weight: bold;
+ writing-mode: vertical-rl;
+ letter-spacing: 6rpx;
+}
+.tutorial-btn-hidden {
+ right: -80rpx;
+}
+.tutorial-btn-hover {
+ background: rgba(0, 0, 0, 0.9);
+ transform: translateY(-50%) scale(1.05);
+}
\ No newline at end of file
diff --git a/miniprogram/pages/sjdingdan/sjdingdan.js b/miniprogram/pages/sjdingdan/sjdingdan.js
new file mode 100644
index 0000000..a0fd572
--- /dev/null
+++ b/miniprogram/pages/sjdingdan/sjdingdan.js
@@ -0,0 +1,323 @@
+// pages/sjdingdan/sjdingdan.js
+const app = getApp();
+import request from '../../utils/request.js';
+
+Page({
+ data: {
+ // 商品类型(和打手端用同一个接口)
+ shangpinleixing: [],
+ xuanzhongLeixingId: null,
+
+ // 搜索关键字(模糊搜索)
+ searchKeyword: '',
+
+ // 状态列表(含全部)
+ statusList: [
+ { name: '全部', key: 'all', zhuangtaiList: [1, 2, 3, 4, 5, 6, 7, 8], 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',
+
+ // 每个状态独立的数据集
+ sjDingdanShuju: {
+ 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 || '',
+
+ // 待结算数量
+ pendingCount: 0,
+ },
+
+ onLoad() {
+ wx.setNavigationBarTitle({ title: '我的派单' });
+ this.loadShangpinLeixing();
+ this.registerNotificationComponent();
+ },
+
+ onShow() {
+ this.registerNotificationComponent();
+ this.loadPendingCount();
+ // 如果已加载过商品类型,刷新当前状态的订单
+ 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()
+ };
+ }
+ },
+
+ // 🆕 加载商品类型(和打手端用同一个接口 /dingdan/dsqdhqddlx)
+ 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;
+ // 拼接图片完整URL
+ 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);
+ },
+
+ // 搜索输入(防抖)
+ 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);
+ },
+
+ // 切换状态Tab
+ switchStatus(e) {
+ const key = e.currentTarget.dataset.key;
+ if (key === this.data.currentStatusKey) return;
+ this.setData({ currentStatusKey: key });
+ const tabData = this.data.sjDingdanShuju[key];
+ // 如果该状态没有数据,则加载;否则只刷新视图
+ if (tabData.list.length === 0 && tabData.hasMore && !tabData.isLoading) {
+ this.loadCurrentStatusOrders(true);
+ } else {
+ this.refreshCurrentListView();
+ }
+ },
+
+ // 🆕 加载订单(核心方法,接口固定用商家订单接口 /dingdan/sjdingdanhq)
+ async loadCurrentStatusOrders(isRefresh = false) {
+ const key = this.data.currentStatusKey;
+ const tabData = this.data.sjDingdanShuju[key];
+ if (tabData.isLoading) return;
+ const page = isRefresh ? 1 : tabData.page;
+
+ this.setData({
+ [`sjDingdanShuju.${key}.isLoading`]: true,
+ isLoading: true,
+ isLoadingMore: !isRefresh
+ });
+
+ try {
+ const statusItem = this.data.statusList.find(s => s.key === key);
+ const zhuangtaiList = statusItem.zhuangtaiList;
+
+ // 构建请求参数
+ 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: '/dingdan/sjdingdanhq', // 商家订单接口(和原来一样)
+ 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;
+
+ // 处理数据:拼接图片、转换状态中文
+ 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.sjDingdanShuju[key].list;
+ const updatedList = isRefresh ? processed : [...currentList, ...processed];
+
+ this.setData({
+ [`sjDingdanShuju.${key}.list`]: updatedList,
+ [`sjDingdanShuju.${key}.page`]: page,
+ [`sjDingdanShuju.${key}.hasMore`]: hasMore,
+ [`sjDingdanShuju.${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({
+ [`sjDingdanShuju.${key}.isLoading`]: false,
+ isLoading: false,
+ isLoadingMore: false,
+ scrollViewRefreshing: false
+ });
+ this.refreshCurrentListView();
+ }
+ },
+
+ // 刷新当前视图
+ refreshCurrentListView() {
+ const key = this.data.currentStatusKey;
+ const tabData = this.data.sjDingdanShuju[key];
+ this.setData({
+ currentList: tabData.list,
+ hasMore: tabData.hasMore,
+ isLoading: tabData.isLoading
+ });
+ },
+
+ // 重置当前状态数据
+ resetCurrentStatusData() {
+ const key = this.data.currentStatusKey;
+ this.setData({
+ [`sjDingdanShuju.${key}.list`]: [],
+ [`sjDingdanShuju.${key}.page`]: 1,
+ [`sjDingdanShuju.${key}.hasMore`]: true,
+ [`sjDingdanShuju.${key}.isLoading`]: false
+ });
+ this.refreshCurrentListView();
+ },
+
+ // 🆕 加载待结算数量
+ async loadPendingCount() {
+ try {
+ const res = await request({
+ url: '/dingdan/sjdingdanhq',
+ method: 'POST',
+ data: {
+ zhuangtai_list: [8],
+ page: 1,
+ page_size: 1
+ }
+ });
+ if (res && res.data.code === 0) {
+ this.setData({ pendingCount: res.data.data.pending_count || 0 });
+ }
+ } catch (error) {
+ console.error('加载待结算数量失败:', error);
+ }
+ },
+
+ // 下拉刷新(由 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.sjDingdanShuju[key];
+ if (tabData.isLoading || !tabData.hasMore) return;
+ this.setData({ [`sjDingdanShuju.${key}.page`]: tabData.page + 1 });
+ this.loadCurrentStatusOrders(false);
+ },
+
+ // 🆕 手动点击"加载更多"按钮
+ onLoadMoreTap() {
+ const key = this.data.currentStatusKey;
+ const tabData = this.data.sjDingdanShuju[key];
+ if (tabData.isLoading || !tabData.hasMore) return;
+ this.setData({ [`sjDingdanShuju.${key}.page`]: tabData.page + 1 });
+ this.loadCurrentStatusOrders(false);
+ },
+
+ // 状态码转中文
+ getZhuangtaiZh(zhuangtai) {
+ const map = {
+ 1: '已下单', 2: '进行中', 3: '已完成',
+ 4: '退款中', 5: '已退款', 6: '退款失败',
+ 7: '指定中', 8: '结算中'
+ };
+ return map[zhuangtai] || '未知状态';
+ },
+
+ // 跳转订单详情
+ goToSjDingdanXiangqing(e) {
+ const item = e.currentTarget.dataset.item;
+ if (!item) return;
+ const dataStr = encodeURIComponent(JSON.stringify(item));
+ wx.navigateTo({
+ url: `/pages/sjddxq/sjddxq?dingdanData=${dataStr}`
+ });
+ },
+
+ // 图片加载失败
+ onImageError() {}
+});
\ No newline at end of file
diff --git a/miniprogram/pages/sjdingdan/sjdingdan.json b/miniprogram/pages/sjdingdan/sjdingdan.json
new file mode 100644
index 0000000..d7516ff
--- /dev/null
+++ b/miniprogram/pages/sjdingdan/sjdingdan.json
@@ -0,0 +1,9 @@
+{
+ "navigationBarTitleText": "我的派单",
+ "enablePullDownRefresh": false,
+ "backgroundTextStyle": "dark",
+ "backgroundColor": "#f8f9fa",
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification"
+ }
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/sjdingdan/sjdingdan.wxml b/miniprogram/pages/sjdingdan/sjdingdan.wxml
new file mode 100644
index 0000000..0d36a72
--- /dev/null
+++ b/miniprogram/pages/sjdingdan/sjdingdan.wxml
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+
+
+
+ {{item.jieshao || '类型'}}
+
+
+
+
+
+
+
+
+
+
+
+ ✕
+
+
+
+
+
+
+ 待结算订单
+ {{pendingCount}}
+
+
+
+
+
+
+
+
+ {{item.name}}
+
+
+ {{pendingCount}}
+
+
+
+
+
+
+
+
+ ⚡ 正在刷新...
+ ▼ 下拉刷新
+
+
+
+
+
+
+
+ 加载订单中...
+
+
+
+
+
+ 暂无订单
+ 快去派发订单吧
+
+
+
+
+
+
+ {{item.create_time}}
+
+
+
+
+ {{item.jieshao || '暂无描述'}}
+ ID: {{item.dingdan_id}}
+ 昵称: {{item.nicheng}}
+
+
+ {{item.zhuangtaiZh}}
+
+ ¥
+ {{item.jine || '0.00'}}
+
+
+
+
+
+
+
+
+ 上拉加载更多
+
+
+
+
+
+ 加载中...
+
+
+
+
+
+
+
+
+
+ —— 没有更多了 ——
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/sjdingdan/sjdingdan.wxss b/miniprogram/pages/sjdingdan/sjdingdan.wxss
new file mode 100644
index 0000000..2e85ea1
--- /dev/null
+++ b/miniprogram/pages/sjdingdan/sjdingdan.wxss
@@ -0,0 +1,430 @@
+/* pages/sjdingdan/sjdingdan.wxss */
+
+/* 页面容器:禁止整体滚动 */
+page {
+ height: 100%;
+ overflow: hidden;
+ }
+ .sj-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;
+ }
+ .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;
+ }
+
+ /* ========== 待结算提示条(保留原样式) ========== */
+ .sj-pending-tip {
+ margin: 20rpx 24rpx;
+ padding: 16rpx 24rpx;
+ background: linear-gradient(145deg, #ffe8cc, #ffdbb5);
+ border-left: 8rpx solid #ffaa00;
+ border-radius: 16rpx;
+ display: flex;
+ align-items: center;
+ gap: 16rpx;
+ box-shadow: 0 8rpx 20rpx rgba(255,170,0,0.3);
+ flex-shrink: 0;
+ }
+ .sj-pending-icon {
+ font-size: 36rpx;
+ color: #ffaa00;
+ }
+ .sj-pending-text {
+ font-size: 28rpx;
+ color: #b25700;
+ font-weight: 600;
+ }
+ .sj-pending-badge {
+ min-width: 44rpx;
+ height: 44rpx;
+ padding: 0 12rpx;
+ background: linear-gradient(145deg, #ff3b3b, #d10000);
+ color: #fff;
+ font-size: 26rpx;
+ font-weight: 700;
+ line-height: 44rpx;
+ text-align: center;
+ border-radius: 22rpx;
+ margin-left: auto;
+ box-shadow: 0 4rpx 12rpx rgba(255,0,0,0.5);
+ }
+
+ /* ========== 主体布局 ========== */
+ .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;
+ position: relative;
+ }
+ .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;
+ }
+ /* 结算中状态的小红点数字 */
+ .sj-status-badge {
+ min-width: 36rpx;
+ height: 36rpx;
+ padding: 0 8rpx;
+ background: linear-gradient(145deg, #ff4d4f, #d10000);
+ color: #fff;
+ font-size: 22rpx;
+ font-weight: 700;
+ line-height: 36rpx;
+ text-align: center;
+ border-radius: 18rpx;
+ margin-left: 8rpx;
+ box-shadow: 0 4rpx 8rpx rgba(255,0,0,0.4);
+ }
+
+ /* 右侧包裹 */
+ .right-wrapper {
+ flex: 1;
+ padding-right: 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;
+ }
+
+ /* 内容容器 */
+ .list-inner {
+ padding: 24rpx 0 200rpx 20rpx;
+ box-sizing: border-box;
+ }
+
+ /* ========== 订单卡片 ========== */
+ .order-card {
+ 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%;
+ display: flex;
+ flex-direction: column;
+ }
+ /* 结算中卡片特殊边框 */
+ .sj-card-jiesuan {
+ border: 2rpx solid #ff5722;
+ box-shadow: 0 6rpx 20rpx rgba(255,87,34,0.15);
+ }
+ .order-card:active {
+ transform: scale(0.98);
+ }
+
+ /* 创建时间 */
+ .card-time {
+ font-size: 24rpx;
+ color: #999;
+ margin-bottom: 12rpx;
+ padding-bottom: 8rpx;
+ border-bottom: 1rpx dashed rgba(0,120,255,0.15);
+ }
+
+ /* 内容行 */
+ .card-content-row {
+ display: flex;
+ align-items: flex-start;
+ }
+
+ /* 商品图片 */
+ .card-img {
+ width: 130rpx;
+ height: 130rpx;
+ border-radius: 18rpx;
+ background: #f0f0f0;
+ margin-right: 24rpx;
+ flex-shrink: 0;
+ }
+
+ /* 中间信息区 */
+ .card-info {
+ flex: 1;
+ min-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: 6rpx;
+ }
+ .card-nicheng {
+ font-size: 22rpx;
+ color: #666;
+ margin-top: 4rpx;
+ }
+
+ /* 右侧状态和价格 */
+ .card-right {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ justify-content: center;
+ min-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;
+ }
+ /* 结算中状态红色高亮(保留原样式) */
+ .sj-status-jiesuan {
+ background: linear-gradient(145deg, #ff4d4f, #d10000) !important;
+ color: #fff !important;
+ border-color: #ffaa00;
+ box-shadow: 0 0 15rpx #ff4d4f;
+ }
+ .card-price {
+ display: flex;
+ align-items: baseline;
+ white-space: nowrap;
+ }
+ .price-symbol {
+ font-size: 28rpx;
+ color: #ffaa00;
+ font-weight: 700;
+ margin-right: 4rpx;
+ }
+ .price-number {
+ font-size: 36rpx;
+ font-weight: 700;
+ color: #333;
+ }
+
+ /* ========== 加载与空状态 ========== */
+ .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;
+ }
+ .empty-subtext {
+ font-size: 26rpx;
+ color: #b0b0b0;
+ margin-top: 10rpx;
+ }
+
+ .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;
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/sjpaidan/sjpaidan.js b/miniprogram/pages/sjpaidan/sjpaidan.js
new file mode 100644
index 0000000..469921c
--- /dev/null
+++ b/miniprogram/pages/sjpaidan/sjpaidan.js
@@ -0,0 +1,363 @@
+// pages/sjpaidan/sjpaidan.js
+import request from '../../utils/request.js'
+import PopupService from '../../services/popupService.js'
+import { showConfirmByScene } from '../../utils/scriptService.js'
+
+Page({
+ data: {
+ sjyue: '0.00',
+ shangpinList: [],
+ selectedTypeId: null,
+ selectedHuiyuanId: null,
+ selectedYaoqiuleixing: null,
+ currentTypeChenghaoList: [],
+
+ dingdanJieshao: '',
+ dingdanBeizhu: '',
+ laobanName: '',
+ zhidingUid: '',
+ jiage: '',
+
+ selectedLabelId: '',
+ selectedLabelName: '',
+
+ commissionEnabled: false,
+ commissionValue: '',
+
+ isSpecialType: false,
+ currentTypeGoodsList: [],
+ selectedGoodsId: null,
+ selectedGoodsTitle: '',
+ commissionLocked: false,
+
+ isLoading: false,
+ isSubmitting: false,
+ tutorialBtnHidden: false,
+ bgImageUrl: ''
+ },
+
+ onLoad() {
+ this.loadBgImage()
+ this.loadShangjiaYue()
+ this.loadShangpinTypes()
+ this.startTutorialBtnTimer()
+ },
+
+ onShow() {
+ this.loadShangjiaYue()
+ this.loadBgImage()
+ this.registerNotificationComponent()
+ this.startTutorialBtnTimer()
+ },
+
+ onHide() {
+ PopupService.reset()
+ },
+
+ onUnload() {
+ if (this.tutorialTimer) clearTimeout(this.tutorialTimer)
+ PopupService.reset()
+ },
+
+ loadBgImage() {
+ const app = getApp()
+ const ossBase = app.globalData.ossImageUrl || 'https://your-oss-bucket.oss-cn-region.aliyuncs.com'
+ const bgUrl = ossBase.replace(/\/$/, '') + '/beijing/shangjiapaidan/bg.jpg'
+ this.setData({ bgImageUrl: bgUrl })
+ },
+
+ loadShangjiaYue() {
+ const app = getApp()
+ const shangjiaData = app.globalData.shangjia || {}
+ this.setData({ sjyue: shangjiaData.sjyue || '0.00' })
+ },
+
+ async loadShangpinTypes() {
+ this.setData({ isLoading: true })
+ try {
+ const res = await request({
+ url: '/dingdan/sjspleixing',
+ method: 'POST'
+ })
+ if (res && res.data.code === 200) {
+ let list = res.data.data || []
+ list.reverse()
+ this.setData({ shangpinList: list, isLoading: false })
+ if (list.length > 0) {
+ this.setSelectedType(list[0])
+ }
+ } else {
+ throw new Error(res.data.msg || '加载失败')
+ }
+ } catch (error) {
+ this.setData({ isLoading: false })
+ wx.showToast({ title: '加载类型失败', icon: 'error' })
+ }
+ },
+
+ // 🔴 切换类型时清空订单介绍、备注、标签、商品模板和押金
+ setSelectedType(item) {
+ const isSpecial = item.is_special_type || false
+
+ this.setData({
+ selectedTypeId: item.id,
+ selectedHuiyuanId: item.huiyuan_id,
+ selectedYaoqiuleixing: item.yaoqiuleixing,
+ currentTypeChenghaoList: item.chenghaoList || [],
+ selectedLabelId: '',
+ selectedLabelName: '',
+ selectedGoodsId: null,
+ selectedGoodsTitle: '',
+ isSpecialType: isSpecial,
+ currentTypeGoodsList: item.goodsList || [],
+ // 清空订单介绍和备注
+ dingdanJieshao: '',
+ dingdanBeizhu: '',
+ // 重置押金,等用户选商品再设置
+ commissionEnabled: false,
+ commissionValue: '',
+ commissionLocked: false
+ })
+ },
+
+ onGoodsItemTap(e) {
+ const goodsId = e.currentTarget.dataset.id
+ if (!goodsId) return
+
+ const goods = this.data.currentTypeGoodsList.find(item => item.id == goodsId)
+ if (!goods) {
+ wx.showToast({ title: '商品数据异常', icon: 'none' })
+ return
+ }
+
+ const hasYongjin = goods.yongjin && parseFloat(goods.yongjin) > 0
+ this.setData({
+ selectedGoodsId: goods.id,
+ selectedGoodsTitle: goods.biaoqian || '',
+ dingdanJieshao: goods.jieshao || '',
+ dingdanBeizhu: '',
+ commissionEnabled: hasYongjin,
+ commissionValue: hasYongjin ? goods.yongjin : '',
+ commissionLocked: true
+ })
+ },
+
+ onDingdanJieshaoInput(e) {
+ if (this.data.isSpecialType) return
+ this.setData({ dingdanJieshao: e.detail.value })
+ },
+ onDingdanBeizhuInput(e) {
+ if (this.data.isSpecialType) return
+ this.setData({ dingdanBeizhu: e.detail.value })
+ },
+ onLaobanNameInput(e) { this.setData({ laobanName: e.detail.value }) },
+ onZhidingUidInput(e) { this.setData({ zhidingUid: e.detail.value }) },
+ onJiageInput(e) {
+ let value = e.detail.value
+ if (value === '' || /^\d+(\.\d{0,2})?$/.test(value)) {
+ this.setData({ jiage: value })
+ }
+ },
+
+ onLabelChange(e) {
+ const idx = e.detail.value
+ const label = this.data.currentTypeChenghaoList[idx]
+ if (label) {
+ this.setData({ selectedLabelId: label.id, selectedLabelName: label.mingcheng })
+ } else {
+ this.setData({ selectedLabelId: '', selectedLabelName: '' })
+ }
+ },
+
+ onCommissionToggle() {
+ if (this.data.commissionLocked) return
+ this.setData({ commissionEnabled: !this.data.commissionEnabled })
+ },
+ onCommissionValueInput(e) {
+ if (this.data.commissionLocked) return
+ let value = e.detail.value
+ if (value === '' || /^\d+(\.\d{0,2})?$/.test(value)) {
+ this.setData({ commissionValue: value })
+ }
+ },
+
+ validateForm() {
+ const {
+ selectedTypeId, dingdanJieshao, laobanName, jiage,
+ sjyue, commissionEnabled, commissionValue,
+ isSpecialType, selectedGoodsId
+ } = this.data
+
+ if (!selectedTypeId) {
+ wx.showToast({ title: '请选择订单类型', icon: 'none' })
+ return false
+ }
+ if (isSpecialType && !selectedGoodsId) {
+ wx.showToast({ title: '请选择一个商品模板', icon: 'none' })
+ return false
+ }
+ if (!dingdanJieshao.trim()) {
+ wx.showToast({ title: '请输入订单介绍', icon: 'none' })
+ return false
+ }
+ if (!laobanName.trim()) {
+ wx.showToast({ title: '请输入老板游戏昵称', icon: 'none' })
+ return false
+ }
+ if (!jiage) {
+ wx.showToast({ title: '请输入订单价格', icon: 'none' })
+ return false
+ }
+
+ const price = parseFloat(jiage)
+ if (isNaN(price) || price < 0.1 || price > 10000) {
+ wx.showToast({ title: '价格需在0.1-10000元之间', icon: 'none' })
+ return false
+ }
+ if (jiage.includes('.') && jiage.split('.')[1].length > 2) {
+ wx.showToast({ title: '最多支持两位小数', icon: 'none' })
+ return false
+ }
+
+ if (commissionEnabled) {
+ const com = parseFloat(commissionValue)
+ if (isNaN(com) || com <= 0) {
+ wx.showToast({ title: '请输入有效的佣金金额', icon: 'none' })
+ return false
+ }
+ if (com > 50000) {
+ wx.showToast({ title: '佣金建议不超过50000元', icon: 'none' })
+ return false
+ }
+ }
+
+ const balance = parseFloat(sjyue)
+ if (price > balance) {
+ wx.showModal({
+ title: '余额不足',
+ content: `当前余额${sjyue}元,需要${jiage}元。请先充值。`,
+ confirmText: '去充值',
+ success: (res) => {
+ if (res.confirm) {
+ wx.navigateTo({ url: '/pages/sjchongzhi/sjchongzhi' })
+ }
+ }
+ })
+ return false
+ }
+
+ return true
+ },
+
+ resetForm() {
+ this.setData({
+ dingdanJieshao: '',
+ dingdanBeizhu: '',
+ laobanName: '',
+ zhidingUid: '',
+ jiage: '',
+ selectedLabelId: '',
+ selectedLabelName: '',
+ selectedGoodsId: null,
+ selectedGoodsTitle: '',
+ commissionEnabled: false,
+ commissionValue: ''
+ })
+ const currentType = this.data.shangpinList.find(t => t.id === this.data.selectedTypeId)
+ if (currentType) this.setSelectedType(currentType)
+ },
+
+ async onSubmit() {
+ if (this.data.isSubmitting) return
+ if (!this.validateForm()) return
+ showConfirmByScene('merchant_dispatch_confirm', () => this.doSubmit())
+ },
+
+ async doSubmit() {
+ if (this.data.isSubmitting) return
+ this.setData({ isSubmitting: true })
+
+ const postData = {
+ shangpinTypeId: this.data.selectedTypeId,
+ huiyuanId: this.data.selectedHuiyuanId,
+ yaoqiuleixing: this.data.selectedYaoqiuleixing,
+ dingdanJieshao: this.data.dingdanJieshao.trim(),
+ dingdanBeizhu: this.data.dingdanBeizhu.trim() || '',
+ laobanName: this.data.laobanName.trim(),
+ zhidingUid: this.data.zhidingUid.trim() || '',
+ jiage: this.data.jiage,
+ labelId: this.data.selectedLabelId || '',
+ labelName: this.data.selectedLabelName || '',
+ commissionEnabled: this.data.commissionEnabled,
+ commissionValue: this.data.commissionEnabled ? this.data.commissionValue : '',
+ goodsId: this.data.selectedGoodsId || ''
+ }
+
+ try {
+ const res = await request({
+ url: '/dingdan/sjpaifa',
+ method: 'POST',
+ data: postData
+ })
+ if (res && res.data.code === 200) {
+ wx.showToast({ title: '派单成功', icon: 'success' })
+ const orderAmount = parseFloat(this.data.jiage)
+ const app = getApp()
+ if (app.globalData.shangjia) {
+ const newBalance = parseFloat(app.globalData.shangjia.sjyue || '0') - orderAmount
+ app.globalData.shangjia.sjyue = newBalance.toFixed(2)
+ app.globalData.shangjia.fadanzong = (parseInt(app.globalData.shangjia.fadanzong || '0') + 1).toString()
+ app.globalData.shangjia.riliushui = (parseFloat(app.globalData.shangjia.riliushui || '0') + orderAmount).toFixed(2)
+ app.globalData.shangjia.yueliushui = (parseFloat(app.globalData.shangjia.yueliushui || '0') + orderAmount).toFixed(2)
+ this.setData({ sjyue: app.globalData.shangjia.sjyue })
+ }
+ setTimeout(() => {
+ this.resetForm()
+ this.setData({ isSubmitting: false })
+ }, 1500)
+ } else {
+ wx.showModal({ title: '派单失败', content: res.data.msg || '请稍后重试', showCancel: false })
+ this.setData({ isSubmitting: false })
+ }
+ } catch (error) {
+ wx.showModal({ title: '请求失败', content: error.message || '网络错误', showCancel: false })
+ this.setData({ isSubmitting: false })
+ }
+ },
+
+ onSelectType(e) {
+ const { item } = e.currentTarget.dataset
+ this.setSelectedType(item)
+ },
+
+ 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()
+ }
+ }
+ },
+
+ startTutorialBtnTimer() {
+ if (this.tutorialTimer) clearTimeout(this.tutorialTimer)
+ this.tutorialTimer = setTimeout(() => {
+ this.setData({ tutorialBtnHidden: true })
+ }, 5000)
+ },
+
+ resetTutorialBtn() {
+ if (this.data.tutorialBtnHidden) this.setData({ tutorialBtnHidden: false })
+ if (this.tutorialTimer) clearTimeout(this.tutorialTimer)
+ this.tutorialTimer = setTimeout(() => {
+ this.setData({ tutorialBtnHidden: true })
+ }, 5000)
+ },
+
+ async onTutorialBtnTap() {
+ this.resetTutorialBtn()
+ PopupService.checkAndShow(this, 'sjpd1')
+ }
+})
\ No newline at end of file
diff --git a/miniprogram/pages/sjpaidan/sjpaidan.json b/miniprogram/pages/sjpaidan/sjpaidan.json
new file mode 100644
index 0000000..484db30
--- /dev/null
+++ b/miniprogram/pages/sjpaidan/sjpaidan.json
@@ -0,0 +1,7 @@
+{
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification"
+ },
+ "navigationBarTitleText": "商家派单"
+
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/sjpaidan/sjpaidan.wxml b/miniprogram/pages/sjpaidan/sjpaidan.wxml
new file mode 100644
index 0000000..574e914
--- /dev/null
+++ b/miniprogram/pages/sjpaidan/sjpaidan.wxml
@@ -0,0 +1,164 @@
+
+
+
+ 可用余额
+
+ {{sjyue}}
+ 元
+
+ 派单将从此余额扣除
+
+
+
+ 选择订单类型
+
+
+
+ {{item.jieshao}}
+
+
+
+
+
+
+ 选择商品模板(点击直接选中)
+
+
+
+ {{item.biaoqian}}
+ 点击选中
+
+
+
+
+
+
+
+ 订单介绍
+
+ {{dingdanJieshao}}
+
+
+ {{dingdanJieshao.length}}/50
+
+
+
+ 订单备注(选填)
+
+ {{dingdanBeizhu.length}}/50
+
+
+
+ 老板昵称/ID
+
+ {{laobanName.length}}/15
+
+
+
+ 指定UID(选填)
+
+
+
+
+ 订单价格(元)
+
+ 范围:0.1 - 10000元
+
+
+
+ 要求打手标签*如金牌打手(选填)
+
+
+ {{selectedLabelName || '无'}}
+
+
+
+
+
+ 要求打手押金(建议≤50元)
+
+
+
+
+
+
+
+ {{isSubmitting ? '派发中...' : '立即派发'}}
+
+
+
+
+
+
+ 加载中...
+
+
+
+
+
+
+ 教程
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/sjpaidan/sjpaidan.wxss b/miniprogram/pages/sjpaidan/sjpaidan.wxss
new file mode 100644
index 0000000..6e051ac
--- /dev/null
+++ b/miniprogram/pages/sjpaidan/sjpaidan.wxss
@@ -0,0 +1,437 @@
+/* pages/sjpaidan/sjpaidan.wxss - 黑金升级版 + 背景图由js动态拼接 */
+page {
+ background-color: #0a0a0f;
+ }
+
+ .page-container {
+ min-height: 100vh;
+ background-size: cover;
+ background-position: center;
+ background-attachment: fixed;
+ position: relative;
+ padding: 30rpx;
+ box-sizing: border-box;
+ padding-bottom: 140rpx;
+ }
+
+ .page-container::before {
+ content: '';
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(10, 10, 15, 0.75);
+ backdrop-filter: blur(6rpx);
+ z-index: 0;
+ pointer-events: none;
+ }
+
+ .balance-card,
+ .section,
+ .form-container,
+ .btn-container,
+ .loading-mask {
+ position: relative;
+ z-index: 1;
+ }
+
+ .balance-card {
+ background: linear-gradient(135deg, rgba(20,20,30,0.95), rgba(30,30,50,0.95));
+ border-radius: 24rpx;
+ padding: 36rpx;
+ margin-bottom: 40rpx;
+ color: #fff;
+ box-shadow: 0 15rpx 35rpx rgba(0,0,0,0.4), 0 0 40rpx rgba(0,180,255,0.15);
+ text-align: center;
+ border: 1.5rpx solid rgba(0,180,255,0.2);
+ backdrop-filter: blur(10rpx);
+ overflow: hidden;
+ }
+ .balance-card::before {
+ content: '';
+ position: absolute;
+ top: -50%; left: -50%;
+ width: 200%; height: 200%;
+ background: linear-gradient(45deg, transparent 30%, rgba(0,180,255,0.1) 50%, transparent 70%);
+ animation: cardShine 6s infinite linear;
+ z-index: 1;
+ }
+ @keyframes cardShine {
+ 0% { transform: translateX(-100%) translateY(-100%) rotate(45deg); }
+ 100% { transform: translateX(100%) translateY(100%) rotate(45deg); }
+ }
+ .balance-label {
+ font-size: 28rpx;
+ color: rgba(255,255,255,0.8);
+ margin-bottom: 18rpx;
+ font-weight: 500;
+ position: relative;
+ z-index: 2;
+ }
+ .balance-amount {
+ display: flex;
+ align-items: baseline;
+ justify-content: center;
+ margin-bottom: 15rpx;
+ position: relative;
+ z-index: 2;
+ }
+ .amount-value {
+ font-size: 64rpx;
+ font-weight: bold;
+ color: #FFD700;
+ text-shadow: 0 0 20rpx rgba(255,215,0,0.7);
+ animation: amountGlow 3s infinite alternate;
+ }
+ @keyframes amountGlow {
+ 0% { color: #FFD700; text-shadow: 0 0 15rpx rgba(255,215,0,0.6); }
+ 100% { color: #FFE44D; text-shadow: 0 0 25rpx rgba(255,215,0,0.9); }
+ }
+ .amount-unit {
+ font-size: 36rpx;
+ color: #FFD700;
+ margin-left: 12rpx;
+ font-weight: 500;
+ }
+ .balance-tip {
+ font-size: 24rpx;
+ color: rgba(255,255,255,0.6);
+ position: relative;
+ z-index: 2;
+ }
+
+ .section {
+ margin-bottom: 35rpx;
+ }
+ .section-title {
+ font-size: 32rpx;
+ font-weight: 700;
+ color: #FFFFFF;
+ margin-bottom: 25rpx;
+ padding-left: 12rpx;
+ border-left: 6rpx solid #9C27B0;
+ text-shadow: 0 2rpx 4rpx rgba(0,0,0,0.3);
+ }
+
+ .type-scroll {
+ white-space: nowrap;
+ display: flex;
+ height: 90rpx;
+ }
+ .type-item {
+ display: inline-flex;
+ flex-shrink: 0;
+ align-items: center;
+ justify-content: center;
+ height: 85rpx;
+ padding: 0 40rpx;
+ margin-right: 20rpx;
+ background: rgba(255,255,255,0.08);
+ border-radius: 45rpx;
+ border: 2rpx solid rgba(255,255,255,0.15);
+ font-size: 28rpx;
+ color: rgba(255,255,255,0.8);
+ transition: all 0.3s cubic-bezier(0.175,0.885,0.32,1.275);
+ backdrop-filter: blur(10rpx);
+ }
+ .type-active {
+ background: linear-gradient(135deg, #9C27B0 0%, #673AB7 100%);
+ border-color: #9C27B0;
+ color: white;
+ transform: scale(1.05);
+ box-shadow: 0 10rpx 25rpx rgba(156,39,176,0.4);
+ }
+ .type-text {
+ max-width: 220rpx;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ font-weight: 500;
+ }
+
+ /* 特殊商品模板 */
+ .goods-scroll {
+ white-space: nowrap;
+ display: flex;
+ height: 160rpx;
+ margin-top: 10rpx;
+ }
+ .goods-item {
+ display: inline-flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ width: 200rpx;
+ height: 140rpx;
+ margin-right: 20rpx;
+ background: rgba(255,255,255,0.08);
+ border-radius: 20rpx;
+ border: 2rpx solid rgba(255,255,255,0.15);
+ padding: 10rpx;
+ box-sizing: border-box;
+ transition: all 0.3s;
+ backdrop-filter: blur(10rpx);
+ }
+ .goods-item:active {
+ background: rgba(255,255,255,0.15);
+ transform: scale(0.96);
+ }
+ .goods-selected {
+ border-color: #FFD700;
+ background: rgba(255,215,0,0.15);
+ box-shadow: 0 0 20rpx rgba(255,215,0,0.3);
+ }
+ .goods-title {
+ font-size: 28rpx;
+ color: #fff;
+ font-weight: 500;
+ text-align: center;
+ word-break: break-all;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ overflow: hidden;
+ margin-bottom: 6rpx;
+ line-height: 1.2;
+ }
+ .goods-detail-hint {
+ font-size: 20rpx;
+ color: rgba(255,255,255,0.5);
+ text-align: center;
+ }
+
+ .form-container {
+ background: rgba(30,30,45,0.85);
+ backdrop-filter: blur(20rpx);
+ border-radius: 30rpx;
+ padding: 30rpx;
+ box-shadow: 0 10rpx 30rpx rgba(0,0,0,0.3);
+ border: 1.5rpx solid rgba(255,255,255,0.1);
+ margin-bottom: 40rpx;
+ }
+ .form-item {
+ margin-bottom: 35rpx;
+ position: relative;
+ }
+ .form-item:last-child {
+ margin-bottom: 0;
+ }
+ .form-label {
+ font-size: 30rpx;
+ font-weight: 600;
+ color: rgba(255,255,255,0.9);
+ display: block;
+ margin-bottom: 15rpx;
+ }
+ .form-label.required::after {
+ content: '*';
+ color: #ff4444;
+ margin-left: 8rpx;
+ }
+ .form-input {
+ width: 100%;
+ height: 80rpx;
+ background: rgba(255,255,255,0.1);
+ border-radius: 16rpx;
+ padding: 0 25rpx;
+ font-size: 28rpx;
+ color: #FFFFFF;
+ border: 2rpx solid rgba(255,255,255,0.2);
+ box-sizing: border-box;
+ transition: all 0.3s;
+ }
+ .form-input:focus {
+ border-color: #9C27B0;
+ background: rgba(255,255,255,0.15);
+ }
+ .input-disabled {
+ background: rgba(255,255,255,0.05);
+ color: rgba(255,255,255,0.6);
+ border-color: rgba(255,255,255,0.08);
+ }
+ .word-count {
+ position: absolute;
+ right: 0;
+ top: 0;
+ font-size: 24rpx;
+ color: rgba(255,255,255,0.5);
+ }
+ .price-tip {
+ font-size: 24rpx;
+ color: rgba(255,255,255,0.5);
+ margin-top: 10rpx;
+ display: block;
+ }
+ .picker-view {
+ background: rgba(255,255,255,0.1);
+ border-radius: 16rpx;
+ padding: 20rpx 25rpx;
+ border: 2rpx solid rgba(255,255,255,0.2);
+ color: rgba(255,255,255,0.9);
+ font-size: 28rpx;
+ }
+ .commission-item {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 20rpx;
+ }
+ .commission-item .form-label {
+ width: 100%;
+ margin-bottom: 10rpx;
+ }
+ .commission-input {
+ flex: 1;
+ background: rgba(255,255,255,0.1);
+ border-radius: 16rpx;
+ padding: 20rpx;
+ color: #fff;
+ border: 2rpx solid rgba(255,255,255,0.2);
+ font-size: 28rpx;
+ }
+ .commission-input.input-disabled {
+ background: rgba(255,255,255,0.05);
+ color: rgba(255,255,255,0.6);
+ }
+
+ .btn-container {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background: linear-gradient(to top, rgba(10,10,15,0.95), transparent);
+ padding: 30rpx;
+ box-sizing: border-box;
+ z-index: 9999;
+ }
+ .submit-btn {
+ background: linear-gradient(135deg, #ff4444 0%, #cc0000 100%);
+ border-radius: 50rpx;
+ height: 90rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-shadow: 0 10rpx 30rpx rgba(255,68,68,0.3);
+ transition: all 0.3s;
+ position: relative;
+ overflow: hidden;
+ }
+ .submit-btn::after {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
+ animation: shine 2s infinite;
+ }
+ .btn-hover {
+ transform: translateY(-2rpx);
+ box-shadow: 0 15rpx 40rpx rgba(255,68,68,0.4);
+ }
+ .btn-disabled {
+ opacity: 0.6;
+ filter: grayscale(0.3);
+ pointer-events: none;
+ }
+ .btn-text {
+ font-size: 36rpx;
+ font-weight: bold;
+ color: white;
+ letter-spacing: 4rpx;
+ position: relative;
+ z-index: 2;
+ }
+ @keyframes shine {
+ 0% { left: -100%; }
+ 100% { left: 100%; }
+ }
+
+ .loading-mask {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0,0,0,0.8);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 10000;
+ }
+ .loading-content {
+ background: rgba(40,40,60,0.95);
+ padding: 60rpx 80rpx;
+ border-radius: 30rpx;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ box-shadow: 0 25rpx 70rpx rgba(0,0,0,0.6);
+ }
+ .loading-spinner {
+ width: 90rpx;
+ height: 90rpx;
+ border: 8rpx solid rgba(0,180,255,0.2);
+ border-top: 8rpx solid #00B4FF;
+ border-radius: 50%;
+ animation: spin 1.2s linear infinite;
+ margin-bottom: 35rpx;
+ }
+ .loading-text {
+ font-size: 30rpx;
+ color: #00B4FF;
+ font-weight: 600;
+ }
+ @keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+ }
+
+ .tutorial-btn {
+ position: fixed;
+ right: 0;
+ top: 20%;
+ transform: translateY(-50%);
+ width: 120rpx;
+ height: 120rpx;
+ background: rgba(0, 0, 0, 0.75);
+ backdrop-filter: blur(15rpx);
+ border-radius: 60rpx 0 0 60rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 10001;
+ box-shadow: -8rpx 0 20rpx rgba(0,0,0,0.3);
+ transition: right 0.3s ease-out;
+ border: 2rpx solid rgba(255,215,0,0.5);
+ border-right: none;
+ }
+ .tutorial-btn-text {
+ color: #FFD700;
+ font-size: 36rpx;
+ font-weight: bold;
+ writing-mode: vertical-rl;
+ letter-spacing: 6rpx;
+ }
+ .tutorial-btn-hidden {
+ right: -80rpx;
+ }
+ .tutorial-btn-hover {
+ background: rgba(0, 0, 0, 0.9);
+ transform: translateY(-50%) scale(1.05);
+ }
+
+
+
+
+
+
+ .form-input-scroll {
+ white-space: normal;
+ word-break: break-all;
+ padding: 0 25rpx;
+ box-sizing: border-box;
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/sjpaihang/sjpaihang.json b/miniprogram/pages/sjpaihang/sjpaihang.json
new file mode 100644
index 0000000..1a33122
--- /dev/null
+++ b/miniprogram/pages/sjpaihang/sjpaihang.json
@@ -0,0 +1,12 @@
+{
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification"
+ },
+ "navigationBarTitleText": "商家排行榜",
+ "navigationBarBackgroundColor": "#000000",
+ "navigationBarTextStyle": "white",
+ "enablePullDownRefresh": false,
+ "backgroundColor": "#000000",
+ "backgroundTextStyle": "dark",
+ "onReachBottomDistance": 50
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/sjpaihang/sjpaihang.wxml b/miniprogram/pages/sjpaihang/sjpaihang.wxml
new file mode 100644
index 0000000..8a45da9
--- /dev/null
+++ b/miniprogram/pages/sjpaihang/sjpaihang.wxml
@@ -0,0 +1,2 @@
+
+正在打开排行榜...
diff --git a/miniprogram/pages/sjpaihang/sjpaihang.wxss b/miniprogram/pages/sjpaihang/sjpaihang.wxss
new file mode 100644
index 0000000..0ac9ca5
--- /dev/null
+++ b/miniprogram/pages/sjpaihang/sjpaihang.wxss
@@ -0,0 +1,5 @@
+.redirect-hint {
+ padding: 80rpx;
+ text-align: center;
+ color: #999;
+}
diff --git a/miniprogram/pages/sjxiaoxi/sjxiaoxi.js b/miniprogram/pages/sjxiaoxi/sjxiaoxi.js
new file mode 100644
index 0000000..969ab0f
--- /dev/null
+++ b/miniprogram/pages/sjxiaoxi/sjxiaoxi.js
@@ -0,0 +1,338 @@
+// pages/sjcf/sjcf.js
+import request from '../../utils/request.js'
+const app = getApp()
+const MEIYE_TIAOSHU = 10
+
+Page({
+ data: {
+ zongshu: 0,
+ daichuli: 0,
+ yichuli: 0,
+ shenfen: 0,
+ chufaList: [],
+ dangqianye: 1,
+ haiyougengduo: true,
+ jiazhaozhong: false,
+ jiazhaigengduo: false,
+ scrollHeight: 0,
+ showXiangqing: false,
+ xuanzhongChufa: {},
+ ossImageUrl: '',
+ fangdouTimer: null
+ },
+
+ onLoad(options) {
+ this.registerNotificationComponent()
+ this.initOSSUrl()
+ this.jisuanGaodu()
+ this.chushihuaShuju()
+ },
+
+ onShow() {
+ this.registerNotificationComponent()
+ },
+
+ onUnload() {
+ if (this.data.fangdouTimer) clearTimeout(this.data.fangdouTimer)
+ },
+
+ onReachBottom() {
+ this.shanglaShuaxin()
+ },
+
+ registerNotificationComponent() {
+ const notificationComp = this.selectComponent('#global-notification')
+ if (notificationComp && notificationComp.showNotification) {
+ app.globalData.globalNotification = {
+ show: (data) => notificationComp.showNotification(data),
+ hide: () => notificationComp.hideNotification()
+ }
+ }
+ },
+
+ initOSSUrl() {
+ const ossImageUrl = app.globalData.ossImageUrl || ''
+ this.setData({ ossImageUrl })
+ },
+
+ jisuanGaodu() {
+ const systemInfo = wx.getSystemInfoSync()
+ const windowHeight = systemInfo.windowHeight
+ const height = windowHeight - 200 - 100 - 40
+ this.setData({
+ scrollHeight: height > 0 ? height : 400
+ })
+ },
+
+ chushihuaShuju() {
+ this.setData({
+ dangqianye: 1,
+ chufaList: [],
+ haiyougengduo: true
+ })
+ this.jiazhuoquTongji()
+ this.jiazhuoquChufaList()
+ },
+
+ async jiazhuoquTongji() {
+ try {
+ // 🔴【修改】商家端接口地址
+ const res = await request({
+ url: '/yonghu/sjcfjlhq',
+ method: 'POST',
+ data: { qingqiu_tongji: true }
+ })
+ if (res.statusCode === 200 && res.data.code === 0) {
+ const data = res.data.data
+ this.setData({
+ zongshu: data.zongshu || 0,
+ daichuli: data.daichuli || 0,
+ yichuli: data.yichuli || 0
+ })
+ }
+ } catch (error) {}
+ },
+
+ async jiazhuoquChufaList(isLoadMore = false) {
+ if (this.data.jiazhaozhong || this.data.jiazhaigengduo) return
+ if (isLoadMore) {
+ if (!this.data.haiyougengduo) return
+ this.setData({ jiazhaigengduo: true })
+ } else {
+ this.setData({ jiazhaozhong: true })
+ }
+
+ try {
+ const params = {
+ page: this.data.dangqianye,
+ page_size: MEIYE_TIAOSHU
+ }
+ if (this.data.shenfen === 0) {
+ params.zhuangtai = 0
+ } else {
+ params.zhuangtai = 1
+ }
+
+ // 🔴【修改】商家端接口地址
+ const res = await request({
+ url: '/yonghu/sjcfjlhq',
+ 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) {
+ if (typeof item.create_time === 'number') {
+ displayTime = String(item.create_time)
+ } else if (typeof item.create_time === 'string') {
+ displayTime = item.create_time
+ } else {
+ displayTime = String(item.create_time)
+ }
+ }
+
+ let displayStatus = '未知状态'
+ let statusClass = 'zhuangtai-weizhi'
+ const statusNum = Number(item.sqzhuangtai)
+
+ if (!isNaN(statusNum)) {
+ if (statusNum === 0) {
+ displayStatus = '待处罚'
+ statusClass = 'zhuangtai-daichuli'
+ } else if (statusNum === 1) {
+ displayStatus = '已处罚'
+ statusClass = 'zhuangtai-yichufa'
+ } else if (statusNum === 2) {
+ displayStatus = '已驳回'
+ statusClass = 'zhuangtai-yibohui'
+ } else if (statusNum === 3) {
+ displayStatus = '申诉中'
+ statusClass = 'zhuangtai-shensuzhong'
+ }
+ }
+
+ const fullZhengjuTupian = (item.zhengju_tupian || []).map(url => this.getFullImageUrl(url))
+ const fullShensuTupian = (item.shensu_tupian || []).map(url => this.getFullImageUrl(url))
+
+ return {
+ ...item,
+ display_time: displayTime,
+ display_status: displayStatus,
+ status_class: statusClass,
+ zhengju_tupian: item.zhengju_tupian || [],
+ shensu_tupian: item.shensu_tupian || [],
+ full_zhengju_tupian: fullZhengjuTupian,
+ full_shensu_tupian: fullShensuTupian
+ }
+ })
+
+ let newList
+ if (isLoadMore) {
+ newList = [...this.data.chufaList, ...processedList]
+ } else {
+ newList = processedList
+ }
+
+ const hasMore = data.has_more === true
+ this.setData({
+ chufaList: newList,
+ haiyougengduo: hasMore,
+ dangqianye: this.data.dangqianye + 1
+ })
+ } else {
+ this.setData({ haiyougengduo: false })
+ }
+ } else {
+ throw new Error(res.data?.msg || '加载处罚记录失败')
+ }
+ } catch (error) {
+ this.setData({ haiyougengduo: false })
+ } finally {
+ if (isLoadMore) {
+ this.setData({ jiazhaigengduo: false })
+ } else {
+ this.setData({ jiazhaozhong: false })
+ }
+ }
+ },
+
+ qiehuanShenfen(e) {
+ const type = parseInt(e.currentTarget.dataset.type)
+ if (this.data.shenfen === type) return
+ this.setData({
+ shenfen: type,
+ dangqianye: 1,
+ chufaList: [],
+ haiyougengduo: true
+ })
+ this.jiazhuoquChufaList()
+ },
+
+ shanglaShuaxin() {
+ 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.jiazhuoquChufaList(true)
+ }, 300)
+ this.setData({ fangdouTimer: timer })
+ },
+
+ chakanXiangqing(e) {
+ const item = e.currentTarget.dataset.item
+ this.setData({
+ xuanzhongChufa: item,
+ showXiangqing: true
+ })
+ },
+
+ guanbiXiangqing() {
+ this.setData({
+ showXiangqing: false,
+ xuanzhongChufa: {}
+ })
+ },
+
+ lianxiKefu() {
+ const kefuLink = app.globalData.kefuConfig?.link || '';
+ const corpId = app.globalData.kefuConfig?.enterpriseId || '';
+
+ if (!kefuLink || !corpId) {
+ wx.showToast({
+ title: '客服配置未加载',
+ icon: 'none'
+ });
+ return;
+ }
+
+ wx.openCustomerServiceChat({
+ extInfo: { url: kefuLink },
+ corpId: corpId,
+ success: (res) => {
+ console.log('跳转客服成功', res);
+ },
+ fail: (err) => {
+ console.error('跳转客服失败', err);
+ wx.showToast({
+ title: '暂时无法连接客服',
+ icon: 'none'
+ });
+ }
+ });
+ },
+
+ getFullImageUrl(relativeUrl) {
+ if (!relativeUrl) return ''
+ if (relativeUrl.startsWith('http')) {
+ return relativeUrl
+ }
+
+ const ossUrl = this.data.ossImageUrl
+ if (!ossUrl) {
+ return relativeUrl
+ }
+
+ let fullUrl
+ if (ossUrl.endsWith('/') && relativeUrl.startsWith('/')) {
+ fullUrl = ossUrl + relativeUrl.substring(1)
+ } else if (!ossUrl.endsWith('/') && !relativeUrl.startsWith('/')) {
+ fullUrl = ossUrl + '/' + relativeUrl
+ } else {
+ fullUrl = ossUrl + relativeUrl
+ }
+
+ return fullUrl
+ },
+
+ yulanTupian(e) {
+ const currentUrl = e.currentTarget.dataset.url
+ const urls = e.currentTarget.dataset.urls || []
+ if (!currentUrl) return
+
+ const fullCurrentUrl = currentUrl.startsWith('http') ? currentUrl : this.getFullImageUrl(currentUrl)
+ const fullUrls = urls.map(url => url.startsWith('http') ? url : this.getFullImageUrl(url))
+
+ wx.previewImage({
+ current: fullCurrentUrl,
+ urls: fullUrls.length > 0 ? fullUrls : [fullCurrentUrl]
+ })
+ },
+
+ fuzhiWenben(e) {
+ const text = e.currentTarget.dataset.text
+ if (!text) return
+
+ wx.setClipboardData({
+ data: text,
+ success: () => {
+ wx.showToast({ title: '复制成功', icon: 'success' })
+ },
+ fail: () => {
+ wx.showToast({ title: '复制失败', icon: 'none' })
+ }
+ })
+ },
+
+ imageLoadError(e) {
+ // 图片加载失败处理(可以添加默认图片)
+ const index = e.currentTarget.dataset.index
+ const type = e.currentTarget.dataset.type
+
+ if (type === 'zhengju') {
+ const key = `xuanzhongChufa.full_zhengju_tupian[${index}]`
+ this.setData({
+ [key]: '/images/image-error.png'
+ })
+ } else if (type === 'shensu') {
+ const key = `xuanzhongChufa.full_shensu_tupian[${index}]`
+ this.setData({
+ [key]: '/images/image-error.png'
+ })
+ }
+ }
+})
\ No newline at end of file
diff --git a/miniprogram/pages/sjxiaoxi/sjxiaoxi.json b/miniprogram/pages/sjxiaoxi/sjxiaoxi.json
new file mode 100644
index 0000000..e3c4071
--- /dev/null
+++ b/miniprogram/pages/sjxiaoxi/sjxiaoxi.json
@@ -0,0 +1,10 @@
+{
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification"
+ },
+ "navigationBarTitleText": "处罚记录",
+ "navigationBarBackgroundColor": "#0a0a12",
+ "navigationBarTextStyle": "white",
+ "backgroundColor": "#0a0a12",
+ "enablePullDownRefresh": false
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/sjxiaoxi/sjxiaoxi.wxml b/miniprogram/pages/sjxiaoxi/sjxiaoxi.wxml
new file mode 100644
index 0000000..a66a2be
--- /dev/null
+++ b/miniprogram/pages/sjxiaoxi/sjxiaoxi.wxml
@@ -0,0 +1,241 @@
+
+
+
+
+ 处罚统计
+
+
+ 总记录
+ {{ zongshu }}
+
+
+ 待处理
+ {{ daichuli }}
+
+
+ 已处理
+ {{ yichuli }}
+
+
+
+
+
+
+
+ 待处理
+ {{ daichuli }}
+
+
+ 已处理
+ {{ yichuli }}
+
+
+
+
+
+
+
+
+
+
+
+
+ 加载中...
+
+
+
+
+
+
+
+ {{ item.display_time }}
+
+ {{ item.display_status }}
+
+
+
+
+
+ 被罚打手:
+ {{ item.qingqiuid || '--' }}
+
+
+
+
+ 处罚理由:
+ {{ item.cfliyou || '无' }}
+
+
+
+
+ 订单编号:
+ {{ item.dingdan_id || '--' }}
+
+
+
+
+
+
+
+
+
+
+
+ 加载更多...
+
+
+
+
+ —— 没有更多记录了 ——
+
+
+
+
+ ⚡
+ 暂无处罚记录
+ 所有事务已处理完毕
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 基本信息
+
+ 订单编号:
+
+ {{ xuanzhongChufa.dingdan_id || '--' }}
+ 复制
+
+
+
+
+ 被罚打手ID:
+
+ {{ xuanzhongChufa.qingqiuid || '--' }}
+ 复制
+
+
+
+ 创建时间:
+ {{ xuanzhongChufa.display_time || '--' }}
+
+
+
+
+
+ 处罚信息
+
+ 处罚状态:
+
+ {{ xuanzhongChufa.display_status }}
+
+
+
+ 处罚理由:
+ {{ xuanzhongChufa.cfliyou || '无' }}
+
+
+
+
+ 证据图片:
+
+
+
+
+
+
+ 预览
+
+
+
+
+
+
+
+
+
+ 申诉信息
+
+ 申诉理由:
+ {{ xuanzhongChufa.ssliyou }}
+
+
+
+ 申诉图片:
+
+
+
+
+
+
+ 预览
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/sjxiaoxi/sjxiaoxi.wxss b/miniprogram/pages/sjxiaoxi/sjxiaoxi.wxss
new file mode 100644
index 0000000..204439d
--- /dev/null
+++ b/miniprogram/pages/sjxiaoxi/sjxiaoxi.wxss
@@ -0,0 +1,751 @@
+/* pages/sjcf/sjcf.wxss */
+/* 🔴【样式与cfss页面基本相同,只添加了独占按钮样式】 */
+
+:root {
+ --cyber-blue: rgba(0, 243, 255, 0.7);
+ --cyber-pink: rgba(255, 0, 255, 0.7);
+ --cyber-purple: rgba(157, 0, 255, 0.7);
+ --cyber-green: rgba(0, 255, 157, 0.7);
+ --cyber-red: rgba(255, 0, 102, 0.7);
+
+ --cyber-bg: #2a2a3a;
+ --cyber-card: #333344;
+ --cyber-border: rgba(255, 255, 255, 0.15);
+ --cyber-glow: rgba(0, 243, 255, 0.2);
+
+ --cyber-text: #ffffff;
+ --cyber-text-light: #e6e6e6;
+ --cyber-text-lighter: #cccccc;
+}
+
+.cfss-container {
+ padding: 30rpx;
+ background: var(--cyber-bg);
+ min-height: 100vh;
+ color: var(--cyber-text) !important;
+ position: relative;
+ overflow: hidden;
+ box-sizing: border-box;
+}
+
+/* 恢复网格背景 */
+.cfss-container::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+
+ background-image:
+ linear-gradient(rgba(0, 243, 255, 0.05) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(0, 243, 255, 0.05) 1px, transparent 1px);
+ background-size: 40rpx 40rpx;
+ pointer-events: none;
+ opacity: 0.15;
+ z-index: 0;
+}
+
+/* ====== 统计卡片 ====== */
+.tongji-card {
+ background: rgba(255, 0, 102, 0.3);
+ border-radius: 20rpx;
+ padding: 30rpx;
+ margin-bottom: 30rpx;
+ border: 1rpx solid var(--cyber-border);
+ box-shadow:
+ 0 0 20rpx rgba(255, 0, 102, 0.2),
+ inset 0 0 10rpx rgba(0, 243, 255, 0.05);
+ position: relative;
+ overflow: hidden;
+ z-index: 1;
+}
+
+.tongji-title {
+ color: var(--cyber-blue);
+ font-size: 34rpx;
+ font-weight: bold;
+ margin-bottom: 30rpx;
+ text-shadow: 0 0 8rpx var(--cyber-blue);
+ letter-spacing: 2rpx;
+}
+
+.tongji-content {
+ display: flex;
+ justify-content: space-between;
+}
+
+.tongji-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ flex: 1;
+}
+
+.tongji-label {
+ color: var(--cyber-text-light) !important;
+ font-size: 26rpx;
+ margin-bottom: 15rpx;
+ opacity: 0.9;
+}
+
+.tongji-value {
+ color: var(--cyber-text) !important;
+ font-size: 44rpx;
+ font-weight: bold;
+ text-shadow: 0 0 8rpx currentColor;
+}
+
+.daichuli-value {
+ color: var(--cyber-red) !important;
+ text-shadow: 0 0 8rpx rgba(255, 0, 102, 0.3);
+}
+
+.yichuli-value {
+ color: var(--cyber-green) !important;
+ text-shadow: 0 0 8rpx rgba(0, 255, 157, 0.3);
+}
+
+/* ====== 类型切换 ====== */
+.leixing-qiehuan {
+ display: flex;
+ background: rgba(255, 0, 102, 0.3);
+ border-radius: 16rpx;
+ padding: 10rpx;
+ margin-bottom: 30rpx;
+ border: 1rpx solid var(--cyber-border);
+ box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.2);
+ z-index: 1;
+}
+
+.leixing-item {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 25rpx 0;
+ border-radius: 12rpx;
+ transition: all 0.3s;
+}
+
+.leixing-item:active {
+ transform: scale(0.98);
+}
+
+.leixing-active {
+ background: linear-gradient(135deg, rgba(0, 243, 255, 0.15), rgba(157, 0, 255, 0.15));
+ box-shadow: 0 0 15rpx rgba(0, 243, 255, 0.2);
+}
+
+.leixing-text {
+ font-size: 28rpx;
+ font-weight: bold;
+ margin-bottom: 8rpx;
+ color: var(--cyber-text-light) !important;
+}
+
+.leixing-active .leixing-text {
+ color: var(--cyber-text) !important;
+ text-shadow: 0 0 8rpx var(--cyber-blue);
+}
+
+.leixing-count {
+ font-size: 24rpx;
+ color: var(--cyber-text-lighter) !important;
+ opacity: 0.9;
+}
+
+.leixing-active .leixing-count {
+ color: rgba(255, 255, 255, 0.9) !important;
+}
+
+/* ====== 处罚列表 ====== */
+.chufa-list {
+ margin-bottom: 0;
+ padding-bottom: 30rpx;
+ z-index: 1;
+}
+
+/* 加载动画 */
+.jiazai-zhong {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 60rpx 0;
+ color: var(--cyber-blue);
+}
+
+.loading-animation {
+ display: flex;
+ justify-content: center;
+ margin-bottom: 20rpx;
+}
+
+.loading-dot {
+ width: 20rpx;
+ height: 20rpx;
+ border-radius: 50%;
+ background-color: var(--cyber-blue);
+ margin: 0 10rpx;
+ animation: pulse 1.5s infinite ease-in-out;
+}
+
+.loading-dot:nth-child(2) {
+ animation-delay: 0.2s;
+}
+
+.loading-dot:nth-child(3) {
+ animation-delay: 0.4s;
+}
+
+@keyframes pulse {
+ 0%, 100% {
+ transform: scale(0.8);
+ opacity: 0.5;
+ }
+ 50% {
+ transform: scale(1.2);
+ opacity: 1;
+ }
+}
+
+/* 处罚卡片 */
+.chufa-card {
+ background: rgba(560, 50, 65, 0.8);
+ border-radius: 20rpx;
+ padding: 30rpx;
+ margin-bottom: 30rpx;
+ border: 1rpx solid var(--cyber-border);
+ position: relative;
+ overflow: hidden;
+ transition: all 0.3s;
+ z-index: 1;
+}
+
+.chufa-card:active {
+ transform: translateY(-4rpx);
+ box-shadow:
+ 0 10rpx 30rpx rgba(0, 0, 0, 0.3),
+ 0 0 25rpx var(--cyber-glow);
+}
+
+.card-border {
+ position: absolute;
+ top: -1px;
+ left: -1px;
+ right: -1px;
+ bottom: -1px;
+ border-radius: 21rpx;
+ background: linear-gradient(45deg, rgba(138, 60, 138, 0.3), rgba(131, 208, 212, 0.3), rgba(255, 0, 255, 0.3), rgba(0, 243, 255, 0.3));
+ z-index: -1;
+ opacity: 0;
+ transition: opacity 0.3s ease;
+}
+
+.chufa-card:active .card-border {
+ opacity: 0.3;
+}
+
+.card-top {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 25rpx;
+}
+
+.shijian {
+ color: var(--cyber-text-light) !important;
+ font-size: 26rpx;
+ font-family: 'Courier New', monospace;
+ letter-spacing: 1rpx;
+ opacity: 0.9;
+}
+
+.zhuangtai-badge {
+ padding: 8rpx 25rpx;
+ border-radius: 20rpx;
+ font-size: 24rpx;
+ font-weight: bold;
+ text-transform: uppercase;
+ letter-spacing: 2rpx;
+ border: 1rpx solid;
+ box-shadow: 0 0 8rpx;
+ color: #ffffff !important;
+}
+
+.zhuangtai-daichuli {
+ background: rgba(255, 0, 102, 0.2);
+ border-color: var(--cyber-red);
+ box-shadow: 0 0 8rpx rgba(255, 0, 102, 0.2);
+}
+
+.zhuangtai-yichufa {
+ background: rgba(0, 255, 157, 0.2);
+ border-color: var(--cyber-green);
+ box-shadow: 0 0 8rpx rgba(0, 255, 157, 0.2);
+}
+
+.zhuangtai-yibohui {
+ background: rgba(102, 102, 102, 0.2);
+ border-color: #666;
+ box-shadow: 0 0 8rpx rgba(102, 102, 102, 0.2);
+}
+
+.zhuangtai-shensuzhong {
+ background: rgba(255, 165, 0, 0.2);
+ border-color: #FFA500;
+ box-shadow: 0 0 8rpx rgba(255, 165, 0, 0.2);
+}
+
+.zhuangtai-weizhi {
+ background: rgba(128, 0, 255, 0.2);
+ border-color: rgba(128, 0, 255, 0.5);
+ box-shadow: 0 0 8rpx rgba(128, 0, 255, 0.2);
+}
+
+/* 其他文字内容 */
+.qingqiu-row, .liyou-row, .dingdan-row {
+ display: flex;
+ align-items: flex-start;
+ margin-bottom: 20rpx;
+}
+
+.qingqiu-label, .liyou-label, .dingdan-label {
+ color: var(--cyber-text-light) !important;
+ font-size: 26rpx;
+ font-weight: bold;
+ margin-right: 15rpx;
+ opacity: 0.9;
+ flex-shrink: 0;
+}
+
+.qingqiu-value, .liyou-value, .dingdan-value {
+ color: var(--cyber-text) !important;
+ font-size: 26rpx;
+ flex: 1;
+ word-break: break-all;
+}
+
+.liyou-value {
+ line-height: 1.5;
+}
+
+.dingdan-value {
+ font-family: 'Courier New', monospace;
+ letter-spacing: 1rpx;
+}
+
+/* 加载更多 */
+.jiazai-gengduo {
+ text-align: center;
+ padding: 40rpx 0;
+ color: var(--cyber-blue);
+ position: relative;
+ overflow: hidden;
+}
+
+.scan-line {
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 2rpx;
+ background: linear-gradient(90deg, transparent, var(--cyber-blue), transparent);
+ animation: scan 2s linear infinite;
+}
+
+@keyframes scan {
+ 0% { left: -100%; }
+ 100% { left: 100%; }
+}
+
+/* 没有更多数据 */
+.meiyou-gengduo {
+ text-align: center;
+ padding: 40rpx 0;
+ color: var(--cyber-green);
+ font-size: 28rpx;
+ opacity: 0.9;
+}
+
+/* 空状态 */
+.kong-zhuangtai {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 100rpx 40rpx;
+ text-align: center;
+ z-index: 1;
+}
+
+.empty-icon {
+ font-size: 120rpx;
+ color: var(--cyber-blue);
+ margin-bottom: 30rpx;
+ text-shadow: 0 0 15rpx var(--cyber-blue);
+ animation: pulse 2s infinite;
+}
+
+.empty-text {
+ font-size: 34rpx;
+ color: var(--cyber-text) !important;
+ margin-bottom: 15rpx;
+ font-weight: bold;
+}
+
+.empty-subtext {
+ font-size: 28rpx;
+ color: var(--cyber-text-light) !important;
+ opacity: 0.8;
+}
+
+/* ====== 弹窗样式 ====== */
+.xiangqing-modal {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 1000;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.modal-mask {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.7);
+}
+
+.modal-content {
+ position: relative;
+ background: #3a3a4a;
+ border-radius: 30rpx;
+ width: 90%;
+ max-height: 80vh;
+ display: flex;
+ flex-direction: column;
+ border: 1rpx solid var(--cyber-border);
+ box-shadow: 0 0 35rpx rgba(135, 218, 223, 0.3);
+ z-index: 1001;
+}
+
+.modal-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 40rpx 30rpx 30rpx 30rpx;
+ border-bottom: 1rpx solid rgba(157, 250, 253, 0.2);
+ flex-shrink: 0;
+}
+
+.modal-title {
+ font-size: 36rpx;
+ font-weight: bold;
+ color: var(--cyber-blue);
+ text-shadow: 0 0 8rpx var(--cyber-blue);
+}
+
+.modal-close {
+ width: 60rpx;
+ height: 60rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 50%;
+ background: rgba(137, 210, 214, 0.1);
+ border: 1rpx solid rgba(0, 243, 255, 0.3);
+}
+
+.modal-close:active {
+ background: rgba(0, 243, 255, 0.2);
+ transform: scale(0.95);
+}
+
+.close-icon {
+ font-size: 40rpx;
+ color: var(--cyber-blue);
+ line-height: 1;
+}
+
+/* 弹窗主体 */
+.modal-body {
+ flex: 1;
+ padding: 30rpx;
+ overflow-y: auto;
+ min-height: 200rpx;
+ box-sizing: border-box;
+}
+
+/* 详情区域 */
+.detail-section {
+ margin-bottom: 40rpx;
+}
+
+.section-title {
+ font-size: 32rpx;
+ font-weight: bold;
+ color: var(--cyber-blue);
+ margin-bottom: 25rpx;
+ padding-bottom: 15rpx;
+ border-bottom: 1rpx solid rgba(0, 243, 255, 0.2);
+ text-shadow: 0 0 8rpx var(--cyber-blue);
+}
+
+.detail-row {
+ display: flex;
+ margin-bottom: 25rpx;
+ align-items: center;
+}
+
+.detail-label {
+ color: var(--cyber-text-light) !important;
+ font-size: 30rpx;
+ min-width: 180rpx;
+ margin-right: 20rpx;
+ line-height: 1.5;
+ opacity: 0.9;
+ flex-shrink: 0;
+}
+
+.detail-value-container {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.detail-value {
+ color: var(--cyber-text) !important;
+ font-size: 30rpx;
+ line-height: 1.5;
+ word-break: break-all;
+ opacity: 0.95;
+ flex: 1;
+ margin-right: 20rpx;
+}
+
+.fuzhi-btn {
+ color: var(--cyber-blue);
+ font-size: 24rpx;
+ padding: 8rpx 20rpx;
+ border-radius: 8rpx;
+ background: rgba(0, 243, 255, 0.1);
+ border: 1rpx solid rgba(0, 243, 255, 0.3);
+ flex-shrink: 0;
+}
+
+.fuzhi-btn:active {
+ background: rgba(0, 243, 255, 0.2);
+}
+
+.full-row {
+ flex-direction: column;
+ align-items: flex-start;
+}
+
+.full-row .detail-label {
+ margin-bottom: 10rpx;
+ min-width: auto;
+}
+
+/* 弹窗内的状态标签 */
+.zhuangtai-badge-inline {
+ padding: 8rpx 24rpx;
+ border-radius: 20rpx;
+ font-size: 26rpx;
+ font-weight: bold;
+ text-transform: uppercase;
+ letter-spacing: 1rpx;
+ border: 1rpx solid;
+ min-width: 120rpx;
+ text-align: center;
+ color: #ffffff !important;
+}
+
+/* 理由方框 */
+.liyou-full {
+ background: rgba(255, 255, 255, 0.05);
+ padding: 25rpx 30rpx;
+ border-radius: 15rpx;
+ line-height: 1.6;
+ border: 1rpx solid rgba(218, 176, 176, 0.1);
+ color: var(--cyber-text) !important;
+ width: 100%;
+ box-sizing: border-box;
+ margin: 0;
+ display: block;
+ opacity: 0.95;
+}
+
+/* 图片网格样式 */
+.tupian-grid {
+ display: flex;
+ flex-wrap: wrap;
+ margin-top: 10rpx;
+ margin-left: -5rpx;
+ margin-right: -5rpx;
+}
+
+.tupian-item {
+ width: calc(33.333% - 10rpx);
+ margin: 5rpx;
+ aspect-ratio: 1;
+ border-radius: 10rpx;
+ overflow: hidden;
+ position: relative;
+ border: 1rpx solid rgba(0, 243, 255, 0.2);
+}
+
+.tupian-image {
+ width: 100%;
+ height: 100%;
+}
+
+/* 图片预览遮罩 */
+.tupian-preview {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.3);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ opacity: 0;
+ transition: opacity 0.3s ease;
+}
+
+.tupian-item:hover .tupian-preview {
+ opacity: 1;
+}
+
+.preview-text {
+ color: white;
+ font-size: 24rpx;
+ background: rgba(0, 243, 255, 0.7);
+ padding: 8rpx 16rpx;
+ border-radius: 8rpx;
+}
+
+/* 🔴【新增】弹窗底部按钮独占一行样式 */
+.modal-footer {
+ padding: 30rpx;
+ border-top: 1rpx solid rgba(0, 243, 255, 0.2);
+ flex-shrink: 0;
+}
+
+.btn-group-single {
+ display: flex;
+ width: 100%;
+}
+
+.btn-group-single .btn {
+ flex: 1;
+ height: 90rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 15rpx;
+ font-size: 32rpx;
+ font-weight: bold;
+ transition: all 0.3s;
+ border: 1rpx solid;
+ box-shadow: 0 0 12rpx;
+ color: var(--cyber-text) !important;
+}
+
+.btn-group-single .btn:active {
+ transform: scale(0.98);
+}
+
+/* 按钮样式 */
+.btn-kefu {
+ background: rgba(0, 243, 255, 0.1);
+ border-color: var(--cyber-blue);
+ box-shadow: 0 0 12rpx rgba(0, 243, 255, 0.2);
+}
+
+/* 响应式调整 */
+@media (max-width: 375px) {
+ .tongji-value {
+ font-size: 36rpx;
+ }
+
+ .shijian,
+ .qingqiu-label,
+ .liyou-label,
+ .dingdan-label {
+ font-size: 24rpx;
+ }
+
+ .qingqiu-value,
+ .liyou-value,
+ .dingdan-value {
+ font-size: 24rpx;
+ }
+
+ .modal-content {
+ width: 95%;
+ }
+}
+
+
+
+/* 🔴【关键修改】图片网格和图片样式 */
+.tupian-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 12rpx;
+ margin-top: 10rpx;
+ width: 100%;
+ }
+
+ .tupian-item {
+ position: relative;
+ width: 100%;
+ aspect-ratio: 1;
+ border-radius: 12rpx;
+ overflow: hidden;
+ border: 1rpx solid rgba(0, 243, 255, 0.3);
+ background-color: rgba(0, 0, 0, 0.2);
+ }
+
+ .tupian-image {
+ width: 100%;
+ height: 100%;
+ display: block;
+ object-fit: cover;
+ }
+
+ /* 图片预览遮罩 */
+ .tupian-preview {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.3);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ opacity: 0;
+ transition: opacity 0.3s ease;
+ }
+
+ .tupian-item:hover .tupian-preview {
+ opacity: 1;
+ }
+
+ .preview-text {
+ color: white;
+ font-size: 24rpx;
+ background: rgba(0, 243, 255, 0.7);
+ padding: 8rpx 16rpx;
+ border-radius: 8rpx;
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/tijiao/tijiao.js b/miniprogram/pages/tijiao/tijiao.js
new file mode 100644
index 0000000..b7dc1e1
--- /dev/null
+++ b/miniprogram/pages/tijiao/tijiao.js
@@ -0,0 +1,313 @@
+// pages/tijiao/tijiao.js
+const app = getApp()
+import request from '../../utils/request.js'
+import PopupService from '../../services/popupService.js'
+
+Page({
+ data: {
+ ossImageUrl: '',
+ apiBaseUrl: '',
+ shangpinData: null,
+ shangpinId: '',
+ nicheng: '',
+ beizhu: '',
+ zhiding: '',
+ jine: 0,
+ isSubmitting: false,
+ canSubmit: false,
+ isPaying: false,
+ orderId: '',
+ payParams: null,
+ checkCount: 0,
+ maxCheckCount: 3
+ },
+
+ onLoad(options) {
+ this.setData({
+ ossImageUrl: app.globalData.ossImageUrl || '',
+ apiBaseUrl: app.globalData.apiBaseUrl || ''
+ })
+ wx.setNavigationBarTitle({ title: '提交订单' })
+ try {
+ if (options.data) {
+ const shangpinData = JSON.parse(decodeURIComponent(options.data))
+ if (shangpinData.image && !shangpinData.image.startsWith('http')) {
+ shangpinData.image = this.data.ossImageUrl + shangpinData.image
+ }
+ if (shangpinData.price) {
+ const price = parseFloat(shangpinData.price) || 0
+ const priceInteger = Math.floor(price)
+ let priceDecimal = '00'
+ const decimalPart = (price - priceInteger).toFixed(2)
+ if (decimalPart > 0) {
+ priceDecimal = decimalPart.toString().split('.')[1]
+ }
+ shangpinData.priceInteger = priceInteger
+ shangpinData.priceDecimal = priceDecimal
+ }
+ this.setData({
+ shangpinData: shangpinData,
+ shangpinId: shangpinData.id || '',
+ jine: parseFloat(shangpinData.price) || 0,
+ })
+ } else {
+ this.handleDataError('商品数据不完整')
+ }
+ } catch (error) {
+ this.handleDataError('解析商品数据失败')
+ }
+ PopupService.checkAndShow(this, 'tijiao');
+ },
+
+ onHide() {
+ const popupComp = this.selectComponent('#popupNotice');
+ if (popupComp && popupComp.cleanup) {
+ popupComp.cleanup();
+ }
+ },
+
+ onShow() {
+ this.registerNotificationComponent();
+ },
+
+ 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()
+ };
+ }
+ },
+
+ handleDataError(errorMsg) {
+ wx.showToast({ title: errorMsg || '数据错误', icon: 'none' })
+ setTimeout(() => { wx.navigateBack() }, 1500)
+ },
+
+ onNicknameInput(e) {
+ this.setData({ nicheng: e.detail.value.trim() }, () => { this.checkFormValid() })
+ },
+ onRemarkInput(e) {
+ this.setData({ beizhu: e.detail.value.trim() })
+ },
+ onZhidingInput(e) {
+ this.setData({ zhiding: e.detail.value.trim() })
+ },
+
+ checkFormValid() {
+ const { nicheng } = this.data
+ this.setData({ canSubmit: nicheng && nicheng.length > 0 && nicheng.length <= 15 })
+ },
+
+ // 提交订单(生产版:已去掉所有调试日志,保留关键错误日志)
+ onSubmitOrder() {
+ const { nicheng, beizhu, zhiding, jine, shangpinId, canSubmit, isSubmitting } = this.data
+
+ if (!canSubmit) {
+ wx.showToast({ title: '请填写游戏昵称', icon: 'none' })
+ return
+ }
+
+ // 防重复提交
+ if (isSubmitting) return
+
+ this.setData({ isSubmitting: true, isPaying: false })
+ wx.showLoading({ title: '创建订单中...', mask: true })
+
+ // 10秒超时
+ const timeoutTimer = setTimeout(() => {
+ wx.hideLoading()
+ wx.showToast({ title: '网络超时,请检查您是否已登录', icon: 'none' })
+ this.setData({ isSubmitting: false, isPaying: false })
+ }, 10000)
+
+ // 获取登录 code(虚拟支付必须每单刷新 session_key)
+ wx.login({
+ success: (loginRes) => {
+ const orderData = {
+ shangpin_id: shangpinId,
+ nicheng: nicheng,
+ beizhu: beizhu || '',
+ zhiding: zhiding || '',
+ jine: jine,
+ code: loginRes.code
+ }
+
+ request({
+ url: '/dingdan/virtual_xiadan',
+ method: 'POST',
+ data: orderData
+ })
+ .then(res => {
+ clearTimeout(timeoutTimer)
+ wx.hideLoading()
+
+ if (res.statusCode === 200 && res.data && res.data.code === 0 && res.data.data) {
+ const payParams = res.data.data.payParams || {}
+ this.setData({
+ orderId: res.data.data.dingdanid,
+ payParams: payParams,
+ isSubmitting: false,
+ isPaying: true,
+ checkCount: 0
+ })
+ // 调起虚拟支付
+ this.triggerVirtualPayment(payParams)
+ } else {
+ const msg = (res.data && res.data.msg) || '创建订单失败'
+ wx.showToast({ title: msg, icon: 'none' })
+ this.setData({ isSubmitting: false, isPaying: false })
+ }
+ })
+ .catch(error => {
+ clearTimeout(timeoutTimer)
+ wx.hideLoading()
+ console.error('下单请求异常', error)
+ wx.showToast({ title: '创建订单失败', icon: 'none' })
+ this.setData({ isSubmitting: false, isPaying: false })
+ })
+ },
+ fail: () => {
+ clearTimeout(timeoutTimer)
+ wx.hideLoading()
+ wx.showToast({ title: '获取登录状态失败', icon: 'none' })
+ this.setData({ isSubmitting: false, isPaying: false })
+ }
+ })
+ },
+
+ // 调起虚拟支付(防抖已通过 isPaying 状态控制)
+ triggerVirtualPayment(payParams) {
+ // 参数完整性检查
+ if (!payParams || !payParams.signData || !payParams.paySig || !payParams.signature) {
+ console.error('支付参数不完整', payParams)
+ wx.showToast({ title: '支付参数异常', icon: 'none' })
+ this.setData({ isSubmitting: false, isPaying: false })
+ return
+ }
+
+ wx.requestVirtualPayment({
+ signData: payParams.signData,
+ paySig: payParams.paySig,
+ signature: payParams.signature,
+ mode: payParams.mode || 'short_series_coin',
+ success: (res) => {
+ // 支付成功,进入复查流程
+ this.onPaymentSuccess()
+ },
+ fail: (err) => {
+ console.error('支付失败', err)
+ if (err.errCode === -2 || (err.errMsg && err.errMsg.indexOf('cancel') !== -1)) {
+ this.handleUserCancel()
+ } else {
+ this.handlePaymentFail(err)
+ }
+ }
+ })
+ },
+
+ // 用户取消支付
+ handleUserCancel() {
+ wx.showToast({ title: '已取消支付', icon: 'none', duration: 1500 })
+ if (this.data.orderId) {
+ request({
+ url: '/dingdan/shibai',
+ method: 'POST',
+ data: { dingdanid: this.data.orderId }
+ }).catch(() => {})
+ }
+ this.setData({ isSubmitting: false, isPaying: false })
+ },
+
+ // 其他支付失败
+ handlePaymentFail(err) {
+ wx.showToast({ title: '支付失败', icon: 'none', duration: 1500 })
+ if (this.data.orderId) {
+ request({
+ url: '/dingdan/shibai',
+ method: 'POST',
+ data: {
+ dingdanid: this.data.orderId,
+ yuanyin: 'payment_fail'
+ }
+ }).catch(() => {})
+ }
+ this.setData({ isSubmitting: false, isPaying: false })
+ },
+
+ // 支付成功,开始双重确认
+ onPaymentSuccess() {
+ if (!this.data.orderId) {
+ wx.showToast({ title: '订单ID丢失', icon: 'none' })
+ this.setData({ isSubmitting: false, isPaying: false })
+ return
+ }
+ wx.showToast({ title: '支付成功,确认中...', icon: 'none', duration: 1500 })
+ this.checkOrderStatus()
+ },
+
+ // 复查订单状态(最多重试3次)
+ checkOrderStatus() {
+ const { orderId, checkCount, maxCheckCount } = this.data
+ wx.showLoading({
+ title: `确认支付状态${checkCount > 0 ? `(${checkCount + 1})` : ''}`,
+ mask: true
+ })
+
+ const timeoutTimer = setTimeout(() => {
+ wx.hideLoading()
+ this.handleConfirmError('网络超时')
+ }, 5000)
+
+ request({
+ url: '/dingdan/virtual_fucha',
+ method: 'POST',
+ data: { dingdanid: orderId }
+ })
+ .then(res => {
+ clearTimeout(timeoutTimer)
+ wx.hideLoading()
+ if ( res.data && res.data.code === 0) {
+ this.handleConfirmSuccess()
+ } else {
+ const msg = (res.data && res.data.msg) || '订单状态异常'
+ this.handleConfirmError(msg)
+ }
+ })
+ .catch(error => {
+ clearTimeout(timeoutTimer)
+ wx.hideLoading()
+ this.handleConfirmError('检查失败')
+ })
+ },
+
+ handleConfirmSuccess() {
+ wx.showToast({ title: '购买成功!', icon: 'success', duration: 1500 })
+ setTimeout(() => {
+ wx.switchTab({ url: '/pages/index/index' })
+ }, 1500)
+ this.setData({ isSubmitting: false, isPaying: false })
+ },
+
+ handleConfirmError(errorMsg) {
+ const { checkCount, maxCheckCount } = this.data
+ const newCheckCount = checkCount + 1
+ if (newCheckCount < maxCheckCount) {
+ this.setData({ checkCount: newCheckCount })
+ setTimeout(() => { this.checkOrderStatus() }, 1000)
+ } else {
+ wx.showModal({
+ title: '支付确认中',
+ content: '支付已成功,但订单状态确认异常。请稍后在订单列表查看,如有问题请联系客服。',
+ showCancel: false,
+ confirmText: '我知道了',
+ success: (res) => {
+ if (res.confirm) wx.switchTab({ url: '/pages/index/index' })
+ }
+ })
+ this.setData({ isSubmitting: false, isPaying: false })
+ }
+ }
+})
\ No newline at end of file
diff --git a/miniprogram/pages/tijiao/tijiao.json b/miniprogram/pages/tijiao/tijiao.json
new file mode 100644
index 0000000..837f016
--- /dev/null
+++ b/miniprogram/pages/tijiao/tijiao.json
@@ -0,0 +1,5 @@
+{
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/pages/tijiao/tijiao.wxml b/miniprogram/pages/tijiao/tijiao.wxml
new file mode 100644
index 0000000..3a6b85e
--- /dev/null
+++ b/miniprogram/pages/tijiao/tijiao.wxml
@@ -0,0 +1,124 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{shangpinData && shangpinData.title ? shangpinData.title : '商品标题'}}
+
+
+
+
+ 价格:
+
+ ¥
+ {{shangpinData && shangpinData.priceInteger ? shangpinData.priceInteger : '0'}}
+ .{{shangpinData && shangpinData.priceDecimal ? shangpinData.priceDecimal : '00'}}
+
+
+
+
+
+
+
+
+
+
+
+
+ 昵称
+ *
+
+
+
+
+
+
+
+ 备注
+
+
+
+
+
+
+
+ 指定服务者ID(选填)
+
+
+
+
+
+
+
+
+ 微信支付
+
+ ✓
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/tijiao/tijiao.wxss b/miniprogram/pages/tijiao/tijiao.wxss
new file mode 100644
index 0000000..c622155
--- /dev/null
+++ b/miniprogram/pages/tijiao/tijiao.wxss
@@ -0,0 +1,280 @@
+/* pages/tijiao/tijiao.wxss */
+
+.submit-page {
+ display: flex;
+ flex-direction: column;
+ min-height: 100vh;
+ background: linear-gradient(135deg, #f8f9ff 0%, #f0f3ff 100%);
+ box-sizing: border-box;
+ }
+
+ /* 商品信息区域 */
+ .product-section {
+ padding: 30rpx 30rpx 20rpx 30rpx;
+ flex-shrink: 0;
+ }
+
+ .product-card {
+ background: white;
+ border-radius: 20rpx;
+ padding: 30rpx;
+ display: flex;
+ align-items: center;
+ box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.06);
+ }
+
+ .product-image-box {
+ width: 160rpx;
+ height: 160rpx;
+ border-radius: 16rpx;
+ overflow: hidden;
+ background: #f5f5f5;
+ flex-shrink: 0;
+ margin-right: 24rpx;
+ }
+
+ .product-image {
+ width: 100%;
+ height: 100%;
+ }
+
+ .product-info {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ height: 160rpx;
+ }
+
+ .product-title {
+ margin-bottom: 20rpx;
+ }
+
+ .title-text {
+ font-size: 30rpx;
+ font-weight: 600;
+ color: #222;
+ line-height: 1.4;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ overflow: hidden;
+ }
+
+ .price-info {
+ display: flex;
+ align-items: baseline;
+ }
+
+ .price-label {
+ font-size: 26rpx;
+ color: #666;
+ font-weight: 500;
+ }
+
+ .price-value {
+ display: flex;
+ align-items: baseline;
+ }
+
+ .price-icon {
+ font-size: 28rpx;
+ color: #ff4d4f;
+ font-weight: 600;
+ }
+
+ .price-integer {
+ font-size: 38rpx;
+ color: #ff4d4f;
+ font-weight: 700;
+ margin-left: 4rpx;
+ }
+
+ .price-decimal {
+ font-size: 28rpx;
+ color: #ff4d4f;
+ font-weight: 600;
+ }
+
+ /* 表单区域 */
+ .form-section {
+ padding: 0 30rpx 20rpx 30rpx;
+ flex-shrink: 0;
+ }
+
+ .form-card {
+ background: white;
+ border-radius: 20rpx;
+ padding: 30rpx;
+ box-shadow: 0 6rpx 24rpx rgba(0, 0, 0, 0.05);
+ }
+
+ .form-item-row {
+ display: flex;
+ align-items: center;
+ padding: 25rpx 0;
+ border-bottom: 1rpx solid #f5f5f5;
+ }
+
+ .form-item-row:last-child {
+ border-bottom: none;
+ }
+
+ .item-label-row {
+ width: 220rpx;
+ flex-shrink: 0;
+ display: flex;
+ align-items: center;
+ }
+
+ .label-text-bold {
+ font-size: 30rpx;
+ font-weight: 600;
+ color: #333;
+ }
+
+ .label-required {
+ font-size: 30rpx;
+ color: #ff4d4f;
+ margin-left: 8rpx;
+ }
+
+ .item-input-row {
+ flex: 1;
+ font-size: 28rpx;
+ color: #222;
+ height: 60rpx;
+ line-height: 60rpx;
+ padding: 0 20rpx;
+ text-align: right;
+ background: #fafafa;
+ border-radius: 10rpx;
+ }
+
+ .input-placeholder-gray {
+ color: #999;
+ font-size: 28rpx;
+ }
+
+ /* 支付方式 */
+ .payment-item {
+ margin-top: 30rpx;
+ padding-top: 30rpx;
+ border-top: 1rpx solid #f0f0f0;
+ }
+
+ .payment-method {
+ display: flex;
+ align-items: center;
+ padding: 20rpx 0;
+ }
+
+ .payment-icon {
+ width: 48rpx;
+ height: 48rpx;
+ margin-right: 20rpx;
+ }
+
+ .payment-text {
+ font-size: 28rpx;
+ color: #333;
+ font-weight: 500;
+ flex: 1;
+ }
+
+ .payment-checkbox {
+ width: 40rpx;
+ height: 40rpx;
+ border-radius: 50%;
+ background: #52c41a;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .checkbox-text {
+ color: white;
+ font-size: 28rpx;
+ font-weight: 600;
+ }
+
+ /* 底部操作栏 */
+ .footer-section {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 20rpx 30rpx;
+ background: white;
+ box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.08);
+ margin-top: auto;
+ flex-shrink: 0;
+ }
+
+ .total-price {
+ display: flex;
+ align-items: baseline;
+ flex: 1;
+ }
+
+ .total-label {
+ font-size: 30rpx;
+ color: #333;
+ font-weight: 600;
+ }
+
+ .total-value {
+ display: flex;
+ align-items: baseline;
+ }
+
+ .submit-btn {
+ width: 240rpx;
+ height: 88rpx;
+ border-radius: 44rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ overflow: hidden;
+ transition: all 0.3s;
+ }
+
+ .submit-btn-active {
+ background: linear-gradient(135deg, #52c41a, #389e0d);
+ box-shadow: 0 8rpx 32rpx rgba(255, 77, 79, 0.4);
+ }
+
+ .submit-btn-disabled {
+ background: #ccc;
+ box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
+ }
+
+ .submit-btn-active:active {
+ transform: scale(0.98);
+ box-shadow: 0 4rpx 20rpx rgba(255, 77, 79, 0.5);
+ }
+
+ .btn-text {
+ font-size: 32rpx;
+ color: white;
+ font-weight: 600;
+ position: relative;
+ z-index: 2;
+ }
+
+ .btn-glow {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
+ transform: translateX(-100%);
+ animation: btnGlow 3s infinite;
+ }
+
+ @keyframes btnGlow {
+ 0% { transform: translateX(-100%); }
+ 50% { transform: translateX(100%); }
+ 100% { transform: translateX(100%); }
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/tixian/components/mode1/mode1.js b/miniprogram/pages/tixian/components/mode1/mode1.js
new file mode 100644
index 0000000..097c9aa
--- /dev/null
+++ b/miniprogram/pages/tixian/components/mode1/mode1.js
@@ -0,0 +1,484 @@
+// pages/tixian/tixian.js
+// 子组件版本 - 完整代码,仅适配生命周期,业务逻辑不动
+
+import request from '../../../../utils/request.js';
+import upload from '../../../../utils/upload.js';
+import PopupService from '../../../../services/popupService.js';
+
+const app = getApp();
+const PAGE_SIZE = 10;
+let lastPullDownTime = 0;
+const PULL_DOWN_INTERVAL = 2000;
+
+Component({
+ data: {
+ imgUrls: {
+ pageBg: '',
+ cardBg: '',
+ iconWechat: '',
+ iconAlipay: '',
+ iconRecord: '',
+ iconRefresh: '',
+ },
+ assetList: [],
+ totalAmount: '0.00',
+ selectedAsset: null,
+ txfangshi: null,
+ txdianhua: '',
+ txzh: '',
+ txtupian: '',
+ ossImageUrl: '',
+ showTixianModal: false,
+ tixianAmount: '',
+ shouxufei: '0.00',
+ shijidaozhang: '0.00',
+ currentRate: '0.00',
+ recordList: [],
+ page: 1,
+ nomore: false,
+ loading: false,
+ isRefreshing: false,
+ canloadmore: true,
+ showSkfsModal: false,
+ tempTxfangshi: null,
+ tempTxPhone: '',
+ tempTxAccount: '',
+ tempTxImage: '',
+ choosingImage: false,
+ showModifyModal: false,
+ modifyingRecord: null,
+ modifyTxfangshi: null,
+ modifyTxPhone: '',
+ modifyTxAccount: '',
+ modifyTxImage: '',
+ modifyImageUrl: '',
+ showFailModal: false,
+ currentFailReason: '',
+ isLoading: false,
+ },
+
+ attached() {
+ this.initImageUrls();
+ this.loadAllAssets();
+ this.loadWithdrawMethod();
+ this.loadWithdrawRecords(true);
+ },
+
+ pageLifetimes: {
+ show() {
+ this.registerNotification();
+ PopupService.checkAndShow(this, 'tixian');
+ },
+ hide() {
+ const popup = this.selectComponent('#popupNotice');
+ popup?.cleanup?.();
+ }
+ },
+
+ methods: {
+ // 下拉刷新
+ onPullDownRefresh() {
+ if (this.data.isRefreshing) {
+ wx.stopPullDownRefresh();
+ return;
+ }
+ this.setData({ isRefreshing: true });
+ this.loadWithdrawRecords(true).then(() => {
+ wx.stopPullDownRefresh();
+ this.setData({ isRefreshing: false });
+ }).catch(() => {
+ wx.stopPullDownRefresh();
+ this.setData({ isRefreshing: false });
+ });
+ },
+
+ // 底部按钮加载更多
+ loadMoreRecords() {
+ if (this.data.nomore) {
+ wx.showToast({ title: '没有更多记录了', icon: 'none' });
+ return;
+ }
+ if (this.data.loading) return;
+ this.loadWithdrawRecords();
+ },
+
+ initImageUrls() {
+ const ossBase = app.globalData.ossImageUrl || '';
+ const imgDir = `${ossBase}beijing/tixian/`;
+ this.setData({
+ imgUrls: {
+ pageBg: `${imgDir}page_bg.png`,
+ cardBg: `${imgDir}card_bg.png`,
+ iconWechat: `${imgDir}icon_wechat.png`,
+ iconAlipay: `${imgDir}icon_alipay.png`,
+ iconRecord: `${imgDir}icon_record.png`,
+ iconRefresh: `${imgDir}icon_refresh.png`,
+ assetTileBg: `${imgDir}asset_tile_bg.png`,
+ }
+ });
+ },
+
+ async loadAllAssets() {
+ this.setData({ isLoading: true });
+ try {
+ const res = await request({ url: '/yonghu/tixianzichan', method: 'POST' });
+ if (res?.data?.code === 200) {
+ const data = res.data.data;
+ const assets = [];
+ if (data.dashou_yue && parseFloat(data.dashou_yue) > 0) {
+ assets.push({ type: 'dashou_yue', label: '打手佣金', amount: data.dashou_yue, rate: data.dashou_rate || 0 });
+ }
+ if (data.dashou_yajin && parseFloat(data.dashou_yajin) > 0) {
+ assets.push({ type: 'dashou_yajin', label: '打手保证金', amount: data.dashou_yajin, rate: data.dashou_yajin_rate || 0 });
+ }
+ if (data.shangjia_yue && parseFloat(data.shangjia_yue) > 0) {
+ assets.push({ type: 'shangjia_yue', label: '商家余额', amount: data.shangjia_yue, rate: data.shangjia_rate || 0 });
+ }
+ if (data.guanshi_fenyong && parseFloat(data.guanshi_fenyong) > 0) {
+ assets.push({ type: 'guanshi_fenyong', label: '管事分红', amount: data.guanshi_fenyong, rate: data.guanshi_rate || 0 });
+ }
+ if (data.zuzhang_fenyong && parseFloat(data.zuzhang_fenyong) > 0) {
+ assets.push({ type: 'zuzhang_fenyong', label: '组长分红', amount: data.zuzhang_fenyong, rate: data.zuzhang_rate || 0 });
+ }
+ if (data.kaoheguan_fenyong && parseFloat(data.kaoheguan_fenyong) > 0) {
+ assets.push({ type: 'kaoheguan_fenyong', label: '考核官分佣', amount: data.kaoheguan_fenyong, rate: data.kaoheguan_rate || 0 });
+ }
+ let total = 0;
+ assets.forEach(item => { total += parseFloat(item.amount); });
+ this.setData({ assetList: assets, totalAmount: total.toFixed(2), isLoading: false });
+ // 缓存费率
+ wx.setStorageSync('dashouRate', data.dashou_rate || 0);
+ wx.setStorageSync('dashou_yajin_rate', data.dashou_yajin_rate || 0);
+ wx.setStorageSync('shangjia_rate', data.shangjia_rate || 0);
+ wx.setStorageSync('guanshi_rate', data.guanshi_rate || 0);
+ wx.setStorageSync('zuzhang_rate', data.zuzhang_rate || 0);
+ wx.setStorageSync('kaoheguan_rate', data.kaoheguan_rate || 0);
+ } else {
+ throw new Error(res?.data?.msg || '获取资产失败');
+ }
+ } catch (err) {
+ console.error(err);
+ wx.showToast({ title: '加载失败', icon: 'none' });
+ this.setData({ isLoading: false });
+ }
+ },
+
+ async loadWithdrawMethod() {
+ const txzh = wx.getStorageSync('txzh') || '';
+ const txtupian = wx.getStorageSync('txtupian') || '';
+ const txfangshi = wx.getStorageSync('txfangshi') || null;
+ const txdianhua = wx.getStorageSync('txdianhua') || '';
+ if (txzh || txtupian) {
+ this.setData({ txzh, txtupian, txfangshi, txdianhua, ossImageUrl: app.globalData.ossImageUrl || '' });
+ } else {
+ try {
+ const res = await request({ url: '/yonghu/tixianhq', method: 'POST' });
+ if (res?.data?.code === 0) {
+ const data = res.data.data;
+ this.setData({
+ txzh: data.txzh || '',
+ txtupian: data.txtupian || '',
+ txfangshi: data.txfangshi || null,
+ txdianhua: data.txdianhua || '',
+ });
+ wx.setStorageSync('txzh', data.txzh || '');
+ wx.setStorageSync('txtupian', data.txtupian || '');
+ wx.setStorageSync('txfangshi', data.txfangshi || null);
+ wx.setStorageSync('txdianhua', data.txdianhua || '');
+ }
+ } catch (err) {}
+ }
+ },
+
+ onSelectAsset(e) {
+ const index = e.currentTarget.dataset.index;
+ const asset = this.data.assetList[index];
+ if (!asset || parseFloat(asset.amount) <= 0) {
+ wx.showToast({ title: '无可提现金额', icon: 'none' });
+ return;
+ }
+ this.setData({
+ selectedAsset: asset,
+ showTixianModal: true,
+ tixianAmount: '',
+ shouxufei: '0.00',
+ shijidaozhang: '0.00',
+ currentRate: asset.rate
+ });
+ },
+
+ onAmountInput(e) {
+ let val = e.detail.value;
+ if (val && !/^\d*\.?\d{0,2}$/.test(val)) return;
+ this.setData({ tixianAmount: val });
+ if (val && parseFloat(val) > 0) {
+ const amount = parseFloat(val);
+ const rate = parseFloat(this.data.selectedAsset.rate) || 0;
+ const fee = amount * rate;
+ const actual = amount - fee;
+ this.setData({ shouxufei: fee.toFixed(2), shijidaozhang: actual.toFixed(2) });
+ } else {
+ this.setData({ shouxufei: '0.00', shijidaozhang: '0.00' });
+ }
+ },
+
+ onQuickAmount(e) {
+ const val = e.currentTarget.dataset.value;
+ this.setData({ tixianAmount: val.toString() });
+ this.onAmountInput({ detail: { value: val.toString() } });
+ },
+
+ async onConfirmWithdraw() {
+ const { selectedAsset, tixianAmount, txfangshi, txzh, txtupian, txdianhua } = this.data;
+ if (!selectedAsset) { wx.showToast({ title: '请选择提现项目', icon: 'none' }); return; }
+ const amount = parseFloat(tixianAmount);
+ if (isNaN(amount) || amount <= 0) { wx.showToast({ title: '请输入有效金额', icon: 'none' }); return; }
+ if (amount > parseFloat(selectedAsset.amount)) { wx.showToast({ title: '超过可提现金额', icon: 'none' }); return; }
+ if (!txfangshi || (!txzh && !txtupian)) {
+ wx.showToast({ title: '请先设置收款方式', icon: 'none' });
+ this.setData({ showTixianModal: false });
+ this.onSetSkfs();
+ return;
+ }
+ let leixing = 1;
+ switch (selectedAsset.type) {
+ case 'dashou_yue': leixing = 1; break;
+ case 'dashou_yajin': leixing = 5; break;
+ case 'shangjia_yue': leixing = 6; break;
+ case 'guanshi_fenyong': leixing = 2; break;
+ case 'zuzhang_fenyong': leixing = 3; break;
+ case 'kaoheguan_fenyong': leixing = 4; break;
+ }
+ let txskfs = 0;
+ if (txzh && txtupian) txskfs = 3;
+ else if (txzh) txskfs = 1;
+ else if (txtupian) txskfs = 2;
+ try {
+ const res = await request({
+ url: '/yonghu/tixian',
+ method: 'POST',
+ data: { leixing, jine: amount.toFixed(2), fangshi: txfangshi, txskfs }
+ });
+ if (res?.data?.code === 0) {
+ const newAssetList = this.data.assetList.map(item => {
+ if (item.type === selectedAsset.type) {
+ const newAmount = (parseFloat(item.amount) - amount).toFixed(2);
+ return { ...item, amount: newAmount };
+ }
+ return item;
+ });
+ let newTotal = 0;
+ newAssetList.forEach(item => { newTotal += parseFloat(item.amount); });
+ this.setData({ assetList: newAssetList, totalAmount: newTotal.toFixed(2), showTixianModal: false, selectedAsset: null });
+ this.loadWithdrawRecords(true);
+ wx.showToast({ title: '提现申请已提交', icon: 'success' });
+ } else {
+ wx.showToast({ title: res?.data?.msg || '提现失败', icon: 'none' });
+ }
+ } catch (err) {
+ wx.showToast({ title: '网络错误', icon: 'none' });
+ }
+ },
+
+ onCloseTixianModal() {
+ this.setData({ showTixianModal: false, selectedAsset: null, tixianAmount: '' });
+ },
+
+ onSetSkfs() {
+ this.setData({
+ showSkfsModal: true,
+ tempTxfangshi: this.data.txfangshi,
+ tempTxPhone: this.data.txdianhua,
+ tempTxAccount: this.data.txzh,
+ tempTxImage: ''
+ });
+ },
+
+ onSelectFangshi(e) {
+ this.setData({ tempTxfangshi: e.currentTarget.dataset.fangshi });
+ },
+
+ onPhoneInput(e) { this.setData({ tempTxPhone: e.detail.value }); },
+ onAccountInput(e) { this.setData({ tempTxAccount: e.detail.value }); },
+
+ onChooseImage() {
+ if (this.data.choosingImage) return;
+ this.setData({ choosingImage: true });
+ wx.chooseMedia({
+ count: 1, mediaType: ['image'],
+ success: (res) => { this.setData({ tempTxImage: res.tempFiles[0].tempFilePath }); },
+ complete: () => { this.setData({ choosingImage: false }); }
+ });
+ },
+
+ onPreviewImage() {
+ if (this.data.tempTxImage) wx.previewImage({ urls: [this.data.tempTxImage] });
+ },
+
+ onDeleteImage() { this.setData({ tempTxImage: '' }); },
+
+ async onConfirmSkfs() {
+ const { tempTxfangshi, tempTxPhone, tempTxAccount, tempTxImage } = this.data;
+ if (!tempTxfangshi) { wx.showToast({ title: '请选择提现方式', icon: 'none' }); return; }
+ if (!tempTxPhone) { wx.showToast({ title: '请输入收款人电话', icon: 'none' }); return; }
+ if (tempTxfangshi == 1 && !tempTxImage) { wx.showToast({ title: '请上传微信收款码', icon: 'none' }); return; }
+ if (tempTxfangshi == 2 && !tempTxAccount && !tempTxImage) {
+ wx.showToast({ title: '请填写支付宝账号或上传收款码', icon: 'none' }); return;
+ }
+ const formData = { txdianhua: tempTxPhone, txzh: tempTxAccount || '' };
+ try {
+ const res = await upload({ url: '/yonghu/sksc', formData, filePath: tempTxImage || null, fileName: 'file' });
+ if (res?.data?.code === 0) {
+ const data = res.data.data;
+ this.setData({
+ txfangshi: tempTxfangshi, txdianhua: tempTxPhone, txzh: data.txzh || '', txtupian: data.txtupian || '', showSkfsModal: false
+ });
+ wx.setStorageSync('txfangshi', tempTxfangshi);
+ wx.setStorageSync('txdianhua', tempTxPhone);
+ wx.setStorageSync('txzh', data.txzh || '');
+ wx.setStorageSync('txtupian', data.txtupian || '');
+ wx.showToast({ title: '设置成功', icon: 'success' });
+ } else {
+ wx.showToast({ title: res?.data?.msg || '保存失败', icon: 'none' });
+ }
+ } catch (err) {
+ wx.showToast({ title: '网络错误', icon: 'none' });
+ }
+ },
+
+ onCloseSkfsModal() { this.setData({ showSkfsModal: false }); },
+
+ async loadWithdrawRecords(refresh = false) {
+ const now = Date.now();
+ if (now - lastPullDownTime < PULL_DOWN_INTERVAL && !refresh) return;
+ if (this.data.loading || this.data.isRefreshing) return;
+ const page = refresh ? 1 : this.data.page;
+ if (this.data.nomore && !refresh) return;
+ this.setData({ loading: true, isRefreshing: refresh });
+ lastPullDownTime = now;
+ try {
+ const res = await request({ url: '/yonghu/tixianjiluhq', method: 'POST', data: { page, limit: PAGE_SIZE } });
+ if (res?.data?.code === 0) {
+ const list = (res.data.data.list || []).map(item => ({
+ ...item,
+ statusText: this.getStatusText(item.zhuangtai),
+ statusColor: this.getStatusColor(item.zhuangtai)
+ }));
+ const newList = refresh ? list : [...this.data.recordList, ...list];
+ const hasMore = res.data.data.has_more === true;
+ this.setData({
+ recordList: newList, page: page + 1, nomore: !hasMore, canloadmore: hasMore,
+ loading: false, isRefreshing: false
+ });
+ } else {
+ this.setData({ loading: false, isRefreshing: false });
+ }
+ } catch (err) {
+ this.setData({ loading: false, isRefreshing: false });
+ }
+ },
+
+ onModifyRecord(e) {
+ const record = e.currentTarget.dataset.record;
+ if (parseInt(record.zhuangtai) !== 1) {
+ wx.showToast({ title: '只有审核中的记录可修改', icon: 'none' });
+ return;
+ }
+ this.setData({
+ modifyingRecord: record,
+ modifyTxfangshi: record.fangshi,
+ modifyTxPhone: record.txdianhua || '',
+ modifyTxAccount: record.txzh || '',
+ modifyTxImage: '',
+ modifyImageUrl: record.txtupian ? (app.globalData.ossImageUrl + record.txtupian) : '',
+ showModifyModal: true
+ });
+ },
+
+ onSelectModifyFangshi(e) { this.setData({ modifyTxfangshi: e.currentTarget.dataset.fangshi }); },
+ onModifyPhoneInput(e) { this.setData({ modifyTxPhone: e.detail.value }); },
+ onModifyAccountInput(e) { this.setData({ modifyTxAccount: e.detail.value }); },
+
+ onChooseModifyImage() {
+ if (this.data.choosingImage) return;
+ this.setData({ choosingImage: true });
+ wx.chooseMedia({
+ count: 1, mediaType: ['image'],
+ success: (res) => { this.setData({ modifyTxImage: res.tempFiles[0].tempFilePath }); },
+ complete: () => { this.setData({ choosingImage: false }); }
+ });
+ },
+
+ onPreviewModifyImage() {
+ if (this.data.modifyTxImage) wx.previewImage({ urls: [this.data.modifyTxImage] });
+ else if (this.data.modifyImageUrl) wx.previewImage({ urls: [this.data.modifyImageUrl] });
+ },
+
+ onDeleteModifyImage() { this.setData({ modifyTxImage: '' }); },
+
+ async onConfirmModify() {
+ const { modifyTxfangshi, modifyTxPhone, modifyTxAccount, modifyTxImage, modifyingRecord } = this.data;
+ if (!modifyTxfangshi) { wx.showToast({ title: '请选择提现方式', icon: 'none' }); return; }
+ if (!modifyTxPhone) { wx.showToast({ title: '请输入收款人电话', icon: 'none' }); return; }
+ if (modifyTxfangshi == 1 && !modifyTxImage) { wx.showToast({ title: '请上传微信收款码', icon: 'none' }); return; }
+ if (modifyTxfangshi == 2 && !modifyTxAccount && !modifyTxImage) {
+ wx.showToast({ title: '请填写支付宝账号或上传收款码', icon: 'none' }); return;
+ }
+ const formData = { tixian_id: modifyingRecord.id, fangshi: modifyTxfangshi, txdianhua: modifyTxPhone, txzh: modifyTxAccount || '' };
+ try {
+ const res = await upload({ url: '/yonghu/yonghutxshxg', formData, filePath: modifyTxImage || null, fileName: 'file' });
+ if (res?.data?.code === 0) {
+ const data = res.data.data;
+ const newList = this.data.recordList.map(item => {
+ if (item.id === modifyingRecord.id) {
+ return { ...item, fangshi: modifyTxfangshi, txdianhua: modifyTxPhone, txzh: modifyTxAccount || '', txtupian: data.txtupian || modifyingRecord.txtupian };
+ }
+ return item;
+ });
+ this.setData({ recordList: newList, showModifyModal: false, modifyingRecord: null });
+ wx.showToast({ title: '修改成功', icon: 'success' });
+ } else {
+ wx.showToast({ title: res?.data?.msg || '修改失败', icon: 'none' });
+ }
+ } catch (err) {
+ wx.showToast({ title: '网络错误', icon: 'none' });
+ }
+ },
+
+ onCloseModifyModal() { this.setData({ showModifyModal: false, modifyingRecord: null }); },
+
+ onViewFailReason(e) {
+ const record = e.currentTarget.dataset.record;
+ const reason = record.fail_reason || record.shibaiyuanyin || '暂无详细信息';
+ this.setData({ currentFailReason: reason, showFailModal: true });
+ },
+
+ onCloseFailModal() { this.setData({ showFailModal: false }); },
+
+ getStatusText(zhuangtai) {
+ const s = parseInt(zhuangtai);
+ if (s === 1) return '审核中';
+ if (s === 2) return '成功';
+ if (s === 3) return '失败';
+ return '未知';
+ },
+ getStatusColor(zhuangtai) {
+ const s = parseInt(zhuangtai);
+ if (s === 1) return '#F5A623';
+ if (s === 2) return '#2ED158';
+ if (s === 3) return '#FF4D4F';
+ return '#999';
+ },
+
+ registerNotification() {
+ const comp = this.selectComponent('#global-notification');
+ if (comp?.showNotification) {
+ app.globalData.globalNotification = {
+ show: (data) => comp.showNotification(data),
+ hide: () => comp.hideNotification()
+ };
+ }
+ },
+ stopPropagation() {}
+ }
+});
\ No newline at end of file
diff --git a/miniprogram/pages/tixian/components/mode1/mode1.json b/miniprogram/pages/tixian/components/mode1/mode1.json
new file mode 100644
index 0000000..9fd2c37
--- /dev/null
+++ b/miniprogram/pages/tixian/components/mode1/mode1.json
@@ -0,0 +1,21 @@
+
+{
+ "component": true,
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification",
+ "popup-notice": "/components/popup-notice/popup-notice"
+ },
+ "navigationBarTitleText": "提现中心",
+ "navigationBarBackgroundColor": "#1a0b2e",
+ "navigationBarTextStyle": "white",
+ "backgroundColor": "#1a0b2e",
+ "enablePullDownRefresh": false,
+ "onReachBottomDistance": 50,
+ "navigationStyle": "default",
+ "disableScroll": false
+ }
+
+
+
+
+
diff --git a/miniprogram/pages/tixian/components/mode1/mode1.wxml b/miniprogram/pages/tixian/components/mode1/mode1.wxml
new file mode 100644
index 0000000..7fb3bab
--- /dev/null
+++ b/miniprogram/pages/tixian/components/mode1/mode1.wxml
@@ -0,0 +1,133 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ 可提总余额
+ ¥{{totalAmount}}
+
+
+
+
+
+
+ ← 左右滑动查看更多资产 →
+
+
+
+
+
+
+
+ {{item.label}}
+ ¥{{item.amount}}
+ 费率 {{item.rate}}%
+
+
+ 暂无可用资金
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ¥{{item.jine}}
+ {{item.create}}
+
+
+ {{item.fangshi == 1 ? '微信' : '支付宝'}}
+ {{item.leixing == 1 ? '佣金' : (item.leixing == 2 ? '分红' : (item.leixing == 3 ? '组长' : (item.leixing == 4 ? '考核官' : '保证金')))}}
+
+
+ {{item.statusText}} ✏修改
+
+ {{item.statusText}}
+ 详情
+
+ {{item.statusText}}
+
+
+ 加载中...
+ —— 没有更多了 ——
+ 加载更多记录
+
+
+
+
+
+
+
+
+
+
+
+
+ 项目{{selectedAsset.label}}
+ 可提¥{{selectedAsset.amount}}
+ 费率{{currentRate}}%
+ 金额
+ ¥{{item}}
+ 手续费 ¥{{shouxufei}}到账 ¥{{shijidaozhang}}
+ 收款方式 {{txfangshi == 1 ? '微信' : (txfangshi == 2 ? '支付宝' : '未设置')}}更换
+
+
+
+
+
+
+
+
+
+
+ 微信支付宝
+
+
+ + 上传收款码预览删除
+
+
+
+
+
+
+
+
+
+
+ 微信支付宝
+
+
+ + 上传新图片预览删除
+
+
+
+
+
+
+
+
+
+ {{currentFailReason}}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/tixian/components/mode1/mode1.wxss b/miniprogram/pages/tixian/components/mode1/mode1.wxss
new file mode 100644
index 0000000..a4c4b31
--- /dev/null
+++ b/miniprogram/pages/tixian/components/mode1/mode1.wxss
@@ -0,0 +1,424 @@
+/* pages/tixian/tixian.wxss - 终极优化版 */
+
+page {
+ background-color: #f5f7fb;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+ height: 100%;
+ }
+
+ .app {
+ display: flex;
+ flex-direction: column;
+ min-height: 100vh;
+ padding: 0 24rpx 0;
+ position: relative;
+ z-index: 1;
+ box-sizing: border-box;
+ }
+
+ /* 页面背景模糊极低,不影响阅读 */
+ .global-bg {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: -1;
+ opacity: 0.1;
+ object-fit: cover;
+ filter: blur(1rpx);
+ }
+
+ /* 资产区域整体与顶部保持间距 */
+ .assets-section {
+ margin-top: 24rpx;
+ flex-shrink: 0;
+ }
+
+ /* 主卡片玻璃质感 - 模糊加重避免抢眼,同时文字对比加强 */
+ .main-card {
+ background: rgba(248, 250, 252, 0.88); /* 稍微降低透明度,让背景更柔和 */
+ backdrop-filter: blur(28rpx); /* 增强模糊,不抢眼 */
+ -webkit-backdrop-filter: blur(28rpx);
+ border-radius: 48rpx;
+ padding: 30rpx 28rpx 24rpx; /* 稍微缩小高度 */
+ margin-bottom: 20rpx;
+ box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.04);
+ border: 1rpx solid rgba(255, 255, 255, 0.4);
+ background-size: cover;
+ }
+
+ /* 总余额 - 字体加深加粗,增加阴影提高可读性 */
+ .balance-wrap {
+ text-align: center;
+ margin-bottom: 20rpx; /* 减小间距,让卡片更紧凑 */
+ }
+ .balance-label {
+ font-size: 28rpx;
+ color: #0f172a; /* 更深色 */
+ letter-spacing: 1px;
+ display: block;
+ margin-bottom: 10rpx;
+ font-weight: 700; /* 加粗 */
+ text-shadow: 0 1rpx 2rpx rgba(255,255,255,0.8);
+ }
+ .balance-value {
+ font-size: 68rpx;
+ font-weight: 800;
+ color: #0f172a;
+ font-family: 'DIN', monospace;
+ letter-spacing: -1rpx;
+ text-shadow: 0 1rpx 2rpx rgba(255,255,255,0.6);
+ }
+
+ /* 内部分隔线(横杠)- 明显且保持层次 */
+ .inner-divider {
+ width: 60%;
+ height: 2rpx;
+ background: linear-gradient(90deg, transparent, #64748b, #64748b, transparent); /* 加深颜色 */
+ margin: 0 auto 20rpx auto;
+ border-radius: 2rpx;
+ opacity: 0.9;
+ }
+
+ /* 左右滑动提示 - 字体明显 */
+ .assets-hint {
+ display: block;
+ text-align: center;
+ font-size: 24rpx;
+ color: #334155; /* 深灰 */
+ margin-bottom: 14rpx;
+ font-weight: 600;
+ }
+
+ .assets-scroll {
+ width: 100%;
+ white-space: nowrap;
+ }
+ .assets-track {
+ display: inline-flex;
+ gap: 16rpx;
+ }
+
+ /* 资产小卡片 - 背景半透模糊,字体深色加粗 */
+ .asset-tile {
+ width: 170rpx;
+ background: rgba(255, 255, 255, 0.75); /* 稍微提高透明度,让模糊背景透出 */
+ backdrop-filter: blur(8rpx); /* 自身轻微模糊,不抢眼 */
+ border-radius: 28rpx;
+ padding: 18rpx 12rpx;
+ display: inline-flex;
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
+ border: 1rpx solid rgba(255,255,255,0.5);
+ box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.02);
+ transition: all 0.2s;
+ white-space: normal;
+ background-size: cover;
+ background-position: center;
+ }
+ .asset-tile:active {
+ background: rgba(255, 255, 255, 0.9);
+ transform: scale(0.98);
+ }
+ .asset-name {
+ font-size: 24rpx;
+ font-weight: 700; /* 加粗 */
+ color: #1e293b; /* 深色 */
+ margin-bottom: 10rpx;
+ text-shadow: 0 1rpx 1rpx rgba(255,255,255,0.5);
+ }
+ .asset-balance {
+ font-size: 28rpx;
+ font-weight: 800; /* 加粗 */
+ color: #2d6a4f;
+ font-family: monospace;
+ margin-bottom: 6rpx;
+ text-shadow: 0 1rpx 1rpx rgba(255,255,255,0.3);
+ }
+ .asset-fee {
+ font-size: 20rpx;
+ font-weight: 600; /* 加粗并加深 */
+ color: #475569;
+ text-shadow: 0 1rpx 1rpx rgba(255,255,255,0.3);
+ }
+ .assets-empty {
+ width: 400rpx;
+ text-align: center;
+ color: #a0abb9;
+ padding: 40rpx 0;
+ }
+
+ /* 外部层次分隔线 - 保持不变,保留分层 */
+ .divider-line {
+ height: 2rpx;
+ background: linear-gradient(90deg, transparent, #cbd5e1, #cbd5e1, transparent);
+ margin: 16rpx 0 20rpx;
+ width: 80%;
+ margin-left: auto;
+ margin-right: auto;
+ }
+
+ /* 提现记录区 - 无改动,自然贴底 */
+ .history-section {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ min-height: 0;
+ margin-top: 0;
+ }
+ .history-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: baseline;
+ padding: 0 8rpx 20rpx;
+ flex-shrink: 0;
+ }
+ .history-title {
+ font-size: 34rpx;
+ font-weight: 600;
+ color: #1e293b;
+ }
+ .history-settings {
+ font-size: 28rpx;
+ color: #3b82f6;
+ }
+ .history-scroll {
+ flex: 1;
+ width: 100%;
+ padding: 0 20rpx;
+ box-sizing: border-box;
+ }
+ .history-item {
+ display: flex;
+ align-items: center;
+ padding: 28rpx 0;
+ border-bottom: 1rpx solid rgba(0,0,0,0.05);
+ }
+ .history-info {
+ width: 160rpx;
+ }
+ .history-amount {
+ font-size: 36rpx;
+ font-weight: 700;
+ color: #1e293b;
+ display: block;
+ }
+ .history-time {
+ font-size: 22rpx;
+ color: #94a3b8;
+ }
+ .history-detail {
+ flex: 1;
+ padding: 0 20rpx;
+ }
+ .history-method {
+ font-size: 30rpx;
+ color: #334155;
+ display: block;
+ margin-bottom: 6rpx;
+ }
+ .history-type {
+ font-size: 24rpx;
+ color: #94a3b8;
+ }
+ .history-status {
+ text-align: right;
+ }
+ .status-badge {
+ font-size: 26rpx;
+ font-weight: 500;
+ padding: 6rpx 14rpx;
+ border-radius: 30rpx;
+ display: inline-block;
+ }
+ .status-wait {
+ color: #f59e0b;
+ background: rgba(245,158,11,0.1);
+ }
+ .status-done {
+ color: #10b981;
+ background: rgba(16,185,129,0.1);
+ }
+ .status-fail {
+ color: #ef4444;
+ background: rgba(239,68,68,0.1);
+ }
+ .fail-link {
+ margin-left: 8rpx;
+ text-decoration: underline;
+ }
+ .load-tip, .more-tip {
+ text-align: center;
+ padding: 30rpx;
+ font-size: 24rpx;
+ color: #94a3b8;
+ }
+ .load-more-btn {
+ margin: 16rpx 0 20rpx;
+ padding: 20rpx;
+ text-align: center;
+ background: rgba(255,255,255,0.7);
+ backdrop-filter: blur(8rpx);
+ border-radius: 40rpx;
+ font-size: 28rpx;
+ color: #3b82f6;
+ border: 1rpx solid rgba(255,255,255,0.5);
+ }
+ .load-more-btn:active {
+ background: rgba(255,255,255,0.9);
+ }
+ .safe-bottom {
+ height: 12rpx;
+ flex-shrink: 0;
+ }
+
+ /* ========== 弹窗样式(完整保留) ========== */
+ .modal-mask {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0,0,0,0.5);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+ }
+ .modal-dialog {
+ width: 600rpx;
+ background: rgba(255,255,255,0.96);
+ backdrop-filter: blur(20rpx);
+ border-radius: 48rpx;
+ overflow: hidden;
+ box-shadow: 0 20rpx 40rpx rgba(0,0,0,0.1);
+ border: 1rpx solid rgba(255,255,255,0.6);
+ }
+ .modal-header {
+ padding: 36rpx 32rpx 20rpx;
+ font-size: 36rpx;
+ font-weight: 700;
+ border-bottom: 1rpx solid rgba(0,0,0,0.05);
+ }
+ .modal-body {
+ padding: 32rpx;
+ }
+ .info-row, .amount-row, .method-row {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 24rpx;
+ font-size: 28rpx;
+ }
+ .amount-row input {
+ border: 1rpx solid rgba(0,0,0,0.1);
+ border-radius: 28rpx;
+ padding: 16rpx 20rpx;
+ width: 260rpx;
+ background: rgba(255,255,255,0.9);
+ }
+ .quick-row {
+ display: flex;
+ gap: 16rpx;
+ margin: 24rpx 0;
+ }
+ .quick-chip {
+ flex: 1;
+ background: rgba(241,245,249,0.8);
+ text-align: center;
+ padding: 12rpx;
+ border-radius: 40rpx;
+ font-size: 28rpx;
+ }
+ .fee-row {
+ display: flex;
+ justify-content: space-between;
+ background: rgba(248,250,252,0.8);
+ padding: 20rpx;
+ border-radius: 28rpx;
+ margin: 20rpx 0;
+ }
+ .link-text {
+ color: #3b82f6;
+ }
+ .toggle-row {
+ display: flex;
+ gap: 20rpx;
+ margin-bottom: 28rpx;
+ }
+ .toggle-item {
+ flex: 1;
+ text-align: center;
+ padding: 20rpx;
+ border: 1rpx solid rgba(0,0,0,0.08);
+ border-radius: 30rpx;
+ background: rgba(255,255,255,0.8);
+ }
+ .toggle-item.active {
+ border-color: #3b82f6;
+ background: #eff6ff;
+ }
+ .field-input {
+ width: 100%;
+ border: 1rpx solid rgba(0,0,0,0.08);
+ border-radius: 28rpx;
+ padding: 20rpx;
+ margin-bottom: 20rpx;
+ box-sizing: border-box;
+ background: rgba(255,255,255,0.9);
+ }
+ .upload-trigger {
+ background: rgba(241,245,249,0.8);
+ border: 1rpx dashed #cbd5e1;
+ border-radius: 28rpx;
+ padding: 28rpx;
+ text-align: center;
+ }
+ .image-preview {
+ margin-top: 16rpx;
+ position: relative;
+ }
+ .image-preview image {
+ width: 100%;
+ height: 200rpx;
+ border-radius: 28rpx;
+ }
+ .image-actions {
+ display: flex;
+ justify-content: space-around;
+ background: rgba(0,0,0,0.6);
+ padding: 12rpx;
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ color: white;
+ border-bottom-left-radius: 28rpx;
+ border-bottom-right-radius: 28rpx;
+ }
+ .current-img {
+ margin: 20rpx 0;
+ }
+ .current-img image {
+ width: 100%;
+ height: 140rpx;
+ border-radius: 24rpx;
+ }
+ .modal-footer {
+ display: flex;
+ border-top: 1rpx solid rgba(0,0,0,0.05);
+ }
+ .modal-btn {
+ flex: 1;
+ text-align: center;
+ padding: 28rpx;
+ font-size: 30rpx;
+ }
+ .modal-btn.cancel {
+ color: #8f9bb3;
+ border-right: 1rpx solid rgba(0,0,0,0.05);
+ }
+ .modal-btn.confirm {
+ color: #3b82f6;
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/tixian/components/mode2/mode2.js b/miniprogram/pages/tixian/components/mode2/mode2.js
new file mode 100644
index 0000000..6ccb60b
--- /dev/null
+++ b/miniprogram/pages/tixian/components/mode2/mode2.js
@@ -0,0 +1,587 @@
+// pages/tixian/components/mode2/mode2.js
+// UI 与 mode1 完全一致,业务逻辑为自动打款审核收款
+import request from '../../../../utils/request.js';
+import PopupService from '../../../../services/popupService.js';
+
+const app = getApp();
+const yemianshuju = 5;
+let lastPullDownTime = 0;
+const PULL_DOWN_INTERVAL = 2000;
+
+const TX_STATUS = {
+ SHENHEZHONG: 1,
+ TIXIAN_CHENGGONG: 2,
+ TIXIAN_SHIBAI: 3,
+ SHENHE_CHENGGONG: 4,
+ SHENHE_SHIBAI: 5,
+ DAI_SHOUKUAN: 6,
+};
+
+const ASSET_CONFIG = {
+ dashou_yue: { leixing: 1, label: '打手佣金' },
+ guanshi_fenyong: { leixing: 2, label: '管事分红' },
+ zuzhang_fenyong: { leixing: 3, label: '组长分红' },
+ kaoheguan_fenyong: { leixing: 4, label: '考核官分佣' },
+ dashou_yajin: { leixing: 5, label: '打手保证金' },
+ shangjia_yue: { leixing: 6, label: '商家余额' },
+};
+
+const LEIXING_LABEL = {
+ 1: '打手佣金', 2: '管事分红', 3: '组长分红',
+ 4: '考核官分佣', 5: '打手保证金', 6: '商家余额',
+};
+
+Component({
+ properties: { options: { type: Object, value: {} } },
+ data: {
+ imgUrls: {
+ pageBg: '', cardBg: '', assetTileBg: '',
+ },
+ assetList: [],
+ totalAmount: '0.00',
+ selectedAsset: null,
+ isLoadingAssets: false,
+ tixianleixing: null,
+ tixianjine: '',
+ currentRate: '0.00',
+ showTixian: false,
+ recordList: [],
+ page: 1,
+ loading: false,
+ nomore: false,
+ canloadmore: true,
+ isRefreshing: false,
+ shouxufei: '0.00',
+ shijidaozhang: '0.00',
+ currentTixianId: null,
+ submitting: false,
+ mchId: '',
+ currentStatusFilter: 0,
+ statusTabs: [
+ { value: 0, label: '全部' },
+ { value: 1, label: '审核中' },
+ { value: 6, label: '待收款' },
+ { value: 4, label: '审核成功' },
+ { value: 5, label: '审核失败' },
+ { value: 2, label: '提现成功' },
+ { value: 3, label: '提现失败' },
+ ],
+ hasPendingCollect: false,
+ showCollectConfirm: false,
+ collectConfirmRecord: null,
+ showDetailModal: false,
+ detailTitle: '',
+ detailContent: '',
+ },
+ lifetimes: {
+ attached() {
+ this.initImageUrls();
+ this.chushihuashuju();
+ }
+ },
+ pageLifetimes: {
+ show() {
+ this.registerNotificationComponent();
+ this.loadAllAssets();
+ PopupService.checkAndShow(this, 'tixian');
+ },
+ hide() {
+ const popupComp = this.selectComponent('#popupNotice');
+ if (popupComp && popupComp.cleanup) popupComp.cleanup();
+ }
+ },
+ methods: {
+ registerNotificationComponent() {
+ const notificationComp = this.selectComponent('#global-notification');
+ if (notificationComp && notificationComp.showNotification) {
+ app.globalData.globalNotification = {
+ show: (data) => notificationComp.showNotification(data),
+ hide: () => notificationComp.hideNotification()
+ };
+ }
+ },
+
+ initImageUrls() {
+ const ossBase = app.globalData.ossImageUrl || '';
+ const imgDir = `${ossBase}beijing/tixian/`;
+ this.setData({
+ imgUrls: {
+ pageBg: `${imgDir}page_bg.png`,
+ cardBg: `${imgDir}card_bg.png`,
+ assetTileBg: `${imgDir}asset_tile_bg.png`,
+ }
+ });
+ },
+
+ parseZhuangtai(item) {
+ const raw = item.zhuangtai ?? item.status ?? item.state;
+ const n = parseInt(raw, 10);
+ return Number.isNaN(n) ? -1 : n;
+ },
+
+ /** 规范化列表字段(兼容后端多种命名 / 嵌套 audit) */
+ normalizeRecordFields(item) {
+ const audit = item.audit || item.shenhe || item.shenhe_info || {};
+ const shenheJiluId = item.shenhe_jilu_id ?? item.shenheJiluId
+ ?? item.audit_id ?? item.shenheid ?? audit.id ?? audit.shenhe_jilu_id ?? null;
+ const shenheDanhao = String(
+ item.shenhe_danhao || item.shenheDanhao || item.audit_shenhe_danhao
+ || audit.shenhe_danhao || audit.danhao || ''
+ ).trim();
+ const tixianjiluId = item.tixianjilu_id ?? item.tixianjiluId ?? item.id ?? null;
+ const zhuangtai = this.parseZhuangtai(item);
+ return {
+ ...item,
+ zhuangtai: zhuangtai >= 0 ? zhuangtai : item.zhuangtai,
+ shenhe_jilu_id: shenheJiluId,
+ shenhe_danhao: shenheDanhao,
+ tixianjilu_id: tixianjiluId,
+ };
+ },
+
+ hasAuditJiluId(item) {
+ const id = item.shenhe_jilu_id;
+ return id !== null && id !== undefined && id !== '' && id !== 0 && id !== '0';
+ },
+
+ hasShenheDanhao(item) {
+ return !!(item.shenhe_danhao && String(item.shenhe_danhao).trim());
+ },
+
+ isNewAuditRecord(item) {
+ return this.hasAuditJiluId(item) && this.hasShenheDanhao(item);
+ },
+
+ /** 待收款是否可点击:状态6 + 审核记录ID + 审核单号 缺一不可 */
+ canCollectRecord(item) {
+ return this.parseZhuangtai(item) === TX_STATUS.DAI_SHOUKUAN
+ && this.hasAuditJiluId(item)
+ && this.hasShenheDanhao(item);
+ },
+
+ getLeixingLabel(leixing) {
+ return LEIXING_LABEL[parseInt(leixing)] || '提现';
+ },
+
+ getStatusBadgeClass(status) {
+ switch (parseInt(status)) {
+ case TX_STATUS.SHENHEZHONG: return 'status-wait';
+ case TX_STATUS.TIXIAN_CHENGGONG:
+ case TX_STATUS.SHENHE_CHENGGONG: return 'status-done';
+ case TX_STATUS.DAI_SHOUKUAN: return 'status-pending';
+ case TX_STATUS.TIXIAN_SHIBAI:
+ case TX_STATUS.SHENHE_SHIBAI: return 'status-fail';
+ default: return 'status-wait';
+ }
+ },
+
+ processRecordItem(item) {
+ item = this.normalizeRecordFields(item);
+ const isNew = this.isNewAuditRecord(item);
+ const status = this.parseZhuangtai(item);
+ let statusText = '未知';
+ let canCollect = false;
+ let canShowDetail = false;
+ let detailReason = '';
+
+ switch (status) {
+ case TX_STATUS.SHENHEZHONG: statusText = '审核中'; break;
+ case TX_STATUS.TIXIAN_CHENGGONG: statusText = '提现成功'; break;
+ case TX_STATUS.TIXIAN_SHIBAI:
+ statusText = '提现失败';
+ if (item.fail_reason || item.bhliyou) {
+ canShowDetail = true;
+ detailReason = item.fail_reason || item.bhliyou;
+ }
+ break;
+ case TX_STATUS.SHENHE_CHENGGONG: statusText = '审核成功'; break;
+ case TX_STATUS.SHENHE_SHIBAI:
+ statusText = '审核失败';
+ if (item.bhliyou) { canShowDetail = true; detailReason = item.bhliyou; }
+ break;
+ case TX_STATUS.DAI_SHOUKUAN:
+ statusText = '待收款';
+ canCollect = this.canCollectRecord(item);
+ break;
+ }
+
+ return {
+ ...item,
+ isNewAudit: isNew,
+ leixingLabel: this.getLeixingLabel(item.leixing),
+ statusText,
+ statusBadgeClass: this.getStatusBadgeClass(status),
+ canCollect,
+ canShowDetail,
+ detailReason,
+ };
+ },
+
+ gengxinYue(data, leixing) {
+ if (!data) return;
+ if (data.new_yongjin !== undefined && data.new_yongjin !== null) {
+ app.globalData.yongjin = data.new_yongjin;
+ }
+ if (data.new_fenyong !== undefined && data.new_fenyong !== null) {
+ app.globalData.guanshi.fenyongtixian = data.new_fenyong;
+ }
+ if (data.new_yajin !== undefined && data.new_yajin !== null) {
+ app.globalData.yajin = data.new_yajin;
+ }
+ if (data.new_shangjia_yue !== undefined && data.new_shangjia_yue !== null) {
+ if (app.globalData.shangjia) app.globalData.shangjia.sjyue = data.new_shangjia_yue;
+ }
+ if (data.current_rate !== undefined && leixing) {
+ const rate = parseFloat(data.current_rate);
+ const rateKeyMap = { 1: 'dashouRate', 5: 'dashou_yajin_rate', 6: 'shangjia_rate', 2: 'guanshi_rate', 3: 'zuzhang_rate', 4: 'kaoheguan_rate' };
+ const key = rateKeyMap[leixing];
+ if (key) wx.setStorageSync(key, rate);
+ }
+ },
+
+ updatePendingCollectFlag(list) {
+ const hasPending = (list || []).some(item => item.canCollect);
+ if (hasPending !== this.data.hasPendingCollect) {
+ this.setData({ hasPendingCollect: hasPending });
+ }
+ },
+
+ async loadAllAssets() {
+ this.setData({ isLoadingAssets: true });
+ try {
+ const res = await request({ url: '/yonghu/tixianzichan', method: 'POST' });
+ if (res?.data?.code === 200 || res?.data?.code === 0) {
+ const data = res.data.data || {};
+ const assets = [];
+ const pushAsset = (type, amount, rate) => {
+ const cfg = ASSET_CONFIG[type];
+ if (!cfg || !amount || parseFloat(amount) <= 0) return;
+ assets.push({
+ type,
+ leixing: cfg.leixing,
+ label: cfg.label,
+ amount: parseFloat(amount).toFixed(2),
+ rate: rate || 0,
+ });
+ };
+ pushAsset('dashou_yue', data.dashou_yue, data.dashou_rate);
+ pushAsset('dashou_yajin', data.dashou_yajin, data.dashou_yajin_rate);
+ pushAsset('shangjia_yue', data.shangjia_yue, data.shangjia_rate);
+ pushAsset('guanshi_fenyong', data.guanshi_fenyong, data.guanshi_rate);
+ pushAsset('zuzhang_fenyong', data.zuzhang_fenyong, data.zuzhang_rate);
+ pushAsset('kaoheguan_fenyong', data.kaoheguan_fenyong, data.kaoheguan_rate);
+
+ let total = 0;
+ assets.forEach(item => { total += parseFloat(item.amount); });
+ this.setData({ assetList: assets, totalAmount: total.toFixed(2), isLoadingAssets: false });
+ } else {
+ throw new Error(res?.data?.msg || '获取资产失败');
+ }
+ } catch (err) {
+ console.error('loadAllAssets', err);
+ this.setData({ isLoadingAssets: false });
+ }
+ },
+
+ chushihuashuju() {
+ this.loadAllAssets();
+ this.huoquJilu(true);
+ },
+
+ /** 点击资产卡片(与 mode1 一致) */
+ onSelectAsset(e) {
+ const index = e.currentTarget.dataset.index;
+ const asset = this.data.assetList[index];
+ if (!asset || parseFloat(asset.amount) <= 0) {
+ wx.showToast({ title: '无可提现金额', icon: 'none' });
+ return;
+ }
+ this.setData({
+ selectedAsset: asset,
+ tixianleixing: asset.leixing,
+ currentRate: asset.rate,
+ tixianjine: '',
+ shouxufei: '0.00',
+ shijidaozhang: '0.00',
+ showTixian: true,
+ });
+ },
+
+ onJineInput(e) {
+ this.setData({ tixianjine: e.detail.value, shouxufei: '0.00', shijidaozhang: '0.00' });
+ },
+
+ onQuickAmountTap(e) {
+ const value = e.currentTarget.dataset.value;
+ this.setData({ tixianjine: value.toString(), shouxufei: '0.00', shijidaozhang: '0.00' });
+ },
+
+ onStatusFilterTap(e) {
+ const value = parseInt(e.currentTarget.dataset.value);
+ if (value === this.data.currentStatusFilter) return;
+ this.setData({ currentStatusFilter: value });
+ this.huoquJilu(true);
+ },
+
+ async onConfirmTixian() {
+ if (this.data.submitting) return;
+ const { tixianleixing, tixianjine, selectedAsset } = this.data;
+ if (!tixianleixing || !selectedAsset) {
+ wx.showToast({ title: '请选择提现类型', icon: 'none' });
+ return;
+ }
+ const jineValue = parseFloat(tixianjine);
+ if (!tixianjine || isNaN(jineValue) || jineValue <= 0) {
+ wx.showToast({ title: '请输入有效金额', icon: 'none' });
+ return;
+ }
+ if (jineValue > parseFloat(selectedAsset.amount)) {
+ wx.showToast({ title: `超过可提${selectedAsset.label}`, icon: 'none' });
+ return;
+ }
+
+ this.setData({ submitting: true });
+ try {
+ const res = await request({
+ url: '/yonghu/zddksh',
+ method: 'POST',
+ data: { leixing: tixianleixing, jine: jineValue.toFixed(2) }
+ });
+ if (res.statusCode === 200 && res.data && res.data.code === 0) {
+ const data = res.data.data || {};
+ this.gengxinYue(data, tixianleixing);
+ if (data.shouxufei !== undefined) {
+ this.setData({
+ shouxufei: parseFloat(data.shouxufei || 0).toFixed(2),
+ shijidaozhang: parseFloat(data.shijidaozhang || 0).toFixed(2),
+ });
+ }
+ this.setData({ showTixian: false, submitting: false, selectedAsset: null });
+ await this.loadAllAssets();
+ this.huoquJilu(true);
+ wx.showModal({
+ title: '申请已提交',
+ content: '您的提现申请已提交,请等待后台审核。审核通过后,请在本页「提现记录」中点击「收款」按钮,主动确认后才能到账微信零钱。',
+ showCancel: false,
+ });
+ } else {
+ wx.showModal({ title: '提现申请失败', content: res.data?.msg || '未知错误', showCancel: false });
+ this.setData({ submitting: false });
+ }
+ } catch (err) {
+ wx.showModal({ title: '网络错误', content: '网络连接失败,请稍后重试', showCancel: false });
+ this.setData({ submitting: false });
+ }
+ },
+
+ onCollectTap(e) {
+ const index = e.currentTarget.dataset.index;
+ const record = this.data.recordList[index];
+ if (!record) return;
+ if (this.parseZhuangtai(record) !== TX_STATUS.DAI_SHOUKUAN) {
+ wx.showToast({ title: '当前状态不可收款', icon: 'none' });
+ return;
+ }
+ if (!this.hasShenheDanhao(record) || !this.hasAuditJiluId(record)) {
+ wx.showToast({ title: '审核信息不完整,请刷新后重试', icon: 'none' });
+ return;
+ }
+ if (this.data.submitting) {
+ wx.showToast({ title: '正在处理中,请稍后', icon: 'none' });
+ return;
+ }
+ this.setData({ showCollectConfirm: true, collectConfirmRecord: record });
+ },
+
+ onCloseCollectConfirm() {
+ if (this.data.submitting) return;
+ this.setData({ showCollectConfirm: false, collectConfirmRecord: null });
+ },
+
+ async onConfirmCollect() {
+ if (this.data.submitting) return;
+ let record = this.data.collectConfirmRecord;
+ if (!record) return;
+
+ if (!this.canCollectRecord(record)) {
+ await this.huoquJilu(true, true);
+ const refreshed = (this.data.recordList || []).find(
+ r => String(r.id) === String(record.id)
+ || String(r.tixianjilu_id) === String(record.tixianjilu_id)
+ );
+ if (refreshed) record = refreshed;
+ }
+ if (!this.canCollectRecord(record)) {
+ wx.showModal({
+ title: '暂时无法收款',
+ content: '缺少审核单号或审核记录ID,请下拉刷新后重试。仍不行请联系客服。',
+ showCancel: false,
+ });
+ return;
+ }
+
+ this.setData({ showCollectConfirm: false, submitting: true, collectConfirmRecord: record });
+ try {
+ const res = await request({
+ url: '/yonghu/tixiansq',
+ method: 'POST',
+ data: {
+ shenhe_danhao: record.shenhe_danhao,
+ shenhe_jilu_id: record.shenhe_jilu_id || '',
+ tixianjilu_id: record.tixianjilu_id || record.id || '',
+ leixing: record.leixing,
+ }
+ });
+ if (res.statusCode === 200 && res.data && res.data.code === 0) {
+ const data = res.data.data;
+ this.setData({
+ currentTixianId: data.tixian_id,
+ mchId: data.mch_id || '',
+ collectConfirmRecord: record,
+ });
+ this.tiaoqiWeixinQueRen(data.package_info);
+ } else {
+ wx.showModal({ title: '收款失败', content: res.data?.msg || '无法发起收款,请稍后重试', showCancel: false });
+ this.setData({ submitting: false, collectConfirmRecord: null });
+ }
+ } catch (err) {
+ wx.showModal({ title: '网络错误', content: '网络连接失败,请稍后重试', showCancel: false });
+ this.setData({ submitting: false, collectConfirmRecord: null });
+ }
+ },
+
+ tiaoqiWeixinQueRen(packageInfo) {
+ const appId = app.globalData.appId;
+ const mchId = this.data.mchId;
+ if (!appId || !mchId) {
+ wx.showModal({ title: '配置错误', content: '无法调起收款,请联系客服', showCancel: false });
+ this.setData({ submitting: false, collectConfirmRecord: null });
+ return;
+ }
+ wx.requestMerchantTransfer({
+ appId, mchId, package: packageInfo,
+ success: () => { this.tongzhiHouduanQueRen(1); },
+ fail: () => {
+ this.tongzhiHouduanQueRen(0);
+ wx.showModal({
+ title: '确认收款失败',
+ content: '微信收款确认失败,您可在记录中再次点击「收款」重试。',
+ showCancel: false,
+ });
+ }
+ });
+ },
+
+ async tongzhiHouduanQueRen(result) {
+ if (!this.data.currentTixianId) {
+ this.setData({ submitting: false, collectConfirmRecord: null });
+ return;
+ }
+ const record = this.data.collectConfirmRecord;
+ try {
+ const res = await request({
+ url: '/yonghu/tixianqr',
+ method: 'POST',
+ data: {
+ tixian_id: this.data.currentTixianId,
+ result: result,
+ shenhe_danhao: record ? record.shenhe_danhao : '',
+ shenhe_jilu_id: record ? (record.shenhe_jilu_id || '') : '',
+ tixianjilu_id: record ? (record.tixianjilu_id || record.id || '') : '',
+ leixing: record ? record.leixing : '',
+ }
+ });
+ if (res.statusCode === 200 && res.data && res.data.code === 0) {
+ const data = res.data.data || {};
+ this.gengxinYue(data, record ? record.leixing : null);
+ await this.loadAllAssets();
+ this.huoquJilu(true);
+ if (result === 1) PopupService.checkAndShow(this, 'tixianchenggong');
+ wx.showToast({
+ title: result === 1 ? '提现成功' : '收款已取消',
+ icon: result === 1 ? 'success' : 'none',
+ duration: 3000,
+ });
+ } else {
+ wx.showModal({ title: '确认状态失败', content: res.data?.msg || '操作失败', showCancel: false });
+ }
+ } catch (err) {
+ wx.showModal({ title: '网络错误', content: '网络连接失败', showCancel: false });
+ } finally {
+ this.setData({ submitting: false, currentTixianId: null, collectConfirmRecord: null });
+ }
+ },
+
+ onViewDetail(e) {
+ const index = e.currentTarget.dataset.index;
+ const record = this.data.recordList[index];
+ if (!record || !record.canShowDetail) return;
+ const status = parseInt(record.zhuangtai);
+ let detailTitle = '失败原因';
+ if (status === TX_STATUS.SHENHE_SHIBAI) detailTitle = '审核失败原因';
+ else if (status === TX_STATUS.TIXIAN_SHIBAI) detailTitle = '提现失败原因';
+ this.setData({
+ showDetailModal: true,
+ detailTitle,
+ detailContent: record.detailReason || '暂无详细信息',
+ });
+ },
+
+ onCloseDetailModal() {
+ this.setData({ showDetailModal: false, detailTitle: '', detailContent: '' });
+ },
+
+ onCloseTixian() {
+ if (this.data.submitting) { wx.showToast({ title: '正在处理中,请稍后', icon: 'none' }); return; }
+ this.setData({
+ showTixian: false, selectedAsset: null, tixianleixing: null,
+ tixianjine: '', shouxufei: '0.00', shijidaozhang: '0.00', submitting: false,
+ });
+ },
+
+ async huoquJilu(fresh = false, force = false) {
+ const now = Date.now();
+ if (!force && now - lastPullDownTime < 2000 && !fresh) return;
+ if (!force && (this.data.loading || this.data.isRefreshing)) return;
+ const page = fresh ? 1 : this.data.page;
+ if (fresh) this.setData({ nomore: false, canloadmore: true, isRefreshing: true });
+ if (this.data.nomore && !fresh) return;
+ this.setData({ loading: true });
+ lastPullDownTime = now;
+ try {
+ const res = await request({
+ url: '/yonghu/tixianjiluhq',
+ method: 'POST',
+ data: { page, limit: yemianshuju, mode: 2, zhuangtai: this.data.currentStatusFilter }
+ });
+ if (res.statusCode === 200 && res.data && res.data.code === 0) {
+ const data = res.data.data;
+ const processedList = (data.list || []).map(item => this.processRecordItem(item));
+ const newList = fresh ? processedList : [...this.data.recordList, ...processedList];
+ this.setData({
+ recordList: newList,
+ page: page + 1,
+ nomore: !(data.has_more === true),
+ canloadmore: data.has_more === true,
+ loading: false,
+ isRefreshing: false,
+ });
+ this.updatePendingCollectFlag(newList);
+ } else {
+ this.setData({ loading: false, isRefreshing: false });
+ }
+ } catch (err) {
+ this.setData({ loading: false, isRefreshing: false });
+ }
+ },
+
+ onReachBottom() {
+ if (this.data.canloadmore && !this.data.nomore && !this.data.loading && !this.data.isRefreshing) {
+ const now = Date.now();
+ if (now - lastPullDownTime >= PULL_DOWN_INTERVAL) this.huoquJilu();
+ }
+ },
+
+ stopPropagation() {}
+ }
+});
diff --git a/miniprogram/pages/tixian/components/mode2/mode2.json b/miniprogram/pages/tixian/components/mode2/mode2.json
new file mode 100644
index 0000000..498339e
--- /dev/null
+++ b/miniprogram/pages/tixian/components/mode2/mode2.json
@@ -0,0 +1,15 @@
+{
+ "component": true,
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification",
+ "popup-notice": "/components/popup-notice/popup-notice"
+ },
+ "navigationBarTitleText": "提现中心",
+ "navigationBarBackgroundColor": "#1a0b2e",
+ "navigationBarTextStyle": "white",
+ "backgroundColor": "#1a0b2e",
+ "enablePullDownRefresh": false,
+ "onReachBottomDistance": 50,
+ "navigationStyle": "default",
+ "disableScroll": false
+ }
diff --git a/miniprogram/pages/tixian/components/mode2/mode2.wxml b/miniprogram/pages/tixian/components/mode2/mode2.wxml
new file mode 100644
index 0000000..9f1a5cb
--- /dev/null
+++ b/miniprogram/pages/tixian/components/mode2/mode2.wxml
@@ -0,0 +1,152 @@
+
+
+
+
+
+
+
+
+ 可提总余额
+ ¥{{totalAmount}}
+
+
+ ← 左右滑动查看更多资产 →
+
+
+
+
+ {{item.label}}
+ ¥{{item.amount}}
+ 费率 {{item.rate}}%
+
+
+ 暂无可用资金
+
+
+
+
+
+
+
+
+
+
+
+ 您有审核通过的提现待收款,请点击「收款」按钮,在微信中主动确认后才能到账。
+
+
+
+
+ {{item.label}}
+
+
+
+
+
+
+ ¥{{item.jine}}
+ {{item.create}}
+
+
+ 微信零钱
+ {{item.leixingLabel}}
+
+
+
+
+ {{item.statusText}}
+ 点击收款
+
+
+ {{item.statusText}}
+ 详情
+
+ {{item.statusText}}
+
+
+ 加载中...
+ —— 没有更多了 ——
+ 加载更多记录
+ 暂无提现记录
+
+
+
+
+
+
+
+
+
+
+
+ 提交后需等待审核,审核通过后请在本页点击「收款」确认到账
+ 项目{{selectedAsset.label}}
+ 可提¥{{selectedAsset.amount}}
+ 费率{{currentRate}}%
+
+ 金额
+
+
+
+ ¥{{item}}
+
+
+ 手续费 ¥{{shouxufei}}
+ 到账 ¥{{shijidaozhang}}
+
+
+
+
+
+
+
+
+
+
+
+ 审核已通过,{{collectConfirmRecord.leixingLabel}} ¥{{collectConfirmRecord.jine}}
+ 请点击「去收款」,然后在微信弹窗中主动确认,资金才会打入您的微信零钱。
+ 审核单号:{{collectConfirmRecord.shenhe_danhao}}
+ 注意:审核通过≠已到账,必须您本人点击确认才能完成收款!
+
+
+
+
+
+
+
+
+
+ {{detailContent}}
+
+
+
+
+
+
diff --git a/miniprogram/pages/tixian/components/mode2/mode2.wxss b/miniprogram/pages/tixian/components/mode2/mode2.wxss
new file mode 100644
index 0000000..b6a5ea4
--- /dev/null
+++ b/miniprogram/pages/tixian/components/mode2/mode2.wxss
@@ -0,0 +1,407 @@
+/* mode2 自动打款 - UI 完全复用 mode1 样式 */
+
+page {
+ background-color: #f5f7fb;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+ height: 100%;
+}
+
+.app {
+ display: flex;
+ flex-direction: column;
+ min-height: 100vh;
+ padding: 0 24rpx 0;
+ position: relative;
+ z-index: 1;
+ box-sizing: border-box;
+}
+
+.global-bg {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: -1;
+ opacity: 0.1;
+ object-fit: cover;
+ filter: blur(1rpx);
+}
+
+.assets-section {
+ margin-top: 24rpx;
+ flex-shrink: 0;
+}
+
+.main-card {
+ background: rgba(248, 250, 252, 0.88);
+ backdrop-filter: blur(28rpx);
+ -webkit-backdrop-filter: blur(28rpx);
+ border-radius: 48rpx;
+ padding: 30rpx 28rpx 24rpx;
+ margin-bottom: 20rpx;
+ box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.04);
+ border: 1rpx solid rgba(255, 255, 255, 0.4);
+ background-size: cover;
+}
+
+.balance-wrap {
+ text-align: center;
+ margin-bottom: 20rpx;
+}
+.balance-label {
+ font-size: 28rpx;
+ color: #0f172a;
+ letter-spacing: 1px;
+ display: block;
+ margin-bottom: 10rpx;
+ font-weight: 700;
+ text-shadow: 0 1rpx 2rpx rgba(255,255,255,0.8);
+}
+.balance-value {
+ font-size: 68rpx;
+ font-weight: 800;
+ color: #0f172a;
+ font-family: 'DIN', monospace;
+ letter-spacing: -1rpx;
+ text-shadow: 0 1rpx 2rpx rgba(255,255,255,0.6);
+}
+
+.inner-divider {
+ width: 60%;
+ height: 2rpx;
+ background: linear-gradient(90deg, transparent, #64748b, #64748b, transparent);
+ margin: 0 auto 20rpx auto;
+ border-radius: 2rpx;
+ opacity: 0.9;
+}
+
+.assets-hint {
+ display: block;
+ text-align: center;
+ font-size: 24rpx;
+ color: #334155;
+ margin-bottom: 14rpx;
+ font-weight: 600;
+}
+
+.assets-scroll {
+ width: 100%;
+ white-space: nowrap;
+}
+.assets-track {
+ display: inline-flex;
+ gap: 16rpx;
+}
+
+.asset-tile {
+ width: 170rpx;
+ background: rgba(255, 255, 255, 0.75);
+ backdrop-filter: blur(8rpx);
+ border-radius: 28rpx;
+ padding: 18rpx 12rpx;
+ display: inline-flex;
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
+ border: 1rpx solid rgba(255,255,255,0.5);
+ box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.02);
+ transition: all 0.2s;
+ white-space: normal;
+ background-size: cover;
+ background-position: center;
+}
+.asset-tile:active {
+ background: rgba(255, 255, 255, 0.9);
+ transform: scale(0.98);
+}
+.asset-name {
+ font-size: 24rpx;
+ font-weight: 700;
+ color: #1e293b;
+ margin-bottom: 10rpx;
+ text-shadow: 0 1rpx 1rpx rgba(255,255,255,0.5);
+}
+.asset-balance {
+ font-size: 28rpx;
+ font-weight: 800;
+ color: #2d6a4f;
+ font-family: monospace;
+ margin-bottom: 6rpx;
+ text-shadow: 0 1rpx 1rpx rgba(255,255,255,0.3);
+}
+.asset-fee {
+ font-size: 20rpx;
+ font-weight: 600;
+ color: #475569;
+ text-shadow: 0 1rpx 1rpx rgba(255,255,255,0.3);
+}
+.assets-empty {
+ width: 400rpx;
+ text-align: center;
+ color: #a0abb9;
+ padding: 40rpx 0;
+}
+
+.divider-line {
+ height: 2rpx;
+ background: linear-gradient(90deg, transparent, #cbd5e1, #cbd5e1, transparent);
+ margin: 16rpx 0 20rpx;
+ width: 80%;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.history-section {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ min-height: 0;
+ margin-top: 0;
+}
+.history-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: baseline;
+ padding: 0 8rpx 20rpx;
+ flex-shrink: 0;
+}
+.history-title {
+ font-size: 34rpx;
+ font-weight: 600;
+ color: #1e293b;
+}
+.history-scroll {
+ flex: 1;
+ width: 100%;
+ padding: 0 20rpx;
+ box-sizing: border-box;
+}
+.history-item {
+ display: flex;
+ align-items: center;
+ padding: 28rpx 0;
+ border-bottom: 1rpx solid rgba(0,0,0,0.05);
+}
+.history-info { width: 160rpx; }
+.history-amount {
+ font-size: 36rpx;
+ font-weight: 700;
+ color: #1e293b;
+ display: block;
+}
+.history-time {
+ font-size: 22rpx;
+ color: #94a3b8;
+}
+.history-detail {
+ flex: 1;
+ padding: 0 20rpx;
+}
+.history-method {
+ font-size: 30rpx;
+ color: #334155;
+ display: block;
+ margin-bottom: 6rpx;
+}
+.history-type {
+ font-size: 24rpx;
+ color: #94a3b8;
+}
+.history-status { text-align: right; }
+
+.status-badge {
+ font-size: 26rpx;
+ font-weight: 500;
+ padding: 6rpx 14rpx;
+ border-radius: 30rpx;
+ display: inline-block;
+}
+.status-wait {
+ color: #f59e0b;
+ background: rgba(245,158,11,0.1);
+}
+.status-done {
+ color: #10b981;
+ background: rgba(16,185,129,0.1);
+}
+.status-fail {
+ color: #ef4444;
+ background: rgba(239,68,68,0.1);
+}
+.status-pending {
+ color: #3b82f6;
+ background: rgba(59,130,246,0.1);
+}
+.fail-link {
+ margin-left: 8rpx;
+ text-decoration: underline;
+}
+.collect-tappable {
+ display: inline-flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 4rpx;
+ padding: 10rpx 18rpx;
+ cursor: pointer;
+ border: 2rpx solid rgba(59,130,246,0.35);
+ box-shadow: 0 4rpx 12rpx rgba(59,130,246,0.15);
+}
+.collect-tappable:active {
+ transform: scale(0.97);
+ opacity: 0.9;
+}
+.collect-sub {
+ font-size: 22rpx;
+ font-weight: 700;
+ color: #1d4ed8;
+ text-decoration: underline;
+}
+
+.load-tip, .more-tip {
+ text-align: center;
+ padding: 30rpx;
+ font-size: 24rpx;
+ color: #94a3b8;
+}
+.load-more-btn {
+ margin: 16rpx 0 20rpx;
+ padding: 20rpx;
+ text-align: center;
+ background: rgba(255,255,255,0.7);
+ backdrop-filter: blur(8rpx);
+ border-radius: 40rpx;
+ font-size: 28rpx;
+ color: #3b82f6;
+ border: 1rpx solid rgba(255,255,255,0.5);
+}
+.load-more-btn:active { background: rgba(255,255,255,0.9); }
+.safe-bottom { height: 12rpx; flex-shrink: 0; }
+
+/* 自动打款专属(少量追加) */
+.pending-tip {
+ margin: 0 8rpx 16rpx;
+ padding: 16rpx 20rpx;
+ font-size: 24rpx;
+ color: #1d4ed8;
+ background: rgba(59,130,246,0.08);
+ border-radius: 20rpx;
+ line-height: 1.5;
+}
+.filter-scroll {
+ width: 100%;
+ white-space: nowrap;
+ margin-bottom: 12rpx;
+}
+.filter-track {
+ display: inline-flex;
+ gap: 12rpx;
+ padding: 0 8rpx;
+}
+.filter-chip {
+ flex-shrink: 0;
+ padding: 10rpx 22rpx;
+ font-size: 24rpx;
+ color: #64748b;
+ background: rgba(255,255,255,0.8);
+ border-radius: 30rpx;
+ border: 1rpx solid rgba(0,0,0,0.06);
+}
+.filter-chip.active {
+ color: #3b82f6;
+ background: #eff6ff;
+ border-color: #3b82f6;
+}
+.auto-tip {
+ font-size: 24rpx;
+ color: #3b82f6;
+ margin-bottom: 20rpx;
+ line-height: 1.5;
+}
+.warn-tip {
+ font-size: 24rpx;
+ color: #f59e0b;
+ margin-top: 16rpx;
+ font-weight: 600;
+}
+
+/* 弹窗(与 mode1 一致) */
+.modal-mask {
+ position: fixed;
+ top: 0; left: 0; right: 0; bottom: 0;
+ background: rgba(0,0,0,0.5);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+}
+.modal-dialog {
+ width: 600rpx;
+ background: rgba(255,255,255,0.96);
+ backdrop-filter: blur(20rpx);
+ border-radius: 48rpx;
+ overflow: hidden;
+ box-shadow: 0 20rpx 40rpx rgba(0,0,0,0.1);
+ border: 1rpx solid rgba(255,255,255,0.6);
+}
+.modal-header {
+ padding: 36rpx 32rpx 20rpx;
+ font-size: 36rpx;
+ font-weight: 700;
+ border-bottom: 1rpx solid rgba(0,0,0,0.05);
+}
+.modal-body {
+ padding: 32rpx;
+ font-size: 28rpx;
+ color: #334155;
+ line-height: 1.6;
+}
+.info-row, .amount-row {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 24rpx;
+ font-size: 28rpx;
+}
+.amount-row input {
+ border: 1rpx solid rgba(0,0,0,0.1);
+ border-radius: 28rpx;
+ padding: 16rpx 20rpx;
+ width: 260rpx;
+ background: rgba(255,255,255,0.9);
+}
+.quick-row {
+ display: flex;
+ gap: 16rpx;
+ margin: 24rpx 0;
+}
+.quick-chip {
+ flex: 1;
+ background: rgba(241,245,249,0.8);
+ text-align: center;
+ padding: 12rpx;
+ border-radius: 40rpx;
+ font-size: 28rpx;
+}
+.fee-row {
+ display: flex;
+ justify-content: space-between;
+ background: rgba(248,250,252,0.8);
+ padding: 20rpx;
+ border-radius: 28rpx;
+ margin: 20rpx 0;
+}
+.modal-footer {
+ display: flex;
+ border-top: 1rpx solid rgba(0,0,0,0.05);
+}
+.modal-btn {
+ flex: 1;
+ text-align: center;
+ padding: 28rpx;
+ font-size: 30rpx;
+}
+.modal-btn.cancel {
+ color: #8f9bb3;
+ border-right: 1rpx solid rgba(0,0,0,0.05);
+}
+.modal-btn.confirm { color: #3b82f6; }
diff --git a/miniprogram/pages/tixian/tixian.js b/miniprogram/pages/tixian/tixian.js
new file mode 100644
index 0000000..7764518
--- /dev/null
+++ b/miniprogram/pages/tixian/tixian.js
@@ -0,0 +1,75 @@
+// pages/tixian/tixian.js
+import request from '../../utils/request.js';
+import PopupService from '../../services/popupService.js';
+
+const app = getApp();
+
+Page({
+ data: {
+ pageMode: 0, // 0:加载中 1:收款码模式 2:微信零钱模式
+ pageOptions: {}, // 保存启动参数,传给子组件
+ loadError: false, // 获取模式失败时展示错误状态
+ },
+
+ onLoad(options) {
+ this.setData({
+ pageOptions: options || {}
+ });
+ this.fetchPageMode();
+ },
+
+ // ✅ 用 onShow,别用 pageLifetimes
+ onShow() {
+ this.registerNotificationComponent();
+ // 如果已经在某个模式下,可以刷新一下子组件数据(可选)
+ // 弹窗检查
+ PopupService.checkAndShow(this, 'tixian');
+ },
+
+ onHide() {
+ const popupComp = this.selectComponent('#popupNotice');
+ if (popupComp && popupComp.cleanup) {
+ popupComp.cleanup();
+ }
+ },
+
+ // ✅ 注册通知组件(弹窗依赖它)
+ registerNotificationComponent() {
+ const notificationComp = this.selectComponent('#global-notification');
+ if (notificationComp && notificationComp.showNotification) {
+ app.globalData.globalNotification = {
+ show: (data) => notificationComp.showNotification(data),
+ hide: () => notificationComp.hideNotification()
+ };
+ }
+ },
+
+ async fetchPageMode() {
+ try {
+ const res = await request({
+ url: '/peizhi/hqtxzsym',
+ method: 'POST'
+ });
+
+ if (res.statusCode === 200 && res.data && res.data.code === 0) {
+ const mode = parseInt(res.data.data.mode);
+ this.setData({
+ pageMode: (mode === 1 || mode === 2) ? mode : 1,
+ loadError: false
+ });
+ } else {
+ this.setData({ pageMode: 1, loadError: false });
+ }
+ } catch (err) {
+ console.error('获取展示模式失败:', err);
+ this.setData({ loadError: true });
+ }
+ },
+
+ retryFetch() {
+ this.setData({ loadError: false, pageMode: 0 });
+ this.fetchPageMode();
+ },
+
+ stopPropagation() {}
+});
\ No newline at end of file
diff --git a/miniprogram/pages/tixian/tixian.json b/miniprogram/pages/tixian/tixian.json
new file mode 100644
index 0000000..fd80470
--- /dev/null
+++ b/miniprogram/pages/tixian/tixian.json
@@ -0,0 +1,15 @@
+{
+ "usingComponents": {
+ "mode1": "./components/mode1/mode1",
+ "mode2": "./components/mode2/mode2"
+ },
+
+ "navigationBarTitleText": "提现中心",
+ "navigationBarBackgroundColor": "#1a0b2e",
+ "navigationBarTextStyle": "white",
+ "backgroundColor": "#1a0b2e",
+ "enablePullDownRefresh": false,
+ "onReachBottomDistance": 50,
+ "navigationStyle": "default",
+ "disableScroll": false
+ }
diff --git a/miniprogram/pages/tixian/tixian.wxml b/miniprogram/pages/tixian/tixian.wxml
new file mode 100644
index 0000000..88e6cdd
--- /dev/null
+++ b/miniprogram/pages/tixian/tixian.wxml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+ 加载中...
+
+
+
+
+ 加载失败,请检查网络
+ 点击重试
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/tixian/tixian.wxss b/miniprogram/pages/tixian/tixian.wxss
new file mode 100644
index 0000000..6aa6530
--- /dev/null
+++ b/miniprogram/pages/tixian/tixian.wxss
@@ -0,0 +1,50 @@
+/* pages/tixian/tixian.wxss */
+.tixian-container {
+ width: 100%;
+ min-height: 100vh;
+ position: relative;
+}
+
+/* 加载状态 */
+.loading-center, .error-center {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 100vh;
+ color: #ffffff;
+ background: #1a0b2e; /* 与赛博朋克背景统一 */
+}
+
+.loading-spinner {
+ width: 60rpx;
+ height: 60rpx;
+ border: 4rpx solid rgba(157, 78, 221, 0.3);
+ border-top-color: #9d4edd;
+ border-radius: 50%;
+ animation: spin 0.8s linear infinite;
+ margin-bottom: 30rpx;
+}
+
+.loading-text {
+ font-size: 28rpx;
+ color: rgba(255, 255, 255, 0.7);
+}
+
+.error-text {
+ font-size: 28rpx;
+ margin-bottom: 30rpx;
+}
+
+.retry-btn {
+ padding: 20rpx 60rpx;
+ background: linear-gradient(90deg, #6a11cb, #2575fc);
+ color: white;
+ border-radius: 40rpx;
+ font-size: 28rpx;
+}
+
+@keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+}
\ No newline at end of file
diff --git a/miniprogram/pages/wode/wode.js b/miniprogram/pages/wode/wode.js
new file mode 100644
index 0000000..26caa22
--- /dev/null
+++ b/miniprogram/pages/wode/wode.js
@@ -0,0 +1,178 @@
+// pages/gerenzhongxin/gerenzhongxin.js
+const app = getApp()
+
+Page({
+ data: {
+ avatarUrl: '',
+ userNicheng: '',
+ userUid: '',
+ showLoginBtn: false,
+ orderCounts: { daifuwu:0, fuwuzhong:0, yiwancheng:0, yituikuan:0 },
+ // 图标
+ settingIcon: '', daifuwuIcon: '', fuwuzhongIcon: '', yiwanchengIcon: '', yituikuanIcon: '',
+ dashouIcon: '', shangjiaIcon: '', guanshiIcon: '', kefuIcon: '',
+ zuzhangIcon: '', guanzhualongIcon: '', arrowRightIcon: '', qingchuIcon: '',
+ bottomSafePadding: 0
+ },
+
+ onLoad() {
+ this.initIconUrls()
+ this.loadUserData()
+ this.setBottomSafe()
+ },
+
+ onShow() {
+ this.loadUserData()
+ this.updateOrderCounts()
+ this.registerNotification()
+ this.checkAvatarPrompt()
+ this.initIconUrls()
+ },
+
+ setBottomSafe() {
+ const sys = wx.getSystemInfoSync()
+ const safeBottom = sys.screenHeight - sys.safeArea.bottom // 底部安全区高度(px)
+ const tabBarHeight = (sys.windowWidth / 750) * 100 // 100rpx 转为 px
+ this.setData({
+ bottomSafePadding: safeBottom + tabBarHeight + 4 // 4px 微调,确保不留缝隙
+ })
+ },
+
+ registerNotification() {
+ const comp = this.selectComponent('#global-notification')
+ if (comp?.showNotification) {
+ app.globalData.globalNotification = {
+ show: d => comp.showNotification(d),
+ hide: () => comp.hideNotification()
+ }
+ }
+ },
+
+ checkAvatarPrompt() {
+ const ds = wx.getStorageSync('dashoustatus')
+ const tx = wx.getStorageSync('touxiang') || ''
+ if (ds === 1 && (!tx || tx === 'a_long/morentouxiang.jpg')) {
+ wx.showModal({
+ title: '提示', content: '请先设置头像',
+ confirmText: '去设置', confirmColor: '#C9A962',
+ success: r => r.confirm && wx.navigateTo({ url: '/pages/xiugai/xiugai' })
+ })
+ }
+ },
+
+ initIconUrls() {
+ const base = (app.globalData.ossImageUrl || '') + '/beijing/tubiao/'
+ this.setData({
+ settingIcon: base + 'grzx_shezhi.jpg',
+ daifuwuIcon: base + 'grzx_daifuwu.jpg',
+ fuwuzhongIcon: base + 'grzx_fuwuzhong.jpg',
+ yiwanchengIcon: base + 'grzx_yiwancheng.jpg',
+ yituikuanIcon: base + 'grzx_yituikuan.jpg',
+ dashouIcon: base + 'grzx_dashou.jpg',
+ shangjiaIcon: base + 'grzx_shangjia.jpg',
+ guanshiIcon: base + 'grzx_guanshi.jpg',
+ kefuIcon: base + 'grzx_kefu.jpg',
+ zuzhangIcon: base + 'grzx_zuzhang.jpg',
+ guanzhualongIcon: base + 'grzx_guanzhualong.jpg',
+ arrowRightIcon: base + 'grzx_arrow_right.jpg',
+ qingchuIcon: base + 'grzx_qingchu.jpg',
+ kaoheguanIcon: base + 'kaoheguan.png', // 考核官认证图标
+ })
+ },
+
+ loadUserData() {
+ const token = wx.getStorageSync('token')
+ const nick = wx.getStorageSync('nicheng') || ''
+ const uid = wx.getStorageSync('uid') || ''
+ const tx = wx.getStorageSync('touxiang') || ''
+ const def = app.globalData.morentouxiang || ''
+ const oss = app.globalData.ossImageUrl || ''
+ let avt = ''
+ if (tx) avt = tx.startsWith('http') ? tx : oss + tx
+ else if (def) avt = def.startsWith('http') ? def : oss + def
+ this.setData({
+ avatarUrl: avt, userNicheng: nick, userUid: uid,
+ showLoginBtn: !token || !nick || !uid
+ })
+ },
+
+ updateOrderCounts() {
+ const c = app.globalData.dingdanTiaoshu || {}
+ this.setData({ orderCounts: {
+ daifuwu: c.daifuwu||0, fuwuzhong: c.fuwuzhong||0,
+ yiwancheng: c.yiwancheng||0, yituikuan: c.yituikuan||0
+ }})
+ },
+
+ goToSetting() { wx.navigateTo({ url: '/pages/xiugai/xiugai' }) },
+
+ handleWechatLogin() {
+ wx.showLoading({ title: '登录中...' })
+ wx.login({
+ success: r => {
+ if (r.code) this.sendLoginRequest(r.code)
+ else { wx.hideLoading(); wx.showToast({ title: '获取code失败', icon: 'none' }) }
+ },
+ fail: () => { wx.hideLoading(); wx.showToast({ title: '微信登录失败', icon: 'none' }) }
+ })
+ },
+
+ sendLoginRequest(code) {
+ const api = app.globalData.apiBaseUrl
+ if (!api) return wx.hideLoading()
+ wx.request({
+ url: api + '/yonghu/wechatlogin', method: 'POST', data: { code },
+ header: { 'content-type': 'application/json' },
+ success: res => {
+ if (res.statusCode === 200 && res.data?.code === 0 && res.data.data) {
+ const d = res.data.data
+ wx.setStorageSync('token', d.token)
+ ;['nicheng','uid','touxiang','dashoustatus','guanshistatus','shangjiastatus','zuzhangstatus','kaoheguanstatus'].forEach(k => {
+ if (d[k] !== undefined) wx.setStorageSync(k, d[k])
+ })
+ app.globalData.zuzhangstatus = d.zuzhangstatus
+ app.globalData.dingdanTiaoshu = {
+ daifuwu: d.dingdantiaoshu?.daifuwu||0,
+ fuwuzhong: d.dingdantiaoshu?.fuwuzhong||0,
+ yiwancheng: d.dingdantiaoshu?.yiwancheng||0,
+ yituikuan: d.dingdantiaoshu?.yituikuan||0
+ }
+ ;['shangjiastatus','dashoustatus','guanshistatus'].forEach(f => {
+ if (d[f] !== undefined) app.globalData[f] = d[f]
+ })
+ this.loadUserData(); this.updateOrderCounts()
+ wx.showToast({ title: '登录成功', icon: 'success' })
+ } else wx.showToast({ title: res.data?.msg || '登录失败', icon: 'none' })
+ wx.hideLoading()
+ },
+ fail: () => { wx.hideLoading(); wx.showToast({ title: '网络错误', icon: 'none' }) }
+ })
+ },
+
+ goToOrder(e) { wx.navigateTo({ url: `/pages/dingdan/dingdan?type=${e.currentTarget.dataset.type}` }) },
+ goToAuth(e) { wx.navigateTo({ url: `/pages/renzheng/renzheng?type=${e.currentTarget.dataset.type}` }) },
+
+ goToKefu() {
+ const { link, enterpriseId } = app.globalData.kefuConfig || {}
+ if (!link || !enterpriseId) { wx.showToast({ title: '客服配置未加载', icon: 'none' }); return }
+ wx.openCustomerServiceChat({ extInfo: { url: link }, corpId: enterpriseId })
+ },
+ goToCustomService() { this.goToKefu() },
+ goToGuanzhuA() { wx.navigateTo({ url: '/pages/guanzhual/guanzhual' }) },
+
+ clearCache() {
+ wx.showModal({
+ title: '清除缓存', content: '将清除所有登录数据,确定继续?',
+ confirmText: '确定', cancelText: '取消',
+ success: r => {
+ if (r.confirm) {
+ wx.clearStorageSync()
+ app.globalData.dingdanTiaoshu = { daifuwu:0, fuwuzhong:0, yiwancheng:0, yituikuan:0 }
+ this.setData({ avatarUrl:'', userNicheng:'', userUid:'', showLoginBtn:true,
+ orderCounts:{ daifuwu:0, fuwuzhong:0, yiwancheng:0, yituikuan:0 } })
+ wx.showToast({ title: '已清除', icon: 'success' })
+ }
+ }
+ })
+ }
+})
\ No newline at end of file
diff --git a/miniprogram/pages/wode/wode.json b/miniprogram/pages/wode/wode.json
new file mode 100644
index 0000000..241131f
--- /dev/null
+++ b/miniprogram/pages/wode/wode.json
@@ -0,0 +1,11 @@
+{
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification",
+ "custom-tab-bar": "/custom-tab-bar/index"
+ },
+ "navigationBarTitleText": "个人中心",
+ "navigationBarBackgroundColor": "#F7F3ED",
+ "navigationBarTextStyle": "black",
+ "backgroundColor": "#F7F3ED",
+ "enablePullDownRefresh": false
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/wode/wode.wxml b/miniprogram/pages/wode/wode.wxml
new file mode 100644
index 0000000..515095d
--- /dev/null
+++ b/miniprogram/pages/wode/wode.wxml
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{userNicheng || '点击登录'}}
+ UID: {{userUid || '••••••'}}
+
+
+
+
+
+
+
+
+ 我的订单
+
+
+
+
+
+ 待服务
+ {{orderCounts.daifuwu}}
+
+
+
+ 服务中
+ {{orderCounts.fuwuzhong}}
+
+
+
+ 已完成
+ {{orderCounts.yiwancheng}}
+
+
+
+ 已退款
+ {{orderCounts.yituikuan}}
+
+
+
+
+
+
+
+
+ 更多服务
+
+
+
+
+ 在线客服
+
+
+
+ 关注阿龙
+
+
+
+
+
+
+
+ 身份认证
+
+
+
+
+ 接单员认证
+
+
+
+ 商家认证
+
+
+
+ 组长认证
+
+
+
+ 管事认证
+
+
+
+ 考核官认证
+
+
+
+
+
+
+
+ 系统功能
+
+
+
+ 清除缓存
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/wode/wode.wxss b/miniprogram/pages/wode/wode.wxss
new file mode 100644
index 0000000..0864783
--- /dev/null
+++ b/miniprogram/pages/wode/wode.wxss
@@ -0,0 +1,254 @@
+/* pages/gerenzhongxin/gerenzhongxin.wxss — 最终完美版(毛玻璃强化+图标左右贴边) */
+
+/* 全局 */
+page {
+ background: #F7F3ED;
+ height: 100vh;
+ overflow: hidden;
+ }
+
+ .page-root {
+ position: relative;
+ width: 100%;
+ height: 100vh;
+ display: flex;
+ flex-direction: column;
+ }
+
+ .bg-layer {
+ position: fixed;
+ top: 0; left: 0;
+ width: 100vw; height: 100vh;
+ background: radial-gradient(ellipse at 30% 20%, rgba(201,169,98,0.08) 0%, transparent 60%),
+ radial-gradient(ellipse at 80% 70%, rgba(201,169,98,0.05) 0%, transparent 50%);
+ z-index: 0;
+ pointer-events: none;
+ }
+
+ .main-scroll {
+ position: relative;
+ z-index: 2;
+ flex: 1;
+ width: 100%;
+ overflow-y: auto;
+ padding-bottom: calc(140rpx + env(safe-area-inset-bottom));
+ }
+
+ .main-content {
+ padding: 60rpx 40rpx 0;
+ }
+
+ /* 图标基础 */
+ .icon-dark {
+ filter: brightness(0) saturate(100%) opacity(0.65);
+ }
+ .icon-color {
+ filter: none;
+ }
+ .icon-size {
+ width: 52rpx;
+ height: 52rpx;
+ }
+
+ /* 图标独立淡金背景 */
+ .icon-bg {
+ background: rgba(201,169,98,0.08);
+ border-radius: 24rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 18rpx;
+ }
+
+ /* 头部(头像完美正圆) */
+ .hero {
+ position: relative;
+ margin-bottom: 100rpx;
+ }
+ .settings-icon {
+ position: absolute;
+ top: 0; right: 0;
+ width: 60rpx; height: 60rpx;
+ display: flex; align-items: center; justify-content: center;
+ }
+ .user-block {
+ display: flex; align-items: center;
+ }
+ .avatar-outside {
+ width: 140rpx;
+ height: 140rpx;
+ border-radius: 50%;
+ background: rgba(201,169,98,0.06);
+ padding: 10rpx;
+ margin-right: 35rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-sizing: border-box;
+ }
+ .avatar {
+ width: 100%;
+ height: 100%;
+ border-radius: 50%;
+ display: block;
+ border: none;
+ }
+ .nickname {
+ font-size: 44rpx; font-weight: 700; color: #1A1A1A;
+ display: block;
+ }
+ .uid {
+ font-size: 28rpx; color: #7A7A7A;
+ margin-top: 12rpx;
+ display: block;
+ }
+ .login-btn {
+ margin-top: 45rpx;
+ width: 100%; height: 92rpx;
+ background: linear-gradient(135deg, #C9A962, #D4B56A);
+ color: white; font-size: 34rpx; font-weight: 600;
+ border-radius: 46rpx;
+ border: none;
+ }
+
+ /* 分区间距 */
+ .section {
+ margin-top: 110rpx;
+ }
+ .section-close {
+ margin-top: 80rpx;
+ }
+
+ .title-line {
+ margin-bottom: 40rpx;
+ padding: 18rpx 28rpx;
+ line-height: 1.2;
+ }
+ .gradient-bg {
+ background: linear-gradient(to right, rgba(201,169,98,0.01), rgba(201,169,98,0.10));
+ }
+ .title-text {
+ font-size: 38rpx;
+ font-weight: 700;
+ color: #1A1A1A;
+ letter-spacing: 2rpx;
+ }
+
+ /* 毛玻璃订单容器(强化玻璃质感,淡灰半透,无白边) */
+ .frosted-box {
+ background: rgba(235, 235, 240, 0.45); /* 淡灰玻璃底 */
+ backdrop-filter: blur(25px);
+ -webkit-backdrop-filter: blur(25px);
+ border-radius: 36rpx;
+ padding: 45rpx 0; /* 去掉左右内边距,让图标真正贴边 */
+ box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.05), inset 0 1rpx 0 rgba(255,255,255,0.7);
+ border: none;
+ }
+
+ /* 订单行 - 图标完全左右贴边,间距最大化 */
+ .order-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0 5rpx; /* 极微的内边距,防止图片被裁剪,但视觉上仍贴边 */
+ }
+ .order-unit {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ position: relative;
+ flex: 1; /* 等分剩余空间 */
+ margin: 0; /* 无额外间距 */
+ }
+ .order-name {
+ font-size: 28rpx;
+ color: #1C1C1C;
+ margin-top: 18rpx;
+ font-weight: 500;
+ }
+ .badge {
+ position: absolute;
+ top: -10rpx;
+ right: 12rpx;
+ background: #C5615E;
+ color: #fff;
+ font-size: 22rpx;
+ min-width: 38rpx;
+ height: 38rpx;
+ border-radius: 19rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0 8rpx;
+ font-weight: 600;
+ }
+
+ /* 更多服务 */
+ .service-row {
+ display: flex;
+ gap: 80rpx;
+ justify-content: flex-start;
+ }
+ .service-unit {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ }
+ .service-name {
+ font-size: 28rpx;
+ color: #1C1C1C;
+ margin-top: 18rpx;
+ font-weight: 500;
+ }
+
+ /* 身份认证 */
+ .auth-row {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 25rpx 17rpx; /* 行间距20rpx,列间距10rpx,可根据实际微调 */
+ align-items: center;
+}
+ .auth-unit {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ flex: 1;
+ min-width: 25%; /* 确保一行最多四个,第五个自动换行 */
+ }
+ .auth-name {
+ font-size: 28rpx;
+ color: #1C1C1C;
+ margin-top: 18rpx;
+ font-weight: 500;
+ }
+
+ /* 清除缓存 */
+ .clear-row {
+ display: flex;
+ align-items: center;
+ padding: 22rpx 0;
+ }
+ .clear-word {
+ flex: 1;
+ font-size: 30rpx;
+ color: #1C1C1C;
+ margin-left: 18rpx;
+ font-weight: 500;
+ }
+ .arrow-right {
+ width: 36rpx;
+ height: 36rpx;
+ opacity: 0.45;
+ margin-left: 15rpx;
+ }
+
+ /* 底部 */
+ .footer {
+ margin-top: 130rpx;
+ padding-bottom: 20rpx;
+ text-align: center;
+ font-size: 26rpx;
+ color: #aaa;
+ }
+ .divider { margin: 0 15rpx; }
+ .coop { color: #C9A962; font-weight: 500; }
\ No newline at end of file
diff --git a/miniprogram/pages/wodedashou/wodedashou.js b/miniprogram/pages/wodedashou/wodedashou.js
new file mode 100644
index 0000000..bc8091e
--- /dev/null
+++ b/miniprogram/pages/wodedashou/wodedashou.js
@@ -0,0 +1,194 @@
+// pages/wodedashou/wodedashou.js
+import request from '../../utils/request.js'
+const app = getApp()
+
+Page({
+ data: {
+ // 统计
+ yaoqingzongshu: 0,
+ zaixianCount: 0,
+ zhengchangCount: 0,
+ fengjinCount: 0,
+
+ // 列表
+ dashouList: [],
+ currentPage: 1,
+ pageSize: 10,
+ hasMore: true,
+ isLoading: false, // 首次加载或筛选时使用
+ isLoadingMore: false, // 上拉加载更多时使用
+
+ // 筛选条件
+ keyword: '',
+ zhanghaozhuangtai: null, // null=全部, 1=正常, 2=封禁
+ zaixianzhuangtai: null, // null=全部, 1=在线, 2=离线
+
+ // 公共资源
+ ossImageUrl: '',
+ defaultAvatar: ''
+ },
+
+ onLoad() {
+ this.initGlobal()
+ this.jiazaiShuju(true)
+ this.registerNotification()
+ },
+
+ registerNotification() {
+ const comp = this.selectComponent('#global-notification')
+ if (comp?.showNotification) {
+ app.globalData.globalNotification = {
+ show: d => comp.showNotification(d),
+ hide: () => comp.hideNotification()
+ }
+ }
+ },
+
+ initGlobal() {
+ const g = app.globalData
+ this.setData({
+ ossImageUrl: g.ossImageUrl || '',
+ defaultAvatar: (g.ossImageUrl || '') + (g.morentouxiang || '')
+ })
+ },
+
+ // 手动点击刷新按钮(重新加载第一页)
+ shuaxinShuju() {
+ if (this.data.isLoading) return
+ this.setData({ currentPage: 1 })
+ this.jiazaiShuju(true)
+ },
+
+ // 上拉触底加载更多(同时也作为按钮点击方法)
+ shanglaJiazaiGengduo() {
+ if (!this.data.hasMore || this.data.isLoadingMore || this.data.isLoading) return
+ this.jiazaiShuju(false)
+ },
+
+ // 核心数据请求
+ async jiazaiShuju(isFirstLoad) {
+ if (isFirstLoad && this.data.isLoading) return
+ if (!isFirstLoad && this.data.isLoadingMore) return
+
+ this.setData({
+ isLoading: isFirstLoad ? true : this.data.isLoading,
+ isLoadingMore: isFirstLoad ? false : true
+ })
+
+ try {
+ const res = await request({
+ url: '/yonghu/gshqyqds',
+ method: 'POST',
+ data: {
+ page: isFirstLoad ? 1 : this.data.currentPage,
+ pageSize: this.data.pageSize,
+ keyword: this.data.keyword,
+ zhanghaozhuangtai: this.data.zhanghaozhuangtai,
+ zaixianzhuangtai: this.data.zaixianzhuangtai
+ }
+ })
+
+ if (res.data.code === 200) {
+ this.handleResponse(res.data.data, isFirstLoad)
+ } else {
+ wx.showToast({ title: res.data.message || '加载失败', icon: 'none' })
+ if (!isFirstLoad) {
+ this.setData({ currentPage: this.data.currentPage - 1 })
+ }
+ }
+ } catch (err) {
+ wx.showToast({ title: '网络异常', icon: 'none' })
+ if (!isFirstLoad) {
+ this.setData({ currentPage: this.data.currentPage - 1 })
+ }
+ } finally {
+ this.setData({
+ isLoading: false,
+ isLoadingMore: false
+ })
+ }
+ },
+
+ handleResponse(data, isFirstLoad) {
+ const process = (list) => list.map(item => ({
+ ...item,
+ touxiangUrl: this.fullAvatar(item.touxiang)
+ }))
+
+ if (isFirstLoad) {
+ this.setData({
+ dashouList: process(data.dashouList || []),
+ yaoqingzongshu: data.yaoqingzongshu || 0,
+ zaixianCount: data.zaixianCount || 0,
+ zhengchangCount: data.zhengchangCount || 0,
+ fengjinCount: data.fengjinCount || 0,
+ currentPage: data.hasMore ? 2 : 1,
+ hasMore: data.hasMore
+ })
+ } else {
+ this.setData({
+ dashouList: [...this.data.dashouList, ...process(data.dashouList || [])],
+ currentPage: this.data.currentPage + 1,
+ hasMore: data.hasMore
+ })
+ }
+ },
+
+ fullAvatar(relative) {
+ if (!relative) return this.data.defaultAvatar
+ if (relative.startsWith('http')) return relative
+ return this.data.ossImageUrl + relative
+ },
+
+ // 筛选输入
+ onKeywordInput(e) {
+ this.setData({ keyword: e.detail.value.trim() })
+ },
+
+ // picker 变更
+ onZhanghaoChange(e) {
+ const idx = parseInt(e.detail.value, 10)
+ this.setData({ zhanghaozhuangtai: idx === 0 ? null : idx })
+ },
+ onZaixianChange(e) {
+ const idx = parseInt(e.detail.value, 10)
+ this.setData({ zaixianzhuangtai: idx === 0 ? null : idx })
+ },
+
+ // 点击筛选按钮
+ applyFilter() {
+ if (this.data.isLoading) return
+ this.setData({ currentPage: 1 })
+ this.jiazaiShuju(true)
+ },
+
+ // 联系打手(私聊)
+ contactDashou(e) {
+ const dashouUid = e.currentTarget.dataset.uid
+ const myUid = wx.getStorageSync('uid')
+ const role = app.globalData.currentRole || 'normal'
+
+ if (role !== 'guanshi') {
+ wx.showModal({ title: '提示', content: '请先切换到管事身份', showCancel: false })
+ return
+ }
+
+ // 确保连接为管事身份
+ app.globalData.currentRole = 'guanshi'
+ wx.setStorageSync('currentRole', 'guanshi')
+ if (app.connectForCurrentRole) {
+ app.connectForCurrentRole()
+ }
+
+ const targetUserId = 'Ds' + dashouUid
+ const item = this.data.dashouList.find(i => i.uid === dashouUid)
+ const param = {
+ toUserId: targetUserId,
+ toName: item?.nicheng || '打手',
+ toAvatar: item?.touxiangUrl || ''
+ }
+ wx.navigateTo({
+ url: '/pages/liaotian/liaotian?data=' + encodeURIComponent(JSON.stringify(param))
+ })
+ }
+})
\ No newline at end of file
diff --git a/miniprogram/pages/wodedashou/wodedashou.json b/miniprogram/pages/wodedashou/wodedashou.json
new file mode 100644
index 0000000..837f016
--- /dev/null
+++ b/miniprogram/pages/wodedashou/wodedashou.json
@@ -0,0 +1,5 @@
+{
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/pages/wodedashou/wodedashou.wxml b/miniprogram/pages/wodedashou/wodedashou.wxml
new file mode 100644
index 0000000..6fbb206
--- /dev/null
+++ b/miniprogram/pages/wodedashou/wodedashou.wxml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ {{item.nicheng}}
+
+ {{item.zaixianzhuangtai==1?'在线':'离线'}}
+ {{item.zhanghaozhuangtai==1?'正常':'封禁'}}
+
+
+ ID: {{item.uid}}
+
+ 接单 {{item.jiedanzongliang || 0}}
+ 成交 {{item.chengjiaozongliang || 0}}
+ 退款 {{item.tuikuanliang || 0}}
+ 余额 ¥{{item.yue || '0.00'}}
+
+ 联系
+
+
+
+
+
+
+ 加载更多
+
+
+
+ 加载中…
+ — 已全部加载 —
+
+
+ 暂无下级打手
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/wodedashou/wodedashou.wxss b/miniprogram/pages/wodedashou/wodedashou.wxss
new file mode 100644
index 0000000..d7bb29c
--- /dev/null
+++ b/miniprogram/pages/wodedashou/wodedashou.wxss
@@ -0,0 +1,258 @@
+/* 全局 */
+page {
+ background: linear-gradient(135deg, #f5f7fa 0%, #e4e9f2 100%);
+ height: 100vh;
+ display: flex;
+ flex-direction: column;
+ }
+
+ .page-container {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ }
+
+ /* 固定头部区域 */
+ .fixed-header {
+ flex-shrink: 0;
+ background: rgba(255,255,255,0.7);
+ backdrop-filter: blur(20px);
+ padding: 20rpx 30rpx 10rpx;
+ z-index: 10;
+ }
+
+ .header-bar {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ }
+
+ .title {
+ font-size: 44rpx;
+ font-weight: 700;
+ color: #2c3e50;
+ }
+
+ .refresh-btn {
+ width: 60rpx;
+ height: 60rpx;
+ border-radius: 50%;
+ background: rgba(255,255,255,0.8);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 36rpx;
+ color: #5a6d7e;
+ transition: all 0.3s;
+ }
+
+ .refresh-btn:active { background: rgba(200,200,200,0.5); }
+
+ /* 搜索栏 */
+ .search-box {
+ display: flex;
+ margin: 15rpx 0;
+ background: white;
+ border-radius: 40rpx;
+ padding: 8rpx 20rpx;
+ box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.05);
+ }
+
+ .search-input {
+ flex: 1;
+ height: 70rpx;
+ font-size: 28rpx;
+ }
+
+ .search-icon {
+ width: 70rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 36rpx;
+ color: #7f8c8d;
+ }
+
+ /* 筛选行 */
+ .filter-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-top: 5rpx;
+ }
+
+ .filter-item {
+ display: flex;
+ align-items: center;
+ font-size: 26rpx;
+ color: #2c3e50;
+ }
+
+ .picker-trigger {
+ margin-left: 10rpx;
+ background: rgba(255,255,255,0.8);
+ border-radius: 20rpx;
+ padding: 6rpx 20rpx;
+ border: 1rpx solid #e0e0e0;
+ min-width: 100rpx;
+ text-align: center;
+ }
+
+ .filter-btn {
+ background: linear-gradient(135deg, #6c8cbf, #8aa5d0);
+ color: white;
+ border-radius: 30rpx;
+ padding: 10rpx 35rpx;
+ font-size: 26rpx;
+ font-weight: 500;
+ }
+
+ /* 统计卡片 */
+ .stats-cards {
+ display: flex;
+ justify-content: space-around;
+ margin-top: 15rpx;
+ margin-bottom: 10rpx;
+ padding: 25rpx 0;
+ background: rgba(255,255,255,0.65);
+ backdrop-filter: blur(15px);
+ border-radius: 24rpx;
+ box-shadow: 0 8rpx 20rpx rgba(0,0,0,0.05);
+ }
+
+ .stat-item { display: flex; flex-direction: column; align-items: center; }
+ .stat-num { font-size: 40rpx; font-weight: 700; color: #2c3e50; }
+ .stat-label { font-size: 24rpx; color: #7f8c8d; margin-top: 8rpx; }
+
+ /* 列表滚动区域(占据剩余高度) */
+ .list-scroll {
+ flex: 1;
+ padding: 0 30rpx;
+ box-sizing: border-box;
+ }
+
+ /* 列表内部容器 */
+ .list-container {
+ padding-bottom: 30rpx;
+ }
+
+ /* 打手卡片 */
+ .dashou-card {
+ display: flex;
+ align-items: center;
+ background: rgba(255,255,255,0.75);
+ backdrop-filter: blur(12px);
+ border-radius: 24rpx;
+ padding: 24rpx;
+ margin-bottom: 20rpx;
+ box-shadow: 0 6rpx 16rpx rgba(0,0,0,0.06);
+ transition: transform 0.2s;
+ }
+
+ .dashou-card:active { transform: scale(0.99); }
+
+ .avatar {
+ width: 120rpx;
+ height: 120rpx;
+ border-radius: 50%;
+ border: 3rpx solid rgba(255,255,255,0.8);
+ box-shadow: 0 4rpx 10rpx rgba(0,0,0,0.1);
+ margin-right: 20rpx;
+ flex-shrink: 0;
+ }
+
+ .info-right { flex: 1; display: flex; flex-direction: column; }
+
+ .row1 {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ }
+
+ .nick {
+ font-size: 32rpx;
+ font-weight: 600;
+ color: #1e272e;
+ max-width: 200rpx;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ .badges { display: flex; gap: 10rpx; }
+ .badge {
+ padding: 4rpx 16rpx;
+ border-radius: 20rpx;
+ font-size: 22rpx;
+ font-weight: 500;
+ color: white;
+ }
+
+ .online { background: #27ae60; }
+ .offline { background: #bdc3c7; }
+ .normal { background: #2980b9; }
+ .banned { background: #e74c3c; }
+
+ .uid { font-size: 24rpx; color: #7f8c8d; margin: 6rpx 0; }
+
+ .row2 {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 12rpx;
+ margin: 6rpx 0;
+ }
+
+ .tag {
+ background: #f0f3f7;
+ padding: 4rpx 14rpx;
+ border-radius: 16rpx;
+ font-size: 22rpx;
+ color: #34495e;
+ }
+
+ .contact-btn {
+ margin-top: 8rpx;
+ align-self: flex-end;
+ background: linear-gradient(135deg, #f39c12, #e67e22);
+ color: white;
+ padding: 10rpx 28rpx;
+ border-radius: 30rpx;
+ font-size: 26rpx;
+ font-weight: 500;
+ box-shadow: 0 4rpx 8rpx rgba(243,156,18,0.3);
+ }
+
+ /* 加载状态 */
+ .load-state { text-align: center; padding: 30rpx; color: #95a5a6; font-size: 24rpx; }
+
+ /* 加载更多按钮(新增) */
+ .load-more-btn {
+ width: 280rpx;
+ height: 80rpx;
+ line-height: 80rpx;
+ text-align: center;
+ background: rgba(108, 140, 191, 0.15);
+ backdrop-filter: blur(10px);
+ border-radius: 40rpx;
+ font-size: 28rpx;
+ font-weight: 500;
+ color: #5a6d7e;
+ border: 1rpx solid rgba(108, 140, 191, 0.3);
+ margin: 20rpx auto;
+ }
+
+ .load-more-btn:active {
+ background: rgba(108, 140, 191, 0.25);
+ transform: scale(0.97);
+ }
+
+ .empty {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding-top: 80rpx;
+ }
+
+ .empty image { width: 240rpx; height: 240rpx; opacity: 0.5; }
+ .empty text { margin-top: 30rpx; color: #b0b8c0; font-size: 28rpx; }
\ No newline at end of file
diff --git a/miniprogram/pages/xiaoxi/xiaoxi.json b/miniprogram/pages/xiaoxi/xiaoxi.json
new file mode 100644
index 0000000..7e4a6e8
--- /dev/null
+++ b/miniprogram/pages/xiaoxi/xiaoxi.json
@@ -0,0 +1,6 @@
+{
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification",
+ "custom-tab-bar": "/custom-tab-bar/index"
+ }
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/xiugai/xiugai.js b/miniprogram/pages/xiugai/xiugai.js
new file mode 100644
index 0000000..4615e24
--- /dev/null
+++ b/miniprogram/pages/xiugai/xiugai.js
@@ -0,0 +1,228 @@
+// pages/xiugai/xiugai.js
+const app = getApp()
+import upload from '../../utils/upload.js'
+
+Page({
+ data: {
+ avatarUrl: '',
+ uid: '',
+ nicheng: '',
+ phone: '',
+ originalAvatar: '',
+ originalNicheng: '',
+ isSaving: false,
+ hasChanges: false,
+ newAvatarFile: null,
+ isLoading: true,
+ isAvatarActive: false,
+ nicknameError: ''
+ },
+
+ onLoad() {
+ wx.setNavigationBarTitle({ title: '编辑资料' })
+ this.loadUserData()
+ },
+
+ onShow() {
+ this.registerNotificationComponent();
+ },
+
+ registerNotificationComponent() {
+ const notificationComp = this.selectComponent('#global-notification');
+ if (notificationComp && notificationComp.showNotification) {
+ app.globalData.globalNotification = {
+ show: (data) => notificationComp.showNotification(data),
+ hide: () => notificationComp.hideNotification()
+ };
+ }
+ },
+
+ // 加载用户数据(手机号只读,不参与修改逻辑)
+ loadUserData() {
+ try {
+ const uid = wx.getStorageSync('uid') || ''
+ const nicheng = wx.getStorageSync('nicheng') || ''
+ const phone = wx.getStorageSync('phone') || ''
+ const touxiang = wx.getStorageSync('touxiang') || ''
+
+ const ossImageUrl = app.globalData.ossImageUrl || ''
+ const defaultAvatar = app.globalData.morentouxiang || ''
+
+ let avatarUrl = ''
+ if (touxiang) {
+ avatarUrl = touxiang.startsWith('http') ? touxiang : `${ossImageUrl}${touxiang}`
+ } else if (defaultAvatar) {
+ avatarUrl = defaultAvatar.startsWith('http') ? defaultAvatar : `${ossImageUrl}${defaultAvatar}`
+ }
+
+ this.setData({
+ avatarUrl,
+ uid,
+ nicheng,
+ phone, // 仅展示
+ originalAvatar: touxiang,
+ originalNicheng: nicheng,
+ isLoading: false
+ })
+ } catch (error) {
+ console.error('加载用户数据失败:', error)
+ this.setData({ isLoading: false })
+ wx.showToast({ title: '加载数据失败', icon: 'none' })
+ }
+ },
+
+ // 微信头像选择(未改动)
+ onChooseAvatar(e) {
+ const avatarTempPath = e.detail.avatarUrl
+ this.setData({
+ avatarUrl: avatarTempPath,
+ newAvatarFile: avatarTempPath
+ }, () => {
+ this.checkChanges()
+ })
+ },
+
+ chooseAvatar() {
+ // 仅保留动画效果(透明按钮已触发 onChooseAvatar)
+ this.setData({ isAvatarActive: true })
+ setTimeout(() => {
+ this.setData({ isAvatarActive: false })
+ }, 500)
+ },
+
+ // 昵称输入(未改动)
+ onNicknameInput(e) {
+ const value = e.detail.value.trim()
+ this.setData({
+ nicheng: value,
+ nicknameError: ''
+ }, () => {
+ this.checkChanges()
+ })
+ },
+
+ // 检查变化(只比较昵称和头像)
+ checkChanges() {
+ const { originalNicheng, nicheng, newAvatarFile } = this.data;
+ const hasChanges = (nicheng !== originalNicheng) || (newAvatarFile !== null);
+ this.setData({ hasChanges });
+ },
+
+ // 保存(不包含手机号逻辑)
+ async onSave() {
+ if (!this.data.hasChanges) {
+ wx.showToast({ title: '没有需要保存的更改', icon: 'none' });
+ return;
+ }
+ if (this.data.isSaving) return;
+
+ // 验证数据(昵称)
+ if (!this.validateData()) return;
+
+ this.setData({ isSaving: true });
+ wx.showLoading({ title: '保存中...', mask: true });
+
+ try {
+ const formData = {};
+ if (this.data.nicheng !== this.data.originalNicheng) {
+ formData.nicheng = this.data.nicheng;
+ }
+
+ const res = await upload({
+ url: '/yonghu/xiugai',
+ formData,
+ filePath: this.data.newAvatarFile,
+ fileName: 'avatar'
+ });
+
+ if (res.statusCode === 200 && res.data) {
+ const response = res.data;
+ if (response.code === 0) {
+ await this.handleUpdateSuccess(response.data);
+ } else {
+ throw new Error(response.msg || '保存失败');
+ }
+ } else {
+ throw new Error('网络错误');
+ }
+ } catch (error) {
+ console.error('保存失败:', error);
+ if (error.message.includes('认证失败') || error.message.includes('401')) {
+ wx.showModal({
+ title: '提示',
+ content: '登录已过期,请重新登录',
+ complete: (res) => {
+ if (res.confirm) {
+ wx.removeStorageSync('token');
+ wx.removeStorageSync('userInfo');
+ wx.switchTab({ url: '/pages/wode/wode' });
+ }
+ }
+ });
+ } else {
+ wx.showToast({ title: error.message || '保存失败', icon: 'none' });
+ }
+ } finally {
+ wx.hideLoading();
+ this.setData({ isSaving: false });
+ }
+ },
+
+ // 验证数据(只验证昵称)
+ validateData() {
+ const { nicheng } = this.data;
+ let hasError = false;
+ this.setData({ nicknameError: '' });
+
+ if (nicheng && nicheng.length > 20) {
+ this.setData({ nicknameError: '昵称不能超过20个字符' });
+ hasError = true;
+ }
+ if (nicheng !== this.data.originalNicheng && nicheng.trim() === '') {
+ this.setData({ nicknameError: '昵称不能为空' });
+ hasError = true;
+ }
+ if (hasError) {
+ if (this.data.nicknameError) {
+ wx.showToast({ title: this.data.nicknameError, icon: 'none' });
+ }
+ return false;
+ }
+ return true;
+ },
+
+ // 更新成功处理(不再处理手机号)
+ async handleUpdateSuccess(updateData) {
+ if (updateData.touxiang !== undefined && updateData.touxiang !== null) {
+ wx.setStorageSync('touxiang', updateData.touxiang);
+ }
+ if (updateData.nicheng !== undefined && updateData.nicheng !== null) {
+ wx.setStorageSync('nicheng', updateData.nicheng);
+ }
+
+ if (updateData.nicheng) {
+ app.globalData.userNicheng = updateData.nicheng;
+ }
+
+ const ossImageUrl = app.globalData.ossImageUrl || '';
+ let avatarUrl = this.data.avatarUrl;
+ if (updateData.touxiang) {
+ avatarUrl = updateData.touxiang.startsWith('http') ?
+ updateData.touxiang : `${ossImageUrl}${updateData.touxiang}`;
+ }
+
+ this.setData({
+ avatarUrl,
+ originalAvatar: updateData.touxiang || this.data.originalAvatar,
+ originalNicheng: updateData.nicheng || this.data.originalNicheng,
+ newAvatarFile: null,
+ hasChanges: false
+ });
+
+ wx.showToast({
+ title: '保存成功',
+ icon: 'success',
+ duration: 2000
+ });
+ }
+});
\ No newline at end of file
diff --git a/miniprogram/pages/xiugai/xiugai.json b/miniprogram/pages/xiugai/xiugai.json
new file mode 100644
index 0000000..a0b74ef
--- /dev/null
+++ b/miniprogram/pages/xiugai/xiugai.json
@@ -0,0 +1,10 @@
+{
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification"
+ },
+ "navigationBarTitleText": "编辑资料",
+ "navigationBarBackgroundColor": "#ffffff",
+ "navigationBarTextStyle": "black",
+ "backgroundColor": "#f8f9fa",
+ "enablePullDownRefresh": false
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/xiugai/xiugai.wxml b/miniprogram/pages/xiugai/xiugai.wxml
new file mode 100644
index 0000000..5ce8a48
--- /dev/null
+++ b/miniprogram/pages/xiugai/xiugai.wxml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+ 点击更换头像
+
+
+
+
+
+
+
+
+ UID
+ {{uid || '••••••'}}
+
+
+
+
+ 昵称
+
+
+ {{nicknameError}}
+
+
+
+
+
+ 手机号
+ {{phone || '未绑定'}}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/xiugai/xiugai.wxss b/miniprogram/pages/xiugai/xiugai.wxss
new file mode 100644
index 0000000..469cc97
--- /dev/null
+++ b/miniprogram/pages/xiugai/xiugai.wxss
@@ -0,0 +1,477 @@
+/* pages/xiugai/xiugai.wxss */
+/* ====== 您的原始样式代码(完全未改动) ====== */
+page {
+ background: linear-gradient(135deg, #fffaf0 0%, #f0f8ff 100%);
+ min-height: 100vh;
+ }
+
+ .xiugai-page {
+ padding: 30rpx 40rpx 100rpx;
+ }
+
+ /* ============ 头像区域样式 ============ */
+ .avatar-section {
+ display: flex;
+ justify-content: center;
+ margin-bottom: 60rpx;
+ padding-top: 30rpx;
+ }
+
+ .avatar-container {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ }
+
+ /* 头像图片 - 添加呼吸动画 */
+ .avatar-image {
+ width: 200rpx;
+ height: 200rpx;
+ border-radius: 50%;
+ border: 6rpx solid #fff;
+ background-color: #f8f9fa;
+ z-index: 2;
+ position: relative;
+ /* 呼吸动画:温和的放大缩小 */
+ animation: gentleBreath 3s ease-in-out infinite;
+ /* 基础阴影 */
+ box-shadow:
+ 0 0 0 1rpx rgba(0, 200, 83, 0.3),
+ 0 15rpx 40rpx rgba(0, 200, 83, 0.15);
+ }
+
+ /* 温和的呼吸动画 */
+ @keyframes gentleBreath {
+ 0%, 100% {
+ transform: scale(1);
+ box-shadow:
+ 0 0 0 2rpx rgba(0, 200, 83, 0.4),
+ 0 15rpx 40rpx rgba(0, 200, 83, 0.2);
+ }
+ 50% {
+ transform: scale(1.03);
+ box-shadow:
+ 0 0 0 5rpx rgba(0, 200, 83, 0.6),
+ 0 20rpx 50rpx rgba(0, 200, 83, 0.3);
+ }
+ }
+
+ /* 边缘微光效果 - 更细腻 */
+ .avatar-halo {
+ position: absolute;
+ top: -8rpx;
+ left: -8rpx;
+ right: -8rpx;
+ bottom: -8rpx;
+ border-radius: 50%;
+ background: transparent;
+ border: 2rpx solid #00C853;
+ animation: gentleGlow 2s ease-in-out infinite;
+ opacity: 0;
+ z-index: 1;
+ pointer-events: none;
+ }
+
+ /* 温和的光晕动画 */
+ @keyframes gentleGlow {
+ 0%, 100% {
+ opacity: 0;
+ transform: scale(0.98);
+ border-color: rgba(0, 200, 83, 0.3);
+ }
+ 50% {
+ opacity: 1;
+ transform: scale(1.05);
+ border-color: rgba(0, 200, 83, 0.8);
+ }
+ }
+
+ /* 像素点环绕效果 */
+ .avatar-container::before {
+ content: '';
+ position: absolute;
+ top: -15rpx;
+ left: -15rpx;
+ right: -15rpx;
+ bottom: -15rpx;
+ border-radius: 50%;
+ background:
+ radial-gradient(circle at 30% 30%, rgba(0, 200, 83, 0.1) 0%, transparent 50%),
+ radial-gradient(circle at 70% 70%, rgba(100, 221, 23, 0.1) 0%, transparent 50%);
+ animation: pixelOrbit 4s linear infinite;
+ z-index: 0;
+ pointer-events: none;
+ }
+
+ @keyframes pixelOrbit {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+ }
+
+ /* 更换提示 */
+ .avatar-hint {
+ margin-top: 25rpx;
+ font-size: 28rpx;
+ color: #666;
+ padding: 12rpx 30rpx;
+ background: rgba(255, 255, 255, 0.9);
+ border-radius: 25rpx;
+ border: 1rpx solid #eee;
+ box-shadow: 0 5rpx 15rpx rgba(0, 0, 0, 0.05);
+ position: relative;
+ overflow: hidden;
+ }
+
+ /* 提示文字底部的微光 */
+ .avatar-hint::before {
+ content: '';
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: 2rpx;
+ background: linear-gradient(90deg, transparent, #00C853, transparent);
+ animation: hintGlow 2s ease-in-out infinite;
+ }
+
+ @keyframes hintGlow {
+ 0%, 100% {
+ opacity: 0.3;
+ }
+ 50% {
+ opacity: 1;
+ }
+ }
+
+ /* 点击特效 - 缩放效果 */
+ .avatar-container.active .avatar-image {
+ animation: clickScale 0.5s ease;
+ }
+
+ @keyframes clickScale {
+ 0%, 100% {
+ transform: scale(1);
+ }
+ 50% {
+ transform: scale(0.95);
+ }
+ }
+
+ /* 下载状态 */
+ .avatar-container.downloading .avatar-image {
+ opacity: 0.7;
+ filter: grayscale(30%);
+ }
+
+ /* 增强光晕效果 */
+ .avatar-halo {
+ position: absolute;
+ top: -8rpx;
+ left: -8rpx;
+ right: -8rpx;
+ bottom: -8rpx;
+ border-radius: 50%;
+ background: transparent;
+ border: 3rpx solid transparent;
+ animation: gentleGlow 2s ease-in-out infinite, colorShift 3s ease-in-out infinite;
+ opacity: 0;
+ z-index: 1;
+ pointer-events: none;
+ }
+
+ @keyframes gentleGlow {
+ 0%, 100% {
+ opacity: 0;
+ transform: scale(0.98);
+ }
+ 50% {
+ opacity: 1;
+ transform: scale(1.05);
+ }
+ }
+
+ @keyframes colorShift {
+ 0% {
+ border-color: rgba(0, 200, 83, 0.5);
+ }
+ 33% {
+ border-color: rgba(100, 221, 23, 0.5);
+ }
+ 66% {
+ border-color: rgba(0, 255, 153, 0.5);
+ }
+ 100% {
+ border-color: rgba(0, 200, 83, 0.5);
+ }
+ }
+
+ /* 头像呼吸效果 */
+ .avatar-image {
+ width: 200rpx;
+ height: 200rpx;
+ border-radius: 50%;
+ border: 6rpx solid #fff;
+ background-color: #f8f9fa;
+ z-index: 2;
+ position: relative;
+ animation: gentleBreath 4s ease-in-out infinite;
+ box-shadow:
+ 0 0 0 1rpx rgba(0, 200, 83, 0.3),
+ 0 15rpx 40rpx rgba(0, 200, 83, 0.15);
+ }
+
+ @keyframes gentleBreath {
+ 0%, 100% {
+ transform: scale(1);
+ box-shadow:
+ 0 0 0 2rpx rgba(0, 200, 83, 0.4),
+ 0 15rpx 40rpx rgba(0, 200, 83, 0.2);
+ }
+ 50% {
+ transform: scale(1.03);
+ box-shadow:
+ 0 0 0 5rpx rgba(0, 200, 83, 0.6),
+ 0 20rpx 50rpx rgba(0, 200, 83, 0.3);
+ }
+ }
+
+ /* 表单区域 */
+ .form-section {
+ background: white;
+ border-radius: 25rpx;
+ overflow: hidden;
+ margin-bottom: 50rpx;
+ box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.08);
+ border: 1rpx solid #f0f0f0;
+ }
+
+ .form-item {
+ display: flex;
+ align-items: center;
+ padding: 40rpx;
+ border-bottom: 1rpx solid #f0f0f0;
+ min-height: 110rpx;
+ }
+
+ .form-item:last-child {
+ border-bottom: none;
+ }
+
+ .item-label {
+ width: 150rpx;
+ font-size: 32rpx;
+ color: #333;
+ font-weight: 500;
+ flex-shrink: 0;
+ }
+
+ .item-value.readonly {
+ flex: 1;
+ font-size: 32rpx;
+ color: #666;
+ }
+
+ .item-input {
+ flex: 1;
+ font-size: 32rpx;
+ color: #333;
+ height: 50rpx;
+ line-height: 50rpx;
+ }
+
+ .placeholder {
+ color: #ccc;
+ font-size: 30rpx;
+ }
+
+ /* 输入框容器 */
+ .input-container {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ }
+
+ /* 输入框错误样式 */
+ .item-input.error {
+ border-bottom: 2rpx solid #ff4d4f;
+ animation: shake 0.5s;
+ }
+
+ @keyframes shake {
+ 0%, 100% { transform: translateX(0); }
+ 10%, 30%, 50%, 70%, 90% { transform: translateX(-2rpx); }
+ 20%, 40%, 60%, 80% { transform: translateX(2rpx); }
+ }
+
+ /* 错误提示 */
+ .error-hint {
+ font-size: 24rpx;
+ color: #ff4d4f;
+ margin-top: 8rpx;
+ padding-left: 10rpx;
+ animation: fadeIn 0.3s;
+ }
+
+ /* 手机号正确提示 */
+ .phone-hint {
+ font-size: 24rpx;
+ color: #00C853;
+ margin-top: 8rpx;
+ padding-left: 10rpx;
+ animation: fadeIn 0.3s;
+ }
+
+ @keyframes fadeIn {
+ from { opacity: 0; transform: translateY(-10rpx); }
+ to { opacity: 1; transform: translateY(0); }
+ }
+
+ /* 保存按钮 */
+ .save-btn {
+ width: 100%;
+ height: 100rpx;
+ line-height: 100rpx;
+ border-radius: 50rpx;
+ background: linear-gradient(135deg, #00C853 0%, #64DD17 100%);
+ color: white;
+ font-size: 34rpx;
+ font-weight: 500;
+ border: none;
+ position: relative;
+ overflow: hidden;
+ box-shadow: 0 15rpx 40rpx rgba(0, 200, 83, 0.25);
+ margin-top: 30rpx;
+ }
+
+ .save-btn[disabled] {
+ background: linear-gradient(135deg, #cccccc 0%, #aaaaaa 100%);
+ color: #999;
+ box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.1);
+ }
+
+ .btn-content {
+ position: relative;
+ z-index: 2;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ height: 100%;
+ }
+
+ .btn-sparkle {
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(
+ 90deg,
+ transparent 0%,
+ rgba(255, 255, 255, 0.2) 50%,
+ transparent 100%
+ );
+ animation: sparkleMove 2.5s ease-in-out infinite;
+ z-index: 1;
+ }
+
+ @keyframes sparkleMove {
+ 0% { left: -100%; }
+ 100% { left: 100%; }
+ }
+
+ .btn-hover {
+ opacity: 0.9;
+ transform: scale(0.98);
+ }
+
+ /* 底部安全区域 */
+ .safe-area {
+ height: 100rpx;
+ width: 100%;
+ }
+
+ /* 🔥🔥🔥【修改开始:新增透明按钮样式】 */
+ /* 说明:此样式仅作用于新增的 .avatar-trigger-button,使其透明并覆盖在头像区域 */
+ /* 此修改不会影响上方任何原有样式,仅新增样式规则 */
+
+ /* 确保头像区域为相对定位,作为透明按钮的定位参考 */
+ .avatar-section {
+ position: relative;
+ display: flex;
+ justify-content: center;
+ margin-bottom: 60rpx;
+ padding-top: 30rpx;
+ }
+
+ /* 透明功能按钮样式 */
+ .avatar-trigger-button {
+ /* 覆盖在原始头像容器上 */
+ position: absolute;
+ top: 0;
+ left: 50%;
+ transform: translateX(-50%);
+ width: 200rpx;
+ height: 200rpx;
+ border-radius: 50%;
+ z-index: 100;
+ /* 关键:完全透明不可见 */
+ background: transparent;
+ border: none;
+ padding: 0;
+ margin: 0;
+ opacity: 0;
+
+ /* 清除所有默认样式 */
+ line-height: 1;
+ font-size: 0;
+ }
+
+ /* 移除按钮默认边框 */
+ .avatar-trigger-button::after {
+ border: none;
+ }
+ /* 🔥🔥🔥【修改结束】 */
+
+ /* ====== 以下为新增样式(手机号获取按钮) ====== */
+
+/* 手机号行布局 */
+.phone-row {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ }
+
+ /* 手机号文本 */
+ .phone-value {
+ font-size: 32rpx;
+ color: #333;
+ flex: 1;
+ }
+
+ /* 获取手机号按钮 */
+ .get-phone-btn {
+ flex-shrink: 0;
+ margin-left: 20rpx;
+ padding: 10rpx 24rpx;
+ background: #4a6cf7;
+ color: #fff;
+ font-size: 26rpx;
+ border-radius: 40rpx;
+ border: none;
+ line-height: 1.4;
+ height: 60rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .phone-btn-hover {
+ opacity: 0.8;
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/xuanren/xuanren.js b/miniprogram/pages/xuanren/xuanren.js
new file mode 100644
index 0000000..5ac9bb3
--- /dev/null
+++ b/miniprogram/pages/xuanren/xuanren.js
@@ -0,0 +1,66 @@
+// pages/xuanren/xuanren.js
+Page({
+
+ /**
+ * 页面的初始数据
+ */
+ data: {
+
+ },
+
+ /**
+ * 生命周期函数--监听页面加载
+ */
+ onLoad(options) {
+
+ },
+
+ /**
+ * 生命周期函数--监听页面初次渲染完成
+ */
+ onReady() {
+
+ },
+
+ /**
+ * 生命周期函数--监听页面显示
+ */
+ onShow() {
+
+ },
+
+ /**
+ * 生命周期函数--监听页面隐藏
+ */
+ onHide() {
+
+ },
+
+ /**
+ * 生命周期函数--监听页面卸载
+ */
+ onUnload() {
+
+ },
+
+ /**
+ * 页面相关事件处理函数--监听用户下拉动作
+ */
+ onPullDownRefresh() {
+
+ },
+
+ /**
+ * 页面上拉触底事件的处理函数
+ */
+ onReachBottom() {
+
+ },
+
+ /**
+ * 用户点击右上角分享
+ */
+ onShareAppMessage() {
+
+ }
+})
\ No newline at end of file
diff --git a/miniprogram/pages/xuanren/xuanren.json b/miniprogram/pages/xuanren/xuanren.json
new file mode 100644
index 0000000..f1ce457
--- /dev/null
+++ b/miniprogram/pages/xuanren/xuanren.json
@@ -0,0 +1,5 @@
+{
+ "usingComponents": {
+ "custom-tab-bar": "/custom-tab-bar/index"
+ }
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/xuanren/xuanren.wxml b/miniprogram/pages/xuanren/xuanren.wxml
new file mode 100644
index 0000000..86909c4
--- /dev/null
+++ b/miniprogram/pages/xuanren/xuanren.wxml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/xuanren/xuanren.wxss b/miniprogram/pages/xuanren/xuanren.wxss
new file mode 100644
index 0000000..9103bc8
--- /dev/null
+++ b/miniprogram/pages/xuanren/xuanren.wxss
@@ -0,0 +1 @@
+/* pages/xuanren/xuanren.wxss */
\ No newline at end of file
diff --git a/miniprogram/pages/yaoqingdashou/yaoqingdashou.js b/miniprogram/pages/yaoqingdashou/yaoqingdashou.js
new file mode 100644
index 0000000..0dc5359
--- /dev/null
+++ b/miniprogram/pages/yaoqingdashou/yaoqingdashou.js
@@ -0,0 +1,248 @@
+// pages/yaoqingma/yaoqingma.js
+// 阿龙电竞 - 邀请码页面 (优化重构版)
+import request from '../../utils/request.js'
+
+Page({
+ /**
+ * 页面的初始数据 (字段保持与你渲染层一致)
+ */
+ data: {
+ yaoqingma: '', // 邀请码
+ isLoading: false, // 是否正在加载
+ copySuccess: false, // 是否复制成功
+ copyTimer: null // 复制成功提示计时器
+ },
+
+ /**
+ * 生命周期函数--监听页面加载
+ */
+ onLoad: function (options) {
+
+ // 仅初始化,不自动请求
+ this.chushihuaYaoqingma();
+ },
+
+ /**
+ * 生命周期函数--监听页面显示
+ */
+ onShow: function () {
+ // 每次显示页面时重置复制状态
+ if (this.data.copySuccess) {
+ this.chongzhiCopyZhuangtai();
+ }
+ },
+ onShow() {
+ // 原有代码...
+
+ // 🆕 注册通知组件
+ this.registerNotificationComponent();
+ this.registerNotificationComponent();
+},
+
+// 🆕 新增:注册通知组件
+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()
+ };
+ }
+},
+
+ /**
+ * 生命周期函数--监听页面卸载
+ */
+ onUnload: function () {
+ // 清理计时器
+ if (this.data.copyTimer) {
+ clearTimeout(this.data.copyTimer);
+ }
+ },
+
+ /**
+ * 初始化邀请码数据
+ */
+ chushihuaYaoqingma: function () {
+
+ // 只从缓存读取并显示,不主动请求
+ try {
+ const cachedYaoqingma = wx.getStorageSync('yaoqingma');
+ if (cachedYaoqingma && cachedYaoqingma.trim() !== '') {
+
+ this.setData({ yaoqingma: cachedYaoqingma });
+ } else {
+
+ }
+ } catch (error) {
+
+ }
+ },
+
+ /**
+ * 处理邀请码相关操作 (主入口函数)
+ * 如果没有邀请码则生成,如果有则复制
+ */
+ chuliYaoqingma: async function () {
+ // 防止重复点击
+ if (this.data.isLoading) {
+ return;
+ }
+
+ // 如果已经有邀请码,执行复制操作
+ if (this.data.yaoqingma && this.data.yaoqingma.trim() !== '') {
+ this.fuzhiYaoqingma();
+ return;
+ }
+
+ // 如果没有邀请码,向后端请求生成
+ await this.shengchengYaoqingma();
+ },
+
+ /**
+ * 向后端请求生成邀请码
+ */
+ shengchengYaoqingma: async function () {
+
+ this.setData({ isLoading: true });
+
+ try {
+ // 使用封装的request工具发送POST请求(与你的调用方式一致)
+ const response = await request({
+ url: '/yonghu/yaoqingma',
+ method: 'POST'
+ // 如果需要参数可以在此添加 data: {}
+ });
+
+
+
+ // 处理业务响应
+ if (response.data.code === 200) {
+ const newYaoqingma = response.data.data.yaoqingma;
+ this.chuliShengchengChenggong(newYaoqingma);
+ } else {
+ this.chuliShiwu('生成失败', response.message);
+ }
+ } catch (error) {
+ // 处理网络或系统错误
+
+ this.chuliShiwu('网络错误', error.message || '请检查网络后重试');
+ }
+ },
+
+ /**
+ * 处理生成成功的逻辑
+ */
+ chuliShengchengChenggong: function (newYaoqingma) {
+
+
+ // 更新页面数据
+ this.setData({
+ yaoqingma: newYaoqingma,
+ isLoading: false
+ });
+
+ // 保存到缓存
+ try {
+ wx.setStorageSync('yaoqingma', newYaoqingma);
+
+ } catch (error) {
+
+ }
+
+ // 显示成功提示
+ wx.showToast({
+ title: '生成成功!',
+ icon: 'success',
+ duration: 2000
+ });
+ },
+
+ /**
+ * 处理错误情况
+ */
+ chuliShiwu: function (title, message) {
+ this.setData({ isLoading: false });
+
+ wx.showToast({
+ title: message || title,
+ icon: 'none',
+ duration: 3000
+ });
+ },
+
+ /**
+ * 复制邀请码到剪贴板
+ */
+ fuzhiYaoqingma: function () {
+ const yaoqingma = this.data.yaoqingma;
+
+ if (!yaoqingma || yaoqingma.trim() === '') {
+ wx.showToast({
+ title: '暂无邀请码可复制',
+ icon: 'none',
+ duration: 2000
+ });
+ return;
+ }
+
+
+ wx.setClipboardData({
+ data: yaoqingma,
+ success: () => {
+
+ this.setData({ copySuccess: true });
+ wx.showToast({
+ title: '已复制',
+ icon: 'success',
+ duration: 1500
+ });
+ // 2秒后重置状态
+ const timer = setTimeout(() => {
+ this.chongzhiCopyZhuangtai();
+ }, 2000);
+ this.setData({ copyTimer: timer });
+ },
+ fail: (error) => {
+ console.error('复制失败:', error);
+ wx.showToast({
+ title: '复制失败',
+ icon: 'none',
+ duration: 2000
+ });
+ }
+ });
+ },
+
+ /**
+ * 重置复制状态
+ */
+ chongzhiCopyZhuangtai: function () {
+ if (this.data.copyTimer) {
+ clearTimeout(this.data.copyTimer);
+ }
+ this.setData({
+ copySuccess: false,
+ copyTimer: null
+ });
+ },
+
+ /**
+ * 用户点击右上角分享
+ */
+ onShareAppMessage: function () {
+ if (this.data.yaoqingma) {
+ return {
+ title: '我的阿龙电竞邀请码',
+ path: `/pages/yaoqingma/yaoqingma?yaoqingma=${this.data.yaoqingma}`
+ };
+ }
+ return {
+ title: '阿龙电竞陪玩平台',
+ path: '/pages/index/index'
+ };
+ }
+});
\ No newline at end of file
diff --git a/miniprogram/pages/yaoqingdashou/yaoqingdashou.json b/miniprogram/pages/yaoqingdashou/yaoqingdashou.json
new file mode 100644
index 0000000..6d5bf3e
--- /dev/null
+++ b/miniprogram/pages/yaoqingdashou/yaoqingdashou.json
@@ -0,0 +1,13 @@
+{
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification"
+ },
+ "navigationBarTitleText": "我的邀请码",
+ "navigationBarBackgroundColor": "#0a0e17",
+ "navigationBarTextStyle": "white",
+ "backgroundColor": "#0a0e17",
+ "backgroundTextStyle": "light",
+ "enablePullDownRefresh": false,
+ "onReachBottomDistance": 50,
+ "disableScroll": false
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/yaoqingdashou/yaoqingdashou.wxml b/miniprogram/pages/yaoqingdashou/yaoqingdashou.wxml
new file mode 100644
index 0000000..6a79b7e
--- /dev/null
+++ b/miniprogram/pages/yaoqingdashou/yaoqingdashou.wxml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+ 专属邀请码
+ 分享邀请码,招募更多大手
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 正在生成邀请码...
+
+
+
+
+ 我的邀请码
+
+ {{yaoqingma || '点击下方按钮生成'}}
+
+ ✓
+ 已复制
+
+
+
+ 此邀请码用于招募大手,每个管事唯一
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{yaoqingma ? '📋' : '✨'}}
+ {{yaoqingma ? '一键复制邀请码' : '生成我的邀请码'}}
+
+
+
+ {{yaoqingma ? '点击复制到剪贴板' : '点击生成专属邀请码'}}
+
+
+
+
+
+ 🔗
+ 将邀请码分享给打手,他们注册时填写即可绑定
+
+
+ 💰
+ 成功邀请的打手充值押金,您将获得分佣奖励
+
+
+ ⚠️
+ 邀请码仅限本人使用,请勿泄露给他人
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/yaoqingdashou/yaoqingdashou.wxss b/miniprogram/pages/yaoqingdashou/yaoqingdashou.wxss
new file mode 100644
index 0000000..0ab84aa
--- /dev/null
+++ b/miniprogram/pages/yaoqingdashou/yaoqingdashou.wxss
@@ -0,0 +1,405 @@
+/* 阿龙电竞 - 邀请码页面样式 */
+/* 页面整体样式 */
+.yaoqingma-page {
+ min-height: 100vh;
+ background: linear-gradient(135deg, #0a0e17 0%, #141a2a 100%);
+ padding: 40rpx 30rpx;
+ position: relative;
+ overflow: hidden;
+ color: #fff;
+ }
+
+ /* 顶部装饰条 */
+ .top-decoration {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 6rpx;
+ background: linear-gradient(90deg,
+ transparent 0%,
+ #00ff88 20%,
+ #00ff88 80%,
+ transparent 100%
+ );
+ opacity: 0.7;
+ }
+
+ /* 页面标题 */
+ .page-title {
+ text-align: center;
+ margin-bottom: 60rpx;
+ margin-top: 40rpx;
+ }
+
+ .title-text {
+ display: block;
+ font-size: 52rpx;
+ font-weight: bold;
+ background: linear-gradient(45deg, #fff 30%, #00ffaa 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+ letter-spacing: 4rpx;
+ margin-bottom: 16rpx;
+ }
+
+ .title-sub {
+ display: block;
+ font-size: 28rpx;
+ color: #88aaff;
+ opacity: 0.8;
+ }
+
+ /* 邀请码卡片 */
+ .yaoqingma-card {
+ background: rgba(20, 25, 40, 0.8);
+ border-radius: 24rpx;
+ padding: 50rpx 40rpx;
+ margin-bottom: 60rpx;
+ position: relative;
+ border: 1rpx solid rgba(0, 255, 136, 0.2);
+ box-shadow: 0 10rpx 30rpx rgba(0, 255, 136, 0.1);
+ transition: all 0.3s ease;
+ }
+
+ /* 卡片加载状态 */
+ .card-loading {
+ opacity: 0.7;
+ }
+
+ /* 卡片装饰角 */
+ .card-corner {
+ position: absolute;
+ width: 40rpx;
+ height: 40rpx;
+ border: 2rpx solid #00ff88;
+ opacity: 0.6;
+ }
+
+ .top-left {
+ top: 12rpx;
+ left: 12rpx;
+ border-right: none;
+ border-bottom: none;
+ }
+
+ .top-right {
+ top: 12rpx;
+ right: 12rpx;
+ border-left: none;
+ border-bottom: none;
+ }
+
+ .bottom-left {
+ bottom: 12rpx;
+ left: 12rpx;
+ border-right: none;
+ border-top: none;
+ }
+
+ .bottom-right {
+ bottom: 12rpx;
+ right: 12rpx;
+ border-left: none;
+ border-top: none;
+ }
+
+ /* 卡片内容 */
+ .card-content {
+ position: relative;
+ z-index: 2;
+ }
+
+ /* 加载状态样式 */
+ .loading-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 40rpx 0;
+ }
+
+ .loading-spinner {
+ width: 80rpx;
+ height: 80rpx;
+ border: 6rpx solid rgba(0, 255, 136, 0.2);
+ border-top-color: #00ff88;
+ border-radius: 50%;
+ animation: spin 1s linear infinite;
+ margin-bottom: 30rpx;
+ }
+
+ @keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+ }
+
+ .loading-text {
+ font-size: 28rpx;
+ color: #88ffcc;
+ opacity: 0.9;
+ }
+
+ /* 正常展示状态 */
+ .code-container {
+ text-align: center;
+ }
+
+ .code-label {
+ display: block;
+ font-size: 32rpx;
+ color: #aaddff;
+ margin-bottom: 40rpx;
+ letter-spacing: 4rpx;
+ }
+
+ .code-display {
+ background: rgba(0, 20, 30, 0.7);
+ border-radius: 16rpx;
+ padding: 40rpx 30rpx;
+ margin-bottom: 30rpx;
+ border: 1rpx solid rgba(0, 255, 136, 0.3);
+ position: relative;
+ overflow: hidden;
+ }
+
+ .code-text {
+ display: block;
+ font-size: 48rpx;
+ font-weight: bold;
+ letter-spacing: 8rpx;
+ font-family: 'Courier New', monospace;
+ color: #00ffaa;
+ text-shadow: 0 0 10rpx rgba(0, 255, 136, 0.5);
+ transition: all 0.3s ease;
+ word-break: break-all;
+ }
+
+ /* 复制成功后的样式 */
+ .code-copied {
+ color: #88ffaa;
+ text-shadow: 0 0 20rpx rgba(136, 255, 170, 0.8);
+ transform: scale(1.02);
+ }
+
+ .code-status {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 255, 136, 0.1);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ opacity: 0;
+ transition: opacity 0.3s ease;
+ border-radius: 16rpx;
+ }
+
+ .status-show {
+ opacity: 1;
+ }
+
+ .code-status .iconfont {
+ font-size: 60rpx;
+ color: #00ff88;
+ margin-bottom: 20rpx;
+ }
+
+ .code-status text {
+ font-size: 28rpx;
+ color: #ccffee;
+ }
+
+ .code-tip {
+ display: block;
+ font-size: 24rpx;
+ color: #8899cc;
+ opacity: 0.8;
+ margin-top: 20rpx;
+ }
+
+ /* 操作按钮区域 */
+ .action-area {
+ margin-top: 40rpx;
+ }
+
+ /* 操作按钮 */
+ .action-btn {
+ background: linear-gradient(135deg, rgba(0, 255, 136, 0.1) 0%, rgba(0, 200, 255, 0.1) 100%);
+ border-radius: 20rpx;
+ padding: 50rpx 40rpx;
+ margin-bottom: 50rpx;
+ position: relative;
+ overflow: hidden;
+ border: 1rpx solid rgba(0, 255, 136, 0.3);
+ transition: all 0.3s ease;
+ text-align: center;
+ }
+
+ /* 按钮悬停效果 */
+ .btn-hover {
+ transform: translateY(-4rpx);
+ box-shadow: 0 20rpx 40rpx rgba(0, 255, 136, 0.2);
+ }
+
+ /* 生成按钮样式 */
+ .btn-generate {
+ background: linear-gradient(135deg, rgba(0, 255, 136, 0.15) 0%, rgba(0, 200, 255, 0.15) 100%);
+ }
+
+ /* 复制按钮样式 */
+ .btn-copy {
+ background: linear-gradient(135deg, rgba(0, 255, 136, 0.2) 0%, rgba(0, 255, 200, 0.2) 100%);
+ }
+
+ /* 禁用状态 */
+ .btn-disabled {
+ opacity: 0.6;
+ pointer-events: none;
+ }
+
+ /* 按钮光效 */
+ .btn-glow {
+ position: absolute;
+ top: -50%;
+ left: -50%;
+ width: 200%;
+ height: 200%;
+ background: radial-gradient(
+ circle at center,
+ rgba(0, 255, 136, 0.3) 0%,
+ transparent 70%
+ );
+ opacity: 0;
+ animation: pulse 3s infinite;
+ }
+
+ @keyframes pulse {
+ 0%, 100% { opacity: 0.3; transform: scale(0.8); }
+ 50% { opacity: 0.6; transform: scale(1.1); }
+ }
+
+ .btn-content {
+ position: relative;
+ z-index: 2;
+ margin-bottom: 20rpx;
+ }
+
+ .btn-icon {
+ display: block;
+ font-size: 60rpx;
+ margin-bottom: 20rpx;
+ opacity: 0.9;
+ }
+
+ .btn-text {
+ display: block;
+ font-size: 40rpx;
+ font-weight: bold;
+ color: #fff;
+ letter-spacing: 2rpx;
+ text-shadow: 0 0 10rpx rgba(0, 255, 136, 0.5);
+ }
+
+ .btn-desc {
+ display: block;
+ font-size: 26rpx;
+ color: #aaddff;
+ opacity: 0.8;
+ }
+
+ /* 使用说明 */
+ .instruction {
+ background: rgba(30, 35, 50, 0.5);
+ border-radius: 16rpx;
+ padding: 40rpx 30rpx;
+ border: 1rpx solid rgba(100, 150, 255, 0.1);
+ }
+
+ .instruction-item {
+ display: flex;
+ align-items: flex-start;
+ margin-bottom: 30rpx;
+ }
+
+ .instruction-item:last-child {
+ margin-bottom: 0;
+ }
+
+ .instruction-icon {
+ font-size: 36rpx;
+ margin-right: 20rpx;
+ opacity: 0.8;
+ flex-shrink: 0;
+ margin-top: 4rpx;
+ }
+
+ .instruction-text {
+ font-size: 26rpx;
+ color: #ccddee;
+ line-height: 1.5;
+ opacity: 0.9;
+ }
+
+ /* 底部装饰 */
+ .bottom-decoration {
+ text-align: center;
+ margin-top: 60rpx;
+ padding-top: 40rpx;
+ border-top: 1rpx solid rgba(255, 255, 255, 0.1);
+ }
+
+ .platform-name {
+ font-size: 24rpx;
+ color: #8899cc;
+ opacity: 0.7;
+ letter-spacing: 2rpx;
+ }
+
+
+
+ /* pages/yaoqingma/yaoqingma.wxss - 仅修改按钮部分 */
+/* ... 其他样式保持不变 ... */
+
+/* 操作按钮 - 缩小尺寸 */
+.action-btn {
+ background: linear-gradient(135deg, rgba(0, 255, 136, 0.1) 0%, rgba(0, 200, 255, 0.1) 100%);
+ border-radius: 20rpx;
+ padding: 36rpx 30rpx; /* 减小内边距 */
+ margin-bottom: 50rpx;
+ position: relative;
+ overflow: hidden;
+ border: 1rpx solid rgba(0, 255, 136, 0.3);
+ transition: all 0.3s ease;
+ text-align: center;
+ }
+
+ /* 按钮内容相应调整 */
+ .btn-icon {
+ display: block;
+ font-size: 52rpx; /* 适当缩小图标 */
+ margin-bottom: 16rpx;
+ opacity: 0.9;
+ }
+
+ .btn-text {
+ display: block;
+ font-size: 34rpx; /* 适当缩小字体 */
+ font-weight: bold;
+ color: #fff;
+ letter-spacing: 2rpx;
+ text-shadow: 0 0 10rpx rgba(0, 255, 136, 0.5);
+ }
+
+ .btn-desc {
+ display: block;
+ font-size: 24rpx; /* 适当缩小说明文字 */
+ color: #aaddff;
+ opacity: 0.8;
+ margin-top: 8rpx;
+ }
+
+ /* ... 其他样式保持不变 ... */
\ No newline at end of file
diff --git a/miniprogram/pages/yqguanshi/yqguanshi.js b/miniprogram/pages/yqguanshi/yqguanshi.js
new file mode 100644
index 0000000..7a0fff4
--- /dev/null
+++ b/miniprogram/pages/yqguanshi/yqguanshi.js
@@ -0,0 +1,356 @@
+// pages/zuzhanghaibao/zuzhanghaibao.js
+import request from '../../utils/request.js';
+
+const app = getApp();
+
+Page({
+ data: {
+ qrcodeUrl: '', // 二维码完整URL
+ yaoqingma: '', // 组长邀请码字符串
+ isLoading: false,
+ showPosterCanvas: false,
+ posterImageTemp: '', // 生成的海报临时路径
+ avatarUrl: '', // 用户头像完整URL
+ uid: '',
+ nickName: '',
+ },
+
+ onLoad() {
+ this.loadFromCache();
+ this.loadUserInfo();
+ },
+
+ onShow() {
+ this.registerNotificationComponent();
+ },
+
+ registerNotificationComponent() {
+ const notificationComp = this.selectComponent('#global-notification');
+ if (notificationComp && notificationComp.showNotification) {
+ app.globalData.globalNotification = {
+ show: (data) => notificationComp.showNotification(data),
+ hide: () => notificationComp.hideNotification()
+ };
+ }
+ },
+
+ /**
+ * 从缓存加载二维码URL和邀请码
+ * 缓存键名使用独特拼音,避免与管事页面冲突
+ */
+ loadFromCache() {
+ try {
+ const relativeUrl = wx.getStorageSync('zuzhang_haibao_url');
+ if (relativeUrl) {
+ const fullUrl = this.getFullImageUrl(relativeUrl);
+ this.setData({ qrcodeUrl: fullUrl });
+ }
+ const yaoqingma = wx.getStorageSync('zuzhang_yaoqingma');
+ if (yaoqingma) {
+ this.setData({ yaoqingma });
+ }
+ } catch (error) {
+ console.error('读取缓存失败', error);
+ }
+ },
+
+ /**
+ * 加载用户头像、昵称、UID
+ */
+ loadUserInfo() {
+ const touxiang = wx.getStorageSync('touxiang');
+ const ossUrl = app.globalData.ossImageUrl || '';
+ const defaultAvatar = ossUrl + (app.globalData.morentouxiang || 'avatar/default.jpg');
+ let avatarUrl = defaultAvatar;
+ if (touxiang && typeof touxiang === 'string' && touxiang.trim() !== '') {
+ avatarUrl = this.getFullImageUrl(touxiang);
+ }
+ const uid = wx.getStorageSync('uid') || '';
+ // 组长昵称可能从全局变量或缓存获取
+ let nickName = app.globalData.nicheng || wx.getStorageSync('nicheng') || '组长';
+ this.setData({ avatarUrl, uid, nickName });
+ },
+
+ /**
+ * 拼接完整图片URL
+ */
+ getFullImageUrl(url) {
+ if (!url) return '';
+ const ossUrl = app.globalData.ossImageUrl || '';
+ return url.startsWith('http') ? url : ossUrl + url;
+ },
+
+ /**
+ * 刷新/获取二维码和邀请码
+ * 调用 /peizhi/zuzhanghb 接口
+ */
+ refreshQRCode() {
+ if (this.data.isLoading) return;
+ this.setData({ isLoading: true });
+
+ request({
+ url: '/peizhi/zuzhanghb',
+ method: 'POST',
+ })
+ .then(res => {
+ if (res.data && res.data.code === 0) {
+ const { url: relativeUrl, yaoqingma } = res.data.data; // 假设返回字段为 url 和 yaoqingma
+ if (relativeUrl) {
+ wx.setStorageSync('zuzhang_haibao_url', relativeUrl);
+ const fullUrl = this.getFullImageUrl(relativeUrl);
+ this.setData({ qrcodeUrl: fullUrl });
+ }
+ if (yaoqingma) {
+ wx.setStorageSync('zuzhang_yaoqingma', yaoqingma);
+ this.setData({ yaoqingma });
+ }
+ wx.showToast({ title: '获取成功', icon: 'success' });
+ } else {
+ wx.showToast({ title: res.data.msg || '获取失败', icon: 'none' });
+ }
+ })
+ .catch(err => {
+ console.error('获取组长海报失败', err);
+ wx.showToast({ title: '网络错误', icon: 'none' });
+ })
+ .finally(() => {
+ this.setData({ isLoading: false });
+ });
+ },
+
+ /**
+ * 保存二维码到相册(先下载网络图片)
+ */
+ saveToAlbum() {
+ if (!this.data.qrcodeUrl) {
+ wx.showToast({ title: '暂无二维码', icon: 'none' });
+ return;
+ }
+
+ wx.showLoading({ title: '保存中...', mask: true });
+ wx.downloadFile({
+ url: this.data.qrcodeUrl,
+ success: (res) => {
+ wx.hideLoading();
+ if (res.statusCode === 200) {
+ this.downloadAndSave(res.tempFilePath);
+ } else {
+ wx.showToast({ title: '下载失败', icon: 'none' });
+ }
+ },
+ fail: (err) => {
+ wx.hideLoading();
+ console.error('下载二维码失败', err);
+ wx.showToast({ title: '下载失败', icon: 'none' });
+ }
+ });
+ },
+
+ /**
+ * 复制邀请码到剪贴板
+ */
+ copyInviteCode() {
+ if (!this.data.yaoqingma) {
+ wx.showToast({ title: '暂无邀请码', icon: 'none' });
+ return;
+ }
+ wx.setClipboardData({
+ data: this.data.yaoqingma,
+ success: () => {
+ wx.showToast({ title: '复制成功', icon: 'success' });
+ },
+ fail: () => {
+ wx.showToast({ title: '复制失败', icon: 'none' });
+ }
+ });
+ },
+
+ /**
+ * 生成专属海报(包含头像、二维码、昵称、标语)
+ */
+ generateMyPoster() {
+ if (!this.data.qrcodeUrl) {
+ wx.showToast({ title: '请先生成二维码', icon: 'none' });
+ return;
+ }
+
+ wx.showLoading({ title: '生成海报中...', mask: true });
+
+ const ossUrl = app.globalData.ossImageUrl || '';
+ // 🔥 组长海报背景图路径:beijing/zuzhangbeijing.jpg
+ const bgUrl = ossUrl + 'beijing/zuzhangbeijing.jpg';
+ const urls = [bgUrl, this.data.qrcodeUrl, this.data.avatarUrl];
+
+ // 并行加载所有图片
+ const promises = urls.map(url => this.loadImage(url));
+ Promise.all(promises)
+ .then((images) => {
+ wx.hideLoading();
+ this.setData({ showPosterCanvas: true }, () => {
+ // 确保 canvas 已渲染
+ setTimeout(() => {
+ this.drawPoster(images[0], images[1], images[2]);
+ }, 200);
+ });
+ })
+ .catch((err) => {
+ wx.hideLoading();
+ wx.showToast({ title: '图片加载失败', icon: 'none' });
+ console.error('图片加载失败', err);
+ });
+ },
+
+ /**
+ * 加载图片为本地路径(用于 canvas 绘制)
+ */
+ loadImage(url) {
+ return new Promise((resolve, reject) => {
+ wx.getImageInfo({
+ src: url,
+ success: (res) => resolve(res.path),
+ fail: reject
+ });
+ });
+ },
+
+ /**
+ * 绘制海报
+ * @param {string} bgPath 背景图本地路径
+ * @param {string} qrPath 二维码本地路径
+ * @param {string} avatarPath 头像本地路径
+ */
+ drawPoster(bgPath, qrPath, avatarPath) {
+ const ctx = wx.createCanvasContext('posterCanvas', this);
+ const query = wx.createSelectorQuery().in(this);
+ query.select('.poster-canvas').boundingClientRect(rect => {
+ if (!rect) {
+ wx.showToast({ title: '画布获取失败', icon: 'none' });
+ return;
+ }
+ const canvasWidth = rect.width;
+ const canvasHeight = rect.height;
+
+ // 1. 绘制背景
+ ctx.drawImage(bgPath, 0, 0, canvasWidth, canvasHeight);
+
+ // 🔥【位置调整】二维码向右上角移动一丢丢 (增加 margin 从 5 到 15)
+ const qrSize = 100;
+ const margin = 15; // 原来 5,现在 15,向右下各移一点,但看起来更协调
+ const qrX = canvasWidth - qrSize - margin;
+ const qrY = canvasHeight - qrSize - margin;
+ ctx.drawImage(qrPath, qrX, qrY, qrSize, qrSize);
+
+ // 🔥【位置调整】头像也向右上移动一丢丢 (原来 margin 5,现在 margin 15)
+ const avatarSize = 60;
+ const avatarMargin = 15;
+ const avatarX = avatarMargin + 10; // 原来 margin 5+20,现在 15+20,相当于右移10
+ const avatarY = canvasHeight - avatarSize - avatarMargin;
+
+ // 圆形头像裁剪
+ ctx.save();
+ ctx.beginPath();
+ ctx.arc(avatarX + avatarSize/2, avatarY + avatarSize/2, avatarSize/2, 0, 2 * Math.PI);
+ ctx.clip();
+ ctx.drawImage(avatarPath, avatarX, avatarY, avatarSize, avatarSize);
+ ctx.restore();
+
+ // 绘制昵称(右侧)
+ ctx.setFontSize(16);
+ ctx.setFillStyle('#333333'); // 根据背景图颜色可调整
+ ctx.setTextAlign('left');
+
+ let nick = this.data.nickName || '组长';
+ if (nick.length > 3) {
+ nick = nick.substring(0, 3) + '...';
+ }
+ const nameText = nick;
+ const textX = avatarX + avatarSize + 8;
+ const textY = avatarY + avatarSize/2 + 6;
+ ctx.fillText(nameText, textX, textY);
+
+ // 绘制邀请语
+ ctx.setFontSize(18);
+ ctx.setFillStyle('#ffaa00'); // 金黄色,呼应组长主题
+ ctx.setTextAlign('left');
+ const slogan = '加入我的团队 ✦';
+ const sloganX = avatarX;
+ const sloganY = avatarY - 12;
+ ctx.fillText(slogan, sloganX, sloganY);
+
+ // 异步绘制
+ ctx.draw(false, () => {
+ wx.canvasToTempFilePath({
+ canvasId: 'posterCanvas',
+ success: (res) => {
+ this.setData({ posterImageTemp: res.tempFilePath });
+ },
+ fail: (err) => {
+ console.error('生成海报临时文件失败', err);
+ }
+ }, this);
+ });
+ }).exec();
+ },
+
+ /**
+ * 保存生成的海报
+ */
+ savePoster() {
+ if (!this.data.posterImageTemp) {
+ wx.showToast({ title: '海报尚未生成', icon: 'none' });
+ return;
+ }
+ this.downloadAndSave(this.data.posterImageTemp);
+ },
+
+ /**
+ * 隐藏海报画布,返回主界面
+ */
+ hidePoster() {
+ this.setData({ showPosterCanvas: false, posterImageTemp: '' });
+ },
+
+ /**
+ * 通用保存图片到相册(带权限检查)
+ */
+ downloadAndSave(filePath) {
+ wx.getSetting({
+ success: (res) => {
+ if (!res.authSetting['scope.writePhotosAlbum']) {
+ wx.authorize({
+ scope: 'scope.writePhotosAlbum',
+ success: () => {
+ this._saveImage(filePath);
+ },
+ fail: () => {
+ wx.showModal({
+ title: '提示',
+ content: '需要您授权保存到相册',
+ success: (modalRes) => {
+ if (modalRes.confirm) {
+ wx.openSetting();
+ }
+ }
+ });
+ }
+ });
+ } else {
+ this._saveImage(filePath);
+ }
+ }
+ });
+ },
+
+ _saveImage(filePath) {
+ wx.saveImageToPhotosAlbum({
+ filePath: filePath,
+ success: () => {
+ wx.showToast({ title: '保存成功', icon: 'success' });
+ },
+ fail: (err) => {
+ console.error('保存失败', err);
+ wx.showToast({ title: '保存失败', icon: 'none' });
+ }
+ });
+ },
+});
\ No newline at end of file
diff --git a/miniprogram/pages/yqguanshi/yqguanshi.json b/miniprogram/pages/yqguanshi/yqguanshi.json
new file mode 100644
index 0000000..e979c12
--- /dev/null
+++ b/miniprogram/pages/yqguanshi/yqguanshi.json
@@ -0,0 +1,8 @@
+{
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification"
+ },
+ "navigationBarTitleText": "组长海报",
+ "navigationBarBackgroundColor": "#0a0e17",
+ "navigationBarTextStyle": "white"
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/yqguanshi/yqguanshi.wxml b/miniprogram/pages/yqguanshi/yqguanshi.wxml
new file mode 100644
index 0000000..c702e9c
--- /dev/null
+++ b/miniprogram/pages/yqguanshi/yqguanshi.wxml
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+ ⚡ 组长专属海报 ⚡
+ 邀请管事加入团队,自动填充您的邀请码
+
+
+
+
+
+
+
+
+
+ ⚡
+ 点击下方按钮生成您的专属二维码
+
+
+
+
+
+ 邀请码:
+ {{yaoqingma}}
+
+ 📋
+ 复制
+
+
+
+
+
+
+
+ 📥
+ 保存二维码
+
+
+ ⟳
+ {{qrcodeUrl ? '重新获取' : '获取二维码'}}
+
+
+
+ 生成中...
+
+
+
+ 🎨
+ 生成专属海报
+
+
+
+
+
+
+
+
+
+ 📥
+ 保存海报
+
+
+ ↩️
+ 返回
+
+
+
+
+
+
+ * 管事扫码后自动进入注册页面,邀请码将自动填充
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/yqguanshi/yqguanshi.wxss b/miniprogram/pages/yqguanshi/yqguanshi.wxss
new file mode 100644
index 0000000..b271fb5
--- /dev/null
+++ b/miniprogram/pages/yqguanshi/yqguanshi.wxss
@@ -0,0 +1,315 @@
+/* pages/zuzhanghaibao/zuzhanghaibao.wxss - 金黄色机甲风格 */
+
+page {
+ background: #0a0e17; /* 深色背景,与管事页面一致,但高亮色改为金黄色 */
+ min-height: 100vh;
+ font-family: 'Avenir', 'PingFang SC', 'Helvetica Neue', sans-serif;
+ color: #fff;
+ }
+
+ /* 背景网格(颜色调为金黄色调) */
+ .grid-bg {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-image:
+ linear-gradient(rgba(255, 200, 0, 0.05) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(255, 200, 0, 0.05) 1px, transparent 1px);
+ background-size: 50rpx 50rpx;
+ pointer-events: none;
+ z-index: 0;
+ }
+
+ .page-container {
+ position: relative;
+ z-index: 2;
+ min-height: 100vh;
+ padding: 30rpx 25rpx;
+ box-sizing: border-box;
+ }
+
+ /* 机械装饰线条(金黄色) */
+ .mech-line {
+ position: absolute;
+ pointer-events: none;
+ z-index: 1;
+ }
+ .line-h {
+ top: 200rpx;
+ left: 0;
+ width: 100%;
+ height: 2rpx;
+ background: linear-gradient(90deg, transparent, #ffaa00, transparent);
+ }
+ .line-v {
+ right: 50rpx;
+ top: 100rpx;
+ width: 2rpx;
+ height: 300rpx;
+ background: linear-gradient(180deg, transparent, #ffaa00, transparent);
+ }
+
+ .content {
+ position: relative;
+ z-index: 3;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ min-height: 100vh;
+ padding: 40rpx 0;
+ }
+
+ /* 标题区域(金黄色光效) */
+ .title-section {
+ text-align: center;
+ margin-bottom: 60rpx;
+ }
+ .title {
+ font-size: 44rpx;
+ font-weight: bold;
+ color: #fff;
+ text-shadow: 0 0 30rpx #ffaa00;
+ letter-spacing: 2rpx;
+ display: block;
+ margin-bottom: 20rpx;
+ }
+ .subtitle {
+ font-size: 28rpx;
+ color: #ffd966;
+ opacity: 0.9;
+ letter-spacing: 1rpx;
+ }
+
+ /* 二维码区域(金黄色边框) */
+ .qrcode-section {
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-bottom: 40rpx; /* 给邀请码留出空间 */
+ }
+ .qrcode-wrapper {
+ position: relative;
+ width: 400rpx;
+ height: 400rpx;
+ background: rgba(20, 20, 20, 0.8);
+ border: 4rpx solid #ffaa00;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-shadow: 0 0 80rpx rgba(255, 170, 0, 0.4);
+ animation: qrcodePulse 2s infinite alternate;
+ }
+ .qrcode-img {
+ width: 90%;
+ height: 90%;
+ object-fit: contain;
+ }
+ .qrcode-glow {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: radial-gradient(circle at 30% 30%, rgba(255, 170, 0, 0.2), transparent 70%);
+ pointer-events: none;
+ }
+ .empty-qrcode {
+ width: 400rpx;
+ height: 400rpx;
+ background: rgba(20, 20, 20, 0.8);
+ border: 4rpx dashed #ffaa00;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ }
+ .empty-icon {
+ font-size: 80rpx;
+ color: #ffaa00;
+ margin-bottom: 20rpx;
+ opacity: 0.5;
+ }
+ .empty-text {
+ font-size: 26rpx;
+ color: #ffd966;
+ text-align: center;
+ padding: 0 40rpx;
+ }
+
+ /* 邀请码展示区域 */
+ .invite-code-section {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: rgba(0, 0, 0, 0.5);
+ padding: 20rpx 30rpx;
+ border-radius: 60rpx;
+ border: 2rpx solid #ffaa00;
+ margin-bottom: 30rpx;
+ width: 90%;
+ box-sizing: border-box;
+ }
+ .code-label {
+ font-size: 28rpx;
+ color: #ffd966;
+ margin-right: 10rpx;
+ }
+ .code-value {
+ font-size: 32rpx;
+ color: #fff;
+ font-weight: bold;
+ letter-spacing: 2rpx;
+ flex: 1;
+ text-align: center;
+ }
+ .copy-btn {
+ display: flex;
+ align-items: center;
+ background: rgba(255, 170, 0, 0.2);
+ padding: 10rpx 20rpx;
+ border-radius: 40rpx;
+ margin-left: 20rpx;
+ border: 2rpx solid #ffaa00;
+ }
+ .copy-icon {
+ font-size: 28rpx;
+ color: #ffaa00;
+ margin-right: 6rpx;
+ }
+ .copy-text {
+ font-size: 24rpx;
+ color: #ffaa00;
+ }
+ .copy-btn:active {
+ background: rgba(255, 170, 0, 0.4);
+ transform: scale(0.96);
+ }
+
+ /* 按钮区域(金黄色斜切风格) */
+ .button-section {
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ margin-bottom: 40rpx;
+ }
+ .btn-group {
+ display: flex;
+ gap: 20rpx;
+ flex-wrap: wrap;
+ justify-content: center;
+ }
+ .btn {
+ width: 220rpx;
+ height: 80rpx;
+ background: rgba(20, 20, 20, 0.8);
+ border: 2rpx solid #ffaa00;
+ border-right: none;
+ border-bottom: none;
+ clip-path: polygon(0 0, 100% 0, 90% 100%, 0 100%);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ transition: 0.2s;
+ cursor: pointer;
+ }
+ .btn:active {
+ transform: scale(0.96);
+ box-shadow: 0 0 40rpx #ffaa00;
+ }
+ .save-btn {
+ border-color: #ffaa00;
+ }
+ .refresh-btn {
+ border-color: #ffaa00;
+ }
+ .refresh-btn .btn-icon {
+ color: #ffaa00;
+ }
+ .poster-btn {
+ border-color: #ffaa00;
+ }
+ .poster-btn .btn-icon {
+ color: #ffaa00;
+ }
+ .loading-btn {
+ opacity: 0.7;
+ pointer-events: none;
+ }
+ .btn-icon {
+ font-size: 36rpx;
+ margin-right: 8rpx;
+ color: #fff;
+ }
+ .btn-text {
+ font-size: 26rpx;
+ font-weight: 500;
+ color: #fff;
+ }
+ .loading-spinner-small {
+ width: 30rpx;
+ height: 30rpx;
+ border: 4rpx solid rgba(255,255,255,0.2);
+ border-top: 4rpx solid #ffaa00;
+ border-radius: 50%;
+ animation: spin 1s linear infinite;
+ margin-right: 10rpx;
+ }
+
+ /* 专属海报展示区 */
+ .poster-section {
+ margin-top: 40rpx;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ width: 100%;
+ }
+ .poster-canvas {
+ width: 600rpx;
+ height: 900rpx;
+ background: #1a1f2e;
+ border: 4rpx solid #ffaa00;
+ box-shadow: 0 0 60rpx #ffaa00;
+ }
+ .poster-actions {
+ display: flex;
+ gap: 30rpx;
+ margin-top: 30rpx;
+ justify-content: center;
+ }
+ .save-poster-btn {
+ background: linear-gradient(45deg, #cc8400, #ffaa00);
+ border-color: #ffaa00;
+ }
+ .back-btn {
+ background: rgba(20, 20, 20, 0.8);
+ border-color: #ffaa00;
+ }
+
+ /* 使用说明 */
+ .instruction {
+ margin-top: 30rpx;
+ padding: 20rpx 40rpx;
+ background: rgba(20, 20, 20, 0.5);
+ border-left: 4rpx solid #ffaa00;
+ clip-path: polygon(0 0, 100% 0, 98% 100%, 0 100%);
+ }
+ .instruction-text {
+ font-size: 24rpx;
+ color: #ffd966;
+ line-height: 1.6;
+ }
+
+ /* 动画 */
+ @keyframes qrcodePulse {
+ 0% { box-shadow: 0 0 40rpx rgba(255, 170, 0, 0.3); }
+ 100% { box-shadow: 0 0 100rpx rgba(255, 170, 0, 0.6); }
+ }
+ @keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/zuzhangduan/zuzhangduan.js b/miniprogram/pages/zuzhangduan/zuzhangduan.js
new file mode 100644
index 0000000..addc538
--- /dev/null
+++ b/miniprogram/pages/zuzhangduan/zuzhangduan.js
@@ -0,0 +1,201 @@
+// pages/zuzhangduan/zuzhangduan.js
+import request from '../../utils/request.js'
+
+Page({
+ data: {
+ // 页面状态
+ isZuzhang: false,
+ isLoading: false,
+
+ // 邀请码相关
+ inviteCode: '',
+
+ // 用户信息
+ uid: '',
+ nicheng: '',
+ avatarUrl: '',
+
+ // 组长信息
+ ketixian: '0.00', // 可提现金额
+ guanshiCount: 0, // 邀请的管事数量
+ fenhongZonge: '0.00', // 分红总额
+ },
+
+ onLoad() {
+ this.checkLoginStatus()
+ },
+
+ onShow() {
+ this.registerNotificationComponent()
+ if (this.data.isZuzhang) {
+ this.getZuzhangInfo()
+ }
+ },
+
+ // 检查登录状态
+ checkLoginStatus() {
+ const token = wx.getStorageSync('token')
+ if (!token) {
+ wx.showModal({
+ title: '提示',
+ content: '您尚未登录,请先登录',
+ confirmText: '去登录',
+ success: (res) => {
+ if (res.confirm) {
+ wx.reLaunch({ url: '/pages/gerenzhongxin/gerenzhongxin' })
+ }
+ }
+ })
+ return false
+ }
+ this.checkZuzhangStatus()
+ return true
+ },
+
+ // 检查组长状态
+ checkZuzhangStatus() {
+ try {
+ const zuzhangstatus = wx.getStorageSync('zuzhangstatus')
+ if (zuzhangstatus === 1) {
+ this.setData({ isZuzhang: true })
+ this.loadUserInfo()
+ this.getZuzhangInfo()
+ } else {
+ this.setData({ isZuzhang: false })
+ }
+ } catch (error) {
+ console.error('读取缓存失败:', error)
+ this.setData({ isZuzhang: false })
+ }
+ },
+
+ // 加载用户信息
+ loadUserInfo() {
+ const app = getApp()
+ const globalData = app.globalData || {}
+
+ const touxiang = wx.getStorageSync('touxiang')
+ const ossImageUrl = globalData.ossImageUrl || ''
+ const morentouxiang = globalData.morentouxiang || 'avatar/default.jpg'
+
+ let avatarUrl = ''
+ if (touxiang) {
+ avatarUrl = ossImageUrl + (touxiang.startsWith('/') ? touxiang : '/' + touxiang)
+ } else {
+ avatarUrl = ossImageUrl + (morentouxiang.startsWith('/') ? morentouxiang : '/' + morentouxiang)
+ }
+
+ const uid = wx.getStorageSync('uid') || ''
+ const nicheng = wx.getStorageSync('nicheng') || globalData.nicheng || ''
+
+ this.setData({ avatarUrl, uid, nicheng })
+ },
+
+ // 注册通知组件
+ 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()
+ }
+ }
+ },
+
+ // 获取组长信息(刷新)
+ async getZuzhangInfo() {
+ this.setData({ isLoading: true })
+ try {
+ const res = await request({
+ url: '/yonghu/zuzhangxinxi',
+ method: 'POST'
+ })
+ if (res && res.data.code === 200) {
+ const data = res.data.data
+ this.setData({
+ ketixian: data.ketixian || '0.00',
+ guanshiCount: data.guanshiCount || 0,
+ fenhongZonge: data.fenhongZonge || '0.00',
+ isLoading: false
+ })
+ wx.showToast({ title: '刷新成功', icon: 'success', duration: 1500 })
+ } else {
+ throw new Error(res?.data?.msg || '获取信息失败')
+ }
+ } catch (error) {
+ console.error('获取组长信息失败:', error)
+ this.setData({ isLoading: false })
+ wx.showToast({ title: error.message || '刷新失败', icon: 'none' })
+ }
+ },
+
+ // 刷新按钮点击
+ refreshZuzhangInfo() {
+ this.getZuzhangInfo()
+ },
+
+ // 邀请码输入
+ onInviteCodeInput(e) {
+ this.setData({ inviteCode: e.detail.value.trim() })
+ },
+
+ // 注册组长
+ async onRegister() {
+ const { inviteCode } = this.data
+ if (!inviteCode) {
+ wx.showToast({ title: '请输入邀请码', icon: 'none', duration: 2000 })
+ return
+ }
+ if (inviteCode.length > 100) {
+ wx.showToast({ title: '邀请码不能超过100字符', icon: 'none', duration: 2000 })
+ return
+ }
+
+ this.setData({ isLoading: true })
+ try {
+ const res = await request({
+ url: '/yonghu/zuzhangzhuce',
+ method: 'POST',
+ data: { inviteCode }
+ })
+ if (res && res.data.code === 200) {
+ wx.setStorageSync('zuzhangstatus', 1)
+ const data = res.data.data
+ this.setData({
+ isZuzhang: true,
+ ketixian: data.ketixian || '0.00',
+ guanshiCount: data.guanshiCount || 0,
+ fenhongZonge: data.fenhongZonge || '0.00',
+ isLoading: false
+ })
+ this.loadUserInfo()
+ wx.showToast({ title: '注册成功!', icon: 'success', duration: 2000 })
+ } else {
+ throw new Error(res?.data?.msg || '注册失败')
+ }
+ } catch (error) {
+ console.error('注册失败:', error)
+ this.setData({ isLoading: false })
+ wx.showToast({ title: error.message || '注册失败', icon: 'error' })
+ }
+ },
+
+ // 🔥【修改】跳转到提现页面,传递from=zuzhang参数
+ goToWithdraw() {
+ const { ketixian } = this.data
+ wx.navigateTo({
+ url: `/pages/tixian/tixian?from=zuzhang&amount=${ketixian}`
+ })
+ },
+
+ // 跳转到分红记录页面
+ goToFenhongJilu() {
+ wx.navigateTo({ url: '/pages/zzfhjilu/zzfhjilu' })
+ },
+
+ // 跳转到邀请管事页面
+ goToYaoqingGuanshi() {
+ wx.navigateTo({ url: '/pages/yqguanshi/yqguanshi' })
+ }
+})
\ No newline at end of file
diff --git a/miniprogram/pages/zuzhangduan/zuzhangduan.json b/miniprogram/pages/zuzhangduan/zuzhangduan.json
new file mode 100644
index 0000000..29241d1
--- /dev/null
+++ b/miniprogram/pages/zuzhangduan/zuzhangduan.json
@@ -0,0 +1,10 @@
+{
+ "navigationBarBackgroundColor": "#0b0f1c",
+ "navigationBarTextStyle": "white",
+ "navigationBarTitleText": "组长驾驶舱",
+ "backgroundColor": "#0b0f1c",
+ "backgroundTextStyle": "light",
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification"
+ }
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/zuzhangduan/zuzhangduan.wxml b/miniprogram/pages/zuzhangduan/zuzhangduan.wxml
new file mode 100644
index 0000000..e9fa248
--- /dev/null
+++ b/miniprogram/pages/zuzhangduan/zuzhangduan.wxml
@@ -0,0 +1,108 @@
+
+
+
+
+ ⚡ 组长认证 ⚡
+
+
+
+
+ 立即激活
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 💰 可提现余额
+
+ {{ketixian}}
+ 元
+
+
+
+
+
+
+
+
+ 👥
+
+ 邀请管事
+ {{guanshiCount}}
+
+
+
+
+ 📊
+
+ 分红总额
+ {{fenhongZonge}}
+
+
+
+
+
+
+
+
+ ⚡ 立即提现 ⚡
+
+
+
+
+
+
+
+ 📈
+ 分红记录
+
+
+ 👥
+ 邀请管事
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/zuzhangduan/zuzhangduan.wxss b/miniprogram/pages/zuzhangduan/zuzhangduan.wxss
new file mode 100644
index 0000000..f3e3aa8
--- /dev/null
+++ b/miniprogram/pages/zuzhangduan/zuzhangduan.wxss
@@ -0,0 +1,562 @@
+/* pages/zuzhangduan/zuzhangduan.wxss - 重设计,更饱满科技感 */
+
+/* ===== 全局变量 ===== */
+page {
+ background: #0a0c14;
+ min-height: 100vh;
+ font-family: 'Avenir', 'PingFang SC', 'Helvetica Neue', sans-serif;
+ color: #fff;
+ }
+
+ /* 背景网格(增强) */
+ page::before {
+ content: '';
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-image:
+ linear-gradient(rgba(0, 255, 255, 0.03) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(0, 255, 255, 0.03) 1px, transparent 1px);
+ background-size: 50rpx 50rpx;
+ pointer-events: none;
+ z-index: 0;
+ }
+
+ .page-container {
+ position: relative;
+ z-index: 2;
+ min-height: 100vh;
+ padding: 40rpx 30rpx;
+ box-sizing: border-box;
+ }
+
+ /* ===== 未注册状态(保持原样,稍作调整) ===== */
+ .unregistered-page {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ min-height: 100vh;
+ background: rgba(0, 10, 20, 0.9);
+ backdrop-filter: blur(20px);
+ -webkit-backdrop-filter: blur(20px);
+ border: 2rpx solid #0ff;
+ box-shadow: 0 0 150rpx rgba(0, 255, 255, 0.4);
+ clip-path: polygon(0 0, 100% 0, 90% 100%, 0 100%);
+ margin: 20rpx;
+ padding: 60rpx 40rpx;
+ }
+
+ .glitch-title {
+ font-size: 40rpx;
+ color: #0ff;
+ text-transform: uppercase;
+ letter-spacing: 8rpx;
+ text-shadow: 0 0 20rpx #0ff, 0 0 40rpx #0ff, 0 0 60rpx #0ff;
+ margin-bottom: 80rpx;
+ border-bottom: 2rpx solid #0ff;
+ padding-bottom: 20rpx;
+ animation: glitch 3s infinite;
+ }
+
+ @keyframes glitch {
+ 0%,100% { transform: skew(0deg); }
+ 95% { transform: skew(2deg); }
+ 96% { transform: skew(-2deg); }
+ 97% { transform: skew(0deg); }
+ }
+
+ .input-area {
+ width: 100%;
+ margin-bottom: 80rpx;
+ }
+
+ .glitch-input {
+ width: 100%;
+ height: 100rpx;
+ background: transparent;
+ border: 2rpx solid #0ff;
+ border-right: none;
+ border-bottom: none;
+ clip-path: polygon(0 0, 100% 0, 90% 100%, 0 100%);
+ padding: 0 40rpx;
+ font-size: 30rpx;
+ color: #fff;
+ caret-color: #0ff;
+ box-sizing: border-box;
+ }
+
+ .glitch-input:focus {
+ box-shadow: inset 0 0 40rpx rgba(0, 255, 255, 0.3);
+ }
+
+ .placeholder {
+ color: rgba(255, 255, 255, 0.2);
+ }
+
+ .glitch-button {
+ width: 100%;
+ height: 100rpx;
+ background: linear-gradient(45deg, #00cccc, #0066ff);
+ clip-path: polygon(0 0, 100% 0, 85% 100%, 0 100%);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ overflow: hidden;
+ box-shadow: 0 0 60rpx #0ff;
+ }
+
+ .button-text {
+ font-size: 36rpx;
+ font-weight: bold;
+ color: #fff;
+ letter-spacing: 4rpx;
+ z-index: 2;
+ }
+
+ .button-glow {
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
+ animation: scan 3s infinite;
+ }
+
+ /* ===== 已注册状态(重设计) ===== */
+ .registered-page {
+ position: relative;
+ padding: 30rpx 20rpx;
+ }
+
+ /* 增强网格背景 */
+ .cyber-grid {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-image:
+ linear-gradient(rgba(0, 255, 255, 0.05) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(0, 255, 255, 0.05) 1px, transparent 1px);
+ background-size: 80rpx 80rpx;
+ pointer-events: none;
+ z-index: 0;
+ }
+
+ /* 扫描线效果(调快) */
+ .scan-line {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 8rpx;
+ background: rgba(0, 255, 255, 0.25);
+ box-shadow: 0 0 60rpx #0ff;
+ animation: scanMove 3s linear infinite;
+ pointer-events: none;
+ z-index: 10;
+ }
+
+ @keyframes scanMove {
+ 0% { top: 0; }
+ 100% { top: 100%; }
+ }
+
+ /* 刷新按钮(能量核心)保持不变 */
+ .refresh-core {
+ position: absolute;
+ top: 20rpx;
+ right: 20rpx;
+ width: 80rpx;
+ height: 80rpx;
+ background: rgba(0, 20, 40, 0.9);
+ border: 2rpx solid #0ff;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 30;
+ box-shadow: 0 0 30rpx #0ff;
+ }
+
+ .core-spinner {
+ width: 40rpx;
+ height: 40rpx;
+ border: 4rpx solid #0ff;
+ border-top-color: transparent;
+ border-radius: 50%;
+ animation: spin 1s linear infinite;
+ }
+
+ .refresh-core:active {
+ box-shadow: 0 0 80rpx #0ff;
+ transform: scale(0.95);
+ }
+
+ /* 头部(增强) */
+ .cyber-header {
+ display: flex;
+ align-items: center;
+ margin: 80rpx 0 60rpx;
+ position: relative;
+ z-index: 20;
+ }
+
+ .avatar-container {
+ width: 140rpx;
+ height: 140rpx;
+ position: relative;
+ margin-right: 30rpx;
+ }
+
+ .avatar {
+ width: 100%;
+ height: 100%;
+ border-radius: 50%;
+ border: 4rpx solid #0ff;
+ box-shadow: 0 0 60rpx #0ff;
+ object-fit: cover;
+ position: relative;
+ z-index: 2;
+ }
+
+ .avatar-ring {
+ position: absolute;
+ top: -10rpx;
+ left: -10rpx;
+ right: -10rpx;
+ bottom: -10rpx;
+ border: 2rpx solid #0ff;
+ border-radius: 50%;
+ opacity: 0.6;
+ animation: pulse 2s infinite;
+ z-index: 1;
+ }
+
+ .avatar-glow {
+ position: absolute;
+ top: -20rpx;
+ left: -20rpx;
+ right: -20rpx;
+ bottom: -20rpx;
+ background: radial-gradient(circle, rgba(0, 255, 255, 0.4) 0%, transparent 70%);
+ border-radius: 50%;
+ z-index: 0;
+ animation: glowPulse 3s infinite alternate;
+ }
+
+ .user-info {
+ flex: 1;
+ }
+
+ .nickname {
+ font-size: 48rpx;
+ font-weight: bold;
+ color: #fff;
+ text-shadow: 0 0 30rpx #0ff, 0 0 60rpx #0ff;
+ margin-bottom: 10rpx;
+ display: block;
+ }
+
+ .uid-tag {
+ display: inline-block;
+ background: rgba(0, 30, 50, 0.8);
+ border-left: 4rpx solid #0ff;
+ padding: 10rpx 40rpx;
+ clip-path: polygon(0 0, 100% 0, 90% 100%, 0 100%);
+ font-size: 28rpx;
+ color: #9cf;
+ box-shadow: 0 0 20rpx rgba(0, 255, 255, 0.2);
+ }
+
+ /* 资产总览卡片 */
+ .asset-card {
+ background: rgba(0, 20, 30, 0.7);
+ backdrop-filter: blur(15px);
+ -webkit-backdrop-filter: blur(15px);
+ border: 2rpx solid #0ff;
+ border-radius: 40rpx;
+ padding: 40rpx 30rpx;
+ margin-bottom: 40rpx;
+ position: relative;
+ overflow: hidden;
+ box-shadow: 0 0 100rpx rgba(0, 255, 255, 0.3);
+ z-index: 20;
+ }
+
+ .card-glow {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: radial-gradient(circle at 70% 30%, rgba(0, 255, 255, 0.2), transparent 70%);
+ pointer-events: none;
+ }
+
+ .asset-title {
+ font-size: 32rpx;
+ color: #9cf;
+ text-transform: uppercase;
+ letter-spacing: 4rpx;
+ margin-bottom: 20rpx;
+ display: flex;
+ align-items: center;
+ }
+
+ .asset-title::before {
+ content: '⚡';
+ margin-right: 10rpx;
+ font-size: 40rpx;
+ color: #0ff;
+ }
+
+ .asset-value {
+ display: flex;
+ align-items: baseline;
+ margin-bottom: 10rpx;
+ }
+
+ .value-number {
+ font-size: 120rpx;
+ font-weight: 800;
+ color: #fff;
+ text-shadow: 0 0 40rpx #0ff, 0 0 80rpx #0ff, 0 0 120rpx #0ff;
+ margin-right: 15rpx;
+ line-height: 1;
+ }
+
+ .value-unit {
+ font-size: 40rpx;
+ color: #0ff;
+ text-shadow: 0 0 20rpx #0ff;
+ }
+
+ .asset-beam {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ height: 4rpx;
+ background: linear-gradient(90deg, transparent, #0ff, #00f, #0ff, transparent);
+ animation: beamMove 4s linear infinite;
+ }
+
+ @keyframes beamMove {
+ 0% { transform: translateX(-100%); }
+ 100% { transform: translateX(100%); }
+ }
+
+ /* 双数据卡片 */
+ .stats-row {
+ display: flex;
+ gap: 30rpx;
+ margin-bottom: 50rpx;
+ z-index: 20;
+ position: relative;
+ }
+
+ .stat-card {
+ flex: 1;
+ background: rgba(0, 20, 30, 0.7);
+ backdrop-filter: blur(10px);
+ -webkit-backdrop-filter: blur(10px);
+ border: 2rpx solid #0ff;
+ border-radius: 30rpx;
+ padding: 30rpx 20rpx;
+ display: flex;
+ align-items: center;
+ position: relative;
+ overflow: hidden;
+ box-shadow: 0 0 50rpx rgba(0, 255, 255, 0.2);
+ }
+
+ .stat-icon {
+ font-size: 70rpx;
+ margin-right: 20rpx;
+ filter: drop-shadow(0 0 15rpx #0ff);
+ }
+
+ .stat-content {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ }
+
+ .stat-label {
+ font-size: 28rpx;
+ color: #9cf;
+ margin-bottom: 10rpx;
+ }
+
+ .stat-number {
+ font-size: 48rpx;
+ font-weight: bold;
+ color: #fff;
+ text-shadow: 0 0 20rpx #0ff;
+ }
+
+ .stat-glow {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: radial-gradient(circle at 30% 30%, rgba(0, 255, 255, 0.15), transparent 70%);
+ pointer-events: none;
+ }
+
+ .stat-card:active {
+ transform: scale(0.98);
+ box-shadow: 0 0 80rpx #0ff;
+ }
+
+ /* 立即提现按钮(无图标,大尺寸) */
+ .withdraw-btn-wrapper {
+ display: flex;
+ justify-content: center;
+ margin: 30rpx 0 50rpx;
+ z-index: 20;
+ position: relative;
+ }
+
+ .withdraw-btn {
+ width: 80%;
+ height: 100rpx;
+ background: linear-gradient(45deg, #00cccc, #0066ff);
+ border-radius: 60rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ overflow: hidden;
+ box-shadow: 0 0 80rpx #0ff, 0 0 150rpx rgba(0, 255, 255, 0.3);
+ border: 2rpx solid rgba(255, 255, 255, 0.3);
+ }
+
+ .btn-text {
+ font-size: 40rpx;
+ font-weight: bold;
+ color: #fff;
+ letter-spacing: 6rpx;
+ z-index: 2;
+ text-shadow: 0 0 20rpx #fff;
+ }
+
+ .btn-glow {
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent);
+ animation: scan 2s infinite;
+ }
+
+ .withdraw-btn:active {
+ transform: scale(0.96);
+ box-shadow: 0 0 120rpx #0ff;
+ }
+
+ /* 底部功能按钮(调整间距) */
+ .function-panel {
+ display: flex;
+ justify-content: space-around;
+ gap: 30rpx;
+ margin-top: 20rpx;
+ z-index: 20;
+ position: relative;
+ }
+
+ .function-btn {
+ flex: 1;
+ background: rgba(0, 20, 30, 0.8);
+ border: 2rpx solid #0ff;
+ border-radius: 40rpx;
+ padding: 30rpx 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ position: relative;
+ overflow: hidden;
+ box-shadow: 0 0 40rpx rgba(0, 255, 255, 0.1);
+ }
+
+ .function-btn::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 4rpx;
+ background: linear-gradient(90deg, transparent, #0ff, transparent);
+ }
+
+ .function-btn:active {
+ transform: translateY(-8rpx);
+ box-shadow: 0 20rpx 80rpx #0ff;
+ }
+
+ .btn-icon {
+ font-size: 80rpx;
+ margin-bottom: 15rpx;
+ filter: drop-shadow(0 0 20rpx #0ff);
+ }
+
+ .btn-label {
+ font-size: 30rpx;
+ color: #fff;
+ font-weight: 500;
+ text-shadow: 0 0 10rpx #0ff;
+ }
+
+ /* 动画 */
+ @keyframes scan {
+ 0% { left: -100%; }
+ 100% { left: 100%; }
+ }
+
+ @keyframes pulse {
+ 0%,100% { opacity: 0.5; transform: scale(1); }
+ 50% { opacity: 1; transform: scale(1.1); }
+ }
+
+ @keyframes glowPulse {
+ 0% { opacity: 0.3; transform: scale(0.9); }
+ 100% { opacity: 0.6; transform: scale(1.2); }
+ }
+
+ @keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+ }
+
+ /* ===== 加载遮罩(保持不变) ===== */
+ .loading-mask {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(2, 6, 12, 0.95);
+ backdrop-filter: blur(20px);
+ -webkit-backdrop-filter: blur(20px);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 9999;
+ }
+
+ .loader {
+ width: 200rpx;
+ height: 200rpx;
+ border: 4rpx solid #0ff;
+ border-radius: 50%;
+ border-top-color: transparent;
+ animation: spin 1s linear infinite;
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/zzfhjilu/zzfhjilu.js b/miniprogram/pages/zzfhjilu/zzfhjilu.js
new file mode 100644
index 0000000..ecc3363
--- /dev/null
+++ b/miniprogram/pages/zzfhjilu/zzfhjilu.js
@@ -0,0 +1,216 @@
+// pages/zuzhangfenhongjilu/zuzhangfenhongjilu.js
+import request from '../../utils/request.js';
+
+const PAGE_SIZE = 5; // 每页数据条数
+const REFRESH_COOLDOWN = 2000; // 刷新冷却时间 (ms)
+
+const app = getApp();
+
+Page({
+ data: {
+ // 统计数据
+ fenhongZonge: '0.00', // 分红总额
+ yaoqingGuanshi: 0, // 邀请管事数
+ fenhongCishu: 0, // 分红次数
+
+ // 列表数据
+ jiluList: [], // 分红记录列表
+ totalCount: 0, // 总记录数(后端返回)
+ currentPage: 1, // 当前页码
+ hasMore: true, // 是否还有更多数据
+ loading: false, // 首次加载中
+ loadingMore: false, // 加载更多中
+ refreshDisabled: false, // 刷新按钮是否冷却
+
+ // 错误状态
+ errorMsg: '', // 错误提示信息
+ },
+
+ onLoad() {
+ this.loadFirstPage();
+ },
+
+ onShow() {
+ this.registerNotification();
+ },
+
+ // 注册全局通知组件
+ registerNotification() {
+ const notificationComp = this.selectComponent('#global-notification');
+ if (notificationComp && notificationComp.showNotification) {
+ app.globalData.globalNotification = {
+ show: (data) => notificationComp.showNotification(data),
+ hide: () => notificationComp.hideNotification()
+ };
+ }
+ },
+
+ // 加载第一页数据
+ loadFirstPage() {
+ this.setData({
+ currentPage: 1,
+ hasMore: true,
+ jiluList: [],
+ errorMsg: ''
+ });
+ this.fetchData(true);
+ },
+
+ // 刷新数据(点击刷新按钮)
+ onRefresh() {
+ if (this.data.refreshDisabled) {
+ wx.showToast({
+ title: `请稍后重试`,
+ icon: 'none',
+ duration: 1000
+ });
+ return;
+ }
+
+ // 启用冷却
+ this.setData({ refreshDisabled: true });
+ setTimeout(() => {
+ this.setData({ refreshDisabled: false });
+ }, REFRESH_COOLDOWN);
+
+ // 显示刷新动画(让按钮旋转)
+ // 执行刷新
+ this.loadFirstPage();
+ },
+
+ // 上拉加载更多
+ onLoadMore() {
+ const { hasMore, loading, loadingMore, currentPage } = this.data;
+ if (!hasMore || loading || loadingMore) return;
+
+ this.setData({ loadingMore: true });
+ this.fetchData(false, currentPage + 1);
+ },
+
+ // 核心数据获取方法
+ async fetchData(reset = false, page = 1) {
+ const url = '/shangpin/zuzhangfenhong'; // 假设后端接口地址
+
+ if (reset) {
+ this.setData({ loading: true, errorMsg: '' });
+ }
+
+ try {
+ const res = await request({
+ url,
+ method: 'POST',
+ data: {
+ page: page,
+ page_size: PAGE_SIZE
+ }
+ });
+
+ if (res.data.code === 200) {
+ const data = res.data.data || {};
+ // 更新统计数据
+ this.setData({
+ fenhongZonge: data.fenhong_zonge || '0.00',
+ yaoqingGuanshi: data.yaoqing_guanshi || 0,
+ fenhongCishu: data.fenhong_cishu || 0,
+ totalCount: data.total || 0
+ });
+
+ // 处理列表数据(拼接头像)
+ const rawList = data.list || [];
+ const processedList = this.processList(rawList);
+
+ if (reset) {
+ // 第一页
+ this.setData({
+ jiluList: processedList,
+ currentPage: page,
+ hasMore: processedList.length === PAGE_SIZE, // 如果等于页大小,认为可能还有更多
+ });
+ } else {
+ // 加载更多
+ const newList = [...this.data.jiluList, ...processedList];
+ this.setData({
+ jiluList: newList,
+ currentPage: page,
+ hasMore: processedList.length === PAGE_SIZE,
+ });
+ }
+
+ // 如果加载后数据为空且是第一页,可以给出提示
+ if (reset && processedList.length === 0) {
+ // 空状态已在 wxml 处理
+ }
+ } else {
+ // 业务错误
+ this.setData({
+ errorMsg: res.data.msg || '数据加载失败'
+ });
+ this.showNotification('error', res.data.msg || '数据加载失败');
+ }
+ } catch (err) {
+ console.error('获取分红记录失败', err);
+ this.setData({
+ errorMsg: '网络异常,请检查网络'
+ });
+ this.showNotification('error', '网络异常');
+ } finally {
+ if (reset) {
+ this.setData({ loading: false });
+ } else {
+ this.setData({ loadingMore: false });
+ }
+ }
+ },
+
+ // 处理列表项:拼接头像、格式化金额和时间
+ processList(list) {
+ const ossUrl = app.globalData.ossImageUrl || '';
+ const defaultAvatar = ossUrl + (app.globalData.morentouxiang || 'avatar/default.jpg');
+
+ return list.map(item => ({
+ // 打手信息
+ dashouAvatar: this.getFullUrl(item.dashou_avatar, ossUrl, defaultAvatar),
+ dashouNicheng: item.dashou_nicheng || '打手',
+ dashouYonghuid: item.dashou_yonghuid || '',
+ // 管事信息
+ guanshiAvatar: this.getFullUrl(item.guanshi_avatar, ossUrl, defaultAvatar),
+ guanshiNicheng: item.guanshi_nicheng || '管事',
+ guanshiYonghuid: item.guanshi_yonghuid || '',
+ // 分红信息
+ fenhong: parseFloat(item.fenhong || 0).toFixed(2),
+ shijian: this.formatTime(item.create_time)
+ }));
+ },
+
+ // 拼接完整图片URL
+ getFullUrl(relativePath, ossUrl, defaultUrl) {
+ if (!relativePath) return defaultUrl;
+ if (relativePath.startsWith('http')) return relativePath;
+ const full = ossUrl + (relativePath.startsWith('/') ? relativePath : '/' + relativePath);
+ return full || defaultUrl;
+ },
+
+ // 格式化时间(根据实际后端返回调整)
+ formatTime(timestamp) {
+ if (!timestamp) return '';
+ try {
+ const date = new Date(timestamp);
+ return `${date.getMonth() + 1}月${date.getDate()}日 ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
+ } catch {
+ return timestamp.substring(0, 10) || '';
+ }
+ },
+
+ // 重试加载(当错误提示出现时)
+ onRetry() {
+ this.loadFirstPage();
+ },
+
+ // 显示通知(可选)
+ showNotification(type, msg) {
+ const noti = app.globalData.globalNotification;
+ if (noti && noti.show) {
+ noti.show({ type, message: msg });
+ }
+ }
+});
\ No newline at end of file
diff --git a/miniprogram/pages/zzfhjilu/zzfhjilu.json b/miniprogram/pages/zzfhjilu/zzfhjilu.json
new file mode 100644
index 0000000..1dc2ad2
--- /dev/null
+++ b/miniprogram/pages/zzfhjilu/zzfhjilu.json
@@ -0,0 +1,10 @@
+{
+ "navigationBarTitleText": "组长分红记录",
+ "navigationBarBackgroundColor": "#0A0C12",
+ "navigationBarTextStyle": "white",
+ "backgroundColor": "#0A0C12",
+ "backgroundTextStyle": "light",
+ "usingComponents": {
+ "global-notification": "/components/global-notification/global-notification"
+ }
+ }
\ No newline at end of file
diff --git a/miniprogram/pages/zzfhjilu/zzfhjilu.wxml b/miniprogram/pages/zzfhjilu/zzfhjilu.wxml
new file mode 100644
index 0000000..65b8ae8
--- /dev/null
+++ b/miniprogram/pages/zzfhjilu/zzfhjilu.wxml
@@ -0,0 +1,119 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ 加载中...
+
+
+
+
+ 📭
+ 暂无分红记录
+ 邀请管事并让打手充值即可获得分红
+ 刷新
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{item.dashouNicheng}}
+ ID: {{item.dashouYonghuid}}
+
+
+
+
+
+ 所属管事
+ →
+
+
+
+
+
+
+ {{item.guanshiNicheng}}
+ ID: {{item.guanshiYonghuid}}
+
+
+ +¥{{item.fenhong}}
+ {{item.shijian}}
+
+
+
+
+
+
+
+
+ 加载更多...
+
+
+
+
+ —— 没有更多了 ——
+
+
+
+
+
+
+
+
+ ⚠️
+ {{errorMsg}}
+ 重试
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/zzfhjilu/zzfhjilu.wxss b/miniprogram/pages/zzfhjilu/zzfhjilu.wxss
new file mode 100644
index 0000000..07221e3
--- /dev/null
+++ b/miniprogram/pages/zzfhjilu/zzfhjilu.wxss
@@ -0,0 +1,728 @@
+/* pages/zuzhangfenhongjilu/zuzhangfenhongjilu.wxss - 超时空战士机甲风格 */
+
+/* ==================== 全局变量与动画 ==================== */
+page {
+ height: 100%;
+ background: #0A0C12;
+ font-family: 'PingFang SC', 'Helvetica Neue', 'Arial', sans-serif;
+ color: #E0E6F0;
+ }
+
+ /* 全局动画定义 */
+ @keyframes scanMove {
+ 0% { transform: translateX(-100%); }
+ 100% { transform: translateX(100%); }
+ }
+
+ @keyframes pulse {
+ 0%,100% { opacity: 0.4; }
+ 50% { opacity: 1; }
+ }
+
+ @keyframes spin {
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
+ }
+
+ @keyframes energyFlow {
+ 0% { background-position: -200% 0; }
+ 100% { background-position: 200% 0; }
+ }
+
+ @keyframes glitch {
+ 0% { transform: translate(0); }
+ 20% { transform: translate(-2px, 2px); }
+ 40% { transform: translate(2px, -2px); }
+ 60% { transform: translate(-2px, 0); }
+ 80% { transform: translate(2px, 0); }
+ 100% { transform: translate(0); }
+ }
+
+ /* ==================== 页面容器 ==================== */
+ .page {
+ height: 100%;
+ position: relative;
+ background: radial-gradient(ellipse at 50% 30%, #1A2635, #0A0C12);
+ }
+
+ /* 背景机甲网格(使用伪元素) */
+ .page::before {
+ content: '';
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-image:
+ linear-gradient(rgba(0, 180, 255, 0.03) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(0, 180, 255, 0.03) 1px, transparent 1px);
+ background-size: 60rpx 60rpx;
+ pointer-events: none;
+ z-index: 0;
+ }
+
+ /* 顶部扫描线(固定) */
+ .page::after {
+ content: '';
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 4rpx;
+ background: linear-gradient(90deg, transparent, #00B4FF, #7A2EFF, transparent);
+ box-shadow: 0 0 30rpx #00B4FF;
+ animation: scanMove 4s linear infinite;
+ z-index: 10;
+ }
+
+ /* ==================== 固定头部 ==================== */
+ .fixed-header {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ z-index: 100;
+ /* 半透明金属质感 */
+ background: rgba(16, 24, 36, 0.85);
+ backdrop-filter: blur(20px);
+ -webkit-backdrop-filter: blur(20px);
+ border-bottom: 2rpx solid #00B4FF;
+ box-shadow: 0 10rpx 40rpx rgba(0, 180, 255, 0.2);
+ }
+
+ .header-content {
+ padding: 30rpx 30rpx 20rpx; /* 缩小上下间距 */
+ width: 100%;
+ box-sizing: border-box;
+ position: relative;
+ }
+
+ /* 统计行 - 机甲风格面板 */
+ .stat-row {
+ display: flex;
+ align-items: center;
+ background: rgba(0, 20, 40, 0.6);
+ border: 2rpx solid #00B4FF;
+ border-radius: 30rpx;
+ padding: 25rpx 15rpx;
+ position: relative;
+ overflow: hidden;
+ box-shadow: 0 0 30rpx rgba(0, 180, 255, 0.3), inset 0 0 20rpx rgba(0, 180, 255, 0.2);
+ }
+
+ /* 面板内部光效 */
+ .stat-row::before {
+ content: '';
+ position: absolute;
+ top: -50%;
+ left: -50%;
+ width: 200%;
+ height: 200%;
+ background: radial-gradient(circle at 30% 30%, rgba(0, 180, 255, 0.2), transparent 70%);
+ animation: pulse 4s infinite;
+ pointer-events: none;
+ }
+
+ .stat-cell {
+ flex: 1;
+ text-align: center;
+ position: relative;
+ z-index: 2;
+ }
+
+ .stat-divider {
+ width: 2rpx;
+ height: 40rpx;
+ background: linear-gradient(to bottom, transparent, #00B4FF, #7A2EFF, transparent);
+ box-shadow: 0 0 10rpx #00B4FF;
+ }
+
+ .stat-label {
+ font-size: 24rpx;
+ color: #A0B8D0;
+ letter-spacing: 2rpx;
+ margin-bottom: 6rpx;
+ display: block;
+ text-transform: uppercase;
+ }
+
+ .stat-number {
+ font-size: 42rpx;
+ font-weight: 800;
+ color: #E0F0FF;
+ line-height: 1.2;
+ display: block;
+ text-shadow: 0 0 15rpx #00B4FF, 0 0 30rpx #7A2EFF;
+ }
+
+ .stat-number.blue {
+ color: #fff;
+ text-shadow: 0 0 20rpx #00B4FF, 0 0 40rpx #7A2EFF;
+ }
+
+ /* 刷新按钮 - 机甲能量环 */
+ .refresh-btn {
+ position: absolute;
+ top: 35rpx;
+ right: 45rpx;
+ width: 80rpx;
+ height: 80rpx;
+ background: rgba(0, 20, 40, 0.8);
+ border: 2rpx solid #00B4FF;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-shadow: 0 0 30rpx #00B4FF;
+ transition: all 0.2s;
+ z-index: 20;
+ }
+
+ .refresh-btn::before {
+ content: '';
+ position: absolute;
+ top: -4rpx;
+ left: -4rpx;
+ right: -4rpx;
+ bottom: -4rpx;
+ border: 2rpx solid rgba(0, 180, 255, 0.5);
+ border-radius: 50%;
+ animation: pulse 2s infinite;
+ }
+
+ .refresh-btn:active {
+ transform: scale(0.9);
+ box-shadow: 0 0 50rpx #7A2EFF;
+ }
+
+ .refresh-icon {
+ font-size: 40rpx;
+ color: #00B4FF;
+ transition: transform 0.3s;
+ z-index: 2;
+ }
+
+ .refresh-icon.spin {
+ animation: spin 1s linear infinite;
+ }
+
+ /* ==================== 滚动区域 ==================== */
+ .scroll-area {
+ position: fixed;
+ top: 240rpx; /* 根据新头部高度调整,更紧凑 */
+ left: 0;
+ width: 100%;
+ bottom: 0;
+ overflow-y: auto;
+ z-index: 50;
+ }
+
+ .list-content {
+ padding: 0 30rpx;
+ box-sizing: border-box;
+ min-height: 100%;
+ }
+
+ /* ==================== 加载中状态 ==================== */
+ .loading-state {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ min-height: 700rpx;
+ color: #A0B8D0;
+ font-size: 28rpx;
+ letter-spacing: 2rpx;
+ }
+
+ .spinner {
+ width: 80rpx;
+ height: 80rpx;
+ border: 6rpx solid rgba(0, 180, 255, 0.2);
+ border-top: 6rpx solid #00B4FF;
+ border-right: 6rpx solid #7A2EFF;
+ border-radius: 50%;
+ animation: spin 1s linear infinite;
+ margin-bottom: 30rpx;
+ box-shadow: 0 0 30rpx rgba(0, 180, 255, 0.3);
+ }
+
+ .spinner.small {
+ width: 50rpx;
+ height: 50rpx;
+ border-width: 4rpx;
+ }
+
+ /* ==================== 空状态 ==================== */
+ .empty-state {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ min-height: 750rpx;
+ background: rgba(0, 20, 40, 0.4);
+ border: 2rpx solid #00B4FF;
+ border-radius: 50rpx;
+ padding: 100rpx 40rpx;
+ margin-top: 30rpx;
+ backdrop-filter: blur(10px);
+ -webkit-backdrop-filter: blur(10px);
+ position: relative;
+ overflow: hidden;
+ box-shadow: 0 0 60rpx rgba(0, 180, 255, 0.2);
+ }
+
+ .empty-state::before {
+ content: '';
+ position: absolute;
+ top: -50%;
+ left: -50%;
+ width: 200%;
+ height: 200%;
+ background: radial-gradient(circle at 30% 30%, rgba(0, 180, 255, 0.1), transparent 70%);
+ animation: pulse 3s infinite;
+ }
+
+ .empty-icon {
+ font-size: 110rpx;
+ color: #00B4FF;
+ margin-bottom: 30rpx;
+ filter: drop-shadow(0 0 30rpx #00B4FF);
+ }
+
+ .empty-title {
+ font-size: 36rpx;
+ font-weight: 700;
+ color: #E0F0FF;
+ margin-bottom: 20rpx;
+ text-shadow: 0 0 15rpx #00B4FF;
+ }
+
+ .empty-tip {
+ font-size: 26rpx;
+ color: #A0B8D0;
+ margin-bottom: 60rpx;
+ text-align: center;
+ max-width: 450rpx;
+ line-height: 1.5;
+ }
+
+ .empty-btn {
+ padding: 20rpx 80rpx;
+ background: linear-gradient(135deg, #00B4FF, #7A2EFF);
+ border-radius: 60rpx;
+ color: #fff;
+ font-size: 30rpx;
+ font-weight: 600;
+ position: relative;
+ overflow: hidden;
+ box-shadow: 0 10rpx 40rpx rgba(0, 180, 255, 0.4);
+ border: none;
+ letter-spacing: 2rpx;
+ }
+
+ .empty-btn::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
+ animation: scanMove 2s infinite;
+ }
+
+ .empty-btn:active {
+ transform: scale(0.96);
+ box-shadow: 0 10rpx 50rpx #7A2EFF;
+ }
+
+ /* ==================== 列表标题 ==================== */
+ .list-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: baseline;
+ margin: 20rpx 0 20rpx; /* 减小上边距 */
+ padding: 0 10rpx;
+ }
+
+ .list-title {
+ font-size: 32rpx;
+ font-weight: 700;
+ color: #00B4FF;
+ text-shadow: 0 0 15rpx #00B4FF;
+ letter-spacing: 2rpx;
+ position: relative;
+ }
+
+ .list-title::after {
+ content: '';
+ position: absolute;
+ bottom: -6rpx;
+ left: 0;
+ width: 100%;
+ height: 2rpx;
+ background: linear-gradient(90deg, transparent, #00B4FF, #7A2EFF, transparent);
+ }
+
+ .list-count {
+ font-size: 24rpx;
+ color: #7A8FAA;
+ background: rgba(0, 180, 255, 0.1);
+ padding: 4rpx 20rpx;
+ border-radius: 30rpx;
+ border: 1rpx solid rgba(0, 180, 255, 0.3);
+ }
+
+ /* ==================== 卡片列表 ==================== */
+ .card-list {
+ display: flex;
+ flex-direction: column;
+ gap: 30rpx;
+ padding-bottom: 50rpx;
+ }
+
+ /* 卡片主体 - 装甲玻璃 */
+ .card {
+ background: rgba(0, 20, 40, 0.5);
+ backdrop-filter: blur(15px);
+ -webkit-backdrop-filter: blur(15px);
+ border: 2rpx solid rgba(0, 180, 255, 0.4);
+ border-radius: 40rpx;
+ padding: 30rpx 20rpx;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ position: relative;
+ overflow: hidden;
+ box-shadow: 0 20rpx 50rpx rgba(0,0,0,0.8), inset 0 0 30rpx rgba(0, 180, 255, 0.2);
+ transition: all 0.2s;
+ width: 100%;
+ box-sizing: border-box;
+ }
+
+ /* 卡片内部光晕 */
+ .card::before {
+ content: '';
+ position: absolute;
+ top: -50%;
+ left: -50%;
+ width: 200%;
+ height: 200%;
+ background: radial-gradient(circle at 30% 30%, rgba(0, 180, 255, 0.1), transparent 70%);
+ opacity: 0.5;
+ z-index: 0;
+ }
+
+ /* 卡片边框动态流光 */
+ .card::after {
+ content: '';
+ position: absolute;
+ top: -2rpx;
+ left: -2rpx;
+ right: -2rpx;
+ bottom: -2rpx;
+ border-radius: 42rpx;
+ background: linear-gradient(135deg, #00B4FF, #7A2EFF, #00B4FF);
+ z-index: -1;
+ opacity: 0;
+ transition: opacity 0.3s;
+ }
+
+ .card:active::after {
+ opacity: 0.6;
+ }
+
+ .card:active {
+ transform: translateY(-6rpx);
+ box-shadow: 0 30rpx 70rpx rgba(0, 180, 255, 0.3);
+ }
+
+ /* 左侧打手信息 */
+ .card-left {
+ display: flex;
+ align-items: center;
+ flex: 2;
+ min-width: 0;
+ z-index: 2;
+ }
+
+ .avatar {
+ width: 90rpx;
+ height: 90rpx;
+ border-radius: 50%;
+ border: 2rpx solid #00B4FF;
+ background: #1A2A3A;
+ margin-right: 20rpx;
+ flex-shrink: 0;
+ box-shadow: 0 0 30rpx rgba(0, 180, 255, 0.3);
+ position: relative;
+ }
+
+ .avatar.small {
+ width: 70rpx;
+ height: 70rpx;
+ }
+
+ .avatar::after {
+ content: '';
+ position: absolute;
+ top: -4rpx;
+ left: -4rpx;
+ right: -4rpx;
+ bottom: -4rpx;
+ border: 1px solid rgba(0, 180, 255, 0.4);
+ border-radius: 50%;
+ animation: pulse 3s infinite;
+ }
+
+ .info {
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ }
+
+ .name {
+ font-size: 28rpx;
+ font-weight: 600;
+ color: #E0F0FF;
+ margin-bottom: 4rpx;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ text-shadow: 0 0 10rpx rgba(0, 180, 255, 0.3);
+ }
+
+ .uid {
+ font-size: 22rpx;
+ color: #A0B8D0;
+ white-space: nowrap;
+ }
+
+ /* 中间机甲箭头 */
+ .card-middle {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ margin: 0 10rpx;
+ flex-shrink: 0;
+ z-index: 2;
+ }
+
+ .tag {
+ font-size: 20rpx;
+ color: #00B4FF;
+ background: rgba(0, 180, 255, 0.1);
+ padding: 4rpx 16rpx;
+ border-radius: 30rpx;
+ border: 1rpx solid rgba(0, 180, 255, 0.3);
+ margin-bottom: 4rpx;
+ white-space: nowrap;
+ letter-spacing: 1rpx;
+ }
+
+ .arrow {
+ font-size: 40rpx;
+ color: #00B4FF;
+ line-height: 1;
+ transform: scaleX(1.5);
+ filter: drop-shadow(0 0 10rpx #00B4FF);
+ position: relative;
+ }
+
+ .arrow::before {
+ content: '';
+ position: absolute;
+ top: 50%;
+ left: -10rpx;
+ width: 20rpx;
+ height: 2rpx;
+ background: #00B4FF;
+ box-shadow: 0 0 10rpx #00B4FF;
+ }
+
+ /* 右侧管事信息 + 金额 */
+ .card-right {
+ display: flex;
+ align-items: center;
+ flex: 3;
+ min-width: 0;
+ justify-content: flex-end;
+ z-index: 2;
+ }
+
+ .amount {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ margin-left: 15rpx;
+ flex-shrink: 0;
+ }
+
+ .num {
+ font-size: 34rpx;
+ font-weight: 800;
+ line-height: 1.2;
+ white-space: nowrap;
+ }
+
+ .num.blue {
+ color: #00B4FF;
+ text-shadow: 0 0 15rpx #00B4FF, 0 0 30rpx #7A2EFF;
+ }
+
+ .time {
+ font-size: 20rpx;
+ color: #7A8FAA;
+ margin-top: 4rpx;
+ }
+
+ /* 卡片底部能量条 */
+ .card-energy {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ height: 2rpx;
+ background: linear-gradient(90deg,
+ transparent 0%,
+ #00B4FF 20%,
+ #7A2EFF 50%,
+ #00B4FF 80%,
+ transparent 100%);
+ background-size: 200% 100%;
+ animation: energyFlow 3s linear infinite;
+ opacity: 0.6;
+ z-index: 3;
+ }
+
+ /* 为每个卡片添加能量条(通过伪元素或额外元素,这里用绝对定位的块,需要在WXML中添加,但我们可以在样式里用::after,但需要修改WXML添加类) */
+ /* 由于不修改WXML,我们使用.card::after已经用于边框,所以另建一个伪元素?不行,只能用额外元素。 */
+ /* 为了不修改WXML,我们利用.card的::before用于光晕,::after用于边框,无法再加。所以建议在WXML中添加一个空view作为能量条,但用户不提供WXML,我们可以暂时不加。但为了效果更好,我们可以利用.card的box-shadow模拟?但动态效果有限。暂时不加,等待用户反馈。 */
+
+ /* ==================== 加载更多 ==================== */
+ .more-state {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 40rpx 0;
+ color: #A0B8D0;
+ font-size: 26rpx;
+ letter-spacing: 2rpx;
+ }
+
+ /* ==================== 没有更多 ==================== */
+ .no-more {
+ text-align: center;
+ padding: 50rpx 0;
+ color: #5A6F8A;
+ font-size: 24rpx;
+ letter-spacing: 2rpx;
+ position: relative;
+ }
+
+ .no-more::before,
+ .no-more::after {
+ content: '';
+ position: absolute;
+ top: 50%;
+ width: 100rpx;
+ height: 2rpx;
+ background: linear-gradient(90deg, transparent, #00B4FF, #7A2EFF, transparent);
+ }
+
+ .no-more::before {
+ left: 20rpx;
+ }
+
+ .no-more::after {
+ right: 20rpx;
+ }
+
+ /* ==================== 错误遮罩 ==================== */
+ .error-mask {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(10, 12, 20, 0.95);
+ backdrop-filter: blur(15px);
+ -webkit-backdrop-filter: blur(15px);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 200;
+ }
+
+ .error-glitch {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: repeating-linear-gradient(
+ 0deg,
+ rgba(255, 0, 0, 0.05) 0px,
+ rgba(0, 255, 0, 0.05) 2px,
+ rgba(0, 0, 255, 0.05) 4px
+ );
+ mix-blend-mode: screen;
+ pointer-events: none;
+ animation: glitch 0.1s infinite;
+ }
+
+ .error-dialog {
+ background: rgba(0, 20, 40, 0.8);
+ border: 2rpx solid #FF4444;
+ border-radius: 50rpx;
+ padding: 60rpx 80rpx;
+ text-align: center;
+ backdrop-filter: blur(10px);
+ -webkit-backdrop-filter: blur(10px);
+ box-shadow: 0 0 100rpx rgba(255, 68, 68, 0.3);
+ z-index: 2;
+ max-width: 600rpx;
+ }
+
+ .error-icon {
+ font-size: 90rpx;
+ color: #FF4444;
+ margin-bottom: 30rpx;
+ filter: drop-shadow(0 0 30rpx #FF4444);
+ }
+
+ .error-msg {
+ font-size: 30rpx;
+ color: #E0E6F0;
+ margin-bottom: 40rpx;
+ line-height: 1.5;
+ }
+
+ .error-btn {
+ padding: 20rpx 70rpx;
+ background: linear-gradient(135deg, #FF4444, #FF8866);
+ border-radius: 60rpx;
+ color: #fff;
+ font-size: 28rpx;
+ font-weight: 600;
+ display: inline-block;
+ position: relative;
+ overflow: hidden;
+ box-shadow: 0 10rpx 40rpx rgba(255, 68, 68, 0.4);
+ }
+
+ .error-btn::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
+ animation: scanMove 2s infinite;
+ }
+
+ .error-btn:active {
+ transform: scale(0.96);
+ }
\ No newline at end of file
diff --git a/miniprogram/services/popupService.js b/miniprogram/services/popupService.js
new file mode 100644
index 0000000..522fe60
--- /dev/null
+++ b/miniprogram/services/popupService.js
@@ -0,0 +1,203 @@
+import request from '../utils/request.js'
+
+const STORAGE_RECORDS = 'popup_records'
+const STORAGE_MUTE_TODAY = 'popup_mute_today'
+
+class PopupService {
+ constructor() {
+ this.records = this.loadRecords()
+ this.muteToday = this.loadMuteToday()
+ this.currentPageKey = null
+ this.isShowing = false // 是否正在展示弹窗队列
+ this.isFetching = false // 是否正在请求弹窗数据
+ }
+
+ loadRecords() {
+ try {
+ return wx.getStorageSync(STORAGE_RECORDS) || {}
+ } catch (e) {
+ return {}
+ }
+ }
+
+ saveRecords() {
+ try {
+ wx.setStorageSync(STORAGE_RECORDS, this.records)
+ } catch (e) {}
+ }
+
+ loadMuteToday() {
+ try {
+ return wx.getStorageSync(STORAGE_MUTE_TODAY) || {}
+ } catch (e) {
+ return {}
+ }
+ }
+
+ saveMuteToday() {
+ try {
+ wx.setStorageSync(STORAGE_MUTE_TODAY, this.muteToday)
+ } catch (e) {}
+ }
+
+ isSamePeriod(date1, date2, interval) {
+ const d1 = new Date(date1)
+ const d2 = new Date(date2)
+ if (interval === 'day') {
+ return d1.toDateString() === d2.toDateString()
+ } else if (interval === 'week') {
+ const getWeek = (d) => {
+ const year = d.getFullYear()
+ const firstDay = new Date(year, 0, 1)
+ const week = Math.ceil((((d - firstDay) / 86400000) + firstDay.getDay() + 1) / 7)
+ return `${year}-${week}`
+ }
+ return getWeek(d1) === getWeek(d2)
+ } else if (interval === 'month') {
+ return d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth()
+ } else if (interval === 'year') {
+ return d1.getFullYear() === d2.getFullYear()
+ }
+ return false
+ }
+
+ shouldShow(popup, pageKey, serverDate, pageIgnoreUserMute) {
+ const popupId = popup.popup_id
+ const strategy = popup.strategy
+ const force_even_muted = popup.force_even_muted || false
+
+ const todayStr = serverDate.split('T')[0]
+ const now = new Date(serverDate)
+
+ const skipMuteCheck = pageIgnoreUserMute || force_even_muted
+ if (!skipMuteCheck) {
+ const mutedDate = this.muteToday[pageKey]
+ if (mutedDate === todayStr) {
+ return false
+ }
+ }
+
+ const key = `${pageKey}_${popupId}`
+ const record = this.records[key]
+ const { type, maxCount = 1, resetInterval = 'day' } = strategy
+
+ if (type === 'once') return !record
+ if (type === 'always') return true
+ if (type === 'daily' || type === 'weekly' || type === 'monthly' || type === 'yearly') {
+ const interval = type
+ if (!record) return true
+ const lastTime = record.lastShowTime
+ if (!lastTime) return true
+ const same = this.isSamePeriod(lastTime, now, interval)
+ if (!same) return true
+ const currentCount = record.showCount || 1
+ return currentCount < maxCount
+ }
+ if (type === 'count') {
+ if (!record) return true
+ const lastTime = record.lastShowTime
+ if (!lastTime) return true
+ const same = this.isSamePeriod(lastTime, now, resetInterval)
+ if (!same) return true
+ const currentCount = record.showCount || 1
+ return currentCount < maxCount
+ }
+ return false
+ }
+
+ recordShow(popupId, pageKey, serverDate) {
+ const key = `${pageKey}_${popupId}`
+ const now = new Date(serverDate)
+ const record = this.records[key] || { showCount: 0, lastShowTime: 0, lastShowDate: '' }
+ record.showCount = (record.showCount || 0) + 1
+ record.lastShowTime = now.getTime()
+ record.lastShowDate = serverDate.split('T')[0]
+ this.records[key] = record
+ this.saveRecords()
+ }
+
+ setMuteToday(pageKey, serverDate) {
+ const todayStr = serverDate.split('T')[0]
+ this.muteToday[pageKey] = todayStr
+ this.saveMuteToday()
+ }
+
+ async checkAndShow(pageInstance, pageKey) {
+ if (this.isShowing || this.isFetching) return
+
+ this.isFetching = true
+ try {
+ const res = await request({
+ url: '/peizhi/tanchuang',
+ method: 'POST',
+ data: { pageKey }
+ })
+ if (res.statusCode !== 200 || res.data.code !== 0) return
+ const { serverTime, popups = [], ignore_user_mute = false } = res.data.data
+ if (!popups.length) return
+
+ const queue = []
+ for (const popup of popups) {
+ if (this.shouldShow(popup, pageKey, serverTime, ignore_user_mute)) {
+ queue.push(popup)
+ }
+ }
+ if (!queue.length) return
+
+ this.isShowing = true
+ this.currentPageKey = pageKey
+ await this.showQueue(pageInstance, queue, 0, serverTime, ignore_user_mute)
+ } catch (error) {
+ console.error('[弹窗] 服务错误:', error)
+ } finally {
+ this.isFetching = false
+ this.isShowing = false
+ }
+ }
+
+ showQueue(pageInstance, queue, index, serverTime, pageIgnoreUserMute) {
+ if (index >= queue.length) return Promise.resolve()
+ const popup = queue[index]
+ const isLast = index === queue.length - 1
+
+ const popupComp = pageInstance.selectComponent('#popupNotice')
+ if (!popupComp) {
+ console.error('[弹窗] 未找到组件 #popupNotice')
+ return Promise.resolve()
+ }
+
+ return new Promise((resolve) => {
+ const showCheckbox = isLast && !pageIgnoreUserMute && !popup.force_even_muted
+ const showData = {
+ popupId: popup.popup_id,
+ title: popup.title || '',
+ duration: popup.duration || 0,
+ images: popup.images || [],
+ showMuteCheckbox: showCheckbox,
+ serverTime: serverTime
+ }
+ const onClose = (result) => {
+ this.recordShow(popup.popup_id, this.currentPageKey, serverTime)
+ if (result.muteToday && showCheckbox) {
+ this.setMuteToday(this.currentPageKey, serverTime)
+ resolve()
+ return
+ }
+ this.showQueue(pageInstance, queue, index + 1, serverTime, pageIgnoreUserMute).then(resolve)
+ }
+ popupComp.show(showData, onClose)
+ })
+ }
+
+ // ========== 🆕 新增:强制重置锁状态 ==========
+ /**
+ * 当页面被隐藏(onHide)时调用,用于释放全局锁,避免影响其他页面的弹窗
+ */
+ reset() {
+ this.isShowing = false;
+ this.isFetching = false;
+ this.currentPageKey = null;
+ }
+}
+
+export default new PopupService()
\ No newline at end of file
diff --git a/miniprogram/sitemap.json b/miniprogram/sitemap.json
new file mode 100644
index 0000000..cd24f35
--- /dev/null
+++ b/miniprogram/sitemap.json
@@ -0,0 +1,7 @@
+{
+ "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
+ "rules": [{
+ "action": "allow",
+ "page": "*"
+ }]
+}
\ No newline at end of file
diff --git a/miniprogram/static/lib/EmojiDecoder.js b/miniprogram/static/lib/EmojiDecoder.js
new file mode 100644
index 0000000..039fb0b
--- /dev/null
+++ b/miniprogram/static/lib/EmojiDecoder.js
@@ -0,0 +1,25 @@
+class EmojiDecoder {
+ emojiMap = null;
+ url = "";
+ patterns = [];
+ metaChars = /[[\]{}()*+?.\\|^$\-,\s]/g;
+ decode = this.decode;
+
+ constructor(url,emojiMap) {
+ this.url = url || '';
+ this.emojiMap = emojiMap || {};
+ for (let i in this.emojiMap) {
+ if (this.emojiMap.hasOwnProperty(i)){
+ this.patterns.push('('+i.replace(this.metaChars, "\\$&")+')');
+ }
+ }
+ }
+
+ decode (text) {
+ return text.replace(new RegExp(this.patterns.join('|'),'g'), (match) => {
+ return typeof this.emojiMap[match] != 'undefined' ? '
' : match;
+ });
+ }
+}
+
+export default EmojiDecoder
\ No newline at end of file
diff --git a/miniprogram/static/lib/goeasy-2.13.24.esm.min.js b/miniprogram/static/lib/goeasy-2.13.24.esm.min.js
new file mode 100644
index 0000000..33035a2
--- /dev/null
+++ b/miniprogram/static/lib/goeasy-2.13.24.esm.min.js
@@ -0,0 +1 @@
+var e,t;!function(e){e.UNIAPP="uniapp",e.REACT_NATIVE="rn",e.COCOS="cocos",e.UNKNOWN="unknown"}(e||(e={}));class s{constructor(){this.framework=null,this.methods={[e.UNIAPP]:this.isUniApp,[e.REACT_NATIVE]:this.isReactNative,[e.COCOS]:this.isCocos};const t=this.methods,s=Object.keys(t);for(const e of s){if((0,t[e])()){this.framework=e;break}}this.framework=this.framework||e.UNKNOWN,this.framework}static currentFramework(){return this.instance||(this.instance=new s),this.instance.framework}isUniApp(){try{return"function"==typeof uni.getSystemInfoSync}catch(e){return!1}}isReactNative(){try{return"object"==typeof navigator&&"ReactNative"===navigator.product}catch(e){return!1}}isTaro(){try{return void 0!==process.env.TARO_ENV}catch(e){return!1}}isCocos(){try{return void 0!==cc.sys.localStorage}catch(e){return!1}}}class i{static init(t){if(s.currentFramework()===e.REACT_NATIVE){if(!t)throw new Error("'reactNativeOptions' is missing when calling GoEasy.getInstance()");this.platform=t.platform,this.asyncStorage=t.asyncStorage}}}!function(e){e.DISCONNECTED="disconnected",e.DISCONNECTING="disconnecting",e.CONNECTING="connecting",e.CONNECTED="connected",e.RECONNECTING="reconnecting",e.RECONNECTED="reconnected",e.EXPIRED_RECONNECTED="reconnected",e.CONNECT_FAILED="connect_failed"}(t||(t={}));const n="2.13.24";var o,r;!function(e){e.PRIVATE="private",e.GROUP="group",e.SYSTEM="system",e.CS="cs"}(o||(o={})),function(e){e.NEW="new",e.SENDING="sending",e.SUCCESS="success",e.FAIL="fail"}(r||(r={}));class a{}class c{constructor(e,t){this.id=e,this.data=t}}let u=new class{isDef(e){return!this.isUndef(e)}isUndef(e){return null==e}isPrimitive(e){return"string"==typeof e||"number"==typeof e||"symbol"==typeof e||"boolean"==typeof e}isObject(e){return null!==e&&"object"==typeof e}isPlainObject(e){return"[object Object]"===Object.prototype.toString.call(e)}isRegExp(e){return"[object RegExp]"===Object.prototype.toString.call(e)}isValidArrayIndex(e){let t=parseFloat(String(e));return t>=0&&Math.floor(t)===t&&isFinite(e)}isString(e){return"string"==typeof e}isNumber(e){return"number"==typeof e}isStringOrNumber(e){return this.isString(e)||this.isNumber(e)}isArray(e){return"[object Array]"===Object.prototype.toString.call(e)}isEmpty(e){return this.isArray(e)?0===e.length:this.isObject(e)?!this.isDef(e):!this.isNumber(e)&&(this.isString(e)?""===e.trim():!this.isDef(e))}isNative(e){return"function"==typeof e&&/native code/.test(e.toString())}isFunction(e){return"function"==typeof e}isBoolean(e){return"boolean"==typeof e}isTrue(e){return!0===e}isFalse(e){return!1===e}isNull(e){return null===e}};var l;!function(e){e.MP_WX="mp-wx",e.MP_WGAME="mp-wgame",e.MP_BYTE="mp-byte",e.MP_BAIDU="mp-baidu",e.MP_ALI="mp-ali",e.BROWSER="browser",e.NODE="node",e.UNI_IOS="uni-ios",e.UNI_ANDROID="uni-android",e.UNI_HARMONY="uni-harmony",e.COCOS_IOS="cocos-ios",e.COCOS_ANDROID="cocos-android",e.RN_IOS="rn-ios",e.RN_ANDROID="rn-android",e.UNKNOWN="unknown"}(l||(l={}));class d{constructor(){this.platform=null,this.methods={[l.BROWSER]:this.isBrowser,[l.MP_WX]:this.isMPWX,[l.MP_WGAME]:this.isMPWeGame,[l.MP_BYTE]:this.isMPByte,[l.MP_BAIDU]:this.isMPBaidu,[l.MP_ALI]:this.isMPAli,[l.NODE]:this.isNode,[l.UNI_IOS]:this.isUniAppIOS,[l.UNI_ANDROID]:this.isUniAppAndroid,[l.UNI_HARMONY]:this.isUniAppHarmony,[l.COCOS_IOS]:this.isCocosIOS,[l.COCOS_ANDROID]:this.isCocosAndroid,[l.RN_IOS]:this.isRNiOS,[l.RN_ANDROID]:this.isRNAndroid};const e=this.methods,t=Object.keys(e);for(const s of t){if((0,e[s])()){this.platform=s;break}}this.platform=this.platform||l.UNKNOWN,this.platform}static currentPlatform(){return this.instance||(this.instance=new d),d.instance.platform}isBrowser(){return"undefined"!=typeof navigator&&"Taro"!==navigator.product&&"ReactNative"!==navigator.product&&"undefined"==typeof GameGlobal&&("undefined"==typeof cc||null!==cc.sys.browserType)&&"undefined"==typeof my&&"undefined"==typeof tt&&"undefined"==typeof swan}isMPWX(){return"object"==typeof wx&&"function"==typeof wx.getSystemInfoSync&&"undefined"==typeof WebSocket&&"undefined"==typeof XMLHttpRequest&&"undefined"==typeof plus&&"undefined"==typeof tt}isMPWeGame(){return"object"==typeof GameGlobal}isMPByte(){return"object"==typeof tt&&"function"==typeof tt.getSystemInfoSync}isMPBaidu(){return"object"==typeof swan&&"function"==typeof swan.getSystemInfoSync}isMPAli(){return"object"==typeof my&&"function"==typeof my.getSystemInfoSync}isNode(){try{return"node"===process.release.name}catch(e){return!1}}isUniAppIOS(){try{return"ios"===uni.getSystemInfoSync().platform&&"app"===uni.getSystemInfoSync().uniPlatform}catch(e){return!1}}isUniAppAndroid(){try{return"android"===uni.getSystemInfoSync().platform&&"app"===uni.getSystemInfoSync().uniPlatform}catch(e){return!1}}isUniAppHarmony(){try{return"harmonyos"===uni.getSystemInfoSync().platform&&"app"===uni.getSystemInfoSync().uniPlatform}catch(e){return!1}}isCocosIOS(){try{return"iOS"===cc.sys.os}catch(e){return!1}}isCocosAndroid(){try{return"Android"===cc.sys.os}catch(e){return!1}}isRNiOS(){try{return"ios"===i.platform.OS}catch(e){return!1}}isRNAndroid(){try{return"android"===i.platform.OS}catch(e){return!1}}}class h{}h.type=l,h.current=d.currentPlatform();class p{onFrontend(e){this.onFrontendCallback=e}}class m extends p{constructor(){super(...arguments),this.runningBackend=!1}startCheck(){"object"==typeof plus&&(plus.globalEvent.addEventListener("resume",(()=>{this.runningBackend=!1,this.runningBackend,this.onFrontendCallback&&this.onFrontendCallback()}),!1),plus.globalEvent.addEventListener("pause",(()=>{this.runningBackend=!0,this.runningBackend}),!1))}isBackend(){return this.runningBackend}support(){const e=d.currentPlatform();return[l.UNI_IOS,l.UNI_ANDROID].includes(e)}}class f extends p{constructor(){super(...arguments),this.runningBackend=!1}startCheck(){wx.onAppShow((()=>{this.runningBackend=!1})),wx.onAppHide((()=>{this.runningBackend=!0}))}isBackend(){return this.runningBackend}support(){return d.currentPlatform()===l.MP_WX}}const g=new class{constructor(){[new m,new f].forEach((e=>{if(e.support())return this.checker=e,void this.checker.startCheck()}))}isBackend(){return this.checker&&this.checker.isBackend()}onFrontend(e){this.checker.onFrontend(e)}};var y,S,E=Object.freeze({__proto__:null,runStatus:g});!function(e){e.authorize="authorize",e.manualDisconnect="manualDisconnect",e.subscribe="subscribe",e.unsubscribe="unsubscribe",e.publish="publish",e.ack="ack",e.historyMessages="historyMessages",e.hereNow="hereNow",e.hereNowByUserIds="hereNowByUserIds",e.PUBSUB_PRESENCE_SUBSCRIBE="PUBSUB_PRESENCE_SUBSCRIBE",e.PUBSUB_PRESENCE_UNSUBSCRIBE="PUBSUB_PRESENCE_UNSUBSCRIBE",e.PUBSUB_PRESENCE_HERENOW="PUBSUB_PRESENCE_HERENOW",e.imLastConversations="imLastConversations",e.markPrivateMessageAsRead="markPrivateMessageAsRead",e.markGroupMessageAsRead="markGroupMessageAsRead",e.imGroupOnlineCount="imGroupOnlineCount",e.imHereNow="imHereNow",e.imGroupHereNow="imGroupHereNow",e.publishIM="publishIM",e.subscribeUserPresence="subscribeUserPresence",e.unsubscribeUserPresence="unsubscribeUserPresence",e.subscribeGroupPresence="subscribeGroupPresence",e.unsubscribeGroupPresence="unsubscribeGroupPresence",e.removeConversation="removeConversation",e.topConversation="topConversation",e.imData="imData",e.subscribeGroups="subscribeGroups",e.unsubscribeGroup="unsubscribeGroup",e.IM_DELETE_MESSAGE="IM_DELETE_MESSAGE",e.IM_HISTORY="IM_HISTORY",e.IM_HISTORY_CHANGE="IM_HISTORY_CHANGE",e.IM_RECALL_MESSAGE="IM_RECALL_MESSAGE",e.IM_MARK_AS_READ="IM_MARK_AS_READ",e.CS_PENDING_CONVERSATION="CS_PENDING_CONVERSATION",e.CS_ACCEPT="CS_ACCEPT",e.CS_END="CS_END",e.CS_TRANSFER="CS_TRANSFER",e.CS_AGENTS="CS_AGENTS",e.CS_CUSTOMER_STATUS="CS_CUSTOMER_STATUS",e.CS_MY_TEAMS="CS_MY_TEAMS",e.CS_ONLINE="CS_ONLINE",e.CS_OFFLINE="CS_OFFLINE",e.CS_LIVE_SESSION="CS_LIVE_SESSION",e.CS_QUIT_LIVE="CS_QUIT_LIVE",e.SET_IOS_BADGE="SET_IOS_BADGE",e.MD_CMD="MD_CMD"}(y||(y={})),function(e){e[e.connect=3e3]="connect",e[e.reconnectionDelayMax=3e3]="reconnectionDelayMax",e[e.commonQuerySingle=2500]="commonQuerySingle",e[e.commonQueryTotal=12e3]="commonQueryTotal",e[e.commonRequestSingle=1700]="commonRequestSingle",e[e.commonRequestTotal=12e3]="commonRequestTotal",e[e.commonInfiniteSingle=1700]="commonInfiniteSingle",e[e.commonInfiniteTotal=864e5]="commonInfiniteTotal"}(S||(S={}));class v{static onSuccess(e,t){u.isFunction(e.onSuccess)&&e.onSuccess(t)}static onFailed(e,t){if(!u.isObject(e)||!u.isFunction(e.onFailed))throw t;e.onFailed(t)}}function b(e,t,s,i){return new(s||(s=Promise))((function(n,o){function r(e){try{c(i.next(e))}catch(e){o(e)}}function a(e){try{c(i.throw(e))}catch(e){o(e)}}function c(e){var t;e.done?n(e.value):(t=e.value,t instanceof s?t:new s((function(e){e(t)}))).then(r,a)}c((i=i.apply(e,t||[])).next())}))}"function"==typeof SuppressedError&&SuppressedError;var C="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function M(e){if(e.__esModule)return e;var t=Object.defineProperty({},"__esModule",{value:!0});return Object.keys(e).forEach((function(s){var i=Object.getOwnPropertyDescriptor(e,s);Object.defineProperty(t,s,i.get?i:{enumerable:!0,get:function(){return e[s]}})})),t}var w={exports:{}};!function(e){function t(e){if(e)return function(e){for(var s in t.prototype)e[s]=t.prototype[s];return e}(e)}w.exports=t,t.prototype.on=t.prototype.addEventListener=function(e,t){return this._callbacks=this._callbacks||{},(this._callbacks["$"+e]=this._callbacks["$"+e]||[]).push(t),this},t.prototype.once=function(e,t){function s(){this.off(e,s),t.apply(this,arguments)}return s.fn=t,this.on(e,s),this},t.prototype.off=t.prototype.removeListener=t.prototype.removeAllListeners=t.prototype.removeEventListener=function(e,t){if(this._callbacks=this._callbacks||{},0==arguments.length)return this._callbacks={},this;var s,i=this._callbacks["$"+e];if(!i)return this;if(1==arguments.length)return delete this._callbacks["$"+e],this;for(var n=0;n>>((3&t)<<3)&255;return A}}for(var k=[],O=0;O<256;++O)k[O]=(O+256).toString(16).substr(1);var P,_,D=function(e,t){var s=t||0,i=k;return[i[e[s++]],i[e[s++]],i[e[s++]],i[e[s++]],"-",i[e[s++]],i[e[s++]],"-",i[e[s++]],i[e[s++]],"-",i[e[s++]],i[e[s++]],"-",i[e[s++]],i[e[s++]],i[e[s++]],i[e[s++]],i[e[s++]],i[e[s++]]].join("")},x=N.exports,U=D,G=0,F=0;var L=function(e,t,s){var i=t&&s||0,n=t||[],o=(e=e||{}).node||P,r=void 0!==e.clockseq?e.clockseq:_;if(null==o||null==r){var a=x();null==o&&(o=P=[1|a[0],a[1],a[2],a[3],a[4],a[5]]),null==r&&(r=_=16383&(a[6]<<8|a[7]))}var c=void 0!==e.msecs?e.msecs:(new Date).getTime(),u=void 0!==e.nsecs?e.nsecs:F+1,l=c-G+(u-F)/1e4;if(l<0&&void 0===e.clockseq&&(r=r+1&16383),(l<0||c>G)&&void 0===e.nsecs&&(u=0),u>=1e4)throw new Error("uuid.v1(): Can't create more than 10M uuids/sec");G=c,F=u,_=r;var d=(1e4*(268435455&(c+=122192928e5))+u)%4294967296;n[i++]=d>>>24&255,n[i++]=d>>>16&255,n[i++]=d>>>8&255,n[i++]=255&d;var h=c/4294967296*1e4&268435455;n[i++]=h>>>8&255,n[i++]=255&h,n[i++]=h>>>24&15|16,n[i++]=h>>>16&255,n[i++]=r>>>8|128,n[i++]=255&r;for(var p=0;p<6;++p)n[i+p]=o[p];return t||U(n)},B=N.exports,q=D;var j=function(e,t,s){var i=t&&s||0;"string"==typeof e&&(t="binary"===e?new Array(16):null,e=null);var n=(e=e||{}).random||(e.rng||B)();if(n[6]=15&n[6]|64,n[8]=63&n[8]|128,t)for(var o=0;o<16;++o)t[i+o]=n[o];return t||q(n)},V=L,H=j,z=H;z.v1=V,z.v4=H;var W=z;class J{support(){return!0}getParams(){return this.params}setData(e){this.active=e.a,this.data=e.d}preConnect(e){}postConnect(){}}class X{static initModule(e){e.support()&&this.modules.set(e.name,e)}static getParams(){return b(this,void 0,void 0,(function*(){let e={};for(const[t,s]of this.modules)e[t]=(yield s.getParams())||null;return e}))}static setDatas(e){Object.keys(e).forEach((t=>{this.modules.get(t).setData(e[t])}))}static preConnect(e){this.modules.forEach(((t,s)=>{t.preConnect(e)}))}static postConnect(){this.modules.forEach(((e,t)=>{e.postConnect()}))}}X.Module=J,X.modules=new Map;const $=I;class Y{constructor(){this.emitter=new $}on(e,t){return this.emitter.on(e,t),this}once(e,t){return this.emitter.once(e,t),this}off(e,t){return this.emitter.off(e,t),this}fire(e,t){return this.emitter.emit(e,t),this}}class Q{constructor(){this.eventDriver=new Y}on(e,t){this.eventDriver.on(e,t)}off(e,t){this.eventDriver.off(e,t)}fire(e,t){this.eventDriver.fire(e,t)}}let K=new class{isDef(e){return!this.isUndef(e)}isUndef(e){return null==e}isPrimitive(e){return"string"==typeof e||"number"==typeof e||"symbol"==typeof e||"boolean"==typeof e}isObject(e){return null!==e&&"object"==typeof e}isPlainObject(e){return"[object Object]"===Object.prototype.toString.call(e)}isRegExp(e){return"[object RegExp]"===Object.prototype.toString.call(e)}isValidArrayIndex(e){let t=parseFloat(String(e));return t>=0&&Math.floor(t)===t&&isFinite(e)}isString(e){return"string"==typeof e}isNumber(e){return"number"==typeof e}isStringOrNumber(e){return this.isString(e)||this.isNumber(e)}isArray(e){return"[object Array]"===Object.prototype.toString.call(e)}isEmpty(e){return this.isArray(e)?0===e.length:this.isObject(e)?!this.isDef(e):!this.isNumber(e)&&(this.isString(e)?""===e.trim():!this.isDef(e))}isNative(e){return"function"==typeof e&&/native code/.test(e.toString())}isFunction(e){return"function"==typeof e}isBoolean(e){return"boolean"==typeof e}isTrue(e){return!0===e}isFalse(e){return!1===e}isNull(e){return null===e}};const Z=I;class ee{constructor(){this.emitter=new Z}on(e,t){if(!K.isString(e))throw Error("event require a string.");if(!K.isFunction(t))throw Error("callback must be a function");this.emitter.on(e,t)}fire(e,t){this.emitter.emit(e,t)}off(e,t){this.emitter.off(e,t)}}class te{static init(e,t,s,i,n,o){this.Socket=e,this.N=t,this.Member=s,this.v=i,this.Platform=n,this.GModules=o}}const se=W;class ie{static get(){return se.v1().replace(/-/g,"")}}var ne,oe,re,ae,ce,ue;(oe=ne||(ne={})).WRITE="WRITE",oe.READ="READ",oe.NONE="NONE";class le{constructor(e){this.permission=ne.NONE,this.singleTimeout=0,this.totalTimeout=0,this.startTime=0,this.complete=!1,this.retried=0,this.unique=!1,this.uuid=ie.get(),this.name=e.name,this.params=e.params,this.permission=e.permission,this.totalTimeout=e.totalTimeout,this.singleTimeout=e.singleTimeout,e.unique&&(this.unique=e.unique),this.success=t=>{this.complete||(this.end(),e.success(t))},this.fail=t=>{this.complete||(this.end(),e.fail(t))}}start(){this.startTime=Date.now(),this.initAutoTimeout()}end(){this.complete=!0,clearTimeout(this.timeoutHandler)}initAutoTimeout(){this.timeoutHandler=setTimeout((()=>{this.complete||this.fail({resultCode:408,content:"Host unreachable or timeout"})}),this.totalTimeout)}}class de extends J{static init(){return this.module=new de,this.module.name=this.GN_MODULE_NAME,this.module}static setRegId(e,t){if(e){this.module.regIdPromise=e;let s=t;this.module.params={v:{npv:s}}}}preConnect(e){this.wxmpId=e.wxmpId}getParams(){return b(this,void 0,void 0,(function*(){if(this.params=this.params||{},this.regIdPromise)try{const e=new Promise(((e,t)=>{setTimeout((()=>t("timeout")),1e3)}));this.params.regId=yield Promise.race([this.regIdPromise,e])}catch(e){console.warn("Failed to register the Manufacturers Push service:"+JSON.stringify(e))}return this.params.wxmpId=this.wxmpId,this.params}))}}de.GN_MODULE_NAME="GN";class he{static init(e){this.allowNotification=!0===e;let t=de.init();X.initModule(t),this.supportAppNotification()&&(this.uniappPlugin=uni.requireNativePlugin("GoEasy-Uniapp"),this.uniappPlugin?(this.regIdPromise=this.askRegId(),this.v=this.uniappPlugin.v(),de.setRegId(this.regIdPromise,this.v)):console.warn("No GoEasy-Uniapp Native Plugin."),this.setClientBadge(0))}static addAssembler(e){this.payloadAssemblers.push(e)}static assemblePayload(e){let t=this.payloadAssemblers.find((t=>t.support(e)));return t?t.assemble(e):e}static createLocalNotification(e,t,s,i,n){g.isBackend()&&(s.g=1,"undefined"!=typeof plus&&(i?(this.uniappPlugin&&this.uniappPlugin.playSound(i),plus.push.createMessage(t,JSON.stringify(s),{title:e,sound:"none"})):plus.push.createMessage(t,JSON.stringify(s),{title:e}),"+1"===n&&this.setClientBadge(he.badge+1)))}static setBadge(e){if(!Number.isInteger(e.badge)||e.badge<0)v.onFailed(e,"badge must be an integer greater than or equal to 0.");else if(he.supportAppNotification())if(this.uniappPlugin&&d.currentPlatform()===l.UNI_IOS){let t=new le({name:y.SET_IOS_BADGE,params:{badge:e.badge},unique:!0,singleTimeout:S.commonRequestSingle,totalTimeout:S.commonRequestTotal,permission:ne.WRITE,success:()=>{v.onSuccess(e),this.setClientBadge(e.badge)},fail:t=>{v.onFailed(e,t.content)}});te.Socket.e(t)}else v.onSuccess(e),this.setClientBadge(e.badge)}static setClientBadge(e){"vivo"!==plus.device.vendor&&(he.badge=e,plus.runtime.setBadgeNumber(e))}static askRegId(){let e=null,t=0;const s=()=>new Promise(((i,n)=>{this.uniappPlugin.regId((e=>{i(e)}),(i=>{if(!(1e6===i.data.code&&t<=10))return clearTimeout(e),n(i);e=setTimeout((()=>{t++,this.regIdPromise=s()}),3500)}))}));return s()}static getRegIdPromise(){return this.regIdPromise}static supportAppNotification(){const e=d.currentPlatform();return this.allowNotification&&(e===l.UNI_ANDROID||e===l.UNI_IOS)}static listenPlusClickNotification(){plus.push.addEventListener("click",(e=>{try{if(e){const t="string"==typeof e.payload?JSON.parse(e.payload):e.payload;if(this.availableIntent(t)){let e=this.assemblePayload(t);plus.push.clear(),this.onClickNotificationCallback(e)}}}catch(e){}}))}static availableIntent(e){return e&&Object.keys(e).length&&e.g&&1===parseInt(e.g)}static getIntentData(){this.uniappPlugin.getIntentData((e=>{if(!this.availableIntent(e))return;let t=this.assemblePayload(e);const s=d.currentPlatform();plus.push.clear(),s===l.UNI_ANDROID&&this.uniappPlugin.clearAll(),this.onClickNotificationCallback(t)}))}static listenClick(){this.listenPlusClickNotification();const e=d.currentPlatform();this.uniappPlugin&&e===l.UNI_ANDROID&&(this.getIntentData(),g.onFrontend((()=>{this.getIntentData()})))}static onClickNotification(e){if(he.supportAppNotification()){if(!u.isFunction(e))throw new Error("The arguments must be a function.");null===this.onClickNotificationCallback?(this.onClickNotificationCallback=e,this.listenClick()):console.warn("The onClickNotification event has been listened on. Please do not listen to it more than once.")}else console.warn("The current environment doesn't support or allowNotification is false.")}}function pe(e){if(null===e||"object"!=typeof e||"isActiveClone"in e)return e;let t=e instanceof Array?[]:{};for(let s in e)"object"==typeof e[s]?t[s]=pe(e[s]):t[s]=e[s];return t}function me(e){return{lang:e?.lang??re?.lang,message:e?.message,abortEarly:e?.abortEarly??re?.abortEarly,abortPipeEarly:e?.abortPipeEarly??re?.abortPipeEarly}}function fe(e){const t=typeof e;return"string"===t?`"${e}"`:"number"===t||"bigint"===t||"boolean"===t?`${e}`:"object"===t||"function"===t?(e&&Object.getPrototypeOf(e)?.constructor?.name)??"null":t}function ge(e,t,s,i,n){const o=n&&"input"in n?n.input:s.value,r=n?.expected??e.expects??null,a=n?.received??fe(o),c={kind:e.kind,type:e.type,input:o,expected:r,received:a,message:`Invalid ${t}: ${r?`Expected ${r} but r`:"R"}eceived ${a}`,requirement:e.requirement,path:n?.path,issues:n?.issues,lang:i.lang,abortEarly:i.abortEarly,abortPipeEarly:i.abortPipeEarly},u="schema"===e.kind,l=n?.message??e.message??(d=e.reference,h=c.lang,ue?.get(d)?.get(h))??(u?function(e){return ce?.get(e)}(c.lang):null)??i.message??function(e){return ae?.get(e)}(c.lang);var d,h;void 0!==l&&(c.message="function"==typeof l?l(c):l),u&&(s.typed=!1),s.issues?s.issues.push(c):s.issues=[c]}function ye(e){return{version:1,vendor:"valibot",validate:t=>e["~run"]({value:t},me())}}function Se(e,t){const s=[...new Set(e)];return s.length>1?`(${s.join(` ${t} `)})`:s[0]??"never"}he.uniappPlugin=null,he.v=null,he.regIdPromise=null,he.onClickNotificationCallback=null,he.payloadAssemblers=new Array;var Ee,ve=class extends Error{constructor(e){super(e[0].message),this.name="ValiError",this.issues=e}};function be(e,t){return{kind:"validation",type:"max_length",reference:be,async:!1,expects:`<=${e}`,requirement:e,message:t,"~run"(e,t){return e.typed&&e.value.length>this.requirement&&ge(this,"length",e,t,{received:`${e.value.length}`}),e}}}function Ce(e){return{kind:"validation",type:"non_empty",reference:Ce,async:!1,expects:"!0",message:e,"~run"(e,t){return e.typed&&0===e.value.length&&ge(this,"length",e,t,{received:"0"}),e}}}function Me(e,t,s){return{kind:"validation",type:"partial_check",reference:Me,async:!1,expects:null,paths:e,requirement:t,message:s,"~run"(t,s){return!t.typed&&!function(e,t){if(e.issues)for(const s of t)for(const t of e.issues){let e=!1;const i=Math.min(s.length,t.path?.length??0);for(let n=0;n(e.typed=!0,e)}}function Re(e,t){return{kind:"schema",type:"array",reference:Re,expects:"Array",async:!1,item:e,message:t,get"~standard"(){return ye(this)},"~run"(e,t){const s=e.value;if(Array.isArray(s)){e.typed=!0,e.value=[];for(let i=0;ie.expects)),"|"),async:!1,options:e,message:t,get"~standard"(){return ye(this)},"~run"(e,t){let s,i,n;for(const o of this.options){const r=o["~run"]({value:e.value},t);if(r.typed){if(!r.issues){s=r;break}i?i.push(r):i=[r]}else n?n.push(r):n=[r]}if(s)return s;if(i){if(1===i.length)return i[0];ge(this,"type",e,t,{issues:Le(i)}),e.typed=!0}else{if(1===n?.length)return n[0];ge(this,"type",e,t,{issues:Le(n)})}return e}}}function qe(...e){return{...e[0],pipe:e,get"~standard"(){return ye(this)},"~run"(t,s){for(const i of e)if("metadata"!==i.kind){if(t.issues&&("schema"===i.kind||"transformation"===i.kind)){t.typed=!1;break}t.issues&&(s.abortEarly||s.abortPipeEarly)||(t=i["~run"](t,s))}return t}}}!function(e){e.TEXT="text",e.IMAGE="image",e.FILE="file",e.VIDEO="video",e.AUDIO="audio"}(Ee||(Ee={}));class je{constructor(e,t,s){this.scene=e,this.id=t,u.isDef(s)&&(this.teamId=s)}toString(){return o.PRIVATE===this.scene||o.GROUP===this.scene?this.scene+"#"+this.id:this.scene+"#"+this.id+"#"+this.teamId}customerId(){if(o.CS===this.scene)return this.id===this.teamId?te.Socket.user().id:this.id}static byScene(e,t,s){return new je(e,t,s)}static byIMMessage(e){let t,s,i=e.scene();if(i===o.PRIVATE){let t=e.senderId,i=e.targetId();s=te.Socket.user().id===t?i:t}else if(i===o.GROUP)s=e.targetId();else{if(i!==o.CS)throw{code:400,content:`scene ${i} not exists`};s=e.targetId(),t=e.teamId}return new je(i,s,t)}static byMessageReadRemoteEvent(e){let t,s=e.scene,i=e.targetId,n=e.markerId,r=e.teamId;return s===o.PRIVATE?t=te.Socket.user().id===n?i:n:s===o.GROUP?t=i:s===o.CS&&(t=i===r?n===te.Socket.user().id?r:n:n===te.Socket.user().id?i:r),new je(s,t,r)}static byIMMessageDeletedEvent(e){let t=e.scene,s=e.deleterId;if(t===o.PRIVATE){let i=te.Socket.user().id===s?e.targetId:s;return new je(t,i)}if(t===o.GROUP)return new je(t,e.targetId)}static byConversationDTO(e){let t=e.lastMessage;return this.byIMMessage(t)}static byConversationId(e,t){let s;if(e===o.PRIVATE){let e=t.split(":",2);s=e[0]===te.Socket.user().id?e[1]:e[0]}else s=t;return new je(e,s)}}class Ve{clearUseLessAttribute(){delete this.buildOptions}isOtherSent(){return this.senderId!==te.Socket.user().id}}class He extends Ve{constructor(){super(...arguments),this.read=!1}scene(){return o.PRIVATE}targetId(){return this.receiverId}}class ze extends Ve{scene(){return o.GROUP}targetId(){return this.groupId}}class We extends Ve{constructor(){super(...arguments),this.accepted=!1}scene(){return o.CS}targetId(){return te.Socket.user().id===this.customerId()?this.teamId:this.customerId()}sendByCustomer(){return this.to===this.teamId}customerId(){return this.sendByCustomer()?this.senderId:this.to}isOtherSent(){return te.Socket.user().id===this.customerId()?this.senderId!==te.Socket.user().id:this.senderId===this.customerId()}}const Je={maxString:e=>qe(Fe(),Ce(),be(e)),nonEmptyString:qe(Fe(),Ce()),object:ke((e=>"object"==typeof e&&null!==e&&!Array.isArray(e)),"must be an object."),userId:qe(Fe(),Ce(),be(60),ke((e=>{if(te.Socket.user().id===e)throw new Error("id can not be the same as your id");return e}))),sceneType:qe(Fe(),Ge([o.PRIVATE,o.GROUP,o.CS]))},Xe={onSuccess:Ue(Oe()),onFailed:Ue(Oe())},$e=xe({appkey:Je.nonEmptyString,host:Je.nonEmptyString,modules:Re(Je.nonEmptyString),allowNotification:Ue(Ae()),forceTLS:Ue(Ae()),supportOldBrowser:Ue(Ae()),reactNativeOptions:Ue(xe({platform:Te(),asyncStorage:Te()})),customProvider:Ue(xe({loadConversations:Oe(),loadHistory:Oe()}))}),Ye=qe(xe(Object.assign({id:Ue(Je.nonEmptyString),data:Ue(Je.object),otp:Ue(Je.nonEmptyString),wxmpId:Ue(xe({appid:Je.nonEmptyString,openid:Je.nonEmptyString})),onProgress:Ue(Oe())},Xe)),Ie(Me([["id"],["data"]],(e=>!X.modules.get("GIM")||e.id&&e.data),"id and data are required for IM module"),["data"])),Qe=xe({id:Je.userId,data:Je.object}),Ke=qe(Be([xe({type:Je.sceneType,id:Je.userId,data:Je.object}),xe({type:Je.sceneType,receivers:Re(Qe)})]),ke((e=>{if(!e.receivers)return!0;const t=e.receivers.map((e=>e.id));if(Array.from(new Set(t)).length200)throw new Error("private message cannot have more than 200 receivers");if(e.type===o.GROUP&&e.receivers.length>3)throw new Error("group message cannot have more than 3 receivers");return!0}))),Ze=Ue(xe({template_id:Fe(),url:Ue(Fe()),miniprogram:Ue(xe({appid:Fe(),pagepath:Fe()})),data:Je.object})),et=qe(xe({channel_id:Ue(Fe()),category:Ue(Fe())}),ke((e=>{if(!e.channel_id&&!e.category)throw{code:400,content:"Must provide at least one of channel_id or category."};return!0}))),st=Ue(xe({title:Je.maxString(32),body:Je.maxString(50),sound:Ue(Fe()),badge:Ue(Fe()),vendorOptions:Ue(xe({huawei:Ue(xe({category:Fe()})),xiaomi:Ue(xe({channel_id:Fe()})),oppo:Ue(et),vivo:Ue(xe({classification:De(),category:Fe()}))}))})),it={image:["gif","jpg","png","jpeg"],video:["avi","mov","rmvb","rm","flv","mp4","3gp","quicktime"],audio:["mp3","ogg","wav","wma","ape","acc","mpeg"]},nt=(e=30,t)=>qe(Te(),Ce(),ke((s=>{var i;if(s.size&&s.size>1024*e*1024)throw new Error(`File size must be <= ${e}MB.`);if(t){const e=s.type||s.tempFile&&s.tempFile.type;if(!e)return!0;const n=null===(i=e.split("/")[1])||void 0===i?void 0:i.toLowerCase();if(n&&!it[t].includes(n))throw new Error(`Only ${it[t].join(",")} is supported for ${t}.`)}return!0})));function ot(e){return xe(Object.assign(Object.assign(Object.assign({},e),{to:Ke,notification:st,wxmpTemplateMsg:Ze,beforeSend:Ue(Oe()),onProgress:Ue(Oe())}),Xe))}const rt=function e(t,s,i){return{kind:"schema",type:"variant",reference:e,expects:"Object",async:!1,key:t,options:s,message:i,get"~standard"(){return ye(this)},"~run"(e,t){const s=e.value;if(s&&"object"==typeof s){let i,n=0,o=this.key,r=[];const a=(e,c)=>{for(const u of e.options){if("variant"===u.type)a(u,new Set(c).add(u.key));else{let e=!0,a=0;for(const i of c){const c=u.entries[i];if(i in s?c["~run"]({typed:!1,value:s[i]},t).issues:"exact_optional"!==c.type&&"optional"!==c.type&&"nullish"!==c.type){e=!1,o!==i&&(n{const t=je.byIMMessage(e[0]);for(let s=1;s{const t=je.byIMMessage(e[0]);for(let s=1;sqe(Fe(),Ce(),ke((t=>{if(!t)throw new Error(`${e} is required.`);return t}))),lt=ut("customId"),dt=ut("id"),ht=xe({userId:Je.nonEmptyString}),pt=xe({groupId:Je.nonEmptyString}),mt=xe({type:Je.sceneType,id:Je.nonEmptyString}),ft=Be([ht,pt,mt]),gt=xe({customer:Qe}),yt=xe({customerId:Je.nonEmptyString,agentId:Je.nonEmptyString}),St=qe(Be([Fe(),De()]),ke((e=>{if(!e&&0!==e)throw new Error("param require string or number.");return!0}))),Et=(e=100)=>qe(Re(St),Ce(),be(e),ke((e=>{if(e.some((e=>0===e.length)))throw new Error("array has empty item.");if(Array.from(new Set(e)).length!e.channel!=!e.channels),"Must provide either channel or channels, but not both or neither"),["channels"]),Ie(Me([["channel"],["presence"]],(e=>{var t;return!(null===(t=e.presence)||void 0===t?void 0:t.enable)||!!te.Socket.user().id}),"Subscription failed. If presence is enable, the id must be specified when calling the connect method"),["presence"])),Ct=xe({userIds:Et(100)}),Mt=xe({groupIds:Et(100)}),wt=xe({channel:St}),It=xe({channel:St,membersLimit:Ue(qe(De(),function e(t,s){return{kind:"validation",type:"max_value",reference:e,async:!1,expects:`<=${t instanceof Date?t.toJSON():fe(t)}`,requirement:t,message:s,"~run"(e,t){return!e.typed||e.value<=this.requirement||ge(this,"value",e,t,{received:e.value instanceof Date?e.value.toJSON():fe(e.value)}),e}}}(100))),onPresence:Oe()}),Nt=xe({groupId:St});function Tt(e,t){var s;try{return function(e,t,s){const i=e["~run"]({value:t},me(s));if(i.issues)throw new ve(i.issues);return i.value}(e,t)}catch(e){if(e instanceof ve){const t=e.issues[0],i=null===(s=t.path)||void 0===s?void 0:s.map((e=>e.key)).filter(Boolean),n=(null==i?void 0:i.join("."))||"value";throw{code:400,content:{string:`TypeError: ${n} must be a string.`,number:`TypeError: ${n} must be a number.`,non_empty:`${n} cannot be empty.`,max_length:`${n} over max length.`,picklist:`${n} has invalid value.`}[t.type]||`${n}: ${t.message}`}}throw e}}const Rt=e=>Tt(lt,e),At=e=>Tt(dt,e),kt=e=>Tt(ht,e),Ot=e=>Tt(pt,e),Pt=e=>Tt(Ct,e),_t=e=>Tt(Mt,e),Dt=e=>Tt(wt,e);class xt{publish(e){Tt(vt,e),e.channel=e.channel.toString();let t={channel:e.channel,content:e.message,nt:e.notification,at:e.accessToken,guid:ie.get(),q:e.qos};e.wxmpTemplateMsg&&(t.wxmpTemplateMsg=pe(e.wxmpTemplateMsg),t.wxmpTemplateMsg.data=JSON.stringify(t.wxmpTemplateMsg.data));let s=new le({name:y.publish,params:t,unique:!0,singleTimeout:S.commonRequestSingle,totalTimeout:S.commonRequestTotal,permission:ne.WRITE,success:function(t){v.onSuccess(e,{code:200,content:"ok"})},fail:function(t){v.onFailed(e,{code:t.resultCode,content:t.content})}});te.Socket.e(s)}}class Ut{constructor(e){if(this.options=e,this.channels=e.channels||[e.channel],!u.isEmpty(e.channel)){let t=e.channel.toString();this.channels=[t]}u.isEmpty(e.channels)||(this.channels=e.channels.toString().split(","))}}var Gt,Ft,Lt;!function(e){e.message="message",e.imMessage="imMessage",e.IM_MSG_SYNC="im:msg:sync",e.IM_MSG_NEW="im:msg:new",e.userPresence="userPresence",e.groupPresence="groupPresence",e.PS_PRESENCE_EVENT="PS_PRESENCE_EVENT",e.IM_MSG_READ="IM_MSG_READ",e.IM_MSG_DELETED="IM_MSG_DELETED",e.IM_MSG_RECALLED="IM_MSG_RECALLED",e.CS_ONLINE_CHANGED="CS_ONLINE_CHANGED"}(Gt||(Gt={})),function(e){e.CONNECTED="CONNECTED",e.RECONNECTED="RECONNECTED",e.DISCONNECTED="DISCONNECTED",e.LOST="LOST",e.EXPIRED_RECONNECTED="EXPIRED_RECONNECTED",e.NEW_MESSAGE="NEW_MESSAGE",e.CONNECTING="CONNECTING"}(Ft||(Ft={}));class Bt{constructor(){this.subscriptions=[],te.Socket.onMessage(Gt.message,this.onNewMessage.bind(this)),te.Socket.on(Ft.EXPIRED_RECONNECTED,this.expiredResubscribe.bind(this)),te.Socket.on(Ft.RECONNECTED,this.resubscribePresenceChannel.bind(this))}expiredResubscribe(){this.subscriptions.forEach((e=>{this.doSubscribe(e,!1)}))}resubscribePresenceChannel(){this.subscriptions.forEach((e=>{let t=e.options;t.presence&&t.presence.enable&&this.doSubscribe(e,!0)}))}onNewMessage(e){if(e.n.indexOf("_presence")>-1)return;e.a&&te.Socket.sendAck("ack",{i:e.i,c:e.n});let t={time:e.t,channel:e.n,content:e.c};this.createNotification(e);let s=this.findSubscriptionByChannel(t.channel);null==s||s.options.onMessage(t)}createNotification(e){const t=te.N.supportAppNotification();if(!u.isObject(e.nt)||!t)return;const s={ch:e.n,ctt:e.c};te.N.createLocalNotification(e.nt.t,e.nt.c,s,e.nt.sound,e.nt.badge)}subscribe(e){Tt(bt,e);let t=new Ut(e);this.doSubscribe(t,!1).then((()=>{this.subscriptions.push(t),v.onSuccess(e,{code:200,content:"ok"})})).catch((t=>{v.onFailed(e,{code:t.resultCode,content:t.content})}))}doSubscribe(e,t){let s=e.options;return new Promise(((i,n)=>{let o=new le({name:y.subscribe,permission:ne.READ,singleTimeout:S.commonRequestSingle,totalTimeout:S.commonRequestTotal,params:{channels:e.channels,accessToken:s.accessToken,presence:s.presence,resubscribe:t},success:()=>{i()},fail:e=>{n(e)}});te.Socket.e(o)}))}unsubscribe(e){Dt(e),e.channel=e.channel.toString();const t=this.findSubscriptionByChannel(e.channel);if(!t)return void e.onFailed({code:400,content:"channel["+e.channel+"] is not subscribed"});let s=new le({name:y.unsubscribe,params:{channel:e.channel,presence:t.options.presence},permission:ne.READ,singleTimeout:S.commonRequestSingle,totalTimeout:S.commonRequestTotal,success:()=>{e.onSuccess({code:200,content:"ok"}),this.removeChannel(e.channel)},fail:function(t){e.onFailed({code:t.resultCode,content:t.content})}});te.Socket.e(s)}removeChannel(e){for(let t=this.subscriptions.length-1;t>=0;t--){const s=this.subscriptions[t].channels,i=s.indexOf(e);if(i>-1){s.splice(i,1),0===s.length&&this.subscriptions.splice(t,1);break}}}findSubscriptionByChannel(e){let t=!1,s=null;for(let i=this.subscriptions.length-1;i>=0;i--){let n=this.subscriptions[i].channels;for(let o=0;o{v.onSuccess(e,{code:t.resultCode||t.code||200,content:t.content})},fail:t=>{v.onFailed(e,{code:t.resultCode||t.code,content:t.content})}});te.Socket.e(t)}}class jt{constructor(){this.channelPresenceMap=new Map,this.onPresenceEvent=e=>{let t=this.channelPresenceMap.get(e.channel);null==t||t.onPresence(e)},this.expireAllChannelPresences=()=>{this.channelPresenceMap.forEach(((e,t)=>{e.expire()}))},this.resubscribe=()=>{this.channelPresenceMap.forEach(((e,t)=>{e.subscribed()&&e.doSubscribe()}))},te.Socket.on(Ft.LOST,this.expireAllChannelPresences),te.Socket.on(Ft.RECONNECTED,this.resubscribe),te.Socket.onMessage(Gt.PS_PRESENCE_EVENT,this.onPresenceEvent)}hereNow(e){Dt(e);let t,s=this.channelPresenceMap.get(e.channel.toString());s&&s.queryPromise?(e.limit&&e.limit>s.membersLimit&&s.doQuery(e.limit),t=s.queryPromise):t=Ht(e.channel,e.limit),t.then((t=>{e.onSuccess(t)})).catch((t=>{e.onFailed(t)}))}subscribe(e){Tt(It,e);let t=e.channel.toString(),s=this.channelPresenceMap.get(t);s||(s=new Vt(t),this.channelPresenceMap.set(t,s)),s.subscribe(e)}unsubscribe(e){Dt(e);let t=e.channel.toString();if(this.channelPresenceMap.get(t)){let s=new le({name:y.PUBSUB_PRESENCE_UNSUBSCRIBE,permission:ne.READ,singleTimeout:S.commonRequestSingle,totalTimeout:S.commonRequestTotal,params:{channel:t},success:s=>{this.channelPresenceMap.delete(t),e.onSuccess()},fail:t=>{e.onFailed(t)}});te.Socket.e(s)}else e.onSuccess()}}class Vt{constructor(e){this.membersLimit=10,this.queried=!1,this.channel=e}onPresence(e){return b(this,void 0,void 0,(function*(){this.queried?this.update(e):yield this.queryPromise,this.on({channel:this.channel,action:e.action,member:e.member,time:e.time,amount:this.amount,members:this.membersByLimit()})}))}membersByLimit(){return this.members.slice(0,this.membersLimit)}subscribe(e){return b(this,void 0,void 0,(function*(){if(e.membersLimit&&(this.membersLimit=Math.min(e.membersLimit,300)),!this.subscribed())try{yield this.doSubscribe()}catch(t){return void e.onFailed(t)}this.on=e.onPresence,e.onSuccess()}))}doSubscribe(){return this.doQuery(this.membersLimit),new Promise(((e,t)=>{let s=new le({name:y.PUBSUB_PRESENCE_SUBSCRIBE,permission:ne.READ,params:{channel:this.channel},singleTimeout:S.commonRequestSingle,totalTimeout:S.commonRequestTotal,success:t=>{e()},fail:e=>{t(e)}});te.Socket.e(s)}))}doQuery(e){this.queryPromise=Ht(this.channel,e),this.queryPromise.then((e=>{this.members=e.content.members,this.amount=e.content.amount,this.queried=!0})).catch((e=>{throw e}))}update(e){if(["join","back"].includes(e.action))this.members.unshift(e.member);else if(["leave","timeout"].includes(e.action)){let t=this.members.findIndex((t=>t.id===e.member.id));t>-1&&this.members.splice(t,1)}this.amount=e.amount}expire(){this.queried=!1,this.queryPromise=null}subscribed(){return void 0!==this.on}}function Ht(e,t){return t||(t=10),new Promise(((s,i)=>{let n={channel:e,limit:t},o=new le({name:y.PUBSUB_PRESENCE_HERENOW,permission:ne.READ,params:n,singleTimeout:S.commonQuerySingle,totalTimeout:S.commonQueryTotal,success:e=>{s(e)},fail:e=>{i(e)}});te.Socket.e(o)}))}class zt{constructor(){this.publisher=new xt,this.subscriber=new Bt,this.presence2=new jt,this.histories=new qt}static init(){this.instance=new zt}publish(e){this.publisher.publish(e)}subscribe(e){this.subscriber.subscribe(e)}unsubscribe(e){this.subscriber.unsubscribe(e)}subscribePresence(e){this.presence2.subscribe(e)}unsubscribePresence(e){this.presence2.unsubscribe(e)}history(e){this.histories.get(e)}hereNow(e){this.presence2.hereNow(e)}}class Wt extends J{static init(){return this.module=new Wt,this.module.name=this.GWS_MODULE_NAME,this.initGN(),this.module}static initGN(){he.addAssembler(new class{assemble(e){return{channel:e.ch,content:e.ctt}}support(e){return!!e.ch}})}postConnect(){zt.init()}static check(){if(!this.module)throw{code:400,content:"PubSub not initialized. Please include 'PUBSUB' in the 'modules' during GoEasy initialization."}}}Wt.GWS_MODULE_NAME="GWS";class Jt{constructor(){}}!function(e){e.aliYun="ALI",e.qiNiu="QN",e.tencent="TX",e.s3="S3"}(Lt||(Lt={}));let Xt=new class extends Jt{constructor(){super()}upload(e,t){try{return e.storageLocation===Lt.s3?this.uploadToS3(e,t):(delete e.parameters.fileRes,new Promise(((s,i)=>{uni.uploadFile({url:e.host,filePath:this.getTempFilePath(e),name:"file",formData:e.parameters,success(e){200===e.statusCode?s():i({code:e.statusCode,content:e.errMsg})},fail(e){i({code:500,content:e.errMsg})}}).onProgressUpdate((e=>{t&&t(e)}))})))}catch(e){return new Promise(((t,s)=>{s({code:500,content:e})}))}}getTempFilePath(e){let t=e.file;return t.tempFilePath||t.fullPath||t.path}uploadToS3(e,t){return new Promise(((t,s)=>b(this,void 0,void 0,(function*(){const i=yield this.fileToArrayBuffer(e);uni.request({url:e.url,method:"PUT",header:e.headers,data:i,success(i){200===i.statusCode?(e.storageLocation===Lt.s3&&(e.url=e.url.split("?")[0]),t()):s({code:i.statusCode,content:i.errMsg})},fail(e){s({code:500,content:e.errMsg})}})}))))}fileToArrayBuffer(e){return new Promise(((t,s)=>{const i=this.getTempFilePath(e);if("undefined"!=typeof plus)plus.io.resolveLocalFileSystemURL(i,(e=>{e.file((e=>{let i=new plus.io.FileReader;i.onloadend=e=>{const s=uni.base64ToArrayBuffer(e.target.result.split(",")[1]);t(s)},i.onerror=function(e){s(e)},i.readAsDataURL(e)}))}),(e=>{s("Resolve file URL failed: "+e.message)}));else if("undefined"!=typeof FileReader){const i=new FileReader;i.onload=()=>{t(i.result)},i.onerror=e=>{s(e)},i.readAsArrayBuffer(e.file)}else if("object"==typeof wx&&wx.canIUse("getFileSystemManager"))try{const e=wx.getFileSystemManager().readFileSync(i);t(e)}catch(e){s(e)}}))}};const $t=new class extends Jt{upload(e,t){try{return e.storageLocation===Lt.s3?this.uploadToS3(e,t):new Promise(((s,i)=>{wx.uploadFile({url:e.host,filePath:this.getTempFilePath(e),name:"file",formData:e.parameters,success(e){200===e.statusCode?s():i({code:e.statusCode,content:e.errMsg})},fail(e){i({code:500,content:e.errMsg})}}).onProgressUpdate((e=>{t&&t(e)}))}))}catch(e){return new Promise(((t,s)=>{s({code:500,content:e})}))}}getTempFilePath(e){let t=e.file||e.fileRes;return t.path||t.tempFilePath}uploadToS3(e,t){return new Promise(((t,s)=>b(this,void 0,void 0,(function*(){const i=yield this.fileToArrayBuffer(this.getTempFilePath(e));wx.request({url:e.url,method:"PUT",header:e.headers,data:i,success(i){200===i.statusCode?(e.storageLocation===Lt.s3&&(e.url=e.url.split("?")[0]),t()):s({code:i.statusCode,content:i.errMsg})},fail(e){s({code:500,content:e.errMsg})}})}))))}fileToArrayBuffer(e){return new Promise(((t,s)=>{try{const s=wx.getFileSystemManager();t(s.readFileSync(e))}catch(e){s(e)}}))}};const Yt=new class extends Jt{constructor(){super()}upload(e,t){return new Promise(((s,i)=>{try{const n=new XMLHttpRequest,o=e.storageLocation===Lt.s3,r=o?"PUT":"POST",a=o?e.url:e.host;n.open(r,a,!0);for(let t in e.headers)n.setRequestHeader(t,e.headers[t]);n.upload.onprogress=function(e){t&&t(e)},n.upload.onloadstart=function(e){t&&t(e)},n.upload.onloadend=function(e){t&&t(e)};let c=new FormData;for(let t in e.parameters)"fileRes"==t?c.append("file",e.parameters[t]):c.append(t,e.parameters[t]);const u=o?e.file:c;n.send(u),n.onreadystatechange=function(){4==n.readyState&&(n.status>=200&&n.status<300||304==n.status?(o&&(e.url=e.url.split("?")[0]),s()):i({code:n.status,content:n.responseText}))}}catch(e){i({code:500,content:e})}}))}fileToArrayBuffer(e){return new Promise(((t,s)=>{const i=new FileReader;i.onload=()=>{t(i.result)},i.onerror=e=>{s(e)},i.readAsArrayBuffer(e)}))}};const Qt=new class extends Jt{upload(e,t){try{return e.storageLocation===Lt.s3?this.uploadToS3(e,t):(delete e.parameters.fileRes,new Promise(((s,i)=>{my.uploadFile({url:e.host,filePath:this.getTempFilePath(e),fileType:"image",name:"file",formData:e.parameters,success(e){200===e.statusCode?s():i({code:e.statusCode,content:e.errMsg})},fail(e){i({code:500,content:e.errMsg})}}).onProgressUpdate((e=>{t&&t(e)}))})))}catch(e){return new Promise(((t,s)=>{s({code:500,content:e})}))}}getTempFilePath(e){let t=e.file;return t.path||t.tempFilePath}uploadToS3(e,t){return new Promise(((t,s)=>b(this,void 0,void 0,(function*(){const i=yield this.fileToArrayBuffer(this.getTempFilePath(e));my.request({url:e.url,method:"PUT",header:e.headers,data:i,success(i){200===i.statusCode?(e.storageLocation===Lt.s3&&(e.url=e.url.split("?")[0]),t()):s({code:i.statusCode,content:i.errMsg})},fail(e){s({code:500,content:e.errMsg})}})}))))}fileToArrayBuffer(e){return new Promise(((t,s)=>{my.getFileSystemManager().readFile({filePath:e,success:e=>{t(e.data)},fail(e){s(e)}})}))}};const Kt=new class extends Jt{upload(e,t){try{return e.storageLocation===Lt.s3?this.uploadToS3(e,t):(delete e.parameters.fileRes,new Promise(((s,i)=>{swan.uploadFile({url:e.host,filePath:this.getTempFilePath(e),name:"file",formData:e.parameters,success(e){200===e.statusCode?s():i({code:e.statusCode,content:e.errMsg})},fail(e){i({code:500,content:e.errMsg})}}).onProgressUpdate((e=>{t&&t(e)}))})))}catch(e){return new Promise(((t,s)=>{s({code:500,content:e})}))}}getTempFilePath(e){let t=e.file;return t.path||t.tempFilePath}uploadToS3(e,t){return new Promise(((t,s)=>b(this,void 0,void 0,(function*(){const i=yield this.fileToArrayBuffer(this.getTempFilePath(e));swan.request({url:e.url,method:"PUT",header:e.headers,data:i,success(i){200===i.statusCode?(e.storageLocation===Lt.s3&&(e.url=e.url.split("?")[0]),t()):s({code:i.statusCode,content:i.errMsg})},fail(e){s({code:500,content:e.errMsg})}})}))))}fileToArrayBuffer(e){return new Promise(((t,s)=>{swan.getFileSystemManager().readFile({filePath:e,success:e=>{t(e.data)},fail:e=>{s(e)}})}))}};class Zt{constructor(e,t,s){this.storageLocation=e,this.url=t,this.name=s}}const es=new class{constructor(){this.uploader={[e.UNIAPP]:Xt,[l.MP_WX]:$t,[l.BROWSER]:Yt,[l.MP_ALI]:Qt,[l.MP_BAIDU]:Kt,[e.REACT_NATIVE]:Yt,[l.MP_BYTE]:$t}}upload(t,i){return b(this,void 0,void 0,(function*(){const n=s.currentFramework(),o=d.currentPlatform(),r=n===e.UNKNOWN?o:n;let a=this.uploader[r];return yield a.upload(t,i),new Zt(t.storageLocation,t.url,t.newFileName)}))}};class ts{resolve(e){return new Promise(((t,s)=>{let i=new le({name:"uploadToken",params:{filename:e},permission:ne.WRITE,singleTimeout:S.commonRequestSingle,totalTimeout:S.commonRequestTotal,fail(e){s(e.content)},success(e){200===e.code?t(e.content):s(e.content)}});te.Socket.e(i)}))}}class ss{constructor(e,t,s,i,n,o,r){this.storageLocation=e,this.host=t,this.headers=s,this.parameters=i,this.file=n,this.newFileName=o,this.url=r}}class is{constructor(){}newFileName(e){return e&&e.newFilename||""}}let ns=new class extends is{constructor(){super()}url(e){return e.host+"/"+e.dir+"/"+this.newFileName(e)}build(e,t,s){let i,n=this.newFileName(e);return i={key:e.dir+"/"+n,OSSAccessKeyId:e.accessKeyId,policy:e.policy,signature:e.signature,success_action_status:"200",fileRes:t},s&&(i={key:e.dir+"/"+n,OSSAccessKeyId:e.accessKeyId,policy:e.policy,signature:e.signature,success_action_status:"200","Content-Disposition":"attachment;filename="+t.name,fileRes:t}),new ss(Lt.aliYun,e.host,null,i,t,n,this.url(e))}};let os=new class extends is{constructor(){super()}url(e){return e.host+"/"+e.key}build(e,t,s){let i={"q-sign-algorithm":e.qSignAlgorithm,"q-ak":e.qAk,"q-key-time":e.qKeyTime,"q-signature":e.qSignature,policy:e.policy,"x-cos-security-token":e.xCosSecurityToken,success_action_status:"200",key:e.key,fileRes:t};return s&&(i={"q-sign-algorithm":e.qSignAlgorithm,"q-ak":e.qAk,"q-key-time":e.qKeyTime,"q-signature":e.qSignature,policy:e.policy,"x-cos-security-token":e.xCosSecurityToken,success_action_status:"200",key:e.key,"Content-Disposition":`attachment;filename=${t.name}`,fileRes:t}),new ss(Lt.tencent,e.host,null,i,t,e.key,this.url(e))}};let rs=new class extends is{constructor(){super()}url(e){return e.uploadUrl}build(e,t,s){const i={file:t};return new ss(Lt.s3,null,{"Content-Type":"application/octet-stream"},i,t,t.name,this.url(e))}};class as{constructor(){this.uploadTokenResolver=new ts}builder(e){if(e===Lt.aliYun)return ns;if(e===Lt.tencent)return os;if(e===Lt.s3)return rs;throw new Error("Only Ali OSS and Tencent COS are supported, unknown storage location:"+e)}build(e,t,s){return b(this,void 0,void 0,(function*(){try{let i=yield this.uploadTokenResolver.resolve(t),n=this.builder(i.vendor).build(i,e,s);return Promise.resolve(n)}catch(e){return Promise.reject(e)}}))}}class cs{constructor(){this.requestBuilder=new as,this.fileUploader=es}upload(e,t,s,i){return b(this,void 0,void 0,(function*(){try{let n=yield this.requestBuilder.build(e,t,i);return this.fileUploader.upload(n,s)}catch(e){return Promise.reject(e)}}))}}var us,ls,ds;!function(e){e.MESSAGE_SENDING="IM_INTERNAL_MESSAGE_SENDING",e.MESSAGE_SEND_SUCCESS="IM_INTERNAL_MESSAGE_SEND_SUCCESS",e.MESSAGE_SEND_FAILED="IM_INTERNAL_MESSAGE_SEND_FAILED",e.MESSAGE_RECEIVED="IM_INTERNAL_MESSAGE_RECEIVED",e.MESSAGE_RECALLED="IM_INTERNAL_MESSAGE_RECALLED",e.MAX_MESSAGE_CHANGED="IM_INTERNAL_MAX_MESSAGE_CHANGED",e.MAX_MESSAGE_DELETED="IM_INTERNAL_MAX_MESSAGE_DELETED",e.UNREAD_AMOUNT_CHANGED="IM_INTERNAL_UNREAD_MESSAGE_CHANGED",e.CS_ONLINE_SUCCESS="CS_ONLINE_SUCCESS",e.CS_OFFLINE_SUCCESS="CS_OFFLINE_SUCCESS",e.CS_ACCEPTED="CS_ACCEPTED",e.CS_ENDED="CS_ENDED",e.CS_TRANSFER="CS_TRANSFER",e.CS_AGENT_MESSAGE_RECEIVED="CS_AGENT_MESSAGE_RECEIVED"}(us||(us={}));class hs{}class ps extends hs{constructor(){super(...arguments),this.goEasyUploader=new cs}improve(e){let t=e.message;return new Promise(((e,s)=>{let i,n,o=t.buildOptions.createOptions;t.type===Ee.VIDEO?(n=t.payload,i=n.video.name):(n=t.payload,i=n.name),this.goEasyUploader.upload(o.file,i,o.onProgress,t.type===Ee.FILE).then((s=>{s.storageLocation!==Lt.s3&&(s.url=encodeURI(s.url)),this.setPayload(s,t),e()})).catch((e=>{s(e)}))}))}setPayload(e,t){t.payload.url=e.url}}class ms extends ps{setPayload(e,t){let s,i=t.payload;switch(i.video.url=e.url,i.video.name=e.name,i.thumbnail.height>200&&(i.thumbnail.height=200,i.thumbnail.width=200*i.video.width/i.video.height),e.storageLocation){case Lt.aliYun:s="?x-oss-process=video/snapshot,t_0000,f_jpg,h_"+i.thumbnail.height+",m_fast,ar_auto";break;case Lt.tencent:s="?ci-process=snapshot&time=1&format=jpg&height="+i.thumbnail.height;break;case Lt.s3:s="?process=video&height="+i.thumbnail.height;break;default:throw new Error("Only Ali OSS and Tencent COS are supported, unknown storage location:"+e.storageLocation)}i.thumbnail.url=e.url+s}}class fs extends ps{setPayload(e,t){let s,i=t.payload;i.url=e.url;const n=i.height>200?200:i.height;switch(e.storageLocation){case Lt.aliYun:s="?x-oss-process=image/resize,m_lfit,h_"+n;break;case Lt.tencent:s=`?imageMogr2/thumbnail/x${n}`;break;case Lt.s3:s=`?process=image&height=${n}`;break;default:throw new Error("Only Ali OSS and Tencent COS are supported, unknown storage location:"+e.storageLocation)}i.thumbnail=e.url+s}}class gs{constructor(){this.improvers={[Ee.FILE]:new ps,[Ee.AUDIO]:new ps,[Ee.IMAGE]:new fs,[Ee.VIDEO]:new ms}}improve(e){let t=this.improvers[e.message.type];if(t)try{return t.improve(e)}catch(e){return Promise.reject(e)}return Promise.resolve()}}class ys extends Q{static init(){this.i=new ys}}class Ss{constructor(e,t,s,i,n){this.validate(e),this.mt=e.type,"receivers"in t?this.receivers=t.receivers:(this.to=t.id,this.d=JSON.stringify(t.data)),this.p=JSON.stringify(e.payload),s&&(this.nt=s),n&&(this.at=n),i&&(this.wxmpTemplateMsg=pe(i),this.wxmpTemplateMsg.data=JSON.stringify(this.wxmpTemplateMsg.data));let r=t.type;if(this.t=r,r===o.CS){let t=e;this.tid=t.teamId}this.guid=e.messageId}validate(e){if(e.type===Ee.TEXT){if(JSON.stringify(e.payload).length>3072)throw Error("message-length limit 3kb")}}}class Es{constructor(){this.payloadImprover=new gs}send(e){this.validate(e);let t=e.message,s=e.accessToken,i=t.buildOptions,n=i.createOptions,a=n.notification,c=n.wxmpTemplateMsg,u=n.to;t.status=r.SENDING;let l=i.complete,d=this.payloadImprover.improve(e);Promise.all([l,d]).then((()=>b(this,void 0,void 0,(function*(){if(n.beforeSend){const e=JSON.parse(JSON.stringify(t));yield n.beforeSend(e)}if("receivers"in n.to){const i=new Array;u.receivers.forEach((e=>{const s=Object.assign(Object.create(Object.getPrototypeOf(t)),t);u.type===o.PRIVATE?s.receiverId=e.id:u.type===o.GROUP&&(s.groupId=e.id),i.push(s);const n={message:s,receiver:e.data};ys.i.fire(us.MESSAGE_SENDING,n)})),this.doSend(u,a,c,s,e,i,t)}else{const i={message:t,receiver:u.data};ys.i.fire(us.MESSAGE_SENDING,i),this.doSend(u,a,c,s,e,[t])}})))).catch((s=>{t.status=r.FAIL,v.onFailed(e,{code:s&&s.code||400,content:s&&s.content||s})}))}doSend(e,t,s,i,n,a,c){let u=new Ss(a[0],e,t,s,i),l=new le({name:y.publishIM,params:u,unique:!0,permission:ne.WRITE,singleTimeout:S.commonRequestSingle,totalTimeout:S.commonRequestTotal,fail:e=>{a.forEach((e=>{e.status=r.FAIL,ys.i.fire(us.MESSAGE_SEND_FAILED,e)})),c&&(c.status=r.FAIL),v.onFailed(n,{code:e.resultCode,content:e.content})},success:e=>{a.forEach((t=>{if(t.status=r.SUCCESS,t.timestamp=e.content.timestamp,t.messageId=JSON.stringify(e.content.id),t.scene()===o.CS){let s=t;s.customerId()!==te.Socket.user().id&&(s.sessionId=e.content.sessionId)}t.clearUseLessAttribute(),ys.i.fire(us.MESSAGE_SEND_SUCCESS,t)})),c&&(c.status=r.SUCCESS,c.timestamp=e.content.timestamp,c.messageId=JSON.stringify(e.content.id),c.clearUseLessAttribute()),v.onSuccess(n)}});te.Socket.e(l)}validate(e){let t=e.message;if(!(t instanceof Ve))throw new Error("it is invalid message");if(t.status!==r.NEW)throw new Error("Please create a new message, a message can only be sent once")}}class vs{insert(e,t){let s=this.binarySearch(e,t);if(s>=0)e.splice(s,1,t);else{let i=-s-1;e.splice(i,0,t)}}binarySearch(e,t){let s=0,i=e.length-1;for(;s<=i;){let n=i+s>>1,o=this.compare(t,e[n]);if(o>0)s=n+1;else{if(!(o<0))return n;i=n-1}}return-s-1}}class bs{constructor(e){this.messages=new Array,this.allLoaded=!1,this.target=e}all(){return this.messages}sliceOverLengthMessages(){this.messages.length>bs.CACHE_MAX_LENGTH&&(this.messages=this.messages.slice(-bs.CACHE_MAX_LENGTH),!0===this.allLoaded&&(this.allLoaded=!1))}getMaxMessage(){return this.messages[this.messages.length-1]}loadLocalMessages(e,t){let s=[],i=this.messages.length;if(t){if(i>0){let n=this.messages[0].timestamp,o=this.messages[i-1].timestamp;if(t>=n&&t<=o)for(let n=i-1;n>=0;n--){let i=this.messages[n];if(i.timestamp0&&s.timestamp===e.lastTimestamp)&&(t.forEach((e=>{bs.sortedInserter.insert(this.messages,e)})),t.lengthe===t.timestamp))}findMessagesByTimes(e){let t=[];return e.forEach((e=>{let s=this.findMessageByTime(e);u.isDef(s)&&t.push(s)})),t}existsMessage(e){return this.findMessageIndexById(e)>-1}findMessageIndexById(e){return this.messages.findIndex((t=>e===t.messageId))}deleteMessage(e){let t=this.findMessageIndexById(e);t>=0&&this.messages.splice(t,1)}recallMessage(e){return e.times.map((e=>this.findMessageByTime(e))).filter((e=>u.isDef(e))).map((t=>(t.recalled=!0,t.recaller=e.recaller,t)))}isEmpty(){return 0===this.messages.length}deleteMessages(e){e.forEach((e=>{this.deleteMessage(e.messageId)}))}saveMessage(e){bs.sortedInserter.insert(this.messages,e),this.deleteGroupConversationMessage(),this.sliceOverLengthMessages()}deleteGroupConversationMessage(){this.target.scene===o.GROUP&&this.messages.length>=2&&1===this.messages[0].x&&this.messages.shift()}maxSuccessMessageTime(){for(let e=this.messages.length-1;e>=0;e--)if(this.messages[e].status===r.SUCCESS)return this.messages[e].timestamp;return 0}minTime(){return this.isEmpty()?0:this.messages[0].timestamp}correctPosition(e){this.deleteMessage(e.messageId),this.saveMessage(e)}}bs.CACHE_MAX_LENGTH=200,bs.sortedInserter=new class extends vs{compare(e,t){let s=e.timestamp-t.timestamp;return s>0?1:0===s?0:-1}};class Cs{constructor(e){this.times=new Array;let t=e[0],s=je.byIMMessage(t);this.scene=s.scene,this.targetId=s.id,e.forEach((e=>{e.status===r.SUCCESS&&this.times.push(e.timestamp)})),this.times.sort(((e,t)=>e{let i=new le({name:y.IM_DELETE_MESSAGE,params:t,permission:ne.WRITE,singleTimeout:S.commonQuerySingle,totalTimeout:S.commonQueryTotal,success:t=>{200===t.code?e(t):s(t)},fail:e=>{s(e)}});te.Socket.e(i)}))}}class ws{constructor(e,t,s,i,n){this.scene=e,this.id=t,this.after=s,this.min=i,this.teamId=n}}class Is{constructor(e,t,s,i){this.id=e,this.scene=t,this.lastTimestamp=s,this.teamId=i}}!function(e){e.ACCEPT="CS_ACCEPT",e.END="CS_END",e.TRANSFER="CS_TRANSFER"}(ls||(ls={}));class Ns{build(e){let t,s=e.t;s===o.PRIVATE?(t=new He,t.read=!1,e.r?t.receiverId=e.r:t.receiverId=te.Socket.user().id):s===o.GROUP?(t=new ze,t.groupId=e.r,t.senderData=e.d?JSON.parse(e.d):{}):s===o.CS&&(t=new We,t.to=e.r,t.teamId=e.tid,t.senderData=e.d?JSON.parse(e.d):{},t.accepted=e.accepted,t.customerId()!==te.Socket.user().id&&(t.sessionId=e.sessionId)),t.senderId=e.s,t.messageId=e.i,t.timestamp=e.ts,t.type=e.mt;let i=e.p;if(u.isDef(i))if(s===o.CS&&t.type===ls.TRANSFER){let e=JSON.parse(i);e.transferTo.data=JSON.parse(e.transferTo.data),t.payload=e}else t.payload=JSON.parse(i);let n=e.rc;return u.isDef(n)&&!0===n?(t.recalled=n,t.recaller=e.recaller):t.recalled=!1,t.status=r.SUCCESS,t}}class Ts{constructor(){this.builder=new Ns}sync(e,t,s,i,n){let o=new ws(e,t,s,i,n);return new Promise(((e,t)=>{let s=new le({name:y.IM_HISTORY_CHANGE,params:o,permission:ne.READ,singleTimeout:S.commonQuerySingle,totalTimeout:S.commonQueryTotal,fail:e=>{t(e)},success:t=>{let s=t.content;e(s)}});te.Socket.e(s)}))}loadServerMessages(e,t){return new Promise(((s,i)=>{let n=new le({name:y.IM_HISTORY,params:t,permission:ne.READ,singleTimeout:S.commonQuerySingle,totalTimeout:S.commonQueryTotal,fail:e=>{s({messages:[],userOffsets:[]})},success:t=>{let i=t.content;i.messages=this.convertServerMessages(e,i.messages),s(i)}});te.Socket.e(n)}))}convertServerMessages(e,t){let s=[],i=e.scene,n=e.id;return t.forEach((t=>{if(t.t=i,o.PRIVATE===i)t.r=t.s===te.Socket.user().id?n:te.Socket.user().id;else if(o.GROUP===i)t.r=n;else if(o.CS===i){let s=e.customerId(),i=e.teamId;s===te.Socket.user().id?t.r=i:t.r=s}let r=this.builder.build(t);s.push(r)})),s}updateServerOffsets(e,t){return b(this,void 0,void 0,(function*(){let s=new Is(t.id,t.scene,e,t.teamId);return new Promise(((e,t)=>{let i=new le({name:y.IM_MARK_AS_READ,params:s,permission:ne.WRITE,singleTimeout:S.commonRequestSingle,totalTimeout:S.commonRequestTotal,success:t=>{e(t)},fail:e=>{t(e)}});te.Socket.e(i)}))}))}}Ts.instance=new Ts;class Rs{constructor(){this.offsetMap=new Map,this.markingTime=0,this.userId=te.Socket.user().id}updateOffset(e,t){let s=this.offsetMap.get(e);return u.isDef(s)?t>s&&(this.offsetMap.set(e,t),!0):(this.offsetMap.set(e,t),!0)}updateUserOffsets(e){e.forEach((e=>{let t=e.userId,s=e.offset;this.updateOffset(t,s)}))}updateMyOffset(e){return this.updateOffset(this.userId,e)}myOffset(){return this.getOffset(this.userId)}getOffset(e){let t=this.offsetMap.get(e);return t||0}}class As{constructor(e,t,s,i,n){this.id=e,this.scene=t,this.lastTimestamp=s,this.limit=i,this.teamId=n}}class ks{constructor(e){this.history=e}pre(){this.oldLastMessage=this.history.getMaxMessage(),this.oldUnreadAmount=this.history.unreadAmount(),this.oldLastMessage&&(this.oldLastMessageRecalled=this.oldLastMessage.recalled,this.oldLastMessageRead=this.oldLastMessage.read,this.oldLastMessageStatus=this.oldLastMessage.status)}post(){let e,t,s,i=this.history.unreadAmount(),n=this.history.getMaxMessage();n&&(s=n.status,e=n.read,t=n.recalled);let o=this.history.target;if(this.oldLastMessage!==n||this.oldLastMessageRead!==e||this.oldLastMessageRecalled!==t||this.oldLastMessageStatus!==s)if(n){const e={message:n,receiver:this.history.to};ys.i.fire(us.MAX_MESSAGE_CHANGED,e)}else ys.i.fire(us.MAX_MESSAGE_DELETED,o);else this.oldUnreadAmount!==i&&ys.i.fire(us.UNREAD_AMOUNT_CHANGED,o)}}class Os{constructor(e,t,s,i){this.scene=e,this.conversationId=t,this.recaller=s,this.times=i}}class Ps{static init(){this.eventCenter=new ee}static on(e,t){this.eventCenter.on(e,t)}static fire(e,t){this.eventCenter.fire(e,t)}static off(e,t){this.eventCenter.off(e,t)}}!function(e){e.PRIVATE_MESSAGE_RECEIVED="PRIVATE_MESSAGE_RECEIVED",e.GROUP_MESSAGE_RECEIVED="GROUP_MESSAGE_RECEIVED",e.SYSTEM_MESSAGE_RECEIVED="SYSTEM_MESSAGE_RECEIVED",e.CONVERSATIONS_UPDATED="CONVERSATIONS_UPDATED",e.HISTORY_EXPIRED="HISTORY_EXPIRED",e.USER_PRESENCE="USER_PRESENCE",e.GROUP_PRESENCE="GROUP_PRESENCE",e.MESSAGE_DELETED="MESSAGE_DELETED",e.MESSAGE_READ="MESSAGE_READ",e.MESSAGE_RECALLED="MESSAGE_RECALLED",e.CS_MESSAGE_RECEIVED="CS_MESSAGE_RECEIVED",e.PENDING_CONVERSATIONS_UPDATED="PENDING_CONVERSATIONS_UPDATED"}(ds||(ds={}));class _s{static init(e){_s.instance=e}static loadCustomMessages(e,t,s){return b(this,void 0,void 0,(function*(){let i=t?t.messageId:"";return(yield this.instance.loadHistory(te.Socket.user().id,e.scene,e.id,i,s)).map((e=>this.convertAbstractMessage(e)))}))}static convertAbstractMessage(e){const t={type:e.type,timestamp:e.timestamp,senderId:e.senderId,messageId:e.messageId,payload:e.payload};if(e.groupId){const s=new ze;return s.groupId=e.groupId,s.senderData=e.senderData,Object.assign(s,Object.assign(Object.assign({},t),{recalled:!1,status:r.SUCCESS})),s}{const s=new He;return s.receiverId=e.receiverId,Object.assign(s,Object.assign(Object.assign({},t),{read:!0,recalled:!1,status:r.SUCCESS})),s}}}class Ds{constructor(e){this.expiredTime=0,this.remoteHistory=Ts.instance,this.target=e,this.userOffsets=new Rs,this.messageCache=new bs(e)}initMaxMessageAndOffsets(e,t){this.existsMessage(e)||(this.messageCache.saveMessage(e),t.forEach((e=>{this.markLocalMessagesRead(this.messageCache.all(),e.userId,e.offset,!1)})))}existsMessage(e){return this.messageCache.existsMessage(e.messageId)}loadHistory(e,t){return b(this,void 0,void 0,(function*(){return this.expiredTime>0&&!this.messageCache.isEmpty()&&(yield this.updateByServerChange()),u.isUndef(t)?t=10:t>30&&(t=30),yield this.loadServerMessages(e,t)}))}loadCustomMessages(e,t,s){return b(this,void 0,void 0,(function*(){if(s>0&&void 0!==_s.instance){let i=e;return t&&(i=this.messageCache.findMessageByTime(t)),yield _s.loadCustomMessages(this.target,i,s)}return[]}))}loadServerMessages(e,t){return b(this,void 0,void 0,(function*(){let s=this.messageCache.loadLocalMessages(t,e);if(!1===this.messageCache.allLoaded&&s.length!==t){let i=t-s.length,n=s[0]?s[0].timestamp:e,o=new As(this.target.id.toString(),this.target.scene,n,i,this.target.teamId),r=yield this.remoteHistory.loadServerMessages(this.target,o),a=r.messages;s=a.concat(s);const c=yield this.loadCustomMessages(s[0],e,t-s.length);a=a.concat(c),s=[...c,...s],this.messageCache.cacheServerMessages(o,a),r.userOffsets.forEach((e=>{this.userOffsets.updateOffset(e.userId,e.offset)})),this.userOffsets.offsetMap.forEach(((e,t)=>{this.markLocalMessagesRead(a,t,e,!1)}))}return s}))}deleteMessages(e){return b(this,void 0,void 0,(function*(){yield this.aopUnreadAmountMaxMessage((()=>b(this,void 0,void 0,(function*(){let t=e.messages;yield Ms.deleteServerMessages(t),this.messageCache.deleteMessages(t),v.onSuccess(e)}))))}))}syncDeletedMessage(e,t){this.aopUnreadAmountMaxMessage((()=>{this.doSyncDeletedMessage(e,t)}))}doSyncDeletedMessage(e,t){if(e===te.Socket.user().id){let e=this.messageCache.findMessagesByTimes(t);this.messageCache.deleteMessages(e),e.length>0&&Ps.fire(ds.MESSAGE_DELETED,e)}}recallMessages(e){return b(this,void 0,void 0,(function*(){yield this.aopUnreadAmountMaxMessage((()=>b(this,void 0,void 0,(function*(){this.doRecall(e)}))))}))}doRecall(e){let t=this.messageCache.recallMessage(e);t.length>0&&Ps.fire(ds.MESSAGE_RECALLED,t)}expire(){this.messageCache.isEmpty()||(this.expiredTime=this.messageCache.maxSuccessMessageTime())}updateByServerChange(){return b(this,void 0,void 0,(function*(){yield this.aopUnreadAmountMaxMessage((()=>b(this,void 0,void 0,(function*(){let e=yield this.remoteHistory.sync(this.target.scene,this.target.id,this.expiredTime,this.messageCache.minTime(),this.target.teamId);e.userOffsets.forEach((e=>{this.markLocalMessagesRead(this.messageCache.all(),e.userId,e.offset,!0)}));let t=e.deletedMessageTimes;t.length>0&&this.doSyncDeletedMessage(te.Socket.user().id,t);let s=e.recalledMessages;s.length>0&&s.forEach((e=>{let t=new Os(this.target.scene,this.target.id,e.recaller,e.times);this.doRecall(t)})),this.expiredTime=0}))))}))}markRead(){return b(this,void 0,void 0,(function*(){yield this.aopUnreadAmountMaxMessage((()=>b(this,void 0,void 0,(function*(){let e=this.messageCache.maxSuccessMessageTime();e>this.userOffsets.myOffset()&&(this.userOffsets.markingTime=e,yield this.remoteHistory.updateServerOffsets(e,this.target),e===this.userOffsets.markingTime&&this.markLocalMessagesRead(this.messageCache.all(),te.Socket.user().id,e,!0))}))))}))}syncMarkedMessage(e){this.aopUnreadAmountMaxMessage((()=>{this.markLocalMessagesRead(this.messageCache.all(),e.markerId,e.time,!0)}))}onMessageSending(e){this.aopUnreadAmountMaxMessage((()=>{this.messageCache.saveMessage(e)}))}onMessageSendSuccess(e){this.aopUnreadAmountMaxMessage((()=>{this.messageCache.correctPosition(e),this.markLocalMessagesRead(this.messageCache.all(),te.Socket.user().id,e.timestamp,!0)}))}onMessageSendFailed(e){this.getMaxMessage()===e&&ys.i.fire(us.MAX_MESSAGE_CHANGED,{message:e,receiver:this.to})}onMessageReceived(e){this.aopUnreadAmountMaxMessage((()=>{this.messageCache.saveMessage(e),this.markLocalMessagesRead(this.messageCache.all(),e.senderId,e.timestamp,!0)}))}aopUnreadAmountMaxMessage(e,t){return b(this,void 0,void 0,(function*(){try{let t=new ks(this);t.pre(),yield e(),t.post()}catch(e){v.onFailed(t,e)}}))}markLocalMessagesRead(e,t,s,i){if(this.userOffsets.updateOffset(t,s),this.isOtherUserId(t)){let t=this.markMySentRead(e,s);i&&t.length>0&&Ps.fire(ds.MESSAGE_READ,t)}else t===te.Socket.user().id&&this.markOthersSentRead(e,s)}markOthersSentRead(e,t){if(this.target.scene===o.PRIVATE)for(let s=e.length-1;s>=0;s--){let i=e[s];if(i.isOtherSent()&&i.timestamp<=t){if(i.read)break;i.read=!0}}}markMySentRead(e,t){let s=new Array;if(this.target.scene===o.PRIVATE)for(let i=e.length-1;i>=0;i--){let n=e[i];if(!n.isOtherSent()&&n.timestamp<=t&&n.status===r.SUCCESS){if(n.read)break;n.read=!0,s.push(n)}}return s}isOtherUserId(e){if(this.target.scene===o.CS){let t=this.target.customerId();return te.Socket.user().id===t?e!==te.Socket.user().id:e===t}return e!==te.Socket.user().id}unreadAmount(e){let t=0,s=this.userOffsets.myOffset(),i=this.messageCache.all();for(const e of i)e.isOtherSent()&&!1===e.recalled&&e.timestamp>s&&(t+=1);return t}getMaxMessage(e){return this.messageCache.getMaxMessage()}maxTime(e){let t=this.getMaxMessage();return u.isDef(t)?t.timestamp:0}}class xs{constructor(e,t){this.teamId=e,this.customerId=t}}class Us{}class Gs{constructor(e,t,s){this.teamId=e,this.teamData=JSON.stringify(t),this.agentData=JSON.stringify(s)}}class Fs{constructor(e){this.teamId=e}}class Ls{constructor(e){this.teamId=e}}var Bs={exports:{}},qs=/^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/,js=["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],Vs=function(e){var t=e,s=e.indexOf("["),i=e.indexOf("]");-1!=s&&-1!=i&&(e=e.substring(0,s)+e.substring(s,i).replace(/:/g,";")+e.substring(i,e.length));for(var n=qs.exec(e||""),o={},r=14;r--;)o[js[r]]=n[r]||"";return-1!=s&&-1!=i&&(o.source=t,o.host=o.host.substring(1,o.host.length-1).replace(/;/g,":"),o.authority=o.authority.replace("[","").replace("]","").replace(/;/g,":"),o.ipv6uri=!0),o},Hs={exports:{}},zs={exports:{}},Ws=1e3,Js=60*Ws,Xs=60*Js,$s=24*Xs,Ys=365.25*$s,Qs=function(e,t){t=t||{};var s=typeof e;if("string"===s&&e.length>0)return function(e){if(e=String(e),e.length>100)return;var t=/^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(e);if(!t)return;var s=parseFloat(t[1]);switch((t[2]||"ms").toLowerCase()){case"years":case"year":case"yrs":case"yr":case"y":return s*Ys;case"days":case"day":case"d":return s*$s;case"hours":case"hour":case"hrs":case"hr":case"h":return s*Xs;case"minutes":case"minute":case"mins":case"min":case"m":return s*Js;case"seconds":case"second":case"secs":case"sec":case"s":return s*Ws;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return s;default:return}}(e);if("number"===s&&!1===isNaN(e))return t.long?function(e){return Ks(e,$s,"day")||Ks(e,Xs,"hour")||Ks(e,Js,"minute")||Ks(e,Ws,"second")||e+" ms"}(e):function(e){if(e>=$s)return Math.round(e/$s)+"d";if(e>=Xs)return Math.round(e/Xs)+"h";if(e>=Js)return Math.round(e/Js)+"m";if(e>=Ws)return Math.round(e/Ws)+"s";return e+"ms"}(e);throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(e))};function Ks(e,t,s){if(!(e=31||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)},t.storage="undefined"!=typeof chrome&&void 0!==chrome.storage?chrome.storage.local:function(){try{return window.localStorage}catch(e){}}(),t.colors=["#0000CC","#0000FF","#0033CC","#0033FF","#0066CC","#0066FF","#0099CC","#0099FF","#00CC00","#00CC33","#00CC66","#00CC99","#00CCCC","#00CCFF","#3300CC","#3300FF","#3333CC","#3333FF","#3366CC","#3366FF","#3399CC","#3399FF","#33CC00","#33CC33","#33CC66","#33CC99","#33CCCC","#33CCFF","#6600CC","#6600FF","#6633CC","#6633FF","#66CC00","#66CC33","#9900CC","#9900FF","#9933CC","#9933FF","#99CC00","#99CC33","#CC0000","#CC0033","#CC0066","#CC0099","#CC00CC","#CC00FF","#CC3300","#CC3333","#CC3366","#CC3399","#CC33CC","#CC33FF","#CC6600","#CC6633","#CC9900","#CC9933","#CCCC00","#CCCC33","#FF0000","#FF0033","#FF0066","#FF0099","#FF00CC","#FF00FF","#FF3300","#FF3333","#FF3366","#FF3399","#FF33CC","#FF33FF","#FF6600","#FF6633","#FF9900","#FF9933","#FFCC00","#FFCC33"],t.formatters.j=function(e){try{return JSON.stringify(e)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}},t.enable(s())}(Hs,Hs.exports);var Zs=Vs,ei=Hs.exports("socket.io-client:url"),ti=function(e,t){var s=e;t=t||"undefined"!=typeof location&&location,null==e&&(e=t.protocol+"//"+t.host);"string"==typeof e&&("/"===e.charAt(0)&&(e="/"===e.charAt(1)?t.protocol+e:t.host+e),/^(https?|wss?):\/\//.test(e)||(ei("protocol-less url %s",e),e=void 0!==t?t.protocol+"//"+e:"https://"+e),ei("parse %s",e),s=Zs(e));s.port||(/^(http|ws)$/.test(s.protocol)?s.port="80":/^(http|ws)s$/.test(s.protocol)&&(s.port="443"));s.path=s.path||"/";var i=-1!==s.host.indexOf(":")?"["+s.host+"]":s.host;return s.id=s.protocol+"://"+i+":"+s.port,s.href=s.protocol+"://"+i+(t&&t.port===s.port?"":":"+s.port),s};var si={},ii={}.toString,ni=Array.isArray||function(e){return"[object Array]"==ii.call(e)};!function(e){Hs.exports("socket.io-parser");var t=w.exports,s=ni;function i(){}e.protocol=4,e.types=["CONNECT","DISCONNECT","EVENT","ACK","ERROR","BINARY_EVENT","BINARY_ACK"],e.CONNECT=0,e.DISCONNECT=1,e.EVENT=2,e.ACK=3,e.ERROR=4,e.BINARY_EVENT=5,e.BINARY_ACK=6,e.Encoder=i,e.Decoder=o;var n=e.ERROR+'"encode error"';function o(){this.reconstructor=null}function r(t){return{type:e.ERROR,data:"parser error: "+t}}i.prototype.encode=function(t,s){var i=function(t){var s=""+t.type;e.BINARY_EVENT!==t.type&&e.BINARY_ACK!==t.type||(s+=t.attachments+"-");t.nsp&&"/"!==t.nsp&&(s+=t.nsp+",");null!=t.id&&(s+=t.id);if(null!=t.data){var i=function(e){try{return JSON.stringify(e)}catch(e){return!1}}(t.data);if(!1===i)return n;s+=i}return s}(t);s([i])},t(o.prototype),o.prototype.add=function(t){var i;if("string"!=typeof t)throw new Error("Unknown type: "+t);i=function(t){var i=0,n={type:Number(t.charAt(0))};if(null==e.types[n.type])return r("unknown packet type "+n.type);if(e.BINARY_EVENT===n.type||e.BINARY_ACK===n.type){for(var o="";"-"!==t.charAt(++i)&&(o+=t.charAt(i),i!=t.length););if(o!=Number(o)||"-"!==t.charAt(i))throw new Error("Illegal attachments");n.attachments=Number(o)}if("/"===t.charAt(i+1))for(n.nsp="";++i;){if(","===(c=t.charAt(i)))break;if(n.nsp+=c,i===t.length)break}else n.nsp="/";var a=t.charAt(i+1);if(""!==a&&Number(a)==a){for(n.id="";++i;){var c;if(null==(c=t.charAt(i))||Number(c)!=c){--i;break}if(n.id+=t.charAt(i),i===t.length)break}n.id=Number(n.id)}if(t.charAt(++i)){var u=function(e){try{return JSON.parse(e)}catch(e){return!1}}(t.substr(i));if(!(!1!==u&&(n.type===e.ERROR||s(u))))return r("invalid payload");n.data=u}return n}(t),this.emit("decoded",i)},o.prototype.destroy=function(){this.reconstructor&&this.reconstructor.finishedReconstruction()}}(si);var oi={exports:{}},ri={},ai={},ci=Object.keys||function(e){var t=[],s=Object.prototype.hasOwnProperty;for(var i in e)s.call(e,i)&&t.push(i);return t},ui=ni,li=Object.prototype.toString,di="function"==typeof Blob||"undefined"!=typeof Blob&&"[object BlobConstructor]"===li.call(Blob),hi="function"==typeof File||"undefined"!=typeof File&&"[object FileConstructor]"===li.call(File),pi=function e(t){if(!t||"object"!=typeof t)return!1;if(ui(t)){for(var s=0,i=t.length;s=55296&&t<=56319&&n=55296&&e<=57343){if(t)throw Error("Lone surrogate U+"+e.toString(16).toUpperCase()+" is not a scalar value");return!1}return!0}function Ci(e,t){return Ei(e>>t&63|128)}function Mi(e,t){if(0==(4294967168&e))return Ei(e);var s="";return 0==(4294965248&e)?s=Ei(e>>6&31|192):0==(4294901760&e)?(bi(e,t)||(e=65533),s=Ei(e>>12&15|224),s+=Ci(e,6)):0==(4292870144&e)&&(s=Ei(e>>18&7|240),s+=Ci(e,12),s+=Ci(e,6)),s+=Ei(63&e|128)}function wi(){if(Si>=yi)throw Error("Invalid byte index");var e=255&gi[Si];if(Si++,128==(192&e))return 63&e;throw Error("Invalid continuation byte")}function Ii(e){var t,s;if(Si>yi)throw Error("Invalid byte index");if(Si==yi)return!1;if(t=255&gi[Si],Si++,0==(128&t))return t;if(192==(224&t)){if((s=(31&t)<<6|wi())>=128)return s;throw Error("Invalid continuation byte")}if(224==(240&t)){if((s=(15&t)<<12|wi()<<6|wi())>=2048)return bi(s,e)?s:65533;throw Error("Invalid continuation byte")}if(240==(248&t)&&(s=(7&t)<<18|wi()<<12|wi()<<6|wi())>=65536&&s<=1114111)return s;throw Error("Invalid UTF-8 detected")}var Ni={version:"2.1.2",encode:function(e,t){for(var s=!1!==(t=t||{}).strict,i=vi(e),n=i.length,o=-1,r="";++o65535&&(n+=Ei((t-=65536)>>>10&1023|55296),t=56320|1023&t),n+=Ei(t);return n}(n)}},Ti=void 0!==Ti?Ti:"undefined"!=typeof WebKitBlobBuilder?WebKitBlobBuilder:"undefined"!=typeof MSBlobBuilder?MSBlobBuilder:"undefined"!=typeof MozBlobBuilder&&MozBlobBuilder,Ri=function(){try{return 2===new Blob(["hi"]).size}catch(e){return!1}}(),Ai=Ri&&function(){try{return 2===new Blob([new Uint8Array([1,2])]).size}catch(e){return!1}}(),ki=Ti&&Ti.prototype.append&&Ti.prototype.getBlob;function Oi(e){return e.map((function(e){if(e.buffer instanceof ArrayBuffer){var t=e.buffer;if(e.byteLength!==t.byteLength){var s=new Uint8Array(e.byteLength);s.set(new Uint8Array(t,e.byteOffset,e.byteLength)),t=s.buffer}return t}return e}))}function Pi(e,t){t=t||{};var s=new Ti;return Oi(e).forEach((function(e){s.append(e)})),t.type?s.getBlob(t.type):s.getBlob()}function _i(e,t){return new Blob(Oi(e),t||{})}"undefined"!=typeof Blob&&(Pi.prototype=Blob.prototype,_i.prototype=Blob.prototype);var Di=Ri?Ai?Blob:_i:ki?Pi:void 0;!function(e){var t=ci,s=pi,i=mi,n=Ni;"undefined"!=typeof navigator&&/Android/i.test(navigator.userAgent),"undefined"!=typeof navigator&&/PhantomJS/i.test(navigator.userAgent),e.protocol=3;var o=e.packets={open:0,close:1,ping:2,pong:3,message:4,upgrade:5,noop:6},r=t(o),a={type:"error",data:"parser error"},c=Di;e.encodePacket=function(e,t,s,i){"function"==typeof t&&(i=t,t=!1),"function"==typeof s&&(i=s,s=null),void 0===e.data||e.data.buffer||e.data;var r=o[e.type];return void 0!==e.data&&(r+=s?n.encode(String(e.data),{strict:!1}):String(e.data)),i(""+r)},e.decodePacket=function(e,t,s){if(void 0===e)return a;if("string"==typeof e){if(s&&!1===(e=function(e){try{e=n.decode(e,{strict:!1})}catch(e){return!1}return e}(e)))return a;var i=e.charAt(0);return Number(i)==i&&r[i]?e.length>1?{type:r[i],data:e.substring(1)}:{type:r[i]}:a}i=new Uint8Array(e)[0];var o=sliceBuffer(e,1);return c&&"blob"===t&&(o=new c([o])),{type:r[i],data:o}},e.encodePayload=function(t,n,o){"function"==typeof n&&(o=n,n=null);var r=s(t);if(!t.length)return o("0:");!function(e,t,s){for(var n=new Array(e.length),o=i(e.length,s),r=function(e,s,i){t(s,(function(t,s){n[e]=s,i(t,n)}))},a=0;a0);return t}function Ji(){var e=Wi(+new Date);return e!==Fi?(Hi=0,Fi=e):e+"."+Wi(Hi++)}for(;zi';i=document.createElement(e)}catch(e){(i=document.createElement("iframe")).name=s.iframeId,i.src="javascript:0"}i.id=s.iframeId,s.form.appendChild(i),s.iframe=i}this.form.action=this.uri(),c(),e=e.replace(hn,"\\\n"),this.area.value=e.replace(dn,"\\n");try{this.form.submit()}catch(e){}this.iframe.attachEvent?this.iframe.onreadystatechange=function(){"complete"===s.iframe.readyState&&a()}:this.iframe.onload=a};var gn,yn,Sn=Ui,En=ai,vn=Li,bn=Bi,Cn=Xi,Mn=Hs.exports("engine.io-client:websocket");("undefined"==typeof uni&&"undefined"==typeof wx&&"undefined"==typeof my&&"undefined"==typeof swan||"undefined"!=typeof WebSocket)&&("undefined"!=typeof WebSocket?gn=WebSocket:"undefined"!=typeof self&&(gn=self.WebSocket||self.MozWebSocket));var wn=gn||yn,In="undefined"!=typeof uni&&"harmonyos"===uni.getSystemInfoSync().platform;(("undefined"!=typeof uni||"undefined"!=typeof wx||"undefined"!=typeof my||"undefined"!=typeof swan)&&"undefined"==typeof WebSocket||"undefined"!=typeof GameGlobal||In)&&(wn=function(e){var t=this;if(t.onopen=function(){},t.onclose=function(){},t.onmessage=function(e){},t.onerror=function(e){},"object"==typeof tt&&tt.getSystemInfo){let s=tt.connectSocket({url:e});t.send=function(e){s.send({data:e})},t.close=function(){s.close()},s.onOpen((function(){t.onopen()})),s.onError((function(e){t.onerror(e)})),s.onMessage((function(e){t.onmessage(e)})),s.onClose((function(){t.onclose()}))}else if("undefined"!=typeof my)my.connectSocket({url:e}),t.send=function(e){my.sendSocketMessage({data:e})},t.close=function(e){my.closeSocket()},my.onSocketOpen((function(e){t.onopen()})),my.onSocketError((function(e){t.onerror(e)})),my.onSocketMessage((function(e){t.onmessage(e)})),my.onSocketClose((e=>{t.onclose(e)}));else if("undefined"!=typeof swan)swan.connectSocket({url:e}),t.send=function(e){swan.sendSocketMessage({data:e})},t.close=function(e){swan.closeSocket()},swan.onSocketOpen((function(e){t.onopen()})),swan.onSocketError((function(e){t.onerror(e)})),swan.onSocketMessage((function(e){t.onmessage(e)})),swan.onSocketClose((e=>{t.onclose(e)}));else if("undefined"!=typeof uni){var s=uni.connectSocket({url:e,complete:()=>{}});t.send=function(e){s.send({data:e})},t.close=function(){s.close()},s.onOpen((function(e){t.onopen()})),s.onError((function(e){t.onerror(e)})),s.onMessage((function(e){t.onmessage(e)})),s.onClose((function(e){t.onclose()}))}else{var i=wx.connectSocket({url:e});t.send=function(e){i.send({data:e})},t.close=function(e){i.close({code:1e3})},i.onOpen((function(){t.onopen()})),i.onError((function(e){t.onerror(e)})),i.onMessage((function(e){t.onmessage(e)})),i.onClose((function(e){t.onclose(e)}))}});var Nn=Tn;function Tn(e){e&&e.forceBase64&&(this.supportsBinary=!1),("undefined"==typeof uni&&"undefined"==typeof wx&&"undefined"==typeof my&&"undefined"==typeof swan||"undefined"!=typeof WebSocket)&&(this.perMessageDeflate=e.perMessageDeflate,this.usingBrowserWebSocket=gn&&!e.forceNode,this.protocols=e.protocols,this.usingBrowserWebSocket||(wn=yn)),Sn.call(this,e)}bn(Tn,Sn),Tn.prototype.name="websocket",Tn.prototype.supportsBinary=!1,Tn.prototype.doOpen=function(){if(this.check()){var e,t,s=this.uri();if(("undefined"==typeof uni&&"undefined"==typeof wx&&"undefined"==typeof my&&"undefined"==typeof swan||"undefined"!=typeof WebSocket)&&(e=this.protocols),(t=("undefined"!=typeof uni||"undefined"!=typeof wx||"undefined"!=typeof my||"undefined"!=typeof swan)&&"undefined"==typeof WebSocket||In?{agent:this.agent}:{agent:this.agent,perMessageDeflate:this.perMessageDeflate}).pfx=this.pfx,t.key=this.key,t.passphrase=this.passphrase,t.cert=this.cert,t.ca=this.ca,t.ciphers=this.ciphers,t.rejectUnauthorized=this.rejectUnauthorized,"object"==typeof navigator&&"ReactNative"===navigator.product&&(t={}),this.extraHeaders&&(t.headers=this.extraHeaders),this.localAddress&&(t.localAddress=this.localAddress),"undefined"!=typeof cc&&"Windows"===cc.sys.os)cc.resources.load("cacert",((e,t)=>{const i=t.nativeUrl;this.ws=new wn(s,[],i),this.ws.binaryType="arraybuffer",this.addEventListeners()}));else{try{("undefined"!=typeof uni||"undefined"!=typeof wx||"undefined"!=typeof my||"undefined"!=typeof swan)&&"undefined"==typeof WebSocket||In?this.ws=new wn(s):this.ws=this.usingBrowserWebSocket&&!this.isReactNative?e?new wn(s,e):new wn(s):new wn(s,e,t)}catch(e){return this.emit("error",e)}void 0===this.ws.binaryType&&(this.supportsBinary=!1),this.ws.supports&&this.ws.supports.binary?(this.supportsBinary=!0,this.ws.binaryType="nodebuffer"):this.ws.binaryType="arraybuffer",this.addEventListeners()}}},Tn.prototype.addEventListeners=function(){var e=this;this.ws.onopen=function(){e.onOpen()},this.ws.onclose=function(){e.onClose()},this.ws.onmessage=function(t){e.onData(t.data)},this.ws.onerror=function(t){e.onError("websocket error",t)}},Tn.prototype.write=function(e){var t=this;this.writable=!1;for(var s=e.length,i=0,n=s;i0&&(this.extraHeaders=t.extraHeaders),t.localAddress&&(this.localAddress=t.localAddress)),this.id=null,this.upgrades=null,this.pingInterval=null,this.pingTimeout=null,this.pingIntervalTimer=null,this.pingTimeoutTimer=null,this.open()}Bn.priorWebsocketSuccess=!1,_n(Bn.prototype),Bn.protocol=Un.protocol,Bn.Socket=Bn,Bn.Transport=Ui,Bn.transports=ri,Bn.parser=ai,Bn.prototype.createTransport=function(e){Dn('creating transport "%s"',e);var t=function(e){var t={};for(var s in e)e.hasOwnProperty(s)&&(t[s]=e[s]);return t}(this.query);t.EIO=Un.protocol,t.transport=e;var s=this.transportOptions[e]||{};return this.id&&(t.sid=this.id),new Pn[e]({query:t,socket:this,agent:s.agent||this.agent,hostname:s.hostname||this.hostname,port:s.port||this.port,secure:s.secure||this.secure,path:s.path||this.path,forceJSONP:s.forceJSONP||this.forceJSONP,jsonp:s.jsonp||this.jsonp,forceBase64:s.forceBase64||this.forceBase64,enablesXDR:s.enablesXDR||this.enablesXDR,timestampRequests:s.timestampRequests||this.timestampRequests,timestampParam:s.timestampParam||this.timestampParam,policyPort:s.policyPort||this.policyPort,pfx:s.pfx||this.pfx,key:s.key||this.key,passphrase:s.passphrase||this.passphrase,cert:s.cert||this.cert,ca:s.ca||this.ca,ciphers:s.ciphers||this.ciphers,rejectUnauthorized:s.rejectUnauthorized||this.rejectUnauthorized,perMessageDeflate:s.perMessageDeflate||this.perMessageDeflate,extraHeaders:s.extraHeaders||this.extraHeaders,forceNode:s.forceNode||this.forceNode,localAddress:s.localAddress||this.localAddress,requestTimeout:s.requestTimeout||this.requestTimeout,protocols:s.protocols||void 0,isReactNative:this.isReactNative})},Bn.prototype.open=function(){var e;if(this.rememberUpgrade&&Bn.priorWebsocketSuccess&&-1!==this.transports.indexOf("websocket"))e="websocket";else{if(0===this.transports.length){var t=this;return void setTimeout((function(){t.emit("error","No transports available")}),0)}e=this.transports[0]}this.readyState="opening";try{e=this.createTransport(e)}catch(e){return this.transports.shift(),void this.open()}e.open(),this.setTransport(e)},Bn.prototype.setTransport=function(e){Dn("setting transport %s",e.name);var t=this;this.transport&&(Dn("clearing existing transport %s",this.transport.name),this.transport.removeAllListeners()),this.transport=e,e.on("drain",(function(){t.onDrain()})).on("packet",(function(e){t.onPacket(e)})).on("error",(function(e){t.onError(e)})).on("close",(function(){t.onClose("transport close")}))},Bn.prototype.probe=function(e){Dn('probing transport "%s"',e);var t=this.createTransport(e,{probe:1}),s=!1,i=this;function n(){if(i.onlyBinaryUpgrades){var n=!this.supportsBinary&&i.transport.supportsBinary;s=s||n}s||(Dn('probe transport "%s" opened',e),t.send([{type:"ping",data:"probe"}]),t.once("packet",(function(n){if(!s)if("pong"===n.type&&"probe"===n.data){if(Dn('probe transport "%s" pong',e),i.upgrading=!0,i.emit("upgrading",t),!t)return;Bn.priorWebsocketSuccess="websocket"===t.name,Dn('pausing current transport "%s"',i.transport.name),i.transport.pause((function(){s||"closed"!==i.readyState&&(Dn("changing transport and sending upgrade packet"),l(),i.setTransport(t),t.send([{type:"upgrade"}]),i.emit("upgrade",t),t=null,i.upgrading=!1,i.flush())}))}else{Dn('probe transport "%s" failed',e);var o=new Error("probe error");o.transport=t.name,i.emit("upgradeError",o)}})))}function o(){s||(s=!0,l(),t.close(),t=null)}function r(s){var n=new Error("probe error: "+s);n.transport=t.name,o(),Dn('probe transport "%s" failed because of error: %s',e,s),i.emit("upgradeError",n)}function a(){r("transport closed")}function c(){r("socket closed")}function u(e){t&&e.name!==t.name&&(Dn('"%s" works - aborting "%s"',e.name,t.name),o())}function l(){t.removeListener("open",n),t.removeListener("error",r),t.removeListener("close",a),i.removeListener("close",c),i.removeListener("upgrading",u)}Bn.priorWebsocketSuccess=!1,t.once("open",n),t.once("error",r),t.once("close",a),this.once("close",c),this.once("upgrading",u),t.open()},Bn.prototype.onOpen=function(){if(Dn("socket open"),this.readyState="open",Bn.priorWebsocketSuccess="websocket"===this.transport.name,this.emit("open"),this.flush(),"open"===this.readyState&&this.upgrade&&this.transport.pause){Dn("starting upgrade probes");for(var e=0,t=this.upgrades.length;e0&&e.jitter<=1?e.jitter:0,this.attempts=0}Jn.prototype.duration=function(){var e=this.ms*Math.pow(this.factor,this.attempts++);if(this.jitter){var t=Math.random(),s=Math.floor(t*this.jitter*e);e=0==(1&Math.floor(10*t))?e-s:e+s}return 0|Math.min(e,this.max)},Jn.prototype.reset=function(){this.attempts=0},Jn.prototype.setMin=function(e){this.ms=e},Jn.prototype.setMax=function(e){this.max=e},Jn.prototype.setJitter=function(e){this.jitter=e};class Xn{static init(e,t,s){this.host=e,(void 0!==t&&!1===t||!0===s)&&(this.https=!1)}static isMP(){return[l.MP_WX,l.MP_ALI,l.MP_BYTE,l.MP_WGAME,l.MP_BAIDU].includes(d.currentPlatform())}static uri(){let e="http";return this.https&&(e+="s"),e+"://"+this.index()+this.host}static index(){return 0==this.i?this.i=Math.floor(Math.random()*this.max)+1:this.i=this.i%this.max+1,this.i}}Xn.i=0,Xn.max=5,Xn.https=!0;var $n=M(Object.freeze({__proto__:null,URIResolver:Xn})),Yn=M(E),Qn=oi.exports,Kn=qn.exports,Zn=w.exports,eo=si,to=Vn,so=zn,io=(Hs.exports("socket.io-client:manager"),On),no=Wn;const{URIResolver:oo}=$n;var ro=Yn.runStatus,ao=Object.prototype.hasOwnProperty,co=uo;function uo(e,t){if(!(this instanceof uo))return new uo(e,t);e&&"object"==typeof e&&(t=e,e=void 0),(t=t||{}).path=t.path||"/socket.io",this.nsps={},this.subs=[],this.opts=t,this.reconnection(!1!==t.reconnection),this.reconnectionAttempts(t.reconnectionAttempts||1/0),this.reconnectionDelay(t.reconnectionDelay||1e3),this.reconnectionDelayMax(t.reconnectionDelayMax||5e3),this.randomizationFactor(t.randomizationFactor||.5),this.backoff=new no({min:this.reconnectionDelay(),max:this.reconnectionDelayMax(),jitter:this.randomizationFactor()}),this.timeout(null==t.timeout?2e4:t.timeout),this.readyState="closed",this.uri=e,this.connecting=[],this.lastPing=null,this.encoding=!1,this.packetBuffer=[];var s=t.parser||eo;this.encoder=new s.Encoder,this.decoder=new s.Decoder,this.autoConnect=!1!==t.autoConnect,this.autoConnect&&this.open()}uo.prototype.emitAll=function(){for(var e in this.emit.apply(this,arguments),this.nsps)ao.call(this.nsps,e)&&this.nsps[e].emit.apply(this.nsps[e],arguments)},uo.prototype.updateSocketIds=function(){for(var e in this.nsps)ao.call(this.nsps,e)&&(this.nsps[e].id=this.generateId(e))},uo.prototype.generateId=function(e){return("/"===e?"":e+"#")+this.engine.id},Zn(uo.prototype),uo.prototype.reconnection=function(e){return arguments.length?(this._reconnection=!!e,this):this._reconnection},uo.prototype.reconnectionAttempts=function(e){return arguments.length?(this._reconnectionAttempts=e,this):this._reconnectionAttempts},uo.prototype.reconnectionDelay=function(e){return arguments.length?(this._reconnectionDelay=e,this.backoff&&this.backoff.setMin(e),this):this._reconnectionDelay},uo.prototype.randomizationFactor=function(e){return arguments.length?(this._randomizationFactor=e,this.backoff&&this.backoff.setJitter(e),this):this._randomizationFactor},uo.prototype.reconnectionDelayMax=function(e){return arguments.length?(this._reconnectionDelayMax=e,this.backoff&&this.backoff.setMax(e),this):this._reconnectionDelayMax},uo.prototype.timeout=function(e){return arguments.length?(this._timeout=e,this):this._timeout},uo.prototype.maybeReconnectOnOpen=function(){!this.reconnecting&&this._reconnection&&0===this.backoff.attempts&&this.reconnect()},uo.prototype.open=uo.prototype.connect=function(e,t){if(this.readyState,~this.readyState.indexOf("open"))return this;this.uri,this.engine=Qn(this.uri,this.opts);var s=this.engine,i=this;this.readyState="opening",this.skipReconnect=!1;var n=to(s,"open",(function(){i.onopen(),e&&e()})),o=to(s,"error",(async function(t){if(i.uri=oo.uri(),i.cleanup(),i.readyState="closed",i.emitAll("connect_error",t),e){var s=new Error("Connection error");s.data=t,e(s)}else i.maybeReconnectOnOpen()}));if(!1!==this._timeout){var r=this._timeout,a=setTimeout((function(){n.destroy(),s.close(),s.emit("error","timeout"),i.emitAll("connect_timeout",r)}),r);this.subs.push({destroy:function(){clearTimeout(a)}})}return this.subs.push(n),this.subs.push(o),this},uo.prototype.onopen=function(){this.cleanup(),this.readyState="open",this.emit("open");var e=this.engine;this.subs.push(to(e,"data",so(this,"ondata"))),this.subs.push(to(e,"ping",so(this,"onping"))),this.subs.push(to(e,"pong",so(this,"onpong"))),this.subs.push(to(e,"error",so(this,"onerror"))),this.subs.push(to(e,"close",so(this,"onclose"))),this.subs.push(to(this.decoder,"decoded",so(this,"ondecoded")))},uo.prototype.onping=function(){this.lastPing=new Date,this.emitAll("ping")},uo.prototype.onpong=function(){this.emitAll("pong",new Date-this.lastPing)},uo.prototype.ondata=function(e){this.decoder.add(e)},uo.prototype.ondecoded=function(e){this.emit("packet",e)},uo.prototype.onerror=function(e){this.emitAll("error",e)},uo.prototype.socket=function(e,t){var s=this.nsps[e];if(!s){s=new Kn(this,e,t),this.nsps[e]=s;var i=this;s.on("connecting",n),s.on("connect",(function(){s.id=i.generateId(e)})),this.autoConnect&&n()}function n(){~io(i.connecting,s)||i.connecting.push(s)}return s},uo.prototype.destroy=function(e){var t=io(this.connecting,e);~t&&this.connecting.splice(t,1),this.connecting.length||this.close()},uo.prototype.packet=function(e){var t=this;e.query&&0===e.type&&(e.nsp+="?"+e.query),t.encoding?t.packetBuffer.push(e):(t.encoding=!0,this.encoder.encode(e,(function(s){for(var i=0;i0&&!this.encoding){var e=this.packetBuffer.shift();this.packet(e)}},uo.prototype.cleanup=function(){for(var e=this.subs.length,t=0;t=this._reconnectionAttempts)this.backoff.reset(),this.emitAll("reconnect_failed"),this.reconnecting=!1;else{var t=this.backoff.duration();this.reconnecting=!0;var s=setTimeout((function(){if(ro.isBackend())return e.reconnecting=!1,e.reconnect(),void e.emitAll("reconnect_error","Uniapp running backend, skipped reconnect...");e.skipReconnect||(e.emitAll("reconnect_attempt",e.backoff.attempts),e.emitAll("reconnecting",e.backoff.attempts),e.skipReconnect||e.open((function(t){t?(e.reconnecting=!1,e.reconnect(),e.emitAll("reconnect_error",t.data)):e.onreconnect()})))}),t);this.subs.push({destroy:function(){clearTimeout(s)}})}},uo.prototype.onreconnect=function(){var e=this.backoff.attempts;this.reconnecting=!1,this.backoff.reset(),this.updateSocketIds(),this.emitAll("reconnect",e)},function(e,t){var s=ti,i=si,n=co;Hs.exports("socket.io-client");e.exports=t=r;var o=t.managers={};function r(e,t){"object"==typeof e&&(t=e,e=void 0),t=t||{};var i,r=s(e),a=r.source,c=r.id,u=r.path,l=o[c]&&u in o[c].nsps;return t.forceNew||t["force new connection"]||!1===t.multiplex||l?i=n(a,t):(o[c]||(o[c]=n(a,t)),i=o[c]),r.query&&!t.query&&(t.query=r.query),i.socket(r.path,t)}t.protocol=i.protocol,t.connect=r,t.Manager=co,t.Socket=qn.exports}(Bs,Bs.exports);const lo=Bs.exports;class ho{constructor(){this.io=lo,this.status=t.DISCONNECTED,this.permissions=[ne.NONE],this.connectedObservers=[],this.disconnectedObservers=[]}connect(){this.status=t.CONNECTING}socketio(){return this.io}on(e,t){this.io.on(e,t)}disconnect(){this.io.disconnect()}getStatus(){return this.status}addConnectedObserver(e){u.isFunction(e)&&this.connectedObservers.push(e)}addDisconnectedObserver(e){u.isFunction(e)&&this.disconnectedObservers.push(e)}notify(e,t){for(let s=0;s{this.status=t.CONNECTED,this.notify(this.connectedObservers)})),this.io.on("reconnecting",(e=>{this.status=t.CONNECTING,this.notify(this.reconnectingObservers,e)})),this.io.on("disconnect",(()=>{this.status=t.DISCONNECTED,this.notify(this.disconnectedObservers)})),this.io.on("connect_error",(function(e){}))}addReconnectingObserver(e){this.reconnectingObservers.push(e)}}class mo{static get(e){const t=encodeURIComponent(e)+"=",s=document.cookie.split("; ");for(const e of s)if(e.startsWith(t))return decodeURIComponent(e.substring(t.length));return null}static set(e,t,s,i,n="/",o=!1){let r=encodeURIComponent(e)+"="+encodeURIComponent(t);s instanceof Date&&(r+="; expires="+s.toGMTString()),n&&(r+="; path="+n),i&&(r+="; domain="+i),o&&(r+="; secure"),document.cookie=r}static remove(e,t,s="/",i=!1){mo.set(e,"",new Date(0),t,s,i)}}class fo{asyncGet(e){let t=this.get(e);return Promise.resolve(t)}asyncPut(e,t){return this.put(e,t),Promise.resolve()}get(e){let t=this.doGet(e);return t=JSON.parse(t),t}put(e,t){this.doPut(e,JSON.stringify(t))}}class go extends fo{constructor(){super(),this.domain=null;this.domain="undefined"!=typeof location&&/^(?:[A-za-z0-9-]+\.)+[A-za-z]{2,4}(?:[\/\?#][\/=\?%\-&~`@[\]\':+!\.#\w]*)?$/.test(location.host)?location.host.split(".").slice(-2).join("."):null}doGet(e){return mo.get(e)||null}doPut(e,t){const s=new Date(2030,12,31,0,0,0,0),i=this.domain;mo.set(e,t,s,i)}remove(e){const t=this.domain;mo.remove(e,t)}support(){return"undefined"!=typeof navigator&&!0===navigator.cookieEnabled}}class yo extends fo{doGet(e){return localStorage.getItem(e)||null}doPut(e,t){localStorage.setItem(e,t)}remove(e){localStorage.removeItem(e)}support(){return!("undefined"!=typeof GameGlobal||"undefined"==typeof localStorage||!localStorage.setItem)}}class So extends fo{doGet(e){return uni.getStorageSync(e)||null}doPut(e,t){uni.setStorageSync(e,t)}remove(e){uni.removeStorageSync(e)}support(){return!("object"!=typeof uni||!uni.getStorageSync)}}class Eo extends fo{doGet(e){return cc.sys.localStorage.getItem(e)||null}doPut(e,t){cc.sys.localStorage.setItem(e,t)}remove(e){cc.sys.localStorage.removeItem(e)}support(){return"undefined"!=typeof cc&&void 0!==cc.sys.localStorage}}class vo extends fo{doGet(e){return wx.getStorageSync(e)||null}doPut(e,t){wx.setStorageSync(e,t)}remove(e){wx.removeStorageSync(e)}support(){return!("object"!=typeof wx||!wx.getStorageSync||"undefined"!=typeof tt)}}class bo extends fo{asyncGet(e){return b(this,void 0,void 0,(function*(){const t=yield i.asyncStorage.getItem(e);return JSON.parse(t)}))}asyncPut(e,t){return i.asyncStorage.setItem(e,JSON.stringify(t))}doPut(e,t){throw new Error("Method not implemented.")}remove(e){i.asyncStorage.removeItem(e)}support(){return s.currentFramework()===e.REACT_NATIVE}doGet(e){throw new Error("Method not implemented.")}}class Co extends fo{doGet(e){const t=my.getStorageSync({key:e}).data||null;return"string"==typeof JSON.parse(t)?JSON.parse(t):t}doPut(e,t){my.setStorageSync({key:e,data:JSON.stringify(t)})}remove(e){my.removeStorageSync({key:e})}support(){return!("undefined"==typeof my||!my.getStorageSync)}}class Mo extends fo{doGet(e){return tt.getStorageSync(e)||null}doPut(e,t){tt.setStorageSync(e,t)}remove(e){tt.removeStorageSync(e)}support(){return!("object"!=typeof tt||!tt.getStorageSync)}}class wo extends fo{doGet(e){return swan.getStorageSync(e)||null}doPut(e,t){swan.setStorageSync(e,t)}remove(e){swan.removeStorageSync(e)}support(){return!("undefined"==typeof swan||!swan.getStorageSync)}}class Io{constructor(){this.supportedStorage=null;const e=Io.storages;e.push(new So),e.push(new Eo),e.push(new yo),e.push(new vo),e.push(new bo),e.push(new Co),e.push(new wo),e.push(new Mo),e.push(new go),this.dispatch(),this.supportedStorage}static localStorage(){return this.instance.supportedStorage}dispatch(){for(let e of Io.storages)if(e.support()){this.supportedStorage=e;break}}}Io.storages=new Array,Io.instance=new Io;class No{static get(){return b(this,void 0,void 0,(function*(){let e,t=Io.localStorage();return null!==t&&(e=yield t.asyncGet(No.SM_KEY)),e}))}static put(e){return b(this,void 0,void 0,(function*(){var t=Io.localStorage();let s=e.sm;null!==t&&s&&t.asyncPut(No.SM_KEY,s)}))}}No.SM_KEY="GE-SM";class To{static e(e,t){function s(e){return function(e){return e>=32&&e<=126}(e)?String.fromCharCode(function(e){let s=e+t;return s>126?s-126+32:s}(e)):String.fromCharCode(e)}let i="";for(let t=0;t{"undefined"!=typeof plus?plus.device.getInfo({success:t=>{e(t.uuid)},fail:e=>{t(e)}}):e(null)}))}static getVideoCard(){var e;if(d.currentPlatform()===l.BROWSER&&"undefined"!=typeof document){const t=document.createElement("canvas"),s=null!==(e=t.getContext("webgl"))&&void 0!==e?e:t.getContext("experimental-webgl");if(s&&"getExtension"in s){let e=0,t=0;if(navigator.userAgent.indexOf("Firefox")>-1)t=s.VENDOR,e=s.RENDERER;else{let i=s.getExtension("WEBGL_debug_renderer_info");if(!i)return null;t=i.UNMASKED_VENDOR_WEBGL,e=i.UNMASKED_RENDERER_WEBGL}const i={vendor:(s.getParameter(t)||"").toString(),renderer:(s.getParameter(e)||"").toString()};return JSON.stringify(i)}}return null}static z(){return b(this,void 0,void 0,(function*(){const e={p:d.currentPlatform(),f:s.currentFramework(),vc:this.getVideoCard(),rfp:this.initRNUniqueId()};return To.e(JSON.stringify(e),5)}))}}class Ao{constructor(e){this.rocketsBuffer=new Set,this.socket=e,this.socket.addConnectedObserver(this.onSocketConnected.bind(this))}emit(e){this.socket.status!==t.DISCONNECTED?(e.start(),this.doEmit(e)):e.fail({resultCode:"409",content:"Please connect first"})}doEmit(e){if(!e.complete)if(this.socket.status!==t.CONNECT_FAILED)if(this.isConnected())if(this.hasPermission(e)){let t=setTimeout((()=>{this.doEmit(e)}),e.singleTimeout);e.unique&&(e.params.retried=e.retried),this.socket.socketio().emit(e.name,e.params,(s=>{clearTimeout(t),200===s.resultCode||200==s.code?e.success(s):e.fail(s)})),e.retried++}else e.fail({resultCode:401,content:"No permission"});else this.isConnecting()&&this.rocketsBuffer.add(e);else e.fail({resultCode:408,content:"Failed to connect GoEasy."})}hasPermission(e){return!!this.socket.permissions.find((t=>t===e.permission))}isConnected(){return[t.CONNECTED,t.RECONNECTED,t.EXPIRED_RECONNECTED,t.DISCONNECTING].includes(this.socket.status)}isConnecting(){return[t.CONNECTING,t.RECONNECTING].includes(this.socket.status)}onSocketConnected(){this.emitBuffer()}emitBuffer(){Array.from(this.rocketsBuffer).forEach((e=>{this.rocketsBuffer.delete(e),this.doEmit(e)}))}}class ko extends Q{static init(){ko.i=new ko}static fire(e,t){this.i.fire(e,t)}static on(e,t){this.i.on(e,t)}static off(e,t){this.i.off(e,t)}}class Oo extends ho{constructor(e,t){super(),this.ioSocket=null,this.sid=null,this.anonymous=!1,this.userId=null,this.artifactVersion=n,this.vname=null,this.uri=null,this.ioOpts=null,this.reconnectingTimes=0,this.messageObservers=new Map,this.connectFailedObservers=[],this.connectingObservers=[],this.expiredReconnectedObservers=[],this.options=e,this.ioSocket=new po({onDisconnected:this.onIoDisconnected.bind(this),onReconnecting:this.onIoReconnecting.bind(this)}),this.ioSocketEmitter=new Ao(this.ioSocket),this.ioSocket.addConnectedObserver(this.onIoReconnected.bind(this)),this.initOptions(t),this.connect()}initUserId(){let e=this.connectOptions.id;u.isEmpty(e)?this.anonymous=!0:this.userId=e.toString()}socketio(){return this.ioSocket.socketio()}extendOptions(){let e=this.connectOptions;if(u.isNull(e.data)||u.isDef(e.data)&&!u.isObject(e.data))throw{code:400,content:"TypeError: data requires an object."};if((u.isDef(e.data)?String(e.data).length:0)>300&&u.isObject(e)&&u.isFunction(e.onFailed))throw{code:400,content:"user.data-length limit 300 byte."};if(u.isObject(e.wxmpId)){if(u.isEmpty(e.wxmpId.appid))throw{code:400,content:"wxmpId.appid is required."};if(u.isEmpty(e.wxmpId.openid))throw{code:400,content:"wxmpId.openid is required. requires string."}}else if(u.isPrimitive(e.wxmpId))throw{code:400,content:"TypeError: wxmpId requires an object."}}initUriAndOpts(){let e=this.options;Xn.init(e.host,e.forceTLS,e.supportOldBrowser),this.uri=Xn.uri();let t=["websocket"];!0===e.supportOldBrowser&&t.push("polling"),this.ioOpts={transports:t,timeout:S.connect,reconnectionDelayMax:S.reconnectionDelayMax}}onIoReconnected(){this.status===t.RECONNECTING&&this.authorize()}sendAck(e,t){this.ioSocket.io.emit(e,t)}initOptions(e){this.connectOptions=e,this.addConnectedObserver(e.onSuccess),this.addConnectFailedObserver(e.onFailed),this.addConnectingObserver(e.onProgress),this.initUserId()}connect(){this.initUriAndOpts(),this.extendOptions(),super.connect(),this.onConnecting(),this.ioSocket.connect({uri:this.uri,opts:this.ioOpts}),this.authorize()}disconnect(){return new Promise(((e,s)=>{this.status=t.DISCONNECTING;let i=()=>{this.ioSocket.disconnect(),this.status=t.DISCONNECTED,ko.fire(Ft.DISCONNECTED),e()};const n=X.modules.get("GN");if(n&&(n.params.regId||this.connectOptions.wxmpId)){let e=e=>{s(e)},t=new le({name:y.manualDisconnect,params:{},permission:ne.READ,singleTimeout:S.commonRequestSingle,totalTimeout:S.commonRequestTotal,fail:e,success:i});te.Socket.e(t)}else i()}))}authorize(){return b(this,void 0,void 0,(function*(){let e=this.connectOptions,t={appkey:this.options.appkey,userId:this.userId,userData:JSON.stringify(e.data),otp:e.otp,artifactVersion:this.artifactVersion,sid:this.sid,mP:yield X.getParams(),a:this.anonymous,z:yield Ro.z(),sm:yield No.get(),c:{n:this.vname,v:this.artifactVersion}};JSON.stringify(t);let s=new le({name:y.authorize,params:t,permission:ne.NONE,singleTimeout:S.commonInfiniteSingle,totalTimeout:S.commonInfiniteTotal,success:e=>{this.onAuthorizeSuccess(e)},fail:e=>{this.onAuthorizeFailed(e)}});this.ioSocketEmitter.emit(s)}))}onConnecting(){ko.fire(Ft.CONNECTING,this.reconnectingTimes),this.notify(this.connectingObservers,this.reconnectingTimes)}onIoReconnecting(){this.reconnectingTimes++,this.status==t.CONNECTED||this.status==t.EXPIRED_RECONNECTED||this.status==t.RECONNECTING?this.status=t.RECONNECTING:this.status=t.CONNECTING,this.onConnecting()}onIoDisconnected(){this.status!==t.DISCONNECTING&&(this.status=t.RECONNECTING,ko.fire(Ft.LOST),this.notify(this.disconnectedObservers))}onAuthorizeSuccess(e){if(No.put(e),X.setDatas(e.mD),this.status===t.RECONNECTING){this.sid!==e.sid?(this.status=t.EXPIRED_RECONNECTED,this.sid=e.sid,ko.fire(Ft.EXPIRED_RECONNECTED),this.notify(this.expiredReconnectedObservers)):(this.status=t.RECONNECTED,ko.fire(Ft.RECONNECTED))}else this.status=t.CONNECTED,this.sid=e.sid;e.enablePublish&&(this.permissions.find((e=>e==ne.WRITE))||this.permissions.push(ne.WRITE)),e.enableSubscribe&&(this.permissions.find((e=>e==ne.READ))||this.permissions.push(ne.READ)),this.reconnectingTimes=0,ko.fire(Ft.CONNECTED),this.notify(this.connectedObservers)}onAuthorizeFailed(e){this.ioSocket.disconnect(),this.status=t.CONNECT_FAILED;let s={code:e.resultCode||408,content:e.content||"Host unreachable or timeout"};this.notify(this.connectFailedObservers,s)}addConnectingObserver(e){u.isFunction(e)&&this.connectingObservers.push(e)}addConnectFailedObserver(e){u.isFunction(e)&&this.connectFailedObservers.push(e)}addExpiredReconnectedObserver(e){u.isFunction(e)&&this.expiredReconnectedObservers.push(e)}onMessage(e,t){this.ioSocket.io._callbacks.hasOwnProperty("$"+e)||this.ioSocket.io.on(e,t)}user(){let e=this.connectOptions;return e?{id:e.id,data:e.data}:null}}class Po{constructor(e){this.guidList=[],this.socket=e}offMessage(e,t){ko.off(Ft.NEW_MESSAGE+"_"+e,t)}onMessage(e,t){ko.on(Ft.NEW_MESSAGE+"_"+e,t),this.socket.onMessage(e,(t=>{this.fire(e,t)}))}fire(e,t){let s=this.filter(t);s&&ko.fire(Ft.NEW_MESSAGE+"_"+e,s)}filter(e){if("string"==typeof e&&(e=JSON.parse(e)),e.i){const t=`${e.i}_${e.r}`;if(this.guidList.findIndex((e=>e===t))>-1)return;this.guidList.unshift(t),this.guidList.length>300&&this.guidList.pop()}return e}}class _o{static init(e){this.i=new _o(e)}constructor(e){this.goeasyOptions=e}static connect(e,s){if(this.status()!==t.DISCONNECTED&&u.isObject(e)&&u.isFunction(e.onFailed))return void e.onFailed({code:408,content:"It is already connected, don't try again until disconnect() is called. "});Tt(Ye,e),ko.init();let i=this.i;X.preConnect(e),i.socket=new Oo(i.goeasyOptions,e),i.socket.vname=s,i.emitter=new Ao(i.socket),i.messageListener=new Po(i.socket),X.postConnect()}static e(e){this.i.emitter.emit(e)}static sendAck(e,t){this.i.socket.sendAck(e,t)}static status(){return this.i&&this.i.socket?this.i.socket.getStatus():t.DISCONNECTED}static on(e,t){ko.on(e,t)}static off(e,t){ko.off(e,t)}static offMessage(e,t){this.i.messageListener.offMessage(e,t)}static onMessage(e,t){this.i.messageListener.onMessage(e,t)}static disconnect(e){this.status()===t.CONNECTED||this.status()===t.RECONNECTED?this.i.socket.disconnect().then((()=>{v.onSuccess(e)})).catch((t=>{v.onFailed(e,t)})):v.onFailed(e,{code:408,content:"Host unreachable or timeout"})}static user(){return this.i.socket.user()}}_o.EVENT=Ft;class Do{static getInstance(){return Do.instance||(Do.instance=new Do),Do.instance}constructor(){this.synchronized=!0,this.onlineChanged=e=>{e.online?this.teamIds.add(e.teamId):this.teamIds.delete(e.teamId)},this.onDisconnected=()=>{this.queryMyTeamPromise=null,this.teamIds=null},this.onConnected=()=>{this.synchronized&&(this.queryMyTeamPromise=this.queryTeams())},_o.on(_o.EVENT.LOST,this.onDisconnected),_o.on(_o.EVENT.RECONNECTED,this.onConnected),_o.onMessage(Gt.CS_ONLINE_CHANGED,this.onlineChanged)}queryTeams(){return this.queryMyTeamPromise||(this.queryMyTeamPromise=new Promise(((e,t)=>{let s=new le({name:y.CS_MY_TEAMS,params:{},permission:ne.READ,singleTimeout:S.commonQuerySingle,totalTimeout:S.commonQueryTotal,fail:e=>{t(e)},success:t=>{this.teamIds=new Set(t.content),this.synchronized=!0,e(this.teamIds)}});te.Socket.e(s)}))),this.queryMyTeamPromise}myTeams(){if(this.synchronized&&this.queryMyTeamPromise)return this.teamIds;throw"please query team first."}isOnline(e,t){return b(this,void 0,void 0,(function*(){yield this.queryTeams(),v.onSuccess(t,this.teamIds.has(e))}))}online(e,t){if(!u.isObject(t.agentData)||!u.isObject(t.teamData))throw{code:400,content:"agentData and teamData require an object"};let s=new Gs(e,t.teamData,t.agentData),i=new le({name:y.CS_ONLINE,params:s,permission:ne.WRITE,singleTimeout:S.commonRequestSingle,totalTimeout:S.commonRequestTotal,fail:e=>{v.onFailed(t,e)},success:s=>{this.teamIds.add(e),v.onSuccess(t),ys.i.fire(us.CS_ONLINE_SUCCESS)}});te.Socket.e(i)}offline(e,t){let s=new Fs(e),i=new le({name:y.CS_OFFLINE,params:s,permission:ne.WRITE,singleTimeout:S.commonRequestSingle,totalTimeout:S.commonRequestTotal,fail:e=>{v.onFailed(t,e)},success:s=>{this.teamIds.delete(e),v.onSuccess(t),ys.i.fire(us.CS_OFFLINE_SUCCESS)}});te.Socket.e(i)}agents(e,t){let s=new Ls(e),i=new le({name:y.CS_AGENTS,params:s,permission:ne.READ,singleTimeout:S.commonQuerySingle,totalTimeout:S.commonQueryTotal,fail:e=>{v.onFailed(t,e)},success:e=>{e.content.forEach((e=>{e.data=JSON.parse(e.data)})),v.onSuccess(t,e)}});te.Socket.e(i)}}class xo{constructor(e){this.onMessageReceived=e=>{let t=xo.session;if(e.scene()===o.CS&&t.liveOptions){let s=e,i=t.liveOptions.customerId;t.teamId===s.teamId&&s.customerId()===i&&(t.tryUpdateStatus(s),t.liveOptions.onNewMessage(s))}},this.teamId=e,ys.i.on(us.CS_AGENT_MESSAGE_RECEIVED,this.onMessageReceived),ys.i.on(us.CS_ACCEPTED,this.onMessageReceived),ys.i.on(us.CS_ENDED,this.onMessageReceived),ys.i.on(us.CS_TRANSFER,this.onMessageReceived),_o.on(Ft.DISCONNECTED,xo.destroy)}static live(e,t){return b(this,void 0,void 0,(function*(){let s=t.customerId;Rt(s);let i=new xs(e,s);yield Do.getInstance().queryTeams();let n=new le({name:y.CS_LIVE_SESSION,params:i,permission:ne.WRITE,singleTimeout:S.commonRequestSingle,totalTimeout:S.commonRequestTotal,fail:e=>{v.onFailed(t,e)},success:s=>{xo.destroy(),this.session=new xo(e),this.session.liveOptions=t;let i=s.content.customerStatus;"ACCEPTED"===i.status&&(i.agent.data=JSON.parse(i.agent.data)),this.session.status=i,this.session.liveOptions.onStatusUpdated(this.session.status),v.onSuccess(t)}});te.Socket.e(n)}))}customerId(){return this.liveOptions.customerId}static isMyCustomer(e){let t=xo.session;if(t&&t.teamId===e.teamId&&t.customerId()===e.customerId()){let s=Do.getInstance().myTeams(),i=t.status.agent;return s.has(e.teamId)&&(!i||i.id===te.Socket.user().id)}return!0}static isMyMessage(e){let t=je.byIMMessage(e);return e.type===ls.TRANSFER&&e.payload.transferTo.id===te.Socket.user().id||this.isMyCustomer(t)}static quit(e){let t=xo.session;if(t){let s=t.liveOptions.customerId;Rt(s);let i=new xs(t.teamId,s),n=new le({name:y.CS_QUIT_LIVE,params:i,permission:ne.WRITE,singleTimeout:S.commonRequestSingle,totalTimeout:S.commonRequestTotal,fail:t=>{v.onFailed(e,t)},success:t=>{xo.destroy(),v.onSuccess(e)}});te.Socket.e(n)}}tryUpdateStatus(e){if("FREE"!==this.status.status&&this.status.sessionId>e.sessionId)return;let t;switch(e.type){case ls.ACCEPT:t=new Us,t.status="ACCEPTED",t.start=e.payload.sessionStart,t.sessionId=e.sessionId,t.agent=new c(e.senderId,e.senderData);break;case ls.END:t=new Us,t.status="FREE";break;case ls.TRANSFER:t=new Us,t.status="ACCEPTED",t.start=e.payload.sessionStart,t.sessionId=e.sessionId,t.agent=e.payload.transferTo;break;default:"FREE"===this.status.status&&(t=new Us,t.status="PENDING",t.start=e.timestamp,t.sessionId=e.sessionId)}t&&(this.status=t,this.liveOptions.onStatusUpdated(t))}}xo.destroy=()=>{let e=xo.session;e&&(ys.i.off(us.CS_AGENT_MESSAGE_RECEIVED,e.onMessageReceived),ys.i.off(us.CS_ACCEPTED,e.onMessageReceived),ys.i.off(us.CS_ENDED,e.onMessageReceived),ys.i.off(us.CS_TRANSFER,e.onMessageReceived),_o.off(Ft.DISCONNECTED,xo.destroy),xo.session=null)};class Uo extends Ds{constructor(e){super(e),this.unread=0,this.markingAmount=0}loadHistory(e,t){return b(this,void 0,void 0,(function*(){return u.isUndef(t)?t=10:t>30&&(t=30),yield this.loadServerMessages(e,t)}))}loadServerMessages(e,t){return b(this,void 0,void 0,(function*(){let s=new As(this.target.id.toString(),this.target.scene,e,t,this.target.teamId),i=yield this.remoteHistory.loadServerMessages(this.target,s);return i.userOffsets.forEach((e=>{this.userOffsets.updateOffset(e.userId,e.offset)})),i.messages}))}deleteMessages(e){return b(this,void 0,void 0,(function*(){v.onFailed(e,"Delete CS message is not supported yet")}))}initMaxMessageAndOffsets(e,t){t.forEach((e=>{this.userOffsets.updateOffset(e.userId,e.offset)})),(u.isUndef(this.acceptedMaxMessage)||this.acceptedMaxMessage.timestamp{this.userOffsets.updateOffset(e.userId,e.offset)})),this.savePendingMessage(e)}savePendingMessage(e){this.pendingMaxMessage?this.pendingMaxMessage.timestampthis.userOffsets.markingTime&&e>t&&(this.userOffsets.markingTime=e,this.markingAmount=this.unread,!0)}postMark(e){e===this.userOffsets.markingTime&&(this.unread-=this.markingAmount,this.markingAmount=0,this.userOffsets.updateOffset(te.Socket.user().id,e),ys.i.fire(us.UNREAD_AMOUNT_CHANGED,this.target))}syncMarkedMessage(e){}getMaxMessage(e){return e?this.acceptedMaxMessage:this.pendingMaxMessage}unreadAmount(e){return e?this.unread:0}existsMessage(e){return this.acceptedMaxMessage&&this.acceptedMaxMessage.messageId===e.messageId||this.pendingMaxMessage&&this.pendingMaxMessage.messageId===e.messageId}maxAcceptedMessageTime(){return this.acceptedMaxMessage?this.acceptedMaxMessage.timestamp:0}maxTime(e){let t=this.getMaxMessage(e);return t?t.timestamp:0}}class Go extends Ds{constructor(e){super(e)}deleteMessages(e){return b(this,void 0,void 0,(function*(){v.onFailed(e,"Delete CS message is not supported yet")}))}}class Fo{static init(){return Fo.instance=new Fo,Fo.instance}constructor(){this.map=new Map,this.onMessageSending=e=>{let t=je.byIMMessage(e.message),s=this.findOrCreateHistory(t);s.to=e.receiver,s.onMessageSending(e.message)},this.onMessageSendSuccess=e=>{let t=je.byIMMessage(e);this.findHistory(t).onMessageSendSuccess(e)},this.onMessageSendFailed=e=>{let t=je.byIMMessage(e);this.findHistory(t).onMessageSendFailed(e)},this.onMessageReceived=e=>{let t=je.byIMMessage(e),s=this.findOrCreateHistory(t);s.expiredTime>0&&(this.map.delete(t.toString()),s=this.findOrCreateHistory(t)),s.onMessageReceived(e)},this.onRemoteMarkRead=e=>{let t=je.byMessageReadRemoteEvent(e),s=this.findHistory(t);s&&s.syncMarkedMessage(e)},this.onRemoteMessageDeleted=e=>{let t=je.byIMMessageDeletedEvent(e),s=this.findHistory(t);s&&s.syncDeletedMessage(e.deleterId,e.times)},this.onMessageRecalled=e=>{let t=je.byConversationId(e.scene,e.conversationId),s=this.findHistory(t);s&&s.recallMessages(e)},this.onDisconnected=()=>{this.map.forEach(((e,t)=>{e.expire()}))},this.onReconnected=()=>{Ps.fire(ds.HISTORY_EXPIRED)},this.destroy=()=>{},this.initialListeners()}initialListeners(){ys.i.on(us.MESSAGE_SENDING,this.onMessageSending),ys.i.on(us.MESSAGE_SEND_SUCCESS,this.onMessageSendSuccess),ys.i.on(us.MESSAGE_SEND_FAILED,this.onMessageSendFailed),ys.i.on(us.MESSAGE_RECEIVED,this.onMessageReceived),ys.i.on(us.CS_AGENT_MESSAGE_RECEIVED,this.onMessageReceived),ys.i.on(us.CS_ACCEPTED,this.onMessageReceived),ys.i.on(us.CS_ENDED,this.onMessageReceived),ys.i.on(us.CS_TRANSFER,this.onMessageReceived),ys.i.on(us.MESSAGE_RECALLED,this.onMessageRecalled),_o.onMessage(Gt.IM_MSG_READ,this.onRemoteMarkRead),_o.onMessage(Gt.IM_MSG_DELETED,this.onRemoteMessageDeleted),_o.on(_o.EVENT.LOST,this.onDisconnected),_o.on(_o.EVENT.RECONNECTED,this.onReconnected)}loadHistory(e,t){return b(this,void 0,void 0,(function*(){Tt(ft,e);let s=this.queryToTarget(e,t),i=this.findOrCreateHistory(s);i.expiredTime>0&&(this.map.delete(s.toString()),i=this.findOrCreateHistory(s));let n=yield i.loadHistory(e.lastTimestamp,e.limit);v.onSuccess(e,{code:200,content:n})}))}queryToTarget(e,t){return e.userId?new je(o.PRIVATE,e.userId):e.groupId?new je(o.GROUP,e.groupId):(o.CS===e.type&&u.isUndef(t)&&(t=e.id),new je(e.type,e.id,t))}privateMarkAsRead(e){return b(this,void 0,void 0,(function*(){kt(e);let t=je.byScene(o.PRIVATE,e.userId);yield this.markAsRead(t,e)}))}groupMarkAsRead(e){return b(this,void 0,void 0,(function*(){Ot(e);let t=je.byScene(o.GROUP,e.groupId);yield this.markAsRead(t,e)}))}markMessageAsRead(e,t){return b(this,void 0,void 0,(function*(){Tt(mt,e),o.CS==e.type&&u.isUndef(t)&&(t=e.id);let s=je.byScene(e.type,e.id,t);yield this.markAsRead(s,e)}))}markAsRead(e,t){return b(this,void 0,void 0,(function*(){let s=this.findHistory(e);s&&(yield s.markRead()),v.onSuccess(t)}))}deleteMessage(e){return b(this,void 0,void 0,(function*(){Tt(ct,e);let t=e.messages[0],s=je.byIMMessage(t),i=this.findHistory(s);if(!i)throw{code:400,content:"No message that could be deleted"};yield i.deleteMessages(e)}))}findOrCreateHistory(e){let t=this.findHistory(e);return t||(t=e.scene===o.CS?te.Socket.user().id===e.customerId()?new Go(e):new Uo(e):new Ds(e),this.map.set(e.toString(),t),t)}static get(e){return Fo.instance.findOrCreateHistory(e)}findHistory(e){return this.map.get(e.toString())}}class Lo{constructor(e){this.top=!1,this.data=null,this.dataLoaded=!1,this.target=e}toDto(){let e=this.target.scene,t=this.target.id,s=new a;return e===o.PRIVATE?s.userId=t:e===o.GROUP?s.groupId=t:e===o.CS&&(s.id=this.target.teamId),s.type=e,s.lastMessage=this.getMaxMessage(),s.unread=this.getUnreadAmount(),s.top=this.top,s.data=this.data,s}getMaxMessage(){return Fo.get(this.target).getMaxMessage()}getUnreadAmount(){return Fo.get(this.target).unreadAmount()}maxMessageTime(){return Fo.get(this.target).maxTime()}}class Bo extends Lo{constructor(e){super(e),this.accepted=!1}toDto(){let e=new a,t=this.target.scene,s=this.target.id,i=this.target.teamId;return e.id=s,e.teamId=i,e.type=t,e.lastMessage=this.getMaxMessage(),e.unread=this.getUnreadAmount(),e.top=this.top,e.data=this.data,e.ended=this.isEnded(),e}isEnded(){let e=this.getMaxMessage(),t=e.type,s=e.payload;return t===ls.END||t===ls.TRANSFER&&s.transferTo.id!==te.Socket.user().id}getMaxMessage(){return Fo.get(this.target).getMaxMessage(this.accepted)}getUnreadAmount(){return Fo.get(this.target).unreadAmount(this.accepted)}maxMessageTime(){return Fo.get(this.target).maxTime(this.accepted)}}class qo{constructor(e,t,s,i){this.type=e,this.top=t,this.targetId=s,this.teamId=i}}class jo{constructor(e,t,s){this.type=e,this.targetId=t,this.teamId=s}}class Vo{constructor(e,t,s){this.type=e,this.targetId=t,this.teamId=s}}class Ho{top(e,t){let s=new qo(e.scene,t,e.id,e.teamId);return new Promise(((e,t)=>{let i=new le({name:y.topConversation,params:s,permission:ne.WRITE,singleTimeout:S.commonRequestSingle,totalTimeout:S.commonRequestTotal,success:s=>{200===s.code?e(s):t(s)},fail:e=>{t(e)}});te.Socket.e(i)}))}remove(e){let t=new jo(e.scene,e.id,e.teamId);return new Promise(((e,s)=>{let i=new le({name:y.removeConversation,params:t,permission:ne.WRITE,singleTimeout:S.commonRequestSingle,totalTimeout:S.commonRequestTotal,success:t=>{200==t.code?e(t):s(t)},fail:e=>{s(e)}});te.Socket.e(i)}))}query(e){return new Promise(((t,s)=>{let i=new le({name:e,params:{},permission:ne.READ,singleTimeout:S.commonQuerySingle,totalTimeout:S.commonQueryTotal,fail:e=>{s(e)},success:e=>b(this,void 0,void 0,(function*(){t(e)}))});te.Socket.e(i)}))}loadData(e){let t=new Vo(e.scene,e.id,e.teamId);return new Promise(((e,s)=>{let i=new le({name:y.imData,params:t,permission:ne.READ,singleTimeout:S.commonQuerySingle,totalTimeout:S.commonQueryTotal,success:t=>{let s=JSON.parse(t.content);e(s)},fail:e=>{s(e)}});te.Socket.e(i)}))}}Ho.instance=new Ho;class zo{constructor(){this.list=new Array,this.builder=new Ns,this.remoteConversations=Ho.instance,this.synchronized=!1,ys.i.on(us.MAX_MESSAGE_CHANGED,this.onMaxMessageChanged.bind(this)),ys.i.on(us.UNREAD_AMOUNT_CHANGED,this.onUnreadMessageChanged.bind(this)),ys.i.on(us.MAX_MESSAGE_DELETED,this.onMaxMessageDeleted.bind(this))}onUnreadMessageChanged(e){this.findConversation(e)&&this.fireUpdated()}fireUpdated(){this.debounce(this.triggerUpdate.bind(this))}triggerUpdate(){let e=this.loadLocalConversations(),t=this.getUpdatedEventName();Ps.fire(t,{unreadTotal:e.content.unreadTotal,conversations:e.content.conversations})}getUpdatedEventName(){return ds.CONVERSATIONS_UPDATED}latestConversations(e){return b(this,void 0,void 0,(function*(){this.synchronized||(yield this.loadServerConversations());let t=this.loadLocalConversations();v.onSuccess(e,t)}))}loadServerConversations(){return b(this,void 0,void 0,(function*(){var e,t;let s=this.rocketName(),i=yield this.remoteConversations.query(s);this.convertAbbrConversation(i.content);const n=null===(t=null===(e=i.content[i.content.length-1])||void 0===e?void 0:e.lmsg)||void 0===t?void 0:t.i;yield this.loadCustomConversation(n),this.synchronized=!0}))}loadCustomConversation(e){return b(this,void 0,void 0,(function*(){if(void 0!==_s.instance){const t=yield _s.instance.loadConversations(te.Socket.user().id,e);for(const e of t){e.lastMessage=_s.convertAbstractMessage(e.lastMessage);const t=e.lastMessage,s=je.byIMMessage(t);let i=this.findConversation(s);u.isUndef(i)&&(i=new Lo(s),i.dataLoaded=!0,i.top=e.top,i.data=e.data,this.insertOne(i)),Fo.get(s).initMaxMessageAndOffsets(t,[]),this.correctPosition(i)}}}))}rocketName(){return y.imLastConversations}convertAbbrConversation(e){let t=e;for(const e of t){let t=e.t,s=e.top,i=e.d?JSON.parse(e.d):{},n=e.userOffsets;e.lmsg.t=t;let r=e.lmsg,a=this.builder.build(r),c=je.byIMMessage(a),l=this.findConversation(c);u.isUndef(l)?(l=this.buildByAbbr(e,a),this.insertOne(l)):(l.top=s,l.data=i),c.scene===o.GROUP&&(a.x=1),Fo.get(c).initMaxMessageAndOffsets(a,n),this.correctPosition(l)}}onMaxMessageDeleted(e){this.removeConversation(e)}onMaxMessageChanged(e){return b(this,void 0,void 0,(function*(){const t=e.message;if(t.scene()===o.CS){let e=t;if(te.Socket.user().id!=e.customerId()&&(!1===e.accepted||e.type===ls.ACCEPT&&e.senderId!=te.Socket.user().id))return}yield this.saveOrUpdateConversation(e)}))}saveOrUpdateConversation(e){return b(this,void 0,void 0,(function*(){const t=e.message;let s=t.status,i=je.byIMMessage(t),n=this.findConversation(i);u.isUndef(n)&&s!==r.FAIL&&(n=this.buildByMessage(t),this.insertOne(n),s===r.SUCCESS&&(n.data=yield this.remoteConversations.loadData(i),n.dataLoaded=!0)),s===r.SENDING&&(n.data=e.receiver,n.dataLoaded=!0),n&&n.dataLoaded&&(this.correctPosition(n),this.fireUpdated())}))}debounce(e){clearTimeout(this.timer),this.timer=setTimeout((()=>{e()}),500)}loadLocalConversations(){let e=0,t=new Array;for(const s of this.list)if(s.dataLoaded&&s.getMaxMessage()){e+=s.getUnreadAmount();let i=s.toDto();t.push(i)}return{code:200,content:{unreadTotal:e,conversations:t}}}findConversationIndex(e){return this.list.findIndex((t=>e.toString()===t.target.toString()))}findConversation(e){let t=this.findConversationIndex(e);return this.list[t]}removeLocalConversation(e){let t=this.findConversationIndex(e.target);this.list.splice(t,1)}insertOne(e){zo.sortedInserter.insert(this.list,e)}correctPosition(e){this.removeLocalConversation(e),this.insertOne(e)}removeConversation(e){let t=this.findConversation(e);t&&(this.removeLocalConversation(t),this.fireUpdated())}top(e,t,s){return b(this,void 0,void 0,(function*(){if(!u.isBoolean(t))throw new Error("top must be boolean.");let i=this.findConversation(e);if(!i)throw new Error("conversation does not exist.");i.top!=t&&(yield this.remoteConversations.top(e,t),i.top=t,this.correctPosition(i)),this.fireUpdated(),v.onSuccess(s)}))}remove(e,t){return b(this,void 0,void 0,(function*(){let s=this.findConversation(e);if(!s)throw new Error("conversation does not exist.");if(s instanceof Bo&&!s.isEnded())throw new Error("CS conversation can only be deleted after it ends");yield this.remoteConversations.remove(e),this.removeLocalConversation(s),this.fireUpdated(),v.onSuccess(t)}))}buildByAbbr(e,t){let s,i=je.byIMMessage(t);if(e.t===o.CS){let e=t;te.Socket.user().id===e.customerId()?s=new Lo(i):(s=new Bo(i),s.accepted=e.accepted)}else s=new Lo(i);return s.dataLoaded=!0,s.top=e.top,s.data=e.d?JSON.parse(e.d):{},s}buildByMessage(e){let t,s=je.byIMMessage(e);if(e.scene()===o.CS){let i=e;te.Socket.user().id===i.customerId()?t=new Lo(s):(t=new Bo(s),t.accepted=i.accepted)}else t=new Lo(s);return t}}zo.sortedInserter=new class extends vs{compare(e,t){if(e.target.scene===t.target.scene&&e.target.id===t.target.id)return 0;let s;if(e.top==t.top){let i=e.maxMessageTime();s=t.maxMessageTime()-i}else s=e.top?-1:1;return s>0?1:-1}};class Wo extends J{static init(){return this.module=new Wo,this.module.name=this.GIM_MODULE_NAME,this.initGN(),this.module}static initGN(){he.addAssembler(new class{assemble(e){let t={messageId:e.id,timestamp:e.tm,type:e.t,senderId:e.sid,toType:e.tt};return e.tt===o.GROUP&&(t.groupId=e.gid),t}support(e){return!!e.sid}})}postConnect(){Yr.init()}static check(){if(!this.module)throw{code:400,content:"IM not initialized. Please include 'IM' in the 'modules' during GoEasy initialization."}}}Wo.GIM_MODULE_NAME="GIM";class Jo extends zo{constructor(){super(),this.expired=!1,ys.i.on(us.CS_ONLINE_SUCCESS,this.onCSOnlineSuccess.bind(this)),ys.i.on(us.CS_OFFLINE_SUCCESS,this.onCSOfflineSuccess.bind(this)),_o.on(_o.EVENT.LOST,this.onDisconnected.bind(this)),_o.on(_o.EVENT.RECONNECTED,this.onConnected.bind(this))}onMaxMessageChanged(e){return b(this,void 0,void 0,(function*(){const t=e.message;if(t.scene()===o.CS){let s=t;if(s.customerId()!=te.Socket.user().id&&(!1===s.accepted||s.type===ls.ACCEPT))if(ls.ACCEPT===t.type){let e=je.byIMMessage(t);this.removeConversation(e)}else yield this.saveOrUpdateConversation(e)}}))}latestConversations(e){const t=Object.create(null,{latestConversations:{get:()=>super.latestConversations}});return b(this,void 0,void 0,(function*(){let s=this.synchronized;yield t.latestConversations.call(this,e),this.list.length>0&&!s&&this.fireUpdated()}))}onUnreadMessageChanged(e){}onCSOnlineSuccess(){return b(this,void 0,void 0,(function*(){yield this.loadServerConversations(),this.fireUpdated()}))}onCSOfflineSuccess(){this.list=[],this.fireUpdated()}getUpdatedEventName(){return ds.PENDING_CONVERSATIONS_UPDATED}rocketName(){return y.CS_PENDING_CONVERSATION}convertAbbrConversation(e){return b(this,void 0,void 0,(function*(){let t=e;for(const e of t){e.lastMessage.t=o.CS;let t=e.customerData,s=e.lastMessage,i=e.userOffsets,n=t?JSON.parse(t):{},r=this.builder.build(s),a=je.byIMMessage(r),c=this.findConversation(a);u.isUndef(c)&&(c=new Bo(a),c.accepted=r.accepted,c.dataLoaded=!0,this.insertOne(c)),c.top=!1,c.data=n,Fo.get(a).initPendingMaxMessageAndOffsets(r,i),this.correctPosition(c)}}))}onDisconnected(){this.expired=!0}onConnected(){return b(this,void 0,void 0,(function*(){this.expired&&Wo.module&&Wo.module.active&&(this.expired=!1,this.list=[],yield this.loadServerConversations(),this.fireUpdated())}))}}class Xo{constructor(){this.conversations=new zo,this.pendingConversations=new Jo}latestConversations(e){this.conversations.latestConversations(e)}latestPendingConversations(e){this.pendingConversations.latestConversations(e)}topPrivateConversation(e){let t=je.byScene(o.PRIVATE,e.userId);this.conversations.top(t,e.top,e)}topGroupConversation(e){let t=je.byScene(o.GROUP,e.groupId);this.conversations.top(t,e.top,e)}topConversation(e){let t=e.conversation;this.validateConversationDTO(t);let s=je.byConversationDTO(t);this.conversations.top(s,e.top,e)}removePrivateConversation(e){let t=je.byScene(o.PRIVATE,e.userId);this.conversations.remove(t,e)}removeGroupConversation(e){let t=je.byScene(o.GROUP,e.groupId);this.conversations.remove(t,e)}removeConversation(e){let t=e.conversation;this.validateConversationDTO(t);let s=je.byConversationDTO(t);this.conversations.remove(s,e)}validateConversationDTO(e){if(!(e instanceof a))throw new Error("Incorrect conversation object.");{let t=e.lastMessage;if(t instanceof We&&t.customerId()!==te.Socket.user().id&&!1===t.accepted)throw new Error("pending conversation cannot be topped or removed.")}}}class $o{constructor(){this.builder=new Ns,te.Socket.onMessage(Gt.imMessage,this.onMessageReceived.bind(this))}sendAck(e){te.Socket.sendAck("imAck",{publishGuid:e})}createNotification(e){const t=te.N.supportAppNotification();if(!u.isObject(e.nt)||e.s===te.Socket.user().id||!t)return;let s={id:e.i,tm:e.ts,t:e.mt,sid:e.s,tt:e.t};s.tt===o.GROUP&&(s.gid=e.r),te.N.createLocalNotification(e.nt.t,e.nt.c,s,e.nt.sound,e.nt.badge)}}class Yo extends $o{constructor(){super(),te.Socket.onMessage(Gt.IM_MSG_SYNC,this.onSyncMessageReceived.bind(this)),te.Socket.onMessage(Gt.IM_MSG_NEW,this.onNewMessageReceived.bind(this))}onMessageReceived(e){if(e.t!==o.CS){let t=this.builder.build(e);this.sendAck(t.messageId);let s=je.byIMMessage(t),i=s.scene;Fo.get(s).existsMessage(t)||(this.createNotification(e),ys.i.fire(us.MESSAGE_RECEIVED,t),i===o.PRIVATE?Ps.fire(ds.PRIVATE_MESSAGE_RECEIVED,t):i===o.GROUP&&Ps.fire(ds.GROUP_MESSAGE_RECEIVED,t))}}onSyncMessageReceived(e){this.sendAck(e.i),e.rs.forEach((t=>{const s=Object.assign({},e);s.t=o.PRIVATE,s.r=t,s.s=te.Socket.user().id,delete s.rs;let i=this.builder.build(s),n=je.byIMMessage(i),r=n.scene;Fo.get(n).existsMessage(i)||(this.createNotification(s),ys.i.fire(us.MESSAGE_RECEIVED,i),r===o.PRIVATE?Ps.fire(ds.PRIVATE_MESSAGE_RECEIVED,i):r===o.GROUP&&Ps.fire(ds.GROUP_MESSAGE_RECEIVED,i))}))}onNewMessageReceived(e){let t=this.builder.build(e);this.sendAck(t.messageId);let s=je.byIMMessage(t),i=s.scene;Fo.get(s).existsMessage(t)||(this.createNotification(e),ys.i.fire(us.MESSAGE_RECEIVED,t),i===o.PRIVATE?Ps.fire(ds.PRIVATE_MESSAGE_RECEIVED,t):i===o.GROUP&&Ps.fire(ds.GROUP_MESSAGE_RECEIVED,t))}}class Qo{constructor(){this.subscribedGroups=[],te.Socket.on(Ft.EXPIRED_RECONNECTED,this.expiredResubscribeGroups.bind(this))}expiredResubscribeGroups(){this.subscribedGroups.forEach((e=>b(this,void 0,void 0,(function*(){yield this.doSubscribeGroup(e)}))))}subscribe(e){_t(e);let t=new Ko(e.groupIds,e.accessToken);this.doSubscribeGroup(t).then((()=>{this.subscribedGroups.push(t),v.onSuccess(e,{code:200,content:"ok"})})).catch((t=>{v.onFailed(e,{code:t.resultCode||408,content:t.content||"Failed to subscribe group message"})}))}doSubscribeGroup(e){return new Promise(((t,s)=>{let i=e.groupIds.toString().split(","),n=new le({name:y.subscribeGroups,params:{groupIds:i,at:e.accessToken},permission:ne.WRITE,singleTimeout:S.commonRequestSingle,totalTimeout:S.commonRequestTotal,success:()=>{t()},fail:e=>{s(e)}});te.Socket.e(n)}))}unsubscribe(e){Ot(e),e.groupId=e.groupId.toString();let t=new le({name:y.unsubscribeGroup,params:{groupId:e.groupId},permission:ne.READ,singleTimeout:S.commonRequestSingle,totalTimeout:S.commonRequestTotal,success:()=>{this.deleteSubscriptionByGroupId(e.groupId),v.onSuccess(e,{code:200,content:"ok"})},fail:t=>{v.onFailed(e,{code:t.resultCode||408,content:t.content||"Failed to unsubscribe group message"})}});te.Socket.e(t)}deleteSubscriptionByGroupId(e){this.subscribedGroups=this.subscribedGroups.filter((t=>{const s=t.groupIds.findIndex((t=>t==e));return s>-1&&t.groupIds.splice(s,1),t.groupIds.length>0}))}}class Ko{constructor(e,t){this.groupIds=e,this.accessToken=t}}class Zo{constructor(){this.newMessageReceived=e=>{let t=null;e.c&&(t=JSON.parse(e.c)),t&&t.events&&t.events.map((e=>{let s=e.userData?JSON.parse(e.userData):{},i={time:e.time,action:e.action,groupOnlineCount:t.userAmount,groupId:t.groupId,id:e.userId,data:s};Ps.fire(ds.GROUP_PRESENCE,i)}))},_o.onMessage(Gt.groupPresence,this.newMessageReceived)}presence(e){_t(e),e.groupIds.toString().split(",");let t={groupIds:e.groupIds};this.emitRocket(y.subscribeGroupPresence,t,(()=>{v.onSuccess(e,{code:200,content:"ok"})}),(t=>{v.onFailed(e,{code:t.code||408,content:t.content||"Failed to subscribe group message"})}),S.commonRequestSingle,S.commonRequestTotal)}unPresence(e){Tt(Nt,e),e.groupId=e.groupId.toString();let t={groupId:e.groupId};this.emitRocket(y.unsubscribeGroupPresence,t,(()=>{v.onSuccess(e,{code:200,content:"ok"})}),(t=>{v.onFailed(e,{code:t.code||408,content:t.content||"Failed to unsubscribe presence"})}),S.commonRequestSingle,S.commonRequestTotal)}emitRocket(e,t,s,i,n,o){let r=new le({name:e,params:t,singleTimeout:n,totalTimeout:o,permission:ne.WRITE,success:s,fail:i});te.Socket.e(r)}}class er{get(e){Ot(e),e.groupId=e.groupId.toString();let t=new le({name:y.imGroupOnlineCount,params:{groupId:e.groupId},permission:ne.READ,singleTimeout:S.commonQuerySingle,totalTimeout:S.commonQueryTotal,fail:function(t){v.onFailed(e,t||{code:408,content:"Failed to query online group users"})},success:function(t){200==t.code?v.onSuccess(e,t):v.onFailed(e,t)}});te.Socket.e(t)}}class tr{doHereNow(e,t,s){let i=new le({name:e,params:t,permission:ne.READ,singleTimeout:S.commonQuerySingle,totalTimeout:S.commonQueryTotal,fail:e=>{v.onFailed(s,e)},success:e=>{let t=e.content;e.content=t.map((e=>{let t=e.userData?JSON.parse(e.userData):{};return{id:e.userId,data:t}})),v.onSuccess(s,e)}});te.Socket.e(i)}}class sr extends tr{hereNow(e){Ot(e),e.groupId=e.groupId.toString();let t={groupId:e.groupId};this.doHereNow(y.imGroupHereNow,t,e)}}class ir{constructor(){this.newMessageReceived=e=>{let t=[];e.c&&(t=JSON.parse(e.c).events||[]),t.map((e=>{let t=e.userData?JSON.parse(e.userData):{},s={time:e.time,action:e.action,id:e.userId,data:t};Ps.fire(ds.USER_PRESENCE,s)}))},_o.onMessage(Gt.userPresence,this.newMessageReceived)}presence(e){Pt(e),e.userIds.toString().split(",");let t={userIds:e.userIds};this.emitRocket(y.subscribeUserPresence,t,(()=>{v.onSuccess(e,{code:200,content:"ok"})}),(t=>{v.onFailed(e,{code:t.code||408,content:t.content||"Failed to subscribe group message"})}),S.commonRequestSingle,S.commonRequestTotal)}unPresence(e){kt(e),e.userId=e.userId.toString();let t={userId:e.userId};this.emitRocket(y.unsubscribeUserPresence,t,(()=>{v.onSuccess(e,{code:200,content:"ok"})}),(t=>{v.onFailed(e,{code:t.code||408,content:t.content||"Failed to unsubscribe presence"})}),S.commonRequestSingle,S.commonRequestTotal)}emitRocket(e,t,s,i,n,o){let r=new le({name:e,params:t,singleTimeout:n,totalTimeout:o,permission:ne.WRITE,success:s,fail:i});te.Socket.e(r)}}class nr extends tr{hereNow(e){Pt(e),e.userIds.toString().split(","),this.doHereNow(y.imHereNow,e,e)}}const or=new class{fileExtension(e,t){if(u.isString(e))try{let s=e.split(t);return s[s.length-1]}catch(e){throw Error(e)}}};class rr{}class ar extends rr{constructor(){super(...arguments),this.contentType="",this.name="",this.size=0,this.url=""}}class cr extends ar{constructor(){super(...arguments),this.width=0,this.height=0}}class ur{build(e){let t=this.create();return this.setPayload(e,t),t}}class lr extends ur{create(){return new ar}setPayload(e,t){let s=t,i=e.createOptions.file;s.url=i.path,s.name=i.name,s.size=i.size,s.contentType=i.type,e.complete=Promise.resolve()}}class dr extends lr{create(){return new cr}setPayload(e,t){super.setPayload(e,t);let s=e.createOptions.file,i=t,n=s.path||s.tempFilePath,o=u.isEmpty(s.name)||void 0===s.name?n:s.name;i.name="wx-image."+or.fileExtension(o,"."),i.contentType="image/"+or.fileExtension(o,"."),i.url=n,i.size=s.size,e.complete=new Promise(((e,t)=>{wx.getImageInfo({src:i.url,success(t){i.width=t.width,i.height=t.height,e()},fail(e){t(e)}})}))}}class hr extends ar{constructor(){super(...arguments),this.duration=0}}class pr extends lr{create(){return new hr}setPayload(e,t){super.setPayload(e,t);let s=e.createOptions.file,i=t,n=s.tempFilePath,o=u.isEmpty(s.name)||null==s.name?n:s.name,r=s.duration,a=s.fileSize;i.url=n,i.size=a,i.duration=r/1e3,i.name="wx-audio."+or.fileExtension(o,"."),i.contentType="audio/"+or.fileExtension(o,"."),e.complete=Promise.resolve()}}class mr extends rr{constructor(){super(...arguments),this.text=""}}class fr extends ur{create(){return new mr}setPayload(e,t){let s=t,i=e.createOptions;s.text=i.text,e.complete=Promise.resolve()}}class gr extends rr{constructor(){super(...arguments),this.video=new Sr,this.thumbnail=new yr}}class yr{constructor(){this.name="",this.url="",this.width=0,this.height=0,this.contentType=""}initURL(e){d.currentPlatform()===l.BROWSER&&this.htmlUrl(e)}htmlUrl(e){let t=document.createElement("canvas");t.width=e.videoWidth,t.height=e.videoHeight,t.getContext("2d").drawImage(e,0,0,t.width,t.height),this.url=t.toDataURL("image/png")}}class Sr{constructor(){this.name="",this.url="",this.width=0,this.height=0,this.contentType="",this.size=0,this.duration=0}}class Er extends ur{create(){return new gr}setPayload(e,t){let s=e.createOptions.file,i=t,n=i.video,o=i.thumbnail,{duration:r,height:a,size:c,tempFilePath:l,thumbTempFilePath:d,width:h,name:p=""}=s,m=u.isEmpty(p)?l:p;n.contentType="video/"+or.fileExtension(m,"."),n.name="wx-video."+or.fileExtension(m,"."),n.url=l,n.width=o.width=h,n.height=o.height=a,n.size=c,n.duration=r,o.url=d,o.contentType="image/jpg",o.name="wx-thumbnail.jpg",e.complete=Promise.resolve()}}class vr extends ur{create(){return new ar}setPayload(e,t){let s=t,i=e.createOptions.file;s.url=i.fullPath||i.path,s.name=i.name,s.size=i.size,s.contentType=i.type,i.type,e.complete=Promise.resolve()}}class br extends vr{create(){return new cr}setPayload(e,t){let s=t,i=e.createOptions.file;s.url=i.path,s.size=i.size;let n=u.isEmpty(i.name)||void 0===i.name?i.path:i.name;s.contentType="image/"+or.fileExtension(n,"."),s.name="uni-image."+or.fileExtension(n,"."),e.complete=new Promise(((e,t)=>{uni.getImageInfo({src:i.path,success(t){s.width=t.width,s.height=t.height,e()},fail(e){t(e)}})}))}}class Cr extends vr{create(){return new hr}setPayload(e,t){let s=e.createOptions,i=t,n=s.file,o=n.tempFilePath,r=u.isEmpty(n.name)||null==n.name?o:n.name;i.url=o,i.name="uni-audio."+or.fileExtension(r,"."),i.contentType="audio/"+or.fileExtension(r,"."),e.complete=new Promise(((e,t)=>{uni.getFileInfo({filePath:o,success:n=>{let r=n.size;if(i.size=r,0===r)e();else if(u.isDef(s.file.duration))i.duration=s.file.duration/1e3,e();else{const s=uni.createInnerAudioContext();s.src=o,s.onCanplay((function(n){n.errCode?(s.destroy(),t(n)):(i.duration=s.duration,s.destroy(),e())})),s.onError((i=>{s.destroy(),-99===i.errCode?e():t(i)}))}},fail(e){t(e)}})}))}}class Mr extends ur{create(){return new gr}setPayload(e,t){let s=e.createOptions.file,i=t,n=i.video,o=i.thumbnail,{duration:r,height:a,size:c,tempFilePath:l,width:d,name:h=""}=s,p=u.isEmpty(h)?l:h;n.size=c,n.width=d,n.height=a,n.url=l,n.duration=r,n.contentType="video/"+or.fileExtension(p,"."),n.name="uni-video."+or.fileExtension(p,"."),o.url=l,o.height=200,o.width=Number((n.width*o.height/n.height).toFixed(0)),o.contentType="image/jpg",o.name="uni-thumbnail.jpg",e.complete=Promise.resolve()}}class wr extends ur{create(){return new ar}setPayload(e,t){let s=t,i=e.createOptions.file,n=window.URL||window.webkitURL;s.url=n.createObjectURL(i),s.name=i.name,s.size=i.size,s.contentType=i.type,e.complete=Promise.resolve()}}class Ir extends wr{create(){return new cr}setPayload(e,t){super.setPayload(e,t);let s=e.createOptions.file,i=t,n=window.URL||window.webkitURL,o=new Image;o.src=n.createObjectURL(s),e.complete=new Promise(((e,t)=>{o.onload=function(){i.width=o.width,i.height=o.height,n.revokeObjectURL(o.src),e()},o.onerror=function(e){n.revokeObjectURL(o.src),t(e)}}))}}class Nr extends wr{create(){return new hr}setPayload(e,t){super.setPayload(e,t);let s=e.createOptions.file,i=t,n=window.URL||window.webkitURL,o=document.createElement("audio");o.src=n.createObjectURL(s),e.complete=new Promise(((e,t)=>{o.onloadedmetadata=()=>{i.duration=o.duration,n.revokeObjectURL(o.src),e()},o.onerror=e=>{n.revokeObjectURL(o.src),t(e)}}))}}class Tr extends ur{create(){return new gr}setPayload(e,t){let s=e.createOptions.file,i=t,n=i.video,o=i.thumbnail,r=window.URL||window.webkitURL,a=document.createElement("video");a.src=r.createObjectURL(s),n.size=s.size,n.name=s.name,n.contentType=s.type,n.url=a.src,o.name=s.name,o.contentType="image/jpg",e.complete=new Promise(((e,t)=>{a.onloadedmetadata=()=>{n.duration=a.duration,n.width=a.videoWidth,n.height=a.videoHeight,o.width=a.videoWidth,o.height=a.videoHeight,o.url=this.getThumbnailUrl(a),r.revokeObjectURL(a.src),e()},a.onerror=function(e){r.revokeObjectURL(a.src),t(e)}}))}getThumbnailUrl(e){let t=document.createElement("canvas");return t.width=e.videoWidth,t.height=e.videoHeight,t.getContext("2d").drawImage(e,0,0,t.width,t.height),t.toDataURL("image/png")}}class Rr extends ur{create(){return new gr}setPayload(e,t){let s=e.createOptions.file,i=t,n=i.video,o=i.thumbnail,{duration:r,height:a,size:c,tempFilePath:u,tempVideoThumbPath:l,width:d}=s;n.size=c,n.width=d,n.height=a,n.url=u,n.duration=r,n.contentType="video/mp4",n.name="ali-video.mp4",o.url=l,o.height=200,o.width=Number((n.width*o.height/n.height).toFixed(0)),o.contentType="image/jpg",o.name="ali-thumbnail.jpg",e.complete=Promise.resolve()}}class Ar extends ur{create(){return new ar}setPayload(e,t){e.complete=Promise.reject("Alipay not support file message.")}}class kr extends Ar{create(){return new cr}setPayload(e,t){let s=t,i=e.createOptions.file;s.url=i.path,s.size=i.size,e.complete=new Promise(((e,t)=>{my.getImageInfo({src:i.path,success(t){s.width=t.width,s.height=t.height,s.contentType="image/"+t.type,s.name="ali-image."+t.type,e()},fail(e){t(e)}})}))}}class Or extends Ar{create(){return new hr}setPayload(e,t){let s=t,i=e.createOptions.file,n=i.tempFilePath;s.url=n,s.name="ali-audio."+or.fileExtension(n,"."),s.contentType="audio/"+or.fileExtension(n,"."),s.duration=i.duration,s.size=i.fileSize,e.complete=Promise.resolve()}}class Pr extends ur{create(){return new gr}setPayload(e,t){let s=e.createOptions.file,i=t,n=i.video,o=i.thumbnail,{duration:r,height:a,size:c,tempFilePath:u,thumbTempFilePath:l,width:d}=s;n.size=c,n.width=d,n.height=a,n.url=u,n.duration=r,n.contentType="video/"+or.fileExtension(u,"."),n.name="baidu-video."+or.fileExtension(u,"."),o.url=l,o.height=200,o.width=Number((n.width*o.height/n.height).toFixed(0)),o.contentType="image/"+or.fileExtension(l,"."),o.name="baidu-thumbnail."+or.fileExtension(u,"."),e.complete=Promise.resolve()}}class _r extends ur{create(){return new ar}setPayload(e,t){e.complete=Promise.reject("Baidu app not support file message.")}}class Dr extends _r{create(){return new hr}setPayload(e,t){let s=t,i=e.createOptions.file,n=i.tempFilePath;s.url=n,s.name="baidu-audio."+or.fileExtension(n,"."),s.contentType="audio/"+or.fileExtension(n,"."),s.duration=i.duration/1e3,s.size=i.fileSize,e.complete=Promise.resolve()}}class xr extends _r{create(){return new cr}setPayload(e,t){let s=t,i=e.createOptions.file;s.url=i.path,s.size=i.size,e.complete=new Promise(((e,t)=>{swan.getImageInfo({src:i.path,success(t){s.width=t.width,s.height=t.height,s.contentType="image/"+t.type,s.name="baidu-image."+t.type,e()},fail(e){t(e)}})}))}}class Ur extends ur{create(){return new ar}setPayload(e,t){let s=t,i=e.createOptions.file;const{uri:n,type:o,size:r,name:a}=i;s.url=decodeURIComponent(n),s.name=a,s.size=r,s.contentType=o,e.complete=Promise.resolve()}}class Gr extends Ur{create(){return new cr}setPayload(e,t){let s=t,i=e.createOptions.file;const{uri:n,fileSize:o,type:r,fileName:a,width:c,height:u}=i;s.url=n,s.size=o,s.contentType=r,s.name=a,s.width=c,s.height=u,e.complete=Promise.resolve()}}class Fr extends ur{create(){return new gr}setPayload(e,t){let s=e.createOptions.file;const i=t,{video:n,thumbnail:o}=i,{uri:r,type:a,fileSize:c,fileName:u,duration:l,height:d,width:h}=s;n.size=c,n.width=h,n.height=d,n.url=r,n.duration=l,n.contentType=a,n.name=u,o.url=r,o.name="rn-thumbnail.jpg",o.height=200,o.width=Number((n.width*o.height/n.height).toFixed(0)),o.contentType="image/jpg",e.complete=Promise.resolve()}}class Lr extends Ur{create(){return new hr}setPayload(e,t){let s=e.createOptions,i=t,n=s.file;const{uri:o,name:r,duration:a,size:c}=n;i.url=o,i.duration=Number((a/1e3).toFixed(2)),i.size=c,i.name=r||"rn-audio."+or.fileExtension(o,"."),i.contentType="audio/"+or.fileExtension(o,"."),s.file={uri:o,type:i.contentType,name:i.name},e.complete=Promise.resolve()}}class Br extends rr{}class qr extends ur{create(){return new Br}setPayload(e,t){let s=e.createOptions;t.payload=s.payload,e.complete=Promise.resolve()}}class jr{constructor(e,t){this.type=e,this.createOptions=t}}class Vr{constructor(){this.framework=s.currentFramework(),this.platform=d.currentPlatform(),this.payloadBuilders={[e.UNIAPP]:{image:new br,file:new vr,audio:new Cr,video:new Mr,text:new fr},[l.MP_WX]:{image:new dr,file:new lr,audio:new pr,video:new Er,text:new fr},[l.BROWSER]:{image:new Ir,file:new wr,audio:new Nr,video:new Tr,text:new fr},[l.MP_ALI]:{image:new kr,file:new Ar,audio:new Or,video:new Rr,text:new fr},[l.MP_BAIDU]:{image:new xr,file:new _r,audio:new Dr,video:new Pr,text:new fr},[e.REACT_NATIVE]:{image:new Gr,file:new Ur,audio:new Lr,video:new Fr,text:new fr},[l.MP_BYTE]:{image:new dr,file:new lr,audio:new pr,video:new Er,text:new fr}}}buildMessage(t,s){this.validate(s,t);const i=this.framework===e.UNKNOWN?this.platform:this.framework;let n=this.payloadBuilders[i][t],o=new jr(t,s);if(n){let e=n.build(o);o.payload=e}else{let e=(new qr).build(o);o.payload=e.payload}let r=this.build(o);return o.complete.then((()=>{v.onSuccess(s,r)})).catch((e=>{v.onFailed(s,e)})),r}build(e){let t,s=e.type,i=e.payload,n=e.createOptions.to,a=n.type;return a===o.GROUP?(t=new ze,t.senderData=te.Socket.user().data,n.id&&(t.groupId=n.id)):a===o.PRIVATE?(t=new He,t.read=!1,n.id&&(t.receiverId=n.id)):a===o.CS&&(t=new We,t.to=n.id.toString(),t.teamId=n.id.toString(),t.senderData=te.Socket.user().data),t.senderId=te.Socket.user().id,t.messageId=ie.get(),t.payload=i,t.timestamp=Date.now(),t.type=s,t.recalled=!1,t.status=r.NEW,t.buildOptions=e,t}validate(e,t){var s;s=Object.assign(Object.assign({},e),{type:t}),Tt(rt,s)}}class Hr extends $o{onMessageReceived(e){if(e.t===o.CS){let t=this.builder.build(e);this.sendAck(t.messageId);let s=je.byIMMessage(t);if(!Fo.get(s).existsMessage(t)){if(t.customerId()===te.Socket.user().id)this.createNotification(e),ys.i.fire(us.MESSAGE_RECEIVED,t),Ps.fire(ds.CS_MESSAGE_RECEIVED,t);else{Do.getInstance().queryTeams().then((()=>{xo.isMyMessage(t)&&this.createNotification(e),ys.i.fire(us.CS_AGENT_MESSAGE_RECEIVED,t)}))}}}}}class zr{constructor(e){this.times=new Array;let t=e[0],s=je.byIMMessage(t);this.scene=s.scene,this.targetId=s.id,e.forEach((e=>{this.times.push(e.timestamp)})),this.times.sort(((e,t)=>e{ys.i.fire(us.MESSAGE_RECALLED,e)},_o.onMessage(Gt.IM_MSG_RECALLED,this.onRemoteRecalled)}recallMessage(e){return b(this,void 0,void 0,(function*(){Tt(at,e);let t=e.messages;yield this.recallServerMessages(t);let s=t[0],i=s.scene(),n=this.toConversationId(i,s.senderId,s.targetId()),o={id:te.Socket.user().id,data:te.Socket.user().data},r=t.map((e=>e.timestamp)),a=new Os(i,n,o,r);ys.i.fire(us.MESSAGE_RECALLED,a),v.onSuccess(e)}))}toConversationId(e,t,s){if(o.PRIVATE===e)return function(e,t){return e.localeCompare(t)>0?`${e}:${t}`:`${t}:${e}`}(t,s);if(o.GROUP===e)return s;throw{code:400,content:`scene: ${e} not support`}}recallServerMessages(e){let t=new zr(e);return 0===t.times.length?Promise.resolve():new Promise(((e,s)=>{let i=new le({name:y.IM_RECALL_MESSAGE,params:t,permission:ne.WRITE,singleTimeout:S.commonRequestSingle,totalTimeout:S.commonRequestTotal,fail:e=>{s(e)},success:t=>{200===t.code?e(t):s(t)}});te.Socket.e(i)}))}}class Jr{constructor(){this.map=new Map}getData(e){return this.map.get(e)}setData(e,t){this.map.set(e,t)}}class Xr{constructor(){this.dataCache=new Jr}static init(){this.i=new Xr}getData(...e){return b(this,void 0,void 0,(function*(){let t=[],s=new Map;return e.forEach((e=>{let i=this.dataCache.getData(e);i?s.set(e,i):t.push(e)})),0===t.length||(yield this.fetchData(...t)).forEach(((e,t)=>{this.dataCache.setData(t,e),s.set(t,e)})),Promise.resolve(s)}))}fetchData(...e){return new Promise(((t,s)=>{let i=new le({name:y.MD_CMD,permission:ne.READ,singleTimeout:S.commonRequestSingle,totalTimeout:S.commonRequestTotal,params:{name:"GET",data:{ids:e}},success:e=>{if(200===e.code){const s=new Map;e.content.forEach((e=>{s.set(e.id,e.data)})),t(s)}else s(e)},fail:e=>{s(e)}});te.Socket.e(i)}))}setData(e,t){this.dataCache.setData(e,t)}}class $r{constructor(){ys.i.on(us.MESSAGE_RECEIVED,this.onMessageReceived),ys.i.on(us.MESSAGE_SENDING,this.onMessageSending)}onMessageSending(e){let t=je.byIMMessage(e.message);t.scene===o.PRIVATE&&Xr.i.setData(t.id,e.receiver)}onMessageReceived(e){if(je.byIMMessage(e).scene===o.GROUP){let t=e;Xr.i.setData(t.senderId,t.senderData)}}}class Yr{constructor(){this._iMReceiver=new Yo,this.csMessageReceiver=new Hr,this.memberDataUpdator=new $r,this._userHereNow=new nr,this.goEasyUploader=new cs,this._groupHereNow=new sr,this._groupOnlineCount=new er,this.groupMessageSubscriber=new Qo,this.messageBuilder=new Vr,this.messageSender=new Es,this.recaller=new Wr,this._groupPresenceSubscriber=new Zo,this._userPresenceSubscriber=new ir,this.conversations=new Xo,this.histories=Fo.init()}static init(){ys.init(),Ps.init(),Yr.instance=new Yr}static i(){if(Yr.instance)return Yr.instance;throw Error("Please connect first.")}validateModules(){if(_o.status()===t.DISCONNECTED)throw Error("Please call connect() first.");Wo.check()}catch(e,t){return b(this,void 0,void 0,(function*(){try{this.validateModules(),yield e()}catch(e){v.onFailed(t,e)}}))}on(e,t){Ps.on(e,t)}off(e,t){Ps.off(e,t)}createTextMessage(e){return this.validateModules(),this.messageBuilder.buildMessage(Ee.TEXT,e)}createImageMessage(e){return this.validateModules(),this.messageBuilder.buildMessage(Ee.IMAGE,e)}createFileMessage(e){return this.validateModules(),this.messageBuilder.buildMessage(Ee.FILE,e)}createAudioMessage(e){return this.validateModules(),this.messageBuilder.buildMessage(Ee.AUDIO,e)}createVideoMessage(e){return this.validateModules(),this.messageBuilder.buildMessage(Ee.VIDEO,e)}createCustomMessage(e){return this.validateModules(),this.messageBuilder.buildMessage(e.type,e)}sendMessage(e){this.catch((()=>{this.messageSender.send(e)}),e)}recallMessage(e){this.catch((()=>{this.recaller.recallMessage(e)}),e)}deleteMessage(e){this.catch((()=>{this.histories.deleteMessage(e)}),e)}markGroupMessageAsRead(e){this.catch((()=>b(this,void 0,void 0,(function*(){yield this.histories.groupMarkAsRead(e)}))),e)}markPrivateMessageAsRead(e){this.catch((()=>b(this,void 0,void 0,(function*(){yield this.histories.privateMarkAsRead(e)}))),e)}markMessageAsRead(e,t){this.catch((()=>b(this,void 0,void 0,(function*(){yield this.histories.markMessageAsRead(e,t)}))),e)}latestConversations(e){this.validateModules(),this.conversations.latestConversations(e)}removePrivateConversation(e){this.catch((()=>this.conversations.removePrivateConversation(e)),e)}removeGroupConversation(e){this.catch((()=>this.conversations.removeGroupConversation(e)),e)}topPrivateConversation(e){this.catch((()=>this.conversations.topPrivateConversation(e)),e)}topGroupConversation(e){this.catch((()=>this.conversations.topGroupConversation(e)),e)}history(e,t){this.catch((()=>{this.histories.loadHistory(e,t)}),e)}subscribeUserPresence(e){this.catch((()=>this._userPresenceSubscriber.presence(e)),e)}unsubscribeUserPresence(e){this.catch((()=>this._userPresenceSubscriber.unPresence(e)),e)}hereNow(e){this.catch((()=>this._userHereNow.hereNow(e)),e)}subscribeGroup(e){this.catch((()=>this.groupMessageSubscriber.subscribe(e)),e)}unsubscribeGroup(e){this.catch((()=>this.groupMessageSubscriber.unsubscribe(e)),e)}subscribeGroupPresence(e){this.catch((()=>this._groupPresenceSubscriber.presence(e)),e)}unsubscribeGroupPresence(e){this.catch((()=>this._groupPresenceSubscriber.unPresence(e)),e)}groupHereNow(e){this.catch((()=>this._groupHereNow.hereNow(e)),e)}groupOnlineCount(e){this.catch((()=>this._groupOnlineCount.get(e)),e)}latestPendingConversations(e){this.validateModules(),this.conversations.latestPendingConversations(e)}topConversation(e){this.validateModules(),this.conversations.topConversation(e)}removeConversation(e){this.validateModules(),this.conversations.removeConversation(e)}}class Qr{constructor(e,t){this.customerId=e,this.teamId=t}}class Kr{constructor(e,t){this.teamId=e;let s=new c(t.id.toString(),JSON.stringify(t.data));this.customer=s}}class Zr{constructor(e,t,s){this.customerId=e,this.teamId=t,this.agentId=s}}class ea{constructor(e,t){this.customerId=e,this.teamId=t}}class ta{constructor(e){this.builder=new Ns,this.teamId=e}accept(e,t){Tt(gt,t);let s=new Kr(e,t.customer),i=new le({name:y.CS_ACCEPT,params:s,permission:ne.WRITE,singleTimeout:S.commonRequestSingle,totalTimeout:S.commonRequestTotal,fail:e=>{v.onFailed(t,e)},success:e=>{let s=this.builder.build(e.content.message);ys.i.fire(us.CS_ACCEPTED,s),v.onSuccess(t)}});te.Socket.e(i)}end(e,t){At(t.id);let s=t.id.toString(),i=new ea(s,e),n=new le({name:y.CS_END,params:i,permission:ne.WRITE,singleTimeout:S.commonRequestSingle,totalTimeout:S.commonRequestTotal,fail:e=>{v.onFailed(t,e)},success:e=>{let s=this.builder.build(e.content.message);ys.i.fire(us.CS_ENDED,s),v.onSuccess(t)}});te.Socket.e(n)}queryCustomerStatus(e,t){return b(this,void 0,void 0,(function*(){At(t.id);let s=yield this.doCustomerStatus(e,t.id);this.activeCustomerStatus=s,this.activeCustomerStatusOptions=t,v.onSuccess(t,s)}))}doCustomerStatus(e,t){let s=t.toString(),i=new Qr(s,e);return new Promise(((e,t)=>{let s=new le({name:y.CS_CUSTOMER_STATUS,params:i,permission:ne.READ,singleTimeout:S.commonQuerySingle,totalTimeout:S.commonQueryTotal,fail:e=>{t(e)},success:t=>{let s=t.content;s.agent&&(s.agent.data=JSON.parse(s.agent.data)),e(s)}});te.Socket.e(s)}))}transfer(e,t){Tt(yt,t);let s=t.customerId.toString(),i=t.agentId.toString(),n=new Zr(s,e,i),o=new le({name:y.CS_TRANSFER,params:n,permission:ne.WRITE,singleTimeout:S.commonRequestSingle,totalTimeout:S.commonRequestTotal,fail:e=>{v.onFailed(t,e)},success:e=>{let s=this.builder.build(e.content.message);ys.i.fire(us.CS_TRANSFER,s),v.onSuccess(t)}});te.Socket.e(o)}}class sa{createTextMessage(e,t){let s=Yr.i().createTextMessage(t);this.extendProps(e,s)}createImageMessage(e,t){let s=Yr.i().createImageMessage(t);this.extendProps(e,s)}createFileMessage(e,t){let s=Yr.i().createFileMessage(t);this.extendProps(e,s)}createAudioMessage(e,t){let s=Yr.i().createAudioMessage(t);this.extendProps(e,s)}createVideoMessage(e,t){let s=Yr.i().createVideoMessage(t);this.extendProps(e,s)}createCustomMessage(e,t){let s=Yr.i().createCustomMessage(t);this.extendProps(e,s)}extendProps(e,t){if(t.scene()===o.CS){let s=t;s.teamId=e,s.accepted=!0}}}class ia{constructor(e){this.teamId=e,this.agentStatus=Do.getInstance(),this.conversationHandler=new ta(e),this.messageCreator=new sa}catch(e,t){return b(this,void 0,void 0,(function*(){try{yield e()}catch(e){v.onFailed(t,e)}}))}isOnline(e){this.catch((()=>{this.agentStatus.isOnline(this.teamId,e)}),e)}online(e){this.catch((()=>{this.agentStatus.online(this.teamId,e)}),e)}offline(e){this.catch((()=>{this.agentStatus.offline(this.teamId,e)}),e)}customerStatus(e){this.catch((()=>{this.conversationHandler.queryCustomerStatus(this.teamId,e)}),e)}accept(e){this.catch((()=>{this.conversationHandler.accept(this.teamId,e)}),e)}end(e){this.catch((()=>{this.conversationHandler.end(this.teamId,e)}),e)}history(e){Yr.i().history(e,this.teamId)}markMessageAsRead(e){Yr.i().markMessageAsRead(e,this.teamId)}createTextMessage(e){this.messageCreator.createTextMessage(this.teamId,e)}createImageMessage(e){this.messageCreator.createImageMessage(this.teamId,e)}createFileMessage(e){this.messageCreator.createFileMessage(this.teamId,e)}createAudioMessage(e){this.messageCreator.createAudioMessage(this.teamId,e)}createVideoMessage(e){this.messageCreator.createVideoMessage(this.teamId,e)}createCustomMessage(e){this.messageCreator.createCustomMessage(this.teamId,e)}transfer(e){this.catch((()=>{this.conversationHandler.transfer(this.teamId,e)}),e)}agents(e){this.catch((()=>{this.agentStatus.agents(this.teamId,e)}),e)}liveSession(e){this.catch((()=>{xo.live(this.teamId,e)}),e)}quitLiveSession(e){this.catch((()=>{xo.quit(e)}),e)}listenCustomer(e){let t={customerId:e.id,onNewMessage:e.onNewMessage,onStatusUpdated:e.onStatusUpdated,onFailed:e.onFailed,onSuccess:e.onSuccess};this.catch((()=>{xo.live(this.teamId,t)}),e)}cancelListenCustomer(e){this.catch((()=>{xo.quit(e)}),e)}}class na{static team(e){Rt(e);let t=this.teams.get(e);return t||(t=new ia(e.toString()),this.teams.set(e.toString(),t)),t}}na.teams=new Map;class oa{constructor(e){this.id=e}isOnline(e){na.team(this.id).isOnline(e)}online(e){na.team(this.id).online(e)}offline(e){na.team(this.id).offline(e)}customerStatus(e){na.team(this.id).customerStatus(e)}accept(e){na.team(this.id).accept(e)}end(e){na.team(this.id).end(e)}history(e){na.team(this.id).history(e)}markMessageAsRead(e){na.team(this.id).markMessageAsRead(e)}createTextMessage(e){na.team(this.id).createTextMessage(e)}createImageMessage(e){na.team(this.id).createImageMessage(e)}createFileMessage(e){na.team(this.id).createFileMessage(e)}createAudioMessage(e){na.team(this.id).createAudioMessage(e)}createVideoMessage(e){na.team(this.id).createVideoMessage(e)}createCustomMessage(e){na.team(this.id).createCustomMessage(e)}transfer(e){na.team(this.id).transfer(e)}agents(e){na.team(this.id).agents(e)}liveSession(e){na.team(this.id).liveSession(e)}quitLiveSession(e){na.team(this.id).quitLiveSession(e)}listenCustomer(e){na.team(this.id).listenCustomer(e)}cancelListenCustomer(e){na.team(this.id).cancelListenCustomer(e)}}class ra{static getInstance(e){return this.init(e),ra}static init(e){if(this.getConnectionStatus()!==t.DISCONNECTED)throw new Error("Initialization failed. Please disconnect and try again.");Tt($e,e),e.modules=e.modules.map((e=>e.toUpperCase())),i.init(e.reactNativeOptions),this.options=e,_o.init(e),e.allowNotification&&he.init(e.allowNotification),e.modules&&(e.modules.includes("PUBSUB")&&X.initModule(Wt.init()),e.modules.includes("IM")&&X.initModule(Wo.init())),Xr.init(),te.init(_o,he,Xr,n,h,X),this.options.customProvider&&_s.init(this.options.customProvider)}static setBadge(e){he.setBadge(e)}static connect(e){_o.connect(e,"JS")}static disconnect(e){_o.disconnect(e)}static getConnectionStatus(){return _o.status()}static onClickNotification(e){he.onClickNotification(e)}static c(e){e.init(te.Socket,te.N,te.Member,te.v,te.Platform,X)}}ra.version=n,ra.IM_EVENT=ds,ra.IM_SCENE=o,ra.im=class{static on(e,t){Yr.i().on(e,t)}static off(e,t){Yr.i().off(e,t)}static createTextMessage(e){return Yr.i().createTextMessage(e)}static createImageMessage(e){return Yr.i().createImageMessage(e)}static createFileMessage(e){return Yr.i().createFileMessage(e)}static createAudioMessage(e){return Yr.i().createAudioMessage(e)}static createVideoMessage(e){return Yr.i().createVideoMessage(e)}static createCustomMessage(e){return Yr.i().createCustomMessage(e)}static sendMessage(e){Yr.i().sendMessage(e)}static recallMessage(e){Yr.i().recallMessage(e)}static deleteMessage(e){Yr.i().deleteMessage(e)}static markGroupMessageAsRead(e){Yr.i().markGroupMessageAsRead(e)}static markPrivateMessageAsRead(e){Yr.i().markPrivateMessageAsRead(e)}static latestConversations(e){Yr.i().latestConversations(e)}static removePrivateConversation(e){Yr.i().removePrivateConversation(e)}static removeGroupConversation(e){Yr.i().removeGroupConversation(e)}static topPrivateConversation(e){Yr.i().topPrivateConversation(e)}static topGroupConversation(e){Yr.i().topGroupConversation(e)}static history(e){Yr.i().history(e)}static subscribeUserPresence(e){Yr.i().subscribeUserPresence(e)}static unsubscribeUserPresence(e){Yr.i().unsubscribeUserPresence(e)}static hereNow(e){Yr.i().hereNow(e)}static subscribeGroup(e){Yr.i().subscribeGroup(e)}static unsubscribeGroup(e){Yr.i().unsubscribeGroup(e)}static subscribeGroupPresence(e){Yr.i().subscribeGroupPresence(e)}static unsubscribeGroupPresence(e){Yr.i().unsubscribeGroupPresence(e)}static groupHereNow(e){Yr.i().groupHereNow(e)}static groupOnlineCount(e){Yr.i().groupOnlineCount(e)}static markMessageAsRead(e){Yr.i().markMessageAsRead(e)}static csteam(e){return new oa(e)}static pendingConversations(e){Yr.i().latestPendingConversations(e)}static topConversation(e){Yr.i().topConversation(e)}static removeConversation(e){Yr.i().removeConversation(e)}},ra.pubsub=class{static publish(e){this.catch((()=>{zt.instance.publish(e)}),e)}static subscribe(e){this.catch((()=>{zt.instance.subscribe(e)}),e)}static unsubscribe(e){this.catch((()=>{zt.instance.unsubscribe(e)}),e)}static subscribePresence(e){this.catch((()=>{zt.instance.subscribePresence(e)}),e)}static unsubscribePresence(e){this.catch((()=>{zt.instance.unsubscribePresence(e)}),e)}static history(e){this.catch((()=>{zt.instance.history(e)}),e)}static hereNow(e){this.catch((()=>{zt.instance.hereNow(e)}),e)}static catch(e,s){try{if(Wt.check(),[t.DISCONNECTED,t.DISCONNECTING,t.CONNECT_FAILED].includes(te.Socket.status()))throw new Error("Please call connect() first.");e()}catch(e){v.onFailed(s,e)}}};export{ra as default};
diff --git a/miniprogram/static/lib/restapi.js b/miniprogram/static/lib/restapi.js
new file mode 100644
index 0000000..3d671c2
--- /dev/null
+++ b/miniprogram/static/lib/restapi.js
@@ -0,0 +1,146 @@
+class RestApi {
+ //用户数据示例
+ users = [
+ {
+ id: '08c0a6ec-a42b-47b2-bb1e-15e0f5f9a19a',
+ name: 'Mattie',
+ password: '123',
+ avatar: '/static/images/Avatar-1.png',
+ email: 'Mattie@goeasy.io',
+ phone: '138xxxxxxxx',
+ },
+ {
+ id: '3bb179af-bcc5-4fe0-9dac-c05688484649',
+ name: 'Wallace',
+ password: '123',
+ avatar: '/static/images/Avatar-2.png',
+ email: 'Wallace@goeasy.io',
+ phone: '138xxxxxxxx',
+ },
+ {
+ id: 'fdee46b0-4b01-4590-bdba-6586d7617f95',
+ name: 'Tracy',
+ password: '123',
+ avatar: '/static/images/Avatar-3.png',
+ email: 'Tracy@goeasy.io',
+ phone: '138xxxxxxxx',
+ },
+ {
+ id: '33c3693b-dbb0-4bc9-99c6-fa77b9eb763f',
+ name: 'Juanita',
+ password: '123',
+ avatar: '/static/images/Avatar-4.png',
+ email: 'Juanita@goeasy.io',
+ phone: '138xxxxxxxx',
+ },
+ ];
+ //群数据示例
+ groups = [
+ {
+ id: 'group-a42b-47b2-bb1e-15e0f5f9a19a',
+ name: '小程序交流群',
+ avatar: '/static/images/wx.png',
+ userList: [
+ '08c0a6ec-a42b-47b2-bb1e-15e0f5f9a19a',
+ '3bb179af-bcc5-4fe0-9dac-c05688484649',
+ 'fdee46b0-4b01-4590-bdba-6586d7617f95',
+ '33c3693b-dbb0-4bc9-99c6-fa77b9eb763f',
+ ],
+ },
+ {
+ id: 'group-4b01-4590-bdba-6586d7617f95',
+ name: 'UniApp交流群',
+ avatar: '/static/images/uniapp.png',
+ userList: [
+ '08c0a6ec-a42b-47b2-bb1e-15e0f5f9a19a',
+ 'fdee46b0-4b01-4590-bdba-6586d7617f95',
+ '33c3693b-dbb0-4bc9-99c6-fa77b9eb763f',
+ ],
+ },
+ {
+ id: 'group-dbb0-4bc9-99c6-fa77b9eb763f',
+ name: 'GoEasy交流群',
+ avatar: '/static/images/goeasy.jpeg',
+ userList: ['08c0a6ec-a42b-47b2-bb1e-15e0f5f9a19a', '3bb179af-bcc5-4fe0-9dac-c05688484649'],
+ },
+ ];
+ // 订单
+ orders = [
+ {
+ id: '252364104325',
+ url: '/static/images/goods1-1.jpg',
+ name: '青桔柠檬气泡美式',
+ price: '¥23',
+ count: 1
+ },
+ {
+ id: '251662058022',
+ url: '/static/images/goods1-2.jpg',
+ name: '咸柠七',
+ price: '¥8',
+ count: 2
+ },
+ {
+ id: '250676186141',
+ url: '/static/images/goods1-3.jpg',
+ name: '黑糖波波鲜奶茶',
+ price: '¥12',
+ count: 1
+ }
+ ];
+
+ findUsers() {
+ return this.users;
+ };
+
+ findFriends(user) {
+ return this.users.filter((v) => v.id !== user.id);
+ }
+
+ findGroups(user) {
+ return this.groups.filter((v) => v.userList.find((id) => id === user.id));
+ }
+
+ findUser(username, password) {
+ return this.users.find((user) => user.name === username && user.password === password);
+ }
+
+ getOrderList() {
+ return this.orders;
+ }
+
+ findGroupById(groupId) {
+ return this.groups.find((group) => group.id === groupId);
+ }
+
+ findUserById(userId) {
+ return this.users.find((user) => user.id === userId);
+ }
+
+ findGroupMembers (groupId) {
+ let members = [];
+ let group = this.groups.find(v => v.id === groupId);
+ this.users.map(user => {
+ let userId = group.userList.find((id)=>{
+ return id === user.id;
+ });
+ members.push(user);
+ });
+ return members;
+ }
+
+ findGroupMemberAvatars (groupId) {
+ let avatars = [];
+ let group = this.groups.find((v) => v.id === groupId);
+ this.users.map((user) => {
+ group.userList.forEach((userId) => {
+ if (user.id === userId) {
+ avatars.push(user.avatar);
+ }
+ });
+ });
+ return avatars;
+ }
+}
+
+export default new RestApi();
diff --git a/miniprogram/static/lib/utils.js b/miniprogram/static/lib/utils.js
new file mode 100644
index 0000000..6bdd9d8
--- /dev/null
+++ b/miniprogram/static/lib/utils.js
@@ -0,0 +1,49 @@
+/**
+ * 格式化日期
+ * @prama t 时间戳
+ * @return str MM-dd HH:mm
+ */
+export function formatDate(t) {
+ t = t || Date.now();
+ let time = new Date(t);
+ let str = time.getMonth() < 9 ? '0' + (time.getMonth() + 1) : time.getMonth() + 1;
+ str += '-';
+ str += time.getDate() < 10 ? '0' + time.getDate() : time.getDate();
+ str += ' ';
+ str += time.getHours();
+ str += ':';
+ str += time.getMinutes() < 10 ? '0' + time.getMinutes() : time.getMinutes();
+ return str;
+};
+
+/**
+ * 距当前时间点的时长
+ * @prama time 13位时间戳
+ * @return str x秒 / x分钟 / x小时
+ */
+export function formateTime(time) {
+ const second = 1000;
+ const minute = second * 60;
+ const hour = minute * 60;
+ const day = hour * 24;
+ const now = new Date().getTime();
+ const diffValue = now - time;
+
+ // 计算差异时间的量级
+ const secondC = diffValue / second;
+ const minC = diffValue / minute;
+ const hourC = diffValue / hour;
+ const dayC = diffValue / day;
+
+ if (dayC >= 1) {
+ return parseInt(dayC) + "天";
+ } else if (hourC >= 1) {
+ return parseInt(hourC) + "小时";
+ } else if (minC >= 1) {
+ return parseInt(minC) + "分钟";
+ } else if (secondC >= 1) {
+ return parseInt(secondC) + "秒";
+ } else {
+ return '0秒';
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/utils/chat-core.js b/miniprogram/utils/chat-core.js
index 6384cb6..afa84ae 100644
--- a/miniprogram/utils/chat-core.js
+++ b/miniprogram/utils/chat-core.js
@@ -2,7 +2,7 @@
import GoEasy from '../static/lib/goeasy-2.13.24.esm.min';
import { jianquanxian } from './imAuth/jianquanxian';
import { parseOrderCardText } from './group-chat.js';
-import { persistGroupMeta } from './im-user.js';
+import { persistGroupMeta, getFreshImUser } from './im-user.js';
let _globalPrivateHandler = null;
let _globalGroupHandler = null;
@@ -28,9 +28,27 @@ function initGlobalMessageSystem(app) {
loadUserSettings(app);
initNetworkListener(app);
initAppStateListener(app);
- checkAndRestoreConnection(app);
+ bindGoEasyLifecycle(app);
setupEventSystem(app);
- setupMessageListeners(app);
+ // 监听器在连接成功 / onShow 时注册,不在 IM 未就绪时空绑
+ checkAndRestoreConnection(app);
+}
+
+function bindGoEasyLifecycle(app) {
+ if (!wx.goEasy || app._goEasyLifecycleBound) return;
+ app._goEasyLifecycleBound = true;
+ try {
+ wx.goEasy.on('connected', () => {
+ app.globalData.goEasyConnection.status = 'connected';
+ setupMessageListeners(app);
+ loadConversations(app);
+ });
+ wx.goEasy.on('disconnected', () => {
+ app.globalData.goEasyConnection.status = 'disconnected';
+ });
+ } catch (e) {
+ console.warn('绑定 GoEasy 生命周期失败:', e);
+ }
}
function getCurrentGoEasyUserId() {
@@ -78,12 +96,8 @@ function initNetworkListener(app) {
function initAppStateListener(app) {
wx.onAppShow(() => {
- ensureConnection(app);
updateCurrentPageState(app);
- // ✅ 连接正常时,主动刷一次未读角标
- if (wx.goEasy.getConnectionStatus && wx.goEasy.getConnectionStatus() === 'connected') {
- loadConversations(app);
- }
+ ensureConnection(app);
});
wx.onAppHide(() => {
pauseHeartbeat(app);
@@ -101,25 +115,34 @@ function checkAndRestoreConnection(app) {
const validityHours = app.globalData.goEasyConnection.config.cacheValidityHours || 12;
if (hoursDiff > validityHours) {
clearSavedConnection(app);
- } else {
- ensureConnection(app);
}
}
+ ensureConnection(app);
} catch (error) {
console.warn('检查保存的连接信息失败:', error);
}
}
function setupEventSystem(app) {
- app.globalData.eventListeners = {};
+ if (!app.globalData.eventListeners) {
+ app.globalData.eventListeners = {};
+ }
}
-// 🔥 修改点:不依赖 saved 缓存,只要连接状态正常就刷新角标
+function isImConnected() {
+ if (!wx.goEasy?.getConnectionStatus) return false;
+ const s = wx.goEasy.getConnectionStatus();
+ return s === 'connected' || s === 'reconnected';
+}
+
+// 进入小程序即尝试连接并监听(不依赖历史缓存)
function ensureConnection(app) {
- const connectionStatus = wx.goEasy.getConnectionStatus ? wx.goEasy.getConnectionStatus() : 'disconnected';
- if (connectionStatus === 'connected' || connectionStatus === 'reconnected') {
+ if (!wx.goEasy?.connect) return;
+ if (app.globalData.chatEnabled === false) return;
+
+ if (isImConnected()) {
setupMessageListeners(app);
- loadConversations(app); // 每次进入前台都刷新未读
+ loadConversations(app);
return;
}
@@ -206,9 +229,21 @@ function disconnectGoEasy(app) {
}
async function connectWithIdentity(app, identityType, userId, isAutoRestore = false) {
- const quanxian = await jianquanxian(app);
- if (!quanxian.allowed) {
- return Promise.reject(new Error(quanxian.reason || '无聊天权限'));
+ if (!wx.goEasy?.connect) {
+ return Promise.reject(new Error('聊天服务未就绪'));
+ }
+
+ // 冷启动/后台恢复:先连上 IM 才能收消息;完整鉴权留给消息页/发消息
+ if (!isAutoRestore) {
+ const quanxian = await jianquanxian(app);
+ if (!quanxian.allowed) {
+ return Promise.reject(new Error(quanxian.reason || '无聊天权限'));
+ }
+ } else {
+ const uid = wx.getStorageSync('uid');
+ if (!uid) {
+ return Promise.reject(new Error('未登录'));
+ }
}
app.globalData.goEasyConnection.autoReconnect = true;
@@ -302,7 +337,7 @@ function connectForCurrentRole(app) {
if (currentUserId) {
wx.goEasy.disconnect({ onSuccess: () => {}, onFailed: () => {} });
}
- connectWithIdentity(app, role, targetUserId, false);
+ connectWithIdentity(app, role, targetUserId, true);
}
async function switchRoleAndReconnect(app, newRole) {
@@ -577,7 +612,7 @@ function cacheMessage(app, message) {
}
function loadConversations(app) {
- if (!getCurrentGoEasyUserId()) return;
+ if (!getCurrentGoEasyUserId() || !wx.goEasy?.im?.latestConversations) return;
wx.goEasy.im.latestConversations({
onSuccess: (result) => {
if (result.unreadTotal !== undefined) {
diff --git a/miniprogram/utils/cos-wx-sdk-v5.js b/miniprogram/utils/cos-wx-sdk-v5.js
new file mode 100644
index 0000000..2f46dc9
--- /dev/null
+++ b/miniprogram/utils/cos-wx-sdk-v5.js
@@ -0,0 +1,13296 @@
+(function webpackUniversalModuleDefinition(root, factory) {
+ if(typeof exports === 'object' && typeof module === 'object')
+ module.exports = factory();
+ else if(typeof define === 'function' && define.amd)
+ define([], factory);
+ else if(typeof exports === 'object')
+ exports["COS"] = factory();
+ else
+ root["COS"] = factory();
+})(window, function() {
+return /******/ (function(modules) { // webpackBootstrap
+/******/ // The module cache
+/******/ var installedModules = {};
+/******/
+/******/ // The require function
+/******/ function __webpack_require__(moduleId) {
+/******/
+/******/ // Check if module is in cache
+/******/ if(installedModules[moduleId]) {
+/******/ return installedModules[moduleId].exports;
+/******/ }
+/******/ // Create a new module (and put it into the cache)
+/******/ var module = installedModules[moduleId] = {
+/******/ i: moduleId,
+/******/ l: false,
+/******/ exports: {}
+/******/ };
+/******/
+/******/ // Execute the module function
+/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ // Flag the module as loaded
+/******/ module.l = true;
+/******/
+/******/ // Return the exports of the module
+/******/ return module.exports;
+/******/ }
+/******/
+/******/
+/******/ // expose the modules object (__webpack_modules__)
+/******/ __webpack_require__.m = modules;
+/******/
+/******/ // expose the module cache
+/******/ __webpack_require__.c = installedModules;
+/******/
+/******/ // define getter function for harmony exports
+/******/ __webpack_require__.d = function(exports, name, getter) {
+/******/ if(!__webpack_require__.o(exports, name)) {
+/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
+/******/ }
+/******/ };
+/******/
+/******/ // define __esModule on exports
+/******/ __webpack_require__.r = function(exports) {
+/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
+/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
+/******/ }
+/******/ Object.defineProperty(exports, '__esModule', { value: true });
+/******/ };
+/******/
+/******/ // create a fake namespace object
+/******/ // mode & 1: value is a module id, require it
+/******/ // mode & 2: merge all properties of value into the ns
+/******/ // mode & 4: return value when already ns object
+/******/ // mode & 8|1: behave like require
+/******/ __webpack_require__.t = function(value, mode) {
+/******/ if(mode & 1) value = __webpack_require__(value);
+/******/ if(mode & 8) return value;
+/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
+/******/ var ns = Object.create(null);
+/******/ __webpack_require__.r(ns);
+/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
+/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
+/******/ return ns;
+/******/ };
+/******/
+/******/ // getDefaultExport function for compatibility with non-harmony modules
+/******/ __webpack_require__.n = function(module) {
+/******/ var getter = module && module.__esModule ?
+/******/ function getDefault() { return module['default']; } :
+/******/ function getModuleExports() { return module; };
+/******/ __webpack_require__.d(getter, 'a', getter);
+/******/ return getter;
+/******/ };
+/******/
+/******/ // Object.prototype.hasOwnProperty.call
+/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
+/******/
+/******/ // __webpack_public_path__
+/******/ __webpack_require__.p = "/Users/chrisftian/Documents/projects/cos-sdk/cos-wx-sdk-v5/dist";
+/******/
+/******/
+/******/ // Load entry module and return exports
+/******/ return __webpack_require__(__webpack_require__.s = "./index.js");
+/******/ })
+/************************************************************************/
+/******/ ({
+
+/***/ "./index.js":
+/*!******************!*\
+ !*** ./index.js ***!
+ \******************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+var COS = __webpack_require__(/*! ./src/cos */ "./src/cos.js");
+module.exports = COS;
+
+/***/ }),
+
+/***/ "./lib/base64.js":
+/*!***********************!*\
+ !*** ./lib/base64.js ***!
+ \***********************/
+/*! no static exports found */
+/***/ (function(module, exports) {
+
+/*
+ * $Id: base64.js,v 2.15 2014/04/05 12:58:57 dankogai Exp dankogai $
+ *
+ * Licensed under the BSD 3-Clause License.
+ * http://opensource.org/licenses/BSD-3-Clause
+ *
+ * References:
+ * http://en.wikipedia.org/wiki/Base64
+ */
+
+var Base64 = function (global) {
+ global = global || {};
+ 'use strict';
+ // existing version for noConflict()
+ var _Base64 = global.Base64;
+ var version = "2.1.9";
+ // if node.js, we use Buffer
+ var buffer;
+ // constants
+ var b64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
+ var b64tab = function (bin) {
+ var t = {};
+ for (var i = 0, l = bin.length; i < l; i++) t[bin.charAt(i)] = i;
+ return t;
+ }(b64chars);
+ var fromCharCode = String.fromCharCode;
+ // encoder stuff
+ var cb_utob = function cb_utob(c) {
+ if (c.length < 2) {
+ var cc = c.charCodeAt(0);
+ return cc < 0x80 ? c : cc < 0x800 ? fromCharCode(0xc0 | cc >>> 6) + fromCharCode(0x80 | cc & 0x3f) : fromCharCode(0xe0 | cc >>> 12 & 0x0f) + fromCharCode(0x80 | cc >>> 6 & 0x3f) + fromCharCode(0x80 | cc & 0x3f);
+ } else {
+ var cc = 0x10000 + (c.charCodeAt(0) - 0xD800) * 0x400 + (c.charCodeAt(1) - 0xDC00);
+ return fromCharCode(0xf0 | cc >>> 18 & 0x07) + fromCharCode(0x80 | cc >>> 12 & 0x3f) + fromCharCode(0x80 | cc >>> 6 & 0x3f) + fromCharCode(0x80 | cc & 0x3f);
+ }
+ };
+ var re_utob = /[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g;
+ var utob = function utob(u) {
+ return u.replace(re_utob, cb_utob);
+ };
+ var cb_encode = function cb_encode(ccc) {
+ var padlen = [0, 2, 1][ccc.length % 3],
+ ord = ccc.charCodeAt(0) << 16 | (ccc.length > 1 ? ccc.charCodeAt(1) : 0) << 8 | (ccc.length > 2 ? ccc.charCodeAt(2) : 0),
+ chars = [b64chars.charAt(ord >>> 18), b64chars.charAt(ord >>> 12 & 63), padlen >= 2 ? '=' : b64chars.charAt(ord >>> 6 & 63), padlen >= 1 ? '=' : b64chars.charAt(ord & 63)];
+ return chars.join('');
+ };
+ var btoa = global.btoa ? function (b) {
+ return global.btoa(b);
+ } : function (b) {
+ return b.replace(/[\s\S]{1,3}/g, cb_encode);
+ };
+ var _encode = buffer ? function (u) {
+ return (u.constructor === buffer.constructor ? u : new buffer(u)).toString('base64');
+ } : function (u) {
+ return btoa(utob(u));
+ };
+ var encode = function encode(u, urisafe) {
+ return !urisafe ? _encode(String(u)) : _encode(String(u)).replace(/[+\/]/g, function (m0) {
+ return m0 == '+' ? '-' : '_';
+ }).replace(/=/g, '');
+ };
+ var encodeURI = function encodeURI(u) {
+ return encode(u, true);
+ };
+ // decoder stuff
+ var re_btou = new RegExp(['[\xC0-\xDF][\x80-\xBF]', '[\xE0-\xEF][\x80-\xBF]{2}', '[\xF0-\xF7][\x80-\xBF]{3}'].join('|'), 'g');
+ var cb_btou = function cb_btou(cccc) {
+ switch (cccc.length) {
+ case 4:
+ var cp = (0x07 & cccc.charCodeAt(0)) << 18 | (0x3f & cccc.charCodeAt(1)) << 12 | (0x3f & cccc.charCodeAt(2)) << 6 | 0x3f & cccc.charCodeAt(3),
+ offset = cp - 0x10000;
+ return fromCharCode((offset >>> 10) + 0xD800) + fromCharCode((offset & 0x3FF) + 0xDC00);
+ case 3:
+ return fromCharCode((0x0f & cccc.charCodeAt(0)) << 12 | (0x3f & cccc.charCodeAt(1)) << 6 | 0x3f & cccc.charCodeAt(2));
+ default:
+ return fromCharCode((0x1f & cccc.charCodeAt(0)) << 6 | 0x3f & cccc.charCodeAt(1));
+ }
+ };
+ var btou = function btou(b) {
+ return b.replace(re_btou, cb_btou);
+ };
+ var cb_decode = function cb_decode(cccc) {
+ var len = cccc.length,
+ padlen = len % 4,
+ n = (len > 0 ? b64tab[cccc.charAt(0)] << 18 : 0) | (len > 1 ? b64tab[cccc.charAt(1)] << 12 : 0) | (len > 2 ? b64tab[cccc.charAt(2)] << 6 : 0) | (len > 3 ? b64tab[cccc.charAt(3)] : 0),
+ chars = [fromCharCode(n >>> 16), fromCharCode(n >>> 8 & 0xff), fromCharCode(n & 0xff)];
+ chars.length -= [0, 0, 2, 1][padlen];
+ return chars.join('');
+ };
+ var atob = global.atob ? function (a) {
+ return global.atob(a);
+ } : function (a) {
+ return a.replace(/[\s\S]{1,4}/g, cb_decode);
+ };
+ var _decode = buffer ? function (a) {
+ return (a.constructor === buffer.constructor ? a : new buffer(a, 'base64')).toString();
+ } : function (a) {
+ return btou(atob(a));
+ };
+ var decode = function decode(a) {
+ return _decode(String(a).replace(/[-_]/g, function (m0) {
+ return m0 == '-' ? '+' : '/';
+ }).replace(/[^A-Za-z0-9\+\/]/g, ''));
+ };
+ var noConflict = function noConflict() {
+ var Base64 = global.Base64;
+ global.Base64 = _Base64;
+ return Base64;
+ };
+ // export Base64
+ var Base64 = {
+ VERSION: version,
+ atob: atob,
+ btoa: btoa,
+ fromBase64: decode,
+ toBase64: encode,
+ utob: utob,
+ encode: encode,
+ encodeURI: encodeURI,
+ btou: btou,
+ decode: decode,
+ noConflict: noConflict
+ };
+ return Base64;
+}();
+module.exports = Base64;
+
+/***/ }),
+
+/***/ "./lib/crypto.js":
+/*!***********************!*\
+ !*** ./lib/crypto.js ***!
+ \***********************/
+/*! no static exports found */
+/***/ (function(module, exports) {
+
+/*
+ CryptoJS v3.1.2
+ code.google.com/p/crypto-js
+ (c) 2009-2013 by Jeff Mott. All rights reserved.
+ code.google.com/p/crypto-js/wiki/License
+ */
+var CryptoJS = CryptoJS || function (g, l) {
+ var e = {},
+ d = e.lib = {},
+ m = function m() {},
+ k = d.Base = {
+ extend: function extend(a) {
+ m.prototype = this;
+ var c = new m();
+ a && c.mixIn(a);
+ c.hasOwnProperty("init") || (c.init = function () {
+ c.$super.init.apply(this, arguments);
+ });
+ c.init.prototype = c;
+ c.$super = this;
+ return c;
+ },
+ create: function create() {
+ var a = this.extend();
+ a.init.apply(a, arguments);
+ return a;
+ },
+ init: function init() {},
+ mixIn: function mixIn(a) {
+ for (var c in a) a.hasOwnProperty(c) && (this[c] = a[c]);
+ a.hasOwnProperty("toString") && (this.toString = a.toString);
+ },
+ clone: function clone() {
+ return this.init.prototype.extend(this);
+ }
+ },
+ p = d.WordArray = k.extend({
+ init: function init(a, c) {
+ a = this.words = a || [];
+ this.sigBytes = c != l ? c : 4 * a.length;
+ },
+ toString: function toString(a) {
+ return (a || n).stringify(this);
+ },
+ concat: function concat(a) {
+ var c = this.words,
+ q = a.words,
+ f = this.sigBytes;
+ a = a.sigBytes;
+ this.clamp();
+ if (f % 4) for (var b = 0; b < a; b++) c[f + b >>> 2] |= (q[b >>> 2] >>> 24 - 8 * (b % 4) & 255) << 24 - 8 * ((f + b) % 4);else if (65535 < q.length) for (b = 0; b < a; b += 4) c[f + b >>> 2] = q[b >>> 2];else c.push.apply(c, q);
+ this.sigBytes += a;
+ return this;
+ },
+ clamp: function clamp() {
+ var a = this.words,
+ c = this.sigBytes;
+ a[c >>> 2] &= 4294967295 << 32 - 8 * (c % 4);
+ a.length = g.ceil(c / 4);
+ },
+ clone: function clone() {
+ var a = k.clone.call(this);
+ a.words = this.words.slice(0);
+ return a;
+ },
+ random: function random(a) {
+ for (var c = [], b = 0; b < a; b += 4) c.push(4294967296 * g.random() | 0);
+ return new p.init(c, a);
+ }
+ }),
+ b = e.enc = {},
+ n = b.Hex = {
+ stringify: function stringify(a) {
+ var c = a.words;
+ a = a.sigBytes;
+ for (var b = [], f = 0; f < a; f++) {
+ var d = c[f >>> 2] >>> 24 - 8 * (f % 4) & 255;
+ b.push((d >>> 4).toString(16));
+ b.push((d & 15).toString(16));
+ }
+ return b.join("");
+ },
+ parse: function parse(a) {
+ for (var c = a.length, b = [], f = 0; f < c; f += 2) b[f >>> 3] |= parseInt(a.substr(f, 2), 16) << 24 - 4 * (f % 8);
+ return new p.init(b, c / 2);
+ }
+ },
+ j = b.Latin1 = {
+ stringify: function stringify(a) {
+ var c = a.words;
+ a = a.sigBytes;
+ for (var b = [], f = 0; f < a; f++) b.push(String.fromCharCode(c[f >>> 2] >>> 24 - 8 * (f % 4) & 255));
+ return b.join("");
+ },
+ parse: function parse(a) {
+ for (var c = a.length, b = [], f = 0; f < c; f++) b[f >>> 2] |= (a.charCodeAt(f) & 255) << 24 - 8 * (f % 4);
+ return new p.init(b, c);
+ }
+ },
+ h = b.Utf8 = {
+ stringify: function stringify(a) {
+ try {
+ return decodeURIComponent(escape(j.stringify(a)));
+ } catch (c) {
+ throw Error("Malformed UTF-8 data");
+ }
+ },
+ parse: function parse(a) {
+ return j.parse(unescape(encodeURIComponent(a)));
+ }
+ },
+ r = d.BufferedBlockAlgorithm = k.extend({
+ reset: function reset() {
+ this._data = new p.init();
+ this._nDataBytes = 0;
+ },
+ _append: function _append(a) {
+ "string" == typeof a && (a = h.parse(a));
+ this._data.concat(a);
+ this._nDataBytes += a.sigBytes;
+ },
+ _process: function _process(a) {
+ var c = this._data,
+ b = c.words,
+ f = c.sigBytes,
+ d = this.blockSize,
+ e = f / (4 * d),
+ e = a ? g.ceil(e) : g.max((e | 0) - this._minBufferSize, 0);
+ a = e * d;
+ f = g.min(4 * a, f);
+ if (a) {
+ for (var k = 0; k < a; k += d) this._doProcessBlock(b, k);
+ k = b.splice(0, a);
+ c.sigBytes -= f;
+ }
+ return new p.init(k, f);
+ },
+ clone: function clone() {
+ var a = k.clone.call(this);
+ a._data = this._data.clone();
+ return a;
+ },
+ _minBufferSize: 0
+ });
+ d.Hasher = r.extend({
+ cfg: k.extend(),
+ init: function init(a) {
+ this.cfg = this.cfg.extend(a);
+ this.reset();
+ },
+ reset: function reset() {
+ r.reset.call(this);
+ this._doReset();
+ },
+ update: function update(a) {
+ this._append(a);
+ this._process();
+ return this;
+ },
+ finalize: function finalize(a) {
+ a && this._append(a);
+ return this._doFinalize();
+ },
+ blockSize: 16,
+ _createHelper: function _createHelper(a) {
+ return function (b, d) {
+ return new a.init(d).finalize(b);
+ };
+ },
+ _createHmacHelper: function _createHmacHelper(a) {
+ return function (b, d) {
+ return new s.HMAC.init(a, d).finalize(b);
+ };
+ }
+ });
+ var s = e.algo = {};
+ return e;
+}(Math);
+(function () {
+ var g = CryptoJS,
+ l = g.lib,
+ e = l.WordArray,
+ d = l.Hasher,
+ m = [],
+ l = g.algo.SHA1 = d.extend({
+ _doReset: function _doReset() {
+ this._hash = new e.init([1732584193, 4023233417, 2562383102, 271733878, 3285377520]);
+ },
+ _doProcessBlock: function _doProcessBlock(d, e) {
+ for (var b = this._hash.words, n = b[0], j = b[1], h = b[2], g = b[3], l = b[4], a = 0; 80 > a; a++) {
+ if (16 > a) m[a] = d[e + a] | 0;else {
+ var c = m[a - 3] ^ m[a - 8] ^ m[a - 14] ^ m[a - 16];
+ m[a] = c << 1 | c >>> 31;
+ }
+ c = (n << 5 | n >>> 27) + l + m[a];
+ c = 20 > a ? c + ((j & h | ~j & g) + 1518500249) : 40 > a ? c + ((j ^ h ^ g) + 1859775393) : 60 > a ? c + ((j & h | j & g | h & g) - 1894007588) : c + ((j ^ h ^ g) - 899497514);
+ l = g;
+ g = h;
+ h = j << 30 | j >>> 2;
+ j = n;
+ n = c;
+ }
+ b[0] = b[0] + n | 0;
+ b[1] = b[1] + j | 0;
+ b[2] = b[2] + h | 0;
+ b[3] = b[3] + g | 0;
+ b[4] = b[4] + l | 0;
+ },
+ _doFinalize: function _doFinalize() {
+ var d = this._data,
+ e = d.words,
+ b = 8 * this._nDataBytes,
+ g = 8 * d.sigBytes;
+ e[g >>> 5] |= 128 << 24 - g % 32;
+ e[(g + 64 >>> 9 << 4) + 14] = Math.floor(b / 4294967296);
+ e[(g + 64 >>> 9 << 4) + 15] = b;
+ d.sigBytes = 4 * e.length;
+ this._process();
+ return this._hash;
+ },
+ clone: function clone() {
+ var e = d.clone.call(this);
+ e._hash = this._hash.clone();
+ return e;
+ }
+ });
+ g.SHA1 = d._createHelper(l);
+ g.HmacSHA1 = d._createHmacHelper(l);
+})();
+(function () {
+ var g = CryptoJS,
+ l = g.enc.Utf8;
+ g.algo.HMAC = g.lib.Base.extend({
+ init: function init(e, d) {
+ e = this._hasher = new e.init();
+ "string" == typeof d && (d = l.parse(d));
+ var g = e.blockSize,
+ k = 4 * g;
+ d.sigBytes > k && (d = e.finalize(d));
+ d.clamp();
+ for (var p = this._oKey = d.clone(), b = this._iKey = d.clone(), n = p.words, j = b.words, h = 0; h < g; h++) n[h] ^= 1549556828, j[h] ^= 909522486;
+ p.sigBytes = b.sigBytes = k;
+ this.reset();
+ },
+ reset: function reset() {
+ var e = this._hasher;
+ e.reset();
+ e.update(this._iKey);
+ },
+ update: function update(e) {
+ this._hasher.update(e);
+ return this;
+ },
+ finalize: function finalize(e) {
+ var d = this._hasher;
+ e = d.finalize(e);
+ d.reset();
+ return d.finalize(this._oKey.clone().concat(e));
+ }
+ });
+})();
+(function () {
+ // Shortcuts
+ var C = CryptoJS;
+ var C_lib = C.lib;
+ var WordArray = C_lib.WordArray;
+ var C_enc = C.enc;
+
+ /**
+ * Base64 encoding strategy.
+ */
+ var Base64 = C_enc.Base64 = {
+ /**
+ * Converts a word array to a Base64 string.
+ *
+ * @param {WordArray} wordArray The word array.
+ *
+ * @return {string} The Base64 string.
+ *
+ * @static
+ *
+ * @example
+ *
+ * var base64String = CryptoJS.enc.Base64.stringify(wordArray);
+ */
+ stringify: function stringify(wordArray) {
+ // Shortcuts
+ var words = wordArray.words;
+ var sigBytes = wordArray.sigBytes;
+ var map = this._map;
+
+ // Clamp excess bits
+ wordArray.clamp();
+
+ // Convert
+ var base64Chars = [];
+ for (var i = 0; i < sigBytes; i += 3) {
+ var byte1 = words[i >>> 2] >>> 24 - i % 4 * 8 & 0xff;
+ var byte2 = words[i + 1 >>> 2] >>> 24 - (i + 1) % 4 * 8 & 0xff;
+ var byte3 = words[i + 2 >>> 2] >>> 24 - (i + 2) % 4 * 8 & 0xff;
+ var triplet = byte1 << 16 | byte2 << 8 | byte3;
+ for (var j = 0; j < 4 && i + j * 0.75 < sigBytes; j++) {
+ base64Chars.push(map.charAt(triplet >>> 6 * (3 - j) & 0x3f));
+ }
+ }
+
+ // Add padding
+ var paddingChar = map.charAt(64);
+ if (paddingChar) {
+ while (base64Chars.length % 4) {
+ base64Chars.push(paddingChar);
+ }
+ }
+ return base64Chars.join('');
+ },
+ /**
+ * Converts a Base64 string to a word array.
+ *
+ * @param {string} base64Str The Base64 string.
+ *
+ * @return {WordArray} The word array.
+ *
+ * @static
+ *
+ * @example
+ *
+ * var wordArray = CryptoJS.enc.Base64.parse(base64String);
+ */
+ parse: function parse(base64Str) {
+ // Shortcuts
+ var base64StrLength = base64Str.length;
+ var map = this._map;
+
+ // Ignore padding
+ var paddingChar = map.charAt(64);
+ if (paddingChar) {
+ var paddingIndex = base64Str.indexOf(paddingChar);
+ if (paddingIndex != -1) {
+ base64StrLength = paddingIndex;
+ }
+ }
+
+ // Convert
+ var words = [];
+ var nBytes = 0;
+ for (var i = 0; i < base64StrLength; i++) {
+ if (i % 4) {
+ var bits1 = map.indexOf(base64Str.charAt(i - 1)) << i % 4 * 2;
+ var bits2 = map.indexOf(base64Str.charAt(i)) >>> 6 - i % 4 * 2;
+ words[nBytes >>> 2] |= (bits1 | bits2) << 24 - nBytes % 4 * 8;
+ nBytes++;
+ }
+ }
+ return WordArray.create(words, nBytes);
+ },
+ _map: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
+ };
+})();
+module.exports = CryptoJS;
+
+/***/ }),
+
+/***/ "./lib/md5.js":
+/*!********************!*\
+ !*** ./lib/md5.js ***!
+ \********************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+/* WEBPACK VAR INJECTION */(function(module) {var __WEBPACK_AMD_DEFINE_RESULT__;function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
+/* https://github.com/emn178/js-md5 */
+(function () {
+ 'use strict';
+
+ var ERROR = 'input is invalid type';
+ var WINDOW = (typeof window === "undefined" ? "undefined" : _typeof(window)) === 'object';
+ var root = WINDOW ? window : {};
+ if (root.JS_MD5_NO_WINDOW) {
+ WINDOW = false;
+ }
+ var WEB_WORKER = !WINDOW && (typeof self === "undefined" ? "undefined" : _typeof(self)) === 'object';
+ if (WEB_WORKER) {
+ root = self;
+ }
+ var COMMON_JS = !root.JS_MD5_NO_COMMON_JS && ( false ? undefined : _typeof(module)) === 'object' && module.exports;
+ var AMD = true && __webpack_require__(/*! !webpack amd options */ "./node_modules/webpack/buildin/amd-options.js");
+ var ARRAY_BUFFER = !root.JS_MD5_NO_ARRAY_BUFFER && typeof ArrayBuffer !== 'undefined';
+ var HEX_CHARS = '0123456789abcdef'.split('');
+ var EXTRA = [128, 32768, 8388608, -2147483648];
+ var SHIFT = [0, 8, 16, 24];
+ var OUTPUT_TYPES = ['hex', 'array', 'digest', 'buffer', 'arrayBuffer', 'base64'];
+ var BASE64_ENCODE_CHAR = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('');
+ var blocks = [],
+ buffer8;
+ if (ARRAY_BUFFER) {
+ var buffer = new ArrayBuffer(68);
+ buffer8 = new Uint8Array(buffer);
+ blocks = new Uint32Array(buffer);
+ }
+ if (root.JS_MD5_NO_NODE_JS || !Array.isArray) {
+ Array.isArray = function (obj) {
+ return Object.prototype.toString.call(obj) === '[object Array]';
+ };
+ }
+ if (ARRAY_BUFFER && (root.JS_MD5_NO_ARRAY_BUFFER_IS_VIEW || !ArrayBuffer.isView)) {
+ ArrayBuffer.isView = function (obj) {
+ return _typeof(obj) === 'object' && obj.buffer && obj.buffer.constructor === ArrayBuffer;
+ };
+ }
+
+ /**
+ * @method hex
+ * @memberof md5
+ * @description Output hash as hex string
+ * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash
+ * @returns {String} Hex string
+ * @example
+ * md5.hex('The quick brown fox jumps over the lazy dog');
+ * // equal to
+ * md5('The quick brown fox jumps over the lazy dog');
+ */
+ /**
+ * @method digest
+ * @memberof md5
+ * @description Output hash as bytes array
+ * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash
+ * @returns {Array} Bytes array
+ * @example
+ * md5.digest('The quick brown fox jumps over the lazy dog');
+ */
+ /**
+ * @method array
+ * @memberof md5
+ * @description Output hash as bytes array
+ * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash
+ * @returns {Array} Bytes array
+ * @example
+ * md5.array('The quick brown fox jumps over the lazy dog');
+ */
+ /**
+ * @method arrayBuffer
+ * @memberof md5
+ * @description Output hash as ArrayBuffer
+ * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash
+ * @returns {ArrayBuffer} ArrayBuffer
+ * @example
+ * md5.arrayBuffer('The quick brown fox jumps over the lazy dog');
+ */
+ /**
+ * @method buffer
+ * @deprecated This maybe confuse with Buffer in node.js. Please use arrayBuffer instead.
+ * @memberof md5
+ * @description Output hash as ArrayBuffer
+ * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash
+ * @returns {ArrayBuffer} ArrayBuffer
+ * @example
+ * md5.buffer('The quick brown fox jumps over the lazy dog');
+ */
+ /**
+ * @method base64
+ * @memberof md5
+ * @description Output hash as base64 string
+ * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash
+ * @returns {String} base64 string
+ * @example
+ * md5.base64('The quick brown fox jumps over the lazy dog');
+ */
+ var createOutputMethod = function createOutputMethod(outputType) {
+ return function (message) {
+ return new Md5(true).update(message)[outputType]();
+ };
+ };
+
+ /**
+ * @method create
+ * @memberof md5
+ * @description Create Md5 object
+ * @returns {Md5} Md5 object.
+ * @example
+ * var hash = md5.create();
+ */
+ /**
+ * @method update
+ * @memberof md5
+ * @description Create and update Md5 object
+ * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash
+ * @returns {Md5} Md5 object.
+ * @example
+ * var hash = md5.update('The quick brown fox jumps over the lazy dog');
+ * // equal to
+ * var hash = md5.create();
+ * hash.update('The quick brown fox jumps over the lazy dog');
+ */
+ var createMethod = function createMethod() {
+ var method = createOutputMethod('hex');
+ method.getCtx = method.create = function () {
+ return new Md5();
+ };
+ method.update = function (message) {
+ return method.create().update(message);
+ };
+ for (var i = 0; i < OUTPUT_TYPES.length; ++i) {
+ var type = OUTPUT_TYPES[i];
+ method[type] = createOutputMethod(type);
+ }
+ return method;
+ };
+
+ /**
+ * Md5 class
+ * @class Md5
+ * @description This is internal class.
+ * @see {@link md5.create}
+ */
+ function Md5(sharedMemory) {
+ if (sharedMemory) {
+ blocks[0] = blocks[16] = blocks[1] = blocks[2] = blocks[3] = blocks[4] = blocks[5] = blocks[6] = blocks[7] = blocks[8] = blocks[9] = blocks[10] = blocks[11] = blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
+ this.blocks = blocks;
+ this.buffer8 = buffer8;
+ } else {
+ if (ARRAY_BUFFER) {
+ var buffer = new ArrayBuffer(68);
+ this.buffer8 = new Uint8Array(buffer);
+ this.blocks = new Uint32Array(buffer);
+ } else {
+ this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
+ }
+ }
+ this.h0 = this.h1 = this.h2 = this.h3 = this.start = this.bytes = this.hBytes = 0;
+ this.finalized = this.hashed = false;
+ this.first = true;
+ }
+
+ /**
+ * @method update
+ * @memberof Md5
+ * @instance
+ * @description Update hash
+ * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash
+ * @returns {Md5} Md5 object.
+ * @see {@link md5.update}
+ */
+ Md5.prototype.update = function (message) {
+ if (this.finalized) {
+ return;
+ }
+ var notString,
+ type = _typeof(message);
+ if (type !== 'string') {
+ if (type === 'object') {
+ if (message === null) {
+ throw ERROR;
+ } else if (ARRAY_BUFFER && (message.constructor === ArrayBuffer || message.constructor.name === 'ArrayBuffer')) {
+ message = new Uint8Array(message);
+ } else if (!Array.isArray(message)) {
+ if (!ARRAY_BUFFER || !ArrayBuffer.isView(message)) {
+ throw ERROR;
+ }
+ }
+ } else {
+ throw ERROR;
+ }
+ notString = true;
+ }
+ var code,
+ index = 0,
+ i,
+ length = message.length,
+ blocks = this.blocks;
+ var buffer8 = this.buffer8;
+ while (index < length) {
+ if (this.hashed) {
+ this.hashed = false;
+ blocks[0] = blocks[16];
+ blocks[16] = blocks[1] = blocks[2] = blocks[3] = blocks[4] = blocks[5] = blocks[6] = blocks[7] = blocks[8] = blocks[9] = blocks[10] = blocks[11] = blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
+ }
+ if (notString) {
+ if (ARRAY_BUFFER) {
+ for (i = this.start; index < length && i < 64; ++index) {
+ buffer8[i++] = message[index];
+ }
+ } else {
+ for (i = this.start; index < length && i < 64; ++index) {
+ blocks[i >> 2] |= message[index] << SHIFT[i++ & 3];
+ }
+ }
+ } else {
+ if (ARRAY_BUFFER) {
+ for (i = this.start; index < length && i < 64; ++index) {
+ code = message.charCodeAt(index);
+ if (code < 0x80) {
+ buffer8[i++] = code;
+ } else if (code < 0x800) {
+ buffer8[i++] = 0xc0 | code >> 6;
+ buffer8[i++] = 0x80 | code & 0x3f;
+ } else if (code < 0xd800 || code >= 0xe000) {
+ buffer8[i++] = 0xe0 | code >> 12;
+ buffer8[i++] = 0x80 | code >> 6 & 0x3f;
+ buffer8[i++] = 0x80 | code & 0x3f;
+ } else {
+ code = 0x10000 + ((code & 0x3ff) << 10 | message.charCodeAt(++index) & 0x3ff);
+ buffer8[i++] = 0xf0 | code >> 18;
+ buffer8[i++] = 0x80 | code >> 12 & 0x3f;
+ buffer8[i++] = 0x80 | code >> 6 & 0x3f;
+ buffer8[i++] = 0x80 | code & 0x3f;
+ }
+ }
+ } else {
+ for (i = this.start; index < length && i < 64; ++index) {
+ code = message.charCodeAt(index);
+ if (code < 0x80) {
+ blocks[i >> 2] |= code << SHIFT[i++ & 3];
+ } else if (code < 0x800) {
+ blocks[i >> 2] |= (0xc0 | code >> 6) << SHIFT[i++ & 3];
+ blocks[i >> 2] |= (0x80 | code & 0x3f) << SHIFT[i++ & 3];
+ } else if (code < 0xd800 || code >= 0xe000) {
+ blocks[i >> 2] |= (0xe0 | code >> 12) << SHIFT[i++ & 3];
+ blocks[i >> 2] |= (0x80 | code >> 6 & 0x3f) << SHIFT[i++ & 3];
+ blocks[i >> 2] |= (0x80 | code & 0x3f) << SHIFT[i++ & 3];
+ } else {
+ code = 0x10000 + ((code & 0x3ff) << 10 | message.charCodeAt(++index) & 0x3ff);
+ blocks[i >> 2] |= (0xf0 | code >> 18) << SHIFT[i++ & 3];
+ blocks[i >> 2] |= (0x80 | code >> 12 & 0x3f) << SHIFT[i++ & 3];
+ blocks[i >> 2] |= (0x80 | code >> 6 & 0x3f) << SHIFT[i++ & 3];
+ blocks[i >> 2] |= (0x80 | code & 0x3f) << SHIFT[i++ & 3];
+ }
+ }
+ }
+ }
+ this.lastByteIndex = i;
+ this.bytes += i - this.start;
+ if (i >= 64) {
+ this.start = i - 64;
+ this.hash();
+ this.hashed = true;
+ } else {
+ this.start = i;
+ }
+ }
+ if (this.bytes > 4294967295) {
+ this.hBytes += this.bytes / 4294967296 << 0;
+ this.bytes = this.bytes % 4294967296;
+ }
+ return this;
+ };
+ Md5.prototype.finalize = function () {
+ if (this.finalized) {
+ return;
+ }
+ this.finalized = true;
+ var blocks = this.blocks,
+ i = this.lastByteIndex;
+ blocks[i >> 2] |= EXTRA[i & 3];
+ if (i >= 56) {
+ if (!this.hashed) {
+ this.hash();
+ }
+ blocks[0] = blocks[16];
+ blocks[16] = blocks[1] = blocks[2] = blocks[3] = blocks[4] = blocks[5] = blocks[6] = blocks[7] = blocks[8] = blocks[9] = blocks[10] = blocks[11] = blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
+ }
+ blocks[14] = this.bytes << 3;
+ blocks[15] = this.hBytes << 3 | this.bytes >>> 29;
+ this.hash();
+ };
+ Md5.prototype.hash = function () {
+ var a,
+ b,
+ c,
+ d,
+ bc,
+ da,
+ blocks = this.blocks;
+ if (this.first) {
+ a = blocks[0] - 680876937;
+ a = (a << 7 | a >>> 25) - 271733879 << 0;
+ d = (-1732584194 ^ a & 2004318071) + blocks[1] - 117830708;
+ d = (d << 12 | d >>> 20) + a << 0;
+ c = (-271733879 ^ d & (a ^ -271733879)) + blocks[2] - 1126478375;
+ c = (c << 17 | c >>> 15) + d << 0;
+ b = (a ^ c & (d ^ a)) + blocks[3] - 1316259209;
+ b = (b << 22 | b >>> 10) + c << 0;
+ } else {
+ a = this.h0;
+ b = this.h1;
+ c = this.h2;
+ d = this.h3;
+ a += (d ^ b & (c ^ d)) + blocks[0] - 680876936;
+ a = (a << 7 | a >>> 25) + b << 0;
+ d += (c ^ a & (b ^ c)) + blocks[1] - 389564586;
+ d = (d << 12 | d >>> 20) + a << 0;
+ c += (b ^ d & (a ^ b)) + blocks[2] + 606105819;
+ c = (c << 17 | c >>> 15) + d << 0;
+ b += (a ^ c & (d ^ a)) + blocks[3] - 1044525330;
+ b = (b << 22 | b >>> 10) + c << 0;
+ }
+ a += (d ^ b & (c ^ d)) + blocks[4] - 176418897;
+ a = (a << 7 | a >>> 25) + b << 0;
+ d += (c ^ a & (b ^ c)) + blocks[5] + 1200080426;
+ d = (d << 12 | d >>> 20) + a << 0;
+ c += (b ^ d & (a ^ b)) + blocks[6] - 1473231341;
+ c = (c << 17 | c >>> 15) + d << 0;
+ b += (a ^ c & (d ^ a)) + blocks[7] - 45705983;
+ b = (b << 22 | b >>> 10) + c << 0;
+ a += (d ^ b & (c ^ d)) + blocks[8] + 1770035416;
+ a = (a << 7 | a >>> 25) + b << 0;
+ d += (c ^ a & (b ^ c)) + blocks[9] - 1958414417;
+ d = (d << 12 | d >>> 20) + a << 0;
+ c += (b ^ d & (a ^ b)) + blocks[10] - 42063;
+ c = (c << 17 | c >>> 15) + d << 0;
+ b += (a ^ c & (d ^ a)) + blocks[11] - 1990404162;
+ b = (b << 22 | b >>> 10) + c << 0;
+ a += (d ^ b & (c ^ d)) + blocks[12] + 1804603682;
+ a = (a << 7 | a >>> 25) + b << 0;
+ d += (c ^ a & (b ^ c)) + blocks[13] - 40341101;
+ d = (d << 12 | d >>> 20) + a << 0;
+ c += (b ^ d & (a ^ b)) + blocks[14] - 1502002290;
+ c = (c << 17 | c >>> 15) + d << 0;
+ b += (a ^ c & (d ^ a)) + blocks[15] + 1236535329;
+ b = (b << 22 | b >>> 10) + c << 0;
+ a += (c ^ d & (b ^ c)) + blocks[1] - 165796510;
+ a = (a << 5 | a >>> 27) + b << 0;
+ d += (b ^ c & (a ^ b)) + blocks[6] - 1069501632;
+ d = (d << 9 | d >>> 23) + a << 0;
+ c += (a ^ b & (d ^ a)) + blocks[11] + 643717713;
+ c = (c << 14 | c >>> 18) + d << 0;
+ b += (d ^ a & (c ^ d)) + blocks[0] - 373897302;
+ b = (b << 20 | b >>> 12) + c << 0;
+ a += (c ^ d & (b ^ c)) + blocks[5] - 701558691;
+ a = (a << 5 | a >>> 27) + b << 0;
+ d += (b ^ c & (a ^ b)) + blocks[10] + 38016083;
+ d = (d << 9 | d >>> 23) + a << 0;
+ c += (a ^ b & (d ^ a)) + blocks[15] - 660478335;
+ c = (c << 14 | c >>> 18) + d << 0;
+ b += (d ^ a & (c ^ d)) + blocks[4] - 405537848;
+ b = (b << 20 | b >>> 12) + c << 0;
+ a += (c ^ d & (b ^ c)) + blocks[9] + 568446438;
+ a = (a << 5 | a >>> 27) + b << 0;
+ d += (b ^ c & (a ^ b)) + blocks[14] - 1019803690;
+ d = (d << 9 | d >>> 23) + a << 0;
+ c += (a ^ b & (d ^ a)) + blocks[3] - 187363961;
+ c = (c << 14 | c >>> 18) + d << 0;
+ b += (d ^ a & (c ^ d)) + blocks[8] + 1163531501;
+ b = (b << 20 | b >>> 12) + c << 0;
+ a += (c ^ d & (b ^ c)) + blocks[13] - 1444681467;
+ a = (a << 5 | a >>> 27) + b << 0;
+ d += (b ^ c & (a ^ b)) + blocks[2] - 51403784;
+ d = (d << 9 | d >>> 23) + a << 0;
+ c += (a ^ b & (d ^ a)) + blocks[7] + 1735328473;
+ c = (c << 14 | c >>> 18) + d << 0;
+ b += (d ^ a & (c ^ d)) + blocks[12] - 1926607734;
+ b = (b << 20 | b >>> 12) + c << 0;
+ bc = b ^ c;
+ a += (bc ^ d) + blocks[5] - 378558;
+ a = (a << 4 | a >>> 28) + b << 0;
+ d += (bc ^ a) + blocks[8] - 2022574463;
+ d = (d << 11 | d >>> 21) + a << 0;
+ da = d ^ a;
+ c += (da ^ b) + blocks[11] + 1839030562;
+ c = (c << 16 | c >>> 16) + d << 0;
+ b += (da ^ c) + blocks[14] - 35309556;
+ b = (b << 23 | b >>> 9) + c << 0;
+ bc = b ^ c;
+ a += (bc ^ d) + blocks[1] - 1530992060;
+ a = (a << 4 | a >>> 28) + b << 0;
+ d += (bc ^ a) + blocks[4] + 1272893353;
+ d = (d << 11 | d >>> 21) + a << 0;
+ da = d ^ a;
+ c += (da ^ b) + blocks[7] - 155497632;
+ c = (c << 16 | c >>> 16) + d << 0;
+ b += (da ^ c) + blocks[10] - 1094730640;
+ b = (b << 23 | b >>> 9) + c << 0;
+ bc = b ^ c;
+ a += (bc ^ d) + blocks[13] + 681279174;
+ a = (a << 4 | a >>> 28) + b << 0;
+ d += (bc ^ a) + blocks[0] - 358537222;
+ d = (d << 11 | d >>> 21) + a << 0;
+ da = d ^ a;
+ c += (da ^ b) + blocks[3] - 722521979;
+ c = (c << 16 | c >>> 16) + d << 0;
+ b += (da ^ c) + blocks[6] + 76029189;
+ b = (b << 23 | b >>> 9) + c << 0;
+ bc = b ^ c;
+ a += (bc ^ d) + blocks[9] - 640364487;
+ a = (a << 4 | a >>> 28) + b << 0;
+ d += (bc ^ a) + blocks[12] - 421815835;
+ d = (d << 11 | d >>> 21) + a << 0;
+ da = d ^ a;
+ c += (da ^ b) + blocks[15] + 530742520;
+ c = (c << 16 | c >>> 16) + d << 0;
+ b += (da ^ c) + blocks[2] - 995338651;
+ b = (b << 23 | b >>> 9) + c << 0;
+ a += (c ^ (b | ~d)) + blocks[0] - 198630844;
+ a = (a << 6 | a >>> 26) + b << 0;
+ d += (b ^ (a | ~c)) + blocks[7] + 1126891415;
+ d = (d << 10 | d >>> 22) + a << 0;
+ c += (a ^ (d | ~b)) + blocks[14] - 1416354905;
+ c = (c << 15 | c >>> 17) + d << 0;
+ b += (d ^ (c | ~a)) + blocks[5] - 57434055;
+ b = (b << 21 | b >>> 11) + c << 0;
+ a += (c ^ (b | ~d)) + blocks[12] + 1700485571;
+ a = (a << 6 | a >>> 26) + b << 0;
+ d += (b ^ (a | ~c)) + blocks[3] - 1894986606;
+ d = (d << 10 | d >>> 22) + a << 0;
+ c += (a ^ (d | ~b)) + blocks[10] - 1051523;
+ c = (c << 15 | c >>> 17) + d << 0;
+ b += (d ^ (c | ~a)) + blocks[1] - 2054922799;
+ b = (b << 21 | b >>> 11) + c << 0;
+ a += (c ^ (b | ~d)) + blocks[8] + 1873313359;
+ a = (a << 6 | a >>> 26) + b << 0;
+ d += (b ^ (a | ~c)) + blocks[15] - 30611744;
+ d = (d << 10 | d >>> 22) + a << 0;
+ c += (a ^ (d | ~b)) + blocks[6] - 1560198380;
+ c = (c << 15 | c >>> 17) + d << 0;
+ b += (d ^ (c | ~a)) + blocks[13] + 1309151649;
+ b = (b << 21 | b >>> 11) + c << 0;
+ a += (c ^ (b | ~d)) + blocks[4] - 145523070;
+ a = (a << 6 | a >>> 26) + b << 0;
+ d += (b ^ (a | ~c)) + blocks[11] - 1120210379;
+ d = (d << 10 | d >>> 22) + a << 0;
+ c += (a ^ (d | ~b)) + blocks[2] + 718787259;
+ c = (c << 15 | c >>> 17) + d << 0;
+ b += (d ^ (c | ~a)) + blocks[9] - 343485551;
+ b = (b << 21 | b >>> 11) + c << 0;
+ if (this.first) {
+ this.h0 = a + 1732584193 << 0;
+ this.h1 = b - 271733879 << 0;
+ this.h2 = c - 1732584194 << 0;
+ this.h3 = d + 271733878 << 0;
+ this.first = false;
+ } else {
+ this.h0 = this.h0 + a << 0;
+ this.h1 = this.h1 + b << 0;
+ this.h2 = this.h2 + c << 0;
+ this.h3 = this.h3 + d << 0;
+ }
+ };
+
+ /**
+ * @method hex
+ * @memberof Md5
+ * @instance
+ * @description Output hash as hex string
+ * @returns {String} Hex string
+ * @see {@link md5.hex}
+ * @example
+ * hash.hex();
+ */
+ Md5.prototype.hex = function () {
+ this.finalize();
+ var h0 = this.h0,
+ h1 = this.h1,
+ h2 = this.h2,
+ h3 = this.h3;
+ return HEX_CHARS[h0 >> 4 & 0x0F] + HEX_CHARS[h0 & 0x0F] + HEX_CHARS[h0 >> 12 & 0x0F] + HEX_CHARS[h0 >> 8 & 0x0F] + HEX_CHARS[h0 >> 20 & 0x0F] + HEX_CHARS[h0 >> 16 & 0x0F] + HEX_CHARS[h0 >> 28 & 0x0F] + HEX_CHARS[h0 >> 24 & 0x0F] + HEX_CHARS[h1 >> 4 & 0x0F] + HEX_CHARS[h1 & 0x0F] + HEX_CHARS[h1 >> 12 & 0x0F] + HEX_CHARS[h1 >> 8 & 0x0F] + HEX_CHARS[h1 >> 20 & 0x0F] + HEX_CHARS[h1 >> 16 & 0x0F] + HEX_CHARS[h1 >> 28 & 0x0F] + HEX_CHARS[h1 >> 24 & 0x0F] + HEX_CHARS[h2 >> 4 & 0x0F] + HEX_CHARS[h2 & 0x0F] + HEX_CHARS[h2 >> 12 & 0x0F] + HEX_CHARS[h2 >> 8 & 0x0F] + HEX_CHARS[h2 >> 20 & 0x0F] + HEX_CHARS[h2 >> 16 & 0x0F] + HEX_CHARS[h2 >> 28 & 0x0F] + HEX_CHARS[h2 >> 24 & 0x0F] + HEX_CHARS[h3 >> 4 & 0x0F] + HEX_CHARS[h3 & 0x0F] + HEX_CHARS[h3 >> 12 & 0x0F] + HEX_CHARS[h3 >> 8 & 0x0F] + HEX_CHARS[h3 >> 20 & 0x0F] + HEX_CHARS[h3 >> 16 & 0x0F] + HEX_CHARS[h3 >> 28 & 0x0F] + HEX_CHARS[h3 >> 24 & 0x0F];
+ };
+
+ /**
+ * @method toString
+ * @memberof Md5
+ * @instance
+ * @description Output hash as hex string
+ * @returns {String} Hex string
+ * @see {@link md5.hex}
+ * @example
+ * hash.toString();
+ */
+ Md5.prototype.toString = Md5.prototype.hex;
+
+ /**
+ * @method digest
+ * @memberof Md5
+ * @instance
+ * @description Output hash as bytes array
+ * @returns {Array} Bytes array
+ * @see {@link md5.digest}
+ * @example
+ * hash.digest();
+ */
+ Md5.prototype.digest = function () {
+ this.finalize();
+ var h0 = this.h0,
+ h1 = this.h1,
+ h2 = this.h2,
+ h3 = this.h3;
+ return [h0 & 0xFF, h0 >> 8 & 0xFF, h0 >> 16 & 0xFF, h0 >> 24 & 0xFF, h1 & 0xFF, h1 >> 8 & 0xFF, h1 >> 16 & 0xFF, h1 >> 24 & 0xFF, h2 & 0xFF, h2 >> 8 & 0xFF, h2 >> 16 & 0xFF, h2 >> 24 & 0xFF, h3 & 0xFF, h3 >> 8 & 0xFF, h3 >> 16 & 0xFF, h3 >> 24 & 0xFF];
+ };
+
+ /**
+ * @method array
+ * @memberof Md5
+ * @instance
+ * @description Output hash as bytes array
+ * @returns {Array} Bytes array
+ * @see {@link md5.array}
+ * @example
+ * hash.array();
+ */
+ Md5.prototype.array = Md5.prototype.digest;
+
+ /**
+ * @method arrayBuffer
+ * @memberof Md5
+ * @instance
+ * @description Output hash as ArrayBuffer
+ * @returns {ArrayBuffer} ArrayBuffer
+ * @see {@link md5.arrayBuffer}
+ * @example
+ * hash.arrayBuffer();
+ */
+ Md5.prototype.arrayBuffer = function () {
+ this.finalize();
+ var buffer = new ArrayBuffer(16);
+ var blocks = new Uint32Array(buffer);
+ blocks[0] = this.h0;
+ blocks[1] = this.h1;
+ blocks[2] = this.h2;
+ blocks[3] = this.h3;
+ return buffer;
+ };
+
+ /**
+ * @method buffer
+ * @deprecated This maybe confuse with Buffer in node.js. Please use arrayBuffer instead.
+ * @memberof Md5
+ * @instance
+ * @description Output hash as ArrayBuffer
+ * @returns {ArrayBuffer} ArrayBuffer
+ * @see {@link md5.buffer}
+ * @example
+ * hash.buffer();
+ */
+ Md5.prototype.buffer = Md5.prototype.arrayBuffer;
+
+ /**
+ * @method base64
+ * @memberof Md5
+ * @instance
+ * @description Output hash as base64 string
+ * @returns {String} base64 string
+ * @see {@link md5.base64}
+ * @example
+ * hash.base64();
+ */
+ Md5.prototype.base64 = function () {
+ var v1,
+ v2,
+ v3,
+ base64Str = '',
+ bytes = this.array();
+ for (var i = 0; i < 15;) {
+ v1 = bytes[i++];
+ v2 = bytes[i++];
+ v3 = bytes[i++];
+ base64Str += BASE64_ENCODE_CHAR[v1 >>> 2] + BASE64_ENCODE_CHAR[(v1 << 4 | v2 >>> 4) & 63] + BASE64_ENCODE_CHAR[(v2 << 2 | v3 >>> 6) & 63] + BASE64_ENCODE_CHAR[v3 & 63];
+ }
+ v1 = bytes[i];
+ base64Str += BASE64_ENCODE_CHAR[v1 >>> 2] + BASE64_ENCODE_CHAR[v1 << 4 & 63] + '==';
+ return base64Str;
+ };
+ var exports = createMethod();
+ if (COMMON_JS) {
+ module.exports = exports;
+ } else {
+ /**
+ * @method md5
+ * @description Md5 hash function, export to global in browsers.
+ * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash
+ * @returns {String} md5 hashes
+ * @example
+ * md5(''); // d41d8cd98f00b204e9800998ecf8427e
+ * md5('The quick brown fox jumps over the lazy dog'); // 9e107d9d372bb6826bd81d3542a419d6
+ * md5('The quick brown fox jumps over the lazy dog.'); // e4d909c290d0fb1ca068ffaddf22cbd0
+ *
+ * // It also supports UTF-8 encoding
+ * md5('中文'); // a7bac2239fcdcb3a067903d8077c4a07
+ *
+ * // It also supports byte `Array`, `Uint8Array`, `ArrayBuffer`
+ * md5([]); // d41d8cd98f00b204e9800998ecf8427e
+ * md5(new Uint8Array([])); // d41d8cd98f00b204e9800998ecf8427e
+ */
+ root.md5 = exports;
+ if (AMD) {
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = (function () {
+ return exports;
+ }).call(exports, __webpack_require__, exports, module),
+ __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+ }
+ }
+})();
+/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./../node_modules/webpack/buildin/module.js */ "./node_modules/webpack/buildin/module.js")(module)))
+
+/***/ }),
+
+/***/ "./lib/request.js":
+/*!************************!*\
+ !*** ./lib/request.js ***!
+ \************************/
+/*! no static exports found */
+/***/ (function(module, exports) {
+
+function camSafeUrlEncode(str) {
+ return encodeURIComponent(str).replace(/!/g, '%21').replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29').replace(/\*/g, '%2A');
+}
+function getObjectKeys(obj, forKey) {
+ var list = [];
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ list.push(forKey ? camSafeUrlEncode(key).toLowerCase() : key);
+ }
+ }
+ return list.sort(function (a, b) {
+ a = a.toLowerCase();
+ b = b.toLowerCase();
+ return a === b ? 0 : a > b ? 1 : -1;
+ });
+}
+;
+var obj2str = function obj2str(obj, lowerCaseKey) {
+ var i, key, val;
+ var list = [];
+ var keyList = getObjectKeys(obj);
+ for (i = 0; i < keyList.length; i++) {
+ key = keyList[i];
+ val = obj[key] === undefined || obj[key] === null ? '' : '' + obj[key];
+ key = lowerCaseKey ? camSafeUrlEncode(key).toLowerCase() : camSafeUrlEncode(key);
+ val = camSafeUrlEncode(val) || '';
+ list.push(key + '=' + val);
+ }
+ return list.join('&');
+};
+var request = function request(params, callback) {
+ var filePath = params.filePath;
+ var headers = params.headers || {};
+ var url = params.url || params.Url;
+ var method = params.method;
+ var onProgress = params.onProgress;
+ var httpDNSServiceId = params.httpDNSServiceId;
+ var requestTask;
+ var cb = function cb(err, response) {
+ var H = response.header;
+ var headers = {};
+ if (H) for (var key in H) {
+ if (H.hasOwnProperty(key)) headers[key.toLowerCase()] = H[key];
+ }
+ callback(err, {
+ statusCode: response.statusCode,
+ headers: headers
+ }, response.data);
+ };
+ if (filePath) {
+ var fileKey;
+ var m = url.match(/^(https?:\/\/[^/]+\/)([^/]*\/?)(.*)$/);
+ if (params.pathStyle) {
+ fileKey = decodeURIComponent(m[3] || '');
+ url = m[1] + m[2];
+ } else {
+ fileKey = decodeURIComponent(m[2] + m[3] || '');
+ url = m[1];
+ }
+
+ // 整理 postObject 参数
+ var formData = {
+ 'key': fileKey,
+ 'success_action_status': 200,
+ 'Signature': headers.Authorization
+ };
+ var headerKeys = ['Cache-Control', 'Content-Type', 'Content-Disposition', 'Content-Encoding', 'Expires', 'x-cos-storage-class', 'x-cos-security-token', 'x-ci-security-token'];
+ for (var i in params.headers) {
+ if (params.headers.hasOwnProperty(i) && (i.indexOf('x-cos-meta-') > -1 || headerKeys.indexOf(i) > -1)) {
+ formData[i] = params.headers[i];
+ }
+ }
+ headers['x-cos-acl'] && (formData.acl = headers['x-cos-acl']);
+ !formData['Content-Type'] && (formData['Content-Type'] = '');
+ requestTask = wx.uploadFile({
+ url: url,
+ method: method,
+ name: 'file',
+ header: headers,
+ filePath: filePath,
+ formData: formData,
+ timeout: params.timeout,
+ success: function success(response) {
+ cb(null, response);
+ },
+ fail: function fail(response) {
+ cb(response.errMsg, response);
+ }
+ });
+ requestTask.onProgressUpdate(function (res) {
+ onProgress && onProgress({
+ loaded: res.totalBytesSent,
+ total: res.totalBytesExpectedToSend,
+ progress: res.progress / 100
+ });
+ });
+ } else {
+ var qsStr = params.qs && obj2str(params.qs) || '';
+ if (qsStr) {
+ url += (url.indexOf('?') > -1 ? '&' : '?') + qsStr;
+ }
+ headers['Content-Length'] && delete headers['Content-Length'];
+ var requestParams = {
+ url: url,
+ method: method,
+ header: headers,
+ dataType: 'text',
+ data: params.body,
+ responseType: params.dataType || 'text',
+ timeout: params.timeout,
+ redirect: 'manual',
+ success: function success(response) {
+ cb(null, response);
+ },
+ fail: function fail(response) {
+ cb(response.errMsg, response);
+ }
+ };
+ if (httpDNSServiceId) {
+ Object.assign(requestParams, {
+ enableHttpDNS: true,
+ httpDNSServiceId: httpDNSServiceId
+ });
+ }
+ requestTask = wx.request(requestParams);
+ }
+ return requestTask;
+};
+module.exports = request;
+
+/***/ }),
+
+/***/ "./node_modules/base64-js/index.js":
+/*!*****************************************!*\
+ !*** ./node_modules/base64-js/index.js ***!
+ \*****************************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+exports.byteLength = byteLength
+exports.toByteArray = toByteArray
+exports.fromByteArray = fromByteArray
+
+var lookup = []
+var revLookup = []
+var Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array
+
+var code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
+for (var i = 0, len = code.length; i < len; ++i) {
+ lookup[i] = code[i]
+ revLookup[code.charCodeAt(i)] = i
+}
+
+// Support decoding URL-safe base64 strings, as Node.js does.
+// See: https://en.wikipedia.org/wiki/Base64#URL_applications
+revLookup['-'.charCodeAt(0)] = 62
+revLookup['_'.charCodeAt(0)] = 63
+
+function getLens (b64) {
+ var len = b64.length
+
+ if (len % 4 > 0) {
+ throw new Error('Invalid string. Length must be a multiple of 4')
+ }
+
+ // Trim off extra bytes after placeholder bytes are found
+ // See: https://github.com/beatgammit/base64-js/issues/42
+ var validLen = b64.indexOf('=')
+ if (validLen === -1) validLen = len
+
+ var placeHoldersLen = validLen === len
+ ? 0
+ : 4 - (validLen % 4)
+
+ return [validLen, placeHoldersLen]
+}
+
+// base64 is 4/3 + up to two characters of the original data
+function byteLength (b64) {
+ var lens = getLens(b64)
+ var validLen = lens[0]
+ var placeHoldersLen = lens[1]
+ return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen
+}
+
+function _byteLength (b64, validLen, placeHoldersLen) {
+ return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen
+}
+
+function toByteArray (b64) {
+ var tmp
+ var lens = getLens(b64)
+ var validLen = lens[0]
+ var placeHoldersLen = lens[1]
+
+ var arr = new Arr(_byteLength(b64, validLen, placeHoldersLen))
+
+ var curByte = 0
+
+ // if there are placeholders, only get up to the last complete 4 chars
+ var len = placeHoldersLen > 0
+ ? validLen - 4
+ : validLen
+
+ var i
+ for (i = 0; i < len; i += 4) {
+ tmp =
+ (revLookup[b64.charCodeAt(i)] << 18) |
+ (revLookup[b64.charCodeAt(i + 1)] << 12) |
+ (revLookup[b64.charCodeAt(i + 2)] << 6) |
+ revLookup[b64.charCodeAt(i + 3)]
+ arr[curByte++] = (tmp >> 16) & 0xFF
+ arr[curByte++] = (tmp >> 8) & 0xFF
+ arr[curByte++] = tmp & 0xFF
+ }
+
+ if (placeHoldersLen === 2) {
+ tmp =
+ (revLookup[b64.charCodeAt(i)] << 2) |
+ (revLookup[b64.charCodeAt(i + 1)] >> 4)
+ arr[curByte++] = tmp & 0xFF
+ }
+
+ if (placeHoldersLen === 1) {
+ tmp =
+ (revLookup[b64.charCodeAt(i)] << 10) |
+ (revLookup[b64.charCodeAt(i + 1)] << 4) |
+ (revLookup[b64.charCodeAt(i + 2)] >> 2)
+ arr[curByte++] = (tmp >> 8) & 0xFF
+ arr[curByte++] = tmp & 0xFF
+ }
+
+ return arr
+}
+
+function tripletToBase64 (num) {
+ return lookup[num >> 18 & 0x3F] +
+ lookup[num >> 12 & 0x3F] +
+ lookup[num >> 6 & 0x3F] +
+ lookup[num & 0x3F]
+}
+
+function encodeChunk (uint8, start, end) {
+ var tmp
+ var output = []
+ for (var i = start; i < end; i += 3) {
+ tmp =
+ ((uint8[i] << 16) & 0xFF0000) +
+ ((uint8[i + 1] << 8) & 0xFF00) +
+ (uint8[i + 2] & 0xFF)
+ output.push(tripletToBase64(tmp))
+ }
+ return output.join('')
+}
+
+function fromByteArray (uint8) {
+ var tmp
+ var len = uint8.length
+ var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes
+ var parts = []
+ var maxChunkLength = 16383 // must be multiple of 3
+
+ // go through the array every three bytes, we'll deal with trailing stuff later
+ for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {
+ parts.push(encodeChunk(uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength)))
+ }
+
+ // pad the end with zeros, but make sure to not forget the extra bytes
+ if (extraBytes === 1) {
+ tmp = uint8[len - 1]
+ parts.push(
+ lookup[tmp >> 2] +
+ lookup[(tmp << 4) & 0x3F] +
+ '=='
+ )
+ } else if (extraBytes === 2) {
+ tmp = (uint8[len - 2] << 8) + uint8[len - 1]
+ parts.push(
+ lookup[tmp >> 10] +
+ lookup[(tmp >> 4) & 0x3F] +
+ lookup[(tmp << 2) & 0x3F] +
+ '='
+ )
+ }
+
+ return parts.join('')
+}
+
+
+/***/ }),
+
+/***/ "./node_modules/buffer/index.js":
+/*!**************************************!*\
+ !*** ./node_modules/buffer/index.js ***!
+ \**************************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+/* WEBPACK VAR INJECTION */(function(global) {/*!
+ * The buffer module from node.js, for the browser.
+ *
+ * @author Feross Aboukhadijeh
+ * @license MIT
+ */
+/* eslint-disable no-proto */
+
+
+
+var base64 = __webpack_require__(/*! base64-js */ "./node_modules/base64-js/index.js")
+var ieee754 = __webpack_require__(/*! ieee754 */ "./node_modules/ieee754/index.js")
+var isArray = __webpack_require__(/*! isarray */ "./node_modules/isarray/index.js")
+
+exports.Buffer = Buffer
+exports.SlowBuffer = SlowBuffer
+exports.INSPECT_MAX_BYTES = 50
+
+/**
+ * If `Buffer.TYPED_ARRAY_SUPPORT`:
+ * === true Use Uint8Array implementation (fastest)
+ * === false Use Object implementation (most compatible, even IE6)
+ *
+ * Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+,
+ * Opera 11.6+, iOS 4.2+.
+ *
+ * Due to various browser bugs, sometimes the Object implementation will be used even
+ * when the browser supports typed arrays.
+ *
+ * Note:
+ *
+ * - Firefox 4-29 lacks support for adding new properties to `Uint8Array` instances,
+ * See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438.
+ *
+ * - Chrome 9-10 is missing the `TypedArray.prototype.subarray` function.
+ *
+ * - IE10 has a broken `TypedArray.prototype.subarray` function which returns arrays of
+ * incorrect length in some situations.
+
+ * We detect these buggy browsers and set `Buffer.TYPED_ARRAY_SUPPORT` to `false` so they
+ * get the Object implementation, which is slower but behaves correctly.
+ */
+Buffer.TYPED_ARRAY_SUPPORT = global.TYPED_ARRAY_SUPPORT !== undefined
+ ? global.TYPED_ARRAY_SUPPORT
+ : typedArraySupport()
+
+/*
+ * Export kMaxLength after typed array support is determined.
+ */
+exports.kMaxLength = kMaxLength()
+
+function typedArraySupport () {
+ try {
+ var arr = new Uint8Array(1)
+ arr.__proto__ = {__proto__: Uint8Array.prototype, foo: function () { return 42 }}
+ return arr.foo() === 42 && // typed array instances can be augmented
+ typeof arr.subarray === 'function' && // chrome 9-10 lack `subarray`
+ arr.subarray(1, 1).byteLength === 0 // ie10 has broken `subarray`
+ } catch (e) {
+ return false
+ }
+}
+
+function kMaxLength () {
+ return Buffer.TYPED_ARRAY_SUPPORT
+ ? 0x7fffffff
+ : 0x3fffffff
+}
+
+function createBuffer (that, length) {
+ if (kMaxLength() < length) {
+ throw new RangeError('Invalid typed array length')
+ }
+ if (Buffer.TYPED_ARRAY_SUPPORT) {
+ // Return an augmented `Uint8Array` instance, for best performance
+ that = new Uint8Array(length)
+ that.__proto__ = Buffer.prototype
+ } else {
+ // Fallback: Return an object instance of the Buffer class
+ if (that === null) {
+ that = new Buffer(length)
+ }
+ that.length = length
+ }
+
+ return that
+}
+
+/**
+ * The Buffer constructor returns instances of `Uint8Array` that have their
+ * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of
+ * `Uint8Array`, so the returned instances will have all the node `Buffer` methods
+ * and the `Uint8Array` methods. Square bracket notation works as expected -- it
+ * returns a single octet.
+ *
+ * The `Uint8Array` prototype remains unmodified.
+ */
+
+function Buffer (arg, encodingOrOffset, length) {
+ if (!Buffer.TYPED_ARRAY_SUPPORT && !(this instanceof Buffer)) {
+ return new Buffer(arg, encodingOrOffset, length)
+ }
+
+ // Common case.
+ if (typeof arg === 'number') {
+ if (typeof encodingOrOffset === 'string') {
+ throw new Error(
+ 'If encoding is specified then the first argument must be a string'
+ )
+ }
+ return allocUnsafe(this, arg)
+ }
+ return from(this, arg, encodingOrOffset, length)
+}
+
+Buffer.poolSize = 8192 // not used by this implementation
+
+// TODO: Legacy, not needed anymore. Remove in next major version.
+Buffer._augment = function (arr) {
+ arr.__proto__ = Buffer.prototype
+ return arr
+}
+
+function from (that, value, encodingOrOffset, length) {
+ if (typeof value === 'number') {
+ throw new TypeError('"value" argument must not be a number')
+ }
+
+ if (typeof ArrayBuffer !== 'undefined' && value instanceof ArrayBuffer) {
+ return fromArrayBuffer(that, value, encodingOrOffset, length)
+ }
+
+ if (typeof value === 'string') {
+ return fromString(that, value, encodingOrOffset)
+ }
+
+ return fromObject(that, value)
+}
+
+/**
+ * Functionally equivalent to Buffer(arg, encoding) but throws a TypeError
+ * if value is a number.
+ * Buffer.from(str[, encoding])
+ * Buffer.from(array)
+ * Buffer.from(buffer)
+ * Buffer.from(arrayBuffer[, byteOffset[, length]])
+ **/
+Buffer.from = function (value, encodingOrOffset, length) {
+ return from(null, value, encodingOrOffset, length)
+}
+
+if (Buffer.TYPED_ARRAY_SUPPORT) {
+ Buffer.prototype.__proto__ = Uint8Array.prototype
+ Buffer.__proto__ = Uint8Array
+ if (typeof Symbol !== 'undefined' && Symbol.species &&
+ Buffer[Symbol.species] === Buffer) {
+ // Fix subarray() in ES2016. See: https://github.com/feross/buffer/pull/97
+ Object.defineProperty(Buffer, Symbol.species, {
+ value: null,
+ configurable: true
+ })
+ }
+}
+
+function assertSize (size) {
+ if (typeof size !== 'number') {
+ throw new TypeError('"size" argument must be a number')
+ } else if (size < 0) {
+ throw new RangeError('"size" argument must not be negative')
+ }
+}
+
+function alloc (that, size, fill, encoding) {
+ assertSize(size)
+ if (size <= 0) {
+ return createBuffer(that, size)
+ }
+ if (fill !== undefined) {
+ // Only pay attention to encoding if it's a string. This
+ // prevents accidentally sending in a number that would
+ // be interpretted as a start offset.
+ return typeof encoding === 'string'
+ ? createBuffer(that, size).fill(fill, encoding)
+ : createBuffer(that, size).fill(fill)
+ }
+ return createBuffer(that, size)
+}
+
+/**
+ * Creates a new filled Buffer instance.
+ * alloc(size[, fill[, encoding]])
+ **/
+Buffer.alloc = function (size, fill, encoding) {
+ return alloc(null, size, fill, encoding)
+}
+
+function allocUnsafe (that, size) {
+ assertSize(size)
+ that = createBuffer(that, size < 0 ? 0 : checked(size) | 0)
+ if (!Buffer.TYPED_ARRAY_SUPPORT) {
+ for (var i = 0; i < size; ++i) {
+ that[i] = 0
+ }
+ }
+ return that
+}
+
+/**
+ * Equivalent to Buffer(num), by default creates a non-zero-filled Buffer instance.
+ * */
+Buffer.allocUnsafe = function (size) {
+ return allocUnsafe(null, size)
+}
+/**
+ * Equivalent to SlowBuffer(num), by default creates a non-zero-filled Buffer instance.
+ */
+Buffer.allocUnsafeSlow = function (size) {
+ return allocUnsafe(null, size)
+}
+
+function fromString (that, string, encoding) {
+ if (typeof encoding !== 'string' || encoding === '') {
+ encoding = 'utf8'
+ }
+
+ if (!Buffer.isEncoding(encoding)) {
+ throw new TypeError('"encoding" must be a valid string encoding')
+ }
+
+ var length = byteLength(string, encoding) | 0
+ that = createBuffer(that, length)
+
+ var actual = that.write(string, encoding)
+
+ if (actual !== length) {
+ // Writing a hex string, for example, that contains invalid characters will
+ // cause everything after the first invalid character to be ignored. (e.g.
+ // 'abxxcd' will be treated as 'ab')
+ that = that.slice(0, actual)
+ }
+
+ return that
+}
+
+function fromArrayLike (that, array) {
+ var length = array.length < 0 ? 0 : checked(array.length) | 0
+ that = createBuffer(that, length)
+ for (var i = 0; i < length; i += 1) {
+ that[i] = array[i] & 255
+ }
+ return that
+}
+
+function fromArrayBuffer (that, array, byteOffset, length) {
+ array.byteLength // this throws if `array` is not a valid ArrayBuffer
+
+ if (byteOffset < 0 || array.byteLength < byteOffset) {
+ throw new RangeError('\'offset\' is out of bounds')
+ }
+
+ if (array.byteLength < byteOffset + (length || 0)) {
+ throw new RangeError('\'length\' is out of bounds')
+ }
+
+ if (byteOffset === undefined && length === undefined) {
+ array = new Uint8Array(array)
+ } else if (length === undefined) {
+ array = new Uint8Array(array, byteOffset)
+ } else {
+ array = new Uint8Array(array, byteOffset, length)
+ }
+
+ if (Buffer.TYPED_ARRAY_SUPPORT) {
+ // Return an augmented `Uint8Array` instance, for best performance
+ that = array
+ that.__proto__ = Buffer.prototype
+ } else {
+ // Fallback: Return an object instance of the Buffer class
+ that = fromArrayLike(that, array)
+ }
+ return that
+}
+
+function fromObject (that, obj) {
+ if (Buffer.isBuffer(obj)) {
+ var len = checked(obj.length) | 0
+ that = createBuffer(that, len)
+
+ if (that.length === 0) {
+ return that
+ }
+
+ obj.copy(that, 0, 0, len)
+ return that
+ }
+
+ if (obj) {
+ if ((typeof ArrayBuffer !== 'undefined' &&
+ obj.buffer instanceof ArrayBuffer) || 'length' in obj) {
+ if (typeof obj.length !== 'number' || isnan(obj.length)) {
+ return createBuffer(that, 0)
+ }
+ return fromArrayLike(that, obj)
+ }
+
+ if (obj.type === 'Buffer' && isArray(obj.data)) {
+ return fromArrayLike(that, obj.data)
+ }
+ }
+
+ throw new TypeError('First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.')
+}
+
+function checked (length) {
+ // Note: cannot use `length < kMaxLength()` here because that fails when
+ // length is NaN (which is otherwise coerced to zero.)
+ if (length >= kMaxLength()) {
+ throw new RangeError('Attempt to allocate Buffer larger than maximum ' +
+ 'size: 0x' + kMaxLength().toString(16) + ' bytes')
+ }
+ return length | 0
+}
+
+function SlowBuffer (length) {
+ if (+length != length) { // eslint-disable-line eqeqeq
+ length = 0
+ }
+ return Buffer.alloc(+length)
+}
+
+Buffer.isBuffer = function isBuffer (b) {
+ return !!(b != null && b._isBuffer)
+}
+
+Buffer.compare = function compare (a, b) {
+ if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) {
+ throw new TypeError('Arguments must be Buffers')
+ }
+
+ if (a === b) return 0
+
+ var x = a.length
+ var y = b.length
+
+ for (var i = 0, len = Math.min(x, y); i < len; ++i) {
+ if (a[i] !== b[i]) {
+ x = a[i]
+ y = b[i]
+ break
+ }
+ }
+
+ if (x < y) return -1
+ if (y < x) return 1
+ return 0
+}
+
+Buffer.isEncoding = function isEncoding (encoding) {
+ switch (String(encoding).toLowerCase()) {
+ case 'hex':
+ case 'utf8':
+ case 'utf-8':
+ case 'ascii':
+ case 'latin1':
+ case 'binary':
+ case 'base64':
+ case 'ucs2':
+ case 'ucs-2':
+ case 'utf16le':
+ case 'utf-16le':
+ return true
+ default:
+ return false
+ }
+}
+
+Buffer.concat = function concat (list, length) {
+ if (!isArray(list)) {
+ throw new TypeError('"list" argument must be an Array of Buffers')
+ }
+
+ if (list.length === 0) {
+ return Buffer.alloc(0)
+ }
+
+ var i
+ if (length === undefined) {
+ length = 0
+ for (i = 0; i < list.length; ++i) {
+ length += list[i].length
+ }
+ }
+
+ var buffer = Buffer.allocUnsafe(length)
+ var pos = 0
+ for (i = 0; i < list.length; ++i) {
+ var buf = list[i]
+ if (!Buffer.isBuffer(buf)) {
+ throw new TypeError('"list" argument must be an Array of Buffers')
+ }
+ buf.copy(buffer, pos)
+ pos += buf.length
+ }
+ return buffer
+}
+
+function byteLength (string, encoding) {
+ if (Buffer.isBuffer(string)) {
+ return string.length
+ }
+ if (typeof ArrayBuffer !== 'undefined' && typeof ArrayBuffer.isView === 'function' &&
+ (ArrayBuffer.isView(string) || string instanceof ArrayBuffer)) {
+ return string.byteLength
+ }
+ if (typeof string !== 'string') {
+ string = '' + string
+ }
+
+ var len = string.length
+ if (len === 0) return 0
+
+ // Use a for loop to avoid recursion
+ var loweredCase = false
+ for (;;) {
+ switch (encoding) {
+ case 'ascii':
+ case 'latin1':
+ case 'binary':
+ return len
+ case 'utf8':
+ case 'utf-8':
+ case undefined:
+ return utf8ToBytes(string).length
+ case 'ucs2':
+ case 'ucs-2':
+ case 'utf16le':
+ case 'utf-16le':
+ return len * 2
+ case 'hex':
+ return len >>> 1
+ case 'base64':
+ return base64ToBytes(string).length
+ default:
+ if (loweredCase) return utf8ToBytes(string).length // assume utf8
+ encoding = ('' + encoding).toLowerCase()
+ loweredCase = true
+ }
+ }
+}
+Buffer.byteLength = byteLength
+
+function slowToString (encoding, start, end) {
+ var loweredCase = false
+
+ // No need to verify that "this.length <= MAX_UINT32" since it's a read-only
+ // property of a typed array.
+
+ // This behaves neither like String nor Uint8Array in that we set start/end
+ // to their upper/lower bounds if the value passed is out of range.
+ // undefined is handled specially as per ECMA-262 6th Edition,
+ // Section 13.3.3.7 Runtime Semantics: KeyedBindingInitialization.
+ if (start === undefined || start < 0) {
+ start = 0
+ }
+ // Return early if start > this.length. Done here to prevent potential uint32
+ // coercion fail below.
+ if (start > this.length) {
+ return ''
+ }
+
+ if (end === undefined || end > this.length) {
+ end = this.length
+ }
+
+ if (end <= 0) {
+ return ''
+ }
+
+ // Force coersion to uint32. This will also coerce falsey/NaN values to 0.
+ end >>>= 0
+ start >>>= 0
+
+ if (end <= start) {
+ return ''
+ }
+
+ if (!encoding) encoding = 'utf8'
+
+ while (true) {
+ switch (encoding) {
+ case 'hex':
+ return hexSlice(this, start, end)
+
+ case 'utf8':
+ case 'utf-8':
+ return utf8Slice(this, start, end)
+
+ case 'ascii':
+ return asciiSlice(this, start, end)
+
+ case 'latin1':
+ case 'binary':
+ return latin1Slice(this, start, end)
+
+ case 'base64':
+ return base64Slice(this, start, end)
+
+ case 'ucs2':
+ case 'ucs-2':
+ case 'utf16le':
+ case 'utf-16le':
+ return utf16leSlice(this, start, end)
+
+ default:
+ if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding)
+ encoding = (encoding + '').toLowerCase()
+ loweredCase = true
+ }
+ }
+}
+
+// The property is used by `Buffer.isBuffer` and `is-buffer` (in Safari 5-7) to detect
+// Buffer instances.
+Buffer.prototype._isBuffer = true
+
+function swap (b, n, m) {
+ var i = b[n]
+ b[n] = b[m]
+ b[m] = i
+}
+
+Buffer.prototype.swap16 = function swap16 () {
+ var len = this.length
+ if (len % 2 !== 0) {
+ throw new RangeError('Buffer size must be a multiple of 16-bits')
+ }
+ for (var i = 0; i < len; i += 2) {
+ swap(this, i, i + 1)
+ }
+ return this
+}
+
+Buffer.prototype.swap32 = function swap32 () {
+ var len = this.length
+ if (len % 4 !== 0) {
+ throw new RangeError('Buffer size must be a multiple of 32-bits')
+ }
+ for (var i = 0; i < len; i += 4) {
+ swap(this, i, i + 3)
+ swap(this, i + 1, i + 2)
+ }
+ return this
+}
+
+Buffer.prototype.swap64 = function swap64 () {
+ var len = this.length
+ if (len % 8 !== 0) {
+ throw new RangeError('Buffer size must be a multiple of 64-bits')
+ }
+ for (var i = 0; i < len; i += 8) {
+ swap(this, i, i + 7)
+ swap(this, i + 1, i + 6)
+ swap(this, i + 2, i + 5)
+ swap(this, i + 3, i + 4)
+ }
+ return this
+}
+
+Buffer.prototype.toString = function toString () {
+ var length = this.length | 0
+ if (length === 0) return ''
+ if (arguments.length === 0) return utf8Slice(this, 0, length)
+ return slowToString.apply(this, arguments)
+}
+
+Buffer.prototype.equals = function equals (b) {
+ if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer')
+ if (this === b) return true
+ return Buffer.compare(this, b) === 0
+}
+
+Buffer.prototype.inspect = function inspect () {
+ var str = ''
+ var max = exports.INSPECT_MAX_BYTES
+ if (this.length > 0) {
+ str = this.toString('hex', 0, max).match(/.{2}/g).join(' ')
+ if (this.length > max) str += ' ... '
+ }
+ return ''
+}
+
+Buffer.prototype.compare = function compare (target, start, end, thisStart, thisEnd) {
+ if (!Buffer.isBuffer(target)) {
+ throw new TypeError('Argument must be a Buffer')
+ }
+
+ if (start === undefined) {
+ start = 0
+ }
+ if (end === undefined) {
+ end = target ? target.length : 0
+ }
+ if (thisStart === undefined) {
+ thisStart = 0
+ }
+ if (thisEnd === undefined) {
+ thisEnd = this.length
+ }
+
+ if (start < 0 || end > target.length || thisStart < 0 || thisEnd > this.length) {
+ throw new RangeError('out of range index')
+ }
+
+ if (thisStart >= thisEnd && start >= end) {
+ return 0
+ }
+ if (thisStart >= thisEnd) {
+ return -1
+ }
+ if (start >= end) {
+ return 1
+ }
+
+ start >>>= 0
+ end >>>= 0
+ thisStart >>>= 0
+ thisEnd >>>= 0
+
+ if (this === target) return 0
+
+ var x = thisEnd - thisStart
+ var y = end - start
+ var len = Math.min(x, y)
+
+ var thisCopy = this.slice(thisStart, thisEnd)
+ var targetCopy = target.slice(start, end)
+
+ for (var i = 0; i < len; ++i) {
+ if (thisCopy[i] !== targetCopy[i]) {
+ x = thisCopy[i]
+ y = targetCopy[i]
+ break
+ }
+ }
+
+ if (x < y) return -1
+ if (y < x) return 1
+ return 0
+}
+
+// Finds either the first index of `val` in `buffer` at offset >= `byteOffset`,
+// OR the last index of `val` in `buffer` at offset <= `byteOffset`.
+//
+// Arguments:
+// - buffer - a Buffer to search
+// - val - a string, Buffer, or number
+// - byteOffset - an index into `buffer`; will be clamped to an int32
+// - encoding - an optional encoding, relevant is val is a string
+// - dir - true for indexOf, false for lastIndexOf
+function bidirectionalIndexOf (buffer, val, byteOffset, encoding, dir) {
+ // Empty buffer means no match
+ if (buffer.length === 0) return -1
+
+ // Normalize byteOffset
+ if (typeof byteOffset === 'string') {
+ encoding = byteOffset
+ byteOffset = 0
+ } else if (byteOffset > 0x7fffffff) {
+ byteOffset = 0x7fffffff
+ } else if (byteOffset < -0x80000000) {
+ byteOffset = -0x80000000
+ }
+ byteOffset = +byteOffset // Coerce to Number.
+ if (isNaN(byteOffset)) {
+ // byteOffset: it it's undefined, null, NaN, "foo", etc, search whole buffer
+ byteOffset = dir ? 0 : (buffer.length - 1)
+ }
+
+ // Normalize byteOffset: negative offsets start from the end of the buffer
+ if (byteOffset < 0) byteOffset = buffer.length + byteOffset
+ if (byteOffset >= buffer.length) {
+ if (dir) return -1
+ else byteOffset = buffer.length - 1
+ } else if (byteOffset < 0) {
+ if (dir) byteOffset = 0
+ else return -1
+ }
+
+ // Normalize val
+ if (typeof val === 'string') {
+ val = Buffer.from(val, encoding)
+ }
+
+ // Finally, search either indexOf (if dir is true) or lastIndexOf
+ if (Buffer.isBuffer(val)) {
+ // Special case: looking for empty string/buffer always fails
+ if (val.length === 0) {
+ return -1
+ }
+ return arrayIndexOf(buffer, val, byteOffset, encoding, dir)
+ } else if (typeof val === 'number') {
+ val = val & 0xFF // Search for a byte value [0-255]
+ if (Buffer.TYPED_ARRAY_SUPPORT &&
+ typeof Uint8Array.prototype.indexOf === 'function') {
+ if (dir) {
+ return Uint8Array.prototype.indexOf.call(buffer, val, byteOffset)
+ } else {
+ return Uint8Array.prototype.lastIndexOf.call(buffer, val, byteOffset)
+ }
+ }
+ return arrayIndexOf(buffer, [ val ], byteOffset, encoding, dir)
+ }
+
+ throw new TypeError('val must be string, number or Buffer')
+}
+
+function arrayIndexOf (arr, val, byteOffset, encoding, dir) {
+ var indexSize = 1
+ var arrLength = arr.length
+ var valLength = val.length
+
+ if (encoding !== undefined) {
+ encoding = String(encoding).toLowerCase()
+ if (encoding === 'ucs2' || encoding === 'ucs-2' ||
+ encoding === 'utf16le' || encoding === 'utf-16le') {
+ if (arr.length < 2 || val.length < 2) {
+ return -1
+ }
+ indexSize = 2
+ arrLength /= 2
+ valLength /= 2
+ byteOffset /= 2
+ }
+ }
+
+ function read (buf, i) {
+ if (indexSize === 1) {
+ return buf[i]
+ } else {
+ return buf.readUInt16BE(i * indexSize)
+ }
+ }
+
+ var i
+ if (dir) {
+ var foundIndex = -1
+ for (i = byteOffset; i < arrLength; i++) {
+ if (read(arr, i) === read(val, foundIndex === -1 ? 0 : i - foundIndex)) {
+ if (foundIndex === -1) foundIndex = i
+ if (i - foundIndex + 1 === valLength) return foundIndex * indexSize
+ } else {
+ if (foundIndex !== -1) i -= i - foundIndex
+ foundIndex = -1
+ }
+ }
+ } else {
+ if (byteOffset + valLength > arrLength) byteOffset = arrLength - valLength
+ for (i = byteOffset; i >= 0; i--) {
+ var found = true
+ for (var j = 0; j < valLength; j++) {
+ if (read(arr, i + j) !== read(val, j)) {
+ found = false
+ break
+ }
+ }
+ if (found) return i
+ }
+ }
+
+ return -1
+}
+
+Buffer.prototype.includes = function includes (val, byteOffset, encoding) {
+ return this.indexOf(val, byteOffset, encoding) !== -1
+}
+
+Buffer.prototype.indexOf = function indexOf (val, byteOffset, encoding) {
+ return bidirectionalIndexOf(this, val, byteOffset, encoding, true)
+}
+
+Buffer.prototype.lastIndexOf = function lastIndexOf (val, byteOffset, encoding) {
+ return bidirectionalIndexOf(this, val, byteOffset, encoding, false)
+}
+
+function hexWrite (buf, string, offset, length) {
+ offset = Number(offset) || 0
+ var remaining = buf.length - offset
+ if (!length) {
+ length = remaining
+ } else {
+ length = Number(length)
+ if (length > remaining) {
+ length = remaining
+ }
+ }
+
+ // must be an even number of digits
+ var strLen = string.length
+ if (strLen % 2 !== 0) throw new TypeError('Invalid hex string')
+
+ if (length > strLen / 2) {
+ length = strLen / 2
+ }
+ for (var i = 0; i < length; ++i) {
+ var parsed = parseInt(string.substr(i * 2, 2), 16)
+ if (isNaN(parsed)) return i
+ buf[offset + i] = parsed
+ }
+ return i
+}
+
+function utf8Write (buf, string, offset, length) {
+ return blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length)
+}
+
+function asciiWrite (buf, string, offset, length) {
+ return blitBuffer(asciiToBytes(string), buf, offset, length)
+}
+
+function latin1Write (buf, string, offset, length) {
+ return asciiWrite(buf, string, offset, length)
+}
+
+function base64Write (buf, string, offset, length) {
+ return blitBuffer(base64ToBytes(string), buf, offset, length)
+}
+
+function ucs2Write (buf, string, offset, length) {
+ return blitBuffer(utf16leToBytes(string, buf.length - offset), buf, offset, length)
+}
+
+Buffer.prototype.write = function write (string, offset, length, encoding) {
+ // Buffer#write(string)
+ if (offset === undefined) {
+ encoding = 'utf8'
+ length = this.length
+ offset = 0
+ // Buffer#write(string, encoding)
+ } else if (length === undefined && typeof offset === 'string') {
+ encoding = offset
+ length = this.length
+ offset = 0
+ // Buffer#write(string, offset[, length][, encoding])
+ } else if (isFinite(offset)) {
+ offset = offset | 0
+ if (isFinite(length)) {
+ length = length | 0
+ if (encoding === undefined) encoding = 'utf8'
+ } else {
+ encoding = length
+ length = undefined
+ }
+ // legacy write(string, encoding, offset, length) - remove in v0.13
+ } else {
+ throw new Error(
+ 'Buffer.write(string, encoding, offset[, length]) is no longer supported'
+ )
+ }
+
+ var remaining = this.length - offset
+ if (length === undefined || length > remaining) length = remaining
+
+ if ((string.length > 0 && (length < 0 || offset < 0)) || offset > this.length) {
+ throw new RangeError('Attempt to write outside buffer bounds')
+ }
+
+ if (!encoding) encoding = 'utf8'
+
+ var loweredCase = false
+ for (;;) {
+ switch (encoding) {
+ case 'hex':
+ return hexWrite(this, string, offset, length)
+
+ case 'utf8':
+ case 'utf-8':
+ return utf8Write(this, string, offset, length)
+
+ case 'ascii':
+ return asciiWrite(this, string, offset, length)
+
+ case 'latin1':
+ case 'binary':
+ return latin1Write(this, string, offset, length)
+
+ case 'base64':
+ // Warning: maxLength not taken into account in base64Write
+ return base64Write(this, string, offset, length)
+
+ case 'ucs2':
+ case 'ucs-2':
+ case 'utf16le':
+ case 'utf-16le':
+ return ucs2Write(this, string, offset, length)
+
+ default:
+ if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding)
+ encoding = ('' + encoding).toLowerCase()
+ loweredCase = true
+ }
+ }
+}
+
+Buffer.prototype.toJSON = function toJSON () {
+ return {
+ type: 'Buffer',
+ data: Array.prototype.slice.call(this._arr || this, 0)
+ }
+}
+
+function base64Slice (buf, start, end) {
+ if (start === 0 && end === buf.length) {
+ return base64.fromByteArray(buf)
+ } else {
+ return base64.fromByteArray(buf.slice(start, end))
+ }
+}
+
+function utf8Slice (buf, start, end) {
+ end = Math.min(buf.length, end)
+ var res = []
+
+ var i = start
+ while (i < end) {
+ var firstByte = buf[i]
+ var codePoint = null
+ var bytesPerSequence = (firstByte > 0xEF) ? 4
+ : (firstByte > 0xDF) ? 3
+ : (firstByte > 0xBF) ? 2
+ : 1
+
+ if (i + bytesPerSequence <= end) {
+ var secondByte, thirdByte, fourthByte, tempCodePoint
+
+ switch (bytesPerSequence) {
+ case 1:
+ if (firstByte < 0x80) {
+ codePoint = firstByte
+ }
+ break
+ case 2:
+ secondByte = buf[i + 1]
+ if ((secondByte & 0xC0) === 0x80) {
+ tempCodePoint = (firstByte & 0x1F) << 0x6 | (secondByte & 0x3F)
+ if (tempCodePoint > 0x7F) {
+ codePoint = tempCodePoint
+ }
+ }
+ break
+ case 3:
+ secondByte = buf[i + 1]
+ thirdByte = buf[i + 2]
+ if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80) {
+ tempCodePoint = (firstByte & 0xF) << 0xC | (secondByte & 0x3F) << 0x6 | (thirdByte & 0x3F)
+ if (tempCodePoint > 0x7FF && (tempCodePoint < 0xD800 || tempCodePoint > 0xDFFF)) {
+ codePoint = tempCodePoint
+ }
+ }
+ break
+ case 4:
+ secondByte = buf[i + 1]
+ thirdByte = buf[i + 2]
+ fourthByte = buf[i + 3]
+ if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80 && (fourthByte & 0xC0) === 0x80) {
+ tempCodePoint = (firstByte & 0xF) << 0x12 | (secondByte & 0x3F) << 0xC | (thirdByte & 0x3F) << 0x6 | (fourthByte & 0x3F)
+ if (tempCodePoint > 0xFFFF && tempCodePoint < 0x110000) {
+ codePoint = tempCodePoint
+ }
+ }
+ }
+ }
+
+ if (codePoint === null) {
+ // we did not generate a valid codePoint so insert a
+ // replacement char (U+FFFD) and advance only 1 byte
+ codePoint = 0xFFFD
+ bytesPerSequence = 1
+ } else if (codePoint > 0xFFFF) {
+ // encode to utf16 (surrogate pair dance)
+ codePoint -= 0x10000
+ res.push(codePoint >>> 10 & 0x3FF | 0xD800)
+ codePoint = 0xDC00 | codePoint & 0x3FF
+ }
+
+ res.push(codePoint)
+ i += bytesPerSequence
+ }
+
+ return decodeCodePointsArray(res)
+}
+
+// Based on http://stackoverflow.com/a/22747272/680742, the browser with
+// the lowest limit is Chrome, with 0x10000 args.
+// We go 1 magnitude less, for safety
+var MAX_ARGUMENTS_LENGTH = 0x1000
+
+function decodeCodePointsArray (codePoints) {
+ var len = codePoints.length
+ if (len <= MAX_ARGUMENTS_LENGTH) {
+ return String.fromCharCode.apply(String, codePoints) // avoid extra slice()
+ }
+
+ // Decode in chunks to avoid "call stack size exceeded".
+ var res = ''
+ var i = 0
+ while (i < len) {
+ res += String.fromCharCode.apply(
+ String,
+ codePoints.slice(i, i += MAX_ARGUMENTS_LENGTH)
+ )
+ }
+ return res
+}
+
+function asciiSlice (buf, start, end) {
+ var ret = ''
+ end = Math.min(buf.length, end)
+
+ for (var i = start; i < end; ++i) {
+ ret += String.fromCharCode(buf[i] & 0x7F)
+ }
+ return ret
+}
+
+function latin1Slice (buf, start, end) {
+ var ret = ''
+ end = Math.min(buf.length, end)
+
+ for (var i = start; i < end; ++i) {
+ ret += String.fromCharCode(buf[i])
+ }
+ return ret
+}
+
+function hexSlice (buf, start, end) {
+ var len = buf.length
+
+ if (!start || start < 0) start = 0
+ if (!end || end < 0 || end > len) end = len
+
+ var out = ''
+ for (var i = start; i < end; ++i) {
+ out += toHex(buf[i])
+ }
+ return out
+}
+
+function utf16leSlice (buf, start, end) {
+ var bytes = buf.slice(start, end)
+ var res = ''
+ for (var i = 0; i < bytes.length; i += 2) {
+ res += String.fromCharCode(bytes[i] + bytes[i + 1] * 256)
+ }
+ return res
+}
+
+Buffer.prototype.slice = function slice (start, end) {
+ var len = this.length
+ start = ~~start
+ end = end === undefined ? len : ~~end
+
+ if (start < 0) {
+ start += len
+ if (start < 0) start = 0
+ } else if (start > len) {
+ start = len
+ }
+
+ if (end < 0) {
+ end += len
+ if (end < 0) end = 0
+ } else if (end > len) {
+ end = len
+ }
+
+ if (end < start) end = start
+
+ var newBuf
+ if (Buffer.TYPED_ARRAY_SUPPORT) {
+ newBuf = this.subarray(start, end)
+ newBuf.__proto__ = Buffer.prototype
+ } else {
+ var sliceLen = end - start
+ newBuf = new Buffer(sliceLen, undefined)
+ for (var i = 0; i < sliceLen; ++i) {
+ newBuf[i] = this[i + start]
+ }
+ }
+
+ return newBuf
+}
+
+/*
+ * Need to make sure that buffer isn't trying to write out of bounds.
+ */
+function checkOffset (offset, ext, length) {
+ if ((offset % 1) !== 0 || offset < 0) throw new RangeError('offset is not uint')
+ if (offset + ext > length) throw new RangeError('Trying to access beyond buffer length')
+}
+
+Buffer.prototype.readUIntLE = function readUIntLE (offset, byteLength, noAssert) {
+ offset = offset | 0
+ byteLength = byteLength | 0
+ if (!noAssert) checkOffset(offset, byteLength, this.length)
+
+ var val = this[offset]
+ var mul = 1
+ var i = 0
+ while (++i < byteLength && (mul *= 0x100)) {
+ val += this[offset + i] * mul
+ }
+
+ return val
+}
+
+Buffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert) {
+ offset = offset | 0
+ byteLength = byteLength | 0
+ if (!noAssert) {
+ checkOffset(offset, byteLength, this.length)
+ }
+
+ var val = this[offset + --byteLength]
+ var mul = 1
+ while (byteLength > 0 && (mul *= 0x100)) {
+ val += this[offset + --byteLength] * mul
+ }
+
+ return val
+}
+
+Buffer.prototype.readUInt8 = function readUInt8 (offset, noAssert) {
+ if (!noAssert) checkOffset(offset, 1, this.length)
+ return this[offset]
+}
+
+Buffer.prototype.readUInt16LE = function readUInt16LE (offset, noAssert) {
+ if (!noAssert) checkOffset(offset, 2, this.length)
+ return this[offset] | (this[offset + 1] << 8)
+}
+
+Buffer.prototype.readUInt16BE = function readUInt16BE (offset, noAssert) {
+ if (!noAssert) checkOffset(offset, 2, this.length)
+ return (this[offset] << 8) | this[offset + 1]
+}
+
+Buffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) {
+ if (!noAssert) checkOffset(offset, 4, this.length)
+
+ return ((this[offset]) |
+ (this[offset + 1] << 8) |
+ (this[offset + 2] << 16)) +
+ (this[offset + 3] * 0x1000000)
+}
+
+Buffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) {
+ if (!noAssert) checkOffset(offset, 4, this.length)
+
+ return (this[offset] * 0x1000000) +
+ ((this[offset + 1] << 16) |
+ (this[offset + 2] << 8) |
+ this[offset + 3])
+}
+
+Buffer.prototype.readIntLE = function readIntLE (offset, byteLength, noAssert) {
+ offset = offset | 0
+ byteLength = byteLength | 0
+ if (!noAssert) checkOffset(offset, byteLength, this.length)
+
+ var val = this[offset]
+ var mul = 1
+ var i = 0
+ while (++i < byteLength && (mul *= 0x100)) {
+ val += this[offset + i] * mul
+ }
+ mul *= 0x80
+
+ if (val >= mul) val -= Math.pow(2, 8 * byteLength)
+
+ return val
+}
+
+Buffer.prototype.readIntBE = function readIntBE (offset, byteLength, noAssert) {
+ offset = offset | 0
+ byteLength = byteLength | 0
+ if (!noAssert) checkOffset(offset, byteLength, this.length)
+
+ var i = byteLength
+ var mul = 1
+ var val = this[offset + --i]
+ while (i > 0 && (mul *= 0x100)) {
+ val += this[offset + --i] * mul
+ }
+ mul *= 0x80
+
+ if (val >= mul) val -= Math.pow(2, 8 * byteLength)
+
+ return val
+}
+
+Buffer.prototype.readInt8 = function readInt8 (offset, noAssert) {
+ if (!noAssert) checkOffset(offset, 1, this.length)
+ if (!(this[offset] & 0x80)) return (this[offset])
+ return ((0xff - this[offset] + 1) * -1)
+}
+
+Buffer.prototype.readInt16LE = function readInt16LE (offset, noAssert) {
+ if (!noAssert) checkOffset(offset, 2, this.length)
+ var val = this[offset] | (this[offset + 1] << 8)
+ return (val & 0x8000) ? val | 0xFFFF0000 : val
+}
+
+Buffer.prototype.readInt16BE = function readInt16BE (offset, noAssert) {
+ if (!noAssert) checkOffset(offset, 2, this.length)
+ var val = this[offset + 1] | (this[offset] << 8)
+ return (val & 0x8000) ? val | 0xFFFF0000 : val
+}
+
+Buffer.prototype.readInt32LE = function readInt32LE (offset, noAssert) {
+ if (!noAssert) checkOffset(offset, 4, this.length)
+
+ return (this[offset]) |
+ (this[offset + 1] << 8) |
+ (this[offset + 2] << 16) |
+ (this[offset + 3] << 24)
+}
+
+Buffer.prototype.readInt32BE = function readInt32BE (offset, noAssert) {
+ if (!noAssert) checkOffset(offset, 4, this.length)
+
+ return (this[offset] << 24) |
+ (this[offset + 1] << 16) |
+ (this[offset + 2] << 8) |
+ (this[offset + 3])
+}
+
+Buffer.prototype.readFloatLE = function readFloatLE (offset, noAssert) {
+ if (!noAssert) checkOffset(offset, 4, this.length)
+ return ieee754.read(this, offset, true, 23, 4)
+}
+
+Buffer.prototype.readFloatBE = function readFloatBE (offset, noAssert) {
+ if (!noAssert) checkOffset(offset, 4, this.length)
+ return ieee754.read(this, offset, false, 23, 4)
+}
+
+Buffer.prototype.readDoubleLE = function readDoubleLE (offset, noAssert) {
+ if (!noAssert) checkOffset(offset, 8, this.length)
+ return ieee754.read(this, offset, true, 52, 8)
+}
+
+Buffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) {
+ if (!noAssert) checkOffset(offset, 8, this.length)
+ return ieee754.read(this, offset, false, 52, 8)
+}
+
+function checkInt (buf, value, offset, ext, max, min) {
+ if (!Buffer.isBuffer(buf)) throw new TypeError('"buffer" argument must be a Buffer instance')
+ if (value > max || value < min) throw new RangeError('"value" argument is out of bounds')
+ if (offset + ext > buf.length) throw new RangeError('Index out of range')
+}
+
+Buffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) {
+ value = +value
+ offset = offset | 0
+ byteLength = byteLength | 0
+ if (!noAssert) {
+ var maxBytes = Math.pow(2, 8 * byteLength) - 1
+ checkInt(this, value, offset, byteLength, maxBytes, 0)
+ }
+
+ var mul = 1
+ var i = 0
+ this[offset] = value & 0xFF
+ while (++i < byteLength && (mul *= 0x100)) {
+ this[offset + i] = (value / mul) & 0xFF
+ }
+
+ return offset + byteLength
+}
+
+Buffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) {
+ value = +value
+ offset = offset | 0
+ byteLength = byteLength | 0
+ if (!noAssert) {
+ var maxBytes = Math.pow(2, 8 * byteLength) - 1
+ checkInt(this, value, offset, byteLength, maxBytes, 0)
+ }
+
+ var i = byteLength - 1
+ var mul = 1
+ this[offset + i] = value & 0xFF
+ while (--i >= 0 && (mul *= 0x100)) {
+ this[offset + i] = (value / mul) & 0xFF
+ }
+
+ return offset + byteLength
+}
+
+Buffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) {
+ value = +value
+ offset = offset | 0
+ if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0)
+ if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value)
+ this[offset] = (value & 0xff)
+ return offset + 1
+}
+
+function objectWriteUInt16 (buf, value, offset, littleEndian) {
+ if (value < 0) value = 0xffff + value + 1
+ for (var i = 0, j = Math.min(buf.length - offset, 2); i < j; ++i) {
+ buf[offset + i] = (value & (0xff << (8 * (littleEndian ? i : 1 - i)))) >>>
+ (littleEndian ? i : 1 - i) * 8
+ }
+}
+
+Buffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) {
+ value = +value
+ offset = offset | 0
+ if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)
+ if (Buffer.TYPED_ARRAY_SUPPORT) {
+ this[offset] = (value & 0xff)
+ this[offset + 1] = (value >>> 8)
+ } else {
+ objectWriteUInt16(this, value, offset, true)
+ }
+ return offset + 2
+}
+
+Buffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert) {
+ value = +value
+ offset = offset | 0
+ if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)
+ if (Buffer.TYPED_ARRAY_SUPPORT) {
+ this[offset] = (value >>> 8)
+ this[offset + 1] = (value & 0xff)
+ } else {
+ objectWriteUInt16(this, value, offset, false)
+ }
+ return offset + 2
+}
+
+function objectWriteUInt32 (buf, value, offset, littleEndian) {
+ if (value < 0) value = 0xffffffff + value + 1
+ for (var i = 0, j = Math.min(buf.length - offset, 4); i < j; ++i) {
+ buf[offset + i] = (value >>> (littleEndian ? i : 3 - i) * 8) & 0xff
+ }
+}
+
+Buffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) {
+ value = +value
+ offset = offset | 0
+ if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)
+ if (Buffer.TYPED_ARRAY_SUPPORT) {
+ this[offset + 3] = (value >>> 24)
+ this[offset + 2] = (value >>> 16)
+ this[offset + 1] = (value >>> 8)
+ this[offset] = (value & 0xff)
+ } else {
+ objectWriteUInt32(this, value, offset, true)
+ }
+ return offset + 4
+}
+
+Buffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert) {
+ value = +value
+ offset = offset | 0
+ if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)
+ if (Buffer.TYPED_ARRAY_SUPPORT) {
+ this[offset] = (value >>> 24)
+ this[offset + 1] = (value >>> 16)
+ this[offset + 2] = (value >>> 8)
+ this[offset + 3] = (value & 0xff)
+ } else {
+ objectWriteUInt32(this, value, offset, false)
+ }
+ return offset + 4
+}
+
+Buffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, noAssert) {
+ value = +value
+ offset = offset | 0
+ if (!noAssert) {
+ var limit = Math.pow(2, 8 * byteLength - 1)
+
+ checkInt(this, value, offset, byteLength, limit - 1, -limit)
+ }
+
+ var i = 0
+ var mul = 1
+ var sub = 0
+ this[offset] = value & 0xFF
+ while (++i < byteLength && (mul *= 0x100)) {
+ if (value < 0 && sub === 0 && this[offset + i - 1] !== 0) {
+ sub = 1
+ }
+ this[offset + i] = ((value / mul) >> 0) - sub & 0xFF
+ }
+
+ return offset + byteLength
+}
+
+Buffer.prototype.writeIntBE = function writeIntBE (value, offset, byteLength, noAssert) {
+ value = +value
+ offset = offset | 0
+ if (!noAssert) {
+ var limit = Math.pow(2, 8 * byteLength - 1)
+
+ checkInt(this, value, offset, byteLength, limit - 1, -limit)
+ }
+
+ var i = byteLength - 1
+ var mul = 1
+ var sub = 0
+ this[offset + i] = value & 0xFF
+ while (--i >= 0 && (mul *= 0x100)) {
+ if (value < 0 && sub === 0 && this[offset + i + 1] !== 0) {
+ sub = 1
+ }
+ this[offset + i] = ((value / mul) >> 0) - sub & 0xFF
+ }
+
+ return offset + byteLength
+}
+
+Buffer.prototype.writeInt8 = function writeInt8 (value, offset, noAssert) {
+ value = +value
+ offset = offset | 0
+ if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80)
+ if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value)
+ if (value < 0) value = 0xff + value + 1
+ this[offset] = (value & 0xff)
+ return offset + 1
+}
+
+Buffer.prototype.writeInt16LE = function writeInt16LE (value, offset, noAssert) {
+ value = +value
+ offset = offset | 0
+ if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)
+ if (Buffer.TYPED_ARRAY_SUPPORT) {
+ this[offset] = (value & 0xff)
+ this[offset + 1] = (value >>> 8)
+ } else {
+ objectWriteUInt16(this, value, offset, true)
+ }
+ return offset + 2
+}
+
+Buffer.prototype.writeInt16BE = function writeInt16BE (value, offset, noAssert) {
+ value = +value
+ offset = offset | 0
+ if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)
+ if (Buffer.TYPED_ARRAY_SUPPORT) {
+ this[offset] = (value >>> 8)
+ this[offset + 1] = (value & 0xff)
+ } else {
+ objectWriteUInt16(this, value, offset, false)
+ }
+ return offset + 2
+}
+
+Buffer.prototype.writeInt32LE = function writeInt32LE (value, offset, noAssert) {
+ value = +value
+ offset = offset | 0
+ if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)
+ if (Buffer.TYPED_ARRAY_SUPPORT) {
+ this[offset] = (value & 0xff)
+ this[offset + 1] = (value >>> 8)
+ this[offset + 2] = (value >>> 16)
+ this[offset + 3] = (value >>> 24)
+ } else {
+ objectWriteUInt32(this, value, offset, true)
+ }
+ return offset + 4
+}
+
+Buffer.prototype.writeInt32BE = function writeInt32BE (value, offset, noAssert) {
+ value = +value
+ offset = offset | 0
+ if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)
+ if (value < 0) value = 0xffffffff + value + 1
+ if (Buffer.TYPED_ARRAY_SUPPORT) {
+ this[offset] = (value >>> 24)
+ this[offset + 1] = (value >>> 16)
+ this[offset + 2] = (value >>> 8)
+ this[offset + 3] = (value & 0xff)
+ } else {
+ objectWriteUInt32(this, value, offset, false)
+ }
+ return offset + 4
+}
+
+function checkIEEE754 (buf, value, offset, ext, max, min) {
+ if (offset + ext > buf.length) throw new RangeError('Index out of range')
+ if (offset < 0) throw new RangeError('Index out of range')
+}
+
+function writeFloat (buf, value, offset, littleEndian, noAssert) {
+ if (!noAssert) {
+ checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38)
+ }
+ ieee754.write(buf, value, offset, littleEndian, 23, 4)
+ return offset + 4
+}
+
+Buffer.prototype.writeFloatLE = function writeFloatLE (value, offset, noAssert) {
+ return writeFloat(this, value, offset, true, noAssert)
+}
+
+Buffer.prototype.writeFloatBE = function writeFloatBE (value, offset, noAssert) {
+ return writeFloat(this, value, offset, false, noAssert)
+}
+
+function writeDouble (buf, value, offset, littleEndian, noAssert) {
+ if (!noAssert) {
+ checkIEEE754(buf, value, offset, 8, 1.7976931348623157E+308, -1.7976931348623157E+308)
+ }
+ ieee754.write(buf, value, offset, littleEndian, 52, 8)
+ return offset + 8
+}
+
+Buffer.prototype.writeDoubleLE = function writeDoubleLE (value, offset, noAssert) {
+ return writeDouble(this, value, offset, true, noAssert)
+}
+
+Buffer.prototype.writeDoubleBE = function writeDoubleBE (value, offset, noAssert) {
+ return writeDouble(this, value, offset, false, noAssert)
+}
+
+// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length)
+Buffer.prototype.copy = function copy (target, targetStart, start, end) {
+ if (!start) start = 0
+ if (!end && end !== 0) end = this.length
+ if (targetStart >= target.length) targetStart = target.length
+ if (!targetStart) targetStart = 0
+ if (end > 0 && end < start) end = start
+
+ // Copy 0 bytes; we're done
+ if (end === start) return 0
+ if (target.length === 0 || this.length === 0) return 0
+
+ // Fatal error conditions
+ if (targetStart < 0) {
+ throw new RangeError('targetStart out of bounds')
+ }
+ if (start < 0 || start >= this.length) throw new RangeError('sourceStart out of bounds')
+ if (end < 0) throw new RangeError('sourceEnd out of bounds')
+
+ // Are we oob?
+ if (end > this.length) end = this.length
+ if (target.length - targetStart < end - start) {
+ end = target.length - targetStart + start
+ }
+
+ var len = end - start
+ var i
+
+ if (this === target && start < targetStart && targetStart < end) {
+ // descending copy from end
+ for (i = len - 1; i >= 0; --i) {
+ target[i + targetStart] = this[i + start]
+ }
+ } else if (len < 1000 || !Buffer.TYPED_ARRAY_SUPPORT) {
+ // ascending copy from start
+ for (i = 0; i < len; ++i) {
+ target[i + targetStart] = this[i + start]
+ }
+ } else {
+ Uint8Array.prototype.set.call(
+ target,
+ this.subarray(start, start + len),
+ targetStart
+ )
+ }
+
+ return len
+}
+
+// Usage:
+// buffer.fill(number[, offset[, end]])
+// buffer.fill(buffer[, offset[, end]])
+// buffer.fill(string[, offset[, end]][, encoding])
+Buffer.prototype.fill = function fill (val, start, end, encoding) {
+ // Handle string cases:
+ if (typeof val === 'string') {
+ if (typeof start === 'string') {
+ encoding = start
+ start = 0
+ end = this.length
+ } else if (typeof end === 'string') {
+ encoding = end
+ end = this.length
+ }
+ if (val.length === 1) {
+ var code = val.charCodeAt(0)
+ if (code < 256) {
+ val = code
+ }
+ }
+ if (encoding !== undefined && typeof encoding !== 'string') {
+ throw new TypeError('encoding must be a string')
+ }
+ if (typeof encoding === 'string' && !Buffer.isEncoding(encoding)) {
+ throw new TypeError('Unknown encoding: ' + encoding)
+ }
+ } else if (typeof val === 'number') {
+ val = val & 255
+ }
+
+ // Invalid ranges are not set to a default, so can range check early.
+ if (start < 0 || this.length < start || this.length < end) {
+ throw new RangeError('Out of range index')
+ }
+
+ if (end <= start) {
+ return this
+ }
+
+ start = start >>> 0
+ end = end === undefined ? this.length : end >>> 0
+
+ if (!val) val = 0
+
+ var i
+ if (typeof val === 'number') {
+ for (i = start; i < end; ++i) {
+ this[i] = val
+ }
+ } else {
+ var bytes = Buffer.isBuffer(val)
+ ? val
+ : utf8ToBytes(new Buffer(val, encoding).toString())
+ var len = bytes.length
+ for (i = 0; i < end - start; ++i) {
+ this[i + start] = bytes[i % len]
+ }
+ }
+
+ return this
+}
+
+// HELPER FUNCTIONS
+// ================
+
+var INVALID_BASE64_RE = /[^+\/0-9A-Za-z-_]/g
+
+function base64clean (str) {
+ // Node strips out invalid characters like \n and \t from the string, base64-js does not
+ str = stringtrim(str).replace(INVALID_BASE64_RE, '')
+ // Node converts strings with length < 2 to ''
+ if (str.length < 2) return ''
+ // Node allows for non-padded base64 strings (missing trailing ===), base64-js does not
+ while (str.length % 4 !== 0) {
+ str = str + '='
+ }
+ return str
+}
+
+function stringtrim (str) {
+ if (str.trim) return str.trim()
+ return str.replace(/^\s+|\s+$/g, '')
+}
+
+function toHex (n) {
+ if (n < 16) return '0' + n.toString(16)
+ return n.toString(16)
+}
+
+function utf8ToBytes (string, units) {
+ units = units || Infinity
+ var codePoint
+ var length = string.length
+ var leadSurrogate = null
+ var bytes = []
+
+ for (var i = 0; i < length; ++i) {
+ codePoint = string.charCodeAt(i)
+
+ // is surrogate component
+ if (codePoint > 0xD7FF && codePoint < 0xE000) {
+ // last char was a lead
+ if (!leadSurrogate) {
+ // no lead yet
+ if (codePoint > 0xDBFF) {
+ // unexpected trail
+ if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
+ continue
+ } else if (i + 1 === length) {
+ // unpaired lead
+ if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
+ continue
+ }
+
+ // valid lead
+ leadSurrogate = codePoint
+
+ continue
+ }
+
+ // 2 leads in a row
+ if (codePoint < 0xDC00) {
+ if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
+ leadSurrogate = codePoint
+ continue
+ }
+
+ // valid surrogate pair
+ codePoint = (leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00) + 0x10000
+ } else if (leadSurrogate) {
+ // valid bmp char, but last char was a lead
+ if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
+ }
+
+ leadSurrogate = null
+
+ // encode utf8
+ if (codePoint < 0x80) {
+ if ((units -= 1) < 0) break
+ bytes.push(codePoint)
+ } else if (codePoint < 0x800) {
+ if ((units -= 2) < 0) break
+ bytes.push(
+ codePoint >> 0x6 | 0xC0,
+ codePoint & 0x3F | 0x80
+ )
+ } else if (codePoint < 0x10000) {
+ if ((units -= 3) < 0) break
+ bytes.push(
+ codePoint >> 0xC | 0xE0,
+ codePoint >> 0x6 & 0x3F | 0x80,
+ codePoint & 0x3F | 0x80
+ )
+ } else if (codePoint < 0x110000) {
+ if ((units -= 4) < 0) break
+ bytes.push(
+ codePoint >> 0x12 | 0xF0,
+ codePoint >> 0xC & 0x3F | 0x80,
+ codePoint >> 0x6 & 0x3F | 0x80,
+ codePoint & 0x3F | 0x80
+ )
+ } else {
+ throw new Error('Invalid code point')
+ }
+ }
+
+ return bytes
+}
+
+function asciiToBytes (str) {
+ var byteArray = []
+ for (var i = 0; i < str.length; ++i) {
+ // Node's code seems to be doing this and not & 0x7F..
+ byteArray.push(str.charCodeAt(i) & 0xFF)
+ }
+ return byteArray
+}
+
+function utf16leToBytes (str, units) {
+ var c, hi, lo
+ var byteArray = []
+ for (var i = 0; i < str.length; ++i) {
+ if ((units -= 2) < 0) break
+
+ c = str.charCodeAt(i)
+ hi = c >> 8
+ lo = c % 256
+ byteArray.push(lo)
+ byteArray.push(hi)
+ }
+
+ return byteArray
+}
+
+function base64ToBytes (str) {
+ return base64.toByteArray(base64clean(str))
+}
+
+function blitBuffer (src, dst, offset, length) {
+ for (var i = 0; i < length; ++i) {
+ if ((i + offset >= dst.length) || (i >= src.length)) break
+ dst[i + offset] = src[i]
+ }
+ return i
+}
+
+function isnan (val) {
+ return val !== val // eslint-disable-line no-self-compare
+}
+
+/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./../webpack/buildin/global.js */ "./node_modules/webpack/buildin/global.js")))
+
+/***/ }),
+
+/***/ "./node_modules/fast-xml-parser/src/fxp.js":
+/*!*************************************************!*\
+ !*** ./node_modules/fast-xml-parser/src/fxp.js ***!
+ \*************************************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+const validator = __webpack_require__(/*! ./validator */ "./node_modules/fast-xml-parser/src/validator.js");
+const XMLParser = __webpack_require__(/*! ./xmlparser/XMLParser */ "./node_modules/fast-xml-parser/src/xmlparser/XMLParser.js");
+const XMLBuilder = __webpack_require__(/*! ./xmlbuilder/json2xml */ "./node_modules/fast-xml-parser/src/xmlbuilder/json2xml.js");
+
+module.exports = {
+ XMLParser: XMLParser,
+ XMLValidator: validator,
+ XMLBuilder: XMLBuilder
+}
+
+/***/ }),
+
+/***/ "./node_modules/fast-xml-parser/src/ignoreAttributes.js":
+/*!**************************************************************!*\
+ !*** ./node_modules/fast-xml-parser/src/ignoreAttributes.js ***!
+ \**************************************************************/
+/*! no static exports found */
+/***/ (function(module, exports) {
+
+function getIgnoreAttributesFn(ignoreAttributes) {
+ if (typeof ignoreAttributes === 'function') {
+ return ignoreAttributes
+ }
+ if (Array.isArray(ignoreAttributes)) {
+ return (attrName) => {
+ for (const pattern of ignoreAttributes) {
+ if (typeof pattern === 'string' && attrName === pattern) {
+ return true
+ }
+ if (pattern instanceof RegExp && pattern.test(attrName)) {
+ return true
+ }
+ }
+ }
+ }
+ return () => false
+}
+
+module.exports = getIgnoreAttributesFn
+
+/***/ }),
+
+/***/ "./node_modules/fast-xml-parser/src/util.js":
+/*!**************************************************!*\
+ !*** ./node_modules/fast-xml-parser/src/util.js ***!
+ \**************************************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+const nameStartChar = ':A-Za-z_\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD';
+const nameChar = nameStartChar + '\\-.\\d\\u00B7\\u0300-\\u036F\\u203F-\\u2040';
+const nameRegexp = '[' + nameStartChar + '][' + nameChar + ']*'
+const regexName = new RegExp('^' + nameRegexp + '$');
+
+const getAllMatches = function(string, regex) {
+ const matches = [];
+ let match = regex.exec(string);
+ while (match) {
+ const allmatches = [];
+ allmatches.startIndex = regex.lastIndex - match[0].length;
+ const len = match.length;
+ for (let index = 0; index < len; index++) {
+ allmatches.push(match[index]);
+ }
+ matches.push(allmatches);
+ match = regex.exec(string);
+ }
+ return matches;
+};
+
+const isName = function(string) {
+ const match = regexName.exec(string);
+ return !(match === null || typeof match === 'undefined');
+};
+
+exports.isExist = function(v) {
+ return typeof v !== 'undefined';
+};
+
+exports.isEmptyObject = function(obj) {
+ return Object.keys(obj).length === 0;
+};
+
+/**
+ * Copy all the properties of a into b.
+ * @param {*} target
+ * @param {*} a
+ */
+exports.merge = function(target, a, arrayMode) {
+ if (a) {
+ const keys = Object.keys(a); // will return an array of own properties
+ const len = keys.length; //don't make it inline
+ for (let i = 0; i < len; i++) {
+ if (arrayMode === 'strict') {
+ target[keys[i]] = [ a[keys[i]] ];
+ } else {
+ target[keys[i]] = a[keys[i]];
+ }
+ }
+ }
+};
+/* exports.merge =function (b,a){
+ return Object.assign(b,a);
+} */
+
+exports.getValue = function(v) {
+ if (exports.isExist(v)) {
+ return v;
+ } else {
+ return '';
+ }
+};
+
+// const fakeCall = function(a) {return a;};
+// const fakeCallNoReturn = function() {};
+
+exports.isName = isName;
+exports.getAllMatches = getAllMatches;
+exports.nameRegexp = nameRegexp;
+
+
+/***/ }),
+
+/***/ "./node_modules/fast-xml-parser/src/validator.js":
+/*!*******************************************************!*\
+ !*** ./node_modules/fast-xml-parser/src/validator.js ***!
+ \*******************************************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+const util = __webpack_require__(/*! ./util */ "./node_modules/fast-xml-parser/src/util.js");
+
+const defaultOptions = {
+ allowBooleanAttributes: false, //A tag can have attributes without any value
+ unpairedTags: []
+};
+
+//const tagsPattern = new RegExp("<\\/?([\\w:\\-_\.]+)\\s*\/?>","g");
+exports.validate = function (xmlData, options) {
+ options = Object.assign({}, defaultOptions, options);
+
+ //xmlData = xmlData.replace(/(\r\n|\n|\r)/gm,"");//make it single line
+ //xmlData = xmlData.replace(/(^\s*<\?xml.*?\?>)/g,"");//Remove XML starting tag
+ //xmlData = xmlData.replace(/()/g,"");//Remove DOCTYPE
+ const tags = [];
+ let tagFound = false;
+
+ //indicates that the root tag has been closed (aka. depth 0 has been reached)
+ let reachedRoot = false;
+
+ if (xmlData[0] === '\ufeff') {
+ // check for byte order mark (BOM)
+ xmlData = xmlData.substr(1);
+ }
+
+ for (let i = 0; i < xmlData.length; i++) {
+
+ if (xmlData[i] === '<' && xmlData[i+1] === '?') {
+ i+=2;
+ i = readPI(xmlData,i);
+ if (i.err) return i;
+ }else if (xmlData[i] === '<') {
+ //starting of tag
+ //read until you reach to '>' avoiding any '>' in attribute value
+ let tagStartPos = i;
+ i++;
+
+ if (xmlData[i] === '!') {
+ i = readCommentAndCDATA(xmlData, i);
+ continue;
+ } else {
+ let closingTag = false;
+ if (xmlData[i] === '/') {
+ //closing tag
+ closingTag = true;
+ i++;
+ }
+ //read tagname
+ let tagName = '';
+ for (; i < xmlData.length &&
+ xmlData[i] !== '>' &&
+ xmlData[i] !== ' ' &&
+ xmlData[i] !== '\t' &&
+ xmlData[i] !== '\n' &&
+ xmlData[i] !== '\r'; i++
+ ) {
+ tagName += xmlData[i];
+ }
+ tagName = tagName.trim();
+ //console.log(tagName);
+
+ if (tagName[tagName.length - 1] === '/') {
+ //self closing tag without attributes
+ tagName = tagName.substring(0, tagName.length - 1);
+ //continue;
+ i--;
+ }
+ if (!validateTagName(tagName)) {
+ let msg;
+ if (tagName.trim().length === 0) {
+ msg = "Invalid space after '<'.";
+ } else {
+ msg = "Tag '"+tagName+"' is an invalid name.";
+ }
+ return getErrorObject('InvalidTag', msg, getLineNumberForPosition(xmlData, i));
+ }
+
+ const result = readAttributeStr(xmlData, i);
+ if (result === false) {
+ return getErrorObject('InvalidAttr', "Attributes for '"+tagName+"' have open quote.", getLineNumberForPosition(xmlData, i));
+ }
+ let attrStr = result.value;
+ i = result.index;
+
+ if (attrStr[attrStr.length - 1] === '/') {
+ //self closing tag
+ const attrStrStart = i - attrStr.length;
+ attrStr = attrStr.substring(0, attrStr.length - 1);
+ const isValid = validateAttributeString(attrStr, options);
+ if (isValid === true) {
+ tagFound = true;
+ //continue; //text may presents after self closing tag
+ } else {
+ //the result from the nested function returns the position of the error within the attribute
+ //in order to get the 'true' error line, we need to calculate the position where the attribute begins (i - attrStr.length) and then add the position within the attribute
+ //this gives us the absolute index in the entire xml, which we can use to find the line at last
+ return getErrorObject(isValid.err.code, isValid.err.msg, getLineNumberForPosition(xmlData, attrStrStart + isValid.err.line));
+ }
+ } else if (closingTag) {
+ if (!result.tagClosed) {
+ return getErrorObject('InvalidTag', "Closing tag '"+tagName+"' doesn't have proper closing.", getLineNumberForPosition(xmlData, i));
+ } else if (attrStr.trim().length > 0) {
+ return getErrorObject('InvalidTag', "Closing tag '"+tagName+"' can't have attributes or invalid starting.", getLineNumberForPosition(xmlData, tagStartPos));
+ } else if (tags.length === 0) {
+ return getErrorObject('InvalidTag', "Closing tag '"+tagName+"' has not been opened.", getLineNumberForPosition(xmlData, tagStartPos));
+ } else {
+ const otg = tags.pop();
+ if (tagName !== otg.tagName) {
+ let openPos = getLineNumberForPosition(xmlData, otg.tagStartPos);
+ return getErrorObject('InvalidTag',
+ "Expected closing tag '"+otg.tagName+"' (opened in line "+openPos.line+", col "+openPos.col+") instead of closing tag '"+tagName+"'.",
+ getLineNumberForPosition(xmlData, tagStartPos));
+ }
+
+ //when there are no more tags, we reached the root level.
+ if (tags.length == 0) {
+ reachedRoot = true;
+ }
+ }
+ } else {
+ const isValid = validateAttributeString(attrStr, options);
+ if (isValid !== true) {
+ //the result from the nested function returns the position of the error within the attribute
+ //in order to get the 'true' error line, we need to calculate the position where the attribute begins (i - attrStr.length) and then add the position within the attribute
+ //this gives us the absolute index in the entire xml, which we can use to find the line at last
+ return getErrorObject(isValid.err.code, isValid.err.msg, getLineNumberForPosition(xmlData, i - attrStr.length + isValid.err.line));
+ }
+
+ //if the root level has been reached before ...
+ if (reachedRoot === true) {
+ return getErrorObject('InvalidXml', 'Multiple possible root nodes found.', getLineNumberForPosition(xmlData, i));
+ } else if(options.unpairedTags.indexOf(tagName) !== -1){
+ //don't push into stack
+ } else {
+ tags.push({tagName, tagStartPos});
+ }
+ tagFound = true;
+ }
+
+ //skip tag text value
+ //It may include comments and CDATA value
+ for (i++; i < xmlData.length; i++) {
+ if (xmlData[i] === '<') {
+ if (xmlData[i + 1] === '!') {
+ //comment or CADATA
+ i++;
+ i = readCommentAndCDATA(xmlData, i);
+ continue;
+ } else if (xmlData[i+1] === '?') {
+ i = readPI(xmlData, ++i);
+ if (i.err) return i;
+ } else{
+ break;
+ }
+ } else if (xmlData[i] === '&') {
+ const afterAmp = validateAmpersand(xmlData, i);
+ if (afterAmp == -1)
+ return getErrorObject('InvalidChar', "char '&' is not expected.", getLineNumberForPosition(xmlData, i));
+ i = afterAmp;
+ }else{
+ if (reachedRoot === true && !isWhiteSpace(xmlData[i])) {
+ return getErrorObject('InvalidXml', "Extra text at the end", getLineNumberForPosition(xmlData, i));
+ }
+ }
+ } //end of reading tag text value
+ if (xmlData[i] === '<') {
+ i--;
+ }
+ }
+ } else {
+ if ( isWhiteSpace(xmlData[i])) {
+ continue;
+ }
+ return getErrorObject('InvalidChar', "char '"+xmlData[i]+"' is not expected.", getLineNumberForPosition(xmlData, i));
+ }
+ }
+
+ if (!tagFound) {
+ return getErrorObject('InvalidXml', 'Start tag expected.', 1);
+ }else if (tags.length == 1) {
+ return getErrorObject('InvalidTag', "Unclosed tag '"+tags[0].tagName+"'.", getLineNumberForPosition(xmlData, tags[0].tagStartPos));
+ }else if (tags.length > 0) {
+ return getErrorObject('InvalidXml', "Invalid '"+
+ JSON.stringify(tags.map(t => t.tagName), null, 4).replace(/\r?\n/g, '')+
+ "' found.", {line: 1, col: 1});
+ }
+
+ return true;
+};
+
+function isWhiteSpace(char){
+ return char === ' ' || char === '\t' || char === '\n' || char === '\r';
+}
+/**
+ * Read Processing insstructions and skip
+ * @param {*} xmlData
+ * @param {*} i
+ */
+function readPI(xmlData, i) {
+ const start = i;
+ for (; i < xmlData.length; i++) {
+ if (xmlData[i] == '?' || xmlData[i] == ' ') {
+ //tagname
+ const tagname = xmlData.substr(start, i - start);
+ if (i > 5 && tagname === 'xml') {
+ return getErrorObject('InvalidXml', 'XML declaration allowed only at the start of the document.', getLineNumberForPosition(xmlData, i));
+ } else if (xmlData[i] == '?' && xmlData[i + 1] == '>') {
+ //check if valid attribut string
+ i++;
+ break;
+ } else {
+ continue;
+ }
+ }
+ }
+ return i;
+}
+
+function readCommentAndCDATA(xmlData, i) {
+ if (xmlData.length > i + 5 && xmlData[i + 1] === '-' && xmlData[i + 2] === '-') {
+ //comment
+ for (i += 3; i < xmlData.length; i++) {
+ if (xmlData[i] === '-' && xmlData[i + 1] === '-' && xmlData[i + 2] === '>') {
+ i += 2;
+ break;
+ }
+ }
+ } else if (
+ xmlData.length > i + 8 &&
+ xmlData[i + 1] === 'D' &&
+ xmlData[i + 2] === 'O' &&
+ xmlData[i + 3] === 'C' &&
+ xmlData[i + 4] === 'T' &&
+ xmlData[i + 5] === 'Y' &&
+ xmlData[i + 6] === 'P' &&
+ xmlData[i + 7] === 'E'
+ ) {
+ let angleBracketsCount = 1;
+ for (i += 8; i < xmlData.length; i++) {
+ if (xmlData[i] === '<') {
+ angleBracketsCount++;
+ } else if (xmlData[i] === '>') {
+ angleBracketsCount--;
+ if (angleBracketsCount === 0) {
+ break;
+ }
+ }
+ }
+ } else if (
+ xmlData.length > i + 9 &&
+ xmlData[i + 1] === '[' &&
+ xmlData[i + 2] === 'C' &&
+ xmlData[i + 3] === 'D' &&
+ xmlData[i + 4] === 'A' &&
+ xmlData[i + 5] === 'T' &&
+ xmlData[i + 6] === 'A' &&
+ xmlData[i + 7] === '['
+ ) {
+ for (i += 8; i < xmlData.length; i++) {
+ if (xmlData[i] === ']' && xmlData[i + 1] === ']' && xmlData[i + 2] === '>') {
+ i += 2;
+ break;
+ }
+ }
+ }
+
+ return i;
+}
+
+const doubleQuote = '"';
+const singleQuote = "'";
+
+/**
+ * Keep reading xmlData until '<' is found outside the attribute value.
+ * @param {string} xmlData
+ * @param {number} i
+ */
+function readAttributeStr(xmlData, i) {
+ let attrStr = '';
+ let startChar = '';
+ let tagClosed = false;
+ for (; i < xmlData.length; i++) {
+ if (xmlData[i] === doubleQuote || xmlData[i] === singleQuote) {
+ if (startChar === '') {
+ startChar = xmlData[i];
+ } else if (startChar !== xmlData[i]) {
+ //if vaue is enclosed with double quote then single quotes are allowed inside the value and vice versa
+ } else {
+ startChar = '';
+ }
+ } else if (xmlData[i] === '>') {
+ if (startChar === '') {
+ tagClosed = true;
+ break;
+ }
+ }
+ attrStr += xmlData[i];
+ }
+ if (startChar !== '') {
+ return false;
+ }
+
+ return {
+ value: attrStr,
+ index: i,
+ tagClosed: tagClosed
+ };
+}
+
+/**
+ * Select all the attributes whether valid or invalid.
+ */
+const validAttrStrRegxp = new RegExp('(\\s*)([^\\s=]+)(\\s*=)?(\\s*([\'"])(([\\s\\S])*?)\\5)?', 'g');
+
+//attr, ="sd", a="amit's", a="sd"b="saf", ab cd=""
+
+function validateAttributeString(attrStr, options) {
+ //console.log("start:"+attrStr+":end");
+
+ //if(attrStr.trim().length === 0) return true; //empty string
+
+ const matches = util.getAllMatches(attrStr, validAttrStrRegxp);
+ const attrNames = {};
+
+ for (let i = 0; i < matches.length; i++) {
+ if (matches[i][1].length === 0) {
+ //nospace before attribute name: a="sd"b="saf"
+ return getErrorObject('InvalidAttr', "Attribute '"+matches[i][2]+"' has no space in starting.", getPositionFromMatch(matches[i]))
+ } else if (matches[i][3] !== undefined && matches[i][4] === undefined) {
+ return getErrorObject('InvalidAttr', "Attribute '"+matches[i][2]+"' is without value.", getPositionFromMatch(matches[i]));
+ } else if (matches[i][3] === undefined && !options.allowBooleanAttributes) {
+ //independent attribute: ab
+ return getErrorObject('InvalidAttr', "boolean attribute '"+matches[i][2]+"' is not allowed.", getPositionFromMatch(matches[i]));
+ }
+ /* else if(matches[i][6] === undefined){//attribute without value: ab=
+ return { err: { code:"InvalidAttr",msg:"attribute " + matches[i][2] + " has no value assigned."}};
+ } */
+ const attrName = matches[i][2];
+ if (!validateAttrName(attrName)) {
+ return getErrorObject('InvalidAttr', "Attribute '"+attrName+"' is an invalid name.", getPositionFromMatch(matches[i]));
+ }
+ if (!attrNames.hasOwnProperty(attrName)) {
+ //check for duplicate attribute.
+ attrNames[attrName] = 1;
+ } else {
+ return getErrorObject('InvalidAttr', "Attribute '"+attrName+"' is repeated.", getPositionFromMatch(matches[i]));
+ }
+ }
+
+ return true;
+}
+
+function validateNumberAmpersand(xmlData, i) {
+ let re = /\d/;
+ if (xmlData[i] === 'x') {
+ i++;
+ re = /[\da-fA-F]/;
+ }
+ for (; i < xmlData.length; i++) {
+ if (xmlData[i] === ';')
+ return i;
+ if (!xmlData[i].match(re))
+ break;
+ }
+ return -1;
+}
+
+function validateAmpersand(xmlData, i) {
+ // https://www.w3.org/TR/xml/#dt-charref
+ i++;
+ if (xmlData[i] === ';')
+ return -1;
+ if (xmlData[i] === '#') {
+ i++;
+ return validateNumberAmpersand(xmlData, i);
+ }
+ let count = 0;
+ for (; i < xmlData.length; i++, count++) {
+ if (xmlData[i].match(/\w/) && count < 20)
+ continue;
+ if (xmlData[i] === ';')
+ break;
+ return -1;
+ }
+ return i;
+}
+
+function getErrorObject(code, message, lineNumber) {
+ return {
+ err: {
+ code: code,
+ msg: message,
+ line: lineNumber.line || lineNumber,
+ col: lineNumber.col,
+ },
+ };
+}
+
+function validateAttrName(attrName) {
+ return util.isName(attrName);
+}
+
+// const startsWithXML = /^xml/i;
+
+function validateTagName(tagname) {
+ return util.isName(tagname) /* && !tagname.match(startsWithXML) */;
+}
+
+//this function returns the line number for the character at the given index
+function getLineNumberForPosition(xmlData, index) {
+ const lines = xmlData.substring(0, index).split(/\r?\n/);
+ return {
+ line: lines.length,
+
+ // column number is last line's length + 1, because column numbering starts at 1:
+ col: lines[lines.length - 1].length + 1
+ };
+}
+
+//this function returns the position of the first character of match within attrStr
+function getPositionFromMatch(match) {
+ return match.startIndex + match[1].length;
+}
+
+
+/***/ }),
+
+/***/ "./node_modules/fast-xml-parser/src/xmlbuilder/json2xml.js":
+/*!*****************************************************************!*\
+ !*** ./node_modules/fast-xml-parser/src/xmlbuilder/json2xml.js ***!
+ \*****************************************************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+//parse Empty Node as self closing node
+const buildFromOrderedJs = __webpack_require__(/*! ./orderedJs2Xml */ "./node_modules/fast-xml-parser/src/xmlbuilder/orderedJs2Xml.js");
+const getIgnoreAttributesFn = __webpack_require__(/*! ../ignoreAttributes */ "./node_modules/fast-xml-parser/src/ignoreAttributes.js")
+
+const defaultOptions = {
+ attributeNamePrefix: '@_',
+ attributesGroupName: false,
+ textNodeName: '#text',
+ ignoreAttributes: true,
+ cdataPropName: false,
+ format: false,
+ indentBy: ' ',
+ suppressEmptyNode: false,
+ suppressUnpairedNode: true,
+ suppressBooleanAttributes: true,
+ tagValueProcessor: function(key, a) {
+ return a;
+ },
+ attributeValueProcessor: function(attrName, a) {
+ return a;
+ },
+ preserveOrder: false,
+ commentPropName: false,
+ unpairedTags: [],
+ entities: [
+ { regex: new RegExp("&", "g"), val: "&" },//it must be on top
+ { regex: new RegExp(">", "g"), val: ">" },
+ { regex: new RegExp("<", "g"), val: "<" },
+ { regex: new RegExp("\'", "g"), val: "'" },
+ { regex: new RegExp("\"", "g"), val: """ }
+ ],
+ processEntities: true,
+ stopNodes: [],
+ // transformTagName: false,
+ // transformAttributeName: false,
+ oneListGroup: false
+};
+
+function Builder(options) {
+ this.options = Object.assign({}, defaultOptions, options);
+ if (this.options.ignoreAttributes === true || this.options.attributesGroupName) {
+ this.isAttribute = function(/*a*/) {
+ return false;
+ };
+ } else {
+ this.ignoreAttributesFn = getIgnoreAttributesFn(this.options.ignoreAttributes)
+ this.attrPrefixLen = this.options.attributeNamePrefix.length;
+ this.isAttribute = isAttribute;
+ }
+
+ this.processTextOrObjNode = processTextOrObjNode
+
+ if (this.options.format) {
+ this.indentate = indentate;
+ this.tagEndChar = '>\n';
+ this.newLine = '\n';
+ } else {
+ this.indentate = function() {
+ return '';
+ };
+ this.tagEndChar = '>';
+ this.newLine = '';
+ }
+}
+
+Builder.prototype.build = function(jObj) {
+ if(this.options.preserveOrder){
+ return buildFromOrderedJs(jObj, this.options);
+ }else {
+ if(Array.isArray(jObj) && this.options.arrayNodeName && this.options.arrayNodeName.length > 1){
+ jObj = {
+ [this.options.arrayNodeName] : jObj
+ }
+ }
+ return this.j2x(jObj, 0, []).val;
+ }
+};
+
+Builder.prototype.j2x = function(jObj, level, ajPath) {
+ let attrStr = '';
+ let val = '';
+ const jPath = ajPath.join('.')
+ for (let key in jObj) {
+ if(!Object.prototype.hasOwnProperty.call(jObj, key)) continue;
+ if (typeof jObj[key] === 'undefined') {
+ // supress undefined node only if it is not an attribute
+ if (this.isAttribute(key)) {
+ val += '';
+ }
+ } else if (jObj[key] === null) {
+ // null attribute should be ignored by the attribute list, but should not cause the tag closing
+ if (this.isAttribute(key)) {
+ val += '';
+ } else if (key[0] === '?') {
+ val += this.indentate(level) + '<' + key + '?' + this.tagEndChar;
+ } else {
+ val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
+ }
+ // val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
+ } else if (jObj[key] instanceof Date) {
+ val += this.buildTextValNode(jObj[key], key, '', level);
+ } else if (typeof jObj[key] !== 'object') {
+ //premitive type
+ const attr = this.isAttribute(key);
+ if (attr && !this.ignoreAttributesFn(attr, jPath)) {
+ attrStr += this.buildAttrPairStr(attr, '' + jObj[key]);
+ } else if (!attr) {
+ //tag value
+ if (key === this.options.textNodeName) {
+ let newval = this.options.tagValueProcessor(key, '' + jObj[key]);
+ val += this.replaceEntitiesValue(newval);
+ } else {
+ val += this.buildTextValNode(jObj[key], key, '', level);
+ }
+ }
+ } else if (Array.isArray(jObj[key])) {
+ //repeated nodes
+ const arrLen = jObj[key].length;
+ let listTagVal = "";
+ let listTagAttr = "";
+ for (let j = 0; j < arrLen; j++) {
+ const item = jObj[key][j];
+ if (typeof item === 'undefined') {
+ // supress undefined node
+ } else if (item === null) {
+ if(key[0] === "?") val += this.indentate(level) + '<' + key + '?' + this.tagEndChar;
+ else val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
+ // val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
+ } else if (typeof item === 'object') {
+ if(this.options.oneListGroup){
+ const result = this.j2x(item, level + 1, ajPath.concat(key));
+ listTagVal += result.val;
+ if (this.options.attributesGroupName && item.hasOwnProperty(this.options.attributesGroupName)) {
+ listTagAttr += result.attrStr
+ }
+ }else{
+ listTagVal += this.processTextOrObjNode(item, key, level, ajPath)
+ }
+ } else {
+ if (this.options.oneListGroup) {
+ let textValue = this.options.tagValueProcessor(key, item);
+ textValue = this.replaceEntitiesValue(textValue);
+ listTagVal += textValue;
+ } else {
+ listTagVal += this.buildTextValNode(item, key, '', level);
+ }
+ }
+ }
+ if(this.options.oneListGroup){
+ listTagVal = this.buildObjectNode(listTagVal, key, listTagAttr, level);
+ }
+ val += listTagVal;
+ } else {
+ //nested node
+ if (this.options.attributesGroupName && key === this.options.attributesGroupName) {
+ const Ks = Object.keys(jObj[key]);
+ const L = Ks.length;
+ for (let j = 0; j < L; j++) {
+ attrStr += this.buildAttrPairStr(Ks[j], '' + jObj[key][Ks[j]]);
+ }
+ } else {
+ val += this.processTextOrObjNode(jObj[key], key, level, ajPath)
+ }
+ }
+ }
+ return {attrStr: attrStr, val: val};
+};
+
+Builder.prototype.buildAttrPairStr = function(attrName, val){
+ val = this.options.attributeValueProcessor(attrName, '' + val);
+ val = this.replaceEntitiesValue(val);
+ if (this.options.suppressBooleanAttributes && val === "true") {
+ return ' ' + attrName;
+ } else return ' ' + attrName + '="' + val + '"';
+}
+
+function processTextOrObjNode (object, key, level, ajPath) {
+ const result = this.j2x(object, level + 1, ajPath.concat(key));
+ if (object[this.options.textNodeName] !== undefined && Object.keys(object).length === 1) {
+ return this.buildTextValNode(object[this.options.textNodeName], key, result.attrStr, level);
+ } else {
+ return this.buildObjectNode(result.val, key, result.attrStr, level);
+ }
+}
+
+Builder.prototype.buildObjectNode = function(val, key, attrStr, level) {
+ if(val === ""){
+ if(key[0] === "?") return this.indentate(level) + '<' + key + attrStr+ '?' + this.tagEndChar;
+ else {
+ return this.indentate(level) + '<' + key + attrStr + this.closeTag(key) + this.tagEndChar;
+ }
+ }else{
+
+ let tagEndExp = '' + key + this.tagEndChar;
+ let piClosingChar = "";
+
+ if(key[0] === "?") {
+ piClosingChar = "?";
+ tagEndExp = "";
+ }
+
+ // attrStr is an empty string in case the attribute came as undefined or null
+ if ((attrStr || attrStr === '') && val.indexOf('<') === -1) {
+ return ( this.indentate(level) + '<' + key + attrStr + piClosingChar + '>' + val + tagEndExp );
+ } else if (this.options.commentPropName !== false && key === this.options.commentPropName && piClosingChar.length === 0) {
+ return this.indentate(level) + `` + this.newLine;
+ }else {
+ return (
+ this.indentate(level) + '<' + key + attrStr + piClosingChar + this.tagEndChar +
+ val +
+ this.indentate(level) + tagEndExp );
+ }
+ }
+}
+
+Builder.prototype.closeTag = function(key){
+ let closeTag = "";
+ if(this.options.unpairedTags.indexOf(key) !== -1){ //unpaired
+ if(!this.options.suppressUnpairedNode) closeTag = "/"
+ }else if(this.options.suppressEmptyNode){ //empty
+ closeTag = "/";
+ }else{
+ closeTag = `>${key}`
+ }
+ return closeTag;
+}
+
+function buildEmptyObjNode(val, key, attrStr, level) {
+ if (val !== '') {
+ return this.buildObjectNode(val, key, attrStr, level);
+ } else {
+ if(key[0] === "?") return this.indentate(level) + '<' + key + attrStr+ '?' + this.tagEndChar;
+ else {
+ return this.indentate(level) + '<' + key + attrStr + '/' + this.tagEndChar;
+ // return this.buildTagStr(level,key, attrStr);
+ }
+ }
+}
+
+Builder.prototype.buildTextValNode = function(val, key, attrStr, level) {
+ if (this.options.cdataPropName !== false && key === this.options.cdataPropName) {
+ return this.indentate(level) + `` + this.newLine;
+ }else if (this.options.commentPropName !== false && key === this.options.commentPropName) {
+ return this.indentate(level) + `` + this.newLine;
+ }else if(key[0] === "?") {//PI tag
+ return this.indentate(level) + '<' + key + attrStr+ '?' + this.tagEndChar;
+ }else{
+ let textValue = this.options.tagValueProcessor(key, val);
+ textValue = this.replaceEntitiesValue(textValue);
+
+ if( textValue === ''){
+ return this.indentate(level) + '<' + key + attrStr + this.closeTag(key) + this.tagEndChar;
+ }else{
+ return this.indentate(level) + '<' + key + attrStr + '>' +
+ textValue +
+ '' + key + this.tagEndChar;
+ }
+ }
+}
+
+Builder.prototype.replaceEntitiesValue = function(textValue){
+ if(textValue && textValue.length > 0 && this.options.processEntities){
+ for (let i=0; i 0) {
+ indentation = EOL;
+ }
+ return arrToStr(jArray, options, "", indentation);
+}
+
+function arrToStr(arr, options, jPath, indentation) {
+ let xmlStr = "";
+ let isPreviousElementTag = false;
+
+ for (let i = 0; i < arr.length; i++) {
+ const tagObj = arr[i];
+ const tagName = propName(tagObj);
+ if(tagName === undefined) continue;
+
+ let newJPath = "";
+ if (jPath.length === 0) newJPath = tagName
+ else newJPath = `${jPath}.${tagName}`;
+
+ if (tagName === options.textNodeName) {
+ let tagText = tagObj[tagName];
+ if (!isStopNode(newJPath, options)) {
+ tagText = options.tagValueProcessor(tagName, tagText);
+ tagText = replaceEntitiesValue(tagText, options);
+ }
+ if (isPreviousElementTag) {
+ xmlStr += indentation;
+ }
+ xmlStr += tagText;
+ isPreviousElementTag = false;
+ continue;
+ } else if (tagName === options.cdataPropName) {
+ if (isPreviousElementTag) {
+ xmlStr += indentation;
+ }
+ xmlStr += ``;
+ isPreviousElementTag = false;
+ continue;
+ } else if (tagName === options.commentPropName) {
+ xmlStr += indentation + ``;
+ isPreviousElementTag = true;
+ continue;
+ } else if (tagName[0] === "?") {
+ const attStr = attr_to_str(tagObj[":@"], options);
+ const tempInd = tagName === "?xml" ? "" : indentation;
+ let piTextNodeName = tagObj[tagName][0][options.textNodeName];
+ piTextNodeName = piTextNodeName.length !== 0 ? " " + piTextNodeName : ""; //remove extra spacing
+ xmlStr += tempInd + `<${tagName}${piTextNodeName}${attStr}?>`;
+ isPreviousElementTag = true;
+ continue;
+ }
+ let newIdentation = indentation;
+ if (newIdentation !== "") {
+ newIdentation += options.indentBy;
+ }
+ const attStr = attr_to_str(tagObj[":@"], options);
+ const tagStart = indentation + `<${tagName}${attStr}`;
+ const tagValue = arrToStr(tagObj[tagName], options, newJPath, newIdentation);
+ if (options.unpairedTags.indexOf(tagName) !== -1) {
+ if (options.suppressUnpairedNode) xmlStr += tagStart + ">";
+ else xmlStr += tagStart + "/>";
+ } else if ((!tagValue || tagValue.length === 0) && options.suppressEmptyNode) {
+ xmlStr += tagStart + "/>";
+ } else if (tagValue && tagValue.endsWith(">")) {
+ xmlStr += tagStart + `>${tagValue}${indentation}${tagName}>`;
+ } else {
+ xmlStr += tagStart + ">";
+ if (tagValue && indentation !== "" && (tagValue.includes("/>") || tagValue.includes(""))) {
+ xmlStr += indentation + options.indentBy + tagValue + indentation;
+ } else {
+ xmlStr += tagValue;
+ }
+ xmlStr += `${tagName}>`;
+ }
+ isPreviousElementTag = true;
+ }
+
+ return xmlStr;
+}
+
+function propName(obj) {
+ const keys = Object.keys(obj);
+ for (let i = 0; i < keys.length; i++) {
+ const key = keys[i];
+ if(!obj.hasOwnProperty(key)) continue;
+ if (key !== ":@") return key;
+ }
+}
+
+function attr_to_str(attrMap, options) {
+ let attrStr = "";
+ if (attrMap && !options.ignoreAttributes) {
+ for (let attr in attrMap) {
+ if(!attrMap.hasOwnProperty(attr)) continue;
+ let attrVal = options.attributeValueProcessor(attr, attrMap[attr]);
+ attrVal = replaceEntitiesValue(attrVal, options);
+ if (attrVal === true && options.suppressBooleanAttributes) {
+ attrStr += ` ${attr.substr(options.attributeNamePrefix.length)}`;
+ } else {
+ attrStr += ` ${attr.substr(options.attributeNamePrefix.length)}="${attrVal}"`;
+ }
+ }
+ }
+ return attrStr;
+}
+
+function isStopNode(jPath, options) {
+ jPath = jPath.substr(0, jPath.length - options.textNodeName.length - 1);
+ let tagName = jPath.substr(jPath.lastIndexOf(".") + 1);
+ for (let index in options.stopNodes) {
+ if (options.stopNodes[index] === jPath || options.stopNodes[index] === "*." + tagName) return true;
+ }
+ return false;
+}
+
+function replaceEntitiesValue(textValue, options) {
+ if (textValue && textValue.length > 0 && options.processEntities) {
+ for (let i = 0; i < options.entities.length; i++) {
+ const entity = options.entities[i];
+ textValue = textValue.replace(entity.regex, entity.val);
+ }
+ }
+ return textValue;
+}
+module.exports = toXml;
+
+
+/***/ }),
+
+/***/ "./node_modules/fast-xml-parser/src/xmlparser/DocTypeReader.js":
+/*!*********************************************************************!*\
+ !*** ./node_modules/fast-xml-parser/src/xmlparser/DocTypeReader.js ***!
+ \*********************************************************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+const util = __webpack_require__(/*! ../util */ "./node_modules/fast-xml-parser/src/util.js");
+
+//TODO: handle comments
+function readDocType(xmlData, i){
+
+ const entities = {};
+ if( xmlData[i + 3] === 'O' &&
+ xmlData[i + 4] === 'C' &&
+ xmlData[i + 5] === 'T' &&
+ xmlData[i + 6] === 'Y' &&
+ xmlData[i + 7] === 'P' &&
+ xmlData[i + 8] === 'E')
+ {
+ i = i+9;
+ let angleBracketsCount = 1;
+ let hasBody = false, comment = false;
+ let exp = "";
+ for(;i') { //Read tag content
+ if(comment){
+ if( xmlData[i - 1] === "-" && xmlData[i - 2] === "-"){
+ comment = false;
+ angleBracketsCount--;
+ }
+ }else{
+ angleBracketsCount--;
+ }
+ if (angleBracketsCount === 0) {
+ break;
+ }
+ }else if( xmlData[i] === '['){
+ hasBody = true;
+ }else{
+ exp += xmlData[i];
+ }
+ }
+ if(angleBracketsCount !== 0){
+ throw new Error(`Unclosed DOCTYPE`);
+ }
+ }else{
+ throw new Error(`Invalid Tag instead of DOCTYPE`);
+ }
+ return {entities, i};
+}
+
+function readEntityExp(xmlData,i){
+ //External entities are not supported
+ //
+
+ //Parameter entities are not supported
+ //
+
+ //Internal entities are supported
+ //
+
+ //read EntityName
+ let entityName = "";
+ for (; i < xmlData.length && (xmlData[i] !== "'" && xmlData[i] !== '"' ); i++) {
+ // if(xmlData[i] === " ") continue;
+ // else
+ entityName += xmlData[i];
+ }
+ entityName = entityName.trim();
+ if(entityName.indexOf(" ") !== -1) throw new Error("External entites are not supported");
+
+ //read Entity Value
+ const startChar = xmlData[i++];
+ let val = ""
+ for (; i < xmlData.length && xmlData[i] !== startChar ; i++) {
+ val += xmlData[i];
+ }
+ return [entityName, val, i];
+}
+
+function isComment(xmlData, i){
+ if(xmlData[i+1] === '!' &&
+ xmlData[i+2] === '-' &&
+ xmlData[i+3] === '-') return true
+ return false
+}
+function isEntity(xmlData, i){
+ if(xmlData[i+1] === '!' &&
+ xmlData[i+2] === 'E' &&
+ xmlData[i+3] === 'N' &&
+ xmlData[i+4] === 'T' &&
+ xmlData[i+5] === 'I' &&
+ xmlData[i+6] === 'T' &&
+ xmlData[i+7] === 'Y') return true
+ return false
+}
+function isElement(xmlData, i){
+ if(xmlData[i+1] === '!' &&
+ xmlData[i+2] === 'E' &&
+ xmlData[i+3] === 'L' &&
+ xmlData[i+4] === 'E' &&
+ xmlData[i+5] === 'M' &&
+ xmlData[i+6] === 'E' &&
+ xmlData[i+7] === 'N' &&
+ xmlData[i+8] === 'T') return true
+ return false
+}
+
+function isAttlist(xmlData, i){
+ if(xmlData[i+1] === '!' &&
+ xmlData[i+2] === 'A' &&
+ xmlData[i+3] === 'T' &&
+ xmlData[i+4] === 'T' &&
+ xmlData[i+5] === 'L' &&
+ xmlData[i+6] === 'I' &&
+ xmlData[i+7] === 'S' &&
+ xmlData[i+8] === 'T') return true
+ return false
+}
+function isNotation(xmlData, i){
+ if(xmlData[i+1] === '!' &&
+ xmlData[i+2] === 'N' &&
+ xmlData[i+3] === 'O' &&
+ xmlData[i+4] === 'T' &&
+ xmlData[i+5] === 'A' &&
+ xmlData[i+6] === 'T' &&
+ xmlData[i+7] === 'I' &&
+ xmlData[i+8] === 'O' &&
+ xmlData[i+9] === 'N') return true
+ return false
+}
+
+function validateEntityName(name){
+ if (util.isName(name))
+ return name;
+ else
+ throw new Error(`Invalid entity name ${name}`);
+}
+
+module.exports = readDocType;
+
+
+/***/ }),
+
+/***/ "./node_modules/fast-xml-parser/src/xmlparser/OptionsBuilder.js":
+/*!**********************************************************************!*\
+ !*** ./node_modules/fast-xml-parser/src/xmlparser/OptionsBuilder.js ***!
+ \**********************************************************************/
+/*! no static exports found */
+/***/ (function(module, exports) {
+
+
+const defaultOptions = {
+ preserveOrder: false,
+ attributeNamePrefix: '@_',
+ attributesGroupName: false,
+ textNodeName: '#text',
+ ignoreAttributes: true,
+ removeNSPrefix: false, // remove NS from tag name or attribute name if true
+ allowBooleanAttributes: false, //a tag can have attributes without any value
+ //ignoreRootElement : false,
+ parseTagValue: true,
+ parseAttributeValue: false,
+ trimValues: true, //Trim string values of tag and attributes
+ cdataPropName: false,
+ numberParseOptions: {
+ hex: true,
+ leadingZeros: true,
+ eNotation: true
+ },
+ tagValueProcessor: function(tagName, val) {
+ return val;
+ },
+ attributeValueProcessor: function(attrName, val) {
+ return val;
+ },
+ stopNodes: [], //nested tags will not be parsed even for errors
+ alwaysCreateTextNode: false,
+ isArray: () => false,
+ commentPropName: false,
+ unpairedTags: [],
+ processEntities: true,
+ htmlEntities: false,
+ ignoreDeclaration: false,
+ ignorePiTags: false,
+ transformTagName: false,
+ transformAttributeName: false,
+ updateTag: function(tagName, jPath, attrs){
+ return tagName
+ },
+ // skipEmptyListItem: false
+};
+
+const buildOptions = function(options) {
+ return Object.assign({}, defaultOptions, options);
+};
+
+exports.buildOptions = buildOptions;
+exports.defaultOptions = defaultOptions;
+
+/***/ }),
+
+/***/ "./node_modules/fast-xml-parser/src/xmlparser/OrderedObjParser.js":
+/*!************************************************************************!*\
+ !*** ./node_modules/fast-xml-parser/src/xmlparser/OrderedObjParser.js ***!
+ \************************************************************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+///@ts-check
+
+const util = __webpack_require__(/*! ../util */ "./node_modules/fast-xml-parser/src/util.js");
+const xmlNode = __webpack_require__(/*! ./xmlNode */ "./node_modules/fast-xml-parser/src/xmlparser/xmlNode.js");
+const readDocType = __webpack_require__(/*! ./DocTypeReader */ "./node_modules/fast-xml-parser/src/xmlparser/DocTypeReader.js");
+const toNumber = __webpack_require__(/*! strnum */ "./node_modules/strnum/strnum.js");
+const getIgnoreAttributesFn = __webpack_require__(/*! ../ignoreAttributes */ "./node_modules/fast-xml-parser/src/ignoreAttributes.js")
+
+// const regx =
+// '<((!\\[CDATA\\[([\\s\\S]*?)(]]>))|((NAME:)?(NAME))([^>]*)>|((\\/)(NAME)\\s*>))([^<]*)'
+// .replace(/NAME/g, util.nameRegexp);
+
+//const tagsRegx = new RegExp("<(\\/?[\\w:\\-\._]+)([^>]*)>(\\s*"+cdataRegx+")*([^<]+)?","g");
+//const tagsRegx = new RegExp("<(\\/?)((\\w*:)?([\\w:\\-\._]+))([^>]*)>([^<]*)("+cdataRegx+"([^<]*))*([^<]+)?","g");
+
+class OrderedObjParser{
+ constructor(options){
+ this.options = options;
+ this.currentNode = null;
+ this.tagsNodeStack = [];
+ this.docTypeEntities = {};
+ this.lastEntities = {
+ "apos" : { regex: /&(apos|#39|#x27);/g, val : "'"},
+ "gt" : { regex: /&(gt|#62|#x3E);/g, val : ">"},
+ "lt" : { regex: /&(lt|#60|#x3C);/g, val : "<"},
+ "quot" : { regex: /&(quot|#34|#x22);/g, val : "\""},
+ };
+ this.ampEntity = { regex: /&(amp|#38|#x26);/g, val : "&"};
+ this.htmlEntities = {
+ "space": { regex: /&(nbsp|#160);/g, val: " " },
+ // "lt" : { regex: /&(lt|#60);/g, val: "<" },
+ // "gt" : { regex: /&(gt|#62);/g, val: ">" },
+ // "amp" : { regex: /&(amp|#38);/g, val: "&" },
+ // "quot" : { regex: /&(quot|#34);/g, val: "\"" },
+ // "apos" : { regex: /&(apos|#39);/g, val: "'" },
+ "cent" : { regex: /&(cent|#162);/g, val: "¢" },
+ "pound" : { regex: /&(pound|#163);/g, val: "£" },
+ "yen" : { regex: /&(yen|#165);/g, val: "¥" },
+ "euro" : { regex: /&(euro|#8364);/g, val: "€" },
+ "copyright" : { regex: /&(copy|#169);/g, val: "©" },
+ "reg" : { regex: /&(reg|#174);/g, val: "®" },
+ "inr" : { regex: /&(inr|#8377);/g, val: "₹" },
+ "num_dec": { regex: /([0-9]{1,7});/g, val : (_, str) => String.fromCharCode(Number.parseInt(str, 10)) },
+ "num_hex": { regex: /([0-9a-fA-F]{1,6});/g, val : (_, str) => String.fromCharCode(Number.parseInt(str, 16)) },
+ };
+ this.addExternalEntities = addExternalEntities;
+ this.parseXml = parseXml;
+ this.parseTextData = parseTextData;
+ this.resolveNameSpace = resolveNameSpace;
+ this.buildAttributesMap = buildAttributesMap;
+ this.isItStopNode = isItStopNode;
+ this.replaceEntitiesValue = replaceEntitiesValue;
+ this.readStopNodeData = readStopNodeData;
+ this.saveTextToParentTag = saveTextToParentTag;
+ this.addChild = addChild;
+ this.ignoreAttributesFn = getIgnoreAttributesFn(this.options.ignoreAttributes)
+ }
+
+}
+
+function addExternalEntities(externalEntities){
+ const entKeys = Object.keys(externalEntities);
+ for (let i = 0; i < entKeys.length; i++) {
+ const ent = entKeys[i];
+ this.lastEntities[ent] = {
+ regex: new RegExp("&"+ent+";","g"),
+ val : externalEntities[ent]
+ }
+ }
+}
+
+/**
+ * @param {string} val
+ * @param {string} tagName
+ * @param {string} jPath
+ * @param {boolean} dontTrim
+ * @param {boolean} hasAttributes
+ * @param {boolean} isLeafNode
+ * @param {boolean} escapeEntities
+ */
+function parseTextData(val, tagName, jPath, dontTrim, hasAttributes, isLeafNode, escapeEntities) {
+ if (val !== undefined) {
+ if (this.options.trimValues && !dontTrim) {
+ val = val.trim();
+ }
+ if(val.length > 0){
+ if(!escapeEntities) val = this.replaceEntitiesValue(val);
+
+ const newval = this.options.tagValueProcessor(tagName, val, jPath, hasAttributes, isLeafNode);
+ if(newval === null || newval === undefined){
+ //don't parse
+ return val;
+ }else if(typeof newval !== typeof val || newval !== val){
+ //overwrite
+ return newval;
+ }else if(this.options.trimValues){
+ return parseValue(val, this.options.parseTagValue, this.options.numberParseOptions);
+ }else{
+ const trimmedVal = val.trim();
+ if(trimmedVal === val){
+ return parseValue(val, this.options.parseTagValue, this.options.numberParseOptions);
+ }else{
+ return val;
+ }
+ }
+ }
+ }
+}
+
+function resolveNameSpace(tagname) {
+ if (this.options.removeNSPrefix) {
+ const tags = tagname.split(':');
+ const prefix = tagname.charAt(0) === '/' ? '/' : '';
+ if (tags[0] === 'xmlns') {
+ return '';
+ }
+ if (tags.length === 2) {
+ tagname = prefix + tags[1];
+ }
+ }
+ return tagname;
+}
+
+//TODO: change regex to capture NS
+//const attrsRegx = new RegExp("([\\w\\-\\.\\:]+)\\s*=\\s*(['\"])((.|\n)*?)\\2","gm");
+const attrsRegx = new RegExp('([^\\s=]+)\\s*(=\\s*([\'"])([\\s\\S]*?)\\3)?', 'gm');
+
+function buildAttributesMap(attrStr, jPath, tagName) {
+ if (this.options.ignoreAttributes !== true && typeof attrStr === 'string') {
+ // attrStr = attrStr.replace(/\r?\n/g, ' ');
+ //attrStr = attrStr || attrStr.trim();
+
+ const matches = util.getAllMatches(attrStr, attrsRegx);
+ const len = matches.length; //don't make it inline
+ const attrs = {};
+ for (let i = 0; i < len; i++) {
+ const attrName = this.resolveNameSpace(matches[i][1]);
+ if (this.ignoreAttributesFn(attrName, jPath)) {
+ continue
+ }
+ let oldVal = matches[i][4];
+ let aName = this.options.attributeNamePrefix + attrName;
+ if (attrName.length) {
+ if (this.options.transformAttributeName) {
+ aName = this.options.transformAttributeName(aName);
+ }
+ if(aName === "__proto__") aName = "#__proto__";
+ if (oldVal !== undefined) {
+ if (this.options.trimValues) {
+ oldVal = oldVal.trim();
+ }
+ oldVal = this.replaceEntitiesValue(oldVal);
+ const newVal = this.options.attributeValueProcessor(attrName, oldVal, jPath);
+ if(newVal === null || newVal === undefined){
+ //don't parse
+ attrs[aName] = oldVal;
+ }else if(typeof newVal !== typeof oldVal || newVal !== oldVal){
+ //overwrite
+ attrs[aName] = newVal;
+ }else{
+ //parse
+ attrs[aName] = parseValue(
+ oldVal,
+ this.options.parseAttributeValue,
+ this.options.numberParseOptions
+ );
+ }
+ } else if (this.options.allowBooleanAttributes) {
+ attrs[aName] = true;
+ }
+ }
+ }
+ if (!Object.keys(attrs).length) {
+ return;
+ }
+ if (this.options.attributesGroupName) {
+ const attrCollection = {};
+ attrCollection[this.options.attributesGroupName] = attrs;
+ return attrCollection;
+ }
+ return attrs
+ }
+}
+
+const parseXml = function(xmlData) {
+ // xmlData = xmlData.replace(/\r\n?/g, "\n"); //TODO: remove this line
+ const xmlObj = new xmlNode('!xml');
+ let currentNode = xmlObj;
+ let textData = "";
+ let jPath = "";
+ for(let i=0; i< xmlData.length; i++){//for each char in XML data
+ const ch = xmlData[i];
+ if(ch === '<'){
+ // const nextIndex = i+1;
+ // const _2ndChar = xmlData[nextIndex];
+ if( xmlData[i+1] === '/') {//Closing Tag
+ const closeIndex = findClosingIndex(xmlData, ">", i, "Closing Tag is not closed.")
+ let tagName = xmlData.substring(i+2,closeIndex).trim();
+
+ if(this.options.removeNSPrefix){
+ const colonIndex = tagName.indexOf(":");
+ if(colonIndex !== -1){
+ tagName = tagName.substr(colonIndex+1);
+ }
+ }
+
+ if(this.options.transformTagName) {
+ tagName = this.options.transformTagName(tagName);
+ }
+
+ if(currentNode){
+ textData = this.saveTextToParentTag(textData, currentNode, jPath);
+ }
+
+ //check if last tag of nested tag was unpaired tag
+ const lastTagName = jPath.substring(jPath.lastIndexOf(".")+1);
+ if(tagName && this.options.unpairedTags.indexOf(tagName) !== -1 ){
+ throw new Error(`Unpaired tag can not be used as closing tag: ${tagName}>`);
+ }
+ let propIndex = 0
+ if(lastTagName && this.options.unpairedTags.indexOf(lastTagName) !== -1 ){
+ propIndex = jPath.lastIndexOf('.', jPath.lastIndexOf('.')-1)
+ this.tagsNodeStack.pop();
+ }else{
+ propIndex = jPath.lastIndexOf(".");
+ }
+ jPath = jPath.substring(0, propIndex);
+
+ currentNode = this.tagsNodeStack.pop();//avoid recursion, set the parent tag scope
+ textData = "";
+ i = closeIndex;
+ } else if( xmlData[i+1] === '?') {
+
+ let tagData = readTagExp(xmlData,i, false, "?>");
+ if(!tagData) throw new Error("Pi Tag is not closed.");
+
+ textData = this.saveTextToParentTag(textData, currentNode, jPath);
+ if( (this.options.ignoreDeclaration && tagData.tagName === "?xml") || this.options.ignorePiTags){
+
+ }else{
+
+ const childNode = new xmlNode(tagData.tagName);
+ childNode.add(this.options.textNodeName, "");
+
+ if(tagData.tagName !== tagData.tagExp && tagData.attrExpPresent){
+ childNode[":@"] = this.buildAttributesMap(tagData.tagExp, jPath, tagData.tagName);
+ }
+ this.addChild(currentNode, childNode, jPath)
+
+ }
+
+
+ i = tagData.closeIndex + 1;
+ } else if(xmlData.substr(i + 1, 3) === '!--') {
+ const endIndex = findClosingIndex(xmlData, "-->", i+4, "Comment is not closed.")
+ if(this.options.commentPropName){
+ const comment = xmlData.substring(i + 4, endIndex - 2);
+
+ textData = this.saveTextToParentTag(textData, currentNode, jPath);
+
+ currentNode.add(this.options.commentPropName, [ { [this.options.textNodeName] : comment } ]);
+ }
+ i = endIndex;
+ } else if( xmlData.substr(i + 1, 2) === '!D') {
+ const result = readDocType(xmlData, i);
+ this.docTypeEntities = result.entities;
+ i = result.i;
+ }else if(xmlData.substr(i + 1, 2) === '![') {
+ const closeIndex = findClosingIndex(xmlData, "]]>", i, "CDATA is not closed.") - 2;
+ const tagExp = xmlData.substring(i + 9,closeIndex);
+
+ textData = this.saveTextToParentTag(textData, currentNode, jPath);
+
+ let val = this.parseTextData(tagExp, currentNode.tagname, jPath, true, false, true, true);
+ if(val == undefined) val = "";
+
+ //cdata should be set even if it is 0 length string
+ if(this.options.cdataPropName){
+ currentNode.add(this.options.cdataPropName, [ { [this.options.textNodeName] : tagExp } ]);
+ }else{
+ currentNode.add(this.options.textNodeName, val);
+ }
+
+ i = closeIndex + 2;
+ }else {//Opening tag
+ let result = readTagExp(xmlData,i, this.options.removeNSPrefix);
+ let tagName= result.tagName;
+ const rawTagName = result.rawTagName;
+ let tagExp = result.tagExp;
+ let attrExpPresent = result.attrExpPresent;
+ let closeIndex = result.closeIndex;
+
+ if (this.options.transformTagName) {
+ tagName = this.options.transformTagName(tagName);
+ }
+
+ //save text as child node
+ if (currentNode && textData) {
+ if(currentNode.tagname !== '!xml'){
+ //when nested tag is found
+ textData = this.saveTextToParentTag(textData, currentNode, jPath, false);
+ }
+ }
+
+ //check if last tag was unpaired tag
+ const lastTag = currentNode;
+ if(lastTag && this.options.unpairedTags.indexOf(lastTag.tagname) !== -1 ){
+ currentNode = this.tagsNodeStack.pop();
+ jPath = jPath.substring(0, jPath.lastIndexOf("."));
+ }
+ if(tagName !== xmlObj.tagname){
+ jPath += jPath ? "." + tagName : tagName;
+ }
+ if (this.isItStopNode(this.options.stopNodes, jPath, tagName)) {
+ let tagContent = "";
+ //self-closing tag
+ if(tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1){
+ if(tagName[tagName.length - 1] === "/"){ //remove trailing '/'
+ tagName = tagName.substr(0, tagName.length - 1);
+ jPath = jPath.substr(0, jPath.length - 1);
+ tagExp = tagName;
+ }else{
+ tagExp = tagExp.substr(0, tagExp.length - 1);
+ }
+ i = result.closeIndex;
+ }
+ //unpaired tag
+ else if(this.options.unpairedTags.indexOf(tagName) !== -1){
+
+ i = result.closeIndex;
+ }
+ //normal tag
+ else{
+ //read until closing tag is found
+ const result = this.readStopNodeData(xmlData, rawTagName, closeIndex + 1);
+ if(!result) throw new Error(`Unexpected end of ${rawTagName}`);
+ i = result.i;
+ tagContent = result.tagContent;
+ }
+
+ const childNode = new xmlNode(tagName);
+ if(tagName !== tagExp && attrExpPresent){
+ childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName);
+ }
+ if(tagContent) {
+ tagContent = this.parseTextData(tagContent, tagName, jPath, true, attrExpPresent, true, true);
+ }
+
+ jPath = jPath.substr(0, jPath.lastIndexOf("."));
+ childNode.add(this.options.textNodeName, tagContent);
+
+ this.addChild(currentNode, childNode, jPath)
+ }else{
+ //selfClosing tag
+ if(tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1){
+ if(tagName[tagName.length - 1] === "/"){ //remove trailing '/'
+ tagName = tagName.substr(0, tagName.length - 1);
+ jPath = jPath.substr(0, jPath.length - 1);
+ tagExp = tagName;
+ }else{
+ tagExp = tagExp.substr(0, tagExp.length - 1);
+ }
+
+ if(this.options.transformTagName) {
+ tagName = this.options.transformTagName(tagName);
+ }
+
+ const childNode = new xmlNode(tagName);
+ if(tagName !== tagExp && attrExpPresent){
+ childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName);
+ }
+ this.addChild(currentNode, childNode, jPath)
+ jPath = jPath.substr(0, jPath.lastIndexOf("."));
+ }
+ //opening tag
+ else{
+ const childNode = new xmlNode( tagName);
+ this.tagsNodeStack.push(currentNode);
+
+ if(tagName !== tagExp && attrExpPresent){
+ childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName);
+ }
+ this.addChild(currentNode, childNode, jPath)
+ currentNode = childNode;
+ }
+ textData = "";
+ i = closeIndex;
+ }
+ }
+ }else{
+ textData += xmlData[i];
+ }
+ }
+ return xmlObj.child;
+}
+
+function addChild(currentNode, childNode, jPath){
+ const result = this.options.updateTag(childNode.tagname, jPath, childNode[":@"])
+ if(result === false){
+ }else if(typeof result === "string"){
+ childNode.tagname = result
+ currentNode.addChild(childNode);
+ }else{
+ currentNode.addChild(childNode);
+ }
+}
+
+const replaceEntitiesValue = function(val){
+
+ if(this.options.processEntities){
+ for(let entityName in this.docTypeEntities){
+ const entity = this.docTypeEntities[entityName];
+ val = val.replace( entity.regx, entity.val);
+ }
+ for(let entityName in this.lastEntities){
+ const entity = this.lastEntities[entityName];
+ val = val.replace( entity.regex, entity.val);
+ }
+ if(this.options.htmlEntities){
+ for(let entityName in this.htmlEntities){
+ const entity = this.htmlEntities[entityName];
+ val = val.replace( entity.regex, entity.val);
+ }
+ }
+ val = val.replace( this.ampEntity.regex, this.ampEntity.val);
+ }
+ return val;
+}
+function saveTextToParentTag(textData, currentNode, jPath, isLeafNode) {
+ if (textData) { //store previously collected data as textNode
+ if(isLeafNode === undefined) isLeafNode = Object.keys(currentNode.child).length === 0
+
+ textData = this.parseTextData(textData,
+ currentNode.tagname,
+ jPath,
+ false,
+ currentNode[":@"] ? Object.keys(currentNode[":@"]).length !== 0 : false,
+ isLeafNode);
+
+ if (textData !== undefined && textData !== "")
+ currentNode.add(this.options.textNodeName, textData);
+ textData = "";
+ }
+ return textData;
+}
+
+//TODO: use jPath to simplify the logic
+/**
+ *
+ * @param {string[]} stopNodes
+ * @param {string} jPath
+ * @param {string} currentTagName
+ */
+function isItStopNode(stopNodes, jPath, currentTagName){
+ const allNodesExp = "*." + currentTagName;
+ for (const stopNodePath in stopNodes) {
+ const stopNodeExp = stopNodes[stopNodePath];
+ if( allNodesExp === stopNodeExp || jPath === stopNodeExp ) return true;
+ }
+ return false;
+}
+
+/**
+ * Returns the tag Expression and where it is ending handling single-double quotes situation
+ * @param {string} xmlData
+ * @param {number} i starting index
+ * @returns
+ */
+function tagExpWithClosingIndex(xmlData, i, closingChar = ">"){
+ let attrBoundary;
+ let tagExp = "";
+ for (let index = i; index < xmlData.length; index++) {
+ let ch = xmlData[index];
+ if (attrBoundary) {
+ if (ch === attrBoundary) attrBoundary = "";//reset
+ } else if (ch === '"' || ch === "'") {
+ attrBoundary = ch;
+ } else if (ch === closingChar[0]) {
+ if(closingChar[1]){
+ if(xmlData[index + 1] === closingChar[1]){
+ return {
+ data: tagExp,
+ index: index
+ }
+ }
+ }else{
+ return {
+ data: tagExp,
+ index: index
+ }
+ }
+ } else if (ch === '\t') {
+ ch = " "
+ }
+ tagExp += ch;
+ }
+}
+
+function findClosingIndex(xmlData, str, i, errMsg){
+ const closingIndex = xmlData.indexOf(str, i);
+ if(closingIndex === -1){
+ throw new Error(errMsg)
+ }else{
+ return closingIndex + str.length - 1;
+ }
+}
+
+function readTagExp(xmlData,i, removeNSPrefix, closingChar = ">"){
+ const result = tagExpWithClosingIndex(xmlData, i+1, closingChar);
+ if(!result) return;
+ let tagExp = result.data;
+ const closeIndex = result.index;
+ const separatorIndex = tagExp.search(/\s/);
+ let tagName = tagExp;
+ let attrExpPresent = true;
+ if(separatorIndex !== -1){//separate tag name and attributes expression
+ tagName = tagExp.substring(0, separatorIndex);
+ tagExp = tagExp.substring(separatorIndex + 1).trimStart();
+ }
+
+ const rawTagName = tagName;
+ if(removeNSPrefix){
+ const colonIndex = tagName.indexOf(":");
+ if(colonIndex !== -1){
+ tagName = tagName.substr(colonIndex+1);
+ attrExpPresent = tagName !== result.data.substr(colonIndex + 1);
+ }
+ }
+
+ return {
+ tagName: tagName,
+ tagExp: tagExp,
+ closeIndex: closeIndex,
+ attrExpPresent: attrExpPresent,
+ rawTagName: rawTagName,
+ }
+}
+/**
+ * find paired tag for a stop node
+ * @param {string} xmlData
+ * @param {string} tagName
+ * @param {number} i
+ */
+function readStopNodeData(xmlData, tagName, i){
+ const startIndex = i;
+ // Starting at 1 since we already have an open tag
+ let openTagCount = 1;
+
+ for (; i < xmlData.length; i++) {
+ if( xmlData[i] === "<"){
+ if (xmlData[i+1] === "/") {//close tag
+ const closeIndex = findClosingIndex(xmlData, ">", i, `${tagName} is not closed`);
+ let closeTagName = xmlData.substring(i+2,closeIndex).trim();
+ if(closeTagName === tagName){
+ openTagCount--;
+ if (openTagCount === 0) {
+ return {
+ tagContent: xmlData.substring(startIndex, i),
+ i : closeIndex
+ }
+ }
+ }
+ i=closeIndex;
+ } else if(xmlData[i+1] === '?') {
+ const closeIndex = findClosingIndex(xmlData, "?>", i+1, "StopNode is not closed.")
+ i=closeIndex;
+ } else if(xmlData.substr(i + 1, 3) === '!--') {
+ const closeIndex = findClosingIndex(xmlData, "-->", i+3, "StopNode is not closed.")
+ i=closeIndex;
+ } else if(xmlData.substr(i + 1, 2) === '![') {
+ const closeIndex = findClosingIndex(xmlData, "]]>", i, "StopNode is not closed.") - 2;
+ i=closeIndex;
+ } else {
+ const tagData = readTagExp(xmlData, i, '>')
+
+ if (tagData) {
+ const openTagName = tagData && tagData.tagName;
+ if (openTagName === tagName && tagData.tagExp[tagData.tagExp.length-1] !== "/") {
+ openTagCount++;
+ }
+ i=tagData.closeIndex;
+ }
+ }
+ }
+ }//end for loop
+}
+
+function parseValue(val, shouldParse, options) {
+ if (shouldParse && typeof val === 'string') {
+ //console.log(options)
+ const newval = val.trim();
+ if(newval === 'true' ) return true;
+ else if(newval === 'false' ) return false;
+ else return toNumber(val, options);
+ } else {
+ if (util.isExist(val)) {
+ return val;
+ } else {
+ return '';
+ }
+ }
+}
+
+
+module.exports = OrderedObjParser;
+
+
+/***/ }),
+
+/***/ "./node_modules/fast-xml-parser/src/xmlparser/XMLParser.js":
+/*!*****************************************************************!*\
+ !*** ./node_modules/fast-xml-parser/src/xmlparser/XMLParser.js ***!
+ \*****************************************************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+const { buildOptions} = __webpack_require__(/*! ./OptionsBuilder */ "./node_modules/fast-xml-parser/src/xmlparser/OptionsBuilder.js");
+const OrderedObjParser = __webpack_require__(/*! ./OrderedObjParser */ "./node_modules/fast-xml-parser/src/xmlparser/OrderedObjParser.js");
+const { prettify} = __webpack_require__(/*! ./node2json */ "./node_modules/fast-xml-parser/src/xmlparser/node2json.js");
+const validator = __webpack_require__(/*! ../validator */ "./node_modules/fast-xml-parser/src/validator.js");
+
+class XMLParser{
+
+ constructor(options){
+ this.externalEntities = {};
+ this.options = buildOptions(options);
+
+ }
+ /**
+ * Parse XML dats to JS object
+ * @param {string|Buffer} xmlData
+ * @param {boolean|Object} validationOption
+ */
+ parse(xmlData,validationOption){
+ if(typeof xmlData === "string"){
+ }else if( xmlData.toString){
+ xmlData = xmlData.toString();
+ }else{
+ throw new Error("XML data is accepted in String or Bytes[] form.")
+ }
+ if( validationOption){
+ if(validationOption === true) validationOption = {}; //validate with default options
+
+ const result = validator.validate(xmlData, validationOption);
+ if (result !== true) {
+ throw Error( `${result.err.msg}:${result.err.line}:${result.err.col}` )
+ }
+ }
+ const orderedObjParser = new OrderedObjParser(this.options);
+ orderedObjParser.addExternalEntities(this.externalEntities);
+ const orderedResult = orderedObjParser.parseXml(xmlData);
+ if(this.options.preserveOrder || orderedResult === undefined) return orderedResult;
+ else return prettify(orderedResult, this.options);
+ }
+
+ /**
+ * Add Entity which is not by default supported by this library
+ * @param {string} key
+ * @param {string} value
+ */
+ addEntity(key, value){
+ if(value.indexOf("&") !== -1){
+ throw new Error("Entity value can't have '&'")
+ }else if(key.indexOf("&") !== -1 || key.indexOf(";") !== -1){
+ throw new Error("An entity must be set without '&' and ';'. Eg. use '#xD' for '
'")
+ }else if(value === "&"){
+ throw new Error("An entity with value '&' is not permitted");
+ }else{
+ this.externalEntities[key] = value;
+ }
+ }
+}
+
+module.exports = XMLParser;
+
+/***/ }),
+
+/***/ "./node_modules/fast-xml-parser/src/xmlparser/node2json.js":
+/*!*****************************************************************!*\
+ !*** ./node_modules/fast-xml-parser/src/xmlparser/node2json.js ***!
+ \*****************************************************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+/**
+ *
+ * @param {array} node
+ * @param {any} options
+ * @returns
+ */
+function prettify(node, options){
+ return compress( node, options);
+}
+
+/**
+ *
+ * @param {array} arr
+ * @param {object} options
+ * @param {string} jPath
+ * @returns object
+ */
+function compress(arr, options, jPath){
+ let text;
+ const compressedObj = {};
+ for (let i = 0; i < arr.length; i++) {
+ const tagObj = arr[i];
+ const property = propName(tagObj);
+ let newJpath = "";
+ if(jPath === undefined) newJpath = property;
+ else newJpath = jPath + "." + property;
+
+ if(property === options.textNodeName){
+ if(text === undefined) text = tagObj[property];
+ else text += "" + tagObj[property];
+ }else if(property === undefined){
+ continue;
+ }else if(tagObj[property]){
+
+ let val = compress(tagObj[property], options, newJpath);
+ const isLeaf = isLeafTag(val, options);
+
+ if(tagObj[":@"]){
+ assignAttributes( val, tagObj[":@"], newJpath, options);
+ }else if(Object.keys(val).length === 1 && val[options.textNodeName] !== undefined && !options.alwaysCreateTextNode){
+ val = val[options.textNodeName];
+ }else if(Object.keys(val).length === 0){
+ if(options.alwaysCreateTextNode) val[options.textNodeName] = "";
+ else val = "";
+ }
+
+ if(compressedObj[property] !== undefined && compressedObj.hasOwnProperty(property)) {
+ if(!Array.isArray(compressedObj[property])) {
+ compressedObj[property] = [ compressedObj[property] ];
+ }
+ compressedObj[property].push(val);
+ }else{
+ //TODO: if a node is not an array, then check if it should be an array
+ //also determine if it is a leaf node
+ if (options.isArray(property, newJpath, isLeaf )) {
+ compressedObj[property] = [val];
+ }else{
+ compressedObj[property] = val;
+ }
+ }
+ }
+
+ }
+ // if(text && text.length > 0) compressedObj[options.textNodeName] = text;
+ if(typeof text === "string"){
+ if(text.length > 0) compressedObj[options.textNodeName] = text;
+ }else if(text !== undefined) compressedObj[options.textNodeName] = text;
+ return compressedObj;
+}
+
+function propName(obj){
+ const keys = Object.keys(obj);
+ for (let i = 0; i < keys.length; i++) {
+ const key = keys[i];
+ if(key !== ":@") return key;
+ }
+}
+
+function assignAttributes(obj, attrMap, jpath, options){
+ if (attrMap) {
+ const keys = Object.keys(attrMap);
+ const len = keys.length; //don't make it inline
+ for (let i = 0; i < len; i++) {
+ const atrrName = keys[i];
+ if (options.isArray(atrrName, jpath + "." + atrrName, true, true)) {
+ obj[atrrName] = [ attrMap[atrrName] ];
+ } else {
+ obj[atrrName] = attrMap[atrrName];
+ }
+ }
+ }
+}
+
+function isLeafTag(obj, options){
+ const { textNodeName } = options;
+ const propCount = Object.keys(obj).length;
+
+ if (propCount === 0) {
+ return true;
+ }
+
+ if (
+ propCount === 1 &&
+ (obj[textNodeName] || typeof obj[textNodeName] === "boolean" || obj[textNodeName] === 0)
+ ) {
+ return true;
+ }
+
+ return false;
+}
+exports.prettify = prettify;
+
+
+/***/ }),
+
+/***/ "./node_modules/fast-xml-parser/src/xmlparser/xmlNode.js":
+/*!***************************************************************!*\
+ !*** ./node_modules/fast-xml-parser/src/xmlparser/xmlNode.js ***!
+ \***************************************************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+class XmlNode{
+ constructor(tagname) {
+ this.tagname = tagname;
+ this.child = []; //nested tags, text, cdata, comments in order
+ this[":@"] = {}; //attributes map
+ }
+ add(key,val){
+ // this.child.push( {name : key, val: val, isCdata: isCdata });
+ if(key === "__proto__") key = "#__proto__";
+ this.child.push( {[key]: val });
+ }
+ addChild(node) {
+ if(node.tagname === "__proto__") node.tagname = "#__proto__";
+ if(node[":@"] && Object.keys(node[":@"]).length > 0){
+ this.child.push( { [node.tagname]: node.child, [":@"]: node[":@"] });
+ }else{
+ this.child.push( { [node.tagname]: node.child });
+ }
+ };
+};
+
+
+module.exports = XmlNode;
+
+/***/ }),
+
+/***/ "./node_modules/ieee754/index.js":
+/*!***************************************!*\
+ !*** ./node_modules/ieee754/index.js ***!
+ \***************************************/
+/*! no static exports found */
+/***/ (function(module, exports) {
+
+/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh */
+exports.read = function (buffer, offset, isLE, mLen, nBytes) {
+ var e, m
+ var eLen = (nBytes * 8) - mLen - 1
+ var eMax = (1 << eLen) - 1
+ var eBias = eMax >> 1
+ var nBits = -7
+ var i = isLE ? (nBytes - 1) : 0
+ var d = isLE ? -1 : 1
+ var s = buffer[offset + i]
+
+ i += d
+
+ e = s & ((1 << (-nBits)) - 1)
+ s >>= (-nBits)
+ nBits += eLen
+ for (; nBits > 0; e = (e * 256) + buffer[offset + i], i += d, nBits -= 8) {}
+
+ m = e & ((1 << (-nBits)) - 1)
+ e >>= (-nBits)
+ nBits += mLen
+ for (; nBits > 0; m = (m * 256) + buffer[offset + i], i += d, nBits -= 8) {}
+
+ if (e === 0) {
+ e = 1 - eBias
+ } else if (e === eMax) {
+ return m ? NaN : ((s ? -1 : 1) * Infinity)
+ } else {
+ m = m + Math.pow(2, mLen)
+ e = e - eBias
+ }
+ return (s ? -1 : 1) * m * Math.pow(2, e - mLen)
+}
+
+exports.write = function (buffer, value, offset, isLE, mLen, nBytes) {
+ var e, m, c
+ var eLen = (nBytes * 8) - mLen - 1
+ var eMax = (1 << eLen) - 1
+ var eBias = eMax >> 1
+ var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0)
+ var i = isLE ? 0 : (nBytes - 1)
+ var d = isLE ? 1 : -1
+ var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0
+
+ value = Math.abs(value)
+
+ if (isNaN(value) || value === Infinity) {
+ m = isNaN(value) ? 1 : 0
+ e = eMax
+ } else {
+ e = Math.floor(Math.log(value) / Math.LN2)
+ if (value * (c = Math.pow(2, -e)) < 1) {
+ e--
+ c *= 2
+ }
+ if (e + eBias >= 1) {
+ value += rt / c
+ } else {
+ value += rt * Math.pow(2, 1 - eBias)
+ }
+ if (value * c >= 2) {
+ e++
+ c /= 2
+ }
+
+ if (e + eBias >= eMax) {
+ m = 0
+ e = eMax
+ } else if (e + eBias >= 1) {
+ m = ((value * c) - 1) * Math.pow(2, mLen)
+ e = e + eBias
+ } else {
+ m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen)
+ e = 0
+ }
+ }
+
+ for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {}
+
+ e = (e << mLen) | m
+ eLen += mLen
+ for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {}
+
+ buffer[offset + i - d] |= s * 128
+}
+
+
+/***/ }),
+
+/***/ "./node_modules/isarray/index.js":
+/*!***************************************!*\
+ !*** ./node_modules/isarray/index.js ***!
+ \***************************************/
+/*! no static exports found */
+/***/ (function(module, exports) {
+
+var toString = {}.toString;
+
+module.exports = Array.isArray || function (arr) {
+ return toString.call(arr) == '[object Array]';
+};
+
+
+/***/ }),
+
+/***/ "./node_modules/mime/Mime.js":
+/*!***********************************!*\
+ !*** ./node_modules/mime/Mime.js ***!
+ \***********************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+/**
+ * @param typeMap [Object] Map of MIME type -> Array[extensions]
+ * @param ...
+ */
+function Mime() {
+ this._types = Object.create(null);
+ this._extensions = Object.create(null);
+
+ for (let i = 0; i < arguments.length; i++) {
+ this.define(arguments[i]);
+ }
+
+ this.define = this.define.bind(this);
+ this.getType = this.getType.bind(this);
+ this.getExtension = this.getExtension.bind(this);
+}
+
+/**
+ * Define mimetype -> extension mappings. Each key is a mime-type that maps
+ * to an array of extensions associated with the type. The first extension is
+ * used as the default extension for the type.
+ *
+ * e.g. mime.define({'audio/ogg', ['oga', 'ogg', 'spx']});
+ *
+ * If a type declares an extension that has already been defined, an error will
+ * be thrown. To suppress this error and force the extension to be associated
+ * with the new type, pass `force`=true. Alternatively, you may prefix the
+ * extension with "*" to map the type to extension, without mapping the
+ * extension to the type.
+ *
+ * e.g. mime.define({'audio/wav', ['wav']}, {'audio/x-wav', ['*wav']});
+ *
+ *
+ * @param map (Object) type definitions
+ * @param force (Boolean) if true, force overriding of existing definitions
+ */
+Mime.prototype.define = function(typeMap, force) {
+ for (let type in typeMap) {
+ let extensions = typeMap[type].map(function(t) {
+ return t.toLowerCase();
+ });
+ type = type.toLowerCase();
+
+ for (let i = 0; i < extensions.length; i++) {
+ const ext = extensions[i];
+
+ // '*' prefix = not the preferred type for this extension. So fixup the
+ // extension, and skip it.
+ if (ext[0] === '*') {
+ continue;
+ }
+
+ if (!force && (ext in this._types)) {
+ throw new Error(
+ 'Attempt to change mapping for "' + ext +
+ '" extension from "' + this._types[ext] + '" to "' + type +
+ '". Pass `force=true` to allow this, otherwise remove "' + ext +
+ '" from the list of extensions for "' + type + '".'
+ );
+ }
+
+ this._types[ext] = type;
+ }
+
+ // Use first extension as default
+ if (force || !this._extensions[type]) {
+ const ext = extensions[0];
+ this._extensions[type] = (ext[0] !== '*') ? ext : ext.substr(1);
+ }
+ }
+};
+
+/**
+ * Lookup a mime type based on extension
+ */
+Mime.prototype.getType = function(path) {
+ path = String(path);
+ let last = path.replace(/^.*[/\\]/, '').toLowerCase();
+ let ext = last.replace(/^.*\./, '').toLowerCase();
+
+ let hasPath = last.length < path.length;
+ let hasDot = ext.length < last.length - 1;
+
+ return (hasDot || !hasPath) && this._types[ext] || null;
+};
+
+/**
+ * Return file extension associated with a mime type
+ */
+Mime.prototype.getExtension = function(type) {
+ type = /^\s*([^;\s]*)/.test(type) && RegExp.$1;
+ return type && this._extensions[type.toLowerCase()] || null;
+};
+
+module.exports = Mime;
+
+
+/***/ }),
+
+/***/ "./node_modules/mime/index.js":
+/*!************************************!*\
+ !*** ./node_modules/mime/index.js ***!
+ \************************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+let Mime = __webpack_require__(/*! ./Mime */ "./node_modules/mime/Mime.js");
+module.exports = new Mime(__webpack_require__(/*! ./types/standard */ "./node_modules/mime/types/standard.js"), __webpack_require__(/*! ./types/other */ "./node_modules/mime/types/other.js"));
+
+
+/***/ }),
+
+/***/ "./node_modules/mime/types/other.js":
+/*!******************************************!*\
+ !*** ./node_modules/mime/types/other.js ***!
+ \******************************************/
+/*! no static exports found */
+/***/ (function(module, exports) {
+
+module.exports = {"application/prs.cww":["cww"],"application/vnd.1000minds.decision-model+xml":["1km"],"application/vnd.3gpp.pic-bw-large":["plb"],"application/vnd.3gpp.pic-bw-small":["psb"],"application/vnd.3gpp.pic-bw-var":["pvb"],"application/vnd.3gpp2.tcap":["tcap"],"application/vnd.3m.post-it-notes":["pwn"],"application/vnd.accpac.simply.aso":["aso"],"application/vnd.accpac.simply.imp":["imp"],"application/vnd.acucobol":["acu"],"application/vnd.acucorp":["atc","acutc"],"application/vnd.adobe.air-application-installer-package+zip":["air"],"application/vnd.adobe.formscentral.fcdt":["fcdt"],"application/vnd.adobe.fxp":["fxp","fxpl"],"application/vnd.adobe.xdp+xml":["xdp"],"application/vnd.adobe.xfdf":["xfdf"],"application/vnd.ahead.space":["ahead"],"application/vnd.airzip.filesecure.azf":["azf"],"application/vnd.airzip.filesecure.azs":["azs"],"application/vnd.amazon.ebook":["azw"],"application/vnd.americandynamics.acc":["acc"],"application/vnd.amiga.ami":["ami"],"application/vnd.android.package-archive":["apk"],"application/vnd.anser-web-certificate-issue-initiation":["cii"],"application/vnd.anser-web-funds-transfer-initiation":["fti"],"application/vnd.antix.game-component":["atx"],"application/vnd.apple.installer+xml":["mpkg"],"application/vnd.apple.keynote":["key"],"application/vnd.apple.mpegurl":["m3u8"],"application/vnd.apple.numbers":["numbers"],"application/vnd.apple.pages":["pages"],"application/vnd.apple.pkpass":["pkpass"],"application/vnd.aristanetworks.swi":["swi"],"application/vnd.astraea-software.iota":["iota"],"application/vnd.audiograph":["aep"],"application/vnd.balsamiq.bmml+xml":["bmml"],"application/vnd.blueice.multipass":["mpm"],"application/vnd.bmi":["bmi"],"application/vnd.businessobjects":["rep"],"application/vnd.chemdraw+xml":["cdxml"],"application/vnd.chipnuts.karaoke-mmd":["mmd"],"application/vnd.cinderella":["cdy"],"application/vnd.citationstyles.style+xml":["csl"],"application/vnd.claymore":["cla"],"application/vnd.cloanto.rp9":["rp9"],"application/vnd.clonk.c4group":["c4g","c4d","c4f","c4p","c4u"],"application/vnd.cluetrust.cartomobile-config":["c11amc"],"application/vnd.cluetrust.cartomobile-config-pkg":["c11amz"],"application/vnd.commonspace":["csp"],"application/vnd.contact.cmsg":["cdbcmsg"],"application/vnd.cosmocaller":["cmc"],"application/vnd.crick.clicker":["clkx"],"application/vnd.crick.clicker.keyboard":["clkk"],"application/vnd.crick.clicker.palette":["clkp"],"application/vnd.crick.clicker.template":["clkt"],"application/vnd.crick.clicker.wordbank":["clkw"],"application/vnd.criticaltools.wbs+xml":["wbs"],"application/vnd.ctc-posml":["pml"],"application/vnd.cups-ppd":["ppd"],"application/vnd.curl.car":["car"],"application/vnd.curl.pcurl":["pcurl"],"application/vnd.dart":["dart"],"application/vnd.data-vision.rdz":["rdz"],"application/vnd.dbf":["dbf"],"application/vnd.dece.data":["uvf","uvvf","uvd","uvvd"],"application/vnd.dece.ttml+xml":["uvt","uvvt"],"application/vnd.dece.unspecified":["uvx","uvvx"],"application/vnd.dece.zip":["uvz","uvvz"],"application/vnd.denovo.fcselayout-link":["fe_launch"],"application/vnd.dna":["dna"],"application/vnd.dolby.mlp":["mlp"],"application/vnd.dpgraph":["dpg"],"application/vnd.dreamfactory":["dfac"],"application/vnd.ds-keypoint":["kpxx"],"application/vnd.dvb.ait":["ait"],"application/vnd.dvb.service":["svc"],"application/vnd.dynageo":["geo"],"application/vnd.ecowin.chart":["mag"],"application/vnd.enliven":["nml"],"application/vnd.epson.esf":["esf"],"application/vnd.epson.msf":["msf"],"application/vnd.epson.quickanime":["qam"],"application/vnd.epson.salt":["slt"],"application/vnd.epson.ssf":["ssf"],"application/vnd.eszigno3+xml":["es3","et3"],"application/vnd.ezpix-album":["ez2"],"application/vnd.ezpix-package":["ez3"],"application/vnd.fdf":["fdf"],"application/vnd.fdsn.mseed":["mseed"],"application/vnd.fdsn.seed":["seed","dataless"],"application/vnd.flographit":["gph"],"application/vnd.fluxtime.clip":["ftc"],"application/vnd.framemaker":["fm","frame","maker","book"],"application/vnd.frogans.fnc":["fnc"],"application/vnd.frogans.ltf":["ltf"],"application/vnd.fsc.weblaunch":["fsc"],"application/vnd.fujitsu.oasys":["oas"],"application/vnd.fujitsu.oasys2":["oa2"],"application/vnd.fujitsu.oasys3":["oa3"],"application/vnd.fujitsu.oasysgp":["fg5"],"application/vnd.fujitsu.oasysprs":["bh2"],"application/vnd.fujixerox.ddd":["ddd"],"application/vnd.fujixerox.docuworks":["xdw"],"application/vnd.fujixerox.docuworks.binder":["xbd"],"application/vnd.fuzzysheet":["fzs"],"application/vnd.genomatix.tuxedo":["txd"],"application/vnd.geogebra.file":["ggb"],"application/vnd.geogebra.tool":["ggt"],"application/vnd.geometry-explorer":["gex","gre"],"application/vnd.geonext":["gxt"],"application/vnd.geoplan":["g2w"],"application/vnd.geospace":["g3w"],"application/vnd.gmx":["gmx"],"application/vnd.google-apps.document":["gdoc"],"application/vnd.google-apps.presentation":["gslides"],"application/vnd.google-apps.spreadsheet":["gsheet"],"application/vnd.google-earth.kml+xml":["kml"],"application/vnd.google-earth.kmz":["kmz"],"application/vnd.grafeq":["gqf","gqs"],"application/vnd.groove-account":["gac"],"application/vnd.groove-help":["ghf"],"application/vnd.groove-identity-message":["gim"],"application/vnd.groove-injector":["grv"],"application/vnd.groove-tool-message":["gtm"],"application/vnd.groove-tool-template":["tpl"],"application/vnd.groove-vcard":["vcg"],"application/vnd.hal+xml":["hal"],"application/vnd.handheld-entertainment+xml":["zmm"],"application/vnd.hbci":["hbci"],"application/vnd.hhe.lesson-player":["les"],"application/vnd.hp-hpgl":["hpgl"],"application/vnd.hp-hpid":["hpid"],"application/vnd.hp-hps":["hps"],"application/vnd.hp-jlyt":["jlt"],"application/vnd.hp-pcl":["pcl"],"application/vnd.hp-pclxl":["pclxl"],"application/vnd.hydrostatix.sof-data":["sfd-hdstx"],"application/vnd.ibm.minipay":["mpy"],"application/vnd.ibm.modcap":["afp","listafp","list3820"],"application/vnd.ibm.rights-management":["irm"],"application/vnd.ibm.secure-container":["sc"],"application/vnd.iccprofile":["icc","icm"],"application/vnd.igloader":["igl"],"application/vnd.immervision-ivp":["ivp"],"application/vnd.immervision-ivu":["ivu"],"application/vnd.insors.igm":["igm"],"application/vnd.intercon.formnet":["xpw","xpx"],"application/vnd.intergeo":["i2g"],"application/vnd.intu.qbo":["qbo"],"application/vnd.intu.qfx":["qfx"],"application/vnd.ipunplugged.rcprofile":["rcprofile"],"application/vnd.irepository.package+xml":["irp"],"application/vnd.is-xpr":["xpr"],"application/vnd.isac.fcs":["fcs"],"application/vnd.jam":["jam"],"application/vnd.jcp.javame.midlet-rms":["rms"],"application/vnd.jisp":["jisp"],"application/vnd.joost.joda-archive":["joda"],"application/vnd.kahootz":["ktz","ktr"],"application/vnd.kde.karbon":["karbon"],"application/vnd.kde.kchart":["chrt"],"application/vnd.kde.kformula":["kfo"],"application/vnd.kde.kivio":["flw"],"application/vnd.kde.kontour":["kon"],"application/vnd.kde.kpresenter":["kpr","kpt"],"application/vnd.kde.kspread":["ksp"],"application/vnd.kde.kword":["kwd","kwt"],"application/vnd.kenameaapp":["htke"],"application/vnd.kidspiration":["kia"],"application/vnd.kinar":["kne","knp"],"application/vnd.koan":["skp","skd","skt","skm"],"application/vnd.kodak-descriptor":["sse"],"application/vnd.las.las+xml":["lasxml"],"application/vnd.llamagraphics.life-balance.desktop":["lbd"],"application/vnd.llamagraphics.life-balance.exchange+xml":["lbe"],"application/vnd.lotus-1-2-3":["123"],"application/vnd.lotus-approach":["apr"],"application/vnd.lotus-freelance":["pre"],"application/vnd.lotus-notes":["nsf"],"application/vnd.lotus-organizer":["org"],"application/vnd.lotus-screencam":["scm"],"application/vnd.lotus-wordpro":["lwp"],"application/vnd.macports.portpkg":["portpkg"],"application/vnd.mapbox-vector-tile":["mvt"],"application/vnd.mcd":["mcd"],"application/vnd.medcalcdata":["mc1"],"application/vnd.mediastation.cdkey":["cdkey"],"application/vnd.mfer":["mwf"],"application/vnd.mfmp":["mfm"],"application/vnd.micrografx.flo":["flo"],"application/vnd.micrografx.igx":["igx"],"application/vnd.mif":["mif"],"application/vnd.mobius.daf":["daf"],"application/vnd.mobius.dis":["dis"],"application/vnd.mobius.mbk":["mbk"],"application/vnd.mobius.mqy":["mqy"],"application/vnd.mobius.msl":["msl"],"application/vnd.mobius.plc":["plc"],"application/vnd.mobius.txf":["txf"],"application/vnd.mophun.application":["mpn"],"application/vnd.mophun.certificate":["mpc"],"application/vnd.mozilla.xul+xml":["xul"],"application/vnd.ms-artgalry":["cil"],"application/vnd.ms-cab-compressed":["cab"],"application/vnd.ms-excel":["xls","xlm","xla","xlc","xlt","xlw"],"application/vnd.ms-excel.addin.macroenabled.12":["xlam"],"application/vnd.ms-excel.sheet.binary.macroenabled.12":["xlsb"],"application/vnd.ms-excel.sheet.macroenabled.12":["xlsm"],"application/vnd.ms-excel.template.macroenabled.12":["xltm"],"application/vnd.ms-fontobject":["eot"],"application/vnd.ms-htmlhelp":["chm"],"application/vnd.ms-ims":["ims"],"application/vnd.ms-lrm":["lrm"],"application/vnd.ms-officetheme":["thmx"],"application/vnd.ms-outlook":["msg"],"application/vnd.ms-pki.seccat":["cat"],"application/vnd.ms-pki.stl":["*stl"],"application/vnd.ms-powerpoint":["ppt","pps","pot"],"application/vnd.ms-powerpoint.addin.macroenabled.12":["ppam"],"application/vnd.ms-powerpoint.presentation.macroenabled.12":["pptm"],"application/vnd.ms-powerpoint.slide.macroenabled.12":["sldm"],"application/vnd.ms-powerpoint.slideshow.macroenabled.12":["ppsm"],"application/vnd.ms-powerpoint.template.macroenabled.12":["potm"],"application/vnd.ms-project":["mpp","mpt"],"application/vnd.ms-word.document.macroenabled.12":["docm"],"application/vnd.ms-word.template.macroenabled.12":["dotm"],"application/vnd.ms-works":["wps","wks","wcm","wdb"],"application/vnd.ms-wpl":["wpl"],"application/vnd.ms-xpsdocument":["xps"],"application/vnd.mseq":["mseq"],"application/vnd.musician":["mus"],"application/vnd.muvee.style":["msty"],"application/vnd.mynfc":["taglet"],"application/vnd.neurolanguage.nlu":["nlu"],"application/vnd.nitf":["ntf","nitf"],"application/vnd.noblenet-directory":["nnd"],"application/vnd.noblenet-sealer":["nns"],"application/vnd.noblenet-web":["nnw"],"application/vnd.nokia.n-gage.ac+xml":["*ac"],"application/vnd.nokia.n-gage.data":["ngdat"],"application/vnd.nokia.n-gage.symbian.install":["n-gage"],"application/vnd.nokia.radio-preset":["rpst"],"application/vnd.nokia.radio-presets":["rpss"],"application/vnd.novadigm.edm":["edm"],"application/vnd.novadigm.edx":["edx"],"application/vnd.novadigm.ext":["ext"],"application/vnd.oasis.opendocument.chart":["odc"],"application/vnd.oasis.opendocument.chart-template":["otc"],"application/vnd.oasis.opendocument.database":["odb"],"application/vnd.oasis.opendocument.formula":["odf"],"application/vnd.oasis.opendocument.formula-template":["odft"],"application/vnd.oasis.opendocument.graphics":["odg"],"application/vnd.oasis.opendocument.graphics-template":["otg"],"application/vnd.oasis.opendocument.image":["odi"],"application/vnd.oasis.opendocument.image-template":["oti"],"application/vnd.oasis.opendocument.presentation":["odp"],"application/vnd.oasis.opendocument.presentation-template":["otp"],"application/vnd.oasis.opendocument.spreadsheet":["ods"],"application/vnd.oasis.opendocument.spreadsheet-template":["ots"],"application/vnd.oasis.opendocument.text":["odt"],"application/vnd.oasis.opendocument.text-master":["odm"],"application/vnd.oasis.opendocument.text-template":["ott"],"application/vnd.oasis.opendocument.text-web":["oth"],"application/vnd.olpc-sugar":["xo"],"application/vnd.oma.dd2+xml":["dd2"],"application/vnd.openblox.game+xml":["obgx"],"application/vnd.openofficeorg.extension":["oxt"],"application/vnd.openstreetmap.data+xml":["osm"],"application/vnd.openxmlformats-officedocument.presentationml.presentation":["pptx"],"application/vnd.openxmlformats-officedocument.presentationml.slide":["sldx"],"application/vnd.openxmlformats-officedocument.presentationml.slideshow":["ppsx"],"application/vnd.openxmlformats-officedocument.presentationml.template":["potx"],"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":["xlsx"],"application/vnd.openxmlformats-officedocument.spreadsheetml.template":["xltx"],"application/vnd.openxmlformats-officedocument.wordprocessingml.document":["docx"],"application/vnd.openxmlformats-officedocument.wordprocessingml.template":["dotx"],"application/vnd.osgeo.mapguide.package":["mgp"],"application/vnd.osgi.dp":["dp"],"application/vnd.osgi.subsystem":["esa"],"application/vnd.palm":["pdb","pqa","oprc"],"application/vnd.pawaafile":["paw"],"application/vnd.pg.format":["str"],"application/vnd.pg.osasli":["ei6"],"application/vnd.picsel":["efif"],"application/vnd.pmi.widget":["wg"],"application/vnd.pocketlearn":["plf"],"application/vnd.powerbuilder6":["pbd"],"application/vnd.previewsystems.box":["box"],"application/vnd.proteus.magazine":["mgz"],"application/vnd.publishare-delta-tree":["qps"],"application/vnd.pvi.ptid1":["ptid"],"application/vnd.quark.quarkxpress":["qxd","qxt","qwd","qwt","qxl","qxb"],"application/vnd.rar":["rar"],"application/vnd.realvnc.bed":["bed"],"application/vnd.recordare.musicxml":["mxl"],"application/vnd.recordare.musicxml+xml":["musicxml"],"application/vnd.rig.cryptonote":["cryptonote"],"application/vnd.rim.cod":["cod"],"application/vnd.rn-realmedia":["rm"],"application/vnd.rn-realmedia-vbr":["rmvb"],"application/vnd.route66.link66+xml":["link66"],"application/vnd.sailingtracker.track":["st"],"application/vnd.seemail":["see"],"application/vnd.sema":["sema"],"application/vnd.semd":["semd"],"application/vnd.semf":["semf"],"application/vnd.shana.informed.formdata":["ifm"],"application/vnd.shana.informed.formtemplate":["itp"],"application/vnd.shana.informed.interchange":["iif"],"application/vnd.shana.informed.package":["ipk"],"application/vnd.simtech-mindmapper":["twd","twds"],"application/vnd.smaf":["mmf"],"application/vnd.smart.teacher":["teacher"],"application/vnd.software602.filler.form+xml":["fo"],"application/vnd.solent.sdkm+xml":["sdkm","sdkd"],"application/vnd.spotfire.dxp":["dxp"],"application/vnd.spotfire.sfs":["sfs"],"application/vnd.stardivision.calc":["sdc"],"application/vnd.stardivision.draw":["sda"],"application/vnd.stardivision.impress":["sdd"],"application/vnd.stardivision.math":["smf"],"application/vnd.stardivision.writer":["sdw","vor"],"application/vnd.stardivision.writer-global":["sgl"],"application/vnd.stepmania.package":["smzip"],"application/vnd.stepmania.stepchart":["sm"],"application/vnd.sun.wadl+xml":["wadl"],"application/vnd.sun.xml.calc":["sxc"],"application/vnd.sun.xml.calc.template":["stc"],"application/vnd.sun.xml.draw":["sxd"],"application/vnd.sun.xml.draw.template":["std"],"application/vnd.sun.xml.impress":["sxi"],"application/vnd.sun.xml.impress.template":["sti"],"application/vnd.sun.xml.math":["sxm"],"application/vnd.sun.xml.writer":["sxw"],"application/vnd.sun.xml.writer.global":["sxg"],"application/vnd.sun.xml.writer.template":["stw"],"application/vnd.sus-calendar":["sus","susp"],"application/vnd.svd":["svd"],"application/vnd.symbian.install":["sis","sisx"],"application/vnd.syncml+xml":["xsm"],"application/vnd.syncml.dm+wbxml":["bdm"],"application/vnd.syncml.dm+xml":["xdm"],"application/vnd.syncml.dmddf+xml":["ddf"],"application/vnd.tao.intent-module-archive":["tao"],"application/vnd.tcpdump.pcap":["pcap","cap","dmp"],"application/vnd.tmobile-livetv":["tmo"],"application/vnd.trid.tpt":["tpt"],"application/vnd.triscape.mxs":["mxs"],"application/vnd.trueapp":["tra"],"application/vnd.ufdl":["ufd","ufdl"],"application/vnd.uiq.theme":["utz"],"application/vnd.umajin":["umj"],"application/vnd.unity":["unityweb"],"application/vnd.uoml+xml":["uoml"],"application/vnd.vcx":["vcx"],"application/vnd.visio":["vsd","vst","vss","vsw"],"application/vnd.visionary":["vis"],"application/vnd.vsf":["vsf"],"application/vnd.wap.wbxml":["wbxml"],"application/vnd.wap.wmlc":["wmlc"],"application/vnd.wap.wmlscriptc":["wmlsc"],"application/vnd.webturbo":["wtb"],"application/vnd.wolfram.player":["nbp"],"application/vnd.wordperfect":["wpd"],"application/vnd.wqd":["wqd"],"application/vnd.wt.stf":["stf"],"application/vnd.xara":["xar"],"application/vnd.xfdl":["xfdl"],"application/vnd.yamaha.hv-dic":["hvd"],"application/vnd.yamaha.hv-script":["hvs"],"application/vnd.yamaha.hv-voice":["hvp"],"application/vnd.yamaha.openscoreformat":["osf"],"application/vnd.yamaha.openscoreformat.osfpvg+xml":["osfpvg"],"application/vnd.yamaha.smaf-audio":["saf"],"application/vnd.yamaha.smaf-phrase":["spf"],"application/vnd.yellowriver-custom-menu":["cmp"],"application/vnd.zul":["zir","zirz"],"application/vnd.zzazz.deck+xml":["zaz"],"application/x-7z-compressed":["7z"],"application/x-abiword":["abw"],"application/x-ace-compressed":["ace"],"application/x-apple-diskimage":["*dmg"],"application/x-arj":["arj"],"application/x-authorware-bin":["aab","x32","u32","vox"],"application/x-authorware-map":["aam"],"application/x-authorware-seg":["aas"],"application/x-bcpio":["bcpio"],"application/x-bdoc":["*bdoc"],"application/x-bittorrent":["torrent"],"application/x-blorb":["blb","blorb"],"application/x-bzip":["bz"],"application/x-bzip2":["bz2","boz"],"application/x-cbr":["cbr","cba","cbt","cbz","cb7"],"application/x-cdlink":["vcd"],"application/x-cfs-compressed":["cfs"],"application/x-chat":["chat"],"application/x-chess-pgn":["pgn"],"application/x-chrome-extension":["crx"],"application/x-cocoa":["cco"],"application/x-conference":["nsc"],"application/x-cpio":["cpio"],"application/x-csh":["csh"],"application/x-debian-package":["*deb","udeb"],"application/x-dgc-compressed":["dgc"],"application/x-director":["dir","dcr","dxr","cst","cct","cxt","w3d","fgd","swa"],"application/x-doom":["wad"],"application/x-dtbncx+xml":["ncx"],"application/x-dtbook+xml":["dtb"],"application/x-dtbresource+xml":["res"],"application/x-dvi":["dvi"],"application/x-envoy":["evy"],"application/x-eva":["eva"],"application/x-font-bdf":["bdf"],"application/x-font-ghostscript":["gsf"],"application/x-font-linux-psf":["psf"],"application/x-font-pcf":["pcf"],"application/x-font-snf":["snf"],"application/x-font-type1":["pfa","pfb","pfm","afm"],"application/x-freearc":["arc"],"application/x-futuresplash":["spl"],"application/x-gca-compressed":["gca"],"application/x-glulx":["ulx"],"application/x-gnumeric":["gnumeric"],"application/x-gramps-xml":["gramps"],"application/x-gtar":["gtar"],"application/x-hdf":["hdf"],"application/x-httpd-php":["php"],"application/x-install-instructions":["install"],"application/x-iso9660-image":["*iso"],"application/x-iwork-keynote-sffkey":["*key"],"application/x-iwork-numbers-sffnumbers":["*numbers"],"application/x-iwork-pages-sffpages":["*pages"],"application/x-java-archive-diff":["jardiff"],"application/x-java-jnlp-file":["jnlp"],"application/x-keepass2":["kdbx"],"application/x-latex":["latex"],"application/x-lua-bytecode":["luac"],"application/x-lzh-compressed":["lzh","lha"],"application/x-makeself":["run"],"application/x-mie":["mie"],"application/x-mobipocket-ebook":["prc","mobi"],"application/x-ms-application":["application"],"application/x-ms-shortcut":["lnk"],"application/x-ms-wmd":["wmd"],"application/x-ms-wmz":["wmz"],"application/x-ms-xbap":["xbap"],"application/x-msaccess":["mdb"],"application/x-msbinder":["obd"],"application/x-mscardfile":["crd"],"application/x-msclip":["clp"],"application/x-msdos-program":["*exe"],"application/x-msdownload":["*exe","*dll","com","bat","*msi"],"application/x-msmediaview":["mvb","m13","m14"],"application/x-msmetafile":["*wmf","*wmz","*emf","emz"],"application/x-msmoney":["mny"],"application/x-mspublisher":["pub"],"application/x-msschedule":["scd"],"application/x-msterminal":["trm"],"application/x-mswrite":["wri"],"application/x-netcdf":["nc","cdf"],"application/x-ns-proxy-autoconfig":["pac"],"application/x-nzb":["nzb"],"application/x-perl":["pl","pm"],"application/x-pilot":["*prc","*pdb"],"application/x-pkcs12":["p12","pfx"],"application/x-pkcs7-certificates":["p7b","spc"],"application/x-pkcs7-certreqresp":["p7r"],"application/x-rar-compressed":["*rar"],"application/x-redhat-package-manager":["rpm"],"application/x-research-info-systems":["ris"],"application/x-sea":["sea"],"application/x-sh":["sh"],"application/x-shar":["shar"],"application/x-shockwave-flash":["swf"],"application/x-silverlight-app":["xap"],"application/x-sql":["sql"],"application/x-stuffit":["sit"],"application/x-stuffitx":["sitx"],"application/x-subrip":["srt"],"application/x-sv4cpio":["sv4cpio"],"application/x-sv4crc":["sv4crc"],"application/x-t3vm-image":["t3"],"application/x-tads":["gam"],"application/x-tar":["tar"],"application/x-tcl":["tcl","tk"],"application/x-tex":["tex"],"application/x-tex-tfm":["tfm"],"application/x-texinfo":["texinfo","texi"],"application/x-tgif":["*obj"],"application/x-ustar":["ustar"],"application/x-virtualbox-hdd":["hdd"],"application/x-virtualbox-ova":["ova"],"application/x-virtualbox-ovf":["ovf"],"application/x-virtualbox-vbox":["vbox"],"application/x-virtualbox-vbox-extpack":["vbox-extpack"],"application/x-virtualbox-vdi":["vdi"],"application/x-virtualbox-vhd":["vhd"],"application/x-virtualbox-vmdk":["vmdk"],"application/x-wais-source":["src"],"application/x-web-app-manifest+json":["webapp"],"application/x-x509-ca-cert":["der","crt","pem"],"application/x-xfig":["fig"],"application/x-xliff+xml":["*xlf"],"application/x-xpinstall":["xpi"],"application/x-xz":["xz"],"application/x-zmachine":["z1","z2","z3","z4","z5","z6","z7","z8"],"audio/vnd.dece.audio":["uva","uvva"],"audio/vnd.digital-winds":["eol"],"audio/vnd.dra":["dra"],"audio/vnd.dts":["dts"],"audio/vnd.dts.hd":["dtshd"],"audio/vnd.lucent.voice":["lvp"],"audio/vnd.ms-playready.media.pya":["pya"],"audio/vnd.nuera.ecelp4800":["ecelp4800"],"audio/vnd.nuera.ecelp7470":["ecelp7470"],"audio/vnd.nuera.ecelp9600":["ecelp9600"],"audio/vnd.rip":["rip"],"audio/x-aac":["aac"],"audio/x-aiff":["aif","aiff","aifc"],"audio/x-caf":["caf"],"audio/x-flac":["flac"],"audio/x-m4a":["*m4a"],"audio/x-matroska":["mka"],"audio/x-mpegurl":["m3u"],"audio/x-ms-wax":["wax"],"audio/x-ms-wma":["wma"],"audio/x-pn-realaudio":["ram","ra"],"audio/x-pn-realaudio-plugin":["rmp"],"audio/x-realaudio":["*ra"],"audio/x-wav":["*wav"],"chemical/x-cdx":["cdx"],"chemical/x-cif":["cif"],"chemical/x-cmdf":["cmdf"],"chemical/x-cml":["cml"],"chemical/x-csml":["csml"],"chemical/x-xyz":["xyz"],"image/prs.btif":["btif"],"image/prs.pti":["pti"],"image/vnd.adobe.photoshop":["psd"],"image/vnd.airzip.accelerator.azv":["azv"],"image/vnd.dece.graphic":["uvi","uvvi","uvg","uvvg"],"image/vnd.djvu":["djvu","djv"],"image/vnd.dvb.subtitle":["*sub"],"image/vnd.dwg":["dwg"],"image/vnd.dxf":["dxf"],"image/vnd.fastbidsheet":["fbs"],"image/vnd.fpx":["fpx"],"image/vnd.fst":["fst"],"image/vnd.fujixerox.edmics-mmr":["mmr"],"image/vnd.fujixerox.edmics-rlc":["rlc"],"image/vnd.microsoft.icon":["ico"],"image/vnd.ms-dds":["dds"],"image/vnd.ms-modi":["mdi"],"image/vnd.ms-photo":["wdp"],"image/vnd.net-fpx":["npx"],"image/vnd.pco.b16":["b16"],"image/vnd.tencent.tap":["tap"],"image/vnd.valve.source.texture":["vtf"],"image/vnd.wap.wbmp":["wbmp"],"image/vnd.xiff":["xif"],"image/vnd.zbrush.pcx":["pcx"],"image/x-3ds":["3ds"],"image/x-cmu-raster":["ras"],"image/x-cmx":["cmx"],"image/x-freehand":["fh","fhc","fh4","fh5","fh7"],"image/x-icon":["*ico"],"image/x-jng":["jng"],"image/x-mrsid-image":["sid"],"image/x-ms-bmp":["*bmp"],"image/x-pcx":["*pcx"],"image/x-pict":["pic","pct"],"image/x-portable-anymap":["pnm"],"image/x-portable-bitmap":["pbm"],"image/x-portable-graymap":["pgm"],"image/x-portable-pixmap":["ppm"],"image/x-rgb":["rgb"],"image/x-tga":["tga"],"image/x-xbitmap":["xbm"],"image/x-xpixmap":["xpm"],"image/x-xwindowdump":["xwd"],"message/vnd.wfa.wsc":["wsc"],"model/vnd.collada+xml":["dae"],"model/vnd.dwf":["dwf"],"model/vnd.gdl":["gdl"],"model/vnd.gtw":["gtw"],"model/vnd.mts":["mts"],"model/vnd.opengex":["ogex"],"model/vnd.parasolid.transmit.binary":["x_b"],"model/vnd.parasolid.transmit.text":["x_t"],"model/vnd.sap.vds":["vds"],"model/vnd.usdz+zip":["usdz"],"model/vnd.valve.source.compiled-map":["bsp"],"model/vnd.vtu":["vtu"],"text/prs.lines.tag":["dsc"],"text/vnd.curl":["curl"],"text/vnd.curl.dcurl":["dcurl"],"text/vnd.curl.mcurl":["mcurl"],"text/vnd.curl.scurl":["scurl"],"text/vnd.dvb.subtitle":["sub"],"text/vnd.fly":["fly"],"text/vnd.fmi.flexstor":["flx"],"text/vnd.graphviz":["gv"],"text/vnd.in3d.3dml":["3dml"],"text/vnd.in3d.spot":["spot"],"text/vnd.sun.j2me.app-descriptor":["jad"],"text/vnd.wap.wml":["wml"],"text/vnd.wap.wmlscript":["wmls"],"text/x-asm":["s","asm"],"text/x-c":["c","cc","cxx","cpp","h","hh","dic"],"text/x-component":["htc"],"text/x-fortran":["f","for","f77","f90"],"text/x-handlebars-template":["hbs"],"text/x-java-source":["java"],"text/x-lua":["lua"],"text/x-markdown":["mkd"],"text/x-nfo":["nfo"],"text/x-opml":["opml"],"text/x-org":["*org"],"text/x-pascal":["p","pas"],"text/x-processing":["pde"],"text/x-sass":["sass"],"text/x-scss":["scss"],"text/x-setext":["etx"],"text/x-sfv":["sfv"],"text/x-suse-ymp":["ymp"],"text/x-uuencode":["uu"],"text/x-vcalendar":["vcs"],"text/x-vcard":["vcf"],"video/vnd.dece.hd":["uvh","uvvh"],"video/vnd.dece.mobile":["uvm","uvvm"],"video/vnd.dece.pd":["uvp","uvvp"],"video/vnd.dece.sd":["uvs","uvvs"],"video/vnd.dece.video":["uvv","uvvv"],"video/vnd.dvb.file":["dvb"],"video/vnd.fvt":["fvt"],"video/vnd.mpegurl":["mxu","m4u"],"video/vnd.ms-playready.media.pyv":["pyv"],"video/vnd.uvvu.mp4":["uvu","uvvu"],"video/vnd.vivo":["viv"],"video/x-f4v":["f4v"],"video/x-fli":["fli"],"video/x-flv":["flv"],"video/x-m4v":["m4v"],"video/x-matroska":["mkv","mk3d","mks"],"video/x-mng":["mng"],"video/x-ms-asf":["asf","asx"],"video/x-ms-vob":["vob"],"video/x-ms-wm":["wm"],"video/x-ms-wmv":["wmv"],"video/x-ms-wmx":["wmx"],"video/x-ms-wvx":["wvx"],"video/x-msvideo":["avi"],"video/x-sgi-movie":["movie"],"video/x-smv":["smv"],"x-conference/x-cooltalk":["ice"]};
+
+/***/ }),
+
+/***/ "./node_modules/mime/types/standard.js":
+/*!*********************************************!*\
+ !*** ./node_modules/mime/types/standard.js ***!
+ \*********************************************/
+/*! no static exports found */
+/***/ (function(module, exports) {
+
+module.exports = {"application/andrew-inset":["ez"],"application/applixware":["aw"],"application/atom+xml":["atom"],"application/atomcat+xml":["atomcat"],"application/atomdeleted+xml":["atomdeleted"],"application/atomsvc+xml":["atomsvc"],"application/atsc-dwd+xml":["dwd"],"application/atsc-held+xml":["held"],"application/atsc-rsat+xml":["rsat"],"application/bdoc":["bdoc"],"application/calendar+xml":["xcs"],"application/ccxml+xml":["ccxml"],"application/cdfx+xml":["cdfx"],"application/cdmi-capability":["cdmia"],"application/cdmi-container":["cdmic"],"application/cdmi-domain":["cdmid"],"application/cdmi-object":["cdmio"],"application/cdmi-queue":["cdmiq"],"application/cu-seeme":["cu"],"application/dash+xml":["mpd"],"application/davmount+xml":["davmount"],"application/docbook+xml":["dbk"],"application/dssc+der":["dssc"],"application/dssc+xml":["xdssc"],"application/ecmascript":["es","ecma"],"application/emma+xml":["emma"],"application/emotionml+xml":["emotionml"],"application/epub+zip":["epub"],"application/exi":["exi"],"application/express":["exp"],"application/fdt+xml":["fdt"],"application/font-tdpfr":["pfr"],"application/geo+json":["geojson"],"application/gml+xml":["gml"],"application/gpx+xml":["gpx"],"application/gxf":["gxf"],"application/gzip":["gz"],"application/hjson":["hjson"],"application/hyperstudio":["stk"],"application/inkml+xml":["ink","inkml"],"application/ipfix":["ipfix"],"application/its+xml":["its"],"application/java-archive":["jar","war","ear"],"application/java-serialized-object":["ser"],"application/java-vm":["class"],"application/javascript":["js","mjs"],"application/json":["json","map"],"application/json5":["json5"],"application/jsonml+json":["jsonml"],"application/ld+json":["jsonld"],"application/lgr+xml":["lgr"],"application/lost+xml":["lostxml"],"application/mac-binhex40":["hqx"],"application/mac-compactpro":["cpt"],"application/mads+xml":["mads"],"application/manifest+json":["webmanifest"],"application/marc":["mrc"],"application/marcxml+xml":["mrcx"],"application/mathematica":["ma","nb","mb"],"application/mathml+xml":["mathml"],"application/mbox":["mbox"],"application/mediaservercontrol+xml":["mscml"],"application/metalink+xml":["metalink"],"application/metalink4+xml":["meta4"],"application/mets+xml":["mets"],"application/mmt-aei+xml":["maei"],"application/mmt-usd+xml":["musd"],"application/mods+xml":["mods"],"application/mp21":["m21","mp21"],"application/mp4":["mp4s","m4p"],"application/msword":["doc","dot"],"application/mxf":["mxf"],"application/n-quads":["nq"],"application/n-triples":["nt"],"application/node":["cjs"],"application/octet-stream":["bin","dms","lrf","mar","so","dist","distz","pkg","bpk","dump","elc","deploy","exe","dll","deb","dmg","iso","img","msi","msp","msm","buffer"],"application/oda":["oda"],"application/oebps-package+xml":["opf"],"application/ogg":["ogx"],"application/omdoc+xml":["omdoc"],"application/onenote":["onetoc","onetoc2","onetmp","onepkg"],"application/oxps":["oxps"],"application/p2p-overlay+xml":["relo"],"application/patch-ops-error+xml":["xer"],"application/pdf":["pdf"],"application/pgp-encrypted":["pgp"],"application/pgp-signature":["asc","sig"],"application/pics-rules":["prf"],"application/pkcs10":["p10"],"application/pkcs7-mime":["p7m","p7c"],"application/pkcs7-signature":["p7s"],"application/pkcs8":["p8"],"application/pkix-attr-cert":["ac"],"application/pkix-cert":["cer"],"application/pkix-crl":["crl"],"application/pkix-pkipath":["pkipath"],"application/pkixcmp":["pki"],"application/pls+xml":["pls"],"application/postscript":["ai","eps","ps"],"application/provenance+xml":["provx"],"application/pskc+xml":["pskcxml"],"application/raml+yaml":["raml"],"application/rdf+xml":["rdf","owl"],"application/reginfo+xml":["rif"],"application/relax-ng-compact-syntax":["rnc"],"application/resource-lists+xml":["rl"],"application/resource-lists-diff+xml":["rld"],"application/rls-services+xml":["rs"],"application/route-apd+xml":["rapd"],"application/route-s-tsid+xml":["sls"],"application/route-usd+xml":["rusd"],"application/rpki-ghostbusters":["gbr"],"application/rpki-manifest":["mft"],"application/rpki-roa":["roa"],"application/rsd+xml":["rsd"],"application/rss+xml":["rss"],"application/rtf":["rtf"],"application/sbml+xml":["sbml"],"application/scvp-cv-request":["scq"],"application/scvp-cv-response":["scs"],"application/scvp-vp-request":["spq"],"application/scvp-vp-response":["spp"],"application/sdp":["sdp"],"application/senml+xml":["senmlx"],"application/sensml+xml":["sensmlx"],"application/set-payment-initiation":["setpay"],"application/set-registration-initiation":["setreg"],"application/shf+xml":["shf"],"application/sieve":["siv","sieve"],"application/smil+xml":["smi","smil"],"application/sparql-query":["rq"],"application/sparql-results+xml":["srx"],"application/srgs":["gram"],"application/srgs+xml":["grxml"],"application/sru+xml":["sru"],"application/ssdl+xml":["ssdl"],"application/ssml+xml":["ssml"],"application/swid+xml":["swidtag"],"application/tei+xml":["tei","teicorpus"],"application/thraud+xml":["tfi"],"application/timestamped-data":["tsd"],"application/toml":["toml"],"application/trig":["trig"],"application/ttml+xml":["ttml"],"application/ubjson":["ubj"],"application/urc-ressheet+xml":["rsheet"],"application/urc-targetdesc+xml":["td"],"application/voicexml+xml":["vxml"],"application/wasm":["wasm"],"application/widget":["wgt"],"application/winhlp":["hlp"],"application/wsdl+xml":["wsdl"],"application/wspolicy+xml":["wspolicy"],"application/xaml+xml":["xaml"],"application/xcap-att+xml":["xav"],"application/xcap-caps+xml":["xca"],"application/xcap-diff+xml":["xdf"],"application/xcap-el+xml":["xel"],"application/xcap-ns+xml":["xns"],"application/xenc+xml":["xenc"],"application/xhtml+xml":["xhtml","xht"],"application/xliff+xml":["xlf"],"application/xml":["xml","xsl","xsd","rng"],"application/xml-dtd":["dtd"],"application/xop+xml":["xop"],"application/xproc+xml":["xpl"],"application/xslt+xml":["*xsl","xslt"],"application/xspf+xml":["xspf"],"application/xv+xml":["mxml","xhvml","xvml","xvm"],"application/yang":["yang"],"application/yin+xml":["yin"],"application/zip":["zip"],"audio/3gpp":["*3gpp"],"audio/adpcm":["adp"],"audio/amr":["amr"],"audio/basic":["au","snd"],"audio/midi":["mid","midi","kar","rmi"],"audio/mobile-xmf":["mxmf"],"audio/mp3":["*mp3"],"audio/mp4":["m4a","mp4a"],"audio/mpeg":["mpga","mp2","mp2a","mp3","m2a","m3a"],"audio/ogg":["oga","ogg","spx","opus"],"audio/s3m":["s3m"],"audio/silk":["sil"],"audio/wav":["wav"],"audio/wave":["*wav"],"audio/webm":["weba"],"audio/xm":["xm"],"font/collection":["ttc"],"font/otf":["otf"],"font/ttf":["ttf"],"font/woff":["woff"],"font/woff2":["woff2"],"image/aces":["exr"],"image/apng":["apng"],"image/avif":["avif"],"image/bmp":["bmp"],"image/cgm":["cgm"],"image/dicom-rle":["drle"],"image/emf":["emf"],"image/fits":["fits"],"image/g3fax":["g3"],"image/gif":["gif"],"image/heic":["heic"],"image/heic-sequence":["heics"],"image/heif":["heif"],"image/heif-sequence":["heifs"],"image/hej2k":["hej2"],"image/hsj2":["hsj2"],"image/ief":["ief"],"image/jls":["jls"],"image/jp2":["jp2","jpg2"],"image/jpeg":["jpeg","jpg","jpe"],"image/jph":["jph"],"image/jphc":["jhc"],"image/jpm":["jpm"],"image/jpx":["jpx","jpf"],"image/jxr":["jxr"],"image/jxra":["jxra"],"image/jxrs":["jxrs"],"image/jxs":["jxs"],"image/jxsc":["jxsc"],"image/jxsi":["jxsi"],"image/jxss":["jxss"],"image/ktx":["ktx"],"image/ktx2":["ktx2"],"image/png":["png"],"image/sgi":["sgi"],"image/svg+xml":["svg","svgz"],"image/t38":["t38"],"image/tiff":["tif","tiff"],"image/tiff-fx":["tfx"],"image/webp":["webp"],"image/wmf":["wmf"],"message/disposition-notification":["disposition-notification"],"message/global":["u8msg"],"message/global-delivery-status":["u8dsn"],"message/global-disposition-notification":["u8mdn"],"message/global-headers":["u8hdr"],"message/rfc822":["eml","mime"],"model/3mf":["3mf"],"model/gltf+json":["gltf"],"model/gltf-binary":["glb"],"model/iges":["igs","iges"],"model/mesh":["msh","mesh","silo"],"model/mtl":["mtl"],"model/obj":["obj"],"model/step+xml":["stpx"],"model/step+zip":["stpz"],"model/step-xml+zip":["stpxz"],"model/stl":["stl"],"model/vrml":["wrl","vrml"],"model/x3d+binary":["*x3db","x3dbz"],"model/x3d+fastinfoset":["x3db"],"model/x3d+vrml":["*x3dv","x3dvz"],"model/x3d+xml":["x3d","x3dz"],"model/x3d-vrml":["x3dv"],"text/cache-manifest":["appcache","manifest"],"text/calendar":["ics","ifb"],"text/coffeescript":["coffee","litcoffee"],"text/css":["css"],"text/csv":["csv"],"text/html":["html","htm","shtml"],"text/jade":["jade"],"text/jsx":["jsx"],"text/less":["less"],"text/markdown":["markdown","md"],"text/mathml":["mml"],"text/mdx":["mdx"],"text/n3":["n3"],"text/plain":["txt","text","conf","def","list","log","in","ini"],"text/richtext":["rtx"],"text/rtf":["*rtf"],"text/sgml":["sgml","sgm"],"text/shex":["shex"],"text/slim":["slim","slm"],"text/spdx":["spdx"],"text/stylus":["stylus","styl"],"text/tab-separated-values":["tsv"],"text/troff":["t","tr","roff","man","me","ms"],"text/turtle":["ttl"],"text/uri-list":["uri","uris","urls"],"text/vcard":["vcard"],"text/vtt":["vtt"],"text/xml":["*xml"],"text/yaml":["yaml","yml"],"video/3gpp":["3gp","3gpp"],"video/3gpp2":["3g2"],"video/h261":["h261"],"video/h263":["h263"],"video/h264":["h264"],"video/iso.segment":["m4s"],"video/jpeg":["jpgv"],"video/jpm":["*jpm","jpgm"],"video/mj2":["mj2","mjp2"],"video/mp2t":["ts"],"video/mp4":["mp4","mp4v","mpg4"],"video/mpeg":["mpeg","mpg","mpe","m1v","m2v"],"video/ogg":["ogv"],"video/quicktime":["qt","mov"],"video/webm":["webm"]};
+
+/***/ }),
+
+/***/ "./node_modules/strnum/strnum.js":
+/*!***************************************!*\
+ !*** ./node_modules/strnum/strnum.js ***!
+ \***************************************/
+/*! no static exports found */
+/***/ (function(module, exports) {
+
+const hexRegex = /^[-+]?0x[a-fA-F0-9]+$/;
+const numRegex = /^([\-\+])?(0*)(\.[0-9]+([eE]\-?[0-9]+)?|[0-9]+(\.[0-9]+([eE]\-?[0-9]+)?)?)$/;
+// const octRegex = /0x[a-z0-9]+/;
+// const binRegex = /0x[a-z0-9]+/;
+
+
+//polyfill
+if (!Number.parseInt && window.parseInt) {
+ Number.parseInt = window.parseInt;
+}
+if (!Number.parseFloat && window.parseFloat) {
+ Number.parseFloat = window.parseFloat;
+}
+
+
+const consider = {
+ hex : true,
+ leadingZeros: true,
+ decimalPoint: "\.",
+ eNotation: true
+ //skipLike: /regex/
+};
+
+function toNumber(str, options = {}){
+ // const options = Object.assign({}, consider);
+ // if(opt.leadingZeros === false){
+ // options.leadingZeros = false;
+ // }else if(opt.hex === false){
+ // options.hex = false;
+ // }
+
+ options = Object.assign({}, consider, options );
+ if(!str || typeof str !== "string" ) return str;
+
+ let trimmedStr = str.trim();
+ // if(trimmedStr === "0.0") return 0;
+ // else if(trimmedStr === "+0.0") return 0;
+ // else if(trimmedStr === "-0.0") return -0;
+
+ if(options.skipLike !== undefined && options.skipLike.test(trimmedStr)) return str;
+ else if (options.hex && hexRegex.test(trimmedStr)) {
+ return Number.parseInt(trimmedStr, 16);
+ // } else if (options.parseOct && octRegex.test(str)) {
+ // return Number.parseInt(val, 8);
+ // }else if (options.parseBin && binRegex.test(str)) {
+ // return Number.parseInt(val, 2);
+ }else{
+ //separate negative sign, leading zeros, and rest number
+ const match = numRegex.exec(trimmedStr);
+ if(match){
+ const sign = match[1];
+ const leadingZeros = match[2];
+ let numTrimmedByZeros = trimZeros(match[3]); //complete num without leading zeros
+ //trim ending zeros for floating number
+
+ const eNotation = match[4] || match[6];
+ if(!options.leadingZeros && leadingZeros.length > 0 && sign && trimmedStr[2] !== ".") return str; //-0123
+ else if(!options.leadingZeros && leadingZeros.length > 0 && !sign && trimmedStr[1] !== ".") return str; //0123
+ else{//no leading zeros or leading zeros are allowed
+ const num = Number(trimmedStr);
+ const numStr = "" + num;
+ if(numStr.search(/[eE]/) !== -1){ //given number is long and parsed to eNotation
+ if(options.eNotation) return num;
+ else return str;
+ }else if(eNotation){ //given number has enotation
+ if(options.eNotation) return num;
+ else return str;
+ }else if(trimmedStr.indexOf(".") !== -1){ //floating number
+ // const decimalPart = match[5].substr(1);
+ // const intPart = trimmedStr.substr(0,trimmedStr.indexOf("."));
+
+
+ // const p = numStr.indexOf(".");
+ // const givenIntPart = numStr.substr(0,p);
+ // const givenDecPart = numStr.substr(p+1);
+ if(numStr === "0" && (numTrimmedByZeros === "") ) return num; //0.0
+ else if(numStr === numTrimmedByZeros) return num; //0.456. 0.79000
+ else if( sign && numStr === "-"+numTrimmedByZeros) return num;
+ else return str;
+ }
+
+ if(leadingZeros){
+ // if(numTrimmedByZeros === numStr){
+ // if(options.leadingZeros) return num;
+ // else return str;
+ // }else return str;
+ if(numTrimmedByZeros === numStr) return num;
+ else if(sign+numTrimmedByZeros === numStr) return num;
+ else return str;
+ }
+
+ if(trimmedStr === numStr) return num;
+ else if(trimmedStr === sign+numStr) return num;
+ // else{
+ // //number with +/- sign
+ // trimmedStr.test(/[-+][0-9]);
+
+ // }
+ return str;
+ }
+ // else if(!eNotation && trimmedStr && trimmedStr !== Number(trimmedStr) ) return str;
+
+ }else{ //non-numeric string
+ return str;
+ }
+ }
+}
+
+/**
+ *
+ * @param {string} numStr without leading zeros
+ * @returns
+ */
+function trimZeros(numStr){
+ if(numStr && numStr.indexOf(".") !== -1){//float
+ numStr = numStr.replace(/0+$/, ""); //remove ending zeros
+ if(numStr === ".") numStr = "0";
+ else if(numStr[0] === ".") numStr = "0"+numStr;
+ else if(numStr[numStr.length-1] === ".") numStr = numStr.substr(0,numStr.length-1);
+ return numStr;
+ }
+ return numStr;
+}
+module.exports = toNumber
+
+
+/***/ }),
+
+/***/ "./node_modules/webpack/buildin/amd-options.js":
+/*!****************************************!*\
+ !*** (webpack)/buildin/amd-options.js ***!
+ \****************************************/
+/*! no static exports found */
+/***/ (function(module, exports) {
+
+/* WEBPACK VAR INJECTION */(function(__webpack_amd_options__) {/* globals __webpack_amd_options__ */
+module.exports = __webpack_amd_options__;
+
+/* WEBPACK VAR INJECTION */}.call(this, {}))
+
+/***/ }),
+
+/***/ "./node_modules/webpack/buildin/global.js":
+/*!***********************************!*\
+ !*** (webpack)/buildin/global.js ***!
+ \***********************************/
+/*! no static exports found */
+/***/ (function(module, exports) {
+
+var g;
+
+// This works in non-strict mode
+g = (function() {
+ return this;
+})();
+
+try {
+ // This works if eval is allowed (see CSP)
+ g = g || new Function("return this")();
+} catch (e) {
+ // This works if the window reference is available
+ if (typeof window === "object") g = window;
+}
+
+// g can still be undefined, but nothing to do about it...
+// We return undefined, instead of nothing here, so it's
+// easier to handle this case. if(!global) { ...}
+
+module.exports = g;
+
+
+/***/ }),
+
+/***/ "./node_modules/webpack/buildin/module.js":
+/*!***********************************!*\
+ !*** (webpack)/buildin/module.js ***!
+ \***********************************/
+/*! no static exports found */
+/***/ (function(module, exports) {
+
+module.exports = function(module) {
+ if (!module.webpackPolyfill) {
+ module.deprecate = function() {};
+ module.paths = [];
+ // module.parent = undefined by default
+ if (!module.children) module.children = [];
+ Object.defineProperty(module, "loaded", {
+ enumerable: true,
+ get: function() {
+ return module.l;
+ }
+ });
+ Object.defineProperty(module, "id", {
+ enumerable: true,
+ get: function() {
+ return module.i;
+ }
+ });
+ module.webpackPolyfill = 1;
+ }
+ return module;
+};
+
+
+/***/ }),
+
+/***/ "./package.json":
+/*!**********************!*\
+ !*** ./package.json ***!
+ \**********************/
+/*! exports provided: name, version, description, main, scripts, repository, author, license, dependencies, devDependencies, default */
+/***/ (function(module) {
+
+module.exports = JSON.parse("{\"name\":\"cos-wx-sdk-v5\",\"version\":\"1.9.0-beta.1\",\"description\":\"小程序 SDK for [腾讯云对象存储服务](https://cloud.tencent.com/product/cos)\",\"main\":\"dist/cos-wx-sdk-v5.min.js\",\"scripts\":{\"prettier\":\"prettier --write src demo/demo-sdk.js demo/test.js demo/ciDemo\",\"dev\":\"cross-env NODE_ENV=development node build.js --mode=development\",\"build\":\"cross-env NODE_ENV=production node build.js --mode=production\",\"sts.js\":\"node server/sts.js\",\"postinstall\":\"node scripts/patch-check.js\"},\"repository\":{\"type\":\"git\",\"url\":\"http://github.com/tencentyun/cos-wx-sdk-v5.git\"},\"author\":\"carsonxu\",\"license\":\"ISC\",\"dependencies\":{\"fast-xml-parser\":\"4.5.0\",\"mime\":\"^2.4.6\"},\"devDependencies\":{\"@babel/core\":\"7.17.9\",\"@babel/preset-env\":\"7.16.11\",\"babel-loader\":\"8.2.5\",\"body-parser\":\"^1.18.3\",\"cross-env\":\"^7.0.3\",\"express\":\"^4.17.1\",\"patch-package\":\"^8.0.0\",\"prettier\":\"^3.0.1\",\"qcloud-cos-sts\":\"^3.0.2\",\"terser-webpack-plugin\":\"4.2.3\",\"webpack\":\"4.46.0\",\"webpack-cli\":\"4.10.0\"}}");
+
+/***/ }),
+
+/***/ "./src/advance.js":
+/*!************************!*\
+ !*** ./src/advance.js ***!
+ \************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+function _regeneratorRuntime() { "use strict"; /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ _regeneratorRuntime = function _regeneratorRuntime() { return e; }; var t, e = {}, r = Object.prototype, n = r.hasOwnProperty, o = Object.defineProperty || function (t, e, r) { t[e] = r.value; }, i = "function" == typeof Symbol ? Symbol : {}, a = i.iterator || "@@iterator", c = i.asyncIterator || "@@asyncIterator", u = i.toStringTag || "@@toStringTag"; function define(t, e, r) { return Object.defineProperty(t, e, { value: r, enumerable: !0, configurable: !0, writable: !0 }), t[e]; } try { define({}, ""); } catch (t) { define = function define(t, e, r) { return t[e] = r; }; } function wrap(t, e, r, n) { var i = e && e.prototype instanceof Generator ? e : Generator, a = Object.create(i.prototype), c = new Context(n || []); return o(a, "_invoke", { value: makeInvokeMethod(t, r, c) }), a; } function tryCatch(t, e, r) { try { return { type: "normal", arg: t.call(e, r) }; } catch (t) { return { type: "throw", arg: t }; } } e.wrap = wrap; var h = "suspendedStart", l = "suspendedYield", f = "executing", s = "completed", y = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} var p = {}; define(p, a, function () { return this; }); var d = Object.getPrototypeOf, v = d && d(d(values([]))); v && v !== r && n.call(v, a) && (p = v); var g = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(p); function defineIteratorMethods(t) { ["next", "throw", "return"].forEach(function (e) { define(t, e, function (t) { return this._invoke(e, t); }); }); } function AsyncIterator(t, e) { function invoke(r, o, i, a) { var c = tryCatch(t[r], t, o); if ("throw" !== c.type) { var u = c.arg, h = u.value; return h && "object" == _typeof(h) && n.call(h, "__await") ? e.resolve(h.__await).then(function (t) { invoke("next", t, i, a); }, function (t) { invoke("throw", t, i, a); }) : e.resolve(h).then(function (t) { u.value = t, i(u); }, function (t) { return invoke("throw", t, i, a); }); } a(c.arg); } var r; o(this, "_invoke", { value: function value(t, n) { function callInvokeWithMethodAndArg() { return new e(function (e, r) { invoke(t, n, e, r); }); } return r = r ? r.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); } }); } function makeInvokeMethod(e, r, n) { var o = h; return function (i, a) { if (o === f) throw Error("Generator is already running"); if (o === s) { if ("throw" === i) throw a; return { value: t, done: !0 }; } for (n.method = i, n.arg = a;;) { var c = n.delegate; if (c) { var u = maybeInvokeDelegate(c, n); if (u) { if (u === y) continue; return u; } } if ("next" === n.method) n.sent = n._sent = n.arg;else if ("throw" === n.method) { if (o === h) throw o = s, n.arg; n.dispatchException(n.arg); } else "return" === n.method && n.abrupt("return", n.arg); o = f; var p = tryCatch(e, r, n); if ("normal" === p.type) { if (o = n.done ? s : l, p.arg === y) continue; return { value: p.arg, done: n.done }; } "throw" === p.type && (o = s, n.method = "throw", n.arg = p.arg); } }; } function maybeInvokeDelegate(e, r) { var n = r.method, o = e.iterator[n]; if (o === t) return r.delegate = null, "throw" === n && e.iterator["return"] && (r.method = "return", r.arg = t, maybeInvokeDelegate(e, r), "throw" === r.method) || "return" !== n && (r.method = "throw", r.arg = new TypeError("The iterator does not provide a '" + n + "' method")), y; var i = tryCatch(o, e.iterator, r.arg); if ("throw" === i.type) return r.method = "throw", r.arg = i.arg, r.delegate = null, y; var a = i.arg; return a ? a.done ? (r[e.resultName] = a.value, r.next = e.nextLoc, "return" !== r.method && (r.method = "next", r.arg = t), r.delegate = null, y) : a : (r.method = "throw", r.arg = new TypeError("iterator result is not an object"), r.delegate = null, y); } function pushTryEntry(t) { var e = { tryLoc: t[0] }; 1 in t && (e.catchLoc = t[1]), 2 in t && (e.finallyLoc = t[2], e.afterLoc = t[3]), this.tryEntries.push(e); } function resetTryEntry(t) { var e = t.completion || {}; e.type = "normal", delete e.arg, t.completion = e; } function Context(t) { this.tryEntries = [{ tryLoc: "root" }], t.forEach(pushTryEntry, this), this.reset(!0); } function values(e) { if (e || "" === e) { var r = e[a]; if (r) return r.call(e); if ("function" == typeof e.next) return e; if (!isNaN(e.length)) { var o = -1, i = function next() { for (; ++o < e.length;) if (n.call(e, o)) return next.value = e[o], next.done = !1, next; return next.value = t, next.done = !0, next; }; return i.next = i; } } throw new TypeError(_typeof(e) + " is not iterable"); } return GeneratorFunction.prototype = GeneratorFunctionPrototype, o(g, "constructor", { value: GeneratorFunctionPrototype, configurable: !0 }), o(GeneratorFunctionPrototype, "constructor", { value: GeneratorFunction, configurable: !0 }), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, u, "GeneratorFunction"), e.isGeneratorFunction = function (t) { var e = "function" == typeof t && t.constructor; return !!e && (e === GeneratorFunction || "GeneratorFunction" === (e.displayName || e.name)); }, e.mark = function (t) { return Object.setPrototypeOf ? Object.setPrototypeOf(t, GeneratorFunctionPrototype) : (t.__proto__ = GeneratorFunctionPrototype, define(t, u, "GeneratorFunction")), t.prototype = Object.create(g), t; }, e.awrap = function (t) { return { __await: t }; }, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, c, function () { return this; }), e.AsyncIterator = AsyncIterator, e.async = function (t, r, n, o, i) { void 0 === i && (i = Promise); var a = new AsyncIterator(wrap(t, r, n, o), i); return e.isGeneratorFunction(r) ? a : a.next().then(function (t) { return t.done ? t.value : a.next(); }); }, defineIteratorMethods(g), define(g, u, "Generator"), define(g, a, function () { return this; }), define(g, "toString", function () { return "[object Generator]"; }), e.keys = function (t) { var e = Object(t), r = []; for (var n in e) r.push(n); return r.reverse(), function next() { for (; r.length;) { var t = r.pop(); if (t in e) return next.value = t, next.done = !1, next; } return next.done = !0, next; }; }, e.values = values, Context.prototype = { constructor: Context, reset: function reset(e) { if (this.prev = 0, this.next = 0, this.sent = this._sent = t, this.done = !1, this.delegate = null, this.method = "next", this.arg = t, this.tryEntries.forEach(resetTryEntry), !e) for (var r in this) "t" === r.charAt(0) && n.call(this, r) && !isNaN(+r.slice(1)) && (this[r] = t); }, stop: function stop() { this.done = !0; var t = this.tryEntries[0].completion; if ("throw" === t.type) throw t.arg; return this.rval; }, dispatchException: function dispatchException(e) { if (this.done) throw e; var r = this; function handle(n, o) { return a.type = "throw", a.arg = e, r.next = n, o && (r.method = "next", r.arg = t), !!o; } for (var o = this.tryEntries.length - 1; o >= 0; --o) { var i = this.tryEntries[o], a = i.completion; if ("root" === i.tryLoc) return handle("end"); if (i.tryLoc <= this.prev) { var c = n.call(i, "catchLoc"), u = n.call(i, "finallyLoc"); if (c && u) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } else if (c) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); } else { if (!u) throw Error("try statement without catch or finally"); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } } } }, abrupt: function abrupt(t, e) { for (var r = this.tryEntries.length - 1; r >= 0; --r) { var o = this.tryEntries[r]; if (o.tryLoc <= this.prev && n.call(o, "finallyLoc") && this.prev < o.finallyLoc) { var i = o; break; } } i && ("break" === t || "continue" === t) && i.tryLoc <= e && e <= i.finallyLoc && (i = null); var a = i ? i.completion : {}; return a.type = t, a.arg = e, i ? (this.method = "next", this.next = i.finallyLoc, y) : this.complete(a); }, complete: function complete(t, e) { if ("throw" === t.type) throw t.arg; return "break" === t.type || "continue" === t.type ? this.next = t.arg : "return" === t.type ? (this.rval = this.arg = t.arg, this.method = "return", this.next = "end") : "normal" === t.type && e && (this.next = e), y; }, finish: function finish(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.finallyLoc === t) return this.complete(r.completion, r.afterLoc), resetTryEntry(r), y; } }, "catch": function _catch(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.tryLoc === t) { var n = r.completion; if ("throw" === n.type) { var o = n.arg; resetTryEntry(r); } return o; } } throw Error("illegal catch attempt"); }, delegateYield: function delegateYield(e, r, n) { return this.delegate = { iterator: values(e), resultName: r, nextLoc: n }, "next" === this.method && (this.arg = t), y; } }, e; }
+function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
+function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
+function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
+var session = __webpack_require__(/*! ./session */ "./src/session.js");
+var Async = __webpack_require__(/*! ./async */ "./src/async.js");
+var EventProxy = __webpack_require__(/*! ./event */ "./src/event.js").EventProxy;
+var util = __webpack_require__(/*! ./util */ "./src/util.js");
+var Tracker = __webpack_require__(/*! ./tracker */ "./src/tracker.js");
+
+// 文件分块上传全过程,暴露的分块上传接口
+function sliceUploadFile(params, callback) {
+ var self = this;
+
+ // 如果小程序版本不支持获取文件分片内容,统一转到 简单上传 接口上传
+ if (!util.canFileSlice()) {
+ params.SkipTask = true;
+ if (self.options.SimpleUploadMethod === 'postObject') {
+ self.postObject(params, callback);
+ } else {
+ self.putObject(params, callback);
+ }
+ return;
+ }
+ var ep = new EventProxy();
+ var TaskId = params.TaskId;
+ var Bucket = params.Bucket;
+ var Region = params.Region;
+ var Key = params.Key;
+ var FilePath = params.FilePath;
+ var ChunkSize = params.ChunkSize || params.SliceSize || self.options.ChunkSize;
+ var AsyncLimit = params.AsyncLimit;
+ var StorageClass = params.StorageClass;
+ var ServerSideEncryption = params.ServerSideEncryption;
+ var FileSize;
+ var onProgress;
+ var onHashProgress = params.onHashProgress;
+ var tracker = params.tracker;
+ tracker && tracker.setParams({
+ chunkSize: ChunkSize
+ });
+
+ // 上传过程中出现错误,返回错误
+ ep.on('error', function (err) {
+ if (!self._isRunningTask(TaskId)) return;
+ var _err = {
+ UploadId: params.UploadData.UploadId || '',
+ err: err,
+ error: err
+ };
+ return callback(_err);
+ });
+
+ // 上传分块完成,开始 uploadSliceComplete 操作
+ ep.on('upload_complete', function (UploadCompleteData) {
+ var _UploadCompleteData = util.extend({
+ UploadId: params.UploadData.UploadId || ''
+ }, UploadCompleteData);
+ callback(null, _UploadCompleteData);
+ });
+
+ // 上传分块完成,开始 uploadSliceComplete 操作
+ ep.on('upload_slice_complete', function (UploadData) {
+ var metaHeaders = {};
+ util.each(params.Headers, function (val, k) {
+ var shortKey = k.toLowerCase();
+ if (shortKey.indexOf('x-cos-meta-') === 0 || shortKey === 'pic-operations') {
+ metaHeaders[k] = val;
+ }
+ });
+ uploadSliceComplete.call(self, {
+ Bucket: Bucket,
+ Region: Region,
+ Key: Key,
+ UploadId: UploadData.UploadId,
+ SliceList: UploadData.SliceList,
+ Headers: metaHeaders,
+ tracker: tracker
+ }, function (err, data) {
+ if (!self._isRunningTask(TaskId)) return;
+ session.removeUsing(UploadData.UploadId);
+ if (err) {
+ onProgress(null, true);
+ return ep.emit('error', err);
+ }
+ session.removeUploadId(UploadData.UploadId);
+ onProgress({
+ loaded: FileSize,
+ total: FileSize
+ }, true);
+ ep.emit('upload_complete', data);
+ });
+ });
+
+ // 获取 UploadId 完成,开始上传每个分片
+ ep.on('get_upload_data_finish', function (UploadData) {
+ // 处理 UploadId 缓存
+ var uuid = session.getFileId(params.FileStat, params.ChunkSize, Bucket, Key);
+ uuid && session.saveUploadId(uuid, UploadData.UploadId, self.options.UploadIdCacheLimit); // 缓存 UploadId
+ session.setUsing(UploadData.UploadId); // 标记 UploadId 为正在使用
+
+ // 获取 UploadId
+ onProgress(null, true); // 任务状态开始 uploading
+ uploadSliceList.call(self, {
+ TaskId: TaskId,
+ Bucket: Bucket,
+ Region: Region,
+ Key: Key,
+ FilePath: FilePath,
+ FileSize: FileSize,
+ SliceSize: ChunkSize,
+ AsyncLimit: AsyncLimit,
+ ServerSideEncryption: ServerSideEncryption,
+ UploadData: UploadData,
+ onProgress: onProgress,
+ tracker: tracker
+ }, function (err, data) {
+ if (!self._isRunningTask(TaskId)) return;
+ if (err) {
+ onProgress(null, true);
+ return ep.emit('error', err);
+ }
+ ep.emit('upload_slice_complete', data);
+ });
+ });
+
+ // 开始获取文件 UploadId,里面会视情况计算 ETag,并比对,保证文件一致性,也优化上传
+ ep.on('get_file_size_finish', function () {
+ onProgress = util.throttleOnProgress.call(self, FileSize, params.onProgress);
+ if (params.UploadData.UploadId) {
+ ep.emit('get_upload_data_finish', params.UploadData);
+ } else {
+ var _params = util.extend({
+ TaskId: TaskId,
+ Bucket: Bucket,
+ Region: Region,
+ Key: Key,
+ Headers: params.Headers,
+ StorageClass: StorageClass,
+ FilePath: FilePath,
+ FileSize: FileSize,
+ SliceSize: ChunkSize,
+ onHashProgress: onHashProgress,
+ tracker: tracker
+ }, params);
+ // 这里用户传入的params.FileSize可能单位不统一,必须使用sdk内获取的大小
+ _params.FileSize = FileSize;
+ getUploadIdAndPartList.call(self, _params, function (err, UploadData) {
+ if (!self._isRunningTask(TaskId)) return;
+ if (err) return ep.emit('error', err);
+ params.UploadData.UploadId = UploadData.UploadId;
+ params.UploadData.PartList = UploadData.PartList;
+ ep.emit('get_upload_data_finish', params.UploadData);
+ });
+ }
+ });
+
+ // 获取上传文件大小
+ FileSize = params.ContentLength;
+ delete params.ContentLength;
+ !params.Headers && (params.Headers = {});
+ util.each(params.Headers, function (item, key) {
+ if (key.toLowerCase() === 'content-length') {
+ delete params.Headers[key];
+ }
+ });
+
+ // 控制分片大小
+ (function () {
+ var SIZE = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 1024 * 2, 1024 * 4, 1024 * 5];
+ var AutoChunkSize = 1024 * 1024;
+ for (var i = 0; i < SIZE.length; i++) {
+ AutoChunkSize = SIZE[i] * 1024 * 1024;
+ if (FileSize / AutoChunkSize <= self.options.MaxPartNumber) break;
+ }
+ params.ChunkSize = params.SliceSize = ChunkSize = Math.max(ChunkSize, AutoChunkSize);
+ })();
+
+ // 开始上传
+ if (FileSize === 0) {
+ params.Body = '';
+ params.ContentLength = 0;
+ params.SkipTask = true;
+ self.putObject(params, function (err, data) {
+ if (err) {
+ return callback(err);
+ }
+ callback(null, data);
+ });
+ } else {
+ ep.emit('get_file_size_finish');
+ }
+}
+
+// 获取上传任务的 UploadId
+function getUploadIdAndPartList(params, callback) {
+ var TaskId = params.TaskId;
+ var Bucket = params.Bucket;
+ var Region = params.Region;
+ var Key = params.Key;
+ var StorageClass = params.StorageClass;
+ var self = this;
+
+ // 计算 ETag
+ var ETagMap = {};
+ var FileSize = params.FileSize;
+ var SliceSize = params.SliceSize;
+ var SliceCount = Math.ceil(FileSize / SliceSize);
+ var FinishSliceCount = 0;
+ var FinishSize = 0;
+ var onHashProgress = util.throttleOnProgress.call(self, FileSize, params.onHashProgress);
+ var getChunkETag = function getChunkETag(PartNumber, callback) {
+ var start = SliceSize * (PartNumber - 1);
+ var end = Math.min(start + SliceSize, FileSize);
+ var ChunkSize = end - start;
+ if (ETagMap[PartNumber]) {
+ callback(null, {
+ PartNumber: PartNumber,
+ ETag: ETagMap[PartNumber],
+ Size: ChunkSize
+ });
+ } else {
+ util.fileSlice(params.FilePath, start, end, function (chunkItem) {
+ try {
+ var md5 = util.getFileMd5(chunkItem);
+ } catch (err) {
+ return callback(err);
+ }
+ var ETag = '"' + md5 + '"';
+ ETagMap[PartNumber] = ETag;
+ FinishSliceCount += 1;
+ FinishSize += ChunkSize;
+ callback(null, {
+ PartNumber: PartNumber,
+ ETag: ETag,
+ Size: ChunkSize
+ });
+ onHashProgress({
+ loaded: FinishSize,
+ total: FileSize
+ });
+ });
+ }
+ };
+
+ // 通过和文件的 md5 对比,判断 UploadId 是否可用
+ var isAvailableUploadList = function isAvailableUploadList(PartList, callback) {
+ var PartCount = PartList.length;
+ // 如果没有分片,通过
+ if (PartCount === 0) {
+ return callback(null, true);
+ }
+ // 检查分片数量
+ if (PartCount > SliceCount) {
+ return callback(null, false);
+ }
+ // 检查分片大小
+ if (PartCount > 1) {
+ var PartSliceSize = Math.max(PartList[0].Size, PartList[1].Size);
+ if (PartSliceSize !== SliceSize) {
+ return callback(null, false);
+ }
+ }
+ // 逐个分片计算并检查 ETag 是否一致
+ var next = function next(index) {
+ if (index < PartCount) {
+ var Part = PartList[index];
+ getChunkETag(Part.PartNumber, function (err, chunk) {
+ if (chunk && chunk.ETag === Part.ETag && chunk.Size === Part.Size) {
+ next(index + 1);
+ } else {
+ callback(null, false);
+ }
+ });
+ } else {
+ callback(null, true);
+ }
+ };
+ next(0);
+ };
+ var ep = new EventProxy();
+ ep.on('error', function (errData) {
+ if (!self._isRunningTask(TaskId)) return;
+ return callback(errData);
+ });
+
+ // 存在 UploadId
+ ep.on('upload_id_available', function (UploadData) {
+ // 转换成 map
+ var map = {};
+ var list = [];
+ util.each(UploadData.PartList, function (item) {
+ map[item.PartNumber] = item;
+ });
+ for (var PartNumber = 1; PartNumber <= SliceCount; PartNumber++) {
+ var item = map[PartNumber];
+ if (item) {
+ item.PartNumber = PartNumber;
+ item.Uploaded = true;
+ } else {
+ item = {
+ PartNumber: PartNumber,
+ ETag: null,
+ Uploaded: false
+ };
+ }
+ list.push(item);
+ }
+ UploadData.PartList = list;
+ callback(null, UploadData);
+ });
+
+ // 不存在 UploadId, 初始化生成 UploadId
+ ep.on('no_available_upload_id', function () {
+ if (!self._isRunningTask(TaskId)) return;
+ var _params = util.extend({
+ Bucket: Bucket,
+ Region: Region,
+ Key: Key,
+ Headers: util.clone(params.Headers),
+ Query: util.clone(params.Query),
+ StorageClass: StorageClass,
+ calledBySdk: 'sliceUploadFile',
+ tracker: params.tracker
+ }, params);
+ self.multipartInit(_params, function (err, data) {
+ if (!self._isRunningTask(TaskId)) return;
+ if (err) return ep.emit('error', err);
+ var UploadId = data.UploadId;
+ if (!UploadId) {
+ return callback({
+ Message: 'no upload id'
+ });
+ }
+ ep.emit('upload_id_available', {
+ UploadId: UploadId,
+ PartList: []
+ });
+ });
+ });
+
+ // 如果已存在 UploadId,找一个可以用的 UploadId
+ ep.on('has_and_check_upload_id', function (UploadIdList) {
+ // 串行地,找一个内容一致的 UploadId
+ UploadIdList = UploadIdList.reverse();
+ Async.eachLimit(UploadIdList, 1, function (UploadId, asyncCallback) {
+ if (!self._isRunningTask(TaskId)) return;
+ // 如果正在上传,跳过
+ if (session.using[UploadId]) {
+ asyncCallback(); // 检查下一个 UploadId
+ return;
+ }
+ // 判断 UploadId 是否可用
+ wholeMultipartListPart.call(self, {
+ Bucket: Bucket,
+ Region: Region,
+ Key: Key,
+ UploadId: UploadId,
+ tracker: params.tracker
+ }, function (err, PartListData) {
+ if (!self._isRunningTask(TaskId)) return;
+ if (err) {
+ session.removeUsing(UploadId);
+ return ep.emit('error', err);
+ }
+ var PartList = PartListData.PartList;
+ PartList.forEach(function (item) {
+ item.PartNumber *= 1;
+ item.Size *= 1;
+ item.ETag = item.ETag || '';
+ });
+ isAvailableUploadList(PartList, function (err, isAvailable) {
+ if (!self._isRunningTask(TaskId)) return;
+ if (err) return ep.emit('error', err);
+ if (isAvailable) {
+ asyncCallback({
+ UploadId: UploadId,
+ PartList: PartList
+ }); // 马上结束
+ } else {
+ asyncCallback(); // 检查下一个 UploadId
+ }
+ });
+ });
+ }, function (AvailableUploadData) {
+ if (!self._isRunningTask(TaskId)) return;
+ onHashProgress(null, true);
+ if (AvailableUploadData && AvailableUploadData.UploadId) {
+ ep.emit('upload_id_available', AvailableUploadData);
+ } else {
+ ep.emit('no_available_upload_id');
+ }
+ });
+ });
+
+ // 在本地缓存找可用的 UploadId
+ ep.on('seek_local_avail_upload_id', function (RemoteUploadIdList) {
+ // 在本地找可用的 UploadId
+ var uuid = session.getFileId(params.FileStat, params.ChunkSize, Bucket, Key);
+ var LocalUploadIdList = session.getUploadIdList(uuid);
+ if (!uuid || !LocalUploadIdList) {
+ ep.emit('has_and_check_upload_id', RemoteUploadIdList);
+ return;
+ }
+ var next = function next(index) {
+ // 如果本地找不到可用 UploadId,再一个个遍历校验远端
+ if (index >= LocalUploadIdList.length) {
+ ep.emit('has_and_check_upload_id', RemoteUploadIdList);
+ return;
+ }
+ var UploadId = LocalUploadIdList[index];
+ // 如果不在远端 UploadId 列表里,跳过并删除
+ if (!util.isInArray(RemoteUploadIdList, UploadId)) {
+ session.removeUploadId(UploadId);
+ next(index + 1);
+ return;
+ }
+ // 如果正在上传,跳过
+ if (session.using[UploadId]) {
+ next(index + 1);
+ return;
+ }
+ // 判断 UploadId 是否存在线上
+ wholeMultipartListPart.call(self, {
+ Bucket: Bucket,
+ Region: Region,
+ Key: Key,
+ UploadId: UploadId,
+ tracker: params.tracker
+ }, function (err, PartListData) {
+ if (!self._isRunningTask(TaskId)) return;
+ if (err) {
+ // 如果 UploadId 获取会出错,跳过并删除
+ session.removeUploadId(UploadId);
+ next(index + 1);
+ } else {
+ // 找到可用 UploadId
+ ep.emit('upload_id_available', {
+ UploadId: UploadId,
+ PartList: PartListData.PartList
+ });
+ }
+ });
+ };
+ next(0);
+ });
+
+ // 获取线上 UploadId 列表
+ ep.on('get_remote_upload_id_list', function () {
+ // 获取符合条件的 UploadId 列表,因为同一个文件可以有多个上传任务。
+ wholeMultipartList.call(self, {
+ Bucket: Bucket,
+ Region: Region,
+ Key: Key,
+ tracker: params.tracker
+ }, function (err, data) {
+ if (!self._isRunningTask(TaskId)) return;
+ if (err) {
+ return ep.emit('error', err);
+ }
+ // 整理远端 UploadId 列表
+ var RemoteUploadIdList = util.filter(data.UploadList, function (item) {
+ return item.Key === Key && (!StorageClass || item.StorageClass.toUpperCase() === StorageClass.toUpperCase());
+ }).reverse().map(function (item) {
+ return item.UploadId || item.UploadID;
+ });
+ if (RemoteUploadIdList.length) {
+ ep.emit('seek_local_avail_upload_id', RemoteUploadIdList);
+ } else {
+ // 远端没有 UploadId,清理缓存的 UploadId
+ var uuid = session.getFileId(params.FileStat, params.ChunkSize, Bucket, Key),
+ LocalUploadIdList;
+ if (uuid && (LocalUploadIdList = session.getUploadIdList(uuid))) {
+ util.each(LocalUploadIdList, function (UploadId) {
+ session.removeUploadId(UploadId);
+ });
+ }
+ ep.emit('no_available_upload_id');
+ }
+ });
+ });
+
+ // 开始找可用 UploadId
+ ep.emit('get_remote_upload_id_list');
+}
+
+// 获取符合条件的全部上传任务 (条件包括 Bucket, Region, Prefix)
+function wholeMultipartList(params, callback) {
+ var self = this;
+ var UploadList = [];
+ var sendParams = {
+ Bucket: params.Bucket,
+ Region: params.Region,
+ Prefix: params.Key,
+ calledBySdk: params.calledBySdk || 'sliceUploadFile',
+ tracker: params.tracker
+ };
+ var next = function next() {
+ self.multipartList(sendParams, function (err, data) {
+ if (err) return callback(err);
+ UploadList.push.apply(UploadList, data.Upload || []);
+ if (data.IsTruncated === 'true') {
+ // 列表不完整
+ sendParams.KeyMarker = data.NextKeyMarker;
+ sendParams.UploadIdMarker = data.NextUploadIdMarker;
+ next();
+ } else {
+ callback(null, {
+ UploadList: UploadList
+ });
+ }
+ });
+ };
+ next();
+}
+
+// 获取指定上传任务的分块列表
+function wholeMultipartListPart(params, callback) {
+ var self = this;
+ var PartList = [];
+ var sendParams = {
+ Bucket: params.Bucket,
+ Region: params.Region,
+ Key: params.Key,
+ UploadId: params.UploadId,
+ calledBySdk: 'sliceUploadFile',
+ tracker: params.tracker
+ };
+ var next = function next() {
+ self.multipartListPart(sendParams, function (err, data) {
+ if (err) return callback(err);
+ PartList.push.apply(PartList, data.Part || []);
+ if (data.IsTruncated === 'true') {
+ // 列表不完整
+ sendParams.PartNumberMarker = data.NextPartNumberMarker;
+ next();
+ } else {
+ callback(null, {
+ PartList: PartList
+ });
+ }
+ });
+ };
+ next();
+}
+
+// 上传文件分块,包括
+/*
+ UploadId (上传任务编号)
+ AsyncLimit (并发量),
+ SliceList (上传的分块数组),
+ FilePath (本地文件的位置),
+ SliceSize (文件分块大小)
+ FileSize (文件大小)
+ onProgress (上传成功之后的回调函数)
+ */
+function uploadSliceList(params, cb) {
+ var self = this;
+ var TaskId = params.TaskId;
+ var Bucket = params.Bucket;
+ var Region = params.Region;
+ var Key = params.Key;
+ var UploadData = params.UploadData;
+ var FileSize = params.FileSize;
+ var SliceSize = params.SliceSize;
+ var ChunkParallel = Math.min(params.AsyncLimit || self.options.ChunkParallelLimit || 1, 256);
+ var FilePath = params.FilePath;
+ var SliceCount = Math.ceil(FileSize / SliceSize);
+ var FinishSize = 0;
+ var ServerSideEncryption = params.ServerSideEncryption;
+ var needUploadSlices = util.filter(UploadData.PartList, function (SliceItem) {
+ if (SliceItem['Uploaded']) {
+ FinishSize += SliceItem['PartNumber'] >= SliceCount ? FileSize % SliceSize || SliceSize : SliceSize;
+ }
+ return !SliceItem['Uploaded'];
+ });
+ var _onProgress2 = params.onProgress;
+ Async.eachLimit(needUploadSlices, ChunkParallel, function (SliceItem, asyncCallback) {
+ if (!self._isRunningTask(TaskId)) return;
+ var PartNumber = SliceItem['PartNumber'];
+ var currentSize = Math.min(FileSize, SliceItem['PartNumber'] * SliceSize) - (SliceItem['PartNumber'] - 1) * SliceSize;
+ var preAddSize = 0;
+ uploadSliceItem.call(self, {
+ TaskId: TaskId,
+ Bucket: Bucket,
+ Region: Region,
+ Key: Key,
+ SliceSize: SliceSize,
+ FileSize: FileSize,
+ PartNumber: PartNumber,
+ ServerSideEncryption: ServerSideEncryption,
+ FilePath: FilePath,
+ UploadData: UploadData,
+ onProgress: function onProgress(data) {
+ FinishSize += data.loaded - preAddSize;
+ preAddSize = data.loaded;
+ _onProgress2({
+ loaded: FinishSize,
+ total: FileSize
+ });
+ },
+ tracker: params.tracker
+ }, function (err, data) {
+ if (!self._isRunningTask(TaskId)) return;
+ if (err) {
+ FinishSize -= preAddSize;
+ } else {
+ FinishSize += currentSize - preAddSize;
+ SliceItem.ETag = data.ETag;
+ }
+ _onProgress2({
+ loaded: FinishSize,
+ total: FileSize
+ });
+ asyncCallback(err || null, data);
+ });
+ }, function (err) {
+ if (!self._isRunningTask(TaskId)) return;
+ if (err) return cb(err);
+ cb(null, {
+ UploadId: UploadData.UploadId,
+ SliceList: UploadData.PartList
+ });
+ });
+}
+
+// 上传指定分片
+function uploadSliceItem(params, callback) {
+ var self = this;
+ var TaskId = params.TaskId;
+ var Bucket = params.Bucket;
+ var Region = params.Region;
+ var Key = params.Key;
+ var FileSize = params.FileSize;
+ var FilePath = params.FilePath;
+ var PartNumber = params.PartNumber * 1;
+ var SliceSize = params.SliceSize;
+ var ServerSideEncryption = params.ServerSideEncryption;
+ var UploadData = params.UploadData;
+ var ChunkRetryTimes = self.options.ChunkRetryTimes + 1;
+ var Headers = params.Headers || {};
+ var start = SliceSize * (PartNumber - 1);
+ var ContentLength = SliceSize;
+ var end = start + SliceSize;
+ if (end > FileSize) {
+ end = FileSize;
+ ContentLength = end - start;
+ }
+ var headersWhiteList = ['x-cos-traffic-limit', 'x-cos-mime-limit'];
+ var headers = {};
+ util.each(Headers, function (v, k) {
+ if (headersWhiteList.indexOf(k) > -1) {
+ headers[k] = v;
+ }
+ });
+ util.fileSlice(FilePath, start, end, function (Body) {
+ var md5 = util.getFileMd5(Body);
+ var contentMd5 = md5 ? util.binaryBase64(md5) : null;
+ var PartItem = UploadData.PartList[PartNumber - 1];
+ Async.retry(ChunkRetryTimes, function (tryCallback) {
+ if (!self._isRunningTask(TaskId)) return;
+ self.multipartUpload({
+ TaskId: TaskId,
+ Bucket: Bucket,
+ Region: Region,
+ Key: Key,
+ ContentLength: ContentLength,
+ PartNumber: PartNumber,
+ UploadId: UploadData.UploadId,
+ ServerSideEncryption: ServerSideEncryption,
+ Body: Body,
+ Headers: headers,
+ onProgress: params.onProgress,
+ ContentMD5: contentMd5,
+ calledBySdk: 'sliceUploadFile',
+ tracker: params.tracker
+ }, function (err, data) {
+ if (!self._isRunningTask(TaskId)) return;
+ if (err) {
+ return tryCallback(err);
+ } else {
+ PartItem.Uploaded = true;
+ return tryCallback(null, data);
+ }
+ });
+ }, function (err, data) {
+ if (!self._isRunningTask(TaskId)) return;
+ return callback(err, data);
+ });
+ });
+}
+
+// 完成分块上传
+function uploadSliceComplete(params, callback) {
+ var Bucket = params.Bucket;
+ var Region = params.Region;
+ var Key = params.Key;
+ var UploadId = params.UploadId;
+ var SliceList = params.SliceList;
+ var self = this;
+ var ChunkRetryTimes = this.options.ChunkRetryTimes + 1;
+ var Parts = SliceList.map(function (item) {
+ return {
+ PartNumber: item.PartNumber,
+ ETag: item.ETag
+ };
+ });
+ // 完成上传的请求也做重试
+ Async.retry(ChunkRetryTimes, function (tryCallback) {
+ self.multipartComplete({
+ Bucket: Bucket,
+ Region: Region,
+ Key: Key,
+ UploadId: UploadId,
+ Parts: Parts,
+ calledBySdk: 'sliceUploadFile',
+ Headers: params.Headers || {},
+ tracker: params.tracker
+ }, tryCallback);
+ }, function (err, data) {
+ callback(err, data);
+ });
+}
+
+// 抛弃分块上传任务
+/*
+ AsyncLimit (抛弃上传任务的并发量),
+ UploadId (上传任务的编号,当 Level 为 task 时候需要)
+ Level (抛弃分块上传任务的级别,task : 抛弃指定的上传任务,file : 抛弃指定的文件对应的上传任务,其他值 :抛弃指定Bucket 的全部上传任务)
+ */
+function abortUploadTask(params, callback) {
+ var Bucket = params.Bucket;
+ var Region = params.Region;
+ var Key = params.Key;
+ var UploadId = params.UploadId;
+ var Level = params.Level || 'task';
+ var AsyncLimit = params.AsyncLimit;
+ var self = this;
+ var ep = new EventProxy();
+ ep.on('error', function (errData) {
+ return callback(errData);
+ });
+
+ // 已经获取到需要抛弃的任务列表
+ ep.on('get_abort_array', function (AbortArray) {
+ abortUploadTaskArray.call(self, {
+ Bucket: Bucket,
+ Region: Region,
+ Key: Key,
+ Headers: params.Headers,
+ AsyncLimit: AsyncLimit,
+ AbortArray: AbortArray
+ }, function (err, data) {
+ if (err) {
+ return callback(err);
+ }
+ callback(null, data);
+ });
+ });
+ if (Level === 'bucket') {
+ // Bucket 级别的任务抛弃,抛弃该 Bucket 下的全部上传任务
+ wholeMultipartList.call(self, {
+ Bucket: Bucket,
+ Region: Region,
+ calledBySdk: 'abortUploadTask'
+ }, function (err, data) {
+ if (err) {
+ return callback(err);
+ }
+ ep.emit('get_abort_array', data.UploadList || []);
+ });
+ } else if (Level === 'file') {
+ // 文件级别的任务抛弃,抛弃该文件的全部上传任务
+ if (!Key) return callback({
+ error: 'abort_upload_task_no_key'
+ });
+ wholeMultipartList.call(self, {
+ Bucket: Bucket,
+ Region: Region,
+ Key: Key,
+ calledBySdk: 'abortUploadTask'
+ }, function (err, data) {
+ if (err) {
+ return callback(err);
+ }
+ ep.emit('get_abort_array', data.UploadList || []);
+ });
+ } else if (Level === 'task') {
+ // 单个任务级别的任务抛弃,抛弃指定 UploadId 的上传任务
+ if (!UploadId) return callback({
+ error: 'abort_upload_task_no_id'
+ });
+ if (!Key) return callback({
+ error: 'abort_upload_task_no_key'
+ });
+ ep.emit('get_abort_array', [{
+ Key: Key,
+ UploadId: UploadId
+ }]);
+ } else {
+ return callback({
+ error: 'abort_unknown_level'
+ });
+ }
+}
+
+// 批量抛弃分块上传任务
+function abortUploadTaskArray(params, callback) {
+ var Bucket = params.Bucket;
+ var Region = params.Region;
+ var Key = params.Key;
+ var AbortArray = params.AbortArray;
+ var AsyncLimit = params.AsyncLimit || 1;
+ var self = this;
+ var index = 0;
+ var resultList = new Array(AbortArray.length);
+ Async.eachLimit(AbortArray, AsyncLimit, function (AbortItem, callback) {
+ var eachIndex = index;
+ if (Key && Key !== AbortItem.Key) {
+ resultList[eachIndex] = {
+ error: {
+ KeyNotMatch: true
+ }
+ };
+ callback(null);
+ return;
+ }
+ var UploadId = AbortItem.UploadId || AbortItem.UploadID;
+ self.multipartAbort({
+ Bucket: Bucket,
+ Region: Region,
+ Key: AbortItem.Key,
+ Headers: params.Headers,
+ UploadId: UploadId
+ }, function (err) {
+ var task = {
+ Bucket: Bucket,
+ Region: Region,
+ Key: AbortItem.Key,
+ UploadId: UploadId
+ };
+ resultList[eachIndex] = {
+ error: err,
+ task: task
+ };
+ callback(null);
+ });
+ index++;
+ }, function (err) {
+ if (err) {
+ return callback(err);
+ }
+ var successList = [];
+ var errorList = [];
+ for (var i = 0, len = resultList.length; i < len; i++) {
+ var item = resultList[i];
+ if (item['task']) {
+ if (item['error']) {
+ errorList.push(item['task']);
+ } else {
+ successList.push(item['task']);
+ }
+ }
+ }
+ return callback(null, {
+ successList: successList,
+ errorList: errorList
+ });
+ });
+}
+
+// 高级上传
+function uploadFile(_x, _x2) {
+ return _uploadFile.apply(this, arguments);
+} // 批量上传文件
+function _uploadFile() {
+ _uploadFile = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(params, callback) {
+ var self, SliceSize, taskList, FileSize, fileInfo, accelerate, realApi, _onTaskReady, _onFileFinish, onFileFinish, simpleUploadMethod, api;
+ return _regeneratorRuntime().wrap(function _callee$(_context) {
+ while (1) switch (_context.prev = _context.next) {
+ case 0:
+ self = this; // 判断多大的文件使用分片上传
+ SliceSize = params.SliceSize === undefined ? self.options.SliceSize : params.SliceSize;
+ taskList = []; // var FileSize = params.FileSize;
+ _context.prev = 3;
+ _context.next = 6;
+ return util.getFileSizeByPath(params.FilePath);
+ case 6:
+ FileSize = _context.sent;
+ _context.next = 13;
+ break;
+ case 9:
+ _context.prev = 9;
+ _context.t0 = _context["catch"](3);
+ callback({
+ error: _context.t0
+ });
+ return _context.abrupt("return");
+ case 13:
+ fileInfo = {
+ TaskId: ''
+ }; // 上传链路
+ if (self.options.EnableReporter) {
+ accelerate = self.options.UseAccelerate || typeof self.options.Domain === 'string' && self.options.Domain.includes('accelerate.');
+ realApi = FileSize > SliceSize ? 'sliceUploadFile' : 'putObject';
+ params.tracker = new Tracker({
+ Beacon: self.options.BeaconReporter,
+ clsReporter: self.options.ClsReporter,
+ bucket: params.Bucket,
+ region: params.Region,
+ apiName: 'uploadFile',
+ realApi: realApi,
+ fileKey: params.Key,
+ fileSize: FileSize,
+ accelerate: accelerate,
+ deepTracker: self.options.DeepTracker,
+ customId: self.options.CustomId,
+ delay: self.options.TrackerDelay
+ });
+ }
+
+ // 整理 option,用于返回给回调
+ util.each(params, function (v, k) {
+ if (_typeof(v) !== 'object' && typeof v !== 'function') {
+ fileInfo[k] = v;
+ }
+ });
+
+ // 处理文件 TaskReady
+ _onTaskReady = params.onTaskReady;
+ params.onTaskReady = function (tid) {
+ fileInfo.TaskId = tid;
+ _onTaskReady && _onTaskReady(tid);
+ };
+
+ // 处理文件完成
+ _onFileFinish = params.onFileFinish;
+ onFileFinish = function onFileFinish(err, data) {
+ // 格式化上报参数并上报
+ params.tracker && params.tracker.report(err, data);
+ _onFileFinish && _onFileFinish(err, data, fileInfo);
+ callback && callback(err, data);
+ }; // 添加上传任务
+ simpleUploadMethod = self.options.SimpleUploadMethod === 'postObject' ? 'postObject' : 'putObject';
+ api = FileSize > SliceSize ? 'sliceUploadFile' : simpleUploadMethod;
+ taskList.push({
+ api: api,
+ params: params,
+ callback: onFileFinish
+ });
+ self._addTasks(taskList);
+ case 24:
+ case "end":
+ return _context.stop();
+ }
+ }, _callee, this, [[3, 9]]);
+ }));
+ return _uploadFile.apply(this, arguments);
+}
+function uploadFiles(_x3, _x4) {
+ return _uploadFiles.apply(this, arguments);
+} // 分片复制文件
+function _uploadFiles() {
+ _uploadFiles = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3(params, callback) {
+ var self, SliceSize, TotalSize, TotalFinish, onTotalProgress, unFinishCount, _onTotalFileFinish, resultList, onTotalFileFinish, taskList, getTaskList;
+ return _regeneratorRuntime().wrap(function _callee3$(_context3) {
+ while (1) switch (_context3.prev = _context3.next) {
+ case 0:
+ self = this; // 判断多大的文件使用分片上传
+ SliceSize = params.SliceSize === undefined ? self.options.SliceSize : params.SliceSize; // 汇总返回进度
+ TotalSize = 0;
+ TotalFinish = 0;
+ onTotalProgress = util.throttleOnProgress.call(self, TotalFinish, params.onProgress); // 汇总返回回调
+ unFinishCount = params.files.length;
+ _onTotalFileFinish = params.onFileFinish;
+ resultList = Array(unFinishCount);
+ onTotalFileFinish = function onTotalFileFinish(err, data, options) {
+ onTotalProgress(null, true);
+ _onTotalFileFinish && _onTotalFileFinish(err, data, options);
+ resultList[options.Index] = {
+ options: options,
+ error: err,
+ data: data
+ };
+ if (--unFinishCount <= 0 && callback) {
+ callback(null, {
+ files: resultList
+ });
+ }
+ }; // 开始处理每个文件
+ taskList = [];
+ getTaskList = function getTaskList() {
+ return params.files.map(function (fileParams, index) {
+ return new Promise( /*#__PURE__*/function () {
+ var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(resolve) {
+ var FileSize, fileInfo, accelerate, realApi, _onTaskReady, PreAddSize, _onProgress, _onFileFinish, onFileFinish, simpleUploadMethod, api;
+ return _regeneratorRuntime().wrap(function _callee2$(_context2) {
+ while (1) switch (_context2.prev = _context2.next) {
+ case 0:
+ FileSize = 0;
+ _context2.prev = 1;
+ _context2.next = 4;
+ return util.getFileSizeByPath(fileParams.FilePath);
+ case 4:
+ FileSize = _context2.sent;
+ _context2.next = 9;
+ break;
+ case 7:
+ _context2.prev = 7;
+ _context2.t0 = _context2["catch"](1);
+ case 9:
+ fileInfo = {
+ Index: index,
+ TaskId: ''
+ }; // 更新文件总大小
+ TotalSize += FileSize;
+
+ // 单个文件上传链路
+ if (self.options.EnableReporter) {
+ accelerate = self.options.UseAccelerate || typeof self.options.Domain === 'string' && self.options.Domain.includes('accelerate.');
+ realApi = FileSize > SliceSize ? 'sliceUploadFile' : 'putObject';
+ fileParams.tracker = new Tracker({
+ Beacon: self.options.BeaconReporter,
+ clsReporter: self.options.ClsReporter,
+ bucket: fileParams.Bucket,
+ region: fileParams.Region,
+ apiName: 'uploadFiles',
+ realApi: realApi,
+ fileKey: fileParams.Key,
+ fileSize: FileSize,
+ accelerate: accelerate,
+ deepTracker: self.options.DeepTracker,
+ customId: self.options.CustomId,
+ delay: self.options.TrackerDelay
+ });
+ }
+
+ // 整理 option,用于返回给回调
+ util.each(fileParams, function (v, k) {
+ if (_typeof(v) !== 'object' && typeof v !== 'function') {
+ fileInfo[k] = v;
+ }
+ });
+
+ // 处理单个文件 TaskReady
+ _onTaskReady = fileParams.onTaskReady;
+ fileParams.onTaskReady = function (tid) {
+ fileInfo.TaskId = tid;
+ _onTaskReady && _onTaskReady(tid);
+ };
+
+ // 处理单个文件进度
+ PreAddSize = 0;
+ _onProgress = fileParams.onProgress;
+ fileParams.onProgress = function (info) {
+ TotalFinish = TotalFinish - PreAddSize + info.loaded;
+ PreAddSize = info.loaded;
+ _onProgress && _onProgress(info);
+ onTotalProgress({
+ loaded: TotalFinish,
+ total: TotalSize
+ });
+ };
+
+ // 处理单个文件完成
+ _onFileFinish = fileParams.onFileFinish;
+ onFileFinish = function onFileFinish(err, data) {
+ // 格式化上报参数并上报
+ fileParams.tracker && fileParams.tracker.report(err, data);
+ _onFileFinish && _onFileFinish(err, data);
+ onTotalFileFinish && onTotalFileFinish(err, data, fileInfo);
+ }; // 添加上传任务
+ simpleUploadMethod = self.options.SimpleUploadMethod === 'postObject' ? 'postObject' : 'putObject';
+ api = FileSize > SliceSize ? 'sliceUploadFile' : simpleUploadMethod;
+ taskList.push({
+ api: api,
+ params: fileParams,
+ callback: onFileFinish
+ });
+ resolve(true);
+ case 24:
+ case "end":
+ return _context2.stop();
+ }
+ }, _callee2, null, [[1, 7]]);
+ }));
+ return function (_x5) {
+ return _ref.apply(this, arguments);
+ };
+ }());
+ });
+ };
+ _context3.next = 13;
+ return Promise.all(getTaskList());
+ case 13:
+ self._addTasks(taskList);
+ case 14:
+ case "end":
+ return _context3.stop();
+ }
+ }, _callee3, this);
+ }));
+ return _uploadFiles.apply(this, arguments);
+}
+function sliceCopyFile(params, callback) {
+ var ep = new EventProxy();
+ var self = this;
+ var Bucket = params.Bucket;
+ var Region = params.Region;
+ var Key = params.Key;
+ var CopySource = params.CopySource;
+ var m = util.getSourceParams.call(this, CopySource);
+ if (!m) {
+ callback({
+ error: 'CopySource format error'
+ });
+ return;
+ }
+ var SourceBucket = m.Bucket;
+ var SourceRegion = m.Region;
+ var SourceKey = decodeURIComponent(m.Key);
+ var CopySliceSize = params.CopySliceSize === undefined ? self.options.CopySliceSize : params.CopySliceSize;
+ CopySliceSize = Math.max(0, CopySliceSize);
+ var ChunkSize = params.CopyChunkSize || this.options.CopyChunkSize;
+ var ChunkParallel = this.options.CopyChunkParallelLimit;
+ var ChunkRetryTimes = this.options.ChunkRetryTimes + 1;
+ var ChunkCount = 0;
+ var FinishSize = 0;
+ var FileSize;
+ var _onProgress3;
+ var SourceResHeaders = {};
+ var SourceHeaders = {};
+ var TargetHeader = {};
+
+ // 分片复制完成,开始 multipartComplete 操作
+ ep.on('copy_slice_complete', function (UploadData) {
+ var metaHeaders = {};
+ util.each(params.Headers, function (val, k) {
+ if (k.toLowerCase().indexOf('x-cos-meta-') === 0) metaHeaders[k] = val;
+ });
+ var Parts = util.map(UploadData.PartList, function (item) {
+ return {
+ PartNumber: item.PartNumber,
+ ETag: item.ETag
+ };
+ });
+ // 完成上传的请求也做重试
+ Async.retry(ChunkRetryTimes, function (tryCallback) {
+ self.multipartComplete({
+ Bucket: Bucket,
+ Region: Region,
+ Key: Key,
+ UploadId: UploadData.UploadId,
+ Parts: Parts,
+ tracker: params.tracker,
+ calledBySdk: 'sliceCopyFile'
+ }, tryCallback);
+ }, function (err, data) {
+ session.removeUsing(UploadData.UploadId); // 标记 UploadId 没被使用了,因为复制没提供重试,所以只要出错,就是 UploadId 停用了。
+ if (err) {
+ _onProgress3(null, true);
+ return callback(err);
+ }
+ session.removeUploadId(UploadData.UploadId);
+ _onProgress3({
+ loaded: FileSize,
+ total: FileSize
+ }, true);
+ callback(null, data);
+ });
+ });
+ ep.on('get_copy_data_finish', function (UploadData) {
+ // 处理 UploadId 缓存
+ var uuid = session.getCopyFileId(CopySource, SourceResHeaders, ChunkSize, Bucket, Key);
+ uuid && session.saveUploadId(uuid, UploadData.UploadId, self.options.UploadIdCacheLimit); // 缓存 UploadId
+ session.setUsing(UploadData.UploadId); // 标记 UploadId 为正在使用
+
+ var needCopySlices = util.filter(UploadData.PartList, function (SliceItem) {
+ if (SliceItem['Uploaded']) {
+ FinishSize += SliceItem['PartNumber'] >= ChunkCount ? FileSize % ChunkSize || ChunkSize : ChunkSize;
+ }
+ return !SliceItem['Uploaded'];
+ });
+ Async.eachLimit(needCopySlices, ChunkParallel, function (SliceItem, asyncCallback) {
+ var PartNumber = SliceItem.PartNumber;
+ var CopySourceRange = SliceItem.CopySourceRange;
+ var currentSize = SliceItem.end - SliceItem.start;
+ var preAddSize = 0;
+ Async.retry(ChunkRetryTimes, function (tryCallback) {
+ copySliceItem.call(self, {
+ Bucket: Bucket,
+ Region: Region,
+ Key: Key,
+ CopySource: CopySource,
+ UploadId: UploadData.UploadId,
+ PartNumber: PartNumber,
+ CopySourceRange: CopySourceRange,
+ tracker: params.tracker,
+ calledBySdk: 'sliceCopyFile',
+ onProgress: function onProgress(data) {
+ FinishSize += data.loaded - preAddSize;
+ preAddSize = data.loaded;
+ _onProgress3({
+ loaded: FinishSize,
+ total: FileSize
+ });
+ }
+ }, tryCallback);
+ }, function (err, data) {
+ if (err) {
+ return asyncCallback(err);
+ }
+ _onProgress3({
+ loaded: FinishSize,
+ total: FileSize
+ });
+ FinishSize += currentSize - preAddSize;
+ SliceItem.ETag = data.ETag;
+ asyncCallback(err || null, data);
+ });
+ }, function (err) {
+ if (err) {
+ session.removeUsing(UploadData.UploadId); // 标记 UploadId 没被使用了,因为复制没提供重试,所以只要出错,就是 UploadId 停用了。
+ _onProgress3(null, true);
+ return callback(err);
+ }
+ ep.emit('copy_slice_complete', UploadData);
+ });
+ });
+ ep.on('get_chunk_size_finish', function () {
+ var createNewUploadId = function createNewUploadId() {
+ self.multipartInit({
+ Bucket: Bucket,
+ Region: Region,
+ Key: Key,
+ Headers: TargetHeader,
+ tracker: params.tracker,
+ calledBySdk: 'sliceCopyFile'
+ }, function (err, data) {
+ if (err) return callback(err);
+ params.UploadId = data.UploadId;
+ ep.emit('get_copy_data_finish', {
+ UploadId: params.UploadId,
+ PartList: params.PartList
+ });
+ });
+ };
+
+ // 在本地找可用的 UploadId
+ var uuid = session.getCopyFileId(CopySource, SourceResHeaders, ChunkSize, Bucket, Key);
+ var LocalUploadIdList = session.getUploadIdList(uuid);
+ if (!uuid || !LocalUploadIdList) return createNewUploadId();
+ var next = function next(index) {
+ // 如果本地找不到可用 UploadId,再一个个遍历校验远端
+ if (index >= LocalUploadIdList.length) return createNewUploadId();
+ var UploadId = LocalUploadIdList[index];
+ // 如果正在被使用,跳过
+ if (session.using[UploadId]) return next(index + 1);
+ // 判断 UploadId 是否存在线上
+ wholeMultipartListPart.call(self, {
+ Bucket: Bucket,
+ Region: Region,
+ Key: Key,
+ UploadId: UploadId,
+ tracker: params.tracker,
+ calledBySdk: 'sliceCopyFile'
+ }, function (err, PartListData) {
+ if (err) {
+ // 如果 UploadId 获取会出错,跳过并删除
+ session.removeUploadId(UploadId);
+ next(index + 1);
+ } else {
+ // 如果异步回来 UploadId 已经被用了,也跳过
+ if (session.using[UploadId]) return next(index + 1);
+ // 找到可用 UploadId
+ var finishETagMap = {};
+ var offset = 0;
+ util.each(PartListData.PartList, function (PartItem) {
+ var size = parseInt(PartItem.Size);
+ var end = offset + size - 1;
+ finishETagMap[PartItem.PartNumber + '|' + offset + '|' + end] = PartItem.ETag;
+ offset += size;
+ });
+ util.each(params.PartList, function (PartItem) {
+ var ETag = finishETagMap[PartItem.PartNumber + '|' + PartItem.start + '|' + PartItem.end];
+ if (ETag) {
+ PartItem.ETag = ETag;
+ PartItem.Uploaded = true;
+ }
+ });
+ ep.emit('get_copy_data_finish', {
+ UploadId: UploadId,
+ PartList: params.PartList
+ });
+ }
+ });
+ };
+ next(0);
+ });
+ ep.on('get_file_size_finish', function () {
+ // 控制分片大小
+ (function () {
+ var SIZE = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 1024 * 2, 1024 * 4, 1024 * 5];
+ var AutoChunkSize = 1024 * 1024;
+ for (var i = 0; i < SIZE.length; i++) {
+ AutoChunkSize = SIZE[i] * 1024 * 1024;
+ if (FileSize / AutoChunkSize <= self.options.MaxPartNumber) break;
+ }
+ params.ChunkSize = ChunkSize = Math.max(ChunkSize, AutoChunkSize);
+ ChunkCount = Math.ceil(FileSize / ChunkSize);
+ var list = [];
+ for (var partNumber = 1; partNumber <= ChunkCount; partNumber++) {
+ var start = (partNumber - 1) * ChunkSize;
+ var end = partNumber * ChunkSize < FileSize ? partNumber * ChunkSize - 1 : FileSize - 1;
+ var item = {
+ PartNumber: partNumber,
+ start: start,
+ end: end,
+ CopySourceRange: 'bytes=' + start + '-' + end
+ };
+ list.push(item);
+ }
+ params.PartList = list;
+ })();
+ var TargetHeader;
+ if (params.Headers['x-cos-metadata-directive'] === 'Replaced') {
+ TargetHeader = params.Headers;
+ } else {
+ TargetHeader = SourceHeaders;
+ }
+ TargetHeader['x-cos-storage-class'] = params.Headers['x-cos-storage-class'] || SourceHeaders['x-cos-storage-class'];
+ TargetHeader = util.clearKey(TargetHeader);
+ /**
+ * 对于归档存储的对象,如果未恢复副本,则不允许 Copy
+ */
+ if (SourceHeaders['x-cos-storage-class'] === 'ARCHIVE' || SourceHeaders['x-cos-storage-class'] === 'DEEP_ARCHIVE') {
+ var restoreHeader = SourceHeaders['x-cos-restore'];
+ if (!restoreHeader || restoreHeader === 'ongoing-request="true"') {
+ callback({
+ error: 'Unrestored archive object is not allowed to be copied'
+ });
+ return;
+ }
+ }
+ /**
+ * 去除一些无用的头部,规避 multipartInit 出错
+ * 这些头部通常是在 putObjectCopy 时才使用
+ */
+ delete TargetHeader['x-cos-copy-source'];
+ delete TargetHeader['x-cos-metadata-directive'];
+ delete TargetHeader['x-cos-copy-source-If-Modified-Since'];
+ delete TargetHeader['x-cos-copy-source-If-Unmodified-Since'];
+ delete TargetHeader['x-cos-copy-source-If-Match'];
+ delete TargetHeader['x-cos-copy-source-If-None-Match'];
+ ep.emit('get_chunk_size_finish');
+ });
+
+ // 获取远端复制源文件的大小
+ self.headObject({
+ Bucket: SourceBucket,
+ Region: SourceRegion,
+ Key: SourceKey,
+ tracker: params.tracker,
+ calledBySdk: 'sliceCopyFile'
+ }, function (err, data) {
+ if (err) {
+ if (err.statusCode && err.statusCode === 404) {
+ callback({
+ ErrorStatus: SourceKey + ' Not Exist'
+ });
+ } else {
+ callback(err);
+ }
+ return;
+ }
+ FileSize = params.FileSize = data.headers['content-length'];
+ if (FileSize === undefined || !FileSize) {
+ callback({
+ error: 'get Content-Length error, please add "Content-Length" to CORS ExposeHeader setting.'
+ });
+ return;
+ }
+ params.tracker && params.tracker.setParams({
+ httpSize: FileSize
+ });
+ _onProgress3 = util.throttleOnProgress.call(self, FileSize, params.onProgress);
+
+ // 开始上传
+ if (FileSize <= CopySliceSize) {
+ if (!params.Headers['x-cos-metadata-directive']) {
+ params.Headers['x-cos-metadata-directive'] = 'Copy';
+ }
+ self.putObjectCopy(Object.assign(params, {
+ calledBySdk: 'sliceCopyFile'
+ }), function (err, data) {
+ if (err) {
+ _onProgress3(null, true);
+ return callback(err);
+ }
+ _onProgress3({
+ loaded: FileSize,
+ total: FileSize
+ }, true);
+ callback(err, data);
+ });
+ } else {
+ var resHeaders = data.headers;
+ SourceResHeaders = resHeaders;
+ SourceHeaders = {
+ 'Cache-Control': resHeaders['cache-control'],
+ 'Content-Disposition': resHeaders['content-disposition'],
+ 'Content-Encoding': resHeaders['content-encoding'],
+ 'Content-Type': resHeaders['content-type'],
+ Expires: resHeaders['expires'],
+ 'x-cos-storage-class': resHeaders['x-cos-storage-class']
+ };
+ util.each(resHeaders, function (v, k) {
+ var metaPrefix = 'x-cos-meta-';
+ if (k.indexOf(metaPrefix) === 0 && k.length > metaPrefix.length) {
+ SourceHeaders[k] = v;
+ }
+ });
+ ep.emit('get_file_size_finish');
+ }
+ });
+}
+
+// 复制指定分片
+function copySliceItem(params, callback) {
+ var TaskId = params.TaskId;
+ var Bucket = params.Bucket;
+ var Region = params.Region;
+ var Key = params.Key;
+ var CopySource = params.CopySource;
+ var UploadId = params.UploadId;
+ var PartNumber = params.PartNumber * 1;
+ var CopySourceRange = params.CopySourceRange;
+ var ChunkRetryTimes = this.options.ChunkRetryTimes + 1;
+ var self = this;
+ Async.retry(ChunkRetryTimes, function (tryCallback) {
+ self.uploadPartCopy({
+ TaskId: TaskId,
+ Bucket: Bucket,
+ Region: Region,
+ Key: Key,
+ CopySource: CopySource,
+ UploadId: UploadId,
+ PartNumber: PartNumber,
+ CopySourceRange: CopySourceRange,
+ onProgress: params.onProgress,
+ tracker: params.tracker,
+ calledBySdk: params.calledBySdk
+ }, function (err, data) {
+ tryCallback(err || null, data);
+ });
+ }, function (err, data) {
+ return callback(err, data);
+ });
+}
+var API_MAP = {
+ sliceUploadFile: sliceUploadFile,
+ abortUploadTask: abortUploadTask,
+ uploadFile: uploadFile,
+ uploadFiles: uploadFiles,
+ sliceCopyFile: sliceCopyFile
+};
+module.exports.init = function (COS, task) {
+ task.transferToTaskMethod(API_MAP, 'sliceUploadFile');
+ util.each(API_MAP, function (fn, apiName) {
+ COS.prototype[apiName] = util.apiWrapper(apiName, fn);
+ });
+};
+
+/***/ }),
+
+/***/ "./src/async.js":
+/*!**********************!*\
+ !*** ./src/async.js ***!
+ \**********************/
+/*! no static exports found */
+/***/ (function(module, exports) {
+
+var eachLimit = function eachLimit(arr, limit, iterator, callback) {
+ callback = callback || function () {};
+ if (!arr.length || limit <= 0) {
+ return callback();
+ }
+ var completed = 0;
+ var started = 0;
+ var running = 0;
+ (function replenish() {
+ if (completed >= arr.length) {
+ return callback();
+ }
+ while (running < limit && started < arr.length) {
+ started += 1;
+ running += 1;
+ iterator(arr[started - 1], function (err) {
+ if (err) {
+ callback(err);
+ callback = function callback() {};
+ } else {
+ completed += 1;
+ running -= 1;
+ if (completed >= arr.length) {
+ callback();
+ } else {
+ replenish();
+ }
+ }
+ });
+ }
+ })();
+};
+var retry = function retry(times, iterator, callback) {
+ var next = function next(index) {
+ iterator(function (err, data) {
+ if (err && index < times) {
+ next(index + 1);
+ } else {
+ callback(err, data);
+ }
+ });
+ };
+ if (times < 1) {
+ callback();
+ } else {
+ next(1);
+ }
+};
+var async = {
+ eachLimit: eachLimit,
+ retry: retry
+};
+module.exports = async;
+
+/***/ }),
+
+/***/ "./src/base.js":
+/*!*********************!*\
+ !*** ./src/base.js ***!
+ \*********************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+/* WEBPACK VAR INJECTION */(function(Buffer) {
+
+var REQUEST = __webpack_require__(/*! ../lib/request */ "./lib/request.js");
+var util = __webpack_require__(/*! ./util */ "./src/util.js");
+var mime = __webpack_require__(/*! mime */ "./node_modules/mime/index.js");
+
+// Bucket 相关
+
+/**
+ * 获取用户的 bucket 列表
+ * @param {Object} params 回调函数,必须,下面为参数列表
+ * 无特殊参数
+ * @param {Function} callback 回调函数,必须
+ */
+function getService(params, callback) {
+ if (typeof params === 'function') {
+ callback = params;
+ params = {};
+ }
+ var protocol = 'https:';
+ var domain = this.options.ServiceDomain;
+ var region = params.Region;
+ if (domain) {
+ domain = domain.replace(/\{\{Region\}\}/gi, region || '').replace(/\{\{.*?\}\}/gi, '');
+ if (!/^[a-zA-Z]+:\/\//.test(domain)) {
+ domain = protocol + '//' + domain;
+ }
+ if (domain.slice(-1) === '/') {
+ domain = domain.slice(0, -1);
+ }
+ } else if (region) {
+ domain = protocol + '//cos.' + region + '.myqcloud.com';
+ } else {
+ domain = protocol + '//service.cos.myqcloud.com';
+ }
+ var SignHost = '';
+ var standardHost = region ? 'cos.' + region + '.myqcloud.com' : 'service.cos.myqcloud.com';
+ var urlHost = domain.replace(/^https?:\/\/([^/]+)(\/.*)?$/, '$1');
+ if (standardHost === urlHost) SignHost = standardHost;
+ submitRequest.call(this, {
+ Action: 'name/cos:GetService',
+ url: domain,
+ method: 'GET',
+ headers: params.Headers,
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err) return callback(err);
+ var buckets = data && data.ListAllMyBucketsResult && data.ListAllMyBucketsResult.Buckets && data.ListAllMyBucketsResult.Buckets.Bucket || [];
+ buckets = util.isArray(buckets) ? buckets : [buckets];
+ var owner = data && data.ListAllMyBucketsResult && data.ListAllMyBucketsResult.Owner || {};
+ callback(null, {
+ Buckets: buckets,
+ Owner: owner,
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/**
+ * 创建 Bucket,并初始化访问权限
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {String} params.ACL 用户自定义文件权限,可以设置:private,public-read;默认值:private,非必须
+ * @param {String} params.GrantRead 赋予被授权者读的权限,格式x-cos-grant-read: uin=" ",uin=" ",非必须
+ * @param {String} params.GrantWrite 赋予被授权者写的权限,格式x-cos-grant-write: uin=" ",uin=" ",非必须
+ * @param {String} params.GrantFullControl 赋予被授权者读写权限,格式x-cos-grant-full-control: uin=" ",uin=" ",非必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回的数据
+ * @return {String} data.Location 操作地址
+ */
+function putBucket(params, callback) {
+ var self = this;
+ var xml = '';
+ if (params['BucketAZConfig']) {
+ var CreateBucketConfiguration = {
+ BucketAZConfig: params.BucketAZConfig
+ };
+ xml = util.json2xml({
+ CreateBucketConfiguration: CreateBucketConfiguration
+ });
+ }
+ submitRequest.call(this, {
+ Action: 'name/cos:PutBucket',
+ method: 'PUT',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ headers: params.Headers,
+ body: xml,
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err) return callback(err);
+ var url = getUrl({
+ protocol: self.options.Protocol,
+ domain: self.options.Domain,
+ bucket: params.Bucket,
+ region: params.Region,
+ isLocation: true
+ });
+ callback(null, {
+ Location: url,
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/**
+ * 查看是否存在该Bucket,是否有权限访问
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回的数据
+ * @return {Boolean} data.BucketExist Bucket是否存在
+ * @return {Boolean} data.BucketAuth 是否有 Bucket 的访问权限
+ */
+function headBucket(params, callback) {
+ submitRequest.call(this, {
+ Action: 'name/cos:HeadBucket',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ headers: params.Headers,
+ method: 'HEAD',
+ tracker: params.tracker
+ }, function (err, data) {
+ callback(err, data);
+ });
+}
+
+/**
+ * 获取 Bucket 下的 object 列表
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {String} params.Prefix 前缀匹配,用来规定返回的文件前缀地址,非必须
+ * @param {String} params.Delimiter 定界符为一个符号,如果有Prefix,则将Prefix到delimiter之间的相同路径归为一类,非必须
+ * @param {String} params.Marker 默认以UTF-8二进制顺序列出条目,所有列出条目从marker开始,非必须
+ * @param {String} params.MaxKeys 单次返回最大的条目数量,默认1000,非必须
+ * @param {String} params.EncodingType 规定返回值的编码方式,非必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回的数据
+ * @return {Object} data.ListBucketResult 返回的 object 列表信息
+ */
+function getBucket(params, callback) {
+ var reqParams = {};
+ reqParams['prefix'] = params['Prefix'] || '';
+ reqParams['delimiter'] = params['Delimiter'];
+ reqParams['marker'] = params['Marker'];
+ reqParams['max-keys'] = params['MaxKeys'];
+ reqParams['encoding-type'] = params['EncodingType'];
+ submitRequest.call(this, {
+ Action: 'name/cos:GetBucket',
+ ResourceKey: reqParams['prefix'],
+ method: 'GET',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ headers: params.Headers,
+ qs: reqParams,
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err) return callback(err);
+ var ListBucketResult = data.ListBucketResult || {};
+ var Contents = ListBucketResult.Contents || [];
+ var CommonPrefixes = ListBucketResult.CommonPrefixes || [];
+ Contents = util.isArray(Contents) ? Contents : [Contents];
+ CommonPrefixes = util.isArray(CommonPrefixes) ? CommonPrefixes : [CommonPrefixes];
+ var result = util.clone(ListBucketResult);
+ util.extend(result, {
+ Contents: Contents,
+ CommonPrefixes: CommonPrefixes,
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ callback(null, result);
+ });
+}
+
+/**
+ * 删除 Bucket
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回的数据
+ * @return {String} data.Location 操作地址
+ */
+function deleteBucket(params, callback) {
+ submitRequest.call(this, {
+ Action: 'name/cos:DeleteBucket',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ headers: params.Headers,
+ method: 'DELETE',
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err && err.statusCode === 204) {
+ return callback(null, {
+ statusCode: err.statusCode
+ });
+ } else if (err) {
+ return callback(err);
+ }
+ callback(null, {
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/**
+ * 设置 Bucket 的 权限列表
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {String} params.ACL 用户自定义文件权限,可以设置:private,public-read;默认值:private,非必须
+ * @param {String} params.GrantRead 赋予被授权者读的权限,格式x-cos-grant-read: uin=" ",uin=" ",非必须
+ * @param {String} params.GrantWrite 赋予被授权者写的权限,格式x-cos-grant-write: uin=" ",uin=" ",非必须
+ * @param {String} params.GrantFullControl 赋予被授权者读写权限,格式x-cos-grant-full-control: uin=" ",uin=" ",非必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回的数据
+ */
+function putBucketAcl(params, callback) {
+ var headers = params.Headers;
+ var xml = '';
+ if (params['AccessControlPolicy']) {
+ var AccessControlPolicy = util.clone(params['AccessControlPolicy'] || {});
+ var Grants = AccessControlPolicy.Grants || AccessControlPolicy.Grant;
+ Grants = util.isArray(Grants) ? Grants : [Grants];
+ delete AccessControlPolicy.Grant;
+ delete AccessControlPolicy.Grants;
+ AccessControlPolicy.AccessControlList = {
+ Grant: Grants
+ };
+ xml = util.json2xml({
+ AccessControlPolicy: AccessControlPolicy
+ });
+ headers['Content-Type'] = 'application/xml';
+ headers['Content-MD5'] = util.binaryBase64(util.md5(xml));
+ }
+
+ // Grant Header 去重
+ util.each(headers, function (val, key) {
+ if (key.indexOf('x-cos-grant-') === 0) {
+ headers[key] = uniqGrant(headers[key]);
+ }
+ });
+ submitRequest.call(this, {
+ Action: 'name/cos:PutBucketACL',
+ method: 'PUT',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ headers: headers,
+ action: 'acl',
+ body: xml,
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err) return callback(err);
+ callback(null, {
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/**
+ * 获取 Bucket 的 权限列表
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回的数据
+ * @return {Object} data.AccessControlPolicy 访问权限信息
+ */
+function getBucketAcl(params, callback) {
+ submitRequest.call(this, {
+ Action: 'name/cos:GetBucketACL',
+ method: 'GET',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ headers: params.Headers,
+ action: 'acl',
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err) return callback(err);
+ var AccessControlPolicy = data.AccessControlPolicy || {};
+ var Owner = AccessControlPolicy.Owner || {};
+ var Grant = AccessControlPolicy.AccessControlList.Grant || [];
+ Grant = util.isArray(Grant) ? Grant : [Grant];
+ var result = decodeAcl(AccessControlPolicy);
+ if (data.headers && data.headers['x-cos-acl']) {
+ result.ACL = data.headers['x-cos-acl'];
+ }
+ result = util.extend(result, {
+ Owner: Owner,
+ Grants: Grant,
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ callback(null, result);
+ });
+}
+
+/**
+ * 设置 Bucket 的 跨域设置
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {Object} params.CORSConfiguration 相关的跨域设置,必须
+ * @param {Array} params.CORSConfiguration.CORSRules 对应的跨域规则
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回的数据
+ */
+function putBucketCors(params, callback) {
+ var CORSConfiguration = params['CORSConfiguration'] || {};
+ var CORSRules = CORSConfiguration['CORSRules'] || params['CORSRules'] || [];
+ CORSRules = util.clone(util.isArray(CORSRules) ? CORSRules : [CORSRules]);
+ util.each(CORSRules, function (rule) {
+ util.each(['AllowedOrigin', 'AllowedHeader', 'AllowedMethod', 'ExposeHeader'], function (key) {
+ var sKey = key + 's';
+ var val = rule[sKey] || rule[key] || [];
+ delete rule[sKey];
+ rule[key] = util.isArray(val) ? val : [val];
+ });
+ });
+ var Conf = {
+ CORSRule: CORSRules
+ };
+ if (params.ResponseVary) Conf.ResponseVary = params.ResponseVary;
+ var xml = util.json2xml({
+ CORSConfiguration: Conf
+ });
+ var headers = params.Headers;
+ headers['Content-Type'] = 'application/xml';
+ headers['Content-MD5'] = util.binaryBase64(util.md5(xml));
+ submitRequest.call(this, {
+ Action: 'name/cos:PutBucketCORS',
+ method: 'PUT',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ body: xml,
+ action: 'cors',
+ headers: headers,
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err) return callback(err);
+ callback(null, {
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/**
+ * 获取 Bucket 的 跨域设置
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回的数据
+ * @return {Object} data.CORSRules Bucket的跨域设置
+ */
+function getBucketCors(params, callback) {
+ submitRequest.call(this, {
+ Action: 'name/cos:GetBucketCORS',
+ method: 'GET',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ headers: params.Headers,
+ action: 'cors',
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err) {
+ if (err.statusCode === 404 && err.error && err.error.Code === 'NoSuchCORSConfiguration') {
+ var result = {
+ CORSRules: [],
+ statusCode: err.statusCode
+ };
+ err.headers && (result.headers = err.headers);
+ callback(null, result);
+ } else {
+ callback(err);
+ }
+ return;
+ }
+ var CORSConfiguration = data.CORSConfiguration || {};
+ var CORSRules = CORSConfiguration.CORSRules || CORSConfiguration.CORSRule || [];
+ CORSRules = util.clone(util.isArray(CORSRules) ? CORSRules : [CORSRules]);
+ var ResponseVary = CORSConfiguration.ResponseVary;
+ util.each(CORSRules, function (rule) {
+ util.each(['AllowedOrigin', 'AllowedHeader', 'AllowedMethod', 'ExposeHeader'], function (key) {
+ var sKey = key + 's';
+ var val = rule[sKey] || rule[key] || [];
+ delete rule[key];
+ rule[sKey] = util.isArray(val) ? val : [val];
+ });
+ });
+ callback(null, {
+ CORSRules: CORSRules,
+ ResponseVary: ResponseVary,
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/**
+ * 删除 Bucket 的 跨域设置
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回的数据
+ */
+function deleteBucketCors(params, callback) {
+ submitRequest.call(this, {
+ Action: 'name/cos:DeleteBucketCORS',
+ method: 'DELETE',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ headers: params.Headers,
+ action: 'cors',
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err && err.statusCode === 204) {
+ return callback(null, {
+ statusCode: err.statusCode
+ });
+ } else if (err) {
+ return callback(err);
+ }
+ callback(null, {
+ statusCode: data.statusCode || err.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/**
+ * 获取 Bucket 的 地域信息
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回数据,包含地域信息 LocationConstraint
+ */
+function getBucketLocation(params, callback) {
+ submitRequest.call(this, {
+ Action: 'name/cos:GetBucketLocation',
+ method: 'GET',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ headers: params.Headers,
+ action: 'location',
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err) return callback(err);
+ callback(null, data);
+ });
+}
+function putBucketPolicy(params, callback) {
+ var Policy = params['Policy'];
+ var PolicyStr = Policy;
+ try {
+ if (typeof Policy === 'string') {
+ Policy = JSON.parse(PolicyStr);
+ } else {
+ PolicyStr = JSON.stringify(Policy);
+ }
+ } catch (e) {
+ callback({
+ error: 'Policy format error'
+ });
+ }
+ var headers = params.Headers;
+ headers['Content-Type'] = 'application/json';
+ headers['Content-MD5'] = util.binaryBase64(util.md5(PolicyStr));
+ submitRequest.call(this, {
+ Action: 'name/cos:PutBucketPolicy',
+ method: 'PUT',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ action: 'policy',
+ body: PolicyStr,
+ headers: headers,
+ json: true,
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err && err.statusCode === 204) {
+ return callback(null, {
+ statusCode: err.statusCode
+ });
+ } else if (err) {
+ return callback(err);
+ }
+ callback(null, {
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/**
+ * 获取 Bucket 的读取权限策略
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回数据
+ */
+function getBucketPolicy(params, callback) {
+ submitRequest.call(this, {
+ Action: 'name/cos:GetBucketPolicy',
+ method: 'GET',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ headers: params.Headers,
+ action: 'policy',
+ rawBody: true,
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err) {
+ if (err.statusCode && err.statusCode === 403) {
+ return callback({
+ ErrorStatus: 'Access Denied'
+ });
+ }
+ if (err.statusCode && err.statusCode === 405) {
+ return callback({
+ ErrorStatus: 'Method Not Allowed'
+ });
+ }
+ if (err.statusCode && err.statusCode === 404) {
+ return callback({
+ ErrorStatus: 'Policy Not Found'
+ });
+ }
+ return callback(err);
+ }
+ var Policy = {};
+ try {
+ Policy = JSON.parse(data.body);
+ } catch (e) {}
+ callback(null, {
+ Policy: Policy,
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/**
+ * 删除 Bucket 的 跨域设置
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回的数据
+ */
+function deleteBucketPolicy(params, callback) {
+ submitRequest.call(this, {
+ Action: 'name/cos:DeleteBucketPolicy',
+ method: 'DELETE',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ headers: params.Headers,
+ action: 'policy',
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err && err.statusCode === 204) {
+ return callback(null, {
+ statusCode: err.statusCode
+ });
+ } else if (err) {
+ return callback(err);
+ }
+ callback(null, {
+ statusCode: data.statusCode || err.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/**
+ * 设置 Bucket 的标签
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {Array} params.TagSet 标签设置,必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回数据
+ */
+function putBucketTagging(params, callback) {
+ var Tagging = params['Tagging'] || {};
+ var Tags = Tagging.TagSet || Tagging.Tags || params['Tags'] || [];
+ Tags = util.clone(util.isArray(Tags) ? Tags : [Tags]);
+ var xml = util.json2xml({
+ Tagging: {
+ TagSet: {
+ Tag: Tags
+ }
+ }
+ });
+ var headers = params.Headers;
+ headers['Content-Type'] = 'application/xml';
+ headers['Content-MD5'] = util.binaryBase64(util.md5(xml));
+ submitRequest.call(this, {
+ Action: 'name/cos:PutBucketTagging',
+ method: 'PUT',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ body: xml,
+ action: 'tagging',
+ headers: headers,
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err && err.statusCode === 204) {
+ return callback(null, {
+ statusCode: err.statusCode
+ });
+ } else if (err) {
+ return callback(err);
+ }
+ callback(null, {
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/**
+ * 获取 Bucket 的标签设置
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回数据
+ */
+function getBucketTagging(params, callback) {
+ submitRequest.call(this, {
+ Action: 'name/cos:GetBucketTagging',
+ method: 'GET',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ headers: params.Headers,
+ action: 'tagging',
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err) {
+ if (err.statusCode === 404 && err.error && (err.error === 'Not Found' || err.error.Code === 'NoSuchTagSet')) {
+ var result = {
+ Tags: [],
+ statusCode: err.statusCode
+ };
+ err.headers && (result.headers = err.headers);
+ callback(null, result);
+ } else {
+ callback(err);
+ }
+ return;
+ }
+ var Tags = [];
+ try {
+ Tags = data.Tagging.TagSet.Tag || [];
+ } catch (e) {}
+ Tags = util.clone(util.isArray(Tags) ? Tags : [Tags]);
+ callback(null, {
+ Tags: Tags,
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/**
+ * 删除 Bucket 的 标签设置
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回的数据
+ */
+function deleteBucketTagging(params, callback) {
+ submitRequest.call(this, {
+ Action: 'name/cos:DeleteBucketTagging',
+ method: 'DELETE',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ headers: params.Headers,
+ action: 'tagging',
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err && err.statusCode === 204) {
+ return callback(null, {
+ statusCode: err.statusCode
+ });
+ } else if (err) {
+ return callback(err);
+ }
+ callback(null, {
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+function putBucketLifecycle(params, callback) {
+ var LifecycleConfiguration = params['LifecycleConfiguration'] || {};
+ var Rules = LifecycleConfiguration.Rules || params.Rules || [];
+ Rules = util.clone(Rules);
+ var xml = util.json2xml({
+ LifecycleConfiguration: {
+ Rule: Rules
+ }
+ });
+ var headers = params.Headers;
+ headers['Content-Type'] = 'application/xml';
+ headers['Content-MD5'] = util.binaryBase64(util.md5(xml));
+ submitRequest.call(this, {
+ Action: 'name/cos:PutBucketLifecycle',
+ method: 'PUT',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ body: xml,
+ action: 'lifecycle',
+ headers: headers,
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err && err.statusCode === 204) {
+ return callback(null, {
+ statusCode: err.statusCode
+ });
+ } else if (err) {
+ return callback(err);
+ }
+ callback(null, {
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+function getBucketLifecycle(params, callback) {
+ submitRequest.call(this, {
+ Action: 'name/cos:GetBucketLifecycle',
+ method: 'GET',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ headers: params.Headers,
+ action: 'lifecycle',
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err) {
+ if (err.statusCode === 404 && err.error && err.error.Code === 'NoSuchLifecycleConfiguration') {
+ var result = {
+ Rules: [],
+ statusCode: err.statusCode
+ };
+ err.headers && (result.headers = err.headers);
+ callback(null, result);
+ } else {
+ callback(err);
+ }
+ return;
+ }
+ var Rules = [];
+ try {
+ Rules = data.LifecycleConfiguration.Rule || [];
+ } catch (e) {}
+ Rules = util.clone(util.isArray(Rules) ? Rules : [Rules]);
+ callback(null, {
+ Rules: Rules,
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+function deleteBucketLifecycle(params, callback) {
+ submitRequest.call(this, {
+ Action: 'name/cos:DeleteBucketLifecycle',
+ method: 'DELETE',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ headers: params.Headers,
+ action: 'lifecycle',
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err && err.statusCode === 204) {
+ return callback(null, {
+ statusCode: err.statusCode
+ });
+ } else if (err) {
+ return callback(err);
+ }
+ callback(null, {
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+function putBucketVersioning(params, callback) {
+ if (!params['VersioningConfiguration']) {
+ callback({
+ error: 'missing param VersioningConfiguration'
+ });
+ return;
+ }
+ var VersioningConfiguration = params['VersioningConfiguration'] || {};
+ var xml = util.json2xml({
+ VersioningConfiguration: VersioningConfiguration
+ });
+ var headers = params.Headers;
+ headers['Content-Type'] = 'application/xml';
+ headers['Content-MD5'] = util.binaryBase64(util.md5(xml));
+ submitRequest.call(this, {
+ Action: 'name/cos:PutBucketVersioning',
+ method: 'PUT',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ body: xml,
+ action: 'versioning',
+ headers: headers,
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err && err.statusCode === 204) {
+ return callback(null, {
+ statusCode: err.statusCode
+ });
+ } else if (err) {
+ return callback(err);
+ }
+ callback(null, {
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+function getBucketVersioning(params, callback) {
+ submitRequest.call(this, {
+ Action: 'name/cos:GetBucketVersioning',
+ method: 'GET',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ headers: params.Headers,
+ action: 'versioning',
+ tracker: params.tracker
+ }, function (err, data) {
+ if (!err) {
+ !data.VersioningConfiguration && (data.VersioningConfiguration = {});
+ }
+ callback(err, data);
+ });
+}
+function putBucketReplication(params, callback) {
+ var ReplicationConfiguration = util.clone(params.ReplicationConfiguration);
+ var xml = util.json2xml({
+ ReplicationConfiguration: ReplicationConfiguration
+ });
+ xml = xml.replace(/<(\/?)Rules>/gi, '<$1Rule>');
+ xml = xml.replace(/<(\/?)Tags>/gi, '<$1Tag>');
+ var headers = params.Headers;
+ headers['Content-Type'] = 'application/xml';
+ headers['Content-MD5'] = util.binaryBase64(util.md5(xml));
+ submitRequest.call(this, {
+ Action: 'name/cos:PutBucketReplication',
+ method: 'PUT',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ body: xml,
+ action: 'replication',
+ headers: headers,
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err && err.statusCode === 204) {
+ return callback(null, {
+ statusCode: err.statusCode
+ });
+ } else if (err) {
+ return callback(err);
+ }
+ callback(null, {
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+function getBucketReplication(params, callback) {
+ submitRequest.call(this, {
+ Action: 'name/cos:GetBucketReplication',
+ method: 'GET',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ headers: params.Headers,
+ action: 'replication',
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err) {
+ if (err.statusCode === 404 && err.error && (err.error === 'Not Found' || err.error.Code === 'ReplicationConfigurationnotFoundError')) {
+ var result = {
+ ReplicationConfiguration: {
+ Rules: []
+ },
+ statusCode: err.statusCode
+ };
+ err.headers && (result.headers = err.headers);
+ callback(null, result);
+ } else {
+ callback(err);
+ }
+ return;
+ }
+ if (!err) {
+ !data.ReplicationConfiguration && (data.ReplicationConfiguration = {});
+ }
+ if (data.ReplicationConfiguration.Rule) {
+ data.ReplicationConfiguration.Rules = data.ReplicationConfiguration.Rule;
+ delete data.ReplicationConfiguration.Rule;
+ }
+ callback(err, data);
+ });
+}
+function deleteBucketReplication(params, callback) {
+ submitRequest.call(this, {
+ Action: 'name/cos:DeleteBucketReplication',
+ method: 'DELETE',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ headers: params.Headers,
+ action: 'replication',
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err && err.statusCode === 204) {
+ return callback(null, {
+ statusCode: err.statusCode
+ });
+ } else if (err) {
+ return callback(err);
+ }
+ callback(null, {
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/**
+ * 设置 Bucket 静态网站配置信息
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {Object} params.WebsiteConfiguration 地域名称,必须
+ * @param {Object} WebsiteConfiguration.IndexDocument 索引文档,必须
+ * @param {Object} WebsiteConfiguration.ErrorDocument 错误文档,非必须
+ * @param {Object} WebsiteConfiguration.RedirectAllRequestsTo 重定向所有请求,非必须
+ * @param {Array} params.RoutingRules 重定向规则,非必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回数据
+ */
+function putBucketWebsite(params, callback) {
+ if (!params['WebsiteConfiguration']) {
+ callback({
+ error: 'missing param WebsiteConfiguration'
+ });
+ return;
+ }
+ var WebsiteConfiguration = util.clone(params['WebsiteConfiguration'] || {});
+ var RoutingRules = WebsiteConfiguration['RoutingRules'] || WebsiteConfiguration['RoutingRule'] || [];
+ RoutingRules = util.isArray(RoutingRules) ? RoutingRules : [RoutingRules];
+ delete WebsiteConfiguration.RoutingRule;
+ delete WebsiteConfiguration.RoutingRules;
+ if (RoutingRules.length) WebsiteConfiguration.RoutingRules = {
+ RoutingRule: RoutingRules
+ };
+ var xml = util.json2xml({
+ WebsiteConfiguration: WebsiteConfiguration
+ });
+ var headers = params.Headers;
+ headers['Content-Type'] = 'application/xml';
+ headers['Content-MD5'] = util.binaryBase64(util.md5(xml));
+ submitRequest.call(this, {
+ Action: 'name/cos:PutBucketWebsite',
+ method: 'PUT',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ body: xml,
+ action: 'website',
+ headers: headers,
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err && err.statusCode === 204) {
+ return callback(null, {
+ statusCode: err.statusCode
+ });
+ } else if (err) {
+ return callback(err);
+ }
+ callback(null, {
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/**
+ * 获取 Bucket 的静态网站配置信息
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回数据
+ */
+function getBucketWebsite(params, callback) {
+ submitRequest.call(this, {
+ Action: 'name/cos:GetBucketWebsite',
+ method: 'GET',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ Key: params.Key,
+ headers: params.Headers,
+ action: 'website',
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err) {
+ if (err.statusCode === 404 && err.error.Code === 'NoSuchWebsiteConfiguration') {
+ var result = {
+ WebsiteConfiguration: {},
+ statusCode: err.statusCode
+ };
+ err.headers && (result.headers = err.headers);
+ callback(null, result);
+ } else {
+ callback(err);
+ }
+ return;
+ }
+ var WebsiteConfiguration = data.WebsiteConfiguration || {};
+ if (WebsiteConfiguration['RoutingRules']) {
+ var RoutingRules = util.clone(WebsiteConfiguration['RoutingRules'].RoutingRule || []);
+ RoutingRules = util.makeArray(RoutingRules);
+ WebsiteConfiguration.RoutingRules = RoutingRules;
+ }
+ callback(null, {
+ WebsiteConfiguration: WebsiteConfiguration,
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/**
+ * 删除 Bucket 的静态网站配置
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回数据
+ */
+function deleteBucketWebsite(params, callback) {
+ submitRequest.call(this, {
+ Action: 'name/cos:DeleteBucketWebsite',
+ method: 'DELETE',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ headers: params.Headers,
+ action: 'website',
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err && err.statusCode === 204) {
+ return callback(null, {
+ statusCode: err.statusCode
+ });
+ } else if (err) {
+ return callback(err);
+ }
+ callback(null, {
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/**
+ * 设置 Bucket 的防盗链白名单或者黑名单
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {Object} params.RefererConfiguration 地域名称,必须
+ * @param {String} RefererConfiguration.Status 是否开启防盗链,枚举值:Enabled、Disabled
+ * @param {String} RefererConfiguration.RefererType 防盗链类型,枚举值:Black-List、White-List,必须
+ * @param {Array} RefererConfiguration.DomianList.Domain 生效域名,必须
+ * @param {String} RefererConfiguration.EmptyReferConfiguration ,非必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回数据
+ */
+function putBucketReferer(params, callback) {
+ if (!params['RefererConfiguration']) {
+ callback({
+ error: 'missing param RefererConfiguration'
+ });
+ return;
+ }
+ var RefererConfiguration = util.clone(params['RefererConfiguration'] || {});
+ var DomainList = RefererConfiguration['DomainList'] || {};
+ var Domains = DomainList['Domains'] || DomainList['Domain'] || [];
+ Domains = util.isArray(Domains) ? Domains : [Domains];
+ if (Domains.length) RefererConfiguration.DomainList = {
+ Domain: Domains
+ };
+ var xml = util.json2xml({
+ RefererConfiguration: RefererConfiguration
+ });
+ var headers = params.Headers;
+ headers['Content-Type'] = 'application/xml';
+ headers['Content-MD5'] = util.binaryBase64(util.md5(xml));
+ submitRequest.call(this, {
+ Action: 'name/cos:PutBucketReferer',
+ method: 'PUT',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ body: xml,
+ action: 'referer',
+ headers: headers,
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err && err.statusCode === 204) {
+ return callback(null, {
+ statusCode: err.statusCode
+ });
+ } else if (err) {
+ return callback(err);
+ }
+ callback(null, {
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/**
+ * 获取 Bucket 的防盗链白名单或者黑名单
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回数据
+ */
+function getBucketReferer(params, callback) {
+ submitRequest.call(this, {
+ Action: 'name/cos:GetBucketReferer',
+ method: 'GET',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ Key: params.Key,
+ headers: params.Headers,
+ action: 'referer',
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err) {
+ if (err.statusCode === 404 && err.error.Code === 'NoSuchRefererConfiguration') {
+ var result = {
+ WebsiteConfiguration: {},
+ statusCode: err.statusCode
+ };
+ err.headers && (result.headers = err.headers);
+ callback(null, result);
+ } else {
+ callback(err);
+ }
+ return;
+ }
+ var RefererConfiguration = data.RefererConfiguration || {};
+ if (RefererConfiguration['DomainList']) {
+ var Domains = util.makeArray(RefererConfiguration['DomainList'].Domain || []);
+ RefererConfiguration.DomainList = {
+ Domains: Domains
+ };
+ }
+ callback(null, {
+ RefererConfiguration: RefererConfiguration,
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/**
+ * 设置 Bucket 自定义域名
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回数据
+ */
+function putBucketDomain(params, callback) {
+ var DomainConfiguration = params['DomainConfiguration'] || {};
+ var DomainRule = DomainConfiguration.DomainRule || params.DomainRule || [];
+ DomainRule = util.clone(DomainRule);
+ var xml = util.json2xml({
+ DomainConfiguration: {
+ DomainRule: DomainRule
+ }
+ });
+ var headers = params.Headers;
+ headers['Content-Type'] = 'application/xml';
+ headers['Content-MD5'] = util.binaryBase64(util.md5(xml));
+ submitRequest.call(this, {
+ Action: 'name/cos:PutBucketDomain',
+ method: 'PUT',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ body: xml,
+ action: 'domain',
+ headers: headers,
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err && err.statusCode === 204) {
+ return callback(null, {
+ statusCode: err.statusCode
+ });
+ } else if (err) {
+ return callback(err);
+ }
+ callback(null, {
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/**
+ * 获取 Bucket 的自定义域名
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回数据
+ */
+function getBucketDomain(params, callback) {
+ submitRequest.call(this, {
+ Action: 'name/cos:GetBucketDomain',
+ method: 'GET',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ headers: params.Headers,
+ action: 'domain',
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err) return callback(err);
+ var DomainRule = [];
+ try {
+ DomainRule = data.DomainConfiguration.DomainRule || [];
+ } catch (e) {}
+ DomainRule = util.clone(util.isArray(DomainRule) ? DomainRule : [DomainRule]);
+ callback(null, {
+ DomainRule: DomainRule,
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/**
+ * 删除 Bucket 自定义域名
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回数据
+ */
+function deleteBucketDomain(params, callback) {
+ submitRequest.call(this, {
+ Action: 'name/cos:DeleteBucketDomain',
+ method: 'DELETE',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ headers: params.Headers,
+ action: 'domain',
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err && err.statusCode === 204) {
+ return callback(null, {
+ statusCode: err.statusCode
+ });
+ } else if (err) {
+ return callback(err);
+ }
+ callback(null, {
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/**
+ * 设置 Bucket 的回源
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回数据
+ */
+function putBucketOrigin(params, callback) {
+ var OriginConfiguration = params['OriginConfiguration'] || {};
+ var OriginRule = OriginConfiguration.OriginRule || params.OriginRule || [];
+ OriginRule = util.clone(OriginRule);
+ var xml = util.json2xml({
+ OriginConfiguration: {
+ OriginRule: OriginRule
+ }
+ });
+ var headers = params.Headers;
+ headers['Content-Type'] = 'application/xml';
+ headers['Content-MD5'] = util.binaryBase64(util.md5(xml));
+ submitRequest.call(this, {
+ Action: 'name/cos:PutBucketOrigin',
+ method: 'PUT',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ body: xml,
+ action: 'origin',
+ headers: headers,
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err && err.statusCode === 204) {
+ return callback(null, {
+ statusCode: err.statusCode
+ });
+ } else if (err) {
+ return callback(err);
+ }
+ callback(null, {
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/**
+ * 获取 Bucket 的回源
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回数据
+ */
+function getBucketOrigin(params, callback) {
+ submitRequest.call(this, {
+ Action: 'name/cos:GetBucketOrigin',
+ method: 'GET',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ headers: params.Headers,
+ action: 'origin',
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err) return callback(err);
+ var OriginRule = [];
+ try {
+ OriginRule = data.OriginConfiguration.OriginRule || [];
+ } catch (e) {}
+ OriginRule = util.clone(util.isArray(OriginRule) ? OriginRule : [OriginRule]);
+ callback(null, {
+ OriginRule: OriginRule,
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/**
+ * 删除 Bucket 的回源
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回数据
+ */
+function deleteBucketOrigin(params, callback) {
+ submitRequest.call(this, {
+ Action: 'name/cos:DeleteBucketOrigin',
+ method: 'DELETE',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ headers: params.Headers,
+ action: 'origin',
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err && err.statusCode === 204) {
+ return callback(null, {
+ statusCode: err.statusCode
+ });
+ } else if (err) {
+ return callback(err);
+ }
+ callback(null, {
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/**
+ * 设置 Bucket 的日志记录
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {(Object|String)} params.BucketLoggingStatus 说明日志记录配置的状态,如果无子节点信息则意为关闭日志记录,必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回数据
+ */
+function putBucketLogging(params, callback) {
+ var xml = util.json2xml({
+ BucketLoggingStatus: params['BucketLoggingStatus'] || ''
+ });
+ var headers = params.Headers;
+ headers['Content-Type'] = 'application/xml';
+ headers['Content-MD5'] = util.binaryBase64(util.md5(xml));
+ submitRequest.call(this, {
+ Action: 'name/cos:PutBucketLogging',
+ method: 'PUT',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ body: xml,
+ action: 'logging',
+ headers: headers,
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err && err.statusCode === 204) {
+ return callback(null, {
+ statusCode: err.statusCode
+ });
+ } else if (err) {
+ return callback(err);
+ }
+ callback(null, {
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/**
+ * 获取 Bucket 的日志记录
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回数据
+ */
+function getBucketLogging(params, callback) {
+ submitRequest.call(this, {
+ Action: 'name/cos:GetBucketLogging',
+ method: 'GET',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ headers: params.Headers,
+ action: 'logging',
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err) return callback(err);
+ delete data.BucketLoggingStatus._xmlns;
+ callback(null, {
+ BucketLoggingStatus: data.BucketLoggingStatus,
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/**
+ * 创建/编辑 Bucket 的清单任务
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {String} params.Id 清单任务的名称,必须
+ * @param {Object} params.InventoryConfiguration 包含清单的配置参数,必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回数据
+ */
+function putBucketInventory(params, callback) {
+ var InventoryConfiguration = util.clone(params['InventoryConfiguration']);
+ if (InventoryConfiguration.OptionalFields) {
+ var Field = InventoryConfiguration.OptionalFields || [];
+ InventoryConfiguration.OptionalFields = {
+ Field: Field
+ };
+ }
+ if (InventoryConfiguration.Destination && InventoryConfiguration.Destination.COSBucketDestination && InventoryConfiguration.Destination.COSBucketDestination.Encryption) {
+ var Encryption = InventoryConfiguration.Destination.COSBucketDestination.Encryption;
+ if (Object.keys(Encryption).indexOf('SSECOS') > -1) {
+ Encryption['SSE-COS'] = Encryption['SSECOS'];
+ delete Encryption['SSECOS'];
+ }
+ }
+ var xml = util.json2xml({
+ InventoryConfiguration: InventoryConfiguration
+ });
+ var headers = params.Headers;
+ headers['Content-Type'] = 'application/xml';
+ headers['Content-MD5'] = util.binaryBase64(util.md5(xml));
+ submitRequest.call(this, {
+ Action: 'name/cos:PutBucketInventory',
+ method: 'PUT',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ body: xml,
+ action: 'inventory',
+ qs: {
+ id: params['Id']
+ },
+ headers: headers,
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err && err.statusCode === 204) {
+ return callback(null, {
+ statusCode: err.statusCode
+ });
+ } else if (err) {
+ return callback(err);
+ }
+ callback(null, {
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/**
+ * 获取 Bucket 的清单任务信息
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {String} params.Id 清单任务的名称,必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回数据
+ */
+function getBucketInventory(params, callback) {
+ submitRequest.call(this, {
+ Action: 'name/cos:GetBucketInventory',
+ method: 'GET',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ headers: params.Headers,
+ action: 'inventory',
+ qs: {
+ id: params['Id']
+ },
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err) return callback(err);
+ var InventoryConfiguration = data['InventoryConfiguration'];
+ if (InventoryConfiguration && InventoryConfiguration.OptionalFields && InventoryConfiguration.OptionalFields.Field) {
+ var Field = InventoryConfiguration.OptionalFields.Field;
+ if (!util.isArray(Field)) {
+ Field = [Field];
+ }
+ InventoryConfiguration.OptionalFields = Field;
+ }
+ if (InventoryConfiguration.Destination && InventoryConfiguration.Destination.COSBucketDestination && InventoryConfiguration.Destination.COSBucketDestination.Encryption) {
+ var Encryption = InventoryConfiguration.Destination.COSBucketDestination.Encryption;
+ if (Object.keys(Encryption).indexOf('SSE-COS') > -1) {
+ Encryption['SSECOS'] = Encryption['SSE-COS'];
+ delete Encryption['SSE-COS'];
+ }
+ }
+ callback(null, {
+ InventoryConfiguration: InventoryConfiguration,
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/**
+ * 获取 Bucket 的清单任务信息
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {String} params.ContinuationToken 当 COS 响应体中 IsTruncated 为 true,且 NextContinuationToken 节点中存在参数值时,您可以将这个参数作为 continuation-token 参数值,以获取下一页的清单任务信息,非必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回数据
+ */
+function listBucketInventory(params, callback) {
+ submitRequest.call(this, {
+ Action: 'name/cos:ListBucketInventory',
+ method: 'GET',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ headers: params.Headers,
+ action: 'inventory',
+ qs: {
+ 'continuation-token': params['ContinuationToken']
+ },
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err) return callback(err);
+ var ListInventoryConfigurationResult = data['ListInventoryConfigurationResult'];
+ var InventoryConfigurations = ListInventoryConfigurationResult.InventoryConfiguration || [];
+ InventoryConfigurations = util.isArray(InventoryConfigurations) ? InventoryConfigurations : [InventoryConfigurations];
+ delete ListInventoryConfigurationResult['InventoryConfiguration'];
+ util.each(InventoryConfigurations, function (InventoryConfiguration) {
+ if (InventoryConfiguration && InventoryConfiguration.OptionalFields && InventoryConfiguration.OptionalFields.Field) {
+ var Field = InventoryConfiguration.OptionalFields.Field;
+ if (!util.isArray(Field)) {
+ Field = [Field];
+ }
+ InventoryConfiguration.OptionalFields = Field;
+ }
+ if (InventoryConfiguration.Destination && InventoryConfiguration.Destination.COSBucketDestination && InventoryConfiguration.Destination.COSBucketDestination.Encryption) {
+ var Encryption = InventoryConfiguration.Destination.COSBucketDestination.Encryption;
+ if (Object.keys(Encryption).indexOf('SSE-COS') > -1) {
+ Encryption['SSECOS'] = Encryption['SSE-COS'];
+ delete Encryption['SSE-COS'];
+ }
+ }
+ });
+ ListInventoryConfigurationResult.InventoryConfigurations = InventoryConfigurations;
+ util.extend(ListInventoryConfigurationResult, {
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ callback(null, ListInventoryConfigurationResult);
+ });
+}
+
+/**
+ * 删除 Bucket 的清单任务
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {String} params.Id 清单任务的名称,必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回数据
+ */
+function deleteBucketInventory(params, callback) {
+ submitRequest.call(this, {
+ Action: 'name/cos:DeleteBucketInventory',
+ method: 'DELETE',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ headers: params.Headers,
+ action: 'inventory',
+ qs: {
+ id: params['Id']
+ },
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err && err.statusCode === 204) {
+ return callback(null, {
+ statusCode: err.statusCode
+ });
+ } else if (err) {
+ return callback(err);
+ }
+ callback(null, {
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/* 全球加速 */
+function putBucketAccelerate(params, callback) {
+ if (!params['AccelerateConfiguration']) {
+ callback({
+ error: 'missing param AccelerateConfiguration'
+ });
+ return;
+ }
+ var configuration = {
+ AccelerateConfiguration: params.AccelerateConfiguration || {}
+ };
+ var xml = util.json2xml(configuration);
+ var headers = {};
+ headers['Content-Type'] = 'application/xml';
+ headers['Content-MD5'] = util.binaryBase64(util.md5(xml));
+ submitRequest.call(this, {
+ Interface: 'putBucketAccelerate',
+ Action: 'name/cos:PutBucketAccelerate',
+ method: 'PUT',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ body: xml,
+ action: 'accelerate',
+ headers: headers,
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err) return callback(err);
+ callback(null, {
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+function getBucketAccelerate(params, callback) {
+ submitRequest.call(this, {
+ Interface: 'getBucketAccelerate',
+ Action: 'name/cos:GetBucketAccelerate',
+ method: 'GET',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ action: 'accelerate',
+ tracker: params.tracker
+ }, function (err, data) {
+ if (!err) {
+ !data.AccelerateConfiguration && (data.AccelerateConfiguration = {});
+ }
+ callback(err, data);
+ });
+}
+
+// Object 相关
+
+/**
+ * 取回对应Object的元数据,Head的权限与Get的权限一致
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {String} params.Key 文件名称,必须
+ * @param {String} params.IfModifiedSince 当Object在指定时间后被修改,则返回对应Object元信息,否则返回304,非必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 为指定 object 的元数据,如果设置了 IfModifiedSince ,且文件未修改,则返回一个对象,NotModified 属性为 true
+ * @return {Boolean} data.NotModified 是否在 IfModifiedSince 时间点之后未修改该 object,则为 true
+ */
+function headObject(params, callback) {
+ submitRequest.call(this, {
+ Action: 'name/cos:HeadObject',
+ method: 'HEAD',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ Key: params.Key,
+ VersionId: params.VersionId,
+ headers: params.Headers,
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err) {
+ var statusCode = err.statusCode;
+ if (params.Headers['If-Modified-Since'] && statusCode && statusCode === 304) {
+ return callback(null, {
+ NotModified: true,
+ statusCode: statusCode
+ });
+ }
+ return callback(err);
+ }
+ data.ETag = util.attr(data.headers, 'etag', '');
+ callback(null, data);
+ });
+}
+function listObjectVersions(params, callback) {
+ var reqParams = {};
+ reqParams['prefix'] = params['Prefix'] || '';
+ reqParams['delimiter'] = params['Delimiter'];
+ reqParams['key-marker'] = params['KeyMarker'];
+ reqParams['version-id-marker'] = params['VersionIdMarker'];
+ reqParams['max-keys'] = params['MaxKeys'];
+ reqParams['encoding-type'] = params['EncodingType'];
+ submitRequest.call(this, {
+ Action: 'name/cos:GetBucketObjectVersions',
+ ResourceKey: reqParams['prefix'],
+ method: 'GET',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ headers: params.Headers,
+ qs: reqParams,
+ action: 'versions',
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err) return callback(err);
+ var ListVersionsResult = data.ListVersionsResult || {};
+ var DeleteMarkers = ListVersionsResult.DeleteMarker || [];
+ DeleteMarkers = util.isArray(DeleteMarkers) ? DeleteMarkers : [DeleteMarkers];
+ var Versions = ListVersionsResult.Version || [];
+ Versions = util.isArray(Versions) ? Versions : [Versions];
+ var result = util.clone(ListVersionsResult);
+ delete result.DeleteMarker;
+ delete result.Version;
+ util.extend(result, {
+ DeleteMarkers: DeleteMarkers,
+ Versions: Versions,
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ callback(null, result);
+ });
+}
+
+/**
+ * 下载 object
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {String} params.Key 文件名称,必须
+ * @param {WriteStream} params.Output 文件写入流,非必须
+ * @param {String} params.IfModifiedSince 当Object在指定时间后被修改,则返回对应Object元信息,否则返回304,非必须
+ * @param {String} params.IfUnmodifiedSince 如果文件修改时间早于或等于指定时间,才返回文件内容。否则返回 412 (precondition failed),非必须
+ * @param {String} params.IfMatch 当 ETag 与指定的内容一致,才返回文件。否则返回 412 (precondition failed),非必须
+ * @param {String} params.IfNoneMatch 当 ETag 与指定的内容不一致,才返回文件。否则返回304 (not modified),非必须
+ * @param {String} params.ResponseContentType 设置返回头部中的 Content-Type 参数,非必须
+ * @param {String} params.ResponseContentLanguage 设置返回头部中的 Content-Language 参数,非必须
+ * @param {String} params.ResponseExpires 设置返回头部中的 Content-Expires 参数,非必须
+ * @param {String} params.ResponseCacheControl 设置返回头部中的 Cache-Control 参数,非必须
+ * @param {String} params.ResponseContentDisposition 设置返回头部中的 Content-Disposition 参数,非必须
+ * @param {String} params.ResponseContentEncoding 设置返回头部中的 Content-Encoding 参数,非必须
+ * @param {Function} callback 回调函数,必须
+ * @param {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @param {Object} data 为对应的 object 数据,包括 body 和 headers
+ */
+function getObject(params, callback) {
+ if (this.options.ObjectKeySimplifyCheck) {
+ // getObject 的 Key 需要校验,避免调用成 getBucket
+ var formatKey = util.simplifyPath(params.Key);
+ if (formatKey === '/') {
+ callback(util.error(new Error('The Getobject Key is illegal')));
+ return;
+ }
+ }
+ var reqParams = params.Query || {};
+ var reqParamsStr = params.QueryString || '';
+ var tracker = params.tracker;
+ tracker && tracker.setParams({
+ signStartTime: new Date().getTime()
+ });
+ reqParams['response-content-type'] = params['ResponseContentType'];
+ reqParams['response-content-language'] = params['ResponseContentLanguage'];
+ reqParams['response-expires'] = params['ResponseExpires'];
+ reqParams['response-cache-control'] = params['ResponseCacheControl'];
+ reqParams['response-content-disposition'] = params['ResponseContentDisposition'];
+ reqParams['response-content-encoding'] = params['ResponseContentEncoding'];
+
+ // 如果用户自己传入了 output
+ submitRequest.call(this, {
+ Action: 'name/cos:GetObject',
+ method: 'GET',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ Key: params.Key,
+ VersionId: params.VersionId,
+ headers: params.Headers,
+ qs: reqParams,
+ qsStr: reqParamsStr,
+ rawBody: true,
+ dataType: params.DataType,
+ tracker: tracker
+ }, function (err, data) {
+ if (err) {
+ var statusCode = err.statusCode;
+ if (params.Headers['If-Modified-Since'] && statusCode && statusCode === 304) {
+ return callback(null, {
+ NotModified: true
+ });
+ }
+ return callback(err);
+ }
+ callback(null, {
+ Body: data.body,
+ ETag: util.attr(data.headers, 'etag', ''),
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/**
+ * 上传 object
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {String} params.Key 文件名称,必须
+ * @param {String} params.Body 上传文件的内容,只支持字符串
+ * @param {String} params.CacheControl RFC 2616 中定义的缓存策略,将作为 Object 元数据保存,非必须
+ * @param {String} params.ContentDisposition RFC 2616 中定义的文件名称,将作为 Object 元数据保存,非必须
+ * @param {String} params.ContentEncoding RFC 2616 中定义的编码格式,将作为 Object 元数据保存,非必须
+ * @param {String} params.ContentLength RFC 2616 中定义的 HTTP 请求内容长度(字节),必须
+ * @param {String} params.ContentType RFC 2616 中定义的内容类型(MIME),将作为 Object 元数据保存,非必须
+ * @param {String} params.Expect 当使用 Expect: 100-continue 时,在收到服务端确认后,才会发送请求内容,非必须
+ * @param {String} params.Expires RFC 2616 中定义的过期时间,将作为 Object 元数据保存,非必须
+ * @param {String} params.ContentSha1 RFC 3174 中定义的 160-bit 内容 SHA-1 算法校验,非必须
+ * @param {String} params.ACL 允许用户自定义文件权限,有效值:private | public-read,非必须
+ * @param {String} params.GrantRead 赋予被授权者读的权限,格式 x-cos-grant-read: uin=" ",uin=" ",非必须
+ * @param {String} params.GrantWrite 赋予被授权者写的权限,格式 x-cos-grant-write: uin=" ",uin=" ",非必须
+ * @param {String} params.GrantFullControl 赋予被授权者读写权限,格式 x-cos-grant-full-control: uin=" ",uin=" ",非必须
+ * @param {String} params.ServerSideEncryption 支持按照指定的加密算法进行服务端数据加密,格式 x-cos-server-side-encryption: "AES256",非必须
+ * @param {Function} params.onProgress 上传进度回调函数
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 为对应的 object 数据
+ * @return {String} data.ETag 为对应上传文件的 ETag 值
+ */
+function putObject(params, callback) {
+ var self = this;
+ var FileSize = params.ContentLength;
+ var onProgress = util.throttleOnProgress.call(self, FileSize, params.onProgress);
+
+ // 特殊处理 Cache-Control、Content-Type,避免代理更改这两个字段导致写入到 Object 属性里
+ var headers = params.Headers;
+ if (!headers['Cache-Control'] && !headers['cache-control']) headers['Cache-Control'] = '';
+ if (!headers['Content-Type'] && !headers['content-type']) {
+ headers['Content-Type'] = mime.getType(params.Key) || 'application/octet-stream';
+ }
+ var needCalcMd5 = params.UploadAddMetaMd5 || self.options.UploadAddMetaMd5 || self.options.UploadCheckContentMd5;
+ var tracker = params.tracker;
+ needCalcMd5 && tracker && tracker.setParams({
+ md5StartTime: new Date().getTime()
+ });
+ util.getBodyMd5(needCalcMd5, params.Body, function (md5) {
+ if (md5) {
+ tracker && tracker.setParams({
+ md5EndTime: new Date().getTime()
+ });
+ if (self.options.UploadCheckContentMd5) headers['Content-MD5'] = util.binaryBase64(md5);
+ if (params.UploadAddMetaMd5 || self.options.UploadAddMetaMd5) headers['x-cos-meta-md5'] = md5;
+ }
+ if (params.ContentLength !== undefined) headers['Content-Length'] = params.ContentLength;
+ onProgress(null, true); // 任务状态开始 uploading
+ submitRequest.call(self, {
+ Action: 'name/cos:PutObject',
+ TaskId: params.TaskId,
+ method: 'PUT',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ Key: params.Key,
+ headers: params.Headers,
+ qs: params.Query,
+ body: params.Body,
+ onProgress: onProgress,
+ tracker: tracker
+ }, function (err, data) {
+ if (err) {
+ onProgress(null, true);
+ return callback(err);
+ }
+ onProgress({
+ loaded: FileSize,
+ total: FileSize
+ }, true);
+ var url = getUrl({
+ ForcePathStyle: self.options.ForcePathStyle,
+ protocol: self.options.Protocol,
+ domain: self.options.Domain,
+ bucket: params.Bucket,
+ region: !self.options.UseAccelerate ? params.Region : 'accelerate',
+ object: params.Key
+ });
+ url = url.substr(url.indexOf('://') + 3);
+ data.Location = url;
+ data.ETag = util.attr(data.headers, 'etag', '');
+ callback(null, data);
+ });
+ });
+}
+
+/**
+ * 上传 object
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {String} params.Key 文件名称,必须
+ * @param {FilePath} params.FilePath 要上传的文件路径
+ * @param {Function} params.onProgress 上传进度回调函数
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 为对应的 object 数据
+ * @return {String} data.ETag 为对应上传文件的 ETag 值
+ */
+function postObject(params, callback) {
+ var self = this;
+ var headers = {};
+ var filePath = params.FilePath;
+ if (!filePath) {
+ callback({
+ error: 'missing param FilePath'
+ });
+ return;
+ }
+ headers['Cache-Control'] = params['CacheControl'];
+ headers['Content-Disposition'] = params['ContentDisposition'];
+ headers['Content-Encoding'] = params['ContentEncoding'];
+ headers['Content-MD5'] = params['ContentMD5'];
+ headers['Content-Length'] = params['ContentLength'];
+ headers['Content-Type'] = params['ContentType'];
+ headers['Expect'] = params['Expect'];
+ headers['Expires'] = params['Expires'];
+ headers['x-cos-acl'] = params['ACL'];
+ headers['x-cos-grant-read'] = params['GrantRead'];
+ headers['x-cos-grant-write'] = params['GrantWrite'];
+ headers['x-cos-grant-full-control'] = params['GrantFullControl'];
+ headers['x-cos-storage-class'] = params['StorageClass'];
+ headers['x-cos-mime-limit'] = params['MimeLimit'];
+ headers['x-cos-traffic-limit'] = params['TrafficLimit'];
+ headers['x-cos-forbid-overwrite'] = params['ForbidOverwrite'];
+ // SSE-C
+ headers['x-cos-server-side-encryption-customer-algorithm'] = params['SSECustomerAlgorithm'];
+ headers['x-cos-server-side-encryption-customer-key'] = params['SSECustomerKey'];
+ headers['x-cos-server-side-encryption-customer-key-MD5'] = params['SSECustomerKeyMD5'];
+ // SSE-COS、SSE-KMS
+ headers['x-cos-server-side-encryption'] = params['ServerSideEncryption'];
+ headers['x-cos-server-side-encryption-cos-kms-key-id'] = params['SSEKMSKeyId'];
+ headers['x-cos-server-side-encryption-context'] = params['SSEContext'];
+
+ // 删除 Content-Length 避免签名错误
+ delete headers['Content-Length'];
+ delete headers['content-length'];
+ for (var key in params) {
+ if (key.indexOf('x-cos-meta-') > -1) {
+ headers[key] = params[key];
+ }
+ }
+ var onProgress = util.throttleOnProgress.call(self, headers['Content-Length'], params.onProgress);
+ submitRequest.call(this, {
+ Action: 'name/cos:PostObject',
+ method: 'POST',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ Key: params.Key,
+ headers: headers,
+ qs: params.Query,
+ filePath: filePath,
+ TaskId: params.TaskId,
+ onProgress: onProgress,
+ tracker: params.tracker
+ }, function (err, data) {
+ onProgress(null, true);
+ if (err) return callback(err);
+ if (data && data.headers) {
+ var headers = data.headers;
+ var ETag = headers.etag || headers.Etag || headers.ETag || '';
+ var filename = filePath.substr(filePath.lastIndexOf('/') + 1);
+ var url = getUrl({
+ ForcePathStyle: self.options.ForcePathStyle,
+ protocol: self.options.Protocol,
+ domain: self.options.Domain,
+ bucket: params.Bucket,
+ region: params.Region,
+ object: params.Key.replace(/\$\{filename\}/g, filename),
+ isLocation: true
+ });
+ return callback(null, {
+ Location: url,
+ statusCode: data.statusCode,
+ headers: headers,
+ ETag: ETag
+ });
+ }
+ callback(null, data);
+ });
+}
+
+/**
+ * 删除 object
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {String} params.Key object名称,必须
+ * @param {Function} callback 回调函数,必须
+ * @param {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @param {Object} data 删除操作成功之后返回的数据
+ */
+function deleteObject(params, callback) {
+ submitRequest.call(this, {
+ Action: 'name/cos:DeleteObject',
+ method: 'DELETE',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ Key: params.Key,
+ headers: params.Headers,
+ VersionId: params.VersionId,
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err) {
+ var statusCode = err.statusCode;
+ if (statusCode && statusCode === 204) {
+ return callback(null, {
+ statusCode: statusCode
+ });
+ } else if (statusCode && statusCode === 404) {
+ return callback(null, {
+ BucketNotFound: true,
+ statusCode: statusCode
+ });
+ } else {
+ return callback(err);
+ }
+ }
+ callback(null, {
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/**
+ * 获取 object 的 权限列表
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {String} params.Key object名称,必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回的数据
+ * @return {Object} data.AccessControlPolicy 权限列表
+ */
+function getObjectAcl(params, callback) {
+ var reqParams = {};
+ if (params.VersionId) {
+ reqParams.versionId = params.VersionId;
+ }
+ submitRequest.call(this, {
+ Action: 'name/cos:GetObjectACL',
+ method: 'GET',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ Key: params.Key,
+ headers: params.Headers,
+ qs: reqParams,
+ action: 'acl',
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err) return callback(err);
+ var AccessControlPolicy = data.AccessControlPolicy || {};
+ var Owner = AccessControlPolicy.Owner || {};
+ var Grant = AccessControlPolicy.AccessControlList && AccessControlPolicy.AccessControlList.Grant || [];
+ Grant = util.isArray(Grant) ? Grant : [Grant];
+ var result = decodeAcl(AccessControlPolicy);
+ if (data.headers && data.headers['x-cos-acl']) {
+ result.ACL = data.headers['x-cos-acl'];
+ }
+ result = util.extend(result, {
+ Owner: Owner,
+ Grants: Grant,
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ callback(null, result);
+ });
+}
+
+/**
+ * 设置 object 的 权限列表
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {String} params.Key object名称,必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回的数据
+ */
+function putObjectAcl(params, callback) {
+ var headers = params.Headers;
+ var xml = '';
+ if (params['AccessControlPolicy']) {
+ var AccessControlPolicy = util.clone(params['AccessControlPolicy'] || {});
+ var Grants = AccessControlPolicy.Grants || AccessControlPolicy.Grant;
+ Grants = util.isArray(Grants) ? Grants : [Grants];
+ delete AccessControlPolicy.Grant;
+ delete AccessControlPolicy.Grants;
+ AccessControlPolicy.AccessControlList = {
+ Grant: Grants
+ };
+ xml = util.json2xml({
+ AccessControlPolicy: AccessControlPolicy
+ });
+ headers['Content-Type'] = 'application/xml';
+ headers['Content-MD5'] = util.binaryBase64(util.md5(xml));
+ }
+
+ // Grant Header 去重
+ util.each(headers, function (val, key) {
+ if (key.indexOf('x-cos-grant-') === 0) {
+ headers[key] = uniqGrant(headers[key]);
+ }
+ });
+ submitRequest.call(this, {
+ Action: 'name/cos:PutObjectACL',
+ method: 'PUT',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ Key: params.Key,
+ action: 'acl',
+ headers: headers,
+ body: xml,
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err) return callback(err);
+ callback(null, {
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/**
+ * Options Object请求实现跨域访问的预请求。即发出一个 OPTIONS 请求给服务器以确认是否可以进行跨域操作。
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {String} params.Key object名称,必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回的数据
+ */
+function optionsObject(params, callback) {
+ var headers = params.Headers;
+ headers['Origin'] = params['Origin'];
+ headers['Access-Control-Request-Method'] = params['AccessControlRequestMethod'];
+ headers['Access-Control-Request-Headers'] = params['AccessControlRequestHeaders'];
+ submitRequest.call(this, {
+ Action: 'name/cos:OptionsObject',
+ method: 'OPTIONS',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ Key: params.Key,
+ headers: headers,
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err) {
+ if (err.statusCode && err.statusCode === 403) {
+ return callback(null, {
+ OptionsForbidden: true,
+ statusCode: err.statusCode
+ });
+ }
+ return callback(err);
+ }
+ var headers = data.headers || {};
+ callback(null, {
+ AccessControlAllowOrigin: headers['access-control-allow-origin'],
+ AccessControlAllowMethods: headers['access-control-allow-methods'],
+ AccessControlAllowHeaders: headers['access-control-allow-headers'],
+ AccessControlExposeHeaders: headers['access-control-expose-headers'],
+ AccessControlMaxAge: headers['access-control-max-age'],
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/**
+ * @param {Object} 参数列表
+ * @param {String} Bucket Bucket 名称
+ * @param {String} Region 地域名称
+ * @param {String} Key 文件名称
+ * @param {String} CopySource 源文件URL绝对路径,可以通过versionid子资源指定历史版本
+ * @param {String} ACL 允许用户自定义文件权限。有效值:private,public-read默认值:private。
+ * @param {String} GrantRead 赋予被授权者读的权限,格式 x-cos-grant-read: uin=" ",uin=" ",当需要给子账户授权时,uin="RootAcountID/SubAccountID",当需要给根账户授权时,uin="RootAcountID"。
+ * @param {String} GrantWrite 赋予被授权者写的权限,格式 x-cos-grant-write: uin=" ",uin=" ",当需要给子账户授权时,uin="RootAcountID/SubAccountID",当需要给根账户授权时,uin="RootAcountID"。
+ * @param {String} GrantFullControl 赋予被授权者读写权限,格式 x-cos-grant-full-control: uin=" ",uin=" ",当需要给子账户授权时,uin="RootAcountID/SubAccountID",当需要给根账户授权时,uin="RootAcountID"。
+ * @param {String} MetadataDirective 是否拷贝元数据,枚举值:Copy, Replaced,默认值Copy。假如标记为Copy,忽略Header中的用户元数据信息直接复制;假如标记为Replaced,按Header信息修改元数据。当目标路径和原路径一致,即用户试图修改元数据时,必须为Replaced
+ * @param {String} CopySourceIfModifiedSince 当Object在指定时间后被修改,则执行操作,否则返回412。可与x-cos-copy-source-If-None-Match一起使用,与其他条件联合使用返回冲突。
+ * @param {String} CopySourceIfUnmodifiedSince 当Object在指定时间后未被修改,则执行操作,否则返回412。可与x-cos-copy-source-If-Match一起使用,与其他条件联合使用返回冲突。
+ * @param {String} CopySourceIfMatch 当Object的ETag和给定一致时,则执行操作,否则返回412。可与x-cos-copy-source-If-Unmodified-Since一起使用,与其他条件联合使用返回冲突。
+ * @param {String} CopySourceIfNoneMatch 当Object的ETag和给定不一致时,则执行操作,否则返回412。可与x-cos-copy-source-If-Modified-Since一起使用,与其他条件联合使用返回冲突。
+ * @param {String} StorageClass 存储级别,枚举值:存储级别,枚举值:Standard, Standard_IA,Archive;默认值:Standard
+ * @param {String} CacheControl 指定所有缓存机制在整个请求/响应链中必须服从的指令。
+ * @param {String} ContentDisposition MIME 协议的扩展,MIME 协议指示 MIME 用户代理如何显示附加的文件
+ * @param {String} ContentEncoding HTTP 中用来对「采用何种编码格式传输正文」进行协定的一对头部字段
+ * @param {String} ContentLength 设置响应消息的实体内容的大小,单位为字节
+ * @param {String} ContentType RFC 2616 中定义的 HTTP 请求内容类型(MIME),例如text/plain
+ * @param {String} Expect 请求的特定的服务器行为
+ * @param {String} Expires 响应过期的日期和时间
+ * @param {String} params.ServerSideEncryption 支持按照指定的加密算法进行服务端数据加密,格式 x-cos-server-side-encryption: "AES256",非必须
+ * @param {String} ContentLanguage 指定内容语言
+ * @param {String} x-cos-meta-* 允许用户自定义的头部信息,将作为 Object 元数据返回。大小限制2K。
+ */
+function putObjectCopy(params, callback) {
+ // 特殊处理 Cache-Control
+ var headers = params.Headers;
+ if (!headers['Cache-Control'] && !!headers['cache-control']) headers['Cache-Control'] = '';
+ var CopySource = params.CopySource || '';
+ var m = util.getSourceParams.call(this, CopySource);
+ if (!m) {
+ callback({
+ error: 'CopySource format error'
+ });
+ return;
+ }
+ var SourceBucket = m.Bucket;
+ var SourceRegion = m.Region;
+ var SourceKey = decodeURIComponent(m.Key);
+ submitRequest.call(this, {
+ Scope: [{
+ action: 'name/cos:GetObject',
+ bucket: SourceBucket,
+ region: SourceRegion,
+ prefix: SourceKey
+ }, {
+ action: 'name/cos:PutObject',
+ bucket: params.Bucket,
+ region: params.Region,
+ prefix: params.Key
+ }],
+ method: 'PUT',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ Key: params.Key,
+ VersionId: params.VersionId,
+ headers: params.Headers,
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err) return callback(err);
+ var result = util.clone(data.CopyObjectResult || {});
+ util.extend(result, {
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ callback(null, result);
+ });
+}
+function uploadPartCopy(params, callback) {
+ var CopySource = params.CopySource || '';
+ var m = util.getSourceParams.call(this, CopySource);
+ if (!m) {
+ callback({
+ error: 'CopySource format error'
+ });
+ return;
+ }
+ var SourceBucket = m.Bucket;
+ var SourceRegion = m.Region;
+ var SourceKey = decodeURIComponent(m.Key);
+ submitRequest.call(this, {
+ Scope: [{
+ action: 'name/cos:GetObject',
+ bucket: SourceBucket,
+ region: SourceRegion,
+ prefix: SourceKey
+ }, {
+ action: 'name/cos:PutObject',
+ bucket: params.Bucket,
+ region: params.Region,
+ prefix: params.Key
+ }],
+ method: 'PUT',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ Key: params.Key,
+ VersionId: params.VersionId,
+ qs: {
+ partNumber: params['PartNumber'],
+ uploadId: params['UploadId']
+ },
+ headers: params.Headers,
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err) return callback(err);
+ var result = util.clone(data.CopyPartResult || {});
+ util.extend(result, {
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ callback(null, result);
+ });
+}
+function deleteMultipleObject(params, callback) {
+ var Objects = params.Objects || [];
+ var Quiet = params.Quiet;
+ Objects = util.isArray(Objects) ? Objects : [Objects];
+ var xml = util.json2xml({
+ Delete: {
+ Object: Objects,
+ Quiet: Quiet || false
+ }
+ });
+ var headers = params.Headers;
+ headers['Content-Type'] = 'application/xml';
+ headers['Content-MD5'] = util.binaryBase64(util.md5(xml));
+ var Scope = util.map(Objects, function (v) {
+ return {
+ action: 'name/cos:DeleteObject',
+ bucket: params.Bucket,
+ region: params.Region,
+ prefix: v.Key
+ };
+ });
+ submitRequest.call(this, {
+ Scope: Scope,
+ method: 'POST',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ body: xml,
+ action: 'delete',
+ headers: headers,
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err) return callback(err);
+ var DeleteResult = data.DeleteResult || {};
+ var Deleted = DeleteResult.Deleted || [];
+ var Errors = DeleteResult.Error || [];
+ Deleted = util.isArray(Deleted) ? Deleted : [Deleted];
+ Errors = util.isArray(Errors) ? Errors : [Errors];
+ var result = util.clone(DeleteResult);
+ util.extend(result, {
+ Error: Errors,
+ Deleted: Deleted,
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ callback(null, result);
+ });
+}
+function restoreObject(params, callback) {
+ var headers = params.Headers;
+ if (!params['RestoreRequest']) {
+ callback({
+ error: 'missing param RestoreRequest'
+ });
+ return;
+ }
+ var RestoreRequest = params.RestoreRequest || {};
+ var xml = util.json2xml({
+ RestoreRequest: RestoreRequest
+ });
+ headers['Content-Type'] = 'application/xml';
+ headers['Content-MD5'] = util.binaryBase64(util.md5(xml));
+ submitRequest.call(this, {
+ Action: 'name/cos:RestoreObject',
+ method: 'POST',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ Key: params.Key,
+ VersionId: params.VersionId,
+ body: xml,
+ action: 'restore',
+ headers: headers,
+ tracker: params.tracker
+ }, function (err, data) {
+ callback(err, data);
+ });
+}
+
+/**
+ * 设置 Object 的标签
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Object名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {Array} params.TagSet 标签设置,必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/42998
+ * @return {Object} data 返回数据
+ */
+function putObjectTagging(params, callback) {
+ var Tagging = params['Tagging'] || {};
+ var Tags = Tagging.TagSet || Tagging.Tags || params['Tags'] || [];
+ Tags = util.clone(util.isArray(Tags) ? Tags : [Tags]);
+ var xml = util.json2xml({
+ Tagging: {
+ TagSet: {
+ Tag: Tags
+ }
+ }
+ });
+ var headers = params.Headers;
+ headers['Content-Type'] = 'application/xml';
+ headers['Content-MD5'] = util.binaryBase64(util.md5(xml));
+ submitRequest.call(this, {
+ Interface: 'putObjectTagging',
+ Action: 'name/cos:PutObjectTagging',
+ method: 'PUT',
+ Bucket: params.Bucket,
+ Key: params.Key,
+ Region: params.Region,
+ body: xml,
+ action: 'tagging',
+ headers: headers,
+ VersionId: params.VersionId,
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err && err.statusCode === 204) {
+ return callback(null, {
+ statusCode: err.statusCode
+ });
+ } else if (err) {
+ return callback(err);
+ }
+ callback(null, {
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/**
+ * 获取 Object 的标签设置
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/42998
+ * @return {Object} data 返回数据
+ */
+function getObjectTagging(params, callback) {
+ submitRequest.call(this, {
+ Interface: 'getObjectTagging',
+ Action: 'name/cos:GetObjectTagging',
+ method: 'GET',
+ Key: params.Key,
+ Bucket: params.Bucket,
+ Region: params.Region,
+ headers: params.Headers,
+ action: 'tagging',
+ VersionId: params.VersionId,
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err) {
+ if (err.statusCode === 404 && err.error && (err.error === 'Not Found' || err.error.Code === 'NoSuchTagSet')) {
+ var result = {
+ Tags: [],
+ statusCode: err.statusCode
+ };
+ err.headers && (result.headers = err.headers);
+ callback(null, result);
+ } else {
+ callback(err);
+ }
+ return;
+ }
+ var Tags = [];
+ try {
+ Tags = data.Tagging.TagSet.Tag || [];
+ } catch (e) {}
+ Tags = util.clone(util.isArray(Tags) ? Tags : [Tags]);
+ callback(null, {
+ Tags: Tags,
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/**
+ * 删除 Object 的 标签设置
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Object名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/42998
+ * @return {Object} data 返回的数据
+ */
+function deleteObjectTagging(params, callback) {
+ submitRequest.call(this, {
+ Interface: 'deleteObjectTagging',
+ Action: 'name/cos:DeleteObjectTagging',
+ method: 'DELETE',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ Key: params.Key,
+ headers: params.Headers,
+ action: 'tagging',
+ VersionId: params.VersionId,
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err && err.statusCode === 204) {
+ return callback(null, {
+ statusCode: err.statusCode
+ });
+ } else if (err) {
+ return callback(err);
+ }
+ callback(null, {
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+// 分块上传
+
+/**
+ * 初始化分块上传
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {String} params.Key object名称,必须
+ * @param {String} params.UploadId object名称,必须
+ * @param {String} params.CacheControl RFC 2616 中定义的缓存策略,将作为 Object 元数据保存,非必须
+ * @param {String} params.ContentDisposition RFC 2616 中定义的文件名称,将作为 Object 元数据保存 ,非必须
+ * @param {String} params.ContentEncoding RFC 2616 中定义的编码格式,将作为 Object 元数据保存,非必须
+ * @param {String} params.ContentType RFC 2616 中定义的内容类型(MIME),将作为 Object 元数据保存,非必须
+ * @param {String} params.Expires RFC 2616 中定义的过期时间,将作为 Object 元数据保存,非必须
+ * @param {String} params.ACL 允许用户自定义文件权限,非必须
+ * @param {String} params.GrantRead 赋予被授权者读的权限 ,非必须
+ * @param {String} params.GrantWrite 赋予被授权者写的权限 ,非必须
+ * @param {String} params.GrantFullControl 赋予被授权者读写权限 ,非必须
+ * @param {String} params.StorageClass 设置Object的存储级别,枚举值:Standard,Standard_IA,Archive,非必须
+ * @param {String} params.ServerSideEncryption 支持按照指定的加密算法进行服务端数据加密,格式 x-cos-server-side-encryption: "AES256",非必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回的数据
+ */
+function multipartInit(params, callback) {
+ var self = this;
+ var headers = params.Headers;
+ var tracker = params.tracker;
+
+ // 特殊处理 Cache-Control、Content-Type
+ if (!headers['Cache-Control'] && !headers['cache-control']) headers['Cache-Control'] = '';
+ if (!headers['Content-Type'] && !headers['content-type']) {
+ headers['Content-Type'] = mime.getType(params.Key) || 'application/octet-stream';
+ }
+ submitRequest.call(self, {
+ Action: 'name/cos:InitiateMultipartUpload',
+ method: 'POST',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ Key: params.Key,
+ action: 'uploads',
+ headers: params.Headers,
+ qs: params.Query,
+ tracker: tracker
+ }, function (err, data) {
+ if (err) {
+ tracker && tracker.parent && tracker.parent.setParams({
+ errorNode: 'multipartInit'
+ });
+ return callback(err);
+ }
+ data = util.clone(data || {});
+ if (data && data.InitiateMultipartUploadResult) {
+ return callback(null, util.extend(data.InitiateMultipartUploadResult, {
+ statusCode: data.statusCode,
+ headers: data.headers
+ }));
+ }
+ callback(null, data);
+ });
+}
+
+/**
+ * 分块上传
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {String} params.Key object名称,必须
+ * @param {String} params.Body 上传文件对象或字符串
+ * @param {String} params.ContentLength RFC 2616 中定义的 HTTP 请求内容长度(字节),非必须
+ * @param {String} params.Expect 当使用 Expect: 100-continue 时,在收到服务端确认后,才会发送请求内容,非必须
+ * @param {String} params.ServerSideEncryption 支持按照指定的加密算法进行服务端数据加密,格式 x-cos-server-side-encryption: "AES256",非必须
+ * @param {String} params.ContentSha1 RFC 3174 中定义的 160-bit 内容 SHA-1 算法校验值,非必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回的数据
+ * @return {Object} data.ETag 返回的文件分块 sha1 值
+ */
+function multipartUpload(params, callback) {
+ var self = this;
+ util.getFileSize('multipartUpload', params, function () {
+ var tracker = params.tracker;
+ var needCalcMd5 = self.options.UploadCheckContentMd5;
+ needCalcMd5 && tracker && tracker.setParams({
+ md5StartTime: new Date().getTime()
+ });
+ util.getBodyMd5(needCalcMd5, params.Body, function (md5) {
+ if (md5) {
+ params.Headers['Content-MD5'] = util.binaryBase64(md5);
+ needCalcMd5 && tracker && tracker.setParams({
+ md5EndTime: new Date().getTime()
+ });
+ }
+ tracker && tracker.setParams({
+ partNumber: params.PartNumber
+ });
+ submitRequest.call(self, {
+ Action: 'name/cos:UploadPart',
+ TaskId: params.TaskId,
+ method: 'PUT',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ Key: params.Key,
+ qs: {
+ partNumber: params['PartNumber'],
+ uploadId: params['UploadId']
+ },
+ headers: params.Headers,
+ onProgress: params.onProgress,
+ body: params.Body || null,
+ tracker: tracker
+ }, function (err, data) {
+ if (err) {
+ tracker && tracker.parent && tracker.parent.setParams({
+ errorNode: 'multipartUpload'
+ });
+ return callback(err);
+ }
+ callback(null, {
+ ETag: util.attr(data.headers, 'etag', {}),
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+ });
+ });
+}
+
+/**
+ * 完成分块上传
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {String} params.Key object名称,必须
+ * @param {Array} params.Parts 分块信息列表,必须
+ * @param {String} params.Parts[i].PartNumber 块编号,必须
+ * @param {String} params.Parts[i].ETag 分块的 sha1 校验值
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回的数据
+ * @return {Object} data.CompleteMultipartUpload 完成分块上传后的文件信息,包括Location, Bucket, Key 和 ETag
+ */
+function multipartComplete(params, callback) {
+ var self = this;
+ var UploadId = params.UploadId;
+ var Parts = params['Parts'];
+ var tracker = params.tracker;
+ for (var i = 0, len = Parts.length; i < len; i++) {
+ if (Parts[i]['ETag'].indexOf('"') === 0) {
+ continue;
+ }
+ Parts[i]['ETag'] = '"' + Parts[i]['ETag'] + '"';
+ }
+ var xml = util.json2xml({
+ CompleteMultipartUpload: {
+ Part: Parts
+ }
+ });
+ var headers = params.Headers;
+ headers['Content-Type'] = 'application/xml';
+ headers['Content-MD5'] = util.binaryBase64(util.md5(xml));
+ submitRequest.call(this, {
+ Action: 'name/cos:CompleteMultipartUpload',
+ method: 'POST',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ Key: params.Key,
+ qs: {
+ uploadId: UploadId
+ },
+ body: xml,
+ headers: headers,
+ tracker: tracker
+ }, function (err, data) {
+ if (err) {
+ tracker && tracker.parent && tracker.parent.setParams({
+ errorNode: 'multipartComplete'
+ });
+ return callback(err);
+ }
+ var url = getUrl({
+ ForcePathStyle: self.options.ForcePathStyle,
+ protocol: self.options.Protocol,
+ domain: self.options.Domain,
+ bucket: params.Bucket,
+ region: !self.options.UseAccelerate ? params.Region : 'accelerate',
+ object: params.Key,
+ isLocation: true
+ });
+ var CompleteMultipartUploadResult = data.CompleteMultipartUploadResult || {};
+ var result = util.extend(CompleteMultipartUploadResult, {
+ Location: url,
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ callback(null, result);
+ });
+}
+
+/**
+ * 分块上传任务列表查询
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {String} params.Delimiter 定界符为一个符号,如果有Prefix,则将Prefix到delimiter之间的相同路径归为一类,定义为Common Prefix,然后列出所有Common Prefix。如果没有Prefix,则从路径起点开始,非必须
+ * @param {String} params.EncodingType 规定返回值的编码方式,非必须
+ * @param {String} params.Prefix 前缀匹配,用来规定返回的文件前缀地址,非必须
+ * @param {String} params.MaxUploads 单次返回最大的条目数量,默认1000,非必须
+ * @param {String} params.KeyMarker 与upload-id-marker一起使用 当upload-id-marker未被指定时,ObjectName字母顺序大于key-marker的条目将被列出 当upload-id-marker被指定时,ObjectName字母顺序大于key-marker的条目被列出,ObjectName字母顺序等于key-marker同时UploadId大于upload-id-marker的条目将被列出,非必须
+ * @param {String} params.UploadIdMarker 与key-marker一起使用 当key-marker未被指定时,upload-id-marker将被忽略 当key-marker被指定时,ObjectName字母顺序大于key-marker的条目被列出,ObjectName字母顺序等于key-marker同时UploadId大于upload-id-marker的条目将被列出,非必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回的数据
+ * @return {Object} data.ListMultipartUploadsResult 分块上传任务信息
+ */
+function multipartList(params, callback) {
+ var reqParams = {};
+ reqParams['delimiter'] = params['Delimiter'];
+ reqParams['encoding-type'] = params['EncodingType'];
+ reqParams['prefix'] = params['Prefix'] || '';
+ reqParams['max-uploads'] = params['MaxUploads'];
+ reqParams['key-marker'] = params['KeyMarker'];
+ reqParams['upload-id-marker'] = params['UploadIdMarker'];
+ reqParams = util.clearKey(reqParams);
+ var tracker = params.tracker;
+ tracker && tracker.setParams({
+ signStartTime: new Date().getTime()
+ });
+ submitRequest.call(this, {
+ Action: 'name/cos:ListMultipartUploads',
+ ResourceKey: reqParams['prefix'],
+ method: 'GET',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ headers: params.Headers,
+ qs: reqParams,
+ action: 'uploads',
+ tracker: tracker
+ }, function (err, data) {
+ if (err) {
+ tracker && tracker.parent && tracker.parent.setParams({
+ errorNode: 'multipartList'
+ });
+ return callback(err);
+ }
+ if (data && data.ListMultipartUploadsResult) {
+ var Upload = data.ListMultipartUploadsResult.Upload || [];
+ var CommonPrefixes = data.ListMultipartUploadsResult.CommonPrefixes || [];
+ CommonPrefixes = util.isArray(CommonPrefixes) ? CommonPrefixes : [CommonPrefixes];
+ Upload = util.isArray(Upload) ? Upload : [Upload];
+ data.ListMultipartUploadsResult.Upload = Upload;
+ data.ListMultipartUploadsResult.CommonPrefixes = CommonPrefixes;
+ }
+ var result = util.clone(data.ListMultipartUploadsResult || {});
+ util.extend(result, {
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ callback(null, result);
+ });
+}
+
+/**
+ * 上传的分块列表查询
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {String} params.Key object名称,必须
+ * @param {String} params.UploadId 标示本次分块上传的ID,必须
+ * @param {String} params.EncodingType 规定返回值的编码方式,非必须
+ * @param {String} params.MaxParts 单次返回最大的条目数量,默认1000,非必须
+ * @param {String} params.PartNumberMarker 默认以UTF-8二进制顺序列出条目,所有列出条目从marker开始,非必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回的数据
+ * @return {Object} data.ListMultipartUploadsResult 分块信息
+ */
+function multipartListPart(params, callback) {
+ var reqParams = {};
+ var tracker = params.tracker;
+ reqParams['uploadId'] = params['UploadId'];
+ reqParams['encoding-type'] = params['EncodingType'];
+ reqParams['max-parts'] = params['MaxParts'];
+ reqParams['part-number-marker'] = params['PartNumberMarker'];
+ submitRequest.call(this, {
+ Action: 'name/cos:ListParts',
+ method: 'GET',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ Key: params.Key,
+ headers: params.Headers,
+ qs: reqParams,
+ tracker: tracker
+ }, function (err, data) {
+ if (err) {
+ tracker && tracker.parent && tracker.parent.setParams({
+ errorNode: 'multipartListPart'
+ });
+ return callback(err);
+ }
+ var ListPartsResult = data.ListPartsResult || {};
+ var Part = ListPartsResult.Part || [];
+ Part = util.isArray(Part) ? Part : [Part];
+ ListPartsResult.Part = Part;
+ var result = util.clone(ListPartsResult);
+ util.extend(result, {
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ callback(null, result);
+ });
+}
+
+/**
+ * 抛弃分块上传
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {String} params.Key object名称,必须
+ * @param {String} params.UploadId 标示本次分块上传的ID,必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回的数据
+ */
+function multipartAbort(params, callback) {
+ var reqParams = {};
+ reqParams['uploadId'] = params['UploadId'];
+ submitRequest.call(this, {
+ Action: 'name/cos:AbortMultipartUpload',
+ method: 'DELETE',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ Key: params.Key,
+ headers: params.Headers,
+ qs: reqParams,
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err) return callback(err);
+ callback(null, {
+ statusCode: data.statusCode,
+ headers: data.headers
+ });
+ });
+}
+
+/**
+ * 追加上传
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {String} params.Key object名称,必须
+ * @param {String} params.Body 上传文件的内容,只支持字符串
+ * @param {Number} params.Position 追加操作的起始点,单位为字节,必须
+ * @param {String} params.CacheControl RFC 2616 中定义的缓存策略,将作为 Object 元数据保存,非必须
+ * @param {String} params.ContentDisposition RFC 2616 中定义的文件名称,将作为 Object 元数据保存,非必须
+ * @param {String} params.ContentEncoding RFC 2616 中定义的编码格式,将作为 Object 元数据保存,非必须
+ * @param {String} params.ContentLength RFC 2616 中定义的 HTTP 请求内容长度(字节),必须
+ * @param {String} params.ContentType RFC 2616 中定义的内容类型(MIME),将作为 Object 元数据保存,非必须
+ * @param {String} params.Expect 当使用 Expect: 100-continue 时,在收到服务端确认后,才会发送请求内容,非必须
+ * @param {String} params.Expires RFC 2616 中定义的过期时间,将作为 Object 元数据保存,非必须
+ * @param {String} params.ACL 允许用户自定义文件权限,有效值:private | public-read,非必须
+ * @param {String} params.GrantRead 赋予被授权者读取对象的权限,格式:id="[OwnerUin]",可使用半角逗号(,)分隔多组被授权者,非必须
+ * @param {String} params.GrantReadAcp 赋予被授权者读取对象的访问控制列表(ACL)的权限,格式:id="[OwnerUin]",可使用半角逗号(,)分隔多组被授权者,非必须
+ * @param {String} params.GrantWriteAcp 赋予被授权者写入对象的访问控制列表(ACL)的权限,格式:id="[OwnerUin]",可使用半角逗号(,)分隔多组被授权者,非必须
+ * @param {String} params.GrantFullControl 赋予被授权者操作对象的所有权限,格式:id="[OwnerUin]",可使用半角逗号(,)分隔多组被授权者,非必须
+ * @param {String} params.StorageClass 设置对象的存储级别,枚举值:STANDARD、STANDARD_IA、ARCHIVE,默认值:STANDARD,非必须
+ * @param {String} params.x-cos-meta-* 允许用户自定义的头部信息,将作为对象的元数据保存。大小限制2KB,非必须
+ * @param {String} params.ContentSha1 RFC 3174 中定义的 160-bit 内容 SHA-1 算法校验,非必须
+ * @param {String} params.ServerSideEncryption 支持按照指定的加密算法进行服务端数据加密,格式 x-cos-server-side-encryption: "AES256",非必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回的数据
+ */
+function appendObject(params, callback) {
+ submitRequest.call(this, {
+ Action: 'name/cos:AppendObject',
+ method: 'POST',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ action: 'append',
+ Key: params.Key,
+ body: params.Body,
+ qs: {
+ position: params.Position
+ },
+ headers: params.Headers,
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err) return callback(err);
+ callback(null, data);
+ });
+}
+
+/**
+ * cos 内置请求
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {String} params.Key object名称,必须
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回的数据
+ */
+function request(params, callback) {
+ submitRequest.call(this, {
+ method: params.Method,
+ Bucket: params.Bucket,
+ Region: params.Region,
+ Key: params.Key,
+ action: params.Action,
+ headers: params.Headers,
+ qs: params.Query,
+ body: params.Body,
+ Url: params.Url,
+ rawBody: params.RawBody,
+ dataType: params.DataType,
+ tracker: params.tracker
+ }, function (err, data) {
+ if (err) return callback(err);
+ if (data && data.body) {
+ data.Body = data.body;
+ delete data.body;
+ }
+ callback(err, data);
+ });
+}
+
+/**
+ * 获取签名
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Method 请求方法,必须
+ * @param {String} params.Key object名称,必须
+ * @param {String} params.Expires 名超时时间,单位秒,可选
+ * @return {String} data 返回签名字符串
+ */
+function getAuth(params) {
+ var self = this;
+ return util.getAuth({
+ SecretId: params.SecretId || this.options.SecretId || '',
+ SecretKey: params.SecretKey || this.options.SecretKey || '',
+ Bucket: params.Bucket,
+ Region: params.Region,
+ Method: params.Method,
+ Key: params.Key,
+ Query: params.Query,
+ Headers: params.Headers,
+ Expires: params.Expires,
+ SystemClockOffset: self.options.SystemClockOffset
+ });
+}
+
+/**
+ * 获取文件下载链接
+ * @param {Object} params 参数对象,必须
+ * @param {String} params.Bucket Bucket名称,必须
+ * @param {String} params.Region 地域名称,必须
+ * @param {String} params.Key object名称,必须
+ * @param {String} params.Method 请求的方法,可选
+ * @param {String} params.Expires 签名超时时间,单位秒,可选
+ * @param {Function} callback 回调函数,必须
+ * @return {Object} err 请求失败的错误,如果请求成功,则为空。https://cloud.tencent.com/document/product/436/7730
+ * @return {Object} data 返回的数据
+ */
+function getObjectUrl(params, callback) {
+ var self = this;
+ var useAccelerate = params.UseAccelerate === undefined ? self.options.UseAccelerate : params.UseAccelerate;
+ var url = getUrl({
+ ForcePathStyle: self.options.ForcePathStyle,
+ protocol: params.Protocol || self.options.Protocol,
+ domain: params.Domain || self.options.Domain,
+ bucket: params.Bucket,
+ region: useAccelerate ? 'accelerate' : params.Region,
+ object: params.Key
+ });
+ var queryParamsStr = '';
+ if (params.Query) {
+ queryParamsStr += util.obj2str(params.Query);
+ }
+ if (params.QueryString) {
+ queryParamsStr += (queryParamsStr ? '&' : '') + params.QueryString;
+ }
+ var syncUrl = url;
+ if (params.Sign !== undefined && !params.Sign) {
+ queryParamsStr && (syncUrl += '?' + queryParamsStr);
+ callback(null, {
+ Url: syncUrl
+ });
+ return syncUrl;
+ }
+
+ // 签名加上 Host,避免跨桶访问
+ var SignHost = getSignHost.call(this, {
+ Bucket: params.Bucket,
+ Region: params.Region,
+ UseAccelerate: params.UseAccelerate,
+ Url: url
+ });
+ var AuthData = getAuthorizationAsync.call(this, {
+ Action: (params.Method || '').toUpperCase() === 'PUT' ? 'name/cos:PutObject' : 'name/cos:GetObject',
+ Bucket: params.Bucket || '',
+ Region: params.Region || '',
+ Method: params.Method || 'get',
+ Key: params.Key,
+ Expires: params.Expires,
+ Headers: params.Headers,
+ Query: params.Query,
+ SignHost: SignHost,
+ ForceSignHost: params.ForceSignHost === false ? false : self.options.ForceSignHost // getObjectUrl支持传参ForceSignHost
+ }, function (err, AuthData) {
+ if (!callback) return;
+ if (err) {
+ callback(err);
+ return;
+ }
+
+ // 兼容万象url qUrlParamList需要再encode一次
+ var replaceUrlParamList = function replaceUrlParamList(url) {
+ var urlParams = url.match(/q-url-param-list.*?(?=&)/g)[0];
+ var encodedParams = 'q-url-param-list=' + encodeURIComponent(urlParams.replace(/q-url-param-list=/, '')).toLowerCase();
+ var reg = new RegExp(urlParams, 'g');
+ var replacedUrl = url.replace(reg, encodedParams);
+ return replacedUrl;
+ };
+ var signUrl = url;
+ signUrl += '?' + (AuthData.Authorization.indexOf('q-signature') > -1 ? replaceUrlParamList(AuthData.Authorization) : 'sign=' + encodeURIComponent(AuthData.Authorization));
+ AuthData.SecurityToken && (signUrl += '&x-cos-security-token=' + AuthData.SecurityToken);
+ AuthData.ClientIP && (signUrl += '&clientIP=' + AuthData.ClientIP);
+ AuthData.ClientUA && (signUrl += '&clientUA=' + AuthData.ClientUA);
+ AuthData.Token && (signUrl += '&token=' + AuthData.Token);
+ queryParamsStr && (signUrl += '&' + queryParamsStr);
+ setTimeout(function () {
+ callback(null, {
+ Url: signUrl
+ });
+ });
+ });
+ if (AuthData) {
+ syncUrl += '?' + AuthData.Authorization + (AuthData.SecurityToken ? '&x-cos-security-token=' + AuthData.SecurityToken : '');
+ queryParamsStr && (syncUrl += '&' + queryParamsStr);
+ } else {
+ queryParamsStr && (syncUrl += '?' + queryParamsStr);
+ }
+ return syncUrl;
+}
+
+/**
+ * 私有方法
+ */
+function decodeAcl(AccessControlPolicy) {
+ var result = {
+ GrantFullControl: [],
+ GrantWrite: [],
+ GrantRead: [],
+ GrantReadAcp: [],
+ GrantWriteAcp: [],
+ ACL: ''
+ };
+ var GrantMap = {
+ FULL_CONTROL: 'GrantFullControl',
+ WRITE: 'GrantWrite',
+ READ: 'GrantRead',
+ READ_ACP: 'GrantReadAcp',
+ WRITE_ACP: 'GrantWriteAcp'
+ };
+ var AccessControlList = AccessControlPolicy && AccessControlPolicy.AccessControlList || {};
+ var Grant = AccessControlList.Grant;
+ if (Grant) {
+ Grant = util.isArray(Grant) ? Grant : [Grant];
+ }
+ var PublicAcl = {
+ READ: 0,
+ WRITE: 0,
+ FULL_CONTROL: 0
+ };
+ Grant && Grant.length && util.each(Grant, function (item) {
+ if (item.Grantee.ID === 'qcs::cam::anyone:anyone' || item.Grantee.URI === 'http://cam.qcloud.com/groups/global/AllUsers') {
+ PublicAcl[item.Permission] = 1;
+ } else if (item.Grantee.ID !== AccessControlPolicy.Owner.ID) {
+ result[GrantMap[item.Permission]].push('id="' + item.Grantee.ID + '"');
+ }
+ });
+ if (PublicAcl.FULL_CONTROL || PublicAcl.WRITE && PublicAcl.READ) {
+ result.ACL = 'public-read-write';
+ } else if (PublicAcl.READ) {
+ result.ACL = 'public-read';
+ } else {
+ result.ACL = 'private';
+ }
+ util.each(GrantMap, function (item) {
+ result[item] = uniqGrant(result[item].join(','));
+ });
+ return result;
+}
+
+// Grant 去重
+function uniqGrant(str) {
+ var arr = str.split(',');
+ var exist = {};
+ var i, item;
+ for (i = 0; i < arr.length;) {
+ item = arr[i].trim();
+ if (exist[item]) {
+ arr.splice(i, 1);
+ } else {
+ exist[item] = true;
+ arr[i] = item;
+ i++;
+ }
+ }
+ return arr.join(',');
+}
+
+// 生成操作 url
+function getUrl(params) {
+ var longBucket = params.bucket;
+ var shortBucket = longBucket.substr(0, longBucket.lastIndexOf('-'));
+ var appId = longBucket.substr(longBucket.lastIndexOf('-') + 1);
+ var domain = params.domain;
+ var region = params.region;
+ var object = params.object;
+ var protocol = 'https:';
+ if (!domain) {
+ if (['cn-south', 'cn-south-2', 'cn-north', 'cn-east', 'cn-southwest', 'sg'].indexOf(region) > -1) {
+ domain = '{Region}.myqcloud.com';
+ } else {
+ domain = 'cos.{Region}.myqcloud.com';
+ }
+ if (!params.ForcePathStyle) {
+ domain = '{Bucket}.' + domain;
+ }
+ }
+ domain = domain.replace(/\{\{AppId\}\}/gi, appId).replace(/\{\{Bucket\}\}/gi, shortBucket).replace(/\{\{Region\}\}/gi, region).replace(/\{\{.*?\}\}/gi, '');
+ domain = domain.replace(/\{AppId\}/gi, appId).replace(/\{BucketName\}/gi, shortBucket).replace(/\{Bucket\}/gi, longBucket).replace(/\{Region\}/gi, region).replace(/\{.*?\}/gi, '');
+ if (!/^[a-zA-Z]+:\/\//.test(domain)) {
+ domain = protocol + '//' + domain;
+ }
+
+ // 去掉域名最后的斜杆
+ if (domain.slice(-1) === '/') {
+ domain = domain.slice(0, -1);
+ }
+ var url = domain;
+ if (params.ForcePathStyle) {
+ url += '/' + longBucket;
+ }
+ url += '/';
+ if (object) {
+ url += util.camSafeUrlEncode(object).replace(/%2F/g, '/');
+ }
+ if (params.isLocation) {
+ url = url.replace(/^https?:\/\//, '');
+ }
+ return url;
+}
+var getSignHost = function getSignHost(opt) {
+ if (!opt.Bucket || !opt.Region) return '';
+ var useAccelerate = opt.UseAccelerate === undefined ? this.options.UseAccelerate : opt.UseAccelerate;
+ var url = opt.Url || getUrl({
+ ForcePathStyle: this.options.ForcePathStyle,
+ protocol: this.options.Protocol,
+ domain: this.options.Domain,
+ bucket: opt.Bucket,
+ region: useAccelerate ? 'accelerate' : opt.Region
+ });
+ var urlHost = url.replace(/^https?:\/\/([^/]+)(\/.*)?$/, '$1');
+ return urlHost;
+};
+
+// 异步获取签名
+function getAuthorizationAsync(params, callback) {
+ var headers = util.clone(params.Headers);
+ var headerHost = '';
+ util.each(headers, function (v, k) {
+ if (v === '') {
+ delete headers[k];
+ }
+ if (k.toLowerCase() === 'host') headerHost = v;
+ });
+
+ // ForceSignHost明确传入false才不加入host签名
+ var forceSignHost = params.ForceSignHost === false ? false : true;
+ // Host 加入签名计算
+ if (!headerHost && params.SignHost && forceSignHost) headers.Host = params.SignHost;
+
+ // 获取凭证的回调,避免用户 callback 多次
+ var cbDone = false;
+ var cb = function cb(err, AuthData) {
+ if (cbDone) return;
+ cbDone = true;
+ if (AuthData && AuthData.XCosSecurityToken && !AuthData.SecurityToken) {
+ AuthData = util.clone(AuthData);
+ AuthData.SecurityToken = AuthData.XCosSecurityToken;
+ delete AuthData.XCosSecurityToken;
+ }
+ callback && callback(err, AuthData);
+ };
+ var self = this;
+ var Bucket = params.Bucket || '';
+ var Region = params.Region || '';
+
+ // PathName
+ var KeyName = params.Action === 'name/cos:PostObject' || !params.Key ? '' : params.Key;
+ if (self.options.ForcePathStyle && Bucket) {
+ KeyName = Bucket + '/' + KeyName;
+ }
+ var Pathname = '/' + KeyName;
+
+ // Action、ResourceKey
+ var StsData = {};
+ var Scope = params.Scope;
+ if (!Scope) {
+ var Action = params.Action || '';
+ var ResourceKey = params.ResourceKey || params.Key || '';
+ Scope = params.Scope || [{
+ action: Action,
+ bucket: Bucket,
+ region: Region,
+ prefix: ResourceKey
+ }];
+ }
+ var ScopeKey = util.md5(JSON.stringify(Scope));
+
+ // STS
+ self._StsCache = self._StsCache || [];
+ (function () {
+ var i, AuthData;
+ for (i = self._StsCache.length - 1; i >= 0; i--) {
+ AuthData = self._StsCache[i];
+ var compareTime = Math.round(util.getSkewTime(self.options.SystemClockOffset) / 1000) + 30;
+ if (AuthData.StartTime && compareTime < AuthData.StartTime || compareTime >= AuthData.ExpiredTime) {
+ self._StsCache.splice(i, 1);
+ continue;
+ }
+ if (!AuthData.ScopeLimit || AuthData.ScopeLimit && AuthData.ScopeKey === ScopeKey) {
+ StsData = AuthData;
+ break;
+ }
+ }
+ })();
+ var calcAuthByTmpKey = function calcAuthByTmpKey() {
+ var KeyTime = '';
+ if (StsData.StartTime && params.Expires) {
+ KeyTime = StsData.StartTime + ';' + (StsData.StartTime + params.Expires * 1);
+ } else if (StsData.StartTime && StsData.ExpiredTime) {
+ KeyTime = StsData.StartTime + ';' + StsData.ExpiredTime;
+ }
+ var Authorization = util.getAuth({
+ SecretId: StsData.TmpSecretId,
+ SecretKey: StsData.TmpSecretKey,
+ Method: params.Method,
+ Pathname: Pathname,
+ Query: params.Query,
+ Headers: headers,
+ Expires: params.Expires,
+ SystemClockOffset: self.options.SystemClockOffset,
+ KeyTime: KeyTime,
+ ForceSignHost: forceSignHost
+ });
+ var AuthData = {
+ Authorization: Authorization,
+ SecurityToken: StsData.SecurityToken || StsData.XCosSecurityToken || '',
+ Token: StsData.Token || '',
+ ClientIP: StsData.ClientIP || '',
+ ClientUA: StsData.ClientUA || '',
+ SignFrom: 'client'
+ };
+ cb(null, AuthData);
+ };
+ var checkAuthError = function checkAuthError(AuthData) {
+ if (AuthData.Authorization) {
+ // 检查签名格式
+ var formatAllow = false;
+ var auth = AuthData.Authorization;
+ if (auth) {
+ if (auth.indexOf(' ') > -1) {
+ formatAllow = false;
+ } else if (auth.indexOf('q-sign-algorithm=') > -1 && auth.indexOf('q-ak=') > -1 && auth.indexOf('q-sign-time=') > -1 && auth.indexOf('q-key-time=') > -1 && auth.indexOf('q-url-param-list=') > -1) {
+ formatAllow = true;
+ } else {
+ try {
+ auth = atob(auth);
+ if (auth.indexOf('a=') > -1 && auth.indexOf('k=') > -1 && auth.indexOf('t=') > -1 && auth.indexOf('r=') > -1 && auth.indexOf('b=') > -1) {
+ formatAllow = true;
+ }
+ } catch (e) {}
+ }
+ }
+ if (!formatAllow) return util.error(new Error('getAuthorization callback params format error'));
+ } else {
+ if (!AuthData.TmpSecretId) return util.error(new Error('getAuthorization callback params missing "TmpSecretId"'));
+ if (!AuthData.TmpSecretKey) return util.error(new Error('getAuthorization callback params missing "TmpSecretKey"'));
+ if (!AuthData.SecurityToken && !AuthData.XCosSecurityToken) return util.error(new Error('getAuthorization callback params missing "SecurityToken"'));
+ if (!AuthData.ExpiredTime) return util.error(new Error('getAuthorization callback params missing "ExpiredTime"'));
+ if (AuthData.ExpiredTime && AuthData.ExpiredTime.toString().length !== 10) return util.error(new Error('getAuthorization callback params "ExpiredTime" should be 10 digits'));
+ if (AuthData.StartTime && AuthData.StartTime.toString().length !== 10) return util.error(new Error('getAuthorization callback params "StartTime" should be 10 StartTime'));
+ }
+ return false;
+ };
+
+ // 先判断是否有临时密钥
+ if (StsData.ExpiredTime && StsData.ExpiredTime - util.getSkewTime(self.options.SystemClockOffset) / 1000 > 60) {
+ // 如果缓存的临时密钥有效,并还有超过60秒有效期就直接使用
+ calcAuthByTmpKey();
+ } else if (self.options.getAuthorization) {
+ // 外部计算签名或获取临时密钥
+ self.options.getAuthorization.call(self, {
+ Bucket: Bucket,
+ Region: Region,
+ Method: params.Method,
+ Key: KeyName,
+ Pathname: Pathname,
+ Query: params.Query,
+ Headers: headers,
+ Scope: Scope,
+ SystemClockOffset: self.options.SystemClockOffset,
+ ForceSignHost: forceSignHost
+ }, function (AuthData) {
+ if (typeof AuthData === 'string') {
+ AuthData = {
+ Authorization: AuthData
+ };
+ }
+ var AuthError = checkAuthError(AuthData);
+ if (AuthError) return cb(AuthError);
+ if (AuthData.Authorization) {
+ cb(null, AuthData);
+ } else {
+ StsData = AuthData || {};
+ StsData.Scope = Scope;
+ StsData.ScopeKey = ScopeKey;
+ self._StsCache.push(StsData);
+ calcAuthByTmpKey();
+ }
+ });
+ } else if (self.options.getSTS) {
+ // 外部获取临时密钥
+ self.options.getSTS.call(self, {
+ Bucket: Bucket,
+ Region: Region
+ }, function (data) {
+ StsData = data || {};
+ StsData.Scope = Scope;
+ StsData.ScopeKey = ScopeKey;
+ if (!StsData.TmpSecretId) StsData.TmpSecretId = StsData.SecretId;
+ if (!StsData.TmpSecretKey) StsData.TmpSecretKey = StsData.SecretKey;
+ var AuthError = checkAuthError(StsData);
+ if (AuthError) return cb(AuthError);
+ self._StsCache.push(StsData);
+ calcAuthByTmpKey();
+ });
+ } else {
+ // 内部计算获取签名
+ return function () {
+ var KeyTime = '';
+ if (self.options.StartTime && params.Expires) {
+ if (self.options.StartTime.toString().length !== 10) {
+ return cb(util.error(new Error('params "StartTime" should be 10 digits')));
+ }
+ KeyTime = self.options.StartTime + ';' + (self.options.StartTime + params.Expires * 1);
+ } else if (self.options.StartTime && self.options.ExpiredTime) {
+ if (self.options.StartTime.toString().length !== 10) {
+ return cb(util.error(new Error('params "StartTime" should be 10 digits')));
+ }
+ if (self.options.ExpiredTime.toString().length !== 10) {
+ return cb(util.error(new Error('params "ExpiredTime" should be 10 digits')));
+ }
+ KeyTime = self.options.StartTime + ';' + self.options.ExpiredTime * 1;
+ }
+ var Authorization = util.getAuth({
+ SecretId: params.SecretId || self.options.SecretId,
+ SecretKey: params.SecretKey || self.options.SecretKey,
+ Method: params.Method,
+ Pathname: Pathname,
+ Query: params.Query,
+ Headers: headers,
+ Expires: params.Expires,
+ KeyTime: KeyTime,
+ SystemClockOffset: self.options.SystemClockOffset,
+ ForceSignHost: forceSignHost
+ });
+ var AuthData = {
+ Authorization: Authorization,
+ SecurityToken: self.options.SecurityToken || self.options.XCosSecurityToken,
+ SignFrom: 'client'
+ };
+ cb(null, AuthData);
+ return AuthData;
+ }();
+ }
+ return '';
+}
+
+// 判断当前请求出错时能否重试
+function allowRetry(err) {
+ var self = this;
+ var canRetry = false;
+ var networkError = false;
+ var isTimeError = false;
+ var serverDate = err.headers && (err.headers.date || err.headers.Date) || err.error && err.error.ServerTime;
+ try {
+ var errorCode = err.error.Code;
+ var errorMessage = err.error.Message;
+ if (errorCode === 'RequestTimeTooSkewed' || errorCode === 'AccessDenied' && errorMessage === 'Request has expired') {
+ isTimeError = true;
+ }
+ } catch (e) {}
+ if (err) {
+ if (isTimeError && serverDate) {
+ var serverTime = Date.parse(serverDate);
+ if (this.options.CorrectClockSkew && Math.abs(util.getSkewTime(this.options.SystemClockOffset) - serverTime) >= 30000) {
+ console.error('error: Local time is too skewed.');
+ this.options.SystemClockOffset = serverTime - Date.now();
+ canRetry = true;
+ }
+ } else if (Math.floor(err.statusCode / 100) === 5) {
+ return {
+ canRetry: true,
+ networkError: false
+ };
+ } else if (err.message === 'timeout') {
+ return {
+ canRetry: true,
+ networkError: self.options.AutoSwitchHost
+ };
+ }
+ /**
+ * 归为网络错误
+ * 1、no statusCode
+ * 2、statusCode === 3xx || 4xx || 5xx && no requestId
+ */
+ if (!err.statusCode) {
+ canRetry = true;
+ networkError = self.options.AutoSwitchHost;
+ } else {
+ var statusCode = Math.floor(err.statusCode / 100);
+ var requestId = (err === null || err === void 0 ? void 0 : err.headers) && (err === null || err === void 0 ? void 0 : err.headers['x-cos-request-id']);
+ if ([3, 4, 5].includes(statusCode) && !requestId) {
+ canRetry = self.options.AutoSwitchHost;
+ networkError = true;
+ }
+ }
+ }
+ return {
+ canRetry: canRetry,
+ networkError: networkError
+ };
+}
+
+/**
+ * requestUrl:请求的url,用于判断是否cos主域名,true才切
+ * clientCalcSign:是否客户端计算签名,服务端返回的签名不能切,true才切
+ * networkError:是否未知网络错误,true才切
+ * */
+function canSwitchHost(_ref) {
+ var requestUrl = _ref.requestUrl,
+ clientCalcSign = _ref.clientCalcSign,
+ networkError = _ref.networkError;
+ if (!this.options.AutoSwitchHost) return false;
+ if (!requestUrl) return false;
+ if (!clientCalcSign) return false;
+ if (!networkError) return false;
+ var commonReg = /^https?:\/\/[^\/]*\.cos\.[^\/]*\.myqcloud\.com(\/.*)?$/;
+ var accelerateReg = /^https?:\/\/[^\/]*\.cos\.accelerate\.myqcloud\.com(\/.*)?$/;
+ // 当前域名是cos主域名才切换
+ var isCommonCosHost = commonReg.test(requestUrl) && !accelerateReg.test(requestUrl);
+ return isCommonCosHost;
+}
+
+// 获取签名并发起请求
+function submitRequest(params, callback) {
+ var self = this;
+
+ // 处理 headers
+ !params.headers && (params.headers = {});
+
+ // 处理 query
+ !params.qs && (params.qs = {});
+ params.VersionId && (params.qs.versionId = params.VersionId);
+ params.qs = util.clearKey(params.qs);
+
+ // 清理 undefined 和 null 字段
+ params.headers && (params.headers = util.clearKey(params.headers));
+ params.qs && (params.qs = util.clearKey(params.qs));
+ var Query = util.clone(params.qs);
+ params.action && (Query[params.action] = '');
+ var contentType;
+ var contentLength = 0;
+ // 指定一个默认的 content-type,如不指定小程序默认会指定 application/json
+ var defaultContentType = 'application/json';
+ util.each(params.headers, function (value, key) {
+ if (key.toLowerCase() === 'content-type') {
+ contentType = value;
+ }
+ if (key.toLowerCase() === 'content-length') {
+ contentLength = value;
+ }
+ });
+ var method = params.method.toLowerCase();
+ var body = params.body;
+ if (body) {
+ if (!contentLength && typeof body === 'string') {
+ // 传了请求体需补充 content-length
+ var buffer = Buffer.from(body, 'utf-8');
+ var _contentLength = buffer.length;
+ params.headers['Content-Length'] = _contentLength;
+ }
+ } else {
+ // 非 get、head 请求的空请求体需补充 content-length = 0
+ var noContentLengthMethods = ['get', 'head'].includes(method);
+ if (!noContentLengthMethods) {
+ params.headers['Content-Length'] = 0;
+ }
+ }
+ // 补充默认 content-type,(putObject/multipartInit 不需要补充)
+ if (contentType === undefined) {
+ params.headers['Content-Type'] = defaultContentType;
+ }
+ var paramsUrl = params.url || params.Url;
+ var SignHost = params.SignHost || getSignHost.call(this, {
+ Bucket: params.Bucket,
+ Region: params.Region,
+ Url: paramsUrl
+ });
+ var tracker = params.tracker;
+ var next = function next(tryTimes) {
+ var oldClockOffset = self.options.SystemClockOffset;
+ if (params.SwitchHost) {
+ // 更换要签的host
+ SignHost = SignHost.replace(/myqcloud.com/, 'tencentcos.cn');
+ }
+ tracker && tracker.setParams({
+ signStartTime: new Date().getTime(),
+ httpRetryTimes: tryTimes - 1
+ });
+ getAuthorizationAsync.call(self, {
+ Bucket: params.Bucket || '',
+ Region: params.Region || '',
+ Method: params.method,
+ Key: params.Key,
+ Query: Query,
+ Headers: params.headers,
+ SignHost: SignHost,
+ Action: params.Action,
+ ResourceKey: params.ResourceKey,
+ Scope: params.Scope,
+ ForceSignHost: self.options.ForceSignHost
+ }, function (err, AuthData) {
+ if (err) {
+ callback(err);
+ return;
+ }
+ tracker && tracker.setParams({
+ signEndTime: new Date().getTime(),
+ httpStartTime: new Date().getTime()
+ });
+ params.AuthData = AuthData;
+ _submitRequest.call(self, params, function (err, data) {
+ var canRetry = false;
+ var networkError = false;
+ if (err) {
+ var info = allowRetry.call(self, err);
+ canRetry = info.canRetry || oldClockOffset !== self.options.SystemClockOffset;
+ networkError = info.networkError;
+ }
+ tracker && tracker.setParams({
+ httpEndTime: new Date().getTime()
+ });
+ if (err && tryTimes < 4 && canRetry) {
+ if (params.headers) {
+ delete params.headers.Authorization;
+ delete params.headers['token'];
+ delete params.headers['clientIP'];
+ delete params.headers['clientUA'];
+ params.headers['x-cos-security-token'] && delete params.headers['x-cos-security-token'];
+ params.headers['x-ci-security-token'] && delete params.headers['x-ci-security-token'];
+ }
+ // 进入重试逻辑时 需判断是否需要切换cos备用域名
+ var switchHost = canSwitchHost.call(self, {
+ requestUrl: (err === null || err === void 0 ? void 0 : err.url) || '',
+ clientCalcSign: (AuthData === null || AuthData === void 0 ? void 0 : AuthData.SignFrom) === 'client',
+ networkError: networkError
+ });
+ params.SwitchHost = switchHost;
+ // 重试时增加请求头,小程序里传字符串类型
+ params.headers['x-cos-sdk-retry'] = 'true';
+ next(tryTimes + 1);
+ } else {
+ callback(err, data);
+ }
+ });
+ });
+ };
+ next(1);
+}
+
+// 发起请求
+function _submitRequest(params, callback) {
+ var self = this;
+ var TaskId = params.TaskId;
+ if (TaskId && !self._isRunningTask(TaskId)) return;
+ var bucket = params.Bucket;
+ var region = params.Region;
+ var object = params.Key;
+ var method = params.method || 'GET';
+ var url = params.url || params.Url;
+ var body = params.body;
+ var json = params.json;
+ var rawBody = params.rawBody;
+ var dataType = params.dataType;
+ var httpDNSServiceId = self.options.HttpDNSServiceId;
+
+ // url
+ if (self.options.UseAccelerate) {
+ region = 'accelerate';
+ }
+ url = url || getUrl({
+ ForcePathStyle: self.options.ForcePathStyle,
+ protocol: self.options.Protocol,
+ domain: self.options.Domain,
+ bucket: bucket,
+ region: region,
+ object: object
+ });
+ if (params.SwitchHost) {
+ // 更换请求的url
+ url = url.replace(/myqcloud.com/, 'tencentcos.cn');
+ }
+ var repoterUrl = object ? url : '';
+ if (params.action) {
+ url = url + '?' + params.action;
+ }
+ if (params.qsStr) {
+ if (url.indexOf('?') > -1) {
+ url = url + '&' + params.qsStr;
+ } else {
+ url = url + '?' + params.qsStr;
+ }
+ }
+ var opt = {
+ method: method,
+ url: url,
+ headers: params.headers,
+ qs: params.qs,
+ filePath: params.filePath,
+ body: body,
+ json: json,
+ httpDNSServiceId: httpDNSServiceId,
+ dataType: dataType
+ };
+
+ // 兼容ci接口
+ var token = 'x-cos-security-token';
+ if (util.isCIHost(url)) {
+ token = 'x-ci-security-token';
+ }
+
+ // 获取签名
+ opt.headers.Authorization = params.AuthData.Authorization;
+ params.AuthData.Token && (opt.headers['token'] = params.AuthData.Token);
+ params.AuthData.ClientIP && (opt.headers['clientIP'] = params.AuthData.ClientIP);
+ params.AuthData.ClientUA && (opt.headers['clientUA'] = params.AuthData.ClientUA);
+ params.AuthData.SecurityToken && (opt.headers[token] = params.AuthData.SecurityToken);
+
+ // 清理 undefined 和 null 字段
+ opt.headers && (opt.headers = util.clearKey(opt.headers));
+ opt = util.clearKey(opt);
+
+ // progress
+ if (params.onProgress && typeof params.onProgress === 'function') {
+ opt.onProgress = function (e) {
+ if (TaskId && !self._isRunningTask(TaskId)) return;
+ var loaded = e ? e.loaded : 0;
+ params.onProgress({
+ loaded: loaded,
+ total: e.total
+ });
+ };
+ }
+ if (this.options.Timeout) {
+ opt.timeout = this.options.Timeout;
+ }
+ self.options.ForcePathStyle && (opt.pathStyle = self.options.ForcePathStyle);
+ self.emit('before-send', opt);
+ var useAccelerate = opt.url.includes('accelerate.');
+ var queryString = opt.qs ? Object.keys(opt.qs).map(function (key) {
+ return "".concat(key, "=").concat(opt.qs[key]);
+ }).join('&') : '';
+ var fullUrl = queryString ? opt.url + '?' + queryString : opt.url;
+ if (params.tracker) {
+ var _opt$body;
+ params.tracker.setParams({
+ url: fullUrl,
+ httpMethod: opt.method,
+ accelerate: useAccelerate,
+ httpSize: ((_opt$body = opt.body) === null || _opt$body === void 0 ? void 0 : _opt$body.size) || 0
+ });
+ // 分块上传时给父级tracker设置url信息
+ if (params.tracker.parent && !params.tracker.parent.params.url) {
+ params.tracker.parent.setParams({
+ url: repoterUrl,
+ accelerate: useAccelerate
+ });
+ }
+ }
+ var sender = REQUEST(opt, function (err, response, body) {
+ if (err === 'abort') return;
+
+ // 返回内容添加 状态码 和 headers
+ var hasReturned;
+ var cb = function cb(err, data) {
+ TaskId && self.off('inner-kill-task', killTask);
+ if (hasReturned) return;
+ hasReturned = true;
+ var attrs = {};
+ response && response.statusCode && (attrs.statusCode = response.statusCode);
+ response && response.headers && (attrs.headers = response.headers);
+ if (err) {
+ opt.url && (attrs.url = opt.url);
+ opt.method && (attrs.method = opt.method);
+ err = util.extend(err || {}, attrs);
+ callback(err, null);
+ } else {
+ data = util.extend(data || {}, attrs);
+ callback(null, data);
+ }
+ sender = null;
+ };
+ // 请求错误,发生网络错误
+ if (err) {
+ cb({
+ error: err
+ });
+ return;
+ }
+
+ // 请求返回码不为 200
+ var statusCode = response.statusCode;
+ var statusSuccess = Math.floor(statusCode / 100) === 2; // 200 202 204 206
+
+ // 不对 body 进行转换,body 直接挂载返回
+ if (rawBody) {
+ if (statusSuccess) {
+ return cb(null, {
+ body: body
+ });
+ } else {
+ // 报错但是返回了 ArrayBuffer,需要解析成 string
+ if (body instanceof ArrayBuffer) {
+ var errorStr = util.arrayBufferToString(body);
+ var json = util.parseResBody(errorStr);
+ var errorBody = json.Error || json;
+ return cb({
+ error: errorBody
+ });
+ }
+ }
+ }
+
+ // 解析body,兼容 xml、json,解析失败时完整返回
+ var jsonRes = util.parseResBody(body);
+ if (!statusSuccess) {
+ cb({
+ error: jsonRes.Error || jsonRes
+ });
+ return;
+ }
+ if (jsonRes.Error) {
+ cb({
+ error: jsonRes.Error
+ });
+ return;
+ }
+ cb(null, jsonRes);
+ });
+
+ // kill task
+ var killTask = function killTask(data) {
+ if (data.TaskId === TaskId) {
+ sender && sender.abort && sender.abort();
+ self.off('inner-kill-task', killTask);
+ }
+ };
+ TaskId && self.on('inner-kill-task', killTask);
+}
+var API_MAP = {
+ // Bucket 相关方法
+ getService: getService,
+ // Bucket
+ putBucket: putBucket,
+ headBucket: headBucket,
+ // Bucket
+ getBucket: getBucket,
+ deleteBucket: deleteBucket,
+ putBucketAcl: putBucketAcl,
+ // BucketACL
+ getBucketAcl: getBucketAcl,
+ putBucketCors: putBucketCors,
+ // BucketCors
+ getBucketCors: getBucketCors,
+ deleteBucketCors: deleteBucketCors,
+ getBucketLocation: getBucketLocation,
+ // BucketLocation
+ getBucketPolicy: getBucketPolicy,
+ // BucketPolicy
+ putBucketPolicy: putBucketPolicy,
+ deleteBucketPolicy: deleteBucketPolicy,
+ putBucketTagging: putBucketTagging,
+ // BucketTagging
+ getBucketTagging: getBucketTagging,
+ deleteBucketTagging: deleteBucketTagging,
+ putBucketLifecycle: putBucketLifecycle,
+ // BucketLifecycle
+ getBucketLifecycle: getBucketLifecycle,
+ deleteBucketLifecycle: deleteBucketLifecycle,
+ putBucketVersioning: putBucketVersioning,
+ // BucketVersioning
+ getBucketVersioning: getBucketVersioning,
+ putBucketReplication: putBucketReplication,
+ // BucketReplication
+ getBucketReplication: getBucketReplication,
+ deleteBucketReplication: deleteBucketReplication,
+ putBucketWebsite: putBucketWebsite,
+ // BucketWebsite
+ getBucketWebsite: getBucketWebsite,
+ deleteBucketWebsite: deleteBucketWebsite,
+ putBucketReferer: putBucketReferer,
+ // BucketReferer
+ getBucketReferer: getBucketReferer,
+ putBucketDomain: putBucketDomain,
+ // BucketDomain
+ getBucketDomain: getBucketDomain,
+ deleteBucketDomain: deleteBucketDomain,
+ putBucketOrigin: putBucketOrigin,
+ // BucketOrigin
+ getBucketOrigin: getBucketOrigin,
+ deleteBucketOrigin: deleteBucketOrigin,
+ putBucketLogging: putBucketLogging,
+ // BucketLogging
+ getBucketLogging: getBucketLogging,
+ putBucketInventory: putBucketInventory,
+ // BucketInventory
+ getBucketInventory: getBucketInventory,
+ listBucketInventory: listBucketInventory,
+ deleteBucketInventory: deleteBucketInventory,
+ putBucketAccelerate: putBucketAccelerate,
+ getBucketAccelerate: getBucketAccelerate,
+ // Object 相关方法
+ getObject: getObject,
+ headObject: headObject,
+ listObjectVersions: listObjectVersions,
+ putObject: putObject,
+ postObject: postObject,
+ deleteObject: deleteObject,
+ getObjectAcl: getObjectAcl,
+ putObjectAcl: putObjectAcl,
+ optionsObject: optionsObject,
+ putObjectCopy: putObjectCopy,
+ deleteMultipleObject: deleteMultipleObject,
+ restoreObject: restoreObject,
+ putObjectTagging: putObjectTagging,
+ getObjectTagging: getObjectTagging,
+ deleteObjectTagging: deleteObjectTagging,
+ appendObject: appendObject,
+ // 分块上传相关方法
+ uploadPartCopy: uploadPartCopy,
+ multipartInit: multipartInit,
+ multipartUpload: multipartUpload,
+ multipartComplete: multipartComplete,
+ multipartList: multipartList,
+ multipartListPart: multipartListPart,
+ multipartAbort: multipartAbort,
+ // 工具方法
+ request: request,
+ getObjectUrl: getObjectUrl,
+ getAuth: getAuth
+};
+module.exports.init = function (COS, task) {
+ task.transferToTaskMethod(API_MAP, 'postObject');
+ task.transferToTaskMethod(API_MAP, 'putObject');
+ util.each(API_MAP, function (fn, apiName) {
+ COS.prototype[apiName] = util.apiWrapper(apiName, fn);
+ });
+};
+/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./../node_modules/buffer/index.js */ "./node_modules/buffer/index.js").Buffer))
+
+/***/ }),
+
+/***/ "./src/cos.js":
+/*!********************!*\
+ !*** ./src/cos.js ***!
+ \********************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+var util = __webpack_require__(/*! ./util */ "./src/util.js");
+var event = __webpack_require__(/*! ./event */ "./src/event.js");
+var task = __webpack_require__(/*! ./task */ "./src/task.js");
+var base = __webpack_require__(/*! ./base */ "./src/base.js");
+var advance = __webpack_require__(/*! ./advance */ "./src/advance.js");
+var pkg = __webpack_require__(/*! ../package.json */ "./package.json");
+var defaultOptions = {
+ SecretId: '',
+ SecretKey: '',
+ SecurityToken: '',
+ // 使用临时密钥需要注意自行刷新 Token
+ StartTime: 0,
+ // 临时密钥返回起始时间
+ ExpiredTime: 0,
+ // 临时密钥过期时间
+ ChunkRetryTimes: 2,
+ FileParallelLimit: 3,
+ ChunkParallelLimit: 3,
+ ChunkSize: 1024 * 1024,
+ SliceSize: 1024 * 1024,
+ CopyChunkParallelLimit: 20,
+ CopyChunkSize: 1024 * 1024 * 10,
+ CopySliceSize: 1024 * 1024 * 10,
+ MaxPartNumber: 10000,
+ ProgressInterval: 1000,
+ UploadQueueSize: 10000,
+ Domain: '',
+ ServiceDomain: '',
+ Protocol: '',
+ CompatibilityMode: false,
+ ForcePathStyle: false,
+ Timeout: 0,
+ // 单位毫秒,0 代表不设置超时时间
+ CorrectClockSkew: true,
+ SystemClockOffset: 0,
+ // 单位毫秒,ms
+ UploadCheckContentMd5: false,
+ UploadAddMetaMd5: false,
+ UploadIdCacheLimit: 50,
+ UseAccelerate: false,
+ ForceSignHost: true,
+ // 默认将host加入签名计算,关闭后可能导致越权风险,建议保持为true
+ HttpDNSServiceId: '',
+ // HttpDNS 服务商 Id,填写后代表开启 HttpDNS 服务。HttpDNS 用法详见https://developers.weixin.qq.com/miniprogram/dev/framework/ability/HTTPDNS.html
+ SimpleUploadMethod: 'postObject',
+ // 高级上传内部判断需要走简单上传时,指定的上传方法,可选postObject或putObject
+ AutoSwitchHost: false,
+ CopySourceParser: null,
+ // 自定义拷贝源解析器
+ ObjectKeySimplifyCheck: true,
+ // 开启合并校验 getObject Key
+ /** 上报相关配置 **/
+ DeepTracker: false,
+ // 上报时是否对每个分块上传做单独上报
+ TrackerDelay: 5000,
+ // 周期性上报,单位毫秒。0代表实时上报
+ CustomId: '',
+ // 自定义上报id
+ BeaconReporter: null,
+ // 灯塔上报组件,如有需要请自行传入,传入即代表开启上报
+ ClsReporter: null // cls 上报组件,如有需要请自行传入,传入即代表开启上报
+};
+
+// 对外暴露的类
+var COS = function COS(options) {
+ this.options = util.extend(util.clone(defaultOptions), options || {});
+ this.options.FileParallelLimit = Math.max(1, this.options.FileParallelLimit);
+ this.options.ChunkParallelLimit = Math.max(1, this.options.ChunkParallelLimit);
+ this.options.ChunkRetryTimes = Math.max(0, this.options.ChunkRetryTimes);
+ this.options.ChunkSize = Math.max(1024 * 1024, this.options.ChunkSize);
+ this.options.CopyChunkParallelLimit = Math.max(1, this.options.CopyChunkParallelLimit);
+ this.options.CopyChunkSize = Math.max(1024 * 1024, this.options.CopyChunkSize);
+ this.options.CopySliceSize = Math.max(0, this.options.CopySliceSize);
+ this.options.MaxPartNumber = Math.max(1024, Math.min(10000, this.options.MaxPartNumber));
+ this.options.Timeout = Math.max(0, this.options.Timeout);
+ this.options.EnableReporter = this.options.BeaconReporter || this.options.ClsReporter;
+ if (this.options.AppId) {
+ console.warn('warning: AppId has been deprecated, Please put it at the end of parameter Bucket(E.g: "test-1250000000").');
+ }
+ if (this.options.SecretId && this.options.SecretId.indexOf(' ') > -1) {
+ console.error('error: SecretId格式错误,请检查');
+ console.error('error: SecretId format is incorrect. Please check');
+ }
+ if (this.options.SecretKey && this.options.SecretKey.indexOf(' ') > -1) {
+ console.error('error: SecretKey格式错误,请检查');
+ console.error('error: SecretKey format is incorrect. Please check');
+ }
+ if (this.options.ForcePathStyle) {
+ console.warn('cos-wx-sdk-v5不再支持使用path-style,仅支持使用virtual-hosted-style,参考文档:https://cloud.tencent.com/document/product/436/96243');
+ throw new Error('ForcePathStyle is not supported');
+ }
+ event.init(this);
+ task.init(this);
+};
+base.init(COS, task);
+advance.init(COS, task);
+COS.util = {
+ md5: util.md5,
+ xml2json: util.xml2json,
+ json2xml: util.json2xml,
+ encodeBase64: util.encodeBase64
+};
+COS.getAuthorization = util.getAuth;
+COS.version = pkg.version;
+module.exports = COS;
+
+/***/ }),
+
+/***/ "./src/event.js":
+/*!**********************!*\
+ !*** ./src/event.js ***!
+ \**********************/
+/*! no static exports found */
+/***/ (function(module, exports) {
+
+var initEvent = function initEvent(cos) {
+ var listeners = {};
+ var getList = function getList(action) {
+ !listeners[action] && (listeners[action] = []);
+ return listeners[action];
+ };
+ cos.on = function (action, callback) {
+ getList(action).push(callback);
+ };
+ cos.off = function (action, callback) {
+ var list = getList(action);
+ for (var i = list.length - 1; i >= 0; i--) {
+ callback === list[i] && list.splice(i, 1);
+ }
+ };
+ cos.emit = function (action, data) {
+ var list = getList(action).map(function (cb) {
+ return cb;
+ });
+ for (var i = 0; i < list.length; i++) {
+ list[i](data);
+ }
+ };
+};
+var EventProxy = function EventProxy() {
+ initEvent(this);
+};
+module.exports.init = initEvent;
+module.exports.EventProxy = EventProxy;
+
+/***/ }),
+
+/***/ "./src/session.js":
+/*!************************!*\
+ !*** ./src/session.js ***!
+ \************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+var util = __webpack_require__(/*! ./util */ "./src/util.js");
+
+// 按照文件特征值,缓存 UploadId
+var cacheKey = 'cos_sdk_upload_cache';
+var expires = 30 * 24 * 3600;
+var cache;
+var timer;
+var getCache = function getCache() {
+ try {
+ var val = JSON.parse(wx.getStorageSync(cacheKey));
+ } catch (e) {}
+ if (!val) val = [];
+ return val;
+};
+var setCache = function setCache() {
+ try {
+ if (cache.length) wx.setStorageSync(cacheKey, JSON.stringify(cache));else wx.removeStorageSync(cacheKey);
+ } catch (e) {}
+};
+var init = function init() {
+ if (cache) return;
+ cache = getCache();
+ // 清理太老旧的数据
+ var changed = false;
+ var now = Math.round(Date.now() / 1000);
+ for (var i = cache.length - 1; i >= 0; i--) {
+ var mtime = cache[i][2];
+ if (!mtime || mtime + expires < now) {
+ cache.splice(i, 1);
+ changed = true;
+ }
+ }
+ changed && setCache();
+};
+
+// 把缓存存到本地
+var save = function save() {
+ if (timer) return;
+ timer = setTimeout(function () {
+ setCache();
+ timer = null;
+ }, 400);
+};
+var mod = {
+ using: {},
+ // 标记 UploadId 正在使用
+ setUsing: function setUsing(uuid) {
+ mod.using[uuid] = true;
+ },
+ // 标记 UploadId 已经没在使用
+ removeUsing: function removeUsing(uuid) {
+ delete mod.using[uuid];
+ },
+ // 用上传参数生成哈希值
+ getFileId: function getFileId(FileStat, ChunkSize, Bucket, Key) {
+ if (FileStat.FilePath && FileStat.size && FileStat.lastModifiedTime && ChunkSize) {
+ return util.md5([FileStat.FilePath].join('::')) + '-' + util.md5([FileStat.size, FileStat.mode, FileStat.lastAccessedTime, FileStat.lastModifiedTime, ChunkSize, Bucket, Key].join('::'));
+ } else {
+ return null;
+ }
+ },
+ // 用上传参数生成哈希值
+ getCopyFileId: function getCopyFileId(copySource, sourceHeaders, ChunkSize, Bucket, Key) {
+ var size = sourceHeaders['content-length'];
+ var etag = sourceHeaders.etag || '';
+ var lastModified = sourceHeaders['last-modified'];
+ if (copySource && ChunkSize) {
+ return util.md5([copySource, size, etag, lastModified, ChunkSize, Bucket, Key].join('::'));
+ } else {
+ return null;
+ }
+ },
+ // 获取文件对应的 UploadId 列表
+ getUploadIdList: function getUploadIdList(uuid) {
+ if (!uuid) return null;
+ init();
+ var list = [];
+ for (var i = 0; i < cache.length; i++) {
+ if (cache[i][0] === uuid) list.push(cache[i][1]);
+ }
+ return list.length ? list : null;
+ },
+ // 缓存 UploadId
+ saveUploadId: function saveUploadId(uuid, UploadId, limit) {
+ init();
+ if (!uuid) return;
+ // 清理没用的 UploadId
+ var part1 = uuid.substr(0, uuid.indexOf('-') + 1);
+ for (var i = cache.length - 1; i >= 0; i--) {
+ var item = cache[i];
+ if (item[0] === uuid && item[1] === UploadId) {
+ cache.splice(i, 1);
+ } else if (uuid !== item[0] && item[0].indexOf(part1) === 0) {
+ // 文件路径相同,但其他信息不同,说明文件改变了或上传参数(存储桶、路径、分片大小)变了,直接清理掉
+ cache.splice(i, 1);
+ }
+ }
+ cache.unshift([uuid, UploadId, Math.round(Date.now() / 1000)]);
+ if (cache.length > limit) cache.splice(limit);
+ save();
+ },
+ // UploadId 已用完,移除掉
+ removeUploadId: function removeUploadId(UploadId) {
+ init();
+ delete mod.using[UploadId];
+ for (var i = cache.length - 1; i >= 0; i--) {
+ if (cache[i][1] === UploadId) cache.splice(i, 1);
+ }
+ save();
+ }
+};
+module.exports = mod;
+
+/***/ }),
+
+/***/ "./src/task.js":
+/*!*********************!*\
+ !*** ./src/task.js ***!
+ \*********************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+var session = __webpack_require__(/*! ./session */ "./src/session.js");
+var util = __webpack_require__(/*! ./util */ "./src/util.js");
+var originApiMap = {};
+var transferToTaskMethod = function transferToTaskMethod(apiMap, apiName) {
+ originApiMap[apiName] = apiMap[apiName];
+ apiMap[apiName] = function (params, callback) {
+ if (params.SkipTask) {
+ originApiMap[apiName].call(this, params, callback);
+ } else {
+ this._addTask(apiName, params, callback);
+ }
+ };
+};
+var initTask = function initTask(cos) {
+ var queue = [];
+ var tasks = {};
+ var uploadingFileCount = 0;
+ var nextUploadIndex = 0;
+
+ // 接口返回简略的任务信息
+ var formatTask = function formatTask(task) {
+ var t = {
+ id: task.id,
+ Bucket: task.Bucket,
+ Region: task.Region,
+ Key: task.Key,
+ FilePath: task.FilePath,
+ state: task.state,
+ loaded: task.loaded,
+ size: task.size,
+ speed: task.speed,
+ percent: task.percent,
+ hashPercent: task.hashPercent,
+ error: task.error
+ };
+ if (task.FilePath) t.FilePath = task.FilePath;
+ return t;
+ };
+ var emitListUpdate = function () {
+ var timer;
+ var emit = function emit() {
+ timer = 0;
+ cos.emit('task-list-update', {
+ list: util.map(queue, formatTask)
+ });
+ cos.emit('list-update', {
+ list: util.map(queue, formatTask)
+ });
+ };
+ return function () {
+ if (!timer) timer = setTimeout(emit);
+ };
+ }();
+ var clearQueue = function clearQueue() {
+ if (queue.length <= cos.options.UploadQueueSize) return;
+ for
+
+ // 如果还太多,才继续清理
+ (var i = 0; i < nextUploadIndex &&
+ // 小于当前操作的 index 才清理
+ i < queue.length &&
+ // 大于队列才清理
+ queue.length > cos.options.UploadQueueSize;) {
+ var isActive = queue[i].state === 'waiting' || queue[i].state === 'checking' || queue[i].state === 'uploading';
+ if (!queue[i] || !isActive) {
+ tasks[queue[i].id] && delete tasks[queue[i].id];
+ queue.splice(i, 1);
+ nextUploadIndex--;
+ } else {
+ i++;
+ }
+ }
+ emitListUpdate();
+ };
+ var startNextTask = function startNextTask() {
+ // 检查是否允许增加执行进程
+ if (uploadingFileCount >= cos.options.FileParallelLimit) return;
+ // 跳过不可执行的任务
+ while (queue[nextUploadIndex] && queue[nextUploadIndex].state !== 'waiting') nextUploadIndex++;
+ // 检查是否已遍历结束
+ if (nextUploadIndex >= queue.length) return;
+ // 上传该遍历到的任务
+ var task = queue[nextUploadIndex];
+ nextUploadIndex++;
+ uploadingFileCount++;
+ task.state = 'checking';
+ task.params.onTaskStart && task.params.onTaskStart(formatTask(task));
+ !task.params.UploadData && (task.params.UploadData = {});
+ var apiParams = util.formatParams(task.api, task.params);
+ originApiMap[task.api].call(cos, apiParams, function (err, data) {
+ if (!cos._isRunningTask(task.id)) return;
+ if (task.state === 'checking' || task.state === 'uploading') {
+ task.state = err ? 'error' : 'success';
+ err && (task.error = err);
+ uploadingFileCount--;
+ emitListUpdate();
+ startNextTask();
+ task.callback && task.callback(err, data);
+ if (task.state === 'success') {
+ if (task.params) {
+ delete task.params.UploadData;
+ delete task.params.Body;
+ delete task.params;
+ }
+ delete task.callback;
+ }
+ }
+ clearQueue();
+ });
+ emitListUpdate();
+ // 异步执行下一个任务
+ setTimeout(startNextTask);
+ };
+ var killTask = function killTask(id, switchToState) {
+ var task = tasks[id];
+ if (!task) return;
+ var waiting = task && task.state === 'waiting';
+ var running = task && (task.state === 'checking' || task.state === 'uploading');
+ if (switchToState === 'canceled' && task.state !== 'canceled' || switchToState === 'paused' && waiting || switchToState === 'paused' && running) {
+ if (switchToState === 'paused' && task.params.Body && typeof task.params.Body.pipe === 'function') {
+ console.error('stream not support pause');
+ return;
+ }
+ task.state = switchToState;
+ cos.emit('inner-kill-task', {
+ TaskId: id,
+ toState: switchToState
+ });
+ try {
+ var UploadId = task && task.params && task.params.UploadData.UploadId;
+ } catch (e) {}
+ if (switchToState === 'canceled' && UploadId) session.removeUsing(UploadId);
+ emitListUpdate();
+ if (running) {
+ uploadingFileCount--;
+ startNextTask();
+ }
+ if (switchToState === 'canceled') {
+ if (task.params) {
+ delete task.params.UploadData;
+ delete task.params.Body;
+ delete task.params;
+ }
+ delete task.callback;
+ }
+ }
+ clearQueue();
+ };
+ cos._addTasks = function (taskList) {
+ util.each(taskList, function (task) {
+ cos._addTask(task.api, task.params, task.callback, true);
+ });
+ emitListUpdate();
+ };
+ cos._addTask = function (api, params, callback, ignoreAddEvent) {
+ // 如果小程序版本不支持获取文件分片内容,统一转到 简单上传 接口上传
+ var simpleUploadMethod = cos.options.SimpleUploadMethod === 'postObject' ? 'postObject' : 'putObject';
+ if (api === 'sliceUploadFile' && !util.canFileSlice()) api = simpleUploadMethod;
+
+ // 复制参数对象
+ params = util.formatParams(api, params);
+
+ // 生成 id
+ var id = util.uuid();
+ params.TaskId = id;
+ params.onTaskReady && params.onTaskReady(id);
+ var task = {
+ // env
+ params: params,
+ callback: callback,
+ api: api,
+ index: queue.length,
+ // task
+ id: id,
+ Bucket: params.Bucket,
+ Region: params.Region,
+ Key: params.Key,
+ FilePath: params.FilePath || '',
+ state: 'waiting',
+ loaded: 0,
+ size: 0,
+ speed: 0,
+ percent: 0,
+ hashPercent: 0,
+ error: null
+ };
+ var onHashProgress = params.onHashProgress;
+ params.onHashProgress = function (info) {
+ if (!cos._isRunningTask(task.id)) return;
+ task.hashPercent = info.percent;
+ onHashProgress && onHashProgress(info);
+ emitListUpdate();
+ };
+ var onProgress = params.onProgress;
+ params.onProgress = function (info) {
+ if (!cos._isRunningTask(task.id)) return;
+ task.state === 'checking' && (task.state = 'uploading');
+ task.loaded = info.loaded;
+ task.size = info.total;
+ task.speed = info.speed;
+ task.percent = info.percent;
+ onProgress && onProgress(info);
+ emitListUpdate();
+ };
+
+ // 异步获取 filesize
+ util.getFileSize(api, params, function (err, size) {
+ // 开始处理上传
+ if (err) {
+ // 如果获取大小出错,不加入队列
+ callback(err);
+ return;
+ }
+ // 获取完文件大小再把任务加入队列
+ tasks[id] = task;
+ queue.push(task);
+ task.size = size;
+ !ignoreAddEvent && emitListUpdate();
+ startNextTask();
+ clearQueue();
+ });
+ return id;
+ };
+ cos._isRunningTask = function (id) {
+ var task = tasks[id];
+ return !!(task && (task.state === 'checking' || task.state === 'uploading'));
+ };
+ cos.getTaskList = function () {
+ return util.map(queue, formatTask);
+ };
+ cos.cancelTask = function (id) {
+ killTask(id, 'canceled');
+ };
+ cos.pauseTask = function (id) {
+ killTask(id, 'paused');
+ };
+ cos.restartTask = function (id) {
+ var task = tasks[id];
+ if (task && (task.state === 'paused' || task.state === 'error')) {
+ task.state = 'waiting';
+ emitListUpdate();
+ nextUploadIndex = Math.min(nextUploadIndex, task.index);
+ startNextTask();
+ }
+ };
+ cos.isUploadRunning = function () {
+ return uploadingFileCount || nextUploadIndex < queue.length;
+ };
+};
+module.exports.transferToTaskMethod = transferToTaskMethod;
+module.exports.init = initTask;
+
+/***/ }),
+
+/***/ "./src/tracker.js":
+/*!************************!*\
+ !*** ./src/tracker.js ***!
+ \************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
+function _regeneratorRuntime() { "use strict"; /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ _regeneratorRuntime = function _regeneratorRuntime() { return e; }; var t, e = {}, r = Object.prototype, n = r.hasOwnProperty, o = Object.defineProperty || function (t, e, r) { t[e] = r.value; }, i = "function" == typeof Symbol ? Symbol : {}, a = i.iterator || "@@iterator", c = i.asyncIterator || "@@asyncIterator", u = i.toStringTag || "@@toStringTag"; function define(t, e, r) { return Object.defineProperty(t, e, { value: r, enumerable: !0, configurable: !0, writable: !0 }), t[e]; } try { define({}, ""); } catch (t) { define = function define(t, e, r) { return t[e] = r; }; } function wrap(t, e, r, n) { var i = e && e.prototype instanceof Generator ? e : Generator, a = Object.create(i.prototype), c = new Context(n || []); return o(a, "_invoke", { value: makeInvokeMethod(t, r, c) }), a; } function tryCatch(t, e, r) { try { return { type: "normal", arg: t.call(e, r) }; } catch (t) { return { type: "throw", arg: t }; } } e.wrap = wrap; var h = "suspendedStart", l = "suspendedYield", f = "executing", s = "completed", y = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} var p = {}; define(p, a, function () { return this; }); var d = Object.getPrototypeOf, v = d && d(d(values([]))); v && v !== r && n.call(v, a) && (p = v); var g = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(p); function defineIteratorMethods(t) { ["next", "throw", "return"].forEach(function (e) { define(t, e, function (t) { return this._invoke(e, t); }); }); } function AsyncIterator(t, e) { function invoke(r, o, i, a) { var c = tryCatch(t[r], t, o); if ("throw" !== c.type) { var u = c.arg, h = u.value; return h && "object" == _typeof(h) && n.call(h, "__await") ? e.resolve(h.__await).then(function (t) { invoke("next", t, i, a); }, function (t) { invoke("throw", t, i, a); }) : e.resolve(h).then(function (t) { u.value = t, i(u); }, function (t) { return invoke("throw", t, i, a); }); } a(c.arg); } var r; o(this, "_invoke", { value: function value(t, n) { function callInvokeWithMethodAndArg() { return new e(function (e, r) { invoke(t, n, e, r); }); } return r = r ? r.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); } }); } function makeInvokeMethod(e, r, n) { var o = h; return function (i, a) { if (o === f) throw Error("Generator is already running"); if (o === s) { if ("throw" === i) throw a; return { value: t, done: !0 }; } for (n.method = i, n.arg = a;;) { var c = n.delegate; if (c) { var u = maybeInvokeDelegate(c, n); if (u) { if (u === y) continue; return u; } } if ("next" === n.method) n.sent = n._sent = n.arg;else if ("throw" === n.method) { if (o === h) throw o = s, n.arg; n.dispatchException(n.arg); } else "return" === n.method && n.abrupt("return", n.arg); o = f; var p = tryCatch(e, r, n); if ("normal" === p.type) { if (o = n.done ? s : l, p.arg === y) continue; return { value: p.arg, done: n.done }; } "throw" === p.type && (o = s, n.method = "throw", n.arg = p.arg); } }; } function maybeInvokeDelegate(e, r) { var n = r.method, o = e.iterator[n]; if (o === t) return r.delegate = null, "throw" === n && e.iterator["return"] && (r.method = "return", r.arg = t, maybeInvokeDelegate(e, r), "throw" === r.method) || "return" !== n && (r.method = "throw", r.arg = new TypeError("The iterator does not provide a '" + n + "' method")), y; var i = tryCatch(o, e.iterator, r.arg); if ("throw" === i.type) return r.method = "throw", r.arg = i.arg, r.delegate = null, y; var a = i.arg; return a ? a.done ? (r[e.resultName] = a.value, r.next = e.nextLoc, "return" !== r.method && (r.method = "next", r.arg = t), r.delegate = null, y) : a : (r.method = "throw", r.arg = new TypeError("iterator result is not an object"), r.delegate = null, y); } function pushTryEntry(t) { var e = { tryLoc: t[0] }; 1 in t && (e.catchLoc = t[1]), 2 in t && (e.finallyLoc = t[2], e.afterLoc = t[3]), this.tryEntries.push(e); } function resetTryEntry(t) { var e = t.completion || {}; e.type = "normal", delete e.arg, t.completion = e; } function Context(t) { this.tryEntries = [{ tryLoc: "root" }], t.forEach(pushTryEntry, this), this.reset(!0); } function values(e) { if (e || "" === e) { var r = e[a]; if (r) return r.call(e); if ("function" == typeof e.next) return e; if (!isNaN(e.length)) { var o = -1, i = function next() { for (; ++o < e.length;) if (n.call(e, o)) return next.value = e[o], next.done = !1, next; return next.value = t, next.done = !0, next; }; return i.next = i; } } throw new TypeError(_typeof(e) + " is not iterable"); } return GeneratorFunction.prototype = GeneratorFunctionPrototype, o(g, "constructor", { value: GeneratorFunctionPrototype, configurable: !0 }), o(GeneratorFunctionPrototype, "constructor", { value: GeneratorFunction, configurable: !0 }), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, u, "GeneratorFunction"), e.isGeneratorFunction = function (t) { var e = "function" == typeof t && t.constructor; return !!e && (e === GeneratorFunction || "GeneratorFunction" === (e.displayName || e.name)); }, e.mark = function (t) { return Object.setPrototypeOf ? Object.setPrototypeOf(t, GeneratorFunctionPrototype) : (t.__proto__ = GeneratorFunctionPrototype, define(t, u, "GeneratorFunction")), t.prototype = Object.create(g), t; }, e.awrap = function (t) { return { __await: t }; }, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, c, function () { return this; }), e.AsyncIterator = AsyncIterator, e.async = function (t, r, n, o, i) { void 0 === i && (i = Promise); var a = new AsyncIterator(wrap(t, r, n, o), i); return e.isGeneratorFunction(r) ? a : a.next().then(function (t) { return t.done ? t.value : a.next(); }); }, defineIteratorMethods(g), define(g, u, "Generator"), define(g, a, function () { return this; }), define(g, "toString", function () { return "[object Generator]"; }), e.keys = function (t) { var e = Object(t), r = []; for (var n in e) r.push(n); return r.reverse(), function next() { for (; r.length;) { var t = r.pop(); if (t in e) return next.value = t, next.done = !1, next; } return next.done = !0, next; }; }, e.values = values, Context.prototype = { constructor: Context, reset: function reset(e) { if (this.prev = 0, this.next = 0, this.sent = this._sent = t, this.done = !1, this.delegate = null, this.method = "next", this.arg = t, this.tryEntries.forEach(resetTryEntry), !e) for (var r in this) "t" === r.charAt(0) && n.call(this, r) && !isNaN(+r.slice(1)) && (this[r] = t); }, stop: function stop() { this.done = !0; var t = this.tryEntries[0].completion; if ("throw" === t.type) throw t.arg; return this.rval; }, dispatchException: function dispatchException(e) { if (this.done) throw e; var r = this; function handle(n, o) { return a.type = "throw", a.arg = e, r.next = n, o && (r.method = "next", r.arg = t), !!o; } for (var o = this.tryEntries.length - 1; o >= 0; --o) { var i = this.tryEntries[o], a = i.completion; if ("root" === i.tryLoc) return handle("end"); if (i.tryLoc <= this.prev) { var c = n.call(i, "catchLoc"), u = n.call(i, "finallyLoc"); if (c && u) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } else if (c) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); } else { if (!u) throw Error("try statement without catch or finally"); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } } } }, abrupt: function abrupt(t, e) { for (var r = this.tryEntries.length - 1; r >= 0; --r) { var o = this.tryEntries[r]; if (o.tryLoc <= this.prev && n.call(o, "finallyLoc") && this.prev < o.finallyLoc) { var i = o; break; } } i && ("break" === t || "continue" === t) && i.tryLoc <= e && e <= i.finallyLoc && (i = null); var a = i ? i.completion : {}; return a.type = t, a.arg = e, i ? (this.method = "next", this.next = i.finallyLoc, y) : this.complete(a); }, complete: function complete(t, e) { if ("throw" === t.type) throw t.arg; return "break" === t.type || "continue" === t.type ? this.next = t.arg : "return" === t.type ? (this.rval = this.arg = t.arg, this.method = "return", this.next = "end") : "normal" === t.type && e && (this.next = e), y; }, finish: function finish(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.finallyLoc === t) return this.complete(r.completion, r.afterLoc), resetTryEntry(r), y; } }, "catch": function _catch(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.tryLoc === t) { var n = r.completion; if ("throw" === n.type) { var o = n.arg; resetTryEntry(r); } return o; } } throw Error("illegal catch attempt"); }, delegateYield: function delegateYield(e, r, n) { return this.delegate = { iterator: values(e), resultName: r, nextLoc: n }, "next" === this.method && (this.arg = t), y; } }, e; }
+function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
+function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
+function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
+function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
+function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
+function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
+var pkg = __webpack_require__(/*! ../package.json */ "./package.json");
+var beacon = null;
+var getBeacon = function getBeacon(Beacon, delay) {
+ if (!beacon) {
+ // 生成 beacon
+ if (typeof Beacon !== 'function') {
+ throw new Error('Beacon not found');
+ }
+ beacon = new Beacon({
+ appkey: '0WEB05PY6MHRGK0U',
+ versionCode: pkg.version,
+ channelID: 'mp_sdk',
+ //渠道,选填
+ openid: 'openid',
+ // 用户id, 选填
+ unionid: 'unid',
+ //用户unionid , 类似idfv,选填
+ strictMode: false,
+ //严苛模式开关, 打开严苛模式会主动抛出异常, 上线请务必关闭!!!
+ delay: delay,
+ // 普通事件延迟上报时间(单位毫秒), 默认1000(1秒),选填
+ sessionDuration: 60 * 1000 // session变更的时间间隔, 一个用户持续30分钟(默认值)没有任何上报则算另一次 session,每变更一次session上报一次启动事件(rqd_applaunched),使用毫秒(ms),最小值30秒,选填
+ });
+ }
+ return beacon;
+};
+
+// 毫秒转秒
+var ms2s = function ms2s(ms) {
+ if (!ms || ms < 0) return 0;
+ return (ms / 1000).toFixed(3);
+};
+var utils = {
+ // 生成uid 每个链路对应唯一一条uid
+ getUid: function getUid() {
+ var S4 = function S4() {
+ return ((1 + Math.random()) * 0x10000 | 0).toString(16).substring(1);
+ };
+ return S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4();
+ },
+ // 获取网络类型 4g | wifi
+ getNetType: function getNetType() {
+ return new Promise(function (resolve) {
+ if (wx.canIUse('getNetworkType')) {
+ try {
+ wx.getNetworkType({
+ success: function success(res) {
+ resolve(res.networkType);
+ }
+ });
+ } catch (e) {
+ resolve('can_not_get_network_type');
+ }
+ } else {
+ resolve('can_not_get_network_type');
+ }
+ });
+ },
+ // 获取系统信息
+ getSystemInfo: function getSystemInfo() {
+ var baseInfo = {
+ // ↓上报项
+ devicePlatform: 'can_not_get_system_info',
+ // ios/anroid/windows/mac/devtools
+ wxVersion: 'can_not_get_system_info',
+ wxSystem: 'can_not_get_system_info',
+ wxSdkVersion: 'can_not_get_system_info'
+ };
+ var appBaseInfo = {};
+ var deviceInfo = {};
+ if (wx.canIUse('getAppBaseInfo')) {
+ appBaseInfo = wx.getAppBaseInfo() || {};
+ }
+ if (wx.canIUse('getDeviceInfo')) {
+ deviceInfo = wx.getDeviceInfo() || {};
+ }
+ var sdkVersion = appBaseInfo.SDKVersion || 'can_not_get_system_info';
+ var version = appBaseInfo.version || 'can_not_get_system_info';
+ var platform = deviceInfo.platform || 'can_not_get_system_info';
+ var system = deviceInfo.system || 'can_not_get_system_info';
+ Object.assign(baseInfo, {
+ devicePlatform: platform,
+ wxVersion: version,
+ wxSystem: system,
+ wxSdkVersion: sdkVersion
+ });
+ return baseInfo;
+ }
+};
+
+// 设备信息,只取一次值
+var deviceInfo = utils.getSystemInfo();
+var transApiName = function transApiName(api) {
+ if (['putObject', 'sliceUploadFile', 'uploadFile', 'uploadFiles'].includes(api)) {
+ return 'UploadTask';
+ } else if (api === 'getObject') {
+ return 'DownloadTask';
+ } else if (['putObjectCopy', 'sliceCopyFile'].includes(api)) {
+ return 'CopyTask';
+ }
+ return api;
+};
+
+// 上报参数驼峰改下划线
+function camel2underline(key) {
+ return key.replace(/([A-Z])/g, '_$1').toLowerCase();
+}
+function formatParams(params) {
+ var formattedParams = {};
+ var successKeys = ['sdkVersionName', 'sdkVersionCode', 'osName', 'networkType', 'requestName', 'requestResult', 'bucket', 'region', 'appid', 'accelerate', 'url', 'host', 'requestPath', 'userAgent', 'httpMethod', 'httpSize', 'httpSpeed', 'httpTookTime', 'httpMd5', 'httpSign', 'httpFullTime', 'httpDomain', 'partNumber', 'httpRetryTimes', 'customId', 'traceId', 'realApi'];
+ var failureKeys = [].concat(successKeys, ['errorNode', 'errorCode', 'errorName', 'errorMessage', 'errorRequestId', 'errorHttpCode', 'errorServiceName', 'errorType', 'fullError']);
+ // 需要上报的参数字段
+ var reporterKeys = params.requestResult === 'Success' ? successKeys : failureKeys;
+ for (var key in params) {
+ if (!reporterKeys.includes(key)) continue;
+ var formattedKey = camel2underline(key);
+ formattedParams[formattedKey] = params[key];
+ }
+ formattedParams['request_name'] = params.realApi ? transApiName(params.realApi) : params.requestName;
+ return formattedParams;
+}
+
+// 链路追踪器
+var Tracker = /*#__PURE__*/function () {
+ function Tracker(opt) {
+ var _this$params;
+ _classCallCheck(this, Tracker);
+ var parent = opt.parent,
+ traceId = opt.traceId,
+ bucket = opt.bucket,
+ region = opt.region,
+ apiName = opt.apiName,
+ realApi = opt.realApi,
+ httpMethod = opt.httpMethod,
+ fileKey = opt.fileKey,
+ fileSize = opt.fileSize,
+ accelerate = opt.accelerate,
+ customId = opt.customId,
+ delay = opt.delay,
+ deepTracker = opt.deepTracker,
+ Beacon = opt.Beacon,
+ clsReporter = opt.clsReporter;
+ var appid = bucket && bucket.substr(bucket.lastIndexOf('-') + 1) || '';
+ this.parent = parent;
+ this.deepTracker = deepTracker;
+ this.delay = delay;
+ if (clsReporter && !this.clsReporter) {
+ this.clsReporter = clsReporter;
+ }
+ // 上报用到的字段
+ this.params = (_this$params = {
+ // 通用字段
+ sdkVersionName: 'cos-wx-sdk-v5',
+ sdkVersionCode: pkg.version,
+ osName: deviceInfo.devicePlatform,
+ networkType: '',
+ requestName: apiName || '',
+ requestResult: '',
+ // sdk api调用结果Success、Failure
+ realApi: realApi,
+ bucket: bucket,
+ region: region,
+ accelerate: accelerate,
+ httpMethod: httpMethod,
+ url: '',
+ host: '',
+ httpDomain: '',
+ requestPath: fileKey || '',
+ errorType: '',
+ errorCode: '',
+ errorName: '',
+ errorMessage: '',
+ errorRequestId: '',
+ errorHttpCode: 0,
+ errorServiceName: '',
+ errorNode: '',
+ httpTookTime: 0,
+ // http整体耗时
+ httpSize: fileSize || 0,
+ // 主要是文件大小,大小 B
+ httpMd5: 0,
+ // MD5耗时
+ httpSign: 0,
+ // 计算签名耗时
+ httpFullTime: 0,
+ // 任务整体耗时(包括md5、签名等)
+ httpSpeed: 0,
+ // 主要关注上传速度,KB/s
+
+ size: fileSize || 0
+ }, _defineProperty(_this$params, "httpMd5", 0), _defineProperty(_this$params, "httpSign", 0), _defineProperty(_this$params, "httpFull", 0), _defineProperty(_this$params, "name", apiName || ''), _defineProperty(_this$params, "tookTime", 0), _defineProperty(_this$params, "md5StartTime", 0), _defineProperty(_this$params, "md5EndTime", 0), _defineProperty(_this$params, "signStartTime", 0), _defineProperty(_this$params, "signEndTime", 0), _defineProperty(_this$params, "httpStartTime", 0), _defineProperty(_this$params, "httpEndTime", 0), _defineProperty(_this$params, "startTime", new Date().getTime()), _defineProperty(_this$params, "endTime", 0), _defineProperty(_this$params, "traceId", traceId || utils.getUid()), _defineProperty(_this$params, "appid", appid), _defineProperty(_this$params, "partNumber", 0), _defineProperty(_this$params, "httpRetryTimes", 0), _defineProperty(_this$params, "customId", customId || ''), _defineProperty(_this$params, "partTime", 0), _this$params);
+ if (Beacon) {
+ this.beacon = getBeacon(Beacon, delay);
+ }
+ }
+
+ // 格式化sdk回调
+ _createClass(Tracker, [{
+ key: "formatResult",
+ value: function () {
+ var _formatResult = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(err, data) {
+ var _err$error, _err$error$error, _err$error2, _err$error2$error, _err$error3, _err$error4, _err$error5, _err$error5$error, _err$error6;
+ var now, networkType, errorCode, errorMessage, errorName, errorHttpCode, errorServiceName, requestId, errorType, isSliceUploadFile, isSliceCopyFile, speed, httpFullTime, httpTookTime, _speed, httpMd5, httpSign, exec;
+ return _regeneratorRuntime().wrap(function _callee$(_context) {
+ while (1) switch (_context.prev = _context.next) {
+ case 0:
+ /**
+ * 解析到err的格式为:
+ * 1.服务端有返回时
+ * {
+ * err: 同下方error,
+ * error: {
+ * error: {
+ * Code: '', Message: '', Resource: '', RequestId: '', TraceId: '',
+ * },
+ * statusCode: xxx,
+ * headers: {},
+ * RequestId: '',
+ * },
+ * }
+ * 2.本地抛出或小程序直接报错
+ * {error: 'message'}或{error: {error: 'message' }}
+ */
+ now = new Date().getTime();
+ _context.next = 3;
+ return utils.getNetType();
+ case 3:
+ networkType = _context.sent;
+ errorCode = err ? (err === null || err === void 0 ? void 0 : (_err$error = err.error) === null || _err$error === void 0 ? void 0 : (_err$error$error = _err$error.error) === null || _err$error$error === void 0 ? void 0 : _err$error$error.Code) || 'Error' : '';
+ errorMessage = err ? (err === null || err === void 0 ? void 0 : (_err$error2 = err.error) === null || _err$error2 === void 0 ? void 0 : (_err$error2$error = _err$error2.error) === null || _err$error2$error === void 0 ? void 0 : _err$error2$error.Message) || (err === null || err === void 0 ? void 0 : (_err$error3 = err.error) === null || _err$error3 === void 0 ? void 0 : _err$error3.error) || (err === null || err === void 0 ? void 0 : err.error) || '' : '';
+ errorName = errorMessage;
+ errorHttpCode = err ? err === null || err === void 0 ? void 0 : (_err$error4 = err.error) === null || _err$error4 === void 0 ? void 0 : _err$error4.statusCode : data.statusCode;
+ errorServiceName = err ? err === null || err === void 0 ? void 0 : (_err$error5 = err.error) === null || _err$error5 === void 0 ? void 0 : (_err$error5$error = _err$error5.error) === null || _err$error5$error === void 0 ? void 0 : _err$error5$error.Resource : '';
+ requestId = err ? (err === null || err === void 0 ? void 0 : (_err$error6 = err.error) === null || _err$error6 === void 0 ? void 0 : _err$error6.RequestId) || '' : (data === null || data === void 0 ? void 0 : data.RequestId) || '';
+ errorType = err ? requestId ? 'Server' : 'Client' : '';
+ if (this.params.requestName === 'getObject') {
+ this.params.httpSize = data ? data.headers && data.headers['content-length'] : 0;
+ }
+
+ // 上报 sliceUploadFile || uploadFile || uploadFiles 命中分块上传时
+ isSliceUploadFile = this.params.realApi === 'sliceUploadFile';
+ isSliceCopyFile = this.params.realApi === 'sliceCopyFile';
+ if (isSliceUploadFile || isSliceCopyFile) {
+ speed = this.params.httpSize / 1024 / this.params.partTime;
+ Object.assign(this.params, {
+ httpSpeed: speed < 0 ? 0 : speed.toFixed(3)
+ });
+ } else {
+ httpFullTime = now - this.params.startTime;
+ httpTookTime = this.params.httpEndTime - this.params.httpStartTime;
+ _speed = this.params.httpSize / 1024 / (httpTookTime / 1000);
+ httpMd5 = this.params.md5EndTime - this.params.md5StartTime;
+ httpSign = this.params.signEndTime - this.params.signStartTime;
+ if (this.parent) {
+ this.parent.addParamValue('httpTookTime', ms2s(httpTookTime));
+ this.parent.addParamValue('httpFullTime', ms2s(httpFullTime));
+ this.parent.addParamValue('httpMd5', ms2s(httpMd5));
+ this.parent.addParamValue('httpSign', ms2s(httpSign));
+ if (['multipartUpload', 'uploadPartCopy', 'putObjectCopy'].includes(this.params.requestName)) {
+ // 只有小分块上传|复制才累计纯请求耗时,计算速度时用到
+ this.parent.addParamValue('partTime', ms2s(httpTookTime));
+ }
+ }
+ Object.assign(this.params, {
+ httpFullTime: ms2s(httpFullTime),
+ httpMd5: ms2s(httpMd5),
+ httpSign: ms2s(httpSign),
+ httpTookTime: ms2s(httpTookTime),
+ httpSpeed: _speed < 0 ? 0 : _speed.toFixed(3)
+ });
+ }
+ Object.assign(this.params, {
+ networkType: networkType,
+ requestResult: err ? 'Failure' : 'Success',
+ errorType: errorType,
+ errorCode: errorCode,
+ errorHttpCode: errorHttpCode,
+ errorName: errorName,
+ errorMessage: errorMessage,
+ errorServiceName: errorServiceName,
+ errorRequestId: requestId
+ });
+ if (err && (!errorCode || !errorMessage)) {
+ // 暂存全量err一段时间 观察是否所有err格式都可被解析
+ this.params.fullError = err ? JSON.stringify(err) : '';
+ }
+ if (this.params.name === 'getObject') {
+ this.params.size = data ? data.headers && data.headers['content-length'] : -1;
+ }
+ if (this.params.url) {
+ try {
+ exec = /^http(s)?:\/\/(.*?)\//.exec(this.params.url);
+ this.params.host = exec[2];
+ } catch (e) {
+ this.params.host = this.params.url;
+ }
+ this.params.httpDomain = this.params.host;
+ }
+ case 19:
+ case "end":
+ return _context.stop();
+ }
+ }, _callee, this);
+ }));
+ function formatResult(_x, _x2) {
+ return _formatResult.apply(this, arguments);
+ }
+ return formatResult;
+ }() // 上报
+ }, {
+ key: "report",
+ value: function () {
+ var _report = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(err, data) {
+ var formattedParams;
+ return _regeneratorRuntime().wrap(function _callee2$(_context2) {
+ while (1) switch (_context2.prev = _context2.next) {
+ case 0:
+ if (!(!this.beacon && !this.clsReporter)) {
+ _context2.next = 2;
+ break;
+ }
+ return _context2.abrupt("return");
+ case 2:
+ _context2.next = 4;
+ return this.formatResult(err, data);
+ case 4:
+ formattedParams = formatParams(this.params);
+ if (this.beacon) {
+ this.sendEventsToBeacon(formattedParams);
+ }
+ if (this.clsReporter) {
+ this.sendEventsToCLS(formattedParams);
+ }
+ case 7:
+ case "end":
+ return _context2.stop();
+ }
+ }, _callee2, this);
+ }));
+ function report(_x3, _x4) {
+ return _report.apply(this, arguments);
+ }
+ return report;
+ }() // 设置当前链路的参数
+ }, {
+ key: "setParams",
+ value: function setParams(params) {
+ Object.assign(this.params, params);
+ }
+ }, {
+ key: "addParamValue",
+ value: function addParamValue(key, value) {
+ this.params[key] = (+this.params[key] + +value).toFixed(3);
+ }
+
+ // 上报灯塔
+ }, {
+ key: "sendEventsToBeacon",
+ value: function sendEventsToBeacon(formattedParams) {
+ // DeepTracker模式下才会上报分块上传内部细节
+ var isSliceUploadFile = this.params.requestName === 'sliceUploadFile' || this.params.realApi === 'sliceUploadFile';
+ if (isSliceUploadFile && !this.deepTracker) {
+ return;
+ }
+ var eventCode = 'qcloud_track_cos_sdk';
+ if (this.delay === 0) {
+ // 实时上报
+ this.beacon && this.beacon.onDirectUserAction(eventCode, formattedParams);
+ } else {
+ // 周期性上报
+ this.beacon && this.beacon.onUserAction(eventCode, formattedParams);
+ }
+ }
+
+ // 上报 cls
+ }, {
+ key: "sendEventsToCLS",
+ value: function sendEventsToCLS(formattedParams) {
+ // 是否实时上报
+ var immediate = !!(this.delay === 0);
+ this.clsReporter.log(formattedParams, immediate);
+ }
+
+ // 生成子实例,与父所属一个链路,可用于分块上传内部流程上报单个分块操作
+ }, {
+ key: "generateSubTracker",
+ value: function generateSubTracker(subParams) {
+ Object.assign(subParams, {
+ parent: this,
+ deepTracker: this.deepTracker,
+ traceId: this.params.traceId,
+ bucket: this.params.bucket,
+ region: this.params.region,
+ accelerate: this.params.accelerate,
+ fileKey: this.params.requestPath,
+ customId: this.params.customId,
+ delay: this.params.delay,
+ clsReporter: this.clsReporter
+ });
+ return new Tracker(subParams);
+ }
+ }]);
+ return Tracker;
+}();
+module.exports = Tracker;
+
+/***/ }),
+
+/***/ "./src/util.js":
+/*!*********************!*\
+ !*** ./src/util.js ***!
+ \*********************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; }
+function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
+function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
+function _regeneratorRuntime() { "use strict"; /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ _regeneratorRuntime = function _regeneratorRuntime() { return e; }; var t, e = {}, r = Object.prototype, n = r.hasOwnProperty, o = Object.defineProperty || function (t, e, r) { t[e] = r.value; }, i = "function" == typeof Symbol ? Symbol : {}, a = i.iterator || "@@iterator", c = i.asyncIterator || "@@asyncIterator", u = i.toStringTag || "@@toStringTag"; function define(t, e, r) { return Object.defineProperty(t, e, { value: r, enumerable: !0, configurable: !0, writable: !0 }), t[e]; } try { define({}, ""); } catch (t) { define = function define(t, e, r) { return t[e] = r; }; } function wrap(t, e, r, n) { var i = e && e.prototype instanceof Generator ? e : Generator, a = Object.create(i.prototype), c = new Context(n || []); return o(a, "_invoke", { value: makeInvokeMethod(t, r, c) }), a; } function tryCatch(t, e, r) { try { return { type: "normal", arg: t.call(e, r) }; } catch (t) { return { type: "throw", arg: t }; } } e.wrap = wrap; var h = "suspendedStart", l = "suspendedYield", f = "executing", s = "completed", y = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} var p = {}; define(p, a, function () { return this; }); var d = Object.getPrototypeOf, v = d && d(d(values([]))); v && v !== r && n.call(v, a) && (p = v); var g = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(p); function defineIteratorMethods(t) { ["next", "throw", "return"].forEach(function (e) { define(t, e, function (t) { return this._invoke(e, t); }); }); } function AsyncIterator(t, e) { function invoke(r, o, i, a) { var c = tryCatch(t[r], t, o); if ("throw" !== c.type) { var u = c.arg, h = u.value; return h && "object" == _typeof(h) && n.call(h, "__await") ? e.resolve(h.__await).then(function (t) { invoke("next", t, i, a); }, function (t) { invoke("throw", t, i, a); }) : e.resolve(h).then(function (t) { u.value = t, i(u); }, function (t) { return invoke("throw", t, i, a); }); } a(c.arg); } var r; o(this, "_invoke", { value: function value(t, n) { function callInvokeWithMethodAndArg() { return new e(function (e, r) { invoke(t, n, e, r); }); } return r = r ? r.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); } }); } function makeInvokeMethod(e, r, n) { var o = h; return function (i, a) { if (o === f) throw Error("Generator is already running"); if (o === s) { if ("throw" === i) throw a; return { value: t, done: !0 }; } for (n.method = i, n.arg = a;;) { var c = n.delegate; if (c) { var u = maybeInvokeDelegate(c, n); if (u) { if (u === y) continue; return u; } } if ("next" === n.method) n.sent = n._sent = n.arg;else if ("throw" === n.method) { if (o === h) throw o = s, n.arg; n.dispatchException(n.arg); } else "return" === n.method && n.abrupt("return", n.arg); o = f; var p = tryCatch(e, r, n); if ("normal" === p.type) { if (o = n.done ? s : l, p.arg === y) continue; return { value: p.arg, done: n.done }; } "throw" === p.type && (o = s, n.method = "throw", n.arg = p.arg); } }; } function maybeInvokeDelegate(e, r) { var n = r.method, o = e.iterator[n]; if (o === t) return r.delegate = null, "throw" === n && e.iterator["return"] && (r.method = "return", r.arg = t, maybeInvokeDelegate(e, r), "throw" === r.method) || "return" !== n && (r.method = "throw", r.arg = new TypeError("The iterator does not provide a '" + n + "' method")), y; var i = tryCatch(o, e.iterator, r.arg); if ("throw" === i.type) return r.method = "throw", r.arg = i.arg, r.delegate = null, y; var a = i.arg; return a ? a.done ? (r[e.resultName] = a.value, r.next = e.nextLoc, "return" !== r.method && (r.method = "next", r.arg = t), r.delegate = null, y) : a : (r.method = "throw", r.arg = new TypeError("iterator result is not an object"), r.delegate = null, y); } function pushTryEntry(t) { var e = { tryLoc: t[0] }; 1 in t && (e.catchLoc = t[1]), 2 in t && (e.finallyLoc = t[2], e.afterLoc = t[3]), this.tryEntries.push(e); } function resetTryEntry(t) { var e = t.completion || {}; e.type = "normal", delete e.arg, t.completion = e; } function Context(t) { this.tryEntries = [{ tryLoc: "root" }], t.forEach(pushTryEntry, this), this.reset(!0); } function values(e) { if (e || "" === e) { var r = e[a]; if (r) return r.call(e); if ("function" == typeof e.next) return e; if (!isNaN(e.length)) { var o = -1, i = function next() { for (; ++o < e.length;) if (n.call(e, o)) return next.value = e[o], next.done = !1, next; return next.value = t, next.done = !0, next; }; return i.next = i; } } throw new TypeError(_typeof(e) + " is not iterable"); } return GeneratorFunction.prototype = GeneratorFunctionPrototype, o(g, "constructor", { value: GeneratorFunctionPrototype, configurable: !0 }), o(GeneratorFunctionPrototype, "constructor", { value: GeneratorFunction, configurable: !0 }), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, u, "GeneratorFunction"), e.isGeneratorFunction = function (t) { var e = "function" == typeof t && t.constructor; return !!e && (e === GeneratorFunction || "GeneratorFunction" === (e.displayName || e.name)); }, e.mark = function (t) { return Object.setPrototypeOf ? Object.setPrototypeOf(t, GeneratorFunctionPrototype) : (t.__proto__ = GeneratorFunctionPrototype, define(t, u, "GeneratorFunction")), t.prototype = Object.create(g), t; }, e.awrap = function (t) { return { __await: t }; }, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, c, function () { return this; }), e.AsyncIterator = AsyncIterator, e.async = function (t, r, n, o, i) { void 0 === i && (i = Promise); var a = new AsyncIterator(wrap(t, r, n, o), i); return e.isGeneratorFunction(r) ? a : a.next().then(function (t) { return t.done ? t.value : a.next(); }); }, defineIteratorMethods(g), define(g, u, "Generator"), define(g, a, function () { return this; }), define(g, "toString", function () { return "[object Generator]"; }), e.keys = function (t) { var e = Object(t), r = []; for (var n in e) r.push(n); return r.reverse(), function next() { for (; r.length;) { var t = r.pop(); if (t in e) return next.value = t, next.done = !1, next; } return next.done = !0, next; }; }, e.values = values, Context.prototype = { constructor: Context, reset: function reset(e) { if (this.prev = 0, this.next = 0, this.sent = this._sent = t, this.done = !1, this.delegate = null, this.method = "next", this.arg = t, this.tryEntries.forEach(resetTryEntry), !e) for (var r in this) "t" === r.charAt(0) && n.call(this, r) && !isNaN(+r.slice(1)) && (this[r] = t); }, stop: function stop() { this.done = !0; var t = this.tryEntries[0].completion; if ("throw" === t.type) throw t.arg; return this.rval; }, dispatchException: function dispatchException(e) { if (this.done) throw e; var r = this; function handle(n, o) { return a.type = "throw", a.arg = e, r.next = n, o && (r.method = "next", r.arg = t), !!o; } for (var o = this.tryEntries.length - 1; o >= 0; --o) { var i = this.tryEntries[o], a = i.completion; if ("root" === i.tryLoc) return handle("end"); if (i.tryLoc <= this.prev) { var c = n.call(i, "catchLoc"), u = n.call(i, "finallyLoc"); if (c && u) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } else if (c) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); } else { if (!u) throw Error("try statement without catch or finally"); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } } } }, abrupt: function abrupt(t, e) { for (var r = this.tryEntries.length - 1; r >= 0; --r) { var o = this.tryEntries[r]; if (o.tryLoc <= this.prev && n.call(o, "finallyLoc") && this.prev < o.finallyLoc) { var i = o; break; } } i && ("break" === t || "continue" === t) && i.tryLoc <= e && e <= i.finallyLoc && (i = null); var a = i ? i.completion : {}; return a.type = t, a.arg = e, i ? (this.method = "next", this.next = i.finallyLoc, y) : this.complete(a); }, complete: function complete(t, e) { if ("throw" === t.type) throw t.arg; return "break" === t.type || "continue" === t.type ? this.next = t.arg : "return" === t.type ? (this.rval = this.arg = t.arg, this.method = "return", this.next = "end") : "normal" === t.type && e && (this.next = e), y; }, finish: function finish(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.finallyLoc === t) return this.complete(r.completion, r.afterLoc), resetTryEntry(r), y; } }, "catch": function _catch(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.tryLoc === t) { var n = r.completion; if ("throw" === n.type) { var o = n.arg; resetTryEntry(r); } return o; } } throw Error("illegal catch attempt"); }, delegateYield: function delegateYield(e, r, n) { return this.delegate = { iterator: values(e), resultName: r, nextLoc: n }, "next" === this.method && (this.arg = t), y; } }, e; }
+function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
+function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
+function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
+var md5 = __webpack_require__(/*! ../lib/md5 */ "./lib/md5.js");
+var CryptoJS = __webpack_require__(/*! ../lib/crypto */ "./lib/crypto.js");
+var base64 = __webpack_require__(/*! ../lib/base64 */ "./lib/base64.js");
+var btoa = base64.btoa;
+var wxfs = wx.getFileSystemManager();
+var Tracker = __webpack_require__(/*! ./tracker */ "./src/tracker.js");
+var _require = __webpack_require__(/*! fast-xml-parser */ "./node_modules/fast-xml-parser/src/fxp.js"),
+ XMLParser = _require.XMLParser,
+ XMLBuilder = _require.XMLBuilder;
+var xmlParser = new XMLParser({
+ ignoreDeclaration: true,
+ // 忽略 XML 声明
+ ignoreAttributes: true,
+ // 忽略属性
+ parseTagValue: false,
+ // 关闭自动解析
+ trimValues: false // 关闭默认 trim
+});
+var xmlBuilder = new XMLBuilder();
+
+// 删掉不需要的#text
+var textNodeName = '#text';
+var deleteTextNodes = function deleteTextNodes(obj) {
+ if (!isObject(obj)) return;
+ for (var i in obj) {
+ var item = obj[i];
+ if (typeof item === 'string') {
+ if (i === textNodeName) {
+ delete obj[i];
+ }
+ } else if (Array.isArray(item)) {
+ item.forEach(function (i) {
+ deleteTextNodes(i);
+ });
+ } else if (isObject(item)) {
+ deleteTextNodes(item);
+ }
+ }
+};
+
+// XML 对象转 JSON 对象
+var xml2json = function xml2json(bodyStr) {
+ var json = xmlParser.parse(bodyStr);
+ deleteTextNodes(json);
+ return json;
+};
+
+// JSON 对象转 XML 对象
+var json2xml = function json2xml(json) {
+ var xml = xmlBuilder.build(json);
+ return xml;
+};
+function camSafeUrlEncode(str) {
+ return encodeURIComponent(str).replace(/!/g, '%21').replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29').replace(/\*/g, '%2A');
+}
+function getObjectKeys(obj, forKey) {
+ var list = [];
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ list.push(forKey ? camSafeUrlEncode(key).toLowerCase() : key);
+ }
+ }
+ return list.sort(function (a, b) {
+ a = a.toLowerCase();
+ b = b.toLowerCase();
+ return a === b ? 0 : a > b ? 1 : -1;
+ });
+}
+
+/**
+ * obj转为string
+ * @param {Object} obj 需要转的对象,必须
+ * @param {Boolean} lowerCaseKey key是否转为小写,默认false,非必须
+ * @return {String} data 返回字符串
+ */
+var obj2str = function obj2str(obj, lowerCaseKey) {
+ var i, key, val;
+ var list = [];
+ var keyList = getObjectKeys(obj);
+ for (i = 0; i < keyList.length; i++) {
+ key = keyList[i];
+ val = obj[key] === undefined || obj[key] === null ? '' : '' + obj[key];
+ key = lowerCaseKey ? camSafeUrlEncode(key).toLowerCase() : camSafeUrlEncode(key);
+ val = camSafeUrlEncode(val) || '';
+ list.push(key + '=' + val);
+ }
+ return list.join('&');
+};
+
+// 可以签入签名的headers
+var signHeaders = ['cache-control', 'content-disposition', 'content-encoding', 'content-length', 'content-md5', 'content-type', 'expect', 'expires', 'host', 'if-match', 'if-modified-since', 'if-none-match', 'if-unmodified-since', 'origin', 'range', 'transfer-encoding', 'pic-operations'];
+var getSignHeaderObj = function getSignHeaderObj(headers) {
+ var signHeaderObj = {};
+ for (var i in headers) {
+ var key = i.toLowerCase();
+ if (key.indexOf('x-cos-') > -1 || key.indexOf('x-ci-') > -1 || signHeaders.indexOf(key) > -1) {
+ signHeaderObj[i] = headers[i];
+ }
+ }
+ return signHeaderObj;
+};
+
+//测试用的key后面可以去掉
+var getAuth = function getAuth(opt) {
+ opt = opt || {};
+ var SecretId = opt.SecretId;
+ var SecretKey = opt.SecretKey;
+ var KeyTime = opt.KeyTime;
+ var method = (opt.method || opt.Method || 'get').toLowerCase();
+ var queryParams = clone(opt.Query || opt.params || {});
+ var headers = getSignHeaderObj(clone(opt.Headers || opt.headers || {}));
+ var Key = opt.Key || '';
+ var pathname;
+ if (opt.UseRawKey) {
+ pathname = opt.Pathname || opt.pathname || '/' + Key;
+ } else {
+ pathname = opt.Pathname || opt.pathname || Key;
+ pathname.indexOf('/') !== 0 && (pathname = '/' + pathname);
+ }
+
+ // ForceSignHost明确传入false才不加入host签名
+ var forceSignHost = opt.ForceSignHost === false ? false : true;
+
+ // 如果有传入存储桶,那么签名默认加 Host 参与计算,避免跨桶访问
+ if (!headers.Host && !headers.host && opt.Bucket && opt.Region && forceSignHost) headers.Host = opt.Bucket + '.cos.' + opt.Region + '.myqcloud.com';
+ if (!SecretId) return console.error('missing param SecretId');
+ if (!SecretKey) return console.error('missing param SecretKey');
+
+ // 签名有效起止时间
+ var now = Math.round(getSkewTime(opt.SystemClockOffset) / 1000) - 1;
+ var exp = now;
+ var Expires = opt.Expires || opt.expires;
+ if (Expires === undefined) {
+ exp += 900; // 签名过期时间为当前 + 900s
+ } else {
+ exp += Expires * 1 || 0;
+ }
+
+ // 要用到的 Authorization 参数列表
+ var qSignAlgorithm = 'sha1';
+ var qAk = SecretId;
+ var qSignTime = KeyTime || now + ';' + exp;
+ var qKeyTime = KeyTime || now + ';' + exp;
+ var qHeaderList = getObjectKeys(headers, true).join(';').toLowerCase();
+ var qUrlParamList = getObjectKeys(queryParams, true).join(';').toLowerCase();
+
+ // 签名算法说明文档:https://www.qcloud.com/document/product/436/7778
+ // 步骤一:计算 SignKey
+ var signKey = CryptoJS.HmacSHA1(qKeyTime, SecretKey).toString();
+
+ // 步骤二:构成 FormatString
+ var formatString = [method, pathname, util.obj2str(queryParams, true), util.obj2str(headers, true), ''].join('\n');
+
+ // 步骤三:计算 StringToSign
+ var stringToSign = ['sha1', qSignTime, CryptoJS.SHA1(formatString).toString(), ''].join('\n');
+
+ // 步骤四:计算 Signature
+ var qSignature = CryptoJS.HmacSHA1(stringToSign, signKey).toString();
+
+ // 步骤五:构造 Authorization
+ var authorization = ['q-sign-algorithm=' + qSignAlgorithm, 'q-ak=' + qAk, 'q-sign-time=' + qSignTime, 'q-key-time=' + qKeyTime, 'q-header-list=' + qHeaderList, 'q-url-param-list=' + qUrlParamList, 'q-signature=' + qSignature].join('&');
+ return authorization;
+};
+var getSourceParams = function getSourceParams(source) {
+ var parser = this.options.CopySourceParser;
+ if (parser) return parser(source);
+ var m = source.match(/^([^.]+-\d+)\.cos(v6|-cdc|-internal)?\.([^.]+)\.((myqcloud\.com)|(tencentcos\.cn))\/(.+)$/);
+ if (!m) return null;
+ return {
+ Bucket: m[1],
+ Region: m[3],
+ Key: m[7]
+ };
+};
+var noop = function noop() {};
+
+// 清除对象里值为的 undefined 或 null 的属性
+var clearKey = function clearKey(obj) {
+ var retObj = {};
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key) && obj[key] !== undefined && obj[key] !== null) {
+ retObj[key] = obj[key];
+ }
+ }
+ return retObj;
+};
+
+// 获取文件分片
+var fileSlice = function fileSlice(FilePath, start, end, callback) {
+ if (FilePath) {
+ wxfs.readFile({
+ filePath: FilePath,
+ position: start,
+ length: end - start,
+ success: function success(res) {
+ callback(res.data);
+ },
+ fail: function fail() {
+ callback(null);
+ }
+ });
+ } else {
+ callback(null);
+ }
+};
+
+// 获取文件内容的 MD5
+var getBodyMd5 = function getBodyMd5(UploadCheckContentMd5, Body, callback) {
+ callback = callback || noop;
+ if (UploadCheckContentMd5) {
+ if (Body && Body instanceof ArrayBuffer) {
+ util.getFileMd5(Body, function (err, md5) {
+ callback(md5);
+ });
+ } else {
+ callback();
+ }
+ } else {
+ callback();
+ }
+};
+
+// 获取文件 md5 值
+var getFileMd5 = function getFileMd5(body, callback) {
+ var hash = md5(body);
+ callback && callback(hash);
+ return hash;
+};
+function clone(obj) {
+ return map(obj, function (v) {
+ return _typeof(v) === 'object' && v !== null ? clone(v) : v;
+ });
+}
+function attr(obj, name, defaultValue) {
+ return obj && name in obj ? obj[name] : defaultValue;
+}
+function extend(target, source) {
+ each(source, function (val, key) {
+ target[key] = source[key];
+ });
+ return target;
+}
+function isArray(arr) {
+ return arr instanceof Array;
+}
+function isObject(obj) {
+ return Object.prototype.toString.call(obj) === '[object Object]';
+}
+function isInArray(arr, item) {
+ var flag = false;
+ for (var i = 0; i < arr.length; i++) {
+ if (item === arr[i]) {
+ flag = true;
+ break;
+ }
+ }
+ return flag;
+}
+function makeArray(arr) {
+ return isArray(arr) ? arr : [arr];
+}
+function each(obj, fn) {
+ for (var i in obj) {
+ if (obj.hasOwnProperty(i)) {
+ fn(obj[i], i);
+ }
+ }
+}
+function map(obj, fn) {
+ var o = isArray(obj) ? [] : {};
+ for (var i in obj) {
+ if (obj.hasOwnProperty(i)) {
+ o[i] = fn(obj[i], i);
+ }
+ }
+ return o;
+}
+function filter(obj, fn) {
+ var iaArr = isArray(obj);
+ var o = iaArr ? [] : {};
+ for (var i in obj) {
+ if (obj.hasOwnProperty(i)) {
+ if (fn(obj[i], i)) {
+ if (iaArr) {
+ o.push(obj[i]);
+ } else {
+ o[i] = obj[i];
+ }
+ }
+ }
+ }
+ return o;
+}
+var binaryBase64 = function binaryBase64(str) {
+ var i,
+ len,
+ _char,
+ res = '';
+ for (i = 0, len = str.length / 2; i < len; i++) {
+ _char = parseInt(str[i * 2] + str[i * 2 + 1], 16);
+ res += String.fromCharCode(_char);
+ }
+ return btoa(res);
+};
+var uuid = function uuid() {
+ var S4 = function S4() {
+ return ((1 + Math.random()) * 0x10000 | 0).toString(16).substring(1);
+ };
+ return S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4();
+};
+var hasMissingParams = function hasMissingParams(apiName, params) {
+ var Bucket = params.Bucket;
+ var Region = params.Region;
+ var Key = params.Key;
+ if (apiName.indexOf('Bucket') > -1 || apiName === 'deleteMultipleObject' || apiName === 'multipartList' || apiName === 'listObjectVersions') {
+ if (!Bucket) return 'Bucket';
+ if (!Region) return 'Region';
+ } else if (apiName.indexOf('Object') > -1 || apiName.indexOf('multipart') > -1 || apiName === 'sliceUploadFile' || apiName === 'abortUploadTask' || apiName === 'uploadFile') {
+ if (!Bucket) return 'Bucket';
+ if (!Region) return 'Region';
+ if (!Key) return 'Key';
+ }
+ return false;
+};
+var formatParams = function formatParams(apiName, params) {
+ // 复制参数对象
+ params = extend({}, params);
+
+ // 统一处理 Headers
+ if (apiName !== 'getAuth' && apiName !== 'getV4Auth' && apiName !== 'getObjectUrl') {
+ var Headers = params.Headers || {};
+ if (params && _typeof(params) === 'object') {
+ (function () {
+ for (var key in params) {
+ if (params.hasOwnProperty(key) && key.indexOf('x-cos-') > -1) {
+ Headers[key] = params[key];
+ }
+ }
+ })();
+ var headerMap = {
+ // params headers
+ 'x-cos-mfa': 'MFA',
+ 'Content-MD5': 'ContentMD5',
+ 'Content-Length': 'ContentLength',
+ 'Content-Type': 'ContentType',
+ Expect: 'Expect',
+ Expires: 'Expires',
+ 'Cache-Control': 'CacheControl',
+ 'Content-Disposition': 'ContentDisposition',
+ 'Content-Encoding': 'ContentEncoding',
+ Range: 'Range',
+ 'If-Modified-Since': 'IfModifiedSince',
+ 'If-Unmodified-Since': 'IfUnmodifiedSince',
+ 'If-Match': 'IfMatch',
+ 'If-None-Match': 'IfNoneMatch',
+ 'x-cos-copy-source': 'CopySource',
+ 'x-cos-copy-source-Range': 'CopySourceRange',
+ 'x-cos-metadata-directive': 'MetadataDirective',
+ 'x-cos-copy-source-If-Modified-Since': 'CopySourceIfModifiedSince',
+ 'x-cos-copy-source-If-Unmodified-Since': 'CopySourceIfUnmodifiedSince',
+ 'x-cos-copy-source-If-Match': 'CopySourceIfMatch',
+ 'x-cos-copy-source-If-None-Match': 'CopySourceIfNoneMatch',
+ 'x-cos-acl': 'ACL',
+ 'x-cos-grant-read': 'GrantRead',
+ 'x-cos-grant-write': 'GrantWrite',
+ 'x-cos-grant-full-control': 'GrantFullControl',
+ 'x-cos-grant-read-acp': 'GrantReadAcp',
+ 'x-cos-grant-write-acp': 'GrantWriteAcp',
+ 'x-cos-storage-class': 'StorageClass',
+ 'x-cos-traffic-limit': 'TrafficLimit',
+ 'x-cos-mime-limit': 'MimeLimit',
+ 'x-cos-forbid-overwrite': 'ForbidOverwrite',
+ // SSE-C
+ 'x-cos-server-side-encryption-customer-algorithm': 'SSECustomerAlgorithm',
+ 'x-cos-server-side-encryption-customer-key': 'SSECustomerKey',
+ 'x-cos-server-side-encryption-customer-key-MD5': 'SSECustomerKeyMD5',
+ // SSE-COS、SSE-KMS
+ 'x-cos-server-side-encryption': 'ServerSideEncryption',
+ 'x-cos-server-side-encryption-cos-kms-key-id': 'SSEKMSKeyId',
+ 'x-cos-server-side-encryption-context': 'SSEContext',
+ // 上传时图片处理
+ 'Pic-Operations': 'PicOperations'
+ };
+ util.each(headerMap, function (paramKey, headerKey) {
+ if (params[paramKey] !== undefined) {
+ Headers[headerKey] = params[paramKey];
+ }
+ });
+ params.Headers = clearKey(Headers);
+ }
+ }
+ return params;
+};
+var apiWrapper = function apiWrapper(apiName, apiFn) {
+ return function (params, callback) {
+ var self = this;
+
+ // 处理参数
+ if (typeof params === 'function') {
+ callback = params;
+ params = {};
+ }
+
+ // 整理参数格式
+ params = formatParams(apiName, params);
+
+ // tracker传递
+ var tracker;
+ if (self.options.EnableReporter) {
+ if (params.calledBySdk === 'sliceUploadFile' || params.calledBySdk === 'sliceCopyFile') {
+ // 分块上传内部方法使用sliceUploadFile的子链路
+ tracker = params.tracker && params.tracker.generateSubTracker({
+ apiName: apiName
+ });
+ } else if (['uploadFile', 'uploadFiles'].includes(apiName)) {
+ // uploadFile、uploadFiles方法在内部处理,此处不处理
+ tracker = null;
+ } else {
+ var fileSize = 0;
+ if (params.Body) {
+ fileSize = typeof params.Body === 'string' ? params.Body.length : params.Body.size || params.Body.byteLength || 0;
+ }
+ var accelerate = self.options.UseAccelerate || typeof self.options.Domain === 'string' && self.options.Domain.includes('accelerate.');
+ tracker = new Tracker({
+ Beacon: self.options.BeaconReporter,
+ clsReporter: self.options.ClsReporter,
+ bucket: params.Bucket,
+ region: params.Region,
+ apiName: apiName,
+ realApi: apiName,
+ accelerate: accelerate,
+ fileKey: params.Key,
+ fileSize: fileSize,
+ deepTracker: self.options.DeepTracker,
+ customId: self.options.CustomId,
+ delay: self.options.TrackerDelay
+ });
+ }
+ }
+ params.tracker = tracker;
+
+ // 代理回调函数
+ var formatResult = function formatResult(result) {
+ if (result && result.headers) {
+ result.headers['x-ci-request-id'] && (result.RequestId = result.headers['x-ci-request-id']);
+ result.headers['x-cos-request-id'] && (result.RequestId = result.headers['x-cos-request-id']);
+ result.headers['x-cos-version-id'] && (result.VersionId = result.headers['x-cos-version-id']);
+ result.headers['x-cos-delete-marker'] && (result.DeleteMarker = result.headers['x-cos-delete-marker']);
+ }
+ return result;
+ };
+ var _callback = function _callback(err, data) {
+ // 格式化上报参数并上报
+ tracker && tracker.report(err, data);
+ callback && callback(formatResult(err), formatResult(data));
+ };
+ var checkParams = function checkParams() {
+ if (apiName !== 'getService' && apiName !== 'abortUploadTask') {
+ // 判断参数是否完整
+ var missingResult = hasMissingParams(apiName, params);
+ if (missingResult) {
+ return 'missing param ' + missingResult;
+ }
+ // 判断 region 格式
+ if (params.Region) {
+ if (params.Region.indexOf('cos.') > -1) {
+ return 'param Region should not be start with "cos."';
+ } else if (!/^([a-z\d-]+)$/.test(params.Region)) {
+ return 'Region format error.';
+ }
+ // 判断 region 格式
+ if (!self.options.CompatibilityMode && params.Region.indexOf('-') === -1 && params.Region !== 'yfb' && params.Region !== 'default' && params.Region !== 'accelerate') {
+ console.warn('warning: param Region format error, find help here: https://cloud.tencent.com/document/product/436/6224');
+ }
+ }
+ // 兼容不带 AppId 的 Bucket
+ if (params.Bucket) {
+ if (!/^([a-z\d-]+)-(\d+)$/.test(params.Bucket)) {
+ if (params.AppId) {
+ params.Bucket = params.Bucket + '-' + params.AppId;
+ } else if (self.options.AppId) {
+ params.Bucket = params.Bucket + '-' + self.options.AppId;
+ } else {
+ return 'Bucket should format as "test-1250000000".';
+ }
+ }
+ if (params.AppId) {
+ console.warn('warning: AppId has been deprecated, Please put it at the end of parameter Bucket(E.g Bucket:"test-1250000000" ).');
+ delete params.AppId;
+ }
+ }
+ // 如果 Key 是 / 开头,强制去掉第一个 /
+ if (params.Key && params.Key.substr(0, 1) === '/') {
+ params.Key = params.Key.substr(1);
+ }
+ }
+ };
+ var errMsg = checkParams();
+ var isSync = ['getAuth', 'getObjectUrl'].includes(apiName);
+ if (!isSync && !callback) {
+ return new Promise(function (resolve, reject) {
+ callback = function callback(err, data) {
+ err ? reject(err) : resolve(data);
+ };
+ if (errMsg) return _callback({
+ error: errMsg
+ });
+ apiFn.call(self, params, _callback);
+ });
+ } else {
+ if (errMsg) return _callback({
+ error: errMsg
+ });
+ var res = apiFn.call(self, params, _callback);
+ if (isSync) return res;
+ }
+ };
+};
+var throttleOnProgress = function throttleOnProgress(total, onProgress) {
+ var self = this;
+ var size0 = 0;
+ var size1 = 0;
+ var time0 = Date.now();
+ var time1;
+ var timer;
+ function update() {
+ timer = 0;
+ if (onProgress && typeof onProgress === 'function') {
+ time1 = Date.now();
+ var speed = Math.max(0, Math.round((size1 - size0) / ((time1 - time0) / 1000) * 100) / 100) || 0;
+ var percent;
+ if (size1 === 0 && total === 0) {
+ percent = 1;
+ } else {
+ percent = Math.floor(size1 / total * 100) / 100 || 0;
+ }
+ time0 = time1;
+ size0 = size1;
+ try {
+ onProgress({
+ loaded: size1,
+ total: total,
+ speed: speed,
+ percent: percent
+ });
+ } catch (e) {}
+ }
+ }
+ return function (info, immediately) {
+ if (info) {
+ size1 = info.loaded;
+ total = info.total;
+ }
+ if (immediately) {
+ clearTimeout(timer);
+ update();
+ } else {
+ if (timer) return;
+ timer = setTimeout(update, self.options.ProgressInterval);
+ }
+ };
+};
+
+// 通过FilePath获取上传文件的内容
+var getFileBody = function getFileBody(FilePath) {
+ return new Promise(function (resolve, reject) {
+ wxfs.readFile({
+ filePath: FilePath,
+ success: function success(res) {
+ resolve(res.data);
+ },
+ fail: function fail(res) {
+ reject((res === null || res === void 0 ? void 0 : res.errMsg) || '');
+ }
+ });
+ });
+};
+var getFileSize = /*#__PURE__*/function () {
+ var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(api, params, callback) {
+ return _regeneratorRuntime().wrap(function _callee$(_context) {
+ while (1) switch (_context.prev = _context.next) {
+ case 0:
+ if (!(api === 'postObject')) {
+ _context.next = 4;
+ break;
+ }
+ callback();
+ _context.next = 21;
+ break;
+ case 4:
+ if (!(api === 'putObject')) {
+ _context.next = 20;
+ break;
+ }
+ if (!(params.Body === undefined && params.FilePath)) {
+ _context.next = 17;
+ break;
+ }
+ _context.prev = 6;
+ _context.next = 9;
+ return getFileBody(params.FilePath);
+ case 9:
+ params.Body = _context.sent;
+ _context.next = 17;
+ break;
+ case 12:
+ _context.prev = 12;
+ _context.t0 = _context["catch"](6);
+ params.Body = undefined;
+ callback({
+ error: "readFile error, ".concat(_context.t0)
+ });
+ return _context.abrupt("return");
+ case 17:
+ if (params.Body !== undefined) {
+ params.ContentLength = params.Body.byteLength;
+ callback(null, params.ContentLength);
+ } else {
+ callback({
+ error: 'missing param Body'
+ });
+ }
+ _context.next = 21;
+ break;
+ case 20:
+ if (params.FilePath) {
+ wxfs.stat({
+ path: params.FilePath,
+ success: function success(res) {
+ var stats = res.stats;
+ params.FileStat = stats;
+ params.FileStat.FilePath = params.FilePath;
+ var size = stats.isDirectory() ? 0 : stats.size;
+ params.ContentLength = size = size || 0;
+ callback(null, size);
+ },
+ fail: function fail(err) {
+ callback(err);
+ }
+ });
+ } else {
+ callback({
+ error: 'missing param FilePath'
+ });
+ }
+ case 21:
+ case "end":
+ return _context.stop();
+ }
+ }, _callee, null, [[6, 12]]);
+ }));
+ return function getFileSize(_x, _x2, _x3) {
+ return _ref.apply(this, arguments);
+ };
+}();
+
+// 通过FilePath获取上传文件的大小
+var getFileSizeByPath = function getFileSizeByPath(filePath) {
+ return new Promise(function (resolve, reject) {
+ wxfs.stat({
+ path: filePath,
+ success: function success(res) {
+ var stats = res.stats;
+ var size = stats.isDirectory() ? 0 : stats.size;
+ resolve(size);
+ },
+ fail: function fail(res) {
+ reject((res === null || res === void 0 ? void 0 : res.errMsg) || '');
+ }
+ });
+ });
+};
+var getSkewTime = function getSkewTime(offset) {
+ return Date.now() + (offset || 0);
+};
+var compareVersion = function compareVersion(v1, v2) {
+ if (!v1 || !v2) return -1;
+ v1 = v1.split('.');
+ v2 = v2.split('.');
+ var len = Math.max(v1.length, v2.length);
+ while (v1.length < len) {
+ v1.push('0');
+ }
+ while (v2.length < len) {
+ v2.push('0');
+ }
+ for (var i = 0; i < len; i++) {
+ var num1 = parseInt(v1[i]);
+ var num2 = parseInt(v2[i]);
+ if (num1 > num2) {
+ return 1;
+ } else if (num1 < num2) {
+ return -1;
+ }
+ }
+ return 0;
+};
+var canFileSlice = function () {
+ var appBaseInfo = {};
+ var deviceInfo = {};
+ if (wx.canIUse('getAppBaseInfo')) {
+ appBaseInfo = wx.getAppBaseInfo() || {};
+ }
+ if (wx.canIUse('getDeviceInfo')) {
+ deviceInfo = wx.getDeviceInfo() || {};
+ }
+ var sdkVersion = appBaseInfo.SDKVersion;
+ var platform = deviceInfo.platform;
+ var support = compareVersion(sdkVersion, '2.10.0') >= 0;
+ var needWarning = !support && platform === 'devtools';
+ return function () {
+ if (needWarning) console.warn('当前小程序版本小于 2.10.0,不支持分片上传,请更新软件。');
+ needWarning = false;
+ return support;
+ };
+}();
+var isCIHost = function isCIHost(url) {
+ return /^https?:\/\/([^/]+\.)?ci\.[^/]+/.test(url);
+};
+var error = function error(err, opt) {
+ var sourceErr = err;
+ err.message = err.message || null;
+ if (typeof opt === 'string') {
+ err.error = opt;
+ err.message = opt;
+ } else if (_typeof(opt) === 'object' && opt !== null) {
+ extend(err, opt);
+ if (opt.code || opt.name) err.code = opt.code || opt.name;
+ if (opt.message) err.message = opt.message;
+ if (opt.stack) err.stack = opt.stack;
+ }
+ if (typeof Object.defineProperty === 'function') {
+ Object.defineProperty(err, 'name', {
+ writable: true,
+ enumerable: false
+ });
+ Object.defineProperty(err, 'message', {
+ enumerable: true
+ });
+ }
+ err.name = opt && opt.name || err.name || err.code || 'Error';
+ if (!err.code) err.code = err.name;
+ if (!err.error) err.error = clone(sourceErr); // 兼容老的错误格式
+
+ return err;
+};
+var encodeBase64 = function encodeBase64(str, safe) {
+ var base64Str = base64.encode(str);
+ // 万象使用的安全base64格式需要特殊处理
+ if (safe) {
+ base64Str = base64Str.replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', '');
+ }
+ return base64Str;
+};
+var simplifyPath = function simplifyPath(path) {
+ var names = path.split('/');
+ var stack = [];
+ var _iterator = _createForOfIteratorHelper(names),
+ _step;
+ try {
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
+ var name = _step.value;
+ if (name === '..') {
+ if (stack.length) {
+ stack.pop();
+ }
+ } else if (name.length && name !== '.') {
+ stack.push(name);
+ }
+ }
+ } catch (err) {
+ _iterator.e(err);
+ } finally {
+ _iterator.f();
+ }
+ return '/' + stack.join('/');
+};
+
+// 将ArrayBuffer转换为字符串
+var arrayBufferToString = function arrayBufferToString(arrayBuffer) {
+ var decoder = new TextDecoder('utf-8');
+ return decoder.decode(arrayBuffer);
+};
+
+// 解析响应体,兼容 xml、json
+var parseResBody = function parseResBody(responseBody) {
+ var json;
+ if (responseBody && typeof responseBody === 'string') {
+ var trimBody = responseBody.trim();
+ var isXml = trimBody.indexOf('<') === 0;
+ var isJson = trimBody.indexOf('{') === 0;
+ if (isXml) {
+ // xml 解析,解析失败返回{}
+ json = util.xml2json(responseBody) || {};
+ } else if (isJson) {
+ // json解析,解析失败返回原始 Body
+ try {
+ // 替换 json 中的换行符为空格,否则解析会出错
+ var formatBody = responseBody.replace(/\n/g, ' ');
+ var parsedBody = JSON.parse(formatBody);
+ // 确保解析出 json 对象
+ if (Object.prototype.toString.call(parsedBody) === '[object Object]') {
+ json = parsedBody;
+ } else {
+ json = responseBody;
+ }
+ } catch (e) {
+ json = responseBody;
+ }
+ } else {
+ json = responseBody;
+ }
+ } else {
+ json = responseBody || {};
+ }
+ return json;
+};
+var util = {
+ noop: noop,
+ formatParams: formatParams,
+ apiWrapper: apiWrapper,
+ xml2json: xml2json,
+ json2xml: json2xml,
+ md5: md5,
+ clearKey: clearKey,
+ fileSlice: fileSlice,
+ getBodyMd5: getBodyMd5,
+ getFileMd5: getFileMd5,
+ binaryBase64: binaryBase64,
+ extend: extend,
+ isArray: isArray,
+ isInArray: isInArray,
+ makeArray: makeArray,
+ each: each,
+ map: map,
+ filter: filter,
+ clone: clone,
+ attr: attr,
+ uuid: uuid,
+ camSafeUrlEncode: camSafeUrlEncode,
+ throttleOnProgress: throttleOnProgress,
+ getFileSize: getFileSize,
+ getFileSizeByPath: getFileSizeByPath,
+ getSkewTime: getSkewTime,
+ obj2str: obj2str,
+ getAuth: getAuth,
+ compareVersion: compareVersion,
+ canFileSlice: canFileSlice,
+ isCIHost: isCIHost,
+ error: error,
+ getSourceParams: getSourceParams,
+ encodeBase64: encodeBase64,
+ simplifyPath: simplifyPath,
+ arrayBufferToString: arrayBufferToString,
+ parseResBody: parseResBody
+};
+module.exports = util;
+
+/***/ })
+
+/******/ });
+});
\ No newline at end of file
diff --git a/miniprogram/utils/group-chat.js b/miniprogram/utils/group-chat.js
index dc81e6b..f259133 100644
--- a/miniprogram/utils/group-chat.js
+++ b/miniprogram/utils/group-chat.js
@@ -8,6 +8,39 @@ const ZHUANGTAI_MAP = {
5: '已退款', 6: '退款失败', 7: '指定中', 8: '结算中',
};
+export function getZhuangtaiText(zhuangtai) {
+ if (zhuangtai == null || zhuangtai === '') return '';
+ const n = Number(zhuangtai);
+ return ZHUANGTAI_MAP[n] || ZHUANGTAI_MAP[zhuangtai] || '';
+}
+
+export function buildDashouShangjiaGroupId(dashouUid, shangjiaUid) {
+ return `group_Ds${dashouUid}_Sj${shangjiaUid}`;
+}
+
+export function buildDashouBossGroupId(dashouUid, bossUid) {
+ return `group_Ds${dashouUid}_Boss${bossUid}`;
+}
+
+export function resolveLocalGroupId(identityType, myUid, partnerUid, fadanPingtai, orderId, isCross) {
+ const cross = isCross === 1 || isCross === true || String(isCross) === '1';
+ if (cross && orderId) {
+ return `group_${orderId}`;
+ }
+ if (!myUid) return orderId ? `group_${orderId}` : null;
+ if (!partnerUid) return orderId ? `group_${orderId}` : null;
+ if (identityType === 'shangjia') {
+ return buildDashouShangjiaGroupId(partnerUid, myUid);
+ }
+ if (identityType === 'dashou') {
+ if (fadanPingtai === 2 || String(fadanPingtai) === '2') {
+ return buildDashouShangjiaGroupId(myUid, partnerUid);
+ }
+ return buildDashouBossGroupId(myUid, partnerUid);
+ }
+ return orderId ? `group_${orderId}` : null;
+}
+
export function isPairGroupId(groupId) {
return /^group_Ds\w+_(Sj|Boss)\w+$/.test(groupId || '');
}
@@ -108,6 +141,8 @@ export function parseOrderCardText(text) {
}
}
+import { getChatImageUrl } from '../static/lib/utils.js';
+
export function normalizeGroupMessage(msg) {
if (!msg) return msg;
if (msg.type === 'text' && msg.payload && msg.payload.text) {
@@ -117,6 +152,26 @@ export function normalizeGroupMessage(msg) {
msg.payload = card;
}
}
+ if (msg.type === 'custom' && msg.payload) {
+ const inner = msg.payload.payload || msg.payload;
+ if (inner && (inner.orderId || inner.dingdan_id)) {
+ msg.type = 'order';
+ msg.payload = {
+ orderId: inner.orderId || inner.dingdan_id,
+ jieshao: inner.jieshao || '',
+ jine: inner.jine || '',
+ beizhu: inner.beizhu || '',
+ zhuangtai: inner.zhuangtai,
+ nicheng: inner.nicheng || '',
+ create_time: inner.create_time || '',
+ };
+ }
+ }
+ if (msg.type === 'image') {
+ const url = getChatImageUrl(msg);
+ msg.payload = { ...(msg.payload || {}), url };
+ msg.imageUrl = url;
+ }
return msg;
}
diff --git a/miniprogram/utils/imAuth/dashoujianquan.js b/miniprogram/utils/imAuth/dashoujianquan.js
new file mode 100644
index 0000000..bf33a06
--- /dev/null
+++ b/miniprogram/utils/imAuth/dashoujianquan.js
@@ -0,0 +1,51 @@
+// utils/imAuth/dashoujianquan.js
+const imRequest = require('./imRequest.js');
+
+/**
+ * 打手 IM 权限检测
+ * POST /yonghu/dshqltqx
+ * 后端返回 { code: 0, allow: 1 } 或 { code: 0, allow: 0 }
+ */
+async function dashoujianquan(app) {
+ const uid = wx.getStorageSync('uid');
+ if (!uid) return { allowed: false, reason: '未登录' };
+
+ try {
+ const res = await imRequest({
+ url: '/yonghu/dshqltqx',
+ method: 'POST',
+ data: { uid }
+ });
+
+ if (res && res.data && res.data.code === 0) {
+ const allow = res.data.allow;
+ if (allow === 1) {
+ return { allowed: true };
+ } else {
+ await app.disconnectGoEasy();
+ wx.showToast({
+ title: '当前身份已不支持消息聊天功能,请充值会员或联系管理员后尝试',
+ icon: 'none',
+ duration: 2000,
+ mask: true
+ });
+ await new Promise(r => setTimeout(r, 2100));
+ return { allowed: false, reason: '打手消息权限被限制' };
+ }
+ }
+ throw new Error(res.data?.msg || '鉴权接口异常');
+ } catch (err) {
+ console.error('打手鉴权失败:', err);
+ await app.disconnectGoEasy();
+ wx.showToast({
+ title: '消息服务暂不可用,请稍后重试',
+ icon: 'none',
+ duration: 2000,
+ mask: true
+ });
+ await new Promise(r => setTimeout(r, 2100));
+ return { allowed: false, reason: '鉴权请求失败' };
+ }
+}
+
+module.exports = { dashoujianquan };
\ No newline at end of file
diff --git a/miniprogram/utils/imAuth/imRequest.js b/miniprogram/utils/imAuth/imRequest.js
new file mode 100644
index 0000000..728eb84
--- /dev/null
+++ b/miniprogram/utils/imAuth/imRequest.js
@@ -0,0 +1,67 @@
+// utils/imAuth/imRequest.js
+// 专用于 IM 鉴权模块的请求封装
+// 与 utils/request.js 功能完全一致,但不依赖 getApp(),API 域名硬编码
+const API_BASE_URL = 'https://6ccy.top/hqhd';
+
+function imRequest(options) {
+ const token = wx.getStorageSync('token');
+ const header = {
+ 'Content-Type': 'application/json',
+ ...options.header,
+ };
+ if (token) {
+ header['Authorization'] = 'Bearer ' + token;
+ }
+
+ return new Promise((resolve, reject) => {
+ wx.request({
+ url: API_BASE_URL + options.url,
+ method: options.method,
+ data: options.data || {},
+ header,
+ success: async (res) => {
+ if (res.statusCode === 401 || res.statusCode === 403) {
+ // 如果全局有登录 Promise 在等待,则等待完成后重试
+ const app = getApp();
+ if (app && app.globalData && app.globalData.loginPromise) {
+ try {
+ await app.globalData.loginPromise;
+ const retryRes = await imRequest(options);
+ resolve(retryRes);
+ } catch (err) {
+ wx.showModal({
+ content: '操作失败,请先登录',
+ success: (modalRes) => {
+ if (modalRes.confirm) {
+ wx.removeStorageSync('token');
+ wx.switchTab({ url: '/pages/wode/wode' });
+ }
+ },
+ });
+ resolve(res);
+ }
+ return;
+ }
+
+ // 常规未登录提示
+ wx.showModal({
+ content: '操作失败,请先登录',
+ success: (modalRes) => {
+ if (modalRes.confirm) {
+ wx.removeStorageSync('token');
+ wx.switchTab({ url: '/pages/wode/wode' });
+ }
+ },
+ });
+ resolve(res);
+ return;
+ }
+ // 正常响应
+ resolve(res);
+ },
+ fail: reject,
+ });
+ });
+}
+
+module.exports = imRequest;
\ No newline at end of file
diff --git a/miniprogram/utils/imAuth/jianquanxian.js b/miniprogram/utils/imAuth/jianquanxian.js
new file mode 100644
index 0000000..f373451
--- /dev/null
+++ b/miniprogram/utils/imAuth/jianquanxian.js
@@ -0,0 +1,58 @@
+// utils/imAuth/jianquanxian.js
+// 原:require('./laobanjianquan')
+// 改:
+const { laobanjianquan } = require('./laobanjianquan.js');
+const { dashoujianquan } = require('./dashoujianquan.js');
+
+/**
+ * IM 长连接权限统一入口
+ * 按顺序进行:
+ * 1. 检查本地 token/uid 是否存在
+ * 2. 若不存在,强制断开已有连接,弹窗提示未登录(强制阅读 2 秒)
+ * 3. 若存在,根据当前角色调用具体的鉴权模块
+ *
+ * @param {Object} app 全局 App 实例
+ * @returns {Promise<{ allowed: boolean, reason?: string }>}
+ */
+async function jianquanxian(app) {
+ // 1. 检测登录态(用 uid 或 token)
+ const token = wx.getStorageSync('token') || wx.getStorageSync('uid');
+ if (!token) {
+ // 没登录:先强制断开(如果已连接)
+ await app.disconnectGoEasy();
+ // 弹窗提示,强制阅读 2 秒
+ wx.showToast({
+ title: '您当前处于未登录状态,消息功能暂不可用',
+ icon: 'none',
+ duration: 2000,
+ mask: true
+ });
+ // 等待提示结束,确保用户看到
+ await new Promise(resolve => setTimeout(resolve, 2100));
+ return { allowed: false, reason: '未登录' };
+ }
+
+ // 2. 获取当前选中的角色
+ const role = app.globalData.currentRole || 'normal';
+
+ // 3. 根据角色分发到具体鉴权模块
+ switch (role) {
+ case 'normal': // 点单老板
+ return laobanjianquan(app);
+
+ case 'dashou': // 打手
+ return dashoujianquan(app);
+
+ // 其他角色暂未细分,统一放行(后续可新增对应文件)
+ case 'shangjia':
+ case 'guanshi':
+ case 'zuzhang':
+ case 'kaoheguan':
+ return { allowed: true };
+
+ default:
+ return { allowed: false, reason: '未知角色' };
+ }
+}
+
+module.exports = { jianquanxian };
\ No newline at end of file
diff --git a/miniprogram/utils/imAuth/laobanjianquan.js b/miniprogram/utils/imAuth/laobanjianquan.js
new file mode 100644
index 0000000..a29361a
--- /dev/null
+++ b/miniprogram/utils/imAuth/laobanjianquan.js
@@ -0,0 +1,51 @@
+// utils/imAuth/laobanjianquan.js
+const imRequest = require('./imRequest.js');
+
+/**
+ * 老板(点单用户)IM 权限检测
+ * POST /yonghu/lbhqltqx
+ * 后端返回 { code: 0, allow: 1 } 或 { code: 0, allow: 0 }
+ */
+async function laobanjianquan(app) {
+ const uid = wx.getStorageSync('uid');
+ if (!uid) return { allowed: false, reason: '未登录' };
+
+ try {
+ const res = await imRequest({
+ url: '/yonghu/lbhqltqx',
+ method: 'POST',
+ data: { uid }
+ });
+
+ if (res && res.data && res.data.code === 0) {
+ const allow = res.data.allow;
+ if (allow === 1) {
+ return { allowed: true };
+ } else {
+ await app.disconnectGoEasy();
+ wx.showToast({
+ title: '当前身份已不支持使用消息功能',
+ icon: 'none',
+ duration: 1000,
+ mask: true
+ });
+ await new Promise(r => setTimeout(r, 1100));
+ return { allowed: false, reason: '当前身份无消息权限' };
+ }
+ }
+ throw new Error(res.data?.msg || '鉴权接口异常');
+ } catch (err) {
+ console.error('老板鉴权失败:', err);
+ await app.disconnectGoEasy();
+ wx.showToast({
+ title: '消息服务暂不可用,请稍后重试',
+ icon: 'none',
+ duration: 2000,
+ mask: true
+ });
+ await new Promise(r => setTimeout(r, 2100));
+ return { allowed: false, reason: '鉴权请求失败' };
+ }
+}
+
+module.exports = { laobanjianquan };
\ No newline at end of file
diff --git a/miniprogram/utils/imageUrl.js b/miniprogram/utils/imageUrl.js
new file mode 100644
index 0000000..a8f15f3
--- /dev/null
+++ b/miniprogram/utils/imageUrl.js
@@ -0,0 +1,74 @@
+// /utils/imageUrl.js - 统一的COS图片路径管理
+const app = getApp();
+
+/**
+ * 获取完整的COS图片URL
+ * @param {string} path - 图片相对路径,如 'a_long/images/common/arrow-down.png'
+ * @returns {string} 完整的URL
+ */
+export function getImageUrl(path) {
+ if (!app || !app.globalData || !app.globalData.ossImageUrl) {
+ console.warn('⚠️ App.globalData.ossImageUrl 未初始化');
+ return path; // 返回原路径作为fallback
+ }
+
+ // 如果已经是完整URL,直接返回
+ if (path.startsWith('http')) {
+ return path;
+ }
+
+ // 拼接完整URL
+ return app.globalData.ossImageUrl + path;
+}
+
+/**
+ * 获取常用图片的URL
+ */
+export const imageUrls = {
+ // 身份相关
+ identityIcon: 'a_long/images/identity/identity-icon.png',
+ arrowDown: 'a_long/images/common/arrow-down.png',
+ bossIcon: 'a_long/images/identity/boss-icon.png',
+ dashouIcon: 'a_long/images/identity/dashou-icon.png',
+ shangjiaIcon: 'a_long/images/identity/shangjia-icon.png',
+
+ // 公共图标
+ actionIcon: 'a_long/images/common/action.png',
+ emptyMessage: 'a_long/images/common/empty-message.png',
+
+ // 通知相关
+ notificationOn: 'a_long/images/notifications/notification-on.png',
+ notificationOff: 'a_long/images/notifications/notification-off.png',
+
+ // TabBar图标
+ tabbarHome: 'a_long/images/tabbar/home.png',
+ tabbarHomeActive: 'a_long/images/tabbar/home-active.png',
+ tabbarCategory: 'a_long/images/tabbar/category.png',
+ tabbarCategoryActive: 'a_long/images/tabbar/category-active.png',
+ tabbarMessage: 'a_long/images/tabbar/message.png',
+ tabbarMessageActive: 'a_long/images/tabbar/message-active.png',
+ tabbarProfile: 'a_long/images/tabbar/profile.png',
+ tabbarProfileActive: 'a_long/images/tabbar/profile-active.png',
+
+ // 用户相关
+ defaultAvatar: 'a_long/images/common/default-avatar.png'
+};
+
+/**
+ * 预加载常用图片
+ */
+export function preloadCommonImages() {
+ const images = Object.values(imageUrls).map(path => getImageUrl(path));
+
+ images.forEach(src => {
+ wx.getImageInfo({
+ src: src,
+ success: () => {
+ console.log('✅ 图片预加载成功:', src);
+ },
+ fail: (err) => {
+ console.warn('⚠️ 图片预加载失败:', src, err);
+ }
+ });
+ });
+}
\ No newline at end of file
diff --git a/miniprogram/utils/message-sender.js b/miniprogram/utils/message-sender.js
index be84948..b167f5a 100644
--- a/miniprogram/utils/message-sender.js
+++ b/miniprogram/utils/message-sender.js
@@ -3,6 +3,11 @@ const app = getApp();
import { formatDate } from '../static/lib/utils';
import request from './request.js';
import { getFreshImUser } from './im-user.js';
+import { sendGoEasyImage } from './chatImageSend.js';
+
+function isCrossPlatformOrder(isCross) {
+ return isCross === 1 || isCross === true || String(isCross) === '1';
+}
function sendGroupMessage(options) {
const { msgData, onSuccess, onSendStart } = options;
@@ -20,8 +25,7 @@ function sendGroupMessage(options) {
const localMsg = buildLocalMessage(msgData);
if (onSendStart) onSendStart(localMsg);
- // 只有 isCross 明确为 0 才走前端 SDK,其余一律走后端
- if (isCross === 0) {
+ if (!isCrossPlatformOrder(isCross)) {
sendViaSDK(localMsg, currentUser, groupId, onSuccess, orderId, isCross, groupName, groupAvatar);
return;
}
@@ -32,7 +36,7 @@ function buildLocalMessage(data) {
const { type, text, filePath, orderPayload, currentUser, groupId } = data;
const now = Date.now();
const base = {
- messageId: `${type}-${now}`,
+ messageId: `local-${type}-${now}`,
timestamp: now,
senderId: currentUser.id,
groupId: groupId,
@@ -45,7 +49,7 @@ function buildLocalMessage(data) {
};
if (type === 'text') return { ...base, type: 'text', payload: { text } };
- if (type === 'image') return { ...base, type: 'image', payload: { url: filePath } };
+ if (type === 'image') return { ...base, type: 'image', payload: { url: filePath }, imageUrl: filePath };
if (type === 'order') return { ...base, type: 'order', payload: orderPayload };
return base;
}
@@ -66,6 +70,7 @@ function sendViaSDK(localMsg, currentUser, groupId, onSuccess, orderId, isCross,
dashouAvatar: groupMeta.dashouAvatar,
partnerAvatar: groupMeta.partnerAvatar,
orderDesc: groupMeta.orderDesc,
+ orderZhuangtai: groupMeta.orderZhuangtai,
};
const to = {
@@ -74,18 +79,35 @@ function sendViaSDK(localMsg, currentUser, groupId, onSuccess, orderId, isCross,
data: toData
};
+ if (localMsg.type === 'image') {
+ sendGoEasyImage({
+ file: localMsg.payload.url,
+ to,
+ onSuccess: (res) => {
+ const serverMsg = res && res.content;
+ if (serverMsg && onSuccess) {
+ onSuccess(localMsg.messageId, 'success', serverMsg);
+ } else if (onSuccess) {
+ onSuccess(localMsg.messageId, 'success');
+ }
+ },
+ onFailed: () => { if (onSuccess) onSuccess(localMsg.messageId, 'failed'); }
+ });
+ return;
+ }
+
let message;
if (localMsg.type === 'text') {
message = wx.goEasy.im.createTextMessage({ text: localMsg.payload.text, to });
- } else if (localMsg.type === 'image') {
- message = wx.goEasy.im.createImageMessage({ file: localMsg.payload.url, to });
} else if (localMsg.type === 'order') {
message = wx.goEasy.im.createCustomMessage({ type: 'order', payload: localMsg.payload, to });
}
wx.goEasy.im.sendMessage({
message,
- onSuccess: () => { if (onSuccess) onSuccess(localMsg.messageId, 'success'); },
+ onSuccess: (res) => {
+ if (onSuccess) onSuccess(localMsg.messageId, 'success', res && res.content);
+ },
onFailed: () => { if (onSuccess) onSuccess(localMsg.messageId, 'failed'); }
});
}
@@ -93,18 +115,17 @@ function sendViaSDK(localMsg, currentUser, groupId, onSuccess, orderId, isCross,
function sendViaBackend(localMsg, currentUser, groupId, orderId, onSuccess) {
const apiUrl = '/dingdan/kptxxfs';
- // 🔥 实时获取当前身份,不再依赖外部传入的 currentUser.id
const uid = wx.getStorageSync('uid');
const role = app.globalData.currentRole || 'normal';
- const prefixMap = { normal: 'Boss', dashou: 'Ds', shangjia: 'Sj', guanshi: 'Gs', zuzhang: 'Zz' };
const identityType = role === 'dashou' ? 'dashou' : (role === 'shangjia' ? 'shangjia' : 'boss');
+ const prefixMap = { normal: 'Boss', dashou: 'Ds', shangjia: 'Sj', guanshi: 'Gs', zuzhang: 'Zz' };
const prefix = prefixMap[role] || 'Boss';
const senderId = prefix + uid;
const freshUser = getFreshImUser(app, role, uid);
const params = {
orderId: orderId,
groupId: groupId,
- identityType: identityType, // 告诉后端当前是什么身份
+ identityType: identityType,
senderId: senderId,
senderName: freshUser.name || currentUser.name || ('用户' + uid),
senderAvatar: freshUser.avatar || currentUser.avatar || (app.globalData.ossImageUrl + app.globalData.morentouxiang),
@@ -135,7 +156,6 @@ function sendViaBackend(localMsg, currentUser, groupId, orderId, onSuccess) {
return;
}
- // 图片消息
if (localMsg.type === 'image') {
const token = wx.getStorageSync('token');
const formData = {
@@ -169,4 +189,4 @@ function sendViaBackend(localMsg, currentUser, groupId, orderId, onSuccess) {
}
}
-module.exports = { sendGroupMessage };
\ No newline at end of file
+module.exports = { sendGroupMessage };
diff --git a/miniprogram/utils/page-assets.js b/miniprogram/utils/page-assets.js
new file mode 100644
index 0000000..22b63a6
--- /dev/null
+++ b/miniprogram/utils/page-assets.js
@@ -0,0 +1,76 @@
+/** 从 globalData.pageAssets / paihangAssets 拼 OSS 完整 URL */
+
+export function buildOssUrl(path) {
+ if (!path) return ''
+ if (String(path).startsWith('http')) return path
+ const g = getApp().globalData
+ const oss = (g.ossImageUrl || '').replace(/\/$/, '')
+ if (!oss) return ''
+ return `${oss}/${String(path).replace(/^\//, '')}`
+}
+
+export function getPaihangImgUrls() {
+ const g = getApp().globalData
+ const assets = g.paihangAssets || {}
+ const def = {
+ pageBg: 'beijing/paihangbang/page-bg.jpg',
+ cardBg: 'beijing/paihangbang/list-card-bg.png',
+ emptyIcon: 'beijing/paihangbang/empty.png',
+ refreshIcon: 'beijing/paihangbang/icon-refresh.png',
+ }
+ return {
+ pageBg: buildOssUrl(assets.pageBg || def.pageBg),
+ cardBg: buildOssUrl(assets.cardBg || def.cardBg),
+ emptyIcon: buildOssUrl(assets.emptyIcon || def.emptyIcon),
+ refreshIcon: buildOssUrl(assets.refreshIcon || def.refreshIcon),
+ }
+}
+
+/** 按资源组从后台配置读取图标/背景(无配置时用 defaultPaths) */
+export function getGroupImgUrls(group, defaultPaths) {
+ const cfg = getApp().globalData.pageAssets?.[group] || {}
+ const urls = {}
+ Object.keys(defaultPaths).forEach((key) => {
+ urls[key] = buildOssUrl(cfg[key] || defaultPaths[key])
+ })
+ return urls
+}
+
+export const SHANGJIADUAN_DEFAULTS = {
+ pageBg: 'beijing/shangjiaduan/page_bg.png',
+ avatarCardBg: 'beijing/shangjiaduan/avatar_card_bg.png',
+ assetCardBg: 'beijing/shangjiaduan/asset_card_bg.png',
+ statCardBg: 'beijing/shangjiaduan/stat_card_bg.png',
+ funcItemBg: 'beijing/shangjiaduan/func_item_bg.png',
+ primaryFuncBg: 'beijing/shangjiaduan/primary_func_bg.png',
+ highlightFuncBg: 'beijing/shangjiaduan/highlight_func_bg.png',
+ iconRelease: 'beijing/shangjiaduan/icon_release.png',
+ iconFast: 'beijing/shangjiaduan/icon_fast.png',
+ iconOrders: 'beijing/shangjiaduan/icon_orders.png',
+ iconService: 'beijing/shangjiaduan/icon_service.png',
+ iconPunish: 'beijing/shangjiaduan/icon_punish.png',
+ iconRank: 'beijing/shangjiaduan/icon_rank.png',
+ iconRecharge: 'beijing/shangjiaduan/icon_recharge.png',
+ iconWithdraw: 'beijing/shangjiaduan/icon_withdraw.png',
+ iconRefresh: 'beijing/shangjiaduan/icon_refresh.png',
+ iconCopy: 'beijing/shangjiaduan/icon_copy.png',
+ avatarFrame: 'beijing/shangjiaduan/avatar_frame.png',
+}
+
+export const GUANSHIDUAN_DEFAULTS = {
+ pageBg: 'beijing/guanshiduan/page-bg.jpg',
+ topDecor: 'beijing/guanshiduan/top-decor.png',
+ cardBg1: 'beijing/guanshiduan/card-bg1.png',
+ cardBg2: 'beijing/guanshiduan/card-bg2.png',
+ iconRefresh: 'beijing/guanshiduan/icon-refresh.png',
+ iconCopy: 'beijing/guanshiduan/icon-copy.png',
+ avatarDefault: 'beijing/morentouxiang.jpg',
+ iconInvite: 'beijing/guanshiduan/icon-invite.png',
+ iconSub: 'beijing/guanshiduan/icon-sub.png',
+ iconRecord: 'beijing/guanshiduan/icon-record.png',
+ iconRank: 'beijing/guanshiduan/icon-rank.png',
+ iconPoster: 'beijing/guanshiduan/icon-poster.png',
+ iconWithdraw: 'beijing/guanshiduan/icon-withdraw.png',
+ iconContact: 'beijing/guanshiduan/icon-contact.png',
+ iconArrow: 'beijing/guanshiduan/icon-arrow.png',
+}
diff --git a/miniprogram/utils/phone-auth.js b/miniprogram/utils/phone-auth.js
new file mode 100644
index 0000000..0b8a654
--- /dev/null
+++ b/miniprogram/utils/phone-auth.js
@@ -0,0 +1,165 @@
+/**
+ * 手机号强制认证 - 完全自包含逻辑模块
+ *
+ * 特点:
+ * 1. 不引用项目中的 request.js / upload.js,避免其顶层 getApp() 带来的加载顺序错误
+ * 2. 所有请求直接使用 wx.request / wx.uploadFile,基础 URL 写死(与 app.globalData.apiBaseUrl 保持一致)
+ * 3. check() 内部捕获所有异常,永远返回 false 或 true,不会抛出错误
+ * 4. submit() 抛出错误由认证页面自己处理,不影响主流程
+ *
+ * 用法:
+ * app.js 中:
+ * try {
+ * const { check } = require('./utils/phone-auth');
+ * if (await check()) { wx.reLaunch(...); return; }
+ * } catch(e) {}
+ *
+ * 认证页面中:
+ * const { submit } = require('../../utils/phone-auth');
+ * await submit(phoneCode, avatarPath);
+ */
+
+// ==================== 基础配置 ====================
+const API_BASE = 'https://6ccy.top/hqhd'; // 与你 app.js 里的 apiBaseUrl 一致
+const AUTH_CHECK_URL = '/peizhi/yhbdsjh'; // 检查是否需要认证
+const AUTH_SUBMIT_URL = '/yonghu/xiugai'; // 提交认证(复用修改信息接口)
+
+// ==================== 内部封装:wx.request ====================
+function innerRequest(url, data = {}) {
+ return new Promise((resolve, reject) => {
+ const token = wx.getStorageSync('token'); // 运行时获取 token,不提前取
+ const header = {
+ 'Content-Type': 'application/json'
+ };
+ if (token) {
+ header['Authorization'] = 'Bearer ' + token;
+ }
+
+ wx.request({
+ url: API_BASE + url,
+ method: 'POST',
+ data,
+ header,
+ success: (res) => {
+ // 正常响应直接 resolve 整个响应对象,让上层自己解析
+ resolve(res);
+ },
+ fail: (err) => {
+ reject(err);
+ }
+ });
+ });
+}
+
+// ==================== 内部封装:wx.uploadFile ====================
+function innerUpload(url, filePath, formData = {}, fileName = 'avatar') {
+ return new Promise((resolve, reject) => {
+ const token = wx.getStorageSync('token');
+ if (!token) {
+ reject(new Error('未登录'));
+ return;
+ }
+
+ // 先验证文件是否存在
+ wx.getFileInfo({
+ filePath,
+ success: () => {
+ wx.uploadFile({
+ url: API_BASE + url,
+ filePath,
+ name: fileName,
+ formData,
+ header: {
+ 'Authorization': 'Bearer ' + token
+ },
+ success: (res) => {
+ // ✅ 修复:wx.uploadFile 成功时 statusCode 为 200
+ if (res.statusCode === 200) {
+ try {
+ const data = JSON.parse(res.data);
+ resolve(data); // 直接返回解析后的对象,方便下游使用
+ } catch (e) {
+ reject(new Error('解析响应数据失败'));
+ }
+ } else if (res.statusCode === 401) {
+ wx.removeStorageSync('token');
+ reject(new Error('认证失败'));
+ } else {
+ reject(new Error(`上传失败,状态码:${res.statusCode}`));
+ }
+ },
+ fail: (err) => {
+ reject(new Error('网络请求失败'));
+ }
+ });
+ },
+ fail: () => {
+ reject(new Error('文件不存在或无法访问'));
+ }
+ });
+ });
+}
+
+// ==================== 公开方法 ====================
+
+/**
+ * 检查是否需要手机号认证
+ * - 无 token 或 任何异常 → 返回 false
+ * - 后端明确 need_auth: true → 返回 true
+ */
+async function check() {
+ const token = wx.getStorageSync('token');
+ if (!token) return false;
+
+ try {
+ const res = await innerRequest(AUTH_CHECK_URL, {});
+ // 假设后端返回格式:{ data: { need_auth: true/false } } 或直接在顶层
+ const body = res.data;
+ return body && body.need_auth === true;
+ } catch (e) {
+ // 任何错误(网络、后端异常)都吞掉,不影响主流程
+ return false;
+ }
+}
+
+/**
+ * 提交认证(手机号 code + 头像文件)
+ * @param {string} phoneCode 微信 getPhoneNumber 返回的 code
+ * @param {string} avatarPath 头像临时路径
+ * @returns {Promise