af-mobile-client-vue3 1.2.26 → 1.2.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CLAUDE.md ADDED
@@ -0,0 +1,184 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ This is a Vue 3 mobile client application (`af-mobile-client-vue3`) built as a smart gas system (智慧燃气) mobile application. It serves as a micro-frontend main application with comprehensive business components and dynamic form capabilities.
8
+
9
+ ## Development Commands
10
+
11
+ ```bash
12
+ # Install dependencies
13
+ pnpm install
14
+
15
+ # Development server (with mock server on port 8086)
16
+ pnpm dev
17
+
18
+ # Build for production
19
+ pnpm build
20
+
21
+ # Build for development environment
22
+ pnpm build:dev
23
+
24
+ # Lint and type checking
25
+ pnpm lint
26
+
27
+ # Auto-fix linting issues
28
+ pnpm lint:fix
29
+
30
+ # Type checking only
31
+ pnpm typecheck
32
+
33
+ # Version release
34
+ pnpm release
35
+ ```
36
+
37
+ ## Technology Stack
38
+
39
+ - **Vue 3** with Composition API and `<script setup>` syntax
40
+ - **TypeScript** for type safety
41
+ - **Vite** as build tool (port 7190)
42
+ - **Vant 4** as primary UI component library
43
+ - **Pinia** for state management with persistence
44
+ - **pnpm** as package manager (requires Node.js >=20.19.0)
45
+ - **UnoCSS** for atomic CSS utilities
46
+ - **@micro-zoe/micro-app** for micro-frontend architecture
47
+
48
+ ## Code Style & Standards
49
+
50
+ From `.cursorrules`:
51
+
52
+ - Use Composition API and `<script setup>` syntax
53
+ - Component names: PascalCase (e.g., `UserProfile`)
54
+ - Variables: camelCase (e.g., `userName`)
55
+ - Boolean variables: use `is/has/should` prefix (e.g., `isLoading`)
56
+ - Event handlers: use `handle` prefix (e.g., `handleSubmit`)
57
+ - CSS classes: kebab-case (e.g., `user-profile`)
58
+ - **Must explicitly import Vant components** - auto-import is disabled for this component library project
59
+ - Use `interface` for type definitions, `type` for unions/intersections
60
+ - Props must specify types
61
+
62
+ ## Architecture
63
+
64
+ ### Directory Structure
65
+
66
+ ```
67
+ src/
68
+ ├── components/
69
+ │ ├── core/ # Core UI components (NavBar, Tabbar, Uploader)
70
+ │ ├── data/ # Business data components
71
+ │ │ ├── XReportForm/ # Dynamic form with JSON configuration
72
+ │ │ ├── XReportGrid/ # Data grid with reporting
73
+ │ │ ├── XForm/ # General form components
74
+ │ │ └── XOlMap/ # OpenLayers map integration
75
+ │ └── layout/ # Layout components
76
+ ├── stores/ # Pinia state management
77
+ ├── utils/ # Utility functions
78
+ ├── views/ # Page components
79
+ ├── router/ # Vue Router configuration
80
+ └── api/ # API layer
81
+ ```
82
+
83
+ ### Key Business Components
84
+
85
+ **XReportForm Component** (`src/components/data/XReportForm/index.vue`):
86
+
87
+ - Dynamic form generation from JSON configuration
88
+ - Supports field types: `input`, `datePicker`, `timePicker`, `dateTimeSecondsPicker`, `curDateInput`, `signature`, `images`, `inputs`, `inputColumns`
89
+ - Built-in validation with custom error messages
90
+ - Slot-based extensibility for complex layouts
91
+ - Integration with file upload and signature capture
92
+
93
+ **XReportGrid Component**:
94
+
95
+ - Data grid with reporting capabilities
96
+ - Print functionality integration
97
+ - Design mode for form configuration
98
+
99
+ **XOlMap Component**:
100
+
101
+ - OpenLayers integration for GIS functionality
102
+ - Location picker with coordinate transformation
103
+
104
+ ### State Management
105
+
106
+ - **Pinia stores** with persistence via `pinia-plugin-persistedstate`
107
+ - **User store**: Authentication, permissions, user data
108
+ - **Settings store**: Application configuration
109
+ - **Route cache store**: Performance optimization
110
+
111
+ ### API Layer
112
+
113
+ - **Axios-based HTTP client** with interceptors
114
+ - **Request/Response transformers** for v3/v4 API compatibility
115
+ - **Automatic token management** and error handling
116
+ - **Mock server integration** for development
117
+
118
+ ## Development Workflow
119
+
120
+ ### Server Configuration
121
+
122
+ - Development server runs on port 7190
123
+ - Mock server runs on port 8086
124
+ - Multiple API proxy endpoints configured in `vite.config.ts`
125
+
126
+ ### Build Process
127
+
128
+ - Code splitting: `third` (node_modules), `views` (business pages)
129
+ - Assets organized in `static/` directory with hashing
130
+ - CSS code splitting disabled for mobile optimization
131
+ - Legacy browser support via `@vitejs/plugin-legacy`
132
+
133
+ ### Git Hooks
134
+
135
+ - Pre-commit: `pnpm lint-staged` (ESLint auto-fix)
136
+ - Commit message: `pnpm commitlint` (conventional commits)
137
+
138
+ ## Micro-Frontend Integration
139
+
140
+ This is a main application for micro-frontend architecture:
141
+
142
+ - Uses `@micro-zoe/micro-app` for micro-app management
143
+ - Child apps register in `microApps.ts`
144
+ - Supports dynamic loading and routing
145
+ - Unmount function available at `window.unmount`
146
+
147
+ ## Mobile-Specific Features
148
+
149
+ - **@vant/touch-emulator** for desktop development
150
+ - **postcss-mobile-forever** for viewport handling
151
+ - **vite-plugin-pwa** for PWA capabilities
152
+ - **VConsole** for mobile debugging
153
+ - Dark mode support throughout the application
154
+
155
+ ## Common Patterns
156
+
157
+ ### Adding New Field Types to XReportForm
158
+
159
+ 1. Add type to `generateDefaultRequiredMessage()` function
160
+ 2. Add case in `formatConfigToForm()` switch statement
161
+ 3. Add template section in the component template
162
+ 4. Follow existing patterns for validation and data binding
163
+
164
+ ### Component Development
165
+
166
+ - Use `<script setup lang="ts">` syntax
167
+ - Explicitly import Vant components
168
+ - Define props with TypeScript interfaces
169
+ - Use `defineEmits` for events
170
+ - Follow existing component structure patterns
171
+
172
+ ### API Integration
173
+
174
+ - Use the existing HTTP client in `src/utils/http/`
175
+ - Follow v3/v4 API patterns established in the codebase
176
+ - Handle errors consistently with existing patterns
177
+
178
+ ## Testing & Quality
179
+
180
+ - **ESLint** with `@antfu/eslint-config`
181
+ - **TypeScript** strict mode
182
+ - **Vue TSC** for type checking
183
+ - **Commitlint** for commit message standards
184
+ - **Lint-staged** for pre-commit hooks
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "af-mobile-client-vue3",
3
3
  "type": "module",
