Compare commits

...

236 Commits

Author SHA1 Message Date
小李 a7849b78a7 三角洲 2026-07-28 14:54:13 +08:00
小李 5d4c0d30e0 优化 2026-07-25 17:56:39 +08:00
小李 bd3a2e9cce 安卓沉浸适配 2026-07-25 17:20:57 +08:00
小李 d9459b1bd0 接口加密 2026-07-20 10:10:55 +08:00
tangxinyue 738f058281 1.携程火车票已出行状态
2.解决和平精英,无畏锲约选择图片后为关闭选项问题
3.转账,支付成功页面优化导航
2026-07-16 16:30:19 +08:00
tangxinyue a215b9b263 编辑数据后重新刷新海报 2026-07-10 13:51:02 +08:00
tangxinyue deca26c218 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-07-10 11:51:44 +08:00
tangxinyue ca06240e86 充值来源 2026-07-10 11:50:57 +08:00
小李 9172ced172 版本号 2026-07-10 11:50:35 +08:00
小李 8c96536d11 工资条底部样式 2026-07-10 10:07:13 +08:00
tangxinyue 6242e47490 优化游戏界面 2026-07-09 14:43:11 +08:00
tangxinyue 2c28dd5cfd Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-07-08 14:41:11 +08:00
tangxinyue 80ae3c1e1e 完成和平精英 无畏契约游戏界面 2026-07-08 14:40:48 +08:00
小李 c933a2c663 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-07-08 10:37:55 +08:00
小李 007f0c7238 工资单新增样式,广东高考理科 2026-07-08 10:37:45 +08:00
tangxinyue 1b65430bd2 修改支付成功页面 2026-07-07 09:14:19 +08:00
tangxinyue a2ab06de2b 完成支付成功页面 2026-07-01 17:35:22 +08:00
小李 3467d370ca 优化 2026-06-30 16:25:07 +08:00
tangxinyue 125e6694d6 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-06-30 16:17:22 +08:00
tangxinyue f842ab9d6a 更改图片 2026-06-30 16:17:01 +08:00
小李 b47742884b 图片下载 2026-06-30 14:57:16 +08:00
小李 7bfa56cf5b Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-06-30 14:11:26 +08:00
小李 597ace78aa 图片优化 2026-06-30 14:11:18 +08:00
tangxinyue 2f57b85609 高铁票出发时间改不了年月 2026-06-30 13:43:09 +08:00
小李 ccdab994a8 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1
# Conflicts:
#	main.js
2026-06-26 15:00:42 +08:00
小李 d985f0dd25 优化 2026-06-26 14:59:44 +08:00
tangxinyue cce9d63ef1 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-06-26 14:58:59 +08:00
tangxinyue 8ce81540d9 短信华为机型时间高亮 2026-06-26 14:58:30 +08:00
小李 76a25d329b Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-06-26 11:00:13 +08:00
小李 8791695869 兑换码 2026-06-26 11:00:05 +08:00
tangxinyue c7496e73e1 优化转账页面 2026-06-26 10:42:09 +08:00
tangxinyue 3b2396ec5c 1.解决华为机型短信发送消息过长输入框变形问题 2026-06-23 14:25:19 +08:00
tangxinyue b90c0c6774 优化视频聊天页 2026-06-18 17:17:59 +08:00
tangxinyue fe48026d19 优化视频通话 2026-06-18 15:00:42 +08:00
小李 476acc7571 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-06-18 14:00:52 +08:00
tangxinyue c625b3472f Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-06-18 13:42:54 +08:00
tangxinyue 7aec27d81b 微信视频通话 2026-06-18 13:42:30 +08:00
小李 bc0c917efa 工资条和高考开关控制 2026-06-18 10:24:37 +08:00
小李 bc53f9552e Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-06-18 09:44:42 +08:00
小李 d047299489 工商银行优化 2026-06-18 09:44:20 +08:00
tangxinyue 82e922eab0 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1
# Conflicts:
#	main.js
2026-06-17 17:04:45 +08:00
tangxinyue b3f3f33c90 完成三角洲 视频单聊通话 修改微信支付页面 2026-06-17 17:04:02 +08:00
小李 906293b350 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-06-16 17:22:27 +08:00
小李 ee086eec01 充值页面配置 2026-06-16 17:22:21 +08:00
tangxinyue 77c3590e5b Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-06-15 13:45:31 +08:00
tangxinyue 1f71a0240d 修改淘宝交易成功 2026-06-15 13:44:40 +08:00
小李 c6a3e2072d 优化重庆 2026-06-12 17:16:59 +08:00
小李 86281a76b6 优化 2026-06-12 17:06:40 +08:00
小李 e7705b1162 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-06-12 16:51:25 +08:00
小李 4e16330877 优化 2026-06-12 16:51:19 +08:00
tangxinyue dbc666a90d 修改退款图片展示宽高 2026-06-12 16:49:12 +08:00
tangxinyue 4d15da3c52 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-06-12 16:32:59 +08:00
tangxinyue 9e3926548e 修改淘宝退款样式 2026-06-12 16:32:35 +08:00
小李 f67ac5b73a Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-06-12 16:16:45 +08:00
小李 d234c1a48a 优化高考 2026-06-12 16:16:40 +08:00
tangxinyue 4b1d7c168d 替换小宝转账banner图片 2026-06-12 16:08:42 +08:00
tangxinyue 34a6810681 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-06-12 15:39:49 +08:00
tangxinyue 8657afdbd7 修改王者荣耀裁剪图片方式 2026-06-12 15:39:01 +08:00
小李 e1775a67e3 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-06-12 14:49:32 +08:00
小李 ab1b4b48c8 高考 2026-06-12 14:49:22 +08:00
tangxinyue 9c5f267b5f 修改淘宝退款页面样式间距 2026-06-12 14:48:13 +08:00
tangxinyue d54877fb85 修改转账页面,可切换图片区域样式,编辑优惠券内容 2026-06-11 11:12:00 +08:00
tangxinyue b1f41cfcbb 完成微信支付页面 2026-06-09 17:41:28 +08:00
tangxinyue 503a4e1194 修改淘宝退款详情页,变更支付宝首页布局,新增视频教程页 2026-06-09 15:48:23 +08:00
tangxinyue 4fcbc5b06f 王者荣耀 2026-06-05 16:04:25 +08:00
tangxinyue bbfe66093e Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-06-05 14:39:33 +08:00
tangxinyue bf57816244 修改王者主页 2026-06-05 14:38:37 +08:00
小李 ebd2c2111c Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-06-05 14:38:14 +08:00
小李 cb56b3ae7e 日志 2026-06-05 14:38:03 +08:00
tangxinyue e501d35d6b Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-06-05 11:30:13 +08:00
tangxinyue aea48b6c58 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1
# Conflicts:
#	main.js
2026-06-05 11:30:03 +08:00
小李 216af408cc 图片 2026-06-05 11:28:23 +08:00
tangxinyue 65270811da 完成王者主页页面 2026-06-05 11:24:43 +08:00
小李 507973d7dc 充值页 2026-06-05 11:15:33 +08:00
小李 45b2bf3f7f 通话详情去掉默认图片 2026-06-04 14:11:33 +08:00
小李 0616283418 通话和银行卡优化 2026-06-04 14:10:28 +08:00
小李 a47cc2a9cd Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-06-04 10:02:48 +08:00
小李 b560d901ab 通话详情ios,工商银行,建设银行,农业银行 2026-06-04 10:02:38 +08:00
tangxinyue 6569f821e8 完成短信(苹果,华为,小米,vivo)各个机型发送图片功能,预览图片页面及图片信息列表展示样式 2026-06-04 09:55:08 +08:00
tangxinyue 1cd2226f2b 修改淘宝京东618购物图片,12306底部banner 2026-06-01 11:48:20 +08:00
tangxinyue 8ec9c844f0 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-06-01 10:03:49 +08:00
tangxinyue cffb2c29fb 修改未发送图片名称 2026-06-01 10:03:09 +08:00
小李 72674f4d77 优化 2026-06-01 09:57:56 +08:00
小李 73b2b3cf25 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-05-30 17:32:13 +08:00
小李 a6074b340b 优化 2026-05-30 17:32:06 +08:00
tangxinyue 37cf237ff1 1 2026-05-30 16:24:07 +08:00
tangxinyue 7836f9855a 修改安卓底部导航栏颜色 2026-05-30 16:16:50 +08:00
tangxinyue c9b393b117 1 2026-05-30 15:50:33 +08:00
tangxinyue 7eb68e8c15 修改618图标间距 2026-05-30 15:47:31 +08:00
tangxinyue 788f4e6b5e 1 2026-05-30 15:35:53 +08:00
tangxinyue c1e4e7be24 增加菜单长按时长 2026-05-30 15:30:15 +08:00
tangxinyue 5dbb4add21 pdd添加默认数据拼单头像 2026-05-30 15:23:13 +08:00
tangxinyue 724138f672 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-05-30 15:12:07 +08:00
tangxinyue 85996e19b1 优化pdd商品价格可输入小数点 2026-05-30 15:11:45 +08:00
小李 dab4d15c24 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-05-30 14:52:57 +08:00
小李 88ebca65e7 通话优化,银行卡优化 2026-05-30 14:52:51 +08:00
tangxinyue 033a3bd9ba pdd添加优惠卷和618活动券显示 2026-05-30 14:39:16 +08:00
tangxinyue e3e29f7f18 优化调整pdd购物 2026-05-30 11:18:21 +08:00
小李 5e02decbf5 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-05-30 09:28:16 +08:00
小李 d72a0da52c 优化 2026-05-30 09:28:11 +08:00
tangxinyue ce76f694b8 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1
# Conflicts:
#	main.js
2026-05-29 18:33:25 +08:00
tangxinyue 43efe376ec 完成pdd购物模块 2026-05-29 18:32:20 +08:00
小李 9210162f17 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-05-29 15:30:14 +08:00
小李 dc692498db 通话详情,招商银行,61主题 2026-05-29 15:30:08 +08:00
tangxinyue 8eca8cc6ce Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-05-27 17:55:32 +08:00
小李 b0af3d1d5e Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-05-27 17:55:19 +08:00
tangxinyue 4b8dc92739 oppo短信图片预览 2026-05-27 17:55:11 +08:00
小李 4cb094c272 优化通话 2026-05-27 17:55:02 +08:00
tangxinyue 0cef1a2016 oppo短信发送图片 2026-05-27 16:59:04 +08:00
tangxinyue ca12683f7e Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1
# Conflicts:
#	main.js
2026-05-27 16:48:30 +08:00
tangxinyue f0bb586b14 oppp短信发送图片 2026-05-27 16:47:38 +08:00
小李 1d87a13e69 版本号 2026-05-27 16:38:52 +08:00
小李 86434ab815 通话详情页 2026-05-27 16:14:34 +08:00
tangxinyue 1d4fd42cbb 优化 2026-05-25 09:40:19 +08:00
小李 eb59411bda 工资条水印跳转 2026-05-21 17:54:56 +08:00
tangxinyue cfe224cf83 修改淘宝闪购充值来源名称 2026-05-21 17:49:45 +08:00
tangxinyue d04cf2cd2f Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-05-21 17:30:19 +08:00
tangxinyue ce4a66ac3c 添加水印,计算进行中闪购订单 2026-05-21 17:30:09 +08:00
小李 865c3e5715 证书,锦旗加上水印 2026-05-21 17:29:30 +08:00
tangxinyue 4cb7784d4a 完成外卖列表详情 2026-05-21 16:31:33 +08:00
tangxinyue 1bb582d51a 完成关于本机页面上滑毛玻璃效果 2026-05-19 16:34:22 +08:00
tangxinyue 2d488612b4 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-05-19 14:21:14 +08:00
tangxinyue b7e2889875 完成关于本机 2026-05-19 14:20:16 +08:00
小李 5b40a807e4 充值页优化 2026-05-19 14:20:08 +08:00
小李 970a252352 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1
# Conflicts:
#	main.js
2026-05-18 17:20:19 +08:00
小李 00946076aa 充值主题 2026-05-18 17:18:47 +08:00
tangxinyue b46210a235 新建外卖详情页 2026-05-15 18:34:52 +08:00
小李 d690a8d48c Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-05-15 09:38:41 +08:00
小李 07d50cc1e9 锦旗优化,证书优化 2026-05-15 09:38:13 +08:00
tangxinyue 519c0f85d6 完成淘宝购物模块 2026-05-15 09:36:12 +08:00
tangxinyue 55c624621d Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-05-13 18:17:24 +08:00
tangxinyue 7012a8624c 淘宝购物详情页80% 2026-05-13 18:16:38 +08:00
小李 b250034b47 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-05-13 17:56:18 +08:00
小李 2d13055961 锦旗优化,证书首页 2026-05-13 17:56:13 +08:00
tangxinyue 026d39a0e5 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-05-11 11:26:38 +08:00
tangxinyue 20c4e7a0de 完成列表页 2026-05-11 11:25:45 +08:00
小李 56e34166ba 锦旗 2026-05-09 10:16:24 +08:00
小李 5acaddf086 新增锦旗 2026-05-08 14:31:39 +08:00
小李 b3b76b9bd9 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1
# Conflicts:
#	main.js
2026-05-06 09:20:12 +08:00
小李 133ec5b7ff 版本号 2026-05-06 09:19:22 +08:00
tangxinyue 5e7738a6db 修改版本号 2026-04-30 16:21:56 +08:00
tangxinyue feb6491733 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-04-30 15:27:30 +08:00
tangxinyue 3586dc4a35 修改webview 2026-04-30 15:26:27 +08:00
小李 38cbbd2ed1 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-04-29 17:50:46 +08:00
小李 c54c630b30 充值页面主题 2026-04-29 17:50:28 +08:00
tangxinyue 1601a3e4ae 修改启动白屏 2026-04-29 14:28:18 +08:00
tangxinyue f3be53fe2f 细化uni-alipay-other埋点,替换12306火车票样式 2026-04-27 11:14:14 +08:00
tangxinyue e6aa5f29ed 优化京东购物模块 2026-04-27 09:19:48 +08:00
tangxinyue 8c39d28ff0 替换京东随机数据 2026-04-24 14:56:30 +08:00
tangxinyue 0962120ab4 测试 2026-04-20 11:10:46 +08:00
tangxinyue de27c2770d 测试Eslint 2026-04-20 10:51:25 +08:00
tangxinyue 6983450d59 测试Eslint 2026-04-20 10:46:27 +08:00
tangxinyue c6ecc56060 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-04-20 09:32:41 +08:00
tangxinyue c506a7fd42 修改支付问题 2026-04-20 09:32:31 +08:00
小李 2b46401bd8 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-04-17 18:05:55 +08:00
小李 8c560a0b8d 1 2026-04-17 18:05:31 +08:00
tangxinyue 009422675d Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-04-17 11:16:11 +08:00
tangxinyue 2ea96ae0fa 修改飞书记录问题 2026-04-17 11:16:04 +08:00
小李 436c94b9a8 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-04-17 09:42:30 +08:00
小李 8c2355c8a0 新增证书 2026-04-17 09:42:17 +08:00
tangxinyue e01f4865e6 完成京东秒送外卖模块 2026-04-15 18:38:50 +08:00
tangxinyue bce2fd578f 购物新增页面85%详情页20% 2026-04-13 18:34:13 +08:00
tangxinyue 21eb405746 完成京东购物 2026-04-10 15:29:12 +08:00
tangxinyue 52af4ca4ff Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-04-10 14:08:58 +08:00
tangxinyue c7a434a24d 完成京东购物模块 2026-04-10 14:08:42 +08:00
小李 3fc1fd8261 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1
# Conflicts:
#	main.js
2026-04-10 14:07:56 +08:00
小李 712b19dc4b genhuan 2026-04-10 14:07:08 +08:00
小李 ad67486e13 更换图片 2026-04-10 14:06:53 +08:00
tangxinyue 107f39efef 完成京东购物新增删除编辑查看详情 2026-04-09 18:30:33 +08:00
tangxinyue 8c3298af2a 完成京东购物新增 2026-04-08 18:32:10 +08:00
tangxinyue a2dc82ab4b 短信优化地区可修改 2026-04-07 16:02:17 +08:00
tangxinyue 7c65356773 完成从夯到拉排名 2026-04-03 15:42:44 +08:00
tangxinyue 2eb5f2be92 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-04-03 13:55:14 +08:00
tangxinyue ebe71e1688 完成从夯道拉排名 2026-04-03 13:54:54 +08:00
小李 c00cb2e135 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-04-02 13:48:39 +08:00
小李 fb3eec2ea3 充值页面新增兑换会员码 2026-04-02 13:48:32 +08:00
tangxinyue 755aff7422 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1
# Conflicts:
#	main.js
2026-03-28 15:44:32 +08:00
tangxinyue b839c6c4ff 京东购物列表页50% 2026-03-28 15:43:55 +08:00
小李 4b25dc3b07 充值页,版本号 2026-03-28 11:31:11 +08:00
小李 a5d27a3283 充值页样式 2026-03-28 11:06:23 +08:00
小李 583ea5a536 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-03-28 10:52:30 +08:00
小李 b17e6c0147 字体其他 2026-03-28 10:52:21 +08:00
tangxinyue 7dcc94c937 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-03-28 10:46:26 +08:00
tangxinyue 0a87094581 修改转账成功页面间距 2026-03-28 10:46:08 +08:00
小李 01500366f2 充值页 2026-03-28 10:37:59 +08:00
小李 30a63eda71 充值页面 2026-03-28 10:37:43 +08:00
小李 c97560d691 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-03-27 09:41:29 +08:00
小李 bd995c7d04 充值页 2026-03-27 09:41:18 +08:00
tangxinyue 3fd398ffa4 转账成功页面添加埋点 2026-03-26 12:03:02 +08:00
tangxinyue 4dbe88a024 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1
# Conflicts:
#	main.js
2026-03-26 11:51:39 +08:00
tangxinyue 935ea47b92 完成转账成功页面及购物app首页 2026-03-26 11:50:54 +08:00
小李 fb6942abbd 日志 2026-03-24 15:20:11 +08:00
小李 6229949c1a Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-03-19 17:05:48 +08:00
小李 f96b11a0cc 通话苹果样式 2026-03-19 17:05:35 +08:00
tangxinyue cfbcb6ac8d 优化苹果短信样式 2026-03-19 16:59:39 +08:00
tangxinyue 78c99caaa5 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1
# Conflicts:
#	main.js
2026-03-19 15:29:34 +08:00
tangxinyue 3b14a94cce 替换iphone短信图标 2026-03-19 15:26:11 +08:00
小李 7b1d55dfa3 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1
# Conflicts:
#	main.js
2026-03-19 15:20:07 +08:00
小李 003d11355c 头部通话提示 2026-03-19 15:18:12 +08:00
tangxinyue bd829907f4 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-03-19 14:56:30 +08:00
tangxinyue cab9b6b4fd 添加双击切换发言人功能添加华为通知信息,调整vivo搜素样式 2026-03-19 14:56:12 +08:00
小李 288b62c8b1 通话引导提示 2026-03-19 11:12:17 +08:00
小李 f005971605 身份证样式对齐 2026-03-18 11:26:49 +08:00
小李 7856f31273 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1
# Conflicts:
#	main.js
2026-03-18 11:14:13 +08:00
小李 2eb1981b8d Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1
# Conflicts:
#	pages/index/index.nvue
2026-03-18 11:13:21 +08:00
tangxinyue 16d2ea2cbd 短信界面添加水印 2026-03-18 10:54:45 +08:00
tangxinyue 2c7da360f4 完成支付宝短信 2026-03-18 10:12:20 +08:00
tangxinyue 261918d871 短信完成80% 2026-03-16 17:32:44 +08:00
小李 b5685a0d6f 身份证,通话 2026-03-16 17:32:36 +08:00
tangxinyue a88ddf46bc 完成oppo,vivo聊天界面ui 2026-03-13 10:52:42 +08:00
tangxinyue 0959867786 完成苹果,华为,小米短信聊天界面ui 2026-03-12 18:34:58 +08:00
tangxinyue eefe793b8f 完成短信列表样式 2026-03-11 18:31:57 +08:00
小李 21c79178fd Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-03-11 11:24:26 +08:00
小李 bd99f8e013 通话样式组件封装完成 2026-03-11 11:24:00 +08:00
tangxinyue 48eec51491 合并代码 2026-03-11 11:23:53 +08:00
tangxinyue ef71a0a669 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-03-10 16:30:48 +08:00
tangxinyue 465f53d15b 完成短信入口及导航栏 2026-03-10 16:30:29 +08:00
小李 6095979884 封装通话组件 2026-03-10 16:29:23 +08:00
小李 b1e6fa9368 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-03-10 09:57:32 +08:00
小李 41fa7f7398 通话列表模拟 2026-03-10 09:57:25 +08:00
tangxinyue 16b3a536a4 完成飞猪火车票 2026-03-10 09:46:54 +08:00
tangxinyue b8975f5015 完成去哪儿火车票 2026-03-09 16:01:04 +08:00
tangxinyue 46de3cbd57 完成去哪儿火车票 2026-03-06 14:19:05 +08:00
tangxinyue 0e43722c8e Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-03-05 16:26:56 +08:00
tangxinyue b149dbd858 修改火车票埋点配置 2026-03-05 16:26:38 +08:00
小李 4a63d5415f Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-03-05 11:12:13 +08:00
小李 62f9f50d35 身份证 2026-03-05 11:12:05 +08:00
tangxinyue b77ee8073d 调整火车票修改逻辑 2026-03-04 17:05:48 +08:00
tangxinyue bf692f48e5 完成携程火车票 2026-03-04 17:00:36 +08:00
tangxinyue b7ae173cd5 1.调整充值页
2.完成携程火车票界面ui
2026-03-04 11:37:29 +08:00
tangxinyue 14d4245a62 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1
# Conflicts:
#	pages/index/index.nvue
2026-03-03 15:48:32 +08:00
tangxinyue cfce21f87e 火车票app首页 2026-03-03 15:45:32 +08:00
小李 a092014941 Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1 2026-03-03 15:44:35 +08:00
小李 b15217007a 身份证 2026-03-03 15:44:04 +08:00
tangxinyue e233f84b4d 修改充值页字体引用 2026-03-03 14:55:08 +08:00
tangxinyue d8f440ed1f 修改小宝模拟器bug 2026-03-02 14:36:46 +08:00
847 changed files with 84990 additions and 4882 deletions

8
.eslintignore Normal file
View File

@ -0,0 +1,8 @@
node_modules/
uni_modules/
unpackage/
dist/
*.min.js
*.css
*.scss
*.json

41
.eslintrc.js Normal file
View File

@ -0,0 +1,41 @@
module.exports = {
root: true,
env: {
browser: true,
es2021: true,
node: true,
'vue/setup-compiler-macros': true
},
extends: [
'eslint:recommended',
'plugin:vue/vue3-essential'
],
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module'
},
plugins: [
'vue'
],
// 核心拦截规则
rules: {
'no-undef': 'error', // 静态拦截致命错误:防止引用不存在的参数(造成崩溃的主因)
'no-unused-vars': 'warn', // 变量未使用仅作黄牌警告,降低大盘爆红率
'vue/multi-word-component-names': 'off', // 兼容历史单字组件命名
'no-empty': 'warn',
'no-constant-condition': 'warn'
},
globals: {
uni: 'readonly',
plus: 'readonly',
wx: 'readonly',
getApp: 'readonly',
getCurrentPages: 'readonly',
setTimeout: 'readonly',
clearTimeout: 'readonly',
setInterval: 'readonly',
clearInterval: 'readonly',
console: 'readonly',
__dirname: 'readonly'
}
}

View File

@ -9,6 +9,10 @@
"customPlaygroundType" : "local",
"playground" : "custom",
"type" : "uni-app:app-android"
},
{
"playground" : "standard",
"type" : "uni-app:app-harmony"
}
]
}

2
.husky/pre-commit Normal file
View File

@ -0,0 +1,2 @@
#!/usr/bin/env sh
npx lint-staged

371
App.vue
View File

@ -1,194 +1,227 @@
<script>
export default {
globalData: {
NativeEvent: true,
recentNativeEvent: "", // 初始化一个全局变量
recentNativeData: 0 // 初始化一个全局变量
},
onLaunch: function (options) {
// === wgt 包启动诊断日志 ===
console.log('=== App Launch 开始 ===')
console.log('启动参数:', JSON.stringify(options))
console.log('环境:', process.env.NODE_ENV)
export default {
globalData: {
NativeEvent: true,
recentNativeEvent: "", // 初始化一个全局变量
recentNativeData: 0 // 初始化一个全局变量
},
onLaunch: function(options) {
uni.setStorageSync('AppUser', {})
uni.setStorageSync('userInfo', {})
// console.log=()=>{}
// === wgt 包启动诊断日志 ===
console.log('=== App Launch 开始 ===')
console.log('启动参数:', JSON.stringify(options))
console.log('环境:', process.env.NODE_ENV)
uni.setStorageSync('onNativeEventReceive', "no")
// 清除上次遗留的跳转指令,防止正常启动时自动跳转
uni.removeStorageSync('jumpTarget_url');
const startTime = Date.now()
uni.setStorageSync('onNativeEventReceive', "no")
// 清除上次遗留的跳转指令,防止正常启动时自动跳转
uni.removeStorageSync('jumpTarget_url');
const startTime = Date.now()
// 1. 获取并存储系统信息(只获取一次)
const systemInfo = uni.getSystemInfoSync()
uni.setStorageSync('systemInfo', {
platform: systemInfo.platform,
system: systemInfo.system,
osName: systemInfo.osName,
osVersion: systemInfo.osVersion,
statusBarHeight: systemInfo.statusBarHeight,
windowWidth: systemInfo.windowWidth,
windowHeight: systemInfo.windowHeight,
isIOS: systemInfo.platform === 'ios',
isAndroid: systemInfo.platform === 'android'
})
// 1. 获取并存储系统信息(只获取一次)
const systemInfo = uni.getSystemInfoSync()
uni.setStorageSync('systemInfo', {
platform: systemInfo.platform,
system: systemInfo.system,
osName: systemInfo.osName,
osVersion: systemInfo.osVersion,
statusBarHeight: systemInfo.statusBarHeight,
windowWidth: systemInfo.windowWidth,
windowHeight: systemInfo.windowHeight,
isIOS: systemInfo.platform === 'ios',
isAndroid: systemInfo.platform === 'android'
})
// 2. 立即设置宿主消息监听(避免错过消息)
this.setupNativeEventListener()
// 2. 立即设置宿主消息监听(避免错过消息)
this.setupNativeEventListener()
// 3. 同步初始化配置(必须完成)
this.initConfig(options)
// 3. 同步初始化配置(必须完成)
this.initConfig(options)
// 启动完成
console.log(`=== App 启动完成,耗时: ${Date.now() - startTime}ms ===`)
// 启动完成
console.log(`=== App 启动完成,耗时: ${Date.now() - startTime}ms ===`)
// 初始化埋点,进入支付宝模拟器首页
this.$apiUserEvent('all', {
type: 'event',
key: 'index',
value: "进入支付宝模拟器首页",
})
},
// 初始化埋点,进入支付宝模拟器首页
this.$apiUserEvent('all', {
type: 'event',
key: 'index',
value: "进入支付宝模拟器首页",
})
},
onShow: function() {
console.log('App Show')
// 监听已在 onLaunch 中设置,这里不再重复
},
onShow: function () {
console.log('App Show')
// 监听已在 onLaunch 中设置,这里不再重复
},
onHide: function() {
console.log('App Hide')
},
onExit: function() {
// 关闭数据库
// #ifdef APP
// plus.sqlite.closeDatabase({ name: 'zyds' })
uni.sendNativeEvent('unimp_stop_alipay', "stop", ret => {
// console.log('宿主App回传的数据:' + ret);
});
// #endif
console.log('App onExit')
},
onHide: function () {
console.log('App Hide')
},
onExit: function () {
// 关闭数据库
// #ifdef APP
// plus.sqlite.closeDatabase({ name: 'zyds' })
uni.sendNativeEvent('unimp_stop_alipay', "stop", ret => {
// console.log('宿主App回传的数据:' + ret);
});
// #endif
console.log('App onExit')
},
methods: {
/**
* 设置宿主消息监听(在 onLaunch 中调用)
*/
setupNativeEventListener() {
if (this.globalData.NativeEvent) {
this.globalData.NativeEvent = false
console.log('开始监听宿主消息')
methods: {
/**
* 设置宿主消息监听(在 onLaunch 中调用)
*/
setupNativeEventListener() {
if (this.globalData.NativeEvent) {
this.globalData.NativeEvent = false
console.log('开始监听宿主消息')
if (typeof uni.onNativeEventReceive === 'function') {
uni.onNativeEventReceive((event, data) => {
if (event) {
console.log('接收到宿主消息:', event, data)
uni.onNativeEventReceive((event, data) => {
if (event) {
console.log('接收到宿主消息:', event, data)
if (event == "token") {
let header = uni.getStorageSync('header') || {}
header["x-token"] = data
uni.setStorageSync('header', header)
console.log('已更新 token')
if (event == "token") {
let header = uni.getStorageSync('header') || {}
header["x-token"] = data
uni.setStorageSync('header', header)
console.log('已更新 token')
//获取宿主用户信息
try {
this.$getUserInfo()
} catch (error) {
console.error('获取用户信息失败:', error)
//获取宿主用户信息
try {
this.$getUserInfo()
} catch (error) {
console.error('获取用户信息失败:', error)
}
} else if (event == "jump") {
if (data) {
console.log('接收到跳转指令,已缓存目标地址:', data);
uni.setStorageSync('jumpTarget_url', data);
// 强制重定向到首页,触发onShow获取用户信息后再跳转
uni.reLaunch({
url: '/pages/index/index'
});
}
} else if (event == 'wx_pay_result' || event == 'ios_pay_result') {
this.globalData.recentNativeEvent = event
this.globalData.recentNativeData = data
}
}
} else if (event == "jump") {
if (data) {
console.log('接收到跳转指令,已缓存目标地址:', data);
uni.setStorageSync('jumpTarget_url', data);
// 强制重定向到首页,触发onShow获取用户信息后再跳转
uni.reLaunch({
url: '/pages/index/index'
});
})
} else {
console.log('uni.onNativeEventReceive 不可用,跳过监听')
}
}
},
/**
* 初始化应用配置
*/
initConfig(options) {
console.log('=== 配置初始化 ===')
// 检查是否有外部传入的配置数据
const hasExtraData = options?.referrerInfo?.extraData &&
JSON.stringify(options.referrerInfo.extraData) !== '{}'
console.log('宿主是否传递配置:', hasExtraData)
if (hasExtraData) {
console.log('→ 使用宿主 extraData')
this.initFromExtraData(options.referrerInfo.extraData)
} else {
console.log('→ 宿主未传递配置,使用默认配置')
// ⚠️ 关键修改:不再强制退出,使用默认配置
this.initDevelopmentConfig()
}
},
/**
* 从外部数据初始化配置
*/
initFromExtraData(extraData) {
// 批量设置 Storage,减少 I/O 次数
let storageData={}
if(uni.getStorageSync('isTest')){
storageData = {
host: extraData.host,
header: {
"x-token": extraData['x-token'],
"x-version": extraData['x-version'],
"x-platform": extraData['x-platform'],
"x-device-id": extraData['x-device-id'],
"x-mobile-brand": extraData['x-mobile-brand'],
"x-mobile-model": extraData['x-mobile-model'],
"x-channel": extraData['x-channel'],
"x-package": extraData['x-package'],
"x-click-id": extraData['x-click-id'],
// #ifdef APP-PLUS
"header": {
"version": this.$version,
}
} else if (event == 'wx_pay_result' || event == 'ios_pay_result') {
this.globalData.recentNativeEvent = event
this.globalData.recentNativeData = data
// #endif
},
encrypt: extraData['encrypt'],
decrypt: extraData['decrypt']
}
}else{
if (extraData['safeBottom']) {
this.$store.state["safeBottomAz"]=Number(extraData['safeBottom'])||0
}
storageData = {
host: extraData.host,
header: {
// #ifdef APP-PLUS
"header": {
"version": this.$version,
}
// #endif
}
}
}
// 批量写入
Object.keys(storageData).forEach(key => {
uni.setStorageSync(key, storageData[key])
})
if (extraData['isCombo']) {
uni.setStorageSync('isCombo', extraData['isCombo'])
}
if (extraData['paytype']) {
uni.setStorageSync('isCombo', extraData['paytype'] == 'combo' ? 'ok' : '')
}
},
/**
* 初始化开发环境配置(也作为默认配置)
*/
initDevelopmentConfig() {
console.log('初始化默认配置')
// 批量设置开发环境配置
const devConfig = {
host: "https://flaunt.batiao8.com/",
header: {
"x-token": "55e2481e-cbd2-4d2d-82d3-b47e191fcfc0"
},
decrypt: "e4rOtnF8tJjtHO7ecZeJHN1rapED5ImB",
encrypt: "xn08hYoizXhZ1zHP8DVqfCm2yHxPmhil"
}
Object.keys(devConfig).forEach(key => {
uni.setStorageSync(key, devConfig[key])
})
console.log('默认配置初始化完成')
}
},
/**
* 初始化应用配置
*/
initConfig(options) {
console.log('=== 配置初始化 ===')
// 检查是否有外部传入的配置数据
const hasExtraData = options?.referrerInfo?.extraData &&
JSON.stringify(options.referrerInfo.extraData) !== '{}'
console.log('宿主是否传递配置:', hasExtraData)
if (hasExtraData) {
console.log('→ 使用宿主 extraData')
this.initFromExtraData(options.referrerInfo.extraData)
} else {
console.log('→ 宿主未传递配置,使用默认配置')
// ⚠️ 关键修改:不再强制退出,使用默认配置
this.initDevelopmentConfig()
}
},
/**
* 从外部数据初始化配置
*/
initFromExtraData(extraData) {
// 批量设置 Storage,减少 I/O 次数
const storageData = {
host: extraData.host,
header: {
"x-token": extraData['x-token'],
"x-version": extraData['x-version'],
"x-platform": extraData['x-platform'],
"x-device-id": extraData['x-device-id'],
"x-mobile-brand": extraData['x-mobile-brand'],
"x-mobile-model": extraData['x-mobile-model'],
"x-channel": extraData['x-channel'],
"x-package": extraData['x-package'],
"x-click-id": extraData['x-click-id'],
// #ifdef APP-PLUS
"header": {
"version": this.$version,
}
// #endif
},
encrypt: extraData['encrypt'],
decrypt: extraData['decrypt']
}
// 批量写入
Object.keys(storageData).forEach(key => {
uni.setStorageSync(key, storageData[key])
})
},
/**
* 初始化开发环境配置(也作为默认配置)
*/
initDevelopmentConfig() {
console.log('初始化默认配置')
// 批量设置开发环境配置
const devConfig = {
host: "https://flaunt.batiao8.com/",
header: { "x-token": "ebe14dab-1879-4c5d-9148-727b96b30aad" },
decrypt: "e4rOtnF8tJjtHO7ecZeJHN1rapED5ImB",
encrypt: "xn08hYoizXhZ1zHP8DVqfCm2yHxPmhil"
}
Object.keys(devConfig).forEach(key => {
uni.setStorageSync(key, devConfig[key])
})
console.log('默认配置初始化完成')
}
}
}
</script>
<style>
@import "./common/color.css";
/* #ifndef APP-NVUE */
@import "./common/color.css";
/* #endif */
</style>

View File

@ -30,4 +30,4 @@ page {
line-height: 1.5;
color: var(--text-color);
background-color: var(--page-bg-color);
}
}

View File

@ -130,401 +130,401 @@ text {
}
.codefun-ml-2 {
margin-left: 4rpx;
margin-left: 2px;
}
.codefun-mt-2 {
margin-top: 4rpx;
margin-top: 2px;
}
.codefun-ml-4 {
margin-left: 8rpx;
margin-left: 4px;
}
.codefun-mt-4 {
margin-top: 8rpx;
margin-top: 4px;
}
.codefun-ml-6 {
margin-left: 12rpx;
margin-left: 6px;
}
.codefun-mt-6 {
margin-top: 12rpx;
margin-top: 6px;
}
.codefun-ml-8 {
margin-left: 16rpx;
margin-left: 8px;
}
.codefun-mt-8 {
margin-top: 16rpx;
margin-top: 8px;
}
.codefun-ml-10 {
margin-left: 20rpx;
margin-left: 10px;
}
.codefun-mt-10 {
margin-top: 20rpx;
margin-top: 10px;
}
.codefun-ml-12 {
margin-left: 24rpx;
margin-left: 12px;
}
.codefun-mt-12 {
margin-top: 24rpx;
margin-top: 12px;
}
.codefun-ml-14 {
margin-left: 28rpx;
margin-left: 14px;
}
.codefun-mt-14 {
margin-top: 28rpx;
margin-top: 14px;
}
.codefun-ml-16 {
margin-left: 32rpx;
margin-left: 16px;
}
.codefun-mt-16 {
margin-top: 32rpx;
margin-top: 16px;
}
.codefun-ml-18 {
margin-left: 36rpx;
margin-left: 18px;
}
.codefun-mt-18 {
margin-top: 36rpx;
margin-top: 18px;
}
.codefun-ml-20 {
margin-left: 40rpx;
margin-left: 20px;
}
.codefun-mt-20 {
margin-top: 40rpx;
margin-top: 20px;
}
.codefun-ml-22 {
margin-left: 44rpx;
margin-left: 22px;
}
.codefun-mt-22 {
margin-top: 44rpx;
margin-top: 22px;
}
.codefun-ml-24 {
margin-left: 48rpx;
margin-left: 24px;
}
.codefun-mt-24 {
margin-top: 48rpx;
margin-top: 24px;
}
.codefun-ml-26 {
margin-left: 52rpx;
margin-left: 26px;
}
.codefun-mt-26 {
margin-top: 52rpx;
margin-top: 26px;
}
.codefun-ml-28 {
margin-left: 56rpx;
margin-left: 28px;
}
.codefun-mt-28 {
margin-top: 56rpx;
margin-top: 28px;
}
.codefun-ml-30 {
margin-left: 60rpx;
margin-left: 30px;
}
.codefun-mt-30 {
margin-top: 60rpx;
margin-top: 30px;
}
.codefun-ml-32 {
margin-left: 64rpx;
margin-left: 32px;
}
.codefun-mt-32 {
margin-top: 64rpx;
margin-top: 32px;
}
.codefun-ml-34 {
margin-left: 68rpx;
margin-left: 34px;
}
.codefun-mt-34 {
margin-top: 68rpx;
margin-top: 34px;
}
.codefun-ml-36 {
margin-left: 72rpx;
margin-left: 36px;
}
.codefun-mt-36 {
margin-top: 72rpx;
margin-top: 36px;
}
.codefun-ml-38 {
margin-left: 76rpx;
margin-left: 38px;
}
.codefun-mt-38 {
margin-top: 76rpx;
margin-top: 38px;
}
.codefun-ml-40 {
margin-left: 80rpx;
margin-left: 40px;
}
.codefun-mt-40 {
margin-top: 80rpx;
margin-top: 40px;
}
.codefun-ml-42 {
margin-left: 84rpx;
margin-left: 42px;
}
.codefun-mt-42 {
margin-top: 84rpx;
margin-top: 42px;
}
.codefun-ml-44 {
margin-left: 88rpx;
margin-left: 44px;
}
.codefun-mt-44 {
margin-top: 88rpx;
margin-top: 44px;
}
.codefun-ml-46 {
margin-left: 92rpx;
margin-left: 46px;
}
.codefun-mt-46 {
margin-top: 92rpx;
margin-top: 46px;
}
.codefun-ml-48 {
margin-left: 96rpx;
margin-left: 48px;
}
.codefun-mt-48 {
margin-top: 96rpx;
margin-top: 48px;
}
.codefun-ml-50 {
margin-left: 100rpx;
margin-left: 50px;
}
.codefun-mt-50 {
margin-top: 100rpx;
margin-top: 50px;
}
.codefun-ml-52 {
margin-left: 104rpx;
margin-left: 52px;
}
.codefun-mt-52 {
margin-top: 104rpx;
margin-top: 52px;
}
.codefun-ml-54 {
margin-left: 108rpx;
margin-left: 54px;
}
.codefun-mt-54 {
margin-top: 108rpx;
margin-top: 54px;
}
.codefun-ml-56 {
margin-left: 112rpx;
margin-left: 56px;
}
.codefun-mt-56 {
margin-top: 112rpx;
margin-top: 56px;
}
.codefun-ml-58 {
margin-left: 116rpx;
margin-left: 58px;
}
.codefun-mt-58 {
margin-top: 116rpx;
margin-top: 58px;
}
.codefun-ml-60 {
margin-left: 120rpx;
margin-left: 60px;
}
.codefun-mt-60 {
margin-top: 120rpx;
margin-top: 60px;
}
.codefun-ml-62 {
margin-left: 124rpx;
margin-left: 62px;
}
.codefun-mt-62 {
margin-top: 124rpx;
margin-top: 62px;
}
.codefun-ml-64 {
margin-left: 128rpx;
margin-left: 64px;
}
.codefun-mt-64 {
margin-top: 128rpx;
margin-top: 64px;
}
.codefun-ml-66 {
margin-left: 132rpx;
margin-left: 66px;
}
.codefun-mt-66 {
margin-top: 132rpx;
margin-top: 66px;
}
.codefun-ml-68 {
margin-left: 136rpx;
margin-left: 68px;
}
.codefun-mt-68 {
margin-top: 136rpx;
margin-top: 68px;
}
.codefun-ml-70 {
margin-left: 140rpx;
margin-left: 70px;
}
.codefun-mt-70 {
margin-top: 140rpx;
margin-top: 70px;
}
.codefun-ml-72 {
margin-left: 144rpx;
margin-left: 72px;
}
.codefun-mt-72 {
margin-top: 144rpx;
margin-top: 72px;
}
.codefun-ml-74 {
margin-left: 148rpx;
margin-left: 74px;
}
.codefun-mt-74 {
margin-top: 148rpx;
margin-top: 74px;
}
.codefun-ml-76 {
margin-left: 152rpx;
margin-left: 76px;
}
.codefun-mt-76 {
margin-top: 152rpx;
margin-top: 76px;
}
.codefun-ml-78 {
margin-left: 156rpx;
margin-left: 78px;
}
.codefun-mt-78 {
margin-top: 156rpx;
margin-top: 78px;
}
.codefun-ml-80 {
margin-left: 160rpx;
margin-left: 80px;
}
.codefun-mt-80 {
margin-top: 160rpx;
margin-top: 80px;
}
.codefun-ml-82 {
margin-left: 164rpx;
margin-left: 82px;
}
.codefun-mt-82 {
margin-top: 164rpx;
margin-top: 82px;
}
.codefun-ml-84 {
margin-left: 168rpx;
margin-left: 84px;
}
.codefun-mt-84 {
margin-top: 168rpx;
margin-top: 84px;
}
.codefun-ml-86 {
margin-left: 172rpx;
margin-left: 86px;
}
.codefun-mt-86 {
margin-top: 172rpx;
margin-top: 86px;
}
.codefun-ml-88 {
margin-left: 176rpx;
margin-left: 88px;
}
.codefun-mt-88 {
margin-top: 176rpx;
margin-top: 88px;
}
.codefun-ml-90 {
margin-left: 180rpx;
margin-left: 90px;
}
.codefun-mt-90 {
margin-top: 180rpx;
margin-top: 90px;
}
.codefun-ml-92 {
margin-left: 184rpx;
margin-left: 92px;
}
.codefun-mt-92 {
margin-top: 184rpx;
margin-top: 92px;
}
.codefun-ml-94 {
margin-left: 188rpx;
margin-left: 94px;
}
.codefun-mt-94 {
margin-top: 188rpx;
margin-top: 94px;
}
.codefun-ml-96 {
margin-left: 192rpx;
margin-left: 96px;
}
.codefun-mt-96 {
margin-top: 192rpx;
margin-top: 96px;
}
.codefun-ml-98 {
margin-left: 196rpx;
margin-left: 98px;
}
.codefun-mt-98 {
margin-top: 196rpx;
margin-top: 98px;
}
.codefun-ml-100 {
margin-left: 200rpx;
margin-left: 100px;
}
.codefun-mt-100 {
margin-top: 200rpx;
margin-top: 100px;
}

View File

@ -256,7 +256,12 @@
display: flex;
}
.shrink-0 {
flex-shrink: 0;
}
.flex-column {
display: flex;
flex-direction: column;
}
@ -282,6 +287,11 @@
align-items: center;
}
.flex-align-start {
display: flex;
align-items: flex-start;
}
.flex-justify-center {
display: flex;
justify-content: center;

View File

@ -1,20 +1,10 @@
<!-- eslint-disable -->
<template>
<view
class="player-wrapper"
:id="videoWrapperId"
:parentId="id"
:randomNum="randomNum"
:change:randomNum="domVideoPlayer.randomNumChange"
:viewportProps="viewportProps"
:change:viewportProps="domVideoPlayer.viewportChange"
:videoSrc="videoSrc"
:change:videoSrc="domVideoPlayer.initVideoPlayer"
:command="eventCommand"
:change:command="domVideoPlayer.triggerCommand"
:func="renderFunc"
:change:func="domVideoPlayer.triggerFunc"
/>
<view class="player-wrapper" :id="videoWrapperId" :parentId="id" :randomNum="randomNum"
:change:randomNum="domVideoPlayer.randomNumChange" :viewportProps="viewportProps"
:change:viewportProps="domVideoPlayer.viewportChange" :videoSrc="videoSrc"
:change:videoSrc="domVideoPlayer.initVideoPlayer" :command="eventCommand"
:change:command="domVideoPlayer.triggerCommand" :func="renderFunc" :change:func="domVideoPlayer.triggerFunc" />
</template>
<script>
@ -158,6 +148,14 @@ export default {
params: { sec, isDelay }
}
}
},
// 组件销毁前自动清理 renderjs 层的原生 video 元素,
// 防止 v-if 移除组件后视频仍在后台播放声音("僵尸视频"问题)
beforeUnmount() {
this.renderFunc = {
name: 'removeHandler',
params: null
}
}
}
</script>
@ -173,7 +171,11 @@ export default {
loadingEl: null,
// 延迟生效的函数
delayFunc: null,
renderProps: {}
renderProps: {},
// 是否主动暂停(通过父组件调用 pause() 方法)
intentionalPause: false,
// 上次自动恢复的时间戳,用于冷却控制
lastAutoRecoverTime: 0
}
},
computed: {
@ -215,7 +217,9 @@ export default {
videoEl.muted = muted
videoEl.playbackRate = playbackRate
videoEl.id = this.playerId
// videoEl.setAttribute('x5-video-player-type', 'h5')
// 开启 X5 内核的 H5 同层渲染,防止原生视频组件层级过高在拖拽时脱离或卡死
videoEl.setAttribute('x5-video-player-type', 'h5')
videoEl.setAttribute('x5-video-player-fullscreen', 'false')
videoEl.setAttribute('preload', 'auto')
videoEl.setAttribute('playsinline', true)
videoEl.setAttribute('webkit-playsinline', true)
@ -315,6 +319,18 @@ export default {
key: 'playing',
value: false
})
// 自动恢复机制:仅在非主动暂停时触发,并带有 2 秒冷却期防止循环拖动
const { loop, autoplay } = this.renderProps
const now = Date.now()
if (loop && autoplay && this.videoEl && !this.intentionalPause && (now - this.lastAutoRecoverTime > 2000)) {
this.lastAutoRecoverTime = now
setTimeout(() => {
if (this.videoEl && this.videoEl.paused && !this.intentionalPause) {
this.videoEl.play().catch(() => {})
}
}, 200)
}
}
this.videoEl.removeEventListener('pause', pauseHandler)
this.videoEl.addEventListener('pause', pauseHandler)
@ -367,7 +383,13 @@ export default {
this.videoEl.addEventListener('loadedmetadata', loadedMetadataHandler)
// 播放进度监听
let lastTime = 0;
const timeupdateHandler = (e) => {
const now = Date.now();
// 增加节流阀,每 500ms 触发一次通信,极大地减少 renderjs 和逻辑层的通讯开销
if (now - lastTime < 500) return;
lastTime = now;
const currentTime = e.target.currentTime
this.$ownerInstance.callMethod('eventEmit', {
event: 'timeupdate',
@ -457,6 +479,12 @@ export default {
},
triggerCommand(eventType) {
if (eventType) {
// 标记主动暂停/播放状态,用于自动恢复机制的判断
if (eventType === 'pause') {
this.intentionalPause = true
} else if (eventType === 'play') {
this.intentionalPause = false
}
this.$ownerInstance.callMethod('resetEventCommand')
this.videoEl && this.videoEl[eventType]()
}
@ -519,6 +547,16 @@ export default {
randomNumChange(val) {
this.num = val
}
},
// renderjs 层直接清理原生 video 元素,
// 确保组件被 v-if 销毁时视频立即停止播放和声音输出
beforeUnmount() {
if (this.videoEl) {
this.videoEl.pause()
this.videoEl.src = ''
this.videoEl.load()
this.videoEl = null
}
}
}
</script>
@ -529,5 +567,7 @@ export default {
height: 100%;
padding: 0;
position: relative;
/* 开启硬件加速,将视频提升为独立合成层 */
transform: translateZ(0);
}
</style>

View File

@ -12,7 +12,7 @@
</view>
<view class="money alipay-font"
:class="item.isAdd ? (isBalance ? 'add-color' : 'red-add-color') : 'minus-color', { 'line-height-51rpx': isBalance }">
{{ item.isAdd ? '+' : '-' }}{{ Number(item.money).toFixed(2) }}
{{ item.isAdd ? '+' : '-' }}{{ numberUtil.formatMoneyWithThousand(item.money) }}
</view>
</view>
@ -39,7 +39,7 @@
<view v-if="item.isRefund" class="refund" :class="{ 'item-box': !isBalance }">已全额退款</view>
<view v-if="isBalance" class="balance secondary" :class="{ 'item-box': !isBalance }">余额
<text class="balance-text wx-font-regular">{{
Number(item.balance).toFixed(2)
numberUtil.formatMoneyWithThousand(item.balance)
}}</text>元
</view>
</view>
@ -58,6 +58,10 @@ import {
reactive
} from 'vue'
import {
numberUtil
} from '@/utils/common.js'
// 定义组件属性
const props = defineProps({
list: {
@ -94,7 +98,7 @@ const handleTouchStart = (e, item) => {
event: e,
item
})
}, 1500)
}, 1000)
}
const handleTouchMove = (e) => {

View File

@ -0,0 +1,232 @@
<template>
<view class="header" :class="['header_'+type]">
<view class="title" v-if="type!='vivo'">
{{title}}
</view>
<view class="search" v-if="type!='oppo'&&type!='huawei'&&type!='vivo'">
<view class="left" >
<image src="/static/image/call/iosSearchLeft.png" mode=""></image>
{{searchTitle}}
</view>
<image v-if="type=='ios'" src="/static/image/call/iosSearchRight.png" mode=""></image>
</view>
<view class="selectType" v-if="type=='huawei'">
<view class="btn " :class="{'active':active}" @click.stop="setActive(true)">
全部来电
</view>
<view class="btn" :class="{'active':!active}" @click.stop="setActive(false)">
未接来电
</view>
</view>
<view class="selectTypeVivo" v-if="type=='vivo'">
<view class="btn " :class="{'active':active}" @click.stop="setActive(true)">
全部
</view>
<view class="btn" :class="{'active':!active}" @click.stop="setActive(false)">
未接
</view>
</view>
<view class="select" v-if="type=='xiaomi'">
全部通话
<image src="/static/image/call/xiaomiHeaderSelectImg.png" mode=""></image>
</view>
</view>
</template>
<script setup>
import {
onMounted,
reactive,
ref,
toRefs
} from 'vue'
const topPopup = ref()
// 定义组件属性
const props = defineProps({
type: {
type: String,
default: 'ios'
},
})
const data = reactive({
active:true,
statusBarHeight: 0,
showTipLayer: true,
title:'最近通话',
searchTitle:"搜索",
list:[
"个人收藏",
"最近通话",
"通讯录",
"拨号键盘",
"语音留言"
]
})
let {
active,
searchTitle,
title,
list,
showTipLayer
} = toRefs(data)
onMounted(() => {
if(props.type=='xiaomi'){
searchTitle.value="搜索联系人"
title.value="通话"
// console.log('aaaaaaaaaaa')
}else if(props.type=='oppo'){
title.value="通话"
}else if(props.type=='huawei'){
title.value="电话"
}
})
function setActive(status){
active.value=status
uni.$emit('setActive',status)
}
</script>
<style scoped lang="scss">
.header{
padding: 0 15px;
background-color: #fff;
.title{
font-weight: bold;
font-size: 32px;
color: #1A1A1A;
}
.search{
margin-top: 10px;
width: 100%;
height: 34px;
background: #EEEEF0;
border-radius: 12px 12px 12px 12px;
display: flex;
align-items: center;
justify-content: space-between;
.left{
display: flex;
align-items: center;
font-size: 16px;
color: #838383;
}
image{
width: 16px;
height: 16px;
margin: 0 8px;
}
}
.select{
padding-left: 15px;
margin-top: 20px;
display: flex;
align-items: center;
font-weight: 400;
font-size: 13px;
color: #8B8DA5;
image{
width: 13px;
height: 13px;
margin-left: 6px;
}
}
}
.selectTypeVivo{
padding-top: 18px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
.btn{
width: 62px;
background-color: #F6F6F6;
text-align: center;
font-size: 13px;
color: #555555;
height: 30px;
line-height: 30px;
border-radius: 8px 0 0 8px;
margin: 0 1px;
}
.btn:nth-child(2){
border-radius:0 8px 8px 0;
}
.active{
font-size: 13px;
color: #1A1A1A;
background: #DCF6E6;
}
}
.header_huawei{
padding-bottom: 14px;
.title{
padding-left: 3px;
font-weight: bold;
font-size: 30px !important;
color: #1A1A1A !important;
}
.selectType{
margin-top: 18px;
height: 38px;
background: #F4F4F4;
border-radius: 19px 19px 19px 19px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 2px;
.btn{
width: 50%;
text-align: center;
font-size: 13px;
color: #555555;
height: 34px;
line-height: 34px;
}
.active{
font-size: 13px;
color: #1A1A1A;
background: #fff;
border-radius: 19px 19px 19px 19px;
}
}
}
.header_xiaomi{
.title{
padding-left: 15px;
font-weight: 400 !important;
font-size: 30px !important;
color: #1A1A1A !important;
}
.search{
margin-top: 10px;
height: 41px !important;
background: #F0F0F0 !important;
border-radius: 21px 21px 21px 21px !important;
.left{
font-size: 16px !important;
color: #A9A9A9 !important;
}
image{
width: 16px;
height: 16px;
margin: 0 8px;
}
}
}
.header_xiaomi{
.search{
.left{
margin-left: 20rpx;
}
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,375 @@
<template>
<view>
<uni-nav-bar :backgroundColor="navBgColor" class="nav-bar" :border="false" :title="title" fixed="true"
statusBar="true">
<template v-slot:left>
<slot name="left">
<view :class="[type+'LeftTitle']" :style="{opacity:(type!='huawei'&&type!='oppo'?1: navOpacity)}">
{{LeftTitle}}
</view>
</slot>
</template>
<view class="nav-bar-title " :class="['nav-bar-title-'+type]">
<slot>
<view class="iosBox" v-if="type=='ios'">
<view class="btn " :class="{'active':active}" @click.stop="setActive(true)">
全部来电
</view>
<view class="btn" :class="{'active':!active}" @click.stop="setActive(false)">
未接来电
</view>
</view>
<view class="oppoBox" v-if="type=='oppo'" :style="{opacity: navOpacity?1-navOpacity:1}">
<view class="btn " :class="{'active':active}" @click.stop="setActive(true)">
全部
</view>
<view class="btn" :class="{'active':!active}" @click.stop="setActive(false)">
未接
</view>
</view>
<view class="title" v-else-if="type=='xiaomi'" :style="{opacity: navOpacity}">
通话
</view>
</slot>
</view>
<template v-slot:right>
<slot name="right">
<view class="rightImgBox" :class="['rightImgBox'+type]">
<image v-if="type=='oppo'||type=='vivo'" class="rightImg"
:src="`/static/image/call/${type}NavRightImg2.png`"></image>
<image v-if="type!='ios'" class="rightImg" :class="['rightImg_'+type]"
:src="`/static/image/call/${type}NavRightImg.png`"></image>
</view>
</slot>
</template>
</uni-nav-bar>
<view class="tipLayer" :style="{ top: `${45 + data.statusBarHeight}px` }" v-if="isTipLayer&&showTipLayer">
<view class="tipLayer-content">
<view class="title">
<slot name="tipLayer">点击此处<text>[{{ tipLayerText }}]</text></slot>
</view>
<image class="close" src="/static/image/common/tipLayer-close.png" mode="" @click="closeTipLayer"></image>
<image v-if="type=='ios'||type=='oppo'" class="triangleImg" src="/static/image/common/tipLayer-eye2.png"></image>
<image v-else class="triangleImg" src="/static/image/common/tipLayer-eye.png"></image>
</view>
</view>
</view>
</template>
<script setup>
import {
onMounted,
reactive,
ref,
toRefs,
watch
} from 'vue'
const topPopup = ref()
// 定义组件属性
const props = defineProps({
bgColor: {
type: String,
default: '#fff'
},
textColor: {
type: String,
default: '#000'
},
title: {
type: String,
default: ''
},
type: {
type: String,
default: 'ios'
},
scrollTop: {
type: Number,
default: 0
},
tipLayerText: {
type: String,
default: ''
},
isTipLayer: {
type: Boolean,
default: false
},
})
const data = reactive({
active: true,
statusBarHeight: 0,
LeftTitle: '',
showTipLayer: true,
navOpacity: 0, // 导航栏透明度
navBgColor: props.bgColor // 导航栏背景色
})
let {
active,
LeftTitle,
showTipLayer,
navOpacity,
navBgColor
} = toRefs(data)
// 监听 scrollTop 变化
watch(() => props.scrollTop, (newValue, oldValue) => {
// console.log('scrollTop changed:', newValue);
// 根据 scrollTop 的值计算导航栏透明度
if (newValue > 0) {
// 当 scrollTop 大于 0 时,透明度逐渐增加
navOpacity.value = Math.min(1, newValue / 100);
// 可以根据需要调整背景色
// navBgColor.value = `rgba(255, 255, 255, ${navOpacity.value})`;
} else {
// 当 scrollTop 为 0 时,透明度为 1
navOpacity.value = 0;
// navBgColor.value = props.bgColor;
}
});
onMounted(() => {
if (props.type == 'ios') {
LeftTitle.value = '编辑'
} else if (props.type == 'vivo') {
LeftTitle.value = '拨号'
} else if (props.type == 'oppo') {
LeftTitle.value = '通话'
} else if (props.type == 'huawei') {
LeftTitle.value = '电话'
} else {
LeftTitle.value = ''
}
// 同步获取系统信息
const systemInfo = uni.getSystemInfoSync();
data.statusBarHeight = systemInfo.statusBarHeight || 0;
if (props.isTipLayer) {
if (uni.getStorageSync("call_" + props.type) == props.type) {
showTipLayer.value = false
}
}
})
function setActive(status) {
active.value = status
uni.$emit('setActive', status)
}
const closeTipLayer = () => {
showTipLayer.value = false
uni.setStorageSync("call_" + props.type, props.type)
emit("refresh")
}
</script>
<style scoped lang="scss">
::v-deep .uni-navbar__header-btns {
width: 27vw !important;
}
.iosLeftTitle {
font-weight: 400;
font-size: 16px;
color: #018AE0;
}
.rightImgBox {
display: flex;
align-items: center;
image {
width: 24px;
height: 24px;
}
}
.rightImg_oppo {
margin-left: 26px;
}
.rightImg_vivo {
margin-left: 26px;
margin-right: 16px;
}
.oppoLeftTitle {
padding-left: 8px;
font-weight: bold;
font-size: 32px;
color: #1A1A1A;
}
.huaweiLeftTitle {
padding-left: 8px;
font-weight: bold;
font-size: 30px;
color: #1A1A1A;
}
.vivoLeftTitle {
padding-left: 34px;
font-weight: bold;
font-size: 30px;
color: #1A1A1A;
}
.nav-bar-title-xiaomi {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
font-weight: bold;
font-size: 18px;
color: #1A1A1A;
}
.nav-bar-title-oppo {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
.oppoBox {
padding: 0 2px;
width: 100%;
display: flex;
align-items: center;
justify-content: space-around;
width: 137px;
height: 34px;
background: #E9E9E9;
border-radius: 17px 17px 17px 17px;
.btn {
font-size: 12px;
color: #6B6B6B;
width: 67px;
height: 30px;
text-align: center;
line-height: 30px;
}
.active {
color: #1A1A1A;
background: #FFFFFF;
border-radius: 17px 17px 17px 17px;
}
}
}
.nav-bar-title-ios {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
.iosBox {
width: 100%;
display: flex;
align-items: center;
justify-content: space-around;
width: 145px;
height: 30px;
background: #EEEEEE;
border-radius: 8px 8px 8px 8px;
.btn {
font-size: 12px;
color: #1A1A1A;
width: 69px;
height: 26px;
text-align: center;
line-height: 26px;
}
.active {
background: #FFFFFF;
box-shadow: 0px 0px 2px 0px rgba(0, 0, 0, 0.1);
border-radius: 6px 6px 6px 6px;
margin: 0 2px;
}
}
}
.rightImg_xiaomi {
width: 20px !important;
height: 20px !important;
}
.rightImg_huawei {
width: 38px !important;
height: 38px !important;
}
.tipLayer {
box-sizing: border-box;
min-width: 200px !important;
height: 48px;
background: #B8EDFE;
border-radius: 8px 8px 8px 8px;
position: fixed;
/* top: 115px; */
left: 50%;
transform: translateX(-50%);
z-index: 999;
.tipLayer-content {
position: relative;
.title {
font-weight: 450;
font-size: 14px;
color: #268FFF;
line-height: 48px;
text-align: center;
text {
font-size: 14px;
font-weight: bold;
color: #006ADD;
}
::v-deep text {
font-size: 14px;
font-weight: bold;
color: #006ADD;
}
}
.triangleImg {
width: 111px;
height: 52px;
pointer-events: none;
position: absolute;
top: -23px;
left: calc(50% - 111px);
}
.triangle {
position: absolute;
top: -57px;
left: calc(50% - 40px);
pointer-events: none;
}
.close {
position: absolute;
top: -5px;
right: -5px;
width: 18px;
height: 18px;
}
}
}
</style>

View File

@ -0,0 +1,193 @@
<template>
<view class="footer" :class="['footer_'+type]" :style="data.style" >
<view class="item" v-for="(item,index) in list" :key="index" :style="{'margin-bottom':$store.state.safeBottomAz+'px'}">
<image :src="`/static/image/call/${type}TabbarImg${index+1}.png`" mode=""></image>
<text>{{item}}</text>
</view>
</view>
<view class="footer footerZhangwei" :class="['footer_'+type]" :style="{'margin-top':$store.state.safeBottomAz+'px'}">
</view>
</template>
<script setup>
import {
onMounted,
reactive,
ref,
toRefs
} from 'vue'
const topPopup = ref()
// 定义组件属性
const props = defineProps({
bgColor: {
type: String,
default: '#fff'
},
textColor: {
type: String,
default: '#000'
},
title: {
type: String,
default: ''
},
type: {
type: String,
default: 'ios'
},
type2: {
type: String,
default: ''
},
})
const data = reactive({
statusBarHeight: 0,
showTipLayer: true,
style:{},
list: [
"个人收藏",
"最近通话",
"通讯录",
"拨号键盘",
"语音留言"
]
})
let {
list,
showTipLayer
} = toRefs(data)
onMounted(() => {
if (props.type == 'xiaomi') {
list.value = ["通话",
"联系人",
"营业厅"
]
}else if (props.type == 'oppo') {
list.value = ["通话",
"联系人",
"营业厅"
]
}else if (props.type == 'huawei') {
list.value = ["电话",
"联系人",
"收藏"
]
}else if (props.type == 'vivo') {
list.value = ["拨号",
"联系人",
"收藏"
]
}
if(props.type2=='ios'){
data.style={'background-color':props.bgColor}
}
})
</script>
<style scoped lang="scss">
.footer {
position: fixed;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: space-around;
background: #f7f7f7;
padding-top: 2px;
.item {
display: flex;
flex-direction: column;
text-align: center;
align-items: center;
image {
width: 32px;
height: 32px;
}
text {
font-size: 10px;
color: #959597;
}
}
}
.footer_ios {
padding-bottom: constant(safe-area-inset-bottom) !important; // 兼容 IOS<11.2
padding-bottom: env(safe-area-inset-bottom) !important; // 兼容 IOS>11.2
.item:nth-child(2) {
text {
color: #007AFC;
}
}
}
.footerZhangwei{
position: relative !important;
opacity: 0 !important;
padding-top: 0 !important;
height: 47px;
background-color: rgba(0, 0, 0, 0) !important;
}
.footer_huawei {
background: #FAFAFA !important;
padding-top: 3px;
.item {
image {
width: 24px;
height: 24px;
}
}
.item:nth-child(1) {
text {
color: #0060EA;
}
}
}
.footer_vivo {
background: #FAFAFA !important;
padding-top: 13px;
padding-bottom: 13px;
.item {
image {
width: 24px;
height: 24px;
}
}
.item:nth-child(1) {
text {
color: #1BA552;
}
}
}
.footer_xiaomi ,.footer_oppo{
padding-top: 7px;
padding-bottom: 17px;
background-color: #fff !important;
.item {
image {
width: 24px;
height: 24px;
}
text {
font-size: 10px;
color: #999999;
}
}
.item:nth-child(1) {
text {
color: #333;
}
}
}
</style>

View File

@ -0,0 +1,246 @@
<template>
<view class="auto-width-input-container" :class="{ 'is-textarea': type === 'textarea' }">
<!-- 测量层:用于计算宽度的影藏文本,必须保持与 input 相同的字体样式 -->
<text v-if="type !== 'textarea'" class="measure-text"
:style="[inputStyle, { visibility: 'hidden', position: 'absolute', whiteSpace: 'nowrap' }]">
{{ modelValue || placeholder }}
</text>
<!-- 输入层 -->
<template v-if="type !== 'textarea'">
<input v-if="!disabled" class="auto-input" :type="type" :value="modelValue" :placeholder="placeholder"
:placeholder-style="placeholderStyle" :style="[inputStyle, { width: finalInputWidth }]" @input="onInput"
:maxlength="maxlength" :focus="isFocus" @blur="onBlur" :readonly="readonly" />
<view v-else class="auto-input flex-align-center"
:style="[inputStyle, { width: finalInputWidth }, !modelValue ? placeholderStyleObject : {}]">
{{ modelValue || placeholder }}
</view>
</template>
<template v-else>
<textarea v-if="!disabled" class="auto-textarea" :value="modelValue" :placeholder="placeholder"
:placeholder-style="placeholderStyle" :style="[inputStyle]" @input="onInput" :maxlength="maxlength"
auto-height :focus="isFocus" @blur="onBlur" :readonly="readonly" />
<view v-else class="auto-textarea" :style="[inputStyle, !modelValue ? placeholderStyleObject : {}]">
{{ modelValue || placeholder }}
</view>
</template>
<!-- 编辑图标 -->
<image v-if="showEdit" class="edit-icon" src="/static/image/common/edit.png" @click="handleFocusIcon"></image>
</view>
</template>
<script setup>
import { ref, computed, watch, nextTick, getCurrentInstance, onMounted } from 'vue';
const props = defineProps({
modelValue: {
type: [String, Number],
default: ''
},
type: {
type: String,
default: 'text' // 支持所有原生类型如 'number', 'tel', 'digit' 或 'textarea'
},
placeholder: {
type: String,
default: '请输入'
},
placeholderStyle: {
type: String,
default: 'color: #999;'
},
fontSize: {
type: String,
default: '28rpx'
},
fontWeight: {
type: String,
default: 'normal'
},
color: {
type: String,
default: '#1A1A1A'
},
maxlength: {
type: Number,
default: 140
},
minWidth: {
type: String,
default: '20rpx'
},
extraWidth: {
type: Number,
default: 10 // 额外的缓冲像素,防止文字抖动
},
showEdit: {
type: Boolean,
default: false
},
readonly: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
}
});
const emit = defineEmits(['update:modelValue', 'change']);
const instance = getCurrentInstance();
const inputWidth = ref(props.minWidth);
// 最终应用的宽度样式
const finalInputWidth = computed(() => {
// 如果是 textarea 或者设置了填满父级,则不使用测量出的宽度
if (props.type === 'textarea') return '100%';
// 尝试检测 class 中是否包含撑开逻辑
const classStr = instance.proxy.$attrs.class || '';
if (classStr.includes('flex-1') || classStr.includes('w100')) {
return '100%';
}
return inputWidth.value;
});
const isFocus = ref(false);
const placeholderStyleObject = computed(() => {
const style = {};
if (!props.placeholderStyle) return style;
const parts = props.placeholderStyle.split(';');
parts.forEach(part => {
const [key, value] = part.split(':');
if (key && value) {
const camelKey = key.trim().replace(/-([a-z])/g, (g) => g[1].toUpperCase());
style[camelKey] = value.trim();
}
});
return style;
});
const inputStyle = computed(() => ({
fontSize: props.fontSize,
fontWeight: props.fontWeight,
color: props.color,
fontFamily: 'inherit'
}));
/**
* 点击图标触发聚焦
*/
const handleFocusIcon = () => {
isFocus.value = false;
nextTick(() => {
isFocus.value = true;
});
};
/**
* 失去焦点处理
*/
const onBlur = () => {
isFocus.value = false;
};
/**
* 核心逻辑:测量隐藏文本的物理宽度
*/
const updateWidth = () => {
if (props.type === 'textarea') return;
// 如果是强制撑开模式,理论上不需要测量,但为了防止切换状态时的布局闪烁,我们仍保持测量
nextTick(() => {
const query = uni.createSelectorQuery().in(instance.proxy);
query.select('.measure-text').boundingClientRect(data => {
if (data && data.width) {
heatWidth(data.width);
}
}).exec();
});
};
const heatWidth = (width) => {
// 加上一点额外的空间,避免在某些平台上因为小数点或字体渲染导致的文字换行
inputWidth.value = (width + props.extraWidth) + 'px';
};
const onInput = (e) => {
const val = e.detail.value;
emit('update:modelValue', val);
emit('change', val);
};
// 监听值的变化实时更新宽度
watch(() => props.modelValue, () => {
updateWidth();
});
// 初始化
onMounted(() => {
updateWidth();
});
</script>
<style lang="less" scoped>
.auto-width-input-container {
position: relative;
display: inline-flex; // 默认维持自适应宽度
align-items: center;
vertical-align: middle;
max-width: 100%;
flex-wrap: nowrap;
// 当父级赋予 flex-1 或手动设置 width: 100% 时,转为标准 flex 布局
&.flex-1,
&.w100 {
display: flex !important;
width: 100% !important;
}
&.is-textarea {
display: flex !important;
width: 100%;
align-items: flex-start;
flex-direction: row !important;
}
.measure-text {
left: -9999rpx;
top: -9999rpx;
pointer-events: none;
}
.auto-input {
min-width: v-bind('props.minWidth');
padding: 0;
margin: 0;
text-align: inherit;
height: 1.4em;
line-height: 1.4em;
flex-shrink: 0;
// 如果容器是撑开的,input 也应该撑开
& {
flex: 1;
}
}
.auto-textarea {
flex: 1 !important;
width: 0 !important;
min-width: 0;
min-height: 1.4em;
padding: 0;
margin: 0;
line-height: 1.4em;
display: block;
}
.edit-icon {
width: 28rpx;
height: 28rpx;
margin-left: 8rpx;
flex-shrink: 0;
margin-top: 4rpx;
}
}
</style>

View File

@ -0,0 +1,223 @@
<template>
<view>
<view class="right-btn" @click="open">
<image src="/static/image/recharge/rightBtnImg.png" mode=""></image>
</view>
<view class="maskExchange" v-if="isMaskExchange">
<view class="box">
<view class="info" v-if="!isExchange">
<view :style="styles">
<uni-easyinput :styles="styles" v-model="code" placeholder="请输入兑换码" :inputBorder="false"
placeholderStyle="text-align: center;"></uni-easyinput>
</view>
<view class="btn" :class="{'noValue':code==''}" @click="getExchange">
兑换
</view>
</view>
<view class="info" v-else>
<view class="title">
{{couponVip.exchange_name}}
</view>
<view class="title">
{{couponVip.exchange_value}}
</view>
<view class="time">
截至日期:{{couponVip.expire_time}}
</view>
<view class="btn btn2" @click="submit">
兑换
</view>
</view>
</view>
<view class="close" @click="close">
<image src="/static/image/recharge/closeE.png" mode=""></image>
</view>
</view>
</view>
</template>
<script>
import {
postJson
} from "@/utils/requests.js"
export default {
name: "exchange",
data() {
return {
code: "",
isMaskExchange: false,
isExchange: false,
couponVip: {
},
styles: {
backgroundColor: '#FFFCEC',
padding: '10px ',
'border-radius': '10px'
}
};
},
methods: {
open() {
this.isMaskExchange = true
this.isExchange = false
this.code = ''
},
close() {
this.isMaskExchange = false
this.code = ''
},
async getExchange() {
if (this.code == '') {
return
}
let couponVip = await this.$requestPromise({
url: 'api/activity/exchange',
method: "GET",
data: {
code: this.code
}
})
console.log(couponVip)
if (couponVip.code != 0) {
uni.showToast({
icon: "none",
title: "兑换码有误"
})
return
}
this.isExchange = true
this.couponVip = couponVip.data
},
async submit() {
let exchangeRes = await postJson('q', 'api/activity/exchange', {
code: this.code
})
if (exchangeRes.code != 0) {
uni.showToast({
icon: "none",
title: "券已使用过了"
})
return
} else {
uni.showToast({
icon: "none",
title: "兑换成功"
})
//获取app用户信息
let user = await proxy.$requestPromise({
url: 'api/user',
method: "GET",
data: {}
})
if (user.code == 0) {
data.appUser = user.data
console.log("app用户信息", data.appUser);
}
this.isMaskExchange = false
this.isExchange = false
uni.navigateBack({
delta: 1
});
}
}
}
}
</script>
<style lang="scss">
.maskExchange {
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
width: 100vw;
height: 100vh;
z-index: 999;
background-color: rgba(0, 0, 0, .5);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.box {
background-image: url('/static/image/recharge/exchange.png');
background-size: 307px 120px;
background-repeat: no-repeat;
width: 307px;
padding: 120px 0 20px 0;
.info {
padding: 16px;
background-color: #fff;
border-radius: 0 0 26px 26px;
input {
background: #FFFCEC;
border-radius: 11px 11px 11px 11px;
padding: 18px 0;
text-align: center;
}
.title {
font-size: 20px;
color: #1a1a1a;
text-align: center;
margin-bottom: 24px;
}
.time {
font-size: 16px;
color: #AAAAAA;
text-align: center;
}
}
.btn {
color: #fff;
margin-top: 32px;
font-size: 18px;
padding: 16px 0;
color: #FFFFFF;
background: linear-gradient(360deg, #4B3F30 0%, #181713 56.43%, #504834 100%), #D8D8D8;
border-radius: 60px;
text-align: center;
}
.btn2 {
margin-top: 12px;
}
.noValue {
opacity: 0.5;
}
}
.close {
margin-top: 16px;
width: 34px;
height: 34px;
image {
width: 100%;
height: 100%;
}
}
}
.right-btn {
position: fixed;
top: 100rpx;
right: 36rpx;
z-index: 1;
width: 54px;
height: 26px;
image {
width: 100%;
height: 100%;
}
}
</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,327 @@
<template>
<view class="preview-container" :class="phone === 'mi' ? 'mi-bg' : 'oppo-bg'" v-if="show">
<template v-if="phone === 'oppo' || !phone || phone === 'iphone' || phone === 'huawei' || phone === 'vivo'">
<view class="header" @tap.stop :style="{ 'padding-top': statusBarHeight }">
<image class="icon-back" src="/static/image/phone-message/oppo/back-white.png" @tap="close"></image>
<image class="icon-download" src="/static/image/phone-message/oppo/save-white.png"></image>
</view>
<!-- 图片显示 -->
<swiper class="preview-swiper" :current="current" @change="onChange">
<swiper-item v-for="(imgSrc, index) in images" :key="index">
<image class="preview-img" :src="imgSrc" mode="aspectFit"></image>
</swiper-item>
</swiper>
</template>
<template v-else-if="phone === 'mi'">
<view class="mi-header" @tap.stop :style="{ 'padding-top': statusBarHeight }">
<!-- 复用现有返回图标并通过滤镜反色 -->
<image class="mi-icon-back" src="/static/image/phone-message/oppo/back-white.png" @tap="close"></image>
<view class="mi-header-center">
<view class="mi-date">{{ currentFormatDate }}</view>
<view class="mi-time">{{ currentFormatTime }}</view>
</view>
<view class="mi-icon-right"></view> <!-- 占位符以居中 -->
</view>
<swiper class="preview-swiper mi-swiper" :current="current" @change="onChange">
<swiper-item v-for="(imgSrc, index) in images" :key="index">
<view class="mi-img-container">
<image class="mi-preview-img" :src="imgSrc" mode="aspectFill"></image>
</view>
</swiper-item>
</swiper>
<view class="mi-footer">
<view class="mi-footer-item">
<image class="mi-footer-icon" src="/static/image/phone-message/mi/baocun.png"></image>
<view class="mi-footer-text">保存</view>
</view>
<view class="mi-footer-item">
<view class="mi-more-icon">⋯</view>
<view class="mi-footer-text">更多</view>
</view>
</view>
</template>
</view>
</template>
<script setup>
import { ref, watch, computed } from 'vue';
const statusBarHeight = (uni.getSystemInfoSync().statusBarHeight || 44) + 'px';
const props = defineProps({
show: Boolean,
images: {
type: Array,
default: () => []
},
times: {
type: Array,
default: () => []
},
currentIndex: {
type: Number,
default: 0
},
phone: {
type: String,
default: ''
}
});
const emit = defineEmits(['update:show']);
const current = ref(0);
watch(() => props.currentIndex, (val) => {
current.value = val;
});
const onChange = (e) => {
current.value = e.detail.current;
};
const currentFormatDate = computed(() => {
if (props.times && props.times.length > current.value) {
const timeVal = props.times[current.value];
if (timeVal) {
if (typeof timeVal === 'string' && timeVal.includes('-')) {
const parts = timeVal.split(' ');
if (parts.length > 0) {
const dateParts = parts[0].split('-');
if (dateParts.length === 3) {
return `${dateParts[0]}年${parseInt(dateParts[1])}月${parseInt(dateParts[2])}日`;
}
}
}
// Fallback
const safeTimeStr = String(timeVal).replace(/-/g, '/');
const d = new Date(safeTimeStr);
if (!isNaN(d.getTime())) {
return `${d.getFullYear()}年${d.getMonth() + 1}月${d.getDate()}日`;
}
}
}
return '';
});
const currentFormatTime = computed(() => {
if (props.times && props.times.length > current.value) {
const timeVal = props.times[current.value];
if (timeVal) {
if (typeof timeVal === 'string' && timeVal.includes(':')) {
const parts = timeVal.split(' ');
if (parts.length > 1) {
return parts[1];
}
}
// Fallback
const safeTimeStr = String(timeVal).replace(/-/g, '/');
const d = new Date(safeTimeStr);
if (!isNaN(d.getTime())) {
const h = d.getHours().toString().padStart(2, '0');
const m = d.getMinutes().toString().padStart(2, '0');
return `${h}:${m}`;
}
}
}
return '';
});
const close = () => {
emit('update:show', false);
};
// const download = () => {
// const currentSrc = props.images[current.value];
// if (!currentSrc) return;
// // 触发下载逻辑
// uni.saveImageToPhotosAlbum({
// filePath: currentSrc,
// success: function () {
// uni.showToast({
// title: '保存成功',
// icon: 'success'
// });
// },
// fail: function () {
// uni.showToast({
// title: '保存失败',
// icon: 'none'
// });
// }
// });
// };
</script>
<style scoped>
.preview-container {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 999999;
display: flex;
flex-direction: column;
}
.oppo-bg {
background-color: #000000;
}
.mi-bg {
background-color: #FFFFFF;
}
.header {
box-sizing: content-box;
width: 100%;
/* 预留状态栏高度 */
height: 98rpx;
display: flex;
justify-content: space-between;
align-items: center;
position: absolute;
top: 0;
left: 0;
z-index: 2;
background-color: rgba(0, 0, 0, 0.6);
}
.icon-back {
width: 48rpx;
height: 48rpx;
margin: 0 34rpx;
}
.icon-download {
width: 48rpx;
height: 48rpx;
margin: 0 34rpx;
}
.preview-swiper {
width: 100vw;
height: 100vh;
position: absolute;
top: 0;
left: 0;
z-index: 1;
}
.preview-img {
width: 100%;
height: 100%;
display: block;
}
/* ================= 小米样式 ================= */
.mi-header {
box-sizing: content-box;
width: 100%;
height: 98rpx;
display: flex;
justify-content: space-between;
align-items: center;
position: absolute;
top: 0;
left: 0;
z-index: 2;
background-color: #FFFFFF;
}
.mi-icon-back {
width: 48rpx;
height: 48rpx;
margin: 0 34rpx;
filter: invert(1);
/* 白变黑 */
}
.mi-icon-right {
width: 48rpx;
height: 48rpx;
margin: 0 34rpx;
}
.mi-header-center {
display: flex;
flex-direction: column;
align-items: center;
}
.mi-date {
font-size: 32rpx;
color: #333333;
font-weight: 500;
}
.mi-time {
font-size: 24rpx;
color: #999999;
margin-top: 4rpx;
}
.mi-swiper {
z-index: 1;
display: flex;
align-items: center;
}
.mi-img-container {
width: 100vw;
height: 100vw;
position: absolute;
top: 50%;
transform: translateY(-50%);
}
.mi-preview-img {
width: 100%;
height: 100%;
display: block;
}
.mi-footer {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 160rpx;
background-color: #FFFFFF;
display: flex;
justify-content: space-around;
align-items: flex-start;
padding-top: 20rpx;
z-index: 2;
}
.mi-footer-item {
display: flex;
flex-direction: column;
align-items: center;
}
.mi-footer-icon {
width: 48rpx;
height: 48rpx;
margin-bottom: 8rpx;
}
.mi-more-icon {
width: 48rpx;
height: 48rpx;
line-height: 38rpx;
font-size: 40rpx;
text-align: center;
color: #000;
margin-bottom: 8rpx;
font-weight: bold;
}
.mi-footer-text {
font-size: 24rpx;
color: #666666;
}
</style>

View File

@ -0,0 +1,778 @@
<template>
<view :style="`${phone}-style`">
<uni-swipe-action class="swipe-action">
<!-- 使用插槽 (请自行给定插槽内容宽度)-->
<uni-swipe-action-item class="swipe-action-item" v-for="item in list" :key="item.id">
<view class="flex flex-align-center " @click="clickItem(item)">
<view class="item flex w100">
<view class="flex flex-align-center left-box">
<view :class="{ 'opacity-0': !item.unRead }" class="dot shrink-0"><text
v-if="phone == 'huawei' || phone == 'oppo'">{{ item.unReadNumber
> 99
? '99+' : (item.unReadNumber || 1) }}</text>
</view>
<image class="img avatar shrink-0" :class="item.imgShape"
:src="item.img || `/static/image/phone-message/${phone}/default.png`" mode="aspectFill">
</image>
</view>
<view class="border-box m-l-24 flex-1 flex flex-align-start">
<view class="main-box flex-1">
<view class="title-box flex-between">
<text class="title">{{ displayTitle(item.title) }}</text>
<text class="time">{{ formatDate(getLastMessage(item.chatList)?.time || item.time)
}}</text>
</view>
<view class="content">
<view v-if="isImageMsg(getLastMessage(item.chatList))"
class="flex flex-align-center">
<template v-if="phone === 'iphone'">
<text>附件: 1张照片</text>
</template>
<template v-else-if="phone === 'huawei'">
<text>您的好友给您发了一个图片</text>
</template>
<template v-else-if="phone == 'oppo'">
<image style="width: 24rpx;height: 24rpx;margin-right: 16rpx;"
src="/static/image/phone-message/oppo/link.png"></image>
<text>[图片]</text>
</template>
<template v-else>
<text>[图片]</text>
</template>
</view>
<rich-text v-else :nodes="getLastMessage(item.chatList)?.content || ''"></rich-text>
</view>
</view>
<view class="box-right h100 flex-column flex-align-center">
<image v-if="phone == 'iphone'" src="/static/image/phone-message/iphone/right.png">
</image>
<image v-if="item.noNotice && phone == 'iphone'" class="m-t-8"
src="/static/image/phone-message/iphone/notice.png">
</image>
</view>
</view>
</view>
</view>
<template v-slot:right>
<view class="flex flex-align-center" style="margin-left: 1px;">
<view class="btn flex-center flex-align-center edit" style="color: #fff;"
@click="editItem(item)">
<image :src="`/static/image/phone-message/edit.png`">
</image>
</view>
<view class="btn flex-center flex-align-center delete" @click="deleteItem(item)">
<image
:src="`/static/image/phone-message/${phone == 'huawei' || phone == 'vivo' ? 'huawei' : 'iphone'}/delete.png`">
</image>
</view>
</view>
</template>
</uni-swipe-action-item>
</uni-swipe-action>
</view>
</template>
<script setup>
import {
dateUtil
} from '@/utils/common.js';
// 定义事件
const emit = defineEmits(['item-click', 'delete-item', 'edit-item'])
const props = defineProps({
// 手机品牌
phone: {
type: String,
default: 'iphone'
},
list: {
type: Array,
default: () => []
}
})
/**
* 时间日期格式化判断
* @param date
*/
const formatDate = (date) => {
if (props.phone == 'oppo') {
return dateUtil.formatMessageTime(date, true)
} else if (props.phone == 'huawei') {
return dateUtil.formatMessageTime(date, true, 'YYYY年M月D日')
} else if (props.phone == 'vivo') {
let d = date;
if (typeof d === 'string') {
d = new Date(d.replace(/-/g, '/'));
} else if (typeof d === 'number') {
d = new Date(d);
}
const isCurrentYear = d.getFullYear() === new Date().getFullYear();
return dateUtil.formatMessageTime(date, true, isCurrentYear ? 'M月D日' : 'YYYY/M/D')
} else {
return dateUtil.formatMessageTime(date)
}
}
/**
* 格式化联系人名称,处理 OPPO 纯数字11位手机号显示
*/
const displayTitle = (title) => {
let t = title || '';
if (props.phone === 'oppo') {
const noSpace = t.replace(/\s+/g, '');
if (/^\d{11}$/.test(noSpace)) {
return noSpace.replace(/(\d{3})(\d{4})(\d{4})/, '$1 $2 $3');
}
}
return t;
}
/**
* 判断该消息是否为图片消息
*/
const isImageMsg = (message) => {
if (!message) return false;
if (message.type === 'image') return true;
if (message.content && typeof message.content === 'string') {
const str = message.content.trim();
if (str.startsWith('<img') && str.endsWith('/>') && str.indexOf('<img') === str.lastIndexOf('<img')) {
return true;
}
}
return false;
}
/**
* 获取最新一条有效消息
*/
const getLastMessage = (chatList) => {
if (!chatList || chatList.length === 0) return null;
return chatList[chatList.length - 1];
}
/**
* 点击列表元素
*/
const clickItem = (item) => {
emit('item-click', item)
}
/**
* 删除列表元素
*/
const deleteItem = (item) => {
emit('delete-item', item)
}
/**
* 修改元素
* @param item
*/
const editItem = (item) => {
emit('edit-item', item)
}
</script>
<style>
@import '@/common/main.css';
</style>
<style lang="less" scoped>
.m-t-4 {
margin-top: 4rpx;
}
.m-t-8 {
margin-top: 8rpx;
}
.m-l-24 {
margin-left: 24rpx;
}
.opacity-0 {
opacity: 0;
}
.swipe-action-item {
background-color: #FFFFFF;
}
.avatar {
border-radius: 50%;
}
.edit {
background-color: #5855D6;
image {
width: 48rpx;
height: 48rpx;
}
}
.main-box {
.title-box {
margin-bottom: 6rpx;
.title {
width: 20px;
flex: 1;
color: #1A1A1A;
font-size: 32rpx;
line-height: 32rpx;
white-space: nowrap;
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
}
.time {
flex-shrink: 0;
margin-left: 20rpx;
}
}
}
// 苹果样式
.iphone-style {
.circle {
border-radius: 50% !important;
}
.square {
border-radius: 16rpx !important;
}
.swipe-action {
margin-top: 28rpx;
}
.swipe-action-item:first-child {
.border-box {
position: relative;
}
.border-box::before {
position: absolute;
content: '';
width: 100%;
height: 1px;
top: 0;
left: 0;
right: 0;
background-color: #D8D8D8;
transform: scaleY(0.3);
}
}
.item {
padding: 0 0 0 14rpx;
height: 146rpx;
align-items: center;
.dot {
width: 20rpx;
height: 20rpx;
background-color: #007BFD;
border-radius: 50%;
}
.img {
width: 84rpx;
height: 84rpx;
margin-left: 16rpx;
border-radius: 50%;
}
.border-box {
position: relative;
padding: 18rpx 24rpx 14rpx 0;
height: 100%;
// box-shadow: inset 0 -0.3px 0 0 #D8D8D8;
}
.border-box::after {
position: absolute;
content: '';
width: 100%;
height: 1px;
bottom: 0;
left: 0;
right: 0;
background-color: #D8D8D8;
transform: scaleY(0.3);
}
.main-box {
.title-box {
margin-bottom: 6rpx;
.title {
color: #1A1A1A;
font-size: 32rpx;
line-height: 32rpx;
font-weight: 600;
}
.time {
color: #838383;
font-size: 28rpx;
line-height: 28rpx;
margin-right: 18rpx;
}
}
.content {
font-size: 28rpx;
line-height: 38rpx;
color: #838383;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
}
.box-right {
image {
width: 28rpx;
height: 28rpx;
}
}
}
.btn {
width: 140rpx;
height: 146rpx;
image {
width: 48rpx;
height: 48rpx;
}
}
.delete {
background-color: #FC3E30;
}
.edit {
background-color: #5855D6;
}
}
// 小米样式
.mi-style {
.item {
padding-top: 44rpx;
height: 170rpx;
.left-box {
position: relative;
height: 40px;
align-items: flex-start;
padding-left: 54rpx;
}
.dot {
position: absolute;
width: 20rpx;
height: 20rpx;
background-color: #FA3D30;
border-radius: 50%;
left: 16rpx;
top: 50%;
transform: translateY(-50%);
}
.img {
width: 76rpx;
height: 76rpx;
}
.border-box {
position: relative;
padding: 4rpx 52rpx 8rpx 0;
margin-left: 22rpx;
height: 100%;
}
.main-box {
.title-box {
margin-bottom: 6rpx;
.title {
color: #1A1A1A;
font-size: 32rpx;
line-height: 32rpx;
}
.time {
color: #9A9A9A;
font-size: 26rpx;
line-height: 28rpx;
}
}
.content {
font-size: 26rpx;
line-height: 38rpx;
color: #656565;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
}
.box-right {
image {
width: 28rpx;
height: 28rpx;
}
}
}
.btn {
width: 140rpx;
height: 146rpx;
image {
width: 48rpx;
height: 48rpx;
}
}
.delete {
background-color: #FC3E30;
}
.edit {
background-color: #5855D6;
image {
width: 48rpx;
height: 48rpx;
}
}
}
// oppo样式
.oppo-style {
.item {
padding-top: 28rpx;
height: 178rpx;
.left-box {
position: relative;
height: 40px;
align-items: flex-start;
padding-left: 34rpx;
}
.dot {
width: 28rpx;
height: 28rpx;
position: absolute;
display: flex;
align-items: center;
justify-content: center;
background-color: #E93A22;
color: #FFFFFF;
line-height: 18rpx;
font-size: 20rpx;
border-radius: 16rpx;
right: -8rpx;
top: -4rpx;
z-index: 1;
border: 2rpx solid #FFFFFF;
}
.img {
width: 76rpx;
height: 76rpx;
}
.border-box {
position: relative;
padding: 4rpx 0 28rpx 0;
margin-left: 32rpx;
margin-right: 34rpx;
height: 100%;
}
.border-box::after {
position: absolute;
content: '';
width: 100%;
height: 1px;
bottom: 0;
left: 0;
right: 0;
background-color: #E2E2E2;
transform: scaleY(0.3);
}
.main-box {
.title-box {
margin-bottom: 16rpx;
.title {
color: #1A1A1A;
font-size: 32rpx;
line-height: 32rpx;
font-weight: 500;
}
.time {
color: #656565;
font-size: 26rpx;
line-height: 28rpx;
}
}
.content {
font-size: 28rpx;
line-height: 38rpx;
color: #737373;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
}
.box-right {
image {
width: 28rpx;
height: 28rpx;
}
}
}
.btn {
width: 140rpx;
height: 146rpx;
image {
width: 48rpx;
height: 48rpx;
}
}
.delete {
background-color: #FC3E30;
}
}
// 华为样式
.huawei-style {
.item {
padding: 0 32rpx;
height: 156rpx;
align-items: center;
.left-box {
position: relative;
height: 40px;
align-items: flex-start;
}
.dot {
position: absolute;
padding: 6rpx 10rpx;
background-color: #E93A22;
color: #FFFFFF;
line-height: 20rpx;
font-size: 20rpx;
border-radius: 16rpx;
right: -8rpx;
top: -12rpx;
z-index: 1;
}
.img {
width: 76rpx;
height: 76rpx;
}
.border-box {
padding: 18rpx 0;
position: relative;
margin-left: 30rpx;
margin-top: 2rpx;
height: 100%;
box-shadow: 0 -0.3px 0 0 #CFCFCF;
}
// .border-box::after {
// position: absolute;
// content: '';
// width: 100%;
// height: 1px;
// bottom: 0;
// left: 0;
// right: 0;
// background-color: #CFCFCF;
// transform: scaleY(0.3);
// }
.main-box {
.title-box {
margin-bottom: 6rpx;
.title {
color: #1A1A1A;
font-size: 32rpx;
line-height: 32rpx;
font-weight: 500;
}
.time {
color: #656565;
font-size: 24rpx;
line-height: 28rpx;
}
}
.content {
font-size: 28rpx;
line-height: 38rpx;
color: #6F6F6F;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
}
.box-right {
image {
width: 28rpx;
height: 28rpx;
}
}
}
.btn {
width: 140rpx;
height: 156rpx;
image {
width: 76rpx;
height: 76rpx;
}
}
.delete {
background-color: #F4F4F4;
}
.edit {
background-color: #5855D6;
image {
width: 48rpx;
height: 48rpx;
}
}
}
// vivo样式
.vivo-style {
.item {
padding: 0 50rpx 0 24rpx;
height: 172rpx;
align-items: center;
.dot {
width: 12rpx;
height: 12rpx;
background-color: #409DFE;
border-radius: 50%;
flex-shrink: 0;
margin-right: 10rpx;
}
.img {
width: 80rpx;
height: 80rpx;
}
.border-box {
padding: 28rpx 0 18rpx;
position: relative;
margin-left: 24rpx;
margin-top: 2rpx;
height: 100%;
}
.main-box {
.title-box {
margin-bottom: 18rpx;
.title {
color: #1A1A1A;
font-size: 32rpx;
line-height: 32rpx;
font-weight: 500;
}
.time {
color: #7C7C7C;
font-size: 26rpx;
line-height: 26rpx;
}
}
.content {
font-size: 28rpx;
line-height: 38rpx;
color: #6F6F6F;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
}
.box-right {
image {
width: 28rpx;
height: 28rpx;
}
}
}
.btn {
width: 140rpx;
height: 156rpx;
image {
width: 76rpx;
height: 76rpx;
}
}
.delete {
background-color: #F4F4F4;
}
.edit {
background-color: #5855D6;
image {
width: 48rpx;
height: 48rpx;
}
}
}
</style>

View File

@ -0,0 +1,596 @@
<template>
<view :class="`${phone}-style`">
<!-- 导航样式 -->
<view class="nav-bar-box" :class="{ 'border-nav-bar-box': isScroll }">
<NavBar :isBack="false" :bgColor="isScroll ? navBarBgColor : '#fff'" :buttonGroup="buttonGroup"
@button-click="util.clickTitlePopupButton" tipLayerType="message-list-tip" isTipLayer isClickNavBarOpenPopup
tipLayerText="添加短信" @add="emit('add')">
<!-- 左侧文字图标 -->
<template v-slot:left>
<view v-if="phone == 'iphone'" class="flex flex-align-center">
<image @click="util.goBack" class="left-icon" src="/static/image/phone-message/iphone/back.png" mode="">
</image>
<text class="left-text">过滤条件</text>
</view>
<view v-if="(phone == 'huawei' && isScroll) || phone == 'vivo'" class="flex flex-align-center">
<text class="left-text">信息</text>
</view>
</template>
<!-- 中间标题 -->
<template v-slot:center>
<view v-if="phone == 'iphone' && isScroll" class="center-text">
信息
</view>
</template>
<!-- 右侧图标 -->
<template v-slot:right>
<!-- iphone -->
<view v-if="phone == 'iphone'">
<image class="right-icon mg-r-30" src="/static/image/phone-message/iphone/more.png" mode="">
</image>
<image class="right-icon mg-r-5" src="/static/image/phone-message/iphone/edit.png" mode="">
</image>
</view>
<!-- mi -->
<view v-if="phone == 'mi'">
<image class="right-icon" src="/static/image/phone-message/mi/setting.png" mode=""></image>
</view>
<!-- oppo -->
<view v-if="phone == 'oppo'">
<image class="right-icon mg-r-52" src="/static/image/phone-message/oppo/search.png" mode="">
</image>
<image class="right-icon mg-r-14" src="/static/image/phone-message/oppo/more.png" mode="">
</image>
</view>
<!-- huawei -->
<view v-if="phone == 'huawei'">
<image v-if="isScroll" class="right-icon" src="/static/image/phone-message/huawei/nav-search.png" mode="">
</image>
<image class="right-icon" src="/static/image/phone-message/huawei/add.png" mode=""></image>
<image class="right-icon" src="/static/image/phone-message/huawei/more.png" mode=""></image>
</view>
<!-- vivo -->
<view v-if="phone == 'vivo'">
<image class="right-icon" src="/static/image/phone-message/vivo/select.png" mode="">
</image>
<image class="right-icon" src="/static/image/phone-message/vivo/add.png" mode=""></image>
<image class="right-icon m-r-34" src="/static/image/phone-message/vivo/more.png" mode="">
</image>
</view>
</template>
</NavBar>
</view>
<!-- 主体内容 -->
<view class="main-container">
<!-- 顶部搜索栏样式 -->
<view class="top-box">
<view v-if="showInfo.text" class="text">{{ showInfo.text }}</view>
<view v-if="showInfo.secondText" class="second-text">{{ showInfo.secondText }}</view>
<view v-if="showInfo.placeholder" class="search-box flex flex-align-center">
<image class="icon" :src="`/static/image/phone-message/${props.phone}/search.png`">
</image>
<input class="input flex-1" :placeholder="showInfo.placeholder" type="text" disabled>
<image v-if="phone != 'mi'" class="icon" :src="`/static/image/phone-message/${props.phone}/mic.png`">
</image>
</view>
</view>
<slot>
</slot>
</view>
<view class="bottom-placeholder" :style="{'margin-top':$store.state.safeBottomAz+'px'}"></view>
<!-- 底部样式 -->
<template v-if="phone == 'mi' || phone == 'oppo'">
<image class="add-message" @click="emit('add')"
:src="`/static/image/phone-message/${props.phone}/add-message.png`"></image>
<view class="bottom-box " :style="{height: (74+$store.state.safeBottomAz)+'px'}">
<view class="flex" :style="{height: '148rpx'}">
<view class="item flex-1 h100">
<image class="icon" :src="`/static/image/phone-message/${props.phone}/bottom-left.png`"></image>
<text>{{ showInfo.bottomLtext }}</text>
</view>
<view class="item flex-1 h100">
<image class="icon" :src="`/static/image/phone-message/${props.phone}/bottom-right.png`"></image>
<text class="grey">{{ showInfo.bottomRtext }}</text>
</view>
</view>
</view>
</template>
</view>
</template>
<script setup>
import NavBar from '@/components/nav-bar/nav-bar.vue'
import {
reactive,
computed
} from 'vue'
import {
util
} from '@/utils/common.js';
const emit = defineEmits(['add', 'setSim', 'setNoticeCount'])
const props = defineProps({
// 手机品牌
phone: {
type: String,
default: 'iphone'
},
// 是否向下滚动
isScroll: {
type: Boolean,
default: false
},
noticeCount: {
type: Number,
default: 0
}
})
const buttonGroup = computed(() => {
const groups = [{
name: "添加短信",
click: () => {
emit('add')
}
}, {
name: "设置卡1卡2运营商",
click: () => {
emit('setSim')
}
}]
if (props.phone == 'huawei') {
groups.push({
name: "设置通知信息未读数",
click: () => {
emit('setNoticeCount')
}
})
}
return groups
})
const navBarBgColor = computed(() => {
return props.phone === 'iphone' ? '#F8F8F8' : '#FFFFFF';
})
const data = reactive({
navBar: {
title: '信息',
},
})
// 展示文字信息
const showInfo = computed(() => {
let text, placeholder, secondText, bottomLtext, bottomRtext
switch (props.phone) {
case "iphone":
text = "信息"
placeholder = '搜索'
break;
case "mi":
text = "主要"
placeholder = '搜索短信'
bottomLtext = "主要"
bottomRtext = "推广"
break;
case "oppo":
text = "消息"
placeholder = ''
bottomLtext = "消息"
bottomRtext = "通知"
break;
case "huawei":
text = "信息"
placeholder = '搜索信息'
secondText = props.noticeCount > 0 ? `${props.noticeCount} 条未读` : ''
break;
case "vivo":
placeholder = '搜索信息'
break;
default:
break;
}
return {
text,
placeholder,
secondText,
bottomLtext,
bottomRtext
}
})
</script>
<style>
@import '@/common/main.css';
page {
background-color: #FFFFFF;
}
</style>
<style lang="less" scoped>
::v-deep .uni-navbar__header-btns {
width: 100px !important;
flex: 1;
}
::v-deep .uni-navbar__header {
align-items: center !important;
}
.bottom-box {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 120rpx;
border-top: 1rpx solid #E7E7E7;
background-color: #ffffff;
z-index: 9;
.item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.icon {
width: 44rpx;
height: 44rpx;
}
text {
font-size: 20rpx;
color: #333;
}
.grey {
color: #999999;
}
}
}
// 苹果机型样式
.iphone-style {
.mg-r-30 {
margin-right: 60rpx;
}
.mg-r-5 {
margin-right: 10rpx;
}
.left-icon {
width: 40rpx;
height: 40rpx;
}
.right-icon {
width: 48rpx;
height: 48rpx;
}
.left-text {
font-size: 32rpx;
color: #0278E2;
margin-left: 10rpx;
}
.center-text {
font-size: 32rpx;
color: #1a1a1a;
}
.border-nav-bar-box {
::v-deep .nav-bar-container {
box-shadow: 0 -0.3px 0 0 #B5B5B5 inset;
}
}
.main-container {
.top-box {
padding: 20rpx 32rpx 0;
.text {
color: #1A1A1A;
font-size: 64rpx;
font-weight: 700;
line-height: 72rpx;
}
.search-box {
margin-top: 20rpx;
background-color: #EEEEF0;
height: 68rpx;
border-radius: 24rpx;
padding: 0 16rpx;
.icon {
width: 32rpx;
height: 32rpx;
}
.input {
margin: 0 14rpx;
::v-deep .input-placeholder {
color: #838383;
font-size: 32rpx;
}
}
}
}
}
}
// 小米机型样式
.mi-style {
.right-icon {
width: 40rpx;
height: 40rpx;
margin-right: 30rpx;
}
.main-container {
.top-box {
padding: 0 22rpx 0;
.text {
color: #1A1A1A;
font-size: 60rpx;
font-weight: 500;
}
.search-box {
margin-top: 32rpx;
background-color: #F0F0F0;
height: 84rpx;
border-radius: 42rpx;
padding: 0 34rpx;
.icon {
width: 32rpx;
height: 32rpx;
}
.input {
margin: 0 18rpx;
::v-deep .input-placeholder {
color: #A9A9A9;
font-size: 32rpx;
}
}
}
}
}
.add-message {
position: fixed;
bottom: 168rpx;
right: 44rpx;
width: 120rpx;
height: 120rpx;
}
.bottom-placeholder {
height: 120rpx;
width: 100%;
}
}
// oppo机型
.oppo-style {
.mg-r-52 {
margin-right: 52rpx;
}
.mg-r-14 {
margin-right: 14rpx;
}
.right-icon {
width: 48rpx;
height: 48rpx;
}
.main-container {
.top-box {
padding: 16rpx 34rpx 0;
margin-bottom: 50rpx;
.text {
color: #1A1A1A;
font-size: 64rpx;
font-weight: 900;
}
}
}
.add-message {
width: 116rpx;
height: 116rpx;
position: fixed;
bottom: 200rpx;
right: 46rpx;
}
.bottom-placeholder {
height: 148rpx;
width: 100%;
}
.bottom-box {
background-color: #FAFAFA;
border-top: 1rpx solid #DCDCDC;
.item {
.icon {
width: 48rpx;
height: 48rpx;
margin-bottom: 12rpx;
}
text {
color: #191919;
}
.grey {
color: #717171;
}
}
}
}
// 华为机型
.huawei-style {
::v-deep .uni-navbar__header-container {
flex: 0;
}
.left-text {
color: #1a1a1a;
font-size: 50rpx;
font-weight: 700;
margin-left: 10rpx;
}
.right-icon {
width: 76rpx;
height: 76rpx;
margin: 0 8rpx;
}
.border-nav-bar-box {
::v-deep .nav-bar {
border-bottom: 1rpx solid #D1D1D1;
}
}
.main-container {
.top-box {
padding: 40rpx 34rpx 0;
.text {
color: #1A1A1A;
font-size: 60rpx;
font-weight: 700;
line-height: 60rpx;
}
.second-text {
color: #646464;
font-size: 28rpx;
margin-top: 10rpx;
}
.search-box {
margin-top: 40rpx;
background-color: #F4F4F4;
height: 76rpx;
border-radius: 38rpx;
padding: 0 24rpx;
.icon {
width: 32rpx;
height: 32rpx;
}
.input {
margin: 0 14rpx;
::v-deep .input-placeholder {
color: #646464;
font-size: 32rpx;
}
}
}
}
}
}
// vivo机型
.vivo-style {
::v-deep .uni-navbar__header-container {
flex: 0;
}
.m-r-34 {
margin-right: 34rpx !important;
}
.right-icon {
width: 44rpx;
height: 44rpx;
margin-right: 52rpx;
}
.left-text {
font-size: 54rpx;
color: #1A1A1A;
font-weight: 700;
margin-left: 30rpx;
}
.main-container {
.top-box {
padding: 0 46rpx;
.search-box {
position: relative;
margin-top: 70rpx;
background-color: #ffffff;
height: 76rpx;
border-radius: 38rpx;
padding: 0 10rpx;
.icon {
width: 40rpx;
height: 40rpx;
flex-shrink: 0;
}
.input {
flex: 1;
margin: 0 20rpx;
::v-deep .input-placeholder {
color: #9A9A9A;
font-size: 32rpx;
}
}
}
.search-box::after {
position: absolute;
content: '';
width: 100%;
height: 12rpx;
background-color: #F0F0F0;
bottom: 0;
left: 0;
}
}
}
}
</style>

View File

@ -0,0 +1,97 @@
<template>
<text
class="custom-icon"
:class="iconClass"
:style="{ color: color, fontSize: size + 'px' }"
@click="$emit('click')"
></text>
</template>
<script>
export default {
name: "my-icon",
props: {
// 图标名称,例如 'icon-home'
name: {
type: String,
default: ""
},
// 图标颜色
color: {
type: String,
default: "#333333"
},
// 图标大小,单位 rpx
size: {
type: [Number, String],
default: 16
}
},
computed: {
iconClass() {
// 这里的 'custom-icon' 必须和下面 font-family 的定义或者样式前缀对应
const name = `icon-${this.name}`;
return name;
}
}
}
</script>
<style scoped>
/* 1. 引入并定义字体名称 */
@font-face {
font-family: 'myIconFont';
/* 微信小程序若本地引用报错,可换成 base64 字符串或网络绝对路径 */
src: url('@/static/font/iconfont.ttf') format('truetype');
}
/* 2. 定义基础样式 */
.custom-icon {
font-family: "myIconFont" !important;
font-size: inherit;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
display: inline-block;
}
.icon-sanjiaozhou-maohao:before {
content: "\e630";
}
.icon-sanjiaozhou-fengexian:before {
content: "\e631";
}
.icon-sanjiaozhou-douhao:before {
content: "\e626";
}
.icon-sanjiaozhou-0:before {
content: "\e629";
}
.icon-sanjiaozhou-1:before {
content: "\e627";
}
.icon-sanjiaozhou-2:before {
content: "\e62b";
}
.icon-sanjiaozhou-3:before {
content: "\e62c";
}
.icon-a-sanjiaozhou-4:before {
content: "\e62f";
}
.icon-sanjiaozhou-5:before {
content: "\e62d";
}
.icon-sanjiaozhou-6:before {
content: "\e628";
}
.icon-sanjiaozhou-7:before {
content: "\e625";
}
.icon-sanjiaozhou-8:before {
content: "\e62a";
}
.icon-sanjiaozhou-9:before {
content: "\e62e";
}
</style>

View File

@ -1,19 +1,19 @@
<template>
<view style="width: 100%;" :style="{ height: `calc(${data.statusBarHeight}px + 88rpx)` }">
<view class="placeholder-box" style="width: 100%;" :style="{ height: `calc(${data.statusBarHeight}px + 88rpx)` }">
<!-- <slot name="statusBar"></slot> -->
</view>
<view class="nav-bar-container" :style="{ backgroundColor: bgColor, zIndex: zIndex }">
<view class="status-placeholder" :style="{ height: `${data.statusBarHeight}px` }">
<slot name="statusBar"></slot>
</view>
<view style="width: 100%;height: 88rpx;" @click="openPopup">
<view class="nav-box" style="width: 100%;height: 88rpx;" @click="openPopup">
<slot>
<uni-nav-bar backgroundColor="#00000000" class="nav-bar" :border="false" :title="title" v-bind="$attrs"
v-on="$attrs">
<template v-slot:left>
<view class="nav-bar-left">
<view class="nav-bar-left" @click.stop="isClickNavBarOpenPopup ? openPopup() : ''">
<slot name="left">
<view class="left-icon" @click.stop="onBack">
<view v-if="isBack" class="left-icon" @click.stop="onBack">
<image class="nav-icon-back"
:src="`/static/image/nav-bar/back-${textColor == '#fff' || textColor == '#fffffff' ? 'white' : 'black'}.png`"
mode="">
@ -28,7 +28,7 @@
</slot>
</view>
<template v-slot:right>
<view class="nav-bar-right" @click.stop="onRightClick">
<view class="nav-bar-right" @click.stop="isClickNavBarOpenPopup ? openPopup() : onRightClick()">
<slot name="right">
<view v-if="isRightIcon" class="right-icon">
<image class="nav-icon-more"
@ -82,276 +82,294 @@
</template>
<script setup>
import popup from '../popup/popup.vue'
import {
onMounted,
reactive,
ref,
toRefs
} from 'vue'
import popup from '../popup/popup.vue'
import {
onMounted,
reactive,
ref,
toRefs
} from 'vue'
const topPopup = ref()
const topPopup = ref()
// 定义组件属性
const props = defineProps({
bgColor: {
type: String,
default: '#fff'
},
textColor: {
type: String,
default: '#000'
},
title: {
type: String,
default: ''
},
buttonGroup: {
type: Array,
default: () => []
},
isRightIcon: {
type: Boolean,
default: false
},
isRightButton: {
type: Boolean,
default: false
},
rightButtonText: {
type: String,
default: '确定'
},
zIndex: {
type: Number,
default: 999
},
noBack: {
type: Boolean,
default: false
},
tipLayerText: {
type: String,
default: ''
},
isTipLayer: {
type: Boolean,
default: false
},
tipLayerType: {
type: String,
default: ''
// 定义组件属性
const props = defineProps({
bgColor: {
type: String,
default: '#fff'
},
textColor: {
type: String,
default: '#000'
},
title: {
type: String,
default: ''
},
buttonGroup: {
type: Array,
default: () => []
},
isRightIcon: {
type: Boolean,
default: false
},
isRightButton: {
type: Boolean,
default: false
},
rightButtonText: {
type: String,
default: '确定'
},
zIndex: {
type: Number,
default: 999
},
noBack: {
type: Boolean,
default: false
},
isBack: {
type: Boolean,
default: true
},
tipLayerText: {
type: String,
default: ''
},
isTipLayer: {
type: Boolean,
default: false
},
tipLayerType: {
type: String,
default: ''
},
isClickNavBarOpenPopup: {
type: Boolean,
default: false
}
})
// 定义事件
const emit = defineEmits(['back', 'right-click', 'button-click', 'refresh'])
const data = reactive({
statusBarHeight: 0,
showTipLayer: true,
})
let {
showTipLayer
} = toRefs(data)
onMounted(() => {
// 同步获取系统信息
const systemInfo = uni.getSystemInfoSync();
data.statusBarHeight = systemInfo.statusBarHeight || 0;
if (props.isTipLayer) {
if (uni.getStorageSync(props.tipLayerType) == props.tipLayerType) {
showTipLayer.value = false
}
}
})
const closeTipLayer = () => {
showTipLayer.value = false
uni.setStorageSync(props.tipLayerType, props.tipLayerType)
emit("refresh")
}
})
// 定义事件
const emit = defineEmits(['back', 'right-click', 'button-click', 'refresh'])
const data = reactive({
statusBarHeight: 0,
showTipLayer: true,
})
let {
showTipLayer
} = toRefs(data)
onMounted(() => {
// 同步获取系统信息
const systemInfo = uni.getSystemInfoSync();
data.statusBarHeight = systemInfo.statusBarHeight || 0;
if (props.isTipLayer) {
if (uni.getStorageSync(props.tipLayerType) == props.tipLayerType) {
showTipLayer.value = false
const openPopup = () => {
if (props.buttonGroup.length > 0) {
topPopup.value.open()
}
}
})
const closeTipLayer = () => {
showTipLayer.value = false
uni.setStorageSync(props.tipLayerType, props.tipLayerType)
emit("refresh")
}
const openPopup = () => {
if (props.buttonGroup.length > 0) {
topPopup.value.open()
// 返回按钮点击事件
const onBack = () => {
emit('back')
// 默认返回上一页
if (props.noBack) return
uni.navigateBack()
}
}
// 返回按钮点击事件
const onBack = () => {
emit('back')
// 默认返回上一页
if (props.noBack) return
uni.navigateBack()
}
// 右侧按钮点击事件
const onRightClick = () => {
emit('right-click')
}
// 右侧按钮点击事件
const onRightClick = () => {
emit('right-click')
}
const closeTopPopup = () => {
topPopup.value.close()
}
const buttonClick = (button) => {
topPopup.value.close()
emit('button-click', button)
}
const buttonClick = (button) => {
closeTopPopup()
emit('button-click', button)
}
// 暴露给父组件的方法
defineExpose({
openPopup,
closeTopPopup
})
</script>
<style scoped>
@import "/common/main.css";
@import "/common/main.css";
.nav-bar-container {
display: flex;
flex-direction: column;
position: fixed !important;
top: 0;
left: 0;
right: 0;
z-index: 999;
}
.nav-bar {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
z-index: 1;
}
.status-placeholder {
width: 100%;
}
::v-deep .uni-navbar__content {
width: 100%;
}
.nav-bar-left {
display: flex;
align-items: center;
justify-content: flex-start;
}
.nav-icon-back {
width: 24px;
height: 24px;
}
.nav-icon-more {
width: 26px;
height: 26px;
}
.nav-bar-title {
flex: 1;
margin: auto;
display: flex;
align-items: center;
justify-content: center;
font-size: 17px;
font-weight: 500;
}
.nav-bar-right {
display: flex;
align-items: center;
justify-content: flex-end;
}
.right-button {
font-size: 12px;
border-radius: 16px;
color: #fff;
text-align: center;
line-height: 30px;
height: 30px;
min-width: 60px;
background: linear-gradient(90deg, #187AFF 0%, #3295FC 100%);
}
.button-box {
width: calc(50% - 8rpx);
text-align: center;
margin-top: 16rpx;
}
.button {
border: 1px solid #E4E4E4;
border-radius: 8px;
height: 42px;
line-height: 42px;
font-size: 28rpx;
}
.tipLayer {
box-sizing: border-box;
min-width: 200px !important;
height: 48px;
background: #B8EDFE;
border-radius: 8px 8px 8px 8px;
position: fixed;
/* top: 115px; */
left: 50%;
transform: translateX(-50%);
z-index: 999;
}
.tipLayer-content {
position: relative;
}
.title {
font-weight: 450;
font-size: 14px;
color: #268FFF;
line-height: 48px;
text-align: center;
text {
font-size: 14px;
font-weight: bold;
color: #006ADD;
.nav-bar-container {
display: flex;
flex-direction: column;
position: fixed !important;
top: 0;
left: 0;
right: 0;
z-index: 999;
}
::v-deep text {
font-size: 14px;
font-weight: bold;
color: #006ADD;
.nav-bar {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
z-index: 1;
}
}
.triangleImg {
width: 111px;
height: 52px;
pointer-events: none;
position: absolute;
top: -23px;
left: calc(50% - 111px);
}
.status-placeholder {
width: 100%;
}
.triangle {
position: absolute;
top: -57px;
left: calc(50% - 40px);
pointer-events: none;
}
::v-deep .uni-navbar__content {
width: 100%;
}
.close {
position: absolute;
top: -5px;
right: -5px;
width: 18px;
height: 18px;
}
.nav-bar-left {
display: flex;
align-items: center;
justify-content: flex-start;
}
.nav-icon-back {
width: 24px;
height: 24px;
}
.nav-icon-more {
width: 26px;
height: 26px;
}
.nav-bar-title {
flex: 1;
margin: auto;
display: flex;
align-items: center;
justify-content: center;
font-size: 17px;
font-weight: 500;
}
.nav-bar-right {
display: flex;
align-items: center;
justify-content: flex-end;
}
.right-button {
font-size: 12px;
border-radius: 16px;
color: #fff;
text-align: center;
line-height: 30px;
height: 30px;
min-width: 60px;
background: linear-gradient(90deg, #187AFF 0%, #3295FC 100%);
}
.button-box {
width: calc(50% - 8rpx);
text-align: center;
margin-top: 16rpx;
}
.button {
border: 1px solid #E4E4E4;
border-radius: 8px;
height: 42px;
line-height: 42px;
font-size: 28rpx;
}
.tipLayer {
box-sizing: border-box;
min-width: 200px !important;
height: 48px;
background: #B8EDFE;
border-radius: 8px 8px 8px 8px;
position: fixed;
/* top: 115px; */
left: 50%;
transform: translateX(-50%);
z-index: 999;
}
.tipLayer-content {
position: relative;
}
.title {
font-weight: 450;
font-size: 14px;
color: #268FFF;
line-height: 48px;
text-align: center;
text {
font-size: 14px;
font-weight: bold;
color: #006ADD;
}
::v-deep text {
font-size: 14px;
font-weight: bold;
color: #006ADD;
}
}
.triangleImg {
width: 111px;
height: 52px;
pointer-events: none;
position: absolute;
top: -23px;
left: calc(50% - 111px);
}
.triangle {
position: absolute;
top: -57px;
left: calc(50% - 40px);
pointer-events: none;
}
.close {
position: absolute;
top: -5px;
right: -5px;
width: 18px;
height: 18px;
}
</style>

View File

@ -0,0 +1,41 @@
<template>
<view class="safeBottomBox">
<view class="safeBottomFiexd" :style="{ height: safeBottomAz + 'px', backgroundColor: bgColor }">
</view>
<view class="safeBottomZhangwei" :style="{ height: safeBottomAz + 'px' }">
</view>
</view>
</template>
<script setup>
import { computed, getCurrentInstance } from 'vue'
const props = defineProps({
bgColor: {
type: String,
default: '#EDEDED'
}
})
const instance = getCurrentInstance()
const safeBottomAz = computed(() => {
const store = instance?.appContext?.config?.globalProperties?.$store
const height = store?.state?.safeBottomAz
return Number(height) || 0
})
</script>
<style lang="scss" scoped>
.safeBottomBox {
width: 100vw;
position: relative;
z-index: 999;
overflow: hidden;
.safeBottomFiexd {
width: 100vw;
position: fixed;
left: 0;
bottom: 0;
}
}
</style>

View File

@ -0,0 +1,831 @@
<template>
<view class="shopping-card" @longpress="onLongPress">
<!-- Header -->
<view class="card-header">
<view class="shop-info">
<!-- <view class="">
</view> -->
<image v-if="item.shopType === 'self'" style="width: 50rpx;margin-right: 12rpx;border-radius: 4rpx;"
src="/static/image/shopping/jingdong/detail/ziyin.png" mode="widthFix"></image>
<image v-if="item.shopType === 'waimai'" style="width: 50rpx;margin-right: 8rpx;border-radius: 4rpx;"
src="/static/image/shopping/jingdong/waimai/waimai.png" mode="widthFix"></image>
<!-- <view class="tag waimai-tag">外卖</view> -->
<image class="jd-tag" v-if="item.shopType === 'jd'" src="/static/image/shopping/jingdong/JD.png">
</image>
<text class="shop-name">{{ item.shopName }}</text>
<uni-icons class="right-icon" type="right" size="12" color="#1A1A1A"></uni-icons>
</view>
<view class="order-status">
<view v-if="item.status === '等待付款'" class="status-warning">
<image style="width: 116rpx;height: 30rpx;" src="/static/image/shopping/jingdong/dengdaifukuan.png">
</image>
<text class="status-desc" v-if="item.statusDesc">{{
item.shopType != 'waimai' ? formatStatusDesc(item.statusDesc) :
formatWaimaiStatusDesc(item.statusDesc) }}</text>
</view>
<text v-else class="status-text"
:class="{ red: item.statusColor === 'red' || item.status === '正在出库' || item.status === '商家备餐中' || item.status === '骑手到店取餐中' || item.status === '骑手已到店' }">{{
item.status }}</text>
</view>
</view>
<!-- Tracking -->
<view class="tracking-box" :class="{ 'waimai-tracking': item.shopType === 'waimai' }"
v-if="item.trackingTitle || item.trackingDesc">
<view class="waimai-tracking-icon" v-if="item.shopType === 'waimai'">
<image style="width: 32rpx; height: 32rpx;" src="/static/image/shopping/jingdong/waimai-logo.png"
mode="aspectFit" v-if="!item.trackingIcon">
</image>
<!-- <image style="width: 28rpx; height: 28rpx;" :src="item.trackingIcon" mode="aspectFit" v-else></image> -->
</view>
<view class="tracking-content">
<view class="tracking-header" v-if="item.shopType !== 'waimai'">
<image class="truck-icon" v-if="!item.trackingIcon"
src="/static/image/shopping/jingdong/cangku.png">
</image>
<image class="truck-icon" :src="item.trackingIcon" mode="aspectFit" v-else></image>
<text class="tracking-title">{{ item.trackingTitle }}</text>
</view>
<view class="tracking-main-title" v-else>
<view class="rich-title">预计<span style="color:#E40C24">{{ item.trackingTitle }}</span> 可送达</view>
</view>
<text class="tracking-desc" v-if="item.trackingDesc">{{ item.trackingDesc }}</text>
<text class="tracking-time">{{ item.trackingTime }}</text>
</view>
<uni-icons v-if="item.shopType !== 'waimai'" class="right-icon" type="right" size="14"
color="#3D3D3D"></uni-icons>
</view>
<!-- Product -->
<view class="product-box"
:class="{ multiple: (item.products && item.products.length > 1) || (item.images && item.images.length > 0) }">
<template v-if="item.products && item.products.length > 1">
<scroll-view class="product-images-scroll" scroll-x :show-scrollbar="false">
<image class="product-img small" v-for="(prod, idx) in item.products" :key="idx" :src="prod.image"
mode="aspectFill"></image>
</scroll-view>
</template>
<template v-else-if="item.products && item.products.length === 1">
<image class="product-img" :src="item.products[0].image" mode="aspectFill"></image>
</template>
<template v-else-if="item.images && item.images.length > 0">
<scroll-view class="product-images-scroll" scroll-x :show-scrollbar="false">
<image class="product-img small" v-for="(img, idx) in item.images" :key="idx" :src="img"
mode="aspectFill"></image>
</scroll-view>
</template>
<template v-else>
<image class="product-img" :src="item.image" mode="aspectFill"></image>
<view class="product-info">
<text class="product-title" :class="{ 'shopping-title': item.shopType !== 'waimai' }">{{
truncatedTitle }}</text>
<text class="product-desc" v-if="item.desc">{{ item.desc }}</text>
<view class="product-tags" v-if="item.tags && item.tags.length">
<text class="tag" v-for="(tag, index) in item.tags" :key="index">{{ tag }}</text>
</view>
</view>
</template>
<view class='flex flex-justify-between flex-align-center'
:class="{ 'flex-1': item.products && item.products.length === 1 }">
<view class="product-info flex-1">
<template v-if="item.products && item.products.length === 1">
<text class="product-title" :class="{ 'shopping-title': item.shopType !== 'waimai' }">{{
truncatedTitle }}</text>
<text class="product-desc" v-if="item.products[0].desc">{{ item.products[0].desc }}</text>
<view class="product-tags" v-if="item.products[0].service">
<text class="tag">{{ item.products[0].service }}</text>
</view>
</template>
</view>
<view class="product-price-box">
<view class="price-wrap wx-font-regular">
<text class="price-symbol">¥</text>
<text class="price-num">{{ item.shopType === 'waimai' ?
(item.totalPrice ? Number(item.totalPrice).toFixed(2) : '0.00')
:
safeFormatPrice(item)
}}</text>
</view>
<text class="product-count"
v-if="item.count || (item.products && item.products[0] && item.products[0].count)">共{{
item.count || (item.products && item.products[0] && item.products[0].count) }}件</text>
</view>
</view>
</view>
<!-- Promo -->
<view class="promo-box"
:class="{ 'plus-promo': item.promoType === 'plus', 'cashback-promo': item.shopType == 'waimai' && (item.status == '骑手到店取餐中' || item.status == '商家备餐中'), 'coupon-promo': item.promoType === 'coupon' }">
<template v-if="item.promoType === 'plus'">
<view class="plus-row">
<image class="plus-tag" src="/static/image/shopping/jingdong/plus.png"></image>
<text class="promo-text">{{ item.promoText }}</text>
</view>
<text class="promo-action">解锁权益 <uni-icons type="right" size="10" color="#654629"></uni-icons> </text>
</template>
<template v-else-if="item.shopType == 'waimai' && (item.status == '骑手到店取餐中' || item.status == '商家备餐中')">
<text class="promo-text text-basic">当笔订单返现成功到账,<text style="color: #E31700;">点击领取</text></text>
<uni-icons type="right" size="10" color="#1A1A1A"></uni-icons>
</template>
<template v-else-if="item.promoType === 'coupon'">
<view class="coupon-row">
<view class="coupon-tag">购物券</view>
<text class="promo-text">{{ item.promoText }}</text>
</view>
<text class="promo-action red">{{ item.promoAction || '去领券' }} <uni-icons type="right" size="10"
color="#ED1C04"></uni-icons></text>
</template>
<template v-else-if="item.promoType === 'text'">
<text class="promo-text text-basic">本单可享 <text class="highlight">{{ item.promoHighlight
}}</text>,快去支付吧~</text>
</template>
<template v-else>
<text class="promo-text text-basic">当笔订单返现成功到账,<text style="color: #E31700;">点击领取</text></text>
</template>
</view>
<!-- Footer -->
<view class="footer-actions">
<view class="more-box" v-if="item.status == '完成' && item.shopType != 'waimai'">
<text class="more-text">更多</text>
</view>
<view class="empty-space" v-else></view>
<view class="action-buttons">
<view class="btn" :class="btn.type || 'default'"
v-for="(btn, index) in getButtons(item.shopType, item.status, item)" :key="index"
@click="$emit('btnClick', btn)">
{{ btn.text }}
<view class="btn-badge" v-if="btn.badge">{{ btn.badge }}</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { defineProps, defineEmits, ref, onMounted, watch, getCurrentInstance } from 'vue';
const props = defineProps({
item: {
type: Object,
required: true,
default: () => ({})
}
});
const emit = defineEmits(['btnClick', 'longpress']);
const onLongPress = (e) => {
emit('longpress', { event: e, item: props.item });
};
// --- JS 标题截断逻辑开始 ---
const instance = getCurrentInstance();
const truncatedTitle = ref('');
const calculateFitTitle = () => {
const originalTitle = props.item.products?.[0]?.title || props.item.title || '';
if (props.item.shopType === 'waimai') {
truncatedTitle.value = originalTitle;
return;
}
setTimeout(() => {
const query = uni.createSelectorQuery().in(instance);
// 测量所在的容器宽度
query.select('.product-info').boundingClientRect(res => {
if (res && res.width) {
const containerWidth = res.width;
const fontSize = uni.upx2px(26); // 26rpx 对应的像素大小
let currentWidth = 0;
let cutIndex = 0;
let found = false;
for (let i = 0; i < originalTitle.length; i++) {
const char = originalTitle[i];
const charCode = char.charCodeAt(0);
let weight = 1;
if (charCode <= 255) {
if (/[A-Z]/.test(char)) weight = 0.7;
else if (/[a-z]/.test(char)) weight = 0.5;
else if (/[0-9]/.test(char)) weight = 0.55;
else if (char === ' ') weight = 0.25;
else weight = 0.5;
}
currentWidth += weight * fontSize;
if (currentWidth > containerWidth - uni.upx2px(4)) {
cutIndex = i;
found = true;
break;
}
}
truncatedTitle.value = found ? originalTitle.slice(0, cutIndex - 1) : originalTitle;
} else {
truncatedTitle.value = originalTitle;
}
}).exec();
}, 200);
};
onMounted(calculateFitTitle);
watch(() => props.item, calculateFitTitle, { deep: true, immediate: true });
// --- JS 标题截断逻辑结束 ---
const formatWaimaiStatusDesc = (desc) => {
if (!desc || !desc.includes(':')) return desc;
const parts = desc.split(':');
if (parts.length === 2) {
const m = parseInt(parts[0]) || 0;
const s = parseInt(parts[1]) || 0;
if (m > 0) {
return `${m}分钟`;
} else {
return `${s}秒`;
}
}
return desc;
};
const formatStatusDesc = (desc) => {
if (!desc || !desc.includes(' : ')) return desc;
const parts = desc.split(' : ');
if (parts.length >= 2) {
return `${parts[0]}时${parts[1]}分`;
}
return desc;
};
const safeFormatPrice = (item) => {
const val = item.price || (item.products && item.products[0] && item.products[0].price);
const num = Number(val);
return isNaN(num) ? '0.00' : num.toFixed(2);
};
const getButtons = (shopType, status, item) => {
const buttons = [];
if (status == '等待付款') {
buttons.push({ text: '取消订单', type: 'default' });
buttons.push({ text: '查看发票', type: 'default' });
buttons.push({ text: '修改订单', type: 'default' });
buttons.push({ text: '去支付', type: 'primary' });
}
if (shopType == 'waimai') {
if (status == '骑手到店取餐中') {
buttons.push({ text: '申请退款', type: 'default' });
buttons.push({ text: '查看发票', type: 'default' });
}
if (status == '商家备餐中') {
buttons.push({ text: '申请退款', type: 'default' });
buttons.push({ text: '查看发票', type: 'default' });
}
if (status == '完成') {
buttons.push({ text: '删除订单', type: 'default' });
buttons.push({ text: '查看发票', type: 'default' });
buttons.push({ text: '退换/售后', type: 'default' });
buttons.push({ text: '再次购买', type: 'primary' });
}
if (status == '已取消') {
buttons.push({ text: '删除订单', type: 'default' });
buttons.push({ text: '钱款去向', type: 'default' });
buttons.push({ text: '再次购买', type: 'primary' });
}
} else if (shopType != 'waimai') {
if (status == '正在出库') {
buttons.push({ text: '查看发票', type: 'default' });
buttons.push({ text: '再次购买', type: 'default' });
buttons.push({ text: '申请退款', type: 'default' });
buttons.push({ text: '修改订单', type: 'default' });
}
if (status == '完成') {
if (item?.trackingTitle == '已签收') {
buttons.push({ text: '卖了换钱', type: 'default' });
buttons.push({ text: '退还/售后', type: 'default' });
buttons.push({ text: '再次购买', type: 'primary' });
} else {
buttons.push({ text: '再次购买', type: 'default' });
buttons.push({ text: '申请退款', type: 'default' });
buttons.push({ text: '修改订单', type: 'default' });
}
}
if (status == '已取消') {
buttons.push({ text: '再次购买', type: 'default' });
buttons.push({ text: '申请退款', type: 'default' });
buttons.push({ text: '修改订单', type: 'primary' });
}
}
console.log(shopType, status);
return buttons;
}
</script>
<style>
@import "/common/main.css";
</style>
<style lang="less" scoped>
.shopping-card {
background-color: #FFFFFF;
border-radius: 24rpx;
padding: 34rpx 18rpx 30rpx;
margin: 0 0 16rpx 0;
box-sizing: border-box;
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
.shop-info {
flex: 1;
width: 20px;
display: flex;
align-items: center;
.tag {
font-size: 20rpx;
padding: 2rpx 6rpx;
border-radius: 4rpx;
margin-right: 8rpx;
font-weight: 500;
color: #FFFFFF;
line-height: 26rpx;
flex-shrink: 0;
&.self-tag {
background-color: #FC3538;
}
&.waimai-tag {
background-color: #FEE74B;
color: #1A1A1A;
}
}
.jd-tag {
width: 32rpx;
height: 32rpx;
flex-shrink: 0;
margin-right: 12rpx;
}
.shop-name {
white-space: nowrap;
font-size: 26rpx;
line-height: 28rpx;
color: #1A1A1A;
font-weight: 700;
overflow: hidden;
text-overflow: ellipsis;
}
.right-icon {
margin-right: 48rpx;
}
}
.order-status {
flex-shrink: 0;
font-size: 26rpx;
.waimai-status-warning {
display: flex;
align-items: center;
height: 32rpx;
border: 2rpx solid #ED1C04;
border-radius: 16rpx;
overflow: hidden;
.w-badge {
background-color: #ED1C04;
color: #FFFFFF;
font-size: 20rpx;
height: 100%;
line-height: 32rpx;
padding: 0 10rpx;
}
.w-time {
color: #ED1C04;
font-size: 20rpx;
line-height: 32rpx;
padding: 0 10rpx;
background-color: #FFFFFF;
}
}
.status-warning {
display: flex;
align-items: center;
background-color: #FEE4E5;
border-radius: 24rpx;
height: 30rpx;
padding-right: 8rpx;
.status-badge {
background-color: #E40C24;
color: #FFFFFF;
font-size: 22rpx;
padding: 0 12rpx;
height: 32rpx;
line-height: 32rpx;
border-radius: 16rpx;
margin-right: 8rpx;
}
.status-desc {
font-size: 22rpx;
line-height: 24rpx;
margin-left: 4rpx;
color: #ED1C04;
}
}
.status-text {
font-size: 22rpx;
color: #87868E;
&.red {
color: #ED1C04;
}
&.gray {
color: #999999;
}
&.black {
color: #1A1A1A;
}
}
}
}
.tracking-box {
display: flex;
justify-content: space-between;
align-items: center;
background-color: #F9F9F9;
padding: 12rpx 20rpx 18rpx;
border-radius: 12rpx;
margin-bottom: 24rpx;
&.waimai-tracking {
align-items: flex-start;
background-color: #F7F8FC;
border-radius: 12rpx;
padding: 12rpx 20rpx 18rpx;
.waimai-tracking-icon {
margin-right: 10rpx;
margin-top: 2rpx;
}
.tracking-main-title {
font-size: 26rpx;
color: #1A1A1A;
font-weight: 700;
line-height: 36rpx;
margin-bottom: 8rpx;
}
.tracking-desc {
margin-top: 0;
font-size: 22rpx;
line-height: 22rpx;
color: #1A1A1A;
margin-left: -42rpx;
}
.tracking-time {
margin-left: -42rpx;
}
}
.tracking-content {
flex: 1;
.tracking-header {
display: flex;
align-items: center;
height: 32rpx;
margin-bottom: 14rpx;
.truck-icon {
width: 32rpx;
height: 32rpx;
margin-right: 10rpx;
}
.tracking-title {
font-size: 26rpx;
color: #1A1A1A;
font-weight: 700;
}
}
.tracking-desc {
font-size: 22rpx;
line-height: 22rpx;
color: #1A1A1A;
display: block;
}
.tracking-time {
margin-top: 12rpx;
font-size: 22rpx;
line-height: 22rpx;
color: #767676;
}
}
.right-icon {}
}
.product-box {
display: flex;
margin-bottom: 24rpx;
align-items: center;
&.multiple {
.product-images-scroll {
flex: 1;
width: 0;
white-space: nowrap;
.product-img {
display: inline-block;
width: 132rpx;
height: 132rpx;
margin-right: 14rpx;
border-radius: 12rpx;
background-color: #F5F5F5;
}
}
.product-price-box {
margin-left: 10rpx;
justify-content: center;
align-items: flex-end;
}
}
.product-img {
width: 144rpx;
height: 144rpx;
border-radius: 12rpx;
flex-shrink: 0;
margin-right: 14rpx;
background-color: #F5F5F5;
}
.product-info {
flex: 1;
display: flex;
flex-direction: column;
width: 20rpx;
.product-title {
font-size: 26rpx;
color: #1A1A1A;
line-height: 36rpx;
// white-space: nowrap;
text-overflow: clip;
overflow: hidden;
&.shopping-title {
width: 100%;
white-space: nowrap;
}
}
.product-desc {
font-size: 22rpx;
line-height: 24rpx;
margin-top: 8rpx;
color: #87868E;
overflow: hidden;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
line-clamp: 2;
text-overflow: ellipsis;
}
.product-tags {
display: flex;
flex-wrap: wrap;
margin-top: 8rpx;
.tag {
font-size: 20rpx;
line-height: 20rpx;
color: #C59446;
margin-right: 12rpx;
}
}
}
.product-price-box {
display: flex;
flex-direction: column;
align-items: flex-end;
margin-left: 20rpx;
.price-wrap {
color: #1A1A1A;
font-size: 30rpx;
margin-bottom: 4rpx;
line-height: 36rpx;
display: flex;
align-items: baseline;
.price-symbol {
font-weight: 500;
}
.price-num {
font-weight: 600;
}
}
.product-count {
font-size: 22rpx;
color: #87868E;
}
}
}
.promo-box {
background-color: #FEEFF2;
padding: 14rpx;
border-radius: 4rpx;
margin-bottom: 24rpx;
font-size: 22rpx;
line-height: 22rpx;
&.plus-promo,
&.cashback-promo,
&.coupon-promo {
display: flex;
justify-content: space-between;
align-items: center;
height: 52rpx;
padding: 0 16rpx;
border-radius: 8rpx;
}
&.plus-promo {
background-color: #FFEDDF;
.plus-row {
display: flex;
align-items: center;
.plus-tag {
width: 56rpx;
height: 26rpx;
margin-right: 8rpx;
}
.promo-text {
font-size: 22rpx;
line-height: 22rpx;
color: #654629;
}
}
.promo-action {
font-size: 22rpx;
color: #A3824E;
}
}
&.cashback-promo,
&.coupon-promo {
background-color: #FCF5F5;
.promo-text {
font-size: 22rpx;
line-height: 22rpx;
color: #1A1A1A;
.highlight {
color: #ED1C04;
}
}
.promo-action {
font-size: 22rpx;
display: flex;
align-items: center;
&.red {
color: #ED1C04;
}
}
}
&.coupon-promo {
.coupon-row {
display: flex;
align-items: center;
.coupon-tag {
font-size: 20rpx;
background-color: #ED1C04;
color: #FFFFFF;
padding: 2rpx 8rpx;
border-radius: 4rpx;
margin-right: 8rpx;
}
}
}
.text-basic {
font-size: 22rpx;
line-height: 22rpx;
color: #1A1A1A;
display: block;
.highlight {
font-size: 22rpx;
line-height: 22rpx;
color: #E31700;
}
}
}
.footer-actions {
display: flex;
justify-content: space-between;
align-items: center;
.more-box {
display: flex;
align-items: center;
.more-text {
white-space: nowrap;
font-size: 22rpx;
color: #87868E;
}
}
.empty-space {
flex: 1;
}
.action-buttons {
display: flex;
align-items: center;
flex-wrap: wrap;
justify-content: flex-end;
position: relative;
z-index: 998;
.btn {
position: relative;
// z-index: 998;
height: 56rpx;
line-height: 56rpx;
padding: 0 24rpx;
border-radius: 8rpx;
font-size: 22rpx;
margin-left: 16rpx;
border: 0.5px solid #C7C7C7;
color: #1A1A1A;
background-color: #FFFFFF;
&.primary {
border-color: #ED1C04;
color: #ED1C04;
}
.btn-badge {
box-sizing: border-box;
position: absolute;
top: -8px;
right: -1px;
background-image: url(/static/image/shopping/jingdong/bubble.png);
background-size: 100% 100%;
background-repeat: no-repeat;
color: #FFFFFF;
font-size: 10px;
line-height: 11px;
padding: 2px 4px 0;
white-space: nowrap;
width: 45px;
height: 22px;
}
}
}
}
}
</style>

View File

@ -7,7 +7,7 @@
<image v-else class="colse" src="/static/image/watermarkColseDark.png" mode=""></image> -->
<image class="watermarkImg" src="/static/image/watermark.png" mode=""></image>
<image class="colse" src="/static/image/watermarkColse.png" mode=""
@click="$goRechargePage('watermark_close')"></image>
@click="$goRechargePage('watermark_close', source)"></image>
</view>
</view>
@ -19,6 +19,10 @@ export default {
dark: {
type: String,
default: 'light'
},
source: {
type: String,
default: 'uni_alipay_other'
}
},
name: "watermark",
@ -56,7 +60,7 @@ export default {
justify-content: center;
z-index: 99 !important;
background-image: url('/static/image/watermarkBG.png');
background-size: 125px 125px;
background-size: 500rpx 500rpx;
.watermarkBox {
width: 200px;

View File

@ -0,0 +1,29 @@
## 自定义水印添加
***
一款声明版权立场的组件
该组件参考了 [315500033@qq.com](https://ext.dcloud.net.cn/plugin?id=362) 大神的组件 修改后符合自己的编码习惯 让水印有更多的展示方式
### 使用方式
#### 在 ``script`` 中引入组件:
```javascript
import wmWatermark from '../../components/wm-watermark.vue';
export default {
components: {wmWatermark}
}
```
#### 在 ``template`` 中使用组件:
```html
<wm-watermark text="这是水印信息"></wm-watermark>
```
### **属性说明**
| 属性 | 类型 | 默认值 | 说明 |
| :---: | :---: | :---: | :--------: |
| text | String | 无 | 设置水印文字 注:(文字优先图片) |
| imgUrl | String | 无 | 设置水印图片链接 注:(文字优先图片) |
| opacity | Number,String | 0.6 | 设置显示透明度 |
| num | Number | 20 | 设置水印数量 |

View File

@ -0,0 +1,72 @@
<template>
<!-- 水印 -->
<view class="watermark">
<block v-for="(item,index) in num" :key="index">
<view class="watermark-text" v-if="text != ''" :style="{opacity:opacity}">{{text}}</view>
<image class="watermark-img" :src="imgUrl" mode="aspectFill" v-if="imgUrl !='' && text ==''" :style="{opacity:opacity}"></image>
</block>
</view>
</template>
<script>
export default {
name:'wm-watermark',
props:{
text:{ //设置水印文字
type:String,
default:''
},
imgUrl:{ //设置水印图片
type:String,
default:''
},
opacity:{ //设置透明度
type:[Number,String],
default:0.6
},
num:{ //设置水印数量
type:Number,
default:30
}
},
data() {
return {
};
}
}
</script>
<style lang="scss">
.watermark{
position: fixed;
width: 1200upx;
height: 100%;
top:0;
left: 0;
bottom: 0;
right: 0;
pointer-events: none;
z-index: 9999999;
display: flex;
flex-wrap: wrap;
overflow: hidden;
transform: rotate(-33deg) translateX(-20px);
.watermark-text{
width: 350upx;
height: 80upx;
display: flex;
justify-content: center;
align-items: center;
// color: red;
color: rgba(0,0,0,0.1);
font-size: 18upx;
white-space: nowrap;
}
.watermark-img{
width: 375upx;
height: 375upx;
z-index: 1;
}
}
</style>

BIN
eslint-output.json Normal file

Binary file not shown.

76
main.js
View File

@ -16,20 +16,78 @@ app.$mount()
import {
createSSRApp
} from 'vue'
import {
store,
useStore
} from './store'
export function createApp() {
const app = createSSRApp(App)
// 从缓存读取系统信息(已在App.vue中获取)
app.config.globalProperties.$pageData = pageData
const systemInfo = uni.getStorageSync('systemInfo') || {}
let systemInfo = uni.getStorageSync('systemInfo') || {}
if (!systemInfo.platform) {
systemInfo = uni.getSystemInfoSync() || {}
}
const windowInfo = uni.getWindowInfo();
app.config.globalProperties.$store = {
state: {
safeBottom: windowInfo.safeAreaInsets?.bottom || 0,
safeBottomAz: 0
}
}
app.config.globalProperties.$system = systemInfo.platform == 'ios' ? 'iOS' : 'Android'
app.config.globalProperties.$systemInfo = systemInfo
uni.setStorageSync('version', '1.0.2.sp4')
app.config.globalProperties.$version = uni.getStorageSync('version')
// #ifdef APP-PLUS&&!APP-HARMONY
app.config.globalProperties.$system = plus.os.name;
// #endif
app.config.globalProperties.$systemInfo = systemInfo;
uni.setStorageSync('version', '1.0.7.sp6');
app.config.globalProperties.$version = uni.getStorageSync('version');
// 环境:development→测试,production→正式(跟构建环境走,无需手改)
const isTest = process.env.NODE_ENV === 'development';
app.config.globalProperties.$isTest = isTest;
// #ifdef APP-PLUS&&!APP-HARMONY
const version = plus.runtime.version;
// #endif
const compareVersion = (v1, v2) => {
const a = String(v1).split('.')
const b = String(v2).split('.')
const len = Math.max(a.length, b.length)
for (let i = 0; i < len; i++) {
const n1 = parseInt(a[i] || '0', 10)
const n2 = parseInt(b[i] || '0', 10)
if (n1 > n2) return 1
if (n1 < n2) return -1
}
return 0
}
//安卓和ios
// #ifdef APP-PLUS && !APP-HARMONY
if (plus.os.name == 'iOS') {
if (compareVersion(version, '1.9') >= 0 && !isTest) {
uni.setStorageSync('isTest', false)
} else {
uni.setStorageSync('isTest', true)
}
} else {
if (compareVersion(version, '2.2.0') >= 0 && !isTest) {
uni.setStorageSync('isTest', false)
} else {
uni.setStorageSync('isTest', true)
}
}
// #endif
//鸿蒙
// #ifdef APP-PLUS && APP-HARMONY
if (compareVersion(version, '2.1.7') >= 0&&!isTest) {
uni.setStorageSync('isTest', false)
} else {
uni.setStorageSync('isTest', true)
}
// #endif
//h5环境
// #ifndef APP-PLUS
uni.setStorageSync('isTest', true)
// #endif
app.use(globalMethods);
return {
app

View File

@ -1,37 +1,64 @@
{
"name": "alipay-emulator",
"appid": "__UNI__D535736",
"description": "",
"versionName": "1.0.0",
"versionCode": 100,
"transformPx": false,
"name" : "alipay-emulator",
"appid" : "__UNI__D535736",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : 100,
"transformPx" : false,
/* 5+App特有相关 */
"app-plus": {
"darkmode": false,
"usingComponents": true,
"nvueStyleCompiler": "uni-app",
"compilerVersion": 3,
"splashscreen": {
"alwaysShowBeforeRender": true,
"waiting": true,
"autoclose": true,
"delay": 0
"app-harmony" : {
"distribute" : {
"splashScreens" : {
"startWindowIcon" : "resource/icon.png", // 启动页图标路径
"startWindowBackground" : "#123456" // 启动页背景颜色
},
"bundleName" : "cn.zuom8.jsloction",
"signingConfigs" : {
"default" : {
"certpath" : "C:\\Users\\Administrator\\Desktop\\jisuloca\\test\\debug.cer",
"keyAlias" : "__uni__1be0b2f",
"keyPassword" : "000000189B31358AF1B1A6FD1CB2D1011E32DF02389A1F92604ECEB091A66654CDCDF6DB4DD18BC3",
"profile" : "C:\\Users\\Administrator\\Desktop\\jisuloca\\test\\jsloction_debugDebug.p7b",
"signAlg" : "SHA256withECDSA",
"storeFile" : "C:\\Users\\Administrator\\Desktop\\jisuloca\\hmos.p12",
"storePassword" : "000000185660236954705CE2E46C7A2FA76565A93989A8DDD5810CC2760ED8D2B479EDD9C488B982"
}
}
}
},
"app-plus" : {
"darkmode" : false,
"usingComponents" : true,
"nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3,
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : false,
"delay" : 0
},
"optimization": {
"subPackages": true
"compatible" : {
"runtimeVersion": "4.75",
"compilerVersion": "5.14",
"ignoreVersion" : true
},
"optimization" : {
"subPackages" : true
},
"runmode": "liberate", // 开启分包优化后,必须配置资源释放模式
"runmode" : "liberate", // 开启分包优化后,必须配置资源释放模式
/* 模块配置 */
"modules": {
"Camera": {},
"Payment": {},
"LivePusher": {}
"modules" : {
"Camera" : {},
"Payment" : {},
"LivePusher" : {}
},
/* 应用发布信息 */
"distribute": {
"distribute" : {
/* android打包配置 */
"android": {
"permissions": [
"android" : {
"permissions" : [
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
@ -50,52 +77,51 @@
]
},
/* ios打包配置 */
"ios": {
"dSYMs": false
"ios" : {
"dSYMs" : false
},
/* SDK配置 */
"sdkConfigs": {
"payment": {
"weixin": {
"__platform__": [
"ios",
"android"
],
"appid": "123456",
"UniversalLinks": "https://hhhhh.com/apple-app-site-association/"
"sdkConfigs" : {
"payment" : {
"weixin" : {
"__platform__" : [ "ios", "android" ],
"appid" : "123456",
"UniversalLinks" : "https://hhhhh.com/apple-app-site-association/"
},
"alipay": {
"__platform__": [
"ios",
"android"
]
"alipay" : {
"__platform__" : [ "ios", "android" ]
}
}
}
},
"nvueLaunchMode": ""
"nvueLaunchMode" : ""
},
/* 快应用特有相关 */
"quickapp": {},
"quickapp" : {},
/* 小程序特有相关 */
"mp-weixin": {
"appid": "",
"setting": {
"urlCheck": false
"mp-weixin" : {
"appid" : "",
"setting" : {
"urlCheck" : false
},
"usingComponents": true
"usingComponents" : true
},
"mp-alipay": {
"usingComponents": true
"mp-alipay" : {
"usingComponents" : true
},
"mp-baidu": {
"usingComponents": true
"mp-baidu" : {
"usingComponents" : true
},
"mp-toutiao": {
"usingComponents": true
"mp-toutiao" : {
"usingComponents" : true
},
"uniStatistics": {
"enable": false
"uniStatistics" : {
"enable" : false
},
"vueVersion": "3"
}
"vueVersion" : "3",
"h5" : {
"router" : {
"mode" : "history"
}
}
}

2315
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,20 +1,40 @@
{
"id": "lius-DomVideoPlayer",
"name": "video-player 视频播放器 html5视频播放器-解决频层级、覆盖",
"displayName": "video-player 视频播放器 html5视频播放器-解决频层级、覆盖",
"version": "2.0.0",
"description": "APP 项目中,uniapp 提供的的 video 原生视频组件层级太高,难以遮挡;该视频播放器可以被其他元素进行覆盖、遮挡,页面具有更高的定制性。",
"keywords": [
"video-player",
"视频播放器",
"视频覆盖",
"视频层级",
"视频遮挡"
],
"dcloudext": {
"category": [
"前端组件",
"通用组件"
]
}
}
"id": "lius-DomVideoPlayer",
"name": "video-player 视频播放器 html5视频播放器-解决频层级、覆盖",
"displayName": "video-player 视频播放器 html5视频播放器-解决频层级、覆盖",
"version": "2.0.0",
"description": "APP 项目中,uniapp 提供的的 video 原生视频组件层级太高,难以遮挡;该视频播放器可以被其他元素进行覆盖、遮挡,页面具有更高的定制性。",
"keywords": [
"video-player",
"视频播放器",
"视频覆盖",
"视频层级",
"视频遮挡"
],
"dcloudext": {
"category": [
"前端组件",
"通用组件"
]
},
"dependencies": {
"@batiao/batiao-sdk-uniapp-unimp": "1.0.374",
"crypto-js": "^4.2.0",
"lottie-web": "^5.13.0",
"uuid": "^13.0.0"
},
"lint-staged": {
"*.{js,vue}": [
"eslint"
]
},
"devDependencies": {
"eslint": "^8.56.0",
"eslint-plugin-vue": "^9.33.0",
"husky": "^9.1.7",
"lint-staged": "^16.4.0"
},
"scripts": {
"prepare": "husky"
}
}

View File

@ -10,6 +10,150 @@
}
],
"subPackages": [
{
"root": "pages/call-log",
"pages": [
{
"path": "call",
"style": {
"navigationBarTitleText": "通话记录页面",
"navigationStyle": "custom"
}
},
{
"path": "detail/callDetail",
"style": {
"navigationBarTitleText": "通话记录详情",
"navigationStyle": "custom"
}
}
]
},
{
"root": "pages/message",
"pages": [
{
"path": "list-index",
"style": {
"navigationBarTitleText": "短信列表首页",
"navigationStyle": "custom"
}
},
{
"path": "chat-page/chat-page",
"style": {
"navigationBarTitleText": "短信聊天页面",
"navigationStyle": "custom",
"app-plus": {
"softinputMode": "adjustResize"
}
}
}
]
},
{
"root": "pages/shopping",
"pages": [
{
"path": "index",
"style": {
"navigationBarTitleText": "购物app首页",
"navigationStyle": "custom"
}
},
{
"path": "jingdong/list-index",
"style": {
"navigationBarTitleText": "京东列表首页",
"navigationStyle": "custom"
}
},
{
"path": "jingdong/order-detail/order-detail",
"style": {
"navigationBarTitleText": "订单详情",
"navigationStyle": "custom"
}
},
{
"path": "jingdong/add-order/add-order",
"style": {
"navigationBarTitleText": "添加订单",
"navigationStyle": "custom"
}
},
{
"path": "jingdong/add-waimai/add-waimai",
"style": {
"navigationBarTitleText": "添加外卖订单页面",
"navigationStyle": "custom"
}
},
{
"path": "jingdong/waimai-order-detail/waimai-order-detail",
"style": {
"navigationBarTitleText": "外卖订单详情",
"navigationStyle": "custom"
}
},
{
"path": "taobao/list-index",
"style": {
"navigationBarTitleText": "淘宝购物外卖订单列表",
"navigationStyle": "custom"
}
},
{
"path": "taobao/shopping-order-detail/shopping-order-detail",
"style": {
"navigationBarTitleText": "淘宝购物详情页",
"navigationStyle": "custom"
}
},
{
"path": "taobao/add-shopping-order/add-shopping-order",
"style": {
"navigationBarTitleText": "新增淘宝购物订单",
"navigationStyle": "custom"
}
},
{
"path": "taobao/shangou-order-detail/shangou-order-detail",
"style": {
"navigationBarTitleText": "淘宝闪购订单详情",
"navigationStyle": "custom"
}
},
{
"path": "taobao/add-shangou-order/add-shangou-order",
"style": {
"navigationBarTitleText": "添加淘宝闪购订单详情",
"navigationStyle": "custom"
}
},
{
"path": "pdd/list-index",
"style": {
"navigationBarTitleText": "拼多多购物列表",
"navigationStyle": "custom"
}
},
{
"path": "pdd/order-detail/order-detail",
"style": {
"navigationBarTitleText": "拼多多订单详情",
"navigationStyle": "custom"
}
},
{
"path": "pdd/add-order/add-order",
"style": {
"navigationBarTitleText": "pdd添加修改订单信息",
"navigationStyle": "custom"
}
}
]
},
{
"root": "pages/balance",
"pages": [
@ -27,6 +171,27 @@
"navigationBarTitleText": "快速入口页面",
"navigationStyle": "custom"
}
},
{
"path": "transfer/transfer",
"style": {
"navigationBarTitleText": "转账模拟",
"navigationStyle": "custom"
}
},
{
"path": "pay-successful/pay-successful",
"style": {
"navigationBarTitleText": "支付成功",
"navigationStyle": "custom"
}
},
{
"path": "pay-successful/edit",
"style": {
"navigationBarTitleText": "修改支付成功",
"navigationStyle": "custom"
}
}
]
},
@ -170,6 +335,197 @@
"navigationStyle": "custom",
"navigationBarTextStyle": "white"
}
},
{
"path": "card/card",
"style": {
"navigationBarTitleText": "身份证",
"navigationStyle": "custom"
}
},
{
"path": "qf-image/qf-image",
"style": {
"navigationBarTitleText": "图片裁剪",
"navigationStyle": "custom"
}
},
{
"path": "train-tickets/ctrip-train-tickets/ctrip-train-tickets",
"style": {
"navigationBarTitleText": "携程火车票",
"navigationStyle": "custom"
}
},
{
"path": "train-tickets/qunar-train-tickets/qunar-train-tickets",
"style": {
"navigationBarTitleText": "去哪儿火车票",
"navigationStyle": "custom"
}
},
{
"path": "train-tickets/fliggy-train-tickets/fliggy-train-tickets",
"style": {
"navigationBarTitleText": "飞猪火车票",
"navigationStyle": "custom"
}
},
{
"path": "ranking/ranking",
"style": {
"navigationBarTitleText": "从夯倒拉排名",
"navigationStyle": "custom"
}
},
{
"path": "certificate/graduate",
"style": {
"navigationBarTitleText": "证书",
"navigationStyle": "custom"
}
},
{
"path": "silkBanner/silkBanner",
"style": {
"navigationBarTitleText": "锦旗",
"navigationStyle": "custom"
}
},
{
"path": "certificate/index",
"style": {
"navigationBarTitleText": "证书",
"navigationStyle": "custom"
}
},
{
"path": "about-this-iphone/about-this-iphone",
"style": {
"navigationBarTitleText": "苹果关于本机",
"navigationStyle": "custom"
}
},
{
"path": "bank/zsyh",
"style": {
"navigationBarTitleText": "招商银行",
"navigationStyle": "custom"
}
},
{
"path": "bank/index",
"style": {
"navigationBarTitleText": "选择银行卡",
"navigationStyle": "custom"
}
},
{
"path": "bank/gsyh",
"style": {
"navigationBarTitleText": "工商银行",
"navigationStyle": "custom"
}
},
{
"path": "bank/nyyh",
"style": {
"navigationBarTitleText": "农业银行",
"navigationStyle": "custom"
}
},
{
"path": "bank/jsyh",
"style": {
"navigationBarTitleText": "建设银行",
"navigationStyle": "custom"
}
},
{
"path": "game/honor-of-kings",
"style": {
"navigationBarTitleText": "王者荣耀",
"navigationStyle": "custom"
}
},
{
"path": "game/sanjiaozhou",
"style": {
"navigationBarTitleText": "三角洲",
"navigationStyle": "custom"
}
},
{
"path" : "game/hepingjingying",
"style" :
{
"navigationBarTitleText": "和平精英",
"navigationStyle": "custom"
}
},
{
"path" : "game/wuweiqiyue",
"style" :
{
"navigationBarTitleText": "无畏契约",
"navigationStyle": "custom"
}
},
{
"path": "game/index",
"style": {
"navigationBarTitleText": "游戏首页",
"navigationStyle": "custom"
}
},
{
"path": "wx-payment-success/wx-payment-success",
"style": {
"navigationBarTitleText": "微信支付成功",
"navigationStyle": "custom"
}
},
{
"path": "score/index",
"style": {
"navigationBarTitleText": "高考分数",
"navigationStyle": "custom"
}
},
{
"path": "score/center",
"style": {
"navigationBarTitleText": "查询中心",
"navigationStyle": "custom"
}
},
{
"path": "score/chongqing",
"style": {
"navigationBarTitleText": "重庆",
"navigationStyle": "custom"
}
},
{
"path": "score/hebei",
"style": {
"navigationBarTitleText": "河北",
"navigationStyle": "custom"
}
},
{
"path": "score/guandong",
"style": {
"navigationBarTitleText": "广东",
"navigationStyle": "custom"
}
},
{
"path": "video-chat/video-chat",
"style": {
"navigationBarTitleText": "视频单聊",
"navigationStyle": "custom"
}
}
]
},
@ -203,6 +559,20 @@
"navigationBarTitleText": "充值页",
"navigationStyle": "custom"
}
},
{
"path": "call-and-message-entry/call-and-message-entry",
"style": {
"navigationBarTitleText": "呼叫和短信入口",
"navigationStyle": "custom"
}
},
{
"path": "shipinjiaocheng/shipinjiaocheng",
"style": {
"navigationBarTitleText": "视频教程",
"navigationStyle": "custom"
}
}
]
}
@ -216,7 +586,9 @@
"pages/common",
"pages/finance-management",
"pages/ant-credit-pay",
"pages/other"
"pages/other",
"pages/message",
"pages/call-log"
]
}
},

View File

@ -1,21 +1,22 @@
<template>
<!-- 水印 -->
<view v-if="$isVip()">
<watermark :dark="data.dark" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark')">
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
<watermark :dark="data.dark" source="uni_alipay_huabei" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark', 'uni_alipay_huabei')">
<image src='/static/lottie/watermark.gif' mode="widthFix" style="width:94px;height:74px;" ></image>
</liu-drag-button>
</view>
<view class="page-container">
<view class="main-container">
<NavBar v-if="!selectedImage" title="花呗" :bgColor="data.navBar.bgColor" tipLayerType="huabei-tip" isTipLayer
tipLayerText="修改花呗信息" :buttonGroup="buttonGroup" @button-click="clickTitlePopupButton">
<NavBar ref="navBarRef" v-if="!selectedImage" title="花呗" :bgColor="data.navBar.bgColor"
tipLayerType="huabei-tip" isTipLayer tipLayerText="修改花呗信息" :buttonGroup="buttonGroup"
@button-click="clickTitlePopupButton">
<!-- 使用作用域插槽自定义按钮渲染,特别是switch的checked绑定 -->
<template #button="{ button }">
<view class="button flex-align-center flex-justify-center">
{{ button.name }}
<view @tap.stop>
<switch v-if="button.isSwitch" :checked="data.huabeiInfo.isOverdue" @change="button.click"
<switch v-if="button.isSwitch" :checked="data.huabeiInfo[button.key]" @change="button.click"
style="transform: scale(0.7);"></switch>
</view>
</view>
@ -44,11 +45,13 @@
<view v-if="huabeiInfo.styleType == 1" class="current-month">{{ huabeiInfo.mouth }}月应还(元)</view>
<view v-else class="current-month">{{ huabeiInfo.mouth }}月账单累计中(元)</view>
<view class="money-box flex-align-center">
<text class="money alipay-font">{{ numberUtil.formatMoneyWithThousand(huabeiInfo.money) }}</text>
<text class="money alipay-font" style="font-size: 48rpx;" v-if="data.huabeiInfo.isPayOff">已还清</text>
<text class="money alipay-font" v-else>{{ numberUtil.formatMoneyWithThousand(huabeiInfo.money) }}</text>
<uni-icons type="right" size="18" color="#B9D6FF"></uni-icons>
</view>
<!-- 样式一 按钮样式 -->
<view v-if="huabeiInfo.styleType == 1 || !huabeiInfo.styleType" class="style-1 button-group">
<view v-if="(huabeiInfo.styleType == 1 || !huabeiInfo.styleType) && !huabeiInfo.isPayOff"
class="style-1 button-group">
<view class="button-item second-button" :class="{ 'ios-button': $system == 'iOS' }">立即还款</view>
<view v-if="!huabeiInfo.isInstallment" class="button-item primary-button"
:class="{ 'ios-button': $system == 'iOS' }">
@ -58,17 +61,20 @@
</view>
</view>
<!-- 样式二 纯气泡样式 -->
<view v-if="huabeiInfo.styleType == 2" class="style-2 bubble-container">
<view v-if="(huabeiInfo.styleType == 2) && !huabeiInfo.isPayOff" class="style-2 bubble-container">
<view class="bubble-box">
<view class="arrow"></view>
<text class="text">{{ huabeiInfo.descText }}</text>
</view>
</view>
<!-- 样式三 气泡带箭头样式 -->
<view v-if="huabeiInfo.styleType == 3" class="style-3 bubble-container">
<view v-if="huabeiInfo.styleType == 3 || huabeiInfo.isPayOff" class="style-3 bubble-container">
<view class="bubble-box">
<view class="arrow"></view>
<text class="text flex-align-center">{{ huabeiInfo.descText }}
<text class="text flex-align-center">
<text style="line-height: 18px;">{{ huabeiInfo.isPayOff ? huabeiInfo.showDescText :
huabeiInfo.descText
}}</text>
<uni-icons type="right" size="18" color="#B9D6FF"></uni-icons>
</text>
</view>
@ -81,7 +87,8 @@
<view class="info-item">
<view class="label">总计额度</view>
<view class="value">{{
numberUtil.formatMoneyWithThousand(Number(huabeiInfo.totalAmount) - Number(huabeiInfo.money))
huabeiInfo.isPayOff ? numberUtil.formatMoneyWithThousand(Number(huabeiInfo.totalAmount)) :
numberUtil.formatMoneyWithThousand(Number(huabeiInfo.totalAmount) - Number(huabeiInfo.money))
}}可用
</view>
</view>
@ -111,6 +118,7 @@
</view>
<canvas canvas-id="crop-canvas"
style="position: fixed; left: -9999px; width: 750rpx; height: 100vh; pointer-events: none;"></canvas>
<ZdySafeBottom bgColor="#fff"/>
<!-- 编辑弹窗 -->
<uni-popup ref="popup" type="center" :mask-click="false">
<view class="popup-content">
@ -183,6 +191,7 @@
<script setup>
import NavBar from '@/components/nav-bar/nav-bar'
import ZdySafeBottom from '@/components/safeBottom/safeBottom.vue'
import {
numberUtil,
util
@ -201,6 +210,8 @@ import {
const instance = getCurrentInstance();
const { proxy } = instance;
const navBarRef = ref(null)
const buttonGroup = [{
name: "编辑花呗数据",
@ -221,6 +232,7 @@ const buttonGroup = [{
}, {
name: "花呗逾期",
isSwitch: true,
key: 'isOverdue',
click: () => {
data.huabeiInfo.isOverdue = !data.huabeiInfo.isOverdue
uni.setStorageSync(data.huabeiInfoStorageKey, data.huabeiInfo)
@ -229,6 +241,20 @@ const buttonGroup = [{
url: '/pages/ant-credit-pay/overdue-payment/overdue-payment'
})
}
navBarRef.value.closeTopPopup()
}
}, {
name: "还清花呗",
isSwitch: true,
key: 'isPayOff',
click: () => {
data.huabeiInfo.isPayOff = !data.huabeiInfo.isPayOff
// uni.setStorageSync(data.huabeiInfoStorageKey, data.huabeiInfo)
if (data.huabeiInfo.isPayOff) {
data.huabeiInfo.showDescText = `${Number(data.huabeiInfo.mouth) + 1}月已累计0.00,查看消费详情报告`
}
uni.setStorageSync(data.huabeiInfoStorageKey, data.huabeiInfo)
navBarRef.value.closeTopPopup()
}
}]
@ -316,7 +342,7 @@ onLoad((option) => {
})
onShow(() => {
// #ifdef APP-PLUS
// #ifdef APP-PLUS&&!APP-HARMONY
util.setAndroidSystemBarColor('#ffffff')
setTimeout(() => {
plus.navigator.setStatusBarStyle("light");
@ -439,7 +465,9 @@ const confirmImage = () => {
data.huabeiInfo.image = saveRes.savedFilePath
selectedImage.value = '' // 隐藏编辑模式
setTimeout(() => {
// #ifdef APP-PLUS&&!APP-HARMONY
plus.navigator.setStatusBarStyle("light");
// #endif
}, 200)
// 保存到缓存
uni.setStorageSync(data.huabeiInfoStorageKey, data.huabeiInfo)
@ -500,7 +528,9 @@ const chooseImage = () => {
selectedImage.value = res.tempFilePaths[0]
data.showMask = true
setTimeout(() => {
// #ifdef APP-PLUS&&!APP-HARMONY
plus.navigator.setStatusBarStyle("dark");
// #endif
}, 500)
}
})
@ -537,7 +567,9 @@ const handleTouchEnd = () => {
const closeImage = () => {
selectedImage.value = ''
data.showMask = false
// #ifdef APP-PLUS&&!APP-HARMONY
plus.navigator.setStatusBarStyle("light");
// #endif
return false
}
@ -704,7 +736,7 @@ const goBack = () => {
justify-content: center;
.text {
color: #ffffff;
color: #B9D6FF;
font-size: 26rpx;
line-height: 1.2;
}
@ -738,7 +770,7 @@ const goBack = () => {
}
.value {
color: #ffffff;
color: #B9D6FF;
font-size: 26rpx;
line-height: 36rpx;
}

View File

@ -1,4 +1,4 @@
<template>
<template>
<view class="page-container">
<view class="main-container">
@ -206,9 +206,9 @@
<!-- 水印 -->
<view v-if="$isVip()">
<watermark :dark="data.dark" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark')">
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
<watermark :dark="data.dark" source="uni_alipay_huabei" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark', 'uni_alipay_huabei')">
<image src='/static/lottie/watermark.gif' mode="widthFix" style="width:94px;height:74px;" ></image>
</liu-drag-button>
</view>
</template>
@ -407,18 +407,22 @@ onPageScroll((e) => {
data.navBar.bgColor = '#F6F6F6'
data.navBar.textColor = '#1a1a1a'
data.isTop = false
// #ifdef APP-PLUS&&!APP-HARMONY
plus.navigator.setStatusBarStyle("dark");
// #endif
} else {
// 回到顶部时恢复原色
data.navBar.bgColor = '#717E91'
data.navBar.textColor = '#fff'
data.isTop = true
// #ifdef APP-PLUS&&!APP-HARMONY
plus.navigator.setStatusBarStyle("light");
// #endif
}
})
onShow(() => {
// #ifdef APP-PLUS
// #ifdef APP-PLUS&&!APP-HARMONY
util.setAndroidSystemBarColor('#F6F6F6')
setTimeout(() => {
plus.navigator.setStatusBarStyle("light");

View File

@ -1,14 +1,14 @@
<template>
<template>
<!-- 水印 -->
<view v-if="$isVip()">
<watermark :dark="data.dark" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark')">
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
<watermark :dark="data.dark" source="uni_alipay_balance" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark', 'uni_alipay_balance')">
<image src='/static/lottie/watermark.gif' mode="widthFix" style="width:94px;height:74px;" ></image>
</liu-drag-button>
</view>
<view class="container" :style="{ height: data.windowHeight + 'px' }">
<view class="bg-container"></view>
<NavBar class="nav-bar" isRightIcon title="" tipLayerType="balance-tip" isTipLayer tipLayerText="修改余额"
<nav-bar class="nav-bar" isRightIcon title="" tipLayerType="balance-tip" isTipLayer tipLayerText="修改余额"
:bgColor="data.navBar.bgColor" :buttonGroup="buttonGroup" @button-click="clickTitlePopupButton">
<template v-slot:left>
<view class="nav-bar-left" @click="util.goBack()">
@ -21,7 +21,7 @@
<image class="nav-icon" src="/static/image/nav-bar/more-white.png" mode=""></image>
</view>
</template>
</NavBar>
</nav-bar>
<scroll-view class="scroll-view" :style="{ height: (data.windowHeight - 44 - data.statusBarHeight) + 'px' }"
scroll-y="true">
<view class="h100 w100 flex-between" style="flex-direction: column;">
@ -58,7 +58,7 @@
<scroll-view class="menu-scroll-view" scroll-x="true" @scroll="onMenuScroll"
:show-scrollbar="false">
<view class="menu-content">
<view class="item menu-icon-box" v-for="item in menuList">
<view class="item menu-icon-box" v-for="(item, index) in menuList" :key="index" >
<image class="menu-icon" :src="`/static/image/balance/menu-icon/${item.label}.png`"
mode="">
</image>
@ -89,6 +89,8 @@
<view class="blue-text">我的客服</view>
<view class="footer-text">余额升级服务由支付宝和网商银行提供</view>
</view>
<safeBottom bgColor="#F0F3F8"/>
</view>
</scroll-view>
@ -111,8 +113,10 @@
</template>
<script setup>
import NavBar from '@/components/nav-bar/nav-bar'
//import NavBar from '@/components/nav-bar/nav-bar'
import BalanceList from '@/components/balance-list/balance-list.vue'
import ZdySafeBottom from '@/components/safeBottom/safeBottom.vue'
import { fastEntranceList } from '@/static/json/initial.json'
import {
util,
@ -211,7 +215,7 @@ onLoad(async () => {
})
onShow(() => {
// #ifdef APP-PLUS
// #ifdef APP-PLUS&&!APP-HARMONY
util.setAndroidSystemBarColor('#F0F3F8')
setTimeout(() => {
plus.navigator.setStatusBarStyle("light");
@ -515,11 +519,12 @@ const onMenuScroll = (e) => {
}
.nav-text {
font-size: 18px;
font-size: 36rpx;
margin-right: 4px;
color: #FFFFFF;
height: 24px;
line-height: 24px;
font-weight: 500;
}
::v-deep .uni-navbar__header-btns-left {

View File

@ -0,0 +1,153 @@
{
"randomData": [
{
"title": "充值满减券",
"tag": "特惠充",
"desc": "满50元可用",
"price": "1",
"butText": "去看看",
"imgUrl": "/static/image/balance/pay-successful/chongzhi.png"
},
{
"title": "芭芭农场肥料",
"tag": "芭芭农场",
"desc": "现实翻倍",
"price": "",
"butText": "去看看",
"imgUrl": "/static/image/balance/pay-successful/88nongchang.png"
},
{
"title": "心相印抽纸优惠券",
"tag": "分期生活",
"desc": "满一元可用",
"price": "1",
"butText": "免费领",
"imgUrl": "/static/image/balance/pay-successful/xinxiangyin.png"
},
{
"title": "蜜雪冰城全场券",
"tag": "外卖福利",
"desc": "满10元可用",
"price": "3",
"butText": "去领取",
"imgUrl": "/static/image/balance/pay-successful/mixue.png"
},
{
"title": "充值满减券",
"tag": "中国石化",
"desc": "满50元可用",
"price": "3",
"butText": "免费领",
"imgUrl": "/static/image/balance/pay-successful/shiyou.png"
},
{
"title": "零售购物券",
"tag": "淘宝闪购",
"desc": "满29元可用",
"price": "8",
"butText": "去领取",
"imgUrl": "/static/image/balance/pay-successful/shangou.png"
},
{
"title": "公交乘车券",
"tag": "公交地铁",
"desc": "享3笔0.3乘车优惠",
"price": "0.8",
"butText": "去看看",
"imgUrl": "/static/image/balance/pay-successful/chengche.png"
},
{
"title": "电燃费优惠红包",
"tag": "生活缴费",
"desc": "缴电燃气费立减",
"price": "0.5",
"butText": "免费领",
"imgUrl": "/static/image/balance/pay-successful/shuidian.png"
}
],
"defaultData": {
"user": "小王",
"money": "88888.88",
"payMethod": "招商银行卡(8888)",
"bannerTime": "07月12日12:22",
"styleType": "style3",
"style1": {
"couponlist": [
{
"title": "充值满减券",
"tag": "中国石化",
"desc": "满50元可用",
"price": "3",
"butText": "免费领",
"imgUrl": "/static/image/balance/pay-successful/shiyou.png"
}
],
"bannerUrl": "/static/image/balance/transfer/bottom-banner-1.png"
},
"style2": {
"couponlist": [
{
"title": "蜜雪冰城全场券",
"tag": "外卖福利",
"desc": "满10元可用",
"price": "3",
"butText": "去领取",
"imgUrl": "/static/image/balance/pay-successful/mixue.png"
},
{
"title": "充值满减券",
"tag": "中国石化",
"desc": "满50元可用",
"price": "3",
"butText": "免费领",
"imgUrl": "/static/image/balance/pay-successful/shiyou.png"
},
{
"title": "零售购物券",
"tag": "淘宝闪购",
"desc": "满29元可用",
"price": "8",
"butText": "去领取",
"imgUrl": "/static/image/balance/pay-successful/shangou.png"
},
{
"title": "公交乘车券",
"tag": "公交地铁",
"desc": "享3笔0.3乘车优惠",
"price": "0.8",
"butText": "去看看",
"imgUrl": "/static/image/balance/pay-successful/chengche.png"
}
]
},
"style3": {
"couponlist": [
{
"title": "充值满减券",
"tag": "中国石化",
"desc": "满50元可用",
"price": "3",
"butText": "免费领",
"imgUrl": "/static/image/balance/pay-successful/shiyou.png"
},
{
"title": "心相印抽纸优惠券",
"tag": "分期生活",
"desc": "满一元可用",
"price": "1",
"butText": "免费领",
"imgUrl": "/static/image/balance/pay-successful/xinxiangyin.png"
},
{
"title": "蜜雪冰城全场券",
"tag": "外卖福利",
"desc": "满10元可用",
"price": "3",
"butText": "去领取",
"imgUrl": "/static/image/balance/pay-successful/mixue.png"
}
],
"bannerTime": "07月12日12:22"
}
}
}

View File

@ -0,0 +1,691 @@
<template>
<view class="container">
<nav-bar bgColor="#F5F5F5" :isBack="true" title="编辑账单详情" isRightButton @right-click="saveData"></nav-bar>
<view class="tabs">
<view class="tab-item" :class="{ active: payData.styleType == 'style1' }"
@click="payData.styleType = 'style1'">样式1
</view>
<view class="tab-item" :class="{ active: payData.styleType == 'style2' }"
@click="payData.styleType = 'style2'">样式2
</view>
<view class="tab-item" :class="{ active: payData.styleType == 'style3' }"
@click="payData.styleType = 'style3'">样式3
</view>
</view>
<view class="main-content">
<!-- 基础信息卡片 -->
<view class="card basic-card">
<view class="amount-wrap">
<text class="symbol">¥</text>
<auto-width-input class="alipay-font" type="digit" v-model="payData.money" fontSize="88rpx"
fontWeight="500" color="#1A1A1A" :showEdit="true"></auto-width-input>
</view>
<view class="info-row">
<view class="info-item">
<auto-width-input v-model="payData.user" fontSize="28rpx" color="#333"
:showEdit="true"></auto-width-input>
</view>
<view class="info-item" style="justify-content: flex-end;">
<text class="value-symbol">¥ </text>
<auto-width-input type="digit" v-model="payData.money" fontSize="28rpx" color="#333"
:showEdit="true"></auto-width-input>
</view>
</view>
<view class="info-row" style="margin-top: 16rpx;">
<view class="info-item">
<text class="label">付款方式</text>
</view>
<view class="info-item" style="justify-content: flex-end;">
<auto-width-input v-model="payData.payMethod" fontSize="28rpx" color="#333"
:showEdit="true"></auto-width-input>
</view>
</view>
</view>
<!-- 样式1 -->
<template v-if="payData.styleType == 'style1'">
<view class="card list-card">
<view class="card-title" style="display: flex; justify-content: space-between;">
支付有礼
<image style="width: 24px;height: 24px;" src="/static/image/common/random-dice.png"
mode="widthFix" @click="randomCoupon('style1')"></image>
</view>
<!-- 券1 -->
<view class="list-item" v-if="payData.style1.couponlist && payData.style1.couponlist[0]">
<view class="left-icon-wrap" @click="chooseImage('style1_coupon_0')">
<image v-if="payData.style1.couponlist[0].imgUrl" class="item-img"
:src="payData.style1.couponlist[0].imgUrl" mode="aspectFill"></image>
<image v-else class="edit-icon-center" src="/static/image/common/edit.png"></image>
</view>
<view class="item-content">
<view class="row-flex" style="flex-wrap: wrap;">
<view class="row-flex" style="margin-right: 12rpx;"
v-if="payData.style1.couponlist[0].price !== undefined">
<auto-width-input v-model="payData.style1.couponlist[0].price" fontSize="28rpx"
color="#E6372D" fontWeight="500"></auto-width-input>
<text class="main-title" style="color: #E6372D; width: auto;">元</text>
<image class="edit-icon" src="/static/image/common/edit.png"
style="margin-left: 8rpx; width: 24rpx; height: 24rpx;"></image>
</view>
<view class="row-flex">
<auto-width-input v-model="payData.style1.couponlist[0].title" fontSize="28rpx"
color="#333" fontWeight="500" :showEdit="true"></auto-width-input>
</view>
</view>
<view class="row-flex mt">
<view class="tag-red" style="margin-right: 12rpx; display: flex; align-items: center;"
v-if="payData.style1.couponlist[0].tag !== undefined">
<auto-width-input v-model="payData.style1.couponlist[0].tag" fontSize="20rpx"
color="#E6372D" :showEdit="true"></auto-width-input>
</view>
<view class="row-flex" v-if="payData.style1.couponlist[0].desc !== undefined">
<auto-width-input v-model="payData.style1.couponlist[0].desc" fontSize="24rpx"
color="#999" :showEdit="true"></auto-width-input>
</view>
</view>
</view>
<view class="right-btn btn-get"
style="display: flex; align-items: center; justify-content: center;"
@click="toggleBtnText(payData.style1.couponlist[0])">
<text style="font-size: 24rpx; color: #fff; font-weight: 500;">{{
payData.style1.couponlist[0].butText || '免费领' }}</text>
</view>
</view>
</view>
<!-- 飞猪广告 -->
<view class="card fliggy-card">
<view class="banner-wrap" @click="toggleBanner">
<image class="banner-img"
:src="payData.style1.bannerUrl || '/static/image/balance/transfer/bottom-banner-1.png'"
mode="widthFix"></image>
<view class="guanggao-tag">
<image style="width: 70rpx;height: 32rpx;"
src="/static/image/balance/transfer/guanggao.png"></image>
</view>
<view class="yaoyiyao-container">
<view class="yaoyiyao-box">
<image class="img" src="/static/image/balance/transfer/yaoyiyao.png" mode="widthFix">
</image>
<text class="text">摇动或点击查看详情</text>
</view>
</view>
</view>
<view class="fliggy-footer"
v-if="payData.style1.bannerUrl == '/static/image/balance/transfer/bottom-banner-1.png'">
<view class="logo-box">
<image class="logo" src="/static/image/balance/transfer/feizhu.png" mode="aspectFit">
</image>
</view>
<view class="banner-info">
<view class="text-content">
<text class="b-title">飞猪旅行</text>
<text class="b-desc">飞猪出行 超多折扣</text>
</view>
<view class="b-btn">领优惠</view>
</view>
</view>
<view class="fliggy-footer"
v-if="payData.style1.bannerUrl == '/static/image/balance/transfer/bottom-banner-2.png'">
<view class="logo-box">
<image class="logo" src="/static/image/balance/transfer/taobaoshangou.png" mode="aspectFit">
</image>
</view>
<view class="banner-info" style="padding: 0;">
<view class="text-content">
<text class="b-title">淘宝闪购</text>
<text class="b-desc">每天领大额外面红包</text>
</view>
<view class="b-btn">去领取</view>
</view>
</view>
</view>
</template>
<!-- 样式2 -->
<template v-if="payData.styleType == 'style2'">
<view class="card list-card">
<view class="card-title" style="display: flex; justify-content: space-between;">支付有礼
<image style="width: 24px;height: 24px;" src="/static/image/common/random-dice.png"
mode="widthFix" @click="randomCoupon('style2')"></image>
</view>
<view class="list-item" v-for="(item, index) in payData.style2.couponlist" :key="index">
<view class="left-icon-wrap" @click="chooseImage('style2_coupon_' + index)">
<image v-if="item.imgUrl" class="item-img" :src="item.imgUrl" mode="aspectFill"></image>
<image v-else class="edit-icon-center" src="/static/image/common/edit.png"></image>
</view>
<view class="item-content">
<view class="row-flex" style="flex-wrap: wrap;">
<view class="row-flex" style="margin-right: 12rpx;" v-if="item.price !== undefined">
<auto-width-input v-model="item.price" fontSize="28rpx"
:color="item.price.includes('g') ? '#333' : '#E6372D'"
fontWeight="500"></auto-width-input>
<text class="main-title"
:style="{ color: item.price.includes('g') ? '#333' : '#E6372D', width: 'auto' }">{{
item.price.includes('g') ? '' : '元' }}</text>
<image class="edit-icon" src="/static/image/common/edit.png"
style="margin-left: 8rpx; width: 24rpx; height: 24rpx;"></image>
</view>
<view class="row-flex">
<auto-width-input v-model="item.title" fontSize="28rpx" color="#333"
fontWeight="500" :showEdit="true"></auto-width-input>
</view>
</view>
<view class="row-flex mt">
<view class="tag-red" :class="{ 'tag-orange': item.tag == '芭芭农场' }"
style="margin-right: 12rpx; display: flex; align-items: center;"
v-if="item.tag !== undefined">
<auto-width-input v-model="item.tag" fontSize="20rpx"
:color="item.tag == '芭芭农场' ? '#EF673C' : '#E6372D'"
:showEdit="true"></auto-width-input>
</view>
<view class="row-flex" v-if="item.desc !== undefined">
<auto-width-input v-model="item.desc" fontSize="24rpx" color="#999"
:showEdit="true"></auto-width-input>
</view>
</view>
</view>
<view class="right-btn btn-get" :class="{ 'btn-look': item.butText == '去看看' }"
style="display: flex; align-items: center; justify-content: center;"
@click="toggleBtnText(item)">
<text style="font-size: 24rpx; color: #fff; font-weight: 500;">{{ item.butText || '免费领'
}}</text>
</view>
</view>
</view>
</template>
<!-- 样式3 -->
<template v-if="payData.styleType == 'style3'">
<view class="card list-card" style="padding-bottom: 24rpx;">
<view class="flex-between" style="margin-bottom: 24rpx;">
<text class="card-title" style="margin-bottom: 0;">碰一下立减</text>
<view class="row-flex">
<auto-width-input v-model="payData.style3.bannerTime" fontSize="24rpx"
color="#666"></auto-width-input>
<text class="time-text" style="width: auto;">后过期</text>
<image class="edit-icon" src="/static/image/common/edit.png"
style="margin-left: 8rpx; width: 24rpx; height: 24rpx;"></image>
</view>
</view>
<view class="banner-box">
<text class="banner-time">{{ payData.style3.bannerTime }}后过期</text>
<image src="/static/image/balance/pay-successful/pengyipengbanner.png"
style="width: 100%; border-radius: 12rpx; display: block;" mode="widthFix"></image>
</view>
</view>
<view class="card-title" style="display: flex; justify-content: flex-end;padding: 0 16px;">
<image style="width: 24px;height: 24px;" src="/static/image/common/random-dice.png" mode="widthFix"
@click="randomCoupon('style3')">
</image>
</view>
<view class="card list-card" style="margin-top: 24rpx;">
<view class="list-item" v-for="(item, index) in payData.style3.couponlist" :key="index">
<view class="left-icon-wrap" @click="chooseImage('style3_coupon_' + index)">
<image v-if="item.imgUrl" class="item-img" :src="item.imgUrl" mode="aspectFill"></image>
<image v-else class="edit-icon-center" src="/static/image/common/edit.png"></image>
</view>
<view class="item-content" style="flex: 1;">
<view class="row-flex">
<auto-width-input v-model="item.title" fontSize="28rpx" color="#333" fontWeight="500"
:showEdit="true"></auto-width-input>
</view>
<view class="row-flex mt">
<view class="tag-red" style="margin-right: 12rpx; display: flex; align-items: center;"
v-if="item.tag !== undefined">
<auto-width-input v-model="item.tag" fontSize="20rpx" color="#E6372D"
:showEdit="true"></auto-width-input>
</view>
<view class="row-flex" v-if="item.desc !== undefined">
<auto-width-input v-model="item.desc" fontSize="24rpx" color="#999"
:showEdit="true"></auto-width-input>
</view>
</view>
</view>
<view class="row-flex" style="align-items: baseline;" v-if="item.price !== undefined">
<auto-width-input v-model="item.price" fontSize="40rpx" color="#E6372D" fontWeight="500"
:showEdit="true"></auto-width-input>
</view>
</view>
</view>
</template>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { defaultData, randomData } from './data.json';
import { util } from '@/utils/common.js';
import autoWidthInput from '@/components/common/auto-width-input.vue';
const payData = ref(JSON.parse(JSON.stringify(defaultData)));
const PAY_STORAGE_KEY = 'alipay_pay_successful_data';
const randomCoupon = (styleName) => {
// #ifdef APP-PLUS
const oldList = payData.value[styleName].couponlist || [];
oldList.forEach(item => {
if (item.imgUrl && !item.imgUrl.startsWith('/static') && !item.imgUrl.startsWith('http')) {
uni.removeSavedFile({ filePath: item.imgUrl });
}
});
// #endif
const count = payData.value[styleName].couponlist.length;
const shuffled = [...randomData].sort(() => 0.5 - Math.random());
const selected = shuffled.slice(0, count);
payData.value[styleName].couponlist = JSON.parse(JSON.stringify(selected));
};
const loadStorageData = () => {
const savedData = uni.getStorageSync(PAY_STORAGE_KEY);
if (savedData) {
payData.value = Object.assign({}, payData.value, savedData);
}
};
onLoad(() => {
loadStorageData();
});
const saveData = () => {
uni.setStorageSync(PAY_STORAGE_KEY, payData.value);
uni.navigateBack();
};
const chooseImage = (type) => {
uni.chooseImage({
count: 1,
success: (res) => {
const tempPath = res.tempFilePaths[0];
let oldPath = '';
if (type === 'style1_coupon_0') {
oldPath = payData.value.style1.couponlist[0].imgUrl;
} else if (type.startsWith('style2_coupon_')) {
const idx = parseInt(type.split('_')[2]);
oldPath = payData.value.style2.couponlist[idx].imgUrl;
} else if (type.startsWith('style3_coupon_')) {
const idx = parseInt(type.split('_')[2]);
oldPath = payData.value.style3.couponlist[idx].imgUrl;
}
const updateImgUrl = (path) => {
if (type === 'style1_coupon_0') {
payData.value.style1.couponlist[0].imgUrl = path;
} else if (type.startsWith('style2_coupon_')) {
const idx = parseInt(type.split('_')[2]);
payData.value.style2.couponlist[idx].imgUrl = path;
} else if (type.startsWith('style3_coupon_')) {
const idx = parseInt(type.split('_')[2]);
payData.value.style3.couponlist[idx].imgUrl = path;
}
};
// #ifdef APP-PLUS
if (oldPath && !oldPath.startsWith('/static') && !oldPath.startsWith('http')) {
// 尝试删除旧的本地文件,避免存储空间堆积
uni.removeSavedFile({ filePath: oldPath });
}
uni.saveFile({
tempFilePath: tempPath,
success: (saveRes) => {
updateImgUrl(saveRes.savedFilePath);
}
});
// #endif
// #ifndef APP-PLUS
updateImgUrl(tempPath);
// #endif
}
});
};
const toggleBtnText = (item) => {
const textList = ['免费领', '去看看', '去领取'];
const currentIndex = textList.indexOf(item.butText);
if (currentIndex > -1) {
item.butText = textList[(currentIndex + 1) % textList.length];
} else {
item.butText = '免费领';
}
};
const toggleBanner = () => {
const current = payData.value.style1.bannerUrl || '/static/image/balance/transfer/bottom-banner-1.png';
if (current.includes('bottom-banner-1.png')) {
payData.value.style1.bannerUrl = '/static/image/balance/transfer/bottom-banner-2.png';
} else {
payData.value.style1.bannerUrl = '/static/image/balance/transfer/bottom-banner-1.png';
}
};
</script>
<style lang="less" scoped>
@import "@/common/main.css";
.container {
min-height: 100vh;
background-color: #F5F5F5;
padding-bottom: 60rpx;
}
.confirm-btn {
background-color: #3884FD;
color: #fff;
font-size: 26rpx;
padding: 10rpx 32rpx;
border-radius: 30rpx;
}
.tabs {
display: flex;
justify-content: center;
gap: 20rpx;
margin: 24rpx 0;
}
.tab-item {
padding: 12rpx 40rpx;
background-color: #fff;
color: #333;
font-size: 28rpx;
border-radius: 8rpx;
border: 1px solid #fff;
}
.tab-item.active {
color: #3884FD;
border-color: #3884FD;
}
.main-content {
padding: 0 0;
}
.card {
background: #fff;
border-radius: 16rpx;
margin: 24rpx;
padding: 32rpx;
}
.basic-card {
display: flex;
flex-direction: column;
align-items: center;
}
.amount-wrap {
display: flex;
align-items: baseline;
justify-content: center;
color: #1A1A1A;
margin-top: 10rpx;
margin-bottom: 50rpx;
.symbol {
font-size: 60rpx;
margin-right: 8rpx;
}
}
.info-row {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
}
.info-item {
display: flex;
align-items: center;
.label {
font-size: 28rpx;
color: #333;
width: 200rpx;
}
.value-symbol {
font-size: 28rpx;
color: #333;
}
}
.edit-icon {
width: 24rpx;
height: 24rpx;
margin-left: 10rpx;
}
.edit-icon-center {
width: 28rpx;
height: 28rpx;
}
.item-img {
width: 100%;
height: 100%;
border-radius: 50%;
}
.left-icon-wrap {
width: 70rpx;
height: 70rpx;
border-radius: 50%;
background-color: #F5F5F5;
display: flex;
justify-content: center;
align-items: center;
margin-right: 20rpx;
}
.list-card {
.card-title {
font-size: 32rpx;
font-weight: bold;
color: #1A1A1A;
margin-bottom: 32rpx;
}
.list-item {
display: flex;
align-items: center;
margin-bottom: 40rpx;
&:last-child {
margin-bottom: 0;
}
}
}
.item-content {
flex: 1;
}
.row-flex {
display: flex;
align-items: center;
}
.mt {
margin-top: 8rpx;
}
.main-title {
font-size: 28rpx;
color: #333;
font-weight: 500;
}
.sub-title {
font-size: 24rpx;
color: #999;
}
.tag-red {
font-size: 20rpx;
color: #E6372D;
background: #FFF1F0;
padding: 4rpx 10rpx;
border-radius: 6rpx;
}
.tag-orange {
color: #EF673C;
background: #FFF2F1;
}
.right-btn {
padding: 10rpx 28rpx;
border-radius: 26rpx;
font-size: 24rpx;
font-weight: 500;
}
.btn-get {
background-color: #E6372D;
color: #fff;
}
.btn-look {
background-color: #E6372D;
color: #fff;
}
.fliggy-card {
padding: 0;
overflow: hidden;
.banner-wrap {
position: relative;
.banner-img {
width: 100%;
display: block;
}
.guanggao-tag {
position: absolute;
top: 16rpx;
right: 16rpx;
}
.yaoyiyao-container {
position: absolute;
bottom: 20rpx;
width: 100%;
display: flex;
justify-content: center;
.yaoyiyao-box {
background: rgba(0, 0, 0, 0.4);
padding: 10rpx 24rpx;
border-radius: 30rpx;
display: flex;
align-items: center;
.img {
width: 24rpx;
margin-right: 10rpx;
}
.text {
color: #fff;
font-size: 24rpx;
}
}
}
}
.fliggy-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx;
.logo-box {
width: 76rpx;
height: 76rpx;
margin-right: 20rpx;
display: flex;
justify-content: center;
align-items: center;
background: #fff;
border-radius: 50%;
.logo {
width: 100%;
height: 100%;
}
}
.banner-info {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
.text-content {
display: flex;
flex-direction: column;
.b-title {
font-size: 28rpx;
color: #333;
font-weight: bold;
}
.b-desc {
font-size: 24rpx;
color: #999;
margin-top: 6rpx;
}
}
.b-btn {
background-color: #E6372D;
color: #fff;
padding: 10rpx 28rpx;
border-radius: 26rpx;
font-size: 24rpx;
}
}
}
}
.flex-between {
display: flex;
justify-content: space-between;
align-items: center;
}
.time-text {
font-size: 24rpx;
color: #666;
}
.banner-box {
position: relative;
width: 100%;
.banner-time {
position: absolute;
z-index: 10;
left: 170rpx;
top: 68rpx;
font-size: 20rpx;
color: #C70B0C;
}
}
</style>

View File

@ -0,0 +1,769 @@
<template>
<!-- 水印 -->
<view v-if="$isVip()">
<watermark dark="light" source="uni_alipay_pay-successful" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark', 'uni_alipay_pay-successful')">
<image src='/static/lottie/watermark.gif' mode="widthFix" style="width:94px;height:74px;" ></image>
</liu-drag-button>
</view>
<view class="container">
<!-- 蓝色背景区域 -->
<view class="header-section">
<nav-bar ref="navBarRef" bgColor="transparent" :isBack="false" :buttonGroup="buttonGroup"
tipLayerType="pay-successful-tip" isTipLayer tipLayerText="修改支付数据" @button-click="handleButtonClick">
<template v-slot:right>
<view class="nav-link w100"
style="display: flex; align-items: center; justify-content: flex-end; white-space: nowrap; padding: 0;">
<text @click="util.goBack()" style="margin-right: 20rpx;">回首页</text>
</view>
</template>
<template v-slot:center>
<view class="status-box">
<image class="image" src="/static/image/balance/transfer/success.png"></image>
<text class="status-text">支付成功</text>
</view>
</template>
</nav-bar>
<!-- 转账状态信息 -->
<view class="success-info">
<view class="amount-box" @click="goEdit">
<text class="symbol">¥</text>
<text class="amount alipay-font">{{ Number(payData.money).toFixed(2) }}</text>
</view>
<!-- 收款方/付款方式 -->
<view class="details-box">
<view class="detail-item">
<text class="label">{{ payData.user }}</text>
<text class="value">{{ Number(payData.money).toFixed(2) }}</text>
</view>
<view class="detail-item">
<text class="label">付款方式</text>
<text class="value">{{ payData.payMethod }}</text>
</view>
</view>
</view>
</view>
<view class="main-content" :style="{ top: `${190 + statusBarHeight}px` }">
<!-- 样式1 -->
<template v-if="payData.styleType == 'style1'">
<view class="pay-success-card">
<image class="title-img" style="width: 106rpx;height: 28rpx;"
src="/static/image/balance/pay-successful/zhifuyouli.png" mode="widthFix"></image>
<view class="banner-footer" style="padding: 0 16rpx;">
<view class="logo-box">
<image class="logo" src="/static/image/balance/pay-successful/zhifufuli.png"
mode="aspectFit">
</image>
</view>
<view class="banner-info" style="border-top: none;">
<view class="text-content">
<text class="b-title">领取<text style="color: #EA4833;">今日</text>支付福利</text>
<text class="b-desc">领优惠,支付更省钱</text>
</view>
<view class="b-btn">去看看</view>
</view>
</view>
<view class="banner-footer" style="padding: 0 16rpx;">
<view class="logo-box">
<image class="logo" :src="payData.style1.couponlist[0].imgUrl" mode="aspectFit">
</image>
</view>
<view class="banner-info">
<view class="text-content">
<text class="b-title">
<text
v-if="payData.style1.couponlist[0].price && payData.style1.couponlist[0].price > 0"
style="color: #EA4833;">{{ payData.style1.couponlist[0].price }}元</text>
{{ payData.style1.couponlist[0].title }}
</text>
<text class="b-desc">
<text class="b-desc-tag">{{ payData.style1.couponlist[0].tag }}</text>
{{ payData.style1.couponlist[0].desc }}
</text>
</view>
<view class="b-btn">{{ payData.style1.couponlist[0].butText }}</view>
</view>
</view>
</view>
<view class="content-section">
<!-- 飞猪广告 -->
<view class="card fliggy-card">
<image class="banner-img" :src="payData.style1.bannerUrl" mode="widthFix" @click="toggleBanner">
</image>
<view class="guanggao-tag">
<image style="width: 70rpx;height: 32rpx;"
src="/static/image/balance/transfer/guanggao.png">
</image>
</view>
<view class="yaoyiyao-container">
<view class="yaoyiyao-box">
<image class="img" src="/static/image/balance/transfer/yaoyiyao.png" mode="widthFix">
</image>
<text class="text">摇动或点击查看详情</text>
</view>
</view>
<view class="banner-footer"
v-if="payData.style1.bannerUrl == '/static/image/balance/transfer/bottom-banner-1.png'">
<view class="logo-box">
<image class="logo" src="/static/image/balance/transfer/feizhu.png" mode="aspectFit">
</image>
</view>
<view class="banner-info" style="padding: 0;">
<view class="text-content">
<text class="b-title">飞猪旅行</text>
<text class="b-desc">飞猪出行 超多折扣</text>
</view>
<view class="b-btn">领优惠</view>
</view>
</view>
<view class="banner-footer"
v-if="payData.style1.bannerUrl == '/static/image/balance/transfer/bottom-banner-2.png'">
<view class="logo-box">
<image class="logo" src="/static/image/balance/transfer/taobaoshangou.png"
mode="aspectFit">
</image>
</view>
<view class="banner-info" style="padding: 0;">
<view class="text-content">
<text class="b-title">淘宝闪购</text>
<text class="b-desc">每天领大额外面红包</text>
</view>
<view class="b-btn">去领取</view>
</view>
</view>
</view>
</view>
</template>
<!-- 样式2 -->
<template v-if="payData.styleType == 'style2'">
<view class="pay-success-card">
<image class="title-img" style="width: 106rpx;height: 28rpx;"
src="/static/image/balance/pay-successful/zhifuyouli.png" mode="widthFix"></image>
<view class="banner-footer" style="padding: 0 16rpx;">
<view class="logo-box">
<image class="logo" src="/static/image/balance/pay-successful/zhifufuli.png"
mode="aspectFit">
</image>
</view>
<view class="banner-info" style="border-top: none;">
<view class="text-content">
<text class="b-title">领取<text style="color: #EA4833;">今日</text>支付福利</text>
<text class="b-desc">领优惠,支付更省钱</text>
</view>
<view class="b-btn">去看看</view>
</view>
</view>
<view class="banner-footer" style="padding: 0 16rpx;"
v-for="item, index in payData.style2.couponlist" :key="index">
<view class="logo-box">
<image class="logo" :src="item.imgUrl" mode="aspectFit">
</image>
</view>
<view class="banner-info">
<view class="text-content">
<text class="b-title">
<text v-if="item.price" style="color: #EA4833;">{{ item.price }}元</text>{{
item.title
}}</text>
<text class="b-desc">
<text class="b-desc-tag">{{ item.tag }}</text>
{{ item.desc }}</text>
</view>
<view class="b-btn">{{ payData.style1.couponlist[0].butText }}</view>
</view>
</view>
</view>
</template>
<template v-if="payData.styleType == 'style3'">
<view style="padding: 24rpx 20rpx;margin-bottom: 24rpx;" class="pay-success-card">
<text class="title">支付有礼</text>
<view class="banner-box">
<text class="banner-time">{{ payData.style3.bannerTime }}后过期</text>
<image src="/static/image/balance/pay-successful/pengyipengbanner.png" style="width: 100%;"
mode="widthFix"></image>
</view>
</view>
<view class="promo-card" v-for="item, index in payData.style3.couponlist" :key="index">
<view class="promo-left-box">
<image class="img" :src="item.imgUrl" mode="aspectFill"></image>
</view>
<view class="promo-right">
<view class="promo-content">
<text class="promo-title">{{ item.title }}</text>
<view class="promo-tags">
<text class="tag">{{ item.tag }}</text>
<text class="tag-desc">{{ item.desc }}</text>
</view>
</view>
<view class="promo-right-box">
<view class="price-box">
<text class="p-num alipay-font">{{ item.price }}</text>
<text class="p-unit">元</text>
</view>
<view class="free-btn">{{ item.butText }}</view>
</view>
</view>
</view>
</template>
</view>
<!-- 底部操作区 -->
<view class="footer-section" :style="{'padding-bottom':($store.state.safeBottomAz+24)+'px'}">
<view class="finish-btn" @click="goBack">完成</view>
</view>
<ZdySafeBottom bgColor="#f5f5f5"/>
</view>
</template>
<script setup>
import {
ref,
onMounted,
reactive,
getCurrentInstance
} from 'vue';
import {
onShow,
onLoad
} from '@dcloudio/uni-app';
import {
util
} from '@/utils/common.js';
import {
defaultData
} from './data.json';
import ZdySafeBottom from '@/components/safeBottom/safeBottom.vue'
const {
appContext,
proxy
} = getCurrentInstance();
const statusBarHeight = ref(0);
const PAY_STORAGE_KEY = 'alipay_pay_successful_data';
const payData = ref(defaultData);
const bannerImages = [
'/static/image/balance/transfer/bottom-banner-1.png',
'/static/image/balance/transfer/bottom-banner-2.png'
];
// 从缓存加载数据
const loadStorageData = () => {
const savedData = uni.getStorageSync(PAY_STORAGE_KEY);
if (savedData) {
payData.value = savedData;
}
};
loadStorageData();
const navBarRef = ref(null);
const buttonGroup = ref([{
name: '编辑支付数据',
action: 'edit'
}]);
const handleButtonClick = (button) => {
if (button.action === 'edit') {
goEdit()
}
};
const goEdit = () => {
uni.navigateTo({
url: '/pages/balance/pay-successful/edit'
});
}
const toggleBanner = () => {
const currentIndex = bannerImages.indexOf(payData.value.style1.bannerUrl);
const nextIndex = (currentIndex + 1) % bannerImages.length;
payData.value.style1.bannerUrl = bannerImages[nextIndex];
uni.setStorageSync(PAY_STORAGE_KEY, payData.value);
};
onLoad(() => {
// 进入支付成功页面埋点
proxy.$apiUserEvent('all', {
type: 'click',
key: 'pay-successful',
value: "支付成功"
})
})
onMounted(() => {
const systemInfo = uni.getSystemInfoSync();
if (systemInfo.statusBarHeight) {
statusBarHeight.value = systemInfo.statusBarHeight;
}
});
onShow(() => {
loadStorageData();
// #ifdef APP-PLUS&&!APP-HARMONY
util.setAndroidSystemBarColor('#F5F5F5')
uni.setNavigationBarColor({
animation: { // 动画效果
duration: 100,
timingFunc: 'easeIn'
}
})
setTimeout(() => {
plus.navigator.setStatusBarStyle("light");
}, 500);
// #endif
})
const goBack = () => {
uni.navigateBack();
};
</script>
<style lang="less" scoped>
@import "@/common/main.css";
.container {
min-height: 100vh;
background-color: #F5F5F5;
}
.header-section {
height: 860rpx;
background: linear-gradient(181deg, #3884FD 60%, rgba(30, 128, 253, 0.5) 81.75%, rgba(30, 128, 253, 0) 100%);
::v-deep .nav-bar-container {
position: relative !important;
}
::v-deep .placeholder-box {
display: none !important;
background: transparent;
}
::v-deep .tipLayer {
position: absolute;
}
::v-deep .uni-navbar__header-btns {
width: 100px !important;
}
}
.status-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 40rpx;
}
.nav-link {
padding: 0 30rpx;
text-align: right;
font-size: 28rpx;
line-height: 28rpx;
color: #fff;
}
.success-info {
display: flex;
flex-direction: column;
align-items: center;
}
.pay-success-card {
margin: 24rpx;
margin-top: 16rpx;
background-color: #FFFFFF;
border-radius: 16rpx;
margin-bottom: 0;
.title-img {
width: 106rpx;
height: 28rpx;
margin-top: 22rpx;
margin-left: 16rpx;
}
.title {
font-weight: 700;
font-size: 24rpx;
color: #252525;
line-height: 24rpx;
}
.banner-box {
position: relative;
width: 660rpx;
height: 166rpx;
margin-top: 18rpx;
.banner-time {
position: absolute;
z-index: 10;
left: 156rpx;
top: 68rpx;
font-weight: 400;
font-size: 20rpx;
color: #C70B0C;
line-height: 20rpx;
}
}
}
.status-box {
display: flex;
align-items: center;
margin-top: 10rpx;
height: 34rpx;
.image {
width: 34rpx;
height: 34rpx;
margin-right: 12rpx;
}
.status-text {
font-size: 34rpx;
color: #FFFFFF;
font-weight: 500;
line-height: 34rpx;
}
}
.amount-box {
display: flex;
align-items: baseline;
color: #fff;
margin-top: 20rpx;
margin-bottom: 60rpx;
.symbol {
font-size: 68rpx;
margin-right: 4rpx;
}
.amount {
font-size: 92rpx;
line-height: 92rpx;
font-weight: 500;
}
}
.details-box {
width: 100%;
padding: 0 46rpx;
.detail-item {
display: flex;
justify-content: space-between;
margin-bottom: 20rpx;
height: 26rpx;
font-size: 26rpx;
color: rgba(255, 255, 255, 0.9);
text {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.9);
line-height: 26rpx;
}
}
}
.content-section {
padding: 24rpx;
padding-bottom: 16rpx;
// background-color: #F5F5F5;
border-radius: 30rpx 30rpx 0 0;
}
.card {
background-color: #fff;
border-radius: 20rpx;
margin-bottom: 24rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.02);
}
.fliggy-card {
overflow: hidden;
position: relative;
.banner-img {
width: 100%;
}
.guanggao-tag {
position: absolute;
top: 16rpx;
right: 16rpx;
}
.yaoyiyao-container {
width: 100%;
display: flex;
justify-content: center;
margin-top: -80rpx;
padding-bottom: 16rpx;
.yaoyiyao-box {
padding: 12rpx 18rpx;
display: flex;
align-items: center;
background-color: #000000;
border-radius: 28rpx;
opacity: 0.5;
.img {
width: 32rpx;
height: 32rpx;
margin-right: 14rpx;
}
.text {
color: #FFFFFF;
font-size: 24rpx;
line-height: 24rpx;
}
}
}
}
.banner-footer {
padding: 24rpx;
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
.logo-box {
width: 76rpx;
height: 76rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
.logo {
width: 76rpx;
height: 76rpx;
}
}
.banner-info {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
border-top: 1rpx solid #F6F6F6;
padding: 30rpx 0;
.text-content {
display: flex;
flex-direction: column;
}
.b-title {
font-size: 28rpx;
line-height: 28rpx;
color: #1A1A1A;
font-weight: 500;
}
.b-desc {
margin-top: 12rpx;
font-size: 24rpx;
line-height: 24rpx;
color: #969696;
.b-desc-tag {
font-size: 24rpx;
color: #E4711F;
background-color: #FDF6F3;
line-height: 24rpx;
padding: 2px 4px;
}
}
}
.b-btn {
padding: 0 20rpx;
background-color: #FF3A31;
color: #fff;
font-size: 26rpx;
height: 52rpx;
line-height: 52rpx;
border-radius: 8rpx;
}
&::after {
content: '';
position: absolute;
background: url('/static/image/balance/pay-successful/more.png');
top: 16rpx;
right: 20rpx;
width: 24rpx;
height: 12rpx;
background-size: contain;
background-repeat: no-repeat;
}
}
.footer-section {
padding: 0px 40rpx 0;
margin-top: 50rpx;
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 0;
display: flex;
justify-content: center;
padding-bottom: 24rpx;
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
padding-bottom: calc(24rpx + constant(safe-area-inset-bottom));
.finish-btn {
height: 84rpx;
width: 390rpx;
line-height: 84rpx;
text-align: center;
border: 1px solid #2172CA;
border-radius: 42rpx;
color: #2172CA;
font-size: 32rpx;
background-color: transparent;
}
}
.main-content {
position: absolute;
left: 0;
top: 190px;
width: 100%;
}
.promo-card {
// padding: 30rpx;
margin: 16rpx;
margin-bottom: 10rpx;
display: flex;
justify-content: space-between;
align-items: center;
.promo-left-box {
display: flex;
align-items: center;
padding: 34rpx;
background-image: url("/static/image/balance/transfer/card-bg-2-left.png");
background-repeat: no-repeat;
background-size: 100% 100%;
background-position: center;
height: 144rpx;
margin-right: -1px;
.img {
width: 70rpx;
height: 70rpx;
border-radius: 50%;
}
}
.promo-right {
height: 144rpx;
background-image: url("/static/image/balance/transfer/card-bg-2-right.png");
background-repeat: no-repeat;
background-size: 100% 100%;
background-position: center;
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
padding: 32rpx 32rpx 24rpx;
.promo-title {
font-size: 28rpx;
line-height: 28rpx;
font-weight: 500;
color: #1A1A1A;
}
.promo-tags {
display: flex;
align-items: center;
margin-top: 8rpx;
.tag {
font-size: 24rpx;
line-height: 34rpx;
height: 34rpx;
color: #EF673C;
background-color: #FFF2F1;
border-radius: 4rpx;
margin-right: 12rpx;
padding: 0 12rpx;
}
.tag-desc {
font-size: 24rpx;
line-height: 24rpx;
color: #969696;
}
}
}
.promo-right-box {
display: flex;
flex-direction: column;
align-items: center;
.price-box {
color: #F93950;
.p-num {
font-size: 34rpx;
line-height: 24rpx;
font-weight: 500;
}
.p-unit {
font-size: 24rpx;
line-height: 24rpx;
margin-left: 2rpx;
}
}
.free-btn {
height: 52rpx;
line-height: 52rpx;
padding: 0 24rpx;
background-color: #FA3A49;
color: #fff;
font-size: 24rpx;
border-radius: 26rpx;
}
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -27,8 +27,10 @@
<!-- 隐藏的text用于测量宽度 -->
<text class="text-measure">{{ billData.name }}</text>
<input class="text-input" type="text" v-model="billData.name" />
<image class="edit-image" src="/static/image/bill/add-bill/edit.png"></image>
<input class="text-input" type="text" :focus="data.focusState.name" v-model="billData.name"
@blur="data.focusState.name = false" />
<image class="edit-image" src="/static/image/bill/add-bill/edit.png" @click="triggerFocus('name')">
</image>
</view>
<!-- 金额 -->
<view class="money-box flex-align-center">
@ -39,8 +41,10 @@
<text class="text-measure font-w500" style="font-size: 64rpx;">{{ billData.money }}</text>
<input class="text-input font-w500" style="font-size: 64rpx;" type="digit"
v-model="billData.money" />
<image class="edit-image" src="/static/image/bill/add-bill/edit.png"></image>
:focus="data.focusState.money" v-model="billData.money"
@blur="data.focusState.money = false" />
<image class="edit-image" src="/static/image/bill/add-bill/edit.png"
@click="triggerFocus('money')"></image>
</view>
</view>
@ -52,9 +56,13 @@
<!-- 详情信息列表 -->
<view class="detail-info-container">
<template v-for="item in billData.itemInfoList" :key="item.id">
<view class="info-item-box" v-if="item.key != 'paymentReward'">
<view class="item-label">
{{ item.label }}
<view class="info-item-box"
v-if="item.key != 'paymentReward' && (item.key != 'transferNote' || billData.merchantOption.isTransferNote)">
<view class="item-label" :class="{ 'switchable-label': item.key === 'createTime' }"
@click="toggleCreateTimeLabel(item)">
<text>{{ item.label }}</text>
<image v-if="item.key === 'createTime'" class="switch-icon"
src="/static/image/bill/add-bill/edit.png"></image>
</view>
<view v-if="item.type != 'link'" class="info-item-input" @click="onClickItemInfo(item)">
<!-- 隐藏的text用于测量宽度 -->
@ -66,7 +74,8 @@
<input
v-if="(item.type == 'text' || item.type == 'digit' || item.type == 'number') && !(data.type == 1 && item.key == 'payMethod')"
:style="{ color: item.textColor ? item.textColor : '#1a1a1a' }" class="text-input"
:type="item.type" :focus="item.focus" v-model="item.value" @click.stop />
:type="item.type" :focus="item.focus" v-model="item.value" @click.stop
@blur="item.focus = false" />
</view>
<image v-if="item.type != 'link' && !(data.type == 1 && item.key == 'payMethod')"
class="edit-image" src="/static/image/bill/add-bill/edit.png"
@ -97,7 +106,7 @@
<view class="right-input-box flex-align-center">
<!-- <text class="right-text">共{{ item.value.number }}件</text> -->
<input class="right-text" :focus="item.focus" type="text" v-model="item.value.quantity"
@click.stop />
@blur="item.focus = false" @click.stop />
<image class="edit-image" src="/static/image/bill/add-bill/edit.png"
@click="onClickItemInfo(item, 'foucs')">
</image>
@ -111,9 +120,11 @@
<view class="info-item-input">
<!-- 隐藏的text用于测量宽度 -->
<text class="text-measure">{{ billData.balance }}</text>
<input class="text-input" type="digit" v-model="billData.balance" />
<input class="text-input" type="digit" :focus="data.focusState.balance"
v-model="billData.balance" @blur="data.focusState.balance = false" />
</view>
<image class="edit-image" src="/static/image/bill/add-bill/edit.png">
<image class="edit-image" src="/static/image/bill/add-bill/edit.png"
@click="triggerFocus('balance')">
</image>
</view>
</view>
@ -122,7 +133,8 @@
<view class="switch-option-container">
<template v-for="option in data.switchOptions" :key="option.id">
<view v-if="option.showTypeIds ? option.showTypeIds.includes(billData.selectId) : true"
class="border-bottom" :class="{ 'no-border-bottom': isNoBorderBottom(option) }">
class="border-bottom"
:class="{ 'no-border-bottom': isNoBorderBottom(option) || option.isMarginBottom, 'margin-bottom-16rpx': option.isMarginBottom }">
<view class="switch-option">
<view class="switch-option-text">{{ option.name }}</view>
<switch v-if="option.isSwitch" color="#1676FE" :checked="billData.merchantOption[option.key]"
@ -172,6 +184,7 @@
<image class="edit-image" src="/static/image/bill/add-bill/edit.png">
</image>
</view>
</template>
</view>
@ -187,8 +200,12 @@
billData.merchantOption.serviceDetailInfo.text }}</text>
<input class="text-input" maxlength="20" style="font-size: 22rpx;" type="text"
v-model="billData.merchantOption.serviceDetailInfo.text" />
<image class="edit-image" src="/static/image/bill/add-bill/edit.png"></image>
:focus="data.focusState.serviceDetail"
v-model="billData.merchantOption.serviceDetailInfo.text"
@blur="data.focusState.serviceDetail = false" />
<image class="edit-image" src="/static/image/bill/add-bill/edit.png"
@click="triggerFocus('serviceDetail')">
</image>
</view>
</view>
<uni-data-select class="select" style="flex: none;width: 80px;"
@ -207,8 +224,11 @@
billData.merchantOption.recommendServiceInfo.text }}</text>
<input class="text-input" maxlength="20" style="font-size: 22rpx;" type="text"
v-model="billData.merchantOption.recommendServiceInfo.text" />
<image class="edit-image" src="/static/image/bill/add-bill/edit.png"></image>
:focus="data.focusState.recommendService"
v-model="billData.merchantOption.recommendServiceInfo.text"
@blur="data.focusState.recommendService = false" />
<image class="edit-image" src="/static/image/bill/add-bill/edit.png"
@click="triggerFocus('recommendService')"></image>
</view>
</view>
<text class="right-text">去看看</text>
@ -224,12 +244,34 @@
billData.merchantOption.serviceRecommendInfo.text }}</text>
<input class="text-input" maxlength="20" style="font-size: 22rpx;" type="text"
v-model="billData.merchantOption.serviceRecommendInfo.text" />
<image class="edit-image" src="/static/image/bill/add-bill/edit.png"></image>
:focus="data.focusState.serviceRecommend"
v-model="billData.merchantOption.serviceRecommendInfo.text"
@blur="data.focusState.serviceRecommend = false" />
<image class="edit-image" src="/static/image/bill/add-bill/edit.png"
@click="triggerFocus('serviceRecommend')"></image>
</view>
</view>
<image class="right-icon" src="/static/image/common/right-grey.png" />
</view>
<view class="bill-analysis-box"
v-if="option.key == 'isShowBillAnalysis' && billData.merchantOption.isShowBillAnalysis">
<view class="bill-analysis-text-box">
<view class="flex-1 over-hidden">
<view class="service-detail-info info-item-input">
<!-- 隐藏的text用于测量宽度 -->
<text class="text-measure" style="font-size: 22rpx;">{{
billData.merchantOption.billAnalysisInfo.text }}</text>
<input class="text-input" maxlength="20" style="font-size: 22rpx;" type="text"
:focus="data.focusState.billAnalysis"
v-model="billData.merchantOption.billAnalysisInfo.text"
@blur="data.focusState.billAnalysis = false" />
<image class="edit-image" src="/static/image/bill/add-bill/edit.png"
@click="triggerFocus('billAnalysis')"></image>
</view>
</view>
</view>
</view>
</view>
</template>
@ -307,6 +349,41 @@ const timepopup = ref(null)
// 账单管理弹窗
const billManagementPopupRef = ref(null)
// 输入框引用定义
const nameInputRef = ref(null)
const moneyInputRef = ref(null)
const balanceInputRef = ref(null)
const serviceDetailInputRef = ref(null)
const recommendServiceInputRef = ref(null)
const serviceRecommendInputRef = ref(null)
const billAnalysisInputRef = ref(null)
/**
* 通用聚焦函数
* @param {Object} inputRef 目标输入框的 ref
*/
const onFocusShortcut = (inputRef) => {
if (!inputRef) return
nextTick(() => {
// uni-app 中 input 组件聚焦通常需要通过设置 focus 属性,但直接调用 focus() 在部分平台更直接
// 我们主要通过 ref 操作或设置数据层状态实现
if (inputRef.value && typeof inputRef.value.focus === 'function') {
inputRef.value.focus()
}
})
}
/**
* 触发指定输入框聚焦(响应式变量方式)
* @param {String} key focusState 中的键名
*/
const triggerFocus = (key) => {
data.focusState[key] = false
nextTick(() => {
data.focusState[key] = true
})
}
const {
addBill,
getBillList,
@ -333,82 +410,97 @@ const serviceDetailRightTextList = [{
}
]
// switch选项
const switchOptions = [{
"id": 7,
"name": "账单分类",
"isSwitch": false,
key: 'billClassify',
},
{
"id": 8,
"name": "标签",
"isSwitch": false,
key: 'tag',
},
{
"id": 9,
"name": "备注",
"isSwitch": false,
key: 'note',
},
{
"id": 1,
"name": "服务详情",
"isSwitch": true,
showTypeIds: [3, 4, 10],
key: 'serviceDetail',
},
{
"id": 2,
"name": "推荐服务",
"isSwitch": true,
showTypeIds: [4],
key: 'recommendService',
},
{
"id": 3,
"name": "服务推荐",
"isSwitch": true,
showTypeIds: [6, 7],
key: 'serviceRecommend',
},
{
"id": 4,
"name": "退款",
"isSwitch": true,
showTypeIds: [12, 13],
key: 'refund',
change: (value) => {
if (billData.value.selectId == 12 || billData.value.selectId == 13) {
if (value) {
billData.value.orderStatus = "已全额退款"
} else {
billData.value.orderStatus = "交易成功"
const switchOptions = [
{
"id": 11,
"name": "转账备注",
"isSwitch": true,
showTypeIds: [7],
key: 'isTransferNote',
isMarginBottom: true
}, {
"id": 7,
"name": "账单分类",
"isSwitch": false,
key: 'billClassify',
},
{
"id": 8,
"name": "标签",
"isSwitch": false,
key: 'tag',
},
{
"id": 9,
"name": "备注",
"isSwitch": false,
key: 'note',
},
{
"id": 1,
"name": "服务详情",
"isSwitch": true,
showTypeIds: [3, 4, 10],
key: 'serviceDetail',
},
{
"id": 2,
"name": "推荐服务",
"isSwitch": true,
showTypeIds: [4],
key: 'recommendService',
},
{
"id": 3,
"name": "服务推荐",
"isSwitch": true,
showTypeIds: [6, 7],
key: 'serviceRecommend',
},
{
"id": 4,
"name": "退款",
"isSwitch": true,
showTypeIds: [12, 13],
key: 'refund',
change: (value) => {
if (billData.value.selectId == 12 || billData.value.selectId == 13) {
if (value) {
billData.value.orderStatus = "已全额退款"
} else {
billData.value.orderStatus = "交易成功"
}
}
}
},
{
"id": 5,
"name": "支付奖励",
"isSwitch": true,
showTypeIds: [1, 3, 4, 13],
key: 'payReward',
},
{
"id": 6,
"name": "计入收支",
"isSwitch": true,
showTypeIds: [1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13],
key: 'countInAndOut',
},
{
"id": 7,
"name": "商家订单号转换条形码",
"isSwitch": true,
showTypeIds: [1, 2, 3],
key: 'isShowBarcode',
},
{
"id": 10,
"name": "账单分析",
"isSwitch": true,
showTypeIds: [1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13],
key: 'isShowBillAnalysis',
}
},
{
"id": 5,
"name": "支付奖励",
"isSwitch": true,
showTypeIds: [1, 3, 4, 13],
key: 'payReward',
},
{
"id": 6,
"name": "计入收支",
"isSwitch": true,
showTypeIds: [1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13],
key: 'countInAndOut',
},
{
"id": 7,
"name": "商家订单号转换条形码",
"isSwitch": true,
showTypeIds: [1, 2, 3],
key: 'isShowBarcode',
}
]
@ -460,6 +552,7 @@ const data = reactive({
merchantOption: {
serviceDetail: false,
isShowBarcode: false,
isTransferNote: false,
serviceDetailInfo: {
imgUrl: '',
rightText: 0,
@ -481,6 +574,10 @@ const data = reactive({
note: {
isImage: false,
text: ''
},
isShowBillAnalysis: false,
billAnalysisInfo: {
text: '本笔登上月收入榜,看看分析吧'
}
}
},
@ -492,7 +589,17 @@ const data = reactive({
tagInputValue: "",
showTagInput: false,
// switch选项
switchOptions: switchOptions
switchOptions: switchOptions,
// 输入框聚焦状态
focusState: {
name: false,
money: false,
balance: false,
serviceDetail: false,
recommendService: false,
serviceRecommend: false,
billAnalysis: false
}
})
let {
@ -502,7 +609,7 @@ let {
} = toRefs(data)
onShow(() => {
// #ifdef APP-PLUS
// #ifdef APP-PLUS&&!APP-HARMONY
util.setAndroidSystemBarColor('#F5F5F5')
setTimeout(() => {
plus.navigator.setStatusBarStyle("dark");
@ -882,6 +989,15 @@ const onRightClick = async () => {
}, 500)
}
/**
* 切换创建时间/支付时间标签
*/
const toggleCreateTimeLabel = (item) => {
if (item.key === 'createTime') {
item.label = item.label === '创建时间' ? '支付时间' : '创建时间'
}
}
// 点击itemInfo
const onClickItemInfo = async (item, action) => {
console.log(item)
@ -955,7 +1071,7 @@ const uploadImage = () => {
*/
const saveAndDisplayImage = (file) => {
// #ifdef H5
return Promise.resolve(file)
// return Promise.resolve(file)
// #endif
// #ifndef H5
@ -1230,6 +1346,22 @@ page {
font-size: 26rpx;
color: var(--text-secondary);
}
.switchable-label {
display: inline-flex;
align-items: center;
padding: 4rpx 10rpx 4rpx 10rpx;
margin-left: -10rpx;
border-radius: 8rpx;
border: 1px dashed #d9d9d9;
background-color: #fcfcfc;
.switch-icon {
width: 18rpx;
height: 18rpx;
margin-left: 6rpx;
}
}
}
.info-item-input {
@ -1279,11 +1411,11 @@ page {
.switch-option-container {
margin: 16rpx 24rpx;
background-color: #ffffff;
border-radius: 16rpx 16rpx 16rpx 16rpx;
.border-bottom {
border-bottom: 1px solid #efefef
border-bottom: 1px solid #efefef;
background-color: #ffffff;
}
.no-border-bottom {
@ -1468,4 +1600,24 @@ page {
}
.bill-analysis-box {
border-top: 0.5px solid #E4E4E4;
padding: 14rpx 22rpx;
.bill-analysis-text-box {
color: #B7971B;
font-size: 26rpx;
background-color: #FBF8F1;
height: 76rpx;
display: flex;
align-items: center;
border-radius: 16rpx;
padding: 0 24rpx;
}
}
.margin-bottom-16rpx {
margin-bottom: 16rpx;
}
</style>

View File

@ -1,9 +1,9 @@
<template>
<template>
<!-- 水印 -->
<view v-if="$isVip()">
<watermark :dark="data.dark" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark')">
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
<watermark :dark="data.dark" source="uni_alipay_bill_details" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark', 'uni_alipay_bill_details')">
<image src='/static/lottie/watermark.gif' mode="widthFix" style="width:94px;height:74px;" ></image>
</liu-drag-button>
</view>
<view style="overflow: hidden;overflow-y: scroll; height: 100vh;">
@ -29,7 +29,8 @@
<!-- 金额 -->
<view class=" money info-item-input alipay-font" style="height: 77rpx;">
<!-- 隐藏的text用于测量宽度 -->
<text class="text-measure font-w500" style="font-size: 64rpx;">{{ billData.money }}</text>
<text class="text-measure font-w500" style="font-size: 64rpx;">{{
numberUtil.formatMoneyWithThousand(billData.money) }}</text>
</view>
</view>
@ -41,7 +42,9 @@
<!-- 详情信息列表 -->
<view class="detail-info-container">
<template v-for="item in visibleItemInfoList" :key="item.id">
<view class="info-item-box" :class="{ 'bottom-border': item.key == 'relatedRecord' }">
<view class="info-item-box"
v-if="item.key != 'transferNote' || billData.merchantOption.isTransferNote"
:class="{ 'bottom-border': item.key == 'relatedRecord' }">
<view class="item-label">
{{ item.label }}
</view>
@ -171,8 +174,9 @@
<view class="bill-management">
<view class="title">账单管理</view>
<view class="top-box" style="background-color: #FBF8F1;">
<text style="font-size: 24rpx;color: #B7971B;">本笔登上月收入榜,看看分析吧</text>
<view v-if="billData.merchantOption.isShowBillAnalysis" class="top-box" style="background-color: #FBF8F1;">
<text style="font-size: 24rpx;color: #B7971B;">{{
billData.merchantOption.billAnalysisInfo.text || '本笔登上月收入榜,看看分析吧' }}</text>
<uni-icons type="right" size="10" color="#B7971B"></uni-icons>
</view>
<view class="bill-classification bill-management-item">
@ -237,6 +241,8 @@
</view>
</view>
<ZdySafeBottom bgColor="#F5F5F5"/>
</view>
@ -247,13 +253,15 @@
import navBar from '@/components/nav-bar/nav-bar.vue'
import billManagementPopup from '@/components/bill-management-popup/bill-management-popup.vue'
import addBillJson from '@/static/json/add-bill.json'
import ZdySafeBottom from '@/components/safeBottom/safeBottom.vue'
const {
billBottomIconList,
classifyTabBar
} = addBillJson
import {
util,
randomUtil
randomUtil,
numberUtil
} from '@/utils/common.js'
import {
@ -359,7 +367,7 @@ const visibleItemInfoList = computed(() => {
onShow(() => {
getBillData(data.billId)
// #ifdef APP-PLUS
// #ifdef APP-PLUS&&!APP-HARMONY
util.setAndroidSystemBarColor('#F5F5F5')
setTimeout(() => {
plus.navigator.setStatusBarStyle("dark");

View File

@ -1,9 +1,9 @@
<template>
<template>
<!-- 水印 -->
<view v-if="$isVip()">
<watermark :dark="data.dark" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark')">
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
<watermark :dark="data.dark" source="uni_alipay_bill" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark', 'uni_alipay_bill')">
<image src='/static/lottie/watermark.gif' mode="widthFix" style="width:94px;height:74px;" ></image>
</liu-drag-button>
</view>
<view style="overflow: hidden;height: 100vh;;">
@ -49,9 +49,9 @@
<view class="income-ande-outCome flex-between">
<view class="flex">
<view class="item"><text>支出¥</text><text class="money wx-font-regular">{{
Number(currentMonthData.outCome).toFixed(2) }}</text></view>
numberUtil.formatMoneyWithThousand(currentMonthData.outCome) }}</text></view>
<view class="item"><text>收入¥</text><text class="money wx-font-regular">{{
Number(currentMonthData.inCome).toFixed(2) }}</text></view>
numberUtil.formatMoneyWithThousand(currentMonthData.inCome) }}</text></view>
</view>
<view class="">
@ -83,13 +83,13 @@
<view class="outCome item">
<text class="title">支出</text>
<text class="amount alipay-font"><text class="font-11 wx-font-regular">¥</text>{{
Number(item.outCome).toFixed(2)
numberUtil.formatMoneyWithThousand(item.outCome)
}}</text>
</view>
<view class="income item">
<text class="title">收入</text>
<text class="amount alipay-font"><text class="font-11 wx-font-regular">¥</text>{{
Number(item.inCome).toFixed(2)
numberUtil.formatMoneyWithThousand(item.inCome)
}}</text>
</view>
</view>
@ -112,6 +112,7 @@
<view class="bill-list">
<BalanceList :isBalance="false" :list="item.list" @longPress="onLongPress" @onBill="billClick" />
</view>
<ZdySafeBottom bgColor="#F5F5F5"/>
</view>
</scroll-view>
@ -129,10 +130,12 @@
<script setup>
import {
dateUtil,
util
util,
numberUtil
} from '@/utils/common.js'
import navBar from '@/components/nav-bar/nav-bar.vue'
import BalanceList from '@/components/balance-list/balance-list.vue'
import ZdySafeBottom from '@/components/safeBottom/safeBottom.vue'
import {
reactive,
@ -246,7 +249,7 @@ onShow(() => {
// 初始获取状态栏高度和屏幕高度
updateStatusBarHeight()
// #ifdef APP-PLUS
// #ifdef APP-PLUS&&!APP-HARMONY
util.setAndroidSystemBarColor('#F5F5F5')
uni.setNavigationBarColor({
animation: { // 动画效果
@ -283,7 +286,7 @@ const getBillDataList = () => {
const createTimeItem = item.itemInfoList.find(info => info.key == 'createTime')
const createTime = createTimeItem ? createTimeItem.value : new Date()
const date = new Date(createTime)
const date = dateUtil.parseSafe(createTime)
const year = date.getFullYear() + ''
const month = (date.getMonth() + 1) + ''
@ -341,7 +344,7 @@ const getBillDataList = () => {
//每个月内的账单按时间倒序
groupList.forEach(group => {
group.list.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp))
group.list.sort((a, b) => dateUtil.parseSafe(b.timestamp) - dateUtil.parseSafe(a.timestamp))
})
data.billList = groupList
@ -623,6 +626,13 @@ page {
overflow: hidden;
overflow-x: scroll;
&::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
color: transparent;
}
.filter-item {
background-color: #ffffff;
color: var(--text-primary);

112
pages/call-log/call.vue Normal file
View File

@ -0,0 +1,112 @@
<template>
<!-- 水印 -->
<view v-if="$isVip()">
<watermark dark="light" source="uni_alipay_other_call" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark', 'uni_alipay_other_call')">
<image src='/static/lottie/watermark.gif' mode="widthFix" style="width:94px;height:74px;" ></image>
</liu-drag-button>
</view>
<view class="container">
<!-- 自定义头部导航栏 -->
<ZdyNavbar tipLayerText="新增记录" :isTipLayer="true" :type="data.type" :scrollTop="data.scrollTop" @click="open" />
<ZdyHeader :type="data.type" />
<callList :type="data.type" ref="callLogList"></callList>
<tabbar :type="data.type" />
<image v-if="data.type != 'ios'" :src="`/static/image/call/${data.type}BtnImg.png`" mode="" class="btnImg"
:class="['btnImg_' + data.type]" :style="{'margin-bottom':$store.state.safeBottomAz+'px'}"></image>
</view>
</template>
<script setup>
import ZdyNavbar from "@/components/call-log/nav-bar/nav-bar.vue"
import ZdyHeader from "@/components/call-log/header/header.vue"
import callList from "@/components/call-log/list/list.vue"
import tabbar from "@/components/call-log/tabbar/tabbar.vue"
import { util } from "@/utils/common.js"
import { ref, reactive, watch, nextTick, getCurrentInstance } from "vue";
import { onLoad, onShow, onReady, onPageScroll, onReachBottom } from "@dcloudio/uni-app";
const { appContext, proxy } = getCurrentInstance();
const data = reactive({
type: 'vivo',
scrollTop: 0
})
let callLogList = ref();
onLoad((option) => {
data.type = option.type
})
onReady(() => {
})
onShow(() => {
// #ifdef APP-PLUS&&!APP-HARMONY
try {
if (plus.os.name === 'Android') {
let colorTabbar = "#FAFAFA"
if (data.type == 'xiaomi' || data.type == 'oppo') {
colorTabbar = "#FFFFFF"
}
console.log(colorTabbar);
util.setAndroidSystemBarColor(colorTabbar)
setTimeout(() => {
plus.navigator.setStatusBarStyle("dark");
}, 500)
}
} catch (error) {
console.log("修改导航条颜色失败", error);
}
// #endif
})
onPageScroll((e) => {
// console.log(e)
data.scrollTop = e.scrollTop
})
onReachBottom(() => {
})
function open() {
// console.log(callLogList.value)
callLogList.value.openAddModal()
}
</script>
<style lang="scss">
page {
background-color: #fff;
}
.btnImg {
position: fixed;
}
.btnImg_vivo {
width: 80px;
height: 96px;
left: 40px;
bottom: 130px;
}
.btnImg_huawei {
width: 54px;
height: 54px;
left: calc(50% - 27px);
bottom: 65px;
}
.btnImg_oppo {
width: 58px;
height: 58px;
right: 20px;
bottom: 100px;
}
.btnImg_xiaomi {
width: 55px;
height: 55px;
right: 34px;
bottom: 100px;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,176 @@
<template>
<view class="page">
<!-- 顶部折叠头部 -->
<view
class="header"
:style="{ height: headerHeight + 'px' }"
>
<!-- 背景 -->
<view class="header-bg"></view>
<!-- 内容层 -->
<view
class="header-content"
:style="{
transform: `translateY(${contentY}px)`
}"
>
<!-- 头像(单独动画) -->
<image
class="avatar"
src="/static/avatar.png"
:style="{
transform: `scale(${avatarScale}) translateY(${avatarY}px)`
}"
/>
<!-- 文本 -->
<view class="info">
<view class="name">张三</view>
<view class="desc">iOS通讯录详情页</view>
</view>
</view>
</view>
<!-- 内容(重点:不会被遮挡) -->
<view class="content">
<view class="card" v-for="i in 30" :key="i">
详情内容 {{ i }}
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
scrollY: 0,
headerMax: 260,
headerMin: 90,
avatarScale: 1,
avatarY: 0,
contentY: 0
};
},
computed: {
headerHeight() {
const y = this.scrollY;
// 🔥 折叠高度(关键)
return Math.max(this.headerMin, this.headerMax - y);
}
},
onPageScroll(e) {
const y = e.scrollTop;
this.scrollY = y;
// =========================
// 1. 进度(0~1)
// =========================
const max = 160;
let p = Math.min(y / max, 1);
// =========================
// 2. 头像缩小(只影响头像)
// =========================
this.avatarScale = 1 - p * 0.4; // 1 → 0.6
// =========================
// 3. 头像上移(贴近顶部)
// =========================
this.avatarY = -p * 20;
// =========================
// 4. 内容层轻微上移
// =========================
this.contentY = -p * 10;
}
};
</script>
<style lang="scss">
.page {
background: #f5f6f7;
}
/* =====================
header(核心)
===================== */
.header {
position: fixed;
top: 0;
left: 0;
width: 100%;
overflow: hidden;
z-index: 10;
}
/* 背景 */
.header-bg {
position: absolute;
width: 100%;
height: 100%;
background: linear-gradient(180deg, #3b82f6, #1e40af);
}
/* 内容区 */
.header-content {
position: absolute;
bottom: 20px;
left: 30px;
display: flex;
align-items: center;
}
/* 头像 */
.avatar {
width: 140rpx;
height: 140rpx;
border-radius: 50%;
margin-right: 20rpx;
border: 4rpx solid rgba(255,255,255,0.6);
transform-origin: center;
}
/* 文本 */
.info {
color: #fff;
}
.name {
font-size: 40rpx;
font-weight: bold;
}
.desc {
font-size: 24rpx;
opacity: 0.85;
margin-top: 6rpx;
}
/* =====================
内容(关键:防遮挡)
===================== */
.content {
padding-top: 260px; /* ⭐ 解决你说的遮挡问题 */
padding: 20rpx;
}
.card {
background: #fff;
margin-bottom: 20rpx;
padding: 30rpx;
border-radius: 16rpx;
box-shadow: 0 10rpx 30rpx rgba(0,0,0,0.05);
}
</style>

View File

@ -1,9 +1,9 @@
<template>
<template>
<!-- 水印 -->
<view v-if="$isVip()">
<watermark :dark="data.dark" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark')">
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
<image src='/static/lottie/watermark.gif' mode="widthFix" style="width:94px;height:74px;" ></image>
</liu-drag-button>
</view>
<view class="container">
@ -20,7 +20,7 @@
</view>
</view>
</view>
<image ref="bgImage" class="bg-image" src="@/static/image/alipay-bill/main-bg.png" mode="widthFix"></image>
<image ref="bgImage" class="bg-image" :src="billBgImage" mode="widthFix"></image>
<image class="eye-image" src="@/static/image/alipay-bill/hide.png" mode="widthFix"></image>
<image class="find-more" src="@/static/image/alipay-bill/find-more.png" mode=""></image>
@ -32,18 +32,18 @@
<view class="frist-money money-type-item">
<view class="type-input-box">
<!-- <{{ moneyFrist.type }}>支出最多! -->
<<view class="percentage-input-box">
&lt;<view class="percentage-input-box">
<text class="ghost-text">{{ moneyFrist.type }}</text>
<input class="percentage-input" type="text" v-model="moneyFrist.type"
@input="inputPercentage($event, 'moneyFrist')" maxlength="4"></input>
</view>>支出最多!
@input="inputPercentage($event, 'moneyFrist')" maxlength="4" />
</view>&gt;支出最多!
</view>
<view class="money">
占
<view class="percentage-input-box">
<text class="ghost-text">{{ moneyFrist.percentage }}</text>
<input class="percentage-input" type="digit" v-model="moneyFrist.percentage"
@input="inputPercentage($event, 'moneyFrist')"></input>
@input="inputPercentage($event, 'moneyFrist')" />
</view>
<view>%</view>
</view>
@ -54,7 +54,7 @@
<view class="percentage-input-box">
<text class="ghost-text">{{ moneySecond.type }}</text>
<input class="percentage-input" type="text" v-model="moneySecond.type"
@input="inputPercentage($event, 'moneySecond')"></input>
@input="inputPercentage($event, 'moneySecond')" />
</view>
</view>
<view class="money">
@ -62,7 +62,7 @@
<view class="percentage-input-box">
<text class="ghost-text">{{ moneySecond.percentage }}</text>
<input class="percentage-input" type="digit" v-model="moneySecond.percentage"
@input="inputPercentage($event, 'moneySecond')"></input>
@input="inputPercentage($event, 'moneySecond')" />
</view>
<view>%</view>
</view>
@ -73,7 +73,7 @@
<view class="percentage-input-box">
<text class="ghost-text">{{ moneyThird.type }}</text>
<input class="percentage-input" type="text" v-model="moneyThird.type"
@input="inputPercentage($event, 'moneyThird')"></input>
@input="inputPercentage($event, 'moneyThird')" />
</view>
</view>
<view class="money">
@ -81,7 +81,7 @@
<view class="percentage-input-box">
<text class="ghost-text">{{ moneyThird.percentage }}</text>
<input class="percentage-input" type="digit" v-model="moneyThird.percentage"
@input="inputPercentage($event, 'moneyThird')"></input>
@input="inputPercentage($event, 'moneyThird')" />
</view>
<view>%</view>
</view>
@ -92,7 +92,7 @@
<view class="percentage-input-box">
<text class="ghost-text">{{ moneyFourth.type }}</text>
<input class="percentage-input" type="text" v-model="moneyFourth.type"
@input="inputPercentage($event, 'moneyFourth')"></input>
@input="inputPercentage($event, 'moneyFourth')" />
</view>
</view>
<view class="money">
@ -100,7 +100,7 @@
<view class="percentage-input-box">
<text class="ghost-text">{{ moneyFourth.percentage }}</text>
<input class="percentage-input" type="digit" v-model="moneyFourth.percentage"
@input="inputPercentage($event, 'moneyFourth')"></input>
@input="inputPercentage($event, 'moneyFourth')" />
</view>
<view>%</view>
</view>
@ -111,7 +111,7 @@
<view class="percentage-input-box">
<text class="ghost-text">{{ moneyFifth.type }}</text>
<input class="percentage-input" type="text" v-model="moneyFifth.type"
@input="inputPercentage($event, 'moneyFifth')"></input>
@input="inputPercentage($event, 'moneyFifth')" />
</view>
</view>
<view class="money">
@ -119,7 +119,7 @@
<view class="percentage-input-box">
<text class="ghost-text">{{ moneyFifth.percentage }}</text>
<input class="percentage-input" type="digit" v-model="moneyFifth.percentage"
@input="inputPercentage($event, 'moneyFifth')"></input>
@input="inputPercentage($event, 'moneyFifth')" />
</view>
<view>%</view>
</view>
@ -156,7 +156,7 @@
<!-- <view v-if="$isVip()">
<watermark :dark="data.dark" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark')">
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
<image src='/static/lottie/watermark.gif' mode="widthFix" style="width:94px;height:74px;" ></image>
</liu-drag-button>
</view> -->
</template>
@ -169,10 +169,9 @@ import {
getCurrentInstance
} from 'vue';
import {
onLoad,
onShow,
onUnload
onLoad
} from '@dcloudio/uni-app';
import { imgLocal } from '@/utils/common.js';
const statusBarHeight = ref(0);
const dialogPopup = ref(null);
const timeDialogPopup = ref(null);
@ -203,7 +202,7 @@ const data = reactive({
type: '服饰装扮',
percentage: "83"
},
navBarHeight: 0
billBgImage: ''
})
let {
@ -214,11 +213,11 @@ let {
moneyFourth,
moneyFifth,
time,
navBarHeight
billBgImage
} = toRefs(data)
onLoad(() => {
onLoad(async () => {
// 进入花呗页面埋点
proxy.$apiUserEvent('all', {
type: 'click',
@ -228,6 +227,9 @@ onLoad(() => {
time.value = getYesterday();
statusBarHeight.value = uni.getSystemInfoSync().statusBarHeight;
data.billBgImage = await imgLocal.getLocalImage('alipay-bill/main-bg.png');
console.log("data.billBgImage", data.billBgImage)
const config = uni.getStorageSync('config')
console.log("---config---", config);
const font = config.config['client.uniapp.font']

View File

@ -0,0 +1,178 @@
<template>
<view>
<NavBar :title="data.navBar.title" :bgColor="data.navBar.bgColor" />
<view class="list-container">
<view class="item" v-for="item in source" :key="item.id"
:style="{ background: `linear-gradient( -270deg, ${item.color.bgColor} 0%, #FFFFFF 70%), #FFFFFF` }">
<view class="content flex flex-align-center">
<image class="logo" :src="`/static/image/common/phone/${item.icon}.png`" mode=""></image>
<view class="name flex-1">{{ item.name }}机型</view>
<view class="right-button" :style="{ background: item.color.buttonColor }"
@click="goPage(data.type == 'message' ? item.messageUrl : item.callUrl)">立即进入</view>
</view>
<view class="line" :style="{ background: item.color.lineColor }"></view>
</view>
</view>
</view>
</template>
<script setup>
import NavBar from '@/components/nav-bar/nav-bar.vue'
import {
ref,
reactive,
getCurrentInstance
} from 'vue'
import {
onLoad
} from "@dcloudio/uni-app";
import {
util
} from '@/utils/common.js';
const {
appContext,
proxy
} = getCurrentInstance();
// 列表
const source = ref([{
name: '苹果',
color: {
bgColor: '#F3EAFF',
lineColor: '#B78EF5',
buttonColor: '#BA8DFF',
},
icon: 'iphone',
messageUrl: "/pages/message/list-index?phone=iphone",
callUrl: "/pages/call-log/call?type=ios"
},
{
name: '华为',
color: {
bgColor: '#FFE9E9',
lineColor: '#FF6969',
buttonColor: '#FB6767',
},
icon: 'huawei',
messageUrl: "/pages/message/list-index?phone=huawei",
callUrl: "/pages/call-log/call?type=huawei"
},
{
name: '小米',
color: {
bgColor: '#FFF0DD',
lineColor: '#FFA143',
buttonColor: '#FFA64D',
},
icon: 'mi',
messageUrl: "/pages/message/list-index?phone=mi",
callUrl: "/pages/call-log/call?type=xiaomi"
},
{
name: 'oppo',
color: {
bgColor: '#E0FFD9',
lineColor: '#56B745',
buttonColor: '#5DCD49',
},
icon: 'oppo',
messageUrl: "/pages/message/list-index?phone=oppo",
callUrl: "/pages/call-log/call?type=oppo"
}, {
name: 'vivo',
color: {
bgColor: '#D4F4FF',
lineColor: '#52C2FF',
buttonColor: '#50C1FE',
},
icon: 'vivo',
messageUrl: "/pages/message/list-index?phone=vivo",
callUrl: "/pages/call-log/call?type=vivo"
},
])
const data = reactive({
navBar: {
title: '选择机型',
bgColor: '#F0F4F9',
},
type: "message"
})
onLoad((options) => {
if (options.type) {
data.type = options.type
}
proxy.$apiUserEvent('all', {
type: 'event',
key: data.type == 'message' ? 'message' : 'call-log',
prefix: '.uni.other.',
value: data.type == 'message' ? '短信' : "通话"
})
})
function goPage(url) {
if (url) {
util.goPage(url)
} else {
uni.showToast({
title: '开发中',
icon: 'none'
})
}
}
</script>
<style>
@import '@/common/main.css';
</style>
<style lang="less">
.list-container {
background-color: #F0F4F9;
padding: 24rpx 32rpx;
}
.item {
width: 100%;
height: 188rpx;
border-radius: 24rpx;
margin-bottom: 24rpx;
padding: 40rpx 36rpx 24rpx 28rpx;
.content {
.logo {
width: 96rpx;
height: 96rpx;
flex-shrink: 0;
}
.name {
margin-left: 32rpx;
color: #1A1A1A;
font-size: 36rpx;
font-weight: 500;
}
.right-button {
width: 140rpx;
height: 64rpx;
border-radius: 16rpx;
color: #ffffff;
font-size: 28rpx;
line-height: 64rpx !important;
text-align: center;
// display: flex;
// align-items: center;
// justify-content: center;
}
}
.line {
width: 100rpx;
height: 8rpx;
filter: blur(5px);
opacity: 0.5;
margin-top: 4rpx;
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,83 @@
<template>
<view>
<nav-bar title="视频教程" bg-color="transparent">
<template v-slot:statusBar>
<view class="top-bg-box">
<image style="width: 100%;" src="/static/image/common/shipinjiaocheng/top-bg.png" mode="widthFix">
</image>
</view>
</template>
</nav-bar>
<view class="video-list">
<view class="video-item" v-for="item in videoHelpList" :key="item.id" @click="clickVideoHelp(item)">
<image class="video-help-img" :src="item.icon"></image>
<text class="video-help-title flex-1">{{ item.text }}</text>
<uni-icons type="forward" size="18" color="#AEAEAE"></uni-icons>
</view>
</view>
<ZdySafeBottom bgColor="#f0f3f8"/>
</view>
</template>
<script setup>
import { ref } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import { storage } from "../../../utils/storage";
import {
util
} from '@/utils/common.js'
const videoHelpList = ref([]);
import ZdySafeBottom from '@/components/safeBottom/safeBottom.vue'
onLoad(() => {
const configData = storage.get("config")
videoHelpList.value = configData.config['client.uniapp.alipay.video_help'] || []
})
/**
* 点击视频教程
* @param item
*/
const clickVideoHelp = (item) => {
const url = item.url
util.goPage(`/pages/common/webview/webview?url=${encodeURIComponent(url)}&title=${item.text}`)
}
</script>
<style lang="less">
.video-list {
background-color: #FFFFFF;
border-radius: 32rpx;
margin: 0 32rpx;
.video-item {
display: flex;
align-items: center;
padding: 20rpx 32rpx;
border-bottom: 1rpx solid #E4E4E4;
&:last-child {
border-bottom: none;
}
.video-help-title {
width: 100%;
font-size: 32rpx;
color: #1A1A1A;
line-height: 42rpx;
margin-left: 16rpx;
}
}
.video-help-img {
flex-shrink: 0;
width: 72rpx;
height: 72rpx;
}
}
</style>

View File

@ -1,134 +1,151 @@
<!-- pages/webview/webview.vue -->
<template>
<view class="webview-container">
<navBar class="nav-bar" :title="pageTitle" :bgColor="data.navBar.bgColor" isBack></navBar>
<navBar class="nav-bar" :title="pageTitle" :bgColor="data.navBar.bgColor" isBack>
<template v-if="isClose" v-slot:right>
<image style="width:22px;height: 22px;" src='/static/image/close.png' @click="onConfirm()"></image>
</template>
</navBar>
<web-view :src="url" :webview-styles="webviewStyles"></web-view>
</view>
</template>
<script setup>
// 自定义头部
// import ZdyNavbar from "@/components/navbar/navbar.vue"
import navBar from '@/components/nav-bar/nav-bar.vue'
import { util } from '@/utils/common.js'
// 自定义头部
// import ZdyNavbar from "@/components/navbar/navbar.vue"
import navBar from '@/components/nav-bar/nav-bar.vue'
import {
util
} from '@/utils/common.js'
import {
ref,
reactive,
watch,
nextTick,
getCurrentInstance,
onMounted,
onBeforeUnmount,
toRefs,
computed
} from "vue";
import {
onLoad,
onReady,
onShow
} from '@dcloudio/uni-app'
import {
ref,
reactive,
watch,
nextTick,
getCurrentInstance,
onMounted,
onBeforeUnmount,
toRefs,
computed
} from "vue";
import {
onLoad,
onReady,
onShow
} from '@dcloudio/uni-app'
const data = reactive({
navBar: {
title: "",
bgColor: '#ffffff',
},
dark: "dark",
// 状态栏高度
systemBarHeight: "0",
})
const data = reactive({
navBar: {
title: "",
bgColor: '#ffffff',
},
dark: "dark",
// 状态栏高度
systemBarHeight: "0",
isClose: false
})
let {
systemBarHeight
} = toRefs(data)
let {
systemBarHeight,
isClose
} = toRefs(data)
const url = ref('')
const pageTitle = ref('')
const url = ref('')
const pageTitle = ref('')
onLoad((options) => {
uni.getSystemInfo({
success: res => {
systemBarHeight.value = res.statusBarHeight; // 获取状态栏高度
onLoad((options) => {
if (options.isClose) {
isClose.value = options.isClose
}
uni.getSystemInfo({
success: res => {
systemBarHeight.value = res.statusBarHeight; // 获取状态栏高度
}
})
if (options.url) {
url.value = decodeURIComponent(options.url)
// const videoExps = [/\.mp4$/i, /\.m3u8$/i, /\.flv$/i, /\.avi$/i, /\.mov$/i, /\.wmv$/i, /\.webm$/i,
// /\.mkv$/i
// ];
// const isVideo = videoExps.some(exp => exp.test(url.value))
// console.log(isVideo)
// if (isVideo) {
// plus.navigator.setStatusBarStyle("light");
// } else {
// plus.navigator.setStatusBarStyle("dark");
// }
}
console.log("options参数", options);
if (options.title) {
console.log("标题", options.title);
pageTitle.value = decodeURIComponent(options.title)
}
})
if (options.url) {
url.value = decodeURIComponent(options.url)
// const videoExps = [/\.mp4$/i, /\.m3u8$/i, /\.flv$/i, /\.avi$/i, /\.mov$/i, /\.wmv$/i, /\.webm$/i,
// /\.mkv$/i
// ];
// const isVideo = videoExps.some(exp => exp.test(url.value))
// console.log(isVideo)
// if (isVideo) {
// plus.navigator.setStatusBarStyle("light");
// } else {
// plus.navigator.setStatusBarStyle("dark");
// }
onShow(() => {
// #ifdef APP-PLUS&&!APP-HARMONY
util.setAndroidSystemBarColor('#ffffff')
setTimeout(() => {
plus.navigator.setStatusBarStyle("dark");
}, 500);
// #endif
})
const onConfirm = () => {
uni.reLaunch({
url: '/pages/index/index'
})
}
console.log("options参数", options);
if (options.title) {
console.log("标题", options.title);
pageTitle.value = decodeURIComponent(options.title)
const goBack = () => {
uni.navigateBack()
}
})
onShow(() => {
// #ifdef APP-PLUS
util.setAndroidSystemBarColor('#ffffff')
setTimeout(() => {
plus.navigator.setStatusBarStyle("dark");
}, 500);
// #endif
})
const isVideo = computed(() => {
if (!url.value) return false
const videoExps = [/\.mp4$/i, /\.m3u8$/i, /\.flv$/i, /\.avi$/i, /\.mov$/i, /\.wmv$/i, /\.webm$/i,
/\.mkv$/i
];
return videoExps.some(exp => exp.test(url.value));
})
const goBack = () => {
uni.navigateBack()
}
const isVideo = computed(() => {
if (!url.value) return false
const videoExps = [/\.mp4$/i, /\.m3u8$/i, /\.flv$/i, /\.avi$/i, /\.mov$/i, /\.wmv$/i, /\.webm$/i,
/\.mkv$/i
];
return videoExps.some(exp => exp.test(url.value));
})
const webviewStyles = computed(() => {
return {
top: `${Number(systemBarHeight.value) + 40}px`,
bottom: '0px',
'padding-bottom': '20px'
}
})
const webviewStyles = computed(() => {
return {
top: `${Number(systemBarHeight.value) + 40}px`,
bottom: '0px',
'padding-bottom': '20px'
}
})
</script>
<style scoped>
.webview-container {
flex: 1;
display: flex;
flex-direction: column;
}
.webview-container {
flex: 1;
display: flex;
flex-direction: column;
}
.header {
padding: 20rpx;
background: #fff;
border-bottom: 1rpx solid #eee;
}
.header {
padding: 20rpx;
background: #fff;
border-bottom: 1rpx solid #eee;
}
.title {
font-size: 32rpx;
font-weight: bold;
text-align: center;
}
.title {
font-size: 32rpx;
font-weight: bold;
text-align: center;
}
.video-web {
position: fixed;
width: 100%;
background-color: #000;
}
.video-web {
position: fixed;
width: 100%;
background-color: #000;
}
</style>
<style>
page {
background-color: #ffffff;
}
page {
background-color: #ffffff;
}
</style>

View File

@ -1,9 +1,9 @@
<template>
<template>
<!-- 水印 -->
<view v-if="$isVip()">
<watermark :dark="data.dark" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark')">
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
<watermark dark="light" source="uni_alipay_finance" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark', 'uni_alipay_finance')">
<image src='/static/lottie/watermark.gif' mode="widthFix" style="width:94px;height:74px;" ></image>
</liu-drag-button>
</view>
<view class="container flex flex-column" @touchstart="handleTouchStart" @touchmove.stop.prevent="handleTouchMove"
@ -163,7 +163,7 @@
</view>
</view>
<view v-if="!selectedImage" class="content flex-1 flex-column flex" @touchstart="clickHandleTouchStart"
<view v-if="!selectedImage" class="content flex-1 flex-column flex" @touchstart="clickHandleTouchStart" :style="{'padding-bottom':$store.state.safeBottomAz+'px'}"
@touchend="clickHandleTouchEnd">
<view v-if="financeInfo.bgImage" class="w100 h100" style="margin-top: -1px;">
<image class="w100 h100" :src="financeInfo.bgImage" mode="widthFix">
@ -188,13 +188,13 @@
</view>
<canvas canvas-id="crop-canvas"
style="position: fixed; left: -9999px; width: 750rpx; height: 100vh; pointer-events: none;"></canvas>
</view>
<!-- 底部导航栏 -->
<view class="bottom-box"
:class="`navigation-menu-${financeInfo.navigationMenuStyle}`, { 'ios-bottom-box': $system == 'iOS' }">
<view class="bottom-item" v-for="item in navigationMenu" :key="item.name" @click="clickBottomItem(item)">
:class="`navigation-menu-${financeInfo.navigationMenuStyle}` + ($system == 'iOS' ? ' ios-bottom-box' : '')">
<view class="bottom-item" v-for="item in navigationMenu" :key="item.name" @click="clickBottomItem(item)" :style="{'padding-bottom':$store.state.safeBottomAz+'px'}">
<image
:src="`/static/image/finance-management/navigation-menu/${financeInfo.navigationMenuStyle}/${item.icon}.png`">
</image>
@ -244,6 +244,7 @@
<script setup>
import NavBar from '@/components/nav-bar/nav-bar.vue'
import { imgLocal } from '@/utils/common.js';
import {
ref,
toRefs,
@ -263,6 +264,7 @@ import {
navigationMenu,
myAssets
} from '@/static/json/fortune.json'
import ZdySafeBottom from '@/components/safeBottom/safeBottom.vue'
import {
numberUtil,
util
@ -298,7 +300,7 @@ const buttonGroup = [{
}
}]
const defualtData = {
let defualtData = {
benefitsText: '尊享礼遇18000元/年',
cardHolderName: 'XiaoMing',
vipLevel: "v3",
@ -355,13 +357,16 @@ onReady(() => {
}).exec()
})
onShow(() => {
onShow(async () => {
defualtData.bgImage = await imgLocal.getLocalImage('finance-management/bg-style/style-1.png');
console.log(defualtData.bgImage)
financeInfo.value = {
...defualtData,
...uni.getStorageSync('financeInfo')
}
console.log("financeInfo.value", financeInfo.value)
// #ifdef APP-PLUS
// #ifdef APP-PLUS&&!APP-HARMONY
util.setAndroidSystemBarColor(financeInfo.value.navigationMenuStyle == 'style-1' ? '#ffffff' : '#F8F8F8')
setTimeout(() => {
plus.navigator.setStatusBarStyle("light");
@ -580,7 +585,9 @@ const chooseImage = () => {
selectedImage.value = res.tempFilePaths[0]
data.showMask = true
setTimeout(() => {
// #ifdef APP-PLUS&&!APP-HARMONY
plus.navigator.setStatusBarStyle("dark");
// #endif
}, 500)
}
})
@ -659,7 +666,9 @@ const confirmImage = () => {
financeInfo.value.bgImage = saveRes.savedFilePath
selectedImage.value = '' // 隐藏编辑模式
setTimeout(() => {
// #ifdef APP-PLUS&&!APP-HARMONY
plus.navigator.setStatusBarStyle("light");
// #endif
}, 200)
// 保存到缓存
uni.setStorageSync("financeInfo", financeInfo.value)
@ -708,7 +717,9 @@ const clickHandleTouchEnd = () => {
const closeImage = () => {
selectedImage.value = ''
data.showMask = false
// #ifdef APP-PLUS&&!APP-HARMONY
plus.navigator.setStatusBarStyle("light");
// #endif
return false
}
@ -746,7 +757,7 @@ const closeStyleDialog = () => {
const confirmStyleDialog = (type) => {
financeInfo.value.navigationMenuStyle = type
uni.setStorageSync('financeInfo', financeInfo.value)
// #ifdef APP-PLUS
// #ifdef APP-PLUS&&!APP-HARMONY
util.setAndroidSystemBarColor(financeInfo.value.navigationMenuStyle == 'style-1' ? '#ffffff' : '#F8F8F8')
// #endif
stylePopup.value.close()

View File

@ -2,13 +2,12 @@
<view>
<NavBar title="选择理财图片样式" bgColor="#F5F5F5" isRightButton @right-click="handleRightButtonClick"></NavBar>
<view class="conatiner flex flex-justify-between">
<view v-for="index in 6" :key="index" class="cover-item flex flex-column flex-align-center"
<view v-for="(item, index) in bgList" :key="index" class="cover-item flex flex-column flex-align-center"
@click="data.selectedImage = index">
<view class="image-box" :class="{ 'active': index == data.selectedImage }">
<image class="w100 h100" :src="`/static/image/finance-management/covers/cover-${index}.png`"
mode="widthFix"></image>
<image class="w100 h100" :src="item" mode="widthFix"></image>
</view>
<view class="text">样式{{ index }}</view>
<view class="text">样式{{ index + 1 }}</view>
</view>
</view>
</view>
@ -17,6 +16,8 @@
<script setup>
import NavBar from '@/components/nav-bar/nav-bar.vue'
import { ref, reactive, toRefs, onMounted } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { imgLocal } from '@/utils/common.js';
const defualtData = {
benefitsText: '尊享礼遇18000元/年',
@ -82,6 +83,14 @@ const data = reactive({
})
const { bgImage, selectedImage, financeInfo } = toRefs(data)
const bgList = ref([])
onLoad(async () => {
for (let i = 1; i <= 6; i++) {
const imgPath = await imgLocal.getLocalImage(`finance-management/covers/cover-${i}.png`)
bgList.value.push(imgPath)
}
})
onMounted(() => {
financeInfo.value = {
@ -90,11 +99,12 @@ onMounted(() => {
}
})
const handleRightButtonClick = () => {
const handleRightButtonClick = async () => {
const financeInfoData = {
...financeInfo.value
}
financeInfoData.bgImage = `/static/image/finance-management/bg-style/style-${data.selectedImage}.png`
// financeInfoData.bgImage = `finance-management/bg-style/style-${data.selectedImage}.png`
financeInfoData.bgImage = await imgLocal.getLocalImage(`finance-management/bg-style/style-${data.selectedImage + 1}.png`)
uni.setStorageSync('financeInfo', financeInfoData)
uni.navigateBack()
}

View File

@ -1,9 +1,9 @@
<template>
<view class="container" :style="{ height: data.windowHeight + 'px' }">
<image class="index-bg-img" :style="{ width: windowWidth + 'px' }" src="/static/image/index/index-bg.png"
mode="widthFix">
<image v-if="isReady" class="index-bg-img" :style="{ width: windowWidth + 'px' }"
src="/static/image/index/index-bg.png" mode="widthFix">
</image>
<view class="nav-bar-box" :style="{ backgroundColor: data.navBarBgColor }">
<view v-if="isReady" class="nav-bar-box" :style="{ backgroundColor: data.navBarBgColor }">
<view class="status-box" :style="{ height: statusBarHeight + 'px' }"></view>
<view class="nav-box">
<view class="left-box" @click="exit" @tap="exit" @touchstart.stop="exit">
@ -15,14 +15,13 @@
</view>
</view>
<view class="content-box" :style="{ height: windowHeight + 'px' }">
<view v-if="isReady" class="content-box" :style="{ height: windowHeight + 'px' }">
<scroll-view scroll-y="true" class="scroll-view"
:style="{ height: (windowHeight - statusBarHeight - 44) + 'px', marginTop: (statusBarHeight + 44) + 'px' }"
@scroll="handleScroll">
<view>
<!-- iOS专用:透明点击区域覆盖导航栏左上角 -->
<view @click="exit" @tap="exit" :style="{
<view @click="exit" :style="{
position: 'fixed',
top: statusBarHeight + 'px',
left: '0px',
@ -40,7 +39,7 @@
:src="`/static/image/index/${userInfo.vip > 1 ? (userInfo.vip == 3 ? 'lifetime-vip-bg' : 'vip-bg') : 'no-vip-bg'}.png`"
mode=""></image>
<view class="user-info-box" :style="{ width: (windowWidth - 32) + 'px' }">
<image class="user-avatar" :src="userInfo.avater"></image>
<image v-if="userInfo.avater" class="user-avatar" :src="userInfo.avater"></image>
<view class="user-info">
<view class="name-box">
<text class="phone-text">ID:{{ userInfo.user_id }}</text>
@ -77,16 +76,16 @@
</view>
</view>
<view class="group-box">
<!-- <view class="group-box">
<image class="title-img" src="/static/image/index/shipingjiaocheng.png"></image>
<view class="video-help-box">
<view class="video-help-item" :style="{ width: (windowWidth - 50) / 4 + 'px' }"
<view class="video-help-item" :style="{ width: (windowWidth - 32) / 4 + 'px' }"
v-for="item in videoHelpList" :key="item.id" @click="clickVideoHelp(item)">
<image class="video-help-img" :src="item.icon"></image>
<text class="video-help-title">{{ item.text }}</text>
</view>
</view>
</view>
</view> -->
<view class="group-box">
<image class="title-img" src="/static/image/index/monixiaobao.png"></image>
@ -108,31 +107,36 @@
<view class="activity-box">
<image class="alipay-year-bill" :style="{ width: (windowWidth - 32) + 'px' }"
src="/static/image/index/alipay-year-bill.png" mode="widthFix"
@click="util.goPage(`/pages/common/alipay-annual-bill/alipay-annual-bill`)"></image>
src="/static/image/index/shipinjiaocheng-banner.png" mode="widthFix"
@click="util.goPage(`/pages/common/shipinjiaocheng/shipinjiaocheng`)"></image>
</view>
<view class="group-box">
<image class="title-img" src="/static/image/index/qita.png"></image>
<view class="video-help-box">
<view class="video-help-item" :style="{ width: (windowWidth - 50) / 4 + 'px' }"
v-for="item in otherList" :key="item.id" @click="clickMenu(item)">
<image class="video-help-img" :src="item.icon"></image>
<text class="video-help-title">{{ item.name }}</text>
</view>
<template v-for="item in otherList" :key="item.id">
<view class="video-help-item" :style="{ width: (windowWidth - 36) / 4 + 'px' }"
@click="clickMenu(item)" v-if="isShowBankIdCard(item)">
<image class="video-help-img" :src="item.icon"></image>
<text class="video-help-title">{{ item.name }}</text>
</view>
</template>
</view>
</view>
</view>
<!-- <view style="border: 1px solid red; text-align: center;padding: 10px;" @click="exit">退出模拟器</view> -->
<view class="footer-box" :class="{ 'ios-padding-bottom': platform === 'ios' }">
<text class="vision-text">版本:{{ vision }}</text>
<text class="vision-text margin-l-6" v-if="qqgroup.enable">{{ qqgroup.text }}</text>
<text class="vision-text" @click="copyNumber(qqgroup.number)">{{ qqgroup.number }}</text>
</view>
<view v-if="platform != 'ios'" :style="{'height':'40px','background':'#F0F4F9'}">
</view>
<view v-if="platform != 'ios'" class="footer-box-safeBottom" :style="{'height':'40px','background':'#F0F4F9',width:data.windowWidth+'px'}">
</view>
</scroll-view>
</view>
</view>
</template>
@ -158,8 +162,18 @@ import {
onLoad,
onShow,
onHide,
onUnload
onUnload,
onReady
} from '@dcloudio/uni-app';
import ZdySafeBottom from '@/components/safeBottom/safeBottom.vue'
onReady(() => {
setTimeout(() => {
// #ifdef APP-PLUS&&!APP-HARMONY
plus.navigator.closeSplashscreen();
// #endif
}, 500);
});
// 内部埋点方法
const apiUserEvent = async (type, adminData) => {
@ -189,7 +203,7 @@ const goRechargePage = () => {
}
})
uni.navigateTo({
url: '/pages/common/recharge/index'
url: '/pages/common/recharge/index?source=uni_alipay_index'
});
}
@ -218,46 +232,131 @@ const menuList = [{
name: "花呗",
isHot: false,
path: "/pages/ant-credit-pay/index"
}, {
icon: "zhuanzhang",
name: "转账模拟",
isHot: false,
path: "/pages/balance/transfer/transfer"
}, {
icon: "zhifuchenggong",
name: "支付成功",
isHot: false,
path: "/pages/balance/pay-successful/pay-successful"
}, {
icon: "nianduzhangdan",
name: "年度账单",
isHot: false,
path: "/pages/common/alipay-annual-bill/alipay-annual-bill"
},
]
const otherList = [{
icon: "/static/image/index/qita/jipiao.png",
name: "机票",
path: "/pages/other/tickets-app/index"
path: "/pages/other/tickets-app/index?type=airTicket"
},
{
icon: "/static/image/index/qita/huochepiao.png",
name: "火车票",
path: "/pages/other/train-tickets/12306-tickets/12306-tickets"
// path: "/pages/other/train-tickets/12306-tickets/12306-tickets"
path: "/pages/other/tickets-app/index?type=trainTicket"
},
{
icon: "/static/image/index/qita/gongzidan.png",
name: "工资单",
path: "/pages/other/splash/splash"
icon: "/static/image/index/qita/gouwu.png",
name: "购物",
path: "/pages/shopping/index"
},
{
icon: "/static/image/other/bank/bank.png",
name: "银行卡",
path: "/pages/other/bank/index"
},
{
icon: "/static/image/index/qita/message.png",
name: "短信",
path: "/pages/common/call-and-message-entry/call-and-message-entry?type=message"
},
{
icon: "/static/image/index/qita/call.png",
name: "通话",
path: "/pages/common/call-and-message-entry/call-and-message-entry?type=call"
},
{
icon: "/static/image/index/qita/shipinqunliao.png",
name: "视频群聊",
path: "/pages/other/video-group-chat/video-group-chat"
},
{
icon: "/static/image/index/qita/danliao.png",
name: "视频聊天",
path: "/pages/other/video-chat/video-chat"
},
{
icon: "/static/image/index/qita/zhifuchenggong-wx.png",
name: "微信支付",
path: "/pages/other/wx-payment-success/wx-payment-success"
},
{
icon: "/static/image/index/qita/gongzidan.png",
name: "工资条",
path: "/pages/other/splash/splash"
},
{
icon: "/static/image/index/qita/card.png",
name: "身份证",
path: "/pages/other/card/card"
},
{
icon: "/static/image/index/qita/about-iphone.png",
name: "关于本机",
path: "/pages/other/about-this-iphone/about-this-iphone"
},
{
icon: "/static/image/index/qita/game.png",
name: "游戏",
path: "/pages/other/game/index"
},
{
icon: "/static/image/other/certificate/certificate.png",
name: "证书",
path: "/pages/other/certificate/index"
},
{
icon: "/static/image/other/silkBanner/silkBanner.png",
name: "锦旗",
path: "/pages/other/silkBanner/silkBanner"
},
{
icon: "/static/image/index/qita/ranking.png",
name: "从夯倒拉排名",
path: "/pages/other/ranking/ranking"
},
{
icon: "/static/image/other/score/gaokao.png",
name: "高考",
path: "/pages/other/score/index"
},
]
const data = reactive({
isReady: false,
navBarBgColor: 'transparent',
statusBarHeight: 0,
windowWidth: 0,
windowHeight: 0,
windowWidth: 390, // 防0导致初次Weex负数高度白屏崩溃
windowHeight: 844, // 防0导致初次Weex负数高度白屏崩溃
userInfo: {},
videoHelpList: [],
noticeInfo: {},
vision: "",
platform: '', // 添加平台信息,
qqgroup: {}
qqgroup: {},
idcard: false,
bank: false,
payslip: false,
score: false,
})
const {
isReady,
statusBarHeight,
windowWidth,
windowHeight,
@ -289,24 +388,58 @@ onLoad(async () => {
data.vision = uni.getStorageSync('version')
})
const updateSystemSize = () => {
try {
const systemInfo = uni.getSystemInfoSync();
// 防止安卓宿主中首次热更新进入,短暂状态下获取宽高均为0导致的白屏bug
if (systemInfo.windowHeight > 0 && systemInfo.windowWidth > 0) {
data.statusBarHeight = systemInfo.statusBarHeight;
data.windowWidth = systemInfo.windowWidth;
data.windowHeight = systemInfo.windowHeight;
// 针对部分小米/底层ROM的顽疾:
// 强制延迟挂载超重DOM,让其宿主完全完成测绘后再放入节点,避免Weex渲染主线程卡死白屏
setTimeout(() => {
data.isReady = true;
}, 300);
} else {
// 未获取到正常宽高,延迟再次进行重试
setTimeout(updateSystemSize, 100);
}
} catch (error) {
setTimeout(updateSystemSize, 100);
}
}
onShow(() => {
// 启动时获取数据
fetchUserData()
// 每次显示时刷新数据
setUserData()
// 获取系统信息
const systemInfo = uni.getSystemInfoSync();
data.statusBarHeight = systemInfo.statusBarHeight;
data.windowWidth = systemInfo.windowWidth;
data.windowHeight = systemInfo.windowHeight;
// 循环检测更新真实屏幕宽高
updateSystemSize();
// #ifdef APP-PLUS
util.setAndroidSystemBarColor('#F0F4F9')
setTimeout(() => {
plus.navigator.setStatusBarStyle("dark");
}, 500)
// util.setAndroidSystemBarColor('#F0F4F9');
// plus.navigator.setStatusBarStyle("dark");
}, 800);
// #endif
})
function isShowBankIdCard(item) {
if (item.name != '银行卡' && item.name != '身份证' && item.name != '工资条' && item.name != '高考') {
return true
} else if (item.name == '银行卡' && data.bank) {
return true
} else if (item.name == '身份证' && data.idcard) {
return true
} else if (item.name == '工资条' && data.payslip) {
return true
} else if (item.name == '高考' && data.score) {
return true
} else {
return false
}
}
/**
* 获取用户数据(从服务器)
*/
@ -330,11 +463,23 @@ const fetchUserData = async () => {
// 处理用户配置结果
if (configResult.status === 'fulfilled') {
console.log('用户配置获取成功')
console.log('用户配置获取成功', configResult)
} else {
console.error('获取用户配置失败:', configResult.reason)
}
if (configResult.value.config['client.uniapp.bank'] || false) {
data.bank = true
}
if (configResult.value.config['client.uniapp.idcard'] || false) {
data.idcard = true
}
if (configResult.value.config['client.uniapp.score'] || false) {
data.score = true
}
if (configResult.value.config['client.uniapp.payslip'] || false) {
data.payslip = true
}
// 刷新页面数据
setUserData()
@ -387,7 +532,7 @@ const setUserData = () => {
const userInfoData = storage.get("userInfo")
data.userInfo = userInfoData || {
user_id: '加载中...',
avater: '/static/default-avatar.png',
avater: '',
vip: 0,
vip_expire: ''
}
@ -445,15 +590,6 @@ const openVip = () => {
goRechargePage()
}
/**
* 点击视频教程
* @param item
*/
const clickVideoHelp = (item) => {
const url = item.url
util.goPage(`/pages/common/webview/webview?url=${encodeURIComponent(url)}&title=${item.text}`)
}
/**
* 点击公告
*/
@ -461,7 +597,7 @@ const clickNotice = () => {
console.log("点击公告", noticeInfo.value)
if (!noticeInfo.value.url) return
const url = noticeInfo.value.url + `&uni_id=${userInfo.value.user_id}`
util.goPage(`/pages/common/webview/webview?url=${encodeURIComponent(url)}&title=${noticeInfo.value.title}`)
util.goPage(`/pages/common/webview/webview?url=${encodeURIComponent(url)}&title=${noticeInfo.value.title}&isClose=${true}`)
}
/**
@ -499,7 +635,9 @@ const exit = () => {
console.log('宿主App回传的数据:' + ret);
});
} else {
// #ifdef APP-PLUS&&!APP-HARMONY
plus.runtime.quit();
// #endif
}
}
@ -633,6 +771,11 @@ onUnload(() => {
})
</script>
<style>
.footer-box-safeBottom{
position: fixed;
left: 0;
bottom: 0;
}
.container {
background-color: #F0F4F9;
}
@ -830,7 +973,7 @@ onUnload(() => {
}
.group-box {
margin: 32rpx;
margin: 16px;
margin-bottom: 0;
}
@ -843,7 +986,7 @@ onUnload(() => {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
justify-content: flex-start;
background-color: #FFFFFF;
padding: 24rpx 0;
border-radius: 24rpx;
@ -887,7 +1030,7 @@ onUnload(() => {
display: flex;
flex-direction: column;
justify-content: flex-end;
margin-bottom: 32rpx;
margin-bottom: 10rpx;
}
.menu-item {
@ -924,6 +1067,7 @@ onUnload(() => {
.activity-box {
margin: 0 32rpx;
margin-top: 20rpx;
}
.footer-box {
@ -933,6 +1077,7 @@ onUnload(() => {
justify-content: center;
margin-top: 40rpx;
margin-bottom: 10rpx;
padding-bottom: 20px;
}
.vision-text {

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,234 @@
[
{
"id": 12365458895222,
"unRead": true,
"unReadNumber": 2,
"noNotice": true,
"img": "",
"title": "京东快递",
"content": "【京东快递】您的快递已送达博朗郡二期...",
"chatList": [
{
"id": "uuid-001-a1",
"time": "2026-03-15 10:24",
"content": "<p>【京东快递】快递尾号29398已放在博朗郡二期惠美家便利店,快递员电话15320547739。</p>",
"isMe": false,
"simKa": 1
},
{
"id": "uuid-001-a2",
"time": "2026-03-16 08:31",
"content": "<p>【京东快递】您的包裹已签收,感谢使用京东快递,期待再次为您服务。</p>",
"isMe": false,
"simKa": 1
}
],
"time": "2026-03-16 08:31:00"
},
{
"id": 12365458895223,
"unRead": false,
"unReadNumber": 0,
"noNotice": false,
"img": "",
"title": "招商银行",
"content": "【招商银行】账户变动提醒",
"chatList": [
{
"id": "uuid-002-b1",
"time": "2026-03-15 12:00",
"content": "<p>【招商银行】您尾号8888的账户于03月15日11:58收入人民币90000.00元,余额965600.50元。</p>",
"isMe": false,
"simKa": 1
}
],
"time": "2026-03-15 12:00:00"
},
{
"id": 12365458895224,
"unRead": true,
"unReadNumber": 1,
"noNotice": false,
"img": "",
"title": "中国移动",
"content": "【中国移动】流量使用周报",
"chatList": [
{
"id": "uuid-003-c1",
"time": "2026-03-14 09:00",
"content": "<p>【中国移动】截至14日09时,您本月已使用通用流量18.5GB,剩余3.2GB。</p>",
"isMe": false,
"simKa": 1
},
{
"id": "uuid-003-c2",
"time": "2026-03-14 18:30",
"content": "<p>【中国移动】流量包订购成功!您已成功领取5GB周末流量包,立即生效。</p>",
"isMe": false,
"simKa: 1"
}
],
"time": "2026-03-14 18:30:00"
},
{
"id": 12365458895225,
"unRead": false,
"unReadNumber": 0,
"noNotice": false,
"img": "",
"title": "验证码中心",
"content": "【抖音】您的验证码是 882931",
"chatList": [
{
"id": "uuid-004-d1",
"time": "2026-03-16 01:10",
"content": "<p>【抖音】验证码 882931,用于手机绑定。5分钟内有效,请勿泄露给他人。</p>",
"isMe": false,
"simKa": 1
}
],
"time": "2026-03-16 01:10:00"
},
{
"id": 12365458895226,
"unRead": false,
"unReadNumber": 0,
"noNotice": true,
"img": "",
"title": "美团外卖",
"content": "【美团】订单配送通知",
"chatList": [
{
"id": "uuid-005-e1",
"time": "2026-03-15 18:30",
"content": "<p>【美团】商家已接单,骑手正在赶往商家,请耐心等待。</p>",
"isMe": false,
"simKa": 2
},
{
"id": "uuid-005-e2",
"time": "2026-03-15 19:15",
"content": "<p>【美团】骑手已送达!祝您用餐愉快,给个五星好评吧!</p>",
"isMe": false,
"simKa": 2
}
],
"time": "2026-03-15 19:15:00"
},
{
"id": 12365458895227,
"unRead": true,
"unReadNumber": 3,
"noNotice": false,
"img": "",
"title": "建设银行",
"content": "【建设银行】还款提醒",
"chatList": [
{
"id": "uuid-006-f1",
"time": "2026-03-10 10:00",
"content": "<p>【建设银行】您03月信用卡账单已出,应还款额为¥3,200.00。</p>",
"isMe": false,
"simKa": 1
},
{
"id": "uuid-006-f2",
"time": "2026-03-13 14:00",
"content": "<p>【建设银行】温馨提醒:您的账单将于3天后到期,请确保扣款账户余额充足。</p>",
"isMe": false,
"simKa": 1
},
{
"id": "uuid-006-f3",
"time": "2026-03-16 09:00",
"content": "<p>【建设银行】扣款成功。感谢您使用建设银行信用卡。</p>",
"isMe": false,
"simKa": 1
}
],
"time": "2026-03-16 09:00:00"
},
{
"id": 12365458895228,
"unRead": false,
"unReadNumber": 0,
"noNotice": false,
"img": "",
"title": "12306",
"content": "【12306】购票成功通知",
"chatList": [
{
"id": "uuid-007-g1",
"time": "2026-03-11 11:00",
"content": "<p>【12306】订单EG12345678支付成功。北京南-上海虹桥,03月20日14:00开。</p>",
"isMe": false,
"simKa": 2
}
],
"time": "2026-03-11 11:00:00"
},
{
"id": 12365458895229,
"unRead": false,
"unReadNumber": 0,
"noNotice": true,
"img": "",
"title": "腾讯科技",
"content": "【腾讯科技】安全提醒",
"chatList": [
{
"id": "uuid-008-h1",
"time": "2026-03-13 22:30",
"content": "<p>【腾讯科技】您的QQ账号在异地登录,登录地点:广州。如非本人操作请及时改密。</p>",
"isMe": false,
"simKa": 1
}
],
"time": "2026-03-13 22:30:00"
},
{
"id": 12365458895230,
"unRead": true,
"unReadNumber": 1,
"noNotice": false,
"img": "",
"title": "菜鸟驿站",
"content": "【菜鸟驿站】取件通知",
"chatList": [
{
"id": "uuid-009-i1",
"time": "2026-03-16 10:15",
"content": "<p>【菜鸟驿站】您的中通包裹已到达。凭取件码 8-2-4002 领取,地址:博朗郡东门。</p>",
"isMe": false,
"simKa: 1"
}
],
"time": "2026-03-16 10:15:00"
},
{
"id": 12365458895231,
"unRead": false,
"unReadNumber": 0,
"noNotice": false,
"img": "",
"title": "阿里云",
"content": "【阿里云】资源包即将到期",
"chatList": [
{
"id": "uuid-010-j1",
"time": "2026-03-12 09:00",
"content": "<p>【阿里云】尊敬的用户,您的OSS存储包将于7天后到期,请及时续费。</p>",
"isMe": false,
"simKa": 1
},
{
"id": "uuid-010-j2",
"time": "2026-03-15 15:00",
"content": "<p>【阿里云】续费成功。您的OSS存储包有效期已延长至2027年03月。</p>",
"isMe": false,
"simKa": 1
}
],
"time": "2026-03-15 15:00:00"
}
]

View File

@ -0,0 +1,778 @@
<template>
<!-- 水印 -->
<view v-if="$isVip()">
<watermark dark="light" source="uni_alipay_other_message" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark', 'uni_alipay_other_message')">
<image src='/static/lottie/watermark.gif' mode="widthFix" style="width:94px;height:74px;" ></image>
</liu-drag-button>
</view>
<view>
<MessageNavBar :phone="data.phone" :isScroll="data.isScroll" @add="openAddPopup" @setSim="setSim"
:noticeCount="allNoticeCount" @setNoticeCount="setNoticeCount">
<view v-if="data.phone == 'huawei'" class="huawei-notice">
<view class="img-box">
<image class="img" src="/static/image/phone-message/huawei/notice.png" mode="aspectFill"></image>
<view class="dot" v-if="data.noticeCount > 0">{{ data.noticeCount > 99 ? '99+' : data.noticeCount }}
</view>
</view>
<view class="text">通知信息</view>
<image class="right-img" src="/static/image/phone-message/huawei/right.png" mode="aspectFill"></image>
</view>
<MessageList :phone="data.phone" :list="defaultList" @item-click="itemClick" @delete-item="deleteItem"
@edit-item="editItem">
</MessageList>
</MessageNavBar>
<ZdySafeBottom v-if="data.phone!='oppo'&&data.phone!='xiaomi'" bgColor="#fff"/>
<!-- 添加短信弹窗 -->
<view v-if="showAddPopup" class="add-mask" @tap="closeAddPopup">
<view class="add-popup" @tap.stop>
<view class="add-header">{{ editingItem ? '编辑短信' : '新建短信' }}</view>
<view class="add-body">
<view class="add-row">
<text class="add-label">头像URL:</text>
<view style="position: relative;">
<view class="image-box" :class="addForm.imgShape" style="width: 84rpx;height: 84rpx;"
@tap="selectImage">
<image v-if="addForm.img" class="image w100 h100" :src="addForm.img" mode="aspectFill">
</image>
<image v-else class="image w100 h100" src="/static/image/phone-message/add.png"
mode="aspectFill">
</image>
</view>
<view v-if="addForm.img" class="delete-img-icon" @tap.stop="deleteImage">
×
</view>
</view>
<view v-if="data.phone == 'iphone'"
style="flex: 1;display: flex;align-items: center;justify-content: flex-end;">
<uni-data-checkbox style="display: flex;justify-content: flex-end;"
v-model="addForm.imgShape" :localdata="shape"></uni-data-checkbox>
</view>
</view>
<view class="add-row">
<text class="add-label required">联系人</text>
<input class="add-input" v-model="addForm.title" placeholder="请输入名称或号码" />
</view>
<view class="add-row" v-if="data.phone == 'oppo'">
<text class="add-label">地区</text>
<input class="add-input" v-model="addForm.area" placeholder="请输入地区" />
</view>
<view class="add-row between" v-if="data.phone == 'iphone'">
<text class="add-label">取消通知</text>
<switch :checked="addForm.noNotice" @change="addForm.noNotice = !addForm.noNotice" />
</view>
<view class="add-row between">
<text class="add-label">是否未读</text>
<switch :checked="addForm.unRead" @change="addForm.unRead = !addForm.unRead" />
</view>
<view class="add-row"
v-if="addForm.unRead && (data.phone == 'oppo' || data.phone == 'huawei' || data.phone == 'iphone')">
<text class="add-label">未读数量</text>
<input class="add-input" type="number" v-model="addForm.unReadNumber" placeholder="请输入未读数量" />
</view>
<view class="add-row" v-if="addForm.chatList.length == 0 && editingItem">
<text class="add-label">消息时间</text>
<view class="time-picker-group">
<picker mode="date" :value="addForm.date" @change="onAddDateChange">
<view class="time-picker-item">
<text>{{ addForm.date || '选择日期' }}</text>
</view>
</picker>
<picker mode="time" :value="addForm.timeOfDay" @change="onAddTimeChange">
<view class="time-picker-item">
<text>{{ addForm.timeOfDay || '选择时刻' }}</text>
</view>
</picker>
</view>
</view>
</view>
<view class="add-footer">
<view class="add-btn cancel" @tap="closeAddPopup">取消</view>
<view class="add-btn confirm" @tap="confirmAdd">确定</view>
</view>
</view>
</view>
<!-- 设置运营商弹窗 -->
<view v-if="showSimPopup" class="add-mask" @tap="closeSimPopup">
<view class="add-popup" @tap.stop>
<view class="add-header">设置卡1卡2运营商</view>
<view class="add-body">
<view class="add-row">
<text class="add-label">卡1运营商</text>
<input class="add-input" v-model="simForm.sim1" placeholder="请输入卡1运营商名称" />
</view>
<view class="add-row">
<text class="add-label">卡2运营商</text>
<input class="add-input" v-model="simForm.sim2" placeholder="请输入卡2运营商名称" />
</view>
</view>
<view class="add-footer">
<view class="add-btn cancel" @tap="closeSimPopup">取消</view>
<view class="add-btn confirm" @tap="confirmSim">确定</view>
</view>
</view>
</view>
<!-- 设置通知信息未读数弹窗 -->
<view v-if="showNoticePopup" class="add-mask" @tap="closeNoticePopup">
<view class="add-popup" @tap.stop>
<view class="add-header">设置通知信息</view>
<view class="add-body">
<view class="add-row">
<text class="add-label">未读数量</text>
<input class="add-input" type="number" v-model="noticeForm.count" placeholder="请输入通知信息数量" />
</view>
</view>
<view class="add-footer">
<view class="add-btn cancel" @tap="closeNoticePopup">取消</view>
<view class="add-btn confirm" @tap="confirmNotice">确定</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import MessageNavBar from '@/components/message/list/message-nav-bar.vue'
import MessageList from '@/components/message/list/list.vue'
import ZdySafeBottom from '@/components/safeBottom/safeBottom.vue'
import defaultData from './defaultData.json'
import {
ref,
reactive,
computed
} from 'vue'
import {
onLoad,
onShow,
onPageScroll
} from "@dcloudio/uni-app";
import {
stringUtil,
util
} from '@/utils/common.js';
const defaultList = ref(defaultData)
const data = reactive({
navBar: {
title: '信息',
bgColor: '#FFFFFF',
},
phone: 'iphone',
isScroll: false,
noticeCount: 256
})
const shape = [{
text: '圆形',
value: 'circle'
}, {
text: '方形',
value: 'square',
}]
const STORAGE_KEY = 'message_list'
const SIM_STORAGE_KEY = 'sim_info'
const NOTICE_COUNT_KEY = 'huawei_notice_count'
const allNoticeCount = computed(() => {
let count = 0
defaultList.value.forEach(element => {
if (element.unRead) {
count += element.unReadNumber
}
});
return Number(data.noticeCount) + Number(count)
})
onLoad((options) => {
if (options.phone) {
data.phone = options.phone
}
})
onShow(() => {
// #ifdef APP-PLUS&&!APP-HARMONY
if (data.phone == 'oppo') {
util.setAndroidSystemBarColor('#FAFAFA')
} else {
util.setAndroidSystemBarColor('#ffffff')
}
setTimeout(() => {
plus.navigator.setStatusBarStyle("dark");
}, 500)
// #endif
// 优先从缓存加载列表
try {
const cached = uni.getStorageSync(STORAGE_KEY)
if (cached) {
defaultList.value = JSON.parse(cached)
}
// 加载通知数量
const cachedNotice = uni.getStorageSync(NOTICE_COUNT_KEY)
if (cachedNotice !== '') {
data.noticeCount = Number(cachedNotice)
}
} catch (e) { console.error(e) }
})
onPageScroll((e) => {
if (e.scrollTop > 60) {
data.isScroll = true
} else {
data.isScroll = false
}
})
/**
* 将图片保存到本地持久化存储
* @param {string} tempFilePath 临时文件路径
* @returns {Promise<string>} 持久化后的文件路径
*/
const saveImageToLocal = (tempFilePath) => {
return new Promise((resolve) => {
// 如果是已经持久化的路径或是静态资源,直接返回
if (!tempFilePath || tempFilePath.startsWith('_doc') || tempFilePath.startsWith('/static')) {
return resolve(tempFilePath)
}
uni.saveFile({
tempFilePath: tempFilePath,
success: (res) => {
console.log('图片持久化成功:', res.savedFilePath)
resolve(res.savedFilePath)
},
fail: (err) => {
console.error('图片持久化失败:', err)
resolve(tempFilePath) // 失败则回退使用原路径,确保业务不中断
}
})
})
}
/**
* 删除本地持久化文件
* @param {string} filePath 文件路径
*/
const removeLocalFile = (filePath) => {
if (filePath && filePath.startsWith('_doc')) {
uni.removeSavedFile({
filePath: filePath,
success: () => {
console.log('本地文件删除成功:', filePath)
},
fail: (err) => {
console.warn('本地文件删除失败:', filePath, err)
}
})
}
}
/**
* 删除元素
* @param item
*/
const deleteItem = (item) => {
uni.showModal({
title: '提示',
content: '确定要删除吗?',
success: (res) => {
if (res.confirm) {
const idx = defaultList.value.findIndex(i => i.id === item.id)
if (idx > -1) {
// 删除关联的本地图片文件(头像)
if (item.img) {
removeLocalFile(item.img)
}
// 删除该对话中所有的图片文件
if (item.chatList && item.chatList.length > 0) {
item.chatList.forEach(msg => {
if (msg.type === 'image' && msg.imgUrl && msg.imgUrl.includes('_doc/')) {
removeLocalFile(msg.imgUrl)
}
})
}
defaultList.value.splice(idx, 1)
uni.setStorageSync(STORAGE_KEY, JSON.stringify(defaultList.value))
}
}
}
})
}
/**
* 选择图片
*/
const selectImage = () => {
uni.chooseImage({
count: 1,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
addForm.img = res.tempFilePaths[0]
}
})
}
const deleteImage = () => {
addForm.img = ''
}
const itemClick = (item) => {
util.goPage(`/pages/message/chat-page/chat-page?phone=${data.phone}&id=${item.id}`)
}
// ===== 添加短信弹窗 =====
const showAddPopup = ref(false)
const addForm = reactive({
title: '',
img: '',
content: '',
date: '',
timeOfDay: ''
})
// 弹窗模式:null=新增,有值=编辑中的条目
const editingItem = ref(null)
const openAddPopup = () => {
// 重置表单,默认时间为当前
const now = new Date()
const pad = v => String(v).padStart(2, '0')
editingItem.value = null
addForm.title = ''
addForm.img = ''
addForm.content = ''
addForm.area = ''
addForm.unRead = false
addForm.unReadNumber = 1
addForm.noNotice = false
addForm.chatList = []
addForm.imgShape = 'circle'
addForm.date = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`
addForm.timeOfDay = `${pad(now.getHours())}:${pad(now.getMinutes())}`
showAddPopup.value = true
}
const closeAddPopup = () => {
showAddPopup.value = false
}
const onAddDateChange = (e) => {
addForm.date = e.detail.value
}
const onAddTimeChange = (e) => {
addForm.timeOfDay = e.detail.value
}
/**
* 编辑元素:回填表单并打开弹窗
*/
const editItem = (item) => {
editingItem.value = item
const lastMsg = item.chatList && item.chatList.length ? item.chatList[item.chatList.length - 1] : null
const timeStr = lastMsg ? lastMsg.time : (item.time || '')
const parts = timeStr.split(' ')
addForm.title = item.title || ''
addForm.img = item.img || ''
addForm.imgShape = item.imgShape || 'circle'
addForm.content = ''
addForm.area = item.area || ''
addForm.unRead = !!item.unRead
addForm.unReadNumber = item.unReadNumber || 0
addForm.noNotice = item.noNotice || false
addForm.date = parts[0] || ''
addForm.timeOfDay = parts[1] || ''
addForm.chatList = item.chatList || []
showAddPopup.value = true
}
const confirmAdd = async () => {
if (!addForm.title.trim()) {
uni.showToast({ title: '请输入联系人名称', icon: 'none' })
return
}
const time = `${addForm.date} ${addForm.timeOfDay}`.trim()
// 处理图片持久化
let finalImgPath = addForm.img
if (editingItem.value) {
// 编辑模式:如果图片发生了变化,保存新图并删除旧图
if (addForm.img !== editingItem.value.img) {
finalImgPath = await saveImageToLocal(addForm.img)
removeLocalFile(editingItem.value.img)
}
} else {
// 新增模式:直接持久化
finalImgPath = await saveImageToLocal(addForm.img)
}
if (editingItem.value) {
// ===== 编辑模式:更新已有条目 =====
const idx = defaultList.value.findIndex(i => i.id === editingItem.value.id)
if (idx > -1) {
defaultList.value[idx].title = addForm.title.trim()
defaultList.value[idx].img = finalImgPath
defaultList.value[idx].unRead = addForm.unRead
defaultList.value[idx].unReadNumber = addForm.unReadNumber
defaultList.value[idx].noNotice = addForm.noNotice
defaultList.value[idx].imgShape = addForm.imgShape
defaultList.value[idx].area = addForm.area
defaultList.value[idx].time = time
}
} else {
// ===== 新增模式 =====
const newItem = {
id: Date.now(),
unRead: addForm.unRead,
unReadNumber: addForm.unReadNumber,
noNotice: addForm.noNotice,
img: finalImgPath,
title: addForm.title.trim(),
imgShape: addForm.imgShape,
area: addForm.area,
chatList: addForm.content.trim() ? [
{
id: stringUtil.uuid(),
time: time,
content: `<p>${addForm.content.trim()}</p>`,
isMe: false
}
] : [],
time: time
}
defaultList.value.unshift(newItem)
}
// 按最新消息时间降序排列
defaultList.value.sort((a, b) => {
const timeA = new Date(((a.chatList && a.chatList.length ? a.chatList[a.chatList.length - 1].time : null) || a.time || '').replace(/-/g, '/')).getTime()
const timeB = new Date(((b.chatList && b.chatList.length ? b.chatList[b.chatList.length - 1].time : null) || b.time || '').replace(/-/g, '/')).getTime()
return timeB - timeA
})
try {
uni.setStorageSync(STORAGE_KEY, JSON.stringify(defaultList.value))
} catch (e) { console.error(e) }
closeAddPopup()
}
// ===== 设置运营商弹窗 =====
const showSimPopup = ref(false)
const simForm = reactive({
sim1: '',
sim2: ''
})
const setSim = () => {
try {
const cached = uni.getStorageSync(SIM_STORAGE_KEY)
const simInfo = cached ? JSON.parse(cached) : { sim1: '中国电信', sim2: '中国移动' }
simForm.sim1 = simInfo.sim1
simForm.sim2 = simInfo.sim2
} catch (e) {
simForm.sim1 = '中国电信'
simForm.sim2 = '中国移动'
}
showSimPopup.value = true
}
const closeSimPopup = () => {
showSimPopup.value = false
}
const confirmSim = () => {
try {
uni.setStorageSync(SIM_STORAGE_KEY, JSON.stringify({
sim1: simForm.sim1.trim() || '联通',
sim2: simForm.sim2.trim() || '移动'
}))
uni.showToast({ title: '保存成功', icon: 'success' })
} catch (e) { console.error(e) }
closeSimPopup()
}
// ===== 设置通知信息未读数弹窗 =====
const showNoticePopup = ref(false)
const noticeForm = reactive({
count: 256
})
const setNoticeCount = () => {
noticeForm.count = data.noticeCount
showNoticePopup.value = true
}
const closeNoticePopup = () => {
showNoticePopup.value = false
}
const confirmNotice = () => {
data.noticeCount = Number(noticeForm.count) || 0
try {
uni.setStorageSync(NOTICE_COUNT_KEY, data.noticeCount)
uni.showToast({ title: '保存成功', icon: 'success' })
} catch (e) { console.error(e) }
closeNoticePopup()
}
</script>
<style>
@import '@/common/main.css';
page {
background-color: #FFFFFF;
}
</style>
<style lang="less">
::v-deep .uni-navbar__header-btns {
width: 100px !important;
flex: 1;
}
.iphone-style {
.mg-r-30 {
margin-right: 60rpx;
}
.mg-r-5 {
margin-right: 10rpx;
}
.left-icon {
width: 40rpx;
height: 40rpx;
}
.right-icon {
width: 48rpx;
height: 48rpx;
}
.left-text {
font-size: 32rpx;
color: #0278E2;
margin-left: 10rpx;
}
.center-text {
font-size: 32rpx;
color: #1a1a1a;
}
}
.mi-style {
.right-icon {
width: 40rpx;
height: 40rpx;
margin-right: 30rpx;
}
}
/* ===== 添加短信弹窗 ===== */
.add-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 999;
display: flex;
justify-content: center;
align-items: center;
width: 100vw;
height: 100vh;
}
.add-popup {
width: 88%;
background-color: #ffffff;
border-radius: 24rpx;
overflow: hidden;
}
.add-header {
padding: 36rpx 40rpx 20rpx;
font-size: 34rpx;
font-weight: bold;
color: #1A1A1A;
text-align: center;
}
.add-body {
padding: 8rpx 0;
}
.add-row {
display: flex;
align-items: center;
padding: 20rpx 32rpx;
gap: 16rpx;
.image-box {
border-radius: 50%;
overflow: hidden;
}
.circle {
border-radius: 50%;
}
.square {
border-radius: 16rpx;
}
}
.between {
justify-content: space-between;
}
.required {
position: relative;
}
.required::before {
position: absolute;
left: -10px;
content: '*';
top: 0;
color: #EA0000;
}
.add-label {
font-size: 28rpx;
color: #1A1A1A;
width: 150rpx;
flex-shrink: 0;
}
.add-input {
flex: 1;
height: 70rpx;
background-color: #F6F6F6;
border-radius: 14rpx;
padding: 0 20rpx;
font-size: 28rpx;
color: #1A1A1A;
::v-deep .uni-input {
color: #aaaaaa;
}
}
.add-footer {
padding: 32rpx 24rpx;
display: flex;
}
.add-btn {
flex: 1;
height: 90rpx;
line-height: 90rpx;
text-align: center;
font-size: 32rpx;
background-color: #F1F1F1;
margin: 0 16rpx;
border-radius: 12rpx;
}
.add-btn.cancel {
color: #767676;
}
.add-btn.confirm {
color: #fff;
background-color: #1777FF;
}
.time-picker-group {
display: flex;
flex-direction: row;
gap: 16rpx;
flex: 1;
}
.time-picker-item {
flex: 1;
height: 70rpx;
line-height: 70rpx;
background-color: #F8F8F8;
border-radius: 8rpx;
padding: 0 20rpx;
font-size: 28rpx;
color: #333333;
text-align: center;
}
.huawei-notice {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 32rpx;
height: 124rpx;
.img-box {
position: relative;
width: 76rpx;
height: 76rpx;
flex-shrink: 0;
.img {
width: 100%;
height: 100%;
}
.dot {
position: absolute;
top: -12rpx;
right: -8rpx;
height: 28rpx;
line-height: 30rpx;
padding: 0 10rpx;
background-color: #EA0000;
border-radius: 16rpx;
font-size: 20rpx;
color: #fff;
}
}
.text {
flex: 1;
font-size: 30rpx;
color: #1A1A1A;
margin: 0 32rpx;
font-weight: 500;
}
.right-img {
width: 28rpx;
height: 28rpx;
flex-shrink: 0;
}
}
.delete-img-icon {
position: absolute;
top: -12rpx;
right: -12rpx;
width: 32rpx;
height: 32rpx;
background-color: red;
color: #fff;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
font-size: 30rpx;
line-height: 28rpx;
padding-bottom: 2rpx;
z-index: 2;
}
</style>

View File

@ -0,0 +1,425 @@
<template>
<view>
<!-- Custom Nav Bar -->
<view class="custom-nav-bar" :class="{ 'android-nav': isAndroid }">
<view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
<view class="nav-content">
<view class="left-icon" @click="back">
<image class="back-icon" src="/static/image/common/iphone-back.png" />
</view>
<view class="title">关于本机</view>
<view class="right-placeholder"></view>
</view>
</view>
<!-- Placeholder to push content down -->
<view class="nav-placeholder" :style="{ height: statusBarHeight + 74 + 'px' }"></view>
<view class="info-container">
<block v-for="(group, groupIndex) in listData" :key="groupIndex">
<!-- Group Header (Optional) -->
<view v-if="group.groupTitle" class="group-header">
<text class="header-text">{{ group.groupTitle }}</text>
</view>
<!-- Group Items -->
<view class="info-group">
<view class="info-item" v-for="(item, itemIndex) in group.items" :key="item.id"
:class="{ 'no-border': itemIndex === group.items.length - 1 }"
@click="handleEdit(groupIndex, itemIndex)">
<text class="item-label">{{ item.label }}</text>
<view class="item-value-box">
<text class="item-value" v-if="item.value !== undefined">{{ item.value }}</text>
<!-- <view class="chevron"></view> -->
<image class="chevron" v-if="item.hasArrow"
src="/static/image/common/about-iphone-right.png">
</image>
</view>
</view>
</view>
</block>
</view>
<!-- Custom Modal -->
<view class="custom-modal-mask" v-if="isModalVisible" @click="closeModal">
<view class="custom-modal" @click.stop>
<view class="modal-header">
<text class="modal-title">{{ modalTitle }}</text>
</view>
<view class="modal-body">
<input class="modal-input" v-model="modalInputValue" :placeholder="modalPlaceholder"
cursor-spacing="20" />
</view>
<view class="modal-footer">
<view class="modal-btn cancel-btn" @click="closeModal">取消</view>
<view class="modal-btn confirm-btn" @click="confirmModal">确定</view>
</view>
</view>
</view>
<ZdySafeBottom bgColor="#f0f3f8"/>
</view>
<!-- 水印 -->
<view v-if="$isVip()">
<watermark dark="light" source="uni_alipay_shopping_about_iphone" />
<liu-drag-button :canDocking="false"
@clickBtn="$goRechargePage('watermark', 'uni_alipay_shopping_about_iphone')">
<image src='/static/lottie/watermark.gif' mode="widthFix" style="width:94px;height:74px;" ></image>
</liu-drag-button>
</view>
</template>
<script setup>
import { ref, onMounted, getCurrentInstance } from 'vue';
import { util } from '@/utils/common.js'
import { onShow, onLoad, onPageScroll } from '@dcloudio/uni-app'
const { proxy } = getCurrentInstance();
import ZdySafeBottom from '@/components/safeBottom/safeBottom.vue'
const CACHE_KEY = 'about_iphone_data';
// Removed scroll logic, styling is now fully CSS-driven
const defaultData = [
{
groupTitle: '',
items: [
{ id: 'name', label: '名称', value: '胡英俊的iPhone', hasArrow: true },
{ id: 'iosVersion', label: 'iOS版本', value: '26.4.2', hasArrow: true },
{ id: 'modelName', label: '型号名称', value: 'iPhone 17 Pro Max', hasArrow: false },
{ id: 'modelNumber', label: '型号', value: 'MG8W4CH/A', hasArrow: false },
{ id: 'serialNumber', label: '序列号', value: 'J5YQ2R5F8R', hasArrow: false }
]
},
{
groupTitle: '',
items: [
{ id: 'warranty', label: '有限保修', value: '到期日期:2026/12/13', hasArrow: true }
]
},
{
groupTitle: '',
items: [
{ id: 'songs', label: '歌曲', value: '0', hasArrow: false },
{ id: 'videos', label: '视频', value: '456', hasArrow: false },
{ id: 'photos', label: '照片', value: '6,568', hasArrow: false },
{ id: 'apps', label: '应用程序', value: '64', hasArrow: false },
{ id: 'capacity', label: '总容量', value: '2 TB', hasArrow: false },
{ id: 'available', label: '可用容量', value: '1.56 TB', hasArrow: false }
]
},
{
groupTitle: '',
items: [
{ id: 'wifi', label: '无线局域网地址', value: '87:5F:4D:48:DF:36', hasArrow: false },
{ id: 'bluetooth', label: '蓝牙', value: '87:5F:4D:2B:DF:68', hasArrow: false },
{ id: 'firmware', label: '调制解调器固件', value: '1.55.25', hasArrow: false },
{ id: 'seid', label: 'SEID', value: '', hasArrow: true },
{ id: 'carrierLock', label: '运营商锁', value: '无SIM卡限制', hasArrow: false }
]
},
{
groupTitle: '主号',
items: [
{ id: 'primaryNetwork', label: '网络', value: '中国移动全球通', hasArrow: false },
{ id: 'primaryCarrier', label: '运营商', value: '中国移动 69.0', hasArrow: false },
{ id: 'primaryIMEI', label: 'IMEI', value: '35 756481 6466451 3', hasArrow: false },
{ id: 'primaryICCID', label: 'ICCID', value: '45632144856512347896', hasArrow: false }
]
},
{
groupTitle: '个人',
items: [
{ id: 'personalNetwork', label: '网络', value: '中国电信', hasArrow: false },
{ id: 'personalCarrier', label: '运营商', value: '中国电信 69.0', hasArrow: false },
{ id: 'personalIMEI', label: 'IMEI', value: '35 756481 6466451 3', hasArrow: false },
{ id: 'personalICCID', label: 'ICCID', value: '45632144856512347896', hasArrow: false }
]
},
{
groupTitle: '',
items: [
{ id: 'trustSettings', label: '证书信任设置', value: '', hasArrow: true }
]
}
];
const listData = ref([]);
const statusBarHeight = ref(0);
const isAndroid = ref(false);
onShow(() => {
// #ifdef APP-PLUS&&!APP-HARMONY
util.setAndroidSystemBarColor('#F2F1F6')
setTimeout(() => {
plus.navigator.setStatusBarStyle("dark");
}, 500);
// #endif
})
onLoad(() => {
proxy.$apiUserEvent('all', {
type: 'event',
key: 'ios-about-phone',
prefix: '.uni.other.',
value: '设置-关于本机'
})
})
onMounted(() => {
const systemInfo = uni.getSystemInfoSync();
statusBarHeight.value = systemInfo.statusBarHeight || 0;
if (systemInfo.osName === 'android' || systemInfo.platform === 'android') {
isAndroid.value = true;
}
const cached = uni.getStorageSync(CACHE_KEY);
if (cached) {
try {
listData.value = JSON.parse(cached);
} catch (e) {
listData.value = JSON.parse(JSON.stringify(defaultData));
}
} else {
listData.value = JSON.parse(JSON.stringify(defaultData));
}
});
const back = () => {
uni.navigateBack();
};
const isModalVisible = ref(false);
const modalTitle = ref('');
const modalPlaceholder = ref('');
const modalInputValue = ref('');
let currentEditGroupIndex = -1;
let currentEditItemIndex = -1;
const handleEdit = (groupIndex, itemIndex) => {
const item = listData.value[groupIndex].items[itemIndex];
currentEditGroupIndex = groupIndex;
currentEditItemIndex = itemIndex;
modalTitle.value = `修改${item.label}`;
modalPlaceholder.value = `请输入新的${item.label}`;
modalInputValue.value = item.value || '';
isModalVisible.value = true;
};
const closeModal = () => {
isModalVisible.value = false;
};
const confirmModal = () => {
if (currentEditGroupIndex !== -1 && currentEditItemIndex !== -1) {
listData.value[currentEditGroupIndex].items[currentEditItemIndex].value = modalInputValue.value;
uni.setStorageSync(CACHE_KEY, JSON.stringify(listData.value));
}
isModalVisible.value = false;
};
</script>
<style lang="less" scoped>
.custom-nav-bar {
opacity: .97;
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 999;
background: linear-gradient(180deg, rgba(242, 242, 246, 0.5) 0%, rgba(242, 242, 246, 0) 100%);
backdrop-filter: saturate(220%) blur(14px);
-webkit-backdrop-filter: saturate(220%) blur(14px);
-webkit-mask-image: linear-gradient(180deg, rgba(242, 242, 246, 0.5) 0%, rgba(242, 242, 246, 0) 100%);
mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 1) calc(100% - 50rpx), rgba(0, 0, 0, 0) 100%);
.nav-content {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10rpx 26rpx 50rpx;
}
.left-icon,
.right-placeholder {
width: 86rpx;
display: flex;
align-items: center;
}
.title {
flex: 1;
text-align: center;
font-size: 32rpx;
font-weight: 500;
color: #1A1A1A;
}
}
/* 安卓手机降级:取消复杂的渐变、滤镜与遮罩,直接使用纯色 */
.custom-nav-bar.android-nav {
opacity: 1 !important;
background: #F2F1F6 !important;
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
mask-image: none !important;
-webkit-mask-image: none !important;
.nav-content {
padding-bottom: 10rpx !important;
}
}
.nav-placeholder {
width: 100%;
padding-top: 146rpx;
}
/* .blur-nav removed */
.back-icon {
box-shadow: 0rpx 0rpx 20rpx 0rpx #E1E1E6;
border: 2rpx solid #FFFFFF;
border-radius: 50%;
width: 86rpx;
height: 86rpx;
}
.info-container {
padding: 30rpx;
padding-top: 0;
padding-bottom: 60rpx;
}
.info-group {
background-color: #FFFFFF;
border-radius: 48rpx;
margin-bottom: 64rpx;
}
.info-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-left: 30rpx;
margin-right: 30rpx;
border-bottom: 0.5px solid #E4E4E4;
line-height: 32rpx;
height: 100rpx;
}
.info-item.no-border {
border-bottom: none;
}
.item-label {
font-size: 32rpx;
color: #1A1A1A;
}
.item-value-box {
display: flex;
align-items: center;
}
.item-value {
font-size: 32rpx;
color: #8A8A8A;
font-weight: 400;
}
.chevron {
width: 24rpx;
height: 24rpx;
margin-left: 12rpx;
}
.group-header {
padding: 0 0 10rpx 30rpx;
}
.header-text {
font-size: 32rpx;
line-height: 32rpx;
margin-bottom: 16rpx;
color: #85858A;
font-weight: 500;
}
.custom-modal-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.4);
z-index: 999;
display: flex;
justify-content: center;
align-items: center;
}
.custom-modal {
width: 600rpx;
background-color: #FFFFFF;
border-radius: 48rpx;
display: flex;
flex-direction: column;
overflow: hidden;
}
.modal-header {
padding: 40rpx 30rpx 30rpx;
text-align: center;
}
.modal-title {
font-size: 34rpx;
color: #1A1A1A;
font-weight: 500;
}
.modal-body {
padding: 0 40rpx 40rpx;
}
.modal-input {
background-color: #F2F1F6;
height: 80rpx;
border-radius: 20rpx;
padding: 0 20rpx;
font-size: 32rpx;
color: #1A1A1A;
}
.modal-footer {
display: flex;
border-top: 0.5px solid #E4E4E4;
height: 100rpx;
}
.modal-btn {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
font-size: 32rpx;
}
.cancel-btn {
color: #8A8A8A;
border-right: 0.5px solid #E4E4E4;
}
.confirm-btn {
color: #007AFF;
font-weight: 500;
}
</style>
<style>
page {
background-color: #F2F1F6;
}
</style>

View File

@ -1,9 +1,10 @@
<template>
<!-- 水印 -->
<view v-if="$isVip()">
<watermark dark="light" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark')">
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
<watermark dark="light" source="uni_alipay_other_airTickets_ctrip" />
<liu-drag-button :canDocking="false"
@clickBtn="$goRechargePage('watermark', 'uni_alipay_other_airTickets_ctrip')">
<image src='/static/lottie/watermark.gif' mode="widthFix" style="width:94px;height:74px;" ></image>
</liu-drag-button>
</view>
<view class="codefun-flex-col page" :style="{ '--font-weight-bold': $system == 'iOS' ? 500 : 700 }">
@ -270,7 +271,7 @@
<text class="codefun-shrink-0 codefun-self-start font_13 text_47">出行人</text>
<view class="codefun-flex-col codefun-flex-1 codefun-relative group_16 ml-41">
<text class="codefun-self-start text_23" @click="goEdit"><text
style="margin-right: 6px;" v-for="value in airTicketsInfo.passengersInfo">{{
style="margin-right: 6px;" v-for="(value, index) in airTicketsInfo.passengersInfo" :key="index">{{
value.name }}</text></text>
<view class="codefun-flex-row codefun-items-center codefun-self-stretch codefun-mt-8">
<text class="font_14 text_49">查看证件、航司预订号、修改姓名/证件</text>
@ -308,8 +309,8 @@
<image class="image_19 pos_15" src="/static/image/other/air-tickets/ctrip-air/connect-icon.png" />
</view>
</view>
<view class="codefun-flex-col codefun-mt-8">
<view class="codefun-flex-col section_12">
<view class="codefun-flex-col codefun-mt-8" :style="{'padding-bottom':$store.state.safeBottomAz+'px'}">
<view class="codefun-flex-col section_12" >
<text class="codefun-self-start font_6 text_52">出行服务</text>
<view class="codefun-self-stretch grid_2 mt-19">
<view class="codefun-flex-col codefun-items-center grid-item_4">
@ -343,7 +344,7 @@
<text class="font_8 text_54">更多服务</text>
<image class="image_11 codefun-ml-6" src="/static/image/other/air-tickets/ctrip-air/more.png" />
</view>
<view class="codefun-flex-col codefun-justify-start section_14">
<view class="codefun-flex-col codefun-justify-start section_14" :style="{'padding-bottom':$store.state.safeBottomAz+'px'}">
<view class="codefun-flex-row equal-division_3">
<view
class="codefun-flex-col codefun-justify-start codefun-items-center text-wrapper_5 text-wrapper_1">
@ -366,12 +367,14 @@
<script setup>
import NavBar from '@/components/nav-bar/nav-bar.vue';
import { reactive, ref, computed, toRefs } from 'vue';
import { reactive, computed, toRefs } from 'vue';
import { onShow, onPageScroll } from '@dcloudio/uni-app';
import { util } from '@/utils/common.js';
import defualtData from '@/pages/other/air-tickets/commom/defualt.json';
import airlineJson from '@/static/json/air-line.json';
import ZdySafeBottom from '@/components/safeBottom/safeBottom.vue'
const buttonGroup = [{
name: "编辑机票信息",
click: () => {
@ -425,7 +428,7 @@ onShow(() => {
if (stored) {
Object.assign(data.airTicketsInfo, stored);
}
// #ifdef APP-PLUS
// #ifdef APP-PLUS&&!APP-HARMONY
util.setAndroidSystemBarColor('#ffffff')
setTimeout(() => {
plus.navigator.setStatusBarStyle("light");

View File

@ -86,10 +86,10 @@
<text class="label">到达机场</text>
<input class="input" v-model="ticketsInfo.flightInfo.endAirport" />
</view>
<!-- <view class="form-item">
<view class="form-item">
<text class="label">时长</text>
<input class="input" v-model="ticketsInfo.flightInfo.duration" placeholder="例: 2时5分" />
</view> -->
</view>
<view class="form-item">
<text class="label">机型</text>
<input class="input" v-model="ticketsInfo.flightInfo.aircraftType" />

View File

@ -1,9 +1,10 @@
<template>
<!-- 水印 -->
<view v-if="$isVip()">
<watermark dark="light" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark')">
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
<watermark dark="light" source="uni_alipay_other_airTickets_fliggy" />
<liu-drag-button :canDocking="false"
@clickBtn="$goRechargePage('watermark', 'uni_alipay_other_airTickets_fliggy')">
<image src='/static/lottie/watermark.gif' mode="widthFix" style="width:94px;height:74px;" ></image>
</liu-drag-button>
</view>
<view class="page-container">
@ -204,8 +205,8 @@
<text class="label" :class="{ 'empty': index !== 0 }">乘机人</text>
<view class="info" @click="goEdit">
<text class="name">{{ item.name }}</text>
<text class="id-card">{{ item.idType }}:{{ item.idType === '身份证' ?
stringUtil.maskIdCard(item.idNumber) : (item.idType === '护照' ?
<text class="id-card">{{ item.idType }}:{{ item.idType.includes('身份证') ?
showFristAndLastNumber(item.idNumber) : (item.idType.includes('护照') ?
stringUtil.maskPassport(item.idNumber) : item.idNumber) }}</text>
<view class="ticket-row">
<text class="ticket-no">票号:{{ item.ticketNo }}</text>
@ -278,10 +279,13 @@
<!-- Note: User image shows floating bottom bar but cut off. Assuming standard action buttons -->
</view>
<view class="safe-area-inset-bottom"></view> <!-- Spacer -->
<view class="" :style="{'height':$store.state.safeBottomAz+'px'}">
</view>
</scroll-view>
<view class="footer-bar">
<view class="flex flex-justify-between">
<view class="flex flex-justify-between" :style="{'padding-bottom':$store.state.safeBottomAz+'px'}">
<view class="flex-1 flex flex-align-center flex-justify-center">
<image class="footer-icon" src="/static/image/other/air-tickets/fliggy-air/zhibo.png" />
<text class="text">联系飞猪</text>
@ -298,11 +302,11 @@
<script setup>
import NavBar from '@/components/nav-bar/nav-bar.vue';
import { ref, onMounted, reactive, toRefs, computed } from 'vue';
import { onMounted, reactive, toRefs, computed } from 'vue';
import { onShow } from '@dcloudio/uni-app';
import defualtData from '@/pages/other/air-tickets/commom/defualt.json';
import airlineJson from '@/static/json/air-line.json';
import { util, numberUtil, stringUtil } from '@/utils/common.js';
import { util, stringUtil } from '@/utils/common.js';
const buttonGroup = [{
name: "编辑机票信息",
@ -371,6 +375,17 @@ const deepMerge = (target, source) => {
return target;
};
const showFristAndLastNumber = (str) => {
if (!str) {
return '';
}
const len = str.length;
if (len <= 2) {
return str;
}
return str.slice(0, 1) + '*'.repeat(len - 2) + str.slice(-1);
}
onShow(() => {
const stored = uni.getStorageSync(data.STORAGE_KEY) || "";
if (stored) {
@ -380,7 +395,7 @@ onShow(() => {
const mergedData = deepMerge(baseData, stored);
Object.assign(data.airTickrtInfo, mergedData);
}
// #ifdef APP-PLUS
// #ifdef APP-PLUS&&!APP-HARMONY
util.setAndroidSystemBarColor('#ffffff')
setTimeout(() => {
plus.navigator.setStatusBarStyle("dark");

View File

@ -1,9 +1,10 @@
<template>
<!-- 水印 -->
<view v-if="$isVip()">
<watermark dark="light" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark')">
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
<watermark dark="light" source="uni_alipay_other_airTickets_qunar" />
<liu-drag-button :canDocking="false"
@clickBtn="$goRechargePage('watermark', 'uni_alipay_other_airTickets_qunar')">
<image src='/static/lottie/watermark.gif' mode="widthFix" style="width:94px;height:74px;" ></image>
</liu-drag-button>
</view>
<view class="page-container">
@ -198,8 +199,8 @@
<view class="row-content"
@click="util.goPage('/pages/other/air-tickets/edit/edit')">
<view class="p-name">{{ p.name }}</view>
<view class="p-sub">{{ p.idType }}: {{ p.idType === '身份证' ?
stringUtil.maskIdCard(p.idNumber) : (p.idType === '护照' ?
<view class="p-sub">{{ p.idType }}: {{ p.idType.includes('身份证') ?
stringUtil.maskIdCard(p.idNumber) : (p.idType.includes('护照') ?
stringUtil.maskPassport(p.idNumber) : p.idNumber) }}</view>
<view class="p-sub">
<text>票号:{{ p.ticketNo }}</text>
@ -333,16 +334,19 @@
</view>
<!-- Branding -->
<view class="branding-area">
<view class="branding-area" >
<image class="image" src="/static/image/other/air-tickets/qunar/branding-area-image.png"
mode="widthFix">
</image>
</view>
<view class="" :style="{'height':$store.state.safeBottomAz+'px'}">
</view>
</view>
</view>
<!-- Bottom Fixed -->
<view class="bottom-bar">
<view class="bottom-bar" :style="{'padding-bottom':$store.state.safeBottomAz+'px'}">
<button class="invoice-btn">开具发票</button>
</view>
</view>
@ -375,7 +379,7 @@ const data = reactive({
ticketData: JSON.parse(JSON.stringify(defualtData))
})
let { navBar, ticketData } = toRefs(data);
let { ticketData } = toRefs(data);
// 计算截止办理值机时间
const ofCheckInTime = computed(() => {
@ -403,7 +407,7 @@ onShow(() => {
Object.assign(data.ticketData, stored);
}
// #ifdef APP-PLUS
// #ifdef APP-PLUS&&!APP-HARMONY
util.setAndroidSystemBarColor('#ffffff')
setTimeout(() => {
plus.navigator.setStatusBarStyle("light");

1184
pages/other/bank/gsyh.vue Normal file

File diff suppressed because it is too large Load Diff

171
pages/other/bank/index.vue Normal file
View File

@ -0,0 +1,171 @@
<template>
<view>
<NavBar :title="data.navBar.title" :bgColor="data.navBar.bgColor" />
<view class="list-container">
<view class="item" v-for="item in source" :key="item.id" @click="goPage(item.callUrl,item.name)"
:style="{ background: ` #FFFFFF` }">
<view class="content flex flex-align-center">
<image class="logo" :src="`/static/image/other/bank/${item.icon}.png`" mode=""></image>
<view class="name flex-1">{{ item.name }}银行</view>
<view class="right-button">
<uni-icons type="right" size="16"></uni-icons>
</view>
</view>
<!-- <view class="line" :style="{ background: item.color.lineColor }"></view> -->
</view>
</view>
</view>
</template>
<script setup>
import NavBar from '@/components/nav-bar/nav-bar.vue'
import {
ref,
reactive,
getCurrentInstance
} from 'vue'
import {
onLoad
} from "@dcloudio/uni-app";
import {
util
} from '@/utils/common.js';
const {
appContext,
proxy
} = getCurrentInstance();
// 列表
const source = ref([{
name: '招商',
color: {
bgColor: '#F3EAFF',
lineColor: '#B78EF5',
buttonColor: '#BA8DFF',
},
icon: 'zsyh',
callUrl: "/pages/other/bank/zsyh"
},
{
name: '工商',
color: {
bgColor: '#FFE9E9',
lineColor: '#FF6969',
buttonColor: '#FB6767',
},
icon: 'gsyh',
callUrl: "/pages/other/bank/gsyh"
},
{
name: '农业',
color: {
bgColor: '#FFF0DD',
lineColor: '#FFA143',
buttonColor: '#FFA64D',
},
icon: 'nyyh',
callUrl: "/pages/other/bank/nyyh"
},
{
name: '建设',
color: {
bgColor: '#E0FFD9',
lineColor: '#56B745',
buttonColor: '#5DCD49',
},
icon: 'jsyh',
callUrl: "/pages/other/bank/jsyh"
}
])
const data = reactive({
navBar: {
title: '选择机型',
bgColor: '#F0F4F9',
},
type: "message"
})
onLoad((options) => {
if (options.type) {
data.type = options.type
}
proxy.$apiUserEvent('all', {
type: 'event',
key: 'bank',
prefix: '.uni.other.',
value:'银行卡首页'
})
})
function goPage(url,name) {
if (url) {
proxy.$apiUserEvent('all', {
type: 'event',
key: 'bank',
prefix: '.uni.other.',
value:name+'银行'
})
util.goPage(url)
} else {
uni.showToast({
title: '开发中',
icon: 'none'
})
}
}
</script>
<style>
@import '@/common/main.css';
</style>
<style lang="less">
.list-container {
background-color: #F0F4F9;
padding: 24rpx 32rpx;
}
.item {
width: 100%;
height: 188rpx;
border-radius: 24rpx;
margin-bottom: 24rpx;
padding: 40rpx 36rpx 24rpx 28rpx;
.content {
.logo {
width: 96rpx;
height: 96rpx;
flex-shrink: 0;
}
.name {
margin-left: 32rpx;
color: #1A1A1A;
font-size: 36rpx;
font-weight: 500;
}
.right-button {
width: 140rpx;
height: 64rpx;
border-radius: 16rpx;
color: #ffffff;
font-size: 28rpx;
line-height: 64rpx !important;
text-align: right;
// display: flex;
// align-items: center;
// justify-content: center;
}
}
.line {
width: 100rpx;
height: 8rpx;
filter: blur(5px);
opacity: 0.5;
margin-top: 4rpx;
}
}
</style>

1760
pages/other/bank/jsyh.vue Normal file

File diff suppressed because it is too large Load Diff

1209
pages/other/bank/nyyh.vue Normal file

File diff suppressed because it is too large Load Diff

1508
pages/other/bank/zsyh.vue Normal file

File diff suppressed because it is too large Load Diff

732
pages/other/card/card.vue Normal file
View File

@ -0,0 +1,732 @@
<template>
<view class="container">
<!-- 自定义头部导航栏 -->
<ZdyNavbar @right-click="edit" isRightButton rightButtonText="编辑" :title="data.navbar.title"
:bgColor="data.navbar.bgColor" :isBack="true" />
<view style="display: flex;align-items: center;background: #fff;border-radius: 6px;margin: 10px;padding: 4px;color: #767676;">
<image src="/static/image/other/notice.png" style="width: 16px;height: 16px;margin-right: 10rpx;margin-left: 10rpx;"></image>
此功能不具备真实性,仅供娱乐
</view>
<image :src="data.code" mode="widthFix" style="width: 100vw;" @click="previewImage"></image>
<view class="button-container">
<button class="btn-save-image" @click="saveImage">保存图片</button>
</view>
<view style="width: 0px;height: 0;overflow: hidden;" v-if="data.shuaxing && data.bgImg">
<l-painter isCanvasToTempFilePath @success="successImage" @progress="progress"
:css="`width:${data.width}px;height:${data.height }px;background-color:#fff;`">
<l-painter-view
:css="`margin-top:109px;margin-left:75px;position: relative;width:666px;height:406px;`">
<!-- 背景图:用 image 替代 background-image,避免鸿蒙本地路径不生效 -->
<l-painter-image :src="data.bgImg" css="width:666px;height:406px;position:absolute;left:0;top:0;object-fit:fill;" />
<!-- 姓名 -->
<l-painter-view :css="`position: absolute;left:122px;top:54px;`">
<l-painter-text :css="data.textCss+data.textCssLeft" :text="data.form.name" />
</l-painter-view>
<l-painter-view :css="`position: absolute;left:122px;top:102px;`">
<l-painter-text :css="data.textCss2+data.textCssLeft" :text="data.form.gender" />
</l-painter-view>
<l-painter-view :css="`position: absolute;left:262px;top:102px;`">
<l-painter-text :css="data.textCss2+data.textCssLeft" :text="data.form.nation" />
</l-painter-view>
<!-- 出生年:逐字绝对定位,避免鸿蒙下多 text 竖排/重叠 -->
<l-painter-text
v-for="(item, index) in String(data.form.year)"
:key="'year-' + index"
:css="data.textCssDigit + `position:absolute;left:${122 + index * 18}px;top:152px;`"
:text="item"
/>
<!-- 月 / 日 -->
<l-painter-view :css="`position: absolute;left:216px;top:152px;`">
<l-painter-text :css="data.textCssMonthDay" :text="String(data.form.month)" />
</l-painter-view>
<l-painter-view :css="`position: absolute;left:278px;top:152px;`">
<l-painter-text :css="data.textCssMonthDay" :text="String(data.form.day)" />
</l-painter-view>
<!-- 住址 -->
<l-painter-text
v-for="(item, index) in addressChars"
:key="'addr-' + index"
:css="data.textCssDigit + `position:absolute;left:${item.left}px;top:${item.top}px;`"
:text="item.text"
/>
<!-- 身份证号 -->
<l-painter-text
v-for="(item, index) in String(data.form.idNumber || '')"
:key="'id-' + index"
:css="data.textCssId + `position:absolute;left:${223 + index * 20}px;top:322px;`"
:text="item"
/>
<!-- 照片(right 在鸿蒙易失效,改为 left) -->
<l-painter-view :css="`position: absolute;left:422px;top:62px;`">
<l-painter-image v-if="data.form.photo" :src="data.form.photo" css="width: 196px; height: 230px;" />
<l-painter-view v-else css="width: 196px; height: 230px;background-color:#fff;">
</l-painter-view>
</l-painter-view>
<l-painter-view v-if="$isVip()" :css="`position: absolute;left:535px;bottom:-78px;`">
<l-painter-image src="/static/image/other/card/shuiyin2.png" css="width: 170px;height: 50px;" />
</l-painter-view>
</l-painter-view>
</l-painter>
</view>
<!-- 编辑弹窗 -->
<view v-if="showEditPopup" class="popup-overlay">
<view class="popup-content">
<view class="popup-header">
<text class="popup-title">编辑身份证</text>
<text class="popup-close" @click="closeEditPopup">×</text>
</view>
<view class="popup-body">
<view class="form-row">
<text class="form-label">姓名:</text>
<input class="form-input" v-model="editForm.name" type="text" />
</view>
<view class="form-row">
<text class="form-label">性别:</text>
<input class="form-input" v-model="editForm.gender" type="text" />
</view>
<view class="form-row">
<text class="form-label">民族:</text>
<input class="form-input" v-model="editForm.nation" type="text" />
</view>
<view class="form-row">
<text class="form-label">出生日期:</text>
<input class="form-input" v-model="editForm.year" type="number" style="width: 100px;" />
<text class="form-separator">-</text>
<input class="form-input" v-model="editForm.month" type="number" style="width: 80px;" />
<text class="form-separator">-</text>
<input class="form-input" v-model="editForm.day" type="number" style="width: 80px;" />
</view>
<view class="form-row">
<text class="form-label">住址:</text>
<input class="form-input" v-model="editForm.address" type="text" />
</view>
<view class="form-row">
<text class="form-label">身份证号:</text>
<input class="form-input" v-model="editForm.idNumber" type="text" />
</view>
<view class="form-row">
<text class="form-label">照片:</text>
<view class="upload-container">
<image v-if="editForm.photo" :src="editForm.photo" class="upload-image" @click="chooseImage" />
<view v-else class="upload-btn" @click="chooseImage">
<text class="upload-text">点击上传照片</text>
</view>
</view>
</view>
</view>
<view class="popup-footer">
<button class="btn-cancel" @click="closeEditPopup">取消</button>
<button class="btn-save" @click="saveEditForm">保存</button>
</view>
</view>
</view>
<!-- 水印 -->
<view v-if="$isVip()">
<liu-drag-button :canDocking="false"
@clickBtn="$goRechargePage('watermark', 'uni_alipay_other_card')">
<image src='/static/lottie/watermark.gif' mode="widthFix" style="width:94px;height:74px;" ></image>
</liu-drag-button>
</view>
</view>
</template>
<script setup>
// 自定义头部
import ZdyNavbar from "@/components/nav-bar/nav-bar.vue"
import { imgLocal } from '@/utils/common.js';
import {
ref,
reactive,
computed,
watch,
nextTick,
getCurrentInstance
} from "vue";
import {
onLoad,
onShow,
onUnload,
onReady,
onPullDownRefresh,
onReachBottom
} from "@dcloudio/uni-app";
const {
appContext,
proxy
} = getCurrentInstance();
const data = reactive({
shuaxing: false,
navbar: {
title: "身份证",
bgColor: '#EDEDED',
},
width: 800,
height: 600,
code: '',
bgImg: '',
form: {
name: '某某',
gender: '男',
nation: '汉',
year: 2000,
month: 1,
day: 1,
address: '翻斗大街翻斗花园二号楼1001室',
idNumber: '110000200001010000',
photo: '',
},
textCss: 'font-family: "SimHei", "Microsoft YaHei", sans-serif;width:100px;color:#3D3D3D;font-size:24px;',
textCss2: 'font-family: "SimHei", "Microsoft YaHei", sans-serif;width:100px;text-align: left;color:#3D3D3D;font-size:22px;',
// 年/月/日/住址单字
textCssDigit: 'font-family: "SimHei", "Microsoft YaHei", sans-serif;width:22px;color:#3D3D3D;font-size:22px;',
textCssMonthDay: 'font-family: "SimHei", "Microsoft YaHei", sans-serif;width:40px;text-align:center;color:#3D3D3D;font-size:22px;',
// 身份证号单字(专用字体)
textCssId: 'font-family: "card", "Microsoft YaHei", sans-serif;width:22px;color:#1a1a1a;font-size:28px;',
textCssLeft: 'text-align: left;',
textCssCenter: 'text-align: center;'
})
/** 住址逐字坐标(固定字宽换行,避免鸿蒙流式布局异常) */
const addressChars = computed(() => {
const chars = String(data.form.address || '').split('')
const startLeft = 122
const startTop = 200
const step = 26
const lineHeight = 32
const perLine = 10
return chars.map((text, index) => ({
text,
left: startLeft + (index % perLine) * step,
top: startTop + Math.floor(index / perLine) * lineHeight
}))
})
function loadCardFont() {
return new Promise((resolve) => {
uni.loadFontFace({
family: 'card',
source: 'url("/static/font/card.ttf")',
success: () => resolve(true),
fail: (err) => {
console.warn('身份证号码字体加载失败', err)
resolve(false)
}
})
})
}
async function loadCardBg() {
try {
const localPath = await imgLocal.getLocalImage('other/card/cardBGImg.png')
return localPath || ''
} catch (e) {
console.error('身份证背景图加载失败', e)
return ''
}
}
// 弹窗相关
const showEditPopup = ref(false);
const editForm = ref({});
onLoad(async(option) => {
uni.showLoading({
title: "生成中"
})
await loadCardFont()
data.bgImg = await loadCardBg()
data.shuaxing = !!data.bgImg
if (!data.bgImg) {
uni.hideLoading()
uni.showToast({ title: '背景图加载失败', icon: 'none' })
}
// 进入身份证页面埋点
proxy.$apiUserEvent('all', {
type: 'event',
key: 'idcard',
prefix: '.uni.other.',
value: "身份证"
})
let formdata=uni.getStorageSync("cardForm")
if(formdata){
data.form=formdata
}
uni.$on("editFormPhoto",(info)=>{
data.code = ""
editForm.value.photo = info;
data.form.photo = info;
})
})
onUnload(() => {
uni.$off('editFormPhoto')
})
onReady(() => {
})
onShow(() => {})
onPullDownRefresh(() => {
setTimeout(() => {
uni.stopPullDownRefresh();
}, 1000);
})
onReachBottom(() => {
})
function successImage(e) {
data.code = e
uni.hideLoading()
}
function progress(e) {
// console.log(e)
}
// 打开编辑弹窗
function edit() {
console.log(data.form)
// 复制当前表单数据到编辑表单
editForm.value = JSON.parse(JSON.stringify(data.form));
// 显示弹窗
showEditPopup.value = true;
}
// 关闭编辑弹窗
function closeEditPopup() {
showEditPopup.value = false;
}
// 保存编辑表单
function saveEditForm() {
uni.showLoading({
title: "生成中"
})
data.shuaxing=false
// 将编辑后的数据复制回原始表单
data.form = editForm.value
data.form.photo=editForm.value.photo
setTimeout(()=>{
data.shuaxing=true
},100)
uni.setStorageSync("cardForm",data.form)
// 关闭弹窗
showEditPopup.value = false;
}
// 选择图片
function chooseImage() {
uni.chooseImage({
count: 1,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
// crop:{
// width:'196px',
// height:"230px"
// },
success: (res) => {
uni.navigateTo({
url:'/pages/other/qf-image/qf-image?src='+res.tempFilePaths[0]
})
},
fail: (err) => {
console.log('选择图片失败', err);
}
});
}
/**
* 将本地图片路径通过 Canvas 转换为 File 对象
* @param {string} localPath - 本地图片路径(如从 uni.chooseImage 获取的 tempFilePath)
* @param {Object} options - 可选参数
* @param {string} options.format - 输出格式 'image/png' 或 'image/jpeg',默认 'image/png'
* @param {number} options.quality - 图片质量(仅 jpeg 有效),0~1,默认 0.92
* @param {number} options.maxWidth - 最大宽度(等比缩放),不填则使用原图尺寸
* @param {number} options.maxHeight - 最大高度(等比缩放),不填则使用原图尺寸
* @returns {Promise<File>} 返回一个 Promise,resolve 为 File 对象
*/
function convertLocalImageToFile(localPath) {
return new Promise((resolve, reject) => {
// #ifdef APP-PLUS&&!APP-HARMONY
// 1. 读取本地图片为 Base64(避免 Image 对象直接加载本地路径的兼容问题)
plus.io.resolveLocalFileSystemURL(localPath, (entry) => {
entry.file((file) => {
const reader = new plus.io.FileReader();
reader.onload = (e) => {
const base64Data = e.target.result; // 格式如 data:image/jpeg;base64,/9j/...
resolve(e.target.result)
};
reader.onerror = (err) => reject(err);
reader.readAsDataURL(file); // 读取为 DataURL
}, reject);
}, reject);
// #endif
// #ifndef APP-PLUS
reject(new Error('plus.io not supported'))
// #endif
});
}
// 预览图片
function previewImage() {
if (data.code) {
uni.previewImage({
urls: [data.code],
current: 0
});
}
}
// 保存图片
function saveImage() {
if (data.code) {
console.log(data.code)
uni.showLoading({
title: '保存中...'
});
try {
// 检查是否为base64格式
if (data.code.startsWith('data:image')) {
// 处理base64格式图片
console.log('开始处理base64图片');
uni.base64ToTempFile({
base64: data.code.split(',')[1], // 去除base64前缀
success: (res) => {
console.log('base64转换成功', res);
if (res.tempFilePath) {
uni.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: () => {
console.log('保存图片成功');
uni.hideLoading();
uni.showToast({
title: '保存成功',
icon: 'success'
});
},
fail: (err) => {
console.log('保存图片失败', err);
uni.hideLoading();
uni.showToast({
title: '保存失败',
icon: 'none'
});
}
});
} else {
console.log('base64转换失败,无临时文件路径');
uni.hideLoading();
uni.showToast({
title: '转换失败',
icon: 'none'
});
}
},
fail: (err) => {
console.log('base64转换失败', err);
uni.hideLoading();
uni.showToast({
title: '转换失败',
icon: 'none'
});
}
});
} else if (data.code.startsWith('_') || data.code.includes('temp') || data.code.includes('cache') || data
.code.includes('_doc') || data.code.includes('_tmp')) {
// 处理本地临时文件
console.log('开始处理本地临时文件');
uni.saveImageToPhotosAlbum({
filePath: data.code,
success: () => {
console.log('保存本地文件成功');
uni.hideLoading();
uni.showToast({
title: '保存成功',
icon: 'success'
});
},
fail: (err) => {
console.log('保存本地文件失败', err);
uni.hideLoading();
uni.showToast({
title: '保存失败',
icon: 'none'
});
}
});
} else {
// 处理网络图片
console.log('开始处理网络图片');
uni.downloadFile({
url: data.code,
success: (res) => {
console.log('下载图片成功', res);
if (res.statusCode === 200 && res.tempFilePath) {
uni.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: () => {
console.log('保存图片成功');
uni.hideLoading();
uni.showToast({
title: '保存成功',
icon: 'success'
});
},
fail: (err) => {
console.log('保存图片失败', err);
uni.hideLoading();
uni.showToast({
title: '保存失败',
icon: 'none'
});
}
});
} else {
console.log('下载图片失败,状态码:', res.statusCode);
uni.hideLoading();
uni.showToast({
title: '下载失败',
icon: 'none'
});
}
},
fail: (err) => {
console.log('下载图片失败', err);
uni.hideLoading();
uni.showToast({
title: '下载失败',
icon: 'none'
});
}
});
}
} catch (error) {
console.log('保存图片异常', error);
uni.hideLoading();
uni.showToast({
title: '保存失败',
icon: 'none'
});
}
} else {
uni.showToast({
title: '暂无图片可保存',
icon: 'none'
});
}
}
</script>
<style lang="scss" >
page {
background-color: #ededed;
}
@font-face {
font-family: "card";
src: url("/static/font/card.ttf");
}
* {
box-sizing: content-box;
}
.aadadad {
text-align: center;
}
.heiti {
font-family: "SimHei", "Microsoft YaHei", sans-serif;
}
/* 弹窗样式 */
.popup-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
}
.popup-content {
background-color: #fff;
border-radius: 10px;
width: 90%;
max-width: 500px;
max-height: 80vh;
overflow-y: auto;
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx;
border-bottom: 1rpx solid #eee;
}
.popup-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.popup-close {
font-size: 48rpx;
color: #999;
cursor: pointer;
}
.popup-body {
padding: 20rpx;
}
.form-section {
margin-bottom: 30rpx;
padding: 20rpx;
background-color: #f5f5f5;
border-radius: 8rpx;
}
.section-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
display: block;
}
.form-row {
display: flex;
align-items: center;
margin-bottom: 20rpx;
}
.form-label {
width: 200rpx;
font-size: 26rpx;
color: #333;
}
.form-input {
flex: 1;
padding: 15rpx;
border: 1rpx solid #ddd;
border-radius: 4rpx;
font-size: 26rpx;
}
.form-separator {
margin: 0 10rpx;
font-size: 26rpx;
color: #333;
}
.form-total {
margin-top: 10rpx;
padding-top: 10rpx;
border-top: 1rpx dashed #ddd;
}
.form-total-input {
background-color: #f9f9f9;
color: #ff6b35;
font-weight: bold;
}
/* 上传图片样式 */
.upload-container {
flex: 1;
position: relative;
}
.upload-btn {
width: 120rpx;
height: 160rpx;
border-radius: 8rpx;
border: 1rpx dashed #ddd;
display: flex;
justify-content: center;
align-items: center;
background-color: #f5f5f5;
}
.upload-text {
font-size: 22rpx;
color: #999;
text-align: center;
}
.upload-image {
width: 98rpx;
height: 115rpx;
border-radius: 1rpx;
object-fit: cover;
border: 1px dashed #ddd;
}
.popup-footer {
display: flex;
justify-content: space-between;
padding: 20rpx;
border-top: 1rpx solid #eee;
}
.btn-cancel,
.btn-save {
width: 48%;
padding: 20rpx;
border-radius: 4rpx;
font-size: 28rpx;
}
.btn-cancel {
background-color: #f5f5f5;
color: #333;
border: 1rpx solid #ddd;
}
.btn-save {
background-color: #187AFF;
color: #fff;
border: none;
}
/* 保存图片按钮 */
.button-container {
display: flex;
justify-content: center;
padding: 30rpx 0;
}
.btn-save-image {
background: linear-gradient(90deg, #187AFF 0%, #3295FC 100%);
color: #fff;
border: none;
padding: 18rpx 60rpx;
border-radius: 60rpx;
font-size: 26rpx;
font-weight: bold;
box-shadow: 0 3rpx 10rpx rgba(7, 66, 193, 0.3);
transition: all 0.3s ease;
text-align: center;
min-width: 160rpx;
}
.btn-save-image:hover {
transform: translateY(-2rpx);
box-shadow: 0 4rpx 12rpx rgba(7, 72, 193, 0.4);
}
.btn-save-image:active {
transform: translateY(0);
box-shadow: 0 2rpx 6rpx rgba(7, 140, 193, 0.3);
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,163 @@
<template>
<view class="container">
<ZdyNavbar :title="data.navbar.title" :bgColor="data.navbar.bgColor" :isAdd="true" :isSearch="$system=='Android'">
</ZdyNavbar>
<view class="card-grid">
<view class="card" v-for="(item, index) in cardList" :key="index">
<view class="title">
{{item.title}}
</view>
<image :src="item.img" class="card-img" />
<button class="card-btn" @click="create(index)">立即制作</button>
</view>
</view>
</view>
</template>
<script setup>
import ZdyNavbar from "@/components/nav-bar/nav-bar.vue"
import {
ref,
reactive,
getCurrentInstance
} from 'vue'
import {
onLoad,
onShow,
onUnload,
onReady,
onPullDownRefresh,
onReachBottom
} from "@dcloudio/uni-app";
const {
appContext,
proxy
} = getCurrentInstance();
const data = reactive({
navbar: {
title: '证件选择',
bgColor: ''
}
})
const cardList = ref([
{
title: '身份证',
img: '/static/image/other/certificate/certificate1.png',
},
{
title: '毕业证',
img: '/static/image/other/certificate/certificate2.png',
},
{
title: '情侣证',
img: '/static/image/other/certificate/certificate3.png',
},
{
title: '分手证',
img: '/static/image/other/certificate/certificate4.png',
},
{
title: '奖状',
img: '/static/image/other/certificate/certificate5.png',
},
{
title: '颜值证',
img: '/static/image/other/certificate/certificate6.png',
},
])
onLoad(()=>{
proxy.$apiUserEvent('all', {
type: 'event',
key: 'certificate',
prefix: '.uni.other.',
value:'证书首页'
})
const config = uni.getStorageSync('config').config
const themeConfig = config?.['client.uniapp.idcard']
if (!themeConfig) {
cardList.value.shift()
}
})
function create(index){
if(index){
uni.navigateTo({
url:'/pages/other/certificate/graduate?styleType='+(index-1)
})
}else{
uni.navigateTo({
url:'/pages/other/card/card'
})
}
}
</script>
<style scoped>
.container {
padding: 20rpx;
background-color: #F0F4F9;
min-height: 100vh;
}
.card-grid {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.card {
width: 48%;
background-color: #fff;
border-radius: 20rpx;
padding: 20rpx;
margin-bottom: 30rpx;
align-items: center;
/* box-shadow: 0 4rpx 10rpx rgba(0, 0, 0, 0.05); */
display: flex;
align-items: center;
flex-direction: column;
}
.title{
font-size: 14px;
color: #1A1A1A;
line-height: 14px;
font-weight: bold;
padding-top: 10rpx;
}
.card-img {
width: 130px;
height: 80px;
/* border-radius: 8px 8px 8px 8px; */
margin-top: 21px;
margin-bottom: 18px;
}
.card-btn {
width: 68px;
height: 28px;
background: linear-gradient(327deg, #1777FF 0%, #17CDFF 100%);
border-radius: 8px 8px 8px 8px;
font-size: 12px;
color: #FFFFFF;
display: flex;
align-items: center;
justify-content: center;
}
button::after{
border: none;
}
button{
padding: 0;
margin: 0;
border: none;
}
</style>

View File

@ -0,0 +1,637 @@
<template>
<view class="honor-of-kings">
<nav-bar title="和平精英" bgColor="#F5F5F5" isRightButton :rightButtonText="rightButtonText"
@right-click="onRightClick">
</nav-bar>
<view style="padding: 8px;box-sizing: border-box;">
<view class="painter-container" @click="handlePreview"
:style="`width:calc(100vw - 16px) ; height: ${posterContainerHeight}px; overflow: hidden; position: relative; transform: translateZ(0);`">
<!-- 运用 js 算出的无误差纯数字 scale 来实现高清画板的等比缩小,彻底兼容所有老旧内核,不再被截断 -->
<view
:style="`width: 822px; transform: scale(${posterScaleRatio}); transform-origin: left top; position: absolute; left: 0; top: 0;`">
<l-painter v-if="bgImage" isCanvasToTempFilePath @success="onPainterSuccess" :css="`width:822px;`">
<l-painter-view :css="`width: 822px; position: relative;`">
<!-- 直接使用主背景图撑开父容器,移除不必要的 opacity: 0 占位图,避免在大屏渲染时产生黑块 -->
<l-painter-image :src="bgImage" css="width: 822px; display: block;"></l-painter-image>
<!-- 人物图片 -->
<l-painter-image :src="hePingData.imgUrl" :css="characterStyle"></l-painter-image>
<template v-if="hePingData.type == 2">
<!-- 头像 -->
<l-painter-image :src="hePingData.avatar"
css="position:absolute;left:50px;bottom:13px;width:36px;height:36px"></l-painter-image>
<!-- 头像框 -->
<l-painter-image src="/static/image/other/game/heping/touxiang.png"
css="position:absolute;left:0;bottom:0;width:100px;height:60px"></l-painter-image>
<!-- 昵称 -->
<l-painter-text :text="hePingData.nickname"
css="position: absolute; left: 102px; bottom: 15px; font-size: 10px; color: #EFF9FF;"></l-painter-text>
</template>
<!-- 水印 -->
<l-painter-image v-if="$isVip()" src="/static/image/other/shuiying.png"
:css="`position: absolute; left: 18.8px; bottom: 17.1px; width: 145.38px;height:42.76px`"></l-painter-image>
</l-painter-view>
</l-painter>
</view>
</view>
</view>
<!-- 水印 -->
<view v-if="$isVip()">
<liu-drag-button :canDocking="false"
@clickBtn="$goRechargePage('watermark', 'uni_alipay_other_game_heping')">
<image src='/static/lottie/watermark.gif' mode="widthFix" style="width:94px;height:74px;" ></image>
</liu-drag-button>
</view>
<view class="save-action">
<button class="save-btn" @click="handleSave">保存</button>
</view>
<!-- 主题选择区域 -->
<view class="theme-selector" :style="{'margin-bottom':$store.state.safeBottomAz+'px'}">
<scroll-view scroll-x="true" class="theme-scroll" :show-scrollbar="false">
<view class="theme-list" >
<view class="theme-item" v-for="(item, index) in ['样式一', '样式二']" :key="index + 1"
:class="{ active: hePingData.type == index + 1 }" @click="handleChangeTheme(index + 1)">
<text class="theme-text">{{ item }}</text>
</view>
</view>
</scroll-view>
</view>
<!-- 数据编辑弹窗 -->
<uni-popup ref="editPopup" type="center">
<view class="edit-popup-content">
<view class="popup-header">
<text class="title">编辑主页数据</text>
</view>
<scroll-view scroll-y class="popup-scroll">
<view class="form-item avatar-form-item">
<text class="label">人物图片</text>
<view class=" avatar-uploader img-box" @click="handleChooseImgUrl">
<view class="avatar-preview" v-if="tempData.imgUrl">
<image class="preview-img" :src="tempData.imgUrl" mode="widthFix"></image>
<view class="delete-icon" @click.stop="handleDeleteImgUrl">
<text>×</text>
</view>
</view>
<view class="upload-btn" v-else>
<text class="plus">+</text>
</view>
</view>
</view>
<view class="form-item avatar-form-item" v-if="tempData.type == 2">
<text class="label">头像</text>
<view class="avatar-uploader" @click="handleChooseAvatar">
<view class="avatar-preview" v-if="tempData.avatar">
<image class="preview-img" :src="tempData.avatar" mode="aspectFill"></image>
<view class="delete-icon" @click.stop="handleDeleteAvatar">
<text>×</text>
</view>
</view>
<view class="upload-btn" v-else @click="handleChooseAvatar">
<text class="plus">+</text>
</view>
</view>
</view>
<view class="form-item" v-if="tempData.type == 2">
<text class="label">昵称</text>
<input class="input" type="text" v-model="tempData.nickname" />
</view>
</scroll-view>
<view class="popup-footer">
<button class="cancel-btn" @click="closeEditPopup">取消</button>
<button class="confirm-btn" @click="confirmEdit">确定</button>
</view>
</view>
</uni-popup>
<!-- 横向全屏放大预览层 -->
<view class="preview-overlay" v-if="showPreview" @click="showPreview = false">
<image class="preview-image" :src="finalPosterPath" mode="aspectFill"></image>
</view>
</view>
</template>
<script setup>
import { ref, reactive, computed, watch, onMounted, getCurrentInstance } from 'vue'
import { onLoad, onReady, onUnload } from '@dcloudio/uni-app'
import { imgLocal } from '@/utils/common.js';
import NavBar from '@/components/nav-bar/nav-bar.vue';
const instance = getCurrentInstance();
const { proxy } = getCurrentInstance();
// 页面表单数据字段
const hePingData = reactive({
type: 1, // 样式类型 1.2
avatar: '/static/image/shopping/pdd/avatars/avatars1.jpg', // 头像
imgUrl: '/static/image/other/game/heping/default.png', // 人物图片
nickname: '甜喵小贝', // 昵称
})
const characterImgWidth = ref(300); // 初始宽度兜底
const characterStyle = computed(() => {
if (hePingData.type == 1) {
return `position: absolute; left: 50%; bottom:43%; transform: translate(-50%,50%); height: 300px; width: 300px;`;
} else {
return `position: absolute; left: 300px; bottom: 50%;transform: translate(-50%,50%); height: 300px; width: 300px;`;
}
});
// 监听人物图片变化,动态获取其原始宽高并计算比例宽度,解决画布必须写明尺寸才能渲染的问题
watch(() => hePingData.imgUrl, (newUrl) => {
if (!newUrl) return;
uni.getImageInfo({
src: newUrl,
success: (res) => {
// 固定高度 300px,宽度等比例拉伸
const calcWidth = (300 / res.height) * res.width;
characterImgWidth.value = Math.round(calcWidth);
},
fail: () => {
characterImgWidth.value = 300; // 获取失败提供兜底宽度
}
});
}, { immediate: true });
const bgImage = ref('')
// 获取系统宽度计算海报缩放比例,代替容易失效的 css calc(),确保全端兼容防截断
const sysInfo = uni.getSystemInfoSync();
const windowWidth = sysInfo.windowWidth || 375;
// 扣除左右共 16px 的 padding,再基于实际画板宽度 822 计算精确缩放比,防止右侧被截断
const posterScaleRatio = (windowWidth - 16) / 822;
const posterContainerHeight = 374 * posterScaleRatio;
onLoad(async () => {
const cachedData = uni.getStorageSync('hePingData');
if (cachedData) {
Object.assign(hePingData, cachedData);
// 兼容处理:强制把历史缓存里的 .jpg 替换为最新的 .png,防止 404
if (hePingData.imgUrl === '/static/image/other/game/heping/default.jpg') {
hePingData.imgUrl = '/static/image/other/game/heping/default.png';
uni.setStorageSync('hePingData', hePingData);
}
}
try {
bgImage.value = await imgLocal.getLocalImage(`other/game/heping/bg-${hePingData.type}.jpg`);
} catch (e) {
console.error('获取背景图失败:', e);
uni.showToast({ title: '背景图加载失败', icon: 'none' });
}
uni.$on('editFormPhoto', (info) => {
if (currentEditImgField.value) {
tempData[currentEditImgField.value] = info;
}
// #ifndef H5
if (info && !info.startsWith('/static/')) {
newAvatars.value.push(info);
}
// #endif
});
// 进入页面埋点
if (proxy && proxy.$apiUserEvent) {
proxy.$apiUserEvent('all', {
type: 'click',
key: 'heping',
prefix: '.uni.other.',
value: "和平精英"
});
}
})
onUnload(() => {
uni.$off('editFormPhoto');
})
const rightButtonText = ref("编辑");
const finalPosterPath = ref('');
const showPreview = ref(false); // 控制全屏预览
const editPopup = ref(null);
const tempData = reactive({});
const newAvatars = ref([]);
const currentEditImgField = ref('');
function onRightClick() {
Object.assign(tempData, JSON.parse(JSON.stringify(hePingData)));
newAvatars.value = [];
editPopup.value.open();
}
const handleChooseAvatar = () => {
currentEditImgField.value = 'avatar';
uni.chooseImage({
count: 1,
success: (res) => {
uni.navigateTo({
url: `/pages/other/qf-image/qf-image?src=${res.tempFilePaths[0]}&width=200&height=200`
});
}
});
};
const handleChooseImgUrl = () => {
currentEditImgField.value = 'imgUrl';
uni.chooseImage({
count: 1,
success: (res) => {
if (res.tempFilePaths[0]) {
// 跳转到 qf-image 组件进行 300x300 尺寸(传参150)的裁剪,并通过 isMatting=1 触发后续抠图
uni.navigateTo({
url: `/pages/other/qf-image/qf-image?src=${res.tempFilePaths[0]}&width=150&height=150&isMatting=1`
});
}
}
});
};
const handleDeleteAvatar = () => {
tempData.avatar = '';
};
const handleDeleteImgUrl = () => {
tempData.imgUrl = '';
};
function closeEditPopup() {
// #ifndef H5
newAvatars.value.forEach(path => {
uni.removeSavedFile({ filePath: path });
});
// #endif
editPopup.value.close();
}
function confirmEdit() {
// #ifndef H5
// 删除多余的新上传文件
newAvatars.value.forEach(path => {
if (path !== tempData.avatar && path !== tempData.imgUrl) {
uni.removeSavedFile({ filePath: path });
}
});
// 删除被替换掉的原本地头像
if (hePingData.avatar !== tempData.avatar && hePingData.avatar && !hePingData.avatar.startsWith('/static/')) {
uni.removeSavedFile({ filePath: hePingData.avatar });
}
if (hePingData.imgUrl !== tempData.imgUrl && hePingData.imgUrl && !hePingData.imgUrl.startsWith('/static/')) {
uni.removeSavedFile({ filePath: hePingData.imgUrl });
}
// #endif
Object.assign(hePingData, tempData);
uni.setStorageSync('hePingData', hePingData);
editPopup.value.close();
// 数据修改后,重新渲染海报
finalPosterPath.value = '';
let tempBg = bgImage.value;
bgImage.value = ''; // 强制卸载旧画板,确保能重新渲染和生成新海报
setTimeout(() => {
bgImage.value = tempBg;
}, 50);
}
async function handleChangeTheme(typeIndex) {
// 切换主题时,先清空旧海报,触发界面“加载中”提示
finalPosterPath.value = '';
bgImage.value = ''; // 强制卸载旧画板,确保能重新渲染和生成新海报
hePingData.type = typeIndex;
uni.setStorageSync('hePingData', hePingData);
try {
bgImage.value = await imgLocal.getLocalImage(`other/game/heping/bg-${typeIndex}.jpg`);
} catch (e) {
console.error('切换主题加载背景图失败:', e);
uni.showToast({ title: '背景加载失败', icon: 'none' });
}
}
// 点击画布触发横向预览
const handlePreview = () => {
if (!finalPosterPath.value) {
uni.showToast({ title: '海报仍在生成中,请稍候', icon: 'none' });
return;
}
showPreview.value = true;
}
// 画布生成成功后触发,保存最终的海报路径
const onPainterSuccess = (path) => {
finalPosterPath.value = path;
}
// 点击保存按钮,将带有渐变字体的最终图片存入相册
const handleSave = () => {
if (!finalPosterPath.value) {
uni.showToast({ title: '图片仍在生成中,请稍候', icon: 'none' })
return
}
uni.saveImageToPhotosAlbum({
filePath: finalPosterPath.value,
success: () => {
uni.showToast({ title: '保存成功', icon: 'success' })
},
fail: () => {
uni.showToast({ title: '保存失败', icon: 'none' })
}
})
}
/**
* 将本地图片路径通过 Canvas 转换为 File 对象
* @param {string} localPath - 本地图片路径(如从 uni.chooseImage 获取的 tempFilePath)
* @param {Object} options - 可选参数
* @param {string} options.format - 输出格式 'image/png' 或 'image/jpeg',默认 'image/png'
* @param {number} options.quality - 图片质量(仅 jpeg 有效),0~1,默认 0.92
* @param {number} options.maxWidth - 最大宽度(等比缩放),不填则使用原图尺寸
* @param {number} options.maxHeight - 最大高度(等比缩放),不填则使用原图尺寸
* @returns {Promise<File>} 返回一个 Promise,resolve 为 File 对象
*/
function convertLocalImageToFile(localPath) {
return new Promise((resolve, reject) => {
// #ifdef APP-PLUS&&!APP-HARMONY
// 1. 读取本地图片为 Base64(避免 Image 对象直接加载本地路径的兼容问题)
plus.io.resolveLocalFileSystemURL(localPath, (entry) => {
entry.file((file) => {
const reader = new plus.io.FileReader();
reader.onload = (e) => {
const base64Data = e.target.result; // 格式如 data:image/jpeg;base64,/9j/...
resolve(e.target.result)
};
reader.onerror = (err) => reject(err);
reader.readAsDataURL(file); // 读取为 DataURL
}, reject);
}, reject);
// #endif
// #ifndef APP-PLUS
reject(new Error('plus.io not supported'))
// #endif
});
}
</script>
<style lang="less" scoped>
.preview-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #000;
z-index: 99999;
/* 抛弃 flex 布局,防止 width: 100vh 被父级限制而发生坍缩 */
}
.preview-overlay .preview-image {
position: absolute;
top: 50%;
left: 50%;
width: 100vh;
height: 100vw;
/* 先拉回自身中心点,再围绕中心点旋转 90 度 */
transform: translate(-50%, -50%) rotate(90deg);
}
.honor-of-kings {
width: 100%;
overflow-x: hidden;
}
.painter-container {
/* 强制画板缩放到设备屏幕宽度,避免物理 px 超出屏幕 */
padding: 8px;
zoom: calc(100vw / 798);
width: 100%;
max-width: 1000px;
/* 限制PC/iPad端的最大宽度 */
margin: 0 auto;
}
.save-action {
margin-top: 60rpx;
display: flex;
flex-direction: column;
align-items: center;
padding-bottom: 220rpx;
/* 留出底部主题固定栏的安全空间 */
.save-btn {
margin-top: 60rpx;
width: 316rpx;
background: #1777FF;
color: #fff;
border-radius: 56rpx;
}
}
.theme-selector {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
padding-top: 20rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
z-index: 99;
.theme-scroll {
width: 100%;
white-space: nowrap;
::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
}
.theme-list {
display: inline-flex;
width: 100%;
justify-content: center;
padding: 0 20rpx;
.theme-item {
display: inline-flex;
vertical-align: top;
margin: 0 10rpx;
padding: 0 40rpx;
height: 84rpx;
border-radius: 12rpx;
background-color: #FFFFFF;
justify-content: center;
align-items: center;
border: 2rpx solid transparent;
box-sizing: border-box;
transition: all 0.3s;
&.active {
background-color: #fff;
border-color: #3B7BFF;
.theme-text {
color: #3B7BFF;
font-weight: bold;
}
}
.theme-text {
font-size: 28rpx;
color: #666;
}
}
}
}
.edit-popup-content {
background-color: #fff;
border-radius: 20rpx;
width: 85vw;
.popup-header {
display: flex;
justify-content: center;
align-items: center;
padding: 40rpx 0 20rpx 0;
.title {
font-size: 34rpx;
font-weight: bold;
color: #333;
}
}
.popup-scroll {
max-height: 55vh;
padding: 20rpx 40rpx;
box-sizing: border-box;
}
.form-item {
display: flex;
align-items: center;
margin-bottom: 16rpx;
padding-bottom: 16rpx;
.label {
width: 200rpx;
font-size: 28rpx;
color: #333;
}
.avatar-uploader {
display: flex;
align-items: center;
.upload-btn {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
background-color: #999DA7;
display: flex;
justify-content: center;
align-items: center;
.plus {
font-size: 60rpx;
color: #fff;
font-weight: 300;
margin-top: -6rpx;
}
}
.avatar-preview {
position: relative;
width: 100rpx;
height: 100rpx;
.preview-img {
width: 100%;
height: 100%;
border-radius: 50%;
}
.delete-icon {
position: absolute;
top: -4rpx;
right: -4rpx;
width: 32rpx;
height: 32rpx;
background-color: red;
color: #fff;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
font-size: 30rpx;
line-height: 28rpx;
}
}
}
.img-box {
width: 200rpx !important;
.avatar-preview {
width: 200rpx !important;
height: auto !important;
}
.preview-img {
width: 100%;
border-radius: 0 !important;
}
}
.input {
flex: 1;
font-size: 28rpx;
background-color: #F7F7F7;
border-radius: 12rpx;
height: 70rpx;
line-height: 70rpx;
padding: 0 20rpx;
color: #333;
}
}
.popup-footer {
display: flex;
justify-content: space-between;
padding: 20rpx 40rpx 40rpx;
button {
width: 46%;
height: 76rpx;
line-height: 76rpx;
font-size: 30rpx;
border-radius: 12rpx;
margin: 0;
&::after {
border: none;
}
}
.cancel-btn {
background-color: #F4F4F4;
color: #666;
}
.confirm-btn {
background-color: #3B7BFF;
color: #fff;
}
}
}
</style>

View File

@ -0,0 +1,829 @@
<template>
<view class="honor-of-kings">
<nav-bar title="王者主页" bgColor="#F5F5F5" isRightButton :rightButtonText="rightButtonText"
@right-click="onRightClick">
</nav-bar>
<view style="padding: 8px;box-sizing: border-box;">
<view class="painter-container" @click="handlePreview"
:style="`width:calc(100vw - 16px) ; height: ${posterContainerHeight}px; overflow: hidden; position: relative; transform: translateZ(0);`">
<!-- 运用 js 算出的无误差纯数字 scale 来实现高清画板的等比缩小,彻底兼容所有老旧内核,不再被截断 -->
<view
:style="`width: 750px; transform: scale(${posterScaleRatio}); transform-origin: left top; position: absolute; left: 0; top: 0;`">
<l-painter isCanvasToTempFilePath @success="onPainterSuccess" :css="`width:750px;`">
<l-painter-view :css="`width: 750.0px; position: relative;`">
<!-- 直接使用主背景图撑开父容器,移除不必要的 opacity: 0 占位图,避免在大屏渲染时产生黑块 -->
<l-painter-image :src="`/static/image/other/game/wangzhe/style-${honorData.type}.jpg`"
css="width: 750.0px; display: block;"></l-painter-image>
<!-- 头像 (纯百分比绝对定位,完美适配所有大屏分辨率) -->
<l-painter-image :src="honorData.avatar"
css="position: absolute; left: 117.0px; top: 76.8px; width: 66px; height: 66px; border-radius: 200px; object-fit: cover; display: block;"></l-painter-image>
<!-- 头像框 (压在头像上方) -->
<l-painter-image :src="headerImage"
css="position: absolute; left: 0; top: 0; width: 183px; height: 141px"></l-painter-image>
<!-- 性别图标 -->
<l-painter-image :src="`/static/image/other/game/wangzhe/${honorData.gender}.png`"
:css="`position: absolute; left: ${honorData.type == 3 ? '217.5px' : '202.5px'}; top: 87.0px; width: 11.8px; height: 11.7px;`"></l-painter-image>
<!-- 昵称渐变图片 -->
<l-painter-image v-if="nicknameImage" :src="nicknameImage"
:css="`position: absolute; left: ${honorData.type == 3 ? '230.3px' : '215.3px'}; top: ${$system == 'iOS' ? '83px' : '85.6px'}; width: ${honorData.nickname.length * 14.9}px;`"></l-painter-image>
<!-- 收到花束 -->
<l-painter-text :text="honorData.receivedFlowers"
css="position: absolute; left: 338.5px; top: 131.4px; width: 40px; text-align: center; font-size: 9px; color: #DAF2FF;font-family: WangZheFont2;"></l-painter-text>
<!-- 热度数 -->
<l-painter-text :text="honorData.popularityCount"
css="position: absolute; left: 366.3px; top: 131.4px; width: 40px; text-align: center; font-size: 9px; color: #DAF2FF;font-family: WangZheFont2;"></l-painter-text>
<!-- 点赞数 -->
<l-painter-text :text="honorData.likeCount"
css="position: absolute; left: 394.0px; top: 131.4px; width: 40px; text-align: center; font-size: 9px; color: #DAF2FF;font-family: WangZheFont2;"></l-painter-text>
<!-- 荣誉一 -->
<l-painter-text :text="honorData.honor1"
css="position: absolute; left: 207.5px; top: 145.6px; width: 80px; text-align: center; font-size: 9px; color: #71B1D3;"></l-painter-text>
<!-- 荣誉二(文案较长,需足够宽度避免换行下移) -->
<l-painter-text :text="honorData.honor2"
css="position: absolute; left: 300.6px; top: 149px; width: 180px; text-align: center; font-size: 7px; line-height: 1em; color: #71B1D3;"></l-painter-text>
<!-- 巅峰万强 -->
<l-painter-text v-if="honorData.type == 4" :text="honorData.peakStrong"
css="position: absolute; left: 227.3px; top: 192.8px; width: 60px; text-align: center; font-size: 8px; color: #E9F5FB;"></l-painter-text>
<!-- 对战场次 -->
<l-painter-text :text="honorData.matchCount"
css="position: absolute; left: 113.0px; top: 242.3px; width: 50px; text-align: center; font-size: 12px; color: #71B1D3;"></l-painter-text>
<!-- 对战被赞 -->
<l-painter-text :text="honorData.matchLikeCount"
css="position: absolute; left: 174.5px; top: 242.3px; width: 50px; text-align: center; font-size: 12px; color: #71B1D3;"></l-painter-text>
<!-- 图鉴等级 -->
<l-painter-text :text="honorData.pokedexLevel"
css="position: absolute; left: 299.5px; top: 238.8px; width: 40px; text-align: center; font-size:9px; color: #71B1D3;"></l-painter-text>
<!-- 皮肤数 -->
<l-painter-text :text="honorData.skinCount"
css="position: absolute; left: 357.3px; top: 238.8px; width: 40px; text-align: center; font-size:9px; color: #71B1D3;"></l-painter-text>
<!-- 游戏天数 -->
<l-painter-text v-if="honorData.type == 1 || honorData.type == 2" :text="honorData.gameDays"
css="position: absolute; left: 177.1px; top: 282.2px; width: 50px; text-align: center; font-size:10px; color: #71B1D3;"></l-painter-text>
<!-- 峡谷对战局数 -->
<l-painter-text v-if="honorData.type == 1 || honorData.type == 2"
:text="honorData.riftMatchCount"
css="position: absolute; left: 297.2px; top: 282.2px; width: 50px; text-align: center; font-size:10px; color: #71B1D3;"></l-painter-text>
<!-- 大乱斗对战局数 -->
<l-painter-text v-if="honorData.type == 1 || honorData.type == 2"
:text="honorData.aramMatchCount"
css="position: absolute; left: 354.9px; top: 282.2px; width: 50px; text-align: center; font-size:10px; color: #71B1D3;"></l-painter-text>
<!-- 亲密度 -->
<l-painter-text v-if="honorData.type == 3 || honorData.type == 4" :text="honorData.intimacy"
css="position: absolute; left: 323.0px; top: 296.2px; width: 50px; text-align: center; font-size:10px; color: #71B1D3;font-family: WangZheFont2;"></l-painter-text>
<!-- 水印 -->
<l-painter-image v-if="$isVip()" src="/static/image/other/shuiying.png"
:css="`position: absolute; left: 18.8px; bottom: 17.1px; width: 145.38px;height:42.76px`"></l-painter-image>
</l-painter-view>
</l-painter>
</view>
</view>
</view>
<!-- 水印 -->
<view v-if="$isVip()">
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark', 'uni_alipay_other_wangzhe')">
<image src='/static/lottie/watermark.gif' mode="widthFix" style="width:94px;height:74px;" ></image>
</liu-drag-button>
</view>
<view class="save-action">
<button class="save-btn" @click="handleSave">保存</button>
</view>
<!-- 主题选择区域 -->
<view class="theme-selector" :style="{'margin-bottom':$store.state.safeBottomAz+'px'}">
<scroll-view scroll-x="true" class="theme-scroll" :show-scrollbar="false">
<view class="theme-list">
<view class="theme-item" v-for="(item, index) in ['样式一', '样式二', '样式三', '样式四']" :key="index + 1"
:class="{ active: honorData.type == index + 1 }" @click="handleChangeTheme(index + 1)">
<text class="theme-text">{{ item }}</text>
</view>
</view>
</scroll-view>
</view>
<!-- 数据编辑弹窗 -->
<uni-popup ref="editPopup" type="center">
<view class="edit-popup-content">
<view class="popup-header">
<text class="title">编辑主页数据</text>
</view>
<scroll-view scroll-y class="popup-scroll">
<!-- <view class="form-item">
<text class="label">样式类型</text>
<input class="input" type="number" v-model="tempData.type" placeholder="1-4" />
</view> -->
<view class="form-item avatar-form-item">
<text class="label">头像</text>
<view class="avatar-uploader" @click="handleChooseAvatar">
<view class="avatar-preview" v-if="tempData.avatar">
<image class="preview-img" :src="tempData.avatar" mode="aspectFill"></image>
<view class="delete-icon">
<text>×</text>
</view>
</view>
<view class="upload-btn" v-else @click="handleChooseAvatar">
<text class="plus">+</text>
</view>
</view>
</view>
<view class="form-item">
<text class="label">昵称</text>
<input class="input" type="text" v-model="tempData.nickname" />
</view>
<view class="form-item">
<text class="label">性别</text>
<view class="radio-group">
<view class="radio-item" @click="tempData.gender = 'man'">
<view class="radio-icon" :class="{ active: tempData.gender === 'man' }"></view>
<text class="radio-text" :class="{ active: tempData.gender === 'man' }">男</text>
</view>
<view class="radio-item" @click="tempData.gender = 'female'">
<view class="radio-icon" :class="{ active: tempData.gender === 'female' }"></view>
<text class="radio-text" :class="{ active: tempData.gender === 'female' }">女</text>
</view>
</view>
</view>
<view class="form-item">
<text class="label">荣誉一</text>
<input class="input" type="text" v-model="tempData.honor1" />
</view>
<view class="form-item">
<text class="label">荣誉二</text>
<input class="input" type="text" v-model="tempData.honor2" />
</view>
<view class="form-item">
<text class="label">巅峰万强</text>
<input class="input" type="text" v-model="tempData.peakStrong" />
</view>
<view class="form-item">
<text class="label">收到花束</text>
<input class="input" type="text" v-model="tempData.receivedFlowers" />
</view>
<view class="form-item">
<text class="label">热度</text>
<input class="input" type="text" v-model="tempData.popularityCount" />
</view>
<view class="form-item">
<text class="label">点赞数</text>
<input class="input" type="text" v-model="tempData.likeCount" />
</view>
<view class="form-item">
<text class="label">对战场次</text>
<input class="input" type="number" v-model="tempData.matchCount" />
</view>
<view class="form-item">
<text class="label">对战被赞</text>
<input class="input" type="number" v-model="tempData.matchLikeCount" />
</view>
<view class="form-item">
<text class="label">图鉴等级</text>
<input class="input" type="number" v-model="tempData.pokedexLevel" />
</view>
<view class="form-item">
<text class="label">皮肤数</text>
<input class="input" type="number" v-model="tempData.skinCount" />
</view>
<view class="form-item">
<text class="label">游戏天数</text>
<input class="input" type="number" v-model="tempData.gameDays" />
</view>
<view class="form-item">
<text class="label">峡谷对战局数</text>
<input class="input" type="number" v-model="tempData.riftMatchCount" />
</view>
<view class="form-item">
<text class="label">大乱斗局数</text>
<input class="input" type="number" v-model="tempData.aramMatchCount" />
</view>
<view class="form-item">
<text class="label">亲密度</text>
<input class="input" type="number" v-model="tempData.intimacy" />
</view>
</scroll-view>
<view class="popup-footer">
<button class="cancel-btn" @click="closeEditPopup">取消</button>
<button class="confirm-btn" @click="confirmEdit">确定</button>
</view>
</view>
</uni-popup>
<!-- 隐藏的画布:用于原生绘制带有纯正渐变色的文本,再转成图片给到海报插件 -->
<view style="position: absolute; left: -9999px; top: -9999px; width: 0; height: 0; overflow: hidden;">
<canvas canvas-id="gradientTextCanvas" id="gradientTextCanvas" style="width: 300px; height: 60px;"></canvas>
</view>
<!-- 横向全屏放大预览层 -->
<view class="preview-overlay" v-if="showPreview" @click="showPreview = false">
<image class="preview-image" :src="finalPosterPath" mode="aspectFill"></image>
</view>
</view>
</template>
<script setup>
import { ref, reactive, onMounted, getCurrentInstance } from 'vue'
import { onLoad, onReady, onUnload } from '@dcloudio/uni-app'
import { imgLocal } from '@/utils/common.js';
const rightButtonText = ref("编辑");
const instance = getCurrentInstance();
const { proxy } = getCurrentInstance();
const nicknameImage = ref('');
const showPreview = ref(false); // 控制全屏预览
const isMountedReady = ref(false);
const isFontLoaded = ref(false);
const headerImage = ref('')
// 获取系统宽度计算海报缩放比例,代替容易失效的 css calc(),确保全端兼容防截断
const sysInfo = uni.getSystemInfoSync();
const windowWidth = sysInfo.windowWidth || 375;
const posterScaleRatio = windowWidth / 750;
const posterContainerHeight = 342 * posterScaleRatio;
// 页面表单数据字段
const honorData = reactive({
type: 1, // 样式类型 1.2.3.4
avatar: '/static/image/shopping/pdd/avatars/avatars1.jpg', // 头像
nickname: '甜喵小贝', // 昵称
gender: 'female', // 性别
region: '重庆市两江新区', // 地区
honor1: '国服第一小乔', // 荣誉1
honor2: '888级大师收藏家·珍耀无双', // 荣誉2
matchCount: 58965, // 对战场次
matchLikeCount: 9746, // 对战被赞数
gameDays: 3127, // 游戏天数
pokedexLevel: 104, // 图鉴等级
skinCount: 444, // 皮肤数
riftMatchCount: 7368, // 峡谷对战局数
aramMatchCount: 5193, // 大乱斗对战局数
receivedFlowers: 520, // 收到花束
popularityCount: '6.0W', // 热度数
likeCount: 4883, // 点赞数
intimacy: '25147',//亲密度
peakStrong: '5632'
})
onLoad(async () => {
const cachedData = uni.getStorageSync('wangzheHonorData');
if (cachedData) {
Object.assign(honorData, cachedData);
}
headerImage.value = await imgLocal.getLocalImage(`other/game/wangzhe/avatar-frame-${honorData.type}.png`)
uni.$on('editFormPhoto', (info) => {
tempData.avatar = info;
// #ifndef H5
if (info && !info.startsWith('/static/')) {
newAvatars.value.push(info);
}
// #endif
});
// 进入页面埋点
proxy.$apiUserEvent('all', {
type: 'click',
key: 'wangzhe',
prefix: '.uni.other.',
value: "王者主页"
})
const config = uni.getStorageSync('config')
console.log("---config---", config);
let fontConfig = config.config['client.uniapp.font'];
try {
if (typeof fontConfig === 'string') {
fontConfig = JSON.parse(fontConfig);
}
} catch (e) {
console.error('字体配置解析失败', e);
}
console.log("字体地址信息", fontConfig?.wangzhe);
// Font loading logic
const fontUrl = fontConfig?.wangzhe;
const fontUrl2 = fontConfig?.wangzhe2;
let loadedCount = 0;
const totalFonts = 2;
const checkAllLoaded = () => {
loadedCount++;
if (loadedCount >= totalFonts) {
isFontLoaded.value = true;
if (isMountedReady.value) drawGradientText();
}
};
const loadSingleFont = (url, name, storageKey, onComplete) => {
if (!url) {
console.warn(`未获取到 ${name} 字体地址,回退使用系统默认字体`);
onComplete && onComplete();
return;
}
const doLoad = (path) => {
uni.loadFontFace({
family: name,
source: `url("${path}")`,
success() {
console.log(`${name} 字体加载成功`);
onComplete && onComplete();
},
fail(err) {
console.error(`${name} 字体加载失败`, err);
onComplete && onComplete();
}
});
};
// #ifdef H5
// H5 环境直接从 URL 加载字体
doLoad(url);
// #endif
// #ifndef H5
// 非 H5 环境使用下载和保存逻辑
const savedPath = uni.getStorageSync(storageKey);
if (savedPath) {
doLoad(savedPath);
} else {
uni.downloadFile({
url: url,
success: (res) => {
if (res.statusCode === 200) {
uni.saveFile({
tempFilePath: res.tempFilePath,
success: (saveRes) => {
const saved = saveRes.savedFilePath;
uni.setStorageSync(storageKey, saved);
console.log(`${name} 字体保存路径`, saved);
doLoad(saved);
},
fail: (err) => {
console.error(`保存 ${name} 文件失败`, err);
// Fallback: 尝试加载临时路径
doLoad(res.tempFilePath);
}
});
} else {
onComplete && onComplete();
}
},
fail: (err) => {
console.error(`下载 ${name} 字体失败`, err);
onComplete && onComplete();
}
});
}
// #endif
};
loadSingleFont(fontUrl, 'WangZheFont', 'wangzhe_font_path', checkAllLoaded);
loadSingleFont(fontUrl2, 'WangZheFont2', 'wangzhe_font2_path', checkAllLoaded);
})
onUnload(() => {
uni.$off('editFormPhoto');
})
const finalPosterPath = ref('');
const editPopup = ref(null);
const tempData = reactive({});
const newAvatars = ref([]);
async function handleChangeTheme(typeIndex) {
// 切换主题时,先清空旧海报,触发界面“加载中”提示
finalPosterPath.value = '';
honorData.type = typeIndex;
uni.setStorageSync('wangzheHonorData', honorData);
headerImage.value = await imgLocal.getLocalImage(`other/game/wangzhe/avatar-frame-${typeIndex}.png`)
}
function onRightClick() {
Object.assign(tempData, honorData);
newAvatars.value = [];
editPopup.value.open();
}
const handleChooseAvatar = () => {
uni.chooseImage({
count: 1,
success: (res) => {
uni.navigateTo({
url: `/pages/other/qf-image/qf-image?src=${res.tempFilePaths[0]}&width=200&height=200`
});
}
});
};
const handleDeleteAvatar = () => {
tempData.avatar = '';
};
function closeEditPopup() {
// #ifndef H5
newAvatars.value.forEach(path => {
uni.removeSavedFile({ filePath: path });
});
// #endif
editPopup.value.close();
}
function confirmEdit() {
const isNicknameChanged = honorData.nickname !== tempData.nickname;
// #ifndef H5
// 删除多余的新上传文件
newAvatars.value.forEach(path => {
if (path !== tempData.avatar) {
uni.removeSavedFile({ filePath: path });
}
});
// 删除被替换掉的原本地头像
if (honorData.avatar !== tempData.avatar && honorData.avatar && !honorData.avatar.startsWith('/static/')) {
uni.removeSavedFile({ filePath: honorData.avatar });
}
// #endif
Object.assign(honorData, tempData);
uni.setStorageSync('wangzheHonorData', honorData);
editPopup.value.close();
// 数据修改后,清空旧海报触发“加载中”提示,等待重新生成
finalPosterPath.value = '';
// 如果昵称改变了,需要重新绘制原生的渐变文字
if (isNicknameChanged && isFontLoaded.value) {
drawGradientText();
}
}
// 点击画布触发横向预览
const handlePreview = () => {
if (!finalPosterPath.value) {
uni.showToast({ title: '海报仍在生成中,请稍候', icon: 'none' });
return;
}
showPreview.value = true;
}
// 画布生成成功后触发,保存最终的海报路径
const onPainterSuccess = (path) => {
finalPosterPath.value = path;
}
// 点击保存按钮,将带有渐变字体的最终图片存入相册
const handleSave = () => {
if (!finalPosterPath.value) {
uni.showToast({ title: '图片仍在生成中,请稍候', icon: 'none' })
return
}
uni.saveImageToPhotosAlbum({
filePath: finalPosterPath.value,
success: () => {
uni.showToast({ title: '保存成功', icon: 'success' })
},
fail: () => {
uni.showToast({ title: '保存失败', icon: 'none' })
}
})
}
onMounted(() => {
isMountedReady.value = true;
if (isFontLoaded.value) {
drawGradientText();
}
})
// 原生 Canvas 绘制渐变文本导出为图片
const drawGradientText = () => {
// 以较大的像素绘制以保证生成的图片清晰
const fontSize = 40;
const textLen = honorData.nickname.length;
// 文本的实际像素宽度
const canvasWidth = fontSize * textLen;
const ctx = uni.createCanvasContext('gradientTextCanvas', instance.proxy);
ctx.clearRect(0, 0, 300, 60);
// 设置粗体与字号,使用指定的 WangZheFont 字体
ctx.font = `${fontSize}px "WangZheFont", sans-serif`;
// 原生创建横向线性渐变
const grd = ctx.createLinearGradient(0, 0, canvasWidth, 0);
grd.addColorStop(0, '#F9D577');
grd.addColorStop(1, '#FFF5C4');
ctx.setFillStyle(grd);
ctx.setTextBaseline('top');
// 绘制文字
ctx.fillText(honorData.nickname, 0, 0);
ctx.draw(false, () => {
// 稍微延迟确保绘制完毕
setTimeout(() => {
uni.canvasToTempFilePath({
canvasId: 'gradientTextCanvas',
x: 0,
y: 0,
width: canvasWidth,
height: fontSize + 10,
destWidth: canvasWidth * 2, // 2倍图保证海报中的清晰度
destHeight: (fontSize + 10) * 2,
success: (res) => {
nicknameImage.value = res.tempFilePath;
}
}, instance.proxy)
}, 200)
})
}
</script>
<style lang="less" scoped>
.preview-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #000;
z-index: 99999;
/* 抛弃 flex 布局,防止 width: 100vh 被父级限制而发生坍缩 */
}
.preview-overlay .preview-image {
position: absolute;
top: 50%;
left: 50%;
width: 100vh;
height: 100vw;
/* 先拉回自身中心点,再围绕中心点旋转 90 度 */
transform: translate(-50%, -50%) rotate(90deg);
}
.honor-of-kings {
width: 100%;
overflow-x: hidden;
}
.painter-container {
/* 强制画板缩放到设备屏幕宽度,避免物理 px 超出屏幕 */
padding: 8px;
zoom: calc(100vw / 798);
width: 100%;
max-width: 1000px;
/* 限制PC/iPad端的最大宽度 */
margin: 0 auto;
}
.save-action {
margin-top: 60rpx;
display: flex;
flex-direction: column;
align-items: center;
padding-bottom: 220rpx;
/* 留出底部主题固定栏的安全空间 */
.save-btn {
margin-top: 60rpx;
width: 316rpx;
background: #1777FF;
color: #fff;
border-radius: 56rpx;
}
}
.theme-selector {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
padding-top: 20rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
z-index: 99;
.theme-scroll {
width: 100%;
white-space: nowrap;
::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
}
.theme-list {
display: inline-block;
padding: 0 20rpx;
.theme-item {
display: inline-flex;
vertical-align: top;
margin: 0 10rpx;
padding: 0 40rpx;
height: 84rpx;
border-radius: 12rpx;
background-color: #FFFFFF;
justify-content: center;
align-items: center;
border: 2rpx solid transparent;
box-sizing: border-box;
transition: all 0.3s;
&.active {
background-color: #fff;
border-color: #3B7BFF;
.theme-text {
color: #3B7BFF;
font-weight: bold;
}
}
.theme-text {
font-size: 28rpx;
color: #666;
}
}
}
}
.edit-popup-content {
background-color: #fff;
border-radius: 20rpx;
width: 85vw;
.popup-header {
display: flex;
justify-content: center;
align-items: center;
padding: 40rpx 0 20rpx 0;
.title {
font-size: 34rpx;
font-weight: bold;
color: #333;
}
}
.popup-scroll {
height: 55vh;
padding: 20rpx 40rpx;
box-sizing: border-box;
}
.form-item {
display: flex;
align-items: center;
margin-bottom: 16rpx;
padding-bottom: 16rpx;
.label {
width: 200rpx;
font-size: 28rpx;
color: #333;
}
.avatar-uploader {
display: flex;
align-items: center;
.upload-btn {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
background-color: #999DA7;
display: flex;
justify-content: center;
align-items: center;
.plus {
font-size: 60rpx;
color: #fff;
font-weight: 300;
margin-top: -6rpx;
}
}
.avatar-preview {
position: relative;
width: 100rpx;
height: 100rpx;
.preview-img {
width: 100%;
height: 100%;
border-radius: 50%;
}
.delete-icon {
position: absolute;
top: -4rpx;
right: -4rpx;
width: 32rpx;
height: 32rpx;
background-color: red;
color: #fff;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
font-size: 30rpx;
line-height: 28rpx;
}
}
}
.input {
flex: 1;
font-size: 28rpx;
background-color: #F7F7F7;
border-radius: 12rpx;
height: 70rpx;
line-height: 70rpx;
padding: 0 20rpx;
color: #333;
}
.radio-group {
display: flex;
align-items: center;
gap: 40rpx;
.radio-item {
display: flex;
align-items: center;
cursor: pointer;
.radio-icon {
width: 28rpx;
height: 28rpx;
border-radius: 50%;
border: 2rpx solid #d9d9d9;
margin-right: 12rpx;
display: flex;
justify-content: center;
align-items: center;
box-sizing: border-box;
&.active {
border-color: #3B7BFF;
&::after {
content: '';
width: 14rpx;
height: 14rpx;
border-radius: 50%;
background-color: #3B7BFF;
}
}
}
.radio-text {
font-size: 28rpx;
color: #666;
&.active {
color: #3B7BFF;
}
}
}
}
}
.popup-footer {
display: flex;
justify-content: space-between;
padding: 20rpx 40rpx 40rpx;
button {
width: 46%;
height: 76rpx;
line-height: 76rpx;
font-size: 30rpx;
border-radius: 12rpx;
margin: 0;
&::after {
border: none;
}
}
.cancel-btn {
background-color: #F4F4F4;
color: #666;
}
.confirm-btn {
background-color: #3B7BFF;
color: #fff;
}
}
}
</style>

150
pages/other/game/index.vue Normal file
View File

@ -0,0 +1,150 @@
<template>
<view class="container">
<nav-bar title="游戏平台" bgColor="#F0F4F9"></nav-bar>
<view class="menu-container">
<view class="card" v-for="(item, index) in menuList" :key="index" style=" background: #ffffff ">
<text class="card-title">{{ item.name }}</text>
<view class="icon-wrapper">
<view class="icon-shadow" :style="{ background: item.shadowColor }"></view>
<image class="icon-img" :src="`/static/image/other/game/${item.icon}.png`" mode="aspectFit"></image>
</view>
<view class="btn" style="background: linear-gradient( 327deg, #1777FF 0%, #17CDFF 100%);"
@click="goTo(item.url)">立即进入</view>
</view>
</view>
</view>
</template>
<script setup>
import {
ref,
getCurrentInstance
} from 'vue';
import {
onLoad
} from '@dcloudio/uni-app'
const {
proxy
} = getCurrentInstance();
onLoad(() => {
proxy.$apiUserEvent('all', {
type: 'event',
key: 'game',
prefix: '.uni.other.',
value: '游戏'
})
})
const menuList = ref([{
name: "王者荣耀",
icon: "wangzhe",
shadowColor: "#FF604F",
url: "/pages/other/game/honor-of-kings"
}, {
name: "三角洲",
icon: "sanjiaozhou",
shadowColor: "#12C96A",
url: "/pages/other/game/sanjiaozhou"
}, {
name: "和平精英",
icon: "hepingjingying",
shadowColor: "#4992F2",
url: "/pages/other/game/hepingjingying"
}, {
name: "无畏契约",
icon: "wuweiqiyue",
shadowColor: "#FF3333",
url: "/pages/other/game/wuweiqiyue"
}]);
const goTo = (url) => {
if (url) {
uni.navigateTo({
url: url
})
} else {
uni.showToast({
title: "开发中",
icon: "none"
})
}
}
</script>
<style>
page {
background-color: #F0F4F9;
}
</style>
<style lang="less" scoped>
.container {
min-height: 100vh;
}
.menu-container {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
padding: 24rpx;
}
.card {
width: calc(50% - 14rpx);
border-radius: 28rpx 28rpx 28rpx 28rpx;
margin-bottom: 24rpx;
display: flex;
flex-direction: column;
align-items: center;
padding-top: 48rpx;
padding-bottom: 40rpx;
box-sizing: border-box;
border: 1px solid #ffffff;
}
.card-title {
font-size: 28rpx;
color: #1A1A1A;
font-weight: 500;
}
.icon-wrapper {
margin-top: 40rpx;
margin-bottom: 62rpx;
position: relative;
width: 116rpx;
height: 116rpx;
display: flex;
justify-content: center;
align-items: center;
}
.icon-img {
width: 116rpx;
height: 116rpx;
z-index: 2;
}
.icon-shadow {
position: absolute;
bottom: -10rpx;
width: 100rpx;
height: 8rpx;
border-radius: 50%;
filter: blur(5px);
z-index: 1;
opacity: 0.6;
}
.btn {
padding: 0 20rpx;
height: 56rpx;
line-height: 56rpx;
border-radius: 16rpx 16rpx 16rpx 16rpx;
color: #fff;
font-size: 24rpx;
text-align: center;
font-weight: 500;
}
</style>

View File

@ -0,0 +1,819 @@
<template>
<view class="honor-of-kings">
<nav-bar title="三角洲结算页" bgColor="#F5F5F5" isRightButton :rightButtonText="rightButtonText"
@right-click="onRightClick">
</nav-bar>
<view style="padding: 8px;box-sizing: border-box;">
<view class="painter-container" @click="handlePreview"
:style="`width:calc(100vw - 16px) ; height: ${posterContainerHeight}px; overflow: hidden; position: relative; transform: translateZ(0);`">
<!-- 运用 js 算出的无误差纯数字 scale 来实现高清画板的等比缩小,彻底兼容所有老旧内核,不再被截断 -->
<view
:style="`width: 750px; transform: scale(${posterScaleRatio}); transform-origin: left top; position: absolute; left: 0; top: 0;`">
<l-painter isCanvasToTempFilePath @success="onPainterSuccess" :css="`width:750px;`">
<l-painter-view :css="`width: 750.0px; position: relative;`">
<!-- 直接使用主背景图撑开父容器,移除不必要的 opacity: 0 占位图,避免在大屏渲染时产生黑块 -->
<l-painter-image :src="`/static/image/other/game/sanjiaozhou/style-${data.type}.jpg`"
css="width: 750.0px; display: block;"></l-painter-image>
<!-- 顶部信息栏:绝对定位,避免鸿蒙 inline-block 竖排 -->
<!-- 地图 -->
<l-painter-image src="/static/image/other/game/sanjiaozhou/line.png"
css="position:absolute;left:73px;top:113px;height:22px;width:1px;"></l-painter-image>
<l-painter-text
css="position:absolute;left:78px;top:113px;color:#CDCDCD;font-size:6px;line-height:1em;"
:text="data.map.level"></l-painter-text>
<l-painter-text
css="position:absolute;left:78px;top:123px;color:#CDCDCD;font-size:10px;line-height:1em;"
:text="data.map.name"></l-painter-text>
<!-- 撤离点 -->
<l-painter-image src="/static/image/other/game/sanjiaozhou/line.png"
css="position:absolute;left:155px;top:113px;height:22px;width:1px;"></l-painter-image>
<l-painter-text
css="position:absolute;left:160px;top:113px;color:#CDCDCD;font-size:6px;line-height:1em;"
text="撤离点"></l-painter-text>
<l-painter-text
css="position:absolute;left:160px;top:123px;color:#CDCDCD;font-size:10px;line-height:1em;"
:text="data.extractionPoint"></l-painter-text>
<!-- 时长 -->
<l-painter-image src="/static/image/other/game/sanjiaozhou/line.png"
css="position:absolute;left:237px;top:113px;height:22px;width:1px;"></l-painter-image>
<l-painter-text
css="position:absolute;left:242px;top:113px;color:#CDCDCD;font-size:6px;line-height:1em;"
text="时长"></l-painter-text>
<l-painter-text v-for="(item, idx) in durationChars" :key="'durationNum_' + idx"
:css="item.css" :text="item.text"></l-painter-text>
<!-- 对局时间 -->
<l-painter-image src="/static/image/other/game/sanjiaozhou/line.png"
css="position:absolute;left:319px;top:113px;height:22px;width:1px;"></l-painter-image>
<l-painter-text
css="position:absolute;left:324px;top:113px;color:#CDCDCD;font-size:6px;line-height:1em;"
text="对局时间"></l-painter-text>
<l-painter-text v-for="(item, idx) in matchTimeChars" :key="'matchTimeNum_' + idx"
:css="item.css" :text="item.text"></l-painter-text>
<!-- 本局收获 -->
<l-painter-text v-for="(item, idx) in harvestChars" :key="'harvestNum_' + idx"
:css="item.css" :text="item.text"></l-painter-text>
<!-- 击败干员 -->
<l-painter-text v-for="(item, idx) in defeatChars" :key="'bigNum_' + idx"
:css="item.css" :text="item.text"></l-painter-text>
<!-- 高光击败 -->
<template v-for="(item, index) in data.defeatInfo" :key="'kill_' + index">
<l-painter-image src="/static/image/other/game/sanjiaozhou/kill.png"
:css="`position:absolute;left:73px;top:${295 + index * 16}px;width:12px;height:12px;`"></l-painter-image>
<l-painter-text
:css="`position:absolute;left:87px;top:${297 + index * 16}px;color:#DBE5E6;font-size:9px;line-height:1em;`"
:text="`${item.name} [${item.rank}]`"></l-painter-text>
</template>
<!-- 玩家名称(鸿蒙不支持 rotate,保持位置) -->
<l-painter-text
css="color:#CDCDCD;font-size:10px;line-height:1em;position:absolute;left:424px;top:310px;transform: rotate(12deg);"
:text="data.name"></l-painter-text>
<l-painter-text
css="color:#CDCDCD;font-size:7px;line-height:1em;position:absolute;left:422px;top:321px;opacity:0.6;transform: rotate(12deg);"
text="信守不渝"></l-painter-text>
<!-- 水印 -->
<l-painter-image v-if="$isVip()" src="/static/image/other/shuiying.png"
:css="`position: absolute; left: 18.8px; bottom: 17.1px; width: 145.38px;height:42.76px`"></l-painter-image>
</l-painter-view>
</l-painter>
</view>
</view>
</view>
<!-- 水印 -->
<view v-if="$isVip()">
<liu-drag-button :canDocking="false"
@clickBtn="$goRechargePage('watermark', 'uni_alipay_other_sanjiaozhou')">
<image src='/static/lottie/watermark.gif' mode="widthFix" style="width:94px;height:74px;" ></image>
</liu-drag-button>
</view>
<view class="save-action">
<button class="save-btn" @click="handleSave">保存</button>
</view>
<!-- 主题选择区域 -->
<view class="theme-selector" :style="{'margin-bottom':$store.state.safeBottomAz+'px'}">
<scroll-view scroll-x="true" class="theme-scroll" :show-scrollbar="false">
<view class="theme-list">
<view class="theme-item" v-for="(item, index) in ['样式一', '样式二', '样式三', '样式四']" :key="index + 1"
:class="{ active: data.type == index + 1 }" @click="handleChangeTheme(index + 1)">
<text class="theme-text">{{ item }}</text>
</view>
</view>
</scroll-view>
</view>
<!-- 数据编辑弹窗 -->
<uni-popup ref="editPopup" type="center">
<view class="edit-popup-content">
<view class="popup-header">
<text class="title">编辑主页数据</text>
</view>
<scroll-view scroll-y class="popup-scroll">
<view class="form-item">
<text class="label">玩家名称</text>
<input class="input" type="text" v-model="tempData.name" />
</view>
<view class="form-item">
<text class="label">地图级别</text>
<view class="button-group">
<view class="button-item" :class="{ active: tempData.map.level === '普通' }"
@click="tempData.map.level = '普通'">普通</view>
<view class="button-item" :class="{ active: tempData.map.level === '机密' }"
@click="tempData.map.level = '机密'">机密</view>
<view class="button-item" :class="{ active: tempData.map.level === '绝密' }"
@click="tempData.map.level = '绝密'">绝密</view>
</view>
</view>
<view class="form-item">
<text class="label">地图名称</text>
<input class="input" type="text" v-model="tempData.map.name" />
</view>
<view class="form-item">
<text class="label">撤离点</text>
<input class="input" type="text" v-model="tempData.extractionPoint" />
</view>
<view class="form-item">
<text class="label">对局时间</text>
<picker mode="multiSelector" :range="matchTimeRange" :value="matchTimeIndex"
@change="onMatchTimeChange" class="input"
style="height: 36px; line-height: 36px; display: flex; align-items: center;">
<text>{{ tempData.matchTime || '请选择时间' }}</text>
</picker>
</view>
<view class="form-item">
<text class="label">时长</text>
<picker mode="multiSelector" :range="durationRange" :value="durationIndex"
@change="onDurationChange" class="input"
style="height: 36px; line-height: 36px; display: flex; align-items: center;">
<text>{{ tempData.duration || '请选择时长' }}</text>
</picker>
</view>
<view class="form-item">
<text class="label">本局收获</text>
<input class="input" type="text" v-model="tempData.harvest" />
</view>
<view class="form-item">
<text class="label">击败干员</text>
<input class="input" type="text" v-model="tempData.defeat" @input="onDefeatChange" />
</view>
<view style="border: 1px dashed #007aff;padding: 10px;border-radius: 8px;"
v-if="tempData.defeatInfo && tempData.defeatInfo.length > 0">
<view v-for="(item, index) in tempData.defeatInfo" :key="'defeat_' + index"
style="margin-bottom: 10px;">
<view
style="display: flex; justify-content: flex-end; gap: 15px; margin-bottom: 5px; font-size: 12px; height: 16px;"
v-if="tempData.defeatInfo.length > 1">
<text v-if="index > 0" @click="moveDefeatUp(index)"
style="color: #007aff; cursor: pointer;">↑ 上移</text>
<text v-if="index < tempData.defeatInfo.length - 1" @click="moveDefeatDown(index)"
style="color: #007aff; cursor: pointer;">↓ 下移</text>
</view>
<view class="form-item">
<text class="label">被击败者{{ index + 1 }}昵称</text>
<input class="input" type="text" v-model="item.name" />
</view>
<view class="form-item" style="margin-bottom: 0;">
<text class="label">被击败者{{ index + 1 }}段位</text>
<input class="input" type="text" v-model="item.rank" />
</view>
</view>
</view>
</scroll-view>
<view class="popup-footer">
<button class="cancel-btn" @click="closeEditPopup">取消</button>
<button class="confirm-btn" @click="confirmEdit">确定</button>
</view>
</view>
</uni-popup>
<!-- 横向全屏放大预览层 -->
<view class="preview-overlay" v-if="showPreview" @click="showPreview = false">
<image class="preview-image" :src="finalPosterPath" mode="aspectFit"></image>
</view>
</view>
</template>
<script setup>
import { ref, reactive, computed, onMounted, getCurrentInstance } from 'vue'
import { onLoad, onUnload } from '@dcloudio/uni-app'
const rightButtonText = ref("编辑");
const { proxy } = getCurrentInstance();
const showPreview = ref(false); // 控制全屏预览
const isMountedReady = ref(false);
// 获取系统宽度计算海报缩放比例,代替容易失效的 css calc(),确保全端兼容防截断
const sysInfo = uni.getSystemInfoSync();
const windowWidth = sysInfo.windowWidth || 375;
const posterScaleRatio = windowWidth / 750;
const posterContainerHeight = 430 * posterScaleRatio;
// 页面表单数据字段
const data = reactive({
type: 1, // 样式类型 1.2.3.4
name: '大坝猛攻哥',
map: {
name: '零号大坝',
level: '机密'
},
extractionPoint: '工业电梯',
duration: "00:25:19",
matchTime: '02-32 21:02',
harvest: '13526559',
defeat: '1',
defeatInfo: [
{
name: '大坝贪吃鼠',
rank: "黑鹰II"
},
]
})
// 数字到 my-icon iconfont Unicode 的映射字典
const digitIconMap = {
'0': '\ue629', '1': '\ue627', '2': '\ue62b', '3': '\ue62c', '4': '\ue62f',
'5': '\ue62d', '6': '\ue628', '7': '\ue625', '8': '\ue62a', '9': '\ue62e',
',': '\ue626', ':': '\ue630',
};
const getDefeatIconText = (defeatStr) => {
if (!defeatStr && defeatStr !== 0) return '';
return String(defeatStr).split('').map(char => digitIconMap[char] || char).join('');
};
/** 时长/对局时间:按步进绝对定位(鸿蒙 inline-block 不横排) */
function layoutTimeChars(str, startLeft, startTop) {
let x = startLeft
return String(str || '').split('').map((char) => {
const fontSize = char === '-' ? 12 : (char === ':' ? 4 : 10)
const step = char === ':' ? 4 : (char === '-' ? 10 : (char === ' ' ? 6 : 8))
const left = x
x += step
return {
text: getDefeatIconText(char),
css: `position:absolute;left:${left}px;top:${startTop}px;color:#CDCDCD;font-size:${fontSize}px;line-height:1em;font-family:myIconFont;`
}
})
}
/** 本局收获数字 */
function layoutHarvestChars(str, startLeft, startTop) {
const formatted = String(str || '').replace(/\B(?=(\d{3})+(?!\d))/g, ',')
let x = startLeft
return formatted.split('').map((char) => {
const fontSize = char === ',' ? 6 : 22
const step = char === ',' ? 8 : 19
const top = char === ',' ? startTop + 3 : startTop
const left = x
x += step
return {
text: getDefeatIconText(char),
css: `position:absolute;left:${left}px;top:${top}px;color:#DBE5E6;font-size:${fontSize}px;line-height:1em;font-family:myIconFont;`
}
})
}
/** 击败干员数字 */
function layoutDefeatChars(str, startLeft, startTop) {
let x = startLeft
return String(str || '').split('').map((char) => {
const left = x
x += 20
return {
text: getDefeatIconText(char),
css: `position:absolute;left:${left}px;top:${startTop}px;color:#DBE5E6;font-size:22px;line-height:1em;font-family:myIconFont;`
}
})
}
const durationChars = computed(() => layoutTimeChars(data.duration, 242, 123))
const matchTimeChars = computed(() => layoutTimeChars(data.matchTime, 324, 123))
const harvestChars = computed(() => layoutHarvestChars(data.harvest, 73, 182))
const defeatChars = computed(() => layoutDefeatChars(data.defeat, 73, 250))
onLoad(() => {
const cachedData = uni.getStorageSync('sanjiaozhouData');
if (cachedData) {
Object.assign(data, cachedData);
}
uni.$on('editFormPhoto', (info) => {
tempData.avatar = info;
// #ifndef H5
if (info && !info.startsWith('/static/')) {
newAvatars.value.push(info);
}
// #endif
});
// 进入页面埋点
proxy.$apiUserEvent('all', {
type: 'click',
key: 'sanjiaozhou',
prefix: '.uni.other.',
value: "三角洲结算页"
})
// 加载海报需要用到的自定义数字字体
uni.loadFontFace({
family: 'myIconFont',
source: `url("/static/font/iconfont.ttf")`,
success() {
console.log('myIconFont 字体加载成功');
},
fail(err) {
console.error('myIconFont 字体加载失败', err);
}
});
});
onUnload(() => {
uni.$off('editFormPhoto');
})
const finalPosterPath = ref('');
const editPopup = ref(null);
const tempData = reactive({
map: {},
defeatInfo: [{}]
});
const newAvatars = ref([]);
const onDefeatChange = (e) => {
const val = parseInt(e.detail.value) || 0;
const maxAllowed = Math.min(3, Math.max(0, val));
if (tempData.defeatInfo.length < maxAllowed) {
const toAdd = maxAllowed - tempData.defeatInfo.length;
for (let i = 0; i < toAdd; i++) {
tempData.defeatInfo.push({ name: '', rank: '' });
}
} else if (tempData.defeatInfo.length > maxAllowed) {
tempData.defeatInfo.splice(maxAllowed);
}
};
const moveDefeatUp = (index) => {
if (index > 0) {
const item = tempData.defeatInfo.splice(index, 1)[0];
tempData.defeatInfo.splice(index - 1, 0, item);
}
};
const moveDefeatDown = (index) => {
if (index < tempData.defeatInfo.length - 1) {
const item = tempData.defeatInfo.splice(index, 1)[0];
tempData.defeatInfo.splice(index + 1, 0, item);
}
};
// 时长选择器数据
const durationRange = ref([
Array.from({ length: 100 }, (_, i) => (i < 10 ? '0' + i : i + '')),
Array.from({ length: 60 }, (_, i) => (i < 10 ? '0' + i : i + '')),
Array.from({ length: 60 }, (_, i) => (i < 10 ? '0' + i : i + ''))
]);
const durationIndex = ref([0, 0, 0]);
const onDurationChange = (e) => {
const val = e.detail.value;
durationIndex.value = val;
tempData.duration = `${durationRange.value[0][val[0]]}:${durationRange.value[1][val[1]]}:${durationRange.value[2][val[2]]}`;
};
// 对局时间选择器数据
const matchTimeRange = ref([
Array.from({ length: 12 }, (_, i) => (i + 1 < 10 ? '0' + (i + 1) : (i + 1) + '')),
Array.from({ length: 31 }, (_, i) => (i + 1 < 10 ? '0' + (i + 1) : (i + 1) + '')),
Array.from({ length: 24 }, (_, i) => (i < 10 ? '0' + i : i + '')),
Array.from({ length: 60 }, (_, i) => (i < 10 ? '0' + i : i + ''))
]);
const matchTimeIndex = ref([0, 0, 0, 0]);
const onMatchTimeChange = (e) => {
const val = e.detail.value;
matchTimeIndex.value = val;
tempData.matchTime = `${matchTimeRange.value[0][val[0]]}-${matchTimeRange.value[1][val[1]]} ${matchTimeRange.value[2][val[2]]}:${matchTimeRange.value[3][val[3]]}`;
};
const initPickerIndexes = () => {
if (tempData.duration) {
const parts = tempData.duration.split(':');
if (parts.length === 3) {
durationIndex.value = [parseInt(parts[0]) || 0, parseInt(parts[1]) || 0, parseInt(parts[2]) || 0];
}
}
if (tempData.matchTime) {
const spaceSplit = tempData.matchTime.split(' ');
if (spaceSplit.length === 2) {
const dateParts = spaceSplit[0].split('-');
const timeParts = spaceSplit[1].split(':');
if (dateParts.length === 2 && timeParts.length === 2) {
matchTimeIndex.value = [(parseInt(dateParts[0]) || 1) - 1, (parseInt(dateParts[1]) || 1) - 1, parseInt(timeParts[0]) || 0, parseInt(timeParts[1]) || 0];
}
}
}
};
function handleChangeTheme(typeIndex) {
// 切换主题时,先清空旧海报,触发界面“加载中”提示
finalPosterPath.value = '';
data.type = typeIndex;
uni.setStorageSync('sanjiaozhouData', data);
}
function onRightClick() {
Object.assign(tempData, JSON.parse(JSON.stringify(data)));
initPickerIndexes();
newAvatars.value = [];
editPopup.value.open();
}
function closeEditPopup() {
// #ifndef H5
newAvatars.value.forEach(path => {
uni.removeSavedFile({ filePath: path });
});
// #endif
editPopup.value.close();
}
function confirmEdit() {
// #ifndef H5
// 删除多余的新上传文件
newAvatars.value.forEach(path => {
if (path !== tempData.avatar) {
uni.removeSavedFile({ filePath: path });
}
});
// 删除被替换掉的原本地头像
if (data.avatar !== tempData.avatar && data.avatar && !data.avatar.startsWith('/static/')) {
uni.removeSavedFile({ filePath: data.avatar });
}
// #endif
Object.assign(data, tempData);
uni.setStorageSync('sanjiaozhouData', data);
editPopup.value.close();
// 数据修改后,清空旧海报触发“加载中”提示,等待重新生成
finalPosterPath.value = '';
}
// 点击画布触发横向预览
const handlePreview = () => {
if (!finalPosterPath.value) {
uni.showToast({ title: '海报仍在生成中,请稍候', icon: 'none' });
return;
}
showPreview.value = true;
}
// 画布生成成功后触发,保存最终的海报路径
const onPainterSuccess = (path) => {
finalPosterPath.value = path;
}
// 点击保存按钮,将带有渐变字体的最终图片存入相册
const handleSave = () => {
if (!finalPosterPath.value) {
uni.showToast({ title: '图片仍在生成中,请稍候', icon: 'none' })
return
}
uni.saveImageToPhotosAlbum({
filePath: finalPosterPath.value,
success: () => {
uni.showToast({ title: '保存成功', icon: 'success' })
},
fail: () => {
uni.showToast({ title: '保存失败', icon: 'none' })
}
})
}
onMounted(() => {
isMountedReady.value = true;
})
</script>
<style lang="less" scoped>
.preview-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #000;
z-index: 99999;
/* 抛弃 flex 布局,防止 width: 100vh 被父级限制而发生坍缩 */
}
.preview-overlay .preview-image {
position: absolute;
top: 50%;
left: 50%;
width: 100vh;
height: 100vw;
/* 先拉回自身中心点,再围绕中心点旋转 90 度 */
transform: translate(-50%, -50%) rotate(90deg);
}
.honor-of-kings {
width: 100%;
overflow-x: hidden;
}
.painter-container {
/* 强制画板缩放到设备屏幕宽度,避免物理 px 超出屏幕 */
padding: 8px;
zoom: calc(100vw / 798);
width: 100%;
max-width: 1000px;
/* 限制PC/iPad端的最大宽度 */
margin: 0 auto;
}
.save-action {
margin-top: 60rpx;
display: flex;
flex-direction: column;
align-items: center;
padding-bottom: 220rpx;
/* 留出底部主题固定栏的安全空间 */
.save-btn {
margin-top: 60rpx;
width: 316rpx;
background: #1777FF;
color: #fff;
border-radius: 56rpx;
}
}
.theme-selector {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
padding-top: 20rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
z-index: 99;
.theme-scroll {
width: 100%;
white-space: nowrap;
::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
}
.theme-list {
display: inline-block;
padding: 0 20rpx;
.theme-item {
display: inline-flex;
vertical-align: top;
margin: 0 10rpx;
padding: 0 40rpx;
height: 84rpx;
border-radius: 12rpx;
background-color: #FFFFFF;
justify-content: center;
align-items: center;
border: 2rpx solid transparent;
box-sizing: border-box;
transition: all 0.3s;
&.active {
background-color: #fff;
border-color: #3B7BFF;
.theme-text {
color: #3B7BFF;
font-weight: bold;
}
}
.theme-text {
font-size: 28rpx;
color: #666;
}
}
}
}
.edit-popup-content {
background-color: #fff;
border-radius: 20rpx;
width: 85vw;
.popup-header {
display: flex;
justify-content: center;
align-items: center;
padding: 40rpx 0 20rpx 0;
.title {
font-size: 34rpx;
font-weight: bold;
color: #333;
}
}
.popup-scroll {
height: 55vh;
padding: 20rpx 40rpx;
box-sizing: border-box;
}
.form-item {
display: flex;
align-items: center;
margin-bottom: 16rpx;
padding-bottom: 16rpx;
.label {
width: 200rpx;
font-size: 28rpx;
color: #333;
}
.avatar-uploader {
display: flex;
align-items: center;
.upload-btn {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
background-color: #999DA7;
display: flex;
justify-content: center;
align-items: center;
.plus {
font-size: 60rpx;
color: #fff;
font-weight: 300;
margin-top: -6rpx;
}
}
.avatar-preview {
position: relative;
width: 100rpx;
height: 100rpx;
.preview-img {
width: 100%;
height: 100%;
border-radius: 50%;
}
.delete-icon {
position: absolute;
top: -4rpx;
right: -4rpx;
width: 32rpx;
height: 32rpx;
background-color: red;
color: #fff;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
font-size: 30rpx;
line-height: 28rpx;
}
}
}
.input {
flex: 1;
font-size: 28rpx;
background-color: #F7F7F7;
border-radius: 12rpx;
height: 70rpx;
line-height: 70rpx;
padding: 0 20rpx;
color: #333;
}
.radio-group {
display: flex;
align-items: center;
gap: 40rpx;
.radio-item {
display: flex;
align-items: center;
cursor: pointer;
.radio-icon {
width: 28rpx;
height: 28rpx;
border-radius: 50%;
border: 2rpx solid #d9d9d9;
margin-right: 12rpx;
display: flex;
justify-content: center;
align-items: center;
box-sizing: border-box;
&.active {
border-color: #3B7BFF;
&::after {
content: '';
width: 14rpx;
height: 14rpx;
border-radius: 50%;
background-color: #3B7BFF;
}
}
}
.radio-text {
font-size: 28rpx;
color: #666;
&.active {
color: #3B7BFF;
}
}
}
}
.button-group {
display: flex;
align-items: center;
flex-wrap: nowrap;
.button-item {
padding: 4rpx 16rpx;
border-radius: 30rpx;
font-size: 26rpx;
margin-right: 16rpx;
color: #666;
background-color: #F7F7F7;
border: 2rpx solid transparent;
white-space: nowrap;
cursor: pointer;
transition: all 0.2s;
&.active {
color: #3B7BFF;
background-color: rgba(59, 123, 255, 0.1);
border-color: #3B7BFF;
}
}
}
}
.popup-footer {
display: flex;
justify-content: space-between;
padding: 20rpx 40rpx 40rpx;
button {
width: 46%;
height: 76rpx;
line-height: 76rpx;
font-size: 30rpx;
border-radius: 12rpx;
margin: 0;
&::after {
border: none;
}
}
.cancel-btn {
background-color: #F4F4F4;
color: #666;
}
.confirm-btn {
background-color: #3B7BFF;
color: #fff;
}
}
}
</style>

View File

@ -0,0 +1,727 @@
<template>
<view class="honor-of-kings">
<nav-bar title="无畏契约" bgColor="#F5F5F5" isRightButton :rightButtonText="rightButtonText"
@right-click="onRightClick">
</nav-bar>
<view style="padding: 8px;box-sizing: border-box;">
<view class="painter-container" @click="handlePreview"
:style="`width:calc(100vw - 16px) ; height: ${posterContainerHeight}px; overflow: hidden; position: relative; transform: translateZ(0);`">
<!-- 运用 js 算出的无误差纯数字 scale 来实现高清画板的等比缩小,彻底兼容所有老旧内核,不再被截断 -->
<view
:style="`width: 822px; transform: scale(${posterScaleRatio}); transform-origin: left top; position: absolute; left: 0; top: 0;`">
<l-painter v-if="bgImage" isCanvasToTempFilePath @success="onPainterSuccess" :css="`width:822px;`">
<l-painter-view :css="`width: 822px; position: relative;`">
<!-- 直接使用主背景图撑开父容器,移除不必要的 opacity: 0 占位图,避免在大屏渲染时产生黑块 -->
<l-painter-image :src="bgImage" css="width: 822px; display: block;"></l-painter-image>
<!-- 人物图片 -->
<l-painter-image :src="wuweiData.imgUrl" :css="characterStyle"></l-painter-image>
<template v-if="wuweiData.type == 1">
<!-- 击败 -->
<l-painter-text :text="wuweiData.killCount"
:css="`${style1TextCommonStyle}; left:61px;`"></l-painter-text>
<!-- 精准射击 -->
<l-painter-text :text="wuweiData.shooting"
:css="`${style1TextCommonStyle}; left:131px;`"></l-painter-text>
<!-- 输出伤害 -->
<l-painter-text :text="wuweiData.output"
:css="`${style1TextCommonStyle}; left:201px;`"></l-painter-text>
<!-- 获得资金 -->
<l-painter-text :text="wuweiData.harvest"
:css="`${style1TextCommonStyle}; left:272px;`"></l-painter-text>
<!-- 三连击败 -->
<l-painter-text :text="wuweiData.threeKill"
:css="`${style1TextCommonStyle}; left:342px;`"></l-painter-text>
</template>
<template v-if="wuweiData.type == 2">
<!-- 击败 -->
<l-painter-text :text="wuweiData.killCount"
css="position: absolute; left: 70px; bottom: 122px; width:40px; text-align:center; font-size: 18px;font-weight: 700;color: #FFFFFF;line-height: 18px;"></l-painter-text>
<!-- 头像 -->
<l-painter-image :src="wuweiData.avatar"
css="position:absolute;left:65.5px;bottom:68.5px;width:42px;height:42px"></l-painter-image>
<!-- 昵称 -->
<l-painter-text :text="truncatedNickname"
css="position: absolute; left: 119px; bottom: 82px; font-size: 13px; font-family: Arial, sans-serif; color: #FFFFFF; width: 117px;"></l-painter-text>
<!-- 阵败 -->
<l-painter-text :text="wuweiData.defeatsCount"
css="position: absolute; left: 132.5px; bottom: 122px; width: 40px; text-align: center; font-size: 18px;font-weight: 700;color: #FFFFFF;line-height: 18px;"></l-painter-text>
<!-- 助攻 -->
<l-painter-text :text="wuweiData.assistCount"
css="position: absolute; left: 195px; bottom: 122px; width: 40px; text-align: center; font-size: 18px;font-weight: 700;color: #FFFFFF;line-height: 18px;"></l-painter-text>
<!-- 战斗分 -->
<l-painter-text :text="`平均战斗评分:${wuweiData.battlePoints}`"
css="position: absolute; left: 80px; bottom: 153px; width: 140px; text-align: center; font-size:10px;color: #FFFFFF;line-height: 10px;"></l-painter-text>
</template>
<!-- 水印 -->
<l-painter-image v-if="$isVip()" src="/static/image/other/shuiying.png"
:css="`position: absolute; left: 18.8px; bottom: 17.1px; width: 145.38px;height:42.76px`"></l-painter-image>
</l-painter-view>
</l-painter>
</view>
</view>
</view>
<!-- 水印 -->
<view v-if="$isVip()">
<liu-drag-button :canDocking="false"
@clickBtn="$goRechargePage('watermark', 'uni_alipay_other_game_wuweiqiyue')">
<image src='/static/lottie/watermark.gif' mode="widthFix" style="width:94px;height:74px;" ></image>
</liu-drag-button>
</view>
<view class="save-action">
<button class="save-btn" @click="handleSave">保存</button>
</view>
<!-- 主题选择区域 -->
<view class="theme-selector" :style="{'margin-bottom':$store.state.safeBottomAz+'px'}">
<scroll-view scroll-x="true" class="theme-scroll" :show-scrollbar="false">
<view class="theme-list">
<view class="theme-item" v-for="(item, index) in ['样式一', '样式二']" :key="index + 1"
:class="{ active: wuweiData.type == index + 1 }" @click="handleChangeTheme(index + 1)">
<text class="theme-text">{{ item }}</text>
</view>
</view>
</scroll-view>
</view>
<!-- 数据编辑弹窗 -->
<uni-popup ref="editPopup" type="center">
<view class="edit-popup-content">
<view class="popup-header">
<text class="title">编辑主页数据</text>
</view>
<scroll-view scroll-y class="popup-scroll">
<view class="form-item avatar-form-item">
<text class="label">人物图片</text>
<view class=" avatar-uploader img-box" @click="handleChooseImgUrl">
<view class="avatar-preview" v-if="tempData.imgUrl">
<image class="preview-img" :src="tempData.imgUrl" mode="widthFix"></image>
<view class="delete-icon" @click.stop="handleDeleteImgUrl">
<text>×</text>
</view>
</view>
<view class="upload-btn" v-else>
<text class="plus">+</text>
</view>
</view>
</view>
<view class="form-item avatar-form-item" v-if="tempData.type == 2">
<text class="label">头像</text>
<view class="avatar-uploader" @click="handleChooseAvatar">
<view class="avatar-preview" v-if="tempData.avatar">
<image class="preview-img" :src="tempData.avatar" mode="aspectFill"></image>
<view class="delete-icon" @click.stop="handleDeleteAvatar">
<text>×</text>
</view>
</view>
<view class="upload-btn" v-else @click="handleChooseAvatar">
<text class="plus">+</text>
</view>
</view>
</view>
<view class="form-item" v-if="tempData.type == 2">
<text class="label">昵称</text>
<input class="input" type="text" v-model="tempData.nickname" />
</view>
<view class="form-item">
<text class="label">击败</text>
<input class="input" type="number" v-model="tempData.killCount" />
</view>
<view class="form-item" v-if="tempData.type == 1">
<text class="label">精准射击</text>
<input class="input" type="number" v-model="tempData.shooting" />
</view>
<view class="form-item" v-if="tempData.type == 1">
<text class="label">输出伤害</text>
<input class="input" type="number" v-model="tempData.output" />
</view>
<view class="form-item" v-if="tempData.type == 1">
<text class="label">获得资金</text>
<input class="input" type="number" v-model="tempData.harvest" />
</view>
<view class="form-item" v-if="tempData.type == 1">
<text class="label">三连击败</text>
<input class="input" type="number" v-model="tempData.threeKill" />
</view>
<view class="form-item" v-if="tempData.type == 2">
<text class="label">阵败</text>
<input class="input" type="number" v-model="tempData.defeatsCount" />
</view>
<view class="form-item" v-if="tempData.type == 2">
<text class="label">助攻</text>
<input class="input" type="number" v-model="tempData.assistCount" />
</view>
<view class="form-item" v-if="tempData.type == 2">
<text class="label">战斗评分</text>
<input class="input" type="number" v-model="tempData.battlePoints" />
</view>
</scroll-view>
<view class="popup-footer">
<button class="cancel-btn" @click="closeEditPopup">取消</button>
<button class="confirm-btn" @click="confirmEdit">确定</button>
</view>
</view>
</uni-popup>
<!-- 横向全屏放大预览层 -->
<view class="preview-overlay" v-if="showPreview" @click="showPreview = false">
<image class="preview-image" :src="finalPosterPath" mode="aspectFill"></image>
</view>
</view>
</template>
<script setup>
import { ref, reactive, computed, watch, onMounted, getCurrentInstance } from 'vue'
import { onLoad, onReady, onUnload } from '@dcloudio/uni-app'
import { imgLocal } from '@/utils/common.js';
import NavBar from '@/components/nav-bar/nav-bar.vue';
const instance = getCurrentInstance();
const { proxy } = getCurrentInstance();
// 页面表单数据字段
const wuweiData = reactive({
type: 1, // 样式类型 1.2
avatar: '/static/image/shopping/pdd/avatars/avatars1.jpg', // 头像
imgUrl: '/static/image/other/game/heping/default.png', // 人物图片
nickname: '甜喵小贝', // 昵称
killCount: 24,
defeatsCount: 2,
assistCount: 12,
battlePoints: 3854,
shooting: 7,
output: 4109,
harvest: 41750,
threeKill: 5
})
const characterImgHeight = ref(350); // 初始高度兜底
const characterStyle = computed(() => {
if (wuweiData.type == 1) {
return `position: absolute; right: 77px; bottom: 0; width: 300px; height: 350px;`;
} else {
return `position: absolute; right: 210px; bottom: 0; width: 300px; height: 350px;`;
}
});
const style1TextCommonStyle = `position: absolute; bottom:79px; width:40px; text-align:center; font-size: 16px;color: #FFFFFF;line-height: 16px;`;
// 手动处理中英文混合宽度的截断,避免依赖 line-clamp 和底层画布可能引发的居中对齐问题
const truncatedNickname = computed(() => {
let name = wuweiData.nickname || '';
let width = 0;
let result = '';
let max = 117 - 15; // 保留大约 15px 的空间给省略号 "..."
for (let i = 0; i < name.length; i++) {
let charCode = name.charCodeAt(i);
if (charCode >= 0 && charCode <= 128) {
width += 7.5; // 英文及数字近似宽度
} else {
width += 13; // 中文字符宽度(font-size: 13px)
}
if (width > max) {
return result + '...';
}
result += name[i];
}
return name;
});
// 监听人物图片变化,动态获取其原始宽高并计算比例高度,解决画布必须写明尺寸才能渲染的问题
watch(() => wuweiData.imgUrl, (newUrl) => {
if (!newUrl) return;
uni.getImageInfo({
src: newUrl,
success: (res) => {
// 固定宽度 300px,高度等比例计算
const calcHeight = (300 / res.width) * res.height;
characterImgHeight.value = Math.round(calcHeight);
},
fail: () => {
characterImgHeight.value = 350; // 获取失败提供兜底高度
}
});
}, { immediate: true });
const bgImage = ref('')
// 获取系统宽度计算海报缩放比例,代替容易失效的 css calc(),确保全端兼容防截断
const sysInfo = uni.getSystemInfoSync();
const windowWidth = sysInfo.windowWidth || 375;
// 扣除左右共 16px 的 padding,再基于实际画板宽度 822 计算精确缩放比,防止右侧被截断
const posterScaleRatio = (windowWidth - 16) / 822;
const posterContainerHeight = 374 * posterScaleRatio;
onLoad(async () => {
const cachedData = uni.getStorageSync('wuweiData');
if (cachedData) {
Object.assign(wuweiData, cachedData);
// 兼容处理:强制把历史缓存里的 .jpg 替换为最新的 .png,防止 404
if (wuweiData.imgUrl === '/static/image/other/game/heping/default.jpg') {
wuweiData.imgUrl = '/static/image/other/game/heping/default.png';
uni.setStorageSync('wuweiData', wuweiData);
}
}
try {
bgImage.value = await imgLocal.getLocalImage(`other/game/wuwei/bg-${wuweiData.type}.jpg`);
} catch (e) {
console.error('获取背景图失败:', e);
uni.showToast({ title: '背景图加载失败', icon: 'none' });
}
uni.$on('editFormPhoto', (info) => {
if (currentEditImgField.value) {
tempData[currentEditImgField.value] = info;
}
// #ifndef H5
if (info && !info.startsWith('/static/')) {
newAvatars.value.push(info);
}
// #endif
});
// 进入页面埋点
if (proxy && proxy.$apiUserEvent) {
proxy.$apiUserEvent('all', {
type: 'click',
key: 'wuwei',
prefix: '.uni.other.',
value: "无畏契约"
});
}
})
onUnload(() => {
uni.$off('editFormPhoto');
})
const rightButtonText = ref("编辑");
const finalPosterPath = ref('');
const showPreview = ref(false); // 控制全屏预览
const editPopup = ref(null);
const tempData = reactive({});
const newAvatars = ref([]);
const currentEditImgField = ref('');
function onRightClick() {
Object.assign(tempData, JSON.parse(JSON.stringify(wuweiData)));
newAvatars.value = [];
editPopup.value.open();
}
const handleChooseAvatar = () => {
currentEditImgField.value = 'avatar';
uni.chooseImage({
count: 1,
success: (res) => {
uni.navigateTo({
url: `/pages/other/qf-image/qf-image?src=${res.tempFilePaths[0]}&width=200&height=200`
});
}
});
};
const handleChooseImgUrl = () => {
currentEditImgField.value = 'imgUrl';
uni.chooseImage({
count: 1,
success: (res) => {
if (res.tempFilePaths[0]) {
// 跳转到 qf-image 组件进行 300x350 尺寸(传参150x175)的裁剪,并通过 isMatting=1 触发后续抠图
uni.navigateTo({
url: `/pages/other/qf-image/qf-image?src=${res.tempFilePaths[0]}&width=150&height=175&isMatting=1`
});
}
}
});
};
const handleDeleteAvatar = () => {
tempData.avatar = '';
};
const handleDeleteImgUrl = () => {
tempData.imgUrl = '';
};
function closeEditPopup() {
// #ifndef H5
newAvatars.value.forEach(path => {
uni.removeSavedFile({ filePath: path });
});
// #endif
editPopup.value.close();
}
function confirmEdit() {
// #ifndef H5
// 删除多余的新上传文件
newAvatars.value.forEach(path => {
if (path !== tempData.avatar && path !== tempData.imgUrl) {
uni.removeSavedFile({ filePath: path });
}
});
// 删除被替换掉的原本地头像
if (wuweiData.avatar !== tempData.avatar && wuweiData.avatar && !wuweiData.avatar.startsWith('/static/')) {
uni.removeSavedFile({ filePath: wuweiData.avatar });
}
if (wuweiData.imgUrl !== tempData.imgUrl && wuweiData.imgUrl && !wuweiData.imgUrl.startsWith('/static/')) {
uni.removeSavedFile({ filePath: wuweiData.imgUrl });
}
// #endif
Object.assign(wuweiData, tempData);
uni.setStorageSync('wuweiData', wuweiData);
editPopup.value.close();
// 数据修改后,重新渲染海报
finalPosterPath.value = '';
let tempBg = bgImage.value;
bgImage.value = ''; // 强制卸载旧画板,确保能重新渲染和生成新海报
setTimeout(() => {
bgImage.value = tempBg;
}, 50);
}
async function handleChangeTheme(typeIndex) {
// 切换主题时,先清空旧海报,触发界面“加载中”提示
finalPosterPath.value = '';
bgImage.value = ''; // 强制卸载旧画板,确保能重新渲染和生成新海报
wuweiData.type = typeIndex;
uni.setStorageSync('wuweiData', wuweiData);
try {
bgImage.value = await imgLocal.getLocalImage(`other/game/wuwei/bg-${typeIndex}.jpg`);
} catch (e) {
console.error('切换主题加载背景图失败:', e);
uni.showToast({ title: '背景加载失败', icon: 'none' });
}
}
// 点击画布触发横向预览
const handlePreview = () => {
if (!finalPosterPath.value) {
uni.showToast({ title: '海报仍在生成中,请稍候', icon: 'none' });
return;
}
showPreview.value = true;
}
// 画布生成成功后触发,保存最终的海报路径
const onPainterSuccess = (path) => {
finalPosterPath.value = path;
}
// 点击保存按钮,将带有渐变字体的最终图片存入相册
const handleSave = () => {
if (!finalPosterPath.value) {
uni.showToast({ title: '图片仍在生成中,请稍候', icon: 'none' })
return
}
uni.saveImageToPhotosAlbum({
filePath: finalPosterPath.value,
success: () => {
uni.showToast({ title: '保存成功', icon: 'success' })
},
fail: () => {
uni.showToast({ title: '保存失败', icon: 'none' })
}
})
}
/**
* 将本地图片路径通过 Canvas 转换为 File 对象
* @param {string} localPath - 本地图片路径(如从 uni.chooseImage 获取的 tempFilePath)
* @param {Object} options - 可选参数
* @param {string} options.format - 输出格式 'image/png' 或 'image/jpeg',默认 'image/png'
* @param {number} options.quality - 图片质量(仅 jpeg 有效),0~1,默认 0.92
* @param {number} options.maxWidth - 最大宽度(等比缩放),不填则使用原图尺寸
* @param {number} options.maxHeight - 最大高度(等比缩放),不填则使用原图尺寸
* @returns {Promise<File>} 返回一个 Promise,resolve 为 File 对象
*/
function convertLocalImageToFile(localPath) {
return new Promise((resolve, reject) => {
// #ifdef APP-PLUS&&!APP-HARMONY
// 1. 读取本地图片为 Base64(避免 Image 对象直接加载本地路径的兼容问题)
plus.io.resolveLocalFileSystemURL(localPath, (entry) => {
entry.file((file) => {
const reader = new plus.io.FileReader();
reader.onload = (e) => {
const base64Data = e.target.result; // 格式如 data:image/jpeg;base64,/9j/...
resolve(e.target.result)
};
reader.onerror = (err) => reject(err);
reader.readAsDataURL(file); // 读取为 DataURL
}, reject);
}, reject);
// #endif
// #ifndef APP-PLUS
reject(new Error('plus.io not supported'))
// #endif
});
}
</script>
<style lang="less" scoped>
.preview-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #000;
z-index: 99999;
/* 抛弃 flex 布局,防止 width: 100vh 被父级限制而发生坍缩 */
}
.preview-overlay .preview-image {
position: absolute;
top: 50%;
left: 50%;
width: 100vh;
height: 100vw;
/* 先拉回自身中心点,再围绕中心点旋转 90 度 */
transform: translate(-50%, -50%) rotate(90deg);
}
.honor-of-kings {
width: 100%;
overflow-x: hidden;
}
.painter-container {
/* 强制画板缩放到设备屏幕宽度,避免物理 px 超出屏幕 */
padding: 8px;
zoom: calc(100vw / 798);
width: 100%;
max-width: 1000px;
/* 限制PC/iPad端的最大宽度 */
margin: 0 auto;
}
.save-action {
margin-top: 60rpx;
display: flex;
flex-direction: column;
align-items: center;
padding-bottom: 220rpx;
/* 留出底部主题固定栏的安全空间 */
.save-btn {
margin-top: 60rpx;
width: 316rpx;
background: #1777FF;
color: #fff;
border-radius: 56rpx;
}
}
.theme-selector {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
padding-top: 20rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
z-index: 99;
.theme-scroll {
width: 100%;
white-space: nowrap;
::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
}
.theme-list {
display: inline-flex;
width: 100%;
justify-content: center;
padding: 0 20rpx;
.theme-item {
display: inline-flex;
vertical-align: top;
margin: 0 10rpx;
padding: 0 40rpx;
height: 84rpx;
border-radius: 12rpx;
background-color: #FFFFFF;
justify-content: center;
align-items: center;
border: 2rpx solid transparent;
box-sizing: border-box;
transition: all 0.3s;
&.active {
background-color: #fff;
border-color: #3B7BFF;
.theme-text {
color: #3B7BFF;
font-weight: bold;
}
}
.theme-text {
font-size: 28rpx;
color: #666;
}
}
}
}
.edit-popup-content {
background-color: #fff;
border-radius: 20rpx;
width: 85vw;
.popup-header {
display: flex;
justify-content: center;
align-items: center;
padding: 40rpx 0 20rpx 0;
.title {
font-size: 34rpx;
font-weight: bold;
color: #333;
}
}
.popup-scroll {
max-height: 55vh;
padding: 20rpx 40rpx;
box-sizing: border-box;
}
.form-item {
display: flex;
align-items: center;
margin-bottom: 16rpx;
padding-bottom: 16rpx;
.label {
width: 200rpx;
font-size: 28rpx;
color: #333;
}
.avatar-uploader {
display: flex;
align-items: center;
.upload-btn {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
background-color: #999DA7;
display: flex;
justify-content: center;
align-items: center;
.plus {
font-size: 60rpx;
color: #fff;
font-weight: 300;
margin-top: -6rpx;
}
}
.avatar-preview {
position: relative;
width: 100rpx;
height: 100rpx;
.preview-img {
width: 100%;
height: 100%;
border-radius: 50%;
}
.delete-icon {
position: absolute;
top: -4rpx;
right: -4rpx;
width: 32rpx;
height: 32rpx;
background-color: red;
color: #fff;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
font-size: 30rpx;
line-height: 28rpx;
}
}
}
.img-box {
width: 200rpx !important;
.avatar-preview {
width: 200rpx !important;
height: auto !important;
}
.preview-img {
width: 100%;
border-radius: 0 !important;
}
}
.input {
flex: 1;
font-size: 28rpx;
background-color: #F7F7F7;
border-radius: 12rpx;
height: 70rpx;
line-height: 70rpx;
padding: 0 20rpx;
color: #333;
}
}
.popup-footer {
display: flex;
justify-content: space-between;
padding: 20rpx 40rpx 40rpx;
button {
width: 46%;
height: 76rpx;
line-height: 76rpx;
font-size: 30rpx;
border-radius: 12rpx;
margin: 0;
&::after {
border: none;
}
}
.cancel-btn {
background-color: #F4F4F4;
color: #666;
}
.confirm-btn {
background-color: #3B7BFF;
color: #fff;
}
}
}
</style>

View File

@ -0,0 +1,117 @@
<template>
<view>
<qf-image-cropper :src="data.src" :width="data.width" :height="data.height" :radius="0" @crop="handleCrop"
:reverseRotatable="true"></qf-image-cropper>
</view>
</template>
<script setup>
import {
ref,
reactive,
watch,
nextTick,
getCurrentInstance
} from "vue";
import {
onLoad,
onShow,
onReady,
onPullDownRefresh,
onReachBottom
} from "@dcloudio/uni-app";
const {
appContext,
proxy
} = getCurrentInstance();
const data = reactive({
src: "",
width:196,
height:230,
isCard:true
})
onLoad((option) => {
console.log(option)
data.src = option.src
if(option.width){
data.width=option.width*2
data.isCard=false
}
if(option.height){
data.height=option.height*2
data.isCard=false
}
if (option.isMatting === '1') {
data.isCard = true; // 强制开启抠图
}
})
function handleCrop(e) {
if(data.isCard){
uni.showLoading({
title:"抠图中"
})
convertLocalImageToFile(e.tempFilePath).then(file => {
proxy.$imageUpload(file.split(',', 2)[1]).then(resimage => {
uni.hideLoading()
// editForm.value.photo = decodeURI(resimage.data);
uni.$emit("editFormPhoto", decodeURI(resimage.data))
uni.navigateBack()
}).catch(err => {
uni.hideLoading()
console.log(err.data.message)
uni.showToast({
icon: "none",
title: "图片不是人像或者过大"
})
})
}).catch(err => {
uni.hideLoading()
})
}else{
uni.saveFile({
tempFilePath: e.tempFilePath,
success: function(res) {
console.log(res)
uni.$emit("editFormPhoto", decodeURI(res.savedFilePath))
uni.navigateBack()
// res.avatar = res.savedFilePath
}
});
}
}
/**
* 将本地图片路径通过 Canvas 转换为 File 对象
* @param {string} localPath - 本地图片路径(如从 uni.chooseImage 获取的 tempFilePath)
* @param {Object} options - 可选参数
* @param {string} options.format - 输出格式 'image/png' 或 'image/jpeg',默认 'image/png'
* @param {number} options.quality - 图片质量(仅 jpeg 有效),0~1,默认 0.92
* @param {number} options.maxWidth - 最大宽度(等比缩放),不填则使用原图尺寸
* @param {number} options.maxHeight - 最大高度(等比缩放),不填则使用原图尺寸
* @returns {Promise<File>} 返回一个 Promise,resolve 为 File 对象
*/
function convertLocalImageToFile(localPath) {
return new Promise((resolve, reject) => {
// #ifdef APP-PLUS&&!APP-HARMONY
// 1. 读取本地图片为 Base64(避免 Image 对象直接加载本地路径的兼容问题)
plus.io.resolveLocalFileSystemURL(localPath, (entry) => {
entry.file((file) => {
const reader = new plus.io.FileReader();
reader.onload = (e) => {
const base64Data = e.target.result; // 格式如 data:image/jpeg;base64,/9j/...
resolve(e.target.result)
};
reader.onerror = (err) => reject(err);
reader.readAsDataURL(file); // 读取为 DataURL
}, reject);
}, reject);
// #endif
// #ifndef APP-PLUS
reject(new Error('plus.io not supported'))
// #endif
});
}
</script>

View File

@ -0,0 +1,790 @@
<template>
<view class="ranking-page">
<nav-bar title="从夯到拉" bgColor="#F5F5F5" isRightButton :rightButtonText="rightButtonText"
@right-click="onRightClick">
</nav-bar>
<view class="content-container">
<view :class="{ 'skin-box': type == 'skin', 'table-box': type != 'skin' }"
:style="type == 'skin' ? { backgroundColor: skinBgColor } : {}">
<template v-if="type == 'skin'">
<view v-if="!isEdit" class="title">{{ title }}</view>
<input v-else class="title title-input" v-model="title" maxlength="12" placeholder="请输入排名标题" />
<image class="img happy" src="/static/image/other/ranking/happy.png"></image>
<image class="img sad" src="/static/image/other/ranking/sad.png"></image>
</template>
<image v-if="$isVip()" class="watermark" :class="{}" src="/static/image/other/card/shuiyin2.png"
mode="heightFix">
</image>
<view class="ranking-table">
<view v-for="(item, index) in tierList" :key="index" class="ranking-row">
<view class="label-box" :style="{ backgroundColor: item.bgColor }"
@click="changeRowBgColor(index)">
<!-- <text class="label-text" :class="{ 'with-stroke': item.hasStroke }"
:style="{ color: item.textColor }">
{{ item.label }}
</text> -->
<image :style="`height:60rpx;width:160rpx;`"
:src="`/static/image/other/ranking/${type == 'skin' ? item.img + '_skin' : item.img}.png`">
</image>
</view>
<view class="items-box ranking-item-list" :id="'row-' + index">
<view v-for="(img, imgIdx) in item.images" :key="imgIdx" class="item-wrapper"
:class="{ 'is-dragging': drag.tierIndex === index && drag.imgIdx === imgIdx }"
:style="getDragStyle(index, imgIdx)" @touchstart="onTouchStart($event, index, imgIdx)"
@touchmove.stop.prevent="onTouchMove" @touchend="onTouchEnd">
<image :src="img" mode="aspectFill" class="item-img"></image>
<view v-if="isEdit" class="del-btn" @click.stop="deleteImage(index, imgIdx)">×</view>
</view>
<view v-if="isEdit && item.images.length < 4" class="add-btn" @click="chooseImage(index)">
<image class="add-icon" src="/static/image/common/add.png"></image>
</view>
</view>
</view>
</view>
</view>
<view class="save-action">
<button v-if="!isEdit" class="save-btn" @click="handleSave">保存图片</button>
<!-- 编辑模式下的全局颜色控制器 -->
<view v-else-if="type == 'skin'" class="bottom-edit-actions">
<view class="color-picker-wrapper">
<view class="color-picker">
<view v-for="color in ['#FFDFDF', '#DFF0FF', '#E0FFDF', '#FDF5C8', '#F3DFFF']" :key="color"
class="color-dot" :style="{ backgroundColor: color }" @click="skinBgColor = color">
</view>
</view>
<input v-if="false" class="hex-input" v-model="skinBgColor" placeholder="#HEX" maxlength="7" />
</view>
<view class="spectrum-picker" @touchstart="handleHueTouch" @touchmove.stop.prevent="handleHueTouch">
<view class="spectrum-bar"></view>
<view class="slider-thumb" :style="{ left: (skinHue / 360 * 100) + '%' }"></view>
</view>
<text class="edit-tip">点击左侧分类标签可切换背景色</text>
</view>
</view>
</view>
<view class="bottom-tabs" :style="{'margin-bottom':$store.state.safeBottomAz+'px'}">
<view class="tab-item" :class="{ active: type == 'base' }" @click="switchType('base')">基础</view>
<view class="tab-item" :class="{ active: type == 'skin' }" @click="switchType('skin')">皮肤</view>
</view>
<view class="painter-container" v-if="isSnapshot">
<l-painter isCanvasToTempFilePath @success="onPainterSuccess"
:css="`width:750rpx; padding: 40rpx; background-color:${type == 'skin' ? skinBgColor : '#F8F8F8'};`">
<l-painter-view
:css="`width: 100%; display: flex; flex-direction: column; position: relative; ${type == 'skin' ? 'padding-top: 120rpx;padding-bottom: 120rpx;' : ''}`">
<template v-if="type == 'skin'">
<l-painter-text :text="title"
css="position: absolute; top: 26rpx; left: 50%; transform: translateX(-50%); font-size: 36rpx; font-weight: bold; color: #333;" />
<l-painter-image src="/static/image/other/ranking/happy.png"
css="position: absolute;top: 58rpx;right: 24rpx; width: 96rpx; height: 96rpx;" />
</template>
<l-painter-view css="border: 3rpx solid #333; background-color: #fff; width: 100%;">
<l-painter-view v-for="(item, index) in tierList" :key="index"
:css="`display: flex; min-height: 148rpx; border-bottom: ${index === tierList.length - 1 ? 'none' : '3rpx solid #333'};`">
<l-painter-view
:css="`width: 176rpx; min-height: 148rpx; background-color: ${item.bgColor}; display: flex; align-items: center; justify-content: center; border-right: 3rpx solid #333;`">
<!-- <l-painter-text :text="item.label"
:css="`font-size: 52rpx; font-weight: bold; color: ${item.textColor}; ${item.hasStroke ? 'text-shadow: 2rpx 2rpx 0 #000, -2rpx -2rpx 0 #000, 2rpx -2rpx 0 #000, -2rpx 2rpx 0 #000, 0 2rpx 0 #000, 0 -2rpx 0 #000, 2rpx 0 0 #000, -2rpx 0 0 #000;' : ''}`" /> -->
<l-painter-image
:src="`/static/image/other/ranking/${type == 'skin' ? item.img + '_skin' : item.img}.png`"
:css="`height: 60rpx; width: 160rpx;`" mode="heightFix" />
</l-painter-view>
<l-painter-view
:css="`flex: 1; display: flex; align-items: center;height: 148rpx;width:490rpx;padding:0 12rpx;`">
<l-painter-image v-for="(img, imgIdx) in item.images" :key="imgIdx" :src="img"
css="width: 105rpx; height: 105rpx; margin: 0 8rpx; object-fit: cover;"
mode="aspectFill" />
</l-painter-view>
</l-painter-view>
</l-painter-view>
<l-painter-view v-if="$isVip()"
:css="type == 'skin' ? `position: absolute;bottom: 32rpx;right: 14rpx;` : `position: absolute;top: 50%;right: 50%;transform: translate(50%, -50%);`">
<l-painter-image src="/static/image/other/card/shuiyin2.png"
css="width: 194rpx;height: 56rpx;" />
</l-painter-view>
<l-painter-image v-if="type == 'skin'" src="/static/image/other/ranking/sad.png"
css="position: absolute;bottom: 76rpx;left: 116rpx; width: 96rpx; height: 96rpx;" />
</l-painter-view>
</l-painter>
</view>
</view>
<!-- 水印 -->
<view v-if="$isVip()">
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark', 'uni_alipay_other_ranking')">
<image src='/static/lottie/watermark.gif' mode="widthFix" style="width:94px;height:74px;" ></image>
</liu-drag-button>
</view>
</template>
<script setup>
import {
ref,
reactive,
getCurrentInstance
} from 'vue';
import {
onLoad
} from '@dcloudio/uni-app';
const {
proxy
} = getCurrentInstance();
const type = ref("base");
const title = ref("xx排名");
const skinBgColor = ref("#FFDFDF");
const skinHue = ref(0); // 记录色相位置
const isSnapshot = ref(false);
const isEdit = ref(false);
const rightButtonText = ref("编辑");
const tierList = ref([{
label: '夯',
bgColor: '#D5171C',
img: 'hang',
textColor: '#FFFFFF',
hasStroke: true,
images: [],
width: 60,
},
{
label: '顶级',
bgColor: '#FF6A0B',
img: 'dingji',
textColor: '#FFFFFF',
hasStroke: true,
images: [],
width: 104,
},
{
label: '人上人',
bgColor: '#FFF06A',
img: 'renshangren',
textColor: '#FFFFFF',
hasStroke: true,
images: [],
width: 156,
},
{
label: 'NPC',
bgColor: '#FDF5C8',
img: 'npc',
textColor: '#FFFFFF',
hasStroke: true,
images: [],
width: 110,
},
{
label: '拉完了',
bgColor: '#FFFFFF',
img: 'lawanle',
textColor: '#FFFFFF',
hasStroke: true,
images: [],
width: 156,
}
]);
// --- 生命周期:加载缓存 ---
onLoad(() => {
// 进入页面埋点
proxy.$apiUserEvent('all', {
type: 'click',
key: 'ranking',
value: "从夯倒拉排名"
})
// 获取缓存数据
const cache = uni.getStorageSync('ranking_config_data');
if (cache) {
title.value = cache.title || title.value;
type.value = cache.type || type.value;
skinBgColor.value = cache.skinBgColor || skinBgColor.value;
skinHue.value = cache.skinHue ?? skinHue.value;
if (cache.tierList) tierList.value = cache.tierList;
console.log(tierList.value);
}
});
// --- 拖拽排序逻辑 ---
const drag = reactive({
tierIndex: -1,
imgIdx: -1,
startX: 0,
startY: 0,
moveX: 0,
moveY: 0
});
// 计算拖拽样式
const getDragStyle = (tIdx, iIdx) => {
if (drag.tierIndex === tIdx && drag.imgIdx === iIdx) {
return {
transform: `translate(${drag.moveX}px, ${drag.moveY}px)`,
zIndex: 99,
transition: 'none'
};
}
return {
transition: 'transform 0.3s ease'
};
};
const onTouchStart = (e, tIdx, iIdx) => {
if (!isEdit.value) return;
const touch = e.touches[0];
drag.tierIndex = tIdx;
drag.imgIdx = iIdx;
drag.startX = touch.clientX;
drag.startY = touch.clientY;
drag.moveX = 0;
drag.moveY = 0;
};
const onTouchMove = (e) => {
if (drag.tierIndex === -1) return;
const touch = e.touches[0];
drag.moveX = touch.clientX - drag.startX;
drag.moveY = touch.clientY - drag.startY;
};
const onTouchEnd = () => {
if (drag.tierIndex === -1) return;
const {
tierIndex,
imgIdx,
moveX
} = drag;
const rowData = tierList.value[tierIndex].images;
// 估算单个item跨度 (100rpx宽度 + 12rpx间距 ≈ 56px,建议根据真机微调)
const itemWidth = 56;
const offset = Math.round(moveX / itemWidth);
let newIndex = imgIdx + offset;
// 限制索引范围
newIndex = Math.max(0, Math.min(newIndex, rowData.length - 1));
if (newIndex !== imgIdx) {
const temp = rowData.splice(imgIdx, 1)[0];
rowData.splice(newIndex, 0, temp);
}
// 重置拖拽状态
drag.tierIndex = -1;
drag.imgIdx = -1;
};
// --- 其他业务逻辑 ---
const chooseImage = (index) => {
const currentCount = tierList.value[index].images.length;
uni.chooseImage({
count: 4 - currentCount,
sizeType: ['compressed'],
success: (res) => {
tierList.value[index].images.push(...res.tempFilePaths);
}
});
};
const deleteImage = (tIdx, iIdx) => {
const path = tierList.value[tIdx].images[iIdx];
removeLocalFile(path); // 物理删除已有文件
tierList.value[tIdx].images.splice(iIdx, 1);
};
const onRightClick = async () => {
if (isEdit.value) {
uni.showLoading({
title: '正在持久化图片...',
mask: true
});
// 1. 深度遍历并持久化所有临时图片
for (let tierIdx = 0; tierIdx < tierList.value.length; tierIdx++) {
const tier = tierList.value[tierIdx];
for (let i = 0; i < tier.images.length; i++) {
const path = tier.images[i];
// 逻辑逻辑:如果是 static 或者是已经保存的 _doc/usr 路径,则跳过
const isStatic = path.startsWith('/static/') || path.startsWith('static/');
const isPersistent = path.indexOf('_doc/') !== -1 || path.indexOf('usr/') !== -1;
if (!isStatic && !isPersistent) {
console.log('检测到待持久化图片:', path);
const newPath = await saveImageToLocal(path);
if (newPath) {
tier.images[i] = newPath;
console.log('持久化成功,新路径:', newPath);
}
}
}
}
// 2. 全部处理完后,进行数据持久化
uni.setStorageSync('ranking_config_data', {
title: title.value,
type: type.value,
skinBgColor: skinBgColor.value,
skinHue: skinHue.value,
tierList: tierList.value
});
uni.hideLoading();
uni.showToast({
title: '已保存',
icon: 'none'
});
}
isEdit.value = !isEdit.value;
rightButtonText.value = isEdit.value ? "确定" : "编辑";
};
/**
* 将临时图片保存到本地永久目录
*/
const saveImageToLocal = (tempFilePath) => {
return new Promise((resolve) => {
// 如果已经是持久化路径,直接返回
if (tempFilePath.indexOf('_doc/') !== -1 || tempFilePath.indexOf('usr/') !== -1) {
return resolve(tempFilePath);
}
uni.saveFile({
tempFilePath: tempFilePath,
success: (res) => {
resolve(res.savedFilePath);
},
fail: (err) => {
console.error('本地持久化保存失败:', err);
// 提示:H5 环境不支持 saveFile 持久化,仅 App/小程序支持
resolve(null);
}
});
});
};
/**
* 物理删除本地已保存的文件
*/
const removeLocalFile = (path) => {
if (!path) return;
// 仅针对非静态资源的本地文件进行删除
if (path.indexOf('_doc/') !== -1 || path.indexOf('usr/') !== -1) {
uni.removeSavedFile({
filePath: path,
success: () => console.log('文件物理删除成功:', path),
fail: (err) => console.log('文件删除失败:', err)
});
}
};
const handleSave = () => {
uni.showLoading({
title: '生成中...',
mask: true
});
isSnapshot.value = true;
};
const onPainterSuccess = (path) => {
const done = () => {
isSnapshot.value = false;
uni.hideLoading();
};
if (!path) return done();
uni.saveImageToPhotosAlbum({
filePath: path,
success: () => uni.showToast({
title: '保存成功'
}),
fail: () => uni.showToast({
title: '保存失败',
icon: 'none'
}),
complete: done
});
};
const switchType = (val) => {
type.value = val;
if (val == 'skin') {
tierList.value[0].bgColor = '#F3575B'
tierList.value[1].bgColor = '#FF9B5B'
tierList.value[2].bgColor = '#FFF59E'
tierList.value[3].bgColor = '#FFFBE1'
tierList.value[4].bgColor = '#FFFFFF'
} else {
tierList.value[0].bgColor = '#D5171C'
tierList.value[1].bgColor = '#FF6A0B'
tierList.value[2].bgColor = '#FFF06A'
tierList.value[3].bgColor = '#FDF5C8'
tierList.value[4].bgColor = '#FFFFFF'
}
// 切换时由于改变了 type,立即同步到缓存中以便记录最后选择的模式
const cache = uni.getStorageSync('ranking_config_data') || {};
cache.type = val;
uni.setStorageSync('ranking_config_data', cache);
}
/**
* 循环切换行背景色 (编辑模式下)
*/
const changeRowBgColor = (index) => {
if (!isEdit.value) return;
const colors = ['#D5171C', '#FF6A0B', '#FFF06A', '#FDF5C8', '#FFFFFF', '#1777FF', '#333333'];
const curr = tierList.value[index].bgColor;
let nextIdx = (colors.indexOf(curr.toUpperCase()) + 1) % colors.length;
if (nextIdx === -1) nextIdx = 0;
tierList.value[index].bgColor = colors[nextIdx];
// 自动适配文字颜色 (深色背景用白色,浅色用黑色)
const darkColors = ['#D5171C', '#1777FF', '#333333'];
tierList.value[index].textColor = darkColors.includes(colors[nextIdx]) ? '#FFFFFF' : '#FFFFFF';
};
/**
* 处理色域滑动
*/
const handleHueTouch = (e) => {
const touch = e.touches[0];
uni.createSelectorQuery().select('.spectrum-picker').boundingClientRect(rect => {
if (rect) {
const x = Math.max(0, Math.min(touch.clientX - rect.left, rect.width));
skinHue.value = Math.round((x / rect.width) * 360);
// 1. 获取 HSL 的数值
const h = skinHue.value;
const s = 70;
const l = 90;
// 2. 转换为 16 进制色值
skinBgColor.value = hslToHex(h, s, l);
}
}).exec();
};
/**
* HSL 转 Hex 工具函数
*/
function hslToHex(h, s, l) {
l /= 100;
const a = s * Math.min(l, 1 - l) / 100;
const f = n => {
const k = (n + h / 30) % 12;
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
return Math.round(255 * color).toString(16).padStart(2, '0');
};
return `#${f(0)}${f(8)}${f(4)}`.toUpperCase();
}
</script>
<style lang="less" scoped>
.ranking-page {
min-height: 100vh;
background-color: #F8F8F8;
padding-bottom: 200rpx;
}
.content-container {
position: relative;
padding: 12rpx 24rpx;
}
.table-box {
position: relative;
.watermark {
position: absolute;
height: 56rpx;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 99;
}
}
.ranking-table {
border: 3rpx solid #333;
background-color: #fff;
}
.ranking-row {
display: flex;
min-height: 148rpx;
border-bottom: 3rpx solid #333;
&:last-child {
border-bottom: none;
}
}
.label-box {
width: 176rpx;
display: flex;
align-items: center;
justify-content: center;
border-right: 3rpx solid #333;
}
.label-text {
font-size: 48rpx;
font-weight: bold;
&.with-stroke {
text-shadow: 2rpx 2rpx 0 #000, -2rpx -2rpx 0 #000, 2rpx -2rpx 0 #000, -2rpx 2rpx 0 #000;
}
}
.items-box {
flex: 1;
display: flex;
align-items: center;
padding: 14rpx 12rpx;
flex-wrap: wrap;
}
.item-wrapper {
width: 105rpx;
height: 105rpx;
position: relative;
margin: 0 6rpx;
touch-action: none;
/* 关键:禁止浏览器默认触摸行为 */
&.is-dragging {
opacity: 0.7;
scale: 1.1;
box-shadow: 0 10rpx 20rpx rgba(0, 0, 0, 0.2);
}
}
.item-img {
width: 100%;
height: 100%;
border-radius: 4rpx;
}
.del-btn {
position: absolute;
top: -12rpx;
right: -12rpx;
width: 36rpx;
height: 36rpx;
background: #ff4d4f;
color: #fff;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
font-size: 24rpx;
z-index: 10;
}
.add-btn {
width: 100rpx;
height: 100rpx;
background: #eee;
display: flex;
justify-content: center;
align-items: center;
border-radius: 4rpx;
.add-icon {
width: 40rpx;
height: 40rpx;
}
}
.save-btn {
margin-top: 60rpx;
width: 400rpx;
background: #1777FF;
color: #fff;
border-radius: 50rpx;
}
.bottom-tabs {
position: fixed;
bottom: 40rpx;
bottom: calc(32rpx + env(safe-area-inset-bottom));
bottom: calc(32rpx + constant(safe-area-inset-bottom));
width: 100%;
display: flex;
justify-content: center;
.tab-item {
width: 180rpx;
height: 80rpx;
line-height: 80rpx;
text-align: center;
background: #fff;
margin: 0 20rpx;
border-radius: 10rpx;
&.active {
border: 4rpx solid #1777FF;
color: #1777FF;
}
}
}
.skin-box {
position: relative;
padding: 120rpx 12rpx;
background-color: #FFDFDF;
.watermark {
height: 56rpx !important;
position: absolute;
right: 14rpx !important;
bottom: 32rpx !important;
}
.title {
width: 260px;
position: absolute;
top: 30rpx;
left: 50%;
transform: translateX(-50%);
font-weight: bold;
text-align: center;
font-size: 36rpx;
color: #333;
}
.title-input {
min-width: 260px;
background: rgba(255, 255, 255, 0.5);
border-radius: 8rpx;
padding: 4rpx 12rpx;
border: 1rpx dashed #1777FF;
}
.title-input {
background: rgba(255, 255, 255, 0.5);
border-radius: 8rpx;
padding: 4rpx 12rpx;
width: 300rpx;
border: 1rpx dashed #1777FF;
}
.img {
position: absolute;
width: 90rpx;
height: 90rpx;
}
.happy {
top: 40rpx;
right: 30rpx;
}
.sad {
bottom: 60rpx;
left: 100rpx;
}
}
.save-action {
margin-top: 60rpx;
display: flex;
flex-direction: column;
align-items: center;
padding-bottom: 60rpx;
}
.bottom-edit-actions {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
}
.edit-tip {
font-size: 22rpx;
color: #999;
margin-top: 10rpx;
}
.color-picker {
display: flex;
gap: 16rpx;
background: #fff;
padding: 12rpx 20rpx;
border-radius: 40rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
}
.color-picker-wrapper {
display: flex;
align-items: center;
margin: 12rpx 0;
}
.hex-input {
width: 150rpx;
height: 56rpx;
background: #fff;
border-radius: 28rpx;
font-size: 24rpx;
text-align: center;
color: #333;
border: 1rpx solid #eee;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
}
.spectrum-picker {
width: 600rpx;
height: 32rpx;
background: #fff;
border-radius: 16rpx;
padding: 4rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
position: relative;
margin: 12rpx 0;
}
.slider-thumb {
position: absolute;
top: 50%;
width: 38rpx;
height: 38rpx;
background: #fff;
border-radius: 50%;
transform: translate(-50%, -50%);
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.3);
border: 2rpx solid #fff;
pointer-events: none;
}
.spectrum-bar {
width: 100%;
height: 100%;
border-radius: 12rpx;
background: linear-gradient(to right, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
}
.color-dot {
width: 32rpx;
height: 32rpx;
border-radius: 50%;
border: 2rpx solid #fff;
box-shadow: 0 0 4rpx rgba(0, 0, 0, 0.2);
margin: 0 10rpx;
}
.painter-container {
position: fixed;
left: -9999rpx;
}
</style>

View File

@ -0,0 +1,136 @@
<script setup>
import { ref } from 'vue';
const list = ref([
{ label: '澶?, bgColor: '#BE0012', textColor: '#FFFFFF', hasShadow: true },
{ label: '椤剁骇', bgColor: '#F17415', textColor: '#FFFFFF', hasShadow: true },
{ label: '浜轰笂浜?, bgColor: '#F9E962', textColor: '#FFFFFF', hasShadow: true },
{ label: 'NPC', bgColor: '#FDF5C0', textColor: '#555555', hasShadow: false },
{ label: '鎷夊畬浜?, bgColor: '#FFFFFF', textColor: '#555555', hasShadow: false }
]);
const handleAdd = (index) => {
console.log('娣诲姞椤?, index);
};
</script>
<template>
<view class="ranking-container">
<view class="ranking-table">
<view v-for="(item, index) in list" :key="index" class="ranking-row">
<!-- 鏍囩鍒?-->
<view class="label-col" :style="{ backgroundColor: item.bgColor }">
<text class="label-text" :class="{ 'with-shadow': item.hasShadow }" :style="{ color: item.textColor }">
{{ item.label }}
</text>
</view>
<!-- 鍐呭鍒?-->
<view class="content-col">
<!-- 浠呭湪绗竴琛屽睍绀烘紨绀哄浘鐗?-->
<template v-if="index === 0">
<view class="item-box">
<image src="/static/images/food.jpg" mode="aspectFill" class="item-img" />
</view>
</template>
<!-- 娣诲姞鎸夐挳 -->
<view class="add-btn" @tap="handleAdd(index)">
<text class="plus-icon">+</text>
</view>
</view>
</view>
</view>
</view>
</template>
<style lang="less">
.ranking-container {
padding: 30rpx 20rpx;
background-color: #F8F8F8;
min-height: 100vh;
}
.ranking-table {
border: 2rpx solid #333;
background-color: #fff;
display: flex;
flex-direction: column;
}
.ranking-row {
display: flex;
min-height: 180rpx;
border-bottom: 2rpx solid #333;
&:last-child {
border-bottom: none;
}
}
.label-col {
width: 200rpx;
display: flex;
align-items: center;
justify-content: center;
border-right: 2rpx solid #333;
padding: 0 10rpx;
box-sizing: border-box;
}
.label-text {
font-size: 52rpx;
font-weight: bold;
text-align: center;
line-height: 1.2;
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
&.with-shadow {
/* 寮哄姏榛戣壊鎻忚竟锛屾ā鎷?:1鏁堟灉 */
text-shadow:
2rpx 2rpx 0 #000,
-2rpx -2rpx 0 #000,
2rpx -2rpx 0 #000,
-2rpx 2rpx 0 #000,
0 2rpx 0 #000,
0 -2rpx 0 #000,
2rpx 0 0 #000,
-2rpx 0 0 #000;
}
}
.content-col {
flex: 1;
display: flex;
flex-wrap: wrap;
align-items: flex-start;
padding: 20rpx;
gap: 20rpx;
}
.item-box {
width: 140rpx;
height: 140rpx;
background-color: #eee;
border: 1rpx solid #ddd;
}
.item-img {
width: 100%;
height: 100%;
}
.add-btn {
width: 140rpx;
height: 140rpx;
background-color: #F2F2F2;
display: flex;
align-items: center;
justify-content: center;
.plus-icon {
font-size: 70rpx;
color: #DBDBDB;
font-weight: 200;
}
}
</style>

View File

@ -0,0 +1,983 @@
<script setup>
import { reactive, ref, onMounted, computed } from 'vue';
import ZdySafeBottom from '@/components/safeBottom/safeBottom.vue'
const CACHE_KEY = 'score_center_info';
const data = reactive({
form: {
examYear: '2016',
idNumber: '***********1234',
name: '肖战',
chinese: '113',
math: '123',
english: '116',
comprehensive: '195'
}
});
const total = computed(() => {
const sum = Number(data.form.chinese || 0) + Number(data.form.math || 0) + Number(data.form.english || 0) + Number(data.form.comprehensive || 0);
return sum || '';
});
const editDialog = reactive({
show: false,
data: {}
});
onMounted(() => {
loadCache();
});
function loadCache() {
const cache = uni.getStorageSync(CACHE_KEY);
if (cache) {
data.form = { ...data.form, ...cache };
}
}
function saveCache() {
uni.setStorageSync(CACHE_KEY, data.form);
}
function openEditDialog() {
editDialog.data = JSON.parse(JSON.stringify(data.form));
editDialog.show = true;
}
function saveEdit() {
data.form = { ...data.form, ...editDialog.data };
saveCache();
editDialog.show = false;
uni.showToast({
title: '保存成功',
icon: 'success'
});
}
const editTotal = computed(() => {
const sum = Number(editDialog.data.chinese || 0) + Number(editDialog.data.math || 0) + Number(editDialog.data.english || 0) + Number(editDialog.data.comprehensive || 0);
return sum || '';
});
function back(){
uni.navigateBack()
}
</script>
<template>
<view class="div">
<view v-if="$isVip()" >
<watermark dark="light" source="uni_alipay_other_score" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark', 'uni_alipay_other_score')">
<image src='/static/lottie/watermark.gif' mode="widthFix" style="width:94px;height:74px;" ></image>
</liu-drag-button>
</view>
<view class="headerBox" :style="{'padding-top':$systemInfo.statusBarHeight*2+'rpx'}">
<view class="header" >
<view class="left" @click="back()">
<image src="/static/image/other/score/center/home.png" mode=""></image>
</view>
<view class="center">
查询专区
</view>
<view class="right">
<image src="/static/image/other/score/center/rightBtn.png" mode=""></image>
</view>
</view>
</view>
<view class="" :style="{height:$systemInfo.statusBarHeight*2+88+'rpx'}">
</view>
<view class="flexcontainer" @click="openEditDialog">
<view class="rectangle_23375">
<text class="text_2"> 普通高考成绩查询 </text>
</view>
<view class="rectangle_23377">
<view class="flexcontainer_1"><text class="text_3"> 考试年份: </text><text class="text_4"> {{data.form.examYear}} 年 </text></view>
<view class="div_424"></view>
<view class="flexcontainer_2"><text class="text_5"> 身份证号: </text><text class="text_6"> {{data.form.idNumber}} </text></view>
<view class="div_425"></view>
<view class="flexcontainer_3"><view class="text_7"> 姓 <text>名:</text> </view><text class="text_8"> {{data.form.name}} </text></view>
<view class="div_426"></view>
<text class="text_9"> 科目成绩: </text>
<view class="group_48319">
<text class="text_10"> 语文 </text>
<view class="div_427"></view>
<text class="text_11"> {{data.form.chinese}} </text>
</view>
<view class="group_48320">
<text class="text_12"> 数学 </text>
<view class="div_427_1"></view>
<text class="text_13"> {{data.form.math}} </text>
</view>
<view class="group_48321">
<text class="text_14"> 外语 </text>
<view class="div_427_2"></view>
<text class="text_15"> {{data.form.english}} </text>
</view>
<view class="group_48322">
<text class="text_16"> 综合 </text>
<view class="div_427_3"></view>
<text class="text_17"> {{data.form.comprehensive}} </text>
</view>
<view class="group_48323">
<text class="text_18"> 总分 </text>
<view class="div_427_4"></view>
<text class="text_19"> {{total}} </text>
</view>
</view>
</view>
<view class="group_48324">
<text class="text_20"> 返回查询页 </text>
</view>
<view class="home">
<view class="home_indicator"></view>
</view>
<safeBottom bgColor="#f4f7fb" />
<!-- 编辑弹窗 -->
<view v-if="editDialog.show" class="editDialog">
<view class="editDialog_bg">
<view class="editDialog_header">
<text class="title">编辑成绩信息</text>
</view>
<view class="editDialog_body">
<view class="formItem">
<text>考试年份</text>
<input v-model="editDialog.data.examYear" type="digit" placeholder="如: 2016" />
</view>
<view class="formItem">
<text>身份证号</text>
<input v-model="editDialog.data.idNumber" placeholder="如: ***********1234" />
</view>
<view class="formItem">
<text>姓名</text>
<input v-model="editDialog.data.name" placeholder="请输入姓名" />
</view>
<view class="formItem">
<text>语文</text>
<input v-model="editDialog.data.chinese" type="digit" placeholder="请输入语文成绩" />
</view>
<view class="formItem">
<text>数学</text>
<input v-model="editDialog.data.math" type="digit" placeholder="请输入数学成绩" />
</view>
<view class="formItem">
<text>外语</text>
<input v-model="editDialog.data.english" type="digit" placeholder="请输入外语成绩" />
</view>
<view class="formItem">
<text>综合</text>
<input v-model="editDialog.data.comprehensive" type="digit" placeholder="请输入综合成绩" />
</view>
<view class="formItem">
<text>总分</text>
<text style="flex:1;text-align:right;font-size:28rpx;color:#333;">{{editTotal}}</text>
</view>
</view>
<view class="popup-footer">
<view class="btn-cancel" @click="editDialog.show=false">取消</view>
<view class="btn-save" @click="saveEdit">保存</view>
</view>
</view>
</view>
</view>
</template>
<style lang="scss">
* {
box-sizing: border-box;
}
.flex-row {
display: flex;
flex-direction: row;
}
.flex-col {
display: flex;
flex-direction: column;
}
.justify-start {
display: flex;
justify-content: flex-start;
}
.justify-center {
display: flex;
justify-content: center;
}
.justify-end {
display: flex;
justify-content: flex-end;
}
.justify-between {
display: flex;
justify-content: space-between;
}
.items-start {
display: flex;
align-items: flex-start;
}
.items-end {
display: flex;
align-items: flex-end;
}
.items-center {
display: flex;
align-items: center;
}
.no-shrink {
flex-shrink: 0;
}
/* 编辑弹窗样式 */
.editDialog {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
}
.editDialog_bg {
width: 680rpx;
max-height: 80vh;
background-color: #ffffff;
border-radius: 20rpx;
overflow: hidden;
display: flex;
flex-direction: column;
}
.editDialog_header {
padding: 30rpx;
border-bottom: 1rpx solid #eee;
text-align: center;
.title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
}
.editDialog_body {
padding: 20rpx 30rpx;
overflow-y: auto;
max-height: 60vh;
}
.formItem {
display: flex;
align-items: center;
padding: 20rpx 0;
border-bottom: 1rpx solid #f5f5f5;
text {
width: 200rpx;
font-size: 28rpx;
color: #666;
flex-shrink: 0;
}
input {
flex: 1;
font-size: 28rpx;
color: #333;
text-align: right;
}
}
.popup-footer {
display: flex;
padding: 20rpx 30rpx;
border-top: 1rpx solid #eee;
gap: 20rpx;
.btn-cancel,
.btn-save {
flex: 1;
height: 80rpx;
line-height: 80rpx;
text-align: center;
border-radius: 10rpx;
font-size: 28rpx;
}
.btn-cancel {
background-color: #f5f5f5;
color: #666;
}
.btn-save {
background-color: #187AFF;
color: #fff;
}
}
</style>
<style lang="scss" scoped>
/* 查询中心 */
.div {
position: relative;
width: 750rpx;
// height: 1624rpx;
overflow: hidden;
background-color: #f4f7fb;
display: flex;
flex-direction: column;
align-items: flex-start;
isolation: isolate;
.headerBox{
position: fixed;
left: 0;
top: 0;
background-color: #fff;
z-index: 999 !important;
width: 100%;
}
.header{
min-height: 88rpx;
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
.center{
font-weight: bold;
font-size: 17px;
color: #1A1A1A;
}
.left,.right{
width: 200rpx;
display: flex;
align-items: center;
}
.left{
margin-left: 30rpx;
image{
width: 42rpx;
height: 42rpx;
}
}
.right{
margin-right: 30rpx;
image{
width: 200rpx;
height: 66rpx;
}
}
}
/* flexContainer */
.flexcontainer {
position: relative;
display: flex;
width: 750rpx;
height: 1222rpx;
flex-direction: column;
align-items: flex-start;
isolation: isolate;
z-index: 2;
margin-top: 0;
margin-left: 0;
/* 矩形 23375 */
.rectangle_23375 {
position: relative;
width: 750rpx;
height: 260rpx;
background-color: #1a92ef;
margin-left: auto;
margin-right: auto;
display: flex;
flex-direction: column;
align-items: flex-start;
z-index: 0;
margin-top: 0;
border-radius: 0 0 24rpx 24rpx;
/* 消息类型设置 */
.text_2 {
position: relative;
text-align: center;
font-size: 36rpx;
font-family: 'PingFang SC';
font-weight: 400;
line-height: 50rpx;
color: #ffffff;
white-space: pre;
height: 50rpx;
margin-top: 24rpx;
margin-bottom: -7rpx;
width: max-content;
margin-left: auto;
margin-right: auto;
}
}
/* 矩形 23377 */
.rectangle_23377 {
position: relative;
width: 688rpx;
height: 1056rpx;
border-radius: 20rpx;
background-color: #ffffff;
margin-left: auto;
margin-right: auto;
display: flex;
flex-direction: column;
align-items: flex-start;
isolation: isolate;
z-index: 2;
margin-top: -94rpx;
/* flexContainer */
.flexcontainer_1 {
position: relative;
display: flex;
width: 344rpx;
height: 32rpx;
flex-direction: row;
align-items: flex-start;
z-index: 0;
margin-top: 32rpx;
margin-left: 30rpx;
/* 考试年份: */
.text_3 {
font-size: 32rpx;
font-family: 'PingFang SC';
font-weight: 400;
line-height: 44rpx;
letter-spacing: 0.34em;
color: #6f6f6f;
white-space: pre;
height: 44rpx;
margin-top: 0;
margin-bottom: -6rpx;
position: relative;
margin-left: 0;
}
/* 2016 年 */
.text_4 {
font-size: 32rpx;
font-family: 'PingFang SC';
font-weight: 400;
line-height: 44rpx;
color: #333333;
white-space: pre;
height: 44rpx;
margin-top: 0;
margin-bottom: -6rpx;
position: relative;
margin-left: 42rpx;
}
}
/* 直线 424 */
.div_424 {
width: 626rpx;
height: 0;
border: 1rpx solid #f1f1f1;
z-index: 4;
position: relative;
margin-top: 46rpx;
margin-left: 30rpx;
}
/* flexContainer */
.flexcontainer_2 {
position: relative;
display: flex;
width: 482rpx;
height: 32rpx;
flex-direction: row;
align-items: flex-start;
z-index: 1;
margin-top: 32rpx;
margin-left: 30rpx;
/* 身份证号: */
.text_5 {
font-size: 32rpx;
font-family: 'PingFang SC';
font-weight: 400;
line-height: 44rpx;
letter-spacing: 0.34em;
color: #6f6f6f;
white-space: pre;
height: 44rpx;
margin-top: 0;
margin-bottom: -6rpx;
position: relative;
margin-left: 0;
}
/* ***********1234 */
.text_6 {
font-size: 32rpx;
font-family: 'PingFang SC';
font-weight: 400;
line-height: 44rpx;
color: #333333;
white-space: pre;
height: 44rpx;
margin-top: 0;
margin-bottom: -6rpx;
position: relative;
margin-left: 42rpx;
}
}
/* 直线 425 */
.div_425 {
width: 626rpx;
height: 0;
border: 1rpx solid #f1f1f1;
z-index: 5;
position: relative;
margin-top: 46rpx;
margin-left: 30rpx;
}
/* flexContainer */
.flexcontainer_3 {
position: relative;
display: flex;
// width: 288rpx;
height: 32rpx;
flex-direction: row;
align-items: flex-start;
z-index: 3;
margin-top: 34rpx;
margin-left: 30rpx;
/* 姓 名: */
.text_7 {
width: 210rpx;
font-size: 32rpx;
font-family: 'PingFang SC';
font-weight: 400;
line-height: 44rpx;
letter-spacing: 0.34em;
color: #6f6f6f;
white-space: pre;
height: 44rpx;
margin-top: 0;
margin-bottom: -6rpx;
position: relative;
margin-left: 0;
display: flex;
justify-content: space-between;
}
/* 肖战 */
.text_8 {
font-size: 32rpx;
font-family: 'PingFang SC';
font-weight: 400;
line-height: 44rpx;
color: #333333;
white-space: pre;
height: 44rpx;
margin-top: 0;
margin-bottom: -6rpx;
position: relative;
margin-left: 64rpx;
}
}
/* 直线 426 */
.div_426 {
width: 626rpx;
height: 0;
border: 1rpx solid #f1f1f1;
z-index: 6;
position: relative;
margin-top: 46rpx;
margin-left: 30rpx;
}
/* 科目成绩: */
.text_9 {
font-size: 32rpx;
font-family: 'PingFang SC';
font-weight: 400;
line-height: 44rpx;
letter-spacing: 0.34em;
color: #6f6f6f;
white-space: pre;
height: 44rpx;
margin-top: 32rpx;
margin-bottom: -6rpx;
z-index: 2;
position: relative;
margin-left: 30rpx;
}
/* 组 48319 */
.group_48319 {
width: 626rpx;
height: 90rpx;
position: relative;
display: flex;
flex-direction: row;
align-items: flex-start;
z-index: 7;
margin-top: 48rpx;
margin-left: 30rpx;
border-radius: 16rpx;
background-color: #f4f7f9;
isolation: isolate;
/* 语文 */
.text_10 {
font-size: 32rpx;
font-family: 'PingFang SC';
font-weight: 400;
line-height: 44rpx;
color: #6f6f6f;
white-space: pre;
height: 44rpx;
margin-top: 26rpx;
margin-bottom: -6rpx;
z-index: 0;
position: relative;
margin-left: 128rpx;
}
/* 直线 427 */
.div_427 {
width: 40rpx;
height: 0;
transform: rotate(90deg);
border: 1rpx solid #e5e8ea;
z-index: 2;
position: relative;
margin-left: 84rpx;
margin-top: 44rpx;
}
/* 113 */
.text_11 {
font-size: 32rpx;
font-family: 'PingFang SC';
font-weight: 400;
line-height: 44rpx;
letter-spacing: -0.04em;
color: #0087cf;
white-space: pre;
height: 44rpx;
margin-top: 26rpx;
margin-bottom: -6rpx;
z-index: 1;
position: relative;
margin-left: 122rpx;
}
}
/* 组 48320 */
.group_48320 {
width: 626rpx;
height: 90rpx;
position: relative;
display: flex;
flex-direction: row;
align-items: flex-start;
z-index: 9;
margin-top: 32rpx;
margin-left: 30rpx;
border-radius: 16rpx;
background-color: #f4f7f9;
isolation: isolate;
/* 数学 */
.text_12 {
font-size: 32rpx;
font-family: 'PingFang SC';
font-weight: 400;
line-height: 44rpx;
color: #6f6f6f;
white-space: pre;
height: 44rpx;
margin-top: 26rpx;
margin-bottom: -6rpx;
z-index: 0;
position: relative;
margin-left: 128rpx;
}
/* 直线 427 */
.div_427_1 {
width: 40rpx;
height: 0;
transform: rotate(90deg);
border: 1rpx solid #e5e8ea;
z-index: 2;
position: relative;
margin-left: 84rpx;
margin-top: 44rpx;
}
/* 123 */
.text_13 {
font-size: 32rpx;
font-family: 'PingFang SC';
font-weight: 400;
line-height: 44rpx;
letter-spacing: -0.04em;
color: #0087cf;
white-space: pre;
height: 44rpx;
margin-top: 26rpx;
margin-bottom: -6rpx;
z-index: 1;
position: relative;
margin-left: 122rpx;
}
}
/* 组 48321 */
.group_48321 {
width: 626rpx;
height: 90rpx;
position: relative;
display: flex;
flex-direction: row;
align-items: flex-start;
z-index: 8;
margin-top: 32rpx;
margin-left: 30rpx;
border-radius: 16rpx;
background-color: #f4f7f9;
isolation: isolate;
/* 外语 */
.text_14 {
font-size: 32rpx;
font-family: 'PingFang SC';
font-weight: 400;
line-height: 44rpx;
color: #6f6f6f;
white-space: pre;
height: 44rpx;
margin-top: 26rpx;
margin-bottom: -6rpx;
z-index: 0;
position: relative;
margin-left: 128rpx;
}
/* 直线 427 */
.div_427_2 {
width: 40rpx;
height: 0;
transform: rotate(90deg);
border: 1rpx solid #e5e8ea;
z-index: 2;
position: relative;
margin-left: 84rpx;
margin-top: 44rpx;
}
/* 116 */
.text_15 {
font-size: 32rpx;
font-family: 'PingFang SC';
font-weight: 400;
line-height: 44rpx;
letter-spacing: -0.04em;
color: #0087cf;
white-space: pre;
height: 44rpx;
margin-top: 26rpx;
margin-bottom: -6rpx;
z-index: 1;
position: relative;
margin-left: 122rpx;
}
}
/* 组 48322 */
.group_48322 {
width: 626rpx;
height: 90rpx;
position: relative;
display: flex;
flex-direction: row;
align-items: flex-start;
z-index: 10;
margin-top: 32rpx;
margin-left: 30rpx;
border-radius: 16rpx;
background-color: #f4f7f9;
isolation: isolate;
/* 综合 */
.text_16 {
font-size: 32rpx;
font-family: 'PingFang SC';
font-weight: 400;
line-height: 44rpx;
color: #6f6f6f;
white-space: pre;
height: 44rpx;
margin-top: 26rpx;
margin-bottom: -6rpx;
z-index: 0;
position: relative;
margin-left: 128rpx;
}
/* 直线 427 */
.div_427_3 {
width: 40rpx;
height: 0;
transform: rotate(90deg);
border: 1rpx solid #e5e8ea;
z-index: 2;
position: relative;
margin-left: 84rpx;
margin-top: 44rpx;
}
/* 195 */
.text_17 {
font-size: 32rpx;
font-family: 'PingFang SC';
font-weight: 400;
line-height: 44rpx;
letter-spacing: -0.04em;
color: #0087cf;
white-space: pre;
height: 44rpx;
margin-top: 26rpx;
margin-bottom: -6rpx;
z-index: 1;
position: relative;
margin-left: 122rpx;
}
}
/* 组 48323 */
.group_48323 {
width: 626rpx;
height: 90rpx;
position: relative;
display: flex;
flex-direction: row;
align-items: flex-start;
z-index: 11;
margin-top: 32rpx;
margin-left: 30rpx;
border-radius: 16rpx;
background-color: #f4f7f9;
isolation: isolate;
/* 总分 */
.text_18 {
font-size: 32rpx;
font-family: 'PingFang SC';
font-weight: 400;
line-height: 44rpx;
color: #6f6f6f;
white-space: pre;
height: 44rpx;
margin-top: 26rpx;
margin-bottom: -6rpx;
z-index: 0;
position: relative;
margin-left: 128rpx;
}
/* 直线 427 */
.div_427_4 {
width: 40rpx;
height: 0;
transform: rotate(90deg);
border: 1rpx solid #e5e8ea;
z-index: 2;
position: relative;
margin-left: 84rpx;
margin-top: 44rpx;
}
/* 549 */
.text_19 {
font-size: 32rpx;
font-family: 'PingFang SC';
font-weight: 400;
line-height: 44rpx;
letter-spacing: -0.04em;
color: #0087cf;
white-space: pre;
height: 44rpx;
margin-top: 26rpx;
margin-bottom: -6rpx;
z-index: 1;
position: relative;
margin-left: 122rpx;
}
}
}
}
/* 组 48324 */
.group_48324 {
width: 688rpx;
height: 90rpx;
position: relative;
font-size: 28rpx;
font-family: 'PingFang SC';
font-weight: 400;
line-height: 40rpx;
color: #ffffff;
white-space: pre;
display: flex;
flex-direction: column;
align-items: flex-start;
z-index: 3;
margin-top: 48rpx;
margin-left: 32rpx;
border-radius: 298rpx;
background-color: #1689f9;
/* 返回查询页 */
.text_20 {
height: 40rpx;
margin-top: 32rpx;
margin-bottom: -6rpx;
position: relative;
margin-left: 274rpx;
}
}
/* home */
.home {
position: absolute;
width: 750rpx;
height: 68rpx;
top: auto;
left: 0;
background-color: rgba(255, 255, 255, 0);
right: 0;
margin-left: auto;
margin-right: auto;
bottom: 0;
display: flex;
flex-direction: column;
align-items: flex-start;
.home_indicator {}
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,622 @@
<script setup>
import { imgLocal } from '@/utils/common.js';
import wmWatermark from "@/components/wm-watermark/wm-watermark.vue"
import DateTimePicker from '@/components/dengrq-datetime-picker/dateTimePicker/index.vue';
import ZdySafeBottom from '@/components/safeBottom/safeBottom.vue'
import {
reactive,
ref,
onMounted,
computed
} from 'vue';
const CACHE_KEY = 'score_guandong_info';
const data = reactive({
bgImg:'',
form: {
subject: '文科',
avatar: '',
name: '大王',
idNumber: '',
examNumber: '123456789',
chinese: '116',
math: '104',
english: '104',
history: '104',
politics: '104',
geography: '104',
total: '104',
undergraduateTotal: '104',
ranking: '104',
technicalTotal: '104',
technicalRanking: '1455',
time: '2025-6-25'
}
});
const total = computed(() => {
const sum = Number(data.form.chinese || 0) + Number(data.form.math || 0) + Number(data.form.english || 0) +
Number(data.form.history || 0) + Number(data.form.politics || 0) + Number(data.form.geography || 0);
return sum || '';
});
function getSubjectLabels(subject) {
const isScience = subject === '理科'
return {
subject3: isScience ? '物理' : '历史',
subject4: isScience ? '化学' : '思想政治',
subject5: isScience ? '生物学' : '地理',
trackName: isScience ? '物理' : '历史',
}
}
const subjectLabels = computed(() => getSubjectLabels(data.form.subject || '文科'))
const editDialog = reactive({
show: false,
data: {}
});
const editSubjectLabels = computed(() => getSubjectLabels(editDialog.data.subject || '文科'))
onMounted(async() => {
data.bgImg= await imgLocal.getLocalImage('other/score/guandong/guandongbg.png');
loadCache();
});
function loadCache() {
const cache = uni.getStorageSync(CACHE_KEY);
if (cache) {
data.form = {
...data.form,
...cache,
subject: cache.subject || '文科'
};
}
}
function saveCache() {
uni.setStorageSync(CACHE_KEY, data.form);
}
function openEditDialog() {
editDialog.data = JSON.parse(JSON.stringify(data.form));
if (!editDialog.data.subject) {
editDialog.data.subject = '文科';
}
editDialog.show = true;
}
function saveEdit() {
data.form = {
...data.form,
...editDialog.data
};
saveCache();
editDialog.show = false;
uni.showToast({
title: '保存成功',
icon: 'success'
});
}
const editTotal = computed(() => {
const sum = Number(editDialog.data.chinese || 0) + Number(editDialog.data.math || 0) + Number(editDialog.data
.english || 0) + Number(editDialog.data.history || 0) + Number(editDialog.data.politics || 0) + Number(
editDialog.data.geography || 0);
return sum || '';
});
function back(){
uni.navigateBack()
}
function onChangeStartDate(e){
editDialog.data.time=e
console.log(e)
}
function getTime(times,status){
let list=times.split('-')
if(status){
return list[0]+'年'
}else{
return list[0]+'年'+list[1]+'月'
}
}
</script>
<template>
<wmWatermark :text="'广东省教育考试院'+data.form.time"/>
<view v-if="$isVip()" >
<watermark dark="light" source="uni_alipay_other_score" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark', 'uni_alipay_other_score')">
<image src='/static/lottie/watermark.gif' mode="widthFix" style="width:94px;height:74px;" ></image>
</liu-drag-button>
</view>
<view class="container">
<view class="headerBox" :style="{'padding-top':$systemInfo.statusBarHeight*2+'rpx'}">
<view class="header" >
<view class="left" @click="back()">
<image src="/static/image/other/score/guandong/leftIcon.png" mode=""></image>
</view>
<view class="center">
考试成绩结果
</view>
<view class="right">
<image src="/static/image/other/score/guandong/rightIcon.png" mode=""></image>
</view>
</view>
</view>
<view class="" :style="{height:$systemInfo.statusBarHeight*2+88+'rpx'}">
</view>
<view class="content">
<view class="title">
{{ getTime(data.form.time,0) }}
</view>
<view class="dec">
{{ getTime(data.form.time,1) }}广东省普通高等学校招生全国统一考试
</view>
</view>
<image class="bgimg" :src="data.bgImg" mode="widthFix"></image>
<view class="list" @click="openEditDialog">
<view class="item">
<view class="text1">
姓名
</view>
<view class="text2">
{{data.form.name}}
</view>
</view>
<view class="item">
<view class="text1">
考生号
</view>
<view class="text2">
{{data.form.examNumber}}
</view>
</view>
<view class="item">
<view class="text1">
语文
</view>
<view class="text2">
{{data.form.chinese}}
</view>
</view>
<view class="item">
<view class="text1">
数学
</view>
<view class="text2">
{{data.form.math}}
</view>
</view>
<view class="item">
<view class="text1">
英语
</view>
<view class="text2">
{{data.form.english}}
</view>
</view>
<view class="item">
<view class="text1">
{{ subjectLabels.subject3 }}
</view>
<view class="text2">
{{data.form.history}}
</view>
</view>
<view class="item">
<view class="text1">
{{ subjectLabels.subject4 }}
</view>
<view class="text2">
{{data.form.politics}}
</view>
</view>
<view class="item">
<view class="text1">
{{ subjectLabels.subject5 }}
</view>
<view class="text2">
{{data.form.geography}}
</view>
</view>
<view class="item">
<view class="text1">
普通类 ({{ subjectLabels.trackName }})总分
</view>
<view class="text2">
{{data.form.total}}
</view>
</view>
<view class="item">
<view class="text1">
本科普通类 ({{ subjectLabels.trackName }})总分 (含加分)
</view>
<view class="text2">
{{data.form.undergraduateTotal}}
</view>
</view>
<view class="item">
<view class="text1">
排位
</view>
<view class="text2">
{{data.form.ranking}}
</view>
</view>
<view class="item">
<view class="text1">
专科普通类 ({{ subjectLabels.trackName }})总分 (含加分)
</view>
<view class="text2">
{{data.form.technicalTotal}}
</view>
</view>
<view class="item">
<view class="text1">
排位
</view>
<view class="text2">
{{data.form.technicalRanking}}
</view>
</view>
</view>
</view>
<safeBottom bgColor="#F3F8FB" />
<!-- 编辑弹窗 -->
<view v-if="editDialog.show" class="editDialog">
<view class="editDialog_bg">
<view class="editDialog_header">
<text class="title">编辑成绩信息</text>
</view>
<view class="editDialog_body">
<view class="formItem">
<text>科目</text>
<view class="subject-options">
<view class="subject-option" :class="{ active: editDialog.data.subject === '文科' }"
@click="editDialog.data.subject = '文科'">文科</view>
<view class="subject-option" :class="{ active: editDialog.data.subject === '理科' }"
@click="editDialog.data.subject = '理科'">理科</view>
</view>
</view>
<view class="formItem">
<text>姓名</text>
<input v-model="editDialog.data.name" placeholder="请输入姓名" />
</view>
<view class="formItem">
<text>证件号</text>
<input v-model="editDialog.data.idNumber" placeholder="请输入证件号" />
</view>
<view class="formItem">
<text>考生号</text>
<input v-model="editDialog.data.examNumber" placeholder="请输入考生号" />
</view>
<view class="formItem">
<text>语文</text>
<input v-model="editDialog.data.chinese" type="digit" placeholder="请输入语文成绩" />
</view>
<view class="formItem">
<text>数学</text>
<input v-model="editDialog.data.math" type="digit" placeholder="请输入数学成绩" />
</view>
<view class="formItem">
<text>英语</text>
<input v-model="editDialog.data.english" type="digit" placeholder="请输入英语成绩" />
</view>
<view class="formItem">
<text>{{ editSubjectLabels.subject3 }}</text>
<input v-model="editDialog.data.history" type="digit" :placeholder="'请输入' + editSubjectLabels.subject3 + '成绩'" />
</view>
<view class="formItem">
<text>{{ editSubjectLabels.subject4 }}</text>
<input v-model="editDialog.data.politics" type="digit" :placeholder="'请输入' + editSubjectLabels.subject4 + '成绩'" />
</view>
<view class="formItem">
<text>{{ editSubjectLabels.subject5 }}</text>
<input v-model="editDialog.data.geography" type="digit" :placeholder="'请输入' + editSubjectLabels.subject5 + '成绩'" />
</view>
<view class="formItem">
<text>普通类 ({{ editSubjectLabels.trackName }})总分</text>
<text style="flex:1;text-align:right;font-size:28rpx;color:#333;">{{editTotal}}</text>
</view>
<view class="formItem">
<text>本科普通类 ({{ editSubjectLabels.trackName }})总分 (含加分)</text>
<input v-model="editDialog.data.undergraduateTotal" type="digit" placeholder="请输入本科总分" />
</view>
<view class="formItem">
<text>排位</text>
<input v-model="editDialog.data.ranking" type="digit" placeholder="请输入排位" />
</view>
<view class="formItem">
<text>专科普通类 ({{ editSubjectLabels.trackName }})总分 (含加分)</text>
<input v-model="editDialog.data.technicalTotal" type="digit" placeholder="请输入专科总分" />
</view>
<view class="formItem">
<text>排位</text>
<input v-model="editDialog.data.technicalRanking" type="digit" placeholder="请输入排位" />
</view>
<view class="formItem">
<text>时间</text>
<view style="width:100%;">
<DateTimePicker :defaultDate="editDialog.data.time" :mode="1" @onChange="onChangeStartDate" />
</view>
</view>
</view>
<view class="popup-footer">
<view class="btn-cancel" @click="editDialog.show=false">取消</view>
<view class="btn-save" @click="saveEdit">保存</view>
</view>
</view>
</view>
</template>
<style lang="scss">
page{
background-color: #F3F8FB;
}
* {
box-sizing: border-box;
}
.flex-row {
display: flex;
flex-direction: row;
}
.flex-col {
display: flex;
flex-direction: column;
}
.justify-start {
display: flex;
justify-content: flex-start;
}
.justify-center {
display: flex;
justify-content: center;
}
.justify-end {
display: flex;
justify-content: flex-end;
}
.justify-between {
display: flex;
justify-content: space-between;
}
.items-start {
display: flex;
align-items: flex-start;
}
.items-end {
display: flex;
align-items: flex-end;
}
.items-center {
display: flex;
align-items: center;
}
.no-shrink {
flex-shrink: 0;
}
/* 编辑弹窗样式 */
.editDialog {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
}
.editDialog_bg {
width: 680rpx;
max-height: 80vh;
background-color: #ffffff;
border-radius: 20rpx;
overflow: hidden;
display: flex;
flex-direction: column;
}
.editDialog_header {
padding: 30rpx;
border-bottom: 1rpx solid #eee;
text-align: center;
.title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
}
.editDialog_body {
padding: 20rpx 30rpx;
overflow-y: auto;
max-height: 60vh;
}
.formItem {
display: flex;
align-items: center;
padding: 20rpx 0;
border-bottom: 1rpx solid #f5f5f5;
text {
width: 200rpx;
font-size: 28rpx;
color: #666;
flex-shrink: 0;
}
input {
flex: 1;
font-size: 28rpx;
color: #333;
text-align: right;
}
.subject-options {
flex: 1;
display: flex;
justify-content: flex-end;
gap: 20rpx;
}
.subject-option {
padding: 8rpx 32rpx;
font-size: 26rpx;
color: #666;
border: 1rpx solid #ddd;
border-radius: 8rpx;
&.active {
color: #187AFF;
border-color: #187AFF;
background-color: rgba(24, 122, 255, 0.08);
}
}
}
.popup-footer {
display: flex;
padding: 20rpx 30rpx;
border-top: 1rpx solid #eee;
gap: 20rpx;
.btn-cancel,
.btn-save {
flex: 1;
height: 80rpx;
line-height: 80rpx;
text-align: center;
border-radius: 10rpx;
font-size: 28rpx;
}
.btn-cancel {
background-color: #f5f5f5;
color: #666;
}
.btn-save {
background-color: #187AFF;
color: #fff;
}
}
</style>
<style lang="scss" scoped>
.headerBox{
z-index: 999;
position: fixed;
left: 0;
top: 0;
width: 100%;
}
.header{
width: 100%;
height: 88rpx;
display: flex;
justify-content: space-between;
align-items: center;
.left{
width: 216rpx;
height: 88rpx;
image{
width: 36rpx;
height: 36rpx;
margin-left: 30rpx;
margin-top:46rpx;
}
}
.center{
font-weight: 400;
font-size: 19px;
color: #FFFFFF;
padding-top: 30rpx;
}
.right{
width: 216rpx;
display: flex;
align-items: center;
image{
width: 200rpx;
height: 64rpx;
}
}
}
.bgimg{
position: fixed;
width: 100%;
left: 0;
top: 0;
}
.content{
position: relative;
z-index: 9;
padding-left: 40rpx;
margin-top: 100rpx;
.title{
font-weight: 400;
font-size: 25px;
color: #FFFFFF;
}
.dec{
font-weight: 400;
font-size: 16px;
color: #FFFFFF;
white-space: nowrap;
}
}
.list{
position: relative;
z-index: 9;
margin: 72rpx 40rpx 0;
background: #FFFFFF;
box-shadow: 0px 0px 5px 0px rgba(0,0,0,0.1);
border-radius: 12px 12px 12px 12px;
padding-left: 32rpx;
.item{
height: 72rpx;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1rpx solid #F2F2F2;
.text1{
font-weight: 400;
font-size: 12px;
color: #898D96;
}
.text2{
font-weight: 400;
font-size: 12px;
color: #1A1A1A;
margin-right: 28rpx;
}
}
}
</style>

1464
pages/other/score/hebei.vue Normal file

File diff suppressed because it is too large Load Diff

180
pages/other/score/index.vue Normal file
View File

@ -0,0 +1,180 @@
<template>
<view>
<NavBar :title="data.navBar.title" :bgColor="data.navBar.bgColor" />
<view class="list-container">
<view class="item" v-for="item in source" :key="item.id" @click="goPage(item.callUrl,item.name)"
:style="{ 'background-image': `url('/static/image/other/score/${item.icon}bg.png')` }">
<view class="content flex flex-align-center">
<image class="logo" :src="`/static/image/other/score/${item.icon}.png`" mode=""></image>
<view class="name flex-1">{{ item.name }}</view>
<view class="right-button">
<!-- <uni-icons type="right" size="16"></uni-icons> -->
</view>
</view>
<!-- <view class="line" :style="{ background: item.color.lineColor }"></view> -->
</view>
</view>
</view>
</template>
<script setup>
import NavBar from '@/components/nav-bar/nav-bar.vue'
import {
ref,
reactive,
getCurrentInstance
} from 'vue'
import {
onLoad
} from "@dcloudio/uni-app";
import {
util
} from '@/utils/common.js';
const {
appContext,
proxy
} = getCurrentInstance();
// 列表
const source = ref([{
name: '查询中心',
color: {
bgColor: '#F3EAFF',
lineColor: '#B78EF5',
buttonColor: '#BA8DFF',
},
icon: 'icon1',
callUrl: "/pages/other/score/center"
},
{
name: '广东省',
color: {
bgColor: '#FFE9E9',
lineColor: '#FF6969',
buttonColor: '#FB6767',
},
icon: 'guandong',
callUrl: "/pages/other/score/guandong"
},
{
name: '重庆市',
color: {
bgColor: '#FFF0DD',
lineColor: '#FFA143',
buttonColor: '#FFA64D',
},
icon: 'chongqing',
callUrl: "/pages/other/score/chongqing"
},
{
name: '河北省',
color: {
bgColor: '#E0FFD9',
lineColor: '#56B745',
buttonColor: '#5DCD49',
},
icon: 'hebei',
callUrl: "/pages/other/score/hebei"
}
])
const data = reactive({
navBar: {
title: '高考成绩',
bgColor: '#F0F4F9',
},
type: "message"
})
onLoad((options) => {
if (options.type) {
data.type = options.type
}
proxy.$apiUserEvent('all', {
type: 'event',
key: 'score',
prefix: '.uni.other.',
value:'高考查询'
})
// #ifdef APP-PLUS&&!APP-HARMONY
util.setAndroidSystemBarColor('#ffffff')
// 保存定时器引用,以便在页面卸载时清理
setTimeout(() => {
plus.navigator.setStatusBarStyle("dark");
}, 500)
// #endif
})
function goPage(url,name) {
if (url) {
proxy.$apiUserEvent('all', {
type: 'event',
key: 'score',
prefix: '.uni.other.',
value:name
})
util.goPage(url)
} else {
uni.showToast({
title: '开发中',
icon: 'none'
})
}
}
</script>
<style>
@import '@/common/main.css';
</style>
<style lang="less">
.list-container {
background-color: #F0F4F9;
padding: 24rpx 32rpx;
}
.item {
width: 100%;
height: 160rpx;
border-radius: 24rpx;
margin-bottom: 24rpx;
padding: 40rpx 36rpx 24rpx 28rpx;
background-repeat: no-repeat;
background-size: 100% 100%;
.content {
.logo {
width: 96rpx;
height: 96rpx;
flex-shrink: 0;
}
.name {
margin-left: 32rpx;
color: #1A1A1A;
font-size: 36rpx;
font-weight: 500;
}
.right-button {
width: 140rpx;
height: 64rpx;
border-radius: 16rpx;
color: #ffffff;
font-size: 28rpx;
line-height: 64rpx !important;
text-align: right;
// display: flex;
// align-items: center;
// justify-content: center;
}
}
.line {
width: 100rpx;
height: 8rpx;
filter: blur(5px);
opacity: 0.5;
margin-top: 4rpx;
}
}
</style>

View File

@ -0,0 +1,869 @@
<template>
<view class="container">
<!-- 自定义头部导航栏 -->
<ZdyNavbar @right-click="edit" isRightButton rightButtonText="编辑" :title="data.navbar.title"
:bgColor="data.navbar.bgColor" :isBack="true" />
<view class="footer-box">
<view class="btn" :style="{'margin-bottom':$store.state.safeBottomAz+'px'}" v-for="(item, index) in data.typeList" :key="index" :class="{ active: data.type == index }"
@click="setType(index)">
{{item}}
</view>
</view>
<view
style="display: flex;align-items: center;background: #fff;border-radius: 6px;margin: 10px;padding: 4px;color: #767676;">
<image src="/static/image/other/notice.png"
style="width: 16px;height: 16px;margin-right: 10rpx;margin-left: 10rpx;"></image>
此功能不具备真实性,仅供娱乐
</view>
<image :src="data.code" mode="widthFix" style="width:calc( 100vw - 24px);margin:0 12px;" @click="previewImage">
</image>
<view class="button-container" :style="{'margin-bottom':$store.state.safeBottomAz+'px'}">
<button class="btn-save-image" @click="saveImage">保存图片</button>
</view>
<view style="width: 0px;height: 0;overflow: hidden;" v-if="data.shuaxing && currentBgImg">
<l-painter isCanvasToTempFilePath @success="successImage" @progress="progress" css="width:351px;height:460px;"
v-if="data.type == 0">
<l-painter-view css="width:351px;height:460px;position: relative;">
<l-painter-image :src="data.bgImg1" css="width:351px;height:460px;position:absolute;left:0;top:0;object-fit:fill;" />
<!-- 落款日期 -->
<l-painter-view css="position: absolute;left:82px;bottom:100px;">
<l-painter-text :css="data.textFont+data.textCss" :text="toVerticalText(data.banner.dateText)" />
</l-painter-view>
<!-- 敬赠落款 -->
<l-painter-view css="position: absolute;left:100px;bottom:100px;">
<l-painter-text :css="data.textFont+data.textCss" :text="toVerticalText(data.banner.donorLine)" />
</l-painter-view>
<!-- 受赠对象(画布宽351,原 right:72 → left:267,避免鸿蒙 right 定位失效) -->
<l-painter-view css="position: absolute;top:80px;left:267px;">
<l-painter-text :css="data.textFont+data.textCss" :text="toVerticalText(data.banner.recipientLine)" />
</l-painter-view>
<!-- 中心大字装饰(原 right 均改为 left,字宽按 20px) -->
<l-painter-view css="position: absolute;top:80px;left:168px;">
<l-painter-text :css="data.textFont+data.textCss1" :text="data.banner.centerChar" />
</l-painter-view>
<l-painter-view css="position: absolute;top:108px;left:120px;">
<l-painter-text :css="data.textFont+data.textCss1" :text="data.banner.centerChar" />
</l-painter-view>
<l-painter-view css="position: absolute;top:108px;left:219px;">
<l-painter-text :css="data.textFont+data.textCss1" :text="data.banner.centerChar" />
</l-painter-view>
<l-painter-view css="position: absolute;top:150px;left:102px;">
<l-painter-text :css="data.textFont+data.textCss1" :text="data.banner.centerChar" />
</l-painter-view>
<l-painter-view css="position: absolute;top:150px;left:235px;">
<l-painter-text :css="data.textFont+data.textCss1" :text="data.banner.centerChar" />
</l-painter-view>
<l-painter-view css="position: absolute;top:192px;left:136px;">
<l-painter-text :css="data.textFont+data.textCss1" :text="data.banner.centerChar" />
</l-painter-view>
<l-painter-view css="position: absolute;top:192px;left:211px;">
<l-painter-text :css="data.textFont+data.textCss1" :text="data.banner.centerChar" />
</l-painter-view>
<!-- 中心主字 -->
<l-painter-view css="position: absolute;top:105px;left:135px;">
<l-painter-text :css="data.textFont+data.textCss2" :text="data.banner.centerChar" />
</l-painter-view>
<!-- 上句赞语 -->
<l-painter-view css="position: absolute;bottom:160px;left:0;">
<l-painter-text :css="data.textFont+data.textCss3" :text="data.banner.phraseTop" />
</l-painter-view>
<!-- 下句赞语 -->
<l-painter-view css="position: absolute;bottom:118px;left:0;">
<l-painter-text :css="data.textFont+data.textCss3" :text="data.banner.phraseBottom" />
</l-painter-view>
<l-painter-view v-if="$isVip()" css="position: absolute;left:127px;bottom:132px;">
<l-painter-image src="/static/image/other/card/shuiyin2.png" css="width: 97px;height: 28px;" />
</l-painter-view>
</l-painter-view>
</l-painter>
<l-painter isCanvasToTempFilePath @success="successImage" @progress="progress" css="width:351px;height:460px;"
v-else-if="data.type == 1">
<l-painter-view css="width:351px;height:460px;position: relative;">
<l-painter-image :src="data.bgImg2" css="width:351px;height:460px;position:absolute;left:0;top:0;object-fit:fill;" />
<!-- 落款日期 -->
<l-painter-view css="position: absolute;left:82px;bottom:100px;">
<l-painter-text :css="data.textFont+data.textCss" :text="toVerticalText(data.banner.dateText)" />
</l-painter-view>
<!-- 敬赠落款 -->
<l-painter-view css="position: absolute;left:100px;bottom:100px;">
<l-painter-text :css="data.textFont+data.textCss" :text="toVerticalText(data.banner.donorLine)" />
</l-painter-view>
<!-- 受赠对象 -->
<l-painter-view css="position: absolute;top:80px;left:267px;">
<l-painter-text :css="data.textFont+data.textCss" :text="toVerticalText(data.banner.recipientLine)" />
</l-painter-view>
<!-- 上句赞语(右列) -->
<l-painter-view css="position: absolute;top:95px;left:188px;">
<l-painter-text :css="data.textFont+data.textCss4" :text="toVerticalText(data.banner.phraseTop)" />
</l-painter-view>
<!-- 下句赞语(左列) -->
<l-painter-view css="position: absolute;top:95px;left:118px;">
<l-painter-text :css="data.textFont+data.textCss4" :text="toVerticalText(data.banner.phraseBottom)" />
</l-painter-view>
<l-painter-view v-if="$isVip()" css="position: absolute;left:127px;bottom:132px;">
<l-painter-image src="/static/image/other/card/shuiyin2.png" css="width: 97px;height: 28px;" />
</l-painter-view>
</l-painter-view>
</l-painter>
</view>
<!-- 编辑弹窗 -->
<view v-if="showEditPopup" class="popup-overlay">
<view class="popup-content">
<view class="popup-header">
<text class="popup-title">编辑锦旗</text>
<text class="popup-close" @click="closeEditPopup">×</text>
</view>
<view class="popup-body">
<view class="form-row">
<text class="form-label">落款日期:</text>
<input class="form-input" v-model="editForm.dateText" type="text" placeholder="如:二零二六年四月" />
</view>
<view class="form-row">
<text class="form-label">敬赠落款:</text>
<input class="form-input" v-model="editForm.donorLine" type="text" placeholder="如:张三敬赠" />
</view>
<view class="form-row">
<text class="form-label">受赠对象:</text>
<input class="form-input" v-model="editForm.recipientLine" type="text" placeholder="如:赠:某某团队" />
</view>
<view class="form-row" v-if="data.type == 0">
<text class="form-label">中心大字:</text>
<input class="form-input" v-model="editForm.centerChar" type="text" maxlength="1" placeholder="单字,如:牛" />
</view>
<view class="form-row">
<text class="form-label">上句赞语:</text>
<input class="form-input" v-model="editForm.phraseTop" type="text" />
</view>
<view class="form-row">
<text class="form-label">下句赞语:</text>
<input class="form-input" v-model="editForm.phraseBottom" type="text" />
</view>
</view>
<view class="popup-footer">
<button class="btn-cancel" @click="closeEditPopup">取消</button>
<button class="btn-save" @click="saveEditForm">保存</button>
</view>
</view>
</view>
<!-- 水印 -->
<view v-if="$isVip()">
<liu-drag-button :canDocking="false"
@clickBtn="$goRechargePage('watermark', 'uni_alipay_other_silkBanner')">
<image src='/static/lottie/watermark.gif' mode="widthFix" style="width:94px;height:74px;" ></image>
</liu-drag-button>
</view>
</view>
</template>
<script setup>
// 自定义头部
import ZdyNavbar from "@/components/nav-bar/nav-bar.vue"
import { imgLocal } from '@/utils/common.js';
import {
ref,
reactive,
computed,
getCurrentInstance
} from "vue";
import {
onLoad,
onShow,
onReady,
onPullDownRefresh,
onReachBottom
} from "@dcloudio/uni-app";
const {
appContext,
proxy
} = getCurrentInstance();
function defaultBanner(type) {
const banners = {
0: {
dateText: '二零二六年四月',
donorLine: '安娜敬赠',
recipientLine: '赠:良子大胃袋',
centerChar: '牛',
phraseTop: '焖子大王',
phraseBottom: '靠谱'
},
1: {
dateText: '二零二六年五月',
donorLine: '小狗敬赠',
recipientLine: '赠:大小姐',
phraseTop: '我勒个豆',
phraseBottom: '太有实力'
}
// 可以继续扩展更多 type
}
// 返回对应 type 的数据,如果没有则返回默认 type 1
return banners[type] || banners[1]
}
function mergeBanner(raw, type) {
return Object.assign(defaultBanner(type), raw && typeof raw === 'object' ? raw : {})
}
/** 竖排:用换行代替窄 width 自动折行(鸿蒙 canvas measureText 下窄宽不换行) */
function toVerticalText(text) {
return String(text ?? '').split('').join('\n')
}
async function loadBannerBg(path, fallback) {
try {
const localPath = await imgLocal.getLocalImage(path)
return localPath || fallback
} catch (e) {
console.error('背景图加载失败,使用本地兜底', path, e)
return fallback
}
}
function refreshPainter() {
data.shuaxing = false
setTimeout(() => {
data.shuaxing = true
}, 80)
}
const data = reactive({
isShow:false,
type: 0,
bgImg1:'',
bgImg2:'',
banner: mergeBanner(null, 0),
shuaxing: false,
navbar: {
title: "锦旗",
bgColor: '#EDEDED',
},
width: 800,
height: 600,
code: '',
textCss: 'text-align:center;font-weight:400;width:12px;color:#FFDD6D;font-size:12px;line-height:1.15em;',
textCss1: 'text-align:center;font-weight:400;width:20px;color:#FFDD6D;font-size:20px;line-height:1em;',
textCss2: 'text-align:center;font-weight:400;width:82px;color:#FFDD6D;font-size:78px;line-height:1em;',
textCss3: 'text-align:center;width:351px;font-weight:400;letter-spacing:16px;color:#FFDD6D;font-size:28px;line-height:1em;',
textCss4: 'text-align:center;width:44px;font-weight:400;color:#FFDD6D;font-size:40px;line-height:1.15em;',
textFont: 'font-family:"certificate2";',
typeList: ['样式1', '样式2']
})
const currentBgImg = computed(() => data.type === 0 ? data.bgImg1 : data.bgImg2)
// 弹窗相关
const showEditPopup = ref(false);
const editForm = ref({});
onLoad(async(option) => {
uni.showLoading({
title: "生成中"
})
data.bgImg1 = await loadBannerBg('other/silkBanner/banner1.png', '/static/image/other/silkBanner/banner1.png')
data.bgImg2 = await loadBannerBg('other/silkBanner/banner2.png', '/static/image/other/silkBanner/banner2.png')
// 进入锦旗页面埋点
proxy.$apiUserEvent('all', {
type: 'event',
key: 'silkBanner',
prefix: '.uni.other.',
value: "锦旗"
})
data.banner = mergeBanner(uni.getStorageSync("silkBanner" + data.type), data.type)
const config = uni.getStorageSync('config')
console.log("---config---", config);
const font = config?.config?.['client.uniapp.font']
const startPaint = () => {
data.isShow = true
data.shuaxing = true
}
if (!font?.certificate2) {
console.warn('字体配置缺失,直接生成锦旗')
startPaint()
return
}
console.log("字体地址信息", font.certificate2);
const fontUrl = font.certificate2;
const fontName = 'certificate2';
const loadFont = (path) => {
uni.loadFontFace({
family: fontName,
source: `url("${path}")`,
success() {
startPaint()
console.log('字体加载成功');
},
fail(err) {
startPaint()
console.error('字体加载失败', err);
}
});
};
// #ifdef H5
loadFont(fontUrl);
// #endif
// #ifndef H5
const savedFontPath = uni.getStorageSync('certificate2_font_path');
if (savedFontPath) {
loadFont(savedFontPath);
} else {
uni.downloadFile({
url: fontUrl,
success: (res) => {
if (res.statusCode === 200) {
uni.saveFile({
tempFilePath: res.tempFilePath,
success: (saveRes) => {
const savedPath = saveRes.savedFilePath;
uni.setStorageSync('certificate2_font_path', savedPath);
console.log("字体保存路径", savedPath);
loadFont(savedPath);
},
fail: (err) => {
console.error('保存文件失败', err);
loadFont(res.tempFilePath);
}
});
} else {
startPaint()
}
},
fail: (err) => {
console.error('下载字体失败', err);
startPaint()
}
});
}
// #endif
})
onReady(() => {
})
onShow(() => {})
onPullDownRefresh(() => {
setTimeout(() => {
uni.stopPullDownRefresh();
}, 1000);
})
onReachBottom(() => {
})
function setType(index) {
if (data.type === index) return
uni.showLoading({
title: "生成中"
})
data.type = index
data.banner = mergeBanner(uni.getStorageSync("silkBanner" + data.type), data.type)
data.code = ''
refreshPainter()
}
function successImage(e) {
data.code = e
uni.hideLoading()
}
function progress(e) {
// console.log(e)
}
// 打开编辑弹窗
function edit() {
editForm.value = mergeBanner(JSON.parse(JSON.stringify(data.banner)), data.type)
showEditPopup.value = true;
}
// 关闭编辑弹窗
function closeEditPopup() {
showEditPopup.value = false;
}
// 保存编辑表单
function saveEditForm() {
uni.showLoading({
title: "生成中"
})
data.banner = mergeBanner(editForm.value, data.type)
data.code = ''
refreshPainter()
uni.setStorageSync("silkBanner" + data.type, data.banner)
// 关闭弹窗
showEditPopup.value = false;
}
/**
* 将本地图片路径通过 Canvas 转换为 File 对象
* @param {string} localPath - 本地图片路径(如从 uni.chooseImage 获取的 tempFilePath)
* @param {Object} options - 可选参数
* @param {string} options.format - 输出格式 'image/png' 或 'image/jpeg',默认 'image/png'
* @param {number} options.quality - 图片质量(仅 jpeg 有效),0~1,默认 0.92
* @param {number} options.maxWidth - 最大宽度(等比缩放),不填则使用原图尺寸
* @param {number} options.maxHeight - 最大高度(等比缩放),不填则使用原图尺寸
* @returns {Promise<File>} 返回一个 Promise,resolve 为 File 对象
*/
function convertLocalImageToFile(localPath) {
return new Promise((resolve, reject) => {
// #ifdef APP-PLUS&&!APP-HARMONY
plus.io.resolveLocalFileSystemURL(localPath, (entry) => {
entry.file((file) => {
const reader = new plus.io.FileReader();
reader.onload = (e) => {
resolve(e.target.result)
};
reader.onerror = (err) => reject(err);
reader.readAsDataURL(file);
}, reject);
}, reject);
// #endif
// #ifndef APP-PLUS
reject(new Error('plus.io not supported'))
// #endif
});
}
// 预览图片
function previewImage() {
if (data.code) {
uni.previewImage({
urls: [data.code],
current: 0
});
}
}
// 保存图片
function saveImage() {
if (data.code) {
console.log(data.code)
uni.showLoading({
title: '保存中...'
});
try {
// 检查是否为base64格式
if (data.code.startsWith('data:image')) {
// 处理base64格式图片
console.log('开始处理base64图片');
uni.base64ToTempFile({
base64: data.code.split(',')[1], // 去除base64前缀
success: (res) => {
console.log('base64转换成功', res);
if (res.tempFilePath) {
uni.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: () => {
console.log('保存图片成功');
uni.hideLoading();
uni.showToast({
title: '保存成功',
icon: 'success'
});
},
fail: (err) => {
console.log('保存图片失败', err);
uni.hideLoading();
uni.showToast({
title: '保存失败',
icon: 'none'
});
}
});
} else {
console.log('base64转换失败,无临时文件路径');
uni.hideLoading();
uni.showToast({
title: '转换失败',
icon: 'none'
});
}
},
fail: (err) => {
console.log('base64转换失败', err);
uni.hideLoading();
uni.showToast({
title: '转换失败',
icon: 'none'
});
}
});
} else if (data.code.startsWith('_') || data.code.includes('temp') || data.code.includes('cache') || data
.code.includes('_doc') || data.code.includes('_tmp')) {
// 处理本地临时文件
console.log('开始处理本地临时文件');
uni.saveImageToPhotosAlbum({
filePath: data.code,
success: () => {
console.log('保存本地文件成功');
uni.hideLoading();
uni.showToast({
title: '保存成功',
icon: 'success'
});
},
fail: (err) => {
console.log('保存本地文件失败', err);
uni.hideLoading();
uni.showToast({
title: '保存失败',
icon: 'none'
});
}
});
} else {
// 处理网络图片
console.log('开始处理网络图片');
uni.downloadFile({
url: data.code,
success: (res) => {
console.log('下载图片成功', res);
if (res.statusCode === 200 && res.tempFilePath) {
uni.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: () => {
console.log('保存图片成功');
uni.hideLoading();
uni.showToast({
title: '保存成功',
icon: 'success'
});
},
fail: (err) => {
console.log('保存图片失败', err);
uni.hideLoading();
uni.showToast({
title: '保存失败',
icon: 'none'
});
}
});
} else {
console.log('下载图片失败,状态码:', res.statusCode);
uni.hideLoading();
uni.showToast({
title: '下载失败',
icon: 'none'
});
}
},
fail: (err) => {
console.log('下载图片失败', err);
uni.hideLoading();
uni.showToast({
title: '下载失败',
icon: 'none'
});
}
});
}
} catch (error) {
console.log('保存图片异常', error);
uni.hideLoading();
uni.showToast({
title: '保存失败',
icon: 'none'
});
}
} else {
uni.showToast({
title: '暂无图片可保存',
icon: 'none'
});
}
}
</script>
<style lang="scss">
page {
background-color: #ededed;
}
.box {
width: 750rpx;
height: 500rpx;
// background-image: url('/static/image/other/certificate/graduate/graduate1.png');
background-repeat: no-repeat;
background-size: 100% 100%;
position: relative;
.title {
position: absolute;
width: 288rpx;
height: 36rpx;
display: flex;
justify-content: center;
align-items: center;
left: 92rpx;
top: 134rpx;
image {
height: 36rpx;
}
}
.icon {
position: absolute;
width: 108rpx;
height: 108rpx;
display: flex;
justify-content: center;
align-items: center;
right: 82rpx;
bottom: 124rpx;
image {
height: 108rpx;
}
}
}
@font-face {
font-family: "card";
src: url("/static/font/card.ttf");
}
* {
box-sizing: content-box;
}
.aadadad {
text-align: center;
}
.heiti {
font-family: "SimHei", "Microsoft YaHei", sans-serif;
}
/* 弹窗样式 */
.popup-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
}
.popup-content {
background-color: #fff;
border-radius: 10px;
width: 90%;
max-width: 500px;
max-height: 80vh;
overflow-y: auto;
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx;
border-bottom: 1rpx solid #eee;
}
.popup-title {
font-size: 32rpx;
font-weight: 400;
color: #333;
}
.popup-close {
font-size: 48rpx;
color: #999;
cursor: pointer;
}
.popup-body {
padding: 20rpx;
}
.form-section {
margin-bottom: 30rpx;
padding: 20rpx;
background-color: #f5f5f5;
border-radius: 8rpx;
}
.section-title {
font-size: 28rpx;
font-weight: 400;
color: #333;
margin-bottom: 20rpx;
display: block;
}
.form-row {
display: flex;
align-items: center;
margin-bottom: 20rpx;
}
.form-label {
width: 200rpx;
font-size: 26rpx;
color: #333;
}
.form-input {
flex: 1;
padding: 15rpx;
border: 1rpx solid #ddd;
border-radius: 4rpx;
font-size: 26rpx;
}
.form-separator {
margin: 0 10rpx;
font-size: 26rpx;
color: #333;
}
.form-total {
margin-top: 10rpx;
padding-top: 10rpx;
border-top: 1rpx dashed #ddd;
}
.form-total-input {
background-color: #f9f9f9;
color: #ff6b35;
font-weight: 400;
}
/* 上传图片样式 */
.upload-container {
flex: 1;
position: relative;
}
.upload-btn {
width: 120rpx;
height: 160rpx;
border-radius: 8rpx;
border: 1rpx dashed #ddd;
display: flex;
justify-content: center;
align-items: center;
background-color: #f5f5f5;
}
.upload-text {
font-size: 22rpx;
color: #999;
text-align: center;
}
.upload-image {
width: 98rpx;
height: 115rpx;
border-radius: 1rpx;
object-fit: cover;
border: 1px dashed #ddd;
}
.popup-footer {
display: flex;
justify-content: space-between;
padding: 20rpx;
border-top: 1rpx solid #eee;
}
.btn-cancel,
.btn-save {
width: calc(48% - 40rpx);
padding: 20rpx;
border-radius: 4rpx;
font-size: 28rpx;
}
.btn-cancel {
background-color: #f5f5f5;
color: #333;
// border: 1rpx solid #ddd;
}
.btn-save {
background-color: #187AFF;
color: #fff;
border: none;
}
/* 保存图片按钮 */
.button-container {
display: flex;
justify-content: center;
position: fixed;
left: 0;
bottom: 86px;
width: 100vw;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
z-index: 10;
}
.btn-save-image {
background: linear-gradient(90deg, #187AFF 0%, #3295FC 100%);
color: #fff;
border: none;
padding: 18rpx 60rpx;
border-radius: 50rpx;
font-size: 26rpx;
font-weight: 400;
box-shadow: 0 3rpx 10rpx rgba(7, 66, 193, 0.3);
transition: all 0.3s ease;
text-align: center;
min-width: 160rpx;
}
.btn-save-image:hover {
transform: translateY(-2rpx);
box-shadow: 0 4rpx 12rpx rgba(7, 72, 193, 0.4);
}
.btn-save-image:active {
transform: translateY(0);
box-shadow: 0 2rpx 6rpx rgba(7, 140, 193, 0.3);
}
.container {
padding-bottom: 220rpx;
}
.footer-box {
position: fixed;
left: 0;
bottom: 16px;
width: 100vw;
display: flex;
justify-content: center;
padding-bottom: constant(safe-area-inset-bottom); // 兼容 IOS<11.2
padding-bottom: env(safe-area-inset-bottom); // 兼容 IOS>11.2
z-index: 10;
.btn {
font-size: 18px;
color: #767676;
width: 180rpx;
height: 80rpx;
line-height: 80rpx;
background: #FFFFFF;
border-radius: 8px 8px 8px 8px;
background: #FFFFFF;
text-align: center;
margin: 0 24rpx;
border: 2px solid #FFFFFF;
color: #767676;
}
.active {
color: #1777FF;
border: 2px solid #1777FF;
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -1,93 +1,140 @@
<template>
<view class="container">
<NavBar title="选择机票" bgColor="#F0F4F9" isBack></NavBar>
<NavBar :title="`选择${type == 'airTicket' ? '机票' : '火车票'}`" bgColor="#F0F4F9" isBack></NavBar>
<view class="content">
<view class="app-card" v-for="(item, index) in appList" :key="index" @click="handleItemClick(item)">
<!-- Background Watermark -->
<image class="watermark" :src="item.bgImage" mode="heightFix"></image>
<template v-for="(item, index) in appList" :key="index">
<view class="app-card"
v-if="(type == 'airTicket' && item.airPath) || (type == 'trainTicket' && item.trainPath)"
@click="handleItemClick(item)">
<!-- Background Watermark -->
<image class="watermark" :src="item.bgImage" mode="heightFix"></image>
<!-- Front Content -->
<view class="card-left">
<view class="logo-box">
<image class="logo" :src="item.logo" mode="aspectFit"></image>
<!-- Hot Tag for Fliggy -->
<view v-if="item.isHot" class="hot-tag">
<image style="width: 72rpx;height:32rpx" src="/static/image/index/hot-icon.png"
mode="aspectFit"></image>
<!-- Front Content -->
<view class="card-left">
<view class="logo-box">
<image class="logo" :src="item.logo" mode="aspectFit"></image>
<!-- Hot Tag for Fliggy -->
<view v-if="item.isHot" class="hot-tag">
<image style="width: 72rpx;height:32rpx" src="/static/image/index/hot-icon.png"
mode="aspectFit"></image>
</view>
</view>
<text class="app-name">{{ item.name }}</text>
</view>
<text class="app-name">{{ item.name }}</text>
</view>
<uni-icons type="right" size="18" color="#CCCCCC"></uni-icons>
</view>
<uni-icons type="right" size="18" color="#CCCCCC"></uni-icons>
</view>
</template>
</view>
</view>
</template>
<script setup>
import NavBar from '@/components/nav-bar/nav-bar.vue';
import { reactive, toRefs, getCurrentInstance } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { util } from '@/utils/common.js'; // Assuming util exists for navigation, similar to previous tasks
import {
reactive,
toRefs,
getCurrentInstance,
ref
} from 'vue';
import {
onLoad
} from '@dcloudio/uni-app';
import {
util
} from '@/utils/common.js'; // Assuming util exists for navigation, similar to previous tasks
const {
appContext,
proxy
} = getCurrentInstance();
let type = ref('airTicket')
const appList = [
{
name: '携程APP',
logo: '/static/image/other/tickets-app/trip-com-logo.png',
bgImage: '/static/image/other/tickets-app/trip-com-bg.png',
airPath: '/pages/other/air-tickets/ctrip-air-tickets/ctrip-air-tickets',
trainPath: "/pages/other/train-tickets/ctrip-train-tickets/ctrip-train-tickets",
isHot: false
},
{
name: '铁路12306',
logo: '/static/image/other/tickets-app/12306-logo.png',
bgImage: '/static/image/other/tickets-app/12306-bg.png',
airPath: '',
trainPath: "/pages/other/train-tickets/12306-tickets/12306-tickets",
isHot: false
}, {
name: '去哪儿APP',
logo: '/static/image/other/tickets-app/qvnar-logo.png',
bgImage: '/static/image/other/tickets-app/qvnar-bg.png',
path: '/pages/other/air-tickets/qunar-air-tickets/qunar-air-tickets',
airPath: '/pages/other/air-tickets/qunar-air-tickets/qunar-air-tickets',
trainPath: "/pages/other/train-tickets/qunar-train-tickets/qunar-train-tickets",
isHot: false
},
{
name: '飞猪APP',
logo: '/static/image/other/tickets-app/fliggy-logo.png',
bgImage: '/static/image/other/tickets-app/fliggy-bg.png',
path: '/pages/other/air-tickets/fliggy-air-tickets/fliggy-air-tickets',
airPath: '/pages/other/air-tickets/fliggy-air-tickets/fliggy-air-tickets',
trainPath: "/pages/other/train-tickets/fliggy-train-tickets/fliggy-train-tickets",
isHot: true
},
{
name: '携程APP',
logo: '/static/image/other/tickets-app/trip-com-logo.png',
bgImage: '/static/image/other/tickets-app/trip-com-bg.png',
path: '/pages/other/air-tickets/ctrip-air-tickets/ctrip-air-tickets',
isHot: false
},
// {
// name: '铁路12306',
// logo: '/static/image/other/tickets-app/12306-logo.png',
// bgImage: '/static/image/other/tickets-app/12306-bg.png',
// path: '/pages/other/train-tickets/12306-tickets/12306-tickets',
// isHot: false
// }
]
onLoad((option) => {
// 进入机票页面埋点
proxy.$apiUserEvent('all', {
type: 'event',
key: 'ticket',
prefix: '.uni.other.',
value: "机票"
})
})
const handleItemClick = (item) => {
if (item.path) {
util.goPage(item.path)
const appType = option.type
if (appType == "trainTicket") {
type.value = "trainTicket"
// 进入高铁票页面埋点
proxy.$apiUserEvent('all', {
type: 'event',
key: 'train_ticket',
prefix: '.uni.other.',
value: "高铁票"
})
} else {
uni.showToast({
title: '开发中',
icon: 'none'
type.value = "airTicket"
// 进入机票页面埋点
proxy.$apiUserEvent('all', {
type: 'event',
key: 'ticket',
prefix: '.uni.other.',
value: "机票"
})
}
}
})
/**
* 跳转火车票/飞机票页面
*/
const handleItemClick = (item) => {
if (type.value == 'trainTicket') {
if (item.trainPath && item.trainPath != "开发中") {
util.goPage(item.trainPath)
} else {
uni.showToast({
title: '开发中',
icon: 'none'
})
}
} else {
if (item.airPath) {
util.goPage(item.airPath)
} else {
uni.showToast({
title: '开发中',
icon: 'none'
})
}
}
}
</script>
<style lang="less" scoped>
.container {

View File

@ -1,9 +1,9 @@
<template>
<!-- 水印 -->
<view v-if="$isVip()">
<watermark dark="light" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark')">
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
<watermark dark="light" source="uni_alipay_other_tickets_12306" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark', 'uni_alipay_other_tickets_12306')">
<image src='/static/lottie/watermark.gif' mode="widthFix" style="width:94px;height:74px;" ></image>
</liu-drag-button>
</view>
<view class="container">
@ -24,8 +24,8 @@
<view class="order-info-box">
<view class="order-number-row">
<text class="order-label">订单号:{{ ticketsInfo.orderInfo.orderNo }}</text>
<image class="copy-btn" @click="copyOrderNo"
src="/static/image/other/train-tickets/12306-tickets/copy-button.png"></image>
<image class="copy-btn" src="/static/image/other/train-tickets/12306-tickets/copy-button.png">
</image>
</view>
<text class="order-time">下单时间:{{ ticketsInfo.orderInfo.orderTime }}</text>
</view>
@ -144,8 +144,7 @@
<!-- 分享区 -->
<view class="share-section">
<text class="share-tips">美好旅途,快与朋友一起分享吧~</text>
<image style="width: 158rpx;height: 50rpx;"
src="/static/image/other/train-tickets/12306-tickets/share.png">
<image style="width: 158rpx;height: 50rpx;" src="/static/image/other/train-tickets/12306-tickets/share.png">
</image>
</view>
@ -157,11 +156,9 @@
<!-- 广告Banner -->
<view class="ad-banner">
<image class="bg-image" src="/static/image/other/train-tickets/12306-tickets/hotel-bg.png"
mode="widthFix">
<image class="bg-image" src="/static/image/other/train-tickets/12306-tickets/hotel-bg.png" mode="widthFix">
</image>
<image class="bg-image" style="opacity: 0;position: relative;"
src="/static/image/other/train-tickets/12306-tickets/hotel-bg.png" mode="widthFix">
<image class="bg-image" style="opacity: 0;position: relative;" src="/static/image/other/train-tickets/12306-tickets/hotel-bg.png" mode="widthFix">
</image>
<view class="hotel-ad">
<view class="hotel-search-row">
@ -190,6 +187,7 @@
src="/static/image/other/train-tickets/12306-tickets/bottom-bg.png" mode="widthFix">
</image>
</view>
<ZdySafeBottom bgColor="#fff"/>
</view>
</scroll-view>
</view>
@ -197,12 +195,13 @@
<script setup>
import NavBar from '@/components/nav-bar/nav-bar.vue';
import ZdySafeBottom from '@/components/safeBottom/safeBottom.vue'
import {
ref,
reactive,
toRefs,
computed,
getCurrentInstance
computed
} from 'vue';
import {
onLoad,
@ -210,11 +209,6 @@ import {
} from '@dcloudio/uni-app';
import { util } from '@/utils/common.js';
const {
appContext,
proxy
} = getCurrentInstance();
const buttonGroup = [{
name: "编辑车票信息",
click: () => {
@ -226,24 +220,7 @@ function goEdit() {
util.goPage('/pages/other/train-tickets/edit/edit')
}
const ticketType = [
{
label: '成人票',
value: '1'
},
{
label: '儿童票',
value: '2'
},
{
label: '学生票',
value: '3'
},
{
label: '残疾军人票',
value: '4'
}
]
const statusBarHeight = ref(20);
const data = reactive({
@ -364,14 +341,6 @@ const calculateNightCount = () => {
onLoad(() => {
const sys = uni.getSystemInfoSync();
statusBarHeight.value = sys.statusBarHeight || 20;
// 进入高铁票页面埋点
proxy.$apiUserEvent('all', {
type: 'event',
key: 'train_ticket',
prefix: '.uni.other.',
value: "高铁票"
})
});
onShow(() => {
@ -379,7 +348,7 @@ onShow(() => {
Object.assign(data.ticketsInfo, uni.getStorageSync('ticketsInfo'))
calculateNightCount()
}
// #ifdef APP-PLUS
// #ifdef APP-PLUS&&!APP-HARMONY
util.setAndroidSystemBarColor('#ffffff')
setTimeout(() => {
plus.navigator.setStatusBarStyle("light");
@ -393,22 +362,6 @@ onShow(() => {
}
});
const goBack = () => {
uni.navigateBack();
};
const copyOrderNo = () => {
uni.setClipboardData({
data: orderInfo.value.orderNo,
success: () => {
uni.showToast({
title: '复制成功',
icon: 'none'
});
}
});
};
const handleAction = (action) => {
uni.showToast({
title: `点击了${action}`,

File diff suppressed because it is too large Load Diff

View File

@ -14,13 +14,21 @@
<text class="label">订单号</text>
<input class="input" v-model="ticketsInfo.orderInfo.orderNo" />
</view>
<picker mode="date" fields="day" :value="getPickerDate(ticketsInfo.orderInfo.orderTime)"
@change="onOrderTimeChange">
<picker v-if="app == '12306'" mode="date" fields="day"
:value="getPickerDate(ticketsInfo.orderInfo.orderTime)" @change="onOrderTimeChange">
<view class="form-item">
<text class="label">下单时间</text>
<view class="input">{{ ticketsInfo.orderInfo.orderTime }}</view>
</view>
</picker>
<view v-if="app != '12306' && app != 'fliggy'" class="form-item">
<text class="label">订单总价</text>
<input class="input" type="number" v-model="ticketsInfo.orderInfo.price" />
</view>
<view v-if="app == 'fliggy'" class="form-item">
<text class="label">已购服务</text>
<input class="input" v-model="ticketsInfo.serviceText" />
</view>
</view>
</view>
@ -71,10 +79,18 @@
<view class="input">{{ ticketsInfo.ticketInfo.arrivalTime }}</view>
</view>
</picker>
<!-- <view class="form-item">
<view class="form-item">
<text class="label">历时</text>
<input class="input" v-model="ticketsInfo.ticketInfo.duration" disabled />
</view> -->
<input class="input" v-model="ticketsInfo.ticketInfo.duration" />
</view>
<view v-if="app == 'ctrip'" class="form-item">
<text class="label">火车名称</text>
<input class="input" v-model="ticketsInfo.ticketInfo.trainName" />
</view>
<view v-if="app == 'fliggy'" class="form-item">
<text class="label">车票状态</text>
<input class="input" v-model="ticketsInfo.ticketInfo.status" />
</view>
</view>
</view>
@ -95,7 +111,8 @@
<text class="label">姓名</text>
<input class="input" v-model="passenger.name" />
</view>
<picker :range="ticketType" range-key="label" @change="(e) => onTicketTypeChange(e, index)">
<picker v-if="app != 'fliggy'" :range="ticketType" range-key="label"
@change="(e) => onTicketTypeChange(e, index)">
<view class="form-item" style="border-bottom: 1rpx solid #F5F5F5;">
<text class="label">票种</text>
<view class="input">{{ passenger.type }}</view>
@ -116,13 +133,21 @@
<view class="form-item">
<text class="label">票价</text>
<input class="input" v-model="passenger.price" />
<input class="input" type="number" v-model="passenger.price" @input="onPriceInput" />
</view>
<view class="form-item">
<view v-if="app == 'ctrip'" class="form-item">
<text class="label">积分</text>
<input class="input" type="number" v-model="passenger.points" />
</view>
<view v-if="app != 'ctrip'" class="form-item">
<text class="label">证件类型</text>
<input class="input" v-model="passenger.idType" />
</view>
<view class="form-item">
<view v-if="app != 'ctrip'" class="form-item">
<text class="label">证件号</text>
<input class="input" v-model="passenger.idNumber" />
</view>
<view v-if="app != 'qunar'" class="form-item">
<text class="label">是否本人</text>
<switch :checked="passenger.isMe" @change="(e) => passenger.isMe = e.detail.value" />
</view>
@ -136,16 +161,20 @@
</view>
<!-- 酒店广告 -->
<view class="section-container">
<view v-if="app == '12306' || app == 'ctrip'" class="section-container">
<view class="section-header" @click="toggleSection('hotelInfo')">
<text class="section-title">酒店广告</text>
<text class="section-title">{{ app == '12306' ? '酒店广告' : '返现任务' }}</text>
<uni-icons :type="collapsed.hotelInfo ? 'bottom' : 'top'" size="16" color="#666"></uni-icons>
</view>
<view class="card" v-show="!collapsed.hotelInfo">
<view class="form-item">
<view v-if="app == '12306'" class="form-item">
<text class="label">城市</text>
<input class="input" v-model="ticketsInfo.hotelInfo.city" />
</view>
<view v-if="app == 'ctrip'" class="form-item">
<text class="label">返现金额</text>
<input class="input" type="number" v-model="ticketsInfo.hotelInfo.cashback" />
</view>
<uni-datetime-picker type="daterange" v-model="hotelDateRange" :border="false">
<view class="form-item">
<text class="label">入住/离店日期</text>
@ -163,12 +192,19 @@
<script setup>
import NavBar from '@/components/nav-bar/nav-bar.vue'
import { reactive, toRefs, onMounted, computed } from 'vue';
import {
reactive,
toRefs,
onMounted,
computed
} from 'vue';
import { onLoad } from '@dcloudio/uni-app';
const defaultData = {
"orderInfo": {
"orderNo": "EJ66223536",
"orderTime": "2026.01.01"
"orderTime": "2026.01.01",
"price": "4440"
},
"ticketInfo": {
"departureTime": "01-01 09:19",
@ -179,45 +215,47 @@ const defaultData = {
"duration": "4时45分",
"date": "2026.01.01",
"weekDay": "星期四",
"gate": "6B"
"gate": "6B",
"trainName": "复兴号"
},
"passengerList": [
{
"name": "张元英",
"type": "成人票",
"seatType": "商务座",
"carriage": "01",
"seatNo": "03C",
"idType": "外国护照(KR)",
"price": "2110",
"status": "已支付",
"isMe": true
}
],
"passengerList": [{
"name": "张元英",
"type": "成人票",
"seatType": "商务座",
"carriage": "01",
"seatNo": "03C",
"idType": "外国护照(KR)",
"idNumber": "123456789012345678",
"price": "2110",
"status": "已支付",
"isMe": true,
"points": 9632
}],
"hotelInfo": {
"city": "上海",
"cashback": "25",
"startDay": "01-01",
"endDay": "01-02"
}
},
serviceText: "多人连坐 ¥10x2份"
}
// 车票类型
const ticketType = [
{
label: '成人票',
value: '1'
},
{
label: '儿童票',
value: '2'
},
{
label: '学生票',
value: '3'
},
{
label: '残疾军人票',
value: '4'
}
const ticketType = [{
label: '成人票',
value: '1'
},
{
label: '儿童票',
value: '2'
},
{
label: '学生票',
value: '3'
},
{
label: '残疾军人票',
value: '4'
}
]
const data = reactive({
@ -227,11 +265,28 @@ const data = reactive({
ticketInfo: false, // Default open ticket info as it is most likely to be edited
passengerList: false,
hotelInfo: true
}
},
app: '12306',
STORAGE_KEY: 'ticketsInfo'
})
let { app } = toRefs(data)
const { ticketsInfo, collapsed } = toRefs(data)
const {
ticketsInfo,
collapsed
} = toRefs(data)
onLoad((options) => {
console.log("options", options)
if (options.app) {
data.app = options.app
}
if (options.storageKey) {
data.STORAGE_KEY = options.storageKey
}
})
const ticketYear = computed(() => {
const dateStr = data.ticketsInfo.ticketInfo.date;
@ -242,14 +297,17 @@ const ticketYear = computed(() => {
})
/**
* 获取酒店日期范围
*/
const hotelDateRange = computed({
get() {
const year = ticketYear.value;
const start = data.ticketsInfo.hotelInfo.startDay ? `${year}-${data.ticketsInfo.hotelInfo.startDay}` : '';
const end = data.ticketsInfo.hotelInfo.endDay ? `${year}-${data.ticketsInfo.hotelInfo.endDay}` : '';
const start = data.ticketsInfo.hotelInfo.startDay ?
`${year}-${data.ticketsInfo.hotelInfo.startDay}` : '';
const end = data.ticketsInfo.hotelInfo.endDay ? `${year}-${data.ticketsInfo.hotelInfo.endDay}` :
'';
if (start && end) {
return [start, end];
}
@ -264,12 +322,39 @@ const hotelDateRange = computed({
})
onMounted(() => {
const stored = uni.getStorageSync('ticketsInfo')
const stored = uni.getStorageSync(data.STORAGE_KEY)
if (stored) {
Object.assign(data.ticketsInfo, stored)
}
// 初始化时同步车票日期到出发时间
const ticketDate = data.ticketsInfo.ticketInfo.date;
if (ticketDate) {
const parts = ticketDate.split('.');
if (parts.length === 3) {
const mmdd = `${parts[1]}-${parts[2]}`;
const oldDepTime = data.ticketsInfo.ticketInfo.departureTime || '';
const timePart = oldDepTime.split(' ')[1] || '00:00';
data.ticketsInfo.ticketInfo.departureTime = `${mmdd} ${timePart}`;
}
}
updateDuration();
})
/**
* 更新总价
*/
const onPriceInput = () => {
setTimeout(() => {
let total = 0;
data.ticketsInfo.passengerList.forEach(item => {
const price = Number(item.price) || 0;
total += price;
});
data.ticketsInfo.orderInfo.price = total.toString();
}, 50);
}
/**
* 确认
*/
@ -305,7 +390,7 @@ const handleRightButtonClick = () => {
}
}
uni.setStorageSync('ticketsInfo', data.ticketsInfo)
uni.setStorageSync(data.STORAGE_KEY, data.ticketsInfo)
uni.navigateBack()
}
@ -327,7 +412,8 @@ const removePassenger = (index) => {
content: '确定要删除该乘客吗?',
success: (res) => {
if (res.confirm) {
data.ticketsInfo.passengerList.splice(index, 1)
data.ticketsInfo.passengerList.splice(index, 1);
onPriceInput();
}
}
})
@ -347,9 +433,11 @@ const addPassenger = () => {
idType: '中国居民身份证',
price: oldPassenger.price || '0',
status: '已支付',
isMe: false
isMe: false,
points: oldPassenger.points || '2898',
}
data.ticketsInfo.passengerList.push(newPassenger)
data.ticketsInfo.passengerList.push(newPassenger);
onPriceInput();
}
/**
@ -407,6 +495,15 @@ const onTicketDateChange = (e) => {
const dateObj = new Date(val.replace(/-/g, '/')); // Compatible
const days = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
data.ticketsInfo.ticketInfo.weekDay = days[dateObj.getDay()];
// 同步出发时间日期
const parts = val.split('-');
if (parts.length === 3) {
const mmdd = `${parts[1]}-${parts[2]}`;
const oldDepTime = data.ticketsInfo.ticketInfo.departureTime || '';
const timePart = oldDepTime.split(' ')[1] || '00:00';
data.ticketsInfo.ticketInfo.departureTime = `${mmdd} ${timePart}`;
}
}
}
@ -432,8 +529,12 @@ const departureTimeHHMM = computed(() => {
* 获取出发时间选择器范围
*/
const departureTimeRange = computed(() => {
const hours = Array.from({ length: 24 }, (_, i) => i < 10 ? '0' + i : '' + i);
const minutes = Array.from({ length: 60 }, (_, i) => i < 10 ? '0' + i : '' + i);
const hours = Array.from({
length: 24
}, (_, i) => i < 10 ? '0' + i : '' + i);
const minutes = Array.from({
length: 60
}, (_, i) => i < 10 ? '0' + i : '' + i);
return [hours, minutes];
})
@ -470,8 +571,12 @@ const arrivalRange = computed(() => {
} else {
dates.push('MM-DD');
}
const hours = Array.from({ length: 24 }, (_, i) => i < 10 ? '0' + i : '' + i);
const minutes = Array.from({ length: 60 }, (_, i) => i < 10 ? '0' + i : '' + i);
const hours = Array.from({
length: 24
}, (_, i) => i < 10 ? '0' + i : '' + i);
const minutes = Array.from({
length: 60
}, (_, i) => i < 10 ? '0' + i : '' + i);
return [dates, hours, minutes];
})
@ -754,4 +859,4 @@ page {
.placeholder {
height: 60rpx;
}
</style>
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,279 @@
<template>
<view class="box">
<view class="codefun-flex-row codefun-justify-between codefun-items-center section_5 codefun-mt-8">
<view class="codefun-flex-row codefun-items-center">
<image class="codefun-shrink-0 image_11" src="/static/image/other/train-tickets/qunar/plan.png" />
<text class="font_3 text_31 codefun-ml-4">抢票方案详情</text>
</view>
<image class="image_4 image_12" src="/static/image/other/train-tickets/qunar/right.png" />
</view>
<view class="codefun-flex-row codefun-items-center section_6 codefun-mt-8">
<view class="codefun-flex-row codefun-flex-1">
<image class="codefun-shrink-0 image_13" src="/static/image/other/train-tickets/qunar/child.png" />
<view class="codefun-flex-col codefun-items-start codefun-flex-1 group_14 codefun-ml-10">
<text class="font_9 text_32">免费携带儿童申报</text>
<text class="font_5 text_34 codefun-mt-8">每名成人可免费携带1名6岁以下儿童</text>
</view>
</view>
<view
class="codefun-flex-col codefun-justify-start codefun-items-center codefun-shrink-0 text-wrapper_4 ml-15 codefun-justify-center">
<text class="font_9 text_33">去申报</text>
</view>
</view>
<view class="codefun-flex-col codefun-justify-start section_7 codefun-mt-8">
<view class="codefun-flex-col group_15">
<text class="codefun-self-start text_35">专属服务</text>
<view
class="codefun-flex-row codefun-justify-between codefun-items-center codefun-self-stretch group_16">
<view class="codefun-flex-row codefun-items-baseline">
<text class="codefun-shrink-0 font_3 text_36">全能抢票套餐</text>
<text class="font_19 codefun-ml-24">20元接送站代金券 x2份</text>
</view>
<image class="image_7" src="/static/image/other/train-tickets/qunar/right.png" />
</view>
<view v-for="(item, index) in services" :key="index"
class="codefun-flex-row codefun-justify-between codefun-items-center codefun-self-end service-item-right">
<text class="font_19">{{ item }}</text>
<image class="image_7" src="/static/image/other/train-tickets/qunar/right.png" />
</view>
</view>
</view>
<view class="codefun-flex-col section_8 codefun-mt-8">
<text class="codefun-self-start font_12 text_41">你可能遇到的问题</text>
<view class="codefun-flex-col codefun-self-stretch mt-25">
<view v-for="(question, index) in questions" :key="index"
class="codefun-flex-row codefun-justify-between codefun-items-center faq-item"
:class="{ 'mt-34': index > 0 }">
<view class="codefun-flex-row codefun-items-center codefun-flex-1">
<view class="codefun-flex-col codefun-items-center indicator-wrap">
<view class="codefun-shrink-0 section_9"></view>
</view>
<text class="font_3" style="margin-left: 14rpx;">{{ question.text }}</text>
</view>
<image class="image_7 faq-arrow" src="/static/image/other/train-tickets/qunar/right.png" />
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
const services = ref([
'腾讯视频vip月卡 x2份',
'加速包 x2份',
'24小时专人抢票 x2份'
]);
const questions = ref([
{ text: '为什么会退票失败' },
{ text: '退款什么时候到账' },
{ text: '已在车站或12306办理退票,退款何时到账' },
{ text: '如何退票' }
]);
</script>
<style>
@import '/common/global.css';
page {
background-color: #F2F5F9;
}
</style>
<style lang="less" scoped>
.mt-25 {
margin-top: 50rpx;
}
.box {
// padding: 0 16rpx;
.section_5 {
padding: 24rpx 28rpx 23.5rpx;
overflow: hidden;
border-radius: 20rpx;
background-color: #ffffff;
.image_11 {
width: 32rpx;
height: 32rpx;
}
.font_3 {
font-size: 26rpx;
line-height: 25.92rpx;
color: #363636;
}
.text_31 {
font-size: 28rpx;
line-height: 26.04rpx;
}
.image_4 {
width: 28rpx;
height: 28rpx;
}
.image_12 {
margin-right: 8rpx;
}
}
.section_6 {
padding: 24rpx 32rpx;
background-color: #ffffff;
background-size: 100% 100%;
background-repeat: no-repeat;
border-radius: 20rpx;
.image_13 {
width: 72rpx;
height: 72rpx;
}
.group_14 {
margin-top: 6.4rpx;
.text_32 {
font-size: 28rpx;
line-height: 26.12rpx;
}
.font_5 {
font-size: 26rpx;
line-height: 21.14rpx;
color: #666666;
}
.text_34 {
color: #999999;
font-size: 24rpx;
line-height: 22.48rpx;
}
}
.text-wrapper_4 {
background-image: linear-gradient(106.4deg, #ff912b -19.9%, #fc6a11 100.3%);
border-radius: 32rpx;
width: 140rpx;
height: 64rpx;
.text_33 {
color: #ffffff;
}
}
.font_9 {
font-size: 26rpx;
line-height: 25.92rpx;
font-weight: 700;
color: #363636;
}
}
.section_7 {
padding: 46rpx 0 48rpx;
background-color: #ffffff;
border-radius: 20rpx;
.group_15 {
margin-left: 30rpx;
margin-right: 36rpx;
overflow: hidden;
.text_35 {
color: #363636;
font-size: 30rpx;
font-weight: 700;
line-height: 27.7rpx;
}
.group_16 {
margin-top: 34.76rpx;
.font_3 {
font-size: 26rpx;
line-height: 25.92rpx;
color: #363636;
}
.text_36 {
line-height: 24.24rpx;
}
}
.font_19 {
font-size: 26rpx;
line-height: 28rpx;
color: #666666;
}
.image_7 {
width: 20rpx;
height: 20rpx;
}
.service-item-right {
margin-top: 24rpx;
width: 450rpx;
}
}
}
.section_8 {
padding: 36.12rpx 20.54rpx 46.56rpx 28.54rpx;
background-image: linear-gradient(180deg, #f1fbfb -21.5%, #ffffff 67.4%);
border-radius: 20rpx;
border: solid 4rpx #ffffff;
.font_12 {
font-size: 32rpx;
line-height: 29.6rpx;
color: #363636;
}
.text_41 {
font-size: 34rpx;
font-weight: 700;
line-height: 31.44rpx;
}
.section_9 {
background-color: #02c8e7;
border-radius: 50%;
width: 8rpx;
height: 8rpx;
}
.indicator-wrap {
width: 20rpx;
display: flex;
align-items: center;
}
.font_3 {
font-size: 26rpx;
line-height: 25.92rpx;
color: #363636;
}
.image_7 {
width: 20rpx;
height: 20rpx;
}
.faq-arrow {
margin-right: 15rpx;
}
.mt-34 {
margin-top: 34rpx;
}
.group_21 {
line-height: 24.62rpx;
}
}
}
</style>

View File

@ -0,0 +1,683 @@
<template>
<!-- 水印 -->
<view v-if="$isVip()">
<watermark dark="light" source="uni_alipay_other_tickets_qunar" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark', 'uni_alipay_other_tickets_qunar')">
<image src='/static/lottie/watermark.gif' mode="widthFix" style="width:94px;height:74px;" ></image>
</liu-drag-button>
</view>
<view class="codefun-flex-col section">
<NavBar :bgColor="data.navBar.bgColor" :buttonGroup="buttonGroup" @button-click="util.clickTitlePopupButton"
tipLayerType="qunar-train-tickets-tip" isTipLayer tipLayerText="修改车票信息">
<template v-slot:right>
<view class="codefun-flex-col group_2">
<view class="codefun-flex-row group_3">
<image class="image_4" src="/static/image/other/train-tickets/qunar/tuigaishuoming.png" />
<image class="image_4 ml-25" src="/static/image/other/train-tickets/qunar/kefu.png" />
</view>
<view class="codefun-flex-row codefun-mt-4">
<text class="font text">退改说明</text>
<text class="font text_2 ml-11">客服</text>
</view>
</view>
</template>
</NavBar>
<view class="codefun-flex-col">
<view class="codefun-flex-row codefun-justify-between codefun-items-center group_4">
<view class="codefun-flex-row codefun-items-center">
<image class="codefun-shrink-0 image_5" src="/static/image/other/train-tickets/qunar/success.png" />
<text class="text_5 ml-7">出票完成</text>
<view
class="codefun-flex-col codefun-justify-start codefun-items-center codefun-shrink-0 text-wrapper ml-7 codefun-justify-center">
<text class="font_5 text_6">订返程</text>
</view>
</view>
<view class="codefun-flex-col section_2">
<view class="codefun-self-start group_5">
<text class="font_2 text_3">¥</text>
<text class="font_2 text_4">{{ trainTickets.orderInfo.price }}</text>
</view>
<image class="codefun-shrink-0 codefun-self-end image_7 image_8"
src="/static/image/other/train-tickets/qunar/right.png" />
<text class="codefun-self-start text_7">支付明细</text>
</view>
</view>
</view>
</view>
<view class="main-box">
<view class="codefun-flex-col section_3">
<view class="codefun-flex-col view">
<view class="codefun-flex-row codefun-items-baseline codefun-self-stretch codefun-justify-between">
<text class="font_5 text_8">取票号:{{ trainTickets.orderInfo.orderNo }}</text>
<view class="group_9 ml-25">
<text class="font_6">{{ trainTickets.ticketInfo.gate }}</text>
</view>
</view>
<view class="codefun-flex-row equal-division codefun-justify-between">
<view class="codefun-flex-col group_10 group_28">
<text class="codefun-self-stretch font_3 text_16">{{
formatDateTime(trainTickets.ticketInfo.departureTime,
trainTickets.ticketInfo.date) }}</text>
<text class="codefun-self-start font_8 mt-7">{{
trainTickets.ticketInfo.departureTime.split(' ')[1]
}}</text>
</view>
<view class="codefun-flex-col codefun-items-center group_10 group_29">
<text class="font_5 text_17">{{ trainTickets.ticketInfo.duration }}</text>
<image class="image_9 mt-11" src="/static/image/other/train-tickets/qunar/jingtingzhan.png" />
</view>
<view class="codefun-flex-col group_10 group_26">
<text class="codefun-self-start font_3 text_18">{{
formatDateTime(trainTickets.ticketInfo.arrivalTime,
trainTickets.ticketInfo.date) }}</text>
<text class="codefun-self-end font_8 mt-7">{{ trainTickets.ticketInfo.arrivalTime.split(' ')[1]
}}</text>
</view>
</view>
<view class="codefun-flex-row codefun-justify-center codefun-self-stretch codefun-relative group_11">
<view class="codefun-flex-row codefun-items-center pos">
<text class="font_9 text_19">{{ trainTickets.ticketInfo.departureStation }}</text>
<image class="codefun-shrink-0 image_4 ml-3"
src="/static/image/other/train-tickets/qunar/location.png" />
</view>
<view class="codefun-flex-row codefun-items-center">
<text class="font_19 text_21">{{ trainTickets.ticketInfo.trainNo }}</text>
<image class="codefun-shrink-0 image_10 codefun-ml-2"
src="/static/image/other/train-tickets/qunar/info.png" />
</view>
<view class="codefun-flex-row codefun-items-center pos_2">
<image class="codefun-shrink-0 image_4"
src="/static/image/other/train-tickets/qunar/location.png" />
<text class="font_9 text_20 codefun-ml-4">{{ trainTickets.ticketInfo.arrivalStation }}</text>
</view>
</view>
</view>
<view class="codefun-flex-col group_12">
<view class="codefun-flex-col section_1" v-for="item in trainTickets.passengerList" :key="item.id">
<view class="codefun-flex-col">
<view class="codefun-flex-row codefun-justify-between codefun-items-center">
<view class="codefun-flex-row codefun-items-baseline">
<text class="font_10">{{ item.name }}</text>
<text class="font_11 text_22 codefun-ml-10">{{ item.idType }}</text>
</view>
<view class="codefun-flex-row">
<view
class="codefun-flex-col codefun-justify-start codefun-items-center codefun-shrink-0 text-wrapper_8">
<text v-if="computeSeatNo(item.seatNo)" class="font_4 text_23">{{
computeSeatNo(item.seatNo) }}</text>
</view>
<text class="font_12 text_51 codefun-ml-6">{{ item.carriage }}车{{ item.seatNo }}号</text>
</view>
</view>
<view class="codefun-flex-row codefun-justify-between codefun-mt-8">
<text class="codefun-self-start font_13">{{ item.idType.includes('身份证') ?
stringUtil.maskIdCard(item.idNumber) : (item.idType.includes('护照') ?
stringUtil.maskPassport(item.idNumber) : item.idNumber) }}</text>
<text class="codefun-self-center font_14 text_25">{{ item.seatType }}</text>
</view>
</view>
<view class="codefun-flex-row codefun-justify-between codefun-items-center group_27 mt-19">
<text class="font_15 text_50">出票成功</text>
<view class="codefun-flex-row">
<view class="codefun-flex-col codefun-justify-center codefun-items-center action-btn">
<text class="font_16">分享</text>
</view>
<view
class="codefun-flex-col codefun-justify-center codefun-items-center action-btn codefun-ml-6">
<text class="font_16">改签</text>
</view>
<view
class="codefun-flex-col codefun-justify-center codefun-items-center action-btn codefun-ml-6">
<text class="font_16">退票</text>
</view>
</view>
</view>
</view>
</view>
</view>
<Box />
<view class="" style="width: 100%;overflow: hidden;">
<ZdySafeBottom bgColor="#f0f3f8" />
</view>
</view>
</template>
<script setup>
import NavBar from '@/components/nav-bar/nav-bar.vue';
import Box from './components/box.vue';
import { reactive, toRefs } from 'vue';
import { onShow, onPageScroll } from '@dcloudio/uni-app';
import { util, stringUtil } from '@/utils/common.js';
import ZdySafeBottom from '@/components/safeBottom/safeBottom.vue'
const buttonGroup = [{
name: "编辑机票信息",
click: () => {
goEdit()
}
}]
function goEdit() {
util.goPage(`/pages/other/train-tickets/edit/edit?app=qunar&storageKey=${data.STORAGE_KEY}`)
}
const data = reactive({
navBar: {
bgColor: 'transparent'
},
trainTickets: {
"orderInfo": {
"orderNo": "EJ66223536",
"price": "4440"
},
"ticketInfo": {
"departureTime": "01-01 09:19",
"departureStation": "北京南",
"arrivalTime": "01-01 14:04",
"arrivalStation": "上海虹桥",
"trainNo": "G905",
"duration": "4时45分",
"date": "2026.01.01",
"weekDay": "星期四",
"gate": "6A、7A进站检票口,6B、7B进站检票",
"trainName": "复兴号"
},
"passengerList": [
{
"name": "张元英",
"type": "成人票",
"seatType": "商务座",
"carriage": "01",
"seatNo": "03C",
"idType": "外国护照(KR)",
"idNumber": "KR123456789",
"price": "2110",
"status": "已支付",
"isMe": true,
"points": 9632
}
]
},
STORAGE_KEY: 'qunarTrainTickets'
})
let { trainTickets } = toRefs(data)
onShow(() => {
const stored = uni.getStorageSync(data.STORAGE_KEY)
if (stored) {
Object.assign(data.trainTickets, stored)
}
})
onPageScroll((e) => {
data.navBar.bgColor = e.scrollTop > 40 ? '#F2F5F9' : 'transparent';
})
const computeSeatNo = (seatNo) => {
if (seatNo.includes('C') || seatNo.includes('F')) {
return '靠窗';
} else if (seatNo.includes('A') || seatNo.includes('D')) {
return '过道';
}
return '';
}
const formatDateTime = (timeStr, dateStr) => {
if (!timeStr) return '';
const parts = timeStr.split(' ');
const md = parts[0];
if (!md) return '';
const mdParts = md.split('-');
let year, month, day;
if (mdParts.length === 3) {
year = mdParts[0];
month = mdParts[1];
day = mdParts[2];
} else if (mdParts.length === 2) {
month = mdParts[0];
day = mdParts[1];
year = new Date().getFullYear();
if (dateStr) {
year = dateStr.split('.')[0];
}
} else {
return '';
}
const dateObj = new Date(Number(year), Number(month) - 1, Number(day));
const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
const week = isNaN(dateObj.getTime()) ? '' : weekDays[dateObj.getDay()];
return `${month}月${day}日 ${week}`;
}
</script>
<style>
@import '/common/global.css';
page {
background-color: #F2F5F9;
width: 100vw;
overflow: hidden;
}
</style>
<style lang="less" scoped>
::v-deep .uni-navbar__header-btns-right {
width: auto !important;
}
.ml-11 {
margin-left: 22rpx;
}
.ml-7 {
margin-left: 14rpx;
}
.ml-25 {
margin-left: 50rpx;
}
.mt-7 {
margin-top: 14rpx;
}
.mt-11 {
margin-top: 22rpx;
}
.ml-3 {
margin-left: 6rpx;
}
.mt-19 {
margin-top: 38rpx;
}
.section {
background: linear-gradient(180deg, #CDF2F6 0%, #F2F5F9 100%);
.image_5 {
width: 44rpx;
height: 44rpx;
}
.group_2 {
.group_3 {
padding-left: 23.26rpx;
padding-right: 4.64rpx;
.image_4 {
width: 28rpx;
height: 28rpx;
}
}
.font {
font-size: 18rpx;
line-height: 16.88rpx;
color: #363636;
}
.text {
line-height: 16.68rpx;
}
.text_2 {
line-height: 16.74rpx;
}
}
.group_4 {
padding-left: 20rpx;
padding-bottom: 30rpx;
.text_5 {
color: #363636;
font-size: 44rpx;
font-weight: 700;
line-height: 41.18rpx;
}
.text-wrapper {
border-radius: 25rpx;
width: 106rpx;
height: 24px;
border: solid 0.5px #28c2dd;
.text_6 {
color: #28c2dd;
font-size: 24rpx;
line-height: 50rpx;
}
}
.section_2 {
padding: 19.76rpx 16.2rpx 13.54rpx;
background: linear-gradient(270deg, rgba(255, 255, 255, 0) 0%, #FFFFFF 100%);
border-radius: 16rpx 0rpx 0rpx 16rpx;
width: 160rpx;
height: 94rpx;
.group_5 {
line-height: 20.46rpx;
.font_2 {
font-size: 26rpx;
line-height: 21.14rpx;
color: #fb8517;
}
.text_3 {
font-size: 28rpx;
line-height: 19.9rpx;
}
.text_4 {
font-size: 28rpx;
line-height: 20.46rpx;
}
}
.image_7 {
width: 20rpx;
height: 20rpx;
}
.image_8 {
margin-right: 11.8rpx;
}
.text_7 {
margin-left: 2.52rpx;
margin-top: 3.84rpx;
color: #363636;
font-size: 20rpx;
line-height: 18.62rpx;
}
}
}
}
.main-box {
padding: 10rpx 16rpx;
padding-bottom: 10rpx;
padding-bottom: calc(10rpx + constant(safe-area-inset-bottom));
padding-bottom: calc(10rpx + env(safe-area-inset-bottom));
.ml-25 {
margin-left: 50rpx;
}
.section_3 {
padding: 20rpx 18rpx 0;
background-color: #ffffff;
border-radius: 20rpx;
.view {
margin: 0 14rpx;
padding-top: 2.96rpx;
.font_5 {
font-size: 26rpx;
line-height: 21.14rpx;
color: #666666;
}
.text_8 {
font-size: 24rpx;
}
.group_9 {
line-height: 21.76rpx;
height: 21.76rpx;
overflow: hidden;
overflow-x: auto;
flex: 1;
text-align: right;
/* 隐藏横向滚动条,但保留滑动效果 */
scrollbar-width: none;
-ms-overflow-style: none;
&::-webkit-scrollbar {
display: none;
}
.font_6 {
white-space: nowrap;
font-size: 22rpx;
line-height: 24rpx;
font-weight: 500;
color: #4FB26E;
}
}
.equal-division {
align-self: flex-start;
margin-top: 21rpx;
width: 100%;
.group_10 {
flex-shrink: 0;
.font_3 {
font-size: 26rpx;
line-height: 25.92rpx;
color: #363636;
}
.text_16 {
font-size: 28rpx;
line-height: 24rpx;
}
.font_8 {
font-size: 52rpx;
color: #363636;
}
.text_17 {
font-size: 24rpx;
line-height: 22.04rpx;
}
.image_9 {
width: 234rpx;
height: 44rpx;
}
.text_18 {
font-size: 28rpx;
line-height: 24rpx;
text-align: right;
width: 100%;
}
}
.group_28 {
padding: 16rpx 0;
width: 200rpx;
}
.group_29 {
padding: 18.84rpx 0 3.54rpx;
}
.group_26 {
padding: 16rpx 0;
width: 200rpx;
}
}
.group_11 {
.pos {
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
.text_19 {
font-size: 28rpx;
line-height: 25.96rpx;
}
}
.font_19 {
font-size: 26rpx;
line-height: 28rpx;
color: #666666;
}
.text_21 {
font-size: 24rpx;
line-height: 17.54rpx;
}
.image_10 {
width: 30rpx;
height: 30rpx;
}
.pos_2 {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
.text_20 {
font-size: 28rpx;
line-height: 25.82rpx;
}
}
.image_4 {
width: 28rpx;
height: 28rpx;
}
.font_9 {
font-size: 26rpx;
line-height: 25.92rpx;
font-weight: 700;
color: #363636;
}
}
}
.group_12 {
padding: 38rpx 0 0;
.section_1 {
padding: 28rpx 10.58rpx 22rpx 19.1rpx;
background-color: #f9fbfc;
border-radius: 16rpx;
margin-bottom: 24rpx;
.text_22 {
font-size: 24rpx;
line-height: 22.22rpx;
}
.text-wrapper_8 {
background-color: #9db2bd;
border-radius: 8rpx;
width: 56rpx;
height: 32rpx;
.text_23 {
line-height: 32rpx;
}
}
.text_51 {
margin-top: 2.92rpx;
}
.text_25 {
margin-right: 4.38rpx;
font-size: 28rpx;
}
.group_27 {
padding: 0 8.98rpx;
.text_50 {
font-size: 28rpx;
}
}
}
.font_10 {
font-size: 26rpx;
line-height: 23.74rpx;
font-weight: 700;
color: #363636;
}
.font_11 {
font-size: 26rpx;
line-height: 21.14rpx;
font-weight: 500;
color: #999999;
}
.font_4 {
font-size: 20rpx;
color: #ffffff;
}
.font_12 {
font-size: 32rpx;
line-height: 29.6rpx;
color: #363636;
font-weight: 500;
}
.font_13 {
font-size: 26rpx;
line-height: 19rpx;
font-weight: 500;
color: #999999;
}
.font_14 {
font-size: 26rpx;
line-height: 25.92rpx;
color: #545356;
}
.font_15 {
font-size: 26rpx;
line-height: 25.92rpx;
font-weight: 700;
color: #f5882c;
}
.font_16 {
font-size: 26rpx;
line-height: 21.14rpx;
font-weight: 700;
color: #363636;
}
.action-btn {
background-color: #ffffff;
border-radius: 30rpx;
width: 104rpx;
height: 60rpx;
border: solid 1rpx #e0e4e3;
text {
font-size: 24rpx;
}
}
}
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,9 @@
<template>
<template>
<!-- 水印 -->
<view v-if="$isVip()">
<watermark dark="light" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark')">
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
<watermark dark="light" source="uni_alipay_other_groupChat" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark', 'uni_alipay_other_groupChat')">
<image src='/static/lottie/watermark.gif' mode="widthFix" style="width:94px;height:74px;" ></image>
</liu-drag-button>
</view>
<view class="container">
@ -64,39 +64,43 @@
<view class="control-buttons">
<!-- 麦克风 -->
<view class="control-item">
<view class="control-btn" :class="{ active: videoData.micOn }" @click="changeInfo('micOn')">
<image class="control-icon"
:src="videoData.micOn ? '/static/image/other/video-call/mic-on.png' : '/static/image/other/video-call/mic-off.png'">
</image>
</view>
<image class="control-btn" :class="{ active: videoData.micOn }" @click="changeInfo('micOn')"
:src="videoData.micOn ? '/static/image/other/video-call/mic-on.png' : '/static/image/other/video-call/mic-off.png'">
</image>
<!-- <view class="" :class="{ active: videoData.micOn }" >
</view> -->
<text class="control-label">{{ videoData.micOn ? '麦克风已开' : '麦克风已关' }}</text>
</view>
<!-- 扬声器 -->
<view class="control-item">
<view class="control-btn" :class="{ active: videoData.speakerOn }" @click="changeInfo('speakerOn')">
<image class="control-icon"
:src="videoData.speakerOn ? '/static/image/other/video-call/speaker-on.png' : '/static/image/other/video-call/speaker-off.png'">
</image>
</view>
<image class="control-btn" :class="{ active: videoData.speakerOn }" @click="changeInfo('speakerOn')"
:src="videoData.speakerOn ? '/static/image/other/video-call/speaker-on.png' : '/static/image/other/video-call/speaker-off.png'">
</image>
<!-- <view class="" :class="{ active: videoData.speakerOn }" @click="changeInfo('speakerOn')">
</view> -->
<text class="control-label">{{ videoData.speakerOn ? '扬声器已开' : '扬声器已关' }}</text>
</view>
<!-- 摄像头 -->
<view class="control-item">
<view class="control-btn" :class="{ active: videoData.cameraOn }" @click="changeInfo('cameraOn')">
<image class="control-icon"
:src="videoData.cameraOn ? '/static/image/other/video-call/camera-on.png' : '/static/image/other/video-call/camera-off.png'">
</image>
</view>
<image class="control-btn" :class="{ active: videoData.cameraOn }" @click="changeInfo('cameraOn')"
:src="videoData.cameraOn ? '/static/image/other/video-call/camera-on.png' : '/static/image/other/video-call/camera-off.png'">
</image>
<!-- <view class="control-btn" :class="{ active: videoData.cameraOn }" @click="changeInfo('cameraOn')">
</view> -->
<text class="control-label">{{ videoData.cameraOn ? '摄像头已开' : '摄像头已关' }}</text>
</view>
</view>
<!-- 挂断按钮 -->
<view class="hangup-btn" @click="hangup">
<image class="hangup-icon" src="/static/image/other/video-call/hangup.png"></image>
</view>
<image class="hangup-btn" @click="hangup" src="/static/image/other/video-call/hangup.png" :style="{'margin-bottom':$store.state.safeBottomAz+'px'}"></image>
<!-- <view class="hangup-btn" @click="hangup">
</view> -->
</view>
</view>
@ -142,11 +146,10 @@
<script setup>
import NavBar from "@/components/nav-bar/nav-bar.vue"
import { ref, toRefs, onMounted, onUnmounted, reactive, computed, getCurrentInstance } from 'vue'
import { toRefs, onMounted, onUnmounted, reactive, computed, getCurrentInstance } from 'vue'
import { onLoad, onShow, onHide } from '@dcloudio/uni-app'
import { util } from '@/utils/common.js'
const {
appContext,
proxy
} = getCurrentInstance();
@ -265,7 +268,10 @@ onLoad(() => {
...videoData,
iconType: videoData.onMic ? videoData.iconType : 1,
videoList: videoData.videoList.map((item) => {
item.videoUrl = item.savedVideoUrl
// #ifdef APP-PLUS&&!APP-HARMONY
item.videoUrl = plus.io.convertLocalFileSystemURL(item.savedVideoUrl)
// #endif
return item
})
}
@ -290,7 +296,7 @@ onShow(() => {
}
startTimer()
// #ifdef APP-PLUS
// #ifdef APP-PLUS&&!APP-HARMONY
util.setAndroidSystemBarColor('#232323')
// 保存定时器引用,以便在页面卸载时清理
statusBarTimer = setTimeout(() => {
@ -523,7 +529,10 @@ const chooseVideo = () => {
camera: 'back',
success: async (res) => {
console.log('选择视频成功:', res)
const videoUrl = plus.io.convertLocalFileSystemURL(res.tempFilePath)
let videoUrl = res.tempFilePath
// #ifdef APP-PLUS&&!APP-HARMONY
videoUrl = plus.io.convertLocalFileSystemURL(res.tempFilePath)
// #endif
const savedVideoUrl = await new Promise((resolve, reject) => {
uni.saveFile({
tempFilePath: videoUrl,
@ -661,7 +670,7 @@ const removeFile = (filePath) => {
console.log('🗑️ 准备删除文件:', filePath)
// #ifdef APP-PLUS
// #ifdef APP-PLUS&&!APP-HARMONY
// 尝试使用 plus.io 删除 (支持 file:// 协议和 _downloads 等路径)
if (filePath.startsWith('file://') || filePath.includes('_downloads') || filePath.includes('saved_video_')) {
plus.io.resolveLocalFileSystemURL(filePath, (entry) => {
@ -681,7 +690,7 @@ const removeFile = (filePath) => {
// 尝试使用 uni.removeSavedFile (主要针对 uni.saveFile 保存的文件)
uni.removeSavedFile({
filePath: filePath,
success: (res) => {
success: () => {
console.log('✅ uni.removeSavedFile 删除成功:', filePath)
},
fail: (err) => {
@ -718,7 +727,7 @@ const handleTouchStart = (event, index) => {
}
// 触摸移动 - 实时更新位置偏移
const handleTouchMove = (event, index) => {
const handleTouchMove = (event) => {
if (!data.dragState.isDragging) return
const touch = event.touches[0]
@ -731,7 +740,7 @@ const handleTouchMove = (event, index) => {
}
// 触摸结束 - 完成拖动并保存
const handleTouchEnd = (event, index) => {
const handleTouchEnd = () => {
// 清除长按定时器
if (data.dragState.longPressTimer) {
clearTimeout(data.dragState.longPressTimer)
@ -1024,7 +1033,6 @@ const stopTimer = () => {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
background-color: #4a4a4a;
display: flex;
align-items: center;
justify-content: center;

View File

@ -0,0 +1,361 @@
<template>
<view class="container">
<nav-bar bgColor="transparent" :isBack="false">
<template v-slot:center>
<view class="flex-cneter title-box flex" style="margin-top: 24rpx;">
<image class="wx-logo" src="/static/image/other/payment-success/payment-success.png"></image>
<text class="title">支付成功</text>
</view>
</template>
</nav-bar>
<view class="content-box flex-column flex-cneter" @click="editPaymentInfo">
<image v-if="paymetInfo.userImg" class="user-img" :src="paymetInfo.userImg" mode="aspectFill"></image>
<text class="product-name">{{ paymetInfo.productName }}</text>
<view class="money flex wx-font-medium" :style="{ 'margin-top': paymetInfo.userImg ? '78rpx' : '46rpx' }">
<image class="symbol" src="/static/image/other/payment-success/symbol-yuan.png"></image>{{
Number(paymetInfo.money).toFixed(2) }}
</view>
<image v-if="paymetInfo.isAd" class="ad-img" src="/static/image/other/payment-success/ad-img.png"
mode="widthFix">
</image>
</view>
<view class="finish-butn" @click="util.goBack()">完成</view>
<!-- 修改支付信息弹窗 -->
<uni-popup ref="editPaymentInfoPopup" type="center">
<view class="edit-popup">
<view class="title">修改支付信息</view>
<view class="input-item flex-align-center">
<text class="label">用户头像</text>
<view class="avatar-uploader flex-1">
<view class="avatar-preview" v-if="paymetInfo.userImg">
<image class="preview-img" :src="paymetInfo.userImg" mode="aspectFill"></image>
<view class="delete-icon" @click="handleDeleteAvatar">×</view>
</view>
<view class="upload-btn" v-else @click="handleChooseAvatar">
<text class="plus">+</text>
</view>
</view>
</view>
<view class="input-item flex-align-center">
<text class="label"><text style="color: #FF3B30;">*</text>商品名称</text>
<input class="flex-1" type="text" v-model="paymetInfo.productName" placeholder="请输入商品名称" />
</view>
<view class="input-item flex-align-center">
<text class="label"><text style="color: #FF3B30;">*</text>支付金额</text>
<input class="flex-1" type="digit" v-model="paymetInfo.money" placeholder="请输入支付金额" />
</view>
<view class="input-item flex-align-center">
<text class="label">展示广告</text>
<switch class="flex-1" :checked="paymetInfo.isAd" @change="paymetInfo.isAd = $event.detail.value"
color="#16B264" style="transform:scale(0.8);transform-origin:left center;" />
</view>
<view class="btn-group flex">
<view class="btn cancel flex-1" @click="editPaymentInfoPopup.close()">取消</view>
<view style="width: 30rpx;"></view>
<view class="btn confirm flex-1" @click="confirmEditPaymentInfo">确定</view>
</view>
</view>
</uni-popup>
</view>
<!-- 水印 -->
<view v-if="$isVip()">
<watermark dark="light" source="uni_alipay_other_wx_pay_success" />
<liu-drag-button :canDocking="false"
@clickBtn="$goRechargePage('watermark', 'uni_alipay_other_wx_pay_success')">
<image src='/static/lottie/watermark.gif' mode="widthFix" style="width:94px;height:74px;" ></image>
</liu-drag-button>
</view>
</template>
<script setup>
import { ref, getCurrentInstance } from 'vue';
import { onShow, onLoad } from '@dcloudio/uni-app';
import { util } from '@/utils/common';
import { storage } from '@/utils/storage';
const {
proxy
} = getCurrentInstance();
const editPaymentInfoPopup = ref(null);
const editPaymentInfo = () => {
editPaymentInfoPopup.value.open('center');
}
const handleChooseAvatar = () => {
uni.chooseImage({
count: 1,
success: (res) => {
const tempFilePath = res.tempFilePaths[0];
// #ifndef H5
uni.saveFile({
tempFilePath: tempFilePath,
success: (saveRes) => {
if (paymetInfo.value.userImg && !paymetInfo.value.userImg.startsWith('/static/')) {
uni.removeSavedFile({ filePath: paymetInfo.value.userImg });
}
paymetInfo.value.userImg = saveRes.savedFilePath;
}
});
// #endif
// #ifdef H5
paymetInfo.value.userImg = tempFilePath;
// #endif
}
});
};
const handleDeleteAvatar = () => {
// #ifndef H5
if (paymetInfo.value.userImg && !paymetInfo.value.userImg.startsWith('/static/')) {
uni.removeSavedFile({ filePath: paymetInfo.value.userImg });
}
// #endif
paymetInfo.value.userImg = '';
};
const confirmEditPaymentInfo = () => {
editPaymentInfoPopup.value.close();
storage.set('wx_payment_success_info', paymetInfo.value);
}
const buttonGroup = [
{
name: "修改支付信息",
click: () => {
editPaymentInfo()
}
}
]
const paymetInfo = ref({
userImg: '',
productName: '王者荣耀',
money: 298,
isAd: false,
});
onShow(() => {
// #ifdef APP-PLUS&&!APP-HARMONY
util.setAndroidSystemBarColor('#FFFFFF')
setTimeout(() => {
plus.navigator.setStatusBarStyle("dark");
}, 500)
// #endif
})
onLoad(async () => {
const cachedInfo = storage.get('wx_payment_success_info');
if (cachedInfo) {
paymetInfo.value = cachedInfo;
} else {
paymetInfo.value = { productName: '王者荣耀', money: 168 };
}
// 微信支付成功页面埋点
proxy.$apiUserEvent('all', {
type: 'event',
key: 'wx_pay_success',
prefix: '.uni.other.',
value: "微信支付成功"
})
})
</script>
<style lang="less" scoped>
.title-box {
.wx-logo {
width: 40rpx;
height: 36rpx;
}
.title {
font-size: 32rpx;
color: #16B264;
line-height: 32rpx;
font-weight: 500;
margin-left: 10rpx;
}
}
.content-box {
margin-top: 162rpx;
align-items: center;
.user-img {
width: 94rpx;
height: 94rpx;
border-radius: 50%;
background-color: #D8D8D8;
transform: translateY(-50%);
}
.product-name {
font-size: 32rpx;
color: #1A1A1A;
line-height: 32rpx;
text-align: center;
}
.money {
align-items: center;
justify-content: center;
margin-top: 46rpx;
font-weight: 500;
font-size: 88rpx;
color: #1A1A1A;
line-height: 88rpx;
.symbol {
width: 32rpx;
height: 46rpx;
margin-right: 20rpx;
}
}
.ad-img {
margin-top: 100rpx;
width: 654rpx;
}
}
.finish-butn {
position: fixed;
left: 50%;
transform: translateX(-50%);
width: 348rpx;
height: 92rpx;
background-color: #F1F1F1;
color: #1A1A1A;
line-height: 92rpx;
font-size: 28rpx;
font-weight: 500;
border-radius: 12rpx;
text-align: center;
margin: 0 auto;
bottom: 170rpx;
bottom: calc(170rpx + env(safe-area-inset-bottom));
bottom: calc(170rpx + constant(safe-area-inset-bottom));
}
.edit-popup {
width: 600rpx;
background-color: #FFFFFF;
border-radius: 24rpx;
padding: 40rpx;
box-sizing: border-box;
.title {
font-size: 32rpx;
color: #1A1A1A;
font-weight: 500;
text-align: center;
margin-bottom: 40rpx;
}
.input-item {
margin-bottom: 30rpx;
.label {
font-size: 28rpx;
color: #1A1A1A;
width: 140rpx;
}
input {
height: 72rpx;
background-color: #F8F8F8;
border-radius: 12rpx;
padding: 0 20rpx;
font-size: 28rpx;
color: #1A1A1A;
}
.avatar-uploader {
display: flex;
align-items: center;
.upload-btn {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
background-color: #F8F8F8;
display: flex;
justify-content: center;
align-items: center;
border: 2rpx dashed #CCCCCC;
.plus {
font-size: 60rpx;
color: #999;
font-weight: 300;
margin-top: -6rpx;
}
}
.avatar-preview {
position: relative;
width: 100rpx;
height: 100rpx;
.preview-img {
width: 100%;
height: 100%;
border-radius: 50%;
}
.delete-icon {
position: absolute;
top: -4rpx;
right: -4rpx;
width: 32rpx;
height: 32rpx;
background-color: red;
color: #fff;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
font-size: 30rpx;
line-height: 28rpx;
}
}
}
}
.btn-group {
margin-top: 50rpx;
justify-content: space-between;
.btn {
height: 80rpx;
border-radius: 12rpx;
text-align: center;
line-height: 80rpx;
font-size: 30rpx;
&.cancel {
background-color: #F1F1F1;
color: #333333;
}
&.confirm {
background-color: #3b82f6;
color: #FFFFFF;
}
}
}
}
</style>
<style>
@import '/common/main.css';
page {
background-color: #FFFFFF;
}
</style>

179
pages/shopping/index.vue Normal file
View File

@ -0,0 +1,179 @@
<template>
<view class="container">
<nav-bar title="购物平台" bgColor="#F0F4F9"></nav-bar>
<view class="menu-container">
<view class="card" v-for="(item, index) in menuList" :key="index"
:style="{ background: `linear-gradient(to bottom, ${item.bgColor[0]} 0%, ${item.bgColor[1]} 80%)` }">
<text class="card-title">{{ item.name }}</text>
<view class="icon-wrapper">
<view class="icon-shadow" :style="{ background: item.shadowColor }"></view>
<image class="icon-img" :src="`/static/image/shopping/${item.icon}.png`" mode="aspectFit"></image>
</view>
<view class="btn" :style="{ background: item.btnBg }" @click="goTo(item.url)">立即进入</view>
</view>
</view>
</view>
</template>
<script setup>
import {
ref,
reactive,
getCurrentInstance
} from 'vue';
import {
onLoad
} from '@dcloudio/uni-app'
const {
proxy
} = getCurrentInstance();
const data = reactive({
phone: ''
})
onLoad((options) => {
if (options.phone) {
data.phone = options.phone
}
proxy.$apiUserEvent('all', {
type: 'event',
key: 'shopping',
prefix: '.uni.other.',
value: '购物'
})
})
const menuList = ref([{
name: "京东",
icon: "jingdong",
bgColor: ["#FFE0DF", "#FFFFFF"],
btnBg: "#FF4848",
shadowColor: "#FF4848",
url: "/pages/shopping/jingdong/list-index"
}, {
name: "淘宝",
icon: "taobao",
bgColor: ["#FFF0DA", "#FFFFFF"],
btnBg: "#FF953C",
shadowColor: "#FF953C",
url: "/pages/shopping/taobao/list-index"
}, {
name: "拼多多",
icon: "pinduoduo",
bgColor: ["#FFE0DF", "#FFFFFF"],
btnBg: "#FF4848",
shadowColor: "#FF4848",
url: "/pages/shopping/pdd/list-index"
}, {
name: "快手",
icon: "kuaishou",
bgColor: ["#FFF0DA", "#FFFFFF"],
btnBg: "#FF953C",
shadowColor: "#FF953C",
url: ""
}, {
name: "抖音",
icon: "douyin",
bgColor: ["#FAE5FF", "#FFFFFF"],
btnBg: "#393939",
shadowColor: "#D15CFF",
url: ""
}, {
name: "得物",
icon: "dewu",
bgColor: ["#FAE5FF", "#FFFFFF"],
btnBg: "#393939",
shadowColor: "#D15CFF",
url: ""
}]);
const goTo = (url) => {
if (url) {
uni.navigateTo({
url: url
})
} else {
uni.showToast({
title: "开发中",
icon: "none"
})
}
}
</script>
<style>
page {
background-color: #F0F4F9;
}
</style>
<style lang="less" scoped>
.container {
min-height: 100vh;
}
.menu-container {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
padding: 24rpx;
}
.card {
width: calc(50% - 14rpx);
border-radius: 28rpx 28rpx 28rpx 28rpx;
margin-bottom: 24rpx;
display: flex;
flex-direction: column;
align-items: center;
padding-top: 48rpx;
padding-bottom: 40rpx;
box-sizing: border-box;
border: 1px solid #ffffff;
}
.card-title {
font-size: 28rpx;
color: #1A1A1A;
font-weight: 500;
}
.icon-wrapper {
margin-top: 40rpx;
margin-bottom: 62rpx;
position: relative;
width: 116rpx;
height: 116rpx;
display: flex;
justify-content: center;
align-items: center;
}
.icon-img {
width: 116rpx;
height: 116rpx;
z-index: 2;
}
.icon-shadow {
position: absolute;
bottom: -10rpx;
width: 100rpx;
height: 8rpx;
border-radius: 50%;
filter: blur(5px);
z-index: 1;
opacity: 0.6;
}
.btn {
padding: 0 20rpx;
height: 56rpx;
line-height: 56rpx;
border-radius: 16rpx 16rpx 16rpx 16rpx;
color: #fff;
font-size: 24rpx;
text-align: center;
font-weight: 500;
}
</style>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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