feat: 问卷空间协作能力前端 (#246)
This commit is contained in:
parent
9448d3c111
commit
7ad01f6b26
@ -11,5 +11,11 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
ecmaVersion: 'latest'
|
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']
|
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||||
ElInput: typeof import('element-plus/es')['ElInput']
|
ElInput: typeof import('element-plus/es')['ElInput']
|
||||||
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
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']
|
ElOption: typeof import('element-plus/es')['ElOption']
|
||||||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||||
ElPopover: typeof import('element-plus/es')['ElPopover']
|
ElPopover: typeof import('element-plus/es')['ElPopover']
|
||||||
@ -27,6 +30,7 @@ declare module 'vue' {
|
|||||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||||
ElRow: typeof import('element-plus/es')['ElRow']
|
ElRow: typeof import('element-plus/es')['ElRow']
|
||||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||||
|
ElSelectV2: typeof import('element-plus/es')['ElSelectV2']
|
||||||
ElSlider: typeof import('element-plus/es')['ElSlider']
|
ElSlider: typeof import('element-plus/es')['ElSlider']
|
||||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||||
ElTable: typeof import('element-plus/es')['ElTable']
|
ElTable: typeof import('element-plus/es')['ElTable']
|
||||||
@ -44,6 +48,7 @@ declare module 'vue' {
|
|||||||
IEpDelete: typeof import('~icons/ep/delete')['default']
|
IEpDelete: typeof import('~icons/ep/delete')['default']
|
||||||
IEpLoading: typeof import('~icons/ep/loading')['default']
|
IEpLoading: typeof import('~icons/ep/loading')['default']
|
||||||
IEpMinus: typeof import('~icons/ep/minus')['default']
|
IEpMinus: typeof import('~icons/ep/minus')['default']
|
||||||
|
IEpMore: typeof import('~icons/ep/more')['default']
|
||||||
IEpPlus: typeof import('~icons/ep/plus')['default']
|
IEpPlus: typeof import('~icons/ep/plus')['default']
|
||||||
IEpQuestionFilled: typeof import('~icons/ep/question-filled')['default']
|
IEpQuestionFilled: typeof import('~icons/ep/question-filled')['default']
|
||||||
IEpRank: typeof import('~icons/ep/rank')['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'
|
import axios from './base'
|
||||||
|
|
||||||
export const getSurveyList = ({ curPage, filter, order }) => {
|
export const getSurveyList = ({ curPage, filter, order, workspaceId }) => {
|
||||||
return axios.get('/survey/getList', {
|
return axios.get('/survey/getList', {
|
||||||
params: {
|
params: {
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
curPage,
|
curPage,
|
||||||
filter,
|
filter,
|
||||||
order
|
order,
|
||||||
|
workspaceId
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,40 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="nav">
|
<div class="nav">
|
||||||
<LogoIcon />
|
<LogoIcon />
|
||||||
<RouterLink v-for="(tab, index) in tabs" :key="index" class="tab-btn" :to="tab.to" replace>
|
<template v-for="(tab, index) in tabs" :key="tab.text + index">
|
||||||
<div class="icon">
|
<router-link :to="tab.to" v-slot="{ isActive }">
|
||||||
<i class="iconfont" :class="tab.icon"></i>
|
<div
|
||||||
</div>
|
:class="[
|
||||||
<p>{{ tab.text }}</p>
|
'tab-btn',
|
||||||
</RouterLink>
|
(['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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<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 LogoIcon from './LogoIcon.vue'
|
||||||
|
import { SurveyPermissions } from '@/management/utils/types/workSpace.ts'
|
||||||
|
const store = useStore()
|
||||||
|
|
||||||
const tabs = [
|
const tabArr = [
|
||||||
{
|
{
|
||||||
text: '编辑问卷',
|
text: '编辑问卷',
|
||||||
icon: 'icon-bianji',
|
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>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.nav {
|
.nav {
|
||||||
|
@ -38,6 +38,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, computed, toRefs } from 'vue'
|
import { ref, reactive, computed, toRefs } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
import { useStore } from 'vuex'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import 'element-plus/theme-chalk/src/message.scss'
|
import 'element-plus/theme-chalk/src/message.scss'
|
||||||
|
|
||||||
@ -78,7 +79,7 @@ const checkForm = (fn: Function) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const store = useStore()
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
if (!state.canSubmit) {
|
if (!state.canSubmit) {
|
||||||
return
|
return
|
||||||
@ -89,10 +90,14 @@ const submit = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
state.canSubmit = false
|
state.canSubmit = false
|
||||||
const res:any = await createSurvey({
|
const payload: any = {
|
||||||
surveyType: selectType,
|
surveyType: selectType,
|
||||||
...state.form
|
...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) {
|
if (res?.code === 200 && res?.data?.id) {
|
||||||
const id = res.data.id
|
const id = res.data.id
|
||||||
router.push({
|
router.push({
|
||||||
|
@ -30,7 +30,7 @@ const onBack = () => {
|
|||||||
|
|
||||||
const toHomePage = () => {
|
const toHomePage = () => {
|
||||||
router.push({
|
router.push({
|
||||||
name: 'Home'
|
name: 'survey'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="title-container">
|
<div class="title-container">
|
||||||
<div
|
<div
|
||||||
class="title"
|
class="title"
|
||||||
@mouseover="showFullTitle"
|
@mouseover="showFullTitle"
|
||||||
@mousemove="updateTooltipPosition"
|
@mousemove="updateTooltipPosition"
|
||||||
@mouseleave="hideFullTitle"
|
@mouseleave="hideFullTitle"
|
||||||
>
|
>
|
||||||
{{ title }}
|
{{ title }}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="tooltip"
|
class="tooltip"
|
||||||
v-if="tooltipVisible"
|
v-if="tooltipVisible"
|
||||||
:style="{ top: tooltipPosition.top + 'px', left: tooltipPosition.left + 'px' }"
|
:style="{ top: tooltipPosition.top + 'px', left: tooltipPosition.left + 'px' }"
|
||||||
>
|
>
|
||||||
{{ title }}
|
{{ title }}
|
||||||
</div>
|
</div>
|
||||||
@ -73,5 +73,3 @@ const hideFullTitle = () => {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<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 { ConditionNode, RuleNode } from '@/common/logicEngine/RuleBuild'
|
||||||
import { qAbleList } from '@/management/utils/constant.js'
|
import { qAbleList } from '@/management/utils/constant.js'
|
||||||
import { cleanRichText } from '@/common/xss'
|
import { cleanRichText } from '@/common/xss'
|
||||||
|
@ -29,18 +29,18 @@ import MaterialGroup from '@/management/pages/edit/components/MaterialGroup.vue'
|
|||||||
import { useStore } from 'vuex'
|
import { useStore } from 'vuex'
|
||||||
import communalLoader from '@materials/communals/communalLoader.js'
|
import communalLoader from '@materials/communals/communalLoader.js'
|
||||||
|
|
||||||
const HeaderContent = ()=>communalLoader.loadComponent('HeaderContent')
|
const HeaderContent = () => communalLoader.loadComponent('HeaderContent')
|
||||||
const MainTitle = ()=>communalLoader.loadComponent('MainTitle')
|
const MainTitle = () => communalLoader.loadComponent('MainTitle')
|
||||||
const SubmitButton = ()=>communalLoader.loadComponent('SubmitButton')
|
const SubmitButton = () => communalLoader.loadComponent('SubmitButton')
|
||||||
const LogoIcon = ()=>communalLoader.loadComponent('LogoIcon')
|
const LogoIcon = () => communalLoader.loadComponent('LogoIcon')
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
MaterialGroup,
|
MaterialGroup,
|
||||||
HeaderContent:HeaderContent(),
|
HeaderContent: HeaderContent(),
|
||||||
MainTitle:MainTitle(),
|
MainTitle: MainTitle(),
|
||||||
SubmitButton:SubmitButton(),
|
SubmitButton: SubmitButton(),
|
||||||
LogoIcon:LogoIcon()
|
LogoIcon: LogoIcon()
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
|
@ -5,17 +5,16 @@
|
|||||||
<TextSelect
|
<TextSelect
|
||||||
v-for="item in Object.keys(selectOptionsDict)"
|
v-for="item in Object.keys(selectOptionsDict)"
|
||||||
:key="item"
|
:key="item"
|
||||||
:effect-fun="onSelectChange"
|
|
||||||
:effect-key="item"
|
|
||||||
:options="selectOptionsDict[item]"
|
:options="selectOptionsDict[item]"
|
||||||
|
:value="selectValueMap[item]"
|
||||||
|
@change="(value) => onSelectChange(item, value)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="search">
|
<div class="search">
|
||||||
<TextButton
|
<TextButton
|
||||||
v-for="item in Object.keys(buttonOptionsDict)"
|
v-for="item in Object.keys(buttonOptionsDict)"
|
||||||
:key="item"
|
:key="item"
|
||||||
:effect-fun="onButtonChange"
|
@change="(value) => onButtonChange(item, value)"
|
||||||
:effect-key="item"
|
|
||||||
:option="buttonOptionsDict[item]"
|
:option="buttonOptionsDict[item]"
|
||||||
:icon="
|
:icon="
|
||||||
buttonOptionsDict[item].icons.find(
|
buttonOptionsDict[item].icons.find(
|
||||||
@ -27,53 +26,58 @@
|
|||||||
<TextSearch placeholder="请输入问卷标题" :value="searchVal" @search="onSearchText" />
|
<TextSearch placeholder="请输入问卷标题" :value="searchVal" @search="onSearchText" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-table
|
<div class="list-wrapper" v-if="total">
|
||||||
v-if="total"
|
<el-table
|
||||||
ref="multipleListTable"
|
v-if="total"
|
||||||
class="list-table"
|
ref="multipleListTable"
|
||||||
:data="dataList"
|
class="list-table"
|
||||||
empty-text="暂无数据"
|
:data="dataList"
|
||||||
row-key="_id"
|
empty-text="暂无数据"
|
||||||
header-row-class-name="tableview-header"
|
row-key="_id"
|
||||||
row-class-name="tableview-row"
|
header-row-class-name="tableview-header"
|
||||||
cell-class-name="tableview-cell"
|
row-class-name="tableview-row"
|
||||||
style="width: 100%"
|
cell-class-name="tableview-cell"
|
||||||
v-loading="loading"
|
style="width: 100%"
|
||||||
@row-click="onRowClick"
|
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"
|
|
||||||
>
|
>
|
||||||
<template #default="scope">
|
<el-table-column column-key="space" width="20" />
|
||||||
<template v-if="field.comp">
|
<el-table-column
|
||||||
<component :is="field.comp" type="table" :value="scope.row" />
|
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>
|
||||||
<template v-else>
|
</el-table-column>
|
||||||
<span class="cell-span">{{ scope.row[field.key] }}</span>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
|
|
||||||
<el-table-column label="操作" :width="300" class-name="table-options" fixed="right">
|
<el-table-column label="操作" :width="230" class-name="table-options" fixed="right">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<ToolBar
|
<ToolBar
|
||||||
:data="scope.row"
|
:data="scope.row"
|
||||||
type="list"
|
type="list"
|
||||||
:tools="getToolConfig(scope.row)"
|
:tools="getToolConfig(scope.row)"
|
||||||
:tool-width="50"
|
:tool-width="50"
|
||||||
@on-delete="onDelete"
|
@click="handleClick"
|
||||||
@on-modify="onModify"
|
/>
|
||||||
/>
|
</template>
|
||||||
</template>
|
</el-table-column>
|
||||||
</el-table-column>
|
</el-table>
|
||||||
</el-table>
|
</div>
|
||||||
|
|
||||||
<div class="list-pagination" v-if="total">
|
<div class="list-pagination" v-if="total">
|
||||||
<el-pagination
|
<el-pagination
|
||||||
@ -95,10 +99,14 @@
|
|||||||
:question-info="questionInfo"
|
:question-info="questionInfo"
|
||||||
@on-close-codify="onCloseModify"
|
@on-close-codify="onCloseModify"
|
||||||
/>
|
/>
|
||||||
|
<CooperModify :modifyId="cooperId" :visible="cooperModify" @on-close-codify="onCooperClose" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</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 { get, map } from 'lodash-es'
|
||||||
|
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
@ -114,8 +122,7 @@ moment.locale('zh-cn')
|
|||||||
import EmptyIndex from '@/management/components/EmptyIndex.vue'
|
import EmptyIndex from '@/management/components/EmptyIndex.vue'
|
||||||
import { CODE_MAP } from '@/management/api/base'
|
import { CODE_MAP } from '@/management/api/base'
|
||||||
import { QOP_MAP } from '@/management/utils/constant'
|
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 ModifyDialog from './ModifyDialog.vue'
|
||||||
import TagModule from './TagModule.vue'
|
import TagModule from './TagModule.vue'
|
||||||
import StateModule from './StateModule.vue'
|
import StateModule from './StateModule.vue'
|
||||||
@ -123,6 +130,9 @@ import ToolBar from './ToolBar.vue'
|
|||||||
import TextSearch from './TextSearch.vue'
|
import TextSearch from './TextSearch.vue'
|
||||||
import TextSelect from './TextSelect.vue'
|
import TextSelect from './TextSelect.vue'
|
||||||
import TextButton from './TextButton.vue'
|
import TextButton from './TextButton.vue'
|
||||||
|
import CooperModify from './CooperModify.vue'
|
||||||
|
import { SurveyPermissions } from '@/management/utils/types/workSpace'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
fieldConfig,
|
fieldConfig,
|
||||||
noListDataConfig,
|
noListDataConfig,
|
||||||
@ -131,224 +141,320 @@ import {
|
|||||||
buttonOptionsDict
|
buttonOptionsDict
|
||||||
} from '../config'
|
} from '../config'
|
||||||
|
|
||||||
export default {
|
const store = useStore()
|
||||||
name: 'BaseList',
|
const router = useRouter()
|
||||||
data() {
|
const props = defineProps({
|
||||||
return {
|
loading: {
|
||||||
fields: ['type', 'title', 'remark', 'owner', 'state', 'createDate', 'updateDate'],
|
type: Boolean,
|
||||||
showModify: false,
|
default: false
|
||||||
modifyType: '',
|
|
||||||
loading: false,
|
|
||||||
noListDataConfig,
|
|
||||||
noSearchDataConfig,
|
|
||||||
questionInfo: {},
|
|
||||||
total: 0,
|
|
||||||
data: [],
|
|
||||||
currentPage: 1,
|
|
||||||
searchVal: '',
|
|
||||||
selectOptionsDict,
|
|
||||||
selectValueMap: {
|
|
||||||
surveyType: '',
|
|
||||||
'curStatus.status': ''
|
|
||||||
},
|
|
||||||
buttonOptionsDict,
|
|
||||||
buttonValueMap: {
|
|
||||||
'curStatus.date': '',
|
|
||||||
createDate: -1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
computed: {
|
data: {
|
||||||
fieldList() {
|
type: Array,
|
||||||
const fieldInfo = map(this.fields, (f) => {
|
default: () => []
|
||||||
return get(fieldConfig, f, null)
|
},
|
||||||
})
|
total: {
|
||||||
return fieldInfo
|
type: Number,
|
||||||
},
|
default: 0
|
||||||
dataList() {
|
}
|
||||||
return this.data.map((item) => {
|
})
|
||||||
return {
|
const emit = defineEmits(['reflush'])
|
||||||
...item,
|
const fields = ['type', 'title', 'remark', 'owner', 'state', 'createDate', 'updateDate']
|
||||||
'curStatus.date': item.curStatus.date
|
const showModify = ref(false)
|
||||||
}
|
const modifyType = ref('')
|
||||||
})
|
const questionInfo = ref({})
|
||||||
},
|
|
||||||
filter() {
|
const currentPage = ref(1)
|
||||||
return [
|
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: '',
|
field: 'title',
|
||||||
condition: [
|
value: searchVal.value,
|
||||||
{
|
comparator: '$regex'
|
||||||
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
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
order() {
|
{
|
||||||
const formatOrder = Object.entries(this.buttonValueMap)
|
comparator: '',
|
||||||
.filter(([, effectValue]) => effectValue)
|
condition: [
|
||||||
.reduce((prev, item) => {
|
{
|
||||||
const [effectKey, effectValue] = item
|
field: 'curStatus.status',
|
||||||
prev.push({ field: effectKey, value: effectValue })
|
value: selectValueMap.value['curStatus.status']
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
} 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: 'delete',
|
||||||
{
|
label: '删除',
|
||||||
key: QOP_MAP.EDIT,
|
icon: 'icon-shanchu'
|
||||||
label: '修改'
|
},
|
||||||
},
|
{
|
||||||
{
|
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',
|
key: 'analysis',
|
||||||
label: '数据'
|
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)
|
return result
|
||||||
if (res.code === CODE_MAP.SUCCESS) {
|
}
|
||||||
ElMessage.success('删除成功')
|
const handleClick = (key, data) => {
|
||||||
this.init()
|
switch (key) {
|
||||||
} else {
|
case QOP_MAP.EDIT:
|
||||||
ElMessage.error(res.errmsg || '删除失败')
|
onModify(data, QOP_MAP.EDIT)
|
||||||
}
|
return
|
||||||
},
|
case QOP_MAP.COPY:
|
||||||
handleCurrentChange(current) {
|
onModify(data, QOP_MAP.COPY)
|
||||||
this.currentPage = current
|
return
|
||||||
this.init()
|
case 'analysis':
|
||||||
},
|
router.push({
|
||||||
onModify(data, type = QOP_MAP.EDIT) {
|
name: 'analysisPage',
|
||||||
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',
|
|
||||||
params: {
|
params: {
|
||||||
id: row._id
|
id: data._id
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
return
|
||||||
onSearchText(e) {
|
case 'release':
|
||||||
this.searchVal = e
|
router.push({
|
||||||
this.currentPage = 1
|
name: 'publish',
|
||||||
this.init()
|
params: {
|
||||||
},
|
id: data._id
|
||||||
onSelectChange(selectValue, selectKey) {
|
}
|
||||||
this.selectValueMap[selectKey] = selectValue
|
})
|
||||||
this.currentPage = 1
|
return
|
||||||
this.init()
|
case 'delete':
|
||||||
},
|
onDelete(data)
|
||||||
onButtonChange(effectValue, effectKey) {
|
return
|
||||||
this.buttonValueMap = {
|
case 'cooper':
|
||||||
'curStatus.date': '',
|
onCooper(data)
|
||||||
createDate: ''
|
return
|
||||||
}
|
default:
|
||||||
this.buttonValueMap[effectKey] = effectValue
|
return
|
||||||
this.init()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
EmptyIndex,
|
|
||||||
ModifyDialog,
|
|
||||||
TagModule,
|
|
||||||
ToolBar,
|
|
||||||
TextSearch,
|
|
||||||
TextSelect,
|
|
||||||
TextButton,
|
|
||||||
StateModule
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@ -365,11 +471,14 @@ export default {
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.list-wrapper {
|
||||||
.list-table {
|
|
||||||
min-height: 620px;
|
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
|
background: #fff;
|
||||||
|
.list-table {
|
||||||
|
min-height: 620px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-pagination {
|
.list-pagination {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
:deep(.el-pagination) {
|
: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>
|
<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 class="list-state-badge" />
|
||||||
<span>{{ statusMaps[value.curStatus.status] }}</span>
|
<span>{{ statusMaps[value.curStatus?.status] }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -9,59 +9,38 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
import StaticIcon from './StaticIcon.vue'
|
import StaticIcon from './StaticIcon.vue'
|
||||||
|
const props = defineProps({
|
||||||
export default {
|
option: {
|
||||||
name: 'TextButton',
|
type: Object,
|
||||||
components: {
|
required: true
|
||||||
StaticIcon
|
|
||||||
},
|
},
|
||||||
data() {
|
icon: {
|
||||||
return {
|
type: String
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
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>
|
</script>
|
||||||
|
|
||||||
|
@ -12,33 +12,28 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
export default {
|
import { computed } from 'vue'
|
||||||
name: 'TextSelect',
|
|
||||||
data() {
|
const props = defineProps({
|
||||||
return {
|
value: {
|
||||||
selectValue: this.options.default
|
type: String,
|
||||||
}
|
default: ''
|
||||||
},
|
},
|
||||||
props: {
|
options: {
|
||||||
options: {
|
type: Object,
|
||||||
type: Object,
|
required: true
|
||||||
required: true
|
|
||||||
},
|
|
||||||
effectFun: {
|
|
||||||
type: Function
|
|
||||||
},
|
|
||||||
effectKey: {
|
|
||||||
type: String
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
selectValue(newSelect) {
|
|
||||||
const { effectFun } = this
|
|
||||||
typeof effectFun === 'function' && effectFun(newSelect, this.effectKey)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
const emit = defineEmits('change')
|
||||||
|
const selectValue = computed({
|
||||||
|
get() {
|
||||||
|
return props.value
|
||||||
|
},
|
||||||
|
set(val) {
|
||||||
|
emit('change', val)
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="tool-bar-root">
|
<div class="tool-bar-root" @click="handleClick">
|
||||||
<template v-if="tools.length">
|
<template v-if="iconTools.length">
|
||||||
<ToolModule
|
<ToolModule
|
||||||
v-for="t in tools"
|
v-for="t in iconTools"
|
||||||
:key="t.key"
|
:key="t.key"
|
||||||
:type="type"
|
:type="type"
|
||||||
:value="t.key"
|
:value="t.key"
|
||||||
@ -10,61 +10,44 @@
|
|||||||
:width="toolWidth"
|
:width="toolWidth"
|
||||||
@call="onCall"
|
@call="onCall"
|
||||||
/>
|
/>
|
||||||
|
<MoreTool
|
||||||
|
v-if="moreTools.length"
|
||||||
|
:type="type"
|
||||||
|
:width="toolWidth"
|
||||||
|
:tools="moreTools"
|
||||||
|
@call="onCall"
|
||||||
|
></MoreTool>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import { QOP_MAP } from '@/management/utils/constant'
|
import { computed } from 'vue'
|
||||||
|
import { slice } from 'lodash-es'
|
||||||
import ToolModule from './ToolModule.vue'
|
import ToolModule from './ToolModule.vue'
|
||||||
|
import MoreTool from './MoreTool.vue'
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
name: 'ToolBar',
|
data: Object,
|
||||||
props: {
|
type: String,
|
||||||
data: Object,
|
toolWidth: Number,
|
||||||
type: String,
|
tools: Array
|
||||||
toolWidth: Number,
|
})
|
||||||
tools: Array
|
const emit = defineEmits(['click'])
|
||||||
},
|
const limit = 4
|
||||||
data() {
|
const iconTools = computed(() => {
|
||||||
return {}
|
return slice(props.tools, 0, limit - 1)
|
||||||
},
|
})
|
||||||
methods: {
|
const moreTools = computed(() => {
|
||||||
onCall(val) {
|
return slice(props.tools, limit - 1)
|
||||||
switch (val.key) {
|
})
|
||||||
case QOP_MAP.EDIT:
|
const onCall = (val) => {
|
||||||
this.$emit('on-modify', this.data, QOP_MAP.EDIT)
|
emit('click', val.key, props.data)
|
||||||
return
|
}
|
||||||
case QOP_MAP.COPY:
|
const handleClick = (e) => {
|
||||||
this.$emit('on-modify', this.data, QOP_MAP.COPY)
|
// 防止事件冒泡,触发父组件的点击事件
|
||||||
return
|
e.preventDefault()
|
||||||
case 'analysis':
|
e.stopPropagation()
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -43,6 +43,7 @@ export default {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
// font-weight: 500;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -5,6 +5,35 @@ export const type = {
|
|||||||
register: '在线报名'
|
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 = {
|
export const fieldConfig = {
|
||||||
type: {
|
type: {
|
||||||
title: '类型',
|
title: '类型',
|
||||||
|
@ -1,45 +1,159 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="question-list-root">
|
<div class="question-list-root">
|
||||||
<div class="login-status">
|
<div class="top-nav">
|
||||||
<img class="logo-img" src="/imgs/Logo.webp" alt="logo" />
|
<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">
|
<div class="login-info">
|
||||||
您好,{{ userInfo?.username }}
|
您好,{{ userInfo?.username }}
|
||||||
<img class="login-info-img" src="/imgs/avatar.webp" />
|
<img class="login-info-img" src="/imgs/avatar.webp" />
|
||||||
<span class="logout" @click="handleLogout">退出</span>
|
<span class="logout" @click="handleLogout">退出</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="list-content">
|
<div class="content-wrap">
|
||||||
<div class="top">
|
<SliderBar :menus="spaceMenus" @select="handleSpaceSelect" />
|
||||||
<h2>问卷列表</h2>
|
<div class="list-content">
|
||||||
<el-button class="create-btn" type="default" @click="onCreate">
|
<div class="top">
|
||||||
<i class="iconfont icon-chuangjian"></i>
|
<h2>{{ spaceType === SpaceType.Group ? '团队空间' : '问卷' }}列表</h2>
|
||||||
<span>创建问卷</span>
|
<div class="operation">
|
||||||
</el-button>
|
<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>
|
</div>
|
||||||
<BaseList />
|
|
||||||
</div>
|
</div>
|
||||||
|
<SpaceModify
|
||||||
|
v-if="showSpaceModify"
|
||||||
|
:type="modifyType"
|
||||||
|
:visible="showSpaceModify"
|
||||||
|
@on-close-codify="onCloseModify"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</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 BaseList from './components/BaseList.vue'
|
||||||
import { mapState, mapActions } from 'vuex'
|
import SpaceList from './components/SpaceList.vue'
|
||||||
export default {
|
import SliderBar from './components/SliderBar.vue'
|
||||||
components: { BaseList },
|
import SpaceModify from './components/SpaceModify.vue'
|
||||||
name: 'listPage',
|
import { SpaceType } from '@/management/utils/types/workSpace'
|
||||||
computed: {
|
|
||||||
...mapState('user', ['userInfo'])
|
const store = useStore()
|
||||||
},
|
const router = useRouter()
|
||||||
methods: {
|
const userInfo = computed(() => {
|
||||||
...mapActions('user', ['logout']),
|
return store.state.user.userInfo
|
||||||
onCreate() {
|
})
|
||||||
this.$router.push('/create')
|
const loading = ref(false)
|
||||||
},
|
const surveyList = computed(() => {
|
||||||
handleLogout() {
|
return store.state.list.surveyList
|
||||||
this.logout()
|
})
|
||||||
this.$router.replace({ name: 'login' })
|
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>
|
</script>
|
||||||
|
|
||||||
@ -47,7 +161,8 @@ export default {
|
|||||||
.question-list-root {
|
.question-list-root {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: #f6f7f9;
|
background-color: #f6f7f9;
|
||||||
.login-status {
|
|
||||||
|
.top-nav {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
color: #4a4c5b;
|
color: #4a4c5b;
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
@ -55,10 +170,25 @@ export default {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
.logo-img {
|
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.04);
|
||||||
width: 90px;
|
.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 {
|
.login-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -78,28 +208,46 @@ export default {
|
|||||||
color: #faa600;
|
color: #faa600;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.content-wrap {
|
||||||
.list-content {
|
position: relative;
|
||||||
height: calc(100% - 56px);
|
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 {
|
.top {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
|
.operation {
|
||||||
|
flex: 0 1 auto;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-btn {
|
.create-btn {
|
||||||
|
background: #4a4c5b;
|
||||||
|
}
|
||||||
|
.space-btn {
|
||||||
|
background: $primary-color;
|
||||||
|
}
|
||||||
|
.btn {
|
||||||
width: 132px;
|
width: 132px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: #4a4c5b;
|
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
|
||||||
.icon-chuangjian {
|
.icon-chuangjian {
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import type { RouteRecordRaw } from 'vue-router'
|
import type { RouteRecordRaw } from 'vue-router'
|
||||||
import { useStore } from 'vuex'
|
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[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
@ -19,7 +22,8 @@ const routes: RouteRecordRaw[] = [
|
|||||||
{
|
{
|
||||||
path: '/survey/:id/edit',
|
path: '/survey/:id/edit',
|
||||||
meta: {
|
meta: {
|
||||||
needLogin: true
|
needLogin: true,
|
||||||
|
premissions: [SurveyPermissions.SurveyManage]
|
||||||
},
|
},
|
||||||
name: 'QuestionEdit',
|
name: 'QuestionEdit',
|
||||||
component: () => import('../pages/edit/index.vue'),
|
component: () => import('../pages/edit/index.vue'),
|
||||||
@ -89,7 +93,8 @@ const routes: RouteRecordRaw[] = [
|
|||||||
path: '/survey/:id/analysis',
|
path: '/survey/:id/analysis',
|
||||||
name: 'analysisPage',
|
name: 'analysisPage',
|
||||||
meta: {
|
meta: {
|
||||||
needLogin: true
|
needLogin: true,
|
||||||
|
premissions: [SurveyPermissions.DataManage]
|
||||||
},
|
},
|
||||||
component: () => import('../pages/analysis/AnalysisPage.vue')
|
component: () => import('../pages/analysis/AnalysisPage.vue')
|
||||||
},
|
},
|
||||||
@ -97,7 +102,8 @@ const routes: RouteRecordRaw[] = [
|
|||||||
path: '/survey/:id/publish',
|
path: '/survey/:id/publish',
|
||||||
name: 'publish',
|
name: 'publish',
|
||||||
meta: {
|
meta: {
|
||||||
needLogin: true
|
needLogin: true,
|
||||||
|
premissions: [SurveyPermissions.SurveyManage]
|
||||||
},
|
},
|
||||||
component: () => import('../pages/publish/PublishPage.vue')
|
component: () => import('../pages/publish/PublishPage.vue')
|
||||||
},
|
},
|
||||||
@ -125,7 +131,7 @@ const router = createRouter({
|
|||||||
routes
|
routes
|
||||||
})
|
})
|
||||||
|
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
if (!store.state.user?.initialized) {
|
if (!store.state.user?.initialized) {
|
||||||
store?.dispatch('user/init')
|
store?.dispatch('user/init')
|
||||||
@ -135,7 +141,24 @@ router.beforeEach((to, from, next) => {
|
|||||||
}
|
}
|
||||||
if (to.meta.needLogin) {
|
if (to.meta.needLogin) {
|
||||||
if (store?.state?.user?.hasLogined) {
|
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 {
|
} else {
|
||||||
next({
|
next({
|
||||||
name: 'login',
|
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
|
export default router
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import { getBannerData } from '@/management/api/skin.js'
|
import { getBannerData } from '@/management/api/skin.js'
|
||||||
|
import { getCollaboratorPermissions } from '@/management/api/space.ts'
|
||||||
|
import { CODE_MAP } from '../api/base'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
async getBannerData({ state, commit }) {
|
async getBannerData({ state, commit }) {
|
||||||
@ -9,5 +11,12 @@ export default {
|
|||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
commit('setBannerList', res.data)
|
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 { createStore } from 'vuex'
|
||||||
import edit from './edit'
|
import edit from './edit'
|
||||||
import user from './user'
|
import user from './user'
|
||||||
|
import list from './list'
|
||||||
import actions from './actions'
|
import actions from './actions'
|
||||||
import mutations from './mutations'
|
import mutations from './mutations'
|
||||||
import state from './state'
|
import state from './state'
|
||||||
@ -13,6 +13,7 @@ export default createStore({
|
|||||||
actions,
|
actions,
|
||||||
modules: {
|
modules: {
|
||||||
edit,
|
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 {
|
export default {
|
||||||
setBannerList(state, data) {
|
setBannerList(state, data) {
|
||||||
state.bannerList = data
|
state.bannerList = data
|
||||||
|
},
|
||||||
|
setCooperPermissions(state, data) {
|
||||||
|
state.cooperPermissions = data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { SurveyPermissions } from '@/management/utils/types/workSpace'
|
||||||
export default {
|
export default {
|
||||||
bannerList: []
|
bannerList: [],
|
||||||
|
cooperPermissions: Object.values(SurveyPermissions)
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'iconfont'; /* Project id 4263849 */
|
font-family: 'iconfont'; /* Project id 4263849 */
|
||||||
src:
|
src:
|
||||||
url('//at.alicdn.com/t/c/font_4263849_xndlbqcha3l.woff2?t=1711101684869') format('woff2'),
|
url('//at.alicdn.com/t/c/font_4263849_xfsn9z31epc.woff2?t=1716556097756') format('woff2'),
|
||||||
url('//at.alicdn.com/t/c/font_4263849_xndlbqcha3l.woff?t=1711101684869') format('woff'),
|
url('//at.alicdn.com/t/c/font_4263849_xfsn9z31epc.woff?t=1716556097756') 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.ttf?t=1716556097756') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconfont {
|
.iconfont {
|
||||||
@ -131,3 +131,9 @@
|
|||||||
.icon-NPSpingfen::before {
|
.icon-NPSpingfen::before {
|
||||||
content: '\e6e7';
|
content: '\e6e7';
|
||||||
}
|
}
|
||||||
|
.icon-wodekongjian::before {
|
||||||
|
content: '\e6ee';
|
||||||
|
}
|
||||||
|
.icon-tuanduikongjian::before {
|
||||||
|
content: '\e6ec';
|
||||||
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
// 问卷操作枚举
|
// 问卷操作枚举
|
||||||
export const QOP_MAP = {
|
export const QOP_MAP = {
|
||||||
|
ADD: 'add',
|
||||||
COPY: 'copy',
|
COPY: 'copy',
|
||||||
EDIT: 'edit'
|
EDIT: 'edit'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const qAbleList = ['radio', 'checkbox', 'binary-choice', 'vote']
|
export const qAbleList = ['radio', 'checkbox', 'binary-choice', 'vote']
|
||||||
export const operatorOptions = [
|
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 (
|
return (
|
||||||
<div class="header-video-warp">
|
<div class="header-video-warp">
|
||||||
<div class="video">
|
<div class="video">
|
||||||
<video {...this.attributeVideo} >
|
<video {...this.attributeVideo}>
|
||||||
<source src={this.bannerConf?.bannerConfig?.videoLink} type="video/mp4" />
|
<source src={this.bannerConf?.bannerConfig?.videoLink} type="video/mp4" />
|
||||||
</video>
|
</video>
|
||||||
{readonly ? (
|
{readonly ? (
|
||||||
|
@ -18,7 +18,6 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
emits: ['select'],
|
emits: ['select'],
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
|
|
||||||
const logoImage = computed(() => {
|
const logoImage = computed(() => {
|
||||||
return props.logoConf?.logoImage
|
return props.logoConf?.logoImage
|
||||||
})
|
})
|
||||||
@ -56,7 +55,7 @@ export default defineComponent({
|
|||||||
<div class="logo-icon-warp" onClick={this.onSelect}>
|
<div class="logo-icon-warp" onClick={this.onSelect}>
|
||||||
<div class="question-logo">
|
<div class="question-logo">
|
||||||
{this.logoImage ? (
|
{this.logoImage ? (
|
||||||
<img src={this.logoImage} style={{width: this.logoImageWidth}} />
|
<img src={this.logoImage} style={{ width: this.logoImageWidth }} />
|
||||||
) : (
|
) : (
|
||||||
this.noLogoRender()
|
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 '@/render/styles/variable.scss'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
@ -20,7 +20,6 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
emits: ['select', 'change'],
|
emits: ['select', 'change'],
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
|
|
||||||
const titleClass = computed(() => {
|
const titleClass = computed(() => {
|
||||||
let classStr = ''
|
let classStr = ''
|
||||||
if (!props.readonly) {
|
if (!props.readonly) {
|
||||||
@ -59,9 +58,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
const richEditorView = shallowRef(null)
|
const richEditorView = shallowRef(null)
|
||||||
if (!props.readonly) {
|
if (!props.readonly) {
|
||||||
richEditorView.value = defineAsyncComponent(
|
richEditorView.value = defineAsyncComponent(() => import('@/common/Editor/RichEditor.vue'))
|
||||||
() => import('@/common/Editor/RichEditor.vue')
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -75,12 +72,9 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
render() {
|
render() {
|
||||||
const { readonly,mainTitle,onTitleInput,richEditorView} = this;
|
const { readonly, mainTitle, onTitleInput, richEditorView } = this
|
||||||
return (
|
return (
|
||||||
<div
|
<div class={['main-title-warp', !readonly ? 'pd15' : '']} onClick={this.handleClick}>
|
||||||
class={['main-title-warp', !readonly ? 'pd15' : '']}
|
|
||||||
onClick={this.handleClick}
|
|
||||||
>
|
|
||||||
{this.isTitleHide ? (
|
{this.isTitleHide ? (
|
||||||
<div class={this.titleClass}>
|
<div class={this.titleClass}>
|
||||||
{!readonly ? (
|
{!readonly ? (
|
||||||
|
@ -43,10 +43,7 @@ export default defineComponent({
|
|||||||
render() {
|
render() {
|
||||||
const { submitConf } = this.props
|
const { submitConf } = this.props
|
||||||
return (
|
return (
|
||||||
<div
|
<div class={['submit-warp', 'preview-submit_wrapper']} onClick={this.handleClick}>
|
||||||
class={['submit-warp', 'preview-submit_wrapper']}
|
|
||||||
onClick={this.handleClick}
|
|
||||||
>
|
|
||||||
<button class="submit-btn" type="primary" onClick={this.submit}>
|
<button class="submit-btn" type="primary" onClick={this.submit}>
|
||||||
{submitConf.submitTitle}
|
{submitConf.submitTitle}
|
||||||
</button>
|
</button>
|
||||||
|
@ -9,7 +9,11 @@
|
|||||||
"
|
"
|
||||||
>
|
>
|
||||||
</Component>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -10,7 +10,8 @@ export const useShowInput = (questionKey) => {
|
|||||||
if (curRange.isShowInput) {
|
if (curRange.isShowInput) {
|
||||||
const rangeKey = `${questionKey}_${key}`
|
const rangeKey = `${questionKey}_${key}`
|
||||||
othersValue[rangeKey] = formValues[rangeKey]
|
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]) {
|
if (!questionVal.toString().includes(key) && formValues[rangeKey]) {
|
||||||
// 如果分值被未被选中且对应的填写更多有值,则清空填写更多
|
// 如果分值被未被选中且对应的填写更多有值,则清空填写更多
|
||||||
const data = {
|
const data = {
|
||||||
|
Loading…
Reference in New Issue
Block a user