Commit fdbd231e57745f4c37fb92a5c79eeb3ea668a1ab
1 parent
8a06965a
init
Showing
14 changed files
with
1043 additions
and
358 deletions
src/api/index.ts
| @@ -56,6 +56,8 @@ export const riderApi = { | @@ -56,6 +56,8 @@ export const riderApi = { | ||
| 56 | request.post('/api/admin/rider/setEnableStatus', null, { params: { riderId, status } }), | 56 | request.post('/api/admin/rider/setEnableStatus', null, { params: { riderId, status } }), |
| 57 | setType: (riderId: number, type: number) => | 57 | setType: (riderId: number, type: number) => |
| 58 | request.post('/api/admin/rider/setType', null, { params: { riderId, type } }), | 58 | request.post('/api/admin/rider/setType', null, { params: { riderId, type } }), |
| 59 | + designateCandidates: (orderId: number) => | ||
| 60 | + request.get('/api/admin/rider/order/candidates', { params: { orderId } }), | ||
| 59 | designate: (orderId: number, riderId: number) => | 61 | designate: (orderId: number, riderId: number) => |
| 60 | request.post('/api/admin/rider/order/designate', null, { params: { orderId, riderId } }), | 62 | request.post('/api/admin/rider/order/designate', null, { params: { orderId, riderId } }), |
| 61 | setTrans: (orderId: number, trans: number) => | 63 | setTrans: (orderId: number, trans: number) => |
| @@ -90,6 +92,7 @@ export const refundApi = { | @@ -90,6 +92,7 @@ export const refundApi = { | ||
| 90 | export const openApi = { | 92 | export const openApi = { |
| 91 | list: (page = 1) => request.get('/api/platform/open/list', { params: { page } }), | 93 | list: (page = 1) => request.get('/api/platform/open/list', { params: { page } }), |
| 92 | create: (data: any) => request.post('/api/platform/open/create', null, { params: data }), | 94 | create: (data: any) => request.post('/api/platform/open/create', null, { params: data }), |
| 95 | + mockDeliveryCreate: (data: any) => request.post('/api/platform/open/mockDelivery/create', data), | ||
| 93 | resetSecret: (appId: number) => | 96 | resetSecret: (appId: number) => |
| 94 | request.post('/api/platform/open/resetSecret', null, { params: { appId } }), | 97 | request.post('/api/platform/open/resetSecret', null, { params: { appId } }), |
| 95 | setStatus: (appId: number, status: number) => | 98 | setStatus: (appId: number, status: number) => |
src/layouts/MainLayout.vue
| 1 | <template> | 1 | <template> |
| 2 | - <a-layout style="min-height: 100vh"> | ||
| 3 | - <a-layout-sider v-model:collapsed="collapsed" collapsible> | ||
| 4 | - <div class="logo">{{ collapsed ? '地利' : '地利外卖管理' }}</div> | ||
| 5 | - <a-menu | ||
| 6 | - v-model:selectedKeys="selectedKeys" | ||
| 7 | - theme="dark" | ||
| 8 | - mode="inline" | ||
| 9 | - @click="onMenuClick" | ||
| 10 | - > | ||
| 11 | - <a-menu-item key="/city"> | ||
| 12 | - <template #icon><global-outlined /></template> | ||
| 13 | - 城市管理 | ||
| 14 | - </a-menu-item> | ||
| 15 | - <a-menu-item key="/substation"> | ||
| 16 | - <template #icon><apartment-outlined /></template> | ||
| 17 | - 分站管理 | ||
| 18 | - </a-menu-item> | ||
| 19 | - <a-sub-menu key="merchant"> | ||
| 20 | - <template #icon><shop-outlined /></template> | ||
| 21 | - <template #title>商家管理</template> | ||
| 22 | - <a-menu-item key="/merchant/enter">入驻申请</a-menu-item> | ||
| 23 | - <a-menu-item key="/merchant/store">店铺管理</a-menu-item> | ||
| 24 | - </a-sub-menu> | ||
| 25 | - <a-menu-item key="/rider"> | ||
| 26 | - <template #icon><user-outlined /></template> | ||
| 27 | - 骑手管理 | ||
| 28 | - </a-menu-item> | ||
| 29 | - <a-menu-item key="/rider/evaluate"> | ||
| 30 | - <template #icon><star-outlined /></template> | ||
| 31 | - 骑手评价 | ||
| 32 | - </a-menu-item> | ||
| 33 | - <a-sub-menu key="orders"> | ||
| 34 | - <template #icon><unordered-list-outlined /></template> | ||
| 35 | - <template #title>订单管理</template> | ||
| 36 | - <a-menu-item key="/order">订单列表</a-menu-item> | ||
| 37 | - <a-menu-item key="/refund">退款管理</a-menu-item> | ||
| 38 | - <a-menu-item key="/delivery/order">配送订单</a-menu-item> | ||
| 39 | - </a-sub-menu> | ||
| 40 | - <a-menu-item key="/open"> | ||
| 41 | - <template #icon><api-outlined /></template> | ||
| 42 | - 开放平台 | ||
| 43 | - </a-menu-item> | ||
| 44 | - </a-menu> | ||
| 45 | - </a-layout-sider> | ||
| 46 | - <a-layout> | ||
| 47 | - <a-layout-header style="background:#fff;padding:0 16px;display:flex;align-items:center;justify-content:flex-end"> | ||
| 48 | - <a-dropdown> | ||
| 49 | - <a-button type="text">管理员 <down-outlined /></a-button> | ||
| 50 | - <template #overlay> | ||
| 51 | - <a-menu> | ||
| 52 | - <a-menu-item @click="handleLogout">退出登录</a-menu-item> | ||
| 53 | - </a-menu> | ||
| 54 | - </template> | ||
| 55 | - </a-dropdown> | ||
| 56 | - </a-layout-header> | ||
| 57 | - <a-layout-content style="margin:16px"> | 2 | + <div class="layout-shell"> |
| 3 | + <aside class="soft-sider" :class="{ collapsed }"> | ||
| 4 | + <button class="sider-toggle" type="button" @click="collapsed = !collapsed"> | ||
| 5 | + <menu-fold-outlined v-if="!collapsed" /> | ||
| 6 | + <menu-unfold-outlined v-else /> | ||
| 7 | + </button> | ||
| 8 | + | ||
| 9 | + <div class="brand-block"> | ||
| 10 | + <div class="brand-mark">DL</div> | ||
| 11 | + <div v-if="!collapsed" class="brand-copy"> | ||
| 12 | + <strong>地利骑手中台</strong> | ||
| 13 | + <span>Soft operations cockpit</span> | ||
| 14 | + </div> | ||
| 15 | + </div> | ||
| 16 | + | ||
| 17 | + <div class="menu-scroll"> | ||
| 18 | + <a-menu | ||
| 19 | + v-model:selectedKeys="selectedKeys" | ||
| 20 | + mode="inline" | ||
| 21 | + @click="onMenuClick" | ||
| 22 | + > | ||
| 23 | + <a-menu-item key="/dashboard"> | ||
| 24 | + <template #icon><home-outlined /></template> | ||
| 25 | + 工作台 | ||
| 26 | + </a-menu-item> | ||
| 27 | + <a-menu-item key="/city"> | ||
| 28 | + <template #icon><global-outlined /></template> | ||
| 29 | + 租户管理 | ||
| 30 | + </a-menu-item> | ||
| 31 | + <a-menu-item key="/substation"> | ||
| 32 | + <template #icon><apartment-outlined /></template> | ||
| 33 | + 分站管理 | ||
| 34 | + </a-menu-item> | ||
| 35 | + <a-sub-menu key="merchant"> | ||
| 36 | + <template #icon><shop-outlined /></template> | ||
| 37 | + <template #title>商家管理</template> | ||
| 38 | + <a-menu-item key="/merchant/enter">入驻申请</a-menu-item> | ||
| 39 | + <a-menu-item key="/merchant/store">店铺管理</a-menu-item> | ||
| 40 | + </a-sub-menu> | ||
| 41 | + <a-menu-item key="/rider"> | ||
| 42 | + <template #icon><user-outlined /></template> | ||
| 43 | + 骑手管理 | ||
| 44 | + </a-menu-item> | ||
| 45 | + <a-menu-item key="/rider/evaluate"> | ||
| 46 | + <template #icon><star-outlined /></template> | ||
| 47 | + 骑手评价 | ||
| 48 | + </a-menu-item> | ||
| 49 | + <a-sub-menu key="orders"> | ||
| 50 | + <template #icon><unordered-list-outlined /></template> | ||
| 51 | + <template #title>订单管理</template> | ||
| 52 | + <a-menu-item key="/order">订单列表</a-menu-item> | ||
| 53 | + <a-menu-item key="/refund">退款管理</a-menu-item> | ||
| 54 | + <a-menu-item key="/delivery/order">配送订单</a-menu-item> | ||
| 55 | + </a-sub-menu> | ||
| 56 | + <a-sub-menu key="open"> | ||
| 57 | + <template #icon><api-outlined /></template> | ||
| 58 | + <template #title>开放平台</template> | ||
| 59 | + <a-menu-item key="/open">应用管理</a-menu-item> | ||
| 60 | + <a-menu-item key="/open/mock-delivery">模拟推单</a-menu-item> | ||
| 61 | + </a-sub-menu> | ||
| 62 | + </a-menu> | ||
| 63 | + </div> | ||
| 64 | + | ||
| 65 | + <div v-if="!collapsed" class="sider-foot"> | ||
| 66 | + <div class="soft-chip soft-chip-green">在线协作</div> | ||
| 67 | + <p>面向运营、分站与骑手管理的统一轻量工作台。</p> | ||
| 68 | + </div> | ||
| 69 | + </aside> | ||
| 70 | + | ||
| 71 | + <main class="content-column"> | ||
| 72 | + <header class="soft-topbar"> | ||
| 73 | + <div> | ||
| 74 | + <p class="eyebrow">Soft-Neo Admin</p> | ||
| 75 | + <h1>{{ currentTitle }}</h1> | ||
| 76 | + </div> | ||
| 77 | + <div class="topbar-actions"> | ||
| 78 | + <div class="date-pill"> | ||
| 79 | + <calendar-outlined /> | ||
| 80 | + <span>{{ todayLabel }}</span> | ||
| 81 | + </div> | ||
| 82 | + <a-dropdown> | ||
| 83 | + <a-button type="text" class="profile-button"> | ||
| 84 | + <span class="profile-avatar">{{ avatarText }}</span> | ||
| 85 | + <span class="profile-copy"> | ||
| 86 | + <strong>{{ auth.userInfo?.userNickname || '管理员' }}</strong> | ||
| 87 | + <small>{{ auth.userInfo?.role === 'admin' ? '超级管理员' : '分站管理员' }}</small> | ||
| 88 | + </span> | ||
| 89 | + <down-outlined /> | ||
| 90 | + </a-button> | ||
| 91 | + <template #overlay> | ||
| 92 | + <a-menu> | ||
| 93 | + <a-menu-item @click="handleLogout">退出登录</a-menu-item> | ||
| 94 | + </a-menu> | ||
| 95 | + </template> | ||
| 96 | + </a-dropdown> | ||
| 97 | + </div> | ||
| 98 | + </header> | ||
| 99 | + | ||
| 100 | + <div class="soft-page-shell"> | ||
| 58 | <router-view /> | 101 | <router-view /> |
| 59 | - </a-layout-content> | ||
| 60 | - </a-layout> | ||
| 61 | - </a-layout> | 102 | + </div> |
| 103 | + </main> | ||
| 104 | + </div> | ||
| 62 | </template> | 105 | </template> |
| 63 | 106 | ||
| 64 | <script setup lang="ts"> | 107 | <script setup lang="ts"> |
| 65 | -import { ref, watch } from 'vue' | 108 | +import { computed, ref, watch } from 'vue' |
| 66 | import { useRouter, useRoute } from 'vue-router' | 109 | import { useRouter, useRoute } from 'vue-router' |
| 67 | import { useAuthStore } from '@/stores/auth' | 110 | import { useAuthStore } from '@/stores/auth' |
| 68 | import { | 111 | import { |
| 69 | GlobalOutlined, ApartmentOutlined, ShopOutlined, | 112 | GlobalOutlined, ApartmentOutlined, ShopOutlined, |
| 70 | - UserOutlined, UnorderedListOutlined, ApiOutlined, DownOutlined, StarOutlined | 113 | + UserOutlined, UnorderedListOutlined, ApiOutlined, DownOutlined, StarOutlined, |
| 114 | + CalendarOutlined, MenuFoldOutlined, MenuUnfoldOutlined, HomeOutlined | ||
| 71 | } from '@ant-design/icons-vue' | 115 | } from '@ant-design/icons-vue' |
| 72 | 116 | ||
| 73 | const router = useRouter() | 117 | const router = useRouter() |
| @@ -75,9 +119,31 @@ const route = useRoute() | @@ -75,9 +119,31 @@ const route = useRoute() | ||
| 75 | const auth = useAuthStore() | 119 | const auth = useAuthStore() |
| 76 | const collapsed = ref(false) | 120 | const collapsed = ref(false) |
| 77 | const selectedKeys = ref([route.path]) | 121 | const selectedKeys = ref([route.path]) |
| 122 | +const titleMap: Record<string, string> = { | ||
| 123 | + '/dashboard': '工作台', | ||
| 124 | + '/city': '租户管理', | ||
| 125 | + '/substation': '分站管理', | ||
| 126 | + '/merchant/enter': '商家入驻', | ||
| 127 | + '/merchant/store': '店铺管理', | ||
| 128 | + '/rider': '骑手管理', | ||
| 129 | + '/rider/evaluate': '骑手评价', | ||
| 130 | + '/order': '订单列表', | ||
| 131 | + '/refund': '退款管理', | ||
| 132 | + '/delivery/order': '配送订单', | ||
| 133 | + '/open': '开放平台', | ||
| 134 | + '/open/mock-delivery': '模拟推单', | ||
| 135 | +} | ||
| 78 | 136 | ||
| 79 | watch(() => route.path, (p) => { selectedKeys.value = [p] }) | 137 | watch(() => route.path, (p) => { selectedKeys.value = [p] }) |
| 80 | 138 | ||
| 139 | +const currentTitle = computed(() => titleMap[route.path] || '外卖管理') | ||
| 140 | +const avatarText = computed(() => (auth.userInfo?.userNickname || '管理员').slice(0, 1)) | ||
| 141 | +const todayLabel = computed(() => new Intl.DateTimeFormat('zh-CN', { | ||
| 142 | + month: 'long', | ||
| 143 | + day: 'numeric', | ||
| 144 | + weekday: 'short', | ||
| 145 | +}).format(new Date())) | ||
| 146 | + | ||
| 81 | function onMenuClick({ key }: { key: string }) { | 147 | function onMenuClick({ key }: { key: string }) { |
| 82 | router.push(key) | 148 | router.push(key) |
| 83 | } | 149 | } |
| @@ -89,15 +155,234 @@ function handleLogout() { | @@ -89,15 +155,234 @@ function handleLogout() { | ||
| 89 | </script> | 155 | </script> |
| 90 | 156 | ||
| 91 | <style scoped> | 157 | <style scoped> |
| 92 | -.logo { | ||
| 93 | - height: 64px; | ||
| 94 | - color: #fff; | ||
| 95 | - font-size: 16px; | ||
| 96 | - font-weight: bold; | 158 | +.layout-shell { |
| 159 | + min-height: 100vh; | ||
| 160 | + display: grid; | ||
| 161 | + grid-template-columns: 280px minmax(0, 1fr); | ||
| 162 | + gap: 22px; | ||
| 163 | + padding: 22px; | ||
| 164 | +} | ||
| 165 | + | ||
| 166 | +.soft-sider, | ||
| 167 | +.soft-topbar { | ||
| 168 | + border: 1px solid rgba(255, 255, 255, 0.58); | ||
| 169 | + background: rgba(255, 255, 255, 0.74); | ||
| 170 | + backdrop-filter: blur(22px); | ||
| 171 | + box-shadow: 0 16px 40px rgba(130, 110, 218, 0.12); | ||
| 172 | +} | ||
| 173 | + | ||
| 174 | +.soft-sider { | ||
| 175 | + position: sticky; | ||
| 176 | + top: 22px; | ||
| 177 | + height: calc(100vh - 44px); | ||
| 178 | + border-radius: 34px; | ||
| 179 | + padding: 18px 14px 18px; | ||
| 97 | display: flex; | 180 | display: flex; |
| 98 | - align-items: center; | ||
| 99 | - justify-content: center; | 181 | + flex-direction: column; |
| 100 | overflow: hidden; | 182 | overflow: hidden; |
| 101 | - white-space: nowrap; | 183 | +} |
| 184 | + | ||
| 185 | +.menu-scroll { | ||
| 186 | + flex: 1; | ||
| 187 | + min-height: 0; | ||
| 188 | + overflow-y: auto; | ||
| 189 | + overflow-x: hidden; | ||
| 190 | + padding-right: 4px; | ||
| 191 | +} | ||
| 192 | + | ||
| 193 | +.menu-scroll::-webkit-scrollbar { | ||
| 194 | + width: 8px; | ||
| 195 | +} | ||
| 196 | + | ||
| 197 | +.menu-scroll::-webkit-scrollbar-thumb { | ||
| 198 | + border-radius: 999px; | ||
| 199 | + background: rgba(140, 124, 240, 0.24); | ||
| 200 | +} | ||
| 201 | + | ||
| 202 | +.soft-sider.collapsed { | ||
| 203 | + width: 88px; | ||
| 204 | +} | ||
| 205 | + | ||
| 206 | +.sider-toggle { | ||
| 207 | + align-self: flex-end; | ||
| 208 | + width: 44px; | ||
| 209 | + height: 44px; | ||
| 210 | + border-radius: 16px; | ||
| 211 | + border: none; | ||
| 212 | + background: rgba(246, 242, 255, 0.9); | ||
| 213 | + color: #7f6de5; | ||
| 214 | + cursor: pointer; | ||
| 215 | + margin-bottom: 12px; | ||
| 216 | +} | ||
| 217 | + | ||
| 218 | +.brand-block { | ||
| 219 | + display: flex; | ||
| 220 | + align-items: center; | ||
| 221 | + gap: 14px; | ||
| 222 | + padding: 10px 10px 20px; | ||
| 223 | +} | ||
| 224 | + | ||
| 225 | +.brand-mark { | ||
| 226 | + width: 52px; | ||
| 227 | + height: 52px; | ||
| 228 | + border-radius: 18px; | ||
| 229 | + background: linear-gradient(145deg, #8c7cf0, #e6b5dc); | ||
| 230 | + color: white; | ||
| 231 | + font-family: 'Outfit', sans-serif; | ||
| 232 | + font-weight: 700; | ||
| 233 | + display: grid; | ||
| 234 | + place-items: center; | ||
| 235 | + box-shadow: 0 14px 24px rgba(140, 124, 240, 0.28); | ||
| 236 | +} | ||
| 237 | + | ||
| 238 | +.brand-copy { | ||
| 239 | + display: flex; | ||
| 240 | + flex-direction: column; | ||
| 241 | +} | ||
| 242 | + | ||
| 243 | +.brand-copy strong, | ||
| 244 | +.profile-copy strong, | ||
| 245 | +.hero-copy h2, | ||
| 246 | +.insight-card strong, | ||
| 247 | +h1 { | ||
| 248 | + font-family: 'Outfit', sans-serif; | ||
| 249 | + color: #2f2946; | ||
| 250 | +} | ||
| 251 | + | ||
| 252 | +.brand-copy span, | ||
| 253 | +.profile-copy small, | ||
| 254 | +.eyebrow, | ||
| 255 | +.hero-copy p, | ||
| 256 | +.insight-card p, | ||
| 257 | +.note-list { | ||
| 258 | + color: #8d88a4; | ||
| 259 | +} | ||
| 260 | + | ||
| 261 | +:deep(.ant-menu) { | ||
| 262 | + min-height: 100%; | ||
| 263 | + background: transparent; | ||
| 264 | +} | ||
| 265 | + | ||
| 266 | +.sider-foot { | ||
| 267 | + margin-top: 14px; | ||
| 268 | + flex-shrink: 0; | ||
| 269 | + border-radius: 24px; | ||
| 270 | + background: linear-gradient(180deg, rgba(245, 241, 255, 0.85), rgba(255, 247, 250, 0.92)); | ||
| 271 | + padding: 16px; | ||
| 272 | +} | ||
| 273 | + | ||
| 274 | +.soft-chip { | ||
| 275 | + display: inline-flex; | ||
| 276 | + align-items: center; | ||
| 277 | + border-radius: 999px; | ||
| 278 | + padding: 6px 12px; | ||
| 279 | + font-size: 12px; | ||
| 280 | + font-weight: 700; | ||
| 281 | +} | ||
| 282 | + | ||
| 283 | +.soft-chip-green { | ||
| 284 | + background: rgba(139, 212, 167, 0.22); | ||
| 285 | + color: #3d8f63; | ||
| 286 | +} | ||
| 287 | + | ||
| 288 | +.content-column { | ||
| 289 | + min-width: 0; | ||
| 290 | +} | ||
| 291 | + | ||
| 292 | +.soft-topbar { | ||
| 293 | + border-radius: 30px; | ||
| 294 | + padding: 18px 24px; | ||
| 295 | + display: flex; | ||
| 296 | + align-items: center; | ||
| 297 | + justify-content: space-between; | ||
| 298 | + gap: 18px; | ||
| 299 | +} | ||
| 300 | + | ||
| 301 | +.soft-topbar h1 { | ||
| 302 | + margin: 4px 0 0; | ||
| 303 | + font-size: 30px; | ||
| 304 | +} | ||
| 305 | + | ||
| 306 | +.eyebrow { | ||
| 307 | + margin: 0; | ||
| 308 | + text-transform: uppercase; | ||
| 309 | + letter-spacing: 0.12em; | ||
| 310 | + font-size: 11px; | ||
| 311 | + font-weight: 700; | ||
| 312 | +} | ||
| 313 | + | ||
| 314 | +.topbar-actions { | ||
| 315 | + display: flex; | ||
| 316 | + align-items: center; | ||
| 317 | + gap: 14px; | ||
| 318 | +} | ||
| 319 | + | ||
| 320 | +.date-pill, | ||
| 321 | +.profile-button { | ||
| 322 | + display: inline-flex; | ||
| 323 | + align-items: center; | ||
| 324 | + gap: 10px; | ||
| 325 | + border-radius: 999px; | ||
| 326 | + background: rgba(255, 255, 255, 0.82); | ||
| 327 | + border: 1px solid rgba(194, 184, 237, 0.38); | ||
| 328 | + padding: 10px 14px; | ||
| 329 | +} | ||
| 330 | + | ||
| 331 | +.profile-button { | ||
| 332 | + height: auto; | ||
| 333 | + padding-right: 12px; | ||
| 334 | +} | ||
| 335 | + | ||
| 336 | +.profile-avatar { | ||
| 337 | + width: 38px; | ||
| 338 | + height: 38px; | ||
| 339 | + border-radius: 14px; | ||
| 340 | + display: grid; | ||
| 341 | + place-items: center; | ||
| 342 | + background: linear-gradient(135deg, #a48ef4, #f1bfd8); | ||
| 343 | + color: white; | ||
| 344 | + font-weight: 700; | ||
| 345 | +} | ||
| 346 | + | ||
| 347 | +.profile-copy { | ||
| 348 | + display: flex; | ||
| 349 | + flex-direction: column; | ||
| 350 | + text-align: left; | ||
| 351 | +} | ||
| 352 | + | ||
| 353 | +@media (max-width: 960px) { | ||
| 354 | + .layout-shell { | ||
| 355 | + grid-template-columns: 1fr; | ||
| 356 | + padding: 14px; | ||
| 357 | + } | ||
| 358 | + | ||
| 359 | + .soft-sider { | ||
| 360 | + position: relative; | ||
| 361 | + top: 0; | ||
| 362 | + height: auto; | ||
| 363 | + } | ||
| 364 | + | ||
| 365 | + .menu-scroll { | ||
| 366 | + overflow: visible; | ||
| 367 | + padding-right: 0; | ||
| 368 | + } | ||
| 369 | + .soft-topbar { | ||
| 370 | + flex-direction: column; | ||
| 371 | + align-items: flex-start; | ||
| 372 | + } | ||
| 373 | +} | ||
| 374 | + | ||
| 375 | +@media (max-width: 720px) { | ||
| 376 | + .soft-sider.collapsed { | ||
| 377 | + width: auto; | ||
| 378 | + } | ||
| 379 | + | ||
| 380 | + .soft-topbar h1 { | ||
| 381 | + font-size: 26px; | ||
| 382 | + } | ||
| 383 | + | ||
| 384 | + .hero-copy h2 { | ||
| 385 | + font-size: 28px; | ||
| 386 | + } | ||
| 102 | } | 387 | } |
| 103 | </style> | 388 | </style> |
src/main.ts
| @@ -4,6 +4,7 @@ import Antd from 'ant-design-vue' | @@ -4,6 +4,7 @@ import Antd from 'ant-design-vue' | ||
| 4 | import 'ant-design-vue/dist/reset.css' | 4 | import 'ant-design-vue/dist/reset.css' |
| 5 | import App from './App.vue' | 5 | import App from './App.vue' |
| 6 | import router from './router' | 6 | import router from './router' |
| 7 | +import './style.css' | ||
| 7 | 8 | ||
| 8 | const app = createApp(App) | 9 | const app = createApp(App) |
| 9 | app.use(createPinia()) | 10 | app.use(createPinia()) |
src/router/index.ts
| @@ -13,13 +13,19 @@ const router = createRouter({ | @@ -13,13 +13,19 @@ const router = createRouter({ | ||
| 13 | { | 13 | { |
| 14 | path: '/', | 14 | path: '/', |
| 15 | component: () => import('@/layouts/MainLayout.vue'), | 15 | component: () => import('@/layouts/MainLayout.vue'), |
| 16 | - redirect: '/city', | 16 | + redirect: '/dashboard', |
| 17 | children: [ | 17 | children: [ |
| 18 | { | 18 | { |
| 19 | + path: 'dashboard', | ||
| 20 | + name: 'Dashboard', | ||
| 21 | + component: () => import('@/views/dashboard/DashboardHome.vue'), | ||
| 22 | + meta: { title: '工作台' }, | ||
| 23 | + }, | ||
| 24 | + { | ||
| 19 | path: 'city', | 25 | path: 'city', |
| 20 | name: 'City', | 26 | name: 'City', |
| 21 | component: () => import('@/views/city/CityList.vue'), | 27 | component: () => import('@/views/city/CityList.vue'), |
| 22 | - meta: { title: '城市管理' }, | 28 | + meta: { title: '租户管理' }, |
| 23 | }, | 29 | }, |
| 24 | { | 30 | { |
| 25 | path: 'substation', | 31 | path: 'substation', |
| @@ -75,6 +81,12 @@ const router = createRouter({ | @@ -75,6 +81,12 @@ const router = createRouter({ | ||
| 75 | component: () => import('@/views/open/OpenAppList.vue'), | 81 | component: () => import('@/views/open/OpenAppList.vue'), |
| 76 | meta: { title: '开放平台' }, | 82 | meta: { title: '开放平台' }, |
| 77 | }, | 83 | }, |
| 84 | + { | ||
| 85 | + path: 'open/mock-delivery', | ||
| 86 | + name: 'OpenMockDelivery', | ||
| 87 | + component: () => import('@/views/open/OpenMockDelivery.vue'), | ||
| 88 | + meta: { title: '模拟推单' }, | ||
| 89 | + }, | ||
| 78 | ], | 90 | ], |
| 79 | }, | 91 | }, |
| 80 | { path: '/:pathMatch(.*)*', redirect: '/' }, | 92 | { path: '/:pathMatch(.*)*', redirect: '/' }, |
src/style.css
| 1 | +@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700&family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap'); | ||
| 2 | + | ||
| 1 | :root { | 3 | :root { |
| 2 | - --text: #6b6375; | ||
| 3 | - --text-h: #08060d; | ||
| 4 | - --bg: #fff; | ||
| 5 | - --border: #e5e4e7; | ||
| 6 | - --code-bg: #f4f3ec; | ||
| 7 | - --accent: #aa3bff; | ||
| 8 | - --accent-bg: rgba(170, 59, 255, 0.1); | ||
| 9 | - --accent-border: rgba(170, 59, 255, 0.5); | ||
| 10 | - --social-bg: rgba(244, 243, 236, 0.5); | ||
| 11 | - --shadow: | ||
| 12 | - rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px; | ||
| 13 | - | ||
| 14 | - --sans: system-ui, 'Segoe UI', Roboto, sans-serif; | ||
| 15 | - --heading: system-ui, 'Segoe UI', Roboto, sans-serif; | ||
| 16 | - --mono: ui-monospace, Consolas, monospace; | ||
| 17 | - | ||
| 18 | - font: 18px/145% var(--sans); | ||
| 19 | - letter-spacing: 0.18px; | ||
| 20 | - color-scheme: light dark; | ||
| 21 | - color: var(--text); | ||
| 22 | - background: var(--bg); | 4 | + --app-bg: |
| 5 | + radial-gradient(circle at top left, rgba(198, 185, 255, 0.58), transparent 28%), | ||
| 6 | + radial-gradient(circle at 85% 18%, rgba(255, 217, 236, 0.7), transparent 24%), | ||
| 7 | + radial-gradient(circle at 80% 84%, rgba(255, 234, 181, 0.45), transparent 22%), | ||
| 8 | + linear-gradient(180deg, #fbfaff 0%, #f7f7fd 48%, #f8f8fb 100%); | ||
| 9 | + --panel: rgba(255, 255, 255, 0.76); | ||
| 10 | + --panel-strong: rgba(255, 255, 255, 0.9); | ||
| 11 | + --panel-tint: linear-gradient(135deg, rgba(198, 185, 255, 0.72), rgba(255, 218, 238, 0.72)); | ||
| 12 | + --line: rgba(182, 172, 226, 0.24); | ||
| 13 | + --line-strong: rgba(140, 124, 240, 0.22); | ||
| 14 | + --text-main: #4f4a68; | ||
| 15 | + --text-soft: #8d88a4; | ||
| 16 | + --text-dark: #2f2946; | ||
| 17 | + --brand: #8c7cf0; | ||
| 18 | + --brand-soft: #c6b9ff; | ||
| 19 | + --brand-deep: #7563df; | ||
| 20 | + --pink: #f4bfd8; | ||
| 21 | + --yellow: #f6d977; | ||
| 22 | + --green: #8bd4a7; | ||
| 23 | + --orange: #ffb284; | ||
| 24 | + --shadow-xl: 0 22px 60px rgba(121, 104, 213, 0.12); | ||
| 25 | + --shadow-lg: 0 16px 35px rgba(137, 123, 214, 0.12); | ||
| 26 | + --shadow-sm: 0 10px 20px rgba(149, 136, 220, 0.08); | ||
| 27 | + --radius-xl: 32px; | ||
| 28 | + --radius-lg: 24px; | ||
| 29 | + --radius-md: 18px; | ||
| 30 | + --radius-sm: 14px; | ||
| 31 | + --font-display: 'Outfit', 'Avenir Next', 'Segoe UI', sans-serif; | ||
| 32 | + --font-body: 'Plus Jakarta Sans', 'Segoe UI', sans-serif; | ||
| 33 | + color: var(--text-main); | ||
| 34 | + font-family: var(--font-body); | ||
| 35 | + line-height: 1.5; | ||
| 36 | + font-weight: 500; | ||
| 23 | font-synthesis: none; | 37 | font-synthesis: none; |
| 24 | text-rendering: optimizeLegibility; | 38 | text-rendering: optimizeLegibility; |
| 25 | -webkit-font-smoothing: antialiased; | 39 | -webkit-font-smoothing: antialiased; |
| 26 | -moz-osx-font-smoothing: grayscale; | 40 | -moz-osx-font-smoothing: grayscale; |
| 27 | - | ||
| 28 | - @media (max-width: 1024px) { | ||
| 29 | - font-size: 16px; | ||
| 30 | - } | 41 | + background: #f8f8ff; |
| 31 | } | 42 | } |
| 32 | 43 | ||
| 33 | -@media (prefers-color-scheme: dark) { | ||
| 34 | - :root { | ||
| 35 | - --text: #9ca3af; | ||
| 36 | - --text-h: #f3f4f6; | ||
| 37 | - --bg: #16171d; | ||
| 38 | - --border: #2e303a; | ||
| 39 | - --code-bg: #1f2028; | ||
| 40 | - --accent: #c084fc; | ||
| 41 | - --accent-bg: rgba(192, 132, 252, 0.15); | ||
| 42 | - --accent-border: rgba(192, 132, 252, 0.5); | ||
| 43 | - --social-bg: rgba(47, 48, 58, 0.5); | ||
| 44 | - --shadow: | ||
| 45 | - rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px; | ||
| 46 | - } | 44 | +* { |
| 45 | + box-sizing: border-box; | ||
| 46 | +} | ||
| 47 | 47 | ||
| 48 | - #social .button-icon { | ||
| 49 | - filter: invert(1) brightness(2); | ||
| 50 | - } | 48 | +html, |
| 49 | +body, | ||
| 50 | +#app { | ||
| 51 | + min-height: 100vh; | ||
| 51 | } | 52 | } |
| 52 | 53 | ||
| 53 | body { | 54 | body { |
| 54 | margin: 0; | 55 | margin: 0; |
| 56 | + background: var(--app-bg); | ||
| 57 | + color: var(--text-main); | ||
| 55 | } | 58 | } |
| 56 | 59 | ||
| 57 | -h1, | ||
| 58 | -h2 { | ||
| 59 | - font-family: var(--heading); | ||
| 60 | - font-weight: 500; | ||
| 61 | - color: var(--text-h); | 60 | +a { |
| 61 | + color: var(--brand-deep); | ||
| 62 | } | 62 | } |
| 63 | 63 | ||
| 64 | -h1 { | ||
| 65 | - font-size: 56px; | ||
| 66 | - letter-spacing: -1.68px; | ||
| 67 | - margin: 32px 0; | ||
| 68 | - @media (max-width: 1024px) { | ||
| 69 | - font-size: 36px; | ||
| 70 | - margin: 20px 0; | ||
| 71 | - } | ||
| 72 | -} | ||
| 73 | -h2 { | ||
| 74 | - font-size: 24px; | ||
| 75 | - line-height: 118%; | ||
| 76 | - letter-spacing: -0.24px; | ||
| 77 | - margin: 0 0 8px; | ||
| 78 | - @media (max-width: 1024px) { | ||
| 79 | - font-size: 20px; | ||
| 80 | - } | ||
| 81 | -} | ||
| 82 | -p { | ||
| 83 | - margin: 0; | 64 | +.ant-layout { |
| 65 | + background: transparent; | ||
| 84 | } | 66 | } |
| 85 | 67 | ||
| 86 | -code, | ||
| 87 | -.counter { | ||
| 88 | - font-family: var(--mono); | ||
| 89 | - display: inline-flex; | ||
| 90 | - border-radius: 4px; | ||
| 91 | - color: var(--text-h); | 68 | +.ant-btn { |
| 69 | + border-radius: 999px; | ||
| 70 | + font-weight: 600; | ||
| 71 | + box-shadow: none; | ||
| 92 | } | 72 | } |
| 93 | 73 | ||
| 94 | -code { | ||
| 95 | - font-size: 15px; | ||
| 96 | - line-height: 135%; | ||
| 97 | - padding: 4px 8px; | ||
| 98 | - background: var(--code-bg); | 74 | +.ant-btn-primary { |
| 75 | + background: linear-gradient(135deg, #8c7cf0 0%, #c995ea 100%); | ||
| 76 | + border: none; | ||
| 77 | + box-shadow: 0 12px 24px rgba(140, 124, 240, 0.24); | ||
| 99 | } | 78 | } |
| 100 | 79 | ||
| 101 | -.counter { | ||
| 102 | - font-size: 16px; | ||
| 103 | - padding: 5px 10px; | ||
| 104 | - border-radius: 5px; | ||
| 105 | - color: var(--accent); | ||
| 106 | - background: var(--accent-bg); | ||
| 107 | - border: 2px solid transparent; | ||
| 108 | - transition: border-color 0.3s; | ||
| 109 | - margin-bottom: 24px; | 80 | +.ant-btn-primary:hover, |
| 81 | +.ant-btn-primary:focus { | ||
| 82 | + background: linear-gradient(135deg, #7d6be8 0%, #c289ea 100%) !important; | ||
| 83 | +} | ||
| 110 | 84 | ||
| 111 | - &:hover { | ||
| 112 | - border-color: var(--accent-border); | ||
| 113 | - } | ||
| 114 | - &:focus-visible { | ||
| 115 | - outline: 2px solid var(--accent); | ||
| 116 | - outline-offset: 2px; | ||
| 117 | - } | 85 | +.ant-btn-default { |
| 86 | + border-color: var(--line); | ||
| 87 | + background: rgba(255, 255, 255, 0.82); | ||
| 118 | } | 88 | } |
| 119 | 89 | ||
| 120 | -.hero { | ||
| 121 | - position: relative; | 90 | +.ant-card { |
| 91 | + border: 1px solid rgba(255, 255, 255, 0.55); | ||
| 92 | + background: var(--panel); | ||
| 93 | + backdrop-filter: blur(18px); | ||
| 94 | + border-radius: var(--radius-lg); | ||
| 95 | + box-shadow: var(--shadow-lg); | ||
| 96 | +} | ||
| 122 | 97 | ||
| 123 | - .base, | ||
| 124 | - .framework, | ||
| 125 | - .vite { | ||
| 126 | - inset-inline: 0; | ||
| 127 | - margin: 0 auto; | ||
| 128 | - } | 98 | +.ant-card .ant-card-head { |
| 99 | + border-bottom: 1px solid rgba(188, 180, 230, 0.18); | ||
| 100 | + min-height: 72px; | ||
| 101 | +} | ||
| 129 | 102 | ||
| 130 | - .base { | ||
| 131 | - width: 170px; | ||
| 132 | - position: relative; | ||
| 133 | - z-index: 0; | ||
| 134 | - } | 103 | +.ant-card .ant-card-head-title { |
| 104 | + font-family: var(--font-display); | ||
| 105 | + font-size: 1.1rem; | ||
| 106 | + color: var(--text-dark); | ||
| 107 | + font-weight: 700; | ||
| 108 | +} | ||
| 135 | 109 | ||
| 136 | - .framework, | ||
| 137 | - .vite { | ||
| 138 | - position: absolute; | ||
| 139 | - } | 110 | +.ant-card .ant-card-body { |
| 111 | + padding: 24px; | ||
| 112 | +} | ||
| 140 | 113 | ||
| 141 | - .framework { | ||
| 142 | - z-index: 1; | ||
| 143 | - top: 34px; | ||
| 144 | - height: 28px; | ||
| 145 | - transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg) | ||
| 146 | - scale(1.4); | ||
| 147 | - } | 114 | +.ant-table-wrapper .ant-table { |
| 115 | + background: transparent; | ||
| 116 | +} | ||
| 148 | 117 | ||
| 149 | - .vite { | ||
| 150 | - z-index: 0; | ||
| 151 | - top: 107px; | ||
| 152 | - height: 26px; | ||
| 153 | - width: auto; | ||
| 154 | - transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg) | ||
| 155 | - scale(0.8); | ||
| 156 | - } | 118 | +.ant-table-wrapper .ant-table-container { |
| 119 | + border-radius: var(--radius-md); | ||
| 120 | + border: 1px solid rgba(194, 185, 239, 0.22); | ||
| 121 | + overflow: hidden; | ||
| 157 | } | 122 | } |
| 158 | 123 | ||
| 159 | -#app { | ||
| 160 | - width: 1126px; | ||
| 161 | - max-width: 100%; | ||
| 162 | - margin: 0 auto; | ||
| 163 | - text-align: center; | ||
| 164 | - border-inline: 1px solid var(--border); | ||
| 165 | - min-height: 100svh; | ||
| 166 | - display: flex; | ||
| 167 | - flex-direction: column; | ||
| 168 | - box-sizing: border-box; | 124 | +.ant-table-wrapper .ant-table-thead > tr > th { |
| 125 | + background: rgba(244, 240, 255, 0.88); | ||
| 126 | + color: var(--text-dark); | ||
| 127 | + border-bottom: none; | ||
| 128 | + font-weight: 700; | ||
| 169 | } | 129 | } |
| 170 | 130 | ||
| 171 | -#center { | ||
| 172 | - display: flex; | ||
| 173 | - flex-direction: column; | ||
| 174 | - gap: 25px; | ||
| 175 | - place-content: center; | ||
| 176 | - place-items: center; | ||
| 177 | - flex-grow: 1; | 131 | +.ant-table-wrapper .ant-table-tbody > tr > td { |
| 132 | + border-bottom: 1px solid rgba(226, 220, 247, 0.72); | ||
| 133 | + background: rgba(255, 255, 255, 0.48); | ||
| 134 | +} | ||
| 178 | 135 | ||
| 179 | - @media (max-width: 1024px) { | ||
| 180 | - padding: 32px 20px 24px; | ||
| 181 | - gap: 18px; | ||
| 182 | - } | 136 | +.ant-table-wrapper .ant-table-tbody > tr:hover > td { |
| 137 | + background: rgba(247, 242, 255, 0.95) !important; | ||
| 183 | } | 138 | } |
| 184 | 139 | ||
| 185 | -#next-steps { | ||
| 186 | - display: flex; | ||
| 187 | - border-top: 1px solid var(--border); | ||
| 188 | - text-align: left; | 140 | +.ant-input, |
| 141 | +.ant-input-affix-wrapper, | ||
| 142 | +.ant-input-password, | ||
| 143 | +.ant-select-selector, | ||
| 144 | +.ant-input-number, | ||
| 145 | +.ant-input-number-input-wrap, | ||
| 146 | +.ant-picker { | ||
| 147 | + border-radius: 16px !important; | ||
| 148 | + border-color: rgba(189, 180, 234, 0.4) !important; | ||
| 149 | + background: rgba(255, 255, 255, 0.78) !important; | ||
| 150 | + box-shadow: none !important; | ||
| 151 | +} | ||
| 189 | 152 | ||
| 190 | - & > div { | ||
| 191 | - flex: 1 1 0; | ||
| 192 | - padding: 32px; | ||
| 193 | - @media (max-width: 1024px) { | ||
| 194 | - padding: 24px 20px; | ||
| 195 | - } | ||
| 196 | - } | 153 | +.ant-input:focus, |
| 154 | +.ant-input-affix-wrapper-focused, | ||
| 155 | +.ant-select-focused .ant-select-selector, | ||
| 156 | +.ant-input-number-focused, | ||
| 157 | +.ant-picker-focused { | ||
| 158 | + border-color: rgba(140, 124, 240, 0.68) !important; | ||
| 159 | + box-shadow: 0 0 0 4px rgba(140, 124, 240, 0.12) !important; | ||
| 160 | +} | ||
| 197 | 161 | ||
| 198 | - .icon { | ||
| 199 | - margin-bottom: 16px; | ||
| 200 | - width: 22px; | ||
| 201 | - height: 22px; | ||
| 202 | - } | 162 | +.ant-modal .ant-modal-content, |
| 163 | +.ant-dropdown .ant-dropdown-menu { | ||
| 164 | + border-radius: 28px; | ||
| 165 | + border: 1px solid rgba(228, 223, 247, 0.7); | ||
| 166 | + background: rgba(255, 255, 255, 0.92); | ||
| 167 | + backdrop-filter: blur(24px); | ||
| 168 | + box-shadow: var(--shadow-xl); | ||
| 169 | +} | ||
| 203 | 170 | ||
| 204 | - @media (max-width: 1024px) { | ||
| 205 | - flex-direction: column; | ||
| 206 | - text-align: center; | ||
| 207 | - } | 171 | +.ant-modal .ant-modal-header { |
| 172 | + background: transparent; | ||
| 173 | + border-bottom: 1px solid rgba(189, 180, 234, 0.18); | ||
| 208 | } | 174 | } |
| 209 | 175 | ||
| 210 | -#docs { | ||
| 211 | - border-right: 1px solid var(--border); | 176 | +.ant-modal .ant-modal-title { |
| 177 | + font-family: var(--font-display); | ||
| 178 | + color: var(--text-dark); | ||
| 179 | + font-weight: 700; | ||
| 180 | +} | ||
| 212 | 181 | ||
| 213 | - @media (max-width: 1024px) { | ||
| 214 | - border-right: none; | ||
| 215 | - border-bottom: 1px solid var(--border); | ||
| 216 | - } | 182 | +.ant-tag { |
| 183 | + border-radius: 999px; | ||
| 184 | + border: none; | ||
| 185 | + padding-inline: 10px; | ||
| 186 | + font-weight: 600; | ||
| 217 | } | 187 | } |
| 218 | 188 | ||
| 219 | -#next-steps ul { | ||
| 220 | - list-style: none; | ||
| 221 | - padding: 0; | ||
| 222 | - display: flex; | ||
| 223 | - gap: 8px; | ||
| 224 | - margin: 32px 0 0; | 189 | +.ant-menu { |
| 190 | + background: transparent !important; | ||
| 191 | +} | ||
| 225 | 192 | ||
| 226 | - .logo { | ||
| 227 | - height: 18px; | ||
| 228 | - } | 193 | +.ant-menu-item, |
| 194 | +.ant-menu-submenu-title { | ||
| 195 | + border-radius: 18px !important; | ||
| 196 | + margin-inline: 8px !important; | ||
| 197 | + margin-block: 6px !important; | ||
| 198 | + width: calc(100% - 16px) !important; | ||
| 199 | +} | ||
| 229 | 200 | ||
| 230 | - a { | ||
| 231 | - color: var(--text-h); | ||
| 232 | - font-size: 16px; | ||
| 233 | - border-radius: 6px; | ||
| 234 | - background: var(--social-bg); | ||
| 235 | - display: flex; | ||
| 236 | - padding: 6px 12px; | ||
| 237 | - align-items: center; | ||
| 238 | - gap: 8px; | ||
| 239 | - text-decoration: none; | ||
| 240 | - transition: box-shadow 0.3s; | ||
| 241 | - | ||
| 242 | - &:hover { | ||
| 243 | - box-shadow: var(--shadow); | ||
| 244 | - } | ||
| 245 | - .button-icon { | ||
| 246 | - height: 18px; | ||
| 247 | - width: 18px; | ||
| 248 | - } | ||
| 249 | - } | 201 | +.ant-menu-light .ant-menu-item-selected, |
| 202 | +.ant-menu-light > .ant-menu .ant-menu-item-selected, | ||
| 203 | +.ant-menu-light .ant-menu-submenu-selected > .ant-menu-submenu-title { | ||
| 204 | + background: linear-gradient(135deg, rgba(140, 124, 240, 0.18), rgba(255, 212, 235, 0.3)) !important; | ||
| 205 | + color: var(--brand-deep) !important; | ||
| 206 | +} | ||
| 250 | 207 | ||
| 251 | - @media (max-width: 1024px) { | ||
| 252 | - margin-top: 20px; | ||
| 253 | - flex-wrap: wrap; | ||
| 254 | - justify-content: center; | 208 | +.ant-menu-item:hover, |
| 209 | +.ant-menu-submenu-title:hover { | ||
| 210 | + color: var(--brand-deep) !important; | ||
| 211 | + background: rgba(255, 255, 255, 0.5) !important; | ||
| 212 | +} | ||
| 255 | 213 | ||
| 256 | - li { | ||
| 257 | - flex: 1 1 calc(50% - 8px); | ||
| 258 | - } | 214 | +.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled) { |
| 215 | + color: var(--brand-deep); | ||
| 216 | + background: rgba(255, 255, 255, 0.92); | ||
| 217 | + border-color: rgba(140, 124, 240, 0.45); | ||
| 218 | + box-shadow: 0 8px 18px rgba(140, 124, 240, 0.12); | ||
| 219 | +} | ||
| 259 | 220 | ||
| 260 | - a { | ||
| 261 | - width: 100%; | ||
| 262 | - justify-content: center; | ||
| 263 | - box-sizing: border-box; | ||
| 264 | - } | ||
| 265 | - } | 221 | +.ant-empty { |
| 222 | + padding: 20px 0; | ||
| 266 | } | 223 | } |
| 267 | 224 | ||
| 268 | -#spacer { | ||
| 269 | - height: 88px; | ||
| 270 | - border-top: 1px solid var(--border); | ||
| 271 | - @media (max-width: 1024px) { | ||
| 272 | - height: 48px; | ||
| 273 | - } | 225 | +.soft-page-shell { |
| 226 | + padding: 24px 24px 28px; | ||
| 274 | } | 227 | } |
| 275 | 228 | ||
| 276 | -.ticks { | 229 | +.soft-section-card { |
| 277 | position: relative; | 230 | position: relative; |
| 278 | - width: 100%; | ||
| 279 | - | ||
| 280 | - &::before, | ||
| 281 | - &::after { | ||
| 282 | - content: ''; | ||
| 283 | - position: absolute; | ||
| 284 | - top: -4.5px; | ||
| 285 | - border: 5px solid transparent; | ||
| 286 | - } | 231 | + overflow: hidden; |
| 232 | +} | ||
| 287 | 233 | ||
| 288 | - &::before { | ||
| 289 | - left: 0; | ||
| 290 | - border-left-color: var(--border); | ||
| 291 | - } | ||
| 292 | - &::after { | ||
| 293 | - right: 0; | ||
| 294 | - border-right-color: var(--border); | 234 | +.soft-section-card::before { |
| 235 | + content: ''; | ||
| 236 | + position: absolute; | ||
| 237 | + inset: 0 auto auto 0; | ||
| 238 | + width: 180px; | ||
| 239 | + height: 180px; | ||
| 240 | + background: radial-gradient(circle, rgba(198, 185, 255, 0.35) 0%, transparent 70%); | ||
| 241 | + pointer-events: none; | ||
| 242 | +} | ||
| 243 | + | ||
| 244 | +@media (max-width: 1200px) { | ||
| 245 | + .soft-page-shell { | ||
| 246 | + padding: 18px; | ||
| 295 | } | 247 | } |
| 296 | } | 248 | } |
src/views/Login.vue
| 1 | <template> | 1 | <template> |
| 2 | <div class="login-wrap"> | 2 | <div class="login-wrap"> |
| 3 | - <a-card title="外卖管理系统" style="width:400px"> | ||
| 4 | - <a-form :model="form" @finish="onSubmit" layout="vertical"> | ||
| 5 | - <a-form-item name="role" label="登录身份"> | ||
| 6 | - <a-radio-group v-model:value="form.role" button-style="solid"> | ||
| 7 | - <a-radio-button value="substation">分站管理员</a-radio-button> | ||
| 8 | - <a-radio-button value="admin">超级管理员</a-radio-button> | ||
| 9 | - </a-radio-group> | ||
| 10 | - </a-form-item> | ||
| 11 | - <a-form-item name="account" :rules="[{ required: true, message: '请输入账号' }]"> | ||
| 12 | - <a-input v-model:value="form.account" placeholder="登录账号" size="large"> | ||
| 13 | - <template #prefix><user-outlined /></template> | ||
| 14 | - </a-input> | ||
| 15 | - </a-form-item> | ||
| 16 | - <a-form-item name="pass" :rules="[{ required: true, message: '请输入密码' }]"> | ||
| 17 | - <a-input-password v-model:value="form.pass" placeholder="密码" size="large"> | ||
| 18 | - <template #prefix><lock-outlined /></template> | ||
| 19 | - </a-input-password> | ||
| 20 | - </a-form-item> | ||
| 21 | - <a-form-item> | ||
| 22 | - <a-button type="primary" html-type="submit" block size="large" :loading="loading"> | ||
| 23 | - 登录 | ||
| 24 | - </a-button> | ||
| 25 | - </a-form-item> | ||
| 26 | - </a-form> | ||
| 27 | - </a-card> | 3 | + <div class="login-shell"> |
| 4 | + <section class="login-visual"> | ||
| 5 | + <div class="visual-badge">Modern Soft-Neo UI</div> | ||
| 6 | + <h1>让配送管理像清晨的云层一样轻盈。</h1> | ||
| 7 | + <p>柔和渐变、圆润卡片与轻插画氛围,把后台工作台重新整理成更亲和的中控体验。</p> | ||
| 8 | + <div class="visual-stats"> | ||
| 9 | + <div> | ||
| 10 | + <span>Theme</span> | ||
| 11 | + <strong>Lavender + Warm Glow</strong> | ||
| 12 | + </div> | ||
| 13 | + <div> | ||
| 14 | + <span>Layout</span> | ||
| 15 | + <strong>Floating Three-Column</strong> | ||
| 16 | + </div> | ||
| 17 | + </div> | ||
| 18 | + <div class="illustration-wrap"> | ||
| 19 | + <div class="bubble bubble-a"></div> | ||
| 20 | + <div class="bubble bubble-b"></div> | ||
| 21 | + <img :src="heroImage" alt="soft illustration" /> | ||
| 22 | + </div> | ||
| 23 | + </section> | ||
| 24 | + | ||
| 25 | + <a-card class="login-card"> | ||
| 26 | + <div class="login-card-head"> | ||
| 27 | + <span class="head-chip">Welcome Back</span> | ||
| 28 | + <h2>外卖管理系统</h2> | ||
| 29 | + <p>登录到柔和重构后的运营工作台</p> | ||
| 30 | + </div> | ||
| 31 | + <a-form :model="form" @finish="onSubmit" layout="vertical"> | ||
| 32 | + <a-form-item name="role" label="登录身份"> | ||
| 33 | + <a-radio-group v-model:value="form.role" button-style="solid"> | ||
| 34 | + <a-radio-button value="substation">分站管理员</a-radio-button> | ||
| 35 | + <a-radio-button value="admin">超级管理员</a-radio-button> | ||
| 36 | + </a-radio-group> | ||
| 37 | + </a-form-item> | ||
| 38 | + <a-form-item name="account" :rules="[{ required: true, message: '请输入账号' }]"> | ||
| 39 | + <a-input v-model:value="form.account" placeholder="登录账号" size="large"> | ||
| 40 | + <template #prefix><user-outlined /></template> | ||
| 41 | + </a-input> | ||
| 42 | + </a-form-item> | ||
| 43 | + <a-form-item name="pass" :rules="[{ required: true, message: '请输入密码' }]"> | ||
| 44 | + <a-input-password v-model:value="form.pass" placeholder="密码" size="large"> | ||
| 45 | + <template #prefix><lock-outlined /></template> | ||
| 46 | + </a-input-password> | ||
| 47 | + </a-form-item> | ||
| 48 | + <a-form-item> | ||
| 49 | + <a-button type="primary" html-type="submit" block size="large" :loading="loading"> | ||
| 50 | + 登录进入中台 | ||
| 51 | + </a-button> | ||
| 52 | + </a-form-item> | ||
| 53 | + </a-form> | ||
| 54 | + </a-card> | ||
| 55 | + </div> | ||
| 28 | </div> | 56 | </div> |
| 29 | </template> | 57 | </template> |
| 30 | 58 | ||
| @@ -35,6 +63,7 @@ import { message } from 'ant-design-vue' | @@ -35,6 +63,7 @@ import { message } from 'ant-design-vue' | ||
| 35 | import { UserOutlined, LockOutlined } from '@ant-design/icons-vue' | 63 | import { UserOutlined, LockOutlined } from '@ant-design/icons-vue' |
| 36 | import { useAuthStore } from '@/stores/auth' | 64 | import { useAuthStore } from '@/stores/auth' |
| 37 | import request from '@/utils/request' | 65 | import request from '@/utils/request' |
| 66 | +import heroImage from '@/assets/hero.png' | ||
| 38 | 67 | ||
| 39 | const router = useRouter() | 68 | const router = useRouter() |
| 40 | const auth = useAuthStore() | 69 | const auth = useAuthStore() |
| @@ -62,6 +91,152 @@ async function onSubmit() { | @@ -62,6 +91,152 @@ async function onSubmit() { | ||
| 62 | display: flex; | 91 | display: flex; |
| 63 | align-items: center; | 92 | align-items: center; |
| 64 | justify-content: center; | 93 | justify-content: center; |
| 65 | - background: #f0f2f5; | 94 | + padding: 24px; |
| 95 | +} | ||
| 96 | + | ||
| 97 | +.login-shell { | ||
| 98 | + width: min(1180px, 100%); | ||
| 99 | + display: grid; | ||
| 100 | + grid-template-columns: minmax(0, 1.15fr) minmax(360px, 430px); | ||
| 101 | + gap: 26px; | ||
| 102 | +} | ||
| 103 | + | ||
| 104 | +.login-visual, | ||
| 105 | +.login-card { | ||
| 106 | + border: 1px solid rgba(255, 255, 255, 0.62); | ||
| 107 | + background: rgba(255, 255, 255, 0.76); | ||
| 108 | + backdrop-filter: blur(24px); | ||
| 109 | + box-shadow: 0 20px 50px rgba(126, 110, 211, 0.15); | ||
| 110 | +} | ||
| 111 | + | ||
| 112 | +.login-visual { | ||
| 113 | + position: relative; | ||
| 114 | + overflow: hidden; | ||
| 115 | + border-radius: 36px; | ||
| 116 | + padding: 42px; | ||
| 117 | +} | ||
| 118 | + | ||
| 119 | +.visual-badge, | ||
| 120 | +.head-chip { | ||
| 121 | + display: inline-flex; | ||
| 122 | + align-items: center; | ||
| 123 | + border-radius: 999px; | ||
| 124 | + padding: 8px 14px; | ||
| 125 | + background: linear-gradient(135deg, rgba(140, 124, 240, 0.14), rgba(255, 210, 232, 0.26)); | ||
| 126 | + color: #6f5fe2; | ||
| 127 | + font-size: 12px; | ||
| 128 | + font-weight: 700; | ||
| 129 | + text-transform: uppercase; | ||
| 130 | + letter-spacing: 0.08em; | ||
| 131 | +} | ||
| 132 | + | ||
| 133 | +.login-visual h1, | ||
| 134 | +.login-card h2 { | ||
| 135 | + font-family: 'Outfit', sans-serif; | ||
| 136 | + color: #2f2946; | ||
| 137 | +} | ||
| 138 | + | ||
| 139 | +.login-visual h1 { | ||
| 140 | + font-size: 46px; | ||
| 141 | + line-height: 1.05; | ||
| 142 | + margin: 18px 0 14px; | ||
| 143 | +} | ||
| 144 | + | ||
| 145 | +.login-visual p, | ||
| 146 | +.login-card-head p { | ||
| 147 | + color: #8d88a4; | ||
| 148 | + max-width: 520px; | ||
| 149 | +} | ||
| 150 | + | ||
| 151 | +.visual-stats { | ||
| 152 | + display: flex; | ||
| 153 | + gap: 14px; | ||
| 154 | + flex-wrap: wrap; | ||
| 155 | + margin: 28px 0; | ||
| 156 | +} | ||
| 157 | + | ||
| 158 | +.visual-stats > div { | ||
| 159 | + min-width: 210px; | ||
| 160 | + border-radius: 22px; | ||
| 161 | + background: rgba(255, 255, 255, 0.7); | ||
| 162 | + padding: 14px 16px; | ||
| 163 | +} | ||
| 164 | + | ||
| 165 | +.visual-stats span { | ||
| 166 | + display: block; | ||
| 167 | + color: #9893ad; | ||
| 168 | + font-size: 12px; | ||
| 169 | + text-transform: uppercase; | ||
| 170 | + letter-spacing: 0.08em; | ||
| 171 | + margin-bottom: 6px; | ||
| 172 | +} | ||
| 173 | + | ||
| 174 | +.visual-stats strong { | ||
| 175 | + color: #342d4f; | ||
| 176 | + font-family: 'Outfit', sans-serif; | ||
| 177 | +} | ||
| 178 | + | ||
| 179 | +.illustration-wrap { | ||
| 180 | + position: relative; | ||
| 181 | + min-height: 280px; | ||
| 182 | + display: flex; | ||
| 183 | + align-items: flex-end; | ||
| 184 | + justify-content: center; | ||
| 185 | +} | ||
| 186 | + | ||
| 187 | +.illustration-wrap img { | ||
| 188 | + position: relative; | ||
| 189 | + z-index: 2; | ||
| 190 | + width: min(100%, 360px); | ||
| 191 | + filter: drop-shadow(0 26px 40px rgba(137, 120, 216, 0.22)); | ||
| 192 | +} | ||
| 193 | + | ||
| 194 | +.bubble { | ||
| 195 | + position: absolute; | ||
| 196 | + border-radius: 999px; | ||
| 197 | +} | ||
| 198 | + | ||
| 199 | +.bubble-a { | ||
| 200 | + width: 260px; | ||
| 201 | + height: 260px; | ||
| 202 | + background: radial-gradient(circle, rgba(198, 185, 255, 0.55) 0%, transparent 68%); | ||
| 203 | + left: 30px; | ||
| 204 | + bottom: 10px; | ||
| 205 | +} | ||
| 206 | + | ||
| 207 | +.bubble-b { | ||
| 208 | + width: 120px; | ||
| 209 | + height: 120px; | ||
| 210 | + background: radial-gradient(circle, rgba(255, 218, 152, 0.45) 0%, transparent 70%); | ||
| 211 | + right: 60px; | ||
| 212 | + top: 24px; | ||
| 213 | +} | ||
| 214 | + | ||
| 215 | +.login-card { | ||
| 216 | + border-radius: 32px; | ||
| 217 | + padding: 8px; | ||
| 218 | +} | ||
| 219 | + | ||
| 220 | +.login-card-head { | ||
| 221 | + margin-bottom: 20px; | ||
| 222 | +} | ||
| 223 | + | ||
| 224 | +.login-card-head h2 { | ||
| 225 | + margin: 16px 0 8px; | ||
| 226 | + font-size: 32px; | ||
| 227 | +} | ||
| 228 | + | ||
| 229 | +@media (max-width: 980px) { | ||
| 230 | + .login-shell { | ||
| 231 | + grid-template-columns: 1fr; | ||
| 232 | + } | ||
| 233 | + | ||
| 234 | + .login-visual { | ||
| 235 | + padding: 28px; | ||
| 236 | + } | ||
| 237 | + | ||
| 238 | + .login-visual h1 { | ||
| 239 | + font-size: 34px; | ||
| 240 | + } | ||
| 66 | } | 241 | } |
| 67 | </style> | 242 | </style> |
src/views/city/CityList.vue
| 1 | <template> | 1 | <template> |
| 2 | <div> | 2 | <div> |
| 3 | - <a-card title="城市/租户管理" :bordered="false"> | 3 | + <a-card title="租户管理" :bordered="false"> |
| 4 | <template #extra> | 4 | <template #extra> |
| 5 | <a-button type="primary" @click="openAdd">新增</a-button> | 5 | <a-button type="primary" @click="openAdd">新增</a-button> |
| 6 | </template> | 6 | </template> |
| @@ -29,11 +29,11 @@ | @@ -29,11 +29,11 @@ | ||
| 29 | </a-card> | 29 | </a-card> |
| 30 | 30 | ||
| 31 | <!-- 新增/编辑弹窗 --> | 31 | <!-- 新增/编辑弹窗 --> |
| 32 | - <a-modal v-model:open="modalVisible" :title="editingId ? '编辑' : '新增城市/租户'" | 32 | + <a-modal v-model:open="modalVisible" :title="editingId ? '编辑' : '新增租户'" |
| 33 | @ok="handleSave" :confirmLoading="saving"> | 33 | @ok="handleSave" :confirmLoading="saving"> |
| 34 | <a-form :model="form" layout="vertical"> | 34 | <a-form :model="form" layout="vertical"> |
| 35 | <a-form-item label="名称"> | 35 | <a-form-item label="名称"> |
| 36 | - <a-input v-model:value="form.name" placeholder="如:广州市 / 某租户名" /> | 36 | + <a-input v-model:value="form.name" placeholder="如:华东一区 / 某租户名" /> |
| 37 | </a-form-item> | 37 | </a-form-item> |
| 38 | <a-form-item label="区划码/编号(选填)"> | 38 | <a-form-item label="区划码/编号(选填)"> |
| 39 | <a-input v-model:value="form.areaCode" placeholder="行政区划码或自定义编号" /> | 39 | <a-input v-model:value="form.areaCode" placeholder="行政区划码或自定义编号" /> |
src/views/dashboard/DashboardHome.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <div class="dashboard-home"> | ||
| 3 | + <section class="hero-card"> | ||
| 4 | + <div class="hero-copy"> | ||
| 5 | + <div class="hero-pill">Soft-Neo Dashboard</div> | ||
| 6 | + <h2>欢迎回到地利外卖运营工作台</h2> | ||
| 7 | + <p>把租户、骑手、订单和分站管理集中在一张更轻盈的首页里,业务页面本身只保留导航与内容,让操作区域更大、更专注。</p> | ||
| 8 | + <div class="hero-grid"> | ||
| 9 | + <div class="hero-metric"> | ||
| 10 | + <span>Operation Focus</span> | ||
| 11 | + <strong>租户 · 骑手 · 订单</strong> | ||
| 12 | + </div> | ||
| 13 | + <div class="hero-metric"> | ||
| 14 | + <span>Visual Mood</span> | ||
| 15 | + <strong>Lavender / Warm Glow</strong> | ||
| 16 | + </div> | ||
| 17 | + </div> | ||
| 18 | + </div> | ||
| 19 | + <div class="hero-visual"> | ||
| 20 | + <div class="orb orb-a"></div> | ||
| 21 | + <div class="orb orb-b"></div> | ||
| 22 | + <img :src="heroImage" alt="dashboard illustration" /> | ||
| 23 | + </div> | ||
| 24 | + </section> | ||
| 25 | + | ||
| 26 | + <section class="content-grid"> | ||
| 27 | + <a-card title="快捷入口" :bordered="false"> | ||
| 28 | + <div class="quick-links"> | ||
| 29 | + <button v-for="item in quickLinks" :key="item.path" class="quick-link" type="button" @click="go(item.path)"> | ||
| 30 | + <strong>{{ item.title }}</strong> | ||
| 31 | + <span>{{ item.desc }}</span> | ||
| 32 | + </button> | ||
| 33 | + </div> | ||
| 34 | + </a-card> | ||
| 35 | + | ||
| 36 | + <a-card title="界面说明" :bordered="false"> | ||
| 37 | + <ul class="soft-notes"> | ||
| 38 | + <li>首页保留大视觉和信息卡片,适合做总览和快捷入口。</li> | ||
| 39 | + <li>其他菜单页只保留紧凑头部和内容区,避免公共模块挤压表格空间。</li> | ||
| 40 | + <li>整体主题继续沿用柔紫渐变、软阴影和圆角卡片。</li> | ||
| 41 | + </ul> | ||
| 42 | + </a-card> | ||
| 43 | + </section> | ||
| 44 | + </div> | ||
| 45 | +</template> | ||
| 46 | + | ||
| 47 | +<script setup lang="ts"> | ||
| 48 | +import { useRouter } from 'vue-router' | ||
| 49 | +import heroImage from '@/assets/hero.png' | ||
| 50 | + | ||
| 51 | +const router = useRouter() | ||
| 52 | + | ||
| 53 | +const quickLinks = [ | ||
| 54 | + { path: '/city', title: '租户管理', desc: '配置配送费、骑手等级与租户信息' }, | ||
| 55 | + { path: '/rider', title: '骑手管理', desc: '查看骑手、设置等级和账号状态' }, | ||
| 56 | + { path: '/order', title: '订单列表', desc: '集中处理配送中的订单流转' }, | ||
| 57 | + { path: '/substation', title: '分站管理', desc: '维护租户站点账号和权限' }, | ||
| 58 | +] | ||
| 59 | + | ||
| 60 | +function go(path: string) { | ||
| 61 | + router.push(path) | ||
| 62 | +} | ||
| 63 | +</script> | ||
| 64 | + | ||
| 65 | +<style scoped> | ||
| 66 | +.dashboard-home { | ||
| 67 | + display: grid; | ||
| 68 | + gap: 22px; | ||
| 69 | +} | ||
| 70 | + | ||
| 71 | +.hero-card { | ||
| 72 | + display: grid; | ||
| 73 | + grid-template-columns: minmax(0, 1.2fr) minmax(260px, 0.9fr); | ||
| 74 | + gap: 18px; | ||
| 75 | + border-radius: 34px; | ||
| 76 | + padding: 28px; | ||
| 77 | + background: | ||
| 78 | + linear-gradient(160deg, rgba(255, 255, 255, 0.82), rgba(255, 255, 255, 0.62)), | ||
| 79 | + linear-gradient(135deg, rgba(198, 185, 255, 0.72), rgba(255, 217, 236, 0.78)); | ||
| 80 | + border: 1px solid rgba(255, 255, 255, 0.62); | ||
| 81 | + box-shadow: 0 18px 44px rgba(132, 114, 212, 0.14); | ||
| 82 | +} | ||
| 83 | + | ||
| 84 | +.hero-pill { | ||
| 85 | + display: inline-flex; | ||
| 86 | + padding: 8px 14px; | ||
| 87 | + border-radius: 999px; | ||
| 88 | + background: rgba(255, 255, 255, 0.76); | ||
| 89 | + color: #6f5fe2; | ||
| 90 | + font-size: 12px; | ||
| 91 | + font-weight: 700; | ||
| 92 | + text-transform: uppercase; | ||
| 93 | + letter-spacing: 0.08em; | ||
| 94 | +} | ||
| 95 | + | ||
| 96 | +.hero-copy h2 { | ||
| 97 | + margin: 16px 0 10px; | ||
| 98 | + font-size: 34px; | ||
| 99 | + line-height: 1.08; | ||
| 100 | + font-family: 'Outfit', sans-serif; | ||
| 101 | + color: #2f2946; | ||
| 102 | +} | ||
| 103 | + | ||
| 104 | +.hero-copy p, | ||
| 105 | +.hero-metric span, | ||
| 106 | +.quick-link span, | ||
| 107 | +.soft-notes { | ||
| 108 | + color: #8d88a4; | ||
| 109 | +} | ||
| 110 | + | ||
| 111 | +.hero-grid { | ||
| 112 | + display: flex; | ||
| 113 | + gap: 14px; | ||
| 114 | + flex-wrap: wrap; | ||
| 115 | + margin-top: 24px; | ||
| 116 | +} | ||
| 117 | + | ||
| 118 | +.hero-metric { | ||
| 119 | + min-width: 190px; | ||
| 120 | + border-radius: 22px; | ||
| 121 | + background: rgba(255, 255, 255, 0.74); | ||
| 122 | + padding: 14px 16px; | ||
| 123 | +} | ||
| 124 | + | ||
| 125 | +.hero-metric span { | ||
| 126 | + display: block; | ||
| 127 | + font-size: 12px; | ||
| 128 | + text-transform: uppercase; | ||
| 129 | + letter-spacing: 0.08em; | ||
| 130 | + margin-bottom: 6px; | ||
| 131 | +} | ||
| 132 | + | ||
| 133 | +.hero-metric strong, | ||
| 134 | +.quick-link strong { | ||
| 135 | + color: #322c4a; | ||
| 136 | + font-family: 'Outfit', sans-serif; | ||
| 137 | +} | ||
| 138 | + | ||
| 139 | +.hero-visual { | ||
| 140 | + position: relative; | ||
| 141 | + display: flex; | ||
| 142 | + align-items: flex-end; | ||
| 143 | + justify-content: center; | ||
| 144 | + min-height: 260px; | ||
| 145 | +} | ||
| 146 | + | ||
| 147 | +.hero-visual img { | ||
| 148 | + position: relative; | ||
| 149 | + z-index: 2; | ||
| 150 | + width: min(100%, 320px); | ||
| 151 | + filter: drop-shadow(0 24px 36px rgba(135, 119, 212, 0.24)); | ||
| 152 | +} | ||
| 153 | + | ||
| 154 | +.orb { | ||
| 155 | + position: absolute; | ||
| 156 | + border-radius: 999px; | ||
| 157 | +} | ||
| 158 | + | ||
| 159 | +.orb-a { | ||
| 160 | + width: 220px; | ||
| 161 | + height: 220px; | ||
| 162 | + background: radial-gradient(circle, rgba(198, 185, 255, 0.48) 0%, transparent 70%); | ||
| 163 | + top: 8px; | ||
| 164 | + right: 50px; | ||
| 165 | +} | ||
| 166 | + | ||
| 167 | +.orb-b { | ||
| 168 | + width: 100px; | ||
| 169 | + height: 100px; | ||
| 170 | + background: radial-gradient(circle, rgba(255, 217, 136, 0.38) 0%, transparent 70%); | ||
| 171 | + right: 24px; | ||
| 172 | + top: 40px; | ||
| 173 | +} | ||
| 174 | + | ||
| 175 | +.content-grid { | ||
| 176 | + display: grid; | ||
| 177 | + grid-template-columns: 1.2fr 0.8fr; | ||
| 178 | + gap: 18px; | ||
| 179 | +} | ||
| 180 | + | ||
| 181 | +.quick-links { | ||
| 182 | + display: grid; | ||
| 183 | + grid-template-columns: repeat(2, minmax(0, 1fr)); | ||
| 184 | + gap: 14px; | ||
| 185 | +} | ||
| 186 | + | ||
| 187 | +.quick-link { | ||
| 188 | + text-align: left; | ||
| 189 | + border: 1px solid rgba(202, 193, 240, 0.34); | ||
| 190 | + background: rgba(255, 255, 255, 0.78); | ||
| 191 | + border-radius: 24px; | ||
| 192 | + padding: 18px; | ||
| 193 | + cursor: pointer; | ||
| 194 | +} | ||
| 195 | + | ||
| 196 | +.soft-notes { | ||
| 197 | + margin: 0; | ||
| 198 | + padding-left: 18px; | ||
| 199 | +} | ||
| 200 | + | ||
| 201 | +.soft-notes li + li { | ||
| 202 | + margin-top: 10px; | ||
| 203 | +} | ||
| 204 | + | ||
| 205 | +@media (max-width: 980px) { | ||
| 206 | + .hero-card, | ||
| 207 | + .content-grid, | ||
| 208 | + .quick-links { | ||
| 209 | + grid-template-columns: 1fr; | ||
| 210 | + } | ||
| 211 | + | ||
| 212 | + .hero-copy h2 { | ||
| 213 | + font-size: 28px; | ||
| 214 | + } | ||
| 215 | +} | ||
| 216 | +</style> |
src/views/merchant/StoreList.vue
| @@ -3,7 +3,7 @@ | @@ -3,7 +3,7 @@ | ||
| 3 | <a-card title="店铺管理" :bordered="false"> | 3 | <a-card title="店铺管理" :bordered="false"> |
| 4 | <template #extra> | 4 | <template #extra> |
| 5 | <a-space> | 5 | <a-space> |
| 6 | - <a-select v-model:value="filterCityId" placeholder="选择城市" allowClear style="width:150px" @change="loadList"> | 6 | + <a-select v-model:value="filterCityId" placeholder="选择租户" allowClear style="width:150px" @change="loadList"> |
| 7 | <a-select-option v-for="c in cityList" :key="c.id" :value="c.id">{{ c.name }}</a-select-option> | 7 | <a-select-option v-for="c in cityList" :key="c.id" :value="c.id">{{ c.name }}</a-select-option> |
| 8 | </a-select> | 8 | </a-select> |
| 9 | <a-input-search v-model:value="keyword" placeholder="搜索店铺名" @search="loadList" style="width:200px" /> | 9 | <a-input-search v-model:value="keyword" placeholder="搜索店铺名" @search="loadList" style="width:200px" /> |
| @@ -43,8 +43,8 @@ | @@ -43,8 +43,8 @@ | ||
| 43 | @ok="handleSave" :confirmLoading="saving" width="600px"> | 43 | @ok="handleSave" :confirmLoading="saving" width="600px"> |
| 44 | <a-form :model="form" layout="vertical"> | 44 | <a-form :model="form" layout="vertical"> |
| 45 | <a-form-item label="店铺名称"><a-input v-model:value="form.name" /></a-form-item> | 45 | <a-form-item label="店铺名称"><a-input v-model:value="form.name" /></a-form-item> |
| 46 | - <a-form-item label="所属城市"> | ||
| 47 | - <a-select v-model:value="form.cityId" placeholder="选择城市"> | 46 | + <a-form-item label="所属租户"> |
| 47 | + <a-select v-model:value="form.cityId" placeholder="选择租户"> | ||
| 48 | <a-select-option v-for="c in cityList" :key="c.id" :value="c.id">{{ c.name }}</a-select-option> | 48 | <a-select-option v-for="c in cityList" :key="c.id" :value="c.id">{{ c.name }}</a-select-option> |
| 49 | </a-select> | 49 | </a-select> |
| 50 | </a-form-item> | 50 | </a-form-item> |
| @@ -111,7 +111,7 @@ const feeForm = reactive({ freeShipping: 0, upToSend: 0 }) | @@ -111,7 +111,7 @@ const feeForm = reactive({ freeShipping: 0, upToSend: 0 }) | ||
| 111 | const columns = [ | 111 | const columns = [ |
| 112 | { title: 'ID', dataIndex: 'id', width: 80 }, | 112 | { title: 'ID', dataIndex: 'id', width: 80 }, |
| 113 | { title: '店铺名', dataIndex: 'name' }, | 113 | { title: '店铺名', dataIndex: 'name' }, |
| 114 | - { title: '城市', dataIndex: 'cityId' }, | 114 | + { title: '租户', dataIndex: 'cityId' }, |
| 115 | { title: '外部编号', dataIndex: 'outStoreId', ellipsis: true }, | 115 | { title: '外部编号', dataIndex: 'outStoreId', ellipsis: true }, |
| 116 | { title: '接入方', dataIndex: 'appKey', ellipsis: true }, | 116 | { title: '接入方', dataIndex: 'appKey', ellipsis: true }, |
| 117 | { title: '地址', dataIndex: 'address', ellipsis: true }, | 117 | { title: '地址', dataIndex: 'address', ellipsis: true }, |
src/views/open/OpenAppList.vue
| @@ -36,8 +36,8 @@ | @@ -36,8 +36,8 @@ | ||
| 36 | <a-modal v-model:open="addVisible" title="创建应用" @ok="handleCreate" :confirmLoading="saving"> | 36 | <a-modal v-model:open="addVisible" title="创建应用" @ok="handleCreate" :confirmLoading="saving"> |
| 37 | <a-form :model="addForm" layout="vertical"> | 37 | <a-form :model="addForm" layout="vertical"> |
| 38 | <a-form-item label="应用名称"><a-input v-model:value="addForm.appName" /></a-form-item> | 38 | <a-form-item label="应用名称"><a-input v-model:value="addForm.appName" /></a-form-item> |
| 39 | - <a-form-item label="关联城市/租户(必填)"> | ||
| 40 | - <a-select v-model:value="addForm.cityId" placeholder="选择城市" style="width:100%"> | 39 | + <a-form-item label="关联租户(必填)"> |
| 40 | + <a-select v-model:value="addForm.cityId" placeholder="选择租户" style="width:100%"> | ||
| 41 | <a-select-option v-for="c in cityList" :key="c.id" :value="c.id">{{ c.name }}</a-select-option> | 41 | <a-select-option v-for="c in cityList" :key="c.id" :value="c.id">{{ c.name }}</a-select-option> |
| 42 | </a-select> | 42 | </a-select> |
| 43 | </a-form-item> | 43 | </a-form-item> |
| @@ -98,7 +98,7 @@ const columns = [ | @@ -98,7 +98,7 @@ const columns = [ | ||
| 98 | { title: 'ID', dataIndex: 'id', width: 80 }, | 98 | { title: 'ID', dataIndex: 'id', width: 80 }, |
| 99 | { title: '应用名称', dataIndex: 'appName' }, | 99 | { title: '应用名称', dataIndex: 'appName' }, |
| 100 | { title: 'AppKey', key: 'appKey' }, | 100 | { title: 'AppKey', key: 'appKey' }, |
| 101 | - { title: '城市/租户', dataIndex: 'cityId' }, | 101 | + { title: '租户', dataIndex: 'cityId' }, |
| 102 | { title: '状态', key: 'status' }, | 102 | { title: '状态', key: 'status' }, |
| 103 | { title: '回调地址', dataIndex: 'webhookUrl', ellipsis: true }, | 103 | { title: '回调地址', dataIndex: 'webhookUrl', ellipsis: true }, |
| 104 | { title: '操作', key: 'action' }, | 104 | { title: '操作', key: 'action' }, |
| @@ -133,7 +133,7 @@ function openAdd() { | @@ -133,7 +133,7 @@ function openAdd() { | ||
| 133 | } | 133 | } |
| 134 | 134 | ||
| 135 | async function handleCreate() { | 135 | async function handleCreate() { |
| 136 | - if (!addForm.cityId) { message.error('请选择城市/租户'); return } | 136 | + if (!addForm.cityId) { message.error('请选择租户'); return } |
| 137 | saving.value = true | 137 | saving.value = true |
| 138 | try { | 138 | try { |
| 139 | await openApi.create(addForm) | 139 | await openApi.create(addForm) |
src/views/order/OrderList.vue
| @@ -51,8 +51,25 @@ | @@ -51,8 +51,25 @@ | ||
| 51 | <!-- 指派骑手弹窗 --> | 51 | <!-- 指派骑手弹窗 --> |
| 52 | <a-modal v-model:open="designateVisible" title="指派骑手" @ok="handleDesignate" :confirmLoading="saving"> | 52 | <a-modal v-model:open="designateVisible" title="指派骑手" @ok="handleDesignate" :confirmLoading="saving"> |
| 53 | <a-form layout="vertical"> | 53 | <a-form layout="vertical"> |
| 54 | - <a-form-item label="骑手ID"> | ||
| 55 | - <a-input-number v-model:value="designateRiderId" style="width:100%" placeholder="输入骑手ID" /> | 54 | + <a-form-item label="选择骑手"> |
| 55 | + <a-select | ||
| 56 | + v-model:value="designateRiderId" | ||
| 57 | + style="width:100%" | ||
| 58 | + placeholder="请选择可指派骑手" | ||
| 59 | + :loading="candidateLoading" | ||
| 60 | + show-search | ||
| 61 | + :filter-option="filterCandidateOption" | ||
| 62 | + option-label-prop="label" | ||
| 63 | + > | ||
| 64 | + <a-select-option | ||
| 65 | + v-for="item in designateCandidates" | ||
| 66 | + :key="item.id" | ||
| 67 | + :value="item.id" | ||
| 68 | + :label="`${item.userNickname || '未命名'}(ID:${item.id})`" | ||
| 69 | + > | ||
| 70 | + {{ item.userNickname || '未命名' }}(ID:{{ item.id }} / {{ item.mobile || '无手机号' }} / {{ item.isRest === 1 ? '休息' : '在线' }}) | ||
| 71 | + </a-select-option> | ||
| 72 | + </a-select> | ||
| 56 | </a-form-item> | 73 | </a-form-item> |
| 57 | </a-form> | 74 | </a-form> |
| 58 | </a-modal> | 75 | </a-modal> |
| @@ -98,11 +115,13 @@ const filterStatus = ref<number | undefined>() | @@ -98,11 +115,13 @@ const filterStatus = ref<number | undefined>() | ||
| 98 | const filterTrans = ref<number | undefined>() | 115 | const filterTrans = ref<number | undefined>() |
| 99 | const keyword = ref('') | 116 | const keyword = ref('') |
| 100 | const designateVisible = ref(false) | 117 | const designateVisible = ref(false) |
| 118 | +const candidateLoading = ref(false) | ||
| 101 | const refundVisible = ref(false) | 119 | const refundVisible = ref(false) |
| 102 | const rejectVisible = ref(false) | 120 | const rejectVisible = ref(false) |
| 103 | const rejectRemark = ref('') | 121 | const rejectRemark = ref('') |
| 104 | const designateOrderId = ref(0) | 122 | const designateOrderId = ref(0) |
| 105 | const designateRiderId = ref<number | undefined>() | 123 | const designateRiderId = ref<number | undefined>() |
| 124 | +const designateCandidates = ref<any[]>([]) | ||
| 106 | const refundRecord = ref<any>(null) | 125 | const refundRecord = ref<any>(null) |
| 107 | const currentRefundRecordId = ref(0) | 126 | const currentRefundRecordId = ref(0) |
| 108 | 127 | ||
| @@ -138,14 +157,26 @@ async function loadList() { | @@ -138,14 +157,26 @@ async function loadList() { | ||
| 138 | } finally { loading.value = false } | 157 | } finally { loading.value = false } |
| 139 | } | 158 | } |
| 140 | 159 | ||
| 141 | -function openDesignate(record: any) { | 160 | +async function openDesignate(record: any) { |
| 142 | designateOrderId.value = record.id | 161 | designateOrderId.value = record.id |
| 143 | designateRiderId.value = undefined | 162 | designateRiderId.value = undefined |
| 163 | + designateCandidates.value = [] | ||
| 164 | + candidateLoading.value = true | ||
| 165 | + try { | ||
| 166 | + const res: any = await riderApi.designateCandidates(record.id) | ||
| 167 | + designateCandidates.value = Array.isArray(res?.data) ? res.data : [] | ||
| 168 | + } finally { | ||
| 169 | + candidateLoading.value = false | ||
| 170 | + } | ||
| 144 | designateVisible.value = true | 171 | designateVisible.value = true |
| 145 | } | 172 | } |
| 146 | 173 | ||
| 174 | +function filterCandidateOption(input: string, option: any) { | ||
| 175 | + return String(option.label || '').toLowerCase().includes(input.toLowerCase()) | ||
| 176 | +} | ||
| 177 | + | ||
| 147 | async function handleDesignate() { | 178 | async function handleDesignate() { |
| 148 | - if (!designateRiderId.value) { message.error('请输入骑手ID'); return } | 179 | + if (!designateRiderId.value) { message.error('请选择骑手'); return } |
| 149 | saving.value = true | 180 | saving.value = true |
| 150 | try { | 181 | try { |
| 151 | await riderApi.designate(designateOrderId.value, designateRiderId.value) | 182 | await riderApi.designate(designateOrderId.value, designateRiderId.value) |
src/views/rider/RiderEvaluateList.vue
| @@ -38,7 +38,7 @@ const columns = [ | @@ -38,7 +38,7 @@ const columns = [ | ||
| 38 | { title: '骑手ID', dataIndex: 'rid' }, | 38 | { title: '骑手ID', dataIndex: 'rid' }, |
| 39 | { title: '评分', key: 'star' }, | 39 | { title: '评分', key: 'star' }, |
| 40 | { title: '内容', dataIndex: 'content', ellipsis: true }, | 40 | { title: '内容', dataIndex: 'content', ellipsis: true }, |
| 41 | - { title: '城市', dataIndex: 'cityId' }, | 41 | + { title: '租户', dataIndex: 'cityId' }, |
| 42 | ] | 42 | ] |
| 43 | 43 | ||
| 44 | async function loadList() { | 44 | async function loadList() { |
src/views/rider/RiderList.vue
| @@ -27,6 +27,11 @@ | @@ -27,6 +27,11 @@ | ||
| 27 | {{ getAccountStatus(record) === 1 ? '正常' : '禁用' }} | 27 | {{ getAccountStatus(record) === 1 ? '正常' : '禁用' }} |
| 28 | </a-tag> | 28 | </a-tag> |
| 29 | </template> | 29 | </template> |
| 30 | + <template v-if="column.key === 'workStatus'"> | ||
| 31 | + <a-tag :color="getWorkStatus(record) === 0 ? 'green' : 'orange'"> | ||
| 32 | + {{ getWorkStatus(record) === 0 ? '在线' : '休息' }} | ||
| 33 | + </a-tag> | ||
| 34 | + </template> | ||
| 30 | <template v-if="column.key === 'type'"> | 35 | <template v-if="column.key === 'type'"> |
| 31 | <a-tag>{{ record.type === 1 ? '兼职' : '全职' }}</a-tag> | 36 | <a-tag>{{ record.type === 1 ? '兼职' : '全职' }}</a-tag> |
| 32 | </template> | 37 | </template> |
| @@ -63,8 +68,8 @@ | @@ -63,8 +68,8 @@ | ||
| 63 | 68 | ||
| 64 | <a-modal v-model:open="modalVisible" title="新增骑手" @ok="handleAdd" :confirmLoading="saving"> | 69 | <a-modal v-model:open="modalVisible" title="新增骑手" @ok="handleAdd" :confirmLoading="saving"> |
| 65 | <a-form :model="form" layout="vertical"> | 70 | <a-form :model="form" layout="vertical"> |
| 66 | - <a-form-item v-if="isAdmin" label="城市"> | ||
| 67 | - <a-select v-model:value="form.cityId" placeholder="选择城市"> | 71 | + <a-form-item v-if="isAdmin" label="租户"> |
| 72 | + <a-select v-model:value="form.cityId" placeholder="选择租户"> | ||
| 68 | <a-select-option v-for="c in cityList" :key="c.id" :value="c.id">{{ c.name }}</a-select-option> | 73 | <a-select-option v-for="c in cityList" :key="c.id" :value="c.id">{{ c.name }}</a-select-option> |
| 69 | </a-select> | 74 | </a-select> |
| 70 | </a-form-item> | 75 | </a-form-item> |
| @@ -132,11 +137,12 @@ const columns = [ | @@ -132,11 +137,12 @@ const columns = [ | ||
| 132 | { title: 'ID', dataIndex: 'id', width: 80 }, | 137 | { title: 'ID', dataIndex: 'id', width: 80 }, |
| 133 | { title: '昵称', dataIndex: 'userNickname' }, | 138 | { title: '昵称', dataIndex: 'userNickname' }, |
| 134 | { title: '手机', dataIndex: 'mobile' }, | 139 | { title: '手机', dataIndex: 'mobile' }, |
| 135 | - { title: '城市ID', dataIndex: 'cityId' }, | 140 | + { title: '租户ID', dataIndex: 'cityId' }, |
| 136 | { title: '等级', key: 'levelName' }, | 141 | { title: '等级', key: 'levelName' }, |
| 137 | { title: '类型', key: 'type' }, | 142 | { title: '类型', key: 'type' }, |
| 138 | { title: '审核状态', key: 'userStatus' }, | 143 | { title: '审核状态', key: 'userStatus' }, |
| 139 | { title: '账号状态', key: 'accountStatus' }, | 144 | { title: '账号状态', key: 'accountStatus' }, |
| 145 | + { title: '骑手状态', key: 'workStatus' }, | ||
| 140 | { title: '余额', dataIndex: 'balance' }, | 146 | { title: '余额', dataIndex: 'balance' }, |
| 141 | { title: '操作', key: 'action' }, | 147 | { title: '操作', key: 'action' }, |
| 142 | ] | 148 | ] |
| @@ -145,6 +151,10 @@ function getAccountStatus(record: any) { | @@ -145,6 +151,10 @@ function getAccountStatus(record: any) { | ||
| 145 | return record.status === 0 ? 0 : 1 | 151 | return record.status === 0 ? 0 : 1 |
| 146 | } | 152 | } |
| 147 | 153 | ||
| 154 | +function getWorkStatus(record: any) { | ||
| 155 | + return record.isRest === 1 ? 1 : 0 | ||
| 156 | +} | ||
| 157 | + | ||
| 148 | async function loadList() { | 158 | async function loadList() { |
| 149 | loading.value = true | 159 | loading.value = true |
| 150 | try { | 160 | try { |
| @@ -177,7 +187,7 @@ async function handleAdd() { | @@ -177,7 +187,7 @@ async function handleAdd() { | ||
| 177 | return | 187 | return |
| 178 | } | 188 | } |
| 179 | if (isAdmin.value && !form.cityId) { | 189 | if (isAdmin.value && !form.cityId) { |
| 180 | - message.error('请选择城市') | 190 | + message.error('请选择租户') |
| 181 | return | 191 | return |
| 182 | } | 192 | } |
| 183 | 193 |
src/views/substation/SubstationList.vue
| @@ -37,8 +37,8 @@ | @@ -37,8 +37,8 @@ | ||
| 37 | <a-modal v-model:open="modalVisible" :title="editingId ? '编辑分站' : '新增分站'" | 37 | <a-modal v-model:open="modalVisible" :title="editingId ? '编辑分站' : '新增分站'" |
| 38 | @ok="handleSave" :confirmLoading="saving"> | 38 | @ok="handleSave" :confirmLoading="saving"> |
| 39 | <a-form :model="form" layout="vertical"> | 39 | <a-form :model="form" layout="vertical"> |
| 40 | - <a-form-item label="管理城市"> | ||
| 41 | - <a-select v-model:value="form.cityId" placeholder="选择城市"> | 40 | + <a-form-item label="所属租户"> |
| 41 | + <a-select v-model:value="form.cityId" placeholder="选择租户"> | ||
| 42 | <a-select-option v-for="c in cityList" :key="c.id" :value="c.id">{{ c.name }}</a-select-option> | 42 | <a-select-option v-for="c in cityList" :key="c.id" :value="c.id">{{ c.name }}</a-select-option> |
| 43 | </a-select> | 43 | </a-select> |
| 44 | </a-form-item> | 44 | </a-form-item> |
| @@ -90,7 +90,7 @@ const columns = [ | @@ -90,7 +90,7 @@ const columns = [ | ||
| 90 | { title: '账号', dataIndex: 'userLogin' }, | 90 | { title: '账号', dataIndex: 'userLogin' }, |
| 91 | { title: '昵称', dataIndex: 'userNickname' }, | 91 | { title: '昵称', dataIndex: 'userNickname' }, |
| 92 | { title: '手机', dataIndex: 'mobile' }, | 92 | { title: '手机', dataIndex: 'mobile' }, |
| 93 | - { title: '城市ID', dataIndex: 'cityId' }, | 93 | + { title: '租户ID', dataIndex: 'cityId' }, |
| 94 | { title: '状态', key: 'status' }, | 94 | { title: '状态', key: 'status' }, |
| 95 | { title: '操作', key: 'action' }, | 95 | { title: '操作', key: 'action' }, |
| 96 | ] | 96 | ] |