Commit 656f58b65f3a10ccf8f69cb4531c58f62426bd85

Authored by 李宇
2 parents e6daf985 21ced84b

Merge branch 'master' of http://39.98.150.180/antissoft/lvqianmeiye_ERP

antis-ncc-admin/src/components/member-portrait-dialog.vue
1 1 <template>
2   - <el-dialog :visible.sync="visibleSync" title="会员画像" :width="'1400px'" append-to-body
  2 + <el-dialog :visible.sync="visibleSync" title="会员画像" :width="'1500px'" append-to-body
3 3 custom-class="member-portrait-dialog" :close-on-click-modal="false" @closed="handleClosed">
4 4 <div v-loading="loading" class="portrait-wrapper">
5   - <!-- 顶部:基础信息栏 -->
  5 + <!-- 顶部:会员核心信息卡片 -->
6 6 <div class="portrait-header">
  7 + <div class="header-avatar">
  8 + <div class="avatar-circle">
  9 + <i class="el-icon-user-solid"></i>
  10 + </div>
  11 + </div>
7 12 <div class="header-main">
8   - <div class="member-name">{{ baseInfo.MemberName || '—' }}</div>
  13 + <div class="member-name-row">
  14 + <h2 class="member-name">{{ baseInfo.MemberName || '—' }}</h2>
  15 + <el-tag v-if="baseInfo.ConsumeLevel !== undefined" :type="getConsumeLevelTagType(baseInfo.ConsumeLevel)" size="small" class="level-tag">
  16 + {{ getConsumeLevelText(baseInfo.ConsumeLevel) }}
  17 + </el-tag>
  18 + </div>
9 19 <div class="member-meta">
10   - <span class="meta-item">
  20 + <div class="meta-item">
11 21 <i class="el-icon-phone"></i>
12   - {{ baseInfo.Mobile || '—' }}
13   - </span>
14   - <span class="meta-item">
  22 + <span>{{ baseInfo.Mobile || '—' }}</span>
  23 + </div>
  24 + <div class="meta-item">
15 25 <i class="el-icon-office-building"></i>
16   - {{ baseInfo.StoreName || '—' }}
17   - </span>
18   - <span class="meta-item" v-if="baseInfo.Channel">
  26 + <span>{{ baseInfo.StoreName || '—' }}</span>
  27 + </div>
  28 + <div class="meta-item" v-if="baseInfo.Channel">
19 29 <i class="el-icon-connection"></i>
20   - {{ baseInfo.Channel }}
21   - </span>
  30 + <span>{{ baseInfo.Channel }}</span>
  31 + </div>
  32 + <div class="meta-item" v-if="baseInfo.MemberCode">
  33 + <i class="el-icon-tickets"></i>
  34 + <span>编码:{{ baseInfo.MemberCode }}</span>
  35 + </div>
22 36 </div>
23 37 </div>
24   - <div class="header-stats">
25   - <div class="stat-item">
26   - <div class="stat-label">剩余权益</div>
27   - <div class="stat-value highlight">¥{{ formatMoney(behaviorSummary.RemainingRightsAmount) }}</div>
28   - </div>
29   - <div class="stat-item">
30   - <div class="stat-label">累计开单</div>
31   - <div class="stat-value">¥{{ formatMoney(behaviorSummary.TotalBillingAmount) }}</div>
32   - </div>
33   - <div class="stat-item">
34   - <div class="stat-label">累计消耗</div>
35   - <div class="stat-value">¥{{ formatMoney(behaviorSummary.TotalConsumeAmount) }}</div>
36   - </div>
37   - <div class="stat-item">
38   - <div class="stat-label">沉睡天数</div>
39   - <div class="stat-value" :class="{ 'text-warning': baseInfo.SleepDays > 30 }">
40   - {{ baseInfo.SleepDays || 0 }} 天
  38 + <div class="header-right">
  39 + <div class="header-stats">
  40 + <div class="stat-card stat-primary">
  41 + <el-tag type="primary" size="small" class="stat-label-tag">剩余权益</el-tag>
  42 + <span class="stat-value">¥{{ formatMoney(behaviorSummary.RemainingRightsAmount) }}</span>
  43 + </div>
  44 + <div class="stat-card stat-success">
  45 + <el-tag type="success" size="small" class="stat-label-tag">累计开单</el-tag>
  46 + <span class="stat-value">¥{{ formatMoney(behaviorSummary.TotalBillingAmount) }}</span>
  47 + </div>
  48 + <div class="stat-card stat-info">
  49 + <el-tag type="info" size="small" class="stat-label-tag">累计消耗</el-tag>
  50 + <span class="stat-value">¥{{ formatMoney(behaviorSummary.TotalConsumeAmount) }}</span>
  51 + </div>
  52 + <div class="stat-card" :class="baseInfo.SleepDays > 30 ? 'stat-warning' : 'stat-default'">
  53 + <el-tag :type="baseInfo.SleepDays > 30 ? 'warning' : 'info'" size="small" class="stat-label-tag">沉睡天数</el-tag>
  54 + <span class="stat-value">{{ baseInfo.SleepDays || 0 }} 天</span>
41 55 </div>
42 56 </div>
43   - </div>
44   - </div>
45   -
46   - <!-- 基础信息区域(独立于标签页) -->
47   - <div class="base-info-section">
48   - <div class="base-info-layout">
49 57 <!-- 会员类型 -->
50   - <div class="card-section">
51   - <div class="section-title">
52   - <i class="el-icon-collection-tag"></i>
53   - 会员类型
54   - </div>
55   - <div class="tag-list">
56   - <div v-for="type in baseInfo.MemberTypes" :key="type.TypeName" class="member-type-item">
57   - <el-tag :type="getMemberTypeTagType(type.TypeName)" size="medium" class="member-type-tag">
58   - {{ type.TypeName }}
  58 + <div class="member-types-section" v-if="baseInfo.MemberTypes && baseInfo.MemberTypes.length > 0">
  59 + <div class="member-types-list">
  60 + <div v-for="type in baseInfo.MemberTypes" :key="type.TypeName" class="member-type-badge">
  61 + <el-tag :type="getMemberTypeTagType(type.TypeName)" size="small" class="member-type-tag">
  62 + {{ type.TypeName }}会员
59 63 </el-tag>
60 64 <span class="member-type-date" v-if="type.BecomeTime">
  65 + <i class="el-icon-calendar"></i>
61 66 {{ formatDate(type.BecomeTime) }}
62 67 </span>
63 68 </div>
64   - <span v-if="!baseInfo.MemberTypes || baseInfo.MemberTypes.length === 0" class="text-muted">无</span>
65   - </div>
66   - </div>
67   -
68   - <!-- 基础信息 -->
69   - <div class="card-section">
70   - <div class="section-title">
71   - <i class="el-icon-info"></i>
72   - 基础信息
73   - </div>
74   - <div class="info-list">
75   - <div class="info-item">
76   - <span class="info-label">会员编码:</span>
77   - <span class="info-value">{{ baseInfo.MemberCode || '—' }}</span>
78   - </div>
79   - <div class="info-item">
80   - <span class="info-label">首次到店:</span>
81   - <span class="info-value">{{ formatDateTime(baseInfo.FirstVisitTime) }}</span>
82   - </div>
83   - <div class="info-item">
84   - <span class="info-label">最后到店:</span>
85   - <span class="info-value">{{ formatDateTime(baseInfo.LastVisitTime) }}</span>
86   - </div>
87   - <div class="info-item">
88   - <span class="info-label">消费等级:</span>
89   - <span class="info-value">{{ getConsumeLevelText(baseInfo.ConsumeLevel) }}</span>
90   - </div>
91 69 </div>
92 70 </div>
93 71 </div>
... ... @@ -99,81 +77,147 @@
99 77 <el-tab-pane label="概览" name="overview">
100 78 <div class="tab-content">
101 79 <!-- 消费行为 -->
102   - <div class="card-section">
103   - <div class="section-title">
  80 + <div class="content-card">
  81 + <div class="card-header">
104 82 <i class="el-icon-shopping-cart-full"></i>
105   - 消费行为
  83 + <span class="card-title">消费行为</span>
