This commit is contained in:
quantulr
2023-10-29 18:37:44 +08:00
commit 373b3d9dff
1813 changed files with 131892 additions and 0 deletions

View File

@ -0,0 +1,113 @@
<template>
<div class="login">
<div class="flex justify-between">
<span class="text-4xl">
{{ hasMobile ? '更换手机号' : '绑定手机号' }}
</span>
</div>
<ElForm
ref="formRef"
class="mt-[35px]"
size="large"
:model="formData"
:rules="formRules"
>
<ElFormItem prop="mobile">
<ElInput
v-model="formData.mobile"
placeholder="请输入手机号码"
/>
</ElFormItem>
<ElFormItem prop="code">
<ElInput v-model="formData.code" placeholder="请输入验证码">
<template #suffix>
<div
class="flex justify-center leading-5 w-[90px] pl-2.5 border-l border-br"
>
<VerificationCode
ref="verificationCodeRef"
@click-get="sendSms"
/>
</div>
</template>
</ElInput>
</ElFormItem>
<ElFormItem class="mt-[60px]">
<ElButton
class="w-full"
type="primary"
@click="handleConfirmLock"
:loading="isLock"
>
确认
</ElButton>
</ElFormItem>
</ElForm>
</div>
</template>
<script lang="ts" setup>
import {
ElForm,
ElFormItem,
ElInput,
ElButton,
FormInstance,
FormRules
} from 'element-plus'
import { smsSend } from '~~/api/app'
import { userBindMobile } from '~~/api/user'
import { SMSEnum } from '~~/enums/appEnums'
import { useAccount } from './useAccount'
import { useUserStore } from '@/stores/user'
const { toggleShowPopup } = useAccount()
const userStore = useUserStore()
const formRef = shallowRef<FormInstance>()
const verificationCodeRef = shallowRef()
const formRules: FormRules = {
mobile: [
{
required: true,
message: '请输入手机号码',
trigger: ['change', 'blur']
}
],
code: [
{
required: true,
message: '请输入验证码',
trigger: ['change', 'blur']
}
]
}
const hasMobile = computed(() => !!userStore.userInfo.mobile)
const formData = reactive({
type: hasMobile.value ? 'change' : 'bind',
mobile: '',
code: ''
})
const sendSms = async () => {
await formRef.value?.validateField(['mobile'])
await smsSend({
scene: hasMobile.value ? SMSEnum.CHANGE_MOBILE : SMSEnum.BIND_MOBILE,
mobile: formData.mobile
})
verificationCodeRef.value?.start()
}
const handleConfirm = async () => {
await formRef.value?.validate()
if (userStore.isLogin) {
await userBindMobile(formData)
} else {
await userBindMobile(formData, { token: userStore.temToken })
userStore.login(userStore.temToken)
await userStore.getUser()
}
toggleShowPopup(false)
}
const { lockFn: handleConfirmLock, isLock } = useLockFn(handleConfirm)
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,164 @@
<template>
<div class="login">
<div class="flex justify-between">
<span class="text-4xl">忘记登录密码</span>
<ElButton
type="primary"
link
@click="setPopupType(PopupTypeEnum.LOGIN)"
v-if="!userStore.isLogin"
>
返回登录
</ElButton>
</div>
<ElForm
ref="formRef"
class="mt-[35px]"
size="large"
:model="formData"
:rules="formRules"
>
<ElFormItem prop="mobile">
<ElInput
v-model="formData.mobile"
placeholder="请输入手机号码"
/>
</ElFormItem>
<ElFormItem prop="code">
<ElInput v-model="formData.code" placeholder="请输入验证码">
<template #suffix>
<div
class="flex justify-center leading-5 w-[90px] pl-2.5 border-l border-br"
>
<VerificationCode
ref="verificationCodeRef"
@click-get="sendSms"
/>
</div>
</template>
</ElInput>
</ElFormItem>
<ElFormItem prop="password">
<ElInput
v-model="formData.password"
placeholder="请输入6-20位数字+字母或符号组合"
type="password"
show-password
/>
</ElFormItem>
<ElFormItem prop="passwordConfirm">
<ElInput
v-model="formData.passwordConfirm"
placeholder="请再次输入密码"
type="password"
show-password
/>
</ElFormItem>
<ElFormItem class="mt-[60px]">
<ElButton
class="w-full"
type="primary"
@click="handleConfirmLock"
:loading="isLock"
>
确认
</ElButton>
</ElFormItem>
</ElForm>
</div>
</template>
<script lang="ts" setup>
import {
ElForm,
ElFormItem,
ElInput,
ElButton,
FormInstance,
FormRules
} from 'element-plus'
import { smsSend } from '~~/api/app'
import { forgotPassword } from '~~/api/account'
import { SMSEnum } from '~~/enums/appEnums'
import { useUserStore } from '~~/stores/user'
import { useAccount, PopupTypeEnum } from './useAccount'
import feedback from '~~/utils/feedback'
const userStore = useUserStore()
const { setPopupType, toggleShowPopup } = useAccount()
const formRef = shallowRef<FormInstance>()
const verificationCodeRef = shallowRef()
const formRules: FormRules = {
mobile: [
{
required: true,
message: '请输入手机号码',
trigger: ['change', 'blur']
},
{
min: 3,
max: 12,
message: '账号长度应为3-12',
trigger: ['change', 'blur']
}
],
code: [
{
required: true,
message: '请输入验证码',
trigger: ['change', 'blur']
}
],
password: [
{
required: true,
message: '请输入6-20位数字+字母或符号组合',
trigger: ['change', 'blur']
},
{
min: 6,
max: 20,
message: '密码长度应为6-20',
trigger: ['change', 'blur']
}
],
passwordConfirm: [
{
validator(rule: any, value: any, callback: any) {
if (value === '') {
callback(new Error('请再次输入密码'))
} else if (value !== formData.password) {
callback(new Error('两次输入的密码不一致'))
} else {
callback()
}
},
trigger: ['change', 'blur']
}
]
}
const formData = reactive({
mobile: '',
password: '',
code: '',
passwordConfirm: ''
})
const sendSms = async () => {
await formRef.value?.validateField(['mobile'])
await smsSend({
scene: SMSEnum.FIND_PASSWORD,
mobile: formData.mobile
})
verificationCodeRef.value?.start()
}
const handleConfirm = async () => {
await formRef.value?.validate()
await forgotPassword(formData)
feedback.msgSuccess('操作成功')
userStore.logout()
setPopupType(PopupTypeEnum.LOGIN)
}
const { lockFn: handleConfirmLock, isLock } = useLockFn(handleConfirm)
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,36 @@
<template>
<div class="account" v-if="showPopup">
<ClientOnly>
<ElDialog
v-model="showPopup"
:width="400"
:close-on-click-modal="false"
>
<div class="px-5 text-tx-primary">
<Login v-show="popupType == PopupTypeEnum.LOGIN" />
<Register v-show="popupType == PopupTypeEnum.REGISTER" />
<ForgotPwd v-show="popupType == PopupTypeEnum.FORGOT_PWD" />
<BindMobile
v-show="popupType == PopupTypeEnum.BIND_MOBILE"
/>
</div>
</ElDialog>
</ClientOnly>
</div>
</template>
<script lang="ts" setup>
import { ElDialog } from 'element-plus'
import Login from './login.vue'
import { useAccount, PopupTypeEnum } from './useAccount'
import Register from './register.vue'
import ForgotPwd from './forgot-pwd.vue'
import BindMobile from './bind-mobile.vue'
import { useUserStore } from '~~/stores/user'
const { popupType, showPopup } = useAccount()
const userStore = useUserStore()
watch(showPopup, (value) => {
if (!value) userStore.temToken = null
})
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,331 @@
<template>
<div class="login">
<div class="text-4xl">欢迎登录</div>
<ElForm
ref="formRef"
class="mt-[35px]"
size="large"
:model="formData"
:rules="formRules"
>
<template
v-if="isAccountLogin && includeLoginWay(LoginWayEnum.ACCOUNT)"
>
<ElFormItem prop="account">
<ElInput
v-model="formData.account"
placeholder="请输入账号/手机号"
/>
</ElFormItem>
<ElFormItem prop="password">
<ElInput
v-model="formData.password"
type="password"
show-password
placeholder="请输入密码"
/>
</ElFormItem>
</template>
<template
v-if="isMobileLogin && includeLoginWay(LoginWayEnum.MOBILE)"
>
<ElFormItem prop="account">
<ElInput
v-model="formData.account"
placeholder="请输入手机号"
/>
</ElFormItem>
<ElFormItem prop="code">
<ElInput v-model="formData.code" placeholder="请输入验证码">
<template #suffix>
<div
class="flex justify-center leading-5 w-[90px] pl-2.5 border-l border-br"
>
<VerificationCode
ref="verificationCodeRef"
@click-get="sendSms"
/>
</div>
</template>
</ElInput>
</ElFormItem>
</template>
<div class="flex">
<div class="flex-1">
<ElButton
v-if="
isAccountLogin &&
includeLoginWay(LoginWayEnum.MOBILE)
"
type="primary"
link
@click="changeLoginWay"
>
手机验证码登录
</ElButton>
<ElButton
v-if="
isMobileLogin &&
includeLoginWay(LoginWayEnum.ACCOUNT)
"
type="primary"
link
@click="changeLoginWay"
>
账号密码登录
</ElButton>
</div>
<ElButton
v-if="isAccountLogin"
link
@click="setPopupType(PopupTypeEnum.FORGOT_PWD)"
>
忘记密码?
</ElButton>
</div>
<ElFormItem class="mt-[30px]">
<ElButton
class="w-full"
type="primary"
:loading="isLock"
@click="loginLock"
>
登录
</ElButton>
</ElFormItem>
<div class="mt-[40px]" v-if="isOpenOtherAuth">
<ElDivider>
<span class="text-tx-secondary font-normal">
第三方登录
</span>
</ElDivider>
<div class="flex justify-center">
<ElButton link @click="getWxCodeLock" v-if="inWxAuth">
<img
class="w-[48px] h-[48px]"
src="@/assets/images/icon/icon_wx.png"
/>
</ElButton>
</div>
</div>
<div
class="mb-[-15px] mx-[-40px] mt-[30px] bg-primary-light-9 rounded-b-md px-[15px] flex leading-10"
>
<div class="flex-1">
<ElCheckbox v-if="isOpenAgreement" v-model="isAgreement">
<span class="text-tx-secondary text-sm">
已阅读并同意
<NuxtLink
:to="`/policy/${PolicyAgreementEnum.SERVICE}`"
custom
v-slot="{ href }"
>
<a
class="text-tx-primary"
:href="href"
target="_blank"
>
《服务协议》
</a>
</NuxtLink>
<NuxtLink
class="text-tx-primary"
:to="`/policy/${PolicyAgreementEnum.PRIVACY}`"
custom
v-slot="{ href }"
>
<a
class="text-tx-primary"
:href="href"
target="_blank"
>
《隐私政策》
</a>
</NuxtLink>
</span>
</ElCheckbox>
</div>
<div>
<ElButton
link
type="primary"
@click="setPopupType(PopupTypeEnum.REGISTER)"
>
<span class="text-sm">注册账号</span>
</ElButton>
</div>
</div>
</ElForm>
</div>
</template>
<script lang="ts" setup>
import {
ElForm,
ElFormItem,
ElInput,
ElButton,
ElDivider,
ElCheckbox,
FormInstance,
FormRules
} from 'element-plus'
import { useAccount, PopupTypeEnum } from './useAccount'
import { getWxCodeUrl, mobileLogin, accountLogin } from '@/api/account'
import { useAppStore } from '@/stores/app'
import { useUserStore } from '@/stores/user'
import { smsSend } from '~~/api/app'
import { PolicyAgreementEnum, SMSEnum } from '~~/enums/appEnums'
import feedback from '~~/utils/feedback'
const appStore = useAppStore()
const userStore = useUserStore()
const { setPopupType, toggleShowPopup } = useAccount()
enum LoginWayEnum {
ACCOUNT = 1,
MOBILE = 2
}
const isAgreement = ref(false)
const formRef = shallowRef<FormInstance>()
const formRules: FormRules = {
account: [
{
required: true,
validator(rule: any, value: any, callback: any) {
if (value === '') {
callback(
new Error(
formData.scene == LoginWayEnum.ACCOUNT
? '请输入账号/手机号'
: '请输入手机号'
)
)
return
}
callback()
},
trigger: ['change', 'blur']
}
],
password: [
{
required: true,
message: '请输入密码',
trigger: ['change', 'blur']
}
],
code: [
{
required: true,
message: '请输入验证码',
trigger: ['change', 'blur']
}
]
}
const formData = reactive({
code: '',
account: '',
password: '',
scene: 0
})
const isAccountLogin = computed(() => formData.scene == LoginWayEnum.ACCOUNT)
const isMobileLogin = computed(() => formData.scene == LoginWayEnum.MOBILE)
const includeLoginWay = (way: LoginWayEnum) =>
appStore.getLoginConfig.loginWay?.includes(way)
const inWxAuth = computed(() => {
return appStore.getLoginConfig.autoLoginAuth.includes(2)
})
const isOpenAgreement = computed(
() => appStore.getLoginConfig.openAgreement == 1
)
const isOpenOtherAuth = computed(
() => appStore.getLoginConfig.openOtherAuth == 1
)
const isForceBindMobile = computed(
() => appStore.getLoginConfig.forceBindMobile == 1
)
const changeLoginWay = () => {
if (formData.scene == LoginWayEnum.ACCOUNT) {
formData.scene = LoginWayEnum.MOBILE
} else {
formData.scene = LoginWayEnum.ACCOUNT
}
}
const verificationCodeRef = shallowRef()
const sendSms = async () => {
await formRef.value?.validateField(['account'])
await smsSend({
scene: SMSEnum.LOGIN,
mobile: formData.account
})
verificationCodeRef.value?.start()
}
const handleLogin = async () => {
await formRef.value?.validate()
const params: any = {}
if (isAccountLogin.value) {
params.username = formData.account
params.password = formData.password
}
if (isMobileLogin.value) {
params.mobile = formData.account
params.code = formData.code
}
let data
switch (formData.scene) {
case LoginWayEnum.ACCOUNT:
data = await accountLogin(params)
break
case LoginWayEnum.MOBILE:
data = await mobileLogin(params)
break
}
if (!data) return
if (isForceBindMobile.value && !data.isBindMobile) {
userStore.temToken = data.token
setPopupType(PopupTypeEnum.BIND_MOBILE)
return
}
userStore.login(data.token)
await userStore.getUser()
toggleShowPopup(false)
}
const { lockFn: handleLoginLock, isLock } = useLockFn(handleLogin)
const agreementConfirm = async () => {
if (isAgreement.value) {
return
}
await feedback.confirm('确认已阅读并同意《服务协议》和《隐私政策》')
isAgreement.value = true
}
const loginLock = async () => {
await agreementConfirm()
await handleLoginLock()
}
const getWxCode = async () => {
await agreementConfirm()
const { url } = await getWxCodeUrl()
window.location.href = url
}
const { lockFn: getWxCodeLock } = useLockFn(getWxCode)
watch(
() => appStore.getLoginConfig,
(value) => {
const { loginWay } = value
if (loginWay && loginWay.length) {
formData.scene = loginWay.at(0)
}
},
{
immediate: true
}
)
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,126 @@
<template>
<div class="login">
<div class="flex justify-between">
<span class="text-4xl">注册账号</span>
<ElButton
type="primary"
link
@click="setPopupType(PopupTypeEnum.LOGIN)"
>
返回登录
</ElButton>
</div>
<ElForm
ref="formRef"
class="mt-[35px]"
size="large"
:model="formData"
:rules="formRules"
>
<ElFormItem prop="username">
<ElInput
v-model="formData.username"
placeholder="请输入创建的账号"
/>
</ElFormItem>
<ElFormItem prop="password">
<ElInput
v-model="formData.password"
type="password"
show-password
placeholder="请输入6-20位数字+字母或符号组合"
/>
</ElFormItem>
<ElFormItem prop="passwordConfirm">
<ElInput
v-model="formData.passwordConfirm"
type="password"
show-password
placeholder="请再次输入密码"
/>
</ElFormItem>
<ElFormItem class="mt-[60px]">
<ElButton
class="w-full"
type="primary"
:loading="isLock"
@click="handleConfirmLock"
>
注册
</ElButton>
</ElFormItem>
</ElForm>
</div>
</template>
<script lang="ts" setup>
import {
ElForm,
ElFormItem,
ElInput,
ElButton,
FormInstance,
FormRules
} from 'element-plus'
import { register } from '~~/api/account'
import feedback from '~~/utils/feedback'
import { useAccount, PopupTypeEnum } from './useAccount'
const { setPopupType } = useAccount()
const formRef = shallowRef<FormInstance>()
const formRules: FormRules = {
username: [
{
required: true,
message: '请输入创建的账号',
trigger: ['change', 'blur']
},
{
min: 3,
max: 12,
message: '账号长度应为3-12',
trigger: ['change', 'blur']
}
],
password: [
{
required: true,
message: '请输入6-20位数字+字母或符号组合',
trigger: ['change', 'blur']
},
{
min: 6,
max: 20,
message: '密码长度应为6-20',
trigger: ['change', 'blur']
}
],
passwordConfirm: [
{
validator(rule: any, value: any, callback: any) {
if (value === '') {
callback(new Error('请再次输入密码'))
} else if (value !== formData.password) {
callback(new Error('两次输入的密码不一致'))
} else {
callback()
}
},
trigger: ['change', 'blur']
}
]
}
const formData = reactive({
username: '',
password: '',
passwordConfirm: ''
})
const handleConfirm = async () => {
await formRef.value?.validate()
await register(formData)
feedback.msgSuccess('注册成功')
setPopupType(PopupTypeEnum.LOGIN)
}
const { lockFn: handleConfirmLock, isLock } = useLockFn(handleConfirm)
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,17 @@
<template>
<div class="flex flex-col justify-center items-center">
<div class="text-tx-regular mb-4">您还未登录请先登录</div>
<ElButton @click="toLogin">登录</ElButton>
</div>
</template>
<script lang="ts" setup>
import { useAccount, PopupTypeEnum } from './useAccount'
import { ElButton } from 'element-plus'
const { setPopupType, toggleShowPopup } = useAccount()
const toLogin = () => {
setPopupType(PopupTypeEnum.LOGIN)
toggleShowPopup(true)
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,23 @@
export enum PopupTypeEnum {
LOGIN,
FORGOT_PWD,
REGISTER,
BIND_MOBILE
}
export const useAccount = () => {
const popupType = useState<PopupTypeEnum>(() => PopupTypeEnum.LOGIN)
const setPopupType = (type: PopupTypeEnum = PopupTypeEnum.LOGIN) => {
popupType.value = type
}
const showPopup = useState(() => false)
const toggleShowPopup = (toggle: boolean) => {
showPopup.value = toggle ?? !showPopup.value
}
return {
popupType,
setPopupType,
showPopup,
toggleShowPopup
}
}

View File

@ -0,0 +1,35 @@
<template>
<footer class="layout-footer text-center bg-[#222222] py-[30px]">
<div class="text-[#bebebe]">
<!-- <NuxtLink> 关于我们 </NuxtLink>
-->
<NuxtLink :to="`/policy/${PolicyAgreementEnum.SERVICE}`">
用户协议
</NuxtLink>
<NuxtLink :to="`/policy/${PolicyAgreementEnum.PRIVACY}`">
隐私政策
</NuxtLink>
<NuxtLink to="/user/info"> 会员中心 </NuxtLink>
</div>
<div class="mt-4 text-tx-secondary">
<a
class="mx-1 hover:underline"
:href="item.link"
target="_blank"
v-for="item in appStore.getCopyrightConfig"
:key="item.link"
>
{{ item.name }}
</a>
</div>
</footer>
</template>
<script lang="ts" setup>
import { useAppStore } from '@/stores/app'
import { PolicyAgreementEnum } from '@/enums/appEnums'
const appStore = useAppStore()
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,24 @@
<template>
<NuxtLink :to="appStore.getAdminUrl" target="_blank">
<ElMenuItem :index="menuItem.path">
<template #title>
<span>
{{ menuItem.name }}
</span>
</template>
</ElMenuItem>
</NuxtLink>
</template>
<script lang="ts" setup>
import { ElMenuItem } from 'element-plus'
import { useAppStore } from '~~/stores/app'
defineProps({
menuItem: {
type: Object,
default: () => ({})
}
})
const appStore = useAppStore()
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,44 @@
<template>
<header class="layout-header text-white bg-primary">
<div class="header-contain">
<Logo class="flex-none mr-4" />
<Navbar class="w-[600px]" />
<div class="flex-1"></div>
<Search class="mr-[40px] flex-none" />
<User class="flex-none" />
</div>
</header>
</template>
<script lang="ts" setup>
import User from './user.vue'
import Search from './search.vue'
import Logo from './logo.vue'
import Navbar from './navbar.vue'
</script>
<style lang="scss" scoped>
.layout-header {
height: var(--header-height);
border-bottom: 1px solid var(--el-border-color-extra-light);
position: sticky;
top: 0;
width: 100%;
z-index: 1999;
.header-contain {
height: 100%;
display: flex;
align-items: center;
max-width: 1200px;
margin: 0 auto;
.navbar {
--el-menu-item-font-size: var(--el-font-size-large);
--el-menu-bg-color: var(--el-color-primary);
--el-menu-active-color: var(--color-white);
--el-menu-text-color: var(--color-white);
--el-menu-item-hover-fill: var(--el-color-primary);
--el-menu-hover-text-color: var(--color-white);
--el-menu-hover-bg-color: var(--el-color-primary);
}
}
}
</style>

View File

@ -0,0 +1,47 @@
<template>
<ClientOnly>
<el-dropdown :max-height="200" :disabled="!hasData">
<span class="flex items-center text-white">
<MenuItem :menu-item="menuItem" :route-path="menuItem.path" />
<span class="ml-[-10px]" v-if="hasData">
<Icon name="el-icon-ArrowDown" />
</span>
</span>
<template #dropdown>
<el-dropdown-menu>
<NuxtLink
:to="{
path: '/information/search',
query: {
cid: item.id,
name: item.name
}
}"
v-for="item in data"
:key="item.id"
>
<el-dropdown-item> {{ item.name }} </el-dropdown-item>
</NuxtLink>
</el-dropdown-menu>
</template>
</el-dropdown>
</ClientOnly>
</template>
<script lang="ts" setup>
import { ElDropdown, ElDropdownItem, ElDropdownMenu } from 'element-plus'
import { getArticleCate } from '~~/api/news'
import MenuItem from '../menu/menu-item.vue'
defineProps({
menuItem: {
type: Object,
default: () => ({})
}
})
const { data } = await useAsyncData(() => getArticleCate())
const hasData = computed(() => {
return data.value && data.value.length
})
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,11 @@
<template>
<NuxtLink v-if="appStore.getWebsiteConfig.pcLogo" class="flex" to="/">
<img :src="appStore.getWebsiteConfig.pcLogo" class="h-[26px]" />
</NuxtLink>
</template>
<script lang="ts" setup>
import { useAppStore } from '~~/stores/app'
const appStore = useAppStore()
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,58 @@
<template>
<div>
<ElMenuItem :index="menuItem.path" @click="showMobilePopup = true">
<template #title>
<span>
{{ menuItem.name }}
</span>
</template>
</ElMenuItem>
<ClientOnly>
<ElDialog
v-model="showMobilePopup"
@close="showMobilePopup = false"
:width="700"
>
<div class="text-center text-tx-primary">
<div class="text-4xl font-medium">移动端演示</div>
<div class="flex my-[40px] justify-around">
<div v-if="oa">
<img :src="oa" class="w-[180px] h-[180px]" alt="" />
<div class="mt-2.5">微信公众号演示</div>
</div>
<div v-if="mnp">
<img
:src="mnp"
class="w-[180px] h-[180px]"
alt=""
/>
<div class="mt-2.5">微信小程序演示</div>
</div>
<div
v-if="!mnp && !oa"
class="w-[180px] h-[180px] flex items-center justify-center"
>
暂无演示
</div>
</div>
</div>
</ElDialog>
</ClientOnly>
</div>
</template>
<script lang="ts" setup>
import { ElMenuItem, ElDialog } from 'element-plus'
import { useAppStore } from '~~/stores/app'
defineProps({
menuItem: {
type: Object,
default: () => ({})
}
})
const appStore = useAppStore()
const mnp = computed(() => appStore.getQrcodeConfig.mnp)
const oa = computed(() => appStore.getQrcodeConfig.oa)
const showMobilePopup = ref(false)
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,55 @@
<template>
<nav>
<Menu
class="navbar"
:menu="menu"
:default-active="activeMenu"
mode="horizontal"
>
<template #item="{ item }">
<MenuItem
v-if="!item.component"
:menu-item="item"
:route-path="item.path"
/>
<div v-else>
<template v-if="item.component == 'information'">
<Information :menu-item="item" />
</template>
<template v-if="item.component == 'mobile'">
<Mobile :menu-item="item" />
</template>
</div>
</template>
</Menu>
</nav>
</template>
<script lang="ts" setup>
import Menu from '../menu/index.vue'
import MenuItem from '../menu/menu-item.vue'
import Admin from './admin.vue'
import Information from './information.vue'
import Mobile from './mobile.vue'
const route = useRoute()
const activeMenu = computed<string>(() => route.path)
const { menu } = useMenu()
</script>
<style lang="scss" scoped>
.navbar {
--el-menu-item-font-size: var(--el-font-size-large);
--el-menu-bg-color: var(--el-color-primary);
--el-menu-active-color: var(--color-white);
--el-menu-text-color: var(--color-white);
--el-menu-item-hover-fill: var(--el-color-primary);
--el-menu-hover-text-color: var(--color-white);
--el-menu-hover-bg-color: var(--el-color-primary);
:deep() {
& > .el-sub-menu {
.el-sub-menu__title:hover {
background-color: var(--el-menu-bg-color);
}
}
}
}
</style>

