feat(passport): 优化扫码登录兼容性与确认交互

- 兼容接口中 code 返回 0 和 200 两种成功状态
- 在扫码登录确认接口添加日志输出,调试响应数据
- 重构扫码登录确认页面逻辑,支持主动扫码和URL扫码两种场景
- 兼容多种token参数名,支持URL编码和旧参数解析
- URL扫码场景自动确认登录,未登录用户自动跳转登录页
- 新增主动扫码功能,支持二维码内容多格式解析(URL/JSON/纯token)
- 优化确认登录后页面交互,支持自动返回或提示用户回PC端刷新
- 增加状态视觉反馈,包括加载、成功、失败及初始状态
- 优化UI细节,使用圆角样式及布局调整提升视觉体验
- 新增页面底部帮助提示文字,提升用户指引
- 新增多页面配置,设置导航栏标题及样式统一管理
- 新增应用密钥凭证、应用操作动态、应用成员、应用版本发布等增删改查功能模块及接口定义
- 新增对应页面表单组件,实现应用相关实体的新增和编辑功能
This commit is contained in:
2026-04-07 16:43:41 +08:00
parent dd9c1708fa
commit 8da0108f56
30 changed files with 1724 additions and 66 deletions

View File

@@ -0,0 +1,101 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api/index';
import type { AppCredential, AppCredentialParam } from './model';
/**
* 分页查询应用密钥凭证
*/
export async function pageAppCredential(params: AppCredentialParam) {
const res = await request.get<ApiResult<PageResult<AppCredential>>>(
'/app/app-credential/page',
params
);
if (res.code === 0) {
return res.data;
}
return Promise.reject(new Error(res.message));
}
/**
* 查询应用密钥凭证列表
*/
export async function listAppCredential(params?: AppCredentialParam) {
const res = await request.get<ApiResult<AppCredential[]>>(
'/app/app-credential',
params
);
if (res.code === 0 && res.data) {
return res.data;
}
return Promise.reject(new Error(res.message));
}
/**
* 添加应用密钥凭证
*/
export async function addAppCredential(data: AppCredential) {
const res = await request.post<ApiResult<unknown>>(
'/app/app-credential',
data
);
if (res.code === 0) {
return res.message;
}
return Promise.reject(new Error(res.message));
}
/**
* 修改应用密钥凭证
*/
export async function updateAppCredential(data: AppCredential) {
const res = await request.put<ApiResult<unknown>>(
'/app/app-credential',
data
);
if (res.code === 0) {
return res.message;
}
return Promise.reject(new Error(res.message));
}
/**
* 删除应用密钥凭证
*/
export async function removeAppCredential(id?: number) {
const res = await request.del<ApiResult<unknown>>(
'/app/app-credential/' + id
);
if (res.code === 0) {
return res.message;
}
return Promise.reject(new Error(res.message));
}
/**
* 批量删除应用密钥凭证
*/
export async function removeBatchAppCredential(data: (number | undefined)[]) {
const res = await request.del<ApiResult<unknown>>(
'/app/app-credential/batch',
{
data
}
);
if (res.code === 0) {
return res.message;
}
return Promise.reject(new Error(res.message));
}
/**
* 根据id查询应用密钥凭证
*/
export async function getAppCredential(id: number) {
const res = await request.get<ApiResult<AppCredential>>(
'/app/app-credential/' + id
);
if (res.code === 0 && res.data) {
return res.data;
}
return Promise.reject(new Error(res.message));
}

View File

@@ -0,0 +1,49 @@
import type { PageParam } from '@/api/index';
/**
* 应用密钥凭证
*/
export interface AppCredential {
// 自增ID
id?: string;
// 关联应用ID
websiteId?: string;
// 凭证名称,如生产环境密钥
name?: string;
// App ID公开
appId?: string;
// App Secret加密存储
appSecret?: string;
// 凭证类型: server/client/webhook
type?: string;
// 权限范围,空格分隔
scopes?: string;
// 到期时间NULL=永不过期
expireTime?: string;
// 最后使用时间
lastUsedAt?: string;
//
remark?: string;
// 排序(数字越小越靠前)
sortNumber?: number;
// 状态, 0正常, 1冻结
status?: number;
// 是否删除, 0否, 1是
deleted?: number;
// 用户ID
userId?: number;
// 租户id
tenantId?: number;
// 创建时间
createTime?: string;
// 修改时间
updateTime?: string;
}
/**
* 应用密钥凭证搜索条件
*/
export interface AppCredentialParam extends PageParam {
id?: number;
keywords?: string;
}

View File

@@ -0,0 +1,101 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api/index';
import type { AppEvent, AppEventParam } from './model';
/**
* 分页查询应用操作动态
*/
export async function pageAppEvent(params: AppEventParam) {
const res = await request.get<ApiResult<PageResult<AppEvent>>>(
'/app/app-event/page',
params
);
if (res.code === 0) {
return res.data;
}
return Promise.reject(new Error(res.message));
}
/**
* 查询应用操作动态列表
*/
export async function listAppEvent(params?: AppEventParam) {
const res = await request.get<ApiResult<AppEvent[]>>(
'/app/app-event',
params
);
if (res.code === 0 && res.data) {
return res.data;
}
return Promise.reject(new Error(res.message));
}
/**
* 添加应用操作动态
*/
export async function addAppEvent(data: AppEvent) {
const res = await request.post<ApiResult<unknown>>(
'/app/app-event',
data
);
if (res.code === 0) {
return res.message;
}
return Promise.reject(new Error(res.message));
}
/**
* 修改应用操作动态
*/
export async function updateAppEvent(data: AppEvent) {
const res = await request.put<ApiResult<unknown>>(
'/app/app-event',
data
);
if (res.code === 0) {
return res.message;
}
return Promise.reject(new Error(res.message));
}
/**
* 删除应用操作动态
*/
export async function removeAppEvent(id?: number) {
const res = await request.del<ApiResult<unknown>>(
'/app/app-event/' + id
);
if (res.code === 0) {
return res.message;
}
return Promise.reject(new Error(res.message));
}
/**
* 批量删除应用操作动态
*/
export async function removeBatchAppEvent(data: (number | undefined)[]) {
const res = await request.del<ApiResult<unknown>>(
'/app/app-event/batch',
{
data
}
);
if (res.code === 0) {
return res.message;
}
return Promise.reject(new Error(res.message));
}
/**
* 根据id查询应用操作动态
*/
export async function getAppEvent(id: number) {
const res = await request.get<ApiResult<AppEvent>>(
'/app/app-event/' + id
);
if (res.code === 0 && res.data) {
return res.data;
}
return Promise.reject(new Error(res.message));
}

