vue2-client 1.13.1 → 1.13.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue2-client",
3
- "version": "1.13.1",
3
+ "version": "1.13.3",
4
4
  "private": false,
5
5
  "scripts": {
6
6
  "serve": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve --no-eslint",
@@ -48,10 +48,10 @@
48
48
  <template v-if="panel.type === 'picture'">
49
49
  <img :src="panel.configName" alt="图片" style="width: 100%; max-width: 500px;"/>
50
50
  </template>
51
- <template v-else-if="['x-form-table','x-add-native-form','x-tree-pro', 'x-his-editor', 'x-tab', 'x-form-group', 'x-report', 'x-buttons', 'x-label-select', 'x-conversation', 'x-check-list', 'x-cardSet', 'x-collapse','x-h-descriptions', 'x-sidebar', 'x-list','x-input','x-time-line', 'x-radio'].includes(panel.type)">
51
+ <template v-else-if="['x-form-table','x-add-native-form','x-tree-pro', 'x-his-editor', 'x-tab', 'x-form-group', 'x-report', 'x-buttons', 'x-label-select', 'x-conversation', 'x-check-list', 'x-cardSet', 'x-collapse','x-h-descriptions', 'x-sidebar', 'x-list','x-input','x-time-line', 'x-radio', 'x-text-card'].includes(panel.type)">
52
52
  <component
53
53
  :is="getComponentName(panel.type)"
54
- :ref="`dynamicComponent_${ panel.type }`"
54
+ :ref="`dynamicComponent_${ panel.type }_${ panelIndex }`"
55
55
  serverName="af-his"
56
56
  :queryParamsName="panel.configName"
57
57
  :parameter="panel.parameter"
@@ -89,7 +89,8 @@ export default {
89
89
  XList: () => import('@vue2-client/base-client/components/his/XList/XList.vue'),
90
90
  XInput: () => import('@vue2-client/base-client/components/common/XInput/XInput.vue'),
91
91
  XTimeLine: () => import('@vue2-client/base-client/components/common/XTimeline/XTimeline.vue'),
92
- XRadio: () => import('@vue2-client/base-client/components/his/XRadio/XRadio.vue')
92
+ XRadio: () => import('@vue2-client/base-client/components/his/XRadio/XRadio.vue'),
93
+ XTextCard: () => import('@vue2-client/base-client/components/his/XTextCard/XTextCard.vue')
93
94
  },
94
95
  data () {
95
96
  return {
@@ -10,7 +10,8 @@
10
10
  <a-descriptions-item
11
11
  v-for="(item) in visibleItems"
12
12
  :key="item.field"
13
- :colon="item.colon !== false">
13
+ :colon="item.colon !== false"
14
+ v-if="data[item.field] !== null && data[item.field] !== undefined">
14
15
  <template #label>
15
16
  <div :class="['label-wrapper', { 'with-avatar': item.showAvatar }]">
16
17
  <a-avatar
@@ -43,7 +44,8 @@
43
44
  <a-descriptions-item
44
45
  v-for="item in hiddenItems"
45
46
  :key="item.field"
46
- :colon="item.colon !== false">
47
+ :colon="item.colon !== false"
48
+ v-if="data[item.field] !== null && data[item.field] !== undefined">
47
49
  <template #label>
48
50
  <div :class="['label-wrapper', { 'with-avatar': item.showAvatar }]">
49
51
  <a-avatar
@@ -99,22 +101,46 @@ export default {
99
101
  computed: {
100
102
  // 获取详情按钮应该显示在第几个标签后
101
103
  detailsAfterIndex () {
102
- return this.config?.detailsConfig?.showAfterIndex || 3
104
+ // 只有明确配置了detailsConfig且设置了showAfterIndex时才使用配置值
105
+ // 否则返回一个很大的值,使所有字段都显示
106
+ return this.config?.detailsConfig?.showAfterIndex || 999
103
107
  },
104
108
  // 判断是否有更多标签需要显示
105
109
  hasMoreItems () {
106
- return this.config?.items?.length > this.detailsAfterIndex
110
+ // 只有明确配置了detailsConfig时才显示详情按钮
111
+ if (!this.config?.detailsConfig) return false
112
+ // 如果没有数据或没有配置项,不显示详情按钮
113
+ if (!this.data || !this.config?.items || !Array.isArray(this.config.items)) return false
114
+ // 获取隐藏项索引
115
+ const hiddenStartIndex = this.detailsAfterIndex || 0
116
+ if (hiddenStartIndex >= this.config.items.length) return false
117
+ // 检查隐藏的项中是否有非空值
118
+ for (let i = hiddenStartIndex; i < this.config.items.length; i++) {
119
+ const item = this.config.items[i]
120
+ if (item && item.field && this.data[item.field] !== null && this.data[item.field] !== undefined) {
121
+ return true
122
+ }
123
+ }
124
+ return false
107
125
  },
108
126
  // 获取应该显示的标签
109
127
  visibleItems () {
110
128
  if (!this.config?.items) return []
111
- if (this.showAllItems) return this.config.items
129
+ // 如果没有配置detailsConfig或者已经展开,则显示所有项
130
+ if (!this.config?.detailsConfig || this.showAllItems) return this.config.items
112
131
  return this.config.items.slice(0, this.detailsAfterIndex)
113
132
  },
114
133
  // 获取隐藏的标签
115
134
  hiddenItems () {
116
135
  if (!this.config?.items) return []
136
+ if (!this.config?.detailsConfig) return [] // 如果没有配置detailsConfig,没有隐藏的项
117
137
  return this.config.items.slice(this.detailsAfterIndex)
138
+ },
139
+ // 获取有效的visible项目数量(排除null值)
140
+ validVisibleItemsCount () {
141
+ if (!this.config?.items || !this.data) return 0
142
+ const items = this.visibleItems
143
+ return items.filter(item => this.data[item.field] !== null && this.data[item.field] !== undefined).length
118
144
  }
119
145
  },
120
146
  created () {
@@ -7,6 +7,7 @@
7
7
  - 支持响应式布局,在不同屏幕尺寸下自动调整列数
8
8
  - 支持折叠/展开功能,控制信息的显示量
9
9
  - 支持自定义样式和头像
10
+ - 自动隐藏值为 null 或 undefined 的字段
10
11
 
11
12
  ## 配置说明
12
13
 
@@ -90,6 +91,10 @@
90
91
  - `buttonText`: 详情按钮显示的文本
91
92
  - `buttonType`: 按钮类型,可选值为 "default", "primary", "dashed", "link" 等
92
93
  - `showAfterIndex`: 控制在显示多少个字段后显示详情按钮
94
+ - **重要说明**:
95
+ - 只有明确配置了 `detailsConfig` 对象时,才会启用折叠功能
96
+ - 如果没有配置 `detailsConfig`,将直接显示所有字段,不会出现详情按钮
97
+ - 如果配置了 `detailsConfig` 但字段总数少于或等于 `showAfterIndex`,则不会显示详情按钮
93
98
 
94
99
  ### 5. 其他配置
95
100
 
@@ -128,13 +133,21 @@ export default {
128
133
  - 可在 `layout` 配置中自定义不同尺寸下的列数
129
134
 
130
135
  2. **折叠/展开功能**:
131
- - 默认只显示 `showAfterIndex` 指定数量的字段
132
- - 点击详情按钮可展开显示全部字段
133
- - 点击收起按钮可恢复默认显示
136
+ - 折叠功能只在明确配置了 `detailsConfig` 对象时启用
137
+ - 没有配置 `detailsConfig` 时,会直接显示所有字段,不会出现详情按钮
138
+ - 配置了 `detailsConfig` 后,只显示 `showAfterIndex` 指定数量的字段,超出部分需点击详情按钮查看
139
+ - 点击详情按钮后会展开显示全部字段,可通过收起按钮恢复
140
+
141
+ 3. **空值处理**:
142
+ - 当字段值为 `null` 或 `undefined` 时,该字段不会显示
143
+ - 折叠功能会忽略值为 `null` 的字段,只有有值的隐藏字段才会触发显示详情按钮
144
+ - 这个特性可以用于根据数据动态控制字段的显示,无需修改配置
134
145
 
135
146
  ## 配置示例
136
147
 
137
- 以下是一个完整的配置示例,用于展示患者基本信息:
148
+ ### 启用折叠功能示例
149
+
150
+ 以下配置将启用折叠功能,只显示前3个字段,其余字段需点击"详细"按钮查看:
138
151
 
139
152
  ```json
140
153
  {
@@ -169,15 +182,51 @@ export default {
169
182
  {
170
183
  "field": "diagnosisNo",
171
184
  "label": "诊号"
185
+ },
186
+ {
187
+ "field": "gender",
188
+ "label": "性别"
189
+ },
190
+ {
191
+ "field": "age",
192
+ "label": "年龄"
172
193
  }
173
194
  // ... 更多字段
174
195
  ],
175
196
  "detailsConfig": {
176
197
  "buttonText": "详细",
177
198
  "buttonType": "link",
178
- "showAfterIndex": 3
199
+ "showAfterIndex": 3 // 设置为3表示只显示前3个字段,其余字段点击"详细"后显示
200
+ },
201
+ "logicName": "patientBasicInfo",
202
+ "serverName": "af-his"
203
+ }
204
+ ```
205
+
206
+ ### 禁用折叠功能示例
207
+
208
+ 如果不希望使用折叠功能,希望一次性显示所有字段,只需要移除 `detailsConfig` 对象:
209
+
210
+ ```json
211
+ {
212
+ "layout": {
213
+ "xl": 8,
214
+ "md": 3,
215
+ "sm": 2,
216
+ "lg": 4,
217
+ "xs": 1,
218
+ "xxl": 8
179
219
  },
220
+ "style": {
221
+ "size": "small",
222
+ "fontSize": "14px",
223
+ "bordered": false
224
+ },
225
+ "items": [
226
+ // 字段配置...
227
+ ],
180
228
  "logicName": "patientBasicInfo",
181
229
  "serverName": "af-his"
230
+ // 没有 detailsConfig,将直接显示所有字段
182
231
  }
183
232
  ```
@@ -0,0 +1,242 @@
1
+ <template>
2
+ <div>
3
+ <div class="text-card">
4
+ <!-- 添加顶部按钮区域 -->
5
+ <div class="text-card-header">
6
+ <div class="add-button" @click="handleAddClick">
7
+ <span class="plus-icon">+</span>
8
+ </div>
9
+ </div>
10
+ <div
11
+ v-for="(item, index) in displayItems"
12
+ :key="index"
13
+ class="text-item"
14
+ @click="handleItemClick(index)"
15
+ @contextmenu.prevent="handleContextMenu(index)">
16
+ <div class="text-content" :class="{ 'is-empty': !item.content }">
17
+ {{ item.content || '点击添加内容' }}
18
+ </div>
19
+ </div>
20
+ <!-- 编辑弹出框 -->
21
+ <div
22
+ v-if="showEdit"
23
+ class="edit-overlay"
24
+ @click.self="handleOverlayClick">
25
+ <div class="edit-card">
26
+ <a-textarea
27
+ v-model="editingContent"
28
+ :autoSize="{ minRows: 3 }"
29
+ @keyup.enter="handleSave"
30
+ ref="textarea"/>
31
+ </div>
32
+ </div>
33
+ <!-- 删除确认弹框 -->
34
+ <a-modal
35
+ v-model="showDeleteModal"
36
+ title="确认删除"
37
+ okText="确认"
38
+ cancelText="取消"
39
+ @ok="handleDeleteConfirm">
40
+ <p>确定要删除这条内容吗?</p>
41
+ </a-modal>
42
+ </div>
43
+ </div>
44
+ </template>
45
+
46
+ <script>
47
+ import { runLogic } from '@vue2-client/services/api/common'
48
+
49
+ export default {
50
+ name: 'XTextCard',
51
+ props: {
52
+ // logic配置名,要求返回一个数组[]
53
+ queryParamsName: {
54
+ type: String,
55
+ default: 'memorandumLOGIC'
56
+ },
57
+ queryParams: {
58
+ type: Object,
59
+ default: () => { return {} }
60
+ },
61
+ // 请求参数
62
+ parameter: {
63
+ type: Object,
64
+ default: () => { return {} }
65
+ }
66
+ },
67
+ data () {
68
+ return {
69
+ localItems: [], // 本地数据
70
+ showEdit: false, // 是否显示编辑框
71
+ editingIndex: -1, // 当前编辑的索引
72
+ editingContent: '', // 编辑框的内容
73
+ showDeleteModal: false, // 是否显示删除确认框
74
+ deleteIndex: -1, // 要删除的项目索引
75
+ }
76
+ },
77
+ computed: {
78
+ // 显示的列表,始终保持一个空行
79
+ displayItems () {
80
+ const items = [...this.localItems]
81
+ // 如果最后一项不是空的,添加一个空项
82
+ if (!items.length || items[items.length - 1].content) {
83
+ items.push({ content: '' })
84
+ }
85
+ return items
86
+ }
87
+ },
88
+ methods: {
89
+ // 添加新方法
90
+ handleAddClick () {
91
+ // 直接打开编辑框,索引设为数组长度(等同于点击最后一个空行)
92
+ this.editingIndex = this.localItems.length
93
+ this.editingContent = ''
94
+ this.showEdit = true
95
+ this.$nextTick(() => {
96
+ this.$refs.textarea?.focus()
97
+ })
98
+ },
99
+ // 点击项目
100
+ handleItemClick (index) {
101
+ this.editingIndex = index
102
+ this.editingContent = this.displayItems[index].content
103
+ this.showEdit = true
104
+ this.$nextTick(() => {
105
+ this.$refs.textarea?.focus()
106
+ })
107
+ },
108
+ // 点击遮罩层保存
109
+ handleOverlayClick () {
110
+ this.handleSave()
111
+ },
112
+ // 保存内容
113
+ handleSave () {
114
+ if (this.editingContent.trim()) {
115
+ if (this.editingIndex === this.localItems.length) {
116
+ // 如果是在最后一个空行添加,push新内容
117
+ this.localItems.push({ content: this.editingContent.trim() })
118
+ } else {
119
+ // 否则更新现有内容
120
+ this.localItems[this.editingIndex].content = this.editingContent.trim()
121
+ }
122
+ // 触发更新事件
123
+ this.$emit('add', this.editingContent.trim())
124
+ }
125
+ this.showEdit = false
126
+ this.editingIndex = -1
127
+ this.editingContent = ''
128
+ },
129
+ // 初始化数据
130
+ async initData (data, parameter) {
131
+ // 从配置中获取数据
132
+ const resData = await runLogic(data, parameter, 'af-his')
133
+ this.localItems = resData.map(content => ({ content: content }))
134
+ return resData
135
+ },
136
+ // 处理右键菜单
137
+ handleContextMenu (index) {
138
+ // 如果是最后一个空行,不显示删除框
139
+ if (index === this.localItems.length) {
140
+ return
141
+ }
142
+ this.deleteIndex = index
143
+ this.showDeleteModal = true
144
+ },
145
+ // 确认删除
146
+ handleDeleteConfirm () {
147
+ if (this.deleteIndex > -1) {
148
+ this.localItems.splice(this.deleteIndex, 1)
149
+ this.$emit('delete', [...this.localItems][this.deleteIndex])
150
+ this.deleteIndex = -1
151
+ }
152
+ this.showDeleteModal = false
153
+ }
154
+ },
155
+ watch: {
156
+ queryParamsName: {
157
+ immediate: true,
158
+ handler (newVal) {
159
+ this.initData(newVal, this.parameter)
160
+ },
161
+ deep: true
162
+ }
163
+ }
164
+ }
165
+ </script>
166
+
167
+ <style scoped lang="less">
168
+ .text-card {
169
+ position: relative;
170
+ width: 100%;
171
+ // 添加头部样式
172
+ .text-card-header {
173
+ padding: 8px 12px;
174
+ border-bottom: 1px solid #f0f0f0;
175
+ .add-button {
176
+ display: inline-flex;
177
+ align-items: center;
178
+ justify-content: center;
179
+ width: 24px;
180
+ height: 24px;
181
+ border-radius: 4px;
182
+ cursor: pointer;
183
+ transition: all 0.3s;
184
+ &:hover {
185
+ background-color: #f5f5f5;
186
+ }
187
+ .plus-icon {
188
+ font-size: 20px;
189
+ line-height: 1;
190
+ color: #5D5C5C;
191
+ font-weight: bold;
192
+ }
193
+ }
194
+ }
195
+
196
+ .text-item {
197
+ padding: 8px 12px;
198
+ border-bottom: 1px solid #f0f0f0;
199
+ cursor: pointer;
200
+ transition: all 0.3s;
201
+ user-select: none; // 防止文本被选中
202
+
203
+ &:hover {
204
+ background-color: #f5f5f5;
205
+ }
206
+
207
+ .text-content {
208
+ white-space: nowrap;
209
+ overflow: hidden;
210
+ text-overflow: ellipsis;
211
+ line-height: 1.5;
212
+
213
+ &.is-empty {
214
+ color: #bfbfbf;
215
+ }
216
+ }
217
+ }
218
+ .edit-overlay {
219
+ position: fixed;
220
+ top: 0;
221
+ left: 0;
222
+ right: 0;
223
+ bottom: 0;
224
+ background-color: rgba(0, 0, 0, 0.45);
225
+ z-index: 1000;
226
+ display: flex;
227
+ align-items: center;
228
+ justify-content: center;
229
+ .edit-card {
230
+ background: white;
231
+ padding: 16px;
232
+ border-radius: 8px;
233
+ width: 90%;
234
+ max-width: 500px;
235
+ box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12);
236
+ .ant-input {
237
+ width: 100%;
238
+ }
239
+ }
240
+ }
241
+ }
242
+ </style>