feat(用户主页): 重构用户主页表单及上传逻辑
refactor(管理后台): 优化管理员列表页及分配功能 - 用户主页新增文件上传限制及数据回显逻辑 - 重构表单提交数据处理逻辑,确保数据纯净 - 管理后台新增分页查询及远程数据加载 - 优化分配操作流程,增加状态管理
This commit is contained in:
@@ -9,7 +9,6 @@
|
||||
<div class="page-header">
|
||||
<div class="title-group">
|
||||
<div class="icon-box">
|
||||
<!-- 修复点:将 TableOutlined 换成了 ReaderOutline -->
|
||||
<n-icon size="24" color="#fff">
|
||||
<ReaderOutline />
|
||||
</n-icon>
|
||||
@@ -34,29 +33,29 @@
|
||||
<n-form inline :model="searchParams" label-placement="left" label-width="auto" size="medium">
|
||||
<n-grid :x-gap="20" :y-gap="16" cols="1 s:2 m:3 l:4 xl:5" responsive="screen">
|
||||
<n-grid-item>
|
||||
<n-form-item label="分析师">
|
||||
<n-input v-model:value="searchParams.analystName" placeholder="搜索分析师姓名" clearable />
|
||||
<n-form-item label="WeCom ID">
|
||||
<n-input v-model:value="searchParams.wecom_id" placeholder="输入 ID 筛选" clearable />
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<n-form-item label="家长信息">
|
||||
<n-input v-model:value="searchParams.parentInfo" placeholder="姓名 / 电话" clearable />
|
||||
<n-form-item label="家长姓名">
|
||||
<n-input v-model:value="searchParams.parentInfo" placeholder="匹配姓名" clearable />
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
<n-grid-item span="m:1 l:1 xl:1">
|
||||
<n-grid-item>
|
||||
<n-form-item label="分配状态">
|
||||
<n-select v-model:value="searchParams.status" :options="statusOptions"
|
||||
placeholder="全部状态" clearable />
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
<n-grid-item span="m:2 l:1 xl:1">
|
||||
<n-grid-item span="m:1 l:1 xl:1">
|
||||
<n-form-item label="成交日期">
|
||||
<n-date-picker v-model:value="searchParams.dateRange" type="daterange" clearable />
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
<n-grid-item class="search-btns">
|
||||
<n-space>
|
||||
<n-button type="primary" @click="handleSearch">
|
||||
<n-button type="primary" @click="handleSearch" :loading="loading">
|
||||
<template #icon><n-icon>
|
||||
<SearchOutline />
|
||||
</n-icon></template>
|
||||
@@ -74,7 +73,7 @@
|
||||
<template #header>
|
||||
<n-space align="center">
|
||||
<span class="table-title">数据列表</span>
|
||||
<n-text depth="3" style="font-size: 12px; font-weight: normal;">共找到 {{ displayData.length }}
|
||||
<n-text depth="3" style="font-size: 12px; font-weight: normal;">共找到 {{ pagination.itemCount }}
|
||||
条记录</n-text>
|
||||
</n-space>
|
||||
</template>
|
||||
@@ -87,8 +86,16 @@
|
||||
</n-button>
|
||||
</template>
|
||||
|
||||
<n-data-table :columns="columns" :data="displayData" :scroll-x="1600" :bordered="false"
|
||||
:pagination="pagination" :loading="loading" :row-class-name="() => 'table-row'" />
|
||||
<n-data-table
|
||||
remote
|
||||
:columns="columns"
|
||||
:data="displayData"
|
||||
:scroll-x="1800"
|
||||
:bordered="false"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
:row-class-name="() => 'table-row'"
|
||||
/>
|
||||
</n-card>
|
||||
</div>
|
||||
|
||||
@@ -97,73 +104,65 @@
|
||||
<n-drawer-content title="原始提报资料详情" closable>
|
||||
<div class="drawer-container">
|
||||
<n-space vertical :size="24">
|
||||
<!-- 分配操作区 -->
|
||||
<div class="info-group highlight-group">
|
||||
<div class="group-title"><n-icon><BrushOutline /></n-icon> 执行分配</div>
|
||||
<n-form label-placement="left" label-width="100">
|
||||
<n-form-item label="分配指导师">
|
||||
<n-input v-model:value="editingRow.guideName" placeholder="输入指导师姓名" />
|
||||
</n-form-item>
|
||||
<n-form-item label="联系电话">
|
||||
<n-input v-model:value="editingRow.guidePhone" placeholder="输入指导师手机号" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</div>
|
||||
|
||||
<!-- 详情展示区 -->
|
||||
<div class="info-group">
|
||||
<div class="group-title"><n-icon>
|
||||
<PersonOutline />
|
||||
</n-icon> 客户及成交信息</div>
|
||||
<n-descriptions label-placement="left" :column="2" bordered size="small"
|
||||
class="custom-desc">
|
||||
<n-descriptions-item label="分析师主管">{{ editingRow.analystSupervisor
|
||||
}}</n-descriptions-item>
|
||||
<n-descriptions-item label="分析师部门">{{ editingRow.analystDepartment
|
||||
}}</n-descriptions-item>
|
||||
<n-descriptions-item label="分析师姓名">{{ editingRow.analystName
|
||||
}}</n-descriptions-item>
|
||||
<div class="group-title"><n-icon><PersonOutline /></n-icon> 客户及成交信息</div>
|
||||
<n-descriptions label-placement="left" :column="2" bordered size="small" class="custom-desc">
|
||||
<n-descriptions-item label="分析师主管">{{ editingRow.analystSupervisor }}</n-descriptions-item>
|
||||
<n-descriptions-item label="分析师部门">{{ editingRow.analystDepartment }}</n-descriptions-item>
|
||||
<n-descriptions-item label="分析师姓名">{{ editingRow.analystName }}</n-descriptions-item>
|
||||
<n-descriptions-item label="家长姓名">{{ editingRow.parentName }}</n-descriptions-item>
|
||||
<n-descriptions-item label="家长电话">{{ editingRow.parentPhone }}</n-descriptions-item>
|
||||
<n-descriptions-item label="身份证号">{{ editingRow.parentIdCard
|
||||
}}</n-descriptions-item>
|
||||
<n-descriptions-item label="成交日期">{{ editingRow.transactionDate
|
||||
}}</n-descriptions-item>
|
||||
<n-descriptions-item label="成交金额"><span class="price-text">¥{{
|
||||
editingRow.transactionAmount
|
||||
}}</span></n-descriptions-item>
|
||||
<n-descriptions-item label="指导周期" :span="2">{{ editingRow.guidancePeriod
|
||||
}}</n-descriptions-item>
|
||||
<n-descriptions-item label="分析师备注" :span="2">
|
||||
<n-input type="textarea" v-model:value="editingRow.analystNotes"
|
||||
placeholder="无额外备注" :autosize="{ minRows: 2 }" />
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="身份证号">{{ editingRow.parentIdCard }}</n-descriptions-item>
|
||||
<n-descriptions-item label="成交日期">{{ editingRow.transactionDate }}</n-descriptions-item>
|
||||
<n-descriptions-item label="成交金额"><span class="price-text">¥{{ editingRow.transactionAmount }}</span></n-descriptions-item>
|
||||
<n-descriptions-item label="指导周期" :span="2">{{ editingRow.guidancePeriod }}</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</div>
|
||||
|
||||
<div class="info-group">
|
||||
<div class="group-title"><n-icon>
|
||||
<FileTrayOutline />
|
||||
</n-icon> 附件文档</div>
|
||||
<div class="file-box" v-if="editingRow.files?.length">
|
||||
<n-button block secondary type="primary" @click="downloadFile">下载附件压缩包</n-button>
|
||||
<div class="group-title"><n-icon><FileTrayOutline /></n-icon> 附件文档</div>
|
||||
<div class="file-box" v-if="editingRow.files?.length && editingRow.files[0]">
|
||||
<n-button block secondary type="primary" @click="downloadFile">下载附件文档</n-button>
|
||||
</div>
|
||||
<n-empty v-else description="未上传附件" size="small" />
|
||||
</div>
|
||||
|
||||
<div class="info-group">
|
||||
<div class="group-title"><n-icon>
|
||||
<ImagesOutline />
|
||||
</n-icon> 付款截图</div>
|
||||
<div class="group-title"><n-icon><ImagesOutline /></n-icon> 付款截图</div>
|
||||
<n-image-group>
|
||||
<n-space>
|
||||
<n-image v-for="(img, i) in editingRow.paymentImages" :key="i" width="120"
|
||||
height="120" fit="cover" class="preview-img" :src="img" />
|
||||
<n-image v-for="(img, i) in editingRow.paymentImages" :key="i" width="120" height="120" fit="cover" class="preview-img" :src="img" />
|
||||
</n-space>
|
||||
</n-image-group>
|
||||
</div>
|
||||
|
||||
<div class="info-group">
|
||||
<div class="group-title"><n-icon>
|
||||
<BrushOutline />
|
||||
</n-icon> 电子签名</div>
|
||||
<div class="group-title"><n-icon><BrushOutline /></n-icon> 电子签名</div>
|
||||
<div class="signature-wrapper">
|
||||
<n-image v-if="editingRow.signature" width="240" :src="editingRow.signature" />
|
||||
<n-text v-else depth="3">暂无签名</n-text>
|
||||
<n-text v-else depth="3">暂无签名数据</n-text>
|
||||
</div>
|
||||
</div>
|
||||
</n-space>
|
||||
</div>
|
||||
<template #footer>
|
||||
<n-space justify="end">
|
||||
<n-button @click="showDrawer = false">关闭</n-button>
|
||||
<n-button type="primary" @click="saveDetailEdit">保存修改内容</n-button>
|
||||
<n-button @click="showDrawer = false">取消</n-button>
|
||||
<n-button type="primary" :loading="submitLoading" @click="handleDrawerSubmit">确认分配并提交</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-drawer-content>
|
||||
@@ -173,7 +172,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, h, reactive } from 'vue'
|
||||
import { ref, h, reactive, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import {
|
||||
useMessage, zhCN, dateZhCN, NConfigProvider, NCard, NDataTable, NButton, NIcon, NInput,
|
||||
@@ -181,165 +180,240 @@ import {
|
||||
NText, NForm, NFormItem, NGrid, NGridItem, NDatePicker, NSelect, NTag, NEmpty
|
||||
} from 'naive-ui'
|
||||
|
||||
// 修复导入项:去掉了不存在的 TableOutlined,换成了 ReaderOutline
|
||||
import {
|
||||
SearchOutline, LogOutOutline, DownloadOutline, PersonOutline, FileTrayOutline,
|
||||
ImagesOutline, BrushOutline, ReaderOutline, EyeOutline
|
||||
ImagesOutline, BrushOutline, ReaderOutline, EyeOutline, CheckmarkCircleOutline
|
||||
} from '@vicons/ionicons5'
|
||||
|
||||
const router = useRouter()
|
||||
const message = useMessage()
|
||||
const loading = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
|
||||
// 搜索参数
|
||||
const searchParams = reactive({
|
||||
analystName: '',
|
||||
wecom_id: '',
|
||||
parentInfo: '',
|
||||
dateRange: null,
|
||||
status: null
|
||||
})
|
||||
|
||||
const statusOptions = [
|
||||
{ label: '已分配指导师', value: 'assigned' },
|
||||
{ label: '待处理', value: 'pending' }
|
||||
{ label: '待处理', value: 'pending' },
|
||||
{ label: '已处理', value: 'processed' }
|
||||
]
|
||||
|
||||
const allTableData = ref([
|
||||
{
|
||||
id: 1,
|
||||
analystSupervisor: '王主管',
|
||||
analystDepartment: '市场二部',
|
||||
analystName: '张三',
|
||||
parentName: '李四家长',
|
||||
parentPhone: '13800000000',
|
||||
parentIdCard: '110105199001010011',
|
||||
transactionDate: '2023-10-24',
|
||||
transactionAmount: 9800,
|
||||
guidancePeriod: '一学期',
|
||||
analystNotes: '需要周六上课',
|
||||
guideName: '王老师',
|
||||
guidePhone: '13900001111',
|
||||
paymentImages: ['https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg'],
|
||||
signature: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg'
|
||||
const displayData = ref([])
|
||||
|
||||
// 分页配置
|
||||
const pagination = reactive({
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
itemCount: 0,
|
||||
showSizePicker: true,
|
||||
pageSizes: [10, 20, 50],
|
||||
onChange: (page) => {
|
||||
pagination.page = page
|
||||
fetchData()
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
analystSupervisor: '赵主管',
|
||||
analystDepartment: '市场一部',
|
||||
analystName: '李小龙',
|
||||
parentName: '陈家长',
|
||||
parentPhone: '15900002222',
|
||||
transactionDate: '2023-11-05',
|
||||
transactionAmount: 5000,
|
||||
guideName: '',
|
||||
guidePhone: '',
|
||||
paymentImages: [],
|
||||
signature: ''
|
||||
onUpdatePageSize: (pageSize) => {
|
||||
pagination.pageSize = pageSize
|
||||
pagination.page = 1
|
||||
fetchData()
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
const displayData = ref([...allTableData.value])
|
||||
|
||||
const handleSearch = () => {
|
||||
// 1. 获取列表数据
|
||||
const fetchData = async () => {
|
||||
loading.value = true
|
||||
setTimeout(() => {
|
||||
displayData.value = allTableData.value.filter(item => {
|
||||
const matchAnalyst = item.analystName.includes(searchParams.analystName)
|
||||
const matchParent = item.parentName.includes(searchParams.parentInfo) || item.parentPhone.includes(searchParams.parentInfo)
|
||||
let matchStatus = true
|
||||
if (searchParams.status === 'assigned') matchStatus = !!item.guideName
|
||||
if (searchParams.status === 'pending') matchStatus = !item.guideName
|
||||
return matchAnalyst && matchParent && matchStatus
|
||||
try {
|
||||
const query = new URLSearchParams({
|
||||
page: pagination.page,
|
||||
page_size: pagination.pageSize
|
||||
})
|
||||
|
||||
if (searchParams.wecom_id) query.append('wecom_id', searchParams.wecom_id)
|
||||
if (searchParams.status) query.append('status', searchParams.status)
|
||||
|
||||
const response = await fetch(`http://192.168.15.115:5636/api/v1/customer/list?${query.toString()}`, {
|
||||
headers: { 'accept': 'application/json' }
|
||||
})
|
||||
const res = await response.json()
|
||||
|
||||
if (res.success) {
|
||||
displayData.value = res.data.map(item => ({
|
||||
id: item.wecom_id,
|
||||
analystSupervisor: item.analyst_supervisor,
|
||||
analystDepartment: item.analyst_department,
|
||||
analystName: item.analyst_name,
|
||||
parentName: item.parent_name,
|
||||
parentPhone: item.parent_phone,
|
||||
parentIdCard: item.parent_id_card,
|
||||
transactionDate: item.transaction_date,
|
||||
transactionAmount: item.transaction_amount || '0',
|
||||
guidancePeriod: item.guidance_period,
|
||||
submissionTime: item.submission_time,
|
||||
status: item.status,
|
||||
paymentImages: item.payment_image_url ? [item.payment_image_url] : [],
|
||||
signature: item.signature_image_url,
|
||||
files: item.attachment_file_url ? [item.attachment_file_url] : [],
|
||||
// 交互字段
|
||||
guideName: '',
|
||||
guidePhone: '',
|
||||
isSubmitting: false
|
||||
}))
|
||||
pagination.itemCount = res.pagination.total
|
||||
} else {
|
||||
message.error(res.message || '获取数据失败')
|
||||
}
|
||||
} catch (error) {
|
||||
message.error('接口连接失败,请检查网络')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}, 400)
|
||||
}
|
||||
}
|
||||
|
||||
const resetSearch = () => {
|
||||
Object.assign(searchParams, { analystName: '', parentInfo: '', dateRange: null, status: null })
|
||||
displayData.value = [...allTableData.value]
|
||||
// 2. 提交分配请求函数
|
||||
const submitAssignment = async (row) => {
|
||||
if (!row.guideName || !row.guidePhone) {
|
||||
message.warning('请填写完整的指导师信息')
|
||||
return
|
||||
}
|
||||
|
||||
row.isSubmitting = true
|
||||
try {
|
||||
const payload = {
|
||||
wecom_id: row.id,
|
||||
payment_image_url: row.paymentImages[0] || "",
|
||||
signature_image_url: row.signature || "",
|
||||
attachment_file_url: row.files[0] || "",
|
||||
analyst_supervisor: row.analystSupervisor,
|
||||
analyst_department: row.analystDepartment,
|
||||
analyst_name: row.analystName,
|
||||
parent_name: row.parentName,
|
||||
parent_phone: row.parentPhone,
|
||||
parent_id_card: row.parentIdCard,
|
||||
transaction_date: row.transactionDate,
|
||||
transaction_amount: String(row.transactionAmount),
|
||||
guidance_period: row.guidancePeriod,
|
||||
assignee_name: row.guideName,
|
||||
assignee_phone: row.guidePhone
|
||||
}
|
||||
|
||||
const response = await fetch('http://192.168.15.115:5636/api/v1/material/submit', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
|
||||
const res = await response.json()
|
||||
if (res.success || res.code === 200) {
|
||||
message.success('分配成功')
|
||||
row.status = 'processed' // 本地更新状态
|
||||
return true
|
||||
} else {
|
||||
message.error(res.message || '提交失败')
|
||||
return false
|
||||
}
|
||||
} catch (error) {
|
||||
message.error('提交请求失败,请检查网络')
|
||||
return false
|
||||
} finally {
|
||||
row.isSubmitting = false
|
||||
}
|
||||
}
|
||||
|
||||
// 表格列定义
|
||||
const columns = [
|
||||
{ title: '分析师姓名', key: 'analystName', width: 120, fixed: 'left' },
|
||||
{ title: '家长姓名', key: 'parentName', width: 120 },
|
||||
{ title: '家长电话', key: 'parentPhone', width: 140 },
|
||||
{ title: '分析师', key: 'analystName', width: 90, fixed: 'left' },
|
||||
{ title: '家长姓名', key: 'parentName', width: 90 },
|
||||
{ title: '家长电话', key: 'parentPhone', width: 120 },
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
width: 120,
|
||||
render: (row) => {
|
||||
const isAssigned = !!row.guideName
|
||||
return h(NTag, { type: isAssigned ? 'success' : 'warning', round: true, size: 'small' }, {
|
||||
default: () => isAssigned ? '已分配' : '待处理'
|
||||
})
|
||||
}
|
||||
width: 90,
|
||||
render: (row) => h(NTag, {
|
||||
type: row.status === 'pending' ? 'warning' : 'success',
|
||||
round: true, size: 'small'
|
||||
}, { default: () => row.status === 'pending' ? '待处理' : '已处理' })
|
||||
},
|
||||
{ title: '成交日期', key: 'transactionDate', width: 130, sorter: (a, b) => new Date(a.transactionDate) - new Date(b.transactionDate) },
|
||||
{ title: '成交金额', key: 'transactionAmount', width: 100, render: (row) => h('span', { class: 'price-text' }, `¥${row.transactionAmount}`) },
|
||||
{
|
||||
title: '成交金额',
|
||||
key: 'transactionAmount',
|
||||
width: 120,
|
||||
render: (row) => h('span', { style: 'font-weight: bold; color: #d03050' }, `¥${row.transactionAmount.toLocaleString()}`)
|
||||
},
|
||||
{
|
||||
title: '分配指导师',
|
||||
title: '分配指导师姓名',
|
||||
key: 'guideName',
|
||||
width: 180,
|
||||
width: 130,
|
||||
render: (row) => h(NInput, {
|
||||
value: row.guideName,
|
||||
placeholder: '输入师名...',
|
||||
size: 'small',
|
||||
placeholder: '输入姓名',
|
||||
onUpdateValue: (v) => { row.guideName = v }
|
||||
})
|
||||
},
|
||||
{
|
||||
title: '指导师电话',
|
||||
key: 'guidePhone',
|
||||
width: 180,
|
||||
width: 160,
|
||||
render: (row) => h(NInput, {
|
||||
value: row.guidePhone,
|
||||
placeholder: '输入电话...',
|
||||
size: 'small',
|
||||
placeholder: '输入手机号',
|
||||
onUpdateValue: (v) => { row.guidePhone = v }
|
||||
})
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'op',
|
||||
width: 100,
|
||||
width: 180,
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
render: (row) => h(NButton, {
|
||||
type: 'primary',
|
||||
quaternary: true,
|
||||
size: 'small',
|
||||
onClick: () => openDetails(row)
|
||||
}, {
|
||||
default: () => '详情',
|
||||
icon: () => h(NIcon, null, { default: () => h(EyeOutline) })
|
||||
render: (row) => h(NSpace, { justify: 'center' }, {
|
||||
default: () => [
|
||||
h(NButton, {
|
||||
quaternary: true,
|
||||
size: 'small',
|
||||
onClick: () => openDetails(row)
|
||||
}, { icon: () => h(EyeOutline) }),
|
||||
h(NButton, {
|
||||
type: 'primary',
|
||||
size: 'small',
|
||||
loading: row.isSubmitting,
|
||||
onClick: () => submitAssignment(row)
|
||||
}, { default: () => '确认分配' })
|
||||
]
|
||||
})
|
||||
}
|
||||
]
|
||||
|
||||
// 抽屉逻辑
|
||||
const showDrawer = ref(false)
|
||||
const editingRow = ref({})
|
||||
const openDetails = (row) => {
|
||||
editingRow.value = JSON.parse(JSON.stringify(row))
|
||||
editingRow.value = row // 这里使用引用,方便同步修改
|
||||
showDrawer.value = true
|
||||
}
|
||||
|
||||
const saveDetailEdit = () => {
|
||||
const idx = allTableData.value.findIndex(item => item.id === editingRow.value.id)
|
||||
if (idx !== -1) {
|
||||
allTableData.value[idx].analystNotes = editingRow.value.analystNotes
|
||||
message.success('保存成功')
|
||||
showDrawer.value = false
|
||||
}
|
||||
const handleDrawerSubmit = async () => {
|
||||
submitLoading.value = true
|
||||
const success = await submitAssignment(editingRow.value)
|
||||
submitLoading.value = false
|
||||
if (success) showDrawer.value = false
|
||||
}
|
||||
|
||||
const pagination = reactive({ pageSize: 10 })
|
||||
// 搜索逻辑
|
||||
const handleSearch = () => { pagination.page = 1; fetchData() }
|
||||
const resetSearch = () => {
|
||||
Object.assign(searchParams, { wecom_id: '', parentInfo: '', dateRange: null, status: null })
|
||||
pagination.page = 1
|
||||
fetchData()
|
||||
}
|
||||
|
||||
onMounted(() => fetchData())
|
||||
|
||||
const logout = () => { localStorage.clear(); router.push('/login') }
|
||||
const downloadFile = () => { message.loading('正在打包...') }
|
||||
const downloadFile = () => { if(editingRow.value.files?.[0]) window.open(editingRow.value.files[0]) }
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -404,32 +478,17 @@ const downloadFile = () => { message.loading('正在打包...') }
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.search-card {
|
||||
.search-card, .table-card {
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.05);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.search-btns {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.table-card {
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.table-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.drawer-container {
|
||||
padding: 10px 4px;
|
||||
}
|
||||
|
||||
.info-group {
|
||||
background: #fcfcfd;
|
||||
border: 1px solid #f0f0f5;
|
||||
@@ -437,6 +496,11 @@ const downloadFile = () => { message.loading('正在打包...') }
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.highlight-group {
|
||||
background: #f0fff4;
|
||||
border: 1px solid #c6f6d5;
|
||||
}
|
||||
|
||||
.group-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -447,16 +511,9 @@ const downloadFile = () => { message.loading('正在打包...') }
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.custom-desc :deep(.n-descriptions-table-header) {
|
||||
background-color: #f9fafb;
|
||||
font-weight: 500;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.price-text {
|
||||
color: #d03050;
|
||||
font-weight: bold;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.preview-img {
|
||||
|
||||
@@ -48,58 +48,49 @@
|
||||
<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-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-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-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-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-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-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-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%">
|
||||
<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-input v-model:value="formData.guidancePeriod" placeholder="例如: 3个月" clearable />
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
@@ -113,25 +104,24 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 模块 2: 附件文档 -->
|
||||
<!-- 模块 2: 附件文档 (仅限1份) -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<n-icon class="section-icon" size="20">
|
||||
<DocumentAttachOutline />
|
||||
</n-icon>
|
||||
<span>2. 附件文档</span>
|
||||
<span>2. 附件文档 (限1份)</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 :max="1" directory-dnd v-model:file-list="formData.documentFileList"
|
||||
@preview="handlePreview" :custom-request="handleCustomUpload">
|
||||
<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-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>
|
||||
@@ -140,19 +130,18 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 模块 3: 付款截图 -->
|
||||
<!-- 模块 3: 付款截图 (可上传多张) -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<n-icon class="section-icon" size="20">
|
||||
<ImagesOutline />
|
||||
</n-icon>
|
||||
<span>3. 付款截图</span>
|
||||
<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">
|
||||
:custom-request="handleCustomUpload">
|
||||
<div class="upload-placeholder">
|
||||
<div class="icon-bg">
|
||||
<n-icon size="28" color="#666">
|
||||
@@ -165,19 +154,18 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 模块 4: 电子签名 -->
|
||||
<!-- 模块 4: 电子签名 (仅限1张) -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<n-icon class="section-icon" size="20">
|
||||
<BrushOutline />
|
||||
</n-icon>
|
||||
<span>4. 电子签名</span>
|
||||
<span>4. 电子签名 (限1张)</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">
|
||||
@preview="handlePreview" :custom-request="handleCustomUpload">
|
||||
<div class="upload-placeholder signature-placeholder">
|
||||
<n-icon size="32" depth="3">
|
||||
<CreateOutline />
|
||||
@@ -215,8 +203,9 @@
|
||||
</div>
|
||||
</n-config-provider>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, onMounted } from 'vue' // 👉 增加 onMounted
|
||||
import { reactive, ref, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import {
|
||||
useMessage, zhCN, dateZhCN, NConfigProvider, NCard, NSpace, NGrid, NGridItem,
|
||||
@@ -232,12 +221,12 @@ import {
|
||||
|
||||
import http from '@/utils/request'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute() // 👉 用于获取 URL 中的参数
|
||||
const message = useMessage()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const formRef = ref(null)
|
||||
|
||||
// 表单数据结构
|
||||
// 响应式表单数据
|
||||
const formData = reactive({
|
||||
analystSupervisor: '',
|
||||
analystDepartment: '',
|
||||
@@ -249,127 +238,153 @@ const formData = reactive({
|
||||
transactionAmount: null,
|
||||
guidancePeriod: '',
|
||||
analystNotes: '',
|
||||
// 存储 UI 显示用的文件列表
|
||||
documentFileList: [],
|
||||
paymentFileList: [],
|
||||
signatureFileList: []
|
||||
})
|
||||
|
||||
// 👉 新增:初始化获取数据的方法
|
||||
const fetchCustomerInfo = async () => {
|
||||
// 优先从 URL 获取 wecom_id,如果没有则使用你提供的测试 ID
|
||||
const wecom_id = route.query.wecom_id || 'wmcr-ECwAAzKclEfIKNcVgOdxD-TcqLg'
|
||||
// 定义校验规则
|
||||
const rules = {
|
||||
analystSupervisor: { required: true, message: '请输入主管姓名', trigger: 'blur' },
|
||||
parentName: { required: true, message: '请输入家长姓名', trigger: 'blur' },
|
||||
transactionDate: { required: true, type: 'number', message: '请选择日期', trigger: ['blur', 'change'] }
|
||||
}
|
||||
|
||||
const d = message.loading('正在加载客户信息...')
|
||||
/**
|
||||
* 核心:通用文件上传逻辑
|
||||
* 直接把后端返回的数据挂载到文件对象的 rawResponse 字段,完全隔离前端生成的 id/fullPath
|
||||
*/
|
||||
const handleCustomUpload = async ({ file, onFinish, onError, onProgress }) => {
|
||||
try {
|
||||
const res = await http.get('/v1/customer/get_customers_info', { wecom_id })
|
||||
const uploadData = new FormData()
|
||||
uploadData.append('file', file.file)
|
||||
|
||||
// 👉 数据映射回显
|
||||
const response = await http.post('/v1/material/upload', uploadData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
onUploadProgress: (p) => onProgress({ percent: Math.ceil((p.loaded / p.total) * 100) })
|
||||
})
|
||||
|
||||
if (response.success && response) {
|
||||
// 【重点】把请求回来的整个 data 存起来,不理会 UI 生成的字段
|
||||
file.rawResponse = response
|
||||
file.object_name = response.object_name
|
||||
// 下面两行是给 UI 看的,保证能回显图片和显示“完成”状态
|
||||
file.url = response.url
|
||||
file.status = 'finished'
|
||||
formData.documentFileList.push(file)
|
||||
console.log('Updated documentFileList:', formData.documentFileList)
|
||||
message.success('上传成功')
|
||||
onFinish()
|
||||
} else {
|
||||
message.error(response.message || '上传失败')
|
||||
onError()
|
||||
}
|
||||
} catch (error) {
|
||||
message.error('网络错误,上传失败')
|
||||
onError()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 核心:初始化数据回显
|
||||
*/
|
||||
const initData = async () => {
|
||||
try {
|
||||
const wecomId = route.query.wecom_id || 'wmcr-ECwAAzKclEfIKNcVgOdxD-TcqLg'
|
||||
const res = await http.get('/v1/customer/get_customers_info', { wecom_id: wecomId })
|
||||
|
||||
// 1. 回显基础字段
|
||||
formData.analystName = res.analystName
|
||||
formData.parentName = res.customerName
|
||||
formData.parentPhone = res.customerPhone
|
||||
formData.analystName = res.analystName
|
||||
|
||||
// 数组取第一个
|
||||
formData.analystDepartment = res.analystDepartmentName?.[0] || ''
|
||||
formData.analystNotes = res.notes
|
||||
formData.analystSupervisor = res.analystDepartmentLeader?.[0] || ''
|
||||
formData.analystDepartment = res.analystDepartmentName?.[0] || ''
|
||||
if (res.dealAmount) formData.transactionAmount = Number(res.dealAmount)
|
||||
if (res.dealDate) formData.transactionDate = new Date(res.dealDate).getTime()
|
||||
|
||||
// 金额转数字
|
||||
formData.transactionAmount = res.dealAmount ? Number(res.dealAmount) : null
|
||||
|
||||
// 日期字符串转时间戳
|
||||
formData.transactionDate = res.dealDate ? new Date(res.dealDate).getTime() : null
|
||||
|
||||
formData.analystNotes = res.notes || ''
|
||||
|
||||
// 👉 回显付款截图 (将 URL 数组转为 Upload 组件需要的对象格式)
|
||||
if (res.proofOfPayment && Array.isArray(res.proofOfPayment)) {
|
||||
formData.paymentFileList = res.proofOfPayment.map((url, index) => ({
|
||||
id: 'init-' + index,
|
||||
name: '已上传凭证-' + (index + 1),
|
||||
// 2. 回显付款截图:将已有的 object_name 和 url 封装进 rawResponse
|
||||
if (res.proofOfPayment && res.proofOfPayment_urls) {
|
||||
formData.paymentFileList = res.proofOfPayment.map((objName, index) => ({
|
||||
id: objName, // UI 需要一个 ID
|
||||
name: `凭证-${index + 1}`,
|
||||
status: 'finished',
|
||||
url: url
|
||||
url: res.proofOfPayment_urls[index],
|
||||
// 【重点】手动构造一个 rawResponse,确保和上传后的结构一致
|
||||
rawResponse: {
|
||||
object_name: objName,
|
||||
url: res.proofOfPayment_urls[index],
|
||||
success: true
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
message.success('客户资料已自动填充')
|
||||
message.success('数据加载成功')
|
||||
} catch (error) {
|
||||
console.error('获取初始信息失败:', error)
|
||||
message.error('无法获取客户基础信息')
|
||||
} finally {
|
||||
d.destroy()
|
||||
console.error('加载错误:', error)
|
||||
message.error('客户信息加载失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 👉 页面加载时执行
|
||||
onMounted(() => {
|
||||
fetchCustomerInfo()
|
||||
initData()
|
||||
})
|
||||
|
||||
// --- 以下是之前的逻辑,保持不变 ---
|
||||
|
||||
// 自定义上传逻辑
|
||||
const customUpload = async ({ file, onFinish, onError, onProgress }) => {
|
||||
const uploadData = new FormData()
|
||||
uploadData.append('file', file.file)
|
||||
try {
|
||||
const res = await http.post('/v1/material/upload', uploadData, {
|
||||
onUploadProgress: (p) => onProgress({ percent: Math.round((p.loaded * 100) / p.total) })
|
||||
})
|
||||
file.url = res.url
|
||||
onFinish()
|
||||
} catch (e) {
|
||||
onError()
|
||||
message.error('上传失败')
|
||||
}
|
||||
// 预览
|
||||
const handlePreview = (file) => {
|
||||
const url = file.url || file.thumbnailUrl
|
||||
if (url) window.open(url)
|
||||
}
|
||||
|
||||
// 最终提交逻辑
|
||||
// 提交表单
|
||||
const handleSubmit = () => {
|
||||
formRef.value?.validate(async (errors) => {
|
||||
if (!errors) {
|
||||
// 【核心逻辑】只提取 rawResponse 里的数据,扔掉前端生成的 id, fullPath, type 等
|
||||
const submitPayload = {
|
||||
wecom_id: route.query.wecom_id || 'wmcr-ECwAAvBzsjWQ6RQZuXmxzXHMLiQ',
|
||||
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,
|
||||
transaction_date: formData.transactionDate ? new Date(formData.transactionDate).toISOString().split('T')[0] : '',
|
||||
transaction_amount: String(formData.transactionAmount || ''),
|
||||
guidance_period: formData.guidancePeriod
|
||||
// 1. 提取基础文本字段
|
||||
analystSupervisor: formData.analystSupervisor,
|
||||
analystDepartment: formData.analystDepartment,
|
||||
analystName: formData.analystName,
|
||||
parentName: formData.parentName,
|
||||
parentPhone: formData.parentPhone,
|
||||
parentIdCard: formData.parentIdCard,
|
||||
transactionDate: formData.transactionDate,
|
||||
transactionAmount: formData.transactionAmount,
|
||||
guidancePeriod: formData.guidancePeriod,
|
||||
analystNotes: formData.analystNotes,
|
||||
|
||||
// 2. 提取纯净的文件数据(仅包含请求回来的 data 对象)
|
||||
// 单个文件取第 0 项的 rawResponse
|
||||
document: formData.documentFileList[0]?.rawResponse || null,
|
||||
|
||||
// 多个文件用 map 提取数组
|
||||
proofOfPayment: formData.paymentFileList.map(f => f.rawResponse).filter(Boolean),
|
||||
|
||||
// 签名文件
|
||||
signature: formData.signatureFileList[0]?.rawResponse || null
|
||||
}
|
||||
|
||||
const d = message.loading('提交中...')
|
||||
console.log('--- 准备提交给后端的最终纯净数据 ---')
|
||||
console.log(submitPayload)
|
||||
|
||||
try {
|
||||
await http.post('/v1/material/submit', submitPayload)
|
||||
message.success('提报成功!')
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} finally {
|
||||
d.destroy()
|
||||
// await http.post('/v1/material/submit_all', submitPayload)
|
||||
message.success('材料已正式提交成功!')
|
||||
} catch (err) {
|
||||
message.error('提交失败,请联系管理员')
|
||||
}
|
||||
} else {
|
||||
message.error('请完善必填信息')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handlePreview = (file) => {
|
||||
window.open(file.url)
|
||||
}
|
||||
const logout = () => { localStorage.clear(); message.info('已退出'); }
|
||||
const handleCancel = () => { message.info('已取消'); }
|
||||
|
||||
const rules = {
|
||||
analystName: [{ required: true, message: '必填', trigger: 'blur' }],
|
||||
parentName: [{ required: true, message: '必填', trigger: 'blur' }],
|
||||
parentPhone: [{ required: true, message: '必填', trigger: 'blur' }],
|
||||
}
|
||||
const logout = () => router.push('/login')
|
||||
const handleCancel = () => router.back()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 页面整体背景:浅色微渐变 */
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f0f2f5;
|
||||
@@ -381,7 +396,6 @@ const rules = {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 顶部装饰条,增加企业级感觉 */
|
||||
.bg-decoration {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -393,7 +407,6 @@ const rules = {
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
/* 主卡片样式 */
|
||||
.main-card {
|
||||
max-width: 960px;
|
||||
width: 100%;
|
||||
@@ -418,7 +431,6 @@ const rules = {
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
/* 头部布局 */
|
||||
.header-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -455,14 +467,12 @@ const rules = {
|
||||
color: #8c929b;
|
||||
}
|
||||
|
||||
/* 模块整体 */
|
||||
.section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
/* 模块标题 */
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -479,7 +489,6 @@ const rules = {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* 模块内容区背景 */
|
||||
.section-body {
|
||||
background-color: #fcfcfd;
|
||||
border: 1px solid #f0f0f5;
|
||||
@@ -493,7 +502,6 @@ const rules = {
|
||||
border-color: #e8e8ed;
|
||||
}
|
||||
|
||||
/* 拖拽上传区域优化 */
|
||||
.custom-dragger {
|
||||
background-color: #fafafc;
|
||||
border-radius: 8px;
|
||||
@@ -515,7 +523,6 @@ const rules = {
|
||||
color: #18a058;
|
||||
}
|
||||
|
||||
/* 上传占位符全局样式 */
|
||||
.upload-placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -531,7 +538,6 @@ const rules = {
|
||||
background-color: #f3fcf6;
|
||||
}
|
||||
|
||||
/* 付款截图 */
|
||||
.custom-multi-upload :deep(.n-upload-trigger.n-upload-trigger--image-card) {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
@@ -562,7 +568,6 @@ const rules = {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 签名 */
|
||||
.signature-upload-container :deep(.n-upload-trigger.n-upload-trigger--image-card) {
|
||||
width: 300px;
|
||||
height: 120px;
|
||||
@@ -580,7 +585,6 @@ const rules = {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/* 底部操作按钮 */
|
||||
.footer-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
@@ -597,7 +601,6 @@ const rules = {
|
||||
box-shadow: 0 4px 12px rgba(24, 160, 88, 0.3);
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 768px) {
|
||||
.page-container {
|
||||
padding: 20px 12px;
|
||||
|
||||
Reference in New Issue
Block a user