hurx пре 2 дана
родитељ
комит
a39b48b9a3

+ 56 - 1
src/api/pc/enterprise/index.ts

@@ -1,5 +1,5 @@
 import request from '@/utils/request';
-import { EnterpriseInfo, ShippingAddress, InvoiceInfo } from './types';
+import { EnterpriseInfo, ShippingAddress, InvoiceInfo, CustomerMessage } from './types';
 
 // ==================== 企业信息管理 ====================
 
@@ -179,3 +179,58 @@ export function getEnterpriseScaleList(params?: any) {
     params: params
   });
 }
+
+// ==================== 消息通知管理 ====================
+
+/**
+ * 查询消息通知列表
+ */
+export function getMessageList(params?: any) {
+  return request({
+    url: '/customer/pcCustomerMessage/list',
+    method: 'get',
+    params: params
+  });
+}
+
+/**
+ * 查询消息通知详情
+ */
+export function getMessageInfo(id: number) {
+  return request({
+    url: `/customer/pcCustomerMessage/${id}`,
+    method: 'get'
+  });
+}
+
+/**
+ * 新增消息通知
+ */
+export function addMessage(data: CustomerMessage) {
+  return request({
+    url: '/customer/pcCustomerMessage',
+    method: 'post',
+    data: data
+  });
+}
+
+/**
+ * 修改消息通知
+ */
+export function updateMessage(data: CustomerMessage) {
+  return request({
+    url: '/customer/pcCustomerMessage',
+    method: 'put',
+    data: data
+  });
+}
+
+/**
+ * 删除消息通知
+ */
+export function deleteMessage(ids: number[]) {
+  return request({
+    url: `/customer/pcCustomerMessage/${ids.join(',')}`,
+    method: 'delete'
+  });
+}

+ 13 - 0
src/api/pc/enterprise/types.ts

@@ -120,3 +120,16 @@ export interface ProductFavorites {
   remark?: string;
   [key: string]: any;
 }
+
+/**
+ * 消息通知
+ */
+export interface CustomerMessage {
+  id?: number;
+  customerId?: number;
+  title?: string;
+  content?: string;
+  readStatus?: string;
+  remark?: string;
+  [key: string]: any;
+}

+ 2 - 1
src/views/enterprise/companyInfo/index.vue

@@ -185,7 +185,8 @@ const loadEnterpriseInfo = async () => {
       companyData.phone = formatValue(data.landline);
       companyData.email = formatValue(businessInfo.email || data.fax);
       companyData.address = formatValue(data.address);
-      companyData.availableAmount = ((parseInt(salesInfo.remainingQuota) || 0) + (parseInt(salesInfo.temporaryQuota) || 0)).toFixed(2);
+      companyData.availableAmount = (parseInt(salesInfo.remainingQuota) || 0).toFixed(2); // 剩余额度
+      // companyData.deptCredit = data.deptCredit || '0.00';部门额度
       companyData.creditAmount = salesInfo.creditAmount || '0.00';
     }
   } catch (error) {

+ 199 - 157
src/views/enterprise/messageNotice/index.vue

@@ -1,7 +1,10 @@
 <template>
-  <div class="page-container">
-    <PageTitle title="消息通知" />
-
+  <div class="message-container">
+    <div class="page-title"><i class="title-bar"></i><span>消息通知</span></div>
+    <!-- 搜索栏 -->
+    <div class="search-bar">
+      <div class="search-right"><el-button type="danger" @click="handleAdd">新增消息通知</el-button></div>
+    </div>
     <!-- Tab切换 -->
     <!-- <div class="tab-nav">
       <div v-for="tab in tabs" :key="tab.key" :class="['tab-item', { active: activeTab === tab.key }]" @click="activeTab = tab.key">
@@ -11,36 +14,80 @@
     </div> -->
 
     <!-- 消息列表 -->
-    <div class="message-list">
-      <div v-for="(item, index) in messageList" :key="index" class="message-item">
-        <div class="message-content">
-          <div :class="['message-icon', item.iconType || 'user']">
-            <el-icon v-if="item.iconType === 'package'" :size="20" color="#e60012"><Box /></el-icon>
-            <template v-else-if="item.iconType === 'budget'"><span class="budget-text">¥</span></template>
-            <el-icon v-else :size="20" color="#fff"><User /></el-icon>
-            <span v-if="item.unread" class="unread-dot"></span>
-          </div>
-          <div class="message-info">
-            <div class="message-title">{{ item.title }}</div>
-            <div class="message-desc">{{ item.desc }}</div>
-          </div>
-          <div class="message-right">
-            <div class="message-time">{{ item.time }}</div>
-            <el-button v-if="item.showAction" type="danger" size="small" @click="handleProcess(item)">去处理</el-button>
+    <!-- <div class="message-list">
+        <div v-for="(item, index) in messageList" :key="index" class="message-item">
+          <div class="message-content">
+            <div :class="['message-icon', item.iconType || 'user']">
+              <el-icon v-if="item.iconType === 'package'" :size="20" color="#e60012"><Box /></el-icon>
+              <template v-else-if="item.iconType === 'budget'"><span class="budget-text">¥</span></template>
+              <el-icon v-else :size="20" color="#fff"><User /></el-icon>
+              <span v-if="item.unread" class="unread-dot"></span>
+            </div>
+            <div class="message-info">
+              <div class="message-title">{{ item.title }}</div>
+              <div class="message-desc">{{ item.content }}</div>
+            </div>
+            <div class="message-right">
+              <div class="message-time">{{ item.createTime }}</div>
+              <el-button v-if="item.showAction" type="danger" size="small" @click="handleProcess(item)">去处理</el-button>
+            </div>
           </div>
         </div>
-      </div>
-      <el-empty v-if="messageList.length === 0" description="暂无消息" />
-    </div>
+        <el-empty v-if="messageList.length === 0" description="暂无消息" />
+      </div> -->
 
     <!-- 分页 -->
-    <TablePagination
-      v-if="messageList.length > 0"
-      v-model:page="queryParams.pageNum"
-      v-model:pageSize="queryParams.pageSize"
-      :total="total"
-      @change="handleQuery"
-    />
+    <!-- <TablePagination
+        v-if="messageList.length > 0"
+        v-model:page="queryParams.pageNum"
+        v-model:pageSize="queryParams.pageSize"
+        :total="total"
+        @change="handleQuery"
+      /> -->
+
+    <!-- 表格 -->
+    <el-table :data="messageList" border style="width: 100%">
+      <el-table-column prop="title" label="标题" width="120" align="center" />
+      <el-table-column prop="content" label="内容" />
+      <el-table-column prop="createTime" label="创建时间" width="160" align="center" />
+
+      <el-table-column label="操作" width="120" align="center" fixed="right">
+        <template #default="{ row }">
+          <div style="display: flex; gap: 8px; justify-content: center">
+            <el-button type="primary" link @click="handleEdit(row)">修改</el-button>
+            <el-button type="danger" link @click="handleDelete(row)">删除</el-button>
+          </div>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <div class="pagination-wrap">
+      <span class="total-text">共计 {{ total }} 条</span>
+      <el-pagination
+        v-model:current-page="queryParams.pageNum"
+        v-model:page-size="queryParams.pageSize"
+        :page-sizes="[10, 20, 50]"
+        :total="total"
+        layout="prev, pager, next, sizes, jumper"
+        @size-change="handleQuery"
+        @current-change="handleQuery"
+      />
+    </div>
+    <!-- 新增/编辑弹窗 -->
+    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="550px">
+      <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
+        <el-form-item label="标题" prop="title">
+          <el-input v-model="form.title" placeholder="请输入标题" />
+        </el-form-item>
+        <el-form-item label="内容" prop="content">
+          <el-input v-model="form.content" type="textare" :min="5" placeholder="请输入内容" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="dialogVisible = false">取消</el-button>
+        <el-button type="danger" @click="handleSave">确定</el-button>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
@@ -49,61 +96,110 @@ import { ref, reactive, watch } from 'vue';
 import { Document, Bell, Warning, User, Box } from '@element-plus/icons-vue';
 import { ElMessage } from 'element-plus';
 import { PageTitle, TablePagination } from '@/components';
+import { getMessageList, getMessageInfo, addMessage, updateMessage, deleteMessage } from '@/api/pc/enterprise';
 
 const activeTab = ref('approval');
+const dialogVisible = ref(false);
+const dialogTitle = ref('新增消息通知');
+const formRef = ref();
+const editingId = ref<number | null>(null);
+const form = reactive({ title: '', content: '' });
+
+const rules = {
+  title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
+  content: [{ required: true, message: '请输入内容', trigger: 'blur' }]
+};
 const tabs = [
   { key: 'approval', label: '审批待办', icon: Document },
   { key: 'arrival', label: '到货提醒', icon: Bell },
   { key: 'budget', label: '预算预警', icon: Warning }
 ];
 
+const resetForm = () => {
+  form.title = '';
+  form.content = '';
+  editingId.value = null;
+};
 const queryParams = reactive({ pageNum: 1, pageSize: 10 });
 const total = ref(0);
 const messageList = ref<any[]>([]);
-
 const loadData = () => {
   if (activeTab.value === 'approval') {
-    messageList.value = [
-      // {
-      //   time: '2025/01/10 11:42:32',
-      //   title: '您有一个审批流程待处理',
-      //   desc: '审批名称:办公用品采购申请',
-      //   unread: true,
-      //   showAction: true,
-      //   iconType: 'user'
-      // },
-      // {
-      //   time: '2025/01/10 11:42:32',
-      //   title: '您的办公用品审批申请已通过',
-      //   desc: '审批名称:办公用品采购申请',
-      //   unread: true,
-      //   showAction: false,
-      //   iconType: 'user'
-      // },
-      // {
-      //   time: '2025/01/10 11:42:32',
-      //   title: '您的办公用品审批申请已通过',
-      //   desc: '审批名称:办公用品采购申请',
-      //   unread: true,
-      //   showAction: false,
-      //   iconType: 'user'
-      // }
-    ];
-    total.value = 3;
+    messageList.value = [];
+    total.value = 0;
   } else if (activeTab.value === 'arrival') {
-    messageList.value = [
-      // { time: '2025/01/10 11:42:32', title: '包裹到货提醒', desc: '物流状态:已到达代售点', unread: true, showAction: false, iconType: 'package' },
-      // { time: '2025/01/10 11:42:32', title: '包裹到货提醒', desc: '物流状态:已到达代售点', unread: false, showAction: false, iconType: 'package' }
-    ];
-    total.value = 2;
+    messageList.value = [];
+    total.value = 0;
   } else {
-    messageList.value = [
-      // { time: '2025/01/10 11:42:32', title: '预算预警', desc: '副标题副副标题', unread: true, showAction: false, iconType: 'budget' },
-      // { time: '2025/01/10 11:42:32', title: '预算预警', desc: '副标题副副标题', unread: false, showAction: false, iconType: 'budget' }
-    ];
-    total.value = 2;
+    messageList.value = [];
+    total.value = 0;
   }
 };
+const handleAdd = () => {
+  resetForm();
+  dialogTitle.value = '新增收货地址';
+  dialogVisible.value = true;
+};
+const handleEdit = (item: any) => {
+  editingId.value = item.id;
+  form.title = item.title;
+  form.content = item.content;
+
+  dialogTitle.value = '编辑收货地址';
+  dialogVisible.value = true;
+};
+const handleSave = async () => {
+  const valid = await formRef.value?.validate();
+  if (!valid) return;
+  try {
+    const data: any = {
+      title: form.title,
+      content: form.content
+    };
+
+    if (editingId.value) {
+      data.id = editingId.value;
+      await updateMessage(data);
+    } else {
+      await addMessage(data);
+    }
+    ElMessage.success(editingId.value ? '修改成功' : '新增成功');
+    dialogVisible.value = false;
+    loadMessageList();
+  } catch (error) {
+    ElMessage.error('操作失败');
+  }
+};
+const handleDelete = (item: any) => {
+  ElMessageBox.confirm('确定要删除该消息通知吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(
+    async () => {
+      try {
+        await deleteMessage([item.id]);
+        ElMessage.success('删除成功');
+        loadMessageList();
+      } catch (error) {
+        ElMessage.error('删除失败');
+      }
+    }
+  );
+};
+
+// 加载消息通知
+const loadMessageList = async () => {
+  try {
+    const res = await getMessageList();
+    if (res.code === 200) {
+      messageList.value = res.rows || [];
+      total.value = res.total || 0;
+    }
+  } catch (error) {
+    ElMessage.error('加载消息通知列表失败');
+  }
+};
+
+onMounted(() => {
+  loadMessageList();
+});
 
 watch(
   activeTab,
@@ -114,7 +210,7 @@ watch(
   { immediate: true }
 );
 const handleQuery = () => {
-  loadData();
+  loadMessageList();
 };
 const handleProcess = (_item: any) => {
   ElMessage.info('跳转到审批处理页面');
@@ -122,100 +218,46 @@ const handleProcess = (_item: any) => {
 </script>
 
 <style scoped lang="scss">
-.tab-nav {
+.message-container {
+  padding: 20px;
+  background: #fff;
+  min-height: 100%;
+  flex: 1;
+}
+.page-title {
+  font-size: 16px;
+  font-weight: bold;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 20px;
+}
+.title-bar {
+  display: inline-block;
+  width: 3px;
+  height: 16px;
+  background: #e60012;
+  border-radius: 2px;
+}
+.search-bar {
   display: flex;
-  gap: 30px;
-  border-bottom: 1px solid #eee;
+  align-items: center;
+  gap: 10px;
   margin-bottom: 20px;
-  .tab-item {
+  .search-right {
+    flex: 1;
     display: flex;
-    align-items: center;
-    gap: 5px;
-    padding: 10px 0;
-    cursor: pointer;
-    color: #666;
-    font-size: 14px;
-    border-bottom: 2px solid transparent;
-    margin-bottom: -1px;
-    &:hover,
-    &.active {
-      color: #333;
-    }
-    &.active {
-      border-bottom-color: #e60012;
-    }
+    justify-content: flex-end;
   }
 }
-.message-list {
-  .message-item {
-    padding: 15px 0;
-    border-bottom: 1px solid #f5f5f5;
-    &:last-child {
-      border-bottom: none;
-    }
-    .message-content {
-      display: flex;
-      align-items: center;
-      gap: 15px;
-      .message-icon {
-        position: relative;
-        width: 40px;
-        height: 40px;
-        border-radius: 8px;
-        display: flex;
-        align-items: center;
-        justify-content: center;
-        flex-shrink: 0;
-        &.user {
-          background: #e60012;
-        }
-        &.package {
-          background: #fff5f5;
-          border: 1px solid #ffe0e0;
-        }
-        &.budget {
-          background: #e60012;
-          .budget-text {
-            color: #fff;
-            font-size: 16px;
-            font-weight: bold;
-          }
-        }
-        .unread-dot {
-          position: absolute;
-          top: -2px;
-          right: -2px;
-          width: 8px;
-          height: 8px;
-          border-radius: 50%;
-          background: #e60012;
-          border: 2px solid #fff;
-        }
-      }
-      .message-info {
-        flex: 1;
-        .message-title {
-          font-size: 14px;
-          font-weight: 500;
-          color: #333;
-          margin-bottom: 5px;
-        }
-        .message-desc {
-          font-size: 13px;
-          color: #999;
-        }
-      }
-      .message-right {
-        display: flex;
-        flex-direction: column;
-        align-items: flex-end;
-        gap: 8px;
-        .message-time {
-          font-size: 12px;
-          color: #999;
-        }
-      }
-    }
+.pagination-wrap {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-top: 20px;
+  .total-text {
+    font-size: 14px;
+    color: #666;
   }
 }
 </style>

+ 111 - 63
src/views/order/orderManage/logisticsDetail.vue

@@ -6,21 +6,15 @@
         <el-option
           v-for="item in logisticsList"
           :key="item.id"
-          :label="`${item.logisticNo},${getDictLabel(deliver_method, item.deliverMethod)}`"
-          :value="item.id"
+          :label="`${item.logisticNo || item.deliverCode},${getDictLabel(deliver_method, item.deliverMethod)}`"
+          :value="item.logisticNo || item.deliverCode"
         />
       </el-select>
 
       <div class="section-title" style="margin-top: 20px">物流信息</div>
       <div class="timeline-container">
         <el-timeline>
-          <el-timeline-item
-            v-for="(item, index) in logisticsTrack"
-            :key="index"
-            :timestamp="item.time"
-            placement="top"
-            :color="index === 0 ? '#409EFF' : '#C0C4CC'"
-          >
+          <el-timeline-item v-for="(item, index) in logisticsTrack" :key="index" :timestamp="item.time" placement="top" color="#409EFF">
             <div class="track-number">{{ item.trackingNo }}</div>
             <div class="track-content">{{ item.content }}</div>
           </el-timeline-item>
@@ -34,7 +28,7 @@
 import { ref, watch } from 'vue';
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const { deliver_method } = toRefs<any>(proxy?.useDict('deliver_method'));
-import { selectOrderDeliverByOrderId, queryTrack } from '@/api/pc/enterprise/order';
+import { selectOrderDeliverByOrderId, queryTrack, listOrderStatusLog } from '@/api/pc/enterprise/order';
 
 interface TrackItem {
   time: string;
@@ -74,24 +68,20 @@ watch(visible, (val) => {
   emit('update:modelValue', val);
 });
 
-watch(selectedLogistics, (val) => {
-  if (val) {
-    loadTrackData(val);
-  }
-});
+// watch(selectedLogistics, (val) => {
+//   if (val) {
+//     loadTrackData(val);
+//   }
+// });
 
 const loadLogisticsData = async () => {
   if (!props.orderId) return;
   try {
     const res = await selectOrderDeliverByOrderId({ orderId: props.orderId });
-    console.log(res);
-
-    if (res.code === 200 && res.rows) {
-      logisticsList.value = res.rows;
-      if (logisticsList.value.length > 0) {
-        selectedLogistics.value = logisticsList.value[0].id as string;
-        handleLogisticNoChange(logisticsList.value[0].logisticNo);
-      }
+    logisticsList.value = res.rows || [];
+    if (logisticsList.value.length > 0) {
+      selectedLogistics.value = logisticsList.value[0].logisticNo || logisticsList.value[0].deliverCode;
+      handleLogisticNoChange(selectedLogistics.value);
     }
   } catch (error) {
     console.error('获取物流单号失败:', error);
@@ -100,24 +90,53 @@ const loadLogisticsData = async () => {
 
 const handleLogisticNoChange = async (logisticNo: string) => {
   const selected = logisticsList.value.find((item) => item.logisticNo === logisticNo);
-  if (!selected) return;
 
   try {
-    const res = await queryTrack({ logisticNo: logisticNo });
-    if (res.status == 200 && res.data && res.data) {
-      logisticsTrack.value = res.data.map((item: any) => ({
-        time: item.time,
-        trackingNo: selected.logisticNo,
-        content: item.context
-      }));
+    if (selected) {
+      const res = await queryTrack({ logisticNo: logisticNo });
+      // 1. 兼容处理:有些接口返回在 res.data,有些可能直接是 res
+      const dataList = res.data || [];
+      if (Array.isArray(dataList) && dataList.length > 0) {
+        logisticsTrack.value = dataList.map((item: any) => {
+          // 2. 核心修复:精准匹配时间字段
+          // 顺丰用 'time',韵达用 'ftime'。
+          // 优先取 ftime (韵达标准),如果没有则取 time (顺丰标准)
+          const displayTime = item.ftime || item.time || item.acceptTime || '';
+
+          return {
+            time: displayTime,
+            // 3. 建议:保留原始状态字段,方便后续筛选(如“已签收”)
+            content: item.context || item.content || '',
+            // 4. 建议:如果有地址字段,也可以映射进来,没有则保持订单号
+            trackingNo: item.location || (selected.orderCode ? `${selected.orderCode}` : '')
+          };
+        });
+      }
     } else {
-      logisticsTrack.value = [
-        {
-          time: (selected as any).createTime || '',
-          trackingNo: selected.orderCode ? `${selected.orderCode}` : '',
-          content: '已下单'
+      await listOrderStatusLog({
+        orderId: props.orderId,
+        logisticNos: selectedLogistics.value,
+        pageNum: 1,
+        pageSize: 100
+      }).then((res) => {
+        if (res && res.code == 200) {
+          logisticsTrack.value = res.rows.map((item: any) => {
+            return {
+              time: item.createTime,
+              trackingNo: item.orderCode ? `${item.orderCode}` : '',
+              content: item.statusName
+            };
+          });
+        } else {
+          logisticsTrack.value = [
+            {
+              time: (selected as any).createTime || '',
+              trackingNo: selected.orderCode ? `${selected.orderCode}` : '',
+              content: '已下单'
+            }
+          ];
         }
-      ];
+      });
     }
   } catch (error) {
     console.error('查询物流轨迹失败:', error);
@@ -125,32 +144,61 @@ const handleLogisticNoChange = async (logisticNo: string) => {
   }
 };
 
-const loadTrackData = async (logisticsId: string | number) => {
-  const selectedItem = logisticsList.value.find((item) => item.id === logisticsId);
-  if (!selectedItem) return;
-
-  try {
-    const res = await queryTrack({ logisticNo: selectedItem.logisticNo });
-    if (res.status == 200 && res.data && res.data) {
-      logisticsTrack.value = res.data.map((item: any) => ({
-        time: item.time,
-        trackingNo: selectedItem.logisticNo,
-        content: item.context
-      }));
-    } else {
-      logisticsTrack.value = [
-        {
-          time: (selectedItem as any).createTime || '',
-          trackingNo: selectedItem.orderCode ? `${selectedItem.orderCode}` : '',
-          content: '已下单'
-        }
-      ];
-    }
-  } catch (error) {
-    console.error('查询物流轨迹失败:', error);
-    logisticsTrack.value = [];
-  }
-};
+// const loadTrackData = async (logisticsId: string | number) => {
+//   const selectedItem = logisticsList.value.find((item) => item.id === logisticsId);
+
+//   try {
+//     if (selectedItem) {
+//       const res = await queryTrack({ logisticNo: selectedItem.logisticNo });
+//       // 1. 兼容处理:有些接口返回在 res.data,有些可能直接是 res
+//       const dataList = res.data || [];
+//       if (Array.isArray(dataList) && dataList.length > 0) {
+//         logisticsTrack.value = dataList.map((item: any) => {
+//           // 2. 核心修复:精准匹配时间字段
+//           // 顺丰用 'time',韵达用 'ftime'。
+//           // 优先取 ftime (韵达标准),如果没有则取 time (顺丰标准)
+//           const displayTime = item.ftime || item.time || item.acceptTime || '';
+
+//           return {
+//             time: displayTime,
+//             // 3. 建议:保留原始状态字段,方便后续筛选(如“已签收”)
+//             content: item.context || item.content || '',
+//             // 4. 建议:如果有地址字段,也可以映射进来,没有则保持订单号
+//             trackingNo: selectedItem.orderCode ? `${selectedItem.orderCode}` : ''
+//           };
+//         });
+//       }
+//     } else {
+//       await listOrderStatusLog({
+//         orderId: props.orderId,
+//         logisticNos: selectedLogistics.value,
+//         pageNum: 1,
+//         pageSize: 100
+//       }).then((res) => {
+//         if (res && res.code == 200) {
+//           logisticsTrack.value = res.rows.map((item: any) => {
+//             return {
+//               time: item.createTime,
+//               trackingNo: item.orderCode ? `${item.orderCode}` : '',
+//               content: item.statusName
+//             };
+//           });
+//         } else {
+//           logisticsTrack.value = [
+//             {
+//               time: (selectedItem as any).createTime || '',
+//               trackingNo: selectedItem.orderCode ? `${selectedItem.orderCode}` : '',
+//               content: '已下单'
+//             }
+//           ];
+//         }
+//       });
+//     }
+//   } catch (error) {
+//     console.error('查询物流轨迹失败:', error);
+//     logisticsTrack.value = [];
+//   }
+// };
 
 const handleClose = () => {
   visible.value = false;