index.vue 12 KB
<template>
  <div class="app-container NCC-flex-main systemConfig">
    <el-form ref="baseForm" :model="baseForm" label-width="120px">
      <el-tabs v-model="activeName" type="border-card" class="NCC-el_tabs">
        <el-tab-pane label="基本设置" name="first">
          <div class="config-tab-panel">
            <BasicConfigPanel
              :form="baseForm"
              :loading="listLoading"
              :saving="btnLoading"
              :logo-uploading="logoUploading"
              :logo-icon-uploading="logoIconUploading"
              :logo-preview="logoPreview"
              :on-upload-logo="uploadSysLogo"
              :on-upload-logo-icon="uploadSysLogoIcon"
              @save="submitForm"
            />
          </div>
        </el-tab-pane>
        <el-tab-pane label="安全设置" name="second">
          <div class="config-tab-panel">
            <SecurityConfigPanel
              :form="baseForm"
              :loading="listLoading"
              :saving="btnLoading"
              @save="submitForm"
              @watermark-switch-change="onWatermarkSwitchChange"
              @watermark-opacity-change="onWatermarkOpacityChange"
              @privacy-mobile-mask-change="onPrivacyMobileMaskChange"
            />
          </div>
        </el-tab-pane>
        <el-tab-pane label="短信设置" name="sms">
          <div class="config-tab-panel">
            <sms-config-panel />
          </div>
        </el-tab-pane>
        <el-tab-pane label="AI 配置" name="third">
          <div class="config-tab-panel">
            <AiConfigPanel
              :form="baseForm"
              :loading="listLoading"
              :saving="btnLoading"
              :default-paraformer-submit-url="defaultParaformerSubmitUrl"
              @save="submitForm"
            />
          </div>
        </el-tab-pane>
        <el-tab-pane label="业务配置" name="business">
          <div class="config-tab-panel">
            <BusinessConfigPanel :form="baseForm" :loading="listLoading" :saving="btnLoading" @save="submitForm" />
          </div>
        </el-tab-pane>
        <el-tab-pane label="会员商城支付" name="mallPay">
          <div class="config-tab-panel">
            <MallPayConfigPanel
              v-if="mallPayReady"
              :form="mallPayForm"
              :saving="mallPayLoading"
              @save="saveMallPay"
            />
          </div>
        </el-tab-pane>
      </el-tabs>
    </el-form>
  </div>
</template>

<script>
import {
  getSystemConfig,
  updateSystemConfig
} from '@/api/system/sysConfig'
import { getPayConfig, savePayConfig } from '@/api/extend/mallConfig'
import BusinessConfigPanel from './business-config-panel'
import BasicConfigPanel from './basic-config-panel'
import SecurityConfigPanel from './security-config-panel'
import AiConfigPanel from './ai-config-panel'
import MallPayConfigPanel from './mall-pay-config-panel'
import SmsConfigPanel from './sms-config-panel'
import { normalizeBusinessConfigFields } from '@/utils/consume-admin-policy'
import { ossUploadFile } from '@/utils/oss-upload'
import { resolveBrandUrl } from '@/utils/sys-brand'
import getPageTitle from '@/utils/get-page-title'