106 84 </div>
107   - <div class="behavior-grid">
108   - <div class="behavior-item">
109   - <div class="behavior-label">开单次数</div>
110   - <div class="behavior-value">{{ behaviorSummary.BillingCount || 0 }}</div>
111   - </div>
112   - <div class="behavior-item">
113   - <div class="behavior-label">消耗次数</div>
114   - <div class="behavior-value">{{ behaviorSummary.ConsumeCount || 0 }}</div>
115   - </div>
116   - <div class="behavior-item">
117   - <div class="behavior-label">退卡次数</div>
118   - <div class="behavior-value">{{ behaviorSummary.RefundCount || 0 }}</div>
119   - </div>
120   - <div class="behavior-item">
121   - <div class="behavior-label">平均开单金额</div>
122   - <div class="behavior-value">¥{{ formatMoney(behaviorSummary.AvgBillingAmount) }}</div>
123   - </div>
124   - <div class="behavior-item">
125   - <div class="behavior-label">平均消耗金额</div>
126   - <div class="behavior-value">¥{{ formatMoney(behaviorSummary.AvgConsumeAmount) }}</div>
127   - </div>
128   - <div class="behavior-item">
129   - <div class="behavior-label">最近开单</div>
130   - <div class="behavior-value">{{ formatDateTime(behaviorSummary.LastBillingTime) }}</div>
131   - </div>
132   - <div class="behavior-item">
133   - <div class="behavior-label">最近消耗</div>
134   - <div class="behavior-value">{{ formatDateTime(behaviorSummary.LastConsumeTime) }}</div>
135   - </div>
136   - <div class="behavior-item">
137   - <div class="behavior-label">首次开单</div>
138   - <div class="behavior-value">{{ formatDateTime(behaviorSummary.FirstBillingTime) }}</div>
139   - </div>
140   - <div class="behavior-item">
141   - <div class="behavior-label">首次消耗</div>
142   - <div class="behavior-value">{{ formatDateTime(behaviorSummary.FirstConsumeTime) }}</div>
  85 + <div class="card-body">
  86 + <div class="behavior-grid">
  87 + <div class="behavior-item">
  88 + <div class="behavior-icon">
  89 + <i class="el-icon-document"></i>
  90 + </div>
  91 + <div class="behavior-content">
  92 + <div class="behavior-label">开单次数</div>
  93 + <div class="behavior-value">{{ behaviorSummary.BillingCount || 0 }}</div>
  94 + </div>
  95 + </div>
  96 + <div class="behavior-item">
  97 + <div class="behavior-icon">
  98 + <i class="el-icon-goods"></i>
  99 + </div>
  100 + <div class="behavior-content">
  101 + <div class="behavior-label">消耗次数</div>
  102 + <div class="behavior-value">{{ behaviorSummary.ConsumeCount || 0 }}</div>
  103 + </div>
  104 + </div>
  105 + <div class="behavior-item">
  106 + <div class="behavior-icon">
  107 + <i class="el-icon-refresh-left"></i>
  108 + </div>
  109 + <div class="behavior-content">
  110 + <div class="behavior-label">退卡次数</div>
  111 + <div class="behavior-value">{{ behaviorSummary.RefundCount || 0 }}</div>
  112 + </div>
  113 + </div>
  114 + <div class="behavior-item">
  115 + <div class="behavior-icon">
  116 + <i class="el-icon-coin"></i>
  117 + </div>
  118 + <div class="behavior-content">
  119 + <div class="behavior-label">平均开单金额</div>
  120 + <div class="behavior-value">¥{{ formatMoney(behaviorSummary.AvgBillingAmount) }}</div>
  121 + </div>
  122 + </div>
  123 + <div class="behavior-item">
  124 + <div class="behavior-icon">
  125 + <i class="el-icon-coin"></i>
  126 + </div>
  127 + <div class="behavior-content">
  128 + <div class="behavior-label">平均消耗金额</div>
  129 + <div class="behavior-value">¥{{ formatMoney(behaviorSummary.AvgConsumeAmount) }}</div>
  130 + </div>
  131 + </div>
  132 + <div class="behavior-item">
  133 + <div class="behavior-icon">
  134 + <i class="el-icon-time"></i>
  135 + </div>
  136 + <div class="behavior-content">
  137 + <div class="behavior-label">最近开单</div>
  138 + <div class="behavior-value">{{ formatDateTime(behaviorSummary.LastBillingTime) }}</div>
  139 + </div>
  140 + </div>
  141 + <div class="behavior-item">
  142 + <div class="behavior-icon">
  143 + <i class="el-icon-time"></i>
  144 + </div>
  145 + <div class="behavior-content">
  146 + <div class="behavior-label">最近消耗</div>
  147 + <div class="behavior-value">{{ formatDateTime(behaviorSummary.LastConsumeTime) }}</div>
  148 + </div>
  149 + </div>
  150 + <div class="behavior-item">
  151 + <div class="behavior-icon">
  152 + <i class="el-icon-calendar"></i>
  153 + </div>
  154 + <div class="behavior-content">
  155 + <div class="behavior-label">首次开单</div>
  156 + <div class="behavior-value">{{ formatDateTime(behaviorSummary.FirstBillingTime) }}</div>
  157 + </div>
  158 + </div>
  159 + <div class="behavior-item">
  160 + <div class="behavior-icon">
  161 + <i class="el-icon-calendar"></i>
  162 + </div>
  163 + <div class="behavior-content">
  164 + <div class="behavior-label">首次消耗</div>
  165 + <div class="behavior-value">{{ formatDateTime(behaviorSummary.FirstConsumeTime) }}</div>
  166 + </div>
  167 + </div>
143 168 </div>
144 169 </div>
145 170 </div>
146 171  
147 172 <!-- 近12个月趋势图 -->
148   - <div class="card-section">
149   - <div class="section-title">
  173 + <div class="content-card">
  174 + <div class="card-header">
150 175 <i class="el-icon-data-line"></i>
151   - 近12个月消费趋势
  176 + <span class="card-title">近12个月消费趋势</span>
  177 + </div>
  178 + <div class="card-body">
  179 + <div ref="trendChart" class="trend-chart"></div>
152 180 </div>
153   - <div ref="trendChart" class="trend-chart"></div>
154 181 </div>
155 182  
156 183 <!-- 消费分析 -->
157   - <div v-if="consumptionAnalysis" class="card-section">
158   - <div class="section-title">
  184 + <div v-if="consumptionAnalysis" class="content-card">
  185 + <div class="card-header">
159 186 <i class="el-icon-data-analysis"></i>
160   - 消费分析
  187 + <span class="card-title">消费分析</span>
161 188 </div>
162   - <div class="analysis-layout">
163   - <div class="analysis-item">
164   - <div class="analysis-label">消费频率</div>
165   - <div class="analysis-value">{{ formatMoney(consumptionAnalysis.ConsumeFrequency) }} 次/月</div>
166   - </div>
167   - <div class="analysis-item">
168   - <div class="analysis-label">开单频率</div>
169   - <div class="analysis-value">{{ formatMoney(consumptionAnalysis.BillingFrequency) }} 次/月</div>
170   - </div>
171   - <div class="analysis-item">
172   - <div class="analysis-label">消费活跃度</div>
173   - <div class="analysis-value">
174   - <el-tag :type="consumptionAnalysis.IsActive ? 'success' : 'info'" size="small">
175   - {{ consumptionAnalysis.IsActive ? '活跃' : '不活跃' }}
176   - </el-tag>
  189 + <div class="card-body">
  190 + <div class="analysis-layout">
  191 + <div class="analysis-item">
  192 + <div class="analysis-icon">
  193 + <i class="el-icon-timer"></i>
  194 + </div>
  195 + <div class="analysis-content">
  196 + <div class="analysis-label">消费频率</div>
  197 + <div class="analysis-value">{{ formatMoney(consumptionAnalysis.ConsumeFrequency) }} 次/月</div>
  198 + </div>
  199 + </div>
  200 + <div class="analysis-item">
  201 + <div class="analysis-icon">
  202 + <i class="el-icon-timer"></i>
  203 + </div>
  204 + <div class="analysis-content">
  205 + <div class="analysis-label">开单频率</div>
  206 + <div class="analysis-value">{{ formatMoney(consumptionAnalysis.BillingFrequency) }} 次/月</div>
  207 + </div>
  208 + </div>
  209 + <div class="analysis-item">
  210 + <div class="analysis-icon">
  211 + <i class="el-icon-success"></i>
  212 + </div>
  213 + <div class="analysis-content">
  214 + <div class="analysis-label">消费活跃度</div>
  215 + <div class="analysis-value">
  216 + <el-tag :type="consumptionAnalysis.IsActive ? 'success' : 'info'" size="small">
  217 + {{ consumptionAnalysis.IsActive ? '活跃' : '不活跃' }}
  218 + </el-tag>
  219 + </div>
  220 + </div>
