Эх сурвалжийг харах

feat(company-sku):公司SKU新增、修改、查看、发布功能添加

xinyan 6 сар өмнө
parent
commit
c251f6401d

+ 1 - 1
src/views/product-manage/competitor-monitor/component/EditDrawer.vue

@@ -1,6 +1,6 @@
 <script lang="ts" setup>
 /**
- * @Name: EditDrawer.vue
+ * @Name: SkuBuilder.vue
  * @Description: 竞品监控-行编辑
  * @Author: Cheney
  */

+ 1 - 1
src/views/product-manage/product-list/component/EditDrawer.vue

@@ -1,6 +1,6 @@
 <script lang="ts" setup>
 /**
- * @Name: EditDrawer.vue
+ * @Name: SkuBuilder.vue
  * @Description: 商品列表-行编辑
  * @Author: Cheney
  */

+ 1 - 1
src/views/product-manage/product-monitor/component/EditDrawer.vue

@@ -1,6 +1,6 @@
 <script lang="ts" setup>
 /**
- * @Name: EditDrawer.vue
+ * @Name: SkuBuilder.vue
  * @Description: 商品监控-行编辑
  * @Author: Cheney
  */

+ 67 - 0
src/views/sku-manage/company-sku/api.ts

@@ -11,6 +11,25 @@ export function getTableData(query: any) {
   });
 }
 
