├── .eslintignore ├── src ├── layouts │ ├── NotFound.vue │ ├── Empty.vue │ └── Home.vue ├── settings.json ├── components │ ├── MdEditor │ │ ├── style.css │ │ └── index.tsx │ ├── DataTable │ │ ├── components │ │ │ ├── index.ts │ │ │ └── ColumnSetting.vue │ │ └── info.ts │ ├── Layout │ │ ├── Header │ │ │ ├── Tips.vue │ │ │ ├── Network.vue │ │ │ ├── Translate.vue │ │ │ ├── Fullscreen.vue │ │ │ ├── Message.vue │ │ │ ├── Dark.vue │ │ │ ├── Tabs.vue │ │ │ └── Window.vue │ │ ├── LoadingContent.vue │ │ ├── Loading.tsx │ │ ├── MultiWindow │ │ │ ├── index.ts │ │ │ ├── index.vue │ │ │ └── setupMultiWindow.ts │ │ ├── Provider.vue │ │ └── Header.vue │ ├── Drawer │ │ ├── utils.ts │ │ └── index.vue │ ├── DataForm │ │ ├── values.ts │ │ └── utils.ts │ ├── VChart │ │ ├── example │ │ │ ├── line-data.ts │ │ │ ├── bar-data.ts │ │ │ ├── pie-data.ts │ │ │ └── pie-rose-data.ts │ │ └── index.tsx │ ├── CardCols.vue │ ├── CardRows.vue │ ├── Card.tsx │ └── DrawerForm │ │ └── index.vue ├── utils │ ├── is.ts │ ├── render.ts │ ├── lock.ts │ └── utils.ts ├── composables │ ├── useDataForm.ts │ ├── useDataTable.ts │ ├── useDarks.ts │ ├── useLanguage.ts │ ├── useWindowFullscreen.ts │ ├── usePrefixStorage.ts │ └── useHello.ts ├── stores │ ├── multiWindowStore.ts │ ├── settingStore.ts │ ├── stateStore.ts │ ├── themeStore.ts │ └── userStore.ts ├── menu.ts ├── apis │ ├── sh.ts │ ├── test.ts │ ├── system.ts │ ├── base.ts │ └── index.ts ├── modules │ ├── pinia.ts │ ├── other.ts │ ├── handler.ts │ ├── index.ts │ ├── i18n.ts │ ├── directives.ts │ └── router.ts ├── main.ts ├── pages │ ├── example │ │ ├── function │ │ │ ├── permission │ │ │ │ ├── user.vue │ │ │ │ ├── admin.vue │ │ │ │ └── index.vue │ │ │ ├── not-keep.vue │ │ │ ├── fullscreen.vue │ │ │ ├── markdown.vue │ │ │ ├── locale.vue │ │ │ ├── clipboard.vue │ │ │ ├── echart │ │ │ │ └── index.vue │ │ │ └── notice.vue │ │ ├── layout │ │ │ ├── basic.vue │ │ │ ├── table-form │ │ │ │ ├── form.ts │ │ │ │ ├── [id].vue │ │ │ │ ├── f1.vue │ │ │ │ ├── f3.vue │ │ │ │ ├── f2.vue │ │ │ │ └── table.ts │ │ │ ├── table.vue │ │ │ ├── form-drawer.vue │ │ │ ├── multi-column.vue │ │ │ ├── form-basic.vue │ │ │ └── form-advanced.vue │ │ ├── readme.vue │ │ └── menu.ts │ ├── inlay │ │ ├── loading.vue │ │ ├── detail.vue │ │ └── message.vue │ ├── system │ │ ├── menu.ts │ │ ├── site.vue │ │ ├── menu.vue │ │ └── role.vue │ └── [...notFound].vue ├── styles │ ├── base.css │ └── main.css ├── mockProdServer.ts ├── types │ ├── env.d.ts │ ├── global.d.ts │ └── components.d.ts └── App.vue ├── .devcontainer.json ├── .husky └── pre-commit ├── mock ├── utils.ts ├── user.ts └── base.ts ├── public ├── logo.png ├── favicon.ico └── browser.js ├── .npmrc ├── scripts ├── template │ ├── layout.hbs │ ├── composable.hbs │ ├── module.hbs │ ├── api.hbs │ ├── component.hbs │ ├── store.hbs │ └── page.hbs ├── shared │ └── base.js ├── create.js └── remove.js ├── .gitignore ├── presets ├── css │ ├── index.ts │ └── postcss.ts ├── plugins │ ├── removelog.ts │ ├── build.ts │ ├── eslint.ts │ ├── dev.ts │ ├── mock.ts │ ├── legacy.ts │ ├── markdown.ts │ ├── pwa.ts │ ├── html.ts │ └── h5.ts ├── server.ts ├── optimize.ts ├── shared │ ├── resolvers.ts │ ├── mock.ts │ └── env.ts └── build.ts ├── locales ├── zh.yml └── en.yml ├── .editorConfig ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── template.code-workspace ├── .eslintrc ├── .env ├── unocss.config.ts ├── tsconfig.json ├── LICENSE ├── README.md ├── vite.config.ts └── package.json /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | src/types 4 | -------------------------------------------------------------------------------- /src/layouts/NotFound.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.devcontainer.json: -------------------------------------------------------------------------------- 1 | {"image":"mcr.microsoft.com/devcontainers/javascript-node","build":{}} -------------------------------------------------------------------------------- /src/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "layout": {}, 3 | "page": {}, 4 | "menu": {} 5 | } 6 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /mock/utils.ts: -------------------------------------------------------------------------------- 1 | export const delay = ms => new Promise(resolve => setTimeout(resolve, ms)) 2 | -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sohaha/naiveui-admin-template/HEAD/public/logo.png -------------------------------------------------------------------------------- /src/components/MdEditor/style.css: -------------------------------------------------------------------------------- 1 | .md-toolbar-wrapper .md-toolbar{ 2 | min-width:auto; 3 | } -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | public-hoist-pattern[]=@vue* 2 | auto-install-peers=true 3 | strict-peer-dependencies=false 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sohaha/naiveui-admin-template/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/utils/is.ts: -------------------------------------------------------------------------------- 1 | export function isURL(path: string) { 2 | return /^(http(s)?:\/\/)/.test(path) 3 | } 4 | -------------------------------------------------------------------------------- /scripts/template/layout.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{pascalCase name}} layout 3 | 4 | 5 | -------------------------------------------------------------------------------- /scripts/template/composable.hbs: -------------------------------------------------------------------------------- 1 | import { ref } from "vue" 2 | 3 | export default {{name}} = () => { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /scripts/template/module.hbs: -------------------------------------------------------------------------------- 1 | import type { App } from "vue" 2 | 3 | 4 | export default (app: App) => { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /src/layouts/Empty.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/composables/useDataForm.ts: -------------------------------------------------------------------------------- 1 | import { useDataForm } from '@/components/DataForm/utils' 2 | 3 | export default useDataForm 4 | -------------------------------------------------------------------------------- /src/composables/useDataTable.ts: -------------------------------------------------------------------------------- 1 | import { useDataTable } from '@/components/DataTable/utils' 2 | 3 | export default useDataTable 4 | -------------------------------------------------------------------------------- /src/components/DataTable/components/index.ts: -------------------------------------------------------------------------------- 1 | import ColumnSetting from './ColumnSetting.vue' 2 | 3 | export default ColumnSetting 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .eslintcache 4 | .history/ 5 | .idea/ 6 | .env.*.local 7 | .DS_Store 8 | .pnpm-store/ 9 | stats.html 10 | -------------------------------------------------------------------------------- /presets/css/index.ts: -------------------------------------------------------------------------------- 1 | import { PostcssConfig } from './postcss' 2 | 3 | export default () => { 4 | return { 5 | postcss: PostcssConfig(), 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /scripts/template/api.hbs: -------------------------------------------------------------------------------- 1 | import type { InstApi } from '@/types/global' 2 | 3 | export function api{{pascalCase name}}(): Promise { 4 | return api.get('/xxx', { }) 5 | } 6 | -------------------------------------------------------------------------------- /scripts/template/component.hbs: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | {{name}} 7 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /locales/zh.yml: -------------------------------------------------------------------------------- 1 | button: 2 | about: 关于 3 | back: 返回 4 | loading_page: 页面加载中 5 | not_found: 未找到页面 6 | operation_tip: "操作:" 7 | page_home: 首页 8 | page_login: 登录 9 | page_table: 表格 10 | -------------------------------------------------------------------------------- /scripts/template/store.hbs: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | 3 | export default defineStore('{{name}}', { 4 | state() { 5 | return {} 6 | }, 7 | getters: {}, 8 | actions: {} 9 | }) 10 | -------------------------------------------------------------------------------- /locales/en.yml: -------------------------------------------------------------------------------- 1 | button: 2 | about: About 3 | back: Back 4 | loading_page: loading 5 | not_found: Notfound 6 | operation_tip: "Action:" 7 | page_home: Home 8 | page_login: Login 9 | page_table: Table 10 | -------------------------------------------------------------------------------- /src/stores/multiWindowStore.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | import useStore from '@/components/Layout/MultiWindow/store' 3 | 4 | export const multiWindowStore = defineStore('multiWindowStore', useStore) 5 | -------------------------------------------------------------------------------- /src/composables/useDarks.ts: -------------------------------------------------------------------------------- 1 | const isDark = useDark() 2 | const toggle = useToggle(isDark) 3 | 4 | const toggleDark = () => { 5 | toggle() 6 | } 7 | 8 | export default () => { 9 | return { isDark, toggleDark } 10 | } 11 | -------------------------------------------------------------------------------- /src/menu.ts: -------------------------------------------------------------------------------- 1 | import type { StMenu } from './types/global' 2 | // import system from '@/pages/system/menu' 3 | 4 | export default [ 5 | { 6 | icon: 'i-bx:home', 7 | path: '/', 8 | }, 9 | // ...system, 10 | ] 11 | -------------------------------------------------------------------------------- /src/apis/sh.ts: -------------------------------------------------------------------------------- 1 | import type { InstApi } from '@/types/global' 2 | 3 | export function yclh(params: { 4 | page: number 5 | pagesize: number 6 | }): Promise { 7 | return apis.get('/0xsys/yc60lh', { 8 | params, 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /presets/plugins/removelog.ts: -------------------------------------------------------------------------------- 1 | import Removelog from 'vite-plugin-removelog' 2 | import { env } from '../shared/env' 3 | 4 | export function RemovelogPlugin() { 5 | return (env.IS_PROD && !env.VITE_APP_MOCK_IN_PRODUCTION) 6 | ? Removelog() 7 | : '' 8 | } 9 | -------------------------------------------------------------------------------- /src/modules/pinia.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue' 2 | import persistedstate from 'pinia-plugin-persistedstate' 3 | 4 | export default (app: App) => { 5 | const pinia = createPinia() 6 | 7 | pinia.use(persistedstate) 8 | 9 | app.use(pinia) 10 | } 11 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import 'uno.css' 2 | import './styles/main.css' 3 | import * as Vue from 'vue' 4 | import App from './App.vue' 5 | import modules from './modules' 6 | 7 | window.Vue = Vue 8 | 9 | const app = createApp(App) 10 | modules(app) 11 | app.mount('#app') 12 | -------------------------------------------------------------------------------- /presets/plugins/build.ts: -------------------------------------------------------------------------------- 1 | import { buildPlugin } from 'vite-plugin-build' 2 | 3 | // https://github.com/samonxian/vite-plugin-build/blob/master/README.zh-CN.md 4 | export function BuildPlugin() { 5 | return buildPlugin({ 6 | // fileBuild: { emitDeclaration: true } 7 | }) 8 | } 9 | -------------------------------------------------------------------------------- /.editorConfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | insert_final_newline = false 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /presets/plugins/eslint.ts: -------------------------------------------------------------------------------- 1 | import eslintPlugin from 'vite-plugin-eslint' 2 | 3 | export function ESLintPlugin() { 4 | return eslintPlugin({ 5 | fix: true, 6 | cache: true, 7 | failOnError: false, 8 | include: ['src/**/*.js', 'src/**/*.vue', 'src/**/*.ts'], 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /src/components/DataTable/info.ts: -------------------------------------------------------------------------------- 1 | export function useInfoDrawer() { 2 | const infoID = ref(0) 3 | const show = ref(false) 4 | 5 | return { 6 | open(id: number | string) { 7 | infoID.value = id 8 | show.value = true 9 | }, 10 | id: infoID, 11 | show, 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /scripts/template/page.hbs: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | {{pascalCase name}} page 7 | 8 | 9 | 12 | 13 | 14 | { 15 | "meta": { 16 | "title": "{{pascalCase name}}" 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /src/modules/other.ts: -------------------------------------------------------------------------------- 1 | import ayyui from 'ayyui' 2 | import 'ayyui/dist/style.css' 3 | import type { App } from 'vue' 4 | import { Level } from '@sohaha/zlog' 5 | 6 | export default (app: App) => { 7 | if (import.meta.env.DEV) 8 | log.level = Level.Trace 9 | else 10 | log.level = Level.Warn 11 | 12 | app.use(ayyui as any) 13 | } 14 | -------------------------------------------------------------------------------- /src/pages/example/function/permission/user.vue: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 当前页面是 user 权限的用户才可见 7 | 8 | 9 | 10 | 13 | 14 | 15 | { 16 | "meta": { 17 | "permission": ["user"], 18 | "title": "user权限" 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /src/pages/example/function/permission/admin.vue: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 当前页面是 admin 权限的用户才可见 7 | 8 | 9 | 10 | 13 | 14 | 15 | { 16 | "meta": { 17 | "permission": ["admin"], 18 | "title": "admin权限" 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /src/modules/handler.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue' 2 | 3 | export default (_: App) => { 4 | // app.config.errorHandler = (err, vm, info) => { 5 | // console.error('errorHandler', err, vm, info) 6 | // } 7 | // app.config.warnHandler = (err, vm, info) => { 8 | // console.error('warnHandler', err, vm, info) 9 | // } 10 | // app.config.performance = true 11 | } 12 | -------------------------------------------------------------------------------- /src/pages/inlay/loading.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | { 16 | "meta": { 17 | "multiWindow": false, 18 | "title": "Loading" 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /src/styles/base.css: -------------------------------------------------------------------------------- 1 | body { 2 | overflow: hidden; 3 | background-color: var(--a-bg-color); 4 | } 5 | 6 | .tip-text{ 7 | color: hsla(var(--a-base-color),var(--a-text-emphasis-light-opacity)); 8 | } 9 | 10 | i[class^='mi-'] { 11 | background-size: 100% 100% !important; 12 | } 13 | 14 | .n-icon { 15 | will-change: transform; 16 | } 17 | 18 | vite-error-overlay { 19 | z-index: 999999; 20 | } 21 | -------------------------------------------------------------------------------- /src/modules/index.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue' 2 | import directives from './directives' 3 | import handler from './handler' 4 | import i18n from './i18n' 5 | import pinia from './pinia' 6 | import router from './router' 7 | import other from './other' 8 | 9 | export default (app: App) => { 10 | pinia(app) 11 | router(app) 12 | handler(app) 13 | directives(app) 14 | i18n(app) 15 | other(app) 16 | } 17 | -------------------------------------------------------------------------------- /src/components/Layout/Header/Tips.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "antfu.iconify", 4 | "mikestead.dotenv", 5 | "redhat.vscode-yaml", 6 | "lokalise.i18n-ally", 7 | "vue.volar", 8 | "steoates.autoimport", 9 | "esbenp.prettier-vscode", 10 | "dbaeumer.vscode-eslint", 11 | "editorconfig.editorconfig", 12 | "usernamehw.errorlens", 13 | "antfu.goto-alias", 14 | "Vue.vscode-typescript-vue-plugin" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /presets/plugins/dev.ts: -------------------------------------------------------------------------------- 1 | import { env } from '../shared/env' 2 | import Inspect from 'vite-plugin-inspect' 3 | import VueDevTools from "vite-plugin-vue-devtools"; 4 | 5 | export function DevPlugins() { 6 | return [ 7 | Inspect({ 8 | dev: env.VITE_DEV_INSPECT, 9 | enabled: !env.IS_PROD && env.VITE_DEV_INSPECT, 10 | }), 11 | // https://github.com/webfansplz/vite-plugin-vue-devtools 12 | !env.IS_PROD && VueDevTools(), 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /template.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": ".", 5 | "name": "Root" 6 | }, 7 | { 8 | "name": "Apis", 9 | "path": "src/apis" 10 | }, 11 | { 12 | "name": "Pages", 13 | "path": "src/pages" 14 | }, 15 | { 16 | "name": "Stores", 17 | "path": "src/stores" 18 | } 19 | ], 20 | "settings": { 21 | "i18n-ally.localesPaths": [ 22 | "locales" 23 | ] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/apis/test.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import type { InstApi } from '@/types/global' 3 | 4 | export function mockLists(params: { 5 | page: number 6 | pagesize: number 7 | }): Promise { 8 | return apis.get('/_mock/lists', { 9 | params, 10 | baseURL: '/', 11 | }) 12 | } 13 | 14 | export function getWeather() { 15 | return axios.get('https://v0.yiketianqi.com/api?unescape=1&version=v61&appid=23035354&appsecret=8YvlPNrz') 16 | } 17 | -------------------------------------------------------------------------------- /src/mockProdServer.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer' 3 | 4 | const mocks: any[] = [] 5 | const mockContext = import.meta.glob('../mock/*.ts', { eager: true }) 6 | 7 | Object.keys(mockContext).forEach((v) => { 8 | const items = mockContext[v] as any 9 | if (items?.default) 10 | mocks.push(...items.default) 11 | }) 12 | 13 | export function setupProdMockServer() { 14 | createProdMockServer(mocks) 15 | } 16 | -------------------------------------------------------------------------------- /src/components/Layout/LoadingContent.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/composables/useLanguage.ts: -------------------------------------------------------------------------------- 1 | import { locale } from '@/modules/i18n' 2 | 3 | export const language = computed(() => { 4 | return locale.value === 'zh' ? '中文' : 'English' 5 | }) 6 | 7 | export const toggleLocale = (lang?: string) => { 8 | if (lang) { 9 | locale.value = lang 10 | return 11 | } 12 | locale.value = locale.value === 'zh' ? 'en' : 'zh' 13 | } 14 | 15 | export default () => { 16 | const { t } = useI18n() 17 | 18 | return { t, locale, toggleLocale, language } 19 | } 20 | -------------------------------------------------------------------------------- /src/pages/example/function/not-keep.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 当前页面切换了就会自动销毁 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | meta: 18 | multiWindow: false 19 | fullscreen: true 20 | i18n: 21 | en: Disposable 22 | zh: 单次页面 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/components/Drawer/utils.ts: -------------------------------------------------------------------------------- 1 | import type { PropType } from 'vue' 2 | 3 | export const props = { 4 | show: Boolean, 5 | loading: Boolean, 6 | closable: Boolean, 7 | title: { 8 | type: String, 9 | default: '', 10 | }, 11 | placement: { 12 | type: String as PropType<'right' | 'left' | 'top' | 'bottom' | 'center' | ''>, 13 | default: '', 14 | }, 15 | width: { 16 | type: String, 17 | default: '50vw', 18 | }, 19 | height: { 20 | type: String, 21 | default: '60vh', 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /src/composables/useWindowFullscreen.ts: -------------------------------------------------------------------------------- 1 | import type { MaybeElementRef, UseFullscreenOptions } from '@vueuse/core' 2 | import { useFullscreen } from '@vueuse/core' 3 | 4 | export default (target?: MaybeElementRef | string | boolean, options?: UseFullscreenOptions) => { 5 | if (target && (typeof target === 'boolean' || typeof target === 'string')) { 6 | target = document.querySelector('#main-window') as HTMLElement 7 | if (!target) 8 | target = document.body 9 | } 10 | 11 | return useFullscreen(target as any, options) 12 | } 13 | -------------------------------------------------------------------------------- /presets/server.ts: -------------------------------------------------------------------------------- 1 | import { env } from './shared/env' 2 | 3 | export function ServerConfig() { 4 | // 开发服务器选项 https://cn.vitejs.dev/config/#server-options 5 | return { 6 | open: false, 7 | port: env.VITE_DEV_SERVE_PORT || 4000, 8 | overlay: true, 9 | proxy: { 10 | '/proxy': { 11 | target: env.VITE_APP_API_BASEURL, 12 | changeOrigin: env.VITE_DEV_PROXY, 13 | rewrite: (path: string) => path.replace(/\/proxy/, ''), 14 | }, 15 | }, 16 | fs: { 17 | strict: true, 18 | }, 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /scripts/shared/base.js: -------------------------------------------------------------------------------- 1 | const showExt = (type) => { 2 | const isTs = type === 'api' || type === 'store' || type === 'module' 3 | const ext = isTs ? 'ts' : 'vue' 4 | return ext 5 | } 6 | 7 | const moduleTypes = [ 8 | 'api', 9 | 'page', 10 | 'store', 11 | 'layout', 12 | 'module', 13 | 'component', 14 | 'composable', 15 | ] 16 | 17 | const showDir = (type) => { 18 | if (type === 'api') 19 | return 'apis' 20 | 21 | return `${type}s` 22 | } 23 | 24 | module.exports = { 25 | showExt, 26 | showDir, 27 | moduleTypes, 28 | } 29 | -------------------------------------------------------------------------------- /presets/plugins/mock.ts: -------------------------------------------------------------------------------- 1 | import { viteMockServe } from 'vite-plugin-mock' 2 | import { env } from '../shared/env' 3 | 4 | // https://github.com/anncwb/vite-plugin-mock 5 | export function MockPlugin() { 6 | const prodEnabled = env.VITE_APP_MOCK_IN_PRODUCTION 7 | 8 | return viteMockServe({ 9 | ignore: /^_/, 10 | mockPath: 'mock', 11 | supportTs: true, 12 | prodEnabled, 13 | injectCode: ` 14 | import { setupProdMockServer } from './mockProdServer'; 15 | setupProdMockServer(); 16 | `, 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /src/composables/usePrefixStorage.ts: -------------------------------------------------------------------------------- 1 | import type { StorageLike, UseStorageOptions } from '@vueuse/core' 2 | import { useStorage } from '@vueuse/core' 3 | 4 | export const storageKey = (key: string) => 5 | `${ 6 | import.meta.env.VITE_APP_STORAGE_PREFIX 7 | || import.meta.env.VITE_APP_TITLE 8 | || 'zls' 9 | }:${key}` 10 | 11 | export default ( 12 | key: string, 13 | initialValue: T, 14 | storage?: StorageLike, 15 | options?: UseStorageOptions, 16 | ) => { 17 | return useStorage(storageKey(key), initialValue, storage, options) 18 | } 19 | -------------------------------------------------------------------------------- /src/components/Layout/Loading.tsx: -------------------------------------------------------------------------------- 1 | import { NSpin } from 'naive-ui' 2 | import { h } from 'vue' 3 | import Card from '@/components/Card' 4 | 5 | export default defineComponent({ 6 | setup(_props) { 7 | // const { t } = useI18n() 8 | return () => h(NSpin, 9 | { 10 | size: 'small', 11 | rotate: false, 12 | }, 13 | { 14 | default: () => h(Card, { 15 | height: true, 16 | }, ''), 17 | // description: () => h('span', t('loading_page')), 18 | // icon: () => h(NIcon, { class: 'i-bx:dots-horizontal' }), 19 | }) 20 | }, 21 | }) 22 | -------------------------------------------------------------------------------- /src/components/Layout/Header/Network.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {{ t('tip') }} 17 | 18 | 19 | 20 | 21 | en: 22 | tip: Network disconnection 23 | 24 | zh: 25 | tip: 网络断开 26 | 27 | -------------------------------------------------------------------------------- /src/components/Layout/Header/Translate.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | toggleLocale()"> 13 | 14 | 15 | 16 | {{ t('tip') }} 17 | 18 | 19 | 20 | 21 | en: 22 | tip: Toggle locales 23 | 24 | zh: 25 | tip: 切换语言 26 | 27 | -------------------------------------------------------------------------------- /src/components/DataForm/values.ts: -------------------------------------------------------------------------------- 1 | export function useValues(model: any) { 2 | const rawValues = ref>({}) 3 | const isNew = ref(false) 4 | function getValues() { 5 | return { ...model.value } 6 | } 7 | 8 | function setValues(v: Record) { 9 | if (!v || !Object.keys(v).length) 10 | isNew.value = true 11 | else 12 | isNew.value = false 13 | 14 | rawValues.value = { ...v } 15 | model.value = { ...v } 16 | } 17 | 18 | onMounted(() => { 19 | rawValues.value = getValues() 20 | }) 21 | 22 | return { 23 | isNew, 24 | getValues, 25 | rawValues, 26 | setValues, 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/components/VChart/example/line-data.ts: -------------------------------------------------------------------------------- 1 | import { LineChart } from 'echarts/charts' 2 | import { use } from 'echarts/core' 3 | import type { ECBasicOption } from 'echarts/types/dist/shared' 4 | import 'vue-echarts' 5 | use(LineChart) 6 | 7 | export const optionLine = ref({ 8 | title: { 9 | text: '折线图', 10 | }, 11 | xAxis: { 12 | type: 'category', 13 | boundaryGap: false, 14 | data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], 15 | }, 16 | yAxis: { 17 | type: 'value', 18 | }, 19 | series: [ 20 | { 21 | data: [820, 932, 901, 934, 1290, 1330, 1320], 22 | type: 'line', 23 | areaStyle: {}, 24 | }, 25 | ], 26 | }) 27 | -------------------------------------------------------------------------------- /src/utils/render.ts: -------------------------------------------------------------------------------- 1 | import { NIcon, NTooltip } from 'naive-ui' 2 | import type { VNode } from 'vue' 3 | 4 | export function renderTooltip( 5 | trigger: VNode | string, 6 | content: VNode | string, 7 | ) { 8 | return h(NTooltip, null, { 9 | trigger: () => trigger, 10 | default: () => content, 11 | }) 12 | } 13 | 14 | export function renderIcon(icon: any, size?: number) { 15 | let props: any = size ? { size } : {} 16 | const isClassIcon = typeof icon === 'string' 17 | if (isClassIcon) 18 | props = { ...props, class: icon } 19 | 20 | return () => { 21 | return h(NIcon, props, { 22 | default: () => (isClassIcon ? h(icon) : null), 23 | }) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@antfu" 4 | ], 5 | "rules": { 6 | "prefer-promise-reject-errors": "off", 7 | "@typescript-eslint/ban-ts-comment": "off", 8 | "no-console": "off", 9 | "@typescript-eslint/no-use-before-define": "off", 10 | "vue/component-name-in-template-casing": [ 11 | "error", 12 | "PascalCase", 13 | { 14 | "registeredComponentsOnly": false 15 | } 16 | ], 17 | "vue/component-tags-order": [ 18 | "error", 19 | { 20 | "order": [ 21 | "script", 22 | "template", 23 | "style", 24 | "route", 25 | "i18n" 26 | ] 27 | } 28 | ] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/pages/example/layout/basic.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | 切换满屏 15 | 16 | {{ t('header') }} 17 | 18 | 19 | 20 | 21 | 22 | 23 | { "meta": { "i18n": { "en": "Basics", "zh": "基础布局" } } } 24 | 25 | 26 | 27 | { 28 | "en": { 29 | "header": "This is a simple page." 30 | }, 31 | "zh": { 32 | "header": "这是一个简单的页面。" 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /src/pages/system/menu.ts: -------------------------------------------------------------------------------- 1 | import type { StMenu } from '@/types/global' 2 | 3 | export default [ 4 | { 5 | type: 'divider', 6 | }, 7 | { 8 | title: '系统管理', 9 | i18n: { 10 | en: 'System', 11 | }, 12 | icon: 'i-bx:wrench', 13 | path: 'system', 14 | children: [ 15 | { 16 | icon: 'i-bx:user', 17 | path: '/system/user', 18 | }, 19 | { 20 | icon: 'i-bx-id-card', 21 | path: '/system/role', 22 | }, 23 | { 24 | icon: 'i-bx-food-menu', 25 | path: '/system/menu', 26 | }, 27 | { 28 | icon: 'i-bx-objects-horizontal-left', 29 | path: '/system/site', 30 | }, 31 | ], 32 | }, 33 | ] as StMenu[] 34 | -------------------------------------------------------------------------------- /src/components/CardCols.vue: -------------------------------------------------------------------------------- 1 | 31 | -------------------------------------------------------------------------------- /presets/plugins/legacy.ts: -------------------------------------------------------------------------------- 1 | import legacy from '@vitejs/plugin-legacy' 2 | import { LegacBrowserslist, browserslist, env } from '../shared/env' 3 | 4 | // https://github.com/vitejs/vite/tree/main/packages/plugin-legacy 5 | export function LegacyPlugin() { 6 | if (!env.IS_PROD) 7 | return null 8 | 9 | return (env.IS_PROD && env.VITE_BUILD_LEGACY) 10 | ? legacy({ 11 | polyfills: true, 12 | targets: browserslist, 13 | modernPolyfills: true, 14 | additionalLegacyPolyfills: ['regenerator-runtime/runtime'], 15 | }) 16 | : legacy({ 17 | ignoreBrowserslistConfig: true, 18 | targets: LegacBrowserslist, 19 | polyfills: false, 20 | // modernPolyfills: true, 21 | renderLegacyChunks: false, 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /src/pages/[...notFound].vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 | {{ t('button.back') }} 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | meta: 28 | multiWindow: false 29 | i18n: 30 | en: Not Found 31 | zh: 页面不存在 32 | 33 | -------------------------------------------------------------------------------- /src/pages/example/layout/table-form/form.ts: -------------------------------------------------------------------------------- 1 | export function useForm() { 2 | const form = useDataForm() 3 | const { setItems, setOptions } = form 4 | 5 | setOptions({ 6 | labelWidth: 80, 7 | labelPlacement: 'top', 8 | }) 9 | 10 | setItems({ 11 | name: { 12 | label: '用户名', 13 | component: 'NInput', 14 | required: true, 15 | }, 16 | passwd: { 17 | label: '密码', 18 | component: 'NInput', 19 | required: true, 20 | props: { 21 | type: 'password', 22 | }, 23 | }, 24 | }) 25 | 26 | const showDrawer = ref(false) 27 | const drawerAction = ref('') 28 | 29 | function submitForm(data: any) { 30 | console.log(data) 31 | } 32 | 33 | return { ...form, showDrawer, drawerAction, submitForm } 34 | } 35 | -------------------------------------------------------------------------------- /src/pages/example/function/fullscreen.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 切换满屏 16 | 17 | 18 | 返回 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | { 31 | "meta": { 32 | "i18n": { 33 | "zh": "切换全屏", 34 | "en": "Fullscreen" 35 | }, 36 | "layout": "Empty", 37 | "fullscreen": true 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /presets/optimize.ts: -------------------------------------------------------------------------------- 1 | import type { DepOptimizationOptions } from 'vite' 2 | import { normalizeResolvers } from './shared/resolvers' 3 | 4 | export default function (): DepOptimizationOptions { 5 | return { 6 | include: normalizeResolvers({ 7 | onlyExist: [ 8 | ['axios', 'axios'], 9 | ['vue-use-api', 'vue-use-api'], 10 | ['@vueuse/core', '@vueuse/core'], 11 | ['naive-ui', 'naive-ui'], 12 | ['vant/es', 'vant'], 13 | ['vant/es/config-provider/style/index', 'vant'], 14 | ['date-fns', 'date-fns'], 15 | ['vue-echarts', 'vue-echarts'], 16 | ['echarts/charts', 'vue-echarts'], 17 | ['echarts/components', 'vue-echarts'], 18 | ['echarts/core', 'vue-echarts'], 19 | ['echarts/renderers', 'vue-echarts'], 20 | ], 21 | include: ['vue', 'vue-router', 'ayyui'], 22 | }) as string[], 23 | exclude: [], 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # 如果需要特别配置只本地生效,请复制当前文件,并重命名为 .env.development.local ,然后在该文件中配置 2 | # 如果生产环境需要特殊配置,可以复制当前文件,并重命名为 .env.production ,然后在该文件中配置 3 | 4 | 5 | # 标题 6 | VITE_APP_TITLE = ZlsApp 7 | 8 | # 接口域名,如:https://73zls.com 9 | VITE_APP_API_BASEURL = 10 | 11 | # 接口请求超时时间,毫秒 12 | VITE_APP_API_TIMEOUT = 10000 13 | 14 | # 开发服务器端口 15 | VITE_DEV_SERVE_PORT = 4000 16 | 17 | # 是否代理转发,true 则转发上面的 VITE_APP_API_BASEURL 18 | VITE_DEV_PROXY = false 19 | 20 | # 项目根目录 21 | VITE_BUILD_BASE = / 22 | 23 | # 构建产物输出目录 24 | VITE_BUILD_OUT_DIR = dist/ 25 | 26 | # 开发时 Inspect 调试支持 27 | VITE_DEV_INSPECT = true 28 | 29 | # 路由模式 hash 或 history 30 | VITE_ROUTER_HISTORY = hash 31 | 32 | # 生产时 mock 支持 33 | VITE_APP_MOCK_IN_PRODUCTION = false 34 | 35 | # 生产时是否构建旧版浏览器兼容代码 36 | VITE_BUILD_LEGACY = false 37 | 38 | # 生产时压缩算法,可选 gzip, brotliCompress, deflate, deflateRaw 39 | VITE_APP_COMPRESSINON_ALGORITHM = 40 | 41 | # 开启 PWA 42 | VITE_BUILD_PWA = false 43 | 44 | 45 | -------------------------------------------------------------------------------- /unocss.config.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defineConfig, presetAttributify, presetUno, 3 | } from 'unocss' 4 | import presetIcons from '@unocss/preset-icons' 5 | import presetWind from '@unocss/preset-wind' 6 | import { presetCore, presetThemeDefault } from 'ayyui' 7 | 8 | export default defineConfig({ 9 | presets: [ 10 | presetAttributify(), 11 | presetUno(), 12 | presetWind(), 13 | presetCore(), 14 | presetThemeDefault(), 15 | // https://icones.netlify.app/ 16 | presetIcons({ 17 | scale: 1.2, 18 | extraProperties: { 19 | 'height': '1em', 20 | 'width': '1em', 21 | 'flex-shrink': '0', 22 | 'display': 'inline-block', 23 | 'vertical-align': 'middle', 24 | }, 25 | }), 26 | ], 27 | include: [ 28 | /.*\/ayyui\.js(.*)?$/, 29 | './**/*.vue', 30 | './**/*.md', 31 | './**/*.ts', 32 | './**/*.tsx', 33 | './src/menu.json', 34 | ], 35 | }) 36 | -------------------------------------------------------------------------------- /src/components/Layout/Header/Fullscreen.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | 14 | 18 | 19 | 20 | {{ isFullscreen ? t('fullscreen_exit') : t('fullscreen') }} 21 | 22 | 23 | 24 | 25 | en: 26 | fullscreen: Toggle Fullscreen 27 | fullscreen_exit: Exit Fullscreen 28 | 29 | zh: 30 | fullscreen: 切换全屏 31 | fullscreen_exit: 退出全屏 32 | 33 | -------------------------------------------------------------------------------- /presets/plugins/markdown.ts: -------------------------------------------------------------------------------- 1 | import LinkAttributes from 'markdown-it-link-attributes' 2 | import Prism from 'markdown-it-prism' 3 | import Markdown from 'vite-plugin-md' 4 | import { env } from './../shared/env' 5 | 6 | export const markdownWrapperClasses = env.VITE_APP_MARKDOWN 7 | ? 'prose md:prose-lg lg:prose-lg dark:prose-invert text-left p-10 prose-slate prose-img:rounded-xl prose-headings:underline prose-a:text-blue-600' 8 | : '' 9 | 10 | export default () => { 11 | return ( 12 | env.VITE_APP_MARKDOWN 13 | && Markdown({ 14 | // builders: [link()], 15 | wrapperClasses: markdownWrapperClasses, 16 | markdownItSetup(md) { 17 | md.use(Prism) 18 | md.use(LinkAttributes, { 19 | matcher: (link: string) => /^:\/\//.test(link), 20 | attrs: { 21 | target: '_blank', 22 | rel: 'noopener', 23 | }, 24 | }) 25 | }, 26 | }) 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /presets/plugins/pwa.ts: -------------------------------------------------------------------------------- 1 | 2 | import { VitePWA } from "vite-plugin-pwa" 3 | import { env } from '../shared/env' 4 | 5 | // https://github.com/antfu/vite-plugin-pwa 6 | export function PWAPlugin() { 7 | return env.VITE_BUILD_PWA ? VitePWA({ 8 | registerType: "autoUpdate", 9 | includeAssets: ["favicon.ico"], 10 | manifest: { 11 | name: "ViteBoot", 12 | short_name: "ViteBoot", 13 | theme_color: "#ffffff", 14 | icons: [ 15 | { 16 | src: "/pwa-192x192.png", 17 | sizes: "192x192", 18 | type: "image/png", 19 | }, 20 | { 21 | src: "/pwa-512x512.png", 22 | sizes: "512x512", 23 | type: "image/png", 24 | }, 25 | { 26 | src: "/pwa-512x512.png", 27 | sizes: "512x512", 28 | type: "image/png", 29 | purpose: "any maskable", 30 | }, 31 | ], 32 | }, 33 | }) : null 34 | } 35 | -------------------------------------------------------------------------------- /src/types/env.d.ts: -------------------------------------------------------------------------------- 1 | 2 | /// 3 | /// 4 | /// 5 | /// 6 | /// 7 | /// 8 | 9 | declare module '*.vue' { 10 | import type { DefineComponent } from 'vue' 11 | const component: DefineComponent<{}, {}, any> 12 | export default component 13 | } 14 | 15 | declare module '*.md' { 16 | import { ComponentOptions } from 'vue' 17 | const Component: ComponentOptions 18 | export default Component 19 | } 20 | 21 | 22 | interface ImportMetaEnv { 23 | [x: string]: any 24 | readonly VITE_APP_TITLE: string 25 | readonly VITE_DEV_PROXY: string 26 | } 27 | 28 | interface ImportMeta { 29 | readonly env: ImportMetaEnv 30 | } 31 | 32 | declare module 'postcss-preset-env' 33 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /presets/shared/resolvers.ts: -------------------------------------------------------------------------------- 1 | import { isPackageExists } from 'local-pkg' 2 | import type { Resolver } from 'unplugin-auto-import/types' 3 | import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' 4 | import type { ComponentResolver } from 'unplugin-vue-components/types' 5 | 6 | type Arrayable = T | Array 7 | type Resolvers = Arrayable> 8 | 9 | export const AutoImportResolvers: Resolvers = [ElementPlusResolver()] 10 | 11 | interface Options { 12 | onlyExist?: [Arrayable | string, string][] 13 | include?: ComponentResolver[] | string[] | Object[] 14 | } 15 | export const normalizeResolvers = (options: Options = {}) => { 16 | const { onlyExist = [], include = [] } = options 17 | 18 | const existedResolvers = [] 19 | for (let i = 0; i < onlyExist.length; i++) { 20 | const [resolver, packageName] = onlyExist[i] 21 | if (isPackageExists(packageName)) 22 | existedResolvers.push(resolver) 23 | } 24 | 25 | return [...existedResolvers, ...include] 26 | } 27 | -------------------------------------------------------------------------------- /presets/plugins/html.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from 'vite' 2 | import { env } from '../shared/env' 3 | 4 | export const GenerateTitle = (): Plugin => { 5 | // let title: string 6 | let base: string 7 | return { 8 | name: 'vite-plugin-env-to-generate-title', 9 | configResolved(_config) { 10 | // title = config.env.VITE_APP_TITLE 11 | base = (env.IS_PROD && env.VITE_BUILD_BASE) ? `${env.VITE_BUILD_BASE}` : '/' 12 | }, 13 | transformIndexHtml(html) { 14 | let appendBody = '' 15 | // https://polyfill.io/v3/polyfill.js?features=Object.fromEntries,es5,es6,es7&flags=gated 16 | if (env.VITE_BUILD_LEGACY) 17 | appendBody = `` 18 | else 19 | appendBody = `` 20 | 21 | html = html.replace('