mirror of
https://github.com/1Panel-dev/MaxKB.git
synced 2025-12-26 01:33:05 +00:00
feat: resource authorization
This commit is contained in:
parent
b1a6c463d0
commit
1dfff30daf
|
|
@ -0,0 +1,43 @@
|
|||
import { Result } from '@/request/Result'
|
||||
import { get, put, post, del } from '@/request/index'
|
||||
import type { pageRequest } from '@/api/type/common'
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
const prefix = '/workspace'
|
||||
/**
|
||||
* 获取资源权限
|
||||
* @query 参数
|
||||
*/
|
||||
const getResourceAuthorization: (workspace_id: String) => Promise<Result<any>> = (workspace_id) => {
|
||||
return get(`${prefix}/${workspace_id}/user_resource_permission`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改成员权限
|
||||
* @param 参数 member_id
|
||||
* @param 参数 {
|
||||
"team_resource_permission_list": [
|
||||
{
|
||||
"auth_target_type": "KNOWLEDGE",
|
||||
"target_id": "string",
|
||||
"auth_type": "ROLE",
|
||||
"permission": {
|
||||
"VIEW": true,
|
||||
"MANAGE": true,
|
||||
"ROLE": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
*/
|
||||
const putResourceAuthorization: (workspace_id: String, body: any) => Promise<Result<any>> = (
|
||||
workspace_id,
|
||||
body,
|
||||
) => {
|
||||
return put(`${prefix}/${workspace_id}/user_resource_permission`, body)
|
||||
}
|
||||
|
||||
export default {
|
||||
getResourceAuthorization,
|
||||
putResourceAuthorization,
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
export enum AuthorizationEnum {
|
||||
MANAGE = 'MANAGE',
|
||||
USE = 'USE',
|
||||
DATASET = 'DATASET',
|
||||
APPLICATION = 'APPLICATION'
|
||||
}
|
||||
|
|
@ -0,0 +1,216 @@
|
|||
export default {
|
||||
title: '应用',
|
||||
createApplication: '创建应用',
|
||||
importApplication: '导入应用',
|
||||
copyApplication: '复制应用',
|
||||
workflow: '高级编排',
|
||||
simple: '简单配置',
|
||||
searchBar: {
|
||||
placeholder: '按名称搜索'
|
||||
},
|
||||
setting: {
|
||||
demo: '演示'
|
||||
},
|
||||
delete: {
|
||||
confirmTitle: '是否删除应用:',
|
||||
confirmMessage: '删除后该应用将不再提供服务,请谨慎操作。'
|
||||
},
|
||||
tip: {
|
||||
ExportError: '导出失败',
|
||||
professionalMessage: '社区版最多支持 5 个应用,如需拥有更多应用,请升级为专业版。',
|
||||
saveErrorMessage: '保存失败,请检查输入或稍后再试',
|
||||
loadingErrorMessage: '加载配置失败,请检查输入或稍后再试'
|
||||
},
|
||||
applicationForm: {
|
||||
title: {
|
||||
appTest: '调试预览',
|
||||
copy: '副本'
|
||||
},
|
||||
form: {
|
||||
appName: {
|
||||
label: '名称',
|
||||
placeholder: '请输入应用名称',
|
||||
requiredMessage: '请输入应用名称'
|
||||
},
|
||||
appDescription: {
|
||||
label: '描述',
|
||||
placeholder: '描述该应用的应用场景及用途,如:XXX 小助手回答用户提出的 XXX 产品使用问题'
|
||||
},
|
||||
appType: {
|
||||
label: '类型',
|
||||
simplePlaceholder: '适合新手创建小助手',
|
||||
workflowPlaceholder: '适合高级用户自定义小助手的工作流'
|
||||
},
|
||||
appTemplate: {
|
||||
blankApp: '空白应用',
|
||||
assistantApp: '知识库问答助手'
|
||||
},
|
||||
aiModel: {
|
||||
label: 'AI 模型',
|
||||
placeholder: '请选择 AI 模型'
|
||||
},
|
||||
roleSettings: {
|
||||
label: '系统角色',
|
||||
placeholder: '你是 xxx 小助手'
|
||||
},
|
||||
prompt: {
|
||||
label: '提示词',
|
||||
noReferences: ' (无引用知识库)',
|
||||
references: ' (引用知识库)',
|
||||
placeholder: '请输入提示词',
|
||||
requiredMessage: '请输入提示词',
|
||||
tooltip:
|
||||
'通过调整提示词内容,可以引导大模型聊天方向,该提示词会被固定在上下文的开头,可以使用变量。',
|
||||
noReferencesTooltip:
|
||||
'通过调整提示词内容,可以引导大模型聊天方向,该提示词会被固定在上下文的开头。可以使用变量:{question} 是用户提出问题的占位符。',
|
||||
referencesTooltip:
|
||||
'通过调整提示词内容,可以引导大模型聊天方向,该提示词会被固定在上下文的开头。可以使用变量:{data} 是引用知识库中分段的占位符;{question} 是用户提出问题的占位符。',
|
||||
defaultPrompt: `已知信息:{data}
|
||||
用户问题:{question}
|
||||
回答要求:
|
||||
- 请使用中文回答用户问题`
|
||||
},
|
||||
historyRecord: {
|
||||
label: '历史聊天记录'
|
||||
},
|
||||
relatedKnowledge: {
|
||||
label: '关联知识库',
|
||||
placeholder: '关联的知识库展示在这里'
|
||||
},
|
||||
multipleRoundsDialogue: '多轮对话',
|
||||
|
||||
prologue: '开场白',
|
||||
defaultPrologue:
|
||||
'您好,我是 XXX 小助手,您可以向我提出 XXX 使用问题。\n- XXX 主要功能有什么?\n- XXX 如何收费?\n- 需要转人工服务',
|
||||
|
||||
problemOptimization: {
|
||||
label: '问题优化',
|
||||
tooltip: '根据历史聊天优化完善当前问题,更利于匹配知识点。'
|
||||
},
|
||||
voiceInput: {
|
||||
label: '语音输入',
|
||||
placeholder: '请选择语音识别模型',
|
||||
requiredMessage: '请选择语音输入模型',
|
||||
autoSend: '自动发送'
|
||||
},
|
||||
voicePlay: {
|
||||
label: '语音播放',
|
||||
placeholder: '请选择语音合成模型',
|
||||
requiredMessage: '请选择语音播放模型',
|
||||
autoPlay: '自动播放',
|
||||
browser: '浏览器播放(免费)',
|
||||
tts: 'TTS模型',
|
||||
listeningTest: '试听'
|
||||
},
|
||||
reasoningContent: {
|
||||
label: '输出思考',
|
||||
tooltip: '请根据模型返回的思考标签设置,标签中间的内容将会认定为思考过程',
|
||||
start: '开始',
|
||||
end: '结束'
|
||||
}
|
||||
},
|
||||
buttons: {
|
||||
publish: '保存并发布',
|
||||
|
||||
addModel: '添加模型'
|
||||
},
|
||||
|
||||
dialog: {
|
||||
addDataset: '添加关联知识库',
|
||||
addDatasetPlaceholder: '所选知识库必须使用相同的 Embedding 模型',
|
||||
selected: '已选',
|
||||
countDataset: '个知识库',
|
||||
|
||||
selectSearchMode: '检索模式',
|
||||
vectorSearch: '向量检索',
|
||||
vectorSearchTooltip: '向量检索是一种基于向量相似度的检索方式,适用于知识库中的大数据量场景。',
|
||||
fullTextSearch: '全文检索',
|
||||
fullTextSearchTooltip:
|
||||
'全文检索是一种基于文本相似度的检索方式,适用于知识库中的小数据量场景。',
|
||||
hybridSearch: '混合检索',
|
||||
hybridSearchTooltip:
|
||||
'混合检索是一种基于向量和文本相似度的检索方式,适用于知识库中的中等数据量场景。',
|
||||
similarityThreshold: '相似度高于',
|
||||
similarityTooltip: '相似度越高相关性越强。',
|
||||
topReferences: '引用分段数 TOP',
|
||||
maxCharacters: '最多引用字符数',
|
||||
noReferencesAction: '无引用知识库分段时',
|
||||
continueQuestioning: '继续向 AI 模型提问',
|
||||
provideAnswer: '指定回答内容',
|
||||
designated_answer:
|
||||
'你好,我是 XXX 小助手,我的知识库只包含了 XXX 产品相关知识,请重新描述您的问题。',
|
||||
defaultPrompt1:
|
||||
'()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在',
|
||||
defaultPrompt2: '标签中'
|
||||
}
|
||||
},
|
||||
applicationAccess: {
|
||||
title: '应用接入',
|
||||
wecom: '企业微信应用',
|
||||
wecomTip: '打造企业微信智能应用',
|
||||
dingtalk: '钉钉应用',
|
||||
dingtalkTip: '打造钉钉智能应用',
|
||||
wechat: '公众号',
|
||||
wechatTip: '打造公众号智能应用',
|
||||
lark: '飞书应用',
|
||||
larkTip: '打造飞书智能应用',
|
||||
slack: 'Slack',
|
||||
slackTip: '打造 Slack 智能应用',
|
||||
setting: '配置',
|
||||
callback: '回调地址',
|
||||
callbackTip: '请输入回调地址',
|
||||
wecomPlatform: '企业微信后台',
|
||||
wechatPlatform: '微信公众平台',
|
||||
dingtalkPlatform: '钉钉开放平台',
|
||||
larkPlatform: '飞书开放平台',
|
||||
wecomSetting: {
|
||||
title: '企业微信应用配置',
|
||||
cropId: '企业 ID',
|
||||
cropIdPlaceholder: '请输入企业 ID',
|
||||
agentIdPlaceholder: '请输入Agent ID',
|
||||
secretPlaceholder: '请输入Secret',
|
||||
tokenPlaceholder: '请输入Token',
|
||||
encodingAesKeyPlaceholder: '请输入EncodingAESKey',
|
||||
authenticationSuccessful: '认证成功',
|
||||
urlInfo: '-应用管理-自建-创建的应用-接收消息-设置 API 接收的 "URL" 中'
|
||||
},
|
||||
dingtalkSetting: {
|
||||
title: '钉钉应用配置',
|
||||
clientIdPlaceholder: '请输入Client ID',
|
||||
clientSecretPlaceholder: '请输入Client Secret',
|
||||
urlInfo: '-机器人页面,设置 "消息接收模式" 为 HTTP模式 ,并把上面URL填写到"消息接收地址"中'
|
||||
},
|
||||
wechatSetting: {
|
||||
title: '公众号应用配置',
|
||||
appId: '开发者ID (APP ID)',
|
||||
appIdPlaceholder: '请输入开发者ID (APP ID)',
|
||||
appSecret: '开发者密钥 (APP SECRET)',
|
||||
appSecretPlaceholder: '请输入开发者密钥 (APP SECRET)',
|
||||
token: '令牌 (TOKEN)',
|
||||
tokenPlaceholder: '请输入令牌 (TOKEN)',
|
||||
aesKey: '消息加解密密钥',
|
||||
aesKeyPlaceholder: '请输入消息加解密密钥',
|
||||
urlInfo: '-设置与开发-基本配置-服务器配置的 "服务器地址URL" 中'
|
||||
},
|
||||
larkSetting: {
|
||||
title: '飞书应用配置',
|
||||
appIdPlaceholder: '请输入App ID',
|
||||
appSecretPlaceholder: '请输入App Secret',
|
||||
verificationTokenPlaceholder: '请输入Verification Token',
|
||||
urlInfo: '-事件与回调-事件配置-配置订阅方式的 "请求地址" 中',
|
||||
folderTokenPlaceholder: '请输入Folder Token'
|
||||
},
|
||||
slackSetting: {
|
||||
title: 'Slack 应用配置',
|
||||
signingSecretPlaceholder: '请输入 Signing Secret',
|
||||
botUserTokenPlaceholder: '请输入 Bot User Token'
|
||||
},
|
||||
copyUrl: '复制链接填入到'
|
||||
},
|
||||
hitTest: {
|
||||
title: '命中测试',
|
||||
text: '针对用户提问调试段落匹配情况,保障回答效果。',
|
||||
emptyMessage1: '命中段落显示在这里',
|
||||
emptyMessage2: '没有命中的分段'
|
||||
}
|
||||
}
|
||||
|
|
@ -6,8 +6,9 @@ import document from './document'
|
|||
import system from './system'
|
||||
import userManage from './user-manage'
|
||||
import resourceAuthorization from './resource-authorization'
|
||||
import application from './application'
|
||||
// import notFound from './404'
|
||||
// import application from './application'
|
||||
|
||||
// import applicationOverview from './application-overview'
|
||||
|
||||
// import user from './user'
|
||||
|
|
@ -27,9 +28,10 @@ export default {
|
|||
document,
|
||||
system,
|
||||
userManage,
|
||||
resourceAuthorization
|
||||
resourceAuthorization,
|
||||
application,
|
||||
// notFound,
|
||||
// application,
|
||||
|
||||
// applicationOverview,
|
||||
|
||||
// user,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,31 @@
|
|||
export default {
|
||||
title: '资源授权',
|
||||
member: '成员',
|
||||
manage: '所有者',
|
||||
permissionSetting: '资源权限配置',
|
||||
addMember: '添加成员',
|
||||
addSubTitle: '成员登录后可以访问到您授权的数据。',
|
||||
searchBar: {
|
||||
placeholder: '请输入用户名搜索'
|
||||
},
|
||||
delete: {
|
||||
button: '移除',
|
||||
confirmTitle: '是否移除成员:',
|
||||
confirmMessage: '移除后将会取消成员拥有的知识库和应用权限。'
|
||||
},
|
||||
setting: {
|
||||
management: '管理',
|
||||
check: '查看'
|
||||
},
|
||||
teamForm: {
|
||||
form: {
|
||||
userName: {
|
||||
label: '用户名/邮箱',
|
||||
placeholder: '请输入成员的用户名或邮箱',
|
||||
requiredMessage: '请输入用户名/邮箱'
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,4 +39,7 @@ $primary-color: #3370ff;
|
|||
/** ai-chat */
|
||||
--dialog-bg-gradient-color:
|
||||
linear-gradient(188deg, rgba(235, 241, 255, 0.2) 39.6%, rgba(231, 249, 255, 0.2) 94.3%), #eff0f1;
|
||||
|
||||
/** 资源授权 */
|
||||
--setting-left-width: 280px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,122 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:close-on-press-escape="false"
|
||||
:close-on-click-modal="false"
|
||||
:destroy-on-close="true"
|
||||
width="600"
|
||||
class="member-dialog"
|
||||
>
|
||||
<template #header="{ titleId, titleClass }">
|
||||
<h4 :id="titleId" :class="titleClass">{{ $t('views.team.addMember') }}</h4>
|
||||
<div class="dialog-sub-title">{{ $t('views.team.addSubTitle') }}</div>
|
||||
</template>
|
||||
|
||||
<el-form
|
||||
ref="addMemberFormRef"
|
||||
:model="memberForm"
|
||||
label-position="top"
|
||||
:rules="rules"
|
||||
require-asterisk-position="right"
|
||||
@submit.prevent
|
||||
>
|
||||
<el-form-item :label="$t('views.team.teamForm.form.userName.label')" prop="users">
|
||||
<tags-input v-model:tags="memberForm.users" :placeholder="$t('views.team.teamForm.form.userName.placeholder')" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click.prevent="dialogVisible = false"> {{ $t('common.cancel') }} </el-button>
|
||||
<el-button type="primary" @click="submitMember(addMemberFormRef)" :loading="loading">
|
||||
{{ $t('common.add') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, onMounted } from 'vue'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { MsgSuccess } from '@/utils/message'
|
||||
import AuthorizationApi from '@/api/user/resource-authorization'
|
||||
import { t } from '@/locales'
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
|
||||
const memberForm = ref({
|
||||
users: []
|
||||
})
|
||||
|
||||
const addMemberFormRef = ref<FormInstance>()
|
||||
|
||||
const loading = ref<boolean>(false)
|
||||
|
||||
const rules = ref<FormRules>({
|
||||
users: [
|
||||
{
|
||||
type: 'array',
|
||||
required: true,
|
||||
message: t('views.team.teamForm.form.userName.requiredMessage'),
|
||||
trigger: 'change'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
watch(dialogVisible, (bool) => {
|
||||
if (!bool) {
|
||||
memberForm.value = {
|
||||
users: []
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
|
||||
const open = () => {
|
||||
dialogVisible.value = true
|
||||
}
|
||||
const submitMember = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
await formEl.validate((valid, fields) => {
|
||||
if (valid) {
|
||||
loading.value = true
|
||||
let idsArray = memberForm.value.users.map((obj: any) => obj.id)
|
||||
AuthorizationApi.postCreatTeamMember(idsArray)
|
||||
.then((res) => {
|
||||
MsgSuccess(t('common.submitSuccess'))
|
||||
emit('refresh', idsArray)
|
||||
dialogVisible.value = false
|
||||
loading.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {})
|
||||
|
||||
defineExpose({ open, close })
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.member-dialog {
|
||||
.el-dialog__header {
|
||||
padding-bottom: 19px;
|
||||
}
|
||||
}
|
||||
.custom-select-multiple {
|
||||
width: 200%;
|
||||
.el-input {
|
||||
min-height: 100px;
|
||||
}
|
||||
.el-select__tags {
|
||||
top: 0;
|
||||
transform: none;
|
||||
padding-top: 8px;
|
||||
}
|
||||
.el-input__wrapper {
|
||||
align-items: start;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,205 @@
|
|||
<template>
|
||||
<el-input
|
||||
v-model="filterText"
|
||||
:placeholder="$t('common.search')"
|
||||
prefix-icon="Search"
|
||||
class="p-24 pt-0 pb-0 mb-16 mt-4"
|
||||
clearable
|
||||
/>
|
||||
<div class="p-24 pt-0">
|
||||
<el-table :data="filterData" :max-height="tableHeight">
|
||||
<el-table-column
|
||||
prop="name"
|
||||
:label="
|
||||
isApplication
|
||||
? $t('views.application.applicationForm.form.appName.label')
|
||||
: $t('views.dataset.datasetForm.form.datasetName.label')
|
||||
"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<div class="flex align-center">
|
||||
<AppAvatar
|
||||
v-if="isApplication && isAppIcon(row?.icon)"
|
||||
style="background: none"
|
||||
class="mr-12"
|
||||
shape="square"
|
||||
:size="24"
|
||||
>
|
||||
<img :src="row?.icon" alt="" />
|
||||
</AppAvatar>
|
||||
|
||||
<AppAvatar
|
||||
v-else-if="row?.name && isApplication"
|
||||
:name="row?.name"
|
||||
pinyinColor
|
||||
shape="square"
|
||||
:size="24"
|
||||
class="mr-12"
|
||||
/>
|
||||
<AppAvatar
|
||||
v-if="row.icon === '1' && isDataset"
|
||||
class="mr-8 avatar-purple"
|
||||
shape="square"
|
||||
:size="24"
|
||||
>
|
||||
<img src="@/assets/icon_web.svg" style="width: 58%" alt="" />
|
||||
</AppAvatar>
|
||||
<AppAvatar
|
||||
v-else-if="row.icon === '2' && isDataset"
|
||||
class="mr-8 avatar-purple"
|
||||
shape="square"
|
||||
:size="24"
|
||||
style="background: none"
|
||||
>
|
||||
<img src="@/assets/logo_lark.svg" style="width: 100%" alt="" />
|
||||
</AppAvatar>
|
||||
<AppAvatar v-else-if="isDataset" class="mr-8 avatar-blue" shape="square" :size="24">
|
||||
<img src="@/assets/icon_document.svg" style="width: 58%" alt="" />
|
||||
</AppAvatar>
|
||||
<auto-tooltip :content="row?.name">
|
||||
{{ row?.name }}
|
||||
</auto-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:label="$t('views.team.setting.management')"
|
||||
align="center"
|
||||
width="100"
|
||||
fixed="right"
|
||||
>
|
||||
<template #header>
|
||||
<el-checkbox
|
||||
:disabled="props.manage"
|
||||
v-model="allChecked[TeamEnum.MANAGE]"
|
||||
:indeterminate="allIndeterminate[TeamEnum.MANAGE]"
|
||||
:label="$t('views.team.setting.management')"
|
||||
/>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<el-checkbox
|
||||
:disabled="props.manage"
|
||||
v-model="row.operate[TeamEnum.MANAGE]"
|
||||
@change="(e: boolean) => checkedOperateChange(TeamEnum.MANAGE, row, e)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:label="$t('views.team.setting.check')"
|
||||
align="center"
|
||||
width="100"
|
||||
fixed="right"
|
||||
>
|
||||
<template #header>
|
||||
<el-checkbox
|
||||
:disabled="props.manage"
|
||||
v-model="allChecked[TeamEnum.USE]"
|
||||
:indeterminate="allIndeterminate[TeamEnum.USE]"
|
||||
:label="$t('views.team.setting.check')"
|
||||
/>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<el-checkbox
|
||||
:disabled="props.manage"
|
||||
v-model="row.operate[TeamEnum.USE]"
|
||||
@change="(e: boolean) => checkedOperateChange(TeamEnum.USE, row, e)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch, computed } from 'vue'
|
||||
import { TeamEnum } from '@/enums/team'
|
||||
import { isAppIcon } from '@/utils/application'
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
id: String,
|
||||
type: String,
|
||||
tableHeight: Number,
|
||||
manage: Boolean
|
||||
})
|
||||
|
||||
const isDataset = computed(() => props.type === TeamEnum.DATASET)
|
||||
const isApplication = computed(() => props.type === TeamEnum.APPLICATION)
|
||||
|
||||
const emit = defineEmits(['update:data'])
|
||||
const allChecked: any = ref({
|
||||
[TeamEnum.MANAGE]: computed({
|
||||
get: () => {
|
||||
return filterData.value.some((item: any) => item.operate[TeamEnum.MANAGE])
|
||||
},
|
||||
set: (val: boolean) => {
|
||||
if (val) {
|
||||
filterData.value.map((item: any) => {
|
||||
item.operate[TeamEnum.MANAGE] = true
|
||||
item.operate[TeamEnum.USE] = true
|
||||
})
|
||||
} else {
|
||||
filterData.value.map((item: any) => {
|
||||
item.operate[TeamEnum.MANAGE] = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}),
|
||||
[TeamEnum.USE]: computed({
|
||||
get: () => {
|
||||
return filterData.value.some((item: any) => item.operate[TeamEnum.USE])
|
||||
},
|
||||
set: (val: boolean) => {
|
||||
if (val) {
|
||||
filterData.value.map((item: any) => {
|
||||
item.operate[TeamEnum.USE] = true
|
||||
})
|
||||
} else {
|
||||
filterData.value.map((item: any) => {
|
||||
item.operate[TeamEnum.USE] = false
|
||||
item.operate[TeamEnum.MANAGE] = false
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const filterText = ref('')
|
||||
|
||||
const filterData = computed(() =>
|
||||
props.data.filter((v: any) => v.name.toLowerCase().includes(filterText.value.toLowerCase()))
|
||||
)
|
||||
|
||||
const allIndeterminate: any = ref({
|
||||
[TeamEnum.MANAGE]: computed(() => {
|
||||
const all_not_checked = filterData.value.every((item: any) => !item.operate[TeamEnum.MANAGE])
|
||||
if (all_not_checked) {
|
||||
return false
|
||||
}
|
||||
return !filterData.value.every((item: any) => item.operate[TeamEnum.MANAGE])
|
||||
}),
|
||||
[TeamEnum.USE]: computed(() => {
|
||||
const all_not_checked = filterData.value.every((item: any) => !item.operate[TeamEnum.USE])
|
||||
if (all_not_checked) {
|
||||
return false
|
||||
}
|
||||
return !filterData.value.every((item: any) => item.operate[TeamEnum.USE])
|
||||
})
|
||||
})
|
||||
|
||||
function checkedOperateChange(Name: string | number, row: any, e: boolean) {
|
||||
props.data.map((item: any) => {
|
||||
if (item.id === row.id) {
|
||||
item.operate[Name] = e
|
||||
if (Name === TeamEnum.MANAGE && e) {
|
||||
item.operate[TeamEnum.USE] = true
|
||||
} else if (Name === TeamEnum.USE && !e) {
|
||||
item.operate[TeamEnum.MANAGE] = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -1,9 +1,223 @@
|
|||
<template><span>22222</span></template>
|
||||
<template>
|
||||
<div class="p-16-24">
|
||||
<h4 class="mb-16">{{ $t('views.userManage.title') }}</h4>
|
||||
<el-card>
|
||||
<div class="resource-authorization flex main-calc-height">
|
||||
<div class="team-member p-8 border-r">
|
||||
<div class="flex-between p-16">
|
||||
<h4>{{ $t('views.resourceAuthorization.member') }}</h4>
|
||||
</div>
|
||||
<div class="team-member-input">
|
||||
<el-input
|
||||
v-model="filterText"
|
||||
:placeholder="$t('common.search')"
|
||||
prefix-icon="Search"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
<div class="list-height-left">
|
||||
<el-scrollbar>
|
||||
<common-list
|
||||
:data="filterMember"
|
||||
class="mt-8"
|
||||
v-loading="loading"
|
||||
@click="clickMemberHandle"
|
||||
:default-active="currentUser"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<div class="flex-between">
|
||||
<div>
|
||||
<span class="mr-8">{{ row.username }}</span>
|
||||
<el-tag v-if="isManage(row.type)" class="default-tag">{{
|
||||
$t('views.resourceAuthorization.manage')
|
||||
}}</el-tag>
|
||||
</div>
|
||||
<div @click.stop style="margin-top: 5px">
|
||||
<el-dropdown trigger="click" v-if="!isManage(row.type)">
|
||||
<span class="cursor">
|
||||
<el-icon class="rotate-90"><MoreFilled /></el-icon>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click.prevent="deleteMember(row)">{{
|
||||
$t('views.resourceAuthorization.delete.button')
|
||||
}}</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</common-list>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
<div class="permission-setting flex" v-loading="rLoading">
|
||||
<div class="team-manage__table">
|
||||
<h4 class="p-24 pb-0 mb-4">{{ $t('views.resourceAuthorization.permissionSetting') }}</h4>
|
||||
<el-tabs v-model="activeName" class="team-manage__tabs">
|
||||
<el-tab-pane
|
||||
v-for="(item, index) in settingTags"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:name="item.value"
|
||||
>
|
||||
<!-- <PermissionSetting
|
||||
:key="index"
|
||||
:data="item.data"
|
||||
:type="item.value"
|
||||
:tableHeight="tableHeight"
|
||||
:manage="isManage(currentType)"
|
||||
></PermissionSetting> -->
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<div class="submit-button">
|
||||
<el-button type="primary" @click="submitPermissions">{{ $t('common.save') }}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
<!-- <CreateMemberDialog ref="CreateMemberRef" @refresh="refresh" /> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, reactive, watch } from 'vue'
|
||||
import AuthorizationApi from '@/api/user/resource-authorization'
|
||||
import type { TeamMember } from '@/api/type/team'
|
||||
// import CreateMemberDialog from './component/CreateMemberDialog.vue'
|
||||
// import PermissionSetting from './component/PermissionSetting.vue'
|
||||
import { MsgSuccess, MsgConfirm } from '@/utils/message'
|
||||
import { AuthorizationEnum } from '@/enums/system'
|
||||
import { t } from '@/locales'
|
||||
// const CreateMemberRef = ref<InstanceType<typeof CreateMemberDialog>>()
|
||||
const loading = ref(false)
|
||||
const rLoading = ref(false)
|
||||
const memberList = ref<TeamMember[]>([]) // 全部成员
|
||||
const filterMember = ref<TeamMember[]>([]) // 搜索过滤后列表
|
||||
const currentUser = ref<String>('')
|
||||
const currentType = ref<String>('')
|
||||
|
||||
onMounted(() => {})
|
||||
const filterText = ref('')
|
||||
|
||||
const activeName = ref(AuthorizationEnum.DATASET)
|
||||
const tableHeight = ref(0)
|
||||
|
||||
const settingTags = reactive([
|
||||
{
|
||||
label: t('views.knowledge.title'),
|
||||
value: AuthorizationEnum.DATASET,
|
||||
data: [] as any,
|
||||
},
|
||||
{
|
||||
label: t('views.application.title'),
|
||||
value: AuthorizationEnum.APPLICATION,
|
||||
data: [] as any,
|
||||
},
|
||||
])
|
||||
|
||||
watch(filterText, (val) => {
|
||||
if (val) {
|
||||
filterMember.value = memberList.value.filter((v) =>
|
||||
v.username.toLowerCase().includes(val.toLowerCase()),
|
||||
)
|
||||
} else {
|
||||
filterMember.value = memberList.value
|
||||
}
|
||||
})
|
||||
|
||||
function isManage(type: String) {
|
||||
return type === 'manage'
|
||||
}
|
||||
|
||||
function submitPermissions() {
|
||||
rLoading.value = true
|
||||
const obj: any = {
|
||||
team_member_permission_list: [],
|
||||
}
|
||||
settingTags.map((item) => {
|
||||
item.data.map((v: any) => {
|
||||
obj['team_member_permission_list'].push({
|
||||
target_id: v.id,
|
||||
type: v.type,
|
||||
operate: v.operate,
|
||||
})
|
||||
})
|
||||
})
|
||||
AuthorizationApi.putResourceAuthorization(currentUser.value, obj)
|
||||
.then(() => {
|
||||
MsgSuccess(t('common.submitSuccess'))
|
||||
ResourcePermissions(currentUser.value)
|
||||
})
|
||||
.catch(() => {
|
||||
rLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function ResourcePermissions() {
|
||||
rLoading.value = true
|
||||
AuthorizationApi.getResourceAuthorization('default')
|
||||
.then((res) => {
|
||||
rLoading.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
rLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function refresh(data?: string[]) {}
|
||||
|
||||
onMounted(() => {
|
||||
tableHeight.value = window.innerHeight - 330
|
||||
window.onresize = () => {
|
||||
return (() => {
|
||||
tableHeight.value = window.innerHeight - 330
|
||||
})()
|
||||
}
|
||||
ResourcePermissions()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
<style lang="scss" scoped>
|
||||
.resource-authorization {
|
||||
.add-user-icon {
|
||||
font-size: 17px;
|
||||
}
|
||||
.team-member-input {
|
||||
padding: 0 calc(var(--app-base-px) * 2);
|
||||
}
|
||||
.team-member {
|
||||
box-sizing: border-box;
|
||||
width: var(--setting-left-width);
|
||||
min-width: var(--setting-left-width);
|
||||
}
|
||||
|
||||
.permission-setting {
|
||||
box-sizing: border-box;
|
||||
width: calc(100% - var(--setting-left-width));
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
.submit-button {
|
||||
position: absolute;
|
||||
top: 54px;
|
||||
right: 24px;
|
||||
}
|
||||
}
|
||||
.list-height-left {
|
||||
height: calc(var(--create-dataset-height) - 60px);
|
||||
}
|
||||
|
||||
&__tabs {
|
||||
margin-top: 10px;
|
||||
|
||||
:deep(.el-tabs__nav-scroll) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
}
|
||||
&__table {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Reference in New Issue