diff --git a/mobile-oa-h5/src/main.js b/mobile-oa-h5/src/main.js
index be52fe4d..88be8e4f 100644
--- a/mobile-oa-h5/src/main.js
+++ b/mobile-oa-h5/src/main.js
@@ -5,11 +5,16 @@ import formCreateMobile from '@form-create/vant'
import App from './App.vue'
import router from './router'
import './styles/tokens.css'
+import './styles/oa-form.css'
import { registerOaComponents } from './oa/register-components'
+import { captureTokenFromUrl } from './utils/auth'
// 三墙主色(勿用 web-admin #10b981)
document.documentElement.style.setProperty('--van-primary-color', '#0a9f6d')
+// 须在路由/组件发请求前捕获小程序传入的 ?token=
+captureTokenFromUrl()
+
const app = createApp(App)
app.use(router)
app.use(Vant)
diff --git a/mobile-oa-h5/src/oa/components/OaDeptSelect.vue b/mobile-oa-h5/src/oa/components/OaDeptSelect.vue
index 8a8c25ab..69a17ce8 100644
--- a/mobile-oa-h5/src/oa/components/OaDeptSelect.vue
+++ b/mobile-oa-h5/src/oa/components/OaDeptSelect.vue
@@ -4,6 +4,7 @@
:model-value="displayText"
readonly
is-link
+ input-align="left"
:disabled="disabled"
placeholder="请选择部门"
@click="openPicker"
@@ -15,7 +16,13 @@
选择部门
确定
-
+
@@ -26,8 +33,8 @@
:title="d.label"
@click="toggle(d.id)"
>
-
-
+
+
@@ -41,8 +48,8 @@
:title="d.label"
@click="pendingSingle = d.id"
>
-
-
+
+
@@ -187,26 +194,3 @@ onMounted(() => {
})
-
diff --git a/mobile-oa-h5/src/oa/components/OaMoneyField.vue b/mobile-oa-h5/src/oa/components/OaMoneyField.vue
index 252505ba..e4d2c77e 100644
--- a/mobile-oa-h5/src/oa/components/OaMoneyField.vue
+++ b/mobile-oa-h5/src/oa/components/OaMoneyField.vue
@@ -4,6 +4,7 @@
v-model="text"
type="number"
inputmode="decimal"
+ input-align="left"
:disabled="disabled"
:placeholder="placeholder"
@update:model-value="onInput"
diff --git a/mobile-oa-h5/src/oa/components/OaNumberField.vue b/mobile-oa-h5/src/oa/components/OaNumberField.vue
new file mode 100644
index 00000000..32e21961
--- /dev/null
+++ b/mobile-oa-h5/src/oa/components/OaNumberField.vue
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
diff --git a/mobile-oa-h5/src/oa/components/OaPersonSelect.vue b/mobile-oa-h5/src/oa/components/OaPersonSelect.vue
index b2f7bff8..9f79d4f9 100644
--- a/mobile-oa-h5/src/oa/components/OaPersonSelect.vue
+++ b/mobile-oa-h5/src/oa/components/OaPersonSelect.vue
@@ -4,6 +4,7 @@
:model-value="displayText"
readonly
is-link
+ input-align="left"
:disabled="disabled"
placeholder="请选择人员"
@click="openPicker"
@@ -15,7 +16,13 @@
选择人员
确定
-
+
@@ -27,8 +34,8 @@
:label="u.sub || undefined"
@click="toggle(u.id)"
>
-
-
+
+
@@ -43,8 +50,8 @@
:label="u.sub || undefined"
@click="pendingSingle = u.id"
>
-
-
+
+
@@ -150,7 +157,7 @@ function confirm() {
async function loadUsers() {
loading.value = true
try {
- const pageSize = 200
+ const pageSize = 100
const raw = []
let page = 1
for (;;) {
@@ -200,27 +207,3 @@ onMounted(() => {
loadUsers()
})
-
-
diff --git a/mobile-oa-h5/src/oa/components/OaPostSelect.vue b/mobile-oa-h5/src/oa/components/OaPostSelect.vue
index cb80fc66..2a570c29 100644
--- a/mobile-oa-h5/src/oa/components/OaPostSelect.vue
+++ b/mobile-oa-h5/src/oa/components/OaPostSelect.vue
@@ -4,6 +4,7 @@
:model-value="displayText"
readonly
is-link
+ input-align="left"
:disabled="disabled"
placeholder="请选择职位"
@click="openPicker"
@@ -15,7 +16,13 @@
选择职位
确定
-
+
@@ -27,8 +34,8 @@
:label="p.sub || undefined"
@click="toggle(p.id)"
>
-
-
+
+
@@ -43,8 +50,8 @@
:label="p.sub || undefined"
@click="pendingSingle = p.id"
>
-
-
+
+
@@ -150,7 +157,7 @@ function confirm() {
async function loadPosts() {
loading.value = true
try {
- const pageSize = 200
+ const pageSize = 100
const raw = []
let page = 1
for (;;) {
@@ -197,26 +204,3 @@ onMounted(() => {
})
-
diff --git a/mobile-oa-h5/src/oa/components/OaReceiveAccountSelect.vue b/mobile-oa-h5/src/oa/components/OaReceiveAccountSelect.vue
index 5b16efc0..4df1c2aa 100644
--- a/mobile-oa-h5/src/oa/components/OaReceiveAccountSelect.vue
+++ b/mobile-oa-h5/src/oa/components/OaReceiveAccountSelect.vue
@@ -4,6 +4,7 @@
:model-value="summary"
readonly
is-link
+ input-align="left"
:disabled="disabled"
placeholder="请选择收款账户"
@click="openPicker"
@@ -21,7 +22,13 @@
选择收款账户
确定
-
+
-
-
+
+
@@ -146,29 +153,9 @@ onMounted(() => {
color: var(--text-muted, #6b7b74);
font-size: 12px;
line-height: 1.5;
+ text-align: left;
}
.oa-recv__line + .oa-recv__line {
margin-top: 4px;
}
-.oa-recv__sheet {
- display: flex;
- flex-direction: column;
- height: 100%;
- background: #fff;
-}
-.oa-recv__hd {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 14px 16px;
- border-bottom: 1px solid var(--border, #dcebe4);
-}
-.oa-recv__ok {
- color: var(--brand-green, #0a9f6d);
- font-weight: 600;
-}
-.oa-recv__list {
- flex: 1;
- overflow: auto;
-}
diff --git a/mobile-oa-h5/src/oa/components/OaRelatedApproval.vue b/mobile-oa-h5/src/oa/components/OaRelatedApproval.vue
index daccaeb7..824223c4 100644
--- a/mobile-oa-h5/src/oa/components/OaRelatedApproval.vue
+++ b/mobile-oa-h5/src/oa/components/OaRelatedApproval.vue
@@ -61,6 +61,9 @@
@@ -74,8 +77,8 @@
:label="pickerLabel(row)"
@click="toggle(row.id)"
>
-
-
+
+
@@ -266,25 +269,4 @@ watch(
flex: 0 0 auto;
color: #94a3b8;
}
-.oa-rel__sheet {
- display: flex;
- flex-direction: column;
- height: 100%;
- background: #fff;
-}
-.oa-rel__hd {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 14px 16px;
- border-bottom: 1px solid var(--border, #dcebe4);
-}
-.oa-rel__ok {
- color: var(--brand-green, #0a9f6d);
- font-weight: 600;
-}
-.oa-rel__picker {
- flex: 1;
- overflow: auto;
-}
diff --git a/mobile-oa-h5/src/oa/components/OaStubField.vue b/mobile-oa-h5/src/oa/components/OaStubField.vue
index 5927ba23..6da27832 100644
--- a/mobile-oa-h5/src/oa/components/OaStubField.vue
+++ b/mobile-oa-h5/src/oa/components/OaStubField.vue
@@ -3,6 +3,7 @@
{{ title || typeName }}
diff --git a/mobile-oa-h5/src/oa/components/OaTripItinerary.vue b/mobile-oa-h5/src/oa/components/OaTripItinerary.vue
index b2b4bb89..e953f093 100644
--- a/mobile-oa-h5/src/oa/components/OaTripItinerary.vue
+++ b/mobile-oa-h5/src/oa/components/OaTripItinerary.vue
@@ -16,6 +16,8 @@
patch(idx, { fromCity: v })"
@@ -41,6 +47,8 @@
patch(idx, { toCity: v })"
@@ -48,6 +56,8 @@
patch(idx, { duration: v === '' ? null : Number(v) })"
diff --git a/mobile-oa-h5/src/oa/form-option.js b/mobile-oa-h5/src/oa/form-option.js
new file mode 100644
index 00000000..cb3e87ee
--- /dev/null
+++ b/mobile-oa-h5/src/oa/form-option.js
@@ -0,0 +1,10 @@
+/** OA 表单全局 option:标题在上、内容左对齐(覆盖 form-create/vant 默认右对齐) */
+export const oaFormOption = {
+ submitBtn: false,
+ resetBtn: false,
+ form: {
+ labelAlign: 'top',
+ inputAlign: 'left',
+ colon: false
+ }
+}
diff --git a/mobile-oa-h5/src/oa/kitchen-sink-rule.js b/mobile-oa-h5/src/oa/kitchen-sink-rule.js
index 8a423152..15da18ab 100644
--- a/mobile-oa-h5/src/oa/kitchen-sink-rule.js
+++ b/mobile-oa-h5/src/oa/kitchen-sink-rule.js
@@ -1,4 +1,5 @@
/** 按 zkxw-sc 已发布模板实际出现的 type 构造;套件带 children(与 Web 结构一致) */
+import { oaFormOption } from './form-option.js'
const LEAVE_TYPE_OPTIONS = [
{ label: '事假', value: '事假' },
@@ -39,7 +40,7 @@ export const kitchenSinkRule = [
{ label: '紧急', value: 'urgent' }
]
},
- { type: 'inputNumber', field: 'qty', title: '数字', value: 1 },
+ { type: 'inputNumber', field: 'qty', title: '数字', value: 1, props: { placeholder: '请输入数字,可含小数' } },
{
type: 'datePicker',
field: 'bizDate',
@@ -277,7 +278,5 @@ export const kitchenSinkRule = [
]
export const kitchenSinkOption = {
- submitBtn: false,
- resetBtn: false,
- form: { labelPosition: 'top' }
+ ...oaFormOption
}
diff --git a/mobile-oa-h5/src/oa/register-components.js b/mobile-oa-h5/src/oa/register-components.js
index b0c9a1b4..b962f4d2 100644
--- a/mobile-oa-h5/src/oa/register-components.js
+++ b/mobile-oa-h5/src/oa/register-components.js
@@ -1,4 +1,5 @@
import OaMoneyField from './components/OaMoneyField.vue'
+import OaNumberField from './components/OaNumberField.vue'
import OaUploadField from './components/OaUploadField.vue'
import OaReceiveAccountSelect from './components/OaReceiveAccountSelect.vue'
import OaRelatedApproval from './components/OaRelatedApproval.vue'
@@ -12,6 +13,7 @@ import OaTripItinerary from './components/OaTripItinerary.vue'
/** 与 web-admin type 名对齐 */
const REAL_COMPONENTS = {
+ inputNumber: OaNumberField,
money: OaMoneyField,
upload: OaUploadField,
receiveAccount: OaReceiveAccountSelect,
diff --git a/mobile-oa-h5/src/styles/oa-form.css b/mobile-oa-h5/src/styles/oa-form.css
new file mode 100644
index 00000000..c8d0c221
--- /dev/null
+++ b/mobile-oa-h5/src/styles/oa-form.css
@@ -0,0 +1,275 @@
+/* form-create + Vant:上下布局与左对齐兜底 */
+.form-create-m .van-field__label--top {
+ width: 100%;
+ margin-bottom: 6px;
+ text-align: left;
+ color: var(--text-primary, #1f2a26);
+ font-size: 13px;
+ font-weight: 600;
+}
+
+.form-create-m .van-field__control {
+ text-align: left !important;
+}
+
+.form-create-m .van-field__control::placeholder {
+ text-align: left;
+ color: var(--text-muted, #6b7b74);
+}
+
+.form-create-m .fc-form-item {
+ margin-bottom: 4px;
+}
+
+/* 自定义控件 / 内置选择触发条:占满整行,箭头贴右 */
+.form-create-m ._fc-select,
+.form-create-m ._fc-date-picker,
+.form-create-m ._fc-time-picker,
+.form-create-m ._fc-calendar,
+.form-create-m ._fc-cascader,
+.form-create-m .oa-pick,
+.form-create-m .oa-money,
+.form-create-m .oa-number,
+.form-create-m .oa-recv,
+.form-create-m .oa-rel,
+.form-create-m .oa-trip,
+.form-create-m .oa-upload {
+ display: block;
+ width: 100%;
+ box-sizing: border-box;
+}
+
+/*
+ * 内层触发 Field 会继承 Form 的 labelAlign=top,导致 value 占满一行、箭头折到下一行最左。
+ * 这里强制单行横排:value 撑满,right-icon 固定右侧。
+ */
+.form-create-m ._fc-select > .van-cell,
+.form-create-m ._fc-date-picker > .van-cell,
+.form-create-m ._fc-time-picker > .van-cell,
+.form-create-m ._fc-calendar > .van-cell,
+.form-create-m ._fc-cascader > .van-cell,
+.form-create-m .oa-pick > .van-cell,
+.form-create-m .oa-money > .van-cell,
+.form-create-m .oa-number > .van-cell,
+.form-create-m .oa-recv > .van-cell {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: nowrap;
+ align-items: center;
+ width: 100%;
+ box-sizing: border-box;
+}
+
+.form-create-m ._fc-select > .van-cell > .van-cell__title,
+.form-create-m ._fc-date-picker > .van-cell > .van-cell__title,
+.form-create-m ._fc-time-picker > .van-cell > .van-cell__title,
+.form-create-m ._fc-calendar > .van-cell > .van-cell__title,
+.form-create-m ._fc-cascader > .van-cell > .van-cell__title,
+.form-create-m .oa-pick > .van-cell > .van-cell__title,
+.form-create-m .oa-recv > .van-cell > .van-cell__title {
+ display: none;
+}
+
+.form-create-m ._fc-select > .van-cell > .van-cell__value,
+.form-create-m ._fc-date-picker > .van-cell > .van-cell__value,
+.form-create-m ._fc-time-picker > .van-cell > .van-cell__value,
+.form-create-m ._fc-calendar > .van-cell > .van-cell__value,
+.form-create-m ._fc-cascader > .van-cell > .van-cell__value,
+.form-create-m .oa-pick > .van-cell > .van-cell__value,
+.form-create-m .oa-money > .van-cell > .van-cell__value,
+.form-create-m .oa-number > .van-cell > .van-cell__value,
+.form-create-m .oa-recv > .van-cell > .van-cell__value {
+ flex: 1 1 auto;
+ width: auto !important;
+ max-width: none !important;
+ min-width: 0;
+ text-align: left;
+}
+
+.form-create-m ._fc-select > .van-cell > .van-cell__right-icon,
+.form-create-m ._fc-date-picker > .van-cell > .van-cell__right-icon,
+.form-create-m ._fc-time-picker > .van-cell > .van-cell__right-icon,
+.form-create-m ._fc-calendar > .van-cell > .van-cell__right-icon,
+.form-create-m ._fc-cascader > .van-cell > .van-cell__right-icon,
+.form-create-m .oa-pick > .van-cell > .van-cell__right-icon,
+.form-create-m .oa-recv > .van-cell > .van-cell__right-icon {
+ flex: 0 0 auto;
+ margin-left: 8px;
+ order: 3;
+}
+
+/* 行程内带自身 label 的 Field:标题在上,箭头仍在内容行右侧 */
+.form-create-m .oa-trip .van-field--label-top {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ width: 100%;
+}
+
+.form-create-m .oa-trip .van-field--label-top > .van-cell__title {
+ flex: 0 0 100%;
+ width: 100%;
+}
+
+.form-create-m .oa-trip .van-field--label-top > .van-cell__value {
+ flex: 1 1 auto;
+ min-width: 0;
+ text-align: left;
+}
+
+.form-create-m .oa-trip .van-field--label-top > .van-cell__right-icon {
+ flex: 0 0 auto;
+ margin-left: 8px;
+}
+
+/* 外层表单项:标题独占一行;内容区整行左对齐(箭头在内层控件右侧) */
+.form-create-m .fc-form-item.van-field--label-top {
+ flex-wrap: wrap;
+}
+
+.form-create-m .fc-form-item.van-field--label-top > .van-cell__title {
+ flex: 0 0 100%;
+ width: 100%;
+ margin-bottom: 6px;
+}
+
+.form-create-m .fc-form-item.van-field--label-top > .van-cell__value {
+ flex: 1 1 100%;
+ width: 100%;
+ max-width: 100%;
+}
+
+/* 选择弹窗通用 */
+.oa-pick__sheet,
+.oa-recv__sheet,
+.oa-rel__sheet {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ background: #fff;
+}
+
+.oa-pick__hd,
+.oa-recv__hd,
+.oa-rel__hd {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 14px 16px;
+ border-bottom: 1px solid var(--border, #dcebe4);
+}
+
+.oa-pick__hd strong,
+.oa-recv__hd strong,
+.oa-rel__hd strong {
+ font-size: 16px;
+ font-weight: 700;
+ color: var(--text-primary, #1f2a26);
+}
+
+.oa-pick__hd > span:first-child,
+.oa-recv__hd > span:first-child,
+.oa-rel__hd > span:first-child {
+ min-width: 48px;
+ color: var(--text-muted, #6b7b74);
+ font-size: 14px;
+}
+
+.oa-pick__ok,
+.oa-recv__ok,
+.oa-rel__ok {
+ min-width: 48px;
+ color: var(--brand-green, #0a9f6d);
+ font-size: 14px;
+ font-weight: 600;
+ text-align: right;
+}
+
+.oa-pick__list,
+.oa-recv__list,
+.oa-rel__picker {
+ flex: 1;
+ padding: 12px;
+ overflow: auto;
+ background: transparent;
+ box-sizing: border-box;
+}
+
+.oa-pick__list .van-cell,
+.oa-recv__list .van-cell,
+.oa-rel__picker .van-cell {
+ align-items: center;
+ margin-bottom: 10px;
+ padding: 12px 14px;
+ text-align: left;
+ background: transparent;
+ border-radius: 10px;
+}
+
+.oa-pick__list .van-cell:last-child,
+.oa-recv__list .van-cell:last-child,
+.oa-rel__picker .van-cell:last-child {
+ margin-bottom: 0;
+}
+
+.oa-pick__list .van-cell::after,
+.oa-recv__list .van-cell::after,
+.oa-rel__picker .van-cell::after {
+ display: none;
+}
+
+.oa-pick__list .van-cell__title,
+.oa-recv__list .van-cell__title,
+.oa-rel__picker .van-cell__title {
+ text-align: left;
+}
+
+.oa-pick__list .van-cell__label,
+.oa-recv__list .van-cell__label,
+.oa-rel__picker .van-cell__label {
+ text-align: left;
+ color: var(--text-muted, #6b7b74);
+}
+
+.oa-pick__check,
+.oa-recv__check,
+.oa-rel__check {
+ margin-right: 8px;
+}
+
+/* 弹窗检索:勿继承表单 labelAlign=top,保持单行垂直居中 */
+.oa-pick__sheet .van-search,
+.oa-recv__sheet .van-search,
+.oa-rel__sheet .van-search {
+ padding: 10px 12px;
+ background: #fff;
+}
+
+.oa-pick__sheet .van-search .van-search__content,
+.oa-recv__sheet .van-search .van-search__content,
+.oa-rel__sheet .van-search .van-search__content {
+ align-items: center;
+}
+
+.oa-pick__sheet .van-search .van-cell,
+.oa-recv__sheet .van-search .van-cell,
+.oa-rel__sheet .van-search .van-cell {
+ align-items: center;
+ padding-top: 0;
+ padding-bottom: 0;
+ min-height: 36px;
+}
+
+.oa-pick__sheet .van-search .van-field__body,
+.oa-recv__sheet .van-search .van-field__body,
+.oa-rel__sheet .van-search .van-field__body {
+ align-items: center;
+}
+
+.oa-pick__sheet .van-search .van-field__control,
+.oa-recv__sheet .van-search .van-field__control,
+.oa-rel__sheet .van-search .van-field__control {
+ height: 36px;
+ line-height: 36px;
+ text-align: left !important;
+}
diff --git a/mobile-oa-h5/src/utils/auth.js b/mobile-oa-h5/src/utils/auth.js
index 3906d343..3d38ca11 100644
--- a/mobile-oa-h5/src/utils/auth.js
+++ b/mobile-oa-h5/src/utils/auth.js
@@ -1,9 +1,45 @@
-/** 从 URL ?token= 读取登录态(小程序 web-view 传入) */
-export function getToken() {
+/** 小程序 web-view 通过 URL ?token= 传入登录态;落盘避免路由跳转后丢失 */
+const TOKEN_KEY = 'zkxw_oa_h5_token'
+
+function readQueryToken() {
try {
- const q = new URLSearchParams(window.location.search)
+ const q = new URLSearchParams(window.location.search || '')
return String(q.get('token') || '').trim()
} catch {
return ''
}
}
+
+/** 启动时调用:把 URL 中的 token 写入 sessionStorage,并尽量从地址栏摘掉敏感参数 */
+export function captureTokenFromUrl() {
+ const fromQuery = readQueryToken()
+ if (fromQuery) {
+ try {
+ sessionStorage.setItem(TOKEN_KEY, fromQuery)
+ } catch {
+ /* ignore quota / private mode */
+ }
+ try {
+ const url = new URL(window.location.href)
+ if (url.searchParams.has('token')) {
+ url.searchParams.delete('token')
+ const next = `${url.pathname}${url.search}${url.hash}`
+ window.history.replaceState(window.history.state, '', next)
+ }
+ } catch {
+ /* ignore */
+ }
+ return fromQuery
+ }
+ return getToken()
+}
+
+export function getToken() {
+ try {
+ const cached = String(sessionStorage.getItem(TOKEN_KEY) || '').trim()
+ if (cached) return cached
+ } catch {
+ /* ignore */
+ }
+ return readQueryToken()
+}