初始化
|
|
@ -0,0 +1,7 @@
|
|||
#基础API 绝对的
|
||||
VITE_BASE_URL_API = 'http://192.168.2.9:6500'
|
||||
VITE_BASE_URL_DRAW_API = 'http://192.168.2.9:6555'
|
||||
#当前IP 相对的
|
||||
VITE_BASE_CURRENT_API = '.'
|
||||
#开发环境
|
||||
VITE_ENV = 'development'
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
#基础API 绝对的
|
||||
VITE_BASE_URL_API = 'http://192.168.2.9:6500'
|
||||
VITE_BASE_URL_DRAW_API = 'http://192.168.2.9:6555'
|
||||
#当前IP 相对的
|
||||
VITE_BASE_CURRENT_API = '.'
|
||||
#生产环境
|
||||
VITE_ENV = 'production'
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
#基础API 绝对的
|
||||
VITE_BASE_URL_API = 'http://192.168.2.9:6500'
|
||||
VITE_BASE_URL_DRAW_API = 'http://192.168.2.9:6555'
|
||||
#当前IP 相对的
|
||||
VITE_BASE_CURRENT_API = '.'
|
||||
#开发环境
|
||||
VITE_ENV = 'test'
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
es6: true,
|
||||
// 使用setup语法糖
|
||||
'vue/setup-compiler-macros': true,
|
||||
},
|
||||
extends: ['airbnb-base', 'plugin:import/recommended', 'plugin:vue/vue3-essential', 'plugin:vue/vue3-recommended', 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'],
|
||||
parser: 'vue-eslint-parser',
|
||||
parserOptions: {
|
||||
parser: '@typescript-eslint/parser',
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['vue', '@typescript-eslint'],
|
||||
rules: {
|
||||
'import/extensions': [2, 'never', { 'web.js': 'never', json: 'never' }],
|
||||
'@typescript-eslint/no-explicit-any': ['off'],
|
||||
'import/no-extraneous-dependencies': [2, { devDependencies: true }],
|
||||
'import/no-unresolved': 'off',
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
'prettier/prettier': 'off',
|
||||
singleQuote:false,
|
||||
semi:'never',
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
eqeqeq: 'off', // 要求使用 === 和 !==
|
||||
indent: ['error', 2, { SwitchCase: 1 }], // 缩进风格
|
||||
'newline-after-var': 'error', // 变量声明后是否需要空一行
|
||||
'no-eq-null': 'error', // 禁止对null使用==或!=运算符
|
||||
'no-eval': 'warn', // 禁止使用eval
|
||||
'no-var': 'error',
|
||||
'no-multi-spaces': 'warn', // 不能用多余的空格
|
||||
'no-trailing-spaces': 'warn', // 一行结束后面不要有空格
|
||||
'no-undef': 'warn', // 不能有未定义的变量
|
||||
'semi-spacing': 'error', // 强制分号后面有空格
|
||||
'@typescript-eslint/semi': 'error',
|
||||
quotes: ['error', 'single'],
|
||||
'arrow-spacing': 'error', // 要求箭头函数的=>前后有空格
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
name: 部署开发环境
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
|
||||
env:
|
||||
IMAGE_NAME: ${{ gitea.repository_owner }}/usercenter.web
|
||||
PUBLISH_PATH: usercenter.web
|
||||
PUBLISH_HOST: 10.255.255.44
|
||||
PUBLISH_PORT: 22
|
||||
PUBLISH_REPLICAS: 1 #启动副本数量
|
||||
|
||||
jobs:
|
||||
job1:
|
||||
name: 编译发布
|
||||
runs-on: linux
|
||||
container:
|
||||
image: node:16-bullseye
|
||||
volumes:
|
||||
#挂载缓存
|
||||
- /opt/.runner/node_modules:/cache
|
||||
|
||||
steps:
|
||||
- name: 下载源码
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: 使用缓存
|
||||
uses: actions/cache@master
|
||||
with:
|
||||
mount: |
|
||||
node_modules
|
||||
|
||||
- name: 编译项目
|
||||
run: |
|
||||
echo =========构建项目开始========
|
||||
rm -rf package-lock.json #删除文件
|
||||
npm config set registry https://registry.npmmirror.com/
|
||||
npm install
|
||||
npm run build:dev
|
||||
echo =========构建项目结束========
|
||||
|
||||
- name: 打包上传镜像
|
||||
uses: actions/docker@master
|
||||
with:
|
||||
registry: ${{ secrets.DOCKER_REGISTRY }}
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
image: ${{ env.IMAGE_NAME }}
|
||||
context: .
|
||||
tags: latest,${{ gitea.ref_name }}
|
||||
|
||||
- name: 生成环境变量文件
|
||||
env:
|
||||
env_file: |
|
||||
REGISTRY=${{ secrets.DOCKER_REGISTRY }}
|
||||
OWNER=${{ gitea.repository_owner }}
|
||||
TAG=${{ gitea.ref_name }}
|
||||
REPLICAS=${{ env.PUBLISH_REPLICAS }}
|
||||
run: |
|
||||
echo "$env_file">.env
|
||||
|
||||
- name: 复制发布脚本
|
||||
uses: docker://appleboy/drone-scp
|
||||
with:
|
||||
host: ${{ env.PUBLISH_HOST }}
|
||||
port: ${{ env.PUBLISH_PORT }}
|
||||
username: ${{ secrets.PUBLISH_USER_NAME }}
|
||||
password: ${{ secrets.PUBLISH_PASSWORD }}
|
||||
target: ${{ env.PUBLISH_PATH }}
|
||||
source: docker-swarm.yaml,.env
|
||||
|
||||
- name: 部署到服务器
|
||||
uses: docker://appleboy/drone-ssh
|
||||
with:
|
||||
host: ${{ env.PUBLISH_HOST }}
|
||||
port: ${{ env.PUBLISH_PORT }}
|
||||
username: ${{ secrets.PUBLISH_USER_NAME }}
|
||||
password: ${{ secrets.PUBLISH_PASSWORD }}
|
||||
script: |
|
||||
|
||||
cd ${{ env.PUBLISH_PATH }}
|
||||
|
||||
[ -f .env ] && export $(sed '/^#/d' .env)
|
||||
|
||||
docker stack deploy -c docker-swarm.yaml --with-registry-auth mk
|
||||
|
||||
- name: 发送构建通知
|
||||
if: ${{ always() }}
|
||||
uses: actions/webhook@master
|
||||
with:
|
||||
urls: https://oapi.dingtalk.com/robot/send?access_token=6ddafcada8f44f4bad4a7314c4d9bd19a895ded0a1ba1afdaff5dd01a5af6781
|
||||
content_type: application/json
|
||||
template: |
|
||||
{
|
||||
"msgtype": "markdown",
|
||||
"markdown": {
|
||||
"title":"项目部署通知",
|
||||
"text": "${{ job.status == 'success' && '✅' || '❌' }}**${{ gitea.repository }}**\n
|
||||
>**构建结果**: ${{ job.status }}
|
||||
>**构建详情**: [点击查看](${{ gitea.server_url }}/${{ gitea.repository }}/actions/runs/${{ gitea.run_number }})
|
||||
>**代码分支**: ${{ gitea.ref_name }}
|
||||
>**提交标识**: ${{ gitea.sha }}
|
||||
>**提交发起**: ${{ gitea.actor }}
|
||||
>**提交信息**: ${{ gitea.event.head_commit.message }}\n
|
||||
"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
name: 部署生产环境
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
env:
|
||||
IMAGE_NAME: ${{ gitea.repository_owner }}/usercenter.web
|
||||
PUBLISH_PATH: usercenter.web
|
||||
PUBLISH_HOST: 10.255.255.74
|
||||
PUBLISH_PORT: 10022
|
||||
PUBLISH_REPLICAS: 1 #启动副本数量
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: 发布版本
|
||||
runs-on: linux
|
||||
outputs:
|
||||
version: ${{ steps.publish_version.outputs.version }}
|
||||
|
||||
steps:
|
||||
- name: 下载源码
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: 发布版本
|
||||
id: publish_version
|
||||
uses: actions/auto-release@master
|
||||
|
||||
job1:
|
||||
name: 编译发布
|
||||
runs-on: linux
|
||||
needs: release
|
||||
steps:
|
||||
- name: 下载源码
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: 下载源码
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: 使用缓存
|
||||
uses: actions/cache@master
|
||||
with:
|
||||
mount: |
|
||||
node_modules
|
||||
|
||||
- name: 编译项目
|
||||
shell: bash
|
||||
run: |
|
||||
echo =========构建项目开始========
|
||||
rm -rf package-lock.json #删除文件
|
||||
npm config set registry https://registry.npmmirror.com/
|
||||
npm install
|
||||
npm run build
|
||||
echo =========构建项目结束========
|
||||
|
||||
- name: 打包上传镜像
|
||||
uses: actions/docker@master
|
||||
with:
|
||||
registry: ${{ secrets.DOCKER_REGISTRY }}
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
image: ${{ env.IMAGE_NAME }}
|
||||
context: .
|
||||
tags: latest,${{ gitea.ref_name }},${{ needs.release.outputs.version }}
|
||||
|
||||
- name: 生成环境变量文件
|
||||
env:
|
||||
env_file: |
|
||||
REGISTRY=${{ secrets.DOCKER_REGISTRY }}
|
||||
OWNER=${{ gitea.repository_owner }}
|
||||
TAG=${{ needs.release.outputs.version }}
|
||||
REPLICAS=${{ env.PUBLISH_REPLICAS }}
|
||||
run: |
|
||||
echo "$env_file">.env
|
||||
|
||||
- name: 复制发布脚本
|
||||
uses: docker://appleboy/drone-scp
|
||||
with:
|
||||
host: ${{ env.PUBLISH_HOST }}
|
||||
port: ${{ env.PUBLISH_PORT }}
|
||||
username: ${{ secrets.PUBLISH_USER_NAME }}
|
||||
password: ${{ secrets.PUBLISH_PASSWORD }}
|
||||
target: ${{ env.PUBLISH_PATH }}
|
||||
source: docker-swarm.yaml,.env
|
||||
|
||||
- name: 部署到服务器
|
||||
uses: docker://appleboy/drone-ssh
|
||||
with:
|
||||
host: ${{ env.PUBLISH_HOST }}
|
||||
port: ${{ env.PUBLISH_PORT }}
|
||||
username: ${{ secrets.PUBLISH_USER_NAME }}
|
||||
password: ${{ secrets.PUBLISH_PASSWORD }}
|
||||
script: |
|
||||
|
||||
cd ${{ env.PUBLISH_PATH }}
|
||||
|
||||
[ -f .env ] && export $(sed '/^#/d' .env)
|
||||
|
||||
docker stack deploy -c docker-swarm.yaml --with-registry-auth mk
|
||||
|
||||
- name: 发送构建通知
|
||||
if: ${{ always() }}
|
||||
uses: actions/webhook@master
|
||||
with:
|
||||
urls: https://oapi.dingtalk.com/robot/send?access_token=6ddafcada8f44f4bad4a7314c4d9bd19a895ded0a1ba1afdaff5dd01a5af6781
|
||||
content_type: application/json
|
||||
template: |
|
||||
{
|
||||
"msgtype": "markdown",
|
||||
"markdown": {
|
||||
"title":"项目部署通知",
|
||||
"text": "${{ job.status == 'success' && '✅' || '❌' }}**${{ gitea.repository }}**\n
|
||||
>**构建结果**: ${{ job.status }}
|
||||
>**构建详情**: [点击查看](${{ gitea.server_url }}/${{ gitea.repository }}/actions/runs/${{ gitea.run_number }})
|
||||
>**代码分支**: ${{ gitea.ref_name }}
|
||||
>**发布版本**: ${{ needs.release.outputs.version }}
|
||||
>**提交标识**: ${{ gitea.sha }}
|
||||
>**提交发起**: ${{ gitea.actor }}
|
||||
>**提交信息**: ${{ gitea.event.head_commit.message }}\n
|
||||
"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
name: 部署测试环境
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- staging
|
||||
|
||||
env:
|
||||
IMAGE_NAME: ${{ gitea.repository_owner }}/usercenter.web
|
||||
PUBLISH_PATH: usercenter.web
|
||||
PUBLISH_HOST: 10.255.255.3
|
||||
PUBLISH_PORT: 22
|
||||
PUBLISH_REPLICAS: 1 #启动副本数量
|
||||
|
||||
jobs:
|
||||
job1:
|
||||
name: 编译发布
|
||||
runs-on: linux
|
||||
container:
|
||||
image: node:16-bullseye
|
||||
volumes:
|
||||
#挂载缓存
|
||||
- /opt/.runner/node_modules:/cache
|
||||
|
||||
steps:
|
||||
- name: 下载源码
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: 使用缓存
|
||||
uses: actions/cache@master
|
||||
with:
|
||||
mount: |
|
||||
node_modules
|
||||
|
||||
- name: 编译项目
|
||||
shell: bash
|
||||
run: |
|
||||
echo =========构建项目开始========
|
||||
rm -rf package-lock.json #删除文件
|
||||
npm config set registry https://registry.npmmirror.com/
|
||||
npm install
|
||||
npm run build
|
||||
echo =========构建项目结束========
|
||||
|
||||
- name: 打包上传镜像
|
||||
uses: actions/docker@master
|
||||
with:
|
||||
registry: ${{ secrets.DOCKER_REGISTRY }}
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
image: ${{ env.IMAGE_NAME }}
|
||||
context: .
|
||||
tags: latest,${{ gitea.ref_name }}
|
||||
|
||||
- name: 生成环境变量文件
|
||||
env:
|
||||
env_file: |
|
||||
REGISTRY=${{ secrets.DOCKER_REGISTRY }}
|
||||
OWNER=${{ gitea.repository_owner }}
|
||||
TAG=${{ gitea.ref_name }}
|
||||
REPLICAS=${{ env.PUBLISH_REPLICAS }}
|
||||
run: |
|
||||
echo "$env_file">.env
|
||||
|
||||
- name: 复制发布脚本
|
||||
uses: docker://appleboy/drone-scp
|
||||
with:
|
||||
host: ${{ env.PUBLISH_HOST }}
|
||||
port: ${{ env.PUBLISH_PORT }}
|
||||
username: ${{ secrets.PUBLISH_USER_NAME }}
|
||||
password: ${{ secrets.PUBLISH_PASSWORD }}
|
||||
target: ${{ env.PUBLISH_PATH }}
|
||||
source: docker-swarm.yaml,.env
|
||||
|
||||
- name: 部署到服务器
|
||||
uses: docker://appleboy/drone-ssh
|
||||
with:
|
||||
host: ${{ env.PUBLISH_HOST }}
|
||||
port: ${{ env.PUBLISH_PORT }}
|
||||
username: ${{ secrets.PUBLISH_USER_NAME }}
|
||||
password: ${{ secrets.PUBLISH_PASSWORD }}
|
||||
script: |
|
||||
|
||||
cd ${{ env.PUBLISH_PATH }}
|
||||
|
||||
[ -f .env ] && export $(sed '/^#/d' .env)
|
||||
|
||||
docker stack deploy -c docker-swarm.yaml --with-registry-auth mk
|
||||
|
||||
- name: 发送构建通知
|
||||
if: ${{ always() }}
|
||||
uses: actions/webhook@master
|
||||
with:
|
||||
urls: https://oapi.dingtalk.com/robot/send?access_token=6ddafcada8f44f4bad4a7314c4d9bd19a895ded0a1ba1afdaff5dd01a5af6781
|
||||
content_type: application/json
|
||||
template: |
|
||||
{
|
||||
"msgtype": "markdown",
|
||||
"markdown": {
|
||||
"title":"项目部署通知",
|
||||
"text": "${{ job.status == 'success' && '✅' || '❌' }}**${{ gitea.repository }}**\n
|
||||
>**构建结果**: ${{ job.status }}
|
||||
>**构建详情**: [点击查看](${{ gitea.server_url }}/${{ gitea.repository }}/actions/runs/${{ gitea.run_number }})
|
||||
>**代码分支**: ${{ gitea.ref_name }}
|
||||
>**提交标识**: ${{ gitea.sha }}
|
||||
>**提交发起**: ${{ gitea.actor }}
|
||||
>**提交信息**: ${{ gitea.event.head_commit.message }}\n
|
||||
"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
name: 部署生产环境
|
||||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
|
||||
env:
|
||||
IMAGE_NAME: ${{ gitea.repository_owner }}/usercenter.web
|
||||
PUBLISH_PATH: usercenter.web
|
||||
PUBLISH_HOST: 10.255.255.74
|
||||
PUBLISH_PORT: 10022
|
||||
PUBLISH_REPLICAS: 1 #启动副本数量
|
||||
|
||||
jobs:
|
||||
job1:
|
||||
name: 编译发布
|
||||
runs-on: linux
|
||||
container:
|
||||
image: node:16-bullseye
|
||||
volumes:
|
||||
#挂载缓存
|
||||
- /opt/.runner/node_modules:/cache
|
||||
|
||||
steps:
|
||||
- name: 下载源码
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: 使用缓存
|
||||
uses: actions/cache@master
|
||||
with:
|
||||
mount: |
|
||||
node_modules
|
||||
|
||||
- name: 编译项目
|
||||
shell: bash
|
||||
run: |
|
||||
echo =========构建项目开始========
|
||||
rm -rf package-lock.json #删除文件
|
||||
npm config set registry https://registry.npmmirror.com/
|
||||
npm install
|
||||
npm run build
|
||||
echo =========构建项目结束========
|
||||
|
||||
- name: 打包上传镜像
|
||||
uses: actions/docker@master
|
||||
with:
|
||||
registry: ${{ secrets.DOCKER_REGISTRY }}
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
image: ${{ env.IMAGE_NAME }}
|
||||
context: .
|
||||
tags: latest,${{ gitea.ref_name }}
|
||||
|
||||
- name: 生成环境变量文件
|
||||
env:
|
||||
env_file: |
|
||||
REGISTRY=${{ secrets.DOCKER_REGISTRY }}
|
||||
OWNER=${{ gitea.repository_owner }}
|
||||
TAG=${{ gitea.ref_name }}
|
||||
REPLICAS=${{ env.PUBLISH_REPLICAS }}
|
||||
run: |
|
||||
echo "$env_file">.env
|
||||
|
||||
- name: 复制发布脚本
|
||||
uses: docker://appleboy/drone-scp
|
||||
with:
|
||||
host: ${{ env.PUBLISH_HOST }}
|
||||
port: ${{ env.PUBLISH_PORT }}
|
||||
username: ${{ secrets.PUBLISH_USER_NAME }}
|
||||
password: ${{ secrets.PUBLISH_PASSWORD }}
|
||||
target: ${{ env.PUBLISH_PATH }}
|
||||
source: docker-swarm.yaml,.env
|
||||
|
||||
- name: 部署到服务器
|
||||
uses: docker://appleboy/drone-ssh
|
||||
with:
|
||||
host: ${{ env.PUBLISH_HOST }}
|
||||
port: ${{ env.PUBLISH_PORT }}
|
||||
username: ${{ secrets.PUBLISH_USER_NAME }}
|
||||
password: ${{ secrets.PUBLISH_PASSWORD }}
|
||||
script: |
|
||||
|
||||
cd ${{ env.PUBLISH_PATH }}
|
||||
|
||||
[ -f .env ] && export $(sed '/^#/d' .env)
|
||||
|
||||
docker stack deploy -c docker-swarm.yaml --with-registry-auth mk
|
||||
|
||||
- name: 发送构建通知
|
||||
if: ${{ always() }}
|
||||
uses: actions/webhook@master
|
||||
with:
|
||||
urls: https://oapi.dingtalk.com/robot/send?access_token=6ddafcada8f44f4bad4a7314c4d9bd19a895ded0a1ba1afdaff5dd01a5af6781
|
||||
content_type: application/json
|
||||
template: |
|
||||
{
|
||||
"msgtype": "markdown",
|
||||
"markdown": {
|
||||
"title":"项目部署通知",
|
||||
"text": "${{ job.status == 'success' && '✅' || '❌' }}**${{ gitea.repository }}**\n
|
||||
>**构建结果**: ${{ job.status }}
|
||||
>**构建详情**: [点击查看](${{ gitea.server_url }}/${{ gitea.repository }}/actions/runs/${{ gitea.run_number }})
|
||||
>**代码分支**: ${{ gitea.ref_name }}
|
||||
>**提交标识**: ${{ gitea.sha }}
|
||||
>**提交发起**: ${{ gitea.actor }}
|
||||
>**提交信息**: ${{ gitea.event.release.target_commitish }}\n
|
||||
"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"tabWidth": 2,
|
||||
"printWidth": 200,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"bracketSpacing": true
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
FROM nginx:alpine
|
||||
|
||||
COPY ./dist /usr/share/nginx/html
|
||||
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
19
README.md
|
|
@ -1 +1,18 @@
|
|||
1
|
||||
# Vue 3 + TypeScript + Vite
|
||||
|
||||
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
- [VS Code](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).
|
||||
|
||||
## Type Support For `.vue` Imports in TS
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
1. Disable the built-in TypeScript Extension
|
||||
1. Run `Extensions: Show Built-in Extensions` from VSCode's command palette
|
||||
2. Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
|
||||
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
// Generated by unplugin-auto-import
|
||||
export {}
|
||||
declare global {
|
||||
const ElLoading: typeof import('element-plus/es')['ElLoading']
|
||||
const ElMessage: typeof import('element-plus/es')['ElMessage']
|
||||
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
export {}
|
||||
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElCascader: typeof import('element-plus/es')['ElCascader']
|
||||
ElCheckTag: typeof import('element-plus/es')['ElCheckTag']
|
||||
ElCollapse: typeof import('element-plus/es')['ElCollapse']
|
||||
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
|
||||
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
|
||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||
ElDivider: typeof import('element-plus/es')['ElDivider']
|
||||
ElDrawer: typeof import('element-plus/es')['ElDrawer']
|
||||
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
||||
ElEmpty: typeof import('element-plus/es')['ElEmpty']
|
||||
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
||||
ElOption: typeof import('element-plus/es')['ElOption']
|
||||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||
ElPopconfirm: typeof import('element-plus/es')['ElPopconfirm']
|
||||
ElRadio: typeof import('element-plus/es')['ElRadio']
|
||||
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
|
||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||
ElResult: typeof import('element-plus/es')['ElResult']
|
||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||
ElStep: typeof import('element-plus/es')['ElStep']
|
||||
ElSteps: typeof import('element-plus/es')['ElSteps']
|
||||
ElTable: typeof import('element-plus/es')['ElTable']
|
||||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||
ElTag: typeof import('element-plus/es')['ElTag']
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||
GradeList: typeof import('./src/components/gradeList/index.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
TeacherList: typeof import('./src/components/teacherList/index.vue')['default']
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
version: "3"
|
||||
|
||||
services:
|
||||
usercenter-web:
|
||||
image: ${REGISTRY:-harbor.w.23544.com:8843}/marking/usercenter.web:${TAG:-latest}
|
||||
hostname: marking
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
ports:
|
||||
- 4400:80
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: ${REPLICAS:-1}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>答题卡制作</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
server{
|
||||
# 监听的端口号
|
||||
listen 80;
|
||||
# 服务名称 生产环境要修改成 公网ip 如 47.105.134.120
|
||||
server_name localhost;
|
||||
# 配置根目录的地址是以 nginx 下的 html 文件夹为根目录来查找的
|
||||
root /usr/share/nginx/html;
|
||||
## html不缓存
|
||||
location = /index.html {
|
||||
add_header Cache-Control "no-cache, no-store";
|
||||
}
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
location @router {
|
||||
rewrite ^.*$ /index.html last;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
{
|
||||
"name": "vue3-project-template",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"test": "vite --mode test",
|
||||
"prod": "vite --mode production",
|
||||
"build": "vue-tsc && vite build",
|
||||
"build:dev": "vite build --mode development",
|
||||
"build:test": "vite build --mode test",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint . --ext src/**/*.{js,jsx,vue,ts,tsx} --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": "^1.4.0",
|
||||
"element-plus": "^2.3.7",
|
||||
"fabric": "5.3.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
"js-cookie": "^3.0.5",
|
||||
"js-md5": "^0.7.3",
|
||||
"pinia": "^2.1.4",
|
||||
"tesseract.js": "^5.1.0",
|
||||
"tesseract.js-core": "^5.1.0",
|
||||
"vue": "^3.2.47",
|
||||
"vue-router": "^4.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/ep": "^1.1.11",
|
||||
"@types/node": "^20.3.1",
|
||||
"@typescript-eslint/eslint-plugin": "^6.3.0",
|
||||
"@typescript-eslint/parser": "^6.3.0",
|
||||
"@vitejs/plugin-vue": "^4.1.0",
|
||||
"@vue/cli-plugin-eslint": "^5.0.8",
|
||||
"@vue/eslint-config-prettier": "^8.0.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^8.46.0",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-plugin-import": "^2.28.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint-plugin-vue": "^9.16.1",
|
||||
"postcss-px-to-viewport": "^1.1.1",
|
||||
"sass": "^1.63.6",
|
||||
"terser": "^5.18.1",
|
||||
"typescript": "^5.0.2",
|
||||
"unplugin-auto-import": "^0.16.4",
|
||||
"unplugin-icons": "^0.16.3",
|
||||
"unplugin-vue-components": "^0.25.1",
|
||||
"vite": "^4.3.9",
|
||||
"vite-plugin-inspect": "^0.7.29",
|
||||
"vue-tsc": "^1.4.2"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
|
|
@ -0,0 +1,11 @@
|
|||
<template>
|
||||
<el-config-provider :locale="zhCn">
|
||||
<router-view />
|
||||
</el-config-provider>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import zhCn from "element-plus/dist/locale/zh-cn.mjs";
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import { request } from '@/utils'
|
||||
export const GetViewSize = (data: any) =>
|
||||
request({
|
||||
url: `/draw/position?X=${data.X}&Y=${data.Y}&Width=${data.Width}&Height=${data.Height}&ImageUrl=${data.ImageUrl}&ImageWidth=${data.ImageWidth}&ImageHeight=${data.ImageHeight}`,
|
||||
method: 'get'
|
||||
})
|
||||
|
||||
export const GetOcrDetail = (mid: string) =>
|
||||
request({
|
||||
url: `/api/ocr/${mid}`,
|
||||
method: 'get'
|
||||
})
|
||||
|
||||
export const PostSave = (data: any) =>
|
||||
request({
|
||||
url: `/api/ocr/save`,
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
|
||||
export const GetLock = (mid: string) =>
|
||||
request({
|
||||
url: `/api/ocr/lock?mid=${mid}`,
|
||||
method: 'get',
|
||||
})
|
||||
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 333 B |
|
After Width: | Height: | Size: 380 B |
|
After Width: | Height: | Size: 830 B |
|
After Width: | Height: | Size: 901 B |
|
After Width: | Height: | Size: 550 KiB |
|
|
@ -0,0 +1,17 @@
|
|||
// 常量配置
|
||||
enum constant {
|
||||
CONFIG_TITLE = 'vue3-vite-ts-pinia',
|
||||
CONFIG_REQUEST_TIMEOUT_TIME = 10000, // 请求超时时间
|
||||
CONFIG_TOKEN = 'TOKEN', // token
|
||||
CONFIG_USERINFO = 'USERINFO', // 用户信息
|
||||
CONFIG_STATUS_CODE_SUCCESS = 100, // 自定义代码 100成功、101失败
|
||||
CONFIG_STATUS_CODE_ERROR = 101,
|
||||
CONFIG_USERNAME_KEY = 'USERNAME', // 用户名
|
||||
CONFIG_PASSWORD_KEY = 'PASSWORD', // 密码
|
||||
CONFIG_IS_REMEMBER_KEY = 'REMEMBER', // 是否记住密码
|
||||
CONFIG_CODE_SUCCESS = 200, // 成功码
|
||||
}
|
||||
// 常规配置
|
||||
const config = {}
|
||||
|
||||
export { config, constant }
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import { createApp } from 'vue'
|
||||
import router from './router'
|
||||
import './styles/index.scss'
|
||||
import App from './App.vue'
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||
import Store from './store'
|
||||
|
||||
// 清除项目中的console
|
||||
if (import.meta.env.VITE_ENV !== 'development') {
|
||||
console.log = function () {}
|
||||
}
|
||||
|
||||
const app = createApp(App)
|
||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||
app.component(key, component)
|
||||
}
|
||||
|
||||
app.use(router).use(Store).mount('#app')
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
<template>
|
||||
<div>
|
||||
<span>坐标:</span>
|
||||
<div>
|
||||
<el-button type="primary" @click="changeFabricPosition(true, 'top')">上移</el-button>
|
||||
<el-button type="primary" @click="changeFabricPosition(false, 'top')">下移</el-button>
|
||||
<el-button type="primary" @click="changeFabricPosition(true, 'left')">左移</el-button>
|
||||
<el-button type="primary" @click="changeFabricPosition(false, 'left')">右移</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span>宽度:</span>
|
||||
<el-input-number v-model="fabricPosition.width" :min="1" @change="changeFabricPosition($event, 'width')"
|
||||
:max="fabricPosition.imgDomWidth" />
|
||||
</div>
|
||||
<div>
|
||||
<span>高度:</span>
|
||||
<el-input-number v-model="fabricPosition.height" :min="1" @change="changeFabricPosition($event, 'height')"
|
||||
:max="fabricPosition.imgDomHight" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
const props = defineProps(["change"]);
|
||||
import { reactive } from "vue";
|
||||
const fabricPosition = reactive<any>({
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
imgDomHight: 1,
|
||||
imgDomWidth: 1
|
||||
})
|
||||
|
||||
const setSize = (data: any, imgDomWidth: number, imgDomHight: number): void => {
|
||||
fabricPosition.top = data.top;
|
||||
fabricPosition.left = data.left;
|
||||
fabricPosition.width = data.width;
|
||||
fabricPosition.height = data.height;
|
||||
fabricPosition.imgDomWidth = imgDomWidth;
|
||||
fabricPosition.imgDomHight = imgDomHight;
|
||||
}
|
||||
|
||||
const changeFabricPosition = (value: number | boolean, str: string): void => {
|
||||
if (typeof value === 'boolean') {
|
||||
if (str === 'top') {
|
||||
props.change(value ? fabricPosition.top-- : fabricPosition.top++, str)
|
||||
} else {
|
||||
props.change(value ? fabricPosition.left-- : fabricPosition.left++, str)
|
||||
}
|
||||
} else {
|
||||
props.change(value, str)
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
setSize,
|
||||
});
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
title=""
|
||||
width="50%"
|
||||
:show-close="false"
|
||||
:center="true"
|
||||
:align-center="true"
|
||||
>
|
||||
<div>
|
||||
<div style="font-size: 20px; margin-bottom: 20px; font-weight: bold">
|
||||
锁定模板后,无法修改,请确认模板信息后操作:
|
||||
</div>
|
||||
<div style="height: 70vh; overflow-y: auto">
|
||||
<el-collapse v-model="activeNames">
|
||||
<el-collapse-item :title="item.title" :name="item.key" v-for="item in list">
|
||||
<div v-if="item.key !== 'otherPoint'">
|
||||
<div v-if="item.data.length">
|
||||
<div v-for="row in item.data">
|
||||
{{ row.questionBefore }}-{{ row.questionAfter }}题 总分{{ row.score }}分
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div style="text-align: center">暂无数据</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div>缺考:{{ item.qk.length ? "已标记" : "未标记" }}</div>
|
||||
<div>屏蔽区:{{ item.pbq.length ? `${item.pbq.length}处` : "未标记" }}</div>
|
||||
<div>反面定位点:{{ item.fmdwd.length ? "已标记" : "未标记" }}</div>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="props.change(), changeDialogVisible(false)"
|
||||
>锁定</el-button
|
||||
>
|
||||
<el-button @click="changeDialogVisible(false)">取消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
const props = defineProps(["change"]);
|
||||
import { ref } from "vue";
|
||||
const dialogVisible = ref<boolean>(false);
|
||||
const activeNames = ref([
|
||||
"objectiveQuestionsPoint",
|
||||
"subjectiveQuestionsPoint",
|
||||
"otherPoint",
|
||||
]);
|
||||
const list = ref<any>([]);
|
||||
const getData = (data: any, templateInfo: any): void => {
|
||||
// 效验
|
||||
let msg = "";
|
||||
if (!templateInfo.headList[2].point.length && !templateInfo.tailsList[2].point.length) {
|
||||
msg = "考号未框选!";
|
||||
}
|
||||
if (!templateInfo.headList[1].point.length && !templateInfo.tailsList[1].point.length) {
|
||||
msg = "学科未框选!";
|
||||
}
|
||||
if (templateInfo.hasPosition) {
|
||||
if (templateInfo.imgSrc.length === 2) {
|
||||
if (
|
||||
templateInfo.headList[0].point.length !== 4 ||
|
||||
templateInfo.tailsList[0].point.length !== 4
|
||||
) {
|
||||
msg = "定位点未框选完成!";
|
||||
}
|
||||
} else {
|
||||
if (templateInfo.headList[0].point.length !== 4) {
|
||||
msg = "定位点未框选完成!";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (msg) {
|
||||
ElMessage.error(msg);
|
||||
return;
|
||||
}
|
||||
// 修改数据
|
||||
changeData(data);
|
||||
// 显示弹窗内容
|
||||
changeDialogVisible(true);
|
||||
};
|
||||
|
||||
const changeDialogVisible = (bool: boolean): void => {
|
||||
dialogVisible.value = bool;
|
||||
};
|
||||
|
||||
const changeData = (data: any): void => {
|
||||
const params = [
|
||||
{ title: "", key: "objectiveQuestionsPoint" },
|
||||
{ title: "", key: "subjectiveQuestionsPoint" },
|
||||
{ title: "", key: "otherPoint" },
|
||||
] as any;
|
||||
const objectiveQuestionsPoint = [], //客观题
|
||||
subjectiveQuestionsPoint = [], //主观题
|
||||
otherPoint = []; //其他
|
||||
for (let index = 0; index < data.length; index++) {
|
||||
const item = data[index];
|
||||
objectiveQuestionsPoint.push(...item.from.objectiveQuestionsPoint);
|
||||
subjectiveQuestionsPoint.push(...item.from.subjectiveQuestionsPoint);
|
||||
otherPoint.push(...item.from.otherPoint);
|
||||
}
|
||||
let objectiveQuestionsPointNumber = 0;
|
||||
objectiveQuestionsPoint.forEach((item: any) => {
|
||||
objectiveQuestionsPointNumber += item.questionNumber;
|
||||
});
|
||||
let subjectiveQuestionsPointNumber = 0;
|
||||
subjectiveQuestionsPoint.forEach((item: any) => {
|
||||
subjectiveQuestionsPointNumber += item.questionAfter - item.questionBefore + 1;
|
||||
});
|
||||
params[0].title = `客观题:${objectiveQuestionsPointNumber}题`;
|
||||
params[0].data = objectiveQuestionsPoint;
|
||||
params[1].title = `主观题:${subjectiveQuestionsPointNumber}题`;
|
||||
params[1].data = subjectiveQuestionsPoint;
|
||||
params[2].title = `其他标记(缺考、屏蔽区、反面定位点)`;
|
||||
params[2].data = otherPoint;
|
||||
params[2].qk = otherPoint.filter((item) => item.type === 1);
|
||||
params[2].pbq = otherPoint.filter((item) => item.type === 2);
|
||||
params[2].fmdwd = otherPoint.filter((item) => item.type === 3);
|
||||
list.value = params;
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
getData,
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<template>
|
||||
<el-result icon="warning" title="404提示" sub-title="你找的页面走丢了~">
|
||||
<template #extra>
|
||||
<el-button type="primary" @click="toNext">返回首页</el-button>
|
||||
</template>
|
||||
</el-result>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useRouter } from "vue-router";
|
||||
const router = useRouter();
|
||||
const toNext = (): void => {
|
||||
router.replace("/Home");
|
||||
};
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import { RouteRecordRaw, createRouter, createWebHashHistory } from 'vue-router'
|
||||
const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: "/",
|
||||
redirect: "/Home"
|
||||
},
|
||||
{
|
||||
path: "/Home",
|
||||
name: "Home",
|
||||
children: [],
|
||||
component: () => import("/src/pages/Home/index.vue")
|
||||
},
|
||||
{
|
||||
path: "/:pathMatch(.*)*",
|
||||
name: "NotFound",
|
||||
children: [],
|
||||
component: () => import("/src/pages/NotFound/index.vue")
|
||||
}
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes,
|
||||
})
|
||||
|
||||
export default router
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
/* eslint-disable */
|
||||
declare module '*.vue' {
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
declare module 'js-md5'
|
||||
declare module 'fabric'
|
||||
declare module 'js-cookie'
|
||||
declare module 'element-plus/dist/locale/zh-cn.mjs'
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import { createPinia } from 'pinia'
|
||||
|
||||
const Store = createPinia()
|
||||
|
||||
export default Store
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import { defineStore } from 'pinia'
|
||||
|
||||
export const userStore = defineStore('USER', {
|
||||
state: () => ({
|
||||
isLogin: false,
|
||||
}),
|
||||
})
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
.el-drawer{
|
||||
.el-drawer__header{
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
@import './public.scss';
|
||||
@import './element-ui.scss';
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
html,
|
||||
body,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:root {
|
||||
@for $i from 1 through 40 {
|
||||
--ft#{$i}: #{$i}px;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
import storage from './package/storage'
|
||||
import request from './request'
|
||||
|
||||
export { storage, request }
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
class LocalStorage {
|
||||
private constructor() {}
|
||||
|
||||
private static instance: LocalStorage | null = null
|
||||
|
||||
static getInstance() {
|
||||
if (LocalStorage.instance === null) {
|
||||
LocalStorage.instance = new LocalStorage()
|
||||
}
|
||||
return LocalStorage.instance
|
||||
}
|
||||
|
||||
setItem(key: string, value: any) {
|
||||
localStorage.setItem(key, value)
|
||||
}
|
||||
|
||||
getItem(key: string) {
|
||||
return localStorage.getItem(key)
|
||||
}
|
||||
|
||||
removeItem(key: string) {
|
||||
localStorage.removeItem(key)
|
||||
}
|
||||
|
||||
removeAll() {
|
||||
localStorage.clear()
|
||||
}
|
||||
}
|
||||
|
||||
export default LocalStorage.getInstance()
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import { AxiosRequestConfig, AxiosResponse } from 'axios'
|
||||
import Request from './request'
|
||||
import { constant } from '@/config'
|
||||
|
||||
// 实例化
|
||||
const req = new Request({
|
||||
baseURL: '',
|
||||
timeout: constant.CONFIG_REQUEST_TIMEOUT_TIME as number,
|
||||
interceptors: {
|
||||
// 请求拦截器
|
||||
requestInterceptors: (config: AxiosRequestConfig) => config,
|
||||
// 响应拦截器 <T = AxiosResponse>(result: T)
|
||||
responseInterceptors: <T = AxiosResponse>(result: T) => result,
|
||||
},
|
||||
})
|
||||
|
||||
const request = (config: any) => {
|
||||
const { method = 'GET' } = config
|
||||
|
||||
if (method === 'get' || method === 'GET') {
|
||||
config.params = config.data
|
||||
}
|
||||
return req.request<any>(config)
|
||||
}
|
||||
|
||||
export default request
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
import axios, { AxiosInstance, AxiosResponse } from 'axios'
|
||||
import { RequestConfig, RequestInterceptors } from './types'
|
||||
import { userStore } from '@/store/user'
|
||||
import { storage } from '@/utils'
|
||||
import Store from '@/store'
|
||||
import { constant } from '@/config'
|
||||
import router from '@/router'
|
||||
let loading = '' as any;
|
||||
|
||||
const store = userStore(Store)
|
||||
|
||||
class Request {
|
||||
// axios实例
|
||||
instance: AxiosInstance
|
||||
|
||||
// 拦截器对象
|
||||
interceptorsObj?: RequestInterceptors
|
||||
|
||||
constructor(config: RequestConfig) {
|
||||
// 创建实例
|
||||
this.instance = axios.create(config)
|
||||
// 类请求拦截器
|
||||
this.instance.interceptors.request.use(
|
||||
(req: any) => {
|
||||
const token = localStorage.getItem('token') || `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI4NGYwNDhjYy1mNGM2LTQzMTgtOWRiNi1mOTRhMmM3YjlkZjMiLCJpYXQiOiIwNS8yMS8yMDI0IDA5OjM4OjAwIiwiaWQiOiIzNCIsInVzZXIiOiIzNCIsIm5hbWUiOiJhZG1pbmZsc3EiLCJyb2xlIjoiNSIsInRlbmFudCI6IjEiLCJwb3NpdGlvbiI6IjQ4ODQ4NDYyNTc5MzA5MyIsInNjb3BlIjoiTGVhcm5BZG1pbiIsIm5iZiI6MTcxNjI1NTQ4MCwiZXhwIjoxNzI0MTc1NDgwLCJpc3MiOiJtay1hZCIsImF1ZCI6Ik1hcmtpbmdBZG1pbkNsaWVudCJ9.MGl5dhcUNpGyjb4R0X_ME-BrXBmljfPiagqhyITVF1g`
|
||||
if (token) {
|
||||
// 如果有token给请求头加上
|
||||
req.headers.Authorization = `${token}`
|
||||
req.timeout = constant.CONFIG_REQUEST_TIMEOUT_TIME
|
||||
}
|
||||
if (req.contentType) {
|
||||
req.headers["Content-Type"] = req.contentType;
|
||||
}
|
||||
return req
|
||||
},
|
||||
(_err: any) => {
|
||||
}
|
||||
)
|
||||
|
||||
// 类响应拦截器
|
||||
this.instance.interceptors.response.use(
|
||||
(res: AxiosResponse) => {
|
||||
const { data: resData, status, config } = res
|
||||
if (status == constant.CONFIG_CODE_SUCCESS && resData && Object.prototype.toString.call(resData) == '[object Object]') {
|
||||
resData.success = false
|
||||
resData.code == constant.CONFIG_CODE_SUCCESS && (resData.success = true)
|
||||
}
|
||||
resData.headers = res.headers['content-disposition'];
|
||||
if (resData.code !== 200) {
|
||||
if (config.responseType === 'blob') {
|
||||
const reader = new FileReader() as any;
|
||||
reader.readAsText(res.data, "utf-8");
|
||||
reader.onload = function () {
|
||||
if (reader.result) {
|
||||
try {
|
||||
ElMessage.error(JSON.parse(reader.result).message)
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
} else {
|
||||
ElMessage.error(resData.message)
|
||||
}
|
||||
}
|
||||
return resData
|
||||
},
|
||||
(err: any) => {
|
||||
function toLogin() {
|
||||
ElMessage.error('登录信息已失效,请重新登录!')
|
||||
storage.removeItem(constant.CONFIG_TOKEN)
|
||||
store.$patch((state) => {
|
||||
state.isLogin = false
|
||||
})
|
||||
if (localStorage.getItem('token')) {
|
||||
location.href = location.origin + "/login.html";
|
||||
} else {
|
||||
router.push({ path: '/login' })
|
||||
}
|
||||
}
|
||||
// 根据自己业务/接口返回做相应调整
|
||||
if (err.response) {
|
||||
const { status, data } = err.response
|
||||
ElMessage.error(data.message || data.title)
|
||||
switch (status) {
|
||||
case 401:
|
||||
toLogin()
|
||||
break
|
||||
case 403:
|
||||
toLogin()
|
||||
break
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(err.message)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
request<T>(config: RequestConfig): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
loading = ElLoading.service({
|
||||
lock: true,
|
||||
background: 'rgba(255,255, 255, 0.3)',
|
||||
body: true
|
||||
})
|
||||
if (config.interceptors?.requestInterceptors) {
|
||||
config = config.interceptors.requestInterceptors(config)
|
||||
}
|
||||
this.instance
|
||||
.request<any, T>(config)
|
||||
.then((res: T) => {
|
||||
// 如果我们为单个响应设置拦截器,这里使用单个响应的拦截器
|
||||
if (config.interceptors?.responseInterceptors) {
|
||||
res = config.interceptors.responseInterceptors<T>(res)
|
||||
}
|
||||
resolve(res || { code: 0 } as any)
|
||||
})
|
||||
.catch((err: any) => {
|
||||
reject(err)
|
||||
}).finally(() => {
|
||||
if (loading) {
|
||||
loading.close()
|
||||
loading = ''
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default Request
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { AxiosRequestConfig, AxiosResponse } from 'axios'
|
||||
|
||||
// 实例拦截器
|
||||
export interface RequestInterceptors {
|
||||
// 请求拦截
|
||||
requestInterceptors?: (config: AxiosRequestConfig) => AxiosRequestConfig
|
||||
requestInterceptorsCatch?: (err: any) => any
|
||||
// 响应拦截
|
||||
responseInterceptors?: <T = AxiosResponse>(config: T) => T
|
||||
responseInterceptorsCatch?: (err: any) => any
|
||||
}
|
||||
|
||||
// 自定义传入的参数
|
||||
export interface RequestConfig extends AxiosRequestConfig {
|
||||
interceptors?: RequestInterceptors
|
||||
}
|
||||
|
||||
export interface useRequestConfig<T> extends RequestConfig {
|
||||
data?: T
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
/// <reference types="vite/client" />
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"lib": [
|
||||
"ESNext",
|
||||
"DOM",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"skipLibCheck": true,
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "preserve",
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.d.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue",
|
||||
"./auto-imports.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
import { resolve } from 'path'
|
||||
import { defineConfig, loadEnv } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
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 Inspect from 'vite-plugin-inspect'
|
||||
import pxtovw from 'postcss-px-to-viewport'
|
||||
const loder_pxtovw = pxtovw({
|
||||
// 这里是设计稿宽度 自己修改
|
||||
viewportWidth: 1920,
|
||||
viewportUnit: 'vw',
|
||||
})
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, process.cwd());
|
||||
return {
|
||||
css: {
|
||||
postcss: {
|
||||
// plugins: [loder_pxtovw],
|
||||
},
|
||||
},
|
||||
server: {
|
||||
port: 8080,
|
||||
host: '0.0.0.0',
|
||||
proxy: {
|
||||
"/api": {
|
||||
target: env.VITE_BASE_URL_API,
|
||||
changeOrigin: true
|
||||
},
|
||||
"/draw": {
|
||||
target: env.VITE_BASE_URL_DRAW_API,
|
||||
changeOrigin: true
|
||||
},
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
alias: [
|
||||
{
|
||||
find: '@',
|
||||
replacement: resolve(__dirname, 'src'),
|
||||
},
|
||||
],
|
||||
},
|
||||
build: {
|
||||
minify: 'terser',
|
||||
terserOptions: {
|
||||
compress: {
|
||||
drop_console: true,
|
||||
},
|
||||
},
|
||||
rollupOptions: {
|
||||
output: {
|
||||
chunkFileNames: 'assets/js/[name]-[hash].js',
|
||||
entryFileNames: 'assets/js/[name]-[hash].js',
|
||||
assetFileNames: 'assets/[ext]/[name]-[hash].[ext]',
|
||||
compact: true,
|
||||
manualChunks: {
|
||||
|
||||
}
|
||||
},
|
||||
cache: true,
|
||||
}
|
||||
},
|
||||
base: './', // 这里更改打包相对绝对路径
|
||||
plugins: [
|
||||
vue(),
|
||||
AutoImport({
|
||||
resolvers: [
|
||||
ElementPlusResolver(),
|
||||
IconsResolver({
|
||||
prefix: 'Icon',
|
||||
}),
|
||||
],
|
||||
}),
|
||||
Components({
|
||||
resolvers: [
|
||||
ElementPlusResolver(),
|
||||
IconsResolver({
|
||||
enabledCollections: ['ep'],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
Icons({
|
||||
autoInstall: true,
|
||||
}),
|
||||
Inspect(),
|
||||
],
|
||||
|
||||
}
|
||||
}
|
||||
)
|
||||