diff --git a/docs/superpowers/specs/2026-08-11-mobile-oa-form-sink-spike-notes.md b/docs/superpowers/specs/2026-08-11-mobile-oa-form-sink-spike-notes.md index 3e622169..d4f4a1bd 100644 --- a/docs/superpowers/specs/2026-08-11-mobile-oa-form-sink-spike-notes.md +++ b/docs/superpowers/specs/2026-08-11-mobile-oa-form-sink-spike-notes.md @@ -20,6 +20,12 @@ - `receiveAccount`:底部多选弹层 + options API - `relatedApproval`:关联列表 + 候选弹层(mine/done/cc) +## P1 真实组件(已落地) + +- `person` / `dept` / `post`:底部选择弹层,值 `{ id, name }`(部门展示全路径) +- `ownerPerson` / `ownerDept`:单选封装,`allowModify` 控制可否改选 +- 仍为 stub:各 Kit(请假/加班/转正/离职/差旅) + ## 本地联调提示 1. `cd mobile-oa-h5 && npm run dev` diff --git a/mobile-oa-h5/src/api/oa.js b/mobile-oa-h5/src/api/oa.js index dea52050..3b2f9b7c 100644 --- a/mobile-oa-h5/src/api/oa.js +++ b/mobile-oa-h5/src/api/oa.js @@ -1,5 +1,17 @@ import { get, post, request } from '../utils/http.js' +export function fetchUserList(params) { + return get('/web/admin/users', params) +} + +export function fetchDeptTree(params) { + return get('/web/admin/depts', params) +} + +export function fetchPostList(params) { + return get('/web/admin/posts', params) +} + export function fetchReceiveAccountOptions() { return get('/web/admin/oa-approval/receive-accounts/options') } diff --git a/mobile-oa-h5/src/oa/components/OaDeptSelect.vue b/mobile-oa-h5/src/oa/components/OaDeptSelect.vue new file mode 100644 index 00000000..8a8c25ab --- /dev/null +++ b/mobile-oa-h5/src/oa/components/OaDeptSelect.vue @@ -0,0 +1,212 @@ + + + + + diff --git a/mobile-oa-h5/src/oa/components/OaOwnerDeptSelect.vue b/mobile-oa-h5/src/oa/components/OaOwnerDeptSelect.vue new file mode 100644 index 00000000..a72447a1 --- /dev/null +++ b/mobile-oa-h5/src/oa/components/OaOwnerDeptSelect.vue @@ -0,0 +1,34 @@ + + + diff --git a/mobile-oa-h5/src/oa/components/OaOwnerPersonSelect.vue b/mobile-oa-h5/src/oa/components/OaOwnerPersonSelect.vue new file mode 100644 index 00000000..473c829b --- /dev/null +++ b/mobile-oa-h5/src/oa/components/OaOwnerPersonSelect.vue @@ -0,0 +1,34 @@ + + + diff --git a/mobile-oa-h5/src/oa/components/OaPersonSelect.vue b/mobile-oa-h5/src/oa/components/OaPersonSelect.vue new file mode 100644 index 00000000..b2f7bff8 --- /dev/null +++ b/mobile-oa-h5/src/oa/components/OaPersonSelect.vue @@ -0,0 +1,226 @@ + + + + + diff --git a/mobile-oa-h5/src/oa/components/OaPostSelect.vue b/mobile-oa-h5/src/oa/components/OaPostSelect.vue new file mode 100644 index 00000000..cb80fc66 --- /dev/null +++ b/mobile-oa-h5/src/oa/components/OaPostSelect.vue @@ -0,0 +1,222 @@ + + + + + diff --git a/mobile-oa-h5/src/oa/kitchen-sink-rule.js b/mobile-oa-h5/src/oa/kitchen-sink-rule.js index c3661ed6..2f044f69 100644 --- a/mobile-oa-h5/src/oa/kitchen-sink-rule.js +++ b/mobile-oa-h5/src/oa/kitchen-sink-rule.js @@ -34,21 +34,41 @@ export const kitchenSinkRule = [ title: '收款账户 receiveAccount', value: [] }, - { type: 'dept', field: 'deptId', title: '部门 dept', value: '' }, - { type: 'person', field: 'personIds', title: '人员 person', value: [] }, + { + type: 'dept', + field: 'deptId', + title: '部门 dept', + value: [], + props: { multiple: true } + }, + { + type: 'person', + field: 'personIds', + title: '人员 person', + value: [], + props: { multiple: true } + }, { type: 'ownerPerson', field: 'ownerPerson', - title: '所属人 ownerPerson', - value: '' + title: '所属人 ownerPerson(可改选)', + value: null, + props: { allowModify: true } }, { type: 'ownerDept', field: 'ownerDept', - title: '所属部门 ownerDept', - value: '' + title: '所属部门 ownerDept(可改选)', + value: null, + props: { allowModify: true } + }, + { + type: 'post', + field: 'postId', + title: '岗位 post', + value: [], + props: { multiple: true } }, - { type: 'post', field: 'postId', title: '岗位 post', value: '' }, { type: 'leaveKit', field: 'leaveKit', title: '请假套件 leaveKit', value: {} }, { type: 'overtimeKit', diff --git a/mobile-oa-h5/src/oa/register-components.js b/mobile-oa-h5/src/oa/register-components.js index 626900a5..12ae21c5 100644 --- a/mobile-oa-h5/src/oa/register-components.js +++ b/mobile-oa-h5/src/oa/register-components.js @@ -3,21 +3,26 @@ import OaMoneyField from './components/OaMoneyField.vue' import OaUploadField from './components/OaUploadField.vue' import OaReceiveAccountSelect from './components/OaReceiveAccountSelect.vue' import OaRelatedApproval from './components/OaRelatedApproval.vue' +import OaPersonSelect from './components/OaPersonSelect.vue' +import OaDeptSelect from './components/OaDeptSelect.vue' +import OaPostSelect from './components/OaPostSelect.vue' +import OaOwnerPersonSelect from './components/OaOwnerPersonSelect.vue' +import OaOwnerDeptSelect from './components/OaOwnerDeptSelect.vue' -/** P0 真实组件;其余仍用 stub,阶段二继续替换 */ +/** P0 + P1 真实组件;套件仍用 stub */ const REAL_COMPONENTS = { money: OaMoneyField, upload: OaUploadField, receiveAccount: OaReceiveAccountSelect, - relatedApproval: OaRelatedApproval + relatedApproval: OaRelatedApproval, + person: OaPersonSelect, + dept: OaDeptSelect, + post: OaPostSelect, + ownerPerson: OaOwnerPersonSelect, + ownerDept: OaOwnerDeptSelect } const STUB_TYPES = [ - 'dept', - 'person', - 'ownerPerson', - 'ownerDept', - 'post', 'leaveKit', 'overtimeKit', 'confirmationKit', diff --git a/mobile-oa-h5/src/utils/id-name-value.js b/mobile-oa-h5/src/utils/id-name-value.js new file mode 100644 index 00000000..520cb3db --- /dev/null +++ b/mobile-oa-h5/src/utils/id-name-value.js @@ -0,0 +1,47 @@ +/** person / dept / post 共用 { id, name } 值结构 */ + +export function isIdNameItem(v) { + if (!v || typeof v !== 'object') return false + return String(v.id || '').trim() !== '' && String(v.name || '').trim() !== '' +} + +export function normalizeIdNameModelValue(raw, multiple) { + if (multiple) { + if (Array.isArray(raw)) { + return raw.filter(isIdNameItem).map((x) => ({ + id: String(x.id).trim(), + name: String(x.name).trim() + })) + } + if (isIdNameItem(raw)) { + return [{ id: String(raw.id).trim(), name: String(raw.name).trim() }] + } + return [] + } + if (isIdNameItem(raw)) { + return { id: String(raw.id).trim(), name: String(raw.name).trim() } + } + if (Array.isArray(raw) && raw.length === 1 && isIdNameItem(raw[0])) { + return { id: String(raw[0].id).trim(), name: String(raw[0].name).trim() } + } + return null +} + +export function formatIdNameDisplay(raw) { + if (Array.isArray(raw)) { + return raw + .filter(isIdNameItem) + .map((x) => x.name) + .filter(Boolean) + .join('、') + } + if (isIdNameItem(raw)) return raw.name + return '' +} + +export function selectedIdsFromNormalized(normalized, multiple) { + if (multiple) { + return Array.isArray(normalized) ? normalized.map((x) => x.id) : [] + } + return normalized && !Array.isArray(normalized) ? [normalized.id] : [] +}