refactor: 优化通用组件实现与工具类型
- src/components/DictSelect: 改用 defineModel 与 computed,修复选项加载时清空已选值与父子值分叉 - src/components/ThemeSwitcher: ThemeSwitch 改名,修正 theneList 拼写 - src/composables/useTableSelection: 显式判空,避免 id 为 0 的行被过滤 - src/utils/download: 收窄入参类型,headers 改为可选 - src/api/system/user: 导出与模板下载接口声明 Blob 响应类型 - src/views/system/dict: 标签类型选项排除空串,el-tag 去掉 as any - src/views/system/user/components/UserImportDialog: 去掉响应类型的 any 标注 - src/layouts、src/views/login: 删除可自动导入组件的冗余 import
This commit is contained in:
@@ -109,7 +109,7 @@ const UserAPI = {
|
||||
|
||||
/** 下载用户导入模板 */
|
||||
downloadTemplate() {
|
||||
return request({
|
||||
return request<Blob>({
|
||||
url: `${USER_BASE_URL}/template`,
|
||||
method: "get",
|
||||
responseType: "blob",
|
||||
@@ -122,7 +122,7 @@ const UserAPI = {
|
||||
* @param queryParams 查询参数
|
||||
*/
|
||||
export(queryParams: UserQueryParams) {
|
||||
return request({
|
||||
return request<Blob>({
|
||||
url: `${USER_BASE_URL}/export`,
|
||||
method: "get",
|
||||
params: queryParams,
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<template>
|
||||
<el-select
|
||||
v-if="type === 'select'"
|
||||
v-model="selectedValue"
|
||||
v-model="selectedSingle"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
clearable
|
||||
:style="style"
|
||||
@change="handleChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="option in options"
|
||||
@@ -18,10 +17,9 @@
|
||||
|
||||
<el-radio-group
|
||||
v-else-if="type === 'radio'"
|
||||
v-model="selectedValue"
|
||||
v-model="selectedSingle"
|
||||
:disabled="disabled"
|
||||
:style="style"
|
||||
@change="handleChange"
|
||||
>
|
||||
<el-radio v-for="option in options" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
@@ -30,10 +28,9 @@
|
||||
|
||||
<el-checkbox-group
|
||||
v-else-if="type === 'checkbox'"
|
||||
v-model="selectedValue"
|
||||
v-model="selectedMulti"
|
||||
:disabled="disabled"
|
||||
:style="style"
|
||||
@change="handleChange"
|
||||
>
|
||||
<el-checkbox v-for="option in options" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
@@ -44,30 +41,33 @@
|
||||
<script setup lang="ts">
|
||||
import { useDictStore } from "@/stores";
|
||||
|
||||
const dictStore = useDictStore();
|
||||
defineOptions({
|
||||
name: "DictSelect",
|
||||
});
|
||||
|
||||
const props = defineProps({
|
||||
/** 字典编码 */
|
||||
code: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
modelValue: {
|
||||
type: [String, Number, Array],
|
||||
required: false,
|
||||
},
|
||||
/** 展示形态(下拉/单选/多选) */
|
||||
type: {
|
||||
type: String,
|
||||
default: "select",
|
||||
validator: (value: string) => ["select", "radio", "checkbox"].includes(value),
|
||||
},
|
||||
/** 提示文字 */
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "请选择",
|
||||
},
|
||||
/** 是否禁用(form-create 只读回显依赖此属性) */
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
/** 行内样式(默认宽度 300px) */
|
||||
style: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
@@ -78,46 +78,51 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
const modelValue = defineModel<string | number | Array<string | number>>();
|
||||
|
||||
const dictStore = useDictStore();
|
||||
|
||||
/** 字典选项 */
|
||||
const options = ref<Array<{ label: string; value: string | number }>>([]);
|
||||
|
||||
const selectedValue = ref<any>(
|
||||
typeof props.modelValue === "string" || typeof props.modelValue === "number"
|
||||
? props.modelValue
|
||||
: Array.isArray(props.modelValue)
|
||||
? props.modelValue
|
||||
: undefined
|
||||
);
|
||||
/**
|
||||
* 单选值(select/radio):选项加载后把外部值翻译为选项的原始值(消除 "1" 与 1 的
|
||||
* 字符串/数字差异),未命中时原样保留不清空;用户选择直接写回 modelValue
|
||||
*/
|
||||
const selectedSingle = computed<string | number | undefined>({
|
||||
get() {
|
||||
const value = modelValue.value;
|
||||
if (value === null || value === undefined || Array.isArray(value)) return undefined;
|
||||
const matched = options.value.find((option) => String(option.value) === String(value));
|
||||
return matched?.value ?? value;
|
||||
},
|
||||
set(value) {
|
||||
modelValue.value = value;
|
||||
},
|
||||
});
|
||||
|
||||
// 监听 modelValue 和 options 的变化
|
||||
/** 多选值(checkbox):非数组入参按空选处理 */
|
||||
const selectedMulti = computed<Array<string | number>>({
|
||||
get() {
|
||||
return Array.isArray(modelValue.value) ? modelValue.value : [];
|
||||
},
|
||||
set(value) {
|
||||
modelValue.value = value;
|
||||
},
|
||||
});
|
||||
|
||||
// 获取字典数据(空编码跳过:设计器画布上未配置字典编码时不发无效请求;
|
||||
// watch 而非 onMounted:设计器右侧面板修改编码后画布即时刷新选项)
|
||||
watch(
|
||||
[() => props.modelValue, () => options.value],
|
||||
([newValue, newOptions]) => {
|
||||
if (newOptions.length > 0 && newValue !== undefined) {
|
||||
if (props.type === "checkbox") {
|
||||
selectedValue.value = Array.isArray(newValue) ? newValue : [];
|
||||
} else {
|
||||
const matchedOption = newOptions.find(
|
||||
(option) => String(option.value) === String(newValue)
|
||||
);
|
||||
selectedValue.value = matchedOption?.value;
|
||||
}
|
||||
} else {
|
||||
selectedValue.value = undefined;
|
||||
() => props.code,
|
||||
async (code) => {
|
||||
if (!code) {
|
||||
options.value = [];
|
||||
return;
|
||||
}
|
||||
await dictStore.loadDictItems(code);
|
||||
options.value = dictStore.getDictItems(code);
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 监听 selectedValue 的变化并触发 update:modelValue
|
||||
function handleChange(val: any) {
|
||||
emit("update:modelValue", val);
|
||||
}
|
||||
|
||||
// 获取字典数据
|
||||
onMounted(async () => {
|
||||
await dictStore.loadDictItems(props.code);
|
||||
options.value = dictStore.getDictItems(props.code);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -38,7 +38,7 @@ const { locale, t } = useI18n();
|
||||
/**
|
||||
* 处理语言切换
|
||||
*
|
||||
* @param lang 语言(zh-cn、en<EFBFBD>?
|
||||
* @param lang 语言(zh-cn、en)
|
||||
*/
|
||||
function handleLanguageChange(lang: string) {
|
||||
locale.value = lang;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<el-dropdown trigger="click" @command="handleDarkChange">
|
||||
<el-dropdown trigger="click" @command="handleThemeChange">
|
||||
<el-icon :size="20">
|
||||
<component :is="currentThemeIcon" />
|
||||
</el-icon>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
v-for="item in theneList"
|
||||
v-for="item in themeList"
|
||||
:key="item.value"
|
||||
:command="item.value"
|
||||
:disabled="settingsStore.theme === item.value"
|
||||
@@ -28,7 +28,7 @@ import { Moon, Sunny, Monitor } from "@element-plus/icons-vue";
|
||||
const { t } = useI18n();
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
const theneList = [
|
||||
const themeList = [
|
||||
{ label: t("login.light"), value: ThemeMode.LIGHT, component: Sunny },
|
||||
{ label: t("login.dark"), value: ThemeMode.DARK, component: Moon },
|
||||
{ label: t("login.auto"), value: ThemeMode.AUTO, component: Monitor },
|
||||
@@ -42,7 +42,7 @@ const currentThemeIcon = computed(() => {
|
||||
return settingsStore.resolvedTheme === ThemeMode.DARK ? Moon : Sunny;
|
||||
});
|
||||
|
||||
const handleDarkChange = (theme: ThemeMode) => {
|
||||
const handleThemeChange = (theme: ThemeMode) => {
|
||||
settingsStore.theme = theme;
|
||||
};
|
||||
</script>
|
||||
@@ -34,7 +34,9 @@ export function useTableSelection<T extends { id?: string | number }>() {
|
||||
* @param selection 选中的行数据列表
|
||||
*/
|
||||
function handleSelectionChange(selection: T[]): void {
|
||||
selectedIds.value = selection.flatMap((item) => (item.id ? [item.id] : []));
|
||||
selectedIds.value = selection.flatMap((item) =>
|
||||
item.id === undefined || item.id === null ? [] : [item.id]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,8 +13,6 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAppStore } from "@/stores";
|
||||
import Hamburger from "@/components/Hamburger/index.vue";
|
||||
import Breadcrumb from "@/components/Breadcrumb/index.vue";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
</div>
|
||||
|
||||
<div class="layout-toolbar__item">
|
||||
<NoticeDropdown />
|
||||
<NoticeCenter />
|
||||
</div>
|
||||
|
||||
<div v-if="showTenantSwitcher" class="layout-toolbar__item">
|
||||
@@ -60,12 +60,6 @@ import { defaults } from "@/settings";
|
||||
import { DeviceEnum, SidebarColor, ThemeMode, LayoutMode } from "@/enums/settings";
|
||||
import { useAppStore, useSettingsStore, useUserStore } from "@/stores";
|
||||
|
||||
import CommandPalette from "@/components/CommandPalette/index.vue";
|
||||
import Fullscreen from "@/components/Fullscreen/index.vue";
|
||||
import SizeSelect from "@/components/SizeSelect/index.vue";
|
||||
import LangSelect from "@/components/LangSelect/index.vue";
|
||||
import NoticeDropdown from "@/components/NoticeDropdown/index.vue";
|
||||
import TenantSwitcher from "@/components/TenantSwitcher/index.vue";
|
||||
import { useTenantStoreHook } from "@/stores/tenant";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -29,9 +29,19 @@ function extractFileName(contentDisposition: string): string {
|
||||
return `download_${Date.now()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载响应:axios 二进制响应(blob 透传整个 response)或自组装的二进制数据
|
||||
*/
|
||||
interface DownloadResponse {
|
||||
/** 二进制内容 */
|
||||
data: BlobPart;
|
||||
/** 响应头(用于解析文件名,自组装数据可省略) */
|
||||
headers?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载文件
|
||||
* @param response Axios 响应对象
|
||||
* @param response 二进制接口响应或 { data } 形式的二进制内容
|
||||
* @param customFileName 自定义文件名(可选)
|
||||
*
|
||||
* @example
|
||||
@@ -44,10 +54,10 @@ function extractFileName(contentDisposition: string): string {
|
||||
* downloadFile(response, "用户列表.xlsx");
|
||||
* ```
|
||||
*/
|
||||
export function downloadFile(response: { data: any; headers: any }, customFileName?: string): void {
|
||||
export function downloadFile(response: DownloadResponse, customFileName?: string): void {
|
||||
try {
|
||||
const fileData = response.data;
|
||||
const contentDisposition = response.headers["content-disposition"];
|
||||
const contentDisposition = String(response.headers?.["content-disposition"] ?? "");
|
||||
const fileName = customFileName || extractFileName(contentDisposition);
|
||||
|
||||
// 创建 Blob 对象
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="login-page">
|
||||
<div class="login-toolbar">
|
||||
<ThemeSwitch />
|
||||
<ThemeSwitcher />
|
||||
<LangSelect size="text-18px" />
|
||||
</div>
|
||||
|
||||
@@ -21,10 +21,7 @@
|
||||
<span class="login-brand__tag-dot" />
|
||||
Enterprise Ready
|
||||
</el-tag>
|
||||
<h1 class="login-brand__title">企业级管理系统</h1>
|
||||
<p class="login-brand__desc">
|
||||
提供安全、高效、可扩展的管理解决方案,助力企业数字化转型与业务增长。
|
||||
</p>
|
||||
<h1 class="login-brand__title">开箱即用的企业级中后台解决方案</h1>
|
||||
</div>
|
||||
<div class="login-brand__features">
|
||||
<div class="login-brand__feature">
|
||||
@@ -171,7 +168,6 @@ import router from "@/router";
|
||||
import { useUserStore } from "@/stores";
|
||||
import { AuthStorage } from "@/utils/auth";
|
||||
import { appConfig } from "@/settings";
|
||||
import ThemeSwitch from "@/components/ThemeSwitch/index.vue";
|
||||
import ResetPwd from "./components/ResetPwd.vue";
|
||||
import QrCodeLogin from "./components/QrCodeLogin.vue";
|
||||
import logo from "@/assets/images/logo.png";
|
||||
@@ -251,7 +247,7 @@ onMounted(() => getCaptcha());
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$primary: #5d87ff;
|
||||
$primary: #165dff;
|
||||
$bg: #f8fafc;
|
||||
$text-primary: #273248;
|
||||
$text-secondary: #667085;
|
||||
@@ -310,8 +306,13 @@ $input-h: 44px;
|
||||
}
|
||||
|
||||
&__logo {
|
||||
box-sizing: border-box;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
padding: 5px;
|
||||
background: rgb(37 99 235 / 8%);
|
||||
border: 1px solid rgb(37 99 235 / 18%);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
&__identity {
|
||||
@@ -325,7 +326,7 @@ $input-h: 44px;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
color: $text-primary;
|
||||
color: #1e3a8a;
|
||||
}
|
||||
|
||||
&__version {
|
||||
@@ -336,9 +337,9 @@ $input-h: 44px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
color: rgba($primary, 0.88);
|
||||
background: rgba($primary, 0.07);
|
||||
border: 1px solid rgba($primary, 0.13);
|
||||
color: rgb(30 58 138 / 80%);
|
||||
background: rgb(37 99 235 / 8%);
|
||||
border: 1px solid rgb(37 99 235 / 16%);
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
@@ -347,8 +348,8 @@ $input-h: 44px;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: min(720px, 100%);
|
||||
padding: 20px 0 88px;
|
||||
width: min(760px, 100%);
|
||||
padding: 40px 0 60px;
|
||||
}
|
||||
|
||||
&__main {
|
||||
@@ -357,13 +358,14 @@ $input-h: 44px;
|
||||
|
||||
&__tag {
|
||||
gap: 8px;
|
||||
height: 28px;
|
||||
padding: 0 13px 0 11px;
|
||||
margin-bottom: 18px;
|
||||
height: 32px;
|
||||
padding: 0 15px 0 13px;
|
||||
margin-bottom: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: $primary;
|
||||
background: rgba($primary, 0.035);
|
||||
border-color: rgba($primary, 0.14);
|
||||
color: #1d4ed8;
|
||||
background: rgb(37 99 235 / 8%);
|
||||
border-color: rgb(37 99 235 / 16%);
|
||||
|
||||
:deep(.el-tag__content) {
|
||||
display: inline-flex;
|
||||
@@ -377,26 +379,26 @@ $input-h: 44px;
|
||||
flex-shrink: 0;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
background: $primary;
|
||||
background: #2563eb;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 0 3px rgba($primary, 0.12);
|
||||
box-shadow: 0 0 0 3px rgb(37 99 235 / 16%);
|
||||
}
|
||||
|
||||
&__title {
|
||||
margin: 0 0 18px;
|
||||
font-size: 46px;
|
||||
margin: 0;
|
||||
font-size: 36px;
|
||||
font-weight: 800;
|
||||
line-height: 1.18;
|
||||
color: #222b3a;
|
||||
line-height: 1.15;
|
||||
color: #1e3a8a;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
&__desc {
|
||||
max-width: 560px;
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
line-height: 1.75;
|
||||
color: $text-secondary;
|
||||
font-size: 18px;
|
||||
line-height: 1.6;
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
&__features {
|
||||
@@ -404,19 +406,19 @@ $input-h: 44px;
|
||||
align-items: center;
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
margin-top: 28px;
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
&__feature {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
gap: 8px;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
height: 28px;
|
||||
padding: 0 13px;
|
||||
font-size: 13px;
|
||||
height: 32px;
|
||||
padding: 0 15px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: $text-primary;
|
||||
color: rgb(30 58 138 / 85%);
|
||||
background: transparent;
|
||||
|
||||
&:first-child {
|
||||
@@ -430,7 +432,7 @@ $input-h: 44px;
|
||||
width: 1px;
|
||||
height: 14px;
|
||||
content: "";
|
||||
background: rgba(39 50 72 / 12%);
|
||||
background: rgb(37 99 235 / 18%);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,11 +440,11 @@ $input-h: 44px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
color: $primary;
|
||||
background: rgba($primary, 0.08);
|
||||
border: 1px solid rgba($primary, 0.1);
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
color: #1d4ed8;
|
||||
background: rgb(37 99 235 / 8%);
|
||||
border: 1px solid rgb(37 99 235 / 16%);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
@@ -450,9 +452,9 @@ $input-h: 44px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
color: $primary;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
&__feature-text {
|
||||
@@ -488,8 +490,8 @@ $input-h: 44px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
margin: 0 0 4px;
|
||||
font-size: 34px;
|
||||
margin: 0 0 10px;
|
||||
font-size: 28px;
|
||||
font-weight: 750;
|
||||
line-height: 1.1;
|
||||
color: $text-primary;
|
||||
@@ -497,8 +499,8 @@ $input-h: 44px;
|
||||
}
|
||||
|
||||
&__desc {
|
||||
margin: 8px 0 24px;
|
||||
font-size: 14px;
|
||||
margin: 10px 0 28px;
|
||||
font-size: 15px;
|
||||
color: $text-muted;
|
||||
}
|
||||
}
|
||||
@@ -579,7 +581,7 @@ $input-h: 44px;
|
||||
.login-btn {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
font-size: 15px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 12px 24px rgba($primary, 0.18);
|
||||
@@ -627,7 +629,7 @@ $input-h: 44px;
|
||||
justify-content: center;
|
||||
height: 44px;
|
||||
padding: 0;
|
||||
font-size: 13px;
|
||||
font-size: 14px;
|
||||
color: $text-secondary;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
@@ -664,41 +666,56 @@ $input-h: 44px;
|
||||
.dark .login-brand {
|
||||
background-image: url("@/assets/images/login/bg-dark.svg");
|
||||
|
||||
&__logo {
|
||||
background: rgb(255 255 255 / 10%);
|
||||
border-color: rgb(255 255 255 / 20%);
|
||||
}
|
||||
|
||||
&__name {
|
||||
color: rgb(255 255 255 / 86%);
|
||||
color: rgb(255 255 255 / 92%);
|
||||
}
|
||||
|
||||
&__version {
|
||||
color: rgb(167 190 255 / 92%);
|
||||
background: rgba($primary, 0.12);
|
||||
border-color: rgba($primary, 0.2);
|
||||
color: rgb(255 255 255 / 85%);
|
||||
background: rgb(255 255 255 / 10%);
|
||||
border-color: rgb(255 255 255 / 16%);
|
||||
}
|
||||
|
||||
&__tag {
|
||||
color: rgba($primary, 0.95);
|
||||
background: rgba($primary, 0.08);
|
||||
border-color: rgba($primary, 0.18);
|
||||
color: #fff;
|
||||
background: rgb(255 255 255 / 8%);
|
||||
border-color: rgb(255 255 255 / 16%);
|
||||
}
|
||||
|
||||
&__tag-dot {
|
||||
background: #fff;
|
||||
box-shadow: 0 0 0 3px rgb(255 255 255 / 18%);
|
||||
}
|
||||
|
||||
&__title {
|
||||
color: rgb(255 255 255 / 90%);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&__desc {
|
||||
color: rgb(226 232 240 / 62%);
|
||||
color: rgb(255 255 255 / 65%);
|
||||
}
|
||||
|
||||
&__feature {
|
||||
color: rgb(255 255 255 / 76%);
|
||||
color: rgb(255 255 255 / 85%);
|
||||
|
||||
&:not(:last-child)::after {
|
||||
background: rgba(255 255 255 / 12%);
|
||||
background: rgb(255 255 255 / 18%);
|
||||
}
|
||||
}
|
||||
|
||||
&__feature-mark {
|
||||
background: rgba($primary, 0.15);
|
||||
border-color: rgba($primary, 0.18);
|
||||
color: #fff;
|
||||
background: rgb(255 255 255 / 10%);
|
||||
border-color: rgb(255 255 255 / 16%);
|
||||
}
|
||||
|
||||
&__feature-icon {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -760,7 +777,7 @@ $input-h: 44px;
|
||||
height: auto;
|
||||
min-height: auto;
|
||||
padding: 28px 40px 0;
|
||||
background: #fff;
|
||||
background: linear-gradient(180deg, #ffffff 0%, #dbeafe 100%);
|
||||
|
||||
&__hero {
|
||||
display: none;
|
||||
|
||||
@@ -136,7 +136,7 @@
|
||||
</template>
|
||||
<el-option v-for="type in tagTypeOptions" :key="type" :label="type" :value="type">
|
||||
<div flex-y-center gap-10px>
|
||||
<el-tag :type="type as any">{{ formData.label ?? "字典标签" }}</el-tag>
|
||||
<el-tag :type="type">{{ formData.label ?? "字典标签" }}</el-tag>
|
||||
<span>{{ type }}</span>
|
||||
</div>
|
||||
</el-option>
|
||||
@@ -180,7 +180,7 @@ const queryFormRef = ref<FormInstance>();
|
||||
const dictItemFormRef = ref<FormInstance>();
|
||||
|
||||
// 标签类型可选项。
|
||||
const tagTypeOptions: NonNullable<DictItemForm["tagType"]>[] = [
|
||||
const tagTypeOptions: Exclude<NonNullable<DictItemForm["tagType"]>, "">[] = [
|
||||
"primary",
|
||||
"success",
|
||||
"info",
|
||||
|
||||
@@ -533,8 +533,8 @@ const showRouteParams = computed(() => formData.type === MenuTypeEnum.MENU);
|
||||
|
||||
const routeNameTooltip = computed(() =>
|
||||
isEmbeddedExternal.value
|
||||
? "开启缓存时填写,需和内嵌页路由名称保持一致,例如 Apifox"
|
||||
: "开启缓存时填写,需和页面组件 name 保持一致,例如 User"
|
||||
? "路由唯一标识,需全局唯一,确保多个内嵌页面互不冲突,例如 Apifox"
|
||||
: "路由唯一标识,需全局唯一,用于动态路由注册与命令面板检索,例如 User"
|
||||
);
|
||||
|
||||
const routeNamePlaceholder = computed(() => (isEmbeddedExternal.value ? "Apifox" : "User"));
|
||||
|
||||
@@ -144,7 +144,7 @@ function handleFileExceed(): void {
|
||||
* 下载导入模板
|
||||
*/
|
||||
function downloadTemplate(): void {
|
||||
UserAPI.downloadTemplate().then((response: any) => {
|
||||
UserAPI.downloadTemplate().then((response) => {
|
||||
downloadFile(response);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user