Commit da3042f75aa8e413de31a3940f6aa811dd01aed4
1 parent
43ce1b69
Add vuedraggable for drag-and-drop functionality in dashboard; update labels for clarity
Showing
3 changed files
with
134 additions
and
34 deletions
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
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, | ... | ... |