Compare commits

...

1 Commits

Author SHA1 Message Date
Weiguo Wang
6cd5fe892d
Feature/vue3 (#98)
* wip: vue3

* fix: dev时C端路由被B端router污染

* wip: debugger material

* wip: render widgets

* wip:回滚editOption

* c端抽离编辑态组件

* wip

* 事件调通

* wip

* feat: 可以正常提交

* fix: typo

* fix: 答题进度条

* fix: form 与 input 事件冲突

* 优化field管理

* 优化代码

* update

* 标题组件

* fix: 组件操作图标样式

---------

Co-authored-by: taoshaung <taoshaung@didiglobal.com>
2024-04-15 11:09:26 +08:00
207 changed files with 2546 additions and 2167 deletions

View File

@ -1,20 +1,15 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = { module.exports = {
root: false, root: true,
env: { 'extends': [
node: true, 'plugin:vue/vue3-essential',
},
extends: [
'plugin:vue/essential',
'eslint:recommended', 'eslint:recommended',
'plugin:prettier/recommended', '@vue/eslint-config-typescript',
'@vue/eslint-config-prettier/skip-formatting'
], ],
parserOptions: { parserOptions: {
parser: '@babel/eslint-parser', ecmaVersion: 'latest'
}, }
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
View File

@ -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

View File

@ -1,5 +0,0 @@
module.exports = {
singleQuote: true, // 使用单引号
semi: true, // 不使用分号
// trailingComma: 'all', // 在对象和数组末尾加上逗号
};

8
web/.prettierrc.json Normal file
View File

@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": false,
"tabWidth": 2,
"singleQuote": true,
"printWidth": 100,
"trailingComma": "none"
}

8
web/env.d.ts vendored Normal file
View 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";
}

View File

@ -2,55 +2,53 @@
"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"
}, },
"dependencies": { "dependencies": {
"@vue/babel-helper-vue-jsx-merge-props": "^1.4.0", "@element-plus/icons-vue": "^2.3.1",
"@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.5.5",
"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", "@tsconfig/node20": "^20.1.2",
"@babel/eslint-parser": "^7.12.16", "@types/node": "^20.11.19",
"@vue/cli-plugin-babel": "~5.0.0", "@vitejs/plugin-vue": "^5.0.3",
"@vue/cli-plugin-eslint": "~5.0.0", "@vitejs/plugin-vue-jsx": "^3.1.0",
"@vue/cli-plugin-router": "~5.0.0", "@vue/eslint-config-prettier": "^8.0.0",
"@vue/cli-plugin-vuex": "~5.0.0", "@vue/eslint-config-typescript": "^12.0.0",
"@vue/cli-service": "~5.0.0", "@vue/tsconfig": "^0.5.1",
"eslint": "^7.32.0", "eslint": "^8.49.0",
"eslint-config-prettier": "^8.3.0", "eslint-plugin-vue": "^9.17.0",
"eslint-plugin-prettier": "^4.0.0", "npm-run-all2": "^6.1.1",
"eslint-plugin-vue": "^8.7.1", "prettier": "^3.0.3",
"less-loader": "^11.1.3", "sass": "^1.72.0",
"postcss-import": "^15.1.0", "typescript": "~5.3.0",
"postcss-url": "^10.1.3", "unplugin-element-plus": "^0.8.0",
"prettier": "^2.4.1", "vite": "^5.1.4",
"sass": "^1.32.7", "vite-plugin-virtual-mpa": "^1.10.1",
"sass-loader": "^12.0.0", "vue-tsc": "^1.8.27"
"speed-measure-webpack-plugin": "^1.5.0",
"style-resources-loader": "^1.5.0",
"vue-style-loader": "^4.1.3",
"vue-template-compiler": "^2.7.14"
}, },
"engines": { "engines": {
"node": ">=14.21.0", "node": ">=14.21.0",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

View File

@ -1,10 +1,10 @@
<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: {

View File

@ -1,118 +1,111 @@
<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 '@/management/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: '',
editor: null,
showToolbar: false,
};
},
props: ['value'], // value v-model
mounted() {
this.create();
},
watch: {
value: {
immediate: true,
handler(newVal) {
const isEqual = newVal === this.curValue;
if (isEqual) return; //
// HTML const curValue = ref('')
this.setHtml(newVal); const editorRef = shallowRef()
}, const showToolbar = ref(props.staticToolBar || false)
},
},
methods: {
// HTML
setHtml(newHtml) {
const editor = this.editor;
if (editor === null) return;
editor.setHtml(newHtml);
},
// editor
create() {
if (this.$refs.editor === null) return;
createEditor({ const mode = 'simple'
selector: this.$refs.editor,
html: this.defaultHtml || this.value || '',
config: {
onCreated: (editor) => {
this.editor = Object.seal(editor);
if (this.value) { const toolbarConfig = {
this.setHtml(this.value);
}
this.$refs.toolbar &&
createToolbar({
editor,
selector: this.$refs.toolbar,
config: {
toolbarKeys: [ toolbarKeys: [
'color', // 'color', //
'bgColor', // 'bgColor', //
'bold', 'bold',
// 'insertImage', //
// 'video',
// 'fontSize', //
// 'justify', //
'insertLink', // 'insertLink', //
// 'clean',
], ],
}, }
mode: 'simple',
}); const editorConfig = {}
},
onChange: (editor) => { 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(); const editorHtml = editor.getHtml();
this.curValue = editorHtml; // html curValue.value = editorHtml; // html
this.$emit('input', editorHtml); // v-model emit('input', editorHtml); // v-model
}, }
onDestroyed: (editor) => { const onFocus = (editor) => {
this.$emit('onDestroyed', editor); emit('onFocus', editor);
editor.destroy(); setToolbarStatus(true);
}, }
onFocus: (editor) => { const onBlur = (editor) => {
this.$emit('onFocus', editor);
this.showToolbar = true;
},
onBlur: (editor) => {
const editorHtml = editor.getHtml(); const editorHtml = editor.getHtml();
this.curValue = editorHtml; // html curValue.value = editorHtml; // html
this.$emit('change', editorHtml); emit('change', editorHtml);
this.$emit('blur', editor); emit('blur', editor);
this.showToolbar = false; 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
setHtml(newVal);
}, },
// customPaste: (editor, event) => { {
// let res; // immediate: true
// this.$emit('customPaste', editor, event, (val) => { }
// res = val; )
// });
// return res; onBeforeMount(() => {
// }, const editor = editorRef.value
}, if (editor == null) return
content: [], editor.destroy()
mode: 'simple', })
});
},
},
};
</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;
@ -120,7 +113,10 @@ export default {
// 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;

View File

@ -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');

View File

@ -1,4 +1,4 @@
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', {
@ -6,5 +6,5 @@ export const getRecycleList = (data) => {
pageSize: 10, pageSize: 10,
...data, ...data,
}, },
}); })
}; }