View File

@ -0,0 +1,50 @@
<template>
<div class="w-[250px] search">
<ElInput
v-model.trim="searchKeyword"
placeholder="请输入关键词"
:suffix-icon="Search"
@keyup.enter="handleToSearch"
/>
</div>
</template>
<script lang="ts" setup>
import { ElInput } from 'element-plus'
import { Search } from '@element-plus/icons-vue'
import feedback from '~~/utils/feedback'
const router = useRouter()
const route = useRoute()
const searchKeyword = ref()
const handleToSearch = () => {
if (!searchKeyword.value) return feedback.msgError('请输入关键词')
router.push({
path: '/information/search',
query: {
keywords: searchKeyword.value
}
})
}
watch(
route,
(routeNew) => {
if (routeNew.path == '/information/search') {
searchKeyword.value = routeNew.query.keywords
} else {
searchKeyword.value = ''
}
},
{
immediate: true
}
)
</script>
<style lang="scss" scoped>
.search {
:deep(.el-input) {
.el-input__wrapper {
border-radius: 16px;
}
}
}
</style>

View File

@ -0,0 +1,65 @@
<template>
<div>
<ElDropdown v-if="userStore.isLogin" @command="handleCommand">
<div class="flex items-center">
<ElAvatar :size="25" :src="userStore.userInfo.avatar" />
<div class="ml-1 text-white text-lg flex">
<span class="mr-2">个人中心</span>
<ElIcon><ArrowDown /></ElIcon>
</div>
</div>
<template #dropdown>
<ElDropdownMenu>
<NuxtLink to="/user/info">
<ElDropdownItem command="user">个人信息</ElDropdownItem>
</NuxtLink>
<NuxtLink to="/user/collection">
<ElDropdownItem command="collect">
我的收藏
</ElDropdownItem>
</NuxtLink>
<NuxtLink to="/account/security">
<ElDropdownItem command="account">
账号安全
</ElDropdownItem>
</NuxtLink>
<ElDropdownItem command="logout">退出登录</ElDropdownItem>
</ElDropdownMenu>
</template>
</ElDropdown>
<div v-else class="cursor-pointer text-lg" @click="handleToLogin">
登录/注册
</div>
</div>
</template>
<script lang="ts" setup>
import {
ElAvatar,
ElDropdown,
ElDropdownMenu,
ElDropdownItem,
ElIcon
} from 'element-plus'
import { ArrowDown } from '@element-plus/icons-vue'
import { useUserStore } from '@/stores/user'
import { PopupTypeEnum, useAccount } from '../account/useAccount'
import feedback from '~~/utils/feedback'
const { setPopupType, toggleShowPopup } = useAccount()
const userStore = useUserStore()
const handleToLogin = () => {
setPopupType(PopupTypeEnum.LOGIN)
toggleShowPopup(true)
}
const handleCommand = async (command: string) => {
switch (command) {
case 'logout':
await feedback.confirm('确定退出登录吗?')
userStore.logout()
}
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,30 @@
<template>
<main class="mx-auto w-[1200px] py-4">
<div
v-if="sidebar.length"
class="mr-4 bg-white rounded-[8px] overflow-hidden"
>
<Menu
:menu="sidebar"
:default-active="activeMenu"
mode="vertical"
/>
</div>
<div
:class="[
'layout-page flex-1 min-w-0 rounded-[8px]',
{
'bg-body': hasSidebar
}
]"
>
<slot />
</div>
</main>
</template>
<script lang="ts" setup>
import Menu from '../menu/index.vue'
const route = useRoute()
const activeMenu = computed<string>(() => route.meta.activeMenu ?? route.path)
const { sidebar, hasSidebar } = useMenu()
</script>

