|
|
@@ -1,137 +1,219 @@
|
|
|
<template>
|
|
|
<ContentWrap title="机构公章管理">
|
|
|
- <div v-loading="loading" class="seal-container">
|
|
|
- <el-alert type="info" :closable="false" show-icon class="mb-20px">
|
|
|
- 当前机构:<strong>{{ tenantName || '—' }}</strong>,公章仅对本机构生效。
|
|
|
- </el-alert>
|
|
|
-
|
|
|
- <div class="seal-box">
|
|
|
- <UploadImg
|
|
|
- v-model="sealUrl"
|
|
|
- v-model:fileName="fileName"
|
|
|
- :width="'220px'"
|
|
|
- :height="'220px'"
|
|
|
- :show-delete="false"
|
|
|
- />
|
|
|
+ <template #header>
|
|
|
+ <div class="seal-header">
|
|
|
+ <el-button :loading="loading" @click="handleRefresh">
|
|
|
+ <Icon icon="ep:refresh" class="mr-5px" />刷新
|
|
|
+ </el-button>
|
|
|
</div>
|
|
|
+ </template>
|
|
|
+ <el-alert type="info" :closable="false" show-icon class="mb-20px">
|
|
|
+ 当前机构:<strong>{{ tenantName || '—' }}</strong
|
|
|
+ >,公章仅对本机构生效,每个类型只能上传一枚公章。
|
|
|
+ </el-alert>
|
|
|
+
|
|
|
+ <div v-loading="loading" class="seal-container">
|
|
|
+ <el-empty
|
|
|
+ v-if="!typeOptions.length"
|
|
|
+ description="未获取到公章类型字典(common_seal),请先配置字典"
|
|
|
+ />
|
|
|
+
|
|
|
+ <div v-for="opt in typeOptions" :key="opt.value" class="seal-card">
|
|
|
+ <div class="seal-card-title">{{ opt.label }}</div>
|
|
|
|
|
|
- <div class="seal-tip">支持 jpg / png / gif,大小不超过 5MB。点击图片即可上传或替换。</div>
|
|
|
+ <div class="seal-box">
|
|
|
+ <UploadImg
|
|
|
+ v-model="formMap[opt.value].url"
|
|
|
+ v-model:fileName="formMap[opt.value].fileName"
|
|
|
+ :width="'220px'"
|
|
|
+ :height="'220px'"
|
|
|
+ :show-delete="false"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
|
|
|
- <div class="seal-actions">
|
|
|
- <el-button type="primary" :loading="saving" @click="handleSave">保存</el-button>
|
|
|
- <el-button type="danger" plain @click="handleDelete">删除公章</el-button>
|
|
|
+ <div class="seal-tip">支持 jpg / png / gif,大小不超过 5MB。点击图片即可上传或替换。</div>
|
|
|
+
|
|
|
+ <div class="seal-actions">
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ :loading="savingType === opt.value"
|
|
|
+ @click="handleSave(opt.value)"
|
|
|
+ >
|
|
|
+ 保存
|
|
|
+ </el-button>
|
|
|
+ <el-button type="danger" plain :disabled="!formMap[opt.value].id" @click="handleDelete(opt.value)">
|
|
|
+ 删除公章
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</ContentWrap>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
-import { ref, computed, onMounted } from 'vue'
|
|
|
+import { ref, reactive, computed, onMounted } from 'vue'
|
|
|
import { ElMessageBox } from 'element-plus'
|
|
|
import { UploadImg } from '@/components/UploadFile'
|
|
|
import { getTenantId } from '@/utils/auth'
|
|
|
import { useUserStore } from '@/store/modules/user'
|
|
|
+import { useDictStore } from '@/store/modules/dict'
|
|
|
import { useMessage } from '@/hooks/web/useMessage'
|
|
|
-import { getTenant, updateTenant, type TenantVO } from '@/api/system/tenant'
|
|
|
+import { getStrDictOptions, DICT_TYPE, type StringDictDataType } from '@/utils/dict'
|
|
|
+import {
|
|
|
+ getCommonSealList,
|
|
|
+ createCommonSeal,
|
|
|
+ updateCommonSeal,
|
|
|
+ deleteCommonSeal,
|
|
|
+ type CommonSealVO
|
|
|
+} from '@/api/system/commonSeal'
|
|
|
+
|
|
|
+defineOptions({ name: 'OfficialSeal' })
|
|
|
+
|
|
|
+/** 单个类型公章的编辑态 */
|
|
|
+interface SealForm {
|
|
|
+ id?: number
|
|
|
+ url: string
|
|
|
+ fileName: string
|
|
|
+}
|
|
|
|
|
|
const message = useMessage()
|
|
|
const userStore = useUserStore()
|
|
|
-const tenantId = getTenantId()
|
|
|
+const dictStore = useDictStore()
|
|
|
+const tenantId = getTenantId() as number
|
|
|
const tenantName = computed(() => userStore.tenant?.name || '')
|
|
|
|
|
|
const loading = ref(false)
|
|
|
-const saving = ref(false)
|
|
|
-const sealUrl = ref('')
|
|
|
-const fileName = ref('')
|
|
|
-
|
|
|
-// 当前租户全量信息(保存 / 删除时要带回全部字段,避免被后端清空)
|
|
|
-const currentTenant = ref<TenantVO | null>(null)
|
|
|
-
|
|
|
-// 演示用占位公章:用于租户尚未上传时的占位预览
|
|
|
-const DEMO_SEAL =
|
|
|
- 'data:image/svg+xml;utf8,' +
|
|
|
- encodeURIComponent(
|
|
|
- '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200"><circle cx="100" cy="100" r="92" fill="none" stroke="#c0392b" stroke-width="6"/><circle cx="100" cy="100" r="76" fill="none" stroke="#c0392b" stroke-width="2"/><text x="100" y="108" font-size="22" fill="#c0392b" text-anchor="middle" font-family="serif">公章示例</text></svg>'
|
|
|
- )
|
|
|
-
|
|
|
-/** 从租户接口读取当前机构公章(字段 expenseCachetUrl) */
|
|
|
-const loadSeal = async () => {
|
|
|
+const savingType = ref('')
|
|
|
+
|
|
|
+/** 公章类型(字典 common_seal) */
|
|
|
+const typeOptions = ref<StringDictDataType[]>([])
|
|
|
+/** 各类型公章编辑数据,key 为字典值 */
|
|
|
+const formMap = reactive<Record<string, SealForm>>({})
|
|
|
+
|
|
|
+/** 按字典构建表单结构,保证模板中 formMap[type] 一定存在 */
|
|
|
+const buildForms = () => {
|
|
|
+ typeOptions.value = getStrDictOptions(DICT_TYPE.COMMON_SEAL)
|
|
|
+ typeOptions.value.forEach((opt) => {
|
|
|
+ if (!formMap[opt.value]) {
|
|
|
+ formMap[opt.value] = { id: undefined, url: '', fileName: '' }
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+/** 拉取当前机构公章列表,按类型回填 */
|
|
|
+const loadSealList = async () => {
|
|
|
loading.value = true
|
|
|
try {
|
|
|
- const data: any = await getTenant(tenantId as number)
|
|
|
- currentTenant.value = data
|
|
|
- sealUrl.value = data?.expenseCachetUrl || ''
|
|
|
- fileName.value = ''
|
|
|
+ const list: CommonSealVO[] = (await getCommonSealList({ tenantId })) || []
|
|
|
+ Object.keys(formMap).forEach((key) => {
|
|
|
+ formMap[key].id = undefined
|
|
|
+ formMap[key].url = ''
|
|
|
+ formMap[key].fileName = ''
|
|
|
+ })
|
|
|
+ list.forEach((item) => {
|
|
|
+ const key = item?.type || ''
|
|
|
+ if (!key) return
|
|
|
+ if (!formMap[key]) {
|
|
|
+ formMap[key] = { id: undefined, url: '', fileName: '' }
|
|
|
+ }
|
|
|
+ formMap[key].id = item.id
|
|
|
+ formMap[key].url = item.url || ''
|
|
|
+ })
|
|
|
} catch {
|
|
|
- sealUrl.value = DEMO_SEAL
|
|
|
- message.notifyWarning('租户接口读取失败,当前显示演示数据')
|
|
|
+ message.error('公章列表加载失败,请稍后重试')
|
|
|
} finally {
|
|
|
loading.value = false
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-const handleSave = async () => {
|
|
|
- if (!sealUrl.value || sealUrl.value === DEMO_SEAL) {
|
|
|
+/** 手动刷新:重新拉取列表,避免新增 / 更新后数据延迟 */
|
|
|
+const handleRefresh = async () => {
|
|
|
+ await loadSealList()
|
|
|
+ message.success('已刷新')
|
|
|
+}
|
|
|
+
|
|
|
+/** 保存:已存在则更新,否则创建 */
|
|
|
+const handleSave = async (type: string) => {
|
|
|
+ const form = formMap[type]
|
|
|
+ if (!form?.url) {
|
|
|
message.error('请先上传公章图片')
|
|
|
return
|
|
|
}
|
|
|
- if (!currentTenant.value) {
|
|
|
- message.error('租户信息未加载,请刷新页面后重试')
|
|
|
- return
|
|
|
- }
|
|
|
- saving.value = true
|
|
|
+ savingType.value = type
|
|
|
try {
|
|
|
- // 全量带回其他字段,只覆盖 expenseCachetUrl
|
|
|
- await updateTenant({
|
|
|
- ...currentTenant.value,
|
|
|
- expenseCachetUrl: sealUrl.value
|
|
|
- })
|
|
|
+ if (form.id) {
|
|
|
+ await updateCommonSeal({ id: form.id, type, url: form.url, tenantId })
|
|
|
+ } else {
|
|
|
+ await createCommonSeal({ type, url: form.url, tenantId })
|
|
|
+ }
|
|
|
message.success('公章已保存')
|
|
|
+ await loadSealList()
|
|
|
} catch {
|
|
|
- message.error('保存失败,请稍后重试')
|
|
|
+ // 失败提示由请求拦截器统一处理
|
|
|
} finally {
|
|
|
- saving.value = false
|
|
|
+ savingType.value = ''
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-const handleDelete = async () => {
|
|
|
- if (!sealUrl.value || sealUrl.value === DEMO_SEAL) {
|
|
|
+/** 删除公章 */
|
|
|
+const handleDelete = async (type: string) => {
|
|
|
+ const form = formMap[type]
|
|
|
+ if (!form?.id) {
|
|
|
message.warning('当前没有可删除的公章')
|
|
|
return
|
|
|
}
|
|
|
- if (!currentTenant.value) {
|
|
|
- message.error('租户信息未加载,请刷新页面后重试')
|
|
|
- return
|
|
|
- }
|
|
|
try {
|
|
|
- await ElMessageBox.confirm('确认删除当前机构公章?删除后需重新上传。', '提示', {
|
|
|
+ await ElMessageBox.confirm('确认删除该类型公章?删除后需重新上传。', '提示', {
|
|
|
type: 'warning'
|
|
|
})
|
|
|
} catch {
|
|
|
return
|
|
|
}
|
|
|
try {
|
|
|
- // 后端无 delete 接口:"删除"= update 时把字段清空
|
|
|
- await updateTenant({
|
|
|
- ...currentTenant.value,
|
|
|
- expenseCachetUrl: ''
|
|
|
- })
|
|
|
- sealUrl.value = ''
|
|
|
- fileName.value = ''
|
|
|
+ await deleteCommonSeal(form.id)
|
|
|
+ form.id = undefined
|
|
|
+ form.url = ''
|
|
|
+ form.fileName = ''
|
|
|
message.success('公章已删除')
|
|
|
} catch {
|
|
|
- message.error('删除失败,请稍后重试')
|
|
|
+ // 失败提示由请求拦截器统一处理
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-onMounted(loadSeal)
|
|
|
+onMounted(async () => {
|
|
|
+ // 字典可能尚未加载(或新增了 common_seal),兜底重新拉取一次
|
|
|
+ if (!getStrDictOptions(DICT_TYPE.COMMON_SEAL).length) {
|
|
|
+ await dictStore.resetDict()
|
|
|
+ }
|
|
|
+ buildForms()
|
|
|
+ await loadSealList()
|
|
|
+})
|
|
|
</script>
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
+.seal-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
.seal-container {
|
|
|
- max-width: 480px;
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 24px;
|
|
|
padding: 10px 0;
|
|
|
}
|
|
|
+.seal-card {
|
|
|
+ width: 280px;
|
|
|
+ padding: 16px;
|
|
|
+ border: 1px solid var(--el-border-color-lighter);
|
|
|
+ border-radius: 8px;
|
|
|
+}
|
|
|
+.seal-card-title {
|
|
|
+ margin-bottom: 12px;
|
|
|
+ font-size: 15px;
|
|
|
+ font-weight: 600;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
.seal-box {
|
|
|
display: flex;
|
|
|
justify-content: center;
|