fix: ui
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 1m2s

This commit is contained in:
sexygoat
2026-04-25 15:30:23 +08:00
parent 79a8edd455
commit 42a7d1410d
3 changed files with 154 additions and 240 deletions

View File

@@ -2,36 +2,30 @@
import { ref, computed } from 'vue'
import { changePassword } from '@/api/auth'
// 表单数据
const formData = ref({
old_password: '',
new_password: '',
confirm_password: '',
})
// 密码可见性
const passwordVisible = ref({
old: false,
new: false,
confirm: false,
})
// 提交状态
const submitting = ref(false)
// 密码强度
const passwordStrength = computed(() => {
const password = formData.value.new_password
if (!password)
return { level: 0, text: '', color: '' }
let strength = 0
// 长度检查
if (password.length >= 8)
strength++
if (password.length >= 12)
strength++
// 复杂度检查
if (/[a-z]/.test(password))
strength++
if (/[A-Z]/.test(password))
@@ -48,7 +42,6 @@ const passwordStrength = computed(() => {
return { level: 3, text: '强', color: '#3ed268' }
})
// 表单验证
const isFormValid = computed(() => {
const { old_password, new_password, confirm_password } = formData.value
return (
@@ -58,255 +51,187 @@ const isFormValid = computed(() => {
)
})
// 切换密码可见性
function togglePasswordVisible(field: 'old' | 'new' | 'confirm') {
passwordVisible.value[field] = !passwordVisible.value[field]
}
// 提交修改
async function submitChange() {
// 验证表单
if (!formData.value.old_password) {
uni.showToast({
title: '请输入当前密码',
icon: 'none',
})
uni.showToast({ title: '请输入当前密码', icon: 'none' })
return
}
if (formData.value.new_password.length < 8) {
uni.showToast({
title: '新密码至少8位',
icon: 'none',
})
uni.showToast({ title: '新密码至少8位', icon: 'none' })
return
}
if (formData.value.new_password !== formData.value.confirm_password) {
uni.showToast({
title: '两次密码输入不一致',
icon: 'none',
})
uni.showToast({ title: '两次密码输入不一致', icon: 'none' })
return
}
if (formData.value.old_password === formData.value.new_password) {
uni.showToast({
title: '新密码不能与当前密码相同',
icon: 'none',
})
uni.showToast({ title: '新密码不能与当前密码相同', icon: 'none' })
return
}
submitting.value = true
try {
// 调用修改密码接口
await changePassword({
old_password: formData.value.old_password,
new_password: formData.value.new_password,
})
uni.showToast({
title: '密码修改成功',
icon: 'success',
duration: 2000,
})
uni.showToast({ title: '密码修改成功', icon: 'success', duration: 2000 })
// 清除登录状态
uni.removeStorageSync('admin-token')
uni.removeStorageSync('refresh_token')
uni.removeStorageSync('user_info')
// 延迟跳转到登录页
setTimeout(() => {
uni.reLaunch({
url: '/pages/common/login/index',
})
uni.reLaunch({ url: '/pages/common/login/index' })
}, 2000)
}
catch (error: any) {
} catch (error: any) {
console.error('修改密码失败:', error)
// 如果是前端逻辑错误(非 API 错误),需要手动显示提示
if (error instanceof Error && error.message && !error.statusCode) {
uni.$u.toast(error.message)
}
// API 请求错误已由拦截器统一处理
}
finally {
} finally {
submitting.value = false
}
}
// 清空表单
function clearForm() {
formData.value = {
old_password: '',
new_password: '',
confirm_password: '',
}
}
</script>
<template>
<view class="bg-[var(--bg-page)] min-h-screen pb-safe">
<view class="px-4 pt-5">
<view class="bg-white rounded-12px p-4 mb-3">
<text class="text-12px text-[#999] block mb-2">当前密码</text>
<view class="flex items-center">
<input
v-model="formData.old_password"
:password="!passwordVisible.old"
class="flex-1 text-16px text-[#212121]"
placeholder="请输入当前密码"
placeholder-class="text-[#ccc]"
>
<image
:src="passwordVisible.old ? '/static/icons/preview-open.png' : '/static/icons/preview-close-one.png'"
class="w-20px h-20px"
mode="aspectFit"
@click="togglePasswordVisible('old')"
/>
<view class="bg-page min-h-screen">
<view class="px-4 py-4">
<!-- 表单卡片 -->
<view class="bg-white rounded-16px p-4">
<!-- 当前密码 -->
<view class="mb-4">
<text class="text-14px text-[#666] block mb-2">当前密码</text>
<view class="flex items-center bg-[#f8fafc] rounded-full px-4 h-12">
<input
v-model="formData.old_password"
:password="!passwordVisible.old"
class="flex-1 text-15px text-[#212121]"
placeholder="请输入当前密码"
placeholder-class="text-[#cbd5e1]"
>
<view @click="togglePasswordVisible('old')">
<i v-if="passwordVisible.old" class="i-mdi-eye text-20px text-[#94a3b8]" />
<i v-else class="i-mdi-eye-off text-20px text-[#94a3b8]" />
</view>
</view>
</view>
<!-- 新密码 -->
<view class="mb-4">
<view class="flex items-center justify-between mb-2">
<text class="text-14px text-[#666]">新密码</text>
<text
v-if="formData.new_password"
:style="{ color: passwordStrength.color }"
class="text-12px font-600"
>
{{ passwordStrength.text }}
</text>
</view>
<view class="flex items-center bg-[#f8fafc] rounded-full px-4 h-12">
<input
v-model="formData.new_password"
:password="!passwordVisible.new"
class="flex-1 text-15px text-[#212121]"
placeholder="请输入新密码"
placeholder-class="text-[#cbd5e1]"
>
<view @click="togglePasswordVisible('new')">
<i v-if="passwordVisible.new" class="i-mdi-eye text-20px text-[#94a3b8]" />
<i v-else class="i-mdi-eye-off text-20px text-[#94a3b8]" />
</view>
</view>
<view v-if="formData.new_password" class="flex items-center gap-1 mt-2">
<view
v-for="i in 3"
:key="i"
class="flex-1 h-2px rounded-full transition-all"
:style="{ backgroundColor: i <= passwordStrength.level ? passwordStrength.color : '#e0e0e0' }"
/>
</view>
</view>
<!-- 确认密码 -->
<view class="mb-6">
<text class="text-14px text-[#666] block mb-2">确认密码</text>
<view class="flex items-center bg-[#f8fafc] rounded-full px-4 h-12">
<input
v-model="formData.confirm_password"
:password="!passwordVisible.confirm"
class="flex-1 text-15px text-[#212121]"
placeholder="请再次输入新密码"
placeholder-class="text-[#cbd5e1]"
>
<view @click="togglePasswordVisible('confirm')">
<i v-if="passwordVisible.confirm" class="i-mdi-eye text-20px text-[#94a3b8]" />
<i v-else class="i-mdi-eye-off text-20px text-[#94a3b8]" />
</view>
</view>
<view v-if="formData.confirm_password" class="mt-2">
<text
:class="[
'text-12px',
formData.new_password === formData.confirm_password ? 'text-[#52c41a]' : 'text-[#ff4d4f]',
]"
>
{{ formData.new_password === formData.confirm_password ? '密码一致' : '密码不一致' }}
</text>
</view>
</view>
<!-- 提交按钮 -->
<view
:class="[
'py-4 rounded-full text-center text-16px font-600 transition-all',
isFormValid && !submitting
? 'bg-brand text-white shadow-brand'
: 'bg-[#e0e0e0] text-[#999]',
]"
@click="submitChange"
>
{{ submitting ? '修改中...' : '确认修改' }}
</view>
</view>
<view class="bg-white rounded-12px p-4 mb-3">
<view class="flex items-center justify-between mb-2">
<text class="text-12px text-[#999]">密码</text>
<text
v-if="formData.new_password"
:style="{ color: passwordStrength.color }"
class="text-12px font-600"
>
{{ passwordStrength.text }}
</text>
</view>
<view class="flex items-center">
<input
v-model="formData.new_password"
:password="!passwordVisible.new"
class="flex-1 text-16px text-[#212121]"
placeholder="请输入新密码"
placeholder-class="text-[#ccc]"
>
<image
:src="passwordVisible.new ? '/static/icons/preview-open.png' : '/static/icons/preview-close-one.png'"
class="w-20px h-20px"
mode="aspectFit"
@click="togglePasswordVisible('new')"
/>
</view>
<view v-if="formData.new_password" class="flex items-center gap-1 mt-2">
<view
v-for="i in 3"
:key="i"
class="flex-1 h-2px rounded-full"
:style="{ backgroundColor: i <= passwordStrength.level ? passwordStrength.color : '#e0e0e0' }"
/>
</view>
</view>
<view class="bg-white rounded-12px p-4 mb-3">
<text class="text-12px text-[#999] block mb-2">确认密码</text>
<view class="flex items-center">
<input
v-model="formData.confirm_password"
:password="!passwordVisible.confirm"
class="flex-1 text-16px text-[#212121]"
placeholder="请再次输入新密码"
placeholder-class="text-[#ccc]"
>
<image
:src="passwordVisible.confirm ? '/static/icons/preview-open.png' : '/static/icons/preview-close-one.png'"
class="w-20px h-20px"
mode="aspectFit"
@click="togglePasswordVisible('confirm')"
/>
</view>
<view v-if="formData.confirm_password" class="mt-2">
<text
:class="[
'text-12px',
formData.new_password === formData.confirm_password ? 'text-[#52c41a]' : 'text-[#ff4d4f]',
]"
>
{{ formData.new_password === formData.confirm_password ? '密码一致' : '密码不一致' }}
</text>
</view>
</view>
<view
:class="[
'py-4 rounded-12px text-center text-16px font-600',
isFormValid && !submitting
? 'bg-brand text-white'
: 'bg-[#f5f5f5] text-[#ccc]',
]"
@click="submitChange"
>
{{ submitting ? '修改中...' : '确认修改' }}
</view>
<view class="bg-white rounded-12px p-4 mt-4">
<text class="text-13px font-600 text-[#212121] block mb-3">密码要求</text>
<!-- 密码要求 -->
<view class="bg-white rounded-16px p-4 mt-4">
<text class="text-14px font-600 text-[#212121] block mb-3">密码要求</text>
<view class="space-y-2">
<view class="flex items-center gap-2">
<text :class="formData.new_password.length >= 8 ? 'text-[#52c41a]' : 'text-[#ccc]'"></text>
<text :class="formData.new_password.length >= 8 ? 'text-[#52c41a]' : 'text-[#cbd5e1]'"></text>
<text class="text-13px text-[#666]">至少8个字符</text>
</view>
<view class="flex items-center gap-2">
<text :class="/[a-zA-Z]/.test(formData.new_password) ? 'text-[#52c41a]' : 'text-[#ccc]'"></text>
<text :class="/[a-zA-Z]/.test(formData.new_password) ? 'text-[#52c41a]' : 'text-[#cbd5e1]'"></text>
<text class="text-13px text-[#666]">包含字母</text>
</view>
<view class="flex items-center gap-2">
<text :class="/\d/.test(formData.new_password) ? 'text-[#52c41a]' : 'text-[#ccc]'"></text>
<text :class="/\d/.test(formData.new_password) ? 'text-[#52c41a]' : 'text-[#cbd5e1]'"></text>
<text class="text-13px text-[#666]">包含数字</text>
</view>
<view class="flex items-center gap-2">
<text :class="/[^a-zA-Z\d]/.test(formData.new_password) ? 'text-[#52c41a]' : 'text-[#ccc]'"></text>
<text :class="/[^a-zA-Z\d]/.test(formData.new_password) ? 'text-[#52c41a]' : 'text-[#cbd5e1]'"></text>
<text class="text-13px text-[#666]">包含特殊字符</text>
</view>
</view>
</view>
<view class="h-8" />
</view>
</view>
</template>
<style scoped>
.center {
display: flex;
align-items: center;
justify-content: center;
}
.space-y-3 > view:not(:last-child) {
margin-bottom: 12px;
}
.space-y-2 > view:not(:last-child) {
margin-bottom: 8px;
}
.leading-relaxed {
line-height: 1.6;
}
.space-y-2 > view:not(:last-child) {
margin-bottom: 8px;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.animate-spin {
animation: spin 1s linear infinite;
}
</style>

View File

@@ -236,6 +236,17 @@ onMounted(async () => {
<!-- 筛选器 -->
<view class="flex items-center gap-2 mt-3 flex-wrap">
<view class="flex bg-[#f1f5f9] rounded-full flex-shrink-0">
<view
v-for="option in statusOptions"
:key="option.value"
class="px-3 py-1.5 text-12px font-medium transition-all rounded-full"
:class="statusFilter === option.value ? 'bg-brand text-white' : 'text-[#64748b]'"
@click="handleStatusChange(option.value)"
>
{{ option.label }}
</view>
</view>
<view class="flex items-center gap-1 px-3 py-1.5 bg-[#f8fafc] rounded-full flex-shrink-0"
@click="showSearchTypePicker = true">
@@ -253,18 +264,6 @@ onMounted(async () => {
<i class="i-mdi-chevron-down text-12px text-[#94a3b8] flex-shrink-0" />
</view>
<view class="flex bg-[#f1f5f9] rounded-full flex-shrink-0">
<view
v-for="option in statusOptions"
:key="option.value"
class="px-3 py-1.5 text-12px font-medium transition-all rounded-full"
:class="statusFilter === option.value ? 'bg-brand text-white' : 'text-[#64748b]'"
@click="handleStatusChange(option.value)"
>
{{ option.label }}
</view>
</view>
<view
v-if="carrierFilter !== undefined || statusFilter !== undefined"
class="flex items-center gap-1 px-2 py-1.5 rounded-full bg-[#ff4d4f]/10 text-[#ff4d4f] flex-shrink-0"

View File

@@ -287,75 +287,65 @@
<view class="px-4 pt-4">
<text class="text-16px font-700 text-[#212121] block mb-3">快捷功能</text>
<view class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] overflow-hidden mb-3">
<view class="grid grid-cols-3 gap-3 mb-3">
<!-- 资产列表 -->
<view class="flex items-center px-4 py-4 active:bg-[#f8fafc] transition-all duration-200" @click="navigateTo('/pages/agent-system/assets/index')">
<image src="@/static/icons/资产列表.png" class="w-40px h-40px rounded-12px mr-3" mode="aspectFit" />
<view class="flex-1">
<text class="text-15px text-[#212121] font-600 block">资产列表</text>
<text class="text-12px text-[#999] block mt-0.5">{{ userInfo?.user_type === 3 ? '代理资产列表' : '企业资产列表' }}</text>
</view>
<i class="i-mdi-chevron-right text-20px text-[#cbd5e1]" />
<view class="bg-white rounded-16px p-3 flex flex-col items-center shadow-[0_2px_12px_rgba(0,0,0,0.06)] aspect-square" @click="navigateTo('/pages/agent-system/assets/index')">
<image src="@/static/icons/资产列表.png" class="w-12 h-12 rounded-12px mb-2" mode="aspectFit" />
<text class="text-13px font-500 text-[#212121]">资产列表</text>
</view>
<!-- 企业端: 授权卡列表 -->
<view v-if="userInfo?.user_type === 4" class="flex items-center px-4 py-4 border-t-1px border-[#f5f5f5] active:bg-[#f8fafc] transition-all duration-200" @click="navigateTo('/pages/agent-system/enterprise-cards/index')">
<image src="@/static/icons/授权卡列表.png" class="w-40px h-40px rounded-12px mr-3" mode="aspectFit" />
<view class="flex-1">
<text class="text-15px text-[#212121] font-600 block">授权卡列表</text>
<text class="text-12px text-[#999] block mt-0.5">企业授权IoT卡</text>
</view>
<i class="i-mdi-chevron-right text-20px text-[#cbd5e1]" />
<view v-if="userInfo?.user_type === 4" class="bg-white rounded-16px p-3 flex flex-col items-center shadow-[0_2px_12px_rgba(0,0,0,0.06)] aspect-square" @click="navigateTo('/pages/agent-system/enterprise-cards/index')">
<image src="@/static/icons/授权卡列表.png" class="w-12 h-12 rounded-12px mb-2" mode="aspectFit" />
<text class="text-13px font-500 text-[#212121]">授权卡</text>
</view>
<!-- 企业端: 授权设备列表 -->
<view v-if="userInfo?.user_type === 4" class="flex items-center px-4 py-4 border-t-1px border-[#f5f5f5] active:bg-[#f8fafc] transition-all duration-200" @click="navigateTo('/pages/agent-system/enterprise-devices/index')">
<image src="@/static/icons/授权设备列表.png" class="w-40px h-40px rounded-12px mr-3" mode="aspectFit" />
<view class="flex-1">
<text class="text-15px text-[#212121] font-600 block">授权设备列表</text>
<text class="text-12px text-[#999] block mt-0.5">企业授权设备</text>
</view>
<i class="i-mdi-chevron-right text-20px text-[#cbd5e1]" />
<view v-if="userInfo?.user_type === 4" class="bg-white rounded-16px p-3 flex flex-col items-center shadow-[0_2px_12px_rgba(0,0,0,0.06)] aspect-square" @click="navigateTo('/pages/agent-system/enterprise-devices/index')">
<image src="@/static/icons/授权设备列表.png" class="w-12 h-12 rounded-12px mb-2" mode="aspectFit" />
<text class="text-13px font-500 text-[#212121]">授权设备</text>
</view>
<!-- 代理端: 佣金中心 -->
<view v-if="userInfo?.user_type === 3" class="flex items-center px-4 py-4 border-t-1px border-[#f5f5f5] active:bg-[#f8fafc] transition-all duration-200" @click="navigateTo('/pages/agent-system/commission-center/index')">
<image src="@/static/icons/佣金中心.png" class="w-40px h-40px rounded-12px mr-3" mode="aspectFit" />
<view class="flex-1">
<text class="text-15px text-[#212121] font-600 block">佣金中心</text>
<text class="text-12px text-[#999] block mt-0.5">收益统计</text>
</view>
<i class="i-mdi-chevron-right text-20px text-[#cbd5e1]" />
<view v-if="userInfo?.user_type === 3" class="bg-white rounded-16px p-3 flex flex-col items-center shadow-[0_2px_12px_rgba(0,0,0,0.06)] aspect-square" @click="navigateTo('/pages/agent-system/commission-center/index')">
<image src="@/static/icons/佣金中心.png" class="w-12 h-12 rounded-12px mb-2" mode="aspectFit" />
<text class="text-13px font-500 text-[#212121]">佣金中心</text>
</view>
<!-- 代理端: 提现管理 -->
<view v-if="userInfo?.user_type === 3" class="flex items-center px-4 py-4 border-t-1px border-[#f5f5f5] active:bg-[#f8fafc] transition-all duration-200" @click="navigateTo('/pages/agent-system/withdraw/index')">
<image src="@/static/icons/提现管理.png" class="w-40px h-40px rounded-12px mr-3" mode="aspectFit" />
<view class="flex-1">
<text class="text-15px text-[#212121] font-600 block">提现管理</text>
<text class="text-12px text-[#999] block mt-0.5">申请提现</text>
</view>
<i class="i-mdi-chevron-right text-20px text-[#cbd5e1]" />
<view v-if="userInfo?.user_type === 3" class="bg-white rounded-16px p-3 flex flex-col items-center shadow-[0_2px_12px_rgba(0,0,0,0.06)] aspect-square" @click="navigateTo('/pages/agent-system/withdraw/index')">
<image src="@/static/icons/提现管理.png" class="w-12 h-12 rounded-12px mb-2" mode="aspectFit" />
<text class="text-13px font-500 text-[#212121]">提现管理</text>
</view>
</view>
</view>
<!-- 其他操作 -->
<!-- 设置 -->
<view class="px-4 pt-4 pb-6">
<text class="text-16px font-700 text-[#212121] block mb-3">设置</text>
<view class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] overflow-hidden">
<!-- 修改密码 -->
<view class="flex items-center px-4 py-4 active:bg-[#f8fafc] transition-all duration-200" @click="navigateTo('/pages/agent-system/change-password/index')">
<image src="@/static/icons/修改密码.png" class="w-40px h-40px rounded-12px mr-3" mode="aspectFit" />
<text class="flex-1 text-15px text-[#212121]">修改密码</text>
<view class="w-40px h-40px rounded-12px bg-[#f0f5ff] flex items-center justify-center mr-3">
<i class="i-mdi-lock-reset text-20px text-brand" />
</view>
<view class="flex-1">
<text class="text-15px text-[#212121] font-500 block">修改密码</text>
<text class="text-12px text-[#999] block mt-0.5">更新登录密码</text>
</view>
<i class="i-mdi-chevron-right text-20px text-[#cbd5e1]" />
</view>
<!-- 退出登录 -->
<view class="flex items-center px-4 py-4 border-t-1px border-[#f5f5f5] active:bg-[#f8fafc] transition-all duration-200" @click="logout">
<image src="@/static/icons/退出登录.png" class="w-40px h-40px rounded-12px mr-3" mode="aspectFit" />
<text class="flex-1 text-15px text-[#212121]">退出登录</text>
<view class="flex items-center px-4 py-4 border-t border-[#f5f5f5] active:bg-[#f8fafc] transition-all duration-200" @click="logout">
<view class="w-40px h-40px rounded-12px bg-[#fff2f0] flex items-center justify-center mr-3">
<i class="i-mdi-logout text-20px text-[#ff4d4f]" />
</view>
<view class="flex-1">
<text class="text-15px text-[#212121] font-500 block">退出登录</text>
<text class="text-12px text-[#999] block mt-0.5">退出当前账号</text>
</view>
<i class="i-mdi-chevron-right text-20px text-[#cbd5e1]" />
</view>
</view>