Merge branch 'develop' of https://gitee.com/youlaiorg/vue3-element-admin into develop
This commit is contained in:
@@ -13,9 +13,9 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="provider" label="AI提供商">
|
||||
<el-form-item prop="aiProvider" label="AI提供商">
|
||||
<el-select
|
||||
v-model="queryParams.provider"
|
||||
v-model="queryParams.aiProvider"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
style="width: 140px"
|
||||
@@ -27,9 +27,9 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="model" label="AI模型">
|
||||
<el-form-item prop="aiModel" label="AI模型">
|
||||
<el-input
|
||||
v-model="queryParams.model"
|
||||
v-model="queryParams.aiModel"
|
||||
placeholder="如 qwen-plus"
|
||||
clearable
|
||||
style="width: 160px"
|
||||
@@ -37,15 +37,15 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="parseSuccess" label="解析状态">
|
||||
<el-form-item prop="parseStatus" label="解析状态">
|
||||
<el-select
|
||||
v-model="queryParams.parseSuccess"
|
||||
v-model="queryParams.parseStatus"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
style="width: 140px"
|
||||
>
|
||||
<el-option label="成功" :value="true" />
|
||||
<el-option label="失败" :value="false" />
|
||||
<el-option label="成功" :value="1" />
|
||||
<el-option label="失败" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
@@ -56,21 +56,9 @@
|
||||
clearable
|
||||
style="width: 140px"
|
||||
>
|
||||
<el-option label="待执行" value="pending" />
|
||||
<el-option label="成功" value="success" />
|
||||
<el-option label="失败" value="failed" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="isDangerous" label="风险操作">
|
||||
<el-select
|
||||
v-model="queryParams.isDangerous"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
style="width: 140px"
|
||||
>
|
||||
<el-option label="是" :value="true" />
|
||||
<el-option label="否" :value="false" />
|
||||
<el-option label="待执行" :value="0" />
|
||||
<el-option label="成功" :value="1" />
|
||||
<el-option label="失败" :value="-1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
@@ -117,29 +105,27 @@
|
||||
min-width="160"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column label="AI提供商" prop="provider" width="120" />
|
||||
<el-table-column label="AI模型" prop="model" width="160" show-overflow-tooltip />
|
||||
<el-table-column label="AI提供商" prop="aiProvider" width="120" />
|
||||
<el-table-column label="AI模型" prop="aiModel" width="160" show-overflow-tooltip />
|
||||
<el-table-column label="解析状态" width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.parseSuccess ? 'success' : 'danger'" size="small">
|
||||
{{ row.parseSuccess ? "成功" : "失败" }}
|
||||
<el-tag :type="row.parseStatus === 1 ? 'success' : 'danger'" size="small">
|
||||
{{ row.parseStatus === 1 ? "成功" : "失败" }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="执行状态" width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.executeStatus" :type="statusTagType[row.executeStatus]" size="small">
|
||||
{{ statusText[row.executeStatus] }}
|
||||
<el-tag
|
||||
v-if="row.executeStatus !== null && row.executeStatus !== undefined"
|
||||
:type="getExecuteStatusTagType(row.executeStatus)"
|
||||
size="small"
|
||||
>
|
||||
{{ getExecuteStatusText(row.executeStatus) }}
|
||||
</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="风险" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.isDangerous" type="warning" size="small">风险</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="置信度" prop="confidence" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.confidence !== undefined && row.confidence !== null">
|
||||
@@ -148,8 +134,7 @@
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="解析耗时(ms)" prop="parseTime" width="120" align="center" />
|
||||
<el-table-column label="执行耗时(ms)" prop="executionTime" width="120" align="center" />
|
||||
<el-table-column label="解析耗时(ms)" prop="parseDurationMs" width="120" align="center" />
|
||||
<el-table-column label="IP地址" prop="ipAddress" width="140" />
|
||||
<el-table-column label="操作" width="100" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
@@ -180,15 +165,15 @@
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="AI提供商">
|
||||
{{ currentRow.provider || "-" }}
|
||||
{{ currentRow.aiProvider || "-" }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="AI模型">
|
||||
{{ currentRow.model || "-" }}
|
||||
{{ currentRow.aiModel || "-" }}
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="解析状态">
|
||||
<el-tag :type="currentRow.parseSuccess ? 'success' : 'danger'" size="small">
|
||||
{{ currentRow.parseSuccess ? "成功" : "失败" }}
|
||||
<el-tag :type="currentRow.parseStatus === 1 ? 'success' : 'danger'" size="small">
|
||||
{{ currentRow.parseStatus === 1 ? "成功" : "失败" }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="置信度">
|
||||
@@ -199,11 +184,10 @@
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="解析耗时">
|
||||
{{ formatNumber(currentRow.parseTime) }} ms
|
||||
{{ formatNumber(currentRow.parseDurationMs) }} ms
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="Token统计">
|
||||
输入 {{ currentRow.inputTokens || 0 }} / 输出 {{ currentRow.outputTokens || 0 }} / 总计
|
||||
{{ currentRow.totalTokens || 0 }}
|
||||
输入 {{ currentRow.inputTokens || 0 }} / 输出 {{ currentRow.outputTokens || 0 }}
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="原始命令" :span="2">
|
||||
@@ -232,22 +216,15 @@
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="执行状态">
|
||||
<el-tag
|
||||
v-if="currentRow.executeStatus"
|
||||
:type="statusTagType[currentRow.executeStatus]"
|
||||
v-if="currentRow.executeStatus !== null && currentRow.executeStatus !== undefined"
|
||||
:type="getExecuteStatusTagType(currentRow.executeStatus)"
|
||||
size="small"
|
||||
>
|
||||
{{ statusText[currentRow.executeStatus] }}
|
||||
{{ getExecuteStatusText(currentRow.executeStatus) }}
|
||||
</el-tag>
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="执行耗时">
|
||||
{{ formatNumber(currentRow.executionTime) }} ms
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="影响行数">
|
||||
{{ formatNumber(currentRow.affectedRows) }}
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item v-if="currentRow.functionArguments" label="执行参数" :span="2">
|
||||
<el-input
|
||||
:model-value="formatJson(currentRow.functionArguments)"
|
||||
@@ -257,39 +234,13 @@
|
||||
/>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item v-if="currentRow.executeResult" label="执行结果" :span="2">
|
||||
<el-input
|
||||
:model-value="formatJson(currentRow.executeResult)"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
readonly
|
||||
/>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item v-if="currentRow.executeErrorMessage" label="执行错误" :span="2">
|
||||
<el-alert :title="currentRow.executeErrorMessage" type="error" :closable="false" />
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="风险操作">
|
||||
<el-tag v-if="currentRow.isDangerous" type="warning" size="small">风险操作</el-tag>
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="是否确认">
|
||||
<span v-if="currentRow.requiresConfirmation">
|
||||
{{ currentRow.userConfirmed ? "已确认" : "待确认" }}
|
||||
</span>
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="IP地址">
|
||||
{{ currentRow.ipAddress || "-" }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="页面路由">
|
||||
{{ currentRow.currentRoute || "-" }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="User-Agent" :span="2">
|
||||
{{ currentRow.userAgent || "-" }}
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ currentRow.createTime }}
|
||||
@@ -297,9 +248,6 @@
|
||||
<el-descriptions-item label="更新时间">
|
||||
{{ currentRow.updateTime || "-" }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="备注" :span="2">
|
||||
{{ currentRow.remark || "-" }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<template #footer>
|
||||
@@ -327,11 +275,10 @@ const queryParams = reactive<AiCommandRecordPageQuery>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
keywords: "",
|
||||
provider: "",
|
||||
model: "",
|
||||
parseSuccess: undefined,
|
||||
executeStatus: "",
|
||||
isDangerous: undefined,
|
||||
aiProvider: "",
|
||||
aiModel: "",
|
||||
parseStatus: undefined,
|
||||
executeStatus: undefined,
|
||||
createTime: ["", ""],
|
||||
});
|
||||
|
||||
@@ -340,17 +287,31 @@ const pageData = ref<AiCommandRecordVO[]>([]);
|
||||
const detailDialogVisible = ref(false);
|
||||
const currentRow = ref<AiCommandRecordVO>();
|
||||
|
||||
const statusText: Record<string, string> = {
|
||||
pending: "待执行",
|
||||
success: "成功",
|
||||
failed: "失败",
|
||||
};
|
||||
function getExecuteStatusText(status: number): string {
|
||||
switch (status) {
|
||||
case 0:
|
||||
return "待执行";
|
||||
case 1:
|
||||
return "成功";
|
||||
case -1:
|
||||
return "失败";
|
||||
default:
|
||||
return "-";
|
||||
}
|
||||
}
|
||||
|
||||
const statusTagType: Record<string, "info" | "success" | "danger"> = {
|
||||
pending: "info",
|
||||
success: "success",
|
||||
failed: "danger",
|
||||
};
|
||||
function getExecuteStatusTagType(status: number): "info" | "success" | "danger" {
|
||||
switch (status) {
|
||||
case 0:
|
||||
return "info";
|
||||
case 1:
|
||||
return "success";
|
||||
case -1:
|
||||
return "danger";
|
||||
default:
|
||||
return "info";
|
||||
}
|
||||
}
|
||||
|
||||
function fetchData() {
|
||||
loading.value = true;
|
||||
|
||||
@@ -86,6 +86,36 @@
|
||||
</el-link>
|
||||
</div>
|
||||
|
||||
<!-- 租户选择对话框 -->
|
||||
<el-dialog
|
||||
v-model="tenantDialogVisible"
|
||||
title="选择登录租户"
|
||||
width="500px"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
:show-close="false"
|
||||
>
|
||||
<div class="tenant-select-content">
|
||||
<p class="tenant-select-tip">检测到你的账号属于多个租户,请选择登录租户:</p>
|
||||
<el-radio-group v-model="selectedTenantId" class="tenant-radio-group">
|
||||
<el-radio
|
||||
v-for="tenant in pendingTenants"
|
||||
:key="tenant.id"
|
||||
:label="tenant.id"
|
||||
class="tenant-radio"
|
||||
>
|
||||
{{ tenant.name }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="tenantDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :disabled="!selectedTenantId" @click="handleTenantSelected">
|
||||
继续
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 第三方登录 -->
|
||||
<div class="third-party-login">
|
||||
<div class="divider-container">
|
||||
@@ -112,11 +142,12 @@
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import type { FormInstance } from "element-plus";
|
||||
import AuthAPI, { type LoginFormData } from "@/api/auth-api";
|
||||
import AuthAPI, { type LoginRequest } from "@/api/auth-api";
|
||||
import router from "@/router";
|
||||
import { useUserStore } from "@/store";
|
||||
import CommonWrapper from "@/components/CommonWrapper/index.vue";
|
||||
import { AuthStorage } from "@/utils/auth";
|
||||
import { ApiCodeEnum } from "@/enums/api/code-enum";
|
||||
|
||||
const { t } = useI18n();
|
||||
const userStore = useUserStore();
|
||||
@@ -132,11 +163,14 @@ const isCapsLock = ref(false);
|
||||
const captchaBase64 = ref();
|
||||
// 记住我
|
||||
const rememberMe = AuthStorage.getRememberMe();
|
||||
// 租户选择对话框
|
||||
const tenantDialogVisible = ref(false);
|
||||
const selectedTenantId = ref<number | null>(null);
|
||||
|
||||
const loginFormData = ref<LoginFormData>({
|
||||
const loginFormData = ref<LoginRequest>({
|
||||
username: "admin",
|
||||
password: "123456",
|
||||
captchaKey: "",
|
||||
captchaId: "",
|
||||
captchaCode: "",
|
||||
rememberMe,
|
||||
});
|
||||
@@ -178,12 +212,15 @@ function getCaptcha() {
|
||||
codeLoading.value = true;
|
||||
AuthAPI.getCaptcha()
|
||||
.then((data) => {
|
||||
loginFormData.value.captchaKey = data.captchaKey;
|
||||
loginFormData.value.captchaId = data.captchaId;
|
||||
captchaBase64.value = data.captchaBase64;
|
||||
})
|
||||
.finally(() => (codeLoading.value = false));
|
||||
}
|
||||
|
||||
// 待选择的租户列表
|
||||
const pendingTenants = ref<Array<{ id: number; name: string }>>([]);
|
||||
|
||||
/**
|
||||
* 登录提交
|
||||
*/
|
||||
@@ -196,14 +233,52 @@ async function handleLoginSubmit() {
|
||||
loading.value = true;
|
||||
|
||||
// 2. 执行登录
|
||||
await userStore.login(loginFormData.value);
|
||||
try {
|
||||
await userStore.login(loginFormData.value);
|
||||
// 登录成功,跳转到目标页面
|
||||
const redirectPath = (route.query.redirect as string) || "/";
|
||||
await router.push(decodeURIComponent(redirectPath));
|
||||
} catch (error: any) {
|
||||
// 检查是否是 choose_tenant 响应
|
||||
if (error?.code === ApiCodeEnum.CHOOSE_TENANT && error?.data?.tenants) {
|
||||
// 需要选择租户
|
||||
pendingTenants.value = error.data.tenants;
|
||||
tenantDialogVisible.value = true;
|
||||
return; // 等待用户选择租户
|
||||
}
|
||||
// 其他错误,刷新验证码并显示错误
|
||||
getCaptcha();
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
// 统一错误处理
|
||||
console.error("登录失败:", error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 租户选择确认后的处理
|
||||
*/
|
||||
async function handleTenantSelected() {
|
||||
if (!selectedTenantId.value) {
|
||||
ElMessage.warning("请选择租户");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
// 使用选中的租户ID重新登录(将 tenantId 设置到表单数据中)
|
||||
const loginData = { ...loginFormData.value, tenantId: selectedTenantId.value };
|
||||
await userStore.login(loginData);
|
||||
// 登录成功,关闭对话框并跳转
|
||||
tenantDialogVisible.value = false;
|
||||
const redirectPath = (route.query.redirect as string) || "/";
|
||||
|
||||
await router.push(decodeURIComponent(redirectPath));
|
||||
} catch (error) {
|
||||
// 4. 统一错误处理
|
||||
getCaptcha(); // 刷新验证码
|
||||
// 登录失败,刷新验证码
|
||||
getCaptcha();
|
||||
console.error("登录失败:", error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
@@ -257,4 +332,42 @@ function toOtherForm(type: "register" | "resetPwd") {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tenant-select-content {
|
||||
padding: 20px 0;
|
||||
|
||||
.tenant-select-tip {
|
||||
margin: 0 0 20px;
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
|
||||
.tenant-radio-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
|
||||
.tenant-radio {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
border-color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
:deep(.el-radio__input.is-checked) {
|
||||
+ .el-radio__label {
|
||||
font-weight: 500;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
import type { FormInstance } from "element-plus";
|
||||
import { Lock } from "@element-plus/icons-vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import AuthAPI, { type LoginFormData } from "@/api/auth-api";
|
||||
import AuthAPI, { type LoginRequest } from "@/api/auth-api";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -113,7 +113,7 @@ const isCapsLock = ref(false); // 是否大写锁定
|
||||
const captchaBase64 = ref(); // 验证码图片Base64字符串
|
||||
const isRead = ref(false);
|
||||
|
||||
interface Model extends LoginFormData {
|
||||
interface Model extends LoginRequest {
|
||||
confirmPassword: string;
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ const model = ref<Model>({
|
||||
username: "admin",
|
||||
password: "123456",
|
||||
confirmPassword: "",
|
||||
captchaKey: "",
|
||||
captchaId: "",
|
||||
captchaCode: "",
|
||||
rememberMe: false,
|
||||
});
|
||||
@@ -182,7 +182,7 @@ function getCaptcha() {
|
||||
codeLoading.value = true;
|
||||
AuthAPI.getCaptcha()
|
||||
.then((data) => {
|
||||
model.value.captchaKey = data.captchaKey;
|
||||
model.value.captchaId = data.captchaId;
|
||||
captchaBase64.value = data.captchaBase64;
|
||||
})
|
||||
.finally(() => (codeLoading.value = false));
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<div class="table-section__toolbar">
|
||||
<div class="table-section__toolbar--actions">
|
||||
<el-button
|
||||
v-hasPerm="['sys:config:add']"
|
||||
v-hasPerm="['sys:config:create']"
|
||||
type="success"
|
||||
icon="plus"
|
||||
@click="handleOpenDialog()"
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<div class="table-section__toolbar">
|
||||
<div class="table-section__toolbar--actions">
|
||||
<el-button
|
||||
v-hasPerm="['sys:dept:add']"
|
||||
v-hasPerm="['sys:dept:create']"
|
||||
type="success"
|
||||
icon="plus"
|
||||
@click="handleOpenDialog()"
|
||||
@@ -74,7 +74,7 @@
|
||||
<el-table-column label="操作" fixed="right" align="left" width="200">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-hasPerm="['sys:dept:add']"
|
||||
v-hasPerm="['sys:dept:create']"
|
||||
type="primary"
|
||||
link
|
||||
size="small"
|
||||
@@ -84,7 +84,7 @@
|
||||
新增
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPerm="['sys:dept:edit']"
|
||||
v-hasPerm="['sys:dept:update']"
|
||||
type="primary"
|
||||
link
|
||||
size="small"
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<div class="table-section__toolbar">
|
||||
<div class="table-section__toolbar--actions">
|
||||
<el-button
|
||||
v-hasPerm="['sys:menu:add']"
|
||||
v-hasPerm="['sys:menu:create']"
|
||||
type="success"
|
||||
icon="plus"
|
||||
@click="handleOpenDialog('0')"
|
||||
@@ -64,7 +64,6 @@
|
||||
<el-tag v-if="scope.row.type === MenuTypeEnum.CATALOG" type="warning">目录</el-tag>
|
||||
<el-tag v-if="scope.row.type === MenuTypeEnum.MENU" type="success">菜单</el-tag>
|
||||
<el-tag v-if="scope.row.type === MenuTypeEnum.BUTTON" type="danger">按钮</el-tag>
|
||||
<el-tag v-if="scope.row.type === MenuTypeEnum.EXTLINK" type="info">外链</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="路由名称" align="left" width="150" prop="routeName" />
|
||||
@@ -82,7 +81,7 @@
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-if="scope.row.type == MenuTypeEnum.CATALOG || scope.row.type == MenuTypeEnum.MENU"
|
||||
v-hasPerm="['sys:menu:add']"
|
||||
v-hasPerm="['sys:menu:create']"
|
||||
type="primary"
|
||||
link
|
||||
size="small"
|
||||
@@ -93,7 +92,7 @@
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
v-hasPerm="['sys:menu:edit']"
|
||||
v-hasPerm="['sys:menu:update']"
|
||||
type="primary"
|
||||
link
|
||||
size="small"
|
||||
@@ -144,15 +143,10 @@
|
||||
<el-radio :value="MenuTypeEnum.CATALOG">目录</el-radio>
|
||||
<el-radio :value="MenuTypeEnum.MENU">菜单</el-radio>
|
||||
<el-radio :value="MenuTypeEnum.BUTTON">按钮</el-radio>
|
||||
<el-radio :value="MenuTypeEnum.EXTLINK">外链</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="formData.type == MenuTypeEnum.EXTLINK" label="外链地址" prop="path">
|
||||
<el-input v-model="formData.routePath" placeholder="请输入外链完整路径" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="formData.type == MenuTypeEnum.MENU" prop="routeName">
|
||||
<el-form-item v-if="formData.type == MenuTypeEnum.MENU && !isExternalLink" prop="routeName">
|
||||
<template #label>
|
||||
<div class="flex-y-center">
|
||||
路由名称
|
||||
@@ -192,10 +186,10 @@
|
||||
v-model="formData.routePath"
|
||||
placeholder="system"
|
||||
/>
|
||||
<el-input v-else v-model="formData.routePath" placeholder="user" />
|
||||
<el-input v-else v-model="formData.routePath" placeholder="user 或 https://example.com" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="formData.type == MenuTypeEnum.MENU" prop="component">
|
||||
<el-form-item v-if="formData.type == MenuTypeEnum.MENU && !isExternalLink" prop="component">
|
||||
<template #label>
|
||||
<div class="flex-y-center">
|
||||
组件路径
|
||||
@@ -216,7 +210,7 @@
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="formData.type == MenuTypeEnum.MENU">
|
||||
<el-form-item v-if="formData.type == MenuTypeEnum.MENU && !isExternalLink">
|
||||
<template #label>
|
||||
<div class="flex-y-center">
|
||||
路由参数
|
||||
@@ -298,7 +292,10 @@
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="formData.type === MenuTypeEnum.MENU" label="缓存页面">
|
||||
<el-form-item
|
||||
v-if="formData.type === MenuTypeEnum.MENU && !isExternalLink"
|
||||
label="缓存页面"
|
||||
>
|
||||
<el-radio-group v-model="formData.keepAlive">
|
||||
<el-radio :value="1">开启</el-radio>
|
||||
<el-radio :value="0">关闭</el-radio>
|
||||
@@ -316,7 +313,7 @@
|
||||
|
||||
<!-- 权限标识 -->
|
||||
<el-form-item v-if="formData.type == MenuTypeEnum.BUTTON" label="权限标识" prop="perm">
|
||||
<el-input v-model="formData.perm" placeholder="sys:user:add" />
|
||||
<el-input v-model="formData.perm" placeholder="sys:user:create" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="formData.type !== MenuTypeEnum.BUTTON" label="图标" prop="icon">
|
||||
@@ -382,14 +379,34 @@ const initialMenuFormData = ref<MenuForm>({
|
||||
});
|
||||
// 菜单表单数据
|
||||
const formData = ref({ ...initialMenuFormData.value });
|
||||
const isExternalLink = computed(
|
||||
() =>
|
||||
formData.value.type === MenuTypeEnum.MENU &&
|
||||
!!formData.value.routePath &&
|
||||
/^https?:\/\//.test(formData.value.routePath)
|
||||
);
|
||||
const validateRouteName = (_: unknown, value: string, callback: (error?: Error) => void) => {
|
||||
if (formData.value.type === MenuTypeEnum.MENU && !isExternalLink.value && !value) {
|
||||
callback(new Error("请输入路由名称"));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
};
|
||||
const validateComponent = (_: unknown, value: string, callback: (error?: Error) => void) => {
|
||||
if (formData.value.type === MenuTypeEnum.MENU && !isExternalLink.value && !value) {
|
||||
callback(new Error("请输入组件路径"));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
};
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
parentId: [{ required: true, message: "请选择父级菜单", trigger: "blur" }],
|
||||
name: [{ required: true, message: "请输入菜单名称", trigger: "blur" }],
|
||||
type: [{ required: true, message: "请选择菜单类型", trigger: "blur" }],
|
||||
routeName: [{ required: true, message: "请输入路由名称", trigger: "blur" }],
|
||||
routeName: [{ validator: validateRouteName, trigger: "blur" }],
|
||||
routePath: [{ required: true, message: "请输入路由路径", trigger: "blur" }],
|
||||
component: [{ required: true, message: "请输入组件路径", trigger: "blur" }],
|
||||
component: [{ validator: validateComponent, trigger: "blur" }],
|
||||
visible: [{ required: true, message: "请选择显示状态", trigger: "change" }],
|
||||
});
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<div class="table-section__toolbar">
|
||||
<div class="table-section__toolbar--actions">
|
||||
<el-button
|
||||
v-hasPerm="['sys:notice:add']"
|
||||
v-hasPerm="['sys:notice:create']"
|
||||
type="success"
|
||||
icon="plus"
|
||||
@click="handleOpenDialog()"
|
||||
@@ -134,7 +134,7 @@
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.publishStatus != 1"
|
||||
v-hasPerm="['sys:notice:edit']"
|
||||
v-hasPerm="['sys:notice:update']"
|
||||
type="primary"
|
||||
size="small"
|
||||
link
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
<div class="table-section__toolbar">
|
||||
<div class="table-section__toolbar--actions">
|
||||
<el-button
|
||||
v-hasPerm="['sys:user:add']"
|
||||
v-hasPerm="['sys:user:create']"
|
||||
type="success"
|
||||
icon="plus"
|
||||
@click="handleOpenDialog()"
|
||||
@@ -130,7 +130,7 @@
|
||||
重置密码
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPerm="'sys:user:edit'"
|
||||
v-hasPerm="'sys:user:update'"
|
||||
type="primary"
|
||||
icon="edit"
|
||||
link
|
||||
|
||||
Reference in New Issue
Block a user