jmash-core-mp 0.1.43 → 0.1.44

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": "jmash-core-mp",
3
- "version": "0.1.43",
3
+ "version": "0.1.44",
4
4
  "private": false,
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -9,8 +9,9 @@
9
9
  <button class="avatar-wrapper" plain open-type="chooseAvatar" bind:chooseavatar@wx="onChooseAvatar"
10
10
  onChooseAvatar@ali="onChooseAvatar">
11
11
  <image class="avatar-img" src="{{ resourceUrl + '/components/gray_head2x.png' }}" mode="aspectFill"
12
- wx:if="{{ !userInfo.avatar }}" />
13
- <image class="avatar-img" src="{{ imageUrl + userInfo.avatar }}" mode="aspectFill" wx:else />
12
+ wx:if="{{ !userInfo.avatar && resourceUrl }}" />
13
+ <image class="avatar-img" src="{{ imageUrl + userInfo.avatar }}" mode="aspectFill"
14
+ wx:elif="{{ resourceUrl }}" />
14
15
  <view class="avatar-badge">
15
16
  <text class="badge-icon">✎</text>
16
17
  </view>
@@ -8,8 +8,9 @@
8
8
  <view class="profile-right">
9
9
  <button class="avatar-wrapper">
10
10
  <image class="avatar-img" src="{{ resourceUrl + '/components/gray_head2x.png' }}" mode="aspectFill"
11
- wx:if="{{ !userInfo.avatar }}" />
12
- <image class="avatar-img" src="{{ imageUrl + userInfo.avatar }}" mode="aspectFill" wx:else />
11
+ wx:if="{{ !userInfo.avatar && resourceUrl }}" />
12
+ <image class="avatar-img" src="{{ imageUrl + userInfo.avatar }}" mode="aspectFill"
13
+ wx:elif="{{ resourceUrl }}" />
13
14
  <view class="avatar-badge">
14
15
  <text class="badge-icon">✎</text>
15
16
  </view>
@@ -19,7 +19,8 @@
19
19
  <view class="article-text-desc">{{ item.intro }}</view>
20
20
  </view>
21
21
  <view class="article-thumb" catch:tap="handlePreview" data-url="{{ item.imgUrl }}">
22
- <image class="article-thumb-img" src="{{ imageUrl + item.imgUrl }}" mode="aspectFill" />
22
+ <image wx:if="{{ imageUrl }}" class="article-thumb-img" src="{{ imageUrl + item.imgUrl }}"
23
+ mode="aspectFill" />
23
24
  </view>
24
25
  </view>
25
26
  <jmash-cms-like organTenant="{{ organTenant }}" interactId="{{ item.infoId }}"
@@ -8,7 +8,8 @@
8
8
  <view class="article-text-desc">{{ item.intro }}</view>
9
9
  </view>
10
10
  <view class="article-thumb" catch:tap="handlePreview" data-url="{{ item.imgUrl }}">
11
- <image class="article-thumb-img" src="{{ imageUrl + item.imgUrl }}" mode="aspectFill" />
11
+ <image wx:if="{{ imageUrl }}" class="article-thumb-img" src="{{ imageUrl + item.imgUrl }}"
12
+ mode="aspectFill" />
12
13
  </view>
13
14
  </view>
14
15
  <jmash-cms-like organTenant="{{ organTenant }}" interactId="{{ item.infoId }}"
@@ -6,8 +6,8 @@
6
6
  <view class="top-left-title">{{ title }}</view>
7
7
  <view class="top-right-more">
8
8
  <view class="more-text">更多</view>
9
- <image class="more-image" src="{{ resourceUrl + '/components/caret-right_blcak.png' }}"
10
- mode="widthFix" />
9
+ <image wx:if="{{ resourceUrl }}" class="more-image"
10
+ src="{{ resourceUrl + '/components/caret-right_blcak.png' }}" mode="widthFix" />
11
11
  </view>
12
12
  </view>
13
13
  <!-- 文章列表 -->
@@ -24,8 +24,8 @@
24
24
  <block wx:for="{{ contents }}" wx:key="contentId" wx:for-item="item">
25
25
  <block wx:if="{{ item.infoType === 'image' }}">
26
26
  <view class="article-image-wrap" wx:for="{{ item.infoContentList }}" wx:key="index" wx:for-item="url">
27
- <image class="article-image" src="{{ imageUrl + '/clip/' + winWid + '/0/' + url }}"
28
- mode="widthFix" />
27
+ <image wx:if="{{ imageUrl }}" class="article-image"
28
+ src="{{ imageUrl + '/clip/' + winWid + '/0/' + url }}" mode="widthFix" />
29
29
  </view>
30
30
  </block>
31
31
  <block wx:if="{{ item.infoType === 'movie' }}">
@@ -12,13 +12,13 @@
12
12
  <view class="cms-bottom-right">
13
13
  <!-- 浏览 -->
14
14
  <view class="cms-bottom-look">
15
- <image class="bottom-look-image-left" src="{{ resourceUrl + '/icon_image/eye_open.png' }}"
16
- mode="widthFix" />
15
+ <image wx:if="{{ resourceUrl }}" class="bottom-look-image-left"
16
+ src="{{ resourceUrl + '/icon_image/eye_open.png' }}" mode="widthFix" />
17
17
  <view>{{ browseCount }}</view>
