diff --git a/web/.eslintrc.cjs b/web/.eslintrc.cjs new file mode 100644 index 00000000..50ee96f9 --- /dev/null +++ b/web/.eslintrc.cjs @@ -0,0 +1,15 @@ +/* eslint-env node */ +require('@rushstack/eslint-patch/modern-module-resolution') + +module.exports = { + root: true, + extends: [ + 'plugin:vue/vue3-essential', + 'eslint:recommended', + '@vue/eslint-config-typescript', + '@vue/eslint-config-prettier/skip-formatting' + ], + parserOptions: { + ecmaVersion: 'latest' + } +} diff --git a/web/.eslintrc.js b/web/.eslintrc.js deleted file mode 100644 index 6a92b6a6..00000000 --- a/web/.eslintrc.js +++ /dev/null @@ -1,20 +0,0 @@ -module.exports = { - root: false, - env: { - node: true, - }, - extends: [ - 'plugin:vue/essential', - 'eslint:recommended', - 'plugin:prettier/recommended', - ], - parserOptions: { - parser: '@babel/eslint-parser', - }, - rules: { - 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', - 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', - 'vue/multi-word-component-names': 'off', - semi: ['error', 'always'], - }, -}; diff --git a/web/.gitignore b/web/.gitignore index 9effb928..6bf0eb84 100644 --- a/web/.gitignore +++ b/web/.gitignore @@ -14,6 +14,7 @@ yarn-debug.log* yarn-error.log* pnpm-debug.log* package-lock.json +pnpm-lock.yaml # Editor directories and files .idea diff --git a/web/.prettierrc.js b/web/.prettierrc.js deleted file mode 100644 index 42b53e57..00000000 --- a/web/.prettierrc.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - singleQuote: true, // 使用单引号 - semi: true, // 不使用分号 - // trailingComma: 'all', // 在对象和数组末尾加上逗号 -}; diff --git a/web/.prettierrc.json b/web/.prettierrc.json new file mode 100644 index 00000000..66e23359 --- /dev/null +++ b/web/.prettierrc.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/prettierrc", + "semi": false, + "tabWidth": 2, + "singleQuote": true, + "printWidth": 100, + "trailingComma": "none" +} \ No newline at end of file diff --git a/web/auto-imports.d.ts b/web/auto-imports.d.ts new file mode 100644 index 00000000..d7d6df67 --- /dev/null +++ b/web/auto-imports.d.ts @@ -0,0 +1,10 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// noinspection JSUnusedGlobalSymbols +// Generated by unplugin-auto-import +export {} +declare global { + const ElMessage: typeof import('element-plus/es')['ElMessage'] + const ElMessageBox: typeof import('element-plus/es')['ElMessageBox'] +} diff --git a/web/babel.config.js b/web/babel.config.js deleted file mode 100644 index 104f822b..00000000 --- a/web/babel.config.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = { - presets: [ - '@vue/cli-plugin-babel/preset', - [ - '@vue/babel-preset-jsx', - { - injectH: false, - }, - ], - ], -}; diff --git a/web/components.d.ts b/web/components.d.ts new file mode 100644 index 00000000..411d01a0 --- /dev/null +++ b/web/components.d.ts @@ -0,0 +1,63 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// Generated by unplugin-vue-components +// Read more: https://github.com/vuejs/core/pull/3399 +export {} + +declare module 'vue' { + export interface GlobalComponents { + ElButton: typeof import('element-plus/es')['ElButton'] + ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] + ElCollapse: typeof import('element-plus/es')['ElCollapse'] + ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem'] + ElColorPicker: typeof import('element-plus/es')['ElColorPicker'] + ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider'] + ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] + ElDialog: typeof import('element-plus/es')['ElDialog'] + ElForm: typeof import('element-plus/es')['ElForm'] + ElFormItem: typeof import('element-plus/es')['ElFormItem'] + ElIcon: typeof import('element-plus/es')['ElIcon'] + ElInput: typeof import('element-plus/es')['ElInput'] + ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] + ElOption: typeof import('element-plus/es')['ElOption'] + ElPagination: typeof import('element-plus/es')['ElPagination'] + ElPopover: typeof import('element-plus/es')['ElPopover'] + ElRadio: typeof import('element-plus/es')['ElRadio'] + ElRadioButton: typeof import('element-plus/es')['ElRadioButton'] + ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] + ElSelect: typeof import('element-plus/es')['ElSelect'] + ElSlider: typeof import('element-plus/es')['ElSlider'] + ElSwitch: typeof import('element-plus/es')['ElSwitch'] + ElTable: typeof import('element-plus/es')['ElTable'] + ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] + ElTabPane: typeof import('element-plus/es')['ElTabPane'] + ElTabs: typeof import('element-plus/es')['ElTabs'] + ElTag: typeof import('element-plus/es')['ElTag'] + ElTimePicker: typeof import('element-plus/es')['ElTimePicker'] + ElTooltip: typeof import('element-plus/es')['ElTooltip'] + 'IEp-[]': typeof import('~icons/ep/[]')['default'] + 'IEp-[test]': typeof import('~icons/ep/[test]')['default'] + 'IEp-]': typeof import('~icons/ep/]')['default'] + IEpBottom: typeof import('~icons/ep/bottom')['default'] + IEpCheck: typeof import('~icons/ep/check')['default'] + IEpCirclePlus: typeof import('~icons/ep/circle-plus')['default'] + IEpClose: typeof import('~icons/ep/close')['default'] + IEpCopyDocument: typeof import('~icons/ep/copy-document')['default'] + IEpLoading: typeof import('~icons/ep/loading')['default'] + IEpQuestionFilled: typeof import('~icons/ep/question-filled')['default'] + IEpRank: typeof import('~icons/ep/rank')['default'] + IEpRemove: typeof import('~icons/ep/remove')['default'] + IEpSearch: typeof import('~icons/ep/search')['default'] + IEpSort: typeof import('~icons/ep/sort')['default'] + IEpSortDown: typeof import('~icons/ep/sort-down')['default'] + IEpSortUp: typeof import('~icons/ep/sort-up')['default'] + IEpTop: typeof import('~icons/ep/top')['default'] + RouterLink: typeof import('vue-router')['RouterLink'] + RouterView: typeof import('vue-router')['RouterView'] + } + export interface ComponentCustomProperties { + vLoading: typeof import('element-plus/es')['ElLoadingDirective'] + vPopover: typeof import('element-plus/es')['ElPopoverDirective'] + } +} diff --git a/web/env.d.ts b/web/env.d.ts new file mode 100644 index 00000000..9706856f --- /dev/null +++ b/web/env.d.ts @@ -0,0 +1,8 @@ +/// +declare module "vuex" { + export * from "vuex/types/index.d.ts"; + export * from "vuex/types/helpers.d.ts"; + export * from "vuex/types/logger.d.ts"; + export * from "vuex/types/vue.d.ts"; + } + \ No newline at end of file diff --git a/web/package.json b/web/package.json index eb054e0a..cada0c8c 100644 --- a/web/package.json +++ b/web/package.json @@ -2,55 +2,57 @@ "name": "web", "version": "0.1.0", "private": true, + "type": "module", "scripts": { - "serve": "vue-cli-service serve", - "build": "vue-cli-service build", - "report": "vue-cli-service build --report", - "lint": "vue-cli-service lint", - "lintfix": "eslint --fix ." + "serve": "vite", + "dev": "vite", + "build": "run-p type-check \"build-only {@}\" --", + "preview": "vite preview", + "build-only": "vite build", + "type-check": "vue-tsc --build --force", + "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", + "format": "prettier --write src/" }, "dependencies": { - "@vue/babel-helper-vue-jsx-merge-props": "^1.4.0", - "@vue/babel-preset-jsx": "^1.4.0", "@wangeditor/editor": "^5.1.23", + "@wangeditor/editor-for-vue": "^5.1.12", "async-validator": "^4.2.5", "axios": "^1.4.0", "clipboard": "^2.0.11", - "core-js": "^3.8.3", "crypto-js": "^4.2.0", - "element-ui": "^2.15.13", + "element-plus": "^2.7.0", "lodash-es": "^4.17.21", "moment": "^2.29.4", "node-forge": "^1.3.1", "qrcode": "^1.5.3", - "vue": "^2.7.14", - "vue-router": "^3.5.1", - "vuedraggable": "^2.24.3", - "vuex": "^3.6.2", + "vue": "^3.4.15", + "vue-router": "^4.2.5", + "vuedraggable": "^4.1.0", + "vuex": "^4.0.2", "xss": "^1.0.14" }, "devDependencies": { - "@babel/core": "^7.12.16", - "@babel/eslint-parser": "^7.12.16", - "@vue/cli-plugin-babel": "~5.0.0", - "@vue/cli-plugin-eslint": "~5.0.0", - "@vue/cli-plugin-router": "~5.0.0", - "@vue/cli-plugin-vuex": "~5.0.0", - "@vue/cli-service": "~5.0.0", - "eslint": "^7.32.0", - "eslint-config-prettier": "^8.3.0", - "eslint-plugin-prettier": "^4.0.0", - "eslint-plugin-vue": "^8.7.1", - "less-loader": "^11.1.3", - "postcss-import": "^15.1.0", - "postcss-url": "^10.1.3", - "prettier": "^2.4.1", - "sass": "^1.32.7", - "sass-loader": "^12.0.0", - "speed-measure-webpack-plugin": "^1.5.0", - "style-resources-loader": "^1.5.0", - "vue-style-loader": "^4.1.3", - "vue-template-compiler": "^2.7.14" + "@iconify-json/ep": "^1.1.15", + "@rushstack/eslint-patch": "^1.10.2", + "@tsconfig/node20": "^20.1.2", + "@types/node": "^20.11.19", + "@vitejs/plugin-vue": "^5.0.3", + "@vitejs/plugin-vue-jsx": "^3.1.0", + "@vue/eslint-config-prettier": "^8.0.0", + "@vue/eslint-config-typescript": "^12.0.0", + "@vue/tsconfig": "^0.5.1", + "eslint": "^8.49.0", + "eslint-plugin-vue": "^9.17.0", + "npm-run-all2": "^6.1.1", + "prettier": "^3.0.3", + "sass": "^1.72.0", + "typescript": "~5.3.0", + "unplugin-auto-import": "^0.17.5", + "unplugin-icons": "^0.18.5", + "unplugin-vue-components": "^0.26.0", + "vite": "^5.1.4", + "vite-plugin-virtual-mpa": "^1.11.0", + "vue-tsc": "^1.8.27" }, "engines": { "node": ">=14.21.0", diff --git a/web/public/management.html b/web/public/management.html deleted file mode 100644 index 6ee8228b..00000000 --- a/web/public/management.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - <%= htmlWebpackPlugin.options.title %> - - -
- - - diff --git a/web/public/render.html b/web/public/render.html deleted file mode 100644 index e31ce4d8..00000000 --- a/web/public/render.html +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - <%= htmlWebpackPlugin.options.title %> - - - - - - -
- - - \ No newline at end of file diff --git a/web/src/common/Editor/EditorV2.vue b/web/src/common/Editor/EditorV2.vue index 0f7ed8a9..38a4ff07 100644 --- a/web/src/common/Editor/EditorV2.vue +++ b/web/src/common/Editor/EditorV2.vue @@ -1,43 +1,43 @@ diff --git a/web/src/management/pages/analysis/components/table.vue b/web/src/management/pages/analysis/components/table.vue deleted file mode 100644 index 21950f0a..00000000 --- a/web/src/management/pages/analysis/components/table.vue +++ /dev/null @@ -1,108 +0,0 @@ - - - diff --git a/web/src/management/pages/create/index.vue b/web/src/management/pages/create/CreatePage.vue similarity index 52% rename from web/src/management/pages/create/index.vue rename to web/src/management/pages/create/CreatePage.vue index 36a2784b..0b9461df 100644 --- a/web/src/management/pages/create/index.vue +++ b/web/src/management/pages/create/CreatePage.vue @@ -1,35 +1,34 @@ + - diff --git a/web/src/management/pages/edit/components/SetterField.vue b/web/src/management/pages/edit/components/SetterField.vue new file mode 100644 index 00000000..9f53717a --- /dev/null +++ b/web/src/management/pages/edit/components/SetterField.vue @@ -0,0 +1,225 @@ + + + + + diff --git a/web/src/management/pages/edit/components/submit.vue b/web/src/management/pages/edit/components/SubmitButton.vue similarity index 53% rename from web/src/management/pages/edit/components/submit.vue rename to web/src/management/pages/edit/components/SubmitButton.vue index b3b1ee50..274b5c28 100644 --- a/web/src/management/pages/edit/components/submit.vue +++ b/web/src/management/pages/edit/components/SubmitButton.vue @@ -1,38 +1,32 @@ + - diff --git a/web/src/management/pages/edit/components/setterField.vue b/web/src/management/pages/edit/components/setterField.vue deleted file mode 100644 index a587192d..00000000 --- a/web/src/management/pages/edit/components/setterField.vue +++ /dev/null @@ -1,190 +0,0 @@ - - - diff --git a/web/src/management/pages/edit/index.vue b/web/src/management/pages/edit/index.vue index 414d2d3e..c7079333 100644 --- a/web/src/management/pages/edit/index.vue +++ b/web/src/management/pages/edit/index.vue @@ -1,42 +1,52 @@ + diff --git a/web/src/management/pages/edit/modules/contentModule/save.vue b/web/src/management/pages/edit/modules/contentModule/SavePanel.vue similarity index 59% rename from web/src/management/pages/edit/modules/contentModule/save.vue rename to web/src/management/pages/edit/modules/contentModule/SavePanel.vue index 3e172240..ba2ddcc3 100644 --- a/web/src/management/pages/edit/modules/contentModule/save.vue +++ b/web/src/management/pages/edit/modules/contentModule/SavePanel.vue @@ -7,115 +7,119 @@ {{ saveText }} - - + + + + diff --git a/web/src/management/pages/edit/modules/generalModule/back.vue b/web/src/management/pages/edit/modules/generalModule/BackPanel.vue similarity index 83% rename from web/src/management/pages/edit/modules/generalModule/back.vue rename to web/src/management/pages/edit/modules/generalModule/BackPanel.vue index 578e5034..e6559fe7 100644 --- a/web/src/management/pages/edit/modules/generalModule/back.vue +++ b/web/src/management/pages/edit/modules/generalModule/BackPanel.vue @@ -4,16 +4,18 @@ 返回 + + diff --git a/web/src/management/pages/edit/modules/questionModule/components/catalogItem.vue b/web/src/management/pages/edit/modules/questionModule/components/CatalogItem.vue similarity index 85% rename from web/src/management/pages/edit/modules/questionModule/components/catalogItem.vue rename to web/src/management/pages/edit/modules/questionModule/components/CatalogItem.vue index a84d3004..9ab8947e 100644 --- a/web/src/management/pages/edit/modules/questionModule/components/catalogItem.vue +++ b/web/src/management/pages/edit/modules/questionModule/components/CatalogItem.vue @@ -10,34 +10,35 @@ - + + diff --git a/web/src/management/pages/edit/modules/questionModule/components/typeList.vue b/web/src/management/pages/edit/modules/questionModule/components/typeList.vue deleted file mode 100644 index f5673f63..00000000 --- a/web/src/management/pages/edit/modules/questionModule/components/typeList.vue +++ /dev/null @@ -1,162 +0,0 @@ - - - - - - diff --git a/web/src/management/pages/edit/modules/settingModule/setting.vue b/web/src/management/pages/edit/modules/settingModule/SettingPanel.vue similarity index 63% rename from web/src/management/pages/edit/modules/settingModule/setting.vue rename to web/src/management/pages/edit/modules/settingModule/SettingPanel.vue index c1a90060..025bcf5b 100644 --- a/web/src/management/pages/edit/modules/settingModule/setting.vue +++ b/web/src/management/pages/edit/modules/settingModule/SettingPanel.vue @@ -10,22 +10,19 @@ + - \ No newline at end of file + diff --git a/web/src/management/pages/edit/modules/settingModule/skin/previewPanel.vue b/web/src/management/pages/edit/modules/settingModule/skin/PreviewPanel.vue similarity index 56% rename from web/src/management/pages/edit/modules/settingModule/skin/previewPanel.vue rename to web/src/management/pages/edit/modules/settingModule/skin/PreviewPanel.vue index 6ea54b15..d0bda6df 100644 --- a/web/src/management/pages/edit/modules/settingModule/skin/previewPanel.vue +++ b/web/src/management/pages/edit/modules/settingModule/skin/PreviewPanel.vue @@ -3,27 +3,16 @@
- +
- - - + + - +
@@ -31,27 +20,27 @@ diff --git a/web/src/management/pages/edit/pages/setting.vue b/web/src/management/pages/edit/pages/SettingPage.vue similarity index 56% rename from web/src/management/pages/edit/pages/setting.vue rename to web/src/management/pages/edit/pages/SettingPage.vue index c38529e4..eceba5e5 100644 --- a/web/src/management/pages/edit/pages/setting.vue +++ b/web/src/management/pages/edit/pages/SettingPage.vue @@ -1,17 +1,19 @@ + + diff --git a/web/src/management/pages/edit/pages/skin/ContentPage.vue b/web/src/management/pages/edit/pages/skin/ContentPage.vue new file mode 100644 index 00000000..cc646c24 --- /dev/null +++ b/web/src/management/pages/edit/pages/skin/ContentPage.vue @@ -0,0 +1,37 @@ + + + diff --git a/web/src/management/pages/edit/pages/skin/ResultPage.vue b/web/src/management/pages/edit/pages/skin/ResultPage.vue new file mode 100644 index 00000000..b7c2d11d --- /dev/null +++ b/web/src/management/pages/edit/pages/skin/ResultPage.vue @@ -0,0 +1,30 @@ + + + diff --git a/web/src/management/pages/edit/pages/skin/content.vue b/web/src/management/pages/edit/pages/skin/content.vue deleted file mode 100644 index fe396def..00000000 --- a/web/src/management/pages/edit/pages/skin/content.vue +++ /dev/null @@ -1,32 +0,0 @@ - - - - \ No newline at end of file diff --git a/web/src/management/pages/edit/pages/skin/index.vue b/web/src/management/pages/edit/pages/skin/index.vue index c211d413..2b4df480 100644 --- a/web/src/management/pages/edit/pages/skin/index.vue +++ b/web/src/management/pages/edit/pages/skin/index.vue @@ -1,10 +1,13 @@ - diff --git a/web/src/render/components/mainRenderer.vue b/web/src/render/components/mainRenderer.vue deleted file mode 100644 index a6372602..00000000 --- a/web/src/render/components/mainRenderer.vue +++ /dev/null @@ -1,44 +0,0 @@ - - - - - diff --git a/web/src/render/components/materialGroup.vue b/web/src/render/components/materialGroup.vue deleted file mode 100644 index 98955c34..00000000 --- a/web/src/render/components/materialGroup.vue +++ /dev/null @@ -1,65 +0,0 @@ - diff --git a/web/src/render/hook/useProgress.js b/web/src/render/hook/useProgress.js deleted file mode 100644 index 058cab53..00000000 --- a/web/src/render/hook/useProgress.js +++ /dev/null @@ -1,40 +0,0 @@ -import store from '../store/index'; -import { computed } from 'vue'; -export const useProgressBar = () => { - const isVariableEmpty = (variable) => { - if (variable === undefined || variable === null) { - return true; - } - if (typeof variable === 'string' && variable.trim() === '') { - return true; - } - if (Array.isArray(variable) && variable.length === 0) { - return true; - } - if (typeof variable === 'object' && Object.keys(variable).length === 0) { - return true; - } - return false; - }; - - const surveySchedule = computed(() => { - let data = { - fillCount: 0, - topicCount: 0, - }; - const formValues = store.state.formValues; - for (let key in formValues) { - if (key.split('_').length > 1) continue; - data.topicCount++; - if (!isVariableEmpty(formValues[key])) data.fillCount++; - } - return data; - }); - - const precent = computed(() => { - const { fillCount, topicCount } = surveySchedule.value; - return Math.floor((100 / topicCount) * fillCount) + '%'; - }); - - return { surveySchedule, precent }; -}; diff --git a/web/src/render/hooks/useCommandComponent.ts b/web/src/render/hooks/useCommandComponent.ts new file mode 100644 index 00000000..2c6b6fa9 --- /dev/null +++ b/web/src/render/hooks/useCommandComponent.ts @@ -0,0 +1,88 @@ +import { createVNode, getCurrentInstance, render } from 'vue' +import type { AppContext, Component, ComponentPublicInstance, VNode } from 'vue' + +export interface Options { + visible?: boolean + onClose?: () => void + appendTo?: HTMLElement | string + [key: string]: unknown +} + +export interface CommandComponent { + (options: Options): VNode + close: () => void +} + +const getAppendToElement = (props: Options): HTMLElement => { + let appendTo: HTMLElement | null = document.body + if (props.appendTo) { + if (typeof props.appendTo === 'string') { + appendTo = document.querySelector(props.appendTo) + } + if (props.appendTo instanceof HTMLElement) { + appendTo = props.appendTo + } + if (!(appendTo instanceof HTMLElement)) { + appendTo = document.body + } + } + return appendTo +} + +const initInstance = ( + Component: T, + props: Options, + container: HTMLElement, + appContext: AppContext | null = null +) => { + const vNode = createVNode(Component, props) + vNode.appContext = appContext + render(vNode, container) + + getAppendToElement(props).appendChild(container) + return vNode +} + +export const useCommandComponent = (Component: T): CommandComponent => { + const appContext = getCurrentInstance()?.appContext + if (appContext) { + const currentProvides = (getCurrentInstance() as any)?.provides + Reflect.set(appContext, 'provides', { ...appContext.provides, ...currentProvides }) + } + + const container = document.createElement('div') + + const close = () => { + render(null, container) + container.parentNode?.removeChild(container) + } + + const CommandComponent = (options: Options): VNode => { + if (!Reflect.has(options, 'visible')) { + options.visible = true + } + if (typeof options.onClose !== 'function') { + options.onClose = close + } else { + const originOnClose = options.onClose + options.onClose = () => { + originOnClose() + close() + } + } + const vNode = initInstance(Component, options, container, appContext) + const vm = vNode.component?.proxy as ComponentPublicInstance + for (const prop in options) { + if (Reflect.has(options, prop) && !Reflect.has(vm.$props, prop)) { + vm[prop as keyof ComponentPublicInstance] = options[prop] + } + } + return vNode + } + + CommandComponent.close = close + + return CommandComponent +} + +export default useCommandComponent diff --git a/web/src/render/hooks/useProgress.js b/web/src/render/hooks/useProgress.js new file mode 100644 index 00000000..731b0a48 --- /dev/null +++ b/web/src/render/hooks/useProgress.js @@ -0,0 +1,44 @@ +import store from '../store/index' +import { computed } from 'vue' +export const useProgressBar = () => { + const isVariableEmpty = (variable) => { + if (variable === undefined || variable === null) { + return true + } + if (typeof variable === 'string' && variable.trim() === '') { + return true + } + if (Array.isArray(variable) && variable.length === 0) { + return true + } + if (typeof variable === 'object' && Object.keys(variable).length === 0) { + return true + } + return false + } + + const surveySchedule = computed(() => { + let data = { + fillCount: 0, + topicCount: 0 + } + const formValues = store.state.formValues + for (let key in formValues) { + if (key.split('_').length > 1) continue + + data.topicCount++ + if (!isVariableEmpty(formValues[key])) { + data.fillCount++ + } + } + + return data + }) + + const percent = computed(() => { + const { fillCount, topicCount } = surveySchedule.value + return Math.floor((100 / topicCount) * fillCount) + '%' + }) + + return { surveySchedule, percent } +} diff --git a/web/src/render/index.html b/web/src/render/index.html new file mode 100644 index 00000000..e555720c --- /dev/null +++ b/web/src/render/index.html @@ -0,0 +1,45 @@ + + + + + + + + + + + + + +
+ + diff --git a/web/src/render/main.js b/web/src/render/main.js index d378be33..cd5b7278 100644 --- a/web/src/render/main.js +++ b/web/src/render/main.js @@ -1,22 +1,17 @@ -import Vue from 'vue'; -import App from './App.vue'; -import store from './store'; -import { filterXSS } from '@/common/xss'; -import DialogPlugin from '@/render/plugins/dialog/index'; -import './styles/reset.scss'; +import { createApp } from 'vue' +import App from './App.vue' +import EventBus from './utils/eventbus' -Vue.config.productionTip = false; +import store from './store' +import './styles/reset.scss' -Vue.use(DialogPlugin); +const app = createApp(App) -Vue.directive('safe-html', { - inserted: function (el, binding) { - const res = filterXSS(binding.value); - el.innerHTML = res; - }, -}); +const $bus = new EventBus() +app.provide('$bus', $bus) +// 挂载到this上 +app.config.globalProperties.$bus = $bus -new Vue({ - render: (h) => h(App), - store, -}).$mount('#app'); +app.use(store) + +app.mount('#app') diff --git a/web/src/render/pages/emptyPage.vue b/web/src/render/pages/EmptyPage.vue similarity index 96% rename from web/src/render/pages/emptyPage.vue rename to web/src/render/pages/EmptyPage.vue index 3c5b94b4..1d5df382 100644 --- a/web/src/render/pages/emptyPage.vue +++ b/web/src/render/pages/EmptyPage.vue @@ -9,8 +9,8 @@ diff --git a/web/src/render/pages/successPage.vue b/web/src/render/pages/SuccessPage.vue similarity index 75% rename from web/src/render/pages/successPage.vue rename to web/src/render/pages/SuccessPage.vue index 2a79d6ac..171cdbfb 100644 --- a/web/src/render/pages/successPage.vue +++ b/web/src/render/pages/SuccessPage.vue @@ -3,28 +3,28 @@
-
+
- +
- diff --git a/web/src/render/plugins/dialog/index.js b/web/src/render/plugins/dialog/index.js index 77add5d2..433c334d 100644 --- a/web/src/render/plugins/dialog/index.js +++ b/web/src/render/plugins/dialog/index.js @@ -1,56 +1,56 @@ -import confirm from '../../components/confirm.vue'; -import alert from '../../components/alert.vue'; +import ConfirmDialog from '../../components/ConfirmDialog.vue' +import AlertDialog from '../../components/AlertDialog.vue' -import { isFunction as _isFunction } from 'lodash-es'; +import { isFunction as _isFunction } from 'lodash-es' export default { install(Vue) { Vue.prototype.$dialog = { confirm(options) { - const MyComponent = Vue.extend(confirm); + const MyComponent = Vue.extend(ConfirmDialog) const instance = new MyComponent({ - propsData: options, - }); + propsData: options + }) const closeConfirm = () => { if (instance && instance.$el) { - instance.$el.remove(); + instance.$el.remove() } - }; + } instance.$on('cancel', () => { if (options?.onCancel && _isFunction(options.onCancel)) { - options.onCancel(closeConfirm); + options.onCancel(closeConfirm) } else { - closeConfirm(); + closeConfirm() } - }); + }) instance.$on('confirm', () => { if (options?.onConfirm && _isFunction(options.onConfirm)) { - options.onConfirm(closeConfirm); + options.onConfirm(closeConfirm) } - }); - instance.$mount(); - document.body.append(instance.$el); + }) + instance.$mount() + document.body.append(instance.$el) }, alert(options) { - const MyComponent = Vue.extend(alert); + const MyComponent = Vue.extend(AlertDialog) const instance = new MyComponent({ - propsData: options, - }); + propsData: options + }) const closeConfirm = () => { if (instance && instance.$el) { - instance.$el.remove(); + instance.$el.remove() } - }; + } instance.$on('confirm', () => { if (options?.onConfirm && _isFunction(options.onConfirm)) { - options.onConfirm(closeConfirm); + options.onConfirm(closeConfirm) } else { - closeConfirm(); + closeConfirm() } - }); - instance.$mount(); - document.body.append(instance.$el); - }, - }; - }, -}; + }) + instance.$mount() + document.body.append(instance.$el) + } + } + } +} diff --git a/web/src/render/store/actions.js b/web/src/render/store/actions.js index 88c8bf58..50dec853 100644 --- a/web/src/render/store/actions.js +++ b/web/src/render/store/actions.js @@ -1,67 +1,67 @@ -import moment from 'moment'; +import moment from 'moment' // 引入中文 -import 'moment/locale/zh-cn'; +import 'moment/locale/zh-cn' // 设置中文 -moment.locale('zh-cn'); -import adapter from '../adapter'; -import { queryVote, getEncryptInfo } from '@/render/api/survey'; -import { CODE_MAP } from '@/management/api/base'; +moment.locale('zh-cn') +import adapter from '../adapter' +import { queryVote, getEncryptInfo } from '@/render/api/survey' +/** + * CODE_MAP不从management引入,在dev阶段,会导致B端 router被加载,进而导致C端路由被添加 baseUrl: /management + */ +const CODE_MAP = { + SUCCESS: 200, + ERROR: 500, + NO_AUTH: 403 +} export default { // 初始化 - init( - { commit, dispatch }, - { bannerConf, baseConf, bottomConf, dataConf, skinConf, submitConf } - ) { - commit('setEnterTime'); - const { begTime, endTime, answerBegTime, answerEndTime } = baseConf; - const { msgContent } = submitConf; - const now = Date.now(); + init({ commit, dispatch }, { bannerConf, baseConf, bottomConf, dataConf, skinConf, submitConf }) { + commit('setEnterTime') + const { begTime, endTime, answerBegTime, answerEndTime } = baseConf + const { msgContent } = submitConf + const now = Date.now() if (now < new Date(begTime).getTime()) { - commit('setRouter', 'errorPage'); + commit('setRouter', 'errorPage') commit('setErrorInfo', { errorType: 'overTime', errorMsg: `

问卷未到开始填写时间,暂时无法进行填写

-

开始时间为: ${begTime}

`, - }); - return; +

开始时间为: ${begTime}

` + }) + return } else if (now > new Date(endTime).getTime()) { - commit('setRouter', 'errorPage'); + commit('setRouter', 'errorPage') commit('setErrorInfo', { errorType: 'overTime', - errorMsg: msgContent.msg_9001 || '您来晚了,感谢支持问卷~', - }); - return; + errorMsg: msgContent.msg_9001 || '您来晚了,感谢支持问卷~' + }) + return } else if (answerBegTime && answerEndTime) { - const momentNow = moment(); - const todayStr = momentNow.format('yyyy-MM-DD'); - const momentStartTime = moment(`${todayStr} ${answerBegTime}`); - const momentEndTime = moment(`${todayStr} ${answerEndTime}`); - if ( - momentNow.isBefore(momentStartTime) || - momentNow.isAfter(momentEndTime) - ) { - commit('setRouter', 'errorPage'); + const momentNow = moment() + const todayStr = momentNow.format('yyyy-MM-DD') + const momentStartTime = moment(`${todayStr} ${answerBegTime}`) + const momentEndTime = moment(`${todayStr} ${answerEndTime}`) + if (momentNow.isBefore(momentStartTime) || momentNow.isAfter(momentEndTime)) { + commit('setRouter', 'errorPage') commit('setErrorInfo', { errorType: 'overTime', errorMsg: `

不在答题时间范围内,暂时无法进行填写

-

答题时间为: ${answerBegTime} ~ ${answerEndTime}

`, - }); - return; +

答题时间为: ${answerBegTime} ~ ${answerEndTime}

` + }) + return } } - commit('setRouter', 'indexPage'); + commit('setRouter', 'indexPage') // 根据初始的schema生成questionData, questionSeq, rules, formValues, 这四个字段 - const { questionData, questionSeq, rules, formValues } = - adapter.generateData({ - bannerConf, - baseConf, - bottomConf, - dataConf, - skinConf, - submitConf, - }); + const { questionData, questionSeq, rules, formValues } = adapter.generateData({ + bannerConf, + baseConf, + bottomConf, + dataConf, + skinConf, + submitConf + }) // 将数据设置到state上 commit('assignState', { @@ -74,53 +74,53 @@ export default { dataConf, skinConf, submitConf, - formValues, - }); + formValues + }) // 获取已投票数据 - dispatch('initVoteData'); + dispatch('initVoteData') }, // 用户输入或者选择后,更新表单数据 changeData({ commit }, data) { - commit('changeFormData', data); + commit('changeFormData', data) }, // 初始化投票题的数据 async initVoteData({ state, commit }) { - const questionData = state.questionData; - const surveyPath = state.surveyPath; + const questionData = state.questionData + const surveyPath = state.surveyPath - const fieldList = []; + const fieldList = [] for (const field in questionData) { - const { type } = questionData[field]; + const { type } = questionData[field] if (/vote/.test(type)) { - fieldList.push(field); + fieldList.push(field) } } if (fieldList.length <= 0) { - return; + return } try { const voteRes = await queryVote({ surveyPath, - fieldList: fieldList.join(','), - }); + fieldList: fieldList.join(',') + }) if (voteRes.code === 200) { - commit('setVoteMap', voteRes.data); + commit('setVoteMap', voteRes.data) } } catch (error) { - console.log(error); + console.log(error) } }, async getEncryptInfo({ commit }) { try { - const res = await getEncryptInfo(); + const res = await getEncryptInfo() if (res.code === CODE_MAP.SUCCESS) { - commit('setEncryptInfo', res.data); + commit('setEncryptInfo', res.data) } } catch (error) { - console.log(error); + console.log(error) } - }, -}; + } +} diff --git a/web/src/render/store/getters.js b/web/src/render/store/getters.js index c2f3f476..9764b6b3 100644 --- a/web/src/render/store/getters.js +++ b/web/src/render/store/getters.js @@ -1,101 +1,97 @@ -import { flatten } from 'lodash-es'; +import { flatten } from 'lodash-es' export default { // 题目列表 renderData: (state) => { - const { questionSeq, questionData, formValues } = state; - let index = 1; + const { questionSeq, questionData, formValues } = state + let index = 1 return ( questionSeq && questionSeq.reduce((pre, item) => { - const questionArr = []; + const questionArr = [] for (const questionKey of item) { - const question = { ...questionData[questionKey] }; - const { type, extraOptions, options, rangeConfig } = question; + const question = { ...questionData[questionKey] } + const { type, extraOptions, options, rangeConfig } = question - const questionVal = formValues[questionKey]; + const questionVal = formValues[questionKey] - question.value = questionVal; + question.value = questionVal // 本题开启了 if (question.showIndex) { - question.indexNumber = index++; + question.indexNumber = index++ } - const allOptions = []; + const allOptions = [] if (Array.isArray(extraOptions)) { - allOptions.push(...extraOptions); + allOptions.push(...extraOptions) } if (Array.isArray(options)) { - allOptions.push(...options); + allOptions.push(...options) } - let othersValue = {}; - let voteTotal = 0; - const voteMap = state.voteMap; + let othersValue = {} + let voteTotal = 0 + const voteMap = state.voteMap if (/vote/.test(type)) { - voteTotal = voteMap?.[questionKey]?.total || 0; + voteTotal = voteMap?.[questionKey]?.total || 0 } // 遍历所有的选项 for (const optionItem of allOptions) { // 开启了更多输入框,生成othersValue的值 if (optionItem.others) { - const opKey = `${questionKey}_${optionItem.hash}`; - optionItem.othersKey = opKey; - optionItem.othersValue = formValues[opKey]; - othersValue[opKey] = formValues[opKey]; + const opKey = `${questionKey}_${optionItem.hash}` + optionItem.othersKey = opKey + optionItem.othersValue = formValues[opKey] + othersValue[opKey] = formValues[opKey] } // 投票题,用户手动选择选项后,要实时更新展示数据和进度 if (/vote/.test(type)) { - const voteCount = voteMap?.[questionKey]?.[optionItem.hash] || 0; + const voteCount = voteMap?.[questionKey]?.[optionItem.hash] || 0 if ( Array.isArray(questionVal) ? questionVal.includes(optionItem.hash) : questionVal === optionItem.hash ) { - optionItem.voteCount = voteCount + 1; - voteTotal = voteTotal + 1; + optionItem.voteCount = voteCount + 1 + voteTotal = voteTotal + 1 } else { - optionItem.voteCount = voteCount; + optionItem.voteCount = voteCount } - question.voteTotal = voteTotal; + question.voteTotal = voteTotal } } // 开启了更多输入框,要将当前的value赋值给question - if ( - rangeConfig && - Object.keys(rangeConfig).length > 0 && - rangeConfig[questionVal] - ) { - const curRange = rangeConfig[questionVal]; + if (rangeConfig && Object.keys(rangeConfig).length > 0 && rangeConfig[questionVal]) { + const curRange = rangeConfig[questionVal] if (curRange?.isShowInput) { - const rangeKey = `${questionKey}_${questionVal}`; - curRange.othersKey = rangeKey; - curRange.othersValue = formValues[rangeKey]; - othersValue[rangeKey] = formValues[rangeKey]; + const rangeKey = `${questionKey}_${questionVal}` + curRange.othersKey = rangeKey + curRange.othersValue = formValues[rangeKey] + othersValue[rangeKey] = formValues[rangeKey] } } // 将othersValue赋值给 - question.othersValue = othersValue; - questionArr.push(question); + question.othersValue = othersValue + questionArr.push(question) } if (questionArr && questionArr.length) { - pre.push(questionArr); + pre.push(questionArr) } - return pre; + return pre }, []) - ); + ) }, // 根据渲染的题目生成的用户输入或者选择的数据 formModel: (state, getters) => { - const { renderData } = getters; + const { renderData } = getters const formdata = flatten(renderData).reduce((pre, current) => { - const { othersValue, type, field } = current; + const { othersValue, type, field } = current if (othersValue && Object.keys(othersValue).length) { - Object.assign(pre, othersValue); + Object.assign(pre, othersValue) } switch (type) { // case 'fillin': @@ -110,12 +106,12 @@ export default { // Object.assign(pre, { [field]: formValues[field] }) // break default: - Object.assign(pre, { [field]: current.value }); - break; + Object.assign(pre, { [field]: current.value }) + break } - return pre; - }, {}); - return formdata; - }, -}; + return pre + }, {}) + return formdata + } +} diff --git a/web/src/render/store/index.js b/web/src/render/store/index.js index eaeceff0..4013b40c 100644 --- a/web/src/render/store/index.js +++ b/web/src/render/store/index.js @@ -1,15 +1,13 @@ -import Vue from 'vue'; -import Vuex, { Store } from 'vuex'; -Vue.use(Vuex); +import { createStore } from 'vuex' -import state from './state'; -import getters from './getters'; -import mutations from './mutations'; -import actions from './actions'; +import state from './state' +import getters from './getters' +import mutations from './mutations' +import actions from './actions' -export default new Store({ +export default createStore({ state, getters, mutations, - actions, -}); + actions +}) diff --git a/web/src/render/store/mutations.js b/web/src/render/store/mutations.js index f3fd4493..d88fb2a1 100644 --- a/web/src/render/store/mutations.js +++ b/web/src/render/store/mutations.js @@ -1,46 +1,45 @@ -import Vue from 'vue'; -import { forEach, set } from 'lodash-es'; +import { forEach, set } from 'lodash-es' export default { // 将数据设置到state上 assignState(state, data) { forEach(data, (value, key) => { - Vue.set(state, key, value); - }); + state[key] = value + }) }, setQuestionData(state, data) { - state.questionData = data; + state.questionData = data }, setRouter(state, data) { - state.router = data; + state.router = data }, setErrorInfo(state, { errorType, errorMsg }) { state.errorInfo = { errorType, - errorMsg, - }; + errorMsg + } }, changeFormData(state, data) { - let { key, value } = data; - set(state, `formValues.${key}`, value); + let { key, value } = data + set(state, `formValues.${key}`, value) // set(state, `questionData.${key}.value`, value) }, changeSelectMoreData(state, data) { - const { key, value, field } = data; - set(state, `questionData.${field}.othersValue.${key}`, value); + const { key, value, field } = data + set(state, `questionData.${field}.othersValue.${key}`, value) }, setEnterTime(state) { - state.enterTime = Date.now(); + state.enterTime = Date.now() }, setSurveyPath(state, data) { - state.surveyPath = data; + state.surveyPath = data }, setVoteMap(state, data) { - Vue.set(state, 'voteMap', data); + state.voteMap = data }, setQuestionSeq(state, data) { - state.questionSeq = data; + state.questionSeq = data }, setEncryptInfo(state, data) { - state.encryptInfo = data; - }, -}; + state.encryptInfo = data + } +} diff --git a/web/src/render/store/state.js b/web/src/render/store/state.js index b69de743..ac3f523f 100644 --- a/web/src/render/store/state.js +++ b/web/src/render/store/state.js @@ -1,4 +1,4 @@ -import { isMobile } from '../utils/index'; +import { isMobile } from '../utils/index' export default { surveyPath: '', @@ -7,10 +7,10 @@ export default { isMobile: isMobile(), errorInfo: { errorType: '', - errorMsg: '', + errorMsg: '' }, enterTime: null, questionSeq: [], // 题目的顺序,因为可能会有分页的情况,所以是一个二维数组[[qid1, qid2], [qid3,qid4]] voteMap: {}, - encryptInfo: null, -}; + encryptInfo: null +} diff --git a/web/src/render/styles/default.scss b/web/src/render/styles/default.scss index 4b67849b..eaebf2a6 100644 --- a/web/src/render/styles/default.scss +++ b/web/src/render/styles/default.scss @@ -1,4 +1,4 @@ -$primary-color: #FAA600; +$primary-color: #faa600; $primary-color-light: hsl(48, 100%, 97%); $title-color-deep: #292a36; @@ -15,7 +15,6 @@ $spliter-color: #f7f7f7; $error-color: #ec4e29; - @import './variable'; $title-size: 0.32rem; diff --git a/web/src/render/styles/dialog.scss b/web/src/render/styles/dialog.scss index 85eb6b08..55845cc4 100644 --- a/web/src/render/styles/dialog.scss +++ b/web/src/render/styles/dialog.scss @@ -18,9 +18,9 @@ } .title { - font-size: .3rem; + font-size: 0.3rem; color: #4a4c5b; letter-spacing: 0; text-align: center; font-weight: 600; -} \ No newline at end of file +} diff --git a/web/src/render/styles/icon.scss b/web/src/render/styles/icon.scss index 6c75ec54..6a01c53e 100644 --- a/web/src/render/styles/icon.scss +++ b/web/src/render/styles/icon.scss @@ -1,12 +1,13 @@ @font-face { - font-family: "iconfont"; /* Project id 4263882 */ - src: url('//at.alicdn.com/t/c/font_4263882_y9jng0gwthr.woff2?t=1695631530492') format('woff2'), - url('//at.alicdn.com/t/c/font_4263882_y9jng0gwthr.woff?t=1695631530492') format('woff'), - url('//at.alicdn.com/t/c/font_4263882_y9jng0gwthr.ttf?t=1695631530492') format('truetype'); + font-family: 'iconfont'; /* Project id 4263882 */ + src: + url('//at.alicdn.com/t/c/font_4263882_y9jng0gwthr.woff2?t=1695631530492') format('woff2'), + url('//at.alicdn.com/t/c/font_4263882_y9jng0gwthr.woff?t=1695631530492') format('woff'), + url('//at.alicdn.com/t/c/font_4263882_y9jng0gwthr.ttf?t=1695631530492') format('truetype'); } .iconfont { - font-family: "iconfont" !important; + font-family: 'iconfont' !important; font-size: 16px; font-style: normal; -webkit-font-smoothing: antialiased; @@ -14,5 +15,5 @@ } .icon-kaishi:before { - content: "\e6ad"; + content: '\e6ad'; } diff --git a/web/src/render/styles/reset.scss b/web/src/render/styles/reset.scss index 981aa5f0..236e4cee 100644 --- a/web/src/render/styles/reset.scss +++ b/web/src/render/styles/reset.scss @@ -7,7 +7,14 @@ body { height: 100%; - font-family: "-apple-system", BlinkMacSystemFont, Droid Sans, Droid Sans Fallback, Helvetica Neue, Helvetica, Arial; + font-family: + '-apple-system', + BlinkMacSystemFont, + Droid Sans, + Droid Sans Fallback, + Helvetica Neue, + Helvetica, + Arial; } html { @@ -28,7 +35,9 @@ input { background-color: #fff; } -button, button:active, button:focus { +button, +button:active, +button:focus { border: none; } @@ -62,9 +71,13 @@ textarea::-webkit-input-placeholder { font-family: normal !important; } - body { - font-family: Pingfang SC, 'Helvetica Neue', Helvetica, STHeiTi, sans-serif; + font-family: + Pingfang SC, + 'Helvetica Neue', + Helvetica, + STHeiTi, + sans-serif; font-size: 14px; line-height: 1.42857143; } @@ -191,4 +204,4 @@ a { a:hover * { text-decoration: none; -} \ No newline at end of file +} diff --git a/web/src/render/styles/variable.scss b/web/src/render/styles/variable.scss index 553eaeff..2b0db56a 100644 --- a/web/src/render/styles/variable.scss +++ b/web/src/render/styles/variable.scss @@ -1,15 +1,15 @@ -$primary-color: #FA881A; -$primary-color-light: #FEF3E8; +$primary-color: #fa881a; +$primary-color-light: #fef3e8; -$title-color-deep: #292A36; -$title-color: #4A4C5B; -$font-color: #6E707C; +$title-color-deep: #292a36; +$title-color: #4a4c5b; +$font-color: #6e707c; -$placeholder-color: #C8C9CD; +$placeholder-color: #c8c9cd; -$disable-color: #F2F4F7; -$border-color: #DEE2E6; +$disable-color: #f2f4f7; +$border-color: #dee2e6; $spliter-color: #f7f7f7; -$error-color: #EC4E29; +$error-color: #ec4e29; diff --git a/web/src/render/utils/encrypt.js b/web/src/render/utils/encrypt.js index 3db82973..1d325e5d 100644 --- a/web/src/render/utils/encrypt.js +++ b/web/src/render/utils/encrypt.js @@ -1,19 +1,19 @@ -import * as forge from 'node-forge'; +import * as forge from 'node-forge' function rsa({ data, secretKey }) { - const publicKeyObject = forge.pki.publicKeyFromPem(secretKey); - const dataArr = []; - const originData = encodeURIComponent(data); - const step = 200; + const publicKeyObject = forge.pki.publicKeyFromPem(secretKey) + const dataArr = [] + const originData = encodeURIComponent(data) + const step = 200 for (let i = 0; i < originData.length; i += step) { const encryptData = forge.util.encode64( publicKeyObject.encrypt(originData.slice(i, i + step), 'RSA-OAEP') - ); - dataArr.push(encryptData); + ) + dataArr.push(encryptData) } - return dataArr; + return dataArr } export default { - rsa, -}; + rsa +} diff --git a/web/src/render/utils/eventbus.js b/web/src/render/utils/eventbus.js new file mode 100644 index 00000000..455d3332 --- /dev/null +++ b/web/src/render/utils/eventbus.js @@ -0,0 +1,27 @@ +export default class EventBus { + constructor() { + this.events = {} + } + emit(eventName, data) { + if (this.events[eventName]) { + this.events[eventName].forEach(function (fn) { + fn(data) + }) + } + } + on(eventName, fn) { + this.events[eventName] = this.events[eventName] || [] + this.events[eventName].push(fn) + } + + off(eventName, fn) { + if (this.events[eventName]) { + for (var i = 0; i < this.events[eventName].length; i++) { + if (this.events[eventName][i] === fn) { + this.events[eventName].splice(i, 1) + break + } + } + } + } +} diff --git a/web/src/render/utils/index.js b/web/src/render/utils/index.js index cb0ec650..610e506f 100644 --- a/web/src/render/utils/index.js +++ b/web/src/render/utils/index.js @@ -1,39 +1,32 @@ export function isMobile() { - const userAgentInfo = navigator.userAgent; - const Agents = [ - 'Android', - 'iPhone', - 'SymbianOS', - 'Windows Phone', - 'iPad', - 'iPod', - ]; - let flag = false; + const userAgentInfo = navigator.userAgent + const Agents = ['Android', 'iPhone', 'SymbianOS', 'Windows Phone', 'iPad', 'iPod'] + let flag = false for (let v = 0; v < Agents.length; v++) { if (userAgentInfo.indexOf(Agents[v]) > 0) { - flag = true; - break; + flag = true + break } } - const w = document.body && document.body.clientWidth; + const w = document.body && document.body.clientWidth if (w > 960) { - return false; + return false } else if (w < 480) { - return true; + return true } else { - return flag; + return flag } } // 对链接做一个兼容转换,支持用户不配置http开头或者配置 // 开头 export const formatLink = (url) => { - url = url.trim(); + url = url.trim() if (!url) { - return url; + return url } if (url.startsWith('http') || url.startsWith('//')) { - return url; + return url } - return `http://${url}`; -}; + return `http://${url}` +} diff --git a/web/tsconfig.app.json b/web/tsconfig.app.json new file mode 100644 index 00000000..2bdf1188 --- /dev/null +++ b/web/tsconfig.app.json @@ -0,0 +1,15 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], + "exclude": ["src/**/__tests__/*"], + "compilerOptions": { + "composite": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + }, + "allowJs": true + } +} diff --git a/web/tsconfig.json b/web/tsconfig.json new file mode 100644 index 00000000..66b5e570 --- /dev/null +++ b/web/tsconfig.json @@ -0,0 +1,11 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.node.json" + }, + { + "path": "./tsconfig.app.json" + } + ] +} diff --git a/web/tsconfig.node.json b/web/tsconfig.node.json new file mode 100644 index 00000000..f0940630 --- /dev/null +++ b/web/tsconfig.node.json @@ -0,0 +1,19 @@ +{ + "extends": "@tsconfig/node20/tsconfig.json", + "include": [ + "vite.config.*", + "vitest.config.*", + "cypress.config.*", + "nightwatch.conf.*", + "playwright.config.*" + ], + "compilerOptions": { + "composite": true, + "noEmit": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + + "module": "ESNext", + "moduleResolution": "Bundler", + "types": ["node"] + } +} diff --git a/web/vite.config.ts b/web/vite.config.ts new file mode 100644 index 00000000..d7e5a8d8 --- /dev/null +++ b/web/vite.config.ts @@ -0,0 +1,117 @@ +import { fileURLToPath, URL } from 'node:url' +import { defineConfig, normalizePath } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueJsx from '@vitejs/plugin-vue-jsx' +import { createMpaPlugin, createPages } from 'vite-plugin-virtual-mpa' +import AutoImport from 'unplugin-auto-import/vite' +import Components from 'unplugin-vue-components/vite' +import Icons from 'unplugin-icons/vite' +import IconsResolver from 'unplugin-icons/resolver' + +import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' + +const isProd = process.env.NODE_ENV === 'production' + +const pages = createPages([ + { + name: 'management', + filename: isProd ? 'management.html' : 'src/management/index.html', + template: 'src/management/index.html', + entry: '/src/management/main.js' + }, + { + name: 'render', + filename: isProd ? 'render.html' : 'src/render/index.html', + template: 'src/render/index.html', + entry: '/src/render/main.js' + } +]) +const mpaPlugin = createMpaPlugin({ + pages, + verbose: true, + rewrites: [ + { + from: /render/, + to: () => normalizePath('/src/render/index.html') + }, + { + from: /\/|\/management\/.?/, + to: () => normalizePath('/src/management/index.html') + } + ] +}) + +// https://vitejs.dev/config/ +export default defineConfig({ + optimizeDeps: { + include: [ + 'lodash-es', + 'async-validator', + 'vuedraggable', + 'element-plus/es', + '@wangeditor/editor-for-vue', + 'element-plus/es/components/*/style/index', + 'element-plus/dist/locale/zh-cn.mjs', + 'clipboard', + 'qrcode', + 'moment', + 'moment/locale/zh-cn' + ] + }, + plugins: [ + vue(), + vueJsx(), + AutoImport({ + resolvers: [ + ElementPlusResolver(), + // Auto import icon components + IconsResolver({ + prefix: 'Icon' + }) + ] + }), + Components({ + resolvers: [ + ElementPlusResolver({ + importStyle: 'sass' + }), + // Auto register icon components + IconsResolver({ + enabledCollections: ['ep'] + }) + ] + }), + Icons({ + autoInstall: true + }), + mpaPlugin + ], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)), + '@management': fileURLToPath(new URL('./src/management', import.meta.url)), + '@materials': fileURLToPath(new URL('./src/materials', import.meta.url)), + '@render': fileURLToPath(new URL('./src/render', import.meta.url)) + } + }, + appType: 'mpa', + css: { + preprocessorOptions: { + scss: { + additionalData: `@use "@/management/styles/element-variables.scss" as *;` + } + } + }, + server: { + port: 8080, + proxy: { + '/api': { + target: 'http://127.0.0.1:3000', + changeOrigin: true + } + } + }, + build: { + rollupOptions: {} + } +}) diff --git a/web/vue.config.js b/web/vue.config.js deleted file mode 100644 index 4ed0498b..00000000 --- a/web/vue.config.js +++ /dev/null @@ -1,124 +0,0 @@ -const { defineConfig } = require('@vue/cli-service'); -const Webpack = require('webpack'); -// 分析打包时间 -const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin'); - -module.exports = defineConfig({ - transpileDependencies: true, - lintOnSave: false, - pages: { - management: { - entry: `src/management/main.js`, - template: 'public/management.html', - filename: `management.html`, - title: '问卷调研', - }, - render: { - entry: `src/render/main.js`, - template: 'public/render.html', - filename: `render.html`, - title: '问卷调研', - }, - }, - css: { - loaderOptions: { - sass: { - additionalData: `@import "./src/management/styles/variable.scss";`, - }, - }, - }, - devServer: { - proxy: { - '/api': { - target: 'http://127.0.0.1:3000', - changeOrigin: true, - }, - }, - setupMiddlewares(middlewares, devServer) { - if (!devServer) { - throw new Error('webpack-dev-server is not defined'); - } - devServer.app.get('/', function (req, res) { - res.redirect('/management'); - }); - return middlewares; - }, - open: true, - }, - configureWebpack: { - plugins: [ - new Webpack.IgnorePlugin({ - resourceRegExp: /^\.\/locale$/, - contextRegExp: /moment$/, - }), - ], - }, - chainWebpack: (config) => { - config.module - .rule('js') - .test(/\.jsx?$/) - .exclude.add(/node_modules/) - .end() - .use('babel-loader') - .loader('babel-loader') - .end(); - - config.optimization.splitChunks({ - cacheGroups: { - setterWidgets: { - name: 'chunk-setterWidgets', - test: /\/materials\/setters[\\/]/, - chunks: 'async', - enforce: true, - }, - materialWidgets: { - name: 'chunk-materialWidgets', - test: /\/materials\/questions[\\/]/, - chunks: 'async', - enforce: true, - }, - commonEditor: { - name: 'chunk-commonEditor', - test: /\/common\/Editor[\\/]/, - enforce: true, - }, - element: { - name: 'chunk-element-ui', - test: /[\\/]node_modules[\\/]element-ui[\\/]/, - chunks: 'all', - priority: 3, - reuseExistingChunk: true, - enforce: true, - }, - moment: { - name: 'chunk-moment', - test: /[\\/]node_modules[\\/]moment[\\/]/, - chunks: 'all', - priority: 3, - reuseExistingChunk: true, - enforce: true, - }, - '@wangeditor': { - name: 'chunk-wangeditor', - test: /[\\/]node_modules[\\/]@wangeditor[\\/]/, - chunks: 'all', - priority: 3, - reuseExistingChunk: true, - enforce: true, - }, - common: { - //抽取所有入口页面都需要的公共chunk - name: 'chunk-common', - chunks: 'initial', - minChunks: 2, - maxInitialRequests: 5, - minSize: 0, - priority: 1, - reuseExistingChunk: true, - enforce: true, - }, - }, - }); - config.plugin('speed').use(SpeedMeasureWebpackPlugin); - }, -});