Commit da3042f75aa8e413de31a3940f6aa811dd01aed4

Authored by “wangming”
1 parent 43ce1b69

Add vuedraggable for drag-and-drop functionality in dashboard; update labels for clarity

store-pc/package-lock.json
... ... @@ -18,6 +18,7 @@
18 18 "normalize.css": "^8.0.1",
19 19 "vue": "^2.6.14",
20 20 "vue-router": "^3.5.1",
  21 + "vuedraggable": "^2.24.3",
21 22 "vuex": "^3.6.2"
22 23 },
23 24 "devDependencies": {
... ... @@ -15394,6 +15395,12 @@
15394 15395 "node": ">=0.10.0"
15395 15396 }
15396 15397 },
  15398 + "node_modules/sortablejs": {
  15399 + "version": "1.10.2",
  15400 + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.10.2.tgz",
  15401 + "integrity": "sha512-YkPGufevysvfwn5rfdlGyrGjt7/CRHwvRPogD/lC+TnvcN29jDpCifKP+rBqf+LRldfXSTh+0CGLcSg0VIxq3A==",
  15402 + "license": "MIT"
  15403 + },
15397 15404 "node_modules/source-list-map": {
15398 15405 "version": "2.0.1",
15399 15406 "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
... ... @@ -17618,6 +17625,15 @@
17618 17625 "node": "^10 || ^12 || >=14"
17619 17626 }
17620 17627 },
  17628 + "node_modules/vuedraggable": {
  17629 + "version": "2.24.3",
  17630 + "resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-2.24.3.tgz",
  17631 + "integrity": "sha512-6/HDXi92GzB+Hcs9fC6PAAozK1RLt1ewPTLjK0anTYguXLAeySDmcnqE8IC0xa7shvSzRjQXq3/+dsZ7ETGF3g==",
  17632 + "license": "MIT",
  17633 + "dependencies": {
  17634 + "sortablejs": "1.10.2"
  17635 + }
  17636 + },