18
18
  </view>
19
19
  <!-- 点赞 -->
20
20
  <view class="cms-bottom-look" catch:tap="handleLike" data-infoid="{{ interactId }}">
21
- <image class="bottom-look-image-right"
21
+ <image wx:if="{{ resourceUrl }}" class="bottom-look-image-right"
22
22
  src="{{ !isLikeStatus ? resourceUrl+'/icon_image/thumbs_up.png' : resourceUrl+'/icon_image/like_fill2x.png' }}"
23
23
  mode="widthFix" />
24
24
  <view>{{ likeCount }}</view>
@@ -5,7 +5,8 @@
5
5
  data-item="{{ item }}">
6
6
  <view class="cms-info">
7
7
  <view class="cms-image" catch:tap="handlePreview" data-url="{{ item.imgUrl }}">
8
- <image class="cms-image-thumb" src="{{ imageUrl + item.imgUrl }}" mode="aspectFill" />
8
+ <image wx:if="{{ imageUrl }}" class="cms-image-thumb" src="{{ imageUrl + item.imgUrl }}"
9
+ mode="aspectFill" />
9
10
  </view>
10
11
  <view class="cms-text">
11
12
  <view class="cms-text-title">{{ item.infoTitle }}</view>
@@ -3,8 +3,8 @@
3
3
  <view class="location-card">
4
4
  <view class="location-body" catch:tap="handleNavigate">
5
5
  <view class="location-image" catch:tap="handlePreview" data-url="{{ article.imgUrl }}">
6
- <image class="location-image-thumb" src="{{ imageUrl + '/clip/300/300/'+ article.imgUrl }}"
7
- mode="aspectFill" />
6
+ <image wx:if="{{ imageUrl }}" class="location-image-thumb"
7
+ src="{{ imageUrl + '/clip/300/300/'+ article.imgUrl }}" mode="aspectFill" />
8
8
  </view>
9
9
  <view class="location-text">
10
10
  <view class="location-name">{{ article.infoTitle }}</view>
@@ -22,8 +22,8 @@
22
22
  <block wx:for="{{ contents }}" wx:key="contentId" wx:for-item="item">
23
23
  <block wx:if="{{ item.infoType === 'image' }}">
24
24
  <view class="article-image-wrap" wx:for="{{ item.infoContentList }}" wx:key="index" wx:for-item="url">
25
- <image class="article-image" src="{{ imageUrl + '/clip/' + winWid + '/0/' + url }}"
26
- mode="widthFix" />
25
+ <image wx:if="{{ imageUrl }}" class="article-image"
26
+ src="{{ imageUrl + '/clip/' + winWid + '/0/' + url }}" mode="widthFix" />
27
27
  </view>
28
28
  </block>
29
29
  <block wx:if="{{ item.infoType === 'movie' }}">
@@ -3,7 +3,8 @@
3
3
  <view class="product-card" wx:for="{{ articleList }}" wx:key="infoId" bind:tap="handleInfo"
4
4
  data-item="{{ item }}">
5
5
  <view class="product-image">
6
- <image class="product-image-thumb" src="{{ imageUrl + item.imgUrl }}" mode="aspectFill" />
6
+ <image wx:if="{{ imageUrl }}" class="product-image-thumb" src="{{ imageUrl + item.imgUrl }}"
7
+ mode="aspectFill" />
7
8
  </view>
8
9
  <view class="product-body">
9
10
  <view class="product-name">{{ item.infoTitle }}</view>
@@ -4,7 +4,8 @@
4
4
  <view class="product-card" wx:for="{{ articleList }}" wx:key="infoId" bind:tap="handleInfo"
5
5
  data-item="{{ item }}">
6
6
  <view class="product-image">
7
- <image class="product-image-thumb" src="{{ imageUrl + item.imgUrl }}" mode="aspectFill" />
7
+ <image wx:if="{{ imageUrl }}" class="product-image-thumb" src="{{ imageUrl + item.imgUrl }}"
8
+ mode="aspectFill" />
8
9
  </view>
9
10
  <view class="product-body">
10
11
  <view class="product-name">{{ item.infoTitle }}</view>
@@ -2,7 +2,7 @@
2
2
  <!-- 头像背景盒子 -->
3
3
  <button plain class="auth-image-box-bgc" open-type="chooseAvatar" bind:chooseavatar="onChooseAvatar">
4
4
  <!-- 显示的头像 -->
5
- <image class="image-box-show" src="{{ avatar }}" mode="aspectFit" />
5
+ <image wx:if="{{ avatar }}" class="image-box-show" src="{{ avatar }}" mode="aspectFit" />
6
6
  <!-- 提示选择头像图片 -->
7
7
  <image wx:if="{{ resourceUrl }}" class="select-image" src="{{ resourceUrl + 'camera2x.png' }}"
8
8
  mode="aspectFit" />
