docs(readme): 更新文档说明
Build and Deploy Vue3 / build (push) Successful in 1m29s
Build and Deploy Vue3 / deploy (push) Successful in 1m14s

- 添加项目使用指南
- 完善API接口描述
- 修正错误的配置示例
This commit is contained in:
shiran
2026-04-23 17:43:31 +08:00
parent 2e073c2b87
commit c0daa6ed11
6 changed files with 2734 additions and 1366 deletions
+587 -198
View File
@@ -91,12 +91,16 @@
>
<el-table-column prop="name" label="名称" min-width="320">
<template #default="{ row }">
<div class="group-name-cell" :style="{ paddingLeft: row.isProduct ? ((row.level - 1) * 24 + 24) + 'px' : (row.level - 1) * 24 + 'px' }">
<div
class="group-name-cell"
:class="{ 'is-clickable': row.isGroup || row.isProduct }"
:style="{ paddingLeft: row.isProduct ? ((row.level - 1) * 24 + 24) + 'px' : (row.level - 1) * 24 + 'px' }"
@click="handleNameCellClick(row)"
>
<!-- 分组的展开/收起按钮 -->
<span
v-if="row.isGroup"
<span
v-if="row.isGroup"
class="expand-icon"
@click="toggleExpand(row)"
>
<el-icon v-if="row._loading"><Loading /></el-icon>
<el-icon v-else :class="{ 'is-expanded': row._expanded }"><ArrowRight /></el-icon>
@@ -270,119 +274,134 @@
<el-dialog
v-model="dialogVisible"
:title="dialogTitle"
width="600px"
width="860px"
append-to-body
class="product-form-dialog"
>
<el-form
ref="groupFormRef"
:model="groupForm"
:rules="groupRules"
label-width="100px"
label-position="top"
class="product-form"
>
<el-form-item label="分组名称" prop="name">
<el-input v-model="groupForm.name" placeholder="请输入分组名称" />
</el-form-item>
<el-form-item label="父级分组" prop="parent_id">
<div class="recommend-user-selector">
<el-input
:model-value="selectedParentName"
:placeholder="isAddingChild ? '已自动设置父级分组' : '无父级(顶级分组)'"
readonly
:disabled="isAddingChild"
@click="!isAddingChild && (showParentSelector = true)"
<!-- 顶部封面 + 基本信息 -->
<div class="product-form-header">
<!-- 左侧封面 -->
<div class="cover-uploader" @click="showCoverSelector = true">
<img v-if="groupForm.cover_url" :src="groupForm.cover_url" class="cover-image" alt="分组封面" />
<div v-else class="cover-placeholder">
<el-icon class="cover-placeholder-icon"><Folder /></el-icon>
<span class="cover-placeholder-text">点击选择封面</span>
</div>
<div class="cover-mask">
<el-icon><Picture /></el-icon>
<span>更换封面</span>
</div>
<el-button
v-if="groupForm.cover_id"
type="danger"
size="small"
circle
class="cover-clear-btn"
@click.stop="clearGroupCover"
>
<template #append>
<el-button @click="!isAddingChild && (showParentSelector = true)" :disabled="isAddingChild">
<el-icon><Search /></el-icon>
</el-button>
</template>
</el-input>
<el-button
v-if="groupForm.parent_id && !isAddingChild"
type="danger"
link
@click="clearParent"
class="clear-btn"
>
清除
<el-icon><Delete /></el-icon>
</el-button>
</div>
<div v-if="isAddingChild" class="form-tip">
添加子级时自动继承父级分组不可修改
</div>
</el-form-item>
<el-form-item label="分组层级" prop="level">
<el-input :model-value="`${groupForm.level}级`" disabled style="width: 100%" />
<div class="form-tip">层级根据父级分组自动计算</div>
</el-form-item>
<el-form-item label="分组封面" prop="cover_id">
<div class="recommend-user-selector">
<el-input
:model-value="groupForm.cover_id ? `文件ID: ${groupForm.cover_id}` : ''"
placeholder="点击选择封面图片"
readonly
@click="showCoverSelector = true"
>
<template #append>
<el-button @click="showCoverSelector = true">
<el-icon><Search /></el-icon>
<!-- 右侧基本信息 -->
<div class="basic-fields">
<el-form-item label="分组名称" prop="name">
<el-input v-model="groupForm.name" placeholder="请输入分组名称" />
</el-form-item>
<el-form-item label="父级分组" prop="parent_id">
<div
class="group-picker"
:class="{ 'is-empty': !selectedParentName, 'is-disabled': isAddingChild }"
@click="!isAddingChild && (showParentSelector = true)"
>
<el-icon class="group-picker-icon"><Folder /></el-icon>
<span class="group-picker-text">
{{ selectedParentName || (isAddingChild ? '已自动设置父级分组' : '无父级(顶级分组)') }}
</span>
<el-button
v-if="groupForm.parent_id && !isAddingChild"
type="danger"
link
size="small"
class="group-picker-clear"
@click.stop="clearParent"
>
清除
</el-button>
</template>
</el-input>
<el-button
v-if="groupForm.cover_id"
type="danger"
link
@click="groupForm.cover_id = undefined"
class="clear-btn"
>
清除
</el-button>
</div>
</el-form-item>
<el-form-item label="分组标签" prop="tag_id">
<div class="recommend-user-selector">
<el-input
:model-value="selectedTagName"
placeholder="点击选择分组标签"
readonly
@click="showTagSelector = true"
>
<template #append>
<el-button @click="showTagSelector = true">
<el-icon><Search /></el-icon>
<el-icon v-if="!isAddingChild" class="group-picker-arrow"><ArrowRight /></el-icon>
</div>
<div v-if="isAddingChild" class="form-tip">
添加子级时自动继承父级分组不可修改
</div>
</el-form-item>
<el-form-item label="分组标签" prop="tag_id">
<div
class="group-picker"
:class="{ 'is-empty': !selectedTagName }"
@click="showTagSelector = true"
>
<el-icon class="group-picker-icon"><CollectionTag /></el-icon>
<span class="group-picker-text">{{ selectedTagName || '点击选择分组标签' }}</span>
<el-button
v-if="groupForm.tag_id"
type="danger"
link
size="small"
class="group-picker-clear"
@click.stop="clearTag"
>
清除
</el-button>
</template>
</el-input>
<el-button
v-if="groupForm.tag_id"
type="danger"
link
@click="clearTag"
class="clear-btn"
>
清除
</el-button>
<el-icon class="group-picker-arrow"><ArrowRight /></el-icon>
</div>
</el-form-item>
</div>
</el-form-item>
<el-form-item label="备注" prop="note">
<el-input v-model="groupForm.note" type="textarea" :rows="4" placeholder="请输入备注" />
</el-form-item>
<el-form-item label="排序索引" prop="index">
<el-input-number
v-model="groupForm.index"
:min="0"
:max="9999"
placeholder="请输入排序索引,数值越小排序越靠前"
style="width: 100%"
</div>
<!-- 基本属性层级 / 排序 / 状态 -->
<div class="form-section">
<div class="form-section-title">基本属性</div>
<div class="form-grid">
<el-form-item label="分组层级" prop="level">
<el-input :model-value="`${groupForm.level} `" disabled style="width: 100%" />
<div class="form-tip">层级根据父级分组自动计算</div>
</el-form-item>
<el-form-item label="排序索引" prop="index">
<el-input-number
v-model="groupForm.index"
:min="0"
:max="9999"
controls-position="right"
placeholder="数值越小越靠前"
style="width: 100%"
/>
<div class="form-tip">数值越小排序越靠前相同索引按创建时间排序</div>
</el-form-item>
<el-form-item label="状态" prop="disable" class="group-status-item">
<el-radio-group v-model="groupForm.disable">
<el-radio-button :value="false">启用</el-radio-button>
<el-radio-button :value="true">禁用</el-radio-button>
</el-radio-group>
</el-form-item>
</div>
</div>
<!-- 备注 -->
<el-form-item prop="note" label="备注" class="note-form-item">
<el-input
v-model="groupForm.note"
type="textarea"
:rows="4"
placeholder="请输入备注(可选)"
resize="none"
/>
<div class="form-tip">数值越小排序越靠前相同索引按创建时间排序</div>
</el-form-item>
<el-form-item label="状态" prop="disable">
<el-radio-group v-model="groupForm.disable">
<el-radio :value="false">启用</el-radio>
<el-radio :value="true">禁用</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
@@ -486,71 +505,80 @@
<el-dialog
v-model="productDialogVisible"
:title="productDialogType === 'add' ? '新增商品' : '编辑商品'"
width="800px"
width="860px"
append-to-body
class="product-form-dialog"
>
<el-form
ref="productFormRef"
:model="productForm"
:rules="productRules"
label-width="120px"
label-position="top"
class="product-form"
>
<el-form-item label="商品名称" prop="name">
<el-input v-model="productForm.name" placeholder="请输入商品名称" />
</el-form-item>
<el-form-item label="商品所属表" prop="table">
<el-input v-model="productForm.table" placeholder="请输入商品所属表" />
</el-form-item>
<el-form-item label="商品分组" prop="good_group_id">
<div class="recommend-user-selector">
<el-input
:model-value="selectedGroupName"
placeholder="请选择商品分组"
readonly
@click="showProductGroupSelector = true"
<!-- 顶部封面 + 基本信息 -->
<div class="product-form-header">
<!-- 左侧封面 -->
<div class="cover-uploader" @click="coverSelectorVisible = true">
<img v-if="productForm.cover_url" :src="productForm.cover_url" class="cover-image" alt="商品封面" />
<div v-else class="cover-placeholder">
<el-icon class="cover-placeholder-icon"><Picture /></el-icon>
<span class="cover-placeholder-text">点击选择封面</span>
</div>
<div class="cover-mask">
<el-icon><Picture /></el-icon>
<span>更换封面</span>
</div>
<el-button
v-if="productForm.cover_id"
type="danger"
size="small"
circle
class="cover-clear-btn"
@click.stop="clearProductCover"
>
<template #prefix>
<el-icon><Folder /></el-icon>
</template>
</el-input>
<el-button
v-if="productForm.good_group_id"
type="danger"
link
@click="clearProductGroup"
class="clear-btn"
>
清除
<el-icon><Delete /></el-icon>
</el-button>
</div>
</el-form-item>
<el-form-item label="商品内容" prop="content">
<el-input v-model="productForm.content" type="textarea" :rows="4" placeholder="请输入商品内容" />
</el-form-item>
<el-form-item label="商品封面" prop="cover_id">
<div class="recommend-user-selector">
<el-input
:model-value="productForm.cover_id ? `文件ID: ${productForm.cover_id}` : ''"
placeholder="点击选择封面图片"
readonly
@click="coverSelectorVisible = true"
>
<template #prefix>
<el-icon><Picture /></el-icon>
</template>
</el-input>
<el-button
v-if="productForm.cover_id"
type="danger"
link
@click="clearProductCover"
class="clear-btn"
>
清除
</el-button>
<!-- 右侧基本信息 -->
<div class="basic-fields">
<el-form-item label="商品名称" prop="name">
<el-input v-model="productForm.name" placeholder="请输入商品名称" />
</el-form-item>
<el-form-item label="所属分组" prop="good_group_id">
<div
class="group-picker"
:class="{ 'is-empty': !selectedGroupName }"
@click="showProductGroupSelector = true"
>
<el-icon class="group-picker-icon"><Folder /></el-icon>
<span class="group-picker-text">{{ selectedGroupName || '点击选择商品分组' }}</span>
<el-button
v-if="productForm.good_group_id"
type="danger"
link
size="small"
class="group-picker-clear"
@click.stop="clearProductGroup"
>
清除
</el-button>
<el-icon class="group-picker-arrow"><ArrowRight /></el-icon>
</div>
</el-form-item>
<el-form-item label="商品介绍" prop="content">
<el-input
v-model="productForm.content"
type="textarea"
:rows="4"
placeholder="请输入商品介绍"
resize="none"
/>
</el-form-item>
</div>
</el-form-item>
</div>
<!-- 封面选择器弹窗 -->
<AvatarSelector
v-model="coverSelectorVisible"
@@ -559,45 +587,119 @@
title="选择封面"
@confirm="handleProductCoverSelect"
/>
<el-form-item label="库存控制" prop="inventory_control">
<el-switch v-model="productForm.inventory_control" active-text="启用" inactive-text="禁用" />
</el-form-item>
<el-form-item label="库存数量" prop="inventory">
<el-input-number v-model="productForm.inventory" :min="0" placeholder="请输入库存" style="width: 100%" />
</el-form-item>
<el-form-item label="商品价格" prop="price">
<div class="unit-input-row">
<el-input-number v-model="productForm.price" :min="0" :precision="2" :step="0.01" placeholder="请输入价格(元)" style="flex:1" />
<span class="unit-text"></span>
<!-- 价格与销售 -->
<div class="form-section">
<div class="form-section-title">价格与销售</div>
<div class="form-grid">
<el-form-item prop="price">
<template #label>
<span>商品价格<span class="unit-suffix"></span></span>
</template>
<el-input-number
v-model="productForm.price"
:min="0"
:precision="2"
:step="0.01"
placeholder="请输入价格"
controls-position="right"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="单个商品数量" prop="pay_num">
<el-input-number
v-model="productForm.pay_num"
:min="1"
placeholder="请输入数量"
controls-position="right"
style="width: 100%"
/>
</el-form-item>
<el-form-item prop="expire_time">
<template #label>
<span>有效期<span class="unit-suffix">0 为永久</span></span>
</template>
<el-input-number
v-model="productForm.expire_time"
:min="0"
placeholder="请输入有效期"
controls-position="right"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="允许续费" prop="can_renew">
<el-switch
v-model="productForm.can_renew"
active-text="允许"
inactive-text="禁止"
/>
</el-form-item>
</div>
</el-form-item>
<el-form-item label="单个商品数量" prop="pay_num">
<el-input-number v-model="productForm.pay_num" :min="1" placeholder="请输入单个商品数量" style="width: 100%" />
</el-form-item>
<el-form-item label="有效期" prop="expire_time">
<div class="unit-input-row">
<el-input-number v-model="productForm.expire_time" :min="0" placeholder="请输入有效期" style="flex:1" />
<span class="unit-text"></span>
</div>
<!-- 库存管理 -->
<div class="form-section">
<div class="form-section-title">库存管理</div>
<div class="form-grid">
<el-form-item label="库存控制" prop="inventory_control">
<el-switch
v-model="productForm.inventory_control"
active-text="启用"
inactive-text="禁用"
/>
</el-form-item>
<el-form-item label="库存数量" prop="inventory">
<el-input-number
v-model="productForm.inventory"
:min="0"
:disabled="!productForm.inventory_control"
placeholder="请输入库存"
controls-position="right"
style="width: 100%"
/>
</el-form-item>
</div>
</el-form-item>
<el-form-item label="推荐" prop="recommend">
<el-switch v-model="productForm.recommend" active-text="启用" inactive-text="禁用" />
</el-form-item>
<el-form-item label="推荐返还(%)" prop="recommend_rebate">
<el-input-number v-model="productForm.recommend_rebate" :min="0" :max="100" placeholder="请输入返还百分比" style="width: 100%" />
</el-form-item>
<el-form-item label="参数类型" prop="arg_type">
<el-select v-model="productForm.arg_type" placeholder="请选择参数类型" style="width: 100%">
<el-option label="所有参数" value="all" />
<el-option label="套餐" value="plan" />
<el-option label="自定义" value="customize" />
</el-select>
<div class="form-tip">all: 所有参数 / plan: 套餐 / customize: 自定义</div>
</el-form-item>
<el-form-item label="允许续费" prop="can_renew">
<el-switch v-model="productForm.can_renew" active-text="允许" inactive-text="禁止" />
</el-form-item>
</div>
<!-- 推荐与参数 -->
<div class="form-section">
<div class="form-section-title">推荐与参数</div>
<div class="form-grid">
<el-form-item label="推荐" prop="recommend">
<el-switch
v-model="productForm.recommend"
active-text="启用"
inactive-text="禁用"
/>
</el-form-item>
<el-form-item prop="recommend_rebate">
<template #label>
<span>推荐返还<span class="unit-suffix">%</span></span>
</template>
<el-input-number
v-model="productForm.recommend_rebate"
:min="0"
:max="100"
:disabled="!productForm.recommend"
placeholder="返还百分比"
controls-position="right"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="参数类型" prop="arg_type" class="arg-type-item">
<el-select
v-model="productForm.arg_type"
placeholder="请选择参数类型"
style="width: 100%"
>
<el-option label="所有参数" value="all" />
<el-option label="套餐" value="plan" />
<el-option label="自定义" value="customize" />
</el-select>
<div class="form-tip">all: 所有参数 / plan: 套餐 / customize: 自定义</div>
</el-form-item>
</div>
</div>
</el-form>
<template #footer>
<div class="dialog-footer">
@@ -657,7 +759,7 @@
<script setup>
import { ref, reactive, computed, onMounted, watch } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Refresh, Search, Folder, ArrowRight, Loading, Grid, List, Document, Picture } from '@element-plus/icons-vue'
import { Plus, Refresh, Search, Folder, ArrowRight, Loading, Grid, List, Document, Picture, Delete, CollectionTag } from '@element-plus/icons-vue'
import {
getProductGroupList,
createProductGroup,
@@ -712,6 +814,7 @@ const groupForm = reactive({
level: 1,
parent_id: undefined,
cover_id: undefined,
cover_url: '',
tag_id: undefined,
index: 0
})
@@ -752,6 +855,7 @@ const productForm = reactive({
tag: '',
content: '',
cover_id: undefined,
cover_url: '',
good_group_id: undefined,
inventory_control: false,
inventory: 0,
@@ -768,9 +872,6 @@ const productRules = {
name: [
{ required: true, message: '请输入商品名称', trigger: 'blur' }
],
table: [
{ required: true, message: '请输入商品所属表', trigger: 'blur' }
],
content: [
{ required: true, message: '请输入商品内容', trigger: 'blur' }
],
@@ -928,6 +1029,14 @@ const loadProductsForGroup = async (groupId) => {
}
}
const handleNameCellClick = (row) => {
if (row.isGroup) {
toggleExpand(row)
} else if (row.isProduct) {
handleEditProduct(row.data, row.parentId)
}
}
const toggleExpand = async (row) => {
const group = row._origRef || findGroupById(row.id)
if (!group || group._loading) return
@@ -1300,6 +1409,7 @@ const handleAdd = (parentRow) => {
level: parentRow.level + 1,
parent_id: parentRow.id,
cover_id: undefined,
cover_url: '',
tag_id: parentTagId || undefined,
index: 0
})
@@ -1314,6 +1424,7 @@ const handleAdd = (parentRow) => {
level: 1,
parent_id: undefined,
cover_id: undefined,
cover_url: '',
tag_id: undefined,
index: 0
})
@@ -1334,6 +1445,7 @@ const handleEdit = (row) => {
level: row.level || 1,
parent_id: row.parentId || undefined,
cover_id: row.coverId || undefined,
cover_url: row.cover || '',
tag_id: row.tag?.id || row.tagId || undefined,
index: row.index || 0
})
@@ -1393,6 +1505,7 @@ const handleAddProduct = () => {
tag: '',
content: '',
cover_id: undefined,
cover_url: '',
good_group_id: undefined,
inventory_control: false,
inventory: 0,
@@ -1424,6 +1537,7 @@ const handleEditProduct = (product, parentGroupId) => {
tag: typeof product.tag === 'string' ? product.tag : '',
content: product.content || '',
cover_id: product.coverId,
cover_url: product.cover || '',
good_group_id: groupId,
inventory_control: product.inventoryControl,
inventory: product.inventory,
@@ -1506,11 +1620,13 @@ const clearProductGroup = () => {
const handleProductCoverSelect = (file) => {
productForm.cover_id = file.cover_id
productForm.cover_url = file.url || ''
coverSelectorVisible.value = false
}
const clearProductCover = () => {
productForm.cover_id = undefined
productForm.cover_url = ''
}
const clearParent = () => {
@@ -1533,6 +1649,12 @@ const confirmParentSelect = () => {
const handleCoverSelect = (file) => {
groupForm.cover_id = file.cover_id
groupForm.cover_url = file.url || ''
}
const clearGroupCover = () => {
groupForm.cover_id = undefined
groupForm.cover_url = ''
}
const handleStatusChange = async (row, disable) => {
@@ -1755,6 +1877,21 @@ onMounted(() => {
display: flex;
align-items: center;
gap: 8px;
border-radius: 4px;
transition: background-color 0.2s;
}
.group-name-cell.is-clickable {
cursor: pointer;
user-select: none;
}
.group-name-cell.is-clickable:hover {
background-color: #f0f7ff;
}
.group-name-cell.is-clickable:hover .expand-icon {
color: #409eff;
}
.group-name {
@@ -1847,6 +1984,258 @@ onMounted(() => {
color: #f56c6c;
}
/* 商品表单优化布局 */
.product-form-dialog :deep(.el-dialog__body) {
padding-top: 16px;
}
.product-form {
--section-gap: 20px;
}
.product-form-header {
display: flex;
gap: 24px;
padding: 16px;
background: #fafbfc;
border: 1px solid #ebeef5;
border-radius: 8px;
margin-bottom: var(--section-gap);
}
.cover-uploader {
position: relative;
width: 180px;
height: 180px;
flex-shrink: 0;
border: 1px dashed #dcdfe6;
border-radius: 8px;
overflow: hidden;
cursor: pointer;
background: #fff;
transition: border-color 0.2s;
}
.cover-uploader:hover {
border-color: #409eff;
}
.cover-image {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.cover-placeholder {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
color: #909399;
}
.cover-placeholder-icon {
font-size: 40px;
}
.cover-placeholder-text {
font-size: 13px;
}
.cover-mask {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.45);
color: #fff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 6px;
opacity: 0;
transition: opacity 0.2s;
font-size: 13px;
}
.cover-mask .el-icon {
font-size: 22px;
}
.cover-uploader:hover .cover-mask {
opacity: 1;
}
.cover-clear-btn {
position: absolute;
top: 8px;
right: 8px;
z-index: 2;
opacity: 0;
transition: opacity 0.2s;
}
.cover-uploader:hover .cover-clear-btn {
opacity: 1;
}
.basic-fields {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 4px;
}
.basic-fields :deep(.el-form-item) {
margin-bottom: 12px;
}
.basic-fields :deep(.el-form-item:last-child) {
margin-bottom: 0;
}
.group-picker {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
height: 32px;
padding: 0 11px;
border: 1px solid #dcdfe6;
border-radius: 4px;
background: #fff;
cursor: pointer;
color: #303133;
font-size: 14px;
transition: border-color 0.2s;
box-sizing: border-box;
}
.group-picker:hover {
border-color: #c0c4cc;
}
.group-picker.is-empty .group-picker-text {
color: #a8abb2;
}
.group-picker.is-disabled {
background: #f5f7fa;
cursor: not-allowed;
color: #a8abb2;
}
.group-picker.is-disabled:hover {
border-color: #dcdfe6;
}
.group-picker.is-disabled .group-picker-text,
.group-picker.is-disabled .group-picker-icon {
color: #a8abb2;
}
.note-form-item {
margin-bottom: 0 !important;
padding: 16px;
border: 1px solid #ebeef5;
border-radius: 8px;
background: #fff;
}
.group-status-item :deep(.el-radio-button__inner) {
padding: 6px 18px;
font-size: 13px;
}
.group-picker-icon {
color: #909399;
flex-shrink: 0;
}
.group-picker-text {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.group-picker-clear {
flex-shrink: 0;
padding: 0 4px;
}
.group-picker-arrow {
color: #c0c4cc;
font-size: 12px;
flex-shrink: 0;
}
.form-section {
margin-bottom: var(--section-gap);
padding: 16px;
border: 1px solid #ebeef5;
border-radius: 8px;
background: #fff;
}
.form-section:last-child {
margin-bottom: 0;
}
.form-section-title {
font-size: 14px;
font-weight: 600;
color: #303133;
margin-bottom: 12px;
padding-left: 8px;
border-left: 3px solid #409eff;
line-height: 1.2;
}
.form-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 24px;
}
.form-grid :deep(.el-form-item) {
margin-bottom: 12px;
}
.form-grid .arg-type-item {
grid-column: span 2;
}
.unit-suffix {
color: #909399;
font-weight: normal;
font-size: 12px;
margin-left: 2px;
}
.product-form :deep(.el-form-item__label) {
font-weight: 500;
padding-bottom: 4px !important;
line-height: 1.4;
}
@media (max-width: 720px) {
.product-form-header {
flex-direction: column;
align-items: center;
}
.form-grid {
grid-template-columns: 1fr;
}
.form-grid .arg-type-item {
grid-column: span 1;
}
}
.product-info {
display: flex;
flex-direction: column;