feat: 初始化提报管理系统前端项目
- 添加基础项目结构及核心功能模块 - 实现用户登录界面及权限控制 - 完成分析师提报表单和管理员数据表格功能 - 配置Vue3 + Vite + Naive UI技术栈 - 集成Pinia状态管理和路由系统 - 添加axios请求封装及全局拦截器
This commit is contained in:
665
src/views/UserHome.vue
Normal file
665
src/views/UserHome.vue
Normal file
@@ -0,0 +1,665 @@
|
||||
<template>
|
||||
<!-- 1. 配置中文语言包 -->
|
||||
<n-config-provider :locale="zhCN" :date-locale="dateZhCN">
|
||||
<div class="page-container">
|
||||
<!-- 顶部装饰背景 -->
|
||||
<div class="bg-decoration"></div>
|
||||
|
||||
<n-card class="main-card" :bordered="false">
|
||||
<!-- 头部 -->
|
||||
<template #header>
|
||||
<div class="header-content">
|
||||
<div class="header-title">
|
||||
<div class="title-icon-wrapper">
|
||||
<n-icon size="24" color="#fff">
|
||||
<AnalyticsOutline />
|
||||
</n-icon>
|
||||
</div>
|
||||
<div>
|
||||
<h2>材料提报中心</h2>
|
||||
<p class="sub-title">请如实填写客户成交信息并上传相关凭证</p>
|
||||
</div>
|
||||
</div>
|
||||
<n-button class="logout-btn" secondary type="error" round @click="logout">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<LogOutOutline />
|
||||
</n-icon>
|
||||
</template>
|
||||
退出系统
|
||||
</n-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<n-form ref="formRef" :model="formData" :rules="rules" label-placement="top"
|
||||
require-mark-placement="right-hanging">
|
||||
|
||||
<n-space vertical :size="32">
|
||||
|
||||
<!-- 模块 1: 客户及成交信息 -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<n-icon class="section-icon" size="20">
|
||||
<IdCardOutline />
|
||||
</n-icon>
|
||||
<span>1. 客户及成交信息录入</span>
|
||||
</div>
|
||||
<div class="section-body">
|
||||
<n-grid :x-gap="24" :y-gap="8" cols="1 s:2 m:3" responsive="screen">
|
||||
<n-grid-item>
|
||||
<n-form-item label="分析师主管" path="analystSupervisor">
|
||||
<n-input v-model:value="formData.analystSupervisor" placeholder="请输入主管姓名"
|
||||
clearable />
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<n-form-item label="分析师部门" path="analystDepartment">
|
||||
<n-input v-model:value="formData.analystDepartment" placeholder="例如: 市场一部"
|
||||
clearable />
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<n-form-item label="分析师姓名" path="analystName">
|
||||
<n-input v-model:value="formData.analystName" placeholder="请输入分析师姓名"
|
||||
clearable />
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<n-form-item label="家长姓名" path="parentName">
|
||||
<n-input v-model:value="formData.parentName" placeholder="请输入家长姓名"
|
||||
clearable />
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<n-form-item label="家长电话" path="parentPhone">
|
||||
<n-input v-model:value="formData.parentPhone" placeholder="请输入联系电话"
|
||||
clearable />
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<n-form-item label="家长身份证号码" path="parentIdCard">
|
||||
<n-input v-model:value="formData.parentIdCard" placeholder="请输入18位身份证号"
|
||||
clearable />
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<n-form-item label="成交日期" path="transactionDate">
|
||||
<n-date-picker v-model:value="formData.transactionDate" type="date"
|
||||
clearable style="width: 100%" />
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<n-form-item label="成交金额" path="transactionAmount">
|
||||
<n-input-number v-model:value="formData.transactionAmount"
|
||||
placeholder="0.00" clearable style="width: 100%">
|
||||
<template #prefix>¥</template>
|
||||
</n-input-number>
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<n-form-item label="指导周期" path="guidancePeriod">
|
||||
<n-input v-model:value="formData.guidancePeriod" placeholder="例如: 3个月"
|
||||
clearable />
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
|
||||
<div style="margin-top: 8px;">
|
||||
<n-form-item label="分析师备注">
|
||||
<n-input v-model:value="formData.analystNotes" type="textarea"
|
||||
placeholder="请填写额外备注说明(选填)..." :autosize="{ minRows: 3, maxRows: 5 }" />
|
||||
</n-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 模块 2: 附件文档 -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<n-icon class="section-icon" size="20">
|
||||
<DocumentAttachOutline />
|
||||
</n-icon>
|
||||
<span>2. 附件文档</span>
|
||||
</div>
|
||||
<div class="section-body">
|
||||
<!-- 移除 :default-upload="false",增加 @custom-request="customUpload" -->
|
||||
<n-upload multiple directory-dnd v-model:file-list="formData.documentFileList"
|
||||
@preview="handlePreview" :custom-request="customUpload">
|
||||
<n-upload-dragger class="custom-dragger">
|
||||
<div class="dragger-icon">
|
||||
<n-icon size="48" :depth="3">
|
||||
<CloudUploadOutline />
|
||||
</n-icon>
|
||||
</div>
|
||||
<n-text style="font-size: 16px; font-weight: 500;">点击或者拖动文件到该区域来上传</n-text>
|
||||
<n-p depth="3" style="margin: 8px 0 0 0; font-size: 13px;">
|
||||
支持 PDF, DOCX, XLSX 等格式,单文件不超过 50MB
|
||||
</n-p>
|
||||
</n-upload-dragger>
|
||||
</n-upload>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 模块 3: 付款截图 -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<n-icon class="section-icon" size="20">
|
||||
<ImagesOutline />
|
||||
</n-icon>
|
||||
<span>3. 付款截图</span>
|
||||
</div>
|
||||
<div class="section-body">
|
||||
<!-- 移除 :default-upload="false",增加 @custom-request="customUpload" -->
|
||||
<n-upload class="custom-multi-upload" accept="image/*" multiple list-type="image-card"
|
||||
v-model:file-list="formData.paymentFileList" @preview="handlePreview"
|
||||
:custom-request="customUpload">
|
||||
<div class="upload-placeholder">
|
||||
<div class="icon-bg">
|
||||
<n-icon size="28" color="#666">
|
||||
<CameraOutline />
|
||||
</n-icon>
|
||||
</div>
|
||||
<span class="placeholder-text">上传凭证</span>
|
||||
</div>
|
||||
</n-upload>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 模块 4: 电子签名 -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<n-icon class="section-icon" size="20">
|
||||
<BrushOutline />
|
||||
</n-icon>
|
||||
<span>4. 电子签名</span>
|
||||
</div>
|
||||
<div class="section-body">
|
||||
<!-- 移除 :default-upload="false",增加 @custom-request="customUpload" -->
|
||||
<n-upload class="signature-upload-container" accept="image/*" :max="1"
|
||||
list-type="image-card" v-model:file-list="formData.signatureFileList"
|
||||
@preview="handlePreview" :custom-request="customUpload">
|
||||
<div class="upload-placeholder signature-placeholder">
|
||||
<n-icon size="32" depth="3">
|
||||
<CreateOutline />
|
||||
</n-icon>
|
||||
<n-text depth="3" style="font-size: 14px; margin-top: 8px;">点击上传手写签名</n-text>
|
||||
</div>
|
||||
</n-upload>
|
||||
</div>
|
||||
</div>
|
||||
</n-space>
|
||||
</n-form>
|
||||
|
||||
<!-- 底部操作 -->
|
||||
<template #footer>
|
||||
<div class="footer-actions">
|
||||
<n-button size="large" class="action-btn" @click="handleCancel">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<CloseOutline />
|
||||
</n-icon>
|
||||
</template>
|
||||
取消录入
|
||||
</n-button>
|
||||
<n-button size="large" type="primary" class="action-btn submit-btn" @click="handleSubmit">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<CheckmarkCircleOutline />
|
||||
</n-icon>
|
||||
</template>
|
||||
提交资料
|
||||
</n-button>
|
||||
</div>
|
||||
</template>
|
||||
</n-card>
|
||||
</div>
|
||||
</n-config-provider>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import {
|
||||
useMessage, zhCN, dateZhCN, NConfigProvider, NCard, NSpace, NGrid, NGridItem,
|
||||
NForm, NFormItem, NInput, NInputNumber, NDatePicker, NUpload, NUploadDragger,
|
||||
NButton, NIcon, NText, NP
|
||||
} from 'naive-ui'
|
||||
|
||||
import {
|
||||
AnalyticsOutline, LogOutOutline, IdCardOutline, DocumentAttachOutline,
|
||||
ImagesOutline, BrushOutline, CloudUploadOutline, CameraOutline,
|
||||
CreateOutline, CloseOutline, CheckmarkCircleOutline
|
||||
} from '@vicons/ionicons5'
|
||||
|
||||
// 👉 引入你项目中的请求封装文件
|
||||
import http from '@/utils/request'
|
||||
|
||||
const router = useRouter()
|
||||
const message = useMessage()
|
||||
const formRef = ref(null)
|
||||
|
||||
// 表单数据结构
|
||||
const formData = reactive({
|
||||
analystSupervisor: '',
|
||||
analystDepartment: '',
|
||||
analystName: '',
|
||||
parentName: '',
|
||||
parentPhone: '',
|
||||
parentIdCard: '',
|
||||
transactionDate: null,
|
||||
transactionAmount: null,
|
||||
guidancePeriod: '',
|
||||
analystNotes: '',
|
||||
documentFileList: [],
|
||||
paymentFileList: [],
|
||||
signatureFileList: []
|
||||
})
|
||||
|
||||
// 表单校验规则
|
||||
const rules = {
|
||||
analystName: [{ required: true, message: '请输入分析师姓名', trigger: 'blur' }],
|
||||
parentName: [{ required: true, message: '请输入家长姓名', trigger: 'blur' }],
|
||||
parentPhone: [{ required: true, message: '请输入联系电话', trigger: 'blur' }],
|
||||
transactionAmount: [{ required: true, type: 'number', message: '请输入成交金额', trigger: 'blur' }],
|
||||
}
|
||||
|
||||
// 预览功能
|
||||
const handlePreview = (file) => {
|
||||
// 优先预览从服务端获取到的 url
|
||||
if (file.url) {
|
||||
window.open(file.url)
|
||||
} else if (file.file) {
|
||||
const url = URL.createObjectURL(file.file)
|
||||
window.open(url)
|
||||
}
|
||||
}
|
||||
|
||||
// 退出登录
|
||||
const logout = () => {
|
||||
localStorage.clear()
|
||||
message.info('已退出系统')
|
||||
// router.push('/login')
|
||||
}
|
||||
|
||||
// 取消
|
||||
const handleCancel = () => {
|
||||
message.info('操作已取消')
|
||||
}
|
||||
|
||||
// 👉 自定义文件上传逻辑
|
||||
const customUpload = async ({ file, data, headers, onFinish, onError, onProgress }) => {
|
||||
// 1. 构造 FormData 对象
|
||||
const uploadData = new FormData()
|
||||
// naive-ui 的 file 是一个包装对象,原生的 File 对象在 file.file 属性中
|
||||
uploadData.append('file', file.file)
|
||||
|
||||
// 如果组件传入了额外数据,也追加进去
|
||||
if (data) {
|
||||
Object.keys(data).forEach((key) => {
|
||||
uploadData.append(key, data[key])
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
// 2. 调用封装好的上传接口 (注意:这里直接写 /v1/material/upload,前提是 utils/request.js 中配置好了 /api 等前缀)
|
||||
const res = await http.post('/v1/material/upload', uploadData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
...headers
|
||||
},
|
||||
// 监听上传进度,并在页面上显示绿色进度条
|
||||
onUploadProgress: (progressEvent) => {
|
||||
const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total)
|
||||
onProgress({ percent: percentCompleted })
|
||||
}
|
||||
})
|
||||
|
||||
// 3. 上传成功后处理数据
|
||||
// 根据你 utils/request.js 的响应拦截器逻辑,`res` 已经是接口的 `data` 部分了
|
||||
// 这里把后台返回的 url 赋值给当前文件对象,便于后续图片预览及提交时抽取数据
|
||||
file.url = res.url
|
||||
file.object_name = res.object_name // 保留一下 object_name,以防后台提交需要用到
|
||||
|
||||
// 4. 通知组件上传成功
|
||||
onFinish()
|
||||
} catch (error) {
|
||||
console.error('上传失败:', error)
|
||||
// 5. 通知组件上传失败,变红
|
||||
onError()
|
||||
message.error(`${file.name} 上传失败,请重试`)
|
||||
}
|
||||
}
|
||||
|
||||
// 👉 提交表单数据
|
||||
// 👉 修改后的提交方法
|
||||
const handleSubmit = () => {
|
||||
formRef.value?.validate(async (errors) => {
|
||||
if (!errors) {
|
||||
// 1. 检查文件上传状态
|
||||
const allFiles = [
|
||||
...formData.documentFileList,
|
||||
...formData.paymentFileList,
|
||||
...formData.signatureFileList
|
||||
]
|
||||
const isUploading = allFiles.some(file => file.status === 'uploading')
|
||||
const hasError = allFiles.some(file => file.status === 'error')
|
||||
|
||||
if (isUploading) return message.warning('文件正在上传中,请稍候')
|
||||
if (hasError) return message.error('部分文件上传失败,请处理后再提交')
|
||||
|
||||
// 2. 构造符合后端接口要求的 JSON 对象
|
||||
// 注意:接口字段为下划线命名,且文件URL要求为字符串
|
||||
const submitPayload = {
|
||||
// 从缓存获取 wecom_id (如果没有则传空字符串或从路由获取)
|
||||
wecom_id: localStorage.getItem('wecom_id') || 'default_user',
|
||||
|
||||
// 文件 URL 处理:取第一个,或者用逗号拼接(根据你接口单数命名的理解,通常传第一个或拼接)
|
||||
payment_image_url: formData.paymentFileList.map(f => f.url).filter(Boolean).join(','),
|
||||
signature_image_url: formData.signatureFileList.map(f => f.url).filter(Boolean).join(','),
|
||||
attachment_file_url: formData.documentFileList.map(f => f.url).filter(Boolean).join(','),
|
||||
|
||||
// 基础文本信息
|
||||
analyst_supervisor: formData.analystSupervisor,
|
||||
analyst_department: formData.analystDepartment,
|
||||
analyst_name: formData.analystName,
|
||||
parent_name: formData.parentName,
|
||||
parent_phone: formData.parentPhone,
|
||||
parent_id_card: formData.parentIdCard,
|
||||
|
||||
// 指导周期
|
||||
guidance_period: formData.guidancePeriod,
|
||||
|
||||
// 特殊处理:金额转为字符串
|
||||
transaction_amount: String(formData.transactionAmount || '0'),
|
||||
|
||||
// 特殊处理:日期转为 YYYY-MM-DD 字符串
|
||||
transaction_date: formData.transactionDate
|
||||
? new Date(formData.transactionDate).toISOString().split('T')[0]
|
||||
: ''
|
||||
}
|
||||
|
||||
const d = message.loading('正在提报材料...', { duration: 0 })
|
||||
|
||||
try {
|
||||
// 3. 调用提交接口
|
||||
// 你的 request.js baseURL 已经包含 /api,所以这里写相对路径
|
||||
const res = await http.post('/v1/material/submit', submitPayload)
|
||||
|
||||
d.destroy()
|
||||
message.success('提报成功!')
|
||||
console.log('提交结果:', res)
|
||||
|
||||
// 4. 提交成功后的操作:比如跳转或重置表单
|
||||
// router.push('/success')
|
||||
} catch (error) {
|
||||
d.destroy()
|
||||
// 错误提示已在 request.js 拦截器处理,这里可以做特定逻辑
|
||||
console.error('提报失败', error)
|
||||
}
|
||||
} else {
|
||||
message.error('请完善表单必填项')
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 页面整体背景:浅色微渐变 */
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f0f2f5;
|
||||
background-image: radial-gradient(circle at 50% 0%, #ffffff 0%, #f0f2f5 80%);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 60px 20px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 顶部装饰条,增加企业级感觉 */
|
||||
.bg-decoration {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 240px;
|
||||
background: linear-gradient(135deg, #18a058 0%, #36ad6a 100%);
|
||||
clip-path: polygon(0 0, 100% 0, 100% 60%, 0% 100%);
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
/* 主卡片样式 */
|
||||
.main-card {
|
||||
max-width: 960px;
|
||||
width: 100%;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.08);
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.main-card :deep(.n-card-header) {
|
||||
padding: 24px 32px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.main-card :deep(.n-card__content) {
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
.main-card :deep(.n-card__footer) {
|
||||
padding: 24px 32px;
|
||||
background-color: #fafafc;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
/* 头部布局 */
|
||||
.header-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.title-icon-wrapper {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: linear-gradient(135deg, #18a058 0%, #36ad6a 100%);
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4px 12px rgba(24, 160, 88, 0.3);
|
||||
}
|
||||
|
||||
.header-title h2 {
|
||||
margin: 0 0 4px 0;
|
||||
font-size: 22px;
|
||||
color: #1f2225;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
color: #8c929b;
|
||||
}
|
||||
|
||||
/* 模块整体 */
|
||||
.section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
/* 模块标题 */
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 17px;
|
||||
font-weight: 600;
|
||||
color: #1f2225;
|
||||
}
|
||||
|
||||
.section-icon {
|
||||
color: #18a058;
|
||||
background-color: #e8f5ed;
|
||||
padding: 6px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* 模块内容区背景 */
|
||||
.section-body {
|
||||
background-color: #fcfcfd;
|
||||
border: 1px solid #f0f0f5;
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.section-body:hover {
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.02);
|
||||
border-color: #e8e8ed;
|
||||
}
|
||||
|
||||
/* 拖拽上传区域优化 */
|
||||
.custom-dragger {
|
||||
background-color: #fafafc;
|
||||
border-radius: 8px;
|
||||
padding: 32px 0;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.custom-dragger:hover {
|
||||
background-color: #f3fcf6;
|
||||
}
|
||||
|
||||
.dragger-icon {
|
||||
margin-bottom: 16px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.custom-dragger:hover .dragger-icon {
|
||||
transform: translateY(-4px);
|
||||
color: #18a058;
|
||||
}
|
||||
|
||||
/* 上传占位符全局样式 */
|
||||
.upload-placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #fafafc;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.upload-placeholder:hover {
|
||||
background-color: #f3fcf6;
|
||||
}
|
||||
|
||||
/* 付款截图 */
|
||||
.custom-multi-upload :deep(.n-upload-trigger.n-upload-trigger--image-card) {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.custom-multi-upload :deep(.n-upload-file.n-upload-file--image-card) {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.icon-bg {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background-color: #fff;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.placeholder-text {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 签名 */
|
||||
.signature-upload-container :deep(.n-upload-trigger.n-upload-trigger--image-card) {
|
||||
width: 300px;
|
||||
height: 120px;
|
||||
border-radius: 8px;
|
||||
border: 2px dashed #d9d9d9;
|
||||
}
|
||||
|
||||
.signature-upload-container :deep(.n-upload-file.n-upload-file--image-card) {
|
||||
width: 300px;
|
||||
height: 120px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.signature-placeholder {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/* 底部操作按钮 */
|
||||
.footer-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
min-width: 120px;
|
||||
border-radius: 8px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
box-shadow: 0 4px 12px rgba(24, 160, 88, 0.3);
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 768px) {
|
||||
.page-container {
|
||||
padding: 20px 12px;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.main-card :deep(.n-card__content) {
|
||||
padding: 20px 16px;
|
||||
}
|
||||
|
||||
.section-body {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.signature-upload-container :deep(.n-upload-trigger.n-upload-trigger--image-card),
|
||||
.signature-upload-container :deep(.n-upload-file.n-upload-file--image-card) {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user