feat: vue3 (#103)
This commit is contained in:
parent
2bca93bf12
commit
6771b831e5
15
web/.eslintrc.cjs
Normal file
15
web/.eslintrc.cjs
Normal file
@ -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'
|
||||||
|
}
|
||||||
|
}
|
@ -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'],
|
|
||||||
},
|
|
||||||
};
|
|
1
web/.gitignore
vendored
1
web/.gitignore
vendored
@ -14,6 +14,7 @@ yarn-debug.log*
|
|||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
pnpm-debug.log*
|
pnpm-debug.log*
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
pnpm-lock.yaml
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.idea
|
.idea
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
singleQuote: true, // 使用单引号
|
|
||||||
semi: true, // 不使用分号
|
|
||||||
// trailingComma: 'all', // 在对象和数组末尾加上逗号
|
|
||||||
};
|
|
8
web/.prettierrc.json
Normal file
8
web/.prettierrc.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/prettierrc",
|
||||||
|
"semi": false,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 100,
|
||||||
|
"trailingComma": "none"
|
||||||
|
}
|
10
web/auto-imports.d.ts
vendored
Normal file
10
web/auto-imports.d.ts
vendored
Normal file
@ -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']
|
||||||
|
}
|
@ -1,11 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
presets: [
|
|
||||||
'@vue/cli-plugin-babel/preset',
|
|
||||||
[
|
|
||||||
'@vue/babel-preset-jsx',
|
|
||||||
{
|
|
||||||
injectH: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
],
|
|
||||||
};
|
|
63
web/components.d.ts
vendored
Normal file
63
web/components.d.ts
vendored
Normal file
@ -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']
|
||||||
|
}
|
||||||
|
}
|
8
web/env.d.ts
vendored
Normal file
8
web/env.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
|
@ -2,55 +2,57 @@
|
|||||||
"name": "web",
|
"name": "web",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve",
|
"serve": "vite",
|
||||||
"build": "vue-cli-service build",
|
"dev": "vite",
|
||||||
"report": "vue-cli-service build --report",
|
"build": "run-p type-check \"build-only {@}\" --",
|
||||||
"lint": "vue-cli-service lint",
|
"preview": "vite preview",
|
||||||
"lintfix": "eslint --fix ."
|
"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": {
|
"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": "^5.1.23",
|
||||||
|
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||||
"async-validator": "^4.2.5",
|
"async-validator": "^4.2.5",
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
"clipboard": "^2.0.11",
|
"clipboard": "^2.0.11",
|
||||||
"core-js": "^3.8.3",
|
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"element-ui": "^2.15.13",
|
"element-plus": "^2.7.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"node-forge": "^1.3.1",
|
"node-forge": "^1.3.1",
|
||||||
"qrcode": "^1.5.3",
|
"qrcode": "^1.5.3",
|
||||||
"vue": "^2.7.14",
|
"vue": "^3.4.15",
|
||||||
"vue-router": "^3.5.1",
|
"vue-router": "^4.2.5",
|
||||||
"vuedraggable": "^2.24.3",
|
"vuedraggable": "^4.1.0",
|
||||||
"vuex": "^3.6.2",
|
"vuex": "^4.0.2",
|
||||||
"xss": "^1.0.14"
|
"xss": "^1.0.14"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.12.16",
|
"@iconify-json/ep": "^1.1.15",
|
||||||
"@babel/eslint-parser": "^7.12.16",
|
"@rushstack/eslint-patch": "^1.10.2",
|
||||||
"@vue/cli-plugin-babel": "~5.0.0",
|
"@tsconfig/node20": "^20.1.2",
|
||||||
"@vue/cli-plugin-eslint": "~5.0.0",
|
"@types/node": "^20.11.19",
|
||||||
"@vue/cli-plugin-router": "~5.0.0",
|
"@vitejs/plugin-vue": "^5.0.3",
|
||||||
"@vue/cli-plugin-vuex": "~5.0.0",
|
"@vitejs/plugin-vue-jsx": "^3.1.0",
|
||||||
"@vue/cli-service": "~5.0.0",
|
"@vue/eslint-config-prettier": "^8.0.0",
|
||||||
"eslint": "^7.32.0",
|
"@vue/eslint-config-typescript": "^12.0.0",
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"@vue/tsconfig": "^0.5.1",
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint": "^8.49.0",
|
||||||
"eslint-plugin-vue": "^8.7.1",
|
"eslint-plugin-vue": "^9.17.0",
|
||||||
"less-loader": "^11.1.3",
|
"npm-run-all2": "^6.1.1",
|
||||||
"postcss-import": "^15.1.0",
|
"prettier": "^3.0.3",
|
||||||
"postcss-url": "^10.1.3",
|
"sass": "^1.72.0",
|
||||||
"prettier": "^2.4.1",
|
"typescript": "~5.3.0",
|
||||||
"sass": "^1.32.7",
|
"unplugin-auto-import": "^0.17.5",
|
||||||
"sass-loader": "^12.0.0",
|
"unplugin-icons": "^0.18.5",
|
||||||
"speed-measure-webpack-plugin": "^1.5.0",
|
"unplugin-vue-components": "^0.26.0",
|
||||||
"style-resources-loader": "^1.5.0",
|
"vite": "^5.1.4",
|
||||||
"vue-style-loader": "^4.1.3",
|
"vite-plugin-virtual-mpa": "^1.11.0",
|
||||||
"vue-template-compiler": "^2.7.14"
|
"vue-tsc": "^1.8.27"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.21.0",
|
"node": ">=14.21.0",
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
|
||||||
<link rel="icon" href="<%= BASE_URL %>imgs/favicon.ico">
|
|
||||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="app"></div>
|
|
||||||
<!-- built files will be auto injected -->
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,49 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
|
||||||
<link rel="icon" href="<%= BASE_URL %>imgs/favicon.ico">
|
|
||||||
<title>
|
|
||||||
<%= htmlWebpackPlugin.options.title %>
|
|
||||||
</title>
|
|
||||||
<script>
|
|
||||||
(function () {
|
|
||||||
function resetRemUnit() {
|
|
||||||
var PC_W = 750;
|
|
||||||
var docEl = window.document.documentElement;
|
|
||||||
var width = docEl.getBoundingClientRect().width || 375;
|
|
||||||
|
|
||||||
if (!(/Android|webOS|iPhone|iPad|iPod|BlackBerry/i).test(navigator.userAgent)) {
|
|
||||||
width = width < PC_W ? width : PC_W;
|
|
||||||
docEl.className += ' ispc-html';
|
|
||||||
}
|
|
||||||
|
|
||||||
var f = Math.min(width / 7.5, 50);
|
|
||||||
docEl.style.fontSize = f + 'px';
|
|
||||||
|
|
||||||
var d = window.document.createElement('div');
|
|
||||||
d.style.width = '1rem';
|
|
||||||
d.style.display = "none";
|
|
||||||
var head = window.document.getElementsByTagName('head')[0];
|
|
||||||
head.appendChild(d);
|
|
||||||
var realf = parseFloat(window.getComputedStyle(d, null).getPropertyValue('width'));
|
|
||||||
|
|
||||||
if (f !== realf) {
|
|
||||||
docEl.style.fontSize = f * (f / realf) + 'px';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resetRemUnit();
|
|
||||||
window.addEventListener('resize', resetRemUnit);
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
<style></style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div id="app"></div>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,43 +1,43 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="editor-v2">
|
<div class="editor-v2">
|
||||||
<RichEditor :value="realData" @input="handleChange" @blur="handleBlur" />
|
<RichEditor :modelValue="realData" @input="handleChange" @blur="handleBlur" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import RichEditor from './RichEditor';
|
import RichEditor from './RichEditor.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
RichEditor,
|
RichEditor
|
||||||
// ReadOnly,
|
// ReadOnly,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
realData: {
|
realData: {
|
||||||
type: String,
|
type: String,
|
||||||
default: () => '',
|
default: () => ''
|
||||||
},
|
},
|
||||||
questionDataList: {
|
questionDataList: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => [],
|
default: () => []
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {};
|
return {}
|
||||||
},
|
},
|
||||||
computed: {},
|
computed: {},
|
||||||
watch: {},
|
watch: {},
|
||||||
destroyed() {
|
unmounted() {
|
||||||
this.$emit('onDestroy');
|
this.$emit('onDestroy')
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleChange(v) {
|
handleChange(v) {
|
||||||
this.$emit('change', v);
|
this.$emit('change', v)
|
||||||
},
|
},
|
||||||
handleBlur(v) {
|
handleBlur(v) {
|
||||||
this.$emit('blur', v);
|
this.$emit('blur', v)
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.editor-v2 {
|
.editor-v2 {
|
||||||
|
@ -4,33 +4,33 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { filterXSS } from '@/common/xss';
|
import { filterXSS } from '@/common/xss'
|
||||||
export default {
|
export default {
|
||||||
name: 'ReadOnly',
|
name: 'ReadOnly',
|
||||||
props: {
|
props: {
|
||||||
realData: {
|
realData: {
|
||||||
type: String,
|
type: String,
|
||||||
default: () => '',
|
default: () => ''
|
||||||
},
|
},
|
||||||
viewData: {
|
viewData: {
|
||||||
type: String,
|
type: String,
|
||||||
default: () => '',
|
default: () => ''
|
||||||
},
|
},
|
||||||
tag: {
|
tag: {
|
||||||
tyle: String,
|
tyle: String,
|
||||||
default: () => '',
|
default: () => ''
|
||||||
},
|
},
|
||||||
border: {
|
border: {
|
||||||
tyle: Boolean,
|
tyle: Boolean,
|
||||||
default: () => false,
|
default: () => false
|
||||||
},
|
},
|
||||||
defaultStyle: {
|
defaultStyle: {
|
||||||
tyle: Boolean,
|
tyle: Boolean,
|
||||||
default: () => false,
|
default: () => false
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {};
|
return {}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
tagHtml() {
|
tagHtml() {
|
||||||
@ -47,38 +47,38 @@ export default {
|
|||||||
border-radius: 0 0.06rem;
|
border-radius: 0 0.06rem;
|
||||||
background: rgba(250,136,26,0.1);
|
background: rgba(250,136,26,0.1);
|
||||||
">${this.tag}</span>`
|
">${this.tag}</span>`
|
||||||
: '';
|
: ''
|
||||||
},
|
},
|
||||||
getHtml() {
|
getHtml() {
|
||||||
const title = filterXSS(this.viewData);
|
const title = filterXSS(this.viewData)
|
||||||
if (!this.tag) return title;
|
if (!this.tag) return title
|
||||||
let html = this.isRichText(title) ? title : `<p>${title}</p>`;
|
let html = this.isRichText(title) ? title : `<p>${title}</p>`
|
||||||
const index = html.lastIndexOf('</p>');
|
const index = html.lastIndexOf('</p>')
|
||||||
if (this.viewData.indexOf(this.tagHtml) < 0) {
|
if (this.viewData.indexOf(this.tagHtml) < 0) {
|
||||||
html = html.slice(0, index) + this.tagHtml + html.slice(index);
|
html = html.slice(0, index) + this.tagHtml + html.slice(index)
|
||||||
}
|
}
|
||||||
return html;
|
return html
|
||||||
},
|
},
|
||||||
getStyle() {
|
getStyle() {
|
||||||
let style = '';
|
let style = ''
|
||||||
if (this.border) {
|
if (this.border) {
|
||||||
style += 'border:1px solid #c8c9cd;padding:10px;';
|
style += 'border:1px solid #c8c9cd;padding:10px;'
|
||||||
}
|
}
|
||||||
if (this.defaultStyle) {
|
if (this.defaultStyle) {
|
||||||
style += 'color: #6e707c;font-size: 12px;';
|
style += 'color: #6e707c;font-size: 12px;'
|
||||||
}
|
}
|
||||||
return style;
|
return style
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
destroyed() {
|
unmounted() {
|
||||||
this.$emit('onDestroy');
|
this.$emit('onDestroy')
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
isRichText(str) {
|
isRichText(str) {
|
||||||
return /^<p[\s\S]*>[\s\S]*<\/p>$/.test(str);
|
return /^<p[\s\S]*>[\s\S]*<\/p>$/.test(str)
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.read-only {
|
.read-only {
|
||||||
|
@ -1,126 +1,119 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="editor-wrapper border">
|
<div class="editor-wrapper border">
|
||||||
<div class="toolbar" ref="toolbar" v-show="showToolbar"></div>
|
<Toolbar
|
||||||
<div class="editor" ref="editor"></div>
|
:class="['toolbar', props.staticToolBar ? 'static-toolbar' : 'dynamic-toolbar']"
|
||||||
|
ref="toolbar"
|
||||||
|
v-show="showToolbar"
|
||||||
|
:editor="editorRef"
|
||||||
|
:defaultConfig="toolbarConfig"
|
||||||
|
:mode="mode"
|
||||||
|
/>
|
||||||
|
<Editor
|
||||||
|
class="editor"
|
||||||
|
ref="editor"
|
||||||
|
:modelValue="curValue"
|
||||||
|
:defaultConfig="editorConfig"
|
||||||
|
@onCreated="onCreated"
|
||||||
|
@onChange="onChange"
|
||||||
|
@onBlur="onBlur"
|
||||||
|
@onFocus="onFocus"
|
||||||
|
:mode="mode"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import { createEditor, createToolbar } from '@wangeditor/editor';
|
import '@wangeditor/editor/dist/css/style.css'
|
||||||
|
import './styles/reset-wangeditor.scss'
|
||||||
|
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
|
||||||
|
import { ref, shallowRef, onBeforeMount, watch } from 'vue'
|
||||||
|
|
||||||
export default {
|
const emit = defineEmits(['input', 'onFocus', 'change', 'blur'])
|
||||||
name: 'richEditor',
|
const model = defineModel()
|
||||||
data() {
|
const props = defineProps(['staticToolBar'])
|
||||||
return {
|
|
||||||
curValue: '',
|
const curValue = ref('')
|
||||||
editor: null,
|
const editorRef = shallowRef()
|
||||||
showToolbar: false,
|
const showToolbar = ref(props.staticToolBar || false)
|
||||||
};
|
|
||||||
},
|
const mode = 'simple'
|
||||||
props: ['value'], // value 用于自定义 v-model
|
|
||||||
mounted() {
|
const toolbarConfig = {
|
||||||
this.create();
|
toolbarKeys: [
|
||||||
},
|
'color', // 字体色
|
||||||
watch: {
|
'bgColor', // 背景色
|
||||||
value: {
|
'bold',
|
||||||
immediate: true,
|
'insertLink' // 链接
|
||||||
handler(newVal) {
|
]
|
||||||
const isEqual = newVal === this.curValue;
|
}
|
||||||
if (isEqual) return; // 和当前内容一样,则忽略
|
|
||||||
|
const editorConfig = {}
|
||||||
|
|
||||||
|
const setHtml = (newHtml) => {
|
||||||
|
const editor = editorRef.value
|
||||||
|
if (editor == null) return
|
||||||
|
editor.setHtml(newHtml)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCreated = (editor) => {
|
||||||
|
editorRef.value = editor
|
||||||
|
if (model.value) {
|
||||||
|
setHtml(model.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const onChange = (editor) => {
|
||||||
|
const editorHtml = editor.getHtml()
|
||||||
|
curValue.value = editorHtml // 记录当前 html 内容
|
||||||
|
emit('input', editorHtml) // 用于自定义 v-model
|
||||||
|
}
|
||||||
|
const onFocus = (editor) => {
|
||||||
|
emit('onFocus', editor)
|
||||||
|
setToolbarStatus(true)
|
||||||
|
}
|
||||||
|
const onBlur = (editor) => {
|
||||||
|
const editorHtml = editor.getHtml()
|
||||||
|
curValue.value = editorHtml // 记录当前 html 内容
|
||||||
|
emit('change', editorHtml)
|
||||||
|
emit('blur', editor)
|
||||||
|
setToolbarStatus(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const setToolbarStatus = (status) => {
|
||||||
|
if (props.staticToolBar) return
|
||||||
|
showToolbar.value = status
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => model.value,
|
||||||
|
(newVal) => {
|
||||||
|
const isEqual = newVal === curValue.value
|
||||||
|
if (isEqual) return // 和当前内容一样,则忽略
|
||||||
|
|
||||||
// 重置 HTML
|
|
||||||
this.setHtml(newVal);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
// 重置 HTML
|
// 重置 HTML
|
||||||
setHtml(newHtml) {
|
setHtml(newVal)
|
||||||
const editor = this.editor;
|
|
||||||
if (editor === null) return;
|
|
||||||
editor.setHtml(newHtml);
|
|
||||||
},
|
|
||||||
// 创建 editor
|
|
||||||
create() {
|
|
||||||
if (this.$refs.editor === null) return;
|
|
||||||
|
|
||||||
createEditor({
|
|
||||||
selector: this.$refs.editor,
|
|
||||||
html: this.defaultHtml || this.value || '',
|
|
||||||
config: {
|
|
||||||
onCreated: (editor) => {
|
|
||||||
this.editor = Object.seal(editor);
|
|
||||||
|
|
||||||
if (this.value) {
|
|
||||||
this.setHtml(this.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$refs.toolbar &&
|
|
||||||
createToolbar({
|
|
||||||
editor,
|
|
||||||
selector: this.$refs.toolbar,
|
|
||||||
config: {
|
|
||||||
toolbarKeys: [
|
|
||||||
'color', // 字体色
|
|
||||||
'bgColor', // 背景色
|
|
||||||
'bold',
|
|
||||||
// 'insertImage', // 插入图片
|
|
||||||
// 'video',
|
|
||||||
// 'fontSize', // 字号
|
|
||||||
// 'justify', // 对齐方式
|
|
||||||
'insertLink', // 链接
|
|
||||||
// 'clean',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
mode: 'simple',
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onChange: (editor) => {
|
|
||||||
const editorHtml = editor.getHtml();
|
|
||||||
this.curValue = editorHtml; // 记录当前 html 内容
|
|
||||||
this.$emit('input', editorHtml); // 用于自定义 v-model
|
|
||||||
},
|
|
||||||
onDestroyed: (editor) => {
|
|
||||||
this.$emit('onDestroyed', editor);
|
|
||||||
editor.destroy();
|
|
||||||
},
|
|
||||||
onFocus: (editor) => {
|
|
||||||
this.$emit('onFocus', editor);
|
|
||||||
this.showToolbar = true;
|
|
||||||
},
|
|
||||||
onBlur: (editor) => {
|
|
||||||
const editorHtml = editor.getHtml();
|
|
||||||
this.curValue = editorHtml; // 记录当前 html 内容
|
|
||||||
this.$emit('change', editorHtml);
|
|
||||||
this.$emit('blur', editor);
|
|
||||||
this.showToolbar = false;
|
|
||||||
},
|
|
||||||
// customPaste: (editor, event) => {
|
|
||||||
// let res;
|
|
||||||
// this.$emit('customPaste', editor, event, (val) => {
|
|
||||||
// res = val;
|
|
||||||
// });
|
|
||||||
// return res;
|
|
||||||
// },
|
|
||||||
},
|
|
||||||
content: [],
|
|
||||||
mode: 'simple',
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
{
|
||||||
|
// immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
const editor = editorRef.value
|
||||||
|
if (editor == null) return
|
||||||
|
editor.destroy()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import url('@wangeditor/editor/dist/css/style.css');
|
|
||||||
@import url('@/management/styles/reset-wangeditor.scss');
|
|
||||||
|
|
||||||
.editor-wrapper {
|
.editor-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
// min-height: 45px;
|
// min-height: 45px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar {
|
.static-toolbar {
|
||||||
|
border-bottom: 1px solid #dedede;
|
||||||
|
}
|
||||||
|
.dynamic-toolbar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: -44px;
|
top: -44px;
|
||||||
|
19
web/src/common/Editor/styles/reset-wangeditor.scss
Normal file
19
web/src/common/Editor/styles/reset-wangeditor.scss
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
:root {
|
||||||
|
--w-e-textarea-bg-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-e-text-container * {
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-e-text-container [data-slate-editor] h1,
|
||||||
|
.w-e-text-container [data-slate-editor] h2,
|
||||||
|
.w-e-text-container [data-slate-editor] h3,
|
||||||
|
.w-e-text-container [data-slate-editor] h4,
|
||||||
|
.w-e-text-container [data-slate-editor] h5 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-e-text-container [data-slate-editor] p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
@ -1,62 +1,62 @@
|
|||||||
import xss from 'xss';
|
import xss from 'xss'
|
||||||
|
|
||||||
const myxss = new xss.FilterXSS({
|
const myxss = new xss.FilterXSS({
|
||||||
onIgnoreTagAttr(tag, name, value) {
|
onIgnoreTagAttr(tag, name, value) {
|
||||||
if (name === 'style' || name === 'class') {
|
if (name === 'style' || name === 'class') {
|
||||||
return `${name}="${value}"`;
|
return `${name}="${value}"`
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined
|
||||||
},
|
},
|
||||||
onIgnoreTag(tag, html) {
|
onIgnoreTag(tag, html) {
|
||||||
// <xxx>过滤为空,否则不过滤为空
|
// <xxx>过滤为空,否则不过滤为空
|
||||||
var re1 = new RegExp('<.+?>', 'g');
|
var re1 = new RegExp('<.+?>', 'g')
|
||||||
if (re1.test(html)) {
|
if (re1.test(html)) {
|
||||||
return '';
|
return ''
|
||||||
} else {
|
} else {
|
||||||
return html;
|
return html
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
const isImg = (html) => {
|
const isImg = (html) => {
|
||||||
html = html + '';
|
html = html + ''
|
||||||
return html.indexOf('<img') > -1;
|
return html.indexOf('<img') > -1
|
||||||
};
|
}
|
||||||
const isVideo = (html) => {
|
const isVideo = (html) => {
|
||||||
html = html + '';
|
html = html + ''
|
||||||
return html.indexOf('<video') > -1;
|
return html.indexOf('<video') > -1
|
||||||
};
|
}
|
||||||
|
|
||||||
export const cleanRichText = (text) => {
|
export const cleanRichText = (text) => {
|
||||||
if (!text) {
|
if (!text) {
|
||||||
return text === 0 ? 0 : '';
|
return text === 0 ? 0 : ''
|
||||||
}
|
}
|
||||||
const html = transformHtmlTag(text);
|
const html = transformHtmlTag(text)
|
||||||
const content = html.replace(/<[^<>]+>/g, '').replace(/ /g, '');
|
const content = html.replace(/<[^<>]+>/g, '').replace(/ /g, '')
|
||||||
if (content) return content;
|
if (content) return content
|
||||||
|
|
||||||
if (isImg(html)) return '图片';
|
if (isImg(html)) return '图片'
|
||||||
if (isVideo(html)) return '视频';
|
if (isVideo(html)) return '视频'
|
||||||
return '文本';
|
return '文本'
|
||||||
};
|
}
|
||||||
export function escapeHtml(html) {
|
export function escapeHtml(html) {
|
||||||
return html.replace(/</g, '<').replace(/>/g, '>');
|
return html.replace(/</g, '<').replace(/>/g, '>')
|
||||||
}
|
}
|
||||||
export const transformHtmlTag = (html) => {
|
export const transformHtmlTag = (html) => {
|
||||||
if (!html) return '';
|
if (!html) return ''
|
||||||
if (typeof html !== 'string') return html + '';
|
if (typeof html !== 'string') return html + ''
|
||||||
return html
|
return html
|
||||||
.replace(html ? /&(?!#?\w+;)/g : /&/g, '&')
|
.replace(html ? /&(?!#?\w+;)/g : /&/g, '&')
|
||||||
.replace(/</g, '<')
|
.replace(/</g, '<')
|
||||||
.replace(/>/g, '>')
|
.replace(/>/g, '>')
|
||||||
.replace(/"/g, '"')
|
.replace(/"/g, '"')
|
||||||
.replace(/'/g, "'")
|
.replace(/'/g, "'")
|
||||||
.replace(/\\\n/g, '\\n');
|
.replace(/\\\n/g, '\\n')
|
||||||
//.replace(/ /g, "")
|
//.replace(/ /g, "")
|
||||||
};
|
}
|
||||||
|
|
||||||
const filterXSSClone = myxss.process.bind(myxss);
|
const filterXSSClone = myxss.process.bind(myxss)
|
||||||
|
|
||||||
export const filterXSS = (html) => filterXSSClone(transformHtmlTag(html));
|
export const filterXSS = (html) => filterXSSClone(transformHtmlTag(html))
|
||||||
|
|
||||||
export const escapeFilterXSS = (html) => escapeHtml(filterXSS(html));
|
export const escapeFilterXSS = (html) => escapeHtml(filterXSS(html))
|
||||||
|
@ -3,11 +3,13 @@
|
|||||||
<router-view></router-view>
|
<router-view></router-view>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'App',
|
name: 'App'
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import url('./styles/icon.scss');
|
@import url('./styles/icon.scss');
|
||||||
@import url('../materials/questions/common/css/icon.scss');
|
@import url('../materials/questions/common/css/icon.scss');
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import axios from './base';
|
import axios from './base'
|
||||||
|
|
||||||
export const getRecycleList = (data) => {
|
export const getRecycleList = (data) => {
|
||||||
return axios.get('/survey/dataStatistic/dataTable', {
|
return axios.get('/survey/dataStatistic/dataTable', {
|
||||||
params: {
|
params: {
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
...data,
|
...data
|
||||||
},
|
}
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import axios from './base';
|
import axios from './base'
|
||||||
|
|
||||||
export const register = (data) => {
|
export const register = (data) => {
|
||||||
return axios.post('/auth/register', data);
|
return axios.post('/auth/register', data)
|
||||||
};
|
}
|
||||||
|
|
||||||
export const login = (data) => {
|
export const login = (data) => {
|
||||||
return axios.post('/auth/login', data);
|
return axios.post('/auth/login', data)
|
||||||
};
|
}
|
||||||
|
@ -1,49 +1,50 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios'
|
||||||
import store from '@/management/store/index';
|
import store from '@/management/store/index'
|
||||||
import router from '@/management/router/index';
|
import router from '@/management/router/index'
|
||||||
import { get as _get } from 'lodash-es';
|
import { get as _get } from 'lodash-es'
|
||||||
|
|
||||||
const instance = axios.create({
|
|
||||||
baseURL: '/api',
|
|
||||||
timeout: 10000,
|
|
||||||
});
|
|
||||||
|
|
||||||
instance.interceptors.response.use(
|
|
||||||
(response) => {
|
|
||||||
if (response.status !== 200) {
|
|
||||||
throw new Error('http请求出错');
|
|
||||||
}
|
|
||||||
const res = response.data;
|
|
||||||
if (res.code === 403) {
|
|
||||||
router.replace({
|
|
||||||
name: 'login',
|
|
||||||
});
|
|
||||||
return res;
|
|
||||||
} else {
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
throw new Error(err);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
instance.interceptors.request.use((config) => {
|
|
||||||
const hasLogined = _get(store, 'state.user.hasLogined');
|
|
||||||
const token = _get(store, 'state.user.userInfo.token');
|
|
||||||
if (hasLogined && token) {
|
|
||||||
if (!config.headers) {
|
|
||||||
config.headers = {};
|
|
||||||
}
|
|
||||||
config.headers.Authorization = `Bearer ${token}`;
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
});
|
|
||||||
|
|
||||||
export default instance;
|
|
||||||
|
|
||||||
export const CODE_MAP = {
|
export const CODE_MAP = {
|
||||||
SUCCESS: 200,
|
SUCCESS: 200,
|
||||||
ERROR: 500,
|
ERROR: 500,
|
||||||
NOTAUTH: 403,
|
NO_AUTH: 403,
|
||||||
};
|
ERR_AUTH: 1001
|
||||||
|
}
|
||||||
|
|
||||||
|
const instance = axios.create({
|
||||||
|
baseURL: '/api',
|
||||||
|
timeout: 10000
|
||||||
|
})
|
||||||
|
|
||||||
|
instance.interceptors.response.use(
|
||||||
|
(response) => {
|
||||||
|
if (response.status !== 200) {
|
||||||
|
throw new Error('http请求出错')
|
||||||
|
}
|
||||||
|
const res = response.data
|
||||||
|
if (res.code === CODE_MAP.NO_AUTH || res.code === CODE_MAP.ERR_AUTH) {
|
||||||
|
router.replace({
|
||||||
|
name: 'login'
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
} else {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
throw new Error(err)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
instance.interceptors.request.use((config) => {
|
||||||
|
const hasLogined = _get(store, 'state.user.hasLogined')
|
||||||
|
const token = _get(store, 'state.user.userInfo.token')
|
||||||
|
if (hasLogined && token) {
|
||||||
|
if (!config.headers) {
|
||||||
|
config.headers = {}
|
||||||
|
}
|
||||||
|
config.headers.Authorization = `Bearer ${token}`
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
})
|
||||||
|
|
||||||
|
export default instance
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import axios from './base';
|
import axios from './base'
|
||||||
|
|
||||||
export const refreshCaptcha = ({ captchaId }) => {
|
export const refreshCaptcha = ({ captchaId }) => {
|
||||||
return axios.post('/auth/captcha', { captchaId });
|
return axios.post('/auth/captcha', { captchaId })
|
||||||
};
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import axios from './base';
|
import axios from './base'
|
||||||
|
|
||||||
export const getBannerData = () => {
|
export const getBannerData = () => {
|
||||||
return axios.get('/survey/getBannerData');
|
return axios.get('/survey/getBannerData')
|
||||||
};
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import axios from './base';
|
import axios from './base'
|
||||||
|
|
||||||
export const getSurveyList = ({ curPage, filter, order }) => {
|
export const getSurveyList = ({ curPage, filter, order }) => {
|
||||||
return axios.get('/survey/getList', {
|
return axios.get('/survey/getList', {
|
||||||
@ -6,48 +6,48 @@ export const getSurveyList = ({ curPage, filter, order }) => {
|
|||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
curPage,
|
curPage,
|
||||||
filter,
|
filter,
|
||||||
order,
|
order
|
||||||
},
|
}
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
export const getSurveyById = (id) => {
|
export const getSurveyById = (id) => {
|
||||||
return axios.get('/survey/getSurvey', {
|
return axios.get('/survey/getSurvey', {
|
||||||
params: {
|
params: {
|
||||||
surveyId: id,
|
surveyId: id
|
||||||
},
|
}
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
export const saveSurvey = ({ surveyId, configData }) => {
|
export const saveSurvey = ({ surveyId, configData }) => {
|
||||||
return axios.post('/survey/updateConf', { surveyId, configData });
|
return axios.post('/survey/updateConf', { surveyId, configData })
|
||||||
};
|
}
|
||||||
|
|
||||||
export const publishSurvey = ({ surveyId }) => {
|
export const publishSurvey = ({ surveyId }) => {
|
||||||
return axios.post('/survey/publishSurvey', {
|
return axios.post('/survey/publishSurvey', {
|
||||||
surveyId,
|
surveyId
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
export const createSurvey = (data) => {
|
export const createSurvey = (data) => {
|
||||||
return axios.post('/survey/createSurvey', data);
|
return axios.post('/survey/createSurvey', data)
|
||||||
};
|
}
|
||||||
|
|
||||||
export const getSurveyHistory = ({ surveyId, historyType }) => {
|
export const getSurveyHistory = ({ surveyId, historyType }) => {
|
||||||
return axios.get('/surveyHisotry/getList', {
|
return axios.get('/surveyHisotry/getList', {
|
||||||
params: {
|
params: {
|
||||||
surveyId,
|
surveyId,
|
||||||
historyType,
|
historyType
|
||||||
},
|
}
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
export const deleteSurvey = (surveyId) => {
|
export const deleteSurvey = (surveyId) => {
|
||||||
return axios.post('/survey/deleteSurvey', {
|
return axios.post('/survey/deleteSurvey', {
|
||||||
surveyId,
|
surveyId
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
export const updateSurvey = (data) => {
|
export const updateSurvey = (data) => {
|
||||||
return axios.post('/survey/updateMeta', data);
|
return axios.post('/survey/updateMeta', data)
|
||||||
};
|
}
|
||||||
|
@ -2,23 +2,23 @@
|
|||||||
<div class="default-empty-root">
|
<div class="default-empty-root">
|
||||||
<img class="img" :src="data.img" />
|
<img class="img" :src="data.img" />
|
||||||
<div class="title">{{ data.title }}</div>
|
<div class="title">{{ data.title }}</div>
|
||||||
<div class="desc" v-html="data.desc" />
|
<div class="desc" v-html="data.desc"></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'Empty',
|
name: 'EmptyModule',
|
||||||
props: {
|
props: {
|
||||||
data: {
|
data: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.default-empty-root {
|
.default-empty-root {
|
||||||
width: 350px;
|
width: 350px;
|
||||||
margin: 200px auto;
|
margin: 200px auto;
|
@ -1,28 +1,22 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="nav">
|
<div class="nav">
|
||||||
<logo></logo>
|
<LogoIcon />
|
||||||
<router-link
|
<RouterLink v-for="(tab, index) in tabList" :key="index" class="tab-btn" :to="tab.to" replace>
|
||||||
v-for="(tab, index) in tabList"
|
|
||||||
:key="index"
|
|
||||||
class="tab-btn"
|
|
||||||
:to="tab.to"
|
|
||||||
replace
|
|
||||||
>
|
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
<i class="iconfont" :class="tab.icon"></i>
|
<i class="iconfont" :class="tab.icon"></i>
|
||||||
</div>
|
</div>
|
||||||
<p>{{ tab.text }}</p>
|
<p>{{ tab.text }}</p>
|
||||||
</router-link>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import logo from './logo.vue';
|
import LogoIcon from './LogoIcon.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'leftMenu',
|
name: 'LeftMenu',
|
||||||
components: {
|
components: {
|
||||||
logo,
|
LogoIcon
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -31,27 +25,27 @@ export default {
|
|||||||
text: '编辑问卷',
|
text: '编辑问卷',
|
||||||
icon: 'icon-bianji',
|
icon: 'icon-bianji',
|
||||||
to: {
|
to: {
|
||||||
name: 'QuestionEditIndex',
|
name: 'QuestionEditIndex'
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: '投放问卷',
|
text: '投放问卷',
|
||||||
icon: 'icon-toufang',
|
icon: 'icon-toufang',
|
||||||
to: {
|
to: {
|
||||||
name: 'publishResultPage',
|
name: 'publishResultPage'
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: '数据统计',
|
text: '数据统计',
|
||||||
icon: 'icon-shujutongji',
|
icon: 'icon-shujutongji',
|
||||||
to: {
|
to: {
|
||||||
name: 'analysisPage',
|
name: 'analysisPage'
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
};
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
@ -3,18 +3,20 @@
|
|||||||
<img src="/imgs/s-logo.webp" />
|
<img src="/imgs/s-logo.webp" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'logoIcon',
|
name: 'LogoIcon',
|
||||||
methods: {
|
methods: {
|
||||||
toHomePage() {
|
toHomePage() {
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
name: 'survey',
|
name: 'survey'
|
||||||
});
|
})
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.navbar-main-logo {
|
.navbar-main-logo {
|
||||||
width: 80px;
|
width: 80px;
|
@ -41,14 +41,14 @@ export const defaultQuestionConfig = {
|
|||||||
text: '选项1',
|
text: '选项1',
|
||||||
others: false,
|
others: false,
|
||||||
othersKey: '',
|
othersKey: '',
|
||||||
placeholderDesc: '',
|
placeholderDesc: ''
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: '选项2',
|
text: '选项2',
|
||||||
others: false,
|
others: false,
|
||||||
othersKey: '',
|
othersKey: '',
|
||||||
placeholderDesc: '',
|
placeholderDesc: ''
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
star: 5,
|
star: 5,
|
||||||
optionOrigin: '',
|
optionOrigin: '',
|
||||||
@ -57,21 +57,21 @@ export const defaultQuestionConfig = {
|
|||||||
numberRange: {
|
numberRange: {
|
||||||
min: {
|
min: {
|
||||||
placeholder: '0',
|
placeholder: '0',
|
||||||
value: 0,
|
value: 0
|
||||||
},
|
},
|
||||||
max: {
|
max: {
|
||||||
placeholder: '1000',
|
placeholder: '1000',
|
||||||
value: 1000,
|
value: 1000
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
textRange: {
|
textRange: {
|
||||||
min: {
|
min: {
|
||||||
placeholder: '0',
|
placeholder: '0',
|
||||||
value: 0,
|
value: 0
|
||||||
},
|
},
|
||||||
max: {
|
max: {
|
||||||
placeholder: '500',
|
placeholder: '500',
|
||||||
value: 500,
|
value: 500
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
|
@ -4,84 +4,75 @@ const menuItems = {
|
|||||||
snapshot: '/imgs/question-type-snapshot/iL84te6xxU1657702189333.webp',
|
snapshot: '/imgs/question-type-snapshot/iL84te6xxU1657702189333.webp',
|
||||||
path: 'InputModule',
|
path: 'InputModule',
|
||||||
icon: 'tixing-danhangshuru',
|
icon: 'tixing-danhangshuru',
|
||||||
title: '单行输入框',
|
title: '单行输入框'
|
||||||
},
|
},
|
||||||
textarea: {
|
textarea: {
|
||||||
type: 'textarea',
|
type: 'textarea',
|
||||||
snapshot: '/imgs/question-type-snapshot/11iAo3ca0u1657702225416.webp',
|
snapshot: '/imgs/question-type-snapshot/11iAo3ca0u1657702225416.webp',
|
||||||
path: 'TextareaModule',
|
path: 'TextareaModule',
|
||||||
icon: 'tixing-duohangshuru',
|
icon: 'tixing-duohangshuru',
|
||||||
title: '多行输入框',
|
title: '多行输入框'
|
||||||
},
|
},
|
||||||
radio: {
|
radio: {
|
||||||
type: 'radio',
|
type: 'radio',
|
||||||
snapshot: '/imgs/question-type-snapshot/TgeRDfURJZ1657702220602.webp',
|
snapshot: '/imgs/question-type-snapshot/TgeRDfURJZ1657702220602.webp',
|
||||||
icon: 'tixing-danxuan',
|
icon: 'tixing-danxuan',
|
||||||
path: 'RadioModule',
|
path: 'RadioModule',
|
||||||
title: '单项选择',
|
title: '单项选择'
|
||||||
},
|
},
|
||||||
checkbox: {
|
checkbox: {
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
path: 'CheckboxModule',
|
path: 'CheckboxModule',
|
||||||
snapshot: '/imgs/question-type-snapshot/Md2YmzBBpV1657702223744.webp',
|
snapshot: '/imgs/question-type-snapshot/Md2YmzBBpV1657702223744.webp',
|
||||||
icon: 'tixing-duoxuan',
|
icon: 'tixing-duoxuan',
|
||||||
title: '多项选择',
|
title: '多项选择'
|
||||||
},
|
},
|
||||||
'binary-choice': {
|
'binary-choice': {
|
||||||
type: 'binary-choice',
|
type: 'binary-choice',
|
||||||
snapshot: '/imgs/question-type-snapshot/blW8U1ckzd1657702223023.webp',
|
snapshot: '/imgs/question-type-snapshot/blW8U1ckzd1657702223023.webp',
|
||||||
path: 'BinaryChoiceModule',
|
path: 'BinaryChoiceModule',
|
||||||
icon: 'tixing-panduanti',
|
icon: 'tixing-panduanti',
|
||||||
title: '判断题',
|
title: '判断题'
|
||||||
},
|
},
|
||||||
'radio-star': {
|
'radio-star': {
|
||||||
type: 'radio-star',
|
type: 'radio-star',
|
||||||
snapshot: '/imgs/question-type-snapshot/7CU6tn4XqT1657702221208.webp',
|
snapshot: '/imgs/question-type-snapshot/7CU6tn4XqT1657702221208.webp',
|
||||||
path: 'StarModule',
|
path: 'StarModule',
|
||||||
icon: 'tixing-pingfen',
|
icon: 'tixing-pingfen',
|
||||||
title: '评分',
|
title: '评分'
|
||||||
},
|
},
|
||||||
'radio-nps': {
|
'radio-nps': {
|
||||||
type: 'radio-nps',
|
type: 'radio-nps',
|
||||||
path: 'NpsModule',
|
path: 'NpsModule',
|
||||||
snapshot: '/imgs/question-type-snapshot/radio-nps.webp',
|
snapshot: '/imgs/question-type-snapshot/radio-nps.webp',
|
||||||
icon: 'NPSpingfen',
|
icon: 'NPSpingfen',
|
||||||
title: 'nps评分',
|
title: 'nps评分'
|
||||||
},
|
},
|
||||||
vote: {
|
vote: {
|
||||||
type: 'vote',
|
type: 'vote',
|
||||||
path: 'VoteModule',
|
path: 'VoteModule',
|
||||||
snapshot: '/imgs/question-type-snapshot/nGTscsZlwn1657702222857.webp',
|
snapshot: '/imgs/question-type-snapshot/nGTscsZlwn1657702222857.webp',
|
||||||
icon: 'tixing-toupiao',
|
icon: 'tixing-toupiao',
|
||||||
title: '投票',
|
title: '投票'
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const menuGroup = [
|
const menuGroup = [
|
||||||
{
|
{
|
||||||
title: '输入类题型',
|
title: '输入类题型',
|
||||||
questionList: ['text', 'textarea'],
|
questionList: ['text', 'textarea']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '选择类题型',
|
title: '选择类题型',
|
||||||
questionList: [
|
questionList: ['radio', 'checkbox', 'binary-choice', 'radio-star', 'radio-nps', 'vote']
|
||||||
'radio',
|
}
|
||||||
'checkbox',
|
]
|
||||||
'binary-choice',
|
|
||||||
'radio-star',
|
|
||||||
'radio-nps',
|
|
||||||
'vote',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const menu = menuGroup.map((group) => {
|
const menu = menuGroup.map((group) => {
|
||||||
group.questionList = group.questionList.map(
|
group.questionList = group.questionList.map((question) => menuItems[question])
|
||||||
(question) => menuItems[question]
|
return group
|
||||||
);
|
})
|
||||||
return group;
|
|
||||||
});
|
|
||||||
|
|
||||||
export const questionTypeList = Object.values(menuItems);
|
export const questionTypeList = Object.values(menuItems)
|
||||||
|
|
||||||
export default menu;
|
export default menu
|
||||||
|
@ -1,41 +1,35 @@
|
|||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
label: '顶部图片地址',
|
label: '顶部图片地址',
|
||||||
type: 'Input',
|
type: 'InputSetter',
|
||||||
key: 'bgImage',
|
key: 'bgImage',
|
||||||
inline: true,
|
|
||||||
direction: 'horizon',
|
|
||||||
labelStyle: { width: '120px' }
|
labelStyle: { width: '120px' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '顶部视频地址',
|
label: '顶部视频地址',
|
||||||
type: 'Input',
|
type: 'InputSetter',
|
||||||
key: 'videoLink',
|
key: 'videoLink',
|
||||||
direction: 'horizon',
|
|
||||||
labelStyle: { width: '120px' }
|
labelStyle: { width: '120px' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '视频海报地址',
|
label: '视频海报地址',
|
||||||
type: 'Input',
|
type: 'InputSetter',
|
||||||
key: 'postImg',
|
key: 'postImg',
|
||||||
direction: 'horizon',
|
|
||||||
labelStyle: { width: '120px' }
|
labelStyle: { width: '120px' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '图片支持点击',
|
label: '图片支持点击',
|
||||||
type: 'CustomedSwitch',
|
type: 'CustomedSwitch',
|
||||||
direction: 'horizon',
|
|
||||||
labelStyle: { width: '120px' },
|
labelStyle: { width: '120px' },
|
||||||
key: 'bgImageAllowJump',
|
key: 'bgImageAllowJump'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '跳转链接',
|
label: '跳转链接',
|
||||||
type: 'Input',
|
type: 'InputSetter',
|
||||||
direction: 'horizon',
|
|
||||||
labelStyle: { width: '120px' },
|
labelStyle: { width: '120px' },
|
||||||
key: 'bgImageJumpLink',
|
key: 'bgImageJumpLink',
|
||||||
relyFunc: (data) => {
|
relyFunc: (data) => {
|
||||||
return !!data?.bgImageAllowJump;
|
return !!data?.bgImageAllowJump
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
];
|
]
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
label: '自定义Logo',
|
label: '自定义Logo',
|
||||||
type: 'Input',
|
type: 'InputSetter',
|
||||||
key: 'logoImage',
|
key: 'logoImage',
|
||||||
tip: '默认尺寸200px*50px',
|
tip: '默认尺寸200px*50px',
|
||||||
direction: 'horizon',
|
|
||||||
labelStyle: { width: '120px' }
|
labelStyle: { width: '120px' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -12,7 +11,6 @@ export default [
|
|||||||
type: 'InputPercent',
|
type: 'InputPercent',
|
||||||
key: 'logoImageWidth',
|
key: 'logoImageWidth',
|
||||||
tip: '填写宽度百分比,例如30%',
|
tip: '填写宽度百分比,例如30%',
|
||||||
direction: 'horizon',
|
|
||||||
labelStyle: { width: '120px' }
|
labelStyle: { width: '120px' }
|
||||||
},
|
}
|
||||||
];
|
]
|
||||||
|
@ -1,46 +1,48 @@
|
|||||||
import bannerConfig from "./bannerConfig"
|
import bannerConfig from './bannerConfig'
|
||||||
import logoConfig from "./logoConfig"
|
import logoConfig from './logoConfig'
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
name: '头图',
|
name: '头图',
|
||||||
key: 'bannerConf.bannerConfig',
|
key: 'bannerConf.bannerConfig',
|
||||||
formConfigList: bannerConfig
|
formConfigList: bannerConfig
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '背景',
|
name: '背景',
|
||||||
key: 'skinConf.backgroundConf',
|
key: 'skinConf.backgroundConf',
|
||||||
formConfigList: [{
|
formConfigList: [
|
||||||
direction: 'space_between',
|
{
|
||||||
label: '背景颜色',
|
label: '背景颜色',
|
||||||
type: 'ColorPicker',
|
type: 'ColorPicker',
|
||||||
key: 'color',
|
key: 'color'
|
||||||
}],
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '主题色',
|
name: '主题色',
|
||||||
key: 'skinConf.themeConf',
|
key: 'skinConf.themeConf',
|
||||||
formConfigList: [{
|
formConfigList: [
|
||||||
direction: 'space_between',
|
{
|
||||||
direction: 'space_between',
|
label: '全局应用',
|
||||||
label: '全局应用',
|
type: 'ColorPicker',
|
||||||
type: 'ColorPicker',
|
key: 'color'
|
||||||
key: 'color',
|
}
|
||||||
}],
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'skinConf.contentConf',
|
key: 'skinConf.contentConf',
|
||||||
name: '内容区域',
|
name: '内容区域',
|
||||||
formConfigList: [{
|
formConfigList: [
|
||||||
direction: 'space_between',
|
{
|
||||||
label: '内容透明度',
|
label: '内容透明度',
|
||||||
type: 'SliderSetter',
|
type: 'SliderSetter',
|
||||||
key: 'opacity',
|
key: 'opacity'
|
||||||
}],
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '品牌logo',
|
name: '品牌logo',
|
||||||
key: 'bottomConf',
|
key: 'bottomConf',
|
||||||
formConfigList: logoConfig
|
formConfigList: logoConfig
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -1,71 +1,62 @@
|
|||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
label: '提交按钮文案',
|
title: '提交按钮文案',
|
||||||
type: 'Input',
|
type: 'InputSetter',
|
||||||
key: 'submitTitle',
|
key: 'submitTitle',
|
||||||
placeholder: '提交',
|
placeholder: '提交',
|
||||||
value: '',
|
value: ''
|
||||||
labelStyle: {
|
|
||||||
fontWeight: 'bold',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '提交确认弹窗',
|
title: '提交确认弹窗',
|
||||||
type: 'Customed',
|
type: 'Customed',
|
||||||
key: 'confirmAgain',
|
key: 'confirmAgain',
|
||||||
labelStyle: {
|
|
||||||
fontWeight: 'bold',
|
|
||||||
},
|
|
||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
label: '是否配置该项',
|
label: '是否配置该项',
|
||||||
|
labelStyle: { width: '120px' },
|
||||||
type: 'CustomedSwitch',
|
type: 'CustomedSwitch',
|
||||||
key: 'confirmAgain.is_again',
|
key: 'confirmAgain.is_again',
|
||||||
direction: 'horizon',
|
value: true
|
||||||
value: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '二次确认文案',
|
label: '二次确认文案',
|
||||||
type: 'Input',
|
labelStyle: { width: '120px' },
|
||||||
|
type: 'InputSetter',
|
||||||
key: 'confirmAgain.again_text',
|
key: 'confirmAgain.again_text',
|
||||||
direction: 'horizon',
|
|
||||||
placeholder: '确认要提交吗?',
|
placeholder: '确认要提交吗?',
|
||||||
value: '确认要提交吗?',
|
value: '确认要提交吗?'
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '提交文案配置',
|
title: '提交文案配置',
|
||||||
type: 'Customed',
|
type: 'Customed',
|
||||||
key: 'msgContent',
|
key: 'msgContent',
|
||||||
labelStyle: {
|
|
||||||
fontWeight: 'bold',
|
|
||||||
},
|
|
||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
label: '已提交',
|
label: '已提交',
|
||||||
type: 'Input',
|
labelStyle: { width: '120px' },
|
||||||
|
type: 'InputSetter',
|
||||||
key: 'msgContent.msg_9002',
|
key: 'msgContent.msg_9002',
|
||||||
placeholder: '请勿多次提交!',
|
placeholder: '请勿多次提交!',
|
||||||
value: '请勿多次提交!',
|
value: '请勿多次提交!'
|
||||||
direction: 'horizon',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '提交结束',
|
label: '提交结束',
|
||||||
type: 'Input',
|
labelStyle: { width: '120px' },
|
||||||
|
type: 'InputSetter',
|
||||||
key: 'msgContent.msg_9003',
|
key: 'msgContent.msg_9003',
|
||||||
placeholder: '您来晚了,已经满额!',
|
placeholder: '您来晚了,已经满额!',
|
||||||
value: '您来晚了,已经满额!',
|
value: '您来晚了,已经满额!'
|
||||||
direction: 'horizon',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '其他提交失败',
|
label: '其他提交失败',
|
||||||
type: 'Input',
|
labelStyle: { width: '120px' },
|
||||||
|
type: 'InputSetter',
|
||||||
key: 'msgContent.msg_9004',
|
key: 'msgContent.msg_9004',
|
||||||
placeholder: '提交失败!',
|
placeholder: '提交失败!',
|
||||||
value: '提交失败!',
|
value: '提交失败!'
|
||||||
direction: 'horizon',
|
}
|
||||||
},
|
]
|
||||||
],
|
}
|
||||||
},
|
]
|
||||||
];
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
'default-1': {
|
'default-1': {
|
||||||
'skinConf.backgroundConf.color': '#90b4fa',
|
'skinConf.backgroundConf.color': '#90b4fa',
|
||||||
'skinConf.themeConf.color': '#FAA600',
|
'skinConf.themeConf.color': '#FAA600'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
16
web/src/management/directive/plainText.ts
Normal file
16
web/src/management/directive/plainText.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { cleanRichText } from '@/common/xss'
|
||||||
|
import type { DirectiveBinding, Directive, Plugin } from 'vue'
|
||||||
|
|
||||||
|
function _plainText(el: HTMLElement, binding: DirectiveBinding) {
|
||||||
|
const text = cleanRichText(binding.value)
|
||||||
|
el.innerText = `${text}`
|
||||||
|
}
|
||||||
|
const plainText: Directive & Plugin = {
|
||||||
|
mounted: _plainText,
|
||||||
|
updated: _plainText,
|
||||||
|
install: function (app) {
|
||||||
|
app.directive('plain-text', this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default plainText
|
17
web/src/management/directive/safeHtml.ts
Normal file
17
web/src/management/directive/safeHtml.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { filterXSS } from '@/common/xss'
|
||||||
|
import type { Directive, Plugin, DirectiveBinding } from 'vue'
|
||||||
|
|
||||||
|
function _safeHtml(el: HTMLElement, binding: DirectiveBinding) {
|
||||||
|
const res = filterXSS(binding.value)
|
||||||
|
el.innerHTML = res
|
||||||
|
}
|
||||||
|
|
||||||
|
const safeHtml: Directive & Plugin = {
|
||||||
|
mounted: _safeHtml,
|
||||||
|
updated: _safeHtml,
|
||||||
|
install: function (app) {
|
||||||
|
app.directive('safe-html', this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default safeHtml
|
12
web/src/management/index.html
Normal file
12
web/src/management/index.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||||
|
<link rel="icon" href="/imgs/favicon.ico" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -1,36 +1,17 @@
|
|||||||
import Vue from 'vue';
|
import { createApp } from 'vue'
|
||||||
import App from './App.vue';
|
import store from './store'
|
||||||
import router from './router';
|
import plainText from './directive/plainText'
|
||||||
import store from './store';
|
import safeHtml from './directive/safeHtml'
|
||||||
import ElementUI from 'element-ui';
|
|
||||||
import './styles/element-variables.scss';
|
|
||||||
import { filterXSS, cleanRichText } from '@/common/xss';
|
|
||||||
|
|
||||||
Vue.config.productionTip = false;
|
import App from './App.vue'
|
||||||
Vue.use(ElementUI);
|
import router from './router'
|
||||||
|
|
||||||
const safeHtml = function (el, binding) {
|
const app = createApp(App)
|
||||||
const res = filterXSS(binding.value);
|
|
||||||
el.innerHTML = res;
|
|
||||||
};
|
|
||||||
|
|
||||||
const plainText = function (el, binding) {
|
app.use(store)
|
||||||
const text = cleanRichText(binding.value);
|
app.use(router)
|
||||||
el.innerText = text;
|
|
||||||
};
|
|
||||||
|
|
||||||
Vue.directive('safe-html', {
|
app.use(plainText)
|
||||||
inserted: safeHtml,
|
app.use(safeHtml)
|
||||||
componentUpdated: safeHtml,
|
|
||||||
});
|
|
||||||
|
|
||||||
Vue.directive('plain-text', {
|
app.mount('#app')
|
||||||
inserted: plainText,
|
|
||||||
componentUpdated: plainText,
|
|
||||||
});
|
|
||||||
|
|
||||||
new Vue({
|
|
||||||
router,
|
|
||||||
store,
|
|
||||||
render: (h) => h(App),
|
|
||||||
}).$mount('#app');
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<h2 class="data-list">数据列表</h2>
|
<h2 class="data-list">数据列表</h2>
|
||||||
<div class="menus">
|
<div class="menus">
|
||||||
<el-switch
|
<el-switch
|
||||||
:value="isShowOriginData"
|
:model-value="isShowOriginData"
|
||||||
active-text="是否展示原数据"
|
active-text="是否展示原数据"
|
||||||
@input="onIsShowOriginChange"
|
@input="onIsShowOriginChange"
|
||||||
>
|
>
|
||||||
@ -15,10 +15,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="tableData.total">
|
<template v-if="tableData.total">
|
||||||
<DataTable
|
<DataTable :main-table-loading="mainTableLoading" :table-data="tableData" />
|
||||||
:main-table-loading="mainTableLoading"
|
|
||||||
:table-data="tableData"
|
|
||||||
/>
|
|
||||||
<el-pagination
|
<el-pagination
|
||||||
background
|
background
|
||||||
layout="prev, pager, next"
|
layout="prev, pager, next"
|
||||||
@ -29,110 +26,114 @@
|
|||||||
</el-pagination>
|
</el-pagination>
|
||||||
</template>
|
</template>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<empty :data="noDataConfig" />
|
<EmptyIndex :data="noDataConfig" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import DataTable from './components/table.vue';
|
import { ElMessage } from 'element-plus'
|
||||||
import empty from '@/management/components/empty';
|
import 'element-plus/theme-chalk/src/message.scss'
|
||||||
import leftMenu from '@/management/components/leftMenu.vue';
|
|
||||||
import { getRecycleList } from '@/management/api/analysis';
|
import EmptyIndex from '@/management/components/EmptyIndex.vue'
|
||||||
|
import LeftMenu from '@/management/components/LeftMenu.vue'
|
||||||
|
import { getRecycleList } from '@/management/api/analysis'
|
||||||
|
|
||||||
|
import DataTable from './components/DataTable.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'analysisPage',
|
name: 'AnalysisPage',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
mainTableLoading: false,
|
mainTableLoading: false,
|
||||||
tableData: {
|
tableData: {
|
||||||
total: 0,
|
total: 0,
|
||||||
listHead: [],
|
listHead: [],
|
||||||
listBody: [],
|
listBody: []
|
||||||
},
|
},
|
||||||
noDataConfig: {
|
noDataConfig: {
|
||||||
title: '暂无数据',
|
title: '暂无数据',
|
||||||
desc: '您的问卷当前还没有数据,快去回收问卷吧!',
|
desc: '您的问卷当前还没有数据,快去回收问卷吧!',
|
||||||
img: '/imgs/icons/analysis-empty.webp',
|
img: '/imgs/icons/analysis-empty.webp'
|
||||||
},
|
},
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
isShowOriginData: false,
|
isShowOriginData: false,
|
||||||
tmpIsShowOriginData: false,
|
tmpIsShowOriginData: false
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
computed: {},
|
computed: {},
|
||||||
created() {
|
created() {
|
||||||
this.init();
|
this.init()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async init() {
|
async init() {
|
||||||
if (!this.$route.params.id) {
|
if (!this.$route.params.id) {
|
||||||
this.$message.error('没有传入问卷参数~');
|
ElMessage.error('没有传入问卷参数~')
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
this.mainTableLoading = true;
|
this.mainTableLoading = true
|
||||||
try {
|
try {
|
||||||
const res = await getRecycleList({
|
const res = await getRecycleList({
|
||||||
page: this.currentPage,
|
page: this.currentPage,
|
||||||
surveyId: this.$route.params.id,
|
surveyId: this.$route.params.id,
|
||||||
isDesensitive: !this.tmpIsShowOriginData, // 发起请求的时候,isShowOriginData还没改变,暂存了一个字段
|
isDesensitive: !this.tmpIsShowOriginData // 发起请求的时候,isShowOriginData还没改变,暂存了一个字段
|
||||||
});
|
})
|
||||||
|
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
const listHead = this.formatHead(res.data.listHead);
|
const listHead = this.formatHead(res.data.listHead)
|
||||||
this.tableData = { ...res.data, listHead };
|
this.tableData = { ...res.data, listHead }
|
||||||
this.mainTableLoading = false;
|
this.mainTableLoading = false
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.$message.error('查询回收数据失败,请重试');
|
ElMessage.error('查询回收数据失败,请重试')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleCurrentChange(current) {
|
handleCurrentChange(current) {
|
||||||
if (this.mainTableLoading) {
|
if (this.mainTableLoading) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
this.currentPage = current;
|
this.currentPage = current
|
||||||
this.init();
|
this.init()
|
||||||
},
|
},
|
||||||
formatHead(listHead = []) {
|
formatHead(listHead = []) {
|
||||||
const head = [];
|
const head = []
|
||||||
|
|
||||||
listHead.forEach((headItem) => {
|
listHead.forEach((headItem) => {
|
||||||
head.push({
|
head.push({
|
||||||
field: headItem.field,
|
field: headItem.field,
|
||||||
title: headItem.title,
|
title: headItem.title
|
||||||
});
|
})
|
||||||
|
|
||||||
if (headItem.othersCode?.length) {
|
if (headItem.othersCode?.length) {
|
||||||
headItem.othersCode.forEach((item) => {
|
headItem.othersCode.forEach((item) => {
|
||||||
head.push({
|
head.push({
|
||||||
field: item.code,
|
field: item.code,
|
||||||
title: `${headItem.title}-${item.option}`,
|
title: `${headItem.title}-${item.option}`
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
return head;
|
return head
|
||||||
},
|
},
|
||||||
async onIsShowOriginChange(data) {
|
async onIsShowOriginChange(data) {
|
||||||
if (this.mainTableLoading) {
|
if (this.mainTableLoading) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
// console.log(data)
|
// console.log(data)
|
||||||
this.tmpIsShowOriginData = data;
|
this.tmpIsShowOriginData = data
|
||||||
await this.init();
|
await this.init()
|
||||||
this.isShowOriginData = data;
|
this.isShowOriginData = data
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
DataTable,
|
DataTable,
|
||||||
empty,
|
EmptyIndex,
|
||||||
leftMenu,
|
LeftMenu
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@ -166,7 +167,7 @@ export default {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||||
|
|
||||||
::v-deep .el-pagination {
|
:deep(.el-pagination) {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
120
web/src/management/pages/analysis/components/DataTable.vue
Normal file
120
web/src/management/pages/analysis/components/DataTable.vue
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
<template>
|
||||||
|
<div class="data-table-wrapper">
|
||||||
|
<el-table
|
||||||
|
ref="multipleTable"
|
||||||
|
:data="props.tableData.listBody"
|
||||||
|
style="width: 100%"
|
||||||
|
header-row-class-name="thead-cell"
|
||||||
|
class="table-border"
|
||||||
|
v-loading="props.mainTableLoading"
|
||||||
|
element-loading-text="数据处理中,请稍等..."
|
||||||
|
>
|
||||||
|
<el-table-column
|
||||||
|
v-for="item in props.tableData.listHead"
|
||||||
|
:key="item.field"
|
||||||
|
:prop="item.field"
|
||||||
|
:label="cleanRichText(item.title)"
|
||||||
|
minWidth="200"
|
||||||
|
>
|
||||||
|
<template #header="scope">
|
||||||
|
<div class="table-row-cell">
|
||||||
|
<span
|
||||||
|
@mouseover="onPopoverRefOver(scope, 'head')"
|
||||||
|
:ref="(el) => (popoverRefMap[scope.column.id] = el)"
|
||||||
|
>
|
||||||
|
{{ scope.column.label.replace(/ /g, '') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #default="scope">
|
||||||
|
<div>
|
||||||
|
<span
|
||||||
|
class="table-row-cell"
|
||||||
|
@mouseover="onPopoverRefOver(scope, 'content')"
|
||||||
|
:ref="(el) => (popoverRefMap[scope.$index + scope.column.property] = el)"
|
||||||
|
>
|
||||||
|
{{ getContent(scope.row[scope.column.property]) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<el-popover
|
||||||
|
ref="popover"
|
||||||
|
popper-style="text-align: center;"
|
||||||
|
:virtual-ref="popoverVirtualRef"
|
||||||
|
placement="top"
|
||||||
|
trigger="hover"
|
||||||
|
virtual-triggering
|
||||||
|
:content="popoverContent"
|
||||||
|
>
|
||||||
|
</el-popover>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { cleanRichText } from '@/common/xss'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
tableData: {
|
||||||
|
type: Object
|
||||||
|
},
|
||||||
|
mainTableLoading: {
|
||||||
|
type: Boolean
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const popoverRefMap = ref({})
|
||||||
|
const popoverVirtualRef = ref()
|
||||||
|
const popoverContent = ref('')
|
||||||
|
|
||||||
|
const getContent = (value) => {
|
||||||
|
const content = cleanRichText(value)
|
||||||
|
return content === 0 ? 0 : content || '未知'
|
||||||
|
}
|
||||||
|
const setPopoverContent = (content) => {
|
||||||
|
popoverContent.value = content
|
||||||
|
}
|
||||||
|
const onPopoverRefOver = (scope, type) => {
|
||||||
|
let popoverContent
|
||||||
|
if (type == 'head') {
|
||||||
|
popoverVirtualRef.value = popoverRefMap.value[scope.column.id]
|
||||||
|
popoverContent = scope.column.label.replace(/ /g, '')
|
||||||
|
}
|
||||||
|
if (type == 'content') {
|
||||||
|
popoverVirtualRef.value = popoverRefMap.value[scope.$index + scope.column.property]
|
||||||
|
popoverContent = getContent(scope.row[scope.column.property])
|
||||||
|
}
|
||||||
|
setPopoverContent(popoverContent)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.data-table-wrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
min-height: 620px;
|
||||||
|
background: #fff;
|
||||||
|
padding: 10px 20px;
|
||||||
|
.table-border {
|
||||||
|
box-sizing: border-box;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
:deep(.el-table__header) {
|
||||||
|
width: 100%;
|
||||||
|
.thead-cell .el-table__cell {
|
||||||
|
.cell {
|
||||||
|
height: 24px;
|
||||||
|
color: #4a4c5b;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.table-row-cell {
|
||||||
|
white-space: nowrap; /* 禁止自动换行 */
|
||||||
|
overflow: hidden; /* 超出部分隐藏 */
|
||||||
|
text-overflow: ellipsis; /* 显示省略号 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,108 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="data-table-wrapper">
|
|
||||||
<el-table
|
|
||||||
ref="multipleTable"
|
|
||||||
:data="tableData.listBody"
|
|
||||||
tooltip-effect="dark"
|
|
||||||
style="width: 100%"
|
|
||||||
header-row-class-name="thead-cell"
|
|
||||||
class="table-border"
|
|
||||||
show-overflow-tooltip
|
|
||||||
v-loading="mainTableLoading"
|
|
||||||
element-loading-text="数据处理中,请稍等..."
|
|
||||||
>
|
|
||||||
<el-table-column
|
|
||||||
v-for="item in tableData.listHead"
|
|
||||||
:key="item.field"
|
|
||||||
:prop="item.field"
|
|
||||||
:label="cleanRichText(item.title)"
|
|
||||||
minWidth="200"
|
|
||||||
>
|
|
||||||
<template slot="header" slot-scope="scope">
|
|
||||||
<div class="table-row-cell">
|
|
||||||
<span slot="reference" v-popover="scope.column.id">
|
|
||||||
{{ scope.column.label.replace(/ /g, '') }}
|
|
||||||
</span>
|
|
||||||
<el-popover
|
|
||||||
:ref="scope.column.id"
|
|
||||||
placement="top-start"
|
|
||||||
width="200"
|
|
||||||
trigger="hover"
|
|
||||||
:content="scope.column.label.replace(/ /g, '')"
|
|
||||||
>
|
|
||||||
</el-popover>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template slot-scope="scope">
|
|
||||||
<span
|
|
||||||
slot="reference"
|
|
||||||
class="table-row-cell"
|
|
||||||
v-popover="scope.$index + scope.column.property"
|
|
||||||
>
|
|
||||||
{{ getContent(scope.row[scope.column.property]) }}
|
|
||||||
</span>
|
|
||||||
<el-popover
|
|
||||||
:ref="scope.$index + scope.column.property"
|
|
||||||
placement="top-start"
|
|
||||||
trigger="hover"
|
|
||||||
width="300"
|
|
||||||
:content="getContent(scope.row[scope.column.property])"
|
|
||||||
>
|
|
||||||
</el-popover>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
import { cleanRichText } from '@/common/xss';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'DataTable',
|
|
||||||
data() {
|
|
||||||
return {};
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
mainTableLoading: Boolean,
|
|
||||||
tableData: Object,
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
cleanRichText,
|
|
||||||
getContent(value) {
|
|
||||||
const content = cleanRichText(value)
|
|
||||||
|
|
||||||
return content === 0 ? 0 : (content || '未知')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
|
||||||
.data-table-wrapper {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
min-height: 620px;
|
|
||||||
background: #fff;
|
|
||||||
padding: 10px 20px;
|
|
||||||
.table-border {
|
|
||||||
box-sizing: border-box;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
::v-deep .el-table__header {
|
|
||||||
width: 100%;
|
|
||||||
.thead-cell .el-table__cell {
|
|
||||||
.cell {
|
|
||||||
height: 24px;
|
|
||||||
color: #4a4c5b;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.table-row-cell {
|
|
||||||
white-space: nowrap; /* 禁止自动换行 */
|
|
||||||
overflow: hidden; /* 超出部分隐藏 */
|
|
||||||
text-overflow: ellipsis; /* 显示省略号 */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,35 +1,34 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="new">
|
<div class="new">
|
||||||
<type-list
|
<TypeList :selectType="selectType" @selectTypeChange="onSelectTypeChange" />
|
||||||
:selectType="selectType"
|
<CreateForm :selectType="selectType" />
|
||||||
@selectTypeChange="onSelectTypeChange"
|
|
||||||
/>
|
|
||||||
<create-form :selectType="selectType" />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import typeList from './components/typeList';
|
import TypeList from './components/TypeList.vue'
|
||||||
import createForm from './components/createForm';
|
import CreateForm from './components/CreateForm.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'createPage',
|
name: 'CreatePage',
|
||||||
components: {
|
components: {
|
||||||
typeList,
|
TypeList,
|
||||||
createForm,
|
CreateForm
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
selectType: 'normal',
|
selectType: 'normal'
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onSelectTypeChange(selectType) {
|
onSelectTypeChange(selectType) {
|
||||||
this.selectType = selectType;
|
this.selectType = selectType
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
.new {
|
.new {
|
||||||
position: relative;
|
position: relative;
|
||||||
min-height: 750px;
|
min-height: 750px;
|
@ -8,20 +8,18 @@
|
|||||||
:model="form"
|
:model="form"
|
||||||
label-width="100px"
|
label-width="100px"
|
||||||
:rules="rules"
|
:rules="rules"
|
||||||
@submit.native.prevent
|
@submit.prevent
|
||||||
>
|
>
|
||||||
<el-form-item prop="title" label="问卷名称">
|
<el-form-item prop="title" label="问卷名称">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="form.title"
|
v-model="form.title"
|
||||||
:class="form.title ? 'nonempty' : 'empty'"
|
:class="form.title ? 'nonempty' : 'empty'"
|
||||||
size="small"
|
|
||||||
placeholder="请输入问卷名称"
|
placeholder="请输入问卷名称"
|
||||||
/>
|
/>
|
||||||
<p class="form-item-tip">该标题可在打开问卷的浏览器顶部展示</p>
|
<p class="form-item-tip">该标题可在打开问卷的浏览器顶部展示</p>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item prop="remark" label="问卷备注">
|
<el-form-item prop="remark" label="问卷备注">
|
||||||
<el-input
|
<el-input
|
||||||
size="small"
|
|
||||||
v-model="form.remark"
|
v-model="form.remark"
|
||||||
:class="form.remark ? 'nonempty' : 'empty'"
|
:class="form.remark ? 'nonempty' : 'empty'"
|
||||||
placeholder="请输入备注"
|
placeholder="请输入备注"
|
||||||
@ -29,91 +27,90 @@
|
|||||||
<p class="form-item-tip">备注仅自己可见</p>
|
<p class="form-item-tip">备注仅自己可见</p>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button
|
<el-button class="create-btn" type="primary" @click="submit" :loading="!canSubmit">
|
||||||
class="create-btn"
|
|
||||||
type="primary"
|
|
||||||
size="small"
|
|
||||||
@click="submit"
|
|
||||||
:loading="!canSubmit"
|
|
||||||
>
|
|
||||||
开始创建
|
开始创建
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { SURVEY_TYPE_LIST } from '../types';
|
import { ElMessage } from 'element-plus'
|
||||||
import { createSurvey } from '@/management/api/survey';
|
import 'element-plus/theme-chalk/src/message.scss'
|
||||||
|
|
||||||
|
import { createSurvey } from '@/management/api/survey'
|
||||||
|
|
||||||
|
import { SURVEY_TYPE_LIST } from '../types'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CreateForm',
|
name: 'CreateForm',
|
||||||
props: {
|
props: {
|
||||||
selectType: {
|
selectType: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'normal',
|
default: 'normal'
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
rules: {
|
rules: {
|
||||||
title: [{ required: true, message: '请输入问卷标题', trigger: 'blur' }],
|
title: [{ required: true, message: '请输入问卷标题', trigger: 'blur' }]
|
||||||
},
|
},
|
||||||
canSubmit: true,
|
canSubmit: true,
|
||||||
form: {
|
form: {
|
||||||
title: '问卷调研',
|
title: '问卷调研',
|
||||||
remark: '问卷调研',
|
remark: '问卷调研'
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
SURVEY_TYPE_LIST() {
|
SURVEY_TYPE_LIST() {
|
||||||
return SURVEY_TYPE_LIST;
|
return SURVEY_TYPE_LIST
|
||||||
},
|
},
|
||||||
title() {
|
title() {
|
||||||
return this.SURVEY_TYPE_LIST.find((item) => item.type === this.selectType)
|
return this.SURVEY_TYPE_LIST.find((item) => item.type === this.selectType)?.title
|
||||||
?.title;
|
}
|
||||||
},
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
checkForm(fn) {
|
checkForm(fn) {
|
||||||
this.$refs.ruleForm.validate((valid) => {
|
this.$refs.ruleForm.validate((valid) => {
|
||||||
valid && typeof fn === 'function' && fn();
|
valid && typeof fn === 'function' && fn()
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
submit() {
|
submit() {
|
||||||
if (!this.canSubmit) {
|
if (!this.canSubmit) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
this.checkForm(async () => {
|
this.checkForm(async () => {
|
||||||
const { selectType } = this;
|
const { selectType } = this
|
||||||
if (!this.canSubmit) {
|
if (!this.canSubmit) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
this.canSubmit = false;
|
this.canSubmit = false
|
||||||
const res = await createSurvey({
|
const res = await createSurvey({
|
||||||
surveyType: selectType,
|
surveyType: selectType,
|
||||||
...this.form,
|
...this.form
|
||||||
});
|
})
|
||||||
if (res.code === 200 && res?.data?.id) {
|
if (res.code === 200 && res?.data?.id) {
|
||||||
const id = res.data.id;
|
const id = res.data.id
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
name: 'QuestionEditIndex',
|
name: 'QuestionEditIndex',
|
||||||
params: {
|
params: {
|
||||||
id,
|
id
|
||||||
},
|
}
|
||||||
});
|
})
|
||||||
} else {
|
} else {
|
||||||
this.$message.error(res.errmsg || '创建失败');
|
ElMessage.error(res.errmsg || '创建失败')
|
||||||
}
|
}
|
||||||
|
|
||||||
this.canSubmit = true;
|
this.canSubmit = true
|
||||||
});
|
})
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
.right-side {
|
.right-side {
|
||||||
width: 538px;
|
width: 538px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
@ -142,7 +139,7 @@ export default {
|
|||||||
border: unset;
|
border: unset;
|
||||||
color: white;
|
color: white;
|
||||||
|
|
||||||
::v-deep span {
|
:deep(span) {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -8,33 +8,35 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
boxShadow: {
|
boxShadow: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
name: 'NavHeader',
|
name: 'NavHeader',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
img: '/imgs/s-logo.webp',
|
img: '/imgs/s-logo.webp'
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
toHomePage() {
|
toHomePage() {
|
||||||
this.$router.replace({
|
this.$router.replace({
|
||||||
name: 'survey',
|
name: 'survey'
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
onBack() {
|
onBack() {
|
||||||
this.$router.go(-1);
|
this.$router.go(-1)
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
.nav-header {
|
.nav-header {
|
||||||
z-index: 99;
|
z-index: 99;
|
||||||
position: relative;
|
position: relative;
|
@ -23,36 +23,38 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
|
||||||
import NavHeader from './navHeader';
|
|
||||||
|
|
||||||
import { SURVEY_TYPE_LIST } from '../types';
|
<script>
|
||||||
|
import NavHeader from './NavHeader.vue'
|
||||||
|
|
||||||
|
import { SURVEY_TYPE_LIST } from '../types'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'LeftSide',
|
name: 'LeftSide',
|
||||||
components: {
|
components: {
|
||||||
NavHeader,
|
NavHeader
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
selectType: {
|
selectType: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: ''
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
renderData() {
|
renderData() {
|
||||||
return SURVEY_TYPE_LIST;
|
return SURVEY_TYPE_LIST
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleSelectType(key, value) {
|
handleSelectType(key, value) {
|
||||||
const { type } = value;
|
const { type } = value
|
||||||
this.$emit('selectTypeChange', type);
|
this.$emit('selectTypeChange', type)
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
.left-side {
|
.left-side {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
@ -3,7 +3,7 @@ export const SURVEY_TYPE_LIST = [
|
|||||||
type: 'normal',
|
type: 'normal',
|
||||||
title: '基础调查',
|
title: '基础调查',
|
||||||
img: '/imgs/create/normal-icon.webp',
|
img: '/imgs/create/normal-icon.webp',
|
||||||
desc: '市场调研 / 用户分析 / 产品测评 / 需求调研',
|
desc: '市场调研 / 用户分析 / 产品测评 / 需求调研'
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// type: 'nps',
|
// type: 'nps',
|
||||||
@ -15,12 +15,12 @@ export const SURVEY_TYPE_LIST = [
|
|||||||
type: 'vote',
|
type: 'vote',
|
||||||
title: '投票评选',
|
title: '投票评选',
|
||||||
img: '/imgs/create/vote-icon.webp',
|
img: '/imgs/create/vote-icon.webp',
|
||||||
desc: '才艺比赛 / 优秀员工 / 最佳人气 / 投票选举',
|
desc: '才艺比赛 / 优秀员工 / 最佳人气 / 投票选举'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'register',
|
type: 'register',
|
||||||
title: '在线报名',
|
title: '在线报名',
|
||||||
img: '/imgs/create/register-icon.webp',
|
img: '/imgs/create/register-icon.webp',
|
||||||
desc: '活动报名 / 会议报名',
|
desc: '活动报名 / 会议报名'
|
||||||
},
|
}
|
||||||
];
|
]
|
||||||
|
@ -1,29 +1,31 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<div class="nav" v-if="$slots.hasOwnProperty('nav')">
|
<div class="nav" v-if="slots.hasOwnProperty('nav')">
|
||||||
<slot name="nav"></slot>
|
<slot name="nav"></slot>
|
||||||
</div>
|
</div>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<slot v-if="$slots.hasOwnProperty('body')" name="body"></slot>
|
<slot v-if="slots.hasOwnProperty('body')" name="body"></slot>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="left" v-if="$slots.hasOwnProperty('left')">
|
<div class="left" v-if="slots.hasOwnProperty('left')">
|
||||||
<slot name="left"></slot>
|
<slot name="left"></slot>
|
||||||
</div>
|
</div>
|
||||||
<div class="center" v-if="$slots.hasOwnProperty('center')">
|
<div class="center" v-if="slots.hasOwnProperty('center')">
|
||||||
<slot name="center"></slot>
|
<slot name="center"></slot>
|
||||||
</div>
|
</div>
|
||||||
<div class="right" v-if="$slots.hasOwnProperty('right')">
|
<div class="right" v-if="slots.hasOwnProperty('right')">
|
||||||
<slot name="right"></slot>
|
<slot name="right"></slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
|
||||||
export default {
|
<script setup>
|
||||||
name: 'commonTemplate',
|
import { useSlots } from 'vue'
|
||||||
};
|
|
||||||
|
const slots = useSlots()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.main {
|
.main {
|
||||||
width: 100%;
|
width: 100%;
|
@ -14,6 +14,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'LogoPreview',
|
name: 'LogoPreview',
|
||||||
@ -22,24 +23,25 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
default: () => {}
|
default: () => {}
|
||||||
},
|
},
|
||||||
isSelected: Boolean,
|
isSelected: Boolean
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {};
|
return {}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onSelect() {
|
onSelect() {
|
||||||
this.$emit('select');
|
this.$emit('select')
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
logoImg() {
|
logoImg() {
|
||||||
const { logoImage = {} } = this.logoConf;
|
const { logoImage } = this.logoConf
|
||||||
return logoImage;
|
return logoImage
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
<style lang="scss" rel="stylesheet/scss" scoped>
|
||||||
.container {
|
.container {
|
||||||
display: flex;
|
display: flex;
|
@ -1,58 +1,56 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="title-wrapper" @click="handleClick()">
|
<div class="title-wrapper" @click="handleClick()">
|
||||||
<div class="main-title" :class="{ active: isSelected }" >
|
<div class="main-title" :class="{ active: isSelected }">
|
||||||
<richEditor
|
<RichEditor
|
||||||
:value="bannerConf?.titleConfig?.mainTitle"
|
:modelValue="bannerConf?.titleConfig?.mainTitle"
|
||||||
@input="onTitleInput"
|
@input="onTitleInput"
|
||||||
></richEditor>
|
></RichEditor>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import richEditor from '@/common/Editor/RichEditor';
|
import RichEditor from '@/common/Editor/RichEditor.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'mainTitlePreview',
|
name: 'mainTitlePreview',
|
||||||
data() {
|
data() {
|
||||||
return {};
|
return {}
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
preview: {
|
preview: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false
|
||||||
},
|
},
|
||||||
bannerConf: {
|
bannerConf: {
|
||||||
type: Object,
|
type: Object
|
||||||
},
|
},
|
||||||
isSelected: {
|
isSelected: {
|
||||||
type: Boolean,
|
type: Boolean
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
computed: {},
|
computed: {},
|
||||||
methods: {
|
methods: {
|
||||||
handleClick() {
|
handleClick() {
|
||||||
if(this.preview) {
|
this.$emit('select')
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
this.$emit('select');
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onTitleInput(val) {
|
onTitleInput(val) {
|
||||||
if (!this.isSelected) {
|
if (!this.isSelected) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
this.$emit('change', {
|
this.$emit('change', {
|
||||||
key: 'titleConfig.mainTitle',
|
key: 'titleConfig.mainTitle',
|
||||||
value: val,
|
value: val
|
||||||
});
|
})
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
richEditor,
|
RichEditor
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
.title-wrapper {
|
.title-wrapper {
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
}
|
}
|
||||||
@ -65,7 +63,7 @@ export default {
|
|||||||
background-color: #f6f7f9;
|
background-color: #f6f7f9;
|
||||||
box-shadow: 0 0 5px #dedede;
|
box-shadow: 0 0 5px #dedede;
|
||||||
|
|
||||||
::v-deep .w-e-text-container {
|
:deep(.w-e-text-container) {
|
||||||
background-color: #f6f7f9;
|
background-color: #f6f7f9;
|
||||||
}
|
}
|
||||||
}
|
}
|
101
web/src/management/pages/edit/components/MaterialGroup.vue
Normal file
101
web/src/management/pages/edit/components/MaterialGroup.vue
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<template>
|
||||||
|
<draggable
|
||||||
|
:list="renderData"
|
||||||
|
handle=".question-wrapper.isSelected"
|
||||||
|
filter=".question-wrapper.isSelected .question.isSelected"
|
||||||
|
:onEnd="checkEnd"
|
||||||
|
:move="checkMove"
|
||||||
|
itemKey="field"
|
||||||
|
>
|
||||||
|
<template #item="{ element, index }">
|
||||||
|
<QuestionWrapper
|
||||||
|
v-bind="$attrs"
|
||||||
|
:ref="`questionWrapper-${element.field}`"
|
||||||
|
:moduleConfig="element"
|
||||||
|
:qIndex="element.qIndex"
|
||||||
|
:indexNumber="element.indexNumber"
|
||||||
|
:isSelected="currentEditOne === index"
|
||||||
|
:isLast="index + 1 === questionDataList.length"
|
||||||
|
@select="handleSelect"
|
||||||
|
>
|
||||||
|
<QuestionContainerB
|
||||||
|
v-bind="$attrs"
|
||||||
|
:type="element.type"
|
||||||
|
:moduleConfig="element"
|
||||||
|
:indexNumber="element.indexNumber"
|
||||||
|
:isSelected="currentEditOne === index"
|
||||||
|
:readonly="true"
|
||||||
|
@select="handleSelect"
|
||||||
|
></QuestionContainerB>
|
||||||
|
</QuestionWrapper>
|
||||||
|
</template>
|
||||||
|
</draggable>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { computed, defineComponent, ref, getCurrentInstance } from 'vue'
|
||||||
|
import QuestionContainerB from '@/materials/questions/QuestionContainerB'
|
||||||
|
import QuestionWrapper from '@/management/pages/edit/components/QuestionWrapper.vue'
|
||||||
|
import draggable from 'vuedraggable'
|
||||||
|
import { filterQuestionPreviewData } from '@/management/utils/index'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
draggable,
|
||||||
|
QuestionWrapper,
|
||||||
|
QuestionContainerB
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
currentEditOne: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
questionDataList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const renderData = computed(() => {
|
||||||
|
return filterQuestionPreviewData(props.questionDataList)
|
||||||
|
})
|
||||||
|
const handleSelect = (index) => {
|
||||||
|
emit('select', index)
|
||||||
|
}
|
||||||
|
const handleChangeSeq = (data) => {
|
||||||
|
emit('changeSeq', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isMoving = ref(false)
|
||||||
|
|
||||||
|
const checkMove = () => {
|
||||||
|
isMoving.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkEnd = ({ oldIndex, newIndex }) => {
|
||||||
|
emit('changeSeq', {
|
||||||
|
type: 'move',
|
||||||
|
index: oldIndex,
|
||||||
|
range: newIndex - oldIndex
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const instance = getCurrentInstance()
|
||||||
|
|
||||||
|
const getQuestionRefByField = (field) => {
|
||||||
|
return instance?.proxy?.$refs[`questionWrapper-${field}`] || null
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
renderData,
|
||||||
|
handleSelect,
|
||||||
|
handleChangeSeq,
|
||||||
|
checkMove,
|
||||||
|
checkEnd,
|
||||||
|
getQuestionRefByField
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
@ -1,49 +1,49 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="nav">
|
<div class="nav">
|
||||||
<div class="left-group">
|
<div class="left-group">
|
||||||
<back></back>
|
<BackPanel></BackPanel>
|
||||||
<pageTitle :style="{ marginLeft: '30px' }" :title="title"></pageTitle>
|
<TitlePanel :style="{ marginLeft: '30px' }" :title="title"></TitlePanel>
|
||||||
</div>
|
</div>
|
||||||
<div class="center-group">
|
<div class="center-group">
|
||||||
<pageNav></pageNav>
|
<NavPanel></NavPanel>
|
||||||
</div>
|
</div>
|
||||||
<div class="right-group">
|
<div class="right-group">
|
||||||
<history></history>
|
<HistoryPanel></HistoryPanel>
|
||||||
<save></save>
|
<SavePanel></SavePanel>
|
||||||
<publish></publish>
|
<PublishPanel></PublishPanel>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import back from '../modules/generalModule/back.vue';
|
import BackPanel from '../modules/generalModule/BackPanel.vue'
|
||||||
import pageTitle from '../modules/generalModule/pageTitle.vue';
|
import TitlePanel from '../modules/generalModule/TitlePanel.vue'
|
||||||
import pageNav from '../modules/generalModule/pageNav.vue';
|
import NavPanel from '../modules/generalModule/NavPanel.vue'
|
||||||
import history from '../modules/contentModule/history.vue';
|
import HistoryPanel from '../modules/contentModule/HistoryPanel.vue'
|
||||||
import save from '../modules/contentModule/save.vue';
|
import SavePanel from '../modules/contentModule/SavePanel.vue'
|
||||||
import publish from '../modules/contentModule/publish.vue';
|
import PublishPanel from '../modules/contentModule/PublishPanel.vue'
|
||||||
import { mapState } from 'vuex';
|
import { mapState } from 'vuex'
|
||||||
import { get as _get } from 'lodash-es';
|
import { get as _get } from 'lodash-es'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'navbar',
|
name: 'ModuleNavbar',
|
||||||
components: {
|
components: {
|
||||||
back,
|
BackPanel,
|
||||||
pageTitle,
|
TitlePanel,
|
||||||
pageNav,
|
NavPanel,
|
||||||
history,
|
HistoryPanel,
|
||||||
save,
|
SavePanel,
|
||||||
publish,
|
PublishPanel
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {};
|
return {}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState({
|
...mapState({
|
||||||
title: (state) => _get(state, 'edit.schema.metaData.title'),
|
title: (state) => _get(state, 'edit.schema.metaData.title')
|
||||||
}),
|
})
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
182
web/src/management/pages/edit/components/QuestionWrapper.vue
Normal file
182
web/src/management/pages/edit/components/QuestionWrapper.vue
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
:class="itemClass"
|
||||||
|
@mouseenter="onMouseenter"
|
||||||
|
@mouseleave="onMouseleave"
|
||||||
|
@click="clickFormItem"
|
||||||
|
>
|
||||||
|
<div><slot v-if="moduleConfig.type !== 'section'"></slot></div>
|
||||||
|
|
||||||
|
<div :class="[showHover ? 'visibily' : 'hidden', 'hoverItem']">
|
||||||
|
<div class="item el-icon-rank" @click.stop.prevent="onMove">
|
||||||
|
<i-ep-rank />
|
||||||
|
</div>
|
||||||
|
<div v-if="showUp" class="item" @click.stop.prevent="onMoveUp">
|
||||||
|
<i-ep-top />
|
||||||
|
</div>
|
||||||
|
<div v-if="showDown" class="item" @click.stop.prevent="onMoveDown">
|
||||||
|
<i-ep-bottom />
|
||||||
|
</div>
|
||||||
|
<div v-if="showCopy" class="item" @click.stop.prevent="onCopy">
|
||||||
|
<i-ep-copyDocument />
|
||||||
|
</div>
|
||||||
|
<div class="item" @click.stop.prevent="onDelete">
|
||||||
|
<i-ep-close />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { ElMessageBox } from 'element-plus'
|
||||||
|
import 'element-plus/theme-chalk/src/message-box.scss'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
qIndex: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
indexNumber: {
|
||||||
|
type: Number,
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
isSelected: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
isLast: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
moduleConfig: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const emit = defineEmits(['changeSeq', 'select'])
|
||||||
|
|
||||||
|
const isHover = ref(false)
|
||||||
|
|
||||||
|
const itemClass = computed(() => {
|
||||||
|
return {
|
||||||
|
'question-wrapper': true,
|
||||||
|
'mouse-hover': isHover.value,
|
||||||
|
isSelected: props.isSelected,
|
||||||
|
spliter: props.moduleConfig.showSpliter
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const showHover = computed(() => {
|
||||||
|
return isHover.value
|
||||||
|
})
|
||||||
|
const showUp = computed(() => {
|
||||||
|
return props.qIndex !== 0
|
||||||
|
})
|
||||||
|
const showDown = computed(() => {
|
||||||
|
return !props.isLast
|
||||||
|
})
|
||||||
|
const showCopy = computed(() => {
|
||||||
|
const field = props.moduleConfig.field
|
||||||
|
const hiddenCopFields = ['mob', 'mobileHidden', 'userAgreement']
|
||||||
|
return hiddenCopFields.indexOf(field) <= -1
|
||||||
|
})
|
||||||
|
|
||||||
|
const clickFormItem = () => {
|
||||||
|
const index = props.qIndex
|
||||||
|
emit('select', index)
|
||||||
|
}
|
||||||
|
const onCopy = () => {
|
||||||
|
const index = props.qIndex
|
||||||
|
emit('changeSeq', { type: 'copy', index })
|
||||||
|
isHover.value = false
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const onMoveUp = () => {
|
||||||
|
const index = props.qIndex
|
||||||
|
emit('changeSeq', { type: 'move', index, range: -1 })
|
||||||
|
isHover.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const onMouseenter = () => {
|
||||||
|
isHover.value = true
|
||||||
|
}
|
||||||
|
const onMouseleave = () => {
|
||||||
|
isHover.value = false
|
||||||
|
}
|
||||||
|
const onMoveDown = () => {
|
||||||
|
const index = props.qIndex
|
||||||
|
emit('changeSeq', { type: 'move', index, range: 1 })
|
||||||
|
isHover.value = false
|
||||||
|
}
|
||||||
|
const onDelete = async () => {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm('本次操作会影响数据统计查看,是否确认删除?', '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
|
||||||
|
const index = props.qIndex
|
||||||
|
emit('changeSeq', { type: 'delete', index })
|
||||||
|
isHover.value = false
|
||||||
|
} catch (error) {
|
||||||
|
console.log('取消删除')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onMove = () => {}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.question-wrapper {
|
||||||
|
position: relative;
|
||||||
|
padding: 0.36rem 0 0.36rem;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
&.spliter {
|
||||||
|
border-bottom: 0.12rem solid $spliter-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mouse-hover {
|
||||||
|
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.09);
|
||||||
|
}
|
||||||
|
&.isSelected {
|
||||||
|
background-color: #f2f4f7;
|
||||||
|
box-shadow: 0 0 5px #e3e4e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hoverItem {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
margin-top: -5px;
|
||||||
|
right: -32px;
|
||||||
|
z-index: 2;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
&.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 5px;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #eceff1;
|
||||||
|
margin-right: 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #506b7b;
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 28px;
|
||||||
|
&:hover {
|
||||||
|
background-color: $primary-color;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
225
web/src/management/pages/edit/components/SetterField.vue
Normal file
225
web/src/management/pages/edit/components/SetterField.vue
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
<template>
|
||||||
|
<el-form class="config-form" @submit.prevent>
|
||||||
|
<div v-for="(item, index) in formFieldData" :key="`${item.key}${index}`" class="group-wrap">
|
||||||
|
<div v-if="item.title" class="group-title">
|
||||||
|
{{ item.title }}
|
||||||
|
|
||||||
|
<el-tooltip v-if="item.tip" :content="item.tip" placement="right">
|
||||||
|
<i-ep-questionFilled class="icon-tip" />
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template v-if="item.type === 'Customed'">
|
||||||
|
<FormItem
|
||||||
|
v-for="(content, contentIndex) in item.content"
|
||||||
|
:key="`${item.key}${contentIndex}`"
|
||||||
|
:form-config="content"
|
||||||
|
>
|
||||||
|
<Component
|
||||||
|
:is="content.type"
|
||||||
|
:form-config="content"
|
||||||
|
:module-config="moduleConfig"
|
||||||
|
@form-change="onFormChange($event, content)"
|
||||||
|
:class="content.contentClass"
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
</template>
|
||||||
|
<FormItem v-else :form-config="item">
|
||||||
|
<Component
|
||||||
|
:is="item.type"
|
||||||
|
:form-config="item"
|
||||||
|
:module-config="moduleConfig"
|
||||||
|
@form-change="onFormChange($event, item)"
|
||||||
|
:class="item.contentClass"
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { get as _get, pick as _pick, isFunction as _isFunction } from 'lodash-es'
|
||||||
|
|
||||||
|
import FormItem from '@/materials/setters/widgets/FormItem.vue'
|
||||||
|
import setterLoader from '@/materials/setters/setterLoader'
|
||||||
|
|
||||||
|
import { FORM_CHANGE_EVENT_KEY } from '@/materials/setters/constant'
|
||||||
|
|
||||||
|
// 静态配置设置动态值
|
||||||
|
const formatValue = ({ item, moduleConfig }) => {
|
||||||
|
if (_isFunction(item.valueAdapter)) {
|
||||||
|
const value = item.valueAdapter({ moduleConfig })
|
||||||
|
|
||||||
|
return value
|
||||||
|
} else {
|
||||||
|
const { key, keys } = item
|
||||||
|
let result = null
|
||||||
|
if (key) {
|
||||||
|
result = _get(moduleConfig, key, item.value)
|
||||||
|
}
|
||||||
|
if (keys) {
|
||||||
|
result = _pick(moduleConfig, keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'SettersField',
|
||||||
|
props: {
|
||||||
|
formConfigList: Array, // 对应题型组件的meta.js内容
|
||||||
|
moduleConfig: Object
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
register: {},
|
||||||
|
formFieldData: [],
|
||||||
|
init: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
FormItem
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
formConfigList: {
|
||||||
|
deep: true,
|
||||||
|
immediate: true,
|
||||||
|
async handler(newVal) {
|
||||||
|
this.init = true
|
||||||
|
if (!newVal || !newVal.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组件注册
|
||||||
|
await this.handleComponentRegister(newVal)
|
||||||
|
|
||||||
|
this.init = false
|
||||||
|
this.formFieldData = this.setValues(this.formConfigList)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// schema变化联动
|
||||||
|
moduleConfig: {
|
||||||
|
deep: true,
|
||||||
|
async handler() {
|
||||||
|
// 配置变化后初次不监听value变化(如题型切换场景避免多次计算)
|
||||||
|
if (this.init) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: 优化,依赖的schema变化时,均会重新计算
|
||||||
|
this.formFieldData = this.setValues(this.formConfigList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setValues(configList = []) {
|
||||||
|
return configList
|
||||||
|
.filter((item) => {
|
||||||
|
// 组件组
|
||||||
|
if (item.type === 'Customed') {
|
||||||
|
item.content = this.setValues(item.content)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!item.type) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (item.hidden) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 动态显隐设置器
|
||||||
|
if (_isFunction(item.relyFunc)) {
|
||||||
|
return item.relyFunc(this.moduleConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
.map((item) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
value: formatValue({ item, moduleConfig: this.moduleConfig }) // 动态复值
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async handleComponentRegister(formFieldData) {
|
||||||
|
let innerSetters = []
|
||||||
|
const setters = formFieldData.map((item) => {
|
||||||
|
if (item.type === 'Customed') {
|
||||||
|
innerSetters.push(...(item.content || []).map((content) => content.type))
|
||||||
|
}
|
||||||
|
|
||||||
|
return item.type
|
||||||
|
})
|
||||||
|
|
||||||
|
const settersSet = new Set([...setters, ...innerSetters])
|
||||||
|
const settersArr = Array.from(settersSet)
|
||||||
|
const allSetters = settersArr.map((item) => {
|
||||||
|
return {
|
||||||
|
type: item,
|
||||||
|
path: item
|
||||||
|
}
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
const comps = await setterLoader.loadComponents(allSetters)
|
||||||
|
for (const comp of comps) {
|
||||||
|
if (!comp) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const { type, component, err } = comp
|
||||||
|
if (!err) {
|
||||||
|
const componentName = component.name
|
||||||
|
if (!this.$options.components) {
|
||||||
|
this.$options.components = {}
|
||||||
|
}
|
||||||
|
this.$options.components[componentName] = component
|
||||||
|
this.register[type] = componentName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onFormChange(data, formConfig) {
|
||||||
|
if (_isFunction(formConfig?.setterAdapter)) {
|
||||||
|
const resultData = formConfig.setterAdapter(data)
|
||||||
|
if (Array.isArray(resultData)) {
|
||||||
|
resultData.forEach((item) => {
|
||||||
|
this.$emit(FORM_CHANGE_EVENT_KEY, item)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.$emit(FORM_CHANGE_EVENT_KEY, resultData)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.$emit(FORM_CHANGE_EVENT_KEY, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.config-form {
|
||||||
|
padding: 15px 0;
|
||||||
|
|
||||||
|
.group-wrap {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-title {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #606266;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.icon-tip {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,38 +1,32 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div class="submit-wrapper" @click="onClick" :class="{ isSelected: isSelected }">
|
||||||
class="submit-wrapper"
|
<el-button class="submit-btn" type="primary">{{ submitConf.submitTitle }}</el-button>
|
||||||
@click="onClick"
|
|
||||||
:class="{ isSelected: isSelected }"
|
|
||||||
>
|
|
||||||
<el-button
|
|
||||||
class="submit-btn"
|
|
||||||
type="primary"
|
|
||||||
>{{ submitConf.submitTitle }}</el-button
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'Submit',
|
name: 'SubmitButton',
|
||||||
data() {
|
data() {
|
||||||
return {};
|
return {}
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
submitConf: Object,
|
submitConf: Object,
|
||||||
isSelected: Boolean,
|
isSelected: Boolean,
|
||||||
skinConf: {
|
skinConf: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onClick() {
|
onClick() {
|
||||||
this.$emit('select');
|
this.$emit('select')
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
.submit-wrapper {
|
.submit-wrapper {
|
||||||
padding: 25px;
|
padding: 25px;
|
||||||
text-align: center;
|
text-align: center;
|
@ -1,121 +0,0 @@
|
|||||||
<script>
|
|
||||||
import { computed, defineComponent, ref, getCurrentInstance } from 'vue';
|
|
||||||
import QuestionContainer from '@/materials/questions/widgets/QuestionContainer.jsx';
|
|
||||||
import questionWrapper from './questionWrapper.vue';
|
|
||||||
import draggable from 'vuedraggable';
|
|
||||||
import { filterQuestionPreviewData } from '@/management/utils/index';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: '',
|
|
||||||
components: { draggable },
|
|
||||||
props: {
|
|
||||||
currentEditOne: {
|
|
||||||
type: [Number, String],
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
questionDataList: {
|
|
||||||
type: Array,
|
|
||||||
default: () => {
|
|
||||||
return [];
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
watch: {},
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const renderData = computed(() => {
|
|
||||||
return filterQuestionPreviewData(props.questionDataList);
|
|
||||||
});
|
|
||||||
const handleSelect = (index) => {
|
|
||||||
emit('select', index);
|
|
||||||
};
|
|
||||||
const handleChangeSeq = (data) => {
|
|
||||||
emit('changeSeq', data);
|
|
||||||
};
|
|
||||||
|
|
||||||
const isMoving = ref(false);
|
|
||||||
|
|
||||||
const checkMove = () => {
|
|
||||||
isMoving.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const checkEnd = ({ oldIndex, newIndex }) => {
|
|
||||||
emit('changeSeq', {
|
|
||||||
type: 'move',
|
|
||||||
index: oldIndex,
|
|
||||||
range: newIndex - oldIndex,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const instance = getCurrentInstance();
|
|
||||||
|
|
||||||
const getQuestionRefByField = (field) => {
|
|
||||||
return instance?.proxy?.$refs[`questionWrapper-${field}`] || null;
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
renderData,
|
|
||||||
handleSelect,
|
|
||||||
handleChangeSeq,
|
|
||||||
checkMove,
|
|
||||||
checkEnd,
|
|
||||||
dragOptions: {
|
|
||||||
animation: 0,
|
|
||||||
group: 'previewList',
|
|
||||||
handle: '.el-icon-rank',
|
|
||||||
scroll: true,
|
|
||||||
scrollSpeed: 2500,
|
|
||||||
scrollSensitivity: 150,
|
|
||||||
forceFallback: true,
|
|
||||||
},
|
|
||||||
getQuestionRefByField,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
render(h) {
|
|
||||||
return (
|
|
||||||
<draggable
|
|
||||||
list={this.renderData}
|
|
||||||
options={this.dragOptions}
|
|
||||||
onEnd={this.checkEnd}
|
|
||||||
move={this.checkMove}
|
|
||||||
>
|
|
||||||
{this.renderData.map((item, index) => {
|
|
||||||
return h(
|
|
||||||
questionWrapper,
|
|
||||||
{
|
|
||||||
ref: `questionWrapper-${item.field}`,
|
|
||||||
key: item.field,
|
|
||||||
props: {
|
|
||||||
moduleConfig: item,
|
|
||||||
qIndex: item.qIndex,
|
|
||||||
indexNumber: item.indexNumber,
|
|
||||||
isSelected: this.currentEditOne === index,
|
|
||||||
isLast: index + 1 === this.questionDataList.length,
|
|
||||||
},
|
|
||||||
on: {
|
|
||||||
...this.$listeners,
|
|
||||||
select: this.handleSelect,
|
|
||||||
changeSeq: this.handleChangeSeq,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[
|
|
||||||
h(QuestionContainer, {
|
|
||||||
props: {
|
|
||||||
type: item.type,
|
|
||||||
moduleConfig: item,
|
|
||||||
indexNumber: item.indexNumber,
|
|
||||||
isSelected: this.currentEditOne === index,
|
|
||||||
readonly: true,
|
|
||||||
},
|
|
||||||
on: {
|
|
||||||
...this.$listeners,
|
|
||||||
select: this.handleSelect,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</draggable>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
@ -1,335 +0,0 @@
|
|||||||
<script>
|
|
||||||
import {
|
|
||||||
defineComponent,
|
|
||||||
reactive,
|
|
||||||
toRefs,
|
|
||||||
computed,
|
|
||||||
getCurrentInstance,
|
|
||||||
} from 'vue';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'QuestionWrapper',
|
|
||||||
props: {
|
|
||||||
qIndex: {
|
|
||||||
type: Number,
|
|
||||||
default: 0,
|
|
||||||
},
|
|
||||||
indexNumber: {
|
|
||||||
type: Number,
|
|
||||||
default: 1,
|
|
||||||
},
|
|
||||||
isSelected: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
isLast: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
moduleConfig: {
|
|
||||||
type: Object,
|
|
||||||
default: () => {
|
|
||||||
return {};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const state = reactive({
|
|
||||||
isHover: false,
|
|
||||||
});
|
|
||||||
const { proxy } = getCurrentInstance();
|
|
||||||
const itemClass = computed(() => {
|
|
||||||
return {
|
|
||||||
'question-wrapper': true,
|
|
||||||
'mouse-hover': state.isHover,
|
|
||||||
isSelected: props.isSelected,
|
|
||||||
spliter: props.moduleConfig.showSpliter,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
const showHover = computed(() => {
|
|
||||||
return state.isHover;
|
|
||||||
});
|
|
||||||
const showUp = computed(() => {
|
|
||||||
return props.qIndex !== 0;
|
|
||||||
});
|
|
||||||
const showDown = computed(() => {
|
|
||||||
return !props.isLast;
|
|
||||||
});
|
|
||||||
const showCopy = computed(() => {
|
|
||||||
const field = props.moduleConfig.field;
|
|
||||||
const hiddenCopFields = ['mob', 'mobileHidden', 'userAgreement'];
|
|
||||||
return hiddenCopFields.indexOf(field) <= -1;
|
|
||||||
});
|
|
||||||
const toggleHoverClass = (status) => {
|
|
||||||
state.isHover = status;
|
|
||||||
};
|
|
||||||
const clickFormItem = () => {
|
|
||||||
const index = props.qIndex;
|
|
||||||
emit('select', index);
|
|
||||||
};
|
|
||||||
const onCopy = () => {
|
|
||||||
const index = props.qIndex;
|
|
||||||
// this.changeQuestionSeq({ type: 'copy', index })
|
|
||||||
emit('changeSeq', { type: 'copy', index });
|
|
||||||
state.isHover = false;
|
|
||||||
};
|
|
||||||
const onMoveUp = () => {
|
|
||||||
const index = props.qIndex;
|
|
||||||
// this.changeQuestionSeq({ type: 'move', index, range: -1 })
|
|
||||||
emit('changeSeq', { type: 'move', index, range: -1 });
|
|
||||||
state.isHover = false;
|
|
||||||
};
|
|
||||||
// const onMoveTop = () => {
|
|
||||||
// const index = props.qIndex
|
|
||||||
// // this.changeQuestionSeq({ type: 'move', index, range: -index })
|
|
||||||
// emit('changeSeq', { type: 'move', index, range: -index })
|
|
||||||
// state.isHover = false
|
|
||||||
// }
|
|
||||||
const onMoveDown = () => {
|
|
||||||
const index = props.qIndex;
|
|
||||||
// this.changeQuestionSeq({ type: 'move', index, range: 1 })
|
|
||||||
emit('changeSeq', { type: 'move', index, range: 1 });
|
|
||||||
state.isHover = false;
|
|
||||||
};
|
|
||||||
const onDelete = async () => {
|
|
||||||
try {
|
|
||||||
await proxy.$confirm(
|
|
||||||
'本次操作会影响数据统计查看,是否确认删除?',
|
|
||||||
'提示',
|
|
||||||
{
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const index = props.qIndex;
|
|
||||||
// this.changeQuestionSeq({ type: 'move', index, range: 1 })
|
|
||||||
emit('changeSeq', { type: 'delete', index });
|
|
||||||
state.isHover = false;
|
|
||||||
} catch (error) {
|
|
||||||
console.log('取消删除');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// const onMoveBottom = () => {
|
|
||||||
// const index = props.qIndex
|
|
||||||
// this.changeQuestionSeq({
|
|
||||||
// type: 'move',
|
|
||||||
// index,
|
|
||||||
// range: props.questionDataList.length - index,
|
|
||||||
// })
|
|
||||||
// state.isHover = false
|
|
||||||
// }
|
|
||||||
return {
|
|
||||||
...toRefs(state),
|
|
||||||
itemClass,
|
|
||||||
showHover,
|
|
||||||
showUp,
|
|
||||||
showDown,
|
|
||||||
showCopy,
|
|
||||||
onCopy,
|
|
||||||
onMoveUp,
|
|
||||||
onMoveDown,
|
|
||||||
onDelete,
|
|
||||||
toggleHoverClass,
|
|
||||||
clickFormItem,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
render() {
|
|
||||||
const { showHover, itemClass, showUp, showDown, showCopy } = this;
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
class={itemClass}
|
|
||||||
onMouseenter={() => {
|
|
||||||
this.isHover = true;
|
|
||||||
}}
|
|
||||||
onMouseleave={() => {
|
|
||||||
this.isHover = false;
|
|
||||||
}}
|
|
||||||
onClick={this.clickFormItem}
|
|
||||||
>
|
|
||||||
{this.moduleConfig.type !== 'section' && (
|
|
||||||
<div>{this.$slots.default}</div>
|
|
||||||
)}
|
|
||||||
{
|
|
||||||
<div class={[showHover ? 'visibily' : 'hidden', 'hoverItem']}>
|
|
||||||
<div
|
|
||||||
class="item move el-icon-rank"
|
|
||||||
vOn:click_stop_prevent={this.onMove}
|
|
||||||
></div>
|
|
||||||
{showUp && (
|
|
||||||
<div
|
|
||||||
class="item iconfont icon-shangyi"
|
|
||||||
vOn:click_stop_prevent={this.onMoveUp}
|
|
||||||
></div>
|
|
||||||
)}
|
|
||||||
{showDown && (
|
|
||||||
<div
|
|
||||||
class="item iconfont icon-xiayi"
|
|
||||||
vOn:click_stop_prevent={this.onMoveDown}
|
|
||||||
></div>
|
|
||||||
)}
|
|
||||||
{showCopy && (
|
|
||||||
<div
|
|
||||||
class="item copy iconfont icon-fuzhi"
|
|
||||||
vOn:click_stop_prevent={this.onCopy}
|
|
||||||
></div>
|
|
||||||
)}
|
|
||||||
<div
|
|
||||||
class="item iconfont icon-shanchu"
|
|
||||||
vOn:click_stop_prevent={this.onDelete}
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
|
||||||
.question-wrapper {
|
|
||||||
position: relative;
|
|
||||||
padding: 0.36rem 0 0.36rem;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
&.spliter {
|
|
||||||
border-bottom: 0.12rem solid $spliter-color;
|
|
||||||
}
|
|
||||||
&:last-child{
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
.editor {
|
|
||||||
display: flex;
|
|
||||||
font-size: 0.32rem;
|
|
||||||
margin-bottom: 0.4rem;
|
|
||||||
padding: 0 0.4rem;
|
|
||||||
.icon-required {
|
|
||||||
color: $error-color;
|
|
||||||
position: absolute;
|
|
||||||
left: 0.16rem;
|
|
||||||
font-size: 0.46rem;
|
|
||||||
}
|
|
||||||
.index {
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.component-wrapper {
|
|
||||||
padding: 0 0.4rem;
|
|
||||||
.editor {
|
|
||||||
padding-left: 0.4rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.no-padding {
|
|
||||||
.component-wrapper {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.mouse-hover {
|
|
||||||
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.09);
|
|
||||||
}
|
|
||||||
&.isSelected {
|
|
||||||
background-color: #f2f4f7;
|
|
||||||
box-shadow: 0 0 5px #e3e4e8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.clear {
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.question-type-section {
|
|
||||||
.module-title {
|
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.horizon {
|
|
||||||
display: flex;
|
|
||||||
.module-title .m-title {
|
|
||||||
width: 1.2rem;
|
|
||||||
margin-right: 8px;
|
|
||||||
text-align: justify;
|
|
||||||
position: relative;
|
|
||||||
&::before {
|
|
||||||
content: ':';
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
right: -5px;
|
|
||||||
}
|
|
||||||
&::after {
|
|
||||||
content: '';
|
|
||||||
display: inline-block;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.component-wrapper {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.hoverItem {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
margin-top: -5px;
|
|
||||||
right: -32px;
|
|
||||||
z-index: 2;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
&.hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.item {
|
|
||||||
margin-top: 5px;
|
|
||||||
display: inline-block;
|
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: #eceff1;
|
|
||||||
margin-right: 2px;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #506b7b;
|
|
||||||
font-size: 12px;
|
|
||||||
text-align: center;
|
|
||||||
line-height: 28px;
|
|
||||||
&:hover {
|
|
||||||
background-color: $primary-color;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.move {
|
|
||||||
cursor: move;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
.copy {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.titleGray {
|
|
||||||
color: #ddd;
|
|
||||||
}
|
|
||||||
.relation-show,
|
|
||||||
.jumpto-show,
|
|
||||||
.listenmerge-show {
|
|
||||||
margin-top: 0.4rem;
|
|
||||||
font-size: 12px;
|
|
||||||
color: $placeholder-color;
|
|
||||||
padding: 0 0.4rem;
|
|
||||||
}
|
|
||||||
.relyList {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
.font-bold {
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
.option-origin-text {
|
|
||||||
color: #ccc;
|
|
||||||
margin-left: 17px;
|
|
||||||
}
|
|
||||||
.sort-tip {
|
|
||||||
font-size: 0.26rem;
|
|
||||||
line-height: 0.26rem;
|
|
||||||
opacity: 0.5;
|
|
||||||
margin-top: -0.24rem;
|
|
||||||
margin-bottom: 0.4rem;
|
|
||||||
padding-left: 0.4rem;
|
|
||||||
color: #92949d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,190 +0,0 @@
|
|||||||
<template>
|
|
||||||
<el-form
|
|
||||||
class="config-form"
|
|
||||||
size="small"
|
|
||||||
:labelPosition="labelPosition"
|
|
||||||
label-width="110px"
|
|
||||||
:inline="inline"
|
|
||||||
@submit.native.prevent
|
|
||||||
>
|
|
||||||
<template v-for="(item, index) in formFieldData">
|
|
||||||
<FormItem :key="item.key + index" class="form-item" :form-config="item">
|
|
||||||
<template v-if="item.type === 'Customed'">
|
|
||||||
<SettersField
|
|
||||||
:key="index"
|
|
||||||
:form-config-list="item.content"
|
|
||||||
:module-config="moduleConfig"
|
|
||||||
@form-change="onFormChange($event, item)"
|
|
||||||
:inline="true"
|
|
||||||
labelPosition="left"
|
|
||||||
:class="item.contentClass"
|
|
||||||
></SettersField>
|
|
||||||
</template>
|
|
||||||
<Component
|
|
||||||
v-else
|
|
||||||
:is="item.type"
|
|
||||||
:module-config="moduleConfig"
|
|
||||||
:form-config="item"
|
|
||||||
@form-change="onFormChange($event, item)"
|
|
||||||
:slot="item.contentPosition || null"
|
|
||||||
/>
|
|
||||||
</FormItem>
|
|
||||||
</template>
|
|
||||||
</el-form>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
import {
|
|
||||||
get as _get,
|
|
||||||
pick as _pick,
|
|
||||||
isFunction as _isFunction,
|
|
||||||
} from 'lodash-es';
|
|
||||||
|
|
||||||
import FormItem from '@/materials/setters/widgets/FormItem.vue';
|
|
||||||
import setterLoader from '@/materials/setters/setterLoader';
|
|
||||||
|
|
||||||
import { FORM_CHANGE_EVENT_KEY } from '@/materials/setters/constant';
|
|
||||||
|
|
||||||
const formatValue = ({ item, moduleConfig }) => {
|
|
||||||
if (_isFunction(item.valueAdapter)) {
|
|
||||||
const value = item.valueAdapter({ moduleConfig });
|
|
||||||
return value;
|
|
||||||
} else {
|
|
||||||
const { key, keys } = item;
|
|
||||||
|
|
||||||
let result = null;
|
|
||||||
if (key) {
|
|
||||||
result = _get(moduleConfig, key, item.value);
|
|
||||||
}
|
|
||||||
if (keys) {
|
|
||||||
result = _pick(moduleConfig, keys);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'SettersField',
|
|
||||||
props: {
|
|
||||||
formConfigList: Array,
|
|
||||||
moduleConfig: Object,
|
|
||||||
inline: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
labelPosition: {
|
|
||||||
type: String,
|
|
||||||
default: 'top',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
registerd: {},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
FormItem,
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
formFieldData() {
|
|
||||||
return this.formConfigList
|
|
||||||
.filter((item) => {
|
|
||||||
if (!item.type) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (item.type !== 'Customed' && !this.registerd[item.type]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (item.hidden) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (_isFunction(item.relyFunc)) {
|
|
||||||
return item.relyFunc(this.moduleConfig);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
})
|
|
||||||
.map((item) => {
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
value: formatValue({ item, moduleConfig: this.moduleConfig }),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
formConfigList: {
|
|
||||||
deep: true,
|
|
||||||
immediate: true,
|
|
||||||
handler(newVal) {
|
|
||||||
if (!newVal || !newVal.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.handleComponentRegister(newVal);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async handleComponentRegister(formFieldData) {
|
|
||||||
const setters = formFieldData.map((item) => item.type);
|
|
||||||
const settersSet = new Set(setters);
|
|
||||||
const settersArr = Array.from(settersSet);
|
|
||||||
const allSetters = settersArr.map((item) => {
|
|
||||||
return {
|
|
||||||
type: item,
|
|
||||||
path: item,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
const comps = await setterLoader.loadComponents(allSetters);
|
|
||||||
for (const comp of comps) {
|
|
||||||
if (!comp) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const { type, component, err } = comp;
|
|
||||||
if (!err) {
|
|
||||||
const componentName = component.name;
|
|
||||||
if (!this.$options.components) {
|
|
||||||
this.$options.components = {};
|
|
||||||
}
|
|
||||||
this.$options.components[componentName] = component;
|
|
||||||
this.$set(this.registerd, type, componentName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onFormChange(data, formConfig) {
|
|
||||||
if (_isFunction(formConfig?.setterAdapter)) {
|
|
||||||
const resultData = formConfig.setterAdapter(data);
|
|
||||||
if (Array.isArray(resultData)) {
|
|
||||||
resultData.forEach((item) => {
|
|
||||||
this.$emit(FORM_CHANGE_EVENT_KEY, item);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.$emit(FORM_CHANGE_EVENT_KEY, resultData);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.$emit(FORM_CHANGE_EVENT_KEY, data);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
|
||||||
.config-form {
|
|
||||||
padding: 15px 0;
|
|
||||||
}
|
|
||||||
.nps-customed-config {
|
|
||||||
.el-form-item {
|
|
||||||
margin-right: 0px;
|
|
||||||
::v-deep .el-form-item__label {
|
|
||||||
width: 70px !important;
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
::v-deep .el-input__inner {
|
|
||||||
width: 234px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,42 +1,52 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="edit-index">
|
<div class="edit-index">
|
||||||
<leftMenu class="left"></leftMenu>
|
<LeftMenu class="left"></LeftMenu>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<commonTemplate style="background-color: #f6f7f9">
|
<CommonTemplate style="background-color: #f6f7f9">
|
||||||
<navbar class="navbar" slot="nav"></navbar>
|
<template #nav>
|
||||||
<router-view slot="body"></router-view>
|
<Navbar class="navbar"></Navbar>
|
||||||
</commonTemplate>
|
</template>
|
||||||
|
<template #body>
|
||||||
|
<router-view></router-view>
|
||||||
|
</template>
|
||||||
|
</CommonTemplate>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import commonTemplate from './components/commonTemplate.vue';
|
import { ElMessage } from 'element-plus'
|
||||||
import navbar from './components/navbar.vue';
|
import 'element-plus/theme-chalk/src/message.scss'
|
||||||
import leftMenu from '@/management/components/leftMenu.vue';
|
|
||||||
|
import LeftMenu from '@/management/components/LeftMenu.vue'
|
||||||
|
|
||||||
|
import CommonTemplate from './components/CommonTemplate.vue'
|
||||||
|
import Navbar from './components/ModuleNavbar.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'questionEditPage',
|
name: 'questionEditPage',
|
||||||
components: {
|
components: {
|
||||||
commonTemplate,
|
CommonTemplate,
|
||||||
navbar,
|
Navbar,
|
||||||
leftMenu,
|
LeftMenu
|
||||||
},
|
},
|
||||||
async created() {
|
async created() {
|
||||||
this.$store.commit('edit/setSurveyId', this.$route.params.id);
|
this.$store.commit('edit/setSurveyId', this.$route.params.id)
|
||||||
try {
|
try {
|
||||||
await this.$store.dispatch('edit/init');
|
await this.$store.dispatch('edit/init')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.$message.error(error.message);
|
ElMessage.error(error.message)
|
||||||
// 自动跳转回列表页
|
// 自动跳转回列表页
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.$router.replace({
|
this.$router.replace({
|
||||||
name: 'survey',
|
name: 'survey'
|
||||||
});
|
})
|
||||||
}, 1000);
|
}, 1000)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.edit-index {
|
.edit-index {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -56,6 +66,7 @@ export default {
|
|||||||
padding-left: 80px;
|
padding-left: 80px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar {
|
.navbar {
|
||||||
border-bottom: 1px solid #e7e9eb;
|
border-bottom: 1px solid #e7e9eb;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-popover placement="top" trigger="click" @show="onShow">
|
<el-popover placement="top" trigger="click" @show="onShow" :width="320">
|
||||||
<el-tabs v-model="currentTab" class="custom-tab" v-if="visible">
|
<el-tabs v-model="currentTab" class="custom-tab" v-if="visible">
|
||||||
<el-tab-pane label="修改历史" name="daily" class="custom-tab-pane">
|
<el-tab-pane label="修改历史" name="daily" class="custom-tab-pane">
|
||||||
<div class="line" v-for="(his, index) in dailyList" :key="index">
|
<div class="line" v-for="(his, index) in dailyList" :key="index">
|
||||||
@ -16,48 +16,51 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
<div class="btn" slot="reference">
|
<template #reference>
|
||||||
<i class="iconfont icon-lishi"></i>
|
<div class="btn">
|
||||||
<span class="btn-txt">历史</span>
|
<i class="iconfont icon-lishi"></i>
|
||||||
</div>
|
<span class="btn-txt">历史</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</el-popover>
|
</el-popover>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
|
||||||
import { getSurveyHistory } from '@/management/api/survey';
|
|
||||||
import moment from 'moment';
|
|
||||||
// 引入中文
|
|
||||||
import 'moment/locale/zh-cn';
|
|
||||||
// 设置中文
|
|
||||||
moment.locale('zh-cn');
|
|
||||||
|
|
||||||
import { mapState } from 'vuex';
|
<script>
|
||||||
import { get as _get } from 'lodash-es';
|
import { getSurveyHistory } from '@/management/api/survey'
|
||||||
|
import moment from 'moment'
|
||||||
|
// 引入中文
|
||||||
|
import 'moment/locale/zh-cn'
|
||||||
|
// 设置中文
|
||||||
|
moment.locale('zh-cn')
|
||||||
|
|
||||||
|
import { mapState } from 'vuex'
|
||||||
|
import { get as _get } from 'lodash-es'
|
||||||
|
|
||||||
const getItemData = (item) => ({
|
const getItemData = (item) => ({
|
||||||
operator: item?.operator?.username || '未知用户',
|
operator: item?.operator?.username || '未知用户',
|
||||||
time: moment(item.createDate).format('YYYY-MM-DD HH:mm:ss'),
|
time: moment(item.createDate).format('YYYY-MM-DD HH:mm:ss')
|
||||||
});
|
})
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'history',
|
name: 'HistoryPanel',
|
||||||
computed: {
|
computed: {
|
||||||
...mapState({
|
...mapState({
|
||||||
surveyId: (state) => _get(state, 'edit.surveyId'),
|
surveyId: (state) => _get(state, 'edit.surveyId')
|
||||||
}),
|
}),
|
||||||
dailyList() {
|
dailyList() {
|
||||||
return this.dailyHis.map(getItemData);
|
return this.dailyHis.map(getItemData)
|
||||||
},
|
},
|
||||||
publishList() {
|
publishList() {
|
||||||
return this.publishHis.map(getItemData);
|
return this.publishHis.map(getItemData)
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
dailyHis: [],
|
dailyHis: [],
|
||||||
publishHis: [],
|
publishHis: [],
|
||||||
currentTab: 'daily',
|
currentTab: 'daily',
|
||||||
visible: false,
|
visible: false
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
surveyId: {
|
surveyId: {
|
||||||
@ -67,31 +70,32 @@ export default {
|
|||||||
const [dailyHis, publishHis] = await Promise.all([
|
const [dailyHis, publishHis] = await Promise.all([
|
||||||
getSurveyHistory({
|
getSurveyHistory({
|
||||||
surveyId: this.surveyId,
|
surveyId: this.surveyId,
|
||||||
historyType: 'dailyHis',
|
historyType: 'dailyHis'
|
||||||
}),
|
}),
|
||||||
getSurveyHistory({
|
getSurveyHistory({
|
||||||
surveyId: this.surveyId,
|
surveyId: this.surveyId,
|
||||||
historyType: 'publishHis',
|
historyType: 'publishHis'
|
||||||
}),
|
})
|
||||||
]);
|
])
|
||||||
this.dailyHis = dailyHis.data || [];
|
this.dailyHis = dailyHis.data || []
|
||||||
this.publishHis = publishHis.data || [];
|
this.publishHis = publishHis.data || []
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onShow() {
|
onShow() {
|
||||||
this.visible = true;
|
this.visible = true
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import url('@/management/styles/edit-btn.scss');
|
@import url('@/management/styles/edit-btn.scss');
|
||||||
.custom-tab {
|
.custom-tab {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
::v-deep .el-tabs__nav {
|
:deep(.el-tabs__nav) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
.el-tabs__item {
|
.el-tabs__item {
|
@ -0,0 +1,75 @@
|
|||||||
|
<template>
|
||||||
|
<el-button type="primary" :loading="isPublishing" class="publish-btn" @click="onPublish">
|
||||||
|
发布
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapState } from 'vuex'
|
||||||
|
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import 'element-plus/theme-chalk/src/message.scss'
|
||||||
|
|
||||||
|
import { get as _get } from 'lodash-es'
|
||||||
|
|
||||||
|
import { publishSurvey, saveSurvey } from '@/management/api/survey'
|
||||||
|
import buildData from './buildData'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'PublishPanel',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isPublishing: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
surveyId: (state) => _get(state, 'edit.surveyId')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async onPublish() {
|
||||||
|
const saveData = buildData(this.$store.state.edit.schema)
|
||||||
|
if (!saveData.surveyId) {
|
||||||
|
ElMessage.error('未获取到问卷id')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.isPublishing) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this.isPublishing = true
|
||||||
|
const saveRes = await saveSurvey(saveData)
|
||||||
|
if (saveRes.code !== 200) {
|
||||||
|
ElMessage.error(saveRes.errmsg || '问卷保存失败')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const publishRes = await publishSurvey({ surveyId: this.surveyId })
|
||||||
|
if (publishRes.code === 200) {
|
||||||
|
ElMessage.success('发布成功')
|
||||||
|
this.$store.dispatch('edit/getSchemaFromRemote')
|
||||||
|
this.$router.push({
|
||||||
|
name: 'publishResultPage'
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
ElMessage.error(`发布失败 ${publishRes.errmsg}`)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error(`发布失败`)
|
||||||
|
} finally {
|
||||||
|
this.isPublishing = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.publish-btn {
|
||||||
|
width: 100px;
|
||||||
|
font-size: 14px;
|
||||||
|
height: 36px;
|
||||||
|
line-height: 36px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
</style>
|
@ -7,115 +7,119 @@
|
|||||||
<span class="sv-text">
|
<span class="sv-text">
|
||||||
{{ saveText }}
|
{{ saveText }}
|
||||||
</span>
|
</span>
|
||||||
<i class="icon el-icon-loading" v-if="autoSaveStatus === 'saving'"></i>
|
<i-ep-loading class="icon" v-if="autoSaveStatus === 'saving'" />
|
||||||
<i
|
<i-ep-check class="icon succeed" v-else-if="autoSaveStatus === 'succeed'" />
|
||||||
class="icon succeed el-icon-check"
|
|
||||||
v-else-if="autoSaveStatus === 'succeed'"
|
|
||||||
></i>
|
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { saveSurvey } from '@/management/api/survey';
|
import { mapState } from 'vuex'
|
||||||
import buildData from './buildData';
|
import { get as _get } from 'lodash-es'
|
||||||
import { mapState } from 'vuex';
|
|
||||||
import { get as _get } from 'lodash-es';
|
import { ElMessage } from 'element-plus'
|
||||||
|
import 'element-plus/theme-chalk/src/message.scss'
|
||||||
|
|
||||||
|
import { saveSurvey } from '@/management/api/survey'
|
||||||
|
import buildData from './buildData'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'save',
|
components: {},
|
||||||
|
name: 'SavePanel',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isSaving: false,
|
isSaving: false,
|
||||||
isShowAutoSave: false,
|
isShowAutoSave: false,
|
||||||
autoSaveStatus: 'succeed',
|
autoSaveStatus: 'succeed'
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState({
|
...mapState({
|
||||||
schemaUpdateTime: (state) => _get(state, 'edit.schemaUpdateTime'),
|
schemaUpdateTime: (state) => _get(state, 'edit.schemaUpdateTime')
|
||||||
}),
|
}),
|
||||||
saveText() {
|
saveText() {
|
||||||
const statusMap = {
|
const statusMap = {
|
||||||
saving: '保存中',
|
saving: '保存中',
|
||||||
succeed: '保存成功',
|
succeed: '保存成功',
|
||||||
failed: '保存失败',
|
failed: '保存失败'
|
||||||
};
|
}
|
||||||
return statusMap[this.autoSaveStatus];
|
return statusMap[this.autoSaveStatus]
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
schemaUpdateTime() {
|
schemaUpdateTime() {
|
||||||
this.triggerAutoSave();
|
this.triggerAutoSave()
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
triggerAutoSave() {
|
triggerAutoSave() {
|
||||||
if (this.autoSaveStatus === 'saving') {
|
if (this.autoSaveStatus === 'saving') {
|
||||||
// 正在调用接口
|
// 正在调用接口
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.triggerAutoSave();
|
this.triggerAutoSave()
|
||||||
}, 1000);
|
}, 1000)
|
||||||
} else {
|
} else {
|
||||||
if (this.timer) {
|
if (this.timer) {
|
||||||
clearTimeout(this.timer);
|
clearTimeout(this.timer)
|
||||||
}
|
}
|
||||||
this.timer = setTimeout(() => {
|
this.timer = setTimeout(() => {
|
||||||
this.autoSaveStatus = 'saving';
|
this.autoSaveStatus = 'saving'
|
||||||
this.isShowAutoSave = true;
|
this.isShowAutoSave = true
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.saveData()
|
this.saveData()
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
this.autoSaveStatus = 'succeed';
|
this.autoSaveStatus = 'succeed'
|
||||||
} else {
|
} else {
|
||||||
this.autoSaveStatus = 'failed';
|
this.autoSaveStatus = 'failed'
|
||||||
}
|
}
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.isShowAutoSave = false;
|
this.isShowAutoSave = false
|
||||||
this.timer = null;
|
this.timer = null
|
||||||
}, 300);
|
}, 300)
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
this.timer = null;
|
this.timer = null
|
||||||
this.autoSaveStatus = 'failed';
|
this.autoSaveStatus = 'failed'
|
||||||
this.isShowAutoSave = true;
|
this.isShowAutoSave = true
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
}, 2000);
|
}, 2000)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async saveData() {
|
async saveData() {
|
||||||
const saveData = buildData(this.$store.state.edit.schema);
|
const saveData = buildData(this.$store.state.edit.schema)
|
||||||
if (!saveData.surveyId) {
|
if (!saveData.surveyId) {
|
||||||
this.$message.error('未获取到问卷id');
|
ElMessage.error('未获取到问卷id')
|
||||||
return null;
|
return null
|
||||||
}
|
}
|
||||||
const res = await saveSurvey(saveData);
|
const res = await saveSurvey(saveData)
|
||||||
return res;
|
return res
|
||||||
},
|
},
|
||||||
async onSave() {
|
async onSave() {
|
||||||
if (this.isSaving) {
|
if (this.isSaving) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
this.isShowAutoSave = false;
|
this.isShowAutoSave = false
|
||||||
try {
|
try {
|
||||||
this.isSaving = true;
|
this.isSaving = true
|
||||||
const res = await this.saveData();
|
const res = await this.saveData()
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
this.$message.success('保存成功');
|
ElMessage.success('保存成功')
|
||||||
} else {
|
} else {
|
||||||
this.$message.error(res.errmsg);
|
ElMessage.error(res.errmsg)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.$message.error('保存问卷失败');
|
ElMessage.error('保存问卷失败')
|
||||||
} finally {
|
} finally {
|
||||||
this.isSaving = false;
|
this.isSaving = false
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import url('@/management/styles/edit-btn.scss');
|
@import url('@/management/styles/edit-btn.scss');
|
||||||
|
|
@ -1,22 +1,22 @@
|
|||||||
import { pick as _pick, get as _get } from 'lodash-es';
|
import { pick as _pick, get as _get } from 'lodash-es'
|
||||||
|
|
||||||
// 生成需要保存到接口的数据
|
// 生成需要保存到接口的数据
|
||||||
export default function (schema) {
|
export default function (schema) {
|
||||||
const surveyId = _get(schema, 'metaData._id');
|
const surveyId = _get(schema, 'metaData._id')
|
||||||
const configData = _pick(schema, [
|
const configData = _pick(schema, [
|
||||||
'bannerConf',
|
'bannerConf',
|
||||||
'baseConf',
|
'baseConf',
|
||||||
'bottomConf',
|
'bottomConf',
|
||||||
'skinConf',
|
'skinConf',
|
||||||
'submitConf',
|
'submitConf',
|
||||||
'questionDataList',
|
'questionDataList'
|
||||||
]);
|
])
|
||||||
configData.dataConf = {
|
configData.dataConf = {
|
||||||
dataList: configData.questionDataList,
|
dataList: configData.questionDataList
|
||||||
};
|
}
|
||||||
delete configData.questionDataList;
|
delete configData.questionDataList
|
||||||
return {
|
return {
|
||||||
surveyId,
|
surveyId,
|
||||||
configData,
|
configData
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,72 +0,0 @@
|
|||||||
<template>
|
|
||||||
<el-button
|
|
||||||
type="primary"
|
|
||||||
:loading="isPublishing"
|
|
||||||
class="publish-btn"
|
|
||||||
@click="onPublish"
|
|
||||||
>
|
|
||||||
发布
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
import { mapState } from 'vuex';
|
|
||||||
import { publishSurvey, saveSurvey } from '@/management/api/survey';
|
|
||||||
import buildData from './buildData';
|
|
||||||
import { get as _get } from 'lodash-es';
|
|
||||||
export default {
|
|
||||||
name: 'publish',
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
isPublishing: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapState({
|
|
||||||
surveyId: (state) => _get(state, 'edit.surveyId'),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async onPublish() {
|
|
||||||
const saveData = buildData(this.$store.state.edit.schema);
|
|
||||||
if (!saveData.surveyId) {
|
|
||||||
this.$message.error('未获取到问卷id');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.isPublishing) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
this.isPublishing = true;
|
|
||||||
const saveRes = await saveSurvey(saveData);
|
|
||||||
if (saveRes.code !== 200) {
|
|
||||||
this.$message.error(saveRes.errmsg || '问卷保存失败');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const publishRes = await publishSurvey({ surveyId: this.surveyId });
|
|
||||||
if (publishRes.code === 200) {
|
|
||||||
this.$message.success('发布成功');
|
|
||||||
this.$store.dispatch('edit/getSchemaFromRemote');
|
|
||||||
this.$router.push({
|
|
||||||
name: 'publishResultPage',
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.$message.error(`发布失败 ${publishRes.errmsg}`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
this.$message.error(`发布失败`);
|
|
||||||
} finally {
|
|
||||||
this.isPublishing = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.publish-btn {
|
|
||||||
width: 100px;
|
|
||||||
font-size: 14px;
|
|
||||||
height: 36px;
|
|
||||||
line-height: 36px;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -4,16 +4,18 @@
|
|||||||
<span>返回</span>
|
<span>返回</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'back',
|
name: 'BackPanel',
|
||||||
methods: {
|
methods: {
|
||||||
onBack() {
|
onBack() {
|
||||||
this.$router.go(-1);
|
window.open('/survey', '_self')
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.back-btn {
|
.back-btn {
|
||||||
height: 100%;
|
height: 100%;
|
@ -1,31 +1,34 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<template v-for="btnItem in btnList">
|
<template v-for="btnItem in btnList" :key="btnItem.key">
|
||||||
<router-link
|
<router-link
|
||||||
class="navbar-btn"
|
|
||||||
:key="btnItem.key"
|
|
||||||
:to="{ name: btnItem.router }"
|
:to="{ name: btnItem.router }"
|
||||||
tag="div"
|
|
||||||
replace
|
replace
|
||||||
v-slot="{ href, route, navigate, isActive, isExactActive }"
|
v-slot="{ href, navigate, isActive, isExactActive }"
|
||||||
custom
|
custom
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
:class="[
|
:class="[
|
||||||
(isActive && btnItem.key === 'skinsettings' ) || isExactActive ? 'router-link-exact-active' : '']"
|
'navbar-btn',
|
||||||
>
|
(isActive && btnItem.key === 'skinsettings') || isExactActive
|
||||||
<i class="iconfont" :class="[btnItem.icon]"></i>
|
? 'router-link-exact-active'
|
||||||
<a :href="href" @click="navigate"><span>{{ btnItem.text }}</span></a>
|
: ''
|
||||||
<!-- <span>{{ btnItem.text }}</span> -->
|
]"
|
||||||
</div>
|
>
|
||||||
|
<i class="iconfont" :class="[btnItem.icon]"></i>
|
||||||
|
<a :href="href" @click="navigate"
|
||||||
|
><span>{{ btnItem.text }}</span></a
|
||||||
|
>
|
||||||
|
<!-- <span>{{ btnItem.text }}</span> -->
|
||||||
|
</div>
|
||||||
</router-link>
|
</router-link>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'pageNav',
|
name: 'NavPanel',
|
||||||
props: {},
|
props: {},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -35,27 +38,28 @@ export default {
|
|||||||
text: '问卷编辑',
|
text: '问卷编辑',
|
||||||
router: 'QuestionEditIndex',
|
router: 'QuestionEditIndex',
|
||||||
key: 'edit',
|
key: 'edit',
|
||||||
next: true,
|
next: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'icon-wenjuanshezhi',
|
icon: 'icon-wenjuanshezhi',
|
||||||
text: '问卷设置',
|
text: '问卷设置',
|
||||||
router: 'QuestionEditSetting',
|
router: 'QuestionEditSetting',
|
||||||
key: 'settings',
|
key: 'settings',
|
||||||
next: true,
|
next: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'icon-yangshishezhi',
|
icon: 'icon-yangshishezhi',
|
||||||
text: '皮肤设置',
|
text: '皮肤设置',
|
||||||
router: 'QuestionSkinSetting',
|
router: 'QuestionSkinSetting',
|
||||||
key: 'skinsettings',
|
key: 'skinsettings',
|
||||||
next: true,
|
next: true
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
};
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.content {
|
.content {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -67,7 +71,7 @@ export default {
|
|||||||
color: #92949d;
|
color: #92949d;
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
a{
|
a {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
&.router-link-exact-active {
|
&.router-link-exact-active {
|
@ -3,17 +3,19 @@
|
|||||||
{{ title }}
|
{{ title }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'pageTitle',
|
name: 'TitlePanel',
|
||||||
props: {
|
props: {
|
||||||
title: {
|
title: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: ''
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.title {
|
.title {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
@ -1,43 +1,44 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-tabs type="border-card" v-model="tabSelected" class="tab-box">
|
<el-tabs type="border-card" v-model="tabSelected" class="tab-box">
|
||||||
<el-tab-pane label="题型选择">
|
<el-tab-pane label="题型选择">
|
||||||
<type-list />
|
<TypeList />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="题目大纲">
|
<el-tab-pane label="题目大纲">
|
||||||
<catalog />
|
<QuestionCatalog />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import typeList from './components/typeList';
|
import TypeList from './components/TypeList.vue'
|
||||||
import catalog from './components/catalog.vue';
|
import QuestionCatalog from './components/QuestionCatalog.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'EditLeftTabPanel',
|
name: 'CatalogPanel',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
tabSelected: '0',
|
tabSelected: '0'
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
typeList,
|
TypeList,
|
||||||
catalog,
|
QuestionCatalog
|
||||||
},
|
},
|
||||||
methods: {},
|
methods: {}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
.tab-box {
|
.tab-box {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
border: none;
|
border: none;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
::v-deep .el-tabs__nav {
|
:deep(.el-tabs__nav) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
::v-deep .el-tabs__item {
|
:deep(.el-tabs__item) {
|
||||||
width: 50%;
|
width: 50%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
@ -2,13 +2,13 @@
|
|||||||
<div class="main-operation" @click="onMainClick" ref="mainOperation">
|
<div class="main-operation" @click="onMainClick" ref="mainOperation">
|
||||||
<div class="operation-wrapper" ref="operationWrapper">
|
<div class="operation-wrapper" ref="operationWrapper">
|
||||||
<div class="box content" ref="box">
|
<div class="box content" ref="box">
|
||||||
<mainTitle
|
<MainTitle
|
||||||
:bannerConf="bannerConf"
|
:bannerConf="bannerConf"
|
||||||
:is-selected="currentEditOne === 'mainTitle'"
|
:is-selected="currentEditOne === 'mainTitle'"
|
||||||
@select="onSelectEditOne('mainTitle')"
|
@select="onSelectEditOne('mainTitle')"
|
||||||
@change="handleChange"
|
@change="handleChange"
|
||||||
/>
|
/>
|
||||||
<materialGroup
|
<MaterialGroup
|
||||||
:current-edit-one="parseInt(currentEditOne)"
|
:current-edit-one="parseInt(currentEditOne)"
|
||||||
:questionDataList="questionDataList"
|
:questionDataList="questionDataList"
|
||||||
@select="onSelectEditOne"
|
@select="onSelectEditOne"
|
||||||
@ -16,7 +16,7 @@
|
|||||||
@changeSeq="onQuestionOperation"
|
@changeSeq="onQuestionOperation"
|
||||||
ref="materialGroup"
|
ref="materialGroup"
|
||||||
/>
|
/>
|
||||||
<submit
|
<SubmitButton
|
||||||
:submit-conf="submitConf"
|
:submit-conf="submitConf"
|
||||||
:skin-conf="skinConf"
|
:skin-conf="skinConf"
|
||||||
:is-selected="currentEditOne === 'submit'"
|
:is-selected="currentEditOne === 'submit'"
|
||||||
@ -28,25 +28,23 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import materialGroup from '@/management/pages/edit/components/materialGroup.vue';
|
import MaterialGroup from '@/management/pages/edit/components/MaterialGroup.vue'
|
||||||
import mainTitle from '@/management/pages/edit/components/mainTitle.vue';
|
import MainTitle from '@/management/pages/edit/components/MainTitle.vue'
|
||||||
import submit from '@/management/pages/edit/components/submit.vue';
|
import SubmitButton from '@/management/pages/edit/components/SubmitButton.vue'
|
||||||
import logo from '@/management/pages/edit/components/logo.vue';
|
import { mapState, mapGetters } from 'vuex'
|
||||||
import { mapState, mapGetters } from 'vuex';
|
import { get as _get } from 'lodash-es'
|
||||||
import { get as _get } from 'lodash-es';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'mainOperation',
|
name: 'PreviewPanel',
|
||||||
components: {
|
components: {
|
||||||
mainTitle,
|
MainTitle,
|
||||||
submit,
|
SubmitButton,
|
||||||
logo,
|
MaterialGroup
|
||||||
materialGroup,
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isAnimating: false,
|
isAnimating: false
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState({
|
...mapState({
|
||||||
@ -55,99 +53,91 @@ export default {
|
|||||||
skinConf: (state) => _get(state, 'edit.schema.skinConf'),
|
skinConf: (state) => _get(state, 'edit.schema.skinConf'),
|
||||||
bottomConf: (state) => _get(state, 'edit.schema.bottomConf'),
|
bottomConf: (state) => _get(state, 'edit.schema.bottomConf'),
|
||||||
questionDataList: (state) => _get(state, 'edit.schema.questionDataList'),
|
questionDataList: (state) => _get(state, 'edit.schema.questionDataList'),
|
||||||
currentEditOne: (state) => _get(state, 'edit.currentEditOne'),
|
currentEditOne: (state) => _get(state, 'edit.currentEditOne')
|
||||||
}),
|
}),
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
currentEditKey: 'edit/currentEditKey',
|
currentEditKey: 'edit/currentEditKey'
|
||||||
}),
|
}),
|
||||||
autoScrollData() {
|
autoScrollData() {
|
||||||
return {
|
return {
|
||||||
currentEditOne: this.currentEditOne,
|
currentEditOne: this.currentEditOne,
|
||||||
len: this.questionDataList.length,
|
len: this.questionDataList.length
|
||||||
};
|
}
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
skinConf: {
|
skinConf: {
|
||||||
handler (skinConf) {
|
handler(skinConf) {
|
||||||
const { themeConf, backgroundConf, contentConf} = skinConf
|
const { themeConf, backgroundConf, contentConf } = skinConf
|
||||||
const root = document.documentElement;
|
const root = document.documentElement
|
||||||
if(themeConf?.color) {
|
if (themeConf?.color) {
|
||||||
root.style.setProperty('--primary-color', themeConf?.color); // 设置主题颜色
|
root.style.setProperty('--primary-color', themeConf?.color) // 设置主题颜色
|
||||||
}
|
}
|
||||||
if(backgroundConf?.color) {
|
if (backgroundConf?.color) {
|
||||||
root.style.setProperty('--primary-background-color', backgroundConf?.color); // 设置背景颜色
|
root.style.setProperty('--primary-background-color', backgroundConf?.color) // 设置背景颜色
|
||||||
}
|
}
|
||||||
if(contentConf?.opacity) {
|
if (contentConf?.opacity) {
|
||||||
root.style.setProperty('--opacity', contentConf?.opacity/100); // 设置全局透明度
|
root.style.setProperty('--opacity', contentConf?.opacity / 100) // 设置全局透明度
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
immediate: true, // 立即触发回调函数
|
immediate: true, // 立即触发回调函数
|
||||||
deep: true
|
deep: true
|
||||||
},
|
},
|
||||||
autoScrollData(newVal) {
|
autoScrollData(newVal) {
|
||||||
const { currentEditOne } = newVal;
|
const { currentEditOne } = newVal
|
||||||
if (typeof currentEditOne === 'number') {
|
if (typeof currentEditOne === 'number') {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// if (this.isAnimating) {
|
const field = this.questionDataList?.[currentEditOne]?.field
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
const field = this.questionDataList?.[currentEditOne]?.field;
|
|
||||||
if (field) {
|
if (field) {
|
||||||
const questionComp =
|
const questionModule = this.$refs.materialGroup.getQuestionRefByField(field)
|
||||||
this.$refs.materialGroup.getQuestionRefByField(field);
|
if (questionModule && questionModule.$el) {
|
||||||
if (questionComp && questionComp.$el) {
|
questionModule.$el.scrollIntoView({
|
||||||
questionComp.$el.scrollIntoView({
|
behavior: 'smooth'
|
||||||
behavior: 'smooth',
|
})
|
||||||
});
|
|
||||||
// this.isAnimating = true;
|
|
||||||
// const maxScrollTop = this.$refs.box.clientHeight - this.$refs.operationWrapper.clientHeight
|
|
||||||
// const targetVal = Math.min(questionComp.$el.offsetTop - this.$refs.operationWrapper.clientHeight / 2, maxScrollTop)
|
|
||||||
// this.animate(this.$refs.operationWrapper, 'scrollTop', targetVal)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 0);
|
}, 0)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
animate(dom, property, targetValue) {
|
animate(dom, property, targetValue) {
|
||||||
const origin = dom[property];
|
const origin = dom[property]
|
||||||
const subVal = targetValue - origin;
|
const subVal = targetValue - origin
|
||||||
|
|
||||||
const flag = subVal < 0 ? -1 : 1;
|
const flag = subVal < 0 ? -1 : 1
|
||||||
|
|
||||||
const step = flag * 50;
|
const step = flag * 50
|
||||||
|
|
||||||
const totalCount = Math.floor(subVal / step) + 1;
|
const totalCount = Math.floor(subVal / step) + 1
|
||||||
|
|
||||||
let runCount = 0;
|
let runCount = 0
|
||||||
const run = () => {
|
const run = () => {
|
||||||
dom[property] += step;
|
dom[property] += step
|
||||||
runCount++;
|
runCount++
|
||||||
if (runCount < totalCount) {
|
if (runCount < totalCount) {
|
||||||
requestAnimationFrame(run);
|
requestAnimationFrame(run)
|
||||||
} else {
|
} else {
|
||||||
this.isAnimating = false;
|
this.isAnimating = false
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
requestAnimationFrame(run);
|
requestAnimationFrame(run)
|
||||||
},
|
},
|
||||||
async onSelectEditOne(currentEditOne) {
|
async onSelectEditOne(currentEditOne) {
|
||||||
this.$store.commit('edit/setCurrentEditOne', currentEditOne);
|
this.$store.commit('edit/setCurrentEditOne', currentEditOne)
|
||||||
},
|
},
|
||||||
handleChange(data) {
|
handleChange(data) {
|
||||||
if (this.currentEditOne === null) {
|
if (this.currentEditOne === null) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
const { key, value } = data;
|
const { key, value } = data
|
||||||
const resultKey = `${this.currentEditKey}.${key}`;
|
const resultKey = `${this.currentEditKey}.${key}`
|
||||||
this.$store.dispatch('edit/changeSchema', { key: resultKey, value });
|
this.$store.dispatch('edit/changeSchema', { key: resultKey, value })
|
||||||
},
|
},
|
||||||
onMainClick(e) {
|
onMainClick(e) {
|
||||||
if (e.target === this.$refs.mainOperation) {
|
if (e.target === this.$refs.mainOperation) {
|
||||||
this.$store.commit('edit/setCurrentEditOne', null);
|
this.$store.commit('edit/setCurrentEditOne', null)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onQuestionOperation(data) {
|
onQuestionOperation(data) {
|
||||||
@ -155,21 +145,21 @@ export default {
|
|||||||
case 'move':
|
case 'move':
|
||||||
this.$store.dispatch('edit/moveQuestion', {
|
this.$store.dispatch('edit/moveQuestion', {
|
||||||
index: data.index,
|
index: data.index,
|
||||||
range: data.range,
|
range: data.range
|
||||||
});
|
})
|
||||||
break;
|
break
|
||||||
case 'delete':
|
case 'delete':
|
||||||
this.$store.dispatch('edit/deleteQuestion', { index: data.index });
|
this.$store.dispatch('edit/deleteQuestion', { index: data.index })
|
||||||
break;
|
break
|
||||||
case 'copy':
|
case 'copy':
|
||||||
this.$store.dispatch('edit/copyQuestion', { index: data.index });
|
this.$store.dispatch('edit/copyQuestion', { index: data.index })
|
||||||
break;
|
break
|
||||||
default:
|
default:
|
||||||
break;
|
break
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
@ -7,7 +7,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="setter-title">{{ currentEditMeta?.title || '' }}</div>
|
<div class="setter-title">{{ currentEditMeta?.title || '' }}</div>
|
||||||
<setterField
|
<SetterField
|
||||||
class="question-config-form"
|
class="question-config-form"
|
||||||
:form-config-list="formConfigList"
|
:form-config-list="formConfigList"
|
||||||
:module-config="moduleConfig"
|
:module-config="moduleConfig"
|
||||||
@ -18,40 +18,41 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import setterField from '@/management/pages/edit/components/setterField.vue';
|
import SetterField from '@/management/pages/edit/components/SetterField.vue'
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'setterWrapper',
|
name: 'SetterPanel',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
tabSelected: '0',
|
tabSelected: '0'
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
currentEditOne() {
|
currentEditOne() {
|
||||||
return this.$store.state?.edit?.currentEditOne;
|
return this.$store.state?.edit?.currentEditOne
|
||||||
},
|
},
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
formConfigList: 'edit/formConfigList',
|
formConfigList: 'edit/formConfigList',
|
||||||
moduleConfig: 'edit/moduleConfig',
|
moduleConfig: 'edit/moduleConfig',
|
||||||
currentEditKey: 'edit/currentEditKey',
|
currentEditKey: 'edit/currentEditKey',
|
||||||
currentEditMeta: 'edit/currentEditMeta',
|
currentEditMeta: 'edit/currentEditMeta'
|
||||||
}),
|
})
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
setterField,
|
SetterField
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onFormChange(data) {
|
onFormChange(data) {
|
||||||
const { key, value } = data;
|
const { key, value } = data
|
||||||
const resultKey = `${this.currentEditKey}.${key}`;
|
const resultKey = `${this.currentEditKey}.${key}`
|
||||||
this.$store.dispatch('edit/changeSchema', { key: resultKey, value });
|
this.$store.dispatch('edit/changeSchema', { key: resultKey, value })
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
.setter-wrapper {
|
.setter-wrapper {
|
||||||
width: 360px;
|
width: 360px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -88,6 +89,6 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.question-config-form {
|
.question-config-form {
|
||||||
padding: 30px 20px 50px 20px!important;
|
padding: 30px 20px 50px 20px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
@ -10,34 +10,35 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'QuestionCatalogItem',
|
name: 'CatalogItem',
|
||||||
data() {
|
data() {
|
||||||
return {};
|
return {}
|
||||||
},
|
},
|
||||||
computed: {},
|
computed: {},
|
||||||
props: {
|
props: {
|
||||||
title: {
|
title: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: ''
|
||||||
},
|
},
|
||||||
indexNumber: {
|
indexNumber: {
|
||||||
type: [String, Number],
|
type: [String, Number],
|
||||||
default: '',
|
default: ''
|
||||||
},
|
},
|
||||||
showIndex: {
|
showIndex: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
components: {},
|
components: {},
|
||||||
methods: {
|
methods: {
|
||||||
onSelect() {
|
onSelect() {
|
||||||
this.$emit('select');
|
this.$emit('select')
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
.question-catalog-item {
|
.question-catalog-item {
|
||||||
position: relative;
|
position: relative;
|
||||||
border-top: 1px solid #ebebeb;
|
border-top: 1px solid #ebebeb;
|
@ -1,12 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="question-catalog-wrapper">
|
<div class="question-catalog-wrapper">
|
||||||
<draggable :list="renderData" :options="dragOptions" @end="onDragEnd">
|
<draggable
|
||||||
<template v-for="(catalogItem, index) in renderData">
|
:list="renderData"
|
||||||
<catalogItem
|
@end="onDragEnd"
|
||||||
:key="catalogItem.field"
|
itemKey="field"
|
||||||
:title="catalogItem.title"
|
handle=".draggHandle"
|
||||||
:indexNumber="catalogItem.indexNumber"
|
host-class="catalog-item-ghost"
|
||||||
:showIndex="catalogItem.showIndex"
|
>
|
||||||
|
<template #item="{ element, index }">
|
||||||
|
<CatalogItem
|
||||||
|
:title="element.title"
|
||||||
|
:indexNumber="element.indexNumber"
|
||||||
|
:showIndex="element.showIndex"
|
||||||
@select="onSelect(index)"
|
@select="onSelect(index)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@ -15,48 +20,43 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import draggable from 'vuedraggable';
|
import draggable from 'vuedraggable'
|
||||||
import catalogItem from './catalogItem';
|
import CatalogItem from './CatalogItem.vue'
|
||||||
import { filterQuestionPreviewData } from '@/management/utils/index';
|
import { filterQuestionPreviewData } from '@/management/utils/index'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'QuestionCatalog',
|
name: 'QuestionCatalog',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {}
|
||||||
dragOptions: {
|
|
||||||
handle: '.draggHandle',
|
|
||||||
ghostClass: 'catalog-item-ghost',
|
|
||||||
dragClass: 'catalog-item-dragging',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
questionDataList() {
|
questionDataList() {
|
||||||
return this.$store.state.edit.schema.questionDataList;
|
return this.$store.state.edit.schema.questionDataList
|
||||||
},
|
},
|
||||||
renderData() {
|
renderData() {
|
||||||
return filterQuestionPreviewData(this.questionDataList) || [];
|
return filterQuestionPreviewData(this.questionDataList) || []
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
draggable,
|
draggable,
|
||||||
catalogItem,
|
CatalogItem
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onDragEnd(data) {
|
onDragEnd(data) {
|
||||||
const { newIndex, oldIndex } = data;
|
const { newIndex, oldIndex } = data
|
||||||
this.$store.dispatch('edit/moveQuestion', {
|
this.$store.dispatch('edit/moveQuestion', {
|
||||||
index: oldIndex,
|
index: oldIndex,
|
||||||
range: newIndex - oldIndex,
|
range: newIndex - oldIndex
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
onSelect(index) {
|
onSelect(index) {
|
||||||
this.$store.commit('edit/setCurrentEditOne', index);
|
this.$store.commit('edit/setCurrentEditOne', index)
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
.question-catalog-wrapper {
|
.question-catalog-wrapper {
|
||||||
padding-bottom: 400px; // 考试题有个上拉框会盖住,改成和题型一致的
|
padding-bottom: 400px; // 考试题有个上拉框会盖住,改成和题型一致的
|
||||||
.catelog-first-page {
|
.catelog-first-page {
|
@ -0,0 +1,131 @@
|
|||||||
|
<template>
|
||||||
|
<el-collapse class="question-type-wrapper" v-model="activeNames">
|
||||||
|
<el-collapse-item
|
||||||
|
v-for="(item, index) of questionMenuConfig"
|
||||||
|
:title="item.title"
|
||||||
|
:name="index"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<div class="questiontype-list">
|
||||||
|
<el-popover
|
||||||
|
v-for="(item, index) in item.questionList"
|
||||||
|
:key="item.type"
|
||||||
|
placement="right"
|
||||||
|
trigger="hover"
|
||||||
|
:popper-class="'qtype-popper-' + (index % 3)"
|
||||||
|
:popper-style="{ width: '369px' }"
|
||||||
|
>
|
||||||
|
<img :src="item.snapshot" width="345px" />
|
||||||
|
<template #reference>
|
||||||
|
<div :key="item.type" class="qtopic-item" @click="onQuestionType({ type: item.type })">
|
||||||
|
<i class="iconfont" :class="['icon-' + item.icon]"></i>
|
||||||
|
<p class="text">{{ item.title }}</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-popover>
|
||||||
|
</div>
|
||||||
|
</el-collapse-item>
|
||||||
|
</el-collapse>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import questionLoader from '@/materials/questions/questionLoader'
|
||||||
|
|
||||||
|
import questionMenuConfig, { questionTypeList } from '@/management/config/questionMenuConfig'
|
||||||
|
import { getQuestionByType } from '@/management/utils/index'
|
||||||
|
import { useStore } from 'vuex'
|
||||||
|
import { get as _get } from 'lodash-es'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
|
const activeNames = ref([0, 1])
|
||||||
|
|
||||||
|
const store = useStore()
|
||||||
|
const questionDataList = computed(() => _get(store, 'state.edit.schema.questionDataList'))
|
||||||
|
|
||||||
|
questionLoader.init({
|
||||||
|
typeList: questionTypeList.map((item) => item.type)
|
||||||
|
})
|
||||||
|
|
||||||
|
const onQuestionType = ({ type }) => {
|
||||||
|
const fields = questionDataList.value.map((item) => item.field)
|
||||||
|
const currentEditOne = _get(store, 'state.edit.currentEditOne')
|
||||||
|
const index =
|
||||||
|
typeof currentEditOne === 'number' ? currentEditOne + 1 : questionDataList.value.length
|
||||||
|
const newQuestion = getQuestionByType(type, fields)
|
||||||
|
newQuestion.title = newQuestion.title = `标题${index + 1}`
|
||||||
|
if (type === 'vote') {
|
||||||
|
newQuestion.innerType = 'radio'
|
||||||
|
}
|
||||||
|
store.dispatch('edit/addQuestion', { question: newQuestion, index })
|
||||||
|
store.commit('edit/setCurrentEditOne', index)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.question-type-wrapper {
|
||||||
|
padding: 0 20px;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
:deep(.el-collapse-item__header) {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #4a4c5b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.questiontype-list {
|
||||||
|
// height: 100%;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
// padding-bottom: 25px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
overflow-x: hidden;
|
||||||
|
scrollbar-width: none;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
border-top: none;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qtopic-item {
|
||||||
|
height: 77px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 12px;
|
||||||
|
border: 1px solid $disable-color;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $primary-color-light;
|
||||||
|
border: 1px solid $primary-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconfont::before {
|
||||||
|
font-size: 21px;
|
||||||
|
color: $font-color-title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.qtype-popper-0 {
|
||||||
|
transform: translateX(183px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.qtype-popper-1 {
|
||||||
|
transform: translateX(106px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.qtype-popper-2 {
|
||||||
|
transform: translateX(30px);
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,162 +0,0 @@
|
|||||||
<template>
|
|
||||||
<el-collapse class="question-type-wrapper" v-model="activeNames">
|
|
||||||
<el-collapse-item
|
|
||||||
v-for="(item, index) of questionMenuConfig"
|
|
||||||
:title="item.title"
|
|
||||||
:name="index"
|
|
||||||
:key="index"
|
|
||||||
>
|
|
||||||
<draggable
|
|
||||||
class="questiontype-list item-wrapper"
|
|
||||||
:options="{
|
|
||||||
element: 'li',
|
|
||||||
sort: false,
|
|
||||||
group: { name: 'previewList', pull: 'clone', put: false },
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<el-popover
|
|
||||||
v-for="(item, index) in item.questionList"
|
|
||||||
:key="item.type"
|
|
||||||
placement="right"
|
|
||||||
trigger="hover"
|
|
||||||
:popper-class="'qtype-popper-' + (index % 3)"
|
|
||||||
>
|
|
||||||
<img :src="item.snapshot" width="345px" />
|
|
||||||
<div
|
|
||||||
slot="reference"
|
|
||||||
:key="item.type"
|
|
||||||
class="qtopic-item"
|
|
||||||
@click="onQuestionType({ type: item.type })"
|
|
||||||
>
|
|
||||||
<i class="iconfont" :class="['icon-' + item.icon]"></i>
|
|
||||||
<p class="text">{{ item.title }}</p>
|
|
||||||
</div>
|
|
||||||
</el-popover>
|
|
||||||
</draggable>
|
|
||||||
</el-collapse-item>
|
|
||||||
</el-collapse>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import questionLoader from '@/materials/questions/questionLoader';
|
|
||||||
import draggable from 'vuedraggable';
|
|
||||||
|
|
||||||
import questionMenuConfig, {
|
|
||||||
questionTypeList,
|
|
||||||
} from '@/management/config/questionMenuConfig';
|
|
||||||
import { getQuestionByType } from '@/management/utils/index';
|
|
||||||
import { mapState, mapActions } from 'vuex';
|
|
||||||
import { get as _get } from 'lodash-es';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'QuestionTypeList',
|
|
||||||
components: {
|
|
||||||
draggable,
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
activeNames: [0, 1],
|
|
||||||
questionMenuConfig,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapState({
|
|
||||||
questionDataList: (state) => _get(state, 'edit.schema.questionDataList'),
|
|
||||||
currentEditOne: (state) => _get(state, 'edit.currentEditOne'),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
async created() {
|
|
||||||
await questionLoader.init({
|
|
||||||
typeList: questionTypeList.map((item) => item.type),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
...mapActions({
|
|
||||||
addQuestion: 'edit/addQuestion',
|
|
||||||
}),
|
|
||||||
onQuestionType({ type }) {
|
|
||||||
const questionDataList = this.questionDataList || [];
|
|
||||||
const fields = questionDataList.map((item) => item.field);
|
|
||||||
const currentEditOne = this.currentEditOne;
|
|
||||||
const index =
|
|
||||||
typeof currentEditOne === 'number'
|
|
||||||
? currentEditOne + 1
|
|
||||||
: questionDataList.length;
|
|
||||||
const newQuestion = getQuestionByType(type, fields);
|
|
||||||
newQuestion.title = newQuestion.title = `标题${index + 1}`;
|
|
||||||
if (type === 'vote') {
|
|
||||||
newQuestion.innerType = 'radio';
|
|
||||||
}
|
|
||||||
this.addQuestion({ question: newQuestion, index });
|
|
||||||
this.$store.commit('edit/setCurrentEditOne', index);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
|
||||||
.question-type-wrapper {
|
|
||||||
padding: 0 20px;
|
|
||||||
border: none;
|
|
||||||
::v-deep .el-collapse-item__header {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #4a4c5b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.questiontype-list {
|
|
||||||
// height: 100%;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
// padding-bottom: 25px;
|
|
||||||
overflow-y: scroll;
|
|
||||||
overflow-x: hidden;
|
|
||||||
scrollbar-width: none;
|
|
||||||
-ms-overflow-style: none;
|
|
||||||
border-top: none;
|
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qtopic-item {
|
|
||||||
height: 77px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 12px;
|
|
||||||
border: 1px solid $disable-color;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: $primary-color-light;
|
|
||||||
border: 1px solid $primary-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.iconfont::before {
|
|
||||||
font-size: 21px;
|
|
||||||
color: $font-color-title;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<style lang="scss" rel="stylesheet/scss">
|
|
||||||
.qtype-popper-0 {
|
|
||||||
transform: translateX(183px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.qtype-popper-1 {
|
|
||||||
transform: translateX(106px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.qtype-popper-2 {
|
|
||||||
transform: translateX(30px);
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -10,22 +10,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<el-form
|
<el-form
|
||||||
class="question-config-form"
|
class="question-config-form"
|
||||||
size="small"
|
|
||||||
label-position="left"
|
label-position="left"
|
||||||
label-width="200px"
|
label-width="200px"
|
||||||
@submit.native.prevent
|
@submit.prevent
|
||||||
>
|
>
|
||||||
<template v-for="(item, index) in form.formList">
|
<template v-for="(item, index) in form.formList">
|
||||||
<FormItem
|
<FormItem
|
||||||
|
v-if="item.type && !item.hidden && Boolean(register[item.type])"
|
||||||
:key="index"
|
:key="index"
|
||||||
v-if="
|
|
||||||
item.type && !item.hidden && Boolean(registerd[item.type])
|
|
||||||
"
|
|
||||||
:form-config="item"
|
:form-config="item"
|
||||||
:style="item.style"
|
:style="item.style"
|
||||||
>
|
>
|
||||||
<Component
|
<Component
|
||||||
v-if="Boolean(registerd[item.type])"
|
v-if="Boolean(register[item.type])"
|
||||||
:is="item.type"
|
:is="item.type"
|
||||||
:module-config="form.dataConfig"
|
:module-config="form.dataConfig"
|
||||||
:form-config="item"
|
:form-config="item"
|
||||||
@ -39,106 +36,95 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import baseConfig from './config/baseConfig';
|
import baseConfig from './config/baseConfig'
|
||||||
import baseFormConfig from './config/baseFormConfig';
|
import baseFormConfig from './config/baseFormConfig'
|
||||||
import FormItem from '@/materials/setters/widgets/FormItem.vue';
|
import FormItem from '@/materials/setters/widgets/FormItem.vue'
|
||||||
import setterLoader from '@/materials/setters/setterLoader';
|
import setterLoader from '@/materials/setters/setterLoader'
|
||||||
import {
|
import { cloneDeep as _cloneDeep, isArray as _isArray, get as _get } from 'lodash-es'
|
||||||
cloneDeep as _cloneDeep,
|
|
||||||
isArray as _isArray,
|
|
||||||
get as _get,
|
|
||||||
} from 'lodash-es';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'QuestionConfig',
|
name: 'SettingPanel',
|
||||||
components: {
|
components: {
|
||||||
FormItem,
|
FormItem
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
formConfigList: [],
|
formConfigList: [],
|
||||||
registerd: {},
|
register: {}
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onFormChange(data) {
|
onFormChange(data) {
|
||||||
this.$store.dispatch('edit/changeSchema', {
|
this.$store.dispatch('edit/changeSchema', {
|
||||||
key: data.key,
|
key: data.key,
|
||||||
value: data.value,
|
value: data.value
|
||||||
});
|
})
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
allSetters() {
|
allSetters() {
|
||||||
const formList = this.formConfigList.map((item) => item.formList).flat();
|
const formList = this.formConfigList.map((item) => item.formList).flat()
|
||||||
const typeList = formList.map((item) => ({
|
const typeList = formList.map((item) => ({
|
||||||
type: item.type,
|
type: item.type,
|
||||||
path: item.path || item.type,
|
path: item.path || item.type
|
||||||
}));
|
}))
|
||||||
return typeList;
|
|
||||||
|
return typeList
|
||||||
},
|
},
|
||||||
renderData() {
|
renderData() {
|
||||||
// todo: 1、给formConfig组装value;2、新增dataConfig字段
|
// todo: 1、给formConfig组装value;2、新增dataConfig字段
|
||||||
const formConfigList = _cloneDeep(this.formConfigList);
|
const formConfigList = _cloneDeep(this.formConfigList)
|
||||||
|
|
||||||
return formConfigList.map((form) => {
|
return formConfigList.map((form) => {
|
||||||
const dataConfig = {};
|
const dataConfig = {}
|
||||||
for (const formItem of form.formList) {
|
for (const formItem of form.formList) {
|
||||||
const formKey = formItem.key ? formItem.key : formItem.keys;
|
const formKey = formItem.key ? formItem.key : formItem.keys
|
||||||
let formValue;
|
let formValue
|
||||||
if (_isArray(formKey)) {
|
if (_isArray(formKey)) {
|
||||||
formValue = [];
|
formValue = []
|
||||||
for (const key of formKey) {
|
for (const key of formKey) {
|
||||||
const val = _get(
|
const val = _get(this.$store.state.edit.schema, key, formItem.value)
|
||||||
this.$store.state.edit.schema,
|
formValue.push(val)
|
||||||
key,
|
dataConfig[key] = val
|
||||||
formItem.value
|
|
||||||
);
|
|
||||||
formValue.push(val);
|
|
||||||
dataConfig[key] = val;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
formValue = _get(
|
formValue = _get(this.$store.state.edit.schema, formKey, formItem.value)
|
||||||
this.$store.state.edit.schema,
|
dataConfig[formKey] = formValue
|
||||||
formKey,
|
|
||||||
formItem.value
|
|
||||||
);
|
|
||||||
dataConfig[formKey] = formValue;
|
|
||||||
}
|
}
|
||||||
formItem.value = formValue;
|
formItem.value = formValue
|
||||||
}
|
}
|
||||||
form.dataConfig = dataConfig;
|
form.dataConfig = dataConfig
|
||||||
return form;
|
return form
|
||||||
});
|
})
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
async created() {
|
async created() {
|
||||||
this.formConfigList = baseConfig.map((item) => {
|
this.formConfigList = baseConfig.map((item) => {
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
formList: item.formList
|
formList: item.formList.map((key) => baseFormConfig[key]).filter((config) => !!config)
|
||||||
.map((key) => baseFormConfig[key])
|
}
|
||||||
.filter((config) => !!config),
|
})
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const comps = await setterLoader.loadComponents(this.allSetters);
|
const comps = await setterLoader.loadComponents(this.allSetters)
|
||||||
for (const comp of comps) {
|
for (const comp of comps) {
|
||||||
if (!comp) {
|
if (!comp) {
|
||||||
continue;
|
continue
|
||||||
}
|
}
|
||||||
const { type, component, err } = comp;
|
const { type, component, err } = comp
|
||||||
if (!err) {
|
if (!err) {
|
||||||
const componentName = component.name;
|
const componentName = component.name
|
||||||
this.$options.components[componentName] = component;
|
this.$options.components[componentName] = component
|
||||||
this.$set(this.registerd, type, componentName);
|
this.register[type] = componentName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
.question-config {
|
.question-config {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 1080px;
|
min-width: 1080px;
|
||||||
@ -177,7 +163,7 @@ export default {
|
|||||||
&:after {
|
&:after {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 41px;
|
top: 42px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 3px;
|
height: 3px;
|
||||||
background-color: $primary-color;
|
background-color: $primary-color;
|
||||||
@ -191,7 +177,7 @@ export default {
|
|||||||
padding-top: 15px;
|
padding-top: 15px;
|
||||||
padding-right: 1rem;
|
padding-right: 1rem;
|
||||||
|
|
||||||
::v-deep .star-form.star-form_horizon .star-form-label {
|
:deep(.star-form.star-form_horizon .star-form-label) {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 3.4rem;
|
width: 3.4rem;
|
||||||
text-align: left;
|
text-align: left;
|
@ -20,13 +20,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { get as _get } from 'lodash-es';
|
import { get as _get } from 'lodash-es'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'banner',
|
name: 'BannerContent',
|
||||||
data() {
|
data() {
|
||||||
return {};
|
return {}
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
bannerConf: {
|
bannerConf: {
|
||||||
@ -34,23 +35,24 @@ export default {
|
|||||||
default: () => {}
|
default: () => {}
|
||||||
},
|
},
|
||||||
isSelected: {
|
isSelected: {
|
||||||
type: Boolean,
|
type: Boolean
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
bgImage() {
|
bgImage() {
|
||||||
return _get(this.bannerConf, 'bannerConfig.bgImage', '');
|
return _get(this.bannerConf, 'bannerConfig.bgImage', '')
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleClick() {
|
handleClick() {
|
||||||
this.$emit('select');
|
this.$emit('select')
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
components: {},
|
components: {}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
.banner-preview {
|
.banner-preview {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
@ -61,6 +63,7 @@ export default {
|
|||||||
.banner {
|
.banner {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -107,7 +110,7 @@ export default {
|
|||||||
background-color: #f6f7f9;
|
background-color: #f6f7f9;
|
||||||
box-shadow: 0 0 5px #dedede;
|
box-shadow: 0 0 5px #dedede;
|
||||||
|
|
||||||
::v-deep .w-e-text-container {
|
:deep(.w-e-text-container) {
|
||||||
background-color: #f6f7f9;
|
background-color: #f6f7f9;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,25 +4,25 @@
|
|||||||
<p class="title-msg" v-safe-html="resultText"></p>
|
<p class="title-msg" v-safe-html="resultText"></p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'OverTime',
|
name: 'OverTime',
|
||||||
props: {
|
props: {
|
||||||
moduleConfig: {
|
moduleConfig: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
resultText() {
|
resultText() {
|
||||||
return (
|
return this.moduleConfig?.submitConf?.msgContent?.msg_9001 || '问卷已过期'
|
||||||
this.moduleConfig?.submitConf?.msgContent?.msg_9001 || '问卷已过期'
|
}
|
||||||
);
|
}
|
||||||
},
|
}
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
.over-time {
|
.over-time {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: 5.5rem;
|
margin-bottom: 5.5rem;
|
@ -9,23 +9,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'Success',
|
name: 'SuccessContent',
|
||||||
props: {
|
props: {
|
||||||
moduleConfig: {
|
moduleConfig: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
successText() {
|
successText() {
|
||||||
return this.moduleConfig?.submitConf?.msgContent?.msg_200 || '';
|
return this.moduleConfig?.submitConf?.msgContent?.msg_200 || ''
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
/*成功页面跳转全屏展示浮层*/
|
/*成功页面跳转全屏展示浮层*/
|
||||||
.suc-page {
|
.suc-page {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@ -53,7 +55,7 @@ export default {
|
|||||||
margin-bottom: 0.4rem;
|
margin-bottom: 0.4rem;
|
||||||
font-size: 0.36rem;
|
font-size: 0.36rem;
|
||||||
color: #666;
|
color: #666;
|
||||||
::v-deep * {
|
:deep(*) {
|
||||||
font-size: 0.36rem;
|
font-size: 0.36rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,11 +2,11 @@ export default [
|
|||||||
{
|
{
|
||||||
title: '时间配置',
|
title: '时间配置',
|
||||||
key: 'timeConfig',
|
key: 'timeConfig',
|
||||||
formList: ['base_effectTime', 'limit_answerTime'],
|
formList: ['base_effectTime', 'limit_answerTime']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '提交限制',
|
title: '提交限制',
|
||||||
key: 'limitConfig',
|
key: 'limitConfig',
|
||||||
formList: ['limit_tLimit'],
|
formList: ['limit_tLimit']
|
||||||
},
|
}
|
||||||
];
|
]
|
||||||
|
@ -4,79 +4,22 @@ export default {
|
|||||||
keys: ['baseConf.begTime', 'baseConf.endTime'],
|
keys: ['baseConf.begTime', 'baseConf.endTime'],
|
||||||
label: '答题有效期',
|
label: '答题有效期',
|
||||||
type: 'QuestionTime',
|
type: 'QuestionTime',
|
||||||
placeholder: 'yyyy-MM-dd hh:mm:ss',
|
placeholder: 'yyyy-MM-dd hh:mm:ss'
|
||||||
// direction: 'horizon',
|
|
||||||
},
|
},
|
||||||
// base_showVote: {
|
|
||||||
// key: 'baseConf.showVoteProcess',
|
|
||||||
// label: '投票配置',
|
|
||||||
// type: 'Select',
|
|
||||||
// direction: 'horizon',
|
|
||||||
// tip: '是否实时展示投票进度',
|
|
||||||
// placement: 'top',
|
|
||||||
// options: [
|
|
||||||
// {
|
|
||||||
// label: '实时展示投票进度',
|
|
||||||
// value: 'allow',
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// label: '提交后才可以查看进度',
|
|
||||||
// value: 'notallow',
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// label: '不展示投票进度',
|
|
||||||
// value: 'never',
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
// },
|
|
||||||
// base_shortestTime: {
|
|
||||||
// key: 'baseConf.shortestTime',
|
|
||||||
// label: '最短答题时长(分钟)',
|
|
||||||
// type: 'InputNumber',
|
|
||||||
// direction: 'horizon',
|
|
||||||
// tip: '问卷仅可在所设置的时间之后才能进行提交,0为无限制',
|
|
||||||
// tipShow: true,
|
|
||||||
// placement: 'top',
|
|
||||||
// },
|
|
||||||
limit_tLimit: {
|
limit_tLimit: {
|
||||||
key: 'baseConf.tLimit',
|
key: 'baseConf.tLimit',
|
||||||
label: '问卷回收总数',
|
label: '问卷回收总数',
|
||||||
type: 'InputNumber',
|
type: 'InputNumber',
|
||||||
// direction: 'horizon',
|
|
||||||
tip: '0为无限制,此功能用于限制该问卷总提交的数据量。当数据量达到限额时,该问卷将不能继续提交',
|
tip: '0为无限制,此功能用于限制该问卷总提交的数据量。当数据量达到限额时,该问卷将不能继续提交',
|
||||||
tipShow: true,
|
tipShow: true,
|
||||||
placement: 'top',
|
placement: 'top',
|
||||||
min: 0,
|
min: 0
|
||||||
},
|
},
|
||||||
limit_answerTime: {
|
limit_answerTime: {
|
||||||
keys: ['baseConf.answerBegTime', 'baseConf.answerEndTime'],
|
keys: ['baseConf.answerBegTime', 'baseConf.answerEndTime'],
|
||||||
label: '答题时段',
|
label: '答题时段',
|
||||||
tip: '问卷仅在指定时间段内可填写',
|
tip: '问卷仅在指定时间段内可填写',
|
||||||
type: 'QuestionTimeHour',
|
type: 'QuestionTimeHour',
|
||||||
// direction: 'horizon',
|
placement: 'top'
|
||||||
placement: 'top',
|
}
|
||||||
},
|
}
|
||||||
// skin_skinColor: {
|
|
||||||
// key: 'skinConf.skinColor',
|
|
||||||
// label: '页面主题颜色',
|
|
||||||
// type: 'Select',
|
|
||||||
// direction: 'horizon',
|
|
||||||
// options: [
|
|
||||||
// {
|
|
||||||
// label: '橘色主题',
|
|
||||||
// value: '#ff8a01',
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// label: '深灰蓝主题',
|
|
||||||
// value: '#4a4c5b',
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
// },
|
|
||||||
// skin_inputBgColor: {
|
|
||||||
// key: 'skinConf.inputBgColor',
|
|
||||||
// label: '输入框底色',
|
|
||||||
// type: 'ColorInput',
|
|
||||||
// direction: 'horizon',
|
|
||||||
// maxlength: 6,
|
|
||||||
// },
|
|
||||||
};
|
|
||||||
|
@ -7,9 +7,9 @@ export default {
|
|||||||
placeholder: '提交成功',
|
placeholder: '提交成功',
|
||||||
value: '提交成功',
|
value: '提交成功',
|
||||||
labelStyle: {
|
labelStyle: {
|
||||||
'font-weight': 'bold',
|
'font-weight': 'bold'
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
OverTime: [
|
OverTime: [
|
||||||
{
|
{
|
||||||
@ -19,8 +19,8 @@ export default {
|
|||||||
placeholder: '问卷已过期',
|
placeholder: '问卷已过期',
|
||||||
value: '问卷已过期',
|
value: '问卷已过期',
|
||||||
labelStyle: {
|
labelStyle: {
|
||||||
'font-weight': 'bold',
|
'font-weight': 'bold'
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
};
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export const EDIT_STATUS_MAP = {
|
export const EDIT_STATUS_MAP = {
|
||||||
SUCCESS: 'Success',
|
SUCCESS: 'Success',
|
||||||
OVERTIME: 'OverTime',
|
OVERTIME: 'OverTime'
|
||||||
};
|
}
|
||||||
|
@ -16,40 +16,42 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapMutations } from 'vuex';
|
import { mapMutations } from 'vuex'
|
||||||
import { EDIT_STATUS_MAP } from '../enum';
|
import { EDIT_STATUS_MAP } from '../enum'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'resultConfigList',
|
name: 'CatalogPanel',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
statusList: [
|
statusList: [
|
||||||
{
|
{
|
||||||
type: EDIT_STATUS_MAP.SUCCESS,
|
type: EDIT_STATUS_MAP.SUCCESS,
|
||||||
title: '提交成功',
|
title: '提交成功',
|
||||||
previewImg: '/imgs/icons/success.webp',
|
previewImg: '/imgs/icons/success.webp'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: EDIT_STATUS_MAP.OVERTIME,
|
type: EDIT_STATUS_MAP.OVERTIME,
|
||||||
title: '问卷过期',
|
title: '问卷过期',
|
||||||
previewImg: '/imgs/icons/overtime.webp',
|
previewImg: '/imgs/icons/overtime.webp'
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
computed: {},
|
computed: {},
|
||||||
methods: {
|
methods: {
|
||||||
...mapMutations({
|
...mapMutations({
|
||||||
changeStatusPreview: 'edit/changeStatusPreview',
|
changeStatusPreview: 'edit/changeStatusPreview'
|
||||||
}),
|
}),
|
||||||
filterDisabledStatus(data) {
|
filterDisabledStatus(data) {
|
||||||
this.changeStatusPreview(data);
|
this.changeStatusPreview(data)
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
.tab-box {
|
.tab-box {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
height: 100%;
|
height: 100%;
|
@ -2,46 +2,44 @@
|
|||||||
<div class="result-config-preview">
|
<div class="result-config-preview">
|
||||||
<div class="result-page-wrap">
|
<div class="result-page-wrap">
|
||||||
<div class="result-page">
|
<div class="result-page">
|
||||||
<component
|
<component :is="currentEditStatus" :key="currentEditStatus" :module-config="moduleConfig" />
|
||||||
:is="currentEditStatus"
|
|
||||||
:key="currentEditStatus"
|
|
||||||
:module-config="moduleConfig"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from 'vuex';
|
import { mapState } from 'vuex'
|
||||||
import success from '../components/success';
|
import SuccessContent from '../components/SuccessContent.vue'
|
||||||
import overTime from '../components/overTime';
|
import OverTime from '../components/OverTime.vue'
|
||||||
import { EDIT_STATUS_MAP } from '../enum';
|
import { EDIT_STATUS_MAP } from '../enum'
|
||||||
import { get as _get } from 'lodash-es';
|
import { get as _get } from 'lodash-es'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ResultConfigPreivew',
|
name: 'PreviewPanel',
|
||||||
props: {},
|
props: {},
|
||||||
data() {
|
data() {
|
||||||
return {};
|
return {}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState({
|
...mapState({
|
||||||
currentEditStatus: (state) => state.edit.currentEditStatus,
|
currentEditStatus: (state) => state.edit.currentEditStatus,
|
||||||
submitConf: (state) => _get(state, 'edit.schema.submitConf'),
|
submitConf: (state) => _get(state, 'edit.schema.submitConf')
|
||||||
}),
|
}),
|
||||||
moduleConfig() {
|
moduleConfig() {
|
||||||
return {
|
return {
|
||||||
submitConf: this.submitConf,
|
submitConf: this.submitConf
|
||||||
};
|
}
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
[EDIT_STATUS_MAP.SUCCESS]: success,
|
[EDIT_STATUS_MAP.SUCCESS]: SuccessContent,
|
||||||
[EDIT_STATUS_MAP.OVERTIME]: overTime,
|
[EDIT_STATUS_MAP.OVERTIME]: OverTime
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
.result-config-preview {
|
.result-config-preview {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -62,8 +60,8 @@ export default {
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
background-color: var(--primary-background-color);
|
background-color: var(--primary-background-color);
|
||||||
padding: 0 0.3rem;
|
padding: 0 0.3rem;
|
||||||
.result-page{
|
.result-page {
|
||||||
background: rgba(255, 255, 255, var(--opacity));
|
background: rgba(255, 255, 255, var(--opacity));
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
@ -3,21 +3,15 @@
|
|||||||
<div class="setter-title">
|
<div class="setter-title">
|
||||||
{{ currentEditText }}
|
{{ currentEditText }}
|
||||||
</div>
|
</div>
|
||||||
<el-form
|
<el-form class="question-config-form" label-position="top" @submit.prevent>
|
||||||
class="question-config-form"
|
<template v-for="(item, index) in formFieldData" :key="index">
|
||||||
size="small"
|
|
||||||
label-position="top"
|
|
||||||
@submit.native.prevent
|
|
||||||
>
|
|
||||||
<template v-for="(item, index) in formFieldData">
|
|
||||||
<FormItem
|
<FormItem
|
||||||
:key="index"
|
v-if="item.type && !item.hidden && Boolean(register[item.type])"
|
||||||
v-if="item.type && !item.hidden && Boolean(registerd[item.type])"
|
|
||||||
:form-config="item"
|
:form-config="item"
|
||||||
:style="item.style"
|
:style="item.style"
|
||||||
>
|
>
|
||||||
<Component
|
<Component
|
||||||
v-if="Boolean(registerd[item.type])"
|
v-if="Boolean(register[item.type])"
|
||||||
:is="item.type"
|
:is="item.type"
|
||||||
:module-config="moduleConfig"
|
:module-config="moduleConfig"
|
||||||
:form-config="item"
|
:form-config="item"
|
||||||
@ -28,115 +22,117 @@
|
|||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import FormItem from '@/materials/setters/widgets/FormItem.vue';
|
import FormItem from '@/materials/setters/widgets/FormItem.vue'
|
||||||
import setterLoader from '@/materials/setters/setterLoader';
|
import setterLoader from '@/materials/setters/setterLoader'
|
||||||
import statusConfig from '../config/statusConfig';
|
import statusConfig from '../config/statusConfig'
|
||||||
import { mapState } from 'vuex';
|
import { mapState } from 'vuex'
|
||||||
import { get as _get, pick as _pick } from 'lodash-es';
|
import { get as _get, pick as _pick } from 'lodash-es'
|
||||||
|
|
||||||
const textMap = {
|
const textMap = {
|
||||||
Success: '提交成功页面配置',
|
Success: '提交成功页面配置',
|
||||||
OverTime: '问卷过期页面配置',
|
OverTime: '问卷过期页面配置'
|
||||||
};
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'StatusEditForm',
|
name: 'SetterPanel',
|
||||||
components: {
|
components: {
|
||||||
FormItem,
|
FormItem
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
registerd: {},
|
register: {}
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
formFieldData() {
|
formFieldData() {
|
||||||
const formList = statusConfig[this.currentEditStatus] || [];
|
const formList = statusConfig[this.currentEditStatus] || []
|
||||||
return formList.map((item) => {
|
return formList.map((item) => {
|
||||||
const value = _get(this.moduleConfig, item.key, item.value);
|
const value = _get(this.moduleConfig, item.key, item.value)
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
value,
|
value
|
||||||
};
|
}
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
currentEditText() {
|
currentEditText() {
|
||||||
return textMap[this.currentEditStatus] || '';
|
return textMap[this.currentEditStatus] || ''
|
||||||
},
|
},
|
||||||
...mapState({
|
...mapState({
|
||||||
currentEditStatus: (state) => state.edit.currentEditStatus,
|
currentEditStatus: (state) => state.edit.currentEditStatus,
|
||||||
submitConf: (state) => _get(state, 'edit.schema.submitConf'),
|
submitConf: (state) => _get(state, 'edit.schema.submitConf')
|
||||||
}),
|
}),
|
||||||
moduleConfig() {
|
moduleConfig() {
|
||||||
return this.submitConf;
|
return this.submitConf
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
formFieldData: {
|
formFieldData: {
|
||||||
immediate: true,
|
immediate: true,
|
||||||
handler(newVal) {
|
handler(newVal) {
|
||||||
if (Array.isArray(newVal)) {
|
if (Array.isArray(newVal)) {
|
||||||
this.handleComponentRegister(newVal);
|
this.handleComponentRegister(newVal)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async handleComponentRegister(formFieldData) {
|
async handleComponentRegister(formFieldData) {
|
||||||
const setters = formFieldData.map((item) => item.type);
|
const setters = formFieldData.map((item) => item.type)
|
||||||
const settersSet = new Set(setters);
|
const settersSet = new Set(setters)
|
||||||
const settersArr = Array.from(settersSet);
|
const settersArr = Array.from(settersSet)
|
||||||
const allSetters = settersArr.map((item) => {
|
const allSetters = settersArr.map((item) => {
|
||||||
return {
|
return {
|
||||||
type: item,
|
type: item,
|
||||||
path: item,
|
path: item
|
||||||
};
|
}
|
||||||
});
|
})
|
||||||
try {
|
try {
|
||||||
const comps = await setterLoader.loadComponents(allSetters);
|
const comps = await setterLoader.loadComponents(allSetters)
|
||||||
for (const comp of comps) {
|
for (const comp of comps) {
|
||||||
if (!comp) {
|
if (!comp) {
|
||||||
continue;
|
continue
|
||||||
}
|
}
|
||||||
const { type, component, err } = comp;
|
const { type, component, err } = comp
|
||||||
if (!err) {
|
if (!err) {
|
||||||
const componentName = component.name;
|
const componentName = component.name
|
||||||
this.$options.components[componentName] = component;
|
this.$options.components[componentName] = component
|
||||||
this.$set(this.registerd, type, componentName);
|
this.register[type] = componentName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getValueFromModuleConfig(item) {
|
getValueFromModuleConfig(item) {
|
||||||
const { key, keys } = item;
|
const { key, keys } = item
|
||||||
const moduleConfig = this.moduleConfig;
|
const moduleConfig = this.moduleConfig
|
||||||
let result = item;
|
let result = item
|
||||||
if (key) {
|
if (key) {
|
||||||
result = {
|
result = {
|
||||||
...item,
|
...item,
|
||||||
value: _get(moduleConfig, key, item.value),
|
value: _get(moduleConfig, key, item.value)
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
if (keys) {
|
if (keys) {
|
||||||
result = {
|
result = {
|
||||||
...item,
|
...item,
|
||||||
value: _pick(moduleConfig, keys),
|
value: _pick(moduleConfig, keys)
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result
|
||||||
},
|
},
|
||||||
onFormChange(data) {
|
onFormChange(data) {
|
||||||
const { key, value } = data;
|
const { key, value } = data
|
||||||
const resultKey = `submitConf.${key}`;
|
const resultKey = `submitConf.${key}`
|
||||||
this.$store.dispatch('edit/changeSchema', { key: resultKey, value });
|
this.$store.dispatch('edit/changeSchema', { key: resultKey, value })
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
.question-edit-form {
|
.question-edit-form {
|
||||||
width: 360px;
|
width: 360px;
|
||||||
height: 100%;
|
height: 100%;
|
@ -2,14 +2,15 @@
|
|||||||
<div class="tab-box">
|
<div class="tab-box">
|
||||||
<div class="title">主题设置</div>
|
<div class="title">主题设置</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="tag-list">
|
<div class="tag-list">
|
||||||
<el-tag
|
<el-tag
|
||||||
v-for="item in groupList"
|
v-for="item in groupList"
|
||||||
:class="[groupName === item.value ? 'current' : '', 'tag']"
|
:class="[groupName === item.value ? 'current' : '', 'tag']"
|
||||||
type = 'info'
|
type="info"
|
||||||
:key="item.value"
|
:key="item.value"
|
||||||
@click="() => changeGroup(item.value)">
|
@click="() => changeGroup(item.value)"
|
||||||
{{item.label}}
|
>
|
||||||
|
{{ item.label }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</div>
|
</div>
|
||||||
<div class="banner-list-wrapper">
|
<div class="banner-list-wrapper">
|
||||||
@ -18,17 +19,10 @@
|
|||||||
v-for="(banner, bannerIndex) in currentBannerList"
|
v-for="(banner, bannerIndex) in currentBannerList"
|
||||||
:key="bannerIndex"
|
:key="bannerIndex"
|
||||||
>
|
>
|
||||||
<img
|
<img class="banner-img" :src="banner.src" loading="lazy" @click="changePreset(banner)" />
|
||||||
class="banner-img"
|
|
||||||
:src="banner.src"
|
|
||||||
loading="lazy"
|
|
||||||
@click="changePreset(banner)"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
@ -36,16 +30,16 @@ import { mapActions } from 'vuex'
|
|||||||
|
|
||||||
import skinPresets from '@/management/config/skinPresets.js'
|
import skinPresets from '@/management/config/skinPresets.js'
|
||||||
export default {
|
export default {
|
||||||
name: 'catalogPanel',
|
name: 'CatalogPanel',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
skinPresets: [],
|
skinPresets: [],
|
||||||
groupName: 'temp',
|
groupName: 'temp'
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
bannerList() {
|
bannerList() {
|
||||||
return this.$store?.state?.bannerList || [];
|
return this.$store?.state?.bannerList || []
|
||||||
},
|
},
|
||||||
groupList() {
|
groupList() {
|
||||||
return Object.keys(this.bannerList).map((key) => {
|
return Object.keys(this.bannerList).map((key) => {
|
||||||
@ -55,20 +49,22 @@ export default {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
currentBannerList () {
|
currentBannerList() {
|
||||||
const arr = Object.keys(this.bannerList).map((key) => {
|
const arr = Object.keys(this.bannerList)
|
||||||
return this.bannerList[key]
|
.map((key) => {
|
||||||
}).map(data => {
|
return this.bannerList[key]
|
||||||
return data.list.map(item => {
|
|
||||||
item.group = data.key;
|
|
||||||
return item;
|
|
||||||
})
|
})
|
||||||
})
|
.map((data) => {
|
||||||
const allbanner = arr.reduce((acc, curr) => {
|
return data.list.map((item) => {
|
||||||
return acc.concat(curr);
|
item.group = data.key
|
||||||
}, []);
|
return item
|
||||||
return allbanner.filter(item => {
|
})
|
||||||
if(this.groupName === "temp") {
|
})
|
||||||
|
const allbanner = arr.reduce((acc, curr) => {
|
||||||
|
return acc.concat(curr)
|
||||||
|
}, [])
|
||||||
|
return allbanner.filter((item) => {
|
||||||
|
if (this.groupName === 'temp') {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return item.group === this.groupName
|
return item.group === this.groupName
|
||||||
@ -76,34 +72,31 @@ export default {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {},
|
||||||
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions({
|
...mapActions({
|
||||||
changeThemePreset: 'edit/changeThemePreset',
|
changeThemePreset: 'edit/changeThemePreset'
|
||||||
}),
|
}),
|
||||||
changeGroup(value) {
|
changeGroup(value) {
|
||||||
this.groupName = value
|
this.groupName = value
|
||||||
},
|
},
|
||||||
changePreset(banner) {
|
changePreset(banner) {
|
||||||
const name = banner.group + '-' + banner.title
|
const name = banner.group + '-' + banner.title
|
||||||
let presets = {
|
let presets = {
|
||||||
'bannerConf.bannerConfig.bgImage': banner.src,
|
'bannerConf.bannerConfig.bgImage': banner.src,
|
||||||
'skinConf.themeConf.color': '#FAA600',
|
'skinConf.themeConf.color': '#FAA600',
|
||||||
'skinConf.backgroundConf.color': '#fff',
|
'skinConf.backgroundConf.color': '#fff'
|
||||||
}
|
}
|
||||||
if(skinPresets[name]){
|
if (skinPresets[name]) {
|
||||||
presets = Object.assign(presets, skinPresets[name])
|
presets = Object.assign(presets, skinPresets[name])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
this.changeThemePreset(presets)
|
this.changeThemePreset(presets)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.tab-box {
|
.tab-box {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -120,16 +113,16 @@ export default {
|
|||||||
// background: #f9fafc;
|
// background: #f9fafc;
|
||||||
border-bottom: 1px solid #edeffc;
|
border-bottom: 1px solid #edeffc;
|
||||||
}
|
}
|
||||||
.content{
|
.content {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
.tag-list{
|
.tag-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
.tag{
|
.tag {
|
||||||
margin: 5px 8px;
|
margin: 5px 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&.current{
|
&.current {
|
||||||
color: $primary-color;
|
color: $primary-color;
|
||||||
background-color: $primary-bg-color;
|
background-color: $primary-bg-color;
|
||||||
}
|
}
|
||||||
@ -153,18 +146,18 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep .el-collapse-item__header {
|
:deep(.el-collapse-item__header) {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: $font-color-title;
|
color: $font-color-title;
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep .el-collapse-item__arrow.is-active {
|
:deep(.el-collapse-item__arrow.is-active) {
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
::v-deep .el-collapse-item__arrow {
|
:deep(.el-collapse-item__arrow) {
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
@ -3,27 +3,16 @@
|
|||||||
<div class="operation-wrapper">
|
<div class="operation-wrapper">
|
||||||
<div class="box" ref="box">
|
<div class="box" ref="box">
|
||||||
<div class="mask"></div>
|
<div class="mask"></div>
|
||||||
<banner
|
<BannerContent :bannerConf="bannerConf" />
|
||||||
:bannerConf="bannerConf"
|
|
||||||
/>
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<mainTitle
|
<MainTitle :isSelected="false" :bannerConf="bannerConf" />
|
||||||
:isSelected="false"
|
<MaterialGroup :questionDataList="questionDataList" ref="MaterialGroup" />
|
||||||
:bannerConf="bannerConf"
|
<SubmitButton
|
||||||
/>
|
|
||||||
<materialGroup
|
|
||||||
:questionDataList="questionDataList"
|
|
||||||
ref="materialGroup"
|
|
||||||
/>
|
|
||||||
<submit
|
|
||||||
:submit-conf="submitConf"
|
:submit-conf="submitConf"
|
||||||
:skin-conf="skinConf"
|
:skin-conf="skinConf"
|
||||||
:is-selected="currentEditOne === 'submit'"
|
:is-selected="currentEditOne === 'submit'"
|
||||||
/>
|
/>
|
||||||
<logo
|
<LogoPreview :logo-conf="bottomConf" :is-selected="currentEditOne === 'logo'" />
|
||||||
:logo-conf="bottomConf"
|
|
||||||
:is-selected="currentEditOne === 'logo'"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -31,27 +20,27 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import materialGroup from '@/management/pages/edit/components/materialGroup.vue';
|
import MaterialGroup from '@/management/pages/edit/components/MaterialGroup.vue'
|
||||||
import banner from '../components/banner.vue';
|
import BannerContent from '../components/BannerContent.vue'
|
||||||
import mainTitle from '@/management/pages/edit/components/mainTitle.vue';
|
import MainTitle from '@/management/pages/edit/components/MainTitle.vue'
|
||||||
import submit from '@/management/pages/edit/components/submit.vue';
|
import SubmitButton from '@/management/pages/edit/components/SubmitButton.vue'
|
||||||
import logo from '@/management/pages/edit/components/logo.vue';
|
import LogoPreview from '@/management/pages/edit/components/LogoPreview.vue'
|
||||||
import { mapState, mapGetters } from 'vuex';
|
import { mapState, mapGetters } from 'vuex'
|
||||||
import { get as _get } from 'lodash-es';
|
import { get as _get } from 'lodash-es'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'previewPanel',
|
name: 'PreviewPanel',
|
||||||
components: {
|
components: {
|
||||||
banner,
|
BannerContent,
|
||||||
mainTitle,
|
MainTitle,
|
||||||
submit,
|
SubmitButton,
|
||||||
logo,
|
LogoPreview,
|
||||||
materialGroup,
|
MaterialGroup
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isAnimating: false,
|
isAnimating: false
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState({
|
...mapState({
|
||||||
@ -60,25 +49,25 @@ export default {
|
|||||||
bottomConf: (state) => _get(state, 'edit.schema.bottomConf'),
|
bottomConf: (state) => _get(state, 'edit.schema.bottomConf'),
|
||||||
skinConf: (state) => _get(state, 'edit.schema.skinConf'),
|
skinConf: (state) => _get(state, 'edit.schema.skinConf'),
|
||||||
questionDataList: (state) => _get(state, 'edit.schema.questionDataList'),
|
questionDataList: (state) => _get(state, 'edit.schema.questionDataList'),
|
||||||
currentEditOne: (state) => _get(state, 'edit.currentEditOne'),
|
currentEditOne: (state) => _get(state, 'edit.currentEditOne')
|
||||||
}),
|
}),
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
currentEditKey: 'edit/currentEditKey',
|
currentEditKey: 'edit/currentEditKey'
|
||||||
}),
|
})
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
skinConf: {
|
skinConf: {
|
||||||
handler (skinConf) {
|
handler(skinConf) {
|
||||||
const { themeConf, backgroundConf, contentConf} = skinConf
|
const { themeConf, backgroundConf, contentConf } = skinConf
|
||||||
const root = document.documentElement;
|
const root = document.documentElement
|
||||||
if(themeConf?.color) {
|
if (themeConf?.color) {
|
||||||
root.style.setProperty('--primary-color', themeConf?.color); // 设置主题颜色
|
root.style.setProperty('--primary-color', themeConf?.color) // 设置主题颜色
|
||||||
}
|
}
|
||||||
if(backgroundConf?.color) {
|
if (backgroundConf?.color) {
|
||||||
root.style.setProperty('--primary-background-color', backgroundConf?.color); // 设置背景颜色
|
root.style.setProperty('--primary-background-color', backgroundConf?.color) // 设置背景颜色
|
||||||
}
|
}
|
||||||
if(contentConf?.opacity.toString()) {
|
if (contentConf?.opacity.toString()) {
|
||||||
root.style.setProperty('--opacity', contentConf?.opacity/100); // 设置全局透明度
|
root.style.setProperty('--opacity', contentConf?.opacity / 100) // 设置全局透明度
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
immediate: true, // 立即触发回调函数
|
immediate: true, // 立即触发回调函数
|
||||||
@ -87,30 +76,30 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
animate(dom, property, targetValue) {
|
animate(dom, property, targetValue) {
|
||||||
const origin = dom[property];
|
const origin = dom[property]
|
||||||
const subVal = targetValue - origin;
|
const subVal = targetValue - origin
|
||||||
|
|
||||||
const flag = subVal < 0 ? -1 : 1;
|
const flag = subVal < 0 ? -1 : 1
|
||||||
|
|
||||||
const step = flag * 50;
|
const step = flag * 50
|
||||||
|
|
||||||
const totalCount = Math.floor(subVal / step) + 1;
|
const totalCount = Math.floor(subVal / step) + 1
|
||||||
|
|
||||||
let runCount = 0;
|
let runCount = 0
|
||||||
const run = () => {
|
const run = () => {
|
||||||
dom[property] += step;
|
dom[property] += step
|
||||||
runCount++;
|
runCount++
|
||||||
if (runCount < totalCount) {
|
if (runCount < totalCount) {
|
||||||
requestAnimationFrame(run);
|
requestAnimationFrame(run)
|
||||||
} else {
|
} else {
|
||||||
this.isAnimating = false;
|
this.isAnimating = false
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
requestAnimationFrame(run);
|
requestAnimationFrame(run)
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@ -143,7 +132,7 @@ export default {
|
|||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
-ms-overflow-style: none;
|
-ms-overflow-style: none;
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@ -151,7 +140,7 @@ export default {
|
|||||||
.box {
|
.box {
|
||||||
background-color: var(--primary-background-color);
|
background-color: var(--primary-background-color);
|
||||||
position: relative;
|
position: relative;
|
||||||
.mask{
|
.mask {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
@ -1,8 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="setter-wrapper">
|
<div class="setter-wrapper">
|
||||||
<div class="setter-title">
|
<div class="setter-title">样式设置</div>
|
||||||
样式设置
|
|
||||||
</div>
|
|
||||||
<div class="setter-content">
|
<div class="setter-content">
|
||||||
<el-collapse v-model="collapse">
|
<el-collapse v-model="collapse">
|
||||||
<el-collapse-item
|
<el-collapse-item
|
||||||
@ -11,51 +9,54 @@
|
|||||||
:title="collapse.name"
|
:title="collapse.name"
|
||||||
:name="collapse.key"
|
:name="collapse.key"
|
||||||
>
|
>
|
||||||
<setterField
|
<SetterField
|
||||||
:form-config-list="collapse.formConfigList"
|
:form-config-list="collapse.formConfigList"
|
||||||
:module-config="_get(schema, collapse.key, {})"
|
:module-config="_get(schema, collapse.key, {})"
|
||||||
@form-change="(key) => { onFormChange(key, collapse.key) }"
|
@form-change="
|
||||||
/>
|
(key) => {
|
||||||
|
onFormChange(key, collapse.key)
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
</el-collapse-item>
|
</el-collapse-item>
|
||||||
</el-collapse>
|
</el-collapse>
|
||||||
</div>
|
</div>
|
||||||
<!-- -->
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import skinConfig from '@/management/config/setterConfig/skinConfig';
|
import skinConfig from '@/management/config/setterConfig/skinConfig'
|
||||||
import setterField from '@/management/pages/edit/components/setterField.vue';
|
import SetterField from '@/management/pages/edit/components/SetterField.vue'
|
||||||
import { mapState, mapGetters } from 'vuex';
|
import { mapState } from 'vuex'
|
||||||
import { get as _get } from 'lodash-es'
|
import { get as _get } from 'lodash-es'
|
||||||
export default {
|
export default {
|
||||||
name: 'setterPanel',
|
name: 'SetterPanel',
|
||||||
components: {
|
components: {
|
||||||
setterField,
|
SetterField
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
collapse: '',
|
collapse: '',
|
||||||
skinConfig,
|
skinConfig
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState({
|
...mapState({
|
||||||
skinConf: (state) => _get(state, 'edit.schema.skinConf'),
|
skinConf: (state) => _get(state, 'edit.schema.skinConf'),
|
||||||
schema: (state) => _get(state, 'edit.schema'),
|
schema: (state) => _get(state, 'edit.schema')
|
||||||
}),
|
})
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
_get,
|
_get,
|
||||||
onFormChange(data,collapse) {
|
onFormChange(data, collapse) {
|
||||||
const { key, value } = data;
|
const { key, value } = data
|
||||||
const currentEditKey = `${collapse}`
|
const currentEditKey = `${collapse}`
|
||||||
const resultKey = `${currentEditKey}.${key}`;
|
const resultKey = `${currentEditKey}.${key}`
|
||||||
this.$store.dispatch('edit/changeSchema', { key: resultKey, value });
|
this.$store.dispatch('edit/changeSchema', { key: resultKey, value })
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.setter-wrapper {
|
.setter-wrapper {
|
||||||
width: 360px;
|
width: 360px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -73,26 +74,25 @@ export default {
|
|||||||
// background: #f9fafc;
|
// background: #f9fafc;
|
||||||
border-bottom: 1px solid #edeffc;
|
border-bottom: 1px solid #edeffc;
|
||||||
}
|
}
|
||||||
.setter-content{
|
.setter-content {
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
.el-collapse {
|
.el-collapse {
|
||||||
border: none;
|
border: none;
|
||||||
::v-deep .el-collapse-item__header{
|
:deep(.el-collapse-item__header) {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #606266;
|
color: #606266;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
::v-deep .el-collapse-item__wrap{
|
:deep(.el-collapse-item__wrap) {
|
||||||
border: none;
|
border: none;
|
||||||
.el-collapse-item__content{
|
.el-collapse-item__content {
|
||||||
padding-bottom: 0px!important;
|
padding-bottom: 0px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.config-form{
|
.config-form {
|
||||||
padding: 0!important;
|
padding: 0 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.no-select-question {
|
.no-select-question {
|
36
web/src/management/pages/edit/pages/EditPage.vue
Normal file
36
web/src/management/pages/edit/pages/EditPage.vue
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<template>
|
||||||
|
<commonTemplate>
|
||||||
|
<template #left>
|
||||||
|
<CatalogPanel></CatalogPanel>
|
||||||
|
</template>
|
||||||
|
<template #center>
|
||||||
|
<PreviewPanel></PreviewPanel>
|
||||||
|
</template>
|
||||||
|
<template #right>
|
||||||
|
<SetterPanel></SetterPanel>
|
||||||
|
</template>
|
||||||
|
</commonTemplate>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import commonTemplate from '../components/CommonTemplate.vue'
|
||||||
|
import CatalogPanel from '../modules/questionModule/CatalogPanel.vue'
|
||||||
|
import PreviewPanel from '../modules/questionModule/PreviewPanel.vue'
|
||||||
|
import SetterPanel from '../modules/questionModule/SetterPanel.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'EditPage',
|
||||||
|
components: {
|
||||||
|
commonTemplate,
|
||||||
|
CatalogPanel,
|
||||||
|
PreviewPanel,
|
||||||
|
SetterPanel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.navbar {
|
||||||
|
border-bottom: 1px solid #e7e9eb;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,17 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="setting-page">
|
<div class="setting-page">
|
||||||
<setting></setting>
|
<SettingPanel></SettingPanel>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import setting from '../modules/settingModule/setting.vue';
|
import SettingPanel from '../modules/settingModule/SettingPanel.vue'
|
||||||
export default {
|
export default {
|
||||||
name: 'questionSettingPage',
|
name: 'SettingPage',
|
||||||
components: {
|
components: {
|
||||||
setting,
|
SettingPanel
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.setting-page {
|
.setting-page {
|
||||||
width: 100%;
|
width: 100%;
|
@ -1,28 +0,0 @@
|
|||||||
<template>
|
|
||||||
<commonTemplate>
|
|
||||||
<catalogPanel slot="left"></catalogPanel>
|
|
||||||
<previewPanel slot="center"></previewPanel>
|
|
||||||
<setterPanel slot="right"></setterPanel>
|
|
||||||
</commonTemplate>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
import commonTemplate from '../components/commonTemplate.vue';
|
|
||||||
import catalogPanel from '../modules/questionModule/catalogPanel.vue';
|
|
||||||
import previewPanel from '../modules/questionModule/previewPanel.vue';
|
|
||||||
import setterPanel from '../modules/questionModule/setterPanel.vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'editIndex',
|
|
||||||
components: {
|
|
||||||
commonTemplate,
|
|
||||||
catalogPanel,
|
|
||||||
previewPanel,
|
|
||||||
setterPanel,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.navbar {
|
|
||||||
border-bottom: 1px solid #e7e9eb;
|
|
||||||
}
|
|
||||||
</style>
|
|
37
web/src/management/pages/edit/pages/skin/ContentPage.vue
Normal file
37
web/src/management/pages/edit/pages/skin/ContentPage.vue
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<template>
|
||||||
|
<commonTemplate>
|
||||||
|
<template #left>
|
||||||
|
<CatalogPanel />
|
||||||
|
</template>
|
||||||
|
<template #center>
|
||||||
|
<PreviewPanel />
|
||||||
|
</template>
|
||||||
|
<template #right>
|
||||||
|
<SetterPanel />
|
||||||
|
</template>
|
||||||
|
</commonTemplate>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import commonTemplate from '../../components/CommonTemplate.vue'
|
||||||
|
import CatalogPanel from '../../modules/settingModule/skin/CatalogPanel.vue'
|
||||||
|
import PreviewPanel from '../../modules/settingModule/skin/PreviewPanel.vue'
|
||||||
|
import SetterPanel from '../../modules/settingModule/skin/SetterPanel.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ContentPage',
|
||||||
|
components: {
|
||||||
|
commonTemplate,
|
||||||
|
CatalogPanel,
|
||||||
|
PreviewPanel,
|
||||||
|
SetterPanel
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.$store.dispatch('getBannerData')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.navbar {
|
||||||
|
border-bottom: 1px solid #e7e9eb;
|
||||||
|
}
|
||||||
|
</style>
|
30
web/src/management/pages/edit/pages/skin/ResultPage.vue
Normal file
30
web/src/management/pages/edit/pages/skin/ResultPage.vue
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<template>
|
||||||
|
<commonTemplate>
|
||||||
|
<template #left>
|
||||||
|
<ResultCatalog />
|
||||||
|
</template>
|
||||||
|
<template #center>
|
||||||
|
<ResultPreview />
|
||||||
|
</template>
|
||||||
|
<template #right>
|
||||||
|
<ResultSetter />
|
||||||
|
</template>
|
||||||
|
</commonTemplate>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import commonTemplate from '../../components/CommonTemplate.vue'
|
||||||
|
import ResultCatalog from '../../modules/settingModule/result/CatalogPanel.vue'
|
||||||
|
import ResultPreview from '../../modules/settingModule/result/PreviewPanel.vue'
|
||||||
|
import ResultSetter from '../../modules/settingModule/result/SetterPanel.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ResultPage',
|
||||||
|
components: {
|
||||||
|
commonTemplate,
|
||||||
|
ResultCatalog,
|
||||||
|
ResultPreview,
|
||||||
|
ResultSetter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -1,32 +0,0 @@
|
|||||||
<template>
|
|
||||||
<commonTemplate>
|
|
||||||
<catalogPanel slot="left"></catalogPanel>
|
|
||||||
<previewPanel slot="center"></previewPanel>
|
|
||||||
<setterPanel slot="right"></setterPanel>
|
|
||||||
</commonTemplate>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
import commonTemplate from '../../components/commonTemplate.vue';
|
|
||||||
import catalogPanel from '../../modules/settingModule/skin/catalogPanel.vue';
|
|
||||||
import previewPanel from '../../modules/settingModule/skin/previewPanel.vue';
|
|
||||||
import setterPanel from '../../modules/settingModule/skin/setterPanel.vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'editIndex',
|
|
||||||
components: {
|
|
||||||
commonTemplate,
|
|
||||||
catalogPanel,
|
|
||||||
previewPanel,
|
|
||||||
setterPanel,
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.$store.dispatch('getBannerData');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.navbar {
|
|
||||||
border-bottom: 1px solid #e7e9eb;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="skin-content">
|
<div class="skin-content">
|
||||||
<div class="navbar-tab">
|
<div class="navbar-tab">
|
||||||
<el-radio-group size="mini" style="margin-bottom: 30px;" v-model="activeRouter">
|
<el-radio-group v-model="activeRouter">
|
||||||
<el-radio-button :label="btnItem.router" :key="btnItem.router" v-for="btnItem in btnList" >
|
<el-radio-button
|
||||||
<span>{{ btnItem.text }}</span>
|
v-for="btnItem in btnList"
|
||||||
</el-radio-button>
|
:key="btnItem.router"
|
||||||
|
:label="btnItem.text"
|
||||||
|
:value="btnItem.router"
|
||||||
|
/>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</div>
|
</div>
|
||||||
<router-view></router-view>
|
<router-view></router-view>
|
||||||
@ -22,24 +25,24 @@ export default {
|
|||||||
text: '内容页',
|
text: '内容页',
|
||||||
router: 'QuestionSkinSetting',
|
router: 'QuestionSkinSetting',
|
||||||
key: 'skinsettings',
|
key: 'skinsettings',
|
||||||
next: true,
|
next: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: '结果页',
|
text: '结果页',
|
||||||
router: 'QuestionEditResultConfig',
|
router: 'QuestionEditResultConfig',
|
||||||
key: 'status',
|
key: 'status'
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
activeRouter: {
|
activeRouter: {
|
||||||
handler (val) {
|
handler(val) {
|
||||||
this.$router.push({ name: val})
|
this.$router.push({ name: val })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.skin-content {
|
.skin-content {
|
||||||
@ -55,14 +58,14 @@ export default {
|
|||||||
top: 10px;
|
top: 10px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
::v-deep .el-radio-button__orig-radio:checked + .el-radio-button__inner{
|
:deep(.el-radio-button__original-radio + .el-radio-button__inner) {
|
||||||
color: $primary-color;
|
font-size: 12px;
|
||||||
background-color: #fff!important;
|
height: 28px;
|
||||||
// &:active{
|
}
|
||||||
// color: $primary-color;
|
:deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) {
|
||||||
// }
|
color: $primary-color;
|
||||||
|
background-color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
<template>
|
|
||||||
<commonTemplate>
|
|
||||||
<resultCatalog slot="left"></resultCatalog>
|
|
||||||
<resultPreview slot="center"></resultPreview>
|
|
||||||
<resultSetter slot="right"></resultSetter>
|
|
||||||
</commonTemplate>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
import commonTemplate from '../../components/commonTemplate.vue';
|
|
||||||
import resultCatalog from '../../modules/settingModule/result/catalogPanel.vue';
|
|
||||||
import resultPreview from '../../modules/settingModule/result/previewPanel.vue';
|
|
||||||
import resultSetter from '../../modules/settingModule/result/setterPanel.vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'editIndex',
|
|
||||||
components: {
|
|
||||||
commonTemplate,
|
|
||||||
resultCatalog,
|
|
||||||
resultPreview,
|
|
||||||
resultSetter,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
<style lang="scss" scoped></style>
|
|
@ -2,7 +2,7 @@
|
|||||||
<div class="tableview-root">
|
<div class="tableview-root">
|
||||||
<div class="filter-wrap">
|
<div class="filter-wrap">
|
||||||
<div class="select">
|
<div class="select">
|
||||||
<text-select
|
<TextSelect
|
||||||
v-for="item in Object.keys(selectOptionsDict)"
|
v-for="item in Object.keys(selectOptionsDict)"
|
||||||
:key="item"
|
:key="item"
|
||||||
:effect-fun="onSelectChange"
|
:effect-fun="onSelectChange"
|
||||||
@ -11,7 +11,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="search">
|
<div class="search">
|
||||||
<text-button
|
<TextButton
|
||||||
v-for="item in Object.keys(buttonOptionsDict)"
|
v-for="item in Object.keys(buttonOptionsDict)"
|
||||||
:key="item"
|
:key="item"
|
||||||
:effect-fun="onButtonChange"
|
:effect-fun="onButtonChange"
|
||||||
@ -20,16 +20,11 @@
|
|||||||
:icon="
|
:icon="
|
||||||
buttonOptionsDict[item].icons.find(
|
buttonOptionsDict[item].icons.find(
|
||||||
(iconItem) => iconItem.effectValue === buttonValueMap[item]
|
(iconItem) => iconItem.effectValue === buttonValueMap[item]
|
||||||
).name
|
).icon
|
||||||
"
|
"
|
||||||
size="mini"
|
link
|
||||||
type="text"
|
|
||||||
></text-button>
|
|
||||||
<text-search
|
|
||||||
placeholder="请输入问卷标题"
|
|
||||||
:value="searchVal"
|
|
||||||
@search="onSearchText"
|
|
||||||
/>
|
/>
|
||||||
|
<TextSearch placeholder="请输入问卷标题" :value="searchVal" @search="onSearchText" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-table
|
<el-table
|
||||||
@ -56,7 +51,7 @@
|
|||||||
:min-width="field.width || field.minWidth"
|
:min-width="field.width || field.minWidth"
|
||||||
class-name="link"
|
class-name="link"
|
||||||
>
|
>
|
||||||
<template slot-scope="scope">
|
<template #default="scope">
|
||||||
<template v-if="field.comp">
|
<template v-if="field.comp">
|
||||||
<component :is="field.comp" type="table" :value="scope.row" />
|
<component :is="field.comp" type="table" :value="scope.row" />
|
||||||
</template>
|
</template>
|
||||||
@ -67,7 +62,7 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
||||||
<el-table-column label="操作" :width="300" class-name="table-options">
|
<el-table-column label="操作" :width="300" class-name="table-options">
|
||||||
<template slot-scope="scope">
|
<template #default="scope">
|
||||||
<ToolBar
|
<ToolBar
|
||||||
:data="scope.row"
|
:data="scope.row"
|
||||||
type="list"
|
type="list"
|
||||||
@ -91,10 +86,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<empty :data="!searchVal ? noListDataConfig : noSearchDataConfig" />
|
<EmptyIndex :data="!searchVal ? noListDataConfig : noSearchDataConfig" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<modify-dialog
|
<ModifyDialog
|
||||||
:type="modifyType"
|
:type="modifyType"
|
||||||
:visible="showModify"
|
:visible="showModify"
|
||||||
:question-info="questionInfo"
|
:question-info="questionInfo"
|
||||||
@ -104,44 +99,43 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { get, map } from 'lodash-es';
|
import { get, map } from 'lodash-es'
|
||||||
import moment from 'moment';
|
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import 'element-plus/theme-chalk/src/message.scss'
|
||||||
|
import 'element-plus/theme-chalk/src/message-box.scss'
|
||||||
|
|
||||||
|
import moment from 'moment'
|
||||||
// 引入中文
|
// 引入中文
|
||||||
import 'moment/locale/zh-cn';
|
import 'moment/locale/zh-cn'
|
||||||
// 设置中文
|
// 设置中文
|
||||||
moment.locale('zh-cn');
|
moment.locale('zh-cn')
|
||||||
import empty from '@/management/components/empty';
|
|
||||||
import ModifyDialog from './modify';
|
import EmptyIndex from '@/management/components/EmptyIndex.vue'
|
||||||
import Tag from './tag';
|
import { CODE_MAP } from '@/management/api/base'
|
||||||
import State from './state';
|
import { QOP_MAP } from '@/management/utils/constant'
|
||||||
import ToolBar from './toolBar';
|
import { getSurveyList, deleteSurvey } from '@/management/api/survey'
|
||||||
import TextSearch from './textSearch';
|
|
||||||
import TextSelect from './textSelect';
|
import ModifyDialog from './ModifyDialog.vue'
|
||||||
import TextButton from './textButton';
|
import TagModule from './TagModule.vue'
|
||||||
|
import StateModule from './StateModule.vue'
|
||||||
|
import ToolBar from './ToolBar.vue'
|
||||||
|
import TextSearch from './TextSearch.vue'
|
||||||
|
import TextSelect from './TextSelect.vue'
|
||||||
|
import TextButton from './TextButton.vue'
|
||||||
import {
|
import {
|
||||||
fieldConfig,
|
fieldConfig,
|
||||||
noListDataConfig,
|
noListDataConfig,
|
||||||
noSearchDataConfig,
|
noSearchDataConfig,
|
||||||
selectOptionsDict,
|
selectOptionsDict,
|
||||||
buttonOptionsDict,
|
buttonOptionsDict
|
||||||
} from '../config';
|
} from '../config'
|
||||||
import { CODE_MAP } from '@/management/api/base';
|
|
||||||
import { QOP_MAP } from '@/management/utils/constant';
|
|
||||||
import { getSurveyList, deleteSurvey } from '@/management/api/survey';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'BaseList',
|
name: 'BaseList',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
fields: [
|
fields: ['type', 'title', 'remark', 'owner', 'state', 'createDate', 'updateDate'],
|
||||||
'type',
|
|
||||||
'title',
|
|
||||||
'remark',
|
|
||||||
'owner',
|
|
||||||
'state',
|
|
||||||
'createDate',
|
|
||||||
'updateDate',
|
|
||||||
],
|
|
||||||
showModify: false,
|
showModify: false,
|
||||||
modifyType: '',
|
modifyType: '',
|
||||||
loading: false,
|
loading: false,
|
||||||
@ -155,29 +149,29 @@ export default {
|
|||||||
selectOptionsDict,
|
selectOptionsDict,
|
||||||
selectValueMap: {
|
selectValueMap: {
|
||||||
surveyType: '',
|
surveyType: '',
|
||||||
'curStatus.status': '',
|
'curStatus.status': ''
|
||||||
},
|
},
|
||||||
buttonOptionsDict,
|
buttonOptionsDict,
|
||||||
buttonValueMap: {
|
buttonValueMap: {
|
||||||
'curStatus.date': '',
|
'curStatus.date': '',
|
||||||
createDate: -1,
|
createDate: -1
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
fieldList() {
|
fieldList() {
|
||||||
const fieldInfo = map(this.fields, (f) => {
|
const fieldInfo = map(this.fields, (f) => {
|
||||||
return get(fieldConfig, f, null);
|
return get(fieldConfig, f, null)
|
||||||
});
|
})
|
||||||
return fieldInfo;
|
return fieldInfo
|
||||||
},
|
},
|
||||||
dataList() {
|
dataList() {
|
||||||
return this.data.map((item) => {
|
return this.data.map((item) => {
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
'curStatus.date': item.curStatus.date,
|
'curStatus.date': item.curStatus.date
|
||||||
};
|
}
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
filter() {
|
filter() {
|
||||||
return [
|
return [
|
||||||
@ -187,193 +181,188 @@ export default {
|
|||||||
{
|
{
|
||||||
field: 'title',
|
field: 'title',
|
||||||
value: this.searchVal,
|
value: this.searchVal,
|
||||||
comparator: '$regex',
|
comparator: '$regex'
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
comparator: '',
|
comparator: '',
|
||||||
condition: [
|
condition: [
|
||||||
{
|
{
|
||||||
field: 'curStatus.status',
|
field: 'curStatus.status',
|
||||||
value: this.selectValueMap['curStatus.status'],
|
value: this.selectValueMap['curStatus.status']
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
comparator: '',
|
comparator: '',
|
||||||
condition: [
|
condition: [
|
||||||
{
|
{
|
||||||
field: 'surveyType',
|
field: 'surveyType',
|
||||||
value: this.selectValueMap.surveyType,
|
value: this.selectValueMap.surveyType
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
},
|
}
|
||||||
];
|
]
|
||||||
},
|
},
|
||||||
order() {
|
order() {
|
||||||
const formatOrder = Object.entries(this.buttonValueMap)
|
const formatOrder = Object.entries(this.buttonValueMap)
|
||||||
.filter(([, effectValue]) => effectValue)
|
.filter(([, effectValue]) => effectValue)
|
||||||
.reduce((prev, item) => {
|
.reduce((prev, item) => {
|
||||||
const [effectKey, effectValue] = item;
|
const [effectKey, effectValue] = item
|
||||||
prev.push({ field: effectKey, value: effectValue });
|
prev.push({ field: effectKey, value: effectValue })
|
||||||
return prev;
|
return prev
|
||||||
}, []);
|
}, [])
|
||||||
return JSON.stringify(formatOrder);
|
return JSON.stringify(formatOrder)
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.init();
|
this.init()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async init() {
|
async init() {
|
||||||
this.loading = true;
|
this.loading = true
|
||||||
try {
|
try {
|
||||||
const filter = JSON.stringify(
|
const filter = JSON.stringify(
|
||||||
this.filter.filter((item) => {
|
this.filter.filter((item) => {
|
||||||
return item.condition[0].value;
|
return item.condition[0].value
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
const res = await getSurveyList({
|
const res = await getSurveyList({
|
||||||
curPage: this.currentPage,
|
curPage: this.currentPage,
|
||||||
filter,
|
filter,
|
||||||
order: this.order,
|
order: this.order
|
||||||
});
|
})
|
||||||
this.loading = false;
|
this.loading = false
|
||||||
if (res.code === CODE_MAP.SUCCESS) {
|
if (res.code === CODE_MAP.SUCCESS) {
|
||||||
this.total = res.data.count;
|
this.total = res.data.count
|
||||||
this.data = res.data.data;
|
this.data = res.data.data
|
||||||
} else {
|
} else {
|
||||||
this.$message({
|
ElMessage.error(res.errmsg)
|
||||||
type: 'error',
|
|
||||||
message: res.errmsg,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.$message({
|
ElMessage.error(error)
|
||||||
type: 'error',
|
this.loading = false
|
||||||
message: error,
|
|
||||||
});
|
|
||||||
this.loading = false;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getStatus(data) {
|
getStatus(data) {
|
||||||
return get(data, 'curStatus.status', 'new');
|
return get(data, 'curStatus.status', 'new')
|
||||||
},
|
},
|
||||||
getToolConfig() {
|
getToolConfig() {
|
||||||
const funcList = [
|
const funcList = [
|
||||||
{
|
{
|
||||||
key: QOP_MAP.EDIT,
|
key: QOP_MAP.EDIT,
|
||||||
label: '修改',
|
label: '修改'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'analysis',
|
key: 'analysis',
|
||||||
label: '数据',
|
label: '数据'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'release',
|
key: 'release',
|
||||||
label: '投放',
|
label: '投放'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'delete',
|
key: 'delete',
|
||||||
label: '删除',
|
label: '删除',
|
||||||
icon: 'icon-shanchu',
|
icon: 'icon-shanchu'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: QOP_MAP.COPY,
|
key: QOP_MAP.COPY,
|
||||||
label: '复制',
|
label: '复制',
|
||||||
icon: 'icon-shanchu',
|
icon: 'icon-shanchu'
|
||||||
},
|
}
|
||||||
];
|
]
|
||||||
return funcList;
|
return funcList
|
||||||
},
|
},
|
||||||
async onDelete(row) {
|
async onDelete(row) {
|
||||||
try {
|
try {
|
||||||
await this.$confirm('是否确认删除?', '提示', {
|
await ElMessageBox.confirm('是否确认删除?', '提示', {
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: '确定',
|
||||||
cancelButtonText: '取消',
|
cancelButtonText: '取消',
|
||||||
type: 'warning',
|
type: 'warning'
|
||||||
});
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('取消删除');
|
console.log('取消删除')
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await deleteSurvey(row._id);
|
const res = await deleteSurvey(row._id)
|
||||||
if (res.code === CODE_MAP.SUCCESS) {
|
if (res.code === CODE_MAP.SUCCESS) {
|
||||||
this.$message.success('删除成功');
|
ElMessage.success('删除成功')
|
||||||
this.init();
|
this.init()
|
||||||
} else {
|
} else {
|
||||||
this.$message.error(res.errmsg || '删除失败');
|
ElMessage.error(res.errmsg || '删除失败')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleCurrentChange(current) {
|
handleCurrentChange(current) {
|
||||||
this.currentPage = current;
|
this.currentPage = current
|
||||||
this.init();
|
this.init()
|
||||||
},
|
},
|
||||||
onModify(data, type = QOP_MAP.EDIT) {
|
onModify(data, type = QOP_MAP.EDIT) {
|
||||||
this.showModify = true;
|
this.showModify = true
|
||||||
this.modifyType = type;
|
this.modifyType = type
|
||||||
this.questionInfo = data;
|
this.questionInfo = data
|
||||||
},
|
},
|
||||||
onCloseModify(type) {
|
onCloseModify(type) {
|
||||||
this.showModify = false;
|
this.showModify = false
|
||||||
this.questionInfo = {};
|
this.questionInfo = {}
|
||||||
if (type === 'update') {
|
if (type === 'update') {
|
||||||
this.init();
|
this.init()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRowClick(row) {
|
onRowClick(row) {
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
name: 'QuestionEditIndex',
|
name: 'QuestionEditIndex',
|
||||||
params: {
|
params: {
|
||||||
id: row._id,
|
id: row._id
|
||||||
},
|
}
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
onSearchText(e) {
|
onSearchText(e) {
|
||||||
this.searchVal = e;
|
this.searchVal = e
|
||||||
this.currentPage = 1;
|
this.currentPage = 1
|
||||||
this.init();
|
this.init()
|
||||||
},
|
},
|
||||||
onSelectChange(selectValue, selectKey) {
|
onSelectChange(selectValue, selectKey) {
|
||||||
this.selectValueMap[selectKey] = selectValue;
|
this.selectValueMap[selectKey] = selectValue
|
||||||
this.currentPage = 1;
|
this.currentPage = 1
|
||||||
this.init();
|
this.init()
|
||||||
},
|
},
|
||||||
onButtonChange(effectValue, effectKey) {
|
onButtonChange(effectValue, effectKey) {
|
||||||
this.buttonValueMap = {
|
this.buttonValueMap = {
|
||||||
'curStatus.date': '',
|
'curStatus.date': '',
|
||||||
createDate: '',
|
createDate: ''
|
||||||
};
|
}
|
||||||
this.buttonValueMap[effectKey] = effectValue;
|
this.buttonValueMap[effectKey] = effectValue
|
||||||
this.init();
|
this.init()
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
empty,
|
EmptyIndex,
|
||||||
ModifyDialog,
|
ModifyDialog,
|
||||||
Tag,
|
TagModule,
|
||||||
ToolBar,
|
ToolBar,
|
||||||
TextSearch,
|
TextSearch,
|
||||||
TextSelect,
|
TextSelect,
|
||||||
TextButton,
|
TextButton,
|
||||||
State,
|
StateModule
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.tableview-root {
|
.tableview-root {
|
||||||
.filter-wrap {
|
.filter-wrap {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
.select {
|
.select {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
.search {
|
.search {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding-bottom: 20px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -383,12 +372,12 @@ export default {
|
|||||||
}
|
}
|
||||||
.list-pagination {
|
.list-pagination {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
::v-deep .el-pagination {
|
:deep(.el-pagination) {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
::v-deep .el-table__header {
|
:deep(.el-table__header) {
|
||||||
.tableview-header .el-table__cell {
|
.tableview-header .el-table__cell {
|
||||||
.cell {
|
.cell {
|
||||||
height: 24px;
|
height: 24px;
|
||||||
@ -397,7 +386,7 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
::v-deep .tableview-row {
|
:deep(.tableview-row) {
|
||||||
.tableview-cell {
|
.tableview-cell {
|
||||||
padding: 5px 0;
|
padding: 5px 0;
|
||||||
&.link {
|
&.link {
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<el-dialog
|
||||||
class="base-dialog-root"
|
class="base-dialog-root"
|
||||||
:visible="visible"
|
:model-value="visible"
|
||||||
width="40%"
|
width="40%"
|
||||||
title="基础信息"
|
title="基础信息"
|
||||||
@close="onClose"
|
@close="onClose"
|
||||||
@ -10,91 +10,97 @@
|
|||||||
class="base-form-root"
|
class="base-form-root"
|
||||||
ref="ruleForm"
|
ref="ruleForm"
|
||||||
:model="current"
|
:model="current"
|
||||||
label-width="80px"
|
|
||||||
:rules="rules"
|
:rules="rules"
|
||||||
label-position="top"
|
label-position="top"
|
||||||
@submit.native.prevent
|
size="large"
|
||||||
|
@submit.prevent
|
||||||
>
|
>
|
||||||
<el-form-item label="标题" prop="title">
|
<el-form-item label="标题" prop="title">
|
||||||
<el-input size="medium" v-model="current.title" />
|
<el-input v-model="current.title" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="备注">
|
<el-form-item label="备注">
|
||||||
<el-input size="medium" v-model="current.remark" />
|
<el-input v-model="current.remark" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<div slot="footer" class="dialog-footer">
|
<template #footer>
|
||||||
<el-button type="primary" class="save-btn" @click="onSave">{{
|
<div class="dialog-footer">
|
||||||
type === QOP_MAP.EDIT ? '保存' : '确定'
|
<el-button type="primary" class="save-btn" @click="onSave">{{
|
||||||
}}</el-button>
|
type === QOP_MAP.EDIT ? '保存' : '确定'
|
||||||
</div>
|
}}</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { CODE_MAP } from '@/management/api/base';
|
import { pick as _pick } from 'lodash-es'
|
||||||
import { updateSurvey, createSurvey } from '@/management/api/survey';
|
|
||||||
import { pick as _pick } from 'lodash-es';
|
import { ElMessage } from 'element-plus'
|
||||||
import { QOP_MAP } from '@/management/utils/constant';
|
import 'element-plus/theme-chalk/src/message.scss'
|
||||||
|
|
||||||
|
import { CODE_MAP } from '@/management/api/base'
|
||||||
|
import { updateSurvey, createSurvey } from '@/management/api/survey'
|
||||||
|
import { QOP_MAP } from '@/management/utils/constant'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'modifyDialog',
|
name: 'ModifyDialog',
|
||||||
props: {
|
props: {
|
||||||
type: String,
|
type: String,
|
||||||
questionInfo: Object,
|
questionInfo: Object,
|
||||||
width: String,
|
width: String,
|
||||||
visible: Boolean,
|
visible: Boolean
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
QOP_MAP,
|
QOP_MAP,
|
||||||
loadingInstance: null,
|
loadingInstance: null,
|
||||||
rules: {
|
rules: {
|
||||||
title: [{ required: true, message: '请输入问卷标题', trigger: 'blur' }],
|
title: [{ required: true, message: '请输入问卷标题', trigger: 'blur' }]
|
||||||
},
|
},
|
||||||
current: this.getCurrent(this.questionInfo),
|
current: this.getCurrent(this.questionInfo)
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
questionInfo: {
|
questionInfo: {
|
||||||
handler(val) {
|
handler(val) {
|
||||||
this.current = this.getCurrent(val);
|
this.current = this.getCurrent(val)
|
||||||
},
|
},
|
||||||
deep: true,
|
deep: true
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getCurrent(val) {
|
getCurrent(val) {
|
||||||
return {
|
return {
|
||||||
..._pick(val, ['title', 'remark']),
|
..._pick(val, ['title', 'remark'])
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
onClose() {
|
onClose() {
|
||||||
this.$emit('on-close-codify');
|
this.$emit('on-close-codify')
|
||||||
},
|
},
|
||||||
async onSave() {
|
async onSave() {
|
||||||
if (this.type === QOP_MAP.COPY) {
|
if (this.type === QOP_MAP.COPY) {
|
||||||
await this.handleCopy();
|
await this.handleCopy()
|
||||||
} else {
|
} else {
|
||||||
await this.handleUpdate();
|
await this.handleUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$emit('on-close-codify', 'update');
|
this.$emit('on-close-codify', 'update')
|
||||||
},
|
},
|
||||||
async handleUpdate() {
|
async handleUpdate() {
|
||||||
try {
|
try {
|
||||||
const res = await updateSurvey({
|
const res = await updateSurvey({
|
||||||
surveyId: this.questionInfo._id,
|
surveyId: this.questionInfo._id,
|
||||||
...this.current,
|
...this.current
|
||||||
});
|
})
|
||||||
|
|
||||||
if (res.code === CODE_MAP.SUCCESS) {
|
if (res.code === CODE_MAP.SUCCESS) {
|
||||||
this.$message.success('修改成功');
|
ElMessage.success('修改成功')
|
||||||
} else {
|
} else {
|
||||||
this.$message.error(res.errmsg);
|
ElMessage.error(res.errmsg)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.$message.error(err);
|
ElMessage.error(err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async handleCopy() {
|
async handleCopy() {
|
||||||
@ -102,26 +108,30 @@ export default {
|
|||||||
const res = await createSurvey({
|
const res = await createSurvey({
|
||||||
createFrom: this.questionInfo._id,
|
createFrom: this.questionInfo._id,
|
||||||
createMethod: QOP_MAP.COPY,
|
createMethod: QOP_MAP.COPY,
|
||||||
...this.current,
|
...this.current
|
||||||
});
|
})
|
||||||
|
|
||||||
if (res.code === CODE_MAP.SUCCESS) {
|
if (res.code === CODE_MAP.SUCCESS) {
|
||||||
const { data } = res;
|
const { data } = res
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
name: 'QuestionEditIndex',
|
name: 'QuestionEditIndex',
|
||||||
params: {
|
params: {
|
||||||
id: data.id,
|
id: data.id
|
||||||
},
|
}
|
||||||
});
|
})
|
||||||
} else {
|
} else {
|
||||||
this.$message.error(res.errmsg);
|
ElMessage.error(res.errmsg)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.$message.error(err);
|
ElMessage.error(err)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" rel="lang/scss" scoped></style>
|
<style lang="scss" rel="lang/scss" scoped>
|
||||||
|
.base-form-root {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
@ -4,21 +4,23 @@
|
|||||||
<span>{{ statusMaps[value.curStatus.status] }}</span>
|
<span>{{ statusMaps[value.curStatus.status] }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { statusMaps } from '../config';
|
import { statusMaps } from '../config'
|
||||||
export default {
|
export default {
|
||||||
name: 'State',
|
name: 'StateModule',
|
||||||
props: {
|
props: {
|
||||||
value: Object,
|
value: Object
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
statusMaps,
|
statusMaps
|
||||||
};
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
.list-state {
|
.list-state {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
15
web/src/management/pages/list/components/StaticIcon.vue
Normal file
15
web/src/management/pages/list/components/StaticIcon.vue
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<template>
|
||||||
|
<i-ep-sort v-if="props.icon === 'sort'" />
|
||||||
|
<i-ep-sort-up v-if="props.icon === 'sort-up'" />
|
||||||
|
<i-ep-sort-down v-if="props.icon === 'sort-down'" />
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
// unplugin-icons只能处理静态组件,因此将使用的动态icon在这里转成静态组件
|
||||||
|
// see https://github.com/unplugin/unplugin-icons/issues/5
|
||||||
|
const props = defineProps({
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
@ -1,27 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<span :class="['list-tag-root', 'list-tag-' + type]">
|
<span :class="['list-tag-root', 'list-tag-' + type]">
|
||||||
<div class="tag-bg"></div>
|
<div class="tag-bg"></div>
|
||||||
<span>{{
|
<span>{{ surveyType[value.surveyType] || surveyType[value.questionType] }}</span>
|
||||||
surveyType[value.surveyType] || surveyType[value.questionType]
|
|
||||||
}}</span>
|
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { type as surveyType } from '../config';
|
import { type as surveyType } from '../config'
|
||||||
export default {
|
export default {
|
||||||
name: 'Tag',
|
name: 'TagModule',
|
||||||
props: {
|
props: {
|
||||||
value: Object,
|
value: Object,
|
||||||
type: String,
|
type: String
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
surveyType,
|
surveyType
|
||||||
};
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
.list-tag-root {
|
.list-tag-root {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user