feat(router): 更新路由结构并优化页面组件

- 移除经营范围按钮,精简导航栏
- 实现文章标题链接功能,提升用户体验
- 添加商品详情页面包屑导航,支持分类跳转
- 引入配送管理相关页面(区域、接单台、配送员、派单)
- 替换控制台布局为站点头部和底部组件
- 重构商品分类页面,集成CMS导航功能
- 新增文章详情页面,支持多种访问方式
- 删除已迁移的创建应用和空应用页面
- 优化样式和组件导入,提升代码质量
This commit is contained in:
2026-01-29 16:21:22 +08:00
parent 26c236041f
commit 682e264a6f
22 changed files with 1309 additions and 881 deletions

View File

@@ -1,164 +1,180 @@
<template>
<div class="login-page" :style="bgStyle">
<div class="overlay" />
<div class="login-shell">
<SiteHeader />
<div v-if="config?.siteName" class="brand">
<img :src="config.sysLogo || defaultLogo" class="brand-logo" alt="logo" />
<h1 class="brand-name">{{ config.siteName }}</h1>
</div>
<div class="login-page" :style="bgStyle">
<div v-if="config?.loginTitle" class="brand-title">{{ config.loginTitle }}</div>
<a-form ref="formRef" :model="form" :rules="rules" class="card">
<div class="card-header">
<template v-if="loginType === 'scan'">
<h2 class="card-title">扫码登录</h2>
</template>
<template v-else>
<h2 class="tab" :class="{ active: loginType === 'sms' }" @click="setLoginType('sms')">
手机号登录
</h2>
<a-divider type="vertical" style="height: 20px" />
<h2
class="tab"
:class="{ active: loginType === 'account' }"
@click="setLoginType('account')"
>
账号登录
</h2>
</template>
<a-button class="switch" type="text" @click="toggleScan" :title="loginType === 'scan' ? '切换到手机号登录' : '切换到扫码登录'">
<QrcodeOutlined v-if="loginType !== 'scan'" />
<MobileOutlined v-else />
</a-button>
<div v-if="config?.siteName" class="brand">
<img :src="config.sysLogo || defaultLogo" class="brand-logo" alt="logo" />
<h1 class="brand-name">{{ config.siteName }}</h1>
</div>
<template v-if="loginType === 'account'">
<a-form-item name="username">
<a-input v-model:value="form.username" size="large" allow-clear placeholder="账号 / 用户ID">
<template #prefix><UserOutlined /></template>
</a-input>
</a-form-item>
<div v-if="config?.loginTitle" class="brand-title">{{ config.loginTitle }}</div>
<a-form-item name="password">
<a-input-password
v-model:value="form.password"
size="large"
placeholder="登录密码"
@press-enter="submitAccount"
<a-form ref="formRef" :model="form" :rules="rules" class="card">
<div class="card-header">
<template v-if="loginType === 'scan'">
<h2 class="card-title">扫码登录</h2>
</template>
<template v-else>
<h2 class="tab" :class="{ active: loginType === 'sms' }" @click="setLoginType('sms')">
手机号登录
</h2>
<a-divider type="vertical" style="height: 20px" />
<h2
class="tab"
:class="{ active: loginType === 'account' }"
@click="setLoginType('account')"
>
账号登录
</h2>
</template>
<a-button
class="switch"
type="text"
@click="toggleScan"
:title="loginType === 'scan' ? '切换到手机号登录' : '切换到扫码登录'"
>
<template #prefix><LockOutlined /></template>
</a-input-password>
</a-form-item>
<QrcodeOutlined v-if="loginType !== 'scan'" />
<MobileOutlined v-else />
</a-button>
</div>
<a-form-item name="code">
<div class="input-group">
<a-input
v-model:value="form.code"
<template v-if="loginType === 'account'">
<a-form-item name="username">
<a-input v-model:value="form.username" size="large" allow-clear placeholder="账号 / 用户ID">
<template #prefix><UserOutlined /></template>
</a-input>
</a-form-item>
<a-form-item name="password">
<a-input-password
v-model:value="form.password"
size="large"
allow-clear
:maxlength="5"
placeholder="验证码"
placeholder="登录密码"
@press-enter="submitAccount"
>
<template #prefix><SafetyCertificateOutlined /></template>
</a-input>
<a-button class="captcha-btn" @click="changeCaptcha">
<img v-if="captcha" :src="captcha" alt="captcha" />
<template #prefix><LockOutlined /></template>
</a-input-password>
</a-form-item>
<a-form-item name="code">
<div class="input-group">
<a-input
v-model:value="form.code"
size="large"
allow-clear
:maxlength="5"
placeholder="验证码"
@press-enter="submitAccount"
>
<template #prefix><SafetyCertificateOutlined /></template>
</a-input>
<a-button class="captcha-btn" @click="changeCaptcha">
<img v-if="captcha" :src="captcha" alt="captcha" />
</a-button>
</div>
</a-form-item>
<a-form-item>
<div class="row">
<a-checkbox v-model:checked="form.remember">记住登录</a-checkbox>
</div>
</a-form-item>
<a-form-item>
<a-button block size="large" type="primary" :loading="loading" @click="submitAccount">
{{ loading ? '登录中' : '登录' }}
</a-button>
</div>
</a-form-item>
</a-form-item>
</template>
<a-form-item>
<div class="row">
<a-checkbox v-model:checked="form.remember">记住登录</a-checkbox>
</div>
</a-form-item>
<a-form-item>
<a-button block size="large" type="primary" :loading="loading" @click="submitAccount">
{{ loading ? '登录中' : '登录' }}
</a-button>
</a-form-item>
</template>
<template v-else-if="loginType === 'sms'">
<a-form-item name="phone">
<a-input v-model:value="form.phone" size="large" allow-clear :maxlength="11" placeholder="请输入手机号码">
<template #addonBefore>+86</template>
</a-input>
</a-form-item>
<a-form-item name="smsCode">
<div class="input-group">
<template v-else-if="loginType === 'sms'">
<a-form-item name="phone">
<a-input
v-model:value="form.smsCode"
v-model:value="form.phone"
size="large"
allow-clear
:maxlength="6"
placeholder="请输入验证码"
@press-enter="submitSms"
/>
<a-button class="captcha-btn" :disabled="countdown > 0" @click="openImgCodeModal">
<span v-if="countdown <= 0">发送验证码</span>
<span v-else>已发送 {{ countdown }} s</span>
:maxlength="11"
placeholder="请输入手机号码"
>
<template #addonBefore>+86</template>
</a-input>
</a-form-item>
<a-form-item name="smsCode">
<div class="input-group">
<a-input
v-model:value="form.smsCode"
size="large"
allow-clear
:maxlength="6"
placeholder="请输入验证码"
@press-enter="submitSms"
/>
<a-button class="captcha-btn" :disabled="countdown > 0" @click="openImgCodeModal">
<span v-if="countdown <= 0">发送验证码</span>
<span v-else>已发送 {{ countdown }} s</span>
</a-button>
</div>
</a-form-item>
<a-form-item>
<a-button block size="large" type="primary" :loading="loading" @click="submitSms">
{{ loading ? '登录中' : '登录' }}
</a-button>
</div>
</a-form-item>
</a-form-item>
</template>
<a-form-item>
<a-button block size="large" type="primary" :loading="loading" @click="submitSms">
{{ loading ? '登录中' : '登录' }}
<template v-else>
<QrLogin @login-success="onQrLoginSuccess" @login-error="onQrLoginError" />
</template>
</a-form>
<div class="copyright hidden">
<span>© {{ new Date().getFullYear() }}</span>
<span class="sep">·</span>
<span>{{ config?.copyright || 'websoft.top Inc.' }}</span>
</div>
<a-modal v-model:open="imgCodeModalOpen" :width="340" :footer="null" title="发送验证码">
<div class="input-group modal-row">
<a-input
v-model:value="imgCode"
size="large"
allow-clear
:maxlength="5"
placeholder="请输入图形验证码"
@press-enter="sendSmsCode"
/>
<a-button class="captcha-btn">
<img alt="captcha" :src="captcha" @click="changeCaptcha" />
</a-button>
</a-form-item>
</template>
</div>
<a-button block size="large" type="primary" :loading="sendingSms" @click="sendSmsCode">
立即发送
</a-button>
</a-modal>
<template v-else>
<QrLogin @login-success="onQrLoginSuccess" @login-error="onQrLoginError" />
</template>
</a-form>
<div class="copyright">
<span>© {{ new Date().getFullYear() }}</span>
<span class="sep">·</span>
<span>{{ config?.copyright || 'websoft.top Inc.' }}</span>
<a-modal v-model:open="selectUserOpen" :width="520" :footer="null" title="选择账号登录">
<a-list item-layout="horizontal" :data-source="admins">
<template #renderItem="{ item }">
<a-list-item class="list-item" @click="selectUser(item)">
<a-list-item-meta :description="`租户ID: ${item.tenantId}`">
<template #title>{{ item.tenantName || item.username }}</template>
<template #avatar>
<a-avatar :src="item.avatar" />
</template>
</a-list-item-meta>
<template #actions><RightOutlined /></template>
</a-list-item>
</template>
</a-list>
</a-modal>
</div>
<a-modal v-model:open="imgCodeModalOpen" :width="340" :footer="null" title="发送验证码">
<div class="input-group modal-row">
<a-input
v-model:value="imgCode"
size="large"
allow-clear
:maxlength="5"
placeholder="请输入图形验证码"
@press-enter="sendSmsCode"
/>
<a-button class="captcha-btn">
<img alt="captcha" :src="captcha" @click="changeCaptcha" />
</a-button>
</div>
<a-button block size="large" type="primary" :loading="sendingSms" @click="sendSmsCode">
立即发送
</a-button>
</a-modal>
<a-modal v-model:open="selectUserOpen" :width="520" :footer="null" title="选择账号登录">
<a-list item-layout="horizontal" :data-source="admins">
<template #renderItem="{ item }">
<a-list-item class="list-item" @click="selectUser(item)">
<a-list-item-meta :description="`租户ID: ${item.tenantId}`">
<template #title>{{ item.tenantName || item.username }}</template>
<template #avatar>
<a-avatar :src="item.avatar" />
</template>
</a-list-item-meta>
<template #actions><RightOutlined /></template>
</a-list-item>
</template>
</a-list>
</a-modal>
<SiteFooter />
</div>
</template>
@@ -183,6 +199,7 @@ import { TEMPLATE_ID } from '@/config/setting'
import { setToken } from '@/utils/token-util'
import type { QrCodeStatusResponse } from '@/api/passport/qrLogin'
// Login page is a public page: keep a lightweight layout and render header/footer locally.
definePageMeta({ layout: 'blank' })
const route = useRoute()
@@ -387,10 +404,16 @@ onUnmounted(() => {
</script>
<style scoped>
.login-shell {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.login-page {
position: relative;
min-height: 100vh;
background: #111827;
flex: 1;
min-height: 0;
background-size: cover;
background-position: center;
padding: 48px 16px;