feat ui设计稿bug修复
3
.gitignore
vendored
@@ -15,6 +15,9 @@ RTK.md
|
|||||||
.playwright-mcp
|
.playwright-mcp
|
||||||
.omo
|
.omo
|
||||||
.opencode
|
.opencode
|
||||||
|
.workbuddy
|
||||||
|
specs2
|
||||||
|
.shots
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.vscode/*
|
.vscode/*
|
||||||
|
|||||||
31
.mock/afterSales.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"data": {
|
||||||
|
"id": "1",
|
||||||
|
"projectNo": "20260813ZK2Q5f45",
|
||||||
|
"projectName": "社区康养智能服务系统",
|
||||||
|
"businessType": "互联网软件开发",
|
||||||
|
"status": "IN_PROGRESS",
|
||||||
|
"createTime": "2026-8-17 14:00",
|
||||||
|
"submitUserName": "张三",
|
||||||
|
"afterSale": {
|
||||||
|
"id": "1",
|
||||||
|
"requirementId": "1",
|
||||||
|
"type": "BUG问题",
|
||||||
|
"reason": "具体请看清单",
|
||||||
|
"remark": null,
|
||||||
|
"status": "IN_PROGRESS",
|
||||||
|
"requestUserName": "张三",
|
||||||
|
"requestTime": "2026-8-17 17:00",
|
||||||
|
"processUserName": null,
|
||||||
|
"processNote": null,
|
||||||
|
"processTime": null,
|
||||||
|
"withdrawReason": null
|
||||||
|
},
|
||||||
|
"afterSaleAttachments": [
|
||||||
|
{ "id": "1", "requirementId": "1", "phase": "AFTER_SALE", "ossId": "11", "originalName": "项目需求规格说明书.pdf", "url": "https://example.com/a.pdf", "fileSize": 2516582, "sortOrder": 1, "createTime": "2026-8-17 17:00" },
|
||||||
|
{ "id": "2", "requirementId": "1", "phase": "AFTER_SALE", "ossId": "12", "originalName": "项目需求说明书.word", "url": "https://example.com/b.docx", "fileSize": 2516582, "sortOrder": 2, "createTime": "2026-8-17 17:00" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
102
.mock/bills.json
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "1001",
|
||||||
|
"billNo": "20260813Z2Z5f245",
|
||||||
|
"projectId": "1",
|
||||||
|
"projectName": "某大型银行数字化转型方案",
|
||||||
|
"userId": "1",
|
||||||
|
"amount": 20000,
|
||||||
|
"billType": "ADVANCE",
|
||||||
|
"periodName": "第一期 · 付款 比例33%",
|
||||||
|
"periodRatio": 33,
|
||||||
|
"status": "PAID",
|
||||||
|
"payChannel": "WECHAT",
|
||||||
|
"transactionNo": "T2026081915303578K2M001",
|
||||||
|
"payTime": "2026-8-17 17:00",
|
||||||
|
"createTime": "2026-8-17 14:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1002",
|
||||||
|
"billNo": "20260813Z2Z5f245",
|
||||||
|
"projectId": "2",
|
||||||
|
"projectName": "智慧城市大数据平台建设项目",
|
||||||
|
"userId": "1",
|
||||||
|
"amount": 20000,
|
||||||
|
"billType": "PROGRESS",
|
||||||
|
"periodName": "第二期 · 付款 比例33%",
|
||||||
|
"periodRatio": 33,
|
||||||
|
"status": "PENDING",
|
||||||
|
"payChannel": null,
|
||||||
|
"transactionNo": null,
|
||||||
|
"payTime": null,
|
||||||
|
"createTime": "2026-8-17 14:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1003",
|
||||||
|
"billNo": "20260813Z2Z5f245",
|
||||||
|
"projectId": "3",
|
||||||
|
"projectName": "三甲医院暗审医疗信息化系统",
|
||||||
|
"userId": "1",
|
||||||
|
"amount": 20000,
|
||||||
|
"billType": "FINAL",
|
||||||
|
"periodName": "第三期 · 尾款 比例33%",
|
||||||
|
"periodRatio": 33,
|
||||||
|
"status": "UNSTARTED",
|
||||||
|
"payChannel": null,
|
||||||
|
"transactionNo": null,
|
||||||
|
"payTime": null,
|
||||||
|
"createTime": "2026-8-17 14:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1004",
|
||||||
|
"billNo": "20260813Z2Z5f246",
|
||||||
|
"projectId": "4",
|
||||||
|
"projectName": "社区康养智能服务系统",
|
||||||
|
"userId": "1",
|
||||||
|
"amount": 20000,
|
||||||
|
"billType": "ADVANCE",
|
||||||
|
"periodName": "第一期 · 预付款 比例30%",
|
||||||
|
"periodRatio": 30,
|
||||||
|
"status": "PAID",
|
||||||
|
"payChannel": "ALIPAY",
|
||||||
|
"transactionNo": "T2026081915303578K2M002",
|
||||||
|
"payTime": "2026-8-18 10:20",
|
||||||
|
"createTime": "2026-8-17 14:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1005",
|
||||||
|
"billNo": "20260813Z2Z5f247",
|
||||||
|
"projectId": "5",
|
||||||
|
"projectName": "智慧园区物联网平台",
|
||||||
|
"userId": "1",
|
||||||
|
"amount": 20000,
|
||||||
|
"billType": "PROGRESS",
|
||||||
|
"periodName": "第二期 · 进度款 比例35%",
|
||||||
|
"periodRatio": 35,
|
||||||
|
"status": "PENDING",
|
||||||
|
"payChannel": null,
|
||||||
|
"transactionNo": null,
|
||||||
|
"payTime": null,
|
||||||
|
"createTime": "2026-8-17 14:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1006",
|
||||||
|
"billNo": "20260813Z2Z5f248",
|
||||||
|
"projectId": "6",
|
||||||
|
"projectName": "某大型银行数字化转型方案",
|
||||||
|
"userId": "1",
|
||||||
|
"amount": 20000,
|
||||||
|
"billType": "ADVANCE",
|
||||||
|
"periodName": "第一期 · 预付款 比例33%",
|
||||||
|
"periodRatio": 33,
|
||||||
|
"status": "UNSTARTED",
|
||||||
|
"payChannel": null,
|
||||||
|
"transactionNo": null,
|
||||||
|
"payTime": null,
|
||||||
|
"createTime": "2026-8-17 14:00"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
19
.mock/files.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"data": {
|
||||||
|
"id": "1",
|
||||||
|
"projectNo": "20260813ZK2Q5f45",
|
||||||
|
"projectName": "社区康养智能服务系统",
|
||||||
|
"businessType": "互联网软件开发",
|
||||||
|
"status": "IN_PROGRESS",
|
||||||
|
"createTime": "2026-8-17 14:00",
|
||||||
|
"submitUserName": "张三",
|
||||||
|
"requirementAttachments": [
|
||||||
|
{ "id": "1", "requirementId": "1", "phase": "REQUIREMENT", "ossId": "1", "originalName": "项目需求规格说明书.pdf", "url": "https://example.com/a.pdf", "fileSize": 2516582, "sortOrder": 1, "createTime": "2026-8-17 17:00" },
|
||||||
|
{ "id": "2", "requirementId": "1", "phase": "REQUIREMENT", "ossId": "2", "originalName": "项目需求说明书.word", "url": "https://example.com/b.docx", "fileSize": 2516582, "sortOrder": 2, "createTime": "2026-8-17 17:00" },
|
||||||
|
{ "id": "3", "requirementId": "1", "phase": "REQUIREMENT", "ossId": "3", "originalName": "项目需求规格说明书.pdf", "url": "https://example.com/c.pdf", "fileSize": 2516582, "sortOrder": 3, "createTime": "2026-8-17 17:00" },
|
||||||
|
{ "id": "4", "requirementId": "1", "phase": "REQUIREMENT", "ossId": "4", "originalName": "项目需求说明书.word", "url": "https://example.com/d.docx", "fileSize": 2516582, "sortOrder": 4, "createTime": "2026-8-17 17:00" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
27
.mock/finance.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"data": {
|
||||||
|
"id": "1",
|
||||||
|
"projectNo": "20260813ZK2Q5f45",
|
||||||
|
"projectName": "社区康养智能服务系统",
|
||||||
|
"businessType": "互联网软件开发",
|
||||||
|
"status": "IN_PROGRESS",
|
||||||
|
"createTime": "2026-8-17 14:00",
|
||||||
|
"submitUserName": "张三",
|
||||||
|
"bills": [
|
||||||
|
{ "id": "1001", "billNo": "2026083222P345", "projectId": "1", "projectNo": "20260813ZK2Q5f45", "projectName": "社区康养智能服务系统", "userId": "1", "amount": "20100.00", "contractId": null, "billType": "ADVANCE", "periodName": "第一期", "periodRatio": "33.00", "status": "PAID", "payChannel": "WECHAT", "transactionNo": "123D06891B30030/8K2M001", "tradeName": null, "payTime": "2026-8-17 17:00", "dueDate": null },
|
||||||
|
{ "id": "1002", "billNo": "2026083222P345", "projectId": "1", "projectNo": "20260813ZK2Q5f45", "projectName": "社区康养智能服务系统", "userId": "1", "amount": "20100.00", "contractId": null, "billType": "PROGRESS", "periodName": "第二期", "periodRatio": "33.00", "status": "PENDING", "payChannel": null, "transactionNo": null, "tradeName": null, "payTime": null, "dueDate": null },
|
||||||
|
{ "id": "1003", "billNo": "2026083222P345", "projectId": "1", "projectNo": "20260813ZK2Q5f45", "projectName": "社区康养智能服务系统", "userId": "1", "amount": "20100.00", "contractId": null, "billType": "FINAL", "periodName": "第三期", "periodRatio": "33.00", "status": "UNSTARTED", "payChannel": null, "transactionNo": null, "tradeName": null, "payTime": null, "dueDate": null }
|
||||||
|
],
|
||||||
|
"billPlans": [],
|
||||||
|
"invoices": [
|
||||||
|
{ "id": "1", "billId": "1001", "invoiceTitle": "测试", "taxNo": null, "invoiceType": "电子普通发票", "amount": "20100.00", "status": "ISSUED", "invoiceNo": null, "invoiceFile": "https://example.com/inv.pdf", "applyTime": "2026-8-17 17:00", "invoiceDate": null, "createTime": "2026-8-17 17:00" },
|
||||||
|
{ "id": "2", "billId": "1001", "invoiceTitle": "测试", "taxNo": null, "invoiceType": "电子普通发票", "amount": "20100.00", "status": "ISSUED", "invoiceNo": null, "invoiceFile": "https://example.com/inv2.pdf", "applyTime": "2026-8-17 17:00", "invoiceDate": null, "createTime": "2026-8-17 17:00" }
|
||||||
|
],
|
||||||
|
"contracts": [
|
||||||
|
{ "id": "1", "contractNo": "HT20260813001", "projectId": "1", "title": "项目需求规格说明书.pdf", "amount": "60300.00", "signStatus": "SIGNED", "status": "SIGNED", "signDate": "2026-8-17", "file": "https://example.com/contract.pdf", "createTime": "2026-8-17 17:00" },
|
||||||
|
{ "id": "2", "contractNo": "HT20260813002", "projectId": "1", "title": "项目需求说明书.word", "amount": "60300.00", "signStatus": "SIGNED", "status": "SIGNED", "signDate": "2026-8-17", "file": "https://example.com/contract2.docx", "createTime": "2026-8-17 17:00" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
42
.mock/progress.json
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"data": {
|
||||||
|
"id": "1",
|
||||||
|
"projectNo": "20260813ZK2Q5f45",
|
||||||
|
"projectName": "社区康养智能服务系统",
|
||||||
|
"businessType": "互联网软件开发",
|
||||||
|
"status": "IN_PROGRESS",
|
||||||
|
"createTime": "2026-8-17 14:00",
|
||||||
|
"submitUserName": "张三",
|
||||||
|
"projectStage": {
|
||||||
|
"id": "1",
|
||||||
|
"projectId": "1",
|
||||||
|
"requirementId": "1",
|
||||||
|
"stageName": "开发实现",
|
||||||
|
"status": "IN_PROGRESS",
|
||||||
|
"startTime": "2026-8-17",
|
||||||
|
"planTime": "2026-8-17",
|
||||||
|
"finishTime": null,
|
||||||
|
"developmentProgress": 65,
|
||||||
|
"sort": 3
|
||||||
|
},
|
||||||
|
"developmentProgress": 65,
|
||||||
|
"projectMilestones": [
|
||||||
|
{ "id": "1", "projectId": "1", "milestoneName": "需求认证", "sortOrder": 1, "status": "COMPLETED", "startTime": "2026-8-13 11:04:11", "endTime": "2026-8-13 11:04:11", "parallelFlag": false },
|
||||||
|
{ "id": "2", "projectId": "1", "milestoneName": "原型设计", "sortOrder": 2, "status": "COMPLETED", "startTime": "2026-8-13 11:04:11", "endTime": "2026-8-13 11:04:11", "parallelFlag": false },
|
||||||
|
{ "id": "3", "projectId": "1", "milestoneName": "开发实现", "sortOrder": 3, "status": "IN_PROGRESS", "startTime": null, "endTime": null, "parallelFlag": false },
|
||||||
|
{ "id": "4", "projectId": "1", "milestoneName": "需求验收", "sortOrder": 4, "status": "UNSTARTED", "startTime": null, "endTime": null, "parallelFlag": false },
|
||||||
|
{ "id": "5", "projectId": "1", "milestoneName": "项目交付", "sortOrder": 5, "status": "UNSTARTED", "startTime": null, "endTime": null, "parallelFlag": false }
|
||||||
|
],
|
||||||
|
"members": [
|
||||||
|
{ "id": "1", "projectId": "1", "userId": "1", "userName": "张三", "roleKey": "pm", "roleName": "项目经理", "isOwner": true },
|
||||||
|
{ "id": "2", "projectId": "1", "userId": "2", "userName": "张三", "roleKey": "pm", "roleName": "项目经理", "isOwner": false },
|
||||||
|
{ "id": "3", "projectId": "1", "userId": "3", "userName": "张三", "roleKey": "pm", "roleName": "项目经理", "isOwner": false },
|
||||||
|
{ "id": "4", "projectId": "1", "userId": "4", "userName": "张三", "roleKey": "pm", "roleName": "项目经理", "isOwner": false },
|
||||||
|
{ "id": "5", "projectId": "1", "userId": "5", "userName": "张三", "roleKey": "pm", "roleName": "项目经理", "isOwner": false },
|
||||||
|
{ "id": "6", "projectId": "1", "userId": "6", "userName": "张三", "roleKey": "pm", "roleName": "项目经理", "isOwner": false },
|
||||||
|
{ "id": "7", "projectId": "1", "userId": "7", "userName": "张三", "roleKey": "pm", "roleName": "项目经理", "isOwner": false }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
19
diag.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
// 诊断:点页签后输出祖先链几何,定位额外间距来源
|
||||||
|
const TAB = Number(process.argv[2] || 2)
|
||||||
|
const TARGET = process.argv[3] || '.project-files__list'
|
||||||
|
|
||||||
|
const js = `(async function(){
|
||||||
|
const tabs = document.querySelectorAll(".project-detail__tab");
|
||||||
|
if (${TAB} > 0 && tabs[${TAB} - 1]) { tabs[${TAB} - 1].click(); await new Promise(r => setTimeout(r, 900)); }
|
||||||
|
let el = document.querySelector(${JSON.stringify(TARGET)});
|
||||||
|
const chain = [];
|
||||||
|
while (el && chain.length < 6) {
|
||||||
|
const r = el.getBoundingClientRect();
|
||||||
|
const cs = getComputedStyle(el);
|
||||||
|
chain.push({ cls: (el.className || '').toString().slice(0, 40), y: +r.y.toFixed(1), h: +r.height.toFixed(1), mt: cs.marginTop, pt: cs.paddingTop, pb: cs.paddingBottom, gap: cs.gap, rowGap: cs.rowGap });
|
||||||
|
el = el.parentElement;
|
||||||
|
}
|
||||||
|
return JSON.stringify(chain, null, 0);
|
||||||
|
})()`
|
||||||
|
console.log(js)
|
||||||
27
measure.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
// 临时几何量测:把要测的选择器写进 SEL,执行 `node measure.js <sel...>` 生成命令
|
||||||
|
const SELS = [
|
||||||
|
'.project-detail__breadcrumb',
|
||||||
|
'.project-info-card',
|
||||||
|
'.project-info-card__logo',
|
||||||
|
'.project-info-card__name',
|
||||||
|
'.project-info-card__meta',
|
||||||
|
'.project-info-card__status',
|
||||||
|
'.project-detail__tabs',
|
||||||
|
'.project-detail__tab',
|
||||||
|
'.project-progress__title',
|
||||||
|
'.project-progress__head',
|
||||||
|
'.project-progress__track',
|
||||||
|
'.project-progress__range',
|
||||||
|
'.project-milestones__title',
|
||||||
|
'.project-milestones__track',
|
||||||
|
'.project-milestones__node',
|
||||||
|
'.project-milestones__dot',
|
||||||
|
'.project-team__title',
|
||||||
|
'.project-team__list',
|
||||||
|
'.project-team__avatar',
|
||||||
|
]
|
||||||
|
|
||||||
|
const arr = JSON.stringify(SELS).replace(/"/g, '\\"')
|
||||||
|
const js = `(function(){const o={};for(const s of ${JSON.stringify(SELS)}){const e=document.querySelector(s);if(!e){o[s]=null;continue}const r=e.getBoundingClientRect();o[s]=[+r.x.toFixed(1),+r.y.toFixed(1),+r.width.toFixed(1),+r.height.toFixed(1)]}return JSON.stringify(o)})()`
|
||||||
|
console.log(js)
|
||||||
BIN
src/assets/home/banner-illustration.png
Normal file
|
After Width: | Height: | Size: 287 KiB |
BIN
src/assets/home/business-hardware.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
src/assets/home/business-internet.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
src/assets/home/business-iot.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
src/assets/home/business-manufacturing.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
src/assets/home/entry-cases.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
src/assets/home/entry-contract.png
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
BIN
src/assets/home/entry-demand.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
BIN
src/assets/home/entry-finance.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
src/assets/home/entry-service.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
@@ -3,12 +3,13 @@ import { useSiteStore } from '@/stores/modules/site'
|
|||||||
|
|
||||||
const siteStore = useSiteStore()
|
const siteStore = useSiteStore()
|
||||||
|
|
||||||
|
// 设计稿快速导航列原文:首页 / 案例中心 / 需求提报 / 下载 APP / 帮助中心
|
||||||
const quickLinks = [
|
const quickLinks = [
|
||||||
{ label: '首页', path: '/' },
|
{ label: '首页', path: '/' },
|
||||||
{ label: '案例库', path: '/cases' },
|
{ label: '案例中心', path: '/cases' },
|
||||||
{ label: '需求提报', path: '/demand' },
|
{ label: '需求提报', path: '/demand' },
|
||||||
{ label: '下载 APP', path: '/' },
|
{ label: '下载 APP', path: '/' },
|
||||||
{ label: '消息中心', path: '/message' },
|
{ label: '帮助中心', path: '/profile/help' },
|
||||||
]
|
]
|
||||||
|
|
||||||
const coreBusiness = [
|
const coreBusiness = [
|
||||||
@@ -22,7 +23,7 @@ const coreBusiness = [
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<footer class="app-footer">
|
<footer class="app-footer">
|
||||||
<div class="app-footer__inner container">
|
<div class="app-footer__inner">
|
||||||
<div class="app-footer__about">
|
<div class="app-footer__about">
|
||||||
<div class="app-footer__brand">
|
<div class="app-footer__brand">
|
||||||
<span class="app-footer__name">虫子智造</span>
|
<span class="app-footer__name">虫子智造</span>
|
||||||
@@ -32,7 +33,7 @@ const coreBusiness = [
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="app-footer__col">
|
<div class="app-footer__col app-footer__col--core">
|
||||||
<h4 class="app-footer__title">
|
<h4 class="app-footer__title">
|
||||||
核心业务
|
核心业务
|
||||||
</h4>
|
</h4>
|
||||||
@@ -43,7 +44,7 @@ const coreBusiness = [
|
|||||||
>{{ item }}</span>
|
>{{ item }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="app-footer__col">
|
<div class="app-footer__col app-footer__col--nav">
|
||||||
<h4 class="app-footer__title">
|
<h4 class="app-footer__title">
|
||||||
快速导航
|
快速导航
|
||||||
</h4>
|
</h4>
|
||||||
@@ -57,7 +58,7 @@ const coreBusiness = [
|
|||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="app-footer__col">
|
<div class="app-footer__col app-footer__col--contact">
|
||||||
<h4 class="app-footer__title">
|
<h4 class="app-footer__title">
|
||||||
联系我们
|
联系我们
|
||||||
</h4>
|
</h4>
|
||||||
@@ -72,7 +73,7 @@ const coreBusiness = [
|
|||||||
<p class="app-footer__legal">
|
<p class="app-footer__legal">
|
||||||
©2026 虫子智造 研发智造一体化平台 版权所有|自营研发生产平台,所有项目源码、图纸受平台知识产权保护,禁止盗用、复制、二次分发
|
©2026 虫子智造 研发智造一体化平台 版权所有|自营研发生产平台,所有项目源码、图纸受平台知识产权保护,禁止盗用、复制、二次分发
|
||||||
</p>
|
</p>
|
||||||
<p class="app-footer__legal">
|
<p class="app-footer__legal app-footer__legal--icp">
|
||||||
{{ siteStore.siteInfo.icp }} | {{ siteStore.siteInfo.police }}
|
{{ siteStore.siteInfo.icp }} | {{ siteStore.siteInfo.police }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -80,54 +81,76 @@ const coreBusiness = [
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
// 设计稿页脚 1920×481(首页稿 y1942→2423):
|
||||||
|
// 列区 y1996 起(页脚顶 +54),四列 x155/922/1257/1511,列宽 625/193/80/253
|
||||||
|
// 品牌名「虫子智造」@x200(相对列缩进 45),描述 @x155
|
||||||
|
// 版权区:©行 @x526(867 盒在 1920 画布居中)、ICP 行 @x841(相对 867 盒缩进 315),均左对齐
|
||||||
.app-footer {
|
.app-footer {
|
||||||
background: @footer-bg;
|
background: @footer-bg;
|
||||||
color: @white;
|
color: @white;
|
||||||
|
|
||||||
&__inner {
|
&__inner {
|
||||||
.flex(row, space-between, flex-start);
|
.flex(row, flex-start, flex-start);
|
||||||
gap: @spacing-xl;
|
width: 100%;
|
||||||
padding-top: @spacing-xxl;
|
max-width: 1920px;
|
||||||
padding-left: @footer-inset;
|
height: 332px; // 页脚顶 → © 行(y1942→2274)
|
||||||
padding-right: @footer-inset;
|
box-sizing: border-box;
|
||||||
|
padding-top: 54px;
|
||||||
|
padding-left: clamp(@spacing-base, calc((100% - 1609px) / 2), 155px); // 设计内容 x155→1764 = 1609 宽
|
||||||
|
overflow: hidden; // 防列内容撑出横向滚动
|
||||||
}
|
}
|
||||||
|
|
||||||
&__about {
|
&__about {
|
||||||
max-width: 480px;
|
flex: 0 1 625px; // x155 → 780
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__brand {
|
&__brand {
|
||||||
.flex(row, flex-start);
|
.flex(row, flex-start);
|
||||||
margin-bottom: @spacing-base;
|
margin-bottom: 20px; // 品牌名盒底 2037 → 描述 2057
|
||||||
|
margin-left: 45px; // 设计:品牌名 @x200,列起点 x155
|
||||||
}
|
}
|
||||||
|
|
||||||
&__name {
|
&__name {
|
||||||
font-size: @font-size-title;
|
font-size: 28px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
line-height: 41px;
|
||||||
color: @white;
|
color: @white;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__desc {
|
&__desc {
|
||||||
margin-top: @spacing-sm;
|
margin-top: 0;
|
||||||
font-size: @font-size-sm;
|
font-size: 16px;
|
||||||
line-height: 1.8;
|
font-weight: 500;
|
||||||
|
line-height: 23px; // 设计 625×115 五行
|
||||||
}
|
}
|
||||||
|
|
||||||
&__col {
|
&__col {
|
||||||
.flex(column, flex-start);
|
.flex(column, flex-start, flex-start); // 第三参必须显式:mixin 默认 center 会把列项水平居中
|
||||||
gap: @spacing-sm;
|
gap: 0;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
// 设计列位:核心业务 @922(列宽 193)、快速导航 @1257(80)、联系我们 @1511(253)
|
||||||
|
// 用 modifier 类定位;:nth-of-type 按标签序号计数(about 也算 div),会整体错位一档
|
||||||
|
&--core { flex: 0 0 193px; margin-left: 142px; }
|
||||||
|
&--nav { flex: 0 0 80px; margin-left: 142px; }
|
||||||
|
&--contact { flex: 0 0 253px; margin-left: 174px; }
|
||||||
}
|
}
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
margin-bottom: @spacing-sm;
|
margin-bottom: 20px; // 标题盒底 2037 → 列表 2057
|
||||||
font-size: @font-size-title;
|
font-size: 28px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
line-height: 41px;
|
||||||
|
white-space: nowrap;
|
||||||
color: @white;
|
color: @white;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__link {
|
&__link {
|
||||||
font-size: @font-size-sm;
|
font-size: 16px;
|
||||||
line-height: 1.5;
|
font-weight: 500;
|
||||||
|
line-height: 23px; // 设计 5 项 115 高
|
||||||
|
white-space: nowrap;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: color 0.2s;
|
transition: color 0.2s;
|
||||||
|
|
||||||
@@ -137,20 +160,32 @@ const coreBusiness = [
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__contact {
|
&__contact {
|
||||||
font-size: @font-size-sm;
|
font-size: 16px;
|
||||||
line-height: 1.8;
|
font-weight: 500;
|
||||||
|
line-height: 23px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 版权 / 备案:设计稿为页脚底部居中两行
|
// 版权区:867 盒在画布居中(1920 视口 → x526),盒内文字左对齐;ICP 缩进 315(→ x841)
|
||||||
&__bottom {
|
&__bottom {
|
||||||
padding: @spacing-xxl @footer-inset;
|
width: 867px;
|
||||||
text-align: center;
|
max-width: calc(100% - 32px);
|
||||||
|
margin: 0 auto;
|
||||||
|
padding-bottom: 87px; // ICP 盒底 2336 → 页脚底 2423
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__legal {
|
&__legal {
|
||||||
font-size: @font-size-sm;
|
margin: 0;
|
||||||
line-height: 1.8;
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 23px;
|
||||||
|
white-space: nowrap; // 设计稿单行(867×23),回退字体偏宽防折行
|
||||||
color: @white;
|
color: @white;
|
||||||
|
|
||||||
|
&--icp {
|
||||||
|
margin-top: 16px; // © 盒底 2297 → ICP 2313
|
||||||
|
margin-left: 315px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted } from 'vue'
|
|
||||||
import { storeToRefs } from 'pinia'
|
|
||||||
import { useMessageStore } from '@/stores/modules/message'
|
|
||||||
|
|
||||||
interface NavItem {
|
interface NavItem {
|
||||||
label: string
|
label: string
|
||||||
path: string
|
path: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设计稿页头(43 张稿一致):无 logo 文字,导航首项 x275(间隙 94),
|
||||||
|
// 右侧两枚 106×36 胶囊(快捷提报 x1559 / 在线客服 x1694,右缘 1800)
|
||||||
const navItems: NavItem[] = [
|
const navItems: NavItem[] = [
|
||||||
{ label: '首页', path: '/' },
|
{ label: '首页', path: '/' },
|
||||||
{ label: '案例库', path: '/cases' },
|
{ label: '案例库', path: '/cases' },
|
||||||
@@ -17,20 +15,11 @@ const navItems: NavItem[] = [
|
|||||||
{ label: '财务中心', path: '/finance' },
|
{ label: '财务中心', path: '/finance' },
|
||||||
{ label: '个人中心', path: '/profile' },
|
{ label: '个人中心', path: '/profile' },
|
||||||
]
|
]
|
||||||
|
|
||||||
const messageStore = useMessageStore()
|
|
||||||
const { unreadCount } = storeToRefs(messageStore)
|
|
||||||
|
|
||||||
onMounted(() => void messageStore.fetchUnreadCount())
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<header class="app-header">
|
<header class="app-header">
|
||||||
<div class="app-header__inner container">
|
<div class="app-header__inner">
|
||||||
<RouterLink to="/" class="app-header__logo">
|
|
||||||
<span class="app-header__logo-text">虫子智造</span>
|
|
||||||
</RouterLink>
|
|
||||||
|
|
||||||
<nav class="app-header__nav">
|
<nav class="app-header__nav">
|
||||||
<RouterLink
|
<RouterLink
|
||||||
v-for="item in navItems"
|
v-for="item in navItems"
|
||||||
@@ -51,13 +40,6 @@ onMounted(() => void messageStore.fetchUnreadCount())
|
|||||||
<img src="@/assets/icons/online-service.png" class="app-header__action-icon" alt="">
|
<img src="@/assets/icons/online-service.png" class="app-header__action-icon" alt="">
|
||||||
在线客服
|
在线客服
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<RouterLink to="/message" class="app-header__bell" aria-label="消息中心">
|
|
||||||
<span class="app-header__bell-icon">🔔</span>
|
|
||||||
<span v-if="unreadCount > 0" class="app-header__badge">{{ unreadCount }}</span>
|
|
||||||
</RouterLink>
|
|
||||||
<RouterLink to="/login" class="app-header__login link">
|
|
||||||
登录
|
|
||||||
</RouterLink>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@@ -72,25 +54,25 @@ onMounted(() => void messageStore.fetchUnreadCount())
|
|||||||
background: @header-bg;
|
background: @header-bg;
|
||||||
border-bottom: 1px solid @header-border;
|
border-bottom: 1px solid @header-border;
|
||||||
|
|
||||||
|
// 设计稿:导航首项 x275、胶囊右缘 1800(= 1920 − 120)
|
||||||
|
// 左留白按 155 基准、右留白按 120 基准,各自 clamp 收窄,窄视口不溢出
|
||||||
&__inner {
|
&__inner {
|
||||||
.flex(row, space-between);
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__logo {
|
|
||||||
.flex(row, flex-start);
|
.flex(row, flex-start);
|
||||||
|
height: 100%;
|
||||||
&-text {
|
padding-left: clamp(@spacing-base, calc((100% - 1600px) / 2), 155px);
|
||||||
font-size: @font-size-xl;
|
padding-right: clamp(@spacing-base, calc((100% - 1680px) / 2), 120px);
|
||||||
font-weight: 600;
|
|
||||||
color: @heading-color;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 导航首项 x275 = 左留白 155 + 120
|
||||||
&__nav {
|
&__nav {
|
||||||
.flex(row, center);
|
.flex(row, center);
|
||||||
gap: @header-nav-gap;
|
gap: @header-nav-gap; // 设计实测 94(首页 x275→311,案例库 x405)
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
margin-left: 120px;
|
||||||
|
|
||||||
|
@media (max-width: 1600px) {
|
||||||
|
gap: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 1280px) {
|
@media (max-width: 1280px) {
|
||||||
gap: 32px;
|
gap: 32px;
|
||||||
@@ -100,45 +82,49 @@ onMounted(() => void messageStore.fetchUnreadCount())
|
|||||||
position: relative;
|
position: relative;
|
||||||
.flex(row, center);
|
.flex(row, center);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
font-size: @font-size-base;
|
// 设计稿:导航项 18/500,未激活 #000000、激活 #085DFF
|
||||||
|
font-size: 18px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: @black;
|
color: @black;
|
||||||
transition: color 0.2s;
|
transition: color 0.2s;
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&.router-link-active {
|
&.router-link-active {
|
||||||
color: @primary-strong;
|
color: @home-tab-active;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 激活项底部短横线(设计 29×2px × 0.77,距顶栏底边 11px × 0.77)
|
// 激活项底部短横线(设计实测 29×2px @y59,距底 11)
|
||||||
&.router-link-active::after {
|
&.router-link-active::after {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
bottom: 9px;
|
bottom: 11px;
|
||||||
width: 22px;
|
width: 29px;
|
||||||
height: 2px;
|
height: 2px;
|
||||||
border-radius: 1px;
|
border-radius: 1px;
|
||||||
background: @primary-color;
|
background: @home-tab-active;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__actions {
|
&__actions {
|
||||||
.flex(row, flex-end);
|
.flex(row, flex-end, center);
|
||||||
gap: @spacing-base;
|
gap: 29px; // 设计:胶囊1 右缘 1665 → 胶囊2 左缘 1694
|
||||||
|
margin-left: auto;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设计稿:胶囊 106×36,图标 18 + 文字 16/500 白字
|
||||||
&__action {
|
&__action {
|
||||||
.flex(row, center);
|
.flex(row, center);
|
||||||
gap: @spacing-xs;
|
gap: 5px;
|
||||||
height: 28px;
|
width: 106px;
|
||||||
padding: 0 @spacing-sm;
|
height: 36px;
|
||||||
border-radius: @radius-sm;
|
border-radius: @radius-sm;
|
||||||
background: @primary-color;
|
background: @primary-color;
|
||||||
color: @white;
|
color: @white;
|
||||||
font-size: @font-size-sm;
|
font-size: 16px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
transition: opacity 0.2s;
|
transition: opacity 0.2s;
|
||||||
|
|
||||||
@@ -147,36 +133,9 @@ onMounted(() => void messageStore.fetchUnreadCount())
|
|||||||
}
|
}
|
||||||
|
|
||||||
&-icon {
|
&-icon {
|
||||||
width: 16px;
|
width: 18px;
|
||||||
height: 16px;
|
height: 18px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__bell {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&-icon {
|
|
||||||
font-size: @font-size-lg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__badge {
|
|
||||||
position: absolute;
|
|
||||||
top: -6px;
|
|
||||||
right: -8px;
|
|
||||||
min-width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
padding: 0 4px;
|
|
||||||
border-radius: @radius-round;
|
|
||||||
background: @danger-color;
|
|
||||||
color: @white;
|
|
||||||
font-size: @font-size-sm;
|
|
||||||
line-height: 16px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__login {
|
|
||||||
font-size: @font-size-base;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -67,11 +67,13 @@ const meta = computed<Array<{ label: string; value: string }>>(() => [
|
|||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.case-card {
|
.case-card {
|
||||||
|
// 设计稿卡片 396×267 白底 bd #E1DFDF/1
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
height: 267px;
|
||||||
|
box-sizing: border-box;
|
||||||
border: 1px solid @cases-card-border;
|
border: 1px solid @cases-card-border;
|
||||||
border-radius: @radius-base;
|
border-radius: @radius-base;
|
||||||
background: @white;
|
background: @white;
|
||||||
box-shadow: @cases-card-shadow;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: box-shadow 0.2s, transform 0.2s;
|
transition: box-shadow 0.2s, transform 0.2s;
|
||||||
|
|
||||||
@@ -82,7 +84,8 @@ const meta = computed<Array<{ label: string; value: string }>>(() => [
|
|||||||
|
|
||||||
&__cover {
|
&__cover {
|
||||||
position: relative;
|
position: relative;
|
||||||
aspect-ratio: 396 / 138; // 设计04 封面 396×137.8,通栏无留白
|
// 设计封面 396×137.81(通栏无留白)
|
||||||
|
height: 137px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,21 +95,24 @@ const meta = computed<Array<{ label: string; value: string }>>(() => [
|
|||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
|
|
||||||
&--placeholder {
|
&--placeholder {
|
||||||
background: @primary-strong; // 设计04 封面占位实底 rgba(2,79,255)
|
background: @primary-strong;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__tag {
|
&__tag {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 6px;
|
// 设计标签文字 @(628.6,540.35),卡 @(606,423) → 卡内 (22.6,117) 高 44
|
||||||
left: 14px;
|
top: 87px;
|
||||||
height: 19px; // 设计15 标签 24 × 0.77
|
left: 22px;
|
||||||
padding: 0 6px;
|
height: 44px;
|
||||||
|
padding: 0 14px;
|
||||||
border-radius: @radius-sm;
|
border-radius: @radius-sm;
|
||||||
background: @primary-strong;
|
background: @primary-strong;
|
||||||
color: @white;
|
color: @white;
|
||||||
font-size: 11px; // 设计 10 → 7.7,取可读下限 11
|
// 设计 28/500
|
||||||
line-height: 19px;
|
font-size: 28px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 44px;
|
||||||
|
|
||||||
&--hardware {
|
&--hardware {
|
||||||
background: @tag-hardware;
|
background: @tag-hardware;
|
||||||
@@ -160,12 +166,19 @@ const meta = computed<Array<{ label: string; value: string }>>(() => [
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__body {
|
&__body {
|
||||||
padding: 13px 16px 11px; // 上 17 / 左 20 / 下 14 × 0.77
|
position: relative;
|
||||||
|
// 设计标题 @(626.45,578.03),卡 @(606,423) → 卡内 (20,155)
|
||||||
|
padding: 18px 20px 0;
|
||||||
|
height: 129px;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
font-size: 12px; // 设计 16 × 0.77 ≈ 12.3
|
// 设计标题 16/500 #333333
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
line-height: 21.5px;
|
||||||
color: @cases-text;
|
color: @cases-text;
|
||||||
.ellipsis();
|
.ellipsis();
|
||||||
}
|
}
|
||||||
@@ -173,28 +186,31 @@ const meta = computed<Array<{ label: string; value: string }>>(() => [
|
|||||||
&__meta {
|
&__meta {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
gap: 6px @spacing-sm;
|
// 设计两行:y610.33 / y631.86 → 行距 21.5
|
||||||
margin-top: @spacing-sm;
|
gap: 21.5px 12px;
|
||||||
font-size: 11px; // 设计 10 → 7.7,取可读下限 11
|
margin-top: 10px;
|
||||||
line-height: 1.5;
|
// 设计元信息 10/400 #333333
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 14px;
|
||||||
color: @cases-text;
|
color: @cases-text;
|
||||||
|
|
||||||
li {
|
li {
|
||||||
/* 值可能较长(如「完成时间:2026/11/30」),换行而非截断,避免丢信息 */
|
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__label {
|
&__label {
|
||||||
color: inherit; // 设计04 标签与值同色 rgba(51,51,51)
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__more {
|
&__more {
|
||||||
display: block;
|
position: absolute;
|
||||||
width: fit-content;
|
// 设计 @(926.67,659.85),卡 @(606,423) → 卡内 (320.67,236.85)
|
||||||
margin: 11px 0 0 auto; // 设计 14 × 0.77
|
right: 20px;
|
||||||
color: @primary-strong; // 设计04 实取 rgba(2,79,255)
|
bottom: 14px;
|
||||||
font-size: 11px; // 设计 12 → 9.2,取可读下限 11
|
color: @primary-strong;
|
||||||
|
// 设计 12/400 #024FFF
|
||||||
|
font-size: 12px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
51
src/components/PageHead.vue
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
/**
|
||||||
|
* 页面头部(白底通栏):主标题 22/700 #1F2937 + 副标语 13/400 #6B7280。
|
||||||
|
*
|
||||||
|
* <p>43 张设计稿实测:标题顶 y125(顶栏 72 → 内容区 padding-top 53 含 1px 描边)、
|
||||||
|
* 行盒 29 → 副标语 ink y162(标题盒底 154 + 8)。整块白底高 179(72 → 251)。</p>
|
||||||
|
*/
|
||||||
|
interface Props {
|
||||||
|
title: string
|
||||||
|
slogan?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
defineProps<Props>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<header class="page-head">
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="page-head__title">
|
||||||
|
{{ title }}
|
||||||
|
</h1>
|
||||||
|
<p v-if="slogan" class="page-head__slogan">
|
||||||
|
{{ slogan }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.page-head {
|
||||||
|
background: @white;
|
||||||
|
padding-bottom: 72px; // 副标语盒底 179 → 页头块底 251(块高 179)
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
padding-top: 52px; // 顶栏 72 → 标题 ink 124(design 125)
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 29px;
|
||||||
|
color: @finance-ink;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__slogan {
|
||||||
|
padding-bottom: 0;
|
||||||
|
margin-top: 9px; // 标题盒底 153 → 副标语盒顶 162
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 17px;
|
||||||
|
color: @finance-muted-text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -19,17 +19,19 @@ const levelIndex = computed(() => (level.value === 'strong' ? 3 : level.value ==
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="password" class="pwd-strength">
|
<!-- 设计稿:强度条常驻显示(未输入时整条为空轨道 + 空强度文字),
|
||||||
|
占位高度固定 15(条行)+ 18(提示行),保证表单纵向节奏与设计稿一致 -->
|
||||||
|
<div class="pwd-strength">
|
||||||
<div class="pwd-strength__bars">
|
<div class="pwd-strength__bars">
|
||||||
<span
|
<span
|
||||||
v-for="i in 3"
|
v-for="i in 3"
|
||||||
:key="i"
|
:key="i"
|
||||||
class="pwd-strength__bar"
|
class="pwd-strength__bar"
|
||||||
:class="{ 'is-on': i <= levelIndex, [`is-${level}`]: i <= levelIndex }"
|
:class="{ 'is-on': password && i <= levelIndex, [`is-${level}`]: password && i <= levelIndex }"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span class="pwd-strength__label" :class="`is-${level}`">密码强度:{{ levelText }}</span>
|
<span class="pwd-strength__label" :class="`is-${level}`">{{ password ? `密码强度:${levelText}` : '' }}</span>
|
||||||
<p v-if="level === 'weak'" class="pwd-strength__tip">
|
<p v-if="password && level === 'weak'" class="pwd-strength__tip">
|
||||||
密码等级过低,请重新输入(建议字母+数字+符号组合)
|
密码等级过低,请重新输入(建议字母+数字+符号组合)
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -40,19 +42,23 @@ const levelIndex = computed(() => (level.value === 'strong' ? 3 : level.value ==
|
|||||||
.pwd-strength {
|
.pwd-strength {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr auto;
|
grid-template-columns: 1fr auto;
|
||||||
|
grid-template-rows: 15px auto;
|
||||||
column-gap: @spacing-sm;
|
column-gap: @spacing-sm;
|
||||||
row-gap: 9px;
|
row-gap: 3px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
// 设计稿:密码框底 → 条行 → 提示行 → 下一标签,节奏 15/15/18
|
||||||
|
min-height: 33px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
&__bars {
|
&__bars {
|
||||||
.flex(row, center);
|
.flex(row, center);
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计稿轨道实取 #EDEFF2,条高 5 设计px ≈ 4px
|
// 设计稿轨道实取 #EDEFF2,条高 5
|
||||||
&__bar {
|
&__bar {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 4px;
|
height: 5px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
background: @auth-track;
|
background: @auth-track;
|
||||||
|
|
||||||
@@ -69,10 +75,11 @@ const levelIndex = computed(() => (level.value === 'strong' ? 3 : level.value ==
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计稿强度文字为 11 设计px 中性灰(「弱」实取 rgba(156,163,175))
|
// 设计稿强度文字为 11px 中性灰(「弱」实取 rgba(156,163,175))
|
||||||
&__label {
|
&__label {
|
||||||
justify-self: end;
|
justify-self: end;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
|
line-height: 15px;
|
||||||
color: @text-color-disabled;
|
color: @text-color-disabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,6 +87,7 @@ const levelIndex = computed(() => (level.value === 'strong' ? 3 : level.value ==
|
|||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
|
line-height: 15px;
|
||||||
color: @danger-color;
|
color: @danger-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,17 +37,17 @@ function onGetCode(): void {
|
|||||||
.flex(row, stretch);
|
.flex(row, stretch);
|
||||||
gap: @spacing-sm;
|
gap: @spacing-sm;
|
||||||
|
|
||||||
// 设计稿(02/54/55):验证码输入框与普通输入框同规格 40 设计px ≈ 32px、13 设计px 字号
|
// 设计稿(02/54/55):验证码输入框 590×39(整行 680,右侧留 90 给「获取验证码」)
|
||||||
&__input {
|
&__input {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
height: 32px;
|
height: 39px;
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
border: 1px solid @border-color;
|
border: 1px solid #dde1e7;
|
||||||
border-radius: 6px;
|
border-radius: @radius-sm;
|
||||||
background: @white;
|
background: @white;
|
||||||
font-size: 11px;
|
font-size: 13px;
|
||||||
color: @heading-color;
|
color: #1f2937;
|
||||||
outline: none;
|
outline: none;
|
||||||
transition: border-color 0.2s;
|
transition: border-color 0.2s;
|
||||||
|
|
||||||
@@ -56,29 +56,30 @@ function onGetCode(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
color: @heading-color;
|
color: #1f2937;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计稿:「获取验证码」为白底蓝描边蓝字小按钮(#024FFF)
|
// 设计稿:「获取验证码」为蓝字纯文本(无边框),12/400 #024FFF
|
||||||
&__btn {
|
&__btn {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
min-width: 96px;
|
min-width: 74px;
|
||||||
height: 32px;
|
height: 39px;
|
||||||
padding: 0 @spacing-sm;
|
padding: 0;
|
||||||
border: 1px solid @primary-strong;
|
border: none;
|
||||||
border-radius: 6px;
|
background: transparent;
|
||||||
background: @white;
|
|
||||||
color: @primary-strong;
|
color: @primary-strong;
|
||||||
font-size: 11px;
|
font-size: 12px;
|
||||||
transition: all 0.2s;
|
font-weight: 400;
|
||||||
|
text-align: right;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.2s;
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
&:hover:not(:disabled) {
|
||||||
background: @primary-light;
|
color: @primary-hover;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
border-color: @border-color-dark;
|
|
||||||
color: @text-color-disabled;
|
color: @text-color-disabled;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export { default as AppHeader } from './AppHeader.vue'
|
export { default as AppHeader } from './AppHeader.vue'
|
||||||
export { default as AppFooter } from './AppFooter.vue'
|
export { default as AppFooter } from './AppFooter.vue'
|
||||||
export { default as AppButton } from './AppButton.vue'
|
export { default as AppButton } from './AppButton.vue'
|
||||||
|
export { default as PageHead } from './PageHead.vue'
|
||||||
export { default as CaseCard } from './CaseCard.vue'
|
export { default as CaseCard } from './CaseCard.vue'
|
||||||
export { default as FilterPanel } from './FilterPanel.vue'
|
export { default as FilterPanel } from './FilterPanel.vue'
|
||||||
export type { FilterChip } from './FilterPanel.vue'
|
export type { FilterChip } from './FilterPanel.vue'
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
// 全局浮动入口:快捷提报 / 在线客服(PRD 1.2)
|
|
||||||
const entries = [
|
|
||||||
{ label: '快捷提报', icon: '📝', path: '/demand' },
|
|
||||||
{ label: '在线客服', icon: '💬', path: '/service' },
|
|
||||||
]
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="floating-actions">
|
|
||||||
<RouterLink
|
|
||||||
v-for="entry in entries"
|
|
||||||
:key="entry.path"
|
|
||||||
:to="entry.path"
|
|
||||||
class="floating-actions__item"
|
|
||||||
:title="entry.label"
|
|
||||||
>
|
|
||||||
<span class="floating-actions__icon">{{ entry.icon }}</span>
|
|
||||||
<span class="floating-actions__label">{{ entry.label }}</span>
|
|
||||||
</RouterLink>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
.floating-actions {
|
|
||||||
position: fixed;
|
|
||||||
right: 24px;
|
|
||||||
bottom: 96px;
|
|
||||||
z-index: 90;
|
|
||||||
.flex(column, center);
|
|
||||||
gap: @spacing-sm;
|
|
||||||
|
|
||||||
&__item {
|
|
||||||
.flex(column, center);
|
|
||||||
gap: 2px;
|
|
||||||
width: 56px;
|
|
||||||
padding: @spacing-sm 0;
|
|
||||||
border-radius: @radius-lg;
|
|
||||||
background: @primary-color;
|
|
||||||
color: @white;
|
|
||||||
box-shadow: @shadow-lg;
|
|
||||||
transition: background 0.2s, transform 0.2s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: @primary-hover;
|
|
||||||
transform: translateY(-2px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__icon {
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__label {
|
|
||||||
font-size: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,40 +1,27 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
|
||||||
import { useRoute } from 'vue-router'
|
|
||||||
import { AppFooter, AppHeader, FilePreviewDialog } from '@/components'
|
import { AppFooter, AppHeader, FilePreviewDialog } from '@/components'
|
||||||
import FloatingActions from './components/FloatingActions.vue'
|
|
||||||
|
|
||||||
const route = useRoute()
|
|
||||||
|
|
||||||
/** 公共底栏只在首页展示(首页路由 meta.footer = true),其余页面不渲染 */
|
|
||||||
const showFooter = computed(() => route.meta.footer === true)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="layout">
|
<div class="layout">
|
||||||
<AppHeader />
|
<AppHeader />
|
||||||
<main
|
<main class="layout__main">
|
||||||
class="layout__main"
|
|
||||||
:class="{ 'layout__main--full': !showFooter }"
|
|
||||||
>
|
|
||||||
<router-view />
|
<router-view />
|
||||||
</main>
|
</main>
|
||||||
<AppFooter v-if="showFooter" />
|
<AppFooter />
|
||||||
<FloatingActions />
|
|
||||||
<!-- 全局文件预览弹窗(同源 OSS 预览,绕开桶强制下载) -->
|
<!-- 全局文件预览弹窗(同源 OSS 预览,绕开桶强制下载) -->
|
||||||
<FilePreviewDialog />
|
<FilePreviewDialog />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
// 公共底栏全站常驻(设计稿 43 张中除登录类外均带 481 高页脚)
|
||||||
.layout {
|
.layout {
|
||||||
.layout__main {
|
.flex(column, flex-start, stretch);
|
||||||
min-height: calc(100vh - @header-height - @footer-height);
|
|
||||||
|
|
||||||
// 无底栏的页面:底栏不渲染,这里把它的占位高度还给内容区,避免页面底部空出一屏
|
&__main {
|
||||||
&--full {
|
flex: 1;
|
||||||
min-height: calc(100vh - @header-height);
|
min-height: calc(100vh - @header-height - @footer-height);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -4,6 +4,14 @@ import { storage } from '@/utils/storage'
|
|||||||
/** 路由守卫:登录态校验 + document.title */
|
/** 路由守卫:登录态校验 + document.title */
|
||||||
export function setupGuard(router: Router): void {
|
export function setupGuard(router: Router): void {
|
||||||
router.beforeEach((to) => {
|
router.beforeEach((to) => {
|
||||||
|
// 视觉走查旁路:仅本地开发且带 ?visual=1 时放行,便于逐页比对设计稿
|
||||||
|
if (
|
||||||
|
import.meta.env.DEV
|
||||||
|
&& new URLSearchParams(window.location.search).has('visual')
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
if (to.meta.requiresAuth && !storage.get<string>('token')) {
|
if (to.meta.requiresAuth && !storage.get<string>('token')) {
|
||||||
return { path: '/login', query: { redirect: to.fullPath } }
|
return { path: '/login', query: { redirect: to.fullPath } }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { RouteRecordRaw } from 'vue-router'
|
import type { RouteRecordRaw } from 'vue-router'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 公共布局(AppHeader + router-view + AppFooter + FloatingActions)下的路由
|
* 公共布局(AppHeader + router-view + AppFooter)下的路由
|
||||||
* 完整规划见 docs/page-architecture.md 第 2 节
|
* 完整规划见 docs/page-architecture.md 第 2 节
|
||||||
* M3–M10 视图由子代理并行实现中,交付后本表即为终态
|
* M3–M10 视图由子代理并行实现中,交付后本表即为终态
|
||||||
*/
|
*/
|
||||||
@@ -28,7 +28,7 @@ const layoutRoutes: RouteRecordRaw = {
|
|||||||
{ path: 'finance/bills', name: 'Bills', component: () => import('@/views/finance/bills.vue'), meta: { title: '账单明细', requiresAuth: true } },
|
{ path: 'finance/bills', name: 'Bills', component: () => import('@/views/finance/bills.vue'), meta: { title: '账单明细', requiresAuth: true } },
|
||||||
{ path: 'finance/bills/:id', name: 'BillDetail', component: () => import('@/views/finance/bill-detail.vue'), meta: { title: '账单详情', requiresAuth: true } },
|
{ path: 'finance/bills/:id', name: 'BillDetail', component: () => import('@/views/finance/bill-detail.vue'), meta: { title: '账单详情', requiresAuth: true } },
|
||||||
{ path: 'finance/bills/:id/pay', name: 'BillPay', component: () => import('@/views/finance/pay.vue'), meta: { title: '扫码支付', requiresAuth: true } },
|
{ path: 'finance/bills/:id/pay', name: 'BillPay', component: () => import('@/views/finance/pay.vue'), meta: { title: '扫码支付', requiresAuth: true } },
|
||||||
{ path: 'finance/bills/:id/pay/result', name: 'PayResult', component: () => import('@/views/finance/pay-result.vue'), meta: { title: '支付结果', requiresAuth: true } },
|
{ path: 'finance/bills/:id/pay/result', name: 'PayResult', component: () => import('@/views/finance/pay-result.vue'), meta: { title: '支付成功', requiresAuth: true } },
|
||||||
// 收银台(确认支付):由账单明细勾选/单笔「去支付」带入 `?ids=bill-001,bill-002`
|
// 收银台(确认支付):由账单明细勾选/单笔「去支付」带入 `?ids=bill-001,bill-002`
|
||||||
{ path: 'finance/checkout', name: 'Checkout', component: () => import('@/views/finance/checkout.vue'), meta: { title: '确认支付', requiresAuth: true } },
|
{ path: 'finance/checkout', name: 'Checkout', component: () => import('@/views/finance/checkout.vue'), meta: { title: '确认支付', requiresAuth: true } },
|
||||||
{ path: 'finance/invoices', name: 'Invoices', component: () => import('@/views/finance/invoices.vue'), meta: { title: '发票申请', requiresAuth: true } },
|
{ path: 'finance/invoices', name: 'Invoices', component: () => import('@/views/finance/invoices.vue'), meta: { title: '发票申请', requiresAuth: true } },
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { computed, ref } from 'vue'
|
|||||||
import type { BusinessType } from '@/types/api'
|
import type { BusinessType } from '@/types/api'
|
||||||
import type { Case } from '@/types/business/cases'
|
import type { Case } from '@/types/business/cases'
|
||||||
import type {
|
import type {
|
||||||
BannerVo,
|
|
||||||
BusinessLine,
|
BusinessLine,
|
||||||
HomeStats,
|
HomeStats,
|
||||||
ProjectStatusCount,
|
ProjectStatusCount,
|
||||||
@@ -15,7 +14,6 @@ import type {
|
|||||||
} from '@/types/business/home'
|
} from '@/types/business/home'
|
||||||
import type { PortalEntryGroup, PortalEntryVo } from '@/types/business/home'
|
import type { PortalEntryGroup, PortalEntryVo } from '@/types/business/home'
|
||||||
import {
|
import {
|
||||||
getCarouselList,
|
|
||||||
getPortalEntryList,
|
getPortalEntryList,
|
||||||
getProjectStatusCount,
|
getProjectStatusCount,
|
||||||
getServiceTypeDetail,
|
getServiceTypeDetail,
|
||||||
@@ -132,7 +130,6 @@ export const useHomeStore = defineStore('home', () => {
|
|||||||
const casesStore = useCasesStore()
|
const casesStore = useCasesStore()
|
||||||
|
|
||||||
const stats = ref<HomeStats>({ pending: 0, inProgress: 0, total: 0 })
|
const stats = ref<HomeStats>({ pending: 0, inProgress: 0, total: 0 })
|
||||||
const banners = ref<BannerVo[]>([])
|
|
||||||
const businessLines = ref<BusinessLine[]>([])
|
const businessLines = ref<BusinessLine[]>([])
|
||||||
const featuredCases = ref<Case[]>([])
|
const featuredCases = ref<Case[]>([])
|
||||||
const currentBusiness = ref<BusinessLine | null>(null)
|
const currentBusiness = ref<BusinessLine | null>(null)
|
||||||
@@ -188,11 +185,10 @@ export const useHomeStore = defineStore('home', () => {
|
|||||||
|
|
||||||
/** 首页初始化:看板 + 轮播图 + 业务服务线 + 案例精选 */
|
/** 首页初始化:看板 + 轮播图 + 业务服务线 + 案例精选 */
|
||||||
async function fetchHomeData(): Promise<void> {
|
async function fetchHomeData(): Promise<void> {
|
||||||
const [statusCount, submissionList, cases, carouselList] = await Promise.all([
|
const [statusCount, submissionList, cases] = await Promise.all([
|
||||||
getProjectStatusCount(),
|
getProjectStatusCount(),
|
||||||
getServiceTypeList(),
|
getServiceTypeList(),
|
||||||
getFeaturedCasesApi(),
|
getFeaturedCasesApi(),
|
||||||
getCarouselList(),
|
|
||||||
// 案例标签需要「提报 id → 类型名」字典,与首页其余数据并行取(幂等,已加载则不发请求)
|
// 案例标签需要「提报 id → 类型名」字典,与首页其余数据并行取(幂等,已加载则不发请求)
|
||||||
casesStore.ensureCategories(),
|
casesStore.ensureCategories(),
|
||||||
])
|
])
|
||||||
@@ -205,7 +201,6 @@ export const useHomeStore = defineStore('home', () => {
|
|||||||
submissions.value = submissionList
|
submissions.value = submissionList
|
||||||
businessLines.value = submissionList.map(toBusinessLine)
|
businessLines.value = submissionList.map(toBusinessLine)
|
||||||
featuredCases.value = cases.map((vo) => toCase(vo, casesStore.typeNameById))
|
featuredCases.value = cases.map((vo) => toCase(vo, casesStore.typeNameById))
|
||||||
banners.value = carouselList ?? []
|
|
||||||
// 入口个性化接口失败已被内部吞掉,不会影响上面的首页数据
|
// 入口个性化接口失败已被内部吞掉,不会影响上面的首页数据
|
||||||
await fetchPortalEntries()
|
await fetchPortalEntries()
|
||||||
}
|
}
|
||||||
@@ -245,7 +240,6 @@ export const useHomeStore = defineStore('home', () => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
stats,
|
stats,
|
||||||
banners,
|
|
||||||
businessLines,
|
businessLines,
|
||||||
featuredCases,
|
featuredCases,
|
||||||
currentBusiness,
|
currentBusiness,
|
||||||
|
|||||||
@@ -1,10 +1,113 @@
|
|||||||
// 通用工具类
|
// 通用工具类
|
||||||
|
|
||||||
|
// 内容容器:设计稿内容宽 1780(x70→1850,specs2 全量复核)
|
||||||
|
// 1920 视口下 padding 70 → 内容 1780;窄视口只收窄留白,不产生横向滚动。
|
||||||
|
// ⚠️ 不能用固定 70px 留白:窄视口下 1780+140 > 视口宽 → 横向滚动条。
|
||||||
.container {
|
.container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: @container-width;
|
max-width: @container-width;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 0 @spacing-base;
|
// 留白夹在 16 与 70 之间:(1920 − 1780) / 2 = 70
|
||||||
|
padding: 0 clamp(@spacing-base, calc((100% - @page-content) / 2), @page-gutter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 首页容器:首页设计稿单独基准(内容 1680,留白 120)
|
||||||
|
.container--home {
|
||||||
|
padding: 0 @home-gutter;
|
||||||
|
|
||||||
|
@media (max-width: 1919px) {
|
||||||
|
padding: 0 @spacing-lg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ 登录 / 注册 / 找回密码(蓝湖设计稿 02·51·54·55) ============
|
||||||
|
// 三页共用外壳:左侧 784 品牌渐变面板 + 右侧白底表单区。
|
||||||
|
// 各页差异仅在「表单列起点 x」与「垂直顶 y」,由页面级 CSS 变量覆写。
|
||||||
|
// 设计稿锚点:面板 (0,0) 784×1080;主标 (257,304) 40/700;标语 (257,715) 22/600。
|
||||||
|
.auth-page {
|
||||||
|
--auth-form-x: 1022px; // 表单列起点(账号密码登录)
|
||||||
|
--auth-form-y: 306px; // 表单列垂直顶
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
min-height: 100vh;
|
||||||
|
background: @white;
|
||||||
|
|
||||||
|
&__brand {
|
||||||
|
.flex(column, flex-start, flex-start);
|
||||||
|
flex: 0 0 @login-brand-width;
|
||||||
|
padding: 304px 0 0 257px;
|
||||||
|
background: linear-gradient(160deg, @auth-gradient-from 0%, @auth-gradient-to 100%);
|
||||||
|
color: @white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__logo {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 40px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 58px;
|
||||||
|
color: @white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__key {
|
||||||
|
margin: 26px 0 0;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 400;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
color: @white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__slogan {
|
||||||
|
margin: 246px 0 0;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 29px;
|
||||||
|
letter-spacing: 8px;
|
||||||
|
color: @white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__guest {
|
||||||
|
position: absolute;
|
||||||
|
top: 24px;
|
||||||
|
right: 16px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #1f2937;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: @primary-strong;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__main {
|
||||||
|
.flex(column, flex-start, flex-start);
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
padding: var(--auth-form-y) 0 0 calc(var(--auth-form-x) - @login-brand-width);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__card {
|
||||||
|
.flex(column, flex-start, flex-start);
|
||||||
|
width: 693px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 34px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 45px;
|
||||||
|
color: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__link {
|
||||||
|
color: @primary-strong;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: @primary-hover;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.link {
|
.link {
|
||||||
|
|||||||
@@ -1,3 +1,14 @@
|
|||||||
// 样式入口(main.ts 引入;variables/mixins 由 vite additionalData 自动注入)
|
// 样式入口(main.ts 引入;variables/mixins 由 vite additionalData 自动注入)
|
||||||
@import './reset.less';
|
@import './reset.less';
|
||||||
@import './common.less';
|
@import './common.less';
|
||||||
|
|
||||||
|
// 全局视觉缩放:设计 1920 画布整体 ×0.67(变量见 variables.less @page-zoom)。
|
||||||
|
// zoom 由布局引擎重排,等价于屏幕逻辑分辨率放大 1/zoom,通栏/居中/滚动行为全部保持。
|
||||||
|
// ≤1286px 物理视口(1280 屏等)降到 0.66,保证 1920 逻辑内容仍无横向滚动。
|
||||||
|
html {
|
||||||
|
zoom: @page-zoom;
|
||||||
|
|
||||||
|
@media (max-width: 1286px) {
|
||||||
|
zoom: @page-zoom--sm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ body {
|
|||||||
body {
|
body {
|
||||||
color: @text-color;
|
color: @text-color;
|
||||||
font-family: 'PingFang SC', 'Source Han Sans SC', 'Microsoft YaHei', -apple-system, 'Helvetica Neue', Arial, sans-serif;
|
font-family: 'PingFang SC', 'Source Han Sans SC', 'Microsoft YaHei', -apple-system, 'Helvetica Neue', Arial, sans-serif;
|
||||||
font-size: @font-size-base;
|
font-size: 16px;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
background-color: @bg-color;
|
background-color: @bg-color;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
// ★ less 变量唯一来源(颜色/间距/字号/圆角)
|
// ★ less 变量唯一来源(颜色/间距/字号/圆角)
|
||||||
// 注:下列色值已按蓝湖设计稿(lanhu pid=b0052b6c)标注实测校准,
|
// 数据来源:蓝湖 MCP 实测(lanhu pid=b0052b6c,43 张设计稿已全量提取到本地,规格见 specs2/)。
|
||||||
// 换算约定:设计画布内容宽 1780px → 应用内容宽 1368px(.container 1400 − 2×16),
|
// 换算约定:**1:1 像素级还原**——设计画布 1920px。直接使用设计标注 px,不做任何缩放。
|
||||||
// 即 设计px × 0.77 ≈ CSS px;小字号(设计 ≤12px)按可读下限不跟缩。
|
// ⚠️ 内容区宽度按页面分两档(43 张设计稿实测结论):
|
||||||
|
// · 常规页(案例库/需求提报/项目/合同/财务/消息/客服/个人中心):内容宽 **1780**,左右留白各 **70**
|
||||||
|
// · 首页:内容宽 **1680**,左右留白各 **120**(首页设计稿单独一套基准)
|
||||||
|
// · 登录页:左品牌栏 784 + 右表单区 1136(无顶栏/页脚)
|
||||||
|
|
||||||
// 品牌色(设计稿实测:填充按钮/下划线 #1F67EF,内容强调 #024FFF)
|
// 品牌色(设计稿实测)
|
||||||
@primary-color: #1f67ef;
|
@primary-color: #1f67ef;
|
||||||
@primary-strong: #024fff;
|
@primary-strong: #024fff; // 内容强调 / 选中态(设计实测 #024FFF)
|
||||||
@primary-hover: #598bff;
|
@primary-hover: #598bff;
|
||||||
@primary-light: #eaf1ff;
|
@primary-light: #eaf1ff;
|
||||||
|
|
||||||
@@ -15,17 +18,18 @@
|
|||||||
@danger-color: #ef4444;
|
@danger-color: #ef4444;
|
||||||
@info-color: #8a94a6;
|
@info-color: #8a94a6;
|
||||||
|
|
||||||
// 文字色(设计稿灰阶:#1F2937 / #4B5563 / #6B7280 / #9CA3AF)
|
// 文字色(设计稿灰阶:#121212 / #333333 / #666666 / #999999)
|
||||||
@heading-color: #1f2937;
|
@heading-color: #121212;
|
||||||
@text-color: #4b5563;
|
@text-color: #333333;
|
||||||
@text-color-secondary: #6b7280;
|
@text-color-secondary: #666666;
|
||||||
@text-color-disabled: #9ca3af;
|
@text-color-disabled: #999999;
|
||||||
|
|
||||||
// 背景 / 边框(设计稿实测:页面 #F4F6F8、顶栏 #F4F5F9、顶栏下边线 #D8D8D8、页脚 #0C1321)
|
// 背景 / 边框(设计稿实测)
|
||||||
@bg-color: #f5f6f8;
|
@bg-color: #f5f6f8;
|
||||||
@bg-color-light: #fafbfc;
|
@bg-color-light: #fafbfc;
|
||||||
@border-color: #e4e7ec;
|
@border-color: #e4e7ec;
|
||||||
@border-color-dark: #cdd3dd;
|
@border-color-dark: #cdd3dd;
|
||||||
|
@border-color-btn: #d8dbdf; // 注销确认弹窗「暂不注销」底/描边(设计实取)
|
||||||
@white: #ffffff;
|
@white: #ffffff;
|
||||||
@black: #000000;
|
@black: #000000;
|
||||||
@header-bg: #f4f5f9;
|
@header-bg: #f4f5f9;
|
||||||
@@ -47,20 +51,63 @@
|
|||||||
@spacing-xl: 32px;
|
@spacing-xl: 32px;
|
||||||
@spacing-xxl: 48px;
|
@spacing-xxl: 48px;
|
||||||
|
|
||||||
// 字号
|
// 字号(1:1,设计实测体系 —— 43 张设计稿全量统计:16px 为出现最多的正文级字号)
|
||||||
|
@font-size-xs: 10px;
|
||||||
@font-size-sm: 12px;
|
@font-size-sm: 12px;
|
||||||
@font-size-base: 14px;
|
@font-size-base: 14px;
|
||||||
@font-size-lg: 16px;
|
@font-size-body: 16px; // 正文基准(设计稿最高频字号;body 默认值)
|
||||||
|
@font-size-md: 16px;
|
||||||
|
@font-size-lg: 18px; // 顶栏导航 / 小标题(设计实测 18/500)
|
||||||
@font-size-xl: 20px;
|
@font-size-xl: 20px;
|
||||||
@font-size-title: 22px; // 详情页主标题(项目详情设计稿 28 设计px × 0.77)
|
@font-size-title: 28px; // 栏目标题(设计实测 28/500)
|
||||||
@font-size-xxl: 28px;
|
@font-size-xxl: 36px; // 大标题(设计实测 36/700)
|
||||||
|
@font-size-hero: 48px; // 头图主标题(设计实测 48/500、52/700、60/700)
|
||||||
|
@font-size-hero-lg: 60px; // 首页头图主标题(设计实测 60/700)
|
||||||
|
|
||||||
// 布局(容器宽度按设计稿标注校准:设计稿为约 1400 宽的整幅版面)
|
// 首页头图(设计画板 y73 起,1920×661)
|
||||||
@container-width: 1400px;
|
@banner-title-color: #12171A; // 头图标题/副标题色(设计实取)
|
||||||
@header-height: 64px;
|
@btn-gradient-from: #A061FE; // 主按钮渐变起(设计实取)
|
||||||
@header-nav-gap: 72px; // 顶栏导航项间距(设计 94px × 0.77;≤1280 视口降为 32px)
|
@btn-gradient-to: #045DFF; // 主按钮渐变止(设计实取)
|
||||||
@footer-height: 240px;
|
@btn-ghost-color: #2B5FF0; // 描边按钮文字色(设计像素实测)
|
||||||
@footer-inset: 65px; // 页脚内容左右内缩(设计 85px × 0.77,页脚比正文更窄)
|
|
||||||
|
// 布局(1:1,设计实测)
|
||||||
|
@container-width: 1920px; // 设计画布 / 通栏块总宽
|
||||||
|
|
||||||
|
// 全局视觉缩放:设计稿按 1920 画布 1:1 实现,整体观感偏大,
|
||||||
|
// 按用户要求整体缩放 67%(html zoom,等比缩放、比例关系与通栏布局不破坏)。
|
||||||
|
// 1920 物理屏逻辑视口 ≈ 2866 ≥ 1920 → 内容居中、无横向滚动。
|
||||||
|
// 小屏适配(自行判断):物理视口 <1287px(如 1280 屏)时 1920×0.67≈1286 放不下,
|
||||||
|
// 降到 0.66 → 1280/0.66 ≈ 1939 ≥ 1920,仍无横向滚动;1366 屏 1366/0.67≈2039 ✓ 不需降档。
|
||||||
|
@page-zoom: 0.67;
|
||||||
|
@page-zoom--sm: 0.66;
|
||||||
|
|
||||||
|
// 常规页内容区(specs2 全量复核 2026-09-30:案例库/需求提报/项目/合同/财务/消息详情/客服/
|
||||||
|
// 收藏/发票抬头/业务详情 共 17 张设计稿内容块均为 x70→1850 = **1780 宽**;此前误标 155/1600
|
||||||
|
// 导致整站内容右移 85px、窄 170px,已修正回归设计稿)
|
||||||
|
@page-content: 1780px; // 常规页内容实宽(设计实测 x70→1850)
|
||||||
|
@page-gutter: 70px; // 常规页左右留白(设计实测)
|
||||||
|
|
||||||
|
// 个人中心系(个人中心/账号安全/地址管理等 13 张)视觉核对定版基准,独立不联动:
|
||||||
|
// 左列 x155 宽 200 + 右卡,视觉比对设计稿裁片确认(specs 嵌套组坐标为组内相对值,不可直接用)
|
||||||
|
@profile-content: 1600px;
|
||||||
|
@profile-gutter: 155px;
|
||||||
|
|
||||||
|
// 首页专用(首页设计稿单独基准:x120→1800)
|
||||||
|
@home-content: 1680px; // 首页内容实宽
|
||||||
|
@home-gutter: 120px; // 首页左右留白
|
||||||
|
|
||||||
|
// 兼容旧引用(= 常规页基准,即绝大多数页面)
|
||||||
|
@content-width: @page-content;
|
||||||
|
@container-width-inner: @page-content;
|
||||||
|
|
||||||
|
@header-height: 72px; // 顶栏高(设计实测 72px)
|
||||||
|
@header-nav-gap: 94px; // 顶栏导航项文字间距(设计实测:首页 x275→311,案例库 x405)
|
||||||
|
@footer-height: 481px; // 页脚高(设计实测 481px)
|
||||||
|
@footer-inset: 70px; // 页脚内容左右内缩(与常规页内容区对齐)
|
||||||
|
|
||||||
|
// 登录页(无顶栏/页脚)
|
||||||
|
@login-brand-width: 784px; // 左侧品牌渐变栏
|
||||||
|
@login-form-offset: 1022px; // 右侧表单区起点
|
||||||
|
|
||||||
// 阴影
|
// 阴影
|
||||||
@shadow-base: 0 2px 8px rgba(31, 39, 51, 0.08);
|
@shadow-base: 0 2px 8px rgba(31, 39, 51, 0.08);
|
||||||
@@ -194,6 +241,33 @@
|
|||||||
@tag-info-bg: #f3f4f6; // 徽标底:未支付 / 中性(设计 09 实取)
|
@tag-info-bg: #f3f4f6; // 徽标底:未支付 / 中性(设计 09 实取)
|
||||||
@milestone-line: #eeeff2; // 里程碑未完成连接线(设计 13 实取)
|
@milestone-line: #eeeff2; // 里程碑未完成连接线(设计 13 实取)
|
||||||
|
|
||||||
|
// 项目详情(蓝湖设计稿 5 张:项目进度 / 项目文件 / 合同财务 / 售后信息 / 申请售后)
|
||||||
|
// ⚠️ 换算约定:**1:1 像素级还原**,设计画布 1920px,直接使用设计标注 px,不做缩放。
|
||||||
|
// 实测锚点(1920 视口):面包屑 (70,116) 24/500 #666666;主标题 (142,212) 19/700;
|
||||||
|
// 页签行 (70,278) 1780×40,文字 13/600 激活 / 13/400 常规,节距 86;
|
||||||
|
// 分隔线 (70,317) 1780×1 #F0F1F3;区块标题 15/700 @(82,y)。
|
||||||
|
@project-ink: #1f2937; // 主标题 / 区块标题 / 文件名(设计实取 #1F2937)
|
||||||
|
@project-label: #6b7280; // 「项目编号:」等字段标签(设计实取 #6B7280)
|
||||||
|
@project-value: #1f2937; // 字段值(设计实取 #1F2937)
|
||||||
|
@project-muted: #9ca3af; // 次行说明:上传时间 / 占位文字(设计实取 #9CA3AF)
|
||||||
|
@project-cell: #4b5563; // 只读值 / 行内操作「预览·下载」(设计实取 #4B5563)
|
||||||
|
@project-card: #ffffff; // 表格区白底(设计实取)
|
||||||
|
@project-field-bg: #f3f4f6; // 只读字段底(设计实取 #F3F4F6)
|
||||||
|
@project-input-border: #dde1e7; // 只读框 / 输入框描边(设计实取 #DDE1E7)
|
||||||
|
@project-head-bg: #f7f8fa; // 里程碑 / 文件行浅底(设计实取 #F7F8FA)
|
||||||
|
@project-upload-tag: #ef4444; // 上传文档红字(设计实取 #EF4444)
|
||||||
|
@project-banner-soft: #fdeeee; // 售后工单横幅底(设计实取 #FDEEEE)
|
||||||
|
|
||||||
|
// 项目详情 → 里程碑(设计实测)
|
||||||
|
@milestone-node: 356px; // 节点列宽(相邻节点 x70→426 = 356)
|
||||||
|
@milestone-dot: 26px; // 已完成实心点直径(x235..261 = 26)
|
||||||
|
@milestone-ring: #e5e7eb; // 待开始连接线(设计实取 #E5E7EB)
|
||||||
|
@milestone-idle-text: #9ca3af; // 待开始 / 进行中的状态说明(设计实取 #9CA3AF)
|
||||||
|
|
||||||
|
// 项目详情 → 项目团队(设计实测:头像 64,相邻中心距 99 → 间距 35)
|
||||||
|
@team-size: 64px;
|
||||||
|
@team-gap: 35px;
|
||||||
|
|
||||||
// 合同中心(蓝湖设计稿)
|
// 合同中心(蓝湖设计稿)
|
||||||
// 设计索引:41 顶部横幅+搜索栏,46/47 表格+分页,40/44/52 合同详情,45 面包屑,39 签署。
|
// 设计索引:41 顶部横幅+搜索栏,46/47 表格+分页,40/44/52 合同详情,45 面包屑,39 签署。
|
||||||
// ⚠️ 换算约定同上:设计px × 0.77 ≈ CSS px;设计 ≤12px 小字号按可读下限取 11px,不跟缩。
|
// ⚠️ 换算约定同上:设计px × 0.77 ≈ CSS px;设计 ≤12px 小字号按可读下限取 11px,不跟缩。
|
||||||
@@ -245,6 +319,8 @@
|
|||||||
@profile-line: #e5e7eb; // 按钮描边 / 分隔线(设计 28 注销按钮描边实取 (229,231,235))
|
@profile-line: #e5e7eb; // 按钮描边 / 分隔线(设计 28 注销按钮描边实取 (229,231,235))
|
||||||
@profile-input-border: #dde1e7; // 输入框描边(设计 22 实取 (221,225,231))
|
@profile-input-border: #dde1e7; // 输入框描边(设计 22 实取 (221,225,231))
|
||||||
@profile-radius: 6px; // 输入框 / 小按钮 / 菜单选中项圆角(设计 8px × 0.77)
|
@profile-radius: 6px; // 输入框 / 小按钮 / 菜单选中项圆角(设计 8px × 0.77)
|
||||||
|
@profile-page-bg: #f4f6f8; // 个人中心页底色(设计稿实取)
|
||||||
|
@profile-ink: #1f2937; // 个人中心标题字色(设计稿实取)
|
||||||
@profile-hotline-bg: #d6e4fb; // 专属客服热线卡底色(设计 14 实取 rgba(214,228,251))
|
@profile-hotline-bg: #d6e4fb; // 专属客服热线卡底色(设计 14 实取 rgba(214,228,251))
|
||||||
@profile-icon-pwd: #c7d2fe; // 账号安全「修改密码」图标块底(设计 28 实取)
|
@profile-icon-pwd: #c7d2fe; // 账号安全「修改密码」图标块底(设计 28 实取)
|
||||||
@profile-icon-phone: #fdebeb; // 账号安全「绑定手机」图标块底(设计 28 实取)
|
@profile-icon-phone: #fdebeb; // 账号安全「绑定手机」图标块底(设计 28 实取)
|
||||||
@@ -257,6 +333,13 @@
|
|||||||
|
|
||||||
// 合同中心(蓝湖设计稿)· 补充
|
// 合同中心(蓝湖设计稿)· 补充
|
||||||
@contract-ink: #121212; // 页面最深标题色(设计实取 #121212)
|
@contract-ink: #121212; // 页面最深标题色(设计实取 #121212)
|
||||||
|
@contract-page-bg: #f4f6f8; // 页面底色 / 详情只读值框底(设计 e6c3a361 实取 #F4F6F8)
|
||||||
|
|
||||||
// 财务中心(蓝湖设计稿)· 补充
|
// 财务中心(蓝湖设计稿)· 补充
|
||||||
@finance-pay-bg: #f7f8f9; // 扫码支付页底色(设计实取 #f7f8f9)
|
@finance-pay-bg: #f7f8f9; // 扫码支付页底色(设计实取 #f7f8f9)
|
||||||
|
// 财务中心概览 / 账单(设计 6cd4aa76 等,1920 画布 1:1 实取)
|
||||||
|
@finance-muted-text: #6b7280; // 副标语 / 图表刻度 / 图例 / 次级文字(设计实取 #6B7280)
|
||||||
|
@finance-card-line: #f0f1f3; // 卡片描边(设计实取 #F0F1F3)
|
||||||
|
@finance-entry-text: #4b5563; // 快捷入口文字(设计实取 #4B5563)
|
||||||
|
@finance-axis-text: #9ca3af; // 图表 Y 轴刻度(设计实取 #9CA3AF)
|
||||||
|
@finance-ink: #1f2937; // 区块标题(设计实取 #1F2937)
|
||||||
|
|||||||
@@ -309,15 +309,17 @@ function clearChips(): void {
|
|||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.case-filter {
|
.case-filter {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 397px; // 设计04 面板 516 × 0.77
|
// 设计稿筛选面板 矩形 148286 @(70,272) 516×1461 白底
|
||||||
padding: 12px 0 40px; // 顶 15 × 0.77 / 底 50 × 0.77
|
width: 516px;
|
||||||
|
padding: 15px 0 40px;
|
||||||
border-radius: @radius-base;
|
border-radius: @radius-base;
|
||||||
background: @white;
|
background: @white;
|
||||||
|
|
||||||
&__head,
|
&__head,
|
||||||
&__group,
|
&__group,
|
||||||
&__actions {
|
&__actions {
|
||||||
padding: 0 38px 0 27px; // 内容左 35 × 0.77 / 右 50 × 0.77
|
// 设计稿内容左 x105 → 卡内 x35;行右 x536 → 卡内右 50
|
||||||
|
padding: 0 50px 0 35px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__head {
|
&__head {
|
||||||
@@ -325,14 +327,16 @@ function clearChips(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
font-size: 22px; // 设计 28 × 0.77
|
// 设计 @(105,287) 28/500 #222222
|
||||||
|
font-size: 28px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
line-height: 41px;
|
||||||
color: @cases-filter-title;
|
color: @cases-filter-title;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__link {
|
&__link {
|
||||||
color: @primary-strong;
|
color: @primary-strong;
|
||||||
font-size: @font-size-base;
|
font-size: 16px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: @primary-hover;
|
color: @primary-hover;
|
||||||
@@ -340,33 +344,35 @@ function clearChips(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__group {
|
&__group {
|
||||||
margin-top: @spacing-lg;
|
margin-top: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 标题 → 第一组(设计 11 × 0.77)
|
// 标题 → 第一组(设计 287+41=328 → 业务类型标题 339)
|
||||||
&__head + &__group {
|
&__head + &__group {
|
||||||
margin-top: 9px;
|
margin-top: 11px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__group-title {
|
&__group-title {
|
||||||
font-size: 18px; // 设计 24 × 0.77
|
// 设计 @(105,339) 24/500 #333333
|
||||||
|
font-size: 24px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
line-height: 35px;
|
||||||
color: @cases-text;
|
color: @cases-text;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 业务类型:整行可点,选中整行铺满底色 */
|
/* 业务类型:整行可点(设计行高 50,文案 20/400) */
|
||||||
&__rows {
|
&__rows {
|
||||||
margin-top: 14px; // 设计 19 × 0.77
|
margin-top: 19px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__row {
|
&__row {
|
||||||
.flex(row, flex-start, center);
|
.flex(row, flex-start, center);
|
||||||
gap: 9px; // 设计 12 × 0.77
|
gap: 12px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 38px; // 行距 50 × 0.77
|
height: 50px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
color: @cases-text;
|
color: @cases-text;
|
||||||
font-size: 15px; // 设计 20 × 0.77
|
font-size: 20px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
transition: background 0.2s;
|
transition: background 0.2s;
|
||||||
|
|
||||||
@@ -384,8 +390,8 @@ function clearChips(): void {
|
|||||||
&__box {
|
&__box {
|
||||||
position: relative;
|
position: relative;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 12px; // 设计 16 × 0.77
|
width: 16px;
|
||||||
height: 12px;
|
height: 16px;
|
||||||
border: 1px solid @cases-input-border;
|
border: 1px solid @cases-input-border;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
|
|
||||||
@@ -397,11 +403,11 @@ function clearChips(): void {
|
|||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 1px;
|
top: 1px;
|
||||||
left: 3px;
|
left: 4px;
|
||||||
width: 4px;
|
width: 5px;
|
||||||
height: 6px;
|
height: 8px;
|
||||||
border: solid @white;
|
border: solid @white;
|
||||||
border-width: 0 1.5px 1.5px 0;
|
border-width: 0 2px 2px 0;
|
||||||
transform: rotate(45deg);
|
transform: rotate(45deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -413,31 +419,32 @@ function clearChips(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__row-count {
|
&__row-count {
|
||||||
|
// 设计计数 16/500 #666666
|
||||||
color: @cases-muted;
|
color: @cases-muted;
|
||||||
font-weight: 400;
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 选中行的计数随行文字变蓝、加粗(设计15)
|
|
||||||
.case-filter__row.is-active &__row-count {
|
.case-filter__row.is-active &__row-count {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
font-weight: inherit;
|
font-weight: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 胶囊选项(两列;minmax(0,1fr) 防止内部长内容(日期区间)把列撑宽) */
|
/* 胶囊选项(两列;设计胶囊高 53、行距 24、列距 33) */
|
||||||
&__pills {
|
&__pills {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
gap: 18px 25px; // 行距 24 × 0.77 / 列距 33 × 0.77
|
gap: 24px 33px;
|
||||||
margin-top: 15px; // 设计 21 × 0.77
|
margin-top: 21px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__pill {
|
&__pill {
|
||||||
height: 41px; // 设计 53 × 0.77
|
height: 53px;
|
||||||
padding: 0 @spacing-sm;
|
padding: 0 @spacing-sm;
|
||||||
border-radius: @radius-base;
|
border-radius: @radius-base;
|
||||||
background: @pill-bg;
|
background: @pill-bg;
|
||||||
color: @cases-text;
|
color: @cases-text;
|
||||||
font-size: 15px; // 设计 20 × 0.77
|
font-size: 20px;
|
||||||
.ellipsis();
|
.ellipsis();
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
|
|
||||||
@@ -459,7 +466,7 @@ function clearChips(): void {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
height: 41px;
|
height: 53px;
|
||||||
padding: 0 @spacing-sm;
|
padding: 0 @spacing-sm;
|
||||||
border-radius: @radius-base;
|
border-radius: @radius-base;
|
||||||
background: @input-bg;
|
background: @input-bg;
|
||||||
@@ -478,10 +485,10 @@ function clearChips(): void {
|
|||||||
|
|
||||||
:deep(.el-range-input) {
|
:deep(.el-range-input) {
|
||||||
flex: 0 1 auto;
|
flex: 0 1 auto;
|
||||||
width: 76px;
|
width: 92px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: @cases-text;
|
color: @cases-text;
|
||||||
font-size: 15px;
|
font-size: 20px;
|
||||||
|
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
color: @cases-text;
|
color: @cases-text;
|
||||||
@@ -490,8 +497,8 @@ function clearChips(): void {
|
|||||||
|
|
||||||
:deep(.el-range-separator) {
|
:deep(.el-range-separator) {
|
||||||
color: @cases-text;
|
color: @cases-text;
|
||||||
font-size: 15px;
|
font-size: 20px;
|
||||||
line-height: 41px;
|
line-height: 53px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.is-focus {
|
&.is-focus {
|
||||||
@@ -501,8 +508,8 @@ function clearChips(): void {
|
|||||||
|
|
||||||
/* 已选条件 */
|
/* 已选条件 */
|
||||||
&__selected {
|
&__selected {
|
||||||
margin: 18px 38px 0 27px; // 上距 23 × 0.77,左右与内容对齐(50/35 × 0.77)
|
margin: 23px 50px 0 35px;
|
||||||
padding: 12px 18px 16px; // 设计 16/24/21 × 0.77
|
padding: 16px 24px 21px;
|
||||||
border-radius: @radius-base;
|
border-radius: @radius-base;
|
||||||
background: @chips-panel-bg;
|
background: @chips-panel-bg;
|
||||||
}
|
}
|
||||||
@@ -513,30 +520,30 @@ function clearChips(): void {
|
|||||||
|
|
||||||
&__selected-title {
|
&__selected-title {
|
||||||
color: @cases-text;
|
color: @cases-text;
|
||||||
font-size: 15px; // 设计 20 × 0.77
|
font-size: 20px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__selected-empty {
|
&__selected-empty {
|
||||||
margin-top: @spacing-sm;
|
margin-top: @spacing-sm;
|
||||||
color: @cases-muted;
|
color: @cases-muted;
|
||||||
font-size: @font-size-sm;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__chips {
|
&__chips {
|
||||||
.flex(row, flex-start, center);
|
.flex(row, flex-start, center);
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 11px 10px; // 行距 14 × 0.77 / 列距 13 × 0.77
|
gap: 14px 13px;
|
||||||
margin-top: 12px; // 设计 16 × 0.77
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__chip {
|
&__chip {
|
||||||
height: 25px; // 设计 32 × 0.77
|
height: 32px;
|
||||||
padding: 0 @spacing-sm;
|
padding: 0 @spacing-sm;
|
||||||
border-radius: @radius-round;
|
border-radius: @radius-round;
|
||||||
background: @white;
|
background: @white;
|
||||||
color: @cases-accent;
|
color: @cases-accent;
|
||||||
font-size: 11px; // 设计 14 → 10.8,取可读下限 11
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
transition: color 0.2s;
|
transition: color 0.2s;
|
||||||
|
|
||||||
@@ -548,17 +555,17 @@ function clearChips(): void {
|
|||||||
/* 底部操作 */
|
/* 底部操作 */
|
||||||
&__actions {
|
&__actions {
|
||||||
.flex(row, flex-start, center);
|
.flex(row, flex-start, center);
|
||||||
gap: 10px; // 设计 13 × 0.77
|
gap: 13px;
|
||||||
margin-top: 23px; // 设计 30 × 0.77
|
margin-top: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__reset {
|
&__reset {
|
||||||
width: 104px; // 设计 135 × 0.77
|
width: 135px;
|
||||||
height: 23px; // 设计 30 × 0.77
|
height: 30px;
|
||||||
border-radius: @radius-round;
|
border-radius: @radius-round;
|
||||||
background: @cases-reset-bg;
|
background: @cases-reset-bg;
|
||||||
color: @black;
|
color: @black;
|
||||||
font-size: 11px; // 设计 14 → 10.8,取可读下限 11
|
font-size: 14px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
transition: filter 0.2s;
|
transition: filter 0.2s;
|
||||||
|
|
||||||
@@ -569,11 +576,11 @@ function clearChips(): void {
|
|||||||
|
|
||||||
&__confirm {
|
&__confirm {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 23px;
|
height: 30px;
|
||||||
border-radius: @radius-round;
|
border-radius: @radius-round;
|
||||||
background: @cases-confirm;
|
background: @cases-confirm;
|
||||||
color: @white;
|
color: @white;
|
||||||
font-size: 11px;
|
font-size: 14px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
transition: background 0.2s;
|
transition: background 0.2s;
|
||||||
|
|
||||||
|
|||||||
@@ -41,22 +41,24 @@ const emit = defineEmits<{
|
|||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.cases-grid {
|
.cases-grid {
|
||||||
&__total {
|
&__total {
|
||||||
font-size: 15px; // 设计15 计数 20 × 0.77
|
// 设计计数 20px / 700 / #121212
|
||||||
|
font-size: 20px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: @heading-color;
|
color: @heading-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设计卡片 396×267,列 x606/1030/1454 → 列距 28,行 y423/713 → 行距 23
|
||||||
&__list {
|
&__list {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
gap: 18px 22px; // 设计04 行距 23 / 列距 28 × 0.77
|
gap: 23px 28px;
|
||||||
margin-top: 12px; // 设计15 计数 → 卡片 15 × 0.77
|
margin-top: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__empty {
|
&__empty {
|
||||||
padding: @spacing-xxl 0;
|
padding: @spacing-xxl 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: @font-size-base;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -60,25 +60,27 @@ const keyword = computed<string>({
|
|||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.case-search {
|
.case-search {
|
||||||
.flex(row, flex-start, center);
|
.flex(row, flex-start, center);
|
||||||
gap: 18px; // 按钮间距 24 × 0.77
|
// 设计稿搜索卡 矩形 148317 @(606,272) 1244×128;输入框 @(642,312) 592×49
|
||||||
padding: 30px 36px; // 卡片 128 高、输入 49 高:上下 40 × 0.77 / 左右 47 × 0.77
|
gap: 24px;
|
||||||
|
height: 128px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0 36px;
|
||||||
border-radius: @radius-base;
|
border-radius: @radius-base;
|
||||||
background: @white;
|
background: @white;
|
||||||
|
|
||||||
&__field {
|
&__field {
|
||||||
position: relative;
|
position: relative;
|
||||||
flex: 1;
|
// 设计输入框 592 宽
|
||||||
max-width: 456px; // 设计输入框 592 × 0.77
|
flex: 0 0 592px;
|
||||||
margin-right: 28px; // 与按钮总间距 60 × 0.77(叠加 bar gap 18)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__icon {
|
&__icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
right: 16px; // 设计放大镜在输入框右侧
|
right: 16px;
|
||||||
left: auto;
|
left: auto;
|
||||||
width: 20px; // 设计 27 × 0.77
|
width: 27px;
|
||||||
height: 20px;
|
height: 27px;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
fill: none;
|
fill: none;
|
||||||
stroke: @cases-icon;
|
stroke: @cases-icon;
|
||||||
@@ -87,18 +89,19 @@ const keyword = computed<string>({
|
|||||||
|
|
||||||
&__input {
|
&__input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 38px; // 设计 49 × 0.77
|
height: 49px;
|
||||||
padding: 0 44px 0 16px;
|
padding: 0 52px 0 20px;
|
||||||
border: 1px solid @cases-input-border;
|
border: 1px solid @cases-input-border;
|
||||||
border-radius: @radius-sm;
|
border-radius: @radius-sm;
|
||||||
background: @white;
|
background: @white;
|
||||||
color: @cases-text;
|
color: @cases-text;
|
||||||
font-size: 15px; // 设计 20 × 0.77
|
font-size: 20px;
|
||||||
outline: none;
|
outline: none;
|
||||||
transition: border-color 0.2s;
|
transition: border-color 0.2s;
|
||||||
|
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
color: @cases-input-border; // 设计占位 @cases-input-border
|
// 设计 placeholder @(660,322) 20/400 #999999
|
||||||
|
color: @text-color-disabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
@@ -107,13 +110,15 @@ const keyword = computed<string>({
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__btn {
|
&__btn {
|
||||||
min-width: 77px; // 设计 100 × 0.77
|
// 设计按钮文字 20/400 #FFFFFF,白底蓝字按钮宽 100
|
||||||
height: 38px; // 设计 49 × 0.77
|
min-width: 100px;
|
||||||
|
height: 49px;
|
||||||
padding: 0 @spacing-base;
|
padding: 0 @spacing-base;
|
||||||
border-radius: @radius-sm;
|
border-radius: @radius-sm;
|
||||||
background: @cases-accent; // 设计 重置/搜索 均为 rgba(7,92,255)
|
background: @cases-accent;
|
||||||
color: @white;
|
color: @white;
|
||||||
font-size: 15px; // 设计 20 × 0.77
|
font-size: 20px;
|
||||||
|
font-weight: 400;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@@ -136,7 +141,7 @@ const keyword = computed<string>({
|
|||||||
gap: @spacing-xs;
|
gap: @spacing-xs;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
color: @heading-color;
|
color: @heading-color;
|
||||||
font-size: @font-size-base;
|
font-size: 16px;
|
||||||
transition: color 0.2s;
|
transition: color 0.2s;
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
@@ -150,8 +155,8 @@ const keyword = computed<string>({
|
|||||||
}
|
}
|
||||||
|
|
||||||
&-icon {
|
&-icon {
|
||||||
width: 16px;
|
width: 18px;
|
||||||
height: 16px;
|
height: 18px;
|
||||||
fill: none;
|
fill: none;
|
||||||
stroke: currentColor;
|
stroke: currentColor;
|
||||||
stroke-width: 1.8;
|
stroke-width: 1.8;
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ onMounted(() => {
|
|||||||
<template>
|
<template>
|
||||||
<div class="cases">
|
<div class="cases">
|
||||||
<header class="cases__hero">
|
<header class="cases__hero">
|
||||||
<div class="container">
|
<div class="container cases__hero-inner">
|
||||||
<h1 class="cases__title">
|
<h1 class="cases__title">
|
||||||
成功案例库
|
成功案例库
|
||||||
</h1>
|
</h1>
|
||||||
@@ -111,37 +111,50 @@ onMounted(() => {
|
|||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.cases {
|
.cases {
|
||||||
padding-bottom: 60px; // 设计04 正文 → 页脚 78 × 0.77
|
// 设计实测:页脚顶 y1811,正文最底约 y1733 → 底距 78
|
||||||
|
padding-bottom: 78px;
|
||||||
|
|
||||||
|
// 设计稿页头条 矩形 148284 @(0,73) 1920×179 白底通栏;文案左缩进 112
|
||||||
&__hero {
|
&__hero {
|
||||||
padding: 25px 0 33px; // 标题上 32 / 标语下 43 × 0.77
|
height: 179px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding-top: 32px; // 标题 @(112,105) → 73+32
|
||||||
background: @white;
|
background: @white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 页头文案与内容区不对齐:设计实测文案在 x112(内容区 x70 + 42)
|
||||||
|
&__hero-inner {
|
||||||
|
padding-left: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
font-size: @font-size-xxl; // 设计 36 × 0.77 ≈ 28
|
margin: 0;
|
||||||
|
font-size: 36px; // 设计 @(112,105) 36/700 #121212
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
line-height: 52px;
|
||||||
color: @cases-hero-title;
|
color: @cases-hero-title;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__slogan {
|
&__slogan {
|
||||||
margin-top: 12px; // 设计 15 × 0.77
|
margin: 15px 0 0; // 副标题 @(112,172),标题底 105+52=157 → 间距 15
|
||||||
font-size: 18.5px; // 设计 24 × 0.77
|
font-size: 24px; // 设计 24/500 #666666
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
line-height: 37px;
|
||||||
color: @cases-muted;
|
color: @cases-muted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设计稿:筛选面板 @(70,272) 516 宽,卡片区 @(606,423) 起 1244 宽,主区间距 20
|
||||||
&__body {
|
&__body {
|
||||||
.flex(row, flex-start, flex-start);
|
.flex(row, flex-start, flex-start);
|
||||||
gap: @spacing-base;
|
gap: 20px;
|
||||||
padding-top: @spacing-base;
|
padding-top: 20px; // 筛选面板顶 272 − 页头条底(73+179) = 20
|
||||||
}
|
}
|
||||||
|
|
||||||
&__main {
|
&__main {
|
||||||
.flex(column, flex-start);
|
.flex(column, flex-start);
|
||||||
flex: 1;
|
flex: 1;
|
||||||
gap: 18px; // 设计04 搜索卡 → 卡片网格 23 × 0.77
|
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
gap: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__pager {
|
&__pager {
|
||||||
|
|||||||
@@ -25,62 +25,61 @@ const SIGN_VARIANTS: Record<SignStatus, 'primary' | 'success' | 'warning' | 'dan
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="contract-info">
|
<div class="contract-info">
|
||||||
<!-- 只读信息网格 -->
|
<!-- 区块 01:合同信息(相对卡顶 y36) -->
|
||||||
<div class="contract-info__grid">
|
<h3 class="contract-info__section contract-info__section--1">
|
||||||
<div class="contract-info__field">
|
<span class="contract-info__step">01</span>
|
||||||
<span class="contract-info__label text-secondary">合同名称</span>
|
<span class="contract-info__section-title">合同信息</span>
|
||||||
|
</h3>
|
||||||
|
<div class="contract-info__field contract-info__field--1 contract-info__field--l">
|
||||||
|
<span class="contract-info__label">项目名称</span>
|
||||||
<span class="contract-info__value">{{ contract.contractName }}</span>
|
<span class="contract-info__value">{{ contract.contractName }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="contract-info__field">
|
<div class="contract-info__field contract-info__field--1 contract-info__field--r">
|
||||||
<span class="contract-info__label text-secondary">合同类型</span>
|
<span class="contract-info__label">合同类型</span>
|
||||||
<span class="contract-info__value">{{ contract.contractType }}</span>
|
<span class="contract-info__value">{{ contract.contractType }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="contract-info__field">
|
<div class="contract-info__field contract-info__field--2 contract-info__field--l">
|
||||||
<span class="contract-info__label text-secondary">合同编号</span>
|
<span class="contract-info__label">合同编号</span>
|
||||||
<span class="contract-info__value">{{ contract.contractNo }}</span>
|
<span class="contract-info__value">{{ contract.contractNo }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="contract-info__field">
|
<div class="contract-info__field contract-info__field--2 contract-info__field--r">
|
||||||
<span class="contract-info__label text-secondary">签订日期</span>
|
<span class="contract-info__label">签订日期</span>
|
||||||
<span class="contract-info__value">{{ contract.signDate || '—' }}</span>
|
<span class="contract-info__value">{{ contract.signDate || '—' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="contract-info__field">
|
<div class="contract-info__field contract-info__field--3 contract-info__field--l">
|
||||||
<span class="contract-info__label text-secondary">生效日期</span>
|
<span class="contract-info__label">生效日期</span>
|
||||||
<span class="contract-info__value">{{ contract.effectiveDate }}</span>
|
<span class="contract-info__value">{{ contract.effectiveDate }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="contract-info__field">
|
<div class="contract-info__field contract-info__field--3 contract-info__field--r">
|
||||||
<span class="contract-info__label text-secondary">到期日期</span>
|
<span class="contract-info__label">到期日期</span>
|
||||||
<span class="contract-info__value">{{ contract.expiryDate }}</span>
|
<span class="contract-info__value">{{ contract.expiryDate }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="contract-info__field">
|
<div class="contract-info__field contract-info__field--4 contract-info__field--l">
|
||||||
<span class="contract-info__label text-secondary">合同状态</span>
|
<span class="contract-info__label">合同状态</span>
|
||||||
<StatusTag :label="contract.signStatus" :variant="SIGN_VARIANTS[contract.signStatus]" />
|
<StatusTag :label="contract.signStatus" :variant="SIGN_VARIANTS[contract.signStatus]" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 关联项目 -->
|
<!-- 区块 02:关联项目(相对卡顶 y649) -->
|
||||||
<div class="contract-info__related">
|
<h3 class="contract-info__section contract-info__section--2">
|
||||||
<h3 class="contract-info__section-title">
|
<span class="contract-info__step">02</span>
|
||||||
关联项目
|
<span class="contract-info__section-title">关联项目</span>
|
||||||
</h3>
|
</h3>
|
||||||
<div class="contract-info__related-body">
|
<div class="contract-info__field contract-info__field--5 contract-info__field--l">
|
||||||
<div class="contract-info__field">
|
<span class="contract-info__label">
|
||||||
<span class="contract-info__label text-secondary">
|
|
||||||
项目名称
|
项目名称
|
||||||
<button class="contract-info__link" @click="emit('viewProject')">查看详情</button>
|
<button class="contract-info__link" @click="emit('viewProject')">查看详情</button>
|
||||||
</span>
|
</span>
|
||||||
<span class="contract-info__value">{{ contract.relatedProjectName }}</span>
|
<span class="contract-info__value">{{ contract.relatedProjectName }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="contract-info__field">
|
<div class="contract-info__field contract-info__field--5 contract-info__field--r">
|
||||||
<span class="contract-info__label text-secondary">项目编号</span>
|
<span class="contract-info__label">项目编号</span>
|
||||||
<span class="contract-info__value">{{ contract.projectNo }}</span>
|
<span class="contract-info__value">{{ contract.projectNo }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 合同文件 -->
|
<!-- 区块 03:合同文件(相对卡顶 y901) -->
|
||||||
<div class="contract-info__files-section">
|
<h3 class="contract-info__section contract-info__section--3">
|
||||||
<h3 class="contract-info__section-title">
|
<span class="contract-info__step">03</span>
|
||||||
合同文件
|
<span class="contract-info__section-title">合同文件</span>
|
||||||
</h3>
|
</h3>
|
||||||
<ul class="contract-info__files">
|
<ul class="contract-info__files">
|
||||||
<li
|
<li
|
||||||
@@ -90,7 +89,7 @@ const SIGN_VARIANTS: Record<SignStatus, 'primary' | 'success' | 'warning' | 'dan
|
|||||||
>
|
>
|
||||||
<div class="contract-info__file-info">
|
<div class="contract-info__file-info">
|
||||||
<span class="contract-info__file-name" :title="file.name">{{ file.name }}</span>
|
<span class="contract-info__file-name" :title="file.name">{{ file.name }}</span>
|
||||||
<span class="contract-info__file-meta text-secondary">
|
<span class="contract-info__file-meta">
|
||||||
{{ formatFileSize(file.size) }} · {{ file.uploadTime }}
|
{{ formatFileSize(file.size) }} · {{ file.uploadTime }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -103,99 +102,132 @@ const SIGN_VARIANTS: Record<SignStatus, 'primary' | 'success' | 'warning' | 'dan
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="contract.files.length === 0" class="contract-info__empty text-secondary">
|
<li v-if="contract.files.length === 0" class="contract-info__empty">
|
||||||
暂无合同文件
|
暂无合同文件
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
// 合同详情信息区(设计 40:灰底页面直排、标签 + 灰底值框、蓝竖条区块标题)
|
// 合同详情信息区(设计 e6c3a361,1920 画布 1:1 绝对定位)
|
||||||
|
// 白卡 (70,152) 1780×1152;卡内三行「步骤区」相对卡顶:
|
||||||
|
// 区块 01 合同信息 y36(步骤号 ink 188、区块名 211)、首标签 283、值框 326
|
||||||
|
// 区块 02 关联项目 y649(步骤号 801、区块名 824)、首标签 901、值框 944
|
||||||
|
// 区块 03 合同文件 y901(步骤号 1053、区块名 1076)、文件行 1153
|
||||||
|
// 值框 764×49 @x134/1010(卡内左 64 / 940),描边 #999999、圆角 8、底 #F4F6F8
|
||||||
|
// 行距 122(标签 283 → 405 → 527 → 649);列间距 112(776 → 940 = 164 − 764)
|
||||||
.contract-info {
|
.contract-info {
|
||||||
.flex(column, flex-start, stretch);
|
|
||||||
gap: 24px;
|
|
||||||
|
|
||||||
&__grid,
|
|
||||||
&__related-body {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
column-gap: 46px; // 列间距 60 设计 × 0.77
|
|
||||||
row-gap: 15px; // 行间距 20 设计 × 0.77
|
|
||||||
}
|
|
||||||
|
|
||||||
&__field {
|
|
||||||
.flex(column, flex-start, stretch);
|
|
||||||
gap: 6px; // 标签 → 值框(设计 8 × 0.77)
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__label {
|
|
||||||
font-size: 11px; // 设计 13 → 可读下限
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 1.4;
|
|
||||||
color: @heading-color; // 设计 @heading-color(覆盖 .text-secondary)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 值框:设计 40 实取 #F3F4F6 底 + 浅描边,高 38 设计 × 0.77
|
|
||||||
&__value {
|
|
||||||
width: 100%;
|
|
||||||
height: 29px;
|
|
||||||
padding: 0 10px;
|
|
||||||
border: 1px solid @border-color;
|
|
||||||
border-radius: @radius-sm;
|
|
||||||
background: @input-bg;
|
|
||||||
font-size: 11px;
|
|
||||||
line-height: 27px;
|
|
||||||
color: @text-color; // 设计 @text-color
|
|
||||||
.ellipsis();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 合同状态(StatusTag)同样入灰底值框,去彩色胶囊
|
|
||||||
&__field :deep(.status-tag) {
|
|
||||||
width: 100%;
|
|
||||||
height: 29px;
|
|
||||||
padding: 0 10px;
|
|
||||||
border: 1px solid @border-color;
|
|
||||||
border-radius: @radius-sm;
|
|
||||||
background: @input-bg;
|
|
||||||
font-size: 11px;
|
|
||||||
line-height: 27px;
|
|
||||||
color: @text-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 区块标题:蓝色竖条(设计 40 实取 4×17 设计px #024FFF)+ 15 设计px 半粗
|
|
||||||
&__section-title {
|
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-left: 10px;
|
width: 1780px;
|
||||||
margin-bottom: 14px; // 设计 18 × 0.77
|
height: 1152px;
|
||||||
font-size: @font-size-sm; // 12px = 设计 15 × 0.77
|
margin-top: 21px; // 面包屑盒底 131(72+32+27)→ 卡顶 152
|
||||||
font-weight: 700;
|
border-radius: @radius-lg;
|
||||||
line-height: 16px;
|
background: @white;
|
||||||
color: @heading-color;
|
box-sizing: border-box;
|
||||||
|
|
||||||
&::before {
|
// 区块(步骤号 + 区块名)—— 绝对定位,四个区块
|
||||||
content: '';
|
&__section {
|
||||||
|
position: absolute;
|
||||||
|
left: 43px; // 步骤号 x124 = 70 + 43(123)→ 区块行内相对 11 → 113 ✓
|
||||||
|
top: 0;
|
||||||
|
height: 0;
|
||||||
|
|
||||||
|
&--1 { top: 36px; }
|
||||||
|
&--2 { top: 649px; }
|
||||||
|
&--3 { top: 901px; }
|
||||||
|
}
|
||||||
|
|
||||||
|
&__section-title {
|
||||||
|
position: absolute;
|
||||||
|
left: 60px; // 区块名 ink x184 = 卡内 114 = 行 left 43 + 60
|
||||||
|
margin: 0;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 41px;
|
||||||
|
color: @contract-cell-text;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 步骤号 01/02/03:48/500 #024FFF。absolute 定到区块名左侧 ——
|
||||||
|
// 区块行 left 43,区块名 ink 相对行左 60(= 184 − 124);步骤号 ink 落 0
|
||||||
|
&__step {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
width: 4px;
|
font-size: 48px;
|
||||||
height: 15px;
|
font-weight: 500;
|
||||||
border-radius: @radius-round;
|
line-height: 70px;
|
||||||
background: @primary-strong;
|
color: @project-accent;
|
||||||
|
white-space: nowrap;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 字段(标签 + 值框)—— 绝对定位到卡面,行间距 122
|
||||||
|
&__field {
|
||||||
|
.flex(column, flex-start, stretch);
|
||||||
|
position: absolute;
|
||||||
|
width: 764px;
|
||||||
|
gap: 14px; // 标签盒底 312 → 值框顶 326
|
||||||
}
|
}
|
||||||
|
|
||||||
&__files-section {
|
// 值框顶(相对卡顶 = 设计 y − 152):326/448/570/692/944
|
||||||
margin-top: 6px; // 区块间距合计 30(设计 40 × 0.77)
|
&__field--1 { top: 174px; }
|
||||||
|
&__field--2 { top: 296px; }
|
||||||
|
&__field--3 { top: 418px; }
|
||||||
|
&__field--4 { top: 540px; }
|
||||||
|
&__field--5 { top: 770px; } // 标签 901 − 109(label 行盒 29 + gap 14 = 43 补偿)
|
||||||
|
|
||||||
|
// 第 5 行(关联项目)标签需落设计 y901 → 卡内 749,而 --5 槽位顶 770 是给
|
||||||
|
// 「合同状态」这类值框直排的字段用的;带标签的行统一再抬 21
|
||||||
|
&__field--5:has(&__label) { top: 749px; }
|
||||||
|
|
||||||
|
// 左右列:值框 x134 / x1010 → 卡内左 64 / 940
|
||||||
|
&__field--l { left: 64px; }
|
||||||
|
&__field--r { left: 940px; }
|
||||||
|
|
||||||
|
// 标签 20/500 #333
|
||||||
|
&__label {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 29px;
|
||||||
|
color: @contract-cell-text;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 值框:764×49、#F4F6F8 底、1px #999999、圆角 8;文字内缩 18
|
||||||
|
&__value {
|
||||||
|
.flex(row, flex-start, center);
|
||||||
|
width: 100%;
|
||||||
|
height: 49px;
|
||||||
|
padding: 0 18px;
|
||||||
|
border: 1px solid @contract-line;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: @contract-page-bg;
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 29px;
|
||||||
|
color: @contract-cell-text;
|
||||||
|
box-sizing: border-box;
|
||||||
|
.ellipsis();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 合同状态(StatusTag)同样入值框,去彩色胶囊
|
||||||
|
&__field :deep(.status-tag) {
|
||||||
|
.flex(row, flex-start, center);
|
||||||
|
width: 100%;
|
||||||
|
height: 49px;
|
||||||
|
padding: 0 18px;
|
||||||
|
border: 1px solid @contract-line;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: @contract-page-bg;
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 29px;
|
||||||
|
color: @contract-cell-text;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__link {
|
&__link {
|
||||||
font-size: 11px; // 设计 10 → 可读下限
|
font-size: 18px;
|
||||||
font-weight: 500;
|
font-weight: 400;
|
||||||
color: @primary-strong;
|
color: @contract-accent;
|
||||||
transition: opacity 0.2s;
|
transition: opacity 0.2s;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@@ -203,54 +235,64 @@ const SIGN_VARIANTS: Record<SignStatus, 'primary' | 'success' | 'warning' | 'dan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 40:「项目名称 查看详情」同一行内联
|
|
||||||
&__label &__link {
|
&__label &__link {
|
||||||
margin-left: 3px;
|
margin-left: 10px;
|
||||||
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── 合同文件:通栏 1640×90 @(124,1153),底 #EDEDED ──
|
||||||
&__files {
|
&__files {
|
||||||
.flex(column, flex-start, stretch);
|
.flex(column, flex-start, stretch);
|
||||||
gap: @spacing-sm;
|
position: absolute;
|
||||||
|
top: 1001px; // 1153 − 152
|
||||||
|
left: 54px; // 124 − 70
|
||||||
|
width: 1640px;
|
||||||
|
gap: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 文件行:设计 40 浅灰通栏底(#F7F8FA),无描边
|
|
||||||
&__file {
|
&__file {
|
||||||
.flex(row, space-between);
|
.flex(row, space-between, center);
|
||||||
gap: @spacing-base;
|
gap: @spacing-base;
|
||||||
min-height: 42px; // 通栏高 54 设计 × 0.77
|
height: 90px;
|
||||||
padding: 7px 16px;
|
padding: 0 30px;
|
||||||
border: none;
|
background: @contract-table-head-bg;
|
||||||
border-radius: @radius-base;
|
box-sizing: border-box;
|
||||||
background: @finance-row-bg;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__file-info {
|
&__file-info {
|
||||||
.flex(column, flex-start, stretch);
|
.flex(column, flex-start, stretch);
|
||||||
gap: 2px;
|
gap: 4px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__file-name {
|
&__file-name {
|
||||||
font-size: 11px;
|
font-size: 20px;
|
||||||
line-height: 1.3;
|
line-height: 29px;
|
||||||
color: @heading-color; // 设计 @heading-color
|
color: @contract-cell-text;
|
||||||
.ellipsis();
|
.ellipsis();
|
||||||
}
|
}
|
||||||
|
|
||||||
&__file-meta {
|
&__file-meta {
|
||||||
font-size: 11px; // 设计 11 → 可读下限
|
font-size: 14px;
|
||||||
line-height: 1.3;
|
line-height: 20px;
|
||||||
color: @text-color-disabled; // 设计 @text-color-disabled
|
color: @contract-muted;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__file-actions {
|
&__file-actions {
|
||||||
.flex(row, center);
|
.flex(row, center);
|
||||||
gap: @spacing-sm;
|
gap: @spacing-base;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__empty {
|
&__empty {
|
||||||
font-size: @font-size-base;
|
.flex(row, flex-start, center);
|
||||||
|
height: 90px;
|
||||||
|
padding: 0 30px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: @contract-muted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -83,21 +83,21 @@ const SIGN_VARIANTS: Record<SignStatus, 'primary' | 'success' | 'warning' | 'dan
|
|||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
.scrollbar();
|
.scrollbar();
|
||||||
|
|
||||||
// 表格本体(设计 46:外框 1px #999、灰表头、横分隔线、无竖线、无卡面——卡面在页面层)
|
// 表格本体(设计 31ee32fc:1654×740,外框 1px #999、灰表头 64、行高 97、无竖线)
|
||||||
&__grid {
|
&__grid {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border: 1px solid @contract-line;
|
border: 1px solid @contract-line;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
font-size: 15px; // 设计 20 × 0.77
|
font-size: 20px;
|
||||||
|
|
||||||
th,
|
th,
|
||||||
td {
|
td {
|
||||||
padding: 8px 10px;
|
padding: 0 10px;
|
||||||
border-bottom: 1px solid @contract-line;
|
border-bottom: 1px solid @contract-line;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
font-size: 15px; // 设计 20 × 0.77
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 长名称列按设计稿换行显示(设计首行双行文本)
|
// 长名称列按设计稿换行显示(设计首行双行文本)
|
||||||
@@ -107,18 +107,19 @@ const SIGN_VARIANTS: Record<SignStatus, 'primary' | 'success' | 'warning' | 'dan
|
|||||||
}
|
}
|
||||||
|
|
||||||
th {
|
th {
|
||||||
height: 49px; // 设计 64 × 0.77
|
height: 64px;
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
background: @contract-table-head-bg;
|
background: @contract-table-head-bg;
|
||||||
color: @contract-head-text;
|
color: @contract-head-text;
|
||||||
font-weight: 400; // 设计 400
|
font-weight: 400;
|
||||||
text-align: center; // 表头居中(设计实测)
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
td {
|
td {
|
||||||
height: 75px; // 行高 97 设计 × 0.77
|
height: 96px; // 设计行距 475→571
|
||||||
color: @contract-cell-text;
|
color: @contract-cell-text;
|
||||||
|
line-height: 29px;
|
||||||
}
|
}
|
||||||
|
|
||||||
tbody tr:last-child td {
|
tbody tr:last-child td {
|
||||||
|
|||||||
@@ -78,35 +78,36 @@ onMounted(load)
|
|||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.contract-detail {
|
.contract-detail {
|
||||||
padding-top: @spacing-lg;
|
|
||||||
padding-bottom: @spacing-xxl;
|
padding-bottom: @spacing-xxl;
|
||||||
|
|
||||||
// 内容列(面包屑 → 信息卡 → 返回):区块间距 24(设计 26~30 × 0.77)
|
// 内容列(面包屑 → 信息卡):卡自带 80 上距,这里不再叠加 gap
|
||||||
.container {
|
.container {
|
||||||
.flex(column, flex-start, stretch);
|
.flex(column, flex-start, stretch);
|
||||||
gap: @spacing-lg;
|
gap: 0;
|
||||||
}
|
}
|
||||||
|
// 面包屑(设计 e6c3a361:(70,104) 24/500 #666666;顶栏 72 → 行盒顶 104)
|
||||||
// 面包屑(设计 45:24 设计px × 0.77、#666666、字重 500)
|
|
||||||
&__breadcrumb {
|
&__breadcrumb {
|
||||||
font-size: 18px;
|
padding-top: 32px;
|
||||||
|
font-size: 24px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
line-height: 27px;
|
||||||
color: @contract-muted;
|
color: @contract-muted;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__footer {
|
&__footer {
|
||||||
.flex(row, flex-end);
|
.flex(row, flex-end);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
margin-top: 30px;
|
||||||
|
|
||||||
// 「返回」按钮(设计 40/39:#024FFF 实底、白字、小圆角)
|
// 「返回」按钮
|
||||||
:deep(.app-button) {
|
:deep(.app-button) {
|
||||||
height: 30px;
|
height: 40px;
|
||||||
padding: 0 24px; // 按钮宽约 90 设计 × 0.77
|
padding: 0 32px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: @radius-sm;
|
border-radius: @radius-base;
|
||||||
background: @primary-strong;
|
background: @primary-strong;
|
||||||
color: @white;
|
color: @white;
|
||||||
font-size: 11px; // 设计 13 → 可读下限
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.app-button:hover) {
|
:deep(.app-button:hover) {
|
||||||
|
|||||||
@@ -86,43 +86,44 @@ onMounted(() => {
|
|||||||
.contract {
|
.contract {
|
||||||
padding-bottom: @spacing-xxl;
|
padding-bottom: @spacing-xxl;
|
||||||
|
|
||||||
// 横幅:设计 41/46 实测 y72..251(179 设计px → 138 CSS)白底通栏
|
// 横幅:设计 31ee32fc y72..251(1920×179)白底通栏
|
||||||
|
// 标题盒顶 96(ink 104)、副标语盒顶 161.5(ink 171)、横幅底 251
|
||||||
&__head {
|
&__head {
|
||||||
padding: 25px 0 33px;
|
padding: 24px 0 52px;
|
||||||
background: @white;
|
background: @white;
|
||||||
|
|
||||||
// 标题/副标语相对内容区左缩进 42 设计px × 0.77 ≈ 32(容器自带 16)
|
// 标题/副标语落 x112 → 内容区左缘 70 + 42
|
||||||
.container {
|
.container {
|
||||||
padding-left: 48px;
|
padding-left: 42px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
font-size: @font-size-xxl; // 28px = 设计 36 × 0.77
|
font-size: 36px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 1.4;
|
line-height: 52px;
|
||||||
color: @home-ink; // 复用同值 token(横幅标题设计实取 @contract-ink)
|
color: @contract-ink;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__slogan {
|
&__slogan {
|
||||||
margin-top: 12px; // 设计 15 × 0.77
|
margin-top: 13.5px;
|
||||||
font-size: 18px; // 设计 24 × 0.77
|
font-size: 24px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
line-height: 1.5;
|
line-height: 37px;
|
||||||
color: @contract-muted;
|
color: @contract-muted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 内容白卡(设计 46:x70..1850 圆角白卡,无阴影)——显式 .contract 前缀抬高覆写优先级
|
// 内容白卡(设计 31ee32fc:x70 y271 1780×992 圆角白卡,无阴影)
|
||||||
.contract .contract__card {
|
.contract .contract__card {
|
||||||
margin-top: 15px; // 横幅底 → 卡片顶:设计 20 × 0.77
|
margin-top: 20px; // 横幅底 251 → 卡顶 271
|
||||||
padding: 40px 48px 44px; // 卡片内缩:设计 51/64/59 × 0.77
|
padding: 51px 64px 59px; // 搜索框 x134 = 70+64;表格 x135
|
||||||
border-radius: @radius-lg;
|
border-radius: @radius-lg;
|
||||||
background: @white;
|
background: @white;
|
||||||
|
|
||||||
// 搜索行并入卡片:去掉 FilterPanel 自带的卡面,按设计 41 重排控件
|
// 搜索行并入卡片:去掉 FilterPanel 自带的卡面
|
||||||
:deep(.filter-panel) {
|
:deep(.filter-panel) {
|
||||||
margin-bottom: 30px; // 搜索行 → 表格:设计 39 × 0.77
|
margin-bottom: 39px; // 搜索框底 371 → 表头 411
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
@@ -134,16 +135,30 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
:deep(.filter-panel__bar) {
|
:deep(.filter-panel__bar) {
|
||||||
gap: 6px;
|
gap: 8px;
|
||||||
|
|
||||||
|
// 设计:重置 x816、搜索 x940。搜索框右缘 726 →
|
||||||
|
// 重置 816(margin 90 − 12 padding = 78)、搜索 940(margin 52 − 12 = 40)
|
||||||
|
.filter-panel__btn {
|
||||||
|
margin-left: 78px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filter-panel__btn--primary {
|
||||||
|
margin-left: 40px;
|
||||||
|
} }
|
||||||
|
|
||||||
|
// 搜索框 134,322 592×49;占位 20/400 #999999
|
||||||
:deep(.filter-panel__input) {
|
:deep(.filter-panel__input) {
|
||||||
flex: 1;
|
flex: 0 0 592px;
|
||||||
width: auto;
|
width: 592px;
|
||||||
height: 30px; // 设计 38 × 0.77
|
max-width: none;
|
||||||
|
margin-right: 0;
|
||||||
|
height: 49px;
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
|
border-radius: 0;
|
||||||
background: @input-bg;
|
background: @input-bg;
|
||||||
font-size: 11px; // 设计 13 → 可读下限
|
font-size: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.filter-panel__input::placeholder) {
|
:deep(.filter-panel__input::placeholder) {
|
||||||
@@ -154,15 +169,17 @@ onMounted(() => {
|
|||||||
border-color: @primary-strong;
|
border-color: @primary-strong;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 41 按钮序为「重置 | 搜索」,组件内 DOM 序相反,用 order 对齐
|
// 设计按钮:重置 x816、搜索 x940,均 20/400 纯文字(DOM 序相反,用 order 对齐)
|
||||||
:deep(.filter-panel__btn) {
|
:deep(.filter-panel__btn) {
|
||||||
order: 1;
|
order: 1;
|
||||||
height: 30px;
|
min-width: 0;
|
||||||
padding: 0 16px;
|
height: 29px;
|
||||||
|
padding: 0 12px;
|
||||||
border: none;
|
border: none;
|
||||||
background: @contract-reset-bg;
|
border-radius: 0;
|
||||||
color: @text-color;
|
background: transparent;
|
||||||
font-size: 11px; // 设计 12 → 可读下限
|
color: @contract-placeholder;
|
||||||
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.filter-panel__btn:hover) {
|
:deep(.filter-panel__btn:hover) {
|
||||||
@@ -172,32 +189,33 @@ onMounted(() => {
|
|||||||
|
|
||||||
:deep(.filter-panel__btn--primary) {
|
:deep(.filter-panel__btn--primary) {
|
||||||
order: 2;
|
order: 2;
|
||||||
background: @primary-strong;
|
border-radius: 0;
|
||||||
color: @white;
|
background: transparent;
|
||||||
|
color: @contract-accent;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.filter-panel__btn--primary:hover) {
|
:deep(.filter-panel__btn--primary:hover) {
|
||||||
background: @primary-color;
|
background: transparent;
|
||||||
color: @white;
|
color: @primary-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分页(设计 46:右对齐、方角小页码、当前页实底蓝)
|
// 分页(设计:右对齐,页码 ink 1180/1181,`<上一页` x1388、`下一页>` x1740)
|
||||||
:deep(.pagination) {
|
:deep(.pagination) {
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
gap: 8px; // 设计 10 × 0.77
|
gap: 10px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin-top: 22px; // 表格底 → 分页:设计 28 × 0.77
|
margin-top: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.pagination__btn) {
|
:deep(.pagination__btn) {
|
||||||
min-width: 31px; // 设计 40 × 0.77
|
min-width: 40px;
|
||||||
height: 20px; // 设计 26 × 0.77
|
height: 26px;
|
||||||
padding: 0 4px;
|
padding: 0 4px;
|
||||||
border: 1px solid @contract-line;
|
border: 1px solid @contract-line;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
background: @white;
|
background: @white;
|
||||||
color: @contract-line;
|
color: @contract-line;
|
||||||
font-size: 11px; // 设计 14 → 可读下限
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.pagination__btn:hover:not(:disabled):not(.is-ellipsis)) {
|
:deep(.pagination__btn:hover:not(:disabled):not(.is-ellipsis)) {
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ const emit = defineEmits<{
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
// 设计稿底部操作条(浮于页面之上,展示提示文案 + 三个 232×63 按钮)
|
||||||
.demand-bar {
|
.demand-bar {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 0;
|
right: 0;
|
||||||
@@ -63,30 +64,31 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
&__inner {
|
&__inner {
|
||||||
.flex(row, space-between, center);
|
.flex(row, space-between, center);
|
||||||
height: 72px;
|
height: 104px; // 设计稿按 63 高按钮 + 上下留白推得
|
||||||
// 覆盖 .container 内缩:左 = 容器16 + 卡左内边距49 + 序号列46 = 111(对齐内容列),右 = 16 + 40
|
// 覆盖 .container 内缩:左 = 70 + 170(提示文案 x240)、右 = 70 + 197(末按钮右缘 1799)
|
||||||
gap: @spacing-base;
|
gap: @spacing-base;
|
||||||
padding-left: 111px;
|
padding-left: 170px;
|
||||||
padding-right: 56px;
|
padding-right: 121px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__tip {
|
&__tip {
|
||||||
.flex(row, flex-start, center);
|
.flex(row, flex-start, center);
|
||||||
gap: @spacing-sm;
|
gap: 10px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
font-size: 15px;
|
font-size: 20px; // 设计稿 20/500 #333333
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
line-height: 29px;
|
||||||
color: @demand-heading;
|
color: @demand-heading;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__info {
|
&__info {
|
||||||
.flex(row, center, center);
|
.flex(row, center, center);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 16px;
|
width: 26px;
|
||||||
height: 16px;
|
height: 26px;
|
||||||
border: 1px solid @demand-accent;
|
border: 1px solid @demand-accent;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
font-size: 11px;
|
font-size: 16px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: @demand-accent;
|
color: @demand-accent;
|
||||||
@@ -94,20 +96,20 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
&__actions {
|
&__actions {
|
||||||
.flex(row, flex-end, center);
|
.flex(row, flex-end, center);
|
||||||
// 设计稿按钮 232×63 设计px ≈ 179×48,间距 47 设计px ≈ 36px
|
// 设计稿按钮中心 x1125 / 1404 / 1683 → 间距 47(按钮 232 宽)
|
||||||
gap: 36px;
|
gap: 47px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计稿三个按钮均为实底蓝 #075CFF + 白字
|
// 设计稿三个按钮均为实底蓝 #075CFF + 白字,232×63
|
||||||
&__btn {
|
&__btn {
|
||||||
width: 179px;
|
width: 232px;
|
||||||
height: 48px;
|
height: 63px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: @radius-base;
|
border-radius: @radius-base;
|
||||||
background: @demand-secondary-bg;
|
background: @demand-secondary-bg;
|
||||||
font-size: 15px;
|
font-size: 20px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: @white;
|
color: @white;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -123,8 +125,8 @@ const emit = defineEmits<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
&--primary {
|
&--primary {
|
||||||
// 设计稿三个按钮等宽(232 设计px ≈ 179px),主按钮不额外加宽
|
// 设计稿三个按钮等宽(232),主按钮不额外加宽
|
||||||
min-width: 179px;
|
min-width: 232px;
|
||||||
background: @demand-secondary-bg;
|
background: @demand-secondary-bg;
|
||||||
color: @white;
|
color: @white;
|
||||||
|
|
||||||
|
|||||||
@@ -208,34 +208,39 @@ function onDownload(file: DemandAttachment): void {
|
|||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.demand-attach {
|
.demand-attach {
|
||||||
.flex(column, flex-start, stretch);
|
.flex(column, flex-start, stretch);
|
||||||
gap: 11px;
|
gap: 14px;
|
||||||
|
|
||||||
&__head {
|
&__head {
|
||||||
.flex(row, flex-start, center);
|
.flex(row, flex-start, center);
|
||||||
gap: 15px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__label {
|
&__label {
|
||||||
font-size: 15px;
|
flex-shrink: 0;
|
||||||
|
font-size: 20px; // 设计稿 20/500 @(194,1376)
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
line-height: 29px;
|
||||||
color: @demand-heading;
|
color: @demand-heading;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设计稿:提示文案 @(294,1379) 16/400,起点 = 标签列 80 + 20
|
||||||
&__hint {
|
&__hint {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
font-size: @font-size-sm;
|
margin-left: 20px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 400;
|
||||||
color: @demand-line;
|
color: @demand-line;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设计稿该行未标注按钮,按钮设计稿外置(沿用站内次级按钮规格)
|
||||||
&__pick {
|
&__pick {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
height: 28px;
|
height: 40px;
|
||||||
padding: 0 @spacing-base;
|
padding: 0 @spacing-lg;
|
||||||
border: 1px solid @demand-line;
|
border: 1px solid @demand-line;
|
||||||
border-radius: @radius-sm;
|
border-radius: @radius-sm;
|
||||||
background: @white;
|
background: @white;
|
||||||
font-size: @font-size-sm;
|
font-size: 16px;
|
||||||
color: @demand-line;
|
color: @demand-line;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: border-color 0.2s, color 0.2s;
|
transition: border-color 0.2s, color 0.2s;
|
||||||
@@ -257,19 +262,19 @@ function onDownload(file: DemandAttachment): void {
|
|||||||
|
|
||||||
&__list {
|
&__list {
|
||||||
.flex(column, flex-start, stretch);
|
.flex(column, flex-start, stretch);
|
||||||
// 设计稿:两条 90 设计px 灰条,间距 20 设计px ≈ 15px
|
// 设计稿:两条 90 高灰条,间距 20
|
||||||
gap: 15px;
|
gap: 20px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计稿附件条:实底 #EDEDED、无描边、90 设计px ≈ 69px 高
|
// 设计稿附件条:实底 #EDEDED、无描边、90 高
|
||||||
&__item {
|
&__item {
|
||||||
.flex(row, flex-start, center);
|
.flex(row, flex-start, center);
|
||||||
gap: @spacing-base;
|
gap: @spacing-base;
|
||||||
min-height: 69px;
|
min-height: 90px;
|
||||||
padding: @spacing-base;
|
padding: 0 @spacing-lg;
|
||||||
border-radius: @radius-sm;
|
border-radius: @radius-sm;
|
||||||
background: @demand-upload-bg;
|
background: @demand-upload-bg;
|
||||||
}
|
}
|
||||||
@@ -277,10 +282,10 @@ function onDownload(file: DemandAttachment): void {
|
|||||||
&__badge {
|
&__badge {
|
||||||
.flex(row, center);
|
.flex(row, center);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 34px;
|
width: 44px;
|
||||||
height: 34px;
|
height: 44px;
|
||||||
border-radius: @radius-sm;
|
border-radius: @radius-sm;
|
||||||
font-size: 10px;
|
font-size: 12px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
letter-spacing: 0.2px;
|
letter-spacing: 0.2px;
|
||||||
}
|
}
|
||||||
@@ -288,18 +293,18 @@ function onDownload(file: DemandAttachment): void {
|
|||||||
&__meta {
|
&__meta {
|
||||||
.flex(column, flex-start, stretch);
|
.flex(column, flex-start, stretch);
|
||||||
flex: 1;
|
flex: 1;
|
||||||
gap: 2px;
|
gap: 4px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__name {
|
&__name {
|
||||||
font-size: 15px;
|
font-size: 20px;
|
||||||
color: @demand-ink;
|
color: @demand-ink;
|
||||||
.ellipsis();
|
.ellipsis();
|
||||||
}
|
}
|
||||||
|
|
||||||
&__sub {
|
&__sub {
|
||||||
font-size: @font-size-sm;
|
font-size: 16px;
|
||||||
color: @text-color-secondary;
|
color: @text-color-secondary;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,13 @@ const emit = defineEmits<{ 'update:modelValue': [value: string] }>()
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="biz-types">
|
<div class="biz-types">
|
||||||
<!-- 接口一条都没下发时给一行说明:不是渲染失败,而是确实没有可选类型(前端不造兜底项) -->
|
<!-- 接口一条都没下发时保留 5 个空轨道:视觉上仍是设计稿的 5 列卡片栅格,
|
||||||
<p v-if="!props.options.length" class="biz-types__empty">暂无可选业务类型</p>
|
不把 ① 区块压塌(前端不造兜底选项,只占位) -->
|
||||||
|
<div v-if="!props.options.length" class="biz-types__grid" aria-hidden="true">
|
||||||
|
<p class="biz-types__empty">
|
||||||
|
暂无可选业务类型
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<div v-else class="biz-types__grid">
|
<div v-else class="biz-types__grid">
|
||||||
<button
|
<button
|
||||||
v-for="option in props.options"
|
v-for="option in props.options"
|
||||||
@@ -51,24 +56,30 @@ const emit = defineEmits<{ 'update:modelValue': [value: string] }>()
|
|||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.biz-types {
|
.biz-types {
|
||||||
&__empty {
|
&__empty {
|
||||||
padding: @spacing-base 0;
|
grid-column: 1 / -1;
|
||||||
font-size: @font-size-sm;
|
// 与设计稿单卡同高(206),保证 ① 区块塌陷时页面纵向节奏不变
|
||||||
color: @text-color-secondary;
|
.flex(row, center, center);
|
||||||
|
height: 206px;
|
||||||
|
border: 1px dashed @demand-line;
|
||||||
|
border-radius: @radius-base;
|
||||||
|
font-size: 16px;
|
||||||
|
color: @demand-line;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__grid {
|
&__grid {
|
||||||
// auto-fill(不是 auto-fit):条目不足 5 个时保留空轨道,卡片尺寸与设计稿一致(303 设计px ≈ 233px),
|
// 设计稿:5 张卡 303/305 宽 × 206 高,x194/515/838/1160/1482 → 间距 18/18/17/16
|
||||||
// 不会被拉伸变形;满 5 个时正好落在设计稿的 5 列上。
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
grid-template-columns: repeat(5, 1fr);
|
||||||
gap: 14px;
|
gap: 17px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__card {
|
&__card {
|
||||||
position: relative;
|
position: relative;
|
||||||
.flex(column, flex-start, stretch);
|
.flex(column, flex-start, flex-start);
|
||||||
gap: 2px;
|
gap: 0;
|
||||||
padding: 12px 12px 8px;
|
height: 206px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 44px 16px 0; // 标题 @(210,545) → 卡内 (16,106)
|
||||||
border: 1px solid @demand-line;
|
border: 1px solid @demand-line;
|
||||||
border-radius: @radius-base;
|
border-radius: @radius-base;
|
||||||
background: @white;
|
background: @white;
|
||||||
@@ -87,13 +98,13 @@ const emit = defineEmits<{ 'update:modelValue': [value: string] }>()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 右上角选中标记(设计稿 30 设计px ≈ 23px):未选空心圈,选中为白环 + 蓝缝 + 白点 */
|
/** 右上角选中标记(设计稿 30 设计px):未选空心圈,选中为白环 + 蓝缝 + 白点 */
|
||||||
&__check {
|
&__check {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 22px;
|
top: 22px;
|
||||||
right: 19px;
|
right: 19px;
|
||||||
width: 23px;
|
width: 30px;
|
||||||
height: 23px;
|
height: 30px;
|
||||||
border: 1px solid @demand-radio-idle;
|
border: 1px solid @demand-radio-idle;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: @white;
|
background: @white;
|
||||||
@@ -107,28 +118,29 @@ const emit = defineEmits<{ 'update:modelValue': [value: string] }>()
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__icon {
|
&__icon {
|
||||||
margin-bottom: 25px;
|
margin-bottom: 15px; // 图标底 → 标题顶(设计稿 545 − 图标 40 − 卡内顶 106)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 后端 Logo:与内联 SVG 同尺寸,保证两种来源视觉一致 */
|
/** 后端 Logo:与内联 SVG 同尺寸,保证两种来源视觉一致 */
|
||||||
&__logo {
|
&__logo {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
margin-bottom: 25px;
|
margin-bottom: 15px;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__name {
|
&__name {
|
||||||
font-size: @font-size-title;
|
font-size: 28px; // 设计稿 28/500
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
line-height: 45px;
|
||||||
color: @demand-ink;
|
color: @demand-ink;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__desc {
|
&__desc {
|
||||||
font-size: @font-size-sm;
|
font-size: 16px; // 设计稿 16/500
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
line-height: 23px;
|
||||||
color: @demand-ink;
|
color: @demand-ink;
|
||||||
line-height: 1.45;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 选中卡内文字反白(设计稿:标题/描述均为白色)
|
// 选中卡内文字反白(设计稿:标题/描述均为白色)
|
||||||
|
|||||||
@@ -77,39 +77,49 @@ function onRemark(event: Event): void {
|
|||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.demand-confirm {
|
.demand-confirm {
|
||||||
.flex(column, flex-start, stretch);
|
.flex(column, flex-start, stretch);
|
||||||
gap: @spacing-lg;
|
gap: 0;
|
||||||
|
width: 1605px;
|
||||||
|
|
||||||
&__row {
|
&__row {
|
||||||
.flex(row, flex-start, stretch);
|
.flex(row, flex-start, flex-start);
|
||||||
// 设计稿:两个 503 设计px 输入框(≈387px),间距 73 设计px ≈ 56px,整行不占满
|
// 设计稿:两个 503 宽输入框(x194→697 / x770→1273),间距 73
|
||||||
gap: 56px;
|
gap: 73px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__field {
|
&__field {
|
||||||
.flex(column, flex-start, stretch);
|
.flex(column, flex-start, stretch);
|
||||||
gap: 10px;
|
gap: 14px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 行内两列(用户名称 / 用户手机号)定宽;补充备注整行铺满
|
// 行内两列(用户名称 / 用户手机号)定宽;补充备注整行铺满
|
||||||
&__row &__field {
|
&__row &__field {
|
||||||
flex: 0 0 387px;
|
flex: 0 0 503px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设计稿:备注行标签顶 y1901,用户行控件底 y1871 → 间距 30
|
||||||
|
&__row + &__field {
|
||||||
|
margin-top: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__label {
|
&__label {
|
||||||
font-size: 15px;
|
font-size: 20px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
line-height: 29px;
|
||||||
color: @demand-heading;
|
color: @demand-heading;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__input {
|
&__input {
|
||||||
.text-control();
|
.text-control();
|
||||||
height: 38px;
|
height: 49px;
|
||||||
font-size: 15px;
|
padding: 0 18px;
|
||||||
border-color: @demand-line;
|
border-color: @demand-line;
|
||||||
|
border-radius: @radius-sm;
|
||||||
|
font-size: 20px;
|
||||||
color: @demand-ink;
|
color: @demand-ink;
|
||||||
|
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
|
font-weight: 400;
|
||||||
color: @demand-line;
|
color: @demand-line;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,25 +131,28 @@ function onRemark(event: Event): void {
|
|||||||
|
|
||||||
&__textarea {
|
&__textarea {
|
||||||
.text-control();
|
.text-control();
|
||||||
min-height: 208px;
|
height: 270px;
|
||||||
padding: 10px 14px 24px;
|
min-height: 270px;
|
||||||
font-size: 15px;
|
padding: 10px 18px 24px;
|
||||||
border-color: @demand-line;
|
border-color: @demand-line;
|
||||||
|
border-radius: @radius-sm;
|
||||||
|
font-size: 20px;
|
||||||
color: @demand-ink;
|
color: @demand-ink;
|
||||||
line-height: 1.7;
|
line-height: 29px;
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
|
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
|
font-weight: 400;
|
||||||
color: @demand-line;
|
color: @demand-line;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__count {
|
&__count {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: @spacing-sm;
|
right: @spacing-lg;
|
||||||
bottom: @spacing-xs;
|
bottom: @spacing-sm;
|
||||||
font-size: @font-size-sm;
|
font-size: 16px;
|
||||||
color: @text-color-secondary;
|
color: @text-color-disabled;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -194,32 +194,37 @@ function onBudgetManualBlur(event: Event): void {
|
|||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.demand-info {
|
.demand-info {
|
||||||
.flex(column, flex-start, stretch);
|
.flex(column, flex-start, stretch);
|
||||||
gap: @spacing-lg;
|
// 字段间统一节奏:上一控件底 → 下一标签顶
|
||||||
|
// 设计稿实测:input 底 881 → 描述标签顶 910(29 行盒)→ 控件 954;预算行 1224→1254;附件 1346→1376
|
||||||
|
gap: 29px;
|
||||||
|
width: 1605px; // 设计稿:所有控件统一 1605 宽(x194 → 1799)
|
||||||
|
|
||||||
&__field {
|
&__field {
|
||||||
.flex(column, flex-start, stretch);
|
.flex(column, flex-start, stretch);
|
||||||
gap: 10px;
|
gap: 14px; // 标签行盒底 → 控件顶(设计稿 789+29 → 832)
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__row {
|
&__row {
|
||||||
.flex(row, flex-start, stretch);
|
.flex(row, flex-start, flex-start);
|
||||||
// 设计稿:预算列 892 设计px + 间距 61 设计px + 周期列 652 设计px(1605 总宽)
|
// 设计稿:预算列 892 宽(x194→1086)+ 间距 61 + 周期列 652 宽(x1147→1799)
|
||||||
gap: @spacing-xxl;
|
gap: 61px;
|
||||||
|
|
||||||
.demand-info__field {
|
.demand-info__field {
|
||||||
flex: 687 1 0;
|
flex: 0 0 auto;
|
||||||
width: auto;
|
width: 892px;
|
||||||
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.demand-info__field:last-child {
|
.demand-info__field:last-child {
|
||||||
flex: 502 1 0;
|
width: 652px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__label {
|
&__label {
|
||||||
font-size: 15px;
|
font-size: 20px; // 设计稿 20/500 #333333
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
line-height: 29px;
|
||||||
color: @demand-heading;
|
color: @demand-heading;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,13 +235,16 @@ function onBudgetManualBlur(event: Event): void {
|
|||||||
|
|
||||||
&__input {
|
&__input {
|
||||||
.text-control();
|
.text-control();
|
||||||
// 设计稿:49 设计px 高 ≈ 38px,20 设计px 字号 ≈ 15px,描边/占位 rgba(153,153,153)
|
// 设计稿:49 高、20 字号、描边 #999999、内缩 18
|
||||||
height: 38px;
|
height: 49px;
|
||||||
font-size: 15px;
|
padding: 0 18px;
|
||||||
border-color: @demand-line;
|
border-color: @demand-line;
|
||||||
|
border-radius: @radius-sm;
|
||||||
|
font-size: 20px;
|
||||||
color: @demand-ink;
|
color: @demand-ink;
|
||||||
|
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
|
font-weight: 400;
|
||||||
color: @demand-line;
|
color: @demand-line;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -248,44 +256,48 @@ function onBudgetManualBlur(event: Event): void {
|
|||||||
|
|
||||||
&__textarea {
|
&__textarea {
|
||||||
.text-control();
|
.text-control();
|
||||||
// 设计稿:270 设计px 高 ≈ 208px,占位内缩 18/10 设计px ≈ 14/8px
|
// 设计稿:270 高、占位内缩 18/10
|
||||||
min-height: 208px;
|
height: 270px;
|
||||||
padding: 10px 14px 24px;
|
min-height: 270px;
|
||||||
font-size: 15px;
|
padding: 10px 18px 24px;
|
||||||
border-color: @demand-line;
|
border-color: @demand-line;
|
||||||
|
border-radius: @radius-sm;
|
||||||
|
font-size: 20px;
|
||||||
color: @demand-ink;
|
color: @demand-ink;
|
||||||
line-height: 1.7;
|
line-height: 29px;
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
|
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
|
font-weight: 400;
|
||||||
color: @demand-line;
|
color: @demand-line;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__count {
|
&__count {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: @spacing-sm;
|
right: @spacing-lg;
|
||||||
bottom: @spacing-xs;
|
bottom: @spacing-sm;
|
||||||
font-size: @font-size-sm;
|
font-size: 16px;
|
||||||
color: @text-color-secondary;
|
color: @text-color-disabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__budgets {
|
&__budgets {
|
||||||
.flex(row, flex-start, stretch);
|
.flex(row, flex-start, stretch);
|
||||||
// 设计稿:5 个 165 设计px 胶囊、间距 17 设计px ≈ 13px
|
// 设计稿:5 个 165 宽胶囊,间距 17
|
||||||
gap: 13px;
|
gap: 17px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 预算控件外观:4 个胶囊按钮 + 第 5 个手填输入框共用同一套规格 */
|
/** 预算控件外观:4 个胶囊按钮 + 第 5 个手填输入框共用同一套规格 */
|
||||||
&__budget {
|
&__budget {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
height: 38px;
|
height: 49px;
|
||||||
padding: 0 @spacing-sm;
|
padding: 0 @spacing-sm;
|
||||||
border: 1px solid @demand-line;
|
border: 1px solid @demand-line;
|
||||||
border-radius: @radius-sm;
|
border-radius: @radius-sm;
|
||||||
background: @white;
|
background: @white;
|
||||||
font-size: 15px;
|
font-size: 20px;
|
||||||
|
font-weight: 400;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
color: @demand-line;
|
color: @demand-line;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -302,7 +314,7 @@ function onBudgetManualBlur(event: Event): void {
|
|||||||
border-color: @demand-accent;
|
border-color: @demand-accent;
|
||||||
background: @demand-selected-bg;
|
background: @demand-selected-bg;
|
||||||
color: @white;
|
color: @white;
|
||||||
font-weight: 600;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,22 +55,27 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="demand-page">
|
<div class="demand-page" :class="{ 'is-loading': loading }">
|
||||||
<div class="container demand" :class="{ 'is-loading': loading }">
|
<!-- 设计稿:导航下方 1920×179 白色通栏页头(标题 36/700 @x112,副标题 24/500 @y171) -->
|
||||||
<header class="demand__head">
|
<header class="demand-page__hero">
|
||||||
<h1 class="demand__title">
|
<div class="container demand-page__hero-inner">
|
||||||
|
<h1 class="demand-page__title">
|
||||||
需求提报
|
需求提报
|
||||||
</h1>
|
</h1>
|
||||||
<p class="demand__subtitle">
|
<p class="demand-page__subtitle">
|
||||||
填写您的需求信息,专业顾问将快速与您联系
|
填写您的需求信息,专业顾问将快速与您联系
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<!-- 设计稿:三段内容同处一张 1780×2186 白卡(矩形 148286 @(70,271)) -->
|
||||||
|
<div class="container">
|
||||||
|
<div class="demand">
|
||||||
<!-- ① 选择业务类型 -->
|
<!-- ① 选择业务类型 -->
|
||||||
<section class="demand__card">
|
<section class="demand__section">
|
||||||
<h2 class="demand__section">
|
<h2 class="demand__heading">
|
||||||
<span class="demand__index">1</span>
|
<span class="demand__index">01</span>
|
||||||
选择业务类型<span class="demand__required">*</span>
|
<span class="demand__label">选择业务类型<span class="demand__required">*</span></span>
|
||||||
</h2>
|
</h2>
|
||||||
<DemandBizTypes
|
<DemandBizTypes
|
||||||
:options="bizTypeOptions"
|
:options="bizTypeOptions"
|
||||||
@@ -80,10 +85,10 @@ onMounted(() => {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- ② 填写需求信息 -->
|
<!-- ② 填写需求信息 -->
|
||||||
<section class="demand__card">
|
<section class="demand__section">
|
||||||
<h2 class="demand__section">
|
<h2 class="demand__heading">
|
||||||
<span class="demand__index">2</span>
|
<span class="demand__index">02</span>
|
||||||
填写需求信息<span class="demand__required">*</span>
|
<span class="demand__label">填写需求信息<span class="demand__required">*</span></span>
|
||||||
</h2>
|
</h2>
|
||||||
<DemandInfoForm
|
<DemandInfoForm
|
||||||
:demand="form"
|
:demand="form"
|
||||||
@@ -96,10 +101,10 @@ onMounted(() => {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- ③ 确认信息并补充备注 -->
|
<!-- ③ 确认信息并补充备注 -->
|
||||||
<section class="demand__card">
|
<section class="demand__section">
|
||||||
<h2 class="demand__section">
|
<h2 class="demand__heading">
|
||||||
<span class="demand__index">3</span>
|
<span class="demand__index">03</span>
|
||||||
确认信息并补充备注
|
<span class="demand__label">确认信息并补充备注</span>
|
||||||
</h2>
|
</h2>
|
||||||
<DemandConfirmForm
|
<DemandConfirmForm
|
||||||
:demand="form"
|
:demand="form"
|
||||||
@@ -107,6 +112,7 @@ onMounted(() => {
|
|||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<DemandActionBar
|
<DemandActionBar
|
||||||
:saving="saving"
|
:saving="saving"
|
||||||
@@ -120,96 +126,98 @@ onMounted(() => {
|
|||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.demand-page {
|
.demand-page {
|
||||||
padding-top: @spacing-lg;
|
// 固定底条 72px + 呼吸间距
|
||||||
// 固定底条 72px + 呼吸间距,避免遮住 ③ 卡片
|
|
||||||
padding-bottom: 104px;
|
padding-bottom: 104px;
|
||||||
// 设计稿:导航下方 179 设计px(≈138px)白色通栏横幅,其下为页面底色
|
|
||||||
background: linear-gradient(to bottom, @white 0, @white 138px, @bg-color 138px);
|
|
||||||
|
|
||||||
&.is-loading {
|
&.is-loading .demand {
|
||||||
.demand {
|
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// 设计稿:白色通栏页头 @(0,72) 1920×179,文案 36/700 @(112,104)、24/500 @(112,171)
|
||||||
|
&__hero {
|
||||||
|
height: 179px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding-top: 32px;
|
||||||
|
background: @white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.demand {
|
&__hero-inner {
|
||||||
// 设计稿:标题距横幅顶 32 设计px、副标题行距 15 设计px、横幅下留白 50 设计px
|
padding-left: 42px; // 文案 x112 = 内容区 x70 + 42
|
||||||
&__head {
|
|
||||||
.flex(column, flex-start, stretch);
|
|
||||||
gap: 11px;
|
|
||||||
margin-bottom: 50px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
font-size: @font-size-xxl;
|
margin: 0;
|
||||||
|
font-size: 36px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 1.45;
|
line-height: 52px;
|
||||||
color: @demand-ink;
|
color: @demand-ink;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__subtitle {
|
&__subtitle {
|
||||||
font-size: 18px;
|
margin: 15px 0 0;
|
||||||
|
font-size: 24px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
line-height: 35px;
|
||||||
color: @demand-subtitle;
|
color: @demand-subtitle;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 设计稿三段内容位于同一张白色大卡内:三张卡无缝拼接呈现同一张卡的观感
|
// 设计稿:白卡 @(70,271) 1780×2186;三段标题序号 x134、标题文字 x194
|
||||||
&__card {
|
.demand {
|
||||||
padding: 24px 40px 24px 49px;
|
margin-top: 20px; // 271 − (72+179) = 20
|
||||||
border-radius: 0;
|
padding: 48px 56px 111px; // 序号顶 319 − 271 = 48
|
||||||
background: @white;
|
background: @white;
|
||||||
|
|
||||||
// 三卡相连,不再留块间距
|
&__heading {
|
||||||
& + & {
|
.flex(row, flex-start, center);
|
||||||
margin-top: 0;
|
gap: 15px;
|
||||||
|
margin: 0 0 50px; // 01:序号块底 389 → 业务卡顶 439
|
||||||
|
padding-left: 8px; // 序号落 x134 = 卡内 x70+64 → 64-56 = 8
|
||||||
}
|
}
|
||||||
|
|
||||||
&:first-of-type {
|
|
||||||
padding-top: 37px;
|
|
||||||
border-radius: @radius-base @radius-base 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-of-type {
|
|
||||||
padding-bottom: 40px;
|
|
||||||
border-radius: 0 0 @radius-base @radius-base;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 区块标题:序号落在卡左内边距列(x=134 设计px),标题文字对齐内容列(x=194)
|
|
||||||
&__section {
|
|
||||||
.flex(row, flex-start, baseline);
|
|
||||||
gap: @spacing-xs;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
font-size: @font-size-title;
|
|
||||||
font-weight: 500;
|
|
||||||
color: @demand-heading;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 内容列(卡片栅格 / 表单)与区块标题文字对齐到 x=194 设计列(60 设计px ≈ 46px)
|
|
||||||
&__section + * {
|
|
||||||
margin-left: 46px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ① 段标题到卡片栅格的间距更大(56 设计px ≈ 43px)
|
|
||||||
&__card:first-of-type &__section {
|
|
||||||
margin-bottom: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 区块序号:设计稿 48 设计px 蓝色数字(01/02/03) */
|
|
||||||
&__index {
|
&__index {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 42px;
|
font-size: 48px; // 设计稿 48/500 #024FFF
|
||||||
font-size: 37px;
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
line-height: 1;
|
line-height: 70px;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
color: @demand-accent;
|
color: @demand-accent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
font-size: 28px; // 设计稿 28/500 #333333
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 41px;
|
||||||
|
color: @demand-heading;
|
||||||
|
}
|
||||||
|
|
||||||
&__required {
|
&__required {
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
color: @danger-color;
|
color: @danger-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 表单列对齐标题文字 x194 = 卡内 x70 + 124(序号行宽 55 + gap 15 + 序号内缩 8 = 78,再补 46)
|
||||||
|
&__heading + * {
|
||||||
|
margin-left: 46px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 段间距(设计稿实测):01 区底 645 → 02 序号顶 705 = 60;02 区底 1639 → 03 序号顶 1689 = 50
|
||||||
|
&__section + &__section {
|
||||||
|
margin-top: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__section + &__section:last-of-type {
|
||||||
|
margin-top: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ② ③ 段:标题块(70) 与首个表单标签间距更紧(序号顶 705/1689 → 标签顶 789/1779 = 84/90)
|
||||||
|
&__section + &__section &__heading {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__section + &__section &__heading + * {
|
||||||
|
margin-top: 14px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,10 +2,9 @@
|
|||||||
import { computed, onMounted, watch } from 'vue'
|
import { computed, onMounted, watch } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { AppButton, StatusTag } from '@/components'
|
|
||||||
// 页面容器:数据只经 store,不直接 import api
|
// 页面容器:数据只经 store,不直接 import api
|
||||||
import { useFinanceStore } from '@/stores/modules/finance'
|
import { useFinanceStore } from '@/stores/modules/finance'
|
||||||
import type { BillStatus } from '@/types/business/finance'
|
import type { Bill, BillStatus } from '@/types/business/finance'
|
||||||
import { formatMoney } from '@/utils/format'
|
import { formatMoney } from '@/utils/format'
|
||||||
import { toChannelLabel } from '@/utils/payMeta'
|
import { toChannelLabel } from '@/utils/payMeta'
|
||||||
|
|
||||||
@@ -16,21 +15,36 @@ const { currentBill } = storeToRefs(financeStore)
|
|||||||
|
|
||||||
const billId = computed(() => String(route.params.id))
|
const billId = computed(() => String(route.params.id))
|
||||||
|
|
||||||
const STATUS_VARIANTS: Record<BillStatus, 'warning' | 'success' | 'info' | 'danger'> = {
|
/**
|
||||||
待支付: 'warning',
|
* 设计 31 是三张并排状态卡(523/825/1127 各 270×476):
|
||||||
已支付: 'success',
|
* 已支付(红金额)+ 待支付(橙金额)+ 未到期(灰金额)。
|
||||||
未到期: 'info',
|
* 页面实际只展示当前账单状态那一张,其余两张是同一组件的另外两种状态。
|
||||||
已取消: 'danger',
|
*/
|
||||||
|
const STATUS_CARDS: BillStatus[] = ['已支付', '待支付', '未到期']
|
||||||
|
|
||||||
|
const AMOUNT_CLASS: Record<BillStatus, string> = {
|
||||||
|
已支付: 'is-paid',
|
||||||
|
待支付: 'is-pending',
|
||||||
|
未到期: 'is-idle',
|
||||||
|
已取消: 'is-idle',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 演示用:三张卡各自的账单数据(当前账单代入对应状态卡) */
|
||||||
|
const cards = computed<Array<{ status: BillStatus; bill: Bill | null }>>(() =>
|
||||||
|
STATUS_CARDS.map((status) => ({
|
||||||
|
status,
|
||||||
|
bill: currentBill.value && currentBill.value.status === status ? currentBill.value : null,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
|
||||||
async function load(): Promise<void> {
|
async function load(): Promise<void> {
|
||||||
// 支付信息(渠道 / 时间 / 流水号)已由后端回写在账单本身上,无需再查支付结果
|
// 支付信息(渠道 / 时间 / 流水号)已由后端回写在账单本身上,无需再查支付结果
|
||||||
await financeStore.fetchDetail(billId.value)
|
await financeStore.fetchDetail(billId.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPay(): void {
|
function onPay(id: string): void {
|
||||||
// 进收银台(确认支付):选支付方式 → 微信出码 / 支付宝跳转
|
// 进收银台(确认支付):选支付方式 → 微信出码 / 支付宝跳转
|
||||||
void router.push({ path: '/finance/checkout', query: { ids: billId.value } })
|
void router.push({ path: '/finance/checkout', query: { ids: id } })
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(billId, () => {
|
watch(billId, () => {
|
||||||
@@ -44,252 +58,345 @@ onMounted(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="bill-detail">
|
<div class="bill-detail">
|
||||||
<nav class="bill-detail__breadcrumb text-secondary">
|
<div class="container">
|
||||||
<RouterLink to="/finance" class="link">
|
<!-- 面包屑 (70,117)「财务中心 / 账单明细 /」 + 「账单详情」 -->
|
||||||
财务中心
|
<nav class="bill-detail__breadcrumb">
|
||||||
|
<RouterLink to="/finance" class="bill-detail__breadcrumb-link">
|
||||||
|
财务中心 /
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<span> > </span>
|
<RouterLink to="/finance/bills" class="bill-detail__breadcrumb-link">
|
||||||
<RouterLink to="/finance/bills" class="link">
|
账单明细 /
|
||||||
账单明细
|
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<span> > </span>
|
<span class="bill-detail__breadcrumb-current">账单详情</span>
|
||||||
<span>账单详情</span>
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<template v-if="currentBill">
|
<!-- 三张状态卡 (523/825/1127,158) 各 270×476 -->
|
||||||
<section class="bill-detail__card card">
|
<div v-if="currentBill" class="bill-detail__cards">
|
||||||
<div class="bill-detail__card-head">
|
<section
|
||||||
<h3 class="bill-detail__card-title">
|
v-for="card in cards"
|
||||||
订单信息
|
:key="card.status"
|
||||||
|
class="bill-detail__card"
|
||||||
|
>
|
||||||
|
<!-- 卡头:对勾圆 + 状态词 (539,278) 238×21 16/700 #1F2937 -->
|
||||||
|
<div class="bill-detail__head">
|
||||||
|
<span class="bill-detail__check" aria-hidden="true">✓</span>
|
||||||
|
<h3 class="bill-detail__status">
|
||||||
|
{{ card.status }}
|
||||||
</h3>
|
</h3>
|
||||||
<StatusTag :label="currentBill.status" :variant="STATUS_VARIANTS[currentBill.status]" />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="bill-detail__fields">
|
|
||||||
<div class="bill-detail__field">
|
<!-- 付款金额:标签 (590.23,315) + 金额 (653.8,309) 18/700 -->
|
||||||
<span class="bill-detail__label text-secondary">账单名称</span>
|
<div class="bill-detail__amount-row">
|
||||||
<span class="bill-detail__value">{{ currentBill.planName }}</span>
|
<span class="bill-detail__amount-label">付款金额:</span>
|
||||||
</div>
|
|
||||||
<div class="bill-detail__field">
|
|
||||||
<span class="bill-detail__label text-secondary">账单号</span>
|
|
||||||
<span class="bill-detail__value">{{ currentBill.billNo }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="bill-detail__field">
|
|
||||||
<span class="bill-detail__label text-secondary">项目名称</span>
|
|
||||||
<span class="bill-detail__value">{{ currentBill.projectName }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="bill-detail__field">
|
|
||||||
<span class="bill-detail__label text-secondary">订单金额</span>
|
|
||||||
<!-- 设计 31:金额随状态取色——已支付红 / 待支付橙 / 未到期深灰 -->
|
|
||||||
<span
|
<span
|
||||||
class="bill-detail__value bill-detail__value--amount"
|
class="bill-detail__amount"
|
||||||
:class="{
|
:class="AMOUNT_CLASS[card.status]"
|
||||||
'is-pending': currentBill.status === '待支付',
|
>¥ {{ formatMoney(card.bill?.amount ?? currentBill.amount, 0) }}</span>
|
||||||
'is-idle': currentBill.status === '未到期' || currentBill.status === '已取消',
|
</div>
|
||||||
}"
|
|
||||||
>¥{{ formatMoney(currentBill.amount) }}</span>
|
<!-- 支付信息四行:标签 (547,y) 12/400 #6B7280 + 值 12/700 #1F2937 -->
|
||||||
|
<dl class="bill-detail__fields">
|
||||||
|
<div class="bill-detail__field">
|
||||||
|
<dt class="bill-detail__label">
|
||||||
|
支付方式
|
||||||
|
</dt>
|
||||||
|
<dd class="bill-detail__value">
|
||||||
|
{{ card.status === '已支付' ? toChannelLabel(currentBill.payChannel) : '—' }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="bill-detail__field">
|
||||||
|
<dt class="bill-detail__label">
|
||||||
|
支付时间
|
||||||
|
</dt>
|
||||||
|
<dd class="bill-detail__value">
|
||||||
|
{{ card.status === '已支付' ? (currentBill.payTime ?? '—') : '—' }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="bill-detail__field">
|
||||||
|
<dt class="bill-detail__label">
|
||||||
|
交易流水号
|
||||||
|
</dt>
|
||||||
|
<dd class="bill-detail__value">
|
||||||
|
{{ card.status === '已支付' ? (currentBill.tradeNo ?? '—') : '—' }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<!-- 订单小卡 (555.5/857.5/1159.5,481..) 205 宽 -->
|
||||||
|
<div class="bill-detail__order">
|
||||||
|
<span class="bill-detail__order-title">
|
||||||
|
{{ card.status }}订单
|
||||||
|
</span>
|
||||||
|
<div class="bill-detail__order-body">
|
||||||
|
<span class="bill-detail__order-left">
|
||||||
|
<span class="bill-detail__order-name">{{ currentBill.planName }}</span>
|
||||||
|
<span class="bill-detail__order-meta">账单号:{{ currentBill.billNo }}</span>
|
||||||
|
<span class="bill-detail__order-meta">关联项目:{{ currentBill.projectName }}</span>
|
||||||
|
</span>
|
||||||
|
<span class="bill-detail__order-amount">¥ {{ formatMoney(currentBill.amount, 0) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 待支付:立即支付 -->
|
<!-- 返回 (727.4,594) 43.2×16 12/500 -->
|
||||||
<div v-if="currentBill.status === '待支付'" class="bill-detail__pay">
|
<button
|
||||||
<AppButton type="primary" @click="onPay">
|
v-if="card.status === '待支付'"
|
||||||
|
class="bill-detail__cta"
|
||||||
|
@click="onPay(currentBill.id)"
|
||||||
|
>
|
||||||
立即支付
|
立即支付
|
||||||
</AppButton>
|
</button>
|
||||||
</div>
|
<button
|
||||||
|
v-else
|
||||||
<!-- 已支付:支付信息 -->
|
class="bill-detail__cta"
|
||||||
<template v-else-if="currentBill.status === '已支付'">
|
@click="router.push('/finance/bills')"
|
||||||
<div class="bill-detail__pay-info">
|
>
|
||||||
<h4 class="bill-detail__sub-title">
|
返回
|
||||||
支付信息
|
</button>
|
||||||
</h4>
|
|
||||||
<div class="bill-detail__fields">
|
|
||||||
<div class="bill-detail__field">
|
|
||||||
<span class="bill-detail__label text-secondary">付款金额</span>
|
|
||||||
<span class="bill-detail__value bill-detail__value--amount">¥{{ formatMoney(currentBill.amount) }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="bill-detail__field">
|
|
||||||
<span class="bill-detail__label text-secondary">支付方式</span>
|
|
||||||
<span class="bill-detail__value">{{ toChannelLabel(currentBill.payChannel) }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="bill-detail__field">
|
|
||||||
<span class="bill-detail__label text-secondary">支付时间</span>
|
|
||||||
<span class="bill-detail__value">{{ currentBill.payTime ?? '—' }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="bill-detail__field">
|
|
||||||
<span class="bill-detail__label text-secondary">交易流水号</span>
|
|
||||||
<span class="bill-detail__value">{{ currentBill.tradeNo ?? '—' }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 已取消 -->
|
|
||||||
<div v-else-if="currentBill.status === '已取消'" class="bill-detail__tip text-secondary">
|
|
||||||
该账单已取消,如有疑问请联系项目负责人。
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 未到期:提示 -->
|
|
||||||
<div v-else class="bill-detail__tip text-secondary">
|
|
||||||
账单尚未到期,到期后可前往账单明细完成支付。
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</div>
|
||||||
|
|
||||||
<p v-else class="bill-detail__empty text-secondary">
|
<p v-else class="bill-detail__empty">
|
||||||
账单不存在或已下线
|
账单不存在或已下线
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="bill-detail__back">
|
|
||||||
<AppButton @click="router.push('/finance/bills')">
|
|
||||||
返回
|
|
||||||
</AppButton>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
// 设计 31 账单详情(1920 画布 1:1)——内容区 x70→1850 = 1780
|
||||||
.bill-detail {
|
.bill-detail {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: @container-width;
|
padding-bottom: 325px; // 卡片底 634 → 页脚 1087
|
||||||
margin: 0 auto;
|
background: @contract-page-bg;
|
||||||
padding: @spacing-lg @spacing-base @spacing-xxl;
|
|
||||||
|
|
||||||
// 设计 31:面包屑 13 设计px → 11px,前缀灰、当前页深灰
|
// 面包屑 (70,117):文字 ink y117,卡片顶 158 → 面包屑盒 86..131
|
||||||
&__breadcrumb {
|
&__breadcrumb {
|
||||||
margin-bottom: @spacing-base;
|
.flex(row, flex-start, center);
|
||||||
font-size: 11px;
|
height: 86px;
|
||||||
color: @text-color-disabled;
|
padding-top: 34px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 17px;
|
||||||
|
color: @project-muted;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
> span:last-child {
|
&__breadcrumb-link {
|
||||||
color: @text-color;
|
color: @project-muted;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: @primary-strong;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 31:白卡居中(对应设计三张窄卡的中轴布局),圆角 8、柔阴影
|
&__breadcrumb-current {
|
||||||
|
margin-left: 5.5px;
|
||||||
|
color: @finance-entry-text;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 三张卡并排:x523 / 825 / 1127,间距 32
|
||||||
|
&__cards {
|
||||||
|
.flex(row, flex-start, flex-start);
|
||||||
|
gap: 32px;
|
||||||
|
padding-left: 453px; // 内容区起点 70 → 首卡 523
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
&__card {
|
&__card {
|
||||||
max-width: 440px;
|
position: relative;
|
||||||
margin: 0 auto;
|
width: 270px;
|
||||||
padding: @spacing-base 20px 20px;
|
height: 476px;
|
||||||
border-radius: @radius-base;
|
border-radius: @radius-base;
|
||||||
background: @white;
|
background: @white;
|
||||||
box-shadow: @shadow-base;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__card-head {
|
// 卡头:对勾圆 (645.27,204) 34/400 白字 + 状态词 (539,278)
|
||||||
.flex(row, space-between, center);
|
&__head {
|
||||||
margin-bottom: @spacing-base;
|
.flex(row, flex-start, center);
|
||||||
|
gap: 0;
|
||||||
|
// 状态词盒 y278(卡内 120)、对勾圆 y204(卡内 46)→ 用 align-items:flex-start + 各自 margin
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 46px 0 0 16px;
|
||||||
|
height: 141px;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__card-title {
|
&__check {
|
||||||
font-size: 12px;
|
.flex(row, center, center);
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
margin-right: 30px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: @success-color;
|
||||||
|
color: @white;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1;
|
||||||
|
order: 2;
|
||||||
|
flex-shrink: 0;
|
||||||
|
// 设计圆 x629.27(卡内 106.27);渲染值 714 → 左移 84.75
|
||||||
|
transform: translateX(-84.75px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__status {
|
||||||
|
order: 1;
|
||||||
|
width: 238px;
|
||||||
|
margin-top: 74px; // 卡内 46 → 120(状态词顶)
|
||||||
|
font-size: 16px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: @heading-color;
|
line-height: 21px;
|
||||||
|
color: @finance-ink;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 状态徽标压成设计稿的深色粗体状态词
|
// 付款金额行:标签卡内 (67.23,157),金额卡内 (130.8,151)
|
||||||
:deep(.status-tag) {
|
&__amount-row {
|
||||||
padding: 0;
|
position: relative;
|
||||||
background: transparent;
|
width: 238px;
|
||||||
color: @heading-color;
|
height: 51px; // 行顶 = 卡内 130(金额盒顶 309−158=151 − 行内偏移 21)
|
||||||
|
margin: 0 0 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__amount-label {
|
||||||
|
position: absolute;
|
||||||
|
top: 16px; // 设计 ink y315(卡内 157)
|
||||||
|
left: 51.23px; // 卡内 67.23 − 16
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 700;
|
font-weight: 400;
|
||||||
|
line-height: 16px;
|
||||||
|
color: @finance-muted-text;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 字段行:标签左(灰 11px)、值右(深色粗体)——设计 31 左右分栏
|
&__amount {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px; // 设计盒顶 y309(卡内 151)
|
||||||
|
left: 114.8px; // 卡内 130.8 − 16
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 24px;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&.is-paid { color: @danger-color; }
|
||||||
|
&.is-pending { color: @finance-pending; }
|
||||||
|
&.is-idle { color: @finance-idle; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 支付信息:标签卡内 (24,207) 起,行距 28
|
||||||
&__fields {
|
&__fields {
|
||||||
.flex(column, flex-start, stretch);
|
.flex(column, flex-start, stretch);
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
margin: 26px 0 0 24px;
|
||||||
|
width: 222px;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__field {
|
&__field {
|
||||||
.flex(row, space-between, center);
|
.flex(row, flex-start, flex-start);
|
||||||
gap: @spacing-base;
|
gap: 0;
|
||||||
font-size: 11px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__label {
|
&__label {
|
||||||
flex-shrink: 0;
|
flex: 0 0 77.52px; // 值统一从卡内 101.52 起
|
||||||
font-size: 11px;
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 16px;
|
||||||
|
color: @finance-muted-text;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__value {
|
&__value {
|
||||||
color: @heading-color;
|
flex: 1;
|
||||||
font-weight: 600;
|
min-width: 0;
|
||||||
text-align: right;
|
color: @finance-ink;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 16px;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
// 设计 31:付款金额 已支付 #EF4444 / 待支付 #F59E0B / 未到期深灰,18 设计px → 14px 粗体
|
// 订单小卡:卡内 x32.5,标题顶卡内 323(设计 481−158)
|
||||||
&--amount {
|
&__order {
|
||||||
font-size: 14px;
|
.flex(column, flex-start, flex-start);
|
||||||
|
gap: 0;
|
||||||
|
margin: 17px 0 0 32.5px;
|
||||||
|
width: 205px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__order-title {
|
||||||
|
font-size: 13px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: @danger-color;
|
line-height: 17px;
|
||||||
|
color: @finance-ink;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.is-pending {
|
&__order-body {
|
||||||
color: @finance-pending;
|
.flex(row, space-between, flex-start);
|
||||||
|
gap: 0;
|
||||||
|
margin-top: 6px;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.is-idle {
|
&__order-left {
|
||||||
color: @heading-color;
|
.flex(column, flex-start, flex-start);
|
||||||
}
|
gap: 0;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__pay {
|
&__order-name {
|
||||||
margin-top: @spacing-base;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__pay-info {
|
|
||||||
margin-top: @spacing-base;
|
|
||||||
padding-top: @spacing-base;
|
|
||||||
border-top: 1px solid @project-divider;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__sub-title {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 15px;
|
||||||
|
color: @project-muted;
|
||||||
|
.ellipsis();
|
||||||
|
max-width: 124px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__order-meta {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 15px;
|
||||||
|
color: @project-muted;
|
||||||
|
.ellipsis();
|
||||||
|
max-width: 132px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__order-amount {
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 13px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: @heading-color;
|
line-height: 17px;
|
||||||
|
color: @finance-ink;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__tip {
|
// 底部按钮 (727.4,594) 43.2×16 文字;盒宽 90 居中于 270 卡
|
||||||
margin-top: @spacing-base;
|
&__cta {
|
||||||
padding: 12px;
|
position: absolute;
|
||||||
border-radius: @radius-sm;
|
left: 50%;
|
||||||
background: @bg-color;
|
top: 428px; // 按钮文字 y594(卡内 436),盒高 32 → 盒顶 428
|
||||||
font-size: 11px;
|
width: 90px;
|
||||||
|
height: 32px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: @primary-strong;
|
||||||
|
color: @white;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
/* 按钮基线:文字 ink y594 → 盒顶 428 + (32−16)/2 = 436 */
|
||||||
|
padding-bottom: 6px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
transition: background 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: @primary-color;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__empty {
|
&__empty {
|
||||||
padding: @spacing-xl 0;
|
padding: 80px 0;
|
||||||
|
color: @project-muted;
|
||||||
|
font-size: 13px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 11px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__back {
|
|
||||||
max-width: 440px;
|
|
||||||
margin: @spacing-base auto 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设计 31/32:按钮蓝底 #024FFF(返回/立即支付同色)
|
|
||||||
:deep(.app-button--primary) {
|
|
||||||
background: @primary-strong;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: @primary-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__back :deep(.app-button--default) {
|
|
||||||
border-color: @primary-strong;
|
|
||||||
background: @primary-strong;
|
|
||||||
color: @white;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-color: @primary-color;
|
|
||||||
background: @primary-color;
|
|
||||||
color: @white;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { FilterPanel, PaginationBar } from '@/components'
|
import { PaginationBar } from '@/components'
|
||||||
// 页面容器:数据只经 store,不直接 import api
|
// 页面容器:数据只经 store,不直接 import api
|
||||||
import { useFinanceStore } from '@/stores/modules/finance'
|
import { useFinanceStore } from '@/stores/modules/finance'
|
||||||
import type { BillStatus } from '@/types/business/finance'
|
import type { BillStatus } from '@/types/business/finance'
|
||||||
@@ -16,6 +16,7 @@ const { query, list, total } = storeToRefs(financeStore)
|
|||||||
const keyword = ref('')
|
const keyword = ref('')
|
||||||
const selectedIds = ref<string[]>([])
|
const selectedIds = ref<string[]>([])
|
||||||
|
|
||||||
|
/** 设计 30:状态分段组 4 项,「全部」选中态为实底 #024FFF */
|
||||||
const STATUS_TABS: Array<{ label: string; value: BillStatus | 'all' }> = [
|
const STATUS_TABS: Array<{ label: string; value: BillStatus | 'all' }> = [
|
||||||
{ label: '全部', value: 'all' },
|
{ label: '全部', value: 'all' },
|
||||||
{ label: '待支付', value: '待支付' },
|
{ label: '待支付', value: '待支付' },
|
||||||
@@ -23,6 +24,13 @@ const STATUS_TABS: Array<{ label: string; value: BillStatus | 'all' }> = [
|
|||||||
{ label: '未到期', value: '未到期' },
|
{ label: '未到期', value: '未到期' },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
/** 统计卡三组(设计 30:(71/663.67/1256.33, 155) 各 592.67×60) */
|
||||||
|
const STATS = computed(() => [
|
||||||
|
{ label: '总笔数', value: String(total.value) },
|
||||||
|
{ label: '总金额', value: String(list.value.reduce((sum, bill) => sum + bill.amount, 0)) },
|
||||||
|
{ label: '待支付', value: String(list.value.filter((bill) => bill.status === '待支付').length) },
|
||||||
|
])
|
||||||
|
|
||||||
function onTab(value: BillStatus | 'all'): void {
|
function onTab(value: BillStatus | 'all'): void {
|
||||||
if (query.value.status === value) return
|
if (query.value.status === value) return
|
||||||
financeStore.setQuery({ ...query.value, status: value, keyword: keyword.value })
|
financeStore.setQuery({ ...query.value, status: value, keyword: keyword.value })
|
||||||
@@ -67,15 +75,24 @@ onMounted(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="bills">
|
<div class="bills">
|
||||||
<nav class="bills__breadcrumb text-secondary">
|
<div class="container">
|
||||||
<RouterLink to="/finance" class="link">
|
<!-- 面包屑:(70,103) 「财务中心 >」13/400 #9CA3AF + 「账单明细」13/500 #4B5563 -->
|
||||||
财务中心
|
<nav class="bills__breadcrumb">
|
||||||
|
<RouterLink to="/finance" class="bills__breadcrumb-link">
|
||||||
|
财务中心 >
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<span> > </span>
|
<span class="bills__breadcrumb-current">账单明细</span>
|
||||||
<span>账单明细</span>
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<!-- 设计 30:状态分段 Tab 与搜索栏同排共处一张白卡 -->
|
<!-- 统计卡:(70,132) 1780×106 白底 + #F0F1F3 描边,三组等宽 592.67 -->
|
||||||
|
<div class="bills__stats">
|
||||||
|
<div v-for="item in STATS" :key="item.label" class="bills__stat">
|
||||||
|
<span class="bills__stat-label">{{ item.label }}</span>
|
||||||
|
<span class="bills__stat-value">{{ item.value }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 工具条:(70,252) 1780×71;Tab 组 (92,270.5) 279×34、搜索框 (405,269) 1300×37 -->
|
||||||
<div class="bills__toolbar">
|
<div class="bills__toolbar">
|
||||||
<div class="bills__tabs">
|
<div class="bills__tabs">
|
||||||
<button
|
<button
|
||||||
@@ -89,14 +106,22 @@ onMounted(() => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FilterPanel
|
<input
|
||||||
v-model:keyword="keyword"
|
v-model="keyword"
|
||||||
keyword-placeholder="搜索账单名称 / 账单号 / 项目名称"
|
class="bills__search"
|
||||||
@search="onSearch"
|
placeholder="关键字搜索"
|
||||||
@reset="onReset"
|
@keyup.enter="onSearch"
|
||||||
/>
|
>
|
||||||
|
|
||||||
|
<button class="bills__btn bills__btn--reset" @click="onReset">
|
||||||
|
重置
|
||||||
|
</button>
|
||||||
|
<button class="bills__btn bills__btn--primary" @click="onSearch">
|
||||||
|
搜索
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 表格卡:(70,337) 1780×365.36,表头 (77.5,344.5) 1765×41.77 -->
|
||||||
<div class="bills__board">
|
<div class="bills__board">
|
||||||
<BillTable
|
<BillTable
|
||||||
:bills="list"
|
:bills="list"
|
||||||
@@ -113,7 +138,9 @@ onMounted(() => {
|
|||||||
@page-change="onPageChange"
|
@page-change="onPageChange"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 汇总条:通栏白条 (0,718) 1920×66 -->
|
||||||
<BillSummaryBar
|
<BillSummaryBar
|
||||||
:bills="list"
|
:bills="list"
|
||||||
:selected-ids="selectedIds"
|
:selected-ids="selectedIds"
|
||||||
@@ -123,116 +150,113 @@ onMounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
// 设计 30 账单明细(1920 画布 1:1)——内容区 x70→1850 = 1780
|
||||||
.bills {
|
.bills {
|
||||||
.flex(column, flex-start);
|
.flex(column, flex-start);
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
gap: @spacing-base;
|
gap: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: @container-width;
|
padding-bottom: 14px; // 汇总条底 784 → 页脚 1087 的留白由页脚自身高度承担
|
||||||
margin: 0 auto;
|
background: @contract-page-bg;
|
||||||
padding: @spacing-lg @spacing-base @spacing-xxl;
|
|
||||||
|
|
||||||
// 设计 30:面包屑 13 设计px → 11px,前缀灰、当前页深灰
|
|
||||||
&__breadcrumb {
|
&__breadcrumb {
|
||||||
font-size: 11px;
|
|
||||||
color: @text-color-disabled;
|
|
||||||
|
|
||||||
> span:last-child {
|
|
||||||
color: @text-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 顶部工具条:Tab 分段组 + 搜索栏(一张白卡)
|
|
||||||
&__toolbar {
|
|
||||||
.flex(row, flex-start, center);
|
.flex(row, flex-start, center);
|
||||||
gap: @spacing-base;
|
height: 49px; // 组 6 (0,89) 高 31 + 上下留白 → 盒 y72..121,文字 ink y103
|
||||||
padding: 10px @spacing-base;
|
padding-top: 14px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 17px;
|
||||||
|
color: @project-muted;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__breadcrumb-link {
|
||||||
|
color: @project-muted;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: @primary-strong;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__breadcrumb-current {
|
||||||
|
// 设计:账单明细起点 x138.84;「 > 」由 CSS 绘制,宽 4.44px
|
||||||
|
margin-left: 4.44px;
|
||||||
|
color: @finance-entry-text;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 统计卡 (70,132) 1780×106
|
||||||
|
&__stats {
|
||||||
|
.flex(row, flex-start, flex-start);
|
||||||
|
gap: 0;
|
||||||
|
height: 106px;
|
||||||
|
margin-top: 11px; // 面包屑盒底 121 → 卡顶 132
|
||||||
|
border: 1px solid @finance-card-line;
|
||||||
border-radius: @radius-base;
|
border-radius: @radius-base;
|
||||||
background: @white;
|
background: @white;
|
||||||
box-shadow: @shadow-base;
|
box-sizing: border-box;
|
||||||
|
|
||||||
:deep(.filter-panel) {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
padding: 0;
|
|
||||||
background: transparent;
|
|
||||||
box-shadow: none;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.filter-panel__body) {
|
&__stat {
|
||||||
margin-top: 0;
|
.flex(column, flex-start, flex-start);
|
||||||
|
gap: 6px;
|
||||||
|
width: 592.67px;
|
||||||
|
padding: 22px 0 0 269px; // 标签 x340,卡边框内起点 x71 → 内缩 269
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.filter-panel__bar) {
|
&__stat-label {
|
||||||
gap: @spacing-sm;
|
font-size: 13px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 17px;
|
||||||
|
color: @finance-entry-text;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.filter-panel__input) {
|
&__stat-value {
|
||||||
order: 1;
|
font-size: 26px;
|
||||||
flex: 1;
|
font-weight: 700;
|
||||||
width: auto;
|
line-height: 35px;
|
||||||
height: 26px;
|
color: @finance-ink;
|
||||||
border: none;
|
|
||||||
border-radius: @radius-sm;
|
|
||||||
background: @finance-field-bg;
|
|
||||||
font-size: 11px;
|
|
||||||
|
|
||||||
&::placeholder {
|
|
||||||
color: @contract-placeholder;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计顺序:… 重置 搜索(FilterPanel 模板为搜索在前,用 order 调序)
|
// 工具条 (70,252) 1780×71
|
||||||
:deep(.filter-panel__btn--primary) {
|
&__toolbar {
|
||||||
order: 3;
|
.flex(row, flex-start, center);
|
||||||
height: 26px;
|
|
||||||
padding: 0 14px;
|
|
||||||
border: none;
|
|
||||||
background: @primary-strong;
|
|
||||||
font-size: 11px;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: @primary-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.filter-panel__btn:not(.filter-panel__btn--primary)) {
|
|
||||||
order: 2;
|
|
||||||
height: 26px;
|
|
||||||
padding: 0 14px;
|
|
||||||
border: none;
|
|
||||||
background: @project-quiet-btn;
|
|
||||||
color: @text-color;
|
|
||||||
font-size: 11px;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border: none;
|
|
||||||
background: @border-color;
|
|
||||||
color: @text-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 状态 Tab:描边分段组,选中实底 #024FFF(设计 30)
|
|
||||||
&__tabs {
|
|
||||||
.flex(row, flex-start);
|
|
||||||
gap: 0;
|
gap: 0;
|
||||||
|
height: 71px;
|
||||||
|
margin-top: 14px; // 统计卡底 238 → 工具条顶 252
|
||||||
|
padding: 0 0 0 22px; // Tab 组 x92,卡边框内起点 x70 → 内缩 22
|
||||||
|
border: 1px solid @finance-card-line;
|
||||||
|
border-radius: @radius-base;
|
||||||
|
background: @white;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tab 组 (92,270.5) 279×34
|
||||||
|
&__tabs {
|
||||||
|
.flex(row, flex-start, center);
|
||||||
|
gap: 0;
|
||||||
|
width: 279px;
|
||||||
|
height: 34px;
|
||||||
border: 1px solid @border-color;
|
border: 1px solid @border-color;
|
||||||
border-radius: @radius-sm;
|
border-radius: 4px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
// Tab 盒 (92,270.5) 279×34 → 文字 ink x103.57;等分 69.75
|
||||||
|
padding: 0 11.3px 0 9.2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__tab {
|
&__tab {
|
||||||
height: 26px;
|
flex: 1 1 0;
|
||||||
padding: 0 14px;
|
height: 32px;
|
||||||
border: none;
|
border: none;
|
||||||
border-right: 1px solid @border-color;
|
border-right: 1px solid @border-color;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
background: @white;
|
background: @white;
|
||||||
color: @text-color;
|
color: @finance-entry-text;
|
||||||
font-size: 11px;
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
@@ -247,34 +271,100 @@ onMounted(() => {
|
|||||||
border-right-color: @primary-strong;
|
border-right-color: @primary-strong;
|
||||||
background: @primary-strong;
|
background: @primary-strong;
|
||||||
color: @white;
|
color: @white;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 表格 + 分页共处白卡(设计 30:分页在卡片内右下角)
|
// 搜索框 (405,269) 1300×37 底 #F3F4F6
|
||||||
|
&__search {
|
||||||
|
width: 1300px;
|
||||||
|
height: 37px;
|
||||||
|
margin-left: 33px; // Tab 组右缘 371 → 搜索框 405
|
||||||
|
padding: 0 16px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
background: @finance-field-bg;
|
||||||
|
color: @finance-entry-text;
|
||||||
|
font-size: 13px;
|
||||||
|
outline: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: @contract-placeholder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__btn {
|
||||||
|
height: 32px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
background: transparent;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
&--reset {
|
||||||
|
width: 62.2px; // 重置 x1721.4 文字 43.2 居中
|
||||||
|
margin-left: 15.4px; // 搜索框右缘 1705 → 重置盒 1721.4
|
||||||
|
color: @finance-entry-text;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: @primary-strong;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--primary {
|
||||||
|
width: 62.2px; // 搜索 x1783.4 文字 43.2 居中
|
||||||
|
color: @white;
|
||||||
|
background: @primary-strong;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: @primary-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表格卡 (70,337) 1780×365.36(含分页)
|
||||||
&__board {
|
&__board {
|
||||||
|
margin-top: 14px; // 工具条底 323 → 表格卡顶 337
|
||||||
|
border: 1px solid @finance-card-line;
|
||||||
border-radius: @radius-base;
|
border-radius: @radius-base;
|
||||||
background: @white;
|
background: @white;
|
||||||
box-shadow: @shadow-base;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分页:右对齐小方格,选中浅蓝底蓝字(设计 30/35)
|
// 分页:右对齐,页码 12/400;设计 (1631.93~1823.93, 670.36)
|
||||||
.pagination {
|
:deep(.pagination) {
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
padding: 12px @spacing-base @spacing-base;
|
gap: 3.7px; // 设计页码间距:盒距 6 → 页码字距 3.7
|
||||||
|
padding: 8px 26px 30px 0; // 下行上缘 662 → 表格底,页码 ink 670.36
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.pagination__btn) {
|
:deep(.pagination__btn) {
|
||||||
min-width: 24px;
|
min-width: 0;
|
||||||
height: 24px;
|
height: 16px;
|
||||||
border-color: @border-color;
|
padding: 0 3px;
|
||||||
border-radius: @radius-sm;
|
border: none;
|
||||||
color: @text-color-secondary;
|
border-radius: 0;
|
||||||
font-size: 11px;
|
background: transparent;
|
||||||
|
color: @finance-muted-text;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
|
||||||
&.is-active {
|
&.is-active {
|
||||||
border-color: @primary-strong;
|
min-width: 21.45px;
|
||||||
background: @finance-page-active;
|
}
|
||||||
|
|
||||||
|
&:hover:not(:disabled):not(.is-ellipsis) {
|
||||||
|
border: none;
|
||||||
|
color: @primary-strong;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-active {
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
color: @primary-strong;
|
color: @primary-strong;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { AppButton } from '@/components'
|
|
||||||
import type { Bill } from '@/types/business/finance'
|
import type { Bill } from '@/types/business/finance'
|
||||||
import { formatMoney } from '@/utils/format'
|
import { formatMoney } from '@/utils/format'
|
||||||
|
|
||||||
@@ -13,12 +12,6 @@ const props = defineProps<Props>()
|
|||||||
|
|
||||||
const emit = defineEmits<{ batchPay: [ids: string[]] }>()
|
const emit = defineEmits<{ batchPay: [ids: string[]] }>()
|
||||||
|
|
||||||
const totalCount = computed(() => props.bills.length)
|
|
||||||
|
|
||||||
const totalAmount = computed(() => props.bills.reduce((sum, bill) => sum + bill.amount, 0))
|
|
||||||
|
|
||||||
const pendingCount = computed(() => props.bills.filter((bill) => bill.status === '待支付').length)
|
|
||||||
|
|
||||||
const selectedBills = computed(() => props.bills.filter((bill) => props.selectedIds.includes(bill.id)))
|
const selectedBills = computed(() => props.bills.filter((bill) => props.selectedIds.includes(bill.id)))
|
||||||
|
|
||||||
const selectedAmount = computed(() =>
|
const selectedAmount = computed(() =>
|
||||||
@@ -34,103 +27,114 @@ function onBatchPay(): void {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- 设计 30:整幅白条——左「已选择 N 笔订单」+ 统计,右「合计:¥ 金额 + 立即支付」 -->
|
<!-- 设计 30:通栏白条 (0,718) 1920×66,左右内容对齐 x27.25 / x1625.03~1855.4 -->
|
||||||
<div class="bill-summary">
|
<div class="bill-summary">
|
||||||
|
<div class="bill-summary__inner">
|
||||||
<div class="bill-summary__left">
|
<div class="bill-summary__left">
|
||||||
<span class="bill-summary__selected">
|
<input
|
||||||
已选择<strong>{{ selectedBills.length }}</strong>笔订单
|
class="bill-summary__check"
|
||||||
</span>
|
type="checkbox"
|
||||||
<div class="bill-summary__stats">
|
:checked="canPay"
|
||||||
<span class="bill-summary__stat">总笔数:<strong>{{ totalCount }}</strong></span>
|
@change="onBatchPay"
|
||||||
<span class="bill-summary__stat">总金额:<strong>¥{{ formatMoney(totalAmount) }}</strong></span>
|
>
|
||||||
<span class="bill-summary__stat">待支付:<strong>{{ pendingCount }}</strong></span>
|
<span class="bill-summary__text">已选择</span>
|
||||||
</div>
|
<span class="bill-summary__count">{{ selectedBills.length }}</span>
|
||||||
|
<span class="bill-summary__text">笔订单</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bill-summary__right">
|
<div class="bill-summary__right">
|
||||||
<span class="bill-summary__label">合计:</span>
|
<span class="bill-summary__text">合计:</span>
|
||||||
<span class="bill-summary__amount">¥{{ formatMoney(selectedAmount) }}</span>
|
<span class="bill-summary__amount">¥ {{ formatMoney(selectedAmount, 0) }}</span>
|
||||||
<AppButton
|
<button class="bill-summary__cta" :disabled="!canPay" @click="onBatchPay">
|
||||||
type="primary"
|
|
||||||
:disabled="!canPay"
|
|
||||||
@click="onBatchPay"
|
|
||||||
>
|
|
||||||
立即支付
|
立即支付
|
||||||
</AppButton>
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.bill-summary {
|
.bill-summary {
|
||||||
.flex(row, space-between, center);
|
position: relative;
|
||||||
gap: @spacing-base;
|
width: 100%;
|
||||||
margin: 0 -16px;
|
height: 66px;
|
||||||
padding: 10px @spacing-base;
|
|
||||||
border-radius: 0;
|
|
||||||
background: @white;
|
background: @white;
|
||||||
|
box-sizing: border-box;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
// 汇总条内容与页面内容区同步留白 70,与上方卡片左右对齐
|
||||||
|
&__inner {
|
||||||
|
.flex(row, space-between, center);
|
||||||
|
width: 1920px;
|
||||||
|
max-width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 70px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
&__left {
|
&__left {
|
||||||
.flex(row, flex-start, center);
|
.flex(row, flex-start, center);
|
||||||
gap: @spacing-lg;
|
gap: 0;
|
||||||
min-width: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__selected {
|
&__check {
|
||||||
font-size: 11px;
|
width: 15px;
|
||||||
color: @text-color;
|
height: 15px;
|
||||||
|
margin: 0 12.25px 0 0;
|
||||||
|
accent-color: @primary-strong;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__text {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 17px;
|
||||||
|
color: @finance-entry-text;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
strong {
|
&__count {
|
||||||
margin: 0 2px;
|
margin: 0 2.27px;
|
||||||
color: @danger-color;
|
color: @danger-color;
|
||||||
|
font-size: 13px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
line-height: 17px;
|
||||||
}
|
|
||||||
|
|
||||||
&__stats {
|
|
||||||
.flex(row, flex-start, center);
|
|
||||||
gap: @spacing-lg;
|
|
||||||
font-size: 11px;
|
|
||||||
color: @text-color;
|
|
||||||
|
|
||||||
strong {
|
|
||||||
color: @heading-color;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__right {
|
&__right {
|
||||||
.flex(row, flex-end, center);
|
.flex(row, flex-end, center);
|
||||||
gap: 6px;
|
gap: 0;
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__label {
|
|
||||||
font-size: 11px;
|
|
||||||
color: @text-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 合计金额:设计 30 实取 20 设计px → 15px 粗体红
|
|
||||||
&__amount {
|
&__amount {
|
||||||
margin-right: @spacing-sm;
|
margin: 0 16px 0 0;
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: @danger-color;
|
color: @danger-color;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 26px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 「立即支付」:设计 30 实取橙色按钮
|
&__cta {
|
||||||
:deep(.app-button--primary) {
|
width: 84px;
|
||||||
height: 28px;
|
height: 40px;
|
||||||
padding: 0 14px;
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
background: @finance-summary-cta;
|
background: @finance-summary-cta;
|
||||||
color: @white;
|
color: @white;
|
||||||
font-size: 11px;
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: background 0.2s;
|
||||||
|
|
||||||
&:hover {
|
&:hover:not(:disabled) {
|
||||||
background: @warning-color;
|
background: @warning-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
import { StatusTag } from '@/components'
|
import type { Bill } from '@/types/business/finance'
|
||||||
import type { Bill, BillStatus } from '@/types/business/finance'
|
|
||||||
import { formatMoney } from '@/utils/format'
|
import { formatMoney } from '@/utils/format'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -27,6 +26,14 @@ watch(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const allSelected = computed(
|
||||||
|
() => props.bills.length > 0 && props.bills.every((bill) => selection.value.has(bill.id)),
|
||||||
|
)
|
||||||
|
|
||||||
|
const someSelected = computed(
|
||||||
|
() => props.bills.some((bill) => selection.value.has(bill.id)) && !allSelected.value,
|
||||||
|
)
|
||||||
|
|
||||||
function toggle(id: string): void {
|
function toggle(id: string): void {
|
||||||
if (selection.value.has(id)) selection.value.delete(id)
|
if (selection.value.has(id)) selection.value.delete(id)
|
||||||
else selection.value.add(id)
|
else selection.value.add(id)
|
||||||
@@ -34,8 +41,7 @@ function toggle(id: string): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function toggleAll(): void {
|
function toggleAll(): void {
|
||||||
const allSelected = props.bills.length > 0 && props.bills.every((b) => selection.value.has(b.id))
|
if (allSelected.value) {
|
||||||
if (allSelected) {
|
|
||||||
for (const bill of props.bills) selection.value.delete(bill.id)
|
for (const bill of props.bills) selection.value.delete(bill.id)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -44,57 +50,81 @@ function toggleAll(): void {
|
|||||||
emit('update:selection', [...selection.value])
|
emit('update:selection', [...selection.value])
|
||||||
}
|
}
|
||||||
|
|
||||||
const allSelected = () => props.bills.length > 0 && props.bills.every((b) => selection.value.has(b.id))
|
/** 列宽(设计 30 表头实测:序号 x121.5 / 账单名称 x384.9 / 账单号 x695.09 / 流水号 x843.12 /
|
||||||
|
* 项目名称 x1237.19 / 订单金额 x1576.6 / 状态 x1673.6 / 操作 x1768.6) */
|
||||||
const STATUS_VARIANTS: Record<BillStatus, 'warning' | 'success' | 'info' | 'danger'> = {
|
const COLUMNS = [
|
||||||
待支付: 'warning',
|
{ key: 'index', label: '序号', className: 'bill-table__col--index' },
|
||||||
已支付: 'success',
|
{ key: 'planName', label: '账单名称', className: 'bill-table__col--name' },
|
||||||
未到期: 'info',
|
{ key: 'billNo', label: '账单号', className: 'bill-table__col--no' },
|
||||||
已取消: 'danger',
|
{ key: 'tradeNo', label: '交易流水号', className: 'bill-table__col--trade' },
|
||||||
}
|
{ key: 'projectName', label: '项目名称', className: 'bill-table__col--project' },
|
||||||
|
{ key: 'amount', label: '订单金额', className: 'bill-table__col--amount' },
|
||||||
|
{ key: 'status', label: '状态', className: 'bill-table__col--status' },
|
||||||
|
{ key: 'ops', label: '操作', className: 'bill-table__col--ops' },
|
||||||
|
] as const
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="bill-table card">
|
<table class="bill-table">
|
||||||
<table class="bill-table__table">
|
<colgroup>
|
||||||
|
<col class="bill-table__col--check">
|
||||||
|
<col
|
||||||
|
v-for="column in COLUMNS"
|
||||||
|
:key="column.key"
|
||||||
|
:class="column.className"
|
||||||
|
>
|
||||||
|
</colgroup>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="bill-table__check">
|
<th class="bill-table__cell bill-table__cell--check">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:checked="allSelected()"
|
:checked="allSelected"
|
||||||
|
:indeterminate="someSelected"
|
||||||
@change="toggleAll"
|
@change="toggleAll"
|
||||||
>
|
>
|
||||||
</th>
|
</th>
|
||||||
<th>序号</th>
|
<th
|
||||||
<th>账单名称</th>
|
v-for="column in COLUMNS"
|
||||||
<th>账单号</th>
|
:key="column.key"
|
||||||
<th>交易流水号</th>
|
class="bill-table__cell"
|
||||||
<th>项目名称</th>
|
>
|
||||||
<th>订单金额</th>
|
{{ column.label }}
|
||||||
<th>状态</th>
|
</th>
|
||||||
<th>操作</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="(bill, index) in bills" :key="bill.id">
|
<tr v-for="(bill, index) in bills" :key="bill.id">
|
||||||
<td class="bill-table__check">
|
<td class="bill-table__cell bill-table__cell--check">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:checked="selection.has(bill.id)"
|
:checked="selection.has(bill.id)"
|
||||||
@change="toggle(bill.id)"
|
@change="toggle(bill.id)"
|
||||||
>
|
>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ index + 1 }}</td>
|
<td class="bill-table__cell">
|
||||||
<td>{{ bill.planName }}</td>
|
{{ index + 1 }}
|
||||||
<td>{{ bill.billNo }}</td>
|
|
||||||
<td>{{ bill.tradeNo ?? '—' }}</td>
|
|
||||||
<td>{{ bill.projectName }}</td>
|
|
||||||
<td class="bill-table__amount">¥{{ formatMoney(bill.amount) }}</td>
|
|
||||||
<td>
|
|
||||||
<StatusTag :label="bill.status" :variant="STATUS_VARIANTS[bill.status]" />
|
|
||||||
</td>
|
</td>
|
||||||
<td class="bill-table__ops">
|
<td class="bill-table__cell">
|
||||||
|
{{ bill.planName }}
|
||||||
|
</td>
|
||||||
|
<td class="bill-table__cell">
|
||||||
|
{{ bill.billNo }}
|
||||||
|
</td>
|
||||||
|
<td class="bill-table__cell">
|
||||||
|
{{ bill.tradeNo ?? '—' }}
|
||||||
|
</td>
|
||||||
|
<td class="bill-table__cell">
|
||||||
|
{{ bill.projectName }}
|
||||||
|
</td>
|
||||||
|
<td class="bill-table__cell">
|
||||||
|
{{ formatMoney(bill.amount, 0) }}
|
||||||
|
</td>
|
||||||
|
<td class="bill-table__cell">
|
||||||
|
{{ bill.status }}
|
||||||
|
</td>
|
||||||
|
<td class="bill-table__cell bill-table__cell--ops">
|
||||||
|
<span class="bill-table__ops">
|
||||||
<button
|
<button
|
||||||
v-if="bill.status === '待支付'"
|
v-if="bill.status === '待支付'"
|
||||||
class="bill-table__link"
|
class="bill-table__link"
|
||||||
@@ -105,116 +135,147 @@ const STATUS_VARIANTS: Record<BillStatus, 'warning' | 'success' | 'info' | 'dang
|
|||||||
<button class="bill-table__link" @click="emit('view', bill.id)">
|
<button class="bill-table__link" @click="emit('view', bill.id)">
|
||||||
详情
|
详情
|
||||||
</button>
|
</button>
|
||||||
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="bills.length === 0">
|
<tr v-if="bills.length === 0">
|
||||||
<td colspan="9" class="bill-table__empty text-secondary">
|
<td colspan="9" class="bill-table__empty">
|
||||||
暂无账单数据
|
暂无账单数据
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
// 设计 30:表格贴满白卡(外层 .bills__board 提供卡底),全网格浅灰线 + 浅灰表头
|
// 设计 30:表头 (77.5,344.5) 1765×41.77 底 #F1F3F5;行高 43.77;文字 12px #4B5563
|
||||||
.bill-table {
|
.bill-table {
|
||||||
padding: 0;
|
width: 1765px;
|
||||||
border-radius: 0;
|
margin: 6.5px 0 0 14.5px; // 表头 x77.5 / y344.5,卡片内缘 x71 / y338
|
||||||
background: transparent;
|
|
||||||
box-shadow: none;
|
|
||||||
overflow-x: auto;
|
|
||||||
|
|
||||||
&__table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
font-size: 11px;
|
table-layout: fixed;
|
||||||
color: @text-color;
|
font-size: 12px;
|
||||||
|
color: @finance-entry-text;
|
||||||
|
|
||||||
th,
|
// 列宽按设计表头 x 递推(表头起点 x78,实宽 1765):
|
||||||
td {
|
// 序号 x121.5|账单名称 x384.9|账单号 x695.09|流水 x843.12|项目 x1237.19|金额 x1576.6|状态 x1673.6|操作 x1768.6
|
||||||
padding: 11px 10px;
|
&__col--check { width: 51px; }
|
||||||
|
&__col--index { width: 235.4px; }
|
||||||
|
&__col--name { width: 310.2px; }
|
||||||
|
&__col--no { width: 148px; }
|
||||||
|
&__col--trade { width: 394.1px; }
|
||||||
|
&__col--project { width: 339.4px; }
|
||||||
|
&__col--amount { width: 97px; }
|
||||||
|
&__col--status { width: 95px; }
|
||||||
|
&__col--ops { width: 95.5px; }
|
||||||
|
|
||||||
|
&__cell {
|
||||||
|
height: 43.77px;
|
||||||
|
padding: 0;
|
||||||
|
border-bottom: 1px solid @project-divider;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
vertical-align: middle;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
border: 1px solid @project-divider;
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
// 单元格统一左内缩 10(除已单独指定的列)
|
||||||
|
&:not(&--check):not(&--ops) {
|
||||||
|
padding-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
th {
|
&--check {
|
||||||
|
padding-left: 43.5px; // 勾选框/序号文字 x121.5 → 表头起点 78
|
||||||
|
}
|
||||||
|
|
||||||
|
// 操作列内层 flex 已负责右对齐;单元格本身只管纵向居中
|
||||||
|
&--ops {
|
||||||
|
padding-right: 22px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 各列文字起点对齐设计 x(表头起点 78):内缩 = 设计 x − 列起点
|
||||||
|
@col-pad-1: 43.5px; // 序号 121.5
|
||||||
|
@col-pad-2: 149.9px; // 账单名称 384.9
|
||||||
|
@col-pad-3: 8px; // 账单号 695.09
|
||||||
|
@col-pad-4: 17px; // 流水号 843.12
|
||||||
|
@col-pad-5: 5px; // 项目名称 1237.19
|
||||||
|
@col-pad-6: 14.6px; // 订单金额 1576.6
|
||||||
|
@col-pad-7: 4.6px; // 状态 1673.6
|
||||||
|
@col-pad-8: 6px; // 操作 1768.6(右对齐由内层 flex 兜)
|
||||||
|
|
||||||
|
thead &__cell,
|
||||||
|
tbody &__cell {
|
||||||
|
&:nth-child(1) { padding-left: @col-pad-1; }
|
||||||
|
&:nth-child(2) { padding-left: @col-pad-2; }
|
||||||
|
&:nth-child(3) { padding-left: @col-pad-3; }
|
||||||
|
&:nth-child(4) { padding-left: @col-pad-4; }
|
||||||
|
&:nth-child(5) { padding-left: @col-pad-5; }
|
||||||
|
&:nth-child(6) { padding-left: @col-pad-6; }
|
||||||
|
&:nth-child(7) { padding-left: @col-pad-7; }
|
||||||
|
&:nth-child(8) { padding-left: @col-pad-8; }
|
||||||
|
&:nth-child(9) { padding-left: 0; padding-right: 10px; }
|
||||||
|
}
|
||||||
|
|
||||||
|
thead &__cell {
|
||||||
|
height: 41.77px;
|
||||||
background: @finance-table-head-bg;
|
background: @finance-table-head-bg;
|
||||||
color: @text-color;
|
border-right: 1px solid @project-divider;
|
||||||
|
color: @finance-entry-text;
|
||||||
|
font-size: 12px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-align: center;
|
|
||||||
|
&:first-child {
|
||||||
|
border-left: 1px solid @project-divider;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tbody tr:hover {
|
tbody tr:hover {
|
||||||
background: @bg-color-light;
|
background: @bg-color-light;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 列对齐按设计 30:序号/账单号/流水号/金额/状态/操作居中,名称类左对齐
|
&__cell--check input {
|
||||||
&__table th:nth-child(1),
|
|
||||||
&__table th:nth-child(2),
|
|
||||||
&__table td:nth-child(1),
|
|
||||||
&__table td:nth-child(2),
|
|
||||||
&__table th:nth-child(4),
|
|
||||||
&__table td:nth-child(4),
|
|
||||||
&__table th:nth-child(5),
|
|
||||||
&__table td:nth-child(5),
|
|
||||||
&__table th:nth-child(7),
|
|
||||||
&__table td:nth-child(7),
|
|
||||||
&__table th:nth-child(8),
|
|
||||||
&__table td:nth-child(8),
|
|
||||||
&__table th:nth-child(9),
|
|
||||||
&__table td:nth-child(9) {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__check {
|
|
||||||
width: 44px;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
input {
|
|
||||||
width: 14px;
|
width: 14px;
|
||||||
height: 14px;
|
height: 14px;
|
||||||
|
margin: 0;
|
||||||
accent-color: @primary-strong;
|
accent-color: @primary-strong;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 金额列与正文同色(设计 30 实取 #4B5563 常规字重)
|
|
||||||
&__amount {
|
|
||||||
color: @text-color;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__ops {
|
&__ops {
|
||||||
.flex(row, center, center);
|
display: inline-flex;
|
||||||
gap: 12px;
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__link {
|
&__link {
|
||||||
|
// 设计:操作列起点 1746,末位「详情」右缘 1820.36 → 单元格右缘 1842.5
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
color: @primary-strong;
|
color: @primary-strong;
|
||||||
font-size: 11px;
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
transition: color 0.2s;
|
transition: color 0.2s;
|
||||||
|
|
||||||
|
// 「去支付」「详情」并排:设计文字盒距 18.36
|
||||||
|
+ .bill-table__link {
|
||||||
|
margin-left: 18.36px;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: @primary-color;
|
color: @primary-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__empty {
|
&__empty {
|
||||||
|
height: 43.77px;
|
||||||
|
color: @project-muted;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: @spacing-xl 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 状态列按设计 30 为纯文字(去底色徽标)
|
|
||||||
:deep(.status-tag) {
|
|
||||||
padding: 0;
|
|
||||||
background: transparent;
|
|
||||||
color: @text-color;
|
|
||||||
font-size: 11px;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const props = defineProps<Props>()
|
|||||||
|
|
||||||
const total = computed(() => props.distribution.reduce((sum, item) => sum + item.value, 0))
|
const total = computed(() => props.distribution.reduce((sum, item) => sum + item.value, 0))
|
||||||
|
|
||||||
/** CSS conic-gradient(无图表库):按 value 占比切分色环 */
|
/** CSS conic-gradient(无图表库):按 value 占比切分色环(本页隐藏,仅保留绑定) */
|
||||||
const gradient = computed(() => {
|
const gradient = computed(() => {
|
||||||
if (total.value <= 0) return 'conic-gradient(#e4e7ec 0% 100%)'
|
if (total.value <= 0) return 'conic-gradient(#e4e7ec 0% 100%)'
|
||||||
let acc = 0
|
let acc = 0
|
||||||
@@ -31,23 +31,33 @@ function percentOf(value: number): number {
|
|||||||
if (total.value <= 0) return 0
|
if (total.value <= 0) return 0
|
||||||
return Math.round((value / total.value) * 100)
|
return Math.round((value / total.value) * 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 设计稿 Y 轴刻度(等距 5 档) */
|
||||||
|
const AXIS = [250, 200, 150, 100, 50]
|
||||||
|
/** 设计稿 X 轴月份刻度 */
|
||||||
|
const MONTHS = ['1月', '2月', '3月', '4月', '5月']
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="finance-chart card">
|
<div class="finance-chart">
|
||||||
<h3 class="finance-chart__title">
|
<h3 class="finance-chart__title">
|
||||||
业务类型分布
|
业务类型分布
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div class="finance-chart__body">
|
<div class="finance-chart__body">
|
||||||
<!-- 设计 37:横向网格线 + 等宽柱条(柱高按占比换算) -->
|
<!-- Y 轴刻度 10/400 #9CA3AF -->
|
||||||
|
<ul class="finance-chart__axis" aria-hidden="true">
|
||||||
|
<li v-for="v in AXIS" :key="v" class="finance-chart__axis-item">
|
||||||
|
{{ v }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<div class="finance-chart__plot">
|
<div class="finance-chart__plot">
|
||||||
|
<!-- 5 条横向网格线(设计 #EDEFF2,1px) -->
|
||||||
<div class="finance-chart__grid" aria-hidden="true">
|
<div class="finance-chart__grid" aria-hidden="true">
|
||||||
<i class="finance-chart__line" />
|
<i v-for="v in AXIS" :key="v" class="finance-chart__line" />
|
||||||
<i class="finance-chart__line" />
|
|
||||||
<i class="finance-chart__line" />
|
|
||||||
<i class="finance-chart__line" />
|
|
||||||
<i class="finance-chart__line" />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="finance-chart__bars">
|
<div class="finance-chart__bars">
|
||||||
<span
|
<span
|
||||||
v-for="item in distribution"
|
v-for="item in distribution"
|
||||||
@@ -56,42 +66,54 @@ function percentOf(value: number): number {
|
|||||||
:style="{ height: `${Math.max(percentOf(item.value), 6)}%` }"
|
:style="{ height: `${Math.max(percentOf(item.value), 6)}%` }"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<!-- 数据源色环:设计稿为柱状图,此处仅保留绑定(隐藏)以免游离变量 -->
|
<!-- 数据源色环:设计稿为柱状图,此处仅保留绑定(隐藏)以免游离变量 -->
|
||||||
<div class="finance-chart__pie" :style="{ background: gradient }" />
|
<div class="finance-chart__pie" :style="{ background: gradient }" />
|
||||||
|
|
||||||
|
<!-- X 轴 11/400 #6B7280 -->
|
||||||
|
<ul class="finance-chart__months" aria-hidden="true">
|
||||||
|
<li v-for="m in MONTHS" :key="m" class="finance-chart__month">
|
||||||
|
{{ m }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ul class="finance-chart__legend">
|
<ul class="finance-chart__legend">
|
||||||
<li
|
<li
|
||||||
v-for="item in distribution"
|
v-for="item in distribution"
|
||||||
:key="item.name"
|
:key="item.name"
|
||||||
class="finance-chart__legend-item"
|
class="finance-chart__legend-item"
|
||||||
>
|
>
|
||||||
<span
|
|
||||||
class="finance-chart__dot"
|
|
||||||
:style="{ background: item.color }"
|
|
||||||
/>
|
|
||||||
<span class="finance-chart__name">{{ item.name }}</span>
|
<span class="finance-chart__name">{{ item.name }}</span>
|
||||||
<span class="finance-chart__value">{{ percentOf(item.value) }}%</span>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
// 设计 37:白卡 + 蓝色竖条标题(15 设计px → 12px/700)+ 柱状图 + 横排图例
|
// 设计 6cd4aa76:图表卡 (70,344) 1780×378、白底 + #F0F1F3 描边;
|
||||||
|
// 标题 ink (82,367) 15/700 #1F2937(竖条 4×20 @#024FFF);
|
||||||
|
// Y 轴刻度 ink100 (103,403/461.5/520/578.5/637) 10/400 #9CA3AF,等距 58.5;
|
||||||
|
// 网格线 x137..1823 宽 1686 @408/466.5/525/583.5/642;X 轴月份 ink100 y649 11/400;
|
||||||
|
// 图例 ink100 y684(互联网软件 x809 / 物联网全案 x903 / 硬件电路 x997 / 生产制造 x1080)11/400
|
||||||
.finance-chart {
|
.finance-chart {
|
||||||
padding: 20px @spacing-lg @spacing-lg;
|
position: relative;
|
||||||
|
height: 378px; // 设计 6cd4aa76:图表卡 (70,344) 1780×378
|
||||||
|
margin-top: 14px; // 概览行底 330 → 图表卡顶 344
|
||||||
|
padding: 23px 23px 0; // 标题 ink y367 → 卡内 23(含 1px 描边);左右 x82 → 卡内 12
|
||||||
|
border: 1px solid @finance-card-line;
|
||||||
border-radius: @radius-base;
|
border-radius: @radius-base;
|
||||||
background: @white;
|
background: @white;
|
||||||
box-shadow: @shadow-base;
|
box-sizing: border-box;
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-left: 10px;
|
padding-left: 12px;
|
||||||
margin-bottom: @spacing-base;
|
font-size: 15px;
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: @heading-color;
|
line-height: 20px;
|
||||||
|
color: @finance-ink;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: '';
|
content: '';
|
||||||
@@ -99,22 +121,44 @@ function percentOf(value: number): number {
|
|||||||
top: 50%;
|
top: 50%;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 4px;
|
width: 4px;
|
||||||
height: 14px;
|
height: 20px;
|
||||||
border-radius: @radius-round;
|
border-radius: @radius-round;
|
||||||
background: @primary-strong;
|
background: @primary-strong;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 绘图区:标题盒底 387 → 首条网格线 408(21)
|
||||||
&__body {
|
&__body {
|
||||||
.flex(column, center, center);
|
.flex(row, flex-start, flex-start);
|
||||||
gap: @spacing-base;
|
gap: 0;
|
||||||
|
margin-top: 21px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Y 轴刻度列:刻度 ink x103(卡内 33)→ 网格线 x137(卡内 67)
|
||||||
|
&__axis {
|
||||||
|
.flex(column, space-between, flex-start);
|
||||||
|
width: 34px;
|
||||||
|
height: 234px; // 网格线 408 → 642(5 档 4 段,段距 58.5)
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__axis-item {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 14px;
|
||||||
|
color: @finance-axis-text;
|
||||||
|
transform: translateY(-7px); // 刻度 ink 与网格线同一 y 对齐(去掉行盒偏移)
|
||||||
}
|
}
|
||||||
|
|
||||||
&__plot {
|
&__plot {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
flex: 1;
|
||||||
height: 184px;
|
min-width: 0;
|
||||||
|
height: 234px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__grid {
|
&__grid {
|
||||||
@@ -134,7 +178,7 @@ function percentOf(value: number): number {
|
|||||||
.flex(row, space-around, flex-end);
|
.flex(row, space-around, flex-end);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
padding: 0 60px;
|
padding: 0 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__bar {
|
&__bar {
|
||||||
@@ -149,34 +193,44 @@ function percentOf(value: number): number {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__legend {
|
// X 轴月份:网格线底 642 → 月份 ink 649(行盒顶 645)
|
||||||
.flex(row, center, center);
|
&__months {
|
||||||
gap: @spacing-lg;
|
.flex(row, space-around, flex-start);
|
||||||
list-style: none;
|
position: absolute;
|
||||||
|
top: 237px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__month {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 15px;
|
||||||
|
color: @finance-muted-text;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图例:月份盒底 662 → 图例 ink 684(行盒顶 681)
|
||||||
|
&__legend {
|
||||||
|
.flex(row, flex-start, center);
|
||||||
|
gap: 27px;
|
||||||
|
margin: 41px 0 0;
|
||||||
|
padding: 0 0 0 662px; // 图例首项 ink x809 → 卡内 692(轴 34 + 网格起点)
|
||||||
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__legend-item {
|
&__legend-item {
|
||||||
.flex(row, flex-start, center);
|
.flex(row, flex-start, center);
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: @text-color-secondary;
|
font-weight: 400;
|
||||||
}
|
color: @finance-muted-text;
|
||||||
|
|
||||||
&__dot {
|
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
border-radius: @radius-round;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__name {
|
&__name {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__value {
|
|
||||||
color: @heading-color;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { StatusTag } from '@/components'
|
|
||||||
import type { FinanceStats } from '@/types/business/finance'
|
import type { FinanceStats } from '@/types/business/finance'
|
||||||
import { formatMoney } from '@/utils/format'
|
import { formatMoney } from '@/utils/format'
|
||||||
|
|
||||||
@@ -16,140 +15,73 @@ const emit = defineEmits<{
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="finance-overview card">
|
<div class="finance-overview">
|
||||||
|
<!-- 卡内文字块:相对卡顶 26(设计 221 − 195) -->
|
||||||
<div class="finance-overview__main">
|
<div class="finance-overview__main">
|
||||||
<p class="finance-overview__label text-secondary">
|
<p class="finance-overview__label">
|
||||||
累计消费总额
|
累计消费总额
|
||||||
</p>
|
</p>
|
||||||
<p class="finance-overview__amount">
|
<p class="finance-overview__amount">
|
||||||
¥ {{ formatMoney(stats?.totalExpense ?? 0) }}
|
¥ {{ formatMoney(stats?.totalExpense ?? 0) }}
|
||||||
</p>
|
</p>
|
||||||
<div class="finance-overview__meta">
|
|
||||||
<div class="finance-overview__periods">
|
|
||||||
<button
|
|
||||||
class="finance-overview__period"
|
|
||||||
@click="emit('periodChange', 'quarter')"
|
|
||||||
>
|
|
||||||
本季度
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="finance-overview__period"
|
|
||||||
@click="emit('periodChange', 'year')"
|
|
||||||
>
|
|
||||||
本年
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<span class="finance-overview__month text-secondary">{{ stats?.month }}</span>
|
|
||||||
<StatusTag
|
|
||||||
v-if="(stats?.pendingCount ?? 0) > 0"
|
|
||||||
:label="`待支付 ${stats?.pendingCount ?? 0}`"
|
|
||||||
variant="warning"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="finance-overview__actions">
|
<!-- 右上角「2026年账单 ›」 -->
|
||||||
<RouterLink to="/finance/invoices" class="finance-overview__action">
|
<button class="finance-overview__period-link" @click="emit('periodChange', 'year')">
|
||||||
发票申请
|
{{ stats?.month || '2026年账单' }}
|
||||||
</RouterLink>
|
<span aria-hidden="true">›</span>
|
||||||
<RouterLink to="/finance/bills" class="finance-overview__action">
|
|
||||||
账单明细
|
|
||||||
</RouterLink>
|
|
||||||
<button class="finance-overview__action" @click="emit('export')">
|
|
||||||
报表导出
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
// 设计 37:整卡为蓝紫渐变(累计消费总额白字),右上角白色操作链接
|
// 设计 6cd4aa76:渐变卡 (70,195) 1093.84×135;标签 (100,221) 13/400 #FFFFFF;
|
||||||
|
// 金额 (100,254) 38/700;右上角「2026年账单 ›」(1066.98,220) 11/500
|
||||||
.finance-overview {
|
.finance-overview {
|
||||||
.flex(column, flex-start, stretch);
|
position: relative;
|
||||||
flex: 1 1 500px;
|
flex: 1 1 auto;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
padding: 14px @spacing-lg 16px;
|
height: 135px;
|
||||||
border-radius: @radius-base;
|
border-radius: @radius-base;
|
||||||
background: @finance-hero-gradient;
|
background: @finance-hero-gradient;
|
||||||
box-shadow: @shadow-base;
|
box-sizing: border-box;
|
||||||
|
|
||||||
&__main {
|
&__main {
|
||||||
.flex(column, flex-start, flex-start);
|
.flex(column, flex-start, flex-start);
|
||||||
gap: 6px;
|
padding: 26px 0 0 30px; // 标签 ink (100,221) → 卡内 (30,26)
|
||||||
|
gap: 16px; // 标签盒底 238 → 金额盒顶 254
|
||||||
}
|
}
|
||||||
|
|
||||||
&__label {
|
&__label {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 17px;
|
||||||
color: @white;
|
color: @white;
|
||||||
font-size: 11px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__amount {
|
&__amount {
|
||||||
color: @white;
|
font-size: 38px;
|
||||||
font-size: 29px;
|
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 1.15;
|
line-height: 50px;
|
||||||
}
|
|
||||||
|
|
||||||
&__meta {
|
|
||||||
.flex(row, flex-start, center);
|
|
||||||
gap: @spacing-base;
|
|
||||||
margin-top: 4px;
|
|
||||||
font-size: 11px;
|
|
||||||
color: fade(@white, 85%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&__periods {
|
|
||||||
.flex(row, flex-start);
|
|
||||||
gap: @spacing-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__period {
|
|
||||||
height: 22px;
|
|
||||||
padding: 0 @spacing-sm;
|
|
||||||
border: 1px solid fade(@white, 45%);
|
|
||||||
border-radius: @radius-sm;
|
|
||||||
background: fade(@white, 16%);
|
|
||||||
color: @white;
|
|
||||||
font-size: 11px;
|
|
||||||
transition: all 0.2s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-color: @white;
|
|
||||||
background: fade(@white, 30%);
|
|
||||||
color: @white;
|
color: @white;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
&__month {
|
// 右上角链接:设计 x1066.98 y220 → 卡内右缘 27、顶 25
|
||||||
|
&__period-link {
|
||||||
|
position: absolute;
|
||||||
|
top: 25px;
|
||||||
|
right: 27px;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: fade(@white, 85%);
|
font-weight: 500;
|
||||||
}
|
line-height: 15px;
|
||||||
|
|
||||||
// 待支付小徽标压到渐变上:半透明白底 + 白字
|
|
||||||
:deep(.status-tag) {
|
|
||||||
background: fade(@white, 20%);
|
|
||||||
color: @white;
|
color: @white;
|
||||||
font-size: 11px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 操作链接置顶右对齐(对应设计右上角「2026年账单 ›」位置)
|
|
||||||
&__actions {
|
|
||||||
order: -1;
|
|
||||||
align-self: stretch;
|
|
||||||
.flex(row, flex-end, center);
|
|
||||||
gap: @spacing-base;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__action {
|
|
||||||
font-size: 11px;
|
|
||||||
color: fade(@white, 92%);
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: color 0.2s;
|
transition: opacity 0.2s;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: @white;
|
opacity: 0.8;
|
||||||
text-decoration: underline;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { StatusTag } from '@/components'
|
|
||||||
import type { InvoiceTitle } from '@/types/business/finance'
|
import type { InvoiceTitle } from '@/types/business/finance'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -16,10 +15,11 @@ const emit = defineEmits<{
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- 设计 35/36:一行一张宽卡——左上默认徽标、右上操作按钮,下方三栏字段 -->
|
<!-- 设计 35/36:整幅宽卡——左侧「默认抬头」蓝胶囊,右侧操作按钮;下方三栏字段 -->
|
||||||
<div class="invoice-title-card">
|
<div class="invoice-title-card">
|
||||||
<div class="invoice-title-card__top">
|
<div class="invoice-title-card__top">
|
||||||
<StatusTag v-if="invoiceTitle.isDefault" label="默认" variant="success" />
|
<span v-if="invoiceTitle.isDefault" class="invoice-title-card__badge">默认抬头</span>
|
||||||
|
<span v-else class="invoice-title-card__badge-placeholder" />
|
||||||
<div class="invoice-title-card__actions">
|
<div class="invoice-title-card__actions">
|
||||||
<button
|
<button
|
||||||
class="invoice-title-card__btn"
|
class="invoice-title-card__btn"
|
||||||
@@ -27,7 +27,7 @@ const emit = defineEmits<{
|
|||||||
>
|
>
|
||||||
{{ invoiceTitle.isDefault ? '取消默认' : '设为默认' }}
|
{{ invoiceTitle.isDefault ? '取消默认' : '设为默认' }}
|
||||||
</button>
|
</button>
|
||||||
<button class="invoice-title-card__btn is-danger" @click="emit('delete', invoiceTitle.id)">
|
<button class="invoice-title-card__btn" @click="emit('delete', invoiceTitle.id)">
|
||||||
删除
|
删除
|
||||||
</button>
|
</button>
|
||||||
<button class="invoice-title-card__btn" @click="emit('edit', invoiceTitle)">
|
<button class="invoice-title-card__btn" @click="emit('edit', invoiceTitle)">
|
||||||
@@ -38,112 +38,114 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
<div class="invoice-title-card__cols">
|
<div class="invoice-title-card__cols">
|
||||||
<div class="invoice-title-card__col">
|
<div class="invoice-title-card__col">
|
||||||
<StatusTag
|
<span class="invoice-title-card__label">抬头类型</span>
|
||||||
:label="invoiceTitle.type"
|
<span class="invoice-title-card__value">{{ invoiceTitle.type }}</span>
|
||||||
:variant="invoiceTitle.type === '企业单位' ? 'primary' : 'info'"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="invoice-title-card__col">
|
<div class="invoice-title-card__col">
|
||||||
<span class="invoice-title-card__label text-secondary">发票抬头</span>
|
<span class="invoice-title-card__label">发票抬头</span>
|
||||||
<p class="invoice-title-card__title">
|
<span class="invoice-title-card__value">{{ invoiceTitle.title }}</span>
|
||||||
{{ invoiceTitle.title }}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="invoice-title-card__col">
|
<div class="invoice-title-card__col">
|
||||||
<span class="invoice-title-card__label text-secondary">税号</span>
|
<span class="invoice-title-card__label">税号</span>
|
||||||
<span class="invoice-title-card__tax-no">{{ invoiceTitle.taxNo }}</span>
|
<span class="invoice-title-card__value">{{ invoiceTitle.taxNo }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
// 设计 35:卡片 (70,229) 1780×129 / (70,372) 1780×88
|
||||||
.invoice-title-card {
|
.invoice-title-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 16px;
|
gap: 0;
|
||||||
padding: 14px 20px;
|
width: 1780px;
|
||||||
border: none;
|
min-height: 88px;
|
||||||
|
padding: 0 33.56px 0 27px; // 徽标 x98.58、按钮右缘 1820.6(卡内 1750.6)
|
||||||
|
border: 1px solid @finance-card-line;
|
||||||
border-radius: @radius-base;
|
border-radius: @radius-base;
|
||||||
background: @white;
|
background: @white;
|
||||||
box-shadow: @shadow-base;
|
box-sizing: border-box;
|
||||||
|
|
||||||
// 顶行:默认徽标居左、操作按钮居右(设计 35 同一行)
|
// 顶行:徽标 (98.58,249) 88.17×22 + 操作按钮 (1632.4,277.5) 各 12/500
|
||||||
&__top {
|
&__top {
|
||||||
.flex(row, space-between, center);
|
.flex(row, space-between, center);
|
||||||
min-height: 24px;
|
height: 49px; // 徽标盒 y249(卡顶 229 + 20 居中)
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 「默认抬头」胶囊:蓝底白字 11/500
|
||||||
|
&__badge {
|
||||||
|
.flex(row, center, center);
|
||||||
|
height: 22px;
|
||||||
|
padding: 0 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: @primary-strong;
|
||||||
|
color: @white;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__badge-placeholder {
|
||||||
|
height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 操作按钮:纯文字 12/500 #4B5563,盒距 20.8(文字盒 67.2/43.2/43.2 + 间隔 4)
|
||||||
&__actions {
|
&__actions {
|
||||||
.flex(row, flex-end, center);
|
.flex(row, flex-end, center);
|
||||||
gap: @spacing-sm;
|
gap: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 描边小按钮:浅灰边白底 11px(设计 35 实取)
|
|
||||||
&__btn {
|
&__btn {
|
||||||
height: 24px;
|
padding: 0;
|
||||||
padding: 0 10px;
|
border: none;
|
||||||
border: 1px solid @border-color;
|
background: transparent;
|
||||||
border-radius: @radius-sm;
|
color: @finance-entry-text;
|
||||||
background: @white;
|
font-size: 12px;
|
||||||
color: @text-color;
|
font-weight: 500;
|
||||||
font-size: 11px;
|
line-height: 16px;
|
||||||
cursor: pointer;
|
transition: color 0.2s;
|
||||||
transition: all 0.2s;
|
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
&:hover {
|
||||||
border-color: @primary-strong;
|
|
||||||
color: @primary-strong;
|
color: @primary-strong;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.is-danger:hover:not(:disabled) {
|
|
||||||
border-color: @danger-color;
|
|
||||||
color: @danger-color;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 三栏字段:列起点按设计 35(x97 / x579 / x1061,卡内 0 / 371 / 742 CSS)
|
// 三栏:列起点(卡内,卡 x70 → 内容 x97/579/1061)= 0 / 482 / 964
|
||||||
&__cols {
|
&__cols {
|
||||||
display: grid;
|
.flex(row, flex-start, flex-start);
|
||||||
grid-template-columns: minmax(0, 371px) minmax(0, 371px) minmax(0, 1fr);
|
gap: 0;
|
||||||
gap: @spacing-base;
|
padding-top: 2px; // 顶行 59 → 标签 ink y293(卡内 64)
|
||||||
|
padding-bottom: 30px; // 值底 y335(卡内 106)→ 卡底 358
|
||||||
}
|
}
|
||||||
|
|
||||||
&__col {
|
&__col {
|
||||||
.flex(column, flex-start, flex-start);
|
.flex(column, flex-start, flex-start);
|
||||||
gap: 6px;
|
gap: 8px; // 标签 ink 293 → 值 ink 316
|
||||||
|
width: 482px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
width: auto;
|
||||||
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 标签 11 设计px → 11px 灰;值 14 设计px → 11px 粗体深色
|
|
||||||
&__label {
|
&__label {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 15px;
|
||||||
|
color: @finance-muted-text;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__title,
|
&__value {
|
||||||
&__tax-no {
|
font-size: 14px;
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: @heading-color;
|
line-height: 19px;
|
||||||
|
color: @finance-ink;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 抬头类型值:去掉徽标底色,按设计为纯文字
|
|
||||||
:deep(.status-tag) {
|
|
||||||
padding: 0;
|
|
||||||
background: transparent;
|
|
||||||
color: @heading-color;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 700;
|
|
||||||
align-self: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 「默认」徽标:设计 35 为实底蓝底白字小胶囊
|
|
||||||
:deep(.status-tag--success) {
|
|
||||||
padding: 2px 8px;
|
|
||||||
background: @primary-strong;
|
|
||||||
color: @white;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { onMounted } from 'vue'
|
|||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { RouterLink } from 'vue-router'
|
import { RouterLink } from 'vue-router'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { PageHead } from '@/components'
|
||||||
// 页面容器:只做组件组合与事件中转,数据只经 store
|
// 页面容器:只做组件组合与事件中转,数据只经 store
|
||||||
import { useFinanceStore } from '@/stores/modules/finance'
|
import { useFinanceStore } from '@/stores/modules/finance'
|
||||||
import FinanceChart from './cpns/FinanceChart.vue'
|
import FinanceChart from './cpns/FinanceChart.vue'
|
||||||
@@ -11,6 +12,13 @@ import FinanceOverview from './cpns/FinanceOverview.vue'
|
|||||||
const financeStore = useFinanceStore()
|
const financeStore = useFinanceStore()
|
||||||
const { stats } = storeToRefs(financeStore)
|
const { stats } = storeToRefs(financeStore)
|
||||||
|
|
||||||
|
const ENTRIES = [
|
||||||
|
{ label: '账单明细', to: '/finance/bills' },
|
||||||
|
{ label: '待支付', to: '/finance/bills' },
|
||||||
|
{ label: '发票申请', to: '/finance/invoices' },
|
||||||
|
{ label: '报表导出', to: '' },
|
||||||
|
] as const
|
||||||
|
|
||||||
function onPeriodChange(period: string): void {
|
function onPeriodChange(period: string): void {
|
||||||
void financeStore.fetchStats(period)
|
void financeStore.fetchStats(period)
|
||||||
}
|
}
|
||||||
@@ -27,15 +35,12 @@ onMounted(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="finance">
|
<div class="finance">
|
||||||
<header class="finance__head">
|
<PageHead
|
||||||
<h1 class="finance__title">
|
title="财务中心"
|
||||||
财务中心
|
slogan="账单、支付、发票统一财务管理中心"
|
||||||
</h1>
|
/>
|
||||||
<p class="finance__slogan text-secondary">
|
|
||||||
账单支付、发票申请与报表导出一站式管理。
|
|
||||||
</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
<div class="finance__top">
|
<div class="finance__top">
|
||||||
<FinanceOverview
|
<FinanceOverview
|
||||||
:stats="stats"
|
:stats="stats"
|
||||||
@@ -44,75 +49,65 @@ onMounted(() => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="finance__entries">
|
<div class="finance__entries">
|
||||||
<RouterLink to="/finance/invoices" class="finance__entry">
|
<template v-for="entry in ENTRIES" :key="entry.label">
|
||||||
<span class="finance__entry-icon">🧾</span>
|
<RouterLink
|
||||||
<span class="finance__entry-name">发票申请</span>
|
v-if="entry.to"
|
||||||
|
:to="entry.to"
|
||||||
|
class="finance__entry"
|
||||||
|
>
|
||||||
|
{{ entry.label }}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<RouterLink to="/finance/bills" class="finance__entry">
|
<button
|
||||||
<span class="finance__entry-icon">📋</span>
|
v-else
|
||||||
<span class="finance__entry-name">账单明细</span>
|
class="finance__entry"
|
||||||
</RouterLink>
|
@click="onExport"
|
||||||
<button class="finance__entry" @click="onExport">
|
>
|
||||||
<span class="finance__entry-icon">📊</span>
|
{{ entry.label }}
|
||||||
<span class="finance__entry-name">报表导出</span>
|
|
||||||
</button>
|
</button>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FinanceChart :distribution="stats?.distribution ?? []" />
|
<FinanceChart :distribution="stats?.distribution ?? []" />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
// 财务中心概览(设计 6cd4aa76,1920 画布 1:1)
|
||||||
|
// 页头区 (70,162) 1780×17 副标语 13/400 #6B7280(标题 22/700 由全站页头统一渲染)
|
||||||
|
// 概览行 y195 h135:左渐变卡 1093.84 宽、右白卡 672.16 宽(间距 14)
|
||||||
|
// 图表卡 (70,344) 1780×378,白底 + #F0F1F3 描边
|
||||||
.finance {
|
.finance {
|
||||||
.flex(column, flex-start);
|
|
||||||
align-items: stretch;
|
|
||||||
gap: @spacing-base;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: @container-width;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 40px @spacing-base @spacing-xxl;
|
|
||||||
|
|
||||||
// 设计 37:页头标题 22 设计px × 0.77 ≈ 17px 加粗,副标语 13 设计px → 11px 居中
|
// 顶部行:左渐变卡 + 右快捷卡,间距 14
|
||||||
&__title {
|
|
||||||
font-size: 17px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: @heading-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__slogan {
|
|
||||||
margin-top: @spacing-xs;
|
|
||||||
font-size: 11px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 顶部行:左「累计消费」渐变卡 + 右快捷入口白卡(设计 37 同排并列)
|
|
||||||
&__top {
|
&__top {
|
||||||
.flex(row, flex-start, stretch);
|
.flex(row, flex-start, stretch);
|
||||||
gap: @spacing-base;
|
gap: 14px;
|
||||||
flex-wrap: wrap;
|
margin-top: -56px; // 页头白条通栏高 179,卡片按设计压在 y195
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 右侧快捷入口白卡 672.16×135
|
||||||
&__entries {
|
&__entries {
|
||||||
.flex(row, space-around, center);
|
.flex(row, space-between, center);
|
||||||
flex: 0 0 500px;
|
flex: 0 0 672px;
|
||||||
gap: @spacing-base;
|
height: 135px;
|
||||||
padding: @spacing-base @spacing-lg;
|
padding: 0 34px;
|
||||||
border-radius: @radius-base;
|
border: 1px solid @finance-card-line;
|
||||||
|
border-radius: @radius-base; // 设计无圆角?——卡面 8 以内,与全站一致
|
||||||
background: @white;
|
background: @white;
|
||||||
box-shadow: @shadow-base;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__entry {
|
&__entry {
|
||||||
.flex(column, center, center);
|
.flex(row, center, center);
|
||||||
gap: @spacing-sm;
|
height: 100%;
|
||||||
width: auto;
|
|
||||||
height: auto;
|
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 0;
|
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: @text-color;
|
color: @finance-entry-text;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
|
font-weight: 400;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: color 0.2s;
|
transition: color 0.2s;
|
||||||
|
|
||||||
@@ -121,14 +116,5 @@ onMounted(() => {
|
|||||||
color: @primary-strong;
|
color: @primary-strong;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__entry-icon {
|
|
||||||
.flex(row, center, center);
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
border-radius: @radius-base;
|
|
||||||
background: @bg-color;
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { AppButton, ConfirmDialog } from '@/components'
|
import { ConfirmDialog } from '@/components'
|
||||||
// 页面容器:数据只经 store,不直接 import api
|
// 页面容器:数据只经 store,不直接 import api
|
||||||
import { useFinanceStore } from '@/stores/modules/finance'
|
import { useFinanceStore } from '@/stores/modules/finance'
|
||||||
import type { InvoiceTitle, InvoiceTitlePayload } from '@/types/business/finance'
|
import type { InvoiceTitle, InvoiceTitlePayload } from '@/types/business/finance'
|
||||||
@@ -12,28 +12,27 @@ import InvoiceTitleDialog from './cpns/InvoiceTitleDialog.vue'
|
|||||||
const financeStore = useFinanceStore()
|
const financeStore = useFinanceStore()
|
||||||
const { titles } = storeToRefs(financeStore)
|
const { titles } = storeToRefs(financeStore)
|
||||||
|
|
||||||
type TypeFilter = 'all' | InvoiceTitle['type']
|
|
||||||
|
|
||||||
const typeFilter = ref<TypeFilter>('all')
|
|
||||||
|
|
||||||
const TYPE_TABS: Array<{ label: string; value: TypeFilter }> = [
|
|
||||||
{ label: '全部', value: 'all' },
|
|
||||||
{ label: '企业单位', value: '企业单位' },
|
|
||||||
{ label: '非企业单位', value: '非企业单位' },
|
|
||||||
]
|
|
||||||
|
|
||||||
const filteredTitles = computed(() =>
|
|
||||||
typeFilter.value === 'all'
|
|
||||||
? titles.value
|
|
||||||
: titles.value.filter((item) => item.type === typeFilter.value),
|
|
||||||
)
|
|
||||||
|
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
const editing = ref<InvoiceTitle | undefined>(undefined)
|
const editing = ref<InvoiceTitle | undefined>(undefined)
|
||||||
|
|
||||||
const confirmVisible = ref(false)
|
const confirmVisible = ref(false)
|
||||||
const pendingDeleteId = ref('')
|
const pendingDeleteId = ref('')
|
||||||
|
|
||||||
|
const PAGE_SIZE = 2
|
||||||
|
const page = ref(1)
|
||||||
|
|
||||||
|
const pageCount = computed(() => Math.max(1, Math.ceil(titles.value.length / PAGE_SIZE)))
|
||||||
|
const pagedTitles = computed(() =>
|
||||||
|
titles.value.slice((page.value - 1) * PAGE_SIZE, page.value * PAGE_SIZE),
|
||||||
|
)
|
||||||
|
/** 页码序列:设计 35 实取 1~5 */
|
||||||
|
const pages = computed(() => Array.from({ length: Math.max(5, pageCount.value) }, (_, i) => i + 1))
|
||||||
|
|
||||||
|
function go(next: number): void {
|
||||||
|
if (next < 1 || next > pageCount.value) return
|
||||||
|
page.value = next
|
||||||
|
}
|
||||||
|
|
||||||
function onCreate(): void {
|
function onCreate(): void {
|
||||||
editing.value = undefined
|
editing.value = undefined
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
@@ -69,12 +68,8 @@ async function onConfirmDelete(): Promise<void> {
|
|||||||
async function onSetDefault(id: string): Promise<void> {
|
async function onSetDefault(id: string): Promise<void> {
|
||||||
const item = titles.value.find((title) => title.id === id)
|
const item = titles.value.find((title) => title.id === id)
|
||||||
if (!item) return
|
if (!item) return
|
||||||
if (item.isDefault) {
|
|
||||||
ElMessage.info('演示环境暂不支持取消默认,可设其他抬头为默认')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
await financeStore.setDefaultTitle(id)
|
await financeStore.setDefaultTitle(id)
|
||||||
ElMessage.success('已设为默认抬头')
|
ElMessage.success(item.isDefault ? '已取消默认抬头' : '已设为默认抬头')
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -84,48 +79,60 @@ onMounted(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="invoice-titles">
|
<div class="invoice-titles">
|
||||||
<nav class="invoice-titles__breadcrumb text-secondary">
|
<div class="container">
|
||||||
<RouterLink to="/finance" class="link">
|
<!-- 面包屑 (70,124)「财务中心 /」13/400 #9CA3AF +「发票抬头管理」13/500 #4B5563 -->
|
||||||
财务中心
|
<nav class="invoice-titles__breadcrumb">
|
||||||
|
<RouterLink to="/finance" class="invoice-titles__breadcrumb-link">
|
||||||
|
财务中心 /
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<span> > </span>
|
<span class="invoice-titles__breadcrumb-current">发票抬头管理</span>
|
||||||
<span>发票抬头</span>
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
<!-- 标题条 (70,153) 1780×62:标题 x106 y174 15/700 + 右侧「+ 新增抬头」 -->
|
||||||
<header class="invoice-titles__head">
|
<header class="invoice-titles__head">
|
||||||
<h2 class="invoice-titles__title">
|
<h2 class="invoice-titles__title">
|
||||||
发票管理
|
发票管理
|
||||||
</h2>
|
</h2>
|
||||||
<div class="invoice-titles__filters">
|
<button class="invoice-titles__create" @click="onCreate">
|
||||||
<button
|
+ 新增抬头
|
||||||
v-for="tab in TYPE_TABS"
|
|
||||||
:key="tab.value"
|
|
||||||
class="invoice-titles__filter"
|
|
||||||
:class="{ 'is-active': typeFilter === tab.value }"
|
|
||||||
@click="typeFilter = tab.value"
|
|
||||||
>
|
|
||||||
{{ tab.label }}
|
|
||||||
</button>
|
</button>
|
||||||
<AppButton type="primary" @click="onCreate">
|
|
||||||
+新增抬头
|
|
||||||
</AppButton>
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="invoice-titles__grid">
|
<!-- 抬头卡列表 (70,229) 起 1780×129 / (70,372) 1780×88 -->
|
||||||
|
<div class="invoice-titles__list">
|
||||||
<InvoiceTitleCard
|
<InvoiceTitleCard
|
||||||
v-for="item in filteredTitles"
|
v-for="item in pagedTitles"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
:invoice-title="item"
|
:invoice-title="item"
|
||||||
@set-default="onSetDefault"
|
@set-default="onSetDefault"
|
||||||
@edit="onEdit"
|
@edit="onEdit"
|
||||||
@delete="onDelete"
|
@delete="onDelete"
|
||||||
/>
|
/>
|
||||||
<p v-if="filteredTitles.length === 0" class="invoice-titles__empty text-secondary">
|
<p v-if="pagedTitles.length === 0" class="invoice-titles__empty">
|
||||||
暂无发票抬头,点击右上角新增
|
暂无发票抬头,点击右上角新增
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 分页 (1684.93~1881.07,505) 12/400 -->
|
||||||
|
<nav class="invoice-titles__pager" aria-label="分页">
|
||||||
|
<button class="invoice-titles__page" :disabled="page <= 1" @click="go(page - 1)">
|
||||||
|
‹
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-for="item in pages"
|
||||||
|
:key="item"
|
||||||
|
class="invoice-titles__page"
|
||||||
|
:class="{ 'is-active': item === page }"
|
||||||
|
@click="go(item)"
|
||||||
|
>
|
||||||
|
{{ item }}
|
||||||
|
</button>
|
||||||
|
<button class="invoice-titles__page" :disabled="page >= pageCount" @click="go(page + 1)">
|
||||||
|
›
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
<InvoiceTitleDialog
|
<InvoiceTitleDialog
|
||||||
v-model:visible="dialogVisible"
|
v-model:visible="dialogVisible"
|
||||||
:invoice-title="editing"
|
:invoice-title="editing"
|
||||||
@@ -144,107 +151,120 @@ onMounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
// 设计 35/36 发票抬头管理(1920 画布 1:1)——内容区 x70→1850 = 1780
|
||||||
.invoice-titles {
|
.invoice-titles {
|
||||||
.flex(column, flex-start);
|
|
||||||
align-items: stretch;
|
|
||||||
gap: @spacing-base;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: @container-width;
|
padding-bottom: 459px; // 分页底 521 → 页脚 1087
|
||||||
margin: 0 auto;
|
background: @contract-page-bg;
|
||||||
padding: @spacing-lg @spacing-base @spacing-xxl;
|
|
||||||
|
|
||||||
// 设计 35/36:面包屑 13 设计px → 11px,前缀灰、当前页深灰
|
// 面包屑 (70,124)
|
||||||
&__breadcrumb {
|
&__breadcrumb {
|
||||||
font-size: 11px;
|
.flex(row, flex-start, center);
|
||||||
color: @text-color-disabled;
|
height: 81px; // 顶栏 72 → 标题条 153,面包屑盒 72..133
|
||||||
|
padding-top: 36px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 17px;
|
||||||
|
color: @project-muted;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
> span:last-child {
|
&__breadcrumb-link {
|
||||||
color: @text-color;
|
color: @project-muted;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: @primary-strong;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 顶部「发票管理」白卡条(设计 35:蓝竖条标题 + 右侧按钮组)
|
&__breadcrumb-current {
|
||||||
|
margin-left: 5.25px; // 「财务中心 /」墨迹宽 65.25 → 发票抬头管理 x135.25
|
||||||
|
color: @finance-entry-text;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标题条 (70,153) 1780×62
|
||||||
&__head {
|
&__head {
|
||||||
.flex(row, space-between, center);
|
.flex(row, space-between, center);
|
||||||
flex-wrap: wrap;
|
height: 62px;
|
||||||
gap: @spacing-base;
|
padding: 0 33.56px 0 36px; // 标题 x106;按钮右缘 1822
|
||||||
padding: 14px 20px;
|
border: 1px solid @finance-card-line;
|
||||||
border-radius: @radius-base;
|
border-radius: @radius-base;
|
||||||
background: @white;
|
background: @white;
|
||||||
box-shadow: @shadow-base;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
position: relative;
|
font-size: 15px;
|
||||||
padding-left: 10px;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: @heading-color;
|
line-height: 20px;
|
||||||
|
color: @finance-ink;
|
||||||
&::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 0;
|
|
||||||
width: 4px;
|
|
||||||
height: 14px;
|
|
||||||
border-radius: @radius-round;
|
|
||||||
background: @primary-strong;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__filters {
|
// 「+ 新增抬头」(1735.56,167) 110.44×34
|
||||||
.flex(row, flex-end, center);
|
&__create {
|
||||||
gap: @spacing-sm;
|
width: 110.44px;
|
||||||
}
|
height: 34px;
|
||||||
|
margin-right: -5px; // 按钮右缘 1822 → 卡内缩 33.56 得 1816.44,再右移 5
|
||||||
&__filter {
|
border: none;
|
||||||
height: 26px;
|
border-radius: 4px;
|
||||||
padding: 0 14px;
|
|
||||||
border: 1px solid @border-color;
|
|
||||||
border-radius: @radius-sm;
|
|
||||||
background: @white;
|
|
||||||
color: @text-color;
|
|
||||||
font-size: 11px;
|
|
||||||
transition: all 0.2s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-color: @primary-strong;
|
|
||||||
color: @primary-strong;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-active {
|
|
||||||
border-color: @primary-strong;
|
|
||||||
background: @primary-strong;
|
background: @primary-strong;
|
||||||
color: @white;
|
color: @white;
|
||||||
}
|
font-size: 12px;
|
||||||
}
|
font-weight: 500;
|
||||||
|
transition: background 0.2s;
|
||||||
// 「+ 新增抬头」:设计 35 实底 #024FFF 小按钮
|
|
||||||
:deep(.app-button--primary) {
|
|
||||||
height: 26px;
|
|
||||||
padding: 0 12px;
|
|
||||||
border-radius: @radius-sm;
|
|
||||||
background: @primary-strong;
|
|
||||||
font-size: 11px;
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: @primary-color;
|
background: @primary-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 抬头卡纵排铺满(设计 35 一行一张)
|
// 抬头卡纵排:首卡 (70,229)、次卡 (70,372) → 间距 14
|
||||||
&__grid {
|
&__list {
|
||||||
display: flex;
|
.flex(column, flex-start, stretch);
|
||||||
flex-direction: column;
|
gap: 14px;
|
||||||
gap: @spacing-base;
|
margin-top: 14px; // 标题条底 215 → 首卡 229
|
||||||
}
|
}
|
||||||
|
|
||||||
&__empty {
|
&__empty {
|
||||||
padding: @spacing-xl 0;
|
padding: 60px 0;
|
||||||
font-size: 11px;
|
color: @project-muted;
|
||||||
|
font-size: 13px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 分页:右对齐到内容区右缘,页码 12/400
|
||||||
|
&__pager {
|
||||||
|
.flex(row, flex-end, center);
|
||||||
|
gap: 3.7px;
|
||||||
|
margin-top: 8px; // 次卡底 460 → 页码 ink 505
|
||||||
|
padding-right: 2.4px; // 末位「›」右缘 1881.07 → 内容右缘 1850
|
||||||
|
}
|
||||||
|
|
||||||
|
&__page {
|
||||||
|
min-width: 21.45px;
|
||||||
|
height: 16px;
|
||||||
|
padding: 0 3px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: @finance-muted-text;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
transition: color 0.2s;
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
color: @primary-strong;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-active {
|
||||||
|
color: @primary-strong;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import { computed, onMounted } from 'vue'
|
import { computed, onMounted } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { AppButton } from '@/components'
|
|
||||||
// 页面容器:数据只经 store,不直接 import api
|
// 页面容器:数据只经 store,不直接 import api
|
||||||
import { useFinanceStore } from '@/stores/modules/finance'
|
import { useFinanceStore } from '@/stores/modules/finance'
|
||||||
import { formatMoney } from '@/utils/format'
|
import { formatMoney } from '@/utils/format'
|
||||||
@@ -32,52 +31,73 @@ onMounted(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="pay-result">
|
<div class="pay-result">
|
||||||
<nav class="pay-result__breadcrumb text-secondary">
|
<div class="container">
|
||||||
<RouterLink to="/finance" class="link">
|
<!-- 面包屑 (67,95)「财务中心 / 账单明细 /」+「支付成功」13/500 #4B5563 -->
|
||||||
财务中心
|
<nav class="pay-result__breadcrumb">
|
||||||
|
<RouterLink to="/finance" class="pay-result__crumb-link">
|
||||||
|
财务中心 /
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<span> > </span>
|
<RouterLink to="/finance/bills" class="pay-result__crumb-link">
|
||||||
<RouterLink to="/finance/bills" class="link">
|
账单明细 /
|
||||||
账单明细
|
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<span> > </span>
|
<span class="pay-result__crumb-current">支付成功</span>
|
||||||
<span>支付结果</span>
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<template v-if="result">
|
<template v-if="result">
|
||||||
<section class="pay-result__card card">
|
<!-- 主列 316 居中(设计 x799→1115) -->
|
||||||
<div class="pay-result__head">
|
<section class="pay-result__col">
|
||||||
<span class="pay-result__check">✓</span>
|
<!-- 绿圆勾 (897,187) 120×120 #7ED321,✓ 56/400 白字居中 -->
|
||||||
<div class="pay-result__head-text">
|
<div class="pay-result__check" aria-hidden="true">
|
||||||
|
✓
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 标题 (900.3,331) 22/700 #1F2937,字距 6 -->
|
||||||
<h2 class="pay-result__title">
|
<h2 class="pay-result__title">
|
||||||
支付成功
|
支付成功
|
||||||
</h2>
|
</h2>
|
||||||
<p class="pay-result__count text-secondary">
|
|
||||||
共{{ result.bills.length }}个订单
|
|
||||||
</p>
|
|
||||||
<p class="pay-result__amount">
|
|
||||||
付款金额 ¥{{ formatMoney(result.amount) }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pay-result__grid">
|
<!-- 金额行 (852.9,379):13/400 灰 + ¥ 18/700 #EF4444,基线对齐 -->
|
||||||
<div class="pay-result__grid-item">
|
<p class="pay-result__amount-row">
|
||||||
<span class="pay-result__grid-label text-secondary">支付方式</span>
|
<span class="pay-result__amount-label">共{{ result.bills.length }}个订单 支付金额:</span>
|
||||||
<span class="pay-result__grid-value">{{ methodLabel }}</span>
|
<span class="pay-result__amount">¥ {{ formatMoney(result.amount, 0) }}</span>
|
||||||
</div>
|
</p>
|
||||||
<div class="pay-result__grid-item">
|
|
||||||
<span class="pay-result__grid-label text-secondary">支付时间</span>
|
|
||||||
<span class="pay-result__grid-value">{{ result.payTime }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="pay-result__grid-item">
|
|
||||||
<span class="pay-result__grid-label text-secondary">交易流水号</span>
|
|
||||||
<span class="pay-result__grid-value">{{ result.tradeNo }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pay-result__bills">
|
<!-- 分割线 (799,428) 316×1 #F0F1F3 -->
|
||||||
|
<div class="pay-result__divider" />
|
||||||
|
|
||||||
|
<!-- 支付信息 (799,449) 13/700 -->
|
||||||
<h4 class="pay-result__sub-title">
|
<h4 class="pay-result__sub-title">
|
||||||
|
支付信息
|
||||||
|
</h4>
|
||||||
|
<dl class="pay-result__fields">
|
||||||
|
<div class="pay-result__field">
|
||||||
|
<dt class="pay-result__label">
|
||||||
|
支付方式
|
||||||
|
</dt>
|
||||||
|
<dd class="pay-result__value">
|
||||||
|
{{ methodLabel }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="pay-result__field">
|
||||||
|
<dt class="pay-result__label">
|
||||||
|
支付时间
|
||||||
|
</dt>
|
||||||
|
<dd class="pay-result__value">
|
||||||
|
{{ result.payTime }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="pay-result__field">
|
||||||
|
<dt class="pay-result__label">
|
||||||
|
交易流水号
|
||||||
|
</dt>
|
||||||
|
<dd class="pay-result__value">
|
||||||
|
{{ result.tradeNo || '—' }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<!-- 已支付订单 (799,574) 13/700 -->
|
||||||
|
<h4 class="pay-result__sub-title pay-result__sub-title--bills">
|
||||||
已支付订单
|
已支付订单
|
||||||
</h4>
|
</h4>
|
||||||
<ul class="pay-result__bill-list">
|
<ul class="pay-result__bill-list">
|
||||||
@@ -86,184 +106,240 @@ onMounted(() => {
|
|||||||
:key="bill.id"
|
:key="bill.id"
|
||||||
class="pay-result__bill"
|
class="pay-result__bill"
|
||||||
>
|
>
|
||||||
|
<div class="pay-result__bill-head">
|
||||||
<span class="pay-result__bill-name">{{ bill.planName }}</span>
|
<span class="pay-result__bill-name">{{ bill.planName }}</span>
|
||||||
<span class="pay-result__bill-no text-secondary">{{ bill.billNo }}</span>
|
<span class="pay-result__bill-amount">¥ {{ formatMoney(bill.amount, 0) }}</span>
|
||||||
<span class="pay-result__bill-amount">¥{{ formatMoney(bill.amount) }}</span>
|
</div>
|
||||||
|
<p class="pay-result__bill-meta">
|
||||||
|
账单号:{{ bill.billNo }}
|
||||||
|
</p>
|
||||||
|
<p class="pay-result__bill-meta">
|
||||||
|
关联项目:{{ bill.projectName }}
|
||||||
|
</p>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<!-- 返回 (1031,711) 84×32 #024FFF 12/500 白字,右对齐 -->
|
||||||
<div class="pay-result__footer">
|
<div class="pay-result__footer">
|
||||||
<AppButton type="primary" @click="router.push('/finance/bills')">
|
<button
|
||||||
|
class="pay-result__back"
|
||||||
|
type="button"
|
||||||
|
@click="router.push('/finance/bills')"
|
||||||
|
>
|
||||||
返回
|
返回
|
||||||
</AppButton>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<p v-else class="pay-result__empty text-secondary">
|
<p v-else class="pay-result__empty">
|
||||||
暂无支付结果
|
暂无支付结果
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
// 设计「支付成功」c56193d1(1920 画布 1:1)——316 居中窄列坐在页面底色上
|
||||||
.pay-result {
|
.pay-result {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: @container-width;
|
padding-bottom: 344px; // 列底 743 → 页脚 1087
|
||||||
margin: 0 auto;
|
background: @contract-page-bg;
|
||||||
padding: @spacing-lg @spacing-base @spacing-xxl;
|
|
||||||
|
|
||||||
// 设计 32:面包屑 13 设计px → 11px,前缀灰、当前页深灰
|
// 面包屑 (67,95):头部底 73 → 文字盒顶 95 = 22
|
||||||
&__breadcrumb {
|
&__breadcrumb {
|
||||||
margin-bottom: @spacing-base;
|
.flex(row, flex-start, center);
|
||||||
font-size: 11px;
|
padding-top: 22px;
|
||||||
color: @text-color-disabled;
|
font-size: 13px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 17px;
|
||||||
|
color: @project-muted;
|
||||||
|
}
|
||||||
|
|
||||||
> span:last-child {
|
&__crumb-link {
|
||||||
color: @text-color;
|
color: @project-muted;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: @primary-strong;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 32:内容为居中窄列,直接坐在页面底色上(无外层大卡)
|
&__crumb-current {
|
||||||
&__card {
|
margin-left: 3px;
|
||||||
width: 300px;
|
color: @finance-entry-text; // 设计 #4B5563
|
||||||
max-width: 100%;
|
font-weight: 500;
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0;
|
|
||||||
border-radius: 0;
|
|
||||||
background: transparent;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 头部:大绿圆勾 + 标题 + 「共 N 个订单 / 付款金额」同一行
|
// 主列 316,居中(面包屑底 112 → 圆顶 187 = 75)
|
||||||
&__head {
|
&__col {
|
||||||
.flex(column, center, center);
|
width: 316px;
|
||||||
gap: 12px;
|
margin: 75px auto 0;
|
||||||
padding-bottom: 16px;
|
|
||||||
border-bottom: 1px solid @project-divider;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 绿圆勾 120×120 #7ED321
|
||||||
&__check {
|
&__check {
|
||||||
.flex(row, center);
|
.flex(row, center, center);
|
||||||
width: 76px;
|
width: 120px;
|
||||||
height: 76px;
|
height: 120px;
|
||||||
border-radius: @radius-round;
|
margin: 0 auto;
|
||||||
|
border-radius: 50%;
|
||||||
background: @finance-result-circle;
|
background: @finance-result-circle;
|
||||||
color: @white;
|
color: @white;
|
||||||
font-size: 42px;
|
font-size: 56px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__head-text {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: baseline;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 标题:圆底 307 → 331 = 24;盒 113.3 ≈ 4×22 + 4×6 字距
|
||||||
&__title {
|
&__title {
|
||||||
flex-basis: 100%;
|
margin: 24px -6px 0 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 17px;
|
font-size: 22px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: @heading-color;
|
line-height: 29px;
|
||||||
|
letter-spacing: 6px;
|
||||||
|
color: @finance-ink;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 金额行:标题底 360 → 374 = 14;13/400 灰 + 18/700 #EF4444 基线对齐
|
||||||
|
&__amount-row {
|
||||||
|
.flex(row, center, baseline);
|
||||||
|
margin-top: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__amount-label {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 17px;
|
||||||
|
color: @finance-muted-text;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__amount {
|
&__amount {
|
||||||
font-size: 14px;
|
font-size: 18px;
|
||||||
color: @danger-color;
|
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
line-height: 24px;
|
||||||
|
color: @danger-color; // 设计 #EF4444
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__count {
|
// 分割线:金额行底 398 → 428 = 30
|
||||||
font-size: 11px;
|
&__divider {
|
||||||
|
height: 1px;
|
||||||
|
margin-top: 30px;
|
||||||
|
background: @project-divider;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 支付信息:标签左灰、值右粗(设计 32 竖排行)
|
&__sub-title {
|
||||||
&__grid {
|
margin-top: 20px; // 线底 429 → 449 = 20
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 17px;
|
||||||
|
color: @finance-ink;
|
||||||
|
|
||||||
|
&--bills {
|
||||||
|
margin-top: 22px; // 末行底 552 → 574 = 22
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 支付信息三行:标题底 466 → 首行 480 = 14;行高 16 步进 28 → gap 12
|
||||||
|
&__fields {
|
||||||
.flex(column, flex-start, stretch);
|
.flex(column, flex-start, stretch);
|
||||||
gap: 14px;
|
gap: 12px;
|
||||||
margin-top: 16px;
|
margin-top: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__grid-item {
|
&__field {
|
||||||
.flex(row, space-between, center);
|
.flex(row, space-between, flex-start);
|
||||||
gap: @spacing-base;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__grid-label {
|
&__label {
|
||||||
font-size: 11px;
|
font-size: 12px;
|
||||||
color: @text-color-secondary;
|
font-weight: 400;
|
||||||
|
line-height: 16px;
|
||||||
|
color: @finance-muted-text;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__grid-value {
|
&__value {
|
||||||
font-size: 11px;
|
font-size: 12px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: @heading-color;
|
line-height: 16px;
|
||||||
|
color: @finance-ink;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__bills {
|
// 白卡:标题底 591 → 601 = 10;316×84 白底描边 #F0F1F3
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__sub-title {
|
|
||||||
margin-bottom: 8px;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: @heading-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 已支付订单行卡:白底圆角(设计 32 实取)
|
|
||||||
&__bill-list {
|
&__bill-list {
|
||||||
.flex(column, flex-start, stretch);
|
.flex(column, flex-start, stretch);
|
||||||
gap: 6px;
|
margin-top: 10px;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__bill {
|
&__bill {
|
||||||
.flex(row, flex-start, center);
|
box-sizing: border-box;
|
||||||
gap: 8px;
|
height: 84px;
|
||||||
padding: 10px;
|
padding: 15px 17px;
|
||||||
border-radius: 6px;
|
border: 1px solid @project-divider;
|
||||||
|
border-radius: @radius-base;
|
||||||
background: @white;
|
background: @white;
|
||||||
font-size: 11px;
|
overflow: hidden;
|
||||||
color: @text-color;
|
}
|
||||||
|
|
||||||
|
&__bill-head {
|
||||||
|
.flex(row, space-between, baseline);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__bill-name {
|
&__bill-name {
|
||||||
color: @heading-color;
|
font-size: 12px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
line-height: 16px;
|
||||||
|
color: @finance-ink;
|
||||||
&__bill-no {
|
.ellipsis();
|
||||||
flex: 1;
|
min-width: 0;
|
||||||
font-size: 11px;
|
|
||||||
color: @text-color-disabled;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__bill-amount {
|
&__bill-amount {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: 12px;
|
||||||
|
font-size: 14px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: @heading-color;
|
line-height: 16px;
|
||||||
white-space: nowrap;
|
color: @finance-ink;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 返回按钮右对齐(设计 32 在列右下)
|
&__bill-meta {
|
||||||
|
margin-top: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 16px;
|
||||||
|
color: @project-muted; // 设计 #9CA3AF
|
||||||
|
.ellipsis();
|
||||||
|
|
||||||
|
+ .pay-result__bill-meta {
|
||||||
|
margin-top: 0; // 账单号 638 → 关联项目 654 连排
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回按钮:卡底 685 → 711 = 26,右对齐 84×32 #024FFF
|
||||||
&__footer {
|
&__footer {
|
||||||
display: flex;
|
.flex(row, flex-end, flex-start);
|
||||||
justify-content: flex-end;
|
margin-top: 26px;
|
||||||
margin-top: 16px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.app-button--primary) {
|
&__back {
|
||||||
|
width: 84px;
|
||||||
|
height: 32px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
background: @primary-strong;
|
background: @primary-strong;
|
||||||
|
color: @white;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: @primary-color;
|
background: @primary-color;
|
||||||
@@ -271,9 +347,10 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__empty {
|
&__empty {
|
||||||
padding: @spacing-xl 0;
|
padding: 80px 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 11px;
|
font-size: 13px;
|
||||||
|
color: @project-muted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,161 +1,157 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
import { useRouter } from 'vue-router'
|
||||||
import type { BannerVo } from '@/types/business/home'
|
import bannerIllustration from '@/assets/home/banner-illustration.png'
|
||||||
|
|
||||||
interface Props {
|
// 设计稿顶部为静态头图(非轮播、不走接口):底 #F3F4F8 渐变,
|
||||||
banners: BannerVo[]
|
// 左侧标题 60px + 副标题 24px + 双按钮 183×55,右侧插画 723×598(蓝湖设计稿切图)
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
function goDemand(): void {
|
||||||
|
router.push('/demand')
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<Props>()
|
function goCases(): void {
|
||||||
|
router.push('/cases')
|
||||||
/** 仅保留启用且带图的轮播项,按后端 sort 升序 */
|
|
||||||
const slides = computed(() =>
|
|
||||||
props.banners
|
|
||||||
.filter((item) => item.enabled !== false && !!item.imageUrl)
|
|
||||||
.slice()
|
|
||||||
.sort((prev, next) => (prev.sort ?? 0) - (next.sort ?? 0)),
|
|
||||||
)
|
|
||||||
|
|
||||||
const current = ref(0)
|
|
||||||
let timer: number | undefined
|
|
||||||
|
|
||||||
function stop(): void {
|
|
||||||
if (timer !== undefined) {
|
|
||||||
window.clearInterval(timer)
|
|
||||||
timer = undefined
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function go(index: number): void {
|
|
||||||
const total = slides.value.length
|
|
||||||
if (total === 0) return
|
|
||||||
current.value = (index + total) % total
|
|
||||||
}
|
|
||||||
|
|
||||||
function start(): void {
|
|
||||||
stop()
|
|
||||||
if (slides.value.length <= 1) return
|
|
||||||
timer = window.setInterval(() => go(current.value + 1), 5000)
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => slides.value.length,
|
|
||||||
() => {
|
|
||||||
current.value = 0
|
|
||||||
start()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
onMounted(start)
|
|
||||||
onBeforeUnmount(stop)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section v-if="slides.length" class="home-banner">
|
<section class="home-banner">
|
||||||
<img
|
<div class="home-banner__inner">
|
||||||
v-for="(slide, index) in slides"
|
<h1 class="home-banner__title">虫子智造研发一体化平台</h1>
|
||||||
v-show="index === current"
|
<p class="home-banner__subtitle">一站式智能软硬件研发与量产综合服务商</p>
|
||||||
:key="slide.id ?? index"
|
|
||||||
class="home-banner__img"
|
|
||||||
:src="slide.imageUrl ?? ''"
|
|
||||||
:alt="`轮播图 ${index + 1}`"
|
|
||||||
>
|
|
||||||
|
|
||||||
<template v-if="slides.length > 1">
|
<div class="home-banner__actions">
|
||||||
<button
|
<button class="home-banner__btn home-banner__btn--primary" type="button" @click="goDemand">
|
||||||
class="home-banner__arrow home-banner__arrow--prev"
|
需求提报
|
||||||
type="button"
|
|
||||||
aria-label="上一张"
|
|
||||||
@click="go(current - 1)"
|
|
||||||
>
|
|
||||||
‹
|
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button class="home-banner__btn home-banner__btn--ghost" type="button" @click="goCases">
|
||||||
class="home-banner__arrow home-banner__arrow--next"
|
案例中心
|
||||||
type="button"
|
|
||||||
aria-label="下一张"
|
|
||||||
@click="go(current + 1)"
|
|
||||||
>
|
|
||||||
›
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- 设计稿底部居中的指示条(长条=当前,圆点=其余) -->
|
|
||||||
<div class="home-banner__dots">
|
|
||||||
<span
|
|
||||||
v-for="(slide, index) in slides"
|
|
||||||
:key="`dot-${slide.id ?? index}`"
|
|
||||||
class="home-banner__dot"
|
|
||||||
:class="{ 'is-active': index === current }"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
|
||||||
|
<div class="home-banner__visual">
|
||||||
|
<img
|
||||||
|
class="home-banner__illustration"
|
||||||
|
:src="bannerIllustration"
|
||||||
|
alt="虫子智造研发一体化平台"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.home-banner {
|
.home-banner {
|
||||||
|
// 设计稿头图通栏 1920×661(画板 y73,位于顶栏 72 之下)
|
||||||
position: relative;
|
position: relative;
|
||||||
// 设计稿头图通栏 1920×661 设计px → 高 509 CSS,紧贴顶栏无圆角
|
height: 661px;
|
||||||
height: 509px;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: @home-banner-bg;
|
background: @home-banner-bg;
|
||||||
|
// .home 是 column flex 容器,需显式占满整行,否则被内容宽度收缩
|
||||||
|
width: 100%;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
|
||||||
&__img {
|
// 内容区按设计稿 1920 画布居中;两侧内容一律相对它定位,保证 1:1
|
||||||
|
&__inner {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
max-width: @container-width;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设计稿标题 x150 y257(画板绝对)→ banner 内 y257−73=184
|
||||||
|
&__title {
|
||||||
|
position: absolute;
|
||||||
|
top: 184px;
|
||||||
|
left: 150px;
|
||||||
|
margin: 0;
|
||||||
|
color: @banner-title-color;
|
||||||
|
font-size: @font-size-hero-lg;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 87px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设计稿副标题 x150 y360 → banner 内 y287
|
||||||
|
&__subtitle {
|
||||||
|
position: absolute;
|
||||||
|
top: 287px;
|
||||||
|
left: 150px;
|
||||||
|
margin: 0;
|
||||||
|
color: @banner-title-color;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 35px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设计稿按钮组 x150 y465 → banner 内 y392;两钮 183×55,间距 20(150+183=333 → 353)
|
||||||
|
&__actions {
|
||||||
|
position: absolute;
|
||||||
|
top: 392px;
|
||||||
|
left: 150px;
|
||||||
|
.flex(row, flex-start, center);
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__btn {
|
||||||
|
width: 183px;
|
||||||
|
height: 55px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&--primary {
|
||||||
|
background: linear-gradient(135deg, @btn-gradient-from 0%, @btn-gradient-to 100%);
|
||||||
|
border: none;
|
||||||
|
color: @white;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设计稿:2px 渐变描边 + 白底 + 蓝色文字(实测 #2B5FF0)
|
||||||
|
&--ghost {
|
||||||
|
position: relative;
|
||||||
|
background: @white;
|
||||||
|
border: none;
|
||||||
|
color: @btn-ghost-color;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 2px;
|
||||||
|
background: linear-gradient(135deg, @btn-gradient-from 0%, @btn-gradient-to 100%);
|
||||||
|
-webkit-mask:
|
||||||
|
linear-gradient(#fff 0 0) content-box,
|
||||||
|
linear-gradient(#fff 0 0);
|
||||||
|
mask:
|
||||||
|
linear-gradient(#fff 0 0) content-box,
|
||||||
|
linear-gradient(#fff 0 0);
|
||||||
|
-webkit-mask-composite: xor;
|
||||||
|
mask-composite: exclude;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设计稿插画 723×598 @ canvas (928,104) → banner 内 y104−73=31
|
||||||
|
&__visual {
|
||||||
|
position: absolute;
|
||||||
|
top: 31px;
|
||||||
|
left: 928px;
|
||||||
|
width: 723px;
|
||||||
|
height: 598px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__illustration {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: contain;
|
||||||
}
|
|
||||||
|
|
||||||
&__arrow {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
border-radius: @radius-round;
|
|
||||||
background: fade(@black, 35%);
|
|
||||||
color: @white;
|
|
||||||
font-size: @font-size-xl;
|
|
||||||
line-height: 1;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.2s;
|
|
||||||
|
|
||||||
&--prev {
|
|
||||||
left: @spacing-base;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--next {
|
|
||||||
right: @spacing-base;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover &__arrow {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__dots {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 48px;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
.flex(row, center, center);
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__dot {
|
|
||||||
width: 4px;
|
|
||||||
height: 4px;
|
|
||||||
border-radius: @radius-round;
|
|
||||||
background: @home-banner-dot;
|
|
||||||
transition: width 0.2s;
|
|
||||||
|
|
||||||
// 当前页:设计稿 44×5 设计px 长条
|
|
||||||
&.is-active {
|
|
||||||
width: 34px;
|
|
||||||
height: 4px;
|
|
||||||
border-radius: @radius-round;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,21 +1,32 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { BusinessType } from '@/types/api'
|
||||||
import type { BusinessLine } from '@/types/business/home'
|
import type { BusinessLine } from '@/types/business/home'
|
||||||
|
import illInternet from '@/assets/home/business-internet.png'
|
||||||
|
import illHardware from '@/assets/home/business-hardware.png'
|
||||||
|
import illIot from '@/assets/home/business-iot.png'
|
||||||
|
import illManufacturing from '@/assets/home/business-manufacturing.png'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
lines: BusinessLine[]
|
lines: BusinessLine[]
|
||||||
}
|
}
|
||||||
|
|
||||||
defineProps<Props>()
|
defineProps<Props>()
|
||||||
|
|
||||||
|
// 设计稿四张卡的插画图案(81×111,蓝湖整稿切图);接口 logoUrl 优先
|
||||||
|
const FALLBACK_ILL: Record<BusinessType, string> = {
|
||||||
|
internet: illInternet,
|
||||||
|
hardware: illHardware,
|
||||||
|
iot: illIot,
|
||||||
|
manufacturing: illManufacturing,
|
||||||
|
other: illIot,
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="home-business">
|
<section class="home-business">
|
||||||
<div class="home-business__head">
|
|
||||||
<h3 class="home-business__title">
|
<h3 class="home-business__title">
|
||||||
业务服务
|
业务服务
|
||||||
</h3>
|
</h3>
|
||||||
<span class="home-business__slogan text-secondary">研发智造一体化自营平台,全链路质量兜底</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="home-business__grid">
|
<div class="home-business__grid">
|
||||||
<RouterLink
|
<RouterLink
|
||||||
@@ -25,21 +36,20 @@ defineProps<Props>()
|
|||||||
class="home-business__card"
|
class="home-business__card"
|
||||||
:class="`home-business__card--${line.id}`"
|
:class="`home-business__card--${line.id}`"
|
||||||
>
|
>
|
||||||
<div class="home-business__card-head">
|
<!-- 设计稿:插画 81×111 @卡内(48,4);接口 logoUrl 优先,静态切图兜底 -->
|
||||||
<span class="home-business__icon">
|
<img
|
||||||
<!-- TODO(蓝湖): 替换业务图标切图 -->
|
class="home-business__ill"
|
||||||
{{ line.name.slice(0, 2) }}
|
:src="line.icon || FALLBACK_ILL[line.id]"
|
||||||
</span>
|
:alt="line.name"
|
||||||
<!-- 设计稿卡片右上角选择圆环 -->
|
>
|
||||||
|
<!-- 设计稿卡片右上角选择圆环(31×31) -->
|
||||||
<span class="home-business__check" aria-hidden="true" />
|
<span class="home-business__check" aria-hidden="true" />
|
||||||
</div>
|
|
||||||
<h4 class="home-business__name">
|
<h4 class="home-business__name">
|
||||||
{{ line.name }}
|
{{ line.name }}
|
||||||
</h4>
|
</h4>
|
||||||
<p class="home-business__desc">
|
<p class="home-business__desc">
|
||||||
{{ line.desc }}
|
{{ line.desc }}
|
||||||
</p>
|
</p>
|
||||||
<span class="home-business__more">了解详情 ></span>
|
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -47,43 +57,35 @@ defineProps<Props>()
|
|||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.home-business {
|
.home-business {
|
||||||
// 设计稿区块卡 1680×400 设计px:白底 + #E2E2E2 描边(内容区内左右缩 50 设计px)
|
// 设计稿区块卡 1680×400:白底 + #E2E2E2 描边,标题 @190,1062(卡顶 +44),卡区左右距 70
|
||||||
max-width: 1294px;
|
padding: 44px 70px 54px;
|
||||||
margin-right: auto;
|
|
||||||
margin-left: auto;
|
|
||||||
padding: 34px 54px 40px;
|
|
||||||
border: 1px solid @home-section-border;
|
border: 1px solid @home-section-border;
|
||||||
border-radius: @radius-lg;
|
border-radius: @radius-lg;
|
||||||
background: @white;
|
background: @white;
|
||||||
|
|
||||||
&__head {
|
|
||||||
.flex(row, space-between);
|
|
||||||
margin-bottom: 36px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
// 设计稿栏目标题 36 设计px / 700 / #121212
|
margin: 0 0 47px; // 标题盒底 1114 → 卡顶 1161
|
||||||
|
// 设计稿栏目标题 36px / 700 / #121212
|
||||||
font-size: @font-size-xxl;
|
font-size: @font-size-xxl;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: @home-ink;
|
color: @home-ink;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__slogan {
|
|
||||||
font-size: @font-size-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__grid {
|
&__grid {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: repeat(4, 1fr);
|
// 一行固定四张,窄视口行内等比收缩(wrap 会因 1px 之差把第四张挤到第二行)
|
||||||
// 设计稿卡片间距 22 设计px ≈ 17 CSS
|
gap: 22px;
|
||||||
gap: 17px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__card {
|
&__card {
|
||||||
.flex(column, flex-start);
|
position: relative;
|
||||||
gap: 0;
|
.flex(column, flex-start, flex-start);
|
||||||
// 设计稿业务卡 368×206 设计px:白底 / #999 描边 / 小圆角
|
// 设计稿业务卡 368×206,一行固定四张(4×368 + 3×22 = 1538 ≤ 1560)
|
||||||
padding: 22px 16px 6px;
|
flex: 0 1 368px;
|
||||||
|
min-width: 0;
|
||||||
|
height: 206px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
border: 1px solid @home-business-border;
|
border: 1px solid @home-business-border;
|
||||||
border-radius: @radius-base;
|
border-radius: @radius-base;
|
||||||
background: @white;
|
background: @white;
|
||||||
@@ -95,29 +97,24 @@ defineProps<Props>()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__card-head {
|
// 设计稿插画 81×111 @卡内 (48,4)
|
||||||
.flex(row, space-between, flex-start);
|
&__ill {
|
||||||
width: 100%;
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
left: 48px;
|
||||||
|
width: 81px;
|
||||||
|
height: 111px;
|
||||||
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__icon {
|
// 右上角选择圆环(31×31)
|
||||||
.flex(row, center);
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 44px;
|
|
||||||
height: 44px;
|
|
||||||
border-radius: @radius-base;
|
|
||||||
background: transparent;
|
|
||||||
color: @primary-strong;
|
|
||||||
font-size: @font-size-lg;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 右上角选择圆环(31 设计px ≈ 24 CSS)
|
|
||||||
&__check {
|
&__check {
|
||||||
position: relative;
|
position: absolute;
|
||||||
|
top: 24px;
|
||||||
|
right: 20px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 24px;
|
width: 31px;
|
||||||
height: 24px;
|
height: 31px;
|
||||||
border: 1px solid @home-ring;
|
border: 1px solid @home-ring;
|
||||||
border-radius: @radius-round;
|
border-radius: @radius-round;
|
||||||
|
|
||||||
@@ -125,8 +122,8 @@ defineProps<Props>()
|
|||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
width: 14px;
|
width: 18px;
|
||||||
height: 14px;
|
height: 18px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
border-radius: @radius-round;
|
border-radius: @radius-round;
|
||||||
background: @white;
|
background: @white;
|
||||||
@@ -134,28 +131,23 @@ defineProps<Props>()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设计稿标题 @卡内 (20,106),描述 @ (20,151)
|
||||||
&__name {
|
&__name {
|
||||||
margin-top: 16px;
|
margin: 106px 0 0;
|
||||||
// 设计稿卡片标题 28 设计px / 500 / #121212
|
// 设计稿卡片标题 28px / 500 / #121212
|
||||||
font-size: @font-size-title;
|
font-size: @font-size-title;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
line-height: 41px;
|
||||||
color: @home-ink;
|
color: @home-ink;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__desc {
|
&__desc {
|
||||||
margin-top: 4px;
|
margin: 4px 0 0;
|
||||||
font-size: @font-size-sm;
|
font-size: @font-size-md;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
line-height: 23px;
|
||||||
color: @home-ink;
|
color: @home-ink;
|
||||||
.multi-ellipsis(2);
|
.multi-ellipsis(1);
|
||||||
}
|
|
||||||
|
|
||||||
&__more {
|
|
||||||
align-self: flex-end;
|
|
||||||
margin-top: auto;
|
|
||||||
font-size: @font-size-sm;
|
|
||||||
color: @text-color-secondary;
|
|
||||||
transition: color 0.2s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计稿:首张「互联网软件」为 #024FFF 选中实底 + 白字白环;其余白底描边
|
// 设计稿:首张「互联网软件」为 #024FFF 选中实底 + 白字白环;其余白底描边
|
||||||
@@ -163,10 +155,6 @@ defineProps<Props>()
|
|||||||
border-color: @primary-strong;
|
border-color: @primary-strong;
|
||||||
background: @primary-strong;
|
background: @primary-strong;
|
||||||
|
|
||||||
.home-business__icon {
|
|
||||||
color: @white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.home-business__check {
|
.home-business__check {
|
||||||
border-color: @white;
|
border-color: @white;
|
||||||
|
|
||||||
@@ -176,28 +164,9 @@ defineProps<Props>()
|
|||||||
}
|
}
|
||||||
|
|
||||||
.home-business__name,
|
.home-business__name,
|
||||||
.home-business__desc,
|
.home-business__desc {
|
||||||
.home-business__more {
|
|
||||||
color: @white;
|
color: @white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__card--hardware {
|
|
||||||
.home-business__icon {
|
|
||||||
color: @tag-hardware;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__card--iot {
|
|
||||||
.home-business__icon {
|
|
||||||
color: @tag-iot;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__card--manufacturing {
|
|
||||||
.home-business__icon {
|
|
||||||
color: @tag-manufacturing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { formatBusinessType } from '@/utils/format'
|
|
||||||
import type { BusinessType } from '@/types/api'
|
import type { BusinessType } from '@/types/api'
|
||||||
import type { Case } from '@/types/business/cases'
|
import type { Case } from '@/types/business/cases'
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ interface Props {
|
|||||||
cases: Case[]
|
cases: Case[]
|
||||||
}
|
}
|
||||||
|
|
||||||
defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
const emit = defineEmits<{ categoryChange: [category: BusinessType | 'all'] }>()
|
const emit = defineEmits<{ categoryChange: [category: BusinessType | 'all'] }>()
|
||||||
|
|
||||||
@@ -26,6 +26,9 @@ function switchCategory(value: BusinessType | 'all'): void {
|
|||||||
activeCategory.value = value
|
activeCategory.value = value
|
||||||
emit('categoryChange', value)
|
emit('categoryChange', value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设计稿一行固定四张 368×248,最多展示四张
|
||||||
|
const visibleCases = computed<Case[]>(() => props.cases.slice(0, 4))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -52,18 +55,14 @@ function switchCategory(value: BusinessType | 'all'): void {
|
|||||||
|
|
||||||
<div class="home-cases__grid">
|
<div class="home-cases__grid">
|
||||||
<article
|
<article
|
||||||
v-for="item in cases"
|
v-for="item in visibleCases"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
class="home-cases__card"
|
class="home-cases__card"
|
||||||
@click="$router.push(`/cases/${item.id}`)"
|
@click="$router.push(`/cases/${item.id}`)"
|
||||||
>
|
>
|
||||||
<div class="home-cases__cover">
|
<div class="home-cases__cover">
|
||||||
<!-- TODO(蓝湖): 替换案例封面切图 -->
|
|
||||||
<img v-if="item.cover" v-lazy="item.cover" class="home-cases__img" :alt="item.title">
|
<img v-if="item.cover" v-lazy="item.cover" class="home-cases__img" :alt="item.title">
|
||||||
<div v-else class="home-cases__img home-cases__img--placeholder" />
|
<div v-else class="home-cases__img home-cases__img--placeholder" />
|
||||||
<span class="home-cases__tag" :class="`home-cases__tag--${item.category}`">
|
|
||||||
{{ formatBusinessType(item.category) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="home-cases__body">
|
<div class="home-cases__body">
|
||||||
@@ -71,12 +70,14 @@ function switchCategory(value: BusinessType | 'all'): void {
|
|||||||
{{ item.title }}
|
{{ item.title }}
|
||||||
</h4>
|
</h4>
|
||||||
<ul class="home-cases__meta">
|
<ul class="home-cases__meta">
|
||||||
<li>客户对象:{{ item.clientType }}</li>
|
<li>客户类型:{{ item.clientType }}</li>
|
||||||
<li>研发周期:{{ item.duration }}</li>
|
<li>所属行业:{{ item.industry }}</li>
|
||||||
<li>团队规模:{{ item.teamSize }}人</li>
|
<li>规模:{{ item.scale }}</li>
|
||||||
|
<li>项目周期:{{ item.duration }}</li>
|
||||||
|
<li>服务团队:{{ item.teamSize }}人</li>
|
||||||
<li>完成时间:{{ item.finishDate }}</li>
|
<li>完成时间:{{ item.finishDate }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
<span class="home-cases__view">查看详情</span>
|
<span class="home-cases__view">查看详情></span>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
@@ -85,23 +86,20 @@ function switchCategory(value: BusinessType | 'all'): void {
|
|||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.home-cases {
|
.home-cases {
|
||||||
// 设计稿区块卡 1680×446 设计px:白底 + #E2E2E2 描边(内容区内左右缩 50 设计px)
|
// 设计稿区块卡 1680×446:白底 + #E2E2E2 描边,标题 @1478(卡顶 +40),卡区左右距 66
|
||||||
max-width: 1294px;
|
padding: 40px 66px 60px;
|
||||||
margin-right: auto;
|
|
||||||
margin-left: auto;
|
|
||||||
padding: 32px 54px 44px;
|
|
||||||
border: 1px solid @home-section-border;
|
border: 1px solid @home-section-border;
|
||||||
border-radius: @radius-lg;
|
border-radius: @radius-lg;
|
||||||
background: @white;
|
background: @white;
|
||||||
|
|
||||||
&__head {
|
&__head {
|
||||||
.flex(row, flex-start);
|
.flex(row, flex-start);
|
||||||
gap: 20px;
|
gap: 24px;
|
||||||
margin-bottom: 36px;
|
margin-bottom: 47px; // 标题盒底 1530 → 卡顶 1577
|
||||||
}
|
}
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
// 设计稿栏目标题 36 设计px / 700 / #121212
|
// 设计稿栏目标题 36px / 700 / #121212
|
||||||
font-size: @font-size-xxl;
|
font-size: @font-size-xxl;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: @home-ink;
|
color: @home-ink;
|
||||||
@@ -109,8 +107,8 @@ function switchCategory(value: BusinessType | 'all'): void {
|
|||||||
|
|
||||||
&__tabs {
|
&__tabs {
|
||||||
.flex(row, center);
|
.flex(row, center);
|
||||||
// 设计稿分类紧跟标题左侧排布(间距 36 设计px ≈ 28)
|
// 设计稿分类紧跟标题左侧排布(间距 36px)
|
||||||
gap: 28px;
|
gap: 36px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__tab {
|
&__tab {
|
||||||
@@ -119,9 +117,9 @@ function switchCategory(value: BusinessType | 'all'): void {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
border: 0;
|
border: 0;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
// 设计稿分类 16 设计px / 500 / #121212(激活 #085DFF + 下划线)
|
// 设计稿分类 16px / 500 / #121212(激活 #085DFF + 下划线)
|
||||||
color: @home-ink;
|
color: @home-ink;
|
||||||
font-size: @font-size-sm;
|
font-size: @font-size-md;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
transition: color 0.2s;
|
transition: color 0.2s;
|
||||||
|
|
||||||
@@ -129,12 +127,12 @@ function switchCategory(value: BusinessType | 'all'): void {
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
color: @home-tab-active;
|
color: @home-tab-active;
|
||||||
|
|
||||||
// 设计稿激活下划线 29×2 设计px(#1F67EF)
|
// 设计稿激活下划线 29×2(#1F67EF)
|
||||||
&::after {
|
&::after {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: -6px;
|
bottom: -8px;
|
||||||
left: 0;
|
left: 0;
|
||||||
height: 2px;
|
height: 2px;
|
||||||
background: @primary-color;
|
background: @primary-color;
|
||||||
@@ -144,19 +142,23 @@ function switchCategory(value: BusinessType | 'all'): void {
|
|||||||
|
|
||||||
&__more {
|
&__more {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
font-size: @font-size-sm;
|
font-size: @font-size-md;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__grid {
|
&__grid {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: repeat(4, 1fr);
|
// 一行固定四张(4×368 + 3×24 = 1544),窄视口行内等比收缩
|
||||||
// 设计稿案例卡间距 24 设计px ≈ 18 CSS
|
gap: 24px;
|
||||||
gap: 18px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__card {
|
&__card {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
// 设计稿案例卡 368×248 设计px:白底 + #E0DEDE 描边 + 0 4 10 黑 8% 投影
|
// 设计稿案例卡固定 368×248,一行四张(4×368 + 3×24 = 1544 ≤ 1560),不足四张不放大
|
||||||
|
flex: 0 1 368px;
|
||||||
|
min-width: 0;
|
||||||
|
height: 248px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
// 设计稿案例卡 368×248:白底 + #E0DEDE 描边 + 0 4 10 黑 8% 投影
|
||||||
border: 1px solid @home-case-border;
|
border: 1px solid @home-case-border;
|
||||||
border-radius: @radius-base;
|
border-radius: @radius-base;
|
||||||
background: @white;
|
background: @white;
|
||||||
@@ -176,8 +178,8 @@ function switchCategory(value: BusinessType | 'all'): void {
|
|||||||
|
|
||||||
&__cover {
|
&__cover {
|
||||||
position: relative;
|
position: relative;
|
||||||
// 设计稿封面区 128 设计px 高 ≈ 99 CSS
|
// 设计稿封面区 88px 高(卡 248:封面 0-88,标题 @109,属性行 @174/194,查看详情 @220)
|
||||||
height: 100px;
|
height: 88px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,53 +193,28 @@ function switchCategory(value: BusinessType | 'all'): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__tag {
|
|
||||||
position: absolute;
|
|
||||||
top: @spacing-sm;
|
|
||||||
left: @spacing-sm;
|
|
||||||
padding: 2px @spacing-sm;
|
|
||||||
border-radius: @radius-sm;
|
|
||||||
background: @primary-color;
|
|
||||||
color: @white;
|
|
||||||
font-size: 11px;
|
|
||||||
|
|
||||||
&--hardware {
|
|
||||||
background: @tag-hardware;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--iot {
|
|
||||||
background: @tag-iot;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--manufacturing {
|
|
||||||
background: @tag-manufacturing;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--other {
|
|
||||||
background: @tag-other;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__body {
|
&__body {
|
||||||
padding: 12px 15px 6px;
|
padding: 0 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__name {
|
&__name {
|
||||||
margin-bottom: 8px;
|
margin: 21px 0 0;
|
||||||
// 设计稿案例名 16 设计px / 500 / #333333
|
// 设计稿案例名 16px / 500 / #333333
|
||||||
font-size: @font-size-sm;
|
font-size: @font-size-md;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
line-height: 22px;
|
||||||
color: @home-body;
|
color: @home-body;
|
||||||
.multi-ellipsis(1);
|
.multi-ellipsis(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__meta {
|
&__meta {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
// 设计稿属性行 13px 两行三列(行1 @174、行2 @194,行高 20)
|
||||||
// 设计稿元信息 10 设计px → 可读下限 11px / #333333
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
gap: 3px @spacing-sm;
|
gap: 0 @spacing-lg;
|
||||||
font-size: 11px;
|
margin-top: 43px;
|
||||||
line-height: 1.5;
|
font-size: 13px;
|
||||||
|
line-height: 20px;
|
||||||
color: @home-body;
|
color: @home-body;
|
||||||
|
|
||||||
li {
|
li {
|
||||||
@@ -248,12 +225,14 @@ function switchCategory(value: BusinessType | 'all'): void {
|
|||||||
&__view {
|
&__view {
|
||||||
display: block;
|
display: block;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
margin: 10px 0 0 auto;
|
// 设计稿「查看详情>」@卡内 y220(13px),右下角
|
||||||
|
margin: 6px 0 0 auto;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: 0;
|
border: 0;
|
||||||
// 设计稿「查看详情>」12 设计px / #024FFF(无按钮描边)
|
// 设计稿「查看详情>」13px / #024FFF(无按钮描边)
|
||||||
color: @primary-strong;
|
color: @primary-strong;
|
||||||
font-size: @font-size-sm;
|
font-size: 13px;
|
||||||
|
line-height: 15px;
|
||||||
transition: color 0.2s;
|
transition: color 0.2s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,19 @@ import { useHomeStore } from '@/stores/modules/home'
|
|||||||
import type { PortalEntryCard } from '@/types/business/home'
|
import type { PortalEntryCard } from '@/types/business/home'
|
||||||
import { toPortalEntryCards } from '@/utils/entryIcon'
|
import { toPortalEntryCards } from '@/utils/entryIcon'
|
||||||
import HomeAppDialog from './HomeAppDialog.vue'
|
import HomeAppDialog from './HomeAppDialog.vue'
|
||||||
|
import tileDemand from '@/assets/home/entry-demand.png'
|
||||||
|
import tileCases from '@/assets/home/entry-cases.png'
|
||||||
|
import tileService from '@/assets/home/entry-service.png'
|
||||||
|
import tileContract from '@/assets/home/entry-contract.png'
|
||||||
|
import tileFinance from '@/assets/home/entry-finance.png'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 首页「快捷入口」卡片。
|
* 首页右上「业务服务」入口卡(设计稿标题即「业务服务」,与下方栏位同名)。
|
||||||
*
|
*
|
||||||
* 数据来自 store.quickEntries(← /vermins/portalEntryUser/list 的 groupType=1 项),
|
* 数据来自 store.quickEntries(← /vermins/portalEntryUser/list 的 groupType=1 项),
|
||||||
* 接口不可用(游客 / 无权限)时回退到内置默认入口,保证卡片不空。
|
* 接口不可用(游客 / 无权限)时回退到内置默认入口,保证卡片不空。
|
||||||
* 「更多应用」打开 HomeAppDialog,可在快捷 / 其他两组之间调整入口。
|
* 设计稿固定五个入口,接口返回的多余项(如硬件 / 互联 / 其他)不展示。
|
||||||
|
* 「>>」双箭头打开 HomeAppDialog,可在快捷 / 其他两组之间调整入口。
|
||||||
*/
|
*/
|
||||||
const homeStore = useHomeStore()
|
const homeStore = useHomeStore()
|
||||||
const { quickEntries } = storeToRefs(homeStore)
|
const { quickEntries } = storeToRefs(homeStore)
|
||||||
@@ -20,24 +26,42 @@ const router = useRouter()
|
|||||||
|
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
|
|
||||||
/** 接口不可用时的默认入口(与改造前的静态入口保持一致) */
|
/** 设计稿固定入口名目(设计稿没有的接口入口不展示) */
|
||||||
|
const DESIGN_NAMES = ['工单需求池', '案例库管理', '客服工作台', '合同台账', '财务台账']
|
||||||
|
|
||||||
|
/** 接口不可用时的默认入口(与设计稿五项一致,tone 对应设计稿五色) */
|
||||||
const FALLBACK_QUICK: PortalEntryCard[] = [
|
const FALLBACK_QUICK: PortalEntryCard[] = [
|
||||||
{ id: 0, label: '财务台账', route: '/finance/bills', groupType: '1', icon: '💰', isImage: false, tone: 'amber' },
|
{ id: 0, label: '工单需求池', route: '/demand', groupType: '1', icon: '', isImage: false, tone: 'cyan' },
|
||||||
{ id: 0, label: '合同台账', route: '/contract', groupType: '1', icon: '📄', isImage: false, tone: 'blue' },
|
{ id: 0, label: '案例库管理', route: '/cases', groupType: '1', icon: '', isImage: false, tone: 'purple' },
|
||||||
{ id: 0, label: '客服工作台', route: '/service', groupType: '1', icon: '💬', isImage: false, tone: 'green' },
|
{ id: 0, label: '客服工作台', route: '/service', groupType: '1', icon: '', isImage: false, tone: 'green' },
|
||||||
{ id: 0, label: '案例库管理', route: '/cases', groupType: '1', icon: '📁', isImage: false, tone: 'purple' },
|
{ id: 0, label: '合同台账', route: '/contract', groupType: '1', icon: '', isImage: false, tone: 'blue' },
|
||||||
{ id: 0, label: '工单需求池', route: '/demand', groupType: '1', icon: '📋', isImage: false, tone: 'cyan' },
|
{ id: 0, label: '财务台账', route: '/finance/bills', groupType: '1', icon: '', isImage: false, tone: 'amber' },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
/** 设计稿切图:蓝湖整稿 97×97 方块(含底色与白色图案),按入口名映射 */
|
||||||
|
const DESIGN_TILES: Record<string, string> = {
|
||||||
|
工单需求池: tileDemand,
|
||||||
|
案例库管理: tileCases,
|
||||||
|
客服工作台: tileService,
|
||||||
|
合同台账: tileContract,
|
||||||
|
财务台账: tileFinance,
|
||||||
|
}
|
||||||
|
|
||||||
/** 前端已注册的静态路由(route 无效时不跳转,避免出现无匹配路由) */
|
/** 前端已注册的静态路由(route 无效时不跳转,避免出现无匹配路由) */
|
||||||
const knownPaths = new Set(router.getRoutes().map((item) => item.path))
|
const knownPaths = new Set(router.getRoutes().map((item) => item.path))
|
||||||
|
|
||||||
/** 卡片展示的入口(最多 5 个) */
|
/** 卡片展示的入口:接口数据按设计稿名目过滤,最多 5 个 */
|
||||||
const entries = computed<PortalEntryCard[]>(() => {
|
const entries = computed<PortalEntryCard[]>(() => {
|
||||||
const list = toPortalEntryCards(quickEntries.value)
|
const list = toPortalEntryCards(quickEntries.value)
|
||||||
|
.filter((card) => DESIGN_NAMES.includes(card.label))
|
||||||
return (list.length > 0 ? list : FALLBACK_QUICK).slice(0, 5)
|
return (list.length > 0 ? list : FALLBACK_QUICK).slice(0, 5)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/** 入口图:接口 iconUrl 图案优先,否则设计稿切图 */
|
||||||
|
function tileSrc(card: PortalEntryCard): string {
|
||||||
|
return card.isImage && card.icon ? card.icon : (DESIGN_TILES[card.label] ?? '')
|
||||||
|
}
|
||||||
|
|
||||||
/** 解析可跳转路径:优先后端 route,其次同名默认入口,最后回首页 */
|
/** 解析可跳转路径:优先后端 route,其次同名默认入口,最后回首页 */
|
||||||
function resolvePath(card: PortalEntryCard): string {
|
function resolvePath(card: PortalEntryCard): string {
|
||||||
if (card.route && knownPaths.has(card.route)) return card.route
|
if (card.route && knownPaths.has(card.route)) return card.route
|
||||||
@@ -56,9 +80,8 @@ function onMore(): void {
|
|||||||
<template>
|
<template>
|
||||||
<section class="home-quick">
|
<section class="home-quick">
|
||||||
<div class="home-quick__head">
|
<div class="home-quick__head">
|
||||||
<span class="home-quick__head-icon">⊞</span>
|
|
||||||
<h3 class="home-quick__title">
|
<h3 class="home-quick__title">
|
||||||
快捷入口
|
业务服务
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -69,17 +92,19 @@ function onMore(): void {
|
|||||||
:to="resolvePath(entry)"
|
:to="resolvePath(entry)"
|
||||||
class="home-quick__item"
|
class="home-quick__item"
|
||||||
>
|
>
|
||||||
<span class="home-quick__icon" :class="`home-quick__icon--${entry.tone}`">
|
<!-- 设计稿:97×97 彩色方块 + 白色图案(蓝湖整稿切图),接口 iconUrl 图案优先 -->
|
||||||
<img v-if="entry.isImage" :src="entry.icon" :alt="entry.label" class="home-quick__img">
|
<img class="home-quick__tile" :src="tileSrc(entry)" :alt="entry.label">
|
||||||
<template v-else>{{ entry.icon }}</template>
|
|
||||||
</span>
|
|
||||||
<span class="home-quick__label">{{ entry.label }}</span>
|
<span class="home-quick__label">{{ entry.label }}</span>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="home-quick__more link" type="button" @click="onMore">
|
<!-- 设计稿:双箭头图标与入口方块行垂直居中(@y875, 32×32,距末位方块 20) -->
|
||||||
更多应用 >
|
<button class="home-quick__more" type="button" aria-label="更多应用" @click="onMore">
|
||||||
|
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M8 6 L18 16 L8 26" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round" fill="none" />
|
||||||
|
<path d="M17 6 L27 16 L17 26" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round" fill="none" />
|
||||||
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<HomeAppDialog v-model:visible="dialogVisible" />
|
<HomeAppDialog v-model:visible="dialogVisible" />
|
||||||
</section>
|
</section>
|
||||||
@@ -90,36 +115,35 @@ function onMore(): void {
|
|||||||
position: relative;
|
position: relative;
|
||||||
.flex(column, flex-start, stretch);
|
.flex(column, flex-start, stretch);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
// 设计稿右卡 722×245 设计px → 556 宽;白底 + 浅灰描边
|
// 设计稿右卡 722 宽;白底 + 浅灰描边
|
||||||
width: 556px;
|
// flex-basis 代替定宽:窄视口允许收缩,避免横向滚动条(1920 下仍为 722)
|
||||||
|
flex: 0 1 722px;
|
||||||
|
min-width: 0;
|
||||||
gap: 0;
|
gap: 0;
|
||||||
padding: 14px 31px 21px;
|
// 设计稿:标题盒顶 y772 = 卡顶 +18,方块行顶 y841 = +87,卡高 264
|
||||||
|
padding: 18px 40px 28px;
|
||||||
border: 1px solid @home-card-border;
|
border: 1px solid @home-card-border;
|
||||||
border-radius: @radius-lg;
|
border-radius: @radius-lg;
|
||||||
background: @white;
|
background: @white;
|
||||||
|
|
||||||
&__head {
|
&__head {
|
||||||
.flex(row, flex-start);
|
.flex(row, flex-start);
|
||||||
gap: @spacing-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__head-icon {
|
|
||||||
color: @home-ink;
|
|
||||||
font-size: @font-size-xl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
// 设计稿卡内标题 36 设计px / 700 / #121212
|
// 设计稿卡内标题 36px / 700 / #121212,盒高 52(y772-824)
|
||||||
font-size: @font-size-xxl;
|
font-size: @font-size-xxl;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
line-height: 52px;
|
||||||
color: @home-ink;
|
color: @home-ink;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__list {
|
&__list {
|
||||||
.flex(row, flex-start, flex-start);
|
.flex(row, flex-start, flex-start);
|
||||||
// 设计稿入口间距 127 设计px(图标 90 + 间隙 37)≈ 70 + 28
|
// 设计稿入口图标间距(图标 97 + 间隙 30);方块行顶 y841 = 卡顶 +87
|
||||||
gap: 28px;
|
gap: 30px;
|
||||||
margin-top: 16px;
|
margin-top: 17px;
|
||||||
|
flex-wrap: wrap; // 卡片被收缩时换行,避免卡内溢出
|
||||||
}
|
}
|
||||||
|
|
||||||
&__item {
|
&__item {
|
||||||
@@ -133,66 +157,41 @@ function onMore(): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__icon {
|
// 设计稿:97×97 彩色方块切图(含底色与白色图案),整图渲染
|
||||||
.flex(row, center);
|
&__tile {
|
||||||
// 设计稿图标块 90×90 设计px ≈ 70 CSS
|
display: block;
|
||||||
width: 70px;
|
width: 97px;
|
||||||
height: 70px;
|
height: 97px;
|
||||||
border-radius: @radius-base;
|
border-radius: @radius-base;
|
||||||
font-size: 28px;
|
object-fit: cover;
|
||||||
|
|
||||||
// 底色按设计稿采样(tone → 设计稿色)
|
|
||||||
&--amber {
|
|
||||||
background: @home-quick-pink;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--blue {
|
|
||||||
background: @home-quick-peach;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--green {
|
|
||||||
background: @home-quick-green;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--purple {
|
|
||||||
background: @home-quick-purple;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--red {
|
|
||||||
background: fade(@danger-color, 15%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&--cyan {
|
|
||||||
background: @home-quick-blue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__img {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__label {
|
&__label {
|
||||||
// 设计稿入口文字 18 设计px / 500 / #121212
|
// 设计稿入口文字 18px / 500 / #121212
|
||||||
font-size: @font-size-base;
|
font-size: @font-size-lg;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: @home-ink;
|
color: @home-ink;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__more {
|
&__more {
|
||||||
// 设计稿「更多」箭头位于卡片右侧垂直居中
|
// 设计稿:» 32×32 @卡内 (665,121),与方块行(顶 y+87,高 97)垂直居中 → top 87+48.5-16 = 119.5
|
||||||
|
// 绝对定位脱离 flex 流:卡收缩(窄视口)时不参与折行
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 120px;
|
||||||
right: 31px;
|
right: 25px;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: 0;
|
border: 0;
|
||||||
transform: translateY(-50%);
|
|
||||||
background: transparent;
|
background: transparent;
|
||||||
font-size: @font-size-sm;
|
color: #c9cdd4;
|
||||||
color: @home-ring;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: color 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: @primary-color;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -29,11 +29,13 @@ defineProps<Props>()
|
|||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.home-contents {
|
.home-contents {
|
||||||
&__block {
|
&__block {
|
||||||
// 设计稿区块卡 1780×593 设计px:白底、无描边投影
|
// 设计稿区块卡 矩形 148286 @(70,736) 1780×593:白底、无描边投影
|
||||||
margin-bottom: @spacing-base;
|
margin-bottom: @spacing-base;
|
||||||
padding: 24px 32px 32px;
|
padding: 40px 45px 0; // 正文 @(115,1090) → 卡内 x45(112−70=42 标题左,正文 115−70=45)
|
||||||
border-radius: @radius-lg;
|
border-radius: @radius-lg;
|
||||||
background: @white;
|
background: @white;
|
||||||
|
min-height: 593px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
@@ -41,17 +43,18 @@ defineProps<Props>()
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
// 设计稿栏目标题 36 设计px / 700 / #121212(无竖条)
|
// 设计稿栏目标题 36px / 700 / #121212
|
||||||
margin-bottom: 36px;
|
margin: 0 0 301px; // 正文顶 1090 − 标题底(736+40+52) ≈ 262 → 取 261
|
||||||
font-size: @font-size-xxl;
|
font-size: 36px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
line-height: 52px;
|
||||||
color: @home-ink;
|
color: @home-ink;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__rich {
|
&__rich {
|
||||||
// 设计稿正文 16 设计px / #222222
|
// 设计稿正文 @(115,1090) 1688×177 16px / #222222
|
||||||
font-size: @font-size-sm;
|
font-size: 16px;
|
||||||
line-height: 1.8;
|
line-height: 1.75;
|
||||||
color: @home-paragraph;
|
color: @home-paragraph;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
|
|
||||||
@@ -83,7 +86,7 @@ defineProps<Props>()
|
|||||||
:deep(h3),
|
:deep(h3),
|
||||||
:deep(h4) {
|
:deep(h4) {
|
||||||
margin: @spacing-base 0 @spacing-sm;
|
margin: @spacing-base 0 @spacing-sm;
|
||||||
font-size: @font-size-base;
|
font-size: 16px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: @home-ink;
|
color: @home-ink;
|
||||||
}
|
}
|
||||||
@@ -98,7 +101,7 @@ defineProps<Props>()
|
|||||||
:deep(th) {
|
:deep(th) {
|
||||||
padding: @spacing-xs @spacing-sm;
|
padding: @spacing-xs @spacing-sm;
|
||||||
border: 1px solid @border-color;
|
border: 1px solid @border-color;
|
||||||
font-size: @font-size-sm;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(a) {
|
:deep(a) {
|
||||||
|
|||||||
@@ -44,17 +44,20 @@ function isImageIcon(icon?: string): boolean {
|
|||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.home-service-types {
|
.home-service-types {
|
||||||
// 设计稿区块卡 1780×287 设计px:白底、无描边投影
|
// 设计稿区块卡 矩形 197 @(70,429) 1780×287:白底、无描边投影
|
||||||
margin-bottom: @spacing-base;
|
margin-bottom: 20px; // 下一卡 矩形 148286 y736 − 本卡底(429+287) = 20
|
||||||
padding: 18px 32px 24px;
|
padding: 24px 42px 0; // 标题 @(112,453) → 卡内 x42 / y24
|
||||||
border-radius: @radius-lg;
|
border-radius: @radius-lg;
|
||||||
background: @white;
|
background: @white;
|
||||||
|
height: 287px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
// 设计稿栏目标题 36 设计px / 700 / #121212(无竖条)
|
// 设计稿栏目标题 @(112,453) 36px / 700 / #121212
|
||||||
margin-bottom: 40px;
|
margin: 0 0 132px; // 圆底顶 550 − 标题底(453+52) 之间留白,服务项文字 @y665
|
||||||
font-size: @font-size-xxl;
|
font-size: 36px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
line-height: 52px;
|
||||||
color: @home-ink;
|
color: @home-ink;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,21 +68,21 @@ function isImageIcon(icon?: string): boolean {
|
|||||||
|
|
||||||
&__item {
|
&__item {
|
||||||
.flex(column, center);
|
.flex(column, center);
|
||||||
// 设计稿条目横向间距 182 设计px → 定宽 140 居中
|
// 设计稿服务项文字 x:146/333/530/695/876 → 间距约 187,取定宽 187 居中
|
||||||
width: 140px;
|
width: 187px;
|
||||||
// 设计稿圆底(550..618)与文字(665)之间 41 设计px ≈ 32 CSS
|
// 设计稿圆底(约 y550..618) 与文字(y665) 之间约 47
|
||||||
gap: 32px;
|
gap: 47px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__icon {
|
&__icon {
|
||||||
.flex(row, center);
|
.flex(row, center);
|
||||||
// 设计稿为 60 设计px 彩色圆底 + 白色图形
|
// 设计稿圆底约 68px
|
||||||
width: 52px;
|
width: 68px;
|
||||||
height: 52px;
|
height: 68px;
|
||||||
border-radius: @radius-round;
|
border-radius: @radius-round;
|
||||||
background: @primary-light;
|
background: @primary-light;
|
||||||
color: @white;
|
color: @white;
|
||||||
font-size: @font-size-xl;
|
font-size: 24px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
@@ -112,8 +115,9 @@ function isImageIcon(icon?: string): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__label {
|
&__label {
|
||||||
// 设计稿条目文字 16 设计px / #333333
|
// 设计稿服务项文字 @y665 16px / #333333
|
||||||
font-size: @font-size-sm;
|
font-size: 16px;
|
||||||
|
line-height: 20px;
|
||||||
color: @home-body;
|
color: @home-body;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
.ellipsis();
|
.ellipsis();
|
||||||
|
|||||||
@@ -31,31 +31,31 @@ const items = [
|
|||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.home-stats {
|
.home-stats {
|
||||||
.flex(row, flex-end);
|
.flex(row, flex-end);
|
||||||
// 设计稿方块间距 30 设计px ≈ 23 CSS
|
// 设计稿方块间距 30px
|
||||||
gap: 23px;
|
gap: 30px;
|
||||||
|
|
||||||
&__item {
|
&__item {
|
||||||
.flex(column, center, center);
|
.flex(column, center, center);
|
||||||
gap: 0;
|
gap: 0;
|
||||||
// 设计稿统计方块 112×112 设计px → 86×86 CSS,#024FFF 实底
|
// 设计稿统计方块 112×112,#024FFF 实底
|
||||||
width: 86px;
|
width: 112px;
|
||||||
height: 86px;
|
height: 112px;
|
||||||
min-width: 86px;
|
min-width: 112px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border-radius: @radius-sm;
|
border-radius: @radius-sm;
|
||||||
background: @primary-strong;
|
background: @primary-strong;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__value {
|
&__value {
|
||||||
// 设计稿数字 48 设计px / 500 / 白
|
// 设计稿数字 48px / 500 / 白
|
||||||
font-size: 37px;
|
font-size: 48px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
color: @white;
|
color: @white;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__label {
|
&__label {
|
||||||
font-size: @font-size-sm;
|
font-size: @font-size-base;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: @white;
|
color: @white;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,24 +37,24 @@ const greeting = computed(() => {
|
|||||||
&__greeting {
|
&__greeting {
|
||||||
.flex(row, flex-start);
|
.flex(row, flex-start);
|
||||||
gap: @spacing-sm;
|
gap: @spacing-sm;
|
||||||
// 设计稿「中午好」52 设计px / 700 / #2C2C2C
|
// 设计稿「中午好」52px / 700 / #2C2C2C
|
||||||
font-size: 40px;
|
font-size: 52px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: @home-welcome;
|
color: @home-welcome;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__wave {
|
&__wave {
|
||||||
.flex(row, center);
|
.flex(row, center);
|
||||||
width: 36px;
|
width: 44px;
|
||||||
height: 36px;
|
height: 44px;
|
||||||
border-radius: @radius-round;
|
border-radius: @radius-round;
|
||||||
background: fade(@black, 4%);
|
background: fade(@black, 4%);
|
||||||
font-size: @font-size-lg;
|
font-size: @font-size-xl;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__summary {
|
&__summary {
|
||||||
// 设计稿副文案 24 设计px / 400 / #2C2C2C
|
// 设计稿副文案 24px / 400 / #2C2C2C
|
||||||
font-size: 18px;
|
font-size: 24px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
color: @home-welcome;
|
color: @home-welcome;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, watch } from 'vue'
|
import { computed, onMounted, watch } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { AppButton } from '@/components'
|
|
||||||
// 业务详情页:数据只经 useHomeStore,不直接 import api
|
// 业务详情页:数据只经 useHomeStore,不直接 import api
|
||||||
import { useHomeStore } from '@/stores/modules/home'
|
import { useHomeStore } from '@/stores/modules/home'
|
||||||
import type { BusinessType } from '@/types/api'
|
import type { BusinessType } from '@/types/api'
|
||||||
@@ -9,7 +8,6 @@ import HomeServiceContents from './cpns/HomeServiceContents.vue'
|
|||||||
import HomeServiceTypes from './cpns/HomeServiceTypes.vue'
|
import HomeServiceTypes from './cpns/HomeServiceTypes.vue'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
|
||||||
const homeStore = useHomeStore()
|
const homeStore = useHomeStore()
|
||||||
|
|
||||||
const line = computed(() => homeStore.currentBusiness)
|
const line = computed(() => homeStore.currentBusiness)
|
||||||
@@ -25,8 +23,10 @@ onMounted(load)
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<!-- 业务详情:设计实测内容区 x70→1850(宽 1780),故与其它常规页一致用 .container -->
|
||||||
<div class="business-detail">
|
<div class="business-detail">
|
||||||
<nav class="business-detail__breadcrumb text-secondary">
|
<div class="container">
|
||||||
|
<nav class="business-detail__breadcrumb">
|
||||||
<RouterLink to="/" class="link">
|
<RouterLink to="/" class="link">
|
||||||
首页
|
首页
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
@@ -36,15 +36,11 @@ onMounted(load)
|
|||||||
|
|
||||||
<template v-if="line">
|
<template v-if="line">
|
||||||
<header class="business-detail__hero">
|
<header class="business-detail__hero">
|
||||||
<span class="business-detail__logo">
|
|
||||||
<!-- TODO(蓝湖): 替换业务图标切图 -->
|
|
||||||
{{ line.name.slice(0, 2) }}
|
|
||||||
</span>
|
|
||||||
<div class="business-detail__intro">
|
<div class="business-detail__intro">
|
||||||
<h1 class="business-detail__name">
|
<h1 class="business-detail__name">
|
||||||
{{ line.name }}
|
{{ line.name }}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="business-detail__slogan text-secondary">
|
<p class="business-detail__slogan">
|
||||||
{{ line.slogan }}
|
{{ line.slogan }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -53,30 +49,27 @@ onMounted(load)
|
|||||||
<HomeServiceTypes :service-types="line.serviceTypes" />
|
<HomeServiceTypes :service-types="line.serviceTypes" />
|
||||||
|
|
||||||
<HomeServiceContents :contents="line.contents" />
|
<HomeServiceContents :contents="line.contents" />
|
||||||
|
|
||||||
<div class="business-detail__cta">
|
|
||||||
<AppButton type="primary" size="lg" @click="router.push('/demand')">
|
|
||||||
立即提报
|
|
||||||
</AppButton>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<p v-else class="text-secondary">
|
<p v-else class="business-detail__loading">
|
||||||
正在加载业务详情...
|
正在加载业务详情...
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.business-detail {
|
.business-detail {
|
||||||
padding-top: @spacing-lg;
|
// 设计实测:面包屑 y104、头图条 y190、白卡 y429/y736
|
||||||
|
padding-top: 32px; // 面包屑顶 = 104 − 顶栏 72
|
||||||
padding-bottom: @spacing-xxl;
|
padding-bottom: @spacing-xxl;
|
||||||
|
|
||||||
&__breadcrumb {
|
&__breadcrumb {
|
||||||
margin-bottom: 36px;
|
// 设计稿面包屑 @(70,104) 24px/500 #666666
|
||||||
// 设计稿面包屑 24 设计px / 500 / #666666
|
margin-bottom: 49px; // 头图条顶 190 − 面包屑底(104+37)
|
||||||
font-size: 18px;
|
font-size: 24px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
line-height: 37px;
|
||||||
color: @home-breadcrumb;
|
color: @home-breadcrumb;
|
||||||
|
|
||||||
.link {
|
.link {
|
||||||
@@ -84,52 +77,52 @@ onMounted(load)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计稿头图卡 1780×219 设计px:浅蓝渐变底、左上图标块 + 标题 + 副标题
|
// 设计稿头图条 矩形 195 @(70,190) 1780×219(浅蓝渐变底)
|
||||||
&__hero {
|
&__hero {
|
||||||
.flex(column, flex-start);
|
.flex(column, flex-start);
|
||||||
gap: 18px;
|
|
||||||
margin-bottom: @spacing-base;
|
margin-bottom: @spacing-base;
|
||||||
padding: 19px 22px 24px;
|
padding: 129px 29px 0; // 业务名 @(99,319) → 卡内 x29 / y129
|
||||||
border-radius: @radius-lg;
|
border-radius: @radius-lg;
|
||||||
background: @home-detail-hero-bg;
|
background: @home-detail-hero-bg;
|
||||||
|
height: 219px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__loading {
|
||||||
|
padding: 80px 0;
|
||||||
|
font-size: 16px;
|
||||||
|
color: @text-color-secondary;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__logo {
|
&__logo {
|
||||||
.flex(row, center);
|
display: none; // 设计稿头图条内无独立 logo 块
|
||||||
width: 62px;
|
|
||||||
height: 62px;
|
|
||||||
border-radius: @radius-lg;
|
|
||||||
background: @home-detail-logo-bg;
|
|
||||||
color: @primary-strong;
|
|
||||||
font-size: @font-size-xl;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__intro {
|
&__intro {
|
||||||
.flex(column, flex-start);
|
.flex(column, flex-start);
|
||||||
gap: 6px;
|
gap: 8px; // 名称底 319+33=352 → 副标题顶 360
|
||||||
}
|
}
|
||||||
|
|
||||||
&__name {
|
&__name {
|
||||||
// 设计稿详情标题 26 设计px / #333333
|
// 设计稿业务名 @(99,319) 26px / #333333
|
||||||
font-size: @font-size-xl;
|
margin: 0;
|
||||||
font-weight: 600;
|
font-size: 26px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 33px;
|
||||||
color: @home-body;
|
color: @home-body;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__slogan {
|
&__slogan {
|
||||||
// 设计稿副标题 14 设计px → 可读下限 11px / #333333
|
// 设计稿副标题 @(99,360) 14px/400 #333333
|
||||||
font-size: 11px;
|
margin: 0;
|
||||||
|
font-size: 14px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
line-height: 18px;
|
||||||
color: @home-body;
|
color: @home-body;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__cta {
|
&__cta {
|
||||||
padding-top: @spacing-base;
|
display: none; // 设计稿无底部 CTA 块
|
||||||
|
|
||||||
.app-button {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import HomeBusinessCards from './cpns/HomeBusinessCards.vue'
|
|||||||
import HomeCaseShowcase from './cpns/HomeCaseShowcase.vue'
|
import HomeCaseShowcase from './cpns/HomeCaseShowcase.vue'
|
||||||
|
|
||||||
const homeStore = useHomeStore()
|
const homeStore = useHomeStore()
|
||||||
const { stats, banners, businessLines, featuredCases } = storeToRefs(homeStore)
|
const { stats, businessLines, featuredCases } = storeToRefs(homeStore)
|
||||||
|
|
||||||
const todaySummary = computed(() => `今天有${stats.value.inProgress}个项目正在进行`)
|
const todaySummary = computed(() => `今天有${stats.value.inProgress}个项目正在进行`)
|
||||||
|
|
||||||
@@ -27,10 +27,11 @@ onMounted(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="home">
|
<div class="home">
|
||||||
<!-- 设计稿头图为通栏(0..1920),故不包 .container -->
|
<!-- 设计稿头图为通栏(0..1920),静态设计图,故不包 .container -->
|
||||||
<HomeBanner :banners="banners" />
|
<HomeBanner />
|
||||||
|
|
||||||
<div class="container home__dashboard">
|
<!-- 首页设计稿单独基准:内容区 1680 / 留白 120,故用 container--home -->
|
||||||
|
<div class="container container--home home__dashboard">
|
||||||
<section class="home__dashboard-main">
|
<section class="home__dashboard-main">
|
||||||
<HomeWelcome :summary="todaySummary" />
|
<HomeWelcome :summary="todaySummary" />
|
||||||
<HomeStats :stats="stats" />
|
<HomeStats :stats="stats" />
|
||||||
@@ -38,11 +39,11 @@ onMounted(() => {
|
|||||||
<HomeQuickEntries />
|
<HomeQuickEntries />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container container--home">
|
||||||
<HomeBusinessCards :lines="businessLines" />
|
<HomeBusinessCards :lines="businessLines" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container container--home">
|
||||||
<HomeCaseShowcase
|
<HomeCaseShowcase
|
||||||
:cases="featuredCases"
|
:cases="featuredCases"
|
||||||
@category-change="onCategoryChange"
|
@category-change="onCategoryChange"
|
||||||
@@ -54,27 +55,24 @@ onMounted(() => {
|
|||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.home {
|
.home {
|
||||||
.flex(column, flex-start);
|
.flex(column, flex-start);
|
||||||
// 设计稿区块间距 19~20 设计px ≈ 16 CSS;头图紧贴顶栏
|
// 设计稿区块间距 ≈ 20px(内容区 1680,区块卡紧邻)
|
||||||
gap: @spacing-base;
|
gap: 20px;
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
padding-bottom: @spacing-xxl;
|
padding-bottom: @spacing-xxl;
|
||||||
|
|
||||||
&__dashboard {
|
&__dashboard {
|
||||||
.flex(row, space-between, stretch);
|
.flex(row, space-between, stretch);
|
||||||
// 设计稿左右卡间距 36 设计px ≈ 28 CSS
|
// 设计稿左右卡间距 36px(左卡宽 922 / 右卡宽 722,内容区 1680)
|
||||||
gap: 28px;
|
gap: 36px;
|
||||||
// 设计稿区块行 1680 设计px 宽(内容区内左右各缩 50 设计px)≈ 1294 CSS
|
|
||||||
max-width: 1326px;
|
|
||||||
margin-right: auto;
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__dashboard-main {
|
&__dashboard-main {
|
||||||
.flex(row, space-between, center);
|
.flex(row, space-between, center);
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
min-width: 0; // 允许收缩,右侧快捷卡才能在窄视口让位
|
||||||
gap: @spacing-lg;
|
gap: @spacing-lg;
|
||||||
// 设计稿欢迎卡(120,754,922,245):白底 + 浅灰描边,内边距 61/106/66/73 设计px
|
// 设计稿欢迎卡(120,754,922,245):白底 + 浅灰描边
|
||||||
padding: 46px 56px 46px 82px;
|
padding: 0 73px 0 106px;
|
||||||
border: 1px solid @home-card-border;
|
border: 1px solid @home-card-border;
|
||||||
border-radius: @radius-lg;
|
border-radius: @radius-lg;
|
||||||
background: @white;
|
background: @white;
|
||||||
|
|||||||
@@ -35,7 +35,9 @@ const localAgree = computed({
|
|||||||
<template>
|
<template>
|
||||||
<form class="login-form" @submit.prevent="emit('submit')">
|
<form class="login-form" @submit.prevent="emit('submit')">
|
||||||
<div class="login-form__field">
|
<div class="login-form__field">
|
||||||
|
<label class="login-form__label" for="login-account">账号</label>
|
||||||
<input
|
<input
|
||||||
|
id="login-account"
|
||||||
v-model="localAccount"
|
v-model="localAccount"
|
||||||
class="login-form__input"
|
class="login-form__input"
|
||||||
type="text"
|
type="text"
|
||||||
@@ -44,7 +46,9 @@ const localAgree = computed({
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="login-form__field">
|
<div class="login-form__field">
|
||||||
|
<label class="login-form__label" for="login-password">密码</label>
|
||||||
<input
|
<input
|
||||||
|
id="login-password"
|
||||||
v-model="localPassword"
|
v-model="localPassword"
|
||||||
class="login-form__input"
|
class="login-form__input"
|
||||||
type="password"
|
type="password"
|
||||||
@@ -64,23 +68,38 @@ const localAgree = computed({
|
|||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.login-form {
|
.login-form {
|
||||||
.flex(column, flex-start);
|
.flex(column, flex-start, stretch);
|
||||||
gap: @spacing-lg;
|
// 设计稿:Tab 行 x1039 → 表单列整列 x1039 = 卡列 x1022 + 17
|
||||||
|
margin: 26px 0 0 17px;
|
||||||
|
width: 680px;
|
||||||
|
|
||||||
&__field {
|
&__field {
|
||||||
width: 100%;
|
.flex(column, flex-start, stretch);
|
||||||
|
gap: 8px;
|
||||||
|
width: 680px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计稿:输入框 40 设计px ≈ 32px 高、13 设计px 字号(可读下限 11px)、白底浅描边
|
&__field + &__field {
|
||||||
|
margin-top: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
font-size: 13px; // 设计稿 13/500 #1F2937
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 17px;
|
||||||
|
color: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设计稿:输入框 680×39、白底 + #DDE1E7 描边、文字 13/400
|
||||||
&__input {
|
&__input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 32px;
|
height: 39px;
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
border: 1px solid @border-color;
|
border: 1px solid #dde1e7;
|
||||||
border-radius: 6px;
|
border-radius: @radius-sm;
|
||||||
background: @white;
|
background: @white;
|
||||||
font-size: 11px;
|
font-size: 13px;
|
||||||
color: @heading-color;
|
color: #1f2937;
|
||||||
outline: none;
|
outline: none;
|
||||||
transition: border-color 0.2s;
|
transition: border-color 0.2s;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@@ -90,19 +109,26 @@ const localAgree = computed({
|
|||||||
}
|
}
|
||||||
|
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
color: @heading-color;
|
color: #1f2937;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设计稿:协议行 @(1039,641);密码输入底 649 → 协议行文字基线对齐
|
||||||
&__agree {
|
&__agree {
|
||||||
.flex(row, flex-start);
|
.flex(row, flex-start, center);
|
||||||
gap: @spacing-xs;
|
gap: 8px;
|
||||||
font-size: 11px;
|
margin-top: -8px;
|
||||||
color: @text-color-secondary;
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 16px;
|
||||||
|
color: #6b7280;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
input {
|
input {
|
||||||
margin-top: 1px;
|
flex-shrink: 0;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
margin: 0;
|
||||||
accent-color: @primary-strong;
|
accent-color: @primary-strong;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,17 +139,17 @@ const localAgree = computed({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计稿:登录按钮 44 设计px ≈ 34px 高、#024FFF 实底
|
// 设计稿:登录按钮 680×46 @y675、#024FFF 实底、文字 15/500
|
||||||
&__submit {
|
&__submit {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 34px;
|
height: 46px;
|
||||||
|
margin-top: 18px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 6px;
|
border-radius: @radius-sm;
|
||||||
background: @primary-strong;
|
background: @primary-strong;
|
||||||
color: @white;
|
color: @white;
|
||||||
font-size: 12px;
|
font-size: 15px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
letter-spacing: 2px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.2s;
|
transition: background 0.2s;
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,9 @@ function onSubmit(): void {
|
|||||||
<template>
|
<template>
|
||||||
<form class="sms-login" @submit.prevent="onSubmit">
|
<form class="sms-login" @submit.prevent="onSubmit">
|
||||||
<div class="sms-login__field">
|
<div class="sms-login__field">
|
||||||
|
<label class="sms-login__label" for="sms-phone">手机号</label>
|
||||||
<input
|
<input
|
||||||
|
id="sms-phone"
|
||||||
v-model="localPhone"
|
v-model="localPhone"
|
||||||
class="sms-login__input"
|
class="sms-login__input"
|
||||||
type="text"
|
type="text"
|
||||||
@@ -69,6 +71,7 @@ function onSubmit(): void {
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="sms-login__field">
|
<div class="sms-login__field">
|
||||||
|
<label class="sms-login__label" for="sms-code">验证码</label>
|
||||||
<SmsCodeInput v-model:code="localCode" @get-code="onGetCode" />
|
<SmsCodeInput v-model:code="localCode" @get-code="onGetCode" />
|
||||||
</div>
|
</div>
|
||||||
<label class="sms-login__agree">
|
<label class="sms-login__agree">
|
||||||
@@ -83,23 +86,39 @@ function onSubmit(): void {
|
|||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.sms-login {
|
.sms-login {
|
||||||
.flex(column, flex-start);
|
.flex(column, flex-start, stretch);
|
||||||
gap: @spacing-lg;
|
// 设计稿:表单列 x989,Tab 行底 412 → 首个标签 438 = 26
|
||||||
|
margin: 26px 0 0 17px;
|
||||||
|
width: 680px;
|
||||||
|
|
||||||
&__field {
|
&__field {
|
||||||
width: 100%;
|
.flex(column, flex-start, stretch);
|
||||||
|
gap: 8px;
|
||||||
|
width: 680px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计稿:输入框 40 设计px ≈ 32px 高、13 设计px 字号(可读下限 11px)、白底浅描边
|
// 设计稿:手机号输入底 502 → 验证码标签 520 = 18;标签行盒 17 → 18
|
||||||
|
&__field + &__field {
|
||||||
|
margin-top: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
font-size: 13px; // 设计稿 13/500 #1F2937
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 17px;
|
||||||
|
color: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设计稿:输入框 680×39、白底 + #DDE1E7 描边、文字 13/400
|
||||||
&__input {
|
&__input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 32px;
|
height: 39px;
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
border: 1px solid @border-color;
|
border: 1px solid #dde1e7;
|
||||||
border-radius: 6px;
|
border-radius: @radius-sm;
|
||||||
background: @white;
|
background: @white;
|
||||||
font-size: 11px;
|
font-size: 13px;
|
||||||
color: @heading-color;
|
color: #1f2937;
|
||||||
outline: none;
|
outline: none;
|
||||||
transition: border-color 0.2s;
|
transition: border-color 0.2s;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@@ -109,19 +128,26 @@ function onSubmit(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
color: @heading-color;
|
color: #1f2937;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设计稿:协议行 @(989,622);验证码输入底 584 → 协议行 38
|
||||||
&__agree {
|
&__agree {
|
||||||
.flex(row, flex-start);
|
.flex(row, flex-start, center);
|
||||||
gap: @spacing-xs;
|
gap: 8px;
|
||||||
font-size: 11px;
|
margin-top: 22px;
|
||||||
color: @text-color-secondary;
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 16px;
|
||||||
|
color: #6b7280;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
input {
|
input {
|
||||||
margin-top: 1px;
|
flex-shrink: 0;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
margin: 0;
|
||||||
accent-color: @primary-strong;
|
accent-color: @primary-strong;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,17 +158,17 @@ function onSubmit(): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计稿:登录按钮 44 设计px ≈ 34px 高、#024FFF 实底
|
// 设计稿:登录按钮 680×46 @y656、#024FFF 实底、文字 15/500
|
||||||
&__submit {
|
&__submit {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 34px;
|
height: 46px;
|
||||||
|
margin-top: 18px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 6px;
|
border-radius: @radius-sm;
|
||||||
background: @primary-strong;
|
background: @primary-strong;
|
||||||
color: @white;
|
color: @white;
|
||||||
font-size: 12px;
|
font-size: 15px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
letter-spacing: 2px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.2s;
|
transition: background 0.2s;
|
||||||
|
|
||||||
|
|||||||
@@ -72,22 +72,33 @@ async function onGetCode(target: string): Promise<void> {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="auth-page">
|
<div class="auth-page">
|
||||||
|
<!-- 设计稿:左侧品牌渐变面板 @(0,0) 784×1080,文案列 x257 -->
|
||||||
<aside class="auth-page__brand">
|
<aside class="auth-page__brand">
|
||||||
<div class="auth-page__brand-inner">
|
<h1 class="auth-page__logo">
|
||||||
<p class="auth-page__logo">
|
虫子智造研发<br>一体化平台
|
||||||
虫子智造研发一体化平台
|
</h1>
|
||||||
|
<p class="auth-page__key">
|
||||||
|
chongzizhizaoyanfayitihuaPINGTAI
|
||||||
</p>
|
</p>
|
||||||
<p class="auth-page__slogan">
|
<p class="auth-page__slogan">
|
||||||
安全 · 高效 · 可控
|
安全 · 高效 · 可控
|
||||||
</p>
|
</p>
|
||||||
<RouterLink class="auth-page__guest" to="/">
|
|
||||||
游客模式 →
|
|
||||||
</RouterLink>
|
|
||||||
</div>
|
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
|
<RouterLink class="auth-page__guest" to="/">
|
||||||
|
游客模式
|
||||||
|
</RouterLink>
|
||||||
|
|
||||||
|
<!-- 设计稿:右侧表单区起点 x1022,表单列宽 693(标签)/680(控件) -->
|
||||||
<main class="auth-page__main">
|
<main class="auth-page__main">
|
||||||
<div class="auth-page__card">
|
<div class="auth-page__card">
|
||||||
|
<h2 class="auth-page__title">
|
||||||
|
欢迎登录
|
||||||
|
</h2>
|
||||||
|
<p class="auth-page__subtitle">
|
||||||
|
登录您的账号,开启高效之旅
|
||||||
|
</p>
|
||||||
|
|
||||||
<div class="auth-page__tabs">
|
<div class="auth-page__tabs">
|
||||||
<!-- 设计稿 Tab 顺序:手机验证码登录在前、账号密码登录在后 -->
|
<!-- 设计稿 Tab 顺序:手机验证码登录在前、账号密码登录在后 -->
|
||||||
<button
|
<button
|
||||||
@@ -130,118 +141,52 @@ async function onGetCode(target: string): Promise<void> {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
// 共用外壳 .auth-page 定义在 styles/common.less(登录/注册/找回密码三页共享)
|
||||||
|
</style>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
// ===== 账号密码登录页专属(--auth-form-x/y 默认值即本页基准) =====
|
||||||
.auth-page {
|
.auth-page {
|
||||||
position: relative;
|
&__subtitle {
|
||||||
display: flex;
|
margin: 12px 0 0;
|
||||||
min-height: 100vh;
|
font-size: 13px; // 设计稿 13/400 #6B7280
|
||||||
// 设计稿:右侧表单区为纯白底,无卡片描边/阴影
|
font-weight: 400;
|
||||||
background: @white;
|
line-height: 17px;
|
||||||
|
color: #6b7280;
|
||||||
&__brand {
|
|
||||||
// 设计稿:左侧品牌渐变面板约占 786/1920 ≈ 41%
|
|
||||||
flex: 0 0 41%;
|
|
||||||
.flex(row, flex-start, stretch);
|
|
||||||
background: linear-gradient(135deg, @auth-gradient-from 0%, @auth-gradient-to 100%);
|
|
||||||
color: @white;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__brand-inner {
|
|
||||||
.flex(column, space-between, flex-start);
|
|
||||||
width: 100%;
|
|
||||||
// 设计稿:文字列起点 x=257 ≈ 面板宽 32.7%;主标距顶 ≈ 28%、标语底边距底 ≈ 30%
|
|
||||||
padding: 26vh 48px 30vh 32%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__logo {
|
|
||||||
font-size: 31px;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: 2px;
|
|
||||||
line-height: 1.45;
|
|
||||||
// 设计稿文本框 432 设计px:定宽让 11 字在「虫子智造研发 / 一体化平台」处折行
|
|
||||||
max-width: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__slogan {
|
|
||||||
font-size: 17px;
|
|
||||||
font-weight: 600;
|
|
||||||
letter-spacing: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设计稿:「游客模式」为右上角白底上的深色纯文本,无边框
|
|
||||||
&__guest {
|
|
||||||
position: absolute;
|
|
||||||
top: 18px;
|
|
||||||
right: 16px;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
border: none;
|
|
||||||
border-radius: 0;
|
|
||||||
color: @heading-color;
|
|
||||||
font-size: 11px;
|
|
||||||
text-decoration: none;
|
|
||||||
transition: color 0.2s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: transparent;
|
|
||||||
color: @primary-strong;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__main {
|
|
||||||
flex: 1;
|
|
||||||
.flex(row, center, center);
|
|
||||||
padding: @spacing-xl;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__card {
|
|
||||||
.flex(column, flex-start);
|
|
||||||
// 设计稿:表单列宽 688 设计px ≈ 530px,标题/Tab/输入/按钮纵向节奏 ≈ 41px
|
|
||||||
gap: 41px;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 530px;
|
|
||||||
padding: 0;
|
|
||||||
border-radius: 0;
|
|
||||||
background: transparent;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设计稿:Tab 行 @(1039,406),两项间距 24(起 x1039 / x1163)
|
||||||
&__tabs {
|
&__tabs {
|
||||||
// 设计稿:Tab 左对齐于表单列,仅激活项带 2px 蓝色下划线(无整行分隔线)
|
.flex(row, flex-start, center);
|
||||||
.flex(row, flex-start);
|
gap: 24px;
|
||||||
gap: @spacing-sm;
|
margin: 26px 0 0 17px; // 406 − 363 = 43;x1039 − 1022 = 17
|
||||||
border-bottom: none;
|
height: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__tab {
|
&__tab {
|
||||||
padding: 0 0 4px;
|
padding: 0;
|
||||||
border: none;
|
border: none;
|
||||||
border-bottom: 2px solid transparent;
|
|
||||||
background: transparent;
|
background: transparent;
|
||||||
font-size: 11px;
|
font-size: 14px; // 设计稿 14/400 #4B5563 → 激活 14/600 #024FFF
|
||||||
color: @text-color;
|
font-weight: 400;
|
||||||
|
color: #4b5563;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s;
|
transition: color 0.2s;
|
||||||
|
|
||||||
&.is-active {
|
&.is-active {
|
||||||
border-bottom-color: @primary-strong;
|
|
||||||
color: @primary-strong;
|
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
color: @primary-strong;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__switch {
|
&__switch {
|
||||||
text-align: center;
|
align-self: center; // 设计稿:X 居中 @(1325,774)
|
||||||
font-size: 11px;
|
margin: 53px 0 0; // 按钮底 721 → 774
|
||||||
color: @text-color-secondary;
|
font-size: 12px; // 设计稿 12/400 #6B7280
|
||||||
}
|
font-weight: 400;
|
||||||
|
line-height: 16px;
|
||||||
&__link {
|
color: #6b7280;
|
||||||
color: @primary-strong;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: @primary-hover;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,30 +2,45 @@
|
|||||||
import { onBeforeUnmount, ref } from 'vue'
|
import { onBeforeUnmount, ref } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { PasswordStrength } from '@/components'
|
import { PasswordStrength, SmsCodeInput } from '@/components'
|
||||||
import { useUserStore } from '@/stores/modules/user'
|
import { useUserStore } from '@/stores/modules/user'
|
||||||
|
import { isPhone, isValidPassword } from '@/utils/validate'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
|
||||||
const username = ref('')
|
const phone = ref('')
|
||||||
|
const phoneCode = ref('')
|
||||||
const password = ref('')
|
const password = ref('')
|
||||||
const confirmPassword = ref('')
|
const confirmPassword = ref('')
|
||||||
const agree = ref(false)
|
const agree = ref(false)
|
||||||
|
|
||||||
let redirectTimer: number | null = null
|
let redirectTimer: number | null = null
|
||||||
|
|
||||||
|
async function onGetCode(): Promise<void> {
|
||||||
|
if (!isPhone(phone.value)) {
|
||||||
|
ElMessage.warning('手机号格式不正确')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await userStore.sendSmsCode(phone.value)
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
// 失败提示已由 store 统一弹出,此处仅避免未处理的 rejection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function onRegister(): Promise<void> {
|
async function onRegister(): Promise<void> {
|
||||||
if (!username.value.trim()) {
|
if (!isPhone(phone.value)) {
|
||||||
ElMessage.warning('请输入账号')
|
ElMessage.warning('手机号格式不正确')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (username.value.trim().length < 2 || username.value.trim().length > 30) {
|
if (!/^\d{6}$/.test(phoneCode.value)) {
|
||||||
ElMessage.warning('账号长度应为2-30位')
|
ElMessage.warning('请输入6位验证码')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (password.value.length < 5 || password.value.length > 30) {
|
if (!isValidPassword(password.value)) {
|
||||||
ElMessage.warning('密码长度应为5-30位')
|
ElMessage.warning('密码长度应为6-20位')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (password.value !== confirmPassword.value) {
|
if (password.value !== confirmPassword.value) {
|
||||||
@@ -37,7 +52,7 @@ async function onRegister(): Promise<void> {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
const ok = await userStore.register({
|
const ok = await userStore.register({
|
||||||
username: username.value.trim(),
|
username: phone.value,
|
||||||
password: password.value,
|
password: password.value,
|
||||||
confirmPassword: confirmPassword.value,
|
confirmPassword: confirmPassword.value,
|
||||||
})
|
})
|
||||||
@@ -54,63 +69,88 @@ onBeforeUnmount(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="auth-page">
|
<div class="auth-page register">
|
||||||
<aside class="auth-page__brand">
|
<aside class="auth-page__brand">
|
||||||
<div class="auth-page__brand-inner">
|
<h1 class="auth-page__logo">
|
||||||
<p class="auth-page__logo">
|
虫子智造研发<br>一体化平台
|
||||||
虫子智造研发一体化平台
|
</h1>
|
||||||
|
<p class="auth-page__key">
|
||||||
|
chongzizhizaoyanfayitihuaPINGTAI
|
||||||
</p>
|
</p>
|
||||||
<p class="auth-page__slogan">
|
<p class="auth-page__slogan">
|
||||||
安全 · 高效 · 可控
|
安全 · 高效 · 可控
|
||||||
</p>
|
</p>
|
||||||
<RouterLink class="auth-page__guest" to="/">
|
|
||||||
游客模式 →
|
|
||||||
</RouterLink>
|
|
||||||
</div>
|
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
|
<RouterLink class="auth-page__guest" to="/">
|
||||||
|
游客模式
|
||||||
|
</RouterLink>
|
||||||
|
|
||||||
<main class="auth-page__main">
|
<main class="auth-page__main">
|
||||||
<div class="auth-page__card">
|
<div class="auth-page__card">
|
||||||
<h1 class="auth-page__title">
|
<h2 class="auth-page__title">
|
||||||
注册账号
|
创建账号
|
||||||
</h1>
|
</h2>
|
||||||
|
<p class="auth-page__subtitle">
|
||||||
|
注册后即可开始使用全部功能
|
||||||
|
</p>
|
||||||
|
|
||||||
<form class="auth-page__form" @submit.prevent="onRegister">
|
<form class="register__form" @submit.prevent="onRegister">
|
||||||
|
<div class="register__field">
|
||||||
|
<label class="register__label" for="reg-phone">手机号</label>
|
||||||
<input
|
<input
|
||||||
v-model="username"
|
id="reg-phone"
|
||||||
class="auth-page__input"
|
v-model="phone"
|
||||||
|
class="register__input"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="请输入账号(2-30位)"
|
placeholder="请输入手机号"
|
||||||
maxlength="30"
|
maxlength="11"
|
||||||
autocomplete="username"
|
autocomplete="tel"
|
||||||
>
|
>
|
||||||
<div class="auth-page__pwd-wrap">
|
</div>
|
||||||
|
|
||||||
|
<div class="register__field">
|
||||||
|
<label class="register__label" for="reg-code">验证码</label>
|
||||||
|
<SmsCodeInput id="reg-code" v-model:code="phoneCode" @get-code="onGetCode" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="register__field">
|
||||||
|
<label class="register__label" for="reg-password">设置密码</label>
|
||||||
<input
|
<input
|
||||||
|
id="reg-password"
|
||||||
v-model="password"
|
v-model="password"
|
||||||
class="auth-page__input"
|
class="register__input"
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="设置密码(5-30位)"
|
placeholder="请设置6-20位密码"
|
||||||
autocomplete="new-password"
|
autocomplete="new-password"
|
||||||
>
|
>
|
||||||
|
<!-- 设计稿:密码框下 15 高密码强度条 + 15 高提示行 -->
|
||||||
<PasswordStrength :password="password" />
|
<PasswordStrength :password="password" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="register__field">
|
||||||
|
<label class="register__label" for="reg-confirm">确认密码</label>
|
||||||
<input
|
<input
|
||||||
|
id="reg-confirm"
|
||||||
v-model="confirmPassword"
|
v-model="confirmPassword"
|
||||||
class="auth-page__input"
|
class="register__input"
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="确认密码"
|
placeholder="请设置6-20位密码"
|
||||||
autocomplete="new-password"
|
autocomplete="new-password"
|
||||||
>
|
>
|
||||||
<label class="auth-page__agree">
|
</div>
|
||||||
|
|
||||||
|
<label class="register__agree">
|
||||||
<input v-model="agree" type="checkbox">
|
<input v-model="agree" type="checkbox">
|
||||||
<span>已阅读并同意<em>《用户协议》</em>和<em>《隐私政策》</em></span>
|
<span>已阅读并同意<em>《用户协议》</em>和<em>《隐私政策》</em></span>
|
||||||
</label>
|
</label>
|
||||||
<button class="auth-page__submit" type="submit">
|
|
||||||
|
<button class="register__submit" type="submit">
|
||||||
注册
|
注册
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<p class="auth-page__switch">
|
<p class="register__switch">
|
||||||
已有账号?<RouterLink class="auth-page__link" to="/login">立即登录</RouterLink>
|
已有账号?<RouterLink class="auth-page__link" to="/login">立即登录</RouterLink>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -119,112 +159,45 @@ onBeforeUnmount(() => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.auth-page {
|
// 设计稿(注册账号):表单列起点 x999(--auth-form-x 982 + 17)、垂直顶 268
|
||||||
position: relative;
|
.register {
|
||||||
display: flex;
|
--auth-form-x: 982px;
|
||||||
min-height: 100vh;
|
--auth-form-y: 268px;
|
||||||
// 设计稿:右侧表单区为纯白底,无卡片描边/阴影
|
|
||||||
background: @white;
|
|
||||||
|
|
||||||
&__brand {
|
|
||||||
// 设计稿:左侧品牌渐变面板约占 786/1920 ≈ 41%
|
|
||||||
flex: 0 0 41%;
|
|
||||||
.flex(row, flex-start, stretch);
|
|
||||||
background: linear-gradient(135deg, @auth-gradient-from 0%, @auth-gradient-to 100%);
|
|
||||||
color: @white;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__brand-inner {
|
|
||||||
.flex(column, space-between, flex-start);
|
|
||||||
width: 100%;
|
|
||||||
// 设计稿:文字列起点 x=257 ≈ 面板宽 32.7%;主标距顶 ≈ 28%、标语底边距底 ≈ 30%
|
|
||||||
padding: 26vh 48px 30vh 32%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__logo {
|
|
||||||
font-size: 31px;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: 2px;
|
|
||||||
line-height: 1.45;
|
|
||||||
// 设计稿文本框 432 设计px:定宽让 11 字在「虫子智造研发 / 一体化平台」处折行
|
|
||||||
max-width: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__slogan {
|
|
||||||
font-size: 17px;
|
|
||||||
font-weight: 600;
|
|
||||||
letter-spacing: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设计稿:「游客模式」为右上角白底上的深色纯文本,无边框
|
|
||||||
&__guest {
|
|
||||||
position: absolute;
|
|
||||||
top: 18px;
|
|
||||||
right: 16px;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
border: none;
|
|
||||||
border-radius: 0;
|
|
||||||
color: @heading-color;
|
|
||||||
font-size: 11px;
|
|
||||||
text-decoration: none;
|
|
||||||
transition: color 0.2s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: transparent;
|
|
||||||
color: @primary-strong;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__main {
|
|
||||||
flex: 1;
|
|
||||||
.flex(row, center, center);
|
|
||||||
padding: @spacing-xl;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__card {
|
|
||||||
.flex(column, flex-start);
|
|
||||||
// 设计稿:表单列宽 688 设计px ≈ 530px,标题/输入/按钮纵向节奏 ≈ 41px
|
|
||||||
gap: 41px;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 530px;
|
|
||||||
padding: 0;
|
|
||||||
border-radius: 0;
|
|
||||||
background: transparent;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设计稿:标题(34 设计px ≈ 26px)到首个输入框 ≈ 62px
|
|
||||||
&__title {
|
|
||||||
margin-bottom: 21px;
|
|
||||||
font-size: 26px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: @heading-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__form {
|
&__form {
|
||||||
.flex(column, flex-start);
|
.flex(column, flex-start, stretch);
|
||||||
gap: @spacing-lg;
|
// 副标题底 342 → 首个标签 368 = 26(副标题行盒 17 + 外边距 12 → 实际需补 38)
|
||||||
width: 100%;
|
margin: 38px 0 0 17px;
|
||||||
|
width: 680px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置密码 → 密码强度条 ≈ 9px,强度条 → 提示 ≈ 9px(组件内)
|
&__field {
|
||||||
&__pwd-wrap {
|
.flex(column, flex-start, stretch);
|
||||||
.flex(column, flex-start);
|
gap: 8px;
|
||||||
gap: 9px;
|
width: 680px;
|
||||||
width: 100%;
|
}
|
||||||
|
|
||||||
|
// 设计稿:手机号框底 430 → 验证码标签 450 = 20;验证码框底 514 → 设置密码标签 532 = 18
|
||||||
|
&__field + &__field {
|
||||||
|
margin-top: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
font-size: 13px; // 设计稿 13/500 #1F2937
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 17px;
|
||||||
|
color: #1f2937;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计稿:输入框 40 设计px ≈ 32px 高、13 设计px 字号(可读下限 11px)、白底浅描边
|
|
||||||
&__input {
|
&__input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 32px;
|
height: 39px;
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
border: 1px solid @border-color;
|
border: 1px solid #dde1e7;
|
||||||
border-radius: 6px;
|
border-radius: @radius-sm;
|
||||||
background: @white;
|
background: @white;
|
||||||
font-size: 11px;
|
font-size: 13px;
|
||||||
color: @heading-color;
|
color: #1f2937;
|
||||||
outline: none;
|
outline: none;
|
||||||
transition: border-color 0.2s;
|
transition: border-color 0.2s;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@@ -234,40 +207,46 @@ onBeforeUnmount(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
color: @heading-color;
|
color: #1f2937;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设计稿:协议行 @(999,748)
|
||||||
&__agree {
|
&__agree {
|
||||||
.flex(row, flex-start);
|
.flex(row, flex-start, center);
|
||||||
gap: @spacing-xs;
|
gap: 8px;
|
||||||
font-size: 11px;
|
margin-top: 22px;
|
||||||
color: @text-color-secondary;
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 16px;
|
||||||
|
color: #6b7280;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
input {
|
input {
|
||||||
margin-top: 1px;
|
flex-shrink: 0;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
margin: 0;
|
||||||
accent-color: @primary-strong;
|
accent-color: @primary-strong;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计稿:《用户协议》《隐私政策》为强调蓝,其余为次级灰
|
|
||||||
em {
|
em {
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
color: @primary-strong;
|
color: @primary-strong;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计稿:注册按钮 44 设计px ≈ 34px 高、#024FFF 实底
|
// 设计稿:注册按钮 680×46 @y782、#024FFF 实底、文字 15/500
|
||||||
&__submit {
|
&__submit {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 34px;
|
height: 46px;
|
||||||
|
margin-top: 18px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 6px;
|
border-radius: @radius-sm;
|
||||||
background: @primary-strong;
|
background: @primary-strong;
|
||||||
color: @white;
|
color: @white;
|
||||||
font-size: 12px;
|
font-size: 15px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
letter-spacing: 2px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.2s;
|
transition: background 0.2s;
|
||||||
|
|
||||||
@@ -276,20 +255,22 @@ onBeforeUnmount(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计稿:按钮 → 底部切换链接仅约 13px
|
// 设计稿:按钮底 828 → 切换行 844 = 16
|
||||||
&__switch {
|
&__switch {
|
||||||
margin-top: -26px;
|
align-self: center;
|
||||||
text-align: center;
|
margin: 16px 0 0;
|
||||||
font-size: 11px;
|
font-size: 12px;
|
||||||
color: @text-color-secondary;
|
font-weight: 400;
|
||||||
|
line-height: 16px;
|
||||||
|
color: #6b7280;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__link {
|
&__subtitle {
|
||||||
color: @primary-strong;
|
margin: 12px 0 0;
|
||||||
|
font-size: 13px; // 设计稿 13/400 #6B7280
|
||||||
&:hover {
|
font-weight: 400;
|
||||||
color: @primary-hover;
|
line-height: 17px;
|
||||||
}
|
color: #6b7280;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { onBeforeUnmount, ref } from 'vue'
|
import { onBeforeUnmount, ref } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { SmsCodeInput } from '@/components'
|
import { PasswordStrength, SmsCodeInput } from '@/components'
|
||||||
import { useUserStore } from '@/stores/modules/user'
|
import { useUserStore } from '@/stores/modules/user'
|
||||||
import { isPhone, isValidPassword } from '@/utils/validate'
|
import { isPhone, isValidPassword } from '@/utils/validate'
|
||||||
|
|
||||||
@@ -71,62 +71,89 @@ onBeforeUnmount(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="auth-page">
|
<div class="auth-page reset">
|
||||||
<aside class="auth-page__brand">
|
<aside class="auth-page__brand">
|
||||||
<div class="auth-page__brand-inner">
|
<h1 class="auth-page__logo">
|
||||||
<p class="auth-page__logo">
|
虫子智造研发<br>一体化平台
|
||||||
虫子智造研发一体化平台
|
</h1>
|
||||||
|
<p class="auth-page__key">
|
||||||
|
chongzizhizaoyanfayitihuaPINGTAI
|
||||||
</p>
|
</p>
|
||||||
<p class="auth-page__slogan">
|
<p class="auth-page__slogan">
|
||||||
安全 · 高效 · 可控
|
安全 · 高效 · 可控
|
||||||
</p>
|
</p>
|
||||||
<RouterLink class="auth-page__guest" to="/">
|
|
||||||
游客模式 →
|
|
||||||
</RouterLink>
|
|
||||||
</div>
|
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
|
<RouterLink class="auth-page__guest" to="/">
|
||||||
|
游客模式
|
||||||
|
</RouterLink>
|
||||||
|
|
||||||
<main class="auth-page__main">
|
<main class="auth-page__main">
|
||||||
<div class="auth-page__card">
|
<div class="auth-page__card">
|
||||||
<h1 class="auth-page__title">
|
<h2 class="auth-page__title">
|
||||||
找回密码
|
找回密码
|
||||||
</h1>
|
</h2>
|
||||||
|
<p class="auth-page__subtitle">
|
||||||
|
使用手机号重新设置您的密码
|
||||||
|
</p>
|
||||||
|
|
||||||
<form class="auth-page__form" @submit.prevent="onReset">
|
<form class="reset__form" @submit.prevent="onReset">
|
||||||
|
<div class="reset__field">
|
||||||
|
<label class="reset__label" for="reset-phone">手机号</label>
|
||||||
<input
|
<input
|
||||||
|
id="reset-phone"
|
||||||
v-model="phone"
|
v-model="phone"
|
||||||
class="auth-page__input"
|
class="reset__input"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="请输入手机号"
|
placeholder="请输入手机号"
|
||||||
maxlength="11"
|
maxlength="11"
|
||||||
autocomplete="tel"
|
autocomplete="tel"
|
||||||
>
|
>
|
||||||
<SmsCodeInput v-model:code="code" @get-code="onGetCode" />
|
</div>
|
||||||
|
|
||||||
|
<div class="reset__field">
|
||||||
|
<label class="reset__label" for="reset-code">验证码</label>
|
||||||
|
<SmsCodeInput id="reset-code" v-model:code="code" @get-code="onGetCode" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="reset__field">
|
||||||
|
<label class="reset__label" for="reset-password">设置密码</label>
|
||||||
<input
|
<input
|
||||||
|
id="reset-password"
|
||||||
v-model="password"
|
v-model="password"
|
||||||
class="auth-page__input"
|
class="reset__input"
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="新密码(6-20位)"
|
placeholder="请设置6-20位密码"
|
||||||
autocomplete="new-password"
|
autocomplete="new-password"
|
||||||
>
|
>
|
||||||
|
<!-- 设计稿:密码框下 15 高密码强度条 + 15 高提示行 -->
|
||||||
|
<PasswordStrength :password="password" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="reset__field">
|
||||||
|
<label class="reset__label" for="reset-confirm">确认密码</label>
|
||||||
<input
|
<input
|
||||||
|
id="reset-confirm"
|
||||||
v-model="confirmPassword"
|
v-model="confirmPassword"
|
||||||
class="auth-page__input"
|
class="reset__input"
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="确认新密码"
|
placeholder="请设置6-20位密码"
|
||||||
autocomplete="new-password"
|
autocomplete="new-password"
|
||||||
>
|
>
|
||||||
<label class="auth-page__agree">
|
</div>
|
||||||
|
|
||||||
|
<label class="reset__agree">
|
||||||
<input v-model="agree" type="checkbox">
|
<input v-model="agree" type="checkbox">
|
||||||
<span>已阅读并同意<em>《用户协议》</em>和<em>《隐私政策》</em></span>
|
<span>已阅读并同意<em>《用户协议》</em>和<em>《隐私政策》</em></span>
|
||||||
</label>
|
</label>
|
||||||
<button class="auth-page__submit" type="submit">
|
|
||||||
|
<button class="reset__submit" type="submit">
|
||||||
确认修改
|
确认修改
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<p class="auth-page__switch">
|
<p class="reset__switch">
|
||||||
想起密码了?<RouterLink class="auth-page__link" to="/login">返回登录</RouterLink>
|
已有账号?<RouterLink class="auth-page__link" to="/login">立即登录</RouterLink>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
@@ -134,105 +161,45 @@ onBeforeUnmount(() => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.auth-page {
|
// 设计稿(找回密码):表单列起点 x1040(--auth-form-x 1023 + 17)、垂直顶 261
|
||||||
position: relative;
|
.reset {
|
||||||
display: flex;
|
--auth-form-x: 1023px;
|
||||||
min-height: 100vh;
|
--auth-form-y: 261px;
|
||||||
// 设计稿:右侧表单区为纯白底,无卡片描边/阴影
|
|
||||||
background: @white;
|
|
||||||
|
|
||||||
&__brand {
|
|
||||||
// 设计稿:左侧品牌渐变面板约占 786/1920 ≈ 41%
|
|
||||||
flex: 0 0 41%;
|
|
||||||
.flex(row, flex-start, stretch);
|
|
||||||
background: linear-gradient(135deg, @auth-gradient-from 0%, @auth-gradient-to 100%);
|
|
||||||
color: @white;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__brand-inner {
|
|
||||||
.flex(column, space-between, flex-start);
|
|
||||||
width: 100%;
|
|
||||||
// 设计稿:文字列起点 x=257 ≈ 面板宽 32.7%;主标距顶 ≈ 28%、标语底边距底 ≈ 30%
|
|
||||||
padding: 26vh 48px 30vh 32%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__logo {
|
|
||||||
font-size: 31px;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: 2px;
|
|
||||||
line-height: 1.45;
|
|
||||||
// 设计稿文本框 432 设计px:定宽让 11 字在「虫子智造研发 / 一体化平台」处折行
|
|
||||||
max-width: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__slogan {
|
|
||||||
font-size: 17px;
|
|
||||||
font-weight: 600;
|
|
||||||
letter-spacing: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设计稿:「游客模式」为右上角白底上的深色纯文本,无边框
|
|
||||||
&__guest {
|
|
||||||
position: absolute;
|
|
||||||
top: 18px;
|
|
||||||
right: 16px;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
border: none;
|
|
||||||
border-radius: 0;
|
|
||||||
color: @heading-color;
|
|
||||||
font-size: 11px;
|
|
||||||
text-decoration: none;
|
|
||||||
transition: color 0.2s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: transparent;
|
|
||||||
color: @primary-strong;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__main {
|
|
||||||
flex: 1;
|
|
||||||
.flex(row, center, center);
|
|
||||||
padding: @spacing-xl;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__card {
|
|
||||||
.flex(column, flex-start);
|
|
||||||
// 设计稿:表单列宽 688 设计px ≈ 530px,标题/输入/按钮纵向节奏 ≈ 41px
|
|
||||||
gap: 41px;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 530px;
|
|
||||||
padding: 0;
|
|
||||||
border-radius: 0;
|
|
||||||
background: transparent;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设计稿:标题(34 设计px ≈ 26px)到首个输入框 ≈ 62px
|
|
||||||
&__title {
|
|
||||||
margin-bottom: 21px;
|
|
||||||
font-size: 26px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: @heading-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__form {
|
&__form {
|
||||||
.flex(column, flex-start);
|
.flex(column, flex-start, stretch);
|
||||||
gap: @spacing-lg;
|
// 副标题底 335 → 首个标签 361 = 26(副标题行盒 17 + 外边距 12 → 实际需补 38)
|
||||||
width: 100%;
|
margin: 38px 0 0 17px;
|
||||||
|
width: 680px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__field {
|
||||||
|
.flex(column, flex-start, stretch);
|
||||||
|
gap: 8px;
|
||||||
|
width: 680px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设计稿:手机号框底 425 → 验证码标签 443 = 18
|
||||||
|
&__field + &__field {
|
||||||
|
margin-top: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
font-size: 13px; // 设计稿 13/500 #1F2937
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 17px;
|
||||||
|
color: #1f2937;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计稿:输入框 40 设计px ≈ 32px 高、13 设计px 字号(可读下限 11px)、白底浅描边
|
|
||||||
&__input {
|
&__input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 32px;
|
height: 39px;
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
border: 1px solid @border-color;
|
border: 1px solid #dde1e7;
|
||||||
border-radius: 6px;
|
border-radius: @radius-sm;
|
||||||
background: @white;
|
background: @white;
|
||||||
font-size: 11px;
|
font-size: 13px;
|
||||||
color: @heading-color;
|
color: #1f2937;
|
||||||
outline: none;
|
outline: none;
|
||||||
transition: border-color 0.2s;
|
transition: border-color 0.2s;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@@ -242,40 +209,46 @@ onBeforeUnmount(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
color: @heading-color;
|
color: #1f2937;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设计稿:协议行 @(1040,741)
|
||||||
&__agree {
|
&__agree {
|
||||||
.flex(row, flex-start);
|
.flex(row, flex-start, center);
|
||||||
gap: @spacing-xs;
|
gap: 8px;
|
||||||
font-size: 11px;
|
margin-top: 22px;
|
||||||
color: @text-color-secondary;
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 16px;
|
||||||
|
color: #6b7280;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
input {
|
input {
|
||||||
margin-top: 1px;
|
flex-shrink: 0;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
margin: 0;
|
||||||
accent-color: @primary-strong;
|
accent-color: @primary-strong;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计稿:《用户协议》《隐私政策》为强调蓝,其余为次级灰
|
|
||||||
em {
|
em {
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
color: @primary-strong;
|
color: @primary-strong;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计稿:确认修改按钮 44 设计px ≈ 34px 高、#024FFF 实底
|
// 设计稿:确认修改按钮 680×46 @y775、#024FFF 实底、文字 15/500
|
||||||
&__submit {
|
&__submit {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 34px;
|
height: 46px;
|
||||||
|
margin-top: 18px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 6px;
|
border-radius: @radius-sm;
|
||||||
background: @primary-strong;
|
background: @primary-strong;
|
||||||
color: @white;
|
color: @white;
|
||||||
font-size: 12px;
|
font-size: 15px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
letter-spacing: 2px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.2s;
|
transition: background 0.2s;
|
||||||
|
|
||||||
@@ -284,20 +257,22 @@ onBeforeUnmount(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计稿:按钮 → 底部切换链接仅约 13px
|
// 设计稿:按钮底 821 → 切换行 838 = 17
|
||||||
&__switch {
|
&__switch {
|
||||||
margin-top: -26px;
|
align-self: center;
|
||||||
text-align: center;
|
margin: 17px 0 0;
|
||||||
font-size: 11px;
|
font-size: 12px;
|
||||||
color: @text-color-secondary;
|
font-weight: 400;
|
||||||
|
line-height: 16px;
|
||||||
|
color: #6b7280;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__link {
|
&__subtitle {
|
||||||
color: @primary-strong;
|
margin: 12px 0 0;
|
||||||
|
font-size: 13px; // 设计稿 13/400 #6B7280
|
||||||
&:hover {
|
font-weight: 400;
|
||||||
color: @primary-hover;
|
line-height: 17px;
|
||||||
}
|
color: #6b7280;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,69 +1,55 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
interface MenuItem {
|
interface MenuItem {
|
||||||
to: string
|
to: string
|
||||||
icon: string
|
|
||||||
label: string
|
label: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设计稿(个人中心)菜单项 y268/304/340/376/412/448/484/520,行距 36,文字 x179(卡内缩进 24)
|
||||||
const menus: MenuItem[] = [
|
const menus: MenuItem[] = [
|
||||||
{ to: '/profile/security', icon: '🔒', label: '账号安全' },
|
{ to: '/profile/security', label: '账号安全' },
|
||||||
{ to: '/profile/address', icon: '📍', label: '收货地址' },
|
{ to: '/profile/address', label: '地址管理' },
|
||||||
{ to: '/profile/favorites', icon: '⭐', label: '我的收藏' },
|
{ to: '/profile/favorites', label: '收藏列表' },
|
||||||
{ to: '/profile/qualifications', icon: '📜', label: '资质管理' },
|
{ to: '/profile/qualifications', label: '资质列表' },
|
||||||
{ to: '/profile/feedback', icon: '💡', label: '意见反馈' },
|
{ to: '/profile/feedback', label: '意见反馈' },
|
||||||
{ to: '/profile/help', icon: '❓', label: '帮助中心' },
|
{ to: '/profile/help', label: '帮助中心' },
|
||||||
{ to: '/profile/about', icon: 'ℹ️', label: '关于我们' },
|
{ to: '/profile/about', label: '关于我们' },
|
||||||
{ to: '/profile/hotline', icon: '📞', label: '客服热线' },
|
{ to: '/profile/hotline', label: '专属客服热线' },
|
||||||
]
|
]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<nav class="profile-sider" aria-label="个人中心导航">
|
<nav class="profile-sider" aria-label="个人中心导航">
|
||||||
<ul class="profile-sider__menu">
|
|
||||||
<li v-for="item in menus" :key="item.to">
|
|
||||||
<RouterLink :to="item.to" class="profile-sider__item">
|
|
||||||
<span class="profile-sider__label">{{ item.label }}</span>
|
|
||||||
</RouterLink>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="profile-sider__footer">
|
|
||||||
<RouterLink
|
<RouterLink
|
||||||
to="/profile/hotline"
|
v-for="item in menus"
|
||||||
class="profile-sider__hotline"
|
:key="item.to"
|
||||||
title="专属客服热线"
|
:to="item.to"
|
||||||
|
class="profile-sider__item"
|
||||||
>
|
>
|
||||||
📞 专属客服热线
|
{{ item.label }}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
|
||||||
</nav>
|
</nav>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
// 设计稿:菜单为独立白卡(245 宽),选中项浅蓝底 @profile-nav-active-bg 圆角 6
|
||||||
.profile-sider {
|
.profile-sider {
|
||||||
.flex(column, space-between);
|
|
||||||
min-height: 480px;
|
|
||||||
padding: @spacing-sm 0;
|
|
||||||
|
|
||||||
&__menu {
|
|
||||||
.flex(column, flex-start);
|
.flex(column, flex-start);
|
||||||
gap: 0;
|
gap: 0;
|
||||||
list-style: none;
|
padding: 4px 0;
|
||||||
}
|
border-radius: @radius-lg;
|
||||||
|
background: @white;
|
||||||
|
|
||||||
// 设计 14/28:纯文字菜单,选中项浅蓝底 + 蓝色加粗,行距 36 × 0.77 ≈ 28
|
|
||||||
&__item {
|
&__item {
|
||||||
.flex(row, flex-start, center);
|
.flex(row, flex-start, center);
|
||||||
height: 28px;
|
height: 36px; // 设计行距 36
|
||||||
padding: 0 @spacing-sm;
|
padding: 0 24px; // 卡 x155 → 文字 x179
|
||||||
border-radius: @profile-radius;
|
border-radius: @profile-radius;
|
||||||
color: @text-color;
|
color: @text-color;
|
||||||
font-size: 11px;
|
font-size: @font-size-base; // 设计 12/400
|
||||||
line-height: 1;
|
font-weight: 400;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: @primary-light;
|
|
||||||
color: @primary-strong;
|
color: @primary-strong;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,25 +59,5 @@ const menus: MenuItem[] = [
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__label {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__footer {
|
|
||||||
padding: @spacing-sm @spacing-sm 0;
|
|
||||||
border-top: 1px solid @project-divider;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__hotline {
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 11px;
|
|
||||||
color: @text-color-secondary;
|
|
||||||
transition: color 0.2s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: @primary-strong;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ function save(): void {
|
|||||||
<div class="profile-user-card__avatar" aria-hidden="true">
|
<div class="profile-user-card__avatar" aria-hidden="true">
|
||||||
王
|
王
|
||||||
</div>
|
</div>
|
||||||
<div class="profile-user-card__info">
|
|
||||||
<input
|
<input
|
||||||
v-model="draft"
|
v-model="draft"
|
||||||
class="profile-user-card__name"
|
class="profile-user-card__name"
|
||||||
@@ -42,51 +41,43 @@ function save(): void {
|
|||||||
@keyup.enter="save"
|
@keyup.enter="save"
|
||||||
@blur="save"
|
@blur="save"
|
||||||
>
|
>
|
||||||
<p class="profile-user-card__phone text-secondary">
|
<p class="profile-user-card__phone">
|
||||||
{{ profile?.phone ?? '' }}
|
{{ profile?.phone ?? '' }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
// 设计 14:左列紫渐变用户卡(渐变实取 #5459E8 → #D2BDF3),内容居中
|
// 设计稿(个人中心):头像 46 圆形居中,名称 18/600,手机 14/400;
|
||||||
|
// 整块 245×110 紫渐变卡,内容垂直居中
|
||||||
.profile-user-card {
|
.profile-user-card {
|
||||||
.flex(column, center);
|
.flex(column, center, center);
|
||||||
gap: @spacing-xs; // 设计 14:头像与文字块间距约 4px
|
height: 110px;
|
||||||
padding: @spacing-base @spacing-sm; // 设计:上 20 / 下 25 设计px ≈ 16
|
|
||||||
border-radius: @radius-lg;
|
border-radius: @radius-lg;
|
||||||
background: linear-gradient(170deg, @profile-user-from 0%, @profile-user-to 100%);
|
background: linear-gradient(180deg, @profile-user-from 0%, @profile-user-to 100%);
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
&__avatar {
|
&__avatar {
|
||||||
.flex(row, center);
|
.flex(row, center);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 42px; // 设计 54 × 0.77
|
width: 42px;
|
||||||
height: 42px;
|
height: 42px;
|
||||||
border-radius: @radius-round;
|
border-radius: @radius-round;
|
||||||
background: @profile-user-avatar;
|
background: fade(@white, 22%);
|
||||||
color: @white;
|
color: @white;
|
||||||
font-size: @font-size-lg;
|
font-size: @font-size-lg;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__info {
|
|
||||||
.flex(column, center);
|
|
||||||
gap: @spacing-xs;
|
|
||||||
min-width: 0;
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 卡内为深色底:名称/号码一律白字(设计 14 实取 #FFFFFF)
|
|
||||||
&__name {
|
&__name {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 16px; // 设计 19 × 0.77
|
height: 26px;
|
||||||
|
margin-top: 6px;
|
||||||
padding: 0 @spacing-xs;
|
padding: 0 @spacing-xs;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
border-radius: @profile-radius;
|
border-radius: @profile-radius;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
font-size: 11px;
|
font-size: @font-size-lg; // 设计 18/600
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: @white;
|
color: @white;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -103,15 +94,17 @@ function save(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
font-size: 11px;
|
font-size: @font-size-lg;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
color: fade(@white, 75%);
|
color: fade(@white, 75%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__phone {
|
&__phone {
|
||||||
font-size: 11px;
|
margin-top: 6px;
|
||||||
|
font-size: @font-size-sm; // 设计 14/400
|
||||||
color: @white;
|
color: @white;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { AppButton } from '@/components'
|
|
||||||
import type { Profile } from '@/types/business/profile'
|
import type { Profile } from '@/types/business/profile'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -17,354 +16,148 @@ const emit = defineEmits<{
|
|||||||
deleteAccount: []
|
deleteAccount: []
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const agree = ref(false)
|
|
||||||
|
|
||||||
const hasEmail = computed(() => Boolean(props.profile?.email))
|
const hasEmail = computed(() => Boolean(props.profile?.email))
|
||||||
|
|
||||||
/** 注销条件(mock 全部满足) */
|
|
||||||
const conditions = [
|
|
||||||
'账号无未完成的订单',
|
|
||||||
'无未结清的款项',
|
|
||||||
'无进行中的合同',
|
|
||||||
'无未处理的工单',
|
|
||||||
]
|
|
||||||
|
|
||||||
/** 注销风险提示 */
|
|
||||||
const risks = [
|
|
||||||
'账号注销后将无法登录本应用,所有个人信息将被永久删除',
|
|
||||||
'您名下的订单、合同、工单等业务数据将一并清除,无法找回',
|
|
||||||
'注销后同一手机号/邮箱将无法再次注册本应用',
|
|
||||||
'如您有未完成的订单或服务请先处理完毕后再进行注销',
|
|
||||||
]
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="security-panel">
|
<div class="security-panel">
|
||||||
<!-- 修改密码 -->
|
<!-- 设计稿:三行白卡 1672×86,行距 16,左缩进 24 -->
|
||||||
<section class="security-panel__row">
|
<section
|
||||||
<span class="security-panel__icon security-panel__icon--pwd" aria-hidden="true" />
|
class="security-panel__row"
|
||||||
<div class="security-panel__main">
|
@click="emit('changePassword')"
|
||||||
<h3 class="security-panel__title">
|
|
||||||
修改密码
|
|
||||||
</h3>
|
|
||||||
<p class="security-panel__status">
|
|
||||||
<span class="security-panel__badge security-panel__badge--ok">已设置</span>
|
|
||||||
<span class="security-panel__note">上次修改:{{ profile?.passwordModifiedAt }}</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<AppButton type="default" @click="emit('changePassword')">
|
|
||||||
修改密码
|
|
||||||
</AppButton>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- 绑定手机 -->
|
|
||||||
<section class="security-panel__row">
|
|
||||||
<span class="security-panel__icon security-panel__icon--phone" aria-hidden="true" />
|
|
||||||
<div class="security-panel__main">
|
|
||||||
<h3 class="security-panel__title">
|
|
||||||
绑定手机
|
|
||||||
</h3>
|
|
||||||
<p class="security-panel__status">
|
|
||||||
<span class="security-panel__badge security-panel__badge--ok">已绑定 {{ profile?.phone }}</span>
|
|
||||||
</p>
|
|
||||||
<p class="security-panel__note">
|
|
||||||
注:换绑新手机号之后,可以用新的手机号及当前密码登录
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<AppButton type="default" @click="emit('bindPhone')">
|
|
||||||
绑定手机
|
|
||||||
</AppButton>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- 绑定邮箱 -->
|
|
||||||
<section class="security-panel__row">
|
|
||||||
<span class="security-panel__icon security-panel__icon--email" aria-hidden="true" />
|
|
||||||
<div class="security-panel__main">
|
|
||||||
<h3 class="security-panel__title">
|
|
||||||
绑定邮箱
|
|
||||||
</h3>
|
|
||||||
<p class="security-panel__status">
|
|
||||||
<template v-if="hasEmail">
|
|
||||||
<span class="security-panel__badge security-panel__badge--ok">已绑定 {{ profile?.email }}</span>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<span class="security-panel__badge">未绑定</span>
|
|
||||||
</template>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="security-panel__actions">
|
|
||||||
<AppButton
|
|
||||||
v-if="!hasEmail"
|
|
||||||
type="default"
|
|
||||||
@click="emit('bindEmail', 'email-bind')"
|
|
||||||
>
|
>
|
||||||
绑定邮箱
|
<div class="security-panel__main">
|
||||||
</AppButton>
|
<h3 class="security-panel__title">
|
||||||
<template v-else>
|
修改密码
|
||||||
<AppButton type="default" @click="emit('bindEmail', 'email-rebind')">
|
</h3>
|
||||||
更换绑定邮箱
|
<p class="security-panel__sub">
|
||||||
</AppButton>
|
上次修改:{{ profile?.passwordModifiedAt }}
|
||||||
<AppButton type="text" @click="emit('unbindEmail')">
|
</p>
|
||||||
取消绑定
|
|
||||||
</AppButton>
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
|
<span class="security-panel__state security-panel__state--ok">已设置</span>
|
||||||
|
<span class="security-panel__arrow" aria-hidden="true">›</span>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- 注销账号(danger 区) -->
|
<section
|
||||||
<section class="security-panel__danger">
|
class="security-panel__row"
|
||||||
<h3 class="security-panel__danger-title">
|
@click="emit('bindPhone')"
|
||||||
注销账号
|
>
|
||||||
|
<div class="security-panel__main">
|
||||||
|
<h3 class="security-panel__title">
|
||||||
|
绑定手机
|
||||||
</h3>
|
</h3>
|
||||||
|
<p class="security-panel__sub">
|
||||||
<div class="security-panel__block">
|
{{ profile?.phone ?? '' }}
|
||||||
<h4 class="security-panel__block-title security-panel__block-title--ok">
|
</p>
|
||||||
注销条件
|
|
||||||
</h4>
|
|
||||||
<ul class="security-panel__checks">
|
|
||||||
<li v-for="item in conditions" :key="item" class="security-panel__check">
|
|
||||||
<span class="security-panel__check-ok">✓</span>{{ item }}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
|
<span class="security-panel__state security-panel__state--ok">已绑定</span>
|
||||||
|
<span class="security-panel__arrow" aria-hidden="true">›</span>
|
||||||
|
</section>
|
||||||
|
|
||||||
<div class="security-panel__block">
|
<section
|
||||||
<h4 class="security-panel__block-title security-panel__block-title--risk">
|
class="security-panel__row"
|
||||||
注销风险提示
|
@click="emit('bindEmail', hasEmail ? 'email-rebind' : 'email-bind')"
|
||||||
</h4>
|
>
|
||||||
<ol class="security-panel__risks">
|
<div class="security-panel__main">
|
||||||
<li v-for="(item, index) in risks" :key="item">
|
<h3 class="security-panel__title">
|
||||||
{{ index + 1 }}. {{ item }}
|
绑定邮箱
|
||||||
</li>
|
</h3>
|
||||||
</ol>
|
<p class="security-panel__sub">
|
||||||
|
{{ hasEmail ? profile?.email : '未绑定邮箱' }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<span
|
||||||
|
class="security-panel__state"
|
||||||
|
:class="{ 'security-panel__state--ok': hasEmail }"
|
||||||
|
>{{ hasEmail ? '已绑定' : '未绑定' }}</span>
|
||||||
|
<span class="security-panel__arrow" aria-hidden="true">›</span>
|
||||||
|
</section>
|
||||||
|
|
||||||
<label class="security-panel__agree">
|
<!-- 设计稿:注销账号 1672×48,灰字,浅灰描边 -->
|
||||||
<input v-model="agree" type="checkbox">
|
<button
|
||||||
<span>已阅读并同意<em>《账号注销协议》</em></span>
|
type="button"
|
||||||
</label>
|
class="security-panel__delete"
|
||||||
|
|
||||||
<AppButton
|
|
||||||
type="default"
|
|
||||||
class="security-panel__delete-btn"
|
|
||||||
:disabled="!agree"
|
|
||||||
@click="emit('deleteAccount')"
|
@click="emit('deleteAccount')"
|
||||||
>
|
>
|
||||||
注销账号
|
注销账号
|
||||||
</AppButton>
|
</button>
|
||||||
</section>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
// 设计 28:白卡行 + 浅灰描边、图标块 44×44 设计px、标题 14 设计px w600、副文 11 设计px
|
// 设计稿(个人中心):
|
||||||
|
// 行卡 1672×86 bg #FFFFFF bd #F0F1F3/1,行距 16;标题 14/600 #1F2937 @x428
|
||||||
|
// 副文 11/400 #9CA3AF;右端状态 11/500(已设置 #16A34A / 未绑定 #6B7280)+ 箭头 ›
|
||||||
|
// 注销按钮 1672×48 bd #E5E7EB/1,文字 15/500 #9CA3AF
|
||||||
.security-panel {
|
.security-panel {
|
||||||
.flex(column, flex-start);
|
.flex(column, flex-start, stretch);
|
||||||
gap: @spacing-base;
|
gap: @spacing-base;
|
||||||
|
|
||||||
&__row {
|
&__row {
|
||||||
.flex(row, flex-start, center);
|
.flex(row, space-between, center);
|
||||||
gap: @spacing-base;
|
height: 86px;
|
||||||
padding: @spacing-base;
|
padding: 0 24px;
|
||||||
border: 1px solid @profile-card-border;
|
border: 1px solid @profile-card-border;
|
||||||
border-radius: @radius-base;
|
border-radius: @radius-base;
|
||||||
background: @white;
|
background: @white;
|
||||||
}
|
box-sizing: border-box;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
|
||||||
// 设计 28 实取图标块底色:#C7D2FE / #FDEBEB / #FEE2E2
|
&:hover {
|
||||||
&__icon {
|
border-color: @profile-line;
|
||||||
flex-shrink: 0;
|
|
||||||
width: 34px; // 设计 44 × 0.77
|
|
||||||
height: 34px;
|
|
||||||
border-radius: @radius-base;
|
|
||||||
|
|
||||||
&--pwd {
|
|
||||||
background: @profile-icon-pwd;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--phone {
|
|
||||||
background: @profile-icon-phone;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--email {
|
|
||||||
background: @profile-icon-email;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__main {
|
&__main {
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
font-size: 11px; // 设计 14 → 10.8,取可读下限 11
|
font-size: @font-size-base; // 设计 14/600
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: @heading-color;
|
color: @profile-ink;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__status {
|
&__sub {
|
||||||
.flex(row, flex-start, center);
|
margin-top: 6px;
|
||||||
flex-wrap: wrap;
|
font-size: @font-size-sm; // 设计 11/400
|
||||||
gap: @spacing-sm;
|
color: @finance-axis-text; // #9CA3AF
|
||||||
margin-top: @spacing-xs;
|
|
||||||
font-size: 11px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 28:已设置/已绑定 = 浅绿底绿字胶囊,未绑定 = 纯灰字
|
&__state {
|
||||||
&__badge {
|
margin-left: auto;
|
||||||
padding: 2px 8px;
|
font-size: @font-size-sm;
|
||||||
border-radius: @radius-sm;
|
|
||||||
background: transparent;
|
|
||||||
color: @text-color-secondary;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
color: @finance-muted-text; // 未绑定 #6B7280
|
||||||
|
|
||||||
&--ok {
|
&--ok {
|
||||||
background: @tag-success-bg;
|
color: @success-color; // 已设置 / 已绑定 #16A34A
|
||||||
color: @success-color;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__note {
|
&__arrow {
|
||||||
margin-top: @spacing-xs;
|
margin-left: 20px;
|
||||||
color: @text-color-disabled;
|
font-size: 21px;
|
||||||
font-size: 11px;
|
line-height: 1;
|
||||||
line-height: 1.6;
|
color: @finance-axis-text;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__actions {
|
&__delete {
|
||||||
.flex(row, center);
|
width: 100%;
|
||||||
gap: @spacing-sm;
|
height: 48px;
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 行内操作按钮:白底灰描边小按钮(设计 20/17 操作按钮同规格)
|
|
||||||
.app-button {
|
|
||||||
height: 28px;
|
|
||||||
padding: 0 12px;
|
|
||||||
border: 1px solid @profile-line;
|
border: 1px solid @profile-line;
|
||||||
border-radius: @profile-radius;
|
|
||||||
background: @white;
|
|
||||||
color: @text-color;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 500;
|
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
|
||||||
border-color: @primary-strong;
|
|
||||||
color: @primary-strong;
|
|
||||||
background: @white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-button--text {
|
|
||||||
border-color: transparent;
|
|
||||||
background: transparent;
|
|
||||||
color: @text-color;
|
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
|
||||||
border-color: transparent;
|
|
||||||
background: transparent;
|
|
||||||
color: @primary-strong;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__danger {
|
|
||||||
.flex(column, flex-start);
|
|
||||||
gap: @spacing-base;
|
|
||||||
padding: @spacing-lg;
|
|
||||||
border: 1px solid @profile-card-border;
|
|
||||||
border-radius: @radius-base;
|
border-radius: @radius-base;
|
||||||
background: @white;
|
background: @white;
|
||||||
}
|
color: @finance-axis-text;
|
||||||
|
font-size: @font-size-base; // 设计 15/500
|
||||||
&__danger-title {
|
|
||||||
font-size: 12px; // 设计 15 → 12
|
|
||||||
font-weight: 700;
|
|
||||||
color: @danger-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__block {
|
|
||||||
.flex(column, flex-start);
|
|
||||||
gap: @spacing-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设计 29:注销条件标题绿、注销风险提示标题红
|
|
||||||
&__block-title {
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 600;
|
|
||||||
|
|
||||||
&--ok {
|
|
||||||
color: @success-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--risk {
|
|
||||||
color: @danger-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__checks {
|
|
||||||
.flex(column, flex-start);
|
|
||||||
gap: @spacing-xs;
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__check {
|
|
||||||
font-size: 11px;
|
|
||||||
color: @text-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__check-ok {
|
|
||||||
margin-right: @spacing-xs;
|
|
||||||
font-weight: 600;
|
|
||||||
color: @success-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__risks {
|
|
||||||
margin: 0;
|
|
||||||
padding-left: @spacing-base;
|
|
||||||
.flex(column, flex-start);
|
|
||||||
gap: @spacing-xs;
|
|
||||||
font-size: 11px;
|
|
||||||
color: @text-color;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__agree {
|
|
||||||
.flex(row, flex-start, center);
|
|
||||||
gap: @spacing-sm;
|
|
||||||
font-size: 11px;
|
|
||||||
color: @text-color-secondary;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
input {
|
|
||||||
accent-color: @primary-strong;
|
|
||||||
}
|
|
||||||
|
|
||||||
em {
|
|
||||||
font-style: normal;
|
|
||||||
color: @primary-strong;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注销账号:白底灰描边整行按钮(设计 28 实取文字 #9CA3AF、15 设计px)
|
|
||||||
&__delete-btn {
|
|
||||||
width: 100%;
|
|
||||||
height: 36px;
|
|
||||||
border: 1px solid @profile-line !important;
|
|
||||||
background: @white !important;
|
|
||||||
color: @text-color-disabled !important;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
&:hover {
|
||||||
border-color: @danger-color !important;
|
border-color: @danger-color;
|
||||||
background: @white !important;
|
color: @danger-color;
|
||||||
color: @danger-color !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
opacity: 1;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import { useProfileStore } from '@/stores/modules/profile'
|
|||||||
import ProfileSider from './cpns/ProfileSider.vue'
|
import ProfileSider from './cpns/ProfileSider.vue'
|
||||||
import ProfileUserCard from './cpns/ProfileUserCard.vue'
|
import ProfileUserCard from './cpns/ProfileUserCard.vue'
|
||||||
|
|
||||||
// 布局容器:顶部用户卡 + 左侧菜单 / 右侧子路由出口,数据只经 store
|
// 布局容器:左侧用户卡 + 菜单(x155 起 245 宽),右侧内容卡(x400 起 1400 宽)
|
||||||
|
// 数据只经 store
|
||||||
const profileStore = useProfileStore()
|
const profileStore = useProfileStore()
|
||||||
const { profile } = storeToRefs(profileStore)
|
const { profile } = storeToRefs(profileStore)
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="profile">
|
<div class="profile">
|
||||||
<div class="profile__container">
|
<div class="profile__container container">
|
||||||
<div class="profile__body">
|
<div class="profile__body">
|
||||||
<aside class="profile__aside">
|
<aside class="profile__aside">
|
||||||
<ProfileUserCard :profile="profile" @save-nickname="onSaveNickname" />
|
<ProfileUserCard :profile="profile" @save-nickname="onSaveNickname" />
|
||||||
@@ -35,40 +36,46 @@ onMounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
// 设计稿(个人中心 1920×1568)锚点:
|
||||||
|
// 面包屑 y23 h26 · 左列 x155 宽 245 · 右卡 x400 y98 1400×532 · 页脚 y1087
|
||||||
|
// 内容区顶 98 = 顶栏 72 + 上留白 26
|
||||||
.profile {
|
.profile {
|
||||||
padding-bottom: @spacing-xxl;
|
min-height: 737px; // 内容区 y98→835
|
||||||
background: @bg-color;
|
background: @profile-page-bg;
|
||||||
|
|
||||||
&__container {
|
&__container {
|
||||||
width: @container-width;
|
padding-top: 26px; // 顶栏 72 → 内容区 98
|
||||||
max-width: calc(100% - @spacing-xl);
|
// 个人中心系独立基准(视觉核对定版 x155/1600),不随全站 70/1780 联动
|
||||||
margin: 0 auto;
|
padding-left: clamp(@spacing-base, calc((100% - @profile-content) / 2), @profile-gutter);
|
||||||
padding-top: @spacing-lg;
|
padding-right: clamp(@spacing-base, calc((100% - @profile-content) / 2), @profile-gutter);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__body {
|
&__body {
|
||||||
.flex(row, flex-start);
|
.flex(row, flex-start, stretch);
|
||||||
gap: @spacing-base;
|
width: 100%;
|
||||||
align-items: flex-start;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 左列(设计14):紫渐变用户卡 + 菜单,直接坐在页面底色上,无外层白卡
|
// 左列 200:用户卡 + 菜单白卡,两卡间距 6
|
||||||
|
// (设计稿左列 245、右卡 1400,合计 1645 超出内容宽 1595,按比例收左列到 200 保右卡 1400)
|
||||||
&__aside {
|
&__aside {
|
||||||
width: 114px; // 设计 148 × 0.77
|
.flex(column, flex-start, stretch);
|
||||||
flex-shrink: 0;
|
gap: 6px;
|
||||||
.flex(column, flex-start);
|
flex: 0 0 200px;
|
||||||
gap: @spacing-base;
|
width: 200px;
|
||||||
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 右侧内容面板(设计14/28):整块白卡,内缩 16px 承载各页面
|
// 右内容卡 1400:白底 + 浅灰描边
|
||||||
&__main {
|
&__main {
|
||||||
flex: 1;
|
flex: 1 1 auto;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
min-height: 400px;
|
min-height: 425px;
|
||||||
padding: @spacing-base;
|
margin-left: 40px; // 左列 200 → 右卡起点 240(设计稿比例:160/245 ≈ 0.65)
|
||||||
border-radius: @radius-base;
|
padding: 24px 24px 0; // 卡内左缩进:卡 → 标题 +28
|
||||||
|
border: 1px solid @profile-card-border;
|
||||||
|
border-radius: @radius-lg;
|
||||||
background: @white;
|
background: @white;
|
||||||
box-shadow: @shadow-base;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -15,6 +15,36 @@ const router = useRouter()
|
|||||||
const profileStore = useProfileStore()
|
const profileStore = useProfileStore()
|
||||||
const { profile } = storeToRefs(profileStore)
|
const { profile } = storeToRefs(profileStore)
|
||||||
|
|
||||||
|
// ── 注销账号二次确认(设计稿 __4d03fd89:风险提示 + 注销条件 + 双按钮) ──
|
||||||
|
const deleteVisible = ref(false)
|
||||||
|
const agree = ref(false)
|
||||||
|
const identityVisible = ref(false)
|
||||||
|
|
||||||
|
/** 注销条件(mock 全部满足) */
|
||||||
|
const conditions = [
|
||||||
|
{ label: '账号无未完成的订单', ok: true },
|
||||||
|
{ label: '账号无未结清的款项', ok: false },
|
||||||
|
{ label: '账号无进行中的合同', ok: true },
|
||||||
|
{ label: '账号无处理中的工单', ok: true },
|
||||||
|
]
|
||||||
|
|
||||||
|
/** 注销风险提示(设计稿 __4d03fd89 原文四条) */
|
||||||
|
const risks = [
|
||||||
|
'账号注销后,您将无法登录本应用,所有个人信息将被永久删除。',
|
||||||
|
'您名下的订单、合同、工单等业务数据将一并清除,无法找回。',
|
||||||
|
'注销后,同一手机号/邮箱将无法再次注册本应用。',
|
||||||
|
'如有未完成的订单或服务,请先处理完毕后再进行注销。',
|
||||||
|
]
|
||||||
|
|
||||||
|
function onDeleteConfirm(): void {
|
||||||
|
if (!agree.value) {
|
||||||
|
ElMessage.warning('请先阅读并同意《账号注销协议》')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 安全身份验证 → 验证通过后注销
|
||||||
|
identityVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
// ── 修改密码 ──
|
// ── 修改密码 ──
|
||||||
const pwdVisible = ref(false)
|
const pwdVisible = ref(false)
|
||||||
|
|
||||||
@@ -66,14 +96,6 @@ function onUnbindConfirm(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── 注销账号 ──
|
// ── 注销账号 ──
|
||||||
const deleteVisible = ref(false)
|
|
||||||
const identityVisible = ref(false)
|
|
||||||
|
|
||||||
function onDeleteConfirm(): void {
|
|
||||||
// 确认注销 → 进入安全身份验证
|
|
||||||
identityVisible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function onIdentitySubmit(payload: { contact: string; code: string }): void {
|
function onIdentitySubmit(payload: { contact: string; code: string }): void {
|
||||||
void (async () => {
|
void (async () => {
|
||||||
await profileStore.verifyIdentity(payload.contact, payload.code)
|
await profileStore.verifyIdentity(payload.contact, payload.code)
|
||||||
@@ -96,6 +118,59 @@ function onIdentitySubmit(payload: { contact: string; code: string }): void {
|
|||||||
@delete-account="deleteVisible = true"
|
@delete-account="deleteVisible = true"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- 注销账号 · 二次确认(设计稿 __4d03fd89) -->
|
||||||
|
<ConfirmDialog
|
||||||
|
v-model:visible="deleteVisible"
|
||||||
|
title="注销账号"
|
||||||
|
confirm-text="注销账号"
|
||||||
|
cancel-text="暂不注销"
|
||||||
|
danger
|
||||||
|
@cancel="agree = false"
|
||||||
|
@confirm="onDeleteConfirm"
|
||||||
|
>
|
||||||
|
<div class="delete-confirm">
|
||||||
|
<span class="delete-confirm__icon" aria-hidden="true">i</span>
|
||||||
|
<h3 class="delete-confirm__title">
|
||||||
|
确定要注销账号吗?
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="delete-confirm__block">
|
||||||
|
<h4 class="delete-confirm__block-title delete-confirm__block-title--risk">
|
||||||
|
⚠ 注销风险提示
|
||||||
|
</h4>
|
||||||
|
<ul class="delete-confirm__risks">
|
||||||
|
<li v-for="(item, index) in risks" :key="item">
|
||||||
|
{{ index + 1 }}、{{ item }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="delete-confirm__block">
|
||||||
|
<h4 class="delete-confirm__block-title delete-confirm__block-title--ok">
|
||||||
|
✓ 注销条件
|
||||||
|
</h4>
|
||||||
|
<ul class="delete-confirm__conditions">
|
||||||
|
<li
|
||||||
|
v-for="item in conditions"
|
||||||
|
:key="item.label"
|
||||||
|
class="delete-confirm__condition"
|
||||||
|
>
|
||||||
|
<span>{{ item.label }}</span>
|
||||||
|
<span
|
||||||
|
class="delete-confirm__mark"
|
||||||
|
:class="item.ok ? 'is-ok' : 'is-fail'"
|
||||||
|
>{{ item.ok ? '✓' : '✕' }}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label class="delete-confirm__agree">
|
||||||
|
<input v-model="agree" type="checkbox">
|
||||||
|
<span>已阅读并同意<em>《账号注销协议》</em></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</ConfirmDialog>
|
||||||
|
|
||||||
<ChangePasswordDialog
|
<ChangePasswordDialog
|
||||||
v-model:visible="pwdVisible"
|
v-model:visible="pwdVisible"
|
||||||
:phone="profile?.phone ?? ''"
|
:phone="profile?.phone ?? ''"
|
||||||
@@ -126,22 +201,128 @@ function onIdentitySubmit(payload: { contact: string; code: string }): void {
|
|||||||
danger
|
danger
|
||||||
@confirm="onUnbindConfirm"
|
@confirm="onUnbindConfirm"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ConfirmDialog
|
|
||||||
v-model:visible="deleteVisible"
|
|
||||||
title="注销账号"
|
|
||||||
message="确定要注销账号吗?"
|
|
||||||
confirm-text="注销账号"
|
|
||||||
cancel-text="暂不注销"
|
|
||||||
danger
|
|
||||||
@confirm="onDeleteConfirm"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.security-page {
|
.security-page {
|
||||||
.flex(column, flex-start);
|
.flex(column, flex-start, stretch);
|
||||||
gap: @spacing-base;
|
gap: @spacing-base;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设计稿(注销账号 __4d03fd89):
|
||||||
|
// 弹层内容列 x660→1420 宽 760;感叹号 30/700 #EF4444;主问 19/700 #1F2937
|
||||||
|
// 风险标题 13/600 #EF4444;正文 13/400 #4B5563 行距 26
|
||||||
|
// 条件行 760×32 底 #FFFFFF bd #DDE1E7;勾选标记 14 圆底
|
||||||
|
// 协议勾选框 16;取消按钮 760×46 底 #D8DBDF;注销按钮 760×46 底 #EF4444
|
||||||
|
.delete-confirm {
|
||||||
|
.flex(column, flex-start);
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 0;
|
||||||
|
|
||||||
|
&__icon {
|
||||||
|
.flex(row, center);
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
border-radius: @radius-round;
|
||||||
|
background: fade(@danger-color, 12%);
|
||||||
|
color: @danger-color;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: 19px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: @profile-ink;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__block {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__block-title {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
|
||||||
|
&--risk {
|
||||||
|
color: @danger-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--ok {
|
||||||
|
color: @success-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__risks {
|
||||||
|
margin-top: 12px;
|
||||||
|
.flex(column, flex-start);
|
||||||
|
gap: 9px;
|
||||||
|
list-style: none;
|
||||||
|
font-size: 13px;
|
||||||
|
color: @finance-entry-text;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__conditions {
|
||||||
|
margin-top: 12px;
|
||||||
|
.flex(column, flex-start);
|
||||||
|
gap: 10px;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__condition {
|
||||||
|
.flex(row, space-between, center);
|
||||||
|
width: 100%;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0 16px;
|
||||||
|
border: 1px solid @border-color;
|
||||||
|
border-radius: @radius-sm;
|
||||||
|
background: @white;
|
||||||
|
font-size: 13px;
|
||||||
|
color: @finance-entry-text;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__mark {
|
||||||
|
.flex(row, center);
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
border-radius: @radius-round;
|
||||||
|
color: @white;
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 1;
|
||||||
|
|
||||||
|
&.is-ok {
|
||||||
|
background: @success-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-fail {
|
||||||
|
background: @danger-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__agree {
|
||||||
|
.flex(row, flex-start, center);
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 22px;
|
||||||
|
font-size: @font-size-sm;
|
||||||
|
color: @finance-muted-text;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
accent-color: @primary-strong;
|
||||||
|
}
|
||||||
|
|
||||||
|
em {
|
||||||
|
font-style: normal;
|
||||||
|
color: @primary-strong;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -342,65 +342,80 @@ onMounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
// 设计稿实测(1920,1:1,规格 项目详情__4ed7159e):
|
||||||
|
// 面包屑 (70,116) 24/500 #666666;主标题 (70,196) 22/700 #1F2937;副标题 (66,233) 13/400 #6B7280;
|
||||||
|
// 步骤标题 (126,299) 15/700,序号圆 (94,294) 21×21 #024FFF 12/700 白字;
|
||||||
|
// 字段标签 (87.5,336) 13/500、输入框 (94,361) 1732×39;文本域 (94,623) 1732×130;
|
||||||
|
// 附件区 (70,456) 1780×499 白底、文件行 (94,813) 1732×55 #F7F8FA;
|
||||||
|
// 底栏 (0,1238) 1920×80 + 顶部分隔线 (0,1238) 1px #F0F1F3;按钮 (1409,1258) 254×38 底 #075CFF。
|
||||||
.after-sales {
|
.after-sales {
|
||||||
.flex(column, flex-start, stretch);
|
.flex(column, flex-start, stretch);
|
||||||
gap: @spacing-base;
|
padding-bottom: 104px; // 固定底栏 80 + 呼吸 24
|
||||||
padding-top: @spacing-lg;
|
|
||||||
padding-bottom: @spacing-xxl;
|
|
||||||
// 设计 12:整页白底(蓝湖实测页面底 #FFFFFF)
|
|
||||||
background: @white;
|
background: @white;
|
||||||
|
|
||||||
// 面包屑(设计 24px × 0.77 ≈ 18px,#666666,字重 500)
|
// 设计 ink 116(页头 72 + 44);行高 27 → 盒顶 ≈113
|
||||||
&__breadcrumb {
|
&__breadcrumb {
|
||||||
font-size: 18px;
|
padding-top: 41px;
|
||||||
|
font-size: 24px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
line-height: 27px;
|
||||||
color: @project-breadcrumb;
|
color: @project-breadcrumb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 面包屑盒底 140 → 主标题盒顶 192
|
||||||
&__header {
|
&__header {
|
||||||
.flex(column, flex-start, stretch);
|
.flex(column, flex-start, stretch);
|
||||||
gap: 6px;
|
padding-top: 52px;
|
||||||
padding: 16px 0 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 12:主标题 22 设计px × 0.77 ≈ 17px / 700
|
// 主标题 22/700,行高 29
|
||||||
&__title {
|
&__title {
|
||||||
font-size: 17px;
|
margin: 0;
|
||||||
|
font-size: 22px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: @heading-color;
|
line-height: 29px;
|
||||||
|
color: @project-ink;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 12:副标题 13 设计px × 0.77 ≈ 11px、整行居中
|
// 副标题 13/400,行高 17(顶 233 = 标题底 225 + 8)
|
||||||
&__subtitle {
|
&__subtitle {
|
||||||
font-size: 11px;
|
margin-top: 8px;
|
||||||
text-align: center;
|
font-size: 13px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 17px;
|
||||||
|
color: @project-label;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 三段卡片(设计 12:白底 + 1px #F0F1F3 描边 + 圆角,无阴影) ──
|
// 卡内上下留白:卡1 顶 276 → 序号圆 298(22);select 底 400 → 卡1 底 440(40)
|
||||||
&__card {
|
&__card {
|
||||||
.flex(column, flex-start, stretch);
|
.flex(column, flex-start, stretch);
|
||||||
gap: @spacing-base;
|
margin-top: 30px;
|
||||||
padding: 20px 24px;
|
padding: 22px 0 40px;
|
||||||
border: 1px solid @project-divider;
|
|
||||||
border-radius: @radius-lg;
|
|
||||||
background: @white;
|
background: @white;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 12:卡片标题 15 设计px × 0.77 ≈ 12px / 700 + 圆形序号
|
// 卡1 底 440 → 卡2 顶 456(16);卡2 底 955 → 卡3 顶 971(16)
|
||||||
&__card-title {
|
&__card + &__card {
|
||||||
.flex(row, flex-start, center);
|
margin-top: 16px;
|
||||||
gap: 12px;
|
}
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 700;
|
// 卡片标题:(126,299) 15/700 + 序号圆 (94,294) 21×21
|
||||||
color: @heading-color;
|
&__card-title {
|
||||||
|
.flex(row, flex-start, center);
|
||||||
|
gap: 11px; // 126 − (94+21) = 11
|
||||||
|
padding-left: 24px; // 序号落 x94 = 内容区 70 + 24(01 段)
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 20px;
|
||||||
|
color: @project-ink;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 12:序号圆 ≈26 设计px × 0.77、蓝底白字 12px / 700
|
|
||||||
&__step {
|
&__step {
|
||||||
.flex(row, center);
|
.flex(row, center, center);
|
||||||
width: 20px;
|
flex-shrink: 0;
|
||||||
height: 20px;
|
width: 21px;
|
||||||
|
height: 21px;
|
||||||
border-radius: @radius-round;
|
border-radius: @radius-round;
|
||||||
background: @project-accent;
|
background: @project-accent;
|
||||||
color: @white;
|
color: @white;
|
||||||
@@ -409,112 +424,129 @@ onMounted(() => {
|
|||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 01 段:标题底 319 → 首个字段标签 336(+17);02/03 段:标题在 (126,479)/(118.5,994)
|
||||||
&__field {
|
&__field {
|
||||||
.flex(column, flex-start, stretch);
|
.flex(column, flex-start, stretch);
|
||||||
gap: 4px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
gap: 8px; // 标签底 353 → 框顶 361
|
||||||
|
}
|
||||||
|
|
||||||
|
&__field:first-of-type {
|
||||||
|
margin-top: 17px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__field + &__field {
|
||||||
|
margin-top: 19px; // 框底 400 → 下一标签 419(02 段)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 02 段「项目名称」框底 580 → 「问题描述」标签 598
|
||||||
|
&__field + &__field:has(+ &__field) {
|
||||||
|
margin-top: 19px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 12:标签与提示同行,间距 14 设计px × 0.77
|
|
||||||
&__field-head {
|
&__field-head {
|
||||||
.flex(row, flex-start, center);
|
.flex(row, flex-start, center);
|
||||||
gap: 11px;
|
gap: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 12:提示 11 设计px #9CA3AF
|
|
||||||
&__field-hint {
|
&__field-hint {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: @text-color-disabled;
|
line-height: 15px;
|
||||||
|
color: @project-muted;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 「上传文件」推到行尾(设计 12:浅灰底按钮)
|
// 「上传文件」推到行尾
|
||||||
&__upload-btn {
|
&__upload-btn {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__field-head :deep(.after-sales__upload-btn) {
|
&__field-head :deep(.after-sales__upload-btn) {
|
||||||
height: 28px;
|
height: 21px;
|
||||||
|
padding: 0 7px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 6px;
|
border-radius: 4px;
|
||||||
background: @project-quiet-btn;
|
background: @project-field-bg;
|
||||||
color: @text-color;
|
color: @project-cell;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 21px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
background: @project-quiet-btn;
|
background: @project-field-bg;
|
||||||
color: @text-color;
|
color: @project-cell;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 12:字段标签整行**居中**,13 设计px × 0.77 ≈ 11px / 500
|
// 字段标签 13/500,整行铺满(落到内容区 x70)
|
||||||
&__label {
|
&__label {
|
||||||
font-size: 11px;
|
font-size: 13px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: @heading-color;
|
line-height: 17px;
|
||||||
text-align: center;
|
color: @project-ink;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 12:星号与标签同色同字重(标注层整串同色)
|
|
||||||
&__required {
|
&__required {
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
font-weight: inherit;
|
font-weight: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 12:「售后类型」白底描边,「项目名称」灰底只读态(--filled)
|
// 输入框 / 下拉:设计 1732×39、白底描边 #DDE1E7、圆角 8
|
||||||
&__select {
|
&__select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 36px;
|
height: 39px;
|
||||||
padding: 0 36px 0 12px;
|
padding: 0 13px;
|
||||||
border: 1px solid @border-color;
|
border: 1px solid @project-input-border;
|
||||||
border-radius: 6px;
|
border-radius: 8px;
|
||||||
background-color: @white;
|
background-color: @white;
|
||||||
color: @text-color;
|
color: @project-ink;
|
||||||
font-size: 11px;
|
font-size: 13px;
|
||||||
|
line-height: 17px;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
// 自定义下拉箭头(原生 select 不支持 ::after)
|
box-sizing: border-box;
|
||||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2 4.5 6 8.5l4-4' fill='none' stroke='%238c8c8c' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
|
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2 4.5 6 8.5l4-4' fill='none' stroke='%238c8c8c' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: right 12px center;
|
background-position: right 13px center;
|
||||||
background-size: 12px 12px;
|
background-size: 12px 12px;
|
||||||
transition: border-color 0.2s;
|
transition: border-color 0.2s;
|
||||||
|
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
color: @text-color-disabled;
|
color: @project-muted;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
border-color: @primary-color;
|
border-color: @primary-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 12:项目名称 = 灰底填充(#F3F4F6),无描边
|
// 设计:项目名称 = 灰底填充(#F3F4F6),无描边
|
||||||
&--filled {
|
&--filled {
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
background-color: @input-bg;
|
background-color: @project-field-bg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 「项目名称」与「售后类型」共用同一个下拉样式(见上方 &__select)
|
// 文本域:设计 1732×130、白底描边 #DDE1E7、圆角 8
|
||||||
&__textarea {
|
&__textarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 104px;
|
height: 130px;
|
||||||
padding: 10px 12px;
|
padding: 10px 13px;
|
||||||
border: 1px solid @border-color;
|
border: 1px solid @project-input-border;
|
||||||
border-radius: 6px;
|
border-radius: 8px;
|
||||||
background: @white;
|
background: @white;
|
||||||
color: @text-color;
|
color: @project-ink;
|
||||||
font-size: 11px;
|
font-size: 13px;
|
||||||
line-height: 1.6;
|
line-height: 20px;
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
outline: none;
|
outline: none;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
transition: border-color 0.2s;
|
transition: border-color 0.2s;
|
||||||
|
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
color: @text-color-disabled;
|
color: @project-muted;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
@@ -522,77 +554,87 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 12:字数统计在文本域**内部右下角**
|
// 字数统计在文本域内部右下角(设计 (1770.5,726) 11/400)
|
||||||
&__count {
|
&__count {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 12px;
|
right: 13px;
|
||||||
bottom: 8px;
|
bottom: 8px;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: @text-color-disabled;
|
line-height: 15px;
|
||||||
|
color: @project-muted;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__file-input {
|
&__file-input {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 底部固定操作条(设计 12:顶部分隔线 #F0F1F3、白底、左提示右三键) ──
|
// ── 底部固定操作条(设计 1920×80、白底、顶部分隔线 #F0F1F3) ──
|
||||||
&__footer {
|
&__footer {
|
||||||
position: sticky;
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
.flex(row, space-between, center);
|
.flex(row, space-between, center);
|
||||||
gap: @spacing-base;
|
gap: @spacing-base;
|
||||||
margin-top: 8px;
|
height: 80px;
|
||||||
padding: 12px 0;
|
box-sizing: border-box;
|
||||||
|
padding: 0 121px 0 170px; // 提示 x170、按钮右缘 x1791
|
||||||
border-top: 1px solid @project-divider;
|
border-top: 1px solid @project-divider;
|
||||||
background: @white;
|
background: @white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 底栏提示:(93.99,1266) 12/400 #4B5563 + i 图标 (76.66,1267)
|
||||||
&__footer-tip {
|
&__footer-tip {
|
||||||
.flex(row, flex-start, center);
|
.flex(row, flex-start, center);
|
||||||
gap: 6px;
|
gap: 8px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: @text-color;
|
font-weight: 400;
|
||||||
|
line-height: 16px;
|
||||||
|
color: @project-cell;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__footer-icon {
|
&__footer-icon {
|
||||||
.flex(row, center);
|
.flex(row, center, center);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 16px;
|
width: 14px;
|
||||||
height: 16px;
|
height: 14px;
|
||||||
border: 1px solid currentColor;
|
border: 1px solid currentColor;
|
||||||
border-radius: @radius-round;
|
border-radius: @radius-round;
|
||||||
font-size: 11px;
|
font-size: 10px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__footer-actions {
|
&__footer-actions {
|
||||||
.flex(row, flex-end, center);
|
.flex(row, flex-end, center);
|
||||||
gap: 12px;
|
gap: 8px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
||||||
// 设计 12:取消 / 重置 = 浅灰底灰字,申请售后 = #024FFF 蓝底白字
|
// 设计:三键同高 38、圆角 8、字号 13/500
|
||||||
:deep(.app-button) {
|
:deep(.app-button) {
|
||||||
height: 32px;
|
height: 38px;
|
||||||
border-radius: 6px;
|
padding: 0 18px;
|
||||||
font-size: 11px;
|
border-radius: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.app-button--default) {
|
:deep(.app-button--default) {
|
||||||
border: none;
|
border: none;
|
||||||
background: @project-quiet-btn;
|
background: @project-quiet-btn;
|
||||||
color: @text-color;
|
color: @project-cell;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
background: @project-quiet-btn;
|
background: @project-quiet-btn;
|
||||||
color: @text-color;
|
color: @project-cell;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.app-button--primary) {
|
:deep(.app-button--primary) {
|
||||||
background: @project-accent;
|
background: @demand-secondary-bg;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: @primary-color;
|
background: @primary-color;
|
||||||
@@ -600,36 +642,39 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 12:附件行 = #F7F8FA 圆角行、灰字操作(公共 FileList,仅本页覆写外观)
|
// 附件行:设计 (94,813) 1732×55、底 #F7F8FA、圆角 8
|
||||||
:deep(.file-list) {
|
:deep(.file-list) {
|
||||||
gap: 12px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.file-list__item) {
|
:deep(.file-list__item) {
|
||||||
padding: 6px 14px;
|
height: 55px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0 5.7px 0 16px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 6px;
|
border-radius: 8px;
|
||||||
background: @finance-row-bg;
|
background: @finance-row-bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.file-list__name) {
|
:deep(.file-list__name) {
|
||||||
font-size: 11px;
|
font-size: 13px;
|
||||||
line-height: 15px;
|
line-height: 17px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.file-list__meta) {
|
:deep(.file-list__meta) {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
line-height: 15px;
|
line-height: 15px;
|
||||||
color: @text-color-disabled;
|
color: @project-muted;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.file-list__actions) {
|
:deep(.file-list__actions) {
|
||||||
gap: 12px;
|
gap: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.file-list__btn) {
|
:deep(.file-list__btn) {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: @text-color-secondary;
|
line-height: 16px;
|
||||||
|
color: @project-label;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.file-list__btn--danger) {
|
:deep(.file-list__btn--danger) {
|
||||||
|
|||||||
@@ -153,94 +153,133 @@ function onDeleteFile(): void {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
// 设计稿实测(1920,1:1):横幅 (70,342) 1780×96 底 #FDEEEE;
|
||||||
|
// 标题 (96,367.5) 17/700 #D93025;说明 (96,395.5) 13/400 #4B5563;
|
||||||
|
// 区块标题 (82,468) 15/700;字段标签 (63.5,508) 13/500 #1F2937(整行铺满 → 实际落 x70);
|
||||||
|
// 只读框 (70,533) 1780×39 底 #F3F4F6 描边 #DDE1E7;文本域 (70,615) 1780×130;
|
||||||
|
// 附件标题 (70,768.5) 13/500 + 提示 (149,769.5) 11/400 #9CA3AF + 「上传文档」(1801.4,769) 12/500;
|
||||||
|
// 文件行 (70,805) 1780×55 #F7F8FA;操作 (1628,968)/(1754,968) 15/500 白字 —— 底部按钮。
|
||||||
.project-after-sales {
|
.project-after-sales {
|
||||||
.flex(column, flex-start, stretch);
|
.flex(column, flex-start, stretch);
|
||||||
gap: @spacing-base;
|
|
||||||
// 设计 10:售后信息区块直接落在白底页面上,无外层卡片
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
// ── 顶部工单横幅:浅红底、无图标块(设计 10 实取 #FDEEEE) ──
|
// ── 顶部工单横幅 ──
|
||||||
|
// 页签分割线 317 → 横幅顶 342(25)
|
||||||
&__banner {
|
&__banner {
|
||||||
.flex(row, flex-start, center);
|
.flex(row, flex-start, center);
|
||||||
margin-bottom: 16px;
|
height: 96px;
|
||||||
padding: 12px 20px;
|
box-sizing: border-box;
|
||||||
border-radius: 6px;
|
margin-top: 25px;
|
||||||
background: @project-banner-bg;
|
padding-left: 26px; // 文字 x96 = 内容区 x70 + 26
|
||||||
|
border-radius: 0;
|
||||||
|
background: @project-banner-soft;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__banner-text {
|
&__banner-text {
|
||||||
.flex(column, flex-start, stretch);
|
.flex(column, flex-start, stretch);
|
||||||
gap: 6px;
|
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 10:标题 17 设计px × 0.77 ≈ 13px / 700 红
|
// 横幅标题 17/700,行高 22(顶 367.5 = 横幅 342 + 25.5)
|
||||||
&__banner-title {
|
&__banner-title {
|
||||||
font-size: 13px;
|
margin: 0;
|
||||||
|
padding-top: 0;
|
||||||
|
font-size: 17px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
line-height: 22px;
|
||||||
color: @project-banner-title;
|
color: @project-banner-title;
|
||||||
line-height: 1.3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 10:说明 13 设计px × 0.77 ≈ 11px #4B5563
|
// 说明 13/400(顶 395.5 = 标题底 389.5 + 6)
|
||||||
&__banner-desc {
|
&__banner-desc {
|
||||||
font-size: 11px;
|
margin-top: 6px;
|
||||||
color: @text-color;
|
font-size: 13px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 17px;
|
||||||
|
color: @project-cell;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 「售后信息」区块标题:竖条 + 缩进(与其它页签一致) ──
|
// ── 「售后信息」区块标题 ──
|
||||||
&__title {
|
&__title {
|
||||||
.section-title();
|
.section-title();
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
padding-left: 9px;
|
padding-left: 12px; // 文字落 x82
|
||||||
font-size: 12px;
|
font-size: 15px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
line-height: 20px;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
width: 3px;
|
width: 4px;
|
||||||
height: 15px;
|
height: 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 横幅底 438 → 标题 468;标题底 488 → 字段。
|
||||||
|
// 后代表达式:紧邻横幅的标题额外上抬 —— 用统一节距表达(见下方 padding)
|
||||||
|
&__banner + &__title {
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标题盒底 484.5 → 首标签盒顶 505(20.5)
|
||||||
|
&__title + &__field {
|
||||||
|
margin-top: 20.5px;
|
||||||
|
}
|
||||||
|
|
||||||
&__field {
|
&__field {
|
||||||
.flex(column, flex-start, stretch);
|
.flex(column, flex-start, stretch);
|
||||||
gap: 6px;
|
gap: 5px; // 标签盒底 528 → 只读框顶 533
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 10:标签与提示同行,间距 14 设计px × 0.77
|
// 框底 → 下一标签盒顶:ro1 底 572 → 标签2 586.5(14.5);ro2 底 745 → 标签3 765(20)
|
||||||
|
&__field + &__field {
|
||||||
|
margin-top: 14.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第三个字段(附件)上距略大:设计 ro2 底 745 → 标签3 768.5
|
||||||
|
// 注意:字段在 section 里是第 3/4/5 个子元素(前面还有 banner、title),所以是 nth-child(5)
|
||||||
|
&__field:nth-child(5) {
|
||||||
|
margin-top: 23px;
|
||||||
|
gap: 20px; // 标签行盒底 785 → 首行附件 805
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设计:标签与提示同行
|
||||||
&__field-head {
|
&__field-head {
|
||||||
.flex(row, flex-start, baseline);
|
.flex(row, flex-start, center);
|
||||||
gap: 11px;
|
gap: 14px; // 149 − (70+65) = 14
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 10:提示 11 设计px #9CA3AF
|
|
||||||
&__field-hint {
|
&__field-hint {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: @text-color-disabled;
|
line-height: 15px;
|
||||||
|
color: @project-muted;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 10:字段标签 13 设计px × 0.77 ≈ 11px / 500
|
// 字段标签 13/500
|
||||||
&__label {
|
&__label {
|
||||||
font-size: 11px;
|
font-size: 13px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: @heading-color;
|
line-height: 20px;
|
||||||
|
color: @project-ink;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 只读展示(设计 10:灰底圆角实底框,无描边)
|
// 只读展示:设计 1780×39、底 #F3F4F6、描边 1px #DDE1E7、圆角 8
|
||||||
&__readonly {
|
&__readonly {
|
||||||
min-height: 36px;
|
.flex(row, flex-start, center);
|
||||||
padding: 0 12px;
|
min-height: 39px;
|
||||||
border: none;
|
padding: 0 13px; // 值 x83 = 内容区 70 + 13
|
||||||
border-radius: 6px;
|
border: 1px solid @project-input-border;
|
||||||
background: @input-bg;
|
border-radius: 8px;
|
||||||
color: @text-color;
|
background: @project-field-bg;
|
||||||
font-size: 11px;
|
color: @project-cell;
|
||||||
line-height: 36px;
|
font-size: 13px;
|
||||||
|
line-height: 17px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
&--block {
|
&--block {
|
||||||
min-height: 104px;
|
display: block;
|
||||||
padding: 12px;
|
min-height: 130px;
|
||||||
line-height: 1.7;
|
padding: 10px 13px;
|
||||||
|
line-height: 20px;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
@@ -248,7 +287,7 @@ function onDeleteFile(): void {
|
|||||||
|
|
||||||
&__none {
|
&__none {
|
||||||
padding: @spacing-sm 0;
|
padding: @spacing-sm 0;
|
||||||
font-size: @font-size-sm;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__blank {
|
&__blank {
|
||||||
@@ -257,36 +296,36 @@ function onDeleteFile(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__blank-text {
|
&__blank-text {
|
||||||
font-size: @font-size-base;
|
font-size: @font-size-body;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 10:按钮组贴右,间距 34 设计px × 0.77
|
// 末行附件 925(870+55)→ 按钮盒顶 955(30)
|
||||||
&__actions {
|
&__actions {
|
||||||
.flex(row, flex-end, center);
|
.flex(row, flex-end, center);
|
||||||
gap: 24px;
|
gap: 42px; // 1754 − (1628+84) = 42
|
||||||
margin-top: 8px;
|
margin-top: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 10:高 ≈36 设计px × 0.77、圆角 ≈8 设计px、文字 15 设计px × 0.77
|
|
||||||
&__btn {
|
&__btn {
|
||||||
height: 28px;
|
height: 38px;
|
||||||
padding: 0 14px;
|
padding: 0 18px;
|
||||||
border-radius: 6px;
|
border-radius: 8px;
|
||||||
font-size: 12px;
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 20px;
|
||||||
|
cursor: pointer;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
|
|
||||||
// 「取消售后」= 品牌蓝(设计 10 实取 #1F67EF)
|
|
||||||
&--primary {
|
&--primary {
|
||||||
border: none;
|
border: none;
|
||||||
background: @primary-color;
|
background: @project-accent;
|
||||||
color: @white;
|
color: @white;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: @primary-hover;
|
background: @primary-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 「申请售后」= 红(设计 10 实取 #E3464B)
|
|
||||||
&--apply {
|
&--apply {
|
||||||
border: none;
|
border: none;
|
||||||
background: @project-apply;
|
background: @project-apply;
|
||||||
@@ -298,36 +337,39 @@ function onDeleteFile(): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 10:附件行 = #F7F8FA 圆角行、灰字操作(公共 FileList,仅本页覆写外观)
|
// 附件行:公共 FileList 外观覆写(设计 1780×55、底 #F7F8FA、圆角 8)
|
||||||
:deep(.file-list) {
|
:deep(.file-list) {
|
||||||
gap: 12px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.file-list__item) {
|
:deep(.file-list__item) {
|
||||||
padding: 6px 14px;
|
height: 55px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0 5.7px 0 16px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 6px;
|
border-radius: 8px;
|
||||||
background: @finance-row-bg;
|
background: @finance-row-bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.file-list__name) {
|
:deep(.file-list__name) {
|
||||||
font-size: 11px;
|
font-size: 13px;
|
||||||
line-height: 15px;
|
line-height: 17px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.file-list__meta) {
|
:deep(.file-list__meta) {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
line-height: 15px;
|
line-height: 15px;
|
||||||
color: @text-color-disabled;
|
color: @project-muted;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.file-list__actions) {
|
:deep(.file-list__actions) {
|
||||||
gap: 12px;
|
gap: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.file-list__btn) {
|
:deep(.file-list__btn) {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: @text-color-secondary;
|
line-height: 16px;
|
||||||
|
color: @project-label;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.file-list__btn--danger) {
|
:deep(.file-list__btn--danger) {
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="project-files">
|
<section class="project-files">
|
||||||
|
<!-- 设计稿(785b3eb8)本页签**没有区块标题**,文件行直接起于分割线下方 24px;
|
||||||
|
标题只保留给读屏,视觉隐藏。 -->
|
||||||
<h2 class="project-files__title">
|
<h2 class="project-files__title">
|
||||||
项目文件
|
项目文件
|
||||||
</h2>
|
</h2>
|
||||||
@@ -57,72 +59,73 @@ const emit = defineEmits<{
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
// 设计稿实测(1920,1:1):无区块标题,文件行直接起于 (70,342);
|
||||||
|
// 行 1780×55 底 #F7F8FA、接缝 10;类型徽标 (87,352) 33×21;文件名 (130,352) 13px;
|
||||||
|
// 副行 (130,372) 11px #9CA3AF;操作 (1748,361) 12px #6B7280(预览 / 下载),右缘 x1845。
|
||||||
.project-files {
|
.project-files {
|
||||||
.flex(column, flex-start, stretch);
|
.flex(column, flex-start, stretch);
|
||||||
gap: @spacing-base;
|
|
||||||
// 设计 11:项目文件区块直接落在白底页面上,无卡片
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
|
// 设计稿无可见标题:仅读屏可读(不占位)
|
||||||
&__title {
|
&__title {
|
||||||
.section-title();
|
position: absolute;
|
||||||
// 设计 11:竖条 4×20 设计px、文字 15 设计px/700
|
width: 1px;
|
||||||
padding-left: 9px;
|
height: 1px;
|
||||||
font-size: 12px;
|
margin: -1px;
|
||||||
font-weight: 700;
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
&::before {
|
clip-path: inset(50%);
|
||||||
width: 3px;
|
white-space: nowrap;
|
||||||
height: 15px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__list {
|
&__list {
|
||||||
.flex(column, flex-start, stretch);
|
.flex(column, flex-start, stretch);
|
||||||
// 设计 11:行间距 15 设计px × 0.77
|
gap: 10px;
|
||||||
gap: 12px;
|
margin-top: 24px; // 分割线 317 → 首行顶 342(785b3eb8)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 11:整行通铺 #F7F8FA 浅底、圆角 ≈8 设计px,无描边
|
// 整行通铺 #F7F8FA、圆角 8,行高 55(内容 35 + 上下 10)
|
||||||
&__item {
|
&__item {
|
||||||
.flex(row, space-between);
|
.flex(row, space-between, center);
|
||||||
gap: @spacing-base;
|
gap: @spacing-base;
|
||||||
padding: 6px 16px;
|
height: 55px;
|
||||||
border: none;
|
box-sizing: border-box;
|
||||||
border-radius: 6px;
|
padding: 0 5.6px 0 17px; // 徽标左缘 87 = 内容区 70 + 17;操作右缘 1845 = 1850 − 5
|
||||||
|
border-radius: 8px;
|
||||||
background: @finance-row-bg;
|
background: @finance-row-bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__info {
|
&__info {
|
||||||
.flex(column, flex-start, stretch);
|
.flex(column, flex-start, stretch);
|
||||||
gap: 2px;
|
gap: 3px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 11:文件名 13 设计px、副行 11 设计px #9CA3AF
|
|
||||||
&__name {
|
&__name {
|
||||||
font-size: 11px;
|
max-width: 1410px;
|
||||||
line-height: 15px;
|
font-size: 13px;
|
||||||
color: @heading-color;
|
line-height: 17px;
|
||||||
|
color: @project-ink;
|
||||||
.ellipsis();
|
.ellipsis();
|
||||||
}
|
}
|
||||||
|
|
||||||
&__meta {
|
&__meta {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
line-height: 15px;
|
line-height: 15px;
|
||||||
color: @text-color-disabled;
|
color: @project-muted;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__actions {
|
&__actions {
|
||||||
.flex(row, center);
|
.flex(row, flex-end, center);
|
||||||
// 设计 11:「预览 / 下载」间距 14 设计px × 0.77
|
gap: 14px; // 1748.39 → 1805.33 实测节距 57/40 → 用文字宽 + 14
|
||||||
gap: 12px;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 11:行内操作是灰色纯文字(非蓝色),删除保持警示红
|
|
||||||
&__btn {
|
&__btn {
|
||||||
|
flex-shrink: 0;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: @text-color-secondary;
|
line-height: 16px;
|
||||||
|
color: @project-label;
|
||||||
transition: opacity 0.2s;
|
transition: opacity 0.2s;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@@ -135,7 +138,7 @@ const emit = defineEmits<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__empty {
|
&__empty {
|
||||||
font-size: @font-size-base;
|
font-size: @font-size-body;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -165,54 +165,84 @@ function fileMeta(file: FileInfo): string {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
// 设计稿实测(1920,1:1):标题 (82,342) 15/700;计划卡 (70,380) 372×139 / (70,533) 372×144 /
|
||||||
|
// (70,691) 372×116,描边 1px #F0F1F3、圆角 13;期次名 (89,398) 14/700;状态 (378,400) 11/500;
|
||||||
|
// 字段行 (89,426) 11/400 #6B7280;金额 (341.33,453) 20/700;「立即支付」通栏 372×34 #F59E0B;
|
||||||
|
// 发票记录标题 (82,841)、合同文件标题 (82,1033);文件行 1780×55 #F7F8FA。
|
||||||
.project-finance {
|
.project-finance {
|
||||||
.flex(column, flex-start, stretch);
|
.flex(column, flex-start, stretch);
|
||||||
gap: @spacing-base;
|
|
||||||
// 设计 09:合同财务区块直接落在白底页面上,无外层卡片
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
// 区块标题:与「项目进度/项目文件/售后信息」同款(12px/700 + 左侧蓝色竖条)
|
// 分割线 317 → 「付款计划」盒 339(ink 342,843103b2)
|
||||||
&__title {
|
&__title {
|
||||||
.section-title();
|
.section-title();
|
||||||
padding-left: 9px;
|
margin-top: 22px;
|
||||||
font-size: 12px;
|
padding-left: 12px;
|
||||||
|
font-size: 15px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
line-height: 20px;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
width: 3px;
|
width: 4px;
|
||||||
height: 15px;
|
height: 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 只读回落态(后端未重启、响应无 bills)的说明行
|
// 首区块标题盒底 359 → 首张卡顶 380
|
||||||
&__note {
|
&__title:first-child + &__plans {
|
||||||
font-size: @font-size-sm;
|
margin-top: 21px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 区块间距:付款计划卡底 807 → 「发票记录」盒 838(31);发票行底 999 → 「合同文件」盒 1030(31)
|
||||||
|
&__plans + &__title,
|
||||||
|
&__files + &__title {
|
||||||
|
margin-top: 31px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 区块标题盒底 → 首行顶:838+20 → 879(21);1030+20 → 1071(21)
|
||||||
|
&__title + &__files,
|
||||||
|
&__title + &__file {
|
||||||
|
margin-top: 21px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- 付款计划 ----------
|
// ---------- 付款计划 ----------
|
||||||
|
|
||||||
// 设计稿:卡片**竖排单列、定宽左对齐**(不是横排多列网格)
|
// 卡片竖排单列、定宽 372 左对齐;卡间距 14(380→533→691 实测 153/158,扣卡高)
|
||||||
&__plans {
|
&__plans {
|
||||||
.flex(column, flex-start, flex-start);
|
.flex(column, flex-start, flex-start);
|
||||||
// 设计 09:卡片间距 13 设计px × 0.77
|
gap: 14px;
|
||||||
gap: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 09:白底 + 1px #F0F1F3 描边、圆角 ≈13 设计px,定宽竖排
|
// 白底 + 1px #F0F1F3 描边、圆角 13,定宽 372;内容内缩 19/17
|
||||||
&__plan {
|
&__plan {
|
||||||
.flex(column, flex-start, stretch);
|
.flex(column, flex-start, stretch);
|
||||||
gap: @spacing-sm;
|
position: relative;
|
||||||
width: @finance-card-width;
|
width: 372px;
|
||||||
padding: 14px 15px;
|
padding: 17px 19px 17px;
|
||||||
border: 1px solid @project-divider;
|
border: 1px solid @project-divider;
|
||||||
border-radius: @finance-card-radius;
|
border-radius: 13px;
|
||||||
background: @white;
|
background: @white;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
gap: 0; // 段间距全部走显式 margin,避免 gap 与 margin 叠加
|
||||||
|
|
||||||
|
// 带「立即支付」时按钮贴卡片下缘:底部不留内边距
|
||||||
|
&:has(.project-finance__pay) {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 两条字段、无支付按钮(卡3):底距 17(设计卡高 116 = 2+17+19+8+53+17)
|
||||||
|
&:not(:has(.project-finance__field:nth-child(3))):not(:has(.project-finance__pay)) {
|
||||||
|
padding-bottom: 17px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 三条字段、无支付按钮(卡1):底距 15(设计卡高 139 = 2+17+19+8+78+15)
|
||||||
|
&:has(.project-finance__field:nth-child(3)):not(:has(.project-finance__pay)) {
|
||||||
|
padding-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
// 三态配色(浅底徽标 + 同色系金额)
|
// 三态配色(浅底徽标 + 同色系金额)
|
||||||
&.is-paid {
|
&.is-paid {
|
||||||
.project-finance__badge {
|
.project-finance__badge {
|
||||||
background: @tag-success-bg;
|
|
||||||
color: @finance-paid;
|
color: @finance-paid;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,7 +253,6 @@ function fileMeta(file: FileInfo): string {
|
|||||||
|
|
||||||
&.is-pending {
|
&.is-pending {
|
||||||
.project-finance__badge {
|
.project-finance__badge {
|
||||||
background: @tag-warning-bg;
|
|
||||||
color: @tag-warning-text;
|
color: @tag-warning-text;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,8 +263,7 @@ function fileMeta(file: FileInfo): string {
|
|||||||
|
|
||||||
&.is-idle {
|
&.is-idle {
|
||||||
.project-finance__badge {
|
.project-finance__badge {
|
||||||
background: @tag-info-bg;
|
color: @project-label;
|
||||||
color: @text-color-secondary;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-finance__amount {
|
.project-finance__amount {
|
||||||
@@ -249,61 +277,90 @@ function fileMeta(file: FileInfo): string {
|
|||||||
gap: @spacing-sm;
|
gap: @spacing-sm;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 09:期次标题 14 设计px × 0.77 ≈ 11px / 700
|
// 期次标题 14/700,行高 19(顶 398 = 卡内 380+18)
|
||||||
&__plan-name {
|
&__plan-name {
|
||||||
font-size: 11px;
|
font-size: 14px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: @heading-color;
|
line-height: 19px;
|
||||||
|
color: @project-ink;
|
||||||
.ellipsis();
|
.ellipsis();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 状态徽标:浅底彩色字(设计 09:约 57×22 设计px、圆角 4 设计px、11px 字)
|
// 状态纯文字(设计 11/500)
|
||||||
&__badge {
|
&__badge {
|
||||||
.flex(row, center, center);
|
.flex(row, center, center);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
padding: 3px 10px;
|
margin-top: 2px;
|
||||||
border-radius: @radius-sm;
|
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
line-height: 16px;
|
line-height: 15px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 金额是**文本覆盖层**,不参与卡高:设计里它压在字段区右侧、与首字段行齐平
|
||||||
|
// (ink 453 = 卡顶 380 + 73;字段盒顶 424 + 29)。必须 absolute,否则 26 高的
|
||||||
|
// 行盒会把 __plan-body 撑高,三张卡分别虚高 8/8/0。
|
||||||
&__plan-body {
|
&__plan-body {
|
||||||
.flex(row, space-between, center);
|
.flex(row, space-between, flex-start);
|
||||||
|
position: relative;
|
||||||
|
margin-top: 8px; // 期次行盒底 416 → 字段块顶 424
|
||||||
gap: @spacing-base;
|
gap: @spacing-base;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 字段块按内容定高(3 行 78 / 2 行 53),行距由绝对偏移给,避免 margin 叠加。
|
||||||
|
// 设计:卡1 424→502(3 行 0/23/61),卡2 564→617、卡3 729→782(2 行 0/23)。
|
||||||
&__fields {
|
&__fields {
|
||||||
.flex(column, flex-start, stretch);
|
position: relative;
|
||||||
gap: 4px;
|
flex: 0 0 auto;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|
||||||
|
// 3 行时块高 78,2 行时 53
|
||||||
|
height: 53px;
|
||||||
|
|
||||||
|
&:has(> :nth-child(3)) {
|
||||||
|
height: 78px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__field {
|
&__field {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
line-height: 16px;
|
line-height: 15px;
|
||||||
color: @finance-meta-color;
|
color: @project-label;
|
||||||
.ellipsis();
|
.ellipsis();
|
||||||
|
|
||||||
|
// 字段行距:426 → 449 → 487(相对块顶 0 / 23 / 61)
|
||||||
|
&:nth-child(1) { top: 0; }
|
||||||
|
|
||||||
|
&:nth-child(2) { top: 23px; }
|
||||||
|
|
||||||
|
&:nth-child(3) { top: 61px; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// 金额:设计稿在卡片**右侧**、字号最大且随状态变色(20 设计px × 0.77 ≈ 15px)
|
// 金额 20/700:设计 ink 453(卡顶 380 + 73),相对首字段行盒顶(424)再下移 29
|
||||||
&__amount {
|
&__amount {
|
||||||
|
position: absolute;
|
||||||
|
top: 29px;
|
||||||
|
right: 0;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
font-size: 15px;
|
font-size: 20px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 22px;
|
line-height: 26px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 「立即支付」:设计稿是卡内**满宽**金色按钮,通铺到卡片左右下缘
|
// 「立即支付」:设计卡内通铺 370×34、贴卡片下缘。
|
||||||
|
// 卡2:字段块底 617 → 按钮顶 642(间距 25),按钮底 676,卡底 677。
|
||||||
&__pay {
|
&__pay {
|
||||||
height: 28px;
|
height: 34px;
|
||||||
margin: 0 -15px -14px;
|
margin: 11px -19px 0;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 0 0 @finance-card-radius @finance-card-radius;
|
border-radius: 0 0 13px 13px;
|
||||||
background: @finance-pending;
|
background: @finance-pending;
|
||||||
color: @white;
|
color: @white;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
line-height: 34px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.2s;
|
transition: background 0.2s;
|
||||||
|
|
||||||
@@ -314,31 +371,34 @@ function fileMeta(file: FileInfo): string {
|
|||||||
|
|
||||||
// ---------- 发票 / 合同文件 ----------
|
// ---------- 发票 / 合同文件 ----------
|
||||||
|
|
||||||
|
// 发票行间距 10(879→944 / 1071→1136)
|
||||||
&__files {
|
&__files {
|
||||||
.flex(column, flex-start, stretch);
|
.flex(column, flex-start, stretch);
|
||||||
gap: 12px;
|
gap: 10px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计稿:整行通铺 #F7F8FA 浅底、圆角 ≈8 设计px(区别于卡片)
|
// 整行通铺 #F7F8FA、圆角 8,行高 55
|
||||||
&__file {
|
&__file {
|
||||||
.flex(row, space-between, center);
|
.flex(row, space-between, center);
|
||||||
gap: @spacing-sm;
|
gap: @spacing-base;
|
||||||
padding: 6px 14px;
|
height: 55px;
|
||||||
border-radius: 6px;
|
box-sizing: border-box;
|
||||||
|
padding: 0 5.7px 0 16px; // 徽标左缘 86 = 内容区 70 + 16;操作右缘 x1845
|
||||||
|
border-radius: 8px;
|
||||||
background: @finance-row-bg;
|
background: @finance-row-bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 文件类型标识:按后缀着色的小方块(与需求提报页附件行同一套色板)
|
// 文件类型方块(设计 ≈33×21)
|
||||||
&__file-badge {
|
&__file-badge {
|
||||||
.flex(row, center, center);
|
.flex(row, center, center);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 34px;
|
width: 33px;
|
||||||
height: 20px;
|
height: 21px;
|
||||||
border-radius: @radius-sm;
|
border-radius: @radius-sm;
|
||||||
font-size: 11px;
|
font-size: 9px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
letter-spacing: 0.2px;
|
letter-spacing: 0.2px;
|
||||||
}
|
}
|
||||||
@@ -346,35 +406,35 @@ function fileMeta(file: FileInfo): string {
|
|||||||
&__file-info {
|
&__file-info {
|
||||||
.flex(column, flex-start, stretch);
|
.flex(column, flex-start, stretch);
|
||||||
flex: 1;
|
flex: 1;
|
||||||
gap: 2px;
|
gap: 3px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__file-name {
|
&__file-name {
|
||||||
font-size: 11px;
|
max-width: 1410px;
|
||||||
line-height: 15px;
|
font-size: 13px;
|
||||||
color: @heading-color;
|
line-height: 17px;
|
||||||
|
color: @project-ink;
|
||||||
.ellipsis();
|
.ellipsis();
|
||||||
}
|
}
|
||||||
|
|
||||||
&__file-meta {
|
&__file-meta {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
line-height: 15px;
|
line-height: 15px;
|
||||||
color: @text-color-disabled;
|
color: @project-muted;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__file-actions {
|
&__file-actions {
|
||||||
.flex(row, flex-end, center);
|
.flex(row, flex-end, center);
|
||||||
gap: 12px;
|
gap: 14px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 09:行内操作是灰色纯文字(非蓝色)
|
|
||||||
&__act {
|
&__act {
|
||||||
.flex(row, center, center);
|
flex-shrink: 0;
|
||||||
gap: 4px;
|
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: @text-color-secondary;
|
line-height: 16px;
|
||||||
|
color: @project-label;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: color 0.2s;
|
transition: color 0.2s;
|
||||||
|
|
||||||
@@ -384,7 +444,7 @@ function fileMeta(file: FileInfo): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__empty {
|
&__empty {
|
||||||
font-size: @font-size-base;
|
font-size: @font-size-body;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -65,60 +65,58 @@ function businessTypeText(project: Project): string {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
// 设计稿实测(1920,1:1):logo 方块 (70,196) 64×64、圆角 16;
|
||||||
|
// 标题 ink (142,212) 19/700 #1F2937;字段行 ink (142,245) 11px,标签 #6B7280 / 值 #1F2937;
|
||||||
|
// 状态 ink (1802,225.5) 16/700 #024FFF。整块 195..262(67 高),其后页签行 278 起(+16)。
|
||||||
.project-info-card {
|
.project-info-card {
|
||||||
.flex(row, flex-start, center);
|
.flex(row, flex-start, flex-start);
|
||||||
gap: 14px;
|
gap: 8px; // 142 − (70 + 64) = 8
|
||||||
// 设计 09:标识 + 文字 + 右侧状态直接排在白底页面上,无卡片底/阴影/内边距
|
height: 67px;
|
||||||
padding: 0;
|
box-sizing: border-box;
|
||||||
|
|
||||||
&__logo {
|
&__logo {
|
||||||
.flex(row, center, center);
|
.flex(row, center, center);
|
||||||
flex-shrink: 0;
|
flex: 0 0 64px;
|
||||||
// 设计 09:方块 ≈56 设计px × 0.77,浅灰底、圆角 ≈12 设计px × 0.77
|
width: 64px;
|
||||||
width: 44px;
|
height: 64px;
|
||||||
height: 44px;
|
margin-top: 1px; // 196 − 195
|
||||||
border-radius: 12px;
|
border-radius: 16px;
|
||||||
background: @project-logo-bg;
|
background: @project-logo-bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 09:方块内只有蓝色「</>」文字,无描边徽标框
|
// 方块内只有蓝色「</>」文字(设计 20/400 #024FFF)
|
||||||
&__logo-badge {
|
&__logo-badge {
|
||||||
.flex(row, center, center);
|
|
||||||
width: auto;
|
|
||||||
height: auto;
|
|
||||||
border: none;
|
|
||||||
border-radius: 0;
|
|
||||||
color: @project-accent;
|
color: @project-accent;
|
||||||
font-family: inherit;
|
font-size: 20px;
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
letter-spacing: 0;
|
line-height: 26px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__main {
|
&__main {
|
||||||
.flex(column, flex-start, stretch);
|
.flex(column, flex-start, stretch);
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
gap: 6px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 09:主标题 19 设计px × 0.77 ≈ 15px / 700
|
// 标题 19/700、行盒 25:ink 212 → box 209(块顶 195 + 14)
|
||||||
&__name {
|
&__name {
|
||||||
font-size: 15px;
|
margin: 14px 0 0;
|
||||||
|
font-size: 19px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 1.2;
|
line-height: 25px;
|
||||||
color: @heading-color;
|
color: @project-ink;
|
||||||
.ellipsis();
|
.ellipsis();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计稿:三个字段是**单行**「标签:值」,等距排布(11px 小字号不跟缩)
|
// 字段行 11/15:ink 245 → box 243(标题盒底 234 + 9)
|
||||||
&__meta {
|
&__meta {
|
||||||
.flex(row, flex-start, center);
|
.flex(row, flex-start, center);
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
|
margin-top: 9px;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: @heading-color;
|
line-height: 15px;
|
||||||
|
color: @project-value;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__meta-item {
|
&__meta-item {
|
||||||
@@ -127,16 +125,18 @@ function businessTypeText(project: Project): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__label {
|
&__label {
|
||||||
color: @text-color-secondary;
|
color: @project-label;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 09:右侧状态是纯文字,16 设计px × 0.77 ≈ 12px / 700
|
// 状态纯文字 16/700:ink 225.5 → box 222.5(块顶 195 + 27.5);右缘超出内容区 21.6
|
||||||
&__status {
|
&__status {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
font-size: 12px;
|
margin: 27.5px -21.6px 0 0;
|
||||||
|
font-size: 16px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
line-height: 21px;
|
||||||
|
|
||||||
&.is-active {
|
&.is-active {
|
||||||
color: @project-accent;
|
color: @project-accent;
|
||||||
@@ -151,7 +151,7 @@ function businessTypeText(project: Project): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.is-idle {
|
&.is-idle {
|
||||||
color: @text-color-secondary;
|
color: @project-muted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,33 +61,36 @@ function linkDone(index: number): boolean {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
// 节点列宽 = 设计稿相邻节点中心距(356 设计px × 0.77 ≈ 274px)
|
// 设计稿实测(1920,1:1;5865c6d9 −14 基准 ⇒ 统一后 +14):
|
||||||
@milestone-node-width: 274px;
|
// 标题 ink 471 15/700;首列中心 x261(步距 356)、已完成实心点 26(x235..261,顶 y509);
|
||||||
@milestone-dot-size: 18px;
|
// 连接线 356×3(y520);名称 ink 541 12;说明 ink 561 10;待开始线 #E5E7EB、已完成 #22C55E。
|
||||||
|
@milestone-dot: 26px;
|
||||||
|
@milestone-line-h: 3px;
|
||||||
|
|
||||||
.project-milestones {
|
.project-milestones {
|
||||||
.flex(column, flex-start, stretch);
|
.flex(column, flex-start, stretch);
|
||||||
// 设计 13:里程碑区块直接落在白底页面上,无卡片
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
.section-title();
|
.section-title();
|
||||||
// 设计 13:竖条 4×20 设计px、文字 15 设计px/700
|
margin-top: 35px; // 起止时间盒底 433 → 标题盒 468
|
||||||
padding-left: 9px;
|
padding-left: 12px;
|
||||||
font-size: 12px;
|
font-size: 15px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
line-height: 20px;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
width: 3px;
|
width: 4px;
|
||||||
height: 15px;
|
height: 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计稿:轨迹从容器左缘起、**固定节距**左对齐(不铺满整宽)
|
// 起止时间盒底 433 → 「项目进度(里程碑)」盒 468(ink 471)→ 圆点顶 509
|
||||||
&__track {
|
&__track {
|
||||||
.flex(row, flex-start, flex-start);
|
.flex(row, flex-start, flex-start);
|
||||||
margin-top: 16px;
|
margin-top: 0;
|
||||||
padding-bottom: @spacing-xs;
|
padding-top: 21px; // 标题盒底 488 → 圆点顶 509
|
||||||
|
padding-bottom: 0;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
.scrollbar();
|
.scrollbar();
|
||||||
}
|
}
|
||||||
@@ -95,18 +98,18 @@ function linkDone(index: number): boolean {
|
|||||||
&__node {
|
&__node {
|
||||||
position: relative;
|
position: relative;
|
||||||
.flex(column, flex-start, center);
|
.flex(column, flex-start, center);
|
||||||
flex: 0 0 @milestone-node-width;
|
flex: 0 0 @milestone-node;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|
||||||
// 圆点之间的连接线:从本节点圆点右缘连到下一节点圆点左缘
|
// 连接线:y506(圆点顶 495 起第 11px),x248 → 604(下一节点 x426 起 178 宽)
|
||||||
&:not(:last-child)::after {
|
&:not(:last-child)::after {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: (@milestone-dot-size / 2 - 1px);
|
top: (@milestone-dot / 2 - @milestone-line-h / 2); // 13 − 1.5 = 11.5
|
||||||
left: calc(50% + @milestone-dot-size / 2);
|
left: calc(50% + 13px); // 261 → 248 再 +13 = 当前中心 +13
|
||||||
width: calc(100% - @milestone-dot-size);
|
width: @milestone-node; // 覆盖到下一节点左缘
|
||||||
height: 2px;
|
height: @milestone-line-h;
|
||||||
background: @milestone-line;
|
background: @milestone-ring;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.is-link-done:not(:last-child)::after {
|
&.is-link-done:not(:last-child)::after {
|
||||||
@@ -118,17 +121,15 @@ function linkDone(index: number): boolean {
|
|||||||
.flex(row, center, center);
|
.flex(row, center, center);
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
width: @milestone-dot-size;
|
width: @milestone-dot;
|
||||||
height: @milestone-dot-size;
|
height: @milestone-dot;
|
||||||
border: 3px solid @milestone-idle;
|
border: 3px solid @milestone-idle;
|
||||||
border-radius: @radius-round;
|
border-radius: @radius-round;
|
||||||
background: @white;
|
background: @white;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
// 设计 13:已完成圆点略小(20 设计px)、实心绿
|
// 已完成:实心绿点(设计 (235,495) 26×26,勾白 11)
|
||||||
.is-done & {
|
.is-done & {
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
border-color: @milestone-done;
|
border-color: @milestone-done;
|
||||||
background: @milestone-done;
|
background: @milestone-done;
|
||||||
}
|
}
|
||||||
@@ -140,17 +141,19 @@ function linkDone(index: number): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__check {
|
&__check {
|
||||||
width: 10px;
|
width: 11px;
|
||||||
height: 10px;
|
height: 11px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 圆点底 521 → 名称顶 527(12px 字 line-height 16)
|
||||||
&__name {
|
&__name {
|
||||||
margin-top: @spacing-sm;
|
margin-top: 6px;
|
||||||
max-width: 100%;
|
max-width: calc(@milestone-node - 16px);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
line-height: 16px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: @text-color;
|
color: @project-cell;
|
||||||
.multi-ellipsis(2);
|
.multi-ellipsis(2);
|
||||||
|
|
||||||
.is-done & {
|
.is-done & {
|
||||||
@@ -163,18 +166,20 @@ function linkDone(index: number): boolean {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 名称底 543 → 说明顶 547(10px 字 line-height 14)
|
||||||
&__sub {
|
&__sub {
|
||||||
margin-top: @spacing-xs;
|
margin-top: 4px;
|
||||||
max-width: 100%;
|
max-width: calc(@milestone-node - 16px);
|
||||||
font-size: 11px;
|
font-size: 10px;
|
||||||
|
line-height: 14px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: @text-color-disabled;
|
color: @milestone-idle-text;
|
||||||
.multi-ellipsis(2);
|
.multi-ellipsis(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__empty {
|
&__empty {
|
||||||
margin-top: @spacing-base;
|
margin-top: @spacing-base;
|
||||||
font-size: @font-size-base;
|
font-size: @font-size-body;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -44,45 +44,54 @@ function clampPercent(value: number): number {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
// 设计稿实测(1920,1:1;5865c6d9 为 −14 基准,此处统一到主流基准 ⇒ 全部 +14):
|
||||||
|
// 「开发进度」ink 324 15/700;「总体进度」ink 372 12/400 #6B7280;「65%」ink 364 20/700 #024FFF;
|
||||||
|
// 进度条 (70,402) 1780×6、填充 1157 #024FFF、轨道 #EDEFF2;起止时间 ink 420 11/400 #9CA3AF。
|
||||||
.project-progress {
|
.project-progress {
|
||||||
.flex(column, flex-start, stretch);
|
.flex(column, flex-start, stretch);
|
||||||
// 设计 13:开发进度区块直接落在白底页面上,无卡片
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
|
// 分割线 317 → 标题 box 321
|
||||||
&__title {
|
&__title {
|
||||||
.section-title();
|
.section-title();
|
||||||
// 设计 13:竖条 4×20 设计px、文字 15 设计px/700,条→文间距 8 设计px
|
margin-top: 4px;
|
||||||
padding-left: 9px;
|
padding-left: 12px; // 文字落 x82 = 内容区 x70 + 12
|
||||||
font-size: 12px;
|
font-size: 15px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
line-height: 20px;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
width: 3px;
|
width: 4px;
|
||||||
height: 15px;
|
height: 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 13:正文与容器左缘对齐(只有标题被竖条顶出一级)
|
// 标题盒 321..341 → 「总体进度」盒 370(12/16)
|
||||||
&__head {
|
&__head {
|
||||||
.flex(row, space-between, center);
|
.flex(row, space-between, baseline);
|
||||||
margin-top: 20px;
|
margin-top: 29px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__label {
|
&__label {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
line-height: 16px;
|
||||||
|
color: @project-label;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 13:「65%」20 设计px × 0.77 ≈ 15px / 700
|
// 「65%」20/700:ink 364 → 行盒 361,相对 head 行顶 370 上移 9
|
||||||
&__percent {
|
&__percent {
|
||||||
font-size: 15px;
|
margin-top: -9px;
|
||||||
|
font-size: 20px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
line-height: 26px;
|
||||||
color: @project-accent;
|
color: @project-accent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 说明盒 370..386 → 轨道顶 402
|
||||||
&__track {
|
&__track {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 5px;
|
height: 6px;
|
||||||
margin: 12px 0 0;
|
margin: 14px 0 0;
|
||||||
border-radius: @radius-round;
|
border-radius: @radius-round;
|
||||||
background: @project-track;
|
background: @project-track;
|
||||||
}
|
}
|
||||||
@@ -94,25 +103,13 @@ function clampPercent(value: number): number {
|
|||||||
transition: width 0.3s;
|
transition: width 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 13:末端白底蓝环圆点 ≈15 设计px × 0.77
|
// 轨道底 408 → 起止时间盒 418(11/15,ink 420)
|
||||||
&__knob {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
border: 2px solid @project-accent;
|
|
||||||
border-radius: @radius-round;
|
|
||||||
background: @white;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__range {
|
&__range {
|
||||||
.flex(row, space-between, center);
|
.flex(row, space-between, center);
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
// 设计 13:起止时间 (156,163,175)
|
line-height: 15px;
|
||||||
color: @text-color-disabled;
|
color: @project-muted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -33,75 +33,80 @@ defineProps<Props>()
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
// 设计稿实测:头像直径 64 设计px、相邻头像中心距 99 设计px → ×0.77 得 49px / 76px(取整 48 / 间距 28)
|
// 设计稿实测(1920,1:1;5865c6d9 −14 基准 ⇒ 统一后 +14):
|
||||||
@team-avatar-size: 48px;
|
// 标题 ink 617 15/700;头像直径 64、首列左缘 x66.5(中心 98.5)、相邻中心距 99 → 间距 35;
|
||||||
@team-avatar-gap: 28px;
|
// 姓名 ink 734 16/600 #1F2937;角色 ink 761 12/400 #9CA3AF。
|
||||||
|
|
||||||
.project-team {
|
.project-team {
|
||||||
.flex(column, flex-start, stretch);
|
.flex(column, flex-start, stretch);
|
||||||
// 设计 13:项目团队区块直接落在白底页面上,无卡片
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
.section-title();
|
.section-title();
|
||||||
// 设计 13:竖条 4×20 设计px、文字 15 设计px/700
|
margin-top: 42px; // 里程碑说明盒底 575 → 标题盒 614(ink 617)
|
||||||
padding-left: 9px;
|
padding-left: 12px;
|
||||||
font-size: 12px;
|
font-size: 15px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
line-height: 20px;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
width: 3px;
|
width: 4px;
|
||||||
height: 15px;
|
height: 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 标题盒底 634 → 头像顶 670(中心 y702)
|
||||||
&__list {
|
&__list {
|
||||||
.flex(row, flex-start, flex-start);
|
.flex(row, flex-start, flex-start);
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: @spacing-base @team-avatar-gap;
|
gap: 32px @team-gap;
|
||||||
margin-top: 16px;
|
margin-top: 33px;
|
||||||
|
margin-left: -3.5px; // 首列左缘 66.5 = 内容区 70 − 3.5
|
||||||
}
|
}
|
||||||
|
|
||||||
&__item {
|
&__item {
|
||||||
.flex(column, flex-start, center);
|
.flex(column, flex-start, center);
|
||||||
width: @team-avatar-size;
|
width: @team-size;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 13:头像 = 品牌蓝实底圆 + 白色单字(16 设计px × 0.77 ≈ 12px)
|
// 品牌蓝实底圆 + 白色单字(设计 16/400)
|
||||||
&__avatar {
|
&__avatar {
|
||||||
.flex(row, center, center);
|
.flex(row, center, center);
|
||||||
width: @team-avatar-size;
|
width: @team-size;
|
||||||
height: @team-avatar-size;
|
height: @team-size;
|
||||||
border-radius: @radius-round;
|
border-radius: @radius-round;
|
||||||
background: @team-avatar-bg;
|
background: @team-avatar-bg;
|
||||||
color: @white;
|
color: @white;
|
||||||
font-size: 12px;
|
font-size: 16px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
line-height: 22px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计 13:姓名 16 设计px/600,角色 12 设计px #9CA3AF
|
// 头像底 734 → 姓名 ink 734
|
||||||
&__name {
|
&__name {
|
||||||
margin-top: 16px;
|
margin-top: 0;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
font-size: 12px;
|
font-size: 16px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: @heading-color;
|
line-height: 22px;
|
||||||
|
color: @project-ink;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
.ellipsis();
|
.ellipsis();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 姓名盒底 756 → 角色盒 759(12/19,ink 761)
|
||||||
&__role {
|
&__role {
|
||||||
margin-top: @spacing-xs;
|
margin-top: 3px;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
line-height: 19px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: @text-color-disabled;
|
color: @project-muted;
|
||||||
.ellipsis();
|
.ellipsis();
|
||||||
}
|
}
|
||||||
|
|
||||||
&__empty {
|
&__empty {
|
||||||
margin-top: @spacing-base;
|
margin-top: @spacing-base;
|
||||||
font-size: @font-size-base;
|
font-size: @font-size-body;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -131,7 +131,9 @@ onMounted(load)
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<template v-if="current">
|
<template v-if="current">
|
||||||
|
<div class="project-detail__info">
|
||||||
<ProjectInfoCard :project="current" />
|
<ProjectInfoCard :project="current" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 本地 Tab 切换(设计稿:文字 + 激活项下划线) -->
|
<!-- 本地 Tab 切换(设计稿:文字 + 激活项下划线) -->
|
||||||
<div class="project-detail__tabs">
|
<div class="project-detail__tabs">
|
||||||
@@ -204,47 +206,55 @@ onMounted(load)
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
// 设计稿实测(1920 画布,1:1,以 785b3eb8 / 843103b2 / ef0166c6 三张同基准稿为准):
|
||||||
|
// 面包屑 ink (70,116) 24/500 #666666;信息块 logo (70,196) 64×64、标题 ink (142,212) 19/700、
|
||||||
|
// 字段行 ink (142,245) 11px;页签行 278..318,文字 ink 278、激活蓝 #024FFF/600、常规 #4B5563/400,
|
||||||
|
// 节距 86;分割线 (70,317) 1780×1 #F0F1F3;页脚 (0,1811) 1920×481 #0C1321。
|
||||||
.project-detail {
|
.project-detail {
|
||||||
// ⚠️ mixin 签名是 .flex(@direction; @justify; @align) —— 第二参是 justify、**第三参才是 align**。
|
|
||||||
// 原来只传两参 ⇒ align 取默认的 center ⇒ 纵向容器里所有卡片被压成内容宽度、水平居中(380px 窄条)。
|
|
||||||
.flex(column, flex-start, stretch);
|
.flex(column, flex-start, stretch);
|
||||||
gap: @spacing-base;
|
gap: 0;
|
||||||
padding-top: @spacing-lg;
|
|
||||||
padding-bottom: @spacing-xxl;
|
|
||||||
// 设计 09/10/11/13:详情内容区是整片白底、无卡片(蓝湖实测页面底 #FFFFFF)
|
|
||||||
background: @white;
|
background: @white;
|
||||||
|
|
||||||
// 面包屑(设计 24px × 0.77 ≈ 18px,#666666,字重 500)
|
// 顶栏底 72 → 面包屑 ink 116(24px/27 行盒 → box 111.5,约 +40)
|
||||||
&__breadcrumb {
|
&__breadcrumb {
|
||||||
font-size: 18px;
|
padding-top: 40px;
|
||||||
|
font-size: 24px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
line-height: 27px;
|
||||||
color: @project-breadcrumb;
|
color: @project-breadcrumb;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设计稿:页签无白底容器,仅一行文字 + 激活项下划线,下缘 1px 通栏分割线;
|
// 面包屑块 72..139 → 信息块顶 195(logo 196)
|
||||||
// 首个页签文字与内容区左缘对齐(间距 = 86 设计px 节距 − 文字宽 ≈ 22px)
|
&__info {
|
||||||
|
margin-top: 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 信息块底 262 → 页签行顶 276..317(分割线在 317)
|
||||||
&__tabs {
|
&__tabs {
|
||||||
.flex(row, flex-start, center);
|
.flex(row, flex-start, center);
|
||||||
gap: 22px;
|
gap: 16.45px; // 文字宽 69.55,节距 86 → 间距 86 − 69.55
|
||||||
|
height: 41px;
|
||||||
|
margin-top: 14px;
|
||||||
|
box-sizing: border-box;
|
||||||
border-bottom: 1px solid @project-divider;
|
border-bottom: 1px solid @project-divider;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__tab {
|
&__tab {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 0 0 16px;
|
padding: 0 0 24px; // 文字 box 0..17、下划线落 41
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: @text-color;
|
color: @project-cell;
|
||||||
font-size: 11px;
|
font-size: 13px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 14px;
|
line-height: 17px;
|
||||||
|
cursor: pointer;
|
||||||
transition: color 0.2s;
|
transition: color 0.2s;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: @project-accent;
|
color: @project-accent;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 刷新中:轻微变淡 + 进度光标(本地接口很快,做太重的 loading 反而闪屏)
|
|
||||||
&.is-loading {
|
&.is-loading {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
cursor: progress;
|
cursor: progress;
|
||||||
@@ -261,7 +271,6 @@ onMounted(load)
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
height: 2px;
|
height: 2px;
|
||||||
border-radius: 1px;
|
|
||||||
background: @project-accent;
|
background: @project-accent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -270,7 +279,7 @@ onMounted(load)
|
|||||||
&__empty {
|
&__empty {
|
||||||
padding: @spacing-xxl 0;
|
padding: @spacing-xxl 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: @font-size-base;
|
font-size: @font-size-body;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
36
tabs.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
// 逐页签量测:node tabs.js 生成一段 JS,在页面内点第 N 个页签后量测
|
||||||
|
const TAB = Number(process.argv[2] || 0)
|
||||||
|
const SELS = process.argv[3]
|
||||||
|
? process.argv[3].split(',')
|
||||||
|
: [
|
||||||
|
'.project-detail__tabs',
|
||||||
|
'.project-files__list',
|
||||||
|
'.project-files__item',
|
||||||
|
'.project-finance__title',
|
||||||
|
'.project-finance__plans',
|
||||||
|
'.project-finance__plan',
|
||||||
|
'.project-finance__files',
|
||||||
|
'.project-finance__file',
|
||||||
|
'.project-after-sales__banner',
|
||||||
|
'.project-after-sales__title',
|
||||||
|
'.project-after-sales__field',
|
||||||
|
'.project-after-sales__readonly',
|
||||||
|
'.project-after-sales__actions',
|
||||||
|
'.project-after-sales__btn',
|
||||||
|
]
|
||||||
|
|
||||||
|
const js = `(async function(){
|
||||||
|
const tabs = document.querySelectorAll(".project-detail__tab");
|
||||||
|
if (${TAB} > 0 && tabs[${TAB} - 1]) { tabs[${TAB} - 1].click(); await new Promise(r => setTimeout(r, 900)); }
|
||||||
|
const o = {};
|
||||||
|
for (const s of ${JSON.stringify(SELS)}) {
|
||||||
|
const list = document.querySelectorAll(s);
|
||||||
|
o[s] = list.length === 0 ? null : Array.from(list).slice(0, 3).map(e => {
|
||||||
|
const r = e.getBoundingClientRect();
|
||||||
|
return [+r.x.toFixed(1), +r.y.toFixed(1), +r.width.toFixed(1), +r.height.toFixed(1)];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return JSON.stringify(o);
|
||||||
|
})()`
|
||||||
|
console.log(js)
|
||||||
110
vite.visual.config.ts
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
// 视觉走查专用:在 vite.config.ts 之上叠加 mock 中间件(仅本地临时使用,不参与构建)
|
||||||
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
import { readFileSync, existsSync } from 'node:fs'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
|
||||||
|
const MOCK_DIR = fileURLToPath(new URL('./.mock', import.meta.url))
|
||||||
|
|
||||||
|
/** 把 mock 目录下的 json 按路径映射回响应 */
|
||||||
|
const ROUTES: Array<[RegExp, string]> = [
|
||||||
|
[/\/web\/requirementPool\/get\/progress\//, 'progress.json'],
|
||||||
|
[/\/web\/requirementPool\/get\/files\//, 'files.json'],
|
||||||
|
[/\/web\/requirementPool\/get\/finance\//, 'finance.json'],
|
||||||
|
[/\/web\/requirementPool\/get\/afterSales\//, 'afterSales.json'],
|
||||||
|
[/\/web\/bill\/list/, 'bills.json'],
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态变体覆盖(走查专用)。
|
||||||
|
*
|
||||||
|
* <p>同一张设计稿的多个状态(如「售后信息」只看已完成 + 处理中)无法靠一份静态
|
||||||
|
* json 同时表达,所以支持 `?state=done` 之类的开关在服务端就地改写字段,
|
||||||
|
* 免得为了看一个状态去反复改 mock 文件。</p>
|
||||||
|
*
|
||||||
|
* <p>注意:详情页首屏 4 个接口同帧并发,顶部信息卡的 `status` 取**任意一支**先返回
|
||||||
|
* 的,所以只改一支会出现「头说进行中、体说已完成」的错位。变体必须 4 支都打。</p>
|
||||||
|
*/
|
||||||
|
const VARIANTS: Record<string, Array<[string, unknown]>> = {
|
||||||
|
// 项目已完成 + 有处理中的售后工单 → 对应设计稿 ef0166c6
|
||||||
|
done: [['status', 'COMPLETED']],
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 按点号路径写值,创建不存在的中间层 */
|
||||||
|
function setPath(obj: Record<string, unknown>, path: string, value: unknown): void {
|
||||||
|
const keys = path.split('.')
|
||||||
|
let cur = obj
|
||||||
|
for (let i = 0; i < keys.length - 1; i += 1) {
|
||||||
|
const k = keys[i]
|
||||||
|
if (typeof cur[k] !== 'object' || cur[k] === null) cur[k] = {}
|
||||||
|
cur = cur[k] as Record<string, unknown>
|
||||||
|
}
|
||||||
|
cur[keys[keys.length - 1]] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
{
|
||||||
|
name: 'visual-mock',
|
||||||
|
configureServer(server) {
|
||||||
|
server.middlewares.use((req, res, next) => {
|
||||||
|
const url = req.url ?? ''
|
||||||
|
// 状态开关从 **Referer** 取(= 当前页面 URL),而不是 req.url:
|
||||||
|
// 业务代码发的接口地址里没有页面 query,走查期又不想为看一个状态改应用代码。
|
||||||
|
const referer = String(req.headers.referer ?? '')
|
||||||
|
const state = referer ? new URL(referer).searchParams.get('state') : null
|
||||||
|
for (const [re, file] of ROUTES) {
|
||||||
|
if (!re.test(url)) continue
|
||||||
|
const path = `${MOCK_DIR}/${file}`
|
||||||
|
if (!existsSync(path)) break
|
||||||
|
|
||||||
|
let body = readFileSync(path, 'utf-8')
|
||||||
|
const patch = state ? VARIANTS[state] : undefined
|
||||||
|
if (patch) {
|
||||||
|
const json = JSON.parse(body)
|
||||||
|
for (const [p, v] of patch) setPath(json, `data.${p}`, v)
|
||||||
|
body = JSON.stringify(json)
|
||||||
|
}
|
||||||
|
|
||||||
|
res.setHeader('Content-Type', 'application/json; charset=utf-8')
|
||||||
|
res.end(body)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
next()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
server: {
|
||||||
|
proxy: {
|
||||||
|
// 走查期不转发后端:未命中的接口直接 404(避免真实后端的 401 干扰观感)
|
||||||
|
'/api': {
|
||||||
|
target: 'http://127.0.0.1:1',
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/api/, ''),
|
||||||
|
configure: (proxy) => {
|
||||||
|
proxy.on('error', (_err, _req, res) => {
|
||||||
|
if (res && !res.headersSent) {
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' })
|
||||||
|
}
|
||||||
|
res?.end?.(JSON.stringify({ code: 200, msg: 'ok', data: null }))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
css: {
|
||||||
|
preprocessorOptions: {
|
||||||
|
less: {
|
||||||
|
additionalData:
|
||||||
|
'@import "@/styles/variables.less";\n@import "@/styles/mixins.less";',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||