feat: 问卷空间协作能力前端 (#246)
This commit is contained in:
parent
f9d75962ed
commit
404ba360b9
@ -11,5 +11,11 @@ module.exports = {
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest'
|
||||
},
|
||||
rules: {
|
||||
'no-empty-pattern': 'off',
|
||||
"vue/multi-word-component-names": ["error", {
|
||||
"ignores": ["index"]
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
5
web/components.d.ts
vendored
5
web/components.d.ts
vendored
@ -19,6 +19,9 @@ declare module 'vue' {
|
||||
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||
ElMenuItemGroup: typeof import('element-plus/es')['ElMenuItemGroup']
|
||||
ElOption: typeof import('element-plus/es')['ElOption']
|
||||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||
ElPopover: typeof import('element-plus/es')['ElPopover']
|
||||
@ -27,6 +30,7 @@ declare module 'vue' {
|
||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||
ElRow: typeof import('element-plus/es')['ElRow']
|
||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||
ElSelectV2: typeof import('element-plus/es')['ElSelectV2']
|
||||
ElSlider: typeof import('element-plus/es')['ElSlider']
|
||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||
ElTable: typeof import('element-plus/es')['ElTable']
|
||||
@ -44,6 +48,7 @@ declare module 'vue' {
|
||||
IEpDelete: typeof import('~icons/ep/delete')['default']
|
||||
IEpLoading: typeof import('~icons/ep/loading')['default']
|
||||
IEpMinus: typeof import('~icons/ep/minus')['default']
|
||||
IEpMore: typeof import('~icons/ep/more')['default']
|
||||
IEpPlus: typeof import('~icons/ep/plus')['default']
|
||||
IEpQuestionFilled: typeof import('~icons/ep/question-filled')['default']
|
||||
IEpRank: typeof import('~icons/ep/rank')['default']
|
||||
|
75
web/src/management/api/space.ts
Normal file
75
web/src/management/api/space.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import axios from './base'
|
||||
|
||||
// 空间
|
||||
export const createSpace = ({ name, description, members }: any) => {
|
||||
return axios.post('/workspace', { name, description, members })
|
||||
}
|
||||
|
||||
export const updateSpace = ({ workspaceId, name, description, members }: any) => {
|
||||
return axios.post(`/workspace/${workspaceId}`, { name, description, members })
|
||||
}
|
||||
|
||||
export const getSpaceList = () => {
|
||||
return axios.get('/workspace')
|
||||
}
|
||||
|
||||
export const getSpaceDetail = (workspaceId: string) => {
|
||||
return axios.get(`/workspace/${workspaceId}`)
|
||||
}
|
||||
|
||||
export const deleteSpace = (workspaceId: string) => {
|
||||
return axios.delete(`/workspace/${workspaceId}`)
|
||||
}
|
||||
|
||||
export const getUserList = (username: string) => {
|
||||
return axios.get(`/user/getUserList`, {
|
||||
params: {
|
||||
username
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 协作权限列表
|
||||
export const getPermissionList = () => {
|
||||
return axios.get('collaborator/getPermissionList')
|
||||
}
|
||||
|
||||
export const saveCollaborator = ({ surveyId, collaborators }: any) => {
|
||||
return axios.post('collaborator/batchSave', {
|
||||
surveyId,
|
||||
collaborators
|
||||
})
|
||||
}
|
||||
|
||||
// 添加协作人
|
||||
export const addCollaborator = ({ surveyId, userId, permissions }: any) => {
|
||||
return axios.post('collaborator', {
|
||||
surveyId,
|
||||
userId,
|
||||
permissions
|
||||
})
|
||||
}
|
||||
// 更新问卷协作信息
|
||||
export const updateCollaborator = ({ surveyId, userId, permissions }: any) => {
|
||||
return axios.post('collaborator/changeUserPermission', {
|
||||
surveyId,
|
||||
userId,
|
||||
permissions
|
||||
})
|
||||
}
|
||||
// 获取问卷协作信息
|
||||
export const getCollaborator = (surveyId: string) => {
|
||||
return axios.get(`collaborator`, {
|
||||
params: {
|
||||
surveyId
|
||||
}
|
||||
})
|
||||
}
|
||||
// 获取问卷协作权限
|
||||
export const getCollaboratorPermissions = (surveyId: string) => {
|
||||
return axios.get(`collaborator/permissions`, {
|
||||
params: {
|
||||
surveyId
|
||||
}
|
||||
})
|
||||
}
|
@ -1,12 +1,13 @@
|
||||
import axios from './base'
|
||||
|
||||
export const getSurveyList = ({ curPage, filter, order }) => {
|
||||
export const getSurveyList = ({ curPage, filter, order, workspaceId }) => {
|
||||
return axios.get('/survey/getList', {
|
||||
params: {
|
||||
pageSize: 10,
|
||||
curPage,
|
||||
filter,
|
||||
order
|
||||
order,
|
||||
workspaceId
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -1,19 +1,40 @@
|
||||
<template>
|
||||
<div class="nav">
|
||||
<LogoIcon />
|
||||
<RouterLink v-for="(tab, index) in tabs" :key="index" class="tab-btn" :to="tab.to" replace>
|
||||
<div class="icon">
|
||||
<i class="iconfont" :class="tab.icon"></i>
|
||||
</div>
|
||||
<p>{{ tab.text }}</p>
|
||||
</RouterLink>
|
||||
<template v-for="(tab, index) in tabs" :key="tab.text + index">
|
||||
<router-link :to="tab.to" v-slot="{ isActive }">
|
||||
<div
|
||||
:class="[
|
||||
'tab-btn',
|
||||
(['QuestionEditIndex', 'QuestionEditSetting', 'QuestionSkinSetting'].includes(
|
||||
route.name
|
||||
) &&
|
||||
tab.to.name === 'QuestionEditIndex') ||
|
||||
isActive
|
||||
? 'router-link-active'
|
||||
: ''
|
||||
]"
|
||||
>
|
||||
<div class="icon">
|
||||
<i class="iconfont" :class="tab.icon"></i>
|
||||
</div>
|
||||
<p>{{ tab.text }}</p>
|
||||
</div>
|
||||
</router-link>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import { useRoute } from 'vue-router'
|
||||
const route = useRoute()
|
||||
import LogoIcon from './LogoIcon.vue'
|
||||
import { SurveyPermissions } from '@/management/utils/types/workSpace.ts'
|
||||
const store = useStore()
|
||||
|
||||
const tabs = [
|
||||
const tabArr = [
|
||||
{
|
||||
text: '编辑问卷',
|
||||
icon: 'icon-bianji',
|
||||
@ -36,6 +57,19 @@ const tabs = [
|
||||
}
|
||||
}
|
||||
]
|
||||
const tabs = ref([])
|
||||
onMounted(async () => {
|
||||
await store.dispatch('fetchCooperPermissions', route.params.id)
|
||||
// 如果有问卷管理权限,则加入问卷编辑和投放菜单
|
||||
if (store.state.cooperPermissions.includes(SurveyPermissions.SurveyManage)) {
|
||||
tabs.value.push(tabArr[0])
|
||||
tabs.value.push(tabArr[1])
|
||||
}
|
||||
// 如果有数据分析权限,则加入数据分析菜单
|
||||
if (store.state.cooperPermissions.includes(SurveyPermissions.DataManage)) {
|
||||
tabs.value.push(tabArr[2])
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.nav {
|
||||
|
@ -38,6 +38,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, toRefs } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useStore } from 'vuex'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import 'element-plus/theme-chalk/src/message.scss'
|
||||
|
||||
@ -78,7 +79,7 @@ const checkForm = (fn: Function) => {
|
||||
}
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const store = useStore()
|
||||
const submit = () => {
|
||||
if (!state.canSubmit) {
|
||||
return
|
||||
@ -89,10 +90,14 @@ const submit = () => {
|
||||
return
|
||||
}
|
||||
state.canSubmit = false
|
||||
const res:any = await createSurvey({
|
||||
const payload: any = {
|
||||
surveyType: selectType,
|
||||
...state.form
|
||||
})
|
||||
}
|
||||
if (store.state.list.workSpaceId) {
|
||||
payload.workspaceId = store.state.list.workSpaceId
|
||||
}
|
||||
const res: any = await createSurvey(payload)
|
||||
if (res?.code === 200 && res?.data?.id) {
|
||||
const id = res.data.id
|
||||
router.push({
|
||||
|
@ -30,7 +30,7 @@ const onBack = () => {
|
||||
|
||||
const toHomePage = () => {
|
||||
router.push({
|
||||
name: 'Home'
|
||||
name: 'survey'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
@ -1,17 +1,17 @@
|
||||
<template>
|
||||
<div class="title-container">
|
||||
<div
|
||||
class="title"
|
||||
@mouseover="showFullTitle"
|
||||
@mousemove="updateTooltipPosition"
|
||||
@mouseleave="hideFullTitle"
|
||||
class="title"
|
||||
@mouseover="showFullTitle"
|
||||
@mousemove="updateTooltipPosition"
|
||||
@mouseleave="hideFullTitle"
|
||||
>
|
||||
{{ title }}
|
||||
</div>
|
||||
<div
|
||||
class="tooltip"
|
||||
v-if="tooltipVisible"
|
||||
:style="{ top: tooltipPosition.top + 'px', left: tooltipPosition.left + 'px' }"
|
||||
class="tooltip"
|
||||
v-if="tooltipVisible"
|
||||
:style="{ top: tooltipPosition.top + 'px', left: tooltipPosition.left + 'px' }"
|
||||
>
|
||||
{{ title }}
|
||||
</div>
|
||||
@ -73,5 +73,3 @@ const hideFullTitle = () => {
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
@ -49,7 +49,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { defineProps, computed, inject, ref, type ComputedRef } from 'vue'
|
||||
import { computed, inject, ref, type ComputedRef } from 'vue'
|
||||
import { ConditionNode, RuleNode } from '@/common/logicEngine/RuleBuild'
|
||||
import { qAbleList } from '@/management/utils/constant.js'
|
||||
import { cleanRichText } from '@/common/xss'
|
||||
|
@ -29,18 +29,18 @@ import MaterialGroup from '@/management/pages/edit/components/MaterialGroup.vue'
|
||||
import { useStore } from 'vuex'
|
||||
import communalLoader from '@materials/communals/communalLoader.js'
|
||||
|
||||
const HeaderContent = ()=>communalLoader.loadComponent('HeaderContent')
|
||||
const MainTitle = ()=>communalLoader.loadComponent('MainTitle')
|
||||
const SubmitButton = ()=>communalLoader.loadComponent('SubmitButton')
|
||||
const LogoIcon = ()=>communalLoader.loadComponent('LogoIcon')
|
||||
const HeaderContent = () => communalLoader.loadComponent('HeaderContent')
|
||||
const MainTitle = () => communalLoader.loadComponent('MainTitle')
|
||||
const SubmitButton = () => communalLoader.loadComponent('SubmitButton')
|
||||
const LogoIcon = () => communalLoader.loadComponent('LogoIcon')
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MaterialGroup,
|
||||
HeaderContent:HeaderContent(),
|
||||
MainTitle:MainTitle(),
|
||||
SubmitButton:SubmitButton(),
|
||||
LogoIcon:LogoIcon()
|
||||
HeaderContent: HeaderContent(),
|
||||
MainTitle: MainTitle(),
|
||||
SubmitButton: SubmitButton(),
|
||||
LogoIcon: LogoIcon()
|
||||
},
|
||||
setup() {
|
||||
const store = useStore()
|
||||
|
@ -5,17 +5,16 @@
|
||||
<TextSelect
|
||||
v-for="item in Object.keys(selectOptionsDict)"
|
||||
:key="item"
|
||||
:effect-fun="onSelectChange"
|
||||
:effect-key="item"
|
||||
:options="selectOptionsDict[item]"
|
||||
:value="selectValueMap[item]"
|
||||
@change="(value) => onSelectChange(item, value)"
|
||||
/>
|
||||
</div>
|
||||
<div class="search">
|
||||
<TextButton
|
||||
v-for="item in Object.keys(buttonOptionsDict)"
|
||||
:key="item"
|
||||
:effect-fun="onButtonChange"
|
||||
:effect-key="item"
|
||||
@change="(value) => onButtonChange(item, value)"
|
||||
:option="buttonOptionsDict[item]"
|
||||
:icon="
|
||||
buttonOptionsDict[item].icons.find(
|
||||
@ -27,53 +26,58 @@
|
||||
<TextSearch placeholder="请输入问卷标题" :value="searchVal" @search="onSearchText" />
|
||||
</div>
|
||||
</div>
|
||||
<el-table
|
||||
v-if="total"
|
||||
ref="multipleListTable"
|
||||
class="list-table"
|
||||
:data="dataList"
|
||||
empty-text="暂无数据"
|
||||
row-key="_id"
|
||||
header-row-class-name="tableview-header"
|
||||
row-class-name="tableview-row"
|
||||
cell-class-name="tableview-cell"
|
||||
style="width: 100%"
|
||||
v-loading="loading"
|
||||
@row-click="onRowClick"
|
||||
>
|
||||
<el-table-column column-key="space" width="20" />
|
||||
<el-table-column
|
||||
v-for="field in fieldList"
|
||||
:key="field.key"
|
||||
:label="field.title"
|
||||
:column-key="field.key"
|
||||
:width="field.width"
|
||||
:min-width="field.width || field.minWidth"
|
||||
class-name="link"
|
||||
<div class="list-wrapper" v-if="total">
|
||||
<el-table
|
||||
v-if="total"
|
||||
ref="multipleListTable"
|
||||
class="list-table"
|
||||
:data="dataList"
|
||||
empty-text="暂无数据"
|
||||
row-key="_id"
|
||||
header-row-class-name="tableview-header"
|
||||
row-class-name="tableview-row"
|
||||
cell-class-name="tableview-cell"
|
||||
style="width: 100%"
|
||||
v-loading="loading"
|
||||
@row-click="onRowClick"
|
||||
>
|
||||
<template #default="scope">
|
||||
<template v-if="field.comp">
|
||||
<component :is="field.comp" type="table" :value="scope.row" />
|
||||
<el-table-column column-key="space" width="20" />
|
||||
<el-table-column
|
||||
v-for="field in fieldList"
|
||||
:key="field.key"
|
||||
:label="field.title"
|
||||
:column-key="field.key"
|
||||
:width="field.width"
|
||||
:min-width="field.width || field.minWidth"
|
||||
class-name="link"
|
||||
>
|
||||
<template #default="scope">
|
||||
<template v-if="field.comp">
|
||||
<component
|
||||
:is="currentComponent(field.comp)"
|
||||
type="table"
|
||||
:value="unref(scope.row)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="cell-span">{{ scope.row[field.key] }}</span>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="cell-span">{{ scope.row[field.key] }}</span>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" :width="300" class-name="table-options" fixed="right">
|
||||
<template #default="scope">
|
||||
<ToolBar
|
||||
:data="scope.row"
|
||||
type="list"
|
||||
:tools="getToolConfig(scope.row)"
|
||||
:tool-width="50"
|
||||
@on-delete="onDelete"
|
||||
@on-modify="onModify"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-table-column label="操作" :width="230" class-name="table-options" fixed="right">
|
||||
<template #default="scope">
|
||||
<ToolBar
|
||||
:data="scope.row"
|
||||
type="list"
|
||||
:tools="getToolConfig(scope.row)"
|
||||
:tool-width="50"
|
||||
@click="handleClick"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<div class="list-pagination" v-if="total">
|
||||
<el-pagination
|
||||
@ -95,10 +99,14 @@
|
||||
:question-info="questionInfo"
|
||||
@on-close-codify="onCloseModify"
|
||||
/>
|
||||
<CooperModify :modifyId="cooperId" :visible="cooperModify" @on-close-codify="onCooperClose" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { ref, computed, unref } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { get, map } from 'lodash-es'
|
||||
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
@ -114,8 +122,7 @@ moment.locale('zh-cn')
|
||||
import EmptyIndex from '@/management/components/EmptyIndex.vue'
|
||||
import { CODE_MAP } from '@/management/api/base'
|
||||
import { QOP_MAP } from '@/management/utils/constant'
|
||||
import { getSurveyList, deleteSurvey } from '@/management/api/survey'
|
||||
|
||||
import { deleteSurvey } from '@/management/api/survey'
|
||||
import ModifyDialog from './ModifyDialog.vue'
|
||||
import TagModule from './TagModule.vue'
|
||||
import StateModule from './StateModule.vue'
|
||||
@ -123,6 +130,9 @@ import ToolBar from './ToolBar.vue'
|
||||
import TextSearch from './TextSearch.vue'
|
||||
import TextSelect from './TextSelect.vue'
|
||||
import TextButton from './TextButton.vue'
|
||||
import CooperModify from './CooperModify.vue'
|
||||
import { SurveyPermissions } from '@/management/utils/types/workSpace'
|
||||
|
||||
import {
|
||||
fieldConfig,
|
||||
noListDataConfig,
|
||||
@ -131,224 +141,320 @@ import {
|
||||
buttonOptionsDict
|
||||
} from '../config'
|
||||
|
||||
export default {
|
||||
name: 'BaseList',
|
||||
data() {
|
||||
return {
|
||||
fields: ['type', 'title', 'remark', 'owner', 'state', 'createDate', 'updateDate'],
|
||||
showModify: false,
|
||||
modifyType: '',
|
||||
loading: false,
|
||||
noListDataConfig,
|
||||
noSearchDataConfig,
|
||||
questionInfo: {},
|
||||
total: 0,
|
||||
data: [],
|
||||
currentPage: 1,
|
||||
searchVal: '',
|
||||
selectOptionsDict,
|
||||
selectValueMap: {
|
||||
surveyType: '',
|
||||
'curStatus.status': ''
|
||||
},
|
||||
buttonOptionsDict,
|
||||
buttonValueMap: {
|
||||
'curStatus.date': '',
|
||||
createDate: -1
|
||||
}
|
||||
}
|
||||
const store = useStore()
|
||||
const router = useRouter()
|
||||
const props = defineProps({
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
computed: {
|
||||
fieldList() {
|
||||
const fieldInfo = map(this.fields, (f) => {
|
||||
return get(fieldConfig, f, null)
|
||||
})
|
||||
return fieldInfo
|
||||
},
|
||||
dataList() {
|
||||
return this.data.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
'curStatus.date': item.curStatus.date
|
||||
}
|
||||
})
|
||||
},
|
||||
filter() {
|
||||
return [
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
total: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
const emit = defineEmits(['reflush'])
|
||||
const fields = ['type', 'title', 'remark', 'owner', 'state', 'createDate', 'updateDate']
|
||||
const showModify = ref(false)
|
||||
const modifyType = ref('')
|
||||
const questionInfo = ref({})
|
||||
|
||||
const currentPage = ref(1)
|
||||
const searchVal = computed(() => {
|
||||
return store.state.list.searchVal
|
||||
})
|
||||
const selectValueMap = computed(() => {
|
||||
return store.state.list.selectValueMap
|
||||
})
|
||||
const buttonValueMap = computed(() => {
|
||||
return store.state.list.buttonValueMap
|
||||
})
|
||||
const currentComponent = computed(() => {
|
||||
return (componentName) => {
|
||||
switch (componentName) {
|
||||
case 'TagModule':
|
||||
return TagModule
|
||||
case 'StateModule':
|
||||
return StateModule
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const fieldList = computed(() => {
|
||||
return map(fields, (f) => {
|
||||
return get(fieldConfig, f, null)
|
||||
})
|
||||
})
|
||||
const data = computed(() => {
|
||||
return props.data
|
||||
})
|
||||
const total = computed(() => {
|
||||
return props.total
|
||||
})
|
||||
const dataList = computed(() => {
|
||||
return data.value.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
'curStatus.date': item.curStatus.date
|
||||
}
|
||||
})
|
||||
})
|
||||
const filter = computed(() => {
|
||||
return [
|
||||
{
|
||||
comparator: '',
|
||||
condition: [
|
||||
{
|
||||
comparator: '',
|
||||
condition: [
|
||||
{
|
||||
field: 'title',
|
||||
value: this.searchVal,
|
||||
comparator: '$regex'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
comparator: '',
|
||||
condition: [
|
||||
{
|
||||
field: 'curStatus.status',
|
||||
value: this.selectValueMap['curStatus.status']
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
comparator: '',
|
||||
condition: [
|
||||
{
|
||||
field: 'surveyType',
|
||||
value: this.selectValueMap.surveyType
|
||||
}
|
||||
]
|
||||
field: 'title',
|
||||
value: searchVal.value,
|
||||
comparator: '$regex'
|
||||
}
|
||||
]
|
||||
},
|
||||
order() {
|
||||
const formatOrder = Object.entries(this.buttonValueMap)
|
||||
.filter(([, effectValue]) => effectValue)
|
||||
.reduce((prev, item) => {
|
||||
const [effectKey, effectValue] = item
|
||||
prev.push({ field: effectKey, value: effectValue })
|
||||
return prev
|
||||
}, [])
|
||||
return JSON.stringify(formatOrder)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.init()
|
||||
},
|
||||
methods: {
|
||||
async init() {
|
||||
this.loading = true
|
||||
try {
|
||||
const filter = JSON.stringify(
|
||||
this.filter.filter((item) => {
|
||||
return item.condition[0].value
|
||||
})
|
||||
)
|
||||
const res = await getSurveyList({
|
||||
curPage: this.currentPage,
|
||||
filter,
|
||||
order: this.order
|
||||
})
|
||||
this.loading = false
|
||||
if (res.code === CODE_MAP.SUCCESS) {
|
||||
this.total = res.data.count
|
||||
this.data = res.data.data
|
||||
} else {
|
||||
ElMessage.error(res.errmsg)
|
||||
{
|
||||
comparator: '',
|
||||
condition: [
|
||||
{
|
||||
field: 'curStatus.status',
|
||||
value: selectValueMap.value['curStatus.status']
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error(error)
|
||||
this.loading = false
|
||||
}
|
||||
]
|
||||
},
|
||||
getStatus(data) {
|
||||
return get(data, 'curStatus.status', 'new')
|
||||
{
|
||||
comparator: '',
|
||||
condition: [
|
||||
{
|
||||
field: 'surveyType',
|
||||
value: selectValueMap.value.surveyType
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
const order = computed(() => {
|
||||
const formatOrder = Object.entries(buttonValueMap.value)
|
||||
.filter(([, effectValue]) => effectValue)
|
||||
.reduce((prev, item) => {
|
||||
const [effectKey, effectValue] = item
|
||||
prev.push({ field: effectKey, value: effectValue })
|
||||
return prev
|
||||
}, [])
|
||||
return JSON.stringify(formatOrder)
|
||||
})
|
||||
const workSpaceId = computed(() => {
|
||||
return store.state.list.workSpaceId
|
||||
})
|
||||
|
||||
const onReflush = async () => {
|
||||
const filterString = JSON.stringify(
|
||||
filter.value.filter((item) => {
|
||||
return item.condition[0].value
|
||||
})
|
||||
)
|
||||
let params = {
|
||||
curPage: currentPage.value,
|
||||
filter: filterString,
|
||||
order: order.value
|
||||
}
|
||||
if (workSpaceId.value) {
|
||||
params.workspaceId = workSpaceId.value
|
||||
}
|
||||
emit('reflush', params)
|
||||
}
|
||||
|
||||
const getToolConfig = (row) => {
|
||||
let funcList = []
|
||||
const permissionsBtn = [
|
||||
{
|
||||
key: QOP_MAP.EDIT,
|
||||
label: '修改'
|
||||
},
|
||||
getToolConfig() {
|
||||
const funcList = [
|
||||
{
|
||||
key: QOP_MAP.EDIT,
|
||||
label: '修改'
|
||||
},
|
||||
{
|
||||
{
|
||||
key: 'delete',
|
||||
label: '删除',
|
||||
icon: 'icon-shanchu'
|
||||
},
|
||||
{
|
||||
key: QOP_MAP.COPY,
|
||||
label: '复制',
|
||||
icon: 'icon-shanchu'
|
||||
},
|
||||
{
|
||||
key: 'analysis',
|
||||
label: '数据'
|
||||
},
|
||||
{
|
||||
key: 'release',
|
||||
label: '投放'
|
||||
},
|
||||
{
|
||||
key: 'cooper',
|
||||
label: '协作'
|
||||
}
|
||||
]
|
||||
if (!store.state.list.workSpaceId) {
|
||||
if (!row.isCollaborated) {
|
||||
// 创建人显示协作按钮
|
||||
funcList = funcList.concat(permissionsBtn)
|
||||
} else {
|
||||
if (row.currentPermissions.includes(SurveyPermissions.DataManage)) {
|
||||
// 协作人判断权限显示数据分析按钮
|
||||
funcList.push({
|
||||
key: 'analysis',
|
||||
label: '数据'
|
||||
},
|
||||
{
|
||||
key: 'release',
|
||||
label: '投放'
|
||||
},
|
||||
{
|
||||
key: 'delete',
|
||||
label: '删除',
|
||||
icon: 'icon-shanchu'
|
||||
},
|
||||
{
|
||||
key: QOP_MAP.COPY,
|
||||
label: '复制',
|
||||
icon: 'icon-shanchu'
|
||||
}
|
||||
]
|
||||
return funcList
|
||||
},
|
||||
async onDelete(row) {
|
||||
try {
|
||||
await ElMessageBox.confirm('是否确认删除?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
} catch (error) {
|
||||
console.log('取消删除')
|
||||
return
|
||||
}
|
||||
if (row.currentPermissions.includes(SurveyPermissions.SurveyManage)) {
|
||||
// 协作人判断权限显示投放按钮
|
||||
funcList.push(
|
||||
{
|
||||
key: QOP_MAP.EDIT,
|
||||
label: '修改'
|
||||
},
|
||||
{
|
||||
key: 'delete',
|
||||
label: '删除',
|
||||
icon: 'icon-shanchu'
|
||||
},
|
||||
{
|
||||
key: QOP_MAP.COPY,
|
||||
label: '复制',
|
||||
icon: 'icon-shanchu'
|
||||
},
|
||||
{
|
||||
key: 'release',
|
||||
label: '投放'
|
||||
}
|
||||
)
|
||||
}
|
||||
if (row.currentPermissions.includes(SurveyPermissions.CollaboratorManage)) {
|
||||
// 协作人判断权限显示协作按钮
|
||||
funcList.push({
|
||||
key: 'cooper',
|
||||
label: '协作'
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 团队空间没有开放协作功能,不需要判断按钮状态
|
||||
permissionsBtn.splice(-1)
|
||||
funcList = permissionsBtn
|
||||
}
|
||||
const order = ['edit', 'analysis', 'release', 'delete', 'copy', 'cooper']
|
||||
const result = funcList.sort((a, b) => order.indexOf(a.key) - order.indexOf(b.key))
|
||||
|
||||
const res = await deleteSurvey(row._id)
|
||||
if (res.code === CODE_MAP.SUCCESS) {
|
||||
ElMessage.success('删除成功')
|
||||
this.init()
|
||||
} else {
|
||||
ElMessage.error(res.errmsg || '删除失败')
|
||||
}
|
||||
},
|
||||
handleCurrentChange(current) {
|
||||
this.currentPage = current
|
||||
this.init()
|
||||
},
|
||||
onModify(data, type = QOP_MAP.EDIT) {
|
||||
this.showModify = true
|
||||
this.modifyType = type
|
||||
this.questionInfo = data
|
||||
},
|
||||
onCloseModify(type) {
|
||||
this.showModify = false
|
||||
this.questionInfo = {}
|
||||
if (type === 'update') {
|
||||
this.init()
|
||||
}
|
||||
},
|
||||
onRowClick(row) {
|
||||
this.$router.push({
|
||||
name: 'QuestionEditIndex',
|
||||
return result
|
||||
}
|
||||
const handleClick = (key, data) => {
|
||||
switch (key) {
|
||||
case QOP_MAP.EDIT:
|
||||
onModify(data, QOP_MAP.EDIT)
|
||||
return
|
||||
case QOP_MAP.COPY:
|
||||
onModify(data, QOP_MAP.COPY)
|
||||
return
|
||||
case 'analysis':
|
||||
router.push({
|
||||
name: 'analysisPage',
|
||||
params: {
|
||||
id: row._id
|
||||
id: data._id
|
||||
}
|
||||
})
|
||||
},
|
||||
onSearchText(e) {
|
||||
this.searchVal = e
|
||||
this.currentPage = 1
|
||||
this.init()
|
||||
},
|
||||
onSelectChange(selectValue, selectKey) {
|
||||
this.selectValueMap[selectKey] = selectValue
|
||||
this.currentPage = 1
|
||||
this.init()
|
||||
},
|
||||
onButtonChange(effectValue, effectKey) {
|
||||
this.buttonValueMap = {
|
||||
'curStatus.date': '',
|
||||
createDate: ''
|
||||
}
|
||||
this.buttonValueMap[effectKey] = effectValue
|
||||
this.init()
|
||||
}
|
||||
},
|
||||
components: {
|
||||
EmptyIndex,
|
||||
ModifyDialog,
|
||||
TagModule,
|
||||
ToolBar,
|
||||
TextSearch,
|
||||
TextSelect,
|
||||
TextButton,
|
||||
StateModule
|
||||
return
|
||||
case 'release':
|
||||
router.push({
|
||||
name: 'publish',
|
||||
params: {
|
||||
id: data._id
|
||||
}
|
||||
})
|
||||
return
|
||||
case 'delete':
|
||||
onDelete(data)
|
||||
return
|
||||
case 'cooper':
|
||||
onCooper(data)
|
||||
return
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
const onDelete = async (row) => {
|
||||
try {
|
||||
await ElMessageBox.confirm('是否确认删除?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
} catch (error) {
|
||||
console.log('取消删除')
|
||||
return
|
||||
}
|
||||
|
||||
const res = await deleteSurvey(row._id)
|
||||
if (res.code === CODE_MAP.SUCCESS) {
|
||||
ElMessage.success('删除成功')
|
||||
onReflush()
|
||||
} else {
|
||||
ElMessage.error(res.errmsg || '删除失败')
|
||||
}
|
||||
}
|
||||
const handleCurrentChange = (current) => {
|
||||
currentPage.value = current
|
||||
onReflush()
|
||||
}
|
||||
const onModify = (data, type = QOP_MAP.EDIT) => {
|
||||
showModify.value = true
|
||||
modifyType.value = type
|
||||
questionInfo.value = data
|
||||
}
|
||||
const onCloseModify = (type) => {
|
||||
showModify.value = false
|
||||
questionInfo.value = {}
|
||||
if (type === 'update') {
|
||||
onReflush()
|
||||
}
|
||||
}
|
||||
const onRowClick = (row) => {
|
||||
router.push({
|
||||
name: 'QuestionEditIndex',
|
||||
params: {
|
||||
id: row._id
|
||||
}
|
||||
})
|
||||
}
|
||||
const onSearchText = (e) => {
|
||||
store.commit('list/setSearchVal', e)
|
||||
currentPage.value = 1
|
||||
onReflush()
|
||||
}
|
||||
const onSelectChange = (selectKey, selectValue) => {
|
||||
store.commit('list/changeSelectValueMap', { key: selectKey, value: selectValue })
|
||||
// selectValueMap.value[selectKey] = selectValue
|
||||
currentPage.value = 1
|
||||
onReflush()
|
||||
}
|
||||
const onButtonChange = (effectKey, effectValue) => {
|
||||
store.commit('list/reserButtonValueMap')
|
||||
store.commit('list/changeButtonValueMap', { key: effectKey, value: effectValue })
|
||||
onReflush()
|
||||
}
|
||||
|
||||
const cooperModify = ref(false)
|
||||
const cooperId = ref('')
|
||||
const onCooper = async (row) => {
|
||||
cooperId.value = row._id
|
||||
cooperModify.value = true
|
||||
}
|
||||
const onCooperClose = () => {
|
||||
cooperModify.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -365,11 +471,14 @@ export default {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.list-table {
|
||||
min-height: 620px;
|
||||
.list-wrapper {
|
||||
padding: 10px 20px;
|
||||
background: #fff;
|
||||
.list-table {
|
||||
min-height: 620px;
|
||||
}
|
||||
}
|
||||
|
||||
.list-pagination {
|
||||
margin-top: 20px;
|
||||
:deep(.el-pagination) {
|
||||
|
192
web/src/management/pages/list/components/CooperModify.vue
Normal file
192
web/src/management/pages/list/components/CooperModify.vue
Normal file
@ -0,0 +1,192 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="base-dialog-root"
|
||||
:model-value="visible"
|
||||
width="40%"
|
||||
:title="formTitle"
|
||||
@close="onClose"
|
||||
>
|
||||
<el-form
|
||||
class="base-form-root"
|
||||
ref="ruleForm"
|
||||
:model="formModel"
|
||||
:rules="rules"
|
||||
label-position="top"
|
||||
size="large"
|
||||
@submit.prevent
|
||||
:disabled="formDisabled"
|
||||
>
|
||||
<el-form-item label="添加协作者" prop="members">
|
||||
<MemberSelect
|
||||
:multiple="true"
|
||||
:members="formModel.members"
|
||||
:options="cooperOptions"
|
||||
@select="handleMemberSelect"
|
||||
@change="handleMembersChange"
|
||||
>
|
||||
</MemberSelect>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="onClose">取消</el-button>
|
||||
<el-button type="primary" class="save-btn" @click="onConfirm" v-if="!formDisabled">
|
||||
确定
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, shallowRef, onMounted, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import 'element-plus/theme-chalk/src/message.scss'
|
||||
import MemberSelect from './MemberSelect.vue'
|
||||
import { getPermissionList, getCollaborator, saveCollaborator } from '@/management/api/space'
|
||||
import { type IMember, SurveyPermissions } from '@/management/utils/types/workSpace'
|
||||
import { CODE_MAP } from '@/management/api/base'
|
||||
const emit = defineEmits(['on-close-codify', 'onFocus', 'change', 'blur'])
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modifyId: string
|
||||
visible: boolean
|
||||
}>(),
|
||||
{
|
||||
modifyId: '',
|
||||
visible: false
|
||||
}
|
||||
)
|
||||
const ruleForm = shallowRef<any>(null)
|
||||
|
||||
const formTitle = ref('协作管理')
|
||||
|
||||
const cooperOptions = ref([])
|
||||
onMounted(async () => {
|
||||
const res: any = await getPermissionList()
|
||||
if (res.code === CODE_MAP.SUCCESS) {
|
||||
cooperOptions.value = res.data.map((item: any) => {
|
||||
return {
|
||||
label: item.name,
|
||||
value: item.value
|
||||
}
|
||||
})
|
||||
} else {
|
||||
ElMessage.error(res.errmsg || '获取权限信息失败')
|
||||
}
|
||||
})
|
||||
|
||||
const formModel = ref({
|
||||
members: [] as IMember[]
|
||||
})
|
||||
watch(
|
||||
() => props.visible,
|
||||
async (val: boolean) => {
|
||||
if (val && props.modifyId) {
|
||||
try {
|
||||
const res: any = await getCollaborator(props.modifyId)
|
||||
if (res.code === CODE_MAP.SUCCESS) {
|
||||
formModel.value.members = res.data?.map((item: any) => {
|
||||
return {
|
||||
_id: item._id,
|
||||
userId: item.userId,
|
||||
username: item.username,
|
||||
role: item.permissions
|
||||
}
|
||||
})
|
||||
} else {
|
||||
formModel.value.members = []
|
||||
ElMessage.error(res.errmsg || '获取协作信息失败')
|
||||
}
|
||||
} catch (err) {
|
||||
ElMessage.error('获取协作信息接口请求错误')
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
const rules = {
|
||||
members: [
|
||||
{
|
||||
trigger: 'change',
|
||||
validator: (rule: any, value: IMember[], callback: Function) => {
|
||||
if (value.length === 0) {
|
||||
callback('请至少添加一名协作者')
|
||||
}
|
||||
if (value.filter((item: IMember) => !item.role.length).length) {
|
||||
callback('请设置协作者对应权限')
|
||||
}
|
||||
if (
|
||||
value.filter(
|
||||
(item: IMember) =>
|
||||
item.role.length === 1 && item.role[0] === SurveyPermissions.CollaboratorManage
|
||||
).length
|
||||
) {
|
||||
callback('不能单独设置协作者管理')
|
||||
}
|
||||
callback()
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
const formDisabled = computed(() => {
|
||||
return false
|
||||
})
|
||||
|
||||
const onClose = () => {
|
||||
emit('on-close-codify')
|
||||
}
|
||||
const onConfirm = async () => {
|
||||
ruleForm.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
try {
|
||||
const collaborators = formModel.value.members.map((i: any) => {
|
||||
const collaborator = {
|
||||
userId: i.userId,
|
||||
permissions: i.role
|
||||
}
|
||||
if (i._id) {
|
||||
;(collaborator as any)._id = i._id
|
||||
}
|
||||
return collaborator
|
||||
})
|
||||
const res: any = await saveCollaborator({
|
||||
surveyId: props.modifyId,
|
||||
collaborators
|
||||
})
|
||||
if (res.code === CODE_MAP.SUCCESS) {
|
||||
ElMessage.success('操作成功')
|
||||
emit('on-close-codify')
|
||||
} else {
|
||||
ElMessage.error(res.errmsg || '协作管理接口调用失败')
|
||||
}
|
||||
} catch (err) {
|
||||
ElMessage.error('createSpace status err' + err)
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleMemberSelect = (val: string, label: string) => {
|
||||
formModel.value.members.push({
|
||||
userId: val,
|
||||
username: label,
|
||||
role: [
|
||||
SurveyPermissions.SurveyManage,
|
||||
SurveyPermissions.DataManage,
|
||||
SurveyPermissions.CollaboratorManage
|
||||
]
|
||||
})
|
||||
}
|
||||
const handleMembersChange = (val: IMember[]) => {
|
||||
formModel.value.members = val
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" rel="lang/scss" scoped>
|
||||
.base-form-root {
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
76
web/src/management/pages/list/components/MemberList.vue
Normal file
76
web/src/management/pages/list/components/MemberList.vue
Normal file
@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<div class="list-wrapper" v-if="list.length">
|
||||
<div class="content" v-for="(item, index) in list" :key="item.userId">
|
||||
<div>{{ item.username }}</div>
|
||||
<div class="operation">
|
||||
<OperationSelect
|
||||
:options="options"
|
||||
v-model="item.role"
|
||||
:multiple="multiple"
|
||||
@customClick="() => handleRemove(index)"
|
||||
:disabled="item.userId === currentUserId"
|
||||
></OperationSelect>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed, withDefaults } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import { type IMember, type ListItem } from '@/management/utils/types/workSpace'
|
||||
import OperationSelect from './OperationSelect.vue'
|
||||
|
||||
const store = useStore()
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
members: IMember[]
|
||||
options: ListItem[]
|
||||
multiple: boolean
|
||||
}>(),
|
||||
{
|
||||
members: () => [],
|
||||
options: () => [],
|
||||
multiple: false
|
||||
}
|
||||
)
|
||||
const emit = defineEmits(['change'])
|
||||
const list = computed({
|
||||
get() {
|
||||
return props.members
|
||||
},
|
||||
set(value) {
|
||||
emit('change', value)
|
||||
}
|
||||
})
|
||||
const currentUserId = computed(() => {
|
||||
return store.state.list.spaceDetail?.currentUserId
|
||||
})
|
||||
const handleRemove = (index: number) => {
|
||||
list.value.splice(index, 1)
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.list-wrapper {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
border: 1px solid #ccc;
|
||||
margin-top: 8px;
|
||||
overflow: auto;
|
||||
.head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #ccc;
|
||||
padding: 0 20px;
|
||||
}
|
||||
.content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
|
||||
.operation {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
93
web/src/management/pages/list/components/MemberSelect.vue
Normal file
93
web/src/management/pages/list/components/MemberSelect.vue
Normal file
@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<div class="wrapper">
|
||||
<el-select-v2
|
||||
v-model="value"
|
||||
filterable
|
||||
remote
|
||||
:remote-method="remoteMethod"
|
||||
clearable
|
||||
:options="selectOptions"
|
||||
:loading="loading"
|
||||
placeholder="请输入账号名搜索"
|
||||
@change="handleSelect"
|
||||
/>
|
||||
<MemberList
|
||||
:members="members"
|
||||
:options="options"
|
||||
@change="handleMemberChange"
|
||||
:multiple="multiple"
|
||||
>
|
||||
</MemberList>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, withDefaults } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import MemberList from './MemberList.vue'
|
||||
import { getUserList } from '@/management/api/space'
|
||||
import {
|
||||
type IMember,
|
||||
type ListItem,
|
||||
type UserRole,
|
||||
roleLabels
|
||||
} from '@/management/utils/types/workSpace'
|
||||
import { CODE_MAP } from '@/management/api/base'
|
||||
|
||||
const store = useStore()
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
members?: IMember[]
|
||||
options?: ListItem[]
|
||||
multiple?: boolean
|
||||
}>(),
|
||||
{
|
||||
members: () => [],
|
||||
options: () => {
|
||||
return Object.keys(roleLabels).map((key) => ({
|
||||
label: roleLabels[key as UserRole],
|
||||
value: key
|
||||
}))
|
||||
},
|
||||
multiple: false
|
||||
}
|
||||
)
|
||||
const emit = defineEmits(['select', 'change'])
|
||||
|
||||
const value = ref('')
|
||||
const selectOptions = ref<ListItem[]>([])
|
||||
const loading = ref(false)
|
||||
|
||||
const remoteMethod = async (query: string) => {
|
||||
if (query !== '') {
|
||||
loading.value = true
|
||||
const res: any = await getUserList(query)
|
||||
if (res.code === CODE_MAP.SUCCESS) {
|
||||
selectOptions.value = res.data.map((item: any) => {
|
||||
// 不可以选中自己
|
||||
const currentUser = item.username === store.state.user.userInfo.username
|
||||
return {
|
||||
value: item.userId,
|
||||
label: item.username,
|
||||
disabled: props.members.map((item) => item.userId).includes(item.userId) || currentUser
|
||||
}
|
||||
})
|
||||
loading.value = false
|
||||
}
|
||||
} else {
|
||||
selectOptions.value = []
|
||||
}
|
||||
}
|
||||
const handleSelect = (val: string) => {
|
||||
value.value = ''
|
||||
emit('select', val, selectOptions.value?.find((item) => item.value === val)?.label)
|
||||
}
|
||||
const handleMemberChange = (val: any) => {
|
||||
emit('change', val)
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
87
web/src/management/pages/list/components/MoreTool.vue
Normal file
87
web/src/management/pages/list/components/MoreTool.vue
Normal file
@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<el-button text type="primary" ref="buttonRef" v-click-outside="onClickOutside">
|
||||
<i-ep-more />
|
||||
</el-button>
|
||||
|
||||
<el-popover
|
||||
ref="popoverRef"
|
||||
:width="width"
|
||||
:virtual-ref="buttonRef"
|
||||
placement="top"
|
||||
trigger="hover"
|
||||
popper-class="more-tool_popper"
|
||||
:popper-options="{ boundariesElement: '.more-tool_root', removeOnDestroy: true }"
|
||||
virtual-triggering
|
||||
>
|
||||
<div :class="[type === 'card' ? 'card-tool_more_ul' : 'table-tool_more_ul', 'popper_body']">
|
||||
<div
|
||||
v-for="t in tools"
|
||||
:key="t.key"
|
||||
:class="[type === 'card' ? 'card-tool-li_base' : 'table-tool-li_base', 'popper_con']"
|
||||
@click="call(t)"
|
||||
>
|
||||
<span class="more_con">{{ t.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, unref } from 'vue'
|
||||
import { ClickOutside as vClickOutside } from 'element-plus'
|
||||
|
||||
defineProps({
|
||||
type: String,
|
||||
placement: String,
|
||||
tools: Array,
|
||||
width: {
|
||||
type: Number,
|
||||
default: 50
|
||||
}
|
||||
})
|
||||
const emit = defineEmits(['popper', 'call'])
|
||||
const buttonRef = ref()
|
||||
const popoverRef = ref()
|
||||
|
||||
const onClickOutside = () => {
|
||||
unref(popoverRef).popperRef?.delayHide?.()
|
||||
}
|
||||
|
||||
const call = (t) => {
|
||||
emit('call', {
|
||||
key: t.key,
|
||||
name: t.label
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" rel="stylesheet/scss">
|
||||
.el-popover.more-tool_popper {
|
||||
min-width: 80px;
|
||||
padding: 8px 3px;
|
||||
.popper_body {
|
||||
.popper_con {
|
||||
cursor: pointer;
|
||||
height: 28px;
|
||||
&:hover {
|
||||
background: #fef6e6 100%;
|
||||
span.more_con {
|
||||
color: #faa600;
|
||||
}
|
||||
}
|
||||
text-align: center;
|
||||
}
|
||||
.popper_con span.more_con {
|
||||
min-width: 76px;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: $font-color;
|
||||
line-height: 28px;
|
||||
font-size: 14px;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
// line-height: 16px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
156
web/src/management/pages/list/components/OperationSelect.vue
Normal file
156
web/src/management/pages/list/components/OperationSelect.vue
Normal file
@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<div class="operation">
|
||||
<el-select
|
||||
size="small"
|
||||
:multiple="multiple"
|
||||
v-model="value"
|
||||
placeholder="请选择"
|
||||
:style="{ width: `${multiple ? 226 : 100}px` }"
|
||||
popper-class="custom-popper"
|
||||
@change="handleChange"
|
||||
class="operation-select"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
<template #header v-if="multiple">
|
||||
<el-checkbox v-model="checkAll" :indeterminate="indeterminate" @change="handleCheckAll">
|
||||
全部权限
|
||||
</el-checkbox>
|
||||
</template>
|
||||
<template #tag v-if="multiple">
|
||||
<el-tag type="primary" v-if="value.length === options.length">全部权限</el-tag>
|
||||
<el-tag v-for="chose in value" :key="chose" v-else>{{ chosenLabel(chose) }}</el-tag>
|
||||
</template>
|
||||
<template #footer>
|
||||
<el-button class="remove-btn" link type="danger" @click="handleClick">
|
||||
删除</el-button
|
||||
>
|
||||
</template>
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { ElMessageBox, type CheckboxValueType } from 'element-plus'
|
||||
|
||||
import 'element-plus/theme-chalk/src/message-box.scss'
|
||||
import { type ListItem } from '@/management/utils/types/workSpace'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
multiple?: boolean
|
||||
modelValue: string | string[]
|
||||
options: ListItem[]
|
||||
width?: number
|
||||
disabled?: boolean
|
||||
}>(),
|
||||
{
|
||||
multiple: false,
|
||||
modelValue: '',
|
||||
options: () => [],
|
||||
width: 100,
|
||||
disabled: false
|
||||
}
|
||||
)
|
||||
const emit = defineEmits(['update:modelValue', 'change', 'customClick'])
|
||||
const value = computed({
|
||||
get() {
|
||||
return props.modelValue
|
||||
},
|
||||
set(value) {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
})
|
||||
const chosenLabel = computed(() => {
|
||||
return (chose: string) => {
|
||||
return props.options.find((i) => i.value === chose)?.label
|
||||
}
|
||||
})
|
||||
const handleChange = (val: string | string[]) => {
|
||||
emit('change', val)
|
||||
}
|
||||
const handleClick = () => {
|
||||
const text = props.multiple
|
||||
? '删除协作者后,用户不再有该问卷下的相关权限'
|
||||
: '删除团队成员后,该成员不再有团队空间的访问权限'
|
||||
ElMessageBox.confirm(text, '是否确认本次删除', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async () => {
|
||||
emit('customClick')
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
const checkAll = ref(false)
|
||||
const indeterminate = ref(false)
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(value) => {
|
||||
if (props.multiple) {
|
||||
// 如果是多选处理全部选中状态
|
||||
if (value.length === 0) {
|
||||
checkAll.value = false
|
||||
indeterminate.value = false
|
||||
} else if (value.length === props.options.length) {
|
||||
checkAll.value = true
|
||||
indeterminate.value = false
|
||||
} else {
|
||||
indeterminate.value = true
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
const handleCheckAll = (val: CheckboxValueType) => {
|
||||
indeterminate.value = false
|
||||
if (val) {
|
||||
value.value = props.options.map((_) => _.value)
|
||||
} else {
|
||||
value.value = []
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.operation {
|
||||
:deep(.el-select__wrapper) {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
:deep(.ishovering) {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
:deep(.el-select__selection, .is-near) {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.operation-select {
|
||||
:deep(.el-select__placeholder) {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="scss">
|
||||
.custom-popper {
|
||||
.el-checkbox {
|
||||
display: flex;
|
||||
height: 22px;
|
||||
}
|
||||
.el-button,
|
||||
.remove-btn {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
</style>
|
130
web/src/management/pages/list/components/SliderBar.vue
Normal file
130
web/src/management/pages/list/components/SliderBar.vue
Normal file
@ -0,0 +1,130 @@
|
||||
<template>
|
||||
<el-menu
|
||||
:default-active="SpaceType.Personal"
|
||||
class="el-menu-vertical"
|
||||
ref="menuRef"
|
||||
@select="handleSelect"
|
||||
>
|
||||
<template v-for="(menu, index) in menus" :key="menu.id">
|
||||
<el-menu-item
|
||||
:class="[index === 0 ? 'bottom' : '', index > 2 ? 'sub-item' : 'main-item']"
|
||||
:index="menu.id"
|
||||
v-if="!menu.children?.length"
|
||||
>
|
||||
<template #title>
|
||||
<div class="title-content">
|
||||
<i :class="['iconfont', menu.icon]"></i>
|
||||
<span>{{ menu.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
<el-menu-item-group v-else>
|
||||
<template #title>
|
||||
<el-menu-item :index="menu.id" class="sub-title main-item">
|
||||
<div class="title-content">
|
||||
<i :class="['iconfont', menu.icon]"></i>
|
||||
<span>{{ menu.name }}</span>
|
||||
</div>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
<el-menu-item v-for="item in menu.children" :key="item.id" :index="item.id">
|
||||
<p>
|
||||
{{ item.name }}
|
||||
</p>
|
||||
</el-menu-item>
|
||||
</el-menu-item-group>
|
||||
</template>
|
||||
</el-menu>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { type MenuItem } from '@/management/utils/types/workSpace'
|
||||
import { SpaceType } from '@/management/utils/types/workSpace'
|
||||
|
||||
const menuRef = ref()
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
menus: Array<MenuItem>
|
||||
}>(),
|
||||
{
|
||||
menus: () => []
|
||||
}
|
||||
)
|
||||
const emit = defineEmits(['select'])
|
||||
const handleSelect = (id: string) => {
|
||||
emit('select', id)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-menu-vertical {
|
||||
border: none;
|
||||
width: 200px;
|
||||
min-height: 400px;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
bottom: 0px;
|
||||
z-index: 999;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
box-shadow: 0 2px 0 0 rgba(0, 0, 0, 0.04);
|
||||
:deep(.el-menu-item) {
|
||||
width: 200px;
|
||||
height: 36px;
|
||||
> p {
|
||||
overflow: hidden;
|
||||
/*文本不会换行*/
|
||||
white-space: nowrap !important;
|
||||
/*当文本溢出包含元素时,以省略号表示超出的文本*/
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&.bottom {
|
||||
border-bottom: 1px solid #e3e4e8;
|
||||
}
|
||||
&.main-item {
|
||||
// margin: 10px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #292a36;
|
||||
height: 48px;
|
||||
}
|
||||
&.sub-item {
|
||||
margin: 0;
|
||||
}
|
||||
&.is-active {
|
||||
// background-color: #F2F4F7;
|
||||
background: #fef6e6 100% !important;
|
||||
}
|
||||
&:hover {
|
||||
background-color: #f2f4f7;
|
||||
}
|
||||
.title-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
:deep(.el-menu-item-group) {
|
||||
> ul {
|
||||
> li {
|
||||
padding-left: 40px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
:deep(.el-menu-item-group__title) {
|
||||
cursor: pointer;
|
||||
padding: 0 !important;
|
||||
}
|
||||
.sub-title {
|
||||
width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.iconfont {
|
||||
font-size: 16px;
|
||||
margin-right: 5px;
|
||||
color: #faa600 !important;
|
||||
}
|
||||
</style>
|
171
web/src/management/pages/list/components/SpaceList.vue
Normal file
171
web/src/management/pages/list/components/SpaceList.vue
Normal file
@ -0,0 +1,171 @@
|
||||
<template>
|
||||
<div class="list-wrap">
|
||||
<el-table
|
||||
ref="multipleListTable"
|
||||
class="list-table"
|
||||
:data="dataList"
|
||||
empty-text="暂无数据"
|
||||
row-key="_id"
|
||||
header-row-class-name="tableview-header"
|
||||
row-class-name="tableview-row"
|
||||
cell-class-name="tableview-cell"
|
||||
style="width: 100%"
|
||||
@row-click="onRowClick"
|
||||
>
|
||||
<el-table-column column-key="space" width="20" />
|
||||
<el-table-column
|
||||
v-for="field in fieldList"
|
||||
:key="(field as any)?.key"
|
||||
:label="(field as any).title"
|
||||
:column-key="(field as any).key"
|
||||
:width="(field as any).width"
|
||||
:min-width="(field as any).width || (field as any).minWidth"
|
||||
class-name="link"
|
||||
>
|
||||
<template #default="scope">
|
||||
<template v-if="(field as any).comp">
|
||||
<component :is="(field as any).comp" type="table" :value="scope.row" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="cell-span">{{ scope.row[(field as any).key] }}</span>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
label="操作"
|
||||
:width="200"
|
||||
label-class-name="operation"
|
||||
class-name="table-options"
|
||||
>
|
||||
<template #default="scope">
|
||||
<div class="tool-root">
|
||||
<!-- <el-button text type="primary" class="tool-root-btn-text" :style="{ width: 50 + 'px' }" @click.stop="handleEnter(scope.row)">进入</el-button> -->
|
||||
<el-button
|
||||
text
|
||||
type="primary"
|
||||
class="tool-root-btn-text"
|
||||
:style="{ width: 50 + 'px' }"
|
||||
@click.stop="handleModify(scope.row._id)"
|
||||
>{{ isAdmin(scope.row._id) ? '修改' : '查看' }}</el-button
|
||||
>
|
||||
<el-button
|
||||
text
|
||||
type="primary"
|
||||
class="tool-root-btn-text"
|
||||
:style="{ width: 50 + 'px' }"
|
||||
@click.stop="handleDelete(scope.row._id)"
|
||||
v-if="isAdmin(scope.row._id)"
|
||||
>删除</el-button
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<SpaceModify
|
||||
v-if="showSpaceModify"
|
||||
:type="modifyType"
|
||||
:visible="showSpaceModify"
|
||||
@on-close-codify="onCloseModify"
|
||||
/>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import 'element-plus/theme-chalk/src/message-box.scss'
|
||||
import { get, map } from 'lodash-es'
|
||||
import { spaceListConfig } from '../config'
|
||||
import SpaceModify from './SpaceModify.vue'
|
||||
import { UserRole } from '@/management/utils/types/workSpace'
|
||||
|
||||
const showSpaceModify = ref(false)
|
||||
const modifyType = ref('edit')
|
||||
const store = useStore()
|
||||
const fields = ['name', 'surveyTotal', 'memberTotal', 'owner', 'createDate']
|
||||
const fieldList = computed(() => {
|
||||
return map(fields, (f) => {
|
||||
return get(spaceListConfig, f, null)
|
||||
})
|
||||
})
|
||||
const dataList = computed(() => {
|
||||
return store.state.list.teamSpaceList
|
||||
})
|
||||
const isAdmin = (id: string) => {
|
||||
return (
|
||||
store.state.list.teamSpaceList.find((item: any) => item._id === id)?.currentUserRole ===
|
||||
UserRole.Admin
|
||||
)
|
||||
}
|
||||
const onRowClick = () => {
|
||||
console.log('onRowClick')
|
||||
}
|
||||
const handleModify = async (id: string) => {
|
||||
await store.dispatch('list/getSpaceDetail', id)
|
||||
modifyType.value = 'edit'
|
||||
showSpaceModify.value = true
|
||||
}
|
||||
|
||||
const handleDelete = (id: string) => {
|
||||
ElMessageBox.confirm(
|
||||
'删除团队后,团队内的问卷将同步被删除,请谨慎考虑!是否确认本次删除?',
|
||||
'提示',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
)
|
||||
.then(async () => {
|
||||
await store.dispatch('list/deleteSpace', id)
|
||||
await store.dispatch('list/getSpaceList')
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
const onCloseModify = () => {
|
||||
showSpaceModify.value = false
|
||||
store.dispatch('list/getSpaceList')
|
||||
}
|
||||
// const handleCurrentChange = (current) => {
|
||||
// this.currentPage = current
|
||||
// this.init()
|
||||
// }
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.list-wrap {
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
.list-table {
|
||||
:deep(.el-table__header) {
|
||||
.tableview-header .el-table__cell {
|
||||
.cell {
|
||||
height: 24px;
|
||||
color: #4a4c5b;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
:deep(.tableview-row) {
|
||||
.tableview-cell {
|
||||
padding: 5px 0;
|
||||
&.link {
|
||||
cursor: pointer;
|
||||
}
|
||||
.cell .cell-span {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.tool-root {
|
||||
display: flex;
|
||||
&:first-child {
|
||||
margin-left: -10px;
|
||||
}
|
||||
.tool-root-btn-text {
|
||||
font-weight: normal !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
157
web/src/management/pages/list/components/SpaceModify.vue
Normal file
157
web/src/management/pages/list/components/SpaceModify.vue
Normal file
@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="base-dialog-root"
|
||||
:model-value="visible"
|
||||
width="40%"
|
||||
:title="formTitle"
|
||||
@close="onClose"
|
||||
>
|
||||
<el-form
|
||||
class="base-form-root"
|
||||
ref="ruleForm"
|
||||
:model="formModel"
|
||||
:rules="rules"
|
||||
label-position="top"
|
||||
size="large"
|
||||
@submit.prevent
|
||||
:disabled="formDisabled"
|
||||
>
|
||||
<el-form-item label="团队名称" prop="name">
|
||||
<el-input v-model="formModel.name" />
|
||||
</el-form-item>
|
||||
<el-form-item label="空间描述">
|
||||
<el-input v-model="formModel.description" />
|
||||
</el-form-item>
|
||||
<el-form-item label="添加成员" prop="members">
|
||||
<MemberSelect
|
||||
:members="formModel.members"
|
||||
@select="handleMemberSelect"
|
||||
@change="handleMembersChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="onClose" v-if="!formDisabled">取消</el-button>
|
||||
<el-button type="primary" class="save-btn" @click="onConfirm" v-if="!formDisabled">
|
||||
确定
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, shallowRef, onMounted } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import { pick as _pick } from 'lodash-es'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import 'element-plus/theme-chalk/src/message.scss'
|
||||
|
||||
import { QOP_MAP } from '@/management/utils/constant'
|
||||
import MemberSelect from './MemberSelect.vue'
|
||||
import { type IMember, type IWorkspace, UserRole } from '@/management/utils/types/workSpace'
|
||||
|
||||
const store = useStore()
|
||||
const emit = defineEmits(['on-close-codify', 'onFocus', 'change', 'blur'])
|
||||
const props = defineProps({
|
||||
type: String,
|
||||
width: String,
|
||||
visible: Boolean
|
||||
})
|
||||
const ruleForm = shallowRef<any>(null)
|
||||
|
||||
const formTitle = computed(() => {
|
||||
return props.type === QOP_MAP.ADD ? '创建团队' : '修改团队'
|
||||
})
|
||||
const formModel = ref<IWorkspace>({
|
||||
name: '',
|
||||
description: '',
|
||||
members: [] as IMember[]
|
||||
})
|
||||
const rules = {
|
||||
name: [{ required: true, message: '请输入团队名称', trigger: 'blur' }],
|
||||
members: [
|
||||
{
|
||||
trigger: 'change',
|
||||
validator: (rule: any, value: IMember[], callback: Function) => {
|
||||
if (props.type === QOP_MAP.EDIT) {
|
||||
if (value.filter((item: IMember) => item.role === UserRole.Admin).length === 0) {
|
||||
callback('请至少设置一名空间管理员')
|
||||
}
|
||||
}
|
||||
callback()
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
const spaceDetail = computed(() => {
|
||||
return store.state.list.spaceDetail
|
||||
})
|
||||
const formDisabled = computed(() => {
|
||||
return spaceDetail.value?._id
|
||||
? store.state.list.teamSpaceList.find((item: any) => item._id === spaceDetail.value._id)
|
||||
.currentUserRole !== UserRole.Admin
|
||||
: false
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (props.type === QOP_MAP.EDIT) {
|
||||
formModel.value = _pick(spaceDetail.value, ['_id', 'name', 'description', 'members'])
|
||||
}
|
||||
})
|
||||
const onClose = () => {
|
||||
formModel.value = {
|
||||
name: '',
|
||||
description: '',
|
||||
members: [] as IMember[]
|
||||
}
|
||||
// 清空空间详情
|
||||
store.commit('list/setSpaceDetail', null)
|
||||
emit('on-close-codify')
|
||||
}
|
||||
|
||||
const onConfirm = async () => {
|
||||
ruleForm.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
if (props.type === QOP_MAP.ADD) {
|
||||
try {
|
||||
await handleAdd()
|
||||
emit('on-close-codify', 'update')
|
||||
} catch (err) {
|
||||
ElMessage.error('createSpace status err' + err)
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
await handleUpdate()
|
||||
emit('on-close-codify', 'update')
|
||||
} catch (err) {
|
||||
ElMessage.error('createSpace status err' + err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleMemberSelect = (val: string, label: string) => {
|
||||
formModel.value.members.push({ userId: val, username: label, role: UserRole.Member })
|
||||
}
|
||||
const handleMembersChange = (val: IMember[]) => {
|
||||
formModel.value.members = val
|
||||
}
|
||||
const handleUpdate = async () => {
|
||||
await store.dispatch('list/updateSpace', formModel.value)
|
||||
}
|
||||
const handleAdd = async () => {
|
||||
await store.dispatch('list/addSpace', formModel.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" rel="lang/scss" scoped>
|
||||
.base-form-root {
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div :class="['list-state', 'list-state-' + value.curStatus.status]">
|
||||
<div :class="['list-state', 'list-state-' + value.curStatus?.status]">
|
||||
<span class="list-state-badge" />
|
||||
<span>{{ statusMaps[value.curStatus.status] }}</span>
|
||||
<span>{{ statusMaps[value.curStatus?.status] }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -9,59 +9,38 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import StaticIcon from './StaticIcon.vue'
|
||||
|
||||
export default {
|
||||
name: 'TextButton',
|
||||
components: {
|
||||
StaticIcon
|
||||
const props = defineProps({
|
||||
option: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
iconIndex: 0
|
||||
}
|
||||
},
|
||||
props: {
|
||||
option: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
effectFun: {
|
||||
type: Function
|
||||
},
|
||||
effectKey: {
|
||||
type: String
|
||||
},
|
||||
icon: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
toggleOptionIcons() {
|
||||
return this.option.icons.slice(1)
|
||||
},
|
||||
iconsLength() {
|
||||
return this.toggleOptionIcons.length
|
||||
},
|
||||
currentIconItem() {
|
||||
let finalIconIndex = this.iconIndex % this.iconsLength
|
||||
return this.toggleOptionIcons[finalIconIndex]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClick() {
|
||||
this.iconIndex++
|
||||
if (this.iconIndex >= this.iconsLength) {
|
||||
this.iconIndex = 0
|
||||
}
|
||||
typeof this.effectFun === 'function' &&
|
||||
this.effectFun(this.currentIconItem.effectValue, this.effectKey)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.iconIndex = this.toggleOptionIcons.findIndex((iconItem) => iconItem.isDefaultValue)
|
||||
icon: {
|
||||
type: String
|
||||
}
|
||||
})
|
||||
const emit = defineEmits(['change'])
|
||||
const toggleOptionIcons = computed(() => {
|
||||
return props.option.icons.slice(1)
|
||||
})
|
||||
const iconIndex = ref(0)
|
||||
iconIndex.value = toggleOptionIcons.value.findIndex((iconItem) => iconItem.isDefaultValue)
|
||||
const iconsLength = computed(() => {
|
||||
return toggleOptionIcons.value.length
|
||||
})
|
||||
const currentIconItem = computed(() => {
|
||||
let finalIconIndex = iconIndex.value % iconsLength.value
|
||||
return toggleOptionIcons.value[finalIconIndex]
|
||||
})
|
||||
const onClick = () => {
|
||||
iconIndex.value++
|
||||
if (iconIndex.value >= iconsLength.value) {
|
||||
iconIndex.value = 0
|
||||
}
|
||||
|
||||
emit('change', currentIconItem.value.effectValue)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -12,33 +12,28 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'TextSelect',
|
||||
data() {
|
||||
return {
|
||||
selectValue: this.options.default
|
||||
}
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: String,
|
||||
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)
|
||||
}
|
||||
options: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
})
|
||||
const emit = defineEmits('change')
|
||||
const selectValue = computed({
|
||||
get() {
|
||||
return props.value
|
||||
},
|
||||
set(val) {
|
||||
emit('change', val)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div class="tool-bar-root">
|
||||
<template v-if="tools.length">
|
||||
<div class="tool-bar-root" @click="handleClick">
|
||||
<template v-if="iconTools.length">
|
||||
<ToolModule
|
||||
v-for="t in tools"
|
||||
v-for="t in iconTools"
|
||||
:key="t.key"
|
||||
:type="type"
|
||||
:value="t.key"
|
||||
@ -10,61 +10,44 @@
|
||||
:width="toolWidth"
|
||||
@call="onCall"
|
||||
/>
|
||||
<MoreTool
|
||||
v-if="moreTools.length"
|
||||
:type="type"
|
||||
:width="toolWidth"
|
||||
:tools="moreTools"
|
||||
@call="onCall"
|
||||
></MoreTool>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { QOP_MAP } from '@/management/utils/constant'
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { slice } from 'lodash-es'
|
||||
import ToolModule from './ToolModule.vue'
|
||||
import MoreTool from './MoreTool.vue'
|
||||
|
||||
export default {
|
||||
name: 'ToolBar',
|
||||
props: {
|
||||
data: Object,
|
||||
type: String,
|
||||
toolWidth: Number,
|
||||
tools: Array
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
methods: {
|
||||
onCall(val) {
|
||||
switch (val.key) {
|
||||
case QOP_MAP.EDIT:
|
||||
this.$emit('on-modify', this.data, QOP_MAP.EDIT)
|
||||
return
|
||||
case QOP_MAP.COPY:
|
||||
this.$emit('on-modify', this.data, QOP_MAP.COPY)
|
||||
return
|
||||
case 'analysis':
|
||||
this.$router.push({
|
||||
name: 'analysisPage',
|
||||
params: {
|
||||
id: this.data._id
|
||||
}
|
||||
})
|
||||
return
|
||||
case 'release':
|
||||
this.$router.push({
|
||||
name: 'publish',
|
||||
params: {
|
||||
id: this.data._id
|
||||
}
|
||||
})
|
||||
return
|
||||
case 'delete':
|
||||
this.$emit('on-delete', this.data)
|
||||
return
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
},
|
||||
components: {
|
||||
ToolModule
|
||||
}
|
||||
const props = defineProps({
|
||||
data: Object,
|
||||
type: String,
|
||||
toolWidth: Number,
|
||||
tools: Array
|
||||
})
|
||||
const emit = defineEmits(['click'])
|
||||
const limit = 4
|
||||
const iconTools = computed(() => {
|
||||
return slice(props.tools, 0, limit - 1)
|
||||
})
|
||||
const moreTools = computed(() => {
|
||||
return slice(props.tools, limit - 1)
|
||||
})
|
||||
const onCall = (val) => {
|
||||
emit('click', val.key, props.data)
|
||||
}
|
||||
const handleClick = (e) => {
|
||||
// 防止事件冒泡,触发父组件的点击事件
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -43,6 +43,7 @@ export default {
|
||||
text-align: center;
|
||||
line-height: 16px;
|
||||
cursor: pointer;
|
||||
// font-weight: 500;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -5,6 +5,35 @@ export const type = {
|
||||
register: '在线报名'
|
||||
}
|
||||
|
||||
export const spaceListConfig = {
|
||||
name: {
|
||||
title: '空间名称',
|
||||
key: 'name',
|
||||
width: 300
|
||||
},
|
||||
surveyTotal: {
|
||||
title: '问卷数',
|
||||
key: 'surveyTotal',
|
||||
width: 150,
|
||||
tip: true
|
||||
},
|
||||
memberTotal: {
|
||||
title: '成员数',
|
||||
key: 'memberTotal',
|
||||
width: 150
|
||||
},
|
||||
owner: {
|
||||
title: '所有者',
|
||||
key: 'owner',
|
||||
width: 150
|
||||
},
|
||||
createDate: {
|
||||
title: '创建时间',
|
||||
key: 'createDate',
|
||||
minWidth: 200
|
||||
}
|
||||
}
|
||||
|
||||
export const fieldConfig = {
|
||||
type: {
|
||||
title: '类型',
|
||||
|
@ -1,45 +1,159 @@
|
||||
<template>
|
||||
<div class="question-list-root">
|
||||
<div class="login-status">
|
||||
<img class="logo-img" src="/imgs/Logo.webp" alt="logo" />
|
||||
<div class="top-nav">
|
||||
<div class="left">
|
||||
<img class="logo-img" src="/imgs/Logo.webp" alt="logo" />
|
||||
<el-menu :default-active="activeIndex" class="el-menu-demo" mode="horizontal">
|
||||
<el-menu-item index="1">问卷列表</el-menu-item>
|
||||
</el-menu>
|
||||
</div>
|
||||
<div class="login-info">
|
||||
您好,{{ userInfo?.username }}
|
||||
<img class="login-info-img" src="/imgs/avatar.webp" />
|
||||
<span class="logout" @click="handleLogout">退出</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-content">
|
||||
<div class="top">
|
||||
<h2>问卷列表</h2>
|
||||
<el-button class="create-btn" type="default" @click="onCreate">
|
||||
<i class="iconfont icon-chuangjian"></i>
|
||||
<span>创建问卷</span>
|
||||
</el-button>
|
||||
<div class="content-wrap">
|
||||
<SliderBar :menus="spaceMenus" @select="handleSpaceSelect" />
|
||||
<div class="list-content">
|
||||
<div class="top">
|
||||
<h2>{{ spaceType === SpaceType.Group ? '团队空间' : '问卷' }}列表</h2>
|
||||
<div class="operation">
|
||||
<el-button
|
||||
class="btn space-btn"
|
||||
type="default"
|
||||
@click="onSpaceCreate"
|
||||
v-if="spaceType == SpaceType.Group"
|
||||
>
|
||||
<i class="iconfont icon-chuangjian"></i>
|
||||
<span>创建团队空间</span>
|
||||
</el-button>
|
||||
<el-button
|
||||
class="btn create-btn"
|
||||
type="default"
|
||||
@click="onCreate"
|
||||
v-if="spaceType !== SpaceType.Group"
|
||||
>
|
||||
<i class="iconfont icon-chuangjian"></i>
|
||||
<span>创建问卷</span>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<BaseList
|
||||
:loading="loading"
|
||||
:data="surveyList"
|
||||
:total="surveyTotal"
|
||||
@reflush="fetchSurveyList"
|
||||
v-if="spaceType !== SpaceType.Group"
|
||||
></BaseList>
|
||||
<SpaceList v-if="spaceType === SpaceType.Group"></SpaceList>
|
||||
</div>
|
||||
<BaseList />
|
||||
</div>
|
||||
<SpaceModify
|
||||
v-if="showSpaceModify"
|
||||
:type="modifyType"
|
||||
:visible="showSpaceModify"
|
||||
@on-close-codify="onCloseModify"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import { useRouter } from 'vue-router'
|
||||
import BaseList from './components/BaseList.vue'
|
||||
import { mapState, mapActions } from 'vuex'
|
||||
export default {
|
||||
components: { BaseList },
|
||||
name: 'listPage',
|
||||
computed: {
|
||||
...mapState('user', ['userInfo'])
|
||||
},
|
||||
methods: {
|
||||
...mapActions('user', ['logout']),
|
||||
onCreate() {
|
||||
this.$router.push('/create')
|
||||
},
|
||||
handleLogout() {
|
||||
this.logout()
|
||||
this.$router.replace({ name: 'login' })
|
||||
import SpaceList from './components/SpaceList.vue'
|
||||
import SliderBar from './components/SliderBar.vue'
|
||||
import SpaceModify from './components/SpaceModify.vue'
|
||||
import { SpaceType } from '@/management/utils/types/workSpace'
|
||||
|
||||
const store = useStore()
|
||||
const router = useRouter()
|
||||
const userInfo = computed(() => {
|
||||
return store.state.user.userInfo
|
||||
})
|
||||
const loading = ref(false)
|
||||
const surveyList = computed(() => {
|
||||
return store.state.list.surveyList
|
||||
})
|
||||
const surveyTotal = computed(() => {
|
||||
return store.state.list.surveyTotal
|
||||
})
|
||||
const activeIndex = ref('1')
|
||||
|
||||
const spaceMenus = computed(() => {
|
||||
return store.state.list.spaceMenus
|
||||
})
|
||||
const workSpaceId = computed(() => {
|
||||
return store.state.list.workSpaceId
|
||||
})
|
||||
const spaceType = computed(() => {
|
||||
return store.state.list.spaceType
|
||||
})
|
||||
const handleSpaceSelect = (id: any) => {
|
||||
if (id === SpaceType.Personal) {
|
||||
// 点击个人空间菜单
|
||||
if (store.state.list.spaceType === SpaceType.Personal) {
|
||||
return
|
||||
}
|
||||
store.commit('list/changeSpaceType', SpaceType.Personal)
|
||||
store.commit('list/changeWorkSpace', '')
|
||||
} else if (id === SpaceType.Group) {
|
||||
// 点击团队空间组菜单
|
||||
if (store.state.list.spaceType === SpaceType.Group) {
|
||||
return
|
||||
}
|
||||
store.commit('list/changeSpaceType', SpaceType.Group)
|
||||
store.commit('list/changeWorkSpace', '')
|
||||
} else if (!Object.values(SpaceType).includes(id)) {
|
||||
// 点击具体团队空间
|
||||
if (store.state.list.workSpaceId === id) {
|
||||
return
|
||||
}
|
||||
store.commit('list/changeSpaceType', SpaceType.Teamwork)
|
||||
store.commit('list/changeWorkSpace', id)
|
||||
}
|
||||
|
||||
fetchSurveyList()
|
||||
}
|
||||
onMounted(() => {
|
||||
fetchSpaceList()
|
||||
fetchSurveyList()
|
||||
})
|
||||
const fetchSpaceList = () => {
|
||||
store.dispatch('list/getSpaceList')
|
||||
}
|
||||
const fetchSurveyList = async (params?: any) => {
|
||||
if (!params) {
|
||||
params = {
|
||||
pageSize: 10,
|
||||
curPage: 1
|
||||
}
|
||||
}
|
||||
if (workSpaceId.value) {
|
||||
params.workspaceId = workSpaceId.value
|
||||
}
|
||||
loading.value = true
|
||||
await store.dispatch('list/getSurveyList', params)
|
||||
loading.value = false
|
||||
}
|
||||
const modifyType = ref('add')
|
||||
const showSpaceModify = ref(false)
|
||||
|
||||
const onCloseModify = (type: string) => {
|
||||
showSpaceModify.value = false
|
||||
if (type === 'update') fetchSpaceList()
|
||||
}
|
||||
const onSpaceCreate = () => {
|
||||
showSpaceModify.value = true
|
||||
}
|
||||
const onCreate = () => {
|
||||
router.push('/create')
|
||||
}
|
||||
const handleLogout = () => {
|
||||
store.dispatch('user/logout')
|
||||
router.replace({ name: 'login' })
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -47,7 +161,8 @@ export default {
|
||||
.question-list-root {
|
||||
height: 100%;
|
||||
background-color: #f6f7f9;
|
||||
.login-status {
|
||||
|
||||
.top-nav {
|
||||
background: #fff;
|
||||
color: #4a4c5b;
|
||||
padding: 0 20px;
|
||||
@ -55,10 +170,25 @@ export default {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.logo-img {
|
||||
width: 90px;
|
||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.04);
|
||||
.left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: calc(100% - 200px);
|
||||
.logo-img {
|
||||
width: 90px;
|
||||
height: fit-content;
|
||||
padding-right: 20px;
|
||||
}
|
||||
.el-menu {
|
||||
width: 100%;
|
||||
height: 56px;
|
||||
border: none !important;
|
||||
:deep(.el-menu-item, .is-active) {
|
||||
border: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.login-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -78,28 +208,46 @@ export default {
|
||||
color: #faa600;
|
||||
}
|
||||
}
|
||||
|
||||
.list-content {
|
||||
.content-wrap {
|
||||
position: relative;
|
||||
height: calc(100% - 56px);
|
||||
padding: 20px;
|
||||
}
|
||||
.list-content {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 32px 32px 32px 232px;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
overflow: scroll;
|
||||
|
||||
.top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
.operation {
|
||||
flex: 0 1 auto;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.create-btn {
|
||||
background: #4a4c5b;
|
||||
}
|
||||
.space-btn {
|
||||
background: $primary-color;
|
||||
}
|
||||
.btn {
|
||||
width: 132px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: #4a4c5b;
|
||||
|
||||
color: #fff;
|
||||
|
||||
.icon-chuangjian {
|
||||
|
@ -1,6 +1,9 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import { useStore } from 'vuex'
|
||||
import { SurveyPermissions } from '@/management/utils/types/workSpace'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import 'element-plus/theme-chalk/src/message.scss'
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
@ -19,7 +22,8 @@ const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '/survey/:id/edit',
|
||||
meta: {
|
||||
needLogin: true
|
||||
needLogin: true,
|
||||
premissions: [SurveyPermissions.SurveyManage]
|
||||
},
|
||||
name: 'QuestionEdit',
|
||||
component: () => import('../pages/edit/index.vue'),
|
||||
@ -89,7 +93,8 @@ const routes: RouteRecordRaw[] = [
|
||||
path: '/survey/:id/analysis',
|
||||
name: 'analysisPage',
|
||||
meta: {
|
||||
needLogin: true
|
||||
needLogin: true,
|
||||
premissions: [SurveyPermissions.DataManage]
|
||||
},
|
||||
component: () => import('../pages/analysis/AnalysisPage.vue')
|
||||
},
|
||||
@ -97,7 +102,8 @@ const routes: RouteRecordRaw[] = [
|
||||
path: '/survey/:id/publish',
|
||||
name: 'publish',
|
||||
meta: {
|
||||
needLogin: true
|
||||
needLogin: true,
|
||||
premissions: [SurveyPermissions.SurveyManage]
|
||||
},
|
||||
component: () => import('../pages/publish/PublishPage.vue')
|
||||
},
|
||||
@ -125,7 +131,7 @@ const router = createRouter({
|
||||
routes
|
||||
})
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
const store = useStore()
|
||||
if (!store.state.user?.initialized) {
|
||||
store?.dispatch('user/init')
|
||||
@ -135,7 +141,24 @@ router.beforeEach((to, from, next) => {
|
||||
}
|
||||
if (to.meta.needLogin) {
|
||||
if (store?.state?.user?.hasLogined) {
|
||||
next()
|
||||
if (to.meta.premissions) {
|
||||
const params = to.params
|
||||
await store.dispatch('fetchCooperPermissions', params.id)
|
||||
if (
|
||||
(to.meta.premissions as []).some((permission) =>
|
||||
store.state?.cooperPermissions?.includes(permission)
|
||||
)
|
||||
) {
|
||||
next()
|
||||
} else {
|
||||
ElMessage.warning('您没有该问卷的相关协作权限')
|
||||
next({
|
||||
name: 'survey'
|
||||
})
|
||||
}
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
} else {
|
||||
next({
|
||||
name: 'login',
|
||||
@ -149,4 +172,17 @@ router.beforeEach((to, from, next) => {
|
||||
}
|
||||
})
|
||||
|
||||
// router.afterEach(async (to, from) => {
|
||||
// const store = useStore()
|
||||
// if (to.meta.premissions) {
|
||||
// const params = to.params
|
||||
// await store.dispatch('fetchCooperPermissions', params.id)
|
||||
// if (!(to.meta.premissions as []).some((permission) => store.state?.cooperPermissions?.includes(permission))) {
|
||||
// ElMessage.warning('您没有该问卷的相关协作权限')
|
||||
// router.push({
|
||||
// name: 'survey'
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
export default router
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { getBannerData } from '@/management/api/skin.js'
|
||||
import { getCollaboratorPermissions } from '@/management/api/space.ts'
|
||||
import { CODE_MAP } from '../api/base'
|
||||
|
||||
export default {
|
||||
async getBannerData({ state, commit }) {
|
||||
@ -9,5 +11,12 @@ export default {
|
||||
if (res.code === 200) {
|
||||
commit('setBannerList', res.data)
|
||||
}
|
||||
},
|
||||
async fetchCooperPermissions({ commit }, id) {
|
||||
const res = await getCollaboratorPermissions(id)
|
||||
console.log(res.data)
|
||||
if (res.code === CODE_MAP.SUCCESS) {
|
||||
commit('setCooperPermissions', res.data.permissions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { createStore } from 'vuex'
|
||||
import edit from './edit'
|
||||
import user from './user'
|
||||
|
||||
import list from './list'
|
||||
import actions from './actions'
|
||||
import mutations from './mutations'
|
||||
import state from './state'
|
||||
@ -13,6 +13,7 @@ export default createStore({
|
||||
actions,
|
||||
modules: {
|
||||
edit,
|
||||
user
|
||||
user,
|
||||
list
|
||||
}
|
||||
})
|
||||
|
258
web/src/management/store/list/index.js
Normal file
258
web/src/management/store/list/index.js
Normal file
@ -0,0 +1,258 @@
|
||||
import {
|
||||
createSpace,
|
||||
getSpaceList,
|
||||
getSpaceDetail,
|
||||
updateSpace,
|
||||
deleteSpace
|
||||
} from '@/management/api/space'
|
||||
import { CODE_MAP } from '@/management/api/base'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import 'element-plus/theme-chalk/src/message.scss'
|
||||
import { getSurveyList as surveyList } from '@/management/api/survey'
|
||||
import { set } from 'lodash-es'
|
||||
import { SpaceType } from '@/management/utils/types/workSpace'
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: {
|
||||
// 空间管理
|
||||
spaceMenus: [
|
||||
{
|
||||
icon: 'icon-wodekongjian',
|
||||
name: '我的空间',
|
||||
id: SpaceType.Personal
|
||||
},
|
||||
{
|
||||
icon: 'icon-tuanduikongjian',
|
||||
name: '团队空间',
|
||||
id: SpaceType.Group,
|
||||
children: [
|
||||
// {
|
||||
// name: '小桔问卷调研团队',
|
||||
// id: 'xxxx',
|
||||
// }
|
||||
]
|
||||
}
|
||||
],
|
||||
spaceType: SpaceType.Personal,
|
||||
workSpaceId: '',
|
||||
spaceDetail: null,
|
||||
teamSpaceList: [],
|
||||
// 列表管理
|
||||
surveyList: [],
|
||||
surveyTotal: 0,
|
||||
searchVal: '',
|
||||
selectValueMap: {
|
||||
surveyType: '',
|
||||
'curStatus.status': ''
|
||||
},
|
||||
buttonValueMap: {
|
||||
'curStatus.date': '',
|
||||
createDate: -1
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
listFliter(state) {
|
||||
return [
|
||||
{
|
||||
comparator: '',
|
||||
condition: [
|
||||
{
|
||||
field: 'title',
|
||||
value: state.searchVal,
|
||||
comparator: '$regex'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
comparator: '',
|
||||
condition: [
|
||||
{
|
||||
field: 'curStatus.status',
|
||||
value: state.selectValueMap['curStatus.status']
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
comparator: '',
|
||||
condition: [
|
||||
{
|
||||
field: 'surveyType',
|
||||
value: state.selectValueMap.surveyType
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
listOrder(state) {
|
||||
const { buttonValueMap } = state
|
||||
return Object.entries(buttonValueMap)
|
||||
.filter(([, effectValue]) => effectValue)
|
||||
.reduce((prev, item) => {
|
||||
const [effectKey, effectValue] = item
|
||||
prev.push({ field: effectKey, value: effectValue })
|
||||
return prev
|
||||
}, [])
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
updateSpaceMenus(state, teamSpace) {
|
||||
// 更新空间列表下的团队空间
|
||||
set(state, 'spaceMenus[1].children', teamSpace)
|
||||
},
|
||||
changeSpaceType(state, spaceType) {
|
||||
state.spaceType = spaceType
|
||||
},
|
||||
changeWorkSpace(state, workSpaceId) {
|
||||
// 切换空间清除筛选条件
|
||||
this.commit('list/reserSelectValueMap')
|
||||
this.commit('list/reserButtonValueMap')
|
||||
this.commit('list/setSearchVal', '')
|
||||
state.workSpaceId = workSpaceId
|
||||
},
|
||||
setSpaceDetail(state, data) {
|
||||
state.spaceDetail = data
|
||||
},
|
||||
setTeamSpaceList(state, data) {
|
||||
state.teamSpaceList = data
|
||||
},
|
||||
setSurveyList(state, list) {
|
||||
state.surveyList = list
|
||||
},
|
||||
setSurveyTotal(state, total) {
|
||||
state.surveyTotal = total
|
||||
},
|
||||
setSearchVal(state, data) {
|
||||
state.searchVal = data
|
||||
},
|
||||
reserSelectValueMap(state) {
|
||||
state.selectValueMap = {
|
||||
surveyType: '',
|
||||
'curStatus.status': ''
|
||||
}
|
||||
},
|
||||
changeSelectValueMap(state, { key, value }) {
|
||||
state.selectValueMap[key] = value
|
||||
},
|
||||
reserButtonValueMap(state) {
|
||||
state.buttonValueMap = {
|
||||
'curStatus.date': '',
|
||||
createDate: -1
|
||||
}
|
||||
},
|
||||
changeButtonValueMap(state, { key, value }) {
|
||||
state.buttonValueMap[key] = value
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
async getSpaceList({ commit }) {
|
||||
try {
|
||||
const res = await getSpaceList()
|
||||
|
||||
if (res.code === CODE_MAP.SUCCESS) {
|
||||
const { list } = res.data
|
||||
const teamSpace = list.map((item) => {
|
||||
return {
|
||||
id: item._id,
|
||||
name: item.name
|
||||
}
|
||||
})
|
||||
commit('setTeamSpaceList', list)
|
||||
commit('updateSpaceMenus', teamSpace)
|
||||
} else {
|
||||
ElMessage.error('getSpaceList' + res.errmsg)
|
||||
}
|
||||
} catch (err) {
|
||||
ElMessage.error('getSpaceList' + err)
|
||||
}
|
||||
},
|
||||
async addSpace({}, params) {
|
||||
const res = await createSpace({
|
||||
name: params.name,
|
||||
description: params.description,
|
||||
members: params.members
|
||||
})
|
||||
|
||||
if (res.code === CODE_MAP.SUCCESS) {
|
||||
ElMessage.success('添加成功')
|
||||
} else {
|
||||
ElMessage.error('createSpace code err' + res.errmsg)
|
||||
}
|
||||
},
|
||||
async getSpaceDetail({ state, commit }, id) {
|
||||
try {
|
||||
const workspaceId = id || state.workSpaceId
|
||||
const res = await getSpaceDetail(workspaceId)
|
||||
if (res.code === CODE_MAP.SUCCESS) {
|
||||
commit('setSpaceDetail', res.data)
|
||||
} else {
|
||||
ElMessage.error('getSpaceList' + res.errmsg)
|
||||
}
|
||||
} catch (err) {
|
||||
ElMessage.error('getSpaceList' + err)
|
||||
}
|
||||
},
|
||||
async updateSpace({}, params) {
|
||||
const res = await updateSpace({
|
||||
workspaceId: params._id,
|
||||
name: params.name,
|
||||
description: params.description,
|
||||
members: params.members
|
||||
})
|
||||
|
||||
if (res.code === CODE_MAP.SUCCESS) {
|
||||
ElMessage.success('更新成功')
|
||||
} else {
|
||||
ElMessage.error(res.errmsg)
|
||||
}
|
||||
},
|
||||
async deleteSpace({}, workspaceId) {
|
||||
try {
|
||||
const res = await deleteSpace(workspaceId)
|
||||
|
||||
if (res.code === CODE_MAP.SUCCESS) {
|
||||
ElMessage.success('删除成功')
|
||||
} else {
|
||||
ElMessage.error(res.errmsg)
|
||||
}
|
||||
} catch (err) {
|
||||
ElMessage.error(err)
|
||||
}
|
||||
},
|
||||
async getSurveyList({ state, getters, commit }, payload) {
|
||||
const filterString = JSON.stringify(
|
||||
getters.listFliter.filter((item) => {
|
||||
return item.condition[0].value
|
||||
})
|
||||
)
|
||||
const orderString = JSON.stringify(getters.listOrder)
|
||||
try {
|
||||
let params = {
|
||||
curPage: payload?.curPage || 1,
|
||||
pageSize: payload?.pageSize || 10, // 默认一页10条
|
||||
filter: filterString,
|
||||
order: orderString,
|
||||
workspaceId: state.workSpaceId
|
||||
}
|
||||
// if(payload?.order) {
|
||||
// params.order = payload.order
|
||||
// }
|
||||
// if(payload.filter) {
|
||||
// params.filter = payload.filter
|
||||
// }
|
||||
// if(payload?.workspaceId) {
|
||||
// params.workspaceId = payload.workspaceId
|
||||
// }
|
||||
const res = await surveyList(params)
|
||||
if (res.code === CODE_MAP.SUCCESS) {
|
||||
commit('setSurveyList', res.data.data)
|
||||
commit('setSurveyTotal', res.data.count)
|
||||
} else {
|
||||
ElMessage.error(res.errmsg)
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error('getSurveyList status' + error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,8 @@
|
||||
export default {
|
||||
setBannerList(state, data) {
|
||||
state.bannerList = data
|
||||
},
|
||||
setCooperPermissions(state, data) {
|
||||
state.cooperPermissions = data
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { SurveyPermissions } from '@/management/utils/types/workSpace'
|
||||
export default {
|
||||
bannerList: []
|
||||
bannerList: [],
|
||||
cooperPermissions: Object.values(SurveyPermissions)
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
@font-face {
|
||||
font-family: 'iconfont'; /* Project id 4263849 */
|
||||
src:
|
||||
url('//at.alicdn.com/t/c/font_4263849_xndlbqcha3l.woff2?t=1711101684869') format('woff2'),
|
||||
url('//at.alicdn.com/t/c/font_4263849_xndlbqcha3l.woff?t=1711101684869') format('woff'),
|
||||
url('//at.alicdn.com/t/c/font_4263849_xndlbqcha3l.ttf?t=1711101684869') format('truetype');
|
||||
url('//at.alicdn.com/t/c/font_4263849_xfsn9z31epc.woff2?t=1716556097756') format('woff2'),
|
||||
url('//at.alicdn.com/t/c/font_4263849_xfsn9z31epc.woff?t=1716556097756') format('woff'),
|
||||
url('//at.alicdn.com/t/c/font_4263849_xfsn9z31epc.ttf?t=1716556097756') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@ -131,3 +131,9 @@
|
||||
.icon-NPSpingfen::before {
|
||||
content: '\e6e7';
|
||||
}
|
||||
.icon-wodekongjian::before {
|
||||
content: '\e6ee';
|
||||
}
|
||||
.icon-tuanduikongjian::before {
|
||||
content: '\e6ec';
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
// 问卷操作枚举
|
||||
export const QOP_MAP = {
|
||||
ADD: 'add',
|
||||
COPY: 'copy',
|
||||
EDIT: 'edit'
|
||||
}
|
||||
|
||||
export const qAbleList = ['radio', 'checkbox', 'binary-choice', 'vote']
|
||||
export const operatorOptions = [
|
||||
{
|
||||
|
59
web/src/management/utils/types/workSpace.ts
Normal file
59
web/src/management/utils/types/workSpace.ts
Normal file
@ -0,0 +1,59 @@
|
||||
export interface ListItem {
|
||||
value: string
|
||||
label: string
|
||||
}
|
||||
|
||||
export interface MenuItem {
|
||||
id: string
|
||||
name: string
|
||||
icon?: string
|
||||
children?: MenuItem[]
|
||||
}
|
||||
|
||||
export type IWorkspace = {
|
||||
id?: string
|
||||
name: string
|
||||
description: string
|
||||
members: IMember[]
|
||||
}
|
||||
export type IMember = {
|
||||
userId: string
|
||||
username: string
|
||||
role: any
|
||||
_id?: string
|
||||
}
|
||||
|
||||
export enum SpaceType {
|
||||
Personal = 'personal',
|
||||
Group = 'group',
|
||||
Teamwork = 'teamwork'
|
||||
}
|
||||
export enum UserRole {
|
||||
Admin = 'admin',
|
||||
Member = 'user'
|
||||
}
|
||||
|
||||
// 定义角色标签映射对象
|
||||
export const roleLabels: Record<UserRole, string> = {
|
||||
[UserRole.Admin]: '管理员',
|
||||
[UserRole.Member]: '成员'
|
||||
}
|
||||
|
||||
export interface ICollaborator {
|
||||
_id?: string
|
||||
userId: string
|
||||
username: string
|
||||
permissions: Array<number>
|
||||
}
|
||||
|
||||
export enum SurveyPermissions {
|
||||
SurveyManage = 'SURVEY_CONF_MANAGE',
|
||||
DataManage = 'SURVEY_RESPONSE_MANAGE',
|
||||
CollaboratorManage = 'SURVEY_COOPERATION_MANAGE'
|
||||
}
|
||||
// 定义协作者权限标签映射对象
|
||||
export const surveyPermissionsLabels: Record<SurveyPermissions, string> = {
|
||||
[SurveyPermissions.SurveyManage]: '问卷管理',
|
||||
[SurveyPermissions.DataManage]: '数据管理',
|
||||
[SurveyPermissions.CollaboratorManage]: '协作管理'
|
||||
}
|
@ -45,7 +45,7 @@ export default defineComponent({
|
||||
return (
|
||||
<div class="header-video-warp">
|
||||
<div class="video">
|
||||
<video {...this.attributeVideo} >
|
||||
<video {...this.attributeVideo}>
|
||||
<source src={this.bannerConf?.bannerConfig?.videoLink} type="video/mp4" />
|
||||
</video>
|
||||
{readonly ? (
|
||||
|
@ -18,7 +18,6 @@ export default defineComponent({
|
||||
},
|
||||
emits: ['select'],
|
||||
setup(props, { emit }) {
|
||||
|
||||
const logoImage = computed(() => {
|
||||
return props.logoConf?.logoImage
|
||||
})
|
||||
@ -56,7 +55,7 @@ export default defineComponent({
|
||||
<div class="logo-icon-warp" onClick={this.onSelect}>
|
||||
<div class="question-logo">
|
||||
{this.logoImage ? (
|
||||
<img src={this.logoImage} style={{width: this.logoImageWidth}} />
|
||||
<img src={this.logoImage} style={{ width: this.logoImageWidth }} />
|
||||
) : (
|
||||
this.noLogoRender()
|
||||
)}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { defineComponent, computed,shallowRef,defineAsyncComponent} from 'vue'
|
||||
import { defineComponent, computed, shallowRef, defineAsyncComponent } from 'vue'
|
||||
import '@/render/styles/variable.scss'
|
||||
import './index.scss'
|
||||
|
||||
@ -20,7 +20,6 @@ export default defineComponent({
|
||||
},
|
||||
emits: ['select', 'change'],
|
||||
setup(props, { emit }) {
|
||||
|
||||
const titleClass = computed(() => {
|
||||
let classStr = ''
|
||||
if (!props.readonly) {
|
||||
@ -59,9 +58,7 @@ export default defineComponent({
|
||||
|
||||
const richEditorView = shallowRef(null)
|
||||
if (!props.readonly) {
|
||||
richEditorView.value = defineAsyncComponent(
|
||||
() => import('@/common/Editor/RichEditor.vue')
|
||||
)
|
||||
richEditorView.value = defineAsyncComponent(() => import('@/common/Editor/RichEditor.vue'))
|
||||
}
|
||||
|
||||
return {
|
||||
@ -75,12 +72,9 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
render() {
|
||||
const { readonly,mainTitle,onTitleInput,richEditorView} = this;
|
||||
const { readonly, mainTitle, onTitleInput, richEditorView } = this
|
||||
return (
|
||||
<div
|
||||
class={['main-title-warp', !readonly ? 'pd15' : '']}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
<div class={['main-title-warp', !readonly ? 'pd15' : '']} onClick={this.handleClick}>
|
||||
{this.isTitleHide ? (
|
||||
<div class={this.titleClass}>
|
||||
{!readonly ? (
|
||||
|
@ -43,10 +43,7 @@ export default defineComponent({
|
||||
render() {
|
||||
const { submitConf } = this.props
|
||||
return (
|
||||
<div
|
||||
class={['submit-warp', 'preview-submit_wrapper']}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
<div class={['submit-warp', 'preview-submit_wrapper']} onClick={this.handleClick}>
|
||||
<button class="submit-btn" type="primary" onClick={this.submit}>
|
||||
{submitConf.submitTitle}
|
||||
</button>
|
||||
|
@ -9,7 +9,11 @@
|
||||
"
|
||||
>
|
||||
</Component>
|
||||
<LogoIcon v-if="!['successPage', 'indexPage'].includes(store.state.router)" :logo-conf="logoConf" :readonly="true" />
|
||||
<LogoIcon
|
||||
v-if="!['successPage', 'indexPage'].includes(store.state.router)"
|
||||
:logo-conf="logoConf"
|
||||
:readonly="true"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
@ -10,7 +10,8 @@ export const useShowInput = (questionKey) => {
|
||||
if (curRange.isShowInput) {
|
||||
const rangeKey = `${questionKey}_${key}`
|
||||
othersValue[rangeKey] = formValues[rangeKey]
|
||||
;(curRange.othersKey = rangeKey), (curRange.othersValue = formValues[rangeKey])
|
||||
curRange.othersKey = rangeKey
|
||||
curRange.othersValue = formValues[rangeKey]
|
||||
if (!questionVal.toString().includes(key) && formValues[rangeKey]) {
|
||||
// 如果分值被未被选中且对应的填写更多有值,则清空填写更多
|
||||
const data = {
|
||||
|
Loading…
Reference in New Issue
Block a user