修正了 pages 名为拼音的问题

This commit is contained in:
2026-06-13 10:44:02 +08:00
parent 76cc4ac55e
commit 296e41a8a7
249 changed files with 29660 additions and 29505 deletions

View File

@@ -0,0 +1,220 @@
// components/jifen-list/jifen-list.js
import request from '../../../../utils/request.js';
const app = getApp();
const MEIYE_TIAOSHU = 5;
Component({
properties: {
status: { type: Number, value: 0, observer: 'reload' },
trigger: { type: Number, value: 0, observer: 'reload' }
},
data: {
chufaList: [],
dangqianye: 1,
haiyougengduo: true,
jiazhaozhong: false,
jiazhaigengduo: false,
ossImageUrl: app.globalData.ossImageUrl || '',
showXiangqing: false,
xuanzhongChufa: {},
showShensuModal: false,
shensuLiyou: '',
shensuTupian: [],
shangchuanJindu: 0,
shangchuanZongshu: 0,
jinduWidth: '0%',
isShangchuanzhong: false,
fangdouTimer: null
},
lifetimes: {
attached() { this.reload(); },
detached() { if (this.data.fangdouTimer) clearTimeout(this.data.fangdouTimer); }
},
methods: {
reload() {
this.setData({ dangqianye: 1, chufaList: [], haiyougengduo: true });
this.loadJifenList();
},
async loadJifenList(isLoadMore = false) {
if (this.data.jiazhaozhong || this.data.jiazhaigengduo) return;
if (isLoadMore && !this.data.haiyougengduo) return;
if (isLoadMore) this.setData({ jiazhaigengduo: true });
else this.setData({ jiazhaozhong: true });
try {
const params = { page: this.data.dangqianye, page_size: MEIYE_TIAOSHU, zhuangtai: this.data.status };
const res = await request({ url: '/yonghu/dshqcfjl', method: 'POST', data: params });
if (res.statusCode === 200 && res.data.code === 0) {
const data = res.data.data;
if (data.list && Array.isArray(data.list)) {
const processedList = data.list.map(item => {
let displayTime = '--';
if (item.create_time) displayTime = item.create_time;
let displayStatus = '未知状态', statusClass = 'zhuangtai-weizhi';
const n = Number(item.sqzhuangtai);
if (!isNaN(n)) {
if (n === 0) { displayStatus = '待处理'; statusClass = 'zhuangtai-daichuli'; }
else if (n === 1) { displayStatus = '已处罚'; statusClass = 'zhuangtai-yichufa'; }
else if (n === 2) { displayStatus = '处罚已撤销'; statusClass = 'zhuangtai-yibohui'; }
else if (n === 3) { displayStatus = '申诉中'; statusClass = 'zhuangtai-shensuzhong'; }
}
return {
...item,
display_time: displayTime, display_status: displayStatus, status_class: statusClass,
full_zhengju_tupian: (item.zhengju_tupian || []).map(url => this.getFullImageUrl(url)),
full_shensu_tupian: (item.shensu_tupian || []).map(url => this.getFullImageUrl(url))
};
});
const newList = isLoadMore ? [...this.data.chufaList, ...processedList] : processedList;
this.setData({ chufaList: newList, haiyougengduo: data.has_more === true, dangqianye: this.data.dangqianye + 1 });
} else this.setData({ haiyougengduo: false });
}
} catch (e) { wx.showToast({ title: '加载失败', icon: 'none' }); this.setData({ haiyougengduo: false }); }
finally { if (isLoadMore) this.setData({ jiazhaigengduo: false }); else this.setData({ jiazhaozhong: false }); }
},
onReachBottom() {
if (this.data.fangdouTimer) clearTimeout(this.data.fangdouTimer);
const timer = setTimeout(() => {
if (this.data.jiazhaozhong || this.data.jiazhaigengduo) return;
if (!this.data.haiyougengduo) return;
this.loadJifenList(true);
}, 300);
this.setData({ fangdouTimer: timer });
},
chakanXiangqing(e) { this.setData({ showXiangqing: true, xuanzhongChufa: e.currentTarget.dataset.item }); },
guanbiXiangqing() { this.setData({ showXiangqing: false, xuanzhongChufa: {} }); },
openShensuModal() {
const { xuanzhongChufa } = this.data;
if (xuanzhongChufa.ssliyou || (xuanzhongChufa.shensu_tupian && xuanzhongChufa.shensu_tupian.length > 0)) {
wx.showToast({ title: '您已经提交过申诉,请等待处理结果', icon: 'none' }); return;
}
this.setData({ showShensuModal: true, shensuLiyou: '', shensuTupian: [], shangchuanJindu: 0, shangchuanZongshu: 0, jinduWidth: '0%', isShangchuanzhong: false });
},
closeShensuModal() { this.setData({ showShensuModal: false }); },
onShensuLiyouInput(e) { this.setData({ shensuLiyou: e.detail.value.slice(0, 500) }); },
chooseShensuTupian() {
if (this.data.isShangchuanzhong) return;
const shengyu = 9 - this.data.shensuTupian.length;
if (shengyu <= 0) { wx.showToast({ title: '最多只能上传9张图片', icon: 'none' }); return; }
wx.chooseMedia({ count: shengyu, mediaType: ['image'], sourceType: ['album', 'camera'], success: (res) => {
const newTupian = [...this.data.shensuTupian, ...res.tempFiles.map(f => f.tempFilePath)];
this.setData({ shensuTupian: newTupian.slice(0, 9) });
}});
},
deleteShensuTupian(e) { const arr = [...this.data.shensuTupian]; arr.splice(e.currentTarget.dataset.index, 1); this.setData({ shensuTupian: arr }); },
yulanShensuTupian(e) {
const idx = e.currentTarget.dataset.index;
const urls = this.data.shensuTupian;
if (!urls[idx]) return;
wx.previewImage({ current: urls[idx], urls });
},
yulanTupian(e) {
const current = e.currentTarget.dataset.url;
const urls = e.currentTarget.dataset.urls || [];
const fullCurrent = current.startsWith('http') ? current : this.getFullImageUrl(current);
const fullUrls = urls.map(url => url.startsWith('http') ? url : this.getFullImageUrl(url));
wx.previewImage({ current: fullCurrent, urls: fullUrls.length ? fullUrls : [fullCurrent] });
},
/* ========== 图片上传(完全照抄原 cfss.js ========== */
// 【仅替换此方法,其余代码一个字别动】
getFullImageUrl(relativeUrl) {
if (!relativeUrl) return '';
if (relativeUrl.startsWith('http')) return relativeUrl;
const ossUrl = this.data.ossImageUrl;
if (!ossUrl) return relativeUrl;
// 确保拼接时只有一个斜杠
const base = ossUrl.replace(/\/+$/, '');
const rel = relativeUrl.replace(/^\/+/, '');
return base + '/' + rel;
},
getFileExtension(filePath) {
const lastDot = filePath.lastIndexOf('.'); if (lastDot === -1) return '.jpg';
const ext = filePath.substring(lastDot).toLowerCase();
return ['.jpg','.jpeg','.png','.gif','.bmp','.webp'].includes(ext) ? ext : '.jpg';
},
async getCOSZhengshu() {
const res = await request({ url: '/dingdan/dsscpz', method: 'POST', data: { dingdan_id: this.data.xuanzhongChufa.dingdan_id || '', yongtu: 'chufa' } });
if (res.statusCode === 200 && res.data.code === 0 && res.data.data) return res.data.data;
throw new Error(res?.data?.msg || '获取上传凭证失败');
},
initCOSClient(tokenData) {
const COS = require('../../../../utils/cos-wx-sdk-v5.js');
const credentials = tokenData.credentials || tokenData;
const bucket = tokenData.bucket || 'julebu-1361527063';
const region = tokenData.region || 'ap-shanghai';
const cos = new COS({ SimpleUploadMethod: 'putObject', getAuthorization: (options, callback) => { callback({ TmpSecretId: credentials.tmpSecretId, TmpSecretKey: credentials.tmpSecretKey, SecurityToken: credentials.sessionToken || '', StartTime: tokenData.startTime || Math.floor(Date.now() / 1000), ExpiredTime: tokenData.expiredTime || (Math.floor(Date.now() / 1000) + 7200) }); } });
return { cos, bucket, region };
},
shangchuanDanZhangTupian(filePath, cosKey, cosClient, bucket, region) {
return new Promise(resolve => {
const timeoutId = setTimeout(() => resolve({ status: 'optimistic_success', key: cosKey }), 2000);
cosClient.uploadFile({ Bucket: bucket, Region: region, Key: cosKey, FilePath: filePath, onProgress: (info) => { if (Math.round(info.percent * 100) === 100) { clearTimeout(timeoutId); resolve({ status: 'optimistic_success', key: cosKey }); } } });
});
},
async piliangShangchuanShensuTupian() {
const total = this.data.shensuTupian.length;
if (total === 0) return [];
this.setData({ shangchuanZongshu: total, shangchuanJindu: 0, jinduWidth: '0%', isShangchuanzhong: true });
let yonghuid = '';
const uid = wx.getStorageSync('uid'); if (uid) yonghuid = String(uid).padStart(7, '0');
else if (app.globalData.userInfo?.yonghuid) yonghuid = String(app.globalData.userInfo.yonghuid).padStart(7, '0');
if (!yonghuid) { wx.showToast({ title: '请先登录', icon: 'none' }); this.setData({ isShangchuanzhong: false }); return []; }
const preGeneratedUrls = [];
for (let i = 0; i < total; i++) {
const timestamp = Date.now() + i, random = Math.floor(Math.random() * 10000);
preGeneratedUrls.push(`a_long/chfajltp/dssstp/${yonghuid}_${timestamp}_${random}${this.getFileExtension(this.data.shensuTupian[i])}`);
}
let cosClient, bucket, region;
try { const tokenData = await this.getCOSZhengshu(); const c = this.initCOSClient(tokenData); cosClient = c.cos; bucket = c.bucket; region = c.region; }
catch (e) { wx.showToast({ title: '上传初始化失败', icon: 'none' }); this.setData({ isShangchuanzhong: false }); return []; }
for (let i = 0; i < total; i++) {
await this.shangchuanDanZhangTupian(this.data.shensuTupian[i], preGeneratedUrls[i], cosClient, bucket, region);
const done = i + 1;
this.setData({ shangchuanJindu: done, jinduWidth: `${((done / total) * 100).toFixed(0)}%` });
}
this.setData({ isShangchuanzhong: false });
return preGeneratedUrls;
},
async submitShensu() {
if (!this.data.shensuLiyou.trim()) { wx.showToast({ title: '请输入申诉理由', icon: 'none' }); return; }
if (this.data.shensuTupian.length === 0) { wx.showModal({ title: '提示', content: '未上传任何图片,确定要提交申诉吗?', success: async (res) => { if (res.confirm) await this.tijiaoShensuData([]); } }); return; }
wx.showToast({ title: '开始上传图片...', icon: 'none' });
try {
const urls = await this.piliangShangchuanShensuTupian();
if (!urls || urls.length === 0) throw new Error('图片上传失败');
await this.tijiaoShensuData(urls);
} catch (e) { wx.showToast({ title: e.message || '提交失败', icon: 'none' }); }
},
async tijiaoShensuData(tupianUrls) {
wx.showToast({ title: '提交申诉中...', icon: 'none' });
try {
const res = await request({ url: '/yonghu/dscfss', method: 'POST', data: { chufa_id: this.data.xuanzhongChufa.id, shensu_liyou: this.data.shensuLiyou, shensu_tupian_urls: tupianUrls } });
if (res.statusCode === 200 && res.data.code === 0) {
const newList = this.data.chufaList.map(item => {
if (item.id === this.data.xuanzhongChufa.id) { return { ...item, sqzhuangtai: 3, ssliyou: this.data.shensuLiyou, display_status: '申诉中', status_class: 'zhuangtai-shensuzhong', full_shensu_tupian: tupianUrls.map(url => this.getFullImageUrl(url)) }; }
return item;
});
this.setData({ chufaList: newList, showShensuModal: false, showXiangqing: false });
wx.showToast({ title: '申诉提交成功,请等待处理', icon: 'success' });
} else throw new Error(res.data?.msg || '提交申诉失败');
} catch (e) { wx.showToast({ title: e.message || '提交失败', icon: 'none' }); }
}
}
});

