123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535 |
- <!-- 只展示修改处,未变更部分用占位注释表示 -->
- <div class="project-detail-container designer-page">
- <!-- 项目标题栏 -->
- <div class="project-header card">
- <div class="header-left">
- <div class="header-content">
- <h1>项目详情</h1>
- <div class="project-meta">
- <span class="project-id">项目ID: {{ projectId }}</span>
- @if (project) { <span class="project-status">{{ project.status }}</span> }
- <!-- 紧急与异常徽标(使用控制流指令) -->
- <!-- 保持已有@if 徽标逻辑不变 -->
- </div>
- </div>
- </div>
-
- <!-- 导航按钮区域 - 移动到标题左侧 -->
- <div class="header-nav">
- <div class="header-nav-tabs">
- @for (tab of tabs; track tab.id) {
- <button
- class="nav-tab"
- [class.active]="activeTab === tab.id"
- (click)="switchTab(tab.id)">
- {{ tab.name }}
- </button>
- }
- </div>
- </div>
- <!-- 四个环节圆圈导航 -->
- <div class="header-center">
- <div class="stage-navigation">
- <div class="stage-nav-item"
- [class.completed]="getSectionStatus('order') === 'completed'"
- [class.active]="getSectionStatus('order') === 'active'"
- (click)="toggleSection('order')">
- <div class="stage-nav-circle">
- <span class="stage-nav-number">1</span>
- </div>
- <div class="stage-nav-label">订单创建</div>
- </div>
- <div class="stage-nav-connector" [class.completed]="getSectionStatus('requirements') === 'completed' || getSectionStatus('requirements') === 'active'"></div>
- <div class="stage-nav-item"
- [class.completed]="getSectionStatus('requirements') === 'completed'"
- [class.active]="getSectionStatus('requirements') === 'active'"
- (click)="toggleSection('requirements')">
- <div class="stage-nav-circle">
- <span class="stage-nav-number">2</span>
- </div>
- <div class="stage-nav-label">确认需求</div>
- </div>
- <div class="stage-nav-connector" [class.completed]="getSectionStatus('delivery') === 'completed' || getSectionStatus('delivery') === 'active'"></div>
- <div class="stage-nav-item"
- [class.completed]="getSectionStatus('delivery') === 'completed'"
- [class.active]="getSectionStatus('delivery') === 'active'"
- (click)="toggleSection('delivery')">
- <div class="stage-nav-circle">
- <span class="stage-nav-number">3</span>
- </div>
- <div class="stage-nav-label">交付执行</div>
- </div>
- <div class="stage-nav-connector" [class.completed]="getSectionStatus('aftercare') === 'completed' || getSectionStatus('aftercare') === 'active'"></div>
- <div class="stage-nav-item"
- [class.completed]="getSectionStatus('aftercare') === 'completed'"
- [class.active]="getSectionStatus('aftercare') === 'active'"
- (click)="toggleSection('aftercare')">
- <div class="stage-nav-circle">
- <span class="stage-nav-number">4</span>
- </div>
- <div class="stage-nav-label">售后</div>
- </div>
- </div>
- </div>
-
- <div class="header-right">
- <div class="header-actions">
- <!-- 导出阶段报告 -->
- <button (click)="exportProjectReport()" class="action-btn secondary-btn">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
- <polyline points="14 2 14 8 20 8"></polyline>
- <line x1="16" y1="13" x2="8" y2="13"></line>
- <line x1="16" y1="17" x2="8" y2="17"></line>
- </svg>
- 导出报告
- </button>
-
- <button (click)="generateReminderMessage()" class="action-btn stagnation-btn">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <circle cx="12" cy="12" r="10"></circle>
- <polyline points="12 6 12 12 16 14"></polyline>
- </svg>
- 设置停滞
- </button>
-
- <!-- 切换项目下拉菜单 -->
- <div class="project-switcher">
- <button (click)="showDropdown = !showDropdown" class="action-btn switch-btn">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
- <polyline points="9 22 9 12 15 12 15 22"></polyline>
- </svg>
- 切换项目
- <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="dropdown-icon">
- <polyline points="6 9 12 15 18 9"></polyline>
- </svg>
- </button>
- @if (showDropdown) {
- <div class="switch-dropdown" (click)="$event.stopPropagation()">
- @for (p of projects; track p.id) {
- <div (click)="switchProject(p.id); showDropdown = false"
- [class.active]="p.id === projectId"
- class="project-item">
- <span class="project-name">{{ p.name }}</span>
- <span class="project-status-badge"
- [class.ongoing]="p.status === '进行中'"
- [class.completed]="p.status === '已完成'"
- [class.pending]="p.status === '待处理'">
- {{ p.status }}
- </span>
- </div>
- }
- </div>
- }
- </div>
- <!-- 返回工作台按钮 -->
- <button (click)="backToWorkbench()" class="action-btn back-btn primary">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M19 12H5"></path>
- <polyline points="12 19 5 12 12 5"></polyline>
- </svg>
- 返回工作台
- </button>
- </div>
- </div>
- <!-- 隐藏的文件输入元素 -->
- @for (process of deliveryProcesses; track process.id) {
- @for (space of process.spaces; track space.id) {
- <input
- type="file"
- [id]="'space-file-input-' + process.id + '-' + space.id"
- accept="image/*"
- multiple
- (change)="onSpaceFileSelected($event, process.id, space.id)"
- style="display: none;" />
- }
- }
- </div>
- <!-- 图片预览模态框 -->
- @if (showImagePreview) {
- <div class="image-preview-modal" (click)="closeImagePreview()">
- <div class="modal-backdrop"></div>
- <div class="modal-content" (click)="$event.stopPropagation()">
- <div class="modal-header">
- <h3>{{ previewImageData?.name }}</h3>
- <button class="close-btn" (click)="closeImagePreview()">
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
- <line x1="18" y1="6" x2="6" y2="18"></line>
- <line x1="6" y1="6" x2="18" y2="18"></line>
- </svg>
- </button>
- </div>
- <div class="modal-body">
- <img [src]="previewImageData?.url" [alt]="previewImageData?.name" />
- </div>
- <div class="modal-footer">
- <div class="image-info">
- <span>文件大小: {{ previewImageData?.size }}</span>
- @if (previewImageData?.reviewStatus) {
- <span class="status-badge">{{ getImageReviewStatusText(previewImageData) }}</span>
- }
- </div>
- <div class="modal-actions">
- <button class="secondary-btn" (click)="downloadImage(previewImageData)">下载</button>
- <button class="danger-btn" (click)="removeImageFromPreview()">删除</button>
- </div>
- </div>
- </div>
- </div>
- }
- <!-- 设计师日历弹窗 -->
- @if (showDesignerCalendar) {
- <div class="image-preview-modal" (click)="closeDesignerCalendar()">
- <div class="modal-backdrop"></div>
- <div class="modal-content" (click)="$event.stopPropagation()">
- <div class="modal-header">
- <h3>选择设计师与排期</h3>
- <button class="close-btn" (click)="closeDesignerCalendar()">
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
- <line x1="18" y1="6" x2="6" y2="18"></line>
- <line x1="6" y1="6" x2="18" y2="18"></line>
- </svg>
- </button>
- </div>
- <div class="modal-body">
- <app-designer-calendar
- [designers]="calendarDesigners"
- [selectedDate]="selectedCalendarDate"
- [projectGroups]="calendarGroups"
- (designerSelected)="onCalendarDesignerSelected($event)"
- (assignmentRequested)="onCalendarAssignmentRequested($event)"
- ></app-designer-calendar>
- </div>
- </div>
- </div>
- }
- <!-- 提醒消息弹窗 -->
- @if (reminderMessage) {
- <div class="reminder-popup">
- {{ reminderMessage }}
- </div>
- }
- <!-- 标准阶段进度(5阶段) -->
- <!-- 已采用@for,不变 -->
- <!-- 顶部导航标签页 -->
- <!-- 原有代码保留 -->
- <!-- 水平导航栏 - 已移动到顶部,此处删除 -->
- <div class="tab-content">
- <!-- 项目进度标签页 -->
- @if (isActiveTab('progress')) {
- <div class="progress-tab-content">
- <div class="main-content-layout">
- <!-- 左侧保留 -->
- <div class="left-column">
- <div class="project-info-card card" [class.collapsed-card]="!isCustomerInfoExpanded">
- <div class="card-header" (click)="toggleCustomerInfo()" style="cursor: pointer;">
- <h2>客户信息</h2>
- <div class="header-actions">
- <div class="sync-status" [class.syncing]="isSyncingCustomerInfo">
- @if (isSyncingCustomerInfo) {
- <span class="sync-indicator">
- <span class="sync-spinner"></span>
- 同步中...
- </span>
- } @else if (lastSyncTime) {
- <span class="sync-time">
- <i class="icon-sync"></i>
- 已同步: {{ formatTime(lastSyncTime) }}
- </span>
- }
- </div>
- <div class="toggle-icon" [class.expanded]="isCustomerInfoExpanded">
- <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
- <path d="M4 6L8 10L12 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
- </svg>
- </div>
- </div>
- </div>
- @if (project) {
- <div class="info-grid" [class.collapsed]="!isCustomerInfoExpanded">
- <!-- 显示订单创建时填写的客户信息,如果有的话 -->
- @if (orderCreationData?.customerInfo) {
- <!-- 基本客户信息 -->
- <div class="info-item key-info"><label>客户姓名</label><span>{{ orderCreationData.customerInfo.name }}</span></div>
- <div class="info-item key-info"><label>联系电话</label><span>{{ orderCreationData.customerInfo.phone }}</span></div>
- @if (orderCreationData.customerInfo.wechat) {
- <div class="info-item"><label>微信号</label><span>{{ orderCreationData.customerInfo.wechat }}</span></div>
- }
- <div class="info-item"><label>客户类型</label><span>{{ orderCreationData.customerInfo.customerType }}</span></div>
- @if (orderCreationData.customerInfo.source) {
- <div class="info-item"><label>客户来源</label><span>{{ orderCreationData.customerInfo.source }}</span></div>
- }
- @if (orderCreationData.customerInfo.remark) {
- <div class="info-item"><label>备注信息</label><span>{{ orderCreationData.customerInfo.remark }}</span></div>
- }
- } @else {
- <!-- 默认显示项目信息 -->
- <div class="info-item key-info"><label>项目负责人</label><span>{{ project.assigneeName }}</span></div>
- }
- <div class="info-item"><label>项目创建</label><span>{{ formatDate(project.createdAt) }}</span></div>
- <div class="info-item"><label>截止日期</label><span>{{ formatDate(project.deadline) }}</span></div>
- </div>
- <div class="tags-container" [class.collapsed]="!isCustomerInfoExpanded" style="margin-top: 12px;">
- <div class="tag-section">
- <h3>客户标签</h3>
- <div class="tags">
- @for (tag of project.customerTags; track $index) {
- <span class="tag">{{ tag.source }} · {{ tag.needType }} · {{ tag.preference }} · {{ tag.colorAtmosphere }}</span>
- }
- @if (project.customerTags.length === 0) { <span class="desc">暂无标签</span> }
- </div>
- </div>
- </div>
-
- } @else {
- <div class="loading-state">
- <div class="loading-spinner"></div>
- <div>正在加载客户信息...</div>
- </div>
- }
- </div>
-
- <!-- 方案确认卡片 - 优化样式与左侧客户信息卡片保持一致 -->
- <div class="proposal-confirm-card card">
- <div class="card-header">
- <h2>方案确认</h2>
- <div class="sync-status">
- <span class="progress-text">{{ getRequiredStagesProgress() }}% 完成</span>
- </div>
- </div>
-
-
- <!-- 素材解析状态 -->
- @if (isAnalyzing) {
- <div class="analysis-progress">
- <div class="progress-header">
- <h4>正在解析素材...</h4>
- <span class="progress-percentage">{{ analysisProgress.toFixed(0) }}%</span>
- </div>
- <div class="progress-bar">
- <div class="progress-fill" [style.width.%]="analysisProgress"></div>
- </div>
- <p class="progress-description">AI正在分析您的需求,生成专属设计方案</p>
- </div>
- }
-
- <!-- 方案展示区域 -->
- @if (proposalAnalysis && !isAnalyzing) {
- <div class="proposal-display">
- <!-- 方案概览 -->
- <div class="proposal-overview">
- <div class="overview-header">
- <h3>{{ proposalAnalysis.name }}</h3>
- <div class="proposal-meta">
- <span class="version">{{ proposalAnalysis.version }}</span>
- <span class="feasibility-score">可行性: {{ proposalAnalysis.feasibility.overall }}%</span>
- </div>
- </div>
-
- <!-- 快速摘要 -->
- <div class="quick-summary">
- <div class="summary-item">
- <span class="label">设计风格</span>
- <span class="value">{{ getStyleSummary() }}</span>
- </div>
- <div class="summary-item">
- <span class="label">色彩方案</span>
- <span class="value">{{ getColorSummary() }}</span>
- </div>
- <div class="summary-item">
- <span class="label">空间效率</span>
- <span class="value">{{ getSpaceEfficiency() }}%</span>
- </div>
- </div>
- </div>
-
- <!-- 详细方案内容 -->
- <div class="proposal-details">
- <!-- 材质规格与分类 -->
- <div class="detail-section">
- <div class="section-header">
- <h4>材质规格与分类</h4>
- <span class="section-count">{{ (proposalAnalysis && proposalAnalysis.materials ? proposalAnalysis.materials.length : 0) }} 类材质</span>
- </div>
- <div class="materials-grid">
- @for (material of (proposalAnalysis && proposalAnalysis.materials ? proposalAnalysis.materials : []); track material.category) {
- <div class="material-card" [class]="material.usage.priority">
- <div class="material-header">
- <h5>{{ material.category }}</h5>
- <span class="usage-percentage">{{ material.usage.percentage }}%</span>
- </div>
- <div class="material-specs">
- <div class="spec-item">
- <span class="spec-label">类型</span>
- <span class="spec-value">{{ material.specifications.type }}</span>
- </div>
- <div class="spec-item">
- <span class="spec-label">等级</span>
- <span class="spec-value">{{ material.specifications.grade }}</span>
- </div>
- <div class="spec-item">
- <span class="spec-label">应用区域</span>
- <span class="spec-value">{{ material.usage.area }}</span>
- </div>
- </div>
- <div class="material-properties">
- <span class="property-tag">{{ material.properties.texture }}</span>
- <span class="property-tag">{{ material.properties.color }}</span>
- </div>
- </div>
- }
- </div>
- </div>
-
- <!-- 设计风格特征 -->
- <div class="detail-section">
- <div class="section-header">
- <h4>设计风格特征</h4>
- <span class="style-name">{{ (proposalAnalysis && proposalAnalysis.designStyle ? proposalAnalysis.designStyle.primaryStyle : null) || '未定义' }}</span>
- </div>
- <div class="style-elements">
- @for (element of (proposalAnalysis && proposalAnalysis.designStyle && proposalAnalysis.designStyle.styleElements ? proposalAnalysis.designStyle.styleElements : []); track element.element) {
- <div class="style-element">
- <div class="element-header">
- <span class="element-name">{{ element.element }}</span>
- <span class="influence-score">{{ element.influence }}%</span>
- </div>
- <div class="element-description">{{ element.description }}</div>
- <div class="influence-bar">
- <div class="influence-fill" [style.width.%]="element.influence"></div>
- </div>
- </div>
- }
- </div>
- <div class="style-characteristics">
- @for (char of (proposalAnalysis && proposalAnalysis.designStyle && proposalAnalysis.designStyle.characteristics ? proposalAnalysis.designStyle.characteristics : []); track char.feature) {
- <div class="characteristic-item" [class]="char.importance">
- <span class="char-feature">{{ char.feature }}</span>
- <span class="char-value">{{ char.value }}</span>
- </div>
- }
- </div>
- </div>
-
- <!-- 色彩搭配方案及占比分析 -->
- <div class="detail-section">
- <div class="section-header">
- <h4>色彩搭配方案及占比分析</h4>
- <span class="harmony-type">{{ (proposalAnalysis && proposalAnalysis.colorScheme && proposalAnalysis.colorScheme.harmony ? proposalAnalysis.colorScheme.harmony.type : null) || '未定义' }}</span>
- </div>
- <div class="color-palette">
- @for (color of (proposalAnalysis && proposalAnalysis.colorScheme && proposalAnalysis.colorScheme.palette ? proposalAnalysis.colorScheme.palette : []); track color.hex) {
- <div class="color-item" [class]="color.role">
- <div class="color-swatch" [style.background-color]="color.hex"></div>
- <div class="color-info">
- <div class="color-name">{{ color.color }}</div>
- <div class="color-percentage">{{ color.percentage }}%</div>
- <div class="color-role">{{ color.role }}</div>
- </div>
- <div class="color-codes">
- <span class="hex-code">{{ color.hex }}</span>
- <span class="rgb-code">RGB({{ color.rgb }})</span>
- </div>
- </div>
- }
- </div>
- <div class="color-psychology">
- <div class="psychology-item">
- <span class="label">氛围营造</span>
- <span class="value">{{ (proposalAnalysis && proposalAnalysis.colorScheme && proposalAnalysis.colorScheme.psychology ? proposalAnalysis.colorScheme.psychology.mood : null) || '未定义' }}</span>
- </div>
- <div class="psychology-item">
- <span class="label">空间感受</span>
- <span class="value">{{ (proposalAnalysis && proposalAnalysis.colorScheme && proposalAnalysis.colorScheme.psychology ? proposalAnalysis.colorScheme.psychology.atmosphere : null) || '未定义' }}</span>
- </div>
- </div>
- </div>
-
- <!-- 空间尺寸数据及功能分区 -->
- <div class="detail-section">
- <div class="section-header">
- <h4>空间尺寸数据及功能分区</h4>
- <span class="total-area">总面积 {{ (proposalAnalysis && proposalAnalysis.spaceLayout && proposalAnalysis.spaceLayout.dimensions ? proposalAnalysis.spaceLayout.dimensions.area : 0) || 0 }}㎡</span>
- </div>
- <div class="space-dimensions">
- <div class="dimension-item">
- <span class="dim-label">长度</span>
- <span class="dim-value">{{ (proposalAnalysis && proposalAnalysis.spaceLayout && proposalAnalysis.spaceLayout.dimensions ? proposalAnalysis.spaceLayout.dimensions.length : 0) || 0 }}m</span>
- </div>
- <div class="dimension-item">
- <span class="dim-label">宽度</span>
- <span class="dim-value">{{ (proposalAnalysis && proposalAnalysis.spaceLayout && proposalAnalysis.spaceLayout.dimensions ? proposalAnalysis.spaceLayout.dimensions.width : 0) || 0 }}m</span>
- </div>
- <div class="dimension-item">
- <span class="dim-label">层高</span>
- <span class="dim-value">{{ (proposalAnalysis && proposalAnalysis.spaceLayout && proposalAnalysis.spaceLayout.dimensions ? proposalAnalysis.spaceLayout.dimensions.height : 0) || 0 }}m</span>
- </div>
- <div class="dimension-item">
- <span class="dim-label">体积</span>
- <span class="dim-value">{{ (proposalAnalysis && proposalAnalysis.spaceLayout && proposalAnalysis.spaceLayout.dimensions ? proposalAnalysis.spaceLayout.dimensions.volume : 0) || 0 }}m³</span>
- </div>
- </div>
- <div class="functional-zones">
- @for (zone of (proposalAnalysis && proposalAnalysis.spaceLayout && proposalAnalysis.spaceLayout.functionalZones ? proposalAnalysis.spaceLayout.functionalZones : []); track zone.zone) {
- <div class="zone-card">
- <div class="zone-header">
- <h5>{{ zone.zone }}</h5>
- <div class="zone-stats">
- <span class="zone-area">{{ zone.area }}㎡</span>
- <span class="zone-percentage">{{ zone.percentage }}%</span>
- </div>
- </div>
- <div class="zone-requirements">
- <div class="requirements-label">功能需求</div>
- <div class="requirements-tags">
- @for (req of zone.requirements; track req) {
- <span class="requirement-tag">{{ req }}</span>
- }
- </div>
- </div>
- <div class="zone-furniture">
- <div class="furniture-label">家具配置</div>
- <div class="furniture-list">
- @for (furniture of zone.furniture; track furniture; let isLast = $last) {
- {{ furniture }}@if (!isLast) {、}
- }
- </div>
- </div>
- </div>
- }
- </div>
- </div>
-
- <!-- 预算与时间线 -->
- <div class="detail-section">
- <div class="section-header">
- <h4>预算与时间线</h4>
- <span class="total-budget">总预算 ¥{{ ((proposalAnalysis && proposalAnalysis.budget ? proposalAnalysis.budget.total : 0) || 0).toLocaleString() }}</span>
- </div>
- <div class="budget-breakdown">
- @for (item of (proposalAnalysis && proposalAnalysis.budget && proposalAnalysis.budget.breakdown ? proposalAnalysis.budget.breakdown : []); track item.category) {
- <div class="budget-item">
- <div class="budget-category">{{ item.category }}</div>
- <div class="budget-amount">¥{{ item.amount.toLocaleString() }}</div>
- <div class="budget-percentage">{{ item.percentage }}%</div>
- <div class="budget-bar">
- <div class="budget-fill" [style.width.%]="item.percentage"></div>
- </div>
- </div>
- }
- </div>
- <div class="timeline">
- @for (phase of (proposalAnalysis && proposalAnalysis.timeline ? proposalAnalysis.timeline : []); track phase.phase) {
- <div class="timeline-item">
- <div class="phase-name">{{ phase.phase }}</div>
- <div class="phase-duration">{{ phase.duration }}天</div>
- </div>
- }
- </div>
- </div>
- </div>
-
- <!-- 方案操作按钮 -->
- <div class="proposal-actions">
- <button class="confirm-btn primary" (click)="confirmProposal()">
- 确认此方案
- </button>
- <button class="adjust-btn secondary" (click)="startMaterialAnalysis()">
- 重新分析
- </button>
- </div>
- </div>
- }
-
- <!-- 需求信息展示区域(原有内容,当没有方案分析时显示) -->
- @if (areRequiredStagesCompleted() && !proposalAnalysis && !isAnalyzing) {
- <div class="info-grid">
- <!-- 色调信息 -->
- @if (requirementKeyInfo.colorAtmosphere.description) {
- <div class="info-item">
- <label>色调</label>
- <span>{{ requirementKeyInfo.colorAtmosphere.description }}</span>
- @if (requirementKeyInfo.colorAtmosphere.mainColor) {
- <div class="color-preview" [style.background-color]="requirementKeyInfo.colorAtmosphere.mainColor"></div>
- }
- </div>
- }
-
- <!-- 材质信息 -->
- @if (requirementKeyInfo.colorAtmosphere.materials && requirementKeyInfo.colorAtmosphere.materials.length > 0) {
- <div class="info-item">
- <label>材质</label>
- <span>
- @for (material of requirementKeyInfo.colorAtmosphere.materials; track material; let isLast = $last) {
- {{ material }}@if (!isLast) {, }
- }
- </span>
- </div>
- }
-
- <!-- 空间结构 -->
- @if (requirementKeyInfo.spaceStructure.aspectRatio > 0) {
- <div class="info-item">
- <label>空间比例</label>
- <span>{{ requirementKeyInfo.spaceStructure.aspectRatio.toFixed(1) }}</span>
- </div>
- }
-
- @if (requirementKeyInfo.spaceStructure.ceilingHeight > 0) {
- <div class="info-item">
- <label>层高</label>
- <span>{{ requirementKeyInfo.spaceStructure.ceilingHeight }}m</span>
- </div>
- }
-
- <!-- 材质权重 -->
- @if (requirementKeyInfo.materialWeights.woodRatio > 0) {
- <div class="info-item">
- <label>木质比例</label>
- <span>{{ requirementKeyInfo.materialWeights.woodRatio }}%</span>
- </div>
- }
-
- @if (requirementKeyInfo.materialWeights.fabricRatio > 0) {
- <div class="info-item">
- <label>布艺比例</label>
- <span>{{ requirementKeyInfo.materialWeights.fabricRatio }}%</span>
- </div>
- }
-
- @if (requirementKeyInfo.materialWeights.metalRatio > 0) {
- <div class="info-item">
- <label>金属比例</label>
- <span>{{ requirementKeyInfo.materialWeights.metalRatio }}%</span>
- </div>
- }
-
- <!-- 预设氛围 -->
- @if (requirementKeyInfo.presetAtmosphere.name) {
- <div class="info-item">
- <label>预设氛围</label>
- <span>{{ requirementKeyInfo.presetAtmosphere.name }}</span>
- </div>
- }
-
- <!-- 订单金额 -->
- <!-- 订单金额 -->
- <div class="info-item">
- <label>订单金额</label>
- <span>¥{{ orderAmount || 0 }}</span>
- </div>
-
- </div>
-
- <!-- 开始分析按钮 -->
- <div class="proposal-actions">
- <button class="confirm-btn" (click)="startMaterialAnalysis()">
- 开始素材解析
- </button>
- </div>
- } @else if (!areRequiredStagesCompleted()) {
- <!-- 等待状态 -->
- <div class="waiting-state">
- <div class="waiting-content">
- <h5>等待需求信息完善</h5>
- <p>需求沟通阶段完成后,方案确认功能将自动开启</p>
- <div class="progress-info">
- <span>当前进度: {{ getRequiredStagesProgress() }}%</span>
- </div>
- </div>
- </div>
- }
- </div>
- </div>
- <!-- 右侧三分之二 - 制作流程进度 -->
- <div class="right-column">
- <!-- 移除顶部四个圆圈,直接展示阶段内容 -->
-
- <!-- 新增:客户信息右侧 2x2 售后模块布局 -->
- @if (expandedSection === 'aftercare') {
- <!-- 上方 2x2 网格:前4个模块 -->
- <div class="aftercare-grid">
-
- <!-- 1/4:尾款结算 -->
- <div class="aftercare-module settlement-module card">
- <div class="module-header">
- <h2>💰 尾款结算</h2>
- <p class="module-description">技术完成验收后自动发起尾款结算流程,支持多种支付方式自动化处理</p>
- </div>
- <div class="settlement-automation">
- <div class="automation-features">
- <div class="feature-card clickable" (click)="showFeatureDetail('自动触发流程', '技术验收完成后,系统会自动创建尾款结算记录,并通知客服跟进。整个流程无需人工干预,提高结算效率。')">
- <div class="feature-icon">🤖</div>
- <div class="feature-content">
- <h4>自动触发流程</h4>
- <p>技术验收完成后自动发起尾款结算申请</p>
- </div>
- </div>
- <div class="feature-card clickable" (click)="showFeatureDetail('自动解密发送', '客户通过小程序完成支付后,系统自动解锁渲染大图,并一键发送给客户,无需手动操作。')">
- <div class="feature-icon">🔓</div>
- <div class="feature-content">
- <h4>自动解密发送</h4>
- <p>小程序支付自动解密并发送大图给客户</p>
- </div>
- </div>
- <div class="feature-card clickable" (click)="showFeatureDetail('凭证智能识别', '上传微信或支付宝支付截图后,AI自动识别支付金额、支付方式和支付时间,提高录入效率。')">
- <div class="feature-icon">📱</div>
- <div class="feature-content">
- <h4>凭证智能识别</h4>
- <p>上传微信/支付宝截图自动提取金额和支付方式</p>
- </div>
- </div>
- <div class="feature-card clickable" (click)="showFeatureDetail('自动通知', '支付完成后,系统自动向客户和相关人员发送通知,告知尾款已到账、大图已解锁,确保信息及时传达。')">
- <div class="feature-icon">🔔</div>
- <div class="feature-content">
- <h4>自动通知</h4>
- <p>支付完成后自动发送"尾款已到账,大图已解锁"通知</p>
- </div>
- </div>
- </div>
- <div class="settlement-status">
- <app-settlement-card [settlements]="settlements"></app-settlement-card>
- </div>
- @if (canEditSection('aftercare') && isTechnicalView()) {
- <div class="automation-actions">
- <button
- class="primary-btn automation-btn"
- (click)="initiateAutoSettlement()"
- [disabled]="isAutoSettling"
- type="button">
- @if (isAutoSettling) {
- <span class="loading-spinner"></span>
- <span>自动化处理中...</span>
- } @else {
- <span>🚀 启动自动化结算</span>
- }
- </button>
- <button class="secondary-btn" (click)="uploadPaymentProof()" type="button">
- <span class="upload-icon">📎</span>
- <span>上传支付凭证</span>
- </button>
- </div>
- }
- </div>
- </div>
-
- <!-- 2/4:全景图合成 -->
- <div class="aftercare-module panoramic-module card">
- <div class="module-header">
- <h2>🖼️ 全景图合成</h2>
- <p class="module-description">集成内部全景图合成服务器,技术上传图片自动生成漫游式全景链接</p>
- </div>
- <div class="panoramic-synthesis">
- <div class="panoramic-features">
- <div class="feature-card clickable" (click)="showFeatureDetail('KR Panel集成', '系统集成了专业的KR Panel全景图合成工具,支持高质量全景图生成,确保最终效果达到行业标准。')">
- <div class="feature-icon">🖥️</div>
- <div class="feature-content">
- <h4>KR Panel集成</h4>
- <p>集成专业全景图合成工具,确保合成质量</p>
- </div>
- </div>
- <div class="feature-card clickable" (click)="showFeatureDetail('智能空间标注', '技术人员上传图片时,系统会智能识别文件名中的空间信息(如「客厅-角度1.jpg」),自动完成空间分类和标注。')">
- <div class="feature-icon">📸</div>
- <div class="feature-content">
- <h4>智能空间标注</h4>
- <p>技术上传图片并标注空间名称(如"客厅-角度1")</p>
- </div>
- </div>
- <div class="feature-card clickable" (click)="showFeatureDetail('自动生成链接', '全景图合成完成后,系统自动生成可分享的漫游链接,客户可以通过链接在线浏览360度全景效果。')">
- <div class="feature-icon">🔗</div>
- <div class="feature-content">
- <h4>自动生成链接</h4>
- <p>自动生成漫游式全景链接并发送给客户</p>
- </div>
- </div>
- </div>
- <div class="recent-syntheses">
- @if (panoramicSyntheses && panoramicSyntheses.length > 0) {
- <div class="panoramic-card-list">
- @for (item of panoramicSyntheses; track item.id) {
- <app-panoramic-synthesis-card
- [synthesis]="item"
- [showActions]="true">
- </app-panoramic-synthesis-card>
- }
- </div>
- } @else {
- <div class="desc">暂无最近合成记录</div>
- }
- </div>
- @if (canEditSection('aftercare')) {
- <div class="panoramic-actions">
- <button class="primary-btn" (click)="startPanoramicSynthesis()" type="button">
- <span>🎨 开始合成</span>
- </button>
- <button class="secondary-btn" (click)="viewPanoramicGallery()" type="button">
- <span>📁 查看全景图库</span>
- </button>
- </div>
- }
- </div>
- </div>
-
- <!-- 3/4:客户评价 -->
- <div class="aftercare-module review-module card">
- <div class="module-header">
- <h2>⭐ 客户评价</h2>
- <p class="module-description">邀请客户进行多维度评价并生成评价链接</p>
- </div>
- <div class="customer-review-enhanced">
- <div class="review-form-container">
- <app-customer-review-form
- [projectName]="project?.name || ''"
- [designerName]="getCurrentDesignerName()"
- (reviewSubmitted)="onReviewSubmitted($event)"
- (reviewSaved)="onReviewSaved($event)">
- </app-customer-review-form>
- </div>
- <div class="review-card-container" style="margin-top:12px;">
- <app-customer-review-card [feedbacks]="feedbacks" [detailedReviews]="detailedReviews"></app-customer-review-card>
- </div>
- @if (canEditSection('aftercare')) {
- <div class="review-actions">
- <button class="primary-btn" (click)="generateReviewLink()" type="button">
- <span>🔗 生成评价链接</span>
- </button>
- <button class="secondary-btn" (click)="confirmCustomerReview()" type="button">
- <span>✅ 确认评价完成</span>
- </button>
- </div>
- }
- </div>
- </div>
-
- <!-- 4/4:投诉处理 -->
- <div class="aftercare-module complaint-module card">
- <div class="module-header">
- <h2>📋 投诉处理</h2>
- <p class="module-description">支持人工创建和关键词自动抓取投诉,提升处理效率</p>
- </div>
- <div class="complaint-management-enhanced">
- <div class="complaint-features">
- <div class="feature-card">
- <div class="feature-icon">👥</div>
- <div class="feature-content">
- <h4>人工创建</h4>
- <p>组长或客服人工创建投诉记录</p>
- </div>
- </div>
- <div class="feature-card">
- <div class="feature-icon">🔍</div>
- <div class="feature-content">
- <h4>关键词抓取</h4>
- <p>自动监测企业微信群关键词(不满意、投诉、退款)</p>
- </div>
- </div>
- <div class="feature-card">
- <div class="feature-icon">🏷️</div>
- <div class="feature-content">
- <h4>智能标注</h4>
- <p>自动标注投诉环节和核心问题</p>
- </div>
- </div>
- <div class="feature-card">
- <div class="feature-icon">📊</div>
- <div class="feature-content">
- <h4>实时更新</h4>
- <p>处理进度实时更新至系统</p>
- </div>
- </div>
- </div>
- <div class="complaint-content">
- <app-complaint-card [complaints]="exceptionHistories"></app-complaint-card>
- </div>
- @if (canEditSection('aftercare')) {
- <div class="complaint-actions">
- <button class="primary-btn" (click)="createComplaintManually()" type="button">
- <span>📝 人工创建投诉</span>
- </button>
- <button class="secondary-btn" (click)="setupKeywordMonitoring()" type="button">
- <span>⚙️ 设置关键词监测</span>
- </button>
- <button class="success-btn" (click)="confirmComplaint()" type="button">
- <span>✅ 确认投诉处理完成</span>
- </button>
- </div>
- }
- </div>
- </div>
- </div>
-
- <!-- 下方横向展示:项目复盘模块 -->
- <div class="project-review-section">
- <div class="review-section-header">
- <div class="header-left">
- <h2 class="section-title">📊 项目复盘</h2>
- <p class="section-subtitle">基于SOP执行数据和经验总结,自动生成复盘报告</p>
- </div>
- <div class="header-right">
- @if (canEditSection('aftercare')) {
- <button class="generate-review-btn" (click)="generateReviewReport()" [disabled]="isGeneratingReview">
- @if (isGeneratingReview) {
- <span class="loading-spinner"></span>
- 生成中...
- } @else {
- 📊 生成复盘报告
- }
- </button>
- @if (projectReview) {
- <button class="export-review-btn" (click)="exportReviewReport()">
- 📤 导出报告
- </button>
- }
- }
- </div>
- </div>
- <!-- Tab切换导航 -->
- <div class="review-tabs">
- <button
- class="review-tab"
- [class.active]="activeReviewTab === 'sop'"
- (click)="activeReviewTab = 'sop'">
- <span class="tab-icon">📈</span>
- <span class="tab-label">SOP执行数据</span>
- </button>
- <button
- class="review-tab"
- [class.active]="activeReviewTab === 'experience'"
- (click)="activeReviewTab = 'experience'">
- <span class="tab-icon">💡</span>
- <span class="tab-label">经验复盘</span>
- </button>
- <button
- class="review-tab"
- [class.active]="activeReviewTab === 'suggestions'"
- (click)="activeReviewTab = 'suggestions'">
- <span class="tab-icon">🔧</span>
- <span class="tab-label">优化建议</span>
- </button>
- </div>
- <!-- Tab内容区域 -->
- <div class="review-content-area">
- <!-- SOP执行数据 -->
- @if (activeReviewTab === 'sop') {
- <div class="sop-data-content">
- <div class="sop-metrics-grid">
- <!-- 关键指标卡片 -->
- <div class="metric-card">
- <div class="metric-header">
- <span class="metric-icon">💬</span>
- <h4 class="metric-title">需求沟通次数</h4>
- </div>
- <div class="metric-value-large">{{ sopMetrics?.communicationCount || 0 }}</div>
- <div class="metric-footer">
- <span class="metric-label">平均水平:</span>
- <span class="metric-benchmark">{{ sopMetrics?.avgCommunication || 5 }}次</span>
- <span class="metric-status" [class.good]="(sopMetrics?.communicationCount || 0) <= (sopMetrics?.avgCommunication || 5)" [class.warning]="(sopMetrics?.communicationCount || 0) > (sopMetrics?.avgCommunication || 5)">
- {{ (sopMetrics?.communicationCount || 0) <= (sopMetrics?.avgCommunication || 5) ? '✓ 良好' : '⚠ 超标' }}
- </span>
- </div>
- </div>
- <div class="metric-card">
- <div class="metric-header">
- <span class="metric-icon">🔄</span>
- <h4 class="metric-title">改图次数</h4>
- </div>
- <div class="metric-value-large">{{ sopMetrics?.revisionCount || 0 }}</div>
- <div class="metric-footer">
- <span class="metric-label">平均水平:</span>
- <span class="metric-benchmark">{{ sopMetrics?.avgRevision || 2 }}次</span>
- <span class="metric-status" [class.good]="(sopMetrics?.revisionCount || 0) <= (sopMetrics?.avgRevision || 2)" [class.warning]="(sopMetrics?.revisionCount || 0) > (sopMetrics?.avgRevision || 2)">
- {{ (sopMetrics?.revisionCount || 0) <= (sopMetrics?.avgRevision || 2) ? '✓ 良好' : '⚠ 超标' }}
- </span>
- </div>
- </div>
- <div class="metric-card">
- <div class="metric-header">
- <span class="metric-icon">⏱️</span>
- <h4 class="metric-title">交付周期</h4>
- </div>
- <div class="metric-value-large">{{ sopMetrics?.deliveryCycle || 0 }}<span class="unit">天</span></div>
- <div class="metric-footer">
- <span class="metric-label">标准周期:</span>
- <span class="metric-benchmark">{{ sopMetrics?.standardCycle || 15 }}天</span>
- <span class="metric-status" [class.good]="(sopMetrics?.deliveryCycle || 0) <= (sopMetrics?.standardCycle || 15)" [class.warning]="(sopMetrics?.deliveryCycle || 0) > (sopMetrics?.standardCycle || 15)">
- {{ (sopMetrics?.deliveryCycle || 0) <= (sopMetrics?.standardCycle || 15) ? '✓ 达标' : '⚠ 延期' }}
- </span>
- </div>
- </div>
- <div class="metric-card">
- <div class="metric-header">
- <span class="metric-icon">⭐</span>
- <h4 class="metric-title">客户满意度</h4>
- </div>
- <div class="metric-value-large">{{ sopMetrics?.customerSatisfaction || 0 }}<span class="unit">/5</span></div>
- <div class="metric-footer">
- <div class="satisfaction-stars">
- @for (star of [1,2,3,4,5]; track star) {
- <span class="star" [class.filled]="star <= (sopMetrics?.customerSatisfaction || 0)">★</span>
- }
- </div>
- </div>
- </div>
- </div>
- <!-- 阶段执行详情 -->
- <div class="sop-stages-section">
- <h3 class="subsection-title">各阶段执行详情</h3>
- <div class="stages-timeline">
- @for (stage of sopStagesData; track stage.name) {
- <div class="stage-timeline-item" [class.completed]="stage.status === 'completed'" [class.ongoing]="stage.status === 'ongoing'" [class.delayed]="stage.isDelayed">
- <div class="stage-indicator">
- <div class="stage-dot"></div>
- @if (!$last) {
- <div class="stage-line"></div>
- }
- </div>
- <div class="stage-content-card">
- <div class="stage-card-header">
- <h4 class="stage-name">{{ stage.name }}</h4>
- <span class="stage-status-badge" [class]="stage.status">
- {{ stage.statusText }}
- </span>
- </div>
- <div class="stage-metrics">
- <div class="stage-metric-item">
- <span class="label">计划时长:</span>
- <span class="value">{{ stage.plannedDuration }}天</span>
- </div>
- <div class="stage-metric-item">
- <span class="label">实际时长:</span>
- <span class="value" [class.warning]="stage.actualDuration > stage.plannedDuration">{{ stage.actualDuration }}天</span>
- </div>
- <div class="stage-metric-item">
- <span class="label">执行评分:</span>
- <span class="value score" [class]="getScoreClass(stage.score)">{{ stage.score }}/100</span>
- </div>
- </div>
- @if (stage.issues && stage.issues.length > 0) {
- <div class="stage-issues">
- <span class="issues-label">⚠️ 问题:</span>
- <span class="issues-text">{{ stage.issues.join('、') }}</span>
- </div>
- }
- </div>
- </div>
- }
- </div>
- </div>
- <!-- 数据图表展示 -->
- <div class="sop-charts-section">
- <h3 class="subsection-title">数据分析图表</h3>
- <div class="charts-grid">
- <div class="chart-card">
- <h4 class="chart-title">阶段耗时对比</h4>
- <div class="chart-placeholder">
- <div class="bar-chart">
- @for (stage of sopStagesData; track stage.name) {
- <div class="bar-group">
- <div class="bar-label">{{ stage.name }}</div>
- <div class="bars">
- <div class="bar planned" [style.height.%]="(stage.plannedDuration / getMaxDuration()) * 100">
- <span class="bar-value">{{ stage.plannedDuration }}</span>
- </div>
- <div class="bar actual" [style.height.%]="(stage.actualDuration / getMaxDuration()) * 100">
- <span class="bar-value">{{ stage.actualDuration }}</span>
- </div>
- </div>
- </div>
- }
- </div>
- <div class="chart-legend">
- <span class="legend-item"><span class="legend-color planned"></span>计划时长</span>
- <span class="legend-item"><span class="legend-color actual"></span>实际时长</span>
- </div>
- </div>
- </div>
- <div class="chart-card">
- <h4 class="chart-title">执行评分雷达图</h4>
- <div class="chart-placeholder radar-chart">
- <div class="radar-info">
- <p>各阶段平均得分:<strong>{{ getAverageScore() }}/100</strong></p>
- <div class="score-items">
- @for (stage of sopStagesData.slice(0, 5); track stage.name) {
- <div class="score-item">
- <span class="stage-label">{{ stage.name }}:</span>
- <span class="stage-score" [class]="getScoreClass(stage.score)">{{ stage.score }}</span>
- </div>
- }
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- }
- <!-- 经验复盘 -->
- @if (activeReviewTab === 'experience') {
- <div class="experience-content">
- <div class="experience-intro">
- <p class="intro-text">基于企业微信沟通记录智能提取关键信息,全面复盘项目过程</p>
- </div>
- <div class="experience-grid">
- <!-- 客户需求 -->
- <div class="experience-card needs-card">
- <div class="card-header-custom">
- <span class="card-icon">📝</span>
- <h3 class="card-title-custom">客户需求</h3>
- <span class="count-badge">{{ experienceData?.customerNeeds?.length || 0 }}项</span>
- </div>
- <div class="card-content-scrollable">
- @if (experienceData?.customerNeeds && experienceData.customerNeeds.length > 0) {
- <ul class="experience-list">
- @for (need of experienceData.customerNeeds; track $index) {
- <li class="experience-item">
- <span class="item-bullet">•</span>
- <div class="item-content">
- <p class="item-text">{{ need.text }}</p>
- <div class="item-meta">
- <span class="meta-time">{{ need.timestamp }}</span>
- <span class="meta-source">来源:{{ need.source }}</span>
- </div>
- </div>
- </li>
- }
- </ul>
- } @else {
- <div class="empty-state-small">暂无客户需求记录</div>
- }
- </div>
- </div>
- <!-- 客户顾虑 -->
- <div class="experience-card concerns-card">
- <div class="card-header-custom">
- <span class="card-icon">🤔</span>
- <h3 class="card-title-custom">客户顾虑</h3>
- <span class="count-badge">{{ experienceData?.customerConcerns?.length || 0 }}项</span>
- </div>
- <div class="card-content-scrollable">
- @if (experienceData?.customerConcerns && experienceData.customerConcerns.length > 0) {
- <ul class="experience-list">
- @for (concern of experienceData.customerConcerns; track $index) {
- <li class="experience-item concern">
- <span class="item-bullet">⚠️</span>
- <div class="item-content">
- <p class="item-text">{{ concern.text }}</p>
- <div class="item-meta">
- <span class="meta-time">{{ concern.timestamp }}</span>
- @if (concern.resolved) {
- <span class="meta-resolved">✓ 已解决</span>
- } @else {
- <span class="meta-unresolved">待处理</span>
- }
- </div>
- </div>
- </li>
- }
- </ul>
- } @else {
- <div class="empty-state-small">暂无客户顾虑记录</div>
- }
- </div>
- </div>
- <!-- 投诉点 -->
- <div class="experience-card complaints-card">
- <div class="card-header-custom">
- <span class="card-icon">📢</span>
- <h3 class="card-title-custom">投诉点</h3>
- <span class="count-badge warning">{{ experienceData?.complaintPoints?.length || 0 }}项</span>
- </div>
- <div class="card-content-scrollable">
- @if (experienceData?.complaintPoints && experienceData.complaintPoints.length > 0) {
- <ul class="experience-list">
- @for (complaint of experienceData.complaintPoints; track $index) {
- <li class="experience-item complaint">
- <span class="item-bullet">❗</span>
- <div class="item-content">
- <p class="item-text">{{ complaint.text }}</p>
- <div class="item-meta">
- <span class="meta-time">{{ complaint.timestamp }}</span>
- <span class="meta-severity" [class]="complaint.severity">{{ complaint.severityText }}</span>
- </div>
- @if (complaint.resolution) {
- <div class="item-resolution">
- <span class="resolution-label">解决方案:</span>
- <span class="resolution-text">{{ complaint.resolution }}</span>
- </div>
- }
- </div>
- </li>
- }
- </ul>
- } @else {
- <div class="empty-state-small">✓ 暂无投诉记录</div>
- }
- </div>
- </div>
- <!-- 项目亮点 -->
- <div class="experience-card highlights-card">
- <div class="card-header-custom">
- <span class="card-icon">✨</span>
- <h3 class="card-title-custom">项目亮点</h3>
- <span class="count-badge success">{{ experienceData?.projectHighlights?.length || 0 }}项</span>
- </div>
- <div class="card-content-scrollable">
- @if (experienceData?.projectHighlights && experienceData.projectHighlights.length > 0) {
- <ul class="experience-list">
- @for (highlight of experienceData.projectHighlights; track $index) {
- <li class="experience-item highlight">
- <span class="item-bullet">⭐</span>
- <div class="item-content">
- <p class="item-text">{{ highlight.text }}</p>
- <div class="item-meta">
- <span class="meta-category">{{ highlight.category }}</span>
- @if (highlight.praised) {
- <span class="meta-praised">👍 客户点赞</span>
- }
- </div>
- </div>
- </li>
- }
- </ul>
- } @else {
- <div class="empty-state-small">暂无项目亮点记录</div>
- }
- </div>
- </div>
- </div>
- <!-- 沟通记录摘要 -->
- <div class="communication-summary">
- <h3 class="subsection-title">关键沟通记录</h3>
- <div class="communication-timeline">
- @if (experienceData?.communications && experienceData.communications.length > 0) {
- @for (comm of experienceData.communications; track $index) {
- <div class="communication-item">
- <div class="comm-time">{{ comm.timestamp }}</div>
- <div class="comm-content">
- <div class="comm-header">
- <span class="comm-participant">{{ comm.participant }}</span>
- <span class="comm-type" [class]="comm.type">{{ comm.typeText }}</span>
- </div>
- <p class="comm-message">{{ comm.message }}</p>
- @if (comm.attachments && comm.attachments.length > 0) {
- <div class="comm-attachments">
- <span class="attachment-icon">📎</span>
- <span class="attachment-count">{{ comm.attachments.length }}个附件</span>
- </div>
- }
- </div>
- </div>
- }
- } @else {
- <div class="empty-state-small">暂无沟通记录</div>
- }
- </div>
- </div>
- </div>
- }
- <!-- 优化建议 -->
- @if (activeReviewTab === 'suggestions') {
- <div class="suggestions-content">
- <div class="suggestions-intro">
- <div class="intro-card">
- <h3 class="intro-title">智能分析与建议</h3>
- <p class="intro-desc">基于项目执行数据和历史经验,为您提供具体可操作的优化建议</p>
- </div>
- </div>
- <div class="suggestions-list">
- @if (optimizationSuggestions && optimizationSuggestions.length > 0) {
- @for (suggestion of optimizationSuggestions; track $index) {
- <div class="suggestion-card" [class]="suggestion.priority">
- <div class="suggestion-header">
- <div class="header-left-content">
- <span class="suggestion-priority-badge" [class]="suggestion.priority">
- {{ suggestion.priorityText }}
- </span>
- <span class="suggestion-category">{{ suggestion.category }}</span>
- </div>
- <div class="header-right-content">
- <span class="suggestion-impact">预期提升:<strong>{{ suggestion.expectedImprovement }}</strong></span>
- </div>
- </div>
-
- <div class="suggestion-body">
- <div class="suggestion-problem">
- <h4 class="problem-title">🔍 问题分析</h4>
- <p class="problem-text">{{ suggestion.problem }}</p>
- <div class="problem-data">
- @for (data of suggestion.dataPoints; track $index) {
- <div class="data-point">
- <span class="data-label">{{ data.label }}:</span>
- <span class="data-value" [class.warning]="data.isWarning">{{ data.value }}</span>
- </div>
- }
- </div>
- </div>
- <div class="suggestion-solution">
- <h4 class="solution-title">💡 优化建议</h4>
- <p class="solution-text">{{ suggestion.solution }}</p>
- </div>
- <div class="suggestion-actions-plan">
- <h4 class="actions-title">📋 行动计划</h4>
- <ul class="actions-list">
- @for (action of suggestion.actionPlan; track $index) {
- <li class="action-item">
- <span class="action-step">{{ $index + 1 }}.</span>
- <span class="action-text">{{ action }}</span>
- </li>
- }
- </ul>
- </div>
- @if (suggestion.references && suggestion.references.length > 0) {
- <div class="suggestion-references">
- <span class="references-label">📚 参考案例:</span>
- @for (ref of suggestion.references; track $index) {
- <span class="reference-tag">{{ ref }}</span>
- }
- </div>
- }
- </div>
- <div class="suggestion-footer">
- <button class="action-btn accept" (click)="acceptSuggestion(suggestion)">
- ✓ 采纳建议
- </button>
- <button class="action-btn detail" (click)="viewSuggestionDetail(suggestion)">
- 查看详情
- </button>
- </div>
- </div>
- }
- } @else {
- <div class="empty-state-large">
- <div class="empty-icon">📊</div>
- <h3 class="empty-title">暂无优化建议</h3>
- <p class="empty-desc">项目数据收集完成后将自动生成智能优化建议</p>
- </div>
- }
- </div>
- <!-- 建议统计 -->
- @if (optimizationSuggestions && optimizationSuggestions.length > 0) {
- <div class="suggestions-stats">
- <h3 class="subsection-title">建议统计</h3>
- <div class="stats-grid">
- <div class="stat-card">
- <div class="stat-icon">🔴</div>
- <div class="stat-content">
- <div class="stat-value">{{ getSuggestionCountByPriority('high') }}</div>
- <div class="stat-label">高优先级</div>
- </div>
- </div>
- <div class="stat-card">
- <div class="stat-icon">🟡</div>
- <div class="stat-content">
- <div class="stat-value">{{ getSuggestionCountByPriority('medium') }}</div>
- <div class="stat-label">中优先级</div>
- </div>
- </div>
- <div class="stat-card">
- <div class="stat-icon">🟢</div>
- <div class="stat-content">
- <div class="stat-value">{{ getSuggestionCountByPriority('low') }}</div>
- <div class="stat-label">低优先级</div>
- </div>
- </div>
- <div class="stat-card">
- <div class="stat-icon">📈</div>
- <div class="stat-content">
- <div class="stat-value">{{ getAverageImprovementPercent() }}%</div>
- <div class="stat-label">平均预期提升</div>
- </div>
- </div>
- </div>
- </div>
- }
- </div>
- }
- </div>
- </div>
- }
-
- <!-- 串式流程:10个阶段横向排列(保持) -->
- <div class="stage-progress-container">
- @for (stage of getVisibleStages(); track stage) {
- <div class="vertical-stage-block" [attr.id]="stageToAnchor(stage)" [class.active]="getStageStatus(stage) === 'active'">
- @if (stage !== '订单创建') {
- <div class="vertical-stage-header">
- <span class="dot" [class.completed]="getStageStatus(stage) === 'completed'" [class.active]="getStageStatus(stage) === 'active'"></span>
- <h3>{{ stage === '需求沟通' ? '需求映射' : (stage === '方案确认' ? '色彩分析报告' : stage) }}</h3>
- </div>
- }
- <!-- 直接复用原阶段内容卡片:按stage匹配显示 -->
- <div class="vertical-stage-body">
- @if (stage === '订单创建') {
- <!-- 重构后的订单创建表单 - 组合order-creation-extra和consultation-order-panel -->
- <div class="order-creation-form-container">
- <!-- 订单分配头部 -->
- <div class="order-assignment-header">
- <h2 class="assignment-title">订单分配</h2>
- <div class="assignment-actions">
- <button
- class="create-order-btn"
- (click)="createOrder()"
- [disabled]="!canCreateOrder()"
- [class.disabled]="!canCreateOrder()"
- >
- <span>创建订单</span>
- </button>
- </div>
- </div>
- <!-- 可滚动的表单内容区域 -->
- <div class="scrollable-form-content">
- <!-- 客户信息显示(如果已同步) -->
- @if (orderCreationData?.customerInfo) {
- <section class="synced-customer-info">
- <h3 class="form-title">客户信息(已同步)</h3>
- <div class="customer-info-display">
- <div class="info-row">
- <div class="info-item">
- <label>客户姓名:</label>
- <span>{{ orderCreationData.customerInfo.name || '未填写' }}</span>
- </div>
- <div class="info-item">
- <label>联系电话:</label>
- <span>{{ orderCreationData.customerInfo.phone || '未填写' }}</span>
- </div>
- </div>
- <div class="info-row">
- <div class="info-item">
- <label>微信号:</label>
- <span>{{ orderCreationData.customerInfo.wechat || '未填写' }}</span>
- </div>
- <div class="info-item">
- <label>客户类型:</label>
- <span>{{ orderCreationData.customerInfo.customerType || '未填写' }}</span>
- </div>
- </div>
- </div>
- </section>
- }
- <!-- 核心必填信息表单 -->
- <section class="core-requirements-form">
- <h3 class="form-title">核心信息 <span class="required-note">(带 * 为必填项)</span></h3>
- <form [formGroup]="orderCreationForm" class="order-form">
- <!-- 第一行:订单金额、小图交付时间 -->
- <div class="form-row">
- <div class="form-field">
- <label for="orderAmount" class="field-label">订单金额 <span class="required">*</span></label>
- <div class="input-with-unit">
- <input
- type="number"
- id="orderAmount"
- formControlName="orderAmount"
- placeholder="请输入订单总金额"
- class="field-input"
- min="0"
- step="0.01">
- <span class="input-unit">元</span>
- </div>
- @if (orderCreationForm.get('orderAmount')?.invalid && orderCreationForm.get('orderAmount')?.touched) {
- <div class="field-error">订单金额为必填项</div>
- }
- <div class="field-hint">报价明细需拆分至具体空间,便于后续分工</div>
- </div>
-
- <div class="form-field">
- <label for="smallImageDeliveryTime" class="field-label">小图交付时间 <span class="required">*</span></label>
- <input
- id="smallImageDeliveryTime"
- type="date"
- formControlName="smallImageDeliveryTime"
- class="field-input"
- />
- @if (orderCreationForm.get('smallImageDeliveryTime')?.invalid && orderCreationForm.get('smallImageDeliveryTime')?.touched) {
- <div class="field-error">小图交付时间为必填项</div>
- }
- <div class="field-hint">确保时间安排合理,便于设计师规划工作</div>
- </div>
- </div>
- <!-- 第二行:装修类型、需求原因 -->
- <div class="form-row">
- <div class="form-field">
- <label for="decorationType" class="field-label">装修类型 <span class="required">*</span></label>
- <select id="decorationType" formControlName="decorationType" class="field-select">
- <option value="">请选择装修类型</option>
- <option value="家装">家装</option>
- <option value="工装">工装</option>
- <option value="软装">软装</option>
- <option value="局部改造">局部改造</option>
- </select>
- @if (orderCreationForm.get('decorationType')?.invalid && orderCreationForm.get('decorationType')?.touched) {
- <div class="field-error">装修类型为必填项</div>
- }
- <div class="field-hint">选择合适的装修类型有助于匹配专业设计师</div>
- </div>
- <div class="form-field">
- <label for="requirementReason" class="field-label">需求原因 <span class="required">*</span></label>
- <textarea
- id="requirementReason"
- formControlName="requirementReason"
- class="field-textarea"
- placeholder="请详细描述装修需求的原因和背景"
- rows="3">
- </textarea>
- @if (orderCreationForm.get('requirementReason')?.invalid && orderCreationForm.get('requirementReason')?.touched) {
- <div class="field-error">需求原因为必填项</div>
- }
- <div class="field-hint">详细的需求背景有助于设计师理解客户期望</div>
- </div>
- </div>
- <!-- 第三行:多设计师标记 -->
- <div class="form-row">
- <div class="form-field full-width">
- <label class="field-label">多设计师标记 <span class="required">*</span></label>
- <div class="checkbox-group">
- <label class="checkbox-item">
- <input
- type="checkbox"
- formControlName="isMultiDesigner"
- class="checkbox-input">
- <span class="checkbox-label">需要多个设计师协作</span>
- </label>
- </div>
- @if (orderCreationForm.get('isMultiDesigner')?.invalid && orderCreationForm.get('isMultiDesigner')?.touched) {
- <div class="field-error">请确认是否需要多设计师协作</div>
- }
- <div class="field-hint">复杂项目建议启用多设计师协作模式</div>
- </div>
- </div>
- </form>
- </section>
- <!-- 报价明细组件 -->
- <section class="quotation-section">
- <h3 class="form-title">报价明细</h3>
- <app-quotation-details
- [initialData]="quotationData"
- (dataChange)="onQuotationDataChange($event)"
- ></app-quotation-details>
- </section>
- <!-- 设计师分配组件 -->
- <section class="designer-assignment-section">
- <h3 class="form-title">设计师分配</h3>
- <div class="designer-assignment-container">
- <app-designer-assignment
- [quotationItems]="quotationData.items"
- [initialAssignment]="designerAssignmentData"
- (assignmentChange)="onDesignerAssignmentChange($event)"
- (designerClick)="onDesignerClick($event)"
- ></app-designer-assignment>
- </div>
- </section>
- <!-- 可选信息表单 -->
- <section class="optional-requirements-form">
- <div class="card-header" (click)="isOptionalFormExpanded = !isOptionalFormExpanded">
- <h3 class="card-title">其他信息 <span class="optional-note">(选填)</span></h3>
- <span class="toggle-icon" [class.expanded]="isOptionalFormExpanded">▼</span>
- </div>
-
- @if (isOptionalFormExpanded) {
- <div class="card-content">
- <form [formGroup]="optionalForm" class="optional-form">
- <!-- 大图交付时间 -->
- <div class="form-row">
- <div class="form-field">
- <label for="largeImageDeliveryTime" class="field-label">大图交付时间</label>
- <input
- id="largeImageDeliveryTime"
- type="date"
- formControlName="largeImageDeliveryTime"
- class="field-input"
- />
- </div>
- </div>
- <!-- 详细需求 -->
- <div class="form-row">
- <div class="form-field full-width">
- <label for="spaceRequirements" class="field-label">涉及空间</label>
- <textarea
- id="spaceRequirements"
- formControlName="spaceRequirements"
- rows="3"
- class="field-textarea"
- placeholder="请描述涉及的空间,如:客厅、卧室、厨房等"
- ></textarea>
- </div>
- </div>
- <div class="form-row">
- <div class="form-field full-width">
- <label for="designAngles" class="field-label">设计角度</label>
- <textarea
- id="designAngles"
- formControlName="designAngles"
- rows="3"
- class="field-textarea"
- placeholder="请明确各个空间的展示角度"
- ></textarea>
- </div>
- </div>
- <div class="form-row">
- <div class="form-field full-width">
- <label for="specialAreaHandling" class="field-label">特殊区域处理</label>
- <textarea
- id="specialAreaHandling"
- formControlName="specialAreaHandling"
- rows="3"
- class="field-textarea"
- placeholder="请描述特殊区域的处理要求"
- ></textarea>
- </div>
- </div>
- <div class="form-row">
- <div class="form-field full-width">
- <label for="materialRequirements" class="field-label">材质要求</label>
- <textarea
- id="materialRequirements"
- formControlName="materialRequirements"
- rows="3"
- class="field-textarea"
- placeholder="请描述对材质的具体要求"
- ></textarea>
- </div>
- </div>
- <div class="form-row">
- <div class="form-field full-width">
- <label for="lightingRequirements" class="field-label">灯光要求</label>
- <textarea
- id="lightingRequirements"
- formControlName="lightingRequirements"
- rows="3"
- class="field-textarea"
- placeholder="请描述对灯光的具体要求"
- ></textarea>
- </div>
- </div>
- </form>
- </div>
- }
- </section>
- </div>
- </div>
- } @else if (stage === '需求沟通') {
- <!-- 需求沟通阶段:确认需求组件 -->
- <app-requirements-confirm-card
- (requirementConfirmed)="syncRequirementKeyInfo($event)"
- (progressUpdated)="syncRequirementKeyInfo($event)"
- (stageCompleted)="onRequirementsStageCompleted($event)"
- (dataUpdated)="onRequirementDataUpdated($event)"
- (mappingDataUpdated)="onMappingDataUpdated($event)"
- (uploadModalRequested)="onUploadModalRequested($event)">
- </app-requirements-confirm-card>
-
- } @else if (stage === '方案确认') {
- <!-- 需求映射面板(替换原色彩分析报告区域) -->
- <div class="requirement-mapping-panel" style="width:100%; display:flex; flex-direction:column; gap:14px;">
- <h3 class="panel-title" style="margin:0 0 16px 0; font-size:18px; font-weight:700; color:#495057;">🎯 需求映射</h3>
-
- <div class="mapping-progress" style="background:#f8f9fa; border-radius:8px; padding:16px; margin-bottom:12px;">
- @if (mappingUploadedFiles.length > 0) {
- <div class="progress-badge" style="display:inline-block; padding:6px 14px; background:linear-gradient(135deg, #667eea 0%, #764ba2 100%); color:white; border-radius:14px; font-size:12px; font-weight:600; margin-bottom:12px;">
- 📸 已同步 {{ mappingUploadedFiles.length }} 张参考图片
- </div>
- }
-
- <div class="steps-list" style="display:flex; flex-direction:column; gap:10px;">
- <div class="step-item" style="display:flex; align-items:center; gap:10px;">
- <span class="step-index" style="width:18px;height:18px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;background:#e9ecef;color:#495057;font-size:12px;">1</span>
- <span class="step-title" style="flex:1;font-size:14px;">图片上传</span>
- <span class="step-status" style="font-size:12px;color:#666;">@if (mappingUploadedFiles.length > 0) { ✅ 完成 } @else { ⭕ 待上传 }</span>
- </div>
- <div class="step-item" style="display:flex; align-items:center; gap:10px;">
- <span class="step-index" style="width:18px;height:18px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;background:#e9ecef;color:#495057;font-size:12px;">2</span>
- <span class="step-title" style="flex:1;font-size:14px;">图片分析</span>
- <span class="step-status" style="font-size:12px;color:#666;">@if (mappingIsAnalyzing) { ⏳ 进行中 } @else if (mappingAnalysisResult) { ✅ 完成 } @else { ⭕ 待开始 }</span>
- </div>
- <div class="step-item" style="display:flex; align-items:center; gap:10px;">
- <span class="step-index" style="width:18px;height:18px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;background:#e9ecef;color:#495057;font-size:12px;">3</span>
- <span class="step-title" style="flex:1;font-size:14px;">需求映射</span>
- <span class="step-status" style="font-size:12px;color:#666;">@if (mappingIsGeneratingMapping) { ⏳ 生成中 } @else if (mappingRequirementMapping) { ✅ 完成 } @else { ⭕ 待生成 }</span>
- </div>
- </div>
- </div>
-
- @if (mappingAnalysisResult) {
- <div class="analysis-summary-panel" style="background:#f8f9fa;border-radius:8px;padding:16px;">
- <h4 style="margin:0 0 12px 0; font-size:15px; font-weight:600; color:#333;">图片分析摘要</h4>
- <div class="param-items" style="display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:10px;">
- <div class="param-row" style="font-size:13px;"><span style="color:#868e96;">主色</span><span style="font-weight:500;">{{ mappingAnalysisResult.primaryColor?.hex || '未知' }}</span></div>
- <div class="param-row" style="font-size:13px;"><span style="color:#868e96;">材质</span><span style="font-weight:500;">{{ getMaterialName(mappingAnalysisResult.materialType) }}</span></div>
- <div class="param-row" style="font-size:13px;"><span style="color:#868e96;">灯光</span><span style="font-weight:500;">{{ getLightingMoodName(mappingAnalysisResult.lightingMood) }}</span></div>
- <div class="param-row" style="font-size:13px;"><span style="color:#868e96;">对比</span><span style="font-weight:500;">{{ mappingAnalysisResult.contrast || '未知' }}</span></div>
- </div>
- </div>
- }
-
- @if (mappingRequirementMapping) {
- <div class="mapping-result-panel" style="background:#f8f9fa;border-radius:8px;padding:16px;">
- <h4 style="margin:0 0 12px 0; font-size:15px; font-weight:600; color:#333;">需求映射结果</h4>
- @if (mappingRequirementMapping.color) {
- <div class="param-section" style="margin-bottom:12px;">
- <div class="section-title" style="font-weight:600;color:#555;margin-bottom:8px;font-size:13px;">色彩参数</div>
- <div class="param-items" style="display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:10px;">
- <div class="param-row" style="font-size:13px;"><span style="color:#868e96;">和谐度</span><span style="font-weight:500;">{{ getColorHarmonyName(mappingRequirementMapping.color?.harmony) }}</span></div>
- <div class="param-row" style="font-size:13px;"><span style="color:#868e96;">色温</span><span style="font-weight:500;">{{ getTemperatureName(mappingRequirementMapping.color?.temperature) }}</span></div>
- </div>
- </div>
- }
- @if (mappingRequirementMapping.space) {
- <div class="param-section">
- <div class="section-title" style="font-weight:600;color:#555;margin-bottom:8px;font-size:13px;">空间参数</div>
- <div class="param-items" style="display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:10px;">
- <div class="param-row" style="font-size:13px;"><span style="color:#868e96;">布局</span><span style="font-weight:500;">{{ getLayoutTypeName(mappingRequirementMapping.space?.layoutType) }}</span></div>
- <div class="param-row" style="font-size:13px;"><span style="color:#868e96;">流线</span><span style="font-weight:500;">{{ getFlowTypeName(mappingRequirementMapping.space?.flowType) }}</span></div>
- </div>
- </div>
- }
- </div>
- }
-
- @if (mappingIsAnalyzing) {
- <div class="analyzing-indicator" style="display:flex; align-items:center; gap:10px; background:#f8f9fa; padding:12px; border-radius:8px;">
- <div class="spinner" style="width:16px;height:16px;border:2px solid #e5e7eb;border-top-color:#3b82f6;border-radius:50%;animation:spin 1s linear infinite;"></div>
- <span style="font-size:14px; color:#666;">正在解析图片...</span>
- </div>
- }
-
- @if (mappingIsGeneratingMapping) {
- <div class="analyzing-indicator" style="display:flex; align-items:center; gap:10px; background:#f8f9fa; padding:12px; border-radius:8px;">
- <div class="spinner" style="width:16px;height:16px;border:2px solid #e5e7eb;border-top-color:#10b981;border-radius:50%;animation:spin 1s linear infinite;"></div>
- <span style="font-size:14px; color:#666;">正在生成需求映射...</span>
- </div>
- }
-
- @if (!mappingAnalysisResult && !mappingIsAnalyzing && mappingUploadedFiles.length === 0) {
- <div class="empty-state" style="text-align:center; padding:32px; color:#999;">
- <div style="font-size:48px; margin-bottom:12px;">📊</div>
- <div style="font-size:14px;">在需求沟通中上传图片后,这里将实时显示需求映射结果</div>
- </div>
- }
- </div>
-
- } @else if (stage === '建模') {
- <!-- 建模阶段:直接显示建模相关内容 -->
- <div class="modeling-stage-panel">
- <!-- 空间列表 -->
- <div class="space-list-container">
- <div class="space-list-header">
- <h4>空间列表</h4>
- @if (canEditSection('delivery')) {
- <button class="add-space-btn"
- (click)="showAddSpaceInput['modeling'] = true">
- <span class="add-icon">+</span>
- <span>添加空间</span>
- </button>
- }
- </div>
- <!-- 添加空间输入框 -->
- @if (showAddSpaceInput['modeling']) {
- <div class="add-space-input-container">
- <input type="text"
- class="space-name-input"
- placeholder="请输入空间名称(如:卧室、餐厅、厨房)"
- [(ngModel)]="newSpaceName['modeling']"
- (keyup.enter)="addSpace('modeling')"
- #spaceInput>
- <div class="input-actions">
- <button class="confirm-btn" (click)="addSpace('modeling')">确认</button>
- <button class="cancel-btn" (click)="cancelAddSpace('modeling')">取消</button>
- </div>
- </div>
- }
- <!-- 空间卡片列表 -->
- <div class="space-cards">
- @for (space of getActiveProcessSpaces('modeling'); track space.id) {
- <div class="space-card" [class.expanded]="space.isExpanded">
- <div class="space-header" (click)="toggleSpace('modeling', space.id)">
- <div class="space-info">
- <span class="space-name">{{ space.name }}</span>
- <span class="space-progress">{{ getSpaceProgress('modeling', space.id) }}%</span>
- </div>
- <div class="space-actions">
- @if (canEditSection('delivery')) {
- <button class="delete-space-btn"
- (click)="$event.stopPropagation(); removeSpace('modeling', space.id)"
- title="删除空间">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
- <polyline points="3,6 5,6 21,6"></polyline>
- <path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
- </svg>
- </button>
- }
- <span class="expand-arrow" [class.expanded]="space.isExpanded">▼</span>
- </div>
- </div>
- @if (space.isExpanded) {
- <div class="space-content">
- <!-- 图片上传区域 -->
- <div class="upload-section">
- @if (canEditSection('delivery')) {
- <div class="upload-dropzone"
- (click)="triggerSpaceFileInput('modeling', space.id)"
- (dragover)="onDragOver($event)"
- (dragleave)="onDragLeave($event)"
- (drop)="onSpaceFileDrop($event, 'modeling', space.id)"
- [class.drag-over]="isDragOver">
- @if (getSpaceImages('modeling', space.id).length === 0) {
- <div class="upload-placeholder">
- <div class="upload-text">点击上传或拖拽文件到此处</div>
- <div class="upload-hint">
- 支持 JPG、PNG 格式,单个文件最大 10MB
- </div>
- </div>
- } @else {
- <!-- 确认上传按钮 -->
- @if (canEditSection('delivery') && getSpaceImages('modeling', space.id).length > 0) {
- <div class="confirm-upload-section">
- <button class="confirm-upload-btn"
- (click)="$event.stopPropagation(); confirmStageUpload('modeling')"
- [disabled]="!canConfirmStageUpload('modeling')">
- <span>确认上传</span>
- </button>
- </div>
- }
- <div class="uploaded-images-grid">
- @for (img of getSpaceImages('modeling', space.id); track img.id) {
- <div class="uploaded-image-item" (click)="previewImage(img)">
- <img [src]="img.url" [alt]="img.name" />
- <div class="image-overlay">
- <div class="image-name">{{ img.name }}</div>
- <div class="image-actions">
- <button class="preview-btn" (click)="$event.stopPropagation(); previewImage(img)">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
- <circle cx="11" cy="11" r="8"></circle>
- <path d="m21 21-4.35-4.35"></path>
- </svg>
- </button>
- @if (canEditSection('delivery')) {
- <button class="delete-btn" (click)="$event.stopPropagation(); removeSpaceImage('modeling', space.id, img.id)">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
- <polyline points="3,6 5,6 21,6"></polyline>
- <path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
- </svg>
- </button>
- }
- </div>
- </div>
- </div>
- }
- <!-- 添加更多图片按钮 -->
- @if (canEditSection('delivery')) {
- <div class="add-more-images-btn" (click)="$event.stopPropagation(); triggerSpaceFileInput('modeling', space.id)">
- <div class="add-icon">+</div>
- <div class="add-text">添加更多</div>
- </div>
- }
- </div>
- }
- </div>
- } @else {
- <div class="readonly-images">
- @if (getSpaceImages('modeling', space.id).length > 0) {
- <div class="uploaded-images-grid">
- @for (img of getSpaceImages('modeling', space.id); track img.id) {
- <div class="uploaded-image-item" (click)="previewImage(img)">
- <img [src]="img.url" [alt]="img.name" />
- <div class="image-overlay">
- <div class="image-name">{{ img.name }}</div>
- </div>
- </div>
- }
- </div>
- } @else {
- <div class="empty-tip">暂无上传的图片</div>
- }
- </div>
- }
- </div>
- <!-- 备注区域 -->
- <div class="notes-section">
- <label class="notes-label">备注</label>
- @if (canEditSection('delivery')) {
- <textarea #modelingNotes
- class="notes-textarea"
- placeholder="请输入备注信息..."
- [value]="getSpaceNotes('modeling', space.id)"
- (blur)="updateSpaceNotes('modeling', space.id, modelingNotes.value || '')">
- </textarea>
- } @else {
- <div class="notes-readonly">
- {{ getSpaceNotes('modeling', space.id) || '暂无备注' }}
- </div>
- }
- </div>
- </div>
- }
- </div>
- }
- </div>
- </div>
- </div>
- } @else if (stage === '软装') {
- <!-- 软装阶段:直接显示软装相关内容 -->
- <div class="soft-decor-stage-panel">
- <!-- 空间列表 -->
- <div class="space-list-container">
- <div class="space-list-header">
- <h4>空间列表</h4>
- @if (canEditSection('delivery')) {
- <button class="add-space-btn"
- (click)="showAddSpaceInput['softDecor'] = true">
- <span class="add-icon">+</span>
- <span>添加空间</span>
- </button>
- }
- </div>
- <!-- 添加空间输入框 -->
- @if (showAddSpaceInput['softDecor']) {
- <div class="add-space-input-container">
- <input type="text"
- class="space-name-input"
- placeholder="请输入空间名称(如:卧室、餐厅、厨房)"
- [(ngModel)]="newSpaceName['softDecor']"
- (keyup.enter)="addSpace('softDecor')"
- #spaceInput>
- <div class="input-actions">
- <button class="confirm-btn" (click)="addSpace('softDecor')">确认</button>
- <button class="cancel-btn" (click)="cancelAddSpace('softDecor')">取消</button>
- </div>
- </div>
- }
- <!-- 空间卡片列表 -->
- <div class="space-cards">
- @for (space of getActiveProcessSpaces('softDecor'); track space.id) {
- <div class="space-card" [class.expanded]="space.isExpanded">
- <div class="space-header" (click)="toggleSpace('softDecor', space.id)">
- <div class="space-info">
- <span class="space-name">{{ space.name }}</span>
- <span class="space-progress">{{ getSpaceProgress('softDecor', space.id) }}%</span>
- </div>
- <div class="space-actions">
- @if (canEditSection('delivery')) {
- <button class="delete-space-btn"
- (click)="$event.stopPropagation(); removeSpace('softDecor', space.id)"
- title="删除空间">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
- <polyline points="3,6 5,6 21,6"></polyline>
- <path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
- </svg>
- </button>
- }
- <span class="expand-arrow" [class.expanded]="space.isExpanded">▼</span>
- </div>
- </div>
- @if (space.isExpanded) {
- <div class="space-content">
- <!-- 图片上传区域 -->
- <div class="upload-section">
- @if (canEditSection('delivery')) {
- <div class="upload-dropzone"
- (click)="triggerSpaceFileInput('softDecor', space.id)"
- (dragover)="onDragOver($event)"
- (dragleave)="onDragLeave($event)"
- (drop)="onSpaceFileDrop($event, 'softDecor', space.id)"
- [class.drag-over]="isDragOver">
- @if (getSpaceImages('softDecor', space.id).length === 0) {
- <div class="upload-placeholder">
- <div class="upload-text">点击上传或拖拽文件到此处</div>
- <div class="upload-hint">
- 建议 ≤1MB 的 JPG/PNG 小图
- </div>
- </div>
- } @else {
- <!-- 确认上传按钮 -->
- @if (canEditSection('delivery') && getSpaceImages('softDecor', space.id).length > 0) {
- <div class="confirm-upload-section">
- <button class="confirm-upload-btn"
- (click)="$event.stopPropagation(); confirmStageUpload('softDecor')"
- [disabled]="!canConfirmStageUpload('softDecor')">
- <span>确认上传</span>
- </button>
- </div>
- }
- <div class="uploaded-images-grid">
- @for (img of getSpaceImages('softDecor', space.id); track img.id) {
- <div class="uploaded-image-item" (click)="previewImage(img)">
- <img [src]="img.url" [alt]="img.name" />
- <div class="image-overlay">
- <div class="image-name">{{ img.name }}</div>
- <div class="image-actions">
- <button class="preview-btn" (click)="$event.stopPropagation(); previewImage(img)">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
- <circle cx="11" cy="11" r="8"></circle>
- <path d="m21 21-4.35-4.35"></path>
- </svg>
- </button>
- @if (canEditSection('delivery')) {
- <button class="delete-btn" (click)="$event.stopPropagation(); removeSpaceImage('softDecor', space.id, img.id)">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
- <polyline points="3,6 5,6 21,6"></polyline>
- <path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
- </svg>
- </button>
- }
- </div>
- </div>
- </div>
- }
- <!-- 添加更多图片按钮 -->
- @if (canEditSection('delivery')) {
- <div class="add-more-images-btn" (click)="$event.stopPropagation(); triggerSpaceFileInput('softDecor', space.id)">
- <div class="add-icon">+</div>
- <div class="add-text">添加更多</div>
- </div>
- }
- </div>
- }
- </div>
- } @else {
- <div class="readonly-images">
- @if (getSpaceImages('softDecor', space.id).length > 0) {
- <div class="uploaded-images-grid">
- @for (img of getSpaceImages('softDecor', space.id); track img.id) {
- <div class="uploaded-image-item" (click)="previewImage(img)">
- <img [src]="img.url" [alt]="img.name" />
- <div class="image-overlay">
- <div class="image-name">{{ img.name }}</div>
- </div>
- </div>
- }
- </div>
- } @else {
- <div class="empty-tip">暂无上传的图片</div>
- }
- </div>
- }
- </div>
- <!-- 备注区域 -->
- <div class="notes-section">
- <label class="notes-label">备注</label>
- @if (canEditSection('delivery')) {
- <textarea #softDecorNotes
- class="notes-textarea"
- placeholder="请输入备注信息..."
- [value]="getSpaceNotes('softDecor', space.id)"
- (blur)="updateSpaceNotes('softDecor', space.id, softDecorNotes.value || '')">
- </textarea>
- } @else {
- <div class="notes-readonly">
- {{ getSpaceNotes('softDecor', space.id) || '暂无备注' }}
- </div>
- }
- </div>
- </div>
- }
- </div>
- }
- </div>
- </div>
- </div>
- } @else if (stage === '渲染') {
- <!-- 渲染阶段:直接显示渲染相关内容 -->
- <div class="rendering-stage-panel">
- <!-- 空间列表 -->
- <div class="space-list-container">
- <div class="space-list-header">
- <h4>空间列表</h4>
- @if (canEditSection('delivery')) {
- <button class="add-space-btn"
- (click)="showAddSpaceInput['rendering'] = true">
- <span class="add-icon">+</span>
- <span>添加空间</span>
- </button>
- }
- </div>
- <!-- 添加空间输入框 -->
- @if (showAddSpaceInput['rendering']) {
- <div class="add-space-input-container">
- <input type="text"
- class="space-name-input"
- placeholder="请输入空间名称(如:卧室、餐厅、厨房)"
- [(ngModel)]="newSpaceName['rendering']"
- (keyup.enter)="addSpace('rendering')"
- #spaceInput>
- <div class="input-actions">
- <button class="confirm-btn" (click)="addSpace('rendering')">确认</button>
- <button class="cancel-btn" (click)="cancelAddSpace('rendering')">取消</button>
- </div>
- </div>
- }
- <!-- 空间卡片列表 -->
- <div class="space-cards">
- @for (space of getActiveProcessSpaces('rendering'); track space.id) {
- <div class="space-card" [class.expanded]="space.isExpanded">
- <div class="space-header" (click)="toggleSpace('rendering', space.id)">
- <div class="space-info">
- <span class="space-name">{{ space.name }}</span>
- <span class="space-progress">{{ getSpaceProgress('rendering', space.id) }}%</span>
- </div>
- <div class="space-actions">
- @if (canEditSection('delivery')) {
- <button class="delete-space-btn"
- (click)="$event.stopPropagation(); removeSpace('rendering', space.id)"
- title="删除空间">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
- <polyline points="3,6 5,6 21,6"></polyline>
- <path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
- </svg>
- </button>
- }
- <span class="expand-arrow" [class.expanded]="space.isExpanded">▼</span>
- </div>
- </div>
- @if (space.isExpanded) {
- <div class="space-content">
- <!-- 图片上传区域 -->
- <div class="upload-section">
- @if (canEditSection('delivery')) {
- <div class="upload-dropzone"
- (click)="triggerSpaceFileInput('rendering', space.id)"
- (dragover)="onDragOver($event)"
- (dragleave)="onDragLeave($event)"
- (drop)="onSpaceFileDrop($event, 'rendering', space.id)"
- [class.drag-over]="isDragOver">
- @if (getSpaceImages('rendering', space.id).length === 0) {
- <div class="upload-placeholder">
- <div class="upload-text">点击上传或拖拽文件到此处</div>
- <div class="upload-hint">
- 需满足4K标准(最长边 ≥ 4000px)
- </div>
- </div>
- } @else {
- <!-- 确认上传按钮 -->
- @if (canEditSection('delivery') && getSpaceImages('rendering', space.id).length > 0) {
- <div class="confirm-upload-section">
- <button class="confirm-upload-btn"
- (click)="$event.stopPropagation(); confirmStageUpload('rendering')"
- [disabled]="!canConfirmStageUpload('rendering')">
- <span>确认上传</span>
- </button>
- </div>
- }
- <div class="uploaded-images-grid">
- @for (img of getSpaceImages('rendering', space.id); track img.id) {
- <div class="uploaded-image-item" (click)="previewImage(img)">
- <img [src]="img.url" [alt]="img.name" />
- <div class="image-overlay">
- <div class="image-name">{{ img.name }}</div>
- <div class="image-actions">
- <button class="preview-btn" (click)="$event.stopPropagation(); previewImage(img)">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
- <circle cx="11" cy="11" r="8"></circle>
- <path d="m21 21-4.35-4.35"></path>
- </svg>
- </button>
- @if (canEditSection('delivery')) {
- <button class="delete-btn" (click)="$event.stopPropagation(); removeSpaceImage('rendering', space.id, img.id)">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
- <polyline points="3,6 5,6 21,6"></polyline>
- <path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
- </svg>
- </button>
- }
- </div>
- </div>
- </div>
- }
- <!-- 添加更多图片按钮 -->
- @if (canEditSection('delivery')) {
- <div class="add-more-images-btn" (click)="$event.stopPropagation(); triggerSpaceFileInput('rendering', space.id)">
- <div class="add-icon">+</div>
- <div class="add-text">添加更多</div>
- </div>
- }
- </div>
- }
- </div>
- } @else {
- <div class="readonly-images">
- @if (getSpaceImages('rendering', space.id).length > 0) {
- <div class="uploaded-images-grid">
- @for (img of getSpaceImages('rendering', space.id); track img.id) {
- <div class="uploaded-image-item" (click)="previewImage(img)">
- <img [src]="img.url" [alt]="img.name" />
- <div class="image-overlay">
- <div class="image-name">{{ img.name }}</div>
- </div>
- </div>
- }
- </div>
- } @else {
- <div class="empty-tip">暂无上传的图片</div>
- }
- </div>
- }
- </div>
- <!-- 备注区域 -->
- <div class="notes-section">
- <label class="notes-label">备注</label>
- @if (canEditSection('delivery')) {
- <textarea #renderingNotes
- class="notes-textarea"
- placeholder="请输入备注信息..."
- [value]="getSpaceNotes('rendering', space.id)"
- (blur)="updateSpaceNotes('rendering', space.id, renderingNotes.value || '')">
- </textarea>
- } @else {
- <div class="notes-readonly">
- {{ getSpaceNotes('rendering', space.id) || '暂无备注' }}
- </div>
- }
- </div>
- </div>
- }
- </div>
- }
- </div>
- </div>
- </div>
- } @else if (stage === '后期') {
- <!-- 后期阶段:直接显示后期相关内容 -->
- <div class="post-production-stage-panel">
- <!-- 空间列表 -->
- <div class="space-list-container">
- <div class="space-list-header">
- <h4>空间列表</h4>
- @if (canEditSection('delivery')) {
- <button class="add-space-btn"
- (click)="showAddSpaceInput['postProduction'] = true">
- <span class="add-icon">+</span>
- <span>添加空间</span>
- </button>
- }
- </div>
- <!-- 添加空间输入框 -->
- @if (showAddSpaceInput['postProduction']) {
- <div class="add-space-input-container">
- <input type="text"
- class="space-name-input"
- placeholder="请输入空间名称(如:卧室、餐厅、厨房)"
- [(ngModel)]="newSpaceName['postProduction']"
- (keyup.enter)="addSpace('postProduction')"
- #spaceInput>
- <div class="input-actions">
- <button class="confirm-btn" (click)="addSpace('postProduction')">确认</button>
- <button class="cancel-btn" (click)="cancelAddSpace('postProduction')">取消</button>
- </div>
- </div>
- }
- <!-- 空间卡片列表 -->
- <div class="space-cards">
- @for (space of getActiveProcessSpaces('postProcess'); track space.id) {
- <div class="space-card" [class.expanded]="space.isExpanded">
- <div class="space-header" (click)="toggleSpace('postProduction', space.id)">
- <div class="space-info">
- <span class="space-name">{{ space.name }}</span>
- <span class="space-progress">{{ getSpaceProgress('postProduction', space.id) }}%</span>
- </div>
- <div class="space-actions">
- @if (canEditSection('delivery')) {
- <button class="delete-space-btn"
- (click)="$event.stopPropagation(); removeSpace('postProduction', space.id)"
- title="删除空间">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
- <polyline points="3,6 5,6 21,6"></polyline>
- <path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
- </svg>
- </button>
- }
- <span class="expand-arrow" [class.expanded]="space.isExpanded">▼</span>
- </div>
- </div>
- @if (space.isExpanded) {
- <div class="space-content">
- <!-- 图片上传区域 -->
- <div class="upload-section">
- @if (canEditSection('delivery')) {
- <div class="upload-dropzone"
- (click)="triggerSpaceFileInput('postProduction', space.id)"
- (dragover)="onDragOver($event)"
- (dragleave)="onDragLeave($event)"
- (drop)="onSpaceFileDrop($event, 'postProduction', space.id)"
- [class.drag-over]="isDragOver">
- @if (getSpaceImages('postProduction', space.id).length === 0) {
- <div class="upload-placeholder">
- <div class="upload-text">点击上传或拖拽文件到此处</div>
- <div class="upload-hint">
- 支持 JPG、PNG 格式,后期处理图片
- </div>
- </div>
- } @else {
- <!-- 确认上传按钮 -->
- @if (canEditSection('delivery') && getSpaceImages('postProduction', space.id).length > 0) {
- <div class="confirm-upload-section">
- <button class="confirm-upload-btn"
- (click)="$event.stopPropagation(); confirmStageUpload('postProduction')"
- [disabled]="!canConfirmStageUpload('postProduction')">
- <span>确认上传</span>
- </button>
- </div>
- }
- <div class="uploaded-images-grid">
- @for (img of getSpaceImages('postProduction', space.id); track img.id) {
- <div class="uploaded-image-item" (click)="previewImage(img)">
- <img [src]="img.url" [alt]="img.name" />
- <div class="image-overlay">
- <div class="image-name">{{ img.name }}</div>
- <div class="image-actions">
- <button class="preview-btn" (click)="$event.stopPropagation(); previewImage(img)">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
- <circle cx="11" cy="11" r="8"></circle>
- <path d="m21 21-4.35-4.35"></path>
- </svg>
- </button>
- @if (canEditSection('delivery')) {
- <button class="delete-btn" (click)="$event.stopPropagation(); removeSpaceImage('postProduction', space.id, img.id)">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
- <polyline points="3,6 5,6 21,6"></polyline>
- <path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
- </svg>
- </button>
- }
- </div>
- </div>
- </div>
- }
- <!-- 添加更多图片按钮 -->
- @if (canEditSection('delivery')) {
- <div class="add-more-images-btn" (click)="$event.stopPropagation(); triggerSpaceFileInput('postProduction', space.id)">
- <div class="add-icon">+</div>
- <div class="add-text">添加更多</div>
- </div>
- }
- </div>
- }
- </div>
- } @else {
- <div class="readonly-images">
- @if (getSpaceImages('postProduction', space.id).length > 0) {
- <div class="uploaded-images-grid">
- @for (img of getSpaceImages('postProduction', space.id); track img.id) {
- <div class="uploaded-image-item" (click)="previewImage(img)">
- <img [src]="img.url" [alt]="img.name" />
- <div class="image-overlay">
- <div class="image-name">{{ img.name }}</div>
- </div>
- </div>
- }
- </div>
- } @else {
- <div class="empty-tip">暂无上传的图片</div>
- }
- </div>
- }
- </div>
- <!-- 备注区域 -->
- <div class="notes-section">
- <label class="notes-label">备注</label>
- @if (canEditSection('delivery')) {
- <textarea #postProductionNotes
- class="notes-textarea"
- placeholder="请输入备注信息..."
- [value]="getSpaceNotes('postProduction', space.id)"
- (blur)="updateSpaceNotes('postProduction', space.id, postProductionNotes.value || '')">
- </textarea>
- } @else {
- <div class="notes-readonly">
- {{ getSpaceNotes('postProduction', space.id) || '暂无备注' }}
- </div>
- }
- </div>
- </div>
- }
- </div>
- }
- </div>
- </div>
- </div>
- } @else if (stage === '尾款结算') {
- <!-- 原尾款结算售后 tab 容器已移除,根据用户要求不再使用 -->
- }
- </div>
- </div>
- }
- </div>
- </div>
- </div>
-
- <!-- 项目人员标签页 -->
- @if (isActiveTab('members')) {
- <div class="members-tab-content">
- <div class="main-content-layout">
- <!-- 项目人员内容 -->
- <div class="members-content">
- <div class="members-header">
- <h2>项目成员</h2>
- <p class="members-count">共 {{ projectMembers.length }} 名成员</p>
- </div>
-
- <div class="members-grid">
- @for (member of projectMembers; track member.id) {
- <div class="member-card">
- <div class="member-avatar">
- <img [src]="member.avatar" [alt]="member.name">
- </div>
- <div class="member-info">
- <h3 class="member-name">{{ member.name }}</h3>
- <p class="member-role">{{ member.role }}</p>
- <div class="member-stats">
- <div class="stat-item">
- <span class="stat-label">技能匹配度</span>
- <div class="progress-bar">
- <div class="progress-fill" [style.width.%]="member.skillMatch"></div>
- </div>
- <span class="stat-value">{{ member.skillMatch }}%</span>
- </div>
- <div class="stat-item">
- <span class="stat-label">项目进度</span>
- <div class="progress-bar">
- <div class="progress-fill" [style.width.%]="member.progress"></div>
- </div>
- <span class="stat-value">{{ member.progress }}%</span>
- </div>
- <div class="stat-item">
- <span class="stat-label">贡献度</span>
- <div class="progress-bar">
- <div class="progress-fill" [style.width.%]="member.contribution"></div>
- </div>
- <span class="stat-value">{{ member.contribution }}%</span>
- </div>
- </div>
- </div>
- </div>
- }
- </div>
-
- @if (projectMembers.length === 0) {
- <div class="empty-state">
- <p>暂无项目成员信息</p>
- </div>
- }
- </div>
- </div>
- </div>
- }
-
- <!-- 项目文件标签页 -->
- @if (isActiveTab('files')) {
- <div class="files-tab-content">
- <div class="main-content-layout">
- <!-- 项目文件内容 -->
- <div class="files-content">
- <div class="files-header">
- <h2>项目文件</h2>
- <p class="files-count">共 {{ projectFiles.length }} 个文件</p>
- </div>
-
- <div class="files-list">
- @for (file of projectFiles; track file.id) {
- <div class="file-item">
- <div class="file-icon">
- <span class="file-type">{{ file.type.toUpperCase() }}</span>
- </div>
- <div class="file-info">
- <h3 class="file-name">{{ file.name }}</h3>
- <div class="file-meta">
- <span class="file-size">{{ file.size }}</span>
- <span class="file-date">{{ file.date }}</span>
- </div>
- </div>
- <div class="file-actions">
- <button class="btn-download" (click)="downloadFile(file)">下载</button>
- <button class="btn-preview" (click)="previewFile(file)">预览</button>
- </div>
- </div>
- }
- </div>
-
- @if (projectFiles.length === 0) {
- <div class="empty-state">
- <p>暂无项目文件</p>
- </div>
- }
- </div>
- </div>
- </div>
- }
- </div>
- }
- <!-- 上传成功弹窗 - 在根级别渲染以确保正确定位,避免被vertical-stage-block影响 -->
- @if (true) {
- <app-upload-success-modal
- [isVisible]="showUploadSuccessModal"
- [uploadedFiles]="uploadedFiles"
- [uploadType]="uploadType"
- [analysisResult]="colorAnalysisResult || undefined"
- [isAnalyzing]="isAnalyzingColors"
- (closeModal)="onModalClose()"
- (analyzeColors)="onAnalyzeColors()"
- (viewReport)="onViewReport()">
- </app-upload-success-modal>
- }
|