feat(layout): 登录登出及菜单权限处理

- 重构了侧边栏菜单的渲染逻辑,支持多级菜单
-优化了菜单项的样式和布局,提高了可读性和美观性- 增加了菜单项的点击事件处理,实现了菜单的动态展开和收缩
-调整了菜单项的路径处理方式,支持动态生成菜单路径- 优化了菜单项的激活状态管理,提高了用户体验
This commit is contained in:
du.meimei
2025-04-24 15:32:31 +08:00
parent f6154f7147
commit b0c0446f52
19 changed files with 533 additions and 204 deletions

View File

@@ -4,4 +4,4 @@
"singleQuote": true, "singleQuote": true,
"printWidth": 80, "printWidth": 80,
"endOfLine": "auto" "endOfLine": "auto"
} }

View File

@@ -4,7 +4,7 @@ import getUrl from '@/assets/js/utils/get-url'
// 登录 // 登录
export function login(data) { export function login(data) {
return request({ return request({
url: getUrl('/user/login', 0), url: getUrl('/sysUserEx/baseLogin'),
method: 'post', method: 'post',
data data
}) })
@@ -13,17 +13,24 @@ export function login(data) {
// 获取个人信息 // 获取个人信息
export function getInfo(token) { export function getInfo(token) {
return request({ return request({
url: getUrl('/user/info', 0), url: getUrl('/sysUserEx/getUserInfo'),
method: 'get', method: 'get'
params: { token } // params: { token }
})
}
// 获取菜单
export function getRouters(token) {
return request({
url: getUrl('/sysUserEx/getLoginUserMenu'),
method: 'get'
// params: { token }
}) })
} }
// 登出 // 登出
export function logout() { export function logout() {
return request({ return request({
url: getUrl('/user/logout', 0), url: getUrl('/sysUserEx/logout'),
method: 'post' method: 'get'
}) })
} }
// 测试示例 // 测试示例

View File

@@ -4,28 +4,55 @@ import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style import 'nprogress/nprogress.css' // progress bar style
import { getToken } from '@/assets/js/utils/auth' // get token from cookie import { getToken } from '@/assets/js/utils/auth' // get token from cookie
import getPageTitle from '@/assets/js/utils/get-page-title' import getPageTitle from '@/assets/js/utils/get-page-title'
import { Message } from 'element-ui'
NProgress.configure({ showSpinner: false }) // NProgress Configuration NProgress.configure({ showSpinner: false }) // NProgress Configuration
const whiteList = ['/authentication', '/home', '/404'] // no redirect whitelist const whiteList = ['/login', '/authentication', '/404'] // no redirect whitelist
router.beforeEach(async (to, from, next) => {
sessionStorage.setItem('token', 'MockToken') console.log('token:' + getToken())
document.title = getPageTitle(to.meta.title)
const hasToken = getToken() router.beforeEach((to, from, next) => {
if (hasToken) { NProgress.start()
next() if (getToken()) {
} else { console.log(getToken())
if (to.path.indexOf('/home') !== -1) { /* has token*/
next(`/authentication?redirect=${to.path}`) if (to.path === '/login') {
next({ path: '/login' })
NProgress.done()
} else { } else {
if (whiteList.indexOf(to.path) !== -1) { // 判断当前用户是否已拉取完user_info信息
next() store
} else { .dispatch('user/getInfo')
next() .then(() => {
NProgress.done() console.log(to)
} store.dispatch('app/GenerateRoutes').then(accessRoutes => {
// // 根据roles权限生成可访问的路由表
// router.addRoutes(accessRoutes) // 动态添加可访问路由表
// // getArea();//初始化省市区划配置信息
// next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
next()
})
})
.catch(err => {
store.dispatch('user/logout').then(() => {
Message.error(err)
next({ path: '/ogin' })
})
})
}
} else {
console.log('没有token')
console.log(to.path)
console.log(to.fullPath)
// 没有token
if (whiteList.indexOf(to.path) !== -1) {
// 在免登录白名单,直接进入
next()
} else {
next(`/login`) // 否则全部重定向到登录页
NProgress.done()
} }
} }
}) })
router.afterEach(() => { router.afterEach(() => {
// finish progress bar // finish progress bar
NProgress.done() NProgress.done()

View File

@@ -29,7 +29,7 @@ service.interceptors.request.use(
if (store.getters.token) { if (store.getters.token) {
// config.headers['sid'] = getToken() // config.headers['sid'] = getToken()
config.headers['sysType'] = '3' config.headers['sysType'] = '3'
config.headers['auth'] = getToken() config.headers['sid'] = getToken()
} }
if (loading) { if (loading) {
endLoading() endLoading()

View File

@@ -342,7 +342,7 @@ h3 {
#app .main-container { #app .main-container {
min-height: 100%; min-height: 100%;
transition: margin-left 0.28s; transition: margin-left 0.28s;
margin-left: 210px; /*margin-left: 210px;*/
position: relative; position: relative;
} }

View File

@@ -8,7 +8,7 @@
.sidebar-container { .sidebar-container {
transition: width 0.28s; transition: width 0.28s;
width: $sideBarWidth !important; //width: $sideBarWidth !important;
background-color: #f0f4fa; background-color: #f0f4fa;
border: none; border: none;
padding: 10px; padding: 10px;

View File

@@ -1,13 +1,23 @@
<template> <template>
<el-dialog :visible.sync="visible" :title="title" :append-to-body="appendToBody" :destroy-on-close="destroyOnClose" <el-dialog
:width="width" :before-close="handleBeforeClose"> :visible.sync="visible"
:title="title"
:append-to-body="appendToBody"
:destroy-on-close="destroyOnClose"
:width="width"
:before-close="handleBeforeClose"
>
<div class="render-dialog-body"> <div class="render-dialog-body">
<slot name="default"></slot> <slot name="default"></slot>
</div> </div>
<div slot="footer"> <div slot="footer">
<slot name="footer"> <slot name="footer">
<el-button size="medium" @click="cancel">{{ cancelButtonText }}</el-button> <el-button size="medium" @click="cancel">{{
<el-button size="medium" type="primary" @click="confirm">{{ confirmButtonText }}</el-button> cancelButtonText
}}</el-button>
<el-button size="medium" type="primary" @click="confirm">{{
confirmButtonText
}}</el-button>
</slot> </slot>
</div> </div>
</el-dialog> </el-dialog>
@@ -33,7 +43,7 @@ export default {
}, },
beforeClose: { beforeClose: {
type: Function, type: Function,
default: () => { } default: () => {}
}, },
visible: { visible: {
type: Boolean, type: Boolean,
@@ -68,8 +78,8 @@ export default {
this.beforeClose() this.beforeClose()
} }
}, },
created() { }, created() {},
mounted() { }, mounted() {},
computed: {} computed: {}
} }
</script> </script>

View File

@@ -21,9 +21,17 @@ export default {
</script> </script>
<template> <template>
<iframe id="iframe" frameborder="0" <iframe
:src="`${iframeSrc}/pdfjs-dist/web/viewer.html?file=${encodeURIComponent(getPdfUrl({ documentId: id }))}`" id="iframe"
class="miner-u" style="width: 100%; height: 800px;"></iframe> frameborder="0"
:src="
`${iframeSrc}/pdfjs-dist/web/viewer.html?file=${encodeURIComponent(
getPdfUrl({ documentId: id })
)}`
"
class="miner-u"
style="width: 100%; height: 800px;"
></iframe>
</template> </template>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

View File

@@ -1,4 +1,5 @@
import Cookies from 'js-cookie' import Cookies from 'js-cookie'
import { getRouters } from '@/api/app/user'
const state = { const state = {
sidebar: { sidebar: {
@@ -44,6 +45,27 @@ const actions = {
}, },
setSidebarList({ commit }, sidebarList) { setSidebarList({ commit }, sidebarList) {
commit('SET_SIDEBAR_LIST', sidebarList) commit('SET_SIDEBAR_LIST', sidebarList)
},
// 生成路由
GenerateRoutes({ commit }) {
return new Promise(resolve => {
// 向后端请求路由数据
getRouters().then(res => {
// const sdata = JSON.parse(JSON.stringify(res.data))
// const rdata = JSON.parse(JSON.stringify(res.data))
// const sidebarRoutes = filterAsyncRouter(sdata)
// const rewriteRoutes = filterAsyncRouter(rdata, false, true)
// const asyncRoutes = filterDynamicRoutes(dynamicRoutes)
// rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true })
// router.addRoutes(asyncRoutes)
// commit('SET_ROUTES', rewriteRoutes)
// commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes))
// commit('SET_DEFAULT_ROUTES', sidebarRoutes)
// commit('SET_TOPBAR_ROUTES', sidebarRoutes)
commit('SET_SIDEBAR_LIST', res.content.content)
resolve()
})
})
} }
} }

View File

@@ -1,5 +1,6 @@
import { login, logout, getInfo } from '@/api/app/user' import { login, logout, getInfo, getRouters } from '@/api/app/user'
import { getToken, setToken, removeToken } from '@/assets/js/utils/auth' import { getToken, setToken, removeToken } from '@/assets/js/utils/auth'
import router from '@/router'
const state = { const state = {
token: getToken(), token: getToken(),
@@ -31,12 +32,13 @@ const actions = {
}, },
// user login // user login
login({ commit }, userInfo) { login({ commit }, userInfo) {
const { username, password } = userInfo const { userName, password } = userInfo
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
login({ username: username.trim(), password: password }) login({ userName: userName.trim(), password: password })
.then(res => { .then(res => {
commit('SET_TOKEN', res.token) setToken(res.content.content)
setToken(res.token) commit('SET_TOKEN', res.content.content)
// getInfo()
resolve() resolve()
}) })
.catch(error => { .catch(error => {
@@ -53,9 +55,10 @@ const actions = {
if (!res) { if (!res) {
reject('Verification failed, please Login again.') reject('Verification failed, please Login again.')
} }
console.log(res)
commit('SET_NAME', res.name) commit('SET_NAME', res.content.content.userName)
commit('SET_AVATAR', res.avatar) // commit('SET_AVATAR', res.avatar)
resolve(res) resolve(res)
}) })
.catch(error => { .catch(error => {

View File

@@ -7,13 +7,17 @@
<logo class="logo" v-if="showLogo" :collapse="isCollapse" /> <logo class="logo" v-if="showLogo" :collapse="isCollapse" />
<div <div
v-for="route in menuList" v-for="route in menuList"
:key="route.path" :key="route.id"
class="primary-menu-item" class="primary-menu-item"
:class="{ active: activeParent === route.path }" :class="{ active: activeParent === route.url }"
@click="selectParentMenu(route)" @click="selectParentMenu(route)"
> >
<i v-if="route.meta && route.meta.icon" class="iconfont fs22" :class="route.meta.icon"></i> <i
<span class="menu-title">{{ route.meta ? route.meta.title : '' }}</span> v-if="route.meta && route.meta.icon"
class="iconfont fs22"
:class="route.meta.icon"
></i>
<span class="menu-title">{{ route.menuName }}</span>
</div> </div>
</div> </div>
<div> <div>
@@ -22,22 +26,28 @@
</div> </div>
<!-- Right side - Submenu --> <!-- Right side - Submenu -->
<div class="submenu-container" v-if="activeParent && currentSubmenu.length"> <div class="submenu-container" v-if="currentSubmenu.length">
<!-- <div class="submenu-header">--> <!-- <div class="submenu-header">-->
<!-- <span>{{ currentParentTitle }}</span>--> <!-- <span>{{ currentParentTitle }}</span>-->
<!-- </div>--> <!-- </div>-->
<el-scrollbar wrap-class="scrollbar-wrapper"> <el-scrollbar wrap-class="scrollbar-wrapper">
<div class="submenu-list"> <div class="submenu-list">
<pre style="font-size: 12px; padding: 10px; display: none;">{{ JSON.stringify(currentSubmenu, null, 2) }}</pre> <pre style="font-size: 12px; padding: 10px; display: none;">{{
JSON.stringify(currentSubmenu, null, 2)
}}</pre>
<div <div
v-for="subItem in currentSubmenu" v-for="subItem in currentSubmenu"
:key="subItem.path" :key="subItem.id"
class="submenu-item" class="submenu-item"
:class="{ active: activeMenu === subItem.path }" :class="{ active: activeMenu === subItem.url }"
@click="navigateTo(subItem.path)" @click="navigateTo(subItem.url)"
> >
<i v-if="subItem.meta && subItem.meta.icon" class="iconfont mr10" :class="subItem.meta.icon" /> <i
<span>{{ subItem.meta ? subItem.meta.title : subItem.name || 'Unnamed Item' }}</span> v-if="subItem.meta && subItem.meta.icon"
class="iconfont mr10"
:class="subItem.meta.icon"
/>
<span>{{ subItem.menuName }}</span>
</div> </div>
</div> </div>
</el-scrollbar> </el-scrollbar>
@@ -47,6 +57,7 @@
</template> </template>
<script> <script>
import Cookies from 'js-cookie'
import { mapGetters, mapActions } from 'vuex' import { mapGetters, mapActions } from 'vuex'
import Logo from './Logo' import Logo from './Logo'
import variables from '@/assets/sass/variables.scss' import variables from '@/assets/sass/variables.scss'
@@ -79,14 +90,14 @@ export default {
}, },
currentSubmenu() { currentSubmenu() {
if (!this.activeParent) return [] if (!this.activeParent) return []
const parent = this.menuList.find(item => item.path === this.activeParent) const parent = this.menuList.find(item => item.url === this.activeParent)
console.log('Parent found:', parent) console.log('Parent found:', parent)
console.log('Children:', parent ? parent.children : 'No children') console.log('Children:', parent ? parent.children : 'No children')
return parent ? parent.children || [] : [] return parent ? parent.children || [] : []
}, },
currentParentTitle() { currentParentTitle() {
if (!this.activeParent) return '' if (!this.activeParent) return ''
const parent = this.menuList.find(item => item.path === this.activeParent) const parent = this.menuList.find(item => item.url === this.activeParent)
return parent && parent.meta ? parent.meta.title : '' return parent && parent.meta ? parent.meta.title : ''
} }
}, },
@@ -103,9 +114,9 @@ export default {
formatList(menu, state, parentPath = null) { formatList(menu, state, parentPath = null) {
menu.map(item => { menu.map(item => {
if (parentPath) { if (parentPath) {
item.path = '/' + parentPath + '/' + item.url item.url = '/' + parentPath + '/' + item.url
} else { } else {
item.path = item.url item.url = item.url
} }
if (item.otherInfo1 == 0) { if (item.otherInfo1 == 0) {
item.name = item.menuName item.name = item.menuName
@@ -115,7 +126,7 @@ export default {
icon: item.img icon: item.img
} }
if (item.children != null) { if (item.children != null) {
this.formatList(item.children, undefined, item.path) this.formatList(item.children, undefined, item.url)
} else { } else {
item.children = [] item.children = []
} }
@@ -126,20 +137,21 @@ export default {
return menu return menu
}, },
selectParentMenu(route) { selectParentMenu(route) {
this.activeParent = route.path this.activeParent = route.url
console.log(Cookies.get('sidebarStatus'))
// this.$store.dispatch('app/toggleSideBar')
// If this parent has children, don't navigate // If this parent has children, don't navigate
if (route.children && route.children.length > 0) { if (route.children && route.children.length > 0) {
// Optionally navigate to the first child // Optionally navigate to the first child
// this.navigateTo(route.children[0].path) // this.navigateTo(route.children[0].url)
} else { } else {
// If no children, navigate to the parent route // If no children, navigate to the parent route
this.navigateTo(route.path) this.navigateTo(route.url)
} }
}, },
navigateTo(path) { navigateTo(url) {
if (path && path !== this.$route.path) { if (url && url !== this.$route.path) {
this.$router.push(path) this.$router.push(url)
} }
}, },
findParentForCurrentRoute() { findParentForCurrentRoute() {
@@ -148,17 +160,21 @@ export default {
// Try to find the parent menu that contains the current route // Try to find the parent menu that contains the current route
for (const parent of this.menuList) { for (const parent of this.menuList) {
// Check if current route is the parent itself // Check if current route is the parent itself
if (parent.path === currentPath) { if (parent.url === currentPath) {
this.activeParent = parent.path this.activeParent = parent.url
return return
} }
// Check if current route is one of the children // Check if current route is one of the children
if (parent.children && parent.children.length) { if (parent.children && parent.children.length) {
const childMatch = parent.children.find(child => child.path === currentPath || currentPath.startsWith(child.path + '/')) const childMatch = parent.children.find(
child =>
child.url === currentPath ||
currentPath.startsWith(child.url + '/')
)
if (childMatch) { if (childMatch) {
this.activeParent = parent.path this.activeParent = parent.url
return return
} }
} }
@@ -166,7 +182,7 @@ export default {
// If no match found, default to first menu item if available // If no match found, default to first menu item if available
if (this.menuList.length > 0 && !this.activeParent) { if (this.menuList.length > 0 && !this.activeParent) {
this.activeParent = this.menuList[0].path this.activeParent = this.menuList[0].url
} }
} }
}, },
@@ -174,14 +190,15 @@ export default {
if (sessionStorage.token !== 'MockToken') { if (sessionStorage.token !== 'MockToken') {
// 获取路由数据 // 获取路由数据
let data = this.$store.state.app.sidebarList let data = this.$store.state.app.sidebarList
console.log('sidebarList')
console.log(data)
// 通过检查路由结构来确定顶级菜单 // 通过检查路由结构来确定顶级菜单
const topLevelRoutes = data.filter(route => { const topLevelRoutes = data.filter(route => {
// 如果路由有 meta 和 children则认为它是顶级菜单 // 如果路由有 meta 和 children则认为它是顶级菜单
return route.meta && route.path && !route.hidden return route.meta && route.children && !route.hidden
}) })
this.menuList = topLevelRoutes this.menuList = data
} else { } else {
// 从路由配置中获取顶级路由 // 从路由配置中获取顶级路由
const topLevelRoutes = this.routes.filter(route => { const topLevelRoutes = this.routes.filter(route => {
@@ -281,6 +298,7 @@ export default {
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
padding: 23px 10px; padding: 23px 10px;
width: 160px;
} }
.submenu-header { .submenu-header {

View File

@@ -1,6 +1,6 @@
<template> <template>
<div :class="{ 'has-logo': showLogo }"> <div :class="{ 'has-logo': showLogo }">
<logo v-if="showLogo" :collapse="isCollapse" /> <logo v-if="showLogo" />
<!--<div class="www el-scrollbar" style="float: left;color:red">--> <!--<div class="www el-scrollbar" style="float: left;color:red">-->
<!--<div v-for="route in this.$router.options" class="box">--> <!--<div v-for="route in this.$router.options" class="box">-->
<!--<el-button>{{ route.name }}</el-button>--> <!--<el-button>{{ route.name }}</el-button>-->

View File

@@ -1,7 +1,11 @@
<template> <template>
<div class="navbar"> <div class="navbar">
<div class="right-menu" v-if="$store.state.settings.sidebarLogo"> <div class="right-menu" v-if="$store.state.settings.sidebarLogo">
<el-dropdown class="avatar-container" trigger="click" placement="top-start"> <el-dropdown
class="avatar-container"
trigger="click"
placement="top-start"
>
<div class="avatar-wrapper"> <div class="avatar-wrapper">
<el-avatar :size="size" :src="circleUrl" class="user-avatar" /> <el-avatar :size="size" :src="circleUrl" class="user-avatar" />
<!-- <i class="el-icon-caret-bottom" />--> <!-- <i class="el-icon-caret-bottom" />-->
@@ -44,8 +48,14 @@ export default {
this.$store.dispatch('app/toggleSideBar') this.$store.dispatch('app/toggleSideBar')
}, },
async logout() { async logout() {
await this.$store.dispatch('user/logout') this.$confirm('确定要退出吗?', '提示', {
this.$router.push(`/login?redirect=${this.$route.fullPath}`) confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
await this.$store.dispatch('user/logout')
this.$router.push(`/login?redirect=${this.$route.fullPath}`)
})
} }
} }
} }

View File

@@ -4,7 +4,7 @@
<sidebar class="sidebar-container" /> <sidebar class="sidebar-container" />
<div class="main-container"> <div class="main-container">
<div :class="{ 'fixed-header': fixedHeader }"> <div :class="{ 'fixed-header': fixedHeader }">
<navbar /> <!-- <navbar />-->
</div> </div>
<app-main /> <app-main />
</div> </div>

View File

@@ -2,14 +2,14 @@
<div class="login-container"> <div class="login-container">
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" auto-complete="on" label-position="left"> <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" auto-complete="on" label-position="left">
<div class="title-container"> <div class="title-container">
<h3 class="title">用户账号</h3> <h3 class="title">欢迎登录</h3>
</div> </div>
<el-form-item prop="username"> <el-form-item prop="userName">
<span class="svg-container"> <span class="svg-container">
<svg-icon icon-class="user" /> <svg-icon icon-class="user" />
</span> </span>
<el-input ref="username" v-model="loginForm.username" placeholder="Username" name="username" type="text" tabindex="1" auto-complete="on" /> <el-input ref="userName" v-model="loginForm.userName" placeholder="userName" name="userName" type="text" tabindex="1" auto-complete="on" />
</el-form-item> </el-form-item>
<el-form-item prop="password"> <el-form-item prop="password">
@@ -33,23 +33,18 @@
</el-form-item> </el-form-item>
<el-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;" @click.native.prevent="handleLogin">登录</el-button> <el-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;" @click.native.prevent="handleLogin">登录</el-button>
<div class="tips">
<span style="margin-right:20px;">username: any</span>
<span>password: any</span>
</div>
</el-form> </el-form>
</div> </div>
</template> </template>
<script> <script>
import { validUsername } from '@/assets/js/utils/validate' import { validuserName } from '@/assets/js/utils/validate'
// import { indexUser } from '@/api/app/user' // import { indexUser } from '@/api/app/user'
export default { export default {
name: 'Login', name: 'Login',
data() { data() {
const validateUsername = (rule, value, callback) => { const validateuserName = (rule, value, callback) => {
if (!validUsername(value)) { if (!validuserName(value)) {
callback(new Error('Please enter the correct user name')) callback(new Error('Please enter the correct user name'))
} else { } else {
callback() callback()
@@ -64,11 +59,11 @@ export default {
} }
return { return {
loginForm: { loginForm: {
username: 'admin', userName: '',
password: '111111' password: ''
}, },
loginRules: { loginRules: {
username: [{ required: true, trigger: 'blur', validator: validateUsername }], userName: [{ required: true, trigger: 'blur' }],
password: [{ required: true, trigger: 'blur', validator: validatePassword }] password: [{ required: true, trigger: 'blur', validator: validatePassword }]
}, },
loading: false, loading: false,
@@ -107,15 +102,15 @@ export default {
this.$refs.loginForm.validate(valid => { this.$refs.loginForm.validate(valid => {
if (valid) { if (valid) {
this.loading = true this.loading = true
// this.$store.dispatch('user/login', this.loginForm) this.$store
localStorage.setItem('token', 'MockToken') .dispatch('user/login', this.loginForm)
// .then(() => { .then(() => {
this.$router.push({ path: '/' }) this.$router.push({ path: '/home' })
this.loading = false this.loading = false
// }) })
// .catch(() => { .catch(() => {
// this.loading = false this.loading = false
// }) })
} else { } else {
return false return false
} }

View File

@@ -1,12 +1,17 @@
<!-- src/views/knowledge/detail/components/DocumentDrawer.vue --> <!-- src/views/knowledge/detail/components/DocumentDrawer.vue -->
<template> <template>
<el-drawer :visible.sync="visible" size="80%" @close="$emit('update:visible', false)"> <el-drawer
:visible.sync="visible"
size="80%"
@close="$emit('update:visible', false)"
>
<!-- drawer title --> <!-- drawer title -->
<template #title> <template #title>
<div class="flex align-items-c"> <div class="flex align-items-c">
<div class="el-icon-document"></div> <div class="el-icon-document"></div>
<div class="ml10">{{ descriptions.dataset.knowledgeName }}</div> <div class="ml10">{{ descriptions.dataset.knowledgeName }}</div>
<el-tag type="info" class="ml10" size="mini"> {{ descriptions.dataset.segmentedMode | filterSegmentedMode }} <el-tag type="info" class="ml10" size="mini">
{{ descriptions.dataset.segmentedMode | filterSegmentedMode }}
</el-tag> </el-tag>
</div> </div>
</template> </template>
@@ -15,7 +20,8 @@
<el-card :shadow="'none'"> <el-card :shadow="'none'">
<el-descriptions size="small" :column="4"> <el-descriptions size="small" :column="4">
<el-descriptions-item label="文件名称"> <el-descriptions-item label="文件名称">
<i class="el-icon-document"></i>{{ descriptions.dataset.knowledgeName }} <i class="el-icon-document"></i
>{{ descriptions.dataset.knowledgeName }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="分段模式"> <el-descriptions-item label="分段模式">
{{ descriptions.dataset.segmentedMode | filterSegmentedMode }} {{ descriptions.dataset.segmentedMode | filterSegmentedMode }}
@@ -120,13 +126,25 @@
<div class="segment-content-container"> <div class="segment-content-container">
<div class="segment-header mb15"> <div class="segment-header mb15">
<div class="segment-summary"> <div class="segment-summary">
<span>{{ descriptions.data ? descriptions.data.length : 0 }}个分段</span> <span
>{{
descriptions.data ? descriptions.data.length : 0
}}个分段</span
>
</div> </div>
</div> </div>
<text-model v-if="descriptions.doc_form === 'text_model'" :descriptions="descriptions" <text-model
:documentDetail="documentDetail" :activeSegment="activeSegment" /> v-if="descriptions.doc_form === 'text_model'"
<q-a-model v-else-if="descriptions.doc_form === 'qa_model'" :descriptions="descriptions" :descriptions="descriptions"
:documentDetail="documentDetail" :activeSegment="activeSegment" /> :documentDetail="documentDetail"
:activeSegment="activeSegment"
/>
<q-a-model
v-else-if="descriptions.doc_form === 'qa_model'"
:descriptions="descriptions"
:documentDetail="documentDetail"
:activeSegment="activeSegment"
/>
</div> </div>
</el-card> </el-card>
<!-- 文件展示 --> <!-- 文件展示 -->
@@ -140,7 +158,10 @@
<script> <script>
import TextModel from './TextModel.vue' import TextModel from './TextModel.vue'
import QAModel from './QAModel.vue' import QAModel from './QAModel.vue'
import { documentSourceOptions, segmentedModeOptionsMap } from '@/assets/js/utils/utilOptions' import {
documentSourceOptions,
segmentedModeOptionsMap
} from '@/assets/js/utils/utilOptions'
import RenderFile from '@/components/RenderFile/Index.vue' import RenderFile from '@/components/RenderFile/Index.vue'
export default { export default {
@@ -212,14 +233,18 @@ export default {
* @return { string } 预处理状态 * @return { string } 预处理状态
*/ */
filterUseMineru(val) { filterUseMineru(val) {
let item = segmentedModeOptionsMap.find(item => item.value === String(val)) let item = segmentedModeOptionsMap.find(
item => item.value === String(val)
)
return item ? item.label : '否' return item ? item.label : '否'
} }
}, },
methods: { methods: {
// 获取文档来源标签 // 获取文档来源标签
getDocumentSourceLabel(sourceValue) { getDocumentSourceLabel(sourceValue) {
const source = this.documentSourceOptions.find(item => item.value === String(sourceValue)) const source = this.documentSourceOptions.find(
item => item.value === String(sourceValue)
)
return source ? source.label : '未知来源' return source ? source.label : '未知来源'
} }
} }

View File

@@ -2,19 +2,35 @@
<div class="segment-split-view"> <div class="segment-split-view">
<div class="segment-list"> <div class="segment-list">
<el-checkbox-group v-model="selectedSegments"> <el-checkbox-group v-model="selectedSegments">
<div class="segment-list-item" v-for="(segment, index) in descriptions.data" :key="index" <div
:class="{ active: activeSegment === index }"> class="segment-list-item"
v-for="(segment, index) in descriptions.data"
:key="index"
:class="{ active: activeSegment === index }"
>
<el-checkbox class="mr pt15" :label="index"> <el-checkbox class="mr pt15" :label="index">
<span class="el-icon-s-unfold"></span> <span class="el-icon-s-unfold"></span>
<span class="segment-number">分段 - {{ (index + 1).toString().padStart(2, '0') }}</span> <span class="segment-number"
<span v-if="segment.word_count > 0"> · {{ segment.word_count }}个字符 ·</span> >分段 - {{ (index + 1).toString().padStart(2, '0') }}</span
>
<span v-if="segment.word_count > 0">
· {{ segment.word_count }}个字符 ·</span
>
<span> {{ 0 }} 次召回次数</span> <span> {{ 0 }} 次召回次数</span>
</el-checkbox> </el-checkbox>
<div @click="handleSegmentClick(index)"> <div @click="handleSegmentClick(index)">
<p>{{ segment.content.slice(0, 20) + '.....' }}</p> <p>{{ segment.content.slice(0, 20) + '.....' }}</p>
<div class="segment-keywords flex" v-if="segment.keywords && segment.keywords.length"> <div
<p v-for="(item, index) in segment.keywords" :key="index" class="mr10">#{{ item }}</p> class="segment-keywords flex"
v-if="segment.keywords && segment.keywords.length"
>
<p
v-for="(item, index) in segment.keywords"
:key="index"
class="mr10"
>
#{{ item }}
</p>
</div> </div>
</div> </div>
</div> </div>
@@ -22,23 +38,47 @@
</div> </div>
<!-- 弹窗 --> <!-- 弹窗 -->
<r-dialog title="分段详情" :visible.sync="dialogVisible" width="50%" append-to-body :before-close="handleClose"> <r-dialog
<div v-if="activeSegment !== null && descriptions.data && descriptions.data.length > 0"> title="分段详情"
:visible.sync="dialogVisible"
width="50%"
append-to-body
:before-close="handleClose"
>
<div
v-if="
activeSegment !== null &&
descriptions.data &&
descriptions.data.length > 0
"
>
<div class="segment-content"> <div class="segment-content">
{{ descriptions.data[activeSegment].content }} {{ descriptions.data[activeSegment].content }}
<div class="flex align-items-c mt20" <div
v-if="descriptions.data[activeSegment].keywords && descriptions.data[activeSegment].keywords.length" class="flex align-items-c mt20"
style="flex-wrap: wrap"> v-if="
descriptions.data[activeSegment].keywords &&
descriptions.data[activeSegment].keywords.length
"
style="flex-wrap: wrap"
>
<span>关键词 </span> <span>关键词 </span>
<el-tag v-for="(item, index) in descriptions.data[activeSegment].keywords" :key="index" <el-tag
class="mr10 ellipsis" size="mini" type="primary"> v-for="(item, index) in descriptions.data[activeSegment].keywords"
:key="index"
class="mr10 ellipsis"
size="mini"
type="primary"
>
{{ item }} {{ item }}
</el-tag> </el-tag>
</div> </div>
</div> </div>
</div> </div>
<span slot="footer" class="dialog-footer"> <span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false" size="medium"> </el-button> <el-button @click="dialogVisible = false" size="medium"
>关 闭</el-button
>
</span> </span>
</r-dialog> </r-dialog>
</div> </div>

View File

@@ -4,47 +4,104 @@
<img :src="knowledgePng_1" class="header-icon" /> <img :src="knowledgePng_1" class="header-icon" />
<div class="ml20" style="flex:1"> <div class="ml20" style="flex:1">
<div class="flex align-items-c"> <div class="flex align-items-c">
<div class="mr20 header" v-if="!editKnowledge">{{ knowledgeName }}</div> <div class="mr20 header" v-if="!editKnowledge">
<el-input class="mr20 w400" size="small" v-else v-model="copyKnowledgeName">{{ knowledgeName }}</el-input> {{ knowledgeName }}
<el-icon class="fs16 el-icon-edit-outline cursor-pointer" @click.native="editKnowledgeName"
v-if="!editKnowledge" />
<div v-else>
<el-button type="primary" size="medium" class="render-button" @click="saveKnowledgeName">保存</el-button>
<el-button size="medium" class="render-button" @click="cancelKnowledgeName">取消</el-button>
</div> </div>
<span class="segment-content">{{ segmentedMode | filterSegmentedMode }}</span> <el-input
class="mr20 w400"
size="small"
v-else
v-model="copyKnowledgeName"
>{{ knowledgeName }}</el-input
>
<el-icon
class="fs16 el-icon-edit-outline cursor-pointer"
@click.native="editKnowledgeName"
v-if="!editKnowledge"
/>
<div v-else>
<el-button
type="primary"
size="medium"
class="render-button"
@click="saveKnowledgeName"
>保存</el-button
>
<el-button
size="medium"
class="render-button"
@click="cancelKnowledgeName"
>取消</el-button
>
</div>
<span class="segment-content">{{
segmentedMode | filterSegmentedMode
}}</span>
</div> </div>
<p class="mt10 fs14" style="line-height: 20px">描述{{ knowledgeDesc }}</p> <p class="mt10 fs14" style="line-height: 20px">
描述{{ knowledgeDesc }}
</p>
<!-- <p class="mt10 fs14" style="line-height: 20px">分段模式{{ segmentedMode | filterSegmentedMode }}</p>--> <!-- <p class="mt10 fs14" style="line-height: 20px">分段模式{{ segmentedMode | filterSegmentedMode }}</p>-->
</div> </div>
<div> <div>
<!-- <el-button type="primary" size="medium" class="normal-button" @click="jumpEditKnowledge">修改知识库</el-button>--> <!-- <el-button type="primary" size="medium" class="normal-button" @click="jumpEditKnowledge">修改知识库</el-button>-->
<el-button type="primary" size="medium" icon="el-icon-edit-outline" class="primary-button" <el-button
@click="jumpEditKnowledge">修改知识库</el-button> type="primary"
<el-button type="primary" size="medium" icon="el-icon-plus" class="primary-button" size="medium"
@click="jumpAddKnowledge">上传知识</el-button> icon="el-icon-edit-outline"
class="primary-button"
@click="jumpEditKnowledge"
>修改知识库</el-button
>
<el-button
type="primary"
size="medium"
icon="el-icon-plus"
class="primary-button"
@click="jumpAddKnowledge"
>上传知识</el-button
>
</div> </div>
</div> </div>
<div class="mt20 card-body"> <div class="mt20 card-body">
<el-empty v-if="!hasList"> <el-empty v-if="!hasList">
<div class="mt20"> <div class="mt20">
<el-button type="primary" size="medium" class="fs14" @click="jumpAddKnowledge">立即添加</el-button> <el-button
type="primary"
size="medium"
class="fs14"
@click="jumpAddKnowledge"
>立即添加</el-button
>
</div> </div>
</el-empty> </el-empty>
<div class="table-container" v-else> <div class="table-container" v-else>
<div class="flex align-items-c justify-content-b"> <div class="flex align-items-c justify-content-b">
<el-form :model="form" label-width="100px" label-position="top" inline> <el-form
:model="form"
label-width="100px"
label-position="top"
inline
>
<el-form-item label="知识文件名称" prop="fileName"> <el-form-item label="知识文件名称" prop="fileName">
<el-input v-model="form.knowledgeNameLike" size="medium" placeholder="请输入知识文件名称" <el-input
@keydown.enter.native="search"></el-input> v-model="form.knowledgeNameLike"
size="medium"
placeholder="请输入知识文件名称"
@keydown.enter.native="search"
></el-input>
</el-form-item> </el-form-item>
<el-form-item label="知识文件来源" prop="documentSource"> <el-form-item label="知识文件来源" prop="documentSource">
<el-select v-model="form.documentSource" size="medium"> <el-select v-model="form.documentSource" size="medium">
<el-option label="全部" value=""></el-option> <el-option label="全部" value=""></el-option>
<el-option v-for="item in documentSourceOptions" :label="item.label" :value="item.value" <el-option
:key="item.value"></el-option> v-for="item in documentSourceOptions"
:label="item.label"
:value="item.value"
:key="item.value"
></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="上传用户" prop="createdUserLike"> <el-form-item label="上传用户" prop="createdUserLike">
@@ -53,32 +110,73 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="关键字"> <el-form-item label="关键字">
<el-input v-model="form.name" placeholder="请输入关键字/敏感词" disabled size="medium"></el-input> <el-input
v-model="form.name"
placeholder="请输入关键字/敏感词"
disabled
size="medium"
></el-input>
</el-form-item> </el-form-item>
<el-form-item label="上传时间" prop="times"> <el-form-item label="上传时间" prop="times">
<el-date-picker size="medium" style="width:100%" v-model="form.times" value-format="yyyy-MM-dd" <el-date-picker
start-placeholder="开始时间" end-placeholder="结束时间" type="daterange"></el-date-picker> size="medium"
style="width:100%"
v-model="form.times"
value-format="yyyy-MM-dd"
start-placeholder="开始时间"
end-placeholder="结束时间"
type="daterange"
></el-date-picker>
</el-form-item> </el-form-item>
</el-form> </el-form>
<div class="mt15 flex align-items-c justify-content-b"> <div class="mt15 flex align-items-c justify-content-b">
<el-button size="medium" type="primary" @click="search">查询</el-button> <el-button size="medium" type="primary" @click="search"
>查询</el-button
>
<el-button size="medium" @click="reset">重置</el-button> <el-button size="medium" @click="reset">重置</el-button>
</div> </div>
</div> </div>
<r-table :columns="columns" :data="list" :deletion="false" :total="total" @page-change="pageChange" <r-table
@current-change="currentChange" :current-page="page" :page-size="pageSize"></r-table> :columns="columns"
:data="list"
:deletion="false"
:total="total"
@page-change="pageChange"
@current-change="currentChange"
:current-page="page"
:page-size="pageSize"
></r-table>
</div> </div>
</div> </div>
<document-drawer :visible.sync="drawer" :descriptions="descriptions" :document-detail="documentDetail" <document-drawer
:active-segment="activeSegment" @update:visible="val => (drawer = val)" /> :visible.sync="drawer"
:descriptions="descriptions"
:document-detail="documentDetail"
:active-segment="activeSegment"
@update:visible="val => (drawer = val)"
/>
<knowledgeForm :visible.sync="drawerForm" :datasetId="$route.query.datasetId" @update:visible="getKnowledgeDetail"> <knowledgeForm
:visible.sync="drawerForm"
:datasetId="$route.query.datasetId"
@update:visible="getKnowledgeDetail"
>
</knowledgeForm> </knowledgeForm>
</div> </div>
</template> </template>
<script> <script>
import { datasetDocumentEx, datasetQueryDelete, datasetQuerySegments, datasetsExPages, datasetUpdate, getDatasetById } from '@/api/generatedApi/index' import {
import { documentSourceOptions, segmentedModeOptionsMap } from '@/assets/js/utils/utilOptions' datasetDocumentEx,
datasetQueryDelete,
datasetQuerySegments,
datasetsExPages,
datasetUpdate,
getDatasetById
} from '@/api/generatedApi/index'
import {
documentSourceOptions,
segmentedModeOptionsMap
} from '@/assets/js/utils/utilOptions'
import DocumentDrawer from './components/documentDetail/DocumentDrawer.vue' import DocumentDrawer from './components/documentDetail/DocumentDrawer.vue'
import knowledgeForm from '@/views/knowledge/detail/components/knowledgeForm.vue' import knowledgeForm from '@/views/knowledge/detail/components/knowledgeForm.vue'
import knowledgePng_1 from '@/assets/images/konwledge/konwledge-1.png' import knowledgePng_1 from '@/assets/images/konwledge/konwledge-1.png'
@@ -211,8 +309,12 @@ export default {
datasetId, datasetId,
page: this.page, page: this.page,
pageSize: this.pageSize, pageSize: this.pageSize,
startCreatedDate: this.form.times[0] ? this.form.times[0] + ' 00:00:00' : '', startCreatedDate: this.form.times[0]
endCreatedDate: this.form.times[1] ? this.form.times[1] + ' 23:59:59' : '' ? this.form.times[0] + ' 00:00:00'
: '',
endCreatedDate: this.form.times[1]
? this.form.times[1] + ' 23:59:59'
: ''
}).then(res => { }).then(res => {
this.list = res.content.content.list this.list = res.content.content.list
this.total = res.content.content.total this.total = res.content.content.total
@@ -242,7 +344,7 @@ export default {
segmentedMode: this.segmentedMode segmentedMode: this.segmentedMode
}, },
...row, ...row,
...res.content.content, ...res.content.content
} }
this.drawer = true this.drawer = true
@@ -274,7 +376,10 @@ export default {
try { try {
// 尝试解析JSON字符串 // 尝试解析JSON字符串
if (typeof rule === 'string' && (rule.startsWith('{') || rule.startsWith('['))) { if (
typeof rule === 'string' &&
(rule.startsWith('{') || rule.startsWith('['))
) {
const parsedRule = JSON.parse(rule) const parsedRule = JSON.parse(rule)
return JSON.stringify(parsedRule, null, 2) return JSON.stringify(parsedRule, null, 2)
} }
@@ -316,11 +421,13 @@ export default {
} }
}, },
filterUseMineru(val) { filterUseMineru(val) {
let item = segmentedModeOptionsMap.find(item => item.value === String(val)) let item = segmentedModeOptionsMap.find(
item => item.value === String(val)
)
return item ? item.label : '否' return item ? item.label : '否'
} }
}, },
created() { }, created() {},
async mounted() { async mounted() {
this.getKnowledgeDetail() this.getKnowledgeDetail()
// 获取知识库文件列表 // 获取知识库文件列表
@@ -353,7 +460,9 @@ export default {
prop: 'documentSource', prop: 'documentSource',
width: '200px', width: '200px',
render: (h, params) => { render: (h, params) => {
let text = documentSourceOptions.find(item => item.value === String(params.row.documentSource)).label let text = documentSourceOptions.find(
item => item.value === String(params.row.documentSource)
).label
return h('div', [h('span', text)]) return h('div', [h('span', text)])
} }
}, },
@@ -368,7 +477,10 @@ export default {
key: '上传用户', key: '上传用户',
prop: 'createdUser', prop: 'createdUser',
render: (h, params) => { render: (h, params) => {
return h('div', !params.row.createdUser ? '-' : params.row.createdUser) return h(
'div',
!params.row.createdUser ? '-' : params.row.createdUser
)
} }
}, },
{ {
@@ -382,33 +494,32 @@ export default {
isRedraw: true, isRedraw: true,
render: (h, params) => { render: (h, params) => {
return h('div', [ return h('div', [
h('el-button', {
class: 'floatSpan',
props: {
type: 'danger',
size: 'mini',
icon: 'el-icon-delete',
title: '删除'
},
on: {
click: () => this.deleteKnowledge(params.row)
}
}),
h( h(
'el-button', 'el-button',
{ {
class: 'floatSpan', class: 'normal-button',
props: { props: {
type: 'danger', type: 'primary',
size: 'mini', size: 'mini',
icon: 'el-icon-delete', disabled: true,
title: '删除' icon: 'el-icon-edit-outline',
title: '编辑'
}, },
on: { on: {}
click: () => this.deleteKnowledge(params.row)
}
}, },
'编辑'
),
h('el-button', {
class: 'normal-button',
props: {
type: 'primary',
size: 'mini',
disabled: true,
icon: 'el-icon-edit-outline',
title: '编辑'
},
on: {}
}, "编辑"
), ),
h( h(
'el-button', 'el-button',

View File

@@ -39,11 +39,20 @@ export default {
}, },
computed: { computed: {
documentStatus() { documentStatus() {
if (this.form.documentStatus === 0 || this.form.documentStatus === '上传中') { if (
this.form.documentStatus === 0 ||
this.form.documentStatus === '上传中'
) {
return '上传中' return '上传中'
} else if (this.form.documentStatus === 1 || this.form.documentStatus === '成功') { } else if (
this.form.documentStatus === 1 ||
this.form.documentStatus === '成功'
) {
return '成功' return '成功'
} else if (this.form.documentStatus === -1 || this.form.documentStatus === '失败') { } else if (
this.form.documentStatus === -1 ||
this.form.documentStatus === '失败'
) {
return '失败' return '失败'
} }
return this.form.documentStatus return this.form.documentStatus
@@ -95,22 +104,38 @@ export default {
</div> </div>
<el-form ref="form" :model="newForm" label-width="120px"> <el-form ref="form" :model="newForm" label-width="120px">
<el-form-item label="知识库"> <el-form-item label="知识库">
<el-input v-model="newForm.datasetName" placeholder="" disabled></el-input> <el-input
v-model="newForm.datasetName"
placeholder=""
disabled
></el-input>
</el-form-item> </el-form-item>
<el-form-item label="知识文件名称"> <el-form-item label="知识文件名称">
<el-input v-model="newForm.fileName" placeholder="" disabled></el-input> <el-input
v-model="newForm.fileName"
placeholder=""
disabled
></el-input>
</el-form-item> </el-form-item>
<el-row> <el-row>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="上传用户"> <el-form-item label="上传用户">
<el-input v-model="newForm.userName" placeholder="" disabled></el-input> <el-input
v-model="newForm.userName"
placeholder=""
disabled
></el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="上传时间"> <el-form-item label="上传时间">
<el-input v-model="newForm.createdDate" placeholder="" disabled></el-input> <el-input
v-model="newForm.createdDate"
placeholder=""
disabled
></el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
@@ -118,12 +143,20 @@ export default {
<el-row> <el-row>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="任务号"> <el-form-item label="任务号">
<el-input v-model="newForm.id" placeholder="" disabled></el-input> <el-input
v-model="newForm.id"
placeholder=""
disabled
></el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="分段模式"> <el-form-item label="分段模式">
<el-input v-model="newForm.segmentedMode" placeholder="null" disabled></el-input> <el-input
v-model="newForm.segmentedMode"
placeholder="null"
disabled
></el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
@@ -131,18 +164,32 @@ export default {
<el-row> <el-row>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="高级模式"> <el-form-item label="高级模式">
<el-input v-model="useMineru" placeholder="" disabled></el-input> <el-input
v-model="useMineru"
placeholder=""
disabled
></el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="任务状态"> <el-form-item label="任务状态">
<el-input v-model="documentStatus" placeholder="" disabled></el-input> <el-input
v-model="documentStatus"
placeholder=""
disabled
></el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<el-form-item label="失败原因" v-if="newForm.documentStatus === -1"> <el-form-item
<el-input v-model="newForm.errMessage" placeholder=""></el-input> label="失败原因"
v-if="newForm.documentStatus === -1"
>
<el-input
v-model="newForm.errMessage"
placeholder=""
></el-input>
</el-form-item> </el-form-item>
</el-form> </el-form>
</el-card> </el-card>
@@ -153,8 +200,14 @@ export default {
<span>知识内容</span> <span>知识内容</span>
</div> </div>
<div class="knowledge-content" v-if="descriptions"> <div class="knowledge-content" v-if="descriptions">
<text-model v-if="descriptions.doc_form === 'text_model'" :descriptions="descriptions" /> <text-model
<q-a-model v-else-if="descriptions.doc_form === 'qa_model'" :descriptions="descriptions" /> v-if="descriptions.doc_form === 'text_model'"
:descriptions="descriptions"
/>
<q-a-model
v-else-if="descriptions.doc_form === 'qa_model'"
:descriptions="descriptions"
/>
</div> </div>
<span v-else>暂无知识内容</span> <span v-else>暂无知识内容</span>
</el-card> </el-card>