Sfoglia il codice sorgente

修改销售发票部分

hurx 12 ore fa
parent
commit
85f88a72d9

+ 11 - 0
src/api/pc/enterprise/index.ts

@@ -46,6 +46,17 @@ export function normalChangePwd(data: any) {
   });
 }
 
+/**
+ * 修改手机号
+ */
+export function changePhonenumber(data: any) {
+  return request({
+    url: '/system/psSysUser/changePhonenumber', //更换手机号
+    method: 'put',
+    data: data
+  });
+}
+
 // ==================== 收货地址管理 ====================
 
 /**

+ 12 - 0
src/api/pc/enterprise/orderReturn.ts

@@ -58,3 +58,15 @@ export function deleteOrderReturn(ids: number[]) {
     method: 'delete'
   });
 }
+
+export function changeOrderReturnStatus(id: string | number, status: string) {
+  const data = {
+    id,
+    status
+  };
+  return request({
+    url: '/order/pcOrder/orderReturn/changeStatus',
+    method: 'put',
+    data: data
+  });
+}

+ 11 - 0
src/api/pc/enterprise/statement.ts

@@ -87,6 +87,17 @@ export function getStatementInvoiceList(params?: any) {
   });
 }
 
+/**
+ * 查询销售发票主详细
+ * @param id
+ */
+export const getStatementInvoice = (id: string | number) => {
+  return request({
+    url: `/bill/pcStatementInvoice/${id}`,
+    method: 'get'
+  });
+};
+
 /**
  * 申请开票
  */

+ 18 - 0
src/router/index.ts

@@ -440,6 +440,12 @@ export const constantRoutes: RouteRecordRaw[] = [
         component: () => import('@/views/cost/itemExpense/index.vue'),
         meta: { title: '分项费用', workbench: true }
       },
+      {
+        path: 'cost/manageDetail',
+        name: 'ManageDetail',
+        component: () => import('@/views/cost/itemExpense/manage.vue'),
+        meta: { title: '分项费用类型', workbench: true }
+      },
       {
         path: 'cost/quotaControl',
         name: 'QuotaControl',
@@ -458,12 +464,24 @@ export const constantRoutes: RouteRecordRaw[] = [
         component: () => import('@/views/reconciliation/billManage/index.vue'),
         meta: { title: '对账单管理', workbench: true }
       },
+      {
+        path: 'reconciliation/billManage/detail',
+        name: 'BillManageDetail',
+        component: () => import('@/views/reconciliation/billManage/detail.vue'),
+        meta: { title: '对账单详情', hidden: true, workbench: true }
+      },
       {
         path: 'reconciliation/invoiceManage',
         name: 'ReconciliationInvoiceManage',
         component: () => import('@/views/reconciliation/invoiceManage/index.vue'),
         meta: { title: '开票管理', workbench: true }
       },
+      {
+        path: 'reconciliation/invoiceManage/detail',
+        name: 'InvoiceManageDetail',
+        component: () => import('@/views/reconciliation/invoiceManage/detail.vue'),
+        meta: { title: '销售发票详情', hidden: true, workbench: true }
+      },
       {
         path: 'valueAdded/maintenance',
         name: 'Maintenance',

+ 3 - 0
src/utils/siteConfig.ts

@@ -68,7 +68,9 @@ export const SITE_ROUTES: Record<any, string[]> = {
     '/enterprise/purchasePlan',
     '/enterprise/purchaseHistory',
     '/reconciliation/billManage',
+    '/reconciliation/billManage/detail',
     '/reconciliation/invoiceManage',
+    '/reconciliation/invoiceManage/detail',
     '/organization/deptManage',
     '/organization/staffManage',
     '/organization/roleManage',
@@ -76,6 +78,7 @@ export const SITE_ROUTES: Record<any, string[]> = {
     '/valueAdded/complaint',
     '/cost/itemExpense',
     '/cost/quotaControl',
+    '/cost/manageDetail',
     '/cost/quotaControl/apply',
     '/enterprise/purchasePlan',
     '/organization/approvalFlow',

+ 11 - 3
src/views/cost/itemExpense/index.vue

@@ -22,9 +22,10 @@
           <span :class="['status-text', row.status === '启用' ? 'active' : '']">{{ row.status == '0' ? '启用' : '禁用' }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="操作" width="100" align="center">
+      <el-table-column label="操作" width="150" align="center">
         <template #default="{ row }">
           <el-button type="primary" link size="small" @click="handleEdit(row)">编辑</el-button>
+          <el-button type="primary" link size="small" @click="handleManage(row)">管理</el-button>
           <el-button type="danger" link size="small" @click="handleDelete(row)">删除</el-button>
         </template>
       </el-table-column>
@@ -59,12 +60,12 @@ import { ref, reactive, computed } from 'vue';
 import { ElMessage, ElMessageBox } from 'element-plus';
 import { getItemExpenseList, addItemExpense, updateItemExpense, deleteItemExpense } from '@/api/pc/cost/itemExpense';
 import { PageTitle } from '@/components';
-import { id } from 'element-plus/es/locale/index.mjs';
+import { useRouter } from 'vue-router';
 
 const dialogVisible = ref(false);
 const formRef = ref();
 const editingRow = ref<any>(null);
-
+const router = useRouter();
 const formData = reactive({
   id: 0,
   expenseName: '',
@@ -110,6 +111,13 @@ const handleEdit = (row: any) => {
   dialogVisible.value = true;
 };
 
+const handleManage = (row: any) => {
+  router.push({
+    path: '/cost/manageDetail',
+    query: { id: row.id }
+  });
+};
+
 const handleDelete = (row: any) => {
   ElMessageBox.confirm(`确定要删除"${row.name}"吗?`, '提示', {
     confirmButtonText: '确定',

+ 101 - 0
src/views/cost/itemExpense/manage.vue

@@ -0,0 +1,101 @@
+<template>
+  <div class="page-container">
+    <div class="page-header">
+      <PageTitle title="分项费用类型明细" />
+      <el-button type="danger" @click="handleBack">返回</el-button>
+    </div>
+
+    <el-table :data="itemDetailList" border>
+      <el-table-column prop="" label="部门名称" min-width="200" />
+      <el-table-column prop="status" label="状态" min-width="100" align="center">
+        <template #default="{ row }">
+          <span :class="['status-text', row.status === '启用' ? 'active' : '']">{{ row.status == '0' ? '启用' : '禁用' }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column prop="" label="现有额度(年)" min-width="150" align="center">
+        <!-- <template #default="{ row }">
+          <span>{{ row.usedQuota || '0.00' }}</span>
+        </template> -->
+      </el-table-column>
+      <el-table-column prop="usedQuota" label="已用额度(年)" min-width="150" align="center">
+        <!-- <template #default="{ row }">
+          <span>{{ row.usedQuota || '0.00' }}</span>
+        </template> -->
+      </el-table-column>
+      <el-table-column prop="usedQuota" label="剩余额度(年)" min-width="150" align="center">
+        <!-- <template #default="{ row }">
+          <span>{{ row.usedQuota || '0.00' }}</span>
+        </template> -->
+      </el-table-column>
+      <el-table-column label="操作" width="100" align="center">
+        <!-- <template #default="{ row }"> </template> -->
+      </el-table-column>
+    </el-table>
+  </div>
+</template>
+<script setup lang="ts">
+import { defineComponent } from 'vue';
+import { useRouter } from 'vue-router';
+const itemDetailList = ref<any[]>([]);
+const router = useRouter();
+
+const handleBack = () => {
+  router.push({
+    path: '/cost/itemExpense'
+  });
+};
+</script>
+<style scoped lang="scss">
+.page-container {
+  padding: 20px;
+  background: #fff;
+  min-height: 100%;
+  flex: 1;
+}
+.page-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+
+  :deep(.page-title) {
+    margin-bottom: 0;
+  }
+}
+.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;
+  align-items: center;
+  gap: 10px;
+  margin-bottom: 20px;
+  .search-right {
+    flex: 1;
+    display: flex;
+    justify-content: flex-end;
+  }
+}
+.pagination-wrap {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-top: 20px;
+  .total-text {
+    font-size: 14px;
+    color: #666;
+  }
+}
+</style>

+ 18 - 4
src/views/enterprise/securitySetting/changePhone.vue

@@ -112,6 +112,7 @@ import { ref, reactive } from 'vue';
 import { Check, CircleCheckFilled, Phone } from '@element-plus/icons-vue';
 import { ElMessage } from 'element-plus';
 import { useRoute } from 'vue-router';
+import { changePhonenumber } from '@/api/pc/enterprise/index';
 const currentStep = ref(1);
 const route = useRoute();
 // 步骤1表单
@@ -190,10 +191,23 @@ const handleNextStep = async () => {
 const handleSubmit = async () => {
   const valid = await step2FormRef.value?.validate().catch(() => false);
   if (!valid) return;
-
-  // TODO: 调用更换手机接口
-  ElMessage.success('手机号码更换成功');
-  currentStep.value = 3;
+  try {
+    const submitData = {
+      phone: step1Form.phone,
+      newPhonenumber: step2Form.newPhone,
+      code: step1Form.code
+    };
+
+    const res: any = await changePhonenumber(submitData);
+    if (res.code === 200) {
+      ElMessage.success('手机号修改成功');
+      currentStep.value = 3;
+    } else {
+      ElMessage.error(res.msg || '手机号修改失败');
+    }
+  } catch (error) {
+    console.error('手机号修改失败:', error);
+  }
 };
 </script>
 

+ 26 - 7
src/views/order/afterSale/index.vue

@@ -81,9 +81,13 @@
             <div class="col-time">{{ item.applyTime }}</div>
             <div class="col-type">{{ getDictLabel(service_type, item.serviceType) }}</div>
             <div class="col-status">
-              <span :class="['status-text', getStatusClass(item.status)]">{{ item.statusText }}</span>
+              <span :class="['status-text', getStatusClass(item.returnStatus)]">{{ item.returnStatusText }}</span>
+            </div>
+
+            <div class="col-action">
+              <el-button type="primary" v-if="item.returnStatus == '0'" link size="small" @click="handleReturn(item)">撤回</el-button
+              ><el-button type="primary" link size="small" @click="handleViewDetail(item)">查看详情</el-button>
             </div>
-            <div class="col-action"><el-button type="primary" link size="small" @click="handleViewDetail(item)">查看详情</el-button></div>
           </div>
         </div>
         <el-empty v-if="afterSaleList.length === 0" description="暂无售后记录" />
@@ -228,7 +232,7 @@ import { useRouter } from 'vue-router';
 import { Search, RefreshRight, Tools, ChatLineSquare, Picture } from '@element-plus/icons-vue';
 import { ElMessage, ElMessageBox } from 'element-plus';
 import { PageTitle, SearchBar, TablePagination } from '@/components';
-import { getOrderReturnList, getOrderReturnInfo, addOrderReturn, updateOrderReturn } from '@/api/pc/enterprise/orderReturn';
+import { getOrderReturnList, getOrderReturnInfo, addOrderReturn, updateOrderReturn, changeOrderReturnStatus } from '@/api/pc/enterprise/orderReturn';
 import type { OrderReturn } from '@/api/pc/enterprise/orderReturnTypes';
 import {
   getComplaintsSuggestionsList,
@@ -236,8 +240,6 @@ import {
   updateComplaintsSuggestions,
   deleteComplaintsSuggestions
 } from '@/api/pc/valueAdded';
-import { status } from 'nprogress';
-import { stat } from 'fs';
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const { service_type, complaints_suggestion_type } = toRefs<any>(proxy?.useDict('service_type', 'complaints_suggestion_type'));
 
@@ -295,6 +297,7 @@ const loadAfterSaleData = async () => {
     const res = await getOrderReturnList({
       pageNum: pagination.page,
       pageSize: pagination.pageSize,
+      status: '0',
       returnStatus: activeStatusTab.value === 'all' ? '' : activeStatusTab.value,
       serviceType: activeMainTab.value === 'return' ? '1' : activeMainTab.value === 'repair' ? '3' : ''
     });
@@ -310,8 +313,9 @@ const loadAfterSaleData = async () => {
         quantity: item.returnProductNum || 1,
         applyTime: item.returnTime,
         serviceType: item.serviceType,
-        status: item.returnStatus,
-        statusText: getStatusText(item.returnStatus),
+        returnStatus: item.returnStatus,
+        status: item.status,
+        returnStatusText: getStatusText(item.returnStatus),
         type: item.serviceType === '1' ? 'return' : 'repair'
       }));
       pagination.total = res.total | 0;
@@ -383,6 +387,21 @@ const handleViewDetail = async (item: any) => {
   });
 };
 
+const handleReturn = async (row: any) => {
+  if (row.status !== '0') {
+    proxy?.$modal.msgWarning('该售后单已处理,请勿重复处理');
+    return;
+  }
+  try {
+    await proxy?.$modal.confirm('确认要撤回该售后单吗?');
+    await changeOrderReturnStatus(row.id, '1');
+    proxy?.$modal.msgSuccess('操作成功');
+  } catch (err) {
+    row.status = row.status === '0' ? '1' : '0';
+  }
+  loadAfterSaleData();
+};
+
 const formatMoney = (val: number | string) => {
   const num = Number(val) || 0;
   return num.toFixed(2);

+ 56 - 49
src/views/reconciliation/billManage/detail.vue

@@ -1,5 +1,12 @@
 <template>
-  <el-dialog title="对账单详情" v-model="visible" width="800px" append-to-body destroy-on-close>
+  <div class="page-container">
+    <div class="page-header">
+      <el-button type="primary" link @click="handleBack">
+        <el-icon><ArrowLeft /></el-icon>返回
+      </el-button>
+      <span class="page-title">对账单详情</span>
+    </div>
+
     <div v-loading="loading">
       <el-descriptions title="基本信息" :column="2" border class="margin-bottom">
         <el-descriptions-item label="对账编号">{{ form.statementOrderNo }}</el-descriptions-item>
@@ -9,13 +16,13 @@
         <el-descriptions-item label="对账人">{{ form.statementSelf || '-' }}</el-descriptions-item>
         <el-descriptions-item label="联系电话">{{ form.statementSelfPhone || '-' }}</el-descriptions-item>
         <el-descriptions-item label="对账状态">
-          <dict-tag :options="statement_status" :value="form.statementStatus" />
+          {{ getDictLabel(statement_status, form.statementStatus) }}
         </el-descriptions-item>
         <el-descriptions-item label="开票状态">
-          <dict-tag :options="invoice_issuance_status" :value="form.isInvoiceStatus" />
+          {{ getDictLabel(invoice_issuance_status, form.isInvoiceStatus) }}
         </el-descriptions-item>
         <el-descriptions-item label="支付状态">
-          <dict-tag :options="payment_status" :value="form.isPaymentStatus" />
+          {{ getDictLabel(payment_status, form.isPaymentStatus) }}
         </el-descriptions-item>
         <el-descriptions-item label="备注" :span="2">{{ form.remark || '-' }}</el-descriptions-item>
       </el-descriptions>
@@ -26,7 +33,6 @@
         <el-table :data="form.detailList" border style="width: 100%; margin-bottom: 20px">
           <el-table-column prop="orderNo" label="订单编号" min-width="150" align="center" />
           <el-table-column prop="deliverCode" label="发货单号" min-width="150" align="center" />
-          <!-- <el-table-column prop="type" label="明细类型" min-width="100" align="center" /> -->
           <el-table-column prop="amount" label="金额(元)" min-width="100" align="center">
             <template #default="{ row }"> ¥{{ row.amount != null ? Number(row.amount).toFixed(2) : '-' }} </template>
           </el-table-column>
@@ -64,26 +70,24 @@
         </el-table>
       </div>
     </div>
-    <template #footer>
-      <div class="dialog-footer">
-        <el-button @click="visible = false">关 闭</el-button>
-      </div>
-    </template>
-  </el-dialog>
+  </div>
 </template>
 
 <script setup lang="ts">
-import { ref, computed, getCurrentInstance, toRefs } from 'vue';
+import { ref, computed, getCurrentInstance, toRefs, onMounted } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import { ArrowLeft } from '@element-plus/icons-vue';
 import { ElMessage } from 'element-plus';
 import { getStatementInfo } from '@/api/pc/enterprise/statement';
-import type { StatementOrder, StatementDetail, StatementProduct } from '@/api/pc/enterprise/statementTypes';
+import type { StatementOrder } from '@/api/pc/enterprise/statementTypes';
 
 const { proxy } = getCurrentInstance() as any;
 const { statement_status, invoice_issuance_status, payment_status } = toRefs<any>(
   proxy?.useDict('statement_status', 'invoice_issuance_status', 'payment_status')
 );
 
-const visible = ref(false);
+const route = useRoute();
+const router = useRouter();
 const loading = ref(false);
 const form = ref<Partial<StatementOrder>>({});
 
@@ -100,6 +104,12 @@ const attachmentList = computed(() => {
   });
 });
 
+const getDictLabel = (dictOptions: any[], value: string) => {
+  if (!dictOptions || !value) return value;
+  const dict = dictOptions.find((item) => item.value === value);
+  return dict ? dict.label : value;
+};
+
 // 预览附件
 const handlePreview = (url: string) => {
   if (!url) {
@@ -109,37 +119,11 @@ const handlePreview = (url: string) => {
   window.open(url, '_blank');
 };
 
-// 下载附件
-const handleDownload = async (url: string, name: string) => {
-  if (!url) {
-    ElMessage.warning('文件地址不存在');
-    return;
-  }
-  try {
-    const response = await fetch(url);
-    const blob = await response.blob();
-    const blobUrl = window.URL.createObjectURL(blob);
-    const link = document.createElement('a');
-    link.href = blobUrl;
-    link.download = name || '附件';
-    document.body.appendChild(link);
-    link.click();
-    document.body.removeChild(link);
-    window.URL.revokeObjectURL(blobUrl);
-  } catch (error) {
-    console.error('下载失败:', error);
-    const link = document.createElement('a');
-    link.href = url;
-    link.download = name || '附件';
-    link.target = '_blank';
-    document.body.appendChild(link);
-    link.click();
-    document.body.removeChild(link);
-  }
+const handleBack = () => {
+  router.back();
 };
 
-const open = async (id: number | string) => {
-  visible.value = true;
+const loadDetail = async (id: number | string) => {
   loading.value = true;
   try {
     const res = await getStatementInfo(id);
@@ -156,16 +140,39 @@ const open = async (id: number | string) => {
   }
 };
 
-defineExpose({
-  open
+onMounted(() => {
+  const id = route.query.id;
+  if (id) {
+    loadDetail(id as string);
+  } else {
+    ElMessage.error('缺少对账单ID');
+    handleBack();
+  }
 });
 </script>
 
-<style scoped>
+<style scoped lang="scss">
+.page-container {
+  padding: 20px;
+  background: #fff;
+  min-height: 100%;
+  flex: 1;
+}
+
+.page-header {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  margin-bottom: 25px;
+
+  .page-title {
+    font-size: 16px;
+    font-weight: bold;
+    color: #333;
+  }
+}
+
 .margin-bottom {
   margin-bottom: 20px;
 }
-.dialog-footer {
-  text-align: right;
-}
 </style>

+ 125 - 12
src/views/reconciliation/billManage/index.vue

@@ -2,7 +2,17 @@
   <div class="page-container">
     <PageTitle title="对账单管理" />
     <SearchBar :form="searchForm" :filters="filters" placeholder="对账编号" />
-    <el-table v-loading="loading" :data="tableData" border style="width: 100%">
+    <el-table
+      v-loading="loading"
+      :data="tableData"
+      border
+      style="width: 100%"
+      @selection-change="handleSelectionChange"
+      @select="handleSelect"
+      @select-all="handleSelectAll"
+      ref="tableRef"
+    >
+      <el-table-column type="selection" width="55" align="center" />
       <el-table-column prop="billNo" label="对账编号" min-width="130" align="center" />
       <el-table-column prop="billDate" label="对账日期" min-width="110" align="center">
         <template #default="{ row }">{{ parseTime(row.billDate, '{y}-{m}-{d}') }}</template>
@@ -30,29 +40,36 @@
       <el-table-column label="操作" width="100" align="center">
         <template #default="{ row }">
           <el-button type="primary" link size="small" @click="handleView(row)">查看</el-button>
-          <el-button type="danger" link size="small" @click="handleConfirm(row)" :disabled="row.billStatus !== '1'">确认</el-button>
+          <el-button type="danger" link size="small" @click="handleConfirm(row)" v-if="row.billStatus == '1'">确认</el-button>
         </template>
       </el-table-column>
     </el-table>
-
+    <div class="bottom-bar">
+      <div class="summary">
+        <span
+          >已选择 <em>{{ selectedCount }}</em> 个对账单</span
+        >
+        <span class="total"
+          >合计金额 <em>¥{{ totalAmount.toFixed(2) }}</em></span
+        >
+        <el-button type="primary" :disabled="selectedCount === 0" @click="handleApplyInvoice">申请开票</el-button>
+      </div>
+    </div>
     <div class="pagination-wrapper">
       <TablePagination v-model:page="pagination.page" v-model:pageSize="pagination.pageSize" :total="pagination.total" />
     </div>
-
-    <!-- 详情弹窗 -->
-    <DetailDialog ref="detailDialogRef" />
   </div>
 </template>
 
 <script setup lang="ts">
 import { reactive, ref, onMounted, watch } from 'vue';
+import { useRouter } from 'vue-router';
 import { ElMessage, ElMessageBox } from 'element-plus';
 import { PageTitle, SearchBar, TablePagination } from '@/components';
-import { getStatementList, getStatementInfo, confirmStatement } from '@/api/pc/enterprise/statement';
+import { getStatementList, getStatementInfo, confirmStatement, applyForInvoice } from '@/api/pc/enterprise/statement';
 import type { StatementOrder } from '@/api/pc/enterprise/statementTypes';
-import DetailDialog from './detail.vue';
 
-const detailDialogRef = ref<InstanceType<typeof DetailDialog>>();
+const router = useRouter();
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const { invoice_issuance_status, statement_status, payment_status } = toRefs<any>(
@@ -85,7 +102,12 @@ const filters = ref([
 const pagination = reactive({ page: 1, pageSize: 5, total: 0 });
 const tableData = ref<any[]>([]);
 const loading = ref(false);
+const selectedRows = ref<any[]>([]);
+const tableRef = ref();
 
+const form = reactive({
+  statementOrderIds: []
+});
 // 加载对账单列表
 const loadStatementList = async () => {
   try {
@@ -119,6 +141,44 @@ const loadStatementList = async () => {
   }
 };
 
+// 处理单个选择
+const handleSelect = (selection: any[], row: any) => {
+  // 判断是选中还是取消选中
+  const isSelected = selection.some((item) => item.id === row.id);
+  if (isSelected && row.billStatus !== '2') {
+    ElMessage.warning('该账单未对账,不可申请开票');
+    // 取消选中状态
+    tableRef.value?.toggleRowSelection(row, false);
+    return;
+  }
+};
+
+// 处理全选
+const handleSelectAll = (selection: any[]) => {
+  // 如果有选中的行,过滤掉 billStatus !== '2' 的行
+  const invalidRows = selection.filter((row) => row.billStatus !== '2');
+  if (invalidRows.length > 0) {
+    ElMessage.warning('部分账单未对账,已自动过滤');
+    // 取消这些行的选中状态
+    invalidRows.forEach((row) => {
+      tableRef.value?.toggleRowSelection(row, false);
+    });
+  }
+};
+
+// 处理选择变化
+const handleSelectionChange = (selection: any[]) => {
+  selectedRows.value = selection.filter((row) => row.billStatus === '2');
+};
+
+// 计算选中数量
+const selectedCount = computed(() => selectedRows.value.length);
+
+// 计算总金额
+const totalAmount = computed(() => {
+  return selectedRows.value.reduce((sum, item) => sum + item.amount, 0);
+});
+
 // 监听分页变化
 watch(
   () => [pagination.page, pagination.pageSize],
@@ -150,9 +210,7 @@ onMounted(() => {
 });
 
 const handleView = (row: any) => {
-  if (detailDialogRef.value) {
-    detailDialogRef.value.open(row.id);
-  }
+  router.push(`/reconciliation/billManage/detail?id=${row.id}`);
 };
 
 const handleConfirm = async (row: any) => {
@@ -174,6 +232,32 @@ const handleConfirm = async (row: any) => {
     }
   }
 };
+
+const handleApplyInvoice = async () => {
+  try {
+    form.statementOrderIds = selectedRows.value.map((row) => row.id);
+    if (!form.statementOrderIds) {
+      ElMessage.warning('请选择要申请的对账单');
+      return;
+    }
+    const billNos = selectedRows.value.map((row) => row.billNo).join('、');
+    await ElMessageBox.confirm(`确定要为以下对账单申请开票吗?\n${billNos}`, '提示', {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning'
+    });
+
+    // TODO: 调用申请开票接口
+    await applyForInvoice(form);
+    ElMessage.success('申请开票成功');
+    loadStatementList();
+  } catch (error) {
+    if (error !== 'cancel') {
+      console.error('申请开票失败:', error);
+      ElMessage.error('申请开票失败');
+    }
+  }
+};
 </script>
 
 <style scoped>
@@ -182,4 +266,33 @@ const handleConfirm = async (row: any) => {
   display: flex;
   justify-content: flex-end;
 }
+.bottom-bar {
+  margin-top: 16px;
+  padding: 16px;
+  background: #fafafa;
+  border: 1px solid #eee;
+  border-radius: 4px;
+  display: flex;
+  justify-content: flex-end;
+}
+.summary {
+  display: flex;
+  align-items: center;
+  gap: 20px;
+}
+
+.summary span {
+  font-size: 14px;
+  color: #666;
+}
+
+.summary em {
+  color: #e60012;
+  font-style: normal;
+  font-weight: bold;
+}
+
+.summary .total em {
+  font-size: 16px;
+}
 </style>

+ 297 - 0
src/views/reconciliation/invoiceManage/detail.vue

@@ -0,0 +1,297 @@
+<template>
+  <div class="page-container">
+    <div class="page-header">
+      <el-button type="primary" link @click="handleBack">
+        <el-icon><ArrowLeft /></el-icon>返回
+      </el-button>
+      <span class="page-title">销售发票详情</span>
+    </div>
+
+    <div v-loading="loading" class="drawer-content">
+      <el-form ref="formRef" :model="form" label-width="120px">
+        <!-- 基本信息 -->
+        <el-divider content-position="left">
+          <span style="color: #409eff; font-weight: 600">基本信息</span>
+        </el-divider>
+
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <el-form-item label="开票单号:" prop="statementInvoiceNo">
+              <el-input v-model="form.statementInvoiceNo" disabled />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="开票状态:" prop="invoiceStatus">
+              {{ getDictLabel(invoice_status, form.invoiceStatus) }}
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="客户名称:" prop="customerName">
+              <el-input v-model="form.customerName" disabled />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <el-form-item label="开票金额:" prop="invoiceAmount">
+              <el-input v-model="form.invoiceAmount" disabled />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="开票日期:" prop="invoiceTime">
+              <el-date-picker
+                v-model="form.invoiceTime"
+                type="date"
+                placeholder="请选择开票日期"
+                style="width: 100%"
+                value-format="YYYY-MM-DD"
+                disabled
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 账单列表 -->
+        <el-divider content-position="left">
+          <span style="color: #409eff; font-weight: 600">账单列表</span>
+        </el-divider>
+
+        <el-table :data="form.detailList" border style="width: 100%; margin-bottom: 20px">
+          <el-table-column type="index" label="序号" width="60" align="center" />
+          <el-table-column prop="statementOrderNo" label="对账单编号" min-width="150" align="center" />
+          <el-table-column prop="statementAmount" label="对账金额" min-width="120" align="center">
+            <template #default="{ row }">
+              {{ row.statementAmount != null ? Number(row.statementAmount).toFixed(2) : '-' }}
+            </template>
+          </el-table-column>
+          <el-table-column prop="orderNo" label="订单编号" min-width="150" align="center" />
+          <el-table-column prop="orderAmount" label="金额" min-width="120" align="center">
+            <template #default="{ row }">
+              {{ row.orderAmount != null ? Number(row.orderAmount).toFixed(2) : '-' }}
+            </template>
+          </el-table-column>
+          <el-table-column prop="orderTime" label="下单日期" min-width="120" align="center" />
+        </el-table>
+
+        <!-- 商品清单 -->
+        <el-divider content-position="left">
+          <span style="color: #409eff; font-weight: 600">商品清单</span>
+        </el-divider>
+
+        <el-table :data="pagedProductList" border style="width: 100%; margin-bottom: 20px">
+          <el-table-column
+            type="index"
+            label="序号"
+            width="60"
+            align="center"
+            :index="(index) => (productPage.pageNum - 1) * productPage.pageSize + index + 1"
+          />
+          <el-table-column prop="orderNo" label="订单编号" min-width="120" align="center" />
+          <el-table-column prop="productNo" label="商品编号" min-width="120" align="center" />
+          <el-table-column prop="itemName" label="商品名称" min-width="180" align="center" show-overflow-tooltip />
+          <el-table-column prop="unitName" label="单位" align="center" />
+          <el-table-column prop="quantity" label="数量" align="center" />
+          <el-table-column prop="unitPrice" label="单价" align="center">
+            <template #default="scope">
+              {{ scope.row.unitPrice ? Number(scope.row.unitPrice).toFixed(2) : '0.00' }}
+            </template>
+          </el-table-column>
+          <el-table-column label="小计" align="center">
+            <template #default="scope">
+              {{ (Number(scope.row.quantity || 0) * Number(scope.row.unitPrice || 0)).toFixed(2) }}
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <!-- 商品清单分页 -->
+        <div class="pagination-wrapper">
+          <el-pagination
+            v-model:current-page="productPage.pageNum"
+            v-model:page-size="productPage.pageSize"
+            :page-sizes="[10, 20, 50]"
+            :total="form.productList?.length || 0"
+            layout="prev, pager, next, sizes, jumper"
+          />
+        </div>
+
+        <!-- 发票附件 -->
+        <div v-if="form.invoiceList && form.invoiceList.length > 0">
+          <el-divider content-position="left">
+            <span style="color: #409eff; font-weight: 600">发票信息</span>
+          </el-divider>
+          <el-table :data="form.invoiceList" border style="width: 100%; margin-bottom: 20px">
+            <el-table-column type="index" label="序号" width="60" align="center" />
+            <el-table-column prop="invoiceAmount" label="发票金额" min-width="120" align="center">
+              <template #default="{ row }">
+                {{ row.invoiceAmount != null ? Number(row.invoiceAmount).toFixed(2) : '-' }}
+              </template>
+            </el-table-column>
+            <el-table-column prop="invoiceDate" label="发票日期" min-width="150" align="center" />
+            <el-table-column prop="invoiceType" label="发票类型" min-width="120" align="center">
+              <template #default="{ row }">
+                {{ getDictLabel(invoice_type, row.invoiceType) }}
+              </template>
+            </el-table-column>
+            <el-table-column label="操作" width="150" align="center" fixed="right">
+              <template #default="{ row }">
+                <el-button type="primary" link @click="handlePreviewInvoice(row)" v-if="row.invoiceAnnex">预览</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+
+        <!-- 发票附件 -->
+        <div v-if="form.annexAddress">
+          <el-divider content-position="left">
+            <span style="color: #409eff; font-weight: 600">发票附件</span>
+          </el-divider>
+          <el-table :data="attachmentList" border style="width: 100%">
+            <el-table-column type="index" label="序号" width="60" align="center" />
+            <el-table-column prop="name" label="文件名称" min-width="200" align="center" />
+            <el-table-column label="操作" width="150" align="center">
+              <template #default="{ row }">
+                <el-button type="primary" link @click="handlePreview(row.url)">预览</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+      </el-form>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, getCurrentInstance, toRefs, onMounted } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import { ArrowLeft } from '@element-plus/icons-vue';
+import { ElMessage } from 'element-plus';
+import { getStatementInvoice } from '@/api/pc/enterprise/statement';
+
+const { proxy } = getCurrentInstance() as any;
+const { invoice_status, invoice_type } = toRefs<any>(proxy?.useDict('invoice_status', 'invoice_type'));
+
+const route = useRoute();
+const router = useRouter();
+const loading = ref(false);
+const form = ref<Record<string, any>>({
+  detailList: [],
+  productList: []
+});
+
+// 商品清单分页
+const productPage = ref({
+  pageNum: 1,
+  pageSize: 10
+});
+
+// 分页后的商品列表
+const pagedProductList = computed(() => {
+  const list = form.value.productList || [];
+  const start = (productPage.value.pageNum - 1) * productPage.value.pageSize;
+  const end = start + productPage.value.pageSize;
+  return list.slice(start, end);
+});
+
+// 附件列表
+const attachmentList = computed(() => {
+  if (!form.value.annexAddress) return [];
+  const urls = form.value.annexAddress.split(',').filter(Boolean);
+  return urls.map((url: string, index: number) => {
+    const fileName = url.split('/').pop() || `附件${index + 1}`;
+    return {
+      name: decodeURIComponent(fileName),
+      url: url.trim()
+    };
+  });
+});
+
+const getDictLabel = (dictOptions: any[], value: string) => {
+  if (!dictOptions || !value) return value;
+  const dict = dictOptions.find((item) => item.value === value);
+  return dict ? dict.label : value;
+};
+
+// 预览附件
+const handlePreview = (url: string) => {
+  if (!url) {
+    ElMessage.warning('文件地址不存在');
+    return;
+  }
+  window.open(url, '_blank');
+};
+
+// 预览发票附件
+const handlePreviewInvoice = (row: any) => {
+  if (!row.invoiceAnnex) {
+    ElMessage.warning('发票附件不存在');
+    return;
+  }
+  window.open(row.invoiceAnnex, '_blank');
+};
+
+const handleBack = () => {
+  router.back();
+};
+
+const loadDetail = async (id: number | string) => {
+  loading.value = true;
+  try {
+    const res = await getStatementInvoice(id);
+    if (res.code === 200) {
+      form.value = res.data || {};
+      // 确保数组存在
+      if (!form.value.detailList) form.value.detailList = [];
+      if (!form.value.productList) form.value.productList = [];
+    } else {
+      ElMessage.error(res.msg || '获取详情失败');
+    }
+  } catch (error) {
+    console.error('获取销售发票详情失败:', error);
+    ElMessage.error('获取销售发票详情失败');
+  } finally {
+    loading.value = false;
+  }
+};
+
+onMounted(() => {
+  const id = route.query.id;
+  if (id) {
+    loadDetail(id as string);
+  } else {
+    ElMessage.error('缺少发票ID');
+    handleBack();
+  }
+});
+</script>
+
+<style scoped lang="scss">
+.page-container {
+  padding: 20px;
+  background: #fff;
+  min-height: 100%;
+  flex: 1;
+}
+
+.page-header {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  margin-bottom: 25px;
+
+  .page-title {
+    font-size: 16px;
+    font-weight: bold;
+    color: #333;
+  }
+}
+
+.drawer-content {
+  padding: 0 10px;
+}
+
+.pagination-wrapper {
+  margin-top: 16px;
+  display: flex;
+  justify-content: flex-end;
+}
+</style>

+ 32 - 104
src/views/reconciliation/invoiceManage/index.vue

@@ -2,39 +2,30 @@
   <div class="page-container">
     <PageTitle title="开票管理" />
 
-    <SearchBar :form="searchForm" :filters="filters" placeholder="对账编号" />
+    <SearchBar :form="searchForm" :filters="filters" placeholder="开票编号" />
 
-    <el-table v-loading="loading" :data="tableData" border style="width: 100%" @selection-change="handleSelectionChange">
-      <el-table-column type="selection" width="55" align="center" />
-      <el-table-column prop="billNo" label="对账编号" min-width="130" align="center" />
-      <el-table-column prop="billDate" label="对账日期" min-width="110" align="center" />
-      <el-table-column prop="amount" label="对账单金额" min-width="110" align="center">
+    <el-table v-loading="loading" :data="tableData" border style="width: 100%">
+      <el-table-column prop="statementInvoiceNo" label="开票编号" min-width="130" align="center" />
+      <el-table-column prop="invoiceTime" label="开票时间" min-width="110" align="center">
+        <template #default="scope">
+          <span>{{ parseTime(scope.row.invoiceTime, '{y}-{m}-{d}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column prop="customerName" label="客户名称" min-width="110" align="center" />
+      <el-table-column prop="amount" label="开票金额" min-width="110" align="center">
         <template #default="{ row }">¥{{ row.amount.toFixed(2) }}</template>
       </el-table-column>
-      <el-table-column prop="billStatus" label="对账状态" min-width="90" align="center">
+      <el-table-column prop="invoiceStatus" label="开票状态" min-width="90" align="center">
         <template #default="{ row }">
-          {{ getDictLabel(statement_status, row.billStatus) }}
+          {{ getDictLabel(invoice_status, row.invoiceStatus) }}
         </template>
       </el-table-column>
       <el-table-column label="操作" width="100" align="center">
-        <!-- <template #default="{ row }">
+        <template #default="{ row }">
           <el-button type="primary" link size="small" @click="handleView(row)">查看</el-button>
-        </template> -->
+        </template>
       </el-table-column>
     </el-table>
-
-    <div class="bottom-bar">
-      <div class="summary">
-        <span
-          >已选择 <em>{{ selectedCount }}</em> 个对账单</span
-        >
-        <span class="total"
-          >合计金额 <em>¥{{ totalAmount.toFixed(2) }}</em></span
-        >
-        <el-button type="primary" :disabled="selectedCount === 0" @click="handleApplyInvoice">申请开票</el-button>
-      </div>
-    </div>
-
     <div class="pagination-wrapper">
       <TablePagination v-model:page="pagination.page" v-model:pageSize="pagination.pageSize" :total="pagination.total" />
     </div>
@@ -44,14 +35,11 @@
 <script setup lang="ts">
 import { reactive, ref, computed, onMounted, watch } from 'vue';
 import { ElMessage, ElMessageBox } from 'element-plus';
+import { useRouter } from 'vue-router';
 import { PageTitle, SearchBar, TablePagination } from '@/components';
-import { getStatementList, applyForInvoice } from '@/api/pc/enterprise/statement';
-import type { StatementOrder } from '@/api/pc/enterprise/statementTypes';
-import { get } from 'http';
+import { getStatementInvoiceList } from '@/api/pc/enterprise/statement';
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
-const { invoice_issuance_status, statement_status, payment_status } = toRefs<any>(
-  proxy?.useDict('invoice_issuance_status', 'statement_status', 'payment_status')
-);
+const { invoice_status } = toRefs<any>(proxy?.useDict('invoice_issuance_status', 'statement_status', 'invoice_status'));
 const searchForm = reactive({
   keyword: '',
   dateRange: [],
@@ -68,28 +56,26 @@ const filters = ref([{ field: 'invoiceStatus', label: '开票状态', options: i
 const pagination = reactive({ page: 1, pageSize: 5, total: 0 });
 const tableData = ref<any[]>([]);
 const loading = ref(false);
-const selectedRows = ref<any[]>([]);
-
+const router = useRouter();
 // 加载对账单列表
-const loadStatementList = async () => {
+const loadStatementInvoice = async () => {
   try {
     loading.value = true;
-    const res = await getStatementList({
+    const res = await getStatementInvoiceList({
       pageNum: pagination.page,
       pageSize: pagination.pageSize,
-      statementOrderNo: searchForm.keyword,
-      isInvoiceStatus: searchForm.invoiceStatus,
-      statementStatus: '2'
+      statementInvoiceNo: searchForm.keyword,
+      isInvoiceStatus: searchForm.invoiceStatus
     });
 
     if (res.code === 200 && res.rows) {
-      tableData.value = res.rows.map((item: StatementOrder) => ({
+      tableData.value = res.rows.map((item: any) => ({
         id: item.id,
-        billNo: item.statementOrderNo,
-        billDate: item.statementDate,
-        amount: parseFloat(item.amount as any) || 0,
-        billStatus: item.statementStatus,
-        invoiceStatus: item.isInvoiceStatus,
+        statementInvoiceNo: item.statementInvoiceNo,
+        invoiceTime: item.invoiceTime,
+        customerName: item.customerName,
+        amount: parseFloat(item.invoiceAmount as any) || 0,
+        invoiceStatus: item.invoiceStatus,
         payStatus: item.isPaymentStatus
       }));
       pagination.total = res.total || 0;
@@ -112,7 +98,7 @@ const getDictLabel = (dictOptions: any[], value: string) => {
 watch(
   () => [pagination.page, pagination.pageSize],
   () => {
-    loadStatementList();
+    loadStatementInvoice();
   }
 );
 
@@ -121,76 +107,18 @@ watch(
   () => [searchForm.keyword, searchForm.invoiceStatus],
   () => {
     pagination.page = 1;
-    loadStatementList();
+    loadStatementInvoice();
   }
 );
 
-// // 加载字典数据
-// const loadDictData = async () => {
-//   try {
-//     const res = await getDictByType('invoice_issuance_status');
-//     if (res.data) {
-//       invoiceStatusOptions.value = [
-//         { label: '全部', value: '' },
-//         ...res.data.map((item) => ({
-//           label: item.dictLabel,
-//           value: item.dictValue
-//         }))
-//       ];
-//       filters.value[0].options = invoiceStatusOptions.value;
-//     }
-//   } catch (error) {
-//     console.error('加载字典数据失败:', error);
-//   }
-// };
-
 // 页面加载时获取数据
 onMounted(() => {
   // loadDictData();
-  loadStatementList();
-});
-
-// 处理选择变化
-const handleSelectionChange = (selection: any[]) => {
-  selectedRows.value = selection;
-};
-
-// 计算选中数量
-const selectedCount = computed(() => selectedRows.value.length);
-
-// 计算总金额
-const totalAmount = computed(() => {
-  return selectedRows.value.reduce((sum, item) => sum + item.amount, 0);
+  loadStatementInvoice();
 });
 
 const handleView = (row: any) => {
-  ElMessage.info(`查看对账单:${row.billNo}`);
-};
-
-const handleApplyInvoice = async () => {
-  try {
-    form.statementOrderIds = selectedRows.value.map((row) => row.id);
-    if (!form.statementOrderIds) {
-      ElMessage.warning('请选择要申请的对账单');
-      return;
-    }
-    const billNos = selectedRows.value.map((row) => row.billNo).join('、');
-    await ElMessageBox.confirm(`确定要为以下对账单申请开票吗?\n${billNos}`, '提示', {
-      confirmButtonText: '确定',
-      cancelButtonText: '取消',
-      type: 'warning'
-    });
-
-    // TODO: 调用申请开票接口
-    await applyForInvoice(form);
-    ElMessage.success('申请开票成功');
-    loadStatementList();
-  } catch (error) {
-    if (error !== 'cancel') {
-      console.error('申请开票失败:', error);
-      ElMessage.error('申请开票失败');
-    }
-  }
+  router.push(`/reconciliation/invoiceManage/detail?id=${row.id}`);
 };
 </script>