Compare commits

...

90 Commits

Author SHA1 Message Date
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
420 changed files with 31831 additions and 1217 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'
}
}

2
.husky/pre-commit Normal file
View File

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

69
App.vue
View File

@ -6,6 +6,8 @@ export default {
recentNativeData: 0 //
},
onLaunch: function (options) {
// console.log=()=>{}
// === wgt ===
console.log('=== App Launch 开始 ===')
console.log('启动参数:', JSON.stringify(options))
@ -76,37 +78,41 @@ export default {
this.globalData.NativeEvent = false
console.log('开始监听宿主消息')
uni.onNativeEventReceive((event, data) => {
if (event) {
console.log('接收到宿主消息:', event, data)
if (typeof uni.onNativeEventReceive === 'function') {
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 if (event == 'wx_pay_result' || event == 'ios_pay_result') {
this.globalData.recentNativeEvent = event
this.globalData.recentNativeData = data
}
}
})
})
} else {
console.log('uni.onNativeEventReceive 不可用,跳过监听')
}
}
},
@ -163,6 +169,9 @@ export default {
Object.keys(storageData).forEach(key => {
uni.setStorageSync(key, storageData[key])
})
if (extraData['isCombo']) {
uni.setStorageSync('isCombo', extraData['isCombo'])
}
},
/**
@ -174,7 +183,7 @@ export default {
//
const devConfig = {
host: "https://flaunt.batiao8.com/",
header: { "x-token": "ebe14dab-1879-4c5d-9148-727b96b30aad" },
header: { "x-token": "da884e6e-fbd7-4d8d-ab0e-cb7b07f9f6fa" },
decrypt: "e4rOtnF8tJjtHO7ecZeJHN1rapED5ImB",
encrypt: "xn08hYoizXhZ1zHP8DVqfCm2yHxPmhil"
}
@ -190,5 +199,7 @@ export default {
</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

@ -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,225 @@
<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: #AAAAAA !important;
}
image{
width: 16px;
height: 16px;
margin: 0 8px;
}
}
}
</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,184 @@
<template>
<view class="footer" :class="['footer_'+type]">
<view class="item" v-for="(item,index) in list" :key="index">
<image :src="`/static/image/call/${type}TabbarImg${index+1}.png`" mode=""></image>
<text>{{item}}</text>
</view>
</view>
<view class="footer footerZhangwei" :class="['footer_'+type]">
<view class="item" v-for="(item,index) in list" :key="index">
<image :src="`/static/image/call/${type}TabbarImg${index+1}.png`" mode=""></image>
<text>{{item}}</text>
</view>
</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'
},
})
const data = reactive({
statusBarHeight: 0,
showTipLayer: true,
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 = ["拨号",
"联系人",
"收藏"
]
}
})
</script>
<style scoped lang="scss">
.footerZhangwei{
position: relative !important;
opacity: 0 !important;
}
.footer {
position: fixed;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: space-around;
background: #f7f7f7;
.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-top: 2px;
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;
}
}
}
.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,244 @@
<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

View File

@ -0,0 +1,874 @@
<template>
<view>
<view v-for="(message, index) in displayList" :key="message.id || index" class="message-item" :class="{
isMe: message.isMe,
'm-t-16': shouldApplyMt16(index) && !shouldShowTime(index),
'is-sort-mode': sortMode,
'sort-dragging': sortMode && dragIndex === index,
'sort-drop-before': sortMode && dragOverIndex === index && dragIndex !== index && dropPosition === 'before',
'sort-drop-after': sortMode && dragOverIndex === index && dragIndex !== index && dropPosition === 'after'
}">
<!-- 排序模式下的拖拽手柄 -->
<view v-if="sortMode" class="sort-handle-wrap" @longpress="onSortLongPress(index, $event)"
@touchmove.stop.prevent="onSortTouchMove(index, $event)" @touchend.stop="onSortTouchEnd(index, $event)">
<view class="sort-handle-bar"></view>
<view class="sort-handle-bar"></view>
<view class="sort-handle-bar"></view>
</view>
<view style="flex: 1; overflow: hidden;">
<view class="time m-t-44" :id="'time-' + index" v-if="shouldShowTime(index)"
@longpress="!sortMode && onMessageLongPress(index, message, 'time')">
<view class="top-text" v-if="phone == 'iphone' && index == 0">信息 · 短信</view>
<view class="top-text" v-if="phone == 'huawei' && index == 0">短信/彩信</view>
<text v-if="phone == 'huawei'">{{ formatHuaweiTopTime(message.time) }}</text>
<text v-else>{{ formatChatTime(message.time) }} <text
v-if="(phone == 'oppo' || (phone == 'vivo' && message.isMe)) && message.simIndex">
{{ simInfo[`sim${message.simIndex}`] }}
</text></text>
<image style="width: 20rpx;height: 24rpx;margin-left: 8rpx; "
v-if="phone == 'oppo' || (phone == 'vivo' && message.isMe)"
:src="`/static/image/phone-message/huawei/chat-ka${message.simIndex}.png`">
</image>
</view>
<view class="chat-box" :id="'msg-' + index" :class="{
'tail-right': shouldApplyTailRight(index),
'tail-left': shouldApplyTailLeft(index),
'delivered': isLastMeMessage(index)
}" @longpress="!sortMode && onMessageLongPress(index, message)">
<text v-if="message.isMe && phone == 'mi'" class="send-text">送达</text>
<view class="chat-bubble">
<view v-html="formatMessageContent(message.content, message.isMe)"></view>
</view>
</view>
<view v-if="phone == 'huawei'" class="second-info">
<text>{{ formatHuaweiBottomTime(message.time) }}</text>
<image :src="`/static/image/phone-message/huawei/chat-ka${message.simIndex}.png`"></image>
</view>
<view v-if="(phone == 'oppo' || phone == 'vivo') && message.isMe" class="second-info">
<text v-if="message.isMe" class="delivered">已送达</text>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, reactive, onMounted, computed, watch, nextTick } from 'vue'
import { onLoad, onPageScroll } from "@dcloudio/uni-app";
import { stringUtil, util } from '@/utils/common.js';
const props = defineProps({
//
phone: {
type: String,
default: 'iphone'
},
messageList: {
type: Array,
default: []
},
//
sortMode: {
type: Boolean,
default: false
}
})
const SIM_STORAGE_KEY = 'sim_info'
const emit = defineEmits(['onMessageLongPress', 'sort'])
const simInfo = ref({
sim1: '中国电信',
sim2: '中国移动'
})
onMounted(() => {
try {
const cached = uni.getStorageSync(SIM_STORAGE_KEY)
simInfo.value = cached ? JSON.parse(cached) : { sim1: '中国电信', sim2: '中国移动' }
} catch (e) {
simInfo.value = { sim1: '中国电信', sim2: '中国移动' }
}
})
//
// props.messageList emit
const localSortList = ref([])
const dragIndex = ref(-1)
const dragOverIndex = ref(-1)
const dropPosition = ref('after') // 'before'=, 'after'=
let isDragging = false
let sortItemRects = []
// sortMode
watch(() => props.sortMode, (val) => {
if (val) {
localSortList.value = props.messageList.map(item => ({ ...item }))
} else {
dragIndex.value = -1
dragOverIndex.value = -1
isDragging = false
sortItemRects = []
localSortList.value = []
}
})
//
const displayList = computed(() => {
if (props.sortMode) return localSortList.value
return props.messageList
})
// isMe==trueisMe==truem-t-16
const shouldApplyMt16 = (index) => {
if (index === 0) return false;
const currentMsg = displayList.value[index];
const prevMsg = displayList.value[index - 1];
if (currentMsg.isMe && !prevMsg.isMe) {
return true;
}
return false;
}
// isMe==trueisMe==truem-t-16
const shouldApplyNextIsMe = (index) => {
const currentMsg = displayList.value[index];
//
if (index >= displayList.value.length - 1) return false;
const nextMsg = displayList.value[index + 1];
if (currentMsg.isMe && nextMsg.isMe) {
return true;
}
return false;
}
//
const isLastMeMessage = (currentIndex) => {
const currentMsg = displayList.value[currentIndex];
if (!currentMsg.isMe) return false;
// isMe=true
for (let i = currentIndex + 1; i < displayList.value.length; i++) {
if (displayList.value[i].isMe) {
return false;
}
}
return true;
}
// tail-right
const shouldApplyTailRight = (index) => {
const currentMsg = displayList.value[index];
if (!currentMsg.isMe) return false; //
//
if (index === displayList.value.length - 1) return true;
const nextMsg = displayList.value[index + 1];
// c: isMe == false ()
if (!nextMsg.isMe) return true;
// a: (180000 ) - 线
const currentMsgTime = new Date(currentMsg.time.replace(/-/g, '/')).getTime();
const nextMsgTime = new Date(nextMsg.time.replace(/-/g, '/')).getTime();
if (!isNaN(currentMsgTime) && !isNaN(nextMsgTime) && (nextMsgTime - currentMsgTime > 180000)) {
return true;
}
//
return false;
}
// tail-left
const shouldApplyTailLeft = (index) => {
const currentMsg = displayList.value[index];
if (currentMsg.isMe) return false;
// isMe==false
if (index === displayList.value.length - 1) return true;
const nextMsg = displayList.value[index + 1];
// c: isMe == true
if (nextMsg.isMe) return true;
// a: (180000 )
const currentMsgTime = new Date(currentMsg.time.replace(/-/g, '/')).getTime();
const nextMsgTime = new Date(nextMsg.time.replace(/-/g, '/')).getTime();
if (!isNaN(currentMsgTime) && !isNaN(nextMsgTime) && (nextMsgTime - currentMsgTime > 180000)) {
return true;
}
// b: isMe == false
for (let i = index + 1; i < displayList.value.length; i++) {
if (!displayList.value[i].isMe) {
return false; // isMe==false
}
}
return true;
}
//
const formatChatTime = (timeStr) => {
if (!timeStr) return '';
const date = new Date(timeStr.replace(/-/g, '/'));
if (isNaN(date.getTime())) return timeStr;
const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const target = new Date(date.getFullYear(), date.getMonth(), date.getDate());
const diffDays = Math.floor((today.getTime() - target.getTime()) / (1000 * 60 * 60 * 24));
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const timeNum = `${hours}:${minutes}`;
if (diffDays === 0) {
if (props.phone == 'iphone' || props.phone == 'oppo') return `今天 ${timeNum}`;
return `${timeNum}`;
} else if (diffDays === 1) {
return `昨天 ${timeNum}`;
} else if (diffDays > 1 && diffDays < 7) {
const weekDays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
return `${weekDays[date.getDay()]} ${timeNum}`;
} else {
//
if (date.getFullYear() === now.getFullYear()) {
return `${date.getMonth() + 1}${date.getDate()}${timeNum}`;
} else {
return `${date.getFullYear()}${date.getMonth() + 1}${date.getDate()}${timeNum}`;
}
}
}
//
const formatHuaweiTopTime = (timeStr) => {
if (!timeStr) return '';
const date = new Date(timeStr.replace(/-/g, '/'));
if (isNaN(date.getTime())) return timeStr;
const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const target = new Date(date.getFullYear(), date.getMonth(), date.getDate());
const diffDays = Math.floor((today.getTime() - target.getTime()) / (1000 * 60 * 60 * 24));
if (diffDays === 0) {
return `今天`;
} else if (diffDays === 1) {
return `昨天`;
} else {
const weekDays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
const weekdayStr = weekDays[date.getDay()];
return `${date.getFullYear()}${date.getMonth() + 1}${date.getDate()}${weekdayStr}`;
}
}
//
const formatHuaweiBottomTime = (timeStr) => {
if (!timeStr) return '';
const date = new Date(timeStr.replace(/-/g, '/'));
if (isNaN(date.getTime())) return timeStr;
const now = new Date();
// 60000
const diffMs = now.getTime() - date.getTime();
if (diffMs >= 0 && diffMs <= 60000) {
return '刚刚';
}
const ampm = date.getHours() >= 12 ? '下午' : '上午';
// 12
let displayHour = date.getHours() % 12;
displayHour = displayHour ? displayHour : 12; // 0() 12
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${ampm}${displayHour}:${minutes}`;
}
//
// timeMode: 'show'=, 'hide'=, 'auto'=
const shouldShowTime = (index) => {
const currentMsg = displayList.value[index];
const mode = currentMsg.timeMode;
// hideTime
if (mode === 'hide' || currentMsg.hideTime) return false;
//
if (mode === 'show') return true;
// auto /
if (index === 0) return true; //
//
if (props.sortMode) return true;
const currentMsgTime = new Date(currentMsg.time.replace(/-/g, '/')).getTime();
const prevMsgTime = new Date(displayList.value[index - 1].time.replace(/-/g, '/')).getTime();
if (isNaN(currentMsgTime) || isNaN(prevMsgTime)) return true;
//
return Math.abs(currentMsgTime - prevMsgTime) > 180000;
}
// 5 线
const formatMessageContent = (content, isMe) => {
if (!content) return '';
// url 5
// URL: http(s)://... xxx.xxx/...
const combinedRegex = /((?:https?:\/\/)?(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(?:\/[a-zA-Z0-9_/%-]*)*)|(\b\d{5,}(?:\.\d+)?\b)/g;
return content.replace(combinedRegex, (match, p1, p2) => {
// 线
if (p2 && p2.includes('.')) {
return match;
}
let color = '#3A85F8'
if (props.phone == 'iphone') color = '#0B7AE3'
if (props.phone == 'mi') color = '#3A85F8'
const colorStyle = !isMe ? `color: ${color};` : '';
return `<span style="text-decoration: underline; ${colorStyle}">${match}</span>`;
});
}
/**
* 长按信息
* @param index
* @param message
*/
const onMessageLongPress = (index, message, type = 'message') => {
emit('onLongPress', index, message, type)
}
// ===================== =====================
/**
* 刷新所有排序条目的 rect 坐标缓存
*/
const refreshSortRects = (callback) => {
const query = uni.createSelectorQuery()
const len = localSortList.value.length
let collected = []
let done = 0
for (let i = 0; i < len; i++) {
query.select('#msg-' + i).boundingClientRect(rect => {
collected[i] = rect
done++
if (done === len && callback) callback(collected)
})
}
query.exec()
}
/**
* 长按手柄开始拖拽
*/
const onSortLongPress = (idx, e) => {
dragIndex.value = idx
dragOverIndex.value = idx
isDragging = true
refreshSortRects(rects => {
sortItemRects = rects
})
uni.vibrateShort({ type: 'medium' })
}
/**
* 拖拽移动 - 根据触摸点 Y 坐标確定悬停位置和插入方向
*/
const onSortTouchMove = (idx, e) => {
if (!isDragging || dragIndex.value === -1) return
if (!e.touches || !e.touches[0]) return
const touchY = e.touches[0].clientY
if (!sortItemRects || sortItemRects.length === 0) return
let overIdx = dragOverIndex.value
for (let i = 0; i < sortItemRects.length; i++) {
const rect = sortItemRects[i]
if (rect && touchY >= rect.top && touchY <= rect.bottom) {
overIdx = i
//
const mid = rect.top + rect.height / 2
dropPosition.value = touchY < mid ? 'before' : 'after'
break
}
}
dragOverIndex.value = overIdx
}
/**
* 拖拽结束 - 根据 dropPosition 決定插入到目标的上方还是下方
*/
const onSortTouchEnd = (idx, e) => {
if (!isDragging) return
const from = dragIndex.value
const to = dragOverIndex.value
if (from !== -1 && to !== -1 && from !== to) {
const list = [...localSortList.value]
const [removed] = list.splice(from, 1)
// from to from to -1
let insertAt = to > from ? to - 1 : to
if (dropPosition.value === 'after') insertAt += 1
list.splice(insertAt, 0, removed)
localSortList.value = list
emit('sort', list.map(item => ({ ...item })))
nextTick(() => {
refreshSortRects(rects => {
sortItemRects = rects
})
})
}
dragIndex.value = -1
dragOverIndex.value = -1
dropPosition.value = 'after'
isDragging = false
}
</script>
<style lang="less" scoped>
/* ===== 排序模式公共样式(跨所有手机品牌通用)===== */
/* 排序模式下每条消息左右横排(手柄 + 气泡取剩余宽度)*/
.is-sort-mode {
display: flex;
flex-direction: row;
align-items: stretch;
}
/* 拖拽手柄区域 */
.sort-handle-wrap {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 60rpx;
flex-shrink: 0;
padding: 0 8rpx;
.sort-handle-bar {
margin: 3rpx 0;
}
}
.sort-handle-bar {
width: 30rpx;
height: 4rpx;
border-radius: 2rpx;
background-color: #CCCCCC;
}
/* 被拖拽中的条目:高亮蓝边 + 轻微缩进 */
.sort-dragging {
opacity: 0.7;
background-color: rgba(0, 122, 255, 0.06) !important;
border-left: 4rpx solid #007AFF;
}
/* 插入线:将拖拽项放在目标上方 */
.sort-drop-before {
border-top: 3rpx dashed #007AFF !important;
padding-top: 4rpx;
}
/* 插入线:将拖拽项放在目标下方 */
.sort-drop-after {
border-bottom: 3rpx dashed #007AFF !important;
padding-bottom: 4rpx;
}
//
.iphone-style {
.m-t-16 {
margin-top: 16rpx !important;
}
.top-text {
text-align: center;
font-size: 20rpx;
color: #838383;
margin-top: 26rpx;
}
.time {
color: #838383;
font-size: 20rpx;
text-align: center;
margin-bottom: 20rpx;
margin-top: 36rpx;
}
.chat-box {
display: flex;
justify-content: flex-start;
width: 100%;
.chat-bubble {
padding: 18rpx 22rpx;
background-color: #E9E9EB;
border-radius: 34rpx;
max-width: 600rpx;
font-size: 32rpx;
line-height: 38rpx;
color: #1A1A1A;
margin: 4rpx 30rpx 0;
word-break: break-all;
}
}
.isMe {
.chat-box {
justify-content: flex-end;
}
.chat-bubble {
background-color: #34C85A;
color: #fff;
}
}
.tail-left {
position: relative;
}
.tail-left::after {
position: absolute;
left: 18rpx;
bottom: 0;
content: '';
background: url('/static/image/phone-message/iphone/left-msg-box.png') no-repeat center bottom;
background-size: 100% 100%;
width: 28rpx;
height: 36rpx;
}
.tail-right {
position: relative;
}
.delivered::before {
position: absolute;
right: 18rpx;
bottom: -12rpx;
content: '已送达';
font-size: 20rpx;
line-height: 20rpx;
color: #8B8B8B;
transform: translateY(100%);
}
.tail-right::after {
position: absolute;
right: 18rpx;
bottom: 0;
content: '';
background: url('/static/image/phone-message/iphone/right-msg-box.png') no-repeat center bottom;
background-size: 100% 100%;
width: 28rpx;
height: 36rpx;
}
}
//
.mi-style {
.top-text {
text-align: center;
font-size: 20rpx;
color: #838383;
margin-top: 26rpx;
}
.time {
padding: 0 54rpx;
color: #646464;
font-size: 24rpx;
line-height: 28rpx;
margin-bottom: 16rpx;
}
.chat-box {
display: flex;
justify-content: flex-start;
width: 100%;
.chat-bubble {
padding: 22rpx 42rpx;
background-color: #FFFFFF;
border-radius: 32rpx;
font-size: 32rpx;
line-height: 50rpx;
color: #1A1A1A;
max-width: 550rpx;
margin: 0 24rpx;
word-break: break-all;
}
}
.message-item {
margin-top: 16rpx;
}
.m-t-44 {
margin-top: 44rpx;
}
.isMe {
.time {
text-align: right;
}
.chat-box {
justify-content: flex-end;
align-items: center;
.send-text {
color: #646464;
font-size: 24rpx;
}
.chat-bubble {
margin-left: 12rpx;
background-color: #3681FF;
color: #FFFFFF;
}
}
}
}
// oppo
.oppo-style {
.m-t-16 {
margin-top: 24rpx !important;
}
.time {
color: #727377;
font-size: 24rpx;
line-height: 24rpx;
text-align: center;
margin-bottom: 20rpx;
margin-top: 48rpx;
display: flex;
justify-content: center;
align-items: center;
}
.message-item {
margin-top: 12rpx;
}
.chat-box {
display: flex;
justify-content: flex-start;
width: 100%;
.chat-bubble {
padding: 28rpx 48rpx;
background-color: #FFFFFF;
border-radius: 32rpx;
max-width: 600rpx;
font-size: 32rpx;
line-height: 44rpx;
color: #1A1A1A;
margin: 0 32rpx;
word-break: break-all;
}
}
.isMe {
.chat-box {
justify-content: flex-end;
}
.chat-bubble {
background-color: #00A1FF;
color: #fff;
}
}
.second-info {
font-size: 24rpx;
text-align: right;
padding: 0 34rpx;
margin-top: 14rpx;
padding-bottom: 6rpx;
.delivered {
font-size: 24rpx;
line-height: 24rpx;
color: #727377;
}
}
}
//
.huawei-style {
.top-text {
text-align: center;
font-size: 22rpx;
line-height: 22rpx;
color: #545454;
margin-bottom: 12rpx;
}
.time {
text-align: center;
font-size: 22rpx;
line-height: 22rpx;
color: #545454;
margin-top: 40rpx;
}
.chat-box {
display: flex;
justify-content: flex-start;
width: 100%;
.chat-bubble {
padding: 12rpx 24rpx;
background-color: #FFFFFF;
border-radius: 4rpx 28rpx 28rpx 28rpx;
max-width: 550rpx;
margin: 0 30rpx;
margin-top: 34rpx;
font-size: 30rpx;
line-height: 38rpx;
color: #1a1a1a;
font-weight: 500;
word-break: break-all;
}
}
.second-info {
display: flex;
align-items: center;
color: #545454;
font-size: 20rpx;
line-height: 24rpx;
margin-top: 16rpx;
padding-left: 52rpx;
padding-right: 56rpx;
image {
width: 14rpx;
height: 18rpx;
margin-left: 8rpx;
}
}
.m-t-44 {
margin-top: 44rpx;
}
.isMe {
.second-info {
justify-content: flex-end;
}
.message-item {
margin-top: 34rpx;
}
.chat-box {
justify-content: flex-end;
align-items: center;
.send-text {
color: #646464;
font-size: 24rpx;
}
.chat-bubble {
margin-left: 12rpx;
background-color: #3681FF;
color: #FFFFFF;
border-radius: 28rpx 4rpx 28rpx 28rpx;
}
}
}
}
// vivo
.vivo-style {
.m-t-16 {
margin-top: 24rpx !important;
}
.time {
color: #ACACAC;
font-size: 24rpx;
line-height: 24rpx;
text-align: center;
margin: 40rpx 0;
display: flex;
justify-content: center;
align-items: center;
}
.message-item {
margin-top: 12rpx;
}
.chat-box {
display: flex;
justify-content: flex-start;
width: 100%;
.chat-bubble {
padding: 38rpx 38rpx 32rpx 40rpx;
background-color: #FFFFFF;
border-radius: 32rpx;
max-width: 100%;
font-size: 36rpx;
line-height: 48rpx;
color: #1A1A1A;
margin: 0 32rpx;
word-break: break-all;
}
}
.isMe {
.chat-box {
justify-content: flex-end;
}
.chat-bubble {
background-color: #0078FE;
color: #fff;
}
}
.second-info {
font-size: 24rpx;
text-align: right;
padding: 0 34rpx;
margin-top: 12rpx;
padding-bottom: 6rpx;
.delivered {
font-size: 24rpx;
line-height: 24rpx;
color: #ACACAC;
}
}
}
</style>

