Forráskód Böngészése

Merge branch 'master' of http://gitlab.smartai.com/zhupei/smartai2dian0

zhupei@smartai.com 1 éve
szülő
commit
fc2270886a

+ 5 - 2
public/i18n/lang-en.json

@@ -36,8 +36,11 @@
     "usernameLength": "Username must be between 3 and 20 characters long",
     "passwordLength": "Password must be between 6 and 20 characters long",
     "emailFormat": "Incorrect email format",
-
-    "logout": "Exit"
+    "logout": "Exit",
+    "okBtn": "OK",
+    "cancelBtn": "Cancel",
+    "tip": "Prompt",
+    "okText": "Are you sure you want to log out?"
   },
   "management": {
     "knowledgeTitle": "Welcome back, GuanLi4",

+ 5 - 2
public/i18n/lang-zh.json

@@ -36,8 +36,11 @@
     "usernameLength": "用户名长度必须在3-20个字符之间",
     "passwordLength": "密码长度必须在6-20个字符之间",
     "emailFormat": "邮箱格式不正确",
-
-    "logout": "退出登录"
+    "logout": "退出登录",
+    "okBtn": "确定",
+    "cancelBtn": "取消",
+    "tip": "提示",
+    "okText": "确定退出登录?"
   },
   "management": {
     "knowledgeTitle": "欢迎回来,GuanLi4",

+ 3 - 2
src/apis/intelligent.js

@@ -4,10 +4,11 @@ import request from '../base/ajax';
  * 获取智能体列表
  * @param {number} current 当前页数
  * @param {number} pageSize 每页条数
+ * @param {string} keyword 搜索
  * */
-export function getDialogList(current, pageSize) {
+export function getDialogList(current, pageSize, keyword) {
   return request({
-    url: `/api/dialog/list?current=${current}&pageSize=${pageSize}`,
+    url: `/api/dialog/list?current=${current}&pageSize=${pageSize}&keyword=${keyword}`,
     method: 'get'
   });
 }

+ 3 - 2
src/apis/knowledge.js

@@ -4,10 +4,11 @@ import request from '../base/ajax';
  * 获取知识列表
  * @param {number} current 当前页数
  * @param {number} pageSize 每页条数
+ * @param {string} keyword 搜索
  * */
-export function getKnowledgeList(current, pageSize) {
+export function getKnowledgeList(current, pageSize, keyword) {
   return request({
-    url: `/api/knowledge/list?current=${current}&pageSize=${pageSize}`,
+    url: `/api/knowledge/list?current=${current}&pageSize=${pageSize}&keyword=${keyword}`,
     method: 'get'
   });
 }

+ 14 - 0
src/apis/model.js

@@ -0,0 +1,14 @@
+import request from '../base/ajax';
+
+/**
+ * 获取模型列表
+ * @param {number} current 当前页数
+ * @param {number} pageSize 每页条数
+ * @param {string} keyword 搜索
+ * */
+export function getModelList(current, pageSize, keyword) {
+  return request({
+    url: `/api/llm/list?current=${current}&pageSize=${pageSize}&keyword=${keyword}`,
+    method: 'get'
+  });
+}

+ 70 - 0
src/modules/intelligent/LabelSelected.vue

@@ -0,0 +1,70 @@
+<template>
+  <div class="label-select">
+    <a-input
+      v-model="searchText"
+      :placeholder="$t('management.search')"
+      style="width: 100%"
+      allow-clear
+      @input="handleSearch"
+    >
+      <template #prefix>
+        <icon-search />
+      </template>
+    </a-input>
+    <a-checkbox-group v-model="checkedList" direction="vertical">
+      <a-checkbox v-for="item in labelList" :key="item.id" :value="item.value">{{ item.name }}</a-checkbox>
+    </a-checkbox-group>
+    <div class="footer">
+      <div class="label-manage" @click="toManageTags"><icon-tags /> 管理标签</div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+
+const emit = defineEmits(['openManageTags']);
+
+const searchText = ref('');
+const checkedList = ref([]);
+const labelList = ref([
+  {
+    id: 1,
+    name: '标签1',
+    value: '1'
+  },
+  {
+    id: 2,
+    name: '标签2',
+    value: '2'
+  },
+  {
+    id: 3,
+    name: '标签3',
+    value: '3'
+  },
+  {
+    id: 4,
+    name: '标签4',
+    value: '4'
+  }
+]);
+
+const handleSearch = () => {};
+const toManageTags = () => {
+  emit('openManageTags');
+};
+</script>
+<style scoped lang="css">
+.footer {
+  display: flex;
+  border-top: 1px solid var(--color-neutral-4);
+}
+.footer .label-manage {
+  margin-top: 10px;
+  cursor: pointer;
+}
+.arco-input-wrapper {
+  background-color: var(--color-neutral-3);
+}
+</style>

+ 105 - 0
src/modules/intelligent/ManageTags.vue

@@ -0,0 +1,105 @@
+<template>
+  <a-modal
+    v-model:visible="show_model"
+    :esc-to-close="false"
+    :footer="false"
+    :maskClosable="false"
+    :align-center="false"
+    title="管理标签"
+    title-align="start"
+    top="15vh"
+    @before-cancel="handleCancel"
+  >
+    <a-space wrap>
+      <a-input
+        v-if="show_input"
+        ref="input_ref"
+        :style="{ width: '90px' }"
+        size="mini"
+        v-model.trim="input_val"
+        @keyup.enter="handleAdd"
+        @blur="handleAdd"
+      />
+      <a-tag
+        v-else
+        :style="{
+          width: '90px',
+          backgroundColor: 'var(--color-fill-2)',
+          border: '1px dashed var(--color-fill-3)',
+          cursor: 'pointer'
+        }"
+        @click="handleEdit"
+      >
+        <template #icon>
+          <icon-plus />
+        </template>
+        创建新标签
+      </a-tag>
+      <a-tag v-for="tag in tags" :key="tag.name">
+        {{ tag.name }}
+        <icon-delete @click="handleDelete(tag)" style="cursor: pointer" />
+      </a-tag>
+    </a-space>
+  </a-modal>
+</template>
+
+<script setup>
+import { ref, computed } from 'vue';
+
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    default: false
+  }
+});
+const emit = defineEmits(['update:visible']);
+
+const tags = ref([
+  {
+    name: '标签1'
+  },
+  {
+    name: '标签2'
+  },
+  {
+    name: '标签3'
+  },
+  {
+    name: '标签4'
+  }
+]);
+const input_ref = ref(null);
+const show_input = ref(false);
+const input_val = ref('');
+const show_model = computed(() => props.visible);
+const handleDelete = (tag) => {
+  tags.value = tags.value.filter((t) => t.name !== tag.name);
+};
+const handleCancel = () => {
+  i;
+  emit('update:visible', false);
+};
+
+const handleEdit = () => {
+  show_input.value = true;
+
+  nextTick(() => {
+    if (input_ref.value) {
+      input_ref.value.focus();
+    }
+  });
+};
+
+const handleAdd = () => {
+  if (input_val.value) {
+    tags.value.push({ name: input_val.value });
+    input_val.value = '';
+  }
+  show_input.value = false;
+};
+</script>
+<style scoped lang="css">
+.arco-form-item-content-wrapper {
+  border-color: var(--color-neutral-4);
+}
+</style>

