# 交付执行阶段 - 消息发送功能详细设计 ## 1. 功能概述 组员上传交付物后,可以选择性地发送消息到客户群,支持: - 预设消息模板快速选择 - 自定义编辑消息内容 - 单独发送图片(无需文字) - 查看已发送的消息历史 --- ## 2. UI设计 ### 2.1 布局结构 左右分栏布局: - **左侧**:消息发送面板(30%宽度) - **右侧**:交付物上传区域(70%宽度) ### 2.2 消息面板 HTML 结构 ```html
``` ### 2.3 样式设计 (SCSS) ```scss .message-panel-container { position: sticky; top: 20px; height: calc(100vh - 120px); overflow: hidden; } .message-panel { display: flex; flex-direction: column; height: 100%; background: #fff; border: 1px solid #e5e7eb; border-radius: 12px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); overflow: hidden; .panel-header { padding: 16px; border-bottom: 1px solid #e5e7eb; display: flex; align-items: center; justify-content: space-between; background: linear-gradient(to bottom, #f9fafb, #ffffff); h3 { margin: 0; font-size: 16px; font-weight: 600; color: #1f2937; } .stage-badge { padding: 4px 12px; background: #4f46e5; color: white; border-radius: 12px; font-size: 12px; font-weight: 500; } } .template-section { padding: 16px; border-bottom: 1px solid #f3f4f6; label { display: block; margin-bottom: 8px; font-size: 13px; font-weight: 500; color: #6b7280; } .template-list { display: flex; flex-direction: column; gap: 8px; } .template-item { display: flex; align-items: flex-start; gap: 8px; padding: 10px; background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 8px; cursor: pointer; transition: all 0.2s; &:hover { background: #f3f4f6; border-color: #d1d5db; } &.selected { background: #ede9fe; border-color: #8b5cf6; } input[type="radio"] { margin-top: 2px; flex-shrink: 0; } .template-text { flex: 1; font-size: 13px; line-height: 1.5; color: #374151; } } } .message-editor { padding: 16px; border-bottom: 1px solid #f3f4f6; label { display: block; margin-bottom: 8px; font-size: 13px; font-weight: 500; color: #6b7280; } textarea { width: 100%; padding: 10px; border: 1px solid #e5e7eb; border-radius: 8px; font-size: 13px; line-height: 1.6; resize: vertical; transition: border-color 0.2s; &:focus { outline: none; border-color: #8b5cf6; box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.1); } } .char-count { margin-top: 4px; text-align: right; font-size: 11px; color: #9ca3af; } } .pending-images { padding: 16px; border-bottom: 1px solid #f3f4f6; label { display: block; margin-bottom: 8px; font-size: 13px; font-weight: 500; color: #6b7280; } .image-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; } .image-item { position: relative; aspect-ratio: 1; border-radius: 8px; overflow: hidden; border: 1px solid #e5e7eb; img { width: 100%; height: 100%; object-fit: cover; } .image-overlay { position: absolute; inset: 0; background: rgba(0, 0, 0, 0.6); display: flex; align-items: center; justify-content: center; opacity: 0; transition: opacity 0.2s; } &:hover .image-overlay { opacity: 1; } .btn-send-single { padding: 6px 12px; background: white; color: #4f46e5; border: none; border-radius: 6px; font-size: 11px; cursor: pointer; } .btn-remove { position: absolute; top: 4px; right: 4px; width: 20px; height: 20px; background: rgba(0, 0, 0, 0.7); color: white; border: none; border-radius: 50%; cursor: pointer; font-size: 14px; line-height: 1; } } } .send-options { padding: 12px 16px; background: #f9fafb; .checkbox-label { display: flex; align-items: center; gap: 8px; font-size: 13px; color: #374151; cursor: pointer; input[type="checkbox"] { width: 16px; height: 16px; } } } .panel-actions { padding: 16px; display: flex; gap: 8px; border-bottom: 1px solid #e5e7eb; button { flex: 1; padding: 10px 16px; border: none; border-radius: 8px; font-size: 13px; font-weight: 500; cursor: pointer; transition: all 0.2s; display: flex; align-items: center; justify-content: center; gap: 6px; &:disabled { opacity: 0.5; cursor: not-allowed; } } .btn-send-message { background: #4f46e5; color: white; &:hover:not(:disabled) { background: #4338ca; } } .btn-send-images { background: #10b981; color: white; &:hover:not(:disabled) { background: #059669; } } } .message-history { flex: 1; display: flex; flex-direction: column; overflow: hidden; .history-header { padding: 12px 16px; background: #f9fafb; display: flex; align-items: center; justify-content: space-between; h4 { margin: 0; font-size: 13px; font-weight: 600; color: #6b7280; } .btn-refresh { padding: 4px; background: transparent; border: none; cursor: pointer; color: #6b7280; &:hover { color: #4f46e5; } } } .history-list { flex: 1; overflow-y: auto; padding: 12px; .empty-state { text-align: center; padding: 40px 20px; color: #9ca3af; font-size: 13px; } .history-item { padding: 12px; background: #f9fafb; border-radius: 8px; margin-bottom: 8px; .msg-meta { display: flex; justify-content: space-between; margin-bottom: 6px; font-size: 11px; .sender { font-weight: 600; color: #4f46e5; } .time { color: #9ca3af; } } .msg-content { font-size: 13px; line-height: 1.5; color: #374151; margin-bottom: 6px; } .msg-images { display: flex; align-items: center; gap: 4px; font-size: 12px; color: #6b7280; } } } } } ``` --- ## 3. 消息模板配置 ```typescript // src/modules/project/constants/message-templates.ts export interface MessageTemplate { id: string; text: string; isDefault?: boolean; } export const MESSAGE_TEMPLATES: Record