View File

@ -0,0 +1,716 @@
<template>
<view :style="`${phone}-style`">
<uni-swipe-action class="swipe-action">
<!-- 使用插槽 请自行给定插槽内容宽度-->
<uni-swipe-action-item class="swipe-action-item" v-for="(item, index) 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">{{ item.title }}</text>
<text class="time">{{ formatDate(item.chatList?.[item.chatList?.length -
1]?.time || item.time)
}}</text>
</view>
<view class="content"
v-html="item.chatList?.[item.chatList?.length - 1]?.content || ''"></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>
import { stringUtil } from '@/utils/common.js';
</script>
<script setup>
import {
ref,
reactive,
computed
} from 'vue'
import {
util,
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)
}
}
/**
* 点击列表元素
*/
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 {
position: absolute;
padding: 4rpx 10rpx;
background-color: #E93A22;
color: #FFFFFF;
line-height: 20rpx;
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 34rpx 28rpx 0;
margin-left: 32rpx;
height: 100%;
}
.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,589 @@
<template>
<view :class="`${phone}-style`">
<!-- 导航样式 -->
<view class="nav-bar-box" :class="{ 'border-nav-bar-box': isScroll }">
<NavBar :isBack="false" :bgColor="isScroll ? data.navBar.bgColor : '#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"></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 flex">
<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>
</template>
</view>
</template>
<script setup>
import NavBar from '@/components/nav-bar/nav-bar.vue'
import {
ref,
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 data = reactive({
navBar: {
title: '信息',
bgColor: '#FFFFFF',
},
})
//
const showInfo = computed(() => {
let text, placeholder, secondText, bottomLtext, bottomRtext
switch (props.phone) {
case "iphone":
text = "信息"
placeholder = '搜索'
data.navBar.bgColor = '#F8F8F8'
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 {
height: 148rpx;
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

@ -1,5 +1,5 @@
<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 }">
@ -11,9 +11,9 @@
<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"
@ -130,6 +130,10 @@ const props = defineProps({
type: Boolean,
default: false
},
isBack: {
type: Boolean,
default: true
},
tipLayerText: {
type: String,
default: ''
@ -141,6 +145,10 @@ const props = defineProps({
tipLayerType: {
type: String,
default: ''
},
isClickNavBarOpenPopup: {
type: Boolean,
default: false
}
})
@ -195,11 +203,21 @@ const onRightClick = () => {
emit('right-click')
}
const closeTopPopup = () => {
topPopup.value.close()
}
const buttonClick = (button) => {
topPopup.value.close()
closeTopPopup()
emit('button-click', button)
}
//
defineExpose({
openPopup,
closeTopPopup
})
</script>
<style scoped>

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",

BIN
eslint-output.json Normal file

Binary file not shown.

15
main.js
View File

@ -16,20 +16,21 @@ 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() || {}
}
app.config.globalProperties.$system = systemInfo.platform == 'ios' ? 'iOS' : 'Android'
// #ifdef APP-PLUS
app.config.globalProperties.$system = plus.os.name;
// #endif
app.config.globalProperties.$systemInfo = systemInfo
uni.setStorageSync('version', '1.0.2.sp4')
uni.setStorageSync('version', '1.0.4.sp17')
app.config.globalProperties.$version = uni.getStorageSync('version')
app.use(globalMethods);
return {
app

View File

@ -1,37 +1,46 @@
{
"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" //
}
}
},
"app-plus" : {
"darkmode" : false,
"usingComponents" : true,
"nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3,
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : false,
"delay" : 0
},
"optimization": {
"subPackages": 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 +59,46 @@
]
},
/* 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"
}

2308
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,20 +1,39 @@
{
"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": {
"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

@ -9,11 +9,90 @@
}
}
],
"subPackages": [
"subPackages": [{
"root": "pages/call-log",
"pages": [{
"path": "call",
"style": {
"navigationBarTitleText": "通话记录页面",
"navigationStyle": "custom"
}
}]
},
{
"root": "pages/message",
"pages": [{
"path": "list-index",
"style": {
"navigationBarTitleText": "短信列表首页",
"navigationStyle": "custom"
}
},
{
"path": "chat-page/chat-page",
"style": {
"navigationBarTitleText": "短信聊天页面",
"navigationStyle": "custom"
}
}
]
},
{
"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"
}
}
]
},
{
"root": "pages/balance",
"pages": [
{
"pages": [{
"path": "index",
"style": {
"navigationBarTitleText": "余额页面",
@ -27,13 +106,19 @@
"navigationBarTitleText": "快速入口页面",
"navigationStyle": "custom"
}
},
{
"path": "transfer/transfer",
"style": {
"navigationBarTitleText": "转账模拟",
"navigationStyle": "custom"
}
}
]
},
{
"root": "pages/bill",
"pages": [
{
"pages": [{
"path": "bill-list/bill-list",
"style": {
"navigationBarTitleText": "账单列表页面",
@ -58,8 +143,7 @@
},
{
"root": "pages/ant-credit-pay",
"pages": [
{
"pages": [{
"path": "index",
"style": {
"navigationBarTitleText": "花呗首页",
@ -77,8 +161,7 @@
},
{
"root": "pages/finance-management",
"pages": [
{
"pages": [{
"path": "index",
"style": {
"navigationBarTitleText": "理财首页",
@ -103,8 +186,7 @@
},
{
"root": "pages/other",
"pages": [
{
"pages": [{
"path": "/video-group-chat/video-group-chat",
"style": {
"navigationBarTitleText": "视频群聊",
@ -170,13 +252,61 @@
"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"
}
}
]
},
{
"root": "pages/common",
"pages": [
{
"pages": [{
"path": "hot-icon/hot-icon",
"style": {
"navigationBarTitleText": "热门图标",
@ -203,6 +333,13 @@
"navigationBarTitleText": "充值页",
"navigationStyle": "custom"
}
},
{
"path": "call-and-message-entry/call-and-message-entry",
"style": {
"navigationBarTitleText": "呼叫和短信入口",
"navigationStyle": "custom"
}
}
]
}
@ -216,7 +353,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')">
<watermark :dark="data.dark" source="uni_alipay_huabei" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark', 'uni_alipay_huabei')">
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
</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>
@ -201,6 +208,8 @@ import {
const instance = getCurrentInstance();
const { proxy } = instance;
const navBarRef = ref(null)
const buttonGroup = [{
name: "编辑花呗数据",
@ -221,6 +230,7 @@ const buttonGroup = [{
}, {
name: "花呗逾期",
isSwitch: true,
key: 'isOverdue',
click: () => {
data.huabeiInfo.isOverdue = !data.huabeiInfo.isOverdue
uni.setStorageSync(data.huabeiInfoStorageKey, data.huabeiInfo)
@ -229,6 +239,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()
}
}]
@ -704,7 +728,7 @@ const goBack = () => {
justify-content: center;
.text {
color: #ffffff;
color: #B9D6FF;
font-size: 26rpx;
line-height: 1.2;
}
@ -738,7 +762,7 @@ const goBack = () => {
}
.value {
color: #ffffff;
color: #B9D6FF;
font-size: 26rpx;
line-height: 36rpx;
}

View File

@ -206,8 +206,8 @@
<!-- 水印 -->
<view v-if="$isVip()">
<watermark :dark="data.dark" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark')">
<watermark :dark="data.dark" source="uni_alipay_huabei" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark', 'uni_alipay_huabei')">
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
</liu-drag-button>
</view>

View File

@ -1,14 +1,14 @@
<template>
<!-- 水印 -->
<view v-if="$isVip()">
<watermark :dark="data.dark" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark')">
<watermark :dark="data.dark" source="uni_alipay_balance" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark', 'uni_alipay_balance')">
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
</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;">
@ -111,7 +111,7 @@
</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 { fastEntranceList } from '@/static/json/initial.json'
import {
@ -211,7 +211,7 @@ onLoad(async () => {
})
onShow(() => {
// #ifdef APP-PLUS
// #ifdef APP-PLUS&&!APP-HARMONY
util.setAndroidSystemBarColor('#F0F3F8')
setTimeout(() => {
plus.navigator.setStatusBarStyle("light");
@ -515,11 +515,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 {

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>
@ -53,8 +57,11 @@
<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="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 +73,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 +105,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 +119,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>
@ -172,6 +182,7 @@
<image class="edit-image" src="/static/image/bill/add-bill/edit.png">
</image>
</view>
</template>
</view>
@ -187,8 +198,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 +222,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 +242,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 +347,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,
@ -408,6 +483,13 @@ const switchOptions = [{
"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',
}
]
@ -481,6 +563,10 @@ const data = reactive({
note: {
isImage: false,
text: ''
},
isShowBillAnalysis: false,
billAnalysisInfo: {
text: '本笔登上月收入榜,看看分析吧'
}
}
},
@ -492,7 +578,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 {
@ -882,6 +978,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 +1060,7 @@ const uploadImage = () => {
*/
const saveAndDisplayImage = (file) => {
// #ifdef H5
return Promise.resolve(file)
// return Promise.resolve(file)
// #endif
// #ifndef H5
@ -1230,6 +1335,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 {
@ -1468,4 +1589,20 @@ 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;
}
}
</style>

View File

@ -1,8 +1,8 @@
<template>
<!-- 水印 -->
<view v-if="$isVip()">
<watermark :dark="data.dark" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark')">
<watermark :dark="data.dark" source="uni_alipay_bill_details" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark', 'uni_alipay_bill_details')">
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
</liu-drag-button>
</view>
@ -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>
@ -171,8 +172,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">
@ -253,7 +255,8 @@ const {
} = addBillJson
import {
util,
randomUtil
randomUtil,
numberUtil
} from '@/utils/common.js'
import {

View File

@ -1,8 +1,8 @@
<template>
<!-- 水印 -->
<view v-if="$isVip()">
<watermark :dark="data.dark" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark')">
<watermark :dark="data.dark" source="uni_alipay_bill" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark', 'uni_alipay_bill')">
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
</liu-drag-button>
</view>
@ -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>
@ -129,7 +129,8 @@
<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'
@ -283,7 +284,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 +342,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 +624,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);

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

@ -0,0 +1,110 @@
<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')">
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
</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]"></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(() => {
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);
}
})
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>

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>

