|
@@ -0,0 +1,384 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="app-container">
|
|
|
|
|
+ <el-card v-loading="loading" shadow="never">
|
|
|
|
|
+ <div slot="header" class="page-header">
|
|
|
|
|
+ <span>AI配置</span>
|
|
|
|
|
+ <el-form :inline="true" size="small" class="tenant-form">
|
|
|
|
|
+ <el-form-item label="选择租户">
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ ref="tenantSelect"
|
|
|
|
|
+ v-model="selectedTenantId"
|
|
|
|
|
+ placeholder="请输入租户名称搜索"
|
|
|
|
|
+ filterable
|
|
|
|
|
+ remote
|
|
|
|
|
+ clearable
|
|
|
|
|
+ :remote-method="handleTenantSearch"
|
|
|
|
|
+ :loading="tenantSelectLoading"
|
|
|
|
|
+ style="width: 280px"
|
|
|
|
|
+ @visible-change="handleTenantDropdownVisible"
|
|
|
|
|
+ @clear="handleTenantSelectClear"
|
|
|
|
|
+ @change="loadConfig"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="item in tenantList"
|
|
|
|
|
+ :key="item.id"
|
|
|
|
|
+ :label="formatTenantLabel(item)"
|
|
|
|
|
+ :value="item.id"
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-option v-if="hasMoreTenants" key="tenant-load-more" disabled class="tenant-load-more-option">
|
|
|
|
|
+ <div class="load-more" @click.stop="loadMoreTenants">
|
|
|
|
|
+ <span>加载更多</span>
|
|
|
|
|
+ <i v-if="tenantLoadingMore" class="el-icon-loading" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-option>
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <el-alert
|
|
|
|
|
+ v-if="!selectedTenantId"
|
|
|
|
|
+ title="当前为系统默认配置(主库),保存后将更新全局配置;选择租户后可单独配置该租户参数"
|
|
|
|
|
+ type="info"
|
|
|
|
|
+ :closable="false"
|
|
|
|
|
+ show-icon
|
|
|
|
|
+ class="tenant-tip"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <el-alert
|
|
|
|
|
+ v-if="selectedTenantId && tenantConfigEmpty"
|
|
|
|
|
+ title="该租户尚未保存过此配置,可直接填写后保存;如需参考系统默认配置,可点击下方按钮加载(不会自动保存)。"
|
|
|
|
|
+ type="warning"
|
|
|
|
|
+ :closable="false"
|
|
|
|
|
+ show-icon
|
|
|
|
|
+ class="tenant-tip"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-button type="text" size="small" :loading="fallbackLoading" @click="loadGlobalConfigAsFallback">
|
|
|
|
|
+ 加载系统默认配置
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </el-alert>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form ref="form" :model="form" label-width="200px">
|
|
|
|
|
+ <!-- iPad服务配置 -->
|
|
|
|
|
+ <el-card shadow="never" class="section-card">
|
|
|
|
|
+ <div slot="header"><span>iPad服务配置</span></div>
|
|
|
|
|
+ <el-row :gutter="20">
|
|
|
|
|
+ <el-col :span="12">
|
|
|
|
|
+ <el-form-item label="URL" prop="url">
|
|
|
|
|
+ <el-input v-model="form.ipad.url" placeholder="请输入URL地址" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ <el-col :span="12">
|
|
|
|
|
+ <el-form-item label="iPad URL" prop="ipadUrl">
|
|
|
|
|
+ <el-input v-model="form.ipad.ipadUrl" placeholder="请输入iPad URL地址" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+ <el-row :gutter="20">
|
|
|
|
|
+ <el-col :span="12">
|
|
|
|
|
+ <el-form-item label="AI Api" prop="aiApi">
|
|
|
|
|
+ <el-input v-model="form.ipad.aiApi" placeholder="请输入AI接口地址" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ <el-col :span="12">
|
|
|
|
|
+ <el-form-item label="Voice Api" prop="voiceApi">
|
|
|
|
|
+ <el-input v-model="form.ipad.voiceApi" placeholder="请输入语音接口地址" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+ <el-row :gutter="20">
|
|
|
|
|
+ <el-col :span="12">
|
|
|
|
|
+ <el-form-item label="Common Api" prop="commonApi">
|
|
|
|
|
+ <el-input v-model="form.ipad.commonApi" placeholder="请输入通用接口地址" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+
|
|
|
|
|
+ <div style="text-align: center; margin-top: 20px; padding-bottom: 20px;">
|
|
|
|
|
+ <el-button type="primary" size="medium" @click="submitForm" :loading="submitLoading">保存配置</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script>
|
|
|
|
|
+import { getConfigByKey, updateConfigByKey } from '@/api/system/config'
|
|
|
|
|
+import { listAdminTenantList } from '@/api/admin/sysCompany'
|
|
|
|
|
+
|
|
|
|
|
+const defaultForm = () => ({
|
|
|
|
|
+ ipad: {
|
|
|
|
|
+ url: '',
|
|
|
|
|
+ ipadUrl: '',
|
|
|
|
|
+ aiApi: '',
|
|
|
|
|
+ voiceApi: '',
|
|
|
|
|
+ commonApi: ''
|
|
|
|
|
+ }
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+function normalizeForm(raw) {
|
|
|
|
|
+ if (!raw || typeof raw !== 'object') {
|
|
|
|
|
+ return defaultForm()
|
|
|
|
|
+ }
|
|
|
|
|
+ const form = defaultForm()
|
|
|
|
|
+ if (raw.ipad && typeof raw.ipad === 'object') {
|
|
|
|
|
+ form.ipad.url = raw.ipad.url || ''
|
|
|
|
|
+ form.ipad.ipadUrl = raw.ipad.ipadUrl || ''
|
|
|
|
|
+ form.ipad.aiApi = raw.ipad.aiApi || ''
|
|
|
|
|
+ form.ipad.voiceApi = raw.ipad.voiceApi || ''
|
|
|
|
|
+ form.ipad.commonApi = raw.ipad.commonApi || ''
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 兼容旧版平铺字段
|
|
|
|
|
+ form.ipad.url = raw.url || ''
|
|
|
|
|
+ form.ipad.ipadUrl = raw.ipadUrl || ''
|
|
|
|
|
+ form.ipad.aiApi = raw.aiApi || ''
|
|
|
|
|
+ form.ipad.voiceApi = raw.voiceApi || ''
|
|
|
|
|
+ form.ipad.commonApi = raw.commonApi || ''
|
|
|
|
|
+ }
|
|
|
|
|
+ return form
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export default {
|
|
|
|
|
+ name: 'AdminAiFrontConfig',
|
|
|
|
|
+ data() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ loading: false,
|
|
|
|
|
+ submitLoading: false,
|
|
|
|
|
+ configId: null,
|
|
|
|
|
+ configKey: 'his.adminUi.aiConfig',
|
|
|
|
|
+ selectedTenantId: null,
|
|
|
|
|
+ tenantConfigEmpty: false,
|
|
|
|
|
+ fallbackLoading: false,
|
|
|
|
|
+ tenantList: [],
|
|
|
|
|
+ tenantTotal: 0,
|
|
|
|
|
+ hasMoreTenants: false,
|
|
|
|
|
+ tenantSelectLoading: false,
|
|
|
|
|
+ tenantLoadingMore: false,
|
|
|
|
|
+ tenantQueryParams: {
|
|
|
|
|
+ pageNum: 1,
|
|
|
|
|
+ pageSize: 20,
|
|
|
|
|
+ tenantName: '',
|
|
|
|
|
+ status: 1
|
|
|
|
|
+ },
|
|
|
|
|
+ form: defaultForm(),
|
|
|
|
|
+ tenantSearchTimer: null,
|
|
|
|
|
+ tenantSelectInputEl: null,
|
|
|
|
|
+ tenantSelectInputHandler: null
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ mounted() {
|
|
|
|
|
+ this.loadConfig()
|
|
|
|
|
+ },
|
|
|
|
|
+ beforeDestroy() {
|
|
|
|
|
+ this.unbindTenantSelectInputListener()
|
|
|
|
|
+ },
|
|
|
|
|
+ methods: {
|
|
|
|
|
+ formatTenantLabel(item) {
|
|
|
|
|
+ if (item.tenantCode) {
|
|
|
|
|
+ return `${item.tenantName}(${item.tenantCode})`
|
|
|
|
|
+ }
|
|
|
|
|
+ return item.tenantName
|
|
|
|
|
+ },
|
|
|
|
|
+ handleTenantSearch(query) {
|
|
|
|
|
+ if (query === undefined) return
|
|
|
|
|
+ this.tenantQueryParams.tenantName = (query || '').trim()
|
|
|
|
|
+ this.tenantQueryParams.pageNum = 1
|
|
|
|
|
+ this.fetchTenantList()
|
|
|
|
|
+ },
|
|
|
|
|
+ handleTenantSelectClear() {
|
|
|
|
|
+ this.handleTenantSearch('')
|
|
|
|
|
+ },
|
|
|
|
|
+ getTenantSelectInputEl() {
|
|
|
|
|
+ const root = this.$refs.tenantSelect && this.$refs.tenantSelect.$el
|
|
|
|
|
+ return root ? root.querySelector('input.el-input__inner') : null
|
|
|
|
|
+ },
|
|
|
|
|
+ bindTenantSelectInputListener() {
|
|
|
|
|
+ this.unbindTenantSelectInputListener()
|
|
|
|
|
+ const input = this.getTenantSelectInputEl()
|
|
|
|
|
+ if (!input) return
|
|
|
|
|
+ this.tenantSelectInputEl = input
|
|
|
|
|
+ this.tenantSelectInputHandler = () => {
|
|
|
|
|
+ const val = (input.value || '').trim()
|
|
|
|
|
+ if (!val && this.tenantQueryParams.tenantName) {
|
|
|
|
|
+ if (this.tenantSearchTimer) clearTimeout(this.tenantSearchTimer)
|
|
|
|
|
+ this.tenantSearchTimer = setTimeout(() => {
|
|
|
|
|
+ this.handleTenantSearch('')
|
|
|
|
|
+ }, 150)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ input.addEventListener('input', this.tenantSelectInputHandler)
|
|
|
|
|
+ },
|
|
|
|
|
+ unbindTenantSelectInputListener() {
|
|
|
|
|
+ if (this.tenantSearchTimer) {
|
|
|
|
|
+ clearTimeout(this.tenantSearchTimer)
|
|
|
|
|
+ this.tenantSearchTimer = null
|
|
|
|
|
+ }
|
|
|
|
|
+ if (this.tenantSelectInputEl && this.tenantSelectInputHandler) {
|
|
|
|
|
+ this.tenantSelectInputEl.removeEventListener('input', this.tenantSelectInputHandler)
|
|
|
|
|
+ }
|
|
|
|
|
+ this.tenantSelectInputEl = null
|
|
|
|
|
+ this.tenantSelectInputHandler = null
|
|
|
|
|
+ },
|
|
|
|
|
+ syncTenantSearchFromInput() {
|
|
|
|
|
+ const input = this.getTenantSelectInputEl()
|
|
|
|
|
+ const inputVal = input ? (input.value || '').trim() : ''
|
|
|
|
|
+ if (!inputVal && this.tenantQueryParams.tenantName) {
|
|
|
|
|
+ this.handleTenantSearch('')
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ handleTenantDropdownVisible(visible) {
|
|
|
|
|
+ if (visible) {
|
|
|
|
|
+ if (this.tenantList.length === 0) {
|
|
|
|
|
+ this.handleTenantSearch('')
|
|
|
|
|
+ }
|
|
|
|
|
+ this.$nextTick(() => {
|
|
|
|
|
+ this.syncTenantSearchFromInput()
|
|
|
|
|
+ this.bindTenantSelectInputListener()
|
|
|
|
|
+ })
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.unbindTenantSelectInputListener()
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ fetchTenantList(isLoadMore = false) {
|
|
|
|
|
+ if (!isLoadMore) {
|
|
|
|
|
+ this.tenantSelectLoading = true
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.tenantLoadingMore = true
|
|
|
|
|
+ }
|
|
|
|
|
+ listAdminTenantList(this.tenantQueryParams).then(response => {
|
|
|
|
|
+ const rows = response.rows || []
|
|
|
|
|
+ if (isLoadMore) {
|
|
|
|
|
+ const existIds = new Set(this.tenantList.map(t => t.id))
|
|
|
|
|
+ const append = rows.filter(r => !existIds.has(r.id))
|
|
|
|
|
+ this.tenantList = this.tenantList.concat(append)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.tenantList = rows
|
|
|
|
|
+ }
|
|
|
|
|
+ this.tenantTotal = response.total || 0
|
|
|
|
|
+ this.hasMoreTenants = this.tenantList.length < this.tenantTotal
|
|
|
|
|
+ }).finally(() => {
|
|
|
|
|
+ this.tenantSelectLoading = false
|
|
|
|
|
+ this.tenantLoadingMore = false
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ loadMoreTenants() {
|
|
|
|
|
+ if (this.tenantLoadingMore || !this.hasMoreTenants) return
|
|
|
|
|
+ this.tenantQueryParams.pageNum += 1
|
|
|
|
|
+ this.fetchTenantList(true)
|
|
|
|
|
+ },
|
|
|
|
|
+ resetForm() {
|
|
|
|
|
+ this.form = defaultForm()
|
|
|
|
|
+ this.configId = null
|
|
|
|
|
+ this.tenantConfigEmpty = false
|
|
|
|
|
+ },
|
|
|
|
|
+ loadConfig() {
|
|
|
|
|
+ this.loading = true
|
|
|
|
|
+ this.tenantConfigEmpty = false
|
|
|
|
|
+ const tenantId = this.selectedTenantId || undefined
|
|
|
|
|
+ getConfigByKey(this.configKey, tenantId).then(response => {
|
|
|
|
|
+ const data = response.data
|
|
|
|
|
+ if (data) {
|
|
|
|
|
+ this.configId = data.configId != null ? data.configId : null
|
|
|
|
|
+ if (data.configValue) {
|
|
|
|
|
+ this.tenantConfigEmpty = false
|
|
|
|
|
+ try {
|
|
|
|
|
+ this.form = normalizeForm(JSON.parse(data.configValue))
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ this.form = defaultForm()
|
|
|
|
|
+ this.tenantConfigEmpty = !!this.selectedTenantId
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.form = defaultForm()
|
|
|
|
|
+ this.tenantConfigEmpty = !!this.selectedTenantId
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.configId = null
|
|
|
|
|
+ this.form = defaultForm()
|
|
|
|
|
+ this.tenantConfigEmpty = !!this.selectedTenantId
|
|
|
|
|
+ }
|
|
|
|
|
+ }).finally(() => {
|
|
|
|
|
+ this.loading = false
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ loadGlobalConfigAsFallback() {
|
|
|
|
|
+ this.fallbackLoading = true
|
|
|
|
|
+ getConfigByKey(this.configKey).then(response => {
|
|
|
|
|
+ if (!response.data || !response.data.configValue) {
|
|
|
|
|
+ this.msgWarning('系统默认配置为空')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ this.form = normalizeForm(JSON.parse(response.data.configValue))
|
|
|
|
|
+ this.msgSuccess('已加载系统默认配置,保存时将写入当前租户')
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ this.msgWarning('系统默认配置解析失败')
|
|
|
|
|
+ }
|
|
|
|
|
+ }).finally(() => {
|
|
|
|
|
+ this.fallbackLoading = false
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ submitForm() {
|
|
|
|
|
+ this.submitLoading = true
|
|
|
|
|
+ const param = {
|
|
|
|
|
+ configId: (this.configId != null && this.configId !== '') ? Number(this.configId) : null,
|
|
|
|
|
+ configName: 'AI配置',
|
|
|
|
|
+ configKey: this.configKey,
|
|
|
|
|
+ configValue: JSON.stringify(this.form),
|
|
|
|
|
+ tenantId: this.selectedTenantId ? String(this.selectedTenantId) : null
|
|
|
|
|
+ }
|
|
|
|
|
+ updateConfigByKey(param).then(response => {
|
|
|
|
|
+ if (response.code === 200) {
|
|
|
|
|
+ this.msgSuccess('修改成功')
|
|
|
|
|
+ this.loadConfig()
|
|
|
|
|
+ }
|
|
|
|
|
+ }).finally(() => {
|
|
|
|
|
+ this.submitLoading = false
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.section-card {
|
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.page-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.tenant-form {
|
|
|
|
|
+ margin-bottom: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.tenant-form >>> .el-form-item {
|
|
|
|
|
+ margin-bottom: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.tenant-tip {
|
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.tenant-load-more-option {
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ padding: 8px 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.load-more {
|
|
|
|
|
+ color: #409eff;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ gap: 5px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.load-more:hover {
|
|
|
|
|
+ color: #66b1ff;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|