feat: 问卷列表新增类型和状态筛选 (#51)

This commit is contained in:
Realabiha 2024-01-18 17:17:06 +08:00 committed by GitHub
parent 642f4f08eb
commit 4d88a33856
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 254 additions and 20 deletions

View File

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

View File

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

View File

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

View File

@ -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(),

View File

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

View File

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

View File

@ -1,4 +1,3 @@
import 'dotenv/config';
import * as os from 'os';
import * as Koa from 'koa';
import * as KoaBodyparser from 'koa-bodyparser';

View File

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

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

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

View File

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