├── .gitlab-ci.yml ├── .husky ├── .gitignore ├── pre-commit └── commit-msg ├── .eslintignore ├── .env.preview ├── .env.development ├── .env.production ├── Dockerfile ├── src ├── store │ ├── modules │ │ ├── app │ │ │ ├── actions.ts │ │ │ ├── getters.ts │ │ │ ├── mutations.ts │ │ │ └── state.ts │ │ ├── user │ │ │ ├── getters.ts │ │ │ ├── modules │ │ │ │ └── team │ │ │ │ │ ├── actions.ts │ │ │ │ │ ├── getters.ts │ │ │ │ │ ├── mutations.ts │ │ │ │ │ └── state.ts │ │ │ ├── mutations.ts │ │ │ ├── state.ts │ │ │ └── actions.ts │ │ ├── index.ts │ │ └── console │ │ │ ├── mutations.ts │ │ │ ├── state.ts │ │ │ ├── getters.ts │ │ │ └── actions.ts │ ├── mutations.ts │ ├── index.ts │ └── utils.ts ├── assets │ ├── logo.png │ ├── fonts │ │ ├── Helvetica.eot │ │ ├── Helvetica.otf │ │ ├── Helvetica.ttf │ │ ├── Helvetica.woff │ │ └── Helvetica.woff2 │ └── images │ │ ├── h-slider1.png │ │ ├── online-p1.png │ │ ├── online-p2.png │ │ ├── tag-icon.png │ │ ├── h-online-p1.png │ │ ├── h-online-p2.png │ │ ├── h-online-p3.png │ │ └── h-online-b-bg.png ├── utils │ ├── hooks │ │ ├── modal.ts │ │ └── index.ts │ ├── validator.ts │ └── common.ts ├── styles │ ├── index.less │ ├── iconfont.less │ ├── antd.less │ ├── var.less │ ├── common.less │ └── normalize.css ├── views │ ├── tsxpage.module.less │ ├── Contact.vue │ ├── Home.vue │ ├── TsxPage.tsx │ ├── AboutMe.vue │ ├── About.vue │ └── test │ │ └── Test.vue ├── i18n │ ├── messages │ │ ├── en.ts │ │ └── zhCN.ts │ └── index.ts ├── layout │ ├── Footer.vue │ ├── Header.vue │ ├── Sidebar.vue │ └── AppLayout.vue ├── api │ ├── editor.ts │ ├── common.ts │ ├── cloudrole.ts │ ├── manage.ts │ ├── team.ts │ ├── axios.ts │ └── user.ts ├── config │ ├── app.ts │ └── color.ts ├── router │ ├── tsx.ts │ ├── about.ts │ └── index.ts ├── shims-vue.d.ts ├── plugins │ ├── index.ts │ └── antd.ts ├── main.ts ├── components │ ├── index.ts │ ├── ChangeLanguage.vue │ ├── ImageDisplay.vue │ ├── TeamMemberItem.vue │ ├── Slider.vue │ ├── global │ │ └── FullLoading.vue │ └── Selector.vue ├── App.vue └── @types │ └── index.ts ├── babel.config.js ├── public ├── favicon.ico └── index.html ├── typedoc.json ├── .gitattributes ├── .commitlintrc.js ├── .prettierrc.js ├── tests ├── api │ └── api.spec.ts ├── unit │ └── example.spec.ts └── README.md ├── .gitignore ├── .editorconfig ├── global.d.ts ├── .github └── workflows │ ├── deploy-ci.yml │ └── demo ├── LICENSE ├── tsconfig.json ├── .eslintrc.js ├── .czrc ├── package.json ├── README.md ├── vue.config.js └── CHANGELOG.md /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | vue.config.js -------------------------------------------------------------------------------- /.env.preview: -------------------------------------------------------------------------------- 1 | NODE_ENV=production 2 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | NODE_ENV=production 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:alpine 2 | COPY dist /usr/share/nginx/html 3 | -------------------------------------------------------------------------------- /src/store/modules/app/actions.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | // 3 | } 4 | -------------------------------------------------------------------------------- /src/store/modules/app/getters.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | // 3 | } 4 | -------------------------------------------------------------------------------- /src/store/modules/user/getters.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | // 3 | } 4 | -------------------------------------------------------------------------------- /src/store/modules/user/modules/team/actions.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | // 3 | } 4 | -------------------------------------------------------------------------------- /src/store/modules/user/modules/team/getters.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | // 3 | } 4 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | npx lint-staged 4 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@vue/cli-plugin-babel/preset'] 3 | } 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibwei/vue3-ts-base/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibwei/vue3-ts-base/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /src/utils/hooks/modal.ts: -------------------------------------------------------------------------------- 1 | export default function () { 2 | console.log('hahah') 3 | } 4 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "mode": "file", 3 | "out": "docs", 4 | "name": "vue-base" 5 | } 6 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit $1 5 | -------------------------------------------------------------------------------- /src/assets/fonts/Helvetica.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibwei/vue3-ts-base/HEAD/src/assets/fonts/Helvetica.eot -------------------------------------------------------------------------------- /src/assets/fonts/Helvetica.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibwei/vue3-ts-base/HEAD/src/assets/fonts/Helvetica.otf -------------------------------------------------------------------------------- /src/assets/fonts/Helvetica.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibwei/vue3-ts-base/HEAD/src/assets/fonts/Helvetica.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Helvetica.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibwei/vue3-ts-base/HEAD/src/assets/fonts/Helvetica.woff -------------------------------------------------------------------------------- /src/assets/fonts/Helvetica.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibwei/vue3-ts-base/HEAD/src/assets/fonts/Helvetica.woff2 -------------------------------------------------------------------------------- /src/assets/images/h-slider1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibwei/vue3-ts-base/HEAD/src/assets/images/h-slider1.png -------------------------------------------------------------------------------- /src/assets/images/online-p1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibwei/vue3-ts-base/HEAD/src/assets/images/online-p1.png -------------------------------------------------------------------------------- /src/assets/images/online-p2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibwei/vue3-ts-base/HEAD/src/assets/images/online-p2.png -------------------------------------------------------------------------------- /src/assets/images/tag-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibwei/vue3-ts-base/HEAD/src/assets/images/tag-icon.png -------------------------------------------------------------------------------- /src/assets/images/h-online-p1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibwei/vue3-ts-base/HEAD/src/assets/images/h-online-p1.png -------------------------------------------------------------------------------- /src/assets/images/h-online-p2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibwei/vue3-ts-base/HEAD/src/assets/images/h-online-p2.png -------------------------------------------------------------------------------- /src/assets/images/h-online-p3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibwei/vue3-ts-base/HEAD/src/assets/images/h-online-p3.png -------------------------------------------------------------------------------- /src/assets/images/h-online-b-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibwei/vue3-ts-base/HEAD/src/assets/images/h-online-b-bg.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.vue linguist-language=TypeScript 2 | *.ts linguist-language=TypeScript 3 | *.tsx linguist-language=TypeScript 4 | -------------------------------------------------------------------------------- /src/styles/index.less: -------------------------------------------------------------------------------- 1 | @import './normalize.css'; 2 | @import './var.less'; 3 | @import './iconfont.less'; 4 | @import './common.less'; -------------------------------------------------------------------------------- /.commitlintrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @reference https://commitlint.js.org/#/ 3 | */ 4 | module.exports = { 5 | extends: ['@commitlint/config-conventional'] 6 | } 7 | -------------------------------------------------------------------------------- /src/store/modules/app/mutations.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | __set(state: any, msg: { key: string; val: any }) { 3 | state[msg.key] = msg.val 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/store/modules/user/mutations.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | __set(state: any, msg: { key: string; val: any }) { 3 | state[msg.key] = msg.val 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/views/tsxpage.module.less: -------------------------------------------------------------------------------- 1 | .tsx{ 2 | color:red; 3 | h1{ 4 | color:green; 5 | } 6 | .title{ 7 | border:1px solid red; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/i18n/messages/en.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | languageName: 'English', 3 | 'Current Language:': 'Current Language:', 4 | cancel: 'cancel', 5 | Play: 'Play' 6 | } 7 | -------------------------------------------------------------------------------- /src/store/modules/user/modules/team/mutations.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | __set(state: any, msg: { key: string; val: any }) { 3 | state[msg.key] = msg.val 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/layout/Footer.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/layout/Header.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/layout/Sidebar.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/store/mutations.ts: -------------------------------------------------------------------------------- 1 | /** 一个封装常规 mutation 操作的常规方法 */ 2 | export default { 3 | __set(state: any, msg: { key: string; val: any }) { 4 | state[msg.key] = msg.val 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/utils/validator.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 正则验证规则 3 | */ 4 | export default { 5 | // 手机 6 | phone: { 7 | pattern: /^0?(1[3-9])[0-9]{9}$/, 8 | message: '请输入正确的手机号码', 9 | trigger: 'blur' 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/api/editor.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | /** 编辑器接口地址 */ 4 | const editorInstance = axios.create({ 5 | baseURL: process.env.VUE_APP_BASE_EDITOR_URL, 6 | timeout: 10000 7 | }) 8 | 9 | export default editorInstance 10 | -------------------------------------------------------------------------------- /src/i18n/messages/zhCN.ts: -------------------------------------------------------------------------------- 1 | const zhCN = { 2 | languageName: '中文(简体)', 3 | 'Current Language:': '已经成功切换到', 4 | cancel: '取消', 5 | Play: '点击播放视频' 6 | } 7 | type i18nType = typeof zhCN 8 | export default zhCN 9 | export { i18nType } 10 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | // reference:: https://prettier.io/docs/en/options.html 2 | module.exports = { 3 | singleQuote: true, 4 | semi: false, 5 | trailingComma: 'none', 6 | endOfLine: 'lf', 7 | printWidth: 999, 8 | proseWrap: 'never', 9 | arrowParens:'avoid' 10 | } 11 | -------------------------------------------------------------------------------- /src/config/app.ts: -------------------------------------------------------------------------------- 1 | /** 跟应用全局相关的静态配置放在这里 */ 2 | import { message } from 'ant-design-vue' 3 | 4 | const AppConfig = { 5 | $message: message 6 | } 7 | const StaticConfig = { 8 | MaxPageSize: 1000, 9 | IconfontURL: '//at.alicdn.com/t/font_2092412_rr3rb5vksd8.js' 10 | } 11 | export { AppConfig, StaticConfig } 12 | -------------------------------------------------------------------------------- /src/router/tsx.ts: -------------------------------------------------------------------------------- 1 | import { RouteRecordRaw } from 'vue-router' 2 | 3 | /** 关于我们页面的路由配置 */ 4 | const AboutRouter: RouteRecordRaw = { 5 | path: '/tsxtest', 6 | name: 'tsxtest', 7 | component: () => 8 | import(/* webpackChunkName: "tsxtest" */ '@/views/TsxPage.tsx') 9 | } 10 | 11 | export default AboutRouter 12 | -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import { DefineComponent } from 'vue' 3 | const component: DefineComponent< 4 | Record, 5 | Record, 6 | unknown 7 | > 8 | export default component 9 | } 10 | 11 | declare module '*.tsx' 12 | declare module '*.less' 13 | -------------------------------------------------------------------------------- /src/views/Contact.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 13 | -------------------------------------------------------------------------------- /tests/api/api.spec.ts: -------------------------------------------------------------------------------- 1 | import UserService from '@/api/user' 2 | import { assert } from 'chai' 3 | 4 | describe('测试 User 模块 Api', () => { 5 | it('测试登录接口', async () => { 6 | const res = await UserService.login({ 7 | userName: 'nihao', 8 | password: 'hahah' 9 | }) 10 | console.log(res) 11 | assert.equal(res.data.code, 1, '业务接受不正常') 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /src/store/modules/user/modules/team/state.ts: -------------------------------------------------------------------------------- 1 | import { Module } from 'vuex' 2 | import { StateType } from '../../../../../@types/index' 3 | const state = { 4 | teamName: '雪狼团队' 5 | } 6 | type TeamStateType = typeof state 7 | 8 | const ModuleTeam: Module = { 9 | namespaced: true, 10 | ...state 11 | } 12 | 13 | export { TeamStateType, state } 14 | export default ModuleTeam 15 | -------------------------------------------------------------------------------- /src/api/common.ts: -------------------------------------------------------------------------------- 1 | import Axios from './axios' 2 | import { HttpResponse } from '@/@types/index' 3 | 4 | /** 5 | * @description 公共模块的的网络请求,所有通用 api 放在此处 6 | */ 7 | 8 | class CommonService { 9 | // 添加团队 10 | static getRoleInfoList(): Promise { 11 | return Axios.get('/bus/common/getRoleInfo', { 12 | responseType: 'json' 13 | }) 14 | } 15 | } 16 | 17 | export default CommonService 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | 25 | # local docs 26 | todo-list.md 27 | *todo-list*.md 28 | docs 29 | yarn-error.log 30 | -------------------------------------------------------------------------------- /src/plugins/index.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | 3 | /** 4 | * @description 加载所有 Plugins 5 | * @param {ReturnType} app 整个应用的实例 6 | */ 7 | export function loadAllPlugins(app: ReturnType) { 8 | const files = require.context('.', true, /\.ts$/) 9 | files.keys().forEach(key => { 10 | if (typeof files(key).default === 'function') { 11 | if (key !== './index.ts') files(key).default(app) 12 | } 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /src/store/modules/app/state.ts: -------------------------------------------------------------------------------- 1 | import { StateType } from '@/@types' 2 | import { Module } from 'vuex' 3 | 4 | const state = { 5 | language: 'zhCN', 6 | theme: 'light', 7 | version: '0.0.1', 8 | fullLoading: false, 9 | loadingText: 'Loading...', 10 | currentActiveNav: '解决方案' 11 | } 12 | type AppStateType = typeof state 13 | 14 | const app: Module = { namespaced: true, ...state } 15 | 16 | export { AppStateType, state } 17 | export default app 18 | -------------------------------------------------------------------------------- /src/router/about.ts: -------------------------------------------------------------------------------- 1 | import { RouteRecordRaw } from 'vue-router' 2 | 3 | /** 关于我们页面的路由配置 */ 4 | const AboutRouter: RouteRecordRaw = { 5 | path: '/about', 6 | name: 'about', 7 | component: () => import(/* webpackChunkName: "about" */ '@/views/About.vue'), 8 | children: [ 9 | { 10 | path: 'me', 11 | name: 'aboutMe', 12 | component: () => 13 | import(/* webpackChunkName: "about-me" */ '@/views/AboutMe.vue') 14 | } 15 | ] 16 | } 17 | 18 | export default AboutRouter 19 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 表示是最顶层的配置文件,发现设为true时,才会停止查找.editorconfig文件 2 | root = true 3 | [*.{js,jsx,ts,tsx,vue}] 4 | charset = utf-8 5 | indent_size = 2 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | 9 | # Unix-style newlines with a newline ending every file 对于所有的文件 始终在文件末尾插入一个新行 10 | [*] 11 | end_of_line = lf 12 | insert_final_newline = true 13 | 14 | # 对于所有的js文件,设置文件字符集为utf-8 15 | [*.js,*.ts,*vue] 16 | charset = utf-8 17 | 18 | # 设置所有JS,vue的缩进为 19 | [*.{js,vue}] 20 | 21 | -------------------------------------------------------------------------------- /src/store/modules/index.ts: -------------------------------------------------------------------------------- 1 | // https://vuex.vuejs.org/en/modules.html 2 | 3 | const files = require.context('.', true, /\.ts$/) 4 | const modules: any = {} 5 | 6 | files.keys().forEach((key) => { 7 | if (key === './index.ts') return 8 | const path = key.replace(/(\.\/|\.ts)/g, '') 9 | const [namespace, imported] = path.split('/') 10 | if (!modules[namespace]) { 11 | modules[namespace] = { 12 | namespaced: true 13 | } 14 | } 15 | modules[namespace][imported] = files(key).default 16 | }) 17 | 18 | export default modules 19 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 4 | 22 | 23 | 32 | -------------------------------------------------------------------------------- /global.d.ts: -------------------------------------------------------------------------------- 1 | import { StateType } from '@/@types' 2 | import { Message } from 'ant-design-vue/types/message' 3 | import { Router, RouteLocationNormalizedLoaded } from 'vue-router' 4 | import { Store } from 'vuex' 5 | 6 | /** 将第三方变量挂载到每一个 vue 示例中 */ 7 | declare module '@vue/runtime-core' { 8 | interface ComponentCustomProperties { 9 | $message: Message 10 | $store: Store 11 | $route: RouteLocationNormalizedLoaded 12 | $router: Router 13 | } 14 | } 15 | 16 | declare module '*.tsx' 17 | 18 | declare global { 19 | interface X { 20 | name: string 21 | age: number 22 | } 23 | 24 | interface X1 { 25 | s: string 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/styles/iconfont.less: -------------------------------------------------------------------------------- 1 | /* 自定义字体 */ 2 | @font-face { 3 | font-family: Helvetica; 4 | src :url('../assets/fonts/Helvetica.eot'); 5 | src : url('../assets/fonts/Helvetica.woff2') format('woff2'); 6 | src : url('../assets/fonts/Helvetica.woff') format('woff'); 7 | src : url('../assets/fonts/Helvetica.otf'); 8 | src : url('../assets/fonts/Helvetica.ttf') format('truetype'); 9 | } 10 | 11 | 12 | .iconfont ::v-deep(.anticon){ 13 | font-family: 'iconfont'; 14 | font-size: 16px; 15 | font-style: normal; 16 | -webkit-font-smoothing: antialiased; 17 | -webkit-text-stroke-width: 0.2px; 18 | -moz-osx-font-smoothing: grayscale; 19 | } 20 | -------------------------------------------------------------------------------- /src/utils/hooks/index.ts: -------------------------------------------------------------------------------- 1 | import { customRef } from 'vue' 2 | 3 | /** 4 | * @description 使用自定义 ref 实现带防抖功能的 v-model : 5 | * @param {any} value 要更改的值 6 | * @param {number} delay 7 | * @returns {T} 8 | */ 9 | 10 | export function useDebouncedRef(value: T, delay = 200) { 11 | let timeout: any = null 12 | return customRef((track, trigger) => { 13 | return { 14 | get() { 15 | track() 16 | return value 17 | }, 18 | set(newValue: T) { 19 | clearTimeout(timeout) 20 | timeout = setTimeout(() => { 21 | value = newValue 22 | trigger() 23 | }, delay) 24 | } 25 | } 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /src/views/TsxPage.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, reactive } from 'vue' 2 | import style from './tsxpage.module.less' 3 | export default defineComponent({ 4 | setup() { 5 | const state = reactive({ 6 | name: 'tsx test page' 7 | }) 8 | 9 | const getName = () => { 10 | alert(state.name) 11 | } 12 | 13 | const dom =
dom gragement test
14 | 15 | return () => ( 16 | <> 17 |
18 | css test:color shoule be green 19 | {dom} 20 |

This is an {state.name}

21 |
22 | 23 | ) 24 | } 25 | }) 26 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from '@/App.vue' 3 | import router from '@/router' 4 | import store from '@/store' 5 | import { AppConfig } from '@/config/app' 6 | 7 | import { loadAllPlugins } from '@/plugins' 8 | import { registeGlobalComponent } from '@/components/index' 9 | 10 | // 语言国际化方案 11 | import '@/i18n/index' 12 | import './styles/antd.less' 13 | 14 | /** 将全局静态配置注入到应用中,可以通过 this.a读取,比 provide 和 inject 手动注入更方便 */ 15 | const app: ReturnType = createApp(App) 16 | app.config.globalProperties = AppConfig 17 | 18 | /** 加载所有 Plugins */ 19 | loadAllPlugins(app) 20 | 21 | /** 自动注册全局组件 */ 22 | registeGlobalComponent(app) 23 | 24 | app.use(store).use(router).mount('#app') 25 | -------------------------------------------------------------------------------- /src/store/modules/console/mutations.ts: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | import router from '@/router' 3 | import { setStoreState } from '@/store/utils' 4 | 5 | export default { 6 | __set(state: any, msg: { key: string; val: any }) { 7 | state[msg.key] = msg.val 8 | }, 9 | updateBackPathList() { 10 | const backPathList = store.state.console.backPathList 11 | const currentPath = router.currentRoute.value.fullPath 12 | const newBackPathList = [...backPathList] 13 | 14 | if (backPathList.length < 0 || backPathList[backPathList.length - 1] !== currentPath) { 15 | newBackPathList.push(currentPath) 16 | } 17 | 18 | setStoreState('console', 'backPathList', newBackPathList) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/deploy-ci.yml: -------------------------------------------------------------------------------- 1 | name: GitHub Actions Build and Deploy Project 2 | on: 3 | push: 4 | branches: 5 | - master 6 | jobs: 7 | build-and-deploy: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@master 12 | 13 | - name: Build and Deploy 14 | uses: JamesIves/github-pages-deploy-action@releases/v2 15 | env: 16 | ACCESS_TOKEN: ${{ secrets.MY_DEPLOY_KEY }} 17 | BASE_BRANCH: master # The branch the action should deploy from. 18 | BRANCH: release # The branch the action should deploy to. 19 | FOLDER: dist # The folder the action should deploy. 20 | BUILD_SCRIPT: yarn && yarn build 21 | -------------------------------------------------------------------------------- /src/config/color.ts: -------------------------------------------------------------------------------- 1 | /* 所有的颜色静态配置,与 styles/antd.less 一致 */ 2 | const Color = { 3 | antd: { 4 | primary: '#00a971', // 全局主色 5 | link: '#00a971', // 链接色 6 | success: '#52c41a', // 成功色 7 | warning: '#faad14', // 警告色 8 | error: '#f5222d', // 错误色 9 | fontSizeBase: '14px', // 主字号 10 | headingColor: 'rgba(0, 0, 0, 0.85)', // 标题色 11 | textColor: 'rgba(0, 0, 0, 0.65)', // 主文本色 12 | textColorSecondary: 'rgba(0, 0, 0, 0.45)', // 次文本色 13 | disabledColor: 'rgba(0, 0, 0, 0.25)', // 失效色 14 | borderRadiusBase: '2px', // 组件/浮层圆角 15 | borderColorBase: '#d9d9d9', // 边框色 16 | boxShadowBase: '0 2px 8px rgba(0, 0, 0, 0.15);' // 17 | } 18 | } 19 | 20 | type ColorType = typeof Color 21 | 22 | export { Color, ColorType } 23 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | <% const IS_DEV = process.env.NODE_ENV === 'development' ? true : false %> 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | <%= htmlWebpackPlugin.options.title %> 11 | 12 | 13 | 14 | 18 |
19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/styles/antd.less: -------------------------------------------------------------------------------- 1 | // 配置 antd 样式相关 2 | @import '~ant-design-vue/dist/antd.less'; // 引入官方提供的 less 样式入口文件 3 | 4 | // 定制主题 5 | @primary-color : #00a971; // 全局主色 6 | @primary : #00a971; 7 | @link-color : #00a971; // 链接色 8 | @success-color : #52c41a; // 成功色 9 | @warning-color : #faad14; // 警告色 10 | @error-color : #f5222d; // 错误色 11 | @font-size-base : 14px; // 主字号 12 | @heading-color : rgba(0, 0, 0, 0.85); // 标题色 13 | @text-color : rgba(0, 0, 0, 0.65); // 主文本色 14 | @text-color-secondary: rgba(0, 0, 0, 0.45); // 次文本色 15 | @disabled-color : rgba(0, 0, 0, 0.25); // 失效色 16 | @border-radius-base : 2px; // 组件/浮层圆角 17 | @border-color-base : #d9d9d9; // 边框色 18 | @box-shadow-base : 0 2px 8px rgba(0, 0, 0, 0.15); // 19 | -------------------------------------------------------------------------------- /src/styles/var.less: -------------------------------------------------------------------------------- 1 | @primary-color: #00a971; // 全局主色 2 | @primary: #00a971; 3 | @link-color: #00a971; // 链接色 4 | @success-color: #52c41a; // 成功色 5 | @warning-color: #faad14; // 警告色 6 | @error-color: #f5222d; // 错误色 7 | @font-size-base: 14px; // 主字号 8 | @heading-color: rgba(0, 0, 0, 0.85); // 标题色 9 | @text-color: rgba(0, 0, 0, 0.65); // 主文本色 10 | @text-color-secondary: rgba(0, 0, 0, 0.45); // 次文本色 11 | @disabled-color: rgba(0, 0, 0, 0.25); // 失效色 12 | @border-radius-base: 0px; // 组件/浮层圆角 13 | @border-color-base: #d9d9d9; // 边框色 14 | @box-shadow-base: 0 2px 8px rgba(0, 0, 0, 0.15); // 15 | @min-width: 1200px; // 内容区域宽度 16 | @banner-title: 40px; // banner 标题文字大小 17 | @banner-button: 24px; // banner 按钮文字大小 18 | @banner-text: 16px; // banner 内容文字大小 19 | @m-top-1: 120px; 20 | @m-top-2: 80px; 21 | @m-top-3: 40px; 22 | -------------------------------------------------------------------------------- /src/styles/common.less: -------------------------------------------------------------------------------- 1 | .g-description, 2 | .g-desc { 3 | font-size : 18px; 4 | font-weight: 400; 5 | color : #333333; 6 | line-height: 27px; 7 | text-align : justify; 8 | } 9 | 10 | .g-title { 11 | font-size : 36px; 12 | font-weight: 500; 13 | color : #333333; 14 | line-height: 36px; 15 | 16 | } 17 | 18 | .g-sub-title { 19 | font-size : 30px; 20 | font-weight: 500; 21 | color : #333333; 22 | line-height: 30px; 23 | } 24 | 25 | .g-flex-row { 26 | display : flex; 27 | flex-flow : row nowrap; 28 | justify-content: center; 29 | align-items : center; 30 | } 31 | 32 | .g-flex-col { 33 | display : flex; 34 | flex-flow : column nowrap; 35 | justify-content: center; 36 | align-items : center; 37 | } 38 | -------------------------------------------------------------------------------- /src/store/modules/user/state.ts: -------------------------------------------------------------------------------- 1 | import { StateType } from '@/@types' 2 | import { Module } from 'vuex' 3 | import ModuleTeam from './modules/team/state' 4 | interface Token { 5 | [propertys: string]: any 6 | } 7 | 8 | const state = { 9 | token: {} as Token, 10 | userDetail: { 11 | email: '', 12 | type: -1, // 用户账号本身类型 0:主账号,1:子账号 13 | userId: -1, 14 | username: '', 15 | description: '', 16 | nickName: '', 17 | phone: '', 18 | tenantId: 0, 19 | roleId: 0 20 | }, 21 | currentTeamRoleId: 0, // 当前所选择的团队用户所具有的权限 22 | currentProjectRoleId: 0 // 当前所选择的项目用户所具有的权限 23 | } 24 | type UserStateType = typeof state 25 | 26 | const user: Module = { 27 | namespaced: true, 28 | ...state, 29 | modules: { 30 | team: ModuleTeam 31 | } 32 | } 33 | 34 | export { UserStateType, state } 35 | export default user 36 | -------------------------------------------------------------------------------- /src/views/AboutMe.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 41 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { createStore, createLogger, Store } from 'vuex' 2 | import createPersistedState from 'vuex-persistedstate' 3 | import mutations from './mutations' 4 | import modules from './modules' 5 | import { StateType } from '@/@types' 6 | import { InjectionKey } from 'vue' 7 | 8 | export const key: InjectionKey> = Symbol() 9 | 10 | const store: Store = createStore({ 11 | strict: true, 12 | mutations, 13 | actions: {}, 14 | modules: { ...modules }, 15 | plugins: 16 | process.env.NODE_ENV !== 'production' 17 | ? [ 18 | createLogger(), 19 | createPersistedState({ 20 | paths: ['app', 'console', 'user'] 21 | }) 22 | ] 23 | : [ 24 | createPersistedState({ 25 | paths: ['app', 'console', 'user'] 26 | }) 27 | ] 28 | }) 29 | 30 | export default store 31 | -------------------------------------------------------------------------------- /src/plugins/antd.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Button, 3 | Card, 4 | Row, 5 | Col, 6 | Tag, 7 | Form, 8 | Input, 9 | ConfigProvider, 10 | Select, 11 | DatePicker, 12 | Dropdown, 13 | Menu, 14 | Divider, 15 | Badge, 16 | BackTop, 17 | Carousel 18 | } from 'ant-design-vue' 19 | 20 | /** 21 | * @description 手动注册 antd-vue 组件,达到按需加载目的 22 | * @description Automatically register components under Button, such as Button.Group 23 | * @param {ReturnType} app 整个应用的实例 24 | * @returns void 25 | */ 26 | export default function loadComponent(app: any) { 27 | app.use(Button) 28 | app.use(Card) 29 | app.use(Row) 30 | app.use(Col) 31 | app.use(Tag) 32 | app.use(Form) 33 | app.use(Input) 34 | app.use(Dropdown) 35 | app.use(Menu) 36 | app.use(Divider) 37 | app.use(ConfigProvider) 38 | app.use(Select) 39 | app.use(DatePicker) 40 | app.use(BackTop) 41 | app.use(Badge) 42 | app.use(Carousel) 43 | } 44 | -------------------------------------------------------------------------------- /src/components/index.ts: -------------------------------------------------------------------------------- 1 | import { kebabCase } from 'lodash' 2 | import { createApp } from 'vue' 3 | import { createFromIconfontCN } from '@ant-design/icons-vue' 4 | import { StaticConfig } from '@/config/app' 5 | // iconfont的使用姿势: 2x.antdv.com/components/icon-cn/#components-icon-demo-use-iconfont.cn 6 | const IconFont: any = createFromIconfontCN({ 7 | scriptUrl: StaticConfig.IconfontURL 8 | }) 9 | 10 | /** 11 | * @description 自动将 ./src/components/global 下的组件注册成为全局组件 12 | * @param {vue} app 当前应用实例 13 | * @returns {void} void 14 | */ 15 | export function registeGlobalComponent( 16 | app: ReturnType 17 | ): void { 18 | const files = require.context('./global', true, /\.(vue|ts)$/) 19 | files.keys().forEach((key) => { 20 | const config = files(key) 21 | const name = kebabCase(key.replace(/^\.\//, '').replace(/\.\w+$/, '')) 22 | app.component(name, config.default || config) 23 | }) 24 | 25 | // 全局注册 iconfont 26 | app.component('IconFont', IconFont) 27 | } 28 | -------------------------------------------------------------------------------- /src/store/modules/user/actions.ts: -------------------------------------------------------------------------------- 1 | import UserService from '@/api/user' 2 | import { setStoreState } from '../../utils' 3 | import Store from '@/store' 4 | /** 5 | * @description 所有跟用户相关的内容 6 | * @return status 返回状态 err_code:1,逻辑正确,err_code:0,发生错误。 7 | */ 8 | 9 | const userActions = { 10 | // 刷新令牌 11 | refreshToken() { 12 | return UserService.refreshToken({ 13 | // eslint-disable-next-line 14 | refresh_token: Store.state.user.token.refresh_token 15 | }).then((res) => { 16 | // token过期时间 17 | const expireTime = res.data.expires_in * 1000 + new Date().getTime() 18 | setStoreState('user', 'token', { ...res.data, expireTime }) 19 | }) 20 | }, 21 | // 获取用户信息 22 | getUserDetail() { 23 | return UserService.getUserDetail().then((res) => { 24 | setStoreState('user', 'userDetail', res.data.data) 25 | }) 26 | } 27 | } 28 | 29 | type UserActionsType = typeof userActions 30 | 31 | export { UserActionsType } 32 | export default userActions 33 | -------------------------------------------------------------------------------- /src/store/modules/console/state.ts: -------------------------------------------------------------------------------- 1 | import { StateType, TeamListType, RoleItemType, BasicUserType } from '@/@types' 2 | import { Module } from 'vuex' 3 | import { CloudRoleItem } from '@/@types/index' 4 | 5 | // 均放置跟控制台 UI 状态相关内容 6 | 7 | const state = { 8 | expired: true, 9 | sidebarFold: false, // 侧边栏菜单是否折叠 10 | thirdPanelFold: true, // 第三级版面是否折叠 11 | teamDetaiPanel: '0', // 团队详情里 tabbar 当前活跃的 key 12 | teamGroupType: 'all', // 成员分组当前选择 13 | myTeamList: [], // 我所在的团队 14 | selectedTeam: {} as TeamListType, // 当前选择的团队 15 | thirdPanelLoading: false, // 团队详情是否正在加载 16 | roleList: [] as RoleItemType[], // 权限列表 17 | subAccountList: [] as BasicUserType[], // 所有的子账号列表 18 | selectedTeamMemberList: [] as BasicUserType[], 19 | backPathList: [] as string[], // 返回路径列表 20 | selectedTeamCloudRoleList: [] as CloudRoleItem[], // 当前选择团队的云角色列表 21 | projectList: [] // 项目列表 22 | } 23 | 24 | type ConsoleStateType = typeof state 25 | 26 | const console: Module = { 27 | namespaced: true, 28 | ...state 29 | } 30 | 31 | export { ConsoleStateType, state } 32 | export default console 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 ibaiwei 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/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router' 2 | import Home from '../views/Home.vue' 3 | 4 | /** 自动加载其他路由模块 */ 5 | const files = require.context('.', true, /\.ts$/) 6 | const modules: Array = [] 7 | files.keys().forEach((key) => { 8 | if (key === './index.ts') return 9 | modules.push(files(key).default) 10 | }) 11 | 12 | const routes: Array = [ 13 | { 14 | path: '/', 15 | name: 'Home', 16 | component: Home 17 | }, 18 | ...modules, 19 | { 20 | path: '/contact', 21 | name: 'Contact', 22 | // route level code-splitting 23 | // this generates a separate chunk (about.[hash].js) for this route 24 | // which is lazy-loaded when the route is visited. 25 | component: () => 26 | import(/* webpackChunkName: "contact" */ '../views/Contact.vue') 27 | }, 28 | { 29 | path: '/tests', 30 | name: 'Tests', 31 | component: () => 32 | import(/* webpackChunkName: "Test" */ '../views/test/Test.vue') 33 | } 34 | ] 35 | 36 | const router = createRouter({ 37 | history: createWebHashHistory(), 38 | routes 39 | }) 40 | 41 | export default router 42 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "declaration": true, 7 | "jsx": "preserve", 8 | "importHelpers": true, 9 | "moduleResolution": "node", 10 | "experimentalDecorators": true, 11 | "skipLibCheck": false, 12 | "esModuleInterop": true, 13 | "allowSyntheticDefaultImports": true, 14 | "sourceMap": false, 15 | "baseUrl": ".", 16 | "rootDir": ".", 17 | "types": [ 18 | "webpack-env", 19 | "chai", 20 | "mocha" 21 | ], 22 | "paths": { 23 | "@/*": [ 24 | "src/*" 25 | ], 26 | "@/views/*": [ 27 | "src/views/*" 28 | ], 29 | "@/components/*": [ 30 | "src/components/*" 31 | ], 32 | "@/assets/*": [ 33 | "src/assets/*" 34 | ], 35 | "@/utils/*": [ 36 | "src/utils/*" 37 | ], 38 | "@/api/*": [ 39 | "src/api/*" 40 | ] 41 | }, 42 | "lib": [ 43 | "esnext", 44 | "dom", 45 | "dom.iterable", 46 | "scripthost" 47 | ] 48 | }, 49 | "exclude": [ 50 | "node_modules", 51 | "dist" 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /src/views/About.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 53 | -------------------------------------------------------------------------------- /src/store/modules/console/getters.ts: -------------------------------------------------------------------------------- 1 | import { CloudRoleItemType } from '../../../@types/index' 2 | import store from '@/store' 3 | 4 | const consoleGetter = { 5 | // 获取云角色列表 6 | getTeamClouldList() { 7 | // 获取用户列表以及云角色列表 8 | const memberList = store.state.console.selectedTeamMemberList.map(item => { 9 | let cloudRoleList: number[] = [] 10 | if (item.cloudRole) { 11 | cloudRoleList = item.cloudRole.split(',').map(id => Number(id)) 12 | } 13 | return { ...item, cloudRoleList: cloudRoleList } 14 | }) 15 | 16 | const cloudRoleList = [...store.state.console.selectedTeamCloudRoleList] 17 | 18 | // 将当前团队所有对应云角色的用户添加到该云角色的成员中去 19 | 20 | const list: CloudRoleItemType[] = [] 21 | for (let i = 0; i < cloudRoleList.length; i++) { 22 | list.push({ ...cloudRoleList[i], members: [] }) 23 | for (let j = 0; j < memberList.length; j++) { 24 | if (memberList[j].cloudRoleList.includes(cloudRoleList[i].cloudRoleId)) { 25 | list[i].members.push(memberList[j]) 26 | } 27 | } 28 | } 29 | return list 30 | } 31 | } 32 | 33 | type ConsoleGettersType = typeof consoleGetter 34 | export { ConsoleGettersType } 35 | export default consoleGetter 36 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parserOptions: { 4 | parser: '@typescript-eslint/parser' 5 | }, 6 | env: { 7 | browser: true, 8 | es6: true, 9 | node: true 10 | }, 11 | plugins: ['prettier', 'vue', '@typescript-eslint/eslint-plugin'], 12 | rules: { 13 | 'prettier/prettier': 'error', 14 | 'no-unused-vars': 'off', 15 | '@typescript-eslint/no-explicit-any': 'off', 16 | '@typescript-eslint/member-delimiter-style': 'off', 17 | '@typescript-eslint/no-var-requires': 'off', 18 | '@typescript-eslint/ban-ts-ignore': 'off', 19 | '@typescript-eslint/class-name-casing': 'off', 20 | 'vue/valid-v-slot': 'off', 21 | 'no-debugger': 'off', 22 | 'vue/experimental-script-setup-vars': 'off', 23 | '@typescript-eslint/explicit-module-boundary-types': 'off' 24 | }, 25 | extends: [ 26 | 'plugin:vue/vue3-essential', 27 | 'eslint:recommended', 28 | '@vue/typescript/recommended' 29 | ], 30 | overrides: [ 31 | { 32 | files: [ 33 | '**/tests/*.{j,t}s?(x)', 34 | '**/tests/**/*.spec.{j,t}s?(x)', 35 | '**/tests/*.spec.{j,t}s?(x)' 36 | ], 37 | env: { 38 | mocha: true 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /tests/unit/example.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, assert } from 'chai' 2 | import { shallowMount } from '@vue/test-utils' 3 | import HelloWorld from '@/components/HelloWorld.vue' 4 | 5 | describe('HelloWorld.vue', () => { 6 | it('renders props.msg when passed', () => { 7 | const msg = 'new message' 8 | const wrapper = shallowMount(HelloWorld, { 9 | props: { msg } 10 | }) 11 | expect(wrapper.text()).to.include(msg) 12 | }) 13 | }) 14 | 15 | describe('基本的测试用例', () => { 16 | it('expect检测基本用例是否正确', () => { 17 | const foo = 'bar', 18 | beverages = { tea: ['chai', 'matcha', 'oolong'] } 19 | expect(foo).to.be.a('string') 20 | expect(foo).to.equal('bar') 21 | expect(foo).to.have.lengthOf(3) 22 | expect(beverages).to.have.property('tea').with.lengthOf(3) 23 | }) 24 | it('asset检测基本用例是否正确', () => { 25 | const foo = 'bar', 26 | beverages = { tea: ['chai', 'matcha', 'oolong'] } 27 | assert.typeOf(foo, 'string') // without optional message 28 | assert.typeOf(foo, 'string', 'foo is a string') // with optional message 29 | assert.equal(foo, 'bar', 'foo equal `bar`') 30 | assert.lengthOf(foo, 3, 'foo`s value has a length of 3') 31 | assert.lengthOf(beverages.tea, 3, 'beverages has 3 types of tea') 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /.github/workflows/demo: -------------------------------------------------------------------------------- 1 | name: GitHub Actions Build and Deploy Project 2 | on: 3 | push: 4 | branches: 5 | - master 6 | jobs: 7 | build-and-deploy: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@master 12 | 13 | - name: Build and Deploy 14 | uses: JamesIves/github-pages-deploy-action@releases/v2 15 | env: 16 | ACCESS_TOKEN: ${{ secrets.MY_DEPLOY_KEY }} 17 | BASE_BRANCH: master # The branch the action should deploy from. 18 | BRANCH: release # The branch the action should deploy to. 19 | FOLDER: dist # The folder the action should deploy. 20 | BUILD_SCRIPT: yarn && yarn build 21 | - name: Deploy 22 | uses: appleboy/ssh-action@master # 使用ssh链接服务器 23 | with: #登录服务器,git 下载 release 分支 24 | host: ${{ secrets.REMOTE_HOST }} 25 | username: ${{ secrets.REMOTE_USER }} 26 | password: ${{ secrets.REMOTE_PASSWORD }} 27 | port: ${{ secrets.REMOTE_PORT }} 28 | script: | 29 | rm -rf /data/www/temp 30 | mkdir /data/www/temp 31 | cd /data/www/temp 32 | git clone -b release https://github.com/ibwei/vue3-ts-base.git 33 | cd /data/www/temp/vue3-ts-base 34 | rm -rf /data/www/vue3/* 35 | mv ./* /data/www/vue3 36 | -------------------------------------------------------------------------------- /.czrc: -------------------------------------------------------------------------------- 1 | { 2 | "path": "cz-conventional-changelog", 3 | "disableScopeLowerCase": false, 4 | "disableSubjectLowerCase": false, 5 | "maxHeaderWidth": 100, 6 | "maxLineWidth": 100, 7 | "defaultType": "", 8 | "defaultScope": "", 9 | "defaultSubject": "", 10 | "defaultBody": "", 11 | "defaultIssues": "", 12 | "types": { 13 | "feat": { 14 | "description": "新功能", 15 | "title": "新增" 16 | }, 17 | "fix": { 18 | "description": "bug 修复", 19 | "title": "修复" 20 | }, 21 | "wip": { 22 | "description": "功能正在开发中,提交以切换其他分支进行其他开发", 23 | "title": "进行中" 24 | }, 25 | "style": { 26 | "description": "UI相关", 27 | "title": "UI" 28 | }, 29 | "refactor": { 30 | "description": "代码重构", 31 | "title": "重构" 32 | }, 33 | "docs": { 34 | "description": "文档更新", 35 | "title": "文档" 36 | }, 37 | "build": { 38 | "description": "构建系统或者包依赖更新", 39 | "title": "构建系统或者包依赖更新" 40 | }, 41 | "ci": { 42 | "description": "CI/CD,自动构建,持续集成相关", 43 | "title": "CI/CD" 44 | }, 45 | "test": { 46 | "description": "测试用例相关", 47 | "title": "测试" 48 | }, 49 | "perf": { 50 | "description": "优化", 51 | "title": "优化" 52 | }, 53 | "chore": { 54 | "description": "非src或者测试文件的更新", 55 | "title": "非 src 或者 测试文件的更新" 56 | }, 57 | "revert": { 58 | "description": "回到上一个版本", 59 | "title": "回溯" 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/layout/AppLayout.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 43 | 44 | 58 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # 单元测试使用检查说明 2 | 3 | - [@vue/cli-plugin-unit-mocha](https://cli.vuejs.org/core-plugins/unit-mocha.html#injected-commands) 4 | - [chai](http://chaijs.com/) 5 | 6 | ## 测试用例常用方法 7 | 8 | > should 和 expect 是 TDD(Test-Driven Development)——测试驱动开发,assert BDD(Behavior-Driven Development)——行为驱动开发 9 | 10 | ### 连接器 11 | 12 | - to 13 | - be 14 | - been 15 | - is 16 | - that 17 | - which 18 | - and 19 | - has 20 | - have 21 | - with 22 | - at 23 | - of 24 | - same 25 | - but 26 | - does 27 | - still 28 | 29 | ### 断言 30 | 31 | - .not 32 | - .deep 33 | - nested 34 | - own 35 | - ordered 36 | - any 37 | - all 38 | - a 39 | - include 40 | - ok 41 | - true 42 | - false 43 | 44 | ### asset 45 | 46 | - [asset](https://www.chaijs.com/api/assert/) 47 | 48 | ## 测试用例 demo 49 | 50 | - should 51 | 52 | ```javascript 53 | chai.should() 54 | foo.should.be.a('string') 55 | foo.should.equal('bar') 56 | foo.should.have.lengthOf(3) 57 | tea.should.have.property('flavors').with.lengthOf(3) 58 | ``` 59 | 60 | - expect 61 | 62 | ```javascript 63 | var expect = chai.expect 64 | expect(foo).to.be.a('string') 65 | expect(foo).to.equal('bar') 66 | expect(foo).to.have.lengthOf(3) 67 | expect(tea).to.have.property('flavors').with.lengthOf(3) 68 | ``` 69 | 70 | - Assert 71 | 72 | ```javascript 73 | var assert = chai.assert 74 | 75 | assert.typeOf(foo, 'string') 76 | assert.equal(foo, 'bar') 77 | assert.lengthOf(foo, 3) 78 | assert.property(tea, 'flavors') 79 | assert.lengthOf(tea.flavors, 3) 80 | ``` 81 | -------------------------------------------------------------------------------- /src/api/cloudrole.ts: -------------------------------------------------------------------------------- 1 | import Axios from './axios' 2 | import { CloudRoleItem, HttpResponse } from '@/@types/index' 3 | import { AddCloudRoleItem } from '../@types/index' 4 | 5 | // 云角色相关的网络请求模块 6 | 7 | export class CloudRoleService { 8 | /** 9 | * @description 查询所在团队的的云角色列表 10 | * @param {number} teamId - 所要查询的团队ID 11 | * @return {HttpResponse} result 12 | */ 13 | static list(teamId: number): Promise { 14 | return Axios(`/bus/cloudRoleTeam/list`, { 15 | method: 'get', 16 | params: { 17 | teamId 18 | } 19 | }) 20 | } 21 | /** 22 | * @description 给团队添加成员 23 | * @param {AddCloudRoleItem} teamId - 团队 id 和云角色 id 24 | * @return {HttpResponse} result 25 | */ 26 | static add(data: AddCloudRoleItem): Promise { 27 | return Axios(`/bus/cloudRoleTeam`, { 28 | method: 'post', 29 | data 30 | }) 31 | } 32 | 33 | /** 34 | * @description 删除团队的云角色 35 | * @param {CloudRoleItem} ids - 云角色 id 36 | * @return {HttpResponse} result 37 | */ 38 | static update(teamCloudRole: CloudRoleItem): Promise { 39 | return Axios(`/bus/cloudRoleTeam/`, { 40 | method: 'put', 41 | data: { 42 | teamCloudRole 43 | } 44 | }) 45 | } 46 | 47 | /** 48 | * @description 删除团队的云角色 49 | * @param {string} ids - 云角色 id 50 | * @return {HttpResponse} result 51 | */ 52 | static delete(ids: string): Promise { 53 | return Axios(`/bus/cloudRoleTeam/${ids}`, { 54 | method: 'delete' 55 | }) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/store/utils.ts: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | 3 | // 定义 state 下的 module 值 4 | type ModuleNameType = 'app' | 'console' | 'user' 5 | 6 | /** 7 | * @description setStoreState -方法是一个 mutaitions 的操作 8 | * @type {T} T - 你要更改的模块的类型 9 | * @param {string} module - 要操作的state 的 module 名 10 | * @param {string} key - 要操作的state 的 module 下的 key 值 11 | * @param {any} value - 当有 msg 参数时,视为赋值操作,触发 mutation,msg 则为要复制的数据. 12 | * @example 如果需要更改 app 模块下的 theme为 dark,这样使用:setStoreState('app','theme','dark') 13 | * @example 目前只支持更改 module 的 state 第一层,不支持单独修改深层嵌套的 key,如需更改,请直接替换第一层的对象 14 | * 如 15 | * ``` const state = { 16 | * name: { 17 | * firstName:'jack', 18 | * lastName:'Ma' 19 | * } 20 | * } 21 | * ``` 22 | * 想要单独修改 firstName,直接使用 setStoreState('app','name',{firstName:'modifiedName',lastName:'Ma'}) 23 | */ 24 | 25 | export function setStoreState( 26 | module: ModuleNameType, 27 | key: keyof T, 28 | value: any 29 | ) { 30 | store.commit({ 31 | type: module + '/__set', 32 | key: key, 33 | val: value 34 | }) 35 | } 36 | 37 | /** 38 | * @description 封装 dispatch 方法 39 | * @type {T} T 你要派发actions的模块的类型 40 | * @example 使用方法如下 const result = await dispatchActions('console','refreshToken',1) 41 | */ 42 | export function dispatchAction( 43 | module: ModuleNameType, 44 | key: keyof T, 45 | value?: any 46 | ) { 47 | store.dispatch(`${module}/${key}`, value) 48 | } 49 | 50 | /** 51 | * @description 封装 dispatch 方法 52 | * @type {T} T 你要获取 getters的模块的类型 53 | * @example 使用方法如下 const result = getStoreGetter('console','list') 54 | */ 55 | export function getStoreGetter(module: ModuleNameType, key: keyof T) { 56 | return store.getters[`${module}/${key}`] 57 | } 58 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 37 | 38 | 79 | -------------------------------------------------------------------------------- /src/components/ChangeLanguage.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 68 | 69 | 90 | -------------------------------------------------------------------------------- /src/i18n/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * vue-i18n 使用姿势说明 3 | * see more : https://pikax.me/vue-composable/composable/i18n/i18n.html#parameters 4 | */ 5 | 6 | import { includes } from 'lodash' 7 | import moment from 'moment' 8 | import { findKeyByValue } from '@/utils/common' 9 | import { useI18n } from 'vue-composable' 10 | import zhCN from '@/i18n/messages/zhCN' 11 | import en from '@/i18n/messages/en' 12 | import store from '@/store' 13 | import { setStoreState } from '../store/utils' 14 | 15 | const __LOCALE__ = store.state.app.language 16 | 17 | if (!__LOCALE__) { 18 | //__LOCALE__ = window.navigator.language.split('-').join('') 19 | setStoreState('app', 'language', 'zhCN') 20 | } 21 | 22 | /** 定义语言模版 */ 23 | export const Locales: any = {} 24 | 25 | /** 26 | * @todo 语言名字命名要考虑三个部分:一是 antdv 组件国际化的语言名字,二是 i18n模版语言的命名,三是浏览器对于语言的命名(这里会跟 http 请* 求相关,也是后端能识别的语言命名),因此要将前两种语言的名字通过字典转换成标准名称,也就是浏览器的语言名使用SO 639-1标准 27 | */ 28 | 29 | export const TranslateTable: { [key: string]: string } = { 30 | en: 'en_US', 31 | zhCN: 'zh_CN' 32 | } 33 | 34 | export const LanguageNameList: { [key: string]: string } = { 35 | en: 'English', 36 | zhCN: '简体(中文)' 37 | } 38 | 39 | export const i18nInstance = useI18n({ 40 | locale: 'zhCN', 41 | messages: { 42 | zhCN, 43 | en 44 | } 45 | }) 46 | 47 | /** 48 | * @description 自动加载 antd-vue 需要的语言模版 49 | */ 50 | function loadAtdLocales() { 51 | const files = require.context('../../node_modules/ant-design-vue/es/locale-provider', true, /\.js$/) 52 | files.keys().forEach(key => { 53 | const fileName = key.slice(2, key.lastIndexOf('.')) 54 | if (includes(TranslateTable, fileName)) { 55 | const localeKey = findKeyByValue(TranslateTable, fileName) 56 | if (localeKey) { 57 | Locales[localeKey] = files(key).default 58 | } 59 | } 60 | }) 61 | } 62 | 63 | /** 64 | * @functin setLang - set the app's language 65 | * @param {string} lang - the language will be setted 66 | * @return {string} lang - langguage name 67 | */ 68 | 69 | function _set(lang: keyof typeof TranslateTable): keyof typeof TranslateTable { 70 | i18nInstance.locale.value = lang as any 71 | // 设置当前语言的时间 72 | moment.locale(TranslateTable[lang]) 73 | // Axios.defaults.headers.common['Accept-Language'] = lang 74 | setStoreState('app', 'language', lang) 75 | return lang 76 | } 77 | 78 | /** 79 | * @functin 异步加载自定义的 i18n 模版 80 | * @param {string} lang - 将要更换的语言 81 | * @return {string} lang - 返回将要更改的语言明后才能 82 | */ 83 | export function setLang(lang: string): Promise { 84 | if (lang === i18nInstance.locale.value) { 85 | return Promise.resolve('same') 86 | } 87 | return Promise.resolve(_set(lang)) 88 | } 89 | 90 | /* 加载 antd 模版 */ 91 | loadAtdLocales() 92 | 93 | /** 设置初始化语言 */ 94 | setLang(__LOCALE__) 95 | -------------------------------------------------------------------------------- /src/components/ImageDisplay.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 50 | 51 | 111 | -------------------------------------------------------------------------------- /src/components/TeamMemberItem.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 55 | 56 | 105 | -------------------------------------------------------------------------------- /src/components/Slider.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 61 | 62 | 129 | -------------------------------------------------------------------------------- /src/store/modules/console/actions.ts: -------------------------------------------------------------------------------- 1 | import TeamService from '../../../api/team' 2 | import { HttpListQuery, StateType, HttpResponse } from '@/@types/index' 3 | import { setStoreState } from '../../utils' 4 | import { Store } from 'vuex' 5 | import CommonService from '@/api/common' 6 | import ManageService from '@/api/manage' 7 | import { StaticConfig } from '@/config/app' 8 | import { RoleType } from '../../../@types/index' 9 | import { CloudRoleService } from '../../../api/cloudrole' 10 | 11 | /** 12 | * @description 所有跟控制台相关的内容 13 | * @return status 返回状态 err_code:1,逻辑正确,err_code:0,发生错误。 14 | */ 15 | 16 | const consoleActions = { 17 | // 获取用户角色列表 18 | async getRoleList(): Promise { 19 | const res = await CommonService.getRoleInfoList() 20 | if (res.status === 200) { 21 | const data = res.data.data 22 | setStoreState('console', 'roleList', data ? data : []) 23 | return res 24 | } 25 | return 0 26 | }, 27 | 28 | // 获取团队列表 29 | async getTeamList(context: Store, params: HttpListQuery): Promise { 30 | const res = await TeamService.list({ ...params }) 31 | if (res.status === 200) { 32 | const data = res.data.data 33 | setStoreState('console', 'myTeamList', data.rows ? data.rows : []) 34 | return res 35 | } 36 | return 0 37 | }, 38 | 39 | // 获取某个团队的所有成员信息 40 | async getTeamMemberList(context: Store, id: number): Promise { 41 | const res = await TeamService.memberList(id as number) 42 | if (res.status === 200) { 43 | const memberList = res.data.data.rows.filter((item: any) => [RoleType['团队超级管理员'], RoleType['团队成员'], RoleType['团队访客'], RoleType['团队管理员']].includes(item.roleId)) 44 | setStoreState('console', 'selectedTeamMemberList', memberList) 45 | } 46 | return 0 47 | }, 48 | 49 | // 获取某个团队的详细信息 50 | async getTeamDetail(context: Store, id: number): Promise { 51 | console.log('chufa') 52 | const res = await TeamService.detail(id) 53 | if (res.status === 200) { 54 | const data = res.data.data 55 | setStoreState('console', 'selectedTeam', data) 56 | return data 57 | } 58 | return 0 59 | }, 60 | 61 | // 获取某个主账号下的所有的 62 | async getSubAccountList(): Promise { 63 | const res = await ManageService.getSubAccountList({ 64 | pageSize: StaticConfig.MaxPageSize, 65 | pageNum: 1 66 | }) 67 | if (res.status === 200) { 68 | const data = res.data.data.rows 69 | setStoreState('console', 'subAccountList', data) 70 | return data 71 | } 72 | return 0 73 | }, 74 | 75 | // 获取当前选择团队下所有的云角色列表 76 | async getTeamCloudRoleList(context: Store, teamId: number): Promise { 77 | console.log(`id=${teamId}`) 78 | const res = await CloudRoleService.list(teamId) 79 | if (res.status === 200) { 80 | const data = res.data.data 81 | console.log(data) 82 | setStoreState('console', 'selectedTeamCloudRoleList', data) 83 | return res 84 | } 85 | return 0 86 | } 87 | } 88 | 89 | type ConsoleActionsType = keyof typeof consoleActions 90 | 91 | export { ConsoleActionsType } 92 | export default consoleActions 93 | -------------------------------------------------------------------------------- /src/api/manage.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 点击右上角头像跳转的所有管理页面接口 3 | */ 4 | 5 | import { HttpResponse } from '@/@types' 6 | import Axios from './axios' 7 | 8 | /** 9 | * @interface ModifyProfileParams -修改个人信息参数 10 | * @property {string} userId -用户id 11 | * @property {string} nickName -昵称 12 | */ 13 | interface ModifyProfileParams { 14 | userId: number 15 | nickName?: string 16 | } 17 | 18 | /** 19 | * @interface ChangePasswordParams -修改密码参数 20 | * @property {string} oldPassword -昵称 21 | * @property {string} newPassword -密码 22 | */ 23 | interface ChangePasswordParams { 24 | oldPassword: string 25 | newPassword: string 26 | } 27 | 28 | /** 29 | * @interface CreateSubAccountParams -创建子账号参数 30 | * @property {string} username -用户名 31 | * @property {string} password -密码 32 | */ 33 | interface CreateSubAccountParams { 34 | username: string 35 | password: string 36 | } 37 | 38 | /** 39 | * @interface GetSubAccountListParams -获取子账号列表参数 40 | * @property {number} pageNum -页码 41 | * @property {number} pageSize -每页数量 42 | */ 43 | interface GetSubAccountListParams { 44 | pageNum: number 45 | pageSize: number 46 | } 47 | 48 | /** 49 | * @interface ResetSubAccountPasswordParams -重置子账号密码参数 50 | * @property {number} userId -用户id 51 | * @property {string} password -新密码 52 | */ 53 | interface ResetSubAccountPasswordParams { 54 | id: number 55 | passWord: string 56 | } 57 | 58 | /** 59 | * @interface FrezeeSubAccountParams -冻结子账号参数 60 | * @property {string} ids -子账号id 61 | */ 62 | interface FrezeeSubAccountParams { 63 | ids: string 64 | } 65 | 66 | class ManageService { 67 | // 修改个人信息 68 | static modifyProfile(params: ModifyProfileParams): Promise { 69 | return Axios('/bus/user', { 70 | method: 'put', 71 | responseType: 'json', 72 | data: params 73 | }) 74 | } 75 | 76 | // 修改密码 77 | static changePassword(params: ChangePasswordParams): Promise { 78 | return Axios('/bus/user/password', { 79 | method: 'put', 80 | responseType: 'json', 81 | params 82 | }) 83 | } 84 | 85 | // 创建子账号 86 | static createSubAccount( 87 | params: CreateSubAccountParams 88 | ): Promise { 89 | return Axios('/bus/subAccount', { 90 | method: 'post', 91 | responseType: 'json', 92 | data: params 93 | }) 94 | } 95 | 96 | // 获取子账号列表 97 | static getSubAccountList( 98 | params: GetSubAccountListParams 99 | ): Promise { 100 | return Axios('/bus/subAccount/list', { 101 | method: 'get', 102 | responseType: 'json', 103 | params 104 | }) 105 | } 106 | 107 | // 重置子账号密码 108 | static resetSubAccountPassword( 109 | params: ResetSubAccountPasswordParams 110 | ): Promise { 111 | return Axios('/bus/subAccount/password/reset', { 112 | method: 'put', 113 | responseType: 'json', 114 | data: [params] 115 | }) 116 | } 117 | 118 | // 冻结子账号 119 | static frezeeSubAccount( 120 | params: FrezeeSubAccountParams 121 | ): Promise { 122 | return Axios('/bus/subAccount/lock', { 123 | method: 'put', 124 | responseType: 'json', 125 | params 126 | }) 127 | } 128 | } 129 | 130 | export default ManageService 131 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue3-base-type", 3 | "version": "0.1.22", 4 | "description": "一个使用vue3+typescript 搭建的项目基础架构类型声明库", 5 | "author": { 6 | "name": "ibwei", 7 | "email": "997132391@qq.com", 8 | "url": "http://me.ibwei.com" 9 | }, 10 | "scripts": { 11 | "serve": "vue-cli-service serve --stats-json", 12 | "build": "vue-cli-service build", 13 | "lint": "vue-cli-service lint", 14 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -w -r 0 -s", 15 | "commit": "npx cz", 16 | "docs": "typedoc --out ./docs/ ./src/ && npm run serve:docs", 17 | "serve:docs": "http-server ./docs -p 3001 -o", 18 | "test:unit": "vue-cli-service test:unit", 19 | "test-dev:unit": "vue-cli-service test:unit --watch", 20 | "test:api": "vue-cli-service test:unit ./tests/api/*.spec.ts", 21 | "test-dev:api": "vue-cli-service test:unit ./tests/api/*.spec.ts --watch", 22 | "analysis": "cross-env use_analyzer=true vue-cli-service build", 23 | "prepare": "husky install" 24 | }, 25 | "main": "dist/index.js", 26 | "files": [ 27 | "@types" 28 | ], 29 | "dependencies": { 30 | "@vueuse/core": "^4.0.0-beta.16", 31 | "ant-design-vue": "^2.0.0-beta.9", 32 | "axios": "^0.20.0", 33 | "core-js": "^3.6.5", 34 | "moment": "^2.27.0", 35 | "vue": "^3.0.0", 36 | "vue-composable": "^1.0.0-beta.10", 37 | "vue-router": "^4.0.0-0", 38 | "vuex": "^4.0.0-0", 39 | "vuex-persistedstate": "^3.1.0" 40 | }, 41 | "devDependencies": { 42 | "@commitlint/cli": "^12.0.1", 43 | "@commitlint/config-conventional": "^12.0.1", 44 | "@types/chai": "^4.2.11", 45 | "@types/lodash": "^4.14.161", 46 | "@types/mocha": "^5.2.4", 47 | "@typescript-eslint/eslint-plugin": "^2.33.0", 48 | "@typescript-eslint/parser": "^2.33.0", 49 | "@vue/cli-plugin-babel": "~4.5.0", 50 | "@vue/cli-plugin-eslint": "~4.5.0", 51 | "@vue/cli-plugin-router": "~4.5.0", 52 | "@vue/cli-plugin-typescript": "~4.5.0", 53 | "@vue/cli-plugin-unit-mocha": "~4.5.0", 54 | "@vue/cli-plugin-vuex": "~4.5.0", 55 | "@vue/cli-service": "~4.5.0", 56 | "@vue/compiler-sfc": "^3.0.0-0", 57 | "@vue/eslint-config-prettier": "^6.0.0", 58 | "@vue/eslint-config-typescript": "^5.0.2", 59 | "@vue/test-utils": "^2.0.0-0", 60 | "babel-eslint": "^10.1.0", 61 | "babel-plugin-import": "^1.13.0", 62 | "chai": "^4.1.2", 63 | "commitizen": "^4.2.1", 64 | "cross-env": "^7.0.3", 65 | "cz-conventional-changelog": "3.3.0", 66 | "deepmerge": "^4.2.2", 67 | "eslint": "^6.7.2", 68 | "eslint-config-prettier": "^6.11.0", 69 | "eslint-plugin-import": "^2.20.2", 70 | "eslint-plugin-node": "^11.1.0", 71 | "eslint-plugin-prettier": "^3.1.3", 72 | "eslint-plugin-promise": "^4.2.1", 73 | "eslint-plugin-standard": "^4.0.0", 74 | "eslint-plugin-vue": "^7.0.0-0", 75 | "http-server": "^0.12.3", 76 | "husky": "^6.0.0", 77 | "less": "^2.7.0", 78 | "less-loader": "^5.0.0", 79 | "lint-staged": "^10.5.1", 80 | "prettier": "^1.19.1", 81 | "style-resources-loader": "^1.3.2", 82 | "ts-node": "^9.0.0", 83 | "typedoc": "^0.19.0", 84 | "typescript": "4.0.2", 85 | "vue-cli-plugin-style-resources-loader": "~0.1.4", 86 | "webpack-bundle-analyzer": "^4.3.0" 87 | }, 88 | "lint-staged": { 89 | "src/**/*.{jsx,txs,ts,js,json,vue,md}": [ 90 | "vue-cli-service lint", 91 | "git add" 92 | ] 93 | }, 94 | "browserslist": [ 95 | "> 1%", 96 | "last 2 versions", 97 | "not dead" 98 | ], 99 | "keywords": [ 100 | "vue3", 101 | "typescript", 102 | "base" 103 | ], 104 | "license": "MIT", 105 | "repository": { 106 | "type": "git", 107 | "url": "https://github.com/ibwei/vue3-base" 108 | }, 109 | "types": "./@types/index.d.ts" 110 | } 111 | -------------------------------------------------------------------------------- /src/api/team.ts: -------------------------------------------------------------------------------- 1 | import Axios from './axios' 2 | import { AddTeamGroupMemberParams } from '@/@types/index' 3 | import { StaticConfig } from '@/config/app' 4 | import { 5 | HttpResponse, 6 | HttpListQuery, 7 | TeamMemberType, 8 | AddTeamGroupParams 9 | } from '@/@types/index' 10 | 11 | /** 12 | * @description 团队网络请求模块,所有跟团队相关的 api 放在此处 13 | */ 14 | 15 | // 新增团队类型 16 | interface CreateParamType { 17 | name: string 18 | description: string 19 | } 20 | 21 | class TeamService { 22 | // 添加团队 23 | static create(params: CreateParamType): Promise { 24 | return Axios.post('/bus/team', params, { 25 | responseType: 'json' 26 | }) 27 | } 28 | 29 | // 所有的团队信息 30 | static update(data: TeamMemberType): Promise { 31 | return Axios.put('/bus/team', { 32 | ...data 33 | }) 34 | } 35 | 36 | // 用户当前所在的团队信息 37 | static list(params: HttpListQuery): Promise { 38 | return Axios.get('/bus/team/list', { 39 | params 40 | }) 41 | } 42 | 43 | // 所有的团队信息 44 | static allList(params: HttpListQuery): Promise { 45 | return Axios.get('/bus/team/listAll', { 46 | params 47 | }) 48 | } 49 | 50 | // 所有的团队信息 51 | static detail(id: number): Promise { 52 | return Axios.get(`/bus/team/${id}`) 53 | } 54 | 55 | // 删除团队 56 | static delete(id: number): Promise { 57 | return Axios.delete(`/bus/team/${id}`) 58 | } 59 | 60 | // 获取团队成员列表 61 | static memberList(teamId: number): Promise { 62 | return Axios.get(`/bus/teamMember/list/${teamId}`, { 63 | params: { 64 | pageSize: StaticConfig.MaxPageSize, 65 | pageNum: 1 66 | } 67 | }) 68 | } 69 | 70 | // 编辑团队成员 71 | static updateMember(list: TeamMemberType): Promise { 72 | return Axios(`/bus/teamMember/`, { 73 | method: 'put', 74 | data: list 75 | }) 76 | } 77 | 78 | // 批量添加团队成员 79 | static addMember(list: TeamMemberType[]): Promise { 80 | return Axios(`/bus/teamMember/`, { 81 | method: 'post', 82 | data: list 83 | }) 84 | } 85 | 86 | // 批量删除团队成员 87 | static deleteMember(list: TeamMemberType[]): Promise { 88 | const path = list.map((item) => item.id).join(',') 89 | console.log('deletepath', path) 90 | return Axios(`/bus/teamMember/${path}`, { 91 | method: 'delete' 92 | }) 93 | } 94 | 95 | // 添加团队分组 96 | static addTeamGroup(data: AddTeamGroupParams): Promise { 97 | return Axios(`/bus/userGroup`, { 98 | method: 'post', 99 | data 100 | }) 101 | } 102 | 103 | // 查询团队分组列表 104 | static getTeamGroupList(): Promise { 105 | return Axios(`/bus/userGroup/list`, { 106 | method: 'get', 107 | params: { 108 | pageSize: StaticConfig.MaxPageSize, 109 | pageNum: 1 110 | } 111 | }) 112 | } 113 | 114 | // 删除团队分组 115 | static deleteTeamGroup(ids: number[]): Promise { 116 | const path = ids.join(',') 117 | return Axios(`/bus/userGroup/${path}`, { 118 | method: 'delete' 119 | }) 120 | } 121 | 122 | // 删除团队分组成员 123 | static deleteTeamGroupMember(ids: number[]): Promise { 124 | const path = ids.join(',') 125 | return Axios(`/bus/userGroup/userGroupMember/${path}`, { 126 | method: 'delete' 127 | }) 128 | } 129 | // 添加团队成员 130 | static addTeamGroupMember( 131 | data: AddTeamGroupMemberParams[] 132 | ): Promise { 133 | return Axios(`/bus/userGroup/userGroupMember`, { 134 | method: 'post', 135 | data 136 | }) 137 | } 138 | 139 | // 获取团队分组里的成员列表 140 | static getTeamGroupMemberList(id: number): Promise { 141 | return Axios(`/bus/userGroup/userGroupMember/list/${id}`, { 142 | method: 'get', 143 | params: { 144 | pageSize: StaticConfig.MaxPageSize, 145 | pageNum: 1 146 | } 147 | }) 148 | } 149 | } 150 | 151 | export default TeamService 152 | -------------------------------------------------------------------------------- /src/api/axios.ts: -------------------------------------------------------------------------------- 1 | import Axios, { AxiosResponse, AxiosRequestConfig, AxiosError } from 'axios' 2 | import router from '@/router' 3 | import { message } from 'ant-design-vue' 4 | import Store from '@/store' 5 | 6 | /** 7 | * get status code 8 | * @param {AxiosResponse} response Axios response object 9 | */ 10 | const getErrorCode2text = (response: AxiosResponse): string => { 11 | /** http status code */ 12 | const code = response.status 13 | /** notice text */ 14 | let message = 'Request Error' 15 | switch (code) { 16 | case 400: 17 | message = 'Request Error' 18 | break 19 | case 401: 20 | message = 'Unauthorized, please login' 21 | break 22 | case 403: 23 | message = '拒绝访问' 24 | break 25 | case 404: 26 | message = '访问资源不存在' 27 | break 28 | case 408: 29 | message = '请求超时' 30 | break 31 | case 500: 32 | message = '位置错误' 33 | break 34 | case 501: 35 | message = '承载服务未实现' 36 | break 37 | case 502: 38 | message = '网关错误' 39 | break 40 | case 503: 41 | message = '服务暂不可用' 42 | break 43 | case 504: 44 | message = '网关超时' 45 | break 46 | case 505: 47 | message = '暂不支持的 HTTP 版本' 48 | break 49 | default: 50 | message = '位置错误' 51 | } 52 | return message 53 | } 54 | 55 | /** 56 | * @returns {AxiosResponse} result 57 | * @tutorial see more:https://github.com/onlyling/some-demo/tree/master/typescript-width-axios 58 | * @example 59 | * service.get<{data: string; code: number}>('/test').then(({data}) => { console.log(data.code) }) 60 | */ 61 | const service = Axios.create({ 62 | baseURL: process.env.VUE_APP_BASE_URL, 63 | timeout: 10000, 64 | headers: { 65 | 'User-Type': 'bus' 66 | } 67 | }) 68 | 69 | /** 70 | * @description 请求发起前的拦截器 71 | * @returns {AxiosRequestConfig} config 72 | */ 73 | service.interceptors.request.use(async (config: AxiosRequestConfig) => { 74 | // 如果是获取token接口: 75 | if (config.url === '/auth/oauth/token') { 76 | let userAccount = '' 77 | // 若存在username,则为登录情况,判断user-account 78 | if (config.params.username) { 79 | userAccount = config.params.username.includes('-') 80 | ? 'ACCOUNT_USER' 81 | : 'ADMIN_USER' 82 | } else { 83 | // 刷新token情况,通过用户信息email是否有值判断 84 | userAccount = Store.state.user.userDetail.email 85 | ? 'ADMIN_USER' 86 | : 'ACCOUNT_USER' 87 | } 88 | 89 | config.headers['User-Account'] = userAccount 90 | config.headers.Authorization = 'Basic ZmViczoxMjM0NTY=' 91 | } else { 92 | // 如果保存有token,则取,否则不添加Authorization 93 | if (localStorage.vuex && JSON.parse(localStorage.vuex).user?.token) { 94 | const token = Store.state.user?.token 95 | const tokenType = token.token_type 96 | const accessToken = token.access_token 97 | config.headers.Authorization = `${tokenType} ${accessToken}` 98 | } 99 | } 100 | 101 | return config 102 | }) 103 | 104 | /** 105 | * @description 响应收到后的拦截器 106 | * @returns {} 107 | */ 108 | service.interceptors.response.use( 109 | /** 请求有响应 */ 110 | async (response: AxiosResponse) => { 111 | if (response.status === 200) { 112 | return Promise.resolve(response) 113 | } else { 114 | const __text = getErrorCode2text(response) 115 | return Promise.reject(new Error(__text)) 116 | } 117 | }, 118 | /** 请求无响应 */ 119 | (error: AxiosError) => { 120 | let __emsg: string = error.message || '' 121 | 122 | if (error.message) { 123 | __emsg = error.message 124 | } 125 | 126 | if (error.response) { 127 | __emsg = error.response.data.message 128 | ? error.response.data.message 129 | : error.response.data.data 130 | } 131 | // timeout 132 | if (__emsg.indexOf('timeout') >= 0) { 133 | __emsg = 'timeout' 134 | } 135 | 136 | if (error?.response?.status === 401) { 137 | if (router.currentRoute.value.path !== '/entry/login') { 138 | message.info('登录凭证已过期,请重新登录') 139 | router.push('/entry/login') 140 | } 141 | return Promise.reject(new Error('401')) 142 | } 143 | return Promise.reject(new Error(__emsg)) 144 | } 145 | ) 146 | 147 | export default service 148 | -------------------------------------------------------------------------------- /src/api/user.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 所有跟用户相关的接口 3 | */ 4 | 5 | import { HttpResponse } from '@/@types' 6 | import Axios from './axios' 7 | 8 | interface HttpParams { 9 | coinName: string 10 | cashName: string 11 | } 12 | 13 | /** 14 | * @interface loginParams -登录参数 15 | * @property {string} grant_type -授权类型 16 | * @property {string} email -邮箱 17 | * @property {string} password -用户密码 18 | */ 19 | interface LoginParams { 20 | grant_type: string 21 | username: string 22 | password: string 23 | } 24 | 25 | /** 26 | * @interface RefreshTokenParams -刷新令牌参数 27 | * @property {string} refresh_token -refresh_token 28 | */ 29 | interface RefreshTokenParams { 30 | refresh_token: string 31 | } 32 | 33 | /** 34 | * @interface SendEmailCodeParams -发送邮件验证码参数 35 | * @property {string} email -邮箱 36 | */ 37 | interface SendEmailCodeParams { 38 | email: string 39 | } 40 | 41 | /** 42 | * @interface VerifyEmailCodeParams -验证邮件验证码参数 43 | * @property {string} email -邮箱 44 | * @property {string} code -验证码 45 | */ 46 | interface VerifyEmailCodeParams { 47 | email: string 48 | code: string 49 | } 50 | 51 | /** 52 | * @interface SendPhoneCodeParams -发送手机验证码参数 53 | * @property {string} phone -手机号 54 | */ 55 | interface SendPhoneCodeParams { 56 | phone: string 57 | } 58 | 59 | /** 60 | * @interface BindPhoneParams -绑定手机参数 61 | * @property {string} phone -手机号 62 | * @property {string} code -手机验证码 63 | */ 64 | interface BindPhoneParams { 65 | phone: string 66 | code: string 67 | } 68 | 69 | /** 70 | * @interface registerParams -注册参数 71 | * @property {string} email -邮箱 72 | * @property {string} password -用户密码 73 | * @property {string} code -验证码 74 | */ 75 | interface RegisterParams { 76 | email: string 77 | password: string 78 | code: string 79 | } 80 | 81 | export interface UserApi { 82 | coin2cash(param: HttpParams): Promise 83 | } 84 | 85 | /** 86 | * @example Axios.get(`https://xxx.com}`) 87 | * @todo Get the exchange rate of the current currency 88 | */ 89 | 90 | class UserService { 91 | // 登录 92 | static async login(params: LoginParams): Promise { 93 | return Axios('/auth/oauth/token', { 94 | method: 'post', 95 | responseType: 'json', 96 | params: params 97 | }) 98 | } 99 | 100 | // 刷新令牌 101 | static async refreshToken(params: RefreshTokenParams): Promise { 102 | return Axios('/auth/oauth/token', { 103 | method: 'post', 104 | responseType: 'json', 105 | params: { 106 | grant_type: 'refresh_token', 107 | ...params 108 | } 109 | }) 110 | } 111 | 112 | // 获取用户信息 113 | static getUserDetail(): Promise { 114 | return Axios('/bus/user/userDetail', { 115 | method: 'get', 116 | responseType: 'json' 117 | }) 118 | } 119 | 120 | // 添加登录记录 121 | static addLoginLog(): Promise { 122 | return Axios('/bus/user/success', { 123 | method: 'get', 124 | responseType: 'json' 125 | }) 126 | } 127 | 128 | // 发送邮箱验证码 129 | static sendEmailCode(params: SendEmailCodeParams): Promise { 130 | return Axios('/bus/common/sendEmailCode', { 131 | method: 'get', 132 | responseType: 'json', 133 | params 134 | }) 135 | } 136 | 137 | // 验证邮箱验证码 138 | static verifyEmailCode(params: VerifyEmailCodeParams): Promise { 139 | return Axios('/bus/common/verifyEmailCode', { 140 | method: 'post', 141 | responseType: 'json', 142 | params 143 | }) 144 | } 145 | 146 | // 发送手机验证码 147 | static sendPhoneCode(params: SendPhoneCodeParams): Promise { 148 | return Axios('/bus/common/sendPhoneCode', { 149 | method: 'get', 150 | responseType: 'json', 151 | params 152 | }) 153 | } 154 | 155 | // 绑定手机 156 | static bindPhone(params: BindPhoneParams): Promise { 157 | return Axios('/bus/user/bindingPhone', { 158 | method: 'post', 159 | responseType: 'json', 160 | params 161 | }) 162 | } 163 | 164 | // 注册 165 | static register(params: RegisterParams): Promise { 166 | return Axios('/bus/user/register', { 167 | method: 'post', 168 | responseType: 'json', 169 | data: params 170 | }) 171 | } 172 | } 173 | 174 | export default UserService 175 | -------------------------------------------------------------------------------- /src/@types/index.ts: -------------------------------------------------------------------------------- 1 | import { AppStateType } from '@/store/modules/app/state' 2 | import { ConsoleStateType } from '@/store/modules/console/state' 3 | import { UserStateType } from '@/store/modules/user/state' 4 | import { TeamStateType } from '@/store/modules/user/modules/team/state' 5 | 6 | // vuex state 的模块的类型 7 | type ModuleType = { 8 | app: AppStateType 9 | console: ConsoleStateType 10 | user: UserStateType & { team: TeamStateType } 11 | } 12 | 13 | // 所有的StateType 14 | export type StateType = ModuleType 15 | 16 | /** http请求响应格式 */ 17 | export declare interface ApiResponse { 18 | errCode: number 19 | errMsg?: string 20 | data?: any 21 | } 22 | 23 | // ant-design-button 颜色 24 | export type ButtonColorType = 25 | | 'primary' 26 | | 'danger' 27 | | 'dashed' 28 | | 'ghost' 29 | | 'default' 30 | | 'link' 31 | 32 | // icon的类型 33 | export type IconType = 'icon' | 'iconfont' 34 | 35 | // 对话框打开类型 36 | export type ModalOpenMode = 'edit' | 'add' | 'other' 37 | 38 | export interface BasicUserType { 39 | id: number 40 | name?: string 41 | avatar?: string 42 | role?: string 43 | department?: string 44 | code?: string 45 | createTime?: string 46 | description?: string 47 | email?: string 48 | lastLoginTime?: string 49 | modifyTime?: string 50 | modifyUser?: number 51 | nickName?: string 52 | phone?: string 53 | roleId?: number 54 | roleName?: string 55 | status?: number 56 | tenantId?: number 57 | type?: string 58 | userId?: number 59 | username?: string 60 | cloudRole?: string 61 | } 62 | 63 | export interface ListParamType { 64 | id: number 65 | pageSize: number 66 | pageNum: number 67 | } 68 | 69 | // 接口响应通过格式 70 | export interface HttpResponse { 71 | status: number 72 | statusText: string 73 | data: { 74 | code: number 75 | desc: string 76 | [key: string]: any 77 | } 78 | } 79 | 80 | // 接口请求列表通用参数配置 81 | export interface HttpListQuery { 82 | pageNum?: number 83 | pageSize?: number 84 | orderNum?: number 85 | [key: string]: any 86 | } 87 | 88 | // 团队列表类型 89 | export interface TeamListType { 90 | createTime?: string 91 | description?: string 92 | id?: number 93 | memberNum?: number 94 | name?: string 95 | orderNum?: number 96 | projectNum?: number 97 | tenantId?: number 98 | roleId?: number // 用户在当前所在团队的权限 99 | } 100 | 101 | // 批量添加团队成员列表 102 | export interface TeamMemberType { 103 | id?: number 104 | roleId?: number 105 | status?: number 106 | teamId?: number 107 | tenantId?: number 108 | toolRole?: string 109 | userId?: number 110 | userTenantId?: number 111 | cloudRole?: string 112 | } 113 | 114 | export enum RoleType { 115 | '超级管理员' = 1, 116 | '子账号', 117 | '团队超级管理员', 118 | '团队管理员', 119 | '团队成员', 120 | '团队访客', 121 | '项目管理员', 122 | '项目成员', 123 | '项目访客' 124 | } 125 | 126 | // 权限列表类型 127 | export interface RoleItemType { 128 | createTime: string 129 | id: number 130 | roleId: number 131 | modifyTime: string 132 | parentId: number 133 | remark: string 134 | roleName: keyof typeof RoleType 135 | type: number 136 | menuIds: string 137 | } 138 | 139 | export interface AddTeamGroupParams { 140 | description?: string 141 | id?: number 142 | teamId?: number 143 | name: string 144 | tenantId?: number 145 | } 146 | 147 | export interface AddTeamGroupMemberParams { 148 | groupId: number 149 | id?: number 150 | userId: number 151 | } 152 | 153 | export interface AddCloudRoleItem { 154 | cloudRoleId: number 155 | teamId: number 156 | tenantId: number 157 | } 158 | // 云角色成员列表类型 159 | export interface CloudRoleItem extends AddCloudRoleItem { 160 | allocatedNum: number 161 | cloudRoleNum: number 162 | endTime: string 163 | id: number 164 | name: string 165 | unallocatedNum: number 166 | cloudRoleName: string 167 | toolId: number 168 | } 169 | 170 | // 更改成员云角色需要的传参 171 | export interface UpdateMemberRoleParams { 172 | cloudRole: string 173 | id: number 174 | teamId: number 175 | tenantId: number 176 | userId: number 177 | } 178 | 179 | // 云角色列表项类型 180 | export type CloudRoleItemType = CloudRoleItem & { members: BasicUserType[] } 181 | 182 | // 云角色成员编辑的列表项类型 183 | 184 | export type EditCloudRoleItemType = { 185 | members: BasicUserType[] 186 | isCheck: boolean 187 | cloudRole: string 188 | teamId: number 189 | tenantId: number 190 | userId: number 191 | allocatedNum: number 192 | cloudRoleNum: number 193 | endTime: string 194 | id: number 195 | name: string 196 | unallocatedNum: number 197 | cloudRoleName: string 198 | toolId: number 199 | cloudRoleId: number 200 | } 201 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 项目基础代码架构说明 2 | 3 | - 如果对你有帮助的话,欢迎star 4 | - demo 演示地址:[https://ibwei.github.io/vue3-ts-base/](https://ibwei.github.io/vue3-ts-base/) 5 | #### 包管理工具 6 | 7 | - 建议使用 yarn,也是 vue-cli4.0+ 默认工具 8 | 9 | #### 主要用到的库 10 | 11 | - vue 全家桶 vue3 + vue-router + vuex + typescript 12 | - http 请求:axios 13 | - ui 库:ant-design-vue. 14 | - 提交规范:git cz commitizen 15 | - 版本更改历史: changelog 16 | - 文档工具:typedoc 17 | - 代码检查:eslint+eslint-typescript,格式化:prettier.提交之前检查与修复:lint-staged 18 | - 测试用例:mocha,ts-node 19 | - webpack 插件:style-resources-loader(全局 less)webpack-bundle-analyzer(包分析工具) splitChunks(代码分离) 20 | 21 | #### 代码基础架构说明 22 | 23 | ``` 24 | |-- 根目录 25 | |-- dist 项目 build 之后的文件夹 26 | |-- docs 文档生成的根目录位置 27 | |-- public 项目静态资源,不经过 webpack,以及默认的模版,适合存放第三方压缩好的资源 28 | |-- src 主要的开发目录 29 | | |-- @types 项目共用的 type 30 | | |-- App.vue 页面渲染根节点 31 | | |-- main.ts 入口文件 32 | | |-- shims-vue.d.ts vue 文件类型的 type 33 | | |-- api http 请求相关 34 | | | |-- apiList.ts api 接口列表 35 | | | |-- axios.ts 业务请求封装 36 | | | |-- editor.ts 其他业务封装 37 | | | |-- user.ts api 请求模块 38 | | |-- assets 存放静态资源,这个文件夹下的文件会走 webpack 压缩流程 39 | | |-- components 40 | | | |-- index.ts 自动注册脚本 41 | | | |-- global 自动注册的全局组件 42 | | | |-- ...其他非全局注册的模块 43 | | |-- config 全局静态配置,不可更改项 44 | | |-- layout 页面页面骨架 45 | | |-- plugins 存放第三方插件 46 | | | |-- index.ts 插件挂载入口 47 | | |-- router 路由 48 | | | |-- index.ts 路由入口 49 | | |-- store vuex 50 | | | |-- modules 多个模块 51 | | | |-- index.ts 自动装载模块 52 | | | |-- app app 模块 53 | | |-- styles 全局样式,一句 ui 库主题样式 54 | | | |-- \_variables.less 55 | | | |-- test.less 56 | | |-- utils 常用函数以及其他有用工具 57 | | | |-- common.ts 58 | | |-- views 页面级组件 59 | | |-- Home.vue 正常页面 60 | | |-- test 组件测试页面 61 | | |-- Test.vue 62 | |-- tests 测试用例 63 | |-- api api 模块 64 | |-- unit 单元测试 65 | |-- .czrc 提交规范选项设置 66 | |-- .editorconfig vscode 编辑器 设置 67 | |-- .env.development 开发环境配置 68 | |-- .env.preview 测试环境配置 69 | |-- .env.production 生产环境配置 70 | |-- .eslintignore eslint 要忽略的文件夹 71 | |-- .eslintrc.js eslint 规则配置 72 | |-- .gitattributes github 语言选项设置 73 | |-- .gitignore git 忽略的文件 74 | |-- .gitlab-ci.yml gitlab CI/CD 配置 75 | |-- .prettierrc.js 格式化插件配置 76 | |-- CHANGELOG.md 版本更改说明 77 | |-- Dockerfile 如果需要容器部署 78 | |-- README.md 项目说明 79 | |-- babel.config.js babel 设置 80 | |-- global.d.ts 全局的 type 81 | |-- package.json npm 配置 82 | |-- tsconfig.json typescript 配置 83 | |-- typedoc.json 文档配置文件 84 | |-- vue.config.js vue-cli 脚手架配置文件 85 | ``` 86 | 87 | #### 组件编写 88 | 89 | - [x] 支持 tsx 方式编写页面,在.tsx 文件或者 class-component 里写 tsx. 90 | - [x] 支持 class-component 写法. 91 | - [x] 同时支持(.vue|.tsx.|.ts) 编写页面,defineComponent 以及 class-componnet 都支持智能提示. 92 | 93 | #### 样式配置 94 | 95 | > 均通过在 vue-cli 中配置 webpack 实现. 96 | 97 | - [x] 自动注入全局样式 98 | - [x] 配置全局 less 变量 99 | - [x] 支持自定义 UI 库的主题颜色 100 | 101 | #### 网络请求 102 | 103 | - [x] 基于 axios 封装脱离具体业务逻辑的网络请求,支持编写脱离浏览器环境的测试用例.(跟业务无关) 104 | - [x] 基于具体业务逻辑再次封装网络请求 (跟业务相关,此项需要依据具体后台应用接口编写) 105 | 106 | #### 数据状态管理 107 | 108 | - [x] 建立应用数据状态管理 109 | - [x] 编写更加简易读取的mutation方法,并完善 type 【新增】 110 | - [x] 支持多个模块,以及自动装载模块 111 | - [x] 支持持久化 112 | 113 | #### UI 库 114 | 115 | - [x] 添加 ant-design-vue,支持组件按需加载 116 | - [x] 将 UI 库部分功能如 message 添加到每个组件实例 117 | 118 | #### 插件与常用工具函数 119 | 120 | - [x] 引用常用工具函数 121 | - [x] 常用 hook 122 | 123 | #### 配置 124 | 125 | - [x] 配置 webpack,分离开发/测试/生产环境配置. 126 | - [x] 添加 webpack 常用插件,优化打包配置. 127 | - [x] 根据环境配置 vue-cli 环境变量(环境相关) 128 | - [x] 配置应用全局静态常量(业务相关) 129 | - [x] 完成 tsconfig 相关配置 130 | - [x] 增加编辑器配置 131 | 132 | #### 开发工具 133 | 134 | - [x] eslint 代码检查,配置 prettier 格式化工具,使检查规则和格式化规则一致 135 | - [x] 新增提交规范 git cz commitizen,统一代码提交规范 136 | - [x] 为没有 type 的库和变量添加 shims 137 | 138 | #### CI/CD 139 | 140 | - [x] 配置自动构建/持续集成配置文件(与部署相关,需结合到具体项目部署情况) 141 | 142 | #### 文档 143 | 144 | - [x] 使用 TYPEDOC 搭建项目文档应用 145 | - [x] 在提交规范的基础上,增加版本更改历史,自动生成 changelog 146 | - [x] 配置 http-server 启动文档应用 147 | 148 | #### 测试用例 149 | 150 | - [x] 支持编写 js,ts 的测试用例 151 | 152 | ## 安装依赖 153 | 154 | ``` 155 | yarn install 156 | npm install 157 | ``` 158 | 159 | ### 开发模式 160 | 161 | ``` 162 | yarn serve 163 | npm run serve 164 | ``` 165 | 166 | ### 生产环境 167 | 168 | ``` 169 | yarn build 170 | npm run build 171 | ``` 172 | 173 | ### 测试用例 174 | 175 | - api 模块开发环境下单元测试 176 | 177 | ``` 178 | npm run test-dev:api 179 | ``` 180 | 181 | - api 模块常规单元测试 182 | 183 | ``` 184 | npm run test:api 185 | ``` 186 | 187 | - 查看打包结果分析 188 | 189 | ``` 190 | npm run analysis 191 | ``` 192 | ### 更多问题 193 | 194 | 如果有问题,请提 issue 说明 => [传送门](https://github.com/ibwei/vue3-base). 195 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer') 2 | .BundleAnalyzerPlugin 3 | 4 | const IS_DEV = process.env.NODE_ENV !== 'production' 5 | /** 6 | * @todo 开发环境配置 7 | * 某些实用工具, plugins 和 loaders 都只能在构建生产环境时才有用 8 | * 在开发时使用 UglifyJsPlugin 来压缩和修改代码是没有意义的,不压缩 9 | */ 10 | 11 | const DEVELOPMENT = webpackConfig => { 12 | /** 13 | * @todo 启用 eval-source-map 更好的测试 14 | * 每个模块使用 eval() 执行,并且 source map 转换为 DataUrl 后添加到 eval() 中。 15 | * 初始化 source map 时比较慢,但是会在重新构建时提供比较快的速度,并且生成实际的文件。 16 | * 行数能够正确映射,因为会映射到原始代码中。它会生成用于开发环境的最佳品质的 source map。 17 | */ 18 | 19 | webpackConfig.store.set('devtool', 'eval-source-map') 20 | webpackConfig.plugin('html').tap(([options]) => [ 21 | Object.assign(options, { 22 | minify: false, 23 | chunksSortMode: 'none' 24 | }) 25 | ]) 26 | 27 | // webpackConfig.plugin('BundleAnalyzerPlugin').use(BundleAnalyzerPlugin) 28 | 29 | return webpackConfig 30 | } 31 | 32 | /** 33 | * @todo 生产环境配置 34 | * 每个额外的 loader/plugin 都有启动时间。尽量少使用不同的工具 35 | */ 36 | 37 | const PRODUCTION = webpackConfig => { 38 | /** 39 | * @todo 不需要启用 source-map,去除 console 的情况下 source-map 根本没用,还浪费大量时间和空间 40 | * 详情见:https://webpack.js.org/configuration/devtool/#devtool 41 | */ 42 | webpackConfig.store.set('devtool', '') 43 | webpackConfig.plugin('html').tap(([options]) => [ 44 | Object.assign(options, { 45 | minify: { 46 | removeComments: true, 47 | removeCommentsFromCDATA: true, 48 | collapseWhitespace: true, 49 | conservativeCollapse: false, 50 | collapseInlineTagWhitespace: true, 51 | collapseBooleanAttributes: true, 52 | removeRedundantAttributes: true, 53 | removeAttributeQuotes: false, 54 | removeEmptyAttributes: true, 55 | removeScriptTypeAttributes: true, 56 | removeStyleLinkTypeAttributes: true, 57 | useShortDoctype: true, 58 | minifyJS: true, 59 | minifyCSS: true 60 | }, 61 | cache: true, // 仅在文件被更改时发出文件 62 | hash: true, // true则将唯一的webpack编译哈希值附加到所有包含的脚本和CSS文件中,这对于清除缓存很有用 63 | scriptLoading: 'defer', // 现代浏览器支持非阻塞javascript加载('defer'),以提高页面启动性能。 64 | inject: true, // true所有javascript资源都将放置在body元素的底部 65 | chunksSortMode: 'none' 66 | }) 67 | ]) 68 | 69 | return webpackConfig 70 | } 71 | 72 | module.exports = { 73 | publicPath: IS_DEV ? '/' : '/vue3-ts-base', 74 | css: { 75 | loaderOptions: { 76 | less: { 77 | globalVars: {}, 78 | srouceMap: IS_DEV, 79 | lessOptions: { 80 | javascriptEnabled: true 81 | } 82 | } 83 | } 84 | }, 85 | devServer: { 86 | proxy: 'http://10.10.10.115:8002' 87 | }, 88 | pluginOptions: { 89 | /** 全局加载less 的 webpack 插件 */ 90 | 'style-resources-loader': { 91 | preProcessor: 'less', 92 | patterns: ['./src/styles/index.less'] 93 | } 94 | }, 95 | /** 96 | * @description 去掉 console信息 97 | * config.optimization.minimizer[0].options.terserOptions.compress.drop_console = true; 98 | * html-webpack-plugin插件配置详情见 https://github.com/jantimon/html-webpack-plugin#options 99 | */ 100 | configureWebpack: config => { 101 | config.optimization = { 102 | splitChunks: { 103 | chunks: 'all', 104 | minSize: 3000, // (默认值:30000)块的最小大小。 105 | minChunks: 1, //(默认值:1)在拆分之前共享模块的最小块数 106 | maxAsyncRequests: 5, //(默认值为5)按需加载时并行请求的最大数量 107 | maxInitialRequests: 6, // (默认值为3)入口点的最大并行请求数 108 | automaticNameDelimiter: '-', 109 | name: true, 110 | cacheGroups: { 111 | lodash: { 112 | name: 'lodash', 113 | test: /[\\/]node_modules[\\/]lodash[\\/]/, 114 | priority: 20 115 | }, 116 | vue: { 117 | name: 'vue', 118 | test: /[\\/]node_modules[\\/]vue[\\/]/ 119 | }, 120 | vuex: { 121 | name: 'vuex', 122 | test: /[\\/]node_modules[\\/]vuex[\\/]/ 123 | }, 124 | 'vuex-presistedstate': { 125 | name: 'vuex-presistedstate', 126 | test: /[\\/]node_modules[\\/]vuex-presistedstate[\\/]/ 127 | }, 128 | 'vue-router': { 129 | name: 'vue-router', 130 | test: /[\\/]node_modules[\\/]vue-router[\\/]/ 131 | }, 132 | 'ant-design-vue': { 133 | name: 'ant-design-vue', 134 | test: /[\\/]node_modules[\\/]ant-design-vue[\\/]/ 135 | }, 136 | moment: { 137 | name: 'moment', 138 | test: /[\\/]node_modules[\\/]moment[\\/]/, 139 | priority: 40 140 | } 141 | } 142 | } 143 | } 144 | }, 145 | chainWebpack: config => { 146 | config.resolve.symlinks(true) 147 | 148 | if (process.env.use_analyzer) { 149 | config.plugin('webpack-bundle-analyzer').use(BundleAnalyzerPlugin) 150 | } 151 | 152 | IS_DEV ? DEVELOPMENT(config) : PRODUCTION(config) 153 | }, 154 | productionSourceMap: false, 155 | lintOnSave: true 156 | } 157 | -------------------------------------------------------------------------------- /src/views/test/Test.vue: -------------------------------------------------------------------------------- 1 | 86 | 87 | 163 | 164 | 205 | -------------------------------------------------------------------------------- /src/components/global/FullLoading.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 31 | 32 | 271 | -------------------------------------------------------------------------------- /src/styles/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /* Document 4 | ========================================================================== */ 5 | /** 6 | * 1. Correct the line height in all browsers. 7 | * 2. Prevent adjustments of font size after orientation changes in iOS. 8 | */ 9 | html { 10 | line-height: 1.15; /* 1 */ 11 | -webkit-text-size-adjust: 100%; /* 2 */ 12 | height: 100%; 13 | } 14 | 15 | /* Sections 16 | ========================================================================== */ 17 | 18 | /** 19 | * Remove the margin in all browsers. 20 | */ 21 | 22 | body { 23 | margin: 0; 24 | height: 100%; 25 | } 26 | 27 | /** 28 | * Render the `main` element consistently in IE. 29 | */ 30 | 31 | main { 32 | display: block; 33 | } 34 | 35 | /** 36 | * Correct the font size and margin on `h1` elements within `section` and 37 | * `article` contexts in Chrome, Firefox, and Safari. 38 | */ 39 | 40 | h1 { 41 | font-size: 2em; 42 | margin: 0.67em 0; 43 | } 44 | 45 | /* Grouping content 46 | ========================================================================== */ 47 | 48 | /** 49 | * 1. Add the correct box sizing in Firefox. 50 | * 2. Show the overflow in Edge and IE. 51 | */ 52 | 53 | hr { 54 | box-sizing: content-box; /* 1 */ 55 | height: 0; /* 1 */ 56 | overflow: visible; /* 2 */ 57 | } 58 | 59 | /** 60 | * 1. Correct the inheritance and scaling of font size in all browsers. 61 | * 2. Correct the odd `em` font sizing in all browsers. 62 | */ 63 | 64 | pre { 65 | font-family: monospace, monospace; /* 1 */ 66 | font-size: 1em; /* 2 */ 67 | } 68 | 69 | /* Text-level semantics 70 | ========================================================================== */ 71 | 72 | /** 73 | * Remove the gray background on active links in IE 10. 74 | */ 75 | 76 | a { 77 | background-color: transparent; 78 | } 79 | 80 | /** 81 | * 1. Remove the bottom border in Chrome 57- 82 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 83 | */ 84 | 85 | abbr[title] { 86 | border-bottom: none; /* 1 */ 87 | text-decoration: underline; /* 2 */ 88 | text-decoration: underline dotted; /* 2 */ 89 | } 90 | 91 | /** 92 | * Add the correct font weight in Chrome, Edge, and Safari. 93 | */ 94 | 95 | b, 96 | strong { 97 | font-weight: bolder; 98 | } 99 | 100 | /** 101 | * 1. Correct the inheritance and scaling of font size in all browsers. 102 | * 2. Correct the odd `em` font sizing in all browsers. 103 | */ 104 | 105 | code, 106 | kbd, 107 | samp { 108 | font-family: monospace, monospace; /* 1 */ 109 | font-size: 1em; /* 2 */ 110 | } 111 | 112 | /** 113 | * Add the correct font size in all browsers. 114 | */ 115 | 116 | small { 117 | font-size: 80%; 118 | } 119 | 120 | /** 121 | * Prevent `sub` and `sup` elements from affecting the line height in 122 | * all browsers. 123 | */ 124 | 125 | sub, 126 | sup { 127 | font-size: 75%; 128 | line-height: 0; 129 | position: relative; 130 | vertical-align: baseline; 131 | } 132 | 133 | sub { 134 | bottom: -0.25em; 135 | } 136 | 137 | sup { 138 | top: -0.5em; 139 | } 140 | 141 | /* Embedded content 142 | ========================================================================== */ 143 | 144 | /** 145 | * Remove the border on images inside links in IE 10. 146 | */ 147 | 148 | img { 149 | border-style: none; 150 | } 151 | 152 | /* Forms 153 | ========================================================================== */ 154 | 155 | /** 156 | * 1. Change the font styles in all browsers. 157 | * 2. Remove the margin in Firefox and Safari. 158 | */ 159 | 160 | button, 161 | input, 162 | optgroup, 163 | select, 164 | textarea { 165 | font-family: inherit; /* 1 */ 166 | font-size: 100%; /* 1 */ 167 | line-height: 1.15; /* 1 */ 168 | margin: 0; /* 2 */ 169 | } 170 | 171 | /** 172 | * Show the overflow in IE. 173 | * 1. Show the overflow in Edge. 174 | */ 175 | 176 | button, 177 | input { 178 | /* 1 */ 179 | overflow: visible; 180 | } 181 | 182 | /** 183 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 184 | * 1. Remove the inheritance of text transform in Firefox. 185 | */ 186 | 187 | button, 188 | select { 189 | /* 1 */ 190 | text-transform: none; 191 | } 192 | 193 | /** 194 | * Correct the inability to style clickable types in iOS and Safari. 195 | */ 196 | 197 | button, 198 | [type='button'], 199 | [type='reset'], 200 | [type='submit'] { 201 | -webkit-appearance: button; 202 | } 203 | 204 | /** 205 | * Remove the inner border and padding in Firefox. 206 | */ 207 | 208 | button::-moz-focus-inner, 209 | [type='button']::-moz-focus-inner, 210 | [type='reset']::-moz-focus-inner, 211 | [type='submit']::-moz-focus-inner { 212 | border-style: none; 213 | padding: 0; 214 | } 215 | 216 | /** 217 | * Restore the focus styles unset by the previous rule. 218 | */ 219 | 220 | button:-moz-focusring, 221 | [type='button']:-moz-focusring, 222 | [type='reset']:-moz-focusring, 223 | [type='submit']:-moz-focusring { 224 | outline: 1px dotted ButtonText; 225 | } 226 | 227 | /** 228 | * Correct the padding in Firefox. 229 | */ 230 | 231 | fieldset { 232 | padding: 0.35em 0.75em 0.625em; 233 | } 234 | 235 | /** 236 | * 1. Correct the text wrapping in Edge and IE. 237 | * 2. Correct the color inheritance from `fieldset` elements in IE. 238 | * 3. Remove the padding so developers are not caught out when they zero out 239 | * `fieldset` elements in all browsers. 240 | */ 241 | 242 | legend { 243 | box-sizing: border-box; /* 1 */ 244 | color: inherit; /* 2 */ 245 | display: table; /* 1 */ 246 | max-width: 100%; /* 1 */ 247 | padding: 0; /* 3 */ 248 | white-space: normal; /* 1 */ 249 | } 250 | 251 | /** 252 | * Add the correct vertical alignment in Chrome, Firefox, and Opera. 253 | */ 254 | 255 | progress { 256 | vertical-align: baseline; 257 | } 258 | 259 | /** 260 | * Remove the default vertical scrollbar in IE 10+. 261 | */ 262 | 263 | textarea { 264 | overflow: auto; 265 | } 266 | 267 | /** 268 | * 1. Add the correct box sizing in IE 10. 269 | * 2. Remove the padding in IE 10. 270 | */ 271 | 272 | [type='checkbox'], 273 | [type='radio'] { 274 | box-sizing: border-box; /* 1 */ 275 | padding: 0; /* 2 */ 276 | } 277 | 278 | /** 279 | * Correct the cursor style of increment and decrement buttons in Chrome. 280 | */ 281 | 282 | [type='number']::-webkit-inner-spin-button, 283 | [type='number']::-webkit-outer-spin-button { 284 | height: auto; 285 | } 286 | 287 | /** 288 | * 1. Correct the odd appearance in Chrome and Safari. 289 | * 2. Correct the outline style in Safari. 290 | */ 291 | 292 | [type='search'] { 293 | -webkit-appearance: textfield; /* 1 */ 294 | outline-offset: -2px; /* 2 */ 295 | } 296 | 297 | /** 298 | * Remove the inner padding in Chrome and Safari on macOS. 299 | */ 300 | 301 | [type='search']::-webkit-search-decoration { 302 | -webkit-appearance: none; 303 | } 304 | 305 | /** 306 | * 1. Correct the inability to style clickable types in iOS and Safari. 307 | * 2. Change font properties to `inherit` in Safari. 308 | */ 309 | 310 | ::-webkit-file-upload-button { 311 | -webkit-appearance: button; /* 1 */ 312 | font: inherit; /* 2 */ 313 | } 314 | 315 | /* Interactive 316 | ========================================================================== */ 317 | 318 | /* 319 | * Add the correct display in Edge, IE 10+, and Firefox. 320 | */ 321 | 322 | details { 323 | display: block; 324 | } 325 | 326 | /* 327 | * Add the correct display in all browsers. 328 | */ 329 | 330 | summary { 331 | display: list-item; 332 | } 333 | 334 | /* Misc 335 | ========================================================================== */ 336 | 337 | /** 338 | * Add the correct display in IE 10+. 339 | */ 340 | 341 | template { 342 | display: none; 343 | } 344 | 345 | /** 346 | * Add the correct display in IE 10. 347 | */ 348 | 349 | [hidden] { 350 | display: none; 351 | } 352 | 353 | ::-webkit-scrollbar { 354 | width: 6px; 355 | height: 6px; 356 | } 357 | 358 | ::-webkit-scrollbar-track { 359 | width: 6px; 360 | background: rgba(16, 31, 28, 0.1); 361 | -webkit-border-radius: 2em; 362 | -moz-border-radius: 2em; 363 | border-radius: 2em; 364 | } 365 | 366 | ::-webkit-scrollbar-thumb { 367 | background-color: rgba(16, 31, 28, 0.5); 368 | background-clip: padding-box; 369 | min-height: 28px; 370 | -webkit-border-radius: 2em; 371 | -moz-border-radius: 2em; 372 | border-radius: 2em; 373 | } 374 | 375 | ::-webkit-scrollbar-thumb:hover { 376 | background-color: rgba(16, 31, 28, 1); 377 | } 378 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.1.21](https://github.com/ibwei/vue3-base/compare/v0.1.20...v0.1.21) (2021-04-07) 2 | 3 | 4 | ### Features 5 | 6 | * **commitlint:** add commitlint ([2296748](https://github.com/ibwei/vue3-base/commit/22967485380f76e25f9821055d83efe2b6c3db92)) 7 | 8 | 9 | 10 | ## [0.1.20](https://github.com/ibwei/vue3-base/compare/v0.1.17...v0.1.20) (2021-03-15) 11 | 12 | 13 | ### Features 14 | 15 | * **tsx:** 增加 tsx 测试用例以及更新 ts 版本&相关依赖 ([cc0e198](https://github.com/ibwei/vue3-base/commit/cc0e198c4ee9fdb171d102bba194d817446b16ff)) 16 | 17 | 18 | 19 | ## [0.1.17](https://github.com/ibwei/vue3-base/compare/0.1.17...v0.1.17) (2021-01-25) 20 | 21 | 22 | ### Bug Fixes 23 | 24 | * **vuex:** 修复 vuex 的 getter 工具函数缺少返回值的问题 ([8f32cdb](https://github.com/ibwei/vue3-base/commit/8f32cdb1c8c2e1ddd3e1aa9c494d93980fb63a2d)) 25 | 26 | 27 | 28 | ## [0.1.17](https://github.com/ibwei/vue3-base/compare/v0.1.16...0.1.17) (2020-12-08) 29 | 30 | 31 | ### Bug Fixes 32 | 33 | * **eslint:** 修复 lint 错误 ([e072b49](https://github.com/ibwei/vue3-base/commit/e072b49bd6b3f707e88a8fbb8035667637d5816d)) 34 | * **lint:** 修复 lint ([3f97086](https://github.com/ibwei/vue3-base/commit/3f97086e6918afb541afc3f53a97c2bf96dde174)) 35 | 36 | 37 | 38 | ## [0.1.16](https://github.com/ibwei/vue3-base/compare/v0.1.15...v0.1.16) (2020-12-02) 39 | 40 | 41 | ### Bug Fixes 42 | 43 | * **api:** 缺少模块的错误 ([8170431](https://github.com/ibwei/vue3-base/commit/8170431f7835b523440c9945be7f2d0c6ccf8bd4)) 44 | * **配置:** 添加静态配置 ([5c6cca5](https://github.com/ibwei/vue3-base/commit/5c6cca54464e8101c92e4e4cff3e8c1fb216b85a)) 45 | 46 | 47 | 48 | ## [0.1.15](https://github.com/ibwei/vue3-base/compare/v0.1.10...v0.1.15) (2020-11-17) 49 | 50 | 51 | ### Bug Fixes 52 | 53 | * **i18n:** 修复 antd-vue 语言国际化出错问题 ([1cd844d](https://github.com/ibwei/vue3-base/commit/1cd844d08d51b0dc115dbe47352caadd684380ef)) 54 | * **i18n:** 修复 i18n修改 antdv组件国际化模版出错问题 ([003bccf](https://github.com/ibwei/vue3-base/commit/003bccfb3047d658a554a32a4dde117aeb5aee59)) 55 | * **i18n:** 修复safari 不支持先行和后行断言的导致的白屏问题 ([384499b](https://github.com/ibwei/vue3-base/commit/384499be25429152a10a277049b391eae969e856)) 56 | * **thme:** 修改 ant-design-vue自定义主题在 build 之后无法覆盖成功的问题 ([29a591d](https://github.com/ibwei/vue3-base/commit/29a591d0cf17429cb528f485171be3b6ae4f7ff8)) 57 | 58 | 59 | ### Performance Improvements 60 | 61 | * **vuex:** 增加 vuex 的模块示例 ([ed70e47](https://github.com/ibwei/vue3-base/commit/ed70e4785538ecf72cc80389cc02d65eefcfe97d)) 62 | 63 | 64 | 65 | ## [0.1.10](https://github.com/ibwei/vue3-base/compare/0.1.9...v0.1.10) (2020-10-18) 66 | 67 | 68 | ### Bug Fixes 69 | 70 | * **ci/cd:** 修复 CI/CD 错误生成的文件 ([2fbba24](https://github.com/ibwei/vue3-base/commit/2fbba2449f345ae9b98ad4b1ea9edde13f3d8a05)) 71 | * **ci/cd:** 完善 CI/CD 配置 ([7ec507f](https://github.com/ibwei/vue3-base/commit/7ec507f74a07cd34feff450bc25f76a4847246be)) 72 | 73 | 74 | ### Features 75 | 76 | * **vuex & test:** 完善 vuex 使用以及更改了组件测试页面 ([a913a06](https://github.com/ibwei/vue3-base/commit/a913a062d4c3a4a800ac95389aff8d2342e9b763)) 77 | * **添加图片以及合并旧版本:** 合并 ([090ec1d](https://github.com/ibwei/vue3-base/commit/090ec1dbab23dda11b76cff17c4e9a68335b52fd)) 78 | 79 | 80 | ### Performance Improvements 81 | 82 | * **public:** 优化默认模版以及去掉CI/CD 错误生成的文件 ([cacd037](https://github.com/ibwei/vue3-base/commit/cacd0377e34dc156e90f56726452f776e48b213f)) 83 | 84 | 85 | 86 | ## [0.1.9](https://github.com/ibwei/vue3-base/compare/v0.1.8...0.1.9) (2020-10-17) 87 | 88 | 89 | ### Bug Fixes 90 | 91 | * **ci:** ci ([a306153](https://github.com/ibwei/vue3-base/commit/a30615390ec92ae7b3145876ad87f8c8623da270)) 92 | * **ci:** cI ([992dcd2](https://github.com/ibwei/vue3-base/commit/992dcd282f549f17a5c5c8a89edcc1879ed574bf)) 93 | * **ci:** cI ([e00cba0](https://github.com/ibwei/vue3-base/commit/e00cba0594213f675c5c4dbdd6f17b3ef9dd4ac3)) 94 | * **ci:** cI ([ed86ad3](https://github.com/ibwei/vue3-base/commit/ed86ad350a9b2007056daa53dfc8029632e74fe7)) 95 | * **ci:** cI ([6929717](https://github.com/ibwei/vue3-base/commit/6929717e9d38bb02a93622d05cff73128fd94c69)) 96 | * **ci:** 修复 CI 配置 ([17839b4](https://github.com/ibwei/vue3-base/commit/17839b44962ef0daae41b1c2a031fc4eb50ad4e5)) 97 | * **ci/cd:** 修复 ci/cd ([fe2437a](https://github.com/ibwei/vue3-base/commit/fe2437ad43fc5dedb9ec39906df6ce579a9b7b0c)) 98 | * **ci/cd:** 更改 ci/cd配置文件 ([ef5b5a2](https://github.com/ibwei/vue3-base/commit/ef5b5a2b7caa064e8eb16224dfa80a6f1e190e85)) 99 | * **edit ci:** 编辑 CI 配置 ([6c82568](https://github.com/ibwei/vue3-base/commit/6c825685cdebdebb018f0f772cf7c7252d745a3d)) 100 | * **fix:** fix ([8da40d1](https://github.com/ibwei/vue3-base/commit/8da40d15a78221618a81849e9d9fdd2b371b08bd)) 101 | * **fix:** fix ([b6f3ad6](https://github.com/ibwei/vue3-base/commit/b6f3ad60ee8c605efbc984473e43d31f24876f61)) 102 | * **fix:** fix ([99ca35f](https://github.com/ibwei/vue3-base/commit/99ca35fdcdb363b3c7b225d8911a8ad652da5ee2)) 103 | * **fix:** fix ([0df6c69](https://github.com/ibwei/vue3-base/commit/0df6c69a14e0309e4acb1d176b9954f220f61850)) 104 | * **fix:** fix ([e475862](https://github.com/ibwei/vue3-base/commit/e475862f11829d77106f23a6ff5925180a738443)) 105 | * **fix:** fix ([1b8c9a0](https://github.com/ibwei/vue3-base/commit/1b8c9a057362d75e2c020e229dd7d03e12d22c93)) 106 | * **fix:** fix ([5949826](https://github.com/ibwei/vue3-base/commit/5949826ec7a8afbfc0f3eea848813bb76be31c6e)) 107 | * **fix:** fix ([516aa2a](https://github.com/ibwei/vue3-base/commit/516aa2a7b0ba136bdd3d844575d155f49c6585f2)) 108 | * **iconfont:** 更改引入 iconfont 方式 ([e11f13c](https://github.com/ibwei/vue3-base/commit/e11f13cde660fa9c255616e7fd2978c32abdc178)) 109 | * **less:** 更新 less 版本,兼容 antdv ([1f9c248](https://github.com/ibwei/vue3-base/commit/1f9c2488fa35fc4e8fe52229db48ca1f53813fb1)) 110 | * **readme.md:** 修改项目说明文档 ([0065d42](https://github.com/ibwei/vue3-base/commit/0065d420b18ee3ddc13396b1436faee090806a16)) 111 | * **types:** 更改部分 type 说明 ([89b0a19](https://github.com/ibwei/vue3-base/commit/89b0a193629576f49146d968460658c66e8e1895)) 112 | * **vue3:** 更新到 vue3 正式版并修复错误 ([074b83a](https://github.com/ibwei/vue3-base/commit/074b83a8db7ed1f68d6fb4faf0f04302743a02c5)) 113 | * **修复 ci/cd:** 修复 ([b078729](https://github.com/ibwei/vue3-base/commit/b078729d502d3d050b184fd02d8c42a9782f0182)) 114 | * **关闭 prettier检查:** 关闭 ([c8b5d4f](https://github.com/ibwei/vue3-base/commit/c8b5d4fb8192b6e78d560a47cbd4630612b283a1)) 115 | * **编辑器配置:** 更新vscode 编辑器配置 ([8858aee](https://github.com/ibwei/vue3-base/commit/8858aeebb60214cab5c740bd96a7f1c433fb93dd)) 116 | 117 | 118 | ### Features 119 | 120 | * **ci/cd:** ci/cd ([2228fcc](https://github.com/ibwei/vue3-base/commit/2228fccb48d0aed7e3a0aee4d374d20a5c1bdc95)) 121 | * **i18n:** 主要增加了国际化方案 ([7affdf8](https://github.com/ibwei/vue3-base/commit/7affdf821bf934e4fc364ab89bb9731475987141)) 122 | * **layout 编写:** 完成 layout 编写,实现全屏切换方式 ([366d49c](https://github.com/ibwei/vue3-base/commit/366d49c9af6427b5a6ba11cd09df28e161457ec4)) 123 | * **vuex:** 完善 vuex ts 类型,重构简易读取方法 ([066d2be](https://github.com/ibwei/vue3-base/commit/066d2be4ef7d2a3996f56e910108b41fcf730e93)) 124 | * **vuex:** 添加了 vuex 打印日志服务 ([badae42](https://github.com/ibwei/vue3-base/commit/badae42d9561fc155a45b152f496e2ca955a5532)) 125 | * **完成 ci/cd配置:** 完成 CI/CD ([262d1b2](https://github.com/ibwei/vue3-base/commit/262d1b24a4c1c50824832f02619600cd03fa6c25)) 126 | * **测试:** 添加测试例子,完善vuex的类型 ([b833160](https://github.com/ibwei/vue3-base/commit/b833160194808b4fd04aa0dbca1223449c7d1e88)) 127 | 128 | 129 | ### Performance Improvements 130 | 131 | * **插件:** 插件更新为自动化加载 ([e0d39c4](https://github.com/ibwei/vue3-base/commit/e0d39c43c81c21d50c250a9d833d9d012ff7a55b)) 132 | 133 | 134 | 135 | ## [0.1.8](https://github.com/ibwei/vue3-base/compare/v0.1.7...v0.1.8) (2020-09-15) 136 | 137 | 138 | 139 | ## [0.1.7](https://github.com/ibwei/vue3-base/compare/d4e851421b72b7f2ff273d8ebc72c08919b94392...v0.1.7) (2020-09-09) 140 | 141 | 142 | ### Bug Fixes 143 | 144 | * **package.json:** 修改 npm 包配置相关 ([ad5ff8c](https://github.com/ibwei/vue3-base/commit/ad5ff8cf82d261c104ba9eefb2e60508714af4cd)) 145 | * **修复提交工具:** 更改git-cz 配置文件 ([372391f](https://github.com/ibwei/vue3-base/commit/372391f79e177c685277409ad873cd963c756ee3)) 146 | 147 | 148 | ### Features 149 | 150 | * **基础代码架构:** 项目基础代码初始化,目前只有vue 全家桶 ([d4e8514](https://github.com/ibwei/vue3-base/commit/d4e851421b72b7f2ff273d8ebc72c08919b94392)) 151 | * **完善 type:** 将第三方库的类型添加到每一个 vue 组件实例中去 ([13b84ab](https://github.com/ibwei/vue3-base/commit/13b84ab084ee388e114618db31ab801d28143bdf)) 152 | * **补全基础代码:** 更换 types 位置,增加 layout 文件,修复 docs 文档,修改 tsconfig 配置等 ([d56645f](https://github.com/ibwei/vue3-base/commit/d56645fcf33959bcf57453690201be3cdd55620c)) 153 | * **项目搭建:** 初步完成除了UI 库以外的基础配置 ([f3da208](https://github.com/ibwei/vue3-base/commit/f3da208fa7bcd9141c829327cf949aaf486a0ce7)) 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /src/components/Selector.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 348 | 349 | 473 | -------------------------------------------------------------------------------- /src/utils/common.ts: -------------------------------------------------------------------------------- 1 | import { BasicUserType, RoleType } from '@/@types' 2 | import store from '@/store' 3 | import { computed } from 'vue' 4 | 5 | /** 6 | * @description 判断传入的日期是否过期 7 | * @param {string} endTime - 过期的时间 8 | * @returns {boolean} 9 | */ 10 | export const isValidDate = (endTime: string) => { 11 | const nowTimeStamp = new Date().getTime() 12 | const endTimeStamp = new Date(endTime.replaceAll('-', '/')).getTime() 13 | 14 | // 过期 15 | if (nowTimeStamp > endTimeStamp) { 16 | return false 17 | } 18 | return true 19 | } 20 | 21 | // 是否具备团队编辑权限 22 | 23 | export const canEditTeam = computed(() => { 24 | if (store.state.user.userDetail.type === 0) { 25 | return true 26 | } 27 | const canEditTeamRoleList = [ 28 | RoleType['超级管理员'], 29 | RoleType['团队超级管理员'], 30 | RoleType['团队管理员'] 31 | ] 32 | if ( 33 | canEditTeamRoleList.includes(store.state.user.currentTeamRoleId as number) 34 | ) { 35 | return true 36 | } 37 | return false 38 | }) 39 | 40 | // 获取显示的名字 41 | 42 | export const getFormmatName = (user: BasicUserType) => { 43 | if (user.nickName) { 44 | return user.nickName 45 | } 46 | if (user.username) { 47 | return user.username.split('-')[0] 48 | } 49 | return '团队超级管理员' 50 | } 51 | 52 | // 获取分组名 53 | export const getGroupName = (key: number): string => { 54 | const roleList = store.state.console.roleList 55 | for (let i = 0; i < roleList.length; i++) { 56 | if (roleList[i].id == key) { 57 | return roleList[i].roleName 58 | } 59 | } 60 | return '暂无角色' 61 | } 62 | 63 | /** 64 | * @description 异步加载 一段js放在 header 65 | * @param {object} url - js 的 url 66 | * @param {function} callback - 成功回调 67 | * @returns { promise}} promise 68 | */ 69 | export function loadScript(url: string) { 70 | return new Promise(() => { 71 | try { 72 | const script: any = document.createElement('script') 73 | script.type = 'text/javascript' 74 | if (script.readyState) { 75 | //IE 76 | script.onreadystatechange = function () { 77 | if ( 78 | script.readyState == 'loaded' || 79 | script.readyState == 'complete' 80 | ) { 81 | script.onreadystatechange = null 82 | Promise.resolve(0) 83 | } 84 | } 85 | } else { 86 | //Others: Firefox, Safari, Chrome, and Opera 87 | script.onload = function () { 88 | Promise.resolve(0) 89 | } 90 | } 91 | script.src = url 92 | document.body.appendChild(script) 93 | } catch (e) { 94 | Promise.reject(e) 95 | } 96 | }) 97 | } 98 | 99 | /** 100 | * @description 通过值查找对象的 key 101 | * @param {object} target - 要查找的对象 102 | * @param {string} value - 要查找的值 103 | * @returns {string} key 返回的 key 104 | */ 105 | export function findKeyByValue( 106 | target: { [key: string]: string }, 107 | value: string 108 | ): string { 109 | const keys = Reflect.ownKeys(target) as Array 110 | for (let i = 0; i < keys.length; i++) { 111 | if (target[keys[i]] === value) { 112 | return keys[i] 113 | } 114 | } 115 | return '' 116 | } 117 | 118 | /** 119 | * @description 将 utc 格式转换为北京时间 120 | * @param {string} date -utc 时间字符串 121 | * @returns {string} 北京时间 122 | */ 123 | export function utc2Beijing(date: string) { 124 | const timestamp: Date = new Date(Date.parse(date)) 125 | let time: number = timestamp.getTime() 126 | time = time / 1000 127 | 128 | // 增加8个小时,北京时间比utc时间多八个时区 129 | time = time + 8 * 60 * 60 130 | 131 | // 时间戳转为时间 132 | const beijingDatetime: any = new Date(parseInt(time + '') * 1000) 133 | 134 | const year = beijingDatetime.getFullYear() 135 | let month: string | number = beijingDatetime.getMonth() + 1 136 | if (month < 10) { 137 | month = `0${month}` 138 | } 139 | let day = beijingDatetime.getDate() 140 | if (day < 10) { 141 | day = `0${day}` 142 | } 143 | let hour = beijingDatetime.getHours() 144 | if (hour < 10) { 145 | hour = `0${hour}` 146 | } 147 | let minute = beijingDatetime.getMinutes() 148 | if (minute < 10) { 149 | minute = `0${minute}` 150 | } 151 | let second = beijingDatetime.getSeconds() 152 | if (second < 10) { 153 | second = `0${second}` 154 | } 155 | 156 | return `${year}-${month}-${day} ${hour}:${minute}:${second}` 157 | } 158 | 159 | /** 复制文本 */ 160 | export function copyText(test: string) { 161 | if (document !== null) { 162 | const tag = document.createElement('input') 163 | tag.setAttribute('id', 'cp_input') 164 | tag.value = test 165 | document.getElementsByTagName('body')[0].appendChild(tag) 166 | const target: any = document.getElementById('cp_input') 167 | if (target) { 168 | target.select() 169 | target.execCommand('copy') 170 | target.remove() 171 | } 172 | } 173 | } 174 | 175 | /** 176 | * @description 获取鼠标位置 177 | * @param {MouseEvent} event 178 | * @return {x:number,y:number} 鼠标位置 179 | */ 180 | export function getMousePos(event: any) { 181 | const e: any = event || window.event 182 | const x = e.clientX 183 | const y = e.clientY 184 | return { x: x, y: y } 185 | } 186 | 187 | /* eslint-disable */ 188 | export function HtmlEncode(text: string) { 189 | return text 190 | .replace(/&/g, '&') 191 | .replace(/\"/g, '"') 192 | .replace(//g, '>') 194 | } 195 | 196 | // base64 encode 197 | export function base64Decode(data: string) { 198 | const b64 = 199 | 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' 200 | let o1 201 | let o2 202 | let o3 203 | let h1 204 | let h2 205 | let h3 206 | let h4 207 | let bits 208 | let i = 0 209 | let ac = 0 210 | let dec = '' 211 | let tmpArr: any = [] 212 | if (!data) { 213 | return data 214 | } 215 | data += '' 216 | do { 217 | h1 = b64.indexOf(data.charAt(i++)) 218 | h2 = b64.indexOf(data.charAt(i++)) 219 | h3 = b64.indexOf(data.charAt(i++)) 220 | h4 = b64.indexOf(data.charAt(i++)) 221 | bits = (h1 << 18) | (h2 << 12) | (h3 << 6) | h4 222 | o1 = (bits >> 16) & 0xff 223 | o2 = (bits >> 8) & 0xff 224 | o3 = bits & 0xff 225 | if (h3 === 64) { 226 | tmpArr[ac++] = String.fromCharCode(o1) 227 | } else if (h4 === 64) { 228 | tmpArr[ac++] = String.fromCharCode(o1, o2) 229 | } else { 230 | tmpArr[ac++] = String.fromCharCode(o1, o2, o3) 231 | } 232 | } while (i < data.length) 233 | dec = tmpArr.join('') 234 | dec = utf8Decode(dec) 235 | return dec 236 | } 237 | 238 | // is valid keypress 239 | export function checkKey(iKey: number) { 240 | if (iKey === 32 || iKey === 229) { 241 | return true 242 | } /* space和exception */ 243 | if (iKey > 47 && iKey < 58) { 244 | return true 245 | } /* number */ 246 | if (iKey > 64 && iKey < 91) { 247 | return true 248 | } /* string */ 249 | if (iKey > 95 && iKey < 108) { 250 | return true 251 | } /* num keyboard 1 */ 252 | if (iKey > 108 && iKey < 112) { 253 | return true 254 | } /* num keyboard 2 */ 255 | if (iKey > 185 && iKey < 193) { 256 | return true 257 | } /* punctuation 1 */ 258 | if (iKey > 218 && iKey < 223) { 259 | return true 260 | } /* num keyboard 2 */ 261 | return false 262 | } 263 | 264 | // get cookie value 265 | export function getCookie(name: string) { 266 | const arr = document.cookie.match( 267 | new RegExp('(^| )' + name + '=([^;]*)(;|$)') 268 | ) 269 | if (arr != null) return unescape(arr[2]) 270 | return null 271 | } 272 | 273 | // get page height 274 | export function getPageHeight() { 275 | const g = document 276 | const a = g.body 277 | const f = g.documentElement 278 | const d = g.compatMode === 'BackCompat' ? a : g.documentElement 279 | return Math.max(f.scrollHeight, a.scrollHeight, d.clientHeight) 280 | } 281 | 282 | // get page view height 283 | export function getPageViewHeight() { 284 | const d = document 285 | const a = d.compatMode === 'BackCompat' ? d.body : d.documentElement 286 | return a.clientHeight 287 | } 288 | // get page view width 289 | export function getPageViewWidth() { 290 | const d = document 291 | const a = d.compatMode === 'BackCompat' ? d.body : d.documentElement 292 | return a.clientWidth 293 | } 294 | // get page width 295 | export function getPageWidth() { 296 | const g = document 297 | const a = g.body 298 | const f = g.documentElement 299 | const d = g.compatMode === 'BackCompat' ? a : g.documentElement 300 | return Math.max(f.scrollWidth, a.scrollWidth, d.clientWidth) 301 | } 302 | 303 | export function getViewSize() { 304 | const de = document.documentElement 305 | const db = document.body 306 | const viewW = de.clientWidth === 0 ? db.clientWidth : de.clientWidth 307 | const viewH = de.clientHeight === 0 ? db.clientHeight : de.clientHeight 308 | return [viewW, viewH] 309 | } 310 | 311 | export function isAndroidMobileDevice() { 312 | return /android/i.test(navigator.userAgent.toLowerCase()) 313 | } 314 | 315 | export function isAppleMobileDevice() { 316 | return /iphone|ipod|ipad|Macintosh/i.test(navigator.userAgent.toLowerCase()) 317 | } 318 | 319 | export function isDigit(value: string) { 320 | const patrn = /^[0-9]*$/ 321 | if (patrn.exec(value) == null || value === '') { 322 | return false 323 | } else { 324 | return true 325 | } 326 | } 327 | 328 | export const isIphonex = () => { 329 | // X XS, XS Max, XR 330 | const xSeriesConfig = [ 331 | { 332 | devicePixelRatio: 3, 333 | width: 375, 334 | height: 812 335 | }, 336 | { 337 | devicePixelRatio: 3, 338 | width: 414, 339 | height: 896 340 | }, 341 | { 342 | devicePixelRatio: 2, 343 | width: 414, 344 | height: 896 345 | } 346 | ] 347 | // h5 348 | if (typeof window !== 'undefined' && window) { 349 | const isIOS = /iphone/gi.test(window.navigator.userAgent) 350 | if (!isIOS) return false 351 | const { devicePixelRatio, screen } = window 352 | const { width, height } = screen 353 | return xSeriesConfig.some( 354 | (item) => 355 | item.devicePixelRatio === devicePixelRatio && 356 | item.width === width && 357 | item.height === height 358 | ) 359 | } 360 | return false 361 | } 362 | 363 | export function isMobileUserAgent() { 364 | return /iphone|ipod|android.*mobile|windows.*phone|blackberry.*mobile/i.test( 365 | window.navigator.userAgent.toLowerCase() 366 | ) 367 | } 368 | 369 | export function isViewportOpen() { 370 | return !!document.getElementById('wixMobileViewport') 371 | } 372 | 373 | export function getOffset(e: MouseEvent) { 374 | const target: EventTarget | null = e.target 375 | if (target) { 376 | const pageCoord: any = getPageCoord(target) 377 | const eventCoord: any = { 378 | X: window.pageXOffset + e.clientX, 379 | Y: window.pageYOffset + e.clientY 380 | } 381 | 382 | const offsetCoord: any = { 383 | X: eventCoord.X - pageCoord.X, 384 | Y: eventCoord.Y - pageCoord.Y 385 | } 386 | return offsetCoord 387 | } 388 | } 389 | 390 | export function getPageCoord(element: any) { 391 | const coord = { X: 0, Y: 0 } 392 | if (element) { 393 | while (element) { 394 | coord.X += element.offsetLeft 395 | coord.Y += element.offsetTop 396 | element = element.offsetParent 397 | } 398 | } 399 | return coord 400 | } 401 | 402 | /** 403 | * @description 设置 cookie 404 | * @params {string} cookie -键名 405 | * @params {any} value -存入的值 406 | * @params {Hours} number -有效期限 407 | */ 408 | export function setCookie(name: string, value: any, Hours: number) { 409 | const d = new Date() 410 | const offset = 8 411 | const utc = d.getTime() + d.getTimezoneOffset() * 60000 412 | const nd = utc + 3600000 * offset 413 | const exp = new Date(nd) 414 | exp.setTime(exp.getTime() + Hours * 60 * 60 * 1000) 415 | document.cookie = 416 | name + 417 | '=' + 418 | escape(value) + 419 | ';path=/;expires=' + 420 | exp.toUTCString() + 421 | ';domain=360doc.com;' 422 | } 423 | 424 | export function uniqueId(): number { 425 | const a: any = Math.random 426 | const b: any = parseInt 427 | return Number( 428 | Number(new Date()).toString() + b(10 * a()) + b(10 * a()) + b(10 * a()) 429 | ) 430 | } 431 | 432 | export function utf8Decode(strData: string) { 433 | let tmpArr: any = [] 434 | let i = 0 435 | let ac = 0 436 | let c1 = 0 437 | let c2 = 0 438 | let c3 = 0 439 | strData += '' 440 | while (i < strData.length) { 441 | c1 = strData.charCodeAt(i) 442 | if (c1 < 128) { 443 | tmpArr[ac++] = String.fromCharCode(c1) 444 | i++ 445 | } else if (c1 > 191 && c1 < 224) { 446 | c2 = strData.charCodeAt(i + 1) 447 | tmpArr[ac++] = String.fromCharCode(((c1 & 31) << 6) | (c2 & 63)) 448 | i += 2 449 | } else { 450 | c2 = strData.charCodeAt(i + 1) 451 | c3 = strData.charCodeAt(i + 2) 452 | tmpArr[ac++] = String.fromCharCode( 453 | ((c1 & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63) 454 | ) 455 | i += 3 456 | } 457 | } 458 | return tmpArr.join('') 459 | } 460 | 461 | /** 462 | * @description 生成随机密码 463 | * @param len 长度 464 | */ 465 | export function createPassword(len: number) { 466 | //可以生成随机密码的相关数组 467 | var num = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] 468 | var english = [ 469 | 'a', 470 | 'b', 471 | 'c', 472 | 'd', 473 | 'e', 474 | 'f', 475 | 'g', 476 | 'h', 477 | 'i', 478 | 'j', 479 | 'k', 480 | 'l', 481 | 'm', 482 | 'n', 483 | 'o', 484 | 'p', 485 | 'q', 486 | 'r', 487 | 's', 488 | 't', 489 | 'u', 490 | 'v', 491 | 'w', 492 | 'x', 493 | 'y', 494 | 'z' 495 | ] 496 | var ENGLISH = [ 497 | 'A', 498 | 'B', 499 | 'C', 500 | 'D', 501 | 'E', 502 | 'F', 503 | 'G', 504 | 'H', 505 | 'I', 506 | 'J', 507 | 'K', 508 | 'L', 509 | 'M', 510 | 'N', 511 | 'O', 512 | 'P', 513 | 'Q', 514 | 'R', 515 | 'S', 516 | 'T', 517 | 'U', 518 | 'V', 519 | 'W', 520 | 'X', 521 | 'Y', 522 | 'Z' 523 | ] 524 | var special = ['-', '_', '#'] 525 | var config = num.concat(english).concat(ENGLISH).concat(special) 526 | 527 | //先放入一个必须存在的 528 | var arr = [] 529 | arr.push(getOne(num)) 530 | arr.push(getOne(english)) 531 | arr.push(getOne(ENGLISH)) 532 | arr.push(getOne(special)) 533 | 534 | for (var i = 4; i < len; i++) { 535 | //从数组里面抽出一个 536 | arr.push(config[Math.floor(Math.random() * config.length)]) 537 | } 538 | 539 | //乱序 540 | var newArr = [] 541 | for (var j = 0; j < len; j++) { 542 | newArr.push(arr.splice(Math.random() * arr.length, 1)[0]) 543 | } 544 | 545 | //随机从数组中抽出一个数值 546 | function getOne(arr: string | any[]) { 547 | return arr[Math.floor(Math.random() * arr.length)] 548 | } 549 | 550 | return newArr.join('') 551 | } 552 | 553 | /** 554 | * @description 获取字节数 555 | * @param s 字符串 556 | */ 557 | export function getBytesLength(s: string) { 558 | return s.replace(/[^\x00-\xff]/gi, '--').length 559 | } 560 | 561 | /** 562 | * 获取服务器时间 563 | * 注意: 564 | * 这个函数在本地运行会拿到本地时间,但是放到服务器上,会正常运行! 565 | */ 566 | export function getServerTime() { 567 | var xmlHttp: XMLHttpRequest 568 | function srvTime() { 569 | try { 570 | // FF, Opera, Safari, Chrome 571 | xmlHttp = new XMLHttpRequest() 572 | } catch (err1) { 573 | //IE 574 | try { 575 | xmlHttp = new ActiveXObject('Msxml2.XMLHTTP') 576 | } catch (err2) { 577 | try { 578 | xmlHttp = new ActiveXObject('Microsoft.XMLHTTP') 579 | } catch (eerr3) { 580 | //AJAX not supported, use CPU time. 581 | alert('AJAX not supported') 582 | } 583 | } 584 | } 585 | xmlHttp.open('HEAD', window.location.href.toString(), false) 586 | xmlHttp.setRequestHeader('Content-Type', 'text/html') 587 | xmlHttp.send('') 588 | return xmlHttp.getResponseHeader('Date') 589 | } 590 | 591 | var st = srvTime() 592 | var date = new Date(st as string) 593 | var timestamp = date.getTime() 594 | return timestamp 595 | } 596 | --------------------------------------------------------------------------------