feat(web-admin): register receiveAccount FormCreate control.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
4e805f9625
commit
cf61ce340d
7 files changed
+435
No files matched your search
@@ -71,6 +71,50 @@ export function fetchReplaceOaRoleUsers(roleId: string, userIds: string[]) {
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 收款账户
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function fetchOaReceiveAccountList(params?: {
|
||||
keyword?: string
|
||||
accountType?: string
|
||||
page?: number
|
||||
pageSize?: number
|
||||
}) {
|
||||
return request.get<{ list: any[]; total: number }>({
|
||||
url: `${BASE}/receive-accounts`,
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function fetchOaReceiveAccountOptions() {
|
||||
return request.get<{ list: any[] }>({ url: `${BASE}/receive-accounts/options` })
|
||||
}
|
||||
|
||||
export function fetchOaReceiveAccountDetail(id: string) {
|
||||
return request.get<any>({ url: `${BASE}/receive-accounts/${id}` })
|
||||
}
|
||||
|
||||
export function fetchCreateOaReceiveAccount(data: Record<string, unknown>) {
|
||||
return request.post({ url: `${BASE}/receive-accounts`, data, showSuccessMessage: true })
|
||||
}
|
||||
|
||||
export function fetchUpdateOaReceiveAccount(id: string, data: Record<string, unknown>) {
|
||||
return request.put({ url: `${BASE}/receive-accounts/${id}`, data, showSuccessMessage: true })
|
||||
}
|
||||
|
||||
export function fetchDeleteOaReceiveAccount(id: string) {
|
||||
return request.del({ url: `${BASE}/receive-accounts/${id}`, showSuccessMessage: true })
|
||||
}
|
||||
|
||||
export function fetchBatchDeleteOaReceiveAccounts(ids: string[]) {
|
||||
return request.post({
|
||||
url: `${BASE}/receive-accounts/batch-delete`,
|
||||
data: { ids },
|
||||
showSuccessMessage: true
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 分组 / 应用 / 草稿 / 发布
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
<!-- FormCreate 自定义控件:收款账户选择(Fresh Mint · PC 下拉 + 窄屏抽屉;预览画布全宽) -->
|
||||
<template>
|
||||
<div class="oa-receive-account-select">
|
||||
<template v-if="disabled">
|
||||
<span class="oa-receive-account-select__readonly">{{ displayText || '—' }}</span>
|
||||
</template>
|
||||
|
||||
<template v-else-if="isNarrow">
|
||||
<button type="button" class="oa-receive-account-select__trigger" @click="openDrawer">
|
||||
<span v-if="displayText" class="oa-receive-account-select__trigger-text">{{
|
||||
displayText
|
||||
}}</span>
|
||||
<span v-else class="oa-receive-account-select__placeholder">请选择收款账户</span>
|
||||
<span class="oa-receive-account-select__chev">▾</span>
|
||||
</button>
|
||||
<el-drawer
|
||||
v-model="drawerVisible"
|
||||
title="选择收款账户"
|
||||
direction="btt"
|
||||
size="75%"
|
||||
append-to-body
|
||||
:close-on-click-modal="false"
|
||||
class="oa-receive-account-drawer"
|
||||
modal-class="protocol-dialog-mask"
|
||||
>
|
||||
<el-input
|
||||
v-model="keyword"
|
||||
clearable
|
||||
placeholder="搜索账户名称或账号"
|
||||
class="oa-receive-account-select__search"
|
||||
/>
|
||||
<div class="oa-receive-account-select__list">
|
||||
<label
|
||||
v-for="a in filteredOptions"
|
||||
:key="a.id"
|
||||
class="oa-receive-account-select__row"
|
||||
>
|
||||
<el-checkbox
|
||||
:model-value="pendingIds.includes(a.id)"
|
||||
@change="(v: boolean | string | number) => onNarrowCheck(a.id, !!v)"
|
||||
/>
|
||||
<span class="oa-receive-account-select__row-label">{{ a.label }}</span>
|
||||
</label>
|
||||
<div v-if="!filteredOptions.length" class="oa-receive-account-select__empty">
|
||||
暂无收款账户
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="drawerVisible = false">
|
||||
<el-icon><Close /></el-icon>
|
||||
取消
|
||||
</el-button>
|
||||
<el-button type="primary" @click="confirmDrawer">
|
||||
<el-icon><Check /></el-icon>
|
||||
确定
|
||||
</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<el-select
|
||||
v-else
|
||||
:model-value="selectModel"
|
||||
class="oa-receive-account-select__select"
|
||||
filterable
|
||||
clearable
|
||||
multiple
|
||||
placeholder="请选择收款账户"
|
||||
@update:model-value="onSelectChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="a in accountOptions"
|
||||
:key="a.id"
|
||||
:label="a.label"
|
||||
:value="a.id"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import { Check, Close } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { fetchOaReceiveAccountOptions } from '@/api/oaApproval'
|
||||
import {
|
||||
formatReceiveAccountDisplay,
|
||||
normalizeReceiveAccountModelValue,
|
||||
optionLabelFromRow,
|
||||
snapshotFromRow,
|
||||
type ReceiveAccountSnapshot
|
||||
} from '@/utils/oa-receive-account-value'
|
||||
|
||||
defineOptions({ name: 'receiveAccount' })
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue?: unknown
|
||||
disabled?: boolean
|
||||
formCreateInject?: Record<string, unknown>
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', v: ReceiveAccountSnapshot[]): void
|
||||
(e: 'change', v: ReceiveAccountSnapshot[]): void
|
||||
}>()
|
||||
|
||||
type AccountOpt = { id: string; label: string }
|
||||
|
||||
const snapshotMap = ref(new Map<string, ReceiveAccountSnapshot>())
|
||||
const accountOptions = ref<AccountOpt[]>([])
|
||||
const isNarrow = ref(false)
|
||||
const drawerVisible = ref(false)
|
||||
const keyword = ref('')
|
||||
const pendingIds = ref<string[]>([])
|
||||
let mql: MediaQueryList | null = null
|
||||
|
||||
const normalized = computed(() => normalizeReceiveAccountModelValue(props.modelValue))
|
||||
|
||||
const displayText = computed(() => formatReceiveAccountDisplay(normalized.value))
|
||||
|
||||
const selectModel = computed(() => normalized.value.map((x) => x.id))
|
||||
|
||||
const filteredOptions = computed(() => {
|
||||
const q = keyword.value.trim().toLowerCase()
|
||||
if (!q) return accountOptions.value
|
||||
return accountOptions.value.filter(
|
||||
(a) => a.label.toLowerCase().includes(q) || a.id.toLowerCase().includes(q)
|
||||
)
|
||||
})
|
||||
|
||||
function emitFromIds(ids: string[]) {
|
||||
const items: ReceiveAccountSnapshot[] = []
|
||||
const seen = new Set<string>()
|
||||
const prevMap = new Map(normalized.value.map((x) => [x.id, x]))
|
||||
for (const id of ids) {
|
||||
const tid = String(id || '').trim()
|
||||
if (!tid || seen.has(tid)) continue
|
||||
seen.add(tid)
|
||||
const snap = snapshotMap.value.get(tid) || prevMap.get(tid)
|
||||
if (snap) items.push(snap)
|
||||
}
|
||||
emit('update:modelValue', items)
|
||||
emit('change', items)
|
||||
}
|
||||
|
||||
function onSelectChange(val: string | string[] | null | undefined) {
|
||||
emitFromIds(Array.isArray(val) ? val.map(String) : [])
|
||||
}
|
||||
|
||||
function openDrawer() {
|
||||
pendingIds.value = normalized.value.map((x) => x.id)
|
||||
keyword.value = ''
|
||||
drawerVisible.value = true
|
||||
}
|
||||
|
||||
function onNarrowCheck(id: string, checked: boolean) {
|
||||
const set = new Set(pendingIds.value)
|
||||
if (checked) set.add(id)
|
||||
else set.delete(id)
|
||||
pendingIds.value = [...set]
|
||||
}
|
||||
|
||||
function confirmDrawer() {
|
||||
emitFromIds(pendingIds.value)
|
||||
drawerVisible.value = false
|
||||
}
|
||||
|
||||
async function loadOptions() {
|
||||
try {
|
||||
const res = await fetchOaReceiveAccountOptions()
|
||||
const list = res?.list || []
|
||||
const map = new Map<string, ReceiveAccountSnapshot>()
|
||||
const opts: AccountOpt[] = []
|
||||
for (const row of list) {
|
||||
const snap = snapshotFromRow(row)
|
||||
if (!snap.id) continue
|
||||
map.set(snap.id, snap)
|
||||
opts.push({ id: snap.id, label: optionLabelFromRow(snap) })
|
||||
}
|
||||
// 保留已选快照(主数据可能已删除该账户)
|
||||
for (const x of normalized.value) {
|
||||
if (!map.has(x.id)) map.set(x.id, x)
|
||||
}
|
||||
snapshotMap.value = map
|
||||
accountOptions.value = opts
|
||||
} catch {
|
||||
snapshotMap.value = new Map()
|
||||
accountOptions.value = []
|
||||
ElMessage.error('加载收款账户选项失败')
|
||||
}
|
||||
}
|
||||
|
||||
function syncNarrow(e?: MediaQueryList | MediaQueryListEvent) {
|
||||
const matches = e && 'matches' in e ? e.matches : !!mql?.matches
|
||||
isNarrow.value = matches
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadOptions()
|
||||
mql = window.matchMedia('(max-width: 767px)')
|
||||
syncNarrow(mql)
|
||||
mql.addEventListener('change', syncNarrow)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
mql?.removeEventListener('change', syncNarrow)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.oa-receive-account-select {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.oa-receive-account-select__select {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.oa-receive-account-select__select :deep(.el-select),
|
||||
.oa-receive-account-select__select :deep(.el-select__wrapper),
|
||||
.oa-receive-account-select__select :deep(.el-tooltip__trigger) {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.oa-receive-account-select__select :deep(.el-select__selection) {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.oa-receive-account-select__select :deep(.el-select__wrapper) {
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.oa-receive-account-select__trigger {
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-height: 40px;
|
||||
padding: 4px 12px;
|
||||
border: 1px solid var(--mint-border, #cbd5e1);
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.oa-receive-account-select__readonly {
|
||||
color: var(--mint-text, #0f172a);
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
white-space: pre-line;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.oa-receive-account-select__placeholder {
|
||||
color: var(--mint-text-muted, #94a3b8);
|
||||
}
|
||||
|
||||
.oa-receive-account-select__trigger-text {
|
||||
min-width: 0;
|
||||
color: var(--mint-text, #0f172a);
|
||||
white-space: pre-line;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.oa-receive-account-select__chev {
|
||||
flex-shrink: 0;
|
||||
margin-left: 8px;
|
||||
color: var(--mint-text-muted, #94a3b8);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.oa-receive-account-select__search {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.oa-receive-account-select__list {
|
||||
max-height: 50vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.oa-receive-account-select__row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
min-height: 44px;
|
||||
padding: 6px 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.oa-receive-account-select__row-label {
|
||||
min-width: 0;
|
||||
color: var(--mint-text, #0f172a);
|
||||
}
|
||||
|
||||
.oa-receive-account-select__empty {
|
||||
padding: 16px;
|
||||
color: var(--mint-text-muted, #94a3b8);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.oa-receive-account-select__select :deep(.el-select__wrapper),
|
||||
.oa-receive-account-select__trigger {
|
||||
min-height: 44px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -20,6 +20,7 @@ import { setupOaRelatedApproval } from '@/utils/oa-form-create-related-approval'
|
||||
import { setupOaDeptSelect } from '@/utils/oa-form-create-dept'
|
||||
import { setupOaMoneyField } from '@/utils/oa-form-create-money'
|
||||
import { setupOaPersonSelect } from '@/utils/oa-form-create-person'
|
||||
import { setupOaReceiveAccountSelect } from '@/utils/oa-form-create-receive-account'
|
||||
|
||||
// FormCreate 按字符串解析 el-input / fc-radio 等,主应用是按需自动导入,不会全局注册;
|
||||
// 必须挂上官方 auto-import,否则发起审批/详情里动态表单控件会残缺(如单选项只剩文字)。
|
||||
@@ -30,6 +31,7 @@ setupOaRelatedApproval(FcDesigner.formCreate)
|
||||
setupOaDeptSelect(FcDesigner.formCreate)
|
||||
setupOaMoneyField(FcDesigner.formCreate)
|
||||
setupOaPersonSelect(FcDesigner.formCreate)
|
||||
setupOaReceiveAccountSelect(FcDesigner.formCreate)
|
||||
|
||||
function maskSecret(value?: string) {
|
||||
if (!value) return '(empty)'
|
||||
|
||||
@@ -17,6 +17,7 @@ import { setupOaRelatedApproval } from '@/utils/oa-form-create-related-approval'
|
||||
import { setupOaDeptSelect } from '@/utils/oa-form-create-dept'
|
||||
import { setupOaMoneyField } from '@/utils/oa-form-create-money'
|
||||
import { setupOaPersonSelect } from '@/utils/oa-form-create-person'
|
||||
import { setupOaReceiveAccountSelect } from '@/utils/oa-form-create-receive-account'
|
||||
import App from './App.vue'
|
||||
|
||||
const app = createApp(App)
|
||||
@@ -37,4 +38,5 @@ setupOaRelatedApproval(FcDesigner.formCreate)
|
||||
setupOaDeptSelect(FcDesigner.formCreate)
|
||||
setupOaMoneyField(FcDesigner.formCreate)
|
||||
setupOaPersonSelect(FcDesigner.formCreate)
|
||||
setupOaReceiveAccountSelect(FcDesigner.formCreate)
|
||||
app.mount('#app')
|
||||
@@ -15,6 +15,7 @@ import { formatProgressPersonLines } from '@/utils/oa-progress-person-lines'
|
||||
import { formatDeptDisplay } from '@/utils/oa-dept-value'
|
||||
import { formatPersonDisplay } from '@/utils/oa-person-value'
|
||||
import { formatMoneyDisplay } from '@/utils/money-format'
|
||||
import { formatReceiveAccountDisplay } from '@/utils/oa-receive-account-value'
|
||||
import { toRmbUpper } from '@/utils/rmb-upper'
|
||||
|
||||
export const OA_PRINT_COMPANY_NAME = '安徽中科兴皖医学研究有限公司'
|
||||
@@ -274,6 +275,12 @@ async function walkRules(
|
||||
continue
|
||||
}
|
||||
|
||||
if (type === 'receiveAccount') {
|
||||
const text = formatReceiveAccountDisplay(value)
|
||||
out.push({ label, text: text || '-' })
|
||||
continue
|
||||
}
|
||||
|
||||
if (UPLOAD_TYPES.has(type) || String(rule.name || '').toLowerCase().includes('upload')) {
|
||||
const urls = extractUrls(value)
|
||||
out.push({ label, urls: urls.length ? urls : [], text: urls.length ? undefined : '-' })
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import FcDesigner from '@form-create/designer'
|
||||
import OaReceiveAccountSelect from '@/components/oa-approval/OaReceiveAccountSelect.vue'
|
||||
import { receiveAccountSelectDragRule } from '@/utils/oa-receive-account-rule'
|
||||
|
||||
const FLAG = '__oaReceiveAccountSelectConfigured'
|
||||
|
||||
export function setupOaReceiveAccountSelect(formCreate?: {
|
||||
component?: (name: string, c: unknown) => void
|
||||
}) {
|
||||
if ((FcDesigner as any)[FLAG]) return
|
||||
FcDesigner.component('receiveAccount', OaReceiveAccountSelect)
|
||||
if (typeof formCreate?.component === 'function') {
|
||||
formCreate.component('receiveAccount', OaReceiveAccountSelect)
|
||||
}
|
||||
if (typeof FcDesigner.addDragRule === 'function') {
|
||||
FcDesigner.addDragRule(receiveAccountSelectDragRule)
|
||||
}
|
||||
;(FcDesigner as any)[FLAG] = true
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/** 与 FormCreate 官方 unique 同类:生成不重复 field */
|
||||
let _uidSeq = 0
|
||||
function uniqueId() {
|
||||
const num = 370 + ++_uidSeq
|
||||
return (
|
||||
'F' +
|
||||
Math.random().toString(36).slice(3, 6) +
|
||||
Number(`${Date.now()}`).toString(36) +
|
||||
num.toString(36) +
|
||||
'r'
|
||||
)
|
||||
}
|
||||
|
||||
/** FormCreate Designer DragRule:收款账户选择 */
|
||||
export const receiveAccountSelectDragRule = {
|
||||
menu: 'main' as const,
|
||||
icon: 'icon-input',
|
||||
label: '收款账户',
|
||||
name: 'receiveAccount',
|
||||
input: true,
|
||||
mask: true,
|
||||
validate: ['string', 'array', 'object'],
|
||||
event: ['change'],
|
||||
rule() {
|
||||
return {
|
||||
type: 'receiveAccount',
|
||||
field: uniqueId(),
|
||||
title: '收款账户',
|
||||
info: '',
|
||||
$required: false,
|
||||
props: {
|
||||
multiple: true
|
||||
}
|
||||
}
|
||||
},
|
||||
props() {
|
||||
return [
|
||||
{
|
||||
type: 'input',
|
||||
field: 'formCreateInfo',
|
||||
title: '提示信息'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user