View File

@@ -0,0 +1,47 @@
import type { PageParam } from '@/api/index';
/**
* 应用操作动态
*/
export interface AppEvent {
// 自增ID
id?: string;
// 关联应用ID
websiteId?: string;
// 事件类型: created/published/updated/domain_bound/member_added/status_changed
eventType?: string;
// 事件标题,如已发布
title?: string;
// 详细描述
content?: string;
// 操作人用户ID
operatorId?: string;
// 操作人名称(冗余)
operator?: string;
// 关联ID如版本ID
refId?: string;
// 关联类型
refType?: string;
// 扩展数据
extra?: string;
// 排序(数字越小越靠前)
sortNumber?: number;
// 状态, 0正常, 1冻结
status?: number;
// 用户ID
userId?: number;
// 租户id
tenantId?: number;
// 创建时间
createTime?: string;
// 修改时间
updateTime?: string;
}
/**
* 应用操作动态搜索条件
*/
export interface AppEventParam extends PageParam {
id?: number;
keywords?: string;
}

View File

@@ -0,0 +1,101 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api/index';
import type { AppUser, AppUserParam } from './model';
/**
* 分页查询应用成员
*/
export async function pageAppUser(params: AppUserParam) {
const res = await request.get<ApiResult<PageResult<AppUser>>>(
'/app/app-user/page',
params
);
if (res.code === 0) {
return res.data;
}
return Promise.reject(new Error(res.message));
}
/**
* 查询应用成员列表
*/
export async function listAppUser(params?: AppUserParam) {
const res = await request.get<ApiResult<AppUser[]>>(
'/app/app-user',
params
);
if (res.code === 0 && res.data) {
return res.data;
}
return Promise.reject(new Error(res.message));
}
/**
* 添加应用成员
*/
export async function addAppUser(data: AppUser) {
const res = await request.post<ApiResult<unknown>>(
'/app/app-user',
data
);
if (res.code === 0) {
return res.message;
}
return Promise.reject(new Error(res.message));
}
/**
* 修改应用成员
*/
export async function updateAppUser(data: AppUser) {
const res = await request.put<ApiResult<unknown>>(
'/app/app-user',
data
);
if (res.code === 0) {
return res.message;
}
return Promise.reject(new Error(res.message));
}
/**
* 删除应用成员
*/
export async function removeAppUser(id?: number) {
const res = await request.del<ApiResult<unknown>>(
'/app/app-user/' + id
);
if (res.code === 0) {
return res.message;
}
return Promise.reject(new Error(res.message));
}
/**
* 批量删除应用成员
*/
export async function removeBatchAppUser(data: (number | undefined)[]) {
const res = await request.del<ApiResult<unknown>>(
'/app/app-user/batch',
{
data
}
);
if (res.code === 0) {
return res.message;
}
return Promise.reject(new Error(res.message));
}
/**
* 根据id查询应用成员
*/
export async function getAppUser(id: number) {
const res = await request.get<ApiResult<AppUser>>(
'/app/app-user/' + id
);
if (res.code === 0 && res.data) {
return res.data;
}
return Promise.reject(new Error(res.message));
}

View File

@@ -0,0 +1,41 @@
import type { PageParam } from '@/api/index';
/**
* 应用成员
*/
export interface AppUser {
// 自增ID
id?: string;
// 关联应用ID
websiteId?: string;
// 用户ID
userId?: string;
// 用户名(冗余)
username?: string;
// 头像(冗余)
avatar?: string;
// 角色: owner/admin/developer/viewer
role?: string;
// 邀请人用户ID
inviteBy?: string;
// 加入时间
inviteTime?: string;
// 排序(数字越小越靠前)
sortNumber?: number;
// 状态, 0正常, 1冻结
status?: number;
// 租户id
tenantId?: number;
// 创建时间
createTime?: string;
// 修改时间
updateTime?: string;
}
/**
* 应用成员搜索条件
*/
export interface AppUserParam extends PageParam {
id?: number;
keywords?: string;
}

View File

@@ -0,0 +1,101 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api/index';
import type { AppVersion, AppVersionParam } from './model';
/**
* 分页查询应用版本发布记录
*/
export async function pageAppVersion(params: AppVersionParam) {
const res = await request.get<ApiResult<PageResult<AppVersion>>>(
'/app/app-version/page',
params
);
if (res.code === 0) {
return res.data;
}
return Promise.reject(new Error(res.message));
}
/**
* 查询应用版本发布记录列表
*/
export async function listAppVersion(params?: AppVersionParam) {
const res = await request.get<ApiResult<AppVersion[]>>(
'/app/app-version',
params
);
if (res.code === 0 && res.data) {
return res.data;
}
return Promise.reject(new Error(res.message));
}
/**
* 添加应用版本发布记录
*/
export async function addAppVersion(data: AppVersion) {
const res = await request.post<ApiResult<unknown>>(
'/app/app-version',
data
);
if (res.code === 0) {
return res.message;
}
return Promise.reject(new Error(res.message));
}
/**
* 修改应用版本发布记录
*/
export async function updateAppVersion(data: AppVersion) {
const res = await request.put<ApiResult<unknown>>(
'/app/app-version',
data
);
if (res.code === 0) {
return res.message;
}
return Promise.reject(new Error(res.message));
}
/**
* 删除应用版本发布记录
*/
export async function removeAppVersion(id?: number) {
const res = await request.del<ApiResult<unknown>>(
'/app/app-version/' + id
);
if (res.code === 0) {
return res.message;
}
return Promise.reject(new Error(res.message));
}
/**
* 批量删除应用版本发布记录
*/
export async function removeBatchAppVersion(data: (number | undefined)[]) {
const res = await request.del<ApiResult<unknown>>(
'/app/app-version/batch',
{
data
}
);
if (res.code === 0) {
return res.message;
}
return Promise.reject(new Error(res.message));
}
/**
* 根据id查询应用版本发布记录
*/
export async function getAppVersion(id: number) {
const res = await request.get<ApiResult<AppVersion>>(
'/app/app-version/' + id
);
if (res.code === 0 && res.data) {
return res.data;
}
return Promise.reject(new Error(res.message));
}

View File

