Compare commits

...

150 Commits

Author SHA1 Message Date
丁杰 573aef3210 服务发布 2026-06-23 09:55:51 +08:00
丁杰 3e39ff1140 Merge branch 'master' into mrr_sj_develop_丁杰_20260609_自定义服务发布 2026-06-22 14:43:11 +08:00
丁杰 ee20019de3 9800活动上线,营业资质非必填 2026-06-22 14:35:50 +08:00
丁杰 fd5923a4dc 修改request结构 2026-06-18 14:58:10 +08:00
丁杰 cd4373f91f 缺省图+非必填 2026-06-18 14:38:29 +08:00
BAKEYi 93133dc460 Merge branch 'master' of https://gitee.com/qtvbidt/mrr.sj.front 2026-06-18 14:06:04 +08:00
BAKEYi 466d1e4d2e 门店信息地址回显 2026-06-18 14:05:59 +08:00
丁杰 122c19909c 新增成长礼包活动页 2026-06-18 14:02:08 +08:00
丁杰 9ded6affa3 1 2026-06-16 14:25:52 +08:00
丁杰 c47c653bf6 Merge branch '改版' into master 2026-06-15 22:36:28 +08:00
丁杰 cc5f824858 个人中心校验 2026-06-15 21:33:34 +08:00
丁杰 177fbb35f9 bug修复,版本号升级。正式域名更换 2026-06-15 20:48:58 +08:00
丁杰 7a071227e7 精选品牌分类,技能审核中修复 2026-06-15 16:01:57 +08:00
丁杰 d01b04bce0 审核状态等一系列bug修复 2026-06-15 14:01:12 +08:00
丁杰 7fe55a5c6f 头像压缩 2026-06-12 11:33:33 +08:00
丁杰 bb183e89fc 修复首页登录监测 2026-06-12 10:45:45 +08:00
丁杰 0eb6237e41 核销流程优化 2026-06-10 11:21:43 +08:00
丁杰 b38847ada8 自营订单核销后跳转详情 2026-06-10 10:38:39 +08:00
丁杰 5578a25653 入驻流程优化 2026-06-09 13:54:44 +08:00
丁杰 b542e85624 1 2026-06-09 11:34:52 +08:00
丁杰 128ebf0124 修复+优化 2026-06-09 10:38:49 +08:00
丁杰 2fcfba42ea 修复若干bug 2026-06-08 17:53:00 +08:00
丁杰 f72f164fd8 作者主页不显示筛选 2026-06-08 11:47:07 +08:00
丁杰 e96c468c38 个人中心图标替换,收藏页面筛选按钮条件显示,入住第二步弹窗按钮颜色 2026-06-08 11:12:16 +08:00
丁杰 a28cc75da8 修复 2026-06-05 18:20:19 +08:00
丁杰 181a93e314 初次提交被驳回逻辑优化 2026-06-05 15:27:42 +08:00
丁杰 5ac5be413b 修复bug 2026-06-05 13:09:01 +08:00
丁杰 62632e0aff UI改样式 2026-06-04 15:58:34 +08:00
丁杰 adf6cea0b7 修复若干bug 2026-06-04 15:00:22 +08:00
BAKEYi 5c42041cbd bug修复 2026-06-04 13:54:54 +08:00
BAKEYi f7755041ad 1 2026-06-04 13:21:49 +08:00
BAKEYi 6364072991 1 2026-06-04 13:21:36 +08:00
BAKEYi 89d53394f1 Merge branch 'mrr_sj_develop_hjy_20260520_商家改版' of https://gitee.com/qtvbidt/mrr.sj.front into mrr_sj_develop_hjy_20260520_商家改版 2026-06-04 11:51:32 +08:00
BAKEYi 2851b8e13b 收藏页tabbar删除 2026-06-04 11:51:30 +08:00
丁杰 d8d6be6206 修复若干bug 2026-06-04 11:45:11 +08:00
BAKEYi fc3bd046e9 粉色图标变更 2026-06-04 10:51:59 +08:00
BAKEYi d6d5f533cf 万元计算 2026-06-03 17:54:37 +08:00
丁杰 1e56c78f12 feat: 完成商家改版相关页面与请求工具修改 2026-06-03 17:49:43 +08:00
BAKEYi d5d7242ec7 1 2026-06-03 17:49:04 +08:00
BAKEYi 3779b9f753 Merge branch 'mrr_sj_develop_hjy_20260520_商家改版' of https://gitee.com/qtvbidt/mrr.sj.front into mrr_sj_develop_hjy_20260520_商家改版 2026-06-03 16:54:20 +08:00
BAKEYi 12826becd5 订单页首次进入不显示+重复点击相同tab会清空数据bug 2026-06-03 16:54:10 +08:00
丁杰 7549907688 配置项修改,学员招募文字修改,认证状态修改。 2026-06-03 16:38:56 +08:00
BAKEYi 811a72cf34 核销页(扫码+手动输入码)缺失参数bug 2026-06-03 16:37:31 +08:00
丁杰 c51f20a320 重置入驻步骤 2026-06-03 14:13:17 +08:00
丁杰 6b85f31b62 技能列表参数修改 2026-06-03 11:56:59 +08:00
BAKEYi 653858b9f8 相关推荐数据显示问题 2026-06-03 11:51:10 +08:00
丁杰 c54fb1f2a9 技能推荐 2026-06-03 11:40:40 +08:00
BAKEYi 14e47ccf0e 品牌列表接口orderTyoe传值bug 2026-06-03 11:26:56 +08:00
BAKEYi 5a34bde3b7 Merge branch 'mrr_sj_develop_hjy_20260520_商家改版' of https://gitee.com/qtvbidt/mrr.sj.front into mrr_sj_develop_hjy_20260520_商家改版 2026-06-02 18:32:18 +08:00
BAKEYi 18805583c1 收藏筛选类目 2026-06-02 18:32:15 +08:00
丁杰 6ae7b4d889 feat: 完成商家改版相关页面与请求工具修改 2026-06-02 18:22:19 +08:00
BAKEYi 753c9932aa 我的收藏列表+富文本处理 2026-06-02 18:21:35 +08:00
丁杰 ea6ad65b86 入住优化 2026-06-02 16:14:19 +08:00
BAKEYi ceff1c73d6 1 2026-06-02 15:59:08 +08:00
BAKEYi 45f6215e2f 1 2026-06-02 15:46:40 +08:00
BAKEYi e253399699 Merge branch 'mrr_sj_develop_hjy_20260520_商家改版' of https://gitee.com/qtvbidt/mrr.sj.front into mrr_sj_develop_hjy_20260520_商家改版 2026-06-02 15:25:12 +08:00
BAKEYi 55fd66f872 接口变更 2026-06-02 15:25:06 +08:00
丁杰 9610fe2679 feat: 完成商家改版相关页面与请求工具修改 2026-06-02 15:23:48 +08:00
BAKEYi 85fbbd0990 下拉刷新bug 2026-06-02 15:08:56 +08:00
BAKEYi fc78b157a9 邀请码确认弹窗 2026-06-02 15:02:18 +08:00
BAKEYi b03ffe7225 接口更换 2026-06-02 14:16:40 +08:00
BAKEYi 00833b4208 订单相关 2026-06-02 13:08:29 +08:00
BAKEYi 8299492d12 主题色变更 2026-06-02 11:39:23 +08:00
BAKEYi f65e18ea3d 营业时间页面 2026-06-02 10:26:55 +08:00
BAKEYi 917a0af084 门店信息中专业经验修改到服务技能页面 2026-06-01 18:18:08 +08:00
BAKEYi a800372ad2 认证步骤逻辑优化 2026-06-01 17:27:11 +08:00
BAKEYi 6d6c279731 Merge branch 'mrr_sj_develop_hjy_20260520_商家改版' of https://gitee.com/qtvbidt/mrr.sj.front into mrr_sj_develop_hjy_20260520_商家改版 2026-06-01 17:21:54 +08:00
BAKEYi d6bae3034b 入驻步骤逻辑 2026-06-01 17:21:51 +08:00
丁杰 fa17e04f30 入驻提示 2026-06-01 17:21:44 +08:00
丁杰 2ecd26d609 入住状态回显 2026-06-01 16:36:47 +08:00
BAKEYi 92637b114f 新域名 2026-06-01 16:02:17 +08:00
BAKEYi 0a8e130c33 Merge branch 'mrr_sj_develop_hjy_20260520_商家改版' of https://gitee.com/qtvbidt/mrr.sj.front into mrr_sj_develop_hjy_20260520_商家改版 2026-06-01 13:55:31 +08:00
BAKEYi 8d885c3ec5 1 2026-06-01 13:54:04 +08:00
BAKEYi e94a376395 技能集市跳转逻辑 2026-06-01 13:54:01 +08:00
丁杰 e79c8eda8a 入驻流程优化 2026-06-01 13:53:15 +08:00
丁杰 21425c0306 修改参数 2026-06-01 11:16:19 +08:00
丁杰 ce03c0de2b 暂停 2026-05-29 15:54:23 +08:00
丁杰 2e13ec14aa feat: 完成商家改版相关页面与请求工具修改 2026-05-29 14:01:18 +08:00
BAKEYi d723a5f2db 1 2026-05-29 13:51:41 +08:00
BAKEYi 87fae84240 换域名+品牌卡片、技能集市组件跳转详情逻辑+品牌详情页面 2026-05-29 13:50:58 +08:00
BAKEYi d3ae42b664 1 2026-05-28 16:15:59 +08:00
BAKEYi b67b581775 bug修复 2026-05-28 16:09:50 +08:00
BAKEYi 95f097f8c9 调整样式 2026-05-28 13:32:28 +08:00
BAKEYi 1184d73a75 搜索组件优化 排序逻辑优化 2026-05-28 13:13:45 +08:00
BAKEYi dda0f4d7d6 Merge branch 'mrr_sj_develop_hjy_20260520_商家改版' of https://gitee.com/qtvbidt/mrr.sj.front into mrr_sj_develop_hjy_20260520_商家改版 2026-05-28 11:46:18 +08:00
BAKEYi ab99d37f99 Merge branch 'mrr_sj_develop_hjy_20260520_商家改版' of https://gitee.com/qtvbidt/mrr.sj.front into mrr_sj_develop_hjy_20260520_商家改版 2026-05-28 11:46:15 +08:00
BAKEYi 6c5347bb9c Merge branch 'mrr_sj_develop_hjy_20260520_商家改版' of https://gitee.com/qtvbidt/mrr.sj.front into mrr_sj_develop_hjy_20260520_商家改版 2026-05-28 11:40:00 +08:00
BAKEYi c4c134417a 1 2026-05-28 11:39:58 +08:00
丁杰 27d4107679 入住信息修改 2026-05-28 11:38:59 +08:00
丁杰 66760ee72a 入住信息修改 2026-05-28 11:38:03 +08:00
BAKEYi b6945acf9b 首页、公告列表与详情、两个精选、我的收藏、总搜索组件 2026-05-28 10:41:27 +08:00
BAKEYi a9455ce98d tabbar bug 2026-05-27 16:38:14 +08:00
BAKEYi 1d611bee0b tabbar bug修复 2026-05-27 15:45:10 +08:00
BAKEYi bbe660cbfc syr改sj bug修复 2026-05-27 15:04:43 +08:00
BAKEYi c0f5ca513b 1.首页未入驻状态更新
2.精选品牌列表页面
3.技能集市列表页面
2026-05-27 14:52:36 +08:00
BAKEYi 2f9055454c 我的收藏页面+技能集市组件 2026-05-26 18:06:07 +08:00
BAKEYi f6ed231d12 打开相册又出现扫码的bug 2026-05-26 14:29:00 +08:00
BAKEYi 0c6ce09506 扫码正确逻辑 2026-05-26 14:04:05 +08:00
BAKEYi 8895bef4e1 Merge branch 'mrr_sj_develop_hjy_20260520_商家改版' of https://gitee.com/qtvbidt/mrr.sj.front into mrr_sj_develop_hjy_20260520_商家改版 2026-05-26 13:59:26 +08:00
BAKEYi 3c3159d5d8 核销未成功页面+扫码全流程 2026-05-26 13:58:25 +08:00
BAKEYi d008710130 公告通知+核销扫描 2026-05-26 10:12:26 +08:00
BAKEYi c2978b30db 1 2026-05-22 15:51:48 +08:00
BAKEYi af91692ba6 Merge branch 'mrr_sj_develop_hjy_20260520_商家改版' of https://gitee.com/qtvbidt/mrr.sj.front into mrr_sj_develop_hjy_20260520_商家改版 2026-05-22 14:36:52 +08:00
BAKEYi 057147d385 商家改版-首页静态页面 2026-05-22 14:36:49 +08:00
丁杰 d6f09178ac 个人中心图标修改,新增我的收藏页面 2026-05-21 16:52:23 +08:00
BAKEYi 4b51811351 测试环境 2026-05-21 11:52:07 +08:00
BAKEYi 624888f58c Merge branch 'mrr_sj_develop_丁杰_20260413_pricer' of https://gitee.com/qtvbidt/mrr.sj.front into mrr_sj_develop_丁杰_20260413_pricer 2026-05-20 16:45:10 +08:00
BAKEYi d5690bc51b 升级版本 2026-05-20 16:44:16 +08:00
cjl520cy 4e21e22bdf bug修改 2026-05-13 17:41:13 +08:00
BAKEYi a5180be20f 13131313邀请码弹窗修改 2026-05-06 18:17:27 +08:00
丁杰 2836b0712f 视频时长限制 2026-05-06 15:18:55 +08:00
丁杰 3e9235844a 限制视频时长和大小 2026-05-06 15:01:38 +08:00
丁杰 659ec0f473 工位订单日期时间显示修改 2026-05-06 13:25:33 +08:00
BAKEYi 665ca23d3b 商家端邀请码自动填充不可更改 2026-04-30 16:58:04 +08:00
BAKEYi 0130ad939d 驳回原因显示bug 2026-04-30 14:56:42 +08:00
BAKEYi 5bb78163b4 样式修改 2026-04-30 14:47:48 +08:00
BAKEYi 7b54ba6749 商家邀请码弹窗bug 2026-04-30 14:46:11 +08:00
cjl520cy ad97d5683c bug修改 2026-04-30 11:17:25 +08:00
cjl520cy 8347f81269 bug修改 2026-04-29 18:17:50 +08:00
cjl520cy 54cb3b744d Merge branch 'mrr_sj_develop_丁杰_20260413_pricer' of https://gitee.com/qtvbidt/mrr.sj.front into mrr_sj_develop_丁杰_20260413_pricer 2026-04-29 16:41:24 +08:00
cjl520cy 7618492df5 bug修改 2026-04-29 16:41:21 +08:00
BAKEYi 7399719cdf Merge branch 'mrr_sj_develop_丁杰_20260413_pricer' of https://gitee.com/qtvbidt/mrr.sj.front into mrr_sj_develop_丁杰_20260413_pricer 2026-04-29 16:06:36 +08:00
BAKEYi 46e0975e89 商家邀请码bug相关 2026-04-29 16:06:33 +08:00
cjl520cy 83b1a6f1a6 Merge branch 'mrr_sj_develop_丁杰_20260413_pricer' of https://gitee.com/qtvbidt/mrr.sj.front into mrr_sj_develop_丁杰_20260413_pricer 2026-04-28 17:48:52 +08:00
cjl520cy f5e3e96be8 bug修改 2026-04-28 17:48:49 +08:00
BAKEYi 8aed4815fd Merge branch 'mrr_sj_develop_丁杰_20260413_pricer' of https://gitee.com/qtvbidt/mrr.sj.front into mrr_sj_develop_丁杰_20260413_pricer 2026-04-28 14:56:17 +08:00
BAKEYi f4e9bcc0f6 修改佣金温馨提示 2026-04-28 14:56:14 +08:00
cjl520cy 0d66d41f15 Merge branch 'mrr_sj_develop_丁杰_20260413_pricer' of https://gitee.com/qtvbidt/mrr.sj.front into mrr_sj_develop_丁杰_20260413_pricer 2026-04-24 17:54:28 +08:00
cjl520cy 9de417bdfc 播放视频页面添加 2026-04-24 17:54:24 +08:00
BAKEYi 7a5181a638 商家工位、工时订单因接口新加字段导致的页面不能正常显示bug修复 2026-04-23 10:23:34 +08:00
BAKEYi 64dea3ff2b Merge branch 'mrr_sj_develop_丁杰_20260413_pricer' of https://gitee.com/qtvbidt/mrr.sj.front into mrr_sj_develop_丁杰_20260413_pricer 2026-04-23 10:13:30 +08:00
BAKEYi a159fbdb22 商家工位订单因接口新加字段导致的页面不能正常显示bug修复 2026-04-23 10:13:27 +08:00
cjl520cy f1e4ee07b0 bug修改 2026-04-22 17:55:06 +08:00
cjl520cy aa43bcf4fd bug修改 2026-04-22 16:50:09 +08:00
BAKEYi d25533df33 Merge branch 'mrr_sj_develop_丁杰_20260413_pricer' of https://gitee.com/qtvbidt/mrr.sj.front into mrr_sj_develop_丁杰_20260413_pricer 2026-04-21 19:06:54 +08:00
BAKEYi 211b33f63f bug修复 2026-04-21 19:06:52 +08:00
cjl520cy e500cb9e71 Merge branch 'mrr_sj_develop_丁杰_20260413_pricer' of https://gitee.com/qtvbidt/mrr.sj.front into mrr_sj_develop_丁杰_20260413_pricer 2026-04-21 17:07:32 +08:00
cjl520cy 0e3e1fb5fa bug修改 2026-04-21 17:07:28 +08:00
BAKEYi 95f880a251 bug修复 2026-04-20 17:56:29 +08:00
BAKEYi c5896dc853 Merge branch 'mrr_sj_develop_丁杰_20260413_pricer' of https://gitee.com/qtvbidt/mrr.sj.front into mrr_sj_develop_丁杰_20260413_pricer 2026-04-20 15:42:23 +08:00
BAKEYi 49b50dc1f0 商家bug表 2026-04-20 15:42:20 +08:00
cjl520cy 90de758ff0 Merge branch 'mrr_sj_develop_丁杰_20260413_pricer' of https://gitee.com/qtvbidt/mrr.sj.front into mrr_sj_develop_丁杰_20260413_pricer 2026-04-20 15:37:11 +08:00
cjl520cy 7fabbd8549 bug修改 2026-04-20 15:37:07 +08:00
BAKEYi 3e50131bdb Merge branch 'mrr_sj_develop_丁杰_20260413_pricer' of https://gitee.com/qtvbidt/mrr.sj.front into mrr_sj_develop_丁杰_20260413_pricer 2026-04-20 13:27:17 +08:00
BAKEYi b584f8f97b 工时记录加上门地址 2026-04-20 13:27:15 +08:00
cjl520cy b4ebbd533c bug修改 2026-04-20 11:06:14 +08:00
cjl520cy f560bcb69d 删除添加服务错误的弹窗 2026-04-20 10:41:15 +08:00
cjl520cy 4dc74695de bug修改 2026-04-19 15:57:27 +08:00
cjl520cy 7bca1214ae bug修改 2026-04-19 15:14:59 +08:00
BAKEYi 0c411882d9 bug修改 2026-04-17 18:45:30 +08:00
258 changed files with 33801 additions and 20888 deletions

519
App.vue
View File