View File

@@ -0,0 +1,3 @@
{
"component": true
}

View File

@@ -0,0 +1,105 @@
<view class="container">
<scroll-view scroll-y class="scroll" bindscrolltolower="onReachBottom">
<view wx:if="{{ jiazhaozhong && chufaList.length === 0 }}" class="tip">加载中...</view>
<block wx:for="{{ chufaList }}" wx:key="id">
<view class="card" bindtap="chakanXiangqing" data-item="{{ item }}">
<view class="card-head">
<text class="time">{{ item.display_time }}</text>
<text class="tag {{ item.status_class }}">{{ item.display_status }}</text>
</view>
<text class="info">申请人:{{ item.qingqiuid || '--' }}</text>
<text class="info">理由:{{ item.cfliyou || '无' }}</text>
<text class="info" wx:if="{{ item.dingdan_id }}">订单:{{ item.dingdan_id }}</text>
</view>
</block>
<!-- ========== 底部:按钮 / 加载中 / 没有更多 ========== -->
<view wx:if="{{ haiyougengduo && !jiazhaigengduo }}" class="load-more-btn" bindtap="onReachBottom">
<text>点击获取更多</text>
</view>
<view wx:if="{{ jiazhaigengduo }}" class="tip">加载中...</view>
<view wx:if="{{ !haiyougengduo && chufaList.length > 0 }}" class="tip">—— 没有更多了 ——</view>
<view wx:if="{{ !jiazhaozhong && chufaList.length === 0 }}" class="empty">暂无积分处罚记录</view>
</scroll-view>
<!-- 详情弹窗(不动) -->
<view wx:if="{{ showXiangqing }}" class="modal-mask">
<view class="modal-panel">
<view class="modal-head">
<text class="modal-title">处罚详情</text>
<view class="modal-close" bindtap="guanbiXiangqing">✕</view>
</view>
<scroll-view scroll-y class="modal-body">
<view class="row"><text class="lbl">订单编号</text><text class="val">{{ xuanzhongChufa.dingdan_id || '--' }}</text></view>
<view class="row"><text class="lbl">申请人ID</text><text class="val">{{ xuanzhongChufa.qingqiuid || '--' }}</text></view>
<view class="row"><text class="lbl">时间</text><text class="val">{{ xuanzhongChufa.display_time }}</text></view>
<view class="row"><text class="lbl">状态</text><text class="val"><text class="tag {{ xuanzhongChufa.status_class }}">{{ xuanzhongChufa.display_status }}</text></text></view>
<view class="row"><text class="lbl">处罚理由</text><text class="val">{{ xuanzhongChufa.cfliyou || '无' }}</text></view>
<view wx:if="{{ xuanzhongChufa.full_zhengju_tupian.length }}" class="section">
<text class="sec-title">证据图片</text>
<view class="img-grid">
<block wx:for="{{ xuanzhongChufa.full_zhengju_tupian }}" wx:key="index">
<view class="img-box" bindtap="yulanTupian" data-url="{{ item }}" data-urls="{{ xuanzhongChufa.full_zhengju_tupian }}">
<image class="img" src="{{ item }}" mode="aspectFill" />
</view>
</block>
</view>
</view>
<view wx:if="{{ xuanzhongChufa.ssliyou || xuanzhongChufa.full_shensu_tupian.length }}" class="section">
<text class="sec-title">我的申诉</text>
<view wx:if="{{ xuanzhongChufa.ssliyou }}" class="row"><text class="lbl">理由</text><text class="val">{{ xuanzhongChufa.ssliyou }}</text></view>
<view wx:if="{{ xuanzhongChufa.full_shensu_tupian.length }}" class="img-grid">
<block wx:for="{{ xuanzhongChufa.full_shensu_tupian }}" wx:key="index">
<view class="img-box" bindtap="yulanTupian" data-url="{{ item }}" data-urls="{{ xuanzhongChufa.full_shensu_tupian }}">
<image class="img" src="{{ item }}" mode="aspectFill" />
</view>
</block>
</view>
</view>
</scroll-view>
<view class="modal-foot">
<view wx:if="{{ xuanzhongChufa.sqzhuangtai === 0 }}" class="btn primary" bindtap="openShensuModal">申诉</view>
<view class="btn default" bindtap="guanbiXiangqing">关闭</view>
</view>
</view>
</view>
<!-- 申诉弹窗(不动) -->
<view wx:if="{{ showShensuModal }}" class="modal-mask">
<view class="modal-panel">
<view class="modal-head">
<text class="modal-title">提交申诉</text>
<view class="modal-close" bindtap="closeShensuModal">✕</view>
</view>
<scroll-view scroll-y class="modal-body">
<view class="form-group">
<text class="sec-title">申诉理由</text>
<textarea class="textarea" placeholder="请输入申诉理由最多500字" value="{{ shensuLiyou }}" bindinput="onShensuLiyouInput" maxlength="500" />
<text class="word-count">{{ shensuLiyou.length }}/500</text>
</view>
<view class="form-group">
<text class="sec-title">上传凭证最多9张</text>
<view class="img-grid">
<block wx:for="{{ shensuTupian }}" wx:key="index">
<view class="img-box">
<image class="img" src="{{ item }}" mode="aspectFill" bindtap="yulanShensuTupian" data-index="{{ index }}" />
<view class="img-del" catchtap="deleteShensuTupian" data-index="{{ index }}">✕</view>
</view>
</block>
<view wx:if="{{ shensuTupian.length < 9 }}" class="img-add" bindtap="chooseShensuTupian">+</view>
</view>
<view wx:if="{{ shangchuanZongshu > 0 }}" class="progress-box">
<view class="progress-bar"><view class="progress-inner" style="width: {{ jinduWidth }}"></view></view>
<text class="progress-text">{{ shangchuanJindu }}/{{ shangchuanZongshu }}</text>
</view>
</view>
</scroll-view>
<view class="modal-foot">
<view class="btn default" bindtap="closeShensuModal">取消</view>
<view class="btn primary" bindtap="submitShensu">提交申诉</view>
</view>
</view>
</view>
</view>

