feat(mobile-oa-h5): add kitchen-sink form page with OA type stubs

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-08-11 12:08:45 +08:00
1 parent 1b45fbaa36
commit 9adff5584e
4 files changed
+237 -2

No files matched your search

@@ -0,0 +1,55 @@
<template>
<div class="oa-stub">
<div class="oa-stub__title">{{ title || typeName }}</div>
<van-field
v-model="inner"
:placeholder="'[' + typeName + '] 占位,后续替换真实交互'"
@update:model-value="onInput"
/>
</div>
</template>
<script setup>
import { computed, ref, watch } from 'vue'
const props = defineProps({
modelValue: { type: [String, Number, Array, Object], default: '' },
formCreateInject: { type: Object, default: null }
})
const emit = defineEmits(['update:modelValue'])
const typeName = computed(
() => props.formCreateInject?.rule?.type || 'unknown'
)
const title = computed(() => props.formCreateInject?.rule?.title || '')
const inner = ref(
typeof props.modelValue === 'string' || typeof props.modelValue === 'number'
? String(props.modelValue ?? '')
: JSON.stringify(props.modelValue ?? '')
)
watch(
() => props.modelValue,
(v) => {
inner.value =
typeof v === 'string' || typeof v === 'number'
? String(v ?? '')
: JSON.stringify(v ?? '')
}
)
function onInput(v) {
emit('update:modelValue', v)
}
</script>
<style scoped>
.oa-stub {
margin-bottom: 12px;
}
.oa-stub__title {
margin: 0 0 6px;
font-size: 14px;
font-weight: 600;
}
</style>
+84
View File
@@ -0,0 +1,84 @@
/** 按 zkxw-sc 已发布模板实际出现的 type 构造;非设计器理论全集 */
export const kitchenSinkRule = [
{ type: 'input', field: 'title', title: '单行文本', value: '', $required: true },
{ type: 'textarea', field: 'remark', title: '多行文本', value: '' },
{
type: 'select',
field: 'level',
title: '下拉',
value: '',
options: [
{ label: '普通', value: 'normal' },
{ label: '紧急', value: 'urgent' }
]
},
{ type: 'inputNumber', field: 'qty', title: '数字', value: 1 },
{
type: 'datePicker',
field: 'bizDate',
title: '日期',
value: '',
props: { type: 'date' }
},
{ type: 'upload', field: 'files', title: '附件 upload', value: [] },
{ type: 'money', field: 'amount', title: '金额 money', value: '' },
{
type: 'relatedApproval',
field: 'related',
title: '关联审批 relatedApproval',
value: []
},
{
type: 'receiveAccount',
field: 'payAccount',
title: '收款账户 receiveAccount',
value: []
},
{ type: 'dept', field: 'deptId', title: '部门 dept', value: '' },
{ type: 'person', field: 'personIds', title: '人员 person', value: [] },
{
type: 'ownerPerson',
field: 'ownerPerson',
title: '所属人 ownerPerson',
value: ''
},
{
type: 'ownerDept',
field: 'ownerDept',
title: '所属部门 ownerDept',
value: ''
},
{ type: 'post', field: 'postId', title: '岗位 post', value: '' },
{ type: 'leaveKit', field: 'leaveKit', title: '请假套件 leaveKit', value: {} },
{
type: 'overtimeKit',
field: 'overtimeKit',
title: '加班套件 overtimeKit',
value: {}
},
{
type: 'confirmationKit',
field: 'confirmationKit',
title: '转正套件 confirmationKit',
value: {}
},
{
type: 'resignationKit',
field: 'resignationKit',
title: '离职套件 resignationKit',
value: {}
},
{ type: 'tripKit', field: 'tripKit', title: '差旅套件 tripKit', value: {} },
{
type: 'tripItinerary',
field: 'tripItinerary',
title: '行程 tripItinerary',
value: []
}
]
export const kitchenSinkOption = {
submitBtn: false,
resetBtn: false,
form: { labelPosition: 'top' }
}
+25 -1
View File
@@ -1,3 +1,27 @@
import OaStubField from './components/OaStubField.vue'
/** 与 web-admin formCreate.component 名称对齐;阶段一可用 stub */
const CUSTOM_TYPES = [
'upload',
'money',
'relatedApproval',
'receiveAccount',
'dept',
'person',
'ownerPerson',
'ownerDept',
'post',
'leaveKit',
'overtimeKit',
'confirmationKit',
'resignationKit',
'tripKit',
'tripItinerary'
]
export function registerOaComponents(formCreate) {
// Task 3 will replace with real registrations
if (!formCreate || typeof formCreate.component !== 'function') return
for (const name of CUSTOM_TYPES) {
formCreate.component(name, OaStubField)
}
}
+73 -1
View File
@@ -1,3 +1,75 @@
<template>
<div style="padding:16px">FormSink 占位(Task 3 实现)</div>
<div class="sink">
<header class="sink__hd">表单水槽测试</header>
<div class="sink__body">
<form-create-mobile
v-model="formData"
v-model:api="fapi"
:rule="rule"
:option="option"
/>
<pre class="sink__json">{{ preview }}</pre>
</div>
<footer class="sink__ft">
<van-button block type="primary" @click="onValidate">校验并导出</van-button>
</footer>
</div>
</template>
<script setup>
import { computed, ref } from 'vue'
import { showToast } from 'vant'
import { kitchenSinkOption, kitchenSinkRule } from '../oa/kitchen-sink-rule'
const formData = ref({})
const fapi = ref(null)
const rule = ref(kitchenSinkRule)
const option = ref(kitchenSinkOption)
const preview = computed(() => JSON.stringify(formData.value, null, 2))
async function onValidate() {
try {
await fapi.value?.validate?.()
showToast('校验通过')
} catch (e) {
showToast(e?.message || '校验失败')
}
}
</script>
<style scoped>
.sink {
min-height: 100vh;
display: flex;
flex-direction: column;
background: var(--bg-page);
}
.sink__hd {
padding: 14px 16px;
font-weight: 700;
color: #fff;
background: linear-gradient(135deg, var(--brand-green), var(--brand-green-dark));
}
.sink__body {
flex: 1;
padding: 12px 16px 88px;
}
.sink__json {
margin-top: 16px;
padding: 10px;
overflow: auto;
font-size: 12px;
background: #fff;
border: 1px solid var(--border);
border-radius: 8px;
}
.sink__ft {
position: fixed;
right: 0;
bottom: 0;
left: 0;
padding: 10px 16px calc(10px + env(safe-area-inset-bottom));
background: #fff;
border-top: 1px solid var(--border);
}
</style>