Compare commits
74 Commits
feat/redes
...
5de611d28f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5de611d28f | ||
|
|
24b2293d71 | ||
|
|
fbf5371086 | ||
|
|
f86281017a | ||
|
|
f51c8d912b | ||
|
|
bcc63f508a | ||
|
|
fad37f511c | ||
|
|
a9a1a557d9 | ||
|
|
383e7b819b | ||
|
|
8f4ecda7eb | ||
|
|
71bb404fe1 | ||
|
|
ef77905dbb | ||
|
|
69923b81b0 | ||
|
|
791e034a6b | ||
|
|
42f4d9f96a | ||
|
|
1d6c12e8d7 | ||
|
|
8dc8600b52 | ||
|
|
94a31b2e47 | ||
|
|
8a8393eede | ||
|
|
882ec0a1b8 | ||
|
|
093c337bc4 | ||
|
|
e2007b008a | ||
|
|
372e22f4b7 | ||
|
|
0ada36a778 | ||
|
|
547d944812 | ||
|
|
bdaada8224 | ||
|
|
b3fea2100b | ||
|
|
1e03191e82 | ||
|
|
25fc534619 | ||
|
|
5dc948efec | ||
|
|
fc7305dffe | ||
|
|
35fa5fd2ae | ||
|
|
898aa41fd2 | ||
|
|
22538a8972 | ||
|
|
379ac883f0 | ||
|
|
2d27f55d69 | ||
|
|
62751369a2 | ||
|
|
10f8a1658e | ||
|
|
bbb840e521 | ||
|
|
404daa4b6d | ||
|
|
40b8f76eef | ||
|
|
5f4a26e693 | ||
|
|
f72b77fe0d | ||
|
|
39773341bd | ||
|
|
16a47c1229 | ||
|
|
715e4f6f7d | ||
|
|
b3c925b0ca | ||
|
|
263515758c | ||
|
|
f4ac65acce | ||
|
|
f5f993d1e2 | ||
|
|
63af194ec5 | ||
|
|
6d327c41cd | ||
|
|
bd0bb149b8 | ||
|
|
102e6c0867 | ||
|
|
b56f68636c | ||
|
|
e0db2b6df0 | ||
|
|
25e9946c17 | ||
|
|
64f479c62a | ||
|
|
c1172f7591 | ||
|
|
04ee3f2c87 | ||
|
|
4cb1e65e6e | ||
|
|
89aafa7317 | ||
|
|
e2d3d8172f | ||
|
|
7c35a0bad6 | ||
|
|
bd0fc9cfdb | ||
|
|
cca7131d68 | ||
|
|
0337d86ba2 | ||
|
|
89a6441f62 | ||
|
|
58d4b92287 | ||
|
|
733fcc5511 | ||
|
|
c06d543af3 | ||
|
|
6b066e35da | ||
|
|
41349ac833 | ||
|
|
c6246c023d |
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(git add:*)"
|
||||
"Bash(git add:*)",
|
||||
"Bash(npm list:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
|
||||
15
.dockerignore
Normal file
15
.dockerignore
Normal file
@@ -0,0 +1,15 @@
|
||||
node_modules
|
||||
.git
|
||||
.gitignore
|
||||
*.md
|
||||
.vscode
|
||||
.idea
|
||||
dist
|
||||
unpackage
|
||||
.DS_Store
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.env.local
|
||||
.env.*.local
|
||||
13
App.vue
13
App.vue
@@ -5,8 +5,6 @@
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "uview-plus/index.scss";
|
||||
|
||||
:root {
|
||||
/* 主色 */
|
||||
--primary: #0A84FF;
|
||||
@@ -41,7 +39,7 @@
|
||||
|
||||
/* 背景色 */
|
||||
--bg-primary: #FFFFFF;
|
||||
--bg-secondary: #F2F2F7;
|
||||
--bg-secondary: #F8F8F8;
|
||||
--bg-tertiary: #FFFFFF;
|
||||
--bg-grouped: #F2F2F7;
|
||||
|
||||
@@ -95,7 +93,8 @@
|
||||
|
||||
page {
|
||||
color: var(--text-primary);
|
||||
background-color: var(--bg-secondary);
|
||||
background: var(--bg-secondary);
|
||||
// min-height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
@@ -105,6 +104,12 @@
|
||||
padding: 30rpx 20rpx;
|
||||
max-width: 750rpx;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.pd-20{
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.card {
|
||||
|
||||
11
Dockerfile
11
Dockerfile
@@ -18,9 +18,20 @@ COPY package.json ./
|
||||
# 安装所有依赖
|
||||
RUN npm install --legacy-peer-deps
|
||||
|
||||
# 安装 git (用于获取 commit 信息)
|
||||
RUN apk add --no-cache git
|
||||
|
||||
# 复制源代码
|
||||
COPY . .
|
||||
|
||||
# 列出文件确认复制成功
|
||||
RUN ls -la
|
||||
|
||||
# 创建 src 目录软链接 (兼容 uni-app 新版本)
|
||||
RUN mkdir -p src && \
|
||||
ln -sf /build/manifest.json /build/src/manifest.json && \
|
||||
ln -sf /build/pages.json /build/src/pages.json
|
||||
|
||||
# 构建 H5 生产版本
|
||||
RUN npm run build:h5
|
||||
|
||||
|
||||
@@ -4,4 +4,5 @@ export { deviceApi } from './modules/device.js';
|
||||
export { exchangeApi } from './modules/exchange.js';
|
||||
export { orderApi } from './modules/order.js';
|
||||
export { realnameApi } from './modules/realname.js';
|
||||
export { walletApi } from './modules/wallet.js';
|
||||
export { walletApi } from './modules/wallet.js';
|
||||
export { wechatApi } from './modules/wechat.js';
|
||||
11
api/modules/wechat.js
Normal file
11
api/modules/wechat.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import request from '@/utils/request.js';
|
||||
|
||||
export const wechatApi = {
|
||||
getJssdkConfig(url) {
|
||||
return request({
|
||||
url: '/api/c/v1/wechat/jssdk-config',
|
||||
method: 'GET',
|
||||
data: { url }
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -2,7 +2,7 @@
|
||||
<view class="card device-status-card">
|
||||
<view class="card-header mb-md flex-row-sb">
|
||||
<view class="title">设备信息</view>
|
||||
<view class="tag-apple tag-success">{{ getOperator(deviceInfo.currentIccid) }}</view>
|
||||
<view class="tag-apple tag-success">{{ deviceInfo.carrier_name || '-' }}</view>
|
||||
</view>
|
||||
<view class="device-info flex-col-g16">
|
||||
<view class="info-row flex-row-sb">
|
||||
@@ -17,7 +17,8 @@
|
||||
</view>
|
||||
<view class="info-row flex-row-sb">
|
||||
<view class="info-label">
|
||||
<view class="subtitle">到期时间:{{ deviceInfo.expireDate }}</view>
|
||||
<view class="subtitle" v-if="isDevice">IMEI:{{ deviceInfo.imei || '-' }}</view>
|
||||
<view class="subtitle" v-else>手机号:{{ deviceInfo.bound_phone || '-' }}</view>
|
||||
</view>
|
||||
<view class="info-values flex-row-g20">
|
||||
<view class="tag-apple tag-primary">{{ statusText }}</view>
|
||||
@@ -47,15 +48,12 @@
|
||||
const props = defineProps({
|
||||
deviceInfo: { type: Object, default: () => ({}) },
|
||||
isRealName: { type: Boolean, default: false },
|
||||
isDevice: { type: Boolean, default: true },
|
||||
opratorList: { type: Array, default: () => [] }
|
||||
});
|
||||
|
||||
defineEmits(['authentication']);
|
||||
|
||||
const getOperator = (iccid) => {
|
||||
return '中国电信';
|
||||
};
|
||||
|
||||
const getSignalText = (rssi) => rssi || '强';
|
||||
|
||||
const statusText = computed(() => {
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
<view class="function-item interactive card-interactive" @tap="$emit('enter', 'asset-package')"
|
||||
role="button" tabindex="0">
|
||||
<view class="function-icon">
|
||||
<image src="/static/shop.png" mode="aspectFit" alt="资产套餐历史"></image>
|
||||
<image src="/static/asset-package-history.png" mode="aspectFit" alt="资产套餐历史"></image>
|
||||
</view>
|
||||
<view class="function-name">资产套餐历史</view>
|
||||
</view>
|
||||
@@ -61,8 +61,8 @@
|
||||
<view class="function-icon">
|
||||
<image src="/static/bind-phone.png" mode="aspectFit" alt="绑定手机号"></image>
|
||||
</view>
|
||||
<view :class="['function-name', alreadyBindPhone ? 'text-primary' : '']">
|
||||
{{ alreadyBindPhone ? '已绑定' : '绑定手机号' }}
|
||||
<view :class="['function-name', alreadyBindPhone ? 'text-primary' : 'text-danger']">
|
||||
{{ alreadyBindPhone ? '已绑定' : '未绑定' }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="function-item interactive card-interactive" @tap="$emit('enter', 'change-phone')" role="button"
|
||||
|
||||
@@ -108,17 +108,13 @@
|
||||
// 只有一个生效中的套餐
|
||||
const activePackage = data.items[0];
|
||||
|
||||
if (activePackage.enable_virtual_data) {
|
||||
// 启用虚流量
|
||||
if (activePackage.enable_virtual_data) {
|
||||
usedMB.value = activePackage.virtual_used_mb || 0;
|
||||
totalMB.value = activePackage.virtual_limit_mb || 0;
|
||||
remainMB.value = activePackage.virtual_remain_mb || 0;
|
||||
} else {
|
||||
// 未启用虚流量,使用真流量
|
||||
usedMB.value = activePackage.data_usage_mb || 0;
|
||||
totalMB.value = activePackage.data_limit_mb || 0;
|
||||
remainMB.value = totalMB.value - usedMB.value;
|
||||
}
|
||||
totalMB.value = activePackage.data_limit_mb || 0;
|
||||
remainMB.value = totalMB.value - usedMB.value;
|
||||
|
||||
// 将套餐信息(包括到期时间)传递给父组件
|
||||
emit('packageLoaded', activePackage);
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
<view class="flex-row-g20">
|
||||
<view class="title">{{ currentCardNo || '-' }}</view>
|
||||
</view>
|
||||
<view class="caption" v-if="isDevice">IMEI:{{ deviceInfo.imei || '-' }}</view>
|
||||
<view class="caption" v-else>手机号:{{ deviceInfo.bound_phone || '-' }}</view>
|
||||
<view class="caption">套餐到期时间:{{ deviceInfo.expireDate || '-' }}</view>
|
||||
</view>
|
||||
<view v-if="isDevice" class="tag-apple" :class="onlineStatus === '在线' ? 'tag-success' : 'tag-warning'">
|
||||
{{ onlineStatus }}
|
||||
|
||||
@@ -44,4 +44,8 @@
|
||||
.wifi-details { flex: 1; }
|
||||
}
|
||||
}
|
||||
|
||||
.btn-group{
|
||||
padding-right: var(--space-md);
|
||||
}
|
||||
</style>
|
||||
|
||||
187
docs/微信扫一扫配置文档.md
Normal file
187
docs/微信扫一扫配置文档.md
Normal file
@@ -0,0 +1,187 @@
|
||||
# uniapp H5 调用微信扫一扫配置文档
|
||||
|
||||
## 一、微信 JSSDK 加载
|
||||
|
||||
**方式:通过 CDN 加载微信官方 JSSDK**
|
||||
|
||||
在 `index.html` 的 `<head>` 中添加:
|
||||
|
||||
```html
|
||||
<script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
|
||||
```
|
||||
|
||||
> **注意:** 不要使用 `import wx from 'jweixin-module'` 或 npm 包方式导入,uni-app 会错误解析导致构建失败。
|
||||
|
||||
---
|
||||
|
||||
## 二、wxsdk.js 封装
|
||||
|
||||
创建 `utils/wxsdk.js`:
|
||||
|
||||
```javascript
|
||||
// utils/wxsdk.js
|
||||
import { wechatApi } from '@/api/index.js'
|
||||
|
||||
// 注意:是 jWeixin,不是 wx
|
||||
// window.wx 会被 uni-app 框架占用为 Vue 模块
|
||||
const wx = window.jWeixin
|
||||
|
||||
/**
|
||||
* 初始化微信 JS-SDK
|
||||
*/
|
||||
export async function initWxConfig() {
|
||||
const url = window.location.href.split('#')[0]
|
||||
try {
|
||||
const data = await wechatApi.getJssdkConfig(url)
|
||||
const { app_id, timestamp, nonce_str, signature } = data
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.config({
|
||||
debug: true,
|
||||
appId: app_id,
|
||||
timestamp: timestamp,
|
||||
nonceStr: nonce_str,
|
||||
signature: signature,
|
||||
jsApiList: ['scanQRCode']
|
||||
})
|
||||
wx.ready(() => {
|
||||
console.log('微信 SDK 就绪')
|
||||
resolve()
|
||||
})
|
||||
wx.error((err) => {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
} catch (e) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调起微信扫一扫
|
||||
* @returns {Promise<string>} 扫码结果字符串
|
||||
*/
|
||||
export function wxScan() {
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.scanQRCode({
|
||||
needResult: 1,
|
||||
scanType: ['qrCode', 'barCode'],
|
||||
success(res) {
|
||||
resolve(res.resultStr)
|
||||
},
|
||||
fail(err) {
|
||||
reject(new Error(err?.errMsg || '扫码失败'))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否在微信环境中
|
||||
*/
|
||||
export function isInWechat() {
|
||||
return /micromessenger/i.test(navigator.userAgent)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、后端接口要求
|
||||
|
||||
后端需提供 `/api/c/v1/wechat/jssdk-config` 接口,返回格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"app_id": "wx1234567890",
|
||||
"timestamp": 1234567890,
|
||||
"nonce_str": "randomstring",
|
||||
"signature": "xxxxx"
|
||||
}
|
||||
```
|
||||
|
||||
> **注意:** `nonce_str` 必须与生成 signature 时使用的一致
|
||||
|
||||
---
|
||||
|
||||
## 四、前端调用示例
|
||||
|
||||
```javascript
|
||||
import { initWxConfig, wxScan, isInWechat } from '@/utils/wxsdk.js'
|
||||
|
||||
async function handleScan() {
|
||||
if (!isInWechat()) {
|
||||
uni.showToast({ title: '请在微信环境中使用', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. 初始化微信 SDK 配置
|
||||
await initWxConfig()
|
||||
|
||||
// 2. 调起扫一扫
|
||||
const result = await wxScan()
|
||||
console.log('扫码结果:', result)
|
||||
// 在此处理扫码结果
|
||||
} catch (err) {
|
||||
console.error('扫码错误:', err)
|
||||
uni.showToast({ title: err.message || JSON.stringify(err), icon: 'none' })
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、注意事项
|
||||
|
||||
| 问题 | 原因 | 解决方案 |
|
||||
|------|------|----------|
|
||||
| `window.wx` 输出 `Module {$emit...}` | `window.wx` 被 uni-app 框架占用为 Vue 事件模块 | 使用 `window.jWeixin` |
|
||||
| `jweixin-module` 打包报 `"default" is not exported` | uni 构建系统错误解析无 default export 的 UMD 模块 | 使用 CDN 加载,不走 npm |
|
||||
| `wx.scanQRCode is not a function` | 微信 SDK 未正确加载 | 确认 `window.jWeixin` 有值 |
|
||||
| 扫码提示 `{}` | `catch` 捕获空错误对象 | 需将错误信息转换为 `Error` 对象返回 |
|
||||
|
||||
---
|
||||
|
||||
## 六、调试建议
|
||||
|
||||
在微信开发者工具中打开控制台,检查以下变量:
|
||||
|
||||
```javascript
|
||||
// 错误情况:被 uni-app 框架占用为 Vue 模块
|
||||
window.wx
|
||||
// 输出:Module {$emit: ƒ, $off: ƒ, $on: ƒ, $once: ƒ, ...}
|
||||
|
||||
// 正确情况:微信 JSSDK
|
||||
window.jWeixin
|
||||
// 输出:{config: ƒ, ready: ƒ, error: ƒ, checkJsApi: ƒ, ...}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 七、完整文件路径
|
||||
|
||||
```
|
||||
project/
|
||||
├── index.html # 引入微信 JSSDK CDN
|
||||
├── utils/
|
||||
│ └── wxsdk.js # 微信扫一扫封装
|
||||
└── api/
|
||||
└── modules/
|
||||
└── wechat.js # 后端接口封装
|
||||
```
|
||||
|
||||
`api/modules/wechat.js` 示例:
|
||||
|
||||
```javascript
|
||||
import request from '@/utils/request.js'
|
||||
|
||||
export const wechatApi = {
|
||||
getJssdkConfig(url) {
|
||||
return request({
|
||||
url: '/api/c/v1/wechat/jssdk-config',
|
||||
method: 'GET',
|
||||
data: { url }
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -5,6 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" />
|
||||
<title></title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/misans@4.1.0/lib/Normal/MiSans-Regular.min.css"/>
|
||||
<script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"><!--app-html--></div>
|
||||
|
||||
15
main.js
15
main.js
@@ -16,7 +16,20 @@ app.$mount()
|
||||
import { createSSRApp } from 'vue'
|
||||
export function createApp() {
|
||||
const app = createSSRApp(App)
|
||||
app.use(uviewPlus)
|
||||
// 使用 uview-plus (版本 3.4.0+)
|
||||
app.use(uviewPlus, () => {
|
||||
return {
|
||||
options: {
|
||||
config: {
|
||||
// 默认单位 rpx
|
||||
unit: 'rpx'
|
||||
},
|
||||
props: {
|
||||
// 可以在这里配置组件默认属性
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
return {
|
||||
app
|
||||
}
|
||||
|
||||
@@ -17,7 +17,10 @@
|
||||
"delay" : 0
|
||||
},
|
||||
/* 模块配置 */
|
||||
"modules" : {},
|
||||
"modules" : {
|
||||
"Barcode" : {},
|
||||
"Camera" : {}
|
||||
},
|
||||
/* 应用发布信息 */
|
||||
"distribute" : {
|
||||
/* android打包配置 */
|
||||
@@ -97,6 +100,8 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sdkConfigs" : {}
|
||||
"sdkConfigs" : {
|
||||
"maps" : {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
7072
package-lock.json
generated
7072
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,14 @@
|
||||
{
|
||||
"scripts": {
|
||||
"dev:h5": "uni",
|
||||
"build:h5": "UNI_INPUT_DIR=. uni build -p h5"
|
||||
"build:h5": "cross-env UNI_INPUT_DIR=. uni build -p h5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dcloudio/types": "3.4.19",
|
||||
"@dcloudio/uni-cli-shared": "3.0.0-4080720251210001",
|
||||
"@dcloudio/uni-stacktracey": "3.0.0-4080720251210001",
|
||||
"@dcloudio/vite-plugin-uni": "3.0.0-4080720251210001",
|
||||
"cross-env": "^10.1.0",
|
||||
"less": "^4.0.0",
|
||||
"sass": "^1.63.2",
|
||||
"vite": "5.2.8"
|
||||
@@ -16,9 +17,10 @@
|
||||
"@dcloudio/uni-app": "3.0.0-4080720251210001",
|
||||
"@dcloudio/uni-components": "3.0.0-4080720251210001",
|
||||
"@dcloudio/uni-h5": "3.0.0-4080720251210001",
|
||||
"vue": "^3.4.0",
|
||||
"clipboard": "^2.0.11",
|
||||
"dayjs": "^1.11.19",
|
||||
"uview-plus": "^3.6.29"
|
||||
"jweixin-module": "^1.6.0",
|
||||
"uview-plus": "^3.6.29",
|
||||
"vue": "^3.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,8 +84,8 @@
|
||||
],
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarTitleText": "信息查询",
|
||||
"navigationBarBackgroundColor": "#F8F8F8",
|
||||
"navigationBarTitleText": "设备卡信息查询",
|
||||
"navigationBarBackgroundColor": "#FFFFFF",
|
||||
"backgroundColor": "#F8F8F8"
|
||||
},
|
||||
"uniIdRouter": {}
|
||||
|
||||
@@ -15,30 +15,30 @@
|
||||
</view>
|
||||
|
||||
<view class="package-info">
|
||||
<view class="info-row flex-row-sb">
|
||||
<!-- <view class="info-row flex-row-sb">
|
||||
<view class="info-label">套餐类型</view>
|
||||
<view class="info-value">{{ item.package_type === 'formal' ? '普通套餐' : '加油包' }}</view>
|
||||
</view>
|
||||
<view class="info-row flex-row-sb">
|
||||
<view class="info-label">使用类型</view>
|
||||
<view class="info-value">{{ item.usage_type === 'single_card' ? '单卡' : '设备' }}</view>
|
||||
</view>
|
||||
</view> -->
|
||||
<view class="info-row flex-row-sb">
|
||||
<view class="info-label">激活时间</view>
|
||||
<view class="info-value">{{ item.activated_at || '-' }}</view>
|
||||
</view>
|
||||
<view class="info-row flex-row-sb">
|
||||
<!-- <view class="info-row flex-row-sb">
|
||||
<view class="info-label">创建时间</view>
|
||||
<view class="info-value">{{ item.created_at }}</view>
|
||||
</view>
|
||||
</view> -->
|
||||
<view class="info-row flex-row-sb">
|
||||
<view class="info-label">到期时间</view>
|
||||
<view class="info-value">{{ item.expires_at || '-' }}</view>
|
||||
</view>
|
||||
<view class="info-row flex-row-sb">
|
||||
<!-- <view class="info-row flex-row-sb">
|
||||
<view class="info-label">优先级</view>
|
||||
<view class="info-value">{{ item.priority }}</view>
|
||||
</view>
|
||||
</view> -->
|
||||
</view>
|
||||
|
||||
<view class="divider"></view>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<!-- 自定义导航栏 -->
|
||||
<view v-if="!fromLogin" class="custom-navbar">
|
||||
<view class="navbar-back" @tap="goBack">
|
||||
<text class="back-text">🔙</text>
|
||||
<image class="back-icon" src="../../static/left.png" mode="aspectFit" />
|
||||
</view>
|
||||
<view class="navbar-title">绑定手机号</view>
|
||||
<view class="navbar-placeholder"></view>
|
||||
@@ -129,7 +129,9 @@
|
||||
};
|
||||
|
||||
const goBack = () => {
|
||||
uni.navigateBack();
|
||||
uni.navigateTo({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -137,6 +139,10 @@
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: var(--bg-secondary);
|
||||
max-width: 750rpx;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.custom-navbar {
|
||||
@@ -160,8 +166,8 @@
|
||||
cursor: pointer;
|
||||
|
||||
.back-icon {
|
||||
font-size: 36rpx;
|
||||
color: var(--text-primary);
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
.back-text {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
:isDevice="userInfo.isDevice" :networkStatus="deviceInfo.network_status" />
|
||||
|
||||
<DeviceStatusCard v-if="userInfo.isDevice" :deviceInfo="deviceInfo" :isRealName="isRealName"
|
||||
@authentication="enterDetail('authentication')" />
|
||||
:isDevice="userInfo.isDevice" @authentication="enterDetail('authentication')" />
|
||||
|
||||
<!-- <VoiceCard v-if="!userInfo.isDevice" :voiceStats="voiceStats" :dateRangeText="dateRangeText"
|
||||
@openDatePicker="openDateRangePicker" /> -->
|
||||
@@ -25,11 +25,11 @@
|
||||
<view class="flex-col-g20">
|
||||
<view class="flex-col-g8">
|
||||
<label class="caption">名称:</label>
|
||||
<up-input placeholder="WIFI名称" border="surround" v-model="wifi_info.ssid" />
|
||||
<up-input placeholder="WIFI名称" class="wifi-input" v-model="wifi_info.ssid" />
|
||||
</view>
|
||||
<view class="flex-col-g8">
|
||||
<label class="caption">密码:</label>
|
||||
<up-input placeholder="WIFI密码" border="surround" v-model="wifi_info.pwd" />
|
||||
<up-input placeholder="WIFI密码" class="wifi-input" v-model="wifi_info.pwd" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="btn-group mt-20">
|
||||
@@ -229,9 +229,9 @@
|
||||
// 流量信息已由 TrafficCard 组件独立获取,这里不再处理
|
||||
|
||||
// 当前卡信息
|
||||
let currentCard = null;
|
||||
if (data.cards && data.cards.length > 0) {
|
||||
// 找到当前卡
|
||||
const currentCard = data.cards.find(card => card.is_current) || data.cards[0];
|
||||
currentCard = data.cards.find(card => card.is_current) || '';
|
||||
deviceInfo.currentIccid = currentCard.iccid || '-';
|
||||
|
||||
// 卡列表
|
||||
@@ -245,6 +245,15 @@
|
||||
deviceInfo.mchList = [];
|
||||
}
|
||||
|
||||
// 运营商信息
|
||||
const carrierTypeMap = {
|
||||
CMCC: '中国移动',
|
||||
CUCC: '中国联通',
|
||||
CTCC: '中国电信',
|
||||
CBN: '中国广电'
|
||||
};
|
||||
deviceInfo.carrier_name = (currentCard && carrierTypeMap[currentCard.carrier_type]) || '-';
|
||||
|
||||
// 设备实时信息
|
||||
if (data.device_realtime) {
|
||||
const rt = data.device_realtime;
|
||||
@@ -379,15 +388,27 @@
|
||||
// #endif
|
||||
};
|
||||
|
||||
const enterBack = () => {
|
||||
const enterBack = async () => {
|
||||
if (!deviceInfo.lan_ip || deviceInfo.lan_ip === '-') {
|
||||
uni.showToast({
|
||||
title: '暂无后台地址',
|
||||
icon: 'none'
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '请将设备开机后, 等待2分钟左右进行连接WiFi',
|
||||
showCancel: false,
|
||||
buttonText: '我知道了'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await new Promise((resolve) => {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '请先连接当前设备WiFi,否则无法访问后台管理',
|
||||
cancelText: '取消',
|
||||
confirmText: '我知道了',
|
||||
success: (res) => res.confirm && resolve()
|
||||
});
|
||||
});
|
||||
|
||||
// 跳转到设备后台管理地址
|
||||
const url = deviceInfo.lan_ip.startsWith('http') ? deviceInfo.lan_ip : `http://${deviceInfo.lan_ip}`;
|
||||
|
||||
@@ -593,52 +614,55 @@
|
||||
return;
|
||||
}
|
||||
|
||||
uni.showLoading({
|
||||
title: '同步中...',
|
||||
mask: true
|
||||
uni.showModal({
|
||||
title: '确认同步',
|
||||
content: '确定要同步运营商数据吗?',
|
||||
success: (res) => {
|
||||
if (!res.confirm) return;
|
||||
uni.showLoading({
|
||||
title: '同步中...',
|
||||
mask: true
|
||||
});
|
||||
|
||||
assetApi.refresh(identifier).then((data) => {
|
||||
uni.hideLoading();
|
||||
|
||||
if (data.accepted) {
|
||||
const refreshTypeText = data.refresh_type === 'device' ? '设备' : '卡';
|
||||
uni.showToast({
|
||||
title: `${refreshTypeText}数据同步成功`,
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
loadAssetInfo();
|
||||
}, 2000);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '同步失败,请稍后重试',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}).catch((e) => {
|
||||
uni.hideLoading();
|
||||
console.error('同步数据失败', e);
|
||||
|
||||
if (e.msg && e.msg.includes('冷却')) {
|
||||
uni.showToast({
|
||||
title: e.msg,
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '同步失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
const data = await assetApi.refresh(identifier);
|
||||
|
||||
uni.hideLoading();
|
||||
|
||||
if (data.accepted) {
|
||||
const refreshTypeText = data.refresh_type === 'device' ? '设备' : '卡';
|
||||
uni.showToast({
|
||||
title: `${refreshTypeText}数据同步成功`,
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
});
|
||||
|
||||
// 同步成功后,延迟重新加载资产信息
|
||||
setTimeout(() => {
|
||||
loadAssetInfo();
|
||||
}, 2000);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '同步失败,请稍后重试',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
uni.hideLoading();
|
||||
console.error('同步数据失败', e);
|
||||
|
||||
// 如果是冷却时间错误,显示提示
|
||||
if (e.msg && e.msg.includes('冷却')) {
|
||||
uni.showToast({
|
||||
title: e.msg,
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '同步失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const enterDetail = (name) => {
|
||||
@@ -712,6 +736,11 @@
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
const token = uni.getStorageSync('token');
|
||||
if (!token) {
|
||||
uni.reLaunch({ url: '/pages/login/login' });
|
||||
return;
|
||||
}
|
||||
initCurrentMonth();
|
||||
loadAssetInfo();
|
||||
});
|
||||
@@ -727,6 +756,12 @@
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.wifi-input {
|
||||
border: 2rpx solid var(--border-light);
|
||||
border-radius: 8rpx;
|
||||
padding: 8rpx 16rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-spacer {
|
||||
|
||||
@@ -1,63 +1,64 @@
|
||||
<template>
|
||||
<view class="login-page">
|
||||
<view class="overlay"></view>
|
||||
<view class="animated-bg">
|
||||
<view class="circle circle-1"></view>
|
||||
<view class="circle circle-2"></view>
|
||||
<view class="circle circle-3"></view>
|
||||
<view class="circle circle-4"></view>
|
||||
<view class="circle circle-5"></view>
|
||||
<view class="shape shape-triangle"></view>
|
||||
<view class="shape shape-square"></view>
|
||||
<view class="shape shape-pentagon"></view>
|
||||
<view class="shape shape-hexagon"></view>
|
||||
</view>
|
||||
<view class="content-wrapper">
|
||||
<view class="content">
|
||||
<view class="header">
|
||||
<view class="main-title">登录</view>
|
||||
<view class="main-title">欢迎登录</view>
|
||||
<view class="sub-title">请输入标识进行登录</view>
|
||||
</view>
|
||||
|
||||
<view class="form-section">
|
||||
<view class="input-wrap" :class="{ focus: inputFocus }">
|
||||
<input
|
||||
v-model="identifier"
|
||||
class="input"
|
||||
placeholder="请输入标识"
|
||||
placeholder-class="placeholder"
|
||||
@focus="inputFocus = true"
|
||||
@blur="inputFocus = false"
|
||||
@input="showError = false"
|
||||
/>
|
||||
</view>
|
||||
<text v-if="showError" class="error">请输入标识</text>
|
||||
</view>
|
||||
|
||||
<view class="btn-section">
|
||||
<button
|
||||
class="btn-login"
|
||||
:class="{ disabled: !identifier || loading || !agreed }"
|
||||
:disabled="!identifier || loading || !agreed"
|
||||
@tap="handleLogin"
|
||||
>
|
||||
<view v-if="loading" class="loading">
|
||||
<view class="dot"/>
|
||||
<view class="dot"/>
|
||||
<view class="dot"/>
|
||||
<view class="form-section">
|
||||
<view class="input-wrap" :class="{ focus: inputFocus }">
|
||||
<input
|
||||
v-model="identifier"
|
||||
class="input"
|
||||
placeholder="请输入标识"
|
||||
placeholder-class="placeholder"
|
||||
@focus="inputFocus = true"
|
||||
@blur="handleBlur"
|
||||
@input="showError = false"
|
||||
/>
|
||||
<image
|
||||
v-if="identifier && inputFocus"
|
||||
class="clear-icon"
|
||||
src="/static/clear.png"
|
||||
mode="aspectFit"
|
||||
@mousedown.prevent="clearInput"
|
||||
@touchstart.prevent="clearInput"
|
||||
></image>
|
||||
</view>
|
||||
<text v-if="showError" class="error">请输入标识</text>
|
||||
</view>
|
||||
|
||||
<view class="btn-section">
|
||||
<button
|
||||
class="btn-login"
|
||||
:class="{ disabled: !identifier || loading || !agreed }"
|
||||
:disabled="!identifier || loading || !agreed"
|
||||
@tap="handleLogin"
|
||||
>
|
||||
<view v-if="loading" class="loading">
|
||||
<view class="dot"/>
|
||||
<view class="dot"/>
|
||||
<view class="dot"/>
|
||||
</view>
|
||||
<text v-else>立即登录</text>
|
||||
</button>
|
||||
|
||||
<button class="btn-scan" @tap="handleScanLogin">
|
||||
<image src="/static/scan-code.png" mode="aspectFit" class="scan-icon"></image>
|
||||
<text>扫一扫登录</text>
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<view class="agreement">
|
||||
<view class="agreement-text">
|
||||
<text class="text">登录即表示同意</text>
|
||||
<text class="link">《用户协议》</text>
|
||||
<text class="text">和</text>
|
||||
<text class="link">《隐私政策》</text>
|
||||
</view>
|
||||
<text v-else>登录</text>
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<view class="agreement">
|
||||
<view class="agreement-text">
|
||||
<text class="text">登录即表示同意</text>
|
||||
<text class="link">《用户协议》</text>
|
||||
<text class="text">和</text>
|
||||
<text class="link">《隐私政策》</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -67,10 +68,32 @@
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { authApi } from '@/api/index.js';
|
||||
import { useUserStore } from '@/store/index.js';
|
||||
import { APP_ID, USE_WECHAT_AUTH } from '@/utils/env.js';
|
||||
import { APP_ID } from '@/utils/env.js';
|
||||
import { initWxConfig, wxScan, isInWechat } from '@/utils/wxsdk.js';
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
onMounted(async () => {
|
||||
const token = uni.getStorageSync('token');
|
||||
if (token) {
|
||||
uni.reLaunch({ url: '/pages/index/index' });
|
||||
}
|
||||
handleWechatCallback();
|
||||
getPathDeviceId();
|
||||
|
||||
// 初始化微信 SDK (仅在微信浏览器内执行)
|
||||
// #ifdef H5
|
||||
if (isInWechat()) {
|
||||
try {
|
||||
await initWxConfig();
|
||||
console.log('微信 SDK 初始化成功');
|
||||
} catch (e) {
|
||||
console.error('微信 SDK 初始化失败', e);
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
});
|
||||
|
||||
const identifier = ref('');
|
||||
const loading = ref(false);
|
||||
const agreed = ref(true);
|
||||
@@ -88,11 +111,23 @@
|
||||
|
||||
const redirectToWxAuth = (assetToken) => {
|
||||
sessionStorage.setItem('assetToken', assetToken);
|
||||
const redirectUri = encodeURIComponent('https://cmp-c.boss160.cn/');
|
||||
const redirectUri = encodeURIComponent(window.location.origin + window.location.pathname);
|
||||
const url = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${APP_ID}&redirect_uri=${redirectUri}&response_type=code&scope=snsapi_userinfo&state=wechat_auth#wechat_redirect`;
|
||||
window.location.href = url;
|
||||
};
|
||||
|
||||
const clearInput = () => {
|
||||
identifier.value = '';
|
||||
showError.value = false;
|
||||
inputFocus.value = true;
|
||||
};
|
||||
|
||||
const handleBlur = () => {
|
||||
setTimeout(() => {
|
||||
inputFocus.value = false;
|
||||
}, 200);
|
||||
};
|
||||
|
||||
const handleLogin = () => {
|
||||
if (!identifier.value) {
|
||||
showError.value = true;
|
||||
@@ -101,6 +136,57 @@
|
||||
doLogin();
|
||||
};
|
||||
|
||||
const handleScanLogin = async () => {
|
||||
// #ifdef H5
|
||||
try {
|
||||
// 检查是否在微信环境
|
||||
if (!isInWechat()) {
|
||||
uni.showToast({ title: '请在微信中打开', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
// 调用扫一扫
|
||||
const resultStr = await wxScan();
|
||||
console.log('扫码结果:', resultStr);
|
||||
|
||||
let value = '';
|
||||
|
||||
// 解析二维码内容
|
||||
if (resultStr.includes('?')) {
|
||||
const params = new URLSearchParams(resultStr.split('?')[1]);
|
||||
value = params.get('cardNum') || '';
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
const match = resultStr.match(/=([^=]+)$/);
|
||||
value = match ? match[1] : '';
|
||||
}
|
||||
|
||||
if (value) {
|
||||
identifier.value = value;
|
||||
handleLogin();
|
||||
} else {
|
||||
uni.showToast({ title: '无效的二维码', icon: 'none' });
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('扫码错误:', err);
|
||||
// uni.showToast({ title: JSON.stringify(err), icon: 'none' });
|
||||
}
|
||||
// #endif
|
||||
// #ifndef H5
|
||||
uni.showToast({ title: '请在微信中打开', icon: 'none' });
|
||||
// #endif
|
||||
};
|
||||
|
||||
const getPathDeviceId = () => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const deviceId = params.get('device_id');
|
||||
if (deviceId) {
|
||||
identifier.value = deviceId;
|
||||
handleLogin();
|
||||
}
|
||||
};
|
||||
|
||||
const doLogin = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
@@ -108,20 +194,6 @@
|
||||
userStore.setAssetToken(verifyData.asset_token);
|
||||
userStore.setIdentifier(identifier.value);
|
||||
|
||||
if (!USE_WECHAT_AUTH) {
|
||||
const loginData = await authApi.wechatLogin(verifyData.asset_token, 'mock_code');
|
||||
userStore.setToken(loginData.token);
|
||||
uni.setStorageSync('identifier', userStore.state.identifier);
|
||||
|
||||
// 判断是否需要绑定手机号
|
||||
if (loginData.need_bind_phone) {
|
||||
uni.redirectTo({ url: '/pages/bind/bind?fromLogin=true' });
|
||||
} else {
|
||||
uni.redirectTo({ url: '/pages/index/index' });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
redirectToWxAuth(verifyData.asset_token);
|
||||
} catch (e) {
|
||||
console.error('登录失败', e);
|
||||
@@ -130,7 +202,6 @@
|
||||
};
|
||||
|
||||
const handleWechatCallback = async () => {
|
||||
if (!USE_WECHAT_AUTH) return;
|
||||
|
||||
const code = getCode();
|
||||
const assetToken = sessionStorage.getItem('assetToken');
|
||||
@@ -145,44 +216,18 @@
|
||||
userStore.setToken(loginData.token);
|
||||
uni.setStorageSync('identifier', userStore.state.identifier);
|
||||
|
||||
// H5 环境获取微信用户信息
|
||||
// #ifdef H5
|
||||
try {
|
||||
// 通过 uni.getUserProfile 获取用户信息(需要用户点击授权)
|
||||
// 注意:这个方法在微信公众号 H5 中不可用,需要通过后端接口获取
|
||||
// 这里暂时保存默认信息,实际应该由后端返回用户信息
|
||||
if (loginData.user_info) {
|
||||
userStore.setUserInfo({
|
||||
avatar: loginData.user_info.avatar || '',
|
||||
nickname: loginData.user_info.nickname || ''
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('获取用户信息失败', err);
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
// 小程序环境可以直接获取用户信息
|
||||
try {
|
||||
const { userInfo } = await uni.getUserProfile({
|
||||
desc: '用于完善用户资料'
|
||||
});
|
||||
if (loginData.user_info) {
|
||||
userStore.setUserInfo({
|
||||
avatar: userInfo.avatarUrl || '',
|
||||
nickname: userInfo.nickName || ''
|
||||
avatar: loginData.user_info.avatar || '',
|
||||
nickname: loginData.user_info.nickname || ''
|
||||
});
|
||||
} catch (err) {
|
||||
console.log('用户取消授权');
|
||||
}
|
||||
// #endif
|
||||
|
||||
// 判断是否需要绑定手机号
|
||||
if (loginData.need_bind_phone) {
|
||||
// 需要绑定手机号,跳转到绑定页面
|
||||
uni.redirectTo({ url: '/pages/bind/bind?fromLogin=true' });
|
||||
} else {
|
||||
// 不需要绑定,直接进入首页
|
||||
uni.redirectTo({ url: '/pages/index/index' });
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -190,343 +235,172 @@
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
handleWechatCallback();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.login-page {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: linear-gradient(135deg, #005cbf 0%, #007aff 50%, #4da6ff 100%);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: radial-gradient(circle at 30% 20%, rgba(255, 255, 255, 0.1) 0%, transparent 50%),
|
||||
radial-gradient(circle at 70% 80%, rgba(255, 255, 255, 0.08) 0%, transparent 50%);
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.animated-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.circle {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.circle-1 {
|
||||
width: 300rpx;
|
||||
height: 300rpx;
|
||||
top: 10%;
|
||||
left: -100rpx;
|
||||
animation: float1 20s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.circle-2 {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
top: 60%;
|
||||
right: -50rpx;
|
||||
animation: float2 15s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.circle-3 {
|
||||
width: 150rpx;
|
||||
height: 150rpx;
|
||||
top: 30%;
|
||||
right: 20%;
|
||||
animation: float3 18s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.circle-4 {
|
||||
width: 250rpx;
|
||||
height: 250rpx;
|
||||
bottom: 10%;
|
||||
left: 10%;
|
||||
animation: float4 22s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.circle-5 {
|
||||
width: 180rpx;
|
||||
height: 180rpx;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
animation: float5 16s infinite ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes float1 {
|
||||
0%, 100% {
|
||||
transform: translate(0, 0) scale(1);
|
||||
opacity: 0.3;
|
||||
}
|
||||
50% {
|
||||
transform: translate(100rpx, -80rpx) scale(1.2);
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes float2 {
|
||||
0%, 100% {
|
||||
transform: translate(0, 0) scale(1);
|
||||
opacity: 0.2;
|
||||
}
|
||||
50% {
|
||||
transform: translate(-80rpx, 100rpx) scale(1.3);
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes float3 {
|
||||
0%, 100% {
|
||||
transform: translate(0, 0) scale(1);
|
||||
opacity: 0.25;
|
||||
}
|
||||
50% {
|
||||
transform: translate(-60rpx, -60rpx) scale(1.1);
|
||||
opacity: 0.45;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes float4 {
|
||||
0%, 100% {
|
||||
transform: translate(0, 0) scale(1);
|
||||
opacity: 0.3;
|
||||
}
|
||||
50% {
|
||||
transform: translate(120rpx, -100rpx) scale(1.15);
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes float5 {
|
||||
0%, 100% {
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
opacity: 0.2;
|
||||
}
|
||||
50% {
|
||||
transform: translate(calc(-50% + 80rpx), calc(-50% + 80rpx)) scale(1.25);
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
.shape {
|
||||
position: absolute;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
backdrop-filter: blur(1px);
|
||||
}
|
||||
|
||||
.shape-triangle {
|
||||
width: 0;
|
||||
height: 0;
|
||||
background: transparent;
|
||||
border-left: 80rpx solid transparent;
|
||||
border-right: 80rpx solid transparent;
|
||||
border-bottom: 140rpx solid rgba(255, 255, 255, 0.1);
|
||||
top: 15%;
|
||||
right: 15%;
|
||||
animation: rotate1 25s infinite linear;
|
||||
}
|
||||
|
||||
.shape-square {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
top: 70%;
|
||||
left: 5%;
|
||||
transform: rotate(45deg);
|
||||
animation: float-rotate 20s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.shape-pentagon {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
clip-path: polygon(50% 0%, 100% 38%, 82% 100%, 18% 100%, 0% 38%);
|
||||
top: 40%;
|
||||
left: 80%;
|
||||
animation: pulse 18s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.shape-hexagon {
|
||||
width: 90rpx;
|
||||
height: 90rpx;
|
||||
clip-path: polygon(30% 0%, 70% 0%, 100% 50%, 70% 100%, 30% 100%, 0% 50%);
|
||||
bottom: 25%;
|
||||
right: 25%;
|
||||
animation: float-spin 22s infinite ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes rotate1 {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
opacity: 0.15;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.25;
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
opacity: 0.15;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes float-rotate {
|
||||
0%, 100% {
|
||||
transform: translate(0, 0) rotate(45deg);
|
||||
opacity: 0.2;
|
||||
}
|
||||
50% {
|
||||
transform: translate(80rpx, -60rpx) rotate(225deg);
|
||||
opacity: 0.35;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
opacity: 0.2;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.3);
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes float-spin {
|
||||
0%, 100% {
|
||||
transform: translate(0, 0) rotate(0deg);
|
||||
opacity: 0.25;
|
||||
}
|
||||
50% {
|
||||
transform: translate(-70rpx, 90rpx) rotate(180deg);
|
||||
opacity: 0.4;
|
||||
}
|
||||
background: var(--bg-secondary);
|
||||
max-width: 750rpx;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
padding: 40rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.content {
|
||||
background: var(--bg-primary);
|
||||
border-radius: var(--radius-large);
|
||||
padding: 60rpx 40rpx;
|
||||
z-index: 1;
|
||||
box-shadow: var(--shadow-medium);
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 80rpx;
|
||||
text-align: center;
|
||||
margin-bottom: 60rpx;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
font-size: 56rpx;
|
||||
font-size: 48rpx;
|
||||
font-weight: 700;
|
||||
color: #FFFFFF;
|
||||
color: var(--text-primary);
|
||||
line-height: 1.3;
|
||||
margin-bottom: 24rpx;
|
||||
letter-spacing: 0.5rpx;
|
||||
text-shadow: 0 2rpx 8rpx rgba(64, 40, 128, 0.3);
|
||||
margin-bottom: 16rpx;
|
||||
letter-spacing: -0.5rpx;
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
font-size: 26rpx;
|
||||
color: var(--text-tertiary);
|
||||
line-height: 1.5;
|
||||
letter-spacing: 0.5rpx;
|
||||
text-shadow: 0 1rpx 4rpx rgba(64, 40, 128, 0.2);
|
||||
}
|
||||
|
||||
.form-section {
|
||||
margin-bottom: 60rpx;
|
||||
margin-bottom: 48rpx;
|
||||
}
|
||||
|
||||
.input-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 20rpx;
|
||||
padding: 32rpx 40rpx;
|
||||
transition: all 0.3s;
|
||||
box-shadow: 0 4rpx 16rpx rgba(64, 40, 128, 0.1);
|
||||
gap: 16rpx;
|
||||
background: var(--bg-secondary);
|
||||
border: 2rpx solid var(--border-color);
|
||||
border-radius: var(--radius-medium);
|
||||
padding: 28rpx 32rpx;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.input-wrap.focus {
|
||||
background: rgba(255, 255, 255, 1);
|
||||
box-shadow: 0 4rpx 20rpx rgba(64, 40, 128, 0.2);
|
||||
background: var(--bg-primary);
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 0 4rpx rgba(10, 132, 255, 0.1);
|
||||
}
|
||||
|
||||
.input {
|
||||
flex: 1;
|
||||
font-size: 32rpx;
|
||||
color: #1A1A1A;
|
||||
font-size: 30rpx;
|
||||
color: var(--text-primary);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
color: #C4C4C4;
|
||||
color: var(--text-quaternary);
|
||||
}
|
||||
|
||||
.clear-icon {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
opacity: 0.4;
|
||||
transition: opacity 0.3s ease;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.clear-icon:active {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.error {
|
||||
font-size: 24rpx;
|
||||
color: #EF4444;
|
||||
color: var(--danger);
|
||||
margin-top: 16rpx;
|
||||
padding-left: 8rpx;
|
||||
}
|
||||
|
||||
.btn-section {
|
||||
margin-bottom: 60rpx;
|
||||
margin-bottom: 48rpx;
|
||||
}
|
||||
|
||||
.btn-login {
|
||||
width: 100%;
|
||||
height: 108rpx;
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 2rpx solid rgba(255, 255, 255, 0.4);
|
||||
border-radius: 20rpx;
|
||||
font-size: 36rpx;
|
||||
height: 96rpx;
|
||||
background: var(--primary);
|
||||
border: none;
|
||||
border-radius: var(--radius-medium);
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #FFFFFF;
|
||||
color: var(--text-inverse);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s;
|
||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.15);
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4rpx 12rpx rgba(10, 132, 255, 0.2);
|
||||
}
|
||||
|
||||
.btn-login.disabled {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
border: 2rpx solid rgba(255, 255, 255, 0.2);
|
||||
background: var(--gray-300);
|
||||
color: var(--text-quaternary);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.btn-login:active:not(.disabled) {
|
||||
opacity: 0.8;
|
||||
transform: translateY(2rpx);
|
||||
box-shadow: 0 2rpx 8rpx rgba(10, 132, 255, 0.2);
|
||||
}
|
||||
|
||||
.btn-scan {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
background: var(--bg-primary);
|
||||
border: 2rpx solid var(--primary);
|
||||
border-radius: var(--radius-medium);
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: var(--primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12rpx;
|
||||
margin-top: 24rpx;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-scan:active {
|
||||
background: rgba(10, 132, 255, 0.05);
|
||||
}
|
||||
|
||||
.scan-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
.loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 10rpx;
|
||||
height: 10rpx;
|
||||
background: #FFFFFF;
|
||||
width: 12rpx;
|
||||
height: 12rpx;
|
||||
background: var(--text-inverse);
|
||||
border-radius: 50%;
|
||||
animation: load 1.4s infinite ease-in-out both;
|
||||
}
|
||||
@@ -563,14 +437,13 @@
|
||||
}
|
||||
|
||||
.text {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
color: var(--text-tertiary);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: #FFFFFF;
|
||||
color: var(--primary);
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -1,24 +1,18 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="pd-20">
|
||||
<view class="wallet-card">
|
||||
<view class="balance-section">
|
||||
<view class="balance-header">
|
||||
<view class="balance-label">账户余额(元)</view>
|
||||
<view class="recharge-btn-wrapper">
|
||||
<button class="btn-recharge" @tap="showRechargeModal = true">充值</button>
|
||||
</view>
|
||||
<view class="wallet-header">
|
||||
<view class="header-left">
|
||||
<view class="balance-label">账户余额</view>
|
||||
<view class="balance-amount">¥{{ formatMoney(walletDetail.balance) }}</view>
|
||||
</view>
|
||||
<view class="balance-amount">{{ formatMoney(walletDetail.balance) }}</view>
|
||||
<button class="btn-recharge" @tap="showRechargeModal = true">充值</button>
|
||||
</view>
|
||||
<view class="wallet-footer">
|
||||
<view class="footer-item">
|
||||
<view class="footer-label">冻结金额</view>
|
||||
<view class="footer-value">{{ formatMoney(walletDetail.frozen_balance) }}</view>
|
||||
</view>
|
||||
<view class="footer-divider"></view>
|
||||
<view class="footer-item">
|
||||
<view class="footer-label">更新时间</view>
|
||||
<view class="footer-value">{{ formatDateTime(walletDetail.updated_at) }}</view>
|
||||
<view class="wallet-info">
|
||||
<view class="info-item">
|
||||
<view class="info-label">冻结金额</view>
|
||||
<view class="info-value">¥{{ formatMoney(walletDetail.frozen_balance) }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -37,7 +31,7 @@
|
||||
<swiper class="tab-swiper" :current="currentTab" @change="onSwiperChange" :circular="false"
|
||||
:acceleration="true">
|
||||
<swiper-item class="swiper-item-content">
|
||||
<scroll-view scroll-y class="list-content">
|
||||
<scroll-view scroll-y class="list-content" @scrolltolower="loadMoreRecharge">
|
||||
<view v-if="rechargeList.length === 0 && !rechargeLoading" class="empty-state">
|
||||
<view class="empty-icon">📋</view>
|
||||
<view class="empty-title">暂无充值订单</view>
|
||||
@@ -77,14 +71,13 @@
|
||||
<view class="load-more" v-if="rechargeList.length > 0">
|
||||
<text v-if="rechargeLoading">加载中...</text>
|
||||
<text v-else-if="rechargeNoMore">— 没有更多了 —</text>
|
||||
<text v-else @tap="loadMoreRecharge">点击加载更多</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</swiper-item>
|
||||
|
||||
<swiper-item class="swiper-item-content">
|
||||
<scroll-view scroll-y class="list-content">
|
||||
<scroll-view scroll-y class="list-content" @scrolltolower="loadMoreTransaction">
|
||||
<view v-if="transactionList.length === 0 && !transactionLoading" class="empty-state">
|
||||
<view class="empty-icon">💰</view>
|
||||
<view class="empty-title">暂无钱包流水</view>
|
||||
@@ -122,7 +115,6 @@
|
||||
<view class="load-more" v-if="transactionList.length > 0">
|
||||
<text v-if="transactionLoading">加载中...</text>
|
||||
<text v-else-if="transactionNoMore">— 没有更多了 —</text>
|
||||
<text v-else @tap="loadMoreTransaction">点击加载更多</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
@@ -162,6 +154,7 @@
|
||||
</up-popup>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -177,6 +170,7 @@
|
||||
import {
|
||||
useUserStore
|
||||
} from '@/store/index.js';
|
||||
import { wechatH5Pay, handlePaymentError, showPaymentToast } from '@/utils/payment.js';
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
@@ -278,10 +272,13 @@
|
||||
// 自定义金额
|
||||
const customAmountNum = parseFloat(customAmount.value);
|
||||
if (!customAmountNum || customAmountNum < 0.01) {
|
||||
uni.showToast({
|
||||
title: '请输入正确的充值金额',
|
||||
icon: 'none'
|
||||
});
|
||||
showRechargeModal.value = false;
|
||||
setTimeout(() => {
|
||||
uni.showToast({
|
||||
title: '请输入正确的充值金额',
|
||||
icon: 'none'
|
||||
});
|
||||
}, 50);
|
||||
return;
|
||||
}
|
||||
amount = Math.round(customAmountNum * 100); // 转换为分
|
||||
@@ -289,10 +286,13 @@
|
||||
// 预设金额
|
||||
amount = selectedAmount.value;
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '请选择充值金额',
|
||||
icon: 'none'
|
||||
});
|
||||
showRechargeModal.value = false;
|
||||
setTimeout(() => {
|
||||
uni.showToast({
|
||||
title: '请选择充值金额',
|
||||
icon: 'none'
|
||||
});
|
||||
}, 50);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -319,11 +319,14 @@
|
||||
// 检查金额范围
|
||||
if (amount < checkData.min_amount || amount > checkData.max_amount) {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: `充值金额范围:¥${(checkData.min_amount / 100).toFixed(2)} - ¥${(checkData.max_amount / 100).toFixed(2)}`,
|
||||
icon: 'none',
|
||||
duration: 2500
|
||||
});
|
||||
showRechargeModal.value = false;
|
||||
setTimeout(() => {
|
||||
uni.showToast({
|
||||
title: `充值金额范围:¥${(checkData.min_amount / 100).toFixed(2)} - ¥${(checkData.max_amount / 100).toFixed(2)}`,
|
||||
icon: 'none',
|
||||
duration: 2500
|
||||
});
|
||||
}, 50);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -339,103 +342,42 @@
|
||||
|
||||
// 验证支付配置
|
||||
if (!rechargeData.pay_config || !rechargeData.pay_config.package) {
|
||||
uni.showToast({
|
||||
title: '支付参数获取失败',
|
||||
icon: 'none'
|
||||
});
|
||||
setTimeout(() => {
|
||||
uni.showToast({
|
||||
title: '支付参数获取失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}, 50);
|
||||
return;
|
||||
}
|
||||
|
||||
// 第三步:调起微信支付
|
||||
// #ifdef H5
|
||||
if (typeof WeixinJSBridge === 'undefined') {
|
||||
uni.showToast({
|
||||
title: '请在微信中打开',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
try {
|
||||
await wechatH5Pay(rechargeData.pay_config);
|
||||
// 支付成功
|
||||
showPaymentToast(true, '充值成功');
|
||||
setTimeout(() => {
|
||||
loadWalletDetail();
|
||||
rechargePage.value = 1;
|
||||
rechargeNoMore.value = false;
|
||||
loadRechargeList();
|
||||
}, 1500);
|
||||
} catch (error) {
|
||||
// 支付失败或取消
|
||||
handlePaymentError(error);
|
||||
}
|
||||
|
||||
WeixinJSBridge.invoke('getBrandWCPayRequest', {
|
||||
appId: rechargeData.pay_config.app_id,
|
||||
timeStamp: rechargeData.pay_config.timestamp,
|
||||
nonceStr: rechargeData.pay_config.nonce_str,
|
||||
package: rechargeData.pay_config.package,
|
||||
signType: rechargeData.pay_config.sign_type,
|
||||
paySign: rechargeData.pay_config.pay_sign
|
||||
}, function(res) {
|
||||
if (res.err_msg === 'get_brand_wcpay_request:ok') {
|
||||
console.log('支付成功', res);
|
||||
uni.showToast({
|
||||
title: '充值成功',
|
||||
icon: 'success'
|
||||
});
|
||||
setTimeout(() => {
|
||||
loadWalletDetail();
|
||||
rechargePage.value = 1;
|
||||
rechargeNoMore.value = false;
|
||||
loadRechargeList();
|
||||
}, 1500);
|
||||
} else if (res.err_msg === 'get_brand_wcpay_request:cancel') {
|
||||
uni.showToast({
|
||||
title: '已取消支付',
|
||||
icon: 'none'
|
||||
});
|
||||
} else {
|
||||
console.error('支付失败', res);
|
||||
uni.showToast({
|
||||
title: '支付失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
});
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
uni.requestPayment({
|
||||
timeStamp: rechargeData.pay_config.timestamp,
|
||||
nonceStr: rechargeData.pay_config.nonce_str,
|
||||
package: rechargeData.pay_config.package,
|
||||
signType: rechargeData.pay_config.sign_type,
|
||||
paySign: rechargeData.pay_config.pay_sign,
|
||||
success: (res) => {
|
||||
console.log('支付成功', res);
|
||||
uni.showToast({
|
||||
title: '充值成功',
|
||||
icon: 'success'
|
||||
});
|
||||
setTimeout(() => {
|
||||
loadWalletDetail();
|
||||
rechargePage.value = 1;
|
||||
rechargeNoMore.value = false;
|
||||
loadRechargeList();
|
||||
}, 1500);
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('支付失败', err);
|
||||
if (err.errMsg && (err.errMsg.includes('cancel') || err.errMsg.includes('用户取消'))) {
|
||||
uni.showToast({
|
||||
title: '已取消支付',
|
||||
icon: 'none'
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '支付失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
// #endif
|
||||
|
||||
} catch (e) {
|
||||
uni.hideLoading();
|
||||
console.error('充值失败', e);
|
||||
const errorMsg = e.msg || e.message || '充值失败,请稍后重试';
|
||||
uni.showToast({
|
||||
title: errorMsg,
|
||||
icon: 'none'
|
||||
});
|
||||
showRechargeModal.value = false;
|
||||
setTimeout(() => {
|
||||
uni.showToast({
|
||||
title: errorMsg,
|
||||
icon: 'none'
|
||||
});
|
||||
}, 50);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -563,83 +505,21 @@
|
||||
}
|
||||
|
||||
// 调起微信支付
|
||||
// #ifdef H5
|
||||
if (typeof WeixinJSBridge === 'undefined') {
|
||||
uni.showToast({
|
||||
title: '请在微信中打开',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
try {
|
||||
await wechatH5Pay(rechargeData.pay_config);
|
||||
// 支付成功
|
||||
showPaymentToast(true, '充值成功');
|
||||
setTimeout(() => {
|
||||
loadWalletDetail();
|
||||
rechargePage.value = 1;
|
||||
rechargeNoMore.value = false;
|
||||
loadRechargeList();
|
||||
}, 1500);
|
||||
} catch (error) {
|
||||
// 支付失败或取消
|
||||
handlePaymentError(error);
|
||||
}
|
||||
|
||||
WeixinJSBridge.invoke('getBrandWCPayRequest', {
|
||||
appId: rechargeData.pay_config.app_id,
|
||||
timeStamp: rechargeData.pay_config.timestamp,
|
||||
nonceStr: rechargeData.pay_config.nonce_str,
|
||||
package: rechargeData.pay_config.package,
|
||||
signType: rechargeData.pay_config.sign_type,
|
||||
paySign: rechargeData.pay_config.pay_sign
|
||||
}, function(res) {
|
||||
if (res.err_msg === 'get_brand_wcpay_request:ok') {
|
||||
uni.showToast({
|
||||
title: '充值成功',
|
||||
icon: 'success'
|
||||
});
|
||||
setTimeout(() => {
|
||||
loadWalletDetail();
|
||||
rechargePage.value = 1;
|
||||
rechargeNoMore.value = false;
|
||||
loadRechargeList();
|
||||
}, 1500);
|
||||
} else if (res.err_msg === 'get_brand_wcpay_request:cancel') {
|
||||
uni.showToast({
|
||||
title: '已取消支付',
|
||||
icon: 'none'
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '支付失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
});
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
uni.requestPayment({
|
||||
timeStamp: rechargeData.pay_config.timestamp,
|
||||
nonceStr: rechargeData.pay_config.nonce_str,
|
||||
package: rechargeData.pay_config.package,
|
||||
signType: rechargeData.pay_config.sign_type,
|
||||
paySign: rechargeData.pay_config.pay_sign,
|
||||
success: (res) => {
|
||||
uni.showToast({
|
||||
title: '充值成功',
|
||||
icon: 'success'
|
||||
});
|
||||
setTimeout(() => {
|
||||
loadWalletDetail();
|
||||
rechargePage.value = 1;
|
||||
rechargeNoMore.value = false;
|
||||
loadRechargeList();
|
||||
}, 1500);
|
||||
},
|
||||
fail: (err) => {
|
||||
if (err.errMsg && (err.errMsg.includes('cancel') || err.errMsg.includes('用户取消'))) {
|
||||
uni.showToast({
|
||||
title: '已取消支付',
|
||||
icon: 'none'
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '支付失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
// #endif
|
||||
|
||||
} catch (e) {
|
||||
uni.hideLoading();
|
||||
console.error('获取支付配置失败', e);
|
||||
@@ -714,84 +594,86 @@
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.container {
|
||||
height: 100vh;
|
||||
min-height: 100vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20rpx;
|
||||
box-sizing: border-box;
|
||||
max-width: 750rpx;
|
||||
margin: 0 auto;
|
||||
padding: 0 !important;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.wallet-card {
|
||||
background: linear-gradient(135deg, #0A84FF 0%, #5AC8FA 50%, #64D2FF 100%);
|
||||
border-radius: 24rpx;
|
||||
padding: 36rpx;
|
||||
background: var(--primary);
|
||||
border-radius: var(--radius-large);
|
||||
padding: 40rpx;
|
||||
color: #fff;
|
||||
box-shadow: 0 8rpx 32rpx rgba(10, 132, 255, 0.3);
|
||||
.wallet-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 32rpx;
|
||||
|
||||
.balance-section {
|
||||
margin-bottom: 28rpx;
|
||||
|
||||
.balance-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8rpx;
|
||||
.header-left {
|
||||
flex: 1;
|
||||
|
||||
.balance-label {
|
||||
font-size: 24rpx;
|
||||
opacity: 0.75;
|
||||
font-size: 26rpx;
|
||||
opacity: 0.85;
|
||||
margin-bottom: 12rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.recharge-btn-wrapper {
|
||||
.btn-recharge {
|
||||
background: #fff;
|
||||
border: none;
|
||||
color: var(--primary);
|
||||
font-size: 26rpx;
|
||||
padding: 12rpx 32rpx;
|
||||
border-radius: 40rpx;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
.balance-amount {
|
||||
font-size: 64rpx;
|
||||
font-weight: 700;
|
||||
letter-spacing: -2rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.balance-amount {
|
||||
font-size: 72rpx;
|
||||
font-weight: 700;
|
||||
letter-spacing: -2rpx;
|
||||
.btn-recharge {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 2rpx solid rgba(255, 255, 255, 0.3);
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
padding: 0rpx 32rpx;
|
||||
border-radius: 50rpx;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wallet-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.wallet-info {
|
||||
padding-top: 24rpx;
|
||||
border-top: 1rpx solid rgba(255, 255, 255, 0.2);
|
||||
|
||||
.footer-divider {
|
||||
width: 1rpx;
|
||||
height: 40rpx;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
margin: 0 32rpx;
|
||||
}
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.footer-item {
|
||||
flex: 1;
|
||||
|
||||
.footer-label {
|
||||
font-size: 22rpx;
|
||||
opacity: 0.7;
|
||||
margin-bottom: 6rpx;
|
||||
.info-label {
|
||||
font-size: 24rpx;
|
||||
opacity: 0.8;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.footer-value {
|
||||
font-size: 26rpx;
|
||||
.info-value {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
@@ -846,17 +728,17 @@
|
||||
}
|
||||
|
||||
.tab-swiper {
|
||||
flex: 1;
|
||||
margin-top: 20rpx;
|
||||
min-height: 0;
|
||||
height: calc(100vh - 500rpx);
|
||||
}
|
||||
|
||||
.swiper-item-content {
|
||||
height: 100%;
|
||||
height: calc(100vh - 500rpx);
|
||||
}
|
||||
|
||||
.list-content {
|
||||
height: 100%;
|
||||
height: calc(100vh - 500rpx);
|
||||
padding-top: 16rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@@ -8,14 +8,16 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="orderList.length === 0 && !loading" class="empty-state">
|
||||
<view class="empty-icon">📋</view>
|
||||
<view class="empty-title">暂无订单</view>
|
||||
<view class="empty-desc">当前账号下暂无订单信息</view>
|
||||
</view>
|
||||
<scroll-view scroll-y class="scroll-container" @scrolltolower="loadMore">
|
||||
|
||||
<scroll-view v-else scroll-y class="order-scroll" @scrolltolower="loadMore">
|
||||
<view class="order-card" v-for="(item, index) in orderList" :key="index">
|
||||
<view v-if="orderList.length === 0 && !loading" class="empty-state">
|
||||
<view class="empty-icon">📋</view>
|
||||
<view class="empty-title">暂无订单</view>
|
||||
<view class="empty-desc">当前账号下暂无订单信息</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="order-list">
|
||||
<view class="order-card" v-for="(item, index) in orderList" :key="index">
|
||||
<view class="card-header">
|
||||
<view class="header-left">
|
||||
<view class="header-row">
|
||||
@@ -31,16 +33,24 @@
|
||||
<view class="card-body">
|
||||
<view class="info-grid">
|
||||
<view class="info-item">
|
||||
<view class="info-label">订单时间</view>
|
||||
<view class="info-value">{{ item.created_at }}</view>
|
||||
<view class="info-label">套餐名称</view>
|
||||
<view class="info-value">
|
||||
<view class="package-item" v-for="(pkgName, pIndex) in item.package_names" :key="pIndex">
|
||||
<text class="package-name">{{ pkgName }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<view class="info-label">订单金额</view>
|
||||
<view class="info-value amount">¥{{ formatMoney(item.total_amount) }}</view>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<view class="info-label">下单时间</view>
|
||||
<view class="info-value">{{ item.created_at }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="divider"></view>
|
||||
<!-- <view class="divider"></view>
|
||||
|
||||
<view class="package-section">
|
||||
<view class="section-label">套餐名称</view>
|
||||
@@ -49,7 +59,7 @@
|
||||
<text class="package-name">{{ pkgName }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view> -->
|
||||
|
||||
<view v-if="item.payment_status === 1" class="card-footer">
|
||||
<button class="btn-pay" @tap="handleOrderPayment(item)">立即支付</button>
|
||||
@@ -57,10 +67,11 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="load-more" v-if="orderList.length > 0">
|
||||
<text v-if="loading">加载中...</text>
|
||||
<text v-else-if="noMore">没有更多了</text>
|
||||
<text v-else>上拉加载更多</text>
|
||||
<view class="load-more" v-if="orderList.length > 0">
|
||||
<text v-if="loading">加载中...</text>
|
||||
<text v-else-if="noMore">没有更多了</text>
|
||||
<text v-else>上拉加载更多</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
@@ -78,6 +89,7 @@
|
||||
import {
|
||||
useUserStore
|
||||
} from '@/store/index.js';
|
||||
import { wechatH5Pay, handlePaymentError, showPaymentToast } from '@/utils/payment.js';
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
@@ -195,81 +207,21 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// #ifdef H5
|
||||
if (typeof WeixinJSBridge === 'undefined') {
|
||||
uni.showToast({
|
||||
title: '请在微信中打开',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
// 调起微信支付
|
||||
try {
|
||||
await wechatH5Pay(payData.pay_config);
|
||||
// 支付成功
|
||||
showPaymentToast(true, '支付成功');
|
||||
setTimeout(() => {
|
||||
page.value = 1;
|
||||
noMore.value = false;
|
||||
loadOrderList();
|
||||
}, 1500);
|
||||
} catch (error) {
|
||||
// 支付失败或取消
|
||||
handlePaymentError(error);
|
||||
}
|
||||
|
||||
WeixinJSBridge.invoke('getBrandWCPayRequest', {
|
||||
appId: payData.pay_config.app_id,
|
||||
timeStamp: payData.pay_config.timestamp,
|
||||
nonceStr: payData.pay_config.nonce_str,
|
||||
package: payData.pay_config.package,
|
||||
signType: payData.pay_config.sign_type,
|
||||
paySign: payData.pay_config.pay_sign
|
||||
}, function(res) {
|
||||
if (res.err_msg === 'get_brand_wcpay_request:ok') {
|
||||
uni.showToast({
|
||||
title: '支付成功',
|
||||
icon: 'success'
|
||||
});
|
||||
setTimeout(() => {
|
||||
page.value = 1;
|
||||
noMore.value = false;
|
||||
loadOrderList();
|
||||
}, 1500);
|
||||
} else if (res.err_msg === 'get_brand_wcpay_request:cancel') {
|
||||
uni.showToast({
|
||||
title: '已取消支付',
|
||||
icon: 'none'
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '支付失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
});
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
uni.requestPayment({
|
||||
timeStamp: payData.pay_config.timestamp,
|
||||
nonceStr: payData.pay_config.nonce_str,
|
||||
package: payData.pay_config.package,
|
||||
signType: payData.pay_config.sign_type,
|
||||
paySign: payData.pay_config.pay_sign,
|
||||
success: () => {
|
||||
uni.showToast({
|
||||
title: '支付成功',
|
||||
icon: 'success'
|
||||
});
|
||||
setTimeout(() => {
|
||||
page.value = 1;
|
||||
noMore.value = false;
|
||||
loadOrderList();
|
||||
}, 1500);
|
||||
},
|
||||
fail: (err) => {
|
||||
if (err.errMsg && (err.errMsg.includes('cancel') || err.errMsg.includes('用户取消'))) {
|
||||
uni.showToast({
|
||||
title: '已取消支付',
|
||||
icon: 'none'
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '支付失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
// #endif
|
||||
|
||||
} catch (e) {
|
||||
uni.hideLoading();
|
||||
console.error('支付失败', e);
|
||||
@@ -288,19 +240,29 @@
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.container {
|
||||
height: 100vh;
|
||||
position: fixed;
|
||||
top: var(--window-top);
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #f5f6f7;
|
||||
background: var(--bg-secondary);
|
||||
max-width: 750rpx;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.filter-tabs {
|
||||
display: flex;
|
||||
background: #fff;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
border-radius: 10rpx;
|
||||
z-index: 10;
|
||||
border-radius: 20rpx;
|
||||
z-index: 100;
|
||||
flex-shrink: 0;
|
||||
margin-top: 20rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
|
||||
.tab-item {
|
||||
position: relative;
|
||||
@@ -330,8 +292,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
.order-scroll {
|
||||
.scroll-container {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
|
||||
@@ -81,6 +81,7 @@
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { assetApi, orderApi, walletApi } from '@/api/index.js';
|
||||
import { useUserStore } from '@/store/index.js';
|
||||
import { wechatH5Pay, handlePaymentError, showPaymentToast } from '@/utils/payment.js';
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
@@ -276,63 +277,18 @@
|
||||
};
|
||||
|
||||
const handleWechatPay = async (payConfig, isForceRecharge = false) => {
|
||||
// 验证参数
|
||||
if (!payConfig.package || !payConfig.package.startsWith('prepay_id=')) {
|
||||
uni.showToast({ title: '支付参数无效', icon: 'none' });
|
||||
return;
|
||||
try {
|
||||
await wechatH5Pay(payConfig);
|
||||
// 支付成功
|
||||
const successMsg = isForceRecharge ? '充值成功,套餐将自动购买' : '支付成功';
|
||||
showPaymentToast(true, successMsg);
|
||||
setTimeout(() => {
|
||||
loadWalletBalance();
|
||||
}, 1500);
|
||||
} catch (error) {
|
||||
// 支付失败或取消
|
||||
handlePaymentError(error);
|
||||
}
|
||||
|
||||
// #ifdef H5
|
||||
if (typeof WeixinJSBridge === 'undefined') {
|
||||
uni.showToast({ title: '请在微信中打开', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
WeixinJSBridge.invoke('getBrandWCPayRequest', {
|
||||
appId: payConfig.app_id,
|
||||
timeStamp: payConfig.timestamp,
|
||||
nonceStr: payConfig.nonce_str,
|
||||
package: payConfig.package,
|
||||
signType: payConfig.sign_type,
|
||||
paySign: payConfig.pay_sign
|
||||
}, function(res) {
|
||||
if (res.err_msg === 'get_brand_wcpay_request:ok') {
|
||||
const successMsg = isForceRecharge ? '充值成功,套餐将自动购买' : '支付成功';
|
||||
uni.showToast({ title: successMsg, icon: 'success', duration: 2000 });
|
||||
setTimeout(() => {
|
||||
loadWalletBalance();
|
||||
}, 1500);
|
||||
} else if (res.err_msg === 'get_brand_wcpay_request:cancel') {
|
||||
uni.showToast({ title: '已取消支付', icon: 'none' });
|
||||
} else {
|
||||
uni.showToast({ title: '支付失败', icon: 'none' });
|
||||
}
|
||||
});
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
uni.requestPayment({
|
||||
timeStamp: payConfig.timestamp,
|
||||
nonceStr: payConfig.nonce_str,
|
||||
package: payConfig.package,
|
||||
signType: payConfig.sign_type,
|
||||
paySign: payConfig.pay_sign,
|
||||
success: (res) => {
|
||||
const successMsg = isForceRecharge ? '充值成功,套餐将自动购买' : '支付成功';
|
||||
uni.showToast({ title: successMsg, icon: 'success', duration: 2000 });
|
||||
setTimeout(() => {
|
||||
loadWalletBalance();
|
||||
}, 1500);
|
||||
},
|
||||
fail: (err) => {
|
||||
if (err.errMsg && (err.errMsg.includes('cancel') || err.errMsg.includes('用户取消'))) {
|
||||
uni.showToast({ title: '已取消支付', icon: 'none' });
|
||||
} else {
|
||||
uni.showToast({ title: '支付失败', icon: 'none' });
|
||||
}
|
||||
}
|
||||
});
|
||||
// #endif
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
BIN
static/clear.png
Normal file
BIN
static/clear.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
BIN
static/left.png
Normal file
BIN
static/left.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
BIN
static/scan-code.png
Normal file
BIN
static/scan-code.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
@@ -36,7 +36,7 @@ export const useUserStore = () => {
|
||||
};
|
||||
|
||||
const setUserInfo = (userInfo) => {
|
||||
state.userInfo = userInfo;
|
||||
Object.assign(state.userInfo, userInfo);
|
||||
uni.setStorageSync('userInfo', userInfo);
|
||||
};
|
||||
|
||||
|
||||
@@ -3,5 +3,3 @@ export const BASE_URL = 'https://cmp-api.boss160.cn';
|
||||
export const APP_TYPE = 'official_account';
|
||||
|
||||
export const APP_ID = 'wx404dc822ce15c5f0';
|
||||
|
||||
export const USE_WECHAT_AUTH = true;
|
||||
96
utils/payment.js
Normal file
96
utils/payment.js
Normal file
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* 微信H5支付工具
|
||||
* 统一处理微信JSAPI支付逻辑
|
||||
*/
|
||||
|
||||
/**
|
||||
* 调起微信H5支付
|
||||
* @param {Object} payConfig - 支付配置对象
|
||||
* @param {string} payConfig.app_id - 微信公众号appid
|
||||
* @param {string} payConfig.timestamp - 时间戳
|
||||
* @param {string} payConfig.nonce_str - 随机字符串
|
||||
* @param {string} payConfig.package - 预支付交易会话标识
|
||||
* @param {string} payConfig.sign_type - 签名方式
|
||||
* @param {string} payConfig.pay_sign - 签名
|
||||
* @returns {Promise} 支付结果
|
||||
*/
|
||||
export function wechatH5Pay(payConfig) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 验证支付配置
|
||||
if (!payConfig || !payConfig.package) {
|
||||
reject(new Error('支付参数无效'));
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证 package 格式
|
||||
if (!payConfig.package.startsWith('prepay_id=')) {
|
||||
reject(new Error('支付参数无效'));
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否在微信环境中
|
||||
if (typeof WeixinJSBridge === 'undefined') {
|
||||
reject(new Error('请在微信中打开'));
|
||||
return;
|
||||
}
|
||||
|
||||
// 调起微信支付
|
||||
WeixinJSBridge.invoke('getBrandWCPayRequest', {
|
||||
appId: payConfig.app_id,
|
||||
timeStamp: payConfig.timestamp,
|
||||
nonceStr: payConfig.nonce_str,
|
||||
package: payConfig.package,
|
||||
signType: payConfig.sign_type,
|
||||
paySign: payConfig.pay_sign
|
||||
}, function(res) {
|
||||
if (res.err_msg === 'get_brand_wcpay_request:ok') {
|
||||
// 支付成功
|
||||
resolve({
|
||||
success: true,
|
||||
message: '支付成功'
|
||||
});
|
||||
} else if (res.err_msg === 'get_brand_wcpay_request:cancel') {
|
||||
// 用户取消支付
|
||||
reject({
|
||||
cancelled: true,
|
||||
message: '已取消支付'
|
||||
});
|
||||
} else {
|
||||
// 支付失败
|
||||
reject({
|
||||
success: false,
|
||||
message: '支付失败',
|
||||
error: res
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示支付结果提示
|
||||
* @param {boolean} success - 是否成功
|
||||
* @param {string} message - 提示消息
|
||||
*/
|
||||
export function showPaymentToast(success, message) {
|
||||
uni.showToast({
|
||||
title: message || (success ? '支付成功' : '支付失败'),
|
||||
icon: success ? 'success' : 'none',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理支付错误
|
||||
* @param {Error|Object} error - 错误对象
|
||||
*/
|
||||
export function handlePaymentError(error) {
|
||||
if (error.cancelled) {
|
||||
// 用户主动取消,提示取消
|
||||
showPaymentToast(false, '已取消支付');
|
||||
} else {
|
||||
// 其他错误
|
||||
const message = error.message || '支付失败';
|
||||
showPaymentToast(false, message);
|
||||
}
|
||||
}
|
||||
@@ -34,11 +34,6 @@ const request = (options) => {
|
||||
if (code === 0 || code === 200) {
|
||||
resolve(data);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: msg || '请求失败',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
reject(res.data);
|
||||
}
|
||||
},
|
||||
|
||||
66
utils/wxsdk.js
Normal file
66
utils/wxsdk.js
Normal file
@@ -0,0 +1,66 @@
|
||||
// utils/wxsdk.js —— 封装微信扫一扫 不用旧的了
|
||||
import { wechatApi } from '@/api/index.js'
|
||||
|
||||
const wx = window.jWeixin
|
||||
|
||||
/**
|
||||
* 初始化微信 JS-SDK
|
||||
*/
|
||||
export async function initWxConfig() {
|
||||
const url = window.location.href.split('#')[0]
|
||||
console.log(url);
|
||||
try {
|
||||
const data = await wechatApi.getJssdkConfig(url)
|
||||
const { app_id, timestamp, nonce_str, signature } = data
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.config({
|
||||
debug: false,
|
||||
appId: app_id,
|
||||
timestamp: timestamp,
|
||||
nonceStr: nonce_str,
|
||||
signature: signature,
|
||||
jsApiList: ['scanQRCode']
|
||||
})
|
||||
wx.ready(() => {
|
||||
console.log('微信 SDK 就绪')
|
||||
resolve()
|
||||
})
|
||||
wx.error((err) => {
|
||||
console.error('wx.config 失败', err)
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('获取微信签名失败', e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调起微信扫一扫
|
||||
* @returns {Promise<string>} 扫码结果字符串
|
||||
*/
|
||||
export function wxScan() {
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.scanQRCode({
|
||||
needResult: 1,
|
||||
scanType: ['qrCode', 'barCode'],
|
||||
success(res) {
|
||||
console.log('扫码成功:', res)
|
||||
resolve(res.resultStr)
|
||||
},
|
||||
fail(err) {
|
||||
console.error('扫码失败:', err)
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否在微信环境中
|
||||
*/
|
||||
export function isInWechat() {
|
||||
return /micromessenger/i.test(navigator.userAgent)
|
||||
}
|
||||
@@ -3,4 +3,12 @@ import uni from '@dcloudio/vite-plugin-uni'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [uni()],
|
||||
})
|
||||
root: process.cwd(),
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
silenceDeprecations: ['legacy-js-api', 'import']
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user