View File

@ -0,0 +1,42 @@
<template>
<ElMenu class="menu" v-bind="$props" :ellipsis="true">
<div v-for="item in menu" :key="item.path">
<slot name="item" :item="item">
<MenuItem :menu-item="item" :route-path="item.path" />
</slot>
</div>
</ElMenu>
</template>
<script lang="ts" setup>
import { ElMenu, menuProps } from 'element-plus'
import { PropType } from 'vue'
import MenuItem from './menu-item.vue'
defineProps({
menu: {
type: Array as PropType<any[]>,
default: () => []
},
...menuProps
})
</script>
<style lang="scss" scoped>
.menu {
&.el-menu--horizontal {
--el-menu-item-height: 40px;
border-bottom: none;
:deep(.el-menu-item) {
span {
border-bottom: 2px solid transparent;
}
&.is-active > span {
border-color: currentColor;
}
}
}
&.el-menu--vertical:not(.el-menu--collapse) {
width: 200px;
}
}
</style>

View File

@ -0,0 +1,65 @@
<template>
<template v-if="!menuItem?.hidden">
<NuxtLink
v-if="!hasShowChild"
:to="routePath"
class="flex items-center w-full"
:custom="menuItem.type == 'custom'"
:external="isExternal(routePath)"
:target="isExternal(routePath) ? '_blank' : ''"
>
<ElMenuItem class="w-full" :index="routePath">
<template #title>
<span>
{{ menuItem.name }}
</span>
</template>
</ElMenuItem>
</NuxtLink>
<ElSubMenu v-else :index="routePath" :popper-offset="12">
<template #title>
<!-- <Icon
v-if="menuItem.icon"
class="menu-item-icon"
:size="16"
:name="menuItem.icon"
/> -->
<span>{{ menuItem.name }}</span>
</template>
<MenuItem
v-for="item in menuItem.children"
:key="resolvePath(item.path)"
:menu-item="item"
:route-path="resolvePath(item.path)"
/>
</ElSubMenu>
</template>
</template>
<script lang="ts" setup>
import { ElMenuItem, ElSubMenu } from 'element-plus'
import { getNormalPath } from '@/utils/util'
import { isExternal } from '@/utils/validate'
const props = defineProps({
menuItem: {
type: Object,
default: () => ({})
},
routePath: {
type: String,
required: true
}
})
const hasShowChild = computed(() => {
const children = props.menuItem.children ?? []
return !!children.filter((item: any) => !item?.hidden).length
})
const resolvePath = (path: string) => {
if (isExternal(path)) {
return path
}
const newPath = getNormalPath(`${props.routePath}/${path}`)
return newPath
}
</script>
<style lang="scss" scoped></style>