177 221 </div>
178 222 </div>
179 223 </div>
... ... @@ -184,26 +228,28 @@
184 228 <!-- 权益明细 -->
185 229 <el-tab-pane label="权益明细" name="assets">
186 230 <div class="tab-content">
187   - <div class="card-section">
188   - <div class="section-title">
  231 + <div class="content-card">
  232 + <div class="card-header">
189 233 <i class="el-icon-wallet"></i>
190   - 权益明细
  234 + <span class="card-title">权益明细</span>
  235 + </div>
  236 + <div class="card-body">
  237 + <el-table :data="remainingItems" size="small" border stripe>
  238 + <el-table-column prop="ItemName" label="品项名称" min-width="140" />
  239 + <el-table-column prop="SourceType" label="来源类型" width="90" />
  240 + <el-table-column prop="UnitPrice" label="单价" width="90">
  241 + <template slot-scope="scope">¥{{ formatMoney(scope.row.UnitPrice) }}</template>
  242 + </el-table-column>
  243 + <el-table-column prop="TotalQuantity" label="总数量" width="80" />
  244 + <el-table-column prop="ConsumedQuantity" label="已消费" width="80" />
  245 + <el-table-column prop="RefundedQuantity" label="已退款" width="80" />
  246 + <el-table-column prop="DeductedQuantity" label="已扣除" width="80" />
  247 + <el-table-column prop="RemainingQuantity" label="剩余" width="80" />
  248 + <el-table-column prop="RemainingValue" label="剩余价值" width="120">
  249 + <template slot-scope="scope">¥{{ formatMoney(scope.row.RemainingValue) }}</template>
  250 + </el-table-column>
  251 + </el-table>
191 252 </div>
192   - <el-table :data="remainingItems" size="small" border stripe>
193   - <el-table-column prop="ItemName" label="品项名称" min-width="140" />
194   - <el-table-column prop="SourceType" label="来源类型" width="90" />
195   - <el-table-column prop="UnitPrice" label="单价" width="90">
196   - <template slot-scope="scope">¥{{ formatMoney(scope.row.UnitPrice) }}</template>
197   - </el-table-column>
198   - <el-table-column prop="TotalQuantity" label="总数量" width="80" />
199   - <el-table-column prop="ConsumedQuantity" label="已消费" width="80" />
200   - <el-table-column prop="RefundedQuantity" label="已退款" width="80" />
201   - <el-table-column prop="DeductedQuantity" label="已扣除" width="80" />
202   - <el-table-column prop="RemainingQuantity" label="剩余" width="80" />
203   - <el-table-column prop="RemainingValue" label="剩余价值" width="120">
204   - <template slot-scope="scope">¥{{ formatMoney(scope.row.RemainingValue) }}</template>
205   - </el-table-column>
206   - </el-table>
207 253 </div>
208 254 </div>
209 255 </el-tab-pane>
... ... @@ -211,24 +257,32 @@
211 257 <!-- 开单列表 -->
212 258 <el-tab-pane label="开单列表" name="billing">
213 259 <div class="tab-content">
214   - <el-table v-loading="billingLoading" :data="billingList" size="small" border stripe>
215   - <el-table-column prop="BillingDate" label="开单日期" width="160">
216   - <template slot-scope="scope">{{ formatDateTime(scope.row.BillingDate) }}</template>
217   - </el-table-column>
218   - <el-table-column prop="StoreName" label="门店" width="150" />
219   - <el-table-column prop="Amount" label="实付金额" width="120">
220   - <template slot-scope="scope">¥{{ formatMoney(scope.row.Amount) }}</template>
221   - </el-table-column>
222   - <el-table-column prop="DebtAmount" label="欠款金额" width="120">
223   - <template slot-scope="scope">¥{{ formatMoney(scope.row.DebtAmount) }}</template>
224   - </el-table-column>
225   - <el-table-column prop="ActivityName" label="活动名称" min-width="150" />
226   - </el-table>
227   - <div class="pagination-bar">
228   - <el-pagination layout="total, sizes, prev, pager, next" :page-sizes="[10, 20, 50]"
229   - :total="billingPagination.total" :current-page="billingPagination.pageIndex"
230   - :page-size="billingPagination.pageSize" @size-change="handleBillingSizeChange"
231   - @current-change="handleBillingPageChange" />
  260 + <div class="content-card">
  261 + <div class="card-header">
  262 + <i class="el-icon-document"></i>
  263 + <span class="card-title">开单列表</span>
  264 + </div>
  265 + <div class="card-body">
  266 + <el-table v-loading="billingLoading" :data="billingList" size="small" border stripe>
  267 + <el-table-column prop="BillingDate" label="开单日期" width="160">
  268 + <template slot-scope="scope">{{ formatDateTime(scope.row.BillingDate) }}</template>
  269 + </el-table-column>
  270 + <el-table-column prop="StoreName" label="门店" width="150" />
  271 + <el-table-column prop="Amount" label="实付金额" width="120">
  272 + <template slot-scope="scope">¥{{ formatMoney(scope.row.Amount) }}</template>
  273 + </el-table-column>
  274 + <el-table-column prop="DebtAmount" label="欠款金额" width="120">
  275 + <template slot-scope="scope">¥{{ formatMoney(scope.row.DebtAmount) }}</template>
  276 + </el-table-column>
  277 + <el-table-column prop="ActivityName" label="活动名称" min-width="150" />
  278 + </el-table>
  279 + <div class="pagination-bar">
  280 + <el-pagination layout="total, sizes, prev, pager, next" :page-sizes="[10, 20, 50]"
  281 + :total="billingPagination.total" :current-page="billingPagination.pageIndex"
  282 + :page-size="billingPagination.pageSize" @size-change="handleBillingSizeChange"
  283 + @current-change="handleBillingPageChange" />
  284 + </div>
  285 + </div>
232 286 </div>
233 287 </div>
234 288 </el-tab-pane>
... ... @@ -236,23 +290,31 @@
236 290 <!-- 消耗列表 -->
237 291 <el-tab-pane label="消耗列表" name="consume">
238 292 <div class="tab-content">
239   - <el-table v-loading="consumeLoading" :data="consumeList" size="small" border stripe>
240   - <el-table-column prop="ConsumeDate" label="消耗日期" width="160">
241   - <template slot-scope="scope">{{ formatDateTime(scope.row.ConsumeDate) }}</template>
242   - </el-table-column>
243   - <el-table-column prop="StoreName" label="门店" width="150" />
244   - <el-table-column prop="Amount" label="消耗金额" width="120">
245   - <template slot-scope="scope">¥{{ formatMoney(scope.row.Amount) }}</template>
246   - </el-table-column>
247   - <el-table-column prop="LaborCost" label="手工费" width="120">
248   - <template slot-scope="scope">¥{{ formatMoney(scope.row.LaborCost) }}</template>
249   - </el-table-column>
250   - </el-table>
251   - <div class="pagination-bar">
252   - <el-pagination layout="total, sizes, prev, pager, next" :page-sizes="[10, 20, 50]"
253   - :total="consumePagination.total" :current-page="consumePagination.pageIndex"
254   - :page-size="consumePagination.pageSize" @size-change="handleConsumeSizeChange"
255   - @current-change="handleConsumePageChange" />
  293 + <div class="content-card">
  294 + <div class="card-header">
  295 + <i class="el-icon-goods"></i>
  296 + <span class="card-title">消耗列表</span>
  297 + </div>
  298 + <div class="card-body">
  299 + <el-table v-loading="consumeLoading" :data="consumeList" size="small" border stripe>
  300 + <el-table-column prop="ConsumeDate" label="消耗日期" width="160">
  301 + <template slot-scope="scope">{{ formatDateTime(scope.row.ConsumeDate) }}</template>
  302 + </el-table-column>
  303 + <el-table-column prop="StoreName" label="门店" width="150" />
  304 + <el-table-column prop="Amount" label="消耗金额" width="120">
  305 + <template slot-scope="scope">¥{{ formatMoney(scope.row.Amount) }}</template>
  306 + </el-table-column>
  307 + <el-table-column prop="LaborCost" label="手工费" width="120">
  308 + <template slot-scope="scope">¥{{ formatMoney(scope.row.LaborCost) }}</template>
  309 + </el-table-column>
  310 + </el-table>
  311 + <div class="pagination-bar">
  312 + <el-pagination layout="total, sizes, prev, pager, next" :page-sizes="[10, 20, 50]"
  313 + :total="consumePagination.total" :current-page="consumePagination.pageIndex"
  314 + :page-size="consumePagination.pageSize" @size-change="handleConsumeSizeChange"
  315 + @current-change="handleConsumePageChange" />
  316 + </div>
  317 + </div>