@ -1,276 +1,263 @@
<script>
// #ifdef APP-PLUS
import {
isNotificationsEnabled,
permissions,
registPush
} from '@/utils/AS-NotifyTheAuthority.js'
import {
permissionListener
} from './utils/permissionTips.js'
// import { appUpdateListener } from '@/common/utils/appUpdateTips.js'
import locationService from './utils/locationService.js'
// import uploadApp from './utils/uploadApp.js'
// #endif
import request from './utils/request'
import {
setVersion
} from './utils/version.js'
//
const aLiSDKModule = uni.requireNativePlugin('AliCloud-NirvanaPns');
export default {
globalData: {
artisanType: 2,
latitude: '38.366667',
longitude: '117.333333',
address: '黄骅市',
servers_region: `河北省-沧州市-黄骅市`,
deviceid: '',
addressRes: {
"type": "gcj02",
"altitude": 0,
"latitude": 38.372266,
"longitude": 117.332895,
"speed": 0,
"accuracy": 30,
// #ifdef APP-PLUS
import {
isNotificationsEnabled,
permissions,
registPush
} from '@/utils/AS-NotifyTheAuthority.js'
import {
permissionListener
} from './utils/permissionTips.js'
// import { appUpdateListener } from '@/common/utils/appUpdateTips.js'
import locationService from './utils/locationService.js'
// import uploadApp from './utils/uploadApp.js'
// #endif
import request from './utils/request'
import {
setVersion
} from './utils/version.js'
import {
APP_BACK_BASE_URL
} from './utils/appBackBase.js'
//
const aLiSDKModule = uni.requireNativePlugin('AliCloud-NirvanaPns');
export default {
globalData: {
artisanType: 2,
latitude: '38.366667',
longitude: '117.333333',
address: '黄骅市',
servers_region: `河北省-沧州市-黄骅市`,
deviceid: '',
addressRes: {
"type": "gcj02",
"altitude": 0,
"latitude": 38.372266,
"longitude": 117.332895,
"speed": 0,
"accuracy": 30,
"adcode": "130983",
"cCode": "130900",
"pcode": "130000",
"address": {
"country": "中国",
"province": "河北省",
"city": "沧州市",
"district": "黄骅市",
"street": "渤海西路",
"streetNum": "35号",
// "poiName": "",
"poiName": "黄骅市人民政府",
"cityCode": "0317",
"adcode": "130983",
"cCode": "130900",
"pcode": "130000",
"address": {
"country": "中国",
"province": "河北省",
"city": "沧州市",
"district": "黄骅市",
"street": "渤海西路",
"streetNum": "35号",
// "poiName": "",
"poiName": "黄骅市人民政府",
"cityCode": "0317",
"adcode": "130983",
"cCode": "130900",
"pcode": "130000",
}
}
},
onLaunch: async function() {
// #ifdef APP-PLUS
uni.hideTabBar();
if (uni.getStorageSync('accessToken')) {
registPush()
//
this.messageUpudateNum()
}
// #endif
//
// await setVersion()
//访
// GET
let version = uni.getStorageSync('setVersion')
let baseUrl
await uni.request({
url: 'https://api.mrrwlkj.top/api/openPuc/getSjApiUrl', //
method: 'GET', // GET
header: {
'Content-Type': 'application/json', //
'VersionCode': version.VersionCode,
'DeviceBrand': version.DeviceBrand,
'DeviceType': version.DeviceType
},
success: (res) => {
if (res.data.code == 200) {
// #ifndef MP-WEIXIN
uni.setStorageSync('baseUrl', res.data.data.url)
// #endif
// #ifdef MP-WEIXIN
wx.setStorageSync('baseUrl', res.data.data.url)
// #endif
this.$store.commit("setLocation", true)
}
console.log('请求成功', res.data);
},
fail: (err) => {
console.log('请求失败', err);
},
complete: () => {
console.log('请求完成(无论成功失败都会执行)');
}
});
// #ifdef APP-PLUS
//
plus.screen.lockOrientation("portrait-primary")
// #endif
// #ifdef APP-PLUS
//
let systemInfo = uni.getSystemInfoSync();
let platform = systemInfo.platform;
if (platform === 'android') {
// console.log('')
permissionListener()
} else if (platform === 'ios') {
// console.log('')
} else {
// console.log('')
}
//
let sdkInfo = '';
//
if (platform == 'android') {
//SDK
aLiSDKModule.setLoggerEnable(true);
aLiSDKModule.expandAuthPageCheckedScope(true);
//
//aLiSDKModule.closeAuthPageReturnBack(true);
//
aLiSDKModule.userControlAuthPageCancel();
//
aLiSDKModule.setAuthPageUseDayLight(false);
sdkInfo =
'cvJWYFO9s7xgJrzD+Ok2m2kCh3x0kdRkfJzGI0oxnyUXGlNAwAzvj87Yd6y030BWposVHUT84av2adBG9SdHl5CJpL0mGi+BRtachnyADJjfdpkJC0e0Uqb45pO7h7ZZvT+sNWQs1sa4JpL0U7hYKkgCP1iVXHoA1zKfzZynON6eKyvkzL1xpoPPag4O50slww4+6XuRVplCOSNCcsLRtot4CYnfsdcMvlV34MY5qB/rV+HNb1velpx4rg5atk3iTEiSLjla69c6pOT6vpjkK1qFVpNMxrEY';
} else if (platform == 'ios') {
sdkInfo =
'BONXQSabNMt1a8Cru1TjeW94aXen5rQHeAxzLGgpFzIUicDYeb9SD0l35MkKvjq7gQemE2HDp6B7adKZ8nrdUk4wgg1LIYSGDYVSc/Q1CwrJ9A3lMUeBfS1UUu3nBBg58bDvG1bl3j6tS4h6FjnYmP5+dDsyqb0YNmIhlSmia5Et7cfBMv2OT85N9WLJc/tOMQ9ZQCnuizKVlofkmDGFgjsBN/Bf2gLq6lTQyVqsgPJE5yEPd4riNg=='
}
aLiSDKModule.setAuthSDKInfo(sdkInfo)
// await uploadApp.uploadApp('onLaunch');
// #endif
console.log('App Launch')
this.globalData.deviceId = uni.getDeviceInfo().deviceId;
// this.globalData.deviceid = 'AC6F605124DFA87DCE40D5C062C94C3B';
// #ifndef MP-WEIXIN
if (uni.getStorageSync('refreshToken')) {
let endTime = uni.getStorageSync('refresh_token_expries');
endTime = new Date(endTime)
//
var nowTime = new Date();
var residueTime = (endTime.getTime() - nowTime.getTime() / 1000);
// 4 14400
if (residueTime < 4 * 60 * 60) {
console.log('----------------------------')
uni.removeStorageSync('accessToken')
uni.removeStorageSync('refreshToken')
}
}
// #endif
// #ifdef MP-WEIXIN
if (wx.getStorageSync('refreshToken')) {
let endTime = wx.getStorageSync('refresh_token_expries');
endTime = new Date(endTime)
//
var nowTime = new Date();
var residueTime = (endTime.getTime() - nowTime.getTime() / 1000);
// 4 14400
if (residueTime < 4 * 60 * 60) {
console.log('----------------------------')
wx.removeStorageSync('accessToken')
wx.removeStorageSync('refreshToken')
}
}
// #endif
},
onShow: function() {
uni.hideTabBar();
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
},
methods: {
//
verifyLogin() {
if (!vuex.state.userInfo.token) {
uni.showModal({
title: '提示',
content: '您需要登陆后才能进行此操作',
confirmColor: '#F1AC66',
success(e) {
if (e.confirm) {
// uni.oprPresentLogin()
uni.navigateTo({
url: '/pages/blogPopup/blogPopup'
});
}
}
})
} else {
return true
}
},
/**
* /县adcode转换为6位市adcode后两位补0
* @param {string|number} districtAdcode - 6位区/县编码字符串或数字类型
* @returns {string|null} 6位市编码无效编码返回null
*/
districtAdcodeTo6DigitCityAdcode(districtAdcode) {
// 1.
const adcodeStr = String(districtAdcode).trim();
// 2. 6
const adcodeReg = /^\d{6}$/;
if (!adcodeReg.test(adcodeStr)) {
console.warn('无效的区/县编码必须是6位数字');
return null;
}
// 3. 4 + "00" 6
const cityAdcode4Digit = adcodeStr.slice(0, 4);
const cityAdcode6Digit = cityAdcode4Digit + '00';
// 4. 6
if (!/^\d{6}$/.test(cityAdcode6Digit)) {
console.warn('编码转换异常');
return null;
}
return cityAdcode6Digit;
},
//
updateAddress(item) {
console.log('item', item)
let location = item.location.split(',');
this.globalData.latitude = location[1];
this.globalData.longitude = location[0];
this.globalData.address = item.name;
this.globalData.servers_region = `${item.pname}-${item.cityname}-${item.adname}`;
this.globalData.pcode = item.pcode;
this.globalData.cCode = this.districtAdcodeTo6DigitCityAdcode(item.adcode);
this.globalData.adcode = item.adcode;
//
this.globalData.addressRes.latitude = location[1];
this.globalData.addressRes.longitude = location[0];
this.globalData.addressRes.address.province = item.pname;
this.globalData.addressRes.address.city = item.cityname;
this.globalData.addressRes.address.district = item.adname;
this.globalData.addressRes.address.street = item.address;
this.globalData.addressRes.address.poiName = item.name;
this.globalData.addressRes.address.streetNum = '';
this.globalData.addressRes.address.cityCode = '';
this.globalData.addressRes.address.pcode = item.pcode;
this.globalData.addressRes.address.cCode = this.districtAdcodeTo6DigitCityAdcode(item.adcode);
this.globalData.addressRes.address.adcode = item.adcode;
this.globalData.addressRes.pcode = item.pcode;
this.globalData.addressRes.cCode = this.districtAdcodeTo6DigitCityAdcode(item.adcode);
this.globalData.addressRes.adcode = item.adcode;
console.log('globalData', this.globalData)
uni.setStorageSync('userAdrees', this.globalData)
},
}
},
onLaunch: async function () {
// #ifdef APP-PLUS
uni.hideTabBar();
if (uni.getStorageSync('accessToken')) {
registPush()
//
this.messageUpudateNum()
}
// #endif
//
// await setVersion()
//访
// #ifndef MP-WEIXIN
uni.setStorageSync('baseUrl', APP_BACK_BASE_URL)
// #endif
// #ifdef MP-WEIXIN
wx.setStorageSync('baseUrl', APP_BACK_BASE_URL)
// #endif
this.$store.commit("setLocation", true)
// #ifdef APP-PLUS
//
plus.screen.lockOrientation("portrait-primary")
// #endif
// #ifdef APP-PLUS
//
let systemInfo = uni.getSystemInfoSync();
let platform = systemInfo.platform;
if (platform === 'android') {
// console.log('')
permissionListener()
} else if (platform === 'ios') {
// console.log('')
} else {
// console.log('')
}
//
let sdkInfo = '';
//
if (platform == 'android') {
//SDK
aLiSDKModule.setLoggerEnable(true);
aLiSDKModule.expandAuthPageCheckedScope(true);
//
//aLiSDKModule.closeAuthPageReturnBack(true);
//
aLiSDKModule.userControlAuthPageCancel();
//
aLiSDKModule.setAuthPageUseDayLight(false);
sdkInfo =
'cvJWYFO9s7xgJrzD+Ok2m2kCh3x0kdRkfJzGI0oxnyUXGlNAwAzvj87Yd6y030BWposVHUT84av2adBG9SdHl5CJpL0mGi+BRtachnyADJjfdpkJC0e0Uqb45pO7h7ZZvT+sNWQs1sa4JpL0U7hYKkgCP1iVXHoA1zKfzZynON6eKyvkzL1xpoPPag4O50slww4+6XuRVplCOSNCcsLRtot4CYnfsdcMvlV34MY5qB/rV+HNb1velpx4rg5atk3iTEiSLjla69c6pOT6vpjkK1qFVpNMxrEY';
} else if (platform == 'ios') {
sdkInfo =
'BONXQSabNMt1a8Cru1TjeW94aXen5rQHeAxzLGgpFzIUicDYeb9SD0l35MkKvjq7gQemE2HDp6B7adKZ8nrdUk4wgg1LIYSGDYVSc/Q1CwrJ9A3lMUeBfS1UUu3nBBg58bDvG1bl3j6tS4h6FjnYmP5+dDsyqb0YNmIhlSmia5Et7cfBMv2OT85N9WLJc/tOMQ9ZQCnuizKVlofkmDGFgjsBN/Bf2gLq6lTQyVqsgPJE5yEPd4riNg=='
}
aLiSDKModule.setAuthSDKInfo(sdkInfo)
// await uploadApp.uploadApp('onLaunch');
// #endif
console.log('App Launch')
this.globalData.deviceId = uni.getDeviceInfo().deviceId;
// this.globalData.deviceid = 'AC6F605124DFA87DCE40D5C062C94C3B';
// #ifndef MP-WEIXIN
if (uni.getStorageSync('refreshToken')) {
let endTime = uni.getStorageSync('refresh_token_expries');
endTime = new Date(endTime)
//
var nowTime = new Date();
var residueTime = (endTime.getTime() - nowTime.getTime() / 1000);
// 4 14400
if (residueTime < 4 * 60 * 60) {
console.log('----------------------------')
uni.removeStorageSync('accessToken')
uni.removeStorageSync('refreshToken')
}
}
// #endif
// #ifdef MP-WEIXIN
if (wx.getStorageSync('refreshToken')) {
let endTime = wx.getStorageSync('refresh_token_expries');
endTime = new Date(endTime)
//
var nowTime = new Date();
var residueTime = (endTime.getTime() - nowTime.getTime() / 1000);
// 4 14400
if (residueTime < 4 * 60 * 60) {
console.log('----------------------------')
wx.removeStorageSync('accessToken')
wx.removeStorageSync('refreshToken')
}
}
// #endif
},
onShow: function () {
uni.hideTabBar();
console.log('App Show')
uni.$emit('appShowRefresh')
},
onHide: function () {
console.log('App Hide')
},
methods: {
//
verifyLogin() {
if (!vuex.state.userInfo.token) {
uni.showModal({
title: '提示',
content: '您需要登陆后才能进行此操作',
confirmColor: '#F1AC66',
success(e) {
if (e.confirm) {
// uni.oprPresentLogin()
uni.navigateTo({
url: '/pages/blogPopup/blogPopup'
});
}
}
})
} else {
return true
}
},
/**
* /县adcode转换为6位市adcode后两位补0
* @param {string|number} districtAdcode - 6位区/县编码字符串或数字类型
* @returns {string|null} 6位市编码无效编码返回null
*/
districtAdcodeTo6DigitCityAdcode(districtAdcode) {
// 1.
const adcodeStr = String(districtAdcode).trim();
// 2. 6
const adcodeReg = /^\d{6}$/;
if (!adcodeReg.test(adcodeStr)) {
console.warn('无效的区/县编码必须是6位数字');
return null;
}
// 3. 4 + "00" 6
const cityAdcode4Digit = adcodeStr.slice(0, 4);
const cityAdcode6Digit = cityAdcode4Digit + '00';
// 4. 6
if (!/^\d{6}$/.test(cityAdcode6Digit)) {
console.warn('编码转换异常');
return null;
}
return cityAdcode6Digit;
},
//
updateAddress(item) {
console.log('item', item)
let location = item.location.split(',');
this.globalData.latitude = location[1];
this.globalData.longitude = location[0];
this.globalData.address = item.name;
this.globalData.servers_region = `${item.pname}-${item.cityname}-${item.adname}`;
this.globalData.pcode = item.pcode;
this.globalData.cCode = this.districtAdcodeTo6DigitCityAdcode(item.adcode);
this.globalData.adcode = item.adcode;
//
this.globalData.addressRes.latitude = location[1];
this.globalData.addressRes.longitude = location[0];
this.globalData.addressRes.address.province = item.pname;
this.globalData.addressRes.address.city = item.cityname;
this.globalData.addressRes.address.district = item.adname;
this.globalData.addressRes.address.street = item.address;
this.globalData.addressRes.address.poiName = item.name;
this.globalData.addressRes.address.streetNum = '';
this.globalData.addressRes.address.cityCode = '';
this.globalData.addressRes.address.pcode = item.pcode;
this.globalData.addressRes.address.cCode = this.districtAdcodeTo6DigitCityAdcode(item.adcode);
this.globalData.addressRes.address.adcode = item.adcode;
this.globalData.addressRes.pcode = item.pcode;
this.globalData.addressRes.cCode = this.districtAdcodeTo6DigitCityAdcode(item.adcode);
this.globalData.addressRes.adcode = item.adcode;
console.log('globalData', this.globalData)
uni.setStorageSync('userAdrees', this.globalData)
},
}
}
</script>
<style>
/*每个页面公共css */
@import '@/static/css/common.css';
</style>
/*每个页面公共css */
@import '@/static/css/common.css';
/* App.vue全局style */
page,
html,
body {
-webkit-text-size-adjust: 100% !important;
text-size-adjust: 100% !important;
}
</style>

92
CHANGE_RECORD.md Normal file
View File

@ -0,0 +1,92 @@
# 改动记录
## 2026-06-12
### 后台长时间运行后首页登录态不同步
问题现象:
- APP 后台运行较长时间后再次进入。
- 商家首页显示未登录或无法及时获取商家信息。
- “我的”页面仍显示已登录。
- 从“我的”页面切回首页后,首页才能正常获取商家信息。
涉及文件:
- `App.vue`
- `pages/home/home.vue`
- `utils/request.js`
改动内容:
- `App.vue`
- 在全局 `onShow` 中新增 `uni.$emit('appShowRefresh')`
- APP 从后台恢复前台时主动通知页面刷新状态,避免只依赖页面级 `onShow`
- `pages/home/home.vue`
- 新增 `refreshPageState` 方法,统一处理首页登录态刷新。
- `onShow`、下拉刷新、APP 前台恢复事件都调用同一套刷新逻辑。
- `onLoad` 注册 `appShowRefresh` 事件。
- `onUnload` 解除 `appShowRefresh` 事件,避免重复监听。
- 有 token 时主动重新请求 `/sj/user/getUser`
- 无 token 时清空 `userInfo`、`authDetails`、`sjId`,并重置 `isUserInfoLoaded`
- `getUserInfo` 请求成功后显式设置 `isLogin = true``isUserInfoLoaded = true`
- `getUserInfo` 请求失败时,如果本地 token 已被清理,则同步首页为未登录状态。
- `utils/request.js`
- `refreshToken` 兼容后端返回结构:
- 支持 token 在 `res.access_token`
- 支持 token 在 `res.data.access_token`
- 刷新 token 成功后同步更新:
- `accessToken`
- `refreshToken`
- `refresh_token_expries`
- 如果刷新接口成功但没有返回 `access_token`,主动抛错,避免写入 `undefined` token。
验证情况:
- 已检查相关 diff未做全文件重构。
- `pages/home/home.vue` 原文件为 CRLF 行尾;为避免整文件行尾改造造成大面积 diff本次保留原行尾。
- 项目 `package.json` 未配置 build/lint 脚本,未执行完整构建校验。
### 订单详情顾客头像被挤压
问题现象:
- `pages/shop/SellerDetail` 页面“顾客信息”一栏中,客户头像在部分图片比例下显示被拉伸或挤压。
涉及文件:
- `pages/shop/SellerDetail.vue`
改动内容:
- 给顾客头像 `<image>` 增加 `mode="aspectFill"`,按头像容器比例裁剪填充,避免原图比例不一致时被拉伸。
- 为 `.shop_photo` 增加 `min-width``min-height`,在 flex 布局中进一步锁定头像为固定正方形尺寸。
验证情况:
- 已执行 `pages/shop/SellerDetail.vue``CHANGE_RECORD.md` 的空白格式检查。
### 核销成功后工时/工位订单详情跳转错误
问题现象:
- 商家扫码核销或输入核销码核销成功后,工时订单和工位订单跳转到详情页时参数或页面类型不正确。
- 工时订单会进入工位详情链路,工位订单会落到普通订单详情链路。
涉及文件:
- `pages/shop/verify/verify-order-detail.vue`
改动内容:
- 新增 `getOrderDetailUrl`,统一按 `order_type` 生成核销成功后的详情页地址。
- `order_type=3` 工时订单跳转到 `/pages/shop/SellerDetail?order_id=...&identity=1`,使用手艺人工时订单详情链路。
- `order_type=4` 工位订单跳转到 `/pages/shop/SellerDetail?order_id=...`,使用商家工位订单详情链路。
- `order_type=6` 自营订单跳转普通订单详情并携带 `serviceType=2`
- 其他普通订单跳转普通订单详情,不再误携带自营参数。
验证情况:
- 已执行 `pages/shop/verify/verify-order-detail.vue``CHANGE_RECORD.md` 的空白格式检查。

View File

@ -15,7 +15,6 @@
</view>
</template>
<script>
<script>
// spark-md5
import SparkMD5 from 'spark-md5'
@ -42,7 +41,7 @@ export default {
maxSize: {
type: Number,
default: 500
}
},
},
data() {
return {
@ -53,7 +52,7 @@ export default {
fileMd5: '',
cancelToken: null
};
}
},
methods: {
//
async chooseVideo() {
@ -319,7 +318,7 @@ export default {
chunkIndex,
fileMd5: this.fileMd5,
chunkSize: this.chunkSize,
totalChunks: Math.ceil(this.file.size / this.chunkSize))
totalChunks: Math.ceil(this.file.size / this.chunkSize)
},
header: {
'Content-Type': 'multipart/form-data'
@ -411,4 +410,4 @@ button {
color: #666;
font-size: 24rpx;
}
</style>
</style>

View File

@ -95,7 +95,7 @@ export default {
color: #999;
}
.tab-text.active {
color: #E8101E; /* 选中颜色 */
color: #FF4767; /* 选中颜色 */
}
.tab-badge {
position: absolute;

View File

@ -50,6 +50,7 @@
v-for="(file, index) in videoList"
:key="file || index"
class="preview-item"
@click="previewVideo(file)"
>
<!-- <video
:src="file"
@ -57,6 +58,11 @@
@click="handlePreview(index)"
/> -->
<image class="preview-image" :src="getVideoPoster(file)" mode="aspectFill"></image>
<view class="video-play-mask">
<view class="video-play-circle">
<view class="video-play-icon"></view>
</view>
</view>
<image
v-if="deletable"
class="delete-icon"
@ -81,6 +87,14 @@
</view>
</view>
</view>
<view v-if="showVideoPlayer" class="video-player-mask" @click="closeVideoPlayer">
<view class="video-player-box" @click.stop>
<video :src="previewVideoUrl" class="video-player" controls autoplay show-fullscreen-btn></video>
<view class="video-player-close" @click="closeVideoPlayer">
<text class="video-player-close-text">&times;</text>
</view>
</view>
</view>
<!-- 提示文字 -->
<!-- <text v-if="tips" class="tips-text">{{ tips }}</text> -->
@ -97,12 +111,12 @@ export default {
props: {
//
value: {
type: Array,
type: [Array, String, Object],
default: () => []
},
//
fileString: {
type: String,
type: [String, Object, Array],
default: ''
},
//
@ -173,10 +187,12 @@ export default {
fileList: [],
oneFile: '',
videoList: [],
loading: false,
loading: false,
dragIndex: -1,
isDragging: false,
suppressPreview: false
suppressPreview: false,
showVideoPlayer: false,
previewVideoUrl: ''
}
},
@ -190,19 +206,57 @@ export default {
value: {
immediate: true,
handler(newVal) {
this.fileList = [...newVal]
if (this.accept === 'video/*') {
const videoValue = this.normalizeVideoValue(newVal)
this.oneFile = videoValue
this.videoList = videoValue ? [videoValue] : []
return
}
this.fileList = Array.isArray(newVal) ? [...newVal] : []
}
},
fileString: {
immediate: true,
handler(newVal) {
this.oneFile = newVal
this.videoList = newVal ? [newVal] : []
const videoValue = this.normalizeVideoValue(newVal)
this.oneFile = videoValue
this.videoList = videoValue ? [videoValue] : []
}
}
},
methods: {
normalizeVideoValue(value) {
if (!value) return ''
if (typeof value === 'string') {
const str = value.trim()
if (!str) return ''
if ((str.startsWith('{') && str.endsWith('}')) || (str.startsWith('[') && str.endsWith(']'))) {
try {
return this.normalizeVideoValue(JSON.parse(str))
} catch (error) {
return str
}
}
return str
}
if (Array.isArray(value)) {
for (const item of value) {
const url = this.normalizeVideoValue(item)
if (url) return url
}
return ''
}
if (typeof value === 'object') {
const candidates = [value.url, value.path, value.filePath, value.tempFilePath, value.src, value.value]
for (const item of candidates) {
const url = this.normalizeVideoValue(item)
if (url) return url
}
return ''
}
return String(value)
},
//
showPermissionDialog(title, content) {
@ -319,8 +373,9 @@ export default {
},
// OSS
async uploadFile(file) {
const uploadFile = await this.prepareUploadFile(file)
//
if (file.size > this.maxSize * 1024 * 1024) {
if (uploadFile.size > this.maxSize * 1024 * 1024) {
uni.showToast({
title: `文件大小不能超过${this.maxSize}MB`,
icon: 'none'
@ -328,7 +383,7 @@ export default {
return Promise.reject(new Error('文件大小超出限制'))
}
let date = `${new Date().getTime()}_${Math.floor(Math.random() * 100000)}`
const fileExt = this.getFileExt(file)
const fileExt = this.getFileExt(uploadFile)
let artisan = this.type==1 ? 'yh' : this.type==2 ? 'syr' : this.type==3 ? 'sj' : '';
let name = `${this.userId}_${artisan}_${date}.${fileExt}`
@ -337,7 +392,7 @@ export default {
// 1. OSS
const ossConfig = await this.getOssConfig()
// 2.
const fileUrl = await this.uploadToOss(file, ossConfig, name)
const fileUrl = await this.uploadToOss(uploadFile, ossConfig, name)
// 3.
if(this.accept == 'image/*') {
const newFile = [`${fileUrl}`]
@ -379,10 +434,90 @@ export default {
getFileExt(file) {
const defaultExt = this.accept === 'video/*' ? 'mp4' : 'jpg'
const source = file && (file.name || file.tempFilePath || file.path || file.url || '')
const cleanPath = String(source).split('?')[0].split('#')[0]
const ext = cleanPath.includes('.') ? cleanPath.split('.').pop().toLowerCase() : ''
return /^[a-z0-9]+$/.test(ext) ? ext : defaultExt
const sources = this.accept === 'video/*'
? [file && file.tempFilePath, file && file.path, file && file.url, file && file.name]
: [file && file.path, file && file.tempFilePath, file && file.url, file && file.name]
for (const source of sources) {
const cleanPath = String(source || '').split('?')[0].split('#')[0]
const ext = cleanPath.includes('.') ? cleanPath.split('.').pop().toLowerCase() : ''
if (/^[a-z0-9]+$/.test(ext)) return ext
}
return defaultExt
},
getFilePath(file) {
if (!file) return ''
return file.tempFilePath || file.path || file.url || file.name || ''
},
getMimeType(file, name) {
const ext = this.getFileExt({ tempFilePath: name, path: name, name })
const mimeTypes = {
jpg: 'image/jpeg',
jpeg: 'image/jpeg',
png: 'image/png',
gif: 'image/gif',
webp: 'image/webp',
mp4: 'video/mp4',
m4v: 'video/x-m4v',
mov: 'video/quicktime',
qt: 'video/quicktime',
webm: 'video/webm',
mpeg: 'video/mpeg',
mpg: 'video/mpeg',
avi: 'video/x-msvideo',
'3gp': 'video/3gpp',
mkv: 'video/x-matroska',
}
return mimeTypes[ext] || (file && file.type) || ''
},
buildOssFormData(ossConfig, objectKey, mimeType) {
const formData = {
key: objectKey,
policy: ossConfig.policy,
OSSAccessKeyId: ossConfig.ossAccessKeyId,
success_action_status: '200',
signature: ossConfig.signature,
}
if (mimeType) {
formData['Content-Type'] = mimeType
formData['x-oss-meta-content-type'] = mimeType
}
return formData
},
async prepareUploadFile(file) {
if (this.accept !== 'video/*') return file
const filePath = this.getFilePath(file)
const fileExt = this.getFileExt(file)
if (!filePath || fileExt === 'mp4' || typeof uni.compressVideo !== 'function') {
return file
}
try {
const res = await uni.compressVideo({
src: filePath,
quality: 'medium'
})
const tempFilePath = res.tempFilePath || res.path
if (!tempFilePath) return file
return {
...file,
tempFilePath,
path: tempFilePath,
size: res.size || file.size,
name: this.replaceFileExt(file.name, 'mp4'),
type: 'video/mp4'
}
} catch (error) {
console.warn('视频压缩失败,使用原文件上传:', error)
return file
}
},
replaceFileExt(name, ext) {
if (!name) return ''
return String(name).replace(/\.[^.]+$/, `.${ext}`)
},
buildFileUrl(ossConfig, name) {
@ -403,6 +538,21 @@ export default {
},
chooseVideoFiles(sourceType, count) {
if (count <= 1 && typeof uni.chooseVideo === 'function') {
return new Promise((resolve, reject) => {
uni.chooseVideo({
sourceType: [sourceType],
compressed: true,
maxDuration: 60,
success: (res) => resolve([{
...res,
tempFilePath: res.tempFilePath || res.path,
path: res.tempFilePath || res.path,
}]),
fail: reject,
});
});
}
if (uni.chooseMedia) {
return new Promise((resolve, reject) => {
uni.chooseMedia({
@ -438,14 +588,20 @@ export default {
return new Promise((resolve, reject) => {
const objectKey = this.buildObjectKey(ossConfig, name)
const fileUrl = this.buildFileUrl(ossConfig, name)
// #ifdef H5
const formData = new FormData()
const mimeType = this.getMimeType(file, name)
let formData;
// #ifdef H5
formData = new FormData()
formData.append('name',name);
formData.append('policy', ossConfig.policy);
formData.append('OSSAccessKeyId', ossConfig.ossAccessKeyId);
formData.append('success_action_status', '200');
formData.append('signature', ossConfig.signature);
formData.append('key', objectKey);
if (mimeType) {
formData.append('Content-Type', mimeType);
formData.append('x-oss-meta-content-type', mimeType);
}
// filefile
formData.append('file', file.file || file.tempFile || file);
fetch(ossConfig.host, { method: 'POST', body: formData},).then((res) => {
@ -459,24 +615,18 @@ export default {
})
// #endif
// #ifdef APP-PLUS || MP-WEIXIN
let formData = {};
formData.name = name;
formData.policy = ossConfig.policy;
formData.OSSAccessKeyId = ossConfig.ossAccessKeyId;
formData.success_action_status = '200';
formData.signature = ossConfig.signature;
formData.key = objectKey;
formData.file = file;
// #ifdef APP-PLUS || MP-WEIXIN
formData = this.buildOssFormData(ossConfig, objectKey, mimeType);
formData.name = name;
let path;
if(this.accept == 'image/*') {
path = file.path
path = this.getFilePath(file)
}else if(this.accept == 'video/*') {
// #ifdef H5
path = file.name
path = this.getFilePath(file)
// #endif
// #ifdef APP-PLUS || MP-WEIXIN
path = file.tempFilePath
path = this.getFilePath(file)
// #endif
}
// 使 uni.uploadFile
@ -484,13 +634,7 @@ export default {
url: ossConfig.host,
filePath: path, //
name: 'file',
formData: {
key: objectKey, //
policy: ossConfig.policy, //
OSSAccessKeyId: ossConfig.ossAccessKeyId, // ID
success_action_status: 200, // 200,204
signature: ossConfig.signature //
},
formData,
success: (res) => {
const statusCode = Number(res.statusCode)
if (statusCode >= 200 && statusCode < 300) {
@ -517,6 +661,17 @@ export default {
})
},
previewVideo(url) {
if (!url) return
this.previewVideoUrl = url
this.showVideoPlayer = true
},
closeVideoPlayer() {
this.showVideoPlayer = false
this.previewVideoUrl = ''
},
startDrag(index) {
if (!this.sortable || this.accept !== 'image/*' || this.fileList.length < 2) return
this.dragIndex = index
@ -662,6 +817,38 @@ export default {
border-radius: 8rpx;
}
.video-play-mask {
position: absolute;
left: 0;
top: 0;
width: 200rpx;
height: 200rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.18);
border-radius: 8rpx;
}
.video-play-circle {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
background-color: rgba(0, 0, 0, 0.55);
display: flex;
align-items: center;
justify-content: center;
}
.video-play-icon {
width: 0;
height: 0;
border-top: 14rpx solid transparent;
border-bottom: 14rpx solid transparent;
border-left: 22rpx solid #fff;
margin-left: 6rpx;
}
.delete-icon {
position: absolute;
top: -10rpx;
@ -670,4 +857,50 @@ export default {
height: 42rpx;
z-index: 5;
}
.video-player-mask {
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
z-index: 9999;
background-color: rgba(0, 0, 0, 0.75);
display: flex;
align-items: center;
justify-content: center;
}
.video-player-box {
position: relative;
width: 690rpx;
background-color: #000;
border-radius: 8rpx;
overflow: hidden;
}
.video-player {
width: 690rpx;
height: 388rpx;
background-color: #000;
}
.video-player-close {
position: absolute;
top: 12rpx;
right: 12rpx;
width: 52rpx;
height: 52rpx;
border-radius: 50%;
background-color: rgba(0, 0, 0, 0.55);
display: flex;
align-items: center;
justify-content: center;
}
.video-player-close-text {
color: #fff;
font-size: 42rpx;
line-height: 52rpx;
}
</style>

View File

@ -43,7 +43,7 @@ export default {
//
color: {
type: String,
default: '#E8101E'
default: '#FF4767'
},
//
backgroundColor: {

View File

@ -402,7 +402,7 @@ export default {
}
.time-option.active .time-text {
color: #E8101E;
color: #FF4767;
font-weight: 500;
}
@ -413,7 +413,7 @@ export default {
.time-discount {
font-size: 24rpx;
color: #E8101E;
color: #FF4767;
padding: 4rpx 12rpx;
background-color: #fff5f6;
border-radius: 20rpx;
@ -444,7 +444,7 @@ export default {
color: #666;
}
.active {
color: #E8101E; /* 选中态主题色 */
color: #FF4767; /* 选中态主题色 */
}
/* 服务时间按钮专属:控制下拉图标尺寸 */

View File

@ -52,7 +52,7 @@ export default {
// props
activeColor: {
type: String,
default: "#E8101E",
default: "#FF4767",
},
// props
inactiveColor: {

View File

@ -59,7 +59,7 @@ export default {
//
activeColor: {
type: String,
default: "#E8101E",
default: "#FF4767",
},
//
inactiveColor: {
@ -129,7 +129,7 @@ export default {
left: 0;
right: 0;
bottom: 0;
border: 2rpx solid #E8101E;
border: 2rpx solid #FF4767;
border-radius: 48rpx;
}
@ -143,6 +143,6 @@ export default {
}
.icon-item.active .icon-text {
color: #E8101E;
color: #FF4767;
}
</style>

View File

@ -2,7 +2,7 @@
<view class="form-checkbox">
<view v-for="(item, index) in options" :key="index" class="checkbox-item" @click="onToggle(item)">
<view class="checkbox-dot" :class="{ active: isChecked(item) }"></view>
<text class="checkbox-label">{{ item }}</text>
<text class="checkbox-label">{{ getOptionLabel(item) }}</text>
</view>
</view>
</template>
@ -16,12 +16,37 @@ export default {
maxCount: { type: Number, default: 0 }
},
methods: {
isChecked: function (val) {
return this.value && this.value.indexOf(val) !== -1;
getOptionValue: function (item) {
if (item && typeof item === 'object') {
if (item.value !== undefined) return item.value;
if (item.id !== undefined) return item.id;
if (item.key !== undefined) return item.key;
if (item.code !== undefined) return item.code;
return item.label;
}
return item;
},
onToggle: function (val) {
getOptionLabel: function (item) {
if (item && typeof item === 'object') {
var label = item.label || item.title || item.name || item.text;
if (label !== undefined && label !== null) return label;
return String(this.getOptionValue(item));
}
return item;
},
findValueIndex: function (list, value) {
for (var i = 0; i < list.length; i++) {
if (list[i] == value) return i;
}
return -1;
},
isChecked: function (item) {
return this.findValueIndex(this.value || [], this.getOptionValue(item)) !== -1;
},
onToggle: function (item) {
var val = this.getOptionValue(item);
var newValue = (this.value || []).slice();
var index = newValue.indexOf(val);
var index = this.findValueIndex(newValue, val);
if (index === -1) {
if (this.maxCount > 0 && newValue.length >= this.maxCount) {
uni.showToast({

View File

@ -1,6 +1,6 @@
<template>
<ali-oss-uploader v-model="currentValue" :max-count="maxCount" :max-size="maxSize" accept="image/*" :userId="userId"
:type="ossType" width="100%" :sortable="true" @input="onInput" @change="onChange" />
<ali-oss-uploader v-model="currentValue" :max-count="maxCount" :max-size="maxSize" accept="image/*" :userId="computedUserId"
:type="computedOssType" width="100%" :sortable="true" @input="onInput" @change="onChange" />
</template>
<script>
@ -9,25 +9,29 @@ import AliOssUploader from '../ali-oss-uploader/ali-oss-uploader.vue'
export default {
name: 'FormItemImage',
components: { AliOssUploader },
props: {
value: { type: Array, default: function () { return []; } },
maxCount: { type: Number, default: 9 },
maxSize: { type: Number, default: 5 }
},
props: {
value: { type: Array, default: function () { return []; } },
maxCount: { type: Number, default: 9 },
maxSize: { type: Number, default: 5 },
userId: { type: [Number, String], default: null },
ossType: { type: [Number, String], default: null }
},
data: function () {
return {
currentValue: []
};
},
computed: {
userId: function () {
return this.$store && this.$store.state && this.$store.state.sjInfo
? this.$store.state.sjInfo.id : null;
},
ossType: function () {
var artisanType = getApp().globalData ? getApp().globalData.artisanType : 0;
return (artisanType || 0) + 1;
}
computedUserId: function () {
if (this.userId) return this.userId;
return this.$store && this.$store.state && this.$store.state.sjInfo
? this.$store.state.sjInfo.id : null;
},
computedOssType: function () {
if (this.ossType) return this.ossType;
var artisanType = getApp().globalData ? getApp().globalData.artisanType : 0;
return (artisanType || 0) + 1;
}
},
watch: {
value: {

View File

@ -1,8 +1,8 @@
<template>
<view class="form-radio">
<view v-for="(item, index) in options" :key="index" class="radio-item" @click="onSelect(item)">
<view class="radio-dot" :class="{ active: value === item }"></view>
<text class="radio-label">{{ item }}</text>
<view class="radio-dot" :class="{ active: isActive(item) }"></view>
<text class="radio-label">{{ getOptionLabel(item) }}</text>
</view>
</view>
</template>
@ -15,8 +15,29 @@ export default {
options: { type: Array, default: function () { return []; } }
},
methods: {
onSelect: function (val) {
this.$emit('input', val);
getOptionValue: function (item) {
if (item && typeof item === 'object') {
if (item.value !== undefined) return item.value;
if (item.id !== undefined) return item.id;
if (item.key !== undefined) return item.key;
if (item.code !== undefined) return item.code;
return item.label;
}
return item;
},
getOptionLabel: function (item) {
if (item && typeof item === 'object') {
var label = item.label || item.title || item.name || item.text;
if (label !== undefined && label !== null) return label;
return String(this.getOptionValue(item));
}
return item;
},
isActive: function (item) {
return this.value == this.getOptionValue(item);
},
onSelect: function (item) {
this.$emit('input', this.getOptionValue(item));
}
}
};

View File

@ -1,8 +1,8 @@
<template>
<picker :range="options" @change="onChange">
<picker :range="displayOptions" @change="onChange">
<view class="form-select">
<text :class="['select-text', value ? '' : 'placeholder']">
{{ value || placeholder }}
<text :class="['select-text', hasValue ? '' : 'placeholder']">
{{ hasValue ? selectedLabel : placeholder }}
</text>
<image src="https://mrrplus.oss-cn-beijing.aliyuncs.com/wxstaic/images/arrow_right.png" class="arrow-right"
mode="aspectFit" />
@ -18,12 +18,48 @@ export default {
options: { type: Array, default: function () { return []; } },
placeholder: { type: String, default: '请选择' }
},
computed: {
displayOptions: function () {
var self = this;
return this.options.map(function (item) {
return self.getOptionLabel(item);
});
},
hasValue: function () {
return this.value !== undefined && this.value !== null && this.value !== '';
},
selectedLabel: function () {
var self = this;
var selected = this.options.find(function (item) {
return self.getOptionValue(item) == self.value;
});
return selected !== undefined ? this.getOptionLabel(selected) : this.value;
}
},
methods: {
getOptionValue: function (item) {
if (item && typeof item === 'object') {
if (item.value !== undefined) return item.value;
if (item.id !== undefined) return item.id;
if (item.key !== undefined) return item.key;
if (item.code !== undefined) return item.code;
return item.label;
}
return item;
},
getOptionLabel: function (item) {
if (item && typeof item === 'object') {
var label = item.label || item.title || item.name || item.text;
if (label !== undefined && label !== null) return label;
return String(this.getOptionValue(item));
}
return item;
},
onChange: function (e) {
var index = e.detail.value;
var selected = this.options[index];
if (selected !== undefined) {
this.$emit('input', selected);
this.$emit('input', this.getOptionValue(selected));
}
}
}

View File

@ -20,16 +20,47 @@
<text class="price-tip-card">{{ group.field.tip }}</text>
</view>
</view>
<view v-else-if="group.field.type === 'textarea'" class="detail-row">
<text :class="['form-label', group.field.is_required === true ? 'required' : '']">{{ group.field.label
}}</text>
<view class="detail-textarea-wrap">
<textarea class="detail-textarea" :value="formData[group.field.key]" :placeholder="group.field.placeholder"
placeholder-class="placeholder" maxlength="200" @input="onInput(group.field.key, $event)" />
<text class="detail-count">{{ (formData[group.field.key] || "").length }}/200</text>
</view>
</view>
<view v-else-if="group.field.type === 'select'" class="form-item">
<text :class="['form-label', group.field.is_required === true ? 'required' : '']">{{ group.field.label
}}</text>
<form-item-select :value="formData[group.field.key]" :options="group.field.options || []"
:placeholder="group.field.placeholder" @input="onInput(group.field.key, $event)" />
</view>
<view v-else-if="group.field.type === 'single_select'" class="form-item" style="flex-wrap: wrap;">
<text :class="['form-label', group.field.is_required === true ? 'required' : '']">{{ group.field.label
}}</text>
<view class="select-options">
<form-item-radio :value="formData[group.field.key]" :options="group.field.options || []"
@input="onInput(group.field.key, $event)" />
</view>
</view>
<view v-else-if="group.field.type === 'multi_select'" class="form-item" style="flex-wrap: wrap;">
<text :class="['form-label', group.field.is_required === true ? 'required' : '']">{{ group.field.label
}}</text>
<view class="select-options">
<form-item-checkbox :value="formData[group.field.key]" :options="group.field.options || []"
:maxCount="group.field.maxCount || 0" @input="onInput(group.field.key, $event)" />
</view>
</view>
<view v-else-if="group.field.type === 'image_multi'" class="form-item-up upload-item">
<view>
<text :class="['form-label', group.field.is_required === true ? 'required' : '']">{{ group.field.label
}}</text>
<text class="upload-tip">(仅可上传{{ group.field.maxCount || 9 }}个图片)</text>
</view>
<view class="upload-content">
<form-item-image :value="formData[group.field.key]" :maxCount="group.field.maxCount || 9"
@input="onInput(group.field.key, $event)" />
</view>
<view class="upload-content">
<form-item-image :value="formData[group.field.key]" :maxCount="group.field.maxCount || 9"
:userId="uploadUserId" @input="onInput(group.field.key, $event)" />
</view>
</view>
<view v-else-if="group.field.type === 'video'" class="form-item-up upload-item">
<view>
@ -107,10 +138,10 @@
<text :class="['form-label', field.is_required === true ? 'required' : '']">{{ field.label }}</text>
<text class="upload-tip">(仅可上传{{ field.maxCount || 9 }}个图片)</text>
</view>
<view class="upload-content">
<form-item-image :value="formData[field.key]" :maxCount="field.maxCount || 9"
@input="onInput(field.key, $event)" />
</view>
<view class="upload-content">
<form-item-image :value="formData[field.key]" :maxCount="field.maxCount || 9"
:userId="uploadUserId" @input="onInput(field.key, $event)" />
</view>
</view>
<view v-else-if="field.type === 'video'" class="form-item-up upload-item no-bg">
<view>

View File

@ -208,7 +208,7 @@ export default {
padding-left: 30rpx;
position: relative;
&__point {
background-color: #E8101E;
background-color: #FF4767;
height: 6rpx;
width: 6rpx;
border-radius: 50%;
@ -221,7 +221,7 @@ export default {
}
.active {
background: #ffffff;
color: #E8101E;
color: #FF4767;
}
}
&__right {
@ -295,17 +295,17 @@ export default {
height: 82rpx;
font-weight: 400;
font-size: 30rpx;
color: #E8101E;
color: #FF4767;
line-height: 42rpx;
text-align: left;
font-style: normal;
}
&__sure {
border-radius: 41rpx;
border: 2rpx solid #E8101E;
border: 2rpx solid #FF4767;
}
&__reset {
background: #E8101E;
background: #FF4767;
border-radius: 41rpx;
color: #ffffff;
}

View File

@ -114,8 +114,8 @@ export default {
}
.active {
background: rgba(252, 67, 124, 0.1);
color: #E8101E;
border: 1rpx solid #E8101E;
color: #FF4767;
border: 1rpx solid #FF4767;
}
}
</style>

View File

@ -548,7 +548,7 @@
width: 690rpx;
height: 68rpx;
background: #FFFFFF;
border: 2rpx solid #E8101E;
border: 2rpx solid #FF4767;
background-color: #fafafa;
display: flex;
align-items: center;

View File

@ -121,7 +121,7 @@ export default {
border: 1rpx solid #fba9bb;
font-weight: 500;
font-size: 24rpx;
color: #E8101E;
color: #FF4767;
text-align: left;
font-style: normal;
}
@ -197,7 +197,7 @@ export default {
margin-top: 10rpx;
font-weight: 500;
font-size: 24rpx;
color: #E8101E;
color: #FF4767;
line-height: 33rpx;
text-align: left;
font-style: normal;

View File

@ -59,7 +59,7 @@ export default {
.loginBtn {
width: 262rpx;
height: 86rpx;
background: #e8101e;
background: #FF4767;
border-radius: 43rpx;
text-align: center;
line-height: 86rpx;

View File

@ -408,11 +408,11 @@ export default {
.isActive {
background: rgba(252, 67, 124, 0.08);
color: #e8101e !important;
color: #FF4767 !important;
}
.isActive .time-picker-content__time__list__item__text {
color: #e8101e !important;
color: #FF4767 !important;
}
/* 选中样式前后弧度 */
@ -433,7 +433,7 @@ border-radius: 0rpx 51rpx 51rpx 0rpx}
.time-picker-content__time__sure__box {
width: 648rpx;
height: 94rpx;
background: #E8101E;
background: #FF4767;
border-radius: 48rpx;
display: flex;
align-items: center;

View File

@ -467,8 +467,8 @@
.isActive {
background: rgba(252, 67, 124, 0.08);
border-radius: 10rpx;
border: 1rpx solid #E8101E;
color: #E8101E;
border: 1rpx solid #FF4767;
color: #FF4767;
/* 选中状态 */
}
@ -493,7 +493,7 @@
&__box {
width: 648rpx;
height: 94rpx;
background: linear-gradient(140deg, #fa4e9a 0%, #E8101E 100%);
background: linear-gradient(140deg, #fa4e9a 0%, #FF4767 100%);
border-radius: 48rpx;
display: flex;
align-items: center;

View File

@ -1,30 +1,19 @@
<template>
<view class="service-code-modal" v-if="show" :style="{bottom:`${popBotom}px`}">
<view class="service-code-modal" v-if="show" :style="{ bottom: `${popBotom}px` }">
<view class="modal-mask" @touchmove.stop.prevent></view>
<view class="modal-content">
<view class="modal-title">开始服务</view>
<view class="modal-subtitle">请输入服务码</view>
<view class="code-box">
<view
v-for="(item, idx) in codeLength"
:key="idx"
<view v-for="(item, idx) in codeLength" :key="idx"
:class="['code-cell', idx === currentIndex ? 'active' : '', code[idx] ? 'filled' : '']">
<text v-if="code[idx]">{{ code[idx] }}</text>
</view>
<!-- 隐藏真实输入框 -->
<input
:adjust-position="false"
class="real-input"
type="number"
:maxlength="codeLength"
v-model="inputValue"
:focus="true"
@input="onInput"
@focus="onFocus"
@keyboardheightchange="onkeyboardheightchange"
@blur="onBlur"
cursor-spacing="100"
ref="realInput" />
<input :adjust-position="false" class="real-input" type="number" :maxlength="codeLength"
v-model="inputValue" :focus="true" @input="onInput" @focus="onFocus"
@keyboardheightchange="onkeyboardheightchange" @blur="onBlur" cursor-spacing="100"
cursor-color="transparent" ref="realInput" />
</view>
<button class="confirm-btn" :disabled="code.length < codeLength" @click="onConfirm">确定</button>
<view class="close-btn" @click="close">×</view>
@ -33,210 +22,212 @@
</template>
<script>
export default {
name: 'serviceCodeInput',
props: {
show: {
type: Boolean,
default: false
},
codeLength: {
type: Number,
default: 6
export default {
name: 'serviceCodeInput',
props: {
show: {
type: Boolean,
default: false
},
codeLength: {
type: Number,
default: 6
}
},
data() {
return {
inputValue: '',
isFocus: true,
keyboardHeight: 0,
popBotom: 0,
}
},
computed: {
code() {
return this.inputValue.split('').slice(0, this.codeLength)
},
currentIndex() {
return this.inputValue.length
}
},
watch: {
// show(val) {
// if (val) {
// this.$refs.realInput && this.$refs.realInput.focus
// } else {
// this.inputValue = ''
// }
// }
},
mounted() {
// uni.onKeyboardHeightChange((res)=>{
// this.keyboardHeight = res.height;
// })
},
methods: {
onInput(e) {
let val = e.detail.value.replace(/[^0-9]/g, '').slice(0, this.codeLength)
this.inputValue = val
this.$emit('input', val)
if (val.length === this.codeLength) {
this.$emit('finish', val)
}
},
data() {
return {
inputValue: '',
isFocus: true,
keyboardHeight: 0,
popBotom:0,
onFocus(e) {
this.isFocus = true
this.inputValue = ''
},
onkeyboardheightchange(e) {
this.popBotom = e.target.height || 0
},
onBlur() {
this.isFocus = false
},
onConfirm() {
if (this.inputValue.length === this.codeLength) {
this.$emit('confirm', this.inputValue);
}
},
computed: {
code() {
return this.inputValue.split('').slice(0, this.codeLength)
},
currentIndex() {
return this.inputValue.length
}
},
watch: {
// show(val) {
// if (val) {
// this.$refs.realInput && this.$refs.realInput.focus
// } else {
// this.inputValue = ''
// }
// }
},
mounted() {
// uni.onKeyboardHeightChange((res)=>{
// this.keyboardHeight = res.height;
// })
},
methods: {
onInput(e) {
let val = e.detail.value.replace(/[^0-9]/g, '').slice(0, this.codeLength)
this.inputValue = val
this.$emit('input', val)
if (val.length === this.codeLength) {
this.$emit('finish', val)
}
},
onFocus(e) {
this.isFocus = true
this.inputValue = ''
},
onkeyboardheightchange(e){
this.popBotom = e.target.height || 0
},
onBlur() {
this.isFocus = false
},
onConfirm() {
if (this.inputValue.length === this.codeLength) {
this.$emit('confirm', this.inputValue);
}
},
close() {
this.$emit('close')
}
close() {
this.$emit('close')
}
}
}
</script>
<style lang="scss" scoped>
.service-code-modal {
.service-code-modal {
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
z-index: 999999;
.modal-mask {
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
z-index: 999999;
background: rgba(0, 0, 0, 0.5);
z-index: -1;
}
.modal-mask {
.modal-content {
position: absolute;
left: 0;
right: 0;
bottom: 0;
background: #fff;
border-radius: 32rpx 32rpx 0 0;
padding: 56rpx 40rpx 40rpx 40rpx;
box-shadow: 0 -8rpx 32rpx rgba(0, 0, 0, 0.08);
min-height: 340rpx;
align-items: center;
display: flex;
flex-direction: column;
animation: modalUp 0.25s;
}
.modal-title {
font-size: 36rpx;
font-weight: bold;
color: #222;
text-align: center;
}
.modal-subtitle {
font-size: 28rpx;
color: #888;
margin: 24rpx 0 40rpx 0;
text-align: center;
}
.code-box {
flex-direction: row;
display: flex;
justify-content: center;
margin-bottom: 48rpx;
position: relative;
.code-cell {
width: 72rpx;
height: 72rpx;
border: 2rpx solid #eee;
border-radius: 16rpx;
margin-right: 24rpx;
font-size: 40rpx;
color: #ff4d8d;
background: #fff;
text-align: center;
line-height: 72rpx;
box-sizing: border-box;
transition: border-color 0.2s;
&.active {
border-color: #ff4d8d;
}
&.filled {
border-color: #ff4d8d;
}
&:last-child {
margin-right: 0;
}
}
.real-input {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.15);
}
.modal-content {
position: absolute;
left: 0;
right: 0;
bottom: 0;
background: #fff;
border-radius: 32rpx 32rpx 0 0;
padding: 56rpx 40rpx 40rpx 40rpx;
box-shadow: 0 -8rpx 32rpx rgba(0, 0, 0, 0.08);
min-height: 340rpx;
align-items: center;
display: flex;
flex-direction: column;
animation: modalUp 0.25s;
}
.modal-title {
font-size: 36rpx;
font-weight: bold;
color: #222;
text-align: center;
}
.modal-subtitle {
font-size: 28rpx;
color: #888;
margin: 24rpx 0 40rpx 0;
text-align: center;
}
.code-box {
flex-direction: row;
display: flex;
justify-content: center;
margin-bottom: 48rpx;
position: relative;
.code-cell {
width: 72rpx;
height: 72rpx;
border: 2rpx solid #eee;
border-radius: 16rpx;
margin-right: 24rpx;
font-size: 40rpx;
color: #ff4d8d;
background: #fff;
text-align: center;
line-height: 72rpx;
box-sizing: border-box;
transition: border-color 0.2s;
&.active {
border-color: #ff4d8d;
}
&.filled {
border-color: #ff4d8d;
}
&:last-child {
margin-right: 0;
}
}
.real-input {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
opacity: 0;
z-index: 2;
}
}
.confirm-btn {
width: 100%;
height: 88rpx;
background: #ff4d8d;
height: 100%;
opacity: 0;
z-index: 2;
caret-color: transparent;
}
}
.confirm-btn {
width: 100%;
height: 88rpx;
background: #ff4d8d;
color: #fff;
font-size: 32rpx;
border-radius: 16rpx;
margin-top: 16rpx;
text-align: center;
line-height: 88rpx;
border: none;
&:disabled {
background: #ffd1e3;
color: #fff;
font-size: 32rpx;
border-radius: 16rpx;
margin-top: 16rpx;
text-align: center;
line-height: 88rpx;
border: none;
&:disabled {
background: #ffd1e3;
color: #fff;
}
}
.close-btn {
position: absolute;
right: 32rpx;
top: 32rpx;
font-size: 48rpx;
color: #bbb;
z-index: 10;
font-weight: bold;
line-height: 1;
}
}
@keyframes modalUp {
from {
transform: translateY(100%);
}
to {
transform: translateY(0);
}
.close-btn {
position: absolute;
right: 32rpx;
top: 32rpx;
font-size: 48rpx;
color: #bbb;
z-index: 10;
font-weight: bold;
line-height: 1;
}
}
@keyframes modalUp {
from {
transform: translateY(100%);
}
to {
transform: translateY(0);
}
}
</style>

View File

@ -181,7 +181,7 @@
position: absolute;
top: 0rpx;
left: 0rpx;
background-color: #E8101E;
background-color: #FF4767;
color: #FFFFFF;
font-size: 24rpx;
padding: 6rpx 14rpx;
@ -247,7 +247,7 @@
.price-symbol {
font-weight: 500;
font-size: 24rpx;
color: #E8101E;
color: #FF4767;
line-height: 33rpx;
text-align: left;
font-style: normal;
@ -256,7 +256,7 @@
.price-value {
font-weight: 500;
font-size: 36rpx;
color: #E8101E;
color: #FF4767;
line-height: 33rpx;
text-align: left;
font-style: normal;
@ -332,12 +332,12 @@
.price-symbol {
font-size: 24rpx;
color: #E8101E;
color: #FF4767;
}
.price-value {
font-size: 32rpx;
color: #E8101E;
color: #FF4767;
font-weight: 500;
margin-right: 12rpx;
}

View File

@ -336,7 +336,7 @@
.price-text{
font-weight: 500;
font-size: 24rpx;
color: #E8101E;
color: #FF4767;
line-height: 33rpx;
text-align: left;
font-style: normal;
@ -345,7 +345,7 @@
.price-symbol {
font-weight: 500;
font-size: 20rpx;
color: #E8101E;
color: #FF4767;
line-height: 33rpx;
text-align: left;
font-style: normal;
@ -354,7 +354,7 @@
.price-value {
font-weight: 500;
font-size: 30rpx;
color: #E8101E;
color: #FF4767;
line-height: 33rpx;
text-align: left;
font-style: normal;

View File

@ -1,63 +1,54 @@
<template>
<view class="step-tab">
<view class="step-container">
<!-- 步骤1: 入驻协议 -->
<view class="step-item" :class="getStepClass(0)">
<view class="step-icon">
<image
v-if="currentStep === 0"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/55a51333-318f-4ae4-8402-aca265bd499d"
<image
v-if="mappedStep === 0"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/d62a1d01-52cb-418e-ba5b-afef513061b9.png"
class="step-img active-circle"
mode="aspectFit"
/>
<view v-else-if="currentStep > 0" class="step-done">1</view>
<view v-else-if="mappedStep > 0" class="step-done">1</view>
<view v-else class="step-number">1</view>
</view>
<text class="step-text" :class="{ active: currentStep === 0 }">入驻协议</text>
<text class="step-text" :class="{ active: mappedStep === 0 }">入驻协议</text>
</view>
<!-- 第一个连接线 -->
<view class="line-segment line-1">
<view class="line-progress" :class="{ active: line1Active, deactivating: line1Deactivating }"></view>
</view>
<!-- 步骤2: 根据身份显示不同信息 -->
<view class="step-item" :class="getStepClass(1)">
<view class="step-icon">
<image
v-if="currentStep === 1"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/55a51333-318f-4ae4-8402-aca265bd499d"
<image
v-if="mappedStep === 1"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/d62a1d01-52cb-418e-ba5b-afef513061b9.png"
class="step-img active-circle"
mode="aspectFit"
/>
<view v-else-if="currentStep > 1" class="step-done">2</view>
<view v-else-if="mappedStep > 1" class="step-done">2</view>
<view v-else class="step-number">2</view>
</view>
<text class="step-text" :class="{ active: currentStep === 1 }">
{{ identity === 1 ? '个人信息' : '资质信息' }}
</text>
<text class="step-text" :class="{ active: mappedStep === 1 }">基本信息</text>
</view>
<!-- 第二个连接线 -->
<view class="line-segment line-2">
<view class="line-progress" :class="{ active: line2Active, deactivating: line2Deactivating }"></view>
</view>
<!-- 步骤3: 根据身份显示不同信息 -->
<view class="step-item" :class="getStepClass(2)">
<view class="step-icon">
<image
v-if="currentStep === 2"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/55a51333-318f-4ae4-8402-aca265bd499d"
<image
v-if="mappedStep === 2"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/d62a1d01-52cb-418e-ba5b-afef513061b9.png"
class="step-img active-circle"
mode="aspectFit"
/>
<view v-else-if="currentStep > 2" class="step-done">3</view>
<view v-else-if="mappedStep > 2" class="step-done">3</view>
<view v-else class="step-number">3</view>
</view>
<text class="step-text" :class="{ active: currentStep === 2 }">
{{ identity === 1 ? '资质信息' : '门店信息' }}
</text>
<text class="step-text" :class="{ active: mappedStep === 2 }">资质认证</text>
</view>
</view>
</view>
@ -67,32 +58,23 @@
export default {
name: "StepTab",
props: {
// (0, 1, 2)
currentStep: {
mappedStep: {
type: Number,
default: 0
},
// 1-2-
identity: {
type: Number,
default: 2
}
},
data() {
return {
prevStep: 0, //
line1Active: false, // 线
line2Active: false, // 线
line1Deactivating: false, // 线
line2Deactivating: false // 线
prevStep: 0,
line1Active: false,
line2Active: false,
line1Deactivating: false,
line2Deactivating: false
};
},
watch: {
// currentStep线
currentStep(newVal, oldVal) {
mappedStep(newVal, oldVal) {
this.prevStep = oldVal;
//
if (newVal > oldVal) {
if (newVal >= 1) {
this.line1Active = true;
@ -102,38 +84,34 @@ export default {
this.line2Active = true;
this.line2Deactivating = false;
}
}
//
else if (newVal < oldVal) {
} else if (newVal < oldVal) {
if (newVal < 2) {
this.line2Deactivating = true;
setTimeout(() => {
this.line2Active = false;
this.line2Deactivating = false;
}, 300); //
}, 300);
}
if (newVal < 1) {
this.line1Deactivating = true;
setTimeout(() => {
this.line1Active = false;
this.line1Deactivating = false;
}, 300); //
}, 300);
}
}
}
},
mounted() {
// 线
this.line1Active = this.currentStep >= 1;
this.line2Active = this.currentStep >= 2;
this.line1Active = this.mappedStep >= 1;
this.line2Active = this.mappedStep >= 2;
},
methods: {
// CSS
getStepClass(stepIndex) {
if (stepIndex === this.currentStep) {
if (stepIndex === this.mappedStep) {
return 'active';
}
else if (stepIndex < this.currentStep) {
else if (stepIndex < this.mappedStep) {
return 'completed';
}
return '';
@ -206,7 +184,7 @@ export default {
justify-content: center;
align-items: center;
font-size: 28rpx;
color: #E8101E;
color: #FF4767;
font-weight: 500;
transition: all 0.3s ease;
box-shadow: inset 0 0 4rpx rgba(232, 16, 30, 0.2);
@ -259,35 +237,16 @@ export default {
transform-origin: left center;
}
/* 激活状态的线条 - 前进动画 */
.line-progress.active {
width: 100%;
background-color: #E8101E;
background-color: #FF4767;
transition: width 0.3s ease;
}
/* 取消激活状态的线条 - 返回动画 */
.line-progress.deactivating {
width: 0%;
background-color: #E8101E;
background-color: #FF4767;
transition: width 0.3s ease;
transform-origin: right center;
}
/* 响应式调整 */
@media (max-width: 750rpx) {
.step-container {
/* padding: 5rpx 40rpx; */
}
.line-segment {
/* width: 100rpx; */
}
/* .line-1,
.line-2 {
margin-left: 15rpx;
margin-right: 15rpx;
} */
}
</style>
</style>

View File

@ -1,123 +1,209 @@
<template>
<uv-tabbar fixed :value="selected" @change="change1" activeColor="#ee0a24" :placeholder="false"
:safeAreaInsetBottom="false" :customStyle="{height:`110rpx`}">
<uv-tabbar-item v-for="(item,index) in $store.state.tabbarList" :key="index" :badge="getNum(index)"
:customStyle="{height:`110rpx`}">
<template v-slot:active-icon>
<image class="icon" :src="item.selectedIconPath"></image>
</template>
<template v-slot:inactive-icon>
<image class="icon" :src="item.iconPath"></image>
</template>
</uv-tabbar-item>
</uv-tabbar>
<uv-tabbar
v-if="tabbarReady"
:key="refreshKey"
fixed
:value="currentTabIndex"
@change="onTabChange"
activeColor="#FF4767"
:placeholder="false"
:safeAreaInsetBottom="false"
:customStyle="{height:'110rpx'}"
>
<uv-tabbar-item
v-for="(item, idx) in tabbarList"
:key="idx"
:name="idx"
:text="item.text"
:badge="getNum(idx)"
:customStyle="{height:'110rpx'}"
@click="onItemClick(idx)"
>
<template v-slot:active-icon>
<image class="icon" :src="item.selectedIconPath"></image>
</template>
<template v-slot:inactive-icon>
<image class="icon" :src="item.iconPath"></image>
</template>
</uv-tabbar-item>
</uv-tabbar>
</template>
<script>
import request from "@/utils/request";
export default {
name: "tab-bar",
props: {
selected: {
type: [Number, String]
}
},
data() {
return {
tabbarList: [{
"iconPath": "/static/images/tabbar/new_home_gray.png",
"text": "首页",
"selectedIconPath": "/static/images/tabbar/new_home.png",
"pagePath": "/pages/home/home"
},
{
"iconPath": "/static/images/tabbar/new_message_gray.png",
"text": "消息",
"selectedIconPath": "/static/images/tabbar/new_message.png",
"pagePath": "/pages/message/message"
},
{
"iconPath": "/static/images/tabbar/new_my_gray.png",
"text": "我的",
"selectedIconPath": "/static/images/tabbar/new_my.png",
"pagePath": "/pages/my/my"
}
]
};
},
computed: {
messageNum() {
return this.$store.state.messageNum || 0
}
},
async created() {
if (!this.$store.state.tabbarList.length) {
let isnNetwork = uni.getStorageSync("isnNetwork");
if (isnNetwork) {
await this.getButtonImg()
} else {
uni.getNetworkType({
success: (res) => {
console.log(res.networkType);
if (res.networkType == "none") {
import { mapState, mapMutations } from 'vuex';
import request from "@/utils/request";
this.$store.commit("setTabbarList", this.tabbarList)
} else {
//
uni.setStorageSync("isnNetwork", 1);
this.getButtonImg()
}
},
});
}
export default {
name: "tab-bar",
// props.selected Vuex
data() {
return {
refreshKey: 0, //
tabbarReady: false, //
tabbarList: [ // tab
{
iconPath: "/static/images/tabbar/new_home_gray.png",
text: "首页",
selectedIconPath: "/static/images/tabbar/new_home.png",
pagePath: "/pages/home/home"
},
{
iconPath: "/static/images/tabbar/new_order_gray.png",
text: "订单",
selectedIconPath: "/static/images/tabbar/new_order.png",
pagePath: "/pages/order/userorder-list"
},
{
iconPath: "/static/images/tabbar/new_message_gray.png",
text: "消息",
selectedIconPath: "/static/images/tabbar/new_message.png",
pagePath: "/pages/message/message"
},
{
iconPath: "/static/images/tabbar/new_my_gray.png",
text: "我的",
selectedIconPath: "/static/images/tabbar/new_my.png",
pagePath: "/pages/my/my"
}
]
};
},
computed: {
//
...mapState(['messageNum']),
// ========= =========
// Vuex
currentTabIndex() {
try {
//
const pages = getCurrentPages();
if (pages && pages.length > 0) {
//
const currentPage = pages[pages.length - 1];
//
const currentPath = '/' + (currentPage.route || currentPage.__route__);
// tabbarList
const index = this.tabbarList.findIndex(item => item.pagePath === currentPath);
if (index !== -1) {
return index;
}
}
// Vuex
return this.$store.state.currentTabIndex || 0;
} catch (e) {
return this.$store.state.currentTabIndex || 0;
}
}
// =================================================
},
watch: {
// tabbarList
tabbarList: {
deep: true,
handler() {
this.rebuildTabbar();
}
}
},
created() {
//
this.tabbarReady = false;
this.$nextTick(() => {
this.tabbarReady = true;
//
// this.getButtonImg();
});
},
methods: {
...mapMutations(['SET_CURRENT_TAB_INDEX']),
}
//
setCurrentTabIndex(index) {
this.SET_CURRENT_TAB_INDEX(index);
},
},
methods: {
getNum(index) {
if (index == 1) {
return this.messageNum
} else {
return 0
}
},
//
async getButtonImg() {
let res = await request.post("/sj/poster/getButtonImg", {});
if (res.code == 200) {
// if (res.data?.photo) {
// this.topBgImage = res.data?.photo + '?time=666'
// }
this.tabbarList.forEach((item, index) => {
if (res.data[index].iconPath) {
item.iconPath = res.data[index].iconPath
}
if (res.data[index].selectedIconPath) {
item.selectedIconPath = res.data[index].selectedIconPath
}
})
}
this.$store.commit("setTabbarList", this.tabbarList)
},
change1(val) {
// pages tabbar
// 使navigateTo 使
uni.switchTab({
url: this.tabbarList[val].pagePath
})
this.messageUpudateNum()
}
}
}
// uv-tabbar
rebuildTabbar() {
this.tabbarReady = false;
this.$nextTick(() => {
this.refreshKey++;
this.tabbarReady = true;
});
},
// tab > 0
getNum(index) {
if (index === 2 && this.messageNum > 0) {
return this.messageNum;
}
return null;
},
//
async getButtonImg() {
try {
let res = await request.post("/sj/poster/getButtonImg", {});
if (res.code === 200) {
this.tabbarList.forEach((item, index) => {
if (res.data[index]?.iconPath) {
item.iconPath = res.data[index].iconPath;
}
if (res.data[index]?.selectedIconPath) {
item.selectedIconPath = res.data[index].selectedIconPath;
}
});
this.$store.commit("setTabbarList", this.tabbarList);
//
this.rebuildTabbar();
}
} catch (err) {
console.error('getButtonImg 请求失败', err);
}
},
// uv-tabbar change
onTabChange(val) {
const idx = parseInt(val, 10);
this.doJump(idx);
},
// item
onItemClick(idx) {
this.doJump(idx);
},
// +
doJump(index) {
const idx = parseInt(index, 10);
if (isNaN(idx) || !this.tabbarList[idx]) return;
// Vuex
this.setCurrentTabIndex(idx);
//
const targetPage = this.tabbarList[idx].pagePath;
uni.switchTab({
url: targetPage,
fail: (err) => console.error('switchTab 失败', err)
});
//
if (typeof this.messageUpudateNum === 'function') {
this.messageUpudateNum();
}
}
}
};
</script>
<style lang="scss">
.icon {
height: 98rpx;
width: 98rpx;
}
::v-deep .uv-tabbar__content{
z-index: 999!important;
}
.icon {
height: 54rpx;
width: 54rpx;
}
::v-deep .uv-tabbar__content {
z-index: 999 !important;
}
</style>

View File

@ -383,7 +383,7 @@
justify-content: center;
width: 690rpx;
height: 80rpx;
background: #E8101E;
background: #FF4767;
border-radius: 38rpx;
font-weight: 400;
font-size: 30rpx;

View File

@ -169,7 +169,7 @@ export default {
justify-content: center;
width: 690rpx;
height: 80rpx;
background: #E8101E;
background: #FF4767;
border-radius: 38rpx;
font-weight: 400;
font-size: 30rpx;

View File

@ -307,7 +307,7 @@ export default {
&__text {
font-weight: 400;
font-size: 18rpx;
color: #E8101E;
color: #FF4767;
line-height: 25rpx;
text-align: left;
font-style: normal;
@ -371,7 +371,7 @@ export default {
&__immediately {
width: 236rpx;
height: 78rpx;
background: #E8101E;
background: #FF4767;
border-radius: 39rpx;
display: flex;
align-items: center;
@ -405,7 +405,7 @@ export default {
&__active {
height: 28rpx;
background: linear-gradient(270deg, #fe78b3 0%, #E8101E 100%);
background: linear-gradient(270deg, #fe78b3 0%, #FF4767 100%);
border-radius: 39rpx;
}

View File

@ -22,7 +22,6 @@
</view>
</view>
</template>
<script src="https://gosspublic.alicdn.com/aliyun-oss-sdk-6.18.0.min.js"></script>
<script>
import request from '../../utils/request';
export default {
@ -235,4 +234,4 @@ import request from '../../utils/request';
font-size: 32rpx;
}
</style>
</style>

View File

@ -200,12 +200,13 @@ import sparkMD5 from 'spark-md5'; // 引入MD5计算库
const loadNext = () => {
const start = currentChunk * chunkSize;
const end = start + chunkSize >= file.size ? file.size : start + chunkSize;
// #ifdef H5
const reader = new FileReader();
// #endif
// #ifndef H5
const reader = uni.getfilesystem;
// #endif
let reader;
// #ifdef H5
reader = new FileReader();
// #endif
// #ifndef H5
reader = uni.getfilesystem;
// #endif
reader.onload = (e) => {
spark.append(e.target.result);
currentChunk++;
@ -226,12 +227,13 @@ import sparkMD5 from 'spark-md5'; // 引入MD5计算库
try {
// ArrayBuffer
const arrayBuffer = await new Promise((resolve) => {
// #ifdef H5
const reader = new FileReader();
// #endif
// #ifndef H5
const reader = uni.getfilesystem;
// #endif
let reader;
// #ifdef H5
reader = new FileReader();
// #endif
// #ifndef H5
reader = uni.getfilesystem;
// #endif
reader.onload = () => resolve(reader.result);
reader.readAsArrayBuffer(chunk);
});
@ -418,4 +420,4 @@ import sparkMD5 from 'spark-md5'; // 引入MD5计算库
font-size: 32rpx;
}
</style>
</style>

View File

@ -2,14 +2,21 @@
"name" : "美融融商家",
"appid" : "__UNI__BBE6285",
"description" : "同城便捷到家美业“技术外卖”服务、同城预约到店服务平台美融融为同城爱美人群提供更优质的更全面的服务。",
"versionName" : "1.0.09",
"versionCode" : 1009,
"versionName" : "1.0.22",
"versionCode" : 1022,
"transformPx" : false,
/* 5+App */
"app-plus" : {
"usingComponents" : true,
"nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3,
"screenOrientation" : [
//
"portrait-primary", //
"portrait-secondary", //
"landscape-primary", //
"landscape-secondary" //
],
"compatible" : {
"ignoreVersion" : true //trueHBuilderX1.9.0
},
@ -34,7 +41,8 @@
"Payment" : {},
"VideoPlayer" : {},
"OAuth" : {},
"Camera" : {}
"Camera" : {},
"Barcode" : {}
},
/* */
"distribute" : {
@ -69,6 +77,7 @@
},
/* ios */
"ios" : {
"UIRequiresFullScreen" : false, //
"urlschemewhitelist" : [ "iosmap" ],
"dSYMs" : false,
"plistcmds" : [

View File

@ -31,6 +31,20 @@
"enablePullDownRefresh": true
}
},
{
"path": "pages/home/growth-package",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom"
}
},
{
"path": "pages/home/growth-package-result",
"style": {
"navigationBarTitleText": "支付结果",
"navigationStyle": "custom"
}
},
{
"path": "pages/message/message",
"style": {
@ -89,8 +103,12 @@
"path": "pages/order/userorder-list",
"style": {
"navigationBarTitleText": "订单",
"navigationBarBackgroundColor": "#FFFFFF"
"navigationBarBackgroundColor": "#FFFFFF",
"app-plus": {
"softinputNavBar": "none"
}
}
},
{
"path": "pages/order/userorder-detail",
@ -150,7 +168,13 @@
}
},
{
"path": "pages/add/add",
"path": "pages/notice/notice_list",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/notice/detail",
"style": {
"navigationStyle": "custom"
}
@ -221,6 +245,12 @@
"navigationStyle": "custom"
}
},
{
"path": "pages/search_common/search",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/settings/index",
"style": {
@ -584,6 +614,30 @@
"navigationStyle": "custom"
}
},
{
"path": "pages/shop/verify/verify-order",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/shop/verify/manual-verify",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/shop/verify/verify-fail",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/shop/verify/verify-order-detail",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/shop/photoAlbum/photoManage",
"style": {
@ -596,6 +650,14 @@
"navigationStyle": "custom"
}
},
{
"path": "pages/shop/photoAlbum/videoplayer",
"style": {
"navigationBarTitleText": "全屏视频播放",
"pageOrientation": "auto",
"navigationStyle": "custom"
}
},
{
"path": "pages/shop/photoAlbum/videoList",
"style": {
@ -943,6 +1005,43 @@
"style": {
"navigationBarTitleText": "拍摄技巧"
}
},
{
"path": "pages/my/myFavorite",
"style": {
"navigationBarTitleText": "我的收藏",
"navigationStyle": "custom"
}
},
{
"path": "pages/jingxuan/selected-skills",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/jingxuan/selected-brands",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/jingxuan/brand-detail",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/jingxuan/skill-detail",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/ruzhu/successruzhu",
"style": {
"navigationBarTitleText": "入驻成功"
}
}
@ -955,7 +1054,7 @@
},
"tabBar": {
"color": "#999",
"selectedColor": "#E8101E",
"selectedColor": "#FF4767",
"backgroundColor": "#fff",
"list": [{
"iconPath": "/static/images/tabbar/home_icon_dis.png",
@ -963,10 +1062,17 @@
"selectedIconPath": "/static/images/tabbar/home_icon_active.png",
"pagePath": "pages/home/home"
},
{ "iconPath": "/static/images/tabbar/new_message_gray.png",
"text": "消息",
"selectedIconPath": "/static/images/tabbar/new_message.png",
"pagePath": "pages/message/message"
{
"iconPath": "/static/images/tabbar/new_order_gray.png",
"text": "订单",
"selectedIconPath": "/static/images/tabbar/new_order.png",
"pagePath": "pages/order/userorder-list"
},
{
"iconPath": "/static/images/tabbar/new_message_gray.png",
"text": "消息",
"selectedIconPath": "/static/images/tabbar/new_message.png",
"pagePath": "pages/message/message"
},
{
"iconPath": "/static/images/tabbar/my_icon_dis.png",
@ -977,4 +1083,4 @@
]
},
"uniIdRouter": {}
}
}

View File

@ -55,7 +55,7 @@
<!-- 设为默认地址 -->
<view class="form-item switch-item">
<text class="label">设为默认地址</text>
<switch :checked="addressInfo.is_default == 1" @change="onSwitchChange" color="#E8101E" />
<switch :checked="addressInfo.is_default == 1" @change="onSwitchChange" color="#FF4767" />
</view>
<!-- 顶部图标 -->
<view class="biao"></view>
@ -622,7 +622,7 @@
.save-btn {
width: 630rpx;
height: 96rpx;
background-color: #E8101E;
background-color: #FF4767;
border-radius: 44rpx;
display: flex;
justify-content: center;

View File

@ -261,7 +261,7 @@
/* .tag {
font-size: 24rpx;
color: #E8101E;
color: #FF4767;
background-color: rgba(252, 67, 124, 0.08);
padding: 6rpx 16rpx;
border-radius: 6rpx;
@ -370,7 +370,7 @@
width: 690rpx;
height: 78rpx;
border-radius: 39rpx;
border: 1rpx solid #E8101E;
border: 1rpx solid #FF4767;
text-align: center;
line-height: 64rpx;
display: flex;
@ -383,7 +383,7 @@
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 30rpx;
color: #E8101E;
color: #FF4767;
line-height: 42rpx;
text-align: left;
font-style: normal;

View File

@ -7,7 +7,7 @@
<!-- <view class="search-container">
<text class="search-container-text" @click="goSelectCity">{{ city.name }}</text>
<search-box v-model="searchKeyword" placeholder="请输入地址" width="571rpx" height="68rpx"
border="1px solid #E8101E" style="padding: 0;" @confirm="onSearch" :showBack="false"></search-box>
border="1px solid #FF4767" style="padding: 0;" @confirm="onSearch" :showBack="false"></search-box>
</view> -->
<view class="search-container">
<text class="search-container-text" @click="goSelectCity">
@ -858,7 +858,7 @@
flex: 1;
height: 68rpx;
background: #FFFFFF;
border: 2rpx solid #E8101E;
border: 2rpx solid #FF4767;
border-radius: 60rpx;
position: relative;
@ -936,7 +936,7 @@
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 26rpx;
color: #E8101E;
color: #FF4767;
line-height: 34rpx;
text-align: center;
font-style: normal;
@ -1124,7 +1124,7 @@
width: 48rpx;
height: 26rpx;
border-radius: 6rpx;
background: #E8101E;
background: #FF4767;
margin-right: 10rpx;
&__text {

View File

@ -55,7 +55,7 @@
<!-- 设为默认地址 -->
<view class="form-item switch-item">
<text class="label">设为默认地址</text>
<switch :checked="addressInfo.is_default == 1" @change="onSwitchChange" color="#E8101E" />
<switch :checked="addressInfo.is_default == 1" @change="onSwitchChange" color="#FF4767" />
</view>
<!-- 顶部图标 -->
<view class="biao"></view>
@ -614,7 +614,7 @@
.save-btn {
width: 630rpx;
height: 96rpx;
background-color: #E8101E;
background-color: #FF4767;
border-radius: 44rpx;
display: flex;
justify-content: center;

View File

@ -666,7 +666,7 @@
flex: 1;
height: 68rpx;
background: #FFFFFF;
border: 2rpx solid #E8101E;
border: 2rpx solid #FF4767;
border-radius: 60rpx;
position: relative;

File diff suppressed because it is too large Load Diff

View File

@ -274,7 +274,7 @@
flex: 1;
height: 68rpx;
background: #FFFFFF;
border: 2rpx solid #E8101E;
border: 2rpx solid #FF4767;
background-color: #fafafa;
display: flex;
align-items: center;

View File

@ -8,7 +8,7 @@
></custom-navbar>
<!-- <view class="search-container">
<search-box v-model="searchKeyword" placeholder="请输入你的服务地址" width="690rpx" height="68rpx"
border="1px solid #E8101E" style="padding: 0;" @confirm="onSearch" :showBack="false"></search-box>
border="1px solid #FF4767" style="padding: 0;" @confirm="onSearch" :showBack="false"></search-box>
</view>
<scroll-view scroll-y class="hao-scroll">
<view class="hot-citys">

View File

@ -1,822 +1,88 @@
<template>
<view style="z-index: 100;">
<!-- 视频滑动主容器 -->
<view class="view-box" :style="viewTopLength">
<!-- 复用的5个视频容器 - 核心复用节点,不可删减 -->
<view class="video-box" :style="itemViewTopLength0">
<video :src="videoInfo_0.src" :style="fillScreen" :autoplay="videoInfo_0.autoplay"
:loop="videoInfo_0.loop" :controls="videoInfo_0.controls"
:enable-play-gesture="videoInfo_0.enablePlayGesture" :objectFit="videoInfo_0.objectFit || objectFit"
:show-fullscreen-btn="videoInfo_0.showFullscreenBtn"
:enable-progress-gesture="videoInfo_0.enableProgressGesture" @timeupdate="timeupdate(0,$event)"
@ended='ended' :show-play-btn="videoInfo_0.showPlayBtn" :show-progress="videoInfo_0.showProgress"
initial-time="0" id='video_id_0' @play="playing(0)" :muted="videoInfo_0.isMuted"
webkit-playsinline="true" playsinline="true">
</video>
<cover-view v-if="!videoInfo_0.flag" :style="stopIcon" class="cover-view-stop" />
<cover-view v-if="cover && videoInfo_0.coverStatus" :style="fillScreen2" class="video-image">
<image :mode="videoInfo_0.mode || mode" :src="videoInfo_0.coverUrl" :style="fillScreen"></image>
</cover-view>
<view class="video-action" @touchstart.stop='videotouchstart' @touchmove.stop="videotochmove"
@touchend.stop='videotouchend' @click.stop="videoClick"></view>
<!-- 视频文本区域 - 每个视频都有 -->
<view class="video-texts">
<view class="flex-row-center" style="flex: 1;">
<text class="video-title">{{ videoInfo_0.src }}</text>
<text class="video-score">评分 4.8</text>
</view>
<view class="flex-row-center-between" style="flex: 1;">
<text class="video-text">{{videoInfo_0.name}}</text>
<!-- 声音图标 - 绑定0号视频的状态点击切换静音 -->
<image class="video-sound"
:src="videoInfo_0.isMuted ? '/static/images/icons/sound_no.png' : '/static/images/icons/sound_yes.png'"
@click.stop="toggleMute(0)"></image>
</view>
</view>
</view>
<view class="video-box" :style="itemViewTopLength1">
<video :src="videoInfo_1.src" :style="fillScreen" :autoplay="videoInfo_1.autoplay"
:loop="videoInfo_1.loop" :controls="videoInfo_1.controls"
:enable-play-gesture="videoInfo_1.enablePlayGesture" :objectFit="videoInfo_1.objectFit || objectFit"
:show-fullscreen-btn="videoInfo_1.showFullscreenBtn"
:enable-progress-gesture="videoInfo_1.enableProgressGesture" @timeupdate="timeupdate(1,$event)"
@ended='ended' :show-play-btn="videoInfo_1.showPlayBtn" :show-progress="videoInfo_1.showProgress"
initial-time="0" id='video_id_1' @play="playing(1)" :muted="videoInfo_1.isMuted"
webkit-playsinline="true" playsinline="true">
</video>
<cover-view v-if="!videoInfo_1.flag" :style="stopIcon" class="cover-view-stop" />
<cover-view v-if="cover && videoInfo_1.coverStatus" :style="fillScreen2" class="video-image">
<image :mode="videoInfo_1.mode || mode" :src="videoInfo_1.coverUrl" :style="fillScreen"></image>
</cover-view>
<view class="video-action" @touchstart='videotouchstart' @touchmove="videotochmove"
@touchend='videotouchend' @click="videoClick"></view>
<!-- 视频文本区域 - 1号视频 -->
<view class="video-texts">
<view class="flex-row-center" style="flex: 1;">
<text class="video-title">珍草品汇理疗馆珍馆服务流程11订单</text>
<text class="video-score">评分 4.8</text>
</view>
<view class="flex-row-center-between" style="flex: 1;">
<text class="video-text">{{videoInfo_1.name}}</text>
<!-- 声音图标 - 绑定1号视频的状态 -->
<image class="video-sound"
:src="videoInfo_1.isMuted ? '/static/images/icons/sound_no.png' : '/static/images/icons/sound_yes.png'"
@click.stop="toggleMute(1)"></image>
</view>
</view>
</view>
<view class="video-box" :style="itemViewTopLength2">
<video :src="videoInfo_2.src" :style="fillScreen" :autoplay="videoInfo_2.autoplay"
:loop="videoInfo_2.loop" :controls="videoInfo_2.controls"
:enable-play-gesture="videoInfo_2.enablePlayGesture" :objectFit="videoInfo_2.objectFit || objectFit"
:show-fullscreen-btn="videoInfo_2.showFullscreenBtn"
:enable-progress-gesture="videoInfo_2.enableProgressGesture" @timeupdate="timeupdate(2,$event)"
@ended='ended' :show-play-btn="videoInfo_2.showPlayBtn" :show-progress="videoInfo_2.showProgress"
initial-time="0" id='video_id_2' @play="playing(2)" :muted="videoInfo_2.isMuted"
webkit-playsinline="true" playsinline="true">
</video>
<cover-view v-if="!videoInfo_2.flag" :style="stopIcon" class="cover-view-stop" />
<cover-view v-if="cover && videoInfo_2.coverStatus" :style="fillScreen2" class="video-image">
<image :mode="videoInfo_2.mode || mode" :src="videoInfo_2.coverUrl" :style="fillScreen"></image>
</cover-view>
<view class="video-action" @touchstart='videotouchstart' @touchmove="videotochmove"
@touchend='videotouchend' @click="videoClick"></view>
<!-- 视频文本区域 - 2号视频 -->
<view class="video-texts">
<view class="flex-row-center" style="flex: 1;">
<text class="video-title">珍草品汇理疗馆珍馆服务流程11订单</text>
<text class="video-score">评分 4.8</text>
</view>
<view class="flex-row-center-between" style="flex: 1;">
<text class="video-text">{{videoInfo_2.name}}</text>
<!-- 声音图标 - 绑定2号视频的状态 -->
<image class="video-sound"
:src="videoInfo_2.isMuted ? '/static/images/icons/sound_no.png' : '/static/images/icons/sound_yes.png'"
@click.stop="toggleMute(2)"></image>
</view>
</view>
</view>
<view class="video-box" :style="itemViewTopLength3">
<video :src="videoInfo_3.src" :style="fillScreen" :autoplay="videoInfo_3.autoplay"
:loop="videoInfo_3.loop" :controls="videoInfo_3.controls"
:enable-play-gesture="videoInfo_3.enablePlayGesture" :objectFit="videoInfo_3.objectFit || objectFit"
:show-fullscreen-btn="videoInfo_3.showFullscreenBtn"
:enable-progress-gesture="videoInfo_3.enableProgressGesture" @timeupdate="timeupdate(3,$event)"
@ended='ended' :show-play-btn="videoInfo_3.showPlayBtn" :show-progress="videoInfo_3.showProgress"
initial-time="0" id='video_id_3' @play="playing(3)" :muted="videoInfo_3.isMuted"
webkit-playsinline="true" playsinline="true">
</video>
<cover-view v-if="!videoInfo_3.flag" :style="stopIcon" class="cover-view-stop" />
<cover-view v-if="cover && videoInfo_3.coverStatus" :style="fillScreen2" class="video-image">
<image :mode="videoInfo_3.mode || mode" :src="videoInfo_3.coverUrl" :style="fillScreen"></image>
</cover-view>
<view class="video-action" @touchstart='videotouchstart' @touchmove="videotochmove"
@touchend='videotouchend' @click="videoClick"></view>
<!-- 视频文本区域 - 3号视频 -->
<view class="video-texts">
<view class="flex-row-center" style="flex: 1;">
<text class="video-title">珍草品汇理疗馆珍馆服务流程11订单</text>
<text class="video-score">评分 4.8</text>
</view>
<view class="flex-row-center-between" style="flex: 1;">
<text class="video-text">{{videoInfo_3.name}}</text>
<!-- 声音图标 - 绑定3号视频的状态 -->
<image class="video-sound"
:src="videoInfo_3.isMuted ? '/static/images/icons/sound_no.png' : '/static/images/icons/sound_yes.png'"
@click.stop="toggleMute(3)"></image>
</view>
</view>
</view>
<view class="video-box" :style="itemViewTopLength4">
<video :src="videoInfo_4.src" :style="fillScreen" :autoplay="videoInfo_4.autoplay"
:loop="videoInfo_4.loop" :controls="videoInfo_4.controls"
:enable-play-gesture="videoInfo_4.enablePlayGesture" :objectFit="videoInfo_4.objectFit || objectFit"
:show-fullscreen-btn="videoInfo_4.showFullscreenBtn"
:enable-progress-gesture="videoInfo_4.enableProgressGesture" @timeupdate="timeupdate(4,$event)"
@ended='ended' :show-play-btn="videoInfo_4.showPlayBtn" :show-progress="videoInfo_4.showProgress"
initial-time="0" id='video_id_4' @play="playing(4)" :muted="videoInfo_4.isMuted"
webkit-playsinline="true" playsinline="true">
</video>
<cover-view v-if="!videoInfo_4.flag" :style="stopIcon" class="cover-view-stop" />
<cover-view v-if="cover && videoInfo_4.coverStatus" :style="fillScreen2" class="video-image">
<image :mode="videoInfo_4.mode || mode" :src="videoInfo_4.coverUrl" :style="fillScreen"></image>
</cover-view>
<view class="video-action" @touchstart='videotouchstart' @touchmove="videotochmove"
@touchend='videotouchend' @click="videoClick"></view>
<!-- 视频文本区域 - 4号视频 -->
<view class="video-texts">
<view class="flex-row-center" style="flex: 1;">
<text class="video-title">珍草品汇理疗馆珍馆服务流程11订单</text>
<text class="video-score">评分 4.8</text>
</view>
<view class="flex-row-center-between" style="flex: 1;">
<text class="video-text">{{videoInfo_4.name}}</text>
<!-- 声音图标 - 绑定4号视频的状态 -->
<image class="video-sound"
:src="videoInfo_4.isMuted ? '/static/images/icons/sound_no.png' : '/static/images/icons/sound_yes.png'"
@click.stop="toggleMute(4)"></image>
</view>
</view>
</view>
<view @touchend='progressTouchend' class="progress-view">
<uv-slider v-if="showSlider" v-model="progressValue" activeColor="#D8D8D8" block-size="6"
backgroundColor="#494949;" class="progress-slider"></uv-slider>
</view>
<!-- 修改Dot指示器 - 最多显示5个 -->
<view class="dots" :style="{top:`${0.5*winHeight}px`}" v-if="videoListLength > 1">
<view class="dots-container">
<view class="dot-item" v-for="(dotIndex, idx) in visibleDots" :key="idx"
:class="{ active: dotIndex === videoIndex }" @click="jumpToVideo(dotIndex)"></view>
</view>
<text class="pageNumber">
{{ videoIndex+1 }}/{{ videoListLength }}
</text>
</view>
<view class="video-page">
<!-- 原生视频组件:全端兼容 -->
<video id="fullVideo" class="video-box" :src="videoUrl" controls show-fullscreen-btn enable-progress-gesture
:autoplay="false" :loop="false" :muted="false" @play="onPlay" @pause="onPause"
@fullscreenchange="handleFullScreenChange" @ended="onVideoEnd"></video>
<!-- 自定义操作按钮 -->
<view class="btn-wrap">
<button type="primary" size="default" @click="openFullScreen">
手动横屏全屏播放
</button>
<button type="warn" size="default" @click="closeFullScreen">
退出全屏
</button>
</view>
</view>
</template>
<script>
let VIDEO_LIST = []
let initVideoInfo = {
src: "",
coverStatus: true,
coverUrl: "",
flag: false,
objectFit: "",
mode: "",
isMuted: false, // 默认不静音
autoplay: false,
loop: true,
controls: false,
enablePlayGesture: true,
showFullscreenBtn: false,
enableProgressGesture: false,
showPlayBtn: false,
showProgress: false
}
export default {
props: {
popVideoList: {
type: Array,
default: [] // 默认值设为空数组JSON避免parse报错
},
marginTop: {
type: String,
default: "88" // 默认值设为空数组JSON避免parse报错
}
},
data() {
return {
playSetTime:null,
showSlider: true,
progressTime: 0,
progressValue: 0,
boxHeight: 0,
objectFit: "fill",
mode: "scaleToFill",
winWidth: 1,
winHeight: 1,
videoIndex: 0,
viewTop: 0,
viewTopPostion: 0,
videoList: [],
videoListLength: 0,
itemViewTop0: 0,
itemViewTop1: 0,
itemViewTop2: 0,
itemViewTop3: 0,
itemViewTop4: 0,
videoInfo_0: initVideoInfo,
videoInfo_1: initVideoInfo,
videoInfo_2: initVideoInfo,
videoInfo_3: initVideoInfo,
videoInfo_4: initVideoInfo,
stopIcon: '',
videotouchstartTimestamp: 0,
videotouchendTimestamp: 0,
moveView: true,
cover: true,
slidingStatus: false,
videoPlayingStatus: false,
preloadedIndex: [],
startPageY: 0,
endPageY: 0,
// 新增控制可视dot的数组
visibleDots: [],
// 测试视频地址,替换自己的
videoUrl: "https://www.w3school.com.cn/i/movie.mp4",
videoContext: null
};
},
watch: {
// videoList: {
// handler(newVal, oldVal) {
// },
// immediate: true, // 可选:组件初始化时立即执行一次(即使值未变化)
// deep: false // 普通类型无需深度监听默认false
// }
},
created(options) {
//#ifdef APP-PLUS
plus.screen.lockOrientation("portrait-primary")
//#endif
if (this.popVideoList.length) {
try {
VIDEO_LIST = []
this.popVideoList.forEach((item, index) => {
let obj = {
"src": "",
"coverStatus": true,
"coverUrl": "",
"flag": false,
"objectFit": "contain",
"mode": "aspectFit",
"isMuted": false, // 默认不静音
"autoplay": false,
"loop": true,
"controls": false,
"enablePlayGesture": true,
"showFullscreenBtn": false,
"enableProgressGesture": false,
"showPlayBtn": false,
"showProgress": false,
"name": ""
}
obj.name = item.name
obj.src = item.url
obj.coverUrl = item.url +
`?x-oss-process=video/snapshot,t_1000,m_fast,f_jpg,w_600,ar_auto`
VIDEO_LIST.push(obj)
if (index == 0) {
initVideoInfo = obj
}
})
} catch (e) {}
this.getSystemInfo();
this.videoList = VIDEO_LIST;
this.videoListLength = this.videoList.length;
this.boxHeight = this.videoListLength * this.winHeight;
for (let i = 0; i < 5; i++) {
if (this.videoList[i]) {
this['videoInfo_' + i] = this.videoList[i]
}
}
// 初始化可视dot范围
this.updateVisibleDots();
// setTimeout(() => {
// this.playVideo(0)
// }, 500)
}
},
onUnload() {
for (let i = 0; i < 5; i++) {
this.unloadVideo(i)
}
},
computed: {
getVideoTop() {
return uni.upx2px(this.marginTop)
},
viewTopLength() {
return `height:${this.boxHeight}px;top:${this.viewTop}px;`
},
fillScreen() {
let style = `height:${this.winHeight}px;width:${this.winWidth}px;`
return style
},
fillScreen2() {
let style = `height:auto;width:${this.winWidth}px;`
return style
},
itemViewTopLength0() {
return `height:${this.winHeight}px;top:${this.itemViewTop0}px;`
},
itemViewTopLength1() {
return `height:${this.winHeight}px;top:${this.itemViewTop1}px;`
},
itemViewTopLength2() {
return `height:${this.winHeight}px;top:${this.itemViewTop2}px;`
},
itemViewTopLength3() {
return `height:${this.winHeight}px;top:${this.itemViewTop3}px;`
},
itemViewTopLength4() {
return `height:${this.winHeight}px;top:${this.itemViewTop4}px;`
}
onReady() {
// 初始化视频实例
this.videoContext = uni.createVideoContext("fullVideo", this);
},
methods: {
// 新增:切换视频静音状态
toggleMute(videoNum) {
// 1. 切换数据层状态
this['videoInfo_' + videoNum].isMuted = !this['videoInfo_' + videoNum].isMuted;
// 2. 同步视频实例静音状态(恢复并完善原有注释代码,加容错)
const videoCtx = uni.createVideoContext("video_id_" + videoNum, this);
if (videoCtx) {
videoCtx.mute(this['videoInfo_' + videoNum].isMuted);
}
// 进入横屏全屏
openFullScreen() {
// direction 90横屏-90反向横屏0自适应
this.videoContext.requestFullScreen({
direction: 90
});
},
// 修改点击dot跳转 - 传真实的视频索引
jumpToVideo(targetIndex) {
if (targetIndex === this.videoIndex || this.slidingStatus) return;
this.stopVideo(this.videoIndex % 5, true);
const diff = targetIndex - this.videoIndex;
this.videoIndex = targetIndex;
this.viewTopPostion = -this.videoIndex * this.winHeight;
this.viewTop = this.viewTopPostion;
if (this.videoListLength > 5) {
this.sortItemView(diff > 0 ? -1 : 1);
}
// 更新可视dot范围
this.updateVisibleDots();
setTimeout(() => {
this.playVideo(this.videoIndex % 5);
}, 400);
// 退出全屏
closeFullScreen() {
this.videoContext.exitFullScreen();
},
// 核心更新可视dot范围 - 最多显示5个
updateVisibleDots() {
const maxShow = 5;
const total = this.videoListLength;
// 视频总数 ≤ 5显示全部
if (total <= maxShow) {
this.visibleDots = Array.from({
length: total
}, (_, i) => i);
return;
}
// 视频总数 > 5显示包含当前索引的5个dot
// 计算起始索引,避免越界
let start = Math.max(0, Math.min(this.videoIndex - 2, total - maxShow));
// 生成5个连续的索引
this.visibleDots = Array.from({
length: maxShow
}, (_, i) => start + i);
// 播放监听
onPlay() {
console.log("视频开始播放");
},
progressTouchend() {
let s = Math.round(this.progressValue / 100 * this.time)
let i = this.videoIndex % 5
uni.createVideoContext("video_id_" + i, this).seek(s)
uni.createVideoContext("video_id_" + i, this).play()
// 暂停监听
onPause() {
console.log("视频暂停");
},
sliderchanging(e) {
console.log(3333, e)
},
getSystemInfo() {
var _this = this;
uni.getSystemInfo({
success: function(res) {
_this.winWidth = res.windowWidth;
_this.winHeight = res.windowHeight;
let p = uni.upx2px(150)
_this.stopIcon = `left:${(res.windowWidth - p)/2}px;top:${(res.windowHeight - p)/2}px;`
_this.itemViewTop1 = res.windowHeight
_this.itemViewTop2 = res.windowHeight * 2
_this.itemViewTop3 = res.windowHeight * 3
_this.itemViewTop4 = res.windowHeight * 4
},
})
},
unloadVideo(index) {
if (index < 0 || index >= this.videoListLength) return;
const nodeNum = index % 5;
const videoCtx = uni.createVideoContext("video_id_" + nodeNum, this);
videoCtx.stop();
this.preloadedIndex = this.preloadedIndex.filter(item => item !== index);
},
playVideo(num) {
clearTimeout(this.playSetTime)
uni.$uv.debounce(() => {
this.videoPlayingStatus = true
this["videoInfo_" + num].flag = true
let VideoContext = uni.createVideoContext("video_id_" + num, this)
this.playSetTime = setTimeout(() => {
VideoContext.play()
}, 400)
}, 30)
},
stopVideo(num, control = false) {
const videoCtx = uni.createVideoContext("video_id_" + num, this);
if (!control) {
this['videoInfo_' + num].coverStatus = true;
videoCtx.stop()
// 全屏状态切换监听
handleFullScreenChange(e) {
if (e.detail.fullScreen) {
uni.setNavigationBarHidden(true); // 全屏隐藏顶部导航
} else {
videoCtx.pause()
uni.setNavigationBarHidden(false); // 退出恢复导航
}
this["videoInfo_" + num].flag = false
console.log("当前全屏状态:", e.detail.fullScreen);
},
nowPlayVideo() {
clearTimeout(this.playSetTime)
uni.$uv.debounce(() => {
this.videoPlayingStatus = true
this["videoInfo_" + this.videoIndex].flag = true
let VideoContext = uni.createVideoContext("video_id_" + this.videoIndex, this)
this.playSetTime = setTimeout(() => {
VideoContext.play()
}, 400)
}, 30)
},
nowStopVideo() {
const videoCtx = uni.createVideoContext("video_id_" + this.videoIndex, this);
videoCtx.pause()
this["videoInfo_" + this.videoIndex].flag = false
},
videoClick() {
let num = this.videoIndex % 5
if (this["videoInfo_" + num].flag) {
this.stopVideo(num, true)
} else {
this.playVideo(num, true)
}
},
videotouchstart(e) {
this.videotouchendTimestamp = this.videotouchstartTimestamp
this.videotouchstartTimestamp = e.timestamp
if ((this.videotouchstartTimestamp - this.videotouchendTimestamp) < 500) return
if (this.slidingStatus) return
this.startPageY = e.changedTouches[0].screenY;
},
videotochmove(e) {
if ((this.videotouchstartTimestamp - this.videotouchendTimestamp) < 500) return
if (this.slidingStatus) return
this.endPageY = e.changedTouches[0].screenY;
let sliding = this.endPageY - this.startPageY
if (sliding > 20 || sliding < -20) {
this.moveView = false
} else {
this.moveView = true
}
if (this.videoIndex == 0 && sliding > 50) {
this.viewTop = 50
} else if (this.videoIndex == this.videoListLength - 1 && sliding < 0) {
this.viewTop = this.viewTopPostion
} else {
this.viewTop = this.viewTopPostion + sliding
}
},
videotouchend(e) {
if ((this.videotouchstartTimestamp - this.videotouchendTimestamp) < 500) return
if (this.slidingStatus) return
this.moveView = true
this.endPageY = e.changedTouches[0].screenY;
let sliding = this.endPageY - this.startPageY
if ((sliding >= -40 && sliding < 0) || (sliding <= 40 && sliding > 0)) {
this.slidingStatus = true
this.videoSlidingReset(sliding)
return
}
if (sliding < -30) {
this.slidingStatus = true
if (this.videoIndex == this.videoListLength - 1) {
this.videoSlidingReset(sliding)
uni.showToast({
title: "已经到底了",
icon: "none"
})
return
}
this.moveView = false
this.videoSlidingToTop(sliding)
return
}
if (sliding > 30) {
this.slidingStatus = true
if (this.videoIndex == 0) {
this.videoSlidingReset(sliding)
return
}
this.moveView = false
this.videoSlidingToBottom(sliding)
}
},
videoSlidingReset(sliding) {
this.viewTop = this.viewTopPostion
this.slidingStatus = false
},
videoSlidingToTop(sliding) {
this.stopVideo(this.videoIndex % 5, true)
this.videoIndex++
if (this.videoListLength > 5) {
this.sortItemView(sliding)
}
this.viewTopPostion = this.viewTopPostion - this.winHeight
setTimeout(() => {
this.playVideo(this.videoIndex % 5)
}, 5)
let timer = setInterval(() => {
this.viewTop = this.viewTop - 35
if (this.viewTop < (this.viewTopPostion + 10)) {
this.viewTop = this.viewTopPostion
clearInterval(timer)
this.slidingStatus = false
this.slidingCallback()
}
}, 3)
},
videoSlidingToBottom(sliding) {
this.stopVideo(this.videoIndex % 5, true)
this.videoIndex--
if (this.videoListLength > 5) {
this.sortItemView(sliding)
}
this.viewTopPostion = this.viewTopPostion + this.winHeight
setTimeout(() => {
this.playVideo(this.videoIndex % 5)
}, 5)
let timer = setInterval(() => {
this.viewTop = this.viewTop + 35
if (this.viewTop > (this.viewTopPostion - 10)) {
this.viewTop = this.viewTopPostion
clearInterval(timer)
this.slidingStatus = false
this.slidingCallback()
}
}, 3)
},
// 修改滑动结束后更新可视dot
slidingCallback() {
this.progressValue = 0
this.updateVisibleDots();
},
//滑动组件排序
sortItemView(silding) {
// 向上滑动
if (silding < 0) {
if (this.videoIndex > 2 && this.videoIndex < this.videoListLength - 2) {
let i = (this.videoIndex - 3) % 5
this["itemViewTop" + i] = this["itemViewTop" + i] + this.winHeight * 5
this['videoInfo_' + i] = this.videoList[this.videoIndex + 2]
}
return
}
// 向下滑动
if (silding > 0) {
if (this.videoIndex > 1 && this.videoIndex < this.videoListLength - 3) {
let i = (this.videoIndex + 3) % 5
this["itemViewTop" + i] = this["itemViewTop" + i] - this.winHeight * 5
this['videoInfo_' + i] = this.videoList[this.videoIndex - 2]
}
}
},
playing(i) {
setTimeout(() => {
this['videoInfo_' + i].coverStatus = false
this.moveView = true
this.slidingStatus = false
}, 600)
},
timeupdate(i, e) {
if (this.videoPlayingStatus) {
this.slidingStatus = false
this.videoPlayingStatus = false
setTimeout(() => {
this['videoInfo_' + i].coverStatus = false
this.moveView = true
}, 200)
}
this["videoInfo_" + i].flag = true
if (e.detail.duration >= this.progressTime) {
if (this.videoIndex%5 == i) {
this.time = e.detail.duration
this.progress = e.detail.currentTime
this.progressValue = (e.detail.currentTime / e.detail.duration) * 100
}
this.showProgress = true
} else {
this.showProgress = false
}
},
ended() {
let num = this.videoIndex % 5
this["videoInfo_" + num].flag = false
this.progressValue = 0
// 视频播放结束
onVideoEnd() {
// 播放完毕自动退出全屏
this.videoContext.exitFullScreen();
}
}
}
};
</script>
<style scoped lang="less">
.view-box {
// position: fixed;
// left: 0;
// right: 0;
background-color: #000;
// z-index: 900;
<style scoped>
.video-page {
padding: 15rpx;
}
.video-box {
position: absolute;
left: 0;
right: 0;
z-index: 2000;
width: 100%;
height: 420rpx;
border-radius: 12rpx;
overflow: hidden;
}
.video-image {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
z-index: 2001;
}
.video-action {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
z-index: 2008;
}
.video-texts {
position: absolute;
left: 30rpx;
right: 30rpx;
bottom: 120rpx;
z-index: 2009; // 确保在视频上方显示
.video-title {
font-weight: 500;
font-size: 24rpx;
color: #FFFFFF;
text-align: left;
font-style: normal;
lines: 1;
max-width: 240rpx;
}
.video-score {
font-weight: 500;
font-size: 24rpx;
color: #FFFFFF;
text-align: left;
font-style: normal;
margin-left: 20rpx;
}
.video-text {
font-weight: 400;
font-size: 28rpx;
color: #FFFFFF;
text-align: left;
font-style: normal;
}
.video-sound {
width: 44rpx;
height: 42rpx;
cursor: pointer; // 点击指针,提示可点击
}
}
.cover-view-stop {
position: absolute;
z-index: 9999;
width: 150rpx;
height: 200rpx;
background-color: rgba(0, 0, 0, 0.3);
border-radius: 50%;
}
.progress-view {
z-index: 9999;
position: fixed;
left: 30rpx;
right: 30rpx;
bottom: 80rpx;
.progress-slider {}
}
// 修改Dot指示器样式 - 最多显示5个 + 过渡动画
.dots {
position: fixed;
left: 20rpx;
top: 0;
transform: translateY(-50%);
z-index: 9999;
flex-direction: row;
.dots-container {
// 限制最多显示5个超出隐藏
width: fit-content;
overflow: hidden;
margin: auto 0;
.dot-item {
width: 10rpx;
height: 10rpx;
background: #FFFFFF;
border-radius: 50%;
// 核心:过渡动画,切换更丝滑
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
cursor: pointer;
margin-bottom: 10rpx;
&.active {
width: 10rpx;
height: 30rpx;
background: #FFFFFF;
border-radius: 5rpx;
}
}
}
.pageNumber {
font-weight: bold;
font-size: 28rpx;
color: #FFFFFF;
line-height: 36rpx;
text-align: left;
font-style: normal;
margin-left: 6rpx;
}
}
.flex-row-center-between {
.btn-wrap {
margin-top: 30rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.flex-row-start-between {
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: space-between;
}
.flex-row-center-center {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.flex-row-center {
display: flex;
flex-direction: row;
align-items: center;
}
.flex-row-start {
display: flex;
flex-direction: row;
align-items: flex-start;
}
.flex-row-end-between {
display: flex;
flex-direction: row;
align-items: flex-end;
justify-content: space-between;
gap: 20rpx;
}
</style>

View File

@ -0,0 +1,106 @@
<template>
<view class="video-page">
<!-- 原生视频组件全端兼容 -->
<video id="fullVideo" class="video-box" :src="videoUrl" controls show-fullscreen-btn enable-progress-gesture
:autoplay="true" :loop="false" :muted="false" @play="onPlay" @pause="onPause" object-fit="cover" :direction="90"
@fullscreenchange="handleFullScreenChange" @ended="onVideoEnd" @fullscreenclick="tapback"></video>
</view>
</template>
<script>
export default {
data() {
return {
videoUrl: "",
videoContext: null
};
},
onLoad(options) {
console.log(options, '页面参数');
//
if (options.url) {
this.videoUrl = options.url;
}
},
onReady() {
// #ifdef APP-PLUS
//plus.screen.unlockOrientation(); //
// #endif
//
this.videoContext = uni.createVideoContext("fullVideo", this);
//
//this.autoFullScreen();
},
methods: {
tapback(e) {
console.log('点击全屏按钮');
// if (e.target.fullScreen) {
// plus.screen.lockOrientation('landscape'); //
// }else{
// plus.screen.lockOrientation('portrait'); //
// }
},
// iOS
autoFullScreen() {
if (!this.videoContext) return;
// iOS090
const direction = uni.getSystemInfoSync().platform === 'ios' ? 0 : 90;
//
setTimeout(() => {
this.videoContext.requestFullScreen({
direction: direction
});
}, 300)
},
//
openFullScreen() {
if (!this.videoContext) return;
const direction = uni.getSystemInfoSync().platform === 'ios' ? 0 : 90;
this.videoContext.requestFullScreen({
direction
});
},
// 退
closeFullScreen() {
if (!this.videoContext) return;
this.videoContext.exitFullScreen();
},
//
onPlay() {
console.log("视频开始播放");
},
//
onPause() {
console.log("视频暂停");
},
//
handleFullScreenChange(e) {
// 退
// uni.setNavigationBarHidden(!!e.detail.fullScreen);
console.log("全屏状态:", e.detail.fullScreen);
},
//
onVideoEnd() {
this.closeFullScreen();
}
}
};
</script>
<style scoped>
.video-page {
width: 100%;
height: 100vh;
box-sizing: border-box;
}
.video-box {
width: 100%;
/* 自适应高度,避免拉伸 */
height: 100%;
object-fit: cover;
}
</style>

View File

@ -1,52 +1,59 @@
<template>
<view class="container">
<view class="positonFixed">
<view class="header-top flex-row-center" :class="{videoTop:current==0}"
:style="{ height: `${88+statusBarHeight}rpx`,paddingTop: `${statusBarHeight}rpx`}">
<view class="header-top flex-row-center" :style="{ height: `${100 + statusBarHeight}rpx`,paddingTop:`${statusBarHeight}rpx`}">
<view class="left-area" @click="goBack">
<image src="/static/images/whiteBack.png" mode="aspectFit" class="back-icon"></image>
<image src="/static/images/back.png" mode="aspectFit" class="back-icon"></image>
</view>
<view class="flex-row-center-center" style="flex: 1;margin-right: 18rpx;height:66rpx;">
<view class="flex-row-center-center" style="flex: 1;margin-right: 18rpx;height:66rpx;">
<uv-tabs :list="list" @click="tabsClick" itemStyle="height:66rpx;" :activeStyle="activeStyle"
:inactiveStyle="inactiveStyle" :current="current" :lineColor="`rgba(232, 16, 30, 1)`"></uv-tabs>
</view>
</view>
<scroll-view scroll-x style="height: 100rpx;" v-if="current==1">
<scroll-view scroll-x style="height: 100rpx;background-color: #fff;" v-if="current==1">
<view class="container-tabs flex-row-center">
<view class="container-tab flex-row-center-center" @click="clickTab(item)"
:class="{active:cardListIndex==item.id}" v-for="(item,index) in cardList[1]" :key="index">
:class="{active:cardListIndex==item.id}" v-for="(item,index) in cardList[user_type]"
:key="index">
<text class="container-tab-text">{{ item.title }}</text>
</view>
</view>
</scroll-view>
</view>
<view :style="swiperStyle3"></view>
<view class="container-content" :style="swiperStyle2">
<swiper class="swiper" circular :style="swiperStyle" :current="current" @change="swiperChange" :disable-touch="current==0">
<swiper class="swiper" circular :style="swiperStyle" :current="current" @change="swiperChange"
:disable-touch="current==0">
<swiper-item>
<videoSlide ref="ideoSlideRrf" :popVideoList="videoList" :marginTop="`${88 + this.statusBarHeight}`" v-show="current==0" v-if="videoList.length"></videoSlide>
<scroll-view scroll-y :style="{height:`${viewportHeight - 100 - statusBarHeight}rpx`}">
<view class="swiper-items3">
<view class="swiper-item3" :class="{item2Left:index%2 == 0}"
v-for="(item,index) in videoList" :key="index" @click="tovideo(item)">
<image class="swiper-item3-video" mode="aspectFill" :src="item.img"></image>
<image
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/c6c039fc-99fd-492e-ae9a-ed43260c94ef.png"
mode="widthFix"
style="width: 80rpx;position: absolute;top: 50%;left: 50%;transform: translate3d(-50%, -50%, 0);">
</image>
</view>
</view>
</scroll-view>
</swiper-item>
<swiper-item>
<scroll-view scroll-y :style="{height:`${viewportHeight-88-this.statusBarHeight-100}rpx`}">
<scroll-view scroll-y :style="{height:`${viewportHeight - 100 - statusBarHeight - 100}rpx`}">
<view class="swiper-items2">
<view class="swiper-item2" :class="{item2Left:index%2 == 0}"
v-for="(item,index) in imagObj[cardListIndex]" :key="index">
v-for="(item,index) in imagObj[cardListIndex]" :key="index" @click="toimg(item)">
<image class="swiper-item2-img" mode="aspectFill" :src="item.url"></image>
<view class="swiper-item2-title">
<text class="swiper-item2-title-text">{{ item.name }}</text>
</view>
</view>
</view>
</scroll-view>
</swiper-item>
</swiper>
</view>
</view>
</template>
@ -68,6 +75,23 @@
title: "主图",
id: 1
},
{
title: "作品",
id: 3
},
{
title: "其他",
id: 4
}
],
2: [{
title: "全部",
id: 0
},
{
title: "主图",
id: 1
},
{
title: "环境",
id: 2
@ -94,7 +118,7 @@
inactiveStyle: {
fontWeight: 500,
fontSize: "32rpx",
color: "#666666",
color: "#333333",
},
current: 1,
statusBarHeight: 0,
@ -108,6 +132,7 @@
};
},
async onLoad(options) {
// 获取状态栏高度
const systemInfo = await uni.getSystemInfoSync()
this.systemInfo = systemInfo
@ -120,6 +145,12 @@
this.getVideoList()
}
},
onShow() {
// // #ifdef APP-PLUS
// plus.screen.unlockOrientation(); //解除屏幕方向的锁定,但是不一定是竖屏;
// plus.screen.lockOrientation('portrait'); //锁死屏幕方向为竖屏
// // #endif
},
computed: {
// 计算swiper的样式
swiperStyle() {
@ -128,68 +159,82 @@
// 注意100vh 是视口高度,需要先转数值计算,再拼接单位
let viewportHeight = this.pxToRpx(this.systemInfo.windowHeight || window?.innerHeight || document
?.documentElement?.clientHeight|| 0, screenWidth ) ;
let height = viewportHeight - 88 - this.statusBarHeight;
?.documentElement?.clientHeight || 0, screenWidth);
let height = viewportHeight - 100 - this.statusBarHeight;
return {
height: `${height}rpx`
};
}
return {
height: `${this.viewportHeight}rpx`
height: `${this.viewportHeight - 100 - this.statusBarHeight}rpx`
};
},
swiperStyle2() {
if (this.current == 1) {
return {
marginTop: `${88 + this.statusBarHeight + 100}rpx`,
height: `${this.viewportHeight-88-this.statusBarHeight-100}rpx`
height: `${this.viewportHeight-100-this.statusBarHeight-100}rpx`
};
}
return {
height: `${this.viewportHeight}rpx`
height: `${this.viewportHeight - 100 - this.statusBarHeight}rpx`
};
},
swiperStyle3() {
if (this.current == 1) {
return {
width: `750rpx`,
height: `${200 + this.statusBarHeight}rpx`,
};
}
return {
width: `750rpx`,
height: `${100 + this.statusBarHeight}rpx`,
};
},
viewportHeight() {
let screenWidth = this.systemInfo.screenWidth;
// 注意100vh 是视口高度,需要先转数值计算,再拼接单位
let viewportHeight = this.pxToRpx(this.systemInfo.windowHeight || window?.innerHeight || document
?.documentElement?.clientHeight|| 0, screenWidth) ;
?.documentElement?.clientHeight || 0, screenWidth);
return viewportHeight
},
},
methods: {
goBack() {
uni.navigateBack(-1)
},
toimg(item) {
console.log(item, 'item');
uni.previewImage({
urls: [item.url], // 外面加 [] 变成数组
current: 0 // 可选,默认第一张
})
},
tovideo(item) {
uni.navigateTo({
url: '/pages/album/components/videoplayer?url=' + item.url
})
},
clickTab(item) {
this.cardListIndex = item.id
},
tabsClick(e) {
this.$nextTick(() => {
this.current = e.index
if(this.current==1){
this.activeStyle.color = "#333333"
this.inactiveStyle.color = "#666666"
this.$refs.ideoSlideRrf.nowStopVideo()
}
if(this.current==0){
this.activeStyle.color = "#fff"
this.inactiveStyle.color = "#ccc"
this.$refs.ideoSlideRrf.nowPlayVideo()
}
})
console.log('12312312312');
this.current = e.index
this.activeStyle.color = "#333333"
this.inactiveStyle.color = "#666666"
},
swiperChange(e) {
this.$nextTick(() => {
this.current = e.detail.current
if(this.current==1){
if (this.current == 1) {
this.activeStyle.color = "#333333"
this.inactiveStyle.color = "#666666"
this.$refs.ideoSlideRrf.nowStopVideo()
}
if(this.current==0){
this.activeStyle.color = "#fff"
this.inactiveStyle.color = "#ccc"
this.$refs.ideoSlideRrf.nowPlayVideo()
if (this.current == 0) {
this.activeStyle.color = "#333333"
this.inactiveStyle.color = "#666666"
}
})
@ -208,12 +253,17 @@
type: 1
}).then(res => {
if (res.code == 200) {
this.imagObj = res.data.list[1]
let all = []
for (let key in res.data.list[1]) {
all = [...all, ...res.data.list[1][key]]
if (res.data.list.length != 0) {
this.imagObj = res.data.list[1]
let all = []
for (let key in res.data.list[1]) {
all = [...all, ...res.data.list[1][key]]
}
this.imagObj[0] = all
} else {
this.imagObj[0] = []
}
this.imagObj[0] = all
}
})
},
@ -224,8 +274,17 @@
type: 2
}).then(res => {
if (res.code == 200) {
for (let key in res.data.list[2]) {
this.videoList = [...this.videoList, ...res.data.list[2][key]]
if (res.data.list.length != 0) {
console.log(res.data, 'res.data');
for (let key in res.data.list[2]) {
this.videoList = [...this.videoList, ...res.data.list[2][key]]
this.videoList.forEach((item) => {
item.img = item.url +
'?x-oss-process=video/snapshot,t_1000,m_fast,f_jpg,w_600,ar_auto'
})
}
} else {
this.videoList = []
}
}
})
@ -249,7 +308,7 @@
z-index: 9999;
background: #f5f5f5;
padding: 0 24rpx;
.left-area {
display: flex;
align-items: center;
@ -266,10 +325,12 @@
}
}
}
.videoTop{
.videoTop {
background: transparent;
::v-deep .uv-tabs__wrapper__nav__line{
background: #fff!important;
::v-deep .uv-tabs__wrapper__nav__line {
background: #fff !important;
}
}
@ -296,7 +357,7 @@
.container-tab-text {
font-weight: 400;
font-size: 28rpx;
color: #E8101E;
color: #FF4767;
line-height: 40rpx;
text-align: left;
font-style: normal;
@ -306,6 +367,7 @@
.container-content {
z-index: 1000;
.swiper {
background: #fff;
@ -349,6 +411,47 @@
}
}
.swiper-items3 {
flex-wrap: wrap;
flex-direction: row;
display: flex;
padding-bottom: 20rpx;
.swiper-item3 {
width: 370rpx;
height: 370rpx;
margin-top: 10rpx;
overflow: hidden;
position: relative;
box-sizing: border-box;
border-radius: 20rpx;
.swiper-item3-video {
width: 370rpx;
height: 370rpx;
}
.swiper-item3-title {
position: absolute;
left: 0;
top: 0;
height: 33rpx;
padding: 0 8rpx;
background: rgba(0, 0, 0, 0.5);
border-radius: 0rpx 0rpx 10rpx 0rpx;
width: fit-content;
.swiper-item3-title-text {
font-weight: 400;
font-size: 18rpx;
color: #FFFFFF;
line-height: 33rpx;
}
}
}
}
.item2Left {
margin-right: 9rpx;
}
@ -398,6 +501,4 @@
align-items: flex-end;
justify-content: space-between;
}
</style>

View File

@ -86,7 +86,7 @@
? "门店下单购买后,您需到上门至门店进行服务,门店端展示价格为"
: ""
}}
<text style="color: #e8101e" v-if="showShopPrice">{{
<text style="color: #FF4767" v-if="showShopPrice">{{
shopPrice
}}</text>
{{ showShopPrice ? "元。" : "" }}
@ -364,19 +364,6 @@
>
</view>
<!-- 新增上门提醒弹窗-->
<transition name="fade">
<view class="popup-mask2" v-if="showReminderPopup" @click="closeReminderPopup(false)">
<view class="popup-content2" @click.stop>
<view class="popup-title">提示</view>
<view class="popup-message">此服务是需要上门服务的哦</view>
<view class="popup-buttons">
<view class="popup-btn popup-btn-notshow" @click="closeReminderPopup(true)">不再提示</view>
<view class="popup-btn popup-btn-know" @click="closeReminderPopup(false)">我已知晓</view>
</view>
</view>
</view>
</transition>
</view>
</template>
@ -402,7 +389,6 @@ export default {
},
data() {
return {
showReminderPopup: false,
artisanType: getApp().globalData.artisanType,
nowQer: "xc",
progress: 0,
@ -474,11 +460,6 @@ export default {
this.serviceTypes = this.serviceTypes.concat(res.data);
});
}
//
const notShow = uni.getStorageSync('addServiceReminderNotShow');
if (!notShow) {
this.showReminderPopup = true;
}
this.getUserInfo();
},
computed: {
@ -575,14 +556,6 @@ export default {
return true;
},
closeReminderPopup(notShowAgain) {
console.log('关闭弹窗,不再提示:', notShowAgain);
if (notShowAgain) {
uni.setStorageSync('addServiceReminderNotShow', true);
}
this.showReminderPopup = false;
},
goImgAndText() {
uni.navigateTo({
url: `/pages/shop/add-img-text?list=${this.formData.graphic_details}`,
@ -1234,13 +1207,13 @@ export default {
.btn-draft {
background-color: #ffffff;
color: #e8101e;
border: 2rpx solid #e8101e;
color: #FF4767;
border: 2rpx solid #FF4767;
box-sizing: border-box;
}
.btn-submit {
background-color: #e8101e;
background-color: #FF4767;
color: #ffffff;
}
@ -1512,13 +1485,13 @@ export default {
}
.popup-btn-know {
background-color: #e8101e;
background-color: #FF4767;
color: #ffffff;
}
.popup-btn-notshow {
background-color: #ffffff;
color: #e8101e;
border: 1rpx #e8101e solid;
color: #FF4767;
border: 1rpx #FF4767 solid;
}
</style>

View File

@ -1204,7 +1204,7 @@ export default {
}
.change-info-btn, .view-change-btn, .resubmit-btn {
background: linear-gradient(180deg, #f52540 0%, #e8101e 100%);
background: linear-gradient(180deg, #ff8e9d 0%, #FF4767 100%);
}
/* 双按钮行样式 */
@ -1225,7 +1225,7 @@ export default {
}
.submit-btn {
background: linear-gradient(180deg, #F52540 0%, #E8101E 100%, #E8101E 100%);
background: linear-gradient(180deg, #ff8e9d 0%, #FF4767 100%);
border-radius: 43rpx;
padding: 0 15rpx;
}
@ -1257,7 +1257,7 @@ export default {
.title-bar {
width: 6rpx;
height: 30rpx;
background-color: #E8101E;
background-color: #FF4767;
margin-right: 7rpx;
border-radius: 5rpx;
}
@ -1405,7 +1405,7 @@ export default {
width: 60rpx;
height: 60rpx;
border: 4rpx solid #fff;
border-top: 4rpx solid #E8101E;
border-top: 4rpx solid #FF4767;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 16rpx;
@ -1470,8 +1470,8 @@ export default {
}
.radio.checked {
background-color: #E8101E;
border-color: #E8101E;
background-color: #FF4767;
border-color: #FF4767;
}
.radio-icon-image {
@ -1640,7 +1640,7 @@ export default {
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(180deg, #f52540 0%, #e8101e 100%);
background: linear-gradient(180deg, #ff8e9d 0%, #FF4767 100%);
}
.popup-btn .btn-text {
@ -1659,7 +1659,7 @@ export default {
}
.confirm-text {
color: #E8101E;
color: #FF4767;
font-weight: 500;
}
@ -1771,13 +1771,13 @@ export default {
}
.tip-btn.cancel {
border: #E8101E 2rpx solid;
border: #FF4767 2rpx solid;
background-color: transparent;
color: #e8101e;
color: #FF4767;
}
.tip-btn.confirm {
background: linear-gradient(180deg, #f52540 0%, #e8101e 100%);
background: linear-gradient(180deg, #ff8e9d 0%, #FF4767 100%);
color: #FFFFFF;
}

View File

@ -165,7 +165,7 @@ export default {
timeRange: '7days',
count: 0,
chartOpts: {
color: ["#E8101E"],
color: ["#FF4767"],
padding: [15, 15, 0, 15],
enableScroll: false,
legend: false,
@ -657,7 +657,7 @@ page {
}
.time-filter .active {
color: #E8101E;
color: #FF4767;
}
.chart-container {
@ -676,7 +676,7 @@ page {
width: 214rpx;
height: 56rpx;
background-color: #FFFFFF;
border: 1rpx solid #E8101E;
border: 1rpx solid #FF4767;
border-radius: 8rpx;
display: flex;
justify-content: center;
@ -687,7 +687,7 @@ page {
.type-name {
font-size: 24rpx;
color: #E8101E;
color: #FF4767;
}
.type-count {
@ -708,7 +708,7 @@ page {
background-color: #F53F3F;
}
.order-type-item.active {
background-color: #E8101E;
background-color: #FF4767;
}
.order-type-item.active .type-name {
color: #FFFFFF;
@ -800,7 +800,7 @@ page {
}
.action-btn.verify .action-text {
font-size: 28rpx;
color: #E8101E;
color: #FF4767;
}
.nothings-box {

View File

@ -249,7 +249,7 @@ page {
flex-flow: row nowrap;
justify-content: center;
align-items: center;
border: 1rpx solid #E8101E;
border: 1rpx solid #FF4767;
background-color: #FFF1F1;
border-radius: 22rpx;
box-sizing: border-box;
@ -267,7 +267,7 @@ page {
}
.verified-text {
font-size: 24rpx;
color: #E8101E;
color: #FF4767;
}
.verified-text.no {
color: #666666;

File diff suppressed because it is too large Load Diff

View File

@ -1544,7 +1544,7 @@ export default {
.tag {
font-size: 24rpx;
color: #E8101E;
color: #FF4767;
margin-left: 14rpx;
}
.textarea {
@ -1655,7 +1655,7 @@ export default {
.save-btn {
width: 702rpx;
height: 64rpx;
background: #E8101E;
background: #FF4767;
border-radius: 32rpx;
display: flex;
align-items: center;
@ -1762,8 +1762,8 @@ export default {
}
.checkbox.checked {
background-color: #E8101E;
border-color: #E8101E;
background-color: #FF4767;
border-color: #FF4767;
}
.check-icon {
@ -1796,7 +1796,7 @@ export default {
}
.popup-btn.confirm {
background: linear-gradient(90deg, #E8101E 0%, #FF618F 100%);
background: linear-gradient(90deg, #FF4767 0%, #FF618F 100%);
}
.popup-btn .btn-text {
@ -1834,7 +1834,7 @@ export default {
}
.region-tab.active {
color: #E8101E;
color: #FF4767;
font-weight: 500;
}
@ -1846,7 +1846,7 @@ export default {
transform: translateX(-50%);
width: 40rpx;
height: 4rpx;
background-color: #E8101E;
background-color: #FF4767;
border-radius: 2rpx;
}
@ -1907,7 +1907,7 @@ export default {
.confirm-text {
font-size: 32rpx;
color: #E8101E;
color: #FF4767;
font-weight: 500;
}
@ -1949,7 +1949,7 @@ export default {
}
.time-block.active .time-value {
color: #E8101E;
color: #FF4767;
}
.time-separator {

View File

@ -43,7 +43,7 @@
<view class="radio-item" @click="editable && setHealthCardValidType('permanent')">
<view class="radio" :class="{ checked: displayData.health_card_expiry_type === 1 }">
<image v-if="displayData.health_card_expiry_type === 1"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/cb185ec8-9d30-4e53-b5dc-14b546013a60"
src="/static/images/agree_y.png"
class="radio-icon-image"
mode="aspectFit"></image>
</view>
@ -52,7 +52,7 @@
<view class="radio-item" @click="editable && setHealthCardValidType('date')">
<view class="radio" :class="{ checked: displayData.health_card_expiry_type === 2 }">
<image v-if="displayData.health_card_expiry_type === 2"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/cb185ec8-9d30-4e53-b5dc-14b546013a60"
src="/static/images/agree_y.png"
class="radio-icon-image"
mode="aspectFit"></image>
</view>
@ -1292,7 +1292,7 @@ export default {
}
.change-info-btn, .view-change-btn, .resubmit-btn {
background: linear-gradient(180deg, #f52540 0%, #e8101e 100%);
background: linear-gradient(180deg, #ff8e9d 0%, #FF4767 100%);
}
/* 双按钮行样式 */
@ -1313,7 +1313,7 @@ export default {
}
.submit-btn {
background: linear-gradient(180deg, #F52540 0%, #E8101E 100%, #E8101E 100%);
background: linear-gradient(180deg, #ff8e9d 0%, #FF4767 100%);
border-radius: 43rpx;
padding: 0 15rpx;
}
@ -1345,7 +1345,7 @@ export default {
.title-bar {
width: 6rpx;
height: 30rpx;
background-color: #E8101E;
background-color: #FF4767;
margin-right: 7rpx;
border-radius: 5rpx;
}
@ -1472,8 +1472,8 @@ export default {
}
.radio.checked {
background-color: #E8101E;
border-color: #E8101E;
background-color: #FF4767;
border-color: #FF4767;
}
.radio-icon-image {

View File

@ -104,7 +104,7 @@
@click="toggleDistrictSelect(index)"
>
<view class="checkbox" :class="{ checked: district.checked }">
<image v-if="district.checked" class="check-icon" src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/66325904-4603-48fd-b03b-90c684af96e9" mode="aspectFit"></image>
<image v-if="district.checked" class="check-icon" src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/6948baf3-aad9-4d87-9483-9db0e462a3f7.png" mode="aspectFit"></image>
</view>
<text class="checkbox-label">{{district.area}}</text>
</view>
@ -752,7 +752,7 @@ export default {
.title-bar {
width: 6rpx;
height: 30rpx;
background-color: #E8101E;
background-color: #FF4767;
margin-right: 10rpx;
border-radius: 5rpx;
}
@ -854,7 +854,7 @@ export default {
.limit-text {
font-size: 24rpx;
color: #E8101E;
color: #FF4767;
}
/* 区域选择器样式 */
@ -942,7 +942,7 @@ export default {
}
.region-tab.active {
color: #E8101E;
color: #FF4767;
font-weight: 500;
}
@ -954,7 +954,7 @@ export default {
transform: translateX(-50%);
width: 40rpx;
height: 4rpx;
background-color: #E8101E;
background-color: #FF4767;
border-radius: 2rpx;
}
@ -994,8 +994,8 @@ export default {
}
.checkbox.checked {
background-color: #E8101E;
border-color: #E8101E;
background-color: #FF4767;
border-color: #FF4767;
}
.check-icon {
@ -1035,7 +1035,7 @@ export default {
}
.popup-btn.confirm {
background: linear-gradient(180deg, #f52540 0%, #e8101e 100%);
background: linear-gradient(180deg, #ff8e9d 0%, #FF4767 100%);
}
.popup-btn .btn-text {
@ -1075,11 +1075,11 @@ export default {
}
.change-info-btn {
background: linear-gradient(180deg, #f52540 0%, #e8101e 100%);
background: linear-gradient(180deg, #ff8e9d 0%, #FF4767 100%);
}
.view-change-btn {
background: linear-gradient(180deg, #f52540 0%, #e8101e 100%);
background: linear-gradient(180deg, #ff8e9d 0%, #FF4767 100%);
}
/* 双按钮行样式 */
@ -1100,7 +1100,7 @@ export default {
}
.submit-btn {
background: linear-gradient(180deg, #F52540 0%, #E8101E 100%, #E8101E 100%);
background: linear-gradient(180deg, #ff8e9d 0%, #FF4767 100%);
border-radius: 43rpx;
padding: 0 15rpx;
}

View File

@ -270,14 +270,50 @@ export default {
});
},
viewEdit(item) {
uni.navigateTo({
url: `/pages/shop/add-service?id=${item.id}&state=${item.state}&first_class_id=${item.first_class_id}`,
});
let url = `/pages/shop/add-service?id=${item.id}&state=${item.state}`;
if (item.first_class_id) {
url += `&first_class_id=${item.first_class_id}`;
}
if (item.second_class_id) {
url += `&second_class_id=${item.second_class_id}`;
}
uni.navigateTo({ url });
},
addService() {
const userId = uni.getStorageSync('userId') || '';
request.post("/sj/firstclass", { id: userId }).then((res) => {
async getCurrentSjId() {
const sjInfo = this.$store && this.$store.state && this.$store.state.sjInfo;
if (sjInfo && sjInfo.id) {
return sjInfo.id;
}
const storageSjId = uni.getStorageSync("sjId");
if (storageSjId) {
return storageSjId;
}
const res = await request.post("/sj/user/getUser");
if (res && res.data && res.data.id) {
if (this.$store && this.$store.commit) {
this.$store.commit("setSjInfo", res.data);
}
uni.setStorageSync("sjId", res.data.id);
return res.data.id;
}
return "";
},
async addService() {
try {
const sjId = await this.getCurrentSjId();
if (!sjId) {
uni.showToast({
title: "未获取到商家信息",
icon: "none",
});
return;
}
const res = await request.post("/sj/firstclass", { id: sjId });
if (res.data && res.data.length > 0) {
this.firstClassList = res.data;
this.selectedFirstIndex = 0;
@ -291,7 +327,13 @@ export default {
icon: "none",
});
}
});
} catch (error) {
console.error("获取商家服务分类失败:", error);
uni.showToast({
title: "获取分类失败",
icon: "none",
});
}
},
loadSecondClass(firstId) {
request.post("/user/secondclass", {
@ -979,4 +1021,4 @@ export default {
color: #ffffff;
}
}
</style>
</style>

View File

@ -25,7 +25,7 @@
<view class="checkbox" :class="{ checked: isCurrentSkillSelected(skill.id) }">
<image v-if="isCurrentSkillSelected(skill.id)"
class="check-icon"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/66325904-4603-48fd-b03b-90c684af96e9"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/6948baf3-aad9-4d87-9483-9db0e462a3f7.png"
mode="aspectFit">
</image>
</view>
@ -45,7 +45,7 @@
<view class="checkbox" :class="{ checked: skill.checked }">
<image v-if="skill.checked"
class="check-icon"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/66325904-4603-48fd-b03b-90c684af96e9"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/6948baf3-aad9-4d87-9483-9db0e462a3f7.png"
mode="aspectFit">
</image>
</view>
@ -721,7 +721,7 @@ export default {
.title-bar {
width: 6rpx;
height: 30rpx;
background-color: #E8101E;
background-color: #FF4767;
margin-right: 10rpx;
border-radius: 5rpx;
}
@ -790,8 +790,8 @@ export default {
}
.checkbox.checked {
background-color: #E8101E;
border-color: #E8101E;
background-color: #FF4767;
border-color: #FF4767;
}
.check-icon {
@ -827,12 +827,12 @@ export default {
.change-info-btn {
margin-top: 20rpx;
background: linear-gradient(180deg, #f52540 0%, #e8101e 100%);
background: linear-gradient(180deg, #ff8e9d 0%, #FF4767 100%);
}
.view-change-btn {
margin-top: 20rpx;
background: linear-gradient(180deg, #f52540 0%, #e8101e 100%);
background: linear-gradient(180deg, #ff8e9d 0%, #FF4767 100%);
}
/* 双按钮行样式 */
@ -853,7 +853,7 @@ export default {
}
.submit-btn {
background: linear-gradient(180deg, #F52540 0%, #E8101E 100%, #E8101E 100%);
background: linear-gradient(180deg, #ff8e9d 0%, #FF4767 100%);
border-radius: 43rpx;
padding: 0 15rpx;
}

View File

@ -371,7 +371,7 @@ export default {
.title-bar {
width: 6rpx;
height: 30rpx;
background-color: #E8101E;
background-color: #FF4767;
margin-right: 10rpx;
border-radius: 5rpx;
}
@ -410,7 +410,7 @@ export default {
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 28rpx;
color: #E8101E;
color: #FF4767;
line-height: 40rpx;
text-align: left;
font-style: normal;
@ -476,7 +476,7 @@ export default {
.confirm-text {
font-size: 32rpx;
color: #E8101E;
color: #FF4767;
font-weight: 500;
}
@ -526,7 +526,7 @@ export default {
}
.time-block.active .time-value-picker {
color: #E8101E;
color: #FF4767;
}
.time-separator {
@ -572,7 +572,7 @@ export default {
border: none;
width: 290rpx;
height: 86rpx;
background: linear-gradient( 180deg, #F52540 0%, #E8101E 100%, #E8101E 100%);
background: linear-gradient( 180deg, #F52540 0%, #FF4767 100%, #FF4767 100%);
border-radius: 43rpx;
}
@ -592,7 +592,7 @@ export default {
}
.btn-confirm-large {
background: #E8101E;
background: #FF4767;
color: white;
}
</style>

View File

@ -100,13 +100,13 @@
<view class="radio-group">
<view class="radio-item" @click="editable && setIdCardValidType(1)">
<view class="radio" :class="{ checked: displayData.idcard_expiry_type === 1 }">
<image v-if="displayData.idcard_expiry_type === 1" src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/cb185ec8-9d30-4e53-b5dc-14b546013a60" class="radio-icon-image" mode="aspectFit"></image>
<image v-if="displayData.idcard_expiry_type === 1" src="/static/images/agree_y.png" class="radio-icon-image" mode="aspectFit"></image>
</view>
<text class="radio-label">长久有效</text>
</view>
<view class="radio-item" @click="editable && setIdCardValidType(2)">
<view class="radio" :class="{ checked: displayData.idcard_expiry_type === 2 }">
<image v-if="displayData.idcard_expiry_type === 2" src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/cb185ec8-9d30-4e53-b5dc-14b546013a60" class="radio-icon-image" mode="aspectFit"></image>
<image v-if="displayData.idcard_expiry_type === 2" src="/static/images/agree_y.png" class="radio-icon-image" mode="aspectFit"></image>
</view>
<text class="radio-label">指定日期有效</text>
</view>
@ -1813,7 +1813,7 @@ export default {
}
.change-info-btn, .view-change-btn, .resubmit-btn {
background: linear-gradient(180deg, #f52540 0%, #e8101e 100%);
background: linear-gradient(180deg, #ff8e9d 0%, #FF4767 100%);
}
/* 双按钮行样式 */
@ -1834,7 +1834,7 @@ export default {
}
.submit-btn {
background: linear-gradient(180deg, #F52540 0%, #E8101E 100%, #E8101E 100%);
background: linear-gradient(180deg, #ff8e9d 0%, #FF4767 100%);
border-radius: 43rpx;
padding: 0 15rpx;
}
@ -1866,7 +1866,7 @@ export default {
.title-bar {
width: 6rpx;
height: 30rpx;
background-color: #E8101E;
background-color: #FF4767;
margin-right: 7rpx;
border-radius: 5rpx;
}
@ -2029,7 +2029,7 @@ export default {
width: 60rpx;
height: 60rpx;
border: 4rpx solid #fff;
border-top: 4rpx solid #E8101E;
border-top: 4rpx solid #FF4767;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 16rpx;
@ -2115,8 +2115,8 @@ export default {
}
.radio.checked {
background-color: #E8101E;
border-color: #E8101E;
background-color: #FF4767;
border-color: #FF4767;
}
.radio-icon-image {
@ -2291,7 +2291,7 @@ export default {
.confirm-text {
font-size: 32rpx;
color: #E8101E;
color: #FF4767;
font-weight: 500;
}
@ -2321,7 +2321,7 @@ export default {
}
.popup-btn.confirm {
background: linear-gradient(180deg, #f52540 0%, #e8101e 100%);
background: linear-gradient(180deg, #ff8e9d 0%, #FF4767 100%);
}
.popup-btn .btn-text {
@ -2431,13 +2431,13 @@ export default {
}
.tip-btn.cancel {
border: #E8101E 2rpx solid;
border: #FF4767 2rpx solid;
background-color: transparent;
color: #e8101e;
color: #FF4767;
}
.tip-btn.confirm {
background: linear-gradient(180deg, #f52540 0%, #e8101e 100%);
background: linear-gradient(180deg, #ff8e9d 0%, #FF4767 100%);
color: #FFFFFF;
}

View File

@ -326,7 +326,7 @@ export default {
.add-icon {
font-size: 32rpx;
color: #E8101E;
color: #FF4767;
margin-right: 8rpx;
}
@ -367,7 +367,7 @@ export default {
}
.confirm-btn {
color: #E8101E;
color: #FF4767;
}
.title {

View File

@ -934,7 +934,7 @@
.get-code-btn {
font-weight: 400;
font-size: 30rpx;
color: #E8101E;
color: #FF4767;
line-height: 42rpx;
text-align: center;
font-style: normal;
@ -959,7 +959,7 @@
.input-error {
display: block;
font-size: 24rpx;
color: #e8101e;
color: #FF4767;
margin-left: 30rpx;
margin-top: -20rpx;
margin-bottom: 20rpx;

View File

@ -132,7 +132,7 @@ import request from '../../utils/request';
.state {
height: 60rpx;
line-height: 60rpx;
background-color: #E8101E;
background-color: #FF4767;
padding: 0 24rpx;
}
.state-text {

View File

@ -132,7 +132,7 @@ export default {
color: #91908F;
}
.btn-box {
background: linear-gradient( 180deg, #F52540 0%, #E8101E 100%);
background: linear-gradient( 180deg, #F52540 0%, #FF4767 100%);
height: 88rpx;
border-radius: 43rpx;
font-weight: 500;

View File

@ -285,13 +285,14 @@ export default {
sourceType: ['album', 'camera'],
compressed: false
})
// #ifdef H5
const file = res.tempFile;
// #endif
// #ifdef APP-PLUS || MP-WEIXIN
const file = res;
// #endif
await this.uploadFile(file)
let selectedFile;
// #ifdef H5
selectedFile = res.tempFile;
// #endif
// #ifdef APP-PLUS || MP-WEIXIN
selectedFile = res;
// #endif
await this.uploadFile(selectedFile)
}
} catch (error) {
console.error('上传出错:', error)
@ -369,8 +370,9 @@ export default {
// OSS
uploadToOss(file, ossConfig, name) {
return new Promise((resolve, reject) => {
// #ifdef H5
const formData = new FormData()
let formData;
// #ifdef H5
formData = new FormData()
formData.append('name',name);
formData.append('policy', ossConfig.policy);
formData.append('OSSAccessKeyId', ossConfig.ossAccessKeyId);
@ -386,15 +388,15 @@ export default {
})
// #endif
// #ifdef APP-PLUS || MP-WEIXIN
let formData = {};
formData.name = name;
formData.policy = ossConfig.policy;
formData.OSSAccessKeyId = ossConfig.ossAccessKeyId;
formData.success_action_status = '200';
formData.signature = ossConfig.signature;
formData.key = ossConfig.dir + name;
formData.file = file;
// #ifdef APP-PLUS || MP-WEIXIN
formData = {};
formData.name = name;
formData.policy = ossConfig.policy;
formData.OSSAccessKeyId = ossConfig.ossAccessKeyId;
formData.success_action_status = '200';
formData.signature = ossConfig.signature;
formData.key = ossConfig.dir + name;
formData.file = file;
let path;
if(this.accept == 'image/*') {
path = file.path
@ -540,4 +542,4 @@ export default {
font-size: 32rpx;
z-index: 1;
}
</style>
</style>

View File

@ -226,7 +226,7 @@
.score {
font-weight: 400;
font-size: 26rpx;
color: #E8101E;
color: #FF4767;
line-height: 37rpx;
}

View File

@ -251,7 +251,7 @@
.score {
font-weight: 400;
font-size: 26rpx;
color: #E8101E;
color: #FF4767;
line-height: 37rpx;
text-align: left;
font-style: normal;

View File

@ -279,7 +279,7 @@
.score {
font-weight: 400;
font-size: 26rpx;
color: #E8101E;
color: #FF4767;
line-height: 37rpx;
text-align: left;
font-style: normal;

View File

@ -177,7 +177,7 @@
}
.active {
color: #E8101E;
color: #FF4767;
}
}

View File

@ -0,0 +1,239 @@
<template>
<view class="brand-card-comp" @click="onClick">
<image class="brand-bg" :src="item.bgSrc" mode="aspectFill"></image>
<image class="mask-bg"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/9bca353c-fa74-407e-a69e-6f5e9aa43955.png"
mode="widthFix"></image>
<view class="card-content">
<view class="avatar-wrapper" style="border-color: #F7E5D7;">
<image class="avatar-img" :src="item.avatar || 'https://dummyimage.com/100x100/ccc/fff.png&text=logo'">
</image>
</view>
<text class="brand-name">{{ item.name }}</text>
<view class="tags-container">
<view class="tag-item" v-for="(tag, idx) in displayTags" :key="idx" :style="getTagStyle(tag)">
<!-- <image class="tag-icon" v-if="tag.icon" :src="tag.icon"></image> -->
<text class="tag-text">{{ tag.text }} </text>
<text class="tag-text1"></text>
</view>
</view>
</view>
</view>
</template>
<script>
//
const TAG_LIBRARY = {
franchise: { //
text: '品牌加盟',
color: '#8D4C1B ',
borderColor: '#D6CAC0 ',
icon: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/60816ae2-92ee-4473-9eca-562e1200dbca.png'
},
recruit: { //
text: '学员招募',
color: '#E5505F',
borderColor: '#EFCFD2 ',
icon: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/fd0699b9-9f3c-4b1e-8693-d0c9ec7eabc3.png'
},
more: { //
text: '更多详情',
color: '#333333',
borderColor: '#ECECEC',
icon: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/cec453ce-f3fc-4881-8f2c-9ec5c61e759c.png'
}
}
export default {
name: 'brand-card',
props: {
//
item: {
type: Object,
default: () => ({})
},
// key ['franchise', 'recruit']
tagKeys: {
type: Array,
default: null
}
},
computed: {
// 使 tagKeys item.tags
displayTags() {
if (this.tagKeys && this.tagKeys.length) {
return this.tagKeys.map(key => TAG_LIBRARY[key]).filter(v => v)
}
// item.tags 使
if (this.item.tags && Array.isArray(this.item.tags)) {
return this.item.tags
}
return []
},
tagsLength() {
return this.displayTags.length
}
},
methods: {
//
onClick() {
// 1. emit便
this.$emit('click', this.item);
// 2.
if (!uni.getStorageSync("accessToken")) {
uni.navigateTo({
url: '/pages/blogPopup/blogPopup'
});
return;
}
// 3.
if (this.item && this.item.id) {
uni.navigateTo({
url: `/pages/jingxuan/brand-detail?id=${this.item.id}`
});
}
},
getTagStyle(tag) {
const len = this.tagsLength
let sizeStyle = {}
if (len === 1) {
sizeStyle = {
width: '288rpx',
height: '42rpx',
padding: '0 12rpx',
boxSizing: 'border-box'
}
} else if (len === 2) {
sizeStyle = {
width: '142rpx',
height: '42rpx',
padding: '0 12rpx',
boxSizing: 'border-box'
}
}
return {
...sizeStyle,
color: tag.color,
borderColor: tag.borderColor
}
}
}
}
</script>
<style lang="scss">
.brand-card-comp {
width: 328rpx;
height: 339rpx;
position: relative;
border-radius: 21rpx;
overflow: hidden;
background-color: #fff;
}
.brand-bg {
width: 100%;
height: 184rpx;
position: absolute;
top: 0;
left: 0;
z-index: 1;
}
.mask-bg {
width: 100%;
height: 251rpx;
position: absolute;
bottom: 0;
left: 0;
z-index: 2;
display: block;
}
.card-content {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
z-index: 3;
display: flex;
flex-direction: column;
align-items: center;
padding-bottom: 24rpx;
}
.avatar-wrapper {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
border: 2rpx solid;
background-color: #fff;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
margin-top: -38rpx;
margin-bottom: 12rpx;
}
.avatar-img {
width: 100%;
height: 100%;
}
.brand-name {
font-size: 28rpx;
font-weight: 500;
color: #333333;
line-height: 34rpx;
margin-bottom: 12rpx;
}
.tags-container {
display: flex;
justify-content: center;
align-items: center;
gap: 20rpx;
flex-wrap: wrap;
}
.tag-item {
display: flex;
align-items: center;
justify-content: center;
border-radius: 21rpx;
border: 1rpx solid;
background-color: transparent;
padding: 7rpx 12rpx;
}
.tag-icon {
width: 26rpx;
height: 24rpx;
margin-right: 6rpx;
flex-shrink: 0;
}
.tag-text {
font-size: 24rpx;
line-height: 28rpx;
white-space: nowrap;
margin-top: 4rpx;
padding-right: 2rpx;
}
.tag-text1 {
font-size: 40rpx;
line-height: 28rpx;
white-space: nowrap;
margin-top: 4rpx;
padding-right: 2rpx;
margin-bottom: 6rpx;
font-weight: 200;
}
</style>

View File

@ -156,6 +156,7 @@
weChatRefund: "/sj/workSeatOrder/weChatRefund",
aliRefund: "/sj/workSeatOrder/aliRefund",
startOrder: "/sj/workSeatOrder/start",
sureOrder:"/sj/workSeatOrder/sure",
endOrder: "/sj/workSeatOrder/end",
ordernum: "/sj/workSeatOrder/orderNum",
list: "/sj/workSeatOrder/list",
@ -209,7 +210,7 @@
secondTabs() {
if (this.activeFirstId == 1 || this.activeFirstId == 2) {
return [{
title: `服务(${this.numInfo.noservernum})`,
title: `开始(${this.numInfo.noservernum})`,
id: 3
},
{
@ -405,6 +406,7 @@
return;
}
this.clickType = true;
console.log(111,this.activeFirstId,this.urls)
request
.post(this.urls[this.activeFirstId].sureOrder, {
id: order.id,
@ -414,7 +416,7 @@
.then((res) => {
if (res.state != 1 || res.code != 200) {
uni.showToast({
title: res.msg,
title: "开始服务",
icon: "none",
});
} else {
@ -459,7 +461,7 @@
console.log(res);
if (res.state == 1 || res.code == 200) {
uni.showToast({
title: res.msg,
title: "开始服务",
icon: "none",
});
this.searchList();
@ -513,27 +515,27 @@
if (this.idType == 1) {
if (this.activeFirstId == 1) {
uni.navigateTo({
url: `/pages/shop/userorder-detail?order_id=${order.id}&order_kind=${order.order_kind}`,
url: `/pages/shop/userorder-detail?order_id=${order.id}&order_kind=${order.order_kind}&haveButton=${this.haveButton}`,
});
}
if (this.activeFirstId == 2) {
uni.navigateTo({
url: `/pages/shop/userorder-detail?order_id=${order.id}&serviceType=2`,
url: `/pages/shop/userorder-detail?order_id=${order.id}&serviceType=2&haveButton=${this.haveButton}`,
});
}
if (this.activeFirstId == 3) {
uni.navigateTo({
url: `/pages/shop/SellerDetail?order_id=${order.id}`,
url: `/pages/shop/SellerDetail?order_id=${order.id}&haveButton=${this.haveButton}`,
});
}
if (this.activeFirstId == 4) {
uni.navigateTo({
url: `/pages/shop/userorder-detail?order_id=${order.id}&order_kind=${order.order_kind}`,
url: `/pages/shop/userorder-detail?order_id=${order.id}&order_kind=${order.order_kind}&haveButton=${this.haveButton}`,
});
}
if (this.activeFirstId == 5) {
uni.navigateTo({
url: `/pages/shop/userorder-detail?order_id=${order.id}&order_kind=${order.order_kind}&serviceType=2`,
url: `/pages/shop/userorder-detail?order_id=${order.id}&order_kind=${order.order_kind}&serviceType=2&haveButton=${this.haveButton}`,
});
}
}
@ -576,7 +578,7 @@
//
&.active {
color: #e8101e; //
color: #FF4767; //
font-weight: 600; //
//
@ -590,8 +592,8 @@
bottom: -6rpx; //
height: 6rpx; //
background: linear-gradient(306deg,
#e8101e 0%,
#e8101e 100%); //
#FF4767 0%,
#FF4767 100%); //
border-radius: 3rpx; //
}
}
@ -626,7 +628,7 @@
//
&.active {
color: #e8101e; //
color: #FF4767; //
background: rgba(252, 67, 124, 0.1); //
font-weight: 600; //
}

View File

@ -0,0 +1,101 @@
<template>
<view class="notice-item" @tap="handleClick">
<view class="item-main">
<text class="item-title line-2">{{ item.title }}</text>
<view class="item-time">
<text class="time-text">发布时间{{ formattedTime }}</text>
</view>
</view>
</view>
</template>
<script>
export default {
name: "NoticeCard",
props: {
//
item: {
type: Object,
default: () => ({})
}
},
computed: {
//
formattedTime() {
const timeStr = this.item.create_time;
if (!timeStr) return '--:--:--';
let formatted = timeStr;
if (timeStr.includes('.') && !timeStr.includes('-')) {
formatted = timeStr.replace(/\./g, '-');
if (formatted.match(/\d{4}-\d{2}-\d{2}\d{2}:\d{2}:\d{2}/)) {
formatted = formatted.replace(/(\d{4}-\d{2}-\d{2})(\d{2}:\d{2}:\d{2})/, '$1 $2');
}
}
if (formatted.includes('-') && formatted.includes(':')) {
return formatted;
}
return timeStr;
}
},
methods: {
handleClick() {
// click
this.$emit('click', this.item);
}
}
};
</script>
<style lang="scss" scoped>
.notice-item {
display: flex;
align-items: center;
justify-content: space-between;
background: #ffffff;
border-radius: 20rpx;
padding: 30rpx;
margin-bottom: 20rpx;
transition: all 0.2s ease;
.item-main {
flex: 1;
min-width: 0; //
.item-title {
font-size: 30rpx;
font-weight: 500;
color: #101010;
line-height: 42rpx;
margin-bottom: 16rpx;
}
.item-time {
display: flex;
align-items: center;
.time-icon {
width: 24rpx;
height: 24rpx;
margin-right: 8rpx;
opacity: 0.6;
}
.time-text {
font-size: 22rpx;
color: #999;
line-height: 30rpx;
}
}
}
}
//
.line-2 {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
word-break: break-all;
}
</style>

View File

@ -171,6 +171,7 @@
},
emitStart() {
this.$emit("start");
},
emitDone() {
this.$emit("done");
@ -181,8 +182,8 @@
<style scoped lang="less">
.order-card {
padding: 20rpx 30rpx 20rpx 30rpx;
margin: 0rpx 30rpx 20rpx 30rpx;
padding: 24rpx 20rpx 24rpx 20rpx;
margin: 0;
background-color: #fff;
border-radius: 12px;
/* box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); */
@ -205,7 +206,7 @@
.stateText {
font-weight: 400;
font-size: 26rpx;
color: #E8101E;
color: #FF4767;
text-align: left;
font-style: normal;
position: absolute;
@ -255,7 +256,7 @@
.service-price {
font-weight: 500;
font-size: 24rpx;
color: #E8101E;
color: #FF4767;
line-height: 34rpx;
text-align: left;
font-style: normal;
@ -292,7 +293,7 @@
letter-spacing: 2px;
text-align: left;
font-style: normal;
background: linear-gradient(166deg, #FC5E72 0%, #E8101E 100%);
background: linear-gradient(166deg, #FC5E72 0%, #FF4767 100%);
border-radius: 6rpx;
padding: 4rpx 4rpx 4rpx 8rpx;
margin-right: 13rpx;
@ -388,8 +389,8 @@
/* 接单按钮 */
.accept-btn {
border: 2rpx solid #E8101E;
color: #E8101E;
border: 2rpx solid #FF4767;
color: #FF4767;
}
.order-address {

View File

@ -129,7 +129,7 @@
.tab-text {
font-weight: 500;
color: #E8101E;
color: #FF4767;
&::after{
content: '';
//
@ -139,7 +139,7 @@
transform: translateX(-50%);
bottom: -6rpx; //
height: 6rpx; //
background: linear-gradient( 306deg, #E8101E 0%, #F25988 100%); //
background: linear-gradient( 306deg, #FF4767 0%, #F25988 100%); //
border-radius: 3rpx; //
}
}

View File

@ -0,0 +1,196 @@
<template>
<view class="skill-item" @click="handleClick">
<view class="cover-box">
<image class="cover" :src="item.coverSrc" mode="aspectFill"></image>
<image v-if="item.video" class="play-icon"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/5c1ddfef-a200-424a-a9dc-03d9395568f6.png"></image>
</view>
<view class="skill-info">
<view class="skill-title line-2">{{ item.title }}</view>
<view class="skill-type" :class="typeClass">
<image class="type-icon" :src="item.typeIcon || defaultTypeIcon"></image>
<text class="type-text">{{ item.type || '平台课程' }}</text>
</view>
<view class="skill-stats">
<image class="view-icon"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/2409b64a-56bb-4d9e-a4cc-5b3444fcf097.png">
</image>
{{ formatViews(item.views) }}观看 · {{ item.time }}
</view>
</view>
</view>
</template>
<script>
export default {
name: 'SkillItem',
props: {
item: {
type: Object,
required: true,
default: () => ({
id: null,
title: '',
coverSrc: '',
type: '平台课程',
views: 0,
time: '',
typeIcon: ''
})
}
},
data() {
return {
defaultTypeIcon: 'https://dummyimage.com/30x30/ccc/fff.png&text=I' //
}
},
computed: {
typeClass() {
const typeMap = {
'平台课程': 'platform-course',
'脸缘品牌': 'brand-course',
'品牌课程': 'brand-course'
}
return typeMap[this.item.type] || 'platform-course'
}
},
methods: {
formatViews(views) {
if (!views) return '0'
if (views >= 10000) {
return (views / 10000).toFixed(1) + 'w'
}
return views.toString()
},
handleClick() {
// 1. emit便
this.$emit('click', this.item);
// 2.
if (!uni.getStorageSync("accessToken")) {
uni.navigateTo({
url: '/pages/blogPopup/blogPopup'
});
return;
}
// 3.
if (this.item && this.item.id) {
uni.navigateTo({
url: `/pages/jingxuan/skill-detail?id=${this.item.id}`
});
}
}
}
}
</script>
<style lang="scss" scoped>
.skill-item {
display: flex;
margin-bottom: 30rpx;
&:last-child {
margin-bottom: 0;
}
.cover-box {
position: relative;
width: 224rpx;
height: 126rpx;
border-radius: 16rpx;
overflow: hidden;
margin-right: 24rpx;
flex-shrink: 0;
.cover {
width: 100%;
height: 100%;
}
.play-icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 39rpx;
height: 39rpx;
}
}
.skill-info {
flex: 1;
display: flex;
min-width: 0;
flex-direction: column;
justify-content: space-between;
.skill-title {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
text-overflow: ellipsis;
overflow: hidden;
font-size: 28rpx;
color: #333;
font-weight: 500;
line-height: 40rpx;
}
.skill-type {
display: flex;
align-items: center;
font-size: 22rpx;
margin-top: 10rpx;
.type-icon {
border: 1rpx solid #D6CAC0;
width: 26rpx;
height: 26rpx;
margin-right: 8rpx;
border-radius: 50%;
}
.type-text {
border-radius: 7rpx;
font-weight: 400;
font-size: 24rpx;
line-height: 33rpx;
padding: 2rpx 10rpx;
}
&.platform-course .type-text {
background: #f9d3d760;
color: #E55463;
}
&.brand-course .type-text {
background: #f9d3d760;
color: #E55463;
}
}
.skill-stats {
font-size: 22rpx;
color: #999;
margin-top: 10rpx;
display: flex;
align-items: center;
.view-icon {
width: 26rpx;
height: 22rpx;
margin-right: 8rpx;
margin-top: -2rpx;
}
}
}
}
.line-2 {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
</style>

View File

@ -6,7 +6,7 @@
<view class="stateText" :class="{
stateText2: dataItem.state > 4 && dataItem.state !== 7, //
stateText3: dataItem.state === 7 //
}" v-if="showState">
}" v-if="showState">
{{ stateText }}
</view>
</view>
@ -18,8 +18,8 @@
<view class="topInfo">
<view class="price_title">
<view class="service-type white-space-nowrap">{{
dataItem.title
}}</view>
dataItem.title
}}</view>
</view>
<view class="service-address">下单时间{{ dataItem.add_time }}</view>
@ -45,299 +45,260 @@
</template>
<script>
export default {
name: "OrderCard",
props: {
dataItem: {
type: Object,
},
typeId: {
type: Number,
},
showState: {
type: Boolean,
default: true,
}
export default {
name: "OrderCard",
props: {
dataItem: {
type: Object,
},
computed: {
photo() {
return this.dataItem.photo? JSON.parse(this.dataItem.photo):"";
},
useTime() {
const start = this.dataItem.reserve_start_time;
const endTime = this.dataItem.reserve_end_time;
function formatTimeRange(startTimeStr, endTimeStr) {
if (!startTimeStr || !endTimeStr) return "未设置";
// Date
const startTime = new Date(startTimeStr.replace(/-/g, '/'));
const endTime = new Date(endTimeStr.replace(/-/g, '/'));
if (isNaN(startTime.getTime()) || isNaN(endTime.getTime())) {
console.error("无效的时间格式");
return "时间格式错误";
}
// YYYY
const formatYear = (date) => {
return date.getFullYear() + '年';
};
// MMDD
const formatMonthDay = (date) => {
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${month}${day}`;
};
// HH:MM
const formatTimePart = (date) => {
const hours = String(date.getHours()).padStart(2, "0");
const minutes = String(date.getMinutes()).padStart(2, "0");
return `${hours}:${minutes}`;
};
//
const isSameDay = startTime.getFullYear() === endTime.getFullYear() &&
startTime.getMonth() === endTime.getMonth() &&
startTime.getDate() === endTime.getDate();
if (isSameDay) {
// 20250529 12:30-13:30
return `${formatYear(startTime)}${formatMonthDay(startTime)} ${formatTimePart(startTime)}-${formatTimePart(endTime)}`;
} else {
// 20250529 12:30-20250530 13:30
return `${formatYear(startTime)}${formatMonthDay(startTime)} ${formatTimePart(startTime)}-${formatYear(endTime)}${formatMonthDay(endTime)} ${formatTimePart(endTime)}`;
}
}
return formatTimeRange(start, endTime);
},
stateText() {
const textList = [
"",
"待支付",
"待接单",
"待开始",
"进行中",
"已完成",
"已评价",
"已取消",
];
return textList[this.dataItem.state];
},
typeId: {
type: Number,
},
methods: {
//
emitCancel() {
this.$emit("cancel", this.dataItem);
},
//
emitAccept() {
this.$emit("accept", this.dataItem);
},
emitStart() {
this.$emit("start", this.dataItem);
},
emitDone() {
this.$emit("done", this.dataItem);
},
goDetail() {
this.$emit("goDetail", this.dataItem);
},
emitGoPay() {
this.$emit("goPay", this.dataItem);
},
emitRefund() {
this.$emit("goRefund", this.dataItem);
}
showState: {
type: Boolean,
default: true,
}
},
computed: {
photo() {
return this.dataItem.photo ? JSON.parse(this.dataItem.photo) : "";
},
};
useTime() {
const start = this.dataItem.reserve_start_time;
const end = this.dataItem.reserve_end_time;
if (!start || !end) return "未设置";
const startDate = new Date(start.replace(/-/g, '/'));
const endDate = new Date(end.replace(/-/g, '/'));
if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) return "时间格式错误";
const pad = (n) => String(n).padStart(2, "0");
const fmt = (d) => `${pad(d.getMonth() + 1)}.${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
return `${fmt(startDate)}-${fmt(endDate)}`;
},
stateText() {
const textList = [
"",
"待支付",
"待接单",
"待开始",
"进行中",
"已完成",
"已评价",
"已取消",
];
return textList[this.dataItem.state];
},
},
methods: {
//
emitCancel() {
this.$emit("cancel", this.dataItem);
},
//
emitAccept() {
this.$emit("accept", this.dataItem);
},
emitStart() {
this.$emit("start", this.dataItem);
},
emitDone() {
this.$emit("done", this.dataItem);
},
goDetail() {
this.$emit("goDetail", this.dataItem);
},
emitGoPay() {
this.$emit("goPay", this.dataItem);
},
emitRefund() {
this.$emit("goRefund", this.dataItem);
}
},
};
</script>
<style lang="less" scoped>
.order-card {
position: relative; //
padding: 24rpx 20rpx 0rpx;
margin: 0rpx 30rpx 20rpx 30rpx;
background-color: #fff;
border-radius: 12px;
/* box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); */
}
.order-card {
position: relative; //
padding: 24rpx 20rpx 0rpx;
margin: 0rpx 30rpx 20rpx 30rpx;
background-color: #fff;
border-radius: 12px;
/* box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); */
}
.orderNumBox {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 40rpx;
.orderNumBox {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 40rpx;
.stateText {
font-weight: 400;
font-size: 26rpx;
color: #E8101E;
text-align: left;
font-style: normal;
position: absolute;
right: 0;
top: 0;
background-image: url("/static/images/icons/tabBj.png");
background-size: 100% auto;
background-position: center top;
background-repeat: no-repeat;
width: 128rpx;
height: 49rpx;
display: flex;
align-items: center;
justify-content: center;
}
.stateText2 {
background-image: url("/static/images/icons/tabBj2.png");
color: #333;
}
.stateText3 {
background-image: url("/static/images/icons/tabBj2.png");
color: #999999 !important;
}
}
/* 订单编号 */
.order-id {
width: 530rpx;
font-size: 28rpx;
color: #333333;
}
/* 服务信息容器 */
.service-container {
display: flex;
align-items: center;
padding-bottom: 24rpx;
border-bottom: 1px solid #f6f5f5;
width: 100%;
}
/* 服务图片 */
.service-img {
width: 172rpx;
height: 168rpx;
border-radius: 24rpx;
margin-right: 30rpx;
flex-shrink: 0;
}
/* 服务信息 */
.service-info {
display: flex;
flex-direction: column;
justify-content: space-between;
width: 100%;
height: 168rpx;
.topInfo {
display: flex;
flex-direction: column;
.price_title {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20rpx;
.service-type {
font-weight: 500;
font-size: 30rpx;
color: #333333;
line-height: 34rpx;
max-width: 300rpx;
}
}
}
.service-price {
font-family: DINPro, DINPro;
font-weight: 500;
font-size: 36rpx;
color: #E8101E;
line-height: 34rpx;
text-align: left;
font-style: normal;
}
}
.service_subTitle {
font-size: 26rpx;
line-height: 26rpx;
color: #666666;
margin-top: 18rpx;
}
.service-address {
.stateText {
font-weight: 400;
font-size: 26rpx;
color: #999999;
line-height: 33rpx;
color: #FF4767;
text-align: left;
font-style: normal;
}
/* 时间信息 */
.time-info {
font-weight: 400;
font-size: 26rpx;
color: #333333;
line-height: 34rpx;
text-align: left;
font-style: normal;
padding-bottom: 20rpx;
margin-top: 28rpx;
}
/* 按钮组 */
.button-group {
display: flex;
justify-content: flex-end;
gap: 30rpx;
padding-bottom: 30rpx;
}
/* 通用按钮样式 */
.btn {
white-space: nowrap;
width: 100rpx;
height: 32rpx;
position: absolute;
right: 0;
top: 0;
background-image: url("/static/images/icons/tabBj.png");
background-size: 100% auto;
background-position: center top;
background-repeat: no-repeat;
width: 128rpx;
height: 49rpx;
display: flex;
align-items: center;
justify-content: center;
padding: 10px 20px;
border-radius: 64rpx;
// box-sizing: border-box;
//height: 64rpx;
//border-radius: 32rpx;
border: 2rpx solid #979797;
font-weight: 400;
font-size: 26rpx;
color: #333333;
line-height: 64rpx;
// padding: 0 28rpx;
}
.stateText2 {
background-image: url("/static/images/icons/tabBj2.png");
color: #333;
}
.stateText3 {
background-image: url("/static/images/icons/tabBj2.png");
color: #999999 !important;
}
}
/* 订单编号 */
.order-id {
width: 530rpx;
font-size: 28rpx;
color: #333333;
}
/* 服务信息容器 */
.service-container {
display: flex;
align-items: center;
padding-bottom: 24rpx;
border-bottom: 1px solid #f6f5f5;
width: 100%;
}
/* 服务图片 */
.service-img {
width: 172rpx;
height: 168rpx;
border-radius: 24rpx;
margin-right: 30rpx;
flex-shrink: 0;
}
/* 服务信息 */
.service-info {
display: flex;
flex-direction: column;
justify-content: space-between;
width: 100%;
height: 168rpx;
.topInfo {
display: flex;
flex-direction: column;
.price_title {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20rpx;
.service-type {
font-weight: 500;
font-size: 30rpx;
color: #333333;
line-height: 34rpx;
max-width: 300rpx;
}
}
}
/* 取消按钮 */
.cancel-btn {
border: 2rpx solid #979797;
color: #333333;
.service-price {
font-family: DINPro, DINPro;
font-weight: 500;
font-size: 36rpx;
color: #FF4767;
line-height: 34rpx;
text-align: left;
font-style: normal;
}
}
/* 接单按钮 */
.accept-btn {
border: 2rpx solid #E8101E;
color: #E8101E;
}
.service_subTitle {
font-size: 26rpx;
line-height: 26rpx;
color: #666666;
margin-top: 18rpx;
}
.service-address {
font-weight: 400;
font-size: 26rpx;
color: #999999;
line-height: 33rpx;
text-align: left;
font-style: normal;
}
/* 时间信息 */
.time-info {
font-weight: 400;
font-size: 26rpx;
color: #333333;
line-height: 34rpx;
text-align: left;
font-style: normal;
padding-bottom: 20rpx;
margin-top: 28rpx;
}
/* 按钮组 */
.button-group {
display: flex;
justify-content: flex-end;
gap: 30rpx;
padding-bottom: 30rpx;
}
/* 通用按钮样式 */
.btn {
white-space: nowrap;
width: 100rpx;
height: 32rpx;
display: flex;
align-items: center;
justify-content: center;
padding: 10px 20px;
border-radius: 64rpx;
// box-sizing: border-box;
//height: 64rpx;
//border-radius: 32rpx;
border: 2rpx solid #979797;
font-weight: 400;
font-size: 26rpx;
color: #333333;
line-height: 64rpx;
// padding: 0 28rpx;
}
/* 取消按钮 */
.cancel-btn {
border: 2rpx solid #979797;
color: #333333;
}
/* 接单按钮 */
.accept-btn {
border: 2rpx solid #FF4767;
color: #FF4767;
}
</style>

View File

@ -0,0 +1,167 @@
<template>
<view class="growth-package-result-page">
<custom-navbar
title="支付结果"
:showBack="true"
backgroundColor="#F7F7F7"
borderBottom="none"
></custom-navbar>
<view class="result-main">
<view class="result-icon" :class="isFail ? 'is-fail' : 'is-success'">
<block v-if="isFail">
<view class="fail-line"></view>
<view class="fail-dot"></view>
</block>
<view v-else class="success-check"></view>
</view>
<view class="result-title">{{ resultInfo.title }}</view>
<view class="result-desc">{{ resultInfo.desc }}</view>
<view class="primary-btn" @tap="handlePrimaryTap">{{ isFail ? "重新支付" : "返回首页" }}</view>
</view>
</view>
</template>
<script>
import CustomNavbar from "@/components/custom-navbar/custom-navbar.vue";
export default {
components: {
CustomNavbar,
},
data() {
return {
status: "success",
};
},
computed: {
isFail() {
return this.status === "fail";
},
resultInfo() {
if (this.isFail) {
return {
title: "报名失败",
desc: "最后一步出了点问题 请重新支付",
};
}
return {
title: "报名成功",
desc: "您已完成支付 感谢你对美融融的支持",
};
},
},
onLoad(options) {
this.status = options.status === "fail" ? "fail" : "success";
},
methods: {
handlePrimaryTap() {
if (this.isFail) {
uni.redirectTo({
url: "/pages/home/growth-package",
});
return;
}
uni.switchTab({
url: "/pages/home/home",
});
},
},
};
</script>
<style lang="scss" scoped>
page {
background: #ffffff;
}
.growth-package-result-page {
min-height: 100vh;
background: #ffffff;
padding-bottom: env(safe-area-inset-bottom);
}
.result-main {
display: flex;
flex-direction: column;
align-items: center;
padding: 246rpx 40rpx 0;
}
.result-icon {
width: 104rpx;
height: 104rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.result-icon.is-success {
background: #12c76b;
}
.result-icon.is-fail {
background: #ff4652;
}
.success-check {
width: 44rpx;
height: 24rpx;
border-left: 8rpx solid #ffffff;
border-bottom: 8rpx solid #ffffff;
transform: rotate(-45deg);
margin-top: -8rpx;
}
.fail-line {
width: 8rpx;
height: 46rpx;
border-radius: 8rpx;
background: #ffffff;
}
.fail-dot {
width: 8rpx;
height: 8rpx;
margin-left: -8rpx;
margin-top: 58rpx;
border-radius: 50%;
background: #ffffff;
}
.result-title {
margin-top: 34rpx;
font-weight: 600;
font-size: 40rpx;
color: #333333;
line-height: 56rpx;
text-align: center;
}
.result-desc {
margin-top: 8rpx;
font-weight: 400;
font-size: 26rpx;
color: #b9b9b9;
line-height: 38rpx;
text-align: center;
}
.primary-btn {
width: 350rpx;
height: 80rpx;
margin-top: 144rpx;
border: 1rpx solid #ff4b67;
border-radius: 40rpx;
box-sizing: border-box;
background: #ffffff;
display: flex;
align-items: center;
justify-content: center;
font-weight: 400;
font-size: 30rpx;
color: #ff4767;
line-height: 42rpx;
}
</style>

View File

@ -0,0 +1,348 @@
<template>
<view class="growth-package-page">
<image class="content-image" src="@/static/images/home/growth-package/intro.png" mode="widthFix"></image>
<view class="deal-carousel">
<swiper class="deal-swiper" autoplay circular :interval="3000" previous-margin="40rpx"
next-margin="40rpx" :current="dealCurrentIndex" @change="handleDealChange">
<swiper-item v-for="(item, index) in dealSlides" :key="item">
<view class="deal-slide" :class="getDealSlideClass(index)">
<image class="deal-slide-image" :src="item" mode="aspectFit"></image>
</view>
</swiper-item>
</swiper>
<view class="deal-indicator">
<view class="indicator-track" v-for="(item, index) in dealSlides" :key="item"
:class="{ active: index === dealCurrentIndex }"></view>
</view>
</view>
<image class="content-image" src="@/static/images/home/growth-package/plan.png" mode="widthFix"></image>
<view class="fixed-action" @tap="handlePayTap">
<image class="action-image" :src="actionImage" mode="widthFix"></image>
</view>
</view>
</template>
<script>
import request from "@/utils/request";
export default {
name: "GrowthPackage",
data() {
return {
dealCurrentIndex: 0,
isPaying: false,
isJoined: false,
dealSlides: [
"/static/images/home/growth-package/deal-1.png",
"/static/images/home/growth-package/deal-2.png",
"/static/images/home/growth-package/deal-3.png",
],
};
},
computed: {
actionImage() {
return this.isJoined
? "/static/images/home/growth-package/action-success.png"
: "/static/images/home/growth-package/action.png";
},
},
onShow() {
this.getGrowthPackageStatus();
},
methods: {
async getGrowthPackageStatus() {
if (!uni.getStorageSync("accessToken")) {
this.isJoined = false;
return;
}
if (this.isPaying) return;
uni.showLoading({
title: "查询报名状态...",
mask: true,
});
try {
const res = await request.post(
"/sj/growthPackage/status",
{},
{
hideLoading: true,
}
);
this.isJoined = !!(res && res.code === 200 && res.data && Number(res.data.is_joined) === 1);
} catch (error) {
console.log("growthPackageStatus error", error);
} finally {
uni.hideLoading();
}
},
handleDealChange(event) {
this.dealCurrentIndex = event.detail.current;
},
getDealSlideClass(index) {
const length = this.dealSlides.length;
const prevIndex = (this.dealCurrentIndex + length - 1) % length;
const nextIndex = (this.dealCurrentIndex + 1) % length;
if (index === this.dealCurrentIndex) {
return "is-active";
}
if (index === prevIndex) {
return "is-prev";
}
if (index === nextIndex) {
return "is-next";
}
return "";
},
handlePayTap() {
if (this.isPaying) return;
if (this.isJoined) return;
if (!uni.getStorageSync("accessToken")) {
uni.navigateTo({
url: "/pages/blogPopup/blogPopup",
});
return;
}
uni.showActionSheet({
itemList: ["微信支付", "支付宝支付"],
success: (res) => {
const payKind = res.tapIndex === 0 ? 2 : 3;
this.createGrowthPackagePay(payKind);
},
});
},
async createGrowthPackagePay(payKind) {
this.isPaying = true;
uni.showLoading({
title: "支付中...",
mask: true,
});
try {
const payRes = await request.post(
"/sj/growthPackage/pay",
{
pay_kind: payKind,
},
{
hideLoading: true,
}
);
if (!payRes || payRes.code !== 200) {
uni.hideLoading();
this.isPaying = false;
if (payRes && payRes.data && (payRes.data.id || payRes.data.number)) {
this.goToPayResult("fail", payRes.data);
return;
}
uni.showToast({
title: (payRes && payRes.msg) || "支付请求失败",
icon: "none",
});
return;
}
const orderInfo = payRes.data || {};
if (Number(orderInfo.is_joined) === 1) {
uni.hideLoading();
this.isPaying = false;
this.isJoined = true;
this.goToPayResult("success", orderInfo.order || {});
return;
}
const paymentParams = orderInfo.pay_params || orderInfo.orderInfo;
if (!paymentParams) {
uni.hideLoading();
this.isPaying = false;
if (orderInfo.id || orderInfo.number) {
this.goToPayResult("fail", orderInfo);
return;
}
uni.showToast({
title: "支付参数缺失",
icon: "none",
});
return;
}
uni.hideLoading();
this.requestGrowthPackagePayment(payKind, paymentParams, orderInfo);
} catch (error) {
uni.hideLoading();
uni.showToast({
title: "支付请求失败",
icon: "none",
});
this.isPaying = false;
console.log("growthPackagePay error", error);
}
},
requestGrowthPackagePayment(payKind, paymentParams, orderInfo) {
const provider = payKind === 2 ? "wxpay" : "alipay";
let payOrderInfo = paymentParams;
if (payKind === 2 && paymentParams && typeof paymentParams === "object") {
payOrderInfo = {
...paymentParams,
};
const systemInfo = uni.getSystemInfoSync();
if (systemInfo.platform === "ios" && payOrderInfo.appid) {
payOrderInfo.appId = payOrderInfo.appid;
delete payOrderInfo.appid;
}
}
uni.requestPayment({
provider,
orderInfo: payOrderInfo,
success: () => {
this.queryGrowthPackagePay(orderInfo);
},
fail: (err) => {
this.isPaying = false;
console.log("growthPackage requestPayment fail", err);
this.goToPayResult("fail", orderInfo);
},
});
},
async queryGrowthPackagePay(orderInfo) {
try {
const queryRes = await request.post(
"/sj/growthPackage/queryPay",
{
id: orderInfo.id,
number: orderInfo.number,
},
{
hideLoading: true,
}
);
const order = queryRes && queryRes.data && queryRes.data.order;
if (queryRes && queryRes.code === 200 && order && Number(order.state) === 2) {
this.isJoined = true;
this.goToPayResult("success", order);
return;
}
this.goToPayResult("fail", orderInfo);
} catch (error) {
console.log("growthPackageQueryPay error", error);
this.goToPayResult("fail", orderInfo);
} finally {
this.isPaying = false;
}
},
goToPayResult(status, orderInfo = {}) {
const params = [`status=${status}`];
if (orderInfo.number) {
params.push(`number=${encodeURIComponent(orderInfo.number)}`);
}
if (orderInfo.pay_money || orderInfo.order_money) {
params.push(`money=${encodeURIComponent(orderInfo.pay_money || orderInfo.order_money)}`);
}
uni.redirectTo({
url: `/pages/home/growth-package-result?${params.join("&")}`,
});
},
},
}
</script>
<style lang="scss">
page {
background: #150903;
}
.growth-package-page {
min-height: 100vh;
padding-bottom: 259rpx;
background: #150903;
.content-image {
display: block;
width: 750rpx;
}
.deal-carousel {
position: relative;
width: 750rpx;
height: 1342rpx;
background: #000000;
overflow: hidden;
}
.deal-swiper {
width: 750rpx;
height: 1280rpx;
background: #000000;
}
.deal-slide {
position: relative;
width: 670rpx;
height: 1280rpx;
background: #000000;
overflow: hidden;
}
.deal-slide-image {
display: block;
width: 670rpx;
height: 1280rpx;
}
.deal-slide.is-prev,
.deal-slide.is-next {
height: 1169rpx;
margin-top: 56rpx;
}
.deal-slide.is-prev .deal-slide-image,
.deal-slide.is-next .deal-slide-image {
position: absolute;
top: -56rpx;
}
.deal-slide.is-prev .deal-slide-image {
right: 10rpx;
border-top-right-radius: 20rpx;
border-bottom-right-radius: 20rpx;
}
.deal-slide.is-next .deal-slide-image {
left: 10rpx;
border-top-left-radius: 20rpx;
border-bottom-left-radius: 20rpx;
}
.deal-indicator {
display: flex;
align-items: center;
justify-content: center;
height: 62rpx;
column-gap: 8rpx;
background: #000000;
}
.indicator-track {
width: 146rpx;
height: 8rpx;
border-radius: 999rpx;
background: rgba(255, 255, 255, 0.42);
}
.indicator-track.active {
background: #ffffff;
}
.fixed-action {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 20;
width: 750rpx;
height: 259rpx;
background: #150903;
}
.action-image {
display: block;
width: 750rpx;
}
}
</style>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,544 @@
<template>
<view>
<custom-navbar title="精选品牌"
:leftImg="'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/60ae3383-6ff6-4f42-818b-d9b09a9bd0e0.png'"
:showUser="true"
backgroundColor="url('https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/ce2e62e1-c26a-45c8-a49d-7ed00b2ce02c.png') center 20%/cover no-repeat"
titleColor="#333" borderBottom="none" :show-headle="true"
headleSrc="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/51ebca5d-95c5-4fb8-8aa3-560a79100d25.png"
@onHeadleClick="goToSearch" class="nav-img-size">
</custom-navbar>
<serviecFirstTab :tabs="tabs" :activeId="activeId" activeColor="#FF4767" @tab-click="tabClick" bgColor="#fff">
</serviecFirstTab>
<CommonList ref="brandListRef" :apiUrl="'/sj/brand/list'" :initialQuery="{}"
:listScrollHeight="`calc(100vh - ${statusBarHeight * 2 + 250}rpx)`" @data-loaded="handleDataLoaded">
<template #headerScroll>
<view class="icon-tabs">
<view v-for="item in iconTabs" :key="item.id" class="icon-tab"
:class="{ active: currentSecondClass == item.id }" @click="selectSecondClass(item.id)">
<image :src="item.photo" class="icon-img" mode="aspectFill"></image>
<text class="icon-text">{{ item.title }}</text>
</view>
</view>
<view class="sort-bar">
<view class="sort-item" @tap="toggleSort('time')">
<text class="sort-text" :class="{ active: orderType === 1 || orderType === 2 }">发布时间</text>
<view class="arrows">
<image class="arrow-icon" :src="orderType === 2 ? iconPinkUp : iconGreyUp"></image>
<image class="arrow-icon" :src="orderType === 1 ? iconPinkDown : iconGreyDown"></image>
</view>
</view>
<view class="sort-item" @tap="toggleSort('view')">
<text class="sort-text" :class="{ active: orderType === 3 || orderType === 4 }">浏览量</text>
<view class="arrows">
<image class="arrow-icon" :src="orderType === 4 ? iconPinkUp : iconGreyUp"></image>
<image class="arrow-icon" :src="orderType === 3 ? iconPinkDown : iconGreyDown"></image>
</view>
</view>
</view>
</template>
<template #listData="{ list }">
<view class="zhanwei">
<view class="brand-grid">
<brand-card class="brand-card-wrap" v-for="(brand, idx) in enrichedBrandList(list)" :key="brand.id || idx"
:item="brand" :tagKeys="brand._tagKeys" @click="handleBrandClick(brand)" />
</view>
</view>
</template>
<template #empty>
<noData></noData>
</template>
</CommonList>
</view>
</template>
<script>
import request from "../../utils/request";
import serviecFirstTab from "@/components/common/service-first-tab.vue";
import CommonList from "@/components/common/CommonList.vue";
import brandCard from "@/pages/home/components/brand-card.vue";
import noData from "@/components/noData/noData.vue";
export default {
components: {
serviecFirstTab,
CommonList,
brandCard,
noData
},
data() {
return {
statusBarHeight: 0,
navHeight: 0,
// /ID
activeId: "",
currentSecondClass: "",
tabs: [],
iconTabs: [],
selectedSecondIds: [], // ID
//
orderType: 0,
//
iconGreyUp: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/e00ab2ac-bccf-42d8-820c-65727327e279.png',
iconGreyDown: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/dfffb601-6877-4608-a3eb-88695b05c6c2.png',
iconPinkUp: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/5bc84dff-e120-4d4b-a33e-25d68e065654.png',
iconPinkDown: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/ff2c2c9d-694a-4adf-b737-66b07f4a63ae.png',
};
},
onLoad(options) {
const systemInfo = uni.getSystemInfoSync();
this.statusBarHeight = systemInfo.statusBarHeight;
this.navHeight = this.statusBarHeight + 44;
if (options.id) {
this.activeId = options.id;
}
this.getFirstClass();
//
this.loadFilterCategories();
},
methods: {
//
goToSearch() {
uni.navigateTo({
url: '/pages/search_common/search?type=brand' //
});
},
// state === 1
async loadFilterCategories() {
try {
const syrId = uni.getStorageSync('syrId') || 1;
const res = await request.post('/sj/firstclass', { id: syrId })
if ((res.code === 200 || res.state === 1) && res.data) {
const firstClasses = res.data;
//
const promises = firstClasses.map(async (first) => {
try {
const subRes = await request.post('/sj/secondclass', { id: first.id })
return {
id: first.id,
title: first.title,
children: ((subRes.code === 200 || subRes.state === 1) && subRes.data) ? subRes.data : []
}
} catch (e) {
return { id: first.id, title: first.title, children: [] }
}
});
const filterData = await Promise.all(promises);
//
this.brandFilterData = filterData.filter(group => group.children.length > 0);
} else {
this.brandFilterData = []
}
} catch (error) {
console.error('获取分类字典失败', error)
}
},
// ============ ============
toggleSort(type) {
if (type === 'time') {
if (this.orderType === 1) {
this.orderType = 2;
} else if (this.orderType === 2) {
this.orderType = 0;
} else {
this.orderType = 1;
}
} else if (type === 'view') {
if (this.orderType === 3) {
this.orderType = 4;
} else if (this.orderType === 4) {
this.orderType = 0;
} else {
this.orderType = 3;
}
}
this.refreshBrandList();
},
// ============ ============
getFirstClass() {
request.post("/sj/firstclass").then((res) => {
this.tabs = res.data || [];
if (this.tabs.length && !this.activeId) {
this.activeId = this.tabs[0].id;
}
this.getSecondClassList();
this.refreshBrandList();
});
},
getSecondClassList() {
if (!this.activeId) return;
request.post('/sj/secondclass', { id: this.activeId }).then((res) => {
this.iconTabs = res.data || [];
});
},
selectSecondClass(id) {
if (this.currentSecondClass === id) {
this.currentSecondClass = "";
} else {
this.currentSecondClass = id;
}
//
this.selectedSecondIds = [];
this.refreshBrandList();
},
tabClick(id) {
if (this.activeId === id) return;
this.activeId = id;
this.currentSecondClass = "";
this.selectedSecondIds = [];
this.getSecondClassList();
this.refreshBrandList();
},
// ============ ============
refreshBrandList() {
//
const targetSecondClass = this.selectedSecondIds.length > 0
? this.selectedSecondIds.join(',')
: this.currentSecondClass;
const params = {
first_class: this.activeId || undefined,
second_class: targetSecondClass || undefined,
// orderType 0 undefined使
order_type: this.orderType === 0 ? undefined : this.orderType
};
Object.keys(params).forEach(key => {
if (params[key] === undefined) delete params[key];
});
if (this.$refs.brandListRef) {
// CommonList queryData
['first_class', 'second_class', 'order_type'].forEach(key => {
if (!Object.prototype.hasOwnProperty.call(params, key) && this.$refs.brandListRef.queryData) {
this.$delete(this.$refs.brandListRef.queryData, key);
}
});
this.$refs.brandListRef.manualRefresh(params);
}
},
enrichedBrandList(list) {
if (!list || !list.length) return [];
return list.map(brand => {
const tagKeys = [];
if (brand.franchise_state === 1) tagKeys.push('franchise');
if (brand.student_state === 1) tagKeys.push('recruit');
if (tagKeys.length === 0) tagKeys.push('more');
return {
...brand,
name: brand.name,
bgSrc: brand.cover_img || 'https://dummyimage.com/300x200/ccc/fff.png',
avatar: brand.logo_img || 'https://dummyimage.com/100x100/ccc/fff.png',
_tagKeys: tagKeys
};
});
},
handleBrandClick(brand) {
uni.navigateTo({
url: `/pages/brand/detail?id=${brand.id}`
});
},
handleDataLoaded(data) {
console.log("品牌列表加载完成", data);
}
}
};
</script>
<style lang="less" scoped>
//
::v-deep .icon-headle {
width: 32rpx !important;
height: 32rpx !important;
}
// Tab
.service-first-tab {
border-bottom: none;
padding-bottom: 27rpx;
position: relative;
z-index: 101;
}
::v-deep .service-first-tab {
box-shadow: none !important;
}
/* ================= 抽屉下拉样式 ================= */
.filter-panel {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 100;
pointer-events: none;
overflow: hidden;
&.show {
pointer-events: auto;
.filter-mask {
opacity: 1;
}
.filter-container {
transform: translateY(0);
}
}
.filter-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.4);
opacity: 0;
transition: opacity 0.3s ease-in-out;
}
.filter-container {
position: absolute;
top: 0;
left: 0;
right: 0;
background: #fff;
border-radius: 0 0 30rpx 30rpx;
padding: 140rpx 30rpx 40rpx;
display: flex;
flex-direction: column;
box-shadow: 0 10rpx 20rpx rgba(0, 0, 0, 0.05);
transform: translateY(-100%);
transition: transform 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
}
}
.filter-scroll {
max-height: 50vh;
}
.filter-group {
margin-bottom: 30rpx;
.group-title {
font-weight: 500;
font-size: 28rpx;
color: #333333;
line-height: 40rpx;
margin-bottom: 20rpx;
}
.pill-list {
display: flex;
flex-wrap: wrap;
gap: 20rpx 10rpx;
/* 控制行间距和列间距 */
}
}
/* 通用胶囊按钮 (一行四个) */
.pill-item {
width: calc(25% - 8rpx);
/* 精确计算一行四个 */
height: 58rpx;
background: #F5F5F5;
font-weight: 400;
font-size: 24rpx;
/* 字号略小以防溢出 */
color: #333333;
line-height: 37rpx;
/* flex 居中文字 */
display: flex;
align-items: center;
justify-content: center;
border-radius: 12rpx;
transition: all 0.3s;
box-sizing: border-box;
white-space: nowrap;
&.active {
background: #FFF0F2;
color: #FF4767;
border: 1rpx solid #FFB0BD;
}
}
.filter-footer {
display: flex;
justify-content: space-between;
margin-top: 40rpx;
.reset-btn,
.confirm-btn {
width: 47%;
text-align: center;
padding: 22rpx 0;
border-radius: 40rpx;
font-size: 28rpx;
}
.reset-btn {
background: #F5F5F5;
color: #666;
}
.confirm-btn {
background: #FF4767;
color: #fff;
}
}
/* ================= 其他页面样式 ================= */
//
.icon-tabs {
display: flex;
flex-flow: row wrap;
justify-content: flex-start;
align-items: center;
column-gap: 36rpx;
row-gap: 30rpx;
background: transparent;
padding: 0 0 0 30rpx;
margin-bottom: 30rpx;
}
.icon-tab {
display: flex;
flex-direction: column;
align-items: center;
}
.icon-img {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
border: 2rpx solid transparent;
}
.icon-text {
margin-top: 10rpx;
font-weight: 400;
font-size: 24rpx;
color: #333333;
line-height: 33rpx;
text-align: left;
}
.icon-tab.active .icon-text {
color: #FF4767;
line-height: 33rpx;
}
.icon-tab.active .icon-img {
border: 2rpx solid #FF4767;
}
//
.sort-bar {
display: flex;
align-items: center;
padding: 0 30rpx;
margin-bottom: 24rpx;
gap: 100rpx;
.sort-item {
display: flex;
align-items: center;
.sort-text {
font-weight: 400;
font-size: 26rpx;
color: #666666;
line-height: 37rpx;
margin-right: 6rpx;
&.active {
color: #FF4767;
font-weight: 500;
}
}
.arrows {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.arrow-icon {
width: 13rpx;
height: 8rpx;
margin: 2rpx 0;
}
}
}
}
//
.zhanwei {
background-color: #f5f5f5;
padding: 20rpx 0 0 0;
border-radius: 20rpx 20rpx 0rpx 0rpx;
}
.brand-grid {
background-color: #ffffff;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
padding: 20rpx 24rpx;
row-gap: 24rpx;
border-radius: 20rpx 20rpx 0rpx 0rpx;
.brand-card-wrap {
width: 48.5%;
}
}
.common-list {
::v-deep .list-container {
padding: 0;
}
::v-deep .list-scroll {
margin-top: 0rpx;
}
}
.service-list {
background-color: #f5f5f5;
border-radius: 20rpx 20rpx 0 0;
}
.nav-img-size::v-deep .icon-headle {
width: 36rpx !important;
height: 36rpx !important;
}
</style>

View File

@ -0,0 +1,579 @@
<template>
<view class="skill-market-page">
<custom-navbar title="技能集市" :showBack="true" backgroundColor="#FFFFFF" :show-headle="true" borderBottom="none"
headleSrc="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/51ebca5d-95c5-4fb8-8aa3-560a79100d25.png"
@onHeadleClick="goToSearch" class="nav-img-size">
</custom-navbar>
<view class="sticky-header-container" :style="{ top: navHeight + 'px' }">
<view class="official-author-block" v-if="authorInfo.show">
<view class="author-left">
<image class="author-avatar" :src="authorInfo.avatar" mode="aspectFill"></image>
<view class="author-info">
<text class="author-name">{{ authorInfo.name }}</text>
<text class="author-desc">发布了{{ authorInfo.videoCount }}条技能</text>
</view>
</view>
<view class="view-more-btn" v-if="!authorInfo.isOfficial" @click="goToBrandDetail">
<text>查看更多</text>
<view class="arrow-right"></view>
</view>
</view>
<view class="sort-bar-wrap">
<view class="sort-bar">
<view class="left-sorts">
<view class="sort-item" @tap="toggleSort('time')">
<text class="sort-text" :class="{ active: orderType === 1 || orderType === 2 }">发布时间</text>
<view class="arrows">
<image class="arrow-icon" :src="orderType === 2 ? iconPinkUp : iconGreyUp"></image>
<image class="arrow-icon" :src="orderType === 1 ? iconPinkDown : iconGreyDown"></image>
</view>
</view>
<view class="sort-item" @tap="toggleSort('view')">
<text class="sort-text" :class="{ active: orderType === 3 || orderType === 4 }">浏览量</text>
<view class="arrows">
<image class="arrow-icon" :src="orderType === 4 ? iconPinkUp : iconGreyUp"></image>
<image class="arrow-icon" :src="orderType === 3 ? iconPinkDown : iconGreyDown"></image>
</view>
</view>
</view>
<view class="right-filter" @tap="toggleFilterPanel" v-if="!authorInfo.show">
<text class="filter-text">筛选</text>
<image class="filter-icon" :src="filterIcon" :class="{ 'rotate': filterVisible }"></image>
</view>
</view>
<view class="filter-panel" :class="{ 'show': filterVisible }" catchtouchmove="true">
<view class="filter-mask" @tap="closeFilterPanel"></view>
<view class="filter-container">
<text class="group-title">课程类型</text>
<view class="course-filter">
<view class="course-type-item" :class="{ active: linkType === '' }" @tap="selectAndApply('')">全部</view>
<view class="course-type-item" :class="{ active: linkType === 1 }" @tap="selectAndApply(1)">平台课程</view>
<view class="course-type-item" :class="{ active: linkType === 2 }" @tap="selectAndApply(2)">品牌课程</view>
</view>
</view>
</view>
</view>
</view>
<view class="list-container">
<view v-if="skillList.length > 0">
<skill-card v-for="(item, index) in skillList" :key="item.id || index" :item="item"
@click="goToDetail(item)"></skill-card>
</view>
<view v-else>
<noData></noData>
</view>
</view>
<view style="height: 60rpx;"></view>
</view>
</template>
<script>
import request from "@/utils/request"
import SkillCard from "@/pages/home/components/skill-card.vue"
import noData from "@/components/noData/noData.vue";
const USE_REAL_API = true
export default {
components: { SkillCard, noData },
data() {
return {
navHeight: 0,
orderType: 0,
linkType: '',
// //
authorInfo: {
show: false, //
isOfficial: false, //
brandId: null, // ID
name: '', // /
avatar: '', //
videoCount: 0 //
},
filterVisible: false,
skillList: [],
page: 1,
limit: 10,
isFinished: false,
iconGreyUp: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/e00ab2ac-bccf-42d8-820c-65727327e279.png',
iconGreyDown: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/dfffb601-6877-4608-a3eb-88695b05c6c2.png',
iconPinkUp: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/5bc84dff-e120-4d4b-a33e-25d68e065654.png',
iconPinkDown: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/ff2c2c9d-694a-4adf-b737-66b07f4a63ae.png',
mockDatabase: []
}
},
computed: {
filterIcon() {
return this.filterVisible
? 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/e00ab2ac-bccf-42d8-820c-65727327e279.png'
: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/bfe7a104-bef8-4432-9487-42f05e3e9035.png'
}
},
//
onLoad(options) {
if (options && options.showAuthor === '1') {
this.authorInfo.show = true;
this.authorInfo.isOfficial = options.isOfficial === '1';
this.authorInfo.brandId = options.linkId || null;
// decodeURIComponent
this.authorInfo.name = options.authorName ? decodeURIComponent(options.authorName) : '未知作者';
this.authorInfo.avatar = options.authorAvatar ? decodeURIComponent(options.authorAvatar) : 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/bbe832bf-aae6-4579-b3cc-246d252488c3';
this.authorInfo.videoCount = options.videoCount || 0;
//
this.linkType = this.authorInfo.isOfficial ? 1 : 2;
} else if (options && options.linkType !== undefined) {
this.linkType = Number(options.linkType);
}
const sysInfo = uni.getSystemInfoSync()
const sbh = sysInfo.statusBarHeight || 0
this.navHeight = sbh
this.initMockData()
this.fetchData(true)
},
onReachBottom() {
if (!this.isFinished) {
this.fetchData(false)
}
},
methods: {
goToSearch() {
uni.navigateTo({
url: '/pages/search_common/search?type=skill'
});
},
//
goToBrandDetail() {
if (this.authorInfo.brandId) {
uni.navigateTo({
url: `/pages/jingxuan/brand-detail?id=${this.authorInfo.brandId}`
});
} else {
uni.showToast({ title: '缺少品牌信息', icon: 'none' });
}
},
toggleSort(type) {
if (type === 'time') {
if (this.orderType === 1) this.orderType = 2
else if (this.orderType === 2) this.orderType = 0
else this.orderType = 1
} else if (type === 'view') {
if (this.orderType === 3) this.orderType = 4
else if (this.orderType === 4) this.orderType = 0
else this.orderType = 3
}
this.fetchData(true)
},
toggleFilterPanel() {
this.filterVisible = !this.filterVisible
},
closeFilterPanel() {
this.filterVisible = false
},
selectAndApply(typeVal) {
this.linkType = typeVal;
//
this.authorInfo.show = false;
this.closeFilterPanel();
this.fetchData(true);
},
async fetchData(isReset = false) {
if (isReset) {
this.page = 1
this.isFinished = false
this.skillList = []
}
if (USE_REAL_API) {
uni.showLoading({ title: '加载中...' })
try {
const params = {
page: this.page,
limit: this.limit
}
if (this.orderType !== 0) params.order_type = this.orderType;
if (this.linkType !== '') params.link_type = this.linkType;
// link_id
if (this.authorInfo.show && !this.authorInfo.isOfficial && this.authorInfo.brandId) {
params.link_id = this.authorInfo.brandId;
}
const res = await request.post('/sj/skill/list', params)
if ((res.code === 200 || res.state === 1) && res.data && res.data.list) {
const list = res.data.list.map(item => this.formatApiData(item))
if (list.length < this.limit) this.isFinished = true
this.skillList = isReset ? list : this.skillList.concat(list)
this.page++
} else {
this.isFinished = true
}
} catch (e) {
uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
uni.hideLoading()
}
} else {
uni.showLoading({ title: '模拟加载中...' })
setTimeout(() => {
let list = [...this.mockDatabase]
if (this.linkType !== '') {
list = list.filter(item => item.link_type === this.linkType)
}
//
if (this.authorInfo.show && !this.authorInfo.isOfficial && this.authorInfo.brandId) {
list = list.filter(item => item.brand_id == this.authorInfo.brandId)
}
if (this.orderType !== 0) {
list.sort((a, b) => {
if (this.orderType === 1) return b.timestamp - a.timestamp
if (this.orderType === 2) return a.timestamp - b.timestamp
if (this.orderType === 3) return b.views - a.views
if (this.orderType === 4) return a.views - b.views
return 0
})
}
const start = (this.page - 1) * this.limit
const end = start + this.limit
const pageData = list.slice(start, end)
if (pageData.length < this.limit) this.isFinished = true
this.skillList = isReset ? pageData : this.skillList.concat(pageData)
this.page++
uni.hideLoading()
}, 500)
}
},
formatApiData(item) {
return {
id: item.id,
title: item.name,
coverSrc: item.cover_img || 'https://dummyimage.com/300x200/ccc/fff.png',
type: item.link_name || (item.link_type === 1 ? '平台课程' : '品牌课程'),
link_type: item.link_type,
views: item.browse_num || 0,
time: item.create_time ? this.formatTimeAgo(item.create_time) : '',
typeIcon: item.link_logo || ''
}
},
formatTimeAgo(timeStr) {
const now = new Date()
const time = new Date(timeStr.replace(/-/g, '/'))
const diff = Math.floor((now - time) / 1000)
if (diff < 3600) return Math.floor(diff / 60) + '分钟前'
if (diff < 86400) return Math.floor(diff / 3600) + '小时前'
if (diff < 604800) return Math.floor(diff / 86400) + '天前'
return timeStr.split(' ')[0]
},
goToDetail(item) {
if (item.id) {
uni.navigateTo({ url: `/pages/skill/detail?id=${item.id}` })
} else {
uni.showToast({ title: '详情页开发中', icon: 'none' })
}
},
initMockData() {
this.mockDatabase = [
{ id: 1, title: '穿戴甲的操作视频扫码验券操作教程', coverSrc: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/e2220528-fdf6-4cad-a25d-ec6cbee1501e.jpg', type: '平台课程', link_type: 1, views: 836000, time: '7小时前', timestamp: Date.now() - 7 * 3600000 },
{ id: 2, title: '脸部按摩操作步骤详情', coverSrc: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/e2220528-fdf6-4cad-a25d-ec6cbee1501e.jpg', type: '品牌课程', link_type: 2, brand_id: 101, views: 8334, time: '1天前', timestamp: Date.now() - 24 * 3600000 },
{ id: 3, title: '全身SPA的操作视频操作教程', coverSrc: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/e2220528-fdf6-4cad-a25d-ec6cbee1501e.jpg', type: '平台课程', link_type: 1, views: 2344, time: '3天前', timestamp: Date.now() - 3 * 24 * 3600000 },
{ id: 4, title: '高级面部护理手法详解最新版', coverSrc: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/e2220528-fdf6-4cad-a25d-ec6cbee1501e.jpg', type: '平台课程', link_type: 1, views: 836, time: '4月26日', timestamp: Date.now() - 30 * 24 * 3600000 },
{ id: 5, title: '光疗美甲进阶全方位解析', coverSrc: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/e2220528-fdf6-4cad-a25d-ec6cbee1501e.jpg', type: '品牌课程', link_type: 2, brand_id: 101, views: 12000, time: '2小时前', timestamp: Date.now() - 2 * 3600000 }
]
}
}
}
</script>
<style lang="scss" scoped>
.skill-market-page {
min-height: 100vh;
background: #fff;
}
/* 统一吸顶容器 */
.sticky-header-container {
position: sticky;
background: #fff;
z-index: 101;
/* 确保整体吸顶时下方有稍微一点阴影或边框分割更好看,可根据 UI 需求开启 */
// border-bottom: 1rpx solid #F5F5F5;
}
/* 官方/品牌 作者展示块样式 */
.official-author-block {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 30rpx;
background: #fff;
border-bottom: 10rpx solid #F5F5F5;
.author-left {
display: flex;
align-items: center;
.author-avatar {
width: 70rpx;
height: 70rpx;
border-radius: 50%;
margin-right: 20rpx;
}
.author-info {
display: flex;
flex-direction: column;
.author-name {
font-weight: 500;
font-size: 28rpx;
color: #333333;
line-height: 40rpx;
margin-bottom: 4rpx;
}
.author-desc {
font-weight: 400;
font-size: 20rpx;
color: #999999;
line-height: 28rpx;
}
}
}
.view-more-btn {
display: flex;
align-items: center;
font-size: 24rpx;
color: #FF4767;
border: 2rpx solid #FF4767;
padding: 8rpx 20rpx;
border-radius: 45rpx;
.arrow-right {
width: 10rpx;
height: 10rpx;
border-top: 2rpx solid #FF4767;
border-right: 2rpx solid #FF4767;
transform: rotate(45deg);
margin-left: 6rpx;
}
}
}
.sort-bar-wrap {
/* 移除单独的 sticky由外层 container 统一控制 */
background: #fff;
position: relative;
}
.sort-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 30rpx;
.left-sorts {
display: flex;
align-items: center;
gap: 100rpx;
.sort-item {
display: flex;
align-items: center;
.sort-text {
font-size: 28rpx;
color: #333333;
margin-right: 6rpx;
font-weight: 400;
&.active {
color: #FF4767;
font-weight: 500;
}
}
.arrows {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.arrow-icon {
width: 14rpx;
height: 8rpx;
margin: 2rpx 0;
}
}
}
}
.right-filter {
display: flex;
align-items: center;
.filter-text {
font-size: 26rpx;
color: #666666;
margin-right: 8rpx;
}
.filter-icon {
width: 24rpx;
height: 24rpx;
transition: transform 0.3s ease;
&.rotate {
transform: rotate(180deg);
width: 18rpx;
height: 12rpx;
}
}
}
}
.list-container {
padding: 0 24rpx 30rpx;
}
.filter-panel {
position: absolute;
top: 100%;
left: 0;
right: 0;
height: 100vh;
z-index: 100;
pointer-events: none;
overflow: hidden;
&.show {
pointer-events: auto;
.filter-mask {
opacity: 1;
}
.filter-container {
transform: translateY(0);
}
}
.filter-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.4);
opacity: 0;
transition: opacity 0.3s ease-in-out;
}
.filter-container {
position: absolute;
top: 0;
left: 0;
right: 0;
background: #fff;
border-radius: 0 0 24rpx 24rpx;
padding: 30rpx 30rpx 40rpx;
display: flex;
flex-direction: column;
transform: translateY(-100%);
transition: transform 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
}
}
.group-title {
font-weight: 500;
font-size: 28rpx;
color: #333333;
margin-bottom: 24rpx;
display: block;
}
.course-filter {
display: flex;
gap: 20rpx;
.course-type-item {
background: #F5F5F5;
color: #333333;
font-size: 26rpx;
padding: 12rpx 36rpx;
border-radius: 12rpx;
border: 1rpx solid transparent;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
&.active {
background: #FFF0F2;
color: #FF4767;
border-color: #FFB0BD;
}
}
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
padding: 120rpx 0;
.empty-icon {
width: 346rpx;
height: 373rpx;
}
}
::v-deep .icon-headle {
width: 32rpx !important;
height: 32rpx !important;
}
::v-deep .back-icon {
width: 28rpx !important;
height: 36rpx !important;
}
.nav-img-size::v-deep .icon-headle {
width: 36rpx !important;
height: 36rpx !important;
}
</style>

View File

@ -0,0 +1,768 @@
<template>
<view class="skill-detail-page">
<custom-navbar title="技能集市" backgroundColor="#ffffff" titleColor="#333" borderBottom="none">
</custom-navbar>
<!-- <view class="mock-switch" @click="toggleMockData">
<text>{{ useMockData ? '关闭模拟数据' : '开启模拟数据' }}</text>
</view> -->
<view class="video-section" v-if="article.videoSrc">
<view class="video-wrapper">
<video v-if="!tipShow" id="customVideo" class="main-video" :src="article.videoSrc" :poster="article.poster"
:controls="showNativeControls" :show-center-play-btn="false" :muted="isMuted" object-fit="cover"
@timeupdate="onTimeUpdate" @loadedmetadata="onLoadedMetaData" @ended="onEnded" @play="onPlay" @pause="onPause"
@fullscreenchange="onFullScreenChange">
<cover-image v-if="!isPlaying && !showNativeControls" class="center-play-icon"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/bb720bb0-4ada-49f6-9ce5-38abfd613d5d.png"
@click.stop="togglePlay"></cover-image>
<cover-view class="custom-control-bar" v-if="!showNativeControls">
<cover-image class="bar-bg"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/ce7f28f8-470d-4f5b-9c7e-ff2bf8635226.png"></cover-image>
<cover-view class="bar-content">
<cover-image v-if="isPlaying" class="ctrl-icon" :src="iconPause" @click.stop="togglePlay"></cover-image>
<cover-image v-if="!isPlaying" class="ctrl-icon" :src="iconPlay" @click.stop="togglePlay"></cover-image>
<cover-view class="time-text">{{ formatTime(currentTime) }}</cover-view>
<cover-view class="slider-track">
<cover-view class="slider-bg"></cover-view>
<cover-view class="slider-active" :style="activeStyle"></cover-view>
<cover-view class="slider-thumb" v-if="currentTime > 0" :style="thumbStyle"></cover-view>
</cover-view>
<cover-view class="time-text">{{ formatTime(duration) }}</cover-view>
<cover-image v-if="!isMuted" class="ctrl-icon mute-icon" :src="iconSoundOn"
@click.stop="toggleMute"></cover-image>
<cover-image v-if="isMuted" class="ctrl-icon mute-icon" :src="iconMute"
@click.stop="toggleMute"></cover-image>
<cover-image class="ctrl-icon fullscreen-icon" :src="iconFullscreen"
@click.stop="toggleFullScreen"></cover-image>
</cover-view>
</cover-view>
</video>
<image v-else class="main-video fallback-poster" :src="article.poster || article.videoSrc" mode="aspectFill">
</image>
</view>
</view>
<view class="article-header" v-if="article.title">
<view class="title">{{ article.title }}</view>
<view class="meta-row">
<view class="author-info" @click="goToAuthorMarket">
<image class="avatar" :src="article.authorAvatar" mode="aspectFill"></image>
<text class="author-name">{{ article.authorName }}</text>
<text class="date">{{ article.date }}</text>
</view>
<view class="favorite-btn" @click="toggleFavorite">
<image class="star-icon" :src="isFavorite ? filledStarUrl : emptyStarUrl" />
<text :class="{ 'active-fav': isFavorite }">{{ isFavorite ? '已收藏' : '收藏' }}</text>
</view>
</view>
</view>
<view class="article-content-wrapper">
<view class="article-content">
<rich-text :nodes="formattedHtmlContent"></rich-text>
</view>
</view>
<view class="recommend-section" v-if="recommendList.length > 0">
<view class="section-header">
<text class="section-title">相关推荐</text>
<view class="refresh-btn" @click="refreshList">
<image class="refresh-icon" :class="{ 'is-rotating': isRefreshing }"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/f2152145-df2a-49ab-87f5-f66b3207d0b0.png">
</image>
<text>换一批</text>
</view>
</view>
<view class="list-container">
<skill-card v-for="(item, index) in recommendList" :key="index" :item="item"></skill-card>
</view>
</view>
<tipsPopup2 :show="tipShow" @closePopup="closePopup" sureText="确认" @okBtn="okBtn">
{{ tipsText }}
</tipsPopup2>
</view>
</template>
<script>
import request from "@/utils/request";
import SkillCard from "@/pages/home/components/skill-card.vue";
import tipsPopup2 from "@/components/tips-popup/tips-popup2";
export default {
components: {
SkillCard,
tipsPopup2
},
data() {
return {
useMockData: false,
articleId: null,
isFavorite: false,
emptyStarUrl: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/53378402-5456-4a80-b2ec-7c18d43d970e.png',
filledStarUrl: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/10461305-6bf8-4016-ae76-3415e055ec63.png',
isRefreshing: false,
isLogin: false,
tipShow: false,
tipsText: "",
tipType: "",
videoContext: null,
isPlaying: false,
isMuted: false,
showNativeControls: false,
currentTime: 0,
duration: 0,
sliderValue: 0,
iconPlay: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/88ca4fe3-645c-48a9-abda-1f9b45714970.png',
iconPause: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/cf9d6dd6-e9df-4c46-bafa-e1a72f7ddbcd.png',
iconSoundOn: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/9f13a630-7efd-4c61-b6fe-b6b282e28e42.png',
iconMute: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/80f856c1-3d66-4b11-b21a-8eb6a3272a3c.png',
iconFullscreen: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/4286ef33-6849-4851-8866-4230d2523a33.png',
article: {},
recommendList: [],
recommendPage: 1
}
},
computed: {
formattedHtmlContent() {
if (!this.article.htmlContent) return '';
let html = this.article.htmlContent;
html = html.replace(/<img/gi, '<img style="max-width:100%; height:auto; display:block; border-radius:10px; margin: 20rpx 0;"');
html = html.replace(/<p/gi, '<p style="margin-bottom: 24rpx; line-height: 40rpx;"');
return html;
},
activeStyle() {
return `width: ${this.sliderValue}%;`;
},
thumbStyle() {
return `left: ${this.sliderValue}%;`;
}
},
onLoad(options) {
if (options && options.id) {
this.articleId = parseInt(options.id, 10);
} else {
this.articleId = 1;
}
this.getPageData();
},
onShow() {
this.isLogin = uni.getStorageSync("accessToken") ? true : false;
},
methods: {
//
goToAuthorMarket() {
const isOfficial = this.article.linkType === 1 ? '1' : '0';
const linkId = this.article.linkId || '';
const name = encodeURIComponent(this.article.authorName || '');
const avatar = encodeURIComponent(this.article.authorAvatar || '');
const count = this.article.authorVideoCount || 0;
uni.navigateTo({
url: `/pages/jingxuan/selected-skills?showAuthor=1&isOfficial=${isOfficial}&linkId=${linkId}&authorName=${name}&authorAvatar=${avatar}&videoCount=${count}`
});
},
getVideoContext() {
if (!this.videoContext) {
this.videoContext = uni.createVideoContext('customVideo', this);
}
return this.videoContext;
},
closePopup() {
this.tipShow = false;
},
okBtn() {
if (this.tipType == "login") {
uni.navigateTo({
url: "/pages/blogPopup/blogPopup",
});
}
this.tipShow = false;
},
formatTime(sec) {
if (!sec) return '00:00';
sec = Math.round(sec);
let m = Math.floor(sec / 60);
let s = sec % 60;
return (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s);
},
onLoadedMetaData(e) {
this.duration = e.detail.duration;
},
onTimeUpdate(e) {
if (e.detail.duration && this.duration === 0) {
this.duration = e.detail.duration;
}
this.currentTime = e.detail.currentTime;
if (this.duration > 0) {
this.sliderValue = Number(((this.currentTime / this.duration) * 100).toFixed(2));
}
},
onEnded() {
this.isPlaying = false;
this.currentTime = 0;
this.sliderValue = 0;
if (this.showNativeControls) {
const ctx = this.getVideoContext();
if (ctx) ctx.exitFullScreen();
}
},
onPlay() { this.isPlaying = true; },
onPause() { this.isPlaying = false; },
onFullScreenChange(e) {
const isFull = e.detail.fullScreen;
if (!isFull) {
this.showNativeControls = false;
} else {
this.showNativeControls = true;
}
},
togglePlay() {
const ctx = this.getVideoContext();
if (!ctx) return;
if (this.isPlaying) {
ctx.pause();
} else {
ctx.play();
}
},
toggleMute() {
this.isMuted = !this.isMuted;
},
toggleFullScreen() {
const ctx = this.getVideoContext();
if (!ctx) return;
this.showNativeControls = true;
this.$nextTick(() => {
ctx.requestFullScreen({ direction: 90 });
});
},
toggleMockData() {
this.useMockData = !this.useMockData;
uni.showToast({ title: this.useMockData ? 'Mock已开启' : 'Mock已关闭', icon: 'none' });
this.getPageData();
},
getPageData() {
if (this.useMockData) {
this.injectMockData();
return;
}
uni.showLoading({ title: '加载中...' });
request.post("/sj/skill/details", {
id: this.articleId
}).then((res) => {
if (res.code == 200 && res.data) {
const data = res.data;
this.article = {
title: data.name || '暂无标题',
// 使
linkType: data.link_type || 1, // 1= 2=
linkId: data.link_id || 0,
authorName: data.link_name || '美融融平台',
authorAvatar: data.link_logo || 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/bbe832bf-aae6-4579-b3cc-246d252488c3',
authorVideoCount: data.link_publish_num || 0, //
date: data.create_time ? data.create_time.substring(0, 10).replace(/-/g, '.') : '',
videoSrc: data.video || '',
poster: data.cover_img || '',
htmlContent: data.detail || ''
};
this.isFavorite = data.collect_state === 1;
request.post("/sj/skill/list", {
page: 1,
limit: 3,
skill_id: data.id
}).then((listRes) => {
if (listRes.code == 200 && listRes.data && listRes.data.list) {
// 使 map
this.recommendList = listRes.data.list.map(item => {
return {
id: item.id,
title: item.name || '暂无标题', //
coverSrc: item.cover_img || '', //
type: item.link_name || '平台课程', // /
typeIcon: item.link_logo || '', //
// /
views: item.total_browse_num || item.real_browse_num || item.browse_num || 0,
//
time: item.create_time ? item.create_time.substring(0, 10).replace(/-/g, '-') : ''
};
});
}
}).catch((err) => {
console.error('获取推荐列表异常', err);
});
} else {
uni.showToast({ title: res.msg || '获取数据失败', icon: 'none' });
}
}).catch((err) => {
console.error('获取技能集市详情异常', err);
}).finally(() => {
uni.hideLoading();
});
},
toggleFavorite() {
if (!this.isLogin) {
if (this.isPlaying) {
this.getVideoContext().pause();
}
this.tipsText = "您现在还未登录,是否确定前往登录?";
this.tipShow = true;
this.tipType = "login";
return;
}
if (this.useMockData) {
this.isFavorite = !this.isFavorite;
uni.showToast({ title: this.isFavorite ? '收藏成功' : '已取消收藏', icon: 'none' });
return;
}
uni.showLoading({ title: '处理中' });
const targetState = this.isFavorite ? 2 : 1;
request.post("/sj/skill/collect", {
id: this.articleId,
state: targetState
}).then(res => {
if (res.code == 200) {
this.isFavorite = !this.isFavorite;
uni.showToast({ title: this.isFavorite ? '收藏成功' : '已取消收藏', icon: 'none' });
} else {
uni.showToast({ title: res.msg || '操作失败', icon: 'none' });
}
}).catch(err => {
uni.showToast({ title: '网络异常', icon: 'none' });
}).finally(() => {
uni.hideLoading();
});
},
refreshList() {
if (this.isRefreshing) return;
this.isRefreshing = true;
uni.showLoading({ title: '加载中' });
// 101+1
if (this.recommendPage >= 10) {
this.recommendPage = 1;
} else {
this.recommendPage++;
}
request.post("/sj/skill/list", {
page: this.recommendPage,
limit: 3,
skill_id: this.article.id
}).then((res) => {
if (res.code == 200 && res.data && res.data.list && res.data.list.length > 0) {
this.recommendList = res.data.list.map(item => ({
id: item.id,
title: item.name || '暂无标题',
coverSrc: item.cover_img || '',
type: item.link_name || '平台课程',
views: item.browse_num || 0,
time: item.create_time ? item.create_time.split(' ')[0] : '',
typeIcon: item.link_logo || '',
video: item.video || ''
}));
} else {
// 1
this.recommendPage = 1;
request.post("/sj/skill/list", {
page: 1,
limit: 3,
skill_id: this.article.id
}).then((res2) => {
if (res2.code == 200 && res2.data && res2.data.list) {
this.recommendList = res2.data.list.map(item => ({
id: item.id,
title: item.name || '暂无标题',
coverSrc: item.cover_img || '',
type: item.link_name || '平台课程',
views: item.browse_num || 0,
time: item.create_time ? item.create_time.split(' ')[0] : '',
typeIcon: item.link_logo || '',
video: item.video || ''
}));
}
});
}
this.isRefreshing = false;
uni.hideLoading();
}).catch(() => {
this.isRefreshing = false;
uni.hideLoading();
});
},
injectMockData() {
this.isFavorite = false;
this.article = {
title: '穿戴甲的操作视频扫码验券操作',
// Mock(linkType2)
linkType: 2,
linkId: 101,
authorName: '蜜丝卡伦官方品牌',
authorAvatar: 'https://images.unsplash.com/photo-1512290923902-8a9f81dc236c?ixlib=rb-4.0.3&auto=format&fit=crop&w=100&q=80',
authorVideoCount: 658,
date: '2026.03.03',
videoSrc: 'https://vjs.zencdn.net/v/oceans.mp4',
poster: 'https://images.unsplash.com/photo-1610992015732-2449b76344bc?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80',
htmlContent: `
<p>最近一向以平价良心宠粉著称的胖东来突然因为一条毛巾被推上风口浪尖</p>
<p>4月下旬抖音博主惊梦人-连续发布多条探店视频称自己跑遍许昌5家胖东来门店发现一边是几块钱的玻璃水洗衣液等亲民好物另一边却摆着标价249元的高端毛巾价格形成反差</p>
<img src="https://dummyimage.com/800x400/ff6b81/ffffff.png&text=占位图" alt="会员权益"/>
`
};
this.loadMockRecommend();
},
loadMockRecommend() {
this.recommendList = [
{
id: 1,
title: '穿戴甲的操作视频扫码验券操作教程...',
coverSrc: 'https://images.unsplash.com/photo-1604654894610-df63bc536371?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80',
type: '平台课程',
typeIcon: 'https://dummyimage.com/100x100/e6505f/fff.png&text=融',
views: 836000,
time: '7小时前'
},
{
id: 2,
title: '脸部按摩操作步骤详情',
coverSrc: 'https://images.unsplash.com/photo-1512290923902-8a9f81dc236c?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80',
type: '脸缘品牌',
typeIcon: 'https://dummyimage.com/100x100/e6505f/fff.png&text=缘',
views: 8334,
time: '1天前'
}
];
}
}
}
</script>
<style lang="scss" scoped>
.skill-detail-page {
background-color: #ffffff;
min-height: 100vh;
padding-bottom: 60rpx;
position: relative;
}
.mock-switch {
position: fixed;
right: 20rpx;
top: 200rpx;
background: #E5505F;
color: #fff;
font-size: 24rpx;
padding: 10rpx 20rpx;
border-radius: 30rpx;
z-index: 999;
box-shadow: 0 4rpx 10rpx rgba(229, 80, 95, 0.4);
}
.video-section {
width: 100%;
height: 420rpx;
.video-wrapper {
position: relative;
width: 100%;
height: 100%;
.main-video {
width: 100%;
height: 100%;
position: relative;
}
.fallback-poster {
z-index: 1;
}
.center-play-icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 80rpx;
height: 80rpx;
z-index: 99;
}
.custom-control-bar {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 70rpx;
z-index: 99;
.bar-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 70rpx;
z-index: 100;
}
.bar-content {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 70rpx;
z-index: 101;
display: flex;
flex-direction: row;
align-items: center;
padding: 0 10rpx;
box-sizing: border-box;
.ctrl-icon {
width: 25rpx;
height: 30rpx;
margin: 0 10rpx;
flex-shrink: 0;
}
.mute-icon {
width: 38rpx;
height: 30rpx;
flex-shrink: 0;
}
.fullscreen-icon {
width: 30rpx;
height: 30rpx;
flex-shrink: 0;
}
.time-text {
font-size: 22rpx;
color: #ffffff;
line-height: 70rpx;
}
.slider-track {
flex: 1;
height: 70rpx;
margin: 0 20rpx;
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
.slider-bg {
position: absolute;
top: 33rpx;
left: 0;
width: 100%;
height: 4rpx;
background-color: rgba(255, 255, 255, 0.4);
}
.slider-active {
position: absolute;
top: 33rpx;
left: 0;
height: 4rpx;
background-color: #FF4767;
}
.slider-thumb {
position: absolute;
top: 25rpx;
margin-left: -10rpx;
width: 20rpx;
height: 20rpx;
border-radius: 50%;
background-color: #FF4767;
}
}
}
}
}
}
.article-header {
padding: 30rpx 30rpx 20rpx;
.title {
font-weight: 500;
font-size: 32rpx;
color: #333333;
line-height: 45rpx;
margin-bottom: 24rpx;
}
.meta-row {
display: flex;
justify-content: space-between;
align-items: center;
.author-info {
display: flex;
align-items: center;
.avatar {
width: 28rpx;
height: 28rpx;
border-radius: 50%;
margin-right: 12rpx;
}
.author-name {
font-weight: 400;
font-size: 24rpx;
color: #666666;
line-height: 33rpx;
margin-right: 16rpx;
}
.date {
font-weight: 400;
font-size: 24rpx;
color: #999999;
line-height: 33rpx;
}
}
.favorite-btn {
display: flex;
align-items: center;
font-weight: 400;
font-size: 24rpx;
color: #999999;
line-height: 33rpx;
.star-icon {
width: 26rpx;
height: 26rpx;
margin-right: 6rpx;
margin-top: -2rpx;
}
.active-fav {
color: #FD5A8C;
}
}
}
}
.article-content-wrapper {
.article-content {
padding: 0 30rpx 40rpx;
font-weight: 400;
font-size: 28rpx;
color: #2A2A2A;
line-height: 40rpx;
::v-deep p {
margin-bottom: 24rpx;
line-height: 40rpx;
}
::v-deep img {
max-width: 100%;
height: auto;
display: block;
border-radius: 10rpx;
margin: 20rpx 0;
}
}
.empty-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 50rpx 0 80rpx;
.empty-icon {
width: 140rpx;
height: 140rpx;
margin-bottom: 24rpx;
opacity: 0.8;
}
.empty-text {
font-size: 26rpx;
color: #A9A9A9;
}
}
}
.recommend-section {
padding: 0 30rpx;
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30rpx;
.section-title {
font-weight: 500;
font-size: 30rpx;
color: #333333;
line-height: 42rpx;
}
.refresh-btn {
display: flex;
align-items: center;
font-size: 26rpx;
color: #999999;
.refresh-icon {
width: 24rpx;
height: 28rpx;
margin-right: 8rpx;
transition: transform 0.5s ease;
&.is-rotating {
transform: rotate(360deg);
}
}
}
}
}
</style>

View File

@ -405,7 +405,7 @@
.login {
width: 630rpx;
height: 96rpx;
background-color: #E8101E;
background-color: #FF4767;
color: #fff;
font-size: 40rpx;
line-height: 96rpx;

View File

@ -18,7 +18,7 @@
</view>
</view>
</view>
<uv-switch v-model="mainSwitch.switch" activeColor="#E8101E" @change="handleSwitchChange('total',mainSwitch, $event)"></uv-switch>
<uv-switch v-model="mainSwitch.switch" activeColor="#FF4767" @change="handleSwitchChange('total',mainSwitch, $event)"></uv-switch>
</view>
</view>
<view class="settings-title" v-if="kindList.length">
@ -34,7 +34,7 @@
</view>
<view class="card-tip">{{ item.text }}</view>
</view>
<uv-switch v-model="item.switch" activeColor="#E8101E" @change="handleSwitchChange('branch',item, $event)"></uv-switch>
<uv-switch v-model="item.switch" activeColor="#FF4767" @change="handleSwitchChange('branch',item, $event)"></uv-switch>
</view>
</view>
@ -267,7 +267,7 @@
}
.color-red {
color: #E8101E !important;
color: #FF4767 !important;
}
/* 弹窗样式 */
@ -324,7 +324,7 @@
.dialog-btn {
width: 476rpx;
height: 86rpx;
background: #E8101E;
background: #FF4767;
border-radius: 44rpx;
display: flex;
align-items: center;

View File

@ -332,7 +332,7 @@
width: 16rpx;
height: 16rpx;
border-radius: 50%;
background-color: #E8101E;
background-color: #FF4767;
margin-left: 8rpx;
}

View File

@ -18,7 +18,7 @@
</view>
</view>
</view>
<uv-switch v-model="mainSwitch.switch" activeColor="#E8101E"
<uv-switch v-model="mainSwitch.switch" activeColor="#FF4767"
@change="handleSwitchChange('total',mainSwitch, $event)"></uv-switch>
</view>
</view>
@ -35,7 +35,7 @@
</view>
<view class="card-tip">{{ item.text }}</view>
</view>
<uv-switch v-model="item.switch" activeColor="#E8101E"
<uv-switch v-model="item.switch" activeColor="#FF4767"
@change="handleSwitchChange('branch',item, $event)"></uv-switch>
</view>
</view>
@ -289,7 +289,7 @@
}
.color-red {
color: #E8101E !important;
color: #FF4767 !important;
}
/* 弹窗样式 */
@ -346,7 +346,7 @@
.dialog-btn {
width: 476rpx;
height: 86rpx;
background: #E8101E;
background: #FF4767;
border-radius: 44rpx;
display: flex;
align-items: center;
@ -437,7 +437,7 @@
width: 300rpx;
height: 80rpx;
line-height: 80rpx;
background: #E8101E;
background: #FF4767;
border-radius: 40rpx;
margin: 0 auto;
}

View File

@ -29,7 +29,7 @@
</view>
<view class="card-tip"> 开启语音提示避免落单</view>
</view>
<uv-switch v-model="value" @change="changeVoice" activeColor="#E8101E"></uv-switch>
<uv-switch v-model="value" @change="changeVoice" activeColor="#FF4767"></uv-switch>
</view>
</view>
@ -292,7 +292,7 @@
}
.color-red {
color: #E8101E !important;
color: #FF4767 !important;
}
//
@ -348,7 +348,7 @@
.dialog-btn {
width: 400rpx;
height: 88rpx;
background: #E8101E;
background: #FF4767;
border-radius: 44rpx;
display: flex;
align-items: center;

File diff suppressed because it is too large Load Diff

670
pages/my/myFavorite.vue Normal file
View File

@ -0,0 +1,670 @@
<template>
<view class="collection-page">
<custom-navbar title="我的收藏" :showBack="true" backgroundColor="#FFFFFF" :show-headle="true" borderBottom="none"
headleSrc="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/b18bac2f-3e4c-4586-9d45-0f088629c9d1.png"
@onHeadleClick="goToSearch" class="serch-tap">
</custom-navbar>
<view class="tab-bar-wrap" :style="{ top: navHeight + 'px' }">
<view class="tab-bar">
<view class="tab-items">
<view class="tab-item" :class="{ active: currentTab === 'brand' }" @tap="switchTab('brand')">
收藏的品牌
</view>
<view class="tab-item" :class="{ active: currentTab === 'course' }" @tap="switchTab('course')">
收藏的课程
</view>
</view>
</view>
</view>
<view class="filter-panel" :class="{ 'show': filterVisible }" :style="{ top: filterTop + 'px' }"
catchtouchmove="true">
<view class="filter-mask" @tap="closeFilterPanel"></view>
<view class="filter-container">
<scroll-view scroll-y class="filter-scroll" v-if="currentTab === 'brand'">
<view class="filter-group" v-for="group in brandFilterData" :key="group.id">
<view class="group-title">{{ group.title }}</view>
<view class="pill-list">
<view class="pill-item" v-for="sub in group.children" :key="sub.id"
:class="{ active: selectedSecondIds.includes(sub.id) }" @tap="toggleSecondSelect(sub.id)">
{{ sub.title }}
</view>
</view>
</view>
</scroll-view>
<view v-else class="course-filter">
<view class="course-type-item" :class="{ active: selectedCourseType === 'platform' }"
@tap="selectCourseType('platform')">
平台课程
</view>
<view class="course-type-item" :class="{ active: selectedCourseType === 'brand' }"
@tap="selectCourseType('brand')">
品牌课程
</view>
</view>
<view class="filter-footer">
<view class="reset-btn" @tap="resetFilter">重置</view>
<view class="confirm-btn" @tap="applyFilterAndClose">确定</view>
</view>
</view>
</view>
<view class="collection-content" v-if="currentTab === 'brand'">
<view class="brand-grid" v-if="filteredBrandList.length > 0">
<brand-card class="brand-card-wrap" v-for="(item, index) in filteredBrandList" :key="index" :item="item"
:tagKeys="item.tagKeys" @click="goToBrandDetail(item)"></brand-card>
</view>
<view class="empty-state" v-else>
<image class="empty-icon"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/da814ede-1adc-4f90-8cdf-5357a12fab27.png">
</image>
</view>
</view>
<view class="collection-content" v-else>
<view class="course-list" v-if="filteredCourseList.length > 0">
<skill-card v-for="(item, index) in filteredCourseList" :key="index" :item="item"></skill-card>
</view>
<view class="empty-state" v-else>
<image class="empty-icon"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/da814ede-1adc-4f90-8cdf-5357a12fab27.png">
</image>
</view>
</view>
<view style="height: 120rpx;"></view>
</view>
</template>
<script>
import BrandCard from "@/pages/home/components/brand-card.vue"
import SkillCard from "@/pages/home/components/skill-card.vue"
import request from "@/utils/request"
// true=false=使
const USE_REAL_API = true
export default {
components: {
BrandCard,
SkillCard
},
data() {
return {
navHeight: 0, //
filterTop: 0, //
currentTab: 'brand',
//
filterVisible: false,
// ()
brandFilterData: [],
selectedSecondIds: [], // ID
//
selectedCourseType: '', // 'platform' 'brand'
//
rawBrandList: [],
rawCourseList: [],
//
filteredBrandList: [],
filteredCourseList: []
}
},
computed: {
filterIcon() {
return this.filterVisible
? 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/e00ab2ac-bccf-42d8-820c-65727327e279.png'
: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/bfe7a104-bef8-4432-9487-42f05e3e9035.png'
}
},
onLoad() {
// Tab
const sysInfo = uni.getSystemInfoSync()
const sbh = sysInfo.statusBarHeight || 0
this.navHeight = sbh
// Tab 106rpx px
this.filterTop = this.navHeight + uni.upx2px(106)
this.loadFilterCategories()
this.loadData()
},
methods: {
//
goToSearch() {
// this.currentTab 'brand' 'course'
uni.navigateTo({
url: `/pages/search_common/search?type=collection_${this.currentTab}`
});
},
goBack() {
uni.navigateBack()
},
addAddress() {
//
},
switchTab(tab) {
if (this.currentTab === tab) return
this.currentTab = tab
// tab
if (tab === 'brand') {
this.resetBrandFilter()
} else {
this.resetCourseFilter()
}
this.loadData()
this.closeFilterPanel()
},
//
toggleFilterPanel() {
this.filterVisible = !this.filterVisible
},
closeFilterPanel() {
this.filterVisible = false
},
// state === 1
async loadFilterCategories() {
try {
const res = await request.post('/sj/firstclass', {})
if ((res.code === 200 || res.state === 1) && res.data) {
const firstClasses = res.data;
//
const promises = firstClasses.map(async (first) => {
try {
const subRes = await request.post('/sj/secondclass', { id: first.id })
return {
id: first.id,
title: first.title,
children: ((subRes.code === 200 || subRes.state === 1) && subRes.data) ? subRes.data : []
}
} catch (e) {
return { id: first.id, title: first.title, children: [] }
}
});
const filterData = await Promise.all(promises);
//
this.brandFilterData = filterData.filter(group => group.children.length > 0);
} else {
this.brandFilterData = []
}
} catch (error) {
console.error('获取分类字典失败', error)
}
},
toggleSecondSelect(secId) {
const index = this.selectedSecondIds.indexOf(secId)
if (index === -1) {
this.selectedSecondIds.push(secId)
} else {
this.selectedSecondIds.splice(index, 1)
}
},
//
selectCourseType(type) {
//
if (this.selectedCourseType === type) {
this.selectedCourseType = ''
} else {
this.selectedCourseType = type
}
},
//
resetBrandFilter() {
this.selectedSecondIds = []
this.applyBrandFilter()
},
resetCourseFilter() {
this.selectedCourseType = ''
this.applyCourseFilter()
},
resetFilter() {
if (this.currentTab === 'brand') {
this.resetBrandFilter()
} else {
this.resetCourseFilter()
}
},
applyFilterAndClose() {
if (this.currentTab === 'brand') {
this.applyBrandFilter()
} else {
this.applyCourseFilter()
}
this.closeFilterPanel()
},
// ID
applyBrandFilter() {
if (!this.rawBrandList.length) {
this.filteredBrandList = []
return
}
if (this.selectedSecondIds.length === 0) {
this.filteredBrandList = [...this.rawBrandList]
} else {
this.filteredBrandList = this.rawBrandList.filter(brand => {
return brand.secondClassIds && brand.secondClassIds.some(id => this.selectedSecondIds.includes(id))
})
}
},
//
applyCourseFilter() {
if (!this.rawCourseList.length) {
this.filteredCourseList = []
return
}
if (!this.selectedCourseType) {
this.filteredCourseList = [...this.rawCourseList]
} else {
this.filteredCourseList = this.rawCourseList.filter(course => {
if (this.selectedCourseType === 'platform') return course.type === '平台课程'
if (this.selectedCourseType === 'brand') return course.type === '品牌课程'
return true
})
}
},
// Tab
loadData() {
if (this.currentTab === 'brand') {
this.loadBrandList()
} else {
this.loadCourseList()
}
},
async loadBrandList() {
if (USE_REAL_API) {
await this.fetchRealBrandList()
} else {
this.mockBrandList()
}
this.applyBrandFilter()
},
async loadCourseList() {
if (USE_REAL_API) {
await this.fetchRealCourseList()
} else {
this.mockCourseList()
}
this.applyCourseFilter()
},
// ============ ============
async fetchRealBrandList() {
try {
// 1.
const res = await request.post('/sj/brand/collectList', { page: 1, limit: 100 })
if ((res.code === 200 || res.state === 1) && res.data && res.data.list) {
// 2. filter(item => item.collect_state === 1)
this.rawBrandList = res.data.list.map(item => {
let tags = []
if (item.franchise_state == 1) tags.push('franchise')
if (item.student_state == 1) tags.push('recruit')
if (tags.length === 0) tags.push('more')
return {
id: item.id,
name: item.name,
bgSrc: item.cover_img || 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/default.jpg',
avatar: item.logo_img || 'https://dummyimage.com/100x100/ccc/fff.png&text=Avatar',
tagKeys: tags,
// 3. split
secondClassIds: item.second_class_ids ? String(item.second_class_ids).split(',').map(Number) : []
}
})
console.log('成功获取品牌收藏列表:', this.rawBrandList)
} else {
this.rawBrandList = []
}
} catch (error) {
console.error('获取品牌收藏失败详情:', error)
}
},
async fetchRealCourseList() {
try {
// 1.
const res = await request.post('/sj/skill/collectList', { page: 1, limit: 100 })
if ((res.code === 200 || res.state === 1) && res.data && res.data.list) {
// 2. filter map
this.rawCourseList = res.data.list.map(item => {
return {
id: item.id,
title: item.name,
coverSrc: item.cover_img,
type: item.link_name || (item.link_type == 1 ? '平台课程' : '品牌课程'),
link_type: item.link_type,
views: item.browse_num || 0,
// 3. 使
time: item.create_time ? this.formatTimeAgo(item.create_time) : '',
typeIcon: item.link_logo || ''
}
})
console.log('成功获取课程收藏列表:', this.rawCourseList)
} else {
this.rawCourseList = []
}
} catch (error) {
console.error('获取课程收藏失败', error)
}
},
formatTimeAgo(timeStr) {
if (!timeStr) return '';
const timeStrSafe = String(timeStr);
const now = new Date()
const time = new Date(timeStrSafe.replace(/-/g, '/'))
const diff = Math.floor((now - time) / 1000)
if (diff < 3600) return Math.floor(diff / 60) + '分钟前'
if (diff < 86400) return Math.floor(diff / 3600) + '小时前'
if (diff < 604800) return Math.floor(diff / 86400) + '天前'
return timeStrSafe.split(' ')[0]
},
goToBrandDetail(item) {
if (item.id) {
uni.navigateTo({ url: `/pages/brand/detail?id=${item.id}` })
}
},
// ============ ============
mockBrandList() {
this.rawBrandList = [
{
id: 1, name: '脸缘品牌', bgSrc: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/e2220528-fdf6-4cad-a25d-ec6cbee1501e.jpg',
avatar: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/e2220528-fdf6-4cad-a25d-ec6cbee1501e.jpg',
tagKeys: ['franchise', 'recruit'], secondClassIds: [76, 77]
},
{
id: 2, name: '美肌工坊', bgSrc: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/e2220528-fdf6-4cad-a25d-ec6cbee1501e.jpg',
avatar: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/e2220528-fdf6-4cad-a25d-ec6cbee1501e.jpg',
tagKeys: ['franchise'], secondClassIds: [78]
},
{
id: 3, name: '悦容堂', bgSrc: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/e2220528-fdf6-4cad-a25d-ec6cbee1501e.jpg',
avatar: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/e2220528-fdf6-4cad-a25d-ec6cbee1501e.jpg',
tagKeys: ['recruit'], secondClassIds: [201, 202]
}
]
},
mockCourseList() {
this.rawCourseList = [
{ id: 1, title: '穿戴甲的操作视频扫码验券操作教程', coverSrc: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/e2220528-fdf6-4cad-a25d-ec6cbee1501e.jpg', type: '平台课程', views: 836000, time: '7小时前' },
{ id: 2, title: '全身SPA的操作视频操作教程', coverSrc: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/e2220528-fdf6-4cad-a25d-ec6cbee1501e.jpg', type: '平台课程', views: 2344, time: '3天前' },
{ id: 3, title: '高级面部护理手法详解', coverSrc: 'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/e2220528-fdf6-4cad-a25d-ec6cbee1501e.jpg', type: '品牌课程', views: 56700, time: '1周前' }
]
}
}
}
</script>
<style lang="scss" scoped>
.collection-page {
min-height: 100vh;
background: #fff;
}
/* 将页面左右 padding 转移到内容区,保证 Tab 背景贯穿全屏 */
.tab-bar-wrap {
position: sticky;
/* 吸顶,保证滑动页面时一直显示在最前面 */
background: #fff;
padding: 10rpx 24rpx;
z-index: 101;
}
.tab-bar {
display: flex;
align-items: center;
justify-content: space-between;
background: transparent;
padding: 10rpx 0;
.tab-items {
display: flex;
align-items: center;
.tab-item {
font-weight: 400;
font-size: 26rpx;
color: #777777;
background: #f5f5f5;
border-radius: 27rpx;
margin-right: 40rpx;
padding: 9rpx 24rpx;
&.active {
color: #FF4767;
background: #FFECEF;
border: 2rpx solid #FFB0BD;
border-radius: 29rpx;
}
}
}
.filter-area {
display: flex;
align-items: center;
padding: 10rpx 0;
.filter-text {
font-size: 24rpx;
color: #666666;
margin-right: 6rpx;
}
.filter-icon {
width: 24rpx;
height: 24rpx;
/* 展开时切换为上下箭头图标,调整大小 */
&.rotate {
width: 18rpx;
height: 12rpx;
}
}
}
}
/* 核心抽屉动画布局 */
.filter-panel {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 100;
/* 低于 Tab区的101保证隐藏时躲在其后 */
pointer-events: none;
/* 面板关闭时,穿透点击到底部列表 */
overflow: hidden;
/* 防止抽屉向上滑出时外溢 */
&.show {
pointer-events: auto;
/* 打开时恢复点击拦截 */
.filter-mask {
opacity: 1;
}
.filter-container {
transform: translateY(50rpx);
/* 滑出 */
}
}
.filter-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.4);
opacity: 0;
transition: opacity 0.3s ease-in-out;
}
.filter-container {
position: absolute;
top: 0;
left: 0;
right: 0;
background: #fff;
border-radius: 0 0 30rpx 30rpx;
padding: 40rpx 30rpx;
max-height: 80vh;
display: flex;
flex-direction: column;
box-shadow: 0 10rpx 20rpx rgba(0, 0, 0, 0.05);
transform: translateY(-100%);
/* 默认隐藏在上方即Tab背后 */
transition: transform 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
}
}
/* 垂直分组设计的样式图3 */
.filter-scroll {
max-height: 50vh;
}
.filter-group {
margin-bottom: 30rpx;
.group-title {
font-weight: 500;
font-size: 28rpx;
color: #333333;
line-height: 40rpx;
margin-bottom: 20rpx;
}
.pill-list {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
}
}
/* 通用胶囊按钮 */
.pill-item {
width: calc(25% - 16rpx);
/* 25%是一行四个,减去间距 */
height: 58rpx;
background: #F5F5F5;
font-weight: 400;
font-size: 26rpx;
color: #333333;
line-height: 37rpx;
/* 使用 flex 居中 */
display: flex;
align-items: center;
justify-content: center;
border-radius: 12rpx;
box-sizing: border-box;
/* 防止 padding 会撑大盒子 */
&.active {
background: #FFF0F2;
color: #FF4767;
border: 1rpx solid #FFB0BD;
}
}
.course-filter {
display: flex;
gap: 20rpx;
/* padding: 20rpx 0 40rpx; */
.course-type-item {
background: #F5F5F5;
color: #666666;
font-size: 26rpx;
padding: 12rpx 40rpx;
border-radius: 12rpx;
&.active {
background: #FFF0F2;
color: #FF4767;
}
}
}
.filter-footer {
display: flex;
justify-content: space-between;
margin-top: 20rpx;
padding-top: 20rpx;
/* border-top: 1rpx solid #F5F5F5; */
.reset-btn,
.confirm-btn {
width: 47%;
text-align: center;
padding: 20rpx 0;
border-radius: 40rpx;
font-size: 28rpx;
}
.reset-btn {
background: #F5F5F5;
color: #666;
}
.confirm-btn {
background: #FF4767;
color: #fff;
}
}
/* 内容区缩进补充 */
.collection-content {
padding: 0 24rpx 24rpx;
margin-top: 20rpx;
overflow: hidden;
}
.brand-grid {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
.brand-card-wrap {
width: 49.2%;
margin-bottom: 20rpx;
}
}
.course-list {
display: flex;
flex-direction: column;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx 0;
.empty-icon {
width: 346rpx;
height: 373rpx;
}
}
::v-deep .icon-headle {
width: 36rpx !important;
height: 36rpx !important;
}
</style>

View File

@ -400,7 +400,7 @@ export default {
left: 60rpx;
width: 630rpx;
height: 96rpx;
background: #E8101E;
background: #FF4767;
border-radius: 58rpx;
display: flex;
align-items: center;
@ -499,7 +499,7 @@ export default {
}
.region-tab.active {
color: #E8101E;
color: #FF4767;
font-weight: 500;
}
@ -511,7 +511,7 @@ export default {
transform: translateX(-50%);
width: 40rpx;
height: 4rpx;
background-color: #E8101E;
background-color: #FF4767;
border-radius: 2rpx;
}
@ -552,7 +552,7 @@ export default {
}
.popup-btn.confirm {
background: linear-gradient(90deg, #E8101E 0%, #FF618F 100%);
background: linear-gradient(90deg, #FF4767 0%, #FF618F 100%);
}
.popup-btn .btn-text {

356
pages/notice/detail.vue Normal file
View File

@ -0,0 +1,356 @@
<template>
<view class="notice-detail-page">
<!-- 自定义顶部导航组件 -->
<custom-navbar
title="公告通知详情"
:showBack="true"
backgroundColor="linear-gradient(180deg, #E6ECFB 0%, #FFFFFF 100%)"
:showHeadle="true"
borderBottom="none"
/>
<!-- 页面主要内容 -->
<view class="page-content">
<!-- 加载状态 -->
<view v-if="loading" class="loading-container">
<view class="loading-icon"></view>
<text class="loading-text">加载中...</text>
</view>
<!-- 详情内容 -->
<view v-else-if="detailData.id" class="detail-card">
<!-- 标题 -->
<text class="detail-title">{{ detailData.title }}</text>
<!-- 发布时间 -->
<view class="detail-time">
<text class="time-text">时间{{ formatTime(detailData.create_time) }}</text>
<text class="browse-text"> <image class="browse-icon" src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/6133cbef-5861-40f8-80f5-d8a86477c94f.png" mode="aspectFit"></image> {{ detailData.browse_num }}</text>
</view>
<!-- 富文本内容 -->
<view class="detail-content">
<rich-text :nodes="detailData.content"></rich-text>
</view>
</view>
<!-- 空状态或错误 -->
<view v-else-if="!loading && !detailData.id" class="empty-container">
<image class="empty-img" src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/8c5e7b2a-3d4f-4b1e-9a7c-6e8f2d1b4a5c.png" mode="aspectFit"></image>
<text class="empty-text">公告不存在或已删除</text>
</view>
</view>
</view>
</template>
<script>
import CustomNavbar from '@/components/custom-navbar/custom-navbar.vue';
import request from '@/utils/request';
export default {
components: {
CustomNavbar
},
data() {
return {
loading: false, //
detailData: { //
id: null,
title: '',
create_time: '',
content: ''
}
};
},
onLoad(options) {
const id = options.id;
if (id) {
this.fetchNoticeDetail(id);
} else {
uni.showToast({
title: '参数错误',
icon: 'none'
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
}
},
methods: {
//
fetchNoticeDetail(id) {
this.loading = true;
request.post('/sj/notice/details', { id: id })
.then(res => {
if (res.code === 200 && res.data) {
this.detailData = res.data;
//
uni.setNavigationBarTitle({
title: this.detailData.title || '公告详情'
});
} else {
uni.showToast({
title: res.msg || '获取详情失败',
icon: 'none'
});
}
})
.catch(err => {
console.error('获取公告详情失败:', err);
uni.showToast({
title: '网络异常,请稍后重试',
icon: 'none'
});
})
.finally(() => {
this.loading = false;
});
},
formatTime(timeStr) {
if (!timeStr) return '--:--:--';
let formatted = timeStr;
if (timeStr.includes('.') && !timeStr.includes('-')) {
const match = timeStr.match(/(\d{4})\.(\d{2})\.(\d{2})(\d{2}:\d{2}:\d{2})/);
if (match) {
formatted = `${match[1]}-${match[2]}-${match[3]} ${match[4]}`;
} else {
formatted = timeStr.replace(/\./g, '-');
}
}
//
if (formatted.includes('-') && formatted.includes(':')) {
// : 2026.05.08 15:00:00
const datePart = formatted.split(' ')[0];
const timePart = formatted.split(' ')[1] || '';
if (datePart && datePart.includes('-')) {
const dotDate = datePart.replace(/-/g, '.');
return `${dotDate} ${timePart}`.trim();
}
return formatted;
}
return timeStr;
},
}
};
</script>
<style lang="scss" scoped>
.notice-detail-page {
min-height: 100vh;
background-color: #fff;
}
.page-content {
// padding: 20rpx 24rpx 60rpx;
}
//
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx 0;
.loading-icon {
width: 60rpx;
height: 60rpx;
border: 4rpx solid #e0e0e0;
border-top-color: #FF4767;
border-radius: 50%;
animation: spin 0.8s linear infinite;
margin-bottom: 20rpx;
}
.loading-text {
font-size: 26rpx;
color: #999;
}
}
@keyframes spin {
to { transform: rotate(360deg); }
}
//
.detail-card {
background: #ffffff;
border-radius: 24rpx;
padding: 20rpx 30rpx;
margin-bottom: 30rpx;
.detail-title {
display: block;
margin-bottom: 14rpx;
font-weight: 500;
font-size: 40rpx;
color: #333333;
line-height: 56rpx;
}
.detail-time {
display: flex;
align-items: center;
margin-bottom: 30rpx;
justify-content: space-between; //
// border-bottom: 1rpx solid #f0f0f0;
.time-icon {
width: 28rpx;
height: 28rpx;
margin-right: 10rpx;
opacity: 0.6;
}
.time-text {
font-weight: 400;
font-size: 28rpx;
color: #B6B6B6;
line-height: 40rpx;
}
.browse-icon {
width: 38rpx;
height: 22rpx;
// margin-right: 10rpx;
}
.browse-text {
margin-right: 10rpx;
font-weight: 400;
font-size: 28rpx;
color: #B08463;
line-height: 40rpx;
}
}
.detail-content {
font-weight: 400;
font-size: 28rpx;
color: #2A2A2A;
line-height: 40rpx;
overflow-x: hidden;
// rich-text
::v-deep img {
max-width: 100%;
height: auto;
display: block;
margin: 30rpx 0;
}
::v-deep p {
margin-bottom: 20rpx; //
padding: 0;
}
//
::v-deep br {
display: block; // <br>
margin-bottom: 30rpx; //
}
}
}
//
.empty-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 150rpx 0;
.empty-img {
width: 200rpx;
height: 200rpx;
margin-bottom: 30rpx;
opacity: 0.6;
}
.empty-text {
font-size: 28rpx;
color: #999;
}
}
//
.member-card {
background: linear-gradient(135deg, #FFE6E8 0%, #FFF0E6 100%);
border-radius: 24rpx;
padding: 30rpx;
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 20rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
.member-info {
flex: 1;
.member-price {
display: block;
font-size: 32rpx;
font-weight: bold;
color: #FF4767;
line-height: 44rpx;
margin-bottom: 8rpx;
}
.member-desc {
display: block;
font-size: 24rpx;
color: #333;
line-height: 34rpx;
margin-bottom: 12rpx;
}
.member-tag {
display: flex;
align-items: center;
.tag-text, .tag-gift {
font-size: 20rpx;
background: rgba(255, 71, 103, 0.15);
padding: 4rpx 12rpx;
border-radius: 20rpx;
color: #FF4767;
margin-right: 12rpx;
}
.tag-gift {
background: rgba(255, 193, 7, 0.2);
color: #E6A100;
}
}
}
.member-btn {
background: linear-gradient(135deg, #FF4767, #FF6B8A);
border-radius: 48rpx;
padding: 14rpx 28rpx;
text-align: center;
min-width: 160rpx;
box-shadow: 0 4rpx 12rpx rgba(255, 71, 103, 0.3);
.btn-text {
display: block;
font-size: 26rpx;
font-weight: 600;
color: #FFFFFF;
line-height: 36rpx;
}
.btn-sub {
display: block;
font-size: 20rpx;
color: rgba(255, 255, 255, 0.9);
line-height: 28rpx;
}
}
}
::v-deep .icon-headle {
width: 32rpx !important;
height: 32rpx !important;
}
</style>

View File

@ -0,0 +1,213 @@
<template>
<view class="notice-page">
<!-- 自定义顶部导航组件 -->
<custom-navbar title="公告通知" :showBack="true" backgroundColor="#FFFFFF" :show-headle="true"
headleSrc="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/51ebca5d-95c5-4fb8-8aa3-560a79100d25.png"
@onHeadleClick="goToSearch" class="nav-img-size" />
<view class="page-content">
<!-- 加载状态 -->
<view v-if="loading" class="loading-container">
<view class="loading-icon"></view>
<text class="loading-text">加载中...</text>
</view>
<!-- 公告列表 -->
<view v-else-if="noticeList.length > 0" class="notice-list">
<NoticeCard v-for="item in noticeList" :key="item.id" :item="item" @click="goToDetail" />
<!-- 加载更多状态 -->
<view v-if="loadingMore" class="load-more">
<text>加载更多...</text>
</view>
<view v-else-if="!hasMore && noticeList.length > 0" class="no-more">
<text>没有更多了</text>
</view>
</view>
<!-- 空状态 -->
<view v-else class="empty-container">
<image class="empty-img"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/8c5e7b2a-3d4f-4b1e-9a7c-6e8f2d1b4a5c.png"
mode="aspectFit"></image>
<text class="empty-text">暂无公告通知</text>
</view>
</view>
</view>
</template>
<script>
import CustomNavbar from '@/components/custom-navbar/custom-navbar.vue';
import NoticeCard from '@/pages/home/components/notice-card.vue';
import request from '@/utils/request';
export default {
components: {
CustomNavbar,
NoticeCard //
},
data() {
return {
loading: false,
loadingMore: false,
noticeList: [],
page: 1,
limit: 10,
total: 0,
hasMore: true
};
},
onLoad() {
this.fetchNoticeList(true);
},
onPullDownRefresh() {
this.fetchNoticeList(true);
},
onReachBottom() {
if (!this.loadingMore && this.hasMore && !this.loading) {
this.fetchNoticeList(false);
}
},
methods: {
//
goToSearch() {
uni.navigateTo({
url: '/pages/search_common/search?type=notice'
});
},
//
fetchNoticeList(reset = true) {
if (reset) {
this.page = 1;
this.hasMore = true;
this.loading = true;
} else {
if (!this.hasMore) return;
this.loadingMore = true;
// +1
this.page++;
}
request.post('/sj/notice/list', {
page: this.page,
limit: this.limit
}).then(res => {
if (res.code === 200 && res.data) {
const list = res.data.list || [];
const total = res.data.total || 0;
this.total = total;
if (reset) {
this.noticeList = list;
} else {
this.noticeList = [...this.noticeList, ...list];
}
//
this.hasMore = this.noticeList.length < total;
} else {
if (reset) this.noticeList = [];
this.hasMore = false;
//
if (!reset && this.page > 1) this.page--;
}
}).catch(err => {
console.error('获取公告列表失败:', err);
if (!reset && this.page > 1) this.page--; //
uni.showToast({
title: '加载失败,请稍后重试',
icon: 'none'
});
}).finally(() => {
this.loading = false;
this.loadingMore = false;
if (reset) {
uni.stopPullDownRefresh();
}
});
},
// NoticeCard $emit item
goToDetail(item) {
if (item.id) {
uni.navigateTo({
url: `/pages/notice/detail?id=${item.id}`
});
}
}
}
};
</script>
<style lang="scss" scoped>
.notice-page {
min-height: 100vh;
background-color: #f5f6f8;
}
.page-content {
padding: 20rpx 20rpx 40rpx;
}
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx 0;
.loading-icon {
width: 60rpx;
height: 60rpx;
border: 4rpx solid #e0e0e0;
border-top-color: #FF4767;
border-radius: 50%;
animation: spin 0.8s linear infinite;
margin-bottom: 20rpx;
}
.loading-text {
font-size: 26rpx;
color: #999;
}
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.load-more,
.no-more {
text-align: center;
padding: 30rpx 0;
font-size: 24rpx;
color: #999;
}
.empty-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 150rpx 0;
.empty-img {
width: 200rpx;
height: 200rpx;
margin-bottom: 30rpx;
opacity: 0.6;
}
.empty-text {
font-size: 28rpx;
color: #999;
}
}
.nav-img-size::v-deep .icon-headle {
width: 36rpx !important;
height: 36rpx !important;
}
</style>

View File

@ -275,18 +275,18 @@ export default {
.rating-score {
font-size: 16rpx;
color: #E8101E;
color: #FF4767;
margin-left: 68rpx;
}
.currency {
font-size: 24rpx;
color: #E8101E;
color: #FF4767;
}
.amount {
font-size: 32rpx;
color: #E8101E;
color: #FF4767;
font-weight: 500;
}
@ -328,7 +328,7 @@ export default {
}
.load-date {
font-size: 24rpx;
color: #E8101E;
color: #FF4767;
}
.load-text {

View File

@ -146,9 +146,6 @@
</view>
</view>
</view>
</template>
<script>
@ -716,7 +713,7 @@ page {
.real-price {
font-size: 32rpx;
font-weight: 500;
color: #E8101E;
color: #FF4767;
}
/* 订单信息卡片 */
.order-card {
@ -788,13 +785,13 @@ page {
margin-left: 20rpx;
}
.btn-border {
border: 1rpx solid #E8101E;
border: 1rpx solid #FF4767;
background-color: #fff;
box-sizing: border-box;
margin-left: 20rpx;
}
.btn-bg {
background-color: #E8101E;
background-color: #FF4767;
}
.button-text {
font-size: 28rpx;
@ -804,7 +801,7 @@ page {
.btn-border .button-text {
font-size: 28rpx;
font-weight: 400;
color: #E8101E;
color: #FF4767;
}
/* APP适配 */
/* #ifdef APP-PLUS */
@ -827,4 +824,4 @@ page {
padding-top: calc(var(--status-bar-height) + env(safe-area-inset-top));
}
/* #endif */
</style>
</style>

View File

@ -24,7 +24,7 @@
<image :src="item.icon" mode="aspectFit" class="method-icon"></image>
<text class="method-name">{{ item.name }}</text>
</view>
<agree-radio :isAgree="item.selected" :yesImg="`/static/images/icons/yes_icon.png`" :radioSize="`40rpx`" :noImg="`/static/images/icons/no_icon.png`"></agree-radio>
<agree-radio :isAgree="item.selected" :yesImg="`/static/images/agree_y.png`" :radioSize="`40rpx`" :noImg="`/static/images/icons/no_icon.png`"></agree-radio>
</view>
</view>
</view>
@ -494,7 +494,7 @@
::v-deep .uv-count-down__text {
font-weight: 400;
font-size: 26rpx;
color: #E8101E;;
color: #FF4767;;
line-height: 37rpx;
text-align: center;
font-style: normal;
@ -559,13 +559,13 @@
.total-amount .symbol {
font-size: 24rpx;
font-weight: 500;
color: #E8101E;
color: #FF4767;
}
.total-amount .amount {
font-size: 28rpx;
font-weight: 500;
color: #E8101E;
color: #FF4767;
}
.amount-lable {
@ -576,7 +576,7 @@
.pay-btn {
width: 680rpx;
height: 88rpx;
background: linear-gradient( 180deg, #F52540 0%, #E8101E 100%);
background: linear-gradient( 180deg, #F52540 0%, #FF4767 100%);
border-radius: 43rpx;
font-weight: 500;
font-size: 30rpx;

View File

@ -623,7 +623,7 @@ export default {
height: 28rpx;
border: 3rpx;
margin-right: 16rpx;
background-color: #E8101E;
background-color: #FF4767;
}
.service-item {
@ -766,7 +766,7 @@ export default {
.price-total .price {
font-size: 28rpx;
font-weight: 500;
color: #E8101E;
color: #FF4767;
}
.amount-lable {
@ -777,7 +777,7 @@ export default {
.submit-btn {
width: 492rpx;
height: 64rpx;
background: #E8101E;
background: #FF4767;
border-radius: 32rpx;
color: #ffffff;
font-size: 28rpx;
@ -984,8 +984,8 @@ export default {
.isActive {
background: rgba(252, 67, 124, 0.08);
border-radius: 10rpx;
border: 1rpx solid #E8101E;
color: #E8101E;
border: 1rpx solid #FF4767;
color: #FF4767;
/* 选中状态 */
}
@ -1010,7 +1010,7 @@ export default {
&__box {
width: 648rpx;
height: 94rpx;
background: linear-gradient(140deg, #fa4e9a 0%, #E8101E 100%);
background: linear-gradient(140deg, #fa4e9a 0%, #FF4767 100%);
border-radius: 48rpx;
display: flex;
align-items: center;

View File

@ -18,7 +18,7 @@
<view class="group-invite"
v-if="orderInfo.state !=1 && orderInfo.team_buy_id && (orderInfo.team_buy_order_state==1||orderInfo.team_buy_order_state==2)">
<view class="invite-text" v-if="orderInfo.team_buy_order_state == 1">参团成功需邀请<text
style="color: #E8101E">{{orderInfo.team_buy.person_num - orderInfo.team_buy.pay_person_num}}</text>人才可完成拼团
style="color: #FF4767">{{orderInfo.team_buy.person_num - orderInfo.team_buy.pay_person_num}}</text>人才可完成拼团
</view>
<view class="invite-text" v-if="orderInfo.team_buy_order_state == 2">参团成功已拼团成功</view>
@ -266,16 +266,16 @@
formatPriceNumber(this.orderInfo.cost_money-this.orderInfo.order_money),
18,
2
)}rpx;color: #E8101E;line-height: 25rpx;text-align: left;font-style: normal;`" />
)}rpx;color: #FF4767;line-height: 25rpx;text-align: left;font-style: normal;`" />
</l-painter-view>
<l-painter-view css="margin-top:44rpx;">
<l-painter-text text="¥"
css="font-weight: 500;font-size: 26rpx;color: #E8101E;line-height: 48rpx;text-align: left;font-style: normal;" />
css="font-weight: 500;font-size: 26rpx;color: #FF4767;line-height: 48rpx;text-align: left;font-style: normal;" />
<l-painter-text :text=" formatPriceNumber(this.orderInfo.order_money)" :css="`font-weight: 500;font-size:${formatPriceSize(
formatPriceNumber(this.orderInfo.order_money),
56,
3
)}rpx;color: #E8101E;line-height: 34rpx;text-align: left;font-style: normal;`" />
)}rpx;color: #FF4767;line-height: 34rpx;text-align: left;font-style: normal;`" />
</l-painter-view>
<l-painter-view css="margin-top:0rpx;">
<l-painter-text :text="`原价¥${formatPriceNumber(this.orderInfo.cost_money)}`" :css="`text-decoration: line-through;font-weight: 400;font-size:${formatPriceSize(
@ -1005,7 +1005,7 @@
.phone-btn {
width: 64rpx;
height: 64rpx;
background-color: #E8101E;
background-color: #FF4767;
border-radius: 32rpx;
}
@ -1249,7 +1249,7 @@
.service-price2 {
font-weight: 500;
font-size: 24rpx;
color: #E8101E;
color: #FF4767;
line-height: 34rpx;
text-align: left;
font-style: normal;
@ -1282,7 +1282,7 @@
font-family: DINPro, DINPro;
font-weight: 500;
font-size: 36rpx;
color: #E8101E;
color: #FF4767;
line-height: 34rpx;
text-align: left;
font-style: normal;
@ -1352,7 +1352,7 @@
}
.btn-bg {
background-color: #E8101E;
background-color: #FF4767;
}
.button-text {
@ -1444,7 +1444,7 @@
align-items: center;
font-weight: 500;
font-size: 24rpx;
color: #E8101E;
color: #FF4767;
line-height: 33rpx;
text-align: left;
font-style: normal;

File diff suppressed because it is too large Load Diff

View File

@ -15,7 +15,7 @@
<view>
<view class="serve-card-right__content__title">{{item.title}}</view>
<view class="serve-card-right__content__price"><text
style="color: #E8101E;font-weight: 500;">¥{{item.server_price}} </text> {{' '}}|
style="color: #FF4767;font-weight: 500;">¥{{item.server_price}} </text> {{' '}}|
{{item.server_time}}分钟
</view>
</view>

View File

@ -25,7 +25,7 @@
<text class="service-name">{{ item.server_title }}</text>
<text class="order-time">下单时间{{ item.pay_time }}</text>
<text class="order-status">
订单状态<text style="color: #E8101E;">{{ getDescriptionByNumber(item.state) }}</text>
订单状态<text style="color: #FF4767;">{{ getDescriptionByNumber(item.state) }}</text>
</text>
</view>
</view>

View File

@ -1,7 +1,7 @@
<template>
<view class="promotion">
<custom-navbar title="推广服务" :leftImg="'/static/images/whiteBack.png'" :showUser="true"
backgroundColor="linear-gradient( 134deg, #F52540 0%, #E8101E 100%)" titleColor="#fff"
backgroundColor="linear-gradient( 134deg, #F52540 0%, #FF4767 100%)" titleColor="#fff"
borderBottom="none"></custom-navbar>
<view class="promotion-search">
<view class="promotion-add">
@ -224,7 +224,7 @@
<style lang="less">
.promotion {
.promotion-search {
background: linear-gradient(134deg, #f52540 0%, #e8101e 100%);
background: linear-gradient(134deg, #f52540 0%, #FF4767 100%);
box-sizing: border-box;
padding: 31rpx 20rpx 60rpx 20rpx;
@ -292,7 +292,7 @@
position: relative;
&.active {
color: #E8101E;
color: #FF4767;
&::after {
content: '';
@ -325,7 +325,7 @@
width: 100%;
font-weight: 400;
font-size: 22rpx;
color: #E8101E;
color: #FF4767;
line-height: 30rpx;
text-align: left;
font-style: normal;
@ -374,7 +374,7 @@
border: 1rpx solid rgba(232, 16, 30, 0.2);
font-weight: 400;
font-size: 24rpx;
color: #E8101E;
color: #FF4767;
line-height: 33rpx;
text-align: left;
font-style: normal;
@ -398,7 +398,7 @@
font-family: DINPro, DINPro;
font-weight: 500;
font-size: 36rpx;
color: #E8101E;
color: #FF4767;
line-height: 46rpx;
text-align: left;
font-style: normal;
@ -468,12 +468,12 @@
}
.closeModal {
border: 2rpx solid #E8101E;
color: #E8101E;
border: 2rpx solid #FF4767;
color: #FF4767;
}
.sureModal {
background: #E8101E;
background: #FF4767;
border-radius: 40rpx;
color: #FFFFFF;
}

View File

@ -2,7 +2,7 @@
<view class="select-serve">
<custom-navbar title="选择服务" backgroundColor="#FFFFFF"></custom-navbar>
<view class="select-tip">
<text style="color: #E8101E;">温馨提示</text>您可自主勾选需推广的服务灵活设置比例形式的推广佣金
<text style="color: #FF4767;">温馨提示</text>您可自主勾选需推广的服务灵活设置比例形式的推广佣金
配置完成后服务将自动进入推广平台曝光触达更多潜在客户后续通过推广渠道如达人链接分享码成功成交的订单
系统会从该笔订单中自动扣除您预设的佣金全程无需手动操作
</view>
@ -85,7 +85,7 @@
&__btn {
width: 690rpx;
height: 78rpx;
background: #E8101E;
background: #FF4767;
border-radius: 798rpx;
display: flex;
align-items: center;

View File

@ -2,7 +2,7 @@
<view class="select-serve">
<custom-navbar title="设置佣金" backgroundColor="#FFFFFF"></custom-navbar>
<view class="select-tip">
<text style="color: #E8101E;">温馨提示</text>支持 快速设置百分比如每单返 2%佣金成功成交的订单系统会按照实付金额*佣金比例进行扣减
<text style="color: #FF4767;">温馨提示</text>支持 按1%~30%区间设置佣金比例订单成功成交后系统将按实付金额 × 佣金比例自动扣减
</view>
<view v-for="(item,index) in selectedItems" :key="index" class="serve-card">
<image :src="item.photo[0]" mode="aspectFill" class="serve-card__img"></image>
@ -34,7 +34,7 @@
<uv-modal ref="modal" title="提示" width="620rpx">
<view class="slot-content">
预估佣金按<text style="color: #E8101E;">订单实付金额 × 佣金比例</text>计算因服务实付金额受活动参与程度影响存在波动最终佣金以订单实际结算结果为准
预估佣金按<text style="color: #FF4767;">订单实付金额 × 佣金比例</text>计算因服务实付金额受活动参与程度影响存在波动最终佣金以订单实际结算结果为准
</view>
<template v-slot:confirmButton>
<view type="default" @click="closeModal" class="closeModal">确定</view>
@ -271,7 +271,7 @@
font-family: DINPro, DINPro;
font-weight: 500;
font-size: 22rpx;
color: #E8101E;
color: #FF4767;
line-height: 34rpx;
text-align: left;
font-style: normal;
@ -328,7 +328,7 @@
&__btn {
width: 690rpx;
height: 78rpx;
background: #E8101E;
background: #FF4767;
border-radius: 798rpx;
display: flex;
align-items: center;
@ -347,7 +347,7 @@
.closeModal {
width: 556rpx;
height: 80rpx;
background: #E8101E;
background: #FF4767;
border-radius: 40rpx;
font-weight: 500;
font-size: 28rpx;

Some files were not shown because too many files have changed in this diff Show More