diff --git a/app/api/property.js b/app/api/property.js index efbec90..0b864fd 100644 --- a/app/api/property.js +++ b/app/api/property.js @@ -46,6 +46,16 @@ export function likeDailyMenuItem(data) { ); } +// 菜单取消点赞/点踩 +export function cancelLikeDailyMenuItem(data) { + return request.post( + 'autogencode/pmdailymenudtllike/cancelLike', + data, + { useAdminUrl: true }, + data + ); +} + // 菜品排行(点赞/点踩) export function getDailyMenuRanking(params) { return request.get( diff --git a/app/pages/supply_chain/stock/index.vue b/app/pages/supply_chain/stock/index.vue index 34e6fd2..8cadd87 100644 --- a/app/pages/supply_chain/stock/index.vue +++ b/app/pages/supply_chain/stock/index.vue @@ -71,8 +71,8 @@ ref="calendar" :insert="false" :range="false" - :start-date="'2020-01-01'" - :end-date="'2030-12-31'" + :start-date="'2026-01-01'" + :end-date="'2099-12-31'" @confirm="confirm" > diff --git a/app/pages/supply_chain/wish_menu/index.vue b/app/pages/supply_chain/wish_menu/index.vue index 12aa2fc..78fb5bd 100644 --- a/app/pages/supply_chain/wish_menu/index.vue +++ b/app/pages/supply_chain/wish_menu/index.vue @@ -3,14 +3,52 @@ 前一天 - - {{ selectedDate }} - + + + {{ selectedDate }} + + + 后一天 默认当天,可切换查看不同日期菜单 + + + + + {{ canteen.label }} + + + + + + + + + + 当日菜单 @@ -19,18 +57,21 @@ - {{ item.itemName || '未命名菜品' }} - {{ item.remark }} - - ¥{{ item.itemPrice }} + + {{ item.itemName || '未命名菜品' }} + + ¥{{ item.itemPrice }} + - 👎 {{ item.dislikeCount || 0 }} + + {{ item.dislikeCount || 0 }} @@ -59,6 +100,21 @@ + + + + + ✕ + + + {{ rankingDateRange.startDate && rankingDateRange.endDate ? `${rankingDateRange.startDate} 至 ${rankingDateRange.endDate}` : '选择日期范围' }} + + + + + + + @@ -66,9 +122,10 @@ {{ index + 1 }} {{ item.itemName || '未命名菜品' }} - - 👍 {{ item.likeCount || 0 }} - 👎 {{ item.dislikeCount || 0 }} + + + + {{ rankingType === 'like' ? (item.likeCount || 0) : (item.dislikeCount || 0) }} @@ -81,10 +138,12 @@ import { listDailyMenuDetails, likeDailyMenuItem, + cancelLikeDailyMenuItem, getDailyMenuRanking } from '@/api/property.js'; export default { + dicts: ['canteen_name'], data() { return { selectedDate: '', @@ -95,21 +154,53 @@ export default { { key: 'breakfast', label: '早餐', list: [] }, { key: 'lunch', label: '中餐', list: [] }, { key: 'dinner', label: '晚餐', list: [] } - ] + ], + canteens: [], + selectedCanteen: '', + rankingDateRange: { + startDate: '', + endDate: '' + }, + showRankingDatePicker: false }; }, onLoad() { this.selectedDate = this.formatDate(new Date()); + this.rankingDateRange.startDate = this.selectedDate; + this.rankingDateRange.endDate = this.selectedDate; + this.loadCanteens(); this.loadPageData(); }, + mounted() { + // 监听字典数据变化 + this.$on('dictChange', () => { + this.loadCanteens(); + }); + }, + beforeUnmount() { + this.$off('dictChange'); + }, methods: { async loadPageData() { await Promise.all([this.loadMenuByDate(), this.loadRanking()]); }, + loadCanteens() { + const canteenDict = this.dict.get('canteen_name') || []; + this.canteens = canteenDict.map(item => ({ + value: item.dictValue, + label: item.dictLabel + })); + if (this.canteens.length > 0 && !this.selectedCanteen) { + this.selectedCanteen = this.canteens[0].value; + } + }, async loadMenuByDate() { try { uni.showLoading({ title: '加载菜单中...', mask: true }); - const res = await listDailyMenuDetails({ menuDate: this.selectedDate }); + const res = await listDailyMenuDetails({ + menuDate: this.selectedDate, + canteenName: this.selectedCanteen + }); const list = res?.data || []; this.menuList = list; this.groupMeals(list); @@ -147,24 +238,58 @@ export default { normalizeMealType(type) { if (!type) return ''; const v = String(type).trim(); - if (v === '早餐' || v === '0' || v.toLowerCase() === 'breakfast') return 'breakfast'; - if (v === '中餐' || v === '午餐' || v === '1' || v.toLowerCase() === 'lunch') return 'lunch'; - if (v === '晚餐' || v === '2' || v.toLowerCase() === 'dinner') return 'dinner'; + if (v === '早餐' || v === '1' || v.toLowerCase() === 'breakfast') return 'breakfast'; + if (v === '中餐' || v === '午餐' || v === '2' || v.toLowerCase() === 'lunch') return 'lunch'; + if (v === '晚餐' || v === '3' || v.toLowerCase() === 'dinner') return 'dinner'; return ''; }, async submitLike(item, likeType) { if (!item || !item.id) return; try { - uni.showLoading({ title: likeType === '1' ? '点赞中...' : '点踩中...', mask: true }); - await likeDailyMenuItem({ - menuDtlId: item.id, - likeType - }); - uni.showToast({ - title: likeType === '1' ? '点赞成功' : '点踩成功', - icon: 'success' - }); - await Promise.all([this.loadMenuByDate(), this.loadRanking()]); + // 判断是否需要取消点赞/点踩 + const isLiked = likeType === '1' && item.isLiked === '1'; + const isDisliked = likeType === '2' && item.isDisliked === '1'; + + if (isLiked || isDisliked) { + uni.showLoading({ title: likeType === '1' ? '取消点赞中...' : '取消点踩中...', mask: true }); + await cancelLikeDailyMenuItem({ + menuDtlId: item.id, + likeType + }); + uni.showToast({ + title: likeType === '1' ? '取消点赞成功' : '取消点踩成功', + icon: 'success' + }); + // 立即更新本地状态,提供即时反馈 + if (likeType === '1') { + item.isLiked = '0'; + item.likeCount = Math.max(0, (item.likeCount || 0) - 1); + } else { + item.isDisliked = '0'; + item.dislikeCount = Math.max(0, (item.dislikeCount || 0) - 1); + } + } else { + uni.showLoading({ title: likeType === '1' ? '点赞中...' : '点踩中...', mask: true }); + await likeDailyMenuItem({ + menuDtlId: item.id, + likeType + }); + uni.showToast({ + title: likeType === '1' ? '点赞成功' : '点踩成功', + icon: 'success' + }); + // 立即更新本地状态,提供即时反馈 + if (likeType === '1') { + item.isLiked = '1'; + item.isDisliked = '0'; + item.likeCount = (item.likeCount || 0) + 1; + } else { + item.isDisliked = '1'; + item.isLiked = '0'; + item.dislikeCount = (item.dislikeCount || 0) + 1; + } + } + // await Promise.all([this.loadMenuByDate(), this.loadRanking()]); } catch (e) { uni.showToast({ title: typeof e === 'string' ? e : '操作失败', @@ -177,10 +302,10 @@ export default { async loadRanking() { try { const res = await getDailyMenuRanking({ - limit: 10, + limit: 20, rankingType: this.rankingType, - startDate: this.selectedDate, - endDate: this.selectedDate + startDate: this.rankingDateRange.startDate, + endDate: this.rankingDateRange.endDate }); this.rankingList = res?.data?.list || []; } catch (e) { @@ -192,21 +317,44 @@ export default { this.rankingType = type; await this.loadRanking(); }, - async onDatePicked(e) { - this.selectedDate = e.detail.value; - await this.loadPageData(); + // 打开日历 + openCalendar() { + this.$refs.calendar.open(); + }, + // 确认日期 + async confirm(e) { + this.selectedDate = e.fulldate; + await this.loadMenuByDate(); }, async changeDateBy(step) { const d = new Date(this.selectedDate.replace(/-/g, '/')); d.setDate(d.getDate() + step); this.selectedDate = this.formatDate(d); - await this.loadPageData(); + await this.loadMenuByDate(); }, formatDate(date) { const y = date.getFullYear(); const m = `${date.getMonth() + 1}`.padStart(2, '0'); const d = `${date.getDate()}`.padStart(2, '0'); return `${y}-${m}-${d}`; + }, + async switchCanteen(canteenValue) { + if (this.selectedCanteen === canteenValue) return; + this.selectedCanteen = canteenValue; + await this.loadPageData(); + }, + openRankingDatePicker() { + this.$refs.rankingCalendar.open(); + }, + async confirmRankingDate(e) { + this.rankingDateRange.startDate = e.range.before; + this.rankingDateRange.endDate = e.range.after; + await this.loadRanking(); + }, + async clearRankingDate() { + this.rankingDateRange.startDate = ''; + this.rankingDateRange.endDate = ''; + await this.loadRanking(); } } }; @@ -219,6 +367,98 @@ export default { padding: 24rpx; } +/* 食堂筛选 */ +.canteen-tabs { + margin-bottom: 20rpx; + overflow-x: auto; + white-space: nowrap; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + &::-webkit-scrollbar { + display: none; + } + + .canteen-tabs-container { + display: inline-flex; + background: #fff; + border-radius: 16rpx; + padding: 8rpx; + } + + .canteen-tab { + padding: 12rpx 24rpx; + margin: 0 4rpx; + border-radius: 999rpx; + font-size: 26rpx; + color: #6b7280; + transition: all 0.3s; + + &.active { + background: #3b82f6; + color: #fff; + font-weight: 600; + } + } +} + +/* 排行榜日期范围选择 */ +.rank-date-range { + margin-bottom: 18rpx; + display: flex; + justify-content: flex-end; +} + +.date-range-container { + display: flex; + align-items: center; + gap: 12rpx; +} + +.date-range-btn { + display: flex; + align-items: center; + padding: 10rpx 16rpx; + background: #f3f4f6; + border-radius: 999rpx; + font-size: 24rpx; + color: #6b7280; + cursor: pointer; + gap: 12rpx; + + .clear-btn { + font-size: 20rpx; + color: #9ca3af; + cursor: pointer; + padding: 0 4rpx; + + &:hover { + color: #ef4444; + } + } + + .date-text { + flex: 1; + font-size: 24rpx; + color: #6b7280; + font-weight: normal; + } + + .icon { + .iconfont { + font-size: 18rpx; + } + } +} + + + +.rank-tabs { + display: flex; + background: #f3f4f6; + border-radius: 999rpx; + padding: 4rpx; +} + .date-card, .section, .meal-card { @@ -248,12 +488,30 @@ export default { font-size: 26rpx; } +.date-picker-container { + display: flex; + align-items: center; +} + .date-text { min-width: 260rpx; text-align: center; font-size: 30rpx; color: #1f2d3d; font-weight: 600; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + + text { + margin-right: 10rpx; + } + + .iconfont { + font-size: 20rpx; + color: #666; + } } .date-tip { @@ -290,34 +548,55 @@ export default { .dish-item { padding: 16rpx 0; border-bottom: 1rpx solid #f2f3f7; + display: flex; + align-items: flex-start; + gap: 16rpx; } .dish-item:last-child { border-bottom: none; } +.dish-main { + flex: 1; + min-width: 0; +} + +.dish-info { + display: flex; + align-items: center; + gap: 16rpx; + margin-bottom: 4rpx; +} + .dish-name { font-size: 28rpx; color: #1f2937; font-weight: 500; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .dish-desc { - margin-top: 8rpx; color: #6b7280; - font-size: 24rpx; + font-size: 20rpx; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .dish-price { - margin-top: 8rpx; color: #ef4444; font-size: 24rpx; + white-space: nowrap; } .action-row { - margin-top: 12rpx; display: flex; gap: 16rpx; + white-space: nowrap; + align-self: center; } .action { @@ -326,6 +605,15 @@ export default { padding: 0 20rpx; border-radius: 26rpx; font-size: 24rpx; + display: flex; + align-items: center; + gap: 8rpx; +} + +.action-icon { + width: 28rpx; + height: 28rpx; + vertical-align: middle; } .like { @@ -342,6 +630,7 @@ export default { display: flex; align-items: center; justify-content: space-between; + margin-bottom: 18rpx; } .rank-tabs { @@ -401,7 +690,26 @@ export default { .rank-right { font-size: 26rpx; - color: #111827; + color: #6b7280; + display: flex; + align-items: center; + gap: 8rpx; + border-radius: 4rpx; + padding: 2rpx 4rpx; +} + +.rank-right.like { + color: #16a34a; +} + +.rank-right.dislike { + color: #dc2626; +} + +.rank-icon { + width: 28rpx; + height: 28rpx; + vertical-align: middle; } .empty { diff --git a/app/static/images/wg/dislike.png b/app/static/images/wg/dislike.png new file mode 100644 index 0000000..0f31abf Binary files /dev/null and b/app/static/images/wg/dislike.png differ diff --git a/app/static/images/wg/dislike_.png b/app/static/images/wg/dislike_.png new file mode 100644 index 0000000..0f8606d Binary files /dev/null and b/app/static/images/wg/dislike_.png differ diff --git a/app/static/images/wg/like.png b/app/static/images/wg/like.png new file mode 100644 index 0000000..6f6e232 Binary files /dev/null and b/app/static/images/wg/like.png differ diff --git a/app/static/images/wg/like_.png b/app/static/images/wg/like_.png new file mode 100644 index 0000000..31c20ac Binary files /dev/null and b/app/static/images/wg/like_.png differ diff --git a/app/utils/request.js b/app/utils/request.js index 41ebac9..2f340be 100644 --- a/app/utils/request.js +++ b/app/utils/request.js @@ -11,7 +11,7 @@ import { } from '../libs/login'; import store from '../store'; import JSONBig from 'json-bigint'; - +const JSONbigString = JSONBig({ storeAsString: true }); /** * 发送请求 @@ -47,16 +47,18 @@ function baseRequest(url, method, data, { method: method || 'GET', header: header, data: requestData, - dataType: 'json', + dataType: 'text', responseType: 'text', success: (res) => { let data = res.data; try { - // 使用 JSONBig 解析响应数据,处理大数值 - data = JSONBig.parse(res.data); + data = JSONbigString.parse(res.data); } catch (e) { // JSONBig 解析失败时,使用原数据 + console.warn('JSONBig 解析失败:', e); + data = JSON.parse(res.data); } + console.debug('接口返回数据:', data); if (noVerify) reslove(data, res); else if (data.code == 200)