更新
This commit is contained in:
parent
f9609403b5
commit
2faa82f4be
33
f/web-kboss/src/api/roleManagement.js
Normal file
33
f/web-kboss/src/api/roleManagement.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
// 获取角色
|
||||||
|
export function getRoleAPI(data) {
|
||||||
|
return request({
|
||||||
|
url: `/role/getRole.dspy`,
|
||||||
|
method: 'get',
|
||||||
|
params: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 添加角色
|
||||||
|
export function addRoleAPI(data) {
|
||||||
|
return request({
|
||||||
|
url: `/reseller/reseller_add_user.dspy`,
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 列表
|
||||||
|
export function getListAPI(data) {
|
||||||
|
return request({
|
||||||
|
url: `/user/get_user_and_role.dspy`,
|
||||||
|
method: 'get',
|
||||||
|
params: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 分配角色 参数为userid roleid
|
||||||
|
export function assignRoleAPI(data) {
|
||||||
|
return request({
|
||||||
|
url: `/user/add_user_role.dspy`,
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -2305,6 +2305,13 @@ export const asyncRoutes = [
|
|||||||
path: "/superAdministrator", component: Layout, redirect: "/superAdministrator/index", meta: {
|
path: "/superAdministrator", component: Layout, redirect: "/superAdministrator/index", meta: {
|
||||||
title: "超级管理员", icon: "el-icon-user-solid", noCache: true, fullPath: "/superAdministrator",
|
title: "超级管理员", icon: "el-icon-user-solid", noCache: true, fullPath: "/superAdministrator",
|
||||||
}, children: [{
|
}, children: [{
|
||||||
|
path: "roleManagement",
|
||||||
|
component: () => import("@/views/superAdministrator/roleManagement"),
|
||||||
|
name: "RoleManagement",
|
||||||
|
meta: {
|
||||||
|
title: "角色管理", fullPath: "/superAdministrator/roleManagement",
|
||||||
|
},
|
||||||
|
}, {
|
||||||
path: "addAdmin", component: () => import("@/views/superAdministrator/addAdmin"), name: "addAdmin", meta: {
|
path: "addAdmin", component: () => import("@/views/superAdministrator/addAdmin"), name: "addAdmin", meta: {
|
||||||
title: "添加业主机构管理员", fullPath: "/superAdministrator/addAdmin",
|
title: "添加业主机构管理员", fullPath: "/superAdministrator/addAdmin",
|
||||||
},
|
},
|
||||||
|
|||||||
@ -7,6 +7,7 @@ const MOBILE_UA_REGEXP = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Ope
|
|||||||
const CUSTOMER_ROLE = '客户';
|
const CUSTOMER_ROLE = '客户';
|
||||||
const OPERATION_ROLE = '运营';
|
const OPERATION_ROLE = '运营';
|
||||||
const FINANCE_ROLE = '财务';
|
const FINANCE_ROLE = '财务';
|
||||||
|
const ADMINISTRATOR_ROLE = '管理员';
|
||||||
|
|
||||||
// 这个用户能看到订单管理里的特殊子菜单,比如历史订单和订单详情。
|
// 这个用户能看到订单管理里的特殊子菜单,比如历史订单和订单详情。
|
||||||
const SPECIAL_ORDER_USER = 'ZhipuHZ';
|
const SPECIAL_ORDER_USER = 'ZhipuHZ';
|
||||||
@ -23,6 +24,8 @@ const OPERATION_EXTRA_ROUTE_PATHS = ['/modelManagement', '/modelInfoConfig', '/o
|
|||||||
// 财务角色需要额外补出来的菜单。
|
// 财务角色需要额外补出来的菜单。
|
||||||
const FINANCE_EXTRA_ROUTE_PATHS = ['/financialOverview'];
|
const FINANCE_EXTRA_ROUTE_PATHS = ['/financialOverview'];
|
||||||
|
|
||||||
|
const ADMINISTRATOR_ROUTE_FULL_PATH = '/superAdministrator/roleManagement';
|
||||||
|
|
||||||
// 普通客户账号默认要补出来的基础菜单。
|
// 普通客户账号默认要补出来的基础菜单。
|
||||||
const BASE_USER_ROUTE_PATHS = ['/orderManagement', '/resourceManagement'];
|
const BASE_USER_ROUTE_PATHS = ['/orderManagement', '/resourceManagement'];
|
||||||
|
|
||||||
@ -350,6 +353,30 @@ function addFinanceRoutes(accessedRoutes, routes, userRoles = [], deviceType = '
|
|||||||
return appendMissingRoutes(accessedRoutes, getRoutesByPath(routes, FINANCE_EXTRA_ROUTE_PATHS));
|
return appendMissingRoutes(accessedRoutes, getRoutesByPath(routes, FINANCE_EXTRA_ROUTE_PATHS));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getAdministratorRoutes(routes, deviceType = 'pc') {
|
||||||
|
if (deviceType !== 'pc') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const superAdminRoute = findRouteByPath(routes, SUPER_ADMIN_ROUTE_PATH);
|
||||||
|
if (!superAdminRoute) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const clonedRoute = cloneRoute(superAdminRoute);
|
||||||
|
clonedRoute.redirect = ADMINISTRATOR_ROUTE_FULL_PATH;
|
||||||
|
clonedRoute.children = (clonedRoute.children || []).filter(child => child.meta?.fullPath === ADMINISTRATOR_ROUTE_FULL_PATH);
|
||||||
|
return clonedRoute.children.length ? [clonedRoute] : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function addAdministratorRoutes(accessedRoutes, routes, userRoles = [], deviceType = 'pc') {
|
||||||
|
if (!userRoles.includes(ADMINISTRATOR_ROLE)) {
|
||||||
|
return accessedRoutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
return appendMissingRoutes(accessedRoutes, getAdministratorRoutes(routes, deviceType));
|
||||||
|
}
|
||||||
|
|
||||||
// token市集是公共菜单,所有登录用户都要能看到。
|
// token市集是公共菜单,所有登录用户都要能看到。
|
||||||
function addCommonRoutes(accessedRoutes, routes, deviceType = 'pc') {
|
function addCommonRoutes(accessedRoutes, routes, deviceType = 'pc') {
|
||||||
const commonRoutes = getRoutesByPath(routes, COMMON_ROUTE_PATHS)
|
const commonRoutes = getRoutesByPath(routes, COMMON_ROUTE_PATHS)
|
||||||
@ -495,6 +522,13 @@ const actions = {
|
|||||||
console.log("用户类型:", userType, "orgType:", orgType, "设备类型:", deviceType);
|
console.log("用户类型:", userType, "orgType:", orgType, "设备类型:", deviceType);
|
||||||
console.log("ACTION generateRoutes - auths:", auths);
|
console.log("ACTION generateRoutes - auths:", auths);
|
||||||
|
|
||||||
|
if (!isSuperAdmin && userRoles.includes(ADMINISTRATOR_ROLE)) {
|
||||||
|
const administratorRoutes = getAdministratorRoutes(asyncRoutes, deviceType);
|
||||||
|
commit("SET_ROUTES", administratorRoutes);
|
||||||
|
resolve(administratorRoutes);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 3. 先生成第一版菜单:超级管理员只拿超管菜单,普通用户按后端 auths 过滤。
|
// 3. 先生成第一版菜单:超级管理员只拿超管菜单,普通用户按后端 auths 过滤。
|
||||||
let accessedRoutes = isSuperAdmin
|
let accessedRoutes = isSuperAdmin
|
||||||
? getSuperAdminRoutes(deviceType)
|
? getSuperAdminRoutes(deviceType)
|
||||||
@ -502,6 +536,7 @@ const actions = {
|
|||||||
|
|
||||||
// 4. token市集是公共入口,所有登录用户都补上。
|
// 4. token市集是公共入口,所有登录用户都补上。
|
||||||
accessedRoutes = addCommonRoutes(accessedRoutes, asyncRoutes, deviceType);
|
accessedRoutes = addCommonRoutes(accessedRoutes, asyncRoutes, deviceType);
|
||||||
|
accessedRoutes = addAdministratorRoutes(accessedRoutes, asyncRoutes, userRoles, deviceType);
|
||||||
|
|
||||||
if (!isSuperAdmin) {
|
if (!isSuperAdmin) {
|
||||||
// 5. 普通用户再补一些固定入口,比如订单、资源、客户专属菜单。
|
// 5. 普通用户再补一些固定入口,比如订单、资源、客户专属菜单。
|
||||||
|
|||||||
@ -382,6 +382,11 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
redirectAfterLogin(res) {
|
redirectAfterLogin(res) {
|
||||||
|
if (res.roles && res.roles.includes('管理员')) {
|
||||||
|
sessionStorage.removeItem('loginRedirectPath')
|
||||||
|
this.$router.push('/superAdministrator/roleManagement').catch(() => {})
|
||||||
|
return
|
||||||
|
}
|
||||||
const redirectPath = sessionStorage.getItem('loginRedirectPath')
|
const redirectPath = sessionStorage.getItem('loginRedirectPath')
|
||||||
if (redirectPath && redirectPath.startsWith('/') && !redirectPath.includes('/login')) {
|
if (redirectPath && redirectPath.startsWith('/') && !redirectPath.includes('/login')) {
|
||||||
sessionStorage.removeItem('loginRedirectPath')
|
sessionStorage.removeItem('loginRedirectPath')
|
||||||
|
|||||||
@ -544,6 +544,7 @@ export default Vue.extend({
|
|||||||
else if (role.includes('运维')) this.$router.push('/operationAndMaintenance/workOrderProcessing')
|
else if (role.includes('运维')) this.$router.push('/operationAndMaintenance/workOrderProcessing')
|
||||||
else if (role.includes('销售')) this.$router.push('/sales/distributorManagement')
|
else if (role.includes('销售')) this.$router.push('/sales/distributorManagement')
|
||||||
else if (role.includes('财务')) this.$router.push('/finance/supplierSettlementStatistics')
|
else if (role.includes('财务')) this.$router.push('/finance/supplierSettlementStatistics')
|
||||||
|
else if (role.includes('管理员')) this.$router.push('/superAdministrator/roleManagement')
|
||||||
else if (role.includes('admin')) this.$router.push('/superAdministrator/addAdmin')
|
else if (role.includes('admin')) this.$router.push('/superAdministrator/addAdmin')
|
||||||
},
|
},
|
||||||
async logout() {
|
async logout() {
|
||||||
|
|||||||
@ -243,7 +243,7 @@ export default {
|
|||||||
this.stopObserveEditorFullscreen()
|
this.stopObserveEditorFullscreen()
|
||||||
this.editorFullscreen = false
|
this.editorFullscreen = false
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
createEmptyForm() {
|
createEmptyForm() {
|
||||||
// 创建新增文章时使用的默认表单结构。
|
// 创建新增文章时使用的默认表单结构。
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
<i class="el-icon-arrow-left"></i>
|
<i class="el-icon-arrow-left"></i>
|
||||||
返回
|
返回
|
||||||
</button>
|
</button>
|
||||||
<span>API文档</span>
|
<span>元境 API 文档</span>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main class="doc-container">
|
<main class="doc-container">
|
||||||
@ -19,40 +19,12 @@
|
|||||||
></el-alert>
|
></el-alert>
|
||||||
|
|
||||||
<section class="doc-hero">
|
<section class="doc-hero">
|
||||||
<h1>{{ apiDoc.model_name }} API 文档</h1>
|
<h1>元境 API 文档</h1>
|
||||||
<p>{{ heroDescription }}</p>
|
<p>统一展示大模型、视频、图像、音频、任务查询等接口说明。</p>
|
||||||
<!-- <div class="quick-tabs">
|
|
||||||
<span v-for="item in quickTabs" :key="item">{{ item }}</span>
|
|
||||||
</div> -->
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section v-loading="loading" class="doc-section">
|
<section v-loading="loading" class="doc-section markdown-section">
|
||||||
<h2>1. 接口地址</h2>
|
<div v-html="renderedMarkdown"></div>
|
||||||
<p>统一使用 HTTPS 请求,所有接口都需要携带平台签发的 API Key。</p>
|
|
||||||
<pre><code>{{ apiUrlText }}</code></pre>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<section class="doc-section">
|
|
||||||
<h2>2. 请求示例</h2>
|
|
||||||
<pre><code>{{ apiDoc.curl_code || requestExample }}</code></pre>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="doc-section">
|
|
||||||
<h2>3. Python 示例</h2>
|
|
||||||
<pre><code>{{ apiDoc.python_code || pythonExample }}</code></pre>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="doc-section">
|
|
||||||
<h2>4. 错误码</h2>
|
|
||||||
<el-table :data="errorCodes" border size="small">
|
|
||||||
<el-table-column prop="code" label="错误码" width="140"></el-table-column>
|
|
||||||
<el-table-column prop="message" label="说明"></el-table-column>
|
|
||||||
<el-table-column prop="suggestion" label="处理建议"></el-table-column>
|
|
||||||
</el-table>
|
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
@ -61,124 +33,137 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { reqModelApiDocument } from '@/api/model/model'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ApiDocument',
|
name: 'ApiDocument',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loading: false,
|
loading: false,
|
||||||
errorMessage: '',
|
errorMessage: '',
|
||||||
apiDoc: {
|
markdownText: '',
|
||||||
id: '',
|
markdownUrl: 'https://token.opencomputing.cn/docs/api_doc_zh.md'
|
||||||
api_url: '',
|
|
||||||
model_id: '',
|
|
||||||
curl_code: '',
|
|
||||||
python_code: '',
|
|
||||||
model_name: '模型'
|
|
||||||
},
|
|
||||||
quickTabs: ['API概览', '认证方式', '请求参数', '代码示例', '错误码'],
|
|
||||||
capabilityTable: [
|
|
||||||
{ name: '文本对话', value: '支持单轮和多轮对话生成', status: '支持' },
|
|
||||||
{ name: '流式输出', value: '通过 stream=true 开启 SSE 增量返回', status: '支持' },
|
|
||||||
{ name: 'Function Call', value: '支持工具调用和结构化参数', status: '支持' },
|
|
||||||
{ name: '图像输入', value: '可在 messages 中传入图片内容', status: '支持' },
|
|
||||||
{ name: '私有化部署', value: '当前公共服务暂不支持私有化', status: '暂不支持' }
|
|
||||||
],
|
|
||||||
requestParams: [
|
|
||||||
{ name: 'model', type: 'string', required: '是', desc: '模型 ID,例如 minimax-m2.5' },
|
|
||||||
{ name: 'messages', type: 'array', required: '是', desc: '对话消息列表,包含 role 和 content' },
|
|
||||||
{ name: 'temperature', type: 'number', required: '否', desc: '采样温度,数值越高输出越随机' },
|
|
||||||
{ name: 'stream', type: 'boolean', required: '否', desc: '是否开启流式返回' },
|
|
||||||
{ name: 'max_tokens', type: 'number', required: '否', desc: '限制模型最大输出长度' }
|
|
||||||
],
|
|
||||||
errorCodes: [
|
|
||||||
{ code: '401', message: '认证失败', suggestion: '检查 API Key 是否正确或过期' },
|
|
||||||
{ code: '429', message: '请求过于频繁', suggestion: '降低并发或等待限流恢复' },
|
|
||||||
{ code: '500', message: '服务异常', suggestion: '稍后重试或联系平台支持' }
|
|
||||||
],
|
|
||||||
requestExample: `curl https://api.kboss.example.com/v2/chat/completions \\
|
|
||||||
-H "Content-Type: application/json" \\
|
|
||||||
-H "Authorization: Bearer $KBOSS_API_KEY" \\
|
|
||||||
-d '{
|
|
||||||
"model": "minimax-m2.5",
|
|
||||||
"messages": [
|
|
||||||
{
|
|
||||||
"role": "user",
|
|
||||||
"content": "帮我写一段模型上架介绍"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"temperature": 0.7,
|
|
||||||
"stream": false
|
|
||||||
}'`,
|
|
||||||
pythonExample: `import requests
|
|
||||||
|
|
||||||
url = "https://api.kboss.example.com/v2/chat/completions"
|
|
||||||
headers = {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Authorization": "Bearer $KBOSS_API_KEY"
|
|
||||||
}
|
|
||||||
data = {
|
|
||||||
"model": "minimax-m2.5",
|
|
||||||
"messages": [{"role": "user", "content": "Hello!"}],
|
|
||||||
"stream": False
|
|
||||||
}
|
|
||||||
response = requests.post(url, headers=headers, json=data)
|
|
||||||
print(response.json())`
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
apiUrlText() {
|
renderedMarkdown() {
|
||||||
const curlUrl = this.getUrlFromCurl(this.apiDoc.curl_code)
|
return this.renderMarkdown(this.markdownText)
|
||||||
const url = this.apiDoc.api_url || curlUrl
|
|
||||||
return url ? `POST ${url}` : '接口地址以后端返回为准'
|
|
||||||
},
|
|
||||||
heroDescription() {
|
|
||||||
const modelName = this.apiDoc.model_name || '当前模型'
|
|
||||||
return `${modelName} 接口调用示例,包含 curl 与 Python 两种接入方式。`
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.fetchApiDocument()
|
this.fetchMarkdownDocument()
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
'$route.query.id'() {
|
|
||||||
this.fetchApiDocument()
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// 通过路由传入的 id 获取模型 API 文档数据。
|
async fetchMarkdownDocument() {
|
||||||
async fetchApiDocument() {
|
|
||||||
const id = this.$route.query.id || this.$route.query.model_id
|
|
||||||
if (!id) {
|
|
||||||
this.errorMessage = '缺少模型ID,无法获取 API 文档'
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.loading = true
|
this.loading = true
|
||||||
this.errorMessage = ''
|
this.errorMessage = ''
|
||||||
try {
|
try {
|
||||||
const res = await reqModelApiDocument({ id })
|
const response = await fetch(this.markdownUrl)
|
||||||
if (res && res.status && res.data) {
|
if (!response.ok) {
|
||||||
this.apiDoc = {
|
throw new Error(`HTTP ${response.status}`)
|
||||||
...this.apiDoc,
|
|
||||||
...res.data,
|
|
||||||
model_name: res.data.model_name || this.apiDoc.model_name
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
this.errorMessage = (res && res.msg) || 'API 文档数据获取失败'
|
this.markdownText = await response.text()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[API文档] 获取模型 API 文档失败', error)
|
console.error('[API文档] 获取 Markdown 文档失败', error)
|
||||||
this.errorMessage = 'API 文档数据获取失败,请稍后重试'
|
this.errorMessage = 'API 文档加载失败,请稍后重试'
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getUrlFromCurl(curlCode) {
|
escapeHtml(text = '') {
|
||||||
if (!curlCode) return ''
|
return String(text)
|
||||||
const match = String(curlCode).match(/https?:\/\/[^\s\\]+/)
|
.replace(/&/g, '&')
|
||||||
return match ? match[0] : ''
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
},
|
||||||
|
renderInline(text = '') {
|
||||||
|
return this.escapeHtml(text)
|
||||||
|
.replace(/`([^`]+)`/g, '<code>$1</code>')
|
||||||
|
.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
|
||||||
|
},
|
||||||
|
renderTable(lines) {
|
||||||
|
const rows = lines
|
||||||
|
.filter(line => line.trim().startsWith('|'))
|
||||||
|
.map(line => line.trim().replace(/^\||\|$/g, '').split('|').map(cell => this.renderInline(cell.trim())))
|
||||||
|
if (!rows.length) return ''
|
||||||
|
const header = rows[0] || []
|
||||||
|
const bodyRows = rows.slice(2)
|
||||||
|
return `<div class="md-table-wrap"><table><thead><tr>${header.map(cell => `<th>${cell}</th>`).join('')}</tr></thead><tbody>${bodyRows.map(row => `<tr>${row.map(cell => `<td>${cell}</td>`).join('')}</tr>`).join('')}</tbody></table></div>`
|
||||||
|
},
|
||||||
|
renderMarkdown(markdown = '') {
|
||||||
|
const lines = markdown.replace(/\r\n/g, '\n').split('\n')
|
||||||
|
const html = []
|
||||||
|
let index = 0
|
||||||
|
|
||||||
|
while (index < lines.length) {
|
||||||
|
const line = lines[index]
|
||||||
|
const trimmed = line.trim()
|
||||||
|
|
||||||
|
if (!trimmed) {
|
||||||
|
index += 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trimmed.startsWith('```')) {
|
||||||
|
const lang = trimmed.replace(/```/, '').trim()
|
||||||
|
const code = []
|
||||||
|
index += 1
|
||||||
|
while (index < lines.length && !lines[index].trim().startsWith('```')) {
|
||||||
|
code.push(lines[index])
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
html.push(`<pre><code class="language-${this.escapeHtml(lang)}">${this.escapeHtml(code.join('\n'))}</code></pre>`)
|
||||||
|
index += 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trimmed.startsWith('|')) {
|
||||||
|
const tableLines = []
|
||||||
|
while (index < lines.length && lines[index].trim().startsWith('|')) {
|
||||||
|
tableLines.push(lines[index])
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
html.push(this.renderTable(tableLines))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/^---+$/.test(trimmed)) {
|
||||||
|
html.push('<hr>')
|
||||||
|
index += 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const heading = trimmed.match(/^(#{1,6})\s+(.*)$/)
|
||||||
|
if (heading) {
|
||||||
|
const level = heading[1].length
|
||||||
|
html.push(`<h${level}>${this.renderInline(heading[2])}</h${level}>`)
|
||||||
|
index += 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/^[-*]\s+/.test(trimmed)) {
|
||||||
|
const items = []
|
||||||
|
while (index < lines.length && /^[-*]\s+/.test(lines[index].trim())) {
|
||||||
|
items.push(lines[index].trim().replace(/^[-*]\s+/, ''))
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
html.push(`<ul>${items.map(item => `<li>${this.renderInline(item)}</li>`).join('')}</ul>`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/^\d+\.\s+/.test(trimmed)) {
|
||||||
|
const items = []
|
||||||
|
while (index < lines.length && /^\d+\.\s+/.test(lines[index].trim())) {
|
||||||
|
items.push(lines[index].trim().replace(/^\d+\.\s+/, ''))
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
html.push(`<ol>${items.map(item => `<li>${this.renderInline(item)}</li>`).join('')}</ol>`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
html.push(`<p>${this.renderInline(trimmed)}</p>`)
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return html.join('')
|
||||||
},
|
},
|
||||||
goBack() {
|
goBack() {
|
||||||
this.$router.back()
|
this.$router.back()
|
||||||
@ -275,8 +260,103 @@ print(response.json())`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.markdown-section {
|
||||||
|
line-height: 1.8;
|
||||||
|
|
||||||
|
::v-deep h1 {
|
||||||
|
margin: 0 0 24px;
|
||||||
|
color: #111827;
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep h2 {
|
||||||
|
margin: 34px 0 16px;
|
||||||
|
padding-left: 10px;
|
||||||
|
color: #1f2d3d;
|
||||||
|
font-size: 22px;
|
||||||
|
border-left: 3px solid #2f6bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep h3 {
|
||||||
|
margin: 26px 0 12px;
|
||||||
|
color: #1f2937;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep h4,
|
||||||
|
::v-deep h5 {
|
||||||
|
margin: 20px 0 10px;
|
||||||
|
color: #334155;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep p,
|
||||||
|
::v-deep li {
|
||||||
|
color: #475569;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep ul,
|
||||||
|
::v-deep ol {
|
||||||
|
padding-left: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep hr {
|
||||||
|
height: 1px;
|
||||||
|
margin: 28px 0;
|
||||||
|
background: #e5e7eb;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep code {
|
||||||
|
padding: 2px 6px;
|
||||||
|
color: #d6336c;
|
||||||
|
background: #fff1f2;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep pre code {
|
||||||
|
padding: 0;
|
||||||
|
color: inherit;
|
||||||
|
background: transparent;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .md-table-wrap {
|
||||||
|
width: 100%;
|
||||||
|
margin: 16px 0 24px;
|
||||||
|
overflow-x: auto;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep table {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 680px;
|
||||||
|
border-collapse: collapse;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep th,
|
||||||
|
::v-deep td {
|
||||||
|
padding: 10px 12px;
|
||||||
|
color: #475569;
|
||||||
|
font-size: 13px;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
border-right: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep th {
|
||||||
|
color: #1f2937;
|
||||||
|
font-weight: 700;
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
margin: 0;
|
margin: 14px 0 24px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
color: #e6edf3;
|
color: #e6edf3;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
|
|||||||
@ -0,0 +1,619 @@
|
|||||||
|
<template>
|
||||||
|
<div class="role-management-page">
|
||||||
|
<div class="page-head">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">ROLE MANAGEMENT</p>
|
||||||
|
<h1>角色管理</h1>
|
||||||
|
<span>统一管理系统用户与角色权限。</span>
|
||||||
|
</div>
|
||||||
|
<el-button type="primary" icon="el-icon-plus" class="add-role-btn" @click="openAddDialog">
|
||||||
|
添加角色
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-card">
|
||||||
|
<el-form :model="queryForm" inline class="filter-form">
|
||||||
|
<el-form-item label="用户名">
|
||||||
|
<el-input
|
||||||
|
v-model.trim="queryForm.username"
|
||||||
|
size="small"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入用户名"
|
||||||
|
@keyup.enter.native="handleSearch"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="手机号">
|
||||||
|
<el-input
|
||||||
|
v-model.trim="queryForm.mobile"
|
||||||
|
size="small"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入手机号"
|
||||||
|
@keyup.enter.native="handleSearch"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button size="small" icon="el-icon-search" type="primary" @click="handleSearch">搜索</el-button>
|
||||||
|
<el-button size="small" icon="el-icon-refresh-left" @click="resetSearch">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-card">
|
||||||
|
<el-table v-loading="loading" :data="roles" class="role-table" empty-text="暂无角色数据">
|
||||||
|
<el-table-column label="序号" width="80" align="center">
|
||||||
|
<template slot-scope="scope">{{ scope.$index + 1 }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="username" label="用户名" min-width="180">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<div class="role-name-cell">
|
||||||
|
<span class="role-avatar">{{ getAvatarText(scope.row.username) }}</span>
|
||||||
|
<div>
|
||||||
|
<strong>{{ scope.row.username }}</strong>
|
||||||
|
<small>{{ getRoleText(scope.row.roles) }}</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="email" label="邮箱" min-width="220" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="mobile" label="手机号" width="150" align="center" />
|
||||||
|
<el-table-column label="角色权限" width="180" align="center">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
{{ getRoleText(scope.row.roles) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="create_at" label="创建时间" width="180" align="center" />
|
||||||
|
<el-table-column label="操作" width="140" align="center">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-button type="text" size="mini" @click="openAssignDialog(scope.row)">分配角色</el-button>
|
||||||
|
<!-- <el-button type="text" size="mini" class="danger-text">删除</el-button> -->
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<div class="table-pagination">
|
||||||
|
<span class="pagination-info">共 {{ total }} 条</span>
|
||||||
|
<el-pagination
|
||||||
|
background
|
||||||
|
layout="prev, pager, next, jumper"
|
||||||
|
:current-page.sync="currentPage"
|
||||||
|
:page-size="pageSize"
|
||||||
|
:total="total"
|
||||||
|
@current-change="handlePageChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
:visible.sync="addDialogVisible"
|
||||||
|
append-to-body
|
||||||
|
width="520px"
|
||||||
|
custom-class="role-add-dialog"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
@close="resetAddForm"
|
||||||
|
>
|
||||||
|
<div slot="title" class="dialog-title">
|
||||||
|
<div class="dialog-icon">
|
||||||
|
<i class="el-icon-user-solid"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3>添加角色用户</h3>
|
||||||
|
<p>创建角色账号,并配置对应角色权限。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-form ref="addForm" :model="addForm" :rules="addRules" label-position="top" class="dialog-form">
|
||||||
|
<el-form-item label="用户名" prop="username">
|
||||||
|
<el-input v-model.trim="addForm.username" maxlength="30" placeholder="请输入用户名" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="密码" prop="password">
|
||||||
|
<el-input v-model.trim="addForm.password" type="password" show-password maxlength="30" placeholder="请输入密码" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="邮箱" prop="email">
|
||||||
|
<el-input v-model.trim="addForm.email" maxlength="60" placeholder="请输入邮箱" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="手机号" prop="mobile">
|
||||||
|
<el-input v-model.trim="addForm.mobile" maxlength="11" placeholder="请输入手机号" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<div slot="footer" class="dialog-footer">
|
||||||
|
<el-button @click="addDialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" class="submit-btn" @click="submitAddRole()">确认添加</el-button>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
:visible.sync="assignDialogVisible"
|
||||||
|
append-to-body
|
||||||
|
width="500px"
|
||||||
|
custom-class="role-add-dialog"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
@close="resetAssignForm"
|
||||||
|
>
|
||||||
|
<div slot="title" class="dialog-title">
|
||||||
|
<div class="dialog-icon">
|
||||||
|
<i class="el-icon-s-check"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3>分配角色</h3>
|
||||||
|
<p>为 {{ currentAssignUser.username || '当前用户' }} 配置可用角色权限。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-form ref="assignForm" :model="assignForm" :rules="assignRules" label-position="top" class="dialog-form">
|
||||||
|
<el-form-item label="角色权限" prop="roleid">
|
||||||
|
<el-select
|
||||||
|
v-model="assignForm.roleid"
|
||||||
|
multiple
|
||||||
|
collapse-tags
|
||||||
|
placeholder="请选择角色权限"
|
||||||
|
class="full-select"
|
||||||
|
filterable
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in roleOptions"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.role"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<div slot="footer" class="dialog-footer">
|
||||||
|
<el-button @click="assignDialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" class="submit-btn" @click="submitAssignRole">确认分配</el-button>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { addRoleAPI, assignRoleAPI, getListAPI, getRoleAPI } from '@/api/roleManagement';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'RoleManagement',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
queryForm: {
|
||||||
|
username: '',
|
||||||
|
mobile: ''
|
||||||
|
},
|
||||||
|
loading: false,
|
||||||
|
currentPage: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 0,
|
||||||
|
addDialogVisible: false,
|
||||||
|
assignDialogVisible: false,
|
||||||
|
currentAssignUser: {},
|
||||||
|
addForm: this.createEmptyRole(),
|
||||||
|
assignForm: {
|
||||||
|
roleid: []
|
||||||
|
},
|
||||||
|
roleOptions: [],
|
||||||
|
addRules: {
|
||||||
|
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
|
||||||
|
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
|
||||||
|
email: [
|
||||||
|
{ required: true, message: '请输入邮箱', trigger: 'blur' },
|
||||||
|
{ type: 'email', message: '请输入正确的邮箱', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
mobile: [
|
||||||
|
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
||||||
|
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
assignRules: {
|
||||||
|
roleid: [{ required: true, type: 'array', message: '请选择角色权限', trigger: 'change' }]
|
||||||
|
},
|
||||||
|
roles: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.getRoleList()
|
||||||
|
this.getUserList()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
createEmptyRole() {
|
||||||
|
return {
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
email: '',
|
||||||
|
mobile: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
openAddDialog() {
|
||||||
|
if (!this.roleOptions.length) this.getRoleList()
|
||||||
|
this.addDialogVisible = true
|
||||||
|
},
|
||||||
|
// 获取角色
|
||||||
|
async getRoleList() {
|
||||||
|
const orgType = sessionStorage.getItem('org_type') || sessionStorage.getItem('orgType') || ''
|
||||||
|
try {
|
||||||
|
const res = await getRoleAPI({ org_type: orgType })
|
||||||
|
if (res && res.status === true) {
|
||||||
|
this.roleOptions = this.normalizeRoleOptions(res.data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.roleOptions = []
|
||||||
|
} catch (error) {
|
||||||
|
this.roleOptions = []
|
||||||
|
this.$message.error('角色权限获取失败')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleSearch() {
|
||||||
|
this.currentPage = 1
|
||||||
|
this.getUserList()
|
||||||
|
},
|
||||||
|
resetSearch() {
|
||||||
|
this.queryForm = {
|
||||||
|
username: '',
|
||||||
|
mobile: ''
|
||||||
|
}
|
||||||
|
this.currentPage = 1
|
||||||
|
this.getUserList()
|
||||||
|
},
|
||||||
|
handlePageChange(page) {
|
||||||
|
this.currentPage = page
|
||||||
|
this.getUserList()
|
||||||
|
},
|
||||||
|
getAvatarText(username) {
|
||||||
|
return String(username || '-').slice(0, 1).toUpperCase()
|
||||||
|
},
|
||||||
|
getRoleText(roles) {
|
||||||
|
return Array.isArray(roles) && roles.length
|
||||||
|
? roles.map(item => item.name).join('、')
|
||||||
|
: '-'
|
||||||
|
},
|
||||||
|
normalizeRoleOptions(list) {
|
||||||
|
return Array.isArray(list)
|
||||||
|
? list.map(item => ({
|
||||||
|
...item,
|
||||||
|
role: item.role || item.name || ''
|
||||||
|
}))
|
||||||
|
: []
|
||||||
|
},
|
||||||
|
mergeRoleOptions(roles = []) {
|
||||||
|
const nextOptions = [...this.roleOptions]
|
||||||
|
roles.forEach(role => {
|
||||||
|
if (role && role.id && !nextOptions.some(item => item.id === role.id)) {
|
||||||
|
nextOptions.push({
|
||||||
|
id: role.id,
|
||||||
|
role: role.name || role.role || role.id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.roleOptions = nextOptions
|
||||||
|
},
|
||||||
|
getOrgId() {
|
||||||
|
return sessionStorage.getItem('orgid') || ''
|
||||||
|
},
|
||||||
|
async getUserList() {
|
||||||
|
this.loading = true
|
||||||
|
try {
|
||||||
|
const res = await getListAPI({
|
||||||
|
orgid: this.getOrgId(),
|
||||||
|
current_page: this.currentPage,
|
||||||
|
page_size: this.pageSize,
|
||||||
|
username: this.queryForm.username,
|
||||||
|
mobile: this.queryForm.mobile
|
||||||
|
})
|
||||||
|
if (res && res.status === true && res.data) {
|
||||||
|
this.roles = Array.isArray(res.data.rows) ? res.data.rows : []
|
||||||
|
this.total = Number(res.data.total_count || 0)
|
||||||
|
this.currentPage = Number(res.data.current_page || this.currentPage)
|
||||||
|
this.pageSize = Number(res.data.page_size || this.pageSize)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.roles = []
|
||||||
|
this.total = 0
|
||||||
|
} catch (error) {
|
||||||
|
this.roles = []
|
||||||
|
this.total = 0
|
||||||
|
this.$message.error('用户角色列表获取失败')
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resetAddForm() {
|
||||||
|
this.addForm = this.createEmptyRole()
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.addForm && this.$refs.addForm.clearValidate()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
resetAssignForm() {
|
||||||
|
this.currentAssignUser = {}
|
||||||
|
this.assignForm = {
|
||||||
|
roleid: []
|
||||||
|
}
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.assignForm && this.$refs.assignForm.clearValidate()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
submitAddRole() {
|
||||||
|
this.$refs.addForm.validate(async valid => {
|
||||||
|
if (!valid) return
|
||||||
|
await this.addUser()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 添加角色用户
|
||||||
|
async addUser() {
|
||||||
|
const orgType = sessionStorage.getItem('org_type') || sessionStorage.getItem('orgType') || ''
|
||||||
|
const data = {
|
||||||
|
username: this.addForm.username,
|
||||||
|
password: this.addForm.password,
|
||||||
|
email: this.addForm.email,
|
||||||
|
mobile: this.addForm.mobile,
|
||||||
|
orgid: this.getOrgId(),
|
||||||
|
org_type: orgType
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await addRoleAPI(data)
|
||||||
|
if (res && res.status === true) {
|
||||||
|
const now = new Date()
|
||||||
|
const createdAt = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`
|
||||||
|
this.roles.unshift({
|
||||||
|
username: this.addForm.username,
|
||||||
|
email: this.addForm.email,
|
||||||
|
mobile: this.addForm.mobile,
|
||||||
|
roles: [],
|
||||||
|
create_at: createdAt
|
||||||
|
})
|
||||||
|
this.total += 1
|
||||||
|
this.$message.success(res.msg || '角色用户添加成功')
|
||||||
|
this.addDialogVisible = false
|
||||||
|
this.getUserList()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$message.error((res && res.msg) || '角色用户添加失败')
|
||||||
|
} catch (error) {
|
||||||
|
this.$message.error('角色用户添加失败')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async openAssignDialog(row) {
|
||||||
|
this.currentAssignUser = row || {}
|
||||||
|
if (!this.roleOptions.length) await this.getRoleList()
|
||||||
|
this.mergeRoleOptions(row.roles || [])
|
||||||
|
this.assignForm.roleid = Array.isArray(row.roles) ? row.roles.map(item => item.id) : []
|
||||||
|
this.assignDialogVisible = true
|
||||||
|
},
|
||||||
|
submitAssignRole() {
|
||||||
|
this.$refs.assignForm.validate(async valid => {
|
||||||
|
if (!valid) return
|
||||||
|
try {
|
||||||
|
const res = await assignRoleAPI({
|
||||||
|
userid: this.currentAssignUser.id,
|
||||||
|
roleid: this.assignForm.roleid
|
||||||
|
})
|
||||||
|
if (res && res.status === true) {
|
||||||
|
this.$message.success(res.msg || '角色分配成功')
|
||||||
|
this.assignDialogVisible = false
|
||||||
|
this.getUserList()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$message.error((res && res.msg) || '角色分配失败')
|
||||||
|
} catch (error) {
|
||||||
|
this.$message.error('角色分配失败')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.role-management-page {
|
||||||
|
min-height: calc(100vh - 84px);
|
||||||
|
padding: 24px;
|
||||||
|
background: #f3f7ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-head {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eyebrow {
|
||||||
|
margin: 0 0 6px;
|
||||||
|
color: #3b82f6;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-head h1 {
|
||||||
|
margin: 0;
|
||||||
|
color: #111827;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-head span {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 8px;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-role-btn {
|
||||||
|
height: 38px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-card,
|
||||||
|
.table-card {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #edf0f5;
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 10px 28px rgba(15, 23, 42, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-card {
|
||||||
|
padding: 16px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-form {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-form ::v-deep .el-form-item {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-form ::v-deep .el-input__inner {
|
||||||
|
border-radius: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-form ::v-deep .el-button--small {
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-card {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-pagination {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 14px 18px;
|
||||||
|
border-top: 1px solid #edf0f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-info {
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-table ::v-deep th {
|
||||||
|
background: #f8fafc;
|
||||||
|
color: #64748b;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-name-cell {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-avatar {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #2563eb;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 800;
|
||||||
|
background: #eff6ff;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-name-cell strong {
|
||||||
|
display: block;
|
||||||
|
color: #1f2937;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-name-cell small {
|
||||||
|
display: block;
|
||||||
|
margin-top: 3px;
|
||||||
|
color: #94a3b8;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger-text {
|
||||||
|
color: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .role-add-dialog {
|
||||||
|
border-radius: 24px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .role-add-dialog .el-dialog__header {
|
||||||
|
padding: 26px 30px 18px;
|
||||||
|
border-bottom: 1px solid #edf0f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .role-add-dialog .el-dialog__body {
|
||||||
|
padding: 24px 30px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .role-add-dialog .el-dialog__footer {
|
||||||
|
padding: 16px 30px 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-icon {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #2563eb;
|
||||||
|
font-size: 20px;
|
||||||
|
background: linear-gradient(135deg, #eff6ff, #dbeafe);
|
||||||
|
border-radius: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-title h3 {
|
||||||
|
margin: 0;
|
||||||
|
color: #111827;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-title p {
|
||||||
|
margin: 5px 0 0;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-form ::v-deep .el-form-item {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-form ::v-deep .el-form-item__label {
|
||||||
|
padding-bottom: 7px;
|
||||||
|
color: #334155;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-form ::v-deep .el-input__inner,
|
||||||
|
.dialog-form ::v-deep .el-textarea__inner {
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-select {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer .el-button {
|
||||||
|
min-width: 96px;
|
||||||
|
height: 38px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn {
|
||||||
|
background: #2563eb;
|
||||||
|
border-color: #2563eb;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
x
Reference in New Issue
Block a user