View File

@ -4,10 +4,11 @@
<view>
<image :src="data.banner" style="width: 100%;height: 244px;"></image>
</view>
<view style="margin-top:-90px;position: relative;">
<exchange v-if="data.tradeCoupon" />
<!-- <view style="margin-top:-90px;position: relative;">
<customTab :isHuise="shouldBeTrue(data.goods)" />
</view>
<scroll-view class="package-items-box" scroll-x="true" v-if="data.goodsList.length">
</view> -->
<scroll-view class="package-items-box" scroll-x="true" v-if="data.goodsList.length" :show-scrollbar="false">
<view class="package-items-container">
<view class="package-item" :class="{ 'active-package-item': selected == (index + 1) }"
v-for="(item, index) in data.goodsList" @click="onSelect(item, index)" :key="index">
@ -17,7 +18,7 @@
</view>
<text class="del-text">{{ item.origin_price }}</text>
</view>
<view class="price-box">
<view class="price-box wx-font-regular">
<text></text><text class="price">{{ item.price }}</text>
</view>
<view class="shen ">
@ -29,6 +30,22 @@
</view>
</view>
</scroll-view>
<view class="vipContent" style="position: relative;">
<view class="top">
<image src="/static/image/recharge/vipContentTopBgImg.png" mode="widthFix"></image>
</view>
<view class="vipList">
<template v-for="(item, index) in data.benefitList" :key="index">
<view class="item"
v-if="data.goods.features == 'common' || (data.goods.features != 'common' && index < 10)">
<image :src="item.url"></image>
<text>{{ item.name }}</text>
</view>
</template>
</view>
</view>
<view class="myCouponList" @click="data.myActivity.length > 0 ? myCoupon.open() : ''">
<view class="left">
<image src="/static/image/recharge/couponMin.png" mode=""></image>
@ -115,7 +132,8 @@
合计
<text></text>
<countUp :num="$toFiexd(data.price, 2)" height="24" style="margin-top: -6px;" color="red"
fontSize='24'></countUp>
fontSize='24'>
</countUp>
已优惠{{ $toFiexd(data.coupon, 0) }}
</view>
<view class="button" @click="activateVip">
@ -334,7 +352,6 @@ const data = reactive({
active_id: 0,
active_ids: 0,
activeitems: {},
coupon: true,
currentUrl: '',
background: ['color1', 'color2', 'color3'],
indicatorDots: false,
@ -361,44 +378,56 @@ const data = reactive({
url: "/static/image/recharge/icon2.png"
},
{
name: "专属客服",
name: "微信模拟",
url: "/static/image/recharge/icon3.png"
},
{
name: "多设备",
name: "小宝模拟",
url: "/static/image/recharge/icon4.png"
},
{
name: "AI聊天模板",
name: "豪车模拟",
url: "/static/image/recharge/icon5.png"
},
{
name: "聊天转账",
name: "模拟来电",
url: "/static/image/recharge/icon6.png"
},
{
name: "限额设置",
name: "视频群聊",
url: "/static/image/recharge/icon7.png"
},
{
name: "零钱修改",
name: "工资单",
url: "/static/image/recharge/icon8.png"
},
{
name: "零钱通",
name: "身份证",
url: "/static/image/recharge/icon9.png"
},
{
name: "分付",
name: "发圈素材",
url: "/static/image/recharge/icon10.png"
},
{
name: "账单",
name: "机票",
url: "/static/image/recharge/icon11.png"
},
{
name: "朋友圈",
name: "高铁表",
url: "/static/image/recharge/icon12.png"
},
{
name: "短信模拟",
url: "/static/image/recharge/icon13.png"
},
{
name: "通话模拟",
url: "/static/image/recharge/icon14.png"
},
{
name: "多设备",
url: "/static/image/recharge/icon15.png"
}
],
goodsList: [],
@ -433,6 +462,9 @@ const data = reactive({
//
isProcessingClick: false,
banner: '/static/image/recharge/banner1.png',
isCombo: false,
source: "uni_alipay_other",
tradeCoupon: false
})
let {
@ -441,7 +473,8 @@ let {
benefitList,
noticeList,
commentList,
paymentMethod
paymentMethod,
source
} = toRefs(data)
/**
@ -455,15 +488,27 @@ onBackPress((e) => {
})
onLoad(async () => {
onLoad(async (option) => {
if (option.source) {
data.source = option.source
}
const config = uni.getStorageSync('config').config
const themeConfig = config?.['client.uniapp.theme']
data.tradeCoupon = config?.['client.trade_coupon'] === true ? true : false
if (themeConfig?.enable) {
if (themeConfig?.theme == '0214') {
data.banner = '/static/image/recharge/214/bannar.png'
// })
} else if (themeConfig?.theme == '0217') {
data.banner = '/static/image/recharge/chunjie/bannar.png'
} else if (themeConfig?.theme == '0405') {
data.banner = '/static/image/recharge/qmj/bannar.png'
data.benefitList.forEach(item => {
item.url = item.url.replace('/static/image/recharge/',
'/static/image/recharge/qmj/')
})
} else if (themeConfig?.theme == '0401') {
data.banner = '/static/image/recharge/yrj/bannar.png'
}
}
let pages = getCurrentPages();
@ -490,6 +535,9 @@ onLoad(async () => {
})
console.log(goods)
if (goods.code == 0) {
data.goodsList.forEach(item => {
item['features'] = item['features'] || ''
})
data.goodsList = goods.data
let zsItem = goods.data[0]
@ -597,8 +645,24 @@ onLoad(async () => {
})
onShow(async () => {
onShow(() => {
if (data.isCombo) {
uni.showModal({
title: '提示',
content: '是否支付成功?',
cancelText: '未支付',
confirmText: "已支付",
success: function (res) {
if (res.confirm) {
console.log('用户点击确定');
paymentResult(uni.getStorageSync('orderId'), paymentMethod.value)
} else if (res.cancel) {
console.log('用户点击取消');
}
}
});
data.isCombo = false
}
})
onUnload(() => {
// uni.offNativeEventReceive()
@ -1106,13 +1170,15 @@ async function activateVip(type = '') {
"支付方式": paymentMethod.value == "wxpay" ? '微信' : "支付宝",
})
let isComBo = uni.getStorageSync('isCombo') == 'ok'
let paymentRes = await postJson('a', 'api/order', {
goods_id: data.goods.goods_id,
coupon: data.active_id ? data.active_id : '',
pay_type: paymentMethod.value == "wxpay" ? 'weixin' : "alipay",
pay_type: paymentMethod.value == "wxpay" ? (data.goods.weixinMpOriId && isComBo ? 'combo' :
'weixin') : "alipay",
"pay_source": "app",
source: "uni_alipay",
// source: "uni_alipay",
source: data.source,
})
@ -1120,23 +1186,38 @@ async function activateVip(type = '') {
if (paymentRes.code == 0) {
let payData = {}
if (paymentMethod.value == "wxpay") {
payData = {
"appid": paymentRes.data.appId, // ID
"noncestr": paymentRes.data.nonceStr, //
"package": "Sign=WXPay", //
"partnerid": paymentRes.data.partnerId, //
"prepayid": paymentRes.data.prepayId, // ID
"timestamp": Number(paymentRes.data.timeStamp), //
"sign": paymentRes.data.sign //
}
uni.setStorageSync('orderId', paymentRes.data.orderId)
//wx
if (proxy.$system != 'iOS') {
uni.sendNativeEvent('wx_pay_start_alipay', paymentRes.data.orderId, ret => {
if (data.goods.weixinMpOriId && isComBo) {
console.log(paymentRes)
let SZappData = {
weixinMpOriId: data.goods.weixinMpOriId,
outTradeNo: paymentRes.data.outTradeNo
}
uni.setStorageSync('orderId', paymentRes.data.orderId)
uni.sendNativeEvent('start_combo_pay', SZappData, ret => {
console.log('宿主App回传的数据' + ret);
});
data.isCombo = true
uni.hideLoading();
return
} else {
payData = {
"appid": paymentRes.data.appId, // ID
"noncestr": paymentRes.data.nonceStr, //
"package": "Sign=WXPay", //
"partnerid": paymentRes.data.partnerId, //
"prepayid": paymentRes.data.prepayId, // ID
"timestamp": Number(paymentRes.data.timeStamp), //
"sign": paymentRes.data.sign //
}
uni.setStorageSync('orderId', paymentRes.data.orderId)
//wx
if (proxy.$system != 'iOS') {
uni.sendNativeEvent('wx_pay_start_alipay', paymentRes.data.orderId, ret => {
console.log('宿主App回传的数据' + ret);
});
}
}
} else {
payData = paymentRes.data.payParam
uni.setStorageSync('orderId', paymentRes.data.orderId)
@ -1467,7 +1548,9 @@ function shouldBeTrue(obj) {
return true;
}
</script>
<style>
@import "@/common/main.css";
</style>
<style lang="scss" scoped>
* {
box-sizing: content-box;
@ -1602,13 +1685,17 @@ function shouldBeTrue(obj) {
padding-bottom: calc(240rpx + env(safe-area-inset-bottom)) !important;
.package-items-box {
position: relative;
background-color: #F7F7F7;
border-radius: 16px 16px 0 0;
width: 100vw;
margin-top: -70px;
// background: linear-gradient(to bottom, #FFFFFF, #F7F7F7);
}
.package-items-container {
display: flex;
padding: 20rpx 32rpx;
padding: 40rpx 32rpx 40rpx 32rpx;
.active-package-item-tips {
position: absolute;
@ -1690,7 +1777,7 @@ function shouldBeTrue(obj) {
width: 184rpx;
height: 24px;
color: #AAAAAA;
font-size: 12px;
font-size: 10px;
line-height: 24px;
font-weight: bold;
white-space: nowrap;
@ -2125,7 +2212,7 @@ function shouldBeTrue(obj) {
}
.question {
margin: 24rpx 32rpx;
margin: 20rpx 32rpx;
padding: 17px 13px;
background-color: #FFFFFF;
border-radius: 10px 10px 10px 10px;
@ -2164,8 +2251,7 @@ function shouldBeTrue(obj) {
}
.myCouponList {
margin: 24rpx 32rpx;
margin: 20rpx 32rpx;
padding: 17px 13px;
height: 18px;
background-color: #FFFFFF;
@ -2409,4 +2495,55 @@ function shouldBeTrue(obj) {
left: 15px;
z-index: 999999;
}
.vipContent {
background-color: #fff;
margin-left: 16px;
width: calc(100% - 32px);
border-radius: 16px;
margin-bottom: 20rpx;
.top {
image {
width: 100%;
}
}
.vipList {
display: flex;
flex-wrap: wrap;
padding: 0 10px 10px 10px;
.item {
width: 20%;
height: 75px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
image {
width: 38px;
height: 38px;
}
text {
margin-top: 5px;
font-size: 10px;
color: #1A1A1A;
line-height: 15px;
text-align: center;
}
}
}
}
@font-face {
font-family: 'AlipayNumber'; //
src: url('/static/font/AlipayNumber.ttf');
}
.wx-font-regular {
font-family: 'AlipayNumber' !important;
}
</style>

View File

@ -1,8 +1,8 @@
<template>
<!-- 水印 -->
<view v-if="$isVip()">
<watermark :dark="data.dark" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark')">
<watermark dark="light" source="uni_alipay_finance" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark', 'uni_alipay_finance')">
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
</liu-drag-button>
</view>
@ -193,7 +193,7 @@
<!-- 底部导航栏 -->
<view class="bottom-box"
:class="`navigation-menu-${financeInfo.navigationMenuStyle}`, { 'ios-bottom-box': $system == 'iOS' }">
: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)">
<image
:src="`/static/image/finance-management/navigation-menu/${financeInfo.navigationMenuStyle}/${item.icon}.png`">

View File

@ -16,13 +16,12 @@
</view>
<view 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',
@ -80,7 +79,7 @@
<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>
@ -106,16 +105,16 @@
</view>
</view>
<view class="activity-box">
<!-- <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>
</view>
</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' }"
<view class="video-help-item" :style="{ width: (windowWidth - 32) / 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>
@ -132,7 +131,6 @@
</view>
</scroll-view>
</view>
</view>
</template>
@ -158,9 +156,19 @@ import {
onLoad,
onShow,
onHide,
onUnload
onUnload,
onReady
} from '@dcloudio/uni-app';
onReady(() => {
// NVUE 挂载极快,但给它 100~200ms 的喘息让原生视图确认渲染
setTimeout(() => {
// #ifdef APP-PLUS
plus.navigator.closeSplashscreen();
// #endif
}, 150);
});
// 内部埋点方法
const apiUserEvent = async (type, adminData) => {
let uni_version = uni.getStorageSync("version")
@ -189,7 +197,7 @@ const goRechargePage = () => {
}
})
uni.navigateTo({
url: '/pages/common/recharge/index'
url: '/pages/common/recharge/index?source=uni_alipay_index'
});
}
@ -218,19 +226,28 @@ const menuList = [{
name: "花呗",
isHot: false,
path: "/pages/ant-credit-pay/index"
}, {
icon: "zhuanzhang",
name: "转账模拟",
isHot: false,
path: "/pages/balance/transfer/transfer"
}, {
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",
@ -242,6 +259,36 @@ const otherList = [{
name: "视频群聊",
path: "/pages/other/video-group-chat/video-group-chat"
},
{
icon: "/static/image/index/qita/card.png",
name: "身份证",
path: "/pages/other/card/card"
},
{
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/ranking.png",
name: "从夯倒拉排名",
path: "/pages/other/ranking/ranking"
},
{
icon: "/static/image/index/qita/gouwu.png",
name: "购物",
path: "/pages/shopping/index"
},
{
icon: "/static/image/other/certificate/certificate.png",
name: "证书",
path: "/pages/other/certificate/graduate"
},
]
const data = reactive({
@ -830,7 +877,7 @@ onUnload(() => {
}
.group-box {
margin: 32rpx;
margin: 16px;
margin-bottom: 0;
}
@ -843,7 +890,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;

View File

@ -0,0 +1,742 @@
<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')">
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
</liu-drag-button>
</view>
<view :class="`${data.phone}-style`">
<ChatLayout :phone="data.phone" :chatInfo="data.data" :sortMode="isSortMode" @send="handleSend"
:number="data.number" @dblclick-left="onDblclickLeft" @dblclick-right="onDblclickRight">
<!-- 弹出操作层及遮罩 -->
<view v-if="showActionPopup" class="action-mask" @tap="closeActionPopup">
<view class="action-popup" :style="{ top: popupTop + 'px', left: popupLeft + 'px' }">
<view class="action-item" @tap.stop="handleEdit">
<image class="action-icon" src="/static/image/phone-message/bianji.png"></image>
<text>编辑</text>
</view>
<view class="action-item" @tap.stop="handleSwap">
<image class="action-icon" src="/static/image/phone-message/huhuan.png"></image>
<text>消息互换</text>
</view>
<view class="action-item" @tap.stop="handleSort">
<image class="action-icon" src="/static/image/phone-message/sort.png"></image>
<text>排序</text>
</view>
<view class="action-item" @tap.stop="handleChangeSpeaker">
<image class="action-icon" src="/static/image/phone-message/change.png"></image>
<text>切换发言人</text>
</view>
<view class="action-item" @tap.stop="handleDelete">
<image class="action-icon" src="/static/image/phone-message/shanchu.png"></image>
<text>删除</text>
</view>
<!-- 向上指的三角形因为要求在长按元素下方 -->
<view class="triangle"></view>
</view>
</view>
<!-- 编辑消息弹窗 -->
<view v-if="showEditPopup" class="edit-mask" @tap="closeEditPopup">
<view class="edit-popup" @tap.stop>
<view class="edit-header">编辑消息</view>
<view class="edit-body">
<view class="edit-row">
<text class="edit-label">时间</text>
<view class="time-picker-group">
<picker mode="date" :fields="$system == 'Android' ? 'day' : ''" :value="editingDate"
@change="onDateChange">
<view class="time-picker-item">
<text>{{ editingDate || '选择日期' }}</text>
</view>
</picker>
<picker mode="time" :fields="$system == 'Android' ? 'minute' : ''"
:value="editingTimeOfDay" @change="onTimeOfDayChange">
<view class="time-picker-item">
<text>{{ editingTimeOfDay || '选择时刻' }}</text>
</view>
</picker>
</view>
</view>
<view class="edit-row" style="align-items: flex-start;">
<text class="edit-label" style="padding-top: 6rpx;">时间显示</text>
<view class="time-mode-group">
<view class="time-mode-btn" :class="{ active: editingTimeMode === 'auto' }"
@tap="editingTimeMode = 'auto'">自动</view>
<view class="time-mode-btn" :class="{ active: editingTimeMode === 'show' }"
@tap="editingTimeMode = 'show'">强制显示</view>
<view class="time-mode-btn" :class="{ active: editingTimeMode === 'hide' }"
@tap="editingTimeMode = 'hide'">强制隐藏</view>
</view>
</view>
<view class="edit-row"
v-if="data.phone == 'huawei' || data.phone == 'oppo' || data.phone == 'vivo'">
<text class="edit-label">SIM卡</text>
<view class="edit-input"
style="padding: 0; background: transparent; display: flex; align-items: center; border-radius: 8rpx; height: 70rpx;">
<uni-data-select v-model="editingSimKa" :localdata="simList" :clear="false"
placeholder="请选择卡号" @change="onSimKaChange"
style="flex: 1; border: none !important; width: 100%;"></uni-data-select>
</view>
</view>
<view class="edit-row">
<text class="edit-label">内容</text>
</view>
<editor id="editor" class="edit-textarea" placeholder="请输入消息内容..." @ready="onEditorReady">
</editor>
</view>
<view class="edit-footer">
<view class="edit-btn cancel" @tap="closeEditPopup">取消</view>
<view class="edit-btn confirm" @tap="confirmEdit">确定</view>
</view>
</view>
</view>
<ChatList :messageList="messageList" :phone="data.phone" :sortMode="isSortMode"
@onLongPress="onMessageLongPress" @sort="onSortChange"></ChatList>
</ChatLayout>
<!-- 排序模式底部工具条 -->
<view v-if="isSortMode" class="sort-toolbar">
<view class="sort-toolbar-tip">长按消息并拖动调整顺序</view>
<view class="sort-toolbar-actions">
<view class="sort-toolbar-btn cancel" @tap="cancelSort">取消</view>
<view class="sort-toolbar-btn confirm" @tap="confirmSort">完成</view>
</view>
</view>
</view>
</template>
<script setup>
import ChatLayout from '@/components/message/chat/chat-layout.vue'
import ChatList from '@/components/message/chat/chat-list.vue'
import defaultData from '../defaultData.json'
import {
ref,
reactive
} from 'vue'
import {
onLoad,
onShow
} from "@dcloudio/uni-app";
import {
stringUtil,
util
} from '@/utils/common.js';
const defaultList = defaultData
// list-index.vue key
const STORAGE_KEY = 'message_list'
let currentId = null // id
let isMe = ref(true)
const messageList = ref([])
const showActionPopup = ref(false)
const popupTop = ref(0)
const popupLeft = ref(0)
const selectedMessage = ref(null)
const showEditPopup = ref(false)
const editingMessage = ref(null)
const editingTime = ref("") // "YYYY-MM-DD HH:mm"
const editingDate = ref("") // "YYYY-MM-DD"
const editingTimeOfDay = ref("") // "HH:mm"
const editingSimKa = ref("")
const editingTimeMode = ref('auto') // 'auto' | 'show' | 'hide'
let editorCtx = null // editor
// ===== =====
const isSortMode = ref(false)
let sortingListCache = [] // ChatList emit
const simList = [
{ text: "无卡号(不显)", value: "" },
{ text: "卡1", value: 1 },
{ text: "卡2", value: 2 }
]
/**
* 将当前 messageList 写回缓存中对应会话的 chatList
* 同时更新列表条目顶层的 time最新消息时间
*/
const saveChatList = () => {
if (!currentId) return
try {
const cached = uni.getStorageSync(STORAGE_KEY)
const list = cached ? JSON.parse(cached) : defaultList
const idx = list.findIndex(item => item.id == currentId)
if (idx > -1) {
list[idx].chatList = messageList.value.map(m => ({ ...m }))
const chatArr = list[idx].chatList
const last = chatArr.length ? chatArr[chatArr.length - 1] : null
if (last && last.time) list[idx].time = last.time
uni.setStorageSync(STORAGE_KEY, JSON.stringify(list))
}
} catch (e) {
//
}
}
const onEditorReady = () => {
uni.createSelectorQuery().select('#editor').context((res) => {
editorCtx = res.context
if (editingMessage.value && editingMessage.value.content) {
editorCtx.setContents({
html: editingMessage.value.content
})
}
}).exec()
}
let longpressType = ref('message')
//
const onMessageLongPress = (index, message, type) => {
selectedMessage.value = message;
longpressType.value = type
const selector = type === 'time' ? '#time-' + index : '#msg-' + index;
uni.createSelectorQuery().select(selector).boundingClientRect(rect => {
if (rect) {
// (bottom + )
popupTop.value = rect.bottom + 10;
//
let left = rect.left + rect.width / 2;
//
uni.getSystemInfo({
success: (info) => {
let popupWidth = 150; //
if (left < popupWidth / 2 + 10) left = popupWidth / 2 + 10;
if (left > info.windowWidth - popupWidth / 2 - 10) left = info.windowWidth - popupWidth / 2 - 10;
popupLeft.value = left;
showActionPopup.value = true;
}
})
}
}).exec();
}
const closeActionPopup = () => {
showActionPopup.value = false;
selectedMessage.value = null;
}
/**
* 编辑消息
*/
const handleEdit = () => {
editingMessage.value = selectedMessage.value;
editingTime.value = selectedMessage.value.time || "";
//
const parts = (selectedMessage.value.time || "").split(' ')
editingDate.value = parts[0] || ""
editingTimeOfDay.value = parts[1] || ""
editingSimKa.value = selectedMessage.value.simIndex !== undefined ? selectedMessage.value.simIndex : "";
editingTimeMode.value = selectedMessage.value.timeMode || 'auto';
showEditPopup.value = true;
closeActionPopup();
//
if (editorCtx) {
setTimeout(() => {
editorCtx.setContents({
html: editingMessage.value.content
})
}, 100)
}
}
const closeEditPopup = () => {
showEditPopup.value = false;
editingMessage.value = null;
}
/**
* 日期选择器回调 - 更新日期部分并同步 editingTime
*/
const onDateChange = (e) => {
editingDate.value = e.detail.value
editingTime.value = `${editingDate.value} ${editingTimeOfDay.value}`.trim()
}
/**
* 时刻选择器回调 - 更新时刻部分并同步 editingTime
*/
const onTimeOfDayChange = (e) => {
editingTimeOfDay.value = e.detail.value
editingTime.value = `${editingDate.value} ${editingTimeOfDay.value}`.trim()
}
/**
* 切换sim卡
* @param value
*/
const onSimKaChange = (value) => {
editingSimKa.value = value
}
const confirmEdit = () => {
if (editingMessage.value && editorCtx) {
editorCtx.getContents({
success: (res) => {
const index = messageList.value.findIndex(item => item.id === editingMessage.value.id)
if (index > -1) {
messageList.value[index].content = res.html;
messageList.value[index].time = editingTime.value;
// timeMode: 'auto'|'show'|'hide'
if (editingTimeMode.value === 'auto') {
delete messageList.value[index].timeMode;
} else {
messageList.value[index].timeMode = editingTimeMode.value;
}
// hideTime
delete messageList.value[index].hideTime;
if (editingSimKa.value) {
messageList.value[index].simIndex = Number(editingSimKa.value);
} else {
delete messageList.value[index].simIndex;
}
}
closeEditPopup();
saveChatList();
}
})
} else {
closeEditPopup();
}
}
/**
* 消息互换
*/
const handleSwap = () => {
const index = messageList.value.findIndex(item => item.id === selectedMessage.value.id)
messageList.value[index].isMe = !messageList.value[index].isMe
closeActionPopup();
saveChatList()
}
/**
* 进入拖动排序模式
*/
const handleSort = () => {
isSortMode.value = true
sortingListCache = messageList.value.map(item => ({ ...item }))
closeActionPopup()
}
/**
* ChatList 拖拽排序后回调 - 实时更新中间缓存
*/
const onSortChange = (newList) => {
sortingListCache = newList
}
/**
* 取消排序还原 messageList
*/
const cancelSort = () => {
isSortMode.value = false
// messageList
}
/**
* 确认排序将拖拽结果写回 messageList
*/
const confirmSort = () => {
if (sortingListCache.length > 0) {
messageList.value = sortingListCache.map(item => ({ ...item }))
}
isSortMode.value = false
saveChatList()
}
const handleChangeSpeaker = () => {
isMe.value = !isMe.value
if (isMe.value) {
uni.showToast({
title: "现在是自己发言",
icon: "none"
})
} else {
uni.showToast({
title: "现在是对方发言",
icon: "none"
})
}
closeActionPopup();
}
/**
* 删除消息
*/
const handleDelete = () => {
const index = messageList.value.findIndex(item => item.id === selectedMessage.value.id)
messageList.value.splice(index, 1)
closeActionPopup();
saveChatList()
}
const data = reactive({
phone: "iphone",
data: {},
number: 0
})
onLoad((options) => {
console.log(options)
if (options.phone) {
data.phone = options.phone
}
if (options.id) {
currentId = options.id
//
try {
const cached = uni.getStorageSync(STORAGE_KEY)
let list = cached ? JSON.parse(cached) : defaultList
const found = list.find(item => item.id == options.id)
if (found) {
data.data = found
let number = 0
list.forEach(item => {
if (item.id == options.id) {
item.unRead = false
item.unReadNumber = 1
}
console.log(number + item.unRead ? item.unReadNumber : 0)
number = number + Number(item.unRead ? item.unReadNumber : 0)
})
data.number = number
console.log(data.data)
uni.setStorageSync(STORAGE_KEY, JSON.stringify(list))
messageList.value = found.chatList || []
return
}
} catch (e) {
//
}
//
data.data = defaultList.find(item => item.id == options.id)
messageList.value = data.data?.chatList || []
}
})
onShow(() => {
// #ifdef APP-PLUS
if (data.phone == 'oppo') {
util.setAndroidSystemBarColor('#FAFAFA')
} else {
util.setAndroidSystemBarColor('#ffffff')
}
setTimeout(() => {
plus.navigator.setStatusBarStyle("dark");
}, 500)
// #endif
})
const handleSend = (params) => {
console.log(params)
params.id = stringUtil.uuid()
params.isMe = isMe.value
messageList.value.push(params)
saveChatList()
}
/**
* 双击左侧
*/
const onDblclickLeft = () => {
isMe.value = false
uni.showToast({
title: "现在是对方发言",
icon: "none"
})
}
/**
* 双击右侧
*/
const onDblclickRight = () => {
isMe.value = true
uni.showToast({
title: "现在是自己发言",
icon: "none"
})
}
</script>
<style lang="less" scoped>
.action-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 999;
}
/* 拖动排序弹窗样式 */
.sort-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1100;
display: flex;
justify-content: center;
align-items: center;
}
.sort-popup {
width: 680rpx;
max-height: 80vh;
background-color: #FFFFFF;
border-radius: 24rpx;
overflow: hidden;
display: flex;
flex-direction: column;
}
/* 排序模式底部工具条 - 覆盖在输入框上,高度与输入框一致 */
.sort-toolbar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 1200;
background-color: rgba(255, 255, 255, 0.97);
display: flex;
flex-direction: row;
align-items: center;
padding: 16rpx 32rpx;
box-shadow: 0 -1rpx 0 rgba(0, 0, 0, 0.1);
}
.sort-toolbar-tip {
flex: 1;
font-size: 24rpx;
color: #999999;
text-align: left;
}
.sort-toolbar-actions {
display: flex;
flex-direction: row;
gap: 20rpx;
}
.sort-toolbar-btn {
height: 72rpx;
line-height: 72rpx;
padding: 0 44rpx;
text-align: center;
font-size: 30rpx;
border-radius: 36rpx;
}
.sort-toolbar-btn.cancel {
background-color: #F0F0F0;
color: #666666;
}
.sort-toolbar-btn.confirm {
background-color: #007AFF;
color: #FFFFFF;
font-weight: bold;
}
.action-popup {
position: fixed;
background-color: #4C4C4C;
border-radius: 12rpx;
padding: 20rpx 30rpx;
display: flex;
justify-content: space-between;
transform: translateX(-50%);
width: 540rpx;
z-index: 10;
}
.action-popup .triangle {
position: absolute;
top: -12rpx;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 12rpx solid transparent;
border-right: 12rpx solid transparent;
border-bottom: 14rpx solid #4C4C4C;
}
.action-item {
display: flex;
flex-direction: column;
align-items: center;
color: #FFFFFF;
font-size: 20rpx;
margin: 0 20rpx;
text {
white-space: nowrap;
}
}
.action-icon {
width: 36rpx;
height: 36rpx;
margin-bottom: 8rpx;
}
/* 编辑弹窗样式 */
.edit-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 99;
display: flex;
justify-content: center;
align-items: center;
}
.edit-popup {
width: 600rpx;
background-color: #FFFFFF;
border-radius: 16rpx;
overflow: hidden;
display: flex;
flex-direction: column;
}
.edit-header {
font-size: 32rpx;
font-weight: bold;
color: #333;
text-align: center;
padding: 30rpx 0;
}
.edit-body {
padding: 30rpx;
}
.edit-textarea {
width: 100%;
height: 200rpx;
background-color: #F8F8F8;
padding: 20rpx;
border-radius: 8rpx;
font-size: 28rpx;
color: #333;
box-sizing: border-box;
}
::v-deep .ql-container {
min-height: 100px;
}
.edit-footer {
display: flex;
}
.edit-btn {
flex: 1;
height: 90rpx;
line-height: 90rpx;
text-align: center;
font-size: 32rpx;
border-radius: 12rpx;
margin: 32rpx 16rpx;
margin-top: 0;
}
.edit-btn.cancel {
color: #767676;
background-color: #F1F1F1;
margin-left: 30rpx;
}
.edit-btn.confirm {
color: #FFFFFF;
font-weight: bold;
background-color: #1777FF;
margin-right: 30rpx;
}
.edit-row {
display: flex;
align-items: center;
margin-bottom: 20rpx;
}
.edit-label {
font-size: 28rpx;
color: #333;
width: 150rpx;
flex-shrink: 0;
}
.edit-input {
flex: 1;
height: 70rpx;
background-color: #F8F8F8;
border-radius: 8rpx;
padding: 0 20rpx;
font-size: 28rpx;
color: #333;
}
.time-mode-group {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.time-mode-btn {
padding: 4rpx 12rpx;
border-radius: 32rpx;
font-size: 26rpx;
color: #666666;
margin: 0 6rpx;
background-color: #F0F0F0;
border: 2rpx solid transparent;
}
.time-mode-btn.active {
color: #007AFF;
background-color: #E8F2FF;
border-color: #007AFF;
}
/* 时间选择器组合样式 */
.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;
}
</style>

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,741 @@
<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')">
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
</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>
<!-- 添加短信弹窗 -->
<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 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="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 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
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) { }
})
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, reject) => {
//
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)
}
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 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) { }
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) { }
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) { }
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;
}
}
</style>