+export function createObj (body: any) {
+  return request({
+    url: '/api/cms/sku/',
+    method: 'post',
+    data: body
+  })
+}
+
+export function PartialUpdateObj (id, body) {
+  return request({
+    url: '/api/cms/sku/' + id + '/',
+    method: 'put',
+    data: body,
+    params: {
+      partial: true
+    }
+  })
+}
+
 export function getBrandOptions() {
   return request({
     url: apiPrefix + 'select/',
@@ -49,3 +68,51 @@ export function createAttr(data:any) {
   })
 }
 
+export function releaseSku(body: any){
+  return request({
+    url: '/api/cms/sku/' + `${body.id}/` ,
+    method: 'PUT',
+    params: { partial: 1 },
+    data: { status: body.status }
+  });
+}
+
+export function getSkuDetail(query: any) {
+  return request({
+		url: '/api/cms/sku/selection/',
+		method: 'GET',
+    params: query
+  });
+}
+
+export function getSkuDetailByPower(query: any) {
+  return request({
+		url: '/api/cms/sku_attr/',
+		method: 'GET',
+		params: query,
+	});
+}
+
+export function getSkuDetailByKind(query: any) {
+  return request({
+		url: '/api/cms/sku_kind/'+`${query}/` ,
+		method: 'GET',
+		// params: query,
+	});
+}
+
+export function getBrandSelect() {
+  return request({
+    url:'/api/cms/sku-brand/select/',
+    method:'get',
+  })
+}
+
+export function GetDictionary (query) {
+  return request({
+    url: '/api/cms/sku_kind/dictionary/',
+    method: 'get',
+    params: { ...query }
+  })
+}
+

+ 0 - 87
src/views/sku-manage/company-sku/component/CreateDialog.vue

@@ -1,87 +0,0 @@
-<script lang="ts" setup>
-/**
- * @Name: CreateDialog.vue
- * @Description: 产品属性-创建对话框
- * @Author: xinyan
- */
-
-import { ElMessage, FormInstance, FormRules } from 'element-plus';
-import { DictionaryStore } from '/@/stores/dictionary';
-import { useResponse } from '/@/utils/useResponse';
-import * as api from '../api';
-import { Close, Finished } from '@element-plus/icons-vue';
-import { createAttr, createBrand } from '../api';
-
-const loading = ref(false);
-const createDialog = <Ref>useTemplateRef('createDialog');
-const createOpen = defineModel({ default: false });
-
-const emit = defineEmits(['refresh']);
-
-interface RuleForm {
-	name: any;
-	key: any;
-}
-
-const ruleFormRef = ref<FormInstance>();
-const ruleForm = reactive<RuleForm>({
-	name: '',
-	key: '',
-});
-
-const rules = reactive<FormRules<RuleForm>>({
-	name: [{ required: true, message: '请输入属性名称', trigger: 'blur' }],
-	key: [{ required: true, message: '请输入属性标识', trigger: 'blur' }],
-});
-
-const submitForm = async (formEl: FormInstance | undefined) => {
-	if (!formEl) return;
-	await formEl.validate(async (valid, fields) => {
-		if (valid) {
-			const body = {
-				name: ruleForm.name,
-				key: ruleForm.key,
-			};
-			const res = await useResponse(api.createAttr, body, loading);
-			if (res.code === 2000) {
-				ElMessage.success('创建成功');
-				createOpen.value = false;
-				emit('refresh');
-			}
-		} else {
-			// createOpen.value = false;
-			ElMessage.error('创建失败,请检查表单');
-		}
-	});
-};
-
-function cancelDialog() {
-	resetForm(ruleFormRef.value);
-	createDialog.value.visible = false;
-}
-
-const resetForm = (formEl: FormInstance | undefined) => {
-	if (!formEl) return;
-	formEl.resetFields();
-};
-</script>
-
-<template>
-	<el-dialog ref="createDialog" v-model="createOpen" :close-on-click-modal="false" :close-on-press-escape="false" :title="`产品品牌 - 创建 `" style="width: 30%">
-		<el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" class="mx-2.5 mt-5" label-width="auto" status-icon>
-					<el-form-item class="font-medium" label="属性名称" prop="name">
-						<el-input v-model="ruleForm.name" placeholder="请输入属性名称" style="width:328px"/>
-					</el-form-item>
-				<el-form-item class="font-medium" label="属性标识" prop="key">
-						<el-input v-model="ruleForm.key" placeholder="请输入属性标识" style="width:328px"/>
-					</el-form-item>
-		</el-form>
-		<template #footer>
-			<el-button :icon="Close" @click="cancelDialog">取 消</el-button>
-			<el-button :icon="Finished" :loading="loading" type="primary" @click="submitForm(ruleFormRef)">确 定</el-button>
-		</template>
-	</el-dialog>
-</template>
-
-<style scoped>
-</style>

+ 152 - 116
src/views/sku-manage/company-sku/component/DataTable.vue

@@ -12,17 +12,17 @@ import { useResponse } from '/@/utils/useResponse';
 import { CompanySkuColumns } from '/@/views/sku-manage/Columns';
 import PermissionButton from '/@/components/PermissionButton/index.vue';
 import DataTableSlot from './DataTableSlot.vue';
-import EditDrawer from './EditDrawer.vue';
-import NoticeDialog from '/src/views/product-manage/product-list/component/NoticeDialog.vue';
+import EditDrawer from './SkuBuilder.vue';
 import * as api from '../api';
 import CreateDialog from '/src/views/sku-manage/product-attribute/component/CreateDialog.vue';
-
+import ShowSkuDrawer from '/@/views/sku-manage/company-sku/component/ShowSkuDrawer.vue';
+import SkuBuilder from '/@/views/sku-manage/company-sku/component/SkuBuilder.vue';
 
 interface Parameter {
-  brandName: string,
-  status: string,
-  kind: string,
-  sku: string
+	brandName: string;
+	status: string;
+	kind: string;
+	sku: string;
 }
 
 const queryParameter: Parameter | undefined = inject('query-parameter');
@@ -30,157 +30,193 @@ const { tableOptions, handlePageChange } = usePagination(fetchList);
 
 const gridRef = ref();
 const gridOptions: any = reactive({
-  id: 'product-attribute-table',
-  keepSource: true,
-  size: 'small',
-  border: false,
-  round: true,
-  stripe: true,
-  currentRowHighLight: true,
-  height: '100%',
-  customConfig: {
-    // mode: 'drawer',
-    // immediate: true,
-    storage: true
-  },
-  toolbarConfig: {
-    size: 'large',
-    custom: true,
-    slots: {
-      tools: 'toolbar_tools',
-      buttons: 'toolbar_buttons'
-    }
-  },
-  rowConfig: {
-    isHover: true
-  },
-  columnConfig: {
-    // resizable: true
-  },
-  pagerConfig: {
-    total: tableOptions.value.total,
-    page: tableOptions.value.page,
-    limit: tableOptions.value.limit
-  },
-  loading: false,
-  loadingConfig: {
-    icon: 'vxe-icon-indicator roll',
-    text: '正在拼命加载中...'
-  },
-  columns: '',
-  data: ''
+	id: 'product-attribute-table',
+	keepSource: true,
+	size: 'small',
+	border: false,
+	round: true,
+	stripe: true,
+	currentRowHighLight: true,
+	height: '100%',
+	customConfig: {
+		// mode: 'drawer',
+		// immediate: true,
+		storage: true,
+	},
+	toolbarConfig: {
+		size: 'large',
+		custom: true,
+		slots: {
+			tools: 'toolbar_tools',
+			buttons: 'toolbar_buttons',
+		},
+	},
+	rowConfig: {
+		isHover: true,
+	},
+	columnConfig: {
+		// resizable: true
+	},
+	pagerConfig: {
+		total: tableOptions.value.total,
+		page: tableOptions.value.page,
+		limit: tableOptions.value.limit,
+	},
+	loading: false,
+	loadingConfig: {
+		icon: 'vxe-icon-indicator roll',
+		text: '正在拼命加载中...',
+	},
+	columns: '',
+	data: '',
 });
 
-const editOpen = ref(false);
+const showSkuBuilder = ref(false);
 const createOpen = ref(false);
-const rowData = ref({});
+const showOpen = ref(false);
+const rowData = ref<any | undefined>(undefined);
 
-const dialogVisible = ref(false);
+const skuData = ref({});
 
 onBeforeMount(() => {
   gridOptions.pagerConfig.limit = 20;
 })
 
 onMounted(() => {
-  fetchList();
+	fetchList();
 });
 
 async function fetchList(isQuery = false) {
   if (isQuery) {
     gridOptions.pagerConfig.page = 1;
   }
-  
-  gridOptions.data = [];
-  gridOptions.columns = [];
-
-  const query = {
-    brand: queryParameter?.brandName,
-    kind: queryParameter?.kind,
-    status: queryParameter?.status,
-    sku__contains: queryParameter?.sku
-  };
-
-  await useTableData(api.getTableData, query, gridOptions);
-  await gridRef.value.loadColumn(CompanySkuColumns);
-  gridOptions.showHeader = Boolean(gridOptions.data?.length);
+
+	gridOptions.data = [];
+	gridOptions.columns = [];
+
+	const query = {
+		brand: queryParameter?.brandName,
+		kind: queryParameter?.kind,
+		status: queryParameter?.status,
+		sku__contains: queryParameter?.sku,
+	};
+
+	await useTableData(api.getTableData, query, gridOptions);
+	await gridRef.value.loadColumn(CompanySkuColumns);
+	gridOptions.showHeader = Boolean(gridOptions.data?.length);
 }
 
 function handleRefresh() {
-  fetchList();
+	fetchList();
 }
 
-function handleEdit(row: any) {
-  editOpen.value = true;
-  rowData.value = row;
+async function handleEdit(row: any) {
+	rowData.value = row;
+	await fetchSkuDetail(row);
+	if (Object.keys(skuData.value).length > 0) {
+		showSkuBuilder.value = true;
+	}
 }
 
 function handleCreate() {
-  createOpen.value = true;
+	rowData.value = undefined;
+	skuData.value = {};
+	showSkuBuilder.value = true;
 }
 
 async function singleDelete(row: any) {
-  const res = await useResponse(api.deleteRow, row.id);
-  if (res.code === 2000) {
-    ElMessage.success({ message: '删除成功', plain: true });
-    handleRefresh();
-  }
+	const res = await useResponse(api.deleteRow, row.id);
+	if (res.code === 2000) {
+		ElMessage.success({ message: '删除成功', plain: true });
+		handleRefresh();
+	}
+}
+
+// TODO: 后端接口报错,不能单个更新
+async function handleReleaseSku(row: any) {
+	const res = await useResponse(api.releaseSku, { id: row.id, status: 3, kind: row.kind.id });
+	if (res.code === 2000) {
+		ElMessage.success({ message: '发布成功', plain: true });
+		handleRefresh();
+	}
+}
+
+async function handleShowSku(row: any) {
+	rowData.value = row;
+	await fetchSkuDetail(row);
+	if (Object.keys(skuData.value).length > 0) {
+		showOpen.value = true;
+	}
+}
+async function fetchSkuDetail(row: any) {
+	const res = await useResponse(api.getSkuDetail, { id: row.id });
+	if (res.code === 2000) {
+		skuData.value = res.data;
+	}
 }
 
 const gridEvents = {
-  custom({ type }: any) {
-    // console.log(`点击 ${type}`)
-    if (type == 'confirm') {
-      fetchList();
-    }
-  }
+	custom({ type }: any) {
+		// console.log(`点击 ${type}`)
+		if (type == 'confirm') {
+			fetchList();
+		}
+	},
 };
 
 defineExpose({ fetchList });
 </script>
 
 <template>
-  <vxe-grid ref="gridRef" v-bind="gridOptions" v-on="gridEvents">
-    <template #toolbar_buttons>
-      <PermissionButton :icon="Plus" plain round type="primary" @click="handleCreate">新 增</PermissionButton>
-    </template>
-    <!-- 工具栏右侧 -->
-    <template #toolbar_tools>
-      <el-button circle class="toolbar-btn mr-3" @click="handleRefresh">
-        <el-icon>
-          <Refresh />
-        </el-icon>
-      </el-button>
-    </template>
-    <template #pager>
-      <vxe-pager
-          v-model:currentPage="gridOptions.pagerConfig.page"
-          v-model:pageSize="gridOptions.pagerConfig.limit"
-          :total="gridOptions.pagerConfig.total"
-          class="mt-1.5"
-          @page-change="handlePageChange"
-      />
-    </template>
-    <template #empty>
-      <el-empty description="暂无数据" />
-    </template>
-    <!-- 自定义列插槽 -->
-    <template v-for="col in CompanySkuColumns" #[`${col.field}`]="{ row }">
-      <DataTableSlot :key="row.id" :field="col.field" :row="row" @edit-row="handleEdit" @handle-delete="singleDelete" />
-    </template>
-  </vxe-grid>
-  <EditDrawer v-if="editOpen" v-model="editOpen" :row-data="rowData" @refresh="handleRefresh" />
-  <NoticeDialog v-if="dialogVisible" v-model="dialogVisible" :row-data="rowData" />
-  <CreateDialog v-if="createOpen" v-model="createOpen" @refresh="fetchList" />
+	<vxe-grid ref="gridRef" v-bind="gridOptions" v-on="gridEvents">
+		<template #toolbar_buttons>
+			<PermissionButton :icon="Plus" plain round type="primary" @click="handleCreate">新 增</PermissionButton>
+		</template>
+		<!-- 工具栏右侧 -->
+		<template #toolbar_tools>
+			<el-button circle class="toolbar-btn mr-3" @click="handleRefresh">
+				<el-icon>
+					<Refresh />
+				</el-icon>
+			</el-button>
+		</template>
+		<template #pager>
+			<vxe-pager
+				v-model:currentPage="gridOptions.pagerConfig.page"
+				v-model:pageSize="gridOptions.pagerConfig.limit"
+				:total="gridOptions.pagerConfig.total"
+				class="mt-1.5"
+				@page-change="handlePageChange"
+			/>
+		</template>
+		<template #empty>
+			<el-empty description="暂无数据" />
+		</template>
+		<!-- 自定义列插槽 -->
+		<template v-for="col in CompanySkuColumns" #[`${col.field}`]="{ row }">
+			<DataTableSlot
+				:key="row.id"
+				:field="col.field"
+				:row="row"
+				@edit-row="handleEdit"
+				@handle-delete="singleDelete"
+				@release-sku="handleReleaseSku"
+				@show-sku="handleShowSku"
+			/>
+		</template>
+	</vxe-grid>
+	<SkuBuilder v-if="showSkuBuilder" v-model="showSkuBuilder" :row-data="rowData" :skuData="skuData" @refresh="handleRefresh" />
+	<ShowSkuDrawer v-if="showOpen" v-model="showOpen" :row-data="rowData" :skuData="skuData"></ShowSkuDrawer>
 </template>
 
 <style scoped>
 .toolbar-btn {
-  width: 34px;
-  height: 34px;
-  font-size: 18px
+	width: 34px;
+	height: 34px;
+	font-size: 18px;
 }
 
 :deep(.custom-el-input .el-select__wrapper) {
-  border-radius: 20px;
+	border-radius: 20px;
 }
 </style>

+ 92 - 70
src/views/sku-manage/company-sku/component/DataTableSlot.vue

@@ -5,93 +5,115 @@
  * @Author: Cheney
  */
 
-import { Delete, DocumentCopy, InfoFilled, Operation } from '@element-plus/icons-vue';
+import { Delete, DocumentCopy, InfoFilled, Operation, Position, View } from '@element-plus/icons-vue';
 import PermissionButton from '/@/components/PermissionButton/index.vue';
 import { getTagType } from '/@/utils/useTagColor';
 import { handleCopy } from '/@/utils/useCopyText';
 
-
 const props = defineProps<{
-  row: any,
-  field: any
+	row: any;
+	field: any;
 }>();
 const { row, field } = props;
 
-const emit = defineEmits([ 'edit-row', 'handle-delete' ]);
+const emit: any = defineEmits(['edit-row', 'handle-delete', 'show-sku', 'release-sku']);
 
 function handleEdit() {
-  emit('edit-row', row);
+	emit('edit-row', row);
 }
 
 function onConfirm() {
-  emit('handle-delete', row);
+	emit('handle-delete', row);
+}
+
+function release() {
+	emit('release-sku', row);
+}
+
+function showDetail() {
+	emit('show-sku', row);
 }
 </script>
 
 <template>
-  <div class="font-medium">
-    <div v-if="field === 'operate'">
-      <div class="flex justify-center gap-2">
-        <PermissionButton circle plain type="warning" @click="handleEdit">
-          <el-icon>
-            <Operation />
-          </el-icon>
-        </PermissionButton>
-        <el-popconfirm
-            :icon="InfoFilled"
-            icon-color="#626AEF"
-            title="你确定要删除此项吗?"
-            width="220"
-            @confirm="onConfirm"
-        >
-          <template #reference>
-            <PermissionButton circle plain type="danger">
-              <el-icon>
-                <Delete />
-              </el-icon>
-            </PermissionButton>
-          </template>
-          <template #actions="{ confirm, cancel }">
-            <el-button size="small" @click="cancel">No!</el-button>
-            <el-button
-                size="small"
-                type="danger"
-                @click="confirm"
-            >
-              Yes?
-            </el-button>
-          </template>
-        </el-popconfirm>
-      </div>
-    </div>
-    <div v-else-if="field === 'sku'" class="flex flex-nowrap">
-      {{ row.sku }}
-      <el-button :disabled="!row.sku" :icon="DocumentCopy" class="ml-2 cursor-pointer" link
-                 @click="handleCopy(row.sku || '')">
-      </el-button>
-    </div>
-    <div v-else-if="field === 'brand'">
-      <el-tag :disable-transitions="true" :type=getTagType(row.brand.brand_name) effect="plain" round>
-        {{ row.brand.brand_name }}
-      </el-tag>
-    </div>
-    <div v-else-if="field === 'kind'">
-      <el-tag :disable-transitions="true" :type=getTagType(row.kind.name) effect="plain" round>
-        {{ row.kind.name }}
-      </el-tag>
-    </div>
-    <div v-else-if="field === 'status'">
-      <el-tag :disable-transitions="true"
-              :type="row.status === 1 ? 'warning' : 'success'">
-        {{ row.status === 1 ? '草稿' : '已发布' }}
-      </el-tag>
-    </div>
-    <div v-else>
-      {{ row[field] }}
-    </div>
-  </div>
+	<div class="font-medium">
+		<div v-if="field === 'operate'">
+			<div class="flex justify-center gap-2 mb-2">
+				<PermissionButton circle plain type="success" @click="showDetail()">
+					<el-icon>
+						<View />
+					</el-icon>
+				</PermissionButton>
+				<el-popconfirm :icon="InfoFilled" icon-color="#626AEF" title="发布后此sku将无法更改, 是否继续?" width="220" @confirm="release">
+					<template #reference>
+						<!--div不可以删除,否则会导致popconfirm的弹出框消失-->
+						<div>
+							<el-tooltip :enterable="false" :show-arrow="false" content="发布" hide-after="0" placement="top" popper-class="custom-btn-tooltip-2">
+								<PermissionButton :color="'#6466F1'" circle plain :disabled="row.status === 3">
+									<el-icon>
+										<Position />
+									</el-icon>
+								</PermissionButton>
+							</el-tooltip>
+						</div>
+					</template>
+					<template #actions="{ confirm, cancel }">
+						<el-button size="small" @click="cancel">No!</el-button>
+						<el-button size="small" type="danger" @click="confirm"> Yes?</el-button>
+					</template>
+				</el-popconfirm>
+			</div>
+			<div class="flex justify-center gap-2">
+				<PermissionButton circle plain type="warning" @click="handleEdit">
+					<el-icon>
+						<Operation />
+					</el-icon>
+				</PermissionButton>
+				<el-popconfirm :icon="InfoFilled" icon-color="#626AEF" title="你确定要删除此项吗?" width="220" @confirm="onConfirm">
+					<template #reference>
+						<PermissionButton circle plain type="danger" :disabled="row.status === 3">
+							<el-icon>
+								<Delete />
+							</el-icon>
+						</PermissionButton>
+					</template>
+					<template #actions="{ confirm, cancel }">
+						<el-button size="small" @click="cancel">No!</el-button>
+						<el-button size="small" type="danger" @click="confirm"> Yes?</el-button>
+					</template>
+				</el-popconfirm>
+			</div>
+		</div>
+		<div v-else-if="field === 'sku'" class="flex flex-nowrap">
+			{{ row.sku }}
+			<el-button :disabled="!row.sku" :icon="DocumentCopy" class="ml-2 cursor-pointer" link @click="handleCopy(row.sku || '')"></el-button>
+		</div>
+		<div v-else-if="field === 'brand'">
+			<el-tag :disable-transitions="true" :type="getTagType(row.brand.brand_name)" effect="plain" round>
+				{{ row.brand.brand_name }}
+			</el-tag>
+		</div>
+		<div v-else-if="field === 'kind'">
+			<el-tag :disable-transitions="true" :type="getTagType(row.kind.name)" effect="plain" round>
+				{{ row.kind.name }}
+			</el-tag>
+		</div>
+		<div v-else-if="field === 'status'">
+			<el-tag :disable-transitions="true" :type="row.status === 1 ? 'warning' : 'success'">
+				{{ row.status === 1 ? '草稿' : '已发布' }}
+			</el-tag>
+		</div>
+		<div v-else>
+			{{ row[field] }}
+		</div>
+	</div>
 </template>
 
-<style scoped>
-
+<style lang="scss">
+.custom-btn-tooltip-2 {
+	background-color: #f0f0fe !important;
+	color: #606266 !important;
+	border: 1px solid #6466f1 !important;
+	font-size: 14px;
+}
 </style>

+ 0 - 112
src/views/sku-manage/company-sku/component/EditDrawer.vue

@@ -1,112 +0,0 @@
-<script lang="ts" setup>
-/**
- * @Name: EditDrawer.vue
- * @Description: 产品属性-行编辑
- * @Author: Cheney
- */
-
-import { ElMessage, FormInstance, FormRules } from 'element-plus';
-import { Close, Finished } from '@element-plus/icons-vue';
-import { useResponse } from '/@/utils/useResponse';
-import * as api from '../api';
-
-
-const btnLoading = ref(false);
-
-const editOpen = defineModel({ default: false });
-
-const editDrawer = <Ref>useTemplateRef('editDrawer');
-
-const props = defineProps({
-  rowData: Object
-});
-const { rowData } = props;
-const emit = defineEmits([ 'refresh' ]);
-
-interface RuleForm {
-  name: any,
-  key: any
-}
-
-const ruleFormRef = ref<FormInstance>();
-const ruleForm = reactive<RuleForm>({
-  name: rowData?.name,
-  key: rowData?.key,
-});
-
-const rules = reactive<FormRules<RuleForm>>({
-  // shop_name: [ { required: true, message: '请输入店铺', trigger: 'blur' } ],
-});
-
-const submitForm = async (formEl: FormInstance | undefined) => {
-  if (!formEl) return;
-  await formEl.validate(async (valid, fields) => {
-    if (valid) {
-      try {
-        const res = await useResponse(api.updateRow, { id: rowData?.id, ...ruleForm }, btnLoading);
-        if (res && res.code == 2000) {
-          editOpen.value = false;
-          ElMessage.success('编辑成功');
-          emit('refresh');
-        }
-      } catch (error) {
-        console.error('Error==>', error);
-      }
-    } else {
-      console.log('error submit!', fields);
-    }
-  });
-};
-
-function closeDrawer() {
-  editDrawer.value.handleClose();
-}
-
-</script>
-
-<template>
-  <div class="drawer-container">
-    <el-drawer ref="editDrawer"
-               v-model="editOpen"
-               :close-on-click-modal="false"
-               :close-on-press-escape="false"
-               :title="`产品属性- 编辑 `"
-               size="25%">
-      <el-form
-          ref="ruleFormRef"
-          :model="ruleForm"
-          :rules="rules"
-          class="mx-2.5 mt-7"
-          label-position="top"
-          label-width="auto"
-          status-icon>
-        <el-form-item class="font-medium" label="属性名称" prop="name">
-          <el-input v-model="ruleForm.name" />
-        </el-form-item>
-        <el-form-item class="font-medium" label="属性标识" prop="key">
-          <el-input v-model="ruleForm.key" />
-        </el-form-item>
-        <el-form-item>
-          <el-divider />
-          <div class="flex flex-1 justify-end">
-            <el-button :icon="Close" @click="closeDrawer">取 消</el-button>
-            <el-button :icon="Finished" :loading="btnLoading" type="primary" @click="submitForm(ruleFormRef)">
-              确 定
-            </el-button>
-          </div>
-        </el-form-item>
-      </el-form>
-    </el-drawer>
-  </div>
-</template>
-
-<style scoped>
-.drawer-container :deep(.el-drawer__header) {
-  border-bottom: none;
-  font-weight: 500;
-}
-
-.drawer-container :deep(.el-drawer__title) {
-  font-size: 18px;
-}
-</style>

+ 143 - 0
src/views/sku-manage/company-sku/component/ShowSkuDrawer.vue

@@ -0,0 +1,143 @@
+<script lang="ts" setup>
+/**
+ * @Name: ShowSkuDrawer.vue
+ * @Description: SKU详情抽屉
+ * @Author: xinyan
+ */
+import * as api from '../api';
+import { useResponse } from '/@/utils/useResponse';
+import { ElMessage } from 'element-plus';
+import XEUtils from 'xe-utils';
+
+const showSkuDrawer = <Ref>useTemplateRef('showSkuDrawer');
+const showOpen = defineModel({ default: false });
+const props = defineProps({
+	skuData: Object,
+	rowData: Object,
+});
+const { skuData, rowData } = props;
+
+const attrList = ref({});
+const powerType = ref([]);
+
+async function initPower() {
+	const resp = await useResponse(api.getSkuDetailByPower, { key: 'power' });
+	if (!resp || resp.data.total === 0) {
+		ElMessage({
+			type: 'warning',
+			message: '警告:未找到产品属性:power(电源规格)!请在产品属性中定义标识为power的电源规格属性!',
+		});
+	}
+	const items = resp.data[0].attr_dict;
+	items.unshift({
+		label: '无',
+		value: '',
+	});
+	powerType.value = items;
+}
+
+async function initData() {
+	const ret = await useResponse(api.getSkuDetailByKind, rowData.kind.id);
+
+	const info = XEUtils.groupBy(ret.data.RelatedAttrs, 'section');
+	const sortedKey = XEUtils.sortBy(Object.keys(info), (key) => key);
+
+	const relatedAttrs = []; // 用于存放解析后的 info
+
+	for (const section of sortedKey) {
+		const attrItems = XEUtils.sortBy(info[section], 'order');
+
+		let selectedIds = [];
+		if (skuData) {
+			selectedIds = skuData[`section${section}`] || []; // 获取对应 section 的选中 ID
+		}
+		const tmpAttrs = [];
+		for (const item of attrItems) {
+			const v = item.attr_dict.find((dict) => selectedIds.includes(dict.id));
+			if (v) {
+				tmpAttrs.push({
+					label: item.attr.name,
+					value: v.label,
+					description: item.description,
+				});
+			}
+		}
+
+		relatedAttrs.push({
+			section: '第' + section + '部分',
+			attrItems: tmpAttrs,
+		});
+	}
+
+	if (skuData.optional) {
+		const optionalAttrs = [];
+		const value = skuData.optional.powerType;
+		const matchedLabel = powerType.value.find((item) => item.value === value)?.label;
+		optionalAttrs.push({
+			label: '电源',
+			value: matchedLabel,
+		});
+		const key = skuData.optional.version;
+		const type = skuData.optional.versionType;
+		optionalAttrs.push({
+			label: '版本', // 属性名
+			value: type == null ? '无' : `${type}${key}`,
+		});
+
+		optionalAttrs.push({
+			label: '版本特征', // 属性名
+			value: rowData.version_feature === '' ? '无' : rowData.version_feature, // 属性值
+		});
+		if (optionalAttrs.length > 0) {
+			relatedAttrs.push({
+				section: '可选部分',
+				attrItems: optionalAttrs,
+			});
+			console.log('=>(ShowSkuDrawer.vue:100) optionalAttrs', optionalAttrs);
+		}
+	}
+	attrList.value = relatedAttrs;
+}
+
+onMounted(() => {
+	initPower();
+	initData();
+});
+</script>
+
+<template>
+	<el-drawer ref="showSkuDrawer" v-model="showOpen" style="width: 40%">
+		<template #title>
+			<div>
+				<span style="font-size: 16px; font-weight: bold">当前SKU:</span>
+				<el-tag style="margin-left: 5px" type="primary">
+					{{ rowData.sku }}
+				</el-tag>
+			</div>
+		</template>
+		<div class="m-3">
+			<el-descriptions v-for="info of attrList" :title="info.section" border style="margin-bottom: 40px">
+				<el-descriptions-item v-for="attr of info.attrItems" label-class-name="my-label">
+					<template #label>
+						<div style="white-space: nowrap">{{ attr.label }}</div>
+						<el-tooltip v-if="Boolean(attr.description)" :content="attr.description" effect="dark" placement="top-start">
+							<i class="el-icon-question"></i>
+						</el-tooltip>
+					</template>
+					<div :style="{ whiteSpace: attr.label !== '版本特征' ? 'nowrap' : 'normal' }" class="my-value">{{ attr.value }}</div>
+				</el-descriptions-item>
+			</el-descriptions>
+		</div>
+	</el-drawer>
+</template>
+
+<style scoped>
+:deep(.my-label) {
+	background: #f5f5f5 !important;
+}
+
+.my-value {
+	font-weight: 500;
+	font-size: 13px;
+}
+</style>

+ 404 - 0
src/views/sku-manage/company-sku/component/SkuBuilder.vue

@@ -0,0 +1,404 @@
+<script lang="ts" setup>
+/**
+ * @Name: SkuBuilder.vue
+ * @Description: 产品属性-行编辑
+ * @Author: Cheney
+ */
+
+import * as api from '/@/views/sku-manage/company-sku/api';
+import { useResponse } from '/@/utils/useResponse';
+import XEUtils from 'xe-utils';
+import { ElForm, ElMessage, FormInstance } from 'element-plus';
+
+const btnLoading = ref(false);
+const showSkuBuilder = defineModel({ default: false });
+const editDrawer = <Ref>useTemplateRef('editDrawer');
+
+interface RuleForm {
+	powerType: any;
+	versionType: any;
+	version: number;
+	versionFeature: string;
+}
+
+const props = defineProps({
+	rowData: Object,
+	skuData: Object,
+});
+const { rowData, skuData } = props;
+const emit = defineEmits(['refresh']);
+
+const selectedBrand = ref(rowData?.brand.id || '');
+const selectedKind = ref(rowData?.kind.id || '');
+const brandOptions = ref([]);
+const kindOptions = ref([]);
+
+const relatedAttrs = ref([]);
+const kindInfo = ref({});
+const formData = reactive<RuleForm>({
+	powerType: '',
+	versionType: '',
+	version: 1,
+	versionFeature: '',
+});
+const powerTypeList = ref([]);
+const skuList = ref([]);
+const isUpdate = computed(() => rowData !== undefined);
+const PublishedStatus = 3;
+const versionTypeList = [
+	{ label: '无', value: '' },
+	{ label: 'T', value: 'T' },
+	{ label: 'V', value: 'V' },
+];
+
+const ruleFormRef = ref<FormInstance>();
+
+const sku = computed(() => {
+	const ret = [];
+	for (const part of skuList.value) {
+		let t = '';
+		for (const k of part) {
+			t += formData[k] || '*';
+		}
+		ret.push(t);
+	}
+	for (const k of ['powerType']) {
+		const val = formData[k];
+		if (val) {
+			ret.push(val);
+		}
+	}
+	if (formData.versionType) {
+		ret.push(formData.versionType + formData.version);
+	}
+	return ret.join('-');
+});
+const disabledSaveButton = computed(() => {
+	if (rowData && rowData.status === PublishedStatus) {
+		return true;
+	}
+	return !selectedKind.value || kindInfo.value.status !== PublishedStatus;
+});
+
+const skuWithDetails = computed(() => {
+	const skuDetails = {};
+	for (const sectionData of relatedAttrs.value) {
+		const sectionName = sectionData.section;
+		skuDetails[sectionName] = []; // 初始化每个 section 的数组
+
+		for (const item of sectionData.items) {
+			const k = item.attr.key + '_' + item.id;
+			const value = formData[k] || '*';
+
+			const attrDict = item.attr_dict.find((dict) => dict.value === value);
+
+			if (attrDict) {
+				skuDetails[sectionName].push(attrDict.id);
+			}
+		}
+	}
+	return skuDetails;
+});
+
+async function initData() {
+	if (!selectedKind.value) {
+		return;
+	}
+
+	const tmpRelatedAttrs = [];
+	const tmpFormData = {
+		powerType: '',
+		versionType: '',
+		version: 1,
+		versionFeature: '',
+	};
+	const tmpSkuList = [];
+
+	const ret = await useResponse(api.getSkuDetailByKind, selectedKind.value);
+	kindInfo.value = ret.data;
+
+	if (kindInfo.value.status !== PublishedStatus) {
+		ElMessage.warning('警告:尚未发布的产品种类!!!');
+	}
+
+	const info = XEUtils.groupBy(ret.data.RelatedAttrs, 'section');
+	const sortedKey = XEUtils.sortBy(Object.keys(info), (key) => key);
+
+	for (const section of sortedKey) {
+		const attrItems = XEUtils.sortBy(info[section], 'order');
+
+		tmpRelatedAttrs.push({
+			section: section,
+			items: attrItems,
+		});
+		const skuPart = [];
+		let selectedIds = []; // 初始化为一个空数组
+		if (skuData) {
+			selectedIds = skuData[`section${section}`] || []; // 获取对应 section 的选中 ID
+		}
+
+		for (const item of attrItems) {
+			const k = item.attr.key + '_' + item.id;
+			let v = '';
+
+			// 检查选中的 ID 是否在当前属性的字典中
+			if (item.attr_dict.some((dict) => selectedIds.includes(dict.id))) {
+				// 找到对应的 dict 并设置值
+				v = item.attr_dict.find((dict) => selectedIds.includes(dict.id))?.value || '';
+			}
+
+			formData[k] = v; // 将值赋给 formData
+			skuPart.push(k); // 将 SKU 部分加入 SKU 列表
+		}
+
+		tmpSkuList.push(skuPart); // 将 SKU 部分加入 SKU 列表
+	}
+	// 用 skuData 的 optional 部分来设置可选项
+	if (skuData && isUpdate.value) {
+		selectedBrand.value = rowData.brand.id;
+		tmpFormData.powerType = skuData.optional.powerType || '';
+		tmpFormData.versionType = skuData.optional.versionType || '';
+		tmpFormData.version = skuData.optional.version || 1;
+		tmpFormData.versionFeature = rowData.version_feature || '';
+	}
+
+	// 最后更新组件的状态
+	Object.assign(formData, tmpFormData);
+	relatedAttrs.value = tmpRelatedAttrs;
+	skuList.value = tmpSkuList;
+}
+
+async function initPower() {
+	const resp = await useResponse(api.getSkuDetailByPower, { key: 'power' });
+	if (!resp || resp.data.total === 0) {
+		ElMessage({
+			type: 'warning',
+			message: '警告:未找到产品属性:power(电源规格)!请在产品属性中定义标识为power的电源规格属性!',
+		});
+	}
+	const items = resp.data[0].attr_dict;
+	items.unshift({
+		label: '无',
+		value: '',
+	});
+	powerTypeList.value = items;
+}
+
+async function saveSku(formName, isSaveAs) {
+	const formattedSkuDetails = {
+		section1: skuWithDetails.value[1] || [],
+		section2: skuWithDetails.value[2] || [],
+		section3: skuWithDetails.value[3] || [],
+	};
+	const optionalData = {
+		powerType: formData.powerType,
+		versionType: formData.versionType || null,
+		version: formData.versionType === '' ? null : formData.version || null,
+	};
+
+	if (isUpdate.value && !isSaveAs) {
+		try {
+			const body = {
+				sku: sku.value,
+				kind: selectedKind.value,
+				brand: selectedBrand.value,
+				version_feature: formData.versionFeature,
+				sku_selection: {
+					...formattedSkuDetails,
+					optional: optionalData,
+				},
+			};
+			const ret = await api.PartialUpdateObj(rowData.id, body);
+
+			ElMessage({
+				message: ret.msg,
+				type: 'success',
+			});
+			showSkuBuilder.value = false;
+			emit('refresh');
+		} catch (error) {
+			console.error('更新操作失败:', error);
+		}
+		return;
+	}
+	ruleFormRef.value.validate(async (valid: boolean) => {
+		if (valid) {
+			if (kindInfo.value.status !== 3) {
+				ElMessage({
+					message: '此产品种类尚未发布!',
+					type: 'warning',
+				});
+			} else {
+				try {
+					const ret = await api.createObj({
+						sku: sku.value,
+						kind: selectedKind.value,
+						brand: selectedBrand.value,
+						version_feature: formData.versionFeature,
+						sku_selection: {
+							...formattedSkuDetails,
+							optional: optionalData,
+						},
+					});
+					if (ret.code === 2000) {
+						ElMessage({
+							message: ret.msg,
+							type: 'success',
+						});
+					}
+					showSkuBuilder.value = false;
+					emit('refresh');
+				} catch (error) {
+					ElMessage.error({
+						message: error.msg,
+					});
+				}
+			}
+		} else {
+			return false;
+		}
+	});
+}
+
+async function changedKind() {
+	await nextTick(); // 等待 DOM 更新
+	if (ruleFormRef.value) {
+		ruleFormRef.value.resetFields(); // 确保 ref 不为 null
+		await initData();
+	}
+}
+
+function changeVersion() {
+	if (formData.version === 1) {
+		ruleFormRef.value!.clearValidate('versionFeature');
+	}
+}
+
+async function fetchOptions() {
+	brandOptions.value = (await useResponse(api.getBrandOptions)).data;
+	kindOptions.value = (await useResponse(api.getKindOptions)).data;
+}
+
+onBeforeMount(() => {
+	initData();
+});
+
+onMounted(() => {
+	fetchOptions();
+	// initData();
+	initPower();
+});
+</script>
+
+<template>
+	<div class="drawer-container">
+		<el-drawer ref="editDrawer" v-model="showSkuBuilder" :close-on-click-modal="false" :close-on-press-escape="false" :title="`SKU生成器`" size="50%">
+			<div class="m-5">
+				<!--选择器-->
+				<div style="margin-bottom: 20px">
+					<span class="select-container">产品品牌:</span>
+					<el-select v-model="selectedBrand" placeholder="请选择品牌" style="width: 25%">
+						<el-option v-for="item in brandOptions" :key="item.id" :label="item.brand_name" :value="item.id" />
+					</el-select>
+				</div>
+				<div style="margin-bottom: 20px" v-if="selectedBrand">
+					<span class="select-container">种类名称:</span>
+					<el-select v-model="selectedKind" :disabled="isUpdate" placeholder="请选择产品种类" style="width: 25%" @change="changedKind">
+						<el-option v-for="item in kindOptions" :key="item.id" :label="item.name" :value="item.id" />
+					</el-select>
+				</div>
+				<!--SKU-->
+				<div v-if="selectedKind" class="sku-text">
+					<span>SKU:{{ sku }}</span>
+				</div>
+				<!--属性-->
+				<div v-if="selectedKind">
+					<el-form ref="ruleFormRef" :model="formData" label-position="right" label-suffix=":">
+						<template v-for="info in relatedAttrs" :key="info.section">
+							<div class="sku-title">第{{ info.section }}部分</div>
+							<el-form-item
+								v-for="item in info.items"
+								:key="item.id"
+								:label="item.attr.name"
+								:prop="item.attr.key + '_' + item.id"
+								:rules="[{ required: true, message: '必填项' }]"
+							>
+								<el-radio-group v-model="formData[item.attr.key + '_' + item.id]">
+									<el-radio v-for="dict in item.attr_dict" :key="dict.id" :label="dict.value">
+										{{ dict.label }}
+									</el-radio>
+								</el-radio-group>
+							</el-form-item>
+						</template>
+						<div class="sku-title">可选部分</div>
+						<el-form-item label="电源" prop="powerType">
+							<el-radio-group v-model="formData.powerType">
+								<el-radio v-for="dict in powerTypeList" :key="dict.value" :label="dict.value">
+									{{ dict.label }}
+								</el-radio>
+							</el-radio-group>
+						</el-form-item>
+						<el-form-item label="版本类型" prop="versionType">
+							<el-radio-group v-model="formData.versionType">
+								<el-radio v-for="dict in versionTypeList" :key="dict.value" :label="dict.value">
+									{{ dict.label }}
+								</el-radio>
+							</el-radio-group>
+						</el-form-item>
+						<el-form-item v-show="formData.versionType" label="版本编号" prop="version">
+							<el-input-number v-model="formData.version" :max="9" :min="1" @change="changeVersion" />
+						</el-form-item>
+						<el-form-item
+							v-bind="{
+								rules: [{ required: formData.version > 1, message: '必填项', trigger: 'blur' }],
+								label: '版本特征',
+								prop: 'versionFeature',
+							}"
+						>
+							<el-input v-model="formData.versionFeature" autosize clearable style="width: 25%" />
+						</el-form-item>
+					</el-form>
+				</div>
+				<div class="button-container">
+					<el-button :disabled="disabledSaveButton" type="primary" @click="saveSku('skuForm', false)">保存</el-button>
+					<el-button :disabled="kindInfo.status !== 3" type="primary" @click="saveSku('skuForm', true)">另存</el-button>
+				</div>
+			</div>
+		</el-drawer>
+	</div>
+</template>
+
+<style scoped>
+.drawer-container :deep(.el-drawer__header) {
+	border-bottom: none;
+	/* font-weight: 500; */
+}
+
+.drawer-container :deep(.el-drawer__title) {
+	font-size: 18px;
+}
+
+.sku-text {
+	margin: 20px 0;
+	color: #515151;
+	font-weight: 500;
+}
+
+.sku-title {
+	font-weight: 500;
+	font-size: 16px;
+	margin-bottom: 10px;
+}
+
+:deep(.el-form-item__label) {
+	font-size: 14px;
+	font-weight: bold;
+}
+
+.select-container {
+	margin-bottom: 10px;
+	font-size: 14px;
+	font-weight: bold;
+	color: #606266;
+}
+</style>

+ 1 - 1
src/views/sku-manage/product-attribute/component/EditDrawer.vue

@@ -1,6 +1,6 @@
 <script lang="ts" setup>
 /**
- * @Name: EditDrawer.vue
+ * @Name: SkuBuilder.vue
  * @Description: 产品属性-行编辑
  * @Author: Cheney
  */

+ 1 - 1
src/views/sku-manage/product-brand/component/EditDrawer.vue

@@ -1,6 +1,6 @@
 <script lang="ts" setup>
 /**
- * @Name: EditDrawer.vue
+ * @Name: SkuBuilder.vue
  * @Description: 产品属性-行编辑
  * @Author: Cheney
  */

+ 1 - 1
src/views/sku-manage/product-category/component/EditDrawer.vue

@@ -1,6 +1,6 @@
 <script lang="ts" setup>
 /**
- * @Name: EditDrawer.vue
+ * @Name: SkuBuilder.vue
  * @Description: 产品属性-行编辑
  * @Author: Cheney
  */