后端(补齐前端已入口但无接口的模块): - Coupon 控制器:center 领券中心 / my 我的券(未使用·已使用·已过期+计数)/ receive 领取 含库存与过期校验、重复领取拦截、领券后自动发消息 - Message 控制器:index 列表(分类筛选)/ read 单条已读 / readAll 全部已读 / unread 角标 支持 user 与 master 两种归属 前端(对应页面,全部接真接口): - pages/address/list 服务地址:列表 / 新增·编辑弹层 / 设为默认 / 删除 - pages/coupon/coupon 领券中心 + 我的券(双 Tab + 状态筛选 + 计数) - pages/message/message 消息中心:分类 / 未读圆点 / 点开自动已读 / 全部已读 / 跳订单 - mine.vue 三个入口(地址·优惠券·消息)由「开发中」改为真实路由 - merchant/workbench 服务地址入口接真实路由 - 首页 TabBar「消息」接入消息中心 - api.js 新增 coupon/message 共 7 个接口 后台登录页(UI 与前端主色调统一): - 左侧橙金渐变品牌区:logo 徽标 + 「优匠优服」立体字 + slogan + 460万+/99.9%/1762万 平台实力 + 保障条 - 右侧白色登录卡:橙色渐变按钮、圆角输入框、错误提示条 - 保留 FastAdmin 全部挂钩(token/验证码/keeplogin/@AdminLoginFormBegin 等 14 项) 验证:5 套用例 178 项断言全通过(47+65+27+24+15),配置自检 8/8
94 行
3.5 KiB
Vue
94 行
3.5 KiB
Vue
<template>
|
||
<view class="page">
|
||
<view class="head">
|
||
<view class="cats">
|
||
<view v-for="c in cats" :key="c.key" class="cat" :class="{ on: category === c.key }" @tap="switchCat(c.key)">{{ c.name }}</view>
|
||
</view>
|
||
<text class="read-all" @tap="readAll">全部已读</text>
|
||
</view>
|
||
|
||
<view class="list">
|
||
<view class="msg" :class="{ unread: !m.is_read }" v-for="m in list" :key="m.id" @tap="open(m)">
|
||
<view class="msg-top">
|
||
<view class="dot" v-if="!m.is_read"></view>
|
||
<text class="title">{{ m.title }}</text>
|
||
<text class="time">{{ m.time_text }}</text>
|
||
</view>
|
||
<text class="content">{{ m.content }}</text>
|
||
<text class="go" v-if="m.order_id">查看订单 ›</text>
|
||
</view>
|
||
<view class="empty" v-if="!loading && !list.length">暂无消息</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref } from 'vue';
|
||
import { onShow, onPullDownRefresh } from '@dcloudio/uni-app';
|
||
import api from '@/utils/api.js';
|
||
import { isLogin, toLogin } from '@/utils/request.js';
|
||
|
||
const list = ref([]);
|
||
const loading = ref(false);
|
||
const category = ref('all');
|
||
const cats = [
|
||
{ key: 'all', name: '全部' },
|
||
{ key: 'order', name: '订单' },
|
||
{ key: 'system', name: '系统' },
|
||
{ key: 'remind', name: '提醒' }
|
||
];
|
||
|
||
async function load() {
|
||
if (!isLogin()) { toLogin(); return; }
|
||
loading.value = true;
|
||
try {
|
||
const res = await api.messageList({ category: category.value, limit: 30 });
|
||
list.value = res.list || [];
|
||
} catch (e) {
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
}
|
||
|
||
function switchCat(k) { category.value = k; load(); }
|
||
|
||
async function open(m) {
|
||
if (!m.is_read) {
|
||
try { await api.messageRead({ id: m.id }); m.is_read = 1; } catch (e) {}
|
||
}
|
||
if (m.order_id) {
|
||
uni.navigateTo({ url: '/pages/order/detail?id=' + m.order_id });
|
||
}
|
||
}
|
||
|
||
async function readAll() {
|
||
try {
|
||
const res = await api.messageReadAll({});
|
||
uni.showToast({ title: '已全部标记已读(' + res.count + ' 条)', icon: 'none' });
|
||
load();
|
||
} catch (e) {}
|
||
}
|
||
|
||
onShow(() => load());
|
||
onPullDownRefresh(() => { load().then(() => uni.stopPullDownRefresh()); });
|
||
</script>
|
||
|
||
<style scoped>
|
||
.page { min-height: 100vh; background: #F5F6F8; padding-bottom: 40rpx; }
|
||
.head { display: flex; align-items: center; background: #fff; padding: 20rpx 24rpx; box-shadow: 0 4rpx 12rpx rgba(0,0,0,.03); }
|
||
.cats { flex: 1; display: flex; }
|
||
.cat { margin-right: 14rpx; padding: 0 22rpx; line-height: 54rpx; border-radius: 27rpx; background: #F5F5F5; font-size: 24rpx; color: #666; }
|
||
.cat.on { background: #FFF3E6; color: #E36B0C; font-weight: 600; }
|
||
.read-all { font-size: 24rpx; color: #F0710D; }
|
||
.list { padding: 22rpx 28rpx; }
|
||
.msg { background: #fff; border-radius: 20rpx; padding: 22rpx 24rpx; margin-bottom: 18rpx; box-shadow: 0 6rpx 20rpx rgba(240,129,31,.06); }
|
||
.msg.unread { border-left: 6rpx solid #F0710D; }
|
||
.msg-top { display: flex; align-items: center; }
|
||
.dot { width: 12rpx; height: 12rpx; border-radius: 50%; background: #F0710D; margin-right: 10rpx; }
|
||
.title { flex: 1; min-width: 0; font-size: 28rpx; font-weight: 600; color: #1F1F1F; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
||
.time { flex-shrink: 0; margin-left: 12rpx; font-size: 20rpx; color: #B5B5B5; }
|
||
.content { display: block; margin-top: 10rpx; font-size: 24rpx; color: #666; line-height: 34rpx; }
|
||
.go { display: block; margin-top: 10rpx; font-size: 22rpx; color: #4A7CFF; }
|
||
.empty { padding: 110rpx 0; text-align: center; font-size: 24rpx; color: #9A9A9A; }
|
||
</style>
|