提交
This commit is contained in:
1 parent
246ad2228e
commit
8e2dd8b9f0
186 files changed
+38821
-2571
No files matched your search
@@ -0,0 +1,211 @@
|
||||
<!-- 通用查看弹窗 —— 样式对齐待办中心「查看详情」 -->
|
||||
<!-- eslint-disable vue/no-v-model-argument -->
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="innerVisible"
|
||||
width="720px"
|
||||
class="system-view-dialog"
|
||||
align-center
|
||||
destroy-on-close
|
||||
:show-close="false"
|
||||
style="backdrop-filter: none"
|
||||
>
|
||||
<div class="dialog-scroll">
|
||||
<!-- hero -->
|
||||
<div class="view-hero">
|
||||
<div class="view-hero-icon">
|
||||
<el-icon :size="28"><component :is="iconComponent" /></el-icon>
|
||||
</div>
|
||||
<div class="view-hero-main">
|
||||
<h2>{{ title }}</h2>
|
||||
<p v-if="meta" class="view-hero-meta">{{ meta }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- sections -->
|
||||
<div v-for="section in sections" :key="section.title" class="view-section">
|
||||
<h3>{{ section.title }}</h3>
|
||||
<dl class="view-kv-grid">
|
||||
<div
|
||||
v-for="field in section.fields"
|
||||
:key="field.label"
|
||||
class="view-kv"
|
||||
:class="{ 'view-kv-span-2': field.span }"
|
||||
>
|
||||
<dt>{{ field.label }}</dt>
|
||||
<dd>{{ field.value ?? '—' }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button class="btn-ghost" @click="innerVisible = false">
|
||||
<el-icon><Close /></el-icon>关闭
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, type Component } from 'vue'
|
||||
import { Close } from '@element-plus/icons-vue'
|
||||
|
||||
export interface ViewField {
|
||||
label: string
|
||||
value: string | number | boolean | null | undefined
|
||||
span?: boolean
|
||||
}
|
||||
|
||||
export interface ViewSection {
|
||||
title: string
|
||||
fields: ViewField[]
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
title: string
|
||||
icon?: Component
|
||||
meta?: string
|
||||
sections: ViewSection[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', val: boolean): void
|
||||
}>()
|
||||
|
||||
const innerVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (val) => emit('update:visible', val)
|
||||
})
|
||||
|
||||
const iconComponent = computed(() => props.icon || (() => null))
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.dialog-scroll {
|
||||
overflow-y: auto;
|
||||
max-height: calc(100vh - 120px);
|
||||
}
|
||||
|
||||
.view-hero {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
padding: 20px 22px;
|
||||
background: linear-gradient(135deg, #ecfdf5 0%, #ffffff 55%);
|
||||
border-bottom: 1px solid #d9e8e2;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.view-hero-icon {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(16, 185, 129, 0.2);
|
||||
background: rgba(16, 185, 129, 0.08);
|
||||
color: #059669;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.view-hero-main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
h2 {
|
||||
margin: 0 0 8px;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
}
|
||||
|
||||
.view-hero-meta {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
color: #64748b;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.view-section {
|
||||
padding: 18px 22px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 0 0 14px;
|
||||
padding-left: 10px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
border-left: 3px solid #10b981;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.view-kv-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px 28px;
|
||||
}
|
||||
|
||||
.view-kv {
|
||||
display: grid;
|
||||
grid-template-columns: 96px 1fr;
|
||||
gap: 8px 12px;
|
||||
font-size: 13px;
|
||||
align-items: start;
|
||||
|
||||
dt {
|
||||
margin: 0;
|
||||
color: #64748b;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin: 0;
|
||||
color: #1e293b;
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
.view-kv-span-2 {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.view-kv-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.view-kv {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.system-view-dialog {
|
||||
.el-dialog__header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.el-dialog__body {
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.el-dialog__footer {
|
||||
padding: 12px 22px;
|
||||
border-top: 1px solid #d9e8e2;
|
||||
background: #f8fafc;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -107,7 +107,6 @@
|
||||
ElDatePicker,
|
||||
ElInput,
|
||||
ElInputTag,
|
||||
ElInputNumber,
|
||||
ElRadioGroup,
|
||||
ElRate,
|
||||
ElSelect,
|
||||
@@ -125,7 +124,7 @@
|
||||
const componentMap = {
|
||||
input: ElInput, // 输入框
|
||||
inputtag: ElInputTag, // 标签输入框
|
||||
number: ElInputNumber, // 数字输入框
|
||||
number: ElInput, // 数字输入框
|
||||
select: ElSelect, // 选择器
|
||||
switch: ElSwitch, // 开关
|
||||
checkbox: ElCheckbox, // 复选框
|
||||
@@ -414,9 +413,22 @@
|
||||
}
|
||||
|
||||
const getProps = (item: FormItem) => {
|
||||
if (item.props) return item.props
|
||||
if (item.props) {
|
||||
// number 类型使用 ElInput 渲染时需要注入 type="number"
|
||||
if (item.type === 'number') {
|
||||
return { type: 'number', step: 'any', ...item.props }
|
||||
}
|
||||
return item.props
|
||||
}
|
||||
const props = { ...item }
|
||||
rootProps.forEach((key) => delete (props as Record<string, any>)[key])
|
||||
// number 类型使用 ElInput 渲染时需要注入 type="number"
|
||||
if (item.type === 'number') {
|
||||
;(props as Record<string, any>).type = 'number'
|
||||
if (!(props as Record<string, any>).step) {
|
||||
;(props as Record<string, any>).step = 'any'
|
||||
}
|
||||
}
|
||||
return props
|
||||
}
|
||||
|
||||
|
||||
@@ -114,7 +114,6 @@
|
||||
ElDatePicker,
|
||||
ElInput,
|
||||
ElInputTag,
|
||||
ElInputNumber,
|
||||
ElRadioGroup,
|
||||
ElRate,
|
||||
ElSelect,
|
||||
@@ -132,7 +131,7 @@
|
||||
const componentMap = {
|
||||
input: ElInput, // 输入框
|
||||
inputTag: ElInputTag, // 标签输入框
|
||||
number: ElInputNumber, // 数字输入框
|
||||
number: ElInput, // 数字输入框
|
||||
select: ElSelect, // 选择器
|
||||
switch: ElSwitch, // 开关
|
||||
checkbox: ElCheckbox, // 复选框
|
||||
@@ -297,9 +296,22 @@
|
||||
}))
|
||||
|
||||
const getProps = (item: SearchFormItem) => {
|
||||
if (item.props) return item.props
|
||||
if (item.props) {
|
||||
// number 类型使用 ElInput 渲染时需要注入 type="number"
|
||||
if (item.type === 'number') {
|
||||
return { type: 'number', step: 'any', ...item.props }
|
||||
}
|
||||
return item.props
|
||||
}
|
||||
const props = { ...item }
|
||||
rootProps.forEach((key) => delete (props as Record<string, any>)[key])
|
||||
// number 类型使用 ElInput 渲染时需要注入 type="number"
|
||||
if (item.type === 'number') {
|
||||
;(props as Record<string, any>).type = 'number'
|
||||
if (!(props as Record<string, any>).step) {
|
||||
;(props as Record<string, any>).step = 'any'
|
||||
}
|
||||
}
|
||||
return props
|
||||
}
|
||||
|
||||
|
||||
@@ -36,12 +36,20 @@
|
||||
}"
|
||||
>
|
||||
<ArtSvgIcon
|
||||
v-if="menu.meta.icon"
|
||||
class="menu-icon text-g-700 dark:text-g-800"
|
||||
:icon="menu.meta.icon"
|
||||
:style="{
|
||||
marginBottom: dualMenuShowText ? '5px' : '0'
|
||||
}"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="dual-menu-icon-placeholder"
|
||||
:style="{
|
||||
marginBottom: dualMenuShowText ? '5px' : '0'
|
||||
}"
|
||||
/>
|
||||
<span v-if="dualMenuShowText" class="text-md text-g-700">
|
||||
{{ $t(menu.meta.title) }}
|
||||
</span>
|
||||
|
||||
@@ -55,6 +55,14 @@
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.dual-menu-icon-placeholder {
|
||||
display: block;
|
||||
flex-shrink: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
span {
|
||||
display: -webkit-box;
|
||||
width: 100%;
|
||||
|
||||
@@ -112,12 +112,25 @@ $popup-menu-radius: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
// 菜单图标
|
||||
// 菜单图标(固定槽宽,无 icon 的一级项用空槽与文字对齐)
|
||||
.menu-icon {
|
||||
box-sizing: border-box;
|
||||
flex-shrink: 0;
|
||||
width: $menu-icon-size;
|
||||
min-width: $menu-icon-size;
|
||||
height: $menu-icon-size;
|
||||
margin-right: 8px;
|
||||
font-size: $menu-icon-size;
|
||||
}
|
||||
|
||||
.menu-icon__virtual {
|
||||
display: block;
|
||||
flex: 0 0 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// 菜单高度
|
||||
.el-sub-menu__title,
|
||||
.el-menu-item {
|
||||
|
||||
+12
-2
@@ -2,12 +2,17 @@
|
||||
<template v-for="(item, index) in filteredMenuItems" :key="getUniqueKey(item, index)">
|
||||
<ElSubMenu v-if="hasChildren(item)" :index="item.path || item.meta.title" :level="level">
|
||||
<template #title>
|
||||
<div v-if="level === 0" class="menu-icon flex-cc">
|
||||
<div
|
||||
v-if="level === 0 && (item.meta.icon || item.meta.virtualMenuIcon)"
|
||||
class="menu-icon flex-cc"
|
||||
>
|
||||
<ArtSvgIcon
|
||||
v-if="item.meta.icon"
|
||||
:icon="item.meta.icon"
|
||||
:color="theme?.iconColor"
|
||||
:style="{ color: theme.iconColor }"
|
||||
/>
|
||||
<span v-else class="menu-icon__virtual" aria-hidden="true" />
|
||||
</div>
|
||||
<span class="menu-name">
|
||||
{{ formatMenuTitle(item.meta.title) }}
|
||||
@@ -30,12 +35,17 @@
|
||||
:level-item="level + 1"
|
||||
@click="goPage(item)"
|
||||
>
|
||||
<div v-if="level === 0" class="menu-icon flex-cc">
|
||||
<div
|
||||
v-if="level === 0 && (item.meta.icon || item.meta.virtualMenuIcon)"
|
||||
class="menu-icon flex-cc"
|
||||
>
|
||||
<ArtSvgIcon
|
||||
v-if="item.meta.icon"
|
||||
:icon="item.meta.icon"
|
||||
:color="theme?.iconColor"
|
||||
:style="{ color: theme.iconColor }"
|
||||
/>
|
||||
<span v-else class="menu-icon__virtual" aria-hidden="true" />
|
||||
</div>
|
||||
<div
|
||||
v-show="item.meta.showBadge && level === 0 && !menuOpen"
|
||||
|
||||
@@ -6,15 +6,15 @@
|
||||
<ElSwitch v-if="config.type === 'switch'" :model-value="modelValue" @change="handleChange" />
|
||||
|
||||
<!-- 数字输入类型 -->
|
||||
<ElInputNumber
|
||||
<ElInput
|
||||
type="number"
|
||||
step="any"
|
||||
v-else-if="config.type === 'input-number'"
|
||||
:model-value="modelValue"
|
||||
:min="config.min"
|
||||
:max="config.max"
|
||||
:step="config.step"
|
||||
:style="config.style"
|
||||
:controls-position="config.controlsPosition"
|
||||
@change="handleChange"
|
||||
@input="handleChange"
|
||||
/>
|
||||
|
||||
<!-- 选择器类型 -->
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="showWorkTab"
|
||||
class="worktab-strip box-border flex-b w-full px-5 mb-3 select-none max-sm:px-[15px]"
|
||||
class="worktab-strip box-border flex w-full items-center px-5 mb-3 select-none max-sm:px-[15px]"
|
||||
>
|
||||
<div class="worktab-scroll-wrap w-full" ref="scrollRef">
|
||||
<div class="worktab-scroll-wrap min-w-0 flex-1" ref="scrollRef">
|
||||
<ul
|
||||
class="float-left whitespace-nowrap !bg-transparent flex"
|
||||
:class="[tabStyle === 'tab-google' ? 'pl-1' : '']"
|
||||
@@ -24,12 +24,6 @@
|
||||
@click="clickTab(item)"
|
||||
@contextmenu.prevent="(e) => showMenu(e, item.path)"
|
||||
>
|
||||
<ArtSvgIcon
|
||||
v-show="item.icon"
|
||||
:icon="item.icon"
|
||||
class="text-base mr-1 group-hover:text-theme"
|
||||
:class="item.path === activeTab ? 'text-theme' : 'text-inherit'"
|
||||
/>
|
||||
{{ item.customTitle || formatMenuTitle(item.title) }}
|
||||
<span
|
||||
v-if="list.length > 1 && !item.fixedTab"
|
||||
@@ -42,7 +36,7 @@
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="flex">
|
||||
<div class="flex shrink-0">
|
||||
<div
|
||||
class="worktab-menu-btn flex-cc relative top-0 size-8 leading-8 text-center c-p tad-200"
|
||||
@click="(e) => showMenu(e, activeTab)"
|
||||
|
||||
Reference in new issue
Block a user