web code format

This commit is contained in:
mizhexiaoxiao 2023-11-27 15:06:42 +08:00
parent 1f5bded672
commit a9fe6e4d5a
31 changed files with 2672 additions and 990 deletions

View File

@ -1,16 +1,20 @@
## 快速开始 ## 快速开始
进入前端目录 进入前端目录
```sh ```sh
cd web cd web
``` ```
安装依赖(建议使用pnpm: https://pnpm.io/zh/installation) 安装依赖(建议使用pnpm: https://pnpm.io/zh/installation)
```sh ```sh
npm i -g pnpm # 已安装可忽略 npm i -g pnpm # 已安装可忽略
pnpm i # 或者 npm i pnpm i # 或者 npm i
``` ```
启动 启动
```sh ```sh
pnpm dev pnpm dev
``` ```

View File

@ -27,7 +27,7 @@ export function createVitePlugins(viteEnv, isBuild) {
open: true, open: true,
gzipSize: true, gzipSize: true,
brotliSize: true, brotliSize: true,
}) }),
) )
} }

View File

@ -10,7 +10,7 @@ const i18n = createI18n({
globalInjection: true, globalInjection: true,
locale: currentLocale || 'en', locale: currentLocale || 'en',
fallbackLocale: 'en', fallbackLocale: 'en',
messages: messages messages: messages,
}) })
export default i18n export default i18n

View File

@ -34,16 +34,16 @@
"label_email": "邮箱", "label_email": "邮箱",
"label_old_password": "旧密码", "label_old_password": "旧密码",
"label_new_password": "新密码", "label_new_password": "新密码",
"label_confirm_password": "确认密码", "label_confirm_password": "确认密码",
"placeholder_username": "请填写姓名", "placeholder_username": "请填写姓名",
"placeholder_email": "请填写邮箱", "placeholder_email": "请填写邮箱",
"placeholder_old_password": "请输入旧密码", "placeholder_old_password": "请输入旧密码",
"placeholder_new_password": "请输入新密码", "placeholder_new_password": "请输入新密码",
"placeholder_confirm_password": "请再次输入新密码", "placeholder_confirm_password": "请再次输入新密码",
"message_username_required": "请输入昵称", "message_username_required": "请输入昵称",
"message_old_password_required": "请输入旧密码", "message_old_password_required": "请输入旧密码",
"message_new_password_required": "请输入新密码", "message_new_password_required": "请输入新密码",
"message_password_confirmation_required": "请再次输入密码", "message_password_confirmation_required": "请再次输入密码",
"message_password_confirmation_diff": "两次密码输入不一致" "message_password_confirmation_diff": "两次密码输入不一致"
}, },
"errors": { "errors": {
@ -59,4 +59,4 @@
"update": "修改" "update": "修改"
} }
} }
} }

View File

@ -43,7 +43,7 @@
"message_username_required": "Please enter username", "message_username_required": "Please enter username",
"message_old_password_required": "Please enter the old password", "message_old_password_required": "Please enter the old password",
"message_new_password_required": "Please enter a new password", "message_new_password_required": "Please enter a new password",
"message_password_confirmation_required": "Please enter confirm password", "message_password_confirmation_required": "Please enter confirm password",
"message_password_confirmation_diff": "Two password inputs are inconsistent" "message_password_confirmation_diff": "Two password inputs are inconsistent"
}, },
"errors": { "errors": {
@ -59,4 +59,4 @@
"update": "Update" "update": "Update"
} }
} }
} }

View File

@ -3,5 +3,5 @@ import * as cn from './cn.json'
export default { export default {
en, en,
cn cn,
} }

View File

@ -1,4 +1,4 @@
<!DOCTYPE html> <!doctype html>
<html lang="cn"> <html lang="cn">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />

View File

