feat(oa): add permission-controlled shared finance remarks to done center
Share one finance remark per approval instance across Web and mobile. Require the explicit permission and the current tenant user's own done task for every read and write; keep general instance responses free of financial remarks. Add idempotent MySQL migration without automatic role grants, cross-end editors, isolated backend and frontend tests, and rollout requirements. Refs #7; platform validation and target database migration remain pending.
This commit is contained in:
1 parent
86df360f35
commit
0e64b413ce
15 files changed
+1188
-78
No files matched your search
@@ -0,0 +1,299 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { test } from 'node:test'
|
||||
import { parse, compileTemplate } from '@vue/compiler-sfc'
|
||||
import ts from 'typescript'
|
||||
import { ref, reactive, computed, proxyRefs } from 'vue'
|
||||
|
||||
const root = new URL('../../', import.meta.url)
|
||||
const fields = [
|
||||
'canManageFinanceRemark',
|
||||
'financeRemarkVisible',
|
||||
'financeRemarkLoading',
|
||||
'financeRemarkSaving',
|
||||
'financeRemarkText',
|
||||
'openFinanceRemark',
|
||||
'saveFinanceRemark',
|
||||
'cancelFinanceRemark',
|
||||
'clearFinanceAccess',
|
||||
'load',
|
||||
'rows'
|
||||
]
|
||||
function evaluate(source, deps, tail = '') {
|
||||
const withoutImports = source.replace(/import[\s\S]*?from\s*['"][^'"]+['"]/g, '')
|
||||
const js = ts.transpileModule(withoutImports + tail, {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 }
|
||||
}).outputText
|
||||
const exports = {}
|
||||
return (
|
||||
new Function(...Object.keys(deps), 'exports', js)(...Object.values(deps), exports) ||
|
||||
exports.default ||
|
||||
exports
|
||||
)
|
||||
}
|
||||
function harness(platform) {
|
||||
const calls = []
|
||||
const hooks = {}
|
||||
const api = {
|
||||
list: {
|
||||
list: [{ id: 'task-1', instanceId: 'instance-1', financeRemark: '列表旧值' }],
|
||||
total: 1,
|
||||
canManageFinanceRemark: true
|
||||
},
|
||||
get: async () => ({ financeRemark: '最新备注' }),
|
||||
put: async () => ({ financeRemark: '' })
|
||||
}
|
||||
const deps = {
|
||||
ref,
|
||||
reactive,
|
||||
computed,
|
||||
defineOptions() {},
|
||||
watchDebounced() {},
|
||||
onMounted() {},
|
||||
onActivated() {},
|
||||
onDeactivated(fn) {
|
||||
hooks.hide = fn
|
||||
},
|
||||
useRouter: () => ({}),
|
||||
useMenuPerm: () => ({ hasMenuPerm: () => false }),
|
||||
useUserStore: () => ({}),
|
||||
ElMessage: { success() {}, error() {}, warning() {} },
|
||||
uni: { showToast() {}, navigateTo() {} },
|
||||
VanEmpty: {},
|
||||
getToken: () => 'token',
|
||||
forceRelogin() {},
|
||||
fetchOaTasks: async () => {
|
||||
calls.push(['list'])
|
||||
return api.list
|
||||
},
|
||||
fetchOaFinanceRemark: async (id) => {
|
||||
calls.push(['get', id])
|
||||
return api.get(id)
|
||||
},
|
||||
fetchUpdateOaFinanceRemark: async (id, text) => {
|
||||
calls.push(['put', id, text])
|
||||
return api.put(id, text)
|
||||
}
|
||||
}
|
||||
const path =
|
||||
platform === 'web'
|
||||
? 'web-admin/src/views/oa-approval/done/index.vue'
|
||||
: 'mobile-h5/pages/oa/done/index.vue'
|
||||
const { descriptor } = parse(readFileSync(new URL(path, root), 'utf8'))
|
||||
let vm
|
||||
if (platform === 'web') {
|
||||
vm = proxyRefs(
|
||||
evaluate(
|
||||
descriptor.scriptSetup.content,
|
||||
deps,
|
||||
'\nreturn {' +
|
||||
fields.map((k) => `${k}: typeof ${k} === 'undefined' ? undefined : ${k}`).join(',') +
|
||||
'}'
|
||||
)
|
||||
)
|
||||
} else {
|
||||
const options = evaluate(descriptor.script.content, deps)
|
||||
vm = options.data()
|
||||
for (const [key, fn] of Object.entries(options.methods)) vm[key] = fn.bind(vm)
|
||||
hooks.hide = () => options.onHide?.call(vm)
|
||||
}
|
||||
const load = () => (platform === 'web' ? vm.load() : vm.loadList(true))
|
||||
const rows = () => (platform === 'web' ? vm.rows : vm.list)
|
||||
return { vm, calls, api, load, rows, hooks, descriptor }
|
||||
}
|
||||
for (const platform of ['web', 'mobile']) {
|
||||
test(`${platform}: capability gates access and opening gets latest instance value`, async () => {
|
||||
const h = harness(platform)
|
||||
await h.load()
|
||||
assert.equal(h.vm.canManageFinanceRemark, true)
|
||||
await h.vm.openFinanceRemark(h.rows()[0])
|
||||
assert.deepEqual(h.calls.at(-1), ['get', 'instance-1'])
|
||||
assert.equal(h.vm.financeRemarkText, '最新备注')
|
||||
assert.equal(h.vm.financeRemarkVisible, true)
|
||||
})
|
||||
test(`${platform}: cancellation makes no write and discards late GET`, async () => {
|
||||
const h = harness(platform)
|
||||
await h.load()
|
||||
let resolve
|
||||
h.api.get = () =>
|
||||
new Promise((r) => {
|
||||
resolve = r
|
||||
})
|
||||
const opening = h.vm.openFinanceRemark(h.rows()[0])
|
||||
h.vm.cancelFinanceRemark()
|
||||
resolve({ financeRemark: '不得重新显示' })
|
||||
await opening
|
||||
assert.equal(h.vm.financeRemarkVisible, false)
|
||||
assert.equal(h.vm.financeRemarkText, '')
|
||||
assert.equal(
|
||||
h.calls.some((c) => c[0] === 'put'),
|
||||
false
|
||||
)
|
||||
})
|
||||
test(`${platform}: save failure preserves input; clear succeeds and refreshes`, async () => {
|
||||
const h = harness(platform)
|
||||
await h.load()
|
||||
await h.vm.openFinanceRemark(h.rows()[0])
|
||||
h.vm.financeRemarkText = '我的未保存输入'
|
||||
h.api.put = async () => {
|
||||
throw new Error('failure')
|
||||
}
|
||||
await h.vm.saveFinanceRemark()
|
||||
assert.equal(h.vm.financeRemarkText, '我的未保存输入')
|
||||
assert.equal(h.vm.financeRemarkVisible, true)
|
||||
h.api.put = async () => ({ financeRemark: '' })
|
||||
h.vm.financeRemarkText = ''
|
||||
await h.vm.saveFinanceRemark()
|
||||
assert.deepEqual(h.calls.slice(-2), [['put', 'instance-1', ''], ['list']])
|
||||
assert.equal(h.vm.financeRemarkVisible, false)
|
||||
})
|
||||
test(`${platform}: missing capability strips cached notes and prevents GET`, async () => {
|
||||
const h = harness(platform)
|
||||
await h.load()
|
||||
await h.vm.openFinanceRemark(h.rows()[0])
|
||||
h.api.list = { list: [{ instanceId: 'instance-1', financeRemark: '敏感备注' }], total: 1 }
|
||||
await h.load()
|
||||
assert.equal(h.vm.canManageFinanceRemark, false)
|
||||
assert.equal(h.vm.financeRemarkVisible, false)
|
||||
assert.equal(h.vm.financeRemarkText, '')
|
||||
assert.equal('financeRemark' in h.rows()[0], false)
|
||||
const count = h.calls.length
|
||||
await h.vm.openFinanceRemark(h.rows()[0])
|
||||
assert.equal(h.calls.length, count)
|
||||
})
|
||||
test(`${platform}: hiding page clears notes and invalidates pending reads`, async () => {
|
||||
const h = harness(platform)
|
||||
await h.load()
|
||||
let resolve
|
||||
h.api.get = () =>
|
||||
new Promise((r) => {
|
||||
resolve = r
|
||||
})
|
||||
const opening = h.vm.openFinanceRemark(h.rows()[0])
|
||||
h.hooks.hide()
|
||||
resolve({ financeRemark: '不得恢复的旧备注' })
|
||||
await opening
|
||||
assert.equal(h.vm.canManageFinanceRemark, false)
|
||||
assert.equal(h.vm.financeRemarkText, '')
|
||||
assert.equal('financeRemark' in h.rows()[0], false)
|
||||
})
|
||||
test(`${platform}: overlong input cannot send PUT`, async () => {
|
||||
const h = harness(platform)
|
||||
await h.load()
|
||||
await h.vm.openFinanceRemark(h.rows()[0])
|
||||
h.vm.financeRemarkText = '字'.repeat(513)
|
||||
await h.vm.saveFinanceRemark()
|
||||
assert.equal(
|
||||
h.calls.some((c) => c[0] === 'put'),
|
||||
false
|
||||
)
|
||||
assert.equal(h.vm.financeRemarkVisible, true)
|
||||
})
|
||||
test(`${platform}: saving while reading or already saving sends no duplicate PUT`, async () => {
|
||||
const h = harness(platform)
|
||||
await h.load()
|
||||
let resolveGet
|
||||
h.api.get = () =>
|
||||
new Promise((r) => {
|
||||
resolveGet = r
|
||||
})
|
||||
const opening = h.vm.openFinanceRemark(h.rows()[0])
|
||||
await h.vm.saveFinanceRemark()
|
||||
assert.equal(
|
||||
h.calls.some((c) => c[0] === 'put'),
|
||||
false
|
||||
)
|
||||
resolveGet({ financeRemark: '备注' })
|
||||
await opening
|
||||
let resolvePut
|
||||
h.api.put = () =>
|
||||
new Promise((r) => {
|
||||
resolvePut = r
|
||||
})
|
||||
const saving = h.vm.saveFinanceRemark()
|
||||
await h.vm.saveFinanceRemark()
|
||||
assert.equal(h.calls.filter((c) => c[0] === 'put').length, 1)
|
||||
resolvePut({ financeRemark: '备注' })
|
||||
await saving
|
||||
})
|
||||
test(`${platform}: late list response cannot restore permission after hiding`, async () => {
|
||||
const h = harness(platform)
|
||||
await h.load()
|
||||
let resolve
|
||||
const previous = h.api.list
|
||||
h.api.list = new Promise((r) => {
|
||||
resolve = r
|
||||
})
|
||||
const loading = h.load()
|
||||
h.hooks.hide()
|
||||
resolve(previous)
|
||||
await loading
|
||||
assert.equal(h.vm.canManageFinanceRemark, false)
|
||||
assert.equal('financeRemark' in h.rows()[0], false)
|
||||
})
|
||||
test(`${platform}: failed latest-value request cannot save stale list text`, async () => {
|
||||
const h = harness(platform)
|
||||
await h.load()
|
||||
h.api.get = async () => {
|
||||
throw new Error('forbidden')
|
||||
}
|
||||
await h.vm.openFinanceRemark(h.rows()[0])
|
||||
assert.equal(h.vm.financeRemarkVisible, false)
|
||||
assert.equal(h.vm.financeRemarkText, '')
|
||||
await h.vm.saveFinanceRemark()
|
||||
assert.equal(
|
||||
h.calls.some((c) => c[0] === 'put'),
|
||||
false
|
||||
)
|
||||
})
|
||||
test(`${platform}: template compiles with plain text and gated edit controls`, () => {
|
||||
const { descriptor } = harness(platform)
|
||||
const result = compileTemplate({
|
||||
source: descriptor.template.content,
|
||||
filename: 'done.vue',
|
||||
id: platform
|
||||
})
|
||||
assert.deepEqual(result.errors, [])
|
||||
assert.match(result.code, /canManageFinanceRemark/)
|
||||
assert.match(result.code, /toDisplayString\([^)]*financeRemark/)
|
||||
assert.doesNotMatch(result.code, /innerHTML/)
|
||||
if (platform === 'mobile')
|
||||
assert.match(result.code, /withModifiers[^\n]*openFinanceRemark[^\n]*[\s\S]*?\["stop"\]/)
|
||||
})
|
||||
}
|
||||
for (const platform of ['web', 'mobile']) {
|
||||
test(`${platform}: finance API uses dedicated authenticated instance endpoint`, async () => {
|
||||
const calls = []
|
||||
const request = (config) => {
|
||||
calls.push(config)
|
||||
return Promise.resolve({})
|
||||
}
|
||||
request.get = (config) => request({ ...config, method: 'GET' })
|
||||
request.put = (config) => request({ ...config, method: 'PUT' })
|
||||
const path =
|
||||
platform === 'web' ? 'web-admin/src/api/oaApproval.ts' : 'mobile-h5/utils/oa-api.js'
|
||||
const api = evaluate(readFileSync(new URL(path, root), 'utf8'), {
|
||||
request,
|
||||
get: (url, header) => request({ url, header, method: 'GET' }),
|
||||
getToken: () => 'token'
|
||||
})
|
||||
assert.equal(typeof api.fetchOaFinanceRemark, 'function')
|
||||
await api.fetchOaFinanceRemark('instance-1')
|
||||
await api.fetchUpdateOaFinanceRemark('instance-1', '')
|
||||
assert.equal(calls[0].method, 'GET')
|
||||
assert.equal(calls[1].method, 'PUT')
|
||||
assert.equal(calls[0].url, '/web/admin/oa-approval/instances/instance-1/finance-remark')
|
||||
assert.equal(calls[1].url, calls[0].url)
|
||||
assert.deepEqual(calls[1].data, { financeRemark: '' })
|
||||
if (platform === 'mobile') assert.equal(calls[1].header.Authorization, 'Bearer token')
|
||||
})
|
||||
}
|
||||
test('web: delayed refresh on an inactive cached page cannot restore finance data', async () => {
|
||||
const h = harness('web')
|
||||
await h.load()
|
||||
h.hooks.hide()
|
||||
const count = h.calls.length
|
||||
await h.load()
|
||||
assert.equal(h.vm.canManageFinanceRemark, false)
|
||||
assert.equal(h.calls.length, count)
|
||||
})
|
||||
@@ -408,7 +408,7 @@ export function fetchOaTasks(
|
||||
const applyTimeFrom = (params?.applyTimeFrom || '').trim()
|
||||
const applyTimeTo = (params?.applyTimeTo || '').trim()
|
||||
const instanceStatus = (params?.instanceStatus || '').trim()
|
||||
return request.get<{ list: any[]; total: number }>({
|
||||
return request.get<{ list: any[]; total: number; canManageFinanceRemark?: boolean }>({
|
||||
url: `${BASE}/tasks/${kind}`,
|
||||
params: {
|
||||
page: params?.page ?? 1,
|
||||
@@ -664,3 +664,18 @@ export function fetchReplaceOaBankReceipts(
|
||||
data: { items }
|
||||
})
|
||||
}
|
||||
|
||||
export type OaFinanceRemark = { instanceId: string; financeRemark: string }
|
||||
|
||||
export function fetchOaFinanceRemark(instanceId: string) {
|
||||
return request.get<OaFinanceRemark>({
|
||||
url: `${BASE}/instances/${encodeURIComponent(instanceId)}/finance-remark`
|
||||
})
|
||||
}
|
||||
|
||||
export function fetchUpdateOaFinanceRemark(instanceId: string, financeRemark: string) {
|
||||
return request.put<OaFinanceRemark>({
|
||||
url: `${BASE}/instances/${encodeURIComponent(instanceId)}/finance-remark`,
|
||||
data: { financeRemark }
|
||||
})
|
||||
}
|
||||
@@ -6,18 +6,10 @@
|
||||
<el-form :model="searchForm" class="filter-form" @submit.prevent>
|
||||
<div class="filter-grid">
|
||||
<el-form-item label="流程名称">
|
||||
<el-input
|
||||
v-model="searchForm.appName"
|
||||
placeholder="请输入流程名称"
|
||||
clearable
|
||||
/>
|
||||
<el-input v-model="searchForm.appName" placeholder="请输入流程名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="申请人">
|
||||
<el-input
|
||||
v-model="searchForm.initiatorName"
|
||||
placeholder="请输入申请人"
|
||||
clearable
|
||||
/>
|
||||
<el-input v-model="searchForm.initiatorName" placeholder="请输入申请人" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="申请时间">
|
||||
<el-date-picker
|
||||
@@ -76,28 +68,13 @@
|
||||
>
|
||||
<template #default="{ row }">{{ row.appName || row.title || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="initiatorUserName"
|
||||
label="申请人"
|
||||
min-width="120"
|
||||
align="center"
|
||||
>
|
||||
<el-table-column prop="initiatorUserName" label="申请人" min-width="120" align="center">
|
||||
<template #default="{ row }">{{ row.initiatorUserName || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="applyTime"
|
||||
label="申请时间"
|
||||
min-width="170"
|
||||
align="center"
|
||||
>
|
||||
<el-table-column prop="applyTime" label="申请时间" min-width="170" align="center">
|
||||
<template #default="{ row }">{{ row.applyTime || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="currentNodeName"
|
||||
label="当前节点"
|
||||
min-width="140"
|
||||
align="center"
|
||||
>
|
||||
<el-table-column prop="currentNodeName" label="当前节点" min-width="140" align="center">
|
||||
<template #default="{ row }">{{ row.currentNodeName || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@@ -116,6 +93,15 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="canManageFinanceRemark"
|
||||
prop="financeRemark"
|
||||
label="财务备注"
|
||||
min-width="220"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">{{ row.financeRemark || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="400" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item primary" @click="openDetail(row)">
|
||||
@@ -130,6 +116,14 @@
|
||||
<el-icon><Printer /></el-icon>
|
||||
打印
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="canManageFinanceRemark"
|
||||
class="btn-op-item"
|
||||
@click="openFinanceRemark(row)"
|
||||
>
|
||||
<el-icon><EditPen /></el-icon>
|
||||
财务备注
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="canUploadBankReceipt"
|
||||
class="btn-op-item"
|
||||
@@ -157,6 +151,49 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<el-dialog
|
||||
:model-value="financeRemarkVisible"
|
||||
title="财务备注"
|
||||
width="560px"
|
||||
class="dialog-form-layout"
|
||||
align-center
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
modal-class="protocol-dialog-mask"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="!financeRemarkSaving"
|
||||
:show-close="!financeRemarkSaving"
|
||||
@update:model-value="cancelFinanceRemark"
|
||||
>
|
||||
<el-form v-loading="financeRemarkLoading" label-position="top" @submit.prevent>
|
||||
<el-form-item label="财务备注">
|
||||
<el-input
|
||||
v-model="financeRemarkText"
|
||||
type="textarea"
|
||||
:rows="5"
|
||||
:maxlength="512"
|
||||
show-word-limit
|
||||
:disabled="financeRemarkLoading || financeRemarkSaving"
|
||||
placeholder="请输入财务备注,留空可清空"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button class="btn-ghost" :disabled="financeRemarkSaving" @click="cancelFinanceRemark">
|
||||
<el-icon><Close /></el-icon>取消
|
||||
</el-button>
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
:loading="financeRemarkSaving"
|
||||
:disabled="financeRemarkLoading"
|
||||
@click="saveFinanceRemark"
|
||||
>
|
||||
<el-icon><Check /></el-icon>确定
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<OaBankReceiptDialog
|
||||
v-model="bankReceiptVisible"
|
||||
:instance-id="bankReceiptInstanceId"
|
||||
@@ -166,11 +203,22 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onActivated, onMounted, reactive, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Printer, RefreshLeft, Search, Upload, View } from '@element-plus/icons-vue'
|
||||
import { fetchOaInstanceDetail, fetchOaTasks } from '@/api/oaApproval'
|
||||
import {
|
||||
Check,
|
||||
Close,
|
||||
EditPen,
|
||||
Printer,
|
||||
RefreshLeft,
|
||||
Search,
|
||||
Upload,
|
||||
View
|
||||
} from '@element-plus/icons-vue'
|
||||
import {
|
||||
fetchOaInstanceDetail,
|
||||
fetchOaTasks,
|
||||
fetchOaFinanceRemark,
|
||||
fetchUpdateOaFinanceRemark
|
||||
} from '@/api/oaApproval'
|
||||
import OaBankReceiptDialog from '@/components/oa-approval/OaBankReceiptDialog.vue'
|
||||
import { useMenuPerm } from '@/hooks/core/useMenuPerm'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
@@ -187,6 +235,82 @@
|
||||
const loading = ref(false)
|
||||
const printingId = ref('')
|
||||
const rows = ref<any[]>([])
|
||||
const canManageFinanceRemark = ref(false)
|
||||
const financeRemarkVisible = ref(false)
|
||||
const financeRemarkLoading = ref(false)
|
||||
const financeRemarkSaving = ref(false)
|
||||
const financeRemarkText = ref('')
|
||||
let financeRemarkInstanceId = ''
|
||||
let financeRemarkVersion = 0
|
||||
let listVersion = 0
|
||||
let pageActive = true
|
||||
|
||||
function cancelFinanceRemark() {
|
||||
financeRemarkVersion++
|
||||
financeRemarkVisible.value = false
|
||||
financeRemarkText.value = ''
|
||||
financeRemarkInstanceId = ''
|
||||
financeRemarkLoading.value = false
|
||||
financeRemarkSaving.value = false
|
||||
}
|
||||
|
||||
function clearFinanceAccess() {
|
||||
canManageFinanceRemark.value = false
|
||||
cancelFinanceRemark()
|
||||
rows.value = rows.value.map((row) => {
|
||||
const sanitized = { ...row }
|
||||
delete sanitized.financeRemark
|
||||
return sanitized
|
||||
})
|
||||
}
|
||||
|
||||
async function openFinanceRemark(row: any) {
|
||||
if (!canManageFinanceRemark.value || financeRemarkSaving.value) return
|
||||
const id = String(row.instanceId || '').trim()
|
||||
if (!id) return
|
||||
cancelFinanceRemark()
|
||||
const version = financeRemarkVersion
|
||||
financeRemarkInstanceId = id
|
||||
financeRemarkVisible.value = true
|
||||
financeRemarkLoading.value = true
|
||||
try {
|
||||
const result = await fetchOaFinanceRemark(id)
|
||||
if (version !== financeRemarkVersion) return
|
||||
financeRemarkText.value = result.financeRemark || ''
|
||||
} catch {
|
||||
if (version === financeRemarkVersion) cancelFinanceRemark()
|
||||
} finally {
|
||||
if (version === financeRemarkVersion) financeRemarkLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function saveFinanceRemark() {
|
||||
if (
|
||||
!canManageFinanceRemark.value ||
|
||||
!financeRemarkInstanceId ||
|
||||
financeRemarkLoading.value ||
|
||||
financeRemarkSaving.value
|
||||
)
|
||||
return
|
||||
if (financeRemarkText.value.length > 512) {
|
||||
ElMessage.warning('财务备注最多 512 个字符')
|
||||
return
|
||||
}
|
||||
const version = financeRemarkVersion
|
||||
financeRemarkSaving.value = true
|
||||
try {
|
||||
await fetchUpdateOaFinanceRemark(financeRemarkInstanceId, financeRemarkText.value)
|
||||
if (version !== financeRemarkVersion) return
|
||||
cancelFinanceRemark()
|
||||
ElMessage.success('财务备注已保存')
|
||||
await load()
|
||||
} catch {
|
||||
// 请求层统一提示错误,保留输入以便重试。
|
||||
} finally {
|
||||
if (version === financeRemarkVersion) financeRemarkSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const page = ref(1)
|
||||
const pageSize = ref(20)
|
||||
const total = ref(0)
|
||||
@@ -210,9 +334,7 @@
|
||||
|
||||
function currentPrinterName() {
|
||||
const info: any = userStore.info || userStore.getUserInfo || {}
|
||||
return String(
|
||||
info.realName || info.nickName || info.userName || info.username || ''
|
||||
).trim()
|
||||
return String(info.realName || info.nickName || info.userName || info.username || '').trim()
|
||||
}
|
||||
|
||||
function statusType(s: string) {
|
||||
@@ -306,16 +428,24 @@
|
||||
}
|
||||
|
||||
async function load() {
|
||||
if (!pageActive) return
|
||||
const version = ++listVersion
|
||||
clearFinanceAccess()
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await fetchOaTasks('done', buildFilterParams())
|
||||
if (version !== listVersion) return
|
||||
canManageFinanceRemark.value = res?.canManageFinanceRemark === true
|
||||
rows.value = res?.list || []
|
||||
if (!canManageFinanceRemark.value) clearFinanceAccess()
|
||||
total.value = res?.total || 0
|
||||
} catch {
|
||||
if (version !== listVersion) return
|
||||
clearFinanceAccess()
|
||||
rows.value = []
|
||||
total.value = 0
|
||||
} finally {
|
||||
loading.value = false
|
||||
if (version === listVersion) loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,6 +458,14 @@
|
||||
{ debounce: 300, deep: true }
|
||||
)
|
||||
|
||||
onDeactivated(() => {
|
||||
pageActive = false
|
||||
listVersion++
|
||||
clearFinanceAccess()
|
||||
})
|
||||
onMounted(load)
|
||||
onActivated(load)
|
||||
onActivated(() => {
|
||||
pageActive = true
|
||||
void load()
|
||||
})
|
||||
</script>
|
||||
Reference in new issue
Block a user