256 318 </div>
257 319 </div>
258 320 </el-tab-pane>
... ... @@ -260,24 +322,32 @@
260 322 <!-- 退卡列表 -->
261 323 <el-tab-pane label="退卡列表" name="refund">
262 324 <div class="tab-content">
263   - <el-table v-loading="refundLoading" :data="refundList" size="small" border stripe>
264   - <el-table-column prop="RefundDate" label="退卡日期" width="160">
265   - <template slot-scope="scope">{{ formatDateTime(scope.row.RefundDate) }}</template>
266   - </el-table-column>
267   - <el-table-column prop="StoreName" label="门店" width="150" />
268   - <el-table-column prop="RefundAmount" label="退卡金额" width="120">
269   - <template slot-scope="scope">¥{{ formatMoney(scope.row.RefundAmount) }}</template>
270   - </el-table-column>
271   - <el-table-column prop="ActualRefundAmount" label="实际退款" width="120">
272   - <template slot-scope="scope">¥{{ formatMoney(scope.row.ActualRefundAmount) }}</template>
273   - </el-table-column>
274   - <el-table-column prop="RefundReason" label="退卡原因" min-width="150" />
275   - </el-table>
276   - <div class="pagination-bar">
277   - <el-pagination layout="total, sizes, prev, pager, next" :page-sizes="[10, 20, 50]"
278   - :total="refundPagination.total" :current-page="refundPagination.pageIndex"
279   - :page-size="refundPagination.pageSize" @size-change="handleRefundSizeChange"
280   - @current-change="handleRefundPageChange" />
  325 + <div class="content-card">
  326 + <div class="card-header">
  327 + <i class="el-icon-refresh-left"></i>
  328 + <span class="card-title">退卡列表</span>
  329 + </div>
  330 + <div class="card-body">
  331 + <el-table v-loading="refundLoading" :data="refundList" size="small" border stripe>
  332 + <el-table-column prop="RefundDate" label="退卡日期" width="160">
  333 + <template slot-scope="scope">{{ formatDateTime(scope.row.RefundDate) }}</template>
  334 + </el-table-column>
  335 + <el-table-column prop="StoreName" label="门店" width="150" />
  336 + <el-table-column prop="RefundAmount" label="退卡金额" width="120">
  337 + <template slot-scope="scope">¥{{ formatMoney(scope.row.RefundAmount) }}</template>
  338 + </el-table-column>
  339 + <el-table-column prop="ActualRefundAmount" label="实际退款" width="120">
  340 + <template slot-scope="scope">¥{{ formatMoney(scope.row.ActualRefundAmount) }}</template>
  341 + </el-table-column>
  342 + <el-table-column prop="RefundReason" label="退卡原因" min-width="150" />
  343 + </el-table>
  344 + <div class="pagination-bar">
  345 + <el-pagination layout="total, sizes, prev, pager, next" :page-sizes="[10, 20, 50]"
  346 + :total="refundPagination.total" :current-page="refundPagination.pageIndex"
  347 + :page-size="refundPagination.pageSize" @size-change="handleRefundSizeChange"
  348 + @current-change="handleRefundPageChange" />
  349 + </div>
  350 + </div>
281 351 </div>
282 352 </div>
283 353 </el-tab-pane>
... ... @@ -620,6 +690,16 @@ export default {
620 690 }
621 691 return levelMap[level] || '普通'
622 692 },
  693 + getConsumeLevelTagType(level) {
  694 + const typeMap = {
  695 + 0: 'info',
  696 + 1: '',
  697 + 2: 'warning',
  698 + 3: 'success',
  699 + 4: 'danger'
  700 + }
  701 + return typeMap[level] || 'info'
  702 + },
