edit.vue 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. <template>
  2. <div class="p-4">
  3. <el-card shadow="never">
  4. <template #header>
  5. <div class="flex justify-between items-center">
  6. <span class="text-lg font-bold">{{ pageTitle }}</span>
  7. <el-button link type="primary" @click="goBack">返回</el-button>
  8. </div>
  9. </template>
  10. <el-form ref="attributesFormRef" :model="form" :rules="rules" label-width="140px" class="max-w-5xl">
  11. <el-row :gutter="20">
  12. <el-col :span="12">
  13. <el-form-item label="品牌名称" prop="productAttributesName">
  14. <el-input v-model="form.productAttributesName" placeholder="请输入品牌名称" />
  15. </el-form-item>
  16. </el-col>
  17. <el-col :span="12">
  18. <el-form-item label="品牌首字母" prop="productAttributesCode">
  19. <el-input v-model="form.productAttributesCode" placeholder="请输入品牌首字母" />
  20. </el-form-item>
  21. </el-col>
  22. </el-row>
  23. <el-row :gutter="20">
  24. <el-col :span="12">
  25. <el-form-item label="品牌名称(英文)" prop="categoryId">
  26. <el-tree-select
  27. v-model="form.categoryId"
  28. :data="categoryOptions"
  29. :props="{ value: 'id', label: 'label', children: 'children' }"
  30. check-strictly
  31. :render-after-expand="false"
  32. clearable
  33. placeholder="请选择关联类别(必须选择第三级)"
  34. >
  35. <template #default="{ node, data }">
  36. <span :style="{ color: !node.isLeaf && node.level < 3 ? '#999' : '' }">
  37. {{ getCategoryFullPath(data.id) }}
  38. </span>
  39. </template>
  40. </el-tree-select>
  41. </el-form-item>
  42. </el-col>
  43. <el-col :span="12">
  44. <el-form-item label="品牌推荐系数" prop="recommendValue">
  45. <el-input-number
  46. v-model="extendFormData.recommendValue"
  47. :min="0"
  48. :max="9999"
  49. controls-position="right"
  50. style="width: 100%"
  51. placeholder="请输入推荐系数"
  52. />
  53. </el-form-item>
  54. </el-col>
  55. </el-row>
  56. <el-form-item label="品牌标题" prop="brandTitle">
  57. <el-input v-model="extendFormData.brandTitle" placeholder="请输入内容" />
  58. </el-form-item>
  59. <el-form-item label="品牌LOGO" prop="brandLogo">
  60. <image-upload v-model="extendFormData.brandLogo" />
  61. </el-form-item>
  62. <el-form-item label="品牌故事" prop="brandStory">
  63. <el-input v-model="extendFormData.brandStory" type="textarea" :rows="3" placeholder="请输入内容" />
  64. </el-form-item>
  65. <el-form-item label="是否显示" prop="isFilter">
  66. <el-radio-group v-model="form.isFilter">
  67. <el-radio :value="1">是</el-radio>
  68. <el-radio :value="0">否</el-radio>
  69. </el-radio-group>
  70. <div class="text-gray-500 text-sm mt-2">
  71. 当品牌下还没有商品的时候。分类的品牌区不会不显示品牌。
  72. </div>
  73. </el-form-item>
  74. <el-form-item label="品牌介绍" prop="brandDescribe">
  75. <editor v-model="extendFormData.brandDescribe" :min-height="192" />
  76. </el-form-item>
  77. <el-form-item>
  78. <el-button type="primary" :loading="buttonLoading" @click="submitForm">保存</el-button>
  79. <el-button @click="goBack">取消</el-button>
  80. </el-form-item>
  81. </el-form>
  82. </el-card>
  83. </div>
  84. </template>
  85. <script setup lang="ts" name="AttributesEdit">
  86. import { getAttributes, addAttributes, updateAttributes } from '@/api/pmsProduct/attributes';
  87. import { AttributesForm } from '@/api/pmsProduct/attributes/types';
  88. import { categoryTree } from '@/api/pmsProduct/base';
  89. import { categoryTreeVO } from '@/api/pmsProduct/category/types';
  90. const route = useRoute();
  91. const router = useRouter();
  92. const { proxy } = getCurrentInstance() as ComponentInternalInstance;
  93. const attributesFormRef = ref<ElFormInstance>();
  94. const buttonLoading = ref(false);
  95. const categoryOptions = ref<categoryTreeVO[]>([]);
  96. const pageTitle = computed(() => {
  97. return route.query.type === 'add' ? '新增属性' : '编辑属性';
  98. });
  99. const initFormData: AttributesForm = {
  100. id: undefined,
  101. categoryId: undefined,
  102. productAttributesCode: undefined,
  103. productAttributesName: undefined,
  104. isOptional: 1,
  105. entryMethod: '1',
  106. isFilter: 1,
  107. attributesList: undefined,
  108. required: 0,
  109. remark: undefined,
  110. }
  111. // 扩展表单字段(用于品牌风格的表单)
  112. const extendFormData = reactive({
  113. brandTitle: undefined as string | undefined,
  114. brandLogo: undefined as string | undefined,
  115. brandStory: undefined as string | undefined,
  116. brandDescribe: undefined as string | undefined,
  117. recommendValue: undefined as number | undefined,
  118. })
  119. const form = ref<AttributesForm>({ ...initFormData });
  120. const rules = reactive({
  121. categoryId: [
  122. { required: true, message: "商品类别不能为空", trigger: "change" },
  123. {
  124. validator: (rule: any, value: any, callback: any) => {
  125. if (!value) {
  126. callback();
  127. return;
  128. }
  129. // 检查是否为第三级类别
  130. const isThirdLevel = checkIsThirdLevel(value);
  131. if (!isThirdLevel) {
  132. callback(new Error('请选择第三级类别'));
  133. } else {
  134. callback();
  135. }
  136. },
  137. trigger: "change"
  138. }
  139. ],
  140. productAttributesCode: [
  141. { required: true, message: "品牌首字母不能为空", trigger: "blur" }
  142. ],
  143. productAttributesName: [
  144. { required: true, message: "品牌名称不能为空", trigger: "blur" }
  145. ],
  146. brandTitle: [
  147. { required: false, message: "品牌标题不能为空", trigger: "blur" }
  148. ],
  149. recommendValue: [
  150. { required: false, message: "推荐系数不能为空", trigger: "blur" }
  151. ]
  152. });
  153. /** 检查是否为第三级类别 */
  154. const checkIsThirdLevel = (categoryId: string | number): boolean => {
  155. const findLevel = (nodes: categoryTreeVO[], targetId: string | number, level: number = 1): number | null => {
  156. for (const node of nodes) {
  157. if (node.id === targetId) {
  158. return level;
  159. }
  160. if (node.children && node.children.length > 0) {
  161. const result = findLevel(node.children, targetId, level + 1);
  162. if (result !== null) return result;
  163. }
  164. }
  165. return null;
  166. };
  167. const level = findLevel(categoryOptions.value, categoryId);
  168. return level === 3;
  169. };
  170. /** 获取分类完整路径 */
  171. const getCategoryFullPath = (categoryId: string | number): string => {
  172. const findPath = (nodes: categoryTreeVO[], targetId: string | number, path: string[] = []): string[] | null => {
  173. for (const node of nodes) {
  174. const currentPath = [...path, node.label];
  175. if (node.id === targetId) {
  176. return currentPath;
  177. }
  178. if (node.children && node.children.length > 0) {
  179. const result = findPath(node.children, targetId, currentPath);
  180. if (result) return result;
  181. }
  182. }
  183. return null;
  184. };
  185. const pathArray = findPath(categoryOptions.value, categoryId);
  186. return pathArray ? pathArray.join(' / ') : '';
  187. }
  188. /** 查询分类树 */
  189. const getCategoryTree = async () => {
  190. const res = await categoryTree();
  191. categoryOptions.value = res.data || [];
  192. }
  193. /** 获取属性详情 */
  194. const getDetail = async () => {
  195. const id = route.query.id as string;
  196. if (id && route.query.type !== 'add') {
  197. const res = await getAttributes(id);
  198. Object.assign(form.value, {
  199. ...res.data,
  200. isOptional: typeof res.data.isOptional === 'string' ? Number(res.data.isOptional) : res.data.isOptional,
  201. isFilter: typeof res.data.isFilter === 'string' ? Number(res.data.isFilter) : res.data.isFilter,
  202. required: typeof res.data.required === 'string' ? Number(res.data.required) : res.data.required,
  203. });
  204. }
  205. };
  206. /** 提交表单 */
  207. const submitForm = () => {
  208. attributesFormRef.value?.validate(async (valid: boolean) => {
  209. if (valid) {
  210. buttonLoading.value = true;
  211. try {
  212. if (form.value.id) {
  213. await updateAttributes(form.value);
  214. } else {
  215. await addAttributes(form.value);
  216. }
  217. proxy?.$modal.msgSuccess("操作成功");
  218. goBack();
  219. } finally {
  220. buttonLoading.value = false;
  221. }
  222. }
  223. });
  224. };
  225. /** 返回 */
  226. const goBack = () => {
  227. router.back();
  228. };
  229. onMounted(() => {
  230. getCategoryTree();
  231. getDetail();
  232. });
  233. </script>