View File

@@ -0,0 +1,86 @@
/* 页面与容器 */
page { background: #f5f5f5; }
/* 🔥 组件自身必须占满宽度,且 padding 左右严格相等 */
.container { width: 100%; box-sizing: border-box; display: flex; flex-direction: column; }
/* 🔥 滚动区 padding 左右必须相等,且容器用 box-sizing */
.scroll { width: 100%; box-sizing: border-box; padding: 20rpx 24rpx; }
/* 提示文字 */
.tip { text-align: center; color: #999; font-size: 24rpx; padding: 30rpx 0; }
.empty { display: flex; flex-direction: column; align-items: center; padding-top: 200rpx; color: #999; font-size: 28rpx; }
/* 🔥 卡片 width:100% + box-sizing 使左右边距完全由父容器决定 */
.card { width: 100%; box-sizing: border-box; background: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 16rpx; }
.card-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12rpx; }
.time { font-size: 24rpx; color: #999; }
.tag { font-size: 20rpx; padding: 2rpx 14rpx; border-radius: 16rpx; display: inline-block; }
.zhuangtai-daichuli { background: #fff3e0; color: #e65100; }
.zhuangtai-yichufa { background: #e8f5e9; color: #2e7d32; }
.zhuangtai-yibohui { background: #f3e5f5; color: #7b1fa2; }
.zhuangtai-shensuzhong { background: #e3f2fd; color: #1565c0; }
.zhuangtai-weizhi { background: #f0f0f0; color: #888; }
.info { display: block; font-size: 26rpx; color: #333; margin-top: 8rpx; }
/* 弹窗遮罩 */
.modal-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 999; display: flex; align-items: flex-end; justify-content: center; }
/* 🔥 弹窗面板 width:100% + box-sizing */
.modal-panel { width: 100%; box-sizing: border-box; max-height: 85vh; background: #fff; border-radius: 24rpx 24rpx 0 0; display: flex; flex-direction: column; overflow: hidden; }
.modal-head { display: flex; justify-content: space-between; align-items: center; padding: 28rpx 30rpx 16rpx; border-bottom: 1px solid #eee; }
.modal-title { font-size: 32rpx; font-weight: 600; }
.modal-close { width: 44rpx; height: 44rpx; border-radius: 50%; background: #f5f5f5; display: flex; align-items: center; justify-content: center; font-size: 26rpx; color: #888; }
/* 🔥 内容区左右 padding 严格相等 */
.modal-body { width: 100%; box-sizing: border-box; padding: 20rpx 30rpx; overflow-y: auto; }
.row { display: flex; margin-bottom: 16rpx; }
.lbl { width: 150rpx; font-size: 26rpx; color: #888; flex-shrink: 0; }
.val { flex: 1; font-size: 26rpx; color: #333; word-break: break-all; }
.section { margin: 20rpx 0; }
.sec-title { font-size: 26rpx; color: #888; margin-bottom: 12rpx; display: block; }
/* 🔥 图片网格gap 均等,无任何额外水平偏移 */
.img-grid { width: 100%; box-sizing: border-box; display: flex; flex-wrap: wrap; gap: 12rpx; }
.img { width: calc((100% - 24rpx) / 3); height: 0; padding-bottom: calc((100% - 24rpx) / 3); border-radius: 10rpx; background: #f0f0f0; display: block; position: relative; }
.img-box { position: relative; width: calc((100% - 24rpx) / 3); height: 0; padding-bottom: calc((100% - 24rpx) / 3); }
.img-box .img { position: absolute; top: 0; left: 0; width: 100%; height: 100%; padding-bottom: 0; }
.img-del { position: absolute; top: -6rpx; right: -6rpx; width: 34rpx; height: 34rpx; background: #e53935; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: #fff; font-size: 18rpx; z-index: 2; }
.img-add { width: calc((100% - 24rpx) / 3); height: 0; padding-bottom: calc((100% - 24rpx) / 3); border: 2rpx dashed #ccc; border-radius: 10rpx; display: flex; align-items: center; justify-content: center; font-size: 52rpx; color: #aaa; box-sizing: border-box; position: relative; }
/* 表单 */
.form-group { margin-bottom: 24rpx; }
.textarea { width: 100%; box-sizing: border-box; height: 180rpx; background: #f9f9f9; border-radius: 10rpx; padding: 16rpx; font-size: 26rpx; border: 1px solid #eee; }
.word-count { text-align: right; font-size: 22rpx; color: #bbb; margin-top: 6rpx; }
.progress-box { margin-top: 12rpx; }
.progress-bar { height: 10rpx; background: #eee; border-radius: 5rpx; }
.progress-inner { height: 100%; background: #2e7d32; border-radius: 5rpx; }
.progress-text { font-size: 22rpx; color: #2e7d32; text-align: right; margin-top: 4rpx; }
/* 🔥 底部按钮区左右 padding 严格均等 */
.modal-foot { width: 100%; box-sizing: border-box; display: flex; padding: 16rpx 30rpx 30rpx; gap: 20rpx; border-top: 1px solid #eee; }
.btn { flex: 1; text-align: center; padding: 22rpx 0; border-radius: 16rpx; font-size: 28rpx; font-weight: 600; }
.primary { background: #2e7d32; color: #fff; }
.default { background: #f0f0f0; color: #555; }
.btn:active { opacity: 0.8; }
/* ===== 获取更多按钮 ===== */
.load-more-btn {
width: 80%;
margin: 20rpx auto 30rpx;
padding: 22rpx 0;
text-align: center;
background: #ffffff;
border: 2rpx solid #e0e0e0;
border-radius: 20rpx;
color: #555;
font-size: 26rpx;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.02);
transition: all 0.15s;
}
.load-more-btn:active {
background: #f5f5f5;
border-color: #2e7d32;
color: #2e7d32;
}