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": {
|
"permissions": {
|
||||||
"allow": [
|
"allow": [
|
||||||
"Bash(git add:*)"
|
"Bash(git add:*)",
|
||||||
|
"Bash(npm list:*)"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"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>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "uview-plus/index.scss";
|
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
/* 主色 */
|
/* 主色 */
|
||||||
--primary: #0A84FF;
|
--primary: #0A84FF;
|
||||||
@@ -41,7 +39,7 @@
|
|||||||
|
|
||||||
/* 背景色 */
|
/* 背景色 */
|
||||||
--bg-primary: #FFFFFF;
|
--bg-primary: #FFFFFF;
|
||||||
--bg-secondary: #F2F2F7;
|
--bg-secondary: #F8F8F8;
|
||||||
--bg-tertiary: #FFFFFF;
|
--bg-tertiary: #FFFFFF;
|
||||||
--bg-grouped: #F2F2F7;
|
--bg-grouped: #F2F2F7;
|
||||||
|
|
||||||
@@ -95,7 +93,8 @@
|
|||||||
|
|
||||||
page {
|
page {
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
background-color: var(--bg-secondary);
|
background: var(--bg-secondary);
|
||||||
|
// min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
@@ -105,6 +104,12 @@
|
|||||||
padding: 30rpx 20rpx;
|
padding: 30rpx 20rpx;
|
||||||
max-width: 750rpx;
|
max-width: 750rpx;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-20{
|
||||||
|
padding: 20rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
|
|||||||
11
Dockerfile
11
Dockerfile
@@ -18,9 +18,20 @@ COPY package.json ./
|
|||||||
# 安装所有依赖
|
# 安装所有依赖
|
||||||
RUN npm install --legacy-peer-deps
|
RUN npm install --legacy-peer-deps
|
||||||
|
|
||||||
|
# 安装 git (用于获取 commit 信息)
|
||||||
|
RUN apk add --no-cache git
|
||||||
|
|
||||||
# 复制源代码
|
# 复制源代码
|
||||||
COPY . .
|
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 生产版本
|
# 构建 H5 生产版本
|
||||||
RUN npm run build:h5
|
RUN npm run build:h5
|
||||||
|
|
||||||
|
|||||||
@@ -4,4 +4,5 @@ export { deviceApi } from './modules/device.js';
|
|||||||
export { exchangeApi } from './modules/exchange.js';
|
export { exchangeApi } from './modules/exchange.js';
|
||||||
export { orderApi } from './modules/order.js';
|
export { orderApi } from './modules/order.js';
|
||||||
export { realnameApi } from './modules/realname.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 device-status-card">
|
||||||
<view class="card-header mb-md flex-row-sb">
|
<view class="card-header mb-md flex-row-sb">
|
||||||
<view class="title">设备信息</view>
|
<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>
|
||||||
<view class="device-info flex-col-g16">
|
<view class="device-info flex-col-g16">
|
||||||
<view class="info-row flex-row-sb">
|
<view class="info-row flex-row-sb">
|
||||||
@@ -17,7 +17,8 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="info-row flex-row-sb">
|
<view class="info-row flex-row-sb">
|
||||||
<view class="info-label">
|
<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>
|
||||||
<view class="info-values flex-row-g20">
|
<view class="info-values flex-row-g20">
|
||||||
<view class="tag-apple tag-primary">{{ statusText }}</view>
|
<view class="tag-apple tag-primary">{{ statusText }}</view>
|
||||||
@@ -47,15 +48,12 @@
|
|||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
deviceInfo: { type: Object, default: () => ({}) },
|
deviceInfo: { type: Object, default: () => ({}) },
|
||||||
isRealName: { type: Boolean, default: false },
|
isRealName: { type: Boolean, default: false },
|
||||||
|
isDevice: { type: Boolean, default: true },
|
||||||
opratorList: { type: Array, default: () => [] }
|
opratorList: { type: Array, default: () => [] }
|
||||||
});
|
});
|
||||||
|
|
||||||
defineEmits(['authentication']);
|
defineEmits(['authentication']);
|
||||||
|
|
||||||
const getOperator = (iccid) => {
|
|
||||||
return '中国电信';
|
|
||||||
};
|
|
||||||
|
|
||||||
const getSignalText = (rssi) => rssi || '强';
|
const getSignalText = (rssi) => rssi || '强';
|
||||||
|
|
||||||
const statusText = computed(() => {
|
const statusText = computed(() => {
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
<view class="function-item interactive card-interactive" @tap="$emit('enter', 'asset-package')"
|
<view class="function-item interactive card-interactive" @tap="$emit('enter', 'asset-package')"
|
||||||
role="button" tabindex="0">
|
role="button" tabindex="0">
|
||||||
<view class="function-icon">
|
<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>
|
||||||
<view class="function-name">资产套餐历史</view>
|
<view class="function-name">资产套餐历史</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -61,8 +61,8 @@
|
|||||||
<view class="function-icon">
|
<view class="function-icon">
|
||||||
<image src="/static/bind-phone.png" mode="aspectFit" alt="绑定手机号"></image>
|
<image src="/static/bind-phone.png" mode="aspectFit" alt="绑定手机号"></image>
|
||||||
</view>
|
</view>
|
||||||
<view :class="['function-name', alreadyBindPhone ? 'text-primary' : '']">
|
<view :class="['function-name', alreadyBindPhone ? 'text-primary' : 'text-danger']">
|
||||||
{{ alreadyBindPhone ? '已绑定' : '绑定手机号' }}
|
{{ alreadyBindPhone ? '已绑定' : '未绑定' }}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="function-item interactive card-interactive" @tap="$emit('enter', 'change-phone')" role="button"
|
<view class="function-item interactive card-interactive" @tap="$emit('enter', 'change-phone')" role="button"
|
||||||
|
|||||||
@@ -108,17 +108,13 @@
|
|||||||
// 只有一个生效中的套餐
|
// 只有一个生效中的套餐
|
||||||
const activePackage = data.items[0];
|
const activePackage = data.items[0];
|
||||||
|
|
||||||
if (activePackage.enable_virtual_data) {
|
if (activePackage.enable_virtual_data) {
|
||||||
// 启用虚流量
|
|
||||||
usedMB.value = activePackage.virtual_used_mb || 0;
|
usedMB.value = activePackage.virtual_used_mb || 0;
|
||||||
totalMB.value = activePackage.virtual_limit_mb || 0;
|
|
||||||
remainMB.value = activePackage.virtual_remain_mb || 0;
|
|
||||||
} else {
|
} else {
|
||||||
// 未启用虚流量,使用真流量
|
|
||||||
usedMB.value = activePackage.data_usage_mb || 0;
|
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);
|
emit('packageLoaded', activePackage);
|
||||||
|
|||||||
@@ -8,8 +8,7 @@
|
|||||||
<view class="flex-row-g20">
|
<view class="flex-row-g20">
|
||||||
<view class="title">{{ currentCardNo || '-' }}</view>
|
<view class="title">{{ currentCardNo || '-' }}</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="caption" v-if="isDevice">IMEI:{{ deviceInfo.imei || '-' }}</view>
|
<view class="caption">套餐到期时间:{{ deviceInfo.expireDate || '-' }}</view>
|
||||||
<view class="caption" v-else>手机号:{{ deviceInfo.bound_phone || '-' }}</view>
|
|
||||||
</view>
|
</view>
|
||||||
<view v-if="isDevice" class="tag-apple" :class="onlineStatus === '在线' ? 'tag-success' : 'tag-warning'">
|
<view v-if="isDevice" class="tag-apple" :class="onlineStatus === '在线' ? 'tag-success' : 'tag-warning'">
|
||||||
{{ onlineStatus }}
|
{{ onlineStatus }}
|
||||||
|
|||||||
@@ -44,4 +44,8 @@
|
|||||||
.wifi-details { flex: 1; }
|
.wifi-details { flex: 1; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-group{
|
||||||
|
padding-right: var(--space-md);
|
||||||
|
}
|
||||||
</style>
|
</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" />
|
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" />
|
||||||
<title></title>
|
<title></title>
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/misans@4.1.0/lib/Normal/MiSans-Regular.min.css"/>
|
<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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"><!--app-html--></div>
|
<div id="app"><!--app-html--></div>
|
||||||
|
|||||||
15
main.js
15
main.js
@@ -16,7 +16,20 @@ app.$mount()
|
|||||||
import { createSSRApp } from 'vue'
|
import { createSSRApp } from 'vue'
|
||||||
export function createApp() {
|
export function createApp() {
|
||||||
const app = createSSRApp(App)
|
const app = createSSRApp(App)
|
||||||
app.use(uviewPlus)
|
// 使用 uview-plus (版本 3.4.0+)
|
||||||
|
app.use(uviewPlus, () => {
|
||||||
|
return {
|
||||||
|
options: {
|
||||||
|
config: {
|
||||||
|
// 默认单位 rpx
|
||||||
|
unit: 'rpx'
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 可以在这里配置组件默认属性
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
return {
|
return {
|
||||||
app
|
app
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,10 @@
|
|||||||
"delay" : 0
|
"delay" : 0
|
||||||
},
|
},
|
||||||
/* 模块配置 */
|
/* 模块配置 */
|
||||||
"modules" : {},
|
"modules" : {
|
||||||
|
"Barcode" : {},
|
||||||
|
"Camera" : {}
|
||||||
|
},
|
||||||
/* 应用发布信息 */
|
/* 应用发布信息 */
|
||||||
"distribute" : {
|
"distribute" : {
|
||||||
/* android打包配置 */
|
/* 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": {
|
"scripts": {
|
||||||
"dev:h5": "uni",
|
"dev:h5": "uni",
|
||||||
"build:h5": "UNI_INPUT_DIR=. uni build -p h5"
|
"build:h5": "cross-env UNI_INPUT_DIR=. uni build -p h5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@dcloudio/types": "3.4.19",
|
"@dcloudio/types": "3.4.19",
|
||||||
"@dcloudio/uni-cli-shared": "3.0.0-4080720251210001",
|
"@dcloudio/uni-cli-shared": "3.0.0-4080720251210001",
|
||||||
"@dcloudio/uni-stacktracey": "3.0.0-4080720251210001",
|
"@dcloudio/uni-stacktracey": "3.0.0-4080720251210001",
|
||||||
"@dcloudio/vite-plugin-uni": "3.0.0-4080720251210001",
|
"@dcloudio/vite-plugin-uni": "3.0.0-4080720251210001",
|
||||||
|
"cross-env": "^10.1.0",
|
||||||
"less": "^4.0.0",
|
"less": "^4.0.0",
|
||||||
"sass": "^1.63.2",
|
"sass": "^1.63.2",
|
||||||
"vite": "5.2.8"
|
"vite": "5.2.8"
|
||||||
@@ -16,9 +17,10 @@
|
|||||||
"@dcloudio/uni-app": "3.0.0-4080720251210001",
|
"@dcloudio/uni-app": "3.0.0-4080720251210001",
|
||||||
"@dcloudio/uni-components": "3.0.0-4080720251210001",
|
"@dcloudio/uni-components": "3.0.0-4080720251210001",
|
||||||
"@dcloudio/uni-h5": "3.0.0-4080720251210001",
|
"@dcloudio/uni-h5": "3.0.0-4080720251210001",
|
||||||
"vue": "^3.4.0",
|
|
||||||
"clipboard": "^2.0.11",
|
"clipboard": "^2.0.11",
|
||||||
"dayjs": "^1.11.19",
|
"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": {
|
"globalStyle": {
|
||||||
"navigationBarTextStyle": "black",
|
"navigationBarTextStyle": "black",
|
||||||
"navigationBarTitleText": "信息查询",
|
"navigationBarTitleText": "设备卡信息查询",
|
||||||
"navigationBarBackgroundColor": "#F8F8F8",
|
"navigationBarBackgroundColor": "#FFFFFF",
|
||||||
"backgroundColor": "#F8F8F8"
|
"backgroundColor": "#F8F8F8"
|
||||||
},
|
},
|
||||||
"uniIdRouter": {}
|
"uniIdRouter": {}
|
||||||
|
|||||||
@@ -15,30 +15,30 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="package-info">
|
<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-label">套餐类型</view>
|
||||||
<view class="info-value">{{ item.package_type === 'formal' ? '普通套餐' : '加油包' }}</view>
|
<view class="info-value">{{ item.package_type === 'formal' ? '普通套餐' : '加油包' }}</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="info-row flex-row-sb">
|
<view class="info-row flex-row-sb">
|
||||||
<view class="info-label">使用类型</view>
|
<view class="info-label">使用类型</view>
|
||||||
<view class="info-value">{{ item.usage_type === 'single_card' ? '单卡' : '设备' }}</view>
|
<view class="info-value">{{ item.usage_type === 'single_card' ? '单卡' : '设备' }}</view>
|
||||||
</view>
|
</view> -->
|
||||||
<view class="info-row flex-row-sb">
|
<view class="info-row flex-row-sb">
|
||||||
<view class="info-label">激活时间</view>
|
<view class="info-label">激活时间</view>
|
||||||
<view class="info-value">{{ item.activated_at || '-' }}</view>
|
<view class="info-value">{{ item.activated_at || '-' }}</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="info-row flex-row-sb">
|
<!-- <view class="info-row flex-row-sb">
|
||||||
<view class="info-label">创建时间</view>
|
<view class="info-label">创建时间</view>
|
||||||
<view class="info-value">{{ item.created_at }}</view>
|
<view class="info-value">{{ item.created_at }}</view>
|
||||||
</view>
|
</view> -->
|
||||||
<view class="info-row flex-row-sb">
|
<view class="info-row flex-row-sb">
|
||||||
<view class="info-label">到期时间</view>
|
<view class="info-label">到期时间</view>
|
||||||
<view class="info-value">{{ item.expires_at || '-' }}</view>
|
<view class="info-value">{{ item.expires_at || '-' }}</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="info-row flex-row-sb">
|
<!-- <view class="info-row flex-row-sb">
|
||||||
<view class="info-label">优先级</view>
|
<view class="info-label">优先级</view>
|
||||||
<view class="info-value">{{ item.priority }}</view>
|
<view class="info-value">{{ item.priority }}</view>
|
||||||
</view>
|
</view> -->
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="divider"></view>
|
<view class="divider"></view>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<!-- 自定义导航栏 -->
|
<!-- 自定义导航栏 -->
|
||||||
<view v-if="!fromLogin" class="custom-navbar">
|
<view v-if="!fromLogin" class="custom-navbar">
|
||||||
<view class="navbar-back" @tap="goBack">
|
<view class="navbar-back" @tap="goBack">
|
||||||
<text class="back-text">🔙</text>
|
<image class="back-icon" src="../../static/left.png" mode="aspectFit" />
|
||||||
</view>
|
</view>
|
||||||
<view class="navbar-title">绑定手机号</view>
|
<view class="navbar-title">绑定手机号</view>
|
||||||
<view class="navbar-placeholder"></view>
|
<view class="navbar-placeholder"></view>
|
||||||
@@ -129,7 +129,9 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const goBack = () => {
|
const goBack = () => {
|
||||||
uni.navigateBack();
|
uni.navigateTo({
|
||||||
|
url: '/pages/index/index'
|
||||||
|
})
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -137,6 +139,10 @@
|
|||||||
.page {
|
.page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: var(--bg-secondary);
|
background: var(--bg-secondary);
|
||||||
|
max-width: 750rpx;
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-navbar {
|
.custom-navbar {
|
||||||
@@ -160,8 +166,8 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
.back-icon {
|
.back-icon {
|
||||||
font-size: 36rpx;
|
width: 40rpx;
|
||||||
color: var(--text-primary);
|
height: 40rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.back-text {
|
.back-text {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
:isDevice="userInfo.isDevice" :networkStatus="deviceInfo.network_status" />
|
:isDevice="userInfo.isDevice" :networkStatus="deviceInfo.network_status" />
|
||||||
|
|
||||||
<DeviceStatusCard v-if="userInfo.isDevice" :deviceInfo="deviceInfo" :isRealName="isRealName"
|
<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"
|
<!-- <VoiceCard v-if="!userInfo.isDevice" :voiceStats="voiceStats" :dateRangeText="dateRangeText"
|
||||||
@openDatePicker="openDateRangePicker" /> -->
|
@openDatePicker="openDateRangePicker" /> -->
|
||||||
@@ -25,11 +25,11 @@
|
|||||||
<view class="flex-col-g20">
|
<view class="flex-col-g20">
|
||||||
<view class="flex-col-g8">
|
<view class="flex-col-g8">
|
||||||
<label class="caption">名称:</label>
|
<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>
|
||||||
<view class="flex-col-g8">
|
<view class="flex-col-g8">
|
||||||
<label class="caption">密码:</label>
|
<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>
|
</view>
|
||||||
<view class="btn-group mt-20">
|
<view class="btn-group mt-20">
|
||||||
@@ -229,9 +229,9 @@
|
|||||||
// 流量信息已由 TrafficCard 组件独立获取,这里不再处理
|
// 流量信息已由 TrafficCard 组件独立获取,这里不再处理
|
||||||
|
|
||||||
// 当前卡信息
|
// 当前卡信息
|
||||||
|
let currentCard = null;
|
||||||
if (data.cards && data.cards.length > 0) {
|
if (data.cards && data.cards.length > 0) {
|
||||||
// 找到当前卡
|
currentCard = data.cards.find(card => card.is_current) || '';
|
||||||
const currentCard = data.cards.find(card => card.is_current) || data.cards[0];
|
|
||||||
deviceInfo.currentIccid = currentCard.iccid || '-';
|
deviceInfo.currentIccid = currentCard.iccid || '-';
|
||||||
|
|
||||||
// 卡列表
|
// 卡列表
|
||||||
@@ -245,6 +245,15 @@
|
|||||||
deviceInfo.mchList = [];
|
deviceInfo.mchList = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 运营商信息
|
||||||
|
const carrierTypeMap = {
|
||||||
|
CMCC: '中国移动',
|
||||||
|
CUCC: '中国联通',
|
||||||
|
CTCC: '中国电信',
|
||||||
|
CBN: '中国广电'
|
||||||
|
};
|
||||||
|
deviceInfo.carrier_name = (currentCard && carrierTypeMap[currentCard.carrier_type]) || '-';
|
||||||
|
|
||||||
// 设备实时信息
|
// 设备实时信息
|
||||||
if (data.device_realtime) {
|
if (data.device_realtime) {
|
||||||
const rt = data.device_realtime;
|
const rt = data.device_realtime;
|
||||||
@@ -379,15 +388,27 @@
|
|||||||
// #endif
|
// #endif
|
||||||
};
|
};
|
||||||
|
|
||||||
const enterBack = () => {
|
const enterBack = async () => {
|
||||||
if (!deviceInfo.lan_ip || deviceInfo.lan_ip === '-') {
|
if (!deviceInfo.lan_ip || deviceInfo.lan_ip === '-') {
|
||||||
uni.showToast({
|
uni.showModal({
|
||||||
title: '暂无后台地址',
|
title: '提示',
|
||||||
icon: 'none'
|
content: '请将设备开机后, 等待2分钟左右进行连接WiFi',
|
||||||
|
showCancel: false,
|
||||||
|
buttonText: '我知道了'
|
||||||
});
|
});
|
||||||
return;
|
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}`;
|
const url = deviceInfo.lan_ip.startsWith('http') ? deviceInfo.lan_ip : `http://${deviceInfo.lan_ip}`;
|
||||||
|
|
||||||
@@ -593,52 +614,55 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uni.showLoading({
|
uni.showModal({
|
||||||
title: '同步中...',
|
title: '确认同步',
|
||||||
mask: true
|
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) => {
|
const enterDetail = (name) => {
|
||||||
@@ -712,6 +736,11 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
const token = uni.getStorageSync('token');
|
||||||
|
if (!token) {
|
||||||
|
uni.reLaunch({ url: '/pages/login/login' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
initCurrentMonth();
|
initCurrentMonth();
|
||||||
loadAssetInfo();
|
loadAssetInfo();
|
||||||
});
|
});
|
||||||
@@ -727,6 +756,12 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wifi-input {
|
||||||
|
border: 2rpx solid var(--border-light);
|
||||||
|
border-radius: 8rpx;
|
||||||
|
padding: 8rpx 16rpx;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-spacer {
|
.bottom-spacer {
|
||||||
|
|||||||
@@ -1,63 +1,64 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="login-page">
|
<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-wrapper">
|
||||||
<view class="content">
|
<view class="content">
|
||||||
<view class="header">
|
<view class="header">
|
||||||
<view class="main-title">登录</view>
|
<view class="main-title">欢迎登录</view>
|
||||||
<view class="sub-title">请输入标识进行登录</view>
|
<view class="sub-title">请输入标识进行登录</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="form-section">
|
<view class="form-section">
|
||||||
<view class="input-wrap" :class="{ focus: inputFocus }">
|
<view class="input-wrap" :class="{ focus: inputFocus }">
|
||||||
<input
|
<input
|
||||||
v-model="identifier"
|
v-model="identifier"
|
||||||
class="input"
|
class="input"
|
||||||
placeholder="请输入标识"
|
placeholder="请输入标识"
|
||||||
placeholder-class="placeholder"
|
placeholder-class="placeholder"
|
||||||
@focus="inputFocus = true"
|
@focus="inputFocus = true"
|
||||||
@blur="inputFocus = false"
|
@blur="handleBlur"
|
||||||
@input="showError = false"
|
@input="showError = false"
|
||||||
/>
|
/>
|
||||||
</view>
|
<image
|
||||||
<text v-if="showError" class="error">请输入标识</text>
|
v-if="identifier && inputFocus"
|
||||||
</view>
|
class="clear-icon"
|
||||||
|
src="/static/clear.png"
|
||||||
<view class="btn-section">
|
mode="aspectFit"
|
||||||
<button
|
@mousedown.prevent="clearInput"
|
||||||
class="btn-login"
|
@touchstart.prevent="clearInput"
|
||||||
:class="{ disabled: !identifier || loading || !agreed }"
|
></image>
|
||||||
:disabled="!identifier || loading || !agreed"
|
</view>
|
||||||
@tap="handleLogin"
|
<text v-if="showError" class="error">请输入标识</text>
|
||||||
>
|
</view>
|
||||||
<view v-if="loading" class="loading">
|
|
||||||
<view class="dot"/>
|
<view class="btn-section">
|
||||||
<view class="dot"/>
|
<button
|
||||||
<view class="dot"/>
|
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>
|
</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>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -67,10 +68,32 @@
|
|||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
import { authApi } from '@/api/index.js';
|
import { authApi } from '@/api/index.js';
|
||||||
import { useUserStore } from '@/store/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();
|
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 identifier = ref('');
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const agreed = ref(true);
|
const agreed = ref(true);
|
||||||
@@ -88,11 +111,23 @@
|
|||||||
|
|
||||||
const redirectToWxAuth = (assetToken) => {
|
const redirectToWxAuth = (assetToken) => {
|
||||||
sessionStorage.setItem('assetToken', 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`;
|
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;
|
window.location.href = url;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const clearInput = () => {
|
||||||
|
identifier.value = '';
|
||||||
|
showError.value = false;
|
||||||
|
inputFocus.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBlur = () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
inputFocus.value = false;
|
||||||
|
}, 200);
|
||||||
|
};
|
||||||
|
|
||||||
const handleLogin = () => {
|
const handleLogin = () => {
|
||||||
if (!identifier.value) {
|
if (!identifier.value) {
|
||||||
showError.value = true;
|
showError.value = true;
|
||||||
@@ -101,6 +136,57 @@
|
|||||||
doLogin();
|
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 () => {
|
const doLogin = async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
@@ -108,20 +194,6 @@
|
|||||||
userStore.setAssetToken(verifyData.asset_token);
|
userStore.setAssetToken(verifyData.asset_token);
|
||||||
userStore.setIdentifier(identifier.value);
|
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);
|
redirectToWxAuth(verifyData.asset_token);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('登录失败', e);
|
console.error('登录失败', e);
|
||||||
@@ -130,7 +202,6 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleWechatCallback = async () => {
|
const handleWechatCallback = async () => {
|
||||||
if (!USE_WECHAT_AUTH) return;
|
|
||||||
|
|
||||||
const code = getCode();
|
const code = getCode();
|
||||||
const assetToken = sessionStorage.getItem('assetToken');
|
const assetToken = sessionStorage.getItem('assetToken');
|
||||||
@@ -145,44 +216,18 @@
|
|||||||
userStore.setToken(loginData.token);
|
userStore.setToken(loginData.token);
|
||||||
uni.setStorageSync('identifier', userStore.state.identifier);
|
uni.setStorageSync('identifier', userStore.state.identifier);
|
||||||
|
|
||||||
// H5 环境获取微信用户信息
|
|
||||||
// #ifdef H5
|
// #ifdef H5
|
||||||
try {
|
if (loginData.user_info) {
|
||||||
// 通过 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: '用于完善用户资料'
|
|
||||||
});
|
|
||||||
userStore.setUserInfo({
|
userStore.setUserInfo({
|
||||||
avatar: userInfo.avatarUrl || '',
|
avatar: loginData.user_info.avatar || '',
|
||||||
nickname: userInfo.nickName || ''
|
nickname: loginData.user_info.nickname || ''
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
console.log('用户取消授权');
|
|
||||||
}
|
}
|
||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
// 判断是否需要绑定手机号
|
|
||||||
if (loginData.need_bind_phone) {
|
if (loginData.need_bind_phone) {
|
||||||
// 需要绑定手机号,跳转到绑定页面
|
|
||||||
uni.redirectTo({ url: '/pages/bind/bind?fromLogin=true' });
|
uni.redirectTo({ url: '/pages/bind/bind?fromLogin=true' });
|
||||||
} else {
|
} else {
|
||||||
// 不需要绑定,直接进入首页
|
|
||||||
uni.redirectTo({ url: '/pages/index/index' });
|
uni.redirectTo({ url: '/pages/index/index' });
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -190,343 +235,172 @@
|
|||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
handleWechatCallback();
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.login-page {
|
.login-page {
|
||||||
position: relative;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
align-items: center;
|
||||||
background: linear-gradient(135deg, #005cbf 0%, #007aff 50%, #4da6ff 100%);
|
justify-content: center;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
background: var(--bg-secondary);
|
||||||
|
max-width: 750rpx;
|
||||||
.overlay {
|
margin: 0 auto;
|
||||||
position: absolute;
|
width: 100%;
|
||||||
top: 0;
|
padding: 40rpx;
|
||||||
left: 0;
|
box-sizing: border-box;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-wrapper {
|
.content-wrapper {
|
||||||
position: relative;
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
border-radius: var(--radius-large);
|
||||||
padding: 60rpx 40rpx;
|
padding: 60rpx 40rpx;
|
||||||
z-index: 1;
|
box-shadow: var(--shadow-medium);
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
margin-bottom: 80rpx;
|
text-align: center;
|
||||||
|
margin-bottom: 60rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-title {
|
.main-title {
|
||||||
font-size: 56rpx;
|
font-size: 48rpx;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #FFFFFF;
|
color: var(--text-primary);
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
margin-bottom: 24rpx;
|
margin-bottom: 16rpx;
|
||||||
letter-spacing: 0.5rpx;
|
letter-spacing: -0.5rpx;
|
||||||
text-shadow: 0 2rpx 8rpx rgba(64, 40, 128, 0.3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sub-title {
|
.sub-title {
|
||||||
font-size: 28rpx;
|
font-size: 26rpx;
|
||||||
color: rgba(255, 255, 255, 0.9);
|
color: var(--text-tertiary);
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
letter-spacing: 0.5rpx;
|
|
||||||
text-shadow: 0 1rpx 4rpx rgba(64, 40, 128, 0.2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-section {
|
.form-section {
|
||||||
margin-bottom: 60rpx;
|
margin-bottom: 48rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-wrap {
|
.input-wrap {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: rgba(255, 255, 255, 0.95);
|
gap: 16rpx;
|
||||||
border-radius: 20rpx;
|
background: var(--bg-secondary);
|
||||||
padding: 32rpx 40rpx;
|
border: 2rpx solid var(--border-color);
|
||||||
transition: all 0.3s;
|
border-radius: var(--radius-medium);
|
||||||
box-shadow: 0 4rpx 16rpx rgba(64, 40, 128, 0.1);
|
padding: 28rpx 32rpx;
|
||||||
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-wrap.focus {
|
.input-wrap.focus {
|
||||||
background: rgba(255, 255, 255, 1);
|
background: var(--bg-primary);
|
||||||
box-shadow: 0 4rpx 20rpx rgba(64, 40, 128, 0.2);
|
border-color: var(--primary);
|
||||||
|
box-shadow: 0 0 0 4rpx rgba(10, 132, 255, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.input {
|
.input {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
font-size: 32rpx;
|
font-size: 30rpx;
|
||||||
color: #1A1A1A;
|
color: var(--text-primary);
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.placeholder {
|
.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 {
|
.error {
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
color: #EF4444;
|
color: var(--danger);
|
||||||
margin-top: 16rpx;
|
margin-top: 16rpx;
|
||||||
padding-left: 8rpx;
|
padding-left: 8rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-section {
|
.btn-section {
|
||||||
margin-bottom: 60rpx;
|
margin-bottom: 48rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-login {
|
.btn-login {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 108rpx;
|
height: 96rpx;
|
||||||
background: rgba(255, 255, 255, 0.25);
|
background: var(--primary);
|
||||||
backdrop-filter: blur(10px);
|
border: none;
|
||||||
border: 2rpx solid rgba(255, 255, 255, 0.4);
|
border-radius: var(--radius-medium);
|
||||||
border-radius: 20rpx;
|
font-size: 32rpx;
|
||||||
font-size: 36rpx;
|
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #FFFFFF;
|
color: var(--text-inverse);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s ease;
|
||||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.15);
|
box-shadow: 0 4rpx 12rpx rgba(10, 132, 255, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-login.disabled {
|
.btn-login.disabled {
|
||||||
background: rgba(255, 255, 255, 0.3);
|
background: var(--gray-300);
|
||||||
color: rgba(255, 255, 255, 0.7);
|
color: var(--text-quaternary);
|
||||||
border: 2rpx solid rgba(255, 255, 255, 0.2);
|
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-login:active:not(.disabled) {
|
.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 {
|
.loading {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8rpx;
|
gap: 12rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dot {
|
.dot {
|
||||||
width: 10rpx;
|
width: 12rpx;
|
||||||
height: 10rpx;
|
height: 12rpx;
|
||||||
background: #FFFFFF;
|
background: var(--text-inverse);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
animation: load 1.4s infinite ease-in-out both;
|
animation: load 1.4s infinite ease-in-out both;
|
||||||
}
|
}
|
||||||
@@ -563,14 +437,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.text {
|
.text {
|
||||||
color: rgba(255, 255, 255, 0.8);
|
color: var(--text-tertiary);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link {
|
.link {
|
||||||
color: #FFFFFF;
|
color: var(--primary);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-decoration: underline;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,24 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="container">
|
<view class="container">
|
||||||
|
<view class="pd-20">
|
||||||
<view class="wallet-card">
|
<view class="wallet-card">
|
||||||
<view class="balance-section">
|
<view class="wallet-header">
|
||||||
<view class="balance-header">
|
<view class="header-left">
|
||||||
<view class="balance-label">账户余额(元)</view>
|
<view class="balance-label">账户余额</view>
|
||||||
<view class="recharge-btn-wrapper">
|
<view class="balance-amount">¥{{ formatMoney(walletDetail.balance) }}</view>
|
||||||
<button class="btn-recharge" @tap="showRechargeModal = true">充值</button>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
<view class="balance-amount">{{ formatMoney(walletDetail.balance) }}</view>
|
<button class="btn-recharge" @tap="showRechargeModal = true">充值</button>
|
||||||
</view>
|
</view>
|
||||||
<view class="wallet-footer">
|
<view class="wallet-info">
|
||||||
<view class="footer-item">
|
<view class="info-item">
|
||||||
<view class="footer-label">冻结金额</view>
|
<view class="info-label">冻结金额</view>
|
||||||
<view class="footer-value">{{ formatMoney(walletDetail.frozen_balance) }}</view>
|
<view class="info-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>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -37,7 +31,7 @@
|
|||||||
<swiper class="tab-swiper" :current="currentTab" @change="onSwiperChange" :circular="false"
|
<swiper class="tab-swiper" :current="currentTab" @change="onSwiperChange" :circular="false"
|
||||||
:acceleration="true">
|
:acceleration="true">
|
||||||
<swiper-item class="swiper-item-content">
|
<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 v-if="rechargeList.length === 0 && !rechargeLoading" class="empty-state">
|
||||||
<view class="empty-icon">📋</view>
|
<view class="empty-icon">📋</view>
|
||||||
<view class="empty-title">暂无充值订单</view>
|
<view class="empty-title">暂无充值订单</view>
|
||||||
@@ -77,14 +71,13 @@
|
|||||||
<view class="load-more" v-if="rechargeList.length > 0">
|
<view class="load-more" v-if="rechargeList.length > 0">
|
||||||
<text v-if="rechargeLoading">加载中...</text>
|
<text v-if="rechargeLoading">加载中...</text>
|
||||||
<text v-else-if="rechargeNoMore">— 没有更多了 —</text>
|
<text v-else-if="rechargeNoMore">— 没有更多了 —</text>
|
||||||
<text v-else @tap="loadMoreRecharge">点击加载更多</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
</swiper-item>
|
</swiper-item>
|
||||||
|
|
||||||
<swiper-item class="swiper-item-content">
|
<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 v-if="transactionList.length === 0 && !transactionLoading" class="empty-state">
|
||||||
<view class="empty-icon">💰</view>
|
<view class="empty-icon">💰</view>
|
||||||
<view class="empty-title">暂无钱包流水</view>
|
<view class="empty-title">暂无钱包流水</view>
|
||||||
@@ -122,7 +115,6 @@
|
|||||||
<view class="load-more" v-if="transactionList.length > 0">
|
<view class="load-more" v-if="transactionList.length > 0">
|
||||||
<text v-if="transactionLoading">加载中...</text>
|
<text v-if="transactionLoading">加载中...</text>
|
||||||
<text v-else-if="transactionNoMore">— 没有更多了 —</text>
|
<text v-else-if="transactionNoMore">— 没有更多了 —</text>
|
||||||
<text v-else @tap="loadMoreTransaction">点击加载更多</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
@@ -162,6 +154,7 @@
|
|||||||
</up-popup>
|
</up-popup>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -177,6 +170,7 @@
|
|||||||
import {
|
import {
|
||||||
useUserStore
|
useUserStore
|
||||||
} from '@/store/index.js';
|
} from '@/store/index.js';
|
||||||
|
import { wechatH5Pay, handlePaymentError, showPaymentToast } from '@/utils/payment.js';
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
@@ -278,10 +272,13 @@
|
|||||||
// 自定义金额
|
// 自定义金额
|
||||||
const customAmountNum = parseFloat(customAmount.value);
|
const customAmountNum = parseFloat(customAmount.value);
|
||||||
if (!customAmountNum || customAmountNum < 0.01) {
|
if (!customAmountNum || customAmountNum < 0.01) {
|
||||||
uni.showToast({
|
showRechargeModal.value = false;
|
||||||
title: '请输入正确的充值金额',
|
setTimeout(() => {
|
||||||
icon: 'none'
|
uni.showToast({
|
||||||
});
|
title: '请输入正确的充值金额',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
}, 50);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
amount = Math.round(customAmountNum * 100); // 转换为分
|
amount = Math.round(customAmountNum * 100); // 转换为分
|
||||||
@@ -289,10 +286,13 @@
|
|||||||
// 预设金额
|
// 预设金额
|
||||||
amount = selectedAmount.value;
|
amount = selectedAmount.value;
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({
|
showRechargeModal.value = false;
|
||||||
title: '请选择充值金额',
|
setTimeout(() => {
|
||||||
icon: 'none'
|
uni.showToast({
|
||||||
});
|
title: '请选择充值金额',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
}, 50);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,11 +319,14 @@
|
|||||||
// 检查金额范围
|
// 检查金额范围
|
||||||
if (amount < checkData.min_amount || amount > checkData.max_amount) {
|
if (amount < checkData.min_amount || amount > checkData.max_amount) {
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
uni.showToast({
|
showRechargeModal.value = false;
|
||||||
title: `充值金额范围:¥${(checkData.min_amount / 100).toFixed(2)} - ¥${(checkData.max_amount / 100).toFixed(2)}`,
|
setTimeout(() => {
|
||||||
icon: 'none',
|
uni.showToast({
|
||||||
duration: 2500
|
title: `充值金额范围:¥${(checkData.min_amount / 100).toFixed(2)} - ¥${(checkData.max_amount / 100).toFixed(2)}`,
|
||||||
});
|
icon: 'none',
|
||||||
|
duration: 2500
|
||||||
|
});
|
||||||
|
}, 50);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,103 +342,42 @@
|
|||||||
|
|
||||||
// 验证支付配置
|
// 验证支付配置
|
||||||
if (!rechargeData.pay_config || !rechargeData.pay_config.package) {
|
if (!rechargeData.pay_config || !rechargeData.pay_config.package) {
|
||||||
uni.showToast({
|
setTimeout(() => {
|
||||||
title: '支付参数获取失败',
|
uni.showToast({
|
||||||
icon: 'none'
|
title: '支付参数获取失败',
|
||||||
});
|
icon: 'none'
|
||||||
|
});
|
||||||
|
}, 50);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 第三步:调起微信支付
|
// 第三步:调起微信支付
|
||||||
// #ifdef H5
|
try {
|
||||||
if (typeof WeixinJSBridge === 'undefined') {
|
await wechatH5Pay(rechargeData.pay_config);
|
||||||
uni.showToast({
|
// 支付成功
|
||||||
title: '请在微信中打开',
|
showPaymentToast(true, '充值成功');
|
||||||
icon: 'none'
|
setTimeout(() => {
|
||||||
});
|
loadWalletDetail();
|
||||||
return;
|
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) {
|
} catch (e) {
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
console.error('充值失败', e);
|
console.error('充值失败', e);
|
||||||
const errorMsg = e.msg || e.message || '充值失败,请稍后重试';
|
const errorMsg = e.msg || e.message || '充值失败,请稍后重试';
|
||||||
uni.showToast({
|
showRechargeModal.value = false;
|
||||||
title: errorMsg,
|
setTimeout(() => {
|
||||||
icon: 'none'
|
uni.showToast({
|
||||||
});
|
title: errorMsg,
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
}, 50);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -563,83 +505,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 调起微信支付
|
// 调起微信支付
|
||||||
// #ifdef H5
|
try {
|
||||||
if (typeof WeixinJSBridge === 'undefined') {
|
await wechatH5Pay(rechargeData.pay_config);
|
||||||
uni.showToast({
|
// 支付成功
|
||||||
title: '请在微信中打开',
|
showPaymentToast(true, '充值成功');
|
||||||
icon: 'none'
|
setTimeout(() => {
|
||||||
});
|
loadWalletDetail();
|
||||||
return;
|
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) {
|
} catch (e) {
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
console.error('获取支付配置失败', e);
|
console.error('获取支付配置失败', e);
|
||||||
@@ -714,84 +594,86 @@
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.container {
|
.container {
|
||||||
height: 100vh;
|
min-height: 100vh;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 20rpx;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
max-width: 750rpx;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 !important;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wallet-card {
|
.wallet-card {
|
||||||
background: linear-gradient(135deg, #0A84FF 0%, #5AC8FA 50%, #64D2FF 100%);
|
background: var(--primary);
|
||||||
border-radius: 24rpx;
|
border-radius: var(--radius-large);
|
||||||
padding: 36rpx;
|
padding: 40rpx;
|
||||||
color: #fff;
|
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 {
|
.header-left {
|
||||||
margin-bottom: 28rpx;
|
flex: 1;
|
||||||
|
|
||||||
.balance-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: 8rpx;
|
|
||||||
|
|
||||||
.balance-label {
|
.balance-label {
|
||||||
font-size: 24rpx;
|
font-size: 26rpx;
|
||||||
opacity: 0.75;
|
opacity: 0.85;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.recharge-btn-wrapper {
|
.balance-amount {
|
||||||
.btn-recharge {
|
font-size: 64rpx;
|
||||||
background: #fff;
|
font-weight: 700;
|
||||||
border: none;
|
letter-spacing: -2rpx;
|
||||||
color: var(--primary);
|
line-height: 1;
|
||||||
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 {
|
.btn-recharge {
|
||||||
font-size: 72rpx;
|
background: rgba(255, 255, 255, 0.2);
|
||||||
font-weight: 700;
|
backdrop-filter: blur(10px);
|
||||||
letter-spacing: -2rpx;
|
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 {
|
.wallet-info {
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding-top: 24rpx;
|
padding-top: 24rpx;
|
||||||
border-top: 1rpx solid rgba(255, 255, 255, 0.2);
|
border-top: 1rpx solid rgba(255, 255, 255, 0.2);
|
||||||
|
|
||||||
.footer-divider {
|
.info-item {
|
||||||
width: 1rpx;
|
display: flex;
|
||||||
height: 40rpx;
|
align-items: center;
|
||||||
background: rgba(255, 255, 255, 0.3);
|
justify-content: space-between;
|
||||||
margin: 0 32rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-item {
|
.info-label {
|
||||||
flex: 1;
|
font-size: 24rpx;
|
||||||
|
opacity: 0.8;
|
||||||
.footer-label {
|
font-weight: 500;
|
||||||
font-size: 22rpx;
|
|
||||||
opacity: 0.7;
|
|
||||||
margin-bottom: 6rpx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer-value {
|
.info-value {
|
||||||
font-size: 26rpx;
|
font-size: 28rpx;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -846,17 +728,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tab-swiper {
|
.tab-swiper {
|
||||||
flex: 1;
|
|
||||||
margin-top: 20rpx;
|
margin-top: 20rpx;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
height: calc(100vh - 500rpx);
|
||||||
}
|
}
|
||||||
|
|
||||||
.swiper-item-content {
|
.swiper-item-content {
|
||||||
height: 100%;
|
height: calc(100vh - 500rpx);
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-content {
|
.list-content {
|
||||||
height: 100%;
|
height: calc(100vh - 500rpx);
|
||||||
padding-top: 16rpx;
|
padding-top: 16rpx;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,14 +8,16 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-if="orderList.length === 0 && !loading" class="empty-state">
|
<scroll-view scroll-y class="scroll-container" @scrolltolower="loadMore">
|
||||||
<view class="empty-icon">📋</view>
|
|
||||||
<view class="empty-title">暂无订单</view>
|
|
||||||
<view class="empty-desc">当前账号下暂无订单信息</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<scroll-view v-else scroll-y class="order-scroll" @scrolltolower="loadMore">
|
<view v-if="orderList.length === 0 && !loading" class="empty-state">
|
||||||
<view class="order-card" v-for="(item, index) in orderList" :key="index">
|
<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="card-header">
|
||||||
<view class="header-left">
|
<view class="header-left">
|
||||||
<view class="header-row">
|
<view class="header-row">
|
||||||
@@ -31,16 +33,24 @@
|
|||||||
<view class="card-body">
|
<view class="card-body">
|
||||||
<view class="info-grid">
|
<view class="info-grid">
|
||||||
<view class="info-item">
|
<view class="info-item">
|
||||||
<view class="info-label">订单时间</view>
|
<view class="info-label">套餐名称</view>
|
||||||
<view class="info-value">{{ item.created_at }}</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>
|
||||||
<view class="info-item">
|
<view class="info-item">
|
||||||
<view class="info-label">订单金额</view>
|
<view class="info-label">订单金额</view>
|
||||||
<view class="info-value amount">¥{{ formatMoney(item.total_amount) }}</view>
|
<view class="info-value amount">¥{{ formatMoney(item.total_amount) }}</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="info-item">
|
||||||
|
<view class="info-label">下单时间</view>
|
||||||
|
<view class="info-value">{{ item.created_at }}</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="divider"></view>
|
<!-- <view class="divider"></view>
|
||||||
|
|
||||||
<view class="package-section">
|
<view class="package-section">
|
||||||
<view class="section-label">套餐名称</view>
|
<view class="section-label">套餐名称</view>
|
||||||
@@ -49,7 +59,7 @@
|
|||||||
<text class="package-name">{{ pkgName }}</text>
|
<text class="package-name">{{ pkgName }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view> -->
|
||||||
|
|
||||||
<view v-if="item.payment_status === 1" class="card-footer">
|
<view v-if="item.payment_status === 1" class="card-footer">
|
||||||
<button class="btn-pay" @tap="handleOrderPayment(item)">立即支付</button>
|
<button class="btn-pay" @tap="handleOrderPayment(item)">立即支付</button>
|
||||||
@@ -57,10 +67,11 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="load-more" v-if="orderList.length > 0">
|
<view class="load-more" v-if="orderList.length > 0">
|
||||||
<text v-if="loading">加载中...</text>
|
<text v-if="loading">加载中...</text>
|
||||||
<text v-else-if="noMore">没有更多了</text>
|
<text v-else-if="noMore">没有更多了</text>
|
||||||
<text v-else>上拉加载更多</text>
|
<text v-else>上拉加载更多</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
@@ -78,6 +89,7 @@
|
|||||||
import {
|
import {
|
||||||
useUserStore
|
useUserStore
|
||||||
} from '@/store/index.js';
|
} from '@/store/index.js';
|
||||||
|
import { wechatH5Pay, handlePaymentError, showPaymentToast } from '@/utils/payment.js';
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
@@ -195,81 +207,21 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// #ifdef H5
|
// 调起微信支付
|
||||||
if (typeof WeixinJSBridge === 'undefined') {
|
try {
|
||||||
uni.showToast({
|
await wechatH5Pay(payData.pay_config);
|
||||||
title: '请在微信中打开',
|
// 支付成功
|
||||||
icon: 'none'
|
showPaymentToast(true, '支付成功');
|
||||||
});
|
setTimeout(() => {
|
||||||
return;
|
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) {
|
} catch (e) {
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
console.error('支付失败', e);
|
console.error('支付失败', e);
|
||||||
@@ -288,19 +240,29 @@
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.container {
|
.container {
|
||||||
height: 100vh;
|
position: fixed;
|
||||||
|
top: var(--window-top);
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
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 {
|
.filter-tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
position: sticky;
|
border-radius: 20rpx;
|
||||||
top: 0;
|
z-index: 100;
|
||||||
border-radius: 10rpx;
|
flex-shrink: 0;
|
||||||
z-index: 10;
|
margin-top: 20rpx;
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||||
|
|
||||||
.tab-item {
|
.tab-item {
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -330,8 +292,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-scroll {
|
.scroll-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-state {
|
.empty-state {
|
||||||
|
|||||||
@@ -81,6 +81,7 @@
|
|||||||
import { ref, reactive, onMounted } from 'vue';
|
import { ref, reactive, onMounted } from 'vue';
|
||||||
import { assetApi, orderApi, walletApi } from '@/api/index.js';
|
import { assetApi, orderApi, walletApi } from '@/api/index.js';
|
||||||
import { useUserStore } from '@/store/index.js';
|
import { useUserStore } from '@/store/index.js';
|
||||||
|
import { wechatH5Pay, handlePaymentError, showPaymentToast } from '@/utils/payment.js';
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
@@ -276,63 +277,18 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleWechatPay = async (payConfig, isForceRecharge = false) => {
|
const handleWechatPay = async (payConfig, isForceRecharge = false) => {
|
||||||
// 验证参数
|
try {
|
||||||
if (!payConfig.package || !payConfig.package.startsWith('prepay_id=')) {
|
await wechatH5Pay(payConfig);
|
||||||
uni.showToast({ title: '支付参数无效', icon: 'none' });
|
// 支付成功
|
||||||
return;
|
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(() => {
|
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) => {
|
const setUserInfo = (userInfo) => {
|
||||||
state.userInfo = userInfo;
|
Object.assign(state.userInfo, userInfo);
|
||||||
uni.setStorageSync('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_TYPE = 'official_account';
|
||||||
|
|
||||||
export const APP_ID = 'wx404dc822ce15c5f0';
|
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) {
|
if (code === 0 || code === 200) {
|
||||||
resolve(data);
|
resolve(data);
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({
|
|
||||||
title: msg || '请求失败',
|
|
||||||
icon: 'none',
|
|
||||||
duration: 2000
|
|
||||||
});
|
|
||||||
reject(res.data);
|
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({
|
export default defineConfig({
|
||||||
plugins: [uni()],
|
plugins: [uni()],
|
||||||
})
|
root: process.cwd(),
|
||||||
|
css: {
|
||||||
|
preprocessorOptions: {
|
||||||
|
scss: {
|
||||||
|
silenceDeprecations: ['legacy-js-api', 'import']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user