View File

@ -1,8 +1,9 @@
<template>
<!-- 水印 -->
<view v-if="$isVip()">
<watermark dark="light" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark')">
<watermark dark="light" source="uni_alipay_other_airTickets_ctrip" />
<liu-drag-button :canDocking="false"
@clickBtn="$goRechargePage('watermark', 'uni_alipay_other_airTickets_ctrip')">
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
</liu-drag-button>
</view>
@ -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>
@ -366,7 +367,7 @@
<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';

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,8 +1,9 @@
<template>
<!-- 水印 -->
<view v-if="$isVip()">
<watermark dark="light" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark')">
<watermark dark="light" source="uni_alipay_other_airTickets_fliggy" />
<liu-drag-button :canDocking="false"
@clickBtn="$goRechargePage('watermark', 'uni_alipay_other_airTickets_fliggy')">
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
</liu-drag-button>
</view>
@ -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>
@ -298,11 +299,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 +372,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) {

View File

@ -1,8 +1,9 @@
<template>
<!-- 水印 -->
<view v-if="$isVip()">
<watermark dark="light" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark')">
<watermark dark="light" source="uni_alipay_other_airTickets_qunar" />
<liu-drag-button :canDocking="false"
@clickBtn="$goRechargePage('watermark', 'uni_alipay_other_airTickets_qunar')">
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
</liu-drag-button>
</view>
@ -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>
@ -375,7 +376,7 @@ const data = reactive({
ticketData: JSON.parse(JSON.stringify(defualtData))
})
let { navBar, ticketData } = toRefs(data);
let { ticketData } = toRefs(data);
//
const ofCheckInTime = computed(() => {

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

@ -0,0 +1,654 @@
<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: 10px;margin: 10px;padding: 2px 5px;">
<image src="/static/image/other/notice.png" style="width: 16px;height: 16px;margin-right: 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">
<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;background-image: url('/static/image/other/card/cardBGImg.png');background-size:6660px 406px;`">
<!-- 头部年月 -->
<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>
<l-painter-view :css="`position: absolute;left:122px;top:152px;`">
<l-painter-text v-for="(item,index) in data.form.year.toString()" :css="data.textCss3+data.textCssLeft"
:text="item" :key="index" />
</l-painter-view>
<l-painter-view :css="`position: absolute;left:192px;top:152px;`">
<l-painter-text :css="data.textCss2+data.textCssCenter" :text="data.form.month.toString()" />
</l-painter-view>
<l-painter-view :css="`position: absolute;left:254px;top:152px;`">
<l-painter-text :css="data.textCss2+data.textCssCenter" :text="data.form.day.toString()" />
</l-painter-view>
<l-painter-view :css="`position: absolute;left:122px;top:200px;width:274px;`">
<l-painter-text v-for="(item,index) in data.form.address" :css="data.textCss3+data.textCssLeft" :text="item"
:key="index" />
</l-painter-view>
<l-painter-view :css="`position: absolute;left:223px;top:322px;`">
<l-painter-text v-for="(item,index) in data.form.idNumber" :css="data.textCss4+data.textCssLeft" :text="item"
:key="index" />
</l-painter-view>
<l-painter-view :css="`position: absolute;right:48px;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>
</template>
<script setup>
//
import ZdyNavbar from "@/components/nav-bar/nav-bar.vue"
import {
ref,
reactive,
watch,
nextTick,
getCurrentInstance
} from "vue";
import {
onLoad,
onShow,
onUnload,
onReady,
onPullDownRefresh,
onReachBottom
} from "@dcloudio/uni-app";
const {
appContext,
proxy
} = getCurrentInstance();
const data = reactive({
shuaxing:true,
navbar: {
title: "身份证",
bgColor: '#EDEDED',
},
width: 800,
height: 600,
code: '',
form: {
name: '某某',
gender: '男',
nation: '汉',
year: 2000,
month: 1,
day: 1,
address: '翻斗大街翻斗花园二号楼1001室',
idNumber: '110000200001010000',
photo: '',
},
textCss: 'word-spacing: 20px;font-family: "SimHei", "Microsoft YaHei", sans-serif;width:100px;color:#3D3D3D;font-size:24px; mix-blend-mode: overlay;',
textCss2: 'word-spacing: 20px;font-family: "SimHei", "Microsoft YaHei", sans-serif;width:100px;text-align: left;color:#3D3D3D;font-size:22px;mix-blend-mode: overlay;',
textCss3: 'word-spacing: 20px;letter-spacing: 10px;margin-right:2px;font-family: "SimHei", "Microsoft YaHei", sans-serif;text-align: left;color:#3D3D3D;font-size:22px;mix-blend-mode: overlay;',
textCss4: 'word-spacing: 20px;margin-right:3px;letter-spacing: 10px;font-family: "card", "Microsoft YaHei", sans-serif;text-align: left;color:#1a1a1a;font-size:28px;mix-blend-mode: overlay;display:flex;',
textCssLeft: 'text-align: left;',
textCssCenter: 'text-align: center;'
})
//
const showEditPopup = ref(false);
const editForm = ref({});
onLoad((option) => {
uni.showLoading({
title: "生成中"
})
//
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>} 返回一个 Promiseresolve File 对象
*/
function convertLocalImageToFile(localPath) {
return new Promise((resolve, reject) => {
// 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);
});
}
//
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" scoped>
@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: 40rpx;
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,110 @@
<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
}
})
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>} 返回一个 Promiseresolve File 对象
*/
function convertLocalImageToFile(localPath) {
return new Promise((resolve, reject) => {
// 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);
});
}
</script>