@@ -0,0 +1,524 @@
1
+ <template>
2
+ <!-- 表单基础单元:左侧标题 + 右侧内容/输入/开关 -->
3
+ <view
4
+ class="form-cell {{ required ? 'form-cell-required' : '' }} {{ border ? 'form-cell-border' : '' }} {{ type === 'textarea' ? 'form-cell-textarea-main' : '' }}">
5
+ <!-- 标题容器 -->
6
+ <view class="form-cell-label-container" wx:if="{{ label }}">
7
+ <view class="form-cell-label">
8
+ <text class="form-cell-label-required" wx:if="{{ required }}">*</text>
9
+ {{ label }}
10
+ </view>
11
+ <view wx:if="{{ subLabel }}" class="form-cell-sub-label">{{ subLabel }}</view>
12
+ </view>
13
+
14
+ <!-- 内容容器 -->
15
+ <view class="form-cell-content">
16
+ <!-- 输入框类型 -->
17
+ <block wx:if="{{ type === 'input' }}">
18
+ <input class="form-cell-input" style="{{ label ? 'text-align: right;' : 'text-align: left;' }}"
19
+ disabled="{{ readonly }}" type="{{ inputType }}" value="{{ value }}" placeholder="{{ placeholder }}"
20
+ placeholder-class="form-cell-placeholder" data-field="{{ field }}" bind:input="handleInput" />
21
+ </block>
22
+
23
+ <!-- 数字输入框类型 -->
24
+ <block wx:if="{{ type === 'number' }}">
25
+ <input class="form-cell-input" style="{{ label ? 'text-align: right;' : 'text-align: left;' }}"
26
+ disabled="{{ readonly }}" type="{{ inputType }}" value="{{ value }}" placeholder="{{ placeholder }}"
27
+ placeholder-class="form-cell-placeholder" data-field="{{ field }}" bind:input="handleInput" />
28
+ </block>
29
+
30
+ <!-- 文本域类型 -->
31
+ <block wx:elif="{{ type === 'textarea' }}">
32
+ <textarea class="form-cell-textarea" disabled="{{ readonly }}" value="{{ value }}"
33
+ placeholder="{{ placeholder }}" placeholder-class="form-cell-placeholder"
34
+ maxlength="{{ maxLength }}" data-field="{{ field }}" bind:input="handleInput" />
35
+ </block>
36
+
37
+ <!-- 开关类型 -->
38
+ <block wx:elif="{{ type === 'switch' }}">
39
+ <switch class="form-cell-switch" disabled="{{ readonly }}" checked="{{ value }}" color="#FC8E24"
40
+ data-field="{{ field }}" bind:change="handleSwitch" />
41
+ </block>
42
+
43
+ <!-- 选择器类型 -->
44
+ <block wx:elif="{{ type === 'picker' }}">
45
+ <view class="form-cell-picker"
46
+ style="{{ label ? 'justify-content: flex-end;' : 'justify-content: flex-start;' }}"
47
+ data-field="{{ field }}" bind:tap="{{ readonly ? '' : 'handlePicker' }}">
48
+ <view class="form-cell-picker-text {{ value ? '' : 'form-cell-picker-empty' }}"
49
+ style="{{ label ? '' : 'margin-right: auto;' }}">
50
+ {{ value || placeholder }}
51
+ </view>
52
+ <text class="form-cell-arrow">›</text>
53
+ </view>
54
+ </block>
55
+
56
+ <!-- 单选类型 -->
57
+ <block wx:elif="{{ type === 'radio' }}">
58
+ <cube-radio-group class="form-cell-radio-group" inline>
59
+ <cube-radio class="form-cell-radio" wx:for="{{ radioOptionList }}" wx:key="index"
60
+ wx:for-item="option" option="{{ option }}" value="{{ radioInnerValue }}"
61
+ data-field="{{ field }}" bind:input="handleRadioChange" />
62
+ </cube-radio-group>
63
+ </block>
64
+
65
+ <!-- 选择器选项类型 -->
66
+ <block wx:elif="{{ type === 'picker-option' }}">
67
+ <!-- 展示区 -->
68
+ <view class="form-cell-picker"
69
+ style="{{ label ? 'justify-content: flex-end;' : 'justify-content: flex-start;' }}"
70
+ data-field="{{ field }}" bind:tap="{{ readonly ? '' : 'handlePickerOptTap' }}">
71
+ <view class="form-cell-picker-text {{ pickerOptDisplayText ? '' : 'form-cell-picker-empty' }}"
72
+ style="{{ label ? '' : 'margin-right: auto;' }}">
73
+ {{ pickerOptDisplayText || placeholder }}
74
+ </view>
75
+ <text class="form-cell-arrow">›</text>
76
+ </view>
77
+ <!-- 弹出选择列表 -->
78
+ <cube-picker-popup wx:ref="pickerOption" show="{{ pickerOptShow }}" list="{{ pickerOptList }}"
79
+ selectedIndex="{{ pickerOptSelectedIndex }}" fullyStop="true" bind:change="handlePickerOptChange"
80
+ bind:confirm="handlePickerOptConfirm" bind:cancel="handlePickerOptClose" />
81
+ </block>
82
+
83
+ <!-- 上传图片类型 -->
84
+ <block wx:elif="{{ type === 'upload' }}">
85
+ <jmash-upload-picture limit="1" value="{{ value }}" bind:getImageArray="handleImageChange" />
86
+ </block>
87
+
88
+ <!-- 插槽类型 -->
89
+ <block wx:elif="{{ type === 'slot' }}">
90
+ <slot />
91
+ </block>
92
+ </view>
93
+ </view>
94
+ </template>
95
+
96
+ <script lang="ts">
97
+ import { createComponent, ref, computed, watch } from "@mpxjs/core";
98
+ import { EventBase } from "../../api/types";
99
+
100
+ createComponent({
101
+ options: {
102
+ styleIsolation: "apply-shared",
103
+ },
104
+ properties: {
105
+ // 表单字段名
106
+ field: {
107
+ type: String,
108
+ value: "",
109
+ },
110
+ // 左侧标题
111
+ label: {
112
+ type: String,
113
+ value: "",
114
+ },
115
+ // 是否必填
116
+ required: {
117
+ type: Boolean,
118
+ value: false,
119
+ },
120
+ // 类型:number / input / textarea / radio / switch / picker / picker-option / upload / slot
121
+ type: {
122
+ type: String,
123
+ value: "input",
124
+ },
125
+ // 单选选项(type 为 radio 时有效)
126
+ radioData: {
127
+ type: Array as any,
128
+ value: [],
129
+ },
130
+ // 单选选项映射:{ valueLable: "显示字段", valueKey: "值字段" }
131
+ radioOptions: {
132
+ type: Object,
133
+ value: {
134
+ valueLable: "value",
135
+ valueKey: "key",
136
+ },
137
+ },
138
+ // 输入类型
139
+ inputType: {
140
+ type: String,
141
+ value: "text",
142
+ },
143
+ // 当前值
144
+ value: {
145
+ type: null,
146
+ value: "",
147
+ },
148
+ // 占位文本
149
+ placeholder: {
150
+ type: String,
151
+ value: "",
152
+ },
153
+ // 最大长度(textarea 有效)
154
+ maxLength: {
155
+ type: Number,
156
+ value: 500,
157
+ },
158
+ // 是否显示底边框
159
+ border: {
160
+ type: Boolean,
161
+ value: true,
162
+ },
163
+ // 子标题
164
+ subLabel: {
165
+ type: String,
166
+ value: "",
167
+ },
168
+ // 是否只读
169
+ readonly: {
170
+ type: Boolean,
171
+ value: false,
172
+ },
173
+ // 自动选择器数据列表
174
+ pickerData: {
175
+ type: Array as any,
176
+ value: [],
177
+ },
178
+ // 自动选择器选项映射:{ valueLable: "显示字段", valueKey: "值字段" }
179
+ pickerOptions: {
180
+ type: Object,
181
+ value: {
182
+ valueLable: "value",
183
+ valueKey: "key",
184
+ },
185
+ },
186
+ },
187
+ setup(props) {
188
+ // 自动选择器弹框显隐
189
+ let pickerOptShow = ref(false);
190
+ // 自动选择器当前选中索引
191
+ let pickerOptSelectedIndex = ref([0]);
192
+ // 自动选择器展示文本
193
+ let pickerOptDisplayText = ref("");
194
+ // 自动选择器数据转 cube-picker-popup 格式(二维数组)
195
+ let pickerOptList = computed(() => {
196
+ const { pickerData, pickerOptions } = props;
197
+ if (!pickerData || !pickerData.length) return [[]];
198
+ return [
199
+ pickerData.map((item: any) => ({
200
+ text: item[pickerOptions.valueLable],
201
+ value: item[pickerOptions.valueKey],
202
+ ...item,
203
+ })),
204
+ ];
205
+ });
206
+
207
+ // 单选选项 value 是否为 boolean 类型
208
+ let isBooleanRadio = computed(() => {
209
+ const { radioData, radioOptions } = props;
210
+ if (!radioData || !radioData.length) return false;
211
+ return radioData.some((item) => typeof item[radioOptions.valueKey] === "boolean");
212
+ });
213
+ // 单选选项转 cube-radio 格式
214
+ let radioOptionList = computed(() => {
215
+ const { radioData, radioOptions } = props;
216
+ if (!radioData || !radioData.length) return [];
217
+ return radioData.map((item: any) => ({
218
+ text: item[radioOptions.valueLable],
219
+ value: isBooleanRadio.value ? String(item[radioOptions.valueKey]) : item[radioOptions.valueKey],
220
+ disabled: props.readonly,
221
+ }));
222
+ });
223
+ // 传给 cube-radio 的 value(boolean 时转字符串)
224
+ let radioInnerValue = ref<string | number | boolean>("");
225
+
226
+ watch(() => props.value, (val) => {
227
+ if (isBooleanRadio.value) {
228
+ radioInnerValue.value = val ? "true" : "false";
229
+ } else {
230
+ radioInnerValue.value = val;
231
+ }
232
+ }, { immediate: true });
233
+
234
+ return {
235
+ pickerOptShow,
236
+ pickerOptSelectedIndex,
237
+ pickerOptDisplayText,
238
+ pickerOptList,
239
+ isBooleanRadio,
240
+ radioOptionList,
241
+ radioInnerValue,
242
+ };
243
+ },
244
+ observers: {
245
+ // 当 value、data 或 options 变化时,刷新展示文本和选中索引
246
+ "value, pickerData, pickerOptions"(value: any, pickerData: any[], pickerOptions: any) {
247
+ if (!pickerData || !pickerData.length || !pickerOptions || !pickerOptions.valueKey) {
248
+ this.pickerOptSelectedIndex = [0];
249
+ this.pickerOptDisplayText = "";
250
+ return;
251
+ }
252
+ const index = pickerData.findIndex((item) => item[pickerOptions.valueKey] === value);
253
+ const selectedIndex = index >= 0 ? index : 0;
254
+ const displayText = index >= 0 ? pickerData[index][pickerOptions.valueLable] : "";
255
+ this.pickerOptSelectedIndex = [selectedIndex];
256
+ this.pickerOptDisplayText = displayText;
257
+ },
258
+ },
259
+ methods: {
260
+ // 输入框内容变化
261
+ handleInput(e: EventBase) {
262
+ const field = e.currentTarget.dataset.field;
263
+ this.triggerEvent("change", { field, value: e.detail.value });
264
+ },
265
+
266
+ // switch 状态变化
267
+ handleSwitch(e: EventBase) {
268
+ const field = e.currentTarget.dataset.field;
269
+ this.triggerEvent("change", { field, value: e.detail.value });
270
+ },
271
+
272
+ // 单选变化
273
+ handleRadioChange(e: EventBase) {
274
+ const field = e.currentTarget.dataset.field;
275
+ let value = e.detail.value;
276
+ // boolean 类型时转回 boolean
277
+ if (this.isBooleanRadio) {
278
+ value = value === "true";
279
+ }
280
+ this.triggerEvent("change", { field, value });
281
+ },
282
+
283
+ // 选择器点击
284
+ handlePicker(e: EventBase) {
285
+ const field = e.currentTarget.dataset.field;
286
+ this.triggerEvent("picker", { field });
287
+ },
288
+
289
+ // 上传图片变化
290
+ handleImageChange(e: EventBase) {
291
+ const imageArr = e.detail as string[];
292
+ this.triggerEvent("change", { field: this.field, value: imageArr[0] || "" });
293
+ },
294
+
295
+ // === 自动选择器方法 ===
296
+
297
+ // 点击展示区,打开选择弹框
298
+ handlePickerOptTap() {
299
+ // 同步当前 value 对应的索引
300
+ this._feedbackPickerOpt();
301
+ this.pickerOptShow = true;
302
+ if (this.$refs.pickerOption) this.$refs.pickerOption.show();
303
+ },
304
+ // 关闭弹框
305
+ handlePickerOptClose() {
306
+ this.pickerOptShow = false;
307
+ if (this.$refs.pickerOption) this.$refs.pickerOption.hide();
308
+ },
309
+ // 滚动选择
310
+ handlePickerOptChange(e: EventBase) {
311
+ const index = e.detail.selectedIndex as number[];
312
+ const item = this.pickerData[index[0]];
313
+ this.pickerOptSelectedIndex = index;
314
+ this.pickerOptDisplayText = item[this.pickerOptions.valueLable];
315
+ },
316
+ // 确认选择
317
+ handlePickerOptConfirm(e: EventBase) {
318
+ const index = e.detail.selectedIndex as number[];
319
+ const item = this.pickerData[index[0]];
320
+ this.pickerOptSelectedIndex = index;
321
+ this.pickerOptDisplayText = item[this.pickerOptions.valueLable];
322
+ // 关闭弹框
323
+ this.handlePickerOptClose();
324
+ this.triggerEvent("change", {
325
+ field: this.field,
326
+ value: item[this.pickerOptions.valueKey],
327
+ name: item[this.pickerOptions.valueLable],
328
+ info: item,
329
+ });
330
+ },
331
+ // 同步 value 到选中索引
332
+ _feedbackPickerOpt() {
333
+ if (!this.pickerData || !this.pickerData.length || !this.pickerOptions || !this.pickerOptions.valueKey) {
334
+ this.pickerOptSelectedIndex = [0];
335
+ return;
336
+ }
337
+ const index = this.pickerData.findIndex(
338
+ (item: any) => item[this.pickerOptions.valueKey] === this.value
339
+ );
340
+ this.pickerOptSelectedIndex = [index >= 0 ? index : 0];
341
+ },
342
+ },
343
+ });
344
+ </script>
345
+
346
+ <style lang="scss">
347
+ @use "../../styles/index.scss" as *;
348
+
349
+ .form-cell {
350
+ display: flex;
351
+ align-items: center;
352
+ justify-content: space-between;
353
+ min-height: 100rpx;
354
+ padding: 20rpx 30rpx;
355
+ box-sizing: border-box;
356
+ background: $background-white-color;
357
+ }
358
+
359
+ // 标题容器
360
+ .form-cell-label-container {
361
+ display: flex;
362
+ flex-direction: column;
363
+ gap: 10rpx;
364
+
365
+ // 标题
366
+ .form-cell-label {
367
+ font-weight: 400;
368
+ font-size: 30rpx;
369
+ color: $font-color;
370
+ line-height: 42rpx;
371
+ margin-right: 30rpx;
372
+
373
+ .form-cell-label-required {
374
+ color: $red-color;
375
+ margin-right: 6rpx;
376
+ }
377
+ }
378
+
379
+ // 副标题
380
+ .form-cell-sub-label {
381
+ font-weight: 400;
382
+ font-size: $font-size-mini;
383
+ color: $font-color-grey-opacity;
384
+ }
385
+ }
386
+
387
+ // 内容容器
388
+ .form-cell-content {
389
+ flex: 1;
390
+ display: flex;
391
+ align-items: center;
392
+ justify-content: flex-end;
393
+
394
+ // 输入框
395
+ .form-cell-input {
396
+ flex: 1;
397
+ font-weight: 400;
398
+ font-size: 30rpx;
399
+ color: $font-color;
400
+ line-height: 42rpx;
401
+ }
402
+
403
+ // 占位符
404
+ .form-cell-placeholder {
405
+ color: $font-color-grey-opacity;
406
+ }
407
+
408
+ // 开关
409
+ .form-cell-switch {
410
+ transform: scale(0.8);
411
+ }
412
+
413
+ // 单选框组
414
+ .form-cell-radio-group {
415
+ flex: 1;
416
+ display: flex;
417
+ align-items: center;
418
+ justify-content: flex-end;
419
+
420
+ .form-cell-radio {
421
+ margin-left: 40rpx;
422
+ }
423
+ }
424
+
425
+ // 自动选择器
426
+ .form-cell-picker {
427
+ flex: 1;
428
+ display: flex;
429
+ align-items: center;
430
+ gap: 10rpx;
431
+
432
+ .form-cell-picker-text {
433
+ font-weight: 400;
434
+ font-size: 30rpx;
435
+ color: $font-color;
436
+ line-height: 42rpx;
437
+ }
438
+
439
+ .form-cell-picker-empty {
440
+ color: $font-color-grey-opacity;
441
+ }
442
+
443
+ .form-cell-arrow {
444
+ color: $font-color-grey-opacity;
445
+ font-size: 32rpx;
446
+ }
447
+ }
448
+ }
449
+
450
+ // 必填项
451
+ .form-cell-required {
452
+ .form-cell-label {
453
+ text {
454
+ color: $red-color;
455
+ }
456
+ }
457
+ }
458
+
459
+ // 边框
460
+ .form-cell-border {
461
+ border-bottom: 1rpx solid $border-line;
462
+
463
+ :last-child {
464
+ border-bottom: none;
465
+ }
466
+ }
467
+
468
+ // 文本域主容器 垂直分布
469
+ .form-cell-textarea-main {
470
+ display: flex;
471
+ flex-direction: column;
472
+ gap: 10rpx;
473
+ align-items: flex-start;
474
+
475
+ .form-cell-content {
476
+ display: block;
477
+ width: 100%;
478
+
479
+ // 文本域
480
+ .form-cell-textarea {
481
+ width: 100%;
482
+ height: 160rpx;
483
+ text-align: left;
484
+ font-weight: 400;
485
+ font-size: 28rpx;
486
+ color: $font-color;
487
+ line-height: 40rpx;
488
+ padding: 20rpx;
489
+ box-sizing: border-box;
490
+ border-radius: $border-mini-radius;
491
+ background-color: $background-color;
492
+ }
493
+ }
494
+ }
495
+
496
+ // 上传图片组件穿透样式
497
+ .upload-picture-box {
498
+ .upload-box {
499
+ width: 90rpx !important;
500
+ height: 90rpx !important;
501
+ flex: auto !important;
502
+ }
503
+
504
+ .image_box {
505
+ width: 90rpx !important;
506
+ height: 90rpx !important;
507
+ flex: auto !important;
508
+ margin: 0 !important;
509
+ }
510
+ }
511
+ </style>
512
+
513
+ <script type="application/json">
514
+ {
515
+ "component": true,
516
+ "usingComponents": {
517
+ "jmash-upload-picture": "./jmash-upload-picture",
518
+ "cube-popup": "@mpxjs/mpx-cube-ui/lib/components/popup/index",
519
+ "cube-picker-popup": "@mpxjs/mpx-cube-ui/lib/components/picker-popup/index",
520
+ "cube-radio-group": "@mpxjs/mpx-cube-ui/lib/components/radio-group/index",
521
+ "cube-radio": "@mpxjs/mpx-cube-ui/lib/components/radio/index",
522
+ }
523
+ }
524
+ </script>
@@ -1,6 +1,7 @@
1
1
  <template>