+ 36 - 10
src/modules/intelligent/MessageBody.vue

@@ -8,11 +8,20 @@
           <template #unchecked> {{ $t('management.offline') }} </template>
         </a-switch>
       </template>
-      <template #labels>
-        <div class="label-btn">
-          <icon-subscribe />
-          <div>{{ $t('management.addLabelBtn') }}</div>
-        </div>
+      <template #labelBtn>
+        <!-- <div class="label-btn"> -->
+        <a-dropdown ref="dropdown" v-model="show_down" class="label-btn" position="bl" :popup-max-height="false">
+          <a-button>
+            <icon-subscribe />
+            {{ $t('management.addLabelBtn') }}</a-button
+          >
+          <template #content>
+            <div class="label-add">
+              <LabelSelected @openManageTags="openManageTags"></LabelSelected>
+            </div>
+          </template>
+        </a-dropdown>
+        <!-- </div> -->
       </template>
       <template #bottom>
         <div class="bottom">
@@ -28,12 +37,15 @@
       <div v-if="scroll_bottom && pages.current >= total_pages">{{ $t('management.noMoreData') }}</div>
     </div>
   </div>
+  <ManageTags v-model:visible="visible"></ManageTags>
 </template>
 
 <script setup>
 import { ref, reactive, onMounted, onBeforeUnmount } from 'vue';
 import CardItem from '../../views/manage/CardItem.vue';
+import ManageTags from './ManageTags.vue';
 import { getImageIntelligent } from '../../views/manage/common';
+import LabelSelected from './LabelSelected.vue';
 import { getDialogList } from '../../apis/intelligent';
 import { bus_intelligentSearch } from '../../base/event-bus';
 
@@ -49,27 +61,30 @@ const moke_data = ref([
 ]);
 const pages = reactive({
   current: 1,
-  pageSize: 25
+  pageSize: 30
 });
 // 总页码
 const total_pages = ref(5);
 const body_container = ref(null);
 const scroll_bottom = ref(false);
 const search_text = ref('');
