web-component-gallery 2.3.6 → 2.3.8

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.
@@ -1,162 +1,105 @@
1
1
  import PropTypes from 'ant-design-vue/es/_util/vue-types'
2
- import { Base64 } from '../../utils/Base64'
3
- import { transferData } from '../../utils/Filter'
2
+ // 引入自定义图标组件
4
3
  import { IconFont } from '../index'
4
+ // 引入文件处理工具函数:格式化数据、获取文件类型、预览文件
5
+ import { formatFileData, getFileType, previewFile } from '../../utils/File'
5
6
 
6
7
  const BrowseProps = {
8
+ // 数据源,支持字符串、数组或对象,默认为空数组
7
9
  data: PropTypes.oneOfType([PropTypes.string, PropTypes.array, PropTypes.object]).def([]),
8
- /** 是否缩略图显示 */
10
+ // 是否以缩略图模式展示
9
11
  isThumb: PropTypes.bool,
10
- /** 支持http地址可配置 */
12
+ // HTTPS 相关的配置对象,默认为空对象
11
13
  httpsUrl: PropTypes.object.def({}),
12
- /** 限制高度 */
14
+ // 限制高度,支持字符串或数字类型
13
15
  astrictH: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
16
+ }
17
+
18
+ // 定义默认图标类型
19
+ const ICON_TYPE = 'file_thumb'
20
+
21
+ /**
22
+ * 根据 URL 和内容类型获取预览所需的标签类型
23
+ * @param {string} url - 文件 URL
24
+ * @param {string} contentType - 内容类型
25
+ * @returns {string} 返回 'img', 'audio', 'video' 或 'iframe'
26
+ */
27
+ const getPreviewType = (url, contentType = '') => {
28
+ const type = getFileType(url, contentType)
29
+ switch (type) {
30
+ case 'PICTURE': return 'img'
31
+ case 'AUDIO': return 'audio'
32
+ case 'VIDEO': return 'video'
33
+ default: return 'iframe'
34
+ }
14
35
  }
15
36
 
