dae8c5016c0b8894119618754acfe509123d2f91..e0430e0b25d759f6505a4e4542562a69c93b1db5
2025-05-07 gaoluyang
客户档案页面开发
e0430e 对比 | 目录
2025-05-07 gaoluyang
样式修改
80fc36 对比 | 目录
已修改12个文件
已添加3个文件
699 ■■■■ 文件已修改
src/assets/images/login-background.jpg 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/index.scss 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/ruoyi.scss 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/sidebar.scss 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/PIMTable/PIMTable.vue 314 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/PIMTable/Pagination.vue 100 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/AppMain.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Navbar.vue 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/TagsView/index.vue 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main.js 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/settings.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/settings.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/customerFile/index.vue 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/login.vue 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/build/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/images/login-background.jpg

src/assets/styles/index.scss
@@ -125,7 +125,22 @@
.app-container {
  padding: 20px;
}
.search_form {
  display: flex;
  align-items: center;
  justify-content: space-between;
  .search_title {
    font-size: 14px;
    font-weight: 700;
    color: #333333;
  }
}
.table_list {
  height: calc(100vh - 11em);
  margin-top: 20px;
  background: #fff;
  padding: 18px
}
.components-container {
  margin: 30px 50px;
  position: relative;
src/assets/styles/ruoyi.scss
@@ -151,7 +151,7 @@
/** è¡¨æ ¼æ›´å¤šæ“ä½œä¸‹æ‹‰æ ·å¼ */
.el-table .el-dropdown-link {
  cursor: pointer;
  color: #409EFF;
  color: #3472D7;
  margin-left: 10px;
}
src/assets/styles/sidebar.scss
@@ -5,6 +5,7 @@
    transition: margin-left .28s;
    margin-left: $base-sidebar-width;
    position: relative;
    background: #F5F7FB;
  }
  .sidebarHide {
@@ -82,12 +83,11 @@
    .sub-menu-title-noDropdown,
    .el-sub-menu__title {
      &:hover {
        background-color: rgba(0, 0, 0, 0.06) !important;
        background-color: rgba(212,221,255,0.56) !important;
      }
    }
    & .theme-dark .is-active > .el-sub-menu__title {
      color: $base-menu-color-active !important;
    & .theme-light .is-active > .el-sub-menu__title {
      color: #000000 !important;
    }
    & .nest-menu .el-sub-menu>.el-sub-menu__title,
@@ -95,16 +95,19 @@
      min-width: $base-sidebar-width !important;
      &:hover {
        background-color: rgba(0, 0, 0, 0.06) !important;
        background-color: rgba(212,221,255,0.56) !important;
      }
      &.is-active {
        background-color: rgba(212,221,255,0.56) !important;
      }
    }
    & .theme-dark .nest-menu .el-sub-menu>.el-sub-menu__title,
    & .theme-dark .el-sub-menu .el-menu-item {
      background-color: $base-sub-menu-background;
    & .theme-light .nest-menu .el-sub-menu>.el-sub-menu__title,
    & .theme-light .el-sub-menu .el-menu-item {
      //background-color: transparent;
      &:hover {
        background-color: $base-sub-menu-hover !important;
        background-color: rgba(212,221,255,0.56) !important;
      }
    }
  }
@@ -130,7 +133,6 @@
        }
      }
    }
    .el-sub-menu {
      overflow: hidden;
@@ -211,7 +213,10 @@
  .el-menu-item {
    &:hover {
      // you can use $sub-menuHover
      background-color: rgba(0, 0, 0, 0.06) !important;
      background-color: rgba(212,221,255,0.56) !important;
    }
    &.is-active {
      background-color: rgba(212,221,255,0.56) !important;
    }
  }
src/components/PIMTable/PIMTable.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,314 @@
<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" />
    <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">
      <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'">
          <slot v-if="item.slot" :index="scope.$index" :name="item.slot" :row="scope.row" />
        </div>
        <!-- è¿›åº¦æ¡ -->
        <div v-else-if="item.dataType == 'progress'">
          <el-progress :percentage="Number(scope.row[item.prop])" />
        </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" />
        </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)">
            {{ 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)">
            {{ 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)">
            {{ 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)}`">
          <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">
              {{ 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>
            </el-upload>
          </template>
        </div>
        <!-- å¯ç‚¹å‡»çš„æ–‡å­— -->
        <div v-else-if="item.dataType == 'link'" class="cell link" style="width: 100%"
             @click="goLink(scope.row, item.linkMethod)">
          <span v-if="!item.formatData">{{ scope.row[item.prop] }}</span>
        </div>
        <!-- é»˜è®¤çº¯å±•示数据 -->
        <div v-else class="cell" style="width: 100%">
          <span v-if="!item.formatData">{{ scope.row[item.prop] }}</span>
          <span v-else>{{ formatters(scope.row[item.prop], item.formatData) }}</span>
        </div>
      </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" />
</template>
<script setup>
import pagination from './Pagination.vue'
import { ref, inject, getCurrentInstance } from "vue"
import { ElMessage } from "element-plus"
// èŽ·å–å…¨å±€çš„ uploadHeader
const { proxy } = getCurrentInstance()
const uploadHeader = proxy.uploadHeader
const javaApi = proxy.javaApi
// Filters
const typeFn = (val, row) => {
  return typeof val === 'function' ? val(row) : val
}
const formatters = (val, format) => {
  return typeof format === 'function' ? format(val) : val
}
// 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: () => {}
  },
  rowClick: {
    type: Function,
    default: () => {}
  },
  currentChange: {
    type: Function,
    default: () => {}
  },
  border: {
    type: Boolean,
    default: true
  },
  highlightCurrentRow: {
    type: Boolean,
    default: false
  },
  headerCellStyle: {
    type: Object,
    default: () => ({})
  },
  column: {
    type: Array,
    default: () => []
  },
  rowClassName: {
    type: Function,
    default: () => ''
  },
  rowStyle: {
    type: [Object, Function],
    default: () => ({})
  },
  tableData: {
    type: Array,
    default: () => []
  },
  rowKey: {
    type: String,
    default: undefined
  },
  page: {
    type: Object,
    default: () => ({
      total: 0,
      current: 0,
      size: 10,
      layout: 'total, sizes, prev, pager, next, jumper'
    })
  }
})
// 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 äº‹ä»¶
const goLink = (row, linkMethod) => {
  if (!linkMethod) {
    return ElMessage.warning("请配置 link äº‹ä»¶")
  }
  const parentMethod = getParentMethod(linkMethod)
  if (typeof parentMethod === 'function') {
    parentMethod(row)
  } else {
    console.warn(`父组件中未找到方法: ${linkMethod}`)
  }
}
// èŽ·å–çˆ¶ç»„ä»¶æ–¹æ³•ï¼ˆç¤ºä¾‹å®žçŽ°ï¼‰
const getParentMethod = (methodName) => {
  const parentMethods = inject('parentMethods', {})
  return parentMethods[methodName]
}
const dataTypeFn = (val, format) => {
  if (typeof format === "function") {
    return format(val)
  } else return val
}
const formatType = (val, format) => {
  if (typeof format === "function") {
    return format(val)
  } else return ""
}
// æ–‡ä»¶å˜åŒ–处理
const handleChange = (file, fileList, index) => {
  if (fileList.length > 1) {
    const earliestFile = fileList[0]
    uploadRefs.value[index]?.handleRemove(earliestFile)
  }
  currentFiles.value[index] = file
}
// æ–‡ä»¶ä¸Šä¼ å‰æ ¡éªŒ
const beforeUpload = (rawFile, index) => {
  currentFiles.value[index] = {}
  if (rawFile.size > 1024 * 1024 * 10) {
    ElMessage.error('上传文件不超过10M')
    return false
  }
  return true
}
// ä¸Šä¼ æˆåŠŸ
const handleSuccessUp = (response, file, fileList, index) => {
  if (response.code == 200) {
    if (uploadRefs[index]) {
      uploadRefs[index].clearFiles()
    }
    currentFiles[index] = file
    ElMessage.success("上传成功")
    resetUploadComponent(index)
  } else {
    ElMessage.error(response.message)
  }
}
const resetUploadComponent = (index) => {
  uploadKeys[index] = Date.now()
}
// ä¸Šä¼ å¤±è´¥
const onError = (error, file, fileList, index) => {
  ElMessage.error('文件上传失败,请重试')
  if (uploadRefs.value[index]) {
    uploadRefs.value[index].clearFiles()
  }
}
// æ–‡ä»¶æ•°é‡è¶…限提示
const onExceed = () => {
  ElMessage.warning('超出文件个数')
}
const paginationSearch = ({ page, limit }) => {
  emit("pagination", { page: page, limit: limit });
}
</script>
<style scoped lang="scss">
</style>
src/components/PIMTable/Pagination.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,100 @@
<template>
  <div :class="{ hidden }" class="pagination-container">
    <el-pagination
      :background="background"
      v-model:current-page="currentPage"
      v-model:page-size="pageSize"
      :layout="layout"
      :page-sizes="pageSizes"
      :pager-count="pagerCount"
      :total="total"
      v-bind="$attrs"
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
    />
  </div>
</template>
<script setup>
import { computed } from 'vue'
import { scrollTo } from '@/utils/scroll-to'
const props = defineProps({
  total: {
    type: Number,
    required: true
  },
  page: {
    type: Number,
    default: 1
  },
  limit: {
    type: Number,
    default: 20
  },
  pageSizes: {
    type: Array,
    default: () => [10, 20, 30, 50, 100]
  },
  pagerCount: {
    type: Number,
    default: () => (document.body.clientWidth < 992 ? 5 : 7)
  },
  layout: {
    type: String,
    default: 'total, sizes, prev, pager, next, jumper'
  },
  background: {
    type: Boolean,
    default: true
  },
  autoScroll: {
    type: Boolean,
    default: true
  },
  hidden: {
    type: Boolean,
    default: false
  }
})
const emit = defineEmits(['update:page', 'update:limit', 'pagination'])
const currentPage = computed({
  get: () => props.page,
  set: (val) => emit('update:page', val)
})
const pageSize = computed({
  get: () => props.limit,
  set: (val) => emit('update:limit', val)
})
const handleSizeChange = (val) => {
  if (currentPage.value * val > props.total) {
    currentPage.value = 1
  }
  emit('pagination', { page: currentPage.value, limit: val })
  if (props.autoScroll) {
    scrollTo(0, 800)
  }
}
const handleCurrentChange = (val) => {
  emit('pagination', { page: val, limit: pageSize.value })
  if (props.autoScroll) {
    scrollTo(0, 800)
  }
}
</script>
<style scoped>
.pagination-container {
  background: #fff;
  padding: 28px 16px;
  margin-top: 10px;
}
.pagination-container.hidden {
  display: none;
}
</style>
src/layout/components/AppMain.vue
@@ -40,6 +40,7 @@
  width: 100%;
  position: relative;
  overflow: hidden;
  background: #F5F7FB;
}
.fixed-header + .app-main {
src/layout/components/Navbar.vue
@@ -1,34 +1,14 @@
<template>
  <div class="navbar">
    <hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
    <breadcrumb v-if="!settingsStore.topNav" id="breadcrumb-container" class="breadcrumb-container" />
    <top-nav v-if="settingsStore.topNav" id="topmenu-container" class="topmenu-container" />
    <div>
      <hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
      <breadcrumb v-if="!settingsStore.topNav" id="breadcrumb-container" class="breadcrumb-container" />
    </div>
<!--    <top-nav v-if="settingsStore.topNav" id="topmenu-container" class="topmenu-container" />-->
    <div class="center-menu">
      <span class="label">系统名称系统名称</span>
    </div>
    <div class="right-menu">
      <template v-if="appStore.device !== 'mobile'">
        <header-search id="header-search" class="right-menu-item" />
        <el-tooltip content="源码地址" effect="dark" placement="bottom">
          <ruo-yi-git id="ruoyi-git" class="right-menu-item hover-effect" />
        </el-tooltip>
        <el-tooltip content="文档地址" effect="dark" placement="bottom">
          <ruo-yi-doc id="ruoyi-doc" class="right-menu-item hover-effect" />
        </el-tooltip>
        <screenfull id="screenfull" class="right-menu-item hover-effect" />
        <el-tooltip content="主题模式" effect="dark" placement="bottom">
          <div class="right-menu-item hover-effect theme-switch-wrapper" @click="toggleTheme">
            <svg-icon v-if="settingsStore.isDark" icon-class="sunny" />
            <svg-icon v-if="!settingsStore.isDark" icon-class="moon" />
          </div>
        </el-tooltip>
        <el-tooltip content="布局大小" effect="dark" placement="bottom">
          <size-select id="size-select" class="right-menu-item hover-effect" />
        </el-tooltip>
      </template>
      <div class="avatar-container">
        <el-dropdown @command="handleCommand" class="right-menu-item hover-effect" trigger="click">
          <div class="avatar-wrapper">
@@ -118,6 +98,17 @@
  position: relative;
  background: var(--navbar-bg);
  box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
  .center-menu {
    line-height: 50px;
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    .label {
      font-weight: bold;
      font-size: 18px;
      color: #333333;
    }
  }
  .hamburger-container {
    line-height: 46px;
@@ -198,14 +189,14 @@
          cursor: pointer;
          width: 40px;
          height: 40px;
          border-radius: 10px;
          border-radius: 50px;
        }
        i {
          cursor: pointer;
          position: absolute;
          right: -20px;
          top: 25px;
          top: 14px;
          font-size: 12px;
        }
      }
src/layout/components/TagsView/index.vue
@@ -261,50 +261,40 @@
.tags-view-container {
  height: 34px;
  width: 100%;
  background: var(--tags-bg, #fff);
  border-bottom: 1px solid var(--tags-item-border, #d8dce5);
  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
  background: transparent;
  .tags-view-wrapper {
    .tags-view-item {
      display: inline-block;
      position: relative;
      cursor: pointer;
      height: 26px;
      line-height: 26px;
      border: 1px solid var(--tags-item-border, #d8dce5);
      color: var(--tags-item-text, #495060);
      background: var(--tags-item-bg, #fff);
      padding: 0 8px;
      height: 32px;
      line-height: 32px;
      //border: 1px solid var(--tags-item-border, #d8dce5);
      color: #4E5463;
      background: #E5E7EA;
      padding: 0 16px;
      font-size: 12px;
      margin-left: 5px;
      margin-top: 4px;
      //margin-left: 5px;
      //margin-top: 4px;
      &:first-of-type {
        margin-left: 15px;
      }
      &:last-of-type {
        margin-right: 15px;
      }
      //&:first-of-type {
      //  margin-left: 8px;
      //}
      //
      //&:last-of-type {
      //  margin-right: 15px;
      //}
      &.active {
        background-color: #42b983;
        color: #fff;
        border-color: #42b983;
        &::before {
          content: '';
          background: #fff;
          display: inline-block;
          width: 8px;
          height: 8px;
          border-radius: 50%;
          position: relative;
          margin-right: 5px;
        }
        background-color: #FFFFFF !important;
        color: #2C51D9;
      }
    }
    //.tags-view-item div {
    //  transform: skew(12deg);
    //  display: inline-block;
    //}
  }
  .contextmenu {
src/main.js
@@ -42,6 +42,10 @@
import ImagePreview from "@/components/ImagePreview"
// å­—典标签组件
import DictTag from '@/components/DictTag'
// è¡¨æ ¼ç»„ä»¶
import PIMTable from "@/components/PIMTable/PIMTable.vue";
import { getToken } from "@/utils/auth";
const app = createApp(App)
@@ -54,6 +58,13 @@
app.config.globalProperties.addDateRange = addDateRange
app.config.globalProperties.selectDictLabel = selectDictLabel
app.config.globalProperties.selectDictLabels = selectDictLabels
app.config.globalProperties.javaApi = 'http://192.168.1.36:8080'
app.config.globalProperties.HaveJson = (val) => {
  return JSON.parse(JSON.stringify(val));
};
app.config.globalProperties.uploadHeader = {
  Authorization: "Bearer " + getToken(),
};
// å…¨å±€ç»„件挂载
app.component('DictTag', DictTag)
@@ -63,6 +74,7 @@
app.component('ImagePreview', ImagePreview)
app.component('RightToolbar', RightToolbar)
app.component('Editor', Editor)
app.component('PIMTable', PIMTable)
app.use(router)
app.use(store)
src/settings.js
@@ -6,7 +6,7 @@
  /**
   * ä¾§è¾¹æ ä¸»é¢˜ æ·±è‰²ä¸»é¢˜theme-dark,浅色主题theme-light
   */
  sideTheme: 'theme-dark',
  sideTheme: 'theme-light',
  /**
   * æ˜¯å¦ç³»ç»Ÿå¸ƒå±€é…ç½®
   */
@@ -25,7 +25,7 @@
  /**
   * æ˜¯å¦å›ºå®šå¤´éƒ¨
   */
  fixedHeader: false,
  fixedHeader: true,
  /**
   * æ˜¯å¦æ˜¾ç¤ºlogo
src/store/modules/settings.js
@@ -14,7 +14,7 @@
  {
    state: () => ({
      title: '',
      theme: storageSetting.theme || '#409EFF',
      theme: storageSetting.theme || '#3472D7',
      sideTheme: storageSetting.sideTheme || sideTheme,
      showSettings: showSettings,
      topNav: storageSetting.topNav === undefined ? topNav : storageSetting.topNav,
src/views/basicData/customerFile/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,43 @@
<template>
<div class="app-container">
  <div class="search_form">
    <div>
      <span class="search_title">客户名称:</span>
      <el-input
          v-model="input2"
          style="width: 240px"
          placeholder="请输入"
          :prefix-icon="Search"
      />
    </div>
    <div>
      <el-button type="primary">新增客户</el-button>
      <el-button>导出</el-button>
      <el-button type="danger" plain>删除</el-button>
    </div>
  </div>
  <div class="table_list">
    <PIMTable :column="tableColumn"></PIMTable>
  </div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import {Search} from "@element-plus/icons-vue";
const input2 = ref('')
const tableColumn = ref([
  {
    label: '批准内容',
    prop: 'ratifyRemark'
  }, {
    label: '批准人',
    prop: 'ratifyName',
  },
])
</script>
<style scoped lang="scss">
</style>
src/views/login.vue
@@ -2,6 +2,7 @@
  <div class="login">
    <el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form">
      <h3 class="title">{{ title }}</h3>
      <el-divider />
      <el-form-item prop="username">
        <el-input
          v-model="loginForm.username"
@@ -10,7 +11,7 @@
          auto-complete="off"
          placeholder="账号"
        >
          <template #prefix><svg-icon icon-class="user" class="el-input__icon input-icon" /></template>
          <template #prefix><el-icon><User /></el-icon></template>
        </el-input>
      </el-form-item>
      <el-form-item prop="password">
@@ -20,27 +21,27 @@
          size="large"
          auto-complete="off"
          placeholder="密码"
          show-password
          @keyup.enter="handleLogin"
        >
          <template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
        </el-input>
      </el-form-item>
      <el-form-item prop="code" v-if="captchaEnabled">
        <el-input
          v-model="loginForm.code"
          size="large"
          auto-complete="off"
          placeholder="验证码"
          style="width: 63%"
          @keyup.enter="handleLogin"
        >
          <template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>
        </el-input>
        <div class="login-code">
          <img :src="codeUrl" @click="getCode" class="login-code-img"/>
        </div>
      </el-form-item>
      <el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox>
<!--      <el-form-item prop="code" v-if="captchaEnabled">-->
<!--        <el-input-->
<!--          v-model="loginForm.code"-->
<!--          size="large"-->
<!--          auto-complete="off"-->
<!--          placeholder="验证码"-->
<!--          style="width: 63%"-->
<!--          @keyup.enter="handleLogin"-->
<!--        >-->
<!--          <template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>-->
<!--        </el-input>-->
<!--        <div class="login-code">-->
<!--          <img :src="codeUrl" @click="getCode" class="login-code-img"/>-->
<!--        </div>-->
<!--      </el-form-item>-->
      <el-form-item style="width:100%;">
        <el-button
          :loading="loading"
@@ -56,11 +57,12 @@
          <router-link class="link-type" :to="'/register'">立即注册</router-link>
        </div>
      </el-form-item>
      <el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox>
    </el-form>
    <!--  åº•部  -->
    <div class="el-login-footer">
      <span>Copyright Â© 2018-2025 ruoyi.vip All Rights Reserved.</span>
    </div>
<!--    <div class="el-login-footer">-->
<!--      <span>Copyright Â© 2018-2025 ruoyi.vip All Rights Reserved.</span>-->
<!--    </div>-->
  </div>
</template>
@@ -80,14 +82,14 @@
  username: "admin",
  password: "admin123",
  rememberMe: false,
  code: "",
  // code: "",
  uuid: ""
})
const loginRules = {
  username: [{ required: true, trigger: "blur", message: "请输入您的账号" }],
  password: [{ required: true, trigger: "blur", message: "请输入您的密码" }],
  code: [{ required: true, trigger: "change", message: "请输入验证码" }]
  // code: [{ required: true, trigger: "change", message: "请输入验证码" }]
}
const codeUrl = ref("")
@@ -165,24 +167,29 @@
<style lang='scss' scoped>
.login {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
  background-image: url("../assets/images/login-background.jpg");
  background-size: cover;
  position: relative;
}
.title {
  margin: 0px auto 30px auto;
  margin: 20px auto 14px auto;
  text-align: center;
  color: #707070;
  color: #3472D7;
  font-size: 28px;
  font-weight: 700;
}
.login-form {
  position: absolute;
  top: 50%;
  right: 19%;
  transform: translate(0, -50%);
  border-radius: 6px;
  background: #ffffff;
  width: 400px;
  padding: 25px 25px 5px 25px;
  width: 420px;
  height: 500px;
  padding: 40px;
  z-index: 1;
  .el-input {
    height: 40px;
@@ -226,4 +233,9 @@
  height: 40px;
  padding-left: 12px;
}
:deep() {
  .el-form-item--default {
    margin-bottom: 36px;
  }
}
</style>
src/views/tool/build/index.vue
@@ -307,7 +307,7 @@
</script>
<style lang='scss'>
$lighterBlue: #409EFF;
$lighterBlue: #3472D7;
.container {
  position: relative;