Merge branch 'main' of https://git.1024tool.vip/zfc/guzhi
@ -6,3 +6,4 @@ VITE_USE_PROXY = true
|
|||||||
|
|
||||||
# base api
|
# base api
|
||||||
VITE_BASE_API = 'http://139.224.70.152:9990/api/v1'
|
VITE_BASE_API = 'http://139.224.70.152:9990/api/v1'
|
||||||
|
# VITE_BASE_API = 'http://127.0.0.1:9999/api/v1'
|
||||||
|
|||||||
@ -1,395 +1,5 @@
|
|||||||
import { request } from '@/utils'
|
import { request } from '@/utils'
|
||||||
|
|
||||||
const baseValuationDetail = {
|
|
||||||
valuation_result: 1180000,
|
|
||||||
created_at: '2024-11-10T09:30:00Z',
|
|
||||||
reviewed_at: null,
|
|
||||||
status: 'pending',
|
|
||||||
admin_notes: null,
|
|
||||||
asset_name: '蜀绣传承精品',
|
|
||||||
institution: '天府非遗文化发展有限公司',
|
|
||||||
industry: '文化创意',
|
|
||||||
annual_revenue: 980000,
|
|
||||||
rd_investment: 165000,
|
|
||||||
three_year_income: [890000, 975000, 1180000],
|
|
||||||
funding_status: '国家资助',
|
|
||||||
inheritor_level: '市级传承人',
|
|
||||||
inheritor_age_count: [4, 6, 2],
|
|
||||||
inheritor_certificates: [
|
|
||||||
'https://dummyimage.com/120x80/edf2ff/409eff&text=证书A1',
|
|
||||||
'https://dummyimage.com/120x80/fef6f0/f0a020&text=证书A2',
|
|
||||||
],
|
|
||||||
heritage_level: '国家级非遗',
|
|
||||||
heritage_asset_level: '一级保护',
|
|
||||||
patent_application_no: '1111111,2222222',
|
|
||||||
historical_evidence: {
|
|
||||||
artifacts: 1,
|
|
||||||
ancient_literature: 2,
|
|
||||||
inheritor_testimony: 0,
|
|
||||||
modern_research: 3,
|
|
||||||
},
|
|
||||||
patent_certificates: ['https://dummyimage.com/120x80/e8f5e9/34a853&text=专利1'],
|
|
||||||
pattern_images: ['https://dummyimage.com/120x80/f3e8ff/9c27b0&text=纹样1'],
|
|
||||||
application_maturity: '推广阶段',
|
|
||||||
application_coverage: '全国覆盖',
|
|
||||||
cooperation_depth: '品牌联名',
|
|
||||||
offline_activities: 4,
|
|
||||||
platform_accounts: {
|
|
||||||
bilibili: { account: 'B站@蜀绣', likes: 1260, comments: 320, shares: 188 },
|
|
||||||
},
|
|
||||||
sales_volume: 5200,
|
|
||||||
link_views: 86500,
|
|
||||||
circulation: '500-1000份',
|
|
||||||
last_market_activity: '近3个月',
|
|
||||||
monthly_transaction: '50-100万元',
|
|
||||||
price_fluctuation: [1200, 3200],
|
|
||||||
model_value_b: 1250000,
|
|
||||||
market_value_c: 1180000,
|
|
||||||
final_value_ab: 1220000,
|
|
||||||
dynamic_pledge_rate: 0.62,
|
|
||||||
calculation_result: {
|
|
||||||
flow: [
|
|
||||||
{
|
|
||||||
title: '基础估值',
|
|
||||||
description: '基于近三年收益与研发投入的模型估值',
|
|
||||||
value: '¥1,250,000',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '市场对标',
|
|
||||||
description: '结合同类资产市场成交价修正',
|
|
||||||
value: '¥1,180,000',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '综合校准',
|
|
||||||
description: '叠加ESG、政策匹配度得出最终估值',
|
|
||||||
value: '¥1,220,000',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const valuationRecords = [
|
|
||||||
{
|
|
||||||
id: 2001,
|
|
||||||
phone: '13800138001',
|
|
||||||
wechat: 'zhangsan_wx',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2002,
|
|
||||||
phone: '13800138002',
|
|
||||||
wechat: 'lisi2024',
|
|
||||||
valuation_result: 880000,
|
|
||||||
created_at: '2024-11-09T14:20:00Z',
|
|
||||||
reviewed_at: '2024-11-09T16:45:00Z',
|
|
||||||
status: 'approved',
|
|
||||||
admin_notes: '评估结果合理,已通过审核',
|
|
||||||
asset_name: '景泰蓝掐丝珐琅',
|
|
||||||
institution: '京华非遗研究院',
|
|
||||||
application_maturity: '成熟期',
|
|
||||||
application_coverage: '华北地区',
|
|
||||||
cooperation_depth: '科技载体',
|
|
||||||
platform_accounts: {
|
|
||||||
douyin: { account: '抖音@景泰蓝工坊', likes: 2350, comments: 610, shares: 302 },
|
|
||||||
},
|
|
||||||
price_fluctuation: [980, 2680],
|
|
||||||
calculation_result: {
|
|
||||||
flow: [
|
|
||||||
{
|
|
||||||
title: '基础估值',
|
|
||||||
description: '模型估算品牌溢价后得出结果',
|
|
||||||
value: '¥900,000',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '市场对标',
|
|
||||||
description: '对比近六个月文博拍卖价格',
|
|
||||||
value: '¥860,000',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '综合校准',
|
|
||||||
description: '结合政策扶持与线上声量校准',
|
|
||||||
value: '¥880,000',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2003,
|
|
||||||
phone: '13800138003',
|
|
||||||
wechat: 'wangwu_user',
|
|
||||||
valuation_result: 2100000,
|
|
||||||
created_at: '2024-11-08T16:45:00Z',
|
|
||||||
reviewed_at: '2024-11-08T18:30:00Z',
|
|
||||||
status: 'approved',
|
|
||||||
admin_notes: '评估价格偏高,但审核通过',
|
|
||||||
asset_name: '苗绣银饰',
|
|
||||||
institution: '黔锦民族文化有限公司',
|
|
||||||
industry: '民族工艺',
|
|
||||||
funding_status: '地方配套资金',
|
|
||||||
inheritor_level: '国家级代表性传承人',
|
|
||||||
inheritor_age_count: [2, 3, 1],
|
|
||||||
application_coverage: '西南片区',
|
|
||||||
platform_accounts: {
|
|
||||||
kuaishou: { account: '快手@苗绣手作', likes: 1800, comments: 420, shares: 210 },
|
|
||||||
},
|
|
||||||
price_fluctuation: [2600, 5200],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2004,
|
|
||||||
phone: '13800138004',
|
|
||||||
wechat: 'zhaoliu_vip',
|
|
||||||
valuation_result: 560000,
|
|
||||||
created_at: '2024-11-07T11:15:00Z',
|
|
||||||
status: 'pending',
|
|
||||||
asset_name: '景德镇青花',
|
|
||||||
institution: '景尚文化传播有限公司',
|
|
||||||
industry: '陶瓷制造',
|
|
||||||
funding_status: '社会资本',
|
|
||||||
platform_accounts: {
|
|
||||||
bilibili: { account: 'B站@青花研习社', likes: 860, comments: 146, shares: 98 },
|
|
||||||
},
|
|
||||||
price_fluctuation: [560, 1200],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2005,
|
|
||||||
phone: '13800138005',
|
|
||||||
wechat: 'sunqi888',
|
|
||||||
valuation_result: 1680000,
|
|
||||||
created_at: '2024-11-06T08:30:00Z',
|
|
||||||
reviewed_at: '2024-11-06T10:15:00Z',
|
|
||||||
status: 'approved',
|
|
||||||
admin_notes: '评估数据完整,审核通过',
|
|
||||||
asset_name: '藏医药香丸',
|
|
||||||
institution: '高原本草研究中心',
|
|
||||||
industry: '中医药',
|
|
||||||
application_coverage: '西藏及周边',
|
|
||||||
cooperation_depth: '国家外交礼品',
|
|
||||||
platform_accounts: {
|
|
||||||
douyin: { account: '抖音@藏医手作', likes: 3120, comments: 815, shares: 356 },
|
|
||||||
},
|
|
||||||
price_fluctuation: [3200, 7600],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2006,
|
|
||||||
phone: '13800138006',
|
|
||||||
wechat: 'zhouba2024',
|
|
||||||
valuation_result: 950000,
|
|
||||||
created_at: '2024-11-05T13:20:00Z',
|
|
||||||
status: 'pending',
|
|
||||||
asset_name: '苏绣屏风',
|
|
||||||
institution: '苏澜绣坊',
|
|
||||||
funding_status: '企业自筹',
|
|
||||||
platform_accounts: {
|
|
||||||
bilibili: { account: 'B站@苏绣博物馆', likes: 980, comments: 240, shares: 130 },
|
|
||||||
},
|
|
||||||
price_fluctuation: [1500, 3600],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2007,
|
|
||||||
phone: '13800138007',
|
|
||||||
wechat: 'wujiu_user',
|
|
||||||
valuation_result: 3200000,
|
|
||||||
created_at: '2024-11-04T15:45:00Z',
|
|
||||||
reviewed_at: '2024-11-04T17:20:00Z',
|
|
||||||
status: 'approved',
|
|
||||||
admin_notes: '高价值资产,评估结果准确',
|
|
||||||
asset_name: '宋锦织造',
|
|
||||||
institution: '苏州织造研究所',
|
|
||||||
funding_status: '国家重点补贴',
|
|
||||||
inheritor_age_count: [6, 8, 4],
|
|
||||||
application_maturity: '成熟期',
|
|
||||||
cooperation_depth: '科技载体',
|
|
||||||
platform_accounts: {
|
|
||||||
douyin: { account: '抖音@宋锦织造', likes: 4800, comments: 1020, shares: 520 },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2008,
|
|
||||||
phone: '13800138008',
|
|
||||||
wechat: 'zhengshi_vip',
|
|
||||||
valuation_result: 750000,
|
|
||||||
created_at: '2024-11-03T10:10:00Z',
|
|
||||||
reviewed_at: '2024-11-03T12:00:00Z',
|
|
||||||
status: 'approved',
|
|
||||||
admin_notes: '评估流程规范,结果可信',
|
|
||||||
asset_name: '黄梅挑花',
|
|
||||||
institution: '徽楚非遗中心',
|
|
||||||
cooperation_depth: '品牌联名',
|
|
||||||
price_fluctuation: [980, 1800],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2009,
|
|
||||||
phone: '13800138009',
|
|
||||||
wechat: 'chenjun2024',
|
|
||||||
valuation_result: 1890000,
|
|
||||||
created_at: '2024-11-02T14:30:00Z',
|
|
||||||
status: 'pending',
|
|
||||||
asset_name: '黎锦织造',
|
|
||||||
institution: '海南黎锦工坊',
|
|
||||||
funding_status: '国家资助',
|
|
||||||
application_coverage: '华南地区',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2010,
|
|
||||||
phone: '13800138010',
|
|
||||||
wechat: 'liuxia_user',
|
|
||||||
valuation_result: 430000,
|
|
||||||
created_at: '2024-11-01T11:45:00Z',
|
|
||||||
reviewed_at: '2024-11-01T13:30:00Z',
|
|
||||||
status: 'approved',
|
|
||||||
admin_notes: '低价值资产,评估合理',
|
|
||||||
asset_name: '大漆工艺',
|
|
||||||
institution: '榫卯器物社',
|
|
||||||
funding_status: '地方专项',
|
|
||||||
cooperation_depth: '品牌联名',
|
|
||||||
platform_accounts: {
|
|
||||||
bilibili: { account: 'B站@大漆工坊', likes: 420, comments: 75, shares: 33 },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2011,
|
|
||||||
phone: '13800138011',
|
|
||||||
wechat: 'zhaolei2024',
|
|
||||||
valuation_result: 2100000,
|
|
||||||
created_at: '2024-10-31T09:20:00Z',
|
|
||||||
reviewed_at: '2024-10-31T11:00:00Z',
|
|
||||||
status: 'approved',
|
|
||||||
admin_notes: '评估报告详细,数据支撑充分',
|
|
||||||
asset_name: '龙泉青瓷',
|
|
||||||
institution: '浙瓷非遗研究院',
|
|
||||||
cooperation_depth: '科技载体',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2012,
|
|
||||||
phone: '13800138012',
|
|
||||||
wechat: 'sunmei_vip',
|
|
||||||
valuation_result: 680000,
|
|
||||||
created_at: '2024-10-30T16:15:00Z',
|
|
||||||
status: 'pending',
|
|
||||||
asset_name: '侗锦织造',
|
|
||||||
institution: '黔东南侗锦合作社',
|
|
||||||
funding_status: '社会资本',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const mockValuationDetails = valuationRecords.map((record) => ({
|
|
||||||
...baseValuationDetail,
|
|
||||||
...record,
|
|
||||||
}))
|
|
||||||
|
|
||||||
const mockAppUsers = [
|
|
||||||
{
|
|
||||||
id: 11111111,
|
|
||||||
phone: '15021982682',
|
|
||||||
wechat: 'f1498480844',
|
|
||||||
created_at: '2024-01-15T10:30:00Z',
|
|
||||||
notes: '测试用户1',
|
|
||||||
remaining_count: 1,
|
|
||||||
user_type: '体验用户',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 11111112,
|
|
||||||
phone: '13800138002',
|
|
||||||
wechat: 'wx_limming2024',
|
|
||||||
created_at: '2024-02-20T14:20:00Z',
|
|
||||||
notes: '付费用户',
|
|
||||||
remaining_count: 5,
|
|
||||||
user_type: '付费用户',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 11111113,
|
|
||||||
phone: '13800138003',
|
|
||||||
wechat: null,
|
|
||||||
created_at: '2024-03-10T08:45:00Z',
|
|
||||||
notes: null,
|
|
||||||
remaining_count: 0,
|
|
||||||
user_type: '体验用户',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 11111114,
|
|
||||||
phone: '13800138004',
|
|
||||||
wechat: 'chenjun_vip',
|
|
||||||
created_at: '2024-04-05T11:30:00Z',
|
|
||||||
notes: 'VIP用户',
|
|
||||||
remaining_count: 10,
|
|
||||||
user_type: 'VIP',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 11111115,
|
|
||||||
phone: '13800138005',
|
|
||||||
wechat: 'liuxia888',
|
|
||||||
created_at: '2024-05-12T16:15:00Z',
|
|
||||||
notes: '体验用户',
|
|
||||||
remaining_count: 3,
|
|
||||||
user_type: '体验用户',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 11111116,
|
|
||||||
phone: '13800138006',
|
|
||||||
wechat: null,
|
|
||||||
created_at: '2024-06-18T09:00:00Z',
|
|
||||||
notes: '新注册用户',
|
|
||||||
remaining_count: 2,
|
|
||||||
user_type: '体验用户',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 11111117,
|
|
||||||
phone: '13800138007',
|
|
||||||
wechat: 'zhaolei2024',
|
|
||||||
created_at: '2024-07-22T12:45:00Z',
|
|
||||||
notes: null,
|
|
||||||
remaining_count: 0,
|
|
||||||
user_type: '体验用户',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 11111118,
|
|
||||||
phone: '13800138008',
|
|
||||||
wechat: 'sunmei_user',
|
|
||||||
created_at: '2024-08-30T15:20:00Z',
|
|
||||||
notes: '活跃用户',
|
|
||||||
remaining_count: 7,
|
|
||||||
user_type: 'VIP',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const defaultInvoiceHeaders = [
|
|
||||||
{
|
|
||||||
company_name: '成都文创科技有限公司',
|
|
||||||
tax_number: '91510100MA7XYZ1234',
|
|
||||||
register_address: '四川省成都市高新区天府三街666号',
|
|
||||||
register_phone: '028-66666666',
|
|
||||||
bank_name: '招商银行成都分行',
|
|
||||||
bank_account: '6225 6666 8888 0000',
|
|
||||||
email: 'finance@scwenchuang.com',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
company_name: '天府文化发展有限公司',
|
|
||||||
tax_number: '91510100678912345K',
|
|
||||||
register_address: '四川省成都市武侯区科华北路88号',
|
|
||||||
register_phone: '028-12345678',
|
|
||||||
bank_name: '中国工商银行成都分行',
|
|
||||||
bank_account: '6212 8888 0000 9999',
|
|
||||||
email: 'invoice@tfculture.com',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const defaultOperationLogs = [
|
|
||||||
{
|
|
||||||
time: '2025-10-31 18:30:30',
|
|
||||||
operator: 'admin',
|
|
||||||
records: ['剩余估值次数:0 -> 1', '类型:付费估值', '备注:新用户'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
time: '2025-10-31 18:30:30',
|
|
||||||
operator: 'admin',
|
|
||||||
records: ['剩余估值次数:2 -> 1', '类型:付费估值', '备注:退款'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
time: '2025-10-31 18:30:30',
|
|
||||||
operator: 'admin',
|
|
||||||
records: ['用户备注:111111111111 -> 22222222222222222222'],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
login: (data) => request.post('/base/access_token', data, { noNeedToken: true }),
|
login: (data) => request.post('/base/access_token', data, { noNeedToken: true }),
|
||||||
getUserInfo: () => request.get('/base/userinfo'),
|
getUserInfo: () => request.get('/base/userinfo'),
|
||||||
@ -430,379 +40,28 @@ export default {
|
|||||||
// auditlog
|
// auditlog
|
||||||
getAuditLogList: (params = {}) => request.get('/auditlog/list', { params }),
|
getAuditLogList: (params = {}) => request.get('/auditlog/list', { params }),
|
||||||
// app users (客户端用户管理) - 使用现有的后端接口
|
// app users (客户端用户管理) - 使用现有的后端接口
|
||||||
getAppUserList: (params = {}) => {
|
getAppUserList: (params = {}) => request.get('/app-user-admin/list', { params }),
|
||||||
// 模拟分页和搜索
|
updateAppUserQuota: (data = {}) => request.post('/app-user-admin/quota', data),
|
||||||
let filteredUsers = [...mockAppUsers]
|
getAppUserQuotaLogs: ({ user_id, ...params } = {}) =>
|
||||||
|
request.get(`/app-user-admin/${user_id}/quota-logs`, { params }),
|
||||||
// 手机号搜索
|
|
||||||
if (params.phone) {
|
|
||||||
filteredUsers = filteredUsers.filter(user =>
|
|
||||||
user.phone.includes(params.phone)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 微信号搜索
|
|
||||||
if (params.wechat) {
|
|
||||||
filteredUsers = filteredUsers.filter(user =>
|
|
||||||
user.wechat && user.wechat.includes(params.wechat)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注册时间筛选(日期范围)
|
|
||||||
if (params.created_at && Array.isArray(params.created_at) && params.created_at.length === 2) {
|
|
||||||
const [startTime, endTime] = params.created_at
|
|
||||||
filteredUsers = filteredUsers.filter(user => {
|
|
||||||
if (!user.created_at) return false
|
|
||||||
const userTime = new Date(user.created_at).getTime()
|
|
||||||
return userTime >= startTime && userTime <= endTime
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 分页处理
|
|
||||||
const page = Number(params.page) || 1
|
|
||||||
const pageSize = Number(params.page_size) || 10
|
|
||||||
const startIndex = (page - 1) * pageSize
|
|
||||||
const endIndex = startIndex + pageSize
|
|
||||||
const paginatedUsers = filteredUsers.slice(startIndex, endIndex)
|
|
||||||
|
|
||||||
// 返回 Promise 模拟异步请求
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
resolve({
|
|
||||||
data: paginatedUsers,
|
|
||||||
total: filteredUsers.length,
|
|
||||||
page,
|
|
||||||
page_size: pageSize,
|
|
||||||
})
|
|
||||||
}, 300)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getAppUserById: (params = {}) =>
|
|
||||||
new Promise((resolve) => {
|
|
||||||
const id = Number(params.id)
|
|
||||||
const user = mockAppUsers.find((item) => item.id === id) || {}
|
|
||||||
setTimeout(() => {
|
|
||||||
resolve({
|
|
||||||
baseInfo: {
|
|
||||||
id: user.id,
|
|
||||||
phone: user.phone,
|
|
||||||
wechat: user.wechat,
|
|
||||||
register_time: user.created_at,
|
|
||||||
notes: user.notes,
|
|
||||||
remaining_count: user.remaining_count,
|
|
||||||
user_type: user.user_type || '体验用户',
|
|
||||||
},
|
|
||||||
invoiceHeaders: defaultInvoiceHeaders,
|
|
||||||
operationLogs: defaultOperationLogs,
|
|
||||||
})
|
|
||||||
}, 300)
|
|
||||||
}),
|
|
||||||
createAppUser: (data = {}) => request.post('/app-user/register', data),
|
createAppUser: (data = {}) => request.post('/app-user/register', data),
|
||||||
updateAppUser: (data = {}) => request.post('/app-user/update', data),
|
updateAppUser: (data = {}) => request.post('/app-user/update', data),
|
||||||
deleteAppUser: (params = {}) => request.delete('/app-user/delete', { params }),
|
deleteAppUser: (params = {}) => request.delete('/app-user/delete', { params }),
|
||||||
// invoice (开票记录)
|
// invoice (交易管理-对公转账记录)
|
||||||
getInvoiceList: (params = {}) => {
|
getInvoiceList: (params = {}) => request.get('/transactions/receipts', { params }),
|
||||||
// Mock 数据
|
getInvoiceById: (params = {}) => request.get(`/transactions/receipts/${params.id}`, { params }),
|
||||||
const mockInvoices = [
|
// 后端接口要求请求体包裹在 data 字段下
|
||||||
{
|
sendInvoice: (data = {}) => request.post('/transactions/send-email', { data }),
|
||||||
id: 1001,
|
|
||||||
created_at: '2024-11-10T09:30:00Z',
|
|
||||||
ticket_type: 'electronic',
|
|
||||||
phone: '13800138001',
|
|
||||||
email: 'zhangsan@company1.com',
|
|
||||||
company_name: '北京科技有限公司',
|
|
||||||
tax_number: '91110000123456789A',
|
|
||||||
register_address: '北京市朝阳区科技园区A座1001室',
|
|
||||||
register_phone: '010-12345678',
|
|
||||||
bank_name: '中国工商银行北京分行',
|
|
||||||
bank_account: '6222021234567890123',
|
|
||||||
invoice_type: 'special',
|
|
||||||
status: 'pending'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 1002,
|
|
||||||
created_at: '2024-11-09T14:20:00Z',
|
|
||||||
ticket_type: 'paper',
|
|
||||||
phone: '13800138002',
|
|
||||||
email: 'lisi@company2.com',
|
|
||||||
company_name: '上海贸易股份有限公司',
|
|
||||||
tax_number: '91310000987654321B',
|
|
||||||
register_address: '上海市浦东新区金融街B座2002室',
|
|
||||||
register_phone: '021-87654321',
|
|
||||||
bank_name: '中国建设银行上海分行',
|
|
||||||
bank_account: '6217001234567890124',
|
|
||||||
invoice_type: 'normal',
|
|
||||||
status: 'invoiced'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 1003,
|
|
||||||
created_at: '2024-11-08T16:45:00Z',
|
|
||||||
ticket_type: 'electronic',
|
|
||||||
phone: '13800138003',
|
|
||||||
email: 'wangwu@company3.com',
|
|
||||||
company_name: '深圳创新科技有限公司',
|
|
||||||
tax_number: '91440300456789012C',
|
|
||||||
register_address: '深圳市南山区高新技术园C座3003室',
|
|
||||||
register_phone: '0755-23456789',
|
|
||||||
bank_name: '招商银行深圳分行',
|
|
||||||
bank_account: '6214851234567890125',
|
|
||||||
invoice_type: 'special',
|
|
||||||
status: 'rejected'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 1004,
|
|
||||||
created_at: '2024-11-07T11:15:00Z',
|
|
||||||
ticket_type: 'paper',
|
|
||||||
phone: '13800138004',
|
|
||||||
email: 'zhaoliu@company4.com',
|
|
||||||
company_name: '广州制造业集团有限公司',
|
|
||||||
tax_number: '91440100789012345D',
|
|
||||||
register_address: '广州市天河区商务中心D座4004室',
|
|
||||||
register_phone: '020-34567890',
|
|
||||||
bank_name: '中国银行广州分行',
|
|
||||||
bank_account: '6013821234567890126',
|
|
||||||
invoice_type: 'normal',
|
|
||||||
status: 'pending'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 1005,
|
|
||||||
created_at: '2024-11-06T08:30:00Z',
|
|
||||||
ticket_type: 'electronic',
|
|
||||||
phone: '13800138005',
|
|
||||||
email: 'sunqi@company5.com',
|
|
||||||
company_name: '杭州互联网科技有限公司',
|
|
||||||
tax_number: '91330100012345678E',
|
|
||||||
register_address: '杭州市西湖区互联网小镇E座5005室',
|
|
||||||
register_phone: '0571-45678901',
|
|
||||||
bank_name: '浙商银行杭州分行',
|
|
||||||
bank_account: '6228481234567890127',
|
|
||||||
invoice_type: 'special',
|
|
||||||
status: 'invoiced'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 1006,
|
|
||||||
created_at: '2024-11-05T13:20:00Z',
|
|
||||||
ticket_type: 'paper',
|
|
||||||
phone: '13800138006',
|
|
||||||
email: 'zhouba@company6.com',
|
|
||||||
company_name: '成都软件开发有限公司',
|
|
||||||
tax_number: '91510100345678901F',
|
|
||||||
register_address: '成都市高新区软件园F座6006室',
|
|
||||||
register_phone: '028-56789012',
|
|
||||||
bank_name: '中国农业银行成都分行',
|
|
||||||
bank_account: '6230521234567890128',
|
|
||||||
invoice_type: 'normal',
|
|
||||||
status: 'pending'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 1007,
|
|
||||||
created_at: '2024-11-04T15:45:00Z',
|
|
||||||
ticket_type: 'electronic',
|
|
||||||
phone: '13800138007',
|
|
||||||
email: 'wujiu@company7.com',
|
|
||||||
company_name: '武汉新能源科技有限公司',
|
|
||||||
tax_number: '91420100678901234G',
|
|
||||||
register_address: '武汉市江汉区新能源产业园G座7007室',
|
|
||||||
register_phone: '027-67890123',
|
|
||||||
bank_name: '交通银行武汉分行',
|
|
||||||
bank_account: '6222601234567890129',
|
|
||||||
invoice_type: 'special',
|
|
||||||
status: 'invoiced'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 1008,
|
|
||||||
created_at: '2024-11-03T10:10:00Z',
|
|
||||||
ticket_type: 'paper',
|
|
||||||
phone: '13800138008',
|
|
||||||
email: 'zhengshi@company8.com',
|
|
||||||
company_name: '西安电子商务有限公司',
|
|
||||||
tax_number: '91610100901234567H',
|
|
||||||
register_address: '西安市雁塔区电商产业园H座8008室',
|
|
||||||
register_phone: '029-78901234',
|
|
||||||
bank_name: '中信银行西安分行',
|
|
||||||
bank_account: '6217711234567890130',
|
|
||||||
invoice_type: 'normal',
|
|
||||||
status: 'refunded'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 1009,
|
|
||||||
created_at: '2024-11-02T14:30:00Z',
|
|
||||||
ticket_type: 'electronic',
|
|
||||||
phone: '13800138009',
|
|
||||||
email: 'wangwu@company9.com',
|
|
||||||
company_name: '天津物流科技有限公司',
|
|
||||||
tax_number: '91120000234567890I',
|
|
||||||
register_address: '天津市滨海新区物流园I座9009室',
|
|
||||||
register_phone: '022-89012345',
|
|
||||||
bank_name: '民生银行天津分行',
|
|
||||||
bank_account: '6226221234567890131',
|
|
||||||
invoice_type: 'special',
|
|
||||||
status: 'refunded'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 1010,
|
|
||||||
created_at: '2024-11-01T11:45:00Z',
|
|
||||||
ticket_type: 'paper',
|
|
||||||
phone: '13800138010',
|
|
||||||
email: 'liuliu@company10.com',
|
|
||||||
company_name: '重庆智能制造有限公司',
|
|
||||||
tax_number: '91500000567890123J',
|
|
||||||
register_address: '重庆市渝北区智能制造园J座1010室',
|
|
||||||
register_phone: '023-90123456',
|
|
||||||
bank_name: '华夏银行重庆分行',
|
|
||||||
bank_account: '6228881234567890132',
|
|
||||||
invoice_type: 'normal',
|
|
||||||
status: 'rejected'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
// 模拟分页和搜索
|
|
||||||
let filteredInvoices = [...mockInvoices]
|
|
||||||
|
|
||||||
// 手机号搜索
|
|
||||||
if (params.phone) {
|
|
||||||
filteredInvoices = filteredInvoices.filter(invoice =>
|
|
||||||
invoice.phone.includes(params.phone)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 公司名称搜索
|
|
||||||
if (params.company_name) {
|
|
||||||
filteredInvoices = filteredInvoices.filter(invoice =>
|
|
||||||
invoice.company_name.includes(params.company_name)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 公司税号搜索
|
|
||||||
if (params.tax_number) {
|
|
||||||
filteredInvoices = filteredInvoices.filter(invoice =>
|
|
||||||
invoice.tax_number.includes(params.tax_number)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 状态筛选
|
|
||||||
if (params.status) {
|
|
||||||
filteredInvoices = filteredInvoices.filter(invoice =>
|
|
||||||
invoice.status === params.status
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交时间筛选
|
|
||||||
if (params.created_at && Array.isArray(params.created_at) && params.created_at.length === 2) {
|
|
||||||
const [startDate, endDate] = params.created_at
|
|
||||||
filteredInvoices = filteredInvoices.filter(invoice => {
|
|
||||||
const invoiceDate = new Date(invoice.created_at)
|
|
||||||
return invoiceDate >= new Date(startDate) && invoiceDate <= new Date(endDate)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 分页处理
|
|
||||||
const page = params.page || 1
|
|
||||||
const pageSize = params.page_size || 10
|
|
||||||
const startIndex = (page - 1) * pageSize
|
|
||||||
const endIndex = startIndex + pageSize
|
|
||||||
const paginatedInvoices = filteredInvoices.slice(startIndex, endIndex)
|
|
||||||
|
|
||||||
// 返回 Promise 模拟异步请求
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
resolve({
|
|
||||||
data: paginatedInvoices,
|
|
||||||
total: filteredInvoices.length,
|
|
||||||
page: page,
|
|
||||||
page_size: pageSize
|
|
||||||
})
|
|
||||||
}, 300) // 模拟网络延迟
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getInvoiceById: (params = {}) => request.get('/invoice/detail', { params }),
|
|
||||||
createInvoice: (data = {}) => request.post('/invoice/create', data),
|
|
||||||
updateInvoice: (data = {}) => request.post('/invoice/update', data),
|
|
||||||
deleteInvoice: (params = {}) => request.delete('/invoice/delete', { params }),
|
|
||||||
updateInvoiceStatus: (data = {}) => request.post('/invoice/update-status', data),
|
|
||||||
remindInvoice: (data = {}) => request.post('/invoice/remind', data),
|
|
||||||
refundInvoice: (data = {}) => request.post('/invoice/refund', data),
|
|
||||||
sendInvoice: (data = {}) => request.post('/invoice/send', data),
|
|
||||||
// valuation (估值评估)
|
// valuation (估值评估)
|
||||||
getValuationList: (params = {}) => {
|
getValuationList: (params = {}) => request.get('/valuations/', { params }),
|
||||||
// 模拟分页和搜索
|
getValuationById: (params = {}) => request.get(`/valuations/${params.valuation_id || params.id}`),
|
||||||
let filteredValuations = [...mockValuationDetails]
|
createValuation: (data = {}) => request.post('/valuations', data),
|
||||||
|
updateValuation: (data = {}) => request.put(`/valuations/${data.id}`, data),
|
||||||
// 手机号搜索
|
deleteValuation: (params = {}) => request.delete(`/valuations/${params.valuation_id || params.id}`),
|
||||||
if (params.phone) {
|
approveValuation: (data = {}) =>
|
||||||
filteredValuations = filteredValuations.filter(valuation =>
|
request.post(`/valuations/${data.valuation_id || data.id}/approve`, { admin_notes: data.admin_notes }),
|
||||||
valuation.phone.includes(params.phone)
|
rejectValuation: (data = {}) =>
|
||||||
)
|
request.post(`/valuations/${data.valuation_id || data.id}/reject`, { admin_notes: data.admin_notes }),
|
||||||
}
|
updateValuationNotes: (data = {}) =>
|
||||||
|
request.put(`/valuations/${data.valuation_id || data.id}/admin-notes`, { admin_notes: data.admin_notes }),
|
||||||
// 微信号搜索
|
|
||||||
if (params.wechat) {
|
|
||||||
filteredValuations = filteredValuations.filter(valuation =>
|
|
||||||
valuation.wechat && valuation.wechat.includes(params.wechat)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 状态筛选
|
|
||||||
if (params.status) {
|
|
||||||
filteredValuations = filteredValuations.filter(valuation =>
|
|
||||||
valuation.status === params.status
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交时间筛选
|
|
||||||
if (params.created_at && Array.isArray(params.created_at) && params.created_at.length === 2) {
|
|
||||||
const [startDate, endDate] = params.created_at
|
|
||||||
filteredValuations = filteredValuations.filter(valuation => {
|
|
||||||
const valuationDate = new Date(valuation.created_at)
|
|
||||||
return valuationDate >= new Date(startDate) && valuationDate <= new Date(endDate)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 分页处理
|
|
||||||
const page = params.page || 1
|
|
||||||
const pageSize = params.page_size || 10
|
|
||||||
const startIndex = (page - 1) * pageSize
|
|
||||||
const endIndex = startIndex + pageSize
|
|
||||||
const paginatedValuations = filteredValuations.slice(startIndex, endIndex).map((item) => ({
|
|
||||||
id: item.id,
|
|
||||||
phone: item.phone,
|
|
||||||
wechat: item.wechat,
|
|
||||||
valuation_result: item.valuation_result,
|
|
||||||
created_at: item.created_at,
|
|
||||||
reviewed_at: item.reviewed_at,
|
|
||||||
status: item.status,
|
|
||||||
admin_notes: item.admin_notes,
|
|
||||||
}))
|
|
||||||
|
|
||||||
// 返回 Promise 模拟异步请求
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
resolve({
|
|
||||||
data: paginatedValuations,
|
|
||||||
total: filteredValuations.length,
|
|
||||||
page: page,
|
|
||||||
page_size: pageSize
|
|
||||||
})
|
|
||||||
}, 300) // 模拟网络延迟
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getValuationById: (params = {}) => {
|
|
||||||
const id = Number(params.valuation_id || params.id)
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
const detail = mockValuationDetails.find((item) => item.id === id)
|
|
||||||
if (detail) {
|
|
||||||
resolve({ data: detail })
|
|
||||||
} else {
|
|
||||||
reject({ code: 404, message: '未找到估值详情' })
|
|
||||||
}
|
|
||||||
}, 200)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
createValuation: (data = {}) => request.post('/valuation', data),
|
|
||||||
updateValuation: (data = {}) => request.put(`/valuation/${data.id}`, data),
|
|
||||||
deleteValuation: (params = {}) => request.delete(`/valuation/${params.valuation_id}`),
|
|
||||||
approveValuation: (data = {}) => request.post(`/valuation/${data.valuation_id}/approve`, data),
|
|
||||||
rejectValuation: (data = {}) => request.post(`/valuation/${data.valuation_id}/reject`, data),
|
|
||||||
updateValuationNotes: (data = {}) => request.put(`/valuation/${data.valuation_id}/admin-notes`, data),
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, watch } from 'vue'
|
import { ref, watch, computed } from 'vue'
|
||||||
import { NModal, NCard, NForm, NFormItem, NInput, NButton, NUpload, NSpace, NText } from 'naive-ui'
|
import { NModal, NCard, NForm, NFormItem, NInput, NButton, NUpload, NSpace, NText, useMessage } from 'naive-ui'
|
||||||
|
import { getToken } from '@/utils/auth/token'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
visible: {
|
visible: {
|
||||||
@ -27,6 +28,12 @@ const formData = ref({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const fileList = ref([])
|
const fileList = ref([])
|
||||||
|
const $message = useMessage()
|
||||||
|
|
||||||
|
const uploadUrl = `${import.meta.env.VITE_BASE_API}/upload/file`
|
||||||
|
const uploadHeaders = computed(() => ({
|
||||||
|
Authorization: `Bearer ${getToken()}`,
|
||||||
|
}))
|
||||||
|
|
||||||
// 监听弹窗打开,初始化数据
|
// 监听弹窗打开,初始化数据
|
||||||
watch(
|
watch(
|
||||||
@ -80,10 +87,42 @@ const beforeUpload = (data) => {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 文件上传变化
|
// 文件上传完成
|
||||||
const handleUploadChange = ({ fileList: newFileList }) => {
|
const handleUploadFinish = ({ file, event }) => {
|
||||||
fileList.value = newFileList
|
try {
|
||||||
formData.value.attachments = newFileList.map(file => file.id || file.name)
|
const res = JSON.parse(event.target.response)
|
||||||
|
// 只要返回了 url 字段,就认为上传成功
|
||||||
|
if (res.code === 200 && res.data?.url) {
|
||||||
|
// 显式查找 fileList 中的文件对象进行更新,确保响应式
|
||||||
|
const targetFile = fileList.value.find(f => f.id === file.id) || file
|
||||||
|
targetFile.url = res.data.url
|
||||||
|
targetFile.name = res.data.filename || targetFile.name
|
||||||
|
targetFile.status = 'finished' // 手动标记为完成
|
||||||
|
|
||||||
|
// 更新 formData.attachments
|
||||||
|
updateAttachments()
|
||||||
|
$message.success('上传成功')
|
||||||
|
} else {
|
||||||
|
$message.error(res.message || '上传失败')
|
||||||
|
// 从列表中移除失败的文件
|
||||||
|
const index = fileList.value.findIndex((item) => item.id === file.id)
|
||||||
|
if (index > -1) fileList.value.splice(index, 1)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('上传响应解析失败:', error)
|
||||||
|
$message.error('上传失败:服务器响应异常')
|
||||||
|
const index = fileList.value.findIndex((item) => item.id === file.id)
|
||||||
|
if (index > -1) fileList.value.splice(index, 1)
|
||||||
|
}
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新附件列表
|
||||||
|
const updateAttachments = () => {
|
||||||
|
// 只要有 url 的都算作有效附件
|
||||||
|
formData.value.attachments = fileList.value
|
||||||
|
.map(file => file.url)
|
||||||
|
.filter(url => !!url)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 移除文件
|
// 移除文件
|
||||||
@ -91,6 +130,7 @@ const handleRemove = ({ file }) => {
|
|||||||
const index = fileList.value.findIndex(item => item.id === file.id)
|
const index = fileList.value.findIndex(item => item.id === file.id)
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
fileList.value.splice(index, 1)
|
fileList.value.splice(index, 1)
|
||||||
|
updateAttachments()
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -157,8 +197,10 @@ const modalTitle = props.mode === 'invoice' ? '开票' : '查看发票'
|
|||||||
multiple
|
multiple
|
||||||
:max="2"
|
:max="2"
|
||||||
list-type="image-card"
|
list-type="image-card"
|
||||||
|
:action="uploadUrl"
|
||||||
|
:headers="uploadHeaders"
|
||||||
:before-upload="beforeUpload"
|
:before-upload="beforeUpload"
|
||||||
@change="handleUploadChange"
|
@finish="handleUploadFinish"
|
||||||
@remove="handleRemove"
|
@remove="handleRemove"
|
||||||
:disabled="mode === 'view'"
|
:disabled="mode === 'view'"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { h, onMounted, ref, resolveDirective, withDirectives } from 'vue'
|
import { h, onMounted, ref, resolveDirective, withDirectives } from 'vue'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
import { NButton, NInput, NTag, NSelect, NDatePicker } from 'naive-ui'
|
import { NButton, NInput, NTag, NSelect, NDatePicker } from 'naive-ui'
|
||||||
|
|
||||||
import CommonPage from '@/components/page/CommonPage.vue'
|
import CommonPage from '@/components/page/CommonPage.vue'
|
||||||
@ -7,7 +8,7 @@ import QueryBarItem from '@/components/query-bar/QueryBarItem.vue'
|
|||||||
import CrudTable from '@/components/table/CrudTable.vue'
|
import CrudTable from '@/components/table/CrudTable.vue'
|
||||||
import InvoiceModal from './InvoiceModal.vue'
|
import InvoiceModal from './InvoiceModal.vue'
|
||||||
|
|
||||||
import { formatDate } from '@/utils'
|
import { formatDate, formatDateTime } from '@/utils'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
|
|
||||||
defineOptions({ name: '开票记录' })
|
defineOptions({ name: '开票记录' })
|
||||||
@ -20,6 +21,18 @@ const vPermission = resolveDirective('permission')
|
|||||||
const invoiceModalVisible = ref(false)
|
const invoiceModalVisible = ref(false)
|
||||||
const invoiceModalMode = ref('view') // 'send' 或 'view'
|
const invoiceModalMode = ref('view') // 'send' 或 'view'
|
||||||
const currentInvoice = ref(null)
|
const currentInvoice = ref(null)
|
||||||
|
const submittedDateRange = ref(null)
|
||||||
|
|
||||||
|
const handleDateRangeChange = (val) => {
|
||||||
|
if (val && val.length === 2) {
|
||||||
|
queryItems.value.submitted_start = dayjs(val[0]).startOf('day').format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
queryItems.value.submitted_end = dayjs(val[1]).endOf('day').format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
} else {
|
||||||
|
queryItems.value.submitted_start = null
|
||||||
|
queryItems.value.submitted_end = null
|
||||||
|
}
|
||||||
|
$table.value?.handleSearch()
|
||||||
|
}
|
||||||
|
|
||||||
// 状态选项
|
// 状态选项
|
||||||
const statusOptions = [
|
const statusOptions = [
|
||||||
@ -57,12 +70,9 @@ const renderInvoiceType = (type) => {
|
|||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: 'ID',
|
title: 'ID',
|
||||||
key: 'receiptId',
|
key: 'id',
|
||||||
width: 80,
|
width: 80,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
render(row) {
|
|
||||||
return row.receipt?.id || '-'
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '提交时间',
|
title: '提交时间',
|
||||||
@ -75,38 +85,25 @@ const columns = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '付款凭证',
|
title: '付款凭证',
|
||||||
key: 'receipt',
|
key: 'receipts',
|
||||||
width: 140,
|
width: 140,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
render(row) {
|
render(row) {
|
||||||
const list = Array.isArray(row.receipt) ? row.receipt : row.receipt ? [row.receipt] : []
|
const list = Array.isArray(row.receipts) ? row.receipts : []
|
||||||
const urls = list
|
const urls = list.map((item) => item?.url).filter(Boolean)
|
||||||
.map((item) => (typeof item === 'string' ? item : item?.url))
|
|
||||||
.filter(Boolean)
|
|
||||||
if (!urls.length) return '-'
|
if (!urls.length) return '-'
|
||||||
return h(
|
return h(
|
||||||
'div',
|
'div',
|
||||||
{ style: 'display:flex; gap:6px; justify-content:center;' },
|
{ style: 'display:flex; gap:6px; justify-content:center; align-items: center;' },
|
||||||
urls.slice(0, 3).map((url, idx) =>
|
urls.slice(0, 3).map((url, idx) =>
|
||||||
h(
|
|
||||||
'a',
|
|
||||||
{
|
|
||||||
href: url,
|
|
||||||
target: '_blank',
|
|
||||||
rel: 'noopener noreferrer',
|
|
||||||
key: `${url}-${idx}`,
|
|
||||||
style: 'display:inline-block',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
default: () =>
|
|
||||||
h('img', {
|
h('img', {
|
||||||
src: url,
|
src: url,
|
||||||
style:
|
style:
|
||||||
'width:46px;height:46px;object-fit:cover;border-radius:4px;border:1px solid #e5e6eb;',
|
'width:40px;height:40px;object-fit:cover;border-radius:4px;border:1px solid #e5e6eb;cursor:pointer;',
|
||||||
alt: '付款凭证',
|
alt: '付款凭证',
|
||||||
}),
|
onClick: () => window.open(url, '_blank'),
|
||||||
}
|
})
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -217,37 +214,31 @@ const columns = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
// 开票按钮:未开票可编辑,其余只读
|
// 开票按钮:未开票可编辑,其余只读
|
||||||
async function handleInvoice(row) {
|
function handleInvoice(row) {
|
||||||
const editable = row.status === 'pending'
|
const editable = row.status === 'pending'
|
||||||
invoiceModalMode.value = editable ? 'send' : 'view'
|
invoiceModalMode.value = editable ? 'send' : 'view'
|
||||||
invoiceModalVisible.value = true
|
|
||||||
await fetchDetail(row)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchDetail(row) {
|
|
||||||
try {
|
|
||||||
const id = row?.receipt?.id || row.id
|
|
||||||
const { data } = await api.getInvoiceById({ id })
|
|
||||||
currentInvoice.value = data || row
|
|
||||||
} catch (error) {
|
|
||||||
currentInvoice.value = row
|
currentInvoice.value = row
|
||||||
$message.error(error?.message || '获取详情失败')
|
invoiceModalVisible.value = true
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确认发送邮件
|
// 确认发送邮件
|
||||||
async function handleInvoiceConfirm(data) {
|
// 确认发送邮件
|
||||||
|
async function handleInvoiceConfirm(formData) {
|
||||||
try {
|
try {
|
||||||
await api.sendInvoice({
|
const payload = {
|
||||||
email: data.email,
|
receipt_id: formData.id,
|
||||||
subject: data.subject,
|
email: formData.email,
|
||||||
body: data.body,
|
subject: formData.email, // 用户要求 subject 传 email
|
||||||
file_url: data.file_url || currentInvoice.value?.receipt?.url,
|
body: formData.content, // 映射 content -> body
|
||||||
})
|
file_url: formData.attachments?.[0] || '', // 映射 attachments -> file_url
|
||||||
|
}
|
||||||
|
|
||||||
|
await api.sendInvoice(payload)
|
||||||
$message.success('发送成功')
|
$message.success('发送成功')
|
||||||
invoiceModalVisible.value = false
|
invoiceModalVisible.value = false
|
||||||
$table.value?.handleSearch()
|
$table.value?.handleSearch()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
$message.error(error?.message || '操作失败')
|
$message.error(error?.message || '操作失败')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -267,12 +258,12 @@ async function handleInvoiceConfirm(data) {
|
|||||||
<template #queryBar>
|
<template #queryBar>
|
||||||
<QueryBarItem label="提交时间" :label-width="80">
|
<QueryBarItem label="提交时间" :label-width="80">
|
||||||
<NDatePicker
|
<NDatePicker
|
||||||
v-model:value="queryItems.submitted_at"
|
v-model:value="submittedDateRange"
|
||||||
type="daterange"
|
type="daterange"
|
||||||
clearable
|
clearable
|
||||||
placeholder="请选择提交时间"
|
placeholder="请选择提交时间"
|
||||||
style="width: 280px"
|
style="width: 280px"
|
||||||
@update:value="$table?.handleSearch()"
|
@update:value="handleDateRangeChange"
|
||||||
/>
|
/>
|
||||||
</QueryBarItem>
|
</QueryBarItem>
|
||||||
<QueryBarItem label="手机号" :label-width="80">
|
<QueryBarItem label="手机号" :label-width="80">
|
||||||
|
|||||||
@ -1,18 +1,31 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { h, onMounted, ref } from 'vue'
|
import { h, onMounted, ref } from 'vue'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
import { NTag, NButton, NInput, NSelect, NDatePicker } from 'naive-ui'
|
import { NTag, NButton, NInput, NSelect, NDatePicker } from 'naive-ui'
|
||||||
|
|
||||||
import CommonPage from '@/components/page/CommonPage.vue'
|
import CommonPage from '@/components/page/CommonPage.vue'
|
||||||
import QueryBarItem from '@/components/query-bar/QueryBarItem.vue'
|
import QueryBarItem from '@/components/query-bar/QueryBarItem.vue'
|
||||||
import CrudTable from '@/components/table/CrudTable.vue'
|
import CrudTable from '@/components/table/CrudTable.vue'
|
||||||
|
|
||||||
import { formatDate } from '@/utils'
|
import { formatDate, formatDateTime } from '@/utils'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
|
|
||||||
defineOptions({ name: '交易记录' })
|
defineOptions({ name: '交易记录' })
|
||||||
|
|
||||||
const $table = ref(null)
|
const $table = ref(null)
|
||||||
const queryItems = ref({})
|
const queryItems = ref({})
|
||||||
|
const submittedDateRange = ref(null)
|
||||||
|
|
||||||
|
const handleDateRangeChange = (val) => {
|
||||||
|
if (val && val.length === 2) {
|
||||||
|
queryItems.value.submitted_start = dayjs(val[0]).startOf('day').format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
queryItems.value.submitted_end = dayjs(val[1]).endOf('day').format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
} else {
|
||||||
|
queryItems.value.submitted_start = null
|
||||||
|
queryItems.value.submitted_end = null
|
||||||
|
}
|
||||||
|
$table.value?.handleSearch()
|
||||||
|
}
|
||||||
|
|
||||||
const statusOptions = [
|
const statusOptions = [
|
||||||
{ label: '全部', value: '' },
|
{ label: '全部', value: '' },
|
||||||
@ -83,12 +96,12 @@ const columns = [
|
|||||||
<template #queryBar>
|
<template #queryBar>
|
||||||
<QueryBarItem label="凭证时间" :label-width="80">
|
<QueryBarItem label="凭证时间" :label-width="80">
|
||||||
<NDatePicker
|
<NDatePicker
|
||||||
v-model:value="queryItems.created_at"
|
v-model:value="submittedDateRange"
|
||||||
type="daterange"
|
type="daterange"
|
||||||
clearable
|
clearable
|
||||||
placeholder="请选择凭证时间"
|
placeholder="请选择凭证时间"
|
||||||
style="width: 280px"
|
style="width: 280px"
|
||||||
@update:value="$table?.handleSearch()"
|
@update:value="handleDateRangeChange"
|
||||||
/>
|
/>
|
||||||
</QueryBarItem>
|
</QueryBarItem>
|
||||||
<QueryBarItem label="手机号" :label-width="80">
|
<QueryBarItem label="手机号" :label-width="80">
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref, watch, h } from 'vue'
|
import { computed, ref, watch, h } from 'vue'
|
||||||
import { NModal, NButton, NTabs, NTabPane, NDataTable, NSpin } from 'naive-ui'
|
import { NModal, NButton, NTabs, NTabPane, NDataTable, NSpin } from 'naive-ui'
|
||||||
|
import { formatDate } from '@/utils'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
visible: {
|
visible: {
|
||||||
@ -45,7 +46,14 @@ const invoiceColumns = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
const logColumns = [
|
const logColumns = [
|
||||||
{ title: '操作时间', key: 'operation_time', width: 180 },
|
{
|
||||||
|
title: '操作时间',
|
||||||
|
key: 'created_at',
|
||||||
|
width: 180,
|
||||||
|
render(row) {
|
||||||
|
return row.created_at ? formatDate(row.created_at, 'YYYY-MM-DD HH:mm:ss') : '-'
|
||||||
|
},
|
||||||
|
},
|
||||||
{ title: '操作人', key: 'operator_name', width: 120 },
|
{ title: '操作人', key: 'operator_name', width: 120 },
|
||||||
{ title: '操作记录', key: 'operation_detail', ellipsis: { tooltip: true } },
|
{ title: '操作记录', key: 'operation_detail', ellipsis: { tooltip: true } },
|
||||||
]
|
]
|
||||||
|
|||||||
@ -60,9 +60,9 @@ const detailSections = computed(() => {
|
|||||||
fields: [
|
fields: [
|
||||||
{ label: '资产名称', type: 'text', value: detail.asset_name || '-' },
|
{ label: '资产名称', type: 'text', value: detail.asset_name || '-' },
|
||||||
{ label: '所属机构/权利人', type: 'text', value: detail.institution || '-' },
|
{ label: '所属机构/权利人', type: 'text', value: detail.institution || '-' },
|
||||||
{ label: '统一社会信用代码/身份证号', type: 'text', value: detail.credit_code || '-' },
|
{ label: '统一社会信用代码/身份证号', type: 'text', value: detail.credit_code || detail.credit_code_or_id || '-' },
|
||||||
{ label: '所属行业', type: 'text', value: detail.industry || '-' },
|
{ label: '所属行业', type: 'text', value: detail.industry || '-' },
|
||||||
{ label: '业务/传承介绍', type: 'text', value: detail.business_heritage_intro || '-' },
|
{ label: '业务/传承介绍', type: 'text', value: detail.business_heritage_intro || detail.biz_intro || '-' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -80,9 +80,13 @@ const detailSections = computed(() => {
|
|||||||
title: '非遗等级与技术',
|
title: '非遗等级与技术',
|
||||||
fields: [
|
fields: [
|
||||||
{ label: '非遗传承人等级', type: 'text', value: detail.inheritor_level || '-' },
|
{ label: '非遗传承人等级', type: 'text', value: detail.inheritor_level || '-' },
|
||||||
{ label: '非遗传承人年龄水平及数量', type: 'list', value: formatAgeDistribution(detail.inheritor_age_count) },
|
{
|
||||||
|
label: '非遗传承人年龄水平及数量',
|
||||||
|
type: 'list',
|
||||||
|
value: formatAgeDistribution(detail.inheritor_age_count || detail.inheritor_ages),
|
||||||
|
},
|
||||||
{ label: '非遗传承人等级证书', type: 'images', value: detail.inheritor_certificates || [] },
|
{ label: '非遗传承人等级证书', type: 'images', value: detail.inheritor_certificates || [] },
|
||||||
{ label: '非遗等级', type: 'text', value: detail.heritage_level || '-' },
|
{ label: '非遗等级', type: 'text', value: detail.heritage_level || detail.heritage_asset_level || '-' },
|
||||||
{ label: '非遗资产所用专利的申请号', type: 'text', value: detail.patent_application_no || '-' },
|
{ label: '非遗资产所用专利的申请号', type: 'text', value: detail.patent_application_no || '-' },
|
||||||
{ label: '非遗资产历史证明证据及数量', type: 'list', value: formatHistoricalEvidence(detail.historical_evidence) },
|
{ label: '非遗资产历史证明证据及数量', type: 'list', value: formatHistoricalEvidence(detail.historical_evidence) },
|
||||||
{
|
{
|
||||||
@ -99,7 +103,11 @@ const detailSections = computed(() => {
|
|||||||
{ label: '非遗资产应用成熟度', type: 'text', value: detail.application_maturity || detail.implementation_stage || '-' },
|
{ label: '非遗资产应用成熟度', type: 'text', value: detail.application_maturity || detail.implementation_stage || '-' },
|
||||||
{ label: '非遗资产应用覆盖范围', type: 'text', value: detail.application_coverage || detail.coverage_area || '-' },
|
{ label: '非遗资产应用覆盖范围', type: 'text', value: detail.application_coverage || detail.coverage_area || '-' },
|
||||||
{ label: '非遗资产跨界合作深度', type: 'text', value: detail.cooperation_depth || detail.collaboration_type || '-' },
|
{ label: '非遗资产跨界合作深度', type: 'text', value: detail.cooperation_depth || detail.collaboration_type || '-' },
|
||||||
{ label: '近12个月线下相关宣讲活动次数', type: 'text', value: formatNumberValue(detail.offline_activities) },
|
{
|
||||||
|
label: '近12个月线下相关宣讲活动次数',
|
||||||
|
type: 'text',
|
||||||
|
value: formatNumberValue(detail.offline_activities ?? detail.offline_teaching_count),
|
||||||
|
},
|
||||||
{ label: '线上相关宣传账号信息', type: 'list', value: formatPlatformAccounts(detail.platform_accounts) },
|
{ label: '线上相关宣传账号信息', type: 'list', value: formatPlatformAccounts(detail.platform_accounts) },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,12 +1,16 @@
|
|||||||
export const STATUS_OPTIONS = [
|
export const STATUS_OPTIONS = [
|
||||||
{ label: '全部', value: '' },
|
{ label: '全部', value: '' },
|
||||||
{ label: '待审核', value: 'pending' },
|
{ label: '待审核', value: 'pending' },
|
||||||
{ label: '已完成', value: 'approved' },
|
{ label: '已通过', value: 'approved' },
|
||||||
|
{ label: '已拒绝', value: 'rejected' },
|
||||||
|
{ label: '已完成', value: 'success' },
|
||||||
]
|
]
|
||||||
|
|
||||||
export const STATUS_MAP = {
|
export const STATUS_MAP = {
|
||||||
pending: { type: 'warning', text: '待审核' },
|
pending: { type: 'warning', text: '待审核' },
|
||||||
approved: { type: 'success', text: '已完成' },
|
approved: { type: 'success', text: '已通过' },
|
||||||
|
rejected: { type: 'error', text: '已拒绝' },
|
||||||
|
success: { type: 'success', text: '已完成' },
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getStatusConfig = (status) => STATUS_MAP[status] || { type: 'default', text: '未知' }
|
export const getStatusConfig = (status) => STATUS_MAP[status] || { type: 'default', text: '未知' }
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { h, onMounted, ref, watch } from 'vue'
|
import { h, onMounted, ref, watch } from 'vue'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import {
|
import {
|
||||||
NButton,
|
NButton,
|
||||||
@ -29,6 +30,30 @@ const router = useRouter()
|
|||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const $table = ref(null)
|
const $table = ref(null)
|
||||||
const queryItems = ref({})
|
const queryItems = ref({})
|
||||||
|
const submittedDateRange = ref(null)
|
||||||
|
const auditedDateRange = ref(null)
|
||||||
|
|
||||||
|
const handleSubmittedDateRangeChange = (val) => {
|
||||||
|
if (val && val.length === 2) {
|
||||||
|
queryItems.value.submitted_start = dayjs(val[0]).startOf('day').format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
queryItems.value.submitted_end = dayjs(val[1]).endOf('day').format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
} else {
|
||||||
|
queryItems.value.submitted_start = null
|
||||||
|
queryItems.value.submitted_end = null
|
||||||
|
}
|
||||||
|
$table.value?.handleSearch()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleAuditedDateRangeChange = (val) => {
|
||||||
|
if (val && val.length === 2) {
|
||||||
|
queryItems.value.audited_start = dayjs(val[0]).startOf('day').format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
queryItems.value.audited_end = dayjs(val[1]).endOf('day').format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
} else {
|
||||||
|
queryItems.value.audited_start = null
|
||||||
|
queryItems.value.audited_end = null
|
||||||
|
}
|
||||||
|
$table.value?.handleSearch()
|
||||||
|
}
|
||||||
|
|
||||||
// 列表与详情公共方法
|
// 列表与详情公共方法
|
||||||
const renderStatus = (status) => {
|
const renderStatus = (status) => {
|
||||||
@ -36,30 +61,44 @@ const renderStatus = (status) => {
|
|||||||
return h(NTag, { type: config.type }, { default: () => config.text })
|
return h(NTag, { type: config.type }, { default: () => config.text })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getValuationResult = (row) => {
|
||||||
|
if (row?.final_value_ab !== undefined && row.final_value_ab !== null) return row.final_value_ab
|
||||||
|
if (row?.market_value_c !== undefined && row.market_value_c !== null) return row.market_value_c
|
||||||
|
if (row?.model_value_b !== undefined && row.model_value_b !== null) return row.model_value_b
|
||||||
|
return row?.valuation_result
|
||||||
|
}
|
||||||
|
|
||||||
// 列定义
|
// 列定义
|
||||||
const columns = [
|
const columns = [
|
||||||
{ title: '编号', key: 'id', width: 80, align: 'center' },
|
{ title: '编号', key: 'id', width: 80, align: 'center' },
|
||||||
{
|
{
|
||||||
title: '手机号',
|
title: '手机号',
|
||||||
key: 'phone',
|
key: 'user_phone',
|
||||||
width: 120,
|
width: 120,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
ellipsis: { tooltip: true },
|
ellipsis: { tooltip: true },
|
||||||
|
render(row) {
|
||||||
|
return row.user_phone || row.user?.phone || '-'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: '微信号',
|
|
||||||
key: 'wechat',
|
|
||||||
width: 120,
|
|
||||||
align: 'center',
|
|
||||||
ellipsis: { tooltip: true },
|
|
||||||
},
|
},
|
||||||
|
// {
|
||||||
|
// title: '微信号',
|
||||||
|
// key: 'wechat',
|
||||||
|
// width: 120,
|
||||||
|
// align: 'center',
|
||||||
|
// ellipsis: { tooltip: true },
|
||||||
|
// render(row) {
|
||||||
|
// return row.wechat || row.user_wechat || row.user?.wechat || '-'
|
||||||
|
// },
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
title: '评估结果',
|
title: '评估结果',
|
||||||
key: 'valuation_result',
|
key: 'valuation_result',
|
||||||
width: 120,
|
width: 120,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
render(row) {
|
render(row) {
|
||||||
return formatAmount(row.valuation_result)
|
const val = getValuationResult(row)
|
||||||
|
return val || val === 0 ? formatAmount(val) : '-'
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -110,7 +149,7 @@ const columns = [
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (row.status === 'approved') {
|
if (['approved', 'rejected', 'success'].includes(row.status)) {
|
||||||
return h(
|
return h(
|
||||||
NButton,
|
NButton,
|
||||||
{
|
{
|
||||||
@ -286,7 +325,7 @@ watch(
|
|||||||
@keypress.enter="$table?.handleSearch()"
|
@keypress.enter="$table?.handleSearch()"
|
||||||
/>
|
/>
|
||||||
</QueryBarItem>
|
</QueryBarItem>
|
||||||
<QueryBarItem label="微信号" :label-width="80">
|
<!-- <QueryBarItem label="微信号" :label-width="80">
|
||||||
<NInput
|
<NInput
|
||||||
v-model:value="queryItems.wechat"
|
v-model:value="queryItems.wechat"
|
||||||
clearable
|
clearable
|
||||||
@ -295,25 +334,25 @@ watch(
|
|||||||
style="width: 200px"
|
style="width: 200px"
|
||||||
@keypress.enter="$table?.handleSearch()"
|
@keypress.enter="$table?.handleSearch()"
|
||||||
/>
|
/>
|
||||||
</QueryBarItem>
|
</QueryBarItem> -->
|
||||||
<QueryBarItem label="提交时间" :label-width="80">
|
<QueryBarItem label="提交时间" :label-width="80">
|
||||||
<NDatePicker
|
<NDatePicker
|
||||||
v-model:value="queryItems.created_at"
|
v-model:value="submittedDateRange"
|
||||||
type="daterange"
|
type="daterange"
|
||||||
clearable
|
clearable
|
||||||
placeholder="请选择提交时间"
|
placeholder="请选择提交时间"
|
||||||
style="width: 280px"
|
style="width: 280px"
|
||||||
@update:value="$table?.handleSearch()"
|
@update:value="handleSubmittedDateRangeChange"
|
||||||
/>
|
/>
|
||||||
</QueryBarItem>
|
</QueryBarItem>
|
||||||
<QueryBarItem label="审核时间" :label-width="80">
|
<QueryBarItem label="审核时间" :label-width="80">
|
||||||
<NDatePicker
|
<NDatePicker
|
||||||
v-model:value="queryItems.reviewed_at"
|
v-model:value="auditedDateRange"
|
||||||
type="daterange"
|
type="daterange"
|
||||||
clearable
|
clearable
|
||||||
placeholder="请选择审核时间"
|
placeholder="请选择审核时间"
|
||||||
style="width: 280px"
|
style="width: 280px"
|
||||||
@update:value="$table?.handleSearch()"
|
@update:value="handleAuditedDateRangeChange"
|
||||||
/>
|
/>
|
||||||
</QueryBarItem>
|
</QueryBarItem>
|
||||||
<QueryBarItem label="状态" :label-width="80">
|
<QueryBarItem label="状态" :label-width="80">
|
||||||
@ -328,12 +367,6 @@ watch(
|
|||||||
</QueryBarItem>
|
</QueryBarItem>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #action>
|
|
||||||
<NButton type="primary" @click="handleAddContent">
|
|
||||||
<TheIcon icon="mdi:plus" :size="18" class="mr-5" />
|
|
||||||
新增文案设置
|
|
||||||
</NButton>
|
|
||||||
</template>
|
|
||||||
</CrudTable>
|
</CrudTable>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,8 @@ export default {
|
|||||||
// 手机号
|
// 手机号
|
||||||
registerPhone: (data) => request.post('/app-user/register', data, { noNeedToken: true }),
|
registerPhone: (data) => request.post('/app-user/register', data, { noNeedToken: true }),
|
||||||
loginPhone: (data) => request.post('/app-user/login', data, { noNeedToken: true }),
|
loginPhone: (data) => request.post('/app-user/login', data, { noNeedToken: true }),
|
||||||
|
// 估值次数
|
||||||
|
userQuota: () => request.get('/app-user/quota'),
|
||||||
// 短信验证码
|
// 短信验证码
|
||||||
sendVerifyCode: (data) => request.post('/sms/send-code', data, { noNeedToken: true }),
|
sendVerifyCode: (data) => request.post('/sms/send-code', data, { noNeedToken: true }),
|
||||||
loginWithVerifyCode: (data) => request.post('/sms/login', data, { noNeedToken: true }),
|
loginWithVerifyCode: (data) => request.post('/sms/login', data, { noNeedToken: true }),
|
||||||
|
|||||||
BIN
web1/src/assets/icon/估值记录.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
web1/src/assets/icon/对公转账.png
Normal file
|
After Width: | Height: | Size: 729 B |
BIN
web1/src/assets/icon/报告.png
Normal file
|
After Width: | Height: | Size: 522 B |
BIN
web1/src/assets/icon/抬头管理.png
Normal file
|
After Width: | Height: | Size: 753 B |
BIN
web1/src/assets/icon/证书.png
Normal file
|
After Width: | Height: | Size: 494 B |
BIN
web1/src/assets/img/home_bg.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
web1/src/assets/img/icon_checkbox.png
Normal file
|
After Width: | Height: | Size: 417 B |
BIN
web1/src/assets/img/icon_code.png
Normal file
|
After Width: | Height: | Size: 768 B |
BIN
web1/src/assets/img/icon_user.png
Normal file
|
After Width: | Height: | Size: 404 B |
BIN
web1/src/assets/img/login_bg_full.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
web1/src/assets/img/login_box_left.png
Normal file
|
After Width: | Height: | Size: 272 KiB |
BIN
web1/src/assets/img/login_btn_bg.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
web1/src/assets/img/login_title.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
100
web1/src/components/AppHeader.vue
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-header">
|
||||||
|
<div class="logo-section" @click="handleLogoClick">
|
||||||
|
<img src="@/assets/images/logo.png" alt="logo" class="logo" />
|
||||||
|
</div>
|
||||||
|
<div class="user-section">
|
||||||
|
<div class="user-avatar" @click="handleUserClick">
|
||||||
|
<img src="@/assets/img/icon_user.png" alt="User" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
function handleLogoClick() {
|
||||||
|
router.push('/home')
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleUserClick() {
|
||||||
|
router.push('/user-center')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.app-header {
|
||||||
|
padding: 20px 40px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
|
||||||
|
height: 80px;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-section {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-section:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
height: 40px;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-avatar {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #EBEEF5;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-avatar img {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-avatar:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.app-header {
|
||||||
|
padding: 15px 20px;
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-avatar {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-avatar img {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -49,7 +49,7 @@ export const basicRoutes = [
|
|||||||
path: 'invoice',
|
path: 'invoice',
|
||||||
component: () => import('@/views/user-center/components/InvoiceManagement.vue'),
|
component: () => import('@/views/user-center/components/InvoiceManagement.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
title: '开票管理',
|
title: '抬头管理',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@ -1,150 +1,132 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="home-container">
|
<div class="home-container">
|
||||||
<div class="header">
|
<AppHeader />
|
||||||
<div class="logo-section">
|
|
||||||
<img src="@/assets/images/logo.png" alt="logo" class="logo" />
|
|
||||||
<div class="user-center" @click="handleUserCenter">
|
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<circle cx="12" cy="8" r="4" stroke="currentColor" stroke-width="2"/>
|
|
||||||
<path d="M6 21C6 17.134 8.686 14 12 14C15.314 14 18 17.134 18 21" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
|
||||||
</svg>
|
|
||||||
<span>个人中心</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="content">
|
<div class="content-wrapper">
|
||||||
<div class="title-section">
|
<div class="glass-card">
|
||||||
|
<div class="card-logo">
|
||||||
|
<img src="@/assets/images/logo.png" alt="logo" />
|
||||||
|
</div>
|
||||||
<h1 class="main-title">非遗IP价值评估系统</h1>
|
<h1 class="main-title">非遗IP价值评估系统</h1>
|
||||||
<p class="subtitle">基于深度学习算法的智能评估系统,为您的知识产权和非物质文化遗产提供专业的价值评估服务</p>
|
<p class="subtitle">基于深度学习算法的智能评估系统,为您的知识产权和非物质文化遗产提供专业的价值评估服务</p>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="action-section">
|
<div class="action-section">
|
||||||
<n-button
|
<button class="start-btn" @click="handleStartEvaluation">
|
||||||
type="primary"
|
|
||||||
size="large"
|
|
||||||
class="start-btn"
|
|
||||||
@click="handleStartEvaluation"
|
|
||||||
>
|
|
||||||
开始评估
|
开始评估
|
||||||
</n-button>
|
<img src="@/assets/images/go.png" alt="arrow" class="arrow-icon" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import AppHeader from '@/components/AppHeader.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
function handleStartEvaluation() {
|
function handleStartEvaluation() {
|
||||||
router.push('/pages')
|
router.push('/pages')
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleUserCenter() {
|
|
||||||
router.push('/user-center')
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.home-container {
|
.home-container {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: linear-gradient(135deg, #f5f7fa 0%, #e8eef5 100%);
|
width: 100%;
|
||||||
|
background-image: url('@/assets/img/home_bg.png');
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
|
||||||
padding: 20px 40px;
|
|
||||||
background: rgba(255, 255, 255, 0.9);
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo-section {
|
.content-wrapper {
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
height: 32px;
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-center {
|
|
||||||
font-size: 16px;
|
|
||||||
color: #303133;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 8px 16px;
|
|
||||||
border-radius: 8px;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-center:hover {
|
|
||||||
background: rgba(136, 12, 34, 0.05);
|
|
||||||
color: #880C22;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-center svg {
|
|
||||||
color: currentColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 40px 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-section {
|
.glass-card {
|
||||||
|
background: rgba(255, 255, 255, 0.4);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
-webkit-backdrop-filter: blur(20px);
|
||||||
|
border-radius: 24px;
|
||||||
|
padding: 60px 80px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: 60px;
|
max-width: 900px;
|
||||||
|
width: 100%;
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-logo {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-logo img {
|
||||||
|
height: 48px;
|
||||||
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-title {
|
.main-title {
|
||||||
font-size: 48px;
|
font-size: 48px;
|
||||||
font-weight: 600;
|
font-weight: 800;
|
||||||
color: #303133;
|
color: #333;
|
||||||
line-height: 1.2;
|
margin-bottom: 24px;
|
||||||
margin: 0 0 20px 0;
|
letter-spacing: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.subtitle {
|
.subtitle {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: #606266;
|
color: #555;
|
||||||
line-height: 1.6;
|
line-height: 1.8;
|
||||||
max-width: 700px;
|
max-width: 600px;
|
||||||
margin: 0 auto;
|
margin: 0 auto 50px;
|
||||||
}
|
|
||||||
|
|
||||||
.action-section {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.start-btn {
|
.start-btn {
|
||||||
height: 50px;
|
background: linear-gradient(90deg, #880C22 0%, #B81C36 100%);
|
||||||
padding: 0 60px;
|
color: white;
|
||||||
font-size: 18px;
|
|
||||||
background: linear-gradient(93deg, #880C22 0%, #A30113 100%);
|
|
||||||
border: none;
|
border: none;
|
||||||
|
padding: 12px 40px;
|
||||||
|
font-size: 18px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 4px 12px rgba(136, 12, 34, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.start-btn:hover {
|
.start-btn:hover {
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
box-shadow: 0 8px 16px rgba(136, 12, 34, 0.3);
|
box-shadow: 0 6px 16px rgba(136, 12, 34, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-icon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
|
.glass-card {
|
||||||
|
padding: 40px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.main-title {
|
.main-title {
|
||||||
font-size: 32px;
|
font-size: 32px;
|
||||||
}
|
}
|
||||||
@ -153,10 +135,8 @@ function handleUserCenter() {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.start-btn {
|
.header {
|
||||||
height: 44px;
|
padding: 15px 20px;
|
||||||
padding: 0 40px;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,107 +1,163 @@
|
|||||||
<template>
|
<template>
|
||||||
<AppPage :show-footer="true" bg-cover :style="{ backgroundImage: `url(${bgImg})` }">
|
<div class="page flex-col">
|
||||||
<div
|
<div class="section_1 flex-col" :style="{ backgroundImage: `url(${loginBgFull})` }">
|
||||||
style="transform: translateY(25px)"
|
<div class="box_1 flex-row">
|
||||||
class="m-auto max-w-1500 min-w-750 f-c-c rounded-12 bg-white bg-opacity-80"
|
<div class="section_2 flex-col">
|
||||||
dark:bg-dark
|
<div class="group_1 flex-col" :style="{ backgroundImage: `url(${loginBoxLeft})` }"></div>
|
||||||
>
|
|
||||||
<div w-750 px-20 style="height: 480px; padding-top: 50px; text-align: center;">
|
|
||||||
<img style="width: 371px; height: 60px; margin: auto;" src="@/assets/images/logo.png" alt="">
|
|
||||||
<div mt-50 style="text-align: center; font-size: 48px; color: #303133; line-height: 48px; font-weight: 600; ">
|
|
||||||
非遗IP价值评估系统
|
|
||||||
</div>
|
</div>
|
||||||
<div style="text-align: center; margin-top: 16px; color: #606266;">
|
<div class="section_3 flex-col">
|
||||||
基于深度学习算法的智能评估系统,为您的知识产权和非物质文化遗产提供专业的价值评估服务
|
<img class="image_1" referrerpolicy="no-referrer" :src="loginTitleImg" />
|
||||||
</div>
|
<n-form ref="formRef" :model="loginInfo" :rules="rules" :show-label="false">
|
||||||
<div mt-30>
|
<n-form-item path="phone">
|
||||||
|
<div class="block_1 flex-row">
|
||||||
|
<div class="image-text_1 flex-row">
|
||||||
|
<img class="thumbnail_1" referrerpolicy="no-referrer" :src="iconUserImg" />
|
||||||
<n-input
|
<n-input
|
||||||
v-model:value="loginInfo.phone"
|
v-model:value="loginInfo.phone"
|
||||||
style="display: inline-block; width: 260px; height: 42px; text-align: left; line-height: 42px;"
|
class="input-reset text-group_1 custom-n-input"
|
||||||
placeholder="请输入手机号"
|
placeholder="请输入账号"
|
||||||
:maxlength="11"
|
type="text"
|
||||||
@keypress.enter="handleLogin"
|
:bordered="false"
|
||||||
>
|
|
||||||
<template #prefix>
|
|
||||||
<img style="width: 18px; height: 18px; margin-right: 8px;" src="@/assets/images/phone.png" alt="">
|
|
||||||
</template>
|
|
||||||
</n-input>
|
|
||||||
|
|
||||||
<n-button
|
|
||||||
w-126
|
|
||||||
h-42
|
|
||||||
rounded-5
|
|
||||||
type="primary"
|
|
||||||
style="background: linear-gradient( 93deg, #880C22 0%, #A30113 100%); margin-left: 20px; font-size: 16px; border: none !important;"
|
|
||||||
@click="handleLogin"
|
|
||||||
>
|
|
||||||
立即登录
|
|
||||||
<img style="width: 18px; height: 18px; margin-left: 2px;" src="@/assets/images/go.png" alt="">
|
|
||||||
</n-button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div mt-20 style="display: flex; justify-content: center; align-items: center;">
|
|
||||||
<n-input
|
|
||||||
v-model:value="loginInfo.verifyCode"
|
|
||||||
style="width: 260px; height: 42px;"
|
|
||||||
placeholder="验证码"
|
|
||||||
:maxlength="6"
|
|
||||||
@keypress.enter="handleLogin"
|
@keypress.enter="handleLogin"
|
||||||
/>
|
/>
|
||||||
<n-button
|
</div>
|
||||||
style="width: 126px; height: 42px; margin-left: 12px; font-size: 14px;"
|
</div>
|
||||||
:disabled="countdown > 0"
|
</n-form-item>
|
||||||
|
<div class="block_2 flex-row justify-between">
|
||||||
|
<n-form-item path="verifyCode" class="verify-code-item">
|
||||||
|
<div class="section_4 flex-row">
|
||||||
|
<div class="image-text_2 flex-row">
|
||||||
|
<img class="thumbnail_2" referrerpolicy="no-referrer" :src="iconCodeImg" />
|
||||||
|
<n-input
|
||||||
|
v-model:value="loginInfo.verifyCode"
|
||||||
|
class="input-reset text-group_2 custom-n-input"
|
||||||
|
placeholder="请输入验证码"
|
||||||
|
type="text"
|
||||||
|
:bordered="false"
|
||||||
|
@keypress.enter="handleLogin"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</n-form-item>
|
||||||
|
<div
|
||||||
|
class="text-wrapper_1 flex-col cursor-pointer"
|
||||||
@click="handleSendCode"
|
@click="handleSendCode"
|
||||||
|
:class="{ disabled: countdown > 0 }"
|
||||||
>
|
>
|
||||||
{{ countdown > 0 ? `${countdown}秒后重试` : '发送验证码' }}
|
<span class="text_1">{{
|
||||||
</n-button>
|
countdown > 0 ? `${countdown}秒后重新获取` : '获取验证码'
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</n-form>
|
||||||
|
<div class="block_3 flex-row">
|
||||||
|
<n-checkbox v-model:checked="isAgreed" class="agreement-checkbox">
|
||||||
|
<span class="text-group_3">阅读并同意</span>
|
||||||
|
<span class="text_2" @click.stop="handleOpenAgreement">《用户协议》</span>
|
||||||
|
</n-checkbox>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- User Agreement Modal -->
|
||||||
|
<n-modal
|
||||||
|
v-model:show="showAgreement"
|
||||||
|
preset="card"
|
||||||
|
title="用户服务协议"
|
||||||
|
style="width: 800px; max-width: 90vw;"
|
||||||
|
:bordered="false"
|
||||||
|
>
|
||||||
|
<div class="agreement-content" style="max-height: 60vh; overflow-y: auto; padding: 0 10px;">
|
||||||
|
<h3>特别提示</h3>
|
||||||
|
<p>在此特别提醒您(用户)在注册成为用户之前,请认真阅读本《用户协议》(以下简称“协议”),确保您充分理解本协议中各条款。请您审慎阅读并选择接受或不接受本协议。除非您接受本协议所有条款,否则您无权注册、登录或使用本协议所涉服务。您的注册、登录、使用等行为将视为对本协议的接受,并同意接受本协议各项条款的约束。</p>
|
||||||
|
<p>本协议约定成都文化产权交易所有限公司(以下简称“成都文交所”)与用户之间关于“IP文产通”平台服务(以下简称“服务”)的权利义务。“用户”是指注册、登录、使用本服务的个人。本协议可由成都文交所随时更新,更新后的协议条款一旦公布即代替原来的协议条款,恕不另行通知,用户可在本网站查阅最新版协议条款。在成都文交所修改协议条款后,如果用户不接受修改后的条款,请立即停止使用成都文交所提供的服务,用户继续使用成都文交所提供的服务将被视为接受修改后的协议。</p>
|
||||||
|
|
||||||
|
<h3>一、服务内容</h3>
|
||||||
|
<p>1.1 本服务的具体内容由成都文交所根据实际情况提供,包括但不限于授权用户通过其账号进行相关操作。</p>
|
||||||
|
<p>1.2 成都文交所提供的服务仅限于平台上的相关功能,用户在使用服务时应遵守相关法律法规及本协议的规定。</p>
|
||||||
|
|
||||||
|
<h3>二、用户账号</h3>
|
||||||
|
<p>2.1 用户在使用本服务前需要注册一个账号。账号注册过程中,用户应提供真实、准确、完整的个人资料。</p>
|
||||||
|
<p>2.2 用户有责任妥善保管注册账号信息及账号密码的安全,因用户保管不善可能导致遭受盗号或密码失窃,责任由用户自行承担。</p>
|
||||||
|
|
||||||
|
<h3>三、用户个人信息保护</h3>
|
||||||
|
<p>3.1 保护用户个人信息是成都文交所的一项基本原则,成都文交所将会采取合理的措施保护用户的个人信息。除法律法规规定的情形外,未经用户许可成都文交所不会向第三方公开、透露用户个人信息。</p>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
<!-- <p style="color: #999; text-align: center; font-size: 12px;">(以上内容为示例,请替换为完整版《用户服务协议(暂行)》)</p> -->
|
||||||
|
</div>
|
||||||
|
</n-modal>
|
||||||
|
<div
|
||||||
|
class="text-wrapper_2 flex-col cursor-pointer"
|
||||||
|
@click="handleLogin"
|
||||||
|
:style="{ backgroundImage: `url(${loginBtnBg})` }"
|
||||||
|
>
|
||||||
|
<span class="text_3">立即登录</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</AppPage>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { lStorage, setToken } from '@/utils'
|
import { ref, onBeforeUnmount } from 'vue'
|
||||||
import bgImg from '@/assets/images/login_bg.png'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
import { setToken } from '@/utils'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import { addDynamicRoutes } from '@/router'
|
|
||||||
import { useI18n } from 'vue-i18n'
|
// Images
|
||||||
|
import loginTitleImg from '@/assets/img/login_title.png'
|
||||||
|
import iconUserImg from '@/assets/img/icon_user.png'
|
||||||
|
import iconCodeImg from '@/assets/img/icon_code.png'
|
||||||
|
import iconCheckboxImg from '@/assets/img/icon_checkbox.png'
|
||||||
|
import loginBgFull from '@/assets/img/login_bg_full.png'
|
||||||
|
import loginBoxLeft from '@/assets/img/login_box_left.png'
|
||||||
|
import loginBtnBg from '@/assets/img/login_btn_bg.png'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { query } = useRoute()
|
const { query } = useRoute()
|
||||||
const { t } = useI18n({ useScope: 'global' })
|
|
||||||
|
|
||||||
const loginInfo = ref({
|
const loginInfo = ref({
|
||||||
phone: '',
|
phone: '',
|
||||||
verifyCode: '',
|
verifyCode: '',
|
||||||
|
requestId: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const isAgreed = ref(false)
|
||||||
|
const showAgreement = ref(false)
|
||||||
const countdown = ref(0)
|
const countdown = ref(0)
|
||||||
let countdownTimer = null
|
let countdownTimer = null
|
||||||
|
|
||||||
initLoginInfo()
|
|
||||||
|
|
||||||
function initLoginInfo() {
|
|
||||||
if(localStorage.getItem('phone')){
|
|
||||||
loginInfo.value.phone = localStorage.getItem('phone')
|
|
||||||
}
|
|
||||||
// const localLoginInfo = lStorage.get('loginInfo')
|
|
||||||
// if (localLoginInfo) {
|
|
||||||
// loginInfo.value.phone = localLoginInfo.phone || ''
|
|
||||||
// loginInfo.value.password = localLoginInfo.password || ''
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|
||||||
// 验证手机号格式
|
// Open Agreement Modal
|
||||||
|
function handleOpenAgreement() {
|
||||||
|
showAgreement.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Initialize login info
|
||||||
|
if (localStorage.getItem('phone')) {
|
||||||
|
loginInfo.value.phone = localStorage.getItem('phone')
|
||||||
|
}
|
||||||
|
|
||||||
|
const formRef = ref(null)
|
||||||
|
const rules = {
|
||||||
|
phone: [
|
||||||
|
{ required: true, message: '请输入手机号', trigger: ['input', 'blur'] },
|
||||||
|
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: ['input', 'blur'] },
|
||||||
|
],
|
||||||
|
verifyCode: [{ required: true, message: '请输入验证码', trigger: ['input', 'blur'] }],
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate phone
|
||||||
function validatePhone(phone) {
|
function validatePhone(phone) {
|
||||||
const phoneReg = /^1[3-9]\d{9}$/
|
const phoneReg = /^1[3-9]\d{9}$/
|
||||||
return phoneReg.test(phone)
|
return phoneReg.test(phone)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送验证码
|
// Send verification code
|
||||||
async function handleSendCode() {
|
async function handleSendCode() {
|
||||||
|
if (countdown.value > 0) return
|
||||||
|
|
||||||
const { phone } = loginInfo.value
|
const { phone } = loginInfo.value
|
||||||
|
|
||||||
if (!phone) {
|
if (!phone) {
|
||||||
@ -115,10 +171,11 @@ async function handleSendCode() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await api.sendVerifyCode({ phone })
|
const res = await api.sendVerifyCode({ phone })
|
||||||
|
loginInfo.value.requestId = res.data.request_id
|
||||||
$message.success('验证码已发送')
|
$message.success('验证码已发送')
|
||||||
|
|
||||||
// 开始倒计时
|
// Start countdown
|
||||||
countdown.value = 60
|
countdown.value = 60
|
||||||
countdownTimer = setInterval(() => {
|
countdownTimer = setInterval(() => {
|
||||||
countdown.value--
|
countdown.value--
|
||||||
@ -128,40 +185,30 @@ async function handleSendCode() {
|
|||||||
}
|
}
|
||||||
}, 1000)
|
}, 1000)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// error is handled by interceptor usually, but we can catch specific ones if needed
|
console.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 登录
|
// Login
|
||||||
async function handleLogin() {
|
async function handleLogin() {
|
||||||
const { phone, verifyCode } = loginInfo.value
|
const { phone, verifyCode, requestId } = loginInfo.value
|
||||||
|
|
||||||
if (!phone) {
|
if (!isAgreed.value) {
|
||||||
$message.warning('请输入手机号')
|
$message.warning('请阅读并同意用户协议')
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validatePhone(phone)) {
|
|
||||||
$message.warning('请输入正确的手机号')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!verifyCode) {
|
|
||||||
$message.warning('请输入验证码')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (verifyCode.length < 4) {
|
|
||||||
$message.warning('请输入完整的验证码')
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
formRef.value?.validate(async (errors) => {
|
||||||
|
if (!errors) {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await api.loginWithVerifyCode({ phone, code: verifyCode })
|
const res = await api.loginWithVerifyCode({
|
||||||
if (res.data?.access_token) {
|
phone_number: phone,
|
||||||
setToken(res.data.access_token)
|
verification_code: verifyCode,
|
||||||
|
device_id: requestId,
|
||||||
|
})
|
||||||
|
if (res.data?.token?.access_token) {
|
||||||
|
setToken(res.data.token.access_token)
|
||||||
localStorage.setItem('phone', phone)
|
localStorage.setItem('phone', phone)
|
||||||
|
|
||||||
if (query.redirect) {
|
if (query.redirect) {
|
||||||
@ -179,9 +226,10 @@ async function handleLogin() {
|
|||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 组件卸载时清除定时器
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
if (countdownTimer) {
|
if (countdownTimer) {
|
||||||
clearInterval(countdownTimer)
|
clearInterval(countdownTimer)
|
||||||
@ -189,3 +237,409 @@ onBeforeUnmount(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* Utility Classes */
|
||||||
|
.flex-col {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.justify-between {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cursor-pointer {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-reset {
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
background: transparent;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Page Styles */
|
||||||
|
.page {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: #f0f2f5; /* Fallback */
|
||||||
|
}
|
||||||
|
|
||||||
|
.section_1 {
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box_1 {
|
||||||
|
box-shadow: 0px 2px 30px 0px rgba(225, 206, 206, 0.22);
|
||||||
|
background-color: rgba(255, 255, 255, 1);
|
||||||
|
width: 900px;
|
||||||
|
height: 500px;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section_2 {
|
||||||
|
background-color: rgba(255, 255, 255, 1);
|
||||||
|
border-radius: 0px 100px 0px 0px;
|
||||||
|
height: 500px;
|
||||||
|
width: 400px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group_1 {
|
||||||
|
/* Matches reference: width 841px, margin-left -270px inside a 400px container */
|
||||||
|
width: 841px;
|
||||||
|
height: 500px;
|
||||||
|
margin-left: -270px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
/* Ensure the image covers this specific area if needed, or default */
|
||||||
|
}
|
||||||
|
|
||||||
|
.section_3 {
|
||||||
|
width: 331px;
|
||||||
|
height: 298px;
|
||||||
|
margin: 101px 85px 0 84px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image_1 {
|
||||||
|
width: 260px;
|
||||||
|
height: 42px;
|
||||||
|
margin-left: 1px;
|
||||||
|
/* Removed object-fit: contain to match reference behavior */
|
||||||
|
}
|
||||||
|
|
||||||
|
.block_1 {
|
||||||
|
background-color: rgba(255, 255, 255, 1);
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 330px;
|
||||||
|
height: 42px;
|
||||||
|
border: 1px solid rgba(229, 229, 229, 1);
|
||||||
|
margin: 50px 0 0 1px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-text_1 {
|
||||||
|
width: 100%;
|
||||||
|
height: 16px;
|
||||||
|
margin: 0 15px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnail_1 {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-group_1 {
|
||||||
|
color: rgba(48, 49, 51, 1);
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: Alibaba-PuHuiTi-R, sans-serif;
|
||||||
|
font-weight: normal;
|
||||||
|
line-height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-group_1::placeholder {
|
||||||
|
color: rgba(189, 189, 189, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.block_2 {
|
||||||
|
width: 330px;
|
||||||
|
height: 42px;
|
||||||
|
margin: 20px 0 0 1px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section_4 {
|
||||||
|
background-color: rgba(255, 255, 255, 1);
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 191px;
|
||||||
|
height: 42px;
|
||||||
|
border: 1px solid rgba(229, 229, 229, 1);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-text_2 {
|
||||||
|
width: 100%;
|
||||||
|
height: 16px;
|
||||||
|
margin: 0 15px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnail_2 {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-group_2 {
|
||||||
|
color: rgba(48, 49, 51, 1);
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: Alibaba-PuHuiTi-R, sans-serif;
|
||||||
|
font-weight: normal;
|
||||||
|
line-height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-group_2::placeholder {
|
||||||
|
color: rgba(189, 189, 189, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-wrapper_1 {
|
||||||
|
background-color: rgba(255, 255, 255, 1);
|
||||||
|
border-radius: 4px;
|
||||||
|
height: 42px;
|
||||||
|
border: 1px solid rgba(229, 229, 229, 1);
|
||||||
|
width: 131px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-wrapper_1:hover:not(.disabled) {
|
||||||
|
border-color: rgba(163, 1, 19, 1);
|
||||||
|
background-color: rgba(163, 1, 19, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-wrapper_1:active:not(.disabled) {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text_1 {
|
||||||
|
color: rgba(48, 49, 51, 1);
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: Alibaba-PuHuiTi-R, sans-serif;
|
||||||
|
font-weight: normal;
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-wrapper_1:hover:not(.disabled) .text_1 {
|
||||||
|
color: rgba(163, 1, 19, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.block_3 {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 36px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-group_3 {
|
||||||
|
color: rgba(136, 136, 136, 1);
|
||||||
|
font-size: 12px;
|
||||||
|
letter-spacing: 0.32px;
|
||||||
|
font-family: Alibaba-PuHuiTi-R, sans-serif;
|
||||||
|
font-weight: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
line-height: 12px;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text_2 {
|
||||||
|
color: rgba(163, 1, 19, 1);
|
||||||
|
font-size: 12px;
|
||||||
|
letter-spacing: 0.32px;
|
||||||
|
font-family: Alibaba-PuHuiTi-M, sans-serif;
|
||||||
|
font-weight: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
line-height: 12px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text_2:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
color: rgba(180, 1, 21, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-wrapper_2 {
|
||||||
|
height: 42px;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
width: 330px;
|
||||||
|
margin: 10px 0 0 1px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-wrapper_2:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(163, 1, 19, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-wrapper_2:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
box-shadow: 0 2px 6px rgba(163, 1, 19, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text_3 {
|
||||||
|
color: rgba(255, 255, 255, 1);
|
||||||
|
font-size: 18px;
|
||||||
|
font-family: Alibaba-PuHuiTi-M, sans-serif;
|
||||||
|
font-weight: normal;
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-wrapper_2:hover .text_3 {
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom Naive UI Overrides */
|
||||||
|
.custom-n-input {
|
||||||
|
background-color: transparent !important;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-n-input :deep(.n-input-wrapper) {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-n-input :deep(.n-input__input-el) {
|
||||||
|
height: 16px !important;
|
||||||
|
line-height: 16px !important;
|
||||||
|
color: rgba(48, 49, 51, 1);
|
||||||
|
font-family: Alibaba-PuHuiTi-R, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-n-input :deep(.n-input__placeholder) {
|
||||||
|
color: rgba(189, 189, 189, 1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-n-input :deep(.n-input__state-border),
|
||||||
|
.custom-n-input :deep(.n-input__border) {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fix Form Item Layout to prevent shifts */
|
||||||
|
:deep(.n-form-item-feedback-wrapper) {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
padding-top: 2px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-form-item) {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure the code input item doesn't break flex layout */
|
||||||
|
.verify-code-item {
|
||||||
|
flex: 1;
|
||||||
|
/* The section_4 inside has fixed width, so we just let it be */
|
||||||
|
max-width: 191px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Input Container Interactions */
|
||||||
|
.block_1,
|
||||||
|
.section_4 {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block_1:hover,
|
||||||
|
.section_4:hover {
|
||||||
|
border-color: rgba(163, 1, 19, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.block_1:focus-within,
|
||||||
|
.section_4:focus-within {
|
||||||
|
border-color: rgba(163, 1, 19, 1);
|
||||||
|
box-shadow: 0 0 0 2px rgba(163, 1, 19, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Error State Styling */
|
||||||
|
:deep(.n-form-item.n-form-item--error) .block_1,
|
||||||
|
:deep(.n-form-item.n-form-item--error) .section_4 {
|
||||||
|
border-color: #d03050;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-form-item.n-form-item--error) .block_1:focus-within,
|
||||||
|
:deep(.n-form-item.n-form-item--error) .section_4:focus-within {
|
||||||
|
border-color: #d03050;
|
||||||
|
box-shadow: 0 0 0 2px rgba(208, 48, 80, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Checkbox Customization */
|
||||||
|
:deep(.agreement-checkbox .n-checkbox-box) {
|
||||||
|
border-radius: 2px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Checked State - Solid Red */
|
||||||
|
:deep(.agreement-checkbox.n-checkbox--checked .n-checkbox-box) {
|
||||||
|
background-color: rgba(163, 1, 19, 1) !important;
|
||||||
|
border: 1px solid rgba(163, 1, 19, 1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide the separate border element when checked to prevent color overlap/double borders */
|
||||||
|
:deep(.agreement-checkbox.n-checkbox--checked .n-checkbox-box .n-checkbox-box__border) {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hover State - Red Border */
|
||||||
|
:deep(.agreement-checkbox:hover .n-checkbox-box .n-checkbox-box__border) {
|
||||||
|
border-color: rgba(163, 1, 19, 1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Focus State - Red Border & Shadow */
|
||||||
|
:deep(.agreement-checkbox.n-checkbox--focus .n-checkbox-box .n-checkbox-box__border) {
|
||||||
|
border-color: rgba(163, 1, 19, 1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.agreement-checkbox.n-checkbox--focus .n-checkbox-box) {
|
||||||
|
box-shadow: 0 0 0 2px rgba(163, 1, 19, 0.2) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure checkmark is white */
|
||||||
|
:deep(.agreement-checkbox .n-checkbox-icon) {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Agreement Modal Styles */
|
||||||
|
.agreement-content {
|
||||||
|
font-family: Alibaba-PuHuiTi-R, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-content h3 {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-content p {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
text-align: justify;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
601
web1/src/views/login/index.vue.backup
Normal file
@ -0,0 +1,601 @@
|
|||||||
|
<template>
|
||||||
|
<div class="page flex-col">
|
||||||
|
<div class="section_1 flex-col" :style="{ backgroundImage: `url(${loginBgFull})` }">
|
||||||
|
<div class="box_1 flex-row">
|
||||||
|
<div class="section_2 flex-col">
|
||||||
|
<div class="group_1 flex-col" :style="{ backgroundImage: `url(${loginBoxLeft})` }"></div>
|
||||||
|
</div>
|
||||||
|
<div class="section_3 flex-col">
|
||||||
|
<img
|
||||||
|
class="image_1"
|
||||||
|
referrerpolicy="no-referrer"
|
||||||
|
:src="loginTitleImg"
|
||||||
|
/>
|
||||||
|
<n-form ref="formRef" :model="loginInfo" :rules="rules" :show-label="false">
|
||||||
|
<n-form-item path="phone">
|
||||||
|
<div class="block_1 flex-row" @click="phoneInputRef?.focus()">
|
||||||
|
<div class="image-text_1 flex-row">
|
||||||
|
<img
|
||||||
|
class="thumbnail_1"
|
||||||
|
referrerpolicy="no-referrer"
|
||||||
|
:src="iconUserImg"
|
||||||
|
/>
|
||||||
|
<n-input
|
||||||
|
ref="phoneInputRef"
|
||||||
|
v-model:value="loginInfo.phone"
|
||||||
|
class="input-reset text-group_1 custom-n-input"
|
||||||
|
placeholder="请输入账号"
|
||||||
|
type="text"
|
||||||
|
:bordered="false"
|
||||||
|
@keypress.enter="handleLogin"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</n-form-item>
|
||||||
|
<div class="block_2 flex-row justify-between">
|
||||||
|
<n-form-item path="verifyCode" class="verify-code-item">
|
||||||
|
<div class="section_4 flex-row" @click="codeInputRef?.focus()">
|
||||||
|
<div class="image-text_2 flex-row">
|
||||||
|
<img
|
||||||
|
class="thumbnail_2"
|
||||||
|
referrerpolicy="no-referrer"
|
||||||
|
:src="iconCodeImg"
|
||||||
|
/>
|
||||||
|
<n-input
|
||||||
|
ref="codeInputRef"
|
||||||
|
v-model:value="loginInfo.verifyCode"
|
||||||
|
class="input-reset text-group_2 custom-n-input"
|
||||||
|
placeholder="请输入验证码"
|
||||||
|
type="text"
|
||||||
|
:bordered="false"
|
||||||
|
@keypress.enter="handleLogin"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</n-form-item>
|
||||||
|
<div
|
||||||
|
class="text-wrapper_1 flex-col cursor-pointer"
|
||||||
|
@click="handleSendCode"
|
||||||
|
:class="{ 'disabled': countdown > 0 }"
|
||||||
|
>
|
||||||
|
<span class="text_1">{{ countdown > 0 ? `${countdown}秒后重新获取` : '获取验证码' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</n-form>
|
||||||
|
<div class="block_3 flex-row">
|
||||||
|
<n-checkbox v-model:checked="isAgreed" class="agreement-checkbox">
|
||||||
|
<span class="text-group_3">阅读并同意</span>
|
||||||
|
<span class="text_2">《用户协议》</span>
|
||||||
|
</n-checkbox>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="text-wrapper_2 flex-col cursor-pointer"
|
||||||
|
@click="handleLogin"
|
||||||
|
:style="{ backgroundImage: `url(${loginBtnBg})` }"
|
||||||
|
>
|
||||||
|
<span class="text_3">立即登录</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onBeforeUnmount } from 'vue'
|
||||||
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
import { setToken } from '@/utils'
|
||||||
|
import api from '@/api'
|
||||||
|
|
||||||
|
// Images
|
||||||
|
import loginTitleImg from '@/assets/img/login_title.png'
|
||||||
|
import iconUserImg from '@/assets/img/icon_user.png'
|
||||||
|
import iconCodeImg from '@/assets/img/icon_code.png'
|
||||||
|
import iconCheckboxImg from '@/assets/img/icon_checkbox.png'
|
||||||
|
import loginBgFull from '@/assets/img/login_bg_full.png'
|
||||||
|
import loginBoxLeft from '@/assets/img/login_box_left.png'
|
||||||
|
import loginBtnBg from '@/assets/img/login_btn_bg.png'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const { query } = useRoute()
|
||||||
|
|
||||||
|
const loginInfo = ref({
|
||||||
|
phone: '',
|
||||||
|
verifyCode: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const isAgreed = ref(false)
|
||||||
|
const countdown = ref(0)
|
||||||
|
let countdownTimer = null
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
// Initialize login info
|
||||||
|
if(localStorage.getItem('phone')){
|
||||||
|
loginInfo.value.phone = localStorage.getItem('phone')
|
||||||
|
}
|
||||||
|
|
||||||
|
const formRef = ref(null)
|
||||||
|
const phoneInputRef = ref(null)
|
||||||
|
const codeInputRef = ref(null)
|
||||||
|
|
||||||
|
const rules = {
|
||||||
|
phone: [
|
||||||
|
{ required: true, message: '请输入手机号', trigger: ['input', 'blur'] },
|
||||||
|
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: ['input', 'blur'] }
|
||||||
|
],
|
||||||
|
verifyCode: [
|
||||||
|
{ required: true, message: '请输入验证码', trigger: ['input', 'blur'] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate phone
|
||||||
|
function validatePhone(phone) {
|
||||||
|
const phoneReg = /^1[3-9]\d{9}$/
|
||||||
|
return phoneReg.test(phone)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send verification code
|
||||||
|
async function handleSendCode() {
|
||||||
|
if (countdown.value > 0) return
|
||||||
|
|
||||||
|
const { phone } = loginInfo.value
|
||||||
|
|
||||||
|
if (!phone) {
|
||||||
|
$message.warning('请输入手机号')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validatePhone(phone)) {
|
||||||
|
$message.warning('请输入正确的手机号')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await api.sendVerifyCode({ phone })
|
||||||
|
$message.success('验证码已发送')
|
||||||
|
|
||||||
|
// Start countdown
|
||||||
|
countdown.value = 60
|
||||||
|
countdownTimer = setInterval(() => {
|
||||||
|
countdown.value--
|
||||||
|
if (countdown.value <= 0) {
|
||||||
|
clearInterval(countdownTimer)
|
||||||
|
countdownTimer = null
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Login
|
||||||
|
async function handleLogin() {
|
||||||
|
const { phone, verifyCode } = loginInfo.value
|
||||||
|
|
||||||
|
if (!isAgreed.value) {
|
||||||
|
$message.warning('请阅读并同意用户协议')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
formRef.value?.validate(async (errors) => {
|
||||||
|
if (!errors) {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await api.loginWithVerifyCode({ phone, code: verifyCode })
|
||||||
|
if (res.data?.access_token) {
|
||||||
|
setToken(res.data.access_token)
|
||||||
|
localStorage.setItem('phone', phone)
|
||||||
|
|
||||||
|
if (query.redirect) {
|
||||||
|
const path = query.redirect
|
||||||
|
Reflect.deleteProperty(query, 'redirect')
|
||||||
|
router.push({ path, query })
|
||||||
|
} else {
|
||||||
|
router.push('/home')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$message.error('登录失败:未获取到token')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (countdownTimer) {
|
||||||
|
clearInterval(countdownTimer)
|
||||||
|
countdownTimer = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* Utility Classes */
|
||||||
|
.flex-col {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.justify-between {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cursor-pointer {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-reset {
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
background: transparent;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Page Styles */
|
||||||
|
.page {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: #f0f2f5; /* Fallback */
|
||||||
|
}
|
||||||
|
|
||||||
|
.section_1 {
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box_1 {
|
||||||
|
box-shadow: 0px 2px 30px 0px rgba(225, 206, 206, 0.22);
|
||||||
|
background-color: rgba(255, 255, 255, 1);
|
||||||
|
width: 900px;
|
||||||
|
height: 500px;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section_2 {
|
||||||
|
background-color: rgba(255, 255, 255, 1);
|
||||||
|
border-radius: 0px 100px 0px 0px;
|
||||||
|
height: 500px;
|
||||||
|
width: 400px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group_1 {
|
||||||
|
/* Matches reference: width 841px, margin-left -270px inside a 400px container */
|
||||||
|
width: 841px;
|
||||||
|
height: 500px;
|
||||||
|
margin-left: -270px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
/* Ensure the image covers this specific area if needed, or default */
|
||||||
|
}
|
||||||
|
|
||||||
|
.section_3 {
|
||||||
|
width: 331px;
|
||||||
|
height: 298px;
|
||||||
|
margin: 101px 85px 0 84px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image_1 {
|
||||||
|
width: 260px;
|
||||||
|
height: 42px;
|
||||||
|
margin-left: 1px;
|
||||||
|
/* Removed object-fit: contain to match reference behavior */
|
||||||
|
}
|
||||||
|
|
||||||
|
.block_1 {
|
||||||
|
background-color: rgba(255, 255, 255, 1);
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 330px;
|
||||||
|
height: 42px;
|
||||||
|
border: 1px solid rgba(229, 229, 229, 1);
|
||||||
|
margin: 50px 0 0 1px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-text_1 {
|
||||||
|
width: 100%;
|
||||||
|
height: 16px;
|
||||||
|
margin: 0 15px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnail_1 {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-group_1 {
|
||||||
|
color: rgba(48, 49, 51, 1);
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: Alibaba-PuHuiTi-R, sans-serif;
|
||||||
|
font-weight: normal;
|
||||||
|
line-height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-group_1::placeholder {
|
||||||
|
color: rgba(189, 189, 189, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.block_2 {
|
||||||
|
width: 330px;
|
||||||
|
height: 42px;
|
||||||
|
margin: 20px 0 0 1px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section_4 {
|
||||||
|
background-color: rgba(255, 255, 255, 1);
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 191px;
|
||||||
|
height: 42px;
|
||||||
|
border: 1px solid rgba(229, 229, 229, 1);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-text_2 {
|
||||||
|
width: 100%;
|
||||||
|
height: 16px;
|
||||||
|
margin: 0 15px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnail_2 {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-group_2 {
|
||||||
|
color: rgba(48, 49, 51, 1);
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: Alibaba-PuHuiTi-R, sans-serif;
|
||||||
|
font-weight: normal;
|
||||||
|
line-height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-group_2::placeholder {
|
||||||
|
color: rgba(189, 189, 189, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-wrapper_1 {
|
||||||
|
background-color: rgba(255, 255, 255, 1);
|
||||||
|
border-radius: 4px;
|
||||||
|
height: 42px;
|
||||||
|
border: 1px solid rgba(229, 229, 229, 1);
|
||||||
|
width: 131px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-wrapper_1:hover:not(.disabled) {
|
||||||
|
border-color: rgba(163, 1, 19, 1);
|
||||||
|
background-color: rgba(163, 1, 19, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-wrapper_1:active:not(.disabled) {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text_1 {
|
||||||
|
color: rgba(48, 49, 51, 1);
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: Alibaba-PuHuiTi-R, sans-serif;
|
||||||
|
font-weight: normal;
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-wrapper_1:hover:not(.disabled) .text_1 {
|
||||||
|
color: rgba(163, 1, 19, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.block_3 {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 36px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-group_3 {
|
||||||
|
color: rgba(136, 136, 136, 1);
|
||||||
|
font-size: 12px;
|
||||||
|
letter-spacing: 0.32px;
|
||||||
|
font-family: Alibaba-PuHuiTi-R, sans-serif;
|
||||||
|
font-weight: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
line-height: 12px;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text_2 {
|
||||||
|
color: rgba(163, 1, 19, 1);
|
||||||
|
font-size: 12px;
|
||||||
|
letter-spacing: 0.32px;
|
||||||
|
font-family: Alibaba-PuHuiTi-M, sans-serif;
|
||||||
|
font-weight: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
line-height: 12px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text_2:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
color: rgba(180, 1, 21, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-wrapper_2 {
|
||||||
|
height: 42px;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
width: 330px;
|
||||||
|
margin: 10px 0 0 1px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-wrapper_2:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(163, 1, 19, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-wrapper_2:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
box-shadow: 0 2px 6px rgba(163, 1, 19, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text_3 {
|
||||||
|
color: rgba(255, 255, 255, 1);
|
||||||
|
font-size: 18px;
|
||||||
|
font-family: Alibaba-PuHuiTi-M, sans-serif;
|
||||||
|
font-weight: normal;
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-wrapper_2:hover .text_3 {
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom Naive UI Overrides */
|
||||||
|
.custom-n-input {
|
||||||
|
background-color: transparent !important;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-n-input :deep(.n-input-wrapper) {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-n-input :deep(.n-input__input-el) {
|
||||||
|
height: 16px !important;
|
||||||
|
line-height: 16px !important;
|
||||||
|
color: rgba(48, 49, 51, 1);
|
||||||
|
font-family: Alibaba-PuHuiTi-R, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-n-input :deep(.n-input__placeholder) {
|
||||||
|
color: rgba(189, 189, 189, 1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-n-input :deep(.n-input__state-border),
|
||||||
|
.custom-n-input :deep(.n-input__border) {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fix Form Item Layout to prevent shifts */
|
||||||
|
:deep(.n-form-item-feedback-wrapper) {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
padding-top: 2px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-form-item) {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure the code input item doesn't break flex layout */
|
||||||
|
.verify-code-item {
|
||||||
|
flex: 1;
|
||||||
|
/* The section_4 inside has fixed width, so we just let it be */
|
||||||
|
max-width: 191px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Input Container Interactions */
|
||||||
|
.block_1,
|
||||||
|
.section_4 {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block_1:hover,
|
||||||
|
.section_4:hover {
|
||||||
|
border-color: rgba(163, 1, 19, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.block_1:focus-within,
|
||||||
|
.section_4:focus-within {
|
||||||
|
border-color: rgba(163, 1, 19, 1);
|
||||||
|
box-shadow: 0 0 0 2px rgba(163, 1, 19, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Error State Styling */
|
||||||
|
:deep(.n-form-item.n-form-item--error) .block_1,
|
||||||
|
:deep(.n-form-item.n-form-item--error) .section_4 {
|
||||||
|
border-color: #d03050;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-form-item.n-form-item--error) .block_1:focus-within,
|
||||||
|
:deep(.n-form-item.n-form-item--error) .section_4:focus-within {
|
||||||
|
border-color: #d03050;
|
||||||
|
box-shadow: 0 0 0 2px rgba(208, 48, 80, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Checkbox Customization */
|
||||||
|
:deep(.agreement-checkbox .n-checkbox-box) {
|
||||||
|
border-radius: 2px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Checked State - Solid Red */
|
||||||
|
:deep(.agreement-checkbox.n-checkbox--checked .n-checkbox-box) {
|
||||||
|
background-color: rgba(163, 1, 19, 1) !important;
|
||||||
|
border: 1px solid rgba(163, 1, 19, 1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide the separate border element when checked to prevent color overlap/double borders */
|
||||||
|
:deep(.agreement-checkbox.n-checkbox--checked .n-checkbox-box .n-checkbox-box__border) {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hover State - Red Border */
|
||||||
|
:deep(.agreement-checkbox:hover .n-checkbox-box .n-checkbox-box__border) {
|
||||||
|
border-color: rgba(163, 1, 19, 1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Focus State - Red Border & Shadow */
|
||||||
|
:deep(.agreement-checkbox.n-checkbox--focus .n-checkbox-box .n-checkbox-box__border) {
|
||||||
|
border-color: rgba(163, 1, 19, 1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.agreement-checkbox.n-checkbox--focus .n-checkbox-box) {
|
||||||
|
box-shadow: 0 0 0 2px rgba(163, 1, 19, 0.2) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure checkmark is white */
|
||||||
|
:deep(.agreement-checkbox .n-checkbox-icon) {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,6 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="pages">
|
<div class="pages">
|
||||||
<div class="left">
|
<AppHeader class="page-header" />
|
||||||
|
<!-- 左侧边栏 - 已隐藏 -->
|
||||||
|
<!-- <div class="left">
|
||||||
<img style="width: 198px; height: 32px; margin: 20px;" src="@/assets/images/logo.png" alt="" @click="navToLogin">
|
<img style="width: 198px; height: 32px; margin: 20px;" src="@/assets/images/logo.png" alt="" @click="navToLogin">
|
||||||
<div style="margin-left: 20px;">
|
<div style="margin-left: 20px;">
|
||||||
<div v-for="(item, index) in historyList" @click="selectTimeBox(item)" :class="{ timeBox: item.id != isSelected, timeBox2: item.id == isSelected}">
|
<div v-for="(item, index) in historyList" @click="selectTimeBox(item)" :class="{ timeBox: item.id != isSelected, timeBox2: item.id == isSelected}">
|
||||||
@ -8,8 +10,7 @@
|
|||||||
<img v-if="item.id == isSelected" class="delete-icon" src="@/assets/images/delete.png" alt="" @click.stop="deleteValuations(item)">
|
<img v-if="item.id == isSelected" class="delete-icon" src="@/assets/images/delete.png" alt="" @click.stop="deleteValuations(item)">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div> -->
|
||||||
</div>
|
|
||||||
<div v-if="status=='create'" class="right">
|
<div v-if="status=='create'" class="right">
|
||||||
<StepProgressBar style="width: 800px; margin: auto; margin-top: 40px;" :steps="steps" :currentStep="currentStep" />
|
<StepProgressBar style="width: 800px; margin: auto; margin-top: 40px;" :steps="steps" :currentStep="currentStep" />
|
||||||
<div class="line"></div>
|
<div class="line"></div>
|
||||||
@ -22,7 +23,7 @@
|
|||||||
<div v-if="currentStep==4" class="title-form">非遗资产衍生商品信息</div>
|
<div v-if="currentStep==4" class="title-form">非遗资产衍生商品信息</div>
|
||||||
</div>
|
</div>
|
||||||
<NForm
|
<NForm
|
||||||
style="margin:auto; margin-top:30px; width:900px; height: calc(100vh - 500px); overflow-y: auto;"
|
class="form-container"
|
||||||
ref="modalFormRef"
|
ref="modalFormRef"
|
||||||
label-placement="top"
|
label-placement="top"
|
||||||
label-align="top"
|
label-align="top"
|
||||||
@ -32,7 +33,7 @@
|
|||||||
require-mark-placement = "left"
|
require-mark-placement = "left"
|
||||||
>
|
>
|
||||||
<n-grid v-if="currentStep == 0" :cols="24" :x-gap="24">
|
<n-grid v-if="currentStep == 0" :cols="24" :x-gap="24">
|
||||||
<n-form-item-gi :span="10" class="form-item" label="资产名称" path="asset_name">
|
<n-form-item-gi :span="12" label="资产名称" path="asset_name">
|
||||||
<template #label>
|
<template #label>
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<span>资产名称</span>
|
<span>资产名称</span>
|
||||||
@ -46,7 +47,8 @@
|
|||||||
</template>
|
</template>
|
||||||
<NInput v-model:value="modalForm.asset_name" placeholder="请输入资产名称" />
|
<NInput v-model:value="modalForm.asset_name" placeholder="请输入资产名称" />
|
||||||
</n-form-item-gi>
|
</n-form-item-gi>
|
||||||
<n-form-item-gi :span="12" class="form-item" label="所属机构/权利人" path="institution">
|
<n-gi :span="12"></n-gi>
|
||||||
|
<n-form-item-gi :span="12" label="所属机构/权利人" path="institution">
|
||||||
<template #label>
|
<template #label>
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<span>所属机构/权利人</span>
|
<span>所属机构/权利人</span>
|
||||||
@ -60,7 +62,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<NInput v-model:value="modalForm.institution" placeholder="请输入所属机构/权利人" />
|
<NInput v-model:value="modalForm.institution" placeholder="请输入所属机构/权利人" />
|
||||||
</n-form-item-gi>
|
</n-form-item-gi>
|
||||||
<n-form-item-gi :span="10" class="form-item" label="统一社会信用代码/身份证号" path="credit_code">
|
<n-form-item-gi :span="12" label="统一社会信用代码/身份证号" path="credit_code">
|
||||||
<template #label>
|
<template #label>
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<span>统一社会信用代码/身份证号</span>
|
<span>统一社会信用代码/身份证号</span>
|
||||||
@ -74,7 +76,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<NInput v-model:value="modalForm.credit_code" placeholder="请输入统一社会信用代码/身份证号" />
|
<NInput v-model:value="modalForm.credit_code" placeholder="请输入统一社会信用代码/身份证号" />
|
||||||
</n-form-item-gi>
|
</n-form-item-gi>
|
||||||
<n-form-item-gi :span="12" class="form-item" label="所属行业" path="industry">
|
<n-form-item-gi :span="12" label="所属行业" path="industry">
|
||||||
<template #label>
|
<template #label>
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<span>所属行业</span>
|
<span>所属行业</span>
|
||||||
@ -93,7 +95,8 @@
|
|||||||
filterable
|
filterable
|
||||||
/>
|
/>
|
||||||
</n-form-item-gi>
|
</n-form-item-gi>
|
||||||
<n-form-item-gi :span="24" class="form-item" label="业务/传承介绍" path="business_heritage_intro">
|
<n-gi :span="12"></n-gi>
|
||||||
|
<n-form-item-gi :span="24" label="业务/传承介绍" path="business_heritage_intro">
|
||||||
<template #label>
|
<template #label>
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<span>业务/传承介绍</span>
|
<span>业务/传承介绍</span>
|
||||||
@ -593,6 +596,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { FormInst, FormItemRule, FormRules } from 'naive-ui'
|
import type { FormInst, FormItemRule, FormRules } from 'naive-ui'
|
||||||
import StepProgressBar from './components/StepProgressBar.vue';
|
import StepProgressBar from './components/StepProgressBar.vue';
|
||||||
|
import AppHeader from '@/components/AppHeader.vue'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import { ref, reactive, onMounted } from 'vue'
|
import { ref, reactive, onMounted } from 'vue'
|
||||||
import { useMessage, useDialog } from 'naive-ui'
|
import { useMessage, useDialog } from 'naive-ui'
|
||||||
@ -1085,10 +1089,11 @@ const previousStep = () => {
|
|||||||
currentStep.value --
|
currentStep.value --
|
||||||
}
|
}
|
||||||
|
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import { unref } from 'vue'
|
import { unref } from 'vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
const navToLogin = () => {
|
const navToLogin = () => {
|
||||||
userStore.logout()
|
userStore.logout()
|
||||||
@ -1100,12 +1105,11 @@ const nextStep = () => {
|
|||||||
}
|
}
|
||||||
modalFormRef.value?.validate((errors) => {
|
modalFormRef.value?.validate((errors) => {
|
||||||
if (!errors) {
|
if (!errors) {
|
||||||
if(currentStep.value != 4){
|
if(currentStep.value < 4){
|
||||||
currentStep.value ++
|
currentStep.value ++
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if(currentStep.value == 4){
|
openSubmitConfirm()
|
||||||
submit()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
message.error('请完善表单')
|
message.error('请完善表单')
|
||||||
@ -1190,10 +1194,12 @@ const submit = () => {
|
|||||||
const getHistoryList = () => {
|
const getHistoryList = () => {
|
||||||
const params = {
|
const params = {
|
||||||
page: 1,
|
page: 1,
|
||||||
size: 99
|
page_size: 99
|
||||||
};
|
};
|
||||||
api.getHistoryList(params).then(res=>{
|
return api.getHistoryList(params).then(res=>{
|
||||||
historyList.value = res.data
|
const list = res?.data?.items ?? res?.data?.results ?? res?.results ?? []
|
||||||
|
historyList.value = Array.isArray(list) ? list : []
|
||||||
|
return res
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const selectedObj = ref({
|
const selectedObj = ref({
|
||||||
@ -1217,6 +1223,15 @@ const retry = () => {
|
|||||||
status.value = 'create'
|
status.value = 'create'
|
||||||
}
|
}
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
|
const openSubmitConfirm = () => {
|
||||||
|
dialog.warning({
|
||||||
|
title: '提示',
|
||||||
|
content: '提交后将消耗评估次数,确认提交?',
|
||||||
|
positiveText: '确认',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: () => submit(),
|
||||||
|
})
|
||||||
|
}
|
||||||
const deleteValuations = (item) => {
|
const deleteValuations = (item) => {
|
||||||
dialog.warning({
|
dialog.warning({
|
||||||
title: '确认删除',
|
title: '确认删除',
|
||||||
@ -1317,6 +1332,14 @@ onMounted(async () => {
|
|||||||
page_size: 99
|
page_size: 99
|
||||||
};
|
};
|
||||||
await getHistoryList()
|
await getHistoryList()
|
||||||
|
|
||||||
|
if (route.query.id) {
|
||||||
|
const targetId = Number(route.query.id)
|
||||||
|
const targetItem = historyList.value.find(item => item.id === targetId)
|
||||||
|
if (targetItem) {
|
||||||
|
selectTimeBox(targetItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
// 使用await提高可读性
|
// 使用await提高可读性
|
||||||
const res = await api.getIndustryList(params);
|
const res = await api.getIndustryList(params);
|
||||||
|
|
||||||
@ -1347,20 +1370,34 @@ onMounted(async () => {
|
|||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
overflow-y: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
background: #F8F8F8;
|
background: #F8F8F8;
|
||||||
.left {
|
/* 左侧边栏样式 - 已隐藏 */
|
||||||
|
/* .left {
|
||||||
width: 270px;
|
width: 270px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
}
|
} */
|
||||||
.right {
|
.right {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
margin: 40px;
|
margin: 20px auto;
|
||||||
height: 100%;
|
max-width: 1200px;
|
||||||
|
width: 95%;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding-bottom: 60px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.page-header{
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
.delete-icon{
|
.delete-icon{
|
||||||
float: right;
|
float: right;
|
||||||
transform: translate(0,2px);
|
transform: translate(0,2px);
|
||||||
@ -1403,6 +1440,12 @@ onMounted(async () => {
|
|||||||
background: #EEEEEE;
|
background: #EEEEEE;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
.form-container{
|
||||||
|
margin: auto;
|
||||||
|
margin-top: 30px;
|
||||||
|
width: 900px;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
.form-item{
|
.form-item{
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 330px;
|
width: 330px;
|
||||||
|
|||||||
@ -1,145 +1,176 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="content-section">
|
<div class="content-section">
|
||||||
|
<div class="section-header">
|
||||||
|
<div class="title-bar"></div>
|
||||||
|
<div class="title-text">对公转账</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="transfer-container">
|
<div class="transfer-container">
|
||||||
<!-- 步骤条 -->
|
<!-- 步骤条 -->
|
||||||
<div class="steps">
|
<div class="steps">
|
||||||
<div class="step" :class="{ active: currentStep >= 1 }">
|
<div class="step" :class="{ active: currentStep >= 1 }">
|
||||||
<div class="step-number">1</div>
|
<div class="step-icon">
|
||||||
|
<span v-if="currentStep === 1">1</span>
|
||||||
|
<svg v-else width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M20 6L9 17L4 12" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
<div class="step-label">对公汇款</div>
|
<div class="step-label">对公汇款</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="step-line" :class="{ active: currentStep >= 2 }"></div>
|
<div class="step-line">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M9 18L15 12L9 6" stroke="#C0C4CC" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
<div class="step" :class="{ active: currentStep >= 2 }">
|
<div class="step" :class="{ active: currentStep >= 2 }">
|
||||||
<div class="step-number">2</div>
|
<div class="step-icon">
|
||||||
|
<span v-if="currentStep <= 2">2</span>
|
||||||
|
<svg v-else width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M20 6L9 17L4 12" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
<div class="step-label">上传凭证</div>
|
<div class="step-label">上传凭证</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="step-line" :class="{ active: currentStep >= 3 }"></div>
|
<div class="step-line">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M9 18L15 12L9 6" stroke="#C0C4CC" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
<div class="step" :class="{ active: currentStep >= 3 }">
|
<div class="step" :class="{ active: currentStep >= 3 }">
|
||||||
<div class="step-number">3</div>
|
<div class="step-icon">3</div>
|
||||||
<div class="step-label">等待到账</div>
|
<div class="step-label">等待到账</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 第一步:转账信息 -->
|
<!-- 第一步:转账信息 -->
|
||||||
<div v-if="currentStep === 1" class="transfer-info">
|
<div v-if="currentStep === 1" class="step-content">
|
||||||
<div class="info-title">第一步</div>
|
<div class="info-card">
|
||||||
<div class="info-content">
|
<div class="info-title">评估收费标准:<span class="highlight">6000元/次</span></div>
|
||||||
<div class="info-row">
|
<div class="info-subtitle">请汇款至以下账户:</div>
|
||||||
<span class="label">评估收费标准:</span>
|
|
||||||
<span class="value highlight">6000元/次</span>
|
<div class="info-grid">
|
||||||
</div>
|
<div class="info-item">
|
||||||
<div class="info-row">
|
|
||||||
<span class="label">请汇款以下账户:</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-row">
|
|
||||||
<span class="label">公司名称:</span>
|
<span class="label">公司名称:</span>
|
||||||
<span class="value">成都文化产权交易所有限公司</span>
|
<span class="value">成都文化产权交易所有限公司</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-row">
|
<div class="info-item">
|
||||||
<span class="label">税号:</span>
|
<span class="label">税号:</span>
|
||||||
<span class="value">91510104586429590T</span>
|
<span class="value">91510104586429590T</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-row">
|
<div class="info-item">
|
||||||
<span class="label">注册地址:</span>
|
<span class="label">注册地址:</span>
|
||||||
<span class="value">成都市锦江区工业园区三色路38号</span>
|
<span class="value">成都市锦江区工业园区三色路38号</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-row">
|
<div class="info-item">
|
||||||
<span class="label">开户行:</span>
|
<span class="label">开户行:</span>
|
||||||
<span class="value">中国民生银行股份有限公司成都科华支行</span>
|
<span class="value">中国民生银行股份有限公司成都科华支行</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-row">
|
<div class="info-item">
|
||||||
<span class="label">开户账号:</span>
|
<span class="label">开户账号:</span>
|
||||||
<span class="value">6343 8264 7</span>
|
<span class="value">6343 8264 7</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-row">
|
<div class="info-item">
|
||||||
<span class="label">电话:</span>
|
<span class="label">电话:</span>
|
||||||
<span class="value">(028) 85915289</span>
|
<span class="value">(028) 85915289</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<n-button class="next-btn" @click="handleNextStep">
|
</div>
|
||||||
下一步
|
|
||||||
</n-button>
|
<div class="btn-container">
|
||||||
|
<button class="primary-btn" @click="handleNextStep">下一步</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 第二步:上传凭证 -->
|
<!-- 第二步:上传凭证 -->
|
||||||
<div v-if="currentStep === 2" class="upload-section">
|
<!-- 第二步:上传凭证 -->
|
||||||
<div class="info-title">第二步:</div>
|
<div v-if="currentStep === 2" class="step-content">
|
||||||
|
<div class="form-container">
|
||||||
|
<n-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="formModel"
|
||||||
|
label-placement="top"
|
||||||
|
require-mark-placement="left"
|
||||||
|
class="custom-form"
|
||||||
|
>
|
||||||
|
<n-form-item label="上传支付凭证" required>
|
||||||
|
<n-upload
|
||||||
|
v-model:file-list="fileList"
|
||||||
|
list-type="image-card"
|
||||||
|
:max="1"
|
||||||
|
accept="image/png,image/jpeg,image/jpg"
|
||||||
|
:custom-request="customRequest"
|
||||||
|
@change="handleUploadChange"
|
||||||
|
>
|
||||||
|
<div class="upload-trigger">
|
||||||
|
<div class="upload-icon">+</div>
|
||||||
|
<div class="upload-text">添加图片</div>
|
||||||
|
</div>
|
||||||
|
</n-upload>
|
||||||
|
<template #feedback>
|
||||||
|
<div class="form-tip">支持jpg、png、jpeg格式,大小不超过5M</div>
|
||||||
|
</template>
|
||||||
|
</n-form-item>
|
||||||
|
|
||||||
<div class="upload-form">
|
<n-form-item label="开票抬头" required>
|
||||||
<!-- 上传支付凭证 -->
|
<div class="select-wrapper">
|
||||||
<div class="form-item">
|
|
||||||
<div class="form-label">上传支付凭证:</div>
|
|
||||||
<div class="upload-area" @click="triggerFileInput">
|
|
||||||
<input
|
|
||||||
ref="fileInput"
|
|
||||||
type="file"
|
|
||||||
accept="image/*"
|
|
||||||
style="display: none"
|
|
||||||
@change="handleFileChange"
|
|
||||||
/>
|
|
||||||
<div v-if="!uploadedFile" class="upload-placeholder">
|
|
||||||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M12 5V19M5 12H19" stroke="#909399" stroke-width="2" stroke-linecap="round"/>
|
|
||||||
</svg>
|
|
||||||
<div class="upload-hint">仅支持图片格式</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="upload-preview">
|
|
||||||
<img :src="uploadedFileUrl" alt="支付凭证" />
|
|
||||||
<div class="upload-remove" @click.stop="removeFile">
|
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M18 6L6 18M6 6L18 18" stroke="white" stroke-width="2" stroke-linecap="round"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 开票抬头 -->
|
|
||||||
<div class="form-item">
|
|
||||||
<div class="form-label">开票抬头:</div>
|
|
||||||
<n-select
|
<n-select
|
||||||
v-model:value="selectedInvoiceHeader"
|
v-model:value="formModel.invoiceHeader"
|
||||||
:options="invoiceHeaderOptions"
|
:options="invoiceHeaderOptions"
|
||||||
placeholder="请选择开票抬头"
|
placeholder="请选择开票抬头"
|
||||||
|
class="flex-1"
|
||||||
/>
|
/>
|
||||||
|
<div class="add-header-link" @click="handleAddHeader">+ 添加抬头</div>
|
||||||
</div>
|
</div>
|
||||||
|
</n-form-item>
|
||||||
|
|
||||||
<!-- 开票类型 -->
|
<n-form-item label="开票类型" required>
|
||||||
<div class="form-item">
|
|
||||||
<div class="form-label">开票类型:</div>
|
|
||||||
<n-select
|
<n-select
|
||||||
v-model:value="selectedInvoiceType"
|
v-model:value="formModel.invoiceType"
|
||||||
:options="invoiceTypeOptions"
|
:options="invoiceTypeOptions"
|
||||||
placeholder="请选择开票类型"
|
placeholder="请选择开票类型"
|
||||||
/>
|
/>
|
||||||
</div>
|
</n-form-item>
|
||||||
|
|
||||||
<!-- 上传按钮 -->
|
<div class="btn-container">
|
||||||
<n-button
|
<n-button
|
||||||
class="upload-btn"
|
type="primary"
|
||||||
:disabled="!uploadedFile || !selectedInvoiceHeader || !selectedInvoiceType"
|
class="submit-btn"
|
||||||
|
:disabled="!isFormValid"
|
||||||
@click="handleUploadSubmit"
|
@click="handleUploadSubmit"
|
||||||
|
color="#A30113"
|
||||||
|
style="width: 200px; height: 40px;"
|
||||||
>
|
>
|
||||||
上传
|
确认上传
|
||||||
</n-button>
|
</n-button>
|
||||||
</div>
|
</div>
|
||||||
|
</n-form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 第三步:等待到账 -->
|
<!-- 第三步:等待到账 -->
|
||||||
<div v-if="currentStep === 3" class="success-section">
|
<div v-if="currentStep === 3" class="step-content success-content">
|
||||||
<div class="success-message">
|
<div class="success-icon">
|
||||||
<div class="success-title">上传成功!预计N个小时内到账</div>
|
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<div class="success-subtitle">请耐心等待</div>
|
<circle cx="12" cy="12" r="12" fill="#67C23A"/>
|
||||||
|
<path d="M17 8L10 15L7 12" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<n-button class="return-btn" @click="handleReturnHome">
|
<div class="success-title">上传成功</div>
|
||||||
返回首页
|
<div class="success-desc">预计1-3个工作日内到账,请耐心等待</div>
|
||||||
</n-button>
|
|
||||||
|
<div class="btn-container">
|
||||||
|
<button class="primary-btn" @click="handleReturnHome">返回首页</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { ref, reactive, computed } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { useMessage } from 'naive-ui'
|
||||||
|
|
||||||
const emit = defineEmits(['return-home'])
|
const emit = defineEmits(['return-home'])
|
||||||
|
|
||||||
@ -152,14 +183,13 @@ const uploadedFile = ref(null)
|
|||||||
const uploadedFileUrl = ref('')
|
const uploadedFileUrl = ref('')
|
||||||
|
|
||||||
// 表单选择相关
|
// 表单选择相关
|
||||||
const selectedInvoiceHeader = ref(null)
|
|
||||||
const selectedInvoiceType = ref(null)
|
const router = useRouter()
|
||||||
|
|
||||||
// 开票抬头选项
|
// 开票抬头选项
|
||||||
const invoiceHeaderOptions = ref([
|
const invoiceHeaderOptions = ref([
|
||||||
{ label: '抬头1', value: 'header1' },
|
{ label: '上海某某科技有限公司', value: 'header1' },
|
||||||
{ label: '抬头2', value: 'header2' },
|
{ label: '北京某某文化有限公司', value: 'header2' }
|
||||||
{ label: '抬头3', value: 'header3' }
|
|
||||||
])
|
])
|
||||||
|
|
||||||
// 开票类型选项
|
// 开票类型选项
|
||||||
@ -175,45 +205,51 @@ function handleNextStep() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 触发文件选择
|
|
||||||
function triggerFileInput() {
|
|
||||||
fileInput.value?.click()
|
// 添加抬头
|
||||||
|
function handleAddHeader() {
|
||||||
|
router.push({ path: '/user-center/invoice', query: { action: 'create' } })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理文件选择
|
// 表单数据
|
||||||
function handleFileChange(event) {
|
const formModel = reactive({
|
||||||
const file = event.target.files?.[0]
|
invoiceHeader: null,
|
||||||
if (file) {
|
invoiceType: null
|
||||||
// 验证文件类型
|
})
|
||||||
if (!file.type.startsWith('image/')) {
|
const fileList = ref([])
|
||||||
$message.warning('请上传图片格式的文件')
|
const message = useMessage()
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadedFile.value = file
|
// 计算表单是否有效
|
||||||
uploadedFileUrl.value = URL.createObjectURL(file)
|
const isFormValid = computed(() => {
|
||||||
}
|
return fileList.value.length > 0 && formModel.invoiceHeader && formModel.invoiceType
|
||||||
|
})
|
||||||
|
|
||||||
|
// 自定义上传请求(模拟)
|
||||||
|
const customRequest = ({ file, onFinish }) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
onFinish()
|
||||||
|
}, 500)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 移除文件
|
// 处理上传变化
|
||||||
function removeFile() {
|
function handleUploadChange({ fileList: newFileList }) {
|
||||||
|
fileList.value = newFileList
|
||||||
|
if (newFileList.length > 0) {
|
||||||
|
uploadedFile.value = newFileList[0].file
|
||||||
|
} else {
|
||||||
uploadedFile.value = null
|
uploadedFile.value = null
|
||||||
uploadedFileUrl.value = ''
|
|
||||||
if (fileInput.value) {
|
|
||||||
fileInput.value.value = ''
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提交上传
|
// 提交上传
|
||||||
function handleUploadSubmit() {
|
function handleUploadSubmit() {
|
||||||
if (!uploadedFile.value || !selectedInvoiceHeader.value || !selectedInvoiceType.value) {
|
if (!isFormValid.value) {
|
||||||
$message.warning('请完成所有必填项')
|
message.warning('请完成所有必填项')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 这里可以调用API上传文件
|
message.success('上传成功')
|
||||||
// 暂时直接进入下一步
|
|
||||||
$message.success('上传成功')
|
|
||||||
currentStep.value = 3
|
currentStep.value = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,8 +274,26 @@ defineExpose({
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
min-height: calc(100vh - 40px);
|
min-height: calc(100vh - 40px);
|
||||||
position: relative;
|
}
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-bar {
|
||||||
|
width: 4px;
|
||||||
|
height: 16px;
|
||||||
|
background: #A30113;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-text {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
}
|
}
|
||||||
|
|
||||||
.transfer-container {
|
.transfer-container {
|
||||||
@ -247,286 +301,201 @@ defineExpose({
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 步骤条样式 */
|
||||||
.steps {
|
.steps {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-bottom: 40px;
|
margin-bottom: 60px;
|
||||||
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.step {
|
.step {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.step-number {
|
.step-icon {
|
||||||
width: 40px;
|
width: 24px;
|
||||||
height: 40px;
|
height: 24px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: #EEEEEE;
|
background: #C0C4CC;
|
||||||
color: #909399;
|
color: white;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-size: 16px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.step.active .step-number {
|
.step.active .step-icon {
|
||||||
background: linear-gradient(93deg, #880C22 0%, #A30113 100%);
|
background: #A30113;
|
||||||
color: white;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.step-label {
|
.step-label {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #909399;
|
color: #909399;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.step.active .step-label {
|
.step.active .step-label {
|
||||||
color: #303133;
|
color: #303133;
|
||||||
font-weight: 500;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.step-line {
|
.step-line {
|
||||||
width: 100px;
|
display: flex;
|
||||||
height: 2px;
|
align-items: center;
|
||||||
background: #EEEEEE;
|
color: #C0C4CC;
|
||||||
margin: 0 16px;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.step-line.active {
|
/* 第一步内容 */
|
||||||
background: #A30113;
|
.info-card {
|
||||||
}
|
background: #F9FAFC;
|
||||||
|
|
||||||
.transfer-info {
|
|
||||||
background: #F5F7FA;
|
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 24px;
|
padding: 40px;
|
||||||
border: 1px solid #EEEEEE;
|
margin-bottom: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-title {
|
.info-title {
|
||||||
font-size: 18px;
|
font-size: 16px;
|
||||||
font-weight: 600;
|
|
||||||
color: #303133;
|
color: #303133;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 12px;
|
||||||
padding-left: 12px;
|
|
||||||
border-left: 4px solid #A30113;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-content {
|
.info-title .highlight {
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
margin-bottom: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-row {
|
|
||||||
display: flex;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-row .label {
|
|
||||||
color: #606266;
|
|
||||||
min-width: 120px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-row .value {
|
|
||||||
color: #303133;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-row .value.highlight {
|
|
||||||
color: #A30113;
|
color: #A30113;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.next-btn {
|
.info-subtitle {
|
||||||
width: 180px;
|
|
||||||
height: 40px;
|
|
||||||
margin: 0 auto;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background: #A30113;
|
|
||||||
color: white;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.next-btn:hover,
|
|
||||||
.next-btn:focus,
|
|
||||||
.next-btn:active {
|
|
||||||
background: #A30113;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 第二步:上传凭证 */
|
|
||||||
.upload-section {
|
|
||||||
background: #F5F7FA;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 24px;
|
|
||||||
border: 1px solid #EEEEEE;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-item {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-label {
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #606266;
|
color: #606266;
|
||||||
font-weight: 500;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-area {
|
.info-grid {
|
||||||
width: 100%;
|
display: grid;
|
||||||
max-width: 400px;
|
gap: 16px;
|
||||||
height: 200px;
|
}
|
||||||
border: 2px dashed #DCDFE6;
|
|
||||||
border-radius: 8px;
|
.info-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
font-size: 14px;
|
||||||
justify-content: center;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
background: white;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-area:hover {
|
.info-item .label {
|
||||||
border-color: #4A90E2;
|
color: #909399;
|
||||||
background: #F5F9FF;
|
width: 80px;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-placeholder {
|
.info-item .value {
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 第二步内容 */
|
||||||
|
.form-container {
|
||||||
|
max-width: 500px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.form-tip {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #C0C4CC;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
|
||||||
|
|
||||||
.upload-hint {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #909399;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-preview {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-preview img {
|
.flex-1 {
|
||||||
width: 100%;
|
flex: 1;
|
||||||
height: 100%;
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-remove {
|
|
||||||
position: absolute;
|
|
||||||
top: 8px;
|
.add-header-link {
|
||||||
right: 8px;
|
font-size: 14px;
|
||||||
width: 32px;
|
color: #A30113;
|
||||||
height: 32px;
|
|
||||||
background: rgba(0, 0, 0, 0.5);
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s ease;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-remove:hover {
|
/* 第三步内容 */
|
||||||
background: rgba(0, 0, 0, 0.7);
|
.success-content {
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-btn {
|
|
||||||
width: 180px;
|
|
||||||
height: 40px;
|
|
||||||
margin: 20px auto 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: #A30113;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-btn:disabled {
|
|
||||||
background: #fab6b6;
|
|
||||||
cursor: not-allowed;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-btn:not(:disabled):hover,
|
|
||||||
.upload-btn:not(:disabled):focus,
|
|
||||||
.upload-btn:not(:disabled):active {
|
|
||||||
background: #A30113;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 第三步:等待到账 */
|
|
||||||
.success-section {
|
|
||||||
background: #F5F7FA;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 60px 24px;
|
|
||||||
border: 1px solid #EEEEEE;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 40px;
|
padding-top: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.success-message {
|
.success-icon {
|
||||||
text-align: center;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.success-title {
|
.success-title {
|
||||||
font-size: 18px;
|
font-size: 20px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #303133;
|
color: #303133;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.success-subtitle {
|
.success-desc {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #606266;
|
color: #909399;
|
||||||
|
margin-bottom: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.return-btn {
|
/* 通用按钮 */
|
||||||
width: 180px;
|
.btn-container {
|
||||||
height: 40px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border-radius: 4px;
|
}
|
||||||
|
|
||||||
|
.primary-btn {
|
||||||
|
width: 200px;
|
||||||
|
height: 40px;
|
||||||
background: #A30113;
|
background: #A30113;
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.return-btn:hover,
|
.primary-btn:hover {
|
||||||
.return-btn:focus,
|
background: #880C22;
|
||||||
.return-btn:active {
|
}
|
||||||
background: #A30113;
|
|
||||||
opacity: 1;
|
.primary-btn:disabled {
|
||||||
|
background: #fab6b6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-trigger {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-icon {
|
||||||
|
font-size: 24px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-text {
|
||||||
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,59 +1,292 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="content-section">
|
<div class="content-section">
|
||||||
<div class="invoice-container">
|
<div class="section-header">
|
||||||
<div class="add-card" @click="handleAddInvoice">
|
<div class="header-left">
|
||||||
<div class="add-text">添加</div>
|
<div class="title-bar"></div>
|
||||||
|
<div class="title-text">抬头管理</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="invoice-list">
|
<div class="add-btn" @click="handleAddInvoice">
|
||||||
<div
|
<span class="plus-icon">+</span>
|
||||||
v-for="invoice in invoiceList"
|
<span>添加抬头</span>
|
||||||
:key="invoice.id"
|
</div>
|
||||||
class="invoice-card"
|
</div>
|
||||||
|
|
||||||
|
<div class="table-container">
|
||||||
|
<n-data-table
|
||||||
|
:columns="columns"
|
||||||
|
:data="invoiceList"
|
||||||
|
:row-key="rowKey"
|
||||||
|
:pagination="false"
|
||||||
|
striped
|
||||||
|
size="medium"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 添加/编辑抬头弹窗 -->
|
||||||
|
<n-modal
|
||||||
|
v-model:show="showModal"
|
||||||
|
:mask-closable="false"
|
||||||
|
preset="card"
|
||||||
|
:title="currentInvoice ? '编辑抬头' : '添加抬头'"
|
||||||
|
class="invoice-modal"
|
||||||
|
:style="{ width: '600px' }"
|
||||||
|
:bordered="false"
|
||||||
|
:segmented="{ content: true }"
|
||||||
>
|
>
|
||||||
<div class="invoice-icon">
|
<n-form
|
||||||
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
ref="formRef"
|
||||||
<rect x="3" y="3" width="18" height="18" rx="2" stroke="#909399" stroke-width="2"/>
|
:model="formData"
|
||||||
<path d="M8 12H16M8 16H13" stroke="#909399" stroke-width="2" stroke-linecap="round"/>
|
:rules="formRules"
|
||||||
</svg>
|
label-placement="top"
|
||||||
</div>
|
require-mark-placement="left"
|
||||||
<div class="invoice-info">
|
:show-feedback="true"
|
||||||
<div class="invoice-name">{{ invoice.name }}</div>
|
>
|
||||||
<div class="invoice-status">{{ invoice.status }}</div>
|
<n-grid :cols="2" :x-gap="16">
|
||||||
</div>
|
<!-- 公司名称 -->
|
||||||
<div class="invoice-action" @click="handleDeleteInvoice(invoice)">
|
<n-form-item-gi :span="1" path="companyName" label="公司名称">
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<n-input
|
||||||
<path d="M3 6H21M8 6V4C8 3.44772 8.44772 3 9 3H15C15.5523 3 16 3.44772 16 4V6M19 6V20C19 20.5523 18.5523 21 18 21H6C5.44772 21 5 20.5523 5 20V6H19Z" stroke="#909399" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
v-model:value="formData.companyName"
|
||||||
</svg>
|
placeholder="请输入公司名称"
|
||||||
</div>
|
clearable
|
||||||
</div>
|
/>
|
||||||
</div>
|
</n-form-item-gi>
|
||||||
|
|
||||||
|
<!-- 公司税号 -->
|
||||||
|
<n-form-item-gi :span="1" path="taxNumber" label="公司税号">
|
||||||
|
<n-input
|
||||||
|
v-model:value="formData.taxNumber"
|
||||||
|
placeholder="请输入公司税号"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</n-form-item-gi>
|
||||||
|
|
||||||
|
<!-- 注册地址 -->
|
||||||
|
<n-form-item-gi :span="2" path="registeredAddress" label="注册地址">
|
||||||
|
<n-input
|
||||||
|
v-model:value="formData.registeredAddress"
|
||||||
|
placeholder="请输入注册地址"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</n-form-item-gi>
|
||||||
|
|
||||||
|
<!-- 注册电话 -->
|
||||||
|
<n-form-item-gi :span="2" path="registeredPhone" label="注册电话">
|
||||||
|
<n-input
|
||||||
|
v-model:value="formData.registeredPhone"
|
||||||
|
placeholder="请输入注册电话"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</n-form-item-gi>
|
||||||
|
|
||||||
|
<!-- 开户银行 -->
|
||||||
|
<n-form-item-gi :span="1" path="bankName" label="开户银行">
|
||||||
|
<n-input
|
||||||
|
v-model:value="formData.bankName"
|
||||||
|
placeholder="请输入开户银行"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</n-form-item-gi>
|
||||||
|
|
||||||
|
<!-- 银行账号 -->
|
||||||
|
<n-form-item-gi :span="1" path="bankAccount" label="银行账号">
|
||||||
|
<n-input
|
||||||
|
v-model:value="formData.bankAccount"
|
||||||
|
placeholder="请输入银行账号"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</n-form-item-gi>
|
||||||
|
|
||||||
|
<!-- 电子邮箱 -->
|
||||||
|
<n-form-item-gi :span="2" path="email" label="电子邮箱">
|
||||||
|
<n-input
|
||||||
|
v-model:value="formData.email"
|
||||||
|
placeholder="请输入邮箱"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
<template #feedback>
|
||||||
|
<span class="email-hint">此邮箱用于接收发票,请填写正确!</span>
|
||||||
|
</template>
|
||||||
|
</n-form-item-gi>
|
||||||
|
</n-grid>
|
||||||
|
</n-form>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<n-button class="cancel-btn" @click="handleCancel">取消</n-button>
|
||||||
|
<n-button class="save-btn" @click="handleSave">保存</n-button>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
|
</n-modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { useRouter } from 'vue-router'
|
import { h, ref, watch } from 'vue'
|
||||||
|
import { NButton, NDataTable, NSpace, NModal, NForm, NFormItemGi, NGrid, NInput } from 'naive-ui'
|
||||||
|
|
||||||
const router = useRouter()
|
const props = defineProps({
|
||||||
|
|
||||||
defineProps({
|
|
||||||
invoiceList: {
|
invoiceList: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => []
|
default: () => []
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['add-invoice', 'delete-invoice'])
|
const emit = defineEmits(['add-invoice', 'delete-invoice', 'update-invoice'])
|
||||||
|
|
||||||
|
const showModal = ref(false)
|
||||||
|
const currentInvoice = ref(null)
|
||||||
|
const formRef = ref(null)
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
const formData = ref({
|
||||||
|
companyName: '',
|
||||||
|
taxNumber: '',
|
||||||
|
registeredAddress: '',
|
||||||
|
registeredPhone: '',
|
||||||
|
bankName: '',
|
||||||
|
bankAccount: '',
|
||||||
|
email: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表单验证规则
|
||||||
|
const formRules = {
|
||||||
|
companyName: [
|
||||||
|
{ required: true, message: '请输入公司名称', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
taxNumber: [
|
||||||
|
{ required: true, message: '请输入公司税号', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
email: [
|
||||||
|
{ required: true, message: '请输入电子邮箱', trigger: 'blur' },
|
||||||
|
{
|
||||||
|
pattern: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
|
||||||
|
message: '请输入正确的邮箱格式',
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听弹窗显示状态,重置表单
|
||||||
|
watch(showModal, (newVal) => {
|
||||||
|
if (newVal) {
|
||||||
|
if (currentInvoice.value) {
|
||||||
|
// 编辑模式,填充数据
|
||||||
|
formData.value = { ...currentInvoice.value }
|
||||||
|
} else {
|
||||||
|
// 新增模式,清空数据
|
||||||
|
formData.value = {
|
||||||
|
companyName: '',
|
||||||
|
taxNumber: '',
|
||||||
|
registeredAddress: '',
|
||||||
|
registeredPhone: '',
|
||||||
|
bankName: '',
|
||||||
|
bankAccount: '',
|
||||||
|
email: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// 添加开票
|
// 添加开票
|
||||||
function handleAddInvoice() {
|
function handleAddInvoice() {
|
||||||
router.push('/invoice-header/add')
|
currentInvoice.value = null
|
||||||
|
showModal.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑开票
|
||||||
|
function handleEditInvoice(invoice) {
|
||||||
|
currentInvoice.value = { ...invoice }
|
||||||
|
showModal.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除开票
|
// 删除开票
|
||||||
function handleDeleteInvoice(invoice) {
|
function handleDeleteInvoice(invoice) {
|
||||||
$message.info('删除开票功能开发中')
|
// TODO: Add confirmation dialog
|
||||||
|
emit('delete-invoice', invoice)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 取消
|
||||||
|
function handleCancel() {
|
||||||
|
showModal.value = false
|
||||||
|
formRef.value?.restoreValidation()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存
|
||||||
|
function handleSave() {
|
||||||
|
formRef.value?.validate((errors) => {
|
||||||
|
if (!errors) {
|
||||||
|
handleModalSubmit(formData.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理弹窗提交
|
||||||
|
function handleModalSubmit(data) {
|
||||||
|
if (currentInvoice.value) {
|
||||||
|
emit('update-invoice', { ...currentInvoice.value, ...data })
|
||||||
|
} else {
|
||||||
|
emit('add-invoice', data)
|
||||||
|
}
|
||||||
|
showModal.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '公司名称',
|
||||||
|
key: 'name',
|
||||||
|
minWidth: 220,
|
||||||
|
ellipsis: true,
|
||||||
|
align: 'center',
|
||||||
|
titleAlign: 'center',
|
||||||
|
render: (row) => row.name || row.companyName || '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '税号',
|
||||||
|
key: 'taxId',
|
||||||
|
minWidth: 200,
|
||||||
|
align: 'center',
|
||||||
|
titleAlign: 'center',
|
||||||
|
render: (row) => row.taxId || row.taxNumber || '123456789012345678'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'actions',
|
||||||
|
width: 200,
|
||||||
|
align: 'center',
|
||||||
|
titleAlign: 'center',
|
||||||
|
render: (row) =>
|
||||||
|
h(
|
||||||
|
NSpace,
|
||||||
|
{ size: 12, justify: 'center' },
|
||||||
|
{
|
||||||
|
default: () => [
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
text: true,
|
||||||
|
type: 'primary',
|
||||||
|
size: 'small',
|
||||||
|
onClick: () => handleEditInvoice(row)
|
||||||
|
},
|
||||||
|
{ default: () => '编辑' }
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
text: true,
|
||||||
|
type: 'error',
|
||||||
|
size: 'small',
|
||||||
|
onClick: () => handleDeleteInvoice(row)
|
||||||
|
},
|
||||||
|
{ default: () => '删除' }
|
||||||
|
)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const rowKey = (row) => row.id ?? row.name ?? row.companyName ?? row.taxId ?? row.taxNumber
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@ -62,96 +295,139 @@ function handleDeleteInvoice(invoice) {
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
min-height: calc(100vh - 40px);
|
min-height: calc(100vh - 40px);
|
||||||
position: relative;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.invoice-container {
|
.section-header {
|
||||||
max-width: 1000px;
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-card {
|
.header-left {
|
||||||
position: absolute;
|
|
||||||
top: 24px;
|
|
||||||
right: 24px;
|
|
||||||
width: 80px;
|
|
||||||
height: 32px;
|
|
||||||
background: #A30113;
|
|
||||||
border-radius: 4px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
gap: 8px;
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-card:hover {
|
.title-bar {
|
||||||
|
width: 4px;
|
||||||
|
height: 16px;
|
||||||
|
background: #A30113;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-text {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: #A30113;
|
||||||
|
color: white;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-btn:hover {
|
||||||
background: #880C22;
|
background: #880C22;
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-text {
|
.plus-icon {
|
||||||
color: white;
|
font-size: 18px;
|
||||||
font-size: 14px;
|
font-weight: bold;
|
||||||
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.invoice-list {
|
.table-container {
|
||||||
display: grid;
|
width: 100%;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
||||||
gap: 16px;
|
|
||||||
margin-top: 60px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.invoice-card {
|
/* 弹窗样式 */
|
||||||
background: #F5F7FA;
|
:deep(.invoice-modal) {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 20px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
border: 1px solid #EEEEEE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.invoice-card:hover {
|
:deep(.invoice-modal .n-card-header) {
|
||||||
transform: translateY(-2px);
|
padding: 20px 24px;
|
||||||
box-shadow: 0 4px 12px rgba(163, 1, 19, 0.1);
|
border-bottom: 1px solid #f0f0f0;
|
||||||
border-color: #A30113;
|
|
||||||
}
|
|
||||||
|
|
||||||
.invoice-icon {
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.invoice-info {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.invoice-name {
|
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 500;
|
font-weight: 600;
|
||||||
color: #303133;
|
color: #303133;
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.invoice-status {
|
:deep(.invoice-modal .n-card__content) {
|
||||||
font-size: 12px;
|
padding: 24px;
|
||||||
color: #909399;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.invoice-action {
|
:deep(.n-form-item-label) {
|
||||||
cursor: pointer;
|
font-size: 14px;
|
||||||
padding: 8px;
|
color: #303133;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-form-item-label__asterisk) {
|
||||||
|
color: #A30113;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-input) {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.invoice-action:hover {
|
:deep(.n-input__input-el::placeholder) {
|
||||||
background: rgba(163, 1, 19, 0.1);
|
color: #C0C4CC;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
.email-hint {
|
||||||
.invoice-list {
|
font-size: 12px;
|
||||||
grid-template-columns: 1fr;
|
color: #A30113;
|
||||||
}
|
margin-top: 4px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
padding-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn {
|
||||||
|
padding: 8px 24px;
|
||||||
|
border: 1px solid #DCDFE6;
|
||||||
|
background: white;
|
||||||
|
color: #606266;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn:hover {
|
||||||
|
border-color: #C0C4CC;
|
||||||
|
background: #F5F7FA;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-btn {
|
||||||
|
padding: 8px 24px;
|
||||||
|
background: #A30113;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-btn:hover {
|
||||||
|
background: #880C22;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,112 +1,116 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="sidebar">
|
<div class="sidebar-card">
|
||||||
<!-- 用户信息 -->
|
|
||||||
<div class="user-info">
|
<div class="user-info">
|
||||||
<div class="avatar">
|
<div class="avatar-wrapper">
|
||||||
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<img src="@/assets/img/icon_user.png" alt="User" />
|
||||||
<circle cx="12" cy="8" r="4" stroke="white" stroke-width="2"/>
|
|
||||||
<path d="M6 21C6 17.134 8.686 14 12 14C15.314 14 18 17.134 18 21" stroke="white" stroke-width="2" stroke-linecap="round"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="user-phone">{{ userPhone }}</div>
|
<div class="user-phone">{{ userPhone || '18988880000' }}</div>
|
||||||
<div class="user-count">累计估值次数: {{ valuationCount }}</div>
|
<div class="valuation-count">剩余评估次数:{{ valuationCount }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 菜单列表 -->
|
|
||||||
<div class="menu-list">
|
<div class="menu-list">
|
||||||
<div
|
<div
|
||||||
v-for="menu in menuList"
|
v-for="menu in menuList"
|
||||||
:key="menu.id"
|
:key="menu.id"
|
||||||
class="menu-item"
|
class="menu-item"
|
||||||
:class="{ active: currentMenu === menu.id }"
|
:class="{ active: currentMenu === menu.id }"
|
||||||
@click="handleMenuClick(menu)"
|
@click="$emit('menu-click', menu)"
|
||||||
>
|
>
|
||||||
<component :is="menu.icon" class="menu-icon" />
|
<div class="menu-icon">
|
||||||
<span class="menu-text">{{ menu.label }}</span>
|
<img :src="getMenuIcon(menu.id)" :alt="menu.label" />
|
||||||
<svg v-if="menu.id !== currentMenu" width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
</div>
|
||||||
<path d="M9 18L15 12L9 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<span class="menu-label">{{ menu.label }}</span>
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import iconHistory from '@/assets/icon/估值记录.png'
|
||||||
|
import iconTransfer from '@/assets/icon/对公转账.png'
|
||||||
|
import iconInvoice from '@/assets/icon/抬头管理.png'
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
userPhone: {
|
userPhone: String,
|
||||||
type: String,
|
valuationCount: Number,
|
||||||
default: ''
|
currentMenu: String,
|
||||||
},
|
menuList: Array
|
||||||
valuationCount: {
|
|
||||||
type: Number,
|
|
||||||
default: 0
|
|
||||||
},
|
|
||||||
currentMenu: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
menuList: {
|
|
||||||
type: Array,
|
|
||||||
default: () => []
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['menu-click'])
|
defineEmits(['menu-click'])
|
||||||
|
|
||||||
function handleMenuClick(menu) {
|
function getMenuIcon(id) {
|
||||||
emit('menu-click', menu)
|
const iconMap = {
|
||||||
|
'history': iconHistory,
|
||||||
|
'transfer': iconTransfer,
|
||||||
|
'invoice': iconInvoice
|
||||||
|
}
|
||||||
|
return iconMap[id]
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.sidebar {
|
.sidebar-card {
|
||||||
width: 240px;
|
width: 280px;
|
||||||
background: white;
|
background: white;
|
||||||
padding: 20px;
|
border-radius: 16px;
|
||||||
box-shadow: 2px 0 4px rgba(0, 0, 0, 0.05);
|
padding: 40px 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.02);
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-info {
|
.user-info {
|
||||||
text-align: center;
|
display: flex;
|
||||||
padding-bottom: 20px;
|
flex-direction: column;
|
||||||
border-bottom: 1px solid #EEEEEE;
|
align-items: center;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar {
|
.avatar-wrapper {
|
||||||
width: 60px;
|
width: 80px;
|
||||||
height: 60px;
|
height: 80px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: linear-gradient(93deg, #880C22 0%, #A30113 100%);
|
background: #f0f0f0;
|
||||||
margin: 0 auto 12px;
|
margin-bottom: 16px;
|
||||||
|
overflow: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.avatar-wrapper img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
.user-phone {
|
.user-phone {
|
||||||
font-size: 16px;
|
font-size: 18px;
|
||||||
font-weight: 500;
|
font-weight: 600;
|
||||||
color: #303133;
|
color: #303133;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-count {
|
.valuation-count {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #909399;
|
color: #909399;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-list {
|
.menu-list {
|
||||||
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-item {
|
.menu-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 12px 16px;
|
padding: 12px 20px;
|
||||||
border-radius: 4px;
|
border-radius: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
color: #606266;
|
color: #606266;
|
||||||
@ -117,23 +121,40 @@ function handleMenuClick(menu) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.menu-item.active {
|
.menu-item.active {
|
||||||
background: rgba(163, 1, 19, 0.08);
|
background: #FFF0F0;
|
||||||
color: #A30113;
|
color: #A30113;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-icon {
|
.menu-icon {
|
||||||
width: 20px;
|
width: 24px;
|
||||||
height: 20px;
|
height: 24px;
|
||||||
margin-right: 12px;
|
margin-right: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-text {
|
.menu-icon img {
|
||||||
flex: 1;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
filter: grayscale(100%);
|
||||||
|
opacity: 0.6;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item.active .menu-icon img {
|
||||||
|
filter: none;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-label {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.sidebar {
|
.sidebar-card {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,187 +1,229 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="content-section">
|
<n-card :bordered="false" class="valuation-card">
|
||||||
<div class="asset-grid">
|
<div class="section-header">
|
||||||
<div
|
<div class="title-block">
|
||||||
v-for="item in assetList"
|
<div class="title-bar"></div>
|
||||||
:key="item.id"
|
<div class="title-text">估值记录</div>
|
||||||
class="asset-card"
|
|
||||||
>
|
|
||||||
<div class="asset-header">
|
|
||||||
<div class="asset-title">资产名称</div>
|
|
||||||
<div class="asset-status" :class="getStatusClass(item.status)">
|
|
||||||
{{ getStatusText(item.status) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="asset-date">{{ formatDate(item.created_at) }}</div>
|
|
||||||
<div class="asset-actions">
|
|
||||||
<div class="action-item" @click="handleDownloadReport(item)">
|
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M12 3V16M12 16L7 11M12 16L17 11" stroke="#4A90E2" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<path d="M3 20H21" stroke="#4A90E2" stroke-width="2" stroke-linecap="round"/>
|
|
||||||
</svg>
|
|
||||||
<span>报告</span>
|
|
||||||
</div>
|
|
||||||
<div class="action-item" @click="handleDownloadCertificate(item)">
|
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M12 3V16M12 16L7 11M12 16L17 11" stroke="#4A90E2" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<path d="M3 20H21" stroke="#4A90E2" stroke-width="2" stroke-linecap="round"/>
|
|
||||||
</svg>
|
|
||||||
<span>证书</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<!-- <n-button quaternary type="primary" size="small" @click="fetchHistory">
|
||||||
|
刷新
|
||||||
|
</n-button> -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<n-data-table
|
||||||
|
:loading="loading"
|
||||||
|
:columns="columns"
|
||||||
|
:data="tableData"
|
||||||
|
:row-key="rowKey"
|
||||||
|
:pagination="pagination"
|
||||||
|
remote
|
||||||
|
striped
|
||||||
|
size="medium"
|
||||||
|
/>
|
||||||
|
</n-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
defineProps({
|
import { h, onMounted, ref, reactive } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { NButton, NSpace } from 'naive-ui'
|
||||||
|
import api from '@/api'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
assetList: {
|
assetList: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => []
|
default: () => [],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const tableData = ref([...props.assetList])
|
||||||
|
|
||||||
|
function handleDownloadReport(item) {
|
||||||
|
window.$message?.info('下载报告功能开发中')
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDownloadCertificate(item) {
|
||||||
|
window.$message?.info('下载证书功能开发中')
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDate(dateStr) {
|
||||||
|
if (!dateStr) return ''
|
||||||
|
return `${dateStr.slice(0, 10)} ${dateStr.slice(11, 16)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const pagination = reactive({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
itemCount: 0,
|
||||||
|
showSizePicker: true,
|
||||||
|
pageSizes: [10, 20, 50],
|
||||||
|
prefix: ({ itemCount }) => `共 ${itemCount} 条`,
|
||||||
|
onChange: (page) => {
|
||||||
|
pagination.page = page
|
||||||
|
fetchHistory()
|
||||||
|
},
|
||||||
|
onUpdatePageSize: (pageSize) => {
|
||||||
|
pagination.pageSize = pageSize
|
||||||
|
pagination.page = 1
|
||||||
|
fetchHistory()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 格式化日期
|
const columns = [
|
||||||
function formatDate(dateStr) {
|
{
|
||||||
if (!dateStr) return ''
|
title: '序号',
|
||||||
return dateStr.slice(0, 10) + ' ' + dateStr.slice(11, 16)
|
key: 'index',
|
||||||
}
|
width: 80,
|
||||||
|
align: 'center',
|
||||||
// 获取状态文本
|
titleAlign: 'center',
|
||||||
function getStatusText(status) {
|
render: (_, index) => (pagination.page - 1) * pagination.pageSize + index + 1
|
||||||
const statusMap = {
|
},
|
||||||
'pending': '审核中',
|
{
|
||||||
'approved': '已通过',
|
title: '资产名称',
|
||||||
'rejected': '已拒绝',
|
key: 'asset_name',
|
||||||
'processing': '评估中'
|
minWidth: 200,
|
||||||
|
ellipsis: true,
|
||||||
|
align: 'center',
|
||||||
|
titleAlign: 'center',
|
||||||
|
render: (row) => h(
|
||||||
|
'div',
|
||||||
|
{
|
||||||
|
class: 'asset-name-link',
|
||||||
|
onClick: () => {
|
||||||
|
router.push({
|
||||||
|
path: '/pages',
|
||||||
|
query: { id: row.id }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
row.asset_name || row.name || '未知资产'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '评估时间',
|
||||||
|
key: 'created_at',
|
||||||
|
minWidth: 180,
|
||||||
|
align: 'center',
|
||||||
|
titleAlign: 'center',
|
||||||
|
render: (row) => formatDate(row.created_at),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'actions',
|
||||||
|
align: 'center',
|
||||||
|
titleAlign: 'center',
|
||||||
|
width: 220,
|
||||||
|
render: (row) =>
|
||||||
|
h(
|
||||||
|
NSpace,
|
||||||
|
{ size: 8, justify: 'center' },
|
||||||
|
{
|
||||||
|
default: () => [
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
text: true,
|
||||||
|
type: 'primary',
|
||||||
|
size: 'small',
|
||||||
|
onClick: (e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
handleDownloadReport(row)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ default: () => '下载报告' }
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
text: true,
|
||||||
|
type: 'primary',
|
||||||
|
size: 'small',
|
||||||
|
onClick: (e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
handleDownloadCertificate(row)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ default: () => '下载证书' }
|
||||||
|
),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const rowKey = (row) => row.id ?? row.asset_name ?? row.name
|
||||||
|
|
||||||
|
async function fetchHistory() {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await api.getHistoryList({
|
||||||
|
page: pagination.page,
|
||||||
|
page_size: pagination.pageSize
|
||||||
|
})
|
||||||
|
const list = res?.data?.items ?? res?.data?.results ?? res?.results ?? []
|
||||||
|
tableData.value = Array.isArray(list) ? list : []
|
||||||
|
pagination.itemCount = res?.data?.total ?? res?.total ?? 0
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载估值记录失败:', error)
|
||||||
|
tableData.value = props.assetList
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
}
|
}
|
||||||
return statusMap[status] || status
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取状态样式类
|
onMounted(fetchHistory)
|
||||||
function getStatusClass(status) {
|
|
||||||
return `status-${status}`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 下载报告
|
|
||||||
function handleDownloadReport(item) {
|
|
||||||
$message.info('下载报告功能开发中')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 下载证书
|
|
||||||
function handleDownloadCertificate(item) {
|
|
||||||
$message.info('下载证书功能开发中')
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.content-section {
|
.valuation-card {
|
||||||
background: white;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 24px;
|
|
||||||
min-height: calc(100vh - 40px);
|
min-height: calc(100vh - 40px);
|
||||||
position: relative;
|
border-radius: 12px;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.asset-grid {
|
.section-header {
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.asset-card {
|
|
||||||
background: #F5F7FA;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 16px;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
border: 1px solid #EEEEEE;
|
|
||||||
}
|
|
||||||
|
|
||||||
.asset-card:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 4px 12px rgba(163, 1, 19, 0.1);
|
|
||||||
border-color: #A30113;
|
|
||||||
}
|
|
||||||
|
|
||||||
.asset-header {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 8px;
|
justify-content: space-between;
|
||||||
}
|
|
||||||
|
|
||||||
.asset-title {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #303133;
|
|
||||||
}
|
|
||||||
|
|
||||||
.asset-date {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #909399;
|
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.asset-actions {
|
.title-block {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-item {
|
.title-bar {
|
||||||
display: flex;
|
width: 4px;
|
||||||
align-items: center;
|
height: 16px;
|
||||||
gap: 4px;
|
background: #a30113;
|
||||||
padding: 6px 12px;
|
border-radius: 2px;
|
||||||
background: white;
|
}
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 12px;
|
.title-text {
|
||||||
color: #A30113;
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-block {
|
||||||
|
padding: 24px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.asset-name-link) {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s ease;
|
color: #A30113;
|
||||||
border: 1px solid #EEEEEE;
|
font-weight: 500;
|
||||||
|
transition: all 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-item:hover {
|
:deep(.asset-name-link:hover) {
|
||||||
background: #A30113;
|
opacity: 0.8;
|
||||||
color: white;
|
text-decoration: underline;
|
||||||
border-color: #A30113;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-item:hover svg path {
|
|
||||||
stroke: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.asset-status {
|
|
||||||
padding: 4px 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-pending {
|
|
||||||
background: #FFF3E0;
|
|
||||||
color: #F57C00;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-approved {
|
|
||||||
background: #E8F5E9;
|
|
||||||
color: #2E7D32;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-rejected {
|
|
||||||
background: #FFEBEE;
|
|
||||||
color: #C62828;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-processing {
|
|
||||||
background: #E3F2FD;
|
|
||||||
color: #1565C0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.asset-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="user-center-container">
|
<div class="user-center-container">
|
||||||
|
<!-- 头部 -->
|
||||||
|
<AppHeader />
|
||||||
|
|
||||||
|
<!-- 主体内容容器 -->
|
||||||
|
<div class="main-container">
|
||||||
<!-- 左侧边栏 -->
|
<!-- 左侧边栏 -->
|
||||||
<UserSidebar
|
<UserSidebar
|
||||||
:user-phone="userPhone"
|
:user-phone="userPhone"
|
||||||
@ -11,13 +16,13 @@
|
|||||||
|
|
||||||
<!-- 右侧内容区 -->
|
<!-- 右侧内容区 -->
|
||||||
<div class="content-area">
|
<div class="content-area">
|
||||||
<!-- 返回按钮 -->
|
<!-- 返回按钮 (暂时注释) -->
|
||||||
<div class="back-button" @click="handleBackToHome">
|
<!-- <div class="back-button" @click="handleBackToHome">
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M15 18L9 12L15 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M15 18L9 12L15 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span>返回首页</span>
|
<span>返回首页</span>
|
||||||
</div>
|
</div> -->
|
||||||
|
|
||||||
<!-- 子路由视图 -->
|
<!-- 子路由视图 -->
|
||||||
<router-view
|
<router-view
|
||||||
@ -27,12 +32,14 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, h, watch, computed } from 'vue'
|
import { ref, onMounted, h, watch, computed } from 'vue'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
|
import AppHeader from '@/components/AppHeader.vue'
|
||||||
import UserSidebar from './components/UserSidebar.vue'
|
import UserSidebar from './components/UserSidebar.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@ -57,7 +64,7 @@ const assetList = ref([])
|
|||||||
// 开票列表
|
// 开票列表
|
||||||
const invoiceList = ref([
|
const invoiceList = ref([
|
||||||
{ id: 1, name: '某某公司', status: '待审' },
|
{ id: 1, name: '某某公司', status: '待审' },
|
||||||
{ id: 2, name: '某某公司', status: '待审' }
|
{ id: 2, name: '某某公司', status: '待审' },
|
||||||
])
|
])
|
||||||
|
|
||||||
// 菜单列表
|
// 菜单列表
|
||||||
@ -65,80 +72,15 @@ const menuList = ref([
|
|||||||
{
|
{
|
||||||
id: 'history',
|
id: 'history',
|
||||||
label: '估值记录',
|
label: '估值记录',
|
||||||
icon: () => h('svg', {
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
viewBox: '0 0 24 24',
|
|
||||||
fill: 'none',
|
|
||||||
xmlns: 'http://www.w3.org/2000/svg'
|
|
||||||
}, [
|
|
||||||
h('path', {
|
|
||||||
d: 'M12 8V12L15 15',
|
|
||||||
stroke: 'currentColor',
|
|
||||||
'stroke-width': 2,
|
|
||||||
'stroke-linecap': 'round'
|
|
||||||
}),
|
|
||||||
h('circle', {
|
|
||||||
cx: 12,
|
|
||||||
cy: 12,
|
|
||||||
r: 9,
|
|
||||||
stroke: 'currentColor',
|
|
||||||
'stroke-width': 2
|
|
||||||
})
|
|
||||||
])
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'transfer',
|
id: 'transfer',
|
||||||
label: '对公转账',
|
label: '对公转账',
|
||||||
icon: () => h('svg', {
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
viewBox: '0 0 24 24',
|
|
||||||
fill: 'none',
|
|
||||||
xmlns: 'http://www.w3.org/2000/svg'
|
|
||||||
}, [
|
|
||||||
h('path', {
|
|
||||||
d: 'M12 2V6M12 18V22M6 12H2M22 12H18',
|
|
||||||
stroke: 'currentColor',
|
|
||||||
'stroke-width': 2,
|
|
||||||
'stroke-linecap': 'round'
|
|
||||||
}),
|
|
||||||
h('circle', {
|
|
||||||
cx: 12,
|
|
||||||
cy: 12,
|
|
||||||
r: 4,
|
|
||||||
stroke: 'currentColor',
|
|
||||||
'stroke-width': 2
|
|
||||||
})
|
|
||||||
])
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'invoice',
|
id: 'invoice',
|
||||||
label: '开票管理',
|
label: '抬头管理',
|
||||||
icon: () => h('svg', {
|
},
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
viewBox: '0 0 24 24',
|
|
||||||
fill: 'none',
|
|
||||||
xmlns: 'http://www.w3.org/2000/svg'
|
|
||||||
}, [
|
|
||||||
h('rect', {
|
|
||||||
x: 3,
|
|
||||||
y: 3,
|
|
||||||
width: 18,
|
|
||||||
height: 18,
|
|
||||||
rx: 2,
|
|
||||||
stroke: 'currentColor',
|
|
||||||
'stroke-width': 2
|
|
||||||
}),
|
|
||||||
h('path', {
|
|
||||||
d: 'M8 12H16M8 16H13',
|
|
||||||
stroke: 'currentColor',
|
|
||||||
'stroke-width': 2,
|
|
||||||
'stroke-linecap': 'round'
|
|
||||||
})
|
|
||||||
])
|
|
||||||
}
|
|
||||||
])
|
])
|
||||||
|
|
||||||
// 返回首页
|
// 返回首页
|
||||||
@ -165,8 +107,13 @@ async function loadData() {
|
|||||||
assetList.value = res.results
|
assetList.value = res.results
|
||||||
valuationCount.value = res.results.length
|
valuationCount.value = res.results.length
|
||||||
}
|
}
|
||||||
|
const res2 = await api.userQuota()
|
||||||
|
if (res2 && res2.data) {
|
||||||
|
valuationCount.value = res2.data.remaining_count
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载数据失败:', error)
|
console.error('加载估值记录失败:', error)
|
||||||
|
$message.error('加载估值记录失败,请稍后重试')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,23 +124,37 @@ onMounted(() => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.user-center-container {
|
.user-center-container {
|
||||||
display: flex;
|
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: #F5F7FA;
|
background: #f5f7fa;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main Container Styles */
|
||||||
|
.main-container {
|
||||||
|
width: 1200px;
|
||||||
|
max-width: 95%;
|
||||||
|
margin: 20px auto;
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
flex: 1;
|
||||||
|
align-items: flex-start; /* Prevent stretching */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 右侧内容区 */
|
/* 右侧内容区 */
|
||||||
.content-area {
|
.content-area {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 20px;
|
background: white;
|
||||||
overflow-y: auto;
|
border-radius: 8px;
|
||||||
|
min-height: calc(100vh - 100px); /* Adjust based on header + margin */
|
||||||
position: relative;
|
position: relative;
|
||||||
|
overflow: hidden; /* Ensure rounded corners */
|
||||||
}
|
}
|
||||||
|
|
||||||
.back-button {
|
.back-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 44px; /* Align with the content card's top padding area */
|
top: 20px;
|
||||||
left: 44px; /* Align with the content card's left padding area */
|
left: 20px;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
@ -204,24 +165,25 @@ onMounted(() => {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
box-shadow: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.back-button:hover {
|
.back-button:hover {
|
||||||
color: #A30113;
|
color: #a30113;
|
||||||
}
|
}
|
||||||
|
|
||||||
.back-button svg {
|
@media (max-width: 1200px) {
|
||||||
transition: transform 0.3s ease;
|
.main-container {
|
||||||
}
|
width: 95%;
|
||||||
|
}
|
||||||
.back-button:hover svg {
|
|
||||||
transform: translateX(-4px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.user-center-container {
|
.main-container {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
padding: 10px 20px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||