This commit is contained in:
Leo_Ding 2025-11-24 11:15:47 +08:00
commit 41f69742e6
6 changed files with 1008 additions and 56 deletions

View File

@ -57,6 +57,21 @@ const routes: RouteRecordRaw[] = [
name: "AdminHome", name: "AdminHome",
component: () => import("@/views/admin/home/index.vue"), component: () => import("@/views/admin/home/index.vue"),
}, },
{
path: "fileStore",
name: "FileStore",
component: () => import("@/views/admin/fileStore/index.vue"),
},
{
path: "history",
name: "History",
component: () => import("@/views/admin/account/history/index.vue"),
},
{
path: "security",
name: "Security",
component: () => import("@/views/admin/account/security/index.vue"),
},
], ],
}, },
], ],

View File

@ -0,0 +1,116 @@
<!-- src/components/AccessLog.vue -->
<template>
<div class="access-log">
<div class="title-wrapper">
<h3>访问记录</h3>
<span class="desc">以下显示内容为账号及其子账号近3个月的登录记录</span>
</div>
<a-table
:columns="columns"
:data-source="loginRecords"
:pagination="false"
size="middle"
:bordered="true"
class="login-table"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
//
interface LoginRecord {
id: number;
time: string;
ip: string;
city: string;
type: string;
operator: string;
}
//
const loginRecords = ref<LoginRecord[]>([
{
id: 1,
time: '2025-11-21 09:58:38',
ip: '183.209.69.36',
city: '中国 江苏省',
type: 'Web微信登录',
operator: '炼丹师5075',
},
{
id: 2,
time: '2025-11-19 17:07:13',
ip: '183.209.69.36',
city: '中国 江苏省',
type: 'Web微信登录',
operator: '炼丹师5075',
},
]);
//
const columns = [
{
title: '访问时间',
dataIndex: 'time',
key: 'time',
width: 200,
},
{
title: '访问IP地址',
dataIndex: 'ip',
key: 'ip',
width: 180,
},
{
title: '访问城市',
dataIndex: 'city',
key: 'city',
width: 180,
},
{
title: '登录类型',
dataIndex: 'type',
key: 'type',
width: 200,
},
{
title: '操作人',
dataIndex: 'operator',
key: 'operator',
width: 180,
},
];
</script>
<style scoped>
.access-log {
padding: 20px;
background-color: #f9f9f9;
}
.title-wrapper {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 16px;
}
.title-wrapper h3 {
margin: 0;
color: #333;
font-size: 1.2em;
}
.desc {
color: #faad14;
font-size: 14px;
font-weight: normal;
}
.login-table {
border-radius: 6px;
}
</style>

View File

