feat(web-admin): wire category tree list markup for scheme A.
Add expand-all control, row meta, and inline add-child on the mp category page. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
ffd25d93e9
commit
5d943cf76b
1 file changed
+308
-237
@@ -1,171 +1,179 @@
|
||||
<template>
|
||||
<MpWebviewShell @ready="onShellReady">
|
||||
<header class="mp-wv-header">
|
||||
<div class="mp-wv-header__row">
|
||||
<button class="mp-wv-header__back" type="button" aria-label="返回" @click="goBack">
|
||||
←
|
||||
</button>
|
||||
<h1 class="mp-wv-header__title">产品分类</h1>
|
||||
<button class="mp-cat-header-add" type="button" @click="openAdd()">新增</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div
|
||||
v-if="loading && !tree.length"
|
||||
class="mp-plan-form-loading mp-plan-form-loading--page"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
aria-busy="true"
|
||||
>
|
||||
<div class="mp-plan-form-loading__orb" aria-hidden="true">
|
||||
<span class="mp-plan-form-loading__dot" />
|
||||
</div>
|
||||
<p class="mp-plan-form-loading__title">正在加载分类</p>
|
||||
<p class="mp-plan-form-loading__desc">请稍候,正在获取产品分类树…</p>
|
||||
<div class="mp-plan-form-loading__skel" aria-hidden="true">
|
||||
<div class="mp-wv-skel mp-wv-skel--block" />
|
||||
<div class="mp-wv-skel mp-wv-skel--mid" />
|
||||
<div class="mp-wv-skel mp-wv-skel--line" />
|
||||
<div class="mp-wv-skel mp-wv-skel--short" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="!flatNodes.length" class="mp-wv-empty">
|
||||
<div class="mp-wv-empty__icon" aria-hidden="true">📁</div>
|
||||
<div class="mp-wv-empty__title">暂无分类</div>
|
||||
<div class="mp-wv-empty__desc">点击右上角「新增」创建顶级分类</div>
|
||||
</div>
|
||||
<div v-else class="mp-cat-list">
|
||||
<div
|
||||
v-for="item in flatNodes"
|
||||
:key="item.node.id"
|
||||
class="mp-cat-row"
|
||||
:style="{ paddingLeft: `${12 + item.depth * 16}px` }"
|
||||
>
|
||||
<div class="mp-product-page">
|
||||
<MpBusinessHeader title="产品分类" back-label="返回产品列表" @back="goBack" />
|
||||
<div class="mp-cat-header-tools">
|
||||
<button
|
||||
class="mp-cat-row__toggle"
|
||||
v-if="flatNodes.length"
|
||||
class="mp-cat-header-btn"
|
||||
type="button"
|
||||
:class="{ 'is-leaf': !item.hasChildren }"
|
||||
:aria-label="item.expanded ? '折叠' : '展开'"
|
||||
:disabled="!item.hasChildren"
|
||||
@click="toggleExpand(item.node.id)"
|
||||
@click="toggleExpandAll"
|
||||
>
|
||||
<span v-if="item.hasChildren">{{ item.expanded ? '▼' : '▶' }}</span>
|
||||
{{ expandAllLabel }}
|
||||
</button>
|
||||
<div class="mp-cat-row__main" @click="openNodeActions(item.node)">
|
||||
<div class="mp-cat-row__name">{{ item.node.categoryName || '—' }}</div>
|
||||
<div class="mp-cat-row__meta">
|
||||
<el-tag
|
||||
:type="dictStore.getTagType('sys_normal_disable', item.node.status)"
|
||||
size="small"
|
||||
<button class="mp-cat-header-btn" type="button" @click="openAdd()">新增</button>
|
||||
</div>
|
||||
|
||||
<div v-if="loading && !tree.length" class="mp-product-loading">
|
||||
<van-loading vertical>正在加载分类</van-loading>
|
||||
</div>
|
||||
<van-empty v-else-if="!flatNodes.length" description="暂无分类,点击右上角新增">
|
||||
<van-button round type="primary" @click="openAdd()">新增分类</van-button>
|
||||
</van-empty>
|
||||
<div v-else class="mp-cat-tree">
|
||||
<div
|
||||
v-for="item in flatNodes"
|
||||
:key="item.node.id"
|
||||
class="mp-cat-row"
|
||||
:class="{
|
||||
'is-l2': item.depth === 1,
|
||||
'is-l3': item.depth >= 2
|
||||
}"
|
||||
:style="{ paddingLeft: `${12 + item.depth * 16}px` }"
|
||||
>
|
||||
<button
|
||||
class="mp-cat-row__toggle"
|
||||
type="button"
|
||||
:class="{ 'is-leaf': !item.hasChildren, 'is-open': item.expanded }"
|
||||
:aria-label="item.expanded ? '折叠' : '展开'"
|
||||
:disabled="!item.hasChildren"
|
||||
@click="toggleExpand(item.node.id)"
|
||||
>
|
||||
<span v-if="item.hasChildren">▶</span>
|
||||
</button>
|
||||
<div class="mp-cat-row__main" @click="openNodeActions(item.node)">
|
||||
<div class="mp-cat-row__name">{{ item.node.categoryName || '—' }}</div>
|
||||
<div class="mp-cat-row__meta">
|
||||
<span
|
||||
class="status-tag"
|
||||
:class="`status-tag--${statusTone(item.node.status)}`"
|
||||
>
|
||||
{{ dictStore.getLabel('sys_normal_disable', item.node.status) || '—' }}
|
||||
</span>
|
||||
<span>排序 {{ item.node.orderNum ?? 0 }}</span>
|
||||
<span>{{ categoryChildMetaLabel(item.node) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="mp-cat-row__add"
|
||||
type="button"
|
||||
aria-label="新增子级"
|
||||
@click="openAdd(item.node)"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
<button
|
||||
class="mp-cat-row__more"
|
||||
type="button"
|
||||
aria-label="操作"
|
||||
@click="openNodeActions(item.node)"
|
||||
>
|
||||
⋯
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<van-action-sheet
|
||||
v-model:show="actionOpen"
|
||||
teleport="body"
|
||||
:actions="actionSheetActions"
|
||||
cancel-text="取消"
|
||||
close-on-click-action
|
||||
@select="onActionSelect"
|
||||
/>
|
||||
|
||||
<van-popup
|
||||
:show="formOpen"
|
||||
position="bottom"
|
||||
round
|
||||
teleport="body"
|
||||
class="mp-customer-vant-popup"
|
||||
:style="{ height: '82%' }"
|
||||
:close-on-click-overlay="!submitLoading"
|
||||
@update:show="onFormShow"
|
||||
>
|
||||
<div class="mp-customer-pool-vant">
|
||||
<van-nav-bar
|
||||
:title="formTitle"
|
||||
left-text="关闭"
|
||||
:left-disabled="submitLoading"
|
||||
@click-left="closeForm"
|
||||
/>
|
||||
<div class="mp-customer-pool-vant__body">
|
||||
<van-cell-group inset>
|
||||
<van-field
|
||||
is-link
|
||||
readonly
|
||||
label="上级分类"
|
||||
:model-value="parentLabel"
|
||||
placeholder="选择上级分类(留空为顶级)"
|
||||
@click="parentPickerOpen = true"
|
||||
/>
|
||||
<van-field
|
||||
v-model="formData.categoryName"
|
||||
label="分类名称"
|
||||
required
|
||||
placeholder="请输入分类名称"
|
||||
:error-message="nameError"
|
||||
/>
|
||||
<van-field
|
||||
v-model="orderNumText"
|
||||
type="digit"
|
||||
label="排序号"
|
||||
placeholder="数值越小越靠前"
|
||||
/>
|
||||
<van-field
|
||||
is-link
|
||||
readonly
|
||||
label="状态"
|
||||
:model-value="statusLabel"
|
||||
placeholder="请选择状态"
|
||||
@click="statusPickerOpen = true"
|
||||
/>
|
||||
<van-field
|
||||
v-model="formData.remark"
|
||||
rows="3"
|
||||
autosize
|
||||
type="textarea"
|
||||
label="备注"
|
||||
placeholder="请输入备注"
|
||||
/>
|
||||
</van-cell-group>
|
||||
</div>
|
||||
<div class="mp-customer-pool-vant__foot">
|
||||
<van-button round :disabled="submitLoading" @click="closeForm">取消</van-button>
|
||||
<van-button round type="primary" :loading="submitLoading" @click="submitForm"
|
||||
>确定</van-button
|
||||
>
|
||||
{{ dictStore.getLabel('sys_normal_disable', item.node.status) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="mp-cat-row__more"
|
||||
type="button"
|
||||
aria-label="操作"
|
||||
@click="openNodeActions(item.node)"
|
||||
>
|
||||
⋯
|
||||
</button>
|
||||
</div>
|
||||
</van-popup>
|
||||
|
||||
<van-popup v-model:show="parentPickerOpen" position="bottom" round teleport="body">
|
||||
<van-picker
|
||||
:columns="parentPickerColumns"
|
||||
:model-value="[formData.parentId || '0']"
|
||||
@confirm="onParentConfirm"
|
||||
@cancel="parentPickerOpen = false"
|
||||
/>
|
||||
</van-popup>
|
||||
|
||||
<van-popup v-model:show="statusPickerOpen" position="bottom" round teleport="body">
|
||||
<van-picker
|
||||
:columns="statusPickerColumns"
|
||||
:model-value="[formData.status || '0']"
|
||||
@confirm="onStatusConfirm"
|
||||
@cancel="statusPickerOpen = false"
|
||||
/>
|
||||
</van-popup>
|
||||
</div>
|
||||
|
||||
<MpBottomSheet v-model="actionOpen" title="分类操作" half>
|
||||
<button class="mp-wv-filter-opt" type="button" @click="onActionAddChild">新增子级</button>
|
||||
<button class="mp-wv-filter-opt" type="button" @click="onActionEdit">编辑</button>
|
||||
<button
|
||||
class="mp-wv-filter-opt is-danger"
|
||||
type="button"
|
||||
:disabled="!canDeleteActionNode"
|
||||
:title="canDeleteActionNode ? '' : CREATOR_DELETE_TIP"
|
||||
@click="onActionDelete"
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
</MpBottomSheet>
|
||||
|
||||
<MpBottomSheet v-model="formOpen" :title="formTitle">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
class="mp-cat-form"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-position="top"
|
||||
scroll-to-error
|
||||
:scroll-into-view-options="{ behavior: 'smooth', block: 'center' }"
|
||||
>
|
||||
<el-form-item label="上级分类">
|
||||
<el-tree-select
|
||||
v-model="formData.parentId"
|
||||
:data="categoryTreeOptions"
|
||||
node-key="id"
|
||||
:props="{ label: 'categoryName', children: 'children' }"
|
||||
value-key="id"
|
||||
check-strictly
|
||||
filterable
|
||||
clearable
|
||||
placeholder="选择上级分类(留空为顶级)"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="分类名称" prop="categoryName">
|
||||
<el-input v-model="formData.categoryName" placeholder="请输入分类名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序号">
|
||||
<el-input-number
|
||||
v-model="formData.orderNum"
|
||||
controls-position="right"
|
||||
:min="0"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="formData.status" placeholder="请选择状态" style="width: 100%">
|
||||
<el-option
|
||||
v-for="opt in dictStore.getOptions('sys_normal_disable')"
|
||||
:key="opt.value"
|
||||
:label="opt.label"
|
||||
:value="opt.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="formData.remark" type="textarea" placeholder="请输入备注" :rows="3" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<button
|
||||
class="mp-wv-btn mp-cat-foot-btn"
|
||||
type="button"
|
||||
:disabled="submitLoading"
|
||||
@click="formOpen = false"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
class="mp-wv-btn mp-wv-btn--primary mp-cat-foot-btn"
|
||||
type="button"
|
||||
:disabled="submitLoading"
|
||||
@click="submitForm"
|
||||
>
|
||||
<span v-if="submitLoading" class="mp-wv-spinner mp-wv-spinner--sm" aria-hidden="true" />
|
||||
<span>{{ submitLoading ? '提交中…' : '确定' }}</span>
|
||||
</button>
|
||||
</template>
|
||||
</MpBottomSheet>
|
||||
</MpWebviewShell>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import type { FormInstance } from 'element-plus'
|
||||
import { showConfirmDialog, showFailToast, showSuccessToast } from 'vant'
|
||||
import MpWebviewShell from '@/layouts/MpWebviewShell.vue'
|
||||
import MpBottomSheet from '@/components/mp/MpBottomSheet.vue'
|
||||
import MpBusinessHeader from '@/components/mp/MpBusinessHeader.vue'
|
||||
import {
|
||||
fetchCategoryTree,
|
||||
fetchCreateCategory,
|
||||
@@ -174,8 +182,14 @@
|
||||
} from '@/api/crm/product'
|
||||
import { useDictStore } from '@/store/modules/dict'
|
||||
import { canDeleteByCreator, CREATOR_DELETE_TIP } from '@/utils/creator-delete'
|
||||
import {
|
||||
areAllExpandableExpanded,
|
||||
categoryChildMetaLabel,
|
||||
collectExpandIds
|
||||
} from '@/utils/mp-crm-product-category-tree'
|
||||
import { MP_CRM_PRODUCT_LIST_PATH } from '@/utils/mp-webview'
|
||||
import '@/styles/mp-webview.scss'
|
||||
import './product.scss'
|
||||
|
||||
type CategoryNode = {
|
||||
id: string
|
||||
@@ -209,8 +223,10 @@
|
||||
const formOpen = ref(false)
|
||||
const formTitle = ref('')
|
||||
const submitLoading = ref(false)
|
||||
const formRef = ref<FormInstance>()
|
||||
const categoryTreeOptions = ref<CategoryNode[]>([])
|
||||
const nameError = ref('')
|
||||
const parentPickerOpen = ref(false)
|
||||
const statusPickerOpen = ref(false)
|
||||
const flatParentOptions = ref<Array<{ text: string; value: string }>>([])
|
||||
const formData = reactive({
|
||||
id: '',
|
||||
parentId: '' as string,
|
||||
@@ -219,14 +235,51 @@
|
||||
status: '0',
|
||||
remark: ''
|
||||
})
|
||||
const formRules = {
|
||||
categoryName: [{ required: true, message: '分类名称不能为空', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
const orderNumText = computed({
|
||||
get: () => String(formData.orderNum ?? 0),
|
||||
set: (v: string) => {
|
||||
formData.orderNum = Number(v || 0)
|
||||
}
|
||||
})
|
||||
|
||||
const canDeleteActionNode = computed(() =>
|
||||
canDeleteByCreator(actionNode.value as Record<string, unknown> | null)
|
||||
)
|
||||
|
||||
const actionSheetActions = computed(() => [
|
||||
{ name: '新增子级', value: 'add' },
|
||||
{ name: '编辑', value: 'edit' },
|
||||
{
|
||||
name: '删除',
|
||||
value: 'delete',
|
||||
color: '#dc2626',
|
||||
disabled: !canDeleteActionNode.value
|
||||
}
|
||||
])
|
||||
|
||||
const parentPickerColumns = computed(() => [
|
||||
{ text: '顶级分类', value: '0' },
|
||||
...flatParentOptions.value
|
||||
])
|
||||
|
||||
const statusPickerColumns = computed(() =>
|
||||
dictStore.getOptions('sys_normal_disable').map((o) => ({
|
||||
text: o.label,
|
||||
value: String(o.value)
|
||||
}))
|
||||
)
|
||||
|
||||
const parentLabel = computed(() => {
|
||||
const id = formData.parentId || '0'
|
||||
if (id === '0' || !id) return '顶级分类'
|
||||
return parentPickerColumns.value.find((c) => c.value === id)?.text || id
|
||||
})
|
||||
|
||||
const statusLabel = computed(() => {
|
||||
return dictStore.getLabel('sys_normal_disable', formData.status) || ''
|
||||
})
|
||||
|
||||
const flatNodes = computed((): FlatItem[] => {
|
||||
const out: FlatItem[] = []
|
||||
const walk = (nodes: CategoryNode[], depth: number) => {
|
||||
@@ -242,27 +295,29 @@
|
||||
return out
|
||||
})
|
||||
|
||||
function collectExpandIds(nodes: CategoryNode[], acc: Record<string, boolean> = {}) {
|
||||
for (const node of nodes) {
|
||||
const children = Array.isArray(node.children) ? node.children : []
|
||||
if (children.length) {
|
||||
acc[node.id] = true
|
||||
collectExpandIds(children, acc)
|
||||
}
|
||||
}
|
||||
return acc
|
||||
function statusTone(status?: string) {
|
||||
if (String(status) === '1') return 'danger'
|
||||
return 'success'
|
||||
}
|
||||
|
||||
function findNodeById(nodes: CategoryNode[], id: string): CategoryNode | null {
|
||||
for (const node of nodes) {
|
||||
if (node.id === id) return node
|
||||
const children = Array.isArray(node.children) ? node.children : []
|
||||
if (children.length) {
|
||||
const found = findNodeById(children, id)
|
||||
if (found) return found
|
||||
}
|
||||
function flattenParents(nodes: CategoryNode[], parents: string[] = []): Array<{ text: string; value: string }> {
|
||||
return nodes.flatMap((node) => {
|
||||
const path = [...parents, String(node.categoryName || '')].filter(Boolean)
|
||||
const current = node.id ? [{ text: path.join(' / '), value: String(node.id) }] : []
|
||||
return current.concat(flattenParents(node.children || [], path))
|
||||
})
|
||||
}
|
||||
|
||||
const expandAllLabel = computed(() =>
|
||||
areAllExpandableExpanded(tree.value, expandedIds.value) ? '全部折叠' : '全部展开'
|
||||
)
|
||||
|
||||
function toggleExpandAll() {
|
||||
if (areAllExpandableExpanded(tree.value, expandedIds.value)) {
|
||||
expandedIds.value = {}
|
||||
return
|
||||
}
|
||||
return null
|
||||
expandedIds.value = collectExpandIds(tree.value)
|
||||
}
|
||||
|
||||
async function loadTree() {
|
||||
@@ -277,17 +332,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function getCategoryTreeSelect() {
|
||||
async function refreshParentOptions() {
|
||||
const res = await fetchCategoryTree({})
|
||||
const data = ((res as { data?: CategoryNode[] }).data || res || []) as CategoryNode[]
|
||||
categoryTreeOptions.value = [
|
||||
{
|
||||
id: '0',
|
||||
categoryName: '顶级分类',
|
||||
parentId: '0',
|
||||
children: Array.isArray(data) ? data : []
|
||||
}
|
||||
]
|
||||
flatParentOptions.value = flattenParents(Array.isArray(data) ? data : [])
|
||||
}
|
||||
|
||||
function toggleExpand(id: string) {
|
||||
@@ -312,16 +360,14 @@
|
||||
formData.orderNum = 0
|
||||
formData.status = '0'
|
||||
formData.remark = ''
|
||||
formRef.value?.resetFields()
|
||||
nameError.value = ''
|
||||
}
|
||||
|
||||
async function openAdd(parentRow?: CategoryNode) {
|
||||
actionOpen.value = false
|
||||
resetForm()
|
||||
await getCategoryTreeSelect()
|
||||
if (parentRow?.id) {
|
||||
formData.parentId = parentRow.id
|
||||
}
|
||||
await refreshParentOptions()
|
||||
if (parentRow?.id) formData.parentId = parentRow.id
|
||||
formTitle.value = '新增产品分类'
|
||||
formOpen.value = true
|
||||
}
|
||||
@@ -329,7 +375,7 @@
|
||||
async function openEdit(node: CategoryNode) {
|
||||
actionOpen.value = false
|
||||
resetForm()
|
||||
await getCategoryTreeSelect()
|
||||
await refreshParentOptions()
|
||||
formData.id = node.id
|
||||
formData.parentId = node.parentId || ''
|
||||
formData.categoryName = node.categoryName || ''
|
||||
@@ -345,68 +391,79 @@
|
||||
actionOpen.value = true
|
||||
}
|
||||
|
||||
function onActionAddChild() {
|
||||
function onActionSelect(action: { value?: string }) {
|
||||
const node = actionNode.value
|
||||
if (!node) return
|
||||
void openAdd(node)
|
||||
}
|
||||
|
||||
function onActionEdit() {
|
||||
const node = actionNode.value
|
||||
if (!node) return
|
||||
void openEdit(node)
|
||||
if (action.value === 'add') void openAdd(node)
|
||||
else if (action.value === 'edit') void openEdit(node)
|
||||
else if (action.value === 'delete') void onActionDelete()
|
||||
}
|
||||
|
||||
async function onActionDelete() {
|
||||
const node = actionNode.value
|
||||
if (!node?.id) return
|
||||
if (!canDeleteByCreator(node as Record<string, unknown>)) {
|
||||
ElMessage.warning(CREATOR_DELETE_TIP)
|
||||
showFailToast(CREATOR_DELETE_TIP)
|
||||
return
|
||||
}
|
||||
try {
|
||||
await ElMessageBox.confirm('是否确认删除该产品分类?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
await showConfirmDialog({ title: '提示', message: '是否确认删除该产品分类?' })
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
actionOpen.value = false
|
||||
try {
|
||||
await fetchDeleteCategory({ id: node.id })
|
||||
ElMessage.success('删除成功')
|
||||
showSuccessToast('删除成功')
|
||||
actionNode.value = null
|
||||
await loadTree()
|
||||
} catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : '删除失败')
|
||||
showFailToast(error instanceof Error ? error.message : '删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
function onFormShow(show: boolean) {
|
||||
if (!submitLoading.value) formOpen.value = show
|
||||
}
|
||||
function closeForm() {
|
||||
if (!submitLoading.value) formOpen.value = false
|
||||
}
|
||||
function onParentConfirm({ selectedValues }: { selectedValues: string[] }) {
|
||||
const v = selectedValues[0] || '0'
|
||||
formData.parentId = v === '0' ? '' : v
|
||||
parentPickerOpen.value = false
|
||||
}
|
||||
function onStatusConfirm({ selectedValues }: { selectedValues: string[] }) {
|
||||
formData.status = selectedValues[0] || '0'
|
||||
statusPickerOpen.value = false
|
||||
}
|
||||
|
||||
async function submitForm() {
|
||||
const valid = await formRef.value?.validate().catch(() => false)
|
||||
if (!valid) return
|
||||
if (!formData.categoryName.trim()) {
|
||||
nameError.value = '分类名称不能为空'
|
||||
return
|
||||
}
|
||||
nameError.value = ''
|
||||
submitLoading.value = true
|
||||
try {
|
||||
const payload = {
|
||||
parentId: formData.parentId || '0',
|
||||
categoryName: formData.categoryName,
|
||||
categoryName: formData.categoryName.trim(),
|
||||
orderNum: formData.orderNum,
|
||||
status: formData.status,
|
||||
remark: formData.remark
|
||||
}
|
||||
if (formData.id) {
|
||||
await fetchUpdateCategory({ id: formData.id, ...payload })
|
||||
ElMessage.success('修改成功')
|
||||
showSuccessToast('修改成功')
|
||||
} else {
|
||||
await fetchCreateCategory(payload)
|
||||
ElMessage.success('新增成功')
|
||||
showSuccessToast('新增成功')
|
||||
}
|
||||
formOpen.value = false
|
||||
await loadTree()
|
||||
} catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : '保存失败')
|
||||
showFailToast(error instanceof Error ? error.message : '保存失败')
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
@@ -418,17 +475,24 @@
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.mp-product-page {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mp-cat-header-add {
|
||||
flex-shrink: 0;
|
||||
position: absolute;
|
||||
top: calc(8px + env(safe-area-inset-top, 0px));
|
||||
right: 16px;
|
||||
z-index: 60;
|
||||
height: 32px;
|
||||
padding: 0 12px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.75);
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
background: rgb(255 255 255 / 15%);
|
||||
border: 1px solid rgb(255 255 255 / 75%);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.mp-cat-header-add:active {
|
||||
@@ -439,13 +503,13 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding-bottom: 24px;
|
||||
padding: 12px 10px 24px;
|
||||
}
|
||||
|
||||
.mp-cat-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
min-height: 52px;
|
||||
padding: 8px 10px 8px 0;
|
||||
background: #fff;
|
||||
@@ -458,13 +522,13 @@
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #0a9f6d;
|
||||
font-size: 11px;
|
||||
line-height: 28px;
|
||||
color: #0a9f6d;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.mp-cat-row__toggle.is-leaf {
|
||||
@@ -493,28 +557,35 @@
|
||||
flex-shrink: 0;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
background: #e9f9f2;
|
||||
color: #087f57;
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
color: #087f57;
|
||||
cursor: pointer;
|
||||
background: #e9f9f2;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.mp-cat-form {
|
||||
padding: 4px 8px 0;
|
||||
.status-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 22px;
|
||||
padding: 2px 8px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.mp-cat-form :deep(.el-form-item) {
|
||||
margin-bottom: 12px;
|
||||
.status-tag--success {
|
||||
color: #087f57;
|
||||
background: #e9f9f2;
|
||||
border-color: #b7ebce;
|
||||
}
|
||||
|
||||
.mp-cat-foot-btn {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.is-danger {
|
||||
color: #dc2626 !important;
|
||||
.status-tag--danger {
|
||||
color: #b91c1c;
|
||||
background: #fef2f2;
|
||||
border-color: #fecaca;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user