Browse Source

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

Co-authored-by: Cursor <cursoragent@cursor.com>
zlwu 3 months ago
parent
commit
9adff5584e

+ 55 - 0
mobile-oa-h5/src/oa/components/OaStubField.vue

@@ -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 - 0
mobile-oa-h5/src/oa/kitchen-sink-rule.js

@@ -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
mobile-oa-h5/src/oa/register-components.js

@@ -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
mobile-oa-h5/src/views/FormSink.vue

@@ -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>