feat: 问卷列表新增类型和状态筛选 (#51)
This commit is contained in:
parent
642f4f08eb
commit
4d88a33856
@ -183,3 +183,10 @@ npm run serve
|
||||
|
||||
## QQ
|
||||
[<img src="https://img-hxy021.didistatic.com/static/starimg/img/iJUmLIHKV21700192846057.png" width="300" />](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=P61UJI_q8AzizyBLGOm-bUvzNrUnSQq-&authKey=yZFtL9biGB5yiIME3%2Bi%2Bf6XMOdTNiuf0pCIaviEEAIryySNzVy6LJ4xl7uHdEcrM&noverify=0&group_code=920623419)
|
||||
|
||||
# Feature
|
||||
[官方Feature](https://github.com/didi/xiaoju-survey/issues/45)
|
||||
|
||||
# CHANGELOG
|
||||
[MAJOR CHANGELOG](https://github.com/didi/xiaoju-survey/issues/48)
|
||||
|
||||
|
@ -19,7 +19,6 @@
|
||||
"@types/koa-bodyparser": "^4.3.10",
|
||||
"@types/koa-router": "^7.4.4",
|
||||
"@types/koa-static": "^4.0.4",
|
||||
"@types/node": "^20.10.8",
|
||||
"@typescript-eslint/eslint-plugin": "^6.15.0",
|
||||
"@typescript-eslint/parser": "^6.15.0",
|
||||
"cross-env": "^7.0.3",
|
||||
@ -33,7 +32,6 @@
|
||||
"dependencies": {
|
||||
"cheerio": "^1.0.0-rc.12",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"glob": "^10.3.10",
|
||||
"joi": "^17.9.2",
|
||||
"jsonwebtoken": "^9.0.1",
|
||||
|
@ -141,7 +141,7 @@ export default class SurveyManage {
|
||||
}
|
||||
|
||||
private getFilter(filterList: Array<FilterItem>) {
|
||||
const allowFilterField = ['title', 'remark', 'surveyType', 'curStatus.status'];
|
||||
const allowFilterField = ['title', 'remark', 'questionType', 'curStatus.status'];
|
||||
return filterList.reduce((preItem, curItem) => {
|
||||
const condition = curItem.condition.filter(item => allowFilterField.includes(item.field)).reduce((pre, cur) => {
|
||||
switch(cur.comparator) {
|
||||
|
@ -1,15 +1,16 @@
|
||||
|
||||
import { SurveyApp, SurveyServer } from '../../decorator';
|
||||
import { Request, Response } from 'koa';
|
||||
import * as Joi from 'joi';
|
||||
import { userService } from './service/userService';
|
||||
import { captchaService } from './service/captchaService';
|
||||
import { getValidateValue } from './utils/index';
|
||||
|
||||
import { CommonError } from '../../types/index';
|
||||
|
||||
@SurveyApp('/api/user')
|
||||
export default class User {
|
||||
@SurveyServer({ type: 'http', method: 'post', routerName: '/register' })
|
||||
async register({ req }) {
|
||||
async register({ req }: { req: Request, res: Response }) {
|
||||
const userInfo = getValidateValue(Joi.object({
|
||||
username: Joi.string().required(),
|
||||
password: Joi.string().required(),
|
||||
@ -33,7 +34,7 @@ export default class User {
|
||||
}
|
||||
|
||||
@SurveyServer({ type: 'http', method: 'post', routerName: '/login' })
|
||||
async login({ req }) {
|
||||
async login({ req }: { req: Request, res: Response }) {
|
||||
const userInfo = getValidateValue(Joi.object({
|
||||
username: Joi.string().required(),
|
||||
password: Joi.string().required(),
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { mongo } from '../db/mongo';
|
||||
import { create } from 'svg-captcha';
|
||||
|
||||
class CaptchaService {
|
||||
|
||||
createCaptcha() {
|
||||
@ -39,4 +38,3 @@ class CaptchaService {
|
||||
}
|
||||
|
||||
export const captchaService = new CaptchaService();
|
||||
|
||||
|
@ -7,7 +7,6 @@ export function getStatusObject({ status }: { status: SURVEY_STATUS }) {
|
||||
date: Date.now(),
|
||||
};
|
||||
}
|
||||
|
||||
export function getValidateValue<T = unknown>(validationResult: Joi.ValidationResult<T>): T {
|
||||
if (validationResult.error) {
|
||||
throw new CommonError(validationResult.error.details.map(e => e.message).join());
|
||||
|
@ -1,4 +1,3 @@
|
||||
import 'dotenv/config';
|
||||
import * as os from 'os';
|
||||
import * as Koa from 'koa';
|
||||
import * as KoaBodyparser from 'koa-bodyparser';
|
||||
|
@ -1,11 +1,20 @@
|
||||
<template>
|
||||
<div class="tableview-root">
|
||||
<div class="filter-wrap">
|
||||
<div class="select">
|
||||
<text-select
|
||||
v-for="item in Object.keys(selectOptionsDict)"
|
||||
:effect-fun="onSelectChange"
|
||||
:effect-key="item"
|
||||
:options="selectOptionsDict[item]"
|
||||
/>
|
||||
</div>
|
||||
<div class="search">
|
||||
<text-search
|
||||
placeholder="请输入问卷标题"
|
||||
:value="searchVal"
|
||||
@search="onSearchText" />
|
||||
@search="onSearchText"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<el-table
|
||||
@ -92,7 +101,8 @@ import Tag from './tag';
|
||||
import State from './state';
|
||||
import ToolBar from './toolBar';
|
||||
import TextSearch from './textSearch'
|
||||
import { fieldConfig, thead, noListDataConfig, noSearchDataConfig } from '../config';
|
||||
import TextSelect from './textSelect'
|
||||
import { fieldConfig, thead, noListDataConfig, noSearchDataConfig, selectOptionsDict } from '../config';
|
||||
import { CODE_MAP } from '@/management/api/base';
|
||||
import { QOP_MAP } from '@/management/utils/constant';
|
||||
import { getSurveyList, deleteSurvey } from '@/management/api/survey';
|
||||
@ -112,7 +122,12 @@ export default {
|
||||
total: 0,
|
||||
data: [],
|
||||
currentPage: 1,
|
||||
searchVal: ''
|
||||
searchVal: '',
|
||||
selectOptionsDict,
|
||||
selectValueMap: {
|
||||
questionType: '',
|
||||
'curStatus.status': ''
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -126,11 +141,31 @@ export default {
|
||||
return [
|
||||
{
|
||||
comparator:"",
|
||||
condition:[{
|
||||
"field":"title",
|
||||
"value":this.searchVal,
|
||||
"comparator":"$regex"
|
||||
}]
|
||||
condition:[
|
||||
{
|
||||
field: "title",
|
||||
value: this.searchVal,
|
||||
comparator: "$regex"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
comparator: "",
|
||||
condition: [
|
||||
{
|
||||
field: "curStatus.status",
|
||||
value: this.selectValueMap["curStatus.status"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
comparator: "",
|
||||
condition: [
|
||||
{
|
||||
field: "questionType",
|
||||
value: this.selectValueMap.questionType
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -142,7 +177,9 @@ export default {
|
||||
async init() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const filter = JSON.stringify(this.filter)
|
||||
const filter = JSON.stringify(this.filter.filter(item => {
|
||||
return item.condition[0].field === 'title' || item.condition[0].value
|
||||
}))
|
||||
const res = await getSurveyList(this.currentPage, filter);
|
||||
this.loading = false;
|
||||
if (res.code === CODE_MAP.SUCCESS) {
|
||||
@ -247,6 +284,11 @@ export default {
|
||||
this.searchVal = e
|
||||
this.currentPage = 1
|
||||
this.init()
|
||||
},
|
||||
onSelectChange(selectValue, selectKey){
|
||||
this.selectValueMap[selectKey] = selectValue
|
||||
this.currentPage = 1
|
||||
this.init()
|
||||
}
|
||||
},
|
||||
components: {
|
||||
@ -255,6 +297,7 @@ export default {
|
||||
Tag,
|
||||
ToolBar,
|
||||
TextSearch,
|
||||
TextSelect,
|
||||
State,
|
||||
},
|
||||
};
|
||||
@ -264,8 +307,12 @@ export default {
|
||||
.tableview-root {
|
||||
.filter-wrap{
|
||||
display: flex;
|
||||
justify-content: right;
|
||||
justify-content: space-between;
|
||||
.select{
|
||||
display: flex;
|
||||
}
|
||||
.search{
|
||||
display: flex;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
}
|
||||
@ -302,4 +349,10 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
.el-select-dropdown__wrap{
|
||||
background: #eee;
|
||||
}
|
||||
.el-select-dropdown__item.hover{
|
||||
background: #fff;
|
||||
}
|
||||
</style>
|
||||
|
57
web/src/management/pages/list/components/textButton.vue
Normal file
57
web/src/management/pages/list/components/textButton.vue
Normal file
@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<div class="text-button-root" @click="onClick">
|
||||
<el-button type="text" :icon="currentOption.icon" size="mini">
|
||||
<slot>{{ currentOption.label }}</slot>
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default{
|
||||
name: 'TextButton',
|
||||
data(){
|
||||
return {
|
||||
optionIndex: 0,
|
||||
optionValue: ''
|
||||
}
|
||||
},
|
||||
props: {
|
||||
options: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
effectFun: {
|
||||
type: Function
|
||||
},
|
||||
effectKey: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
optionsLength(){
|
||||
return this.options.length
|
||||
},
|
||||
currentOption(){
|
||||
return this.options[this.optionIndex % this.optionsLength]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
currentOption(newOption){
|
||||
typeof this.effectFun === 'function' && this.effectFun(newOption, this.effectKey)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClick(){
|
||||
if(this.optionIndex > this.optionsLength){
|
||||
this.optionIndex = 0
|
||||
return
|
||||
}
|
||||
this.optionIndex++
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.el-button{
|
||||
margin-right: 20px;
|
||||
}
|
||||
</style>
|
59
web/src/management/pages/list/components/textSelect.vue
Normal file
59
web/src/management/pages/list/components/textSelect.vue
Normal file
@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<div class="text-select-root">
|
||||
<el-select v-model="selectValue" :placeholder="options.label">
|
||||
<el-option
|
||||
v-for="item in options.value"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'TextSelect',
|
||||
data(){
|
||||
return {
|
||||
selectValue: this.options.default
|
||||
}
|
||||
},
|
||||
props: {
|
||||
options: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
effectFun:{
|
||||
type: Function
|
||||
},
|
||||
effectKey: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
selectValue(newSelect){
|
||||
const {effectFun} = this
|
||||
typeof effectFun === 'function' && effectFun(newSelect, this.effectKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-select{
|
||||
width: 105px;
|
||||
line-height: 35px;
|
||||
margin-right: 20px;
|
||||
::v-deep .el-input__inner{
|
||||
border: none;
|
||||
height: 35px;
|
||||
// line-height: 35px;
|
||||
}
|
||||
::v-deep .el-icon-arrow-up:before{
|
||||
position: relative;
|
||||
top: -2px;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
@ -72,3 +72,66 @@ export const statusMaps = {
|
||||
removed: '',
|
||||
pausing: '',
|
||||
};
|
||||
|
||||
// 问卷类型
|
||||
export const questionTypeSelect = {
|
||||
label: '问卷类型',
|
||||
value: [
|
||||
{
|
||||
value: '',
|
||||
label: '全部类型'
|
||||
},
|
||||
{
|
||||
value: 'normal',
|
||||
label: '基础调查'
|
||||
},
|
||||
// {
|
||||
// value: 'exam',
|
||||
// label: '在线考试'
|
||||
// },
|
||||
// {
|
||||
// value: 'nps',
|
||||
// label: 'NPS评分'
|
||||
// },
|
||||
{
|
||||
value: 'vote',
|
||||
label: '投票评选'
|
||||
},
|
||||
{
|
||||
value: 'register',
|
||||
label: '在线报名'
|
||||
},
|
||||
],
|
||||
default: ''
|
||||
}
|
||||
|
||||
// 问卷状态
|
||||
export const curStatusSelect = {
|
||||
label: '问卷状态',
|
||||
value: [
|
||||
{
|
||||
value: '',
|
||||
label: '全部状态'
|
||||
},
|
||||
{
|
||||
value: 'new',
|
||||
label: '未发布'
|
||||
},
|
||||
{
|
||||
value: 'published',
|
||||
label: '已发布'
|
||||
},
|
||||
{
|
||||
value: 'editing',
|
||||
label: '修改中'
|
||||
}
|
||||
],
|
||||
default: ''
|
||||
}
|
||||
|
||||
export const selectOptionsDict = Object.freeze({
|
||||
questionType: questionTypeSelect,
|
||||
'curStatus.status': curStatusSelect
|
||||
})
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user