16
- // 文件类型常量
17
- const FILE_TYPES = {
18
- PICTURE: ["gif", "jpeg", "png", "jpg", "bmp", "tif", "svg", "psd", "raw", "WMF", "webp", "apng"],
19
- AUDIO: ["mp3", "wma", "flac", "aac", "mmf", "amr", "m4a", "m4r", "ogg", "mp2", "wav"],
20
- VIDEO: ["avi", "flv", "mpg", "mpeg", "mpe", "m1v", "m2v", "mpv2", "mp2v", "dat", "ts", "tp", "tpr", "pva", "pss", "mp4", "m4v", "m4p", "m4b", "3gp", "3gpp", "3g2", "3gp2", "ogg", "mov", "qt", "amr", "rm", "ram", "rmvb", "rpm"]
21
- }
22
-
23
- // 初始化数据
24
- const setInnerData = (data, https) => {
25
- // 处理Base64图片数据
26
- if (typeof data === 'string' && data.startsWith('data:image')) {
27
- return [{
28
- url: data,
29
- contentType: 'image',
30
- name: 'base64_image'
31
- }]
37
+ /**
38
+ * 渲染单个文件内容的函数
39
+ * @param {function} h - Vue 的渲染函数
40
+ * @param {object} param1 - 包含 url name 的对象
41
+ * @param {array} images - 图片列表,用于预览
42
+ * @returns {VNode} 返回渲染的虚拟 DOM 节点
43
+ */
44
+ const renderContent = function (h, { url, name }, images) {
45
+ if (!url) return
46
+
47
+ const { isThumb, astrictH } = this
48
+ const tag = getPreviewType(url)
49
+ const isImage = tag === 'img'
50
+ const isMedia = ['audio', 'video'].includes(tag)
51
+
52
+ // 点击处理函数:如果是图片或缩略图模式,则触发预览
53
+ const handleClick = () => {
54
+ if (isImage || isThumb) previewFile(url, images, this)
32
55
  }
33
56
 
34
- // 解析数据
35
- let innerData = transferData(data, 'Array')
36
-
37
- // 确保返回数组格式
38
- const dataArray = Array.isArray(innerData) ? innerData : [innerData]
39
-
40
- const { FILEURL, KKFILEURL, OVERVIEWFILEURL } = https
41
-
42
- // URL前缀配置检查
43
- if (!FILEURL || !KKFILEURL || !OVERVIEWFILEURL) {
44
- console.warn('缺少必要的URL配置')
45
- return dataArray
57
+ // 如果是非图片且处于缩略图模式,渲染带图标的占位符
58
+ if (!isImage && isThumb) {
59
+ return (
60
+ <div class="Browse__FileThumb" onClick={handleClick}>
61
+ <IconFont type={ICON_TYPE} />
62
+ <span>{name}</span>
63
+ </div>
64
+ )
46
65
  }
47
66
 
48
- // 处理URL
49
- return dataArray.map(dataItem => {
50
- if (!dataItem || typeof dataItem !== 'object') {
51
- return dataItem
52
- }
53
-
54
- // 如果已经是完整URL,直接返回
55
- if (dataItem.url && (dataItem.url.startsWith('http') || dataItem.url.startsWith('data:image'))) {
56
- return dataItem
57
- }
58
-
59
- // 构造完整URL
60
- const baseUrl = getCustomTag(dataItem.url) === 'iframe' && KKFILEURL && OVERVIEWFILEURL
61
- ? `${KKFILEURL}${encodeURIComponent(Base64.encode(`${OVERVIEWFILEURL}${dataItem.url}`))}`
62
- : `${FILEURL}${dataItem.url || ''}`;
63
-
64
- return {
65
- ...dataItem,
66
- url: baseUrl
67
- }
68
- })
69
- }
70
-
71
- // 不同类型不同容器承载
72
- const getCustomTag = (url) => {
73
- // base64 图片处理
74
- if (url.startsWith('data:image')) return 'img'
75
-
76
- // 文件扩展名处理
77
- const ext = url.slice(url.lastIndexOf('.') + 1).toLowerCase()
78
- return FILE_TYPES.PICTURE.includes(ext) ? 'img' :
79
- FILE_TYPES.AUDIO.includes(ext) ? 'audio' :
80
- FILE_TYPES.VIDEO.includes(ext) ? 'video' : 'iframe'
81
- }
82
-
83
- function renderContent(h, {url, name}, images) {
84
- if (!url) return
85
-
86
- const { isThumb, astrictH, $viewerApi, $postM } = this
87
-
88
- const MEDIA_TYPES = {
89
- IFRAME: 'iframe',
90
- IMAGE: 'img'
91
- }
92
-
93
- const ICON_TYPE = "file_thumb"
94
-
95
- const CustomTag = getCustomTag(url)
96
-
97
- const attrs = {
98
- src: url,
99
- ...(astrictH && { style: `width: auto; height: ${astrictH}px; objectFit: contain;` })
100
- }
101
-
102
- const handleClick = () => {
103
- if (CustomTag !== MEDIA_TYPES.IMAGE && !isThumb) return
104
-
105
- if (CustomTag === MEDIA_TYPES.IFRAME && isThumb) {
106
- $postM({
107
- type: 'customModal',
108
- name: 'browse',
109
- params: { data: { url } }
110
- })
111
- return
112
- }
113
-
114
- try {
115
- $viewerApi({
116
- options: {
117
- url: 'url',
118
- toolbar: true,
119
- initialViewIndex: images.findIndex(file => file.url == url)
120
- },
121
- images
122
- })
123
- } catch(error){ console.error('未安装v-viewer@1.6.4组件库') }
124
- }
67
+ // 构建基础标签属性
68
+ const attrs = {
69
+ src: url,
70
+ // 音视频和 iframe 显示控制条
71
+ ...((isMedia || tag === 'iframe') && { controls: true }),
72
+ // 音视频自动播放配置
73
+ ...(isMedia && { autoplay: true, muted: true, playsinline: true })
74
+ }
125
75
 
126
- if (CustomTag === MEDIA_TYPES.IFRAME) {
127
- if (isThumb) {
128
- return <div class="Browse__FileThumb">
129
- <IconFont type={ICON_TYPE} onClick={handleClick} />
130
- <span>{name}</span>
131
- </div>
132
- }
133
- attrs['controls'] = true
134
- }
76
+ // 构建基础标签样式
77
+ const style = {
78
+ ...(astrictH && { width: 'auto', height: `${astrictH}px` })
79
+ }
135
80
 
136
- return <CustomTag {...{ attrs }} onClick={handleClick} />
81
+ // 渲染实际的媒体标签
82
+ return <tag {...{ attrs, style }} onClick={handleClick} />
137
83
  }
138
84
 
85
+ // 定义 Browse 组件
139
86
  const Browse = {
140
87
  name: 'Browse',
141
88
  props: BrowseProps,
142
- render(h, content) {
143
- const {data, $https, httpsUrl} = this
144
- const innerData = setInnerData(data, {...$https, ...httpsUrl})
89
+ render(h) {
90
+ const { data, $https, httpsUrl } = this
91
+ // 格式化文件数据,合并 HTTPS 配置
92
+ const list = formatFileData(data, { ...$https, ...httpsUrl })
145
93
 
146
94
  return (
147
95
  <div class="Browse">
148
- {
149
- innerData.map( dataItem =>
150
- renderContent.call(this, h, dataItem, innerData)
151
- )
152
- }
96
+ {list.map(item => renderContent.call(this, h, item, list))}
153
97
  </div>
154
98
  )
155
99
  }
156
100
  }
157
101
 
158
- Browse.install = function (Vue) {
159
- Vue.component('Browse', Browse)
160
- }
102
+ // 安装方法,用于注册全局组件
103
+ Browse.install = Vue => Vue.component('Browse', Browse)
161
104
 
162
- export default Browse
105
+ export default Browse
@@ -1,3 +1,12 @@
1
+ video,
1
2
  iframe {
3
+ border: 0;
2
4
  .layout();
5
+ }
6
+
7
+ img {
8
+ max-width: 100%;
9
+ max-height: 100%;
10
+ object-fit: contain;
11
+ .square(auto);
3
12
  }
@@ -178,6 +178,8 @@ export default {
178
178
  return {
179
179
  allowClear: true,
180
180
  showSearch: true,
181
+ // 多选、标签模式下默认最多显示3个Tag
182
+ // maxTagCount: 3,
181
183
  defaultActiveFirstOption: !this.isInput,
182
184
  filterOption: this.shouldUseLocalFilter,
183
185
  ...this.$attrs
@@ -497,11 +499,9 @@ export default {
497
499
  })
498
500
 
499
501
  let newInnerOptions = []
500
- if (this.pagination.current === 1 && isReset) {
501
- newInnerOptions = [...records]
502
- } else {
503
- newInnerOptions = [...this.innerOptions, ...this.handleFilter(records)]
504
- }
502
+ this.pagination.current === 1 && isReset
503
+ ? newInnerOptions = [...records]
504
+ : newInnerOptions = [...this.innerOptions, ...this.handleFilter(records)]
505
505
 
506
506
  // 对合并后的数据进行去重
507
507
  this.innerOptions = this.deduplicateOptions(newInnerOptions)
@@ -1,113 +1,154 @@
1
1
  <template>
2
- <div class="TAntdUpload">
2
+ <div :class="['a-upload', `a-upload--${uploadConfig.listType}`]">
3
3
  <Upload
4
4
  name="file"
5
5
  :file-list="fileList"
6
- :accept="fileAccept"
7
- v-bind="uploadAttrs"
6
+ v-bind="uploadConfig"
8
7
  @change="handleFileChange"
9
8
  @preview="handleFilePreview"
10
- @reject="$message.error(`上传文件类型只能为${fileAccept}`)"
9
+ @reject="handleFileReject"
11
10
  >
11
+ <!-- 文本按钮模式 -->
12
12
  <Button
13
13
  type="primary"
14
- v-if="uploadAttrs.listType === 'text'"
14
+ v-if="isTextType"
15
+ :disabled="!canUpload"
15
16
  >
16
17
  <Icon type="upload" />上传
17
18
  </Button>
19
+
20
+ <!-- 卡片模式 -->
18
21
  <template v-else>
19
- <Icon v-if="!fileList || fileList.length < uploadLen"
22
+ <Icon
23
+ v-if="canUpload"
20
24
  type="plus"
21
25
  style="font-size: 36px"
22
26
  />
23
27
  </template>
24
28
  </Upload>
25
29
 
26
- <span class="tips" v-if="tips">{{ tips }}</span>
30
+ <div class="a-upload__tips" v-if="tips">{{ tips }}</div>
27
31
  </div>
28
32
  </template>
29
33
 
30
34
  <script>
31
- import { Icon, Button, Upload, message } from 'ant-design-vue'
32
35
 
33
- // 常量定义
34
- const IMAGE_SUFFIX = new Set(['JPEG', 'JPG', 'GIF', 'PNG', 'TIFF', 'WEBP', 'BMP'])
36
+ import { previewFile } from '../../utils/File'
37
+ import { transferData } from '../../utils/Filter'
38
+ import { Icon, Button, Upload } from 'ant-design-vue'
35
39
 
36
40
  export default {
37
41
  name: 'AUpload',
42
+
38
43
  model: {
39
44
  prop: 'value',
40
45
  event: 'handleFileUpload'
41
46
  },
42
- components: { Icon, Button, Upload },
47
+
48
+ components: {
49
+ Icon,
50
+ Button,
51
+ Upload
52
+ },
53
+
54
+ inheritAttrs: false,
55
+
43
56
  props: {
57
+ // 绑定值,支持字符串或数组格式
44
58
  value: {
45
59
  type: [String, Array],
46
60
  default: () => []
47
61
  },
48
- /* 提示文字 */
62
+ // 提示文本信息
49
63
  tips: String,
50
- /* 上传文件数量限制 */
64
+ // 允许上传的最大文件数量
51
65
  uploadLen: {
52
66
  type: Number,
53
67
  default: 1
54
68
  },
55
- /* 上传接口 */
69
+ // 自定义文件上传接口地址
56
70
  fileUploadUrl: String,
57
- /* 显示需要pin的接口前缀 */
71
+ // 文件访问地址的前缀 URL
58
72
  filePrefixUrl: String,
59
- /* 上传文件格式限制 */
60
- fileAccept: String,
61
- /* 上传文件携带Headers信息 */
73
+ // 允许上传的文件类型限制(accept 属性)
74
+ fileAccept: String,
75
+ // 上传请求的额外头部信息
62
76
  fileHeaders: Object,
63
- /* 删除文件函数回调 */
77
+ // 自定义文件删除处理函数,若不传则使用默认 store action
64
78
  fileRemoveHandle: Function
65
79
  },
80
+
66
81
  data() {
67
82
  return {
68
83
  fileList: [],
69
- modalValue: []
84
+ internalFileList: []
70
85
  }
71
86
  },
87
+
72
88
  watch: {
73
- value() {
74
- this.setResetFile()
89
+ value: {
90
+ handler: 'resetFileList',
91
+ immediate: true
75
92
  }
76
93
  },
94
+
77
95
  computed: {
78
- uploadAttrs() {
79
- const { filePrefixUrl, fileUploadUrl, fileHeaders } = this
96
+ isTextType() {
97
+ return this.uploadConfig.listType === 'text'
98
+ },
99
+
100
+ canUpload() {
101
+ return this.fileList?.length < this.uploadLen && !this.uploadConfig.disabled
102
+ },
103
+
104
+ httpsUrl() {
105
+ const { $https, filePrefixUrl, fileUploadUrl } = this
106
+
107
+ return {
108
+ ...$https,
109
+ FILEURL: filePrefixUrl ?? $https?.FILEURL,
110
+ UPLOADURL: fileUploadUrl ?? $https?.UPLOADURL
111
+ }
112
+ },
113
+
114
+ uploadConfig() {
115
+ const { httpsUrl, fileAccept, fileHeaders, $store, $attrs } = this
116
+
80
117
  return {
81
118
  listType: 'picture-card',
82
- prefix: filePrefixUrl ?? this.$https.FILEURL,
83
- action: fileUploadUrl ?? this.$https.UPLOADURL,
84
- headers: fileHeaders ?? this.$store.getters.fileHeaders,
85
- ...this.$attrs
119
+ accept: fileAccept,
120
+ prefix: httpsUrl.FILEURL,
121
+ action: httpsUrl.UPLOADURL,
122
+ headers: fileHeaders ?? $store?.getters?.fileHeaders,
123
+ ...$attrs
86
124
  }
87
125
  }
88
126
  },
89
- mounted() {
90
- this.setResetFile()
91
- },
127
+
92
128
  beforeDestroy() {
93
- this.fileList = []
94
- this.modalValue = []
129
+ this.cleanup()
95
130
  },
131
+
96
132
  methods: {
97
- /**
98
- * 重置文件列表
99
- */
100
- setResetFile() {
101
- let arrayObject
102
- try {
103
- arrayObject = JSON.parse(this.value)
104
- } catch (e) {
105
- arrayObject = [].concat(this.value)
106
- }
107
- this.modalValue = arrayObject ?? []
108
- this.fileList = this.modalValue.map(file => ({
133
+ // 重置文件列表
134
+ resetFileList() {
135
+ const rawList = transferData(this.value, 'Array')
136
+
137
+ // 标准化内部数据:统一使用 id,移除 uid
138
+ this.internalFileList = rawList.map(file => {
139
+ const normalized = { ...file }
140
+ normalized.id = normalized.id || normalized.uid
141
+ if (normalized.uid) delete normalized.uid
142
+
143
+ return normalized
144
+ })
145
+
146
+ // 构建展示列表:适配 Ant Design Upload 组件需要的 uid 格式
147
+ const { prefix } = this.uploadConfig
148
+ this.fileList = this.internalFileList.map(file => ({
109
149
  ...file,
110
- url: `${this.uploadAttrs.prefix}${file.url}`,
150
+ uid: file.id,
151
+ url: `${prefix}${file.url}`,
111
152
  type: file.contentType || file.type,
112
153
  status: 'done'
113
154
  }))
@@ -117,70 +158,90 @@ export default {
117
158
  * 文件预览处理
118
159
  * @param {Object} file - 文件对象
119
160
  */
120
- handleFilePreview({uid, type, url}) {
121
- if (!type || !url) return
122
-
123
- const suffix = url.substr(url.lastIndexOf('.') + 1).toUpperCase()
124
-
125
- if (type.includes('image') || IMAGE_SUFFIX.has(suffix)) {
126
- this.$viewerApi({
127
- options: {
128
- url: 'url',
129
- toolbar: true,
130
- initialViewIndex: this.fileList.findIndex(file => file.url === url)
131
- },
132
- images: this.fileList
133
- })
134
- return
135
- }
136
-
137
- // 仅适用于子项目调起
138
- this.$postM({
139
- type: 'customModal',
140
- name: 'browse',
141
- params: { data: this.modalValue.find(item => item.uid === uid) }
142
- })
161
+ handleFilePreview({ uid }) {
162
+ const sourceFile = this.internalFileList.find(f => f.id === uid)
163
+ previewFile(sourceFile, this.internalFileList, this)
143
164
  },
144
165
 
145
166
  /**
146
167
  * 文件状态变化处理
147
- * @param {Object} file - 文件对象
148
- * @param {Array} fileList - 文件列表
168
+ * @param {Object} params - 参数对象
169
+ * @param {Object} params.file - 当前文件对象
170
+ * @param {Array} params.fileList - 文件列表
149
171
  */
150
172
  handleFileChange({ file, fileList }) {
151
- const {uid, status, response} = file
173
+ const { uid, status, response } = file
152
174
 
175
+ // 上传中
153
176
  if (status === 'uploading') {
154
177
  this.fileList = fileList
155
178
  return
156
179
  }
157
180
 
181
+ // 上传失败:后端返回非 200 状态码
158
182
  if (response && response.code !== 200) {
159
- this.fileList = this.fileList.filter(e => e.uid !== uid)
160
- this.$message.error(response.msg || '上传失败')
183
+ this.fileList = this.fileList.filter(item => item.uid !== uid)
184
+ this.$message.error(response.msg || '文件上传失败')
161
185
  return
162
186
  }
163
187
 
188
+ // 根据状态处理
164
189
  switch (status) {
165
190
  case 'removed':
166
- const requestHandle = this.fileRemoveHandle
167
- ? this.fileRemoveHandle({ id: uid })
168
- : this.$store.dispatch('fileRemoveHandle', { id: uid })
169
- requestHandle.then(() => {
170
- this.modalValue = this.modalValue.filter(e => e.uid !== uid)
171
- this.$emit('handleFileUpload', JSON.stringify(this.modalValue))
172
- })
191
+ this.handleFileRemove(uid)
173
192
  break
174
193
  case 'done':
175
- const {id, ...innerData} = response.data
176
- this.modalValue.push({...innerData, uid: id})
177
- this.$emit('handleFileUpload', JSON.stringify(this.modalValue))
194
+ this.handleFileDone(response)
178
195
  break
179
196
  case 'error':
180
197
  this.$message.error('文件上传失败')
181
198
  break
182
199
  }
200
+ },
201
+
202
+ /**
203
+ * 文件类型拒绝
204
+ */
205
+ handleFileReject() {
206
+ this.$message.error(`上传文件类型只能为${this.fileAccept}`)
207
+ },
208
+
209
+ /**
210
+ * 删除文件
211
+ * @param {String} id - 文件 ID
212
+ */
213
+ handleFileRemove(id) {
214
+ const requestHandle = this.fileRemoveHandle
215
+ ? this.fileRemoveHandle({ id })
216
+ : this.$store.dispatch('fileRemoveHandle', { id })
217
+
218
+ requestHandle.then(() => {
219
+ this.internalFileList = this.internalFileList.filter(e => e.id !== id)
220
+ this.$emit('handleFileUpload', JSON.stringify(this.internalFileList))
221
+ })
222
+ },
223
+
224
+ /**
225
+ * 上传完成处理
226
+ * @param {Object} response - 上传响应对象
227
+ */
228
+ handleFileDone(response) {
229
+ if (!response?.data) {
230
+ this.$message.error('文件上传失败!')
231
+ return
232
+ }
233
+
234
+ this.internalFileList.push(response.data)
235
+ this.$emit('handleFileUpload', JSON.stringify(this.internalFileList))
236
+ },
237
+
238
+ /**
239
+ * 清理数据
240
+ */
241
+ cleanup() {
242
+ this.fileList = []
243
+ this.internalFileList = []
183
244
  }
184
245
  }
185
246
  }
186
- </script>
247
+ </script>
@@ -1,10 +1,10 @@
1
-
2
1
  .ADatePicker {
3
2
  width: 100%;
4
3
  align-items: center;
5
- .flex-layout(@flexGap: 0 6px);
4
+ .flex-layout(@flexGap: 0 6px);
6
5
 
7
6
  .ant-calendar-picker {
8
7
  flex: 1;
8
+ min-width: 0 !important;
9
9
  }
10
10
  }
@@ -1,18 +1,42 @@
1
-
2
- // 添加flex属性解决css中div莫名高度
3
1
  .ASelectCustom {
4
- width: 100%;
2
+ align-items: center;
3
+ .flex-layout();
5
4
 
6
5
  .loading-more {
7
6
  font-size: @font-size-base;
8
7
  text-align: center;
9
8
  padding: @padding-xs 0;
10
9
  box-sizing: border-box;
11
- }
10
+ }
11
+
12
+ .ant-select {
13
+ &-selection {
14
+ .layout(100%, @input-height-base);
15
+
16
+ &--multiple {
17
+ padding-bottom: 0;
18
+ }
19
+
20
+ &__rendered {
21
+ overflow: hidden;
22
+ white-space: nowrap;
23
+ text-overflow: ellipsis;
24
+ // 2px为上下边框兼容
25
+ height: @input-height-base - 2px;
26
+
27
+ ul {
28
+ width: auto;
29
+ display: flex;
30
+
31
+ li { min-width: 70px; }
32
+ }
33
+ }
34
+ }
12
35
 
13
- .ant-select-dropdown-menu {
14
- max-height: 300px;
15
- overflow-y: auto;
16
- .scrollbarStyle();
36
+ &-dropdown-menu {
37
+ max-height: 300px;
38
+ overflow-y: auto;
39
+ .scrollbarStyle();
40
+ }
17
41
  }
18
42
  }