623 703 getMemberTypeTagType(typeName) {
624 704 const typeMap = {
625 705 '生美': 'success',
... ... @@ -651,178 +731,262 @@ export default {
651 731 <style lang="scss" scoped>
652 732 .member-portrait-dialog {
653 733 .el-dialog {
654   - border-radius: 14px;
  734 + border-radius: 16px;
655 735 overflow: hidden;
656 736 }
657 737  
658 738 .el-dialog__header {
659   - padding: 16px 20px;
660   - border-bottom: 1px solid #ebeef5;
  739 + padding: 20px 24px;
  740 + border-bottom: 1px solid #e4e7ed;
  741 + background: #409EFF;
  742 + color: #fff;
  743 +
  744 + .el-dialog__title {
  745 + color: #fff;
  746 + font-size: 18px;
  747 + font-weight: 600;
  748 + }
  749 +
  750 + .el-dialog__close {
  751 + color: #fff;
  752 + font-size: 20px;
  753 +
  754 + &:hover {
  755 + color: rgba(255, 255, 255, 0.8);
  756 + }
  757 + }
661 758 }
662 759  
663 760 .el-dialog__body {
664   - padding: 20px;
  761 + padding: 24px;
665 762 background: #f5f7fa;
666 763 color: #303133;
667 764 overflow-y: auto;
668   - max-height: calc(90vh - 100px);
  765 + max-height: calc(90vh - 120px);
669 766 }
670 767 }
671 768  
672 769 .portrait-wrapper {
  770 + // 顶部会员信息卡片
673 771 .portrait-header {
674 772 background: #fff;
675   - border: 1px solid #ebeef5;
676   - border-radius: 10px;
677   - padding: 16px 20px;
678   - margin-bottom: 16px;
  773 + border-radius: 12px;
  774 + padding: 24px;
  775 + margin-bottom: 20px;
679 776 display: flex;
680   - justify-content: space-between;
681   - align-items: center;
682   - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
  777 + align-items: flex-start;
  778 + gap: 24px;
  779 + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
  780 + border: 1px solid #e4e7ed;
  781 +
  782 + .header-avatar {
  783 + flex-shrink: 0;
  784 +
  785 + .avatar-circle {
  786 + width: 80px;
  787 + height: 80px;
  788 + border-radius: 50%;
  789 + background: #409EFF;
  790 + display: flex;
  791 + align-items: center;
  792 + justify-content: center;
  793 + color: #fff;
  794 + font-size: 36px;
  795 + box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
  796 + }
  797 + }
683 798  
684 799 .header-main {
685 800 flex: 1;
  801 + min-width: 0;
686 802  
687   - .member-name {
688   - font-size: 22px;
689   - font-weight: 600;
690   - color: #303133;
691   - margin-bottom: 10px;
  803 + .member-name-row {
  804 + display: flex;
  805 + align-items: center;
  806 + gap: 12px;
  807 + margin-bottom: 16px;
  808 +
  809 + .member-name {
  810 + font-size: 24px;
  811 + font-weight: 700;
  812 + color: #303133;
  813 + margin: 0;
  814 + line-height: 1.2;
  815 + }
  816 +
  817 + .level-tag {
  818 + font-weight: 600;
  819 + }
692 820 }
693 821  
694 822 .member-meta {
695 823 display: flex;
696   - gap: 16px;
697 824 flex-wrap: wrap;
  825 + gap: 20px;
698 826  
699 827 .meta-item {
700   - font-size: 13px;
  828 + font-size: 14px;
701 829 color: #606266;
702 830 display: flex;
703 831 align-items: center;
704   - gap: 6px;
  832 + gap: 8px;
705 833  
706 834 i {
707 835 color: #909399;
  836 + font-size: 16px;
  837 + }
  838 +
  839 + span {
  840 + line-height: 1.5;
708 841 }
709 842 }
710 843 }
711 844 }
712 845  
713   - .header-stats {
  846 + .header-right {
714 847 display: flex;
715   - gap: 24px;
716   -
717   - .stat-item {
718   - text-align: center;
  848 + flex-direction: column;
  849 + align-items: flex-end;
  850 + gap: 16px;
  851 + flex-shrink: 0;
719 852  
720   - .stat-label {
721   - font-size: 12px;
722   - color: #909399;
723   - margin-bottom: 6px;
724   - }
  853 + .header-stats {
  854 + display: flex;
  855 + gap: 16px;
  856 + flex-shrink: 0;
725 857  
726   - .stat-value {
727   - font-size: 16px;
728   - font-weight: 600;
729   - color: #303133;
  858 + .stat-card {
  859 + min-width: 100px;
  860 + padding: 6px 12px;
  861 + border-radius: 6px;
  862 + border: 1px solid #e4e7ed;
  863 + display: flex;
  864 + align-items: center;
  865 + gap: 8px;
  866 + transition: all 0.2s ease;
730 867  
731   - &.highlight {
732   - color: #409EFF;
  868 + &:hover {
  869 + opacity: 0.9;
  870 + transform: translateY(-1px);
  871 + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
733 872 }
734 873  
735   - &.text-warning {
736   - color: #E6A23C;
  874 + .stat-label-tag {
  875 + font-weight: 600;
  876 + font-size: 12px;
  877 + padding: 2px 10px;
  878 + margin: 0;
  879 + flex-shrink: 0;
737 880 }
738   - }
739   - }
740   - }
741   - }
742   -
743   - .base-info-section {
744   - margin-bottom: 12px;
745 881  
746   - .base-info-layout {
747   - display: flex;
748   - gap: 12px;
749   -
750   - .card-section {
751   - flex: 1;
752   - padding: 10px 12px;
753   - margin-bottom: 0;
  882 + .stat-value {
  883 + font-size: 13px;
  884 + font-weight: 600;
  885 + color: #303133;
  886 + white-space: nowrap;
  887 + line-height: 1.2;
  888 + flex: 1;
  889 + min-width: 0;
  890 + }
754 891  
755   - .section-title {
756   - font-size: 13px;
757   - margin-bottom: 8px;
758   - }
  892 + &.stat-primary {
  893 + background: #409EFF;
759 894  
760   - .tag-list {
761   - gap: 6px;
  895 + .stat-value {
  896 + color: #fff;
  897 + }
  898 + }
762 899  
763   - .member-type-item {
764   - padding: 6px 8px;
765   - font-size: 12px;
  900 + &.stat-success {
  901 + background: #67C23A;
766 902  
767   - .member-type-tag {
768   - font-size: 12px;
769   - padding: 2px 10px;
  903 + .stat-value {
  904 + color: #fff;
770 905 }
  906 + }
771 907  
772   - .member-type-date {
773   - font-size: 11px;
  908 + &.stat-info {
  909 + background: #909399;
  910 +
  911 + .stat-value {
  912 + color: #fff;
774 913 }
775 914 }
776   - }
777 915  
778   - .info-list {
779   - .info-item {
780   - padding: 4px 0;
781   - font-size: 12px;
  916 + &.stat-warning {
  917 + background: #E6A23C;
782 918  
783   - .info-label {
784   - width: 75px;
785   - font-size: 12px;
  919 + .stat-value {
  920 + color: #fff;
786 921 }
  922 + }
787 923  
788   - .info-value {
789   - font-size: 12px;
  924 + &.stat-default {
  925 + background: #606266;
  926 +
  927 + .stat-value {
  928 + color: #fff;
790 929 }
791 930 }
792 931 }
793 932 }
794   - }
795   - }
796 933  
797   - .portrait-tabs {
798   - ::v-deep .el-tabs__header {
799   - margin-bottom: 16px;
800   - }
  934 + .member-types-section {
  935 + display: flex;
  936 + align-items: center;
  937 + gap: 12px;
  938 + flex-wrap: wrap;
  939 +
  940 + .member-types-label {
  941 + font-size: 13px;
  942 + color: #909399;
  943 + display: flex;
  944 + align-items: center;
  945 + gap: 6px;
  946 + flex-shrink: 0;
  947 +
  948 + i {
  949 + color: #409EFF;
  950 + font-size: 14px;
  951 + }
  952 + }
801 953  
802   - ::v-deep .el-tabs__content {
803   - .tab-content {
804   - .analysis-layout {
  954 + .member-types-list {
805 955 display: flex;
806   - gap: 20px;
807 956 flex-wrap: wrap;
  957 + gap: 12px;
808 958  
809   - .analysis-item {
810   - min-width: 150px;
811   - padding: 12px;
  959 + .member-type-badge {
  960 + display: flex;
  961 + align-items: center;
  962 + gap: 8px;
  963 + padding: 6px 12px;
812 964 background: #f8f9fa;
813 965 border-radius: 6px;
814   - border: 1px solid #ebeef5;
  966 + border: 1px solid #e4e7ed;
  967 + transition: all 0.2s ease;
815 968  
816   - .analysis-label {
  969 + &:hover {
  970 + background: #f0f2f5;
  971 + border-color: #409EFF;
  972 + }
  973 +
  974 + .member-type-tag {
  975 + font-weight: 600;
817 976 font-size: 12px;
818   - color: #909399;
819   - margin-bottom: 6px;
  977 + padding: 2px 10px;
820 978 }
821 979  
822   - .analysis-value {
823   - font-size: 14px;
824   - color: #303133;
825   - font-weight: 500;
  980 + .member-type-date {
  981 + font-size: 12px;
  982 + color: #909399;
  983 + display: flex;
  984 + align-items: center;
  985 + gap: 4px;
  986 +
  987 + i {
  988 + font-size: 12px;
  989 + }
826 990 }
827 991 }
828 992 }
... ... @@ -830,117 +994,202 @@ export default {
830 994 }
831 995 }
832 996  
833   - .card-section {
834   - background: #fff;
835   - border: 1px solid #ebeef5;
836   - border-radius: 10px;
837   - padding: 14px;
838   - margin-bottom: 16px;
839   - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
840 997  
841   - .section-title {
  998 + // 选项卡样式
  999 + .portrait-tabs {
  1000 + ::v-deep .el-tabs__header {
  1001 + margin-bottom: 20px;
  1002 + background: #fff;
  1003 + padding: 0 20px;
  1004 + border-radius: 12px;
  1005 + border: 1px solid #e4e7ed;
  1006 + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
  1007 + }
  1008 +
  1009 + ::v-deep .el-tabs__nav-wrap::after {
  1010 + display: none;
  1011 + }
  1012 +
  1013 + ::v-deep .el-tabs__item {
842 1014 font-size: 14px;
843   - font-weight: 600;
844   - color: #303133;
845   - margin-bottom: 12px;
846   - display: flex;
847   - align-items: center;
848   - gap: 6px;
  1015 + font-weight: 500;
  1016 + padding: 0 24px;
  1017 + height: 50px;
  1018 + line-height: 50px;
849 1019  
850   - i {
  1020 + &.is-active {
851 1021 color: #409EFF;
  1022 + font-weight: 600;
  1023 + }
  1024 + }
  1025 +
  1026 + ::v-deep .el-tabs__active-bar {
  1027 + background-color: #409EFF;
  1028 + height: 3px;
  1029 + }
  1030 +
  1031 + .tab-content {
  1032 + .content-card {
  1033 + background: #fff;
  1034 + border-radius: 12px;
  1035 + border: 1px solid #e4e7ed;
  1036 + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
  1037 + margin-bottom: 20px;
  1038 + overflow: hidden;
  1039 +
  1040 + .card-header {
  1041 + padding: 18px 20px;
  1042 + background: #f8f9fa;
  1043 + border-bottom: 1px solid #e4e7ed;
  1044 + display: flex;
  1045 + align-items: center;
  1046 + gap: 8px;
  1047 +
  1048 + i {
  1049 + font-size: 18px;
  1050 + color: #409EFF;
  1051 + }
  1052 +
  1053 + .card-title {
  1054 + font-size: 15px;
  1055 + font-weight: 600;
  1056 + color: #303133;
  1057 + }
  1058 + }
  1059 +
  1060 + .card-body {
  1061 + padding: 20px;
  1062 + }
852 1063 }
853 1064 }
854 1065 }
855 1066  
  1067 + // 消费行为网格
856 1068 .behavior-grid {
857 1069 display: grid;
858 1070 grid-template-columns: repeat(3, 1fr);
859   - gap: 12px;
  1071 + gap: 16px;
860 1072  
861 1073 .behavior-item {
862 1074 background: #f8f9fa;
863   - padding: 10px;
864   - border-radius: 6px;
865   - border: 1px solid #ebeef5;
  1075 + border-radius: 10px;
  1076 + border: 1px solid #e4e7ed;
  1077 + padding: 16px;
  1078 + display: flex;
  1079 + align-items: center;
  1080 + gap: 12px;
  1081 + transition: all 0.3s ease;
866 1082  
867   - .behavior-label {
868   - font-size: 12px;
869   - color: #909399;
870   - margin-bottom: 6px;
  1083 + &:hover {
  1084 + background: #fff;
  1085 + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
  1086 + transform: translateY(-2px);
871 1087 }
872 1088  
873   - .behavior-value {
874   - font-size: 13px;
875   - color: #303133;
876   - font-weight: 500;
  1089 + .behavior-icon {
  1090 + width: 40px;
  1091 + height: 40px;
  1092 + border-radius: 8px;
  1093 + background: #409EFF;
  1094 + display: flex;
  1095 + align-items: center;
  1096 + justify-content: center;
  1097 + color: #fff;
  1098 + font-size: 18px;
  1099 + flex-shrink: 0;
877 1100 }
878   - }
879   - }
880   -
881   - .tag-list {
882   - display: flex;
883   - flex-direction: column;
884   - gap: 8px;
885 1101  
886   - .member-type-item {
887   - display: flex;
888   - align-items: center;
889   - gap: 10px;
890   - padding: 8px;
891   - background: #f8f9fa;
892   - border-radius: 6px;
893   - border-left: 3px solid #409EFF;
  1102 + .behavior-content {
  1103 + flex: 1;
  1104 + min-width: 0;
894 1105  
895   - .member-type-tag {
896   - font-weight: 600;
897   - font-size: 13px;
898   - padding: 4px 12px;
899   - }
  1106 + .behavior-label {
  1107 + font-size: 12px;
  1108 + color: #909399;
  1109 + margin-bottom: 6px;
  1110 + }
900 1111  
901   - .member-type-date {
902   - font-size: 12px;
903   - color: #909399;
904   - margin-left: auto;
  1112 + .behavior-value {
  1113 + font-size: 15px;
  1114 + color: #303133;
  1115 + font-weight: 600;
  1116 + white-space: nowrap;
  1117 + overflow: hidden;
  1118 + text-overflow: ellipsis;
  1119 + }
905 1120 }
906 1121 }
907 1122 }
908 1123  
909   - .info-list {
910   - .info-item {
  1124 + // 消费分析布局
  1125 + .analysis-layout {
  1126 + display: flex;
  1127 + gap: 20px;
  1128 + flex-wrap: wrap;
  1129 +
  1130 + .analysis-item {
  1131 + flex: 1;
  1132 + min-width: 200px;
  1133 + background: #f8f9fa;
  1134 + border-radius: 10px;
  1135 + border: 1px solid #e4e7ed;
  1136 + padding: 16px;
911 1137 display: flex;
912   - padding: 6px 0;
913   - border-bottom: 1px solid #f0f0f0;
  1138 + align-items: center;
  1139 + gap: 12px;
  1140 + transition: all 0.3s ease;
914 1141  
915   - &:last-child {
916   - border-bottom: none;
  1142 + &:hover {
  1143 + background: #fff;
  1144 + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
  1145 + transform: translateY(-2px);
917 1146 }
918 1147  
919   - .info-label {
920   - font-size: 13px;
921   - color: #909399;
922   - width: 90px;
  1148 + .analysis-icon {
  1149 + width: 40px;
  1150 + height: 40px;
  1151 + border-radius: 8px;
  1152 + background: #67C23A;
  1153 + display: flex;
  1154 + align-items: center;
  1155 + justify-content: center;
  1156 + color: #fff;
  1157 + font-size: 18px;
923 1158 flex-shrink: 0;
924 1159 }
925 1160  
926   - .info-value {
927   - font-size: 13px;
928   - color: #303133;
  1161 + .analysis-content {
929 1162 flex: 1;
  1163 + min-width: 0;
  1164 +
  1165 + .analysis-label {
  1166 + font-size: 12px;
  1167 + color: #909399;
  1168 + margin-bottom: 6px;
  1169 + }
  1170 +
  1171 + .analysis-value {
  1172 + font-size: 15px;
  1173 + color: #303133;
  1174 + font-weight: 600;
  1175 + }
930 1176 }
931 1177 }
932 1178 }
933 1179  
  1180 + // 趋势图
934 1181 .trend-chart {
935 1182 width: 100%;
936   - height: 350px;
  1183 + height: 400px;
937 1184 }
938 1185  
  1186 + // 分页
939 1187 .pagination-bar {
940 1188 display: flex;
941 1189 justify-content: flex-end;
942   - padding: 10px 0 4px 0;
943   - margin-top: 12px;
  1190 + padding: 16px 0 0 0;
  1191 + margin-top: 16px;
  1192 + border-top: 1px solid #e4e7ed;
944 1193 }
945 1194  
946 1195 .text-muted {
... ...
门店数据页面设计方案.md 0 → 100644
  1 +# 门店数据页面设计方案
  2 +
  3 +## 一、现有门店数据结构分析
  4 +
  5 +### 1.1 门店基础信息表 (lq_mdxx)
  6 +
  7 +**核心字段:**
  8 +- **基本信息**:门店编码(mdbm)、单据门店编号(djmdbh)、店名(dm)、城市(cs)、地址(dz)
  9 +- **联系信息**:姓名(xm)、电话号码(dhhm)、座机(zj)
  10 +- **状态信息**:最新状态(zxzt)、开业时间(kysj)、状态(status)
  11 +- **组织归属**:事业部(syb)、教育部(jyb)、科技部(kjb)、大项目部(dxmb)
  12 +- **归属时间**:归属起始时间(gsqssj)、归属终止时间(gszzsj)
  13 +- **工商信息**:工商名称(gsmc)、法人(fr)、有无社保(ywsb)
  14 +- **门店分类**:门店类型(StoreType: 200平门店/旗舰店)、门店类别(StoreCategory: A类/B类/C类)
  15 +- **目标设置**:
  16 + - 门店生命线(xsyj)
  17 + - 消耗业绩(xhyj)
  18 + - 项目数(xms)
  19 + - 人头1(rt1)、人头2(rt2)
  20 + - 人次(rc)
  21 +- **人员信息**:在职人数(zzrs)
  22 +
  23 +### 1.2 门店相关业务表
  24 +
  25 +**开单相关:**
  26 +- `lq_kd_kdjlb` - 开单记录表(门店业绩来源)
  27 +- `lq_kd_pxmx` - 开单品项明细表
  28 +- `lq_kd_jksyj` - 开单健康师业绩表
  29 +
  30 +**消耗相关:**
  31 +- `lq_xh_hyhk` - 会员耗卡表(消耗业绩来源)
  32 +- `lq_xh_pxmx` - 耗卡品项明细表
  33 +- `lq_xh_jksyj` - 耗卡健康师业绩表
  34 +- `lq_xh_kjbsyj` - 耗卡科技部业绩表
  35 +
  36 +**退卡相关:**
  37 +- `lq_hytk_hytk` - 会员退卡表
  38 +- `lq_tkjlb` - 退卡记录表
  39 +
  40 +**统计相关:**
  41 +- `lq_statistics_store_total_performance` - 门店总业绩统计表
  42 +- `lq_share_statistics_store` - 门店股份统计表
  43 +
  44 +## 二、现有门店相关接口梳理
  45 +
  46 +### 2.1 门店基础信息接口 (LqMdxxService)
  47 +
  48 +| 接口路径 | 方法 | 功能说明 |
  49 +|---------|------|---------|
  50 +| `/api/Extend/LqMdxx/{id}` | GET | 获取门店详细信息 |
  51 +| `/api/Extend/LqMdxx` | GET | 获取门店列表(分页,支持多条件筛选) |
  52 +| `/api/Extend/LqMdxx` | POST | 新建门店信息 |
  53 +| `/api/Extend/LqMdxx/{id}` | PUT | 更新门店信息 |
  54 +| `/api/Extend/LqMdxx/{id}/targets` | PUT | 更新门店目标字段 |
  55 +| `/api/Extend/LqMdxx/{id}` | DELETE | 删除门店(软删除) |
  56 +| `/api/Extend/LqMdxx/Selector` | GET | 获取门店下拉选择数据 |
  57 +| `/api/Extend/LqMdxx/Selector/StoreCategory` | GET | 获取门店类别下拉选择 |
  58 +| `/api/Extend/LqMdxx/Selector/StoreType` | GET | 获取门店类型下拉选择 |
  59 +
  60 +### 2.2 门店报表接口 (LqReportService)
  61 +
  62 +| 接口路径 | 方法 | 功能说明 |
  63 +|---------|------|---------|
  64 +| `/api/Extend/LqReport/get-store-performance-trend` | POST | 获取门店业绩趋势数据(月度/周度/日度) |
  65 +| `/api/Extend/LqReport/get-store-performance-ranking` | POST | 获取门店业绩排行榜 |
  66 +| `/api/Extend/LqReport/get-store-item-statistics` | POST | 获取门店项目指标统计数据 |
  67 +| `/api/Extend/LqReport/get-store-remaining-rights` | POST | 获取门店剩余权益统计 |
  68 +| `/api/Extend/LqReport/get-store-performance-comparison` | POST | 获取门店业绩对比数据 |
  69 +
  70 +### 2.3 门店日报接口 (LqDailyReportService)
  71 +
  72 +| 接口路径 | 方法 | 功能说明 |
  73 +|---------|------|---------|
  74 +| `/api/Extend/LqDailyReport/get-store-daily-statistics` | POST | 获取门店每日运营统计数据 |
  75 +
  76 +### 2.4 门店统计接口 (LqStatisticsService)
  77 +
  78 +| 接口路径 | 方法 | 功能说明 |
  79 +|---------|------|---------|
  80 +| `/api/Extend/LqStatistics/GetStorePerformanceList` | GET | 获取门店业绩统计列表(消耗业绩) |
  81 +| `/api/Extend/LqStatistics/GetStoreOrderPerformanceList` | GET | 获取门店开单业绩统计列表 |
  82 +| `/api/Extend/LqStatistics/StoreStatistics` | POST | 获取门店统计信息(支持日期范围) |
  83 +
  84 +## 三、门店数据页面可展示维度
  85 +
  86 +### 3.1 门店概览(Dashboard)
  87 +
  88 +**核心指标卡片:**
  89 +1. **门店总数**:当前状态为"开店"的门店数量
  90 +2. **本月总业绩**:所有门店本月开单业绩总和
  91 +3. **本月消耗业绩**:所有门店本月消耗业绩总和
  92 +4. **本月完成率**:总消耗业绩 / 总目标业绩
  93 +5. **活跃门店数**:本月有开单或消耗的门店数量
  94 +6. **平均单店业绩**:总业绩 / 门店数
  95 +
  96 +**数据来源:**
  97 +- 门店列表:`/api/Extend/LqMdxx`
  98 +- 业绩统计:`/api/Extend/LqStatistics/GetStoreOrderPerformanceList` 或 `/api/Extend/LqStatistics/GetStorePerformanceList`
  99 +
  100 +### 3.2 门店列表页
  101 +
  102 +**展示内容:**
  103 +- 门店基本信息:店名、编码、城市、地址、状态
  104 +- 门店分类:门店类型、门店类别
  105 +- 组织归属:所属事业部/教育部/科技部/大项目部
  106 +- 目标信息:门店生命线、消耗业绩目标、项目数目标
  107 +- 实时业绩:本月开单业绩、本月消耗业绩、完成率
  108 +- 运营指标:人头数、人次、项目数
  109 +
  110 +**功能:**
  111 +- 多条件筛选(城市、状态、门店类型、门店类别、组织归属)
  112 +- 排序(按业绩、完成率等)
  113 +- 分页展示
  114 +- 点击门店进入详情页
  115 +
  116 +**数据来源:**
  117 +- 门店列表:`/api/Extend/LqMdxx`
  118 +- 业绩数据:`/api/Extend/LqStatistics/StoreStatistics`(实时计算)
  119 +
  120 +### 3.3 门店详情页
  121 +
  122 +#### 3.3.1 门店基础信息卡片
  123 +
  124 +**展示内容:**
  125 +- 门店基本信息:店名、编码、城市、地址、联系方式
  126 +- 门店状态:最新状态、开业时间、在职人数
  127 +- 组织归属:所属部门、归属时间范围
  128 +- 门店分类:门店类型、门店类别
  129 +- 目标设置:各项目标值(可编辑)
  130 +
  131 +**数据来源:**
  132 +- `/api/Extend/LqMdxx/{id}`
  133 +
  134 +#### 3.3.2 业绩概览
  135 +
  136 +**核心指标:**
  137 +- **开单业绩**:本月开单金额、开单次数、平均开单金额
  138 +- **消耗业绩**:本月消耗金额、消耗次数、平均消耗金额
  139 +- **完成率**:消耗业绩 / 目标业绩
  140 +- **剩余权益**:门店会员剩余权益总额
  141 +- **退卡情况**:退卡金额、退卡次数
  142 +
  143 +**数据来源:**
  144 +- `/api/Extend/LqStatistics/StoreStatistics`
  145 +- `/api/Extend/LqReport/get-store-remaining-rights`
  146 +
  147 +#### 3.3.3 业绩趋势图
  148 +
  149 +**展示内容:**
  150 +- 近12个月开单业绩趋势(折线图)
  151 +- 近12个月消耗业绩趋势(折线图)
  152 +- 开单与消耗对比(双折线图)
  153 +
  154 +**数据来源:**
  155 +- `/api/Extend/LqReport/get-store-performance-trend`
  156 +
  157 +#### 3.3.4 运营指标分析
  158 +
  159 +**展示内容:**
  160 +- **人头数**:去重后的消费会员数
  161 +- **人次**:日度去重客户数
  162 +- **项目数**:消耗项目总数(原始项目数、加班项目数、陪同项目数)
  163 +- **客单价**:消耗业绩 / 消耗人次
  164 +- **项目单价**:消耗业绩 / 项目数
  165 +
  166 +**数据来源:**
  167 +- `/api/Extend/LqDailyReport/get-store-daily-statistics`
  168 +
  169 +#### 3.3.5 项目指标统计
  170 +
  171 +**展示内容:**
  172 +- 消耗项目数、消耗率
  173 +- 客单项目数、消耗客单价
  174 +- 开单金额、消耗金额
  175 +- 消耗人次
  176 +
  177 +**数据来源:**
  178 +- `/api/Extend/LqReport/get-store-item-statistics`
  179 +
  180 +#### 3.3.6 品项分析
  181 +
  182 +**展示内容:**
  183 +- 各品项消耗金额排行(柱状图)
  184 +- 各品项消耗次数排行
  185 +- 品项分类占比(饼图:生美/科美/医美/产品)
  186 +
  187 +**数据来源:**
  188 +- `/api/Extend/LqReport/get-store-item-statistics`(需要扩展接口支持品项明细)
  189 +
  190 +#### 3.3.7 每日运营数据
  191 +
  192 +**展示内容:**
  193 +- 日期、人头数、人次、项目数、消耗业绩
  194 +- 支持时间范围筛选
  195 +- 表格展示,支持排序
  196 +
  197 +**数据来源:**
  198 +- `/api/Extend/LqDailyReport/get-store-daily-statistics`
  199 +
  200 +#### 3.3.8 门店对比分析
  201 +
  202 +**展示内容:**
  203 +- 与同类型门店对比(门店类型、门店类别)
  204 +- 与同组织门店对比(事业部/教育部/科技部)
  205 +- 业绩排名、完成率排名
  206 +
  207 +**数据来源:**
  208 +- `/api/Extend/LqReport/get-store-performance-comparison`
  209 +- `/api/Extend/LqReport/get-store-performance-ranking`
  210 +
  211 +### 3.4 门店排行榜
  212 +
  213 +**展示内容:**
  214 +- 门店业绩排行榜(条形图)
  215 +- 门店完成率排行榜
  216 +- 门店消耗业绩排行榜
  217 +- 支持按月份筛选
  218 +
  219 +**数据来源:**
  220 +- `/api/Extend/LqReport/get-store-performance-ranking`
  221 +
  222 +### 3.5 门店对比分析页
  223 +
  224 +**展示内容:**
  225 +- 多门店业绩对比(折线图)
  226 +- 多门店完成率对比(柱状图)
  227 +- 多门店运营指标对比(表格)
  228 +
  229 +**数据来源:**
  230 +- `/api/Extend/LqReport/get-store-performance-comparison`
  231 +
  232 +## 四、页面设计方案
  233 +
  234 +### 4.1 页面结构
  235 +
  236 +```
  237 +门店数据页面
  238 +├── 门店概览(Dashboard)
  239 +│ ├── 核心指标卡片(6个)
  240 +│ ├── 门店业绩趋势图(近12个月)
  241 +│ ├── 门店排行榜(Top 10)
  242 +│ └── 门店分布地图(可选)
  243 +│
  244 +├── 门店列表
  245 +│ ├── 筛选条件栏
  246 +│ ├── 门店列表表格
  247 +│ └── 分页组件
  248 +│
  249 +├── 门店详情(点击门店进入)
  250 +│ ├── 基础信息卡片
  251 +│ ├── 业绩概览卡片
  252 +│ ├── 业绩趋势图
  253 +│ ├── 运营指标分析
  254 +│ ├── 项目指标统计
  255 +│ ├── 品项分析
  256 +│ ├── 每日运营数据表格
  257 +│ └── 门店对比分析
  258 +│
  259 +└── 门店对比(多门店对比)
  260 + ├── 门店选择器
  261 + ├── 业绩对比图表
  262 + └── 指标对比表格
  263 +```
  264 +
  265 +### 4.2 页面布局建议
  266 +
  267 +**参考现有页面风格:**
  268 +- 使用卡片式布局(参考 `form9.vue` 领导驾驶舱)
  269 +- 统一的颜色方案(主色:#409EFF)
  270 +- 统计卡片高度100px,内边距12px,圆角12px
  271 +- 图表使用 ECharts
  272 +- 表格使用 NCC-table 或 Element UI Table
  273 +
  274 +### 4.3 交互设计
  275 +
  276 +**筛选功能:**
  277 +- 时间范围选择(年月选择器)
  278 +- 门店多选(支持全选)
  279 +- 城市筛选
  280 +- 门店类型/类别筛选
  281 +- 组织归属筛选
  282 +- 状态筛选
  283 +
  284 +**数据刷新:**
  285 +- 手动刷新按钮
  286 +- 自动刷新(可选,如每5分钟)
  287 +
  288 +**数据导出:**
  289 +- 支持导出Excel
  290 +- 支持导出PDF(可选)
  291 +
  292 +## 五、需要新增的接口
  293 +
  294 +### 5.1 门店概览数据接口
  295 +
  296 +**接口路径:** `/api/Extend/LqReport/get-store-overview`
  297 +
  298 +**功能:** 获取门店概览的核心指标数据
  299 +
  300 +**返回数据:**
  301 +```json
  302 +{
  303 + "totalStores": 50,
  304 + "activeStores": 45,
  305 + "monthBillingAmount": 1000000.00,
  306 + "monthConsumeAmount": 800000.00,
  307 + "completionRate": 80.00,
  308 + "avgStorePerformance": 20000.00
  309 +}
  310 +```
  311 +
  312 +### 5.2 门店品项明细统计接口
  313 +
  314 +**接口路径:** `/api/Extend/LqReport/get-store-item-detail-statistics`
  315 +
  316 +**功能:** 获取门店各品项的消耗统计(用于品项分析)
  317 +
  318 +**参数:**
  319 +- storeId: 门店ID
  320 +- startTime: 开始时间
  321 +- endTime: 结束时间
  322 +
  323 +**返回数据:**
  324 +```json
  325 +{
  326 + "storeId": "xxx",
  327 + "storeName": "xxx",
  328 + "items": [
  329 + {
  330 + "itemId": "xxx",
  331 + "itemName": "xxx",
  332 + "itemCategory": "生美",
  333 + "consumeAmount": 10000.00,
  334 + "consumeCount": 50,
  335 + "consumeQuantity": 100
  336 + }
  337 + ]
  338 +}
  339 +```
  340 +
  341 +### 5.3 门店多维度对比接口(扩展)
  342 +
  343 +**接口路径:** `/api/Extend/LqReport/get-store-multi-comparison`
  344 +
  345 +**功能:** 支持多门店、多指标、多时间段的对比分析
  346 +
  347 +## 六、技术实现建议
  348 +
  349 +### 6.1 前端组件
  350 +
  351 +**主要组件:**
  352 +- `store-dashboard.vue` - 门店概览页
  353 +- `store-list.vue` - 门店列表页
  354 +- `store-detail.vue` - 门店详情页
  355 +- `store-comparison.vue` - 门店对比页
  356 +- `store-stat-card.vue` - 统计卡片组件(可复用)
  357 +- `store-trend-chart.vue` - 趋势图组件(可复用)
  358 +
  359 +### 6.2 后端服务
  360 +
  361 +**建议创建:** `StoreDataService.cs` 专门用于门店数据统计
  362 +
  363 +**职责:**
  364 +- 聚合多个数据源的统计结果
  365 +- 提供统一的门店数据查询接口
  366 +- 缓存常用统计数据(提升性能)
  367 +
  368 +### 6.3 数据缓存策略
  369 +
  370 +**建议:**
  371 +- 门店基础信息:缓存30分钟
  372 +- 门店业绩数据:缓存10分钟(实时性要求高)
  373 +- 门店趋势数据:缓存1小时(历史数据变化少)
  374 +
  375 +## 七、开发优先级建议
  376 +
  377 +### 第一阶段(核心功能)
  378 +1. ✅ 门店列表页(基础信息展示、筛选、排序)
  379 +2. ✅ 门店详情页(基础信息、业绩概览、趋势图)
  380 +3. ✅ 门店概览Dashboard(核心指标卡片)
  381 +
  382 +### 第二阶段(增强功能)
  383 +4. ✅ 门店运营指标分析
  384 +5. ✅ 门店项目指标统计
  385 +6. ✅ 门店排行榜
  386 +
  387 +### 第三阶段(高级功能)
  388 +7. ✅ 门店品项分析
  389 +8. ✅ 门店对比分析
  390 +9. ✅ 门店每日运营数据表格
  391 +
  392 +### 第四阶段(优化功能)
  393 +10. ✅ 数据导出功能
  394 +11. ✅ 数据缓存优化
  395 +12. ✅ 性能优化
  396 +
  397 +## 八、注意事项
  398 +
  399 +### 8.1 数据权限
  400 +- 需要根据用户组织权限过滤门店数据
  401 +- 参考现有权限控制逻辑(如 `LqZjlMdsmxszService.GetManagedStores`)
  402 +
  403 +### 8.2 数据一致性
  404 +- 统计数据和列表数据使用相同的过滤条件
  405 +- 时间范围、门店筛选保持一致
  406 +
  407 +### 8.3 性能优化
  408 +- 大数据量查询使用分页
  409 +- 复杂统计查询考虑使用统计表(如 `lq_statistics_store_total_performance`)
  410 +- 避免N+1查询问题
  411 +
  412 +### 8.4 数据准确性
  413 +- 确保统计逻辑与业务逻辑一致
  414 +- 注意 `F_IsEffective = 1` 的有效数据过滤
  415 +- 注意门店状态过滤(`zxzt = '开店'`)
  416 +
  417 +## 九、参考现有页面
  418 +
  419 +- **领导驾驶舱** (`form9.vue`) - 参考布局和样式
  420 +- **会员画像** (`member-portrait-dialog.vue`) - 参考详情页设计
  421 +- **KPI数据穿透** (`kpi-drill-dialog.vue`) - 参考数据展示方式
  422 +
... ...