Jelajahi Sumber

feat(product-manage): 添加商品监控功能并优化编辑抽屉

- 新增监控商品功能,包括批量开启监控和单个商品监控状态切换
- 优化编辑抽屉样式,增加取消按钮功能
- 添加获取员工选项和已存在员工的接口
- 优化通知对话框,支持自动获取已绑定邮箱的用户
WanGxC 7 bulan lalu
induk
melakukan
1696d3b1c3

+ 25 - 0
src/views/product-manage/product-list/api.ts

@@ -31,6 +31,7 @@ export function getBrandsOptions() {
     url: apiPrefix + 'brands/',
   });
 }
+
 export function getShopOptions() {
   return request({
     url: '/api/choice/marketplace_shops/select/',
@@ -38,6 +39,30 @@ export function getShopOptions() {
   });
 }
 
+export function getStaffsOptions(query: any) {
+  return request({
+    url: '/api/system/user/select/',
+    params: query,
+    method: 'GET'
+  })
+}
+
+export function getExistingStaffs(query: any) {
+  return request({
+    url: apiPrefix + 'alarm-manage/',
+    params: query,
+    method: 'GET'
+  })
+}
+
+export function postMonitor(body: any) {
+  return request({
+    url: apiPrefix + 'monitor/',
+    data: body,
+    method: 'POST'
+  })
+}
+
 export function updateShopDetail(body: any) {
   return request({
     url: apiPrefix + `${ body.id }/`,

+ 29 - 9
src/views/product-manage/product-list/component/DataTable.vue

@@ -15,6 +15,7 @@ import VerticalDivider from '/src/components/VerticalDivider/index.vue';
 import { productColumns } from '../ColumnsTsx';
 import { downloadFile } from '/@/utils/service';
 import DataTableSlot from './DataTableSlot.vue';
+import { ElMessage } from 'element-plus';
 
 
 interface Parameter {
@@ -65,6 +66,7 @@ const gridOptions: any = reactive({
 });
 
 const checkedList = ref<Set<number>>(new Set());
+const btnLoading = ref(false);
 
 const editOpen = ref(false);
 const rowData = ref({});
@@ -99,13 +101,6 @@ function handleRefresh() {
   fetchList();
 }
 
-async function batchOpen() {
-  const ids = Array.from(checkedList.value);
-  await useResponse(api.updateShopDetail, { ids, status: 1 });
-  checkedList.value.clear();
-  await fetchList();
-}
-
 function selectChangeEvent({ checked, row }: any) {
   if (checked) {
     checkedList.value.add(row.id); // 获取单个数据
@@ -138,6 +133,29 @@ function handleNotice(row: any) {
   rowData.value = row;
 }
 
+async function switchMonitor(row: any) {
+  const res = await useResponse(api.postMonitor, { ids: [row.id], status: row.is_monitor ? 1 : 0 })
+  if (res && res.code === 2000) {
+    ElMessage.success('操作成功!');
+  } else {
+    row.is_monitor = !row.is_monitor;
+    ElMessage.error('操作失败!');
+  }
+}
+
+
+async function batchOpen() {
+  const ids = Array.from(checkedList.value);
+  console.log("(DataTable.vue: 148)=> ids", ids);
+  const res = await useResponse(api.postMonitor, { ids, status: 1 }, btnLoading);
+  if (res && res.code === 2000) {
+    ElMessage.success('操作成功!');
+  }
+  checkedList.value.clear();
+  await fetchList();
+}
+
+
 function downloadTemplate() {
   const urlMap: { [key: string]: string } = {
     notice: '/api/choice/goods/alarm/import_data/',
@@ -167,7 +185,8 @@ defineExpose({ fetchList });
     <!-- 工具栏左侧 -->
     <template #toolbar_buttons>
       <div class="flex gap-2">
-        <PermissionButton :disabled="!checkedList.size" :icon="Open" plain round type="primary" @click="batchOpen">
+        <PermissionButton :disabled="!checkedList.size" :icon="Open" :loading="btnLoading" plain round type="primary" 
+                          @click="batchOpen">
           批量开启
         </PermissionButton>
         <div class="custom-el-input">
@@ -225,7 +244,8 @@ defineExpose({ fetchList });
     </template>
     <!-- 自定义列插槽 -->
     <template v-for="col in productColumns" #[`${col.field}`]="{ row }">
-      <DataTableSlot :key="row.id" :field="col.field" :row="row" @edit-row="handleEdit" @handle-notice="handleNotice" />
+      <DataTableSlot :key="row.id" :field="col.field" :row="row" @edit-row="handleEdit" @handle-notice="handleNotice"
+     @handle-monitor="switchMonitor" />
     </template>
   </vxe-grid>
   <EditDrawer v-if="editOpen" v-model="editOpen" :row-data="rowData" @refresh="handleRefresh" />

+ 6 - 2
src/views/product-manage/product-list/component/DataTableSlot.vue

@@ -18,7 +18,7 @@ const props = defineProps<{
 }>();
 const { row, field } = props;
 
-const emit = defineEmits([ 'edit-row', 'handle-notice' ]);
+const emit = defineEmits([ 'edit-row', 'handle-notice', 'handle-monitor' ]);
 
 const countryInfoStore = useCountryInfoStore();
 const country = countryInfoStore.countries.find(c => c.code == row.country_code);
@@ -34,12 +34,16 @@ function handleEdit(row: any) {
 function handleNotice(row: any) {
   emit('handle-notice', row);
 }
+
+function handleMonitor(row: any) {
+  emit('handle-monitor', row);
+}
 </script>
 
 <template>
   <div class="font-medium">
     <div v-if="field === 'is_monitor'">
-      <el-switch v-model=row.is_monitor />
+      <el-switch v-model=row.is_monitor @change="handleMonitor(row)" />
     </div>
     <div v-else-if="field === 'product_info'">
       <ProductInfo :img-width="50" :item="row" style="min-width: 230px" />

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

@@ -6,7 +6,7 @@
  */
 
 import { ElMessage, FormInstance, FormRules } from 'element-plus';
-import { Finished } from '@element-plus/icons-vue';
+import { Close, Finished } from '@element-plus/icons-vue';
 import * as api from '../api';
 
 
@@ -17,6 +17,8 @@ const btnLoading = ref(false);
 
 const editOpen = defineModel({ default: false });
 
+const editDrawer = <Ref>useTemplateRef('editDrawer');
+
 const props = defineProps({
   rowData: Object
 });
@@ -55,12 +57,8 @@ const submitForm = async (formEl: FormInstance | undefined) => {
   if (!formEl) return;
   await formEl.validate(async (valid, fields) => {
     if (valid) {
-      const body = {
-        id: rowData?.id,
-        ...ruleForm,
-      }
       try {
-        const res = await useResponse( api.updateRow, body, btnLoading);
+        const res = await useResponse(api.updateRow, { id: rowData?.id, ...ruleForm }, btnLoading);
         if (res && res.code == 2000) {
           editOpen.value = false;
           ElMessage.success('编辑成功');
@@ -74,56 +72,76 @@ const submitForm = async (formEl: FormInstance | undefined) => {
     }
   });
 };
+
+function closeDrawer() {
+  editDrawer.value.handleClose();
+}
 </script>
 
 <template>
-  <el-drawer v-model="editOpen"
-             :close-on-click-modal="false"
-             :close-on-press-escape="false"
-             :title="`店铺编辑 - `"
-             size="30%">
-    <el-form
-        ref="ruleFormRef"
-        :model="ruleForm"
-        :rules="rules"
-        class="mx-2.5 mt-2.5"
-        label-width="auto"
-        status-icon>
-      <el-form-item label="SKU" prop="sku">
-        <el-input v-model="ruleForm.sku" />
-      </el-form-item>
-      <el-form-item label="店 铺" prop="shop_name">
-        <el-select v-model="ruleForm.shop_name">
-          <el-option v-for="item in shopOptions" :key="item.id" :label="item.name" :value="item.id" />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="分 组" prop="tag">
-        <el-select v-model="ruleForm.tag">
-          <el-option v-for="item in groupOptions" :label="item.tag" :value="item.tag" />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="展示价格" prop="show_price">
-        <el-input v-model="ruleForm.show_price" />
-      </el-form-item>
-      <el-form-item label="平时活动售价" prop="activity_price">
-        <el-input v-model="ruleForm.activity_price" />
-      </el-form-item>
-      <el-form-item label="最低活动售价" prop="minimum_price">
-        <el-input v-model="ruleForm.minimum_price" />
-      </el-form-item>
-      <el-form-item>
-        <el-divider />
-        <div class="flex flex-1 justify-end">
-          <el-button :icon="Finished" :loading="btnLoading" plain type="primary" @click="submitForm(ruleFormRef)">
-            确 定
-          </el-button>
-          <!--<el-button @click="resetForm(ruleFormRef)">重 置</el-button>-->
-        </div>
-      </el-form-item>
-    </el-form>
-  </el-drawer>
+  <div class="drawer-container">
+    <el-drawer
+        ref="editDrawer"
+        v-model="editOpen"
+        :close-on-click-modal="false"
+        :close-on-press-escape="false"
+        size="30%"
+        title="商品列表- 编辑">
+      <!--<div class="mx-2.5">-->
+      <!--  -->
+      <!--<el-divider style="margin: 0"  />-->
+      <!--</div>-->
+      <el-form
+          ref="ruleFormRef"
+          :model="ruleForm"
+          :rules="rules"
+          class="mx-2.5 mt-7"
+          label-width="auto"
+          status-icon>
+        <el-form-item class="font-medium" label="SKU" prop="sku">
+          <el-input v-model="ruleForm.sku" />
+        </el-form-item>
+        <el-form-item class="font-medium" label="店 铺" prop="shop_name">
+          <el-select v-model="ruleForm.shop_name">
+            <el-option v-for="item in shopOptions" :key="item.id" :label="item.name" :value="item.id" />
+          </el-select>
+        </el-form-item>
+        <el-form-item class="font-medium" label="分 组" prop="tag">
+          <el-select v-model="ruleForm.tag">
+            <el-option v-for="item in groupOptions" :label="item.tag" :value="item.tag" />
+          </el-select>
+        </el-form-item>
+        <el-form-item class="font-medium" label="展示价格" prop="show_price">
+          <el-input v-model="ruleForm.show_price" />
+        </el-form-item>
+        <el-form-item class="font-medium" label="平时活动售价" prop="activity_price">
+          <el-input v-model="ruleForm.activity_price" />
+        </el-form-item>
+        <el-form-item class="font-medium" label="最低活动售价" prop="minimum_price">
+          <el-input v-model="ruleForm.minimum_price" />
+        </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>

+ 36 - 17
src/views/product-manage/product-list/component/NoticeDialog.vue

@@ -4,20 +4,32 @@
  * @Author: Cheney
  */
 import { ElMessage } from 'element-plus';
+import { useResponse } from '/@/utils/useResponse';
+import * as api from '../api';
+import { getStaffsOptions } from '../api';
+import { Close, Finished } from '@element-plus/icons-vue';
 
 
-const { rowData } = defineProps<{
-  rowData: object;
-}>();
+const props = defineProps({
+  rowData: Object
+});
+const { rowData } = props;
 
 const dialogVisible = defineModel({ default: false });
 
+const noticeDialog = <Ref>useTemplateRef('noticeDialog');
+
 const staffSelect = ref('');
 const staffOptions: any = ref([]);
 const staffTags: any = ref([]);
 const staffLoading = ref(false);
 const currentRow: any = ref(null);
 
+onBeforeMount(() => {
+  fetchStaffsOptions();
+  fetchExistingStaff();
+})
+
 function handleClose(done: any) {
   staffSelect.value = '';
   staffTags.value = [];
@@ -40,15 +52,21 @@ function isOptionDisabled(id: any) {
 }
 
 function removeTag(tag: any) {
+  console.log('tag=> ', tag);
   staffTags.value = staffTags.value.filter((t: any) => t.id !== tag.id);
+  staffSelect.value = '';
 }
 
-async function fetchExistingStaff(row: any) {
-  const query = {
-    id: row.value.id ? row.value.id : row.value.rowid
-  };
-  // const resp = await api.getExistingStaffs(query);
-  // staffTags.value = resp.data;
+async function fetchStaffsOptions() {
+  const res = await useResponse(api.getStaffsOptions);
+  staffOptions.value = res.data;
+}
+
+async function fetchExistingStaff() {
+  const resp = await useResponse(api.getExistingStaffs, {
+    id: rowData?.id ? rowData?.id : rowData?.rowid
+  });
+  staffTags.value = resp.data;
 }
 
 async function addStaffs() {
@@ -72,15 +90,16 @@ async function addStaffs() {
 }
 
 function cancelDialog() {
-  handleClose(() => {
-    dialogVisible.value = false;
-  });
+  staffSelect.value = '';
+  staffTags.value = [];
+  noticeDialog.value.visible = false
 }
 </script>
 
 <template>
   <div>
     <el-dialog
+        ref="noticeDialog"
         v-model="dialogVisible"
         :before-close="handleClose"
         :close-on-click-modal="false"
@@ -103,16 +122,14 @@ function cancelDialog() {
           </el-select>
         </el-col>
       </el-row>
-      <el-row :gutter="20">
+      <el-row :gutter="20" class="mb-4">
         <el-col :span="2">
-
         </el-col>
         <el-col :span="20" class="ml-2.5">
           <i class="bi bi-info-circle"></i>
           <span class="ml-1" style="color: #909399">仅可添加已绑定邮箱的用户</span>
         </el-col>
       </el-row>
-      <el-divider style="margin: 12px 0 20px 0"></el-divider>
       <div class="flex flex-wrap gap-1.5">
         <el-tag
             v-for="tag in staffTags"
@@ -122,9 +139,11 @@ function cancelDialog() {
           {{ tag.username }}
         </el-tag>
       </div>
+      
+      <el-divider style="margin: 12px 0 20px 0"></el-divider>
       <span slot="footer" class="dialog-footer">
-        <el-button @click="cancelDialog">取 消</el-button>
-        <el-button :loading="staffLoading" type="primary" @click="addStaffs">确 定</el-button>
+        <el-button :icon="Close" @click="cancelDialog">取 消</el-button>
+        <el-button :icon="Finished" :loading="staffLoading" type="primary" @click="addStaffs">确 定</el-button>
       </span>
     </el-dialog>
   </div>