├── src ├── layouts │ ├── foot.vue │ ├── menu.vue │ └── default-layout.vue ├── assets │ ├── css │ │ ├── base.less │ │ ├── main.css │ │ ├── logo.svg │ │ └── base.css │ └── img │ │ ├── logo.png │ │ ├── logo1.png │ │ └── login-bg.svg ├── components │ ├── types │ │ ├── modal.ts │ │ ├── table.ts │ │ └── form.ts │ ├── pagination.vue │ ├── modal.vue │ ├── search-form.vue │ ├── avatar-uploader.vue │ ├── form.vue │ └── table.vue ├── types │ └── axios.d.ts ├── App.vue ├── global.d.ts ├── apis │ ├── index.ts │ ├── dashboard.ts │ ├── link.ts │ ├── notice.ts │ ├── category.ts │ ├── banner.ts │ ├── website-info.ts │ ├── user.ts │ ├── blog.ts │ └── special.ts ├── stores │ ├── index.ts │ └── module │ │ ├── blog.ts │ │ ├── category.ts │ │ ├── website-info.ts │ │ └── user.ts ├── views │ ├── friend-link │ │ └── friend-link.vue │ ├── blog-category │ │ └── index.vue │ ├── website-info │ │ ├── config.ts │ │ └── website-info.vue │ ├── editor │ │ └── index.vue │ ├── dashboard │ │ └── dashbord.vue │ ├── special │ │ ├── sectionDetail.vue │ │ ├── section.vue │ │ └── index.vue │ ├── login │ │ └── login.vue │ ├── blog │ │ ├── createBlog.vue │ │ └── blog.vue │ ├── category │ │ └── index.vue │ ├── banner │ │ └── index.vue │ ├── notice │ │ └── index.vue │ ├── link │ │ └── index.vue │ └── webmaster-info │ │ └── webmaster-info.vue ├── shims-vue.d.ts ├── utils │ ├── constant.ts │ └── http.ts ├── main.ts └── router │ └── index.ts ├── .husky ├── pre-commit └── commit-msg ├── public └── favicon.ico ├── .vscode ├── extensions.json └── settings.json ├── .gitpod.yml ├── env.d.ts ├── .babelrc ├── .env.production ├── .env.development ├── .prettierrc.json ├── cypress ├── fixtures │ └── example.json ├── e2e │ ├── example.cy.ts │ └── tsconfig.json └── support │ ├── e2e.ts │ └── commands.ts ├── tsconfig.vitest.json ├── cypress.config.ts ├── tsconfig.node.json ├── tsconfig.app.json ├── 工程化配置.md ├── index.html ├── .gitignore ├── vitest.config.ts ├── tsconfig.json ├── commitlint.config.js ├── Dockerfile ├── .eslintrc.cjs ├── LICENSE ├── vite.config.ts ├── .cz-config.js ├── components.d.ts ├── .stylelintrc.js ├── package.json └── README.md /src/layouts/foot.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/css/base.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/types/modal.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/types/table.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | pnpm lint 5 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helloworld-Co/flygoose-blog-admin/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"], 3 | } 4 | -------------------------------------------------------------------------------- /src/assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helloworld-Co/flygoose-blog-admin/HEAD/src/assets/img/logo.png -------------------------------------------------------------------------------- /src/assets/img/logo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helloworld-Co/flygoose-blog-admin/HEAD/src/assets/img/logo1.png -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no -- commitlint --edit "$1" 5 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | ports: 2 | - port: 3344 3 | onOpen: open-preview 4 | tasks: 5 | - init: pnpm 6 | command: pnpm dev 7 | -------------------------------------------------------------------------------- /env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | interface ImportMetaEnv { 3 | readonly VITE_API_BASE_URL: string 4 | } 5 | -------------------------------------------------------------------------------- /src/types/axios.d.ts: -------------------------------------------------------------------------------- 1 | export interface CustomResponseType { 2 | code: number 3 | message: string 4 | data: T 5 | } 6 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["import", { "libraryName": "ant-design-vue", "libraryDirectory": "es", "style": "css" }] // `style: true` 会加载 less 文件 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /src/assets/css/main.css: -------------------------------------------------------------------------------- 1 | @import url('./base.css'); 2 | 3 | html, 4 | body { 5 | overflow: hidden; 6 | } 7 | 8 | #app { 9 | width: 100%; 10 | height: 100%; 11 | } 12 | -------------------------------------------------------------------------------- /src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.vue" { 2 | import { defineComponent } from "vue"; 3 | const Component: ReturnType; 4 | export default Component; 5 | } -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | # VITE_API_BASE_URL = "/" 2 | VITE_API_BASE_URL = "https://flygoose-admin.helloworld.net/api/" 3 | VITE_API_BASE_UPLOAD_IMG_URL = "https://flygoose-admin.helloworld.net" -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | # VITE_API_BASE_URL = "/api" 2 | # 请求地址 3 | VITE_API_BASE_URL = "https://test-blog.helloworld.net/api/" 4 | # 图片地址 5 | VITE_API_BASE_UPLOAD_IMG_URL = "https://test-blog.helloworld.net" -------------------------------------------------------------------------------- /src/apis/index.ts: -------------------------------------------------------------------------------- 1 | export * from './user' 2 | export * from './website-info' 3 | export * from './blog' 4 | export * from './category' 5 | export * from './special' 6 | export * from './dashboard' 7 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/prettierrc", 3 | "semi": false, 4 | "tabWidth": 2, 5 | "singleQuote": true, 6 | "printWidth": 100, 7 | "trailingComma": "none" 8 | } -------------------------------------------------------------------------------- /cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } 6 | -------------------------------------------------------------------------------- /tsconfig.vitest.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.app.json", 3 | "exclude": [], 4 | "compilerOptions": { 5 | "composite": true, 6 | "lib": [], 7 | "types": ["node", "jsdom"] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'cypress' 2 | 3 | export default defineConfig({ 4 | e2e: { 5 | specPattern: 'cypress/e2e/**/*.{cy,spec}.{js,jsx,ts,tsx}', 6 | baseUrl: 'http://localhost:4173' 7 | } 8 | }) 9 | -------------------------------------------------------------------------------- /src/stores/index.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue'; 2 | import { createPinia } from 'pinia'; 3 | const store = createPinia(); 4 | 5 | export function setupStore(app: App) { 6 | app.use(store); 7 | } 8 | 9 | export { store }; -------------------------------------------------------------------------------- /cypress/e2e/example.cy.ts: -------------------------------------------------------------------------------- 1 | // https://docs.cypress.io/api/introduction/api.html 2 | 3 | describe('My First Test', () => { 4 | it('visits the app root url', () => { 5 | cy.visit('/') 6 | cy.contains('h1', 'You did it!') 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.node.json", 3 | "include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "types": ["node"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /cypress/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.web.json", 3 | "include": ["./**/*", "../support/**/*"], 4 | "compilerOptions": { 5 | "isolatedModules": false, 6 | "target": "es5", 7 | "lib": ["es5", "dom"], 8 | "types": ["cypress"] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/assets/css/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/apis/dashboard.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/http' 2 | 3 | /** 4 | * @description 获取仪表盘数据 5 | */ 6 | export const getDashboardInfo = (): Promise => { 7 | return request<{ token: string }>({ 8 | url: '/admin/workStation/getStatistics', 9 | method: 'POST' 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /src/components/types/form.ts: -------------------------------------------------------------------------------- 1 | export interface IFormCinfig { 2 | [x: string]: any 3 | id?: string 4 | label: string 5 | field: string 6 | slotType: string 7 | placeholder?: string 8 | options?: option[] 9 | } 10 | 11 | export type option = { 12 | label: string 13 | value: number 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.web.json", 3 | "include": ["env.d.ts", "src/**/*", "src/**/*.vue", "src/types"], 4 | "exclude": ["src/**/__tests__/*"], 5 | "compilerOptions": { 6 | "composite": true, 7 | "baseUrl": ".", 8 | "paths": { 9 | "@/*": ["./src/*"] 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/views/friend-link/friend-link.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 友链管理 4 | 5 | 6 | 7 | 8 | 9 | 17 | -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | // declare module '*.vue' { 2 | // import type { DefineComponent } from 'vue' 3 | // const component: DefineComponent<{}, {}, any> 4 | // export default component 5 | // } 6 | 7 | // declare module 'axios' 8 | declare module '@kangc/v-md-editor' 9 | declare module '@kangc/v-md-editor/lib/theme/vuepress.js' 10 | declare module 'prismjs' 11 | -------------------------------------------------------------------------------- /src/views/blog-category/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | blogCategoryPage 4 | 5 | 6 | 7 | 8 | 9 | 17 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.fixAll": "never", 4 | "source.fixAll.eslint": "explicit", 5 | "source.fixAll.stylelint": "explicit" 6 | }, 7 | "editor.formatOnSave": true, 8 | "editor.defaultFormatter": "esbenp.prettier-vscode", 9 | "editor.tabSize": 2, 10 | "stylelint.validate": ["css", "less", "vue", "html"] 11 | } 12 | -------------------------------------------------------------------------------- /工程化配置.md: -------------------------------------------------------------------------------- 1 | pnpm i @types/node // 在 compilerOptions 中指定的类型库 "node" 的入口点 2 | shims-vue.d.ts // ts 无法识别.vue 文件 3 | node: true, // 解决 module 找不到的报错 (eslintrc 文件) 4 | 5 | 提交规范配置 6 | https://juejin.cn/post/7194051099702558781 7 | https://juejin.cn/post/7205116210077958181 8 | 9 | stylelint 10 | https://juejin.cn/post/6940127032932040735#heading-5 11 | https://juejin.cn/post/7118294114734440455#heading-19 12 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 飞鹅后台管理系统 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/views/website-info/config.ts: -------------------------------------------------------------------------------- 1 | export const columns = [ 2 | { 3 | title: 'name', 4 | dataIndex: 'name', 5 | width: '25%' 6 | }, 7 | { 8 | title: 'age', 9 | dataIndex: 'age', 10 | width: '15%' 11 | }, 12 | { 13 | title: 'address', 14 | dataIndex: 'address', 15 | width: '40%' 16 | }, 17 | { 18 | title: 'operation', 19 | dataIndex: 'operation' 20 | } 21 | ] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | # Editor directories and files 21 | # .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from 'node:url' 2 | import { mergeConfig } from 'vite' 3 | import { configDefaults, defineConfig } from 'vitest/config' 4 | import viteConfig from './vite.config' 5 | 6 | export default mergeConfig( 7 | viteConfig, 8 | defineConfig({ 9 | test: { 10 | environment: 'jsdom', 11 | exclude: [...configDefaults.exclude, 'e2e/*'], 12 | root: fileURLToPath(new URL('./', import.meta.url)) 13 | } 14 | }) 15 | ) 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./tsconfig.node.json" 6 | }, 7 | { 8 | "path": "./tsconfig.app.json" 9 | }, 10 | { 11 | "path": "./tsconfig.vitest.json" 12 | } 13 | ], 14 | "compilerOptions": { 15 | "baseUrl": "./", // 解析非相对模块的基础地址,默认是当前目录 16 | "paths": { 17 | // 路径映射,相对于baseUrl 18 | "@/*": ["src/*"], 19 | "#/*": ["src/types/*"] 20 | } 21 | }, 22 | "include": ["src/types/*.d.ts", "src/types/*.ts"] 23 | } 24 | -------------------------------------------------------------------------------- /src/views/editor/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 18 | 19 | 27 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['gitmoji'] 3 | // rules: { 4 | // 'type-enum': [ 5 | // 2, 6 | // 'always', 7 | // [ 8 | // 'initial', 9 | // 'build', 10 | // 'ci', 11 | // 'docs', 12 | // 'feat', 13 | // 'fix', 14 | // 'perf', 15 | // 'refactor', 16 | // 'revert', 17 | // 'style', 18 | // 'test', 19 | // 'chore', 20 | // 'wip', 21 | // 'mv', 22 | // 'delete', 23 | // 'ui', 24 | // 'up', 25 | // 'down', 26 | // 'docker' 27 | // ] 28 | // ] 29 | // } 30 | } 31 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18.19-alpine3.18 as builder 2 | 3 | COPY . /hellworld-blog 4 | 5 | WORKDIR /hellworld-blog 6 | 7 | RUN npm install -g pnpm --registry=https://registry.npm.taobao.org 8 | 9 | RUN pnpm install --no-frozen-lockfile --registry=https://registry.npm.taobao.org 10 | 11 | RUN pnpm run build 12 | 13 | # deploy flygoose web 14 | FROM node:18.19-alpine3.18 15 | 16 | RUN adduser -D -u 6666 www 17 | 18 | #copy flygoose web 19 | COPY --from=builder /hellworld-blog/dist /apps/hellworld-blog-admin/ 20 | 21 | 22 | WORKDIR /apps/hellworld-blog-admin/ 23 | 24 | RUN chown -R www /apps/hellworld-blog-admin/ 25 | 26 | USER www 27 | 28 | EXPOSE 58081 29 | 30 | CMD ["sh", "-c", "pm2 start pm2.config.js"] -------------------------------------------------------------------------------- /cypress/support/e2e.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /src/apis/link.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/http' 2 | 3 | /** 4 | * @description 更新友链 5 | */ 6 | export const updateLink = (data: any): Promise => { 7 | return request<{ token: string }>({ 8 | url: '/admin/link/update', 9 | method: 'POST', 10 | data 11 | }) 12 | } 13 | 14 | /** 15 | * @description 创建友链 16 | */ 17 | export const createLink = (data: any): Promise => { 18 | return request<{ token: string }>({ 19 | url: '/admin/link/create', 20 | method: 'POST', 21 | data 22 | }) 23 | } 24 | 25 | /** 26 | * @description 获取友链列表 27 | */ 28 | export const getLinkList = (data: any): Promise => { 29 | return request<{ token: string }>({ 30 | url: '/admin/link/getLinkList', 31 | method: 'POST', 32 | data 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /src/apis/notice.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/http' 2 | 3 | /** 4 | * @description 创建通知 5 | */ 6 | export const noticeCreate = (data: any): Promise => { 7 | return request<{ token: string }>({ 8 | url: '/admin/notice/create', 9 | method: 'POST', 10 | data 11 | }) 12 | } 13 | 14 | /** 15 | * @description 更新通知 16 | */ 17 | export const noticeUpdate = (data: any): Promise => { 18 | return request<{ token: string }>({ 19 | url: '/admin/notice/update', 20 | method: 'POST', 21 | data 22 | }) 23 | } 24 | 25 | /** 26 | * @description 通知列表 27 | */ 28 | 29 | export const getNoticeList = (data: any): Promise => { 30 | return request<{ token: string }>({ 31 | url: '/admin/notice/getNoticeList', 32 | method: 'POST', 33 | data 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /src/apis/category.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/http' 2 | 3 | /** 4 | * @description 获取分类列表 5 | */ 6 | export const getCategoryList = (data: any): Promise => { 7 | return request<{ token: string }>({ 8 | url: '/admin/category/getCategoryList', 9 | method: 'POST', 10 | data 11 | }) 12 | } 13 | 14 | /** 15 | * @description 更新分类 16 | */ 17 | export const updateCategory = (data: any): Promise => { 18 | return request<{ token: string }>({ 19 | url: '/admin/category/update', 20 | method: 'POST', 21 | data 22 | }) 23 | } 24 | 25 | /** 26 | * @description 创建分类 27 | */ 28 | export const createCategory = (data: any): Promise => { 29 | return request<{ token: string }>({ 30 | url: '/admin/category/create', 31 | method: 'POST', 32 | data 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /src/components/pagination.vue: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 30 | 31 | 37 | -------------------------------------------------------------------------------- /src/apis/banner.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/http' 2 | 3 | /** 4 | * @description 创建轮播图 5 | * @returns {Promise} 创建轮播图 6 | */ 7 | export const bannerCreate = (data: any): Promise => { 8 | return request<{ token: string }>({ 9 | url: '/admin/banner/create', 10 | method: 'POST', 11 | data 12 | }) 13 | } 14 | 15 | /** 16 | * @description 更新轮播图 17 | */ 18 | export const bannerUpdate = (data: any): Promise => { 19 | return request<{ token: string }>({ 20 | url: '/admin/banner/update', 21 | method: 'POST', 22 | data 23 | }) 24 | } 25 | 26 | /** 27 | * @description 获取轮播图列表 28 | */ 29 | 30 | export const bannerGetBannerList = (data: any): Promise => { 31 | return request<{ token: string }>({ 32 | url: '/admin/banner/getBannerList', 33 | method: 'POST', 34 | data 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | require('@rushstack/eslint-patch/modern-module-resolution') 3 | 4 | module.exports = { 5 | root: true, 6 | env: { 7 | browser: true, 8 | node: true 9 | }, 10 | // 解决module找不到的报错 11 | extends: [ 12 | 'plugin:vue/vue3-essential', 13 | 'eslint:recommended', 14 | '@vue/eslint-config-typescript', 15 | '@vue/eslint-config-prettier/skip-formatting' 16 | ], 17 | overrides: [ 18 | { 19 | files: ['cypress/e2e/**/*.{cy,spec}.{js,ts,jsx,tsx}'], 20 | extends: ['plugin:cypress/recommended'] 21 | } 22 | ], 23 | parserOptions: { 24 | ecmaVersion: 'latest' 25 | }, 26 | rules: { 27 | // 关闭驼峰命名规则 28 | 'vue/multi-word-component-names': 0, 29 | 'max-lines': ['error', 300], 30 | 'vue/valid-v-slot': 0 31 | } 32 | // extends: ["prettier"] 33 | } 34 | -------------------------------------------------------------------------------- /src/components/modal.vue: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 43 | -------------------------------------------------------------------------------- /src/utils/constant.ts: -------------------------------------------------------------------------------- 1 | import type { option } from '@/components/types/form' 2 | export const STATUSENUM: { [key: number]: { name: string; color: string } } = { 3 | 10: { 4 | name: '已创建', 5 | color: '#00B5CE' 6 | }, 7 | 20: { 8 | name: '已下架', 9 | color: '#F4212E' 10 | }, 11 | 30: { 12 | name: '已发布', 13 | color: '#1BC47D' 14 | } 15 | } 16 | 17 | // const d = Object.keys(STATUSENUM).map((item: string) => { 18 | // return { 19 | // label: STATUSENUM[parseInt(item)].name 20 | // } 21 | // }) 22 | export const STATUSLIST: option[] = [ 23 | { label: '全部', value: 0 }, 24 | { label: '已创建 ', value: 10 }, 25 | { label: '已下架', value: 20 }, 26 | { label: '已发布', value: 30 } 27 | ] 28 | 29 | export const NORMALSTATUS = [ 30 | { label: '全部', value: -1 }, 31 | { label: '已上架', value: 1 }, 32 | { label: '已下架', value: 0 } 33 | ] 34 | export const NORMALSTATUSENUM = { 35 | 1: { 36 | name: '已上架', 37 | color: '#1BC47D' 38 | }, 39 | 0: { 40 | name: '已下架', 41 | color: '#F4212E' 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Helloworld-Co 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/apis/website-info.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/http' 2 | 3 | /** 4 | * @description 获取网站信息 5 | * @returns {Promise} 请求网站信息 6 | */ 7 | export const getUsedSiteInfo = (): Promise => { 8 | return request<{ token: string }>({ 9 | url: '/admin/site/getUsedSiteInfo', 10 | method: 'POST' 11 | }) 12 | } 13 | 14 | /** 15 | * @description 修改网站信息 16 | * @returns {Promise} 修改网站信息 17 | */ 18 | export const updateSite = (data: any): Promise => { 19 | return request<{ token: string }>({ 20 | url: '/admin/site/updateSite', 21 | method: 'POST', 22 | data 23 | }) 24 | } 25 | 26 | /** 27 | * @description 获取网站信息列表 28 | * @returns {Promise} 获取网站信息列表 29 | */ 30 | export const getSiteInfoList = (): Promise => { 31 | return request<{ token: string }>({ 32 | url: '/admin/site/getSiteInfoList', 33 | method: 'POST' 34 | }) 35 | } 36 | 37 | /** 38 | * @description 创建网站信息 39 | * @returns {Promise} 创建网站信息 40 | */ 41 | export const createSite = (data: any): Promise => { 42 | return request<{ token: string }>({ 43 | url: '/admin/site/createSite', 44 | method: 'POST', 45 | data 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /src/stores/module/blog.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | import { store } from '@/stores' 3 | import { updateBlog, getBlogListByStatus, createBlog } from '@/apis' 4 | 5 | 6 | const useBlogStore = defineStore('blog', () => { 7 | 8 | 9 | 10 | async function updateBlogAction(params: any) { 11 | updateBlog(params).then((result: any) => { 12 | console.log("🚀 result:", result) 13 | }).catch((err: any) => { 14 | console.log("🚀 err:", err) 15 | }); 16 | } 17 | 18 | 19 | async function getBlogListByStatusAction(p: any) { 20 | return getBlogListByStatus(p).then((result: any) => { 21 | return Promise.resolve(result.data) 22 | }).catch((err: any) => { 23 | console.log("🚀 err:", err) 24 | }); 25 | } 26 | 27 | async function createBlogAction(params: any) { 28 | return createBlog(params).then((result: any) => { 29 | return Promise.resolve(result.data) 30 | }).catch((err: any) => { 31 | console.log("🚀 err:", err) 32 | }); 33 | } 34 | 35 | 36 | return { updateBlogAction, getBlogListByStatusAction, createBlogAction } 37 | }) 38 | 39 | export function useBlogStoreWithOut() { 40 | return useBlogStore(store) 41 | } 42 | -------------------------------------------------------------------------------- /src/stores/module/category.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | import { store } from '@/stores' 3 | import { getCategoryList, updateCategory, createCategory } from '@/apis' 4 | 5 | 6 | const useCategoryStore = defineStore('category', () => { 7 | 8 | async function getCategoryListAction(params: any) { 9 | return getCategoryList(params).then((result: any) => { 10 | return Promise.resolve(result.data) 11 | }).catch((err: any) => { 12 | throw new Error(err) 13 | }); 14 | } 15 | 16 | 17 | async function updateCategoryAction(p: any) { 18 | return updateCategory(p).then((result: any) => { 19 | return Promise.resolve(result.data) 20 | }).catch((err: any) => { 21 | throw new Error(err) 22 | }); 23 | } 24 | 25 | async function createCategoryAction(params: any) { 26 | return createCategory(params).then((result: any) => { 27 | return Promise.resolve(result.data) 28 | }).catch((err: any) => { 29 | throw new Error(err) 30 | }); 31 | } 32 | 33 | 34 | return { getCategoryListAction, updateCategoryAction, createCategoryAction } 35 | }) 36 | 37 | export function useCategoryWithOut() { 38 | return useCategoryStore(store) 39 | } 40 | -------------------------------------------------------------------------------- /src/components/search-form.vue: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 48 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Shaoli 3 | * @Date: 2023-03-27 22:05:52 4 | * @LastEditors: Shaoli 5 | * @LastEditTime: 2023-03-27 23:10:36 6 | * @Description: 请填写文件描述 7 | */ 8 | import { createApp } from 'vue' 9 | import router from './router' 10 | import { setupStore } from '@/stores' 11 | import App from './App.vue' 12 | 13 | import './assets/css/main.css' 14 | import 'normalize.css' 15 | import { useUserStoreWithOut } from './stores/module/user' 16 | 17 | // md编辑器 18 | import VueMarkdownEditor from '@kangc/v-md-editor' 19 | import '@kangc/v-md-editor/lib/style/base-editor.css' 20 | import vuepressTheme from '@kangc/v-md-editor/lib/theme/vuepress.js' 21 | import '@kangc/v-md-editor/lib/theme/style/vuepress.css' 22 | import Prism from 'prismjs' 23 | 24 | import Antd from 'ant-design-vue'; 25 | import 'ant-design-vue/dist/antd.css'; 26 | 27 | VueMarkdownEditor.use(vuepressTheme, { 28 | Prism 29 | }) 30 | 31 | async function bootstrap() { 32 | const app = createApp(App) 33 | app.use(router) 34 | app.use(VueMarkdownEditor) 35 | app.use(Antd); 36 | setupStore(app) 37 | // 刷新页面的时候重新赋值token 38 | if (!localStorage.getItem('token')) { 39 | useUserStoreWithOut().token = '' 40 | } else { 41 | useUserStoreWithOut().token = localStorage.getItem('token') as string 42 | useUserStoreWithOut().userInfo = JSON.parse(localStorage.getItem('userInfo') as string) 43 | } 44 | app.mount('#app') 45 | } 46 | 47 | bootstrap() 48 | -------------------------------------------------------------------------------- /src/views/dashboard/dashbord.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 37 | 47 | -------------------------------------------------------------------------------- /src/stores/module/website-info.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | import { store } from '@/stores' 3 | import { getUsedSiteInfo, updateSite, getSiteInfoList, createSite } from '@/apis' 4 | 5 | 6 | const useWebsiteInfoStore = defineStore('websiteInfo', () => { 7 | async function getUsedSiteInfoAction() { 8 | getUsedSiteInfo().then((result: any) => { 9 | console.log("🚀 result:", result) 10 | }).catch((err: any) => { 11 | console.log("🚀 err:", err) 12 | }); 13 | } 14 | 15 | 16 | async function updateSiteAction(params: any) { 17 | updateSite(params).then((result: any) => { 18 | console.log("🚀 result:", result) 19 | }).catch((err: any) => { 20 | console.log("🚀 err:", err) 21 | }); 22 | } 23 | 24 | 25 | async function getSiteInfoListAction() { 26 | return getSiteInfoList().then((result: any) => { 27 | return Promise.resolve(result.data.list) 28 | }).catch((err: any) => { 29 | console.log("🚀 err:", err) 30 | }); 31 | } 32 | 33 | async function createSiteAction(params: any) { 34 | return createSite(params).then((result: any) => { 35 | return Promise.resolve(result.data) 36 | }).catch((err: any) => { 37 | console.log("🚀 err:", err) 38 | }); 39 | } 40 | 41 | 42 | return { getUsedSiteInfoAction, updateSiteAction, getSiteInfoListAction, createSiteAction } 43 | }) 44 | 45 | export function useWebsiteInfoStoreWithOut() { 46 | return useWebsiteInfoStore(store) 47 | } 48 | -------------------------------------------------------------------------------- /cypress/support/commands.ts: -------------------------------------------------------------------------------- 1 | /// 2 | // *********************************************** 3 | // This example commands.ts shows you how to 4 | // create various custom commands and overwrite 5 | // existing commands. 6 | // 7 | // For more comprehensive examples of custom 8 | // commands please read more here: 9 | // https://on.cypress.io/custom-commands 10 | // *********************************************** 11 | // 12 | // 13 | // -- This is a parent command -- 14 | // Cypress.Commands.add('login', (email, password) => { ... }) 15 | // 16 | // 17 | // -- This is a child command -- 18 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) 19 | // 20 | // 21 | // -- This is a dual command -- 22 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) 23 | // 24 | // 25 | // -- This will overwrite an existing command -- 26 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) 27 | // 28 | // declare global { 29 | // namespace Cypress { 30 | // interface Chainable { 31 | // login(email: string, password: string): Chainable 32 | // drag(subject: string, options?: Partial): Chainable 33 | // dismiss(subject: string, options?: Partial): Chainable 34 | // visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable 35 | // } 36 | // } 37 | // } 38 | 39 | export {} 40 | -------------------------------------------------------------------------------- /src/apis/user.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/http' 2 | 3 | /** 4 | * @description 登陆 5 | * @returns {Promise} 登陆 6 | */ 7 | export const login = (data: any): Promise => { 8 | return request<{ token: string }>({ 9 | url: '/admin/access/login', 10 | method: 'POST', 11 | data 12 | }) 13 | } 14 | export const logout = (data: any): Promise => { 15 | return request<{ token: string }>({ 16 | url: '/admin/access/logout', 17 | method: 'POST', 18 | data 19 | }) 20 | } 21 | 22 | /** 23 | * @description 获取站长信息 24 | * @returns {Promise} 请求站长信息 25 | */ 26 | export const getWebmasterInfo = (): Promise => { 27 | return request<{ token: string }>({ 28 | url: '/admin/site/getWebmasterInfo', 29 | method: 'POST' 30 | }) 31 | } 32 | 33 | /** 34 | * @description 修改站长信息 35 | * @returns {promise} 修改站长信息 36 | */ 37 | export const updateWebmasterInfo = (data: any): Promise => { 38 | return request<{ token: string }>({ 39 | url: '/admin/site/updateWebmasterInfo', 40 | method: 'POST', 41 | data 42 | }) 43 | } 44 | 45 | /** 46 | * @description 获取站长信息列表 47 | * @returns {promise} 获取站长信息列表 48 | */ 49 | export const getWebmasterInfoList = (): Promise => { 50 | return request<{ token: string }>({ 51 | url: '/admin/site/getWebmasterInfoList', 52 | method: 'POST' 53 | }) 54 | } 55 | 56 | /** 57 | * @description 获取站长信息列表 58 | * @returns {promise} 获取站长信息列表 59 | */ 60 | export const createWebmasterInfo = (data: any): Promise => { 61 | return request<{ token: string }>({ 62 | url: '/admin/site/createWebmasterInfo', 63 | method: 'POST', 64 | data 65 | }) 66 | } 67 | -------------------------------------------------------------------------------- /src/assets/css/base.css: -------------------------------------------------------------------------------- 1 | /* color palette from */ 2 | :root { 3 | --vt-c-white: #fff; 4 | --vt-c-white-soft: #f8f8f8; 5 | --vt-c-white-mute: #f2f2f2; 6 | --vt-c-black: #181818; 7 | --vt-c-black-soft: #222; 8 | --vt-c-black-mute: #282828; 9 | --vt-c-indigo: #2c3e50; 10 | --vt-c-divider-light-1: rgb(60 60 60 / 29%); 11 | --vt-c-divider-light-2: rgb(60 60 60 / 12%); 12 | --vt-c-divider-dark-1: rgb(84 84 84 / 65%); 13 | --vt-c-divider-dark-2: rgb(84 84 84 / 48%); 14 | --vt-c-text-light-1: var(--vt-c-indigo); 15 | --vt-c-text-light-2: rgb(60 60 60 / 66%); 16 | --vt-c-text-dark-1: var(--vt-c-white); 17 | --vt-c-text-dark-2: rgb(235 235 235 / 64%); 18 | } 19 | 20 | /* semantic color variables for this project */ 21 | 22 | /* :root { 23 | --color-background: var(--vt-c-white); 24 | --color-background-soft: var(--vt-c-white-soft); 25 | --color-background-mute: var(--vt-c-white-mute); 26 | --color-border: var(--vt-c-divider-light-2); 27 | --color-border-hover: var(--vt-c-divider-light-1); 28 | --color-heading: var(--vt-c-text-light-1); 29 | --color-text: var(--vt-c-text-light-1); 30 | --section-gap: 160px; 31 | } */ 32 | 33 | @media (prefers-color-scheme: dark) { 34 | :root { 35 | --color-background: var(--vt-c-black); 36 | --color-background-soft: var(--vt-c-black-soft); 37 | --color-background-mute: var(--vt-c-black-mute); 38 | --color-border: var(--vt-c-divider-dark-2); 39 | --color-border-hover: var(--vt-c-divider-dark-1); 40 | --color-heading: var(--vt-c-text-dark-1); 41 | --color-text: var(--vt-c-text-dark-2); 42 | } 43 | } 44 | 45 | 46 | .ant-pagination { 47 | text-align: center; 48 | } -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | // import { fileURLToPath, URL } from 'node:url' 2 | import path, { resolve } from 'path' 3 | import { defineConfig } from 'vite' 4 | import vue from '@vitejs/plugin-vue' 5 | import vueJsx from '@vitejs/plugin-vue-jsx' 6 | import Components from 'unplugin-vue-components/vite' 7 | import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers' 8 | 9 | // https://vitejs.dev/config/ 10 | export default defineConfig({ 11 | plugins: [ 12 | vue(), 13 | vueJsx(), 14 | Components({ 15 | resolvers: [AntDesignVueResolver()] 16 | }) 17 | ], 18 | resolve: { 19 | alias: { 20 | // '@': fileURLToPath(new URL('./src', import.meta.url)), 21 | // '#': fileURLToPath(new URL('./src/types', import.meta.url)), 22 | '@': resolve(__dirname, './src'), 23 | '#': resolve(__dirname, './src/types') 24 | } 25 | // extensions: ['.vue'] 26 | // extensions: 27 | // ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'] 28 | }, 29 | server: { 30 | // proxy: { 31 | // '/api': { 32 | // target: 'https://test-blog.helloworld.net/api/admin/', 33 | // changeOrigin: true 34 | // // rewrite: (path) => path.replace(/^\/api/, '') 35 | // }, 36 | // '/static': { 37 | // target: 'http://192.168.168.10:29091', 38 | // changeOrigin: true 39 | // } 40 | // }, 41 | // hmr: true 42 | }, 43 | css: { 44 | preprocessorOptions: { 45 | less: { 46 | modifyVars: { 47 | hack: `true; @import (reference) "${path.resolve('src/assets/css/base.less')}";` 48 | }, 49 | javascriptEnabled: true 50 | } 51 | } 52 | } 53 | }) 54 | -------------------------------------------------------------------------------- /src/apis/blog.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/http' 2 | 3 | /** 4 | * @description 获取网站信息 5 | * @returns {Promise} 请求网站信息 6 | */ 7 | export const getUsedBlogInfo = (): Promise => { 8 | return request<{ token: string }>({ 9 | url: '/admin/site/getUsedBlogInfo', 10 | method: 'POST' 11 | }) 12 | } 13 | 14 | /** 15 | * @description 修改网站信息 16 | * @returns {Promise} 修改网站信息 17 | */ 18 | export const updateBlog = (data: any): Promise => { 19 | return request<{ token: string }>({ 20 | url: '/admin/blog/updateBlog', 21 | method: 'POST', 22 | data 23 | }) 24 | } 25 | 26 | /** 27 | * @description 获取网站信息列表 28 | * @returns {Promise} 获取网站信息列表 29 | */ 30 | export const searchBlog = (data: any): Promise => { 31 | return request<{ token: string }>({ 32 | url: '/admin/blog/searchBlog', 33 | method: 'POST', 34 | data 35 | }) 36 | } 37 | 38 | /** 39 | * @description 创建网站信息 40 | * @returns {Promise} 创建网站信息 41 | */ 42 | export const publishBlog = (data: any): Promise => { 43 | return request<{ token: string }>({ 44 | url: '/admin/blog/publishBlog', 45 | method: 'POST', 46 | data 47 | }) 48 | } 49 | 50 | /** 51 | * @description 获取博客详情 52 | */ 53 | export const getBlogDetail = (data: any): Promise => { 54 | return request<{ token: string }>({ 55 | // url: 'v8/blog/getBlogDetail', 56 | url: '/admin/blog/getBlogDetail', 57 | method: 'POST', 58 | data 59 | }) 60 | } 61 | 62 | /** 63 | * @description 获取博客详情 64 | */ 65 | export const updateBlogStatus = (data: any): Promise => { 66 | return request<{ token: string }>({ 67 | // url: 'v8/blog/updateBlogStatus', 68 | url: '/admin/blog/updateBlogStatus', 69 | method: 'POST', 70 | data 71 | }) 72 | } 73 | -------------------------------------------------------------------------------- /src/apis/special.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/http' 2 | 3 | /** 4 | * @description 搜索专栏 5 | */ 6 | export const searchSpecial = (data: any): Promise => { 7 | return request<{ token: string }>({ 8 | url: '/admin/special/searchSpecial', 9 | method: 'POST', 10 | data 11 | }) 12 | } 13 | 14 | /** 15 | * @description 更新专栏 16 | */ 17 | export const updateSpecial = (data: any): Promise => { 18 | return request<{ token: string }>({ 19 | url: '/admin/special/update', 20 | method: 'POST', 21 | data 22 | }) 23 | } 24 | 25 | /** 26 | * @description 创建专栏 27 | */ 28 | export const createSpecial = (data: any): Promise => { 29 | return request<{ token: string }>({ 30 | url: '/admin/special/create', 31 | method: 'POST', 32 | data 33 | }) 34 | } 35 | 36 | /** 37 | * @description 添加小节 38 | */ 39 | export const addSection = (data: any): Promise => { 40 | return request<{ token: string }>({ 41 | url: '/admin/special/addSection', 42 | method: 'POST', 43 | data 44 | }) 45 | } 46 | 47 | /** 48 | * @description 更新小节 49 | */ 50 | export const updateSection = (data: any): Promise => { 51 | return request<{ token: string }>({ 52 | url: '/admin/special/updateSection', 53 | method: 'POST', 54 | data 55 | }) 56 | } 57 | 58 | /** 59 | * @description 获取专栏小节列表 60 | */ 61 | export const getSectionList = (data: any): Promise => { 62 | return request<{ token: string }>({ 63 | url: '/admin/special/getSectionList', 64 | method: 'POST', 65 | data 66 | }) 67 | } 68 | 69 | /** 70 | * @description 获取小节详情 71 | */ 72 | export const getSectionDetail = (data: any): Promise => { 73 | return request<{ token: string }>({ 74 | url: '/admin/special/getSectionDetail', 75 | method: 'POST', 76 | data 77 | }) 78 | } 79 | -------------------------------------------------------------------------------- /src/stores/module/user.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | import { store } from '@/stores' 3 | import { login, getWebmasterInfo, updateWebmasterInfo, getWebmasterInfoList, createWebmasterInfo } from '@/apis' 4 | import router from '@/router' 5 | import { ref, unref } from 'vue' 6 | 7 | 8 | interface IUserInfo { 9 | [key: string]: string | number 10 | } 11 | 12 | const useUserStore = defineStore('user', () => { 13 | const token = ref('') 14 | const userInfo = ref({}) 15 | 16 | async function loginAction(params: any) { 17 | const res = await login(params) 18 | token.value = res.data.token 19 | localStorage.setItem('token', unref(token)) 20 | await getWebmasterInfoAction() 21 | router.push('/blog') 22 | 23 | } 24 | 25 | async function getWebmasterInfoAction() { 26 | getWebmasterInfo().then((result) => { 27 | userInfo.value = result.data 28 | localStorage.setItem('userInfo', JSON.stringify(userInfo.value)) 29 | }) 30 | } 31 | 32 | 33 | async function updateWebmasterInfoAction(params: any) { 34 | return updateWebmasterInfo(params).then((result) => { 35 | return Promise.resolve(result.data) 36 | }) 37 | } 38 | 39 | async function getWebmasterInfoListAction() { 40 | return getWebmasterInfoList().then((result) => { 41 | return Promise.resolve(result.data.list) 42 | }).catch((err) => { 43 | console.log("🚀 err:", err) 44 | }); 45 | } 46 | 47 | async function createWebmasterInfoAction(params: any) { 48 | return createWebmasterInfo(params).then((result) => { 49 | return Promise.resolve(result.data) 50 | }).catch((err) => { 51 | console.log("🚀 err:", err) 52 | }); 53 | } 54 | 55 | 56 | return { token, userInfo, loginAction, getWebmasterInfoAction, updateWebmasterInfoAction, getWebmasterInfoListAction, createWebmasterInfoAction } 57 | }) 58 | 59 | export function useUserStoreWithOut() { 60 | return useUserStore(store) 61 | } 62 | -------------------------------------------------------------------------------- /src/views/special/sectionDetail.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 22 | 23 | 24 | 25 | 保存 26 | 取消 27 | 28 | 29 | 30 | 31 | 73 | 82 | -------------------------------------------------------------------------------- /.cz-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // 引导用户输入的提示信息 3 | types: [ 4 | { value: ":rocket: initial", name: "🎉 initial: 初始化项目" }, 5 | { value: ":construction: wip", name: "🚧 wip: 工作进行中" }, 6 | { value: ":sparkles: feat", name: "✨ feat: 新增一个功能" }, 7 | { value: ":bug: fix", name: "🐛 fix: 修复一个Bug" }, 8 | { 9 | value: ":hammer: refactor", 10 | name: "🔨 refactor: 重构(既不修复bug也不添加特性的代码更改)", 11 | }, 12 | { value: ":pencil: docs", name: "📝 docs: 文档变更" }, 13 | { 14 | value: ":white_check_mark: test", 15 | name: "✅ test: 添加缺失的测试或更正现有的测试", 16 | }, 17 | { 18 | value: ":thought_balloon: chore", 19 | name: "🗯 chore: 构建过程或辅助工具的变动", 20 | }, 21 | { value: "revert", name: "⏪ revert: 代码回退" }, 22 | { value: ":zap: perf", name: "⚡️ perf: 提升性能" }, 23 | { value: ":lipstick: ui", name: "💄 ui: 更新UI和样式" }, 24 | { value: ":art: style", name: "🎨 style: 改进代码结构/代码格式" }, 25 | { value: ":truck: mv", name: "🚚 mv: 移动重命名文件" }, 26 | { value: ":fire: delte", name: "🔥 delte: 删除文件" }, 27 | { value: ":fire: up", name: "⬆️ up: 升级依赖" }, 28 | { value: ":fire: down", name: "⬇️ down: 降级依赖" }, 29 | { value: ":whale: docker", name: "🐳 ocker: docker相关" }, 30 | { value: ":bookmark: tag", name: "🔖 tag: 发行/版本标签" }, 31 | { value: ":ambulance: patch", name: "🚑 patch: 重要补丁" }, 32 | ], 33 | // 选择scope的提示信息 34 | messages: { 35 | type: "请选择您要提交的类型:", 36 | scope: "请输入修改范围(可选):", 37 | // allowCustomScopes为true时使用 38 | customScope: "请输入文件修改范围(可选):", 39 | subject: "请简要描述提交(必选):", 40 | body: "请输入详细描述,使用'|'换行(可选):", 41 | breaking: "列出任何突破性的变化(可选)", 42 | footer: "请输入要关闭的issue(可选)。例:#31,#34:", 43 | confirmCommit: "您确定要继续执行上面的提交吗?", 44 | }, 45 | // scopes: [ 46 | // "user", 47 | // "login", 48 | // "home", 49 | // "order", 50 | // "product", 51 | // "cart", 52 | // "address", 53 | // "pay", 54 | // "coupon", 55 | // "search", 56 | // "category", 57 | // "detail", 58 | // "other", 59 | // ], 60 | // 跳过某些问题 61 | skipQuestions: [], 62 | allowCustomScopes: true, 63 | allowBreakingChanges: ["feat", "fix"], 64 | subjectLimit: 100, 65 | }; -------------------------------------------------------------------------------- /src/utils/http.ts: -------------------------------------------------------------------------------- 1 | import axios, { type AxiosRequestConfig, AxiosError } from 'axios' 2 | // import { message } from 'ant-design-vue' 3 | import type { CustomResponseType } from '@/types/axios' 4 | import router from '@/router' 5 | import { message } from 'ant-design-vue' 6 | 7 | const service = axios.create({ 8 | baseURL: import.meta.env.VITE_API_BASE_URL, 9 | timeout: 30 * 1000, 10 | // 请求是否携带cookie 11 | withCredentials: true 12 | }) 13 | 14 | // 请求拦截器 15 | service.interceptors.request.use( 16 | (config) => { 17 | // 可以处理token等 18 | config.headers['Content-Type'] = 19 | config.headers['Content-Type'] || 'application/x-www-form-urlencoded' 20 | if (config.url !== '/admin/access/login') { 21 | config.headers.token = localStorage.getItem('token') 22 | config.headers.Authorization = `Bearer ${localStorage.getItem('token')}` 23 | } 24 | return config 25 | }, 26 | (err) => { 27 | return Promise.reject(err) 28 | } 29 | ) 30 | 31 | // 响应拦截器 32 | service.interceptors.response.use( 33 | (response) => { 34 | const { status } = response 35 | if (status < 200 || status >= 300) { 36 | // 统一处理http错误,或者处理后抛到业务代码 TODO 37 | } 38 | if (response.data.code === 1004) { 39 | router.push('/login') 40 | } 41 | // 错误处理 42 | if (response?.data?.code === 0) { 43 | message.error(response?.data?.message) 44 | throw new Error(response?.data?.message) 45 | } 46 | return response 47 | }, 48 | (err) => { 49 | const { status } = err.response 50 | // 根据返回的http状态码做不同的处理,比如错误提示等 TODO 51 | switch (status) { 52 | case 401: 53 | // 鉴权失败 54 | router.push('/login') 55 | 56 | break 57 | case 403: 58 | // 没有权限 59 | break 60 | case 500: 61 | // 服务端错误 62 | // router.push('/login') 63 | break 64 | 65 | default: 66 | break 67 | } 68 | 69 | return Promise.reject(err) 70 | } 71 | ) 72 | 73 | // 封装一层以更好的统一定义接口返回的类型 74 | const request = (config: AxiosRequestConfig): Promise> => { 75 | return new Promise((resolve, reject) => { 76 | service 77 | .request>(config) 78 | .then((res) => { 79 | resolve(res.data) 80 | }) 81 | .catch((err: Error | AxiosError) => { 82 | reject(err) 83 | }) 84 | }) 85 | } 86 | 87 | export default request 88 | -------------------------------------------------------------------------------- /components.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* prettier-ignore */ 3 | // @ts-nocheck 4 | // Generated by unplugin-vue-components 5 | // Read more: https://github.com/vuejs/core/pull/3399 6 | import '@vue/runtime-core' 7 | 8 | export {} 9 | 10 | declare module '@vue/runtime-core' { 11 | export interface GlobalComponents { 12 | ABreadcrumb: typeof import('ant-design-vue/es')['Breadcrumb'] 13 | ABreadcrumbItem: typeof import('ant-design-vue/es')['BreadcrumbItem'] 14 | AButton: typeof import('ant-design-vue/es')['Button'] 15 | ACard: typeof import('ant-design-vue/es')['Card'] 16 | ACol: typeof import('ant-design-vue/es')['Col'] 17 | ADatePicker: typeof import('ant-design-vue/es')['DatePicker'] 18 | ADescriptions: typeof import('ant-design-vue/es')['Descriptions'] 19 | ADescriptionsItem: typeof import('ant-design-vue/es')['DescriptionsItem'] 20 | ADrawer: typeof import('ant-design-vue/es')['Drawer'] 21 | AForm: typeof import('ant-design-vue/es')['Form'] 22 | AFormItem: typeof import('ant-design-vue/es')['FormItem'] 23 | AInput: typeof import('ant-design-vue/es')['Input'] 24 | AInputNumber: typeof import('ant-design-vue/es')['InputNumber'] 25 | AInputPassword: typeof import('ant-design-vue/es')['InputPassword'] 26 | ALayout: typeof import('ant-design-vue/es')['Layout'] 27 | ALayoutContent: typeof import('ant-design-vue/es')['LayoutContent'] 28 | ALayoutSider: typeof import('ant-design-vue/es')['LayoutSider'] 29 | AMenu: typeof import('ant-design-vue/es')['Menu'] 30 | AMenuItem: typeof import('ant-design-vue/es')['MenuItem'] 31 | AModal: typeof import('ant-design-vue/es')['Modal'] 32 | APagination: typeof import('ant-design-vue/es')['Pagination'] 33 | ARow: typeof import('ant-design-vue/es')['Row'] 34 | ASelect: typeof import('ant-design-vue/es')['Select'] 35 | AStatistic: typeof import('ant-design-vue/es')['Statistic'] 36 | ATable: typeof import('ant-design-vue/es')['Table'] 37 | ATag: typeof import('ant-design-vue/es')['Tag'] 38 | ATextarea: typeof import('ant-design-vue/es')['Textarea'] 39 | AUpload: typeof import('ant-design-vue/es')['Upload'] 40 | AvatarUploader: typeof import('./src/components/avatar-uploader.vue')['default'] 41 | Form: typeof import('./src/components/form.vue')['default'] 42 | Modal: typeof import('./src/components/modal.vue')['default'] 43 | Pagination: typeof import('./src/components/pagination.vue')['default'] 44 | RouterLink: typeof import('vue-router')['RouterLink'] 45 | RouterView: typeof import('vue-router')['RouterView'] 46 | SearchForm: typeof import('./src/components/search-form.vue')['default'] 47 | Table: typeof import('./src/components/table.vue')['default'] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/layouts/menu.vue: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | Navigation One 15 | 16 | 17 | 18 | 19 | Item 1 20 | Option 1 21 | Option 2 22 | 23 | 24 | Option 3 25 | Option 4 26 | 27 | 28 | 29 | 30 | 31 | 32 | Navigation Two 33 | Option 5 34 | Option 6 35 | 36 | Option 7 37 | Option 8 38 | 39 | 40 | 41 | 42 | 43 | 44 | Navigation Three 45 | Option 9 46 | Option 10 47 | Option 11 48 | Option 12 49 | 50 | 51 | 52 | 88 | -------------------------------------------------------------------------------- /src/components/avatar-uploader.vue: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 15 | 16 | Upload 17 | 18 | 19 | 20 | 82 | 98 | -------------------------------------------------------------------------------- /.stylelintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'stylelint-config-standard', 4 | 'stylelint-config-prettier', 5 | 'stylelint-config-recommended-less', 6 | 'stylelint-config-standard-vue' 7 | ], 8 | plugins: ['stylelint-order'], 9 | // 不同格式的文件指定自定义语法 10 | overrides: [ 11 | { 12 | files: ['**/*.(less|css|vue|html)'], 13 | customSyntax: 'postcss-less' 14 | }, 15 | { 16 | files: ['**/*.(html|vue)'], 17 | customSyntax: 'postcss-html' 18 | } 19 | ], 20 | ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts', '**/*.json', '**/*.md', '**/*.yaml'], 21 | rules: { 22 | 'no-descending-specificity': null, // 禁止在具有较高优先级的选择器后出现被其覆盖的较低优先级的选择器 23 | 'selector-pseudo-element-no-unknown': [ 24 | true, 25 | { 26 | ignorePseudoElements: ['v-deep'] 27 | } 28 | ], 29 | 'selector-pseudo-class-no-unknown': [ 30 | true, 31 | { 32 | ignorePseudoClasses: ['deep'] 33 | } 34 | ], 35 | // 指定样式的排序 36 | 'order/properties-order': [ 37 | 'position', 38 | 'top', 39 | 'right', 40 | 'bottom', 41 | 'left', 42 | 'z-index', 43 | 'display', 44 | 'justify-content', 45 | 'align-items', 46 | 'float', 47 | 'clear', 48 | 'overflow', 49 | 'overflow-x', 50 | 'overflow-y', 51 | 'padding', 52 | 'padding-top', 53 | 'padding-right', 54 | 'padding-bottom', 55 | 'padding-left', 56 | 'margin', 57 | 'margin-top', 58 | 'margin-right', 59 | 'margin-bottom', 60 | 'margin-left', 61 | 'width', 62 | 'min-width', 63 | 'max-width', 64 | 'height', 65 | 'min-height', 66 | 'max-height', 67 | 'font-size', 68 | 'font-family', 69 | 'text-align', 70 | 'text-justify', 71 | 'text-indent', 72 | 'text-overflow', 73 | 'text-decoration', 74 | 'white-space', 75 | 'color', 76 | 'background', 77 | 'background-position', 78 | 'background-repeat', 79 | 'background-size', 80 | 'background-color', 81 | 'background-clip', 82 | 'border', 83 | 'border-style', 84 | 'border-width', 85 | 'border-color', 86 | 'border-top-style', 87 | 'border-top-width', 88 | 'border-top-color', 89 | 'border-right-style', 90 | 'border-right-width', 91 | 'border-right-color', 92 | 'border-bottom-style', 93 | 'border-bottom-width', 94 | 'border-bottom-color', 95 | 'border-left-style', 96 | 'border-left-width', 97 | 'border-left-color', 98 | 'border-radius', 99 | 'opacity', 100 | 'filter', 101 | 'list-style', 102 | 'outline', 103 | 'visibility', 104 | 'box-shadow', 105 | 'text-shadow', 106 | 'resize', 107 | 'transition' 108 | ] 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/layouts/default-layout.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | {{ item.name }} 13 | 14 | 15 | 16 | 17 | 18 | 19 | {{ router.currentRoute.value.meta.name }} 20 | 21 | 退出登陆 22 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 91 | 114 | -------------------------------------------------------------------------------- /src/components/form.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 36 | 37 | 38 | { 45 | handleChange(info, item.field) 46 | } 47 | " 48 | > 49 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 109 | -------------------------------------------------------------------------------- /src/views/login/login.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | flygoose 博客管理系统 5 | 14 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 41 | 登陆 42 | 43 | 44 | 45 | 46 | 47 | 48 | 97 | 133 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flygoose-blog-admin", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "vite build", 8 | "build:dev": "vite build --mode development", 9 | "build:prod": "vite build --mode production", 10 | "preview": "vite preview", 11 | "test:unit": "vitest", 12 | "test:e2e": "start-server-and-test preview http://localhost:4173 'cypress run --e2e'", 13 | "test:e2e:dev": "start-server-and-test 'vite dev --port 4173' http://localhost:4173 'cypress open --e2e'", 14 | "build-only": "vite build", 15 | "type-check": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false", 16 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", 17 | "format": "prettier --write src/", 18 | "prepare": "husky install", 19 | "lint:fix": "eslint --ext \".ts,.js,.vue\" --ignore-path .gitignore . --fix", 20 | "precommit": "lint-staged", 21 | "commit": "cz-customizable", 22 | "preinstall": "npx only-allow pnpm" 23 | }, 24 | "dependencies": { 25 | "@ant-design/icons-vue": "^6.1.0", 26 | "@kangc/v-md-editor": "^2.3.15", 27 | "@types/node": "^18.14.6", 28 | "ant-design-vue": "^3.2.15", 29 | "axios": "^1.3.4", 30 | "dayjs": "^1.11.7", 31 | "js-md5": "^0.8.3", 32 | "lodash.omit": "^4.5.0", 33 | "marked": "^1.2.7", 34 | "normalize.css": "^8.0.1", 35 | "pinia": "^2.0.32", 36 | "pinia-plugin-persistedstate": "^3.1.0", 37 | "prismjs": "^1.29.0", 38 | "vue": "^3.2.47", 39 | "vue-router": "^4.1.6", 40 | "vue3-colorpicker": "^2.1.2" 41 | }, 42 | "devDependencies": { 43 | "@commitlint/cli": "^17.4.4", 44 | "@commitlint/config-conventional": "^17.4.4", 45 | "@rushstack/eslint-patch": "^1.2.0", 46 | "@types/jsdom": "^21.1.0", 47 | "@types/lodash.omit": "^4.5.7", 48 | "@vitejs/plugin-vue": "^4.0.0", 49 | "@vitejs/plugin-vue-jsx": "^3.0.0", 50 | "@vue/eslint-config-prettier": "^7.1.0", 51 | "@vue/eslint-config-typescript": "^11.0.2", 52 | "@vue/test-utils": "^2.3.0", 53 | "@vue/tsconfig": "^0.1.3", 54 | "commitizen": "^4.3.0", 55 | "commitlint-config-gitmoji": "^2.3.1", 56 | "cypress": "^12.7.0", 57 | "cz-conventional-changelog": "^3.3.0", 58 | "cz-customizable": "^7.0.0", 59 | "eslint": "^8.34.0", 60 | "eslint-plugin-cypress": "^2.12.1", 61 | "eslint-plugin-vue": "^9.9.0", 62 | "husky": "^8.0.0", 63 | "i": "^0.3.7", 64 | "jsdom": "^21.1.0", 65 | "less": "^4.1.3", 66 | "less-loader": "^11.1.0", 67 | "lint-staged": "^13.1.2", 68 | "npm-run-all": "^4.1.5", 69 | "postcss": "^8.4.21", 70 | "postcss-html": "^1.5.0", 71 | "postcss-less": "^6.0.0", 72 | "prettier": "^2.8.4", 73 | "start-server-and-test": "^2.0.0", 74 | "stylelint": "^15.2.0", 75 | "stylelint-config-prettier": "^9.0.5", 76 | "stylelint-config-recommended-less": "^1.0.4", 77 | "stylelint-config-standard": "^30.0.1", 78 | "stylelint-config-standard-vue": "^1.0.0", 79 | "stylelint-less": "^1.0.6", 80 | "stylelint-order": "^6.0.2", 81 | "typescript": "~4.8.4", 82 | "unplugin-vue-components": "^0.24.0", 83 | "vite": "^4.1.4", 84 | "vitest": "^0.29.1", 85 | "vue-tsc": "^1.2.0" 86 | }, 87 | "config": { 88 | "cz-commitizen": { 89 | "path": "node_modules/cz-customizable" 90 | } 91 | }, 92 | "husky": { 93 | "hooks": { 94 | "pre-commit": "lint-staged" 95 | } 96 | }, 97 | "lint-staged": { 98 | "*.{js,ts,vue}": [ 99 | "eslint --fix", 100 | "git add" 101 | ] 102 | }, 103 | "volta": { 104 | "node": "16.19.1" 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hellowolrd-blog-new 2 | 3 | This template should help get you started developing with Vue 3 in Vite. 4 | 5 | ## Recommended IDE Setup 6 | 7 | [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin). 8 | 9 | ## Type Support for `.vue` Imports in TS 10 | 11 | TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types. 12 | 13 | If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps: 14 | 15 | 1. Disable the built-in TypeScript Extension 16 | 1. Run `Extensions: Show Built-in Extensions` from VSCode's command palette 17 | 2. Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)` 18 | 2. Reload the VSCode window by running `Developer: Reload Window` from the command palette. 19 | 20 | ## Customize configuration 21 | 22 | See [Vite Configuration Reference](https://vitejs.dev/config/). 23 | 24 | ## 飞鹅周边 (可访问一下地址访问体验) 25 | 26 | ##### 飞鹅官网 - flygoose.helloworld.net 27 | 28 | ##### 飞鹅博客 - flygoose-blog.helloworld.net 29 | 30 | ##### 飞鹅管理系统 - flygoose-admin.helloworld.net 31 | 32 | ##### helloworld 开发者社区 - www.helloworld.net 33 | 34 | ## 部署相关 35 | 36 | #### 环境安装 37 | 38 | 确保你已经全局安装了 Node.js 和 npm / yarn,并且版本满足 Vue3 项目需求。node 版本 16+即可。 39 | 40 | ###### yarn 安装 41 | 42 | ```sh 43 | npm install --g yarn 44 | ``` 45 | 46 | 1. 安装 Node.js 47 | 1. 访问 Node.js 官方网站(https://nodejs.org/en/download/) 来进行下载。 48 | 2. 确认安装成功后,通过命令行 `node -v`来检查 Node.js 和 npm 的版本信息。 49 | 2. 克隆项目到本地 50 | 51 | #### 依赖安装 + 项目启动 + 项目打包 52 | 53 | 在项目根目录下使用 cmd 终端 or vscode 编辑器终端进行初始化依赖安装、启动、打包。 54 | 55 | ##### 依赖安装 56 | 57 | ```sh 58 | pnpm install / pnpm install / yarn install 59 | ``` 60 | 61 | 在此建议使用 pnpm 进行安装,如果没有安装 pnpm,可以再 package.json 文件中将如下代码注释 62 | 63 | ```sh 64 | "preinstall": "npx only-allow pnpm" 65 | ``` 66 | 67 | ##### 项目启动 68 | 69 | ```sh 70 | npm run dev / yarn run dev 71 | ``` 72 | 73 | ##### 项目接口请求地址修改 74 | 75 | 在根目录找到 `.env.development` 文件 76 | 77 | ##### (注:在此项目中,`.env.development`、`.env.production` 文件是用来区分不同环境,具体可根据项目需求进行修改) 78 | 79 | ```sh 80 | // 接口请求地址 81 | VITE_API_BASE_URL = "https://test-blog.helloworld.net/api/" 82 | // 图片地址 83 | VITE_API_BASE_UPLOAD_IMG_URL = "https://test-blog.helloworld.net" 84 | ``` 85 | 86 | ##### 项目打包(具体可查看 package.json 文件中的 scripts 配置,根据项目需求进行修改) 87 | 88 | ```sh 89 | npm run build:prod // 打包生产环境 90 | npm run build:dev // 打包开发环境 91 | ``` 92 | 93 | ## Project Setup 94 | 95 | ```sh 96 | npm install 97 | ``` 98 | 99 | ### Compile and Hot-Reload for Development 100 | 101 | ```sh 102 | npm run dev 103 | ``` 104 | 105 | ### Type-Check, Compile and Minify for Production 106 | 107 | ```sh 108 | npm run build 109 | ``` 110 | 111 | ### Run Unit Tests with [Vitest](https://vitest.dev/) 112 | 113 | ```sh 114 | npm run test:unit 115 | ``` 116 | 117 | ### Run End-to-End Tests with [Cypress](https://www.cypress.io/) 118 | 119 | ```sh 120 | npm run test:e2e:dev 121 | ``` 122 | 123 | This runs the end-to-end tests against the Vite development server. 124 | It is much faster than the production build. 125 | 126 | But it's still recommended to test the production build with `test:e2e` before deploying (e.g. in CI environments): 127 | 128 | ```sh 129 | npm run build 130 | npm run test:e2e 131 | ``` 132 | 133 | ### Lint with [ESLint](https://eslint.org/) 134 | 135 | ```sh 136 | npm run lint 137 | ``` 138 | 139 | commit your code use pnpm commit 140 | -------------------------------------------------------------------------------- /src/components/table.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{ item.name }} 15 | 16 | 编辑 17 | 18 | 19 | 20 | 21 | 22 | {{ dayjs(record[column.dataIndex]).format('YYYY-MM-DD hh:mm:ss') }} 23 | 24 | 25 | 26 | 30 | {{ 31 | column.enum 32 | ? column.enum[record[column.dataIndex]].name 33 | : record[column.dataIndex] === 1 34 | ? '已上架' 35 | : record[column.dataIndex] === 0 36 | ? '已下架' 37 | : '' 38 | }} 39 | 40 | 41 | 42 | 43 | 44 | 45 | {{ 46 | item ? item : '无' 47 | }} 48 | 49 | 50 | 51 | {{ record[column.dataIndex] }} 52 | 60 | 61 | 62 | 63 | 74 | {{ 75 | column.enum ? column.enum[record[column.dataIndex]].name : record[column.dataIndex] 76 | }} 77 | 78 | 79 | 80 | 81 | 82 | 83 | 108 | 120 | -------------------------------------------------------------------------------- /src/views/blog/createBlog.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 45 | 46 | 47 | 56 | 57 | 58 | {{ 59 | route.query.id ? '保存' : '创建' 60 | }} 61 | 取消 62 | 63 | 64 | 65 | 66 | 141 | 142 | 147 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Shaoli 3 | * @Date: 2023-03-27 22:05:52 4 | * @LastEditors: Shaoli 5 | * @LastEditTime: 2023-03-27 22:59:43 6 | * @Description: 请填写文件描述 7 | */ 8 | import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router' 9 | import Login from '@/views/login/login.vue' 10 | import Dashboard from '@/views/dashboard/dashbord.vue' 11 | import Blog from '@/views/blog/blog.vue' 12 | import CreateBlog from '@/views/blog/createBlog.vue' 13 | import { useUserStoreWithOut } from '@/stores/module/user' 14 | import DefaultLayout from '@/layouts/default-layout.vue' 15 | import WebmasterInfo from '@/views/webmaster-info/webmaster-info.vue' 16 | import WebsiteInfo from '@/views/website-info/website-info.vue' 17 | import Editor from '@/views/editor/index.vue' 18 | import Category from '@/views/category/index.vue' 19 | import Special from '@/views/special/index.vue' 20 | import Section from '@/views/special/section.vue' 21 | import SectionDetail from '@/views/special/sectionDetail.vue' 22 | import LinkList from '@/views/link/index.vue' 23 | import Banner from '@/views/banner/index.vue' 24 | import Notice from '@/views/notice/index.vue' 25 | 26 | const routes: RouteRecordRaw[] = [ 27 | { 28 | path: '/login', 29 | name: 'Login', 30 | component: Login 31 | }, 32 | { 33 | path: '/', 34 | component: DefaultLayout, 35 | redirect: '/dashboard', 36 | children: [ 37 | { 38 | path: '/notice', 39 | name: 'Notice', 40 | component: Notice, 41 | meta: { 42 | name: '公告管理' 43 | } 44 | }, 45 | { 46 | path: '/dashboard', 47 | name: 'Dashboard', 48 | component: Dashboard, 49 | meta: { 50 | name: '仪表盘' 51 | } 52 | }, 53 | { 54 | path: '/blog', 55 | name: 'Blog', 56 | component: Blog, 57 | meta: { 58 | name: '博客' 59 | } 60 | }, 61 | { 62 | path: '/link', 63 | name: 'link', 64 | component: LinkList, 65 | meta: { 66 | name: '友链' 67 | } 68 | }, 69 | { 70 | path: '/createBlog', 71 | name: 'createBlog', 72 | component: CreateBlog, 73 | meta: { 74 | name: '创建博客' 75 | } 76 | }, 77 | { 78 | path: '/editBlog', 79 | name: 'editBlog', 80 | component: CreateBlog, 81 | meta: { 82 | name: '编辑博客' 83 | } 84 | }, 85 | { 86 | path: '/websiteinfo', 87 | name: 'WebsiteInfo', 88 | component: WebsiteInfo, 89 | meta: { 90 | name: '网站信息' 91 | } 92 | }, 93 | { 94 | path: '/webmasterinfo', 95 | name: 'WebmasterInfo', 96 | component: WebmasterInfo, 97 | meta: { 98 | name: '站长信息' 99 | } 100 | }, 101 | { 102 | path: '/editor', 103 | name: 'Editor', 104 | component: Editor, 105 | meta: { 106 | name: '编辑器' 107 | } 108 | }, 109 | { 110 | path: '/category', 111 | name: 'Category', 112 | component: Category, 113 | meta: { 114 | name: '分类' 115 | } 116 | }, 117 | { 118 | path: '/special', 119 | name: 'special', 120 | component: Special, 121 | meta: { 122 | name: '专栏' 123 | } 124 | }, 125 | { 126 | path: '/section', 127 | name: 'section', 128 | component: Section, 129 | meta: { 130 | name: '小节' 131 | } 132 | }, 133 | { 134 | path: '/sectionDetail', 135 | name: 'sectionDetail', 136 | component: SectionDetail, 137 | meta: { 138 | name: '小节详情' 139 | } 140 | }, 141 | { 142 | path: '/banner', 143 | name: 'banner', 144 | component: Banner, 145 | meta: { 146 | name: '轮播图' 147 | } 148 | } 149 | ] 150 | } 151 | ] 152 | 153 | const router = createRouter({ 154 | history: createWebHistory(import.meta.env.BASE_URL), 155 | routes 156 | }) 157 | 158 | router.beforeEach(async (to) => { 159 | if ( 160 | // 检查用户是否已登录 161 | !useUserStoreWithOut().token && 162 | // ❗️ 避免无限重定向 163 | to.name !== 'Login' 164 | ) { 165 | // 将用户重定向到登录页面 166 | return { name: 'Login' } 167 | } 168 | }) 169 | 170 | export default router 171 | -------------------------------------------------------------------------------- /src/views/special/section.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 专栏名称: {{ $route.query.title }} 5 | 6 | 7 | 专栏简介: {{ $route.query.intro }} 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 查询 17 | 18 | 19 | 创建小节 20 | 21 | 28 | 29 | 30 | 31 | 32 | 33 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 183 | 184 | 216 | -------------------------------------------------------------------------------- /src/views/category/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 查询 10 | 11 | 12 | 创建分类 13 | 14 | 15 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 211 | 212 | 219 | -------------------------------------------------------------------------------- /src/views/banner/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 查询 11 | 12 | 13 | 创建 14 | 15 | 22 | 23 | 24 | 25 | 26 | 27 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 206 | 207 | 222 | -------------------------------------------------------------------------------- /src/views/notice/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 查询 11 | 12 | 13 | 创建 14 | 15 | 22 | 23 | 24 | 25 | 26 | 27 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 208 | 209 | 224 | -------------------------------------------------------------------------------- /src/views/special/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 查询 15 | 16 | 17 | 创建专栏 18 | 19 | 26 | 27 | 28 | 29 | 30 | 31 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 229 | 230 | 245 | -------------------------------------------------------------------------------- /src/views/link/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 查询 11 | 12 | 13 | 创建 14 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 231 | 232 | 247 | -------------------------------------------------------------------------------- /src/views/blog/blog.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 查询 15 | 16 | 17 | 创建博客 18 | 19 | 26 | 27 | 28 | 29 | 30 | 31 | 38 | 39 | 40 | 45 | 47 | 48 | 49 | 50 | {{ detailInfo.title }} 51 | 52 | {{ dayjs(detailInfo.createTime).format('YYYY-MM-DD hh:mm:ss') }} 53 | 54 | 55 | {{ dayjs(detailInfo.updateTime).format('YYYY-MM-DD hh:mm:ss') }} 56 | 57 | 58 | {{ dayjs(detailInfo.publishTime).format('YYYY-MM-DD hh:mm:ss') }} 59 | 60 | {{ detailInfo.intro }} 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 223 | 224 | 231 | -------------------------------------------------------------------------------- /src/views/website-info/website-info.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 更改网站信息 6 | 7 | 12 | 13 | {{ websiteInfo.title }} 14 | {{ 15 | websiteInfo.slogan 16 | }} 17 | {{ websiteInfo.intro }} 18 | 21 | {{ 22 | websiteInfo.status === 1 ? '已上架' : websiteInfo.status === 0 ? '已下架' : '' 23 | }} 24 | {{ websiteInfo.copyright }} 25 | 26 | {{ websiteInfo.icp }} 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 232 | 233 | 240 | -------------------------------------------------------------------------------- /src/views/webmaster-info/webmaster-info.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ 4 | dataSource.length > 0 ? '更新站长信息' : '创建站长信息' 5 | }} 6 | 11 | 12 | 15 | {{ webMasterInfo.nicker }} 16 | {{ webMasterInfo.job }} 17 | {{ webMasterInfo.email }} 18 | {{ webMasterInfo.qq }} 19 | {{ webMasterInfo.wechat }} 20 | {{ webMasterInfo.intro }} 21 | {{ 22 | webMasterInfo.slogan 23 | }} 24 | 27 | {{ 28 | webMasterInfo.status === 1 ? '已上架' : webMasterInfo.status === 0 ? '已下架' : '' 29 | }} 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 254 | 255 | 266 | -------------------------------------------------------------------------------- /src/assets/img/login-bg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 21 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | --------------------------------------------------------------------------------