export default {
  name: 'system-sysConfig',
  components: {
    BusinessConfigPanel,
    BasicConfigPanel,
    SecurityConfigPanel,
    AiConfigPanel,
    MallPayConfigPanel,
    SmsConfigPanel
  },
  data() {
    return {
      defaultParaformerSubmitUrl: 'https://dashscope.aliyuncs.com/api/v1/services/audio/asr/transcription',
      activeName: 'first',
      listLoading: false,
      btnLoading: false,
      logoUploading: false,
      logoIconUploading: false,
      mallPayLoading: false,
      mallPayReady: false,
      mallPayForm: {
        MchId: '',
        ApiV3Key: '',
        AppId: '',
        AppSecret: '',
        NotifyUrl: '',
        PayEnabled: '0'
      },
      baseForm: {
        sysName: '',
        sysLogo: '',
        sysLogoIcon: '',
        sysDescription: '',
        sysVersion: '',
        copyright: '',
        companyName: '',
        companyCode: '',
        companyAddress: '',
        companyContacts: '',
        companyTelePhone: '',
        companyEmail: '',
        singleLogin: '1',
        tokenTimeout: '1',
        lastLoginTimeSwitch: 1,
        whitelistSwitch: 1,
        watermarkSwitch: 0,
        watermarkOpacityPc: 10,
        watermarkOpacityApp: 14,
        privacyMobileMask: 0,
        whiteListIp: '',
        emailPop3Host: '',
        emailPop3Port: '110',
        emailSmtpHost: '',
        emailSmtpPort: '25',
        emailSenderName: '',
        emailAccount: '',
        emailPassword: '',
        emailSsl: 0,
        smsCompany: '1',
        smsKeyId: '',
        smsKeySecret: '',
        smsSignName: '',
        smsTemplateId: '',
        smsAppId: '',
        qyhCorpId: '',
        qyhAgentId: '',
        qyhAgentSecret: '',
        qyhCorpSecret: '',
        qyhIsSynOrg: 0,
        qyhIsSynUser: 0,
        dingSynAppKey: '',
        dingSynAppSecret: '',
        dingAgentId: '',
        dingSynIsSynOrg: 0,
        dingSynIsSynUser: 0,
        aiSpeechApiUrl: '',
        aiSpeechApiKey: '',
        aiSpeechModel: '',
        aiChatApiUrl: '',
        aiChatModel: 'qwen-plus',
        aiChatApiKey: '',
        aiSummarizeSystemPrompt: '',
        consumeEditableDays: 3,
        consumeAllowCrossMonthEdit: 0,
        adminConsumeVoidEditableDays: 0,
        adminConsumeDeleteEditableDays: 0,
        abnormalConsumeAmountTolerance: 0.01,
        tkGiftItemIds: '61',
        sleepRemindDays: 30,
        sleepReturnDays: 60,
        commissionTierHelpSwitch: 1,
        trainingCheckinQrRotateMinutes: 3,
        memberCustomTagMaxCount: 5,
        largeAmountBillingThreshold: 50000,
        largeAmountConsumeThreshold: 50000,
        largeAmountRefundThreshold: 50000,
        largeAmountTransferThreshold: 50000
      }
    }
  },
  created() {
    this.initData()
    this.initMallPayConfig()
  },
  methods: {
    // 加载会员商城支付配置(Category=MallPayment)
    initMallPayConfig() {
      this.mallPayLoading = true
      getPayConfig().then(res => {
        const data = (res && res.data) || {}
        this.mallPayForm = {
          MchId: data.mchId || '',
          ApiV3Key: data.apiV3Key || '',
          AppId: data.appId || '',
          AppSecret: data.appSecret || '',
          NotifyUrl: data.notifyUrl || '',
          PayEnabled: data.payEnabled === '1' || data.payEnabled === 1 ? '1' : '0'
        }
      }).catch(() => { }).finally(() => {
        this.mallPayLoading = false
        this.mallPayReady = true
      })
    },
    // 保存会员商城支付配置
    saveMallPay() {
      this.mallPayLoading = true
      savePayConfig({ ...this.mallPayForm }).then(res => {
        this.$message({
          message: (res && res.msg) || '保存成功',
          type: 'success',
          duration: 1500,
          onClose: () => {
            this.mallPayLoading = false
            this.initMallPayConfig()
          }
        })
      }).catch(() => {
        this.mallPayLoading = false
      })
    },
    initData() {
      this.listLoading = true
      this.$nextTick(() => {
        getSystemConfig().then(res => {
          this.baseForm = Object.assign({}, this.baseForm, res.data || {})
          if (!this.baseForm.aiChatModel) {
            this.baseForm.aiChatModel = 'qwen-plus'
          }
          this.baseForm.watermarkSwitch = Number(this.baseForm.watermarkSwitch) === 1 ? 1 : 0
          this.baseForm.watermarkOpacityPc = this.normalizeOpacity(this.baseForm.watermarkOpacityPc, 10)
          this.baseForm.watermarkOpacityApp = this.normalizeOpacity(this.baseForm.watermarkOpacityApp, 14)
          this.baseForm.privacyMobileMask = Number(this.baseForm.privacyMobileMask) === 1 ? 1 : 0
          this.baseForm.commissionTierHelpSwitch = this.baseForm.commissionTierHelpSwitch == null
            ? 1
            : (Number(this.baseForm.commissionTierHelpSwitch) === 1 ? 1 : 0)
          this.baseForm.consumeAllowCrossMonthEdit = this.baseForm.consumeAllowCrossMonthEdit == null
            ? 0
            : (Number(this.baseForm.consumeAllowCrossMonthEdit) === 1 ? 1 : 0)
          this.baseForm = normalizeBusinessConfigFields(this.baseForm)
          this.syncSysBrand()
          this.listLoading = false
        }).catch(() => {
          this.listLoading = false
        })
      })
    },
    logoPreview(url) {
      return resolveBrandUrl(url) || url
    },
    uploadSysLogo(options) {
      this.logoUploading = true
      ossUploadFile(options.file, 'annexpic').then(data => {
        this.logoUploading = false
        this.baseForm.sysLogo = data.url || data.name || ''
      }).catch(() => {
        this.logoUploading = false
        this.$message({ message: 'Logo 上传失败,请重试', type: 'error' })
      })
    },
    uploadSysLogoIcon(options) {
      this.logoIconUploading = true
      ossUploadFile(options.file, 'annexpic').then(data => {
        this.logoIconUploading = false
        this.baseForm.sysLogoIcon = data.url || data.name || ''
      }).catch(() => {
        this.logoIconUploading = false
        this.$message({ message: '折叠图标上传失败,请重试', type: 'error' })
      })
    },
    syncSysBrand() {
      this.$store.commit('settings/SET_SYS_BRAND', {
        sysName: this.baseForm.sysName,
        sysLogo: this.baseForm.sysLogo,
        sysLogoIcon: this.baseForm.sysLogoIcon,
        watermarkSwitch: Number(this.baseForm.watermarkSwitch) === 1 ? 1 : 0,
        watermarkOpacityPc: this.normalizeOpacity(this.baseForm.watermarkOpacityPc, 10),
        privacyMobileMask: Number(this.baseForm.privacyMobileMask) === 1 ? 1 : 0
      })
      document.title = getPageTitle(this.$route.meta.title, this.$route.meta.zhTitle)
    },
    normalizeOpacity(val, defaultVal) {
      const n = Number(val)
      if (!Number.isFinite(n)) return defaultVal
      if (n < 1) return 1
      if (n > 100) return 100
      return Math.round(n)
    },
    /** 开关切换后立刻同步到 Vuex,使全站水印即时显隐;持久化仍依赖「保存」 */
    onWatermarkSwitchChange() {
      this.syncSysBrand()
    },
    /** PC 深浅即时预览;持久化仍依赖「保存」 */
    onWatermarkOpacityChange() {
      this.baseForm.watermarkOpacityPc = this.normalizeOpacity(this.baseForm.watermarkOpacityPc, 10)
      this.syncSysBrand()
    },
    /** 隐私开关即时同步到 Vuex;持久化仍依赖「保存」 */
    onPrivacyMobileMaskChange() {
      this.syncSysBrand()
    },
    submitForm() {
      this.btnLoading = true
      const payload = normalizeBusinessConfigFields({ ...this.baseForm })
      payload.watermarkSwitch = Number(payload.watermarkSwitch) === 1 ? 1 : 0
      payload.watermarkOpacityPc = this.normalizeOpacity(payload.watermarkOpacityPc, 10)
      payload.watermarkOpacityApp = this.normalizeOpacity(payload.watermarkOpacityApp, 14)
      payload.privacyMobileMask = Number(payload.privacyMobileMask) === 1 ? 1 : 0
      payload.commissionTierHelpSwitch = payload.commissionTierHelpSwitch == null
        ? 1
        : (Number(payload.commissionTierHelpSwitch) === 1 ? 1 : 0)
      payload.consumeAllowCrossMonthEdit = payload.consumeAllowCrossMonthEdit == null
        ? 0
        : (Number(payload.consumeAllowCrossMonthEdit) === 1 ? 1 : 0)
      updateSystemConfig(payload).then(res => {
        this.syncSysBrand()
        this.$message({
          message: res.msg,
          type: 'success',
          duration: 1500,
          onClose: () => {
            this.btnLoading = false
            this.initData()
          }
        })
      }).catch(() => {
        this.btnLoading = false
      })
    }
  }
}
</script>
<style lang="scss" scoped>
.systemConfig {
  padding: 0;

  >>>.el-card__header {
    padding: 6px 20px;
  }

  >>>.el-tabs--border-card {
    box-shadow: none;
    height: calc(100vh - 120px);
  }

  >>>.el-tabs--border-card>.el-tabs__content {
    padding: 16px 20px 10px;
    height: calc(100% - 40px);
    overflow: hidden;
  }

  >>>.el-tab-pane {
    height: 100%;
  }

  .config-tab-panel {
    margin: 0;
    height: 100%;
    min-height: calc(100vh - 200px);
  }

  .saveBtn {
    width: 100px;
  }
}
</style>