ソースを参照

feat(product): 添加产品品牌管理功能

- 新增品牌编辑页面,支持品牌基本信息、LOGO、故事等字段录入
- 实现品牌API接口,包括查询、新增、修改、删除等操作
- 添加通用工具函数,包括URL判断、图片处理、深度克隆等功能
- 集成富文本编辑器和图片上传组件
- 实现品牌授权信息表格管理功能
- 添加产品分类和计量单位相关API接口
- 集成税收编码选择组件,支持树形结构浏览
- 完善系统人员信息管理API接口
- 优化路由配置,添加品牌编辑页面路由规则
肖路 3 週間 前
コミット
fc900caee7

+ 3 - 2
src/api/product/category/index.ts

@@ -66,10 +66,11 @@ export const delCategory = (id: string | number | Array<string | number>) => {
  * 查询产品分类列表(排除节点)
  * @param id
   */
-export const listCategoryExcludeChild = (id: string | number): AxiosPromise<CategoryVO[]> => {
+export const listCategoryExcludeChild = (id: string | number, platform?: number): AxiosPromise<CategoryVO[]> => {
   return request({
     url: '/product/category/tree/exclude/' + id,
-    method: 'get'
+    method: 'get',
+    params: platform !== undefined ? { platform } : undefined
   });
 };
 

+ 43 - 18
src/components/TaxCodeSelect/index.vue

@@ -12,11 +12,10 @@
         <div class="tax-tree-panel">
           <el-tree
             ref="treeRef"
+            :data="treeData"
             :props="treeProps"
             node-key="id"
             highlight-current
-            lazy
-            :load="loadNode"
             :expand-on-click-node="false"
             @node-click="handleNodeClick"
             @node-dblclick="handleTreeDblClick"
@@ -46,26 +45,27 @@
             border
             highlight-current-row
             height="360px"
+            :row-class-name="rowClassName"
             @row-dblclick="handleRowDblClick"
           >
             <el-table-column label="编码" align="center" prop="taxationNo" min-width="110">
               <template #default="{ row }">
-                <el-link type="primary" :underline="false">{{ row.taxationNo }}</el-link>
+                <el-link :type="isRowDisabled(row) ? 'info' : 'primary'" :disabled="isRowDisabled(row)" :underline="false">{{ row.taxationNo }}</el-link>
               </template>
             </el-table-column>
             <el-table-column label="合并编码" align="center" prop="mergeNo" min-width="130" show-overflow-tooltip>
               <template #default="{ row }">
-                <el-link type="primary" :underline="false">{{ row.mergeNo }}</el-link>
+                <el-link :type="isRowDisabled(row) ? 'info' : 'primary'" :disabled="isRowDisabled(row)" :underline="false">{{ row.mergeNo }}</el-link>
               </template>
             </el-table-column>
             <el-table-column label="名称" align="center" prop="name" min-width="120" show-overflow-tooltip>
               <template #default="{ row }">
-                <el-link type="primary" :underline="false">{{ row.name }}</el-link>
+                <el-link :type="isRowDisabled(row) ? 'info' : 'primary'" :disabled="isRowDisabled(row)" :underline="false">{{ row.name }}</el-link>
               </template>
             </el-table-column>
             <el-table-column label="简称" align="center" prop="abbreviation" min-width="100" show-overflow-tooltip>
               <template #default="{ row }">
-                <el-link type="primary" :underline="false">{{ row.abbreviation }}</el-link>
+                <el-link :type="isRowDisabled(row) ? 'info' : 'primary'" :disabled="isRowDisabled(row)" :underline="false">{{ row.abbreviation }}</el-link>
               </template>
             </el-table-column>
             <!-- <el-table-column label="说明" align="center" prop="remark" min-width="150" show-overflow-tooltip />
@@ -88,7 +88,7 @@
 </template>
 
 <script setup lang="ts">
-import { listTaxCode, getTaxCode } from '@/api/system/taxCode';
+import { listTaxCode, getTaxCode, getTaxCodeTree } from '@/api/system/taxCode';
 import { TaxCodeVO, TaxCodeQuery } from '@/api/system/taxCode/types';
 import useDialog from '@/hooks/useDialog';
 
@@ -101,8 +101,9 @@ const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const dialog = useDialog({ title: '税收编码选择  请双击选择税收编码' });
 
 const treeRef = ref<ElTreeInstance>();
-const treeProps = { label: 'name' };
+const treeProps = { label: 'label', children: 'children' };
 const currentEchoId = ref<string | number | undefined>();
+const treeData = ref<any[]>([]);
 
 const listData = ref<TaxCodeVO[]>([]);
 const loading = ref(false);
@@ -115,17 +116,18 @@ const queryParams = ref<TaxCodeQuery>({
   parentId: undefined,
   name: undefined,
   taxationNo: undefined,
+
   params: {}
 });
 
-/** 懒加载树节点 */
-const loadNode = async (node: any, resolve: (data: any[]) => void) => {
-  const parentId = node.level === 0 ? 0 : node.data.id;
+/** 加载树形数据 */
+const loadTreeData = async () => {
   try {
-    const res = await listTaxCode({ parentId, pageNum: 1, pageSize: 9999, params: {} });
-    resolve(res.rows ?? []);
-  } catch {
-    resolve([]);
+    const res = await getTaxCodeTree();
+    treeData.value = res.data ?? [];
+  } catch (error) {
+    console.error('加载税收编码树失败:', error);
+    treeData.value = [];
   }
 };
 
@@ -170,8 +172,7 @@ const getList = async () => {
   loading.value = true;
   try {
     const res = await listTaxCode(queryParams.value);
-    // 只展示无下级(isBottom === '0')的叶子节点
-    listData.value = (res.rows ?? []).filter((row: TaxCodeVO) => row.isBottom === '0');
+    listData.value = res.rows ?? [];
     total.value = res.total ?? 0;
   } finally {
     loading.value = false;
@@ -202,9 +203,22 @@ const handleSearch = () => {
   getList();
 };
 
+/** 判断行是否禁用:非叶子节点(isBottom === 0)不可选 */
+const isRowDisabled = (row: TaxCodeVO): boolean => {
+  return Number((row as any)?.isBottom) === 0;
+};
+
+/** 行样式类名:禁用行显示为灰色 */
+const rowClassName = ({ row }: { row: TaxCodeVO }): string => {
+  return isRowDisabled(row) ? 'tax-code-row-disabled' : '';
+};
+
 /** 双击行选择 */
 const handleRowDblClick = (row: TaxCodeVO) => {
-  if (row.isBottom !== '0') return;
+  if (isRowDisabled(row)) {
+    proxy?.$modal.msgWarning('该节点不是叶子节点,不可选择');
+    return;
+  }
   emit('select', row);
   dialog.closeDialog();
 };
@@ -228,6 +242,7 @@ watch(
   () => dialog.visible.value,
   async (val) => {
     if (val) {
+      await loadTreeData();
       await getList();
       if (currentEchoId.value) {
         await echoById(currentEchoId.value);
@@ -271,4 +286,14 @@ defineExpose({ open, close: dialog.closeDialog });
   display: flex;
   align-items: center;
 }
+
+:deep(.tax-code-row-disabled) {
+  background-color: var(--el-fill-color-light) !important;
+  color: var(--el-text-color-disabled);
+  cursor: not-allowed;
+}
+
+:deep(.tax-code-row-disabled:hover > td.el-table__cell) {
+  background-color: var(--el-fill-color-light) !important;
+}
 </style>

+ 45 - 16
src/views/product/category/index.vue

@@ -76,21 +76,32 @@
               <el-tree-select
                 v-model="form.parentId"
                 :data="categoryOptions"
-                :props="{ value: 'id', label: 'label', children: 'children' } as any"
+                :props="{ value: 'id', label: 'categoryName', children: 'children' } as any"
                 value-key="id"
                 placeholder="选择上级分类"
                 check-strictly
               />
             </el-form-item>
           </el-col>
-          <el-col :span="12">
-            <el-form-item label="分类名称" prop="categoryName">
-              <el-input v-model="form.categoryName" placeholder="请输入分类名称" />
+          <el-col :span="24">
+            <el-form-item label="A10分类" prop="categoryNo">
+              <el-tree-select
+                v-model="form.categoryNo"
+                :data="a10TreeOptions"
+                :props="{ value: 'categoryNo', label: 'categoryName', children: 'children' } as any"
+                node-key="categoryNo"
+                value-key="categoryNo"
+                placeholder="请选择A10分类"
+                filterable
+                clearable
+                check-strictly
+                style="width: 100%"
+              />
             </el-form-item>
           </el-col>
           <el-col :span="12">
-            <el-form-item label="分类编号" prop="categoryNo">
-              <el-input v-model="form.categoryNo" placeholder="请输入分类编号" />
+            <el-form-item label="分类名称" prop="categoryName">
+              <el-input v-model="form.categoryName" placeholder="请输入分类名称" />
             </el-form-item>
           </el-col>
           <el-col :span="12">
@@ -118,14 +129,7 @@
               <el-input-number v-model="form.sort" controls-position="right" :min="0" style="width: 100%" />
             </el-form-item>
           </el-col>
-          <el-col :span="12">
-            <el-form-item label="平台" prop="platform">
-              <el-select v-model="form.platform" placeholder="请选择所属平台" style="width: 100%">
-                <el-option label="PC端" :value="0" />
-                <el-option label="工业品" :value="1" />
-              </el-select>
-            </el-form-item>
-          </el-col>
+        
           <el-col :span="12">
             <el-form-item label="是否提示" prop="isShow">
               <el-switch v-model="form.isShow" :active-value="1" :inactive-value="0" />
@@ -158,6 +162,8 @@
 
 <script setup name="Category" lang="ts">
 import { listCategory, getCategory, delCategory, addCategory, updateCategory, listCategoryExcludeChild, setCategoryReviewer } from '@/api/product/category';
+import { listCategory as listCategoryA10 } from '@/api/product/categoryA10';
+import { CategoryVO as CategoryA10VO } from '@/api/product/categoryA10/types';
 import UserSelect from '@/components/UserSelect/index.vue';
 import { UserVO } from '@/api/system/user/types';
 import { CategoryVO, CategoryQuery, CategoryForm } from '@/api/product/category/types';
@@ -181,6 +187,10 @@ const categoryList = ref<CategoryVO[]>([]);
 const loading = ref(true);
 const showSearch = ref(true);
 const categoryOptions = ref<CategoryOptionsType[]>([]);
+// A10 分类列表(用于分类编号选择框)
+const a10Options = ref<CategoryA10VO[]>([]);
+// A10 分类树形结构(用于 el-tree-select)
+const a10TreeOptions = ref<any[]>([]);
 // 存储懒加载的子节点加载状态和 resolve 函数
 const lazyTreeNodeMap = ref<Map<string | number, { tree: any; treeNode: any; resolve: (data: CategoryVO[]) => void }>>(new Map());
 
@@ -299,10 +309,19 @@ const refreshLazyNodes = () => {
   });
 };
 
+/** 加载 A10 分类选项列表 */
+const loadA10Options = async () => {
+  const res = await listCategoryA10();
+  const list = (res as any).rows || res.data || [];
+  a10Options.value = list;
+  // 构建树形结构
+  a10TreeOptions.value = proxy?.handleTree<any>(list, 'id') || [];
+}
+
 /** 新增按钮操作 */
 const handleAdd = async (row?: CategoryVO) => {
   reset();
-  const res = await listCategory();
+  const res = await listCategory({ platform: routePlatform.value });
   const responseData = (res as any).rows || res.data || [];
   const data = proxy?.handleTree<CategoryOptionsType>(responseData, 'id');
   if (data) {
@@ -310,6 +329,10 @@ const handleAdd = async (row?: CategoryVO) => {
     if (row && row.id) {
       form.value.parentId = row?.id;
     }
+    // 预填充路由中的平台
+    if (routePlatform.value !== undefined) {
+      form.value.platform = routePlatform.value;
+    }
     dialog.visible = true;
     dialog.title = '添加产品分类';
   }
@@ -320,7 +343,7 @@ const handleUpdate = async (row: CategoryVO) => {
   reset();
   const res = await getCategory(row.id);
   form.value = res.data;
-  const response = await listCategoryExcludeChild(row.id);
+  const response = await listCategoryExcludeChild(row.id, routePlatform.value);
   // 根据后端返回结构,如果是 TableDataInfo 则用 rows,如果是直接数组则用 data
   const responseData = (response as any).rows || response.data || [];
   const data = proxy?.handleTree<CategoryOptionsType>(responseData, 'id');
@@ -343,6 +366,10 @@ const handleUpdate = async (row: CategoryVO) => {
 const submitForm = () => {
   categoryFormRef.value?.validate(async (valid: boolean) => {
     if (valid) {
+      // 统一使用路由中的 platform 提交
+      if (routePlatform.value !== undefined) {
+        form.value.platform = routePlatform.value;
+      }
       form.value.id ? await updateCategory(form.value) : await addCategory(form.value);
       proxy?.$modal.msgSuccess('操作成功');
       dialog.visible = false;
@@ -380,5 +407,7 @@ const handleReviewerSelected = async (users: UserVO[]) => {
 
 onMounted(() => {
   getList();
+  // 页面加载时预加载 A10 分类树
+  loadA10Options();
 });
 </script>

+ 1 - 1
src/views/product/unit/index.vue

@@ -69,7 +69,7 @@
           <el-input v-model="form.isShow" placeholder="请输入是否显示:1=是,0=否" />
         </el-form-item>
         <el-form-item label="备注" prop="remark">
-            <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
+          <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
         </el-form-item>
       </el-form>
       <template #footer>