已修改11个文件
已添加3个文件
884 ■■■■ 文件已修改
src/api/basicData/customerFile.js 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/salesManagement/salesLedger.js 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/user.js 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/element-ui.scss 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/ruoyi.scss 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/variables.module.scss 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/PIMTable/PIMTable.vue 149 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/PIMTable/Pagination.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main.js 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/settings.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/customerFile/index.vue 286 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/login.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salesLedger/index.vue 305 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/build/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/basicData/customerFile.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,52 @@
// å®¢æˆ·æ¡£æ¡ˆé¡µé¢æŽ¥å£
import request from '@/utils/request'
// åˆ†é¡µæŸ¥è¯¢
export function listCustomer(query) {
    return request({
        url: '/basic/customer/list',
        method: 'get',
        params: query
    })
}
// æŸ¥è¯¢å®¢æˆ·æ¡£æ¡ˆè¯¦ç»†
export function getCustomer(id) {
    return request({
        url: '/basic/customer/' + id,
        method: 'get'
    })
}
// æ–°å¢žå®¢æˆ·æ¡£æ¡ˆ
export function addCustomer(data) {
    return request({
        url: '/basic/customer/addCustomer',
        method: 'post',
        data: data
    })
}
// ä¿®æ”¹å®¢æˆ·æ¡£æ¡ˆ
export function updateCustomer(data) {
    return request({
        url: '/basic/customer/updateCustomer',
        method: 'post',
        data: data
    })
}
// å¯¼å‡ºå®¢æˆ·æ¡£æ¡ˆ
export function exportCustomer(query) {
    return request({
        url: '/basic/customer/export',
        method: 'get',
        params: query,
        responseType: 'blob'
    })
}
// åˆ é™¤å®¢æˆ·æ¡£æ¡ˆ
export function delCustomer(ids) {
    return request({
        url: '/basic/customer/delCustomer',
        method: 'delete',
        data: ids
    })
}
src/api/salesManagement/salesLedger.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
// é”€å”®å°è´¦é¡µé¢æŽ¥å£
import request from '@/utils/request'
// åˆ†é¡µæŸ¥è¯¢
export function ledgerList(query) {
    return request({
        url: '/sales/ledger/list',
        method: 'get',
        params: query
    })
}
src/api/system/user.js
@@ -134,3 +134,10 @@
    method: 'get'
  })
}
// æŸ¥è¯¢ç”¨æˆ·åˆ—表
export function userListNoPage() {
  return request({
    url: '/system/user/userListNoPage',
    method: 'get'
  })
}
src/assets/styles/element-ui.scss
@@ -52,6 +52,62 @@
  left: 0;
  position: relative;
  margin: 0 auto;
  border-radius: 8px;
  padding: 0 !important;
}
.el-dialog__header {
  background: #F5F6F7;
  padding: 12px 16px;
  border-radius: 8px 8px 0 0;
}
.el-dialog__title {
  font-weight: 400;
  font-size: 16px;
  color: #2E3033;
}
.el-dialog__body {
  padding: 16px 40px 0 40px;
}
.el-dialog__footer {
  text-align: center;
  padding: 16px;
}
.el-message-box {
  padding: 0 !important;
  border-radius: 8px;
}
.el-message-box__header {
  background: #F5F6F7;
  padding: 12px 16px;
  border-radius: 8px 8px 0 0;
}
.el-message-box__title {
  font-weight: 400;
  font-size: 16px;
  color: #2E3033;
}
.el-message-box__content {
  padding: 16px 40px 0 40px;
}
.el-message-box__container {
  justify-content: center;
}
.el-message-box__btns {
  text-align: center;
  padding: 16px;
  display: flex;
  flex-direction: row-reverse;
  justify-content: center;
  align-items: center;
  .el-button--primary {
    margin-right: 12px;
  }
}
.el-table__expanded-cell {
  padding: 0 !important;
  .el-table__header-wrapper {
    background-color: #F5F8FF !important;
  }
}
// refine element ui upload
src/assets/styles/ruoyi.scss
@@ -71,14 +71,13 @@
  overflow: auto;
  overflow-x: hidden;
  max-height: 70vh;
  padding: 10px 20px 0;
}
.el-table {
  .el-table__header-wrapper, .el-table__fixed-header-wrapper {
    th {
      word-break: break-word;
      background-color: #f8f8f9 !important;
      background-color: #F0F1F5 !important;
      color: #515a6e;
      height: 40px !important;
      font-size: 13px;
@@ -151,7 +150,7 @@
/** è¡¨æ ¼æ›´å¤šæ“ä½œä¸‹æ‹‰æ ·å¼ */
.el-table .el-dropdown-link {
  cursor: pointer;
  color: #3472D7;
  color: #2C51D9;
  margin-left: 10px;
}
src/assets/styles/variables.module.scss
@@ -182,7 +182,7 @@
    .el-table__header-wrapper, .el-table__fixed-header-wrapper {
      th {
        background-color: var(--el-bg-color-overlay, #f8f8f9) !important;
        background-color: var(--el-bg-color-overlay, #F0F1F5) !important;
        color: var(--el-text-color-regular, #515a6e);
      }
    }
src/components/PIMTable/PIMTable.vue
@@ -1,22 +1,20 @@
<template>
  <el-table ref="multipleTable" v-loading="tableLoading" :border="border" :data="tableData"
            :header-cell-style="{ background: '#F0F1F5', color: '#333333' }" :height="height"
            :highlight-current-row="highlightCurrentRow" :row-class-name="rowClassName" :row-style="rowStyle"
            :row-key="rowKey" :span-method="spanMethod" stripe style="width: 100%" tooltip-effect="dark" @row-click="rowClick"
            @current-change="currentChange" @selection-change="handleSelectionChange" class="lims-table">
    <el-table-column align="center" type="selection" width="55" v-if="isSelection" />
    <el-table-column align="center" label="序号" type="index" width="60" :index="indexMethod" />
    :header-cell-style="{ background: '#F0F1F5', color: '#333333' }" height="calc(100vh - 18.5em)"
    :highlight-current-row="highlightCurrentRow" :row-class-name="rowClassName" :row-style="rowStyle" :row-key="rowKey"
    style="width: 100%" tooltip-effect="dark" @row-click="rowClick" @current-change="currentChange"
    @selection-change="handleSelectionChange" class="lims-table">
    <el-table-column align="center" type="selection" width="55" />
    <el-table-column align="center" label="序号" type="index" width="60" />
    <el-table-column v-for="(item, index) in column" :key="index" :column-key="item.columnKey"
                     :filter-method="item.filterHandler" :filter-multiple="item.filterMultiple" :filtered-value="item.filteredValue"
                     :filters="item.filters" :fixed="item.fixed" :label="item.label" :prop="item.prop"
                     :show-overflow-tooltip="!(item.dataType === 'action' || item.dataType === 'slot')"
                     :min-width="item.dataType == 'action' ? btnWidth : item.width"
                     :sortable="!!item.sortable" :type="item.type" :width="item.dataType == 'action' ? btnWidth : item.width" align="center">
      :filter-method="item.filterHandler" :filter-multiple="item.filterMultiple" :filtered-value="item.filteredValue"
      :filters="item.filters" :fixed="item.fixed" :label="item.label" :prop="item.prop" show-overflow-tooltip
      :align="item.align" :sortable="!!item.sortable" :type="item.type" :width="item.width">
      <template v-if="item.hasOwnProperty('colunmTemplate')" #[item.colunmTemplate]="scope">
        <slot v-if="item.theadSlot" :name="item.theadSlot" :index="scope.$index" :row="scope.row" />
      </template>
      <template #default="scope">
        <!-- æ’æ§½ -->
        <div v-if="item.dataType == 'slot'">
@@ -28,57 +26,58 @@
        </div>
        <!-- å›¾ç‰‡ -->
        <div v-else-if="item.dataType == 'image'">
          <img :src="javaApi + '/img/' + scope.row[item.prop]" alt="" style="width: 40px; height: 40px; margin-top: 10px" />
          <img :src="javaApi + '/img/' + scope.row[item.prop]" alt=""
            style="width: 40px; height: 40px; margin-top: 10px" />
        </div>
        <!-- tag -->
        <div v-else-if="item.dataType == 'tag'">
          <el-tag v-if="typeof dataTypeFn(scope.row[item.prop], item.formatData) === 'string'"
                  :title="formatters(scope.row[item.prop], item.formatData)"
                  :type="formatType(scope.row[item.prop], item.formatType)">
            :title="formatters(scope.row[item.prop], item.formatData)"
            :type="formatType(scope.row[item.prop], item.formatType)">
            {{ formatters(scope.row[item.prop], item.formatData) }}
          </el-tag>
          <el-tag v-for="(tag, index) in dataTypeFn(scope.row[item.prop], item.formatData)"
                  v-else-if="typeof dataTypeFn(scope.row[item.prop], item.formatData) === 'object'"
                  :key="index" :title="formatters(scope.row[item.prop], item.formatData)" :type="formatType(tag, item.formatType)">
            v-else-if="typeof dataTypeFn(scope.row[item.prop], item.formatData) === 'object'" :key="index"
            :title="formatters(scope.row[item.prop], item.formatData)" :type="formatType(tag, item.formatType)">
            {{ item.tagGroup ? tag[item.tagGroup.label] ?? tag : tag }}
          </el-tag>
          <el-tag v-else :title="formatters(scope.row[item.prop], item.formatData)" :type="formatType(scope.row[item.prop], item.formatType)">
          <el-tag v-else :title="formatters(scope.row[item.prop], item.formatData)"
            :type="formatType(scope.row[item.prop], item.formatType)">
            {{ formatters(scope.row[item.prop], item.formatData) }}
          </el-tag>
        </div>
        <!-- æŒ‰é’® -->
        <div v-else-if="item.dataType == 'action'"
             :style="`min-width:${getWidth(item.operation, scope.row)}`">
        <div v-else-if="item.dataType == 'action'">
          <template v-for="(o, key) in item.operation" :key="key">
            <el-button v-show="o.type != 'upload'" size="small" v-if="o.showHide ? o.showHide(scope.row) : true"
                       :disabled="o.disabled ? o.disabled(scope.row) : false" :plain="o.plain"
                       :style="{ color: (o.name === '删除' || o.name === 'delete') ? '#f56c6c' : o.color }" :type="typeFn(o.type, scope.row)"
                       @click="o.clickFun(scope.row)" :key="key">
              :disabled="o.disabled ? o.disabled(scope.row) : false" :plain="o.plain" type="primary"
              :style="{ color: (o.name === '删除' || o.name === 'delete') ? '#f56c6c' : o.color }" link
              @click="o.clickFun(scope.row)" :key="key">
              {{ o.name }}
            </el-button>
            <el-upload :action="javaApi + o.url + '?id=' + (o.uploadIdFun ? o.uploadIdFun(scope.row) : scope.row.id)"
                       ref="uploadRef" size="small" :multiple="o.multiple ? o.multiple : false" :limit="1"
                       :disabled="o.disabled ? o.disabled(scope.row) : false"
                       :accept="o.accept ? o.accept : '.jpg,.jpeg,.png,.gif,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.zip,.rar'"
                       v-if="o.type == 'upload'" style="display: inline-block; width: 50px"
                       v-show="o.showHide ? o.showHide(scope.row) : true" :headers="uploadHeader"
                       :before-upload="(file) => beforeUpload(file, scope.$index)"
                       :on-change="(file, fileList) => handleChange(file, fileList, scope.$index)"
                       :on-error="(error, file, fileList) => onError(error, file, fileList, scope.$index)"
                       :on-success="(response, file, fileList) => handleSuccessUp(response, file, fileList, scope.$index)"
                       :on-exceed="onExceed" :show-file-list="false">
              <el-button :size="o.size ? o.size : 'small'" type="text"
                         :disabled="o.disabled ? o.disabled(scope.row) : false">{{ o.name }}</el-button>
              ref="uploadRef" size="small" :multiple="o.multiple ? o.multiple : false" :limit="1"
              :disabled="o.disabled ? o.disabled(scope.row) : false"
              :accept="o.accept ? o.accept : '.jpg,.jpeg,.png,.gif,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.zip,.rar'"
              v-if="o.type == 'upload'" style="display: inline-block; width: 50px"
              v-show="o.showHide ? o.showHide(scope.row) : true" :headers="uploadHeader"
              :before-upload="(file) => beforeUpload(file, scope.$index)"
              :on-change="(file, fileList) => handleChange(file, fileList, scope.$index)"
              :on-error="(error, file, fileList) => onError(error, file, fileList, scope.$index)"
              :on-success="(response, file, fileList) => handleSuccessUp(response, file, fileList, scope.$index)"
              :on-exceed="onExceed" :show-file-list="false">
              <el-button :size="o.size ? o.size : 'small'" link type="primary"
                :disabled="o.disabled ? o.disabled(scope.row) : false">{{ o.name }}</el-button>
            </el-upload>
          </template>
        </div>
        <!-- å¯ç‚¹å‡»çš„æ–‡å­— -->
        <div v-else-if="item.dataType == 'link'" class="cell link" style="width: 100%"
             @click="goLink(scope.row, item.linkMethod)">
          @click="goLink(scope.row, item.linkMethod)">
          <span v-if="!item.formatData">{{ scope.row[item.prop] }}</span>
        </div>
        <!-- é»˜è®¤çº¯å±•示数据 -->
@@ -89,8 +88,8 @@
      </template>
    </el-table-column>
  </el-table>
  <pagination v-show="page.total > 0" :total="page.total" :layout="page.layout" :page="page.current"
              :limit="page.size" @pagination="paginationSearch" />
  <pagination v-show="total > 0" :total="total" :layout="page.layout" :page="page.current" :limit="page.size"
    @pagination="paginationSearch" />
</template>
<script setup>
@@ -103,6 +102,8 @@
const uploadHeader = proxy.uploadHeader
const javaApi = proxy.javaApi
const emit = defineEmits(["pagination"]);
// Filters
const typeFn = (val, row) => {
  return typeof val === 'function' ? val(row) : val
@@ -114,29 +115,21 @@
// Props(使用 defineProps çš„非 TS å½¢å¼ï¼‰
const props = defineProps({
  isSelection: {
    type: Boolean,
    default: false
  },
  height: {
    type: [String, null],
    default: null
  },
  tableLoading: {
    type: Boolean,
    default: false
  },
  handleSelectionChange: {
    type: Function,
    default: () => {}
    default: () => { }
  },
  rowClick: {
    type: Function,
    default: () => {}
    default: () => { }
  },
  currentChange: {
    type: Function,
    default: () => {}
    default: () => { }
  },
  border: {
    type: Boolean,
@@ -178,50 +171,21 @@
      size: 10,
      layout: 'total, sizes, prev, pager, next, jumper'
    })
  },
  total: {
    type: Number,
    default: 0
  }
})
// Data
const spanList = ref([])
const btnWidth = ref('120px')
const uploadRefs = ref([])
const currentFiles = ref({})
const uploadKeys = ref({})
// åˆå¹¶å•元格方法
const spanMethod = ({ row, column, rowIndex, columnIndex }) => {
  if (column.find((m) => m.mergeCol)) {
    let i = Number(rowIndex)
    const obj = spanList.value.find((item, index) => {
      i = index
      return item.index == columnIndex
    })
    if (obj) {
      const _row = spanList[i].arr[rowIndex]
      const _col = _row > 0 ? 1 : 0
      return {
        rowspan: _row,
        colspan: _col
      }
    }
  }
}
const indexMethod = (index) => {
  return (props.page.current - 1) * props.page.size + index + 1
}
const getWidth = (row, row0) => {
  let count = 0
  row.forEach((a) => {
    if (a.showHide !== undefined && a.showHide(row0)) {
      count += a.name.length
    } else if (!a.showHide) {
      count += a.name.length
    }
  })
  btnWidth.value = count * 15 + 60 + "px"
  return count * 15 + 60 + "px"
}
// ç‚¹å‡» link äº‹ä»¶
@@ -311,4 +275,15 @@
</script>
<style scoped lang="scss">
:deep(.cell) {
  padding: 0 !important;
}
.cell {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  padding-right: 0 !important;
  padding-left: 0 !important;
}
</style>
src/components/PIMTable/Pagination.vue
@@ -91,8 +91,8 @@
<style scoped>
.pagination-container {
  background: #fff;
  padding: 28px 16px;
  margin-top: 10px;
  padding: 16px 0;
  margin-top: 0;
}
.pagination-container.hidden {
  display: none;
src/main.js
@@ -90,5 +90,6 @@
  // æ”¯æŒ large、default、small
  size: Cookies.get('size') || 'default'
})
app._context.components.ElDialog.props.closeOnClickModal.default = false
app.mount('#app')
src/store/modules/settings.js
@@ -14,7 +14,7 @@
  {
    state: () => ({
      title: '',
      theme: storageSetting.theme || '#3472D7',
      theme: storageSetting.theme || '#2C51D9',
      sideTheme: storageSetting.sideTheme || sideTheme,
      showSettings: showSettings,
      topNav: storageSetting.topNav === undefined ? topNav : storageSetting.topNav,
src/views/basicData/customerFile/index.vue
@@ -4,38 +4,302 @@
    <div>
      <span class="search_title">客户名称:</span>
      <el-input
          v-model="input2"
          v-model="searchForm.customerName"
          style="width: 240px"
          placeholder="请输入"
          @change="handleQuery"
          clearable
          :prefix-icon="Search"
      />
      <el-button type="primary" @click="handleQuery" style="margin-left: 10px">搜索</el-button>
    </div>
    <div>
      <el-button type="primary">新增客户</el-button>
      <el-button>导出</el-button>
      <el-button type="danger" plain>删除</el-button>
      <el-button type="primary" @click="openForm('add')">新增客户</el-button>
      <el-button @click="handleOut">导出</el-button>
      <el-button type="danger" plain @click="handleDelete">删除</el-button>
    </div>
  </div>
  <div class="table_list">
    <PIMTable :column="tableColumn"></PIMTable>
    <PIMTable :column="tableColumn" :tableData="tableData" :page="page" :handleSelectionChange="handleSelectionChange"
              :tableLoading="tableLoading" @pagination="pagination" :total="total"></PIMTable>
  </div>
  <el-dialog v-model="dialogFormVisible" :title="operationType === 'add' ? '新增客户信息' : '编辑客户信息'" width="70%" @close="closeDia">
    <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="客户名称:" prop="customerName">
            <el-input v-model="form.customerName" placeholder="请输入" clearable/>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="纳税人识别号:" prop="taxpayerIdentificationNumber">
            <el-input v-model="form.taxpayerIdentificationNumber" placeholder="请输入" clearable/>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="公司地址:" prop="companyAddress">
            <el-input v-model="form.companyAddress" placeholder="请输入" clearable/>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="公司电话:" prop="companyPhone">
            <el-input v-model="form.companyPhone" placeholder="请输入" clearable/>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="联系人:" prop="contactPerson">
            <el-input v-model="form.contactPerson" placeholder="请输入" clearable/>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="联系电话:" prop="contactPhone">
            <el-input v-model="form.contactPhone" placeholder="请输入" clearable/>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="维护人:" prop="maintainer">
            <el-select v-model="form.maintainer" placeholder="请选择" clearable>
              <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" :value="item.nickName"/>
            </el-select>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="维护时间:" prop="maintenanceTime">
            <el-date-picker
                style="width: 100%"
                v-model="form.maintenanceTime"
                value-format="YYYY-MM-DD"
                format="YYYY-MM-DD"
                type="date"
                placeholder="请选择"
                clearable
            />
          </el-form-item>
        </el-col>
      </el-row>
    </el-form>
    <template #footer>
      <div class="dialog-footer">
        <el-button type="primary" @click="submitForm">确认</el-button>
        <el-button @click="closeDia">取消</el-button>
      </div>
    </template>
  </el-dialog>
</div>
</template>
<script setup>
import { ref } from 'vue'
import {Search} from "@element-plus/icons-vue";
import {addCustomer, delCustomer, getCustomer, listCustomer, updateCustomer} from "@/api/basicData/customerFile.js";
import {ElMessageBox } from "element-plus";
import {userListNoPage} from "@/api/system/user.js";
const { proxy } = getCurrentInstance()
const input2 = ref('')
const tableColumn = ref([
  {
    label: '批准内容',
    prop: 'ratifyRemark'
  }, {
    label: '批准人',
    prop: 'ratifyName',
    label: '客户名称',
    prop: 'customerName',
  },
  {
    label: '纳税人识别码',
    prop: 'taxpayerIdentificationNumber'
  },
  {
    label: '地址及联系方式',
    prop: 'addressPhone'
  },
  {
    label: '联系人',
    prop: 'contactPerson'
  },
  {
    label: '联系电话',
    prop: 'contactPhone',
  },
  {
    label: '维护人',
    prop: 'maintainer',
  },
  {
    label: '维护时间',
    prop: 'maintenanceTime',
  },
  {
    dataType: "action",
    label: "操作",
    align: 'center',
    operation: [
      {
        name: "编辑",
        type: "text",
        clickFun: (row) => {
          openForm('edit', row);
        },
      },
    ],
  },
])
const tableData = ref([])
const selectedRows = ref([])
const userList = ref([])
const tableLoading = ref(false)
const page = reactive({
  current: 1,
  size: 10,
})
const total = ref(0)
// ç”¨æˆ·ä¿¡æ¯è¡¨å•弹框数据
const operationType = ref('')
const dialogFormVisible = ref(false)
const data = reactive({
  searchForm: {
    customerName: '',
  },
  form: {
    customerName: '',
    taxpayerIdentificationNumber: '',
    companyAddress: '',
    companyPhone: '',
    contactPerson: '',
    contactPhone: '',
    maintainer: '',
    maintenanceTime: '',
  },
  rules: {
    customerName: [{ required: true, message: "请输入", trigger: "blur" }],
    taxpayerIdentificationNumber: [{ required: true, message: "请输入", trigger: "blur" }],
    companyAddress: [{ required: true, message: "请输入", trigger: "blur" }],
    companyPhone: [{ required: true, message: "请输入", trigger: "blur" }],
    contactPerson: [{ required: true, message: "请输入", trigger: "blur" }],
    contactPhone: [{ required: true, message: "请输入", trigger: "blur" }],
    maintainer: [{ required: false, message: "请选择", trigger: "change" }],
    maintenanceTime: [{ required: false, message: "请选择", trigger: "change" }],
  }
})
const { searchForm, form, rules } = toRefs(data)
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = 1
  getList()
}
const pagination = ({ current, limit }) => {
  page.current = current;
  page.size = limit;
  getList()
}
const getList = () => {
  tableLoading.value = true
  listCustomer({...searchForm.value, ...page}).then(res => {
    tableLoading.value = false
    tableData.value = res.rows
    total.value = res.total
  })
}
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  selectedRows.value = selection
}
// æ‰“开弹框
const openForm = (type, row) => {
  operationType.value = type
  form.value = {}
  userListNoPage().then(res => {
    userList.value = res.data
  })
  if (type === 'edit') {
    getCustomer(row.id).then(res => {
      form.value = {...res.data}
    })
  }
  dialogFormVisible.value = true
}
// æäº¤è¡¨å•
const submitForm = () => {
  proxy.$refs["formRef"].validate(valid => {
    if (valid) {
      if (operationType.value === "edit") {
        submitEdit()
      } else {
        submitAdd()
      }
    }
  })
}
// æäº¤æ–°å¢ž
const submitAdd = () => {
  addCustomer(form.value).then(res => {
    proxy.$modal.msgSuccess("提交成功")
    closeDia()
    getList()
  })
}
// æäº¤ä¿®æ”¹
const submitEdit = () => {
  updateCustomer(form.value).then(res => {
    proxy.$modal.msgSuccess("提交成功")
    closeDia()
    getList()
  })
}
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  proxy.resetForm("formRef")
  dialogFormVisible.value = false
}
// å¯¼å‡º
const handleOut = () => {
  ElMessageBox.confirm(
      '选中的内容将被导出,是否确认导出?',
      '导出', {
        confirmButtonText: '确认',
        cancelButtonText: '取消',
        type: 'warning',
      }
  ).then(() => {
    proxy.download("/basic/customer/export", {}, '客户档案.xlsx')
  }).catch(() => {
    proxy.$modal.msg("已取消")
  })
}
// åˆ é™¤
const handleDelete = () => {
  let ids = []
  if (selectedRows.value.length > 0) {
    ids = selectedRows.value.map(item => item.id);
  } else {
    proxy.$modal.msgWarning('请选择数据')
    return
  }
  ElMessageBox.confirm(
      '选中的内容将被删除,是否确认删除?',
      '删除提示', {
        confirmButtonText: '确认',
        cancelButtonText: '取消',
        type: 'warning',
      }
  ).then(() => {
    tableLoading.value = true
    delCustomer(ids).then(res => {
      proxy.$modal.msgSuccess("删除成功")
      getList()
    }).finally(() => {
      tableLoading.value = false
    })
  }).catch(() => {
    proxy.$modal.msg("已取消")
  })
}
getList()
</script>
<style scoped lang="scss">
src/views/login.vue
@@ -175,7 +175,7 @@
.title {
  margin: 20px auto 14px auto;
  text-align: center;
  color: #3472D7;
  color: #2C51D9;
  font-size: 28px;
  font-weight: 700;
}
src/views/salesManagement/salesLedger/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,305 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <div>
        <span class="search_title">客户名称:</span>
        <el-input
            v-model="searchForm.customerName"
            style="width: 240px"
            placeholder="请输入"
            @change="handleQuery"
            clearable
            :prefix-icon="Search"
        />
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px">搜索</el-button>
      </div>
      <div>
        <el-button type="primary" @click="openForm('add')">新增台账</el-button>
        <el-button @click="handleOut">导出</el-button>
        <el-button type="danger" plain @click="handleDelete">删除</el-button>
      </div>
    </div>
    <div class="table_list">
      <el-table :data="tableData" border :preserve-expanded-content="preserveExpanded" v-loading="tableLoading"
                @selection-change="handleSelectionChange" @expand-change="expandChange"
                height="calc(100vh - 18.5em)">
        <el-table-column align="center" type="selection" width="55" />
        <el-table-column type="expand">
          <template #default="props">
            <el-table :data="props.row.children" border>
              <el-table-column align="center" label="序号" type="index" width="60" />
              <el-table-column label="产品大类" prop="productCategory" />
              <el-table-column label="规格型号" prop="specificationModel" />
              <el-table-column label="单位" prop="unit" />
              <el-table-column label="数量" prop="quantity" />
              <el-table-column label="税率" prop="taxRate" />
              <el-table-column label="含税单价(元)" prop="taxInclusiveUnitPrice" />
              <el-table-column label="含税总价(元)" prop="taxInclusiveTotalPrice" />
              <el-table-column label="不含税总价(元)" prop="taxExclusiveTotalPrice" />
            </el-table>
          </template>
        </el-table-column>
        <el-table-column align="center" label="序号" type="index" width="60" />
        <el-table-column label="销售合同号" prop="salesContractNo" show-overflow-tooltip/>
        <el-table-column label="客户合同号" prop="customerContractNo" show-overflow-tooltip/>
        <el-table-column label="客户名称" prop="customerName" show-overflow-tooltip/>
        <el-table-column label="业务员" prop="salesman" show-overflow-tooltip/>
        <el-table-column label="项目名称" prop="projectName" show-overflow-tooltip/>
        <el-table-column label="录入人" prop="entryPerson" show-overflow-tooltip/>
        <el-table-column label="录入日期" prop="entryDate" show-overflow-tooltip/>
        <el-table-column fixed="right" label="操作" min-width="60" align="center">
          <template #default="scope">
            <el-button link type="primary" size="small" @click="openForm('edit', scope.row);">编辑</el-button>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" layout="total, sizes, prev, pager, next, jumper" :page="page.current"
                  :limit="page.size" @pagination="paginationChange" />
    </div>
    <el-dialog v-model="dialogFormVisible" :title="operationType === 'add' ? '新增客户信息' : '编辑客户信息'" width="70%" @close="closeDia">
      <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="客户名称:" prop="customerName">
              <el-input v-model="form.customerName" placeholder="请输入" clearable/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="纳税人识别号:" prop="taxpayerIdentificationNumber">
              <el-input v-model="form.taxpayerIdentificationNumber" placeholder="请输入" clearable/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="公司地址:" prop="companyAddress">
              <el-input v-model="form.companyAddress" placeholder="请输入" clearable/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="公司电话:" prop="companyPhone">
              <el-input v-model="form.companyPhone" placeholder="请输入" clearable/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="联系人:" prop="contactPerson">
              <el-input v-model="form.contactPerson" placeholder="请输入" clearable/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="联系电话:" prop="contactPhone">
              <el-input v-model="form.contactPhone" placeholder="请输入" clearable/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="维护人:" prop="maintainer">
              <el-select v-model="form.maintainer" placeholder="请选择" clearable>
                <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" :value="item.nickName"/>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="维护时间:" prop="maintenanceTime">
              <el-date-picker
                  style="width: 100%"
                  v-model="form.maintenanceTime"
                  value-format="YYYY-MM-DD"
                  format="YYYY-MM-DD"
                  type="date"
                  placeholder="请选择"
                  clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">确认</el-button>
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import pagination from '@/components/PIMTable/Pagination.vue'
import { ref } from 'vue'
import {Search} from "@element-plus/icons-vue";
import {addCustomer, delCustomer, getCustomer, listCustomer, updateCustomer} from "@/api/basicData/customerFile.js";
import {ElMessageBox } from "element-plus";
import {userListNoPage} from "@/api/system/user.js";
import {ledgerList} from "@/api/salesManagement/salesLedger.js";
const { proxy } = getCurrentInstance()
const preserveExpanded = ref(false)
const tableData = ref([])
const selectedRows = ref([])
const userList = ref([])
const tableLoading = ref(false)
const page = reactive({
  current: 1,
  size: 10,
})
const total = ref(0)
// ç”¨æˆ·ä¿¡æ¯è¡¨å•弹框数据
const operationType = ref('')
const dialogFormVisible = ref(false)
const data = reactive({
  searchForm: {
    customerName: '',
  },
  form: {
    customerName: '',
    taxpayerIdentificationNumber: '',
    companyAddress: '',
    companyPhone: '',
    contactPerson: '',
    contactPhone: '',
    maintainer: '',
    maintenanceTime: '',
  },
  rules: {
    customerName: [{ required: true, message: "请输入", trigger: "blur" }],
    taxpayerIdentificationNumber: [{ required: true, message: "请输入", trigger: "blur" }],
    companyAddress: [{ required: true, message: "请输入", trigger: "blur" }],
    companyPhone: [{ required: true, message: "请输入", trigger: "blur" }],
    contactPerson: [{ required: true, message: "请输入", trigger: "blur" }],
    contactPhone: [{ required: true, message: "请输入", trigger: "blur" }],
    maintainer: [{ required: false, message: "请选择", trigger: "change" }],
    maintenanceTime: [{ required: false, message: "请选择", trigger: "change" }],
  }
})
const { searchForm, form, rules } = toRefs(data)
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = 1
  getList()
}
const paginationChange = ({ current, limit }) => {
  page.current = current;
  page.size = limit;
  getList()
}
const getList = () => {
  tableLoading.value = true
  ledgerList({...searchForm.value, ...page}).then(res => {
    tableLoading.value = false
    tableData.value = res.rows
    total.value = res.total
  })
}
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  selectedRows.value = selection
}
// å±•开行
const expandChange = (row, expandedRows) => {
  const isExpanded = expandedRows.includes(row.id);
  if (isExpanded) {
    console.log('该行已展开');
    // å¯ä»¥åœ¨è¿™é‡Œè¿›è¡Œæ‡’加载或其他操作
  } else {
    console.log('该行已收起');
  }
}
// æ‰“开弹框
const openForm = (type, row) => {
  operationType.value = type
  form.value = {}
  userListNoPage().then(res => {
    userList.value = res.data
  })
  if (type === 'edit') {
    getCustomer(row.id).then(res => {
      form.value = {...res.data}
    })
  }
  dialogFormVisible.value = true
}
// æäº¤è¡¨å•
const submitForm = () => {
  proxy.$refs["formRef"].validate(valid => {
    if (valid) {
      if (operationType.value === "edit") {
        submitEdit()
      } else {
        submitAdd()
      }
    }
  })
}
// æäº¤æ–°å¢ž
const submitAdd = () => {
  addCustomer(form.value).then(res => {
    proxy.$modal.msgSuccess("提交成功")
    closeDia()
    getList()
  })
}
// æäº¤ä¿®æ”¹
const submitEdit = () => {
  updateCustomer(form.value).then(res => {
    proxy.$modal.msgSuccess("提交成功")
    closeDia()
    getList()
  })
}
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  proxy.resetForm("formRef")
  dialogFormVisible.value = false
}
// å¯¼å‡º
const handleOut = () => {
  ElMessageBox.confirm(
      '选中的内容将被导出,是否确认导出?',
      '导出', {
        confirmButtonText: '确认',
        cancelButtonText: '取消',
        type: 'warning',
      }
  ).then(() => {
    proxy.download("/basic/customer/export", {}, '客户档案.xlsx')
  }).catch(() => {
    proxy.$modal.msg("已取消")
  })
}
// åˆ é™¤
const handleDelete = () => {
  let ids = []
  if (selectedRows.value.length > 0) {
    ids = selectedRows.value.map(item => item.id);
  } else {
    proxy.$modal.msgWarning('请选择数据')
    return
  }
  ElMessageBox.confirm(
      '选中的内容将被删除,是否确认删除?',
      '导出', {
        confirmButtonText: '确认',
        cancelButtonText: '取消',
        type: 'warning',
      }
  ).then(() => {
    delCustomer(ids).then(res => {
      proxy.$modal.msgSuccess("删除成功")
    })
    getList()
  }).catch(() => {
    proxy.$modal.msg("已取消")
  })
}
getList()
</script>
<style scoped lang="scss">
</style>
src/views/tool/build/index.vue
@@ -307,7 +307,7 @@
</script>
<style lang='scss'>
$lighterBlue: #3472D7;
$lighterBlue: #2C51D9;
.container {
  position: relative;