2
2
  <cube-popup wx:if="{{ show }}" wx:ref="popup" zIndex="{{ zIndex }}" position="{{ position }}"
3
- transition="{{ transition }}" maskClosable="{{ true }}" bind:maskClick="closePopup">
3
+ transition="{{ transition }}" maskClosable="{{ maskClosable }}"
4
+ bind:maskClick="{{ maskClosable ? closePopup : '' }}">
4
5
  <view class="auth-mask-popup"
5
6
  style="width: {{ width }}; height: {{ height }}; background-color: {{ backColor }};border-top-left-radius: {{ borderRadius }};border-top-right-radius: {{ borderRadius }};">
6
7
  <view class="popup-title" wx:if="{{ titleShow }}">
@@ -69,6 +70,11 @@ createComponent({
69
70
  type: String,
70
71
  value: "move-up",
71
72
  },
73
+ // 是否可点击遮罩层关闭弹窗
74
+ maskClosable: {
75
+ type: Boolean,
76
+ value: true,
77
+ },
72
78
  // 层级
73
79
  zIndex: {
74
80
  type: Number,
@@ -0,0 +1,153 @@
1
+ <template>
2
+ <!-- 左滑删除单元格 -->
3
+ <view class="swipe-cell" bind:touchstart="onTouchStart" bind:touchmove="onTouchMove" bind:touchend="onTouchEnd">
4
+ <!-- 删除按钮 -->
5
+ <view class="swipe-cell-actions" style="width: {{ actionTotalWidth }}px;">
6
+ <view class="swipe-cell-delete" style="width: {{ actionWidth }}px;" catch:tap="handleDelete">
7
+ {{ deleteText }}
8
+ </view>
9
+ </view>
10
+ <!-- 内容 -->
11
+ <view class="swipe-cell-content" style="transform: translate3d({{ translateX }}px, 0, 0);">
12
+ <slot />
13
+ </view>
14
+ </view>
15
+ </template>
16
+
17
+ <script lang="ts">
18
+ import mpx, { createComponent, ref } from "@mpxjs/core";
19
+ import { EventBase } from "../../api/types";
20
+
21
+ createComponent({
22
+ options: {
23
+ styleIsolation: "apply-shared",
24
+ },
25
+ properties: {
26
+ // 当前展开项的 key(父组件传入,用于协调一次只展开一个)
27
+ activeKey: {
28
+ type: String,
29
+ value: "",
30
+ observer(newVal: string) {
31
+ if (newVal !== this.itemKey) {
32
+ this.translateX = 0;
33
+ }
34
+ },
35
+ },
36
+ // 本项唯一 key
37
+ itemKey: {
38
+ type: String,
39
+ value: "",
40
+ },
41
+ // 删除按钮文字
42
+ deleteText: {
43
+ type: String,
44
+ value: "删除",
45
+ },
46
+ // 删除按钮宽度(rpx)
47
+ deleteWidth: {
48
+ type: Number,
49
+ value: 110,
50
+ },
51
+ },
52
+ setup(props) {
53
+ // 滑动偏移量(px)
54
+ let translateX = ref(0);
55
+ // 触摸开始 X 坐标
56
+ let touchStartX = ref(0);
57
+ // 触摸开始偏移量
58
+ let touchStartOffsetX = ref(0);
59
+ // 删除按钮宽度(px)
60
+ const systemInfo = mpx.getSystemInfoSync();
61
+ let actionWidth = ref(Math.round((props.deleteWidth * systemInfo.windowWidth) / 750));
62
+ // 删除按钮总宽度(含间距,px)
63
+ let actionTotalWidth = ref(actionWidth.value + Math.round((2 * systemInfo.windowWidth) / 750));
64
+
65
+ return {
66
+ translateX,
67
+ touchStartX,
68
+ touchStartOffsetX,
69
+ actionWidth,
70
+ actionTotalWidth,
71
+ };
72
+ },
73
+ methods: {
74
+ // 触摸开始
75
+ onTouchStart(e: EventBase) {
76
+ const startX = e.touches?.[0]?.clientX ?? 0;
77
+ this.touchStartX = startX;
78
+ this.touchStartOffsetX = this.translateX;
79
+ // 通知父组件本项开始滑动,协调关闭其他项
80
+ this.triggerEvent("open", { key: this.itemKey });
81
+ },
82
+ // 触摸移动
83
+ onTouchMove(e: EventBase) {
84
+ const moveX = e.touches?.[0]?.clientX ?? 0;
85
+ const deltaX = moveX - this.touchStartX;
86
+ const nextOffset = Math.max(
87
+ -this.actionTotalWidth,
88
+ Math.min(0, this.touchStartOffsetX + deltaX)
89
+ );
90
+ this.translateX = nextOffset;
91
+ },
92
+ // 触摸结束
93
+ onTouchEnd() {
94
+ if (this.translateX < -this.actionWidth / 2) {
95
+ this.translateX = -this.actionTotalWidth;
96
+ } else {
97
+ this.translateX = 0;
98
+ }
99
+ },
100
+ // 点击删除按钮
101
+ handleDelete() {
102
+ this.triggerEvent("delete", { key: this.itemKey });
103
+ },
104
+ },
105
+ });
106
+ </script>
107
+
108
+ <style lang="scss">
109
+ @use "../../styles/index.scss" as *;
110
+
111
+ .swipe-cell {
112
+ position: relative;
113
+ overflow: hidden;
114
+ }
115
+
116
+ // 删除按钮区域
117
+ .swipe-cell-actions {
118
+ position: absolute;
119
+ top: 0;
120
+ right: 0;
121
+ height: 100%;
122
+ display: flex;
123
+ justify-content: flex-end;
124
+ align-items: stretch;
125
+ z-index: 1;
126
+ }
127
+
128
+ // 删除按钮
129
+ .swipe-cell-delete {
130
+ display: flex;
131
+ align-items: center;
132
+ justify-content: center;
133
+ background-color: $red-color;
134
+ color: $white-color;
135
+ font-size: 28rpx;
136
+ border-radius: $border-mini-radius;
137
+ }
138
+
139
+ // 滑动内容
140
+ .swipe-cell-content {
141
+ position: relative;
142
+ z-index: 2;
143
+ will-change: transform;
144
+ transition: transform 0.15s ease-out;
145
+ }
146
+ </style>
147
+
148
+ <script type="application/json">
149
+ {
150
+ "component": true,
151
+ "usingComponents": {}
152
+ }
153
+ </script>
@@ -3,9 +3,9 @@
3
3
  <view class="upload-picture-box">
4
4
  <block wx:for="{{ imageArray }}" wx:key="index">
5
5
  <view class="image_box">
6
- <image class="upload_image" src="{{ baseUrl + item }}" />
7
- <image class="del_image" src="{{ resourceUrl + 'file_del_icon.png' }}" mode="widthFix" data-index="{{ index }}"
8
- bind:tap="handleDel" />
6
+ <image wx:if="{{ baseUrl }}" class="upload_image" src="{{ baseUrl + item }}" />
7
+ <image wx:if="{{ resourceUrl }}" class="del_image" src="{{ resourceUrl + 'file_del_icon.png' }}" mode="widthFix"
8
+ data-index="{{ index }}" bind:tap="handleDel" />
9
9
  </view>
10
10
  </block>
11
11
  <view wx:if="{{ imageArray.length < limit }}" class="upload-box" bind:tap="handleUploadImage">
@@ -18,7 +18,7 @@
18
18
  </template>
19
19
 
20
20
  <script lang="ts">
21
- import mpx, { createComponent, ref } from "@mpxjs/core";
21
+ import mpx, { createComponent, ref, watch } from "@mpxjs/core";
22
22
  import { EventBase } from "../../api/types";
23
23
  import { db } from "../../utils/db";
24
24
 
@@ -31,9 +31,14 @@ createComponent({
31
31
  limit: {
32
32
  type: Number,
33
33
  value: 6
34
+ },
35
+ // 回显图片(字符串或数组)
36
+ value: {
37
+ type: null,
38
+ value: ""
34
39
  }
35
40
  },
36
- setup() {
41
+ setup(props) {
37
42
  let config = getApp().globalData.config;
38
43
  // 资源路径
39
44
  let resourceUrl = config.resourceUrl + "/images/mall/";
@@ -42,6 +47,17 @@ createComponent({
42
47
  // 上传图片数组
43
48
  let imageArray = ref([] as Array<string>);
44
49
 
50
+ // 回显已有图片(value 支持字符串或数组)
51
+ watch(() => props.value, (val) => {
52
+ if (Array.isArray(val)) {
53
+ imageArray.value = val;
54
+ } else if (val) {
55
+ imageArray.value = [val];
56
+ } else {
57
+ imageArray.value = [];
58
+ }
59
+ }, { immediate: true });
60
+
45
61
  return {
46
62
  resourceUrl,
47
63
  baseUrl,
@@ -129,10 +145,11 @@ createComponent({
129
145
  display: flex;
130
146
  align-items: center;
131
147
  flex-wrap: wrap;
148
+ gap: 20rpx;
132
149
  }
133
150
 
134
151
  .upload_image {
135
- width: 100%;
152
+ width: 196rpx;
136
153
  height: 196rpx;
137
154
  vertical-align: top;
138
155
  }
@@ -142,10 +159,11 @@ createComponent({
142
159
  font-size: 24rpx;
143
160
  display: flex;
144
161
  flex-direction: column;
145
- height: 192rpx;
162
+ width: 196rpx;
163
+ height: 196rpx;
146
164
  justify-content: center;
147
165
  align-items: center;
148
- flex: 0 0 calc((100% - 42rpx)/3);
166
+ // flex: 0 0 calc((100% - 42rpx)/3);
149
167
 
150
168
  .upload-icon {
151
169
  width: 60rpx;
@@ -155,9 +173,10 @@ createComponent({
155
173
 
156
174
  .image_box {
157
175
  position: relative;
176
+ width: 196rpx;
158
177
  height: 196rpx;
159
- flex: 0 0 calc((100% - 42rpx)/3);
160
- margin: 0 20rpx 20rpx 0;
178
+ // flex: 0 0 calc((100% - 42rpx)/3);
179
+ // margin: 0 20rpx 20rpx 0;
161
180
  }
162
181
 
163
182
  .image_box:nth-child(3n) {
@@ -6,7 +6,8 @@
6
6
  <!-- 头像区域 -->
7
7
  <view class="avatar-section">
8
8
  <button class="avatar-wrapper" open-type="chooseAvatar" bind:chooseavatar="onChooseAvatar">
9
- <image class="avatar-img" src="{{ avatarUrl || avatar }}" mode="aspectFill" />
9
+ <image wx:if="{{ avatarUrl || avatar }}" class="avatar-img" src="{{ avatarUrl || avatar }}"
10
+ mode="aspectFill" />
10
11
  <view class="avatar-badge">
11
12
  <text class="badge-icon">✎</text>
12
13
  </view>
@@ -3,7 +3,7 @@
3
3
  <view class="login_box">
4
4
  <view class="avatar-section">
5
5
  <view class="avatar-wrapper">
6
- <image class="avatar-img" src="{{ avatar }}" />
6
+ <image wx:if="{{ avatar }}" class="avatar-img" src="{{ avatar }}" />
7
7
  </view>
8
8
  <view class="avatar-tip">欢迎登录</view>
9
9
  </view>
@@ -17,7 +17,8 @@
17
17
  <!-- 头像区域 -->
18
18
  <view class="avatar-section">
19
19
  <button class="avatar-wrapper" open-type="chooseAvatar" bind:chooseavatar="onChooseAvatar">
20
- <image class="avatar-img" src="{{ avatarUrl || avatar }}" mode="aspectFill" />
20
+ <image wx:if="{{ avatarUrl || avatar }}" class="avatar-img" src="{{ avatarUrl || avatar }}"
21
+ mode="aspectFill" />
21
22
  <view class="avatar-badge">
22
23
  <text class="badge-icon">✎</text>
23
24
  </view>