+const dropdown = ref(null);
+const visible = ref(false);
 
 const getDialog = async () => {
   try {
-    const res = await getDialogList(pages.current, pages.pageSize);
+    const res = await getDialogList(pages.current, pages.pageSize, search_text.value);
     const data = res.data.rows.map((item) => {
       return {
         ...item,
         time: new Date(item.update_time).toLocaleString('zh-CN'),
         desc: item.description,
         img_src: getImageIntelligent(item.icon),
-        user: item.user || 'admin',
+        user: item.user.userName || '-',
         online: item.status == 1
       };
     });
+    total_pages.value = Math.ceil(res.data.total / pages.pageSize);
     moke_data.value.push(...data);
   } catch (err) {
     console.log(err);
@@ -80,16 +95,22 @@ const scrollHandler = (e) => {
     scroll_bottom.value = true;
     // 调用函数加载更多数据
     if (pages.current < total_pages.value) {
-      pages.current += 1;
       getDialog();
+      pages.current += 1;
     } else {
     }
   }
 };
+const openManageTags = () => {
+  visible.value = true;
+};
 bus_intelligentSearch.on((text) => {
   search_text.value = text;
+  moke_data.value = [];
+  pages.current = 1;
   getDialog();
 });
+
 onMounted(() => {
   getDialog();
   body_container.value.addEventListener('scroll', scrollHandler);
@@ -116,10 +137,15 @@ onBeforeUnmount(() => {
   display: flex;
   gap: 10px;
   padding: 8px 16px;
-  background-color: var(--color-neutral-3);
+  background-color: var(--color-neutral-2);
+  color: var(--color-neutral-6);
   font-size: 12px;
   cursor: pointer;
 }
+.label-add {
+  width: 240px;
+  padding: 10px;
+}
 .bottom {
   display: flex;
   justify-content: space-between;

+ 52 - 0
src/modules/intelligent/ModalItem.vue

@@ -0,0 +1,52 @@
+<template>
+  <a-modal
+    v-model:visible="showModal"
+    title="添加智能体"
+    title-align="start"
+    :align-center="false"
+    top="15vh"
+    @cancel="handleCancel"
+    @before-ok="handleBeforeOk"
+  >
+    <a-form :model="form">
+      <a-form-item field="name" label="名称" validate-trigger="input" required>
+        <a-input v-model="form.name" placeholder="请输入名称" />
+      </a-form-item>
+    </a-form>
+  </a-modal>
+</template>
+
+<script setup>
+import { ref, computed } from 'vue';
+
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    default: false
+  }
+});
+
+const emit = defineEmits(['update:visible']);
+
+const form = ref({
+  name: ''
+});
+const showModal = computed(() => props.visible);
+const handleBeforeOk = (done) => {
+  if (form.value.name.trim().length) {
+    // TODO:添加智能体
+    done();
+  } else {
+    return false;
+  }
+};
+const handleCancel = () => {
+  form.value.name = '';
+  emit('update:visible', false);
+};
+</script>
+<style scoped lang="css">
+.arco-form-item-content-wrapper {
+  border-color: var(--color-neutral-4);
+}
+</style>

+ 6 - 1
src/modules/intelligent/TopSetting.vue

@@ -18,14 +18,17 @@
       {{ $t('management.createIntelligent') }}
     </a-button>
   </a-space>
+  <ModalItem v-model:visible="visible"></ModalItem>
 </template>
 
 <script setup>
 import { ref } from 'vue';
+import ModalItem from './ModalItem.vue';
 import { funcDebounce } from '../../utils/utils';
 import { bus_intelligentSearch } from '../../base/event-bus';
 
 const searchText = ref('');
+const visible = ref(false);
 // 搜索
 const handleSearch = funcDebounce(
   () => {
@@ -35,7 +38,9 @@ const handleSearch = funcDebounce(
   false
 );
 // 创建智能体
-const handleCreate = () => {};
+const handleCreate = () => {
+  visible.value = true;
+};
 </script>
 <style scoped lang="scss"></style>
 <style>

+ 10 - 8
src/modules/knowledge/MessageBody.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="body-container" ref="body_container">
-    <CardItem v-for="item in moke_data" :key="item.name" :name="item.name" :img_src="item.img_src">
+    <CardItem v-for="item in moke_data" :key="item.id" :name="item.name" :img_src="item.img_src">
       <template #bottom>
         <div class="bottom">
           <div>{{ item.count }}{{ $t('management.document') }}</div>
@@ -42,15 +42,14 @@ const moke_data = ref([
 ]);
 const pages = reactive({
   current: 1,
-  pageSize: 25
+  pageSize: 30
 });
 // 总页码
-const total_pages = ref(5);
+const total_pages = ref(0);
 const body_container = ref(null);
 const scroll_bottom = ref(false);
 // 搜索文本
 const search_text = ref('');
-
 const handleSelect = ({ value }) => {
   if (value === 'edit') {
   } else {
@@ -59,17 +58,18 @@ const handleSelect = ({ value }) => {
 const getKnowledge = async () => {
   // TODO: 等待接口修改后,需要将searchText传到接口中
   try {
-    const res = await getKnowledgeList(pages.current, pages.pageSize);
+    const res = await getKnowledgeList(pages.current, pages.pageSize, search_text.value);
     if (res.code == 200) {
       const data = res.data.rows.map((item) => {
         return {
           ...item,
           time: new Date(item.update_time).toLocaleString('zh-CN'),
           img_src: getImageKnowLedge(item.avatar),
-          count: item.count ? item.count : 292
+          count: item.count ? item.count : 0
         };
       });
       moke_data.value.push(...data);
+      total_pages.value = Math.ceil(res.data.total / pages.pageSize);
     }
   } catch (err) {
     console.log(err, 'flag');
@@ -80,15 +80,16 @@ const scrollHandler = (e) => {
     scroll_bottom.value = true;
     // 调用函数加载更多数据
     if (pages.current < total_pages.value) {
-      pages.current += 1;
       getKnowledge();
-    } else {
+      pages.current += 1;
     }
   }
 };
 
 bus_knowledgeSearch.on((text) => {
   search_text.value = text;
+  moke_data.value = [];
+  pages.current = 1;
   getKnowledge();
 });
 
@@ -106,6 +107,7 @@ onBeforeUnmount(() => {
 .body-container {
   display: grid;
   grid-template-columns: repeat(6, 1fr);
+  align-content: flex-start;
   gap: 20px;
   overflow-y: scroll;
   height: calc(100% - 80px);

+ 52 - 0
src/modules/knowledge/ModalItem.vue

@@ -0,0 +1,52 @@
+<template>
+  <a-modal
+    v-model:visible="showModal"
+    title="添加知识库"
+    title-align="start"
+    :align-center="false"
+    top="15vh"
+    @cancel="handleCancel"
+    @before-ok="handleBeforeOk"
+  >
+    <a-form :model="form">
+      <a-form-item field="name" label="名称" validate-trigger="input" required>
+        <a-input v-model="form.name" placeholder="请输入名称" />
+      </a-form-item>
+    </a-form>
+  </a-modal>
+</template>
+
+<script setup>
+import { ref, computed } from 'vue';
+
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    default: false
+  }
+});
+
+const emit = defineEmits(['update:visible']);
+
+const form = ref({
+  name: ''
+});
+const showModal = computed(() => props.visible);
+const handleBeforeOk = (done) => {
+  if (form.value.name.trim().length) {
+    // TODO:添加知识库
+    done();
+  } else {
+    return false;
+  }
+};
+const handleCancel = () => {
+  form.value.name = '';
+  emit('update:visible', false);
+};
+</script>
+<style scoped lang="css">
+.arco-form-item-content-wrapper {
+  border-color: var(--color-neutral-4);
+}
+</style>

+ 7 - 1
src/modules/knowledge/TopSetting.vue

@@ -18,14 +18,18 @@
       {{ $t('management.createKnowledgeBase') }}
     </a-button>
   </a-space>
+  <modalItem v-model:visible="visible" />
 </template>
 
 <script setup>
 import { ref } from 'vue';
+import modalItem from './modalItem.vue';
 import { funcDebounce } from '../../utils/utils';
 import { bus_knowledgeSearch } from '../../base/event-bus';
 
 const searchText = ref('');
+// 模态框控制
+const visible = ref(false);
 // 搜索
 const handleSearch = funcDebounce(
   () => {
@@ -35,7 +39,9 @@ const handleSearch = funcDebounce(
   false
 );
 // 创建知识库
-const handleCreate = () => {};
+const handleCreate = () => {
+  visible.value = true;
+};
 </script>
 <style scoped lang="scss"></style>
 <style>

+ 1 - 2
src/modules/login/FormItem.vue

@@ -150,8 +150,7 @@ const changeFormType = (e) => {
 .reg-link {
   color: rgb(var(--arcoblue-6));
 }
-</style>
-<style>
+
 .arco-form-item-label-col {
   padding: 0px;
 }

+ 131 - 21
src/modules/permission/account/DeptConfigModal.vue

@@ -1,36 +1,146 @@
 <template>
-  <a-modal width="50%" v-model:visible="deptvisible" title="部门配置" @cancel="handleCancel(2)" @ok="editDeptHandleOk">
+  <a-modal width="50%" v-model:visible="visible" title="部门配置" @cancel="handleCancel" @ok="editDeptHandleOk">
     <div :style="{ display: 'flex' }">
-      <a-card :style="{ 'width': '460px', 'height': '500px', 'overflow-y': 'auto' }" title="机构" hoverable>
-        <a-tree class="tree-demo" v-model:checked-keys="checkedKeys" v-model:expanded-keys="expandKdys"
-          :checkable="true" :data="treeData" :show-line="showLine" @check="onCheck" :fieldNames="{
+      <a-card class="card-body" title="机构" hoverable>
+        <a-tree :data="dept_list" v-model:checked-keys="checked_keys" v-model:expanded-keys="expand_kdys"
+          :checkable="true" :show-line="true" @check="onCheck" :fieldNames="{
             key: 'deptId',
             title: 'deptName',
             children: 'children',
           }">
         </a-tree>
       </a-card>
-      <div :style="{ 'display': 'flex', 'flex-direction': 'column' }">
-        <a-card class="card-demo" title="用户所属部门" :style="{ 'width': '460px', 'height': '500px', 'overflow-y': 'auto' }">
-          <a-space wrap>
-            <a-tag v-for="(tag, index) of checkStrictly" :key="tag.deptId">
-              {{ tag.deptName }}
-            </a-tag>
-          </a-space>
-          <a-divider />
-          <a-space wrap>
-            部门角色:
-            <a-tag v-for="(tag, index) of checkStrictlyDeptRole" :key="tag.roleId">
-              {{ tag.roleName }}
-            </a-tag>
-          </a-space>
-        </a-card>
-      </div>
+      <a-card class="card-body" title="用户所属部门">
+        <a-space wrap>
+          <a-tag v-for="(tag, index) of check_strictly" :key="tag.deptId">
+            {{ tag.deptName }}
+          </a-tag>
+        </a-space>
+        <a-divider />
+        <div style="margin-bottom: 8px;">部门角色:</div>
+        <a-space wrap>
+          <a-tag v-for="(tag, index) of check_strictly_dept_role" :key="tag.roleId" color="arcoblue">
+            {{ tag.roleName }}
+          </a-tag>
+        </a-space>
+      </a-card>
     </div>
   </a-modal>
 </template>
 
 <script setup>
+import { ref, toRefs, onMounted } from 'vue';
+
+// 定义组件的 props 和 emits
+const props = defineProps({
+  visible: Boolean,
+  dept_list: {
+    type: Array,
+    default: () => []
+  },
+  record: {
+    type: Object,
+    default: false
+  }
+});
+
+const { visible, dept_list, record } = toRefs(props);
+
+const emit = defineEmits(['close', 'ok']);
+
+// 组件内部状态
+const checked_keys = ref([]);
+// 默认展开第一节
+const expand_kdys = ref(['0']);
+const check_strictly = ref([]);
+const check_strictly_dept_role = ref([]);
+
+// 递归取消所有子节点的选择
+const cancelChildSelection = (node, selected_nodes) => {
+  if (node.children && node.children.length > 0) {
+    node.children.forEach(child => {
+      const child_index = selected_nodes.findIndex(val => val.deptId === child.deptId);
+      if (child_index !== -1) {
+        // 取消子节点的选择
+        selected_nodes.splice(child_index, 1);
+        // 递归取消子节点的选择
+        cancelChildSelection(child, selected_nodes);
+      }
+    });
+  }
+}
+
+// 处理树形组件的勾选变化
+const onCheck = (new_checked_keys, event) => {
+  const o = { deptId: event.node.deptId, deptName: event.node.deptName };
+  if (event.checked) {
+    // 添加部门角色
+    event.node.roles.forEach((val) => {
+      check_strictly_dept_role.value.push(val);
+    });
+    // 添加部门
+    check_strictly.value.push(o);
+  } else {
+    // 移除部门
+    const dept_index = check_strictly.value.findIndex((val) => val.deptId === event.node.deptId);
+    check_strictly.value.splice(dept_index, 1);
+    // 移除部门角色
+    const role_index = check_strictly_dept_role.value.findIndex((val) => val.deptId === event.node.deptId);
+    check_strictly_dept_role.value.splice(role_index, 1);
+    // 递归取消所有子节点的选择
+    cancelChildSelection(event.node, check_strictly.value);
+  }
+};
+
+// 处理取消事件
+const handleCancel = () => {
+  visible.value = false;
+  emit('close');
+};
+
+// 处理确认事件
+const editDeptHandleOk = () => {
+  emit('ok', { checked_keys: checked_keys.value });
+};
+
+onMounted(() => {
+  // 处理传入的部门数据
+  if (record?.dept) {
+    record.dept.forEach((val) => {
+      check_strictly.value.push({
+        deptId: val.deptId,
+        deptName: val.deptName
+      });
+      checked_keys.value.push(val.deptId);
+      expand_kdys.value.push(val.deptId);
+      record.roles.forEach(
+        (r) => {
+          if (r.dept) {
+            r.dept.forEach((d) => {
+              if (d.deptId == val.deptId) {
+                check_strictly_dept_role.value.push({
+                  roleId: r.roleId,
+                  roleName: r.roleName,
+                  deptId: d.deptId,
+                });
+              }
+            });
+          }
+        }
+      );
+    });
+  }
+});
 </script>
 
-<style lang="css" scoped></style>
+<style lang="css" scoped>
+.card-body {
+  width: 460px;
+  height: 500px;
+  overflow-y: auto;
+}
+
+.card-body:first-child {
+  margin-right: 16px;
+}
+</style>

+ 4 - 4
src/modules/permission/account/EditModal.vue

@@ -1,5 +1,5 @@
 <template>
-  <a-modal v-model:visible="visible" :title="title" @ok="editHandleOk" @cancel="editHandleCancel" width="48%">
+  <a-modal v-model:visible="visible" :title="title" @ok="ok" @cancel="editHandleCancel" width="48%">
     <a-form ref="formRef" :model="edit_form" auto-label-width>
       <a-row :gutter="20">
         <a-col :span="10">
@@ -62,15 +62,15 @@ const props = defineProps({
 });
 
 // 事件发射器
-const emit = defineEmits(['save', 'cancel']);
+const emit = defineEmits(['ok', 'cancel']);
 
 const { edit_form, visible, title, roles, field_names } = toRefs(props);
 
 // 处理确认事件
-const editHandleOk = () => {
+const ok = () => {
   // 进行表单验证和提交逻辑
   // 根据需要增加表单验证逻辑
-  emit('save', edit_form);
+  emit('ok', edit_form);
 }
 
 const editHandleCancel = () => {

+ 26 - 18
src/modules/permission/account/ViewPermission.vue

@@ -1,28 +1,28 @@
 <template>
-  <a-modal width="50%" v-model:visible="visible" title="用户所有权限" okText="关闭" hide-cancel="true" @ok="handleOk">
+  <a-modal width="50%" v-model:visible="visible" title="用户权限" okText="关闭" hide-cancel="true" @ok="handleOk">
     <div :style="{ 'display': 'flex', 'flex-direction': 'column' }">
-      <a-card class="card-demo" title="用户所有权限" hoverable>
+      <a-card class="card-body" hoverable>
+        <div class="title-box"><icon-menu size="18" />菜单功能:</div>
         <a-space wrap>
-          菜单功能:
-          <a-tag v-for="(tag, index) in menu_permissions_list" :key="tag.menuId">
-            {{ tag.menuName }}
+          <a-tag v-for="item in menu_permissions_list" :key="item.id">
+            {{ item.name }}
           </a-tag>
         </a-space>
         <a-divider />
+        <div class="title-box"><icon-bookmark size="18" />知识库:</div>
         <a-space wrap>
-          知识库:
-          <a-tag v-for="(tag, index) in knowledge_permissions_list" :key="tag.knowledgeId">
-            {{ tag.knowledgeName }}
+          <a-tag v-for="item in knowledge_permissions_list" color="arcoblue" :key="item.id">
+            {{ item.name }}
           </a-tag>
         </a-space>
         <a-divider />
+        <div class="title-box"><icon-relation size="18" /> 智能体:</div>
         <a-space wrap>
-          智能体:
-          <a-tag v-for="(tag, index) in dialog_permissions_list" :key="tag.dialogId">
-            {{ tag.dialogName }}
+          <a-tag v-for="item in dialog_permissions_list" color="green" :key="item.id">
+            {{ item.name }}
           </a-tag>
-          <a-tag v-for="(tag, index) in agent_permissions_list" :key="tag.agentId">
-            {{ tag.agentName }}
+          <a-tag v-for="item in agent_permissions_list" color="green" :key="item.id">
+            {{ item.name }}
           </a-tag>
         </a-space>
       </a-card>
@@ -34,10 +34,7 @@
 import { toRefs } from 'vue';
 // 定义Props,接收外部传入的数据
 const props = defineProps({
-  visible: {
-    type: Boolean,
-    required: true
-  },
+  visible: Boolean,
   menu_permissions_list: {
     type: Array,
   },
@@ -67,7 +64,7 @@ const handleOk = () => {
 </script>
 
 <style lang="css" scoped>
-.card-demo {
+.card-body {
   width: 100%;
   margin-left: 24px;
   transition-property: all;
@@ -75,4 +72,15 @@ const handleOk = () => {
   overflow-y: auto;
   margin: 1px;
 }
+
+.title-box {
+  display: flex;
+  align-items: center;
+  font-size: 16px;
+  margin-bottom: 12px;
+}
+
+.title-box>svg {
+  margin-right: 6px;
+}
 </style>

+ 183 - 22
src/modules/permission/account/index.vue

@@ -11,9 +11,9 @@
         <a-popconfirm content="确定重置该账号密码吗?" @ok="handleResetPassword(record)">
           <a-button type="text">重制密码</a-button>
         </a-popconfirm>
-        <a-button type="text" @click="handleEdit(record)">编辑</a-button>
-        <a-button type="text" @click="handleView(record)">查看权限</a-button>
-        <a-button type="text" @click="handleDeptConfig(record)">部门配置</a-button>
+        <a-button type="text" @click="handleEdit(record, true)">编辑</a-button>
+        <a-button type="text" @click="handleView(record, true)">查看权限</a-button>
+        <a-button type="text" @click="handleDeptConfig(record, true)">部门配置</a-button>
         <a-popconfirm content="确定删除该条数据吗?" type="warning" @ok="handleDelete(record)">
           <a-button type="text" status="warning">删除</a-button>
         </a-popconfirm>
@@ -21,13 +21,17 @@
     </DataTable>
   </a-card>
 
-  <EditModal :visible.sync="edit_visible" @save="editModalSave" @cancel="edit_visible = false" :title="'编辑'"
-    :edit_form="edit_form" />
+  <EditModal :visible.sync="edit_visible" :edit_form="edit_form" @ok="editModalSave" @cancel="handleEdit(null, false)"
+    :title="'编辑'" />
 
-  <ViewPermission :visible.sync="view_permission_visible" @cancel="view_permission_visible = false"
-    :view_form="view_form" :menu_permissions_list="menu_permissions_list"
+  <ViewPermission :visible.sync="view_permission_visible" @cancel="handleView(null, false)"
+    @close="handleView(null, false)" :view_form="view_form" :menu_permissions_list="menu_permissions_list"
     :knowledge_permissions_list="knowledge_permissions_list" :dialog_permissions_list="dialog_permissions_list"
     :agent_permissions_list="agent_permissions_list" />
+
+  <DeptConfigModal :visible.sync="dept_config_visible" :dept_list="dept_list" :record="current_record" @ok=""
+    @cancel="handleDeptConfig(null, false)" />
+
 </template>
 
 <script setup>
@@ -37,7 +41,9 @@ import DataTable from '../../../views/permission/DataTable.vue';
 import Action from '../../../views/permission/Action.vue';
 import EditModal from './EditModal.vue';
 import ViewPermission from './ViewPermission.vue';
+import DeptConfigModal from './DeptConfigModal.vue';
 
+const current_record = ref(null);
 // 表格相关
 const table_data = ref([
   { id: 1, name: '李雷', age: 25, status: true },
@@ -69,12 +75,159 @@ const edit_form = reactive({
   role: []
 });
 
+
+
 // 查看权限相关
 const view_permission_visible = ref(false);
-const menu_permissions_list = ref([])
-const knowledge_permissions_list = ref([])
-const dialog_permissions_list = ref([])
-const agent_permissions_list = ref([])
+const menu_permissions_list = ref([
+  { name: '智能体设置', id: 1 },
+  { name: 'root', id: 2 },
+  { name: '智能体列表', id: 3 },
+  { name: '会话记录', id: 4 },
+])
+const knowledge_permissions_list = ref([
+  { name: '知识库管理', id: 1 },
+  { name: '知识库列表', id: 2 },
+  { name: '知识库分类', id: 3 },
+])
+const dialog_permissions_list = ref([
+  { name: '对话管理', id: 1 },
+  { name: '对话列表', id: 2 },
+  { name: '对话分类', id: 3 },
+])
+const agent_permissions_list = ref([
+  { name: '智能体管理', id: 1 },
+  { name: '智能体列表', id: 2 },
+  { name: '智能体分类', id: 3 },
+])
+
+// 部门配置相关
+const dept_config_visible = ref(false);
+const dept_list = ref([
+  {
+    "address": null,
+    "children": [
+      {
+        "address": "",
+        "children": [],
+        "code": null,
+        "createTime": "Thu, 12 Sep 2024 14:56:43 GMT",
+        "deptId": "2af7cb5e-1178-4adc-8abb-9780f7543081",
+        "deptName": "成都",
+        "email": "",
+        "iconCls": null,
+        "leader": "成都",
+        "orderNum": 0,
+        "parentId": "0",
+        "parentName": "总部",
+        "phone": "成都",
+        "roles": [],
+        "status": "",
+        "updateTime": "Thu, 12 Sep 2024 14:56:43 GMT"
+      },
+      {
+        "address": null,
+        "children": [],
+        "code": null,
+        "createTime": "Sun, 22 May 2022 09:59:33 GMT",
+        "deptId": "5477d9a9-e41e-485f-bb08-697e8facef88",
+        "deptName": "南京分公司",
+        "email": "ss@ada.com",
+        "iconCls": null,
+        "leader": "dd",
+        "orderNum": 0,
+        "parentId": "0",
+        "parentName": "总部",
+        "phone": "18905189016",
+        "roles": [],
+        "status": "0",
+        "updateTime": "Tue, 12 Nov 2024 03:05:10 GMT"
+      },
+      {
+        "address": null,
+        "children": [
+          {
+            "address": "",
+            "children": [
+              {
+                "address": "",
+                "children": [],
+                "code": null,
+                "createTime": "Tue, 12 Nov 2024 03:09:57 GMT",
+                "deptId": "c6ae93de-2436-4090-b567-ec4835c74112",
+                "deptName": "成都",
+                "email": "",
+                "iconCls": null,
+                "leader": "柔柔弱弱",
+                "orderNum": 0,
+                "parentId": "f86724f0-94d9-445c-9d95-3ec2540da0f8",
+                "parentName": "太平洋派出所",
+                "phone": "1234242342",
+                "roles": [],
+                "status": "",
+                "updateTime": "Tue, 12 Nov 2024 06:58:15 GMT"
+              }
+            ],
+            "code": null,
+            "createTime": "Thu, 05 Sep 2024 03:15:13 GMT",
+            "deptId": "f86724f0-94d9-445c-9d95-3ec2540da0f8",
+            "deptName": "太平洋派出所",
+            "email": "",
+            "iconCls": null,
+            "leader": "12",
+            "orderNum": 0,
+            "parentId": "ce627e90-57d6-4ed4-a789-1f3dd467ae7d",
+            "parentName": "上海分公司",
+            "phone": "12",
+            "roles": [
+              {
+                "deptId": "f86724f0-94d9-445c-9d95-3ec2540da0f8",
+                "roleId": "19f00d46-8f1b-45b5-b7b7-6197d7b8cb33",
+                "roleName": "管理员1"
+              }
+            ],
+            "status": "0",
+            "updateTime": "Tue, 12 Nov 2024 06:58:07 GMT"
+          }
+        ],
+        "code": null,
+        "createTime": "Tue, 24 May 2022 23:54:10 GMT",
+        "deptId": "ce627e90-57d6-4ed4-a789-1f3dd467ae7d",
+        "deptName": "上海分公司",
+        "email": null,
+        "iconCls": null,
+        "leader": "jack",
+        "orderNum": 0,
+        "parentId": "0",
+        "parentName": "总部",
+        "phone": null,
+        "roles": [
+          {
+            "deptId": "ce627e90-57d6-4ed4-a789-1f3dd467ae7d",
+            "roleId": "301ca518-2ed0-4092-a2ee-e46c24a42c8a",
+            "roleName": "普通"
+          }
+        ],
+        "status": "0",
+        "updateTime": "Wed, 18 Sep 2024 09:58:16 GMT"
+      }
+    ],
+    "code": null,
+    "createTime": "Mon, 28 Nov 2016 10:34:54 GMT",
+    "deptId": "0",
+    "deptName": "总部",
+    "email": null,
+    "iconCls": "ext-icon-bricks",
+    "leader": null,
+    "orderNum": 100,
+    "parentId": "",
+    "parentName": "",
+    "phone": null,
+    "roles": [],
+    "status": "0",
+    "updateTime": "Mon, 28 Nov 2016 10:35:12 GMT"
+  }
+]);
 
 const searchClick = (value) => {
   console.log('value', value)
@@ -96,20 +249,28 @@ const handleResetPassword = (record) => {
   });
 }
 
-const handleEdit = (record) => {
-  edit_visible.value = true;
-  console.log('record', record)
-  edit_form.loginName = record.loginName;
-  edit_form.userName = record.userName;
-  edit_form.phoneNumber = record.phoneNumber;
-  edit_form.email = record.email;
-  edit_form.password = record.password;
-  edit_form.role = record.role;
+const handleEdit = (record, visible) => {
+  if (record) {
+    console.log('record', record)
+    edit_form.loginName = record.loginName;
+    edit_form.userName = record.userName;
+    edit_form.phoneNumber = record.phoneNumber;
+    edit_form.email = record.email;
+    edit_form.password = record.password;
+    edit_form.role = record.role;
+  }
+
+  edit_visible.value = visible;
 }
 
-const handleView = (record) => {
+const handleView = (record, visible) => {
   console.log('record', record)
-  view_permission_visible.value = true;
+  view_permission_visible.value = visible;
+}
+
+const handleDeptConfig = (record, visible) => {
+  current_record.value = record;
+  dept_config_visible.value = visible;
 }
 const editModalSave = () => {
   edit_visible.value = false;

+ 69 - 0
src/modules/permission/group/MembertConfigModal.vue

@@ -0,0 +1,69 @@
+<template>
+  <a-modal width="50%" v-model:visible="visible" title="权限配置" @cancel="handleCancel" @ok="ok">
+    <div class="membert-config-modal">
+      <a-transfer show-search :data="memberList" :default-value="selectedMemberList" :source-input-search-props="{
+        placeholder: '请输入用户名搜索',
+      }" :target-input-search-props="{
+        placeholder: '请输入用户名搜索',
+      }">
+        <template #source-title>可选列表</template>
+        <template #target-title>已选列表</template>
+      </a-transfer>
+    </div>
+
+  </a-modal>
+</template>
+
+<script setup>
+import { ref, toRefs } from 'vue';
+
+// 定义组件的 props 和 emits
+const props = defineProps({
+  visible: Boolean,
+});
+const { visible } = toRefs(props);
+
+const emit = defineEmits(['cancel', 'ok']);
+
+const memberList = ref([
+  {
+    label: '张三',
+    value: 'zhangsan',
+  },
+  {
+    label: ' 李四',
+    value: 'lisi',
+  },
+  {
+    label: '张三',
+    value: 'zhangsan',
+  },
+]);
+
+const selectedMemberList = ref(['zhangsan']);
+
+const handleCancel = () => {
+  emit('close');
+};
+
+const ok = () => {
+  emit('ok', selectedMemberList.value);
+};
+</script>
+
+<style scoped>
+.membert-config-modal {
+  width: 100%;
+  display: flex;
+  justify-content: center;
+}
+
+:deep(.arco-transfer) {
+  width: 100%;
+}
+
+:deep(.arco-transfer-view) {
+  width: 45%;
+  height: 500px;
+}
+</style>

+ 0 - 0
src/modules/permission/org/index.vue


+ 116 - 0
src/modules/permission/role/DeptConfigModal.vue

@@ -0,0 +1,116 @@
+<template>
+  <a-modal width="50%" v-model:visible="visible" title="部门配置" @cancel="handleCancel" @ok="editDeptHandleOk">
+    <div :style="{ display: 'flex' }">
+      <a-card class="card-body" title="机构" hoverable>
+        <a-tree :data="dept_list" v-model:checked-keys="checked_keys" v-model:expanded-keys="expand_kdys"
+          :checkable="true" :show-line="true" @check="onCheck" :fieldNames="{
+            key: 'deptId',
+            title: 'deptName',
+            children: 'children',
+          }">
+        </a-tree>
+      </a-card>
+      <a-card class="card-body" title="角色所属部门">
+        <a-space wrap>
+          <a-tag v-for="(tag, index) of check_strictly" :key="tag.deptId">
+            {{ tag.deptName }}
+          </a-tag>
+        </a-space>
+      </a-card>
+    </div>
+  </a-modal>
+</template>
+
+<script setup>
+import { ref, toRefs, onMounted } from 'vue';
+
+// 定义组件的 props 和 emits
+const props = defineProps({
+  visible: Boolean,
+  dept_list: {
+    type: Array,
+    default: () => []
+  },
+  record: {
+    type: Object,
+    default: false
+  }
+});
+
+const { visible, dept_list, record } = toRefs(props);
+
+const emit = defineEmits(['close', 'ok']);
+
+// 组件内部状态
+const checked_keys = ref([]);
+// 默认展开第一节
+const expand_kdys = ref(['0']);
+const check_strictly = ref([]);
+
+// 递归取消所有子节点的选择
+const cancelChildSelection = (node, selected_nodes) => {
+  if (node.children && node.children.length > 0) {
+    node.children.forEach(child => {
+      const child_index = selected_nodes.findIndex(val => val.deptId === child.deptId);
+      if (child_index !== -1) {
+        // 取消子节点的选择
+        selected_nodes.splice(child_index, 1);
+        // 递归取消子节点的选择
+        cancelChildSelection(child, selected_nodes);
+      }
+    });
+  }
+}
+
+// 处理树形组件的勾选变化
+const onCheck = (new_checked_keys, event) => {
+  const o = { deptId: event.node.deptId, deptName: event.node.deptName };
+  if (event.checked) {
+    // 添加部门
+    check_strictly.value.push(o);
+  } else {
+    // 移除部门
+    const dept_index = check_strictly.value.findIndex((val) => val.deptId === event.node.deptId);
+    check_strictly.value.splice(dept_index, 1);
+    // 递归取消所有子节点的选择
+    cancelChildSelection(event.node, check_strictly.value);
+  }
+};
+
+// 处理取消事件
+const handleCancel = () => {
+  visible.value = false;
+  emit('close');
+};
+
+// 处理确认事件
+const editDeptHandleOk = () => {
+  emit('ok', { checked_keys: checked_keys.value });
+};
+
+onMounted(() => {
+  // 处理传入的部门数据
+  if (record?.dept) {
+    record.dept.forEach((val) => {
+      check_strictly.value.push({
+        deptId: val.deptId,
+        deptName: val.deptName
+      });
+      checked_keys.value.push(val.deptId);
+      expand_kdys.value.push(val.deptId);
+    });
+  }
+});
+</script>
+
+<style lang="css" scoped>
+.card-body {
+  width: 460px;
+  height: 500px;
+  overflow-y: auto;
+}
+
+.card-body:first-child {
+  margin-right: 16px;
+}
+</style>

+ 140 - 0
src/modules/permission/role/PermissionConfigModal.vue

@@ -0,0 +1,140 @@
+<template>
+  <a-modal width="50%" v-model:visible="visible" title="权限配置" @cancel="handleCancel" @ok="editDeptHandleOk">
+    <a-tabs :active-key="selected_tab_key" @change="handleTabChange">
+      <a-tab-pane v-for="tab in tabs" :key="tab.key">
+        <template #title>
+          <component :is="tab.icon" /> {{ tab.title }}
+        </template>
+      </a-tab-pane>
+    </a-tabs>
+    <div style="display: flex;">
+      <a-card class="card-body" :title="getCurrentTabTitle" hoverable>
+        <a-tree :data="dept_list" v-model:checked-keys="checked_keys" v-model:expanded-keys="expand_kdys"
+          :checkable="true" :show-line="true" @check="onCheck" :fieldNames="{
+            key: 'deptId',
+            title: 'deptName',
+            children: 'children',
+          }">
+        </a-tree>
+      </a-card>
+      <a-card class="card-body" title="角色所有权限">
+        <div style="margin-bottom: 8px;">菜单功能:</div>
+        <a-space wrap>
+          <a-tag v-for="(tag, index) of check_strictly" :key="tag.deptId">
+            {{ tag.deptName }}
+          </a-tag>
+        </a-space>
+      </a-card>
+    </div>
+  </a-modal>
+</template>
+
+<script setup>
+import { ref, toRefs, onMounted, computed } from 'vue';
+
+// 定义组件的 props 和 emits
+const props = defineProps({
+  visible: Boolean,
+  dept_list: {
+    type: Array,
+    default: () => []
+  },
+  record: Object
+});
+
+const { visible, dept_list, record } = toRefs(props);
+
+const emit = defineEmits(['getActiveTab', 'close', 'ok']);
+
+const tabs = [
+  { key: "1", icon: "icon-menu", title: "菜单" },
+  { key: "2", icon: "icon-storage", title: "知识库" },
+  { key: "3", icon: "icon-robot", title: "智能体" },
+  { key: "4", icon: "icon-relation", title: "模型" }
+]
+const selected_tab_key = ref(tabs[0].key);
+
+// 组件内部状态
+const checked_keys = ref([]);
+// 默认展开第一节
+const expand_kdys = ref(['0']);
+const check_strictly = ref([]);
+
+// 递归取消所有子节点的选择
+const cancelChildSelection = (node, selectedNodes) => {
+  if (node.children && node.children.length > 0) {
+    node.children.forEach(child => {
+      const childIndex = selectedNodes.findIndex(val => val.deptId === child.deptId);
+      if (childIndex !== -1) {
+        // 取消子节点的选择
+        selectedNodes.splice(childIndex, 1);
+        // 递归取消子节点的选择
+        cancelChildSelection(child, selectedNodes);
+      }
+    });
+  }
+}
+
+// 处理树形组件的勾选变化
+const onCheck = (newCheckedKeys, event) => {
+  const o = { deptId: event.node.deptId, deptName: event.node.deptName };
+  if (event.checked) {
+    // 添加部门
+    check_strictly.value.push(o);
+  } else {
+    // 移除部门
+    const deptIndex = check_strictly.value.findIndex((val) => val.deptId === event.node.deptId);
+    check_strictly.value.splice(deptIndex, 1);
+    // 递归取消所有子节点的选择
+    cancelChildSelection(event.node, check_strictly.value);
+  }
+};
+
+const handleTabChange = (key) => {
+  const currentTab = tabs.find(tab => tab.key === key);
+  emit('getActiveTab', currentTab);
+  selected_tab_key.value = key;
+};
+// 处理取消事件
+const handleCancel = () => {
+  visible.value = false;
+  emit('close');
+};
+
+// 处理确认事件
+const editDeptHandleOk = () => {
+  emit('ok', { checkedKeys: checked_keys.value });
+};
+
+const getCurrentTabTitle = computed(() => {
+  const currentTab = tabs.find(tab => tab.key === selected_tab_key.value);
+  return currentTab ? currentTab.title : '菜单';
+});
+
+onMounted(() => {
+  // 处理传入的部门数据
+  if (record?.dept) {
+    record.dept.forEach((val) => {
+      check_strictly.value.push({
+        deptId: val.deptId,
+        deptName: val.deptName
+      });
+      checked_keys.value.push(val.deptId);
+      expand_kdys.value.push(val.deptId);
+    });
+  }
+
+});
+</script>
+
+<style lang="css" scoped>
+.card-body {
+  width: 460px;
+  height: 500px;
+  overflow-y: auto;
+}
+
+.card-body:first-child {
+  margin-right: 16px;
+}
+</style>

+ 0 - 0
src/modules/permission/role/index.vue


+ 6 - 6
src/views/common/header-btns/BtnCurrentUser.vue

@@ -8,20 +8,20 @@
       <a-doption value="logout"><icon-export />{{ $t('login.logout') }}</a-doption>
     </template>
   </a-dropdown>
+  <ModalLogout v-model:visible="visible"></ModalLogout>
 </template>
 
 <script setup>
-import { useRouter } from 'vue-router';
+import { ref } from 'vue';
+import ModalLogout from './ModalLogout.vue';
 import { useCurrentUserStore } from '../../../base/store/use-current-user';
 
-const router = useRouter();
-
 const currentUser = useCurrentUserStore();
 
-const handleSelect = (value) => {
-  console.log(value, '退出登录');
+const visible = ref(false);
 
-  router.push({ path: '/login', replace: true });
+const handleSelect = (value) => {
+  visible.value = true;
 };
 </script>
 

+ 44 - 0
src/views/common/header-btns/ModalLogout.vue

@@ -0,0 +1,44 @@
+<template>
+  <a-modal
+    :visible="showModal"
+    @ok="handleOk"
+    @cancel="handleCancel"
+    :okText="$t('login.okBtn')"
+    :cancelText="$t('login.cancelBtn')"
+    unmountOnClose
+  >
+    <template #title><icon-exclamation-circle-fill class="tips-icon" /> {{ $t('login.tip') }} </template>
+    <div>{{ $t('login.okText') }}</div>
+  </a-modal>
+</template>
+
+<script setup>
+import { computed } from 'vue';
+import { useRouter } from 'vue-router';
+import { kuky_Authorization } from '../../../base/storage';
+
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    default: false
+  }
+});
+const emit = defineEmits(['update:visible']);
+const router = useRouter();
+const showModal = computed(() => props.visible);
+
+const handleOk = () => {
+  kuky_Authorization.remove();
+  router.push({ path: '/login', replace: true });
+  emit('update:visible', false);
+};
+const handleCancel = () => {
+  emit('update:visible', false);
+};
+</script>
+<style scoped lang="css">
+.tips-icon {
+  font-size: 20px;
+  color: rgb(var(--orange-6));
+}
+</style>

+ 3 - 1
src/views/manage/CardItem.vue

@@ -10,7 +10,9 @@
     <div class="card-des">
       <slot name="description"></slot>
     </div>
-
+    <div class="card-label">
+      <slot name="labelBtn"></slot>
+    </div>
     <div class="card-bottom">
       <slot name="bottom"></slot>
     </div>