diff --git a/.docker-compose/nginx/conf.d/my.conf b/.docker-compose/nginx/conf.d/my.conf new file mode 100644 index 0000000..28e40c1 --- /dev/null +++ b/.docker-compose/nginx/conf.d/my.conf @@ -0,0 +1,28 @@ +client_max_body_size 100m; +server { + listen 8080; + server_name localhost; + proxy_read_timeout 3600; + proxy_send_timeout 3600; + #charset koi8-r; + #access_log logs/host.access.log main; + + location / { + root /usr/share/nginx/html; + add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0'; + try_files $uri $uri/ /index.html; + } + + location /api { + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + rewrite ^/api/(.*)$ /$1 break; #重写 + proxy_pass http://129.204.101.51:9999; # 设置代理服务器的协议和地址 + } + + location /api/swagger/index.html { + proxy_pass http://129.204.101.51:9999/swagger/index.html; + } + } \ No newline at end of file diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..c6c0864 --- /dev/null +++ b/.env.development @@ -0,0 +1,5 @@ +# 变量必须以 VITE_ 为前缀才能暴露给外部读取 +NODE_ENV = 'development' +VITE_APP_BASE_API = 'https://tools.1024tool.vip/' +VITE_SERVE = "https://tools.1024tool.vip/" +# VITE_SERVE = "http://192.168.210.29:18100/" diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..a092139 --- /dev/null +++ b/.env.production @@ -0,0 +1,5 @@ +# 变量必须以 VITE_ 为前缀才能暴露给外部读取 +NODE_ENV = 'production' +VITE_APP_BASE_API = 'https://tools.1024tool.vip/' +VITE_SERVE = "https://tools.1024tool.vip/" + diff --git a/.env.test b/.env.test new file mode 100644 index 0000000..0f8c01f --- /dev/null +++ b/.env.test @@ -0,0 +1,5 @@ +NODE_ENV = 'development' + +VITE_APP_BASE_API = 'http://129.204.101.51:9999/' +VITE_SERVE = "http://129.204.101.51:9999/" + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..24f3c90 --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* +components.d.ts +node_modules +dist +dist-ssr +*.local +.vite +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +*.zip diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..67b7d4d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,24 @@ +FROM registry.cn-shanghai.aliyuncs.com/server1024/node:16.17.1 AS build-stage + +WORKDIR /mht/ +COPY . . + +# 设置国内源并调试网络问题 +RUN npm config set registry https://registry.npmmirror.com +RUN npm config set network-timeout 600000 + +RUN npm install --legacy-peer-deps +# 确保 vite 已经全局安装 +RUN npm install -g vite + +# 检查 vite 是否安装成功 +RUN vite --version + +# 运行构建命令 +RUN npm run build:prod + +FROM registry.cn-shanghai.aliyuncs.com/server1024/nginx:base AS production-stage + +COPY .docker-compose/nginx/conf.d/my.conf /etc/nginx/conf.d/my.conf +COPY --from=build-stage /mht/dist /usr/share/nginx/html +RUN ls -al /usr/share/nginx/html \ No newline at end of file diff --git a/auto-imports.d.ts b/auto-imports.d.ts new file mode 100644 index 0000000..f5dae85 --- /dev/null +++ b/auto-imports.d.ts @@ -0,0 +1,11 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// noinspection JSUnusedGlobalSymbols +// Generated by unplugin-auto-import +export {} +declare global { + const ElMessage: typeof import('element-plus/es')['ElMessage'] + const ElMessageBox: typeof import('element-plus/es')['ElMessageBox'] + const ElNotification: typeof import('element-plus/es')['ElNotification'] +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..8e49c8a --- /dev/null +++ b/index.html @@ -0,0 +1,19 @@ + + + + + + + + 儿科后台管理 + + + + + + +
+ + + + \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..56c81a7 --- /dev/null +++ b/package.json @@ -0,0 +1,69 @@ +{ + "name": "mihoutao", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite --open", + "build": "vite build", + "build:test": "vite build --mode test", + "build:prod": "vite build --mode production", + "build:no": "vite build", + "preview": "vite preview", + "lint": "eslint src", + "fix": "eslint src --fix", + "format": "prettier --write \"./**/*.{html,vue,ts,js,json,md}\"", + "lint:eslint": "eslint src/**/*.{ts,vue} --cache --fix", + "lint:style": "stylelint src/**/*.{css,scss,vue} --cache --fix" + }, + "dependencies": { + "@element-plus/icons-vue": "2.1.0", + "@wangeditor/editor": "^5.1.23", + "@wangeditor/editor-for-vue": "next", + "axios": "^1.4.0", + "default-passive-events": "^2.0.0", + "element-plus": "^2.8.0", + "mitt": "^3.0.1", + "moment": "^2.29.4", + "nprogress": "^0.2.0", + "pinia": "^2.1.6", + "pinia-plugin-persistedstate": "^3.2.0", + "vue": "^3.3.4", + "vue-router": "^4.2.4" + }, + "devDependencies": { + "@babel/eslint-parser": "^7.22.9", + "@iconify-json/ep": "^1.1.11", + "@typescript-eslint/eslint-plugin": "^6.2.0", + "@typescript-eslint/parser": "^6.2.0", + "@vitejs/plugin-vue": "^4.2.3", + "eslint": "^8.45.0", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-vue": "^9.15.1", + "mockjs": "^1.1.0", + "postcss": "^8.4.27", + "postcss-html": "^1.5.0", + "postcss-pxtorem": "^6.1.0", + "postcss-scss": "^4.0.6", + "sass": "^1.64.1", + "sass-loader": "^13.3.2", + "stylelint": "^15.10.2", + "stylelint-config-prettier": "^9.0.5", + "stylelint-config-recess-order": "^4.3.0", + "stylelint-config-recommended-scss": "^12.0.0", + "stylelint-config-standard": "^34.0.0", + "stylelint-config-standard-scss": "^10.0.0", + "stylelint-config-standard-vue": "^1.0.0", + "stylelint-order": "^6.0.3", + "stylelint-scss": "^5.0.1", + "typescript": "^5.0.2", + "unplugin-auto-import": "^0.16.6", + "unplugin-icons": "^0.16.5", + "unplugin-vue-components": "^0.25.1", + "vite": "^4.4.5", + "vite-plugin-mock": "2.9.6", + "vite-plugin-svg-icons": "^2.0.1", + "vue-tsc": "^1.8.5" + } +} diff --git a/postcss.config.cjs b/postcss.config.cjs new file mode 100644 index 0000000..6944eee --- /dev/null +++ b/postcss.config.cjs @@ -0,0 +1,8 @@ +module.exports = { + // plugins: { + // 'postcss-pxtorem': { + // rootValue: 100, // 设置根元素字体大小,1rem = 16px + // propList: ['*'], // 将所有属性都转换成rem + // }, + // }, + }; \ No newline at end of file diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..9e29126 Binary files /dev/null and b/public/favicon.ico differ diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..36bb8c0 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/src/api/login.ts b/src/api/login.ts new file mode 100644 index 0000000..3b05b41 --- /dev/null +++ b/src/api/login.ts @@ -0,0 +1,91 @@ +import request from '@/utils/request' + + +// 登录 +export const userLogin = (data: any) => { + return request({ + url: `api/customer/login`, + method: 'post', + data + }) +} +// 注册 +export const userRegister = (data: any) => { + return request({ + url: `api/customer/register`, + method: 'post', + data + }) +} + +// 发送邮箱验证码 +export const sendEmail = (data: any) => { + return request({ + url: `api/customer/send_activation_email`, + method: 'post', + data + }) +} + +// 获取用户信息 +export const getUserInfo = () => { + return request({ + url: `api/customer/info`, + method: 'get' + }) +} + +// 激活账号 + +export const verifyCode = (code: any) => { + return request({ + url: `api/customer/verify_activation_email/${code}`, + method: 'get' + }) +} + +// 扫码登录 + +export const getRr = (data) => { + return request({ + url: `api/customer/qr`, + method: 'post', + data + }) +} + +// 轮询登录结果 + +export const getRrLogin = (data) => { + return request({ + url: `api/customer/qr_login`, + method: 'post', + data + }) +} + + +export const getUserSearch = (data) => { + return request({ + url: `api/customer/search`, + method: 'post', + data + }) +} + +export const setUserRecharge = (data) => { + return request({ + url: `api/customer/recharge`, + method: 'post', + data + }) +} + +// 充值记录 +export const rechargeList = (data) => { + return request({ + url: `api/customer/recharge/list`, + method: 'get', + params: data + }) +} \ No newline at end of file diff --git a/src/api/news.ts b/src/api/news.ts new file mode 100644 index 0000000..3b05b41 --- /dev/null +++ b/src/api/news.ts @@ -0,0 +1,91 @@ +import request from '@/utils/request' + + +// 登录 +export const userLogin = (data: any) => { + return request({ + url: `api/customer/login`, + method: 'post', + data + }) +} +// 注册 +export const userRegister = (data: any) => { + return request({ + url: `api/customer/register`, + method: 'post', + data + }) +} + +// 发送邮箱验证码 +export const sendEmail = (data: any) => { + return request({ + url: `api/customer/send_activation_email`, + method: 'post', + data + }) +} + +// 获取用户信息 +export const getUserInfo = () => { + return request({ + url: `api/customer/info`, + method: 'get' + }) +} + +// 激活账号 + +export const verifyCode = (code: any) => { + return request({ + url: `api/customer/verify_activation_email/${code}`, + method: 'get' + }) +} + +// 扫码登录 + +export const getRr = (data) => { + return request({ + url: `api/customer/qr`, + method: 'post', + data + }) +} + +// 轮询登录结果 + +export const getRrLogin = (data) => { + return request({ + url: `api/customer/qr_login`, + method: 'post', + data + }) +} + + +export const getUserSearch = (data) => { + return request({ + url: `api/customer/search`, + method: 'post', + data + }) +} + +export const setUserRecharge = (data) => { + return request({ + url: `api/customer/recharge`, + method: 'post', + data + }) +} + +// 充值记录 +export const rechargeList = (data) => { + return request({ + url: `api/customer/recharge/list`, + method: 'get', + params: data + }) +} \ No newline at end of file diff --git a/src/assets/images/error_images/401.png b/src/assets/images/error_images/401.png new file mode 100644 index 0000000..90bbf6e Binary files /dev/null and b/src/assets/images/error_images/401.png differ diff --git a/src/assets/images/error_images/404.png b/src/assets/images/error_images/404.png new file mode 100644 index 0000000..14fa725 Binary files /dev/null and b/src/assets/images/error_images/404.png differ diff --git a/src/assets/images/error_images/cloud.png b/src/assets/images/error_images/cloud.png new file mode 100644 index 0000000..247c06b Binary files /dev/null and b/src/assets/images/error_images/cloud.png differ diff --git a/src/assets/images/logo.svg b/src/assets/images/logo.svg new file mode 100644 index 0000000..35201ee --- /dev/null +++ b/src/assets/images/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/welcome.png b/src/assets/images/welcome.png new file mode 100644 index 0000000..df138ab Binary files /dev/null and b/src/assets/images/welcome.png differ diff --git a/src/components/SvgIcon/index.vue b/src/components/SvgIcon/index.vue new file mode 100644 index 0000000..bbc6500 --- /dev/null +++ b/src/components/SvgIcon/index.vue @@ -0,0 +1,35 @@ + + + + diff --git a/src/components/index.ts b/src/components/index.ts new file mode 100644 index 0000000..61704b3 --- /dev/null +++ b/src/components/index.ts @@ -0,0 +1,20 @@ +import SvgIcon from './SvgIcon/index.vue' + +import type { App, Component } from 'vue' + +import * as ElementPlusIconsVue from '@element-plus/icons-vue' + +const allGlobalComponent: Component = { SvgIcon } + +export default { + install(app: App) { + Object.keys(allGlobalComponent).forEach((key: string) => { + // 注册为全局组件 + app.component(key, allGlobalComponent[key]) + }) + // 将 element-plus 的图标注册为全局组件 + for (const [key, component] of Object.entries(ElementPlusIconsVue)) { + app.component(key, component) + } + }, +} diff --git a/src/directive/has.ts b/src/directive/has.ts new file mode 100644 index 0000000..9446eee --- /dev/null +++ b/src/directive/has.ts @@ -0,0 +1,15 @@ +// 按钮权限的实现 +import pinia from '@/store' +import useUserStore from '@/store/modules/user' + +export const isHasButton = (app: any) => { + // 自定义指令 + app.directive('has', { + mounted(el: any, options: any) { + const userStore = useUserStore(pinia) + if (!userStore.buttons.includes(options.value)) { + el.parentNode.removeChild(el) + } + } + }) +} \ No newline at end of file diff --git a/src/eventBus/index.ts b/src/eventBus/index.ts new file mode 100644 index 0000000..1a13398 --- /dev/null +++ b/src/eventBus/index.ts @@ -0,0 +1,2 @@ +import mitt from 'mitt' +export default mitt() \ No newline at end of file diff --git a/src/layout/header/index.vue b/src/layout/header/index.vue new file mode 100644 index 0000000..f90e254 --- /dev/null +++ b/src/layout/header/index.vue @@ -0,0 +1,129 @@ + + + + diff --git a/src/layout/home/header.vue b/src/layout/home/header.vue new file mode 100644 index 0000000..9d8c566 --- /dev/null +++ b/src/layout/home/header.vue @@ -0,0 +1,120 @@ + + + + diff --git a/src/layout/home/index.vue b/src/layout/home/index.vue new file mode 100644 index 0000000..682c059 --- /dev/null +++ b/src/layout/home/index.vue @@ -0,0 +1,80 @@ + + + + \ No newline at end of file diff --git a/src/layout/home/menu.vue b/src/layout/home/menu.vue new file mode 100644 index 0000000..a43b65c --- /dev/null +++ b/src/layout/home/menu.vue @@ -0,0 +1,55 @@ + + + \ No newline at end of file diff --git a/src/layout/setting/index.vue b/src/layout/setting/index.vue new file mode 100644 index 0000000..562bb4f --- /dev/null +++ b/src/layout/setting/index.vue @@ -0,0 +1,133 @@ + + + \ No newline at end of file diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..7e67e15 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,40 @@ +import { createApp } from 'vue' +import App from './App.vue' +// import 'virtual:svg-icons-register' +import globalComponent from './components/index' +import { router } from './router' +import pinia from './store' +import ElementPlus from 'element-plus' +import zhCn from 'element-plus/es/locale/lang/zh-cn' +// default-passive-events 是一个轻量级的 JavaScript 库,旨在通过自动为支持 EventListenerOptions 的浏览器设置事件监听器的 { passive: true } 选项来优化滚动性能。这有助于减少触摸和滚轮事件处理时的延迟,从而提升用户体验。 +import 'default-passive-events' + +// 修改主题必须文件 +import 'element-plus/theme-chalk/src/index.scss' + +// 重置样式 +import '@/styles/index.scss' + +// 暗黑模式 +import 'element-plus/theme-chalk/dark/css-vars.css' + +// 自定义指令 +// import { isHasButton } from '@/directive/has.ts' + +// 屏幕自适应 +// import './utils/fitter.js' + + + +const app = createApp(App) +app.use(ElementPlus, { + locale: zhCn, + }) +app.use(globalComponent) +app.use(pinia) +app.use(router) +app.mount('#app') + + + + \ No newline at end of file diff --git a/src/router/index.ts b/src/router/index.ts new file mode 100644 index 0000000..b179c02 --- /dev/null +++ b/src/router/index.ts @@ -0,0 +1,106 @@ +import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router' +// import Nprogress from 'nprogress' +// import 'nprogress/nprogress.css' +import { GET_TOKEN } from '@/utils/token' + + +const constantRoutes = [ + + { + path: '/', + name: 'Layout', + component: () => import('@/layout/home/index.vue'), + redirect: '/user', + meta: { + title: '首页' + }, + children: [ + { + path: '/user', + name: 'user', + component: () => import('@/views/user/index.vue'), + meta: { + title: '患者管理' + }, + }, + { + path: '/news', + name: 'news', + component: () => import('@/views/news/index.vue'), + meta: { + title: '文章管理' + }, + }, + { + path: '/addNew', + name: 'addNew', + component: () => import('@/views/news/addNew.vue'), + meta: { + title: '添加文章' + }, + }, + ], + }, + + { + path: '/login', + name: 'login', + component: () => import('@/views/login/index.vue'), + meta: { + title: '登录', + }, + }, + + { + path: '/:pathMatch(.*)', + name: 'Any', + component: () => import('@/views/404/index.vue'), + meta: { + title: '404', + hidden: true, + icon: '', + }, + } +] + + + +export const router = createRouter({ + history: createWebHistory(), + routes: constantRoutes, + // 切换路由跳转到顶部 + scrollBehavior() { + return { + left: 0, + top: 0 + } + } +}) + +const whiteList = ['/login'] + +// 全局前置守卫 +router.beforeEach(async (to, from, next) => { + // Nprogress.start() + const token = GET_TOKEN() + console.log(token) + if (token) { + if (to.path === '/login') { + next('/index') + } else { + next() + } + } else { + if (whiteList.indexOf(to.path) > -1) { + next() + } else { + next('/login') + } + } +}) + +// 全局后置守卫 +router.afterEach(() => { + // Nprogress.done() +}) + diff --git a/src/store/index.ts b/src/store/index.ts new file mode 100644 index 0000000..4b22a06 --- /dev/null +++ b/src/store/index.ts @@ -0,0 +1,8 @@ +// 大仓库 +import { createPinia } from 'pinia' +import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' + +const pinia = createPinia() +pinia.use(piniaPluginPersistedstate) + +export default pinia diff --git a/src/store/modules/store.ts b/src/store/modules/store.ts new file mode 100644 index 0000000..71db30b --- /dev/null +++ b/src/store/modules/store.ts @@ -0,0 +1,13 @@ +import { defineStore } from "pinia" + +const pinia = defineStore('main', { + state: () => ({ + + }), + + actions: { + + } +}) + +export default pinia \ No newline at end of file diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts new file mode 100644 index 0000000..b95656b --- /dev/null +++ b/src/store/modules/user.ts @@ -0,0 +1,33 @@ +// 用户相关仓库 +import { defineStore } from 'pinia' +import { ref } from 'vue' +import { REMOVE_TOKEN } from '@/utils/token' + +const useUserStore = defineStore( + 'mihoutaoUser', + () => { + const userInfo = ref({}) + const token = ref('') + const getUserInfo = async (data: any) => { + userInfo.value = data + } + + const userLogout = async () => { + //退出登录请求 + REMOVE_TOKEN() + userInfo.value = {} + } + + return { + userInfo, + token, + getUserInfo, + userLogout, + } + }, + { + persist: true + } +) + +export default useUserStore diff --git a/src/styles/element/index.scss b/src/styles/element/index.scss new file mode 100644 index 0000000..153dbed --- /dev/null +++ b/src/styles/element/index.scss @@ -0,0 +1,11 @@ +// styles/element/index.scss +/* 只需要重写你需要的即可 */ +@forward 'element-plus/theme-chalk/src/common/var.scss' with ( + $colors: ( + 'primary': ( + 'base': #5CC4A7, + ), + ), + +); + diff --git a/src/styles/index.scss b/src/styles/index.scss new file mode 100644 index 0000000..c4428f2 --- /dev/null +++ b/src/styles/index.scss @@ -0,0 +1,111 @@ +//引入清除默认样式 +@import './reset.scss'; + +:root { + // --my-shadow: 0 1px 3px 0 rgba(0, 0, 0, .1); + // --my-shadow-large: 0px 0px 3px 5px var(--el-border-color); + // --my-shadow-small: 0px 0px 0px 1px var(--el-border-color); + --my-border-radius: 8px; + --my-border-radius-large: 16px; + --my-border-radius-small: 4px; + --el-fill-color-blank: rgba(240, 245, 245, 1) !important; +} + +//滚动条外观设置 +::-webkit-scrollbar { + width: 5px; + height: 5px; +} + +::-webkit-scrollbar-thumb { + width: 10px; + background-color: #c4cbd7; + border-radius: 10px; +} + +.el-popover.el-popper { + border-radius: var(--my-border-radius) !important; +} + +.el-popper__arrow { + display: none; +} + +.el-popper.is-light { + border: 0; +} + +.pagination-box { + // overflow: hidden; + margin-top: 25px; + text-align: right; + height: 30px; + + .el-pagination { + float: right; + } +} +.el-table { + --el-table-header-text-color: var(--el-color-primary) !important; + --el-table-bg-color: var(--el-bg-color) !important; +} + + +.el-input { + --el-select-input-focus-border-color: none !important; + --el-input-bg-color: rgba(240, 245, 245, 1) !important; + --el-input-focus-border-color: none; + --el-input-border-color: none !important; +} + +.el-input__wrapper.is-focus, +.el-select__wrapper.is-focused, +.el-select__wrapper { + box-shadow: none !important; +} + +.el-textarea, +.el-date-editor, +.el-cascader { + --el-input-focus-border-color: none !important; + --el-input-bg-color: rgba(240, 245, 245, 1) !important; + --el-input-border-color: none !important; +} + +.el-input__wrapper:hover, +.el-textarea__inner:hover, +.el-input-group__append:hover, +.el-input-group__prepend:hover { + box-shadow: none !important; + +} + +.el-input__wrapper.is-focus, +.el-textarea__inner:focus, +.el-date-editor.is-active { + box-shadow: none !important; + +} + +.el-input-group__append, +.el-input-group__prepend { + background: var(--el-input-bg-color) !important; + box-shadow: none !important; + padding: 0 10px !important; +} + + +.el-textarea, +.el-date-editor, +.el-cascader { + --el-input-focus-border-color: none !important; + --el-input-border-color: none !important; +} + +.el-radio{ + --el-radio-input-bg-color: #fff!important; +} + +.el-descriptions__body { + background-color: #fff!important; +} \ No newline at end of file diff --git a/src/styles/reset.scss b/src/styles/reset.scss new file mode 100644 index 0000000..d80fa75 --- /dev/null +++ b/src/styles/reset.scss @@ -0,0 +1,202 @@ +*, +*:after, +*:before { + box-sizing: border-box; + outline: none; + font-family: Avenir, Helvetica, Arial, sans-serif; +} + +html { + height: 100%; + font-size: 16px; + // min-width: 1300px; +} + +html, +body, +div, +span, +applet, +object, +iframe, +h1, +h2, +h3, +h4, +h5, +h6, +p, +blockquote, +pre, +a, +abbr, +acronym, +address, +big, +cite, +code, +del, +dfn, +em, +img, +ins, +kbd, +q, +s, +samp, +small, +strike, +strong, +sub, +sup, +tt, +var, +b, +u, +i, +center, +dl, +dt, +dd, +ol, +ul, +li, +fieldset, +form, +label, +legend, +table, +caption, +tbody, +tfoot, +thead, +tr, +th, +td, +article, +aside, +canvas, +details, +embed, +figure, +figcaption, +footer, +header, +hgroup, +menu, +nav, +output, +ruby, +section, +summary, +time, +mark, +audio, +video { + // font: inherit; + + font-size: 100%; + + margin: 0; + padding: 0; + + vertical-align: baseline; + + border: 0; +} + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +menu, +nav, +section { + display: block; +} + +body { + height: 100%; + line-height: 1; + font-size: 16px; + color: var(--el-text-color-regular); +} + +ol, +ul { + list-style: none; +} + +blockquote, +q { + quotes: none; + + &:before, + &:after { + content: ''; + content: none; + } +} + +sub, +sup { + font-size: 75%; + line-height: 0; + + position: relative; + + vertical-align: baseline; +} + +sup { + top: -.5em; +} + +sub { + bottom: -.25em; +} + +table { + border-spacing: 0; + border-collapse: collapse; +} + +input, +textarea, +button { + // font-family: inhert; + font-family: Avenir, Helvetica, Arial, sans-serif; + font-size: inherit; + + color: inherit; +} + +select { + text-indent: .01px; + text-overflow: ''; + + border: 0; + border-radius: 0; + + -webkit-appearance: none; + -moz-appearance: none; +} + +select::-ms-expand { + display: none; +} + +code, +pre { + font-family: monospace, monospace; + font-size: 1em; +} + + +html.dark { + --el-text-color-regular: #606266; +} \ No newline at end of file diff --git a/src/utils/fitter.js b/src/utils/fitter.js new file mode 100644 index 0000000..d6c93da --- /dev/null +++ b/src/utils/fitter.js @@ -0,0 +1,25 @@ +// 计算公式当前屏幕宽度winWidth除以设计稿宽度1920得到屏幕与设计稿的比例,根据比例设置根元素的fontSize,设计稿的100px等于1rem + +// 获取屏幕宽度 +var winWidth = document.documentElement.offsetWidth || document.body.offsetWidth +// 获取html跟元素 +var oHtml = document.getElementsByTagName('html')[0] +// oHtml.style.fontSize = 100 * winWidth / 1680 + 'px' +if(winWidth > 1680){ + oHtml.style.fontSize = 100 * 1680 / 1680 + 'px' +} else { + oHtml.style.fontSize = 100 * winWidth / 1680 + 'px' +} + +// 页面大小发生变化事重新设置 +window.addEventListener('resize', function() { + var winWidth = document.documentElement.offsetWidth || document.body.offsetWidth + var oHtml = document.getElementsByTagName('html')[0] + + if(winWidth > 1680){ + oHtml.style.fontSize = 100 * 1680 / 1680 + 'px' + } else { + oHtml.style.fontSize = 100 * winWidth / 1680 + 'px' + } + +}) \ No newline at end of file diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..d0efc5b --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,55 @@ +/** + * @description 生成随机数 + * @param {Number} min 最小值 + * @param {Number} max 最大值 + * @returns {Number} + */ +export function randomNum(min: number, max: number): number { + const num = Math.floor(Math.random() * (min - max) + max) + return num +} + +/** + * rgba转# + * @param color 颜色 + */ +export function getHexColor(color: string) { + const values = color + .replace(/rgba?\(/, '') + .replace(/\)/, '') + .replace(/[\s+]/g, '') + .split(',') + const a = parseFloat(values[3]), + r = Math.floor(a * parseInt(values[0]) + (1 - a) * 255), + g = Math.floor(a * parseInt(values[1]) + (1 - a) * 255), + b = Math.floor(a * parseInt(values[2]) + (1 - a) * 255) + return '#' + + ('0' + r.toString(16)).slice(-2) + + ('0' + g.toString(16)).slice(-2) + + ('0' + b.toString(16)).slice(-2) +} + +/** + * 获取Component的方法 + * @param view 路径 + * @param type 路由层数 目前只兼容二级路由 + */ +export function _getViews(view: any, type: any) { + let res; + let modules: any; + if (type == "one") { + modules = import.meta.glob("/src/view/*.vue"); + } else { + modules = import.meta.glob("/src/view/**/*.vue"); + } + for (const path in modules) { + const dir = + type == "one" + ? path.split("view/")[1].split(".vue")[0] + : path.split("view/")[1].split(".vue")[0]; + if (dir === view) { + res = () => modules[path](); + } + } + return res; +}; \ No newline at end of file diff --git a/src/utils/request.ts b/src/utils/request.ts new file mode 100644 index 0000000..b893b26 --- /dev/null +++ b/src/utils/request.ts @@ -0,0 +1,68 @@ +import axios from 'axios' +import { ElNotification } from 'element-plus' +import { GET_TOKEN, REMOVE_TOKEN } from '@/utils/token' +import useUserStore from '@/store/modules/user' +import pinia from '@/store' +//创建axios实例 +const request = axios.create({ + baseURL: import.meta.env.VITE_APP_BASE_API, + timeout: 120000, +}) +// 存储需要取消的请求 +const requestsToCancel = {}; + +//请求拦截器 +request.interceptors.request.use((config: any) => { + if (GET_TOKEN()) { + config.headers.Authorization = GET_TOKEN() + } + // 为每个请求生成一个唯一的cancel token + config.cancelToken = new axios.CancelToken(cancel => { + requestsToCancel[config.url] = cancel; + }); + return config +}) +//响应拦截器 +request.interceptors.response.use( + (response: any) => { + return response.data + + }, + (error: any) => { + //处理网络错误 + if (error.response?.data.code == 20107) { + return + } + // if (error.response?.data.code == 10101) { + // Object.values(requestsToCancel).forEach(cancel => cancel()); + // } + if (error.response?.data.code == 10103) { + Object.values(requestsToCancel).forEach(cancel => cancel()); + const useUser = useUserStore(pinia) + useUser.userInfo = {} + useUser.token = '' + REMOVE_TOKEN() + ElNotification({ + type: 'error', + message: error.response.data.message, + duration: 2000, + onClose: () => { + if(location.pathname != '/index'){ + location.href = '/index' + } + } + }) + return + } + + ElNotification({ + type: 'error', + message: error.response.data.message, + duration: 2000, + }) + + return Promise.reject(error) + }, +) + +export default request diff --git a/src/utils/time.ts b/src/utils/time.ts new file mode 100644 index 0000000..2458a2d --- /dev/null +++ b/src/utils/time.ts @@ -0,0 +1,12 @@ +export const getTime = () => { + const hours = new Date().getHours() + if (hours <= 9) { + return '早上好 🌅' + } else if (hours <= 12) { + return '上午好 🌞' + } else if (hours <= 18) { + return '下午好 ☕️' + } else { + return '晚上好 🌛' + } +} diff --git a/src/utils/token.ts b/src/utils/token.ts new file mode 100644 index 0000000..02fe731 --- /dev/null +++ b/src/utils/token.ts @@ -0,0 +1,18 @@ +export const SET_TOKEN = (token: string) => { + localStorage.setItem('TOKEN', token) +} + + + +export const GET_TOKEN = () => { + return localStorage.getItem('TOKEN') +} + +export const REMOVE_TOKEN = () => { + localStorage.removeItem('TOKEN') +} +export const SET_USER_TYPE = (type) => { + localStorage.removeItem('userType') +} + + diff --git a/src/utils/validate.ts b/src/utils/validate.ts new file mode 100644 index 0000000..bdd4e18 --- /dev/null +++ b/src/utils/validate.ts @@ -0,0 +1,42 @@ + + +// 电话校验 +export const checkPhoneNumber = (rule: any, value: any, callback: any) => { + const reg = /^[1][3456789][0-9]{9}$/ + if ((reg.test(value))) { + callback() + } else { + callback(new Error('validate.please_enter_the_correct_phone_number')) + } +} +// 验证邮箱的规则 +export const checkEmail = (rule: any, value: any, callback: any) => { + // 验证邮箱的正则表达式 + const reg = /^[a-zA-Z0-9]+([\.-_][a-zA-Z0-9]+)*@[a-zA-Z0-9]+([\.-][a-zA-Z0-9]+)*\.[a-zA-Z]{2,}$/ + if (reg.test(value)) { + callback() + } + callback(new Error('请输入正确的的邮箱')) +} +// 密码规则 +export const checkPassword= (rule: any, value: any, callback: any) => { + + const reg = /^[a-zA-Z0-9\W]{6,10}$/ + if (reg.test(value)) { + callback() + } + callback(new Error('你输入6到10位密码, 不能包含特殊符号')) +} + +// 数字验证 +export const checkNumber = (rule: any, value: any, callback: any) => { + if(isNaN(value)){ + callback(new Error('validate.please_enter_a_number_greater_than_0')) + } else { + if (value > 0) { + callback() + } else { + callback(new Error('validate.please_enter_a_number_greater_than_0')) + } + } +} \ No newline at end of file diff --git a/src/views/404/index.vue b/src/views/404/index.vue new file mode 100644 index 0000000..332f814 --- /dev/null +++ b/src/views/404/index.vue @@ -0,0 +1,52 @@ + + + + + \ No newline at end of file diff --git a/src/views/adminPages/AICreation/index.vue b/src/views/adminPages/AICreation/index.vue new file mode 100644 index 0000000..22e9f02 --- /dev/null +++ b/src/views/adminPages/AICreation/index.vue @@ -0,0 +1,769 @@ + + + \ No newline at end of file diff --git a/src/views/adminPages/hot/index.vue b/src/views/adminPages/hot/index.vue new file mode 100644 index 0000000..f8c6324 --- /dev/null +++ b/src/views/adminPages/hot/index.vue @@ -0,0 +1,128 @@ + + + \ No newline at end of file diff --git a/src/views/adminPages/works/index.vue b/src/views/adminPages/works/index.vue new file mode 100644 index 0000000..533dfdb --- /dev/null +++ b/src/views/adminPages/works/index.vue @@ -0,0 +1,8 @@ + + diff --git a/src/views/home/index.vue b/src/views/home/index.vue new file mode 100644 index 0000000..9cf39c8 --- /dev/null +++ b/src/views/home/index.vue @@ -0,0 +1,63 @@ + + + + + \ No newline at end of file diff --git a/src/views/index/index.vue b/src/views/index/index.vue new file mode 100644 index 0000000..2b9bcba --- /dev/null +++ b/src/views/index/index.vue @@ -0,0 +1,88 @@ + + + + + \ No newline at end of file diff --git a/src/views/login/index.vue b/src/views/login/index.vue new file mode 100644 index 0000000..7475031 --- /dev/null +++ b/src/views/login/index.vue @@ -0,0 +1,112 @@ + + + + + \ No newline at end of file diff --git a/src/views/news/addNew.vue b/src/views/news/addNew.vue new file mode 100644 index 0000000..decb156 --- /dev/null +++ b/src/views/news/addNew.vue @@ -0,0 +1,760 @@ + + + \ No newline at end of file diff --git a/src/views/news/index.vue b/src/views/news/index.vue new file mode 100644 index 0000000..c6e47f4 --- /dev/null +++ b/src/views/news/index.vue @@ -0,0 +1,48 @@ + + + + + \ No newline at end of file diff --git a/src/views/user/index.vue b/src/views/user/index.vue new file mode 100644 index 0000000..0928210 --- /dev/null +++ b/src/views/user/index.vue @@ -0,0 +1,40 @@ + + + + + \ No newline at end of file diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a1d5e9f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "node", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + + "baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录 + "paths": { + //路径映射,相对于baseUrl + "@/*": ["src/*"] + }, + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..42872c5 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..d9c07a4 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,90 @@ +import { defineConfig, loadEnv } from 'vite' +import vue from '@vitejs/plugin-vue' + +import path from 'path' + +import AutoImport from 'unplugin-auto-import/vite' +import Components from 'unplugin-vue-components/vite' +import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' + +import Icons from 'unplugin-icons/vite' +import IconsResolver from 'unplugin-icons/resolver' + +import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' + +import { viteMockServe } from 'vite-plugin-mock' +// 引入 fs 模块 +// import fs from 'fs' +// const fs = require('fs'); +// https://vitejs.dev/config/ +export default defineConfig(({ command, mode }) => { + const env = loadEnv(mode, process.cwd()) + + return { + plugins: [ + vue(), + + AutoImport({ + resolvers: [ + ElementPlusResolver(), + IconsResolver({ + prefix: 'Icon', + }), + ], + }), + Components({ + resolvers: [ + ElementPlusResolver({importStyle:"sass"}), + IconsResolver({ + enabledCollections: ['ep'], + }), + ], + }), + Icons({ + autoInstall: true, + }), + createSvgIconsPlugin({ + // Specify the icon folder to be cached + iconDirs: [path.resolve(process.cwd(), 'src/assets/icons/default'), path.resolve(process.cwd(), 'src/assets/icons/bosch')], + // Specify symbolId format + symbolId: 'icon-[dir]-[name]', + }), + viteMockServe({ + localEnabled: command === 'serve', + }), + ], + resolve: { + alias: { + '@': path.resolve('./src'), // 相对路径别名配置,使用 @ 代替 src + }, + }, + css: { //配置全局scss + preprocessorOptions: { + scss: { + quietDeps: true, + logger: { + warn: () => {} + }, + javascriptEnabled: true, + // additionalData: `@use "@/styles/element/index.scss" as *;`, + }, + }, + }, + // 代理跨域 + server: { + // https: { + // key: fs.readFileSync('./cert.key'), // 证书密钥路径 + // cert: fs.readFileSync('./cert.crt') // 证书文件路径 + // }, + host: '0.0.0.0', + proxy: { + [env.VITE_APP_BASE_API]: { + target: env.VITE_SERVE, + changeOrigin: true, + rewrite: (path) => + path.replace(new RegExp(`^${env.VITE_APP_BASE_API}`), ''), + }, + }, + }, + } +})