gaoluyang
2025-05-08 9ff9962d8d2d4cb1cd5d6abcb902e916c44cda03
客户档案页面开发
已修改10个文件
已添加1个文件
470 ■■■■ 文件已修改
src/api/basicData/customerFile.js 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/user.js 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/element-ui.scss 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/ruoyi.scss 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/PIMTable/PIMTable.vue 66 ●●●● 补丁 | 查看 | 原始文档 | 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 282 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/login.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/build/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/basicData/customerFile.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,51 @@
// å®¢æˆ·æ¡£æ¡ˆé¡µé¢æŽ¥å£
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(id) {
    return request({
        url: '/basic/customer/' + id,
        method: 'delete'
    })
}
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,56 @@
  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;
  }
}
// refine element ui upload
src/assets/styles/ruoyi.scss
@@ -71,7 +71,6 @@
  overflow: auto;
  overflow-x: hidden;
  max-height: 70vh;
  padding: 10px 20px 0;
}
.el-table {
@@ -151,7 +150,7 @@
/** è¡¨æ ¼æ›´å¤šæ“ä½œä¸‹æ‹‰æ ·å¼ */
.el-table .el-dropdown-link {
  cursor: pointer;
  color: #3472D7;
  color: #2C51D9;
  margin-left: 10px;
}
src/components/PIMTable/PIMTable.vue
@@ -1,11 +1,11 @@
<template>
  <el-table ref="multipleTable" v-loading="tableLoading" :border="border" :data="tableData"
            :header-cell-style="{ background: '#F0F1F5', color: '#333333' }" :height="height"
            :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" :span-method="spanMethod" stripe style="width: 100%" tooltip-effect="dark" @row-click="rowClick"
            :row-key="rowKey" 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" />
    <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"
@@ -51,12 +51,11 @@
        </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)"
                       :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>
@@ -71,7 +70,7 @@
                       :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"
              <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>
@@ -89,7 +88,7 @@
      </template>
    </el-table-column>
  </el-table>
  <pagination v-show="page.total > 0" :total="page.total" :layout="page.layout" :page="page.current"
  <pagination v-show="total > 0" :total="total" :layout="page.layout" :page="page.current"
              :limit="page.size" @pagination="paginationSearch" />
</template>
@@ -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,14 +115,6 @@
// Props(使用 defineProps çš„非 TS å½¢å¼ï¼‰
const props = defineProps({
  isSelection: {
    type: Boolean,
    default: false
  },
  height: {
    type: [String, null],
    default: null
  },
  tableLoading: {
    type: Boolean,
    default: false
@@ -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 äº‹ä»¶
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,298 @@
    <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: "操作",
    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(() => {
    delCustomer(ids).then(res => {
      proxy.$modal.msgSuccess("删除成功")
    })
    getList()
  }).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/tool/build/index.vue
@@ -307,7 +307,7 @@
</script>
<style lang='scss'>
$lighterBlue: #3472D7;
$lighterBlue: #2C51D9;
.container {
  position: relative;