@@ -0,0 +1,53 @@
import type { PageParam } from '@/api/index';
/**
* 应用版本发布记录
*/
export interface AppVersion {
// 自增ID
id?: string;
// 关联应用ID
websiteId?: string;
// 版本号,如 1.0.0
versionNo?: string;
// 版本名称
versionName?: string;
// 版本更新说明
changelog?: string;
// 安装包地址
packageUrl?: string;
// 包大小(字节)
packageSize?: string;
// 包MD5/SHA256
packageHash?: string;
// 环境: development/staging/production
env?: string;
// 状态 0=构建中 1=已发布 2=已回滚 3=构建失败
status?: number;
// 是否为当前版本
isCurrent?: string;
// 发布人用户ID
publishBy?: string;
// 发布时间
publishTime?: string;
// 备注
remark?: string;
// 排序(数字越小越靠前)
sortNumber?: number;
// 用户ID
userId?: number;
// 租户id
tenantId?: number;
// 创建时间
createTime?: string;
// 修改时间
updateTime?: string;
}
/**
* 应用版本发布记录搜索条件
*/
export interface AppVersionParam extends PageParam {
id?: number;
keywords?: string;
}

View File

@@ -105,7 +105,8 @@ export async function generateQRToken(data?: GenerateQRTokenParam) {
...data ...data
} }
); );
if (res.code === 0 && res.data) { // 兼容 code === 0 和 code === 200
if ((res.code === 0 || res.code === 200) && res.data) {
return res.data; return res.data;
} }
return Promise.reject(new Error(res.message)); return Promise.reject(new Error(res.message));
@@ -118,7 +119,8 @@ export async function checkQRLoginStatus(token: string) {
const res = await request.get<ApiResult<QRLoginStatusResult>>( const res = await request.get<ApiResult<QRLoginStatusResult>>(
SERVER_API_URL + `/qr-login/status/${token}` SERVER_API_URL + `/qr-login/status/${token}`
); );
if (res.code === 0 && res.data) { // 兼容 code === 0 和 code === 200
if ((res.code === 0 || res.code === 200) && res.data) {
return res.data; return res.data;
} }
return Promise.reject(new Error(res.message)); return Promise.reject(new Error(res.message));
@@ -126,16 +128,22 @@ export async function checkQRLoginStatus(token: string) {
/** /**
* 确认扫码登录(通用接口) * 确认扫码登录(通用接口)
*
* @description 小程序端确认扫码登录
* @param data - 包含 token 和用户信息
*/ */
export async function confirmQRLogin(data: ConfirmLoginParam) { export async function confirmQRLogin(data: ConfirmLoginParam) {
const res = await request.post<ApiResult<ConfirmLoginResult>>( const res = await request.post<ApiResult<ConfirmLoginResult>>(
SERVER_API_URL + '/qr-login/confirm', SERVER_API_URL + '/qr-login/confirm',
data data
); );
if (res.code === 0 && res.data) { console.log('[QRLogin API] confirmQRLogin 响应:', res);
// 兼容 code === 0 和 code === 200
if ((res.code === 0 || res.code === 200) && res.data) {
return res.data; return res.data;
} }
return Promise.reject(new Error(res.message)); return Promise.reject(new Error(res.message || '登录确认失败'));
} }
/** /**
@@ -162,10 +170,13 @@ export async function confirmWechatQRLogin(token: string, userId: number) {
SERVER_API_URL + '/qr-login/confirm', SERVER_API_URL + '/qr-login/confirm',
data data
); );
if (res.code === 0 && res.data) { console.log('[QRLogin API] confirmWechatQRLogin 响应:', res);
// 兼容 code === 0 和 code === 200
if ((res.code === 0 || res.code === 200) && res.data) {
return res.data; return res.data;
} }
return Promise.reject(new Error(res.message)); return Promise.reject(new Error(res.message || '确认登录失败'));
} catch (error: any) { } catch (error: any) {
return Promise.reject(new Error(error.message || '确认登录失败')); return Promise.reject(new Error(error.message || '确认登录失败'));
} }

View File

@@ -0,0 +1,4 @@
export default definePageConfig({
navigationBarTitleText: '新增应用密钥凭证',
navigationBarTextStyle: 'black'
})

View File

@@ -0,0 +1,98 @@
import {useEffect, useState, useRef} from "react";
import {useRouter} from '@tarojs/taro'
import {Button, Loading, CellGroup, Input, TextArea, Form} from '@nutui/nutui-react-taro'
import Taro from '@tarojs/taro'
import {View} from '@tarojs/components'
import {AppCredential} from "@/api/app/appCredential/model";
import {getAppCredential, listAppCredential, updateAppCredential, addAppCredential} from "@/api/app/appCredential";
const AddAppCredential = () => {
const {params} = useRouter();
const [loading, setLoading] = useState<boolean>(true)
const [FormData, setFormData] = useState<AppCredential>({})
const formRef = useRef<any>(null)
const reload = async () => {
if (params.id) {
const data = await getAppCredential(Number(params.id))
setFormData(data)
} else {
setFormData({})
}
}
// 提交表单
const submitSucceed = async (values: any) => {
try {
if (params.id) {
// 编辑模式
await updateAppCredential({
...values,
id: Number(params.id)
})
} else {
// 新增模式
await addAppCredential(values)
}
Taro.showToast({
title: `操作成功`,
icon: 'success'
})
setTimeout(() => {
return Taro.navigateBack()
}, 1000)
} catch (error) {
Taro.showToast({
title: `操作失败`,
icon: 'error'
});
}
}
const submitFailed = (error: any) => {
console.log(error, 'err...')
}
useEffect(() => {
reload().then(() => {
setLoading(false)
})
}, []);
if (loading) {
return <Loading className={'px-2'}></Loading>
}
return (
<>
<Form
ref={formRef}
divider
initialValues={FormData}
labelPosition="left"
onFinish={(values) => submitSucceed(values)}
onFinishFailed={(errors) => submitFailed(errors)}
footer={
<div
style={{
display: 'flex',
justifyContent: 'center',
width: '100%'
}}
>
<Button
nativeType="submit"
type="success"
size="large"
className={'w-full'}
block
>
{params.id ? '更新' : '保存'}
</Button>
</div>
}
>
<CellGroup style={{padding: '4px 0'}}>
<Form.Item name="websiteId" label="关联应用ID" initialValue={FormData.websiteId} required>

View File

@@ -0,0 +1,4 @@
export default definePageConfig({
navigationBarTitleText: '应用密钥凭证管理',
navigationBarTextStyle: 'black'
})

View File

@@ -0,0 +1,64 @@
import {useState} from "react";
import Taro, {useDidShow} from '@tarojs/taro'
import {Button, Cell, CellGroup, Space, Empty, ConfigProvider, Divider} from '@nutui/nutui-react-taro'
import {Dongdong, ArrowRight, CheckNormal, Checked} from '@nutui/icons-react-taro'
import {View} from '@tarojs/components'
import {AppCredential} from "@/api/app/appCredential/model";
import {listAppCredential, removeAppCredential, updateAppCredential} from "@/api/app/appCredential";
const AppCredentialList = () => {
const [list, setList] = useState<AppCredential[]>([])
const reload = () => {
listAppCredential({
// 添加查询条件
})
.then(data => {
setList(data || [])
})
.catch(() => {
Taro.showToast({
title: '获取数据失败',
icon: 'error'
});
})
}
const onDel = async (id?: number) => {
await removeAppCredential(id)
Taro.showToast({
title: '删除成功',
icon: 'success'
});
reload();
}
useDidShow(() => {
reload()
});
if (list.length == 0) {
return (
<ConfigProvider>
<div className={'h-full flex flex-col justify-center items-center'} style={{
height: 'calc(100vh - 300px)',
}}>
<Empty
style={{
backgroundColor: 'transparent'
}}
description="暂无数据"
/>
<Space>
<Button onClick={() => Taro.navigateTo({url: '/app/appCredential/add'})}></Button>
</Space>
</div>
</ConfigProvider>
)
}
return (
<>
{list.map((item, _) => (
<Cell.Group key={item.

View File

@@ -0,0 +1,4 @@
export default definePageConfig({
navigationBarTitleText: '新增应用操作动态',
navigationBarTextStyle: 'black'
})

98
src/app/appEvent/add.tsx Normal file
View File

@@ -0,0 +1,98 @@
import {useEffect, useState, useRef} from "react";
import {useRouter} from '@tarojs/taro'
import {Button, Loading, CellGroup, Input, TextArea, Form} from '@nutui/nutui-react-taro'
import Taro from '@tarojs/taro'
import {View} from '@tarojs/components'
import {AppEvent} from "@/api/app/appEvent/model";
import {getAppEvent, listAppEvent, updateAppEvent, addAppEvent} from "@/api/app/appEvent";
const AddAppEvent = () => {
const {params} = useRouter();
const [loading, setLoading] = useState<boolean>(true)
const [FormData, setFormData] = useState<AppEvent>({})
const formRef = useRef<any>(null)
const reload = async () => {
if (params.id) {
const data = await getAppEvent(Number(params.id))
setFormData(data)
} else {
setFormData({})
}
}
// 提交表单
const submitSucceed = async (values: any) => {
try {
if (params.id) {
// 编辑模式
await updateAppEvent({
...values,
id: Number(params.id)
})
} else {
// 新增模式
await addAppEvent(values)
}
Taro.showToast({
title: `操作成功`,
icon: 'success'
})
setTimeout(() => {
return Taro.navigateBack()
}, 1000)
} catch (error) {
Taro.showToast({
title: `操作失败`,
icon: 'error'
});
}
}
const submitFailed = (error: any) => {
console.log(error, 'err...')
}
useEffect(() => {
reload().then(() => {
setLoading(false)
})
}, []);
if (loading) {
return <Loading className={'px-2'}></Loading>
}
return (
<>
<Form
ref={formRef}
divider
initialValues={FormData}
labelPosition="left"
onFinish={(values) => submitSucceed(values)}
onFinishFailed={(errors) => submitFailed(errors)}
footer={
<div
style={{
display: 'flex',
justifyContent: 'center',
width: '100%'
}}
>
<Button
nativeType="submit"
type="success"
size="large"
className={'w-full'}
block
>
{params.id ? '更新' : '保存'}
</Button>
</div>
}
>
<CellGroup style={{padding: '4px 0'}}>
<Form.Item name="websiteId" label="关联应用ID" initialValue={FormData.websiteId} required>

View File

@@ -0,0 +1,4 @@
export default definePageConfig({
navigationBarTitleText: '应用操作动态管理',
navigationBarTextStyle: 'black'
})

View File

@@ -0,0 +1,64 @@
import {useState} from "react";
import Taro, {useDidShow} from '@tarojs/taro'
import {Button, Cell, CellGroup, Space, Empty, ConfigProvider, Divider} from '@nutui/nutui-react-taro'
import {Dongdong, ArrowRight, CheckNormal, Checked} from '@nutui/icons-react-taro'
import {View} from '@tarojs/components'
import {AppEvent} from "@/api/app/appEvent/model";
import {listAppEvent, removeAppEvent, updateAppEvent} from "@/api/app/appEvent";
const AppEventList = () => {
const [list, setList] = useState<AppEvent[]>([])
const reload = () => {
listAppEvent({
// 添加查询条件
})
.then(data => {
setList(data || [])
})
.catch(() => {
Taro.showToast({
title: '获取数据失败',
icon: 'error'
});
})
}
const onDel = async (id?: number) => {
await removeAppEvent(id)
Taro.showToast({
title: '删除成功',
icon: 'success'
});
reload();
}
useDidShow(() => {
reload()
});
if (list.length == 0) {
return (
<ConfigProvider>
<div className={'h-full flex flex-col justify-center items-center'} style={{
height: 'calc(100vh - 300px)',
}}>
<Empty
style={{
backgroundColor: 'transparent'
}}
description="暂无数据"
/>
<Space>
<Button onClick={() => Taro.navigateTo({url: '/app/appEvent/add'})}></Button>
</Space>
</div>
</ConfigProvider>
)
}
return (
<>
{list.map((item, _) => (
<Cell.Group key={item.

View File

@@ -0,0 +1,4 @@
export default definePageConfig({
navigationBarTitleText: '新增应用成员',
navigationBarTextStyle: 'black'
})

98
src/app/appUser/add.tsx Normal file
View File

@@ -0,0 +1,98 @@
import {useEffect, useState, useRef} from "react";
import {useRouter} from '@tarojs/taro'
import {Button, Loading, CellGroup, Input, TextArea, Form} from '@nutui/nutui-react-taro'
import Taro from '@tarojs/taro'
import {View} from '@tarojs/components'
import {AppUser} from "@/api/app/appUser/model";
import {getAppUser, listAppUser, updateAppUser, addAppUser} from "@/api/app/appUser";
const AddAppUser = () => {
const {params} = useRouter();
const [loading, setLoading] = useState<boolean>(true)
const [FormData, setFormData] = useState<AppUser>({})
const formRef = useRef<any>(null)
const reload = async () => {
if (params.id) {
const data = await getAppUser(Number(params.id))
setFormData(data)
} else {
setFormData({})
}
}
// 提交表单
const submitSucceed = async (values: any) => {
try {
if (params.id) {
// 编辑模式
await updateAppUser({
...values,
id: Number(params.id)
})
} else {
// 新增模式
await addAppUser(values)
}
Taro.showToast({
title: `操作成功`,
icon: 'success'
})
setTimeout(() => {
return Taro.navigateBack()
}, 1000)
} catch (error) {
Taro.showToast({
title: `操作失败`,
icon: 'error'
});
}
}
const submitFailed = (error: any) => {
console.log(error, 'err...')
}
useEffect(() => {
reload().then(() => {
setLoading(false)
})
}, []);
if (loading) {
return <Loading className={'px-2'}></Loading>
}
return (
<>
<Form
ref={formRef}
divider
initialValues={FormData}
labelPosition="left"
onFinish={(values) => submitSucceed(values)}
onFinishFailed={(errors) => submitFailed(errors)}
footer={
<div
style={{
display: 'flex',
justifyContent: 'center',
width: '100%'
}}
>
<Button
nativeType="submit"
type="success"
size="large"
className={'w-full'}
block
>
{params.id ? '更新' : '保存'}
</Button>
</div>
}
>
<CellGroup style={{padding: '4px 0'}}>
<Form.Item name="websiteId" label="关联应用ID" initialValue={FormData.websiteId} required>

View File

@@ -0,0 +1,4 @@
export default definePageConfig({
navigationBarTitleText: '应用成员管理',
navigationBarTextStyle: 'black'
})

64
src/app/appUser/index.tsx Normal file
View File

@@ -0,0 +1,64 @@
import {useState} from "react";
import Taro, {useDidShow} from '@tarojs/taro'
import {Button, Cell, CellGroup, Space, Empty, ConfigProvider, Divider} from '@nutui/nutui-react-taro'
import {Dongdong, ArrowRight, CheckNormal, Checked} from '@nutui/icons-react-taro'
import {View} from '@tarojs/components'
import {AppUser} from "@/api/app/appUser/model";
import {listAppUser, removeAppUser, updateAppUser} from "@/api/app/appUser";
const AppUserList = () => {
const [list, setList] = useState<AppUser[]>([])
const reload = () => {
listAppUser({
// 添加查询条件
})
.then(data => {
setList(data || [])
})
.catch(() => {
Taro.showToast({
title: '获取数据失败',
icon: 'error'
});
})
}
const onDel = async (id?: number) => {
await removeAppUser(id)
Taro.showToast({
title: '删除成功',
icon: 'success'
});
reload();
}
useDidShow(() => {
reload()
});
if (list.length == 0) {
return (
<ConfigProvider>
<div className={'h-full flex flex-col justify-center items-center'} style={{
height: 'calc(100vh - 300px)',
}}>
<Empty
style={{
backgroundColor: 'transparent'
}}
description="暂无数据"
/>
<Space>
<Button onClick={() => Taro.navigateTo({url: '/app/appUser/add'})}></Button>
</Space>
</div>
</ConfigProvider>
)
}
return (
<>
{list.map((item, _) => (
<Cell.Group key={item.

View File

@@ -0,0 +1,4 @@
export default definePageConfig({
navigationBarTitleText: '新增应用版本发布记录',
navigationBarTextStyle: 'black'
})

View File

@@ -0,0 +1,98 @@
import {useEffect, useState, useRef} from "react";
import {useRouter} from '@tarojs/taro'
import {Button, Loading, CellGroup, Input, TextArea, Form} from '@nutui/nutui-react-taro'
import Taro from '@tarojs/taro'
import {View} from '@tarojs/components'
import {AppVersion} from "@/api/app/appVersion/model";
import {getAppVersion, listAppVersion, updateAppVersion, addAppVersion} from "@/api/app/appVersion";
const AddAppVersion = () => {
const {params} = useRouter();
const [loading, setLoading] = useState<boolean>(true)
const [FormData, setFormData] = useState<AppVersion>({})
const formRef = useRef<any>(null)
const reload = async () => {
if (params.id) {
const data = await getAppVersion(Number(params.id))
setFormData(data)
} else {
setFormData({})
}
}
// 提交表单
const submitSucceed = async (values: any) => {
try {
if (params.id) {
// 编辑模式
await updateAppVersion({
...values,
id: Number(params.id)
})
} else {
// 新增模式
await addAppVersion(values)
}
Taro.showToast({
title: `操作成功`,
icon: 'success'
})
setTimeout(() => {
return Taro.navigateBack()
}, 1000)
} catch (error) {
Taro.showToast({
title: `操作失败`,
icon: 'error'
});
}
}
const submitFailed = (error: any) => {
console.log(error, 'err...')
}
useEffect(() => {
reload().then(() => {
setLoading(false)
})
}, []);
if (loading) {
return <Loading className={'px-2'}></Loading>
}
return (
<>
<Form
ref={formRef}
divider
initialValues={FormData}
labelPosition="left"
onFinish={(values) => submitSucceed(values)}
onFinishFailed={(errors) => submitFailed(errors)}
footer={
<div
style={{
display: 'flex',
justifyContent: 'center',
width: '100%'
}}
>
<Button
nativeType="submit"
type="success"
size="large"
className={'w-full'}
block
>
{params.id ? '更新' : '保存'}
</Button>
</div>
}
>
<CellGroup style={{padding: '4px 0'}}>
<Form.Item name="websiteId" label="关联应用ID" initialValue={FormData.websiteId} required>

View File

@@ -0,0 +1,4 @@
export default definePageConfig({
navigationBarTitleText: '应用版本发布记录管理',
navigationBarTextStyle: 'black'
})

View File

@@ -0,0 +1,64 @@
import {useState} from "react";
import Taro, {useDidShow} from '@tarojs/taro'
import {Button, Cell, CellGroup, Space, Empty, ConfigProvider, Divider} from '@nutui/nutui-react-taro'
import {Dongdong, ArrowRight, CheckNormal, Checked} from '@nutui/icons-react-taro'
import {View} from '@tarojs/components'
import {AppVersion} from "@/api/app/appVersion/model";
import {listAppVersion, removeAppVersion, updateAppVersion} from "@/api/app/appVersion";
const AppVersionList = () => {
const [list, setList] = useState<AppVersion[]>([])
const reload = () => {
listAppVersion({
// 添加查询条件
})
.then(data => {
setList(data || [])
})
.catch(() => {
Taro.showToast({
title: '获取数据失败',
icon: 'error'
});
})
}
const onDel = async (id?: number) => {
await removeAppVersion(id)
Taro.showToast({
title: '删除成功',
icon: 'success'
});
reload();
}
useDidShow(() => {
reload()
});
if (list.length == 0) {
return (
<ConfigProvider>
<div className={'h-full flex flex-col justify-center items-center'} style={{
height: 'calc(100vh - 300px)',
}}>
<Empty
style={{
backgroundColor: 'transparent'
}}
description="暂无数据"
/>
<Space>
<Button onClick={() => Taro.navigateTo({url: '/app/appVersion/add'})}></Button>
</Space>
</div>
</ConfigProvider>
)
}
return (
<>
{list.map((item, _) => (
<Cell.Group key={item.

View File

@@ -8,37 +8,121 @@ import { useUser } from '@/hooks/useUser';
/** /**
* 扫码登录确认页面 * 扫码登录确认页面
* 用于处理从二维码跳转过来的登录确认 *
* 支持两种场景:
* 1. 主动扫码:用户点击按钮调用微信扫码,扫描 PC 端二维码
* 2. URL 扫码(小程序码):用户扫描小程序码后,微信自动打开此页面
*
* URL 扫码场景:
* - 微信「扫普通链接二维码打开小程序」配置的二维码规则:`https://websopy.websoft.top/wx-scan/`
* - 扫码后 URL`https://websopy.websoft.top/wx-scan?token=xxx`
* - 小程序接收到参数后自动确认登录
*/ */
const QRConfirmPage: React.FC = () => { const QRConfirmPage: React.FC = () => {
const router = useRouter(); const router = useRouter();
const { user, getDisplayName } = useUser(); const { user, getDisplayName, isLoggedIn } = useUser();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [confirmed, setConfirmed] = useState(false); const [confirmed, setConfirmed] = useState(false);
const [error, setError] = useState<string>(''); const [error, setError] = useState<string>('');
const [token, setToken] = useState<string>(''); const [token, setToken] = useState<string>('');
const [loginMethod, setLoginMethod] = useState<'scan' | 'url'>('url');
useEffect(() => { useEffect(() => {
// 从 URL 参数中获取 token // 从 URL 参数中获取 token
const { qrCodeKey, token: urlToken } = router.params; const params = router.params;
const loginToken = qrCodeKey || urlToken;
// 兼容多种参数名
// 1. 直接参数:?token=xxx
// 2. URL 编码参数:?q=xxx扫普通链接二维码场景
// 3. 旧版参数:?qrCodeKey=xxx
let loginToken = params.token || params.qrCodeKey || '';
// 如果是 q 参数URL 编码的完整 URL需要解析
if (params.q) {
try {
const decodedUrl = decodeURIComponent(params.q);
console.log('[QRConfirm] 解码后的 URL:', decodedUrl);
// 解析 token
const url = new URL(decodedUrl);
loginToken = url.searchParams.get('token') ||
url.searchParams.get('qrCodeKey') ||
'';
setLoginMethod('url');
} catch (e) {
console.error('[QRConfirm] 解析 q 参数失败:', e);
// 尝试直接使用 q 作为 token
loginToken = decodeURIComponent(params.q);
setLoginMethod('url');
}
} else if (loginToken) {
setLoginMethod('url');
}
if (loginToken) { if (loginToken) {
setToken(loginToken); setToken(loginToken);
console.log('[QRConfirm] 获取到 token:', loginToken);
// URL 扫码场景:自动确认登录
if (!params.qrCodeKey && !params.token) {
// 如果不是直接参数,说明是 URL 扫码,自动确认
setTimeout(() => {
handleAutoConfirm(loginToken);
}, 500);
}
} else { } else {
setError('无效的登录链接'); setError('无效的登录链接');
} }
}, [router.params]); }, [router.params]);
// 确认登录 /**
const handleConfirmLogin = async () => { * 自动确认登录URL 扫码场景)
if (!token) { */
const handleAutoConfirm = async (loginToken: string) => {
if (!isLoggedIn || !user?.userId) {
// 用户未登录,跳转到登录页面
console.log('[QRConfirm] 用户未登录,跳转到登录页');
Taro.showToast({
title: '请先登录小程序',
icon: 'none',
duration: 2000
});
setTimeout(() => {
Taro.redirectTo({
url: '/passport/login'
});
}, 2000);
return;
}
await handleConfirmLogin(loginToken);
};
/**
* 确认登录
*/
const handleConfirmLogin = async (loginToken?: string) => {
const confirmToken = loginToken || token;
if (!confirmToken) {
setError('缺少登录token'); setError('缺少登录token');
return; return;
} }
if (!user?.userId) { if (!user?.userId) {
setError('请先登录小程序'); setError('请先登录小程序');
Taro.showToast({
title: '请先登录小程序',
icon: 'none'
});
setTimeout(() => {
Taro.redirectTo({
url: '/passport/login'
});
}, 1500);
return; return;
} }
@@ -47,7 +131,7 @@ const QRConfirmPage: React.FC = () => {
setError(''); setError('');
const result = await confirmQRLogin({ const result = await confirmQRLogin({
token, token: confirmToken,
userId: user.userId, userId: user.userId,
platform: 'wechat', platform: 'wechat',
wechatInfo: { wechatInfo: {
@@ -64,81 +148,212 @@ const QRConfirmPage: React.FC = () => {
duration: 2000 duration: 2000
}); });
// 3秒后自动返回 // 3秒后自动关闭或返回
setTimeout(() => { setTimeout(() => {
// 尝试返回上一页,如果没有则关闭
const pages = Taro.getCurrentPages();
if (pages.length > 1) {
Taro.navigateBack(); Taro.navigateBack();
} else {
// 小程序场景下,提示用户回到 PC 端
Taro.showModal({
title: '登录成功',
content: '请回到电脑端刷新页面',
showCancel: false,
confirmText: '我知道了'
});
}
}, 3000); }, 3000);
} else { } else {
setError(result.message || '登录确认失败'); setError(result.message || '登录确认失败');
} }
} catch (err: any) { } catch (err: any) {
console.error('[QRConfirm] 确认登录失败:', err);
setError(err.message || '登录确认失败'); setError(err.message || '登录确认失败');
} finally { } finally {
setLoading(false); setLoading(false);
} }
}; };
// 取消登录 /**
const handleCancel = () => { * 手动确认登录(主动扫码场景)
Taro.navigateBack(); */
const handleManualConfirm = () => {
handleConfirmLogin();
}; };
// 重试 /**
* 取消登录
*/
const handleCancel = () => {
const pages = Taro.getCurrentPages();
if (pages.length > 1) {
Taro.navigateBack();
} else {
Taro.switchTab({
url: '/pages/user/user'
});
}
};
/**
* 重试
*/
const handleRetry = () => { const handleRetry = () => {
setError(''); setError('');
setConfirmed(false); setConfirmed(false);
handleConfirmLogin(); handleConfirmLogin();
}; };
return ( /**
<View className="qr-confirm-page min-h-screen bg-gray-50"> * 打开微信扫码
<View className="p-4"> */
{/* 主要内容卡片 */} const handleScan = () => {
<Card className="bg-white rounded-lg shadow-sm"> Taro.scanCode({
<View className="p-6 text-center"> success: async (res) => {
{/* 图标 */} console.log('[QRConfirm] 扫码成功:', res);
// 解析二维码内容
let scanToken = '';
const qrContent = res.result;
try {
// 尝试解析 URL
if (qrContent.includes('http')) {
const url = new URL(qrContent);
scanToken = url.searchParams.get('token') ||
url.searchParams.get('qrCodeKey') ||
'';
}
// 尝试解析 JSON
if (!scanToken && qrContent.startsWith('{')) {
const parsed = JSON.parse(qrContent);
scanToken = parsed.token || parsed.qrCodeKey || '';
}
// 直接作为 token
if (!scanToken && qrContent.length >= 32) {
scanToken = qrContent;
}
if (scanToken) {
setToken(scanToken);
setLoginMethod('scan');
handleConfirmLogin(scanToken);
} else {
setError('无效的二维码内容');
}
} catch (e) {
console.error('[QRConfirm] 解析二维码失败:', e);
setError('二维码解析失败');
}
},
fail: (err) => {
console.error('[QRConfirm] 扫码失败:', err);
if (err.errMsg !== 'scanCode:fail cancel') {
setError('扫码失败,请重试');
}
}
});
};
// 渲染状态:加载中
const renderLoading = () => (
<View className="mb-6"> <View className="mb-6">
{loading ? (
<View className="w-16 h-16 mx-auto flex items-center justify-center"> <View className="w-16 h-16 mx-auto flex items-center justify-center">
<Loading className="text-blue-500" /> <Loading className="text-blue-500" />
</View> </View>
) : confirmed ? ( </View>
);
// 渲染状态:成功
const renderSuccess = () => (
<View className="mb-6">
<View className="w-16 h-16 mx-auto bg-green-100 rounded-full flex items-center justify-center"> <View className="w-16 h-16 mx-auto bg-green-100 rounded-full flex items-center justify-center">
<Success className="text-green-500" /> <Success className="text-green-500" size="32" />
</View> </View>
) : error ? ( </View>
);
// 渲染状态:错误
const renderError = () => (
<View className="mb-6">
<View className="w-16 h-16 mx-auto bg-red-100 rounded-full flex items-center justify-center"> <View className="w-16 h-16 mx-auto bg-red-100 rounded-full flex items-center justify-center">
<Failure className="text-red-500" /> <Failure className="text-red-500" size="32" />
</View> </View>
) : ( </View>
);
// 渲染状态:初始(用户未扫码)
const renderInitial = () => (
<View className="mb-6">
<View className="w-16 h-16 mx-auto bg-blue-100 rounded-full flex items-center justify-center"> <View className="w-16 h-16 mx-auto bg-blue-100 rounded-full flex items-center justify-center">
<User size="32" className="text-blue-500" /> <User size="32" className="text-blue-500" />
</View> </View>
)}
</View> </View>
);
// 获取标题
const getTitle = () => {
if (loading) return '正在确认登录...';
if (confirmed) return '登录确认成功';
if (error) return '登录确认失败';
return loginMethod === 'url' ? '扫码登录确认' : '确认登录';
};
// 获取描述
const getDescription = () => {
if (loading) return '请稍候,正在为您确认登录';
if (confirmed) return '您已成功确认登录,网页端将自动登录';
if (error) return error;
if (loginMethod === 'url') {
return '检测到登录请求,是否确认登录?';
}
return `确认使用 ${getDisplayName()} 登录网页端?`;
};
return (
<View className="qr-confirm-page min-h-screen bg-gradient-to-b from-blue-50 to-white">
<View className="p-4">
{/* Logo/品牌区域 */}
<View className="text-center pt-8 pb-6">
<View className="w-20 h-20 mx-auto bg-white rounded-2xl shadow-lg flex items-center justify-center mb-4">
<Text className="text-3xl">🔐</Text>
</View>
<Text className="text-gray-400 text-sm">WebSoft Platform</Text>
</View>
{/* 主要内容卡片 */}
<Card className="bg-white rounded-2xl shadow-xl -mt-4">
<View className="p-6 text-center">
{/* 状态图标 */}
{loading ? renderLoading() : confirmed ? renderSuccess() : error ? renderError() : renderInitial()}
{/* 标题 */} {/* 标题 */}
<Text className="text-xl font-bold text-gray-800 mb-2 block"> <Text className="text-xl font-bold text-gray-800 mb-2 block">
{loading ? '正在确认登录...' : {getTitle()}
confirmed ? '登录确认成功' :
error ? '登录确认失败' : '确认登录'}
</Text> </Text>
{/* 描述 */} {/* 描述 */}
<Text className="text-gray-600 mb-6 block"> <Text className="text-gray-600 mb-6 block text-sm">
{loading ? '请稍候,正在为您确认登录' : {getDescription()}
confirmed ? '您已成功确认登录,网页端将自动登录' :
error ? error :
`确认使用 ${getDisplayName()} 登录网页端?`}
</Text> </Text>
{/* 用户信息 */} {/* 用户信息 */}
{!loading && !confirmed && !error && user && ( {!loading && !confirmed && !error && user && (
<View className="bg-gray-50 rounded-lg p-4 mb-6"> <View className="bg-gray-50 rounded-xl p-4 mb-6">
<View className="flex items-center justify-center"> <View className="flex items-center justify-center">
{user.avatar ? (
<View
className="w-12 h-12 rounded-full bg-blue-100 mr-3 overflow-hidden"
style={{ backgroundImage: `url(${user.avatar})`, backgroundSize: 'cover' }}
/>
) : (
<View className="w-12 h-12 bg-blue-100 rounded-full flex items-center justify-center mr-3"> <View className="w-12 h-12 bg-blue-100 rounded-full flex items-center justify-center mr-3">
<User className="text-blue-500" size="20" /> <User className="text-blue-500" size="20" />
</View> </View>
)}
<View className="text-left"> <View className="text-left">
<Text className="text-sm font-medium text-gray-800 block"> <Text className="text-sm font-medium text-gray-800 block">
{user.nickname || user.username || '用户'} {user.nickname || user.username || '用户'}
@@ -151,6 +366,15 @@ const QRConfirmPage: React.FC = () => {
</View> </View>
)} )}
{/* Token 信息 */}
{token && !loading && !confirmed && (
<View className="bg-blue-50 rounded-lg p-3 mb-4">
<Text className="text-xs text-blue-600">
{token.substring(0, 20)}...{token.substring(token.length - 10)}
</Text>
</View>
)}
{/* 操作按钮 */} {/* 操作按钮 */}
<View className="space-y-3"> <View className="space-y-3">
{loading ? ( {loading ? (
@@ -158,7 +382,7 @@ const QRConfirmPage: React.FC = () => {
type="default" type="default"
size="large" size="large"
disabled disabled
className="w-full" className="w-full rounded-xl"
> >
... ...
</Button> </Button>
@@ -167,7 +391,7 @@ const QRConfirmPage: React.FC = () => {
type="success" type="success"
size="large" size="large"
onClick={handleCancel} onClick={handleCancel}
className="w-full" className="w-full rounded-xl"
> >
</Button> </Button>
@@ -177,27 +401,36 @@ const QRConfirmPage: React.FC = () => {
type="primary" type="primary"
size="large" size="large"
onClick={handleRetry} onClick={handleRetry}
className="w-full" className="w-full rounded-xl"
> >
</Button> </Button>
<Button <Button
type="default" type="default"
size="large" size="large"
onClick={handleScan}
className="w-full rounded-xl"
>
</Button>
<Button
type="default"
size="small"
onClick={handleCancel} onClick={handleCancel}
className="w-full" className="w-full rounded-xl"
fill="none"
> >
</Button> </Button>
</View> </View>
) : ( ) : loginMethod === 'scan' ? (
<View className="mt-3"> <View>
<Button <Button
type="primary" type="primary"
size="large" size="large"
onClick={handleConfirmLogin} onClick={handleManualConfirm}
className="w-full mb-2" className="w-full mb-2 rounded-xl"
disabled={!token || !user?.userId} disabled={!token}
> >
</Button> </Button>
@@ -205,18 +438,29 @@ const QRConfirmPage: React.FC = () => {
type="default" type="default"
size="large" size="large"
onClick={handleCancel} onClick={handleCancel}
className="w-full" className="w-full rounded-xl"
fill="none"
> >
</Button> </Button>
</View> </View>
) : (
// URL 扫码场景:自动确认中
<Button
type="primary"
size="large"
onClick={handleManualConfirm}
className="w-full rounded-xl"
>
</Button>
)} )}
</View> </View>
</View> </View>
</Card> </Card>
{/* 安全提示 */} {/* 安全提示 */}
<Card className="bg-yellow-50 border border-yellow-200 rounded-lg mt-4"> <Card className="bg-yellow-50 border border-yellow-200 rounded-xl mt-4">
<View className="p-4"> <View className="p-4">
<View className="flex items-start"> <View className="flex items-start">
<Tips className="text-yellow-600 mr-2 mt-1" size="16" /> <Tips className="text-yellow-600 mr-2 mt-1" size="16" />
@@ -231,6 +475,13 @@ const QRConfirmPage: React.FC = () => {
</View> </View>
</View> </View>
</Card> </Card>
{/* 底部说明 */}
<View className="text-center mt-6 pb-8">
<Text className="text-xs text-gray-400">
</Text>
</View>
</View> </View>
</View> </View>
); );

View File

@@ -0,0 +1,4 @@
export default definePageConfig({
navigationBarTitleText: '骑手中心'
})

63
src/rider/index.tsx Normal file
View File

@@ -0,0 +1,63 @@
import React from 'react'
import Taro from '@tarojs/taro'
import {View, Text} from '@tarojs/components'
import {Avatar, Button} from '@nutui/nutui-react-taro'
import {User} from '@nutui/icons-react-taro'
import {useThemeStyles} from '@/hooks/useTheme'
import {useUser} from '@/hooks/useUser'
const RiderIndex: React.FC = () => {
const themeStyles = useThemeStyles()
const {isLoggedIn, loading: userLoading, getAvatarUrl, getDisplayName} = useUser()
if (!isLoggedIn && !userLoading) {
return (
<View className="bg-gray-100 min-h-screen p-4">
<View className="bg-white rounded-xl p-4">
<Text className="text-gray-700"></Text>
<View className="mt-3">
<Button type="primary" onClick={() => Taro.navigateTo({url: '/passport/login'})}>
</Button>
</View>
</View>
</View>
)
}
return (
<View className="bg-gray-100 min-h-screen">
<View className="px-4 py-6 relative overflow-hidden" style={themeStyles.primaryBackground}>
<View className="flex items-center justify-between relative z-10">
<Avatar
size="50"
src={getAvatarUrl()}
icon={<User />}
className="mr-4"
style={{border: '2px solid rgba(255, 255, 255, 0.3)'}}
/>
<View className="flex-1 flex-col">
<View className="text-white text-lg font-bold mb-1">{getDisplayName()}</View>
<View className="text-sm" style={{color: 'rgba(255, 255, 255, 0.8)'}}>
</View>
</View>
</View>
</View>
<View className="p-4">
<View className="bg-white rounded-xl p-4">
<Text className="text-gray-800 font-semibold"></Text>
<View className="mt-3">
<Button type="primary" block onClick={() => Taro.navigateTo({url: '/rider/orders/index'})}>
</Button>
</View>
</View>
</View>
</View>
)
}
export default RiderIndex

View File

@@ -0,0 +1,5 @@
export default definePageConfig({
navigationBarTitleText: '骑手订单',
navigationBarTextStyle: 'black'
})

View File

@@ -0,0 +1,50 @@
import {useMemo} from 'react'
import Taro from '@tarojs/taro'
import {Button} from '@nutui/nutui-react-taro'
import {View, Text} from '@tarojs/components'
import OrderList from '@/user/order/components/OrderList'
export default function RiderOrders() {
const isLoggedIn = useMemo(() => {
return !!Taro.getStorageSync('access_token') && !!Taro.getStorageSync('UserId')
}, [])
const riderId = useMemo(() => {
const raw = Number(Taro.getStorageSync('UserId'))
return Number.isFinite(raw) && raw > 0 ? raw : undefined
}, [])
if (!isLoggedIn) {
return (
<View className="bg-gray-50 min-h-screen p-4">
<View className="bg-white rounded-lg p-4">
<Text className="text-sm text-gray-700"></Text>
<View className="mt-3">
<Button type="primary" size="small" onClick={() => Taro.navigateTo({url: '/passport/login'})}>
</Button>
</View>
</View>
</View>
)
}
if (!riderId) {
return (
<View className="bg-gray-50 min-h-screen p-4">
<View className="bg-white rounded-lg p-4">
<Text className="text-sm text-gray-700">UserId</Text>
</View>
</View>
)
}
return (
<View className="bg-gray-50 min-h-screen">
<View className="px-3">
<OrderList mode="rider" baseParams={{riderId}} />
</View>
</View>
)
}