17621 17637 "node_modules/vuex": {
17622 17638 "version": "3.6.2",
17623 17639 "resolved": "https://registry.npmjs.org/vuex/-/vuex-3.6.2.tgz",
... ...
store-pc/package.json
... ... @@ -18,6 +18,7 @@
18 18 "normalize.css": "^8.0.1",
19 19 "vue": "^2.6.14",
20 20 "vue-router": "^3.5.1",
  21 + "vuedraggable": "^2.24.3",
21 22 "vuex": "^3.6.2"
22 23 },
23 24 "devDependencies": {
... ...
store-pc/src/views/dashboard/index.vue
... ... @@ -5,7 +5,7 @@
5 5 <div class="store-info">
6 6 <div class="store-name">{{ currentStoreName }} · 工作台</div>
7 7 <div class="store-subtitle">
8   - 今日预约 · 开单,一眼总览
  8 + 今日到店 · 开单,一眼总览
9 9 <span class="schedule-link" @click.stop="$store.commit('SET_SCHEDULE_DIALOG', true)">打开排班</span>
10 10 </div>
11 11 </div>
... ... @@ -89,7 +89,7 @@
89 89 </div>
90 90 </div>
91 91  
92   - <div class="layout-grid">
  92 + <div class="layout-grid" :class="{ 'layout-grid--reminder-open': reminderCenterOpen }">
93 93 <!-- 左侧:概览 + 六大板块 -->
94 94 <div class="left-column">
95 95 <!-- 今日概览 -->
... ... @@ -145,12 +145,22 @@
145 145 </div>
146 146 </div>
147 147  
148   - <!-- 六大业务板块 -->
  148 + <!-- 业务板块:支持拖拽排序并本地记忆顺序 -->
149 149 <div class="modules-grid">
150   - <el-row :gutter="16">
  150 + <draggable
  151 + v-model="modules"
  152 + tag="el-row"
  153 + :component-data="{ props: { gutter: 16 } }"
  154 + :options="moduleDragOptions"
  155 + @end="onModuleDragEnd"
  156 + >
151 157 <el-col v-for="module in modules" :key="module.key" :span="8">
152   - <el-card shadow="hover" class="module-card" :body-style="{ padding: '20px 20px 18px' }"
153   - @click.native="handleModuleCardClick($event, module)">
  158 + <el-card
  159 + shadow="hover"
  160 + class="module-card module-card--draggable"
  161 + :body-style="{ padding: '20px 20px 18px' }"
  162 + @click.native="handleModuleCardClick($event, module)"
  163 + >
154 164 <div class="module-header">
155 165 <div class="module-title-wrap">
156 166 <div class="module-title">{{ module.title }}</div>
... ... @@ -166,19 +176,28 @@
166 176 </div>
167 177 <div class="module-actions-spacer"></div>
168 178 <div class="module-actions">
169   - <button type="button" class="primary-action" @click.stop="handleModulePrimary(module)">{{
170   - module.primaryAction }}</button>
171   - <span v-if="module.secondaryText" class="secondary-text"
172   - @click.stop="handleModuleSecondary(module)">{{
173   - module.secondaryText }}</span>
  179 + <button
  180 + type="button"
  181 + class="primary-action"
  182 + @click.stop="handleModulePrimary(module)"
  183 + >
  184 + {{ module.primaryAction }}
  185 + </button>
  186 + <span
  187 + v-if="module.secondaryText"
  188 + class="secondary-text"
  189 + @click.stop="handleModuleSecondary(module)"
  190 + >
  191 + {{ module.secondaryText }}
  192 + </span>
174 193 </div>
175 194 </el-card>
176 195 </el-col>
177   - </el-row>
  196 + </draggable>
178 197 </div>
179 198 </div>
180 199  
181   - <!-- 提醒中心侧栏(今日邀约 / 今日预约 / 生日提醒 / 沉睡提醒) -->
  200 + <!-- 提醒中心侧栏(我的待办 / 今日到店 / 生日提醒 / 沉睡提醒) -->
182 201 <div class="reminder-center">
183 202 <!-- 右侧窄栏入口 -->
184 203 <div class="reminder-center-toggle">
... ... @@ -230,7 +249,7 @@
230 249 </div>
231 250 <div v-else class="empty-tip">暂无待办事项</div>
232 251 </el-tab-pane>
233   - <el-tab-pane label="今日预约" name="booking">
  252 + <el-tab-pane label="今日到店" name="booking">
234 253 <div v-if="todayBooking.length" class="timeline-list">
235 254 <div v-for="item in todayBooking" :key="item.id" class="timeline-item">
236 255 <div class="time">{{ item.time }}</div>
... ... @@ -303,7 +322,10 @@
303 322 </div>
304 323 <div v-if="birthdayDisplayList.length" class="timeline-list cal-member-list">
305 324 <div v-for="item in birthdayDisplayList" :key="item.id" class="birthday-item">
306   - <div class="birthday-day-badge">{{ item.day }}日</div>
  325 + <div class="birthday-day-badge">
  326 + <i class="el-icon-present birthday-icon"></i>
  327 + <span class="birthday-day-text">{{ item.day }}日</span>
  328 + </div>
307 329 <div class="birthday-info">
308 330 <div class="birthday-name">
309 331 {{ item.name }}
... ... @@ -405,6 +427,7 @@
405 427 </template>
406 428  
407 429 <script>
  430 +import draggable from 'vuedraggable'
408 431 import MemberProfileDialog from '@/components/MemberProfileDialog.vue'
409 432 import TuokeLeadDialog from '@/components/TuokeLeadDialog.vue'
410 433 import InviteAddDialog from '@/components/InviteAddDialog.vue'
... ... @@ -438,7 +461,8 @@ export default {
438 461 ConsumeListDialog,
439 462 RefundListDialog,
440 463 GoldTriangleDialog,
441   - StoreTargetDialog
  464 + StoreTargetDialog,
  465 + draggable
442 466 },
443 467 data() {
444 468 return {
... ... @@ -549,7 +573,7 @@ export default {
549 573 icon: 'el-icon-date',
550 574 iconBg: 'linear-gradient(135deg, #f97316, #facc15)',
551 575 metricValue: 0,
552   - metricLabel: '今日预约',
  576 + metricLabel: '今日到店',
553 577 primaryAction: '新建预约',
554 578 secondaryText: '打开预约日历'
555 579 },
... ... @@ -618,6 +642,7 @@ export default {
618 642 secondaryText: ''
619 643 }
620 644 ],
  645 + moduleOrderStorageKey: 'store_dashboard_module_order',
621 646 todayInvite: [],
622 647 todayBooking: [
623 648 {
... ... @@ -847,6 +872,14 @@ export default {
847 872 while (days.length % 7 !== 0) days.push({ day: null, members: [] })
848 873 return days
849 874 },
  875 + moduleDragOptions() {
  876 + return {
  877 + animation: 200,
  878 + ghostClass: 'module-card--ghost',
  879 + chosenClass: 'module-card--chosen',
  880 + handle: '.module-card--draggable'
  881 + }
  882 + },
850 883 storeTargetMonthValue() {
851 884 const now = new Date()
852 885 const next = new Date(now.getFullYear(), now.getMonth() + 1, 1)
... ... @@ -867,7 +900,7 @@ export default {
867 900 },
868 901 {
869 902 key: 'booking',
870   - label: '今日预约',
  903 + label: '今日到店',
871 904 count: this.todayBooking.length,
872 905 icon: 'el-icon-date'
873 906 },
... ... @@ -875,7 +908,7 @@ export default {
875 908 key: 'birthday',
876 909 label: '生日提醒',
877 910 count: birthdayTodayCount,
878   - icon: 'el-icon-gift'
  911 + icon: 'el-icon-present'
879 912 },
880 913 {
881 914 key: 'sleeping',
... ... @@ -918,6 +951,7 @@ export default {
918 951 this.$nextTick(() => {
919 952 this.reminderFirstRenderDone = true
920 953 })
  954 + this.applyModuleOrderFromStorage()
921 955 },
922 956 beforeDestroy() {
923 957 document.removeEventListener('fullscreenchange', this.onFullscreenChange)
... ... @@ -934,6 +968,39 @@ export default {
934 968 onFullscreenChange() {
935 969 this.isFullscreen = !!document.fullscreenElement
936 970 },
  971 + applyModuleOrderFromStorage() {
  972 + try {
  973 + const cached = window.localStorage.getItem(this.moduleOrderStorageKey)
  974 + if (!cached) return
  975 + const orderKeys = JSON.parse(cached)
  976 + if (!Array.isArray(orderKeys) || !orderKeys.length) return
  977 +
  978 + const map = this.modules.reduce((acc, item) => {
  979 + acc[item.key] = item
  980 + return acc
  981 + }, {})
  982 +
  983 + const ordered = []
  984 + orderKeys.forEach(key => {
  985 + if (map[key]) {
  986 + ordered.push(map[key])
  987 + delete map[key]
  988 + }
  989 + })
  990 +
  991 + Object.keys(map).forEach(key => {
  992 + ordered.push(map[key])
  993 + })
  994 +
  995 + this.modules = ordered
  996 + } catch (e) {
  997 + // ignore parse errors
  998 + }
  999 + },
  1000 + onModuleDragEnd() {
  1001 + const orderKeys = this.modules.map(item => item.key)
  1002 + window.localStorage.setItem(this.moduleOrderStorageKey, JSON.stringify(orderKeys))
  1003 + },
937 1004 handleUserCommand(cmd) {
938 1005 if (cmd === 'store') {
939 1006 this.storeSwitchDialogVisible = true
... ... @@ -1731,11 +1798,18 @@ export default {
1731 1798  
1732 1799 .layout-grid {
1733 1800 display: grid;
1734   - grid-template-columns: minmax(0, 1fr) auto;
  1801 + /* 收起时:主内容更宽,右侧只保留竖向按钮条宽度(约 88px + 间距) */
  1802 + grid-template-columns: minmax(0, 1fr) 108px;
1735 1803 gap: 20px;
1736 1804 flex: 1;
1737 1805 min-height: 0;
1738 1806 align-items: stretch;
  1807 + transition: grid-template-columns 0.25s ease-out;
  1808 +}
  1809 +
  1810 +.layout-grid--reminder-open {
  1811 + /* 展开时:主内容略窄,为竖向条 + 面板预留宽度 */
  1812 + grid-template-columns: minmax(0, 1fr) 520px;
1739 1813 }
1740 1814  
1741 1815 .left-column {
... ... @@ -2032,6 +2106,7 @@ export default {
2032 2106  
2033 2107 /* 提醒中心侧栏 - 容器 */
2034 2108 .reminder-center {
  2109 + position: relative;
2035 2110 display: flex;
2036 2111 flex-direction: row-reverse;
2037 2112 align-items: stretch;
... ... @@ -2050,6 +2125,12 @@ export default {
2050 2125 padding: 10px 4px;
2051 2126 gap: 6px;
2052 2127 pointer-events: auto;
  2128 + /* 固定宽度,避免在列宽动画时被压缩 */
  2129 + flex: 0 0 88px;
  2130 + position: relative;
  2131 + z-index: 2;
  2132 + /* 高度跟随列一起拉满 */
  2133 + height: 100%;
2053 2134 }
2054 2135  
2055 2136 /* 我的待办入口样式复用提醒入口,无需额外样式 */
... ... @@ -2154,19 +2235,6 @@ export default {
2154 2235 box-shadow: 0 6px 14px rgba(37, 99, 235, 0.35);
2155 2236 }
2156 2237  
2157   -.reminder-toggle-item.todo-entry {
2158   - /* 未选中时完全使用通用样式,不额外高亮 */
2159   -}
2160   -
2161   -.reminder-toggle-item.todo-entry.is-active {
2162   - background: linear-gradient(180deg, rgba(253, 230, 138, 1), rgba(252, 165, 165, 1));
2163   -}
2164   -
2165   -.reminder-toggle-item.todo-entry .label-text {
2166   - color: #92400e;
2167   - font-weight: 600;
2168   -}
2169   -
2170 2238 .reminder-toggle-close {
2171 2239 margin-top: 4px;
2172 2240 width: 26px;
... ... @@ -2189,9 +2257,14 @@ export default {
2189 2257 /* 提醒中心侧栏 - 主体卡片(右侧有间距滑出) */
2190 2258 .reminder-center-panel {
2191 2259 width: 400px;
  2260 + /* 与右侧竖向按钮条留出间距 */
2192 2261 margin-right: 20px;
2193 2262 pointer-events: auto;
2194 2263 display: flex;
  2264 + /* 固定宽度,由外层 grid 列宽承担动画 */
  2265 + flex: 0 0 400px;
  2266 + position: relative;
  2267 + z-index: 1;
2195 2268 }
2196 2269  
2197 2270 /* 提醒面板布局:独立四角圆角卡片 */
... ... @@ -2652,7 +2725,7 @@ export default {
2652 2725 }
2653 2726  
2654 2727 .birthday-day-badge {
2655   - width: 36px;
  2728 + width: 40px;
2656 2729 height: 36px;
2657 2730 border-radius: 8px;
2658 2731 background: rgba(99, 102, 241, 0.08);
... ... @@ -2664,6 +2737,16 @@ export default {
2664 2737 justify-content: center;
2665 2738 flex-shrink: 0;
2666 2739 text-align: center;
  2740 + flex-direction: column;
  2741 +}
  2742 +
  2743 +.birthday-icon {
  2744 + font-size: 14px;
  2745 + margin-bottom: 2px;
  2746 +}
  2747 +
  2748 +.birthday-day-text {
  2749 + line-height: 1;
2667 2750 }
2668 2751  
2669 2752 .birthday-info,
... ...