@ -0,0 +1,346 @@
<!-- src/components/AccountSecurity.vue -->
<script setup lang="ts">
import { ref, reactive } from 'vue';
import {
CheckCircleOutlined,
ExclamationCircleOutlined,
CloseOutlined,
SaveOutlined,
} from '@ant-design/icons-vue';
import { Modal, message } from 'ant-design-vue';
//
interface SecurityItem {
title: string;
description: string;
status: 'unset' | 'bound' | 'unverified' | 'verified';
actionText: string;
actionHandler: () => void;
}
const securityItems = ref<SecurityItem[]>([
{
title: '登录密码',
description: '安全性高的密码可以使账号更安全。建议您定期更换密码设置一个包含字母和数字且长度超过8位的密码',
status: 'unset',
actionText: '设置',
actionHandler: () => openSetPasswordModal(),
},
{
title: '手机绑定',
description: '您已绑定了手机178****5075您的手机号可以直接用于登录、找回密码等',
status: 'bound',
actionText: '修改',
actionHandler: () => alert('跳转到修改手机号页面'),
},
{
title: '实名认证',
description: '实名认证后可以使用AutoDL更完整的功能如打开实例的自定义服务等',
status: 'unverified',
actionText: '立即认证',
actionHandler: () => alert('跳转到实名认证页面'),
},
{
title: '微信绑定',
description: '您已绑定微信,可快速扫码登录',
status: 'bound',
actionText: '解绑',
actionHandler: () => alert('确认解绑微信?'),
},
{
title: '邮箱绑定',
description: '绑定邮箱后可接收系统消息,如余额不足、实例即将到期、实例即将释放等消息',
status: 'unset',
actionText: '绑定',
actionHandler: () => alert('跳转到绑定邮箱页面'),
},
]);
const getStatusIcon = (status: SecurityItem['status']) => {
switch (status) {
case 'bound':
return CheckCircleOutlined;
case 'unverified':
case 'unset':
return ExclamationCircleOutlined;
default:
return null;
}
};
const getStatusColor = (status: SecurityItem['status']) => {
switch (status) {
case 'bound':
return 'green';
case 'unverified':
case 'unset':
return 'red';
default:
return 'gray';
}
};
const getActionColor = (status: SecurityItem['status']) => {
return 'blue'; //
};
//
const setPasswordModalVisible = ref(false);
//
const form = reactive({
phoneNumber: '+86 178****5075', //
smsCode: '',
newPassword: '',
confirmPassword: '',
});
//
const countdown = ref(0);
const isSending = ref(false);
//
const sendSmsCode = () => {
if (isSending.value) return;
isSending.value = true;
countdown.value = 60;
const timer = setInterval(() => {
countdown.value--;
if (countdown.value <= 0) {
clearInterval(timer);
isSending.value = false;
countdown.value = 0;
}
}, 1000);
message.success('验证码已发送');
};
//
const resetForm = () => {
form.smsCode = '';
form.newPassword = '';
form.confirmPassword = '';
countdown.value = 0;
isSending.value = false;
};
//
const openSetPasswordModal = () => {
resetForm();
setPasswordModalVisible.value = true;
};
//
const closeSetPasswordModal = () => {
setPasswordModalVisible.value = false;
resetForm();
};
//
const savePassword = () => {
if (!form.smsCode) {
message.error('请输入验证码');
return;
}
if (!form.newPassword) {
message.error('请输入新密码');
return;
}
if (!form.confirmPassword) {
message.error('请确认密码');
return;
}
if (form.newPassword !== form.confirmPassword) {
message.error('两次输入的密码不一致');
return;
}
if (!/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,16}$/.test(form.newPassword)) {
message.error('密码必须为8~16位且包含字母和数字');
return;
}
//
message.success('密码设置成功!');
closeSetPasswordModal();
};
</script>
<template>
<div class="account-security">
<h2>账号安全</h2>
<div v-for="(item, index) in securityItems" :key="index" class="security-item">
<div class="item-header">
<span class="title">{{ item.title }}</span>
<div class="status">
<component
:is="getStatusIcon(item.status)"
v-if="getStatusIcon(item.status)"
:class="['icon', `color-${getStatusColor(item.status)}`]"
/>
<span :class="['text', `color-${getStatusColor(item.status)}`]">
{{ item.status === 'bound' ? '已绑定' : item.status === 'unverified' ? '未认证' : '未设置' }}
</span>
<span class="divider">|</span>
<button
class="action-btn"
:class="`color-${getActionColor(item.status)}`"
@click="item.actionHandler"
>
{{ item.actionText }}
</button>
</div>
</div>
<p class="description">{{ item.description }}</p>
</div>
<!-- 设置密码弹窗 -->
<a-modal
v-model:visible="setPasswordModalVisible"
title="设置密码"
width="500px"
:footer="null"
@cancel="closeSetPasswordModal"
>
<div style="padding: 24px 0;">
<div style="margin-bottom: 24px; font-size: 16px; color: #333;">
手机号{{ form.phoneNumber }}
</div>
<div style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;">
<span style="color: red;">*</span>
<label style="font-size: 14px; color: #333;">手机验证码</label>
<a-input
v-model:value="form.smsCode"
placeholder="请输入验证码"
style="flex: 1;"
/>
<a-button
type="primary"
:disabled="isSending || countdown > 0"
@click="sendSmsCode"
style="width: 120px;"
>
{{ isSending || countdown > 0 ? `${countdown}s后重发` : '发送验证码' }}
</a-button>
</div>
<div style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;">
<span style="color: red;">*</span>
<label style="font-size: 14px; color: #333;">设置新密码</label>
<a-input
v-model:value="form.newPassword"
placeholder="请输入816位包含数字和字母的密码"
type="password"
style="flex: 1;"
/>
</div>
<div style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;">
<span style="color: red;">*</span>
<label style="font-size: 14px; color: #3 33;">确认新密码</label>
<a-input
v-model:value="form.confirmPassword"
placeholder="请确认密码"
type="password"
style="flex: 1;"
/>
</div>
</div>
<div style="text-align: right; padding: 16px 24px;">
<a-button @click="closeSetPasswordModal" style="margin-right: 8px;">取消</a-button>
<a-button type="primary" @click="savePassword">保存</a-button>
</div>
</a-modal>
</div>
</template>
<style scoped>
.account-security {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
padding: 20px;
background-color: #f9f9f9;
}
.account-security h2 {
margin-top: 0;
color: #333;
font-size: 1.4em;
}
.security-item {
border: 1px solid #ddd;
border-radius: 6px;
margin-bottom: 16px;
padding: 16px;
background: white;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.item-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.title {
font-weight: bold;
color: #333;
}
.status {
display: flex;
align-items: center;
gap: 8px;
}
.icon {
font-size: 16px;
line-height: 1;
}
.text {
font-size: 14px;
font-weight: normal;
}
.divider {
color: #ccc;
font-size: 14px;
}
.action-btn {
background: none;
border: none;
color: #0066cc;
cursor: pointer;
font-size: 14px;
padding: 0;
margin-left: 8px;
text-decoration: underline;
}
.action-btn:hover {
text-decoration: none;
}
.color-green {
color: #52c41a !important;
}
.color-red {
color: #ff4d4f !important;
}
.color-blue {
color: #0066cc !important;
}
.description {
color: #666;
font-size: 14px;
line-height: 1.5;
}
</style>

View File

@ -0,0 +1,255 @@
<template>
<div class="container">
<!-- 标题 -->
<div class="header">
<h1>文件存储</h1>
</div>
<!-- 区域选择 -->
<div class="region-section">
<h2 class="section-title">选择存储区域</h2>
<div class="region-tabs">
<button
v-for="region in regions"
:key="region.id"
:class="['region-tab', { active: selectedRegion === region.id }]"
@click="selectRegion(region.id)"
>
{{ region.name }}
</button>
</div>
</div>
<!-- 中心图标 -->
<div class="center-section">
<div class="center-icon">
<i class="icon-book"></i>
</div>
</div>
<!-- 主要操作按钮 -->
<div class="action-section">
<button class="btn-primary" @click="initializeStorage">
初始化文件存储
</button>
<p class="action-hint">将在 {{ getSelectedRegionName() }} 创建存储空间</p>
</div>
<!-- 底部帮助链接 -->
<div class="help-section">
<div class="help-links">
<a href="#" class="link" @click.prevent="viewHelp">文件存储使用介绍</a>
<a href="#" class="link" @click.prevent="viewPricing">查看计费规则</a>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
//
interface Region {
id: string
name: string
}
const regions = [
{ id: 'a100', name: 'A100专区' },
{ id: 'v100', name: 'V100专区' },
{ id: 'foshan', name: '佛山区' },
{ id: 'beijing', name: '北京B区' }
]
const selectedRegion = ref('a100') // A100
//
const getSelectedRegionName = () => {
const region = regions.find(r => r.id === selectedRegion.value)
return region ? region.name : ''
}
//
const selectRegion = (id: string) => {
selectedRegion.value = id
}
//
const initializeStorage = () => {
alert(`正在初始化 ${selectedRegion.value} 的文件存储...`)
//
}
//
const viewHelp = () => {
alert('文件存储使用介绍')
}
//
const viewPricing = () => {
alert('计费规则详情')
}
</script>
<style scoped>
.container {
/* max-width: 800px;
margin: 0 auto; */
padding: 40px 30px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
color: #333;
}
/* 标题区域 */
.header {
margin-bottom: 40px;
}
h1 {
font-size: 2rem;
font-weight: 600;
color: #1a1a1a;
margin-bottom: 8px;
}
/* 区域选择 */
.region-section {
margin-bottom: 40px;
}
.section-title {
font-size: 1.1rem;
font-weight: 500;
color: #555;
margin-bottom: 16px;
}
.region-tabs {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.region-tab {
padding: 12px 24px;
border: 1px solid #ddd;
background: white;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
font-size: 14px;
min-width: 140px;
}
.region-tab:hover {
border-color: #999;
}
.region-tab.active {
background-color: #3498db;
color: white;
border-color: #3498db;
}
/* 中心图标区域 */
.center-section {
margin: 40px 0;
display: flex;
justify-content: center;
}
.center-icon {
width: 80px;
height: 80px;
display: flex;
align-items: center;
justify-content: center;
background: #f5f5f5;
border-radius: 8px;
}
.icon-book {
font-size: 36px;
color: #3498db;
}
.icon-book::before {
content: "📁";
}
/* 操作区域 */
.action-section {
margin: 40px 0;
text-align: center;
}
.btn-primary {
background-color: #3498db;
color: white;
border: none;
padding: 14px 32px;
border-radius: 6px;
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: background 0.2s;
}
.btn-primary:hover {
background-color: #2980b9;
}
.action-hint {
margin-top: 12px;
font-size: 0.9rem;
color: #666;
}
/* 帮助区域 */
.help-section {
margin-top: 50px;
}
.help-links {
display: flex;
gap: 30px;
justify-content: center;
}
.link {
text-decoration: none;
color: #3498db;
font-size: 14px;
transition: color 0.2s;
}
.link:hover {
color: #2980b9;
text-decoration: underline;
}
/* 响应式设计 */
@media (max-width: 768px) {
.container {
padding: 30px 20px;
}
.region-tabs {
flex-direction: column;
}
.region-tab {
width: 100%;
max-width: 100%;
}
.help-links {
flex-direction: column;
gap: 15px;
}
.center-section {
justify-content: center;
}
}
</style>

View File

@ -1,67 +1,144 @@
<template> <template>
<div class="admin-layout"> <div class="admin-layout">
<a-menu v-model:openKeys="state.openKeys" v-model:selectedKeys="state.selectedKeys" style="width: 256px" <!-- 左侧菜单 -->
:mode="state.mode" :items="items" :theme="state.theme"></a-menu> <a-menu
<div class="admin-contain"> v-model:openKeys="state.openKeys"
<router-view /> v-model:selectedKeys="state.selectedKeys"
</div> class="admin-sidebar"
:mode="state.mode"
:items="items"
:theme="state.theme"
@select="handleMenuSelect"
/>
<!-- 右侧内容区 -->
<div class="admin-contain">
<router-view />
</div> </div>
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { h, reactive } from 'vue'; import { h, reactive, computed } from 'vue';
import { useRouter } from 'vue-router';
import { import {
MailOutlined, HomeOutlined,
CalendarOutlined, FolderOpenOutlined,
AppstoreOutlined, ConsoleSqlOutlined,
SettingOutlined, GlobalOutlined,
LaptopOutlined,
MoneyCollectOutlined,
TeamOutlined,
} from '@ant-design/icons-vue'; } from '@ant-design/icons-vue';
import type { MenuMode, MenuTheme } from 'ant-design-vue'; import type { MenuMode, MenuTheme } from 'ant-design-vue';
import { ItemType } from 'ant-design-vue'; import { ItemType } from 'ant-design-vue';
const state = reactive({ const router = useRouter();
mode: 'inline' as MenuMode,
theme: 'light' as MenuTheme,
selectedKeys: ['1'],
openKeys: ['sub1'],
});
function getItem( //
label: string, interface MenuItem {
key: string, path: string;
icon?: any, name: string;
children?: ItemType[], icon?: any;
type?: 'group', children?: Omit<MenuItem, 'icon'>[]; // icon
): ItemType {
return {
key,
icon,
children,
label,
type,
} as ItemType;
} }
const items: ItemType[] = reactive([ const menuItems: MenuItem[] = [
getItem('Navigation One', '1', h(MailOutlined)), { path: '/layout/overview', name: '总览', icon: HomeOutlined },
getItem('Navigation Two', '2', h(CalendarOutlined)), { path: '/layout/container', name: '容器实例', icon: ConsoleSqlOutlined },
getItem('Navigation Two', 'sub1', h(AppstoreOutlined), [ { path: '/layout/admin/fileStore', name: '文件存储', icon: FolderOpenOutlined },
getItem('Option 3', '3'), { path: '/layout/image', name: '镜像', icon: GlobalOutlined },
getItem('Option 4', '4'), { path: '/layout/publicData', name: '公开数据', icon: LaptopOutlined },
getItem('Submenu', 'sub1-2', null, [getItem('Option 5', '5'), getItem('Option 6', '6')]), {
]), path: '/layout/fee',
getItem('Navigation Three', 'sub2', h(SettingOutlined), [ name: '费用',
getItem('Option 7', '7'), icon: MoneyCollectOutlined,
getItem('Option 8', '8'), children: [
getItem('Option 9', '9'), { path: '/layout/fee/detail', name: '详情' },
getItem('Option 10', '10'), { path: '/layout/fee/bill', name: '账单' },
]), ],
]); },
{
path: '/layout/admin',
name: '账号',
icon: TeamOutlined,
children: [
{ path: '/layout/admin/account/security', name: '账号安全' },
{ path: '/layout/admin/account/history', name: '访问记录' },
{ path: '/controlPanel/account/security', name: '安全设置' },
],
},
];
//
const state = reactive({
mode: 'inline' as MenuMode,
theme: 'light' as MenuTheme,
selectedKeys: ['/controlPanel/overview'], //
openKeys: ['/controlPanel/fee', '/controlPanel/account'], //
});
//
function getItem(
label: string,
key: string,
icon?: any,
children?: ItemType[],
type?: 'group'
): ItemType {
return {
key,
icon,
children,
label,
type,
} as ItemType;
}
// menuItems a-menu items
const items = computed(() => {
return menuItems.map((item) => {
if (item.children && item.children.length > 0) {
const childItems = item.children.map((child) =>
getItem(child.name, child.path)
);
return getItem(item.name, item.path, h(item.icon), childItems);
} else {
return getItem(item.name, item.path, h(item.icon));
}
});
});
const handleMenuSelect = ({ key }: { key: string }) => {
console.log("11!",key);
router.push(key);
};
const changeMode = (checked: boolean) => { const changeMode = (checked: boolean) => {
state.mode = checked ? 'vertical' : 'inline'; state.mode = checked ? 'vertical' : 'inline';
}; };
const changeTheme = (checked: boolean) => { const changeTheme = (checked: boolean) => {
state.theme = checked ? 'dark' : 'light'; state.theme = checked ? 'dark' : 'light';
}; };
</script> </script>
<style scoped>
.admin-layout {
display: flex;
height: 100vh; /* 全屏高度 */
width: 100vw;
overflow: hidden;
}
.admin-sidebar {
width: 256px; /* 和你原来设定一致 */
flex-shrink: 0; /* 防止被压缩 */
height: 100%;
}
.admin-contain {
flex: 1; /* 占据剩余空间 */
padding: 20px;
overflow-y: auto; /* 内容超出可滚动 */
background-color: #f0f2f5; /* 可选:和 Ant Design 风格一致 */
}
</style>

View File

@ -1,7 +1,13 @@
<!-- src/components/AccountSecurity.vue --> <!-- src/components/AccountSecurity.vue -->
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref, reactive } from 'vue';
import { CheckCircleOutlined, ExclamationCircleOutlined } from '@ant-design/icons-vue'; import {
CheckCircleOutlined,
ExclamationCircleOutlined,
CloseOutlined,
SaveOutlined,
} from '@ant-design/icons-vue';
import { Modal, message } from 'ant-design-vue';
// //
interface SecurityItem { interface SecurityItem {
@ -18,7 +24,7 @@ const securityItems = ref<SecurityItem[]>([
description: '安全性高的密码可以使账号更安全。建议您定期更换密码设置一个包含字母和数字且长度超过8位的密码', description: '安全性高的密码可以使账号更安全。建议您定期更换密码设置一个包含字母和数字且长度超过8位的密码',
status: 'unset', status: 'unset',
actionText: '设置', actionText: '设置',
actionHandler: () => alert('跳转到设置密码页面'), actionHandler: () => openSetPasswordModal(),
}, },
{ {
title: '手机绑定', title: '手机绑定',
@ -50,7 +56,7 @@ const securityItems = ref<SecurityItem[]>([
}, },
]); ]);
//
const getStatusIcon = (status: SecurityItem['status']) => { const getStatusIcon = (status: SecurityItem['status']) => {
switch (status) { switch (status) {
case 'bound': case 'bound':
@ -76,10 +82,87 @@ const getStatusColor = (status: SecurityItem['status']) => {
}; };
const getActionColor = (status: SecurityItem['status']) => { const getActionColor = (status: SecurityItem['status']) => {
if (status === 'bound') { return 'blue'; //
return 'blue'; };
//
const setPasswordModalVisible = ref(false);
//
const form = reactive({
phoneNumber: '+86 178****5075', //
smsCode: '',
newPassword: '',
confirmPassword: '',
});
//
const countdown = ref(0);
const isSending = ref(false);
//
const sendSmsCode = () => {
if (isSending.value) return;
isSending.value = true;
countdown.value = 60;
const timer = setInterval(() => {
countdown.value--;
if (countdown.value <= 0) {
clearInterval(timer);
isSending.value = false;
countdown.value = 0;
}
}, 1000);
message.success('验证码已发送');
};
//
const resetForm = () => {
form.smsCode = '';
form.newPassword = '';
form.confirmPassword = '';
countdown.value = 0;
isSending.value = false;
};
//
const openSetPasswordModal = () => {
resetForm();
setPasswordModalVisible.value = true;
};
//
const closeSetPasswordModal = () => {
setPasswordModalVisible.value = false;
resetForm();
};
//
const savePassword = () => {
if (!form.smsCode) {
message.error('请输入验证码');
return;
} }
return 'blue'; // if (!form.newPassword) {
message.error('请输入新密码');
return;
}
if (!form.confirmPassword) {
message.error('请确认密码');
return;
}
if (form.newPassword !== form.confirmPassword) {
message.error('两次输入的密码不一致');
return;
}
if (!/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,16}$/.test(form.newPassword)) {
message.error('密码必须为8~16位且包含字母和数字');
return;
}
//
message.success('密码设置成功!');
closeSetPasswordModal();
}; };
</script> </script>
@ -110,6 +193,66 @@ const getActionColor = (status: SecurityItem['status']) => {
</div> </div>
<p class="description">{{ item.description }}</p> <p class="description">{{ item.description }}</p>
</div> </div>
<!-- 设置密码弹窗 -->
<a-modal
v-model:visible="setPasswordModalVisible"
title="设置密码"
width="500px"
:footer="null"
@cancel="closeSetPasswordModal"
>
<div style="padding: 24px 0;">
<div style="margin-bottom: 24px; font-size: 16px; color: #333;">
手机号{{ form.phoneNumber }}
</div>
<div style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;">
<span style="color: red;">*</span>
<label style="font-size: 14px; color: #333;">手机验证码</label>
<a-input
v-model:value="form.smsCode"
placeholder="请输入验证码"
style="flex: 1;"
/>
<a-button
type="primary"
:disabled="isSending || countdown > 0"
@click="sendSmsCode"
style="width: 120px;"
>
{{ isSending || countdown > 0 ? `${countdown}s后重发` : '发送验证码' }}
</a-button>
</div>
<div style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;">
<span style="color: red;">*</span>
<label style="font-size: 14px; color: #333;">设置新密码</label>
<a-input
v-model:value="form.newPassword"
placeholder="请输入816位包含数字和字母的密码"
type="password"
style="flex: 1;"
/>
</div>
<div style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;">
<span style="color: red;">*</span>
<label style="font-size: 14px; color: #3 33;">确认新密码</label>
<a-input
v-model:value="form.confirmPassword"
placeholder="请确认密码"
type="password"
style="flex: 1;"
/>
</div>
</div>
<div style="text-align: right; padding: 16px 24px;">
<a-button @click="closeSetPasswordModal" style="margin-right: 8px;">取消</a-button>
<a-button type="primary" @click="savePassword">保存</a-button>
</div>
</a-modal>
</div> </div>
</template> </template>