Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import type { DynamicFormValue } from '../../type'
import { computed, onMounted, reactive } from 'vue'

import { ElMessage, ElMessageBox } from 'element-plus'
import { MsgConfirm, MsgError, MsgSuccess, MsgWarning } from '@/utils/message'
const props = defineProps<{ modelValue: DynamicFormValue }>()
const emit = defineEmits(['update:modelValue'])
const formValue = computed({
Expand Down Expand Up @@ -93,15 +93,6 @@ function openAddChildDialog(node: TreeNode) {
addDialog.formList = [createEmptyRow()]
}

function appendAddRow() {
addDialog.formList.push(createEmptyRow())
}

function removeAddRow(index: number) {
if (addDialog.formList.length === 1) return
addDialog.formList.splice(index, 1)
}

function closeAddDialog() {
addDialog.visible = false
addDialog.mode = 'root'
Expand All @@ -115,7 +106,7 @@ function submitAdd() {
.filter((item) => item.label && item.value)

if (!validList.length) {
ElMessage.warning('请至少填写一条完整数据')
MsgWarning('请至少填写一条完整数据')
return
}

Expand All @@ -126,7 +117,7 @@ function submitAdd() {
} else {
const parent = addDialog.parentNode
if (!parent) {
ElMessage.error('未找到父节点')
MsgError('未找到父节点')
return
}

Expand All @@ -136,7 +127,7 @@ function submitAdd() {
parent.children.push(...newNodes)
}

ElMessage.success('保存成功')
MsgSuccess('保存成功')
closeAddDialog()
}

Expand All @@ -161,32 +152,32 @@ function submitEdit() {
const value = editDialog.form.value.trim()

if (!label || !value) {
ElMessage.warning('标签和选项值不能为空')
MsgWarning('标签和选项值不能为空')
return
}

if (!editDialog.targetNode) {
ElMessage.error('未找到父节点')
MsgError('未找到父节点')
return
}

editDialog.targetNode.label = label
editDialog.targetNode.value = value

ElMessage.success('保存成功')
MsgSuccess('保存成功')
closeEditDialog()
}

/* -------------------- 删除 -------------------- */

function handleDelete(node: TreeNode) {
ElMessageBox.confirm(`确定删除「${node.label}」吗?`, '提示', { type: 'warning' })
MsgConfirm('提示', `确定删除「${node.label}」吗?`, { type: 'warning' })
.then(() => {
const removed = removeNodeById(formValue.value.treeData, node.id)
if (removed) {
ElMessage.success('删除成功')
MsgSuccess('删除成功')
} else {
ElMessage.error('删除失败')
MsgError('删除失败')
}
})
.catch(() => {})
Expand Down Expand Up @@ -216,45 +207,34 @@ function removeNodeById(list: TreeNode[], targetId: string): boolean {
</script>

<template>
<el-form-item prop="treeData" :rules="[{ message: '选项必填', blur: 'change', type: 'array', min: 1 }]">
<el-form-item prop="treeData" class="mk-hide-asterisk" :rules="[{ required: true, message: '请添加选项', blur: 'change', type: 'array', min: 1 }]">
<template #label>
<div class="flex-between">
<span>
选项
<span class="color-danger">*</span>
</span>
<div class="flex">
<el-checkbox v-model="formValue.multiple" label="允许多选" size="large" class="pr-8" />
<span class="mk-required">选项</span>
<div class="flex gap-3">
<el-checkbox v-model="formValue.multiple" label="允许多选" />
<el-button link type="primary" @click="openAddRootDialog">
<MkIcon name="icon_add_outlined" class="mr-4"></MkIcon>
<MkIcon name="icon_add_outlined"></MkIcon>
</el-button>
</div>
</div>
</template>
<el-card shadow="never" class="border-r-6 w-full" style="--el-card-padding: 8px">
<el-card shadow="never" class="w-full" style="--el-card-padding: 12px">
<el-tree :data="formValue.treeData" node-key="id" default-expand-all :expand-on-click-node="false" :props="treeProps" class="option-tree">
<template #default="{ data, node }">
<div class="flex-between w-full">
<div class="ellipsis" :title="`${data.label}-${data.value}`" style="max-width: 350px">
<span>{{ data.label }}-{{ data.value }}</span>
</div>

<div>
<span class="mr-4" v-if="node.level < 5">
<el-button link @click.stop="openAddChildDialog(data)">
<MkIcon name="icon_add_outlined" class="color-secondary"></MkIcon>
</el-button>
</span>
<span class="mr-4">
<el-button link @click.stop="openEditDialog(data)">
<MkIcon name="icon_edit_outlined" class="color-secondary"></MkIcon>
</el-button>
</span>
<span>
<el-button link @click.stop="handleDelete(data)">
<MkIcon name="icon_delete-trash_outlined" class="color-secondary"></MkIcon>
</el-button>
</span>
<div class="flex-between min-w-0 flex-1 gap-2">
<div class="min-w-0 flex-1 truncate" :title="`${data.label}-${data.value}`">{{ data.label }}-{{ data.value }}</div>
<div class="flex shrink-0 items-center">
<el-button text @click.stop="openEditDialog(data)">
<MkIcon name="icon_edit_outlined"></MkIcon>
</el-button>
<el-button text @click.stop="openAddChildDialog(data)" v-if="node.level < 5">
<MkIcon name="icon_add_outlined"></MkIcon>
</el-button>

<el-button text @click.stop="handleDelete(data)">
<MkIcon name="icon_delete-trash_outlined"></MkIcon>
</el-button>
</div>
</div>
</template>
Expand Down Expand Up @@ -283,94 +263,40 @@ function removeNodeById(list: TreeNode[], targetId: string): boolean {
/>
</el-form-item>
<!-- 添加弹窗 -->
<MkDialog
v-model="addDialog.visible"
:title="addDialog.mode === 'root' ? '添加一级选项' : '添加子选项'"
label-position="top"
require-asterisk-position="right"
@submit.prevent
>
<el-scrollbar>
<el-row :gutter="8" style="margin-right: 10px" class="tag-list-max-list">
<template v-for="(item, index) in addDialog.formList" :key="index">
<el-col :span="12">
<el-form-item>
<template #label>
{{ index === 0 ? '标签' : '' }}
<span class="color-danger" v-if="index === 0"> *</span>
</template>
<el-input v-model.trim="item.label" class="w-full" placeholder="请输入选项标签" maxlength="50"></el-input>
</el-form-item>
</el-col>
<el-col :span="11">
<el-form-item class="w-full">
<template #label>
{{ index === 0 ? '选项值' : '' }}
<span class="color-danger" v-if="index === 0">*</span>
</template>
<el-input v-model.trim="item.value" placeholder="请输入选项值" maxlength="100"></el-input>
</el-form-item>
</el-col>
<el-col :span="1">
<el-button
:disabled="addDialog.formList.length === 1"
link
@click="removeAddRow(index)"
:style="{ marginTop: index === 0 ? '35px' : '12px' }"
>
<MkIcon name="icon_delete-trash_outlined"></MkIcon>
</el-button>
</el-col>
<MkDialog v-model="addDialog.visible" :title="addDialog.mode === 'root' ? '添加一级选项' : '添加子选项'">
<el-form :model="addDialog" label-position="top" require-asterisk-position="right" @submit.prevent>
<MkFormList v-model="addDialog.formList" :default-item="createEmptyRow" item-key="key">
<template #default="{ index, item: option }">
<el-form-item :label="index === 0 ? '标签' : ''" :required="index === 0" class="min-w-0 flex-1 small">
<el-input v-model.trim="option.label" placeholder="请输入选项标签" maxlength="50" />
</el-form-item>
<el-form-item :label="index === 0 ? '选项值' : ''" :required="index === 0" class="min-w-0 flex-1 small">
<el-input v-model.trim="option.value" placeholder="请输入选项值" maxlength="100" />
</el-form-item>
</template>
</el-row>
</el-scrollbar>
<el-button link type="primary" @click="appendAddRow">
<MkIcon name="icon_add_outlined" class="mr-4" />
添加
</el-button>
</MkFormList>
</el-form>
<template #footer>
<el-button @click="closeAddDialog">取消</el-button>
<el-button plain @click="closeAddDialog">取消</el-button>
<el-button type="primary" @click="submitAdd">添加</el-button>
</template>
</MkDialog>

<!-- 编辑弹窗 -->
<MkDialog
:close-on-click-modal="true"
:close-on-press-escape="true"
v-model="editDialog.visible"
title="编辑"
width="520px"
destroy-on-close
label-position="top"
require-asterisk-position="right"
@submit.prevent
>
<el-row :gutter="8">
<el-col :span="12">
<el-form-item>
<template #label>
标签
<span class="color-danger"> *</span>
</template>
<MkDialog :close-on-click-modal="true" :close-on-press-escape="true" v-model="editDialog.visible" title="编辑">
<el-form :model="editDialog.form" label-position="top" require-asterisk-position="right" @submit.prevent>
<div class="flex gap-2">
<el-form-item label="标签" required class="min-w-0 flex-1 small">
<el-input v-model.trim="editDialog.form.label" placeholder="请输入选项标签" maxlength="50" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item class="w-full">
<template #label>
选项值
<span class="color-danger">*</span>
</template>
<el-form-item label="选项值" required class="min-w-0 flex-1 small">
<el-input v-model.trim="editDialog.form.value" placeholder="请输入选项值" maxlength="100" />
</el-form-item>
</el-col>
</el-row>

</div>
</el-form>
<template #footer>
<el-button @click="closeEditDialog">取消</el-button>
<el-button plain @click="closeEditDialog">取消</el-button>
<el-button type="primary" @click="submitEdit">保存</el-button>
</template>
</MkDialog>
</template>
<style lang="scss" scoped></style>
12 changes: 7 additions & 5 deletions ui/src/components/mk-dynamics-form/items/upload/UploadInput.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script setup lang="ts">
import type { DynamicFormValue } from '../../type'
import { computed, inject, ref, useAttrs } from 'vue'
import { ElMessage } from 'element-plus'
import { MsgWarning } from '@/utils/message'
import type { FormField } from '@/components/mk-dynamics-form/type'
import { downloadByURL, getAttrsArray, getFileUrl } from '@/utils/common'
import { getFileExtension, getFileIconUrl } from '@/utils/icon'
Expand Down Expand Up @@ -45,7 +45,9 @@ const images = computed(() => filesWithUrl.value.filter(ofType(imageExtensions))
const audioFiles = computed(() => filesWithUrl.value.filter(ofType(audioExtensions)))
const videoFiles = computed(() => filesWithUrl.value.filter(ofType(videoExtensions)))
// 非图片/音频/视频的(文档、压缩包等)统一走下载卡片
const downloadFiles = computed(() => filesWithUrl.value.filter((f: DynamicFormValue) => !ofType([...imageExtensions, ...audioExtensions, ...videoExtensions])(f)))
const downloadFiles = computed(() =>
filesWithUrl.value.filter((f: DynamicFormValue) => !ofType([...imageExtensions, ...audioExtensions, ...videoExtensions])(f)),
)

function downloadFile(item: DynamicFormValue) {
downloadByURL(item.url, item.name)
Expand All @@ -56,18 +58,18 @@ const loading = ref<boolean>(false)
const uploadFile = async (file: DynamicFormValue, fileList: DynamicFormValue[]) => {
fileList.splice(fileList.indexOf(file), 1)
if (fileArray.value.find((f: DynamicFormValue) => f.name === file.name)) {
ElMessage.warning('文件名重复')
MsgWarning('文件名重复')

return
}
const maxFileSize = (props.formField as DynamicFormValue).max_file_size
if (file.size / 1024 / 1024 > maxFileSize) {
ElMessage.warning('文件大小不能超过 ' + maxFileSize + 'MB')
MsgWarning('文件大小不能超过 ' + maxFileSize + 'MB')
return
}

if (fileList.length > attrs.limit) {
ElMessage.warning('最多只能上传 ' + attrs.limit + ' 个文件')
MsgWarning('最多只能上传 ' + attrs.limit + ' 个文件')
return
}
upload(file.raw, loading).then((ok: DynamicFormValue) => {
Expand Down
3 changes: 3 additions & 0 deletions ui/src/styles/STYLE_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,9 @@ Tailwind v4 的 `border` 只设置边框宽度和样式,不提供 `--tw-border
所有 `el-button` 的 `:active` 和 `.is-active` 视觉统一复用各自的 hover 样式,不额外加深背景、
边框或文字颜色;普通、plain、text、link、circle 及各语义类型按钮均遵循该规则。

Tree 的 `.el-tree-node__content` 统一保留 4px 底部间距,使同级节点及展开后的父子节点行
保持一致间距;规则维护在 `element-plus.scss` 的 tree 分组。

按钮式多选使用 `el-checkbox-group` 和 `el-checkbox-button`,外观与按钮式单选一致:
组容器提供圆角边框、4px 内边距和间距,选中项使用主题色文字与浅色背景,不显示相邻按钮连接阴影。
规则统一维护在 `element-plus.scss` 的 checkbox 分组;仅包含 `el-checkbox-button` 的组容器
Expand Down
19 changes: 19 additions & 0 deletions ui/src/styles/element-plus.scss
Original file line number Diff line number Diff line change
Expand Up @@ -1172,6 +1172,25 @@
}
}

/* tree */
.el-tree {
--el-tree-node-content-height: 32px;
--el-tree-node-hover-bg-color: var(--mk-N900-transparent-10);
--el-tree-text-color: var(--el-text-color-primary);
--el-tree-expand-icon-color: var(--mk-N600);
.el-tree-node__content {
border-radius: var(--el-border-radius-base);
margin-bottom: var(--spacing);
padding-right: calc(var(--spacing) * 2);
}
.el-tree-node__expand-icon {
font-size: 14px;
}
.el-tree__empty-block {
min-height: 22px;
}
}

/* upload */
.el-upload {
--el-upload-dragger-padding-horizontal: 40px;
Expand Down
Loading