View File

@ -0,0 +1,784 @@
<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">
<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>
</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

@ -8,7 +8,7 @@
<view class="button-container">
<button class="btn-save-image" @click="saveImage">保存图片</button>
</view>
<l-painter isCanvasToTempFilePath @success="data.code = $event" hidden
<l-painter isCanvasToTempFilePath @success="successImage" @progress="progress" hidden
:css="`width:${data.width}px;height:${data.width / 4 * 3}px;`">
<l-painter-view
:css="`position: relative;width:${data.width}px;height:${data.width / 4 * 3}px;background-image: url('/static/image/other/gzd.png');`">
@ -228,7 +228,21 @@ const data = reactive({
//
const showEditPopup = ref(false);
const editForm = ref({});
//
function successImage(e){
data.code=e
}
function progress(e){
if(e<0.03){
uni.showLoading({
title:"生成中"
})
}
if(e==1){
uni.hideLoading()
}
console.log(e)
}
//
function edit() {
console.log(data.form)

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,8 +1,8 @@
<template>
<!-- 水印 -->
<view v-if="$isVip()">
<watermark dark="light" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark')">
<watermark dark="light" source="uni_alipay_other_tickets_12306" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark', 'uni_alipay_other_tickets_12306')">
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
</liu-drag-button>
</view>
@ -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>
@ -201,8 +201,7 @@ 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(() => {
@ -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,26 @@ const hotelDateRange = computed({
})
onMounted(() => {
const stored = uni.getStorageSync('ticketsInfo')
const stored = uni.getStorageSync(data.STORAGE_KEY)
if (stored) {
Object.assign(data.ticketsInfo, stored)
}
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 +377,7 @@ const handleRightButtonClick = () => {
}
}
uni.setStorageSync('ticketsInfo', data.ticketsInfo)
uni.setStorageSync(data.STORAGE_KEY, data.ticketsInfo)
uni.navigateBack()
}
@ -327,7 +399,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 +420,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();
}
/**
@ -432,8 +507,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 +549,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 +837,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,678 @@
<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')">
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
</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>
</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';
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;
}
</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: 50rpx;
border: solid 1rpx #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>

View File

@ -1,8 +1,8 @@
<template>
<template>
<!-- 水印 -->
<view v-if="$isVip()">
<watermark dark="light" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark')">
<watermark dark="light" source="uni_alipay_other_groupChat" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark', 'uni_alipay_other_groupChat')">
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
</liu-drag-button>
</view>
@ -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"></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();
@ -681,7 +684,7 @@ const removeFile = (filePath) => {
// 使 uni.removeSavedFile ( uni.saveFile )
uni.removeSavedFile({
filePath: filePath,
success: (res) => {
success: () => {
console.log('✅ uni.removeSavedFile 删除成功:', filePath)
},
fail: (err) => {
@ -718,7 +721,7 @@ const handleTouchStart = (event, index) => {
}
// -
const handleTouchMove = (event, index) => {
const handleTouchMove = (event) => {
if (!data.dragState.isDragging) return
const touch = event.touches[0]
@ -731,7 +734,7 @@ const handleTouchMove = (event, index) => {
}
// -
const handleTouchEnd = (event, index) => {
const handleTouchEnd = () => {
//
if (data.dragState.longPressTimer) {
clearTimeout(data.dragState.longPressTimer)

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

@ -0,0 +1,174 @@
<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"
url: ""
}, {
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: ""
}, {
name: "拼多多",
icon: "pinduoduo",
bgColor: ["#FFE0DF", "#FFFFFF"],
btnBg: "#FF4848",
shadowColor: "#FF4848",
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

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

View File

@ -0,0 +1,18 @@
<template>
<view class="container">
<view v-for="item in mockOrderList" :key="item.id">
{{ item.name }}
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
/**
* 模拟订单列表
*/
const mockOrderList = ref([]);
</script>
<style lang="less"></style>

BIN
static/font/card.ttf Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 583 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 949 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1023 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 839 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 714 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 B

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