feat: 租户应用订阅页面优化 - 标签筛选、中文显示、快捷复制链接
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, onMounted, computed, watch } from 'vue'
|
import { ref, reactive, onMounted, computed, watch } from 'vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { Delete, Plus } from '@element-plus/icons-vue'
|
import { Delete, Plus, CopyDocument } from '@element-plus/icons-vue'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
|
||||||
@@ -19,9 +19,11 @@ const query = reactive({
|
|||||||
|
|
||||||
// 租户列表
|
// 租户列表
|
||||||
const tenantList = ref([])
|
const tenantList = ref([])
|
||||||
|
const tenantMap = ref({}) // code -> name 映射
|
||||||
|
|
||||||
// 应用列表(从应用管理获取)
|
// 应用列表(从应用管理获取)
|
||||||
const appList = ref([])
|
const appList = ref([])
|
||||||
|
const appMap = ref({}) // app_code -> app_name 映射
|
||||||
const appRequireJssdk = ref({}) // app_code -> require_jssdk
|
const appRequireJssdk = ref({}) // app_code -> require_jssdk
|
||||||
const appBaseUrl = ref({}) // app_code -> base_url
|
const appBaseUrl = ref({}) // app_code -> base_url
|
||||||
const appConfigSchema = ref({}) // app_code -> config_schema
|
const appConfigSchema = ref({}) // app_code -> config_schema
|
||||||
@@ -128,22 +130,42 @@ async function fetchTenants() {
|
|||||||
try {
|
try {
|
||||||
const res = await api.get('/api/tenants', { params: { size: 1000 } })
|
const res = await api.get('/api/tenants', { params: { size: 1000 } })
|
||||||
tenantList.value = res.data.items || []
|
tenantList.value = res.data.items || []
|
||||||
|
// 构建 code -> name 映射
|
||||||
|
const map = {}
|
||||||
|
tenantList.value.forEach(t => {
|
||||||
|
map[t.code] = t.name
|
||||||
|
})
|
||||||
|
tenantMap.value = map
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('获取租户列表失败:', e)
|
console.error('获取租户列表失败:', e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取租户中文名
|
||||||
|
function getTenantName(code) {
|
||||||
|
return tenantMap.value[code] || code
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取应用中文名
|
||||||
|
function getAppName(code) {
|
||||||
|
return appMap.value[code] || code
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchApps() {
|
async function fetchApps() {
|
||||||
try {
|
try {
|
||||||
const res = await api.get('/api/apps', { params: { size: 100 } })
|
const res = await api.get('/api/apps', { params: { size: 100 } })
|
||||||
const apps = res.data.items || []
|
const apps = res.data.items || []
|
||||||
appList.value = apps.map(a => ({ app_code: a.app_code, app_name: a.app_name }))
|
appList.value = apps.map(a => ({ app_code: a.app_code, app_name: a.app_name }))
|
||||||
|
|
||||||
|
// 构建 app_code -> app_name 映射
|
||||||
|
const map = {}
|
||||||
for (const app of apps) {
|
for (const app of apps) {
|
||||||
|
map[app.app_code] = app.app_name
|
||||||
appRequireJssdk.value[app.app_code] = app.require_jssdk || false
|
appRequireJssdk.value[app.app_code] = app.require_jssdk || false
|
||||||
appBaseUrl.value[app.app_code] = app.base_url || ''
|
appBaseUrl.value[app.app_code] = app.base_url || ''
|
||||||
appConfigSchema.value[app.app_code] = app.config_schema || []
|
appConfigSchema.value[app.app_code] = app.config_schema || []
|
||||||
}
|
}
|
||||||
|
appMap.value = map
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('获取应用列表失败:', e)
|
console.error('获取应用列表失败:', e)
|
||||||
}
|
}
|
||||||
@@ -338,6 +360,36 @@ async function handleViewToken(row) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 快速选择租户标签
|
||||||
|
function selectTenant(code) {
|
||||||
|
if (query.tenant_id === code) {
|
||||||
|
query.tenant_id = '' // 再次点击取消选中
|
||||||
|
} else {
|
||||||
|
query.tenant_id = code
|
||||||
|
}
|
||||||
|
handleSearch()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制带 token 的链接
|
||||||
|
async function copyTokenLink(row) {
|
||||||
|
try {
|
||||||
|
const res = await api.get(`/api/tenant-apps/${row.id}/token`)
|
||||||
|
const token = res.data.access_token
|
||||||
|
const baseUrl = res.data.base_url || appBaseUrl.value[row.app_code] || ''
|
||||||
|
|
||||||
|
if (!baseUrl) {
|
||||||
|
ElMessage.warning('该应用未配置访问地址')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = `${baseUrl}?token=${token}`
|
||||||
|
await navigator.clipboard.writeText(url)
|
||||||
|
ElMessage.success('链接已复制')
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.error('复制失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchTenants()
|
fetchTenants()
|
||||||
fetchApps()
|
fetchApps()
|
||||||
@@ -361,16 +413,32 @@ onMounted(() => {
|
|||||||
</el-alert>
|
</el-alert>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 租户快速筛选标签 -->
|
||||||
|
<div class="tenant-tags">
|
||||||
|
<span class="tag-label">租户筛选:</span>
|
||||||
|
<el-tag
|
||||||
|
v-for="tenant in tenantList"
|
||||||
|
:key="tenant.code"
|
||||||
|
:type="query.tenant_id === tenant.code ? '' : 'info'"
|
||||||
|
:effect="query.tenant_id === tenant.code ? 'dark' : 'plain'"
|
||||||
|
class="tenant-tag"
|
||||||
|
@click="selectTenant(tenant.code)"
|
||||||
|
>
|
||||||
|
{{ tenant.name }}
|
||||||
|
</el-tag>
|
||||||
|
<el-tag
|
||||||
|
v-if="query.tenant_id"
|
||||||
|
type="danger"
|
||||||
|
effect="plain"
|
||||||
|
class="tenant-tag clear-tag"
|
||||||
|
@click="selectTenant('')"
|
||||||
|
>
|
||||||
|
清除筛选
|
||||||
|
</el-tag>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 搜索栏 -->
|
<!-- 搜索栏 -->
|
||||||
<div class="search-bar">
|
<div class="search-bar">
|
||||||
<el-select v-model="query.tenant_id" placeholder="选择租户" clearable filterable style="width: 200px">
|
|
||||||
<el-option
|
|
||||||
v-for="tenant in tenantList"
|
|
||||||
:key="tenant.code"
|
|
||||||
:label="`${tenant.name} (${tenant.code})`"
|
|
||||||
:value="tenant.code"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
<el-select v-model="query.app_code" placeholder="选择应用" clearable style="width: 150px">
|
<el-select v-model="query.app_code" placeholder="选择应用" clearable style="width: 150px">
|
||||||
<el-option v-for="app in appList" :key="app.app_code" :label="app.app_name" :value="app.app_code" />
|
<el-option v-for="app in appList" :key="app.app_code" :label="app.app_name" :value="app.app_code" />
|
||||||
</el-select>
|
</el-select>
|
||||||
@@ -380,10 +448,33 @@ onMounted(() => {
|
|||||||
<!-- 表格 -->
|
<!-- 表格 -->
|
||||||
<el-table v-loading="loading" :data="tableData" style="width: 100%">
|
<el-table v-loading="loading" :data="tableData" style="width: 100%">
|
||||||
<el-table-column prop="id" label="ID" width="60" />
|
<el-table-column prop="id" label="ID" width="60" />
|
||||||
<el-table-column prop="tenant_id" label="租户ID" width="120" />
|
<el-table-column label="租户" width="120">
|
||||||
<el-table-column prop="app_code" label="应用代码" width="150" />
|
<template #default="{ row }">
|
||||||
<el-table-column prop="app_name" label="备注名称" width="150" />
|
<span class="cell-name">{{ getTenantName(row.tenant_id) }}</span>
|
||||||
<el-table-column label="企微应用" width="180">
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="应用" width="150">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span class="cell-name">{{ getAppName(row.app_code) }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="app_name" label="备注" width="120" />
|
||||||
|
<el-table-column label="快捷链接" width="100">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button
|
||||||
|
v-if="row.access_token && appBaseUrl[row.app_code]"
|
||||||
|
type="primary"
|
||||||
|
link
|
||||||
|
size="small"
|
||||||
|
@click="copyTokenLink(row)"
|
||||||
|
>
|
||||||
|
<el-icon><CopyDocument /></el-icon>
|
||||||
|
复制
|
||||||
|
</el-button>
|
||||||
|
<span v-else style="color: #c0c4cc; font-size: 12px">-</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="企微应用" width="140">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<template v-if="row.wechat_app">
|
<template v-if="row.wechat_app">
|
||||||
<el-tag type="success" size="small">{{ row.wechat_app.name }}</el-tag>
|
<el-tag type="success" size="small">{{ row.wechat_app.name }}</el-tag>
|
||||||
@@ -391,13 +482,7 @@ onMounted(() => {
|
|||||||
<el-tag v-else type="info" size="small">未关联</el-tag>
|
<el-tag v-else type="info" size="small">未关联</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="Token 状态" width="120">
|
<el-table-column label="配置" width="80">
|
||||||
<template #default="{ row }">
|
|
||||||
<el-tag v-if="row.access_token" type="success" size="small">已生成</el-tag>
|
|
||||||
<el-tag v-else type="danger" size="small">未生成</el-tag>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="自定义配置" width="100">
|
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag v-if="row.custom_configs && row.custom_configs.length > 0" type="primary" size="small">
|
<el-tag v-if="row.custom_configs && row.custom_configs.length > 0" type="primary" size="small">
|
||||||
{{ row.custom_configs.length }} 项
|
{{ row.custom_configs.length }} 项
|
||||||
@@ -405,18 +490,18 @@ onMounted(() => {
|
|||||||
<span v-else style="color: #909399; font-size: 12px">-</span>
|
<span v-else style="color: #909399; font-size: 12px">-</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="状态" width="80">
|
<el-table-column label="状态" width="70">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag :type="row.status === 1 ? 'success' : 'info'" size="small">
|
<el-tag :type="row.status === 1 ? 'success' : 'info'" size="small">
|
||||||
{{ row.status === 1 ? '启用' : '禁用' }}
|
{{ row.status === 1 ? '启用' : '禁用' }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="280" fixed="right">
|
<el-table-column label="操作" width="240" fixed="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button v-if="authStore.isOperator" type="primary" link size="small" @click="handleEdit(row)">编辑</el-button>
|
<el-button v-if="authStore.isOperator" type="primary" link size="small" @click="handleEdit(row)">编辑</el-button>
|
||||||
<el-button v-if="authStore.isOperator" type="success" link size="small" @click="handleViewToken(row)">查看Token</el-button>
|
<el-button v-if="authStore.isOperator" type="success" link size="small" @click="handleViewToken(row)">Token</el-button>
|
||||||
<el-button v-if="authStore.isOperator" type="warning" link size="small" @click="handleRegenerateToken(row)">重置Token</el-button>
|
<el-button v-if="authStore.isOperator" type="warning" link size="small" @click="handleRegenerateToken(row)">重置</el-button>
|
||||||
<el-button v-if="authStore.isOperator" type="danger" link size="small" @click="handleDelete(row)">删除</el-button>
|
<el-button v-if="authStore.isOperator" type="danger" link size="small" @click="handleDelete(row)">删除</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -653,6 +738,43 @@ onMounted(() => {
|
|||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 租户标签筛选 */
|
||||||
|
.tenant-tags {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: #f5f7fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-label {
|
||||||
|
color: #606266;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tenant-tag {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tenant-tag:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-tag {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell-name {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
.token-dialog-content {
|
.token-dialog-content {
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user