feat: 完成客户拜访模块全功能开发

本次提交实现了完整的客户拜访业务系统:
1.  新增对象存储上传服务,支持S3兼容协议的RustFS存储
2.  完善客户拜访实体与数据库表结构,补充全业务字段
3.  开发拜访台账与记录的前后端接口,包含增删改查、批量创建、状态流转等能力
4.  新增拜访表单页面与路由,支持父级菜单补全面包屑
5.  新增勘察列表页预填客户ID跳转逻辑
6.  补充UI设计文档与页面截图
7.  调整日志中间件跳过上传路由,优化RBAC权限配置
This commit is contained in:
andyesfly committed 2026-09-14 11:42:10 +08:00
1 parent 68af363ca9
commit 1c83e09fa1
33 files changed
+6920 -587

No files matched your search

@@ -0,0 +1,200 @@
---
name: Industrial Precision
colors:
surface: '#ffffff'
surface-dim: '#ccdbf1'
surface-bright: '#f8f9ff'
surface-container-lowest: '#ffffff'
surface-container-low: '#eef4ff'
surface-container: '#e5efff'
surface-container-high: '#dbe9ff'
surface-container-highest: '#d5e4fa'
on-surface: '#0d1c2c'
on-surface-variant: '#424655'
inverse-surface: '#233142'
inverse-on-surface: '#e9f1ff'
outline: '#727787'
outline-variant: '#c2c6d8'
surface-tint: '#0056d2'
primary: '#0054cd'
on-primary: '#ffffff'
primary-container: '#136bfb'
on-primary-container: '#fefcff'
inverse-primary: '#b2c5ff'
secondary: '#712ae2'
on-secondary: '#ffffff'
secondary-container: '#8a4cfc'
on-secondary-container: '#fffbff'
tertiary: '#00685f'
on-tertiary: '#ffffff'
tertiary-container: '#008378'
on-tertiary-container: '#f4fffc'
error: '#ba1a1a'
on-error: '#ffffff'
error-container: '#ffdad6'
on-error-container: '#93000a'
primary-fixed: '#dae2ff'
primary-fixed-dim: '#b2c5ff'
on-primary-fixed: '#001847'
on-primary-fixed-variant: '#0040a1'
secondary-fixed: '#eaddff'
secondary-fixed-dim: '#d2bbff'
on-secondary-fixed: '#25005a'
on-secondary-fixed-variant: '#5a00c6'
tertiary-fixed: '#89f5e7'
tertiary-fixed-dim: '#6bd8cb'
on-tertiary-fixed: '#00201d'
on-tertiary-fixed-variant: '#005049'
background: '#f8f9ff'
on-background: '#0d1c2c'
surface-variant: '#d5e4fa'
bg-page: '#eef3f8'
text-muted: '#5b6b7e'
border-rule: '#dbe4ee'
status-warn: '#e0852b'
status-error: '#dc2626'
accent-subtle: rgba(29,111,255,0.08)
typography:
display-lg:
fontFamily: hankenGrotesk
fontSize: 32px
fontWeight: '700'
lineHeight: 40px
headline-md:
fontFamily: hankenGrotesk
fontSize: 23px
fontWeight: '700'
lineHeight: 28px
title-sm:
fontFamily: hankenGrotesk
fontSize: 17px
fontWeight: '700'
lineHeight: 24px
body-md:
fontFamily: hankenGrotesk
fontSize: 15px
fontWeight: '400'
lineHeight: 24px
label-bold:
fontFamily: hankenGrotesk
fontSize: 13px
fontWeight: '700'
lineHeight: 16px
data-mono:
fontFamily: jetbrainsMono
fontSize: 12px
fontWeight: '500'
lineHeight: 16px
caption-sm:
fontFamily: hankenGrotesk
fontSize: 12px
fontWeight: '700'
lineHeight: 14px
headline-md-mobile:
fontFamily: hankenGrotesk
fontSize: 20px
fontWeight: '700'
lineHeight: 26px
rounded:
sm: 0.25rem
DEFAULT: 0.5rem
md: 0.75rem
lg: 1rem
xl: 1.5rem
full: 9999px
spacing:
base: 4px
xs: 8px
sm: 12px
md: 16px
lg: 24px
xl: 40px
container-max: 1200px
gutter: 16px
---
## Brand & Style
The design system is engineered for the **Xintong Smart Power Operation Management APP**, targeting power engineers and grid operators. The brand personality is **Professional, Efficient, and Industrial Tech**. It emphasizes reliability through a structured, high-density interface that transforms complex electrical data into actionable insights.
The design style is a blend of **Corporate Modern** and **Information-Dense Minimalism**. It utilizes a "Layered Surface" approach where data is organized into logical card blocks, using subtle elevations and clear semantic color-coding to denote system status. The aesthetic is clean and functional, avoiding decorative elements in favor of technical precision and rapid scanability.
- **Professionalism:** High-contrast typography and a restrained color palette.
- **Efficiency:** Condensed layouts that prioritize data visualization and task status.
- **Reliability:** Solid borders, consistent spacing, and clear state indicators.
## Colors
The color architecture is strictly functional, mapping specifically to the operation layers of the power management system.
- **Primary (Deep Blue):** Reserved for core branding, navigation, and "Client" layer interactions.
- **Secondary (Purple):** Dedicated to "Gateway" operations and task-specific management.
- **Tertiary (Teal):** Used for "Service" layer success states and optimized performance indicators.
- **Neutral (Ink):** Used for primary text to ensure maximum legibility against light surfaces.
**Semantic Usage:**
- Use **status-warn (Amber)** for non-critical alerts or data synchronization states.
- Use **status-error (Red)** for critical power failures or permission denials.
- The **bg-page** provides a soft, low-strain canvas for long-duration monitoring, while **surface** (White) distinguishes interactive cards and data containers.
## Typography
This design system uses **Hanken Grotesk** as the primary typeface for its sharp, contemporary feel that bridges the gap between industrial and tech. **JetBrains Mono** is employed for technical data, device IDs, and numerical values to emphasize the system's engineering DNA.
- **Hierarchy:** Use `display-lg` for dashboard summaries. `title-sm` is the default for card headers.
- **Data Visualization:** All tabular data and technical strings must use `data-mono` for character alignment and clarity.
- **Mobile Adaptation:** For mobile screens, headlines should scale down to `headline-md-mobile` to maintain visual balance while preserving authority.
## Layout & Spacing
The layout utilizes a **Flexible Grid System** with a focus on logical grouping through card-based containment.
- **Grid:** A 12-column grid is used for desktop views, transitioning to a single-column stacked layout for mobile.
- **Rhythm:** An 8px-based spacing scale ensures vertical consistency. Use `lg (24px)` for outer card padding and `md (16px)` for internal element spacing.
- **Mobile Margins:** Standard mobile side margins are set to `md (16px)` to maximize screen real estate for dense data tables.
- **Sectioning:** Distinct operational modules should be separated by `xl (40px)` of vertical space to prevent cognitive overload.
## Elevation & Depth
This design system conveys hierarchy through **Tonal Layering** and **Subtle Ambient Shadows**. We avoid heavy skeuomorphism in favor of a "Flat-Plus" approach.
- **Base Layer:** The page background (`bg-page`) sits at the lowest elevation.
- **Mid Layer (Cards):** UI cards use a white surface with a subtle shadow (`0 4px 14px rgba(0,0,0,.07)`) to appear slightly lifted.
- **Interactive Layer:** On hover or active state, cards may transition to a higher elevation (`0 8px 28px rgba(0,0,0,.1)`) with a slight upward translation of -2px.
- **Overlays:** Modals and dropdowns use the highest z-index with a distinct backdrop blur to maintain focus on the task at hand.
- **Gradients:** Use soft radial gradients in the background (Blue/Purple) to create environmental depth without interfering with content legibility.
## Shapes
The shape language is **Rounded**, balancing industrial rigidity with modern software approachability.
- **Cards & Sections:** Use the standard `rounded-lg (1rem)` for primary containers.
- **Nodes & Blocks:** Architecture and data nodes use `rounded (0.5rem)` to fit more compactly within layouts.
- **Interactive Elements:** Buttons and tags use a "Pill-shape" (`rounded-xl` or 999px) to distinguish them from structural containers.
- **Borders:** Use `1px solid` with the `border-rule` color for most containers. Active states or primary focus areas may increase to `1.5px`.
## Components
**Buttons:**
- **Primary:** Solid `primary_color` with white text. Pill-shaped.
- **Secondary:** Transparent with `primary_color` border or subtle purple tint for operation tasks.
- **Danger:** Solid `status-error` for irreversible actions.
**Cards:**
- White background, `1px` border-rule, and `rounded-lg` corners.
- Headers should have a `18px` internal padding and a bottom border to separate from the content body.
**Input Fields:**
- Soft white background with `1px` border.
- Focused states should use a `primary_color` border with a subtle outer glow.
**Chips & Tags:**
- Used for status (e.g., "Active", "Offline").
- Backgrounds should use `8% opacity` versions of the semantic colors (Blue, Green, Red) with bolded text in the 100% opacity color.
**Data Visualization:**
- Charts should use the primary blue and secondary purple as the main data series colors.
- Use `data-mono` for all axis labels and tooltips to ensure technical accuracy.
**Status Indicators:**
- Small `7px` circular dots used within lists or architecture nodes to show real-time connectivity status.
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"/><meta content="width=device-width, initial-scale=1.0" name="viewport"/><link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=hankenGrotesk:wght@100..900&amp;display=swap" rel="stylesheet"/><style>@layer base{html,body{margin:0;padding:0;}body{overscroll-behavior:none;}main>:first-child{margin-top:0!important;}main>:last-child{margin-bottom:0!important;}}::-webkit-scrollbar{display:none;}</style><script src="https://cdn.tailwindcss.com"></script><script id="tailwind-config">tailwind.config={"darkMode":"class","theme":{"extend":{"colors":{"secondary-fixed-dim":"#d2bbff","secondary":"#712ae2","secondary-fixed":"#eaddff","on-primary":"#ffffff","surface-container-lowest":"#ffffff","error":"#ba1a1a","on-error":"#ffffff","on-surface":"#0d1c2c","on-primary-fixed-variant":"#0040a1","outline-variant":"#c2c6d8","tertiary-fixed-dim":"#6bd8cb","on-secondary-fixed-variant":"#5a00c6","tertiary-container":"#008378","primary-container":"#136bfb","secondary-container":"#8a4cfc","tertiary":"#00685f","on-tertiary-fixed-variant":"#005049","on-secondary-fixed":"#25005a","surface-container-highest":"#d5e4fa","surface-container-low":"#eef4ff","background":"#f8f9ff","on-error-container":"#93000a","inverse-primary":"#b2c5ff","surface-dim":"#ccdbf1","on-primary-fixed":"#001847","surface-container-high":"#dbe9ff","outline":"#727787","status-warn":"#e0852b","on-secondary":"#ffffff","surface-tint":"#0056d2","bg-page":"#eef3f8","surface-variant":"#d5e4fa","primary":"#0054cd","on-tertiary-container":"#f4fffc","inverse-surface":"#233142","text-muted":"#5b6b7e","on-tertiary":"#ffffff","accent-subtle":"rgba(29,111,255,0.08)","primary-fixed":"#dae2ff","tertiary-fixed":"#89f5e7","surface-bright":"#f8f9ff","on-primary-container":"#fefcff","on-background":"#0d1c2c","primary-fixed-dim":"#b2c5ff","border-rule":"#dbe4ee","status-error":"#dc2626","inverse-on-surface":"#e9f1ff","error-container":"#ffdad6","on-tertiary-fixed":"#00201d","on-secondary-container":"#fffbff","on-surface-variant":"#424655","surface":"#ffffff","surface-container":"#e5efff"},"borderRadius":{"DEFAULT":"0.25rem","lg":"0.5rem","xl":"0.75rem","full":"9999px"},"spacing":{"md":"16px","sm":"12px","xl":"40px","container-max":"1200px","base":"4px","xs":"8px","gutter":"16px","lg":"24px"},"fontFamily":{"body-md":["hankenGrotesk"],"headline-md-mobile":["hankenGrotesk"],"caption-sm":["hankenGrotesk"],"label-bold":["hankenGrotesk"],"headline-md":["hankenGrotesk"],"title-sm":["hankenGrotesk"],"data-mono":["jetbrainsMono"],"display-lg":["hankenGrotesk"]},"fontSize":{"body-md":["15px",{"lineHeight":"24px","fontWeight":"400"}],"headline-md-mobile":["20px",{"lineHeight":"26px","fontWeight":"700"}],"caption-sm":["12px",{"lineHeight":"14px","fontWeight":"700"}],"label-bold":["13px",{"lineHeight":"16px","fontWeight":"700"}],"headline-md":["23px",{"lineHeight":"28px","fontWeight":"700"}],"title-sm":["17px",{"lineHeight":"24px","fontWeight":"700"}],"data-mono":["12px",{"lineHeight":"16px","fontWeight":"500"}],"display-lg":["32px",{"lineHeight":"40px","fontWeight":"700"}]}}}}</script></head><body class="bg-background font-body-md text-body-md text-on-surface antialiased"><aside class="fixed left-0 top-0 h-full w-72 bg-inverse-surface text-inverse-on-surface z-50 flex flex-col justify-between shadow-[0_1px_8px_rgba(0,0,0,0.04)]"><div class="flex flex-col flex-1 min-h-0"><div class="h-16 px-md flex items-center gap-sm bg-inverse-surface"><div class="w-10 h-10 rounded-lg bg-primary flex items-center justify-center text-on-primary shadow-sm"><span class="material-symbols-outlined text-[24px]">bolt</span></div><div class="flex flex-col"><div class="flex items-center gap-xs"><span class="font-headline-md text-headline-md text-white tracking-wide">XT-POWER</span><span class="font-caption-sm text-caption-sm bg-primary px-xs py-0.5 rounded text-white">OS</span></div><span class="font-caption-sm text-caption-sm text-outline-variant tracking-wider">鑫潼智电 · 智能配用电中枢</span></div></div><div class="flex-1 overflow-y-auto px-xs py-md"><nav class="flex flex-col gap-1" data-active-classes="bg-primary text-white font-label-bold rounded-lg shadow-sm"><a class="flex items-center gap-sm px-md py-sm rounded-lg text-outline-variant hover:bg-surface-container-highest/10 hover:text-white transition-colors" data-path="workbench" href="#"><span class="material-symbols-outlined text-[20px]">dashboard</span><span class="font-label-bold text-label-bold">运营工作台</span></a><div class="flex flex-col"><a class="flex items-center justify-between px-md py-sm rounded-lg bg-primary text-white font-label-bold shadow-sm transition-colors" data-path="customer-management" href="#"><div class="flex items-center gap-sm"><span class="material-symbols-outlined text-[20px]">apartment</span><span class="font-label-bold text-label-bold">客户管理</span></div><span class="material-symbols-outlined text-[16px]">expand_more</span></a><div class="flex flex-col ml-lg pl-md mt-1 mb-1 gap-1 border-l border-outline/30"><a aria-current="page" class="px-sm py-1.5 rounded-lg text-white font-label-bLine truncated
<!-- 面包屑导航与页头标题区 -->
<div class="flex flex-col gap-xs">
<nav class="flex items-center gap-xs text-text-muted font-caption-sm text-caption-sm">
<span class="hover:text-primary cursor-pointer transition-colors">客户管理</span>
<span class="material-symbols-outlined text-[14px]">chevron_right</span>
<span class="hover:text-primary cursor-pointer transition-colors">客户拜访管理</span>
<span class="material-symbols-outlined text-[14px]">chevron_right</span>
<span class="text-on-surface font-bold">新增客户拜访记录</span>
</nav>
<div class="flex flex-wrap items-center justify-between gap-md mt-1"><div class="flex items-center gap-sm"><div class="w-10 h-10 rounded-lg bg-surface-container-high flex items-center justify-center text-primary shadow-sm"><span class="material-symbols-outlined text-[24px]">handshake</span></div><div><div class="flex items-center gap-xs"><h1 class="font-headline-md text-headline-md text-on-surface">新增拜访跟进记录</h1><span class="px-xs py-0.5 rounded text-primary font-caption-sm text-caption-sm bg-primary/10 font-bold">售前CRM跟进</span></div><p class="font-caption-sm text-caption-sm text-text-muted mt-0.5">聚焦售前需求沟通、合作意向摸排与跟进计划,轻量高效记录</p></div></div><div class="flex items-center gap-xs"><button class="px-md py-1.5 rounded-lg bg-surface text-text-muted hover:text-on-surface border border-border-rule font-caption-sm text-caption-sm transition-colors flex items-center gap-1 shadow-sm" type="button"><span class="material-symbols-outlined text-[16px]">history</span><span>历史跟进台账</span></button></div></div>
</div>
<!-- 双栏主次架构 (8 : 4) -->
<div class="grid grid-cols-1 lg:grid-cols-12 gap-lg items-start"><div class="lg:col-span-8 flex flex-col gap-lg min-w-0"><section class="bg-surface rounded-xl p-lg shadow-sm flex flex-col gap-md"><div class="flex items-center justify-between pb-sm bg-surface-container-low/40 -mx-lg -mt-lg px-lg pt-md rounded-t-xl"><div class="flex items-center gap-xs"><div class="w-2 h-4 rounded-full bg-primary"></div><h2 class="font-title-sm text-title-sm text-on-surface">模块一:拜访基本信息</h2><span class="font-data-mono text-data-mono text-text-muted">(Visit Details)</span></div><span class="text-status-warn font-caption-sm text-caption-sm flex items-center gap-1"><span class="material-symbols-outlined text-[14px]">info</span>* 为必填项</span></div><div class="grid grid-cols-1 md:grid-cols-2 gap-md pt-xs"><div class="flex flex-col gap-1.5 md:col-span-2"><label class="font-label-bold text-label-bold text-on-surface flex items-center justify-between"><span>关联客户 <span class="text-status-error">*</span></span><span class="font-caption-sm text-caption-sm text-text-muted font-normal">支持客户全称或拼音首字母智能检索</span></label><div class="w-full flex items-center justify-between px-md py-2.5 rounded-lg bg-background text-on-surface cursor-pointer shadow-inner"><div class="flex items-center gap-sm"><span class="material-symbols-outlined text-primary text-[20px]">business</span><div class="flex items-center gap-xs"><span class="font-label-bold text-label-bold text-on-surface">华中师范大学南湖校区</span><span class="px-1.5 py-0.5 bg-primary/10 text-primary font-data-mono text-data-mono rounded">CUST-2026-0012</span></div></div><div class="flex items-center gap-xs"><span class="px-xs py-0.5 rounded font-caption-sm text-caption-sm bg-primary/10 text-primary font-bold">重点推进客户</span><span class="material-symbols-outlined text-text-muted text-[18px]">arrow_drop_down</span></div></div></div><div class="flex flex-col gap-1.5 md:col-span-2"><label class="font-label-bold text-label-bold text-on-surface">拜访方式 <span class="text-status-error">*</span></label><div class="grid grid-cols-2 sm:grid-cols-4 gap-xs"><button class="flex items-center justify-center gap-1 py-2 px-sm rounded-lg bg-primary text-on-primary font-label-bold text-label-bold shadow-sm" type="button"><span class="material-symbols-outlined text-[16px]">pin_drop</span><span>现场拜访</span></button><button class="flex items-center justify-center gap-1 py-2 px-sm rounded-lg bg-surface-container-low text-on-surface font-label-bold text-label-bold hover:bg-surface-container transition-colors" type="button"><span class="material-symbols-outlined text-[16px]">handshake</span><span>商务拜访</span></button><button class="flex items-center justify-center gap-1 py-2 px-sm rounded-lg bg-surface-container-low text-on-surface font-label-bold text-label-bold hover:bg-surface-container transition-colors" type="button"><span class="material-symbols-outlined text-[16px]">phone_in_talk</span><span>电话沟通</span></button><button class="flex items-center justify-center gap-1 py-2 px-sm rounded-lg bg-surface-container-low text-on-surface font-label-bold text-label-bold hover:bg-surface-container transition-colors" type="button"><span class="material-symbols-outlined text-[16px]">videocam</span><span>线上会议</span></button></div></div><div class="flex flex-col gap-1.5"><label class="font-label-bold text-label-bold text-on-surface">拜访时间 <span class="text-status-error">*</span></label><div class="flex items-center justify-between px-md py-2 rounded-lg bg-background text-on-surface shadow-inner"><div class="flex items-center gap-xs"><span class="material-symbols-outlined text-text-muted text-[18px]">calendar_today</span><span class="font-data-mono text-data-mono">2026-03-28 14:30 ~ 16:00</span></div><span class="font-caption-sm text-caption-sm text-primary font-bold bg-primary/10 px-xs py-0.5 rounded">1.5h</span></div></div><div class="flex flex-col gap-1.5"><label class="font-label-bold text-label-bold text-on-surface">业务负责人及陪同人员 <span class="text-status-error">*</span></label><div class="flex items-center gap-xs"><div class="flex-1 flex items-center gap-xs px-sm py-2 rounded-lg bg-background shadow-inner text-on-surface"><span class="w-5 h-5 rounded-full bg-primary text-white flex items-center justify-center text-[10px] font-bold">李</span><span class="font-body-md text-body-md">李志敏</span><span class="font-caption-sm text-caption-sm text-text-muted">(负责人)</span></div><div class="flex-1 flex items-center gap-xs px-sm py-2 rounded-lg bg-background shadow-inner text-on-surface"><span class="w-5 h-5 rounded-full bg-surface-container-highest text-on-surface flex items-center justify-center text-[10px] font-bold">王</span><input class="w-full bg-transparent focus:outline-none text-body-md font-body-md text-on-surface" type="text" value="王助理 (商务陪同)"/></div></divLine truncated
</div></main></div></body></html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 507 KiB