View File

@ -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)
}; }

View File

@ -1,49 +1,49 @@
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({ const instance = axios.create({
baseURL: '/api', baseURL: '/api',
timeout: 10000, timeout: 10000,
}); })
instance.interceptors.response.use( instance.interceptors.response.use(
(response) => { (response) => {
if (response.status !== 200) { if (response.status !== 200) {
throw new Error('http请求出错'); throw new Error('http请求出错')
} }
const res = response.data; const res = response.data
if (res.code === 403) { if (res.code === 403) {
router.replace({ router.replace({
name: 'login', name: 'login',
}); })
return res; return res
} else { } else {
return res; return res
} }
}, },
(err) => { (err) => {
throw new Error(err); throw new Error(err)
} }
); )
instance.interceptors.request.use((config) => { instance.interceptors.request.use((config) => {
const hasLogined = _get(store, 'state.user.hasLogined'); const hasLogined = _get(store, 'state.user.hasLogined')
const token = _get(store, 'state.user.userInfo.token'); const token = _get(store, 'state.user.userInfo.token')
if (hasLogined && token) { if (hasLogined && token) {
if (!config.headers) { if (!config.headers) {
config.headers = {}; config.headers = {}
} }
config.headers.Authorization = `Bearer ${token}`; config.headers.Authorization = `Bearer ${token}`
} }
return config; return config
}); })
export default instance; export default instance
export const CODE_MAP = { export const CODE_MAP = {
SUCCESS: 200, SUCCESS: 200,
ERROR: 500, ERROR: 500,
NOTAUTH: 403, NOTAUTH: 403,
}; }

View File

@ -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 })
}; }

View File

@ -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')
}; }

View File

@ -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', {
@ -8,30 +8,30 @@ export const getSurveyList = ({ curPage, filter, order }) => {
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', {
@ -39,15 +39,15 @@ export const getSurveyHistory = ({ surveyId, historyType }) => {
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)
}; }

View File

@ -2,7 +2,7 @@
<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>
@ -15,7 +15,7 @@ export default {
required: true, required: true,
}, },
}, },
}; }
</script> </script>
<style lang="scss" rel="stylesheet/scss" scoped> <style lang="scss" rel="stylesheet/scss" scoped>

View File

@ -17,7 +17,7 @@
</template> </template>
<script> <script>
import logo from './logo.vue'; import logo from './logo.vue'
export default { export default {
name: 'leftMenu', name: 'leftMenu',
@ -49,9 +49,9 @@ export default {
}, },
}, },
], ],
}; }
}, },
}; }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -3,6 +3,7 @@
<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',
@ -10,11 +11,12 @@ export default {
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;

View File

@ -0,0 +1,44 @@
export default {
metaData: null,
bannerConf: {
titleConfig: {
mainTitle: '<h3 style="text-align: center">欢迎填写问卷</h3>',
subTitle: `<p>为了给您提供更好的服务,希望您能抽出几分钟时间,将您的感受和建议告诉我们,<span style="color: rgb(204, 0, 0)">期待您的参与!</span></p>`,
applyTitle: '',
},
bannerConfig: {
bgImage: '',
bgImageAllowJump: false,
bgImageJumpLink: '',
videoLink: '',
postImg: '',
},
},
bottomConf: {
logoImage: '',
logoImageWidth: '28%',
},
skinConf: {
skinColor: '#4a4c5b',
inputBgColor: '#ffffff',
},
baseConf: {
begTime: '',
endTime: '',
language: 'chinese',
showVoteProcess: 'allow',
tLimit: 0,
answerBegTime: '',
answerEndTime: '',
answerLimitTime: 0,
},
submitConf: {
submitTitle: '',
msgContent: {},
confirmAgain: {
is_again: true,
},
link: '',
},
questionDataList: [],
};

View File

@ -74,4 +74,4 @@ export const defaultQuestionConfig = {
value: 500, value: 500,
}, },
}, },
}; }