@ -8,7 +8,8 @@
"preview": "vite preview", "preview": "vite preview",
"lint": "eslint --ext .js,.vue .", "lint": "eslint --ext .js,.vue .",
"lint:fix": "eslint --fix --ext .js,.vue .", "lint:fix": "eslint --fix --ext .js,.vue .",
"lint:staged": "lint-staged" "lint:staged": "lint-staged",
"prettier": "npx prettier --write ."
}, },
"dependencies": { "dependencies": {
"@iconify/json": "^2.2.101", "@iconify/json": "^2.2.101",

3438
web/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,3 @@
<template> <template>
<footer f-c-c flex-col text-14 color="#6a6a6a"> <footer f-c-c flex-col text-14 color="#6a6a6a"></footer>
</footer>
</template> </template>

View File

@ -22,13 +22,13 @@
<script setup> <script setup>
import { defineComponent, h } from 'vue' import { defineComponent, h } from 'vue'
import { import {
zhCN, zhCN,
dateZhCN, dateZhCN,
darkTheme, darkTheme,
useLoadingBar, useLoadingBar,
useDialog, useDialog,
useMessage, useMessage,
useNotification, useNotification,
} from 'naive-ui' } from 'naive-ui'
import { useCssVar } from '@vueuse/core' import { useCssVar } from '@vueuse/core'
import { kebabCase } from 'lodash-es' import { kebabCase } from 'lodash-es'

View File

@ -53,7 +53,7 @@ const isEmpty = computed(() => props.empty && !props.loading && network.value)
const showPlaceholder = computed(() => props.loading || isEmpty.value || !network.value) const showPlaceholder = computed(() => props.loading || isEmpty.value || !network.value)
const networkErrorDesc = computed(() => const networkErrorDesc = computed(() =>
props.showNetworkReload ? `${NETWORK_ERROR_MSG}, 点击重试` : NETWORK_ERROR_MSG props.showNetworkReload ? `${NETWORK_ERROR_MSG}, 点击重试` : NETWORK_ERROR_MSG,
) )
function handleReload() { function handleReload() {
@ -71,7 +71,7 @@ const stopHandle = watch(
if (!newValue) { if (!newValue) {
network.value = window.navigator.onLine network.value = window.navigator.onLine
} }
} },
) )
onUnmounted(() => { onUnmounted(() => {

View File

@ -28,7 +28,7 @@ watchDebounced(
filterIcons() filterIcons()
emit('update:value', choosed.value) emit('update:value', choosed.value)
}, },
{ debounce: 200 } { debounce: 200 },
) )
</script> </script>

View File

@ -1,24 +1,24 @@
<template> <template>
<n-dropdown :options="options" @select="handleChangeLocale"> <n-dropdown :options="options" @select="handleChangeLocale">
<n-icon mr-20 size="18" style="cursor: pointer"> <n-icon mr-20 size="18" style="cursor: pointer">
<icon-mdi:globe/> <icon-mdi:globe />
</n-icon> </n-icon>
</n-dropdown> </n-dropdown>
</template> </template>
<script setup> <script setup>
import {useI18n} from 'vue-i18n' import { useI18n } from 'vue-i18n'
import {useAppStore} from "@/store"; import { useAppStore } from '@/store'
const store = useAppStore() const store = useAppStore()
const { availableLocales, t } = useI18n() const { availableLocales, t } = useI18n()
const options = computed(() => { const options = computed(() => {
let select = [] let select = []
availableLocales.forEach(locale => { availableLocales.forEach((locale) => {
select.push({ select.push({
label: t('lang', 1, {'locale': locale}), label: t('lang', 1, { locale: locale }),
key: locale key: locale,
}) })
}) })
return select return select

View File

@ -13,7 +13,7 @@ import { renderIcon } from '@/utils'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
const {t} = useI18n() const { t } = useI18n()
const router = useRouter() const router = useRouter()

View File

@ -19,5 +19,5 @@ import FullScreen from './components/FullScreen.vue'
import UserAvatar from './components/UserAvatar.vue' import UserAvatar from './components/UserAvatar.vue'
import GithubSite from './components/GithubSite.vue' import GithubSite from './components/GithubSite.vue'
import ThemeMode from './components/ThemeMode.vue' import ThemeMode from './components/ThemeMode.vue'
import Languages from './components/Languages.vue'; import Languages from './components/Languages.vue'
</script> </script>

View File

@ -48,7 +48,7 @@ watch(
const title = route.meta?.title const title = route.meta?.title
tagsStore.addTag({ name, path, title }) tagsStore.addTag({ name, path, title })
}, },
{ immediate: true } { immediate: true },
) )
watch( watch(
@ -60,7 +60,7 @@ watch(
const { offsetLeft: x, offsetWidth: width } = activeTabElement const { offsetLeft: x, offsetWidth: width } = activeTabElement
scrollXRef.value?.handleScroll(x + width, width) scrollXRef.value?.handleScroll(x + width, width)
}, },
{ immediate: true } { immediate: true },
) )
const handleTagClick = (path) => { const handleTagClick = (path) => {

View File

@ -1,5 +1,5 @@
import i18n from '~/i18n' import i18n from '~/i18n'
const {t} = i18n.global const { t } = i18n.global
const Layout = () => import('@/layout/index.vue') const Layout = () => import('@/layout/index.vue')

View File

@ -4,7 +4,7 @@ import { sStorage } from '@/utils'
import i18n from '~/i18n' import i18n from '~/i18n'
const currentLocale = sStorage.get('locale') const currentLocale = sStorage.get('locale')
const {locale} = i18n.global const { locale } = i18n.global
const isDark = useDark() const isDark = useDark()
export const useAppStore = defineStore('app', { export const useAppStore = defineStore('app', {
@ -16,7 +16,7 @@ export const useAppStore = defineStore('app', {
/** keepAlive路由的key重新赋值可重置keepAlive */ /** keepAlive路由的key重新赋值可重置keepAlive */
aliveKeys: {}, aliveKeys: {},
isDark, isDark,
locale: currentLocale || 'en' locale: currentLocale || 'en',
} }
}, },
actions: { actions: {
@ -55,6 +55,6 @@ export const useAppStore = defineStore('app', {
this.locale = newLocale this.locale = newLocale
locale.value = newLocale locale.value = newLocale
sStorage.set('locale', newLocale) sStorage.set('locale', newLocale)
} },
}, },
}) })

View File

@ -1,24 +1,22 @@
export function useResize(el, cb) { export function useResize(el, cb) {
const observer = new ResizeObserver((entries) => { const observer = new ResizeObserver((entries) => {
cb(entries[0].contentRect); cb(entries[0].contentRect)
}); })
observer.observe(el); observer.observe(el)
return observer; return observer
} }
const install = (app) => { const install = (app) => {
let observer; let observer
app.directive('resize', { app.directive('resize', {
mounted(el, binding) { mounted(el, binding) {
observer = useResize(el, binding.value); observer = useResize(el, binding.value)
}, },
beforeUnmount() { beforeUnmount() {
observer?.disconnect(); observer?.disconnect()
}, },
}); })
}; }
useResize.install = install; useResize.install = install

View File

@ -5,7 +5,9 @@
<icon-custom-unauthorized text-400px text-primary></icon-custom-unauthorized> <icon-custom-unauthorized text-400px text-primary></icon-custom-unauthorized>
</template> </template>
<template #footer> <template #footer>
<n-button type="primary" @click="replace('/')">{{ $t('views.errors.text_back_to_home') }}</n-button> <n-button type="primary" @click="replace('/')">{{
$t('views.errors.text_back_to_home')
}}</n-button>
</template> </template>
</n-result> </n-result>
</AppPage> </AppPage>

View File

@ -5,7 +5,9 @@
<icon-custom-forbidden text-400px text-primary></icon-custom-forbidden> <icon-custom-forbidden text-400px text-primary></icon-custom-forbidden>
</template> </template>
<template #footer> <template #footer>
<n-button type="primary" @click="replace('/')">{{ $t('views.errors.text_back_to_home') }}</n-button> <n-button type="primary" @click="replace('/')">{{
$t('views.errors.text_back_to_home')
}}</n-button>
</template> </template>
</n-result> </n-result>
</AppPage> </AppPage>

View File

@ -5,7 +5,9 @@
<icon-custom-not-found text-400px text-primary></icon-custom-not-found> <icon-custom-not-found text-400px text-primary></icon-custom-not-found>
</template> </template>
<template #footer> <template #footer>
<n-button type="primary" @click="replace('/')">{{ $t('views.errors.text_back_to_home') }}</n-button> <n-button type="primary" @click="replace('/')">{{
$t('views.errors.text_back_to_home')
}}</n-button>
</template> </template>
</n-result> </n-result>
</AppPage> </AppPage>

View File

@ -5,7 +5,9 @@
<icon-custom-server-error text-400px text-primary></icon-custom-server-error> <icon-custom-server-error text-400px text-primary></icon-custom-server-error>
</template> </template>
<template #footer> <template #footer>
<n-button type="primary" @click="replace('/')">{{ $t('views.errors.text_back_to_home') }}</n-button> <n-button type="primary" @click="replace('/')">{{
$t('views.errors.text_back_to_home')
}}</n-button>
</template> </template>
</n-result> </n-result>
</AppPage> </AppPage>

View File

@ -57,11 +57,11 @@ import { lStorage, setToken } from '@/utils'
import bgImg from '@/assets/images/login_bg.webp' import bgImg from '@/assets/images/login_bg.webp'
import api from '@/api' import api from '@/api'
import { addDynamicRoutes } from '@/router' import { addDynamicRoutes } from '@/router'
import {useI18n} from 'vue-i18n' import { useI18n } from 'vue-i18n'
const router = useRouter() const router = useRouter()
const { query } = useRoute() const { query } = useRoute()
const {t} = useI18n({ useScope: "global" }) const { t } = useI18n({ useScope: 'global' })
const loginInfo = ref({ const loginInfo = ref({
username: '', username: '',

View File

@ -1,13 +1,13 @@
<script setup> <script setup>
import { ref } from 'vue' import { ref } from 'vue'
import { NButton, NForm, NFormItem, NInput, NTabPane, NTabs, NImage } from 'naive-ui' import { NButton, NForm, NFormItem, NInput, NTabPane, NTabs, NImage } from 'naive-ui'
import {useI18n} from "vue-i18n"; import { useI18n } from 'vue-i18n'
import CommonPage from '@/components/page/CommonPage.vue' import CommonPage from '@/components/page/CommonPage.vue'
import { useUserStore } from '@/store' import { useUserStore } from '@/store'
import api from '@/api' import api from '@/api'
import { is } from '~/src/utils' import { is } from '~/src/utils'
const {t} = useI18n() const { t } = useI18n()
const userStore = useUserStore() const userStore = useUserStore()
const isLoading = ref(false) const isLoading = ref(false)
@ -137,12 +137,22 @@ function validatePasswordSame(rule, value) {
<NImage width="100" :src="infoForm.avatar"></NImage> <NImage width="100" :src="infoForm.avatar"></NImage>
</NFormItem> </NFormItem>
<NFormItem :label="$t('views.profile.label_username')" path="username"> <NFormItem :label="$t('views.profile.label_username')" path="username">
<NInput v-model:value="infoForm.username" type="text" :placeholder="$t('views.profile.placeholder_username')" /> <NInput
v-model:value="infoForm.username"
type="text"
:placeholder="$t('views.profile.placeholder_username')"
/>
</NFormItem> </NFormItem>
<NFormItem :label="$t('views.profile.label_email')" path="email"> <NFormItem :label="$t('views.profile.label_email')" path="email">
<NInput v-model:value="infoForm.email" type="text" :placeholder="$t('views.profile.placeholder_email')" /> <NInput
v-model:value="infoForm.email"
type="text"
:placeholder="$t('views.profile.placeholder_email')"
/>
</NFormItem> </NFormItem>
<NButton type="primary" :loading="isLoading" @click="updateProfile"> {{$t("common.buttons.update")}} </NButton> <NButton type="primary" :loading="isLoading" @click="updateProfile">
{{ $t('common.buttons.update') }}
</NButton>
</NForm> </NForm>
</div> </div>
</NTabPane> </NTabPane>
@ -182,7 +192,9 @@ function validatePasswordSame(rule, value) {
:placeholder="$t('views.profile.placeholder_confirm_password')" :placeholder="$t('views.profile.placeholder_confirm_password')"
/> />
</NFormItem> </NFormItem>
<NButton type="primary" :loading="isLoading" @click="updatePassword"> {{$t("common.buttons.update")}} </NButton> <NButton type="primary" :loading="isLoading" @click="updatePassword">
{{ $t('common.buttons.update') }}
</NButton>
</NForm> </NForm>
</NTabPane> </NTabPane>
</NTabs> </NTabs>

View File

@ -141,9 +141,9 @@ const columns = [
{ {
default: () => '编辑', default: () => '编辑',
icon: renderIcon('material-symbols:edit', { size: 16 }), icon: renderIcon('material-symbols:edit', { size: 16 }),
} },
), ),
[[vPermission, 'post/api/v1/api/update']] [[vPermission, 'post/api/v1/api/update']],
), ),
h( h(
NPopconfirm, NPopconfirm,
@ -163,12 +163,12 @@ const columns = [
{ {
default: () => '删除', default: () => '删除',
icon: renderIcon('material-symbols:delete-outline', { size: 16 }), icon: renderIcon('material-symbols:delete-outline', { size: 16 }),
} },
), ),
[[vPermission, 'delete/api/v1/api/delete']] [[vPermission, 'delete/api/v1/api/delete']],
), ),
default: () => h('div', {}, '确定删除该API吗?'), default: () => h('div', {}, '确定删除该API吗?'),
} },
), ),
] ]
}, },

View File

@ -135,9 +135,9 @@ const columns = [
handleAdd() handleAdd()
}, },
}, },
{ default: () => '子菜单', icon: renderIcon('material-symbols:add', { size: 16 }) } { default: () => '子菜单', icon: renderIcon('material-symbols:add', { size: 16 }) },
), ),
[[vPermission, 'post/api/v1/menu/create']] [[vPermission, 'post/api/v1/menu/create']],
), ),
withDirectives( withDirectives(
h( h(
@ -154,9 +154,9 @@ const columns = [
{ {
default: () => '编辑', default: () => '编辑',
icon: renderIcon('material-symbols:edit-outline', { size: 16 }), icon: renderIcon('material-symbols:edit-outline', { size: 16 }),
} },
), ),
[[vPermission, 'post/api/v1/menu/update']] [[vPermission, 'post/api/v1/menu/update']],
), ),
h( h(
NPopconfirm, NPopconfirm,
@ -177,12 +177,12 @@ const columns = [
{ {
default: () => '删除', default: () => '删除',
icon: renderIcon('material-symbols:delete-outline', { size: 16 }), icon: renderIcon('material-symbols:delete-outline', { size: 16 }),
} },
), ),
[[vPermission, 'delete/api/v1/menu/delete']] [[vPermission, 'delete/api/v1/menu/delete']],
), ),
default: () => h('div', {}, '确定删除该菜单吗?'), default: () => h('div', {}, '确定删除该菜单吗?'),
} },
), ),
] ]
}, },

View File

@ -144,9 +144,9 @@ const columns = [
{ {
default: () => '编辑', default: () => '编辑',
icon: renderIcon('material-symbols:edit-outline', { size: 16 }), icon: renderIcon('material-symbols:edit-outline', { size: 16 }),
} },
), ),
[[vPermission, 'post/api/v1/role/update']] [[vPermission, 'post/api/v1/role/update']],
), ),
h( h(
NPopconfirm, NPopconfirm,
@ -167,12 +167,12 @@ const columns = [
{ {
default: () => '删除', default: () => '删除',
icon: renderIcon('material-symbols:delete-outline', { size: 16 }), icon: renderIcon('material-symbols:delete-outline', { size: 16 }),
} },
), ),
[[vPermission, 'delete/api/v1/role/delete']] [[vPermission, 'delete/api/v1/role/delete']],
), ),
default: () => h('div', {}, '确定删除该角色吗?'), default: () => h('div', {}, '确定删除该角色吗?'),
} },
), ),
withDirectives( withDirectives(
h( h(
@ -195,9 +195,9 @@ const columns = [
{ {
default: () => '设置权限', default: () => '设置权限',
icon: renderIcon('material-symbols:edit-outline', { size: 16 }), icon: renderIcon('material-symbols:edit-outline', { size: 16 }),
} },
), ),
[[vPermission, 'get/api/v1/role/authorized']] [[vPermission, 'get/api/v1/role/authorized']],
), ),
] ]
}, },

View File

@ -99,7 +99,7 @@ const columns = [
const group = [] const group = []
for (let i = 0; i < roles.length; i++) for (let i = 0; i < roles.length; i++)
group.push( group.push(
h(NTag, { type: 'info', style: { margin: '2px 3px' } }, { default: () => roles[i].name }) h(NTag, { type: 'info', style: { margin: '2px 3px' } }, { default: () => roles[i].name }),
) )
return h('span', group) return h('span', group)
}, },
@ -113,7 +113,7 @@ const columns = [
return h( return h(
NTag, NTag,
{ type: 'info', style: { margin: '2px 3px' } }, { type: 'info', style: { margin: '2px 3px' } },
{ default: () => (row.is_superuser ? '是' : '否') } { default: () => (row.is_superuser ? '是' : '否') },
) )
}, },
}, },
@ -130,7 +130,7 @@ const columns = [
{ {
default: () => (row.last_login !== null ? formatDate(row.last_login) : null), default: () => (row.last_login !== null ? formatDate(row.last_login) : null),
icon: renderIcon('mdi:update', { size: 16 }), icon: renderIcon('mdi:update', { size: 16 }),
} },
) )
}, },
}, },
@ -175,9 +175,9 @@ const columns = [
{ {
default: () => '编辑', default: () => '编辑',
icon: renderIcon('material-symbols:edit', { size: 16 }), icon: renderIcon('material-symbols:edit', { size: 16 }),
} },
), ),
[[vPermission, 'post/api/v1/user/update']] [[vPermission, 'post/api/v1/user/update']],
), ),
h( h(
NPopconfirm, NPopconfirm,
@ -197,12 +197,12 @@ const columns = [
{ {
default: () => '删除', default: () => '删除',
icon: renderIcon('material-symbols:delete-outline', { size: 16 }), icon: renderIcon('material-symbols:delete-outline', { size: 16 }),
} },
), ),
[[vPermission, 'delete/api/v1/user/delete']] [[vPermission, 'delete/api/v1/user/delete']],
), ),
default: () => h('div', {}, '确定删除该用户吗?'), default: () => h('div', {}, '确定删除该用户吗?'),
} },
), ),
] ]
}, },

View File

@ -6,7 +6,9 @@
<div flex items-center> <div flex items-center>
<img rounded-full width="60" :src="userStore.avatar" /> <img rounded-full width="60" :src="userStore.avatar" />
<div ml-10> <div ml-10>
<p text-20 font-semibold> {{ $t('views.workbench.text_hello', {username: userStore.name}) }}</p> <p text-20 font-semibold>
{{ $t('views.workbench.text_hello', { username: userStore.name }) }}
</p>
<p mt-5 text-14 op-60>{{ $t('views.workbench.text_welcome') }}</p> <p mt-5 text-14 op-60>{{ $t('views.workbench.text_welcome') }}</p>
</div> </div>
</div> </div>
@ -16,9 +18,15 @@
</div> </div>
</n-card> </n-card>
<n-card :title="$t('views.workbench.label_project')" size="small" :segmented="true" mt-15 rounded-10> <n-card
:title="$t('views.workbench.label_project')"
size="small"
:segmented="true"
mt-15
rounded-10
>
<template #header-extra> <template #header-extra>
<n-button text type="primary">{{$t('views.workbench.label_more')}}</n-button> <n-button text type="primary">{{ $t('views.workbench.label_more') }}</n-button>
</template> </template>
<div flex flex-wrap justify-between> <div flex flex-wrap justify-between>
<n-card <n-card
@ -29,7 +37,7 @@
title="Vue FastAPI Admin" title="Vue FastAPI Admin"
size="small" size="small"
> >
<p op-60>{{dummyText}}</p> <p op-60>{{ dummyText }}</p>
</n-card> </n-card>
</div> </div>
</n-card> </n-card>
@ -41,8 +49,8 @@
import { useUserStore } from '@/store' import { useUserStore } from '@/store'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
const dummyText = "一个基于 Vue3.0、FastAPI、Naive UI 的轻量级后台管理模板" const dummyText = '一个基于 Vue3.0、FastAPI、Naive UI 的轻量级后台管理模板'
const {t} = useI18n({ useScope: "global" }) const { t } = useI18n({ useScope: 'global' })
const statisticData = computed(() => [ const statisticData = computed(() => [
{ {