From eca47cecf33c93a7cf711f736f9a4d9be48392f9 Mon Sep 17 00:00:00 2001
From: yuan <123@>
Date: 星期六, 28 三月 2026 14:41:42 +0800
Subject: [PATCH] feat(enterpriseInfo): 新增企业信息管理页面

---
 src/views/basicData/enterpriseInfo/index.vue |  654 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/api/basicData/enterpriseInfo.js          |   47 +++
 2 files changed, 701 insertions(+), 0 deletions(-)

diff --git a/src/api/basicData/enterpriseInfo.js b/src/api/basicData/enterpriseInfo.js
new file mode 100644
index 0000000..d4c034e
--- /dev/null
+++ b/src/api/basicData/enterpriseInfo.js
@@ -0,0 +1,47 @@
+// 浼佷笟闂ㄦ埛椤甸潰鎺ュ彛
+import request from '@/utils/request'
+
+// 鑾峰彇浼佷笟淇℃伅
+export function getEnterpriseInfo() {
+    return request({
+        url: '/system/enterpriseInfo/getInfo',
+        method: 'get'
+    })
+}
+
+// 淇濆瓨浼佷笟淇℃伅
+export function saveEnterpriseInfo(data) {
+    return request({
+        url: '/system/enterpriseInfo/save',
+        method: 'post',
+        data: data
+    })
+}
+
+// 涓婁紶Logo
+export function uploadLogo(file) {
+    const formData = new FormData()
+    formData.append('file', file)
+    return request({
+        url: '/system/enterpriseInfo/uploadLogo',
+        method: 'post',
+        data: formData,
+        headers: {
+            'Content-Type': 'multipart/form-data'
+        }
+    })
+}
+
+// 涓婁紶浜岀淮鐮�
+export function uploadQrCode(file) {
+    const formData = new FormData()
+    formData.append('file', file)
+    return request({
+        url: '/system/enterpriseInfo/uploadQrCode',
+        method: 'post',
+        data: formData,
+        headers: {
+            'Content-Type': 'multipart/form-data'
+        }
+    })
+}
diff --git a/src/views/basicData/enterpriseInfo/index.vue b/src/views/basicData/enterpriseInfo/index.vue
new file mode 100644
index 0000000..1469568
--- /dev/null
+++ b/src/views/basicData/enterpriseInfo/index.vue
@@ -0,0 +1,654 @@
+<template>
+  <div class="app-container">
+    <!-- 椤甸潰鏍囬鏍� -->
+    <div class="page-header">
+      <h2>浼佷笟闂ㄦ埛</h2>
+      <div class="header-actions">
+        <el-button @click="handlePreview" :icon="View" type="info" plain>棰勮</el-button>
+        <el-button @click="toggleEdit" :icon="isEdit ? 'Close' : 'Edit'" :type="isEdit ? 'default' : 'primary'">
+          {{ isEdit ? '鍙栨秷缂栬緫' : '缂栬緫' }}
+        </el-button>
+      </div>
+    </div>
+
+    <!-- 浼佷笟淇℃伅鍗$墖 -->
+    <div class="enterprise-info-card" v-loading="loading">
+      <!-- 鍩烘湰淇℃伅鍖哄煙 -->
+      <div class="info-section">
+        <div class="section-header">
+          <h3>鍩烘湰淇℃伅</h3>
+        </div>
+        <el-descriptions :column="2" border class="info-descriptions">
+          <el-descriptions-item label="鍏徃鍚嶇О">
+            <el-input v-if="isEdit" v-model="form.companyName" placeholder="璇疯緭鍏ュ叕鍙稿悕绉�" clearable />
+            <span v-else class="content-text">{{ form.companyName || '-' }}</span>
+          </el-descriptions-item>
+          <el-descriptions-item label="鑱旂郴浜�">
+            <el-input v-if="isEdit" v-model="form.contactPerson" placeholder="璇疯緭鍏ヨ仈绯讳汉" clearable />
+            <span v-else class="content-text">{{ form.contactPerson || '-' }}</span>
+          </el-descriptions-item>
+          <el-descriptions-item label="鑱旂郴鐢佃瘽">
+            <el-input v-if="isEdit" v-model="form.contactPhone" placeholder="璇疯緭鍏ヨ仈绯荤數璇�" clearable />
+            <span v-else class="content-text">{{ form.contactPhone || '-' }}</span>
+          </el-descriptions-item>
+          <el-descriptions-item label="鍏徃鍦板潃">
+            <el-input v-if="isEdit" v-model="form.companyAddress" placeholder="璇疯緭鍏ュ叕鍙稿湴鍧�" clearable />
+            <span v-else class="content-text">{{ form.companyAddress || '-' }}</span>
+          </el-descriptions-item>
+          <el-descriptions-item label="鍏徃缃戠珯">
+            <el-input v-if="isEdit" v-model="form.website" placeholder="璇疯緭鍏ュ叕鍙哥綉绔�" clearable />
+            <a v-else-if="form.website" :href="form.website" target="_blank" class="link-text">{{ form.website }}</a>
+            <span v-else class="content-text">-</span>
+          </el-descriptions-item>
+        </el-descriptions>
+      </div>
+
+      <!-- Logo鍜屼簩缁寸爜鍖哄煙 -->
+      <div class="info-section">
+        <div class="section-header">
+          <h3>浼佷笟鏍囪瘑</h3>
+        </div>
+        <div class="logo-qr-container">
+          <!-- 鍏徃Logo -->
+          <div class="upload-item">
+            <span class="upload-label">鍏徃Logo</span>
+            <div class="upload-wrapper">
+              <el-upload
+                v-if="isEdit"
+                class="logo-uploader"
+                :show-file-list="false"
+                :before-upload="(file) => beforeLogoUpload(file, 'companyLogo')"
+                action="#">
+                <img v-if="form.companyLogo" :src="'/file/preview?url=' + form.companyLogo" class="uploaded-image" />
+                <div v-else class="upload-placeholder">
+                  <el-icon class="upload-icon"><Plus /></el-icon>
+                  <span class="upload-text">涓婁紶Logo</span>
+                </div>
+              </el-upload>
+              <img
+                v-else-if="form.companyLogo"
+                :src="'/file/preview?url=' + form.companyLogo"
+                class="display-image"
+              />
+              <div v-else class="empty-placeholder">
+                <el-icon :size="40"><Picture /></el-icon>
+                <span>鏆傛棤Logo</span>
+              </div>
+            </div>
+          </div>
+
+          <!-- 浜岀淮鐮� -->
+          <div class="upload-item">
+            <span class="upload-label">浜岀淮鐮�</span>
+            <div class="upload-wrapper">
+              <el-upload
+                v-if="isEdit"
+                class="qr-uploader"
+                :show-file-list="false"
+                :before-upload="(file) => beforeLogoUpload(file, 'qrCode')"
+                action="#">
+                <img v-if="form.qrCode" :src="'/file/preview?url=' + form.qrCode" class="uploaded-image" />
+                <div v-else class="upload-placeholder">
+                  <el-icon class="upload-icon"><Plus /></el-icon>
+                  <span class="upload-text">涓婁紶浜岀淮鐮�</span>
+                </div>
+              </el-upload>
+              <img
+                v-else-if="form.qrCode"
+                :src="'/file/preview?url=' + form.qrCode"
+                class="display-image"
+              />
+              <div v-else class="empty-placeholder">
+                <el-icon :size="40"><Picture /></el-icon>
+                <span>鏆傛棤浜岀淮鐮�</span>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 鍏徃绠�浠� -->
+      <div class="info-section">
+        <div class="section-header">
+          <h3>鍏徃绠�浠�</h3>
+        </div>
+        <div class="content-editor">
+          <el-input
+            v-if="isEdit"
+            v-model="form.companyIntro"
+            type="textarea"
+            :rows="6"
+            maxlength="2000"
+            show-word-limit
+            placeholder="璇疯緭鍏ュ叕鍙哥畝浠�..."
+          />
+          <div v-else class="content-display" v-html="form.companyIntro || '<span class=\'empty-text\'>鏆傛棤鍏徃绠�浠�</span>'"></div>
+        </div>
+      </div>
+
+      <!-- 浜у搧浠嬬粛 -->
+      <div class="info-section">
+        <div class="section-header">
+          <h3>浜у搧浠嬬粛</h3>
+        </div>
+        <div class="content-editor">
+          <el-input
+            v-if="isEdit"
+            v-model="form.productIntro"
+            type="textarea"
+            :rows="6"
+            maxlength="2000"
+            show-word-limit
+            placeholder="璇疯緭鍏ヤ骇鍝佷粙缁�..."
+          />
+          <div v-else class="content-display" v-html="form.productIntro || '<span class=\'empty-text\'>鏆傛棤浜у搧浠嬬粛</span>'"></div>
+        </div>
+      </div>
+
+      <!-- 璁惧浠嬬粛 -->
+      <div class="info-section">
+        <div class="section-header">
+          <h3>璁惧浠嬬粛</h3>
+        </div>
+        <div class="content-editor">
+          <el-input
+            v-if="isEdit"
+            v-model="form.equipmentIntro"
+            type="textarea"
+            :rows="6"
+            maxlength="2000"
+            show-word-limit
+            placeholder="璇疯緭鍏ヨ澶囦粙缁�..."
+          />
+          <div v-else class="content-display" v-html="form.equipmentIntro || '<span class=\'empty-text\'>鏆傛棤璁惧浠嬬粛</span>'"></div>
+        </div>
+      </div>
+
+      <!-- 鎿嶄綔鎸夐挳 -->
+      <div v-if="isEdit" class="form-actions">
+        <el-button type="primary" @click="handleSave" :loading="saving" size="large">淇濆瓨</el-button>
+        <el-button @click="handleCancel" size="large">鍙栨秷</el-button>
+      </div>
+    </div>
+
+    <!-- 棰勮寮圭獥 -->
+    <el-dialog
+      v-model="previewVisible"
+      title="浼佷笟淇℃伅棰勮"
+      width="70%"
+      :destroy-on-close="true">
+      <div class="preview-content">
+        <div class="preview-header">
+          <img v-if="form.companyLogo" :src="'/file/preview?url=' + form.companyLogo" class="preview-logo" />
+          <div class="preview-title">
+            <h1>{{ form.companyName || '鍏徃鍚嶇О' }}</h1>
+            <p v-if="form.website">{{ form.website }}</p>
+          </div>
+        </div>
+        <el-divider />
+        <div class="preview-section">
+          <h4>鑱旂郴鏂瑰紡</h4>
+          <p>鑱旂郴浜猴細{{ form.contactPerson || '-' }}</p>
+          <p>鑱旂郴鐢佃瘽锛歿{ form.contactPhone || '-' }}</p>
+          <p>鍏徃鍦板潃锛歿{ form.companyAddress || '-' }}</p>
+        </div>
+        <div class="preview-section">
+          <h4>鍏徃绠�浠�</h4>
+          <div v-html="form.companyIntro || '鏆傛棤绠�浠�'"></div>
+        </div>
+        <div class="preview-section">
+          <h4>浜у搧浠嬬粛</h4>
+          <div v-html="form.productIntro || '鏆傛棤浠嬬粛'"></div>
+        </div>
+        <div class="preview-section">
+          <h4>璁惧浠嬬粛</h4>
+          <div v-html="form.equipmentIntro || '鏆傛棤浠嬬粛'"></div>
+        </div>
+        <div v-if="form.qrCode" class="preview-section preview-qr">
+          <h4>鎵爜鍏虫敞</h4>
+          <img :src="'/file/preview?url=' + form.qrCode" class="qr-image" />
+        </div>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+  import { ref, reactive, onMounted } from 'vue'
+  import { ElMessage } from 'element-plus'
+  import { Plus, Picture, View } from '@element-plus/icons-vue'
+  import { getEnterpriseInfo, saveEnterpriseInfo, uploadLogo, uploadQrCode } from '@/api/basicData/enterpriseInfo.js'
+
+  const { proxy } = getCurrentInstance()
+
+  const loading = ref(false)
+  const saving = ref(false)
+  const isEdit = ref(false)
+  const previewVisible = ref(false)
+  const uploadType = ref('')
+
+  const form = reactive({
+    id: null,
+    companyName: '',
+    companyLogo: '',
+    companyIntro: '',
+    productIntro: '',
+    equipmentIntro: '',
+    contactPerson: '',
+    contactPhone: '',
+    companyAddress: '',
+    website: '',
+    qrCode: ''
+  })
+
+  // 娣辨嫹璐濆師濮嬫暟鎹紝鐢ㄤ簬鍙栨秷鏃舵仮澶�
+  let originalForm = {}
+
+  // 鑾峰彇浼佷笟淇℃伅
+  const fetchInfo = () => {
+    loading.value = true
+    getEnterpriseInfo().then(res => {
+      if (res.code === 200 && res.data) {
+        Object.assign(form, res.data)
+        originalForm = JSON.parse(JSON.stringify(res.data))
+      }
+    }).finally(() => {
+      loading.value = false
+    })
+  }
+
+  // 鍒囨崲缂栬緫妯″紡
+  const toggleEdit = () => {
+    if (isEdit.value) {
+      // 鍙栨秷缂栬緫锛屾仮澶嶅師濮嬫暟鎹�
+      Object.assign(form, originalForm)
+      isEdit.value = false
+    } else {
+      // 杩涘叆缂栬緫妯″紡
+      originalForm = JSON.parse(JSON.stringify(form))
+      isEdit.value = true
+    }
+  }
+
+  // 棰勮
+  const handlePreview = () => {
+    previewVisible.value = true
+  }
+
+  // 鏂囦欢涓婁紶鍓嶆牎楠�
+  const beforeLogoUpload = (file, type) => {
+    const isImage = file.type.startsWith('image/')
+    const isLt2M = file.size / 1024 / 1024 < 2
+
+    if (!isImage) {
+      ElMessage.error('鍙兘涓婁紶鍥剧墖鏂囦欢锛�')
+      return false
+    }
+    if (!isLt2M) {
+      ElMessage.error('鍥剧墖澶у皬涓嶈兘瓒呰繃 2MB锛�')
+      return false
+    }
+
+    uploadType.value = type
+    uploadFileReq(file)
+    return false
+  }
+
+  // 涓婁紶鏂囦欢璇锋眰
+  const uploadFileReq = (file) => {
+    const uploadFn = uploadType.value === 'companyLogo' ? uploadLogo : uploadQrCode
+    uploadFn(file).then(res => {
+      if (res.code === 200) {
+        const path = res.data.tempPath
+        if (uploadType.value === 'companyLogo') {
+          form.companyLogo = path
+        } else {
+          form.qrCode = path
+        }
+        ElMessage.success('涓婁紶鎴愬姛')
+      } else {
+        ElMessage.error(res.msg || '涓婁紶澶辫触')
+      }
+    }).catch(() => {
+      ElMessage.error('涓婁紶澶辫触')
+    })
+  }
+
+  // 淇濆瓨
+  const handleSave = () => {
+    saving.value = true
+    saveEnterpriseInfo(form).then(res => {
+      if (res.code === 200) {
+        ElMessage.success('淇濆瓨鎴愬姛')
+        isEdit.value = false
+        fetchInfo()
+      } else {
+        ElMessage.error(res.msg || '淇濆瓨澶辫触')
+      }
+    }).finally(() => {
+      saving.value = false
+    })
+  }
+
+  // 鍙栨秷
+  const handleCancel = () => {
+    Object.assign(form, originalForm)
+    isEdit.value = false
+  }
+
+  onMounted(() => {
+    fetchInfo()
+  })
+</script>
+
+<style scoped lang="scss">
+.page-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 16px 24px;
+  background: #fff;
+  border-radius: 8px;
+  margin-bottom: 16px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+
+  h2 {
+    margin: 0;
+    font-size: 20px;
+    font-weight: 600;
+    color: #303133;
+  }
+
+  .header-actions {
+    display: flex;
+    gap: 10px;
+  }
+}
+
+.enterprise-info-card {
+  background: #fff;
+  padding: 24px;
+  border-radius: 8px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
+}
+
+.info-section {
+  margin-bottom: 32px;
+
+  &:last-of-type {
+    margin-bottom: 0;
+  }
+
+  .section-header {
+    margin-bottom: 16px;
+
+    h3 {
+      margin: 0;
+      font-size: 16px;
+      font-weight: 600;
+      color: #303133;
+      padding-bottom: 10px;
+      border-bottom: 2px solid #409eff;
+      display: inline-block;
+      position: relative;
+
+      &::after {
+        content: '';
+        position: absolute;
+        left: 0;
+        bottom: -2px;
+        width: 60px;
+        height: 2px;
+        background-color: #409eff;
+      }
+    }
+  }
+}
+
+.info-descriptions {
+  :deep(.el-descriptions__label) {
+    width: 120px;
+    background-color: #f5f7fa;
+    font-weight: 500;
+    color: #606266;
+  }
+
+  :deep(.el-descriptions__content) {
+    color: #303133;
+  }
+}
+
+.content-text {
+  color: #606266;
+}
+
+.link-text {
+  color: #409eff;
+  text-decoration: none;
+
+  &:hover {
+    text-decoration: underline;
+  }
+}
+
+.logo-qr-container {
+  display: flex;
+  gap: 40px;
+}
+
+.upload-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+
+  .upload-label {
+    font-size: 14px;
+    color: #606266;
+    margin-bottom: 12px;
+    font-weight: 500;
+  }
+
+  .upload-wrapper {
+    width: 140px;
+    height: 140px;
+  }
+}
+
+/* el-upload 鏍硅妭鐐规槸澶栧眰 div锛岀湡姝hЕ鍙戝尯鍦ㄥ唴灞� .el-upload锛岄渶璁╁唴灞傞摵婊″灞傝櫄绾挎 */
+.logo-uploader,
+.qr-uploader {
+  display: flex;
+  flex-direction: column;
+  box-sizing: border-box;
+  width: 140px;
+  height: 140px;
+  border: 1px dashed #d9d9d9;
+  border-radius: 8px;
+  cursor: pointer;
+  transition: all 0.3s;
+  overflow: hidden;
+
+  :deep(.el-upload) {
+    flex: 1;
+    min-height: 0;
+    width: 100%;
+    display: flex !important;
+    flex-direction: column;
+    align-items: stretch;
+    box-sizing: border-box;
+    border: none;
+    outline: none;
+  }
+
+  :deep(.uploaded-image),
+  :deep(.upload-placeholder) {
+    flex: 1 1 auto;
+    min-height: 0;
+    min-width: 0;
+    width: 100%;
+  }
+
+  &:hover {
+    border-color: #409eff;
+  }
+
+  :deep(.uploaded-image) {
+    object-fit: contain;
+  }
+
+  :deep(.upload-placeholder) {
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    background: #fafafa;
+    color: #8c939d;
+
+    .upload-icon {
+      font-size: 32px;
+      margin-bottom: 8px;
+    }
+
+    .upload-text {
+      font-size: 12px;
+    }
+  }
+}
+
+.display-image {
+  width: 140px;
+  height: 140px;
+  object-fit: contain;
+  border-radius: 8px;
+  border: 1px solid #e4e7ed;
+}
+
+.empty-placeholder {
+  width: 140px;
+  height: 140px;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  background: #fafafa;
+  border-radius: 8px;
+  border: 1px dashed #e4e7ed;
+  color: #c0c4cc;
+
+  span {
+    margin-top: 8px;
+    font-size: 12px;
+  }
+}
+
+.content-editor {
+  :deep(.el-textarea__inner) {
+    border-radius: 6px;
+    font-size: 14px;
+    line-height: 1.8;
+  }
+}
+
+.content-display {
+  padding: 16px 20px;
+  background: #fafafa;
+  border-radius: 6px;
+  border: 1px solid #ebeef5;
+  line-height: 1.8;
+  color: #606266;
+  min-height: 120px;
+  white-space: pre-wrap;
+
+  :deep(.empty-text) {
+    color: #c0c4cc;
+    font-style: italic;
+  }
+}
+
+.form-actions {
+  display: flex;
+  justify-content: center;
+  gap: 16px;
+  padding-top: 24px;
+  margin-top: 16px;
+  border-top: 1px solid #ebeef5;
+}
+
+/* 棰勮寮圭獥鏍峰紡 */
+.preview-content {
+  .preview-header {
+    display: flex;
+    align-items: center;
+    gap: 20px;
+    padding: 20px;
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    border-radius: 8px;
+    color: #fff;
+    margin-bottom: 20px;
+
+    .preview-logo {
+      width: 80px;
+      height: 80px;
+      object-fit: contain;
+      background: #fff;
+      border-radius: 8px;
+      padding: 8px;
+    }
+
+    .preview-title {
+      h1 {
+        margin: 0 0 8px 0;
+        font-size: 28px;
+        font-weight: 600;
+      }
+
+      p {
+        margin: 0;
+        opacity: 0.9;
+        font-size: 14px;
+      }
+    }
+  }
+
+  .preview-section {
+    margin-bottom: 24px;
+
+    h4 {
+      font-size: 16px;
+      font-weight: 600;
+      color: #303133;
+      margin: 0 0 12px 0;
+      padding-bottom: 8px;
+      border-bottom: 1px solid #ebeef5;
+    }
+
+    p {
+      margin: 8px 0;
+      color: #606266;
+      line-height: 1.6;
+    }
+
+    :deep(div) {
+      line-height: 1.8;
+      color: #606266;
+      white-space: pre-wrap;
+    }
+  }
+
+  .preview-qr {
+    text-align: center;
+
+    .qr-image {
+      width: 150px;
+      height: 150px;
+      margin-top: 12px;
+      border-radius: 8px;
+      border: 1px solid #ebeef5;
+    }
+  }
+}
+
+:deep(.el-divider) {
+  margin: 16px 0;
+}
+</style>

--
Gitblit v1.9.3