Merge branch 'main' of https://gitlab.guxuan.icu/Leo_Ding/GPU_Web
This commit is contained in:
commit
386a64ee3e
@ -72,6 +72,22 @@ const routes: RouteRecordRaw[] = [
|
|||||||
name: "Security",
|
name: "Security",
|
||||||
component: () => import("@/views/admin/account/security/index.vue"),
|
component: () => import("@/views/admin/account/security/index.vue"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "realnameAuth",
|
||||||
|
name: "RealnameAuth",
|
||||||
|
component: () => import("@/views/admin/account/realnameAuth/index.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "enterRealAuth",
|
||||||
|
name: "EnterRealAuth",
|
||||||
|
component: () => import("@/views/admin/account/enterRealAuth/index.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "image",
|
||||||
|
name: "Image",
|
||||||
|
component: () => import("@/views/admin/image/index.vue"),
|
||||||
|
},
|
||||||
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
419
src/views/admin/account/enterRealAuth/index.vue
Normal file
419
src/views/admin/account/enterRealAuth/index.vue
Normal file
@ -0,0 +1,419 @@
|
|||||||
|
<!-- src/views/EnterpriseRealNameAuth.vue -->
|
||||||
|
<template>
|
||||||
|
<div class="enterprise-real-name-auth">
|
||||||
|
<!-- 步骤条 -->
|
||||||
|
<a-steps :current="0" class="steps">
|
||||||
|
<a-step title="填写证件信息" />
|
||||||
|
<a-step title="后台审核" />
|
||||||
|
<a-step title="完成认证" />
|
||||||
|
</a-steps>
|
||||||
|
|
||||||
|
<!-- 表单 -->
|
||||||
|
<a-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="formState"
|
||||||
|
:label-col="{ span: 4 }"
|
||||||
|
:wrapper-col="{ span: 20 }"
|
||||||
|
layout="horizontal"
|
||||||
|
class="auth-form"
|
||||||
|
>
|
||||||
|
<!-- 企业信息 -->
|
||||||
|
<div class="section-title">企业信息</div>
|
||||||
|
|
||||||
|
<a-alert
|
||||||
|
message="企业信息、法人/被授权人信息仅用于进行实名认证,不会泄露您的信息"
|
||||||
|
type="warning"
|
||||||
|
show-icon
|
||||||
|
style="margin-bottom: 24px"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<a-form-item
|
||||||
|
label="企业证件类型:"
|
||||||
|
name="certType"
|
||||||
|
:rules="[{ required: true, message: '请选择企业证件类型' }]"
|
||||||
|
>
|
||||||
|
<a-select
|
||||||
|
v-model:value="formState.certType"
|
||||||
|
placeholder="请选择"
|
||||||
|
style="width: 400px"
|
||||||
|
>
|
||||||
|
<a-select-option value="business_license">营业执照</a-select-option>
|
||||||
|
<!-- 可扩展其他类型 -->
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item
|
||||||
|
label="上传企业证件附件:"
|
||||||
|
:rules="[{ required: true, validator: validateBusinessLicense }]"
|
||||||
|
>
|
||||||
|
<div class="upload-area">
|
||||||
|
<a-upload
|
||||||
|
name="file"
|
||||||
|
list-type="picture-card"
|
||||||
|
:show-upload-list="false"
|
||||||
|
:before-upload="beforeUpload"
|
||||||
|
@change="handleBusinessLicenseChange"
|
||||||
|
accept="image/png,image/jpeg"
|
||||||
|
>
|
||||||
|
<div class="upload-placeholder">
|
||||||
|
<UserOutlined />
|
||||||
|
<div style="margin-top: 8px">企业法人营业执照</div>
|
||||||
|
</div>
|
||||||
|
</a-upload>
|
||||||
|
<div v-if="formState.businessLicenseUrl" class="upload-preview">
|
||||||
|
<img :src="formState.businessLicenseUrl" alt="营业执照" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="upload-tips">支持 jpg、jpeg、png 格式图片,大小不超过 10M</div>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item
|
||||||
|
label="企业名称:"
|
||||||
|
name="enterpriseName"
|
||||||
|
:rules="[{ required: true, message: '请输入企业名称' }]"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="formState.enterpriseName"
|
||||||
|
placeholder="请输入企业名称"
|
||||||
|
style="width: 400px"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item
|
||||||
|
label="统一社会信用代码:"
|
||||||
|
name="creditCode"
|
||||||
|
:rules="[
|
||||||
|
{ required: true, message: '请输入统一社会信用代码' },
|
||||||
|
{ pattern: /^[0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}$/, message: '请输入有效的统一社会信用代码' }
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="formState.creditCode"
|
||||||
|
placeholder="请输入统一社会信用代码"
|
||||||
|
style="width: 400px"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 法人/被授权人信息 -->
|
||||||
|
<div class="section-title">法人/被授权人信息</div>
|
||||||
|
|
||||||
|
<a-form-item
|
||||||
|
label="请选择您的身份:"
|
||||||
|
name="identity"
|
||||||
|
:rules="[{ required: true, message: '请选择身份' }]"
|
||||||
|
>
|
||||||
|
<a-radio-group v-model:value="formState.identity">
|
||||||
|
<a-radio value="legal_representative">法定代表人</a-radio>
|
||||||
|
<a-radio value="authorized_person">被授权人</a-radio>
|
||||||
|
</a-radio-group>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 身份证正反面 -->
|
||||||
|
<a-form-item
|
||||||
|
label="上传身份证件附件:"
|
||||||
|
:rules="[
|
||||||
|
{ required: true, validator: validateIdCardFront },
|
||||||
|
{ required: true, validator: validateIdCardBack }
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<div class="id-card-uploads">
|
||||||
|
<!-- 正面 -->
|
||||||
|
<div class="upload-area">
|
||||||
|
<a-upload
|
||||||
|
name="file"
|
||||||
|
list-type="picture-card"
|
||||||
|
:show-upload-list="false"
|
||||||
|
:before-upload="beforeUpload"
|
||||||
|
@change="handleIdCardFrontChange"
|
||||||
|
accept="image/png,image/jpeg"
|
||||||
|
>
|
||||||
|
<div class="upload-placeholder">
|
||||||
|
<IdcardOutlined />
|
||||||
|
<div style="margin-top: 8px">身份证件正面</div>
|
||||||
|
</div>
|
||||||
|
</a-upload>
|
||||||
|
<div v-if="formState.idCardFrontUrl" class="upload-preview">
|
||||||
|
<img :src="formState.idCardFrontUrl" alt="身份证正面" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 背面 -->
|
||||||
|
<div class="upload-area">
|
||||||
|
<a-upload
|
||||||
|
name="file"
|
||||||
|
list-type="picture-card"
|
||||||
|
:show-upload-list="false"
|
||||||
|
:before-upload="beforeUpload"
|
||||||
|
@change="handleIdCardBackChange"
|
||||||
|
accept="image/png,image/jpeg"
|
||||||
|
>
|
||||||
|
<div class="upload-placeholder">
|
||||||
|
<IdcardOutlined />
|
||||||
|
<div style="margin-top: 8px">身份证件背面</div>
|
||||||
|
</div>
|
||||||
|
</a-upload>
|
||||||
|
<div v-if="formState.idCardBackUrl" class="upload-preview">
|
||||||
|
<img :src="formState.idCardBackUrl" alt="身份证背面" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="upload-tips">支持 jpg、jpeg、png 格式图片,大小不超过 10M</div>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item
|
||||||
|
label="姓名:"
|
||||||
|
name="name"
|
||||||
|
:rules="[{ required: true, message: '请输入姓名' }]"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="formState.name"
|
||||||
|
placeholder="请输入姓名"
|
||||||
|
style="width: 400px"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item
|
||||||
|
label="证件号码:"
|
||||||
|
name="idNumber"
|
||||||
|
:rules="[
|
||||||
|
{ required: true, message: '请输入证件号码' },
|
||||||
|
{ pattern: /^\d{17}[\dXx]$/, message: '请输入有效的18位身份证号码' }
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="formState.idNumber"
|
||||||
|
placeholder="请输入证件号码"
|
||||||
|
style="width: 400px"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 协议同意 -->
|
||||||
|
<a-form-item :wrapper-col="{ offset: 4 }">
|
||||||
|
<a-checkbox v-model:checked="formState.agreed">
|
||||||
|
同意
|
||||||
|
<a href="/docs/real_name_cert/" target="_blank">《AutoDL实名认证服务协议》</a>
|
||||||
|
<a href="/docs/privacy_policy/" target="_blank">《隐私政策》</a>
|
||||||
|
,请务必提供真实信息,AutoDL算力云有权自行或委托第三方,审查您提供的信息是否真实/有效,若提供虚假信息,由此带来的后果由您承担。
|
||||||
|
</a-checkbox>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 操作按钮 -->
|
||||||
|
<a-form-item :wrapper-col="{ offset: 4 }">
|
||||||
|
<a-button type="primary" @click="handleSubmit">确认提交</a-button>
|
||||||
|
<a-button style="margin-left: 12px" @click="handleCancel">取消</a-button>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
ref,
|
||||||
|
reactive,
|
||||||
|
UploadChangeParam,
|
||||||
|
UploadFile
|
||||||
|
} from 'vue';
|
||||||
|
import {
|
||||||
|
message,
|
||||||
|
FormInstance,
|
||||||
|
UploadProps
|
||||||
|
} from 'ant-design-vue';
|
||||||
|
import {
|
||||||
|
UserOutlined,
|
||||||
|
IdcardOutlined
|
||||||
|
} from '@ant-design/icons-vue';
|
||||||
|
|
||||||
|
// 表单引用
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
interface FormState {
|
||||||
|
certType: string | null;
|
||||||
|
businessLicenseUrl: string | null;
|
||||||
|
enterpriseName: string;
|
||||||
|
creditCode: string;
|
||||||
|
identity: 'legal_representative' | 'authorized_person' | null;
|
||||||
|
idCardFrontUrl: string | null;
|
||||||
|
idCardBackUrl: string | null;
|
||||||
|
name: string;
|
||||||
|
idNumber: string;
|
||||||
|
agreed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formState = reactive<FormState>({
|
||||||
|
certType: null,
|
||||||
|
businessLicenseUrl: null,
|
||||||
|
enterpriseName: '',
|
||||||
|
creditCode: '',
|
||||||
|
identity: null,
|
||||||
|
idCardFrontUrl: null,
|
||||||
|
idCardBackUrl: null,
|
||||||
|
name: '',
|
||||||
|
idNumber: '',
|
||||||
|
agreed: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// 文件大小限制:10MB
|
||||||
|
const MAX_FILE_SIZE = 10 * 1024 * 1024;
|
||||||
|
|
||||||
|
// 上传前校验
|
||||||
|
const beforeUpload: UploadProps['beforeUpload'] = (file) => {
|
||||||
|
const isLt10M = file.size < MAX_FILE_SIZE;
|
||||||
|
if (!isLt10M) {
|
||||||
|
message.error('文件大小不能超过 10MB!');
|
||||||
|
}
|
||||||
|
const isValidType = ['image/jpeg', 'image/png'].includes(file.type);
|
||||||
|
if (!isValidType) {
|
||||||
|
message.error('仅支持 JPG/PNG 格式!');
|
||||||
|
}
|
||||||
|
return isLt10M && isValidType;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理营业执照上传
|
||||||
|
const handleBusinessLicenseChange = (info: UploadChangeParam<UploadFile>) => {
|
||||||
|
if (info.file.status === 'done') {
|
||||||
|
// 模拟获取 URL(实际应调用上传接口)
|
||||||
|
const url = URL.createObjectURL(info.file.originFileObj!);
|
||||||
|
formState.businessLicenseUrl = url;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理身份证正面
|
||||||
|
const handleIdCardFrontChange = (info: UploadChangeParam<UploadFile>) => {
|
||||||
|
if (info.file.status === 'done') {
|
||||||
|
const url = URL.createObjectURL(info.file.originFileObj!);
|
||||||
|
formState.idCardFrontUrl = url;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理身份证背面
|
||||||
|
const handleIdCardBackChange = (info: UploadChangeParam<UploadFile>) => {
|
||||||
|
if (info.file.status === 'done') {
|
||||||
|
const url = URL.createObjectURL(info.file.originFileObj!);
|
||||||
|
formState.idCardBackUrl = url;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 自定义校验函数
|
||||||
|
const validateBusinessLicense = () => {
|
||||||
|
if (!formState.businessLicenseUrl) {
|
||||||
|
return Promise.reject(new Error('请上传企业证件'));
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateIdCardFront = () => {
|
||||||
|
if (!formState.idCardFrontUrl) {
|
||||||
|
return Promise.reject(new Error('请上传身份证正面'));
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateIdCardBack = () => {
|
||||||
|
if (!formState.idCardBackUrl) {
|
||||||
|
return Promise.reject(new Error('请上传身份证背面'));
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 提交
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
await formRef.value?.validateFields();
|
||||||
|
if (!formState.agreed) {
|
||||||
|
message.error('请阅读并同意协议');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
message.success('提交成功!');
|
||||||
|
// TODO: 调用 API 提交表单
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Validation failed:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 取消
|
||||||
|
const handleCancel = () => {
|
||||||
|
// TODO: 返回上一页或重置
|
||||||
|
message.info('已取消');
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.enterprise-real-name-auth {
|
||||||
|
padding: 24px;
|
||||||
|
background-color: #fff;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.steps {
|
||||||
|
max-width: 800px;
|
||||||
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2937;
|
||||||
|
margin: 24px 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-area {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 24px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-placeholder {
|
||||||
|
text-align: center;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-placeholder i {
|
||||||
|
font-size: 24px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-preview {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-preview img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.id-card-uploads {
|
||||||
|
display: flex;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-tips {
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.auth-form :deep(.ant-form-item-label) {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.id-card-uploads {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-area {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
310
src/views/admin/account/realnameAuth/index.vue
Normal file
310
src/views/admin/account/realnameAuth/index.vue
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
<!-- src/views/RealNameAuth.vue -->
|
||||||
|
<template>
|
||||||
|
<div class="real-name-auth-page">
|
||||||
|
<div class="header">
|
||||||
|
<h1>实名认证</h1>
|
||||||
|
<p>请选择您的认证类型</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="auth-container">
|
||||||
|
<!-- 个人认证 -->
|
||||||
|
<a-card class="auth-card" :bordered="true">
|
||||||
|
<template #title>
|
||||||
|
<div class="card-title">
|
||||||
|
<UserOutlined class="title-icon" />
|
||||||
|
<span>个人认证</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="auth-content" @click="openPersonalAuthModal">
|
||||||
|
<div class="option-list">
|
||||||
|
<div class="option-item">
|
||||||
|
<CheckCircleOutlined class="option-icon" />
|
||||||
|
<div class="option-info">
|
||||||
|
<div class="option-title">个人身份证认证</div>
|
||||||
|
<div class="option-desc">快速认证,保障账号安全</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
|
||||||
|
<!-- 企业认证 -->
|
||||||
|
<a-card class="auth-card" :bordered="true">
|
||||||
|
<template #title>
|
||||||
|
<div class="card-title">
|
||||||
|
<BankOutlined class="title-icon" />
|
||||||
|
<span>企业认证</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #extra>
|
||||||
|
<span class="extra-text">支持对公打款认证、证件认证</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="auth-content">
|
||||||
|
<div class="option-list">
|
||||||
|
<div class="option-item" @click="goEnterRealAuth">
|
||||||
|
<CheckCircleOutlined class="option-icon" />
|
||||||
|
<div class="option-info">
|
||||||
|
<div class="option-title">企业证件认证</div>
|
||||||
|
<div class="option-desc">营业执照等企业资质认证</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="option-item disabled">
|
||||||
|
<ClockCircleOutlined class="option-icon" />
|
||||||
|
<div class="option-info">
|
||||||
|
<div class="option-title">银行对公账户认证</div>
|
||||||
|
<div class="option-desc">即将上线,敬请期待</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 个人实名认证弹窗 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:open="personalAuthModalVisible"
|
||||||
|
title="实名认证"
|
||||||
|
width="500px"
|
||||||
|
:footer="null"
|
||||||
|
@cancel="closePersonalAuthModal"
|
||||||
|
>
|
||||||
|
<div style="padding: 24px;">
|
||||||
|
<!-- 提示信息 -->
|
||||||
|
<div
|
||||||
|
style="margin-bottom: 24px; padding: 12px; background: #fff7e6; border: 1px solid #fda4af; color: #f59e0b; border-radius: 4px; font-size: 14px; line-height: 1.5;"
|
||||||
|
>
|
||||||
|
<span style="color: #f59e0b; margin-right: 8px;">⚠️</span>
|
||||||
|
您的实名认证信息将会加密保存,不会泄露给第三方
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 姓名输入 -->
|
||||||
|
<div style="margin-bottom: 24px; 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="personalForm.realName" placeholder="请输入您的姓名" style="flex: 1;" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 身份证号输入 -->
|
||||||
|
<div style="margin-bottom: 24px; 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="personalForm.idCard" placeholder="请输入您的身份证号" style="flex: 1;" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 按钮区 -->
|
||||||
|
<div style="text-align: right; padding: 16px 0;">
|
||||||
|
<a-button @click="closePersonalAuthModal">取消</a-button>
|
||||||
|
<a-button type="primary" @click="submitPersonalAuth" style="margin-left: 8px;">确定</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive } from 'vue';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import {
|
||||||
|
UserOutlined,
|
||||||
|
BankOutlined,
|
||||||
|
CheckCircleOutlined,
|
||||||
|
ClockCircleOutlined
|
||||||
|
} from '@ant-design/icons-vue';
|
||||||
|
import router from '../../../../router';
|
||||||
|
|
||||||
|
// 控制个人认证弹窗
|
||||||
|
const personalAuthModalVisible = ref(false);
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
const personalForm = reactive({
|
||||||
|
realName: '',
|
||||||
|
idCard: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 打开个人认证弹窗
|
||||||
|
const openPersonalAuthModal = () => {
|
||||||
|
personalAuthModalVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 跳转到企业认证页面
|
||||||
|
const goEnterRealAuth = () => {
|
||||||
|
// TODO: 跳转到企业认证页面
|
||||||
|
router.push('/layout/admin/enterRealAuth')
|
||||||
|
};
|
||||||
|
|
||||||
|
// 关闭弹窗并重置表单
|
||||||
|
const closePersonalAuthModal = () => {
|
||||||
|
personalAuthModalVisible.value = false;
|
||||||
|
personalForm.realName = '';
|
||||||
|
personalForm.idCard = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 提交认证
|
||||||
|
const submitPersonalAuth = () => {
|
||||||
|
if (!personalForm.realName.trim()) {
|
||||||
|
message.error('请输入真实姓名');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!personalForm.idCard.trim()) {
|
||||||
|
message.error('请输入身份证号');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const idCardRegex = /^\d{17}[\dXx]$/;
|
||||||
|
if (!idCardRegex.test(personalForm.idCard)) {
|
||||||
|
message.error('请输入有效的18位身份证号码');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
message.success('实名认证提交成功!');
|
||||||
|
closePersonalAuthModal();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.real-name-auth-page {
|
||||||
|
padding: 24px;
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
text-align: left;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
font-size: 28px;
|
||||||
|
color: #1f2937;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header p {
|
||||||
|
color: #6b7280;
|
||||||
|
font-size: 16px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-card {
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-card:hover {
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-icon {
|
||||||
|
margin-right: 8px;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.extra-text {
|
||||||
|
color: #6b7280;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-content {
|
||||||
|
padding: 8px 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-list {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 16px;
|
||||||
|
background: #f8fafc;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-item:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-item.disabled {
|
||||||
|
background: #f9fafb;
|
||||||
|
border-color: #f3f4f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-icon {
|
||||||
|
margin-right: 12px;
|
||||||
|
font-size: 20px;
|
||||||
|
margin-top: 2px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-item:not(.disabled) .option-icon {
|
||||||
|
color: #10b981;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-item.disabled .option-icon {
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-title {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #374151;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-item.disabled .option-title {
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-desc {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-item.disabled .option-desc {
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.real-name-auth-page {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-container {
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-item {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -8,54 +8,10 @@ import {
|
|||||||
SaveOutlined,
|
SaveOutlined,
|
||||||
} from '@ant-design/icons-vue';
|
} from '@ant-design/icons-vue';
|
||||||
import { Modal, message } from 'ant-design-vue';
|
import { Modal, message } from 'ant-design-vue';
|
||||||
|
// 在 <script setup> 中引入 router
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
// 定义安全项接口
|
const router = useRouter();
|
||||||
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']) => {
|
const getStatusIcon = (status: SecurityItem['status']) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
@ -85,23 +41,92 @@ const getActionColor = (status: SecurityItem['status']) => {
|
|||||||
return 'blue'; // 统一蓝色即可
|
return 'blue'; // 统一蓝色即可
|
||||||
};
|
};
|
||||||
|
|
||||||
// 弹窗控制
|
|
||||||
|
// 安全项接口
|
||||||
|
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: () => openEditPhoneModal(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '实名认证',
|
||||||
|
description: '实名认证后可以使用AutoDL更完整的功能,如打开实例的自定义服务等',
|
||||||
|
status: 'unverified',
|
||||||
|
actionText: '立即认证',
|
||||||
|
actionHandler: () => {
|
||||||
|
router.push('/layout/admin/realnameAuth');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// title: '微信绑定',
|
||||||
|
// description: '您已绑定微信,可快速扫码登录',
|
||||||
|
// status: 'bound',
|
||||||
|
// actionText: '解绑',
|
||||||
|
// actionHandler: () => alert('确认解绑微信?'),
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
title: '邮箱绑定(即将上线)',
|
||||||
|
description: '绑定邮箱后可接收系统消息,如余额不足、实例即将到期、实例即将释放等消息',
|
||||||
|
status: 'unset',
|
||||||
|
actionText: '绑定',
|
||||||
|
actionHandler: () => alert('跳转到绑定邮箱页面'),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 设置密码弹窗
|
||||||
const setPasswordModalVisible = ref(false);
|
const setPasswordModalVisible = ref(false);
|
||||||
|
const editPhoneModalVisible = ref(false);
|
||||||
|
// 新增:用于控制实名认证弹窗
|
||||||
|
const realNameModalVisible = ref(false);
|
||||||
|
|
||||||
|
// 打开实名认证弹窗
|
||||||
|
const openRealNameAuthModal = () => {
|
||||||
|
realNameModalVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 关闭实名认证弹窗
|
||||||
|
const closeRealNameAuthModal = () => {
|
||||||
|
realNameModalVisible.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
// 表单数据
|
// 表单数据
|
||||||
const form = reactive({
|
const passwordForm = reactive({
|
||||||
phoneNumber: '+86 178****5075', // 示例手机号
|
|
||||||
smsCode: '',
|
smsCode: '',
|
||||||
newPassword: '',
|
newPassword: '',
|
||||||
confirmPassword: '',
|
confirmPassword: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const phoneForm = reactive({
|
||||||
|
countryCode: '+86',
|
||||||
|
newPhoneNumber: '',
|
||||||
|
smsCode: '',
|
||||||
|
password: '', // 登录密码
|
||||||
|
});
|
||||||
|
|
||||||
// 验证码倒计时
|
// 验证码倒计时
|
||||||
const countdown = ref(0);
|
const countdown = ref(0);
|
||||||
const isSending = ref(false);
|
const isSending = ref(false);
|
||||||
|
|
||||||
// 发送验证码
|
// 发送验证码
|
||||||
const sendSmsCode = () => {
|
const sendSmsCode = (formType: 'password' | 'phone') => {
|
||||||
if (isSending.value) return;
|
if (isSending.value) return;
|
||||||
isSending.value = true;
|
isSending.value = true;
|
||||||
countdown.value = 60;
|
countdown.value = 60;
|
||||||
@ -116,54 +141,98 @@ const sendSmsCode = () => {
|
|||||||
message.success('验证码已发送');
|
message.success('验证码已发送');
|
||||||
};
|
};
|
||||||
|
|
||||||
// 重置表单
|
|
||||||
const resetForm = () => {
|
|
||||||
form.smsCode = '';
|
|
||||||
form.newPassword = '';
|
|
||||||
form.confirmPassword = '';
|
|
||||||
countdown.value = 0;
|
|
||||||
isSending.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 打开设置密码弹窗
|
// 打开设置密码弹窗
|
||||||
const openSetPasswordModal = () => {
|
const openSetPasswordModal = () => {
|
||||||
resetForm();
|
resetPasswordForm();
|
||||||
setPasswordModalVisible.value = true;
|
setPasswordModalVisible.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 打开修改手机号弹窗
|
||||||
|
const openEditPhoneModal = () => {
|
||||||
|
resetPhoneForm();
|
||||||
|
editPhoneModalVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
// 关闭弹窗
|
// 关闭弹窗
|
||||||
const closeSetPasswordModal = () => {
|
const closeSetPasswordModal = () => {
|
||||||
setPasswordModalVisible.value = false;
|
setPasswordModalVisible.value = false;
|
||||||
resetForm();
|
resetPasswordForm();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const closeEditPhoneModal = () => {
|
||||||
|
editPhoneModalVisible.value = false;
|
||||||
|
resetPhoneForm();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 重置表单
|
||||||
|
const resetPasswordForm = () => {
|
||||||
|
passwordForm.smsCode = '';
|
||||||
|
passwordForm.newPassword = '';
|
||||||
|
passwordForm.confirmPassword = '';
|
||||||
|
countdown.value = 0;
|
||||||
|
isSending.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetPhoneForm = () => {
|
||||||
|
phoneForm.countryCode = '+86';
|
||||||
|
phoneForm.newPhoneNumber = '';
|
||||||
|
phoneForm.smsCode = '';
|
||||||
|
phoneForm.password = '';
|
||||||
|
countdown.value = 0;
|
||||||
|
isSending.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 保存密码
|
// 保存密码
|
||||||
const savePassword = () => {
|
const savePassword = () => {
|
||||||
if (!form.smsCode) {
|
if (!passwordForm.smsCode) {
|
||||||
message.error('请输入验证码');
|
message.error('请输入验证码');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!form.newPassword) {
|
if (!passwordForm.newPassword) {
|
||||||
message.error('请输入新密码');
|
message.error('请输入新密码');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!form.confirmPassword) {
|
if (!passwordForm.confirmPassword) {
|
||||||
message.error('请确认密码');
|
message.error('请确认密码');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (form.newPassword !== form.confirmPassword) {
|
if (passwordForm.newPassword !== passwordForm.confirmPassword) {
|
||||||
message.error('两次输入的密码不一致');
|
message.error('两次输入的密码不一致');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,16}$/.test(form.newPassword)) {
|
if (!/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,16}$/.test(passwordForm.newPassword)) {
|
||||||
message.error('密码必须为8~16位,且包含字母和数字');
|
message.error('密码必须为8~16位,且包含字母和数字');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 模拟提交成功
|
|
||||||
message.success('密码设置成功!');
|
message.success('密码设置成功!');
|
||||||
closeSetPasswordModal();
|
closeSetPasswordModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 保存手机号
|
||||||
|
const savePhone = () => {
|
||||||
|
if (!phoneForm.newPhoneNumber) {
|
||||||
|
message.error('请输入新手机号');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!/^\d{11}$/.test(phoneForm.newPhoneNumber)) {
|
||||||
|
message.error('请输入有效的11位手机号');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!phoneForm.smsCode) {
|
||||||
|
message.error('请输入验证码');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!phoneForm.password) {
|
||||||
|
message.error('请输入账号登录密码');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
message.success('手机号修改成功!');
|
||||||
|
closeEditPhoneModal();
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -204,21 +273,21 @@ const savePassword = () => {
|
|||||||
>
|
>
|
||||||
<div style="padding: 24px 0;">
|
<div style="padding: 24px 0;">
|
||||||
<div style="margin-bottom: 24px; font-size: 16px; color: #333;">
|
<div style="margin-bottom: 24px; font-size: 16px; color: #333;">
|
||||||
手机号:{{ form.phoneNumber }}
|
手机号:+86 178****5075
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;">
|
<div style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;">
|
||||||
<span style="color: red;">*</span>
|
<span style="color: red;">*</span>
|
||||||
<label style="font-size: 14px; color: #333;">手机验证码:</label>
|
<label style="font-size: 14px; color: #333;">手机验证码:</label>
|
||||||
<a-input
|
<a-input
|
||||||
v-model:value="form.smsCode"
|
v-model:value="passwordForm.smsCode"
|
||||||
placeholder="请输入验证码"
|
placeholder="请输入验证码"
|
||||||
style="flex: 1;"
|
style="flex: 1;"
|
||||||
/>
|
/>
|
||||||
<a-button
|
<a-button
|
||||||
type="primary"
|
type="primary"
|
||||||
:disabled="isSending || countdown > 0"
|
:disabled="isSending || countdown > 0"
|
||||||
@click="sendSmsCode"
|
@click="sendSmsCode('password')"
|
||||||
style="width: 120px;"
|
style="width: 120px;"
|
||||||
>
|
>
|
||||||
{{ isSending || countdown > 0 ? `${countdown}s后重发` : '发送验证码' }}
|
{{ isSending || countdown > 0 ? `${countdown}s后重发` : '发送验证码' }}
|
||||||
@ -229,7 +298,7 @@ const savePassword = () => {
|
|||||||
<span style="color: red;">*</span>
|
<span style="color: red;">*</span>
|
||||||
<label style="font-size: 14px; color: #333;">设置新密码:</label>
|
<label style="font-size: 14px; color: #333;">设置新密码:</label>
|
||||||
<a-input
|
<a-input
|
||||||
v-model:value="form.newPassword"
|
v-model:value="passwordForm.newPassword"
|
||||||
placeholder="请输入8~16位包含数字和字母的密码"
|
placeholder="请输入8~16位包含数字和字母的密码"
|
||||||
type="password"
|
type="password"
|
||||||
style="flex: 1;"
|
style="flex: 1;"
|
||||||
@ -240,7 +309,7 @@ const savePassword = () => {
|
|||||||
<span style="color: red;">*</span>
|
<span style="color: red;">*</span>
|
||||||
<label style="font-size: 14px; color: #333;">确认新密码:</label>
|
<label style="font-size: 14px; color: #333;">确认新密码:</label>
|
||||||
<a-input
|
<a-input
|
||||||
v-model:value="form.confirmPassword"
|
v-model:value="passwordForm.confirmPassword"
|
||||||
placeholder="请确认密码"
|
placeholder="请确认密码"
|
||||||
type="password"
|
type="password"
|
||||||
style="flex: 1;"
|
style="flex: 1;"
|
||||||
@ -253,6 +322,70 @@ const savePassword = () => {
|
|||||||
<a-button type="primary" @click="savePassword">保存</a-button>
|
<a-button type="primary" @click="savePassword">保存</a-button>
|
||||||
</div>
|
</div>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
|
<!-- 修改手机号弹窗 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:visible="editPhoneModalVisible"
|
||||||
|
title="修改手机号"
|
||||||
|
width="500px"
|
||||||
|
:footer="null"
|
||||||
|
@cancel="closeEditPhoneModal"
|
||||||
|
>
|
||||||
|
<div style="padding: 24px 0;">
|
||||||
|
<div style="margin-bottom: 24px; display: flex; align-items: center; gap: 8px;">
|
||||||
|
<span style="color: red;">*</span>
|
||||||
|
<label style="font-size: 14px; color: #333;">输入新手机号:</label>
|
||||||
|
<a-select
|
||||||
|
v-model:value="phoneForm.countryCode"
|
||||||
|
style="width: 80px;"
|
||||||
|
placeholder="+86"
|
||||||
|
>
|
||||||
|
<a-select-option value="+86">+86</a-select-option>
|
||||||
|
<a-select-option value="+852">+852</a-select-option>
|
||||||
|
<a-select-option value="+853">+853</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
<a-input
|
||||||
|
v-model:value="phoneForm.newPhoneNumber"
|
||||||
|
placeholder="请输入新手机号"
|
||||||
|
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: #333;">手机验证码:</label>
|
||||||
|
<a-input
|
||||||
|
v-model:value="phoneForm.smsCode"
|
||||||
|
placeholder="请输入验证码"
|
||||||
|
style="flex: 1;"
|
||||||
|
/>
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
:disabled="isSending || countdown > 0"
|
||||||
|
@click="sendSmsCode('phone')"
|
||||||
|
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="phoneForm.password"
|
||||||
|
placeholder="请输入账号登录密码"
|
||||||
|
type="password"
|
||||||
|
style="flex: 1;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="text-align: right; padding: 16px 24px;">
|
||||||
|
<a-button @click="closeEditPhoneModal" style="margin-right: 8px;">取消</a-button>
|
||||||
|
<a-button type="primary" @click="savePhone">确定</a-button>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
350
src/views/admin/image/index.vue
Normal file
350
src/views/admin/image/index.vue
Normal file
@ -0,0 +1,350 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, onMounted } from 'vue';
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
Pagination,
|
||||||
|
Progress,
|
||||||
|
message,
|
||||||
|
Button
|
||||||
|
} from 'ant-design-vue';
|
||||||
|
|
||||||
|
// 表格列定义
|
||||||
|
interface ImageItem {
|
||||||
|
uid: string;
|
||||||
|
name: string;
|
||||||
|
size: string;
|
||||||
|
status: string;
|
||||||
|
shareInfo: string;
|
||||||
|
source: string;
|
||||||
|
region: string;
|
||||||
|
baseImage: string;
|
||||||
|
createTime: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟数据
|
||||||
|
const imageList = ref<ImageItem[]>([
|
||||||
|
{
|
||||||
|
uid: 'img-001',
|
||||||
|
name: 'Ubuntu 20.04',
|
||||||
|
size: '2.5GB',
|
||||||
|
status: '可用',
|
||||||
|
shareInfo: '私有',
|
||||||
|
source: '系统镜像',
|
||||||
|
region: '华北1',
|
||||||
|
baseImage: 'ubuntu:20.04',
|
||||||
|
createTime: '2023-10-01 10:30:00'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uid: 'img-002',
|
||||||
|
name: 'CentOS 7',
|
||||||
|
size: '1.8GB',
|
||||||
|
status: '可用',
|
||||||
|
shareInfo: '共享',
|
||||||
|
source: '自定义',
|
||||||
|
region: '华东2',
|
||||||
|
baseImage: 'centos:7',
|
||||||
|
createTime: '2023-10-02 14:20:00'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uid: 'img-003',
|
||||||
|
name: 'Windows Server 2019',
|
||||||
|
size: '4.2GB',
|
||||||
|
status: '构建中',
|
||||||
|
shareInfo: '私有',
|
||||||
|
source: '自定义',
|
||||||
|
region: '华南1',
|
||||||
|
baseImage: 'windows:2019',
|
||||||
|
createTime: '2023-10-03 16:45:00'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uid: 'img-004',
|
||||||
|
name: 'Debian 11',
|
||||||
|
size: '1.9GB',
|
||||||
|
status: '可用',
|
||||||
|
shareInfo: '私有',
|
||||||
|
source: '系统镜像',
|
||||||
|
region: '华北2',
|
||||||
|
baseImage: 'debian:11',
|
||||||
|
createTime: '2023-10-04 09:15:00'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uid: 'img-005',
|
||||||
|
name: 'AlmaLinux 8',
|
||||||
|
size: '2.1GB',
|
||||||
|
status: '可用',
|
||||||
|
shareInfo: '共享',
|
||||||
|
source: '自定义',
|
||||||
|
region: '华东1',
|
||||||
|
baseImage: 'almalinux:8',
|
||||||
|
createTime: '2023-10-05 11:30:00'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 存储容量
|
||||||
|
const storageUsed = ref('12.5GB');
|
||||||
|
const storagePeak = ref('13.2GB');
|
||||||
|
const expectedFee = ref('0元');
|
||||||
|
const freeStorage = ref('30.00GB');
|
||||||
|
const paidStorage = ref('0GB');
|
||||||
|
|
||||||
|
// 计算进度百分比
|
||||||
|
const storagePercent = computed(() => {
|
||||||
|
const usedGB = parseFloat(storageUsed.value.replace('GB', ''));
|
||||||
|
const totalGB = 30;
|
||||||
|
return Math.min(100, Math.round((usedGB / totalGB) * 100));
|
||||||
|
});
|
||||||
|
|
||||||
|
// 分页配置
|
||||||
|
const pagination = reactive({
|
||||||
|
current: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: imageList.value.length,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 加载状态
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
// 定义列
|
||||||
|
const columns = [
|
||||||
|
{ title: '镜像UUID', dataIndex: 'uid', key: 'uid', width: 150 },
|
||||||
|
{ title: '镜像名称', dataIndex: 'name', key: 'name', width: 150 },
|
||||||
|
{ title: '大小', dataIndex: 'size', key: 'size', width: 100 },
|
||||||
|
{ title: '状态', dataIndex: 'status', key: 'status', width: 100 },
|
||||||
|
{ title: '共享信息', dataIndex: 'shareInfo', key: 'shareInfo', width: 100 },
|
||||||
|
{ title: '来源', dataIndex: 'source', key: 'source', width: 100 },
|
||||||
|
{ title: '缓存地区', dataIndex: 'region', key: 'region', width: 120 },
|
||||||
|
{ title: '原基础镜像信息', dataIndex: 'baseImage', key: 'baseImage', width: 180 },
|
||||||
|
{ title: '创建时间', dataIndex: 'createTime', key: 'createTime', width: 160 },
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'action',
|
||||||
|
slots: { customRender: 'action' },
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 分页事件处理
|
||||||
|
const onPageChange = (page: number, pageSize: number) => {
|
||||||
|
console.log(`切换到第 ${page} 页,每页 ${pageSize} 条`);
|
||||||
|
pagination.current = page;
|
||||||
|
pagination.pageSize = pageSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onShowSizeChange = (current: number, pageSize: number) => {
|
||||||
|
console.log(`每页显示 ${pageSize} 条`);
|
||||||
|
pagination.current = current;
|
||||||
|
pagination.pageSize = pageSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 操作处理函数
|
||||||
|
const handleView = (record: ImageItem) => {
|
||||||
|
message.info(`查看镜像: ${record.name}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = (record: ImageItem) => {
|
||||||
|
message.info(`删除镜像: ${record.name}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 计算当前页数据
|
||||||
|
const getCurrentPageData = computed(() => {
|
||||||
|
const start = (pagination.current - 1) * pagination.pageSize;
|
||||||
|
const end = start + pagination.pageSize;
|
||||||
|
return imageList.value.slice(start, end);
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
console.log('MyImages 组件已挂载');
|
||||||
|
});
|
||||||
|
|
||||||
|
import { computed } from 'vue';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="my-images-page">
|
||||||
|
<!-- 顶部警告提示 -->
|
||||||
|
<div class="top-warning">
|
||||||
|
<span class="title">我的镜像</span>
|
||||||
|
<span class="warning-text">
|
||||||
|
连续3个月未登录或欠费50元以上,平台保留删除数据的权利,具体规则请参考
|
||||||
|
<a href="#" class="link">文档</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 存储容量信息 -->
|
||||||
|
<div class="storage-info">
|
||||||
|
<div class="storage-desc">
|
||||||
|
存储容量大小:<strong>{{ storageUsed }}</strong>
|
||||||
|
(今天容量使用峰值{{ storagePeak }},预计扣费{{ expectedFee }})
|
||||||
|
</div>
|
||||||
|
<a href="#" class="view-rule">查看计费规则</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 存储进度条 -->
|
||||||
|
<div class="progress-container">
|
||||||
|
<a-progress :percent="storagePercent" :show-info="true" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 免费/付费容量 -->
|
||||||
|
<div class="capacity-info">
|
||||||
|
<span><span class="dot free"></span>免费:{{ freeStorage }}</span>
|
||||||
|
<span><span class="dot paid"></span>付费:{{ paidStorage }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 表格 -->
|
||||||
|
<div class="table-container">
|
||||||
|
<a-table
|
||||||
|
:data-source="getCurrentPageData"
|
||||||
|
:columns="columns"
|
||||||
|
:pagination="false"
|
||||||
|
:scroll="{ x: 'max-content', y: 400 }"
|
||||||
|
:row-key="(record) => record.uid"
|
||||||
|
:loading="loading"
|
||||||
|
class="image-table"
|
||||||
|
>
|
||||||
|
<template #action="{ record }">
|
||||||
|
<a-button type="link" size="small" @click="handleView(record)">查看</a-button>
|
||||||
|
<a-button type="link" size="small" danger @click="handleDelete(record)">删除</a-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #empty>
|
||||||
|
<div class="table-empty">暂无数据</div>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 分页固定在底部 -->
|
||||||
|
<div class="pagination-container">
|
||||||
|
<a-pagination
|
||||||
|
v-model:current="pagination.current"
|
||||||
|
v-model:page-size="pagination.pageSize"
|
||||||
|
:total="pagination.total"
|
||||||
|
show-size-changer
|
||||||
|
show-jumper
|
||||||
|
show-less-items
|
||||||
|
:page-size-options="[5, 10, 20, 50]"
|
||||||
|
@change="onPageChange"
|
||||||
|
@showSizeChange="onShowSizeChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.my-images-page {
|
||||||
|
padding: 24px;
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.top-warning {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #000;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-text {
|
||||||
|
color: #f56a00;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
color: #1890ff;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.storage-info {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
background: #e6f7ff;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #333;
|
||||||
|
|
||||||
|
.storage-desc {
|
||||||
|
font-size: 14px;
|
||||||
|
strong {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-rule {
|
||||||
|
color: #1890ff;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-container {
|
||||||
|
margin: 16px 0;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.capacity-info {
|
||||||
|
display: flex;
|
||||||
|
gap: 32px;
|
||||||
|
margin: 16px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
display: inline-block;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.free {
|
||||||
|
background-color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paid {
|
||||||
|
background-color: #f56a00;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
flex: 1;
|
||||||
|
margin: 24px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-table {
|
||||||
|
background: white;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-empty {
|
||||||
|
text-align: center;
|
||||||
|
padding: 60px 0;
|
||||||
|
color: #999;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-container {
|
||||||
|
margin-top: auto;
|
||||||
|
padding-top: 24px;
|
||||||
|
text-align: center;
|
||||||
|
border-top: 1px solid #e8e8e8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ const menuItems: MenuItem[] = [
|
|||||||
{ path: '/layout/overview', name: '总览', icon: HomeOutlined },
|
{ path: '/layout/overview', name: '总览', icon: HomeOutlined },
|
||||||
{ path: '/layout/container', name: '容器实例', icon: ConsoleSqlOutlined },
|
{ path: '/layout/container', name: '容器实例', icon: ConsoleSqlOutlined },
|
||||||
{ path: '/layout/admin/fileStore', name: '文件存储', icon: FolderOpenOutlined },
|
{ path: '/layout/admin/fileStore', name: '文件存储', icon: FolderOpenOutlined },
|
||||||
{ path: '/layout/image', name: '镜像', icon: GlobalOutlined },
|
{ path: '/layout/admin/image', name: '镜像', icon: GlobalOutlined },
|
||||||
{ path: '/layout/publicData', name: '公开数据', icon: LaptopOutlined },
|
{ path: '/layout/publicData', name: '公开数据', icon: LaptopOutlined },
|
||||||
{
|
{
|
||||||
path: '/layout/fee',
|
path: '/layout/fee',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user