4
- "version": "1.2.26",
4
+ "version": "1.2.28",
5
5
  "packageManager": "pnpm@10.12.3",
6
6
  "description": "Vue + Vite component lib",
7
7
  "engines": {
@@ -146,6 +146,14 @@ const slots = useSlots()
146
146
  // 当前组件实例(不推荐使用,可能会在后续的版本更迭中调整,暂时用来绑定函数的上下文)
147
147
  const currInst = getCurrentInstance()
148
148
 
149
+ // 列表底部的文字显示
150
+ function finishedBottomText() {
151
+ if (buttonState.value?.add && buttonState.value.add === true && (filterButtonPermissions('add').state === false || ((filterButtonPermissions('add').state === true && userState.f.resources.f_role_name.includes((filterButtonPermissions('add').roleStr))))))
152
+ return '已加载全部内容,如需新增请点击右上角的 + 号'
153
+ else
154
+ return '已加载全部内容'
155
+ }
156
+
149
157
  onBeforeMount(() => {
150
158
  initComponent()
151
159
  })
@@ -584,7 +592,7 @@ defineExpose({
584
592
  v-model:loading="loading"
585
593
  class="list_main"
586
594
  :finished="finished"
587
- finished-text="已加载全部内容"
595
+ :finished-text="finishedBottomText()"
588
596
  :immediate-check="isInitQuery"
589
597
  @load="onLoad"
590
598
  >
@@ -38,6 +38,7 @@ interface GroupFormItems {
38
38
  groupName?: string
39
39
  tableName?: string
40
40
  paramLogicName?: string
41
+ isGroupForm?: boolean
41
42
  }
42
43
 
43
44
  interface InitParams {
@@ -56,6 +57,7 @@ const props = withDefaults(defineProps<{
56
57
  submitButton?: boolean
57
58
  isHandleFormKey?: boolean
58
59
  paramLogicNameParam?: any
60
+ isGroupForm?: boolean
59
61
  }>(), {
60
62
  configName: undefined,
61
63
  groupFormItems: null,
@@ -66,6 +68,7 @@ const props = withDefaults(defineProps<{
66
68
  submitButton: true,
67
69
  isHandleFormKey: true,
68
70
  paramLogicNameParam: {},
71
+ isGroupForm: false,
69
72
  })
70
73
 
71
74
  const emits = defineEmits(['onSubmit', 'xFormItemEmitFunc'])
@@ -569,7 +572,7 @@ defineExpose({ init, form, formGroupName, validate, asyncSubmit, setForm, getFor
569
572
  </script>
570
573
 
571
574
  <template>
572
- <VanForm v-if="loaded" ref="xFormRef" class="x-form-container" @submit="onSubmit">
575
+ <VanForm v-if="loaded" ref="xFormRef" class="x-form-container" :class="{ 'use-full-height': !props.isGroupForm }" @submit="onSubmit">
573
576
  <div class="form-fields-scrollable">
574
577
  <VanCellGroup inset>
575
578
  <XFormItem
@@ -608,8 +611,11 @@ defineExpose({ init, form, formGroupName, validate, asyncSubmit, setForm, getFor
608
611
  </template>
609
612
 
610
613
  <style scoped>
611
- .x-form-container {
614
+ .use-full-height {
612
615
  height: calc(100vh - var(--van-nav-bar-height) - 20px);
616
+ }
617
+
618
+ .x-form-container {
613
619
  display: flex;
614
620
  flex-direction: column;
615
621
  max-height: 100vh;
@@ -1,12 +1,8 @@
1
1
  <script setup lang="ts">
2
2
  import XForm from '@af-mobile-client-vue3/components/data/XForm/index.vue'
3
3
  import { getConfigByName } from '@af-mobile-client-vue3/services/api/common'
4
- import {
5
- Button as VanButton,
6
- Tab as VanTab,
7
- Tabs as VanTabs,
8
- } from 'vant'
9
- import { defineEmits, defineProps, onBeforeMount, ref, watch } from 'vue'
4
+ import { Button as VanButton, Tab as VanTab, Tabs as VanTabs } from 'vant'
5
+ import { defineEmits, defineProps, onBeforeMount, onMounted, ref, watch } from 'vue'
10
6
 
11
7
  const props = withDefaults(defineProps<{
12
8
  configName?: string
@@ -34,16 +30,12 @@ const submitGroup = ref(false)
34
30
  const submitSimple = ref(false)
35
31
  const isInit = ref(false)
36
32
  const initStatus = ref(false)
37
- const propsData = ref({})
33
+ const propsData = ref<Form>({})
38
34
 
39
35
  // 组件初始化函数
40
36
  function init(params: Form) {
41
37
  initStatus.value = true
42
38
  propsData.value = {
43
- // configName: '',
44
- // serviceName: undefined,
45
- // groupFormData: () => ({}),
46
- // mode: '查询',
47
39
  configName: props.configName,
48
40
  serviceName: props.serviceName,
49
41
  groupFormData: props.groupFormData,
@@ -53,7 +45,6 @@ function init(params: Form) {
53
45
  formData.value = propsData.value.groupFormData
54
46
  getConfigByName(propsData.value.configName, (result) => {
55
47
  if (result?.groups) {
56
- // submitGroup.value = true
57
48
  groupItems.value = result.groups
58
49
  result.groups.forEach((group) => {
59
50
  if (!formData.value[group.groupName])
@@ -63,7 +54,7 @@ function init(params: Form) {
63
54
  })
64
55
  }
65
56
  else {
66
- submitSimple.value = result.showSubmitBtn
57
+ submitSimple.value = result?.showSubmitBtn
67
58
  groupItems.value = [{ ...result }]
68
59
  }
69
60
  isInit.value = true
@@ -85,26 +76,32 @@ async function submit() {
85
76
  emit('submit', formData.value)
86
77
  }
87
78
 
88
- // function initXForm(index: number) {
89
- // 获取自身示例
90
- // refs[`xFormListRef-${index}`].init({})
91
- // }
79
+ // 动态计算 offsetTop = var(--van-nav-bar-height) + 10px
80
+ const offsetTop = ref(0)
81
+ onMounted(() => {
82
+ const root = document.documentElement
83
+ const navBarHeight = getComputedStyle(root).getPropertyValue('--van-nav-bar-height')
84
+ offsetTop.value = Number.parseInt(navBarHeight, 10) || 60 + 10
85
+ })
92
86
 
93
87
  defineExpose({ init })
94
88
  </script>
95
89
 
96
90
  <template>
97
91
  <div v-if="isInit" id="x-form-group">
98
- <VanTabs scrollspy sticky>
92
+ <VanTabs scrollspy sticky :offset-top="offsetTop">
99
93
  <VanTab
100
94
  v-for="(item, index) in groupItems"
101
95
  :key="item.groupName ? (item.groupName + index) : index"
102
96
  :title="item.describe ? item.describe : item.tableName "
103
97
  >
104
- <div class="x-form-group-item">
105
- <!-- :ref="`xFormListRef-${index}`" -->
98
+ <div
99
+ class="x-form-group-item"
100
+ :class="{ 'is-last': index === groupItems.length - 1 }"
101
+ >
106
102
  <XForm
107
103
  ref="xFormListRef"
104
+ :is-group-form="true"
108
105
  :mode="props.mode"
109
106
  :group-form-items="item"
110
107
  :form-data="item.groupName ? formData[item.groupName] : formData"
@@ -124,10 +121,23 @@ defineExpose({ init })
124
121
 
125
122
  <style scoped lang="less">
126
123
  #x-form-group {
124
+ display: flex;
125
+ flex-direction: column;
127
126
  background-color: rgb(247, 248, 250);
128
- padding-bottom: 10px;
127
+ height: calc(100vh - var(--van-nav-bar-height) - 20px);
128
+ flex: 1;
129
+ overflow-y: auto;
130
+ // 让 Tabs 区域自适应剩余空间
131
+ .van-tabs {
132
+ flex: 1;
133
+ min-height: 0;
134
+ overflow: auto;
135
+ }
129
136
  .x-form-group-item {
130
- margin: 20px 0;
137
+ margin-bottom: 20px;
131
138
  }
132
139
  }
140
+ .x-form-group-item.is-last {
141
+ min-height: calc(100vh - var(--van-nav-bar-height) - 40px);
142
+ }
133
143
  </style>
@@ -0,0 +1,208 @@
1
+ <script setup lang="ts">
2
+ import { Field as vanField, Picker as vanPicker, Popup as vanPopup } from 'vant'
3
+ import { computed, ref, watch } from 'vue'
4
+
5
+ interface Props {
6
+ modelValue?: string | number | Date
7
+ label?: string
8
+ placeholder?: string
9
+ title?: string
10
+ format?: string
11
+ }
12
+
13
+ const props = withDefaults(defineProps<Props>(), {
14
+ modelValue: undefined,
15
+ label: '选择时间',
16
+ placeholder: '请选择时间',
17
+ title: '选择完整时间',
18
+ format: 'YYYY-MM-DD HH:mm:ss',
19
+ })
20
+
21
+ const emit = defineEmits<{
22
+ 'update:modelValue': [value: string]
23
+ 'confirm': [value: string]
24
+ }>()
25
+
26
+ const showPicker = ref(false)
27
+ const columns = ref<any[]>([])
28
+ const selectedValues = ref<string[]>([])
29
+
30
+ const displayValue = computed(() => {
31
+ if (props.modelValue) {
32
+ const date = new Date(props.modelValue)
33
+ return formatDate(date, props.format)
34
+ }
35
+ return ''
36
+ })
37
+
38
+ watch(() => showPicker.value, (val) => {
39
+ if (val) {
40
+ columns.value = []
41
+ selectedValues.value = []
42
+ getColumns()
43
+ }
44
+ }, { immediate: true })
45
+
46
+ function formatDate(date: Date, format: string): string {
47
+ const year = date.getFullYear()
48
+ const month = String(date.getMonth() + 1).padStart(2, '0')
49
+ const day = String(date.getDate()).padStart(2, '0')
50
+ const hours = String(date.getHours()).padStart(2, '0')
51
+ const minutes = String(date.getMinutes()).padStart(2, '0')
52
+ const seconds = String(date.getSeconds()).padStart(2, '0')
53
+
54
+ return format
55
+ .replace('YYYY', String(year))
56
+ .replace('YY', String(year).slice(-2))
57
+ .replace('MM', month)
58
+ .replace('DD', day)
59
+ .replace('HH', hours)
60
+ .replace('mm', minutes)
61
+ .replace('ss', seconds)
62
+ }
63
+
64
+ function getColumns() {
65
+ const strtime = props.modelValue ? String(props.modelValue) : ''
66
+
67
+ let dateValues: Date
68
+ if (strtime) {
69
+ const date = new Date(strtime.replace(/-/g, '/'))
70
+ dateValues = date
71
+ }
72
+ else {
73
+ dateValues = new Date()
74
+ }
75
+
76
+ const Y = dateValues.getFullYear()
77
+ const M = dateValues.getMonth()
78
+ const D = dateValues.getDate()
79
+ const h = dateValues.getHours()
80
+ const m = dateValues.getMinutes()
81
+ const s = dateValues.getSeconds()
82
+
83
+ // 生成年份列 (前后10年)
84
+ const year: any[] = []
85
+ const currentYear = new Date().getFullYear()
86
+ for (let i = currentYear - 10; i < currentYear + 10; i++) {
87
+ year.push({ text: i.toString(), value: i.toString() })
88
+ }
89
+
90
+ // 生成月份列 (01-12)
91
+ const month: any[] = []
92
+ for (let i = 1; i <= 12; i++) {
93
+ const monthStr = i.toString().padStart(2, '0')
94
+ month.push({ text: monthStr, value: monthStr })
95
+ }
96
+
97
+ // 生成日期列 (01-31,根据当前年月动态计算)
98
+ const daysInMonth = getCountDays(Y, M + 1)
99
+ const day: any[] = []
100
+ for (let i = 1; i <= daysInMonth; i++) {
101
+ const dayStr = i.toString().padStart(2, '0')
102
+ day.push({ text: dayStr, value: dayStr })
103
+ }
104
+
105
+ // 生成小时列 (00-23)
106
+ const hour: any[] = []
107
+ for (let i = 0; i < 24; i++) {
108
+ const hourStr = i.toString().padStart(2, '0')
109
+ hour.push({ text: hourStr, value: hourStr })
110
+ }
111
+
112
+ // 生成分钟列 (00-59)
113
+ const minute: any[] = []
114
+ for (let i = 0; i < 60; i++) {
115
+ const minuteStr = i.toString().padStart(2, '0')
116
+ minute.push({ text: minuteStr, value: minuteStr })
117
+ }
118
+
119
+ // 生成秒钟列 (00-59)
120
+ const second: any[] = []
121
+ for (let i = 0; i < 60; i++) {
122
+ const secondStr = i.toString().padStart(2, '0')
123
+ second.push({ text: secondStr, value: secondStr })
124
+ }
125
+
126
+ columns.value = [year, month, day, hour, minute, second]
127
+
128
+ // 设置默认选中值
129
+ const _M = (M + 1).toString().padStart(2, '0')
130
+ const _D = D.toString().padStart(2, '0')
131
+ const _h = h.toString().padStart(2, '0')
132
+ const _m = m.toString().padStart(2, '0')
133
+ const _s = s.toString().padStart(2, '0')
134
+
135
+ selectedValues.value = [Y.toString(), _M, _D, _h, _m, _s]
136
+ }
137
+
138
+ function getCountDays(year: number, month: number): number {
139
+ const day = new Date(year, month, 0)
140
+ return day.getDate()
141
+ }
142
+
143
+ function onChange() {
144
+ // 当年月发生变化时,重新计算日期列
145
+ if (selectedValues.value.length >= 2) {
146
+ const year = Number.parseInt(selectedValues.value[0])
147
+ const month = Number.parseInt(selectedValues.value[1])
148
+ const currentDay = Number.parseInt(selectedValues.value[2])
149
+
150
+ const daysInMonth = getCountDays(year, month)
151
+
152
+ // 重新生成日期列
153
+ const dayColumn: any[] = []
154
+ for (let i = 1; i <= daysInMonth; i++) {
155
+ const dayStr = i.toString().padStart(2, '0')
156
+ dayColumn.push({ text: dayStr, value: dayStr })
157
+ }
158
+
159
+ // 如果当前选中的日期超过了新月份的最大天数,调整为最大天数
160
+ if (currentDay > daysInMonth) {
161
+ selectedValues.value[2] = daysInMonth.toString().padStart(2, '0')
162
+ }
163
+
164
+ // 更新列
165
+ columns.value[2] = dayColumn
166
+ }
167
+ }
168
+
169
+ function onCancel() {
170
+ showPicker.value = false
171
+ }
172
+
173
+ function onConfirm() {
174
+ const endval = `${selectedValues.value[0]}-${selectedValues.value[1]}-${selectedValues.value[2]} ${selectedValues.value[3]}:${selectedValues.value[4]}:${selectedValues.value[5]}`
175
+
176
+ emit('update:modelValue', endval)
177
+ emit('confirm', endval)
178
+ showPicker.value = false
179
+ }
180
+ </script>
181
+
182
+ <template>
183
+ <div>
184
+ <van-field
185
+ v-model="displayValue"
186
+ :label="label"
187
+ :placeholder="placeholder"
188
+ readonly
189
+ is-link
190
+ @click="showPicker = true"
191
+ />
192
+
193
+ <van-popup v-model:show="showPicker" position="bottom" round @close="onCancel">
194
+ <van-picker
195
+ v-model="selectedValues"
196
+ :title="title"
197
+ :columns="columns"
198
+ @change="onChange"
199
+ @cancel="onCancel"
200
+ @confirm="onConfirm"
201
+ />
202
+ </van-popup>
203
+ </div>
204
+ </template>
205
+
206
+ <style scoped>
207
+ /* 可以根据需要添加自定义样式 */
208
+ </style>
@@ -1,11 +1,11 @@
1
1
  <script setup lang="ts">
2
2
  import Uploader from '@af-mobile-client-vue3/components/core/Uploader/index.vue'
3
+ import DateTimeSecondsPicker from '@af-mobile-client-vue3/components/data/XReportForm/DateTimeSecondsPicker.vue'
3
4
  import XReportFormJsonRender from '@af-mobile-client-vue3/components/data/XReportForm/XReportFormJsonRender.vue'
4
5
  import XSignature from '@af-mobile-client-vue3/components/data/XSignature/index.vue'
5
6
  import { getConfigByNameWithoutIndexedDB } from '@af-mobile-client-vue3/services/api/common'
6
7
  import {
7
8
  showFailToast,
8
- Toast,
9
9
  Button as vanButton,
10
10
  Cell as vanCell,
11
11
  CellGroup as vanCellGroup,
@@ -17,9 +17,7 @@ import {
17
17
  Skeleton as vanSkeleton,
18
18
  } from 'vant'
19
19
  import { defineEmits, nextTick, reactive, ref, watch } from 'vue'
20
- // https://calendar.hxkj.vip/
21
- import VueHashCalendar from 'vue3-hash-calendar'
22
- import 'vue3-hash-calendar/es/index.css'
20
+ // 移除 VueHashCalendar,使用自定义的 DateTimeSecondsPicker
23
21
 
24
22
  // ------------------------- 类型定义 -------------------------
25
23
  interface configDefine {
@@ -66,12 +64,6 @@ let timer: NodeJS.Timeout
66
64
  const scanFinish = ref(false)
67
65
  // 折叠面板当前激活的值
68
66
  const activeCollapseName = ref('副标题')
69
- // 日期选择器显示状态
70
- const showDatePicker = ref(false)
71
- const showTimePicker = ref(false)
72
- // 当前操作的日期时间字段
73
- const currentDateField = ref('')
74
- const currentTimeField = ref('')
75
67
 
76
68
  watch(() => props.configName, () => {
77
69
  // 这里是你的处理逻辑
@@ -128,6 +120,8 @@ function generateDefaultRequiredMessage(field: any): string {
128
120
  return `请选择${label || '日期'}`
129
121
  case 'timePicker':
130
122
  return `请选择${label || '时间'}`
123
+ case 'dateTimeSecondsPicker':
124
+ return `请选择${label || '完整时间'}`
131
125
  case 'curDateInput':
132
126
  return `请设置${label || '时间'}`
133
127
  case 'signature':
@@ -564,6 +558,12 @@ function formatConfigToForm(config: configDefine): void {
564
558
  tempObj.type = 'timePicker'
565
559
  tempObj.required = row[j].required
566
560
  break
561
+ case 'dateTimeSecondsPicker' :
562
+ tempObj.dataIndex = row[j].dataIndex
563
+ tempObj.text = row[j].text
564
+ tempObj.type = 'dateTimeSecondsPicker'
565
+ tempObj.required = row[j].required
566
+ break
567
567
  case 'signature' :
568
568
  tempObj.dataIndex = row[j].dataIndex
569
569
  tempObj.text = row[j].text
@@ -855,45 +855,7 @@ function getNow() {
855
855
  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
856
856
  }
857
857
 
858
- // 打开日期选择器
859
- function openDatePicker(dataIndex: string) {
860
- console.log('打开日期选择器:', dataIndex)
861
- currentDateField.value = dataIndex
862
- showDatePicker.value = true
863
- console.log('日期选择器状态:', showDatePicker.value)
864
- }
865
-
866
- // 打开时间选择器
867
- function openTimePicker(dataIndex: string) {
868
- console.log('打开时间选择器:', dataIndex)
869
- currentTimeField.value = dataIndex
870
- showTimePicker.value = true
871
- console.log('时间选择器状态:', showTimePicker.value)
872
- }
873
-
874
- // 确认选择日期
875
- function onConfirmDate(value: any) {
876
- console.log('onConfirmDate', value)
877
- activatedConfig.data[currentDateField.value] = value
878
- showDatePicker.value = false
879
- currentDateField.value = ''
880
- }
881
-
882
- // 确认选择时间
883
- function onConfirmTime(value: any) {
884
- console.log('onConfirmTime', value)
885
- activatedConfig.data[currentTimeField.value] = value
886
- showTimePicker.value = false
887
- currentTimeField.value = ''
888
- }
889
-
890
- // 取消选择
891
- function onCancelPicker() {
892
- showDatePicker.value = false
893
- showTimePicker.value = false
894
- currentDateField.value = ''
895
- currentTimeField.value = ''
896
- }
858
+ // 移除了旧的日期时间选择器相关方法,现在使用 DateTimeSecondsPicker 组件
897
859
  </script>
898
860
 
899
861
  <template>
@@ -913,46 +875,46 @@ function onCancelPicker() {
913
875
  <!-- 标题 -->
914
876
  <h2 v-if="activatedConfig.title" class="title" v-html="activatedConfig.title" />
915
877
  <!-- 副标题 -->
916
- <div v-if="activatedConfig.subTitle && activatedConfig.subTitle.length > 0" class="form_item">
917
- <van-collapse v-model="activeCollapseName" accordion>
918
- <van-collapse-item title="副标题" name="副标题">
919
- <van-form>
920
- <van-cell-group inset>
921
- <template v-for="(subCell, cellIndex) in activatedConfig.subTitle" :key="cellIndex">
922
- <template v-if="subCell.type === 'inputs'">
923
- <template v-for="(item, itemIndex) in formatInputs(subCell)" :key="itemIndex">
924
- <van-field
925
- v-model="activatedConfig.data[subCell.dataIndex][itemIndex]"
926
- :label="`${item}:`"
927
- clearable
928
- type="textarea"
929
- rows="1"
930
- autosize
931
- :placeholder="`请输入${item}`"
932
- />
933
- </template>
934
- </template>
935
- <template v-else>
936
- <van-field
937
- v-model="activatedConfig.data[subCell.dataIndex]"
938
- :label="`${subCell.format}:`"
939
- clearable
940
- type="textarea"
941
- rows="1"
942
- autosize
943
- :placeholder="`请输入${subCell.format}`"
944
- />
945
- </template>
946
- </template>
947
- </van-cell-group>
948
- </van-form>
949
- </van-collapse-item>
950
- </van-collapse>
951
- </div>
878
+ <!-- <div v-if="activatedConfig.subTitle && activatedConfig.subTitle.length > 0" class="form_item"> -->
879
+ <!-- <van-collapse v-model="activeCollapseName" accordion> -->
880
+ <!-- <van-collapse-item title="副标题" name="副标题"> -->
881
+ <!-- <van-form> -->
882
+ <!-- <van-cell-group inset> -->
883
+ <!-- <template v-for="(subCell, cellIndex) in activatedConfig.subTitle" :key="cellIndex"> -->
884
+ <!-- <template v-if="subCell.type === 'inputs'"> -->
885
+ <!-- <template v-for="(item, itemIndex) in formatInputs(subCell)" :key="itemIndex"> -->
886
+ <!-- <van-field -->
887
+ <!-- v-model="activatedConfig.data[subCell.dataIndex][itemIndex]" -->
888
+ <!-- :label="`${item}:`" -->
889
+ <!-- clearable -->
890
+ <!-- type="textarea" -->
891
+ <!-- rows="1" -->
892
+ <!-- autosize -->
893
+ <!-- :placeholder="`请输入${item}`" -->
894
+ <!-- /> -->
895
+ <!-- </template> -->
896
+ <!-- </template> -->
897
+ <!-- <template v-else> -->
898
+ <!-- <van-field -->
899
+ <!-- v-model="activatedConfig.data[subCell.dataIndex]" -->
900
+ <!-- :label="`${subCell.format}:`" -->
901
+ <!-- clearable -->
902
+ <!-- type="textarea" -->
903
+ <!-- rows="1" -->
904
+ <!-- autosize -->
905
+ <!-- :placeholder="`请输入${subCell.format}`" -->
906
+ <!-- /> -->
907
+ <!-- </template> -->
908
+ <!-- </template> -->
909
+ <!-- </van-cell-group> -->
910
+ <!-- </van-form> -->
911
+ <!-- </van-collapse-item> -->
912
+ <!-- </van-collapse> -->
913
+ <!-- </div> -->
952
914
  <!-- 表单项 -->
953
915
  <div class="form_item">
954
916
  <van-collapse v-model="activeCollapseName" accordion>
955
- <van-collapse-item title="表单项" name="表单项">
917
+ <van-collapse-item title="操作卡内容" name="操作卡内容">
956
918
  <van-form>
957
919
  <template v-for="(row, index) in activatedConfig.columns" :key="index">
958
920
  <!-- value属性 -->
@@ -1020,7 +982,7 @@ function onCancelPicker() {
1020
982
  {{ row.valueText || '当前时间' }}
1021
983
  </div>
1022
984
  <div class="cur-date-input-value">
1023
- {{ activatedConfig.data[row.dataIndex] || '未设置' }}
985
+ {{ activatedConfig.data[row.dataIndex] || '未操作' }}
1024
986
  </div>
1025
987
  </div>
1026
988
  <div class="cur-date-input-action">
@@ -1058,22 +1020,32 @@ function onCancelPicker() {
1058
1020
  </van-cell-group>
1059
1021
  <!-- datePicker -->
1060
1022
  <van-cell-group v-else-if="row.type === 'datePicker'" inset :title="row.label" :class="{ 'required-field': row.required }">
1061
- <van-field
1023
+ <DateTimeSecondsPicker
1062
1024
  v-model="activatedConfig.data[row.dataIndex]"
1063
1025
  :label="`${row.label || '日期'}:`"
1064
1026
  placeholder="请选择日期"
1065
- readonly
1066
- @click="openDatePicker(row.dataIndex)"
1027
+ title="选择日期"
1028
+ format="YYYY-MM-DD"
1067
1029
  />
1068
1030
  </van-cell-group>
1069
1031
  <!-- timePicker -->
1070
1032
  <van-cell-group v-else-if="row.type === 'timePicker'" inset :title="row.label" :class="{ 'required-field': row.required }">
1071
- <van-field
1033
+ <DateTimeSecondsPicker
1072
1034
  v-model="activatedConfig.data[row.dataIndex]"
1073
1035
  :label="`${row.label || '时间'}:`"
1074
1036
  placeholder="请选择时间"
1075
- readonly
1076
- @click="openTimePicker(row.dataIndex)"
1037
+ title="选择时间"
1038
+ format="YYYY-MM-DD HH:mm:ss"
1039
+ />
1040
+ </van-cell-group>
1041
+ <!-- dateTimeSecondsPicker -->
1042
+ <van-cell-group v-else-if="row.type === 'dateTimeSecondsPicker'" inset :title="row.label" :class="{ 'required-field': row.required }">
1043
+ <DateTimeSecondsPicker
1044
+ v-model="activatedConfig.data[row.dataIndex]"
1045
+ :label="`${row.label || '完整时间'}:`"
1046
+ placeholder="请选择完整时间"
1047
+ title="选择完整时间"
1048
+ format="YYYY-MM-DD HH:mm:ss"
1077
1049
  />
1078
1050
  </van-cell-group>
1079
1051
  <!-- inputs -->
@@ -1235,31 +1207,29 @@ function onCancelPicker() {
1235
1207
  <van-skeleton title :row="5" />
1236
1208
  </div>
1237
1209
 
1238
- <!-- 日期选择器弹窗 -->
1239
- <VueHashCalendar v-model:visible="showDatePicker" format="YY-MM-DD" model="dialog" picker-type="date" @confirm="onConfirmDate" />
1240
- <VueHashCalendar v-model:visible="showTimePicker" format="YY-MM-DD hh:mm:ss" picker-type="datetime" model="dialog" @confirm="onConfirmTime" />
1210
+ <!-- 已移除 VueHashCalendar,现在使用内置的 DateTimeSecondsPicker 组件 -->
1241
1211
  </template>
1242
1212
 
1243
1213
  <style scoped lang="less">
1244
- .main{
1214
+ .main {
1245
1215
  padding-top: 4vh;
1246
1216
  width: 100vw;
1247
1217
  height: 100vh;
1248
1218
  background-color: #eff2f5;
1249
1219
 
1250
- .title{
1220
+ .title {
1251
1221
  padding-bottom: 2vh;
1252
1222
  color: rgb(50, 50, 51);
1253
1223
  text-align: center;
1254
1224
  margin: 0 0 3vh;
1255
1225
  }
1256
1226
 
1257
- .text_box{
1227
+ .text_box {
1258
1228
  margin-top: 2vh;
1259
1229
  margin-bottom: 2vh;
1260
1230
  }
1261
1231
 
1262
- .main_text{
1232
+ .main_text {
1263
1233
  padding-left: 16px;
1264
1234
  font-weight: 400;
1265
1235
  line-height: 1.6;
@@ -1268,53 +1238,53 @@ function onCancelPicker() {
1268
1238
  font-size: 14px;
1269
1239
  }
1270
1240
 
1271
- .show_value_item{
1241
+ .show_value_item {
1272
1242
  text-align: center;
1273
1243
  font-size: 1.2em;
1274
1244
  }
1275
1245
 
1276
- .cell_group{
1246
+ .cell_group {
1277
1247
  //margin-top: 2vh;
1278
1248
  //margin-bottom: 2vh;
1279
1249
  }
1280
1250
 
1281
- .form_item{
1251
+ .form_item {
1282
1252
  margin-top: 2vh;
1283
1253
  }
1284
1254
 
1285
- .button_group{
1255
+ .button_group {
1286
1256
  text-align: center;
1287
1257
  margin-top: 3vh;
1288
1258
  margin-bottom: 3vh;
1289
1259
  }
1290
1260
 
1291
- .button_group>:first-child {
1261
+ .button_group > :first-child {
1292
1262
  margin-right: 3vw;
1293
1263
  }
1294
1264
 
1295
- .divider{
1265
+ .divider {
1296
1266
  color: #1989fa;
1297
1267
  border-color: #1989fa;
1298
- padding: 0 16px
1268
+ padding: 0 16px;
1299
1269
  }
1300
1270
 
1301
- .submit_button{
1271
+ .submit_button {
1302
1272
  background-color: #eff2f5;
1303
1273
  padding: 5vh;
1304
1274
  }
1305
1275
  }
1306
1276
 
1307
- .skeleton{
1308
- margin-top: 5vh
1277
+ .skeleton {
1278
+ margin-top: 5vh;
1309
1279
  }
1310
- .my-cell-group{
1311
- margin: 0 0 10px 0
1280
+ .my-cell-group {
1281
+ margin: 0 0 10px 0;
1312
1282
  }
1313
1283
  :deep(.van-collapse-item__content) {
1314
1284
  background-color: #eff2f5;
1315
1285
  padding: 10px 0;
1316
1286
  }
1317
- :deep(.van-cell-group__title){
1287
+ :deep(.van-cell-group__title) {
1318
1288
  padding-top: 10px;
1319
1289
  padding-bottom: 10px;
1320
1290
  }
@@ -1325,7 +1295,7 @@ function onCancelPicker() {
1325
1295
  // 必填字段星号样式 - 只对星号生效
1326
1296
  :deep(.van-field__label) {
1327
1297
  // 将包含星号的文本进行特殊处理
1328
- &[style*="color"] {
1298
+ &[style*='color'] {
1329
1299
  color: #323233 !important;
1330
1300
  }
1331
1301
  }
@@ -1340,7 +1310,7 @@ function onCancelPicker() {
1340
1310
  margin-right: 2px;
1341
1311
  }
1342
1312
  }
1343
- :deep(.van-uploader__wrapper){
1313
+ :deep(.van-uploader__wrapper) {
1344
1314
  padding: 10px;
1345
1315
  display: flex;
1346
1316
  flex-wrap: wrap;
@@ -1371,7 +1341,7 @@ function onCancelPicker() {
1371
1341
  word-wrap: break-word;
1372
1342
  white-space: normal;
1373
1343
  display: -webkit-box;
1374
- -webkit-line-clamp: 2; // 最多显示2行
1344
+ -webkit-line-clamp: 4; // 最多显示2行
1375
1345
  -webkit-box-orient: vertical;
1376
1346
  overflow: hidden;
1377
1347
  }
@@ -29,3 +29,8 @@ html.dark {
29
29
  .van-tabbar--fixed {
30
30
  position: fixed !important;
31
31
  }
32
+
33
+ // 所有按钮 添加圆角
34
+ .van-button {
35
+ border-radius: 10px !important;
36
+ }
@@ -6,8 +6,15 @@ import { ref } from 'vue'
6
6
  import { useRoute } from 'vue-router'
7
7
 
8
8
  // 纯表单
9
- const configName = ref('AddConstructionForm')
10
- const serviceName = ref('af-linepatrol')
9
+ const configName = ref('testFormGroup')
10
+ const serviceName = ref('af-apply')
11
+
12
+ // const configName = ref("计划下发Form")
13
+ // const serviceName = ref("af-linepatrol")
14
+
15
+ // 表单组
16
+ // const configName = ref('lngChargeAuditMobileFormGroup')
17
+ // const serviceName = ref('af-gaslink')
11
18
 
12
19
  const formData = ref({})
13
20
  const formGroup = ref(null)
@@ -18,6 +25,36 @@ function submit(_result) {
18
25
  history.back()
19
26
  })
20
27
  }
28
+
29
+ // 表单组——数据
30
+ // function initComponents () {
31
+ // runLogic('getlngChargeAuditMobileFormGroupData', {id: 29}, 'af-gaslink').then((res) => {
32
+ // formData.value = {...res}
33
+ // })
34
+ // }
35
+
36
+ // 纯表单——数据
37
+ // function initComponents() {
38
+ // formData.value = { plan_name: 'af-llllll', plan_point: '1号点位', plan_single: '1号点位', plan_range: '2024-12-12' }
39
+ // }
40
+
41
+ // function initComponents() {
42
+ // runLogic('getlngChargeAuditMobileFormGroupData', { id: route.query?.id, o_id: route.query?.o_id }, 'af-gaslink').then((res) => {
43
+ // console.log('res------', res)
44
+ // formData.value = { ...res }
45
+ // formGroup.value.init({
46
+ // configName: configName.value,
47
+ // serviceName: serviceName.value,
48
+ // groupFormData: { ...res },
49
+ // mode: "新增"
50
+ // })
51
+ // isInit.value = true
52
+ // })
53
+ // }
54
+
55
+ // onBeforeMount(() => {
56
+ // initComponents()
57
+ // })
21
58
  </script>
22
59
 
23
60
  <template>
@@ -31,7 +31,7 @@ function submit(data) {
31
31
  :config-name="configName"
32
32
  :service-name="serviceName"
33
33
  :param-logic-name-param="{ aa: 123 }"
34
- :form-data="{f_project_name: 333}"
34
+ :form-data="{ f_project_name: 333 }"
35
35
  @x-form-item-emit-func="emitFunc"
36
36
  @on-submit="submit"
37
37
  />