View File

@ -55,7 +55,7 @@ const menuItems = {
icon: 'tixing-toupiao', icon: 'tixing-toupiao',
title: '投票', title: '投票',
}, },
}; }
const menuGroup = [ const menuGroup = [
{ {
@ -73,15 +73,13 @@ const menuGroup = [
'vote', '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

View File

@ -1,3 +1,4 @@
export default [ export default [
{ {
label: '顶部图片地址', label: '顶部图片地址',
@ -37,5 +38,22 @@ export default [
relyFunc: (data) => { relyFunc: (data) => {
return !!data?.bgImageAllowJump; return !!data?.bgImageAllowJump;
}, },
content: [
{
label: '图片支持点击',
type: 'CustomedSwitch',
direction: 'space_between',
key: 'bannerConfig.bgImageAllowJump',
}, },
]; {
label: '跳转链接',
type: 'Input',
direction: 'horizon',
key: 'bannerConfig.bgImageJumpLink',
relyFunc: (data) => {
return !!data?.bannerConfig?.bgImageAllowJump
},
},
],
},
]

View File

@ -15,4 +15,4 @@ export default [
direction: 'horizon', direction: 'horizon',
labelStyle: { width: '120px' } labelStyle: { width: '120px' }
}, },
]; ]

View File

@ -68,4 +68,4 @@ export default [
}, },
], ],
}, },
]; ]

View 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

View 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

View File

@ -4,11 +4,10 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>imgs/favicon.ico"> <link rel="icon" href="/imgs/favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
<!-- built files will be auto injected --> <!-- <script type="module" src="./main.ts"></script> -->
</body> </body>
</html> </html>

View File

@ -1,36 +0,0 @@
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
import ElementUI from 'element-ui';
import './styles/element-variables.scss';
import { filterXSS, cleanRichText } from '@/common/xss';
Vue.config.productionTip = false;
Vue.use(ElementUI);
const safeHtml = function (el, binding) {
const res = filterXSS(binding.value);
el.innerHTML = res;
};
const plainText = function (el, binding) {
const text = cleanRichText(binding.value);
el.innerText = text;
};
Vue.directive('safe-html', {
inserted: safeHtml,
componentUpdated: safeHtml,
});
Vue.directive('plain-text', {
inserted: plainText,
componentUpdated: plainText,
});
new Vue({
router,
store,
render: (h) => h(App),
}).$mount('#app');

View File

@ -0,0 +1,23 @@
import { createApp } from 'vue'
import store from './store'
import './styles/element-variables.scss'
import ElementPlus from 'element-plus'
// 手动引入避免ElMessage动态加载导致的el主题覆盖自定义主题
import 'element-plus/theme-chalk/el-message.css'
import 'element-plus/theme-chalk/el-message-box.css'
import plainText from './directive/plainText'
import safeHtml from './directive/safeHtml'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(store)
app.use(router)
app.use(ElementPlus, { size: 'default'})
app.use(plainText)
app.use(safeHtml)
app.mount('#app')

View File

@ -18,7 +18,7 @@
:label="cleanRichText(item.title)" :label="cleanRichText(item.title)"
minWidth="200" minWidth="200"
> >
<template slot="header" slot-scope="scope"> <template #header="scope">
<div class="table-row-cell"> <div class="table-row-cell">
<span slot="reference" v-popover="scope.column.id"> <span slot="reference" v-popover="scope.column.id">
{{ scope.column.label.replace(/&nbsp;/g, '') }} {{ scope.column.label.replace(/&nbsp;/g, '') }}
@ -33,7 +33,7 @@
</el-popover> </el-popover>
</div> </div>
</template> </template>
<template slot-scope="scope"> <template #default="scope">
<span <span
slot="reference" slot="reference"
class="table-row-cell" class="table-row-cell"
@ -54,13 +54,14 @@
</el-table> </el-table>
</div> </div>
</template> </template>
<script> <script>
import { cleanRichText } from '@/common/xss'; import { cleanRichText } from '@/common/xss'
export default { export default {
name: 'DataTable', name: 'DataTable',
data() { data() {
return {}; return {}
}, },
props: { props: {
mainTableLoading: Boolean, mainTableLoading: Boolean,
@ -75,8 +76,9 @@ export default {
return content === 0 ? 0 : (content || '未知') return content === 0 ? 0 : (content || '未知')
} }
}, },
}; }
</script> </script>
<style lang="scss" rel="stylesheet/scss" scoped> <style lang="scss" rel="stylesheet/scss" scoped>
.data-table-wrapper { .data-table-wrapper {
position: relative; position: relative;
@ -89,7 +91,7 @@ export default {
box-sizing: border-box; box-sizing: border-box;
text-align: center; text-align: center;
} }
::v-deep .el-table__header { :deep(.el-table__header) {
width: 100%; width: 100%;
.thead-cell .el-table__cell { .thead-cell .el-table__cell {
.cell { .cell {

View File

@ -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"
> >
@ -36,10 +36,10 @@
</template> </template>
<script> <script>
import DataTable from './components/table.vue'; import DataTable from './components/table.vue'
import empty from '@/management/components/empty'; import empty from '@/management/components/empty.vue'
import leftMenu from '@/management/components/leftMenu.vue'; import leftMenu from '@/management/components/leftMenu.vue'
import { getRecycleList } from '@/management/api/analysis'; import { getRecycleList } from '@/management/api/analysis'
export default { export default {
name: 'analysisPage', name: 'analysisPage',
@ -59,71 +59,71 @@ export default {
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('没有传入问卷参数~'); this.$message.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('查询回收数据失败,请重试'); this.$message.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
}, },
}, },
@ -132,7 +132,7 @@ export default {
empty, empty,
leftMenu, leftMenu,
}, },
}; }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -166,7 +166,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;

View File

@ -14,14 +14,12 @@
<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="请输入备注"
@ -32,7 +30,6 @@
<el-button <el-button
class="create-btn" class="create-btn"
type="primary" type="primary"
size="small"
@click="submit" @click="submit"
:loading="!canSubmit" :loading="!canSubmit"
> >
@ -42,9 +39,10 @@
</el-form> </el-form>
</div> </div>
</template> </template>
<script> <script>
import { SURVEY_TYPE_LIST } from '../types'; import { SURVEY_TYPE_LIST } from '../types'
import { createSurvey } from '@/management/api/survey'; import { createSurvey } from '@/management/api/survey'
export default { export default {
name: 'CreateForm', name: 'CreateForm',
@ -64,55 +62,56 @@ export default {
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 || '创建失败'); this.$message.error(res.errmsg || '创建失败')
} }
this.canSubmit = true; this.canSubmit = true
}); })
}, },
}, },
}; }
</script> </script>
<style lang="scss" rel="stylesheet/scss" scoped> <style lang="scss" rel="stylesheet/scss" scoped>
.right-side { .right-side {
width: 538px; width: 538px;
@ -142,7 +141,7 @@ export default {
border: unset; border: unset;
color: white; color: white;
::v-deep span { :deep(span) {
font-size: 14px; font-size: 14px;
} }
} }

View File

@ -8,6 +8,7 @@
</div> </div>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
props: { props: {
@ -20,20 +21,21 @@ export default {
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" rel="stylesheet/scss" scoped>
.nav-header { .nav-header {
z-index: 99; z-index: 99;

View File

@ -23,10 +23,11 @@
</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',
@ -41,17 +42,18 @@ export 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" rel="stylesheet/scss" scoped>
.left-side { .left-side {
position: relative; position: relative;

View File

@ -7,9 +7,10 @@
<create-form :selectType="selectType" /> <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',
@ -20,15 +21,16 @@ export default {
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" rel="stylesheet/scss" scoped>
.new { .new {
position: relative; position: relative;

View File

@ -23,4 +23,4 @@ export const SURVEY_TYPE_LIST = [
img: '/imgs/create/register-icon.webp', img: '/imgs/create/register-icon.webp',
desc: '活动报名 / 会议报名', desc: '活动报名 / 会议报名',
}, },
]; ]

View File

@ -1,29 +1,35 @@
<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, useAttrs, getCurrentInstance } from 'vue'
};
const slots = useSlots()
const attrs = useAttrs()
const current = getCurrentInstance()
window.vm = current
console.log('current',current)
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.main { .main {
width: 100%; width: 100%;

View File

@ -14,6 +14,7 @@
</div> </div>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: 'LogoPreview', name: 'LogoPreview',
@ -25,21 +26,22 @@ export 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;

View File

@ -2,19 +2,20 @@
<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: {
@ -31,27 +32,24 @@ export default {
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" rel="stylesheet/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;
} }
} }

View File

@ -1,13 +1,50 @@
<template>
<draggable
:list="renderData"
:options="dragOptions"
: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"
@changeSeq="handleChangeSeq"
>
<QuestionContainer
v-bind="$attrs"
:type="element.type"
:moduleConfig="element"
:indexNumber="element.indexNumber"
:isSelected="currentEditOne === index"
:readonly="true"
@select="handleSelect"
></QuestionContainer>
</questionWrapper>
</template>
</draggable>
</template>
<script> <script>
import { computed, defineComponent, ref, getCurrentInstance } from 'vue'; import { computed, defineComponent, ref, getCurrentInstance, h } from 'vue'
import QuestionContainer from '@/materials/questions/widgets/QuestionContainer.jsx'; import QuestionContainer from '@/materials/questions/widgets/QuestionContainer.jsx'
import questionWrapper from './questionWrapper.vue'; import questionWrapper from './questionWrapper.vue'
import draggable from 'vuedraggable'; import draggable from 'vuedraggable'
import { filterQuestionPreviewData } from '@/management/utils/index'; import { filterQuestionPreviewData } from '@/management/utils/index'
export default defineComponent({ export default defineComponent({
name: '', components: {
components: { draggable }, draggable,
questionWrapper,
QuestionContainer,
},
props: { props: {
currentEditOne: { currentEditOne: {
type: [Number, String], type: [Number, String],
@ -16,41 +53,40 @@ export default defineComponent({
questionDataList: { questionDataList: {
type: Array, type: Array,
default: () => { default: () => {
return []; return []
}, },
}, },
}, },
watch: {},
setup(props, { emit }) { setup(props, { emit }) {
const renderData = computed(() => { const renderData = computed(() => {
return filterQuestionPreviewData(props.questionDataList); return filterQuestionPreviewData(props.questionDataList)
}); })
const handleSelect = (index) => { const handleSelect = (index) => {
emit('select', index); emit('select', index)
}; }
const handleChangeSeq = (data) => { const handleChangeSeq = (data) => {
emit('changeSeq', data); emit('changeSeq', data)
}; }
const isMoving = ref(false); const isMoving = ref(false)
const checkMove = () => { const checkMove = () => {
isMoving.value = true; isMoving.value = true
}; }
const checkEnd = ({ oldIndex, newIndex }) => { const checkEnd = ({ oldIndex, newIndex }) => {
emit('changeSeq', { emit('changeSeq', {
type: 'move', type: 'move',
index: oldIndex, index: oldIndex,
range: newIndex - oldIndex, range: newIndex - oldIndex,
}); })
}; }
const instance = getCurrentInstance(); const instance = getCurrentInstance()
const getQuestionRefByField = (field) => { const getQuestionRefByField = (field) => {
return instance?.proxy?.$refs[`questionWrapper-${field}`] || null; return instance?.proxy?.$refs[`questionWrapper-${field}`] || null
}; }
return { return {
renderData, renderData,
@ -68,54 +104,7 @@ export default defineComponent({
forceFallback: true, forceFallback: true,
}, },
getQuestionRefByField, 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> </script>

View File

@ -16,14 +16,14 @@
</template> </template>
<script> <script>
import back from '../modules/generalModule/back.vue'; import back from '../modules/generalModule/back.vue'
import pageTitle from '../modules/generalModule/pageTitle.vue'; import pageTitle from '../modules/generalModule/pageTitle.vue'
import pageNav from '../modules/generalModule/pageNav.vue'; import pageNav from '../modules/generalModule/pageNav.vue'
import history from '../modules/contentModule/history.vue'; import history from '../modules/contentModule/history.vue'
import save from '../modules/contentModule/save.vue'; import save from '../modules/contentModule/save.vue'
import publish from '../modules/contentModule/publish.vue'; import publish from '../modules/contentModule/publish.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: 'navbar',
@ -36,14 +36,14 @@ export default {
publish, publish,
}, },
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>

View File

@ -1,4 +1,4 @@
<script> <script lang="jsx">
import { import {
defineComponent, defineComponent,
reactive, reactive,
@ -6,6 +6,7 @@ import {
computed, computed,
getCurrentInstance, getCurrentInstance,
} from 'vue'; } from 'vue';
import { Rank, Top, Bottom, CopyDocument, Delete } from '@element-plus/icons-vue'
export default defineComponent({ export default defineComponent({
name: 'QuestionWrapper', name: 'QuestionWrapper',
@ -119,6 +120,7 @@ export default defineComponent({
// }) // })
// state.isHover = false // state.isHover = false
// } // }
const onMove = () => {}
return { return {
...toRefs(state), ...toRefs(state),
itemClass, itemClass,
@ -127,6 +129,7 @@ export default defineComponent({
showDown, showDown,
showCopy, showCopy,
onCopy, onCopy,
onMove,
onMoveUp, onMoveUp,
onMoveDown, onMoveDown,
onDelete, onDelete,
@ -148,36 +151,46 @@ export default defineComponent({
onClick={this.clickFormItem} onClick={this.clickFormItem}
> >
{this.moduleConfig.type !== 'section' && ( {this.moduleConfig.type !== 'section' && (
<div>{this.$slots.default}</div> <div>{this.$slots.default()}</div>
)} )}
{ {
<div class={[showHover ? 'visibily' : 'hidden', 'hoverItem']}> <div class={[showHover ? 'visibily' : 'hidden', 'hoverItem']}>
<div <div
class="item move el-icon-rank" class="item"
vOn:click_stop_prevent={this.onMove} onClickPrevent={this.onMove}
></div> >
<el-icon><Rank /></el-icon>
</div>
{showUp && ( {showUp && (
<div <div
class="item iconfont icon-shangyi" class="item"
vOn:click_stop_prevent={this.onMoveUp} onClickPrevent={this.onMoveUp}
></div> >
<el-icon><Top /></el-icon>
</div>
)} )}
{showDown && ( {showDown && (
<div <div
class="item iconfont icon-xiayi" class="item"
vOn:click_stop_prevent={this.onMoveDown} onClickPrevent={this.onMoveDown}
></div> >
<el-icon><Bottom /></el-icon>
</div>
)} )}
{showCopy && ( {showCopy && (
<div <div
class="item copy iconfont icon-fuzhi" class="item"
vOn:click_stop_prevent={this.onCopy} onClickPrevent={this.onCopy}
></div> >
<el-icon><CopyDocument /></el-icon>
</div>
)} )}
<div <div
class="item iconfont icon-shanchu" class="item"
vOn:click_stop_prevent={this.onDelete} onClickPrevent={this.onDelete}
></div> >
<el-icon><Delete /></el-icon>
</div>
</div> </div>
} }
</div> </div>
@ -193,9 +206,9 @@ export default defineComponent({
&.spliter { &.spliter {
border-bottom: 0.12rem solid $spliter-color; border-bottom: 0.12rem solid $spliter-color;
} }
&:last-child{ // &:last-child{
border: none; // border: none;
} // }
.editor { .editor {
display: flex; display: flex;
font-size: 0.32rem; font-size: 0.32rem;
@ -276,8 +289,10 @@ export default defineComponent({
display: none; display: none;
} }
.item { .item {
display: flex;
align-items: center;
justify-content: center;
margin-top: 5px; margin-top: 5px;
display: inline-block;
width: 28px; width: 28px;
height: 28px; height: 28px;
border-radius: 50%; border-radius: 50%;

View File

@ -1,14 +1,13 @@
<template> <template>
<el-form <el-form
class="config-form" class="config-form"
size="small"
:labelPosition="labelPosition" :labelPosition="labelPosition"
label-width="110px" label-width="110px"
:inline="inline" :inline="inline"
@submit.native.prevent @submit.native.prevent
> >
<template v-for="(item, index) in formFieldData"> <template v-for="(item, index) in formFieldData" :key="item.key + index">
<FormItem :key="item.key + index" class="form-item" :form-config="item"> <FormItem class="form-item" :form-config="item">
<template v-if="item.type === 'Customed'"> <template v-if="item.type === 'Customed'">
<SettersField <SettersField
:key="index" :key="index"
@ -32,35 +31,32 @@
</template> </template>
</el-form> </el-form>
</template> </template>
<script> <script>
import { import { get as _get, pick as _pick, isFunction as _isFunction } from 'lodash-es'
get as _get,
pick as _pick,
isFunction as _isFunction,
} from 'lodash-es';
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 { FORM_CHANGE_EVENT_KEY } from '@/materials/setters/constant'; import { FORM_CHANGE_EVENT_KEY } from '@/materials/setters/constant'
const formatValue = ({ item, moduleConfig }) => { const formatValue = ({ item, moduleConfig }) => {
if (_isFunction(item.valueAdapter)) { if (_isFunction(item.valueAdapter)) {
const value = item.valueAdapter({ moduleConfig }); const value = item.valueAdapter({ moduleConfig })
return value; return value
} else { } else {
const { key, keys } = item; const { key, keys } = item
let result = null; let result = null
if (key) { if (key) {
result = _get(moduleConfig, key, item.value); result = _get(moduleConfig, key, item.value)
} }
if (keys) { if (keys) {
result = _pick(moduleConfig, keys); result = _pick(moduleConfig, keys)
} }
return result; return result
} }
}; }
export default { export default {
name: 'SettersField', name: 'SettersField',
@ -79,7 +75,7 @@ export default {
data() { data() {
return { return {
registerd: {}, registerd: {},
}; }
}, },
components: { components: {
FormItem, FormItem,
@ -89,25 +85,25 @@ export default {
return this.formConfigList return this.formConfigList
.filter((item) => { .filter((item) => {
if (!item.type) { if (!item.type) {
return false; return false
} }
if (item.type !== 'Customed' && !this.registerd[item.type]) { if (item.type !== 'Customed' && !this.registerd[item.type]) {
return false; return false
} }
if (item.hidden) { if (item.hidden) {
return false; return false
} }
if (_isFunction(item.relyFunc)) { if (_isFunction(item.relyFunc)) {
return item.relyFunc(this.moduleConfig); return item.relyFunc(this.moduleConfig)
} }
return true; return true
}) })
.map((item) => { .map((item) => {
return { return {
...item, ...item,
value: formatValue({ item, moduleConfig: this.moduleConfig }), value: formatValue({ item, moduleConfig: this.moduleConfig }),
}; }
}); })
}, },
}, },
watch: { watch: {
@ -116,61 +112,62 @@ export default {
immediate: true, immediate: true,
handler(newVal) { handler(newVal) {
if (!newVal || !newVal.length) { if (!newVal || !newVal.length) {
return; return
} }
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
if (!this.$options.components) { if (!this.$options.components) {
this.$options.components = {}; this.$options.components = {}
} }
this.$options.components[componentName] = component; this.$options.components[componentName] = component
this.$set(this.registerd, type, componentName); this.registerd[type] = componentName
} }
} }
} catch (err) { } catch (err) {
console.error(err); console.error(err)
} }
}, },
onFormChange(data, formConfig) { onFormChange(data, formConfig) {
if (_isFunction(formConfig?.setterAdapter)) { if (_isFunction(formConfig?.setterAdapter)) {
const resultData = formConfig.setterAdapter(data); const resultData = formConfig.setterAdapter(data)
if (Array.isArray(resultData)) { if (Array.isArray(resultData)) {
resultData.forEach((item) => { resultData.forEach((item) => {
this.$emit(FORM_CHANGE_EVENT_KEY, item); this.$emit(FORM_CHANGE_EVENT_KEY, item)
}); })
} else { } else {
this.$emit(FORM_CHANGE_EVENT_KEY, resultData); this.$emit(FORM_CHANGE_EVENT_KEY, resultData)
} }
} else { } else {
this.$emit(FORM_CHANGE_EVENT_KEY, data); this.$emit(FORM_CHANGE_EVENT_KEY, data)
} }
}, },
}, },
}; }
</script> </script>
<style lang="scss" rel="stylesheet/scss" scoped> <style lang="scss" rel="stylesheet/scss" scoped>
.config-form { .config-form {
padding: 15px 0; padding: 15px 0;
@ -178,11 +175,11 @@ export default {
.nps-customed-config { .nps-customed-config {
.el-form-item { .el-form-item {
margin-right: 0px; margin-right: 0px;
::v-deep .el-form-item__label { :deep(.el-form-item__label) {
width: 70px !important; width: 70px !important;
margin-right: 8px; margin-right: 8px;
} }
::v-deep .el-input__inner { :deep(.el-input__inner) {
width: 234px; width: 234px;
} }
} }

View File

@ -11,11 +11,12 @@
> >
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: 'Submit', name: 'Submit',
data() { data() {
return {}; return {}
}, },
props: { props: {
submitConf: Object, submitConf: Object,
@ -27,11 +28,12 @@ export default {
}, },
methods: { methods: {
onClick() { onClick() {
this.$emit('select'); this.$emit('select')
}, },
}, },
}; }
</script> </script>
<style lang="scss" rel="stylesheet/scss" scoped> <style lang="scss" rel="stylesheet/scss" scoped>
.submit-wrapper { .submit-wrapper {
padding: 25px; padding: 25px;

View File

@ -3,17 +3,21 @@
<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>
</template>
<template #body>
<router-view></router-view>
</template>
</commonTemplate> </commonTemplate>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import commonTemplate from './components/commonTemplate.vue'; import commonTemplate from './components/commonTemplate.vue'
import navbar from './components/navbar.vue'; import navbar from './components/navbar.vue'
import leftMenu from '@/management/components/leftMenu.vue'; import leftMenu from '@/management/components/leftMenu.vue'
export default { export default {
name: 'questionEditPage', name: 'questionEditPage',
components: { components: {
@ -22,21 +26,23 @@ export default {
leftMenu, leftMenu,
}, },
async created() { async created() {
this.$store.commit('edit/setSurveyId', this.$route.params.id); this.$store.commit('edit/setSurveyId', this.$route.params.id)
this.$store.dispatch('getBannerData')
try { try {
await this.$store.dispatch('edit/init'); await this.$store.dispatch('edit/init')
} catch (error) { } catch (error) {
this.$message.error(error.message); this.$message.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 +62,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;
} }

View File

@ -1,8 +1,8 @@
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',
@ -10,13 +10,13 @@ export default function (schema) {
'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,
}; }
} }

View File

@ -22,21 +22,22 @@
</div> </div>
</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: 'history',
@ -45,10 +46,10 @@ export default {
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() {
@ -57,7 +58,7 @@ export default {
publishHis: [], publishHis: [],
currentTab: 'daily', currentTab: 'daily',
visible: false, visible: false,
}; }
}, },
watch: { watch: {
surveyId: { surveyId: {
@ -73,25 +74,26 @@ export default {
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 {

View File

@ -8,17 +8,18 @@
发布 发布
</el-button> </el-button>
</template> </template>
<script> <script>
import { mapState } from 'vuex'; import { mapState } from 'vuex'
import { publishSurvey, saveSurvey } from '@/management/api/survey'; import { publishSurvey, saveSurvey } from '@/management/api/survey'
import buildData from './buildData'; import buildData from './buildData'
import { get as _get } from 'lodash-es'; import { get as _get } from 'lodash-es'
export default { export default {
name: 'publish', name: 'publish',
data() { data() {
return { return {
isPublishing: false, isPublishing: false,
}; }
}, },
computed: { computed: {
...mapState({ ...mapState({
@ -27,40 +28,41 @@ export default {
}, },
methods: { methods: {
async onPublish() { async onPublish() {
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'); this.$message.error('未获取到问卷id')
return; return
} }
if (this.isPublishing) { if (this.isPublishing) {
return; return
} }
try { try {
this.isPublishing = true; this.isPublishing = true
const saveRes = await saveSurvey(saveData); const saveRes = await saveSurvey(saveData)
if (saveRes.code !== 200) { if (saveRes.code !== 200) {
this.$message.error(saveRes.errmsg || '问卷保存失败'); this.$message.error(saveRes.errmsg || '问卷保存失败')
return; return
} }
const publishRes = await publishSurvey({ surveyId: this.surveyId }); const publishRes = await publishSurvey({ surveyId: this.surveyId })
if (publishRes.code === 200) { if (publishRes.code === 200) {
this.$message.success('发布成功'); this.$message.success('发布成功')
this.$store.dispatch('edit/getSchemaFromRemote'); this.$store.dispatch('edit/getSchemaFromRemote')
this.$router.push({ this.$router.push({
name: 'publishResultPage', name: 'publishResultPage',
}); })
} else { } else {
this.$message.error(`发布失败 ${publishRes.errmsg}`); this.$message.error(`发布失败 ${publishRes.errmsg}`)
} }
} catch (error) { } catch (error) {
this.$message.error(`发布失败`); this.$message.error(`发布失败`)
} finally { } finally {
this.isPublishing = false; this.isPublishing = false
} }
}, },
}, },
}; }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.publish-btn { .publish-btn {
width: 100px; width: 100px;

View File

@ -7,29 +7,35 @@
<span class="sv-text"> <span class="sv-text">
{{ saveText }} {{ saveText }}
</span> </span>
<i class="icon el-icon-loading" v-if="autoSaveStatus === 'saving'"></i> <el-icon class="icon" v-if="autoSaveStatus === 'saving'"><el-icon-loading /></el-icon>
<i <el-icon class="icon succeed" v-else-if="autoSaveStatus === 'succeed'"><el-icon-check /></el-icon>
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 {
import buildData from './buildData'; Loading as ElIconLoading,
import { mapState } from 'vuex'; Check as ElIconCheck,
import { get as _get } from 'lodash-es'; } from '@element-plus/icons-vue'
import { saveSurvey } from '@/management/api/survey'
import buildData from './buildData'
import { mapState } from 'vuex'
import { get as _get } from 'lodash-es'
export default { export default {
components: {
ElIconLoading,
ElIconCheck,
},
name: 'save', name: 'save',
data() { data() {
return { return {
isSaving: false, isSaving: false,
isShowAutoSave: false, isShowAutoSave: false,
autoSaveStatus: 'succeed', autoSaveStatus: 'succeed',
}; }
}, },
computed: { computed: {
...mapState({ ...mapState({
@ -40,13 +46,13 @@ export default {
saving: '保存中', saving: '保存中',
succeed: '保存成功', succeed: '保存成功',
failed: '保存失败', failed: '保存失败',
}; }
return statusMap[this.autoSaveStatus]; return statusMap[this.autoSaveStatus]
}, },
}, },
watch: { watch: {
schemaUpdateTime() { schemaUpdateTime() {
this.triggerAutoSave(); this.triggerAutoSave()
}, },
}, },
methods: { methods: {
@ -54,68 +60,69 @@ export default {
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'); this.$message.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('保存成功'); this.$message.success('保存成功')
} else { } else {
this.$message.error(res.errmsg); this.$message.error(res.errmsg)
} }
} catch (error) { } catch (error) {
this.$message.error('保存问卷失败'); this.$message.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');

View File

@ -4,16 +4,18 @@
<span>返回</span> <span>返回</span>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: 'back', name: 'back',
methods: { methods: {
onBack() { onBack() {
this.$router.go(-1); this.$router.go(-1)
}, },
}, },
}; }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.back-btn { .back-btn {
height: 100%; height: 100%;

View File

@ -1,9 +1,7 @@
<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" tag="div"
replace replace
@ -12,6 +10,7 @@
> >
<div <div
:class="[ :class="[
'navbar-btn',
(isActive && btnItem.key === 'skinsettings' ) || isExactActive ? 'router-link-exact-active' : '']" (isActive && btnItem.key === 'skinsettings' ) || isExactActive ? 'router-link-exact-active' : '']"
> >
<i class="iconfont" :class="[btnItem.icon]"></i> <i class="iconfont" :class="[btnItem.icon]"></i>
@ -23,6 +22,7 @@
</template> </template>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: 'pageNav', name: 'pageNav',
@ -52,10 +52,11 @@ export default {
next: true, next: true,
}, },
], ],
}; }
}, },
}; }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.content { .content {
display: flex; display: flex;

View File

@ -3,6 +3,7 @@
{{ title }} {{ title }}
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: 'pageTitle', name: 'pageTitle',
@ -12,8 +13,9 @@ export default {
default: '', default: '',
}, },
}, },
}; }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.title { .title {
overflow: hidden; overflow: hidden;

View File

@ -1,32 +1,33 @@
<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 /> <Catalog />
</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 Catalog from './components/catalog.vue'
export default { export default {
name: 'EditLeftTabPanel', name: 'EditLeftTabPanel',
data() { data() {
return { return {
tabSelected: '0', tabSelected: '0',
}; }
}, },
components: { components: {
typeList, TypeList,
catalog, Catalog,
}, },
methods: {}, methods: {},
}; }
</script> </script>
<style lang="scss" rel="stylesheet/scss" scoped> <style lang="scss" rel="stylesheet/scss" scoped>
.tab-box { .tab-box {
width: 300px; width: 300px;
@ -34,10 +35,10 @@ export default {
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;
} }

View File

@ -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"
@end="onDragEnd"
itemKey="field"
handle=".draggHandle"
host-class="catalog-item-ghost"
>
<template #item="{ element, index }">
<catalogItem <catalogItem
:key="catalogItem.field" :title="element.title"
:title="catalogItem.title" :indexNumber="element.indexNumber"
:indexNumber="catalogItem.indexNumber" :showIndex="element.showIndex"
:showIndex="catalogItem.showIndex"
@select="onSelect(index)" @select="onSelect(index)"
/> />
</template> </template>
@ -15,27 +20,22 @@
</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: {
@ -44,18 +44,19 @@ export default {
}, },
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" rel="stylesheet/scss" scoped>
.question-catalog-wrapper { .question-catalog-wrapper {
padding-bottom: 400px; // padding-bottom: 400px; //

View File

@ -12,7 +12,7 @@
export default { export default {
name: 'QuestionCatalogItem', name: 'QuestionCatalogItem',
data() { data() {
return {}; return {}
}, },
computed: {}, computed: {},
props: { props: {
@ -32,11 +32,12 @@ export default {
components: {}, components: {},
methods: { methods: {
onSelect() { onSelect() {
this.$emit('select'); this.$emit('select')
}, },
}, },
}; }
</script> </script>
<style lang="scss" rel="stylesheet/scss" scoped> <style lang="scss" rel="stylesheet/scss" scoped>
.question-catalog-item { .question-catalog-item {
position: relative; position: relative;

Some files were not shown because too many files have changed in this diff Show More