Compare commits
23 Commits
改版
...
mrr_sj_dev
| Author | SHA1 | Date |
|---|---|---|
|
|
573aef3210 | |
|
|
3e39ff1140 | |
|
|
ee20019de3 | |
|
|
fd5923a4dc | |
|
|
cd4373f91f | |
|
|
93133dc460 | |
|
|
466d1e4d2e | |
|
|
122c19909c | |
|
|
9ded6affa3 | |
|
|
c47c653bf6 | |
|
|
79ee295c18 | |
|
|
0e9bca8d97 | |
|
|
6b659a6878 | |
|
|
6aa66ce311 | |
|
|
a7f9544bc5 | |
|
|
d4a6a63b65 | |
|
|
90edc49e80 | |
|
|
126e236cc2 | |
|
|
e89470df68 | |
|
|
a7654b4071 | |
|
|
dd731c584a | |
|
|
a4df9a6269 | |
|
|
f7e8388e67 |
42
App.vue
|
|
@ -16,6 +16,9 @@ import request from './utils/request'
|
||||||
import {
|
import {
|
||||||
setVersion
|
setVersion
|
||||||
} from './utils/version.js'
|
} from './utils/version.js'
|
||||||
|
import {
|
||||||
|
APP_BACK_BASE_URL
|
||||||
|
} from './utils/appBackBase.js'
|
||||||
//初始化一键登录插件
|
//初始化一键登录插件
|
||||||
const aLiSDKModule = uni.requireNativePlugin('AliCloud-NirvanaPns');
|
const aLiSDKModule = uni.requireNativePlugin('AliCloud-NirvanaPns');
|
||||||
export default {
|
export default {
|
||||||
|
|
@ -66,38 +69,13 @@ export default {
|
||||||
// await setVersion()
|
// await setVersion()
|
||||||
|
|
||||||
//获取访问路由
|
//获取访问路由
|
||||||
// 发起 GET 请求
|
// #ifndef MP-WEIXIN
|
||||||
let version = uni.getStorageSync('setVersion')
|
uni.setStorageSync('baseUrl', APP_BACK_BASE_URL)
|
||||||
let baseUrl
|
// #endif
|
||||||
await uni.request({
|
// #ifdef MP-WEIXIN
|
||||||
url: 'https://api.mrrwlkj.top/api/openPuc/getSjApiUrl', // 接口地址
|
wx.setStorageSync('baseUrl', APP_BACK_BASE_URL)
|
||||||
method: 'GET', // 请求方法(默认 GET)
|
// #endif
|
||||||
header: {
|
this.$store.commit("setLocation", true)
|
||||||
'Content-Type': 'application/json', // 请求头
|
|
||||||
'VersionCode': version.VersionCode,
|
|
||||||
'DeviceBrand': version.DeviceBrand,
|
|
||||||
'DeviceType': version.DeviceType
|
|
||||||
},
|
|
||||||
success: (res) => {
|
|
||||||
|
|
||||||
if (res.data.code == 200) {
|
|
||||||
// #ifndef MP-WEIXIN
|
|
||||||
uni.setStorageSync('baseUrl', res.data.data.url)
|
|
||||||
// #endif
|
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
wx.setStorageSync('baseUrl', res.data.data.url)
|
|
||||||
// #endif
|
|
||||||
this.$store.commit("setLocation", true)
|
|
||||||
}
|
|
||||||
console.log('请求成功', res.data);
|
|
||||||
},
|
|
||||||
fail: (err) => {
|
|
||||||
console.log('请求失败', err);
|
|
||||||
},
|
|
||||||
complete: () => {
|
|
||||||
console.log('请求完成(无论成功失败都会执行)');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// #ifdef APP-PLUS
|
// #ifdef APP-PLUS
|
||||||
//禁止手机横屏
|
//禁止手机横屏
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
# 添加服务页改动记录
|
||||||
|
|
||||||
|
## 2026-06-11
|
||||||
|
|
||||||
|
### 1. 接口返回视频字段未正常显示
|
||||||
|
|
||||||
|
涉及文件:
|
||||||
|
|
||||||
|
- `pages/shop/add-service.vue`
|
||||||
|
- `components/dynamic-form/dynamic-form.vue`
|
||||||
|
- `components/dynamic-form/FormItemVideo.vue`
|
||||||
|
|
||||||
|
改动内容:
|
||||||
|
|
||||||
|
- `pages/shop/add-service.vue`
|
||||||
|
- 动态模板转换时不再过滤 `fieldType === 'video'` 的字段。
|
||||||
|
- 新增 `hasDynamicVideoField`,当接口模板已经返回视频字段时,使用动态表单内的视频上传组件。
|
||||||
|
- 原页面固定视频上传块保留为兜底,仅在模板没有视频字段时显示,避免重复显示两个视频上传组件。
|
||||||
|
- 给 `dynamic-form` 传入 `upload-user-id`,让动态视频上传能拿到当前发布用户 ID。
|
||||||
|
- 初始化动态字段时支持分组结构,视频多选字段按数组初始化。
|
||||||
|
|
||||||
|
- `components/dynamic-form/dynamic-form.vue`
|
||||||
|
- 视频字段渲染时传入 `maxCount`、`maxSize/maxFileSize`。
|
||||||
|
- 新增 `uploadUserId` prop,并传给 `form-item-video`。
|
||||||
|
- 多视频字段初始化为空数组,单视频字段初始化为空字符串。
|
||||||
|
|
||||||
|
- `components/dynamic-form/FormItemVideo.vue`
|
||||||
|
- 支持单视频和多视频值类型。
|
||||||
|
- 使用 `ali-oss-uploader` 上传视频时传入 `kind=2`。
|
||||||
|
- 支持视频上传成功后回写、删除后清空、已有视频回显。
|
||||||
|
- 上传用户 ID 优先使用父组件传入值,兜底读取 Vuex 中的商家信息。
|
||||||
|
|
||||||
|
### 2. 视频上传后传给后端的地址打不开
|
||||||
|
|
||||||
|
涉及文件:
|
||||||
|
|
||||||
|
- `components/ali-oss-uploader/ali-oss-uploader.vue`
|
||||||
|
|
||||||
|
改动内容:
|
||||||
|
|
||||||
|
- 上传 OSS 后不再无条件拼接 URL 并回传。
|
||||||
|
- H5 `fetch` 上传和 APP/小程序 `uni.uploadFile` 上传都必须返回 `2xx` 才认为成功。
|
||||||
|
- 上传失败时 reject,并触发 `error`,避免把不存在的 OSS 地址提交给后端。
|
||||||
|
- 统一生成 OSS object key,上传使用的 key 和回传给后端的 URL 使用同一套路径规则。
|
||||||
|
- 文件扩展名提取增加兜底逻辑:
|
||||||
|
- 视频默认 `.mp4`
|
||||||
|
- 图片默认 `.jpg`
|
||||||
|
- 清理临时路径中的 query/hash,避免生成异常对象名。
|
||||||
|
|
||||||
|
### 3. 添加服务页多图上传支持拖动排序
|
||||||
|
|
||||||
|
涉及文件:
|
||||||
|
|
||||||
|
- `components/ali-oss-uploader/ali-oss-uploader.vue`
|
||||||
|
- `components/dynamic-form/FormItemImage.vue`
|
||||||
|
- `pages/shop/add-service.vue`
|
||||||
|
|
||||||
|
改动内容:
|
||||||
|
|
||||||
|
- `components/ali-oss-uploader/ali-oss-uploader.vue`
|
||||||
|
- 新增 `sortable` prop,默认关闭。
|
||||||
|
- 图片上传列表支持长按开始拖动。
|
||||||
|
- 拖动到其他图片位置时实时交换数组顺序。
|
||||||
|
- 每次排序后立即触发 `input` 和 `change`,向父组件回写排序后的 `fileList`。
|
||||||
|
- 拖动中增加视觉状态,降低透明度并缩放。
|
||||||
|
- 拖动结束后短暂抑制图片预览,避免排序后误触发预览。
|
||||||
|
- 图片预览使用当前 `fileList` 字符串数组,保持预览顺序和提交顺序一致。
|
||||||
|
|
||||||
|
- `components/dynamic-form/FormItemImage.vue`
|
||||||
|
- 动态表单多图上传开启 `sortable`。
|
||||||
|
|
||||||
|
- `pages/shop/add-service.vue`
|
||||||
|
- 提交逻辑沿用当前数组顺序:
|
||||||
|
- `image_multi` 字段按动态表单当前数组顺序传参。
|
||||||
|
- `photo` 字段通过 `Object.values(this.formData.photo || {})` 取当前顺序传参。
|
||||||
|
|
||||||
|
### 4. 校验情况
|
||||||
|
|
||||||
|
- 已执行 `git diff --check`,当前相关改动无 whitespace 错误。
|
||||||
|
- 项目 `package.json` 未配置 build/lint 脚本,未执行完整构建校验。
|
||||||
|
- 本地缺少 `vue-template-compiler`,未执行 Vue 模板编译校验。
|
||||||
|
|
@ -15,7 +15,6 @@
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
<script>
|
<script>
|
||||||
// 引入 spark-md5 计算文件哈希
|
// 引入 spark-md5 计算文件哈希
|
||||||
import SparkMD5 from 'spark-md5'
|
import SparkMD5 from 'spark-md5'
|
||||||
|
|
@ -42,7 +41,7 @@ export default {
|
||||||
maxSize: {
|
maxSize: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 500
|
default: 500
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -53,7 +52,7 @@ export default {
|
||||||
fileMd5: '',
|
fileMd5: '',
|
||||||
cancelToken: null
|
cancelToken: null
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// 选择视频文件
|
// 选择视频文件
|
||||||
async chooseVideo() {
|
async chooseVideo() {
|
||||||
|
|
@ -319,7 +318,7 @@ export default {
|
||||||
chunkIndex,
|
chunkIndex,
|
||||||
fileMd5: this.fileMd5,
|
fileMd5: this.fileMd5,
|
||||||
chunkSize: this.chunkSize,
|
chunkSize: this.chunkSize,
|
||||||
totalChunks: Math.ceil(this.file.size / this.chunkSize))
|
totalChunks: Math.ceil(this.file.size / this.chunkSize)
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
'Content-Type': 'multipart/form-data'
|
'Content-Type': 'multipart/form-data'
|
||||||
|
|
@ -411,4 +410,4 @@ button {
|
||||||
color: #666;
|
color: #666;
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,15 @@
|
||||||
<view class="preview-container" :style="'width:'+width">
|
<view class="preview-container" :style="'width:'+width">
|
||||||
<!-- 预览区域 -->
|
<!-- 预览区域 -->
|
||||||
<view v-if="accept == 'image/*'" class="preview-container" :style="'width:'+width">
|
<view v-if="accept == 'image/*'" class="preview-container" :style="'width:'+width">
|
||||||
<view
|
<view
|
||||||
v-for="(file, index) in fileList"
|
v-for="(file, index) in fileList"
|
||||||
:key="index"
|
:key="file || index"
|
||||||
class="preview-item"
|
class="preview-item image-preview-item"
|
||||||
|
:class="{ dragging: dragIndex === index }"
|
||||||
|
@longpress="startDrag(index)"
|
||||||
|
@touchmove="handleDragMove"
|
||||||
|
@touchend="endDrag"
|
||||||
|
@touchcancel="endDrag"
|
||||||
>
|
>
|
||||||
<image
|
<image
|
||||||
:src="file"
|
:src="file"
|
||||||
|
|
@ -14,13 +19,17 @@
|
||||||
mode="aspectFill"
|
mode="aspectFill"
|
||||||
@click="handlePreview(index)"
|
@click="handlePreview(index)"
|
||||||
/>
|
/>
|
||||||
<view v-if="deletable" class="delete-icon" @click.stop="handleDelete(index)">
|
<image
|
||||||
×
|
v-if="deletable"
|
||||||
</view>
|
class="delete-icon"
|
||||||
|
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/6ab3fc63-3bad-403d-bbfc-9609a93c048a.png"
|
||||||
|
mode="aspectFit"
|
||||||
|
@click.stop="handleDelete(index)"
|
||||||
|
></image>
|
||||||
</view>
|
</view>
|
||||||
<!-- 上传按钮 -->
|
<!-- 上传按钮 -->
|
||||||
<view class="preview-item">
|
<view class="preview-item">
|
||||||
<view
|
<view
|
||||||
class="upload-btn"
|
class="upload-btn"
|
||||||
@click="handleUpload"
|
@click="handleUpload"
|
||||||
:style="btnStyle"
|
:style="btnStyle"
|
||||||
|
|
@ -31,32 +40,45 @@
|
||||||
</slot>
|
</slot>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 视频预览区域 -->
|
<!-- 视频预览区域 -->
|
||||||
<view class="preview-container" :style="'width:'+width" v-if="accept == 'video/*'">
|
<view class="preview-container" :style="'width:'+width" v-if="accept == 'video/*'">
|
||||||
<view
|
<block v-if="showPreview">
|
||||||
class="preview-item"
|
<view
|
||||||
v-if="oneFile"
|
v-for="(file, index) in videoList"
|
||||||
>
|
:key="file || index"
|
||||||
<!-- <video
|
class="preview-item"
|
||||||
:src="oneFile"
|
@click="previewVideo(file)"
|
||||||
class="preview-image"
|
>
|
||||||
@click="handlePreview(index)"
|
<!-- <video
|
||||||
/> -->
|
:src="file"
|
||||||
<image class="preview-image" :src="oneFile + '?x-oss-process=video/snapshot,t_0,f_jpg'" mode="aspectFill"></image>
|
class="preview-image"
|
||||||
<view v-if="deletable" class="delete-icon" @click.stop="handleDelete()">
|
@click="handlePreview(index)"
|
||||||
×
|
/> -->
|
||||||
|
<image class="preview-image" :src="getVideoPoster(file)" mode="aspectFill"></image>
|
||||||
|
<view class="video-play-mask">
|
||||||
|
<view class="video-play-circle">
|
||||||
|
<view class="video-play-icon"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<image
|
||||||
|
v-if="deletable"
|
||||||
|
class="delete-icon"
|
||||||
|
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/6ab3fc63-3bad-403d-bbfc-9609a93c048a.png"
|
||||||
|
mode="aspectFit"
|
||||||
|
@click.stop="handleDelete(index)"
|
||||||
|
></image>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</block>
|
||||||
<view class="preview-item">
|
<view class="preview-item">
|
||||||
<!-- 上传按钮 -->
|
<!-- 上传按钮 -->
|
||||||
<view
|
<view
|
||||||
class="upload-btn"
|
class="upload-btn"
|
||||||
@click="handleUpload"
|
@click="handleUpload"
|
||||||
:style="btnStyle"
|
:style="btnStyle"
|
||||||
v-if="!oneFile"
|
v-if="videoCount < maxCount"
|
||||||
>
|
>
|
||||||
<slot name="button">
|
<slot name="button">
|
||||||
<text class="plus-icon">+</text>
|
<text class="plus-icon">+</text>
|
||||||
|
|
@ -65,9 +87,17 @@
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view v-if="showVideoPlayer" class="video-player-mask" @click="closeVideoPlayer">
|
||||||
|
<view class="video-player-box" @click.stop>
|
||||||
|
<video :src="previewVideoUrl" class="video-player" controls autoplay show-fullscreen-btn></video>
|
||||||
|
<view class="video-player-close" @click="closeVideoPlayer">
|
||||||
|
<text class="video-player-close-text">×</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
<!-- 提示文字 -->
|
<!-- 提示文字 -->
|
||||||
<!-- <text v-if="tips" class="tips-text">{{ tips }}</text> -->
|
<!-- <text v-if="tips" class="tips-text">{{ tips }}</text> -->
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -77,16 +107,16 @@ import permissionUtils from '../../utils/per'
|
||||||
import locationService from '../../utils/locationService';
|
import locationService from '../../utils/locationService';
|
||||||
export default {
|
export default {
|
||||||
name: 'AliOssUploader',
|
name: 'AliOssUploader',
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
// 已上传文件列表
|
// 已上传文件列表
|
||||||
value: {
|
value: {
|
||||||
type: Array,
|
type: [Array, String, Object],
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
// 单个上传文件
|
// 单个上传文件
|
||||||
fileString: {
|
fileString: {
|
||||||
type: String,
|
type: [String, Object, Array],
|
||||||
default: ''
|
default: ''
|
||||||
},
|
},
|
||||||
// 按钮文字
|
// 按钮文字
|
||||||
|
|
@ -145,34 +175,89 @@ export default {
|
||||||
kind: {
|
kind: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 1
|
default: 1
|
||||||
|
},
|
||||||
|
sortable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
fileList: [],
|
fileList: [],
|
||||||
oneFile: '',
|
oneFile: '',
|
||||||
loading: false
|
videoList: [],
|
||||||
|
loading: false,
|
||||||
|
dragIndex: -1,
|
||||||
|
isDragging: false,
|
||||||
|
suppressPreview: false,
|
||||||
|
showVideoPlayer: false,
|
||||||
|
previewVideoUrl: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
videoCount() {
|
||||||
|
return this.showPreview ? this.videoList.length : 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
value: {
|
value: {
|
||||||
immediate: true,
|
immediate: true,
|
||||||
handler(newVal) {
|
handler(newVal) {
|
||||||
this.fileList = [...newVal]
|
if (this.accept === 'video/*') {
|
||||||
|
const videoValue = this.normalizeVideoValue(newVal)
|
||||||
|
this.oneFile = videoValue
|
||||||
|
this.videoList = videoValue ? [videoValue] : []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.fileList = Array.isArray(newVal) ? [...newVal] : []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fileString: {
|
fileString: {
|
||||||
immediate: true,
|
immediate: true,
|
||||||
handler(newVal) {
|
handler(newVal) {
|
||||||
this.oneFile = newVal
|
const videoValue = this.normalizeVideoValue(newVal)
|
||||||
|
this.oneFile = videoValue
|
||||||
|
this.videoList = videoValue ? [videoValue] : []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
normalizeVideoValue(value) {
|
||||||
|
if (!value) return ''
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
const str = value.trim()
|
||||||
|
if (!str) return ''
|
||||||
|
if ((str.startsWith('{') && str.endsWith('}')) || (str.startsWith('[') && str.endsWith(']'))) {
|
||||||
|
try {
|
||||||
|
return this.normalizeVideoValue(JSON.parse(str))
|
||||||
|
} catch (error) {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
for (const item of value) {
|
||||||
|
const url = this.normalizeVideoValue(item)
|
||||||
|
if (url) return url
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
const candidates = [value.url, value.path, value.filePath, value.tempFilePath, value.src, value.value]
|
||||||
|
for (const item of candidates) {
|
||||||
|
const url = this.normalizeVideoValue(item)
|
||||||
|
if (url) return url
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
return String(value)
|
||||||
|
},
|
||||||
|
|
||||||
// 显示权限说明弹窗
|
// 显示权限说明弹窗
|
||||||
showPermissionDialog(title, content) {
|
showPermissionDialog(title, content) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
|
|
@ -272,12 +357,15 @@ export default {
|
||||||
await this.uploadFile(file);
|
await this.uploadFile(file);
|
||||||
}
|
}
|
||||||
} else if (this.accept === 'video/*') {
|
} else if (this.accept === 'video/*') {
|
||||||
const res = await uni.chooseVideo({
|
const count = this.maxCount - this.videoCount;
|
||||||
sourceType: [sourceType],
|
if (count <= 0) {
|
||||||
compressed: true,
|
uni.showToast({ title: `最多上传${this.maxCount}个文件`, icon: 'none' });
|
||||||
maxDuration: 60
|
return;
|
||||||
});
|
}
|
||||||
await this.uploadFile(res);
|
const files = await this.chooseVideoFiles(sourceType, count);
|
||||||
|
for (const file of files) {
|
||||||
|
await this.uploadFile(file);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('选择文件失败:', error);
|
console.error('选择文件失败:', error);
|
||||||
|
|
@ -285,38 +373,27 @@ export default {
|
||||||
},
|
},
|
||||||
// 上传文件到OSS
|
// 上传文件到OSS
|
||||||
async uploadFile(file) {
|
async uploadFile(file) {
|
||||||
|
const uploadFile = await this.prepareUploadFile(file)
|
||||||
// 检查文件大小
|
// 检查文件大小
|
||||||
if (file.size > this.maxSize * 1024 * 1024) {
|
if (uploadFile.size > this.maxSize * 1024 * 1024) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: `文件大小不能超过${this.maxSize}MB`,
|
title: `文件大小不能超过${this.maxSize}MB`,
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
return Promise.reject(new Error('文件大小超出限制'))
|
return Promise.reject(new Error('文件大小超出限制'))
|
||||||
}
|
}
|
||||||
let date = new Date().getTime()
|
let date = `${new Date().getTime()}_${Math.floor(Math.random() * 100000)}`
|
||||||
let imageType;
|
const fileExt = this.getFileExt(uploadFile)
|
||||||
if(this.accept == 'image/*') {
|
|
||||||
imageType = file.path.split('.')
|
|
||||||
}else if(this.accept == 'video/*') {
|
|
||||||
// #ifdef H5
|
|
||||||
imageType = file.name.split('.')
|
|
||||||
// #endif
|
|
||||||
// #ifdef APP-PLUS || MP-WEIXIN
|
|
||||||
imageType = file.tempFilePath.split('.')
|
|
||||||
// #endif
|
|
||||||
}
|
|
||||||
console.log('imageType',imageType)
|
|
||||||
|
|
||||||
let artisan = this.type==1 ? 'yh' : this.type==2 ? 'syr' : this.type==3 ? 'sj' : '';
|
let artisan = this.type==1 ? 'yh' : this.type==2 ? 'syr' : this.type==3 ? 'sj' : '';
|
||||||
let name = `${this.userId}_${artisan}_${date}.${imageType[imageType.length - 1]}`
|
let name = `${this.userId}_${artisan}_${date}.${fileExt}`
|
||||||
console.log('name',name)
|
console.log('name',name)
|
||||||
try {
|
try {
|
||||||
// 1. 获取OSS上传凭证
|
// 1. 获取OSS上传凭证
|
||||||
const ossConfig = await this.getOssConfig()
|
const ossConfig = await this.getOssConfig()
|
||||||
// 2. 上传文件
|
// 2. 上传文件
|
||||||
const uploadRes = await this.uploadToOss(file, ossConfig, name)
|
const fileUrl = await this.uploadToOss(uploadFile, ossConfig, name)
|
||||||
// 3. 添加到文件列表
|
// 3. 添加到文件列表
|
||||||
const fileUrl = `${ossConfig.host}/${ossConfig.dir}${name}`
|
|
||||||
if(this.accept == 'image/*') {
|
if(this.accept == 'image/*') {
|
||||||
const newFile = [`${fileUrl}`]
|
const newFile = [`${fileUrl}`]
|
||||||
this.fileList = this.fileList.concat(newFile)
|
this.fileList = this.fileList.concat(newFile)
|
||||||
|
|
@ -327,19 +404,23 @@ export default {
|
||||||
}else if(this.accept == 'video/*') {
|
}else if(this.accept == 'video/*') {
|
||||||
const newFile = fileUrl
|
const newFile = fileUrl
|
||||||
this.oneFile = fileUrl
|
this.oneFile = fileUrl
|
||||||
this.$emit('input', this.oneFile)
|
if (this.showPreview) {
|
||||||
this.$emit('change', this.oneFile)
|
this.videoList = this.videoList.concat([newFile]).slice(0, this.maxCount)
|
||||||
|
}
|
||||||
|
const emitValue = this.maxCount > 1 && this.showPreview ? this.videoList : this.oneFile
|
||||||
|
this.$emit('input', emitValue)
|
||||||
|
this.$emit('change', emitValue)
|
||||||
this.$emit('success', newFile)
|
this.$emit('success', newFile)
|
||||||
return newFile
|
return newFile
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('上传失败:', error)
|
console.error('上传失败:', error)
|
||||||
this.$emit('error', error)
|
this.$emit('error', error)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 获取OSS配置
|
// 获取OSS配置
|
||||||
async getOssConfig() {
|
async getOssConfig() {
|
||||||
try {
|
try {
|
||||||
|
|
@ -350,45 +431,202 @@ export default {
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getFileExt(file) {
|
||||||
|
const defaultExt = this.accept === 'video/*' ? 'mp4' : 'jpg'
|
||||||
|
const sources = this.accept === 'video/*'
|
||||||
|
? [file && file.tempFilePath, file && file.path, file && file.url, file && file.name]
|
||||||
|
: [file && file.path, file && file.tempFilePath, file && file.url, file && file.name]
|
||||||
|
for (const source of sources) {
|
||||||
|
const cleanPath = String(source || '').split('?')[0].split('#')[0]
|
||||||
|
const ext = cleanPath.includes('.') ? cleanPath.split('.').pop().toLowerCase() : ''
|
||||||
|
if (/^[a-z0-9]+$/.test(ext)) return ext
|
||||||
|
}
|
||||||
|
return defaultExt
|
||||||
|
},
|
||||||
|
|
||||||
|
getFilePath(file) {
|
||||||
|
if (!file) return ''
|
||||||
|
return file.tempFilePath || file.path || file.url || file.name || ''
|
||||||
|
},
|
||||||
|
|
||||||
|
getMimeType(file, name) {
|
||||||
|
const ext = this.getFileExt({ tempFilePath: name, path: name, name })
|
||||||
|
const mimeTypes = {
|
||||||
|
jpg: 'image/jpeg',
|
||||||
|
jpeg: 'image/jpeg',
|
||||||
|
png: 'image/png',
|
||||||
|
gif: 'image/gif',
|
||||||
|
webp: 'image/webp',
|
||||||
|
mp4: 'video/mp4',
|
||||||
|
m4v: 'video/x-m4v',
|
||||||
|
mov: 'video/quicktime',
|
||||||
|
qt: 'video/quicktime',
|
||||||
|
webm: 'video/webm',
|
||||||
|
mpeg: 'video/mpeg',
|
||||||
|
mpg: 'video/mpeg',
|
||||||
|
avi: 'video/x-msvideo',
|
||||||
|
'3gp': 'video/3gpp',
|
||||||
|
mkv: 'video/x-matroska',
|
||||||
|
}
|
||||||
|
return mimeTypes[ext] || (file && file.type) || ''
|
||||||
|
},
|
||||||
|
|
||||||
|
buildOssFormData(ossConfig, objectKey, mimeType) {
|
||||||
|
const formData = {
|
||||||
|
key: objectKey,
|
||||||
|
policy: ossConfig.policy,
|
||||||
|
OSSAccessKeyId: ossConfig.ossAccessKeyId,
|
||||||
|
success_action_status: '200',
|
||||||
|
signature: ossConfig.signature,
|
||||||
|
}
|
||||||
|
if (mimeType) {
|
||||||
|
formData['Content-Type'] = mimeType
|
||||||
|
formData['x-oss-meta-content-type'] = mimeType
|
||||||
|
}
|
||||||
|
return formData
|
||||||
|
},
|
||||||
|
|
||||||
|
async prepareUploadFile(file) {
|
||||||
|
if (this.accept !== 'video/*') return file
|
||||||
|
const filePath = this.getFilePath(file)
|
||||||
|
const fileExt = this.getFileExt(file)
|
||||||
|
if (!filePath || fileExt === 'mp4' || typeof uni.compressVideo !== 'function') {
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await uni.compressVideo({
|
||||||
|
src: filePath,
|
||||||
|
quality: 'medium'
|
||||||
|
})
|
||||||
|
const tempFilePath = res.tempFilePath || res.path
|
||||||
|
if (!tempFilePath) return file
|
||||||
|
return {
|
||||||
|
...file,
|
||||||
|
tempFilePath,
|
||||||
|
path: tempFilePath,
|
||||||
|
size: res.size || file.size,
|
||||||
|
name: this.replaceFileExt(file.name, 'mp4'),
|
||||||
|
type: 'video/mp4'
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('视频压缩失败,使用原文件上传:', error)
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
replaceFileExt(name, ext) {
|
||||||
|
if (!name) return ''
|
||||||
|
return String(name).replace(/\.[^.]+$/, `.${ext}`)
|
||||||
|
},
|
||||||
|
|
||||||
|
buildFileUrl(ossConfig, name) {
|
||||||
|
const host = String(ossConfig.host || '').replace(/\/+$/, '')
|
||||||
|
return `${host}/${this.buildObjectKey(ossConfig, name)}`
|
||||||
|
},
|
||||||
|
|
||||||
|
buildObjectKey(ossConfig, name) {
|
||||||
|
const dir = String(ossConfig.dir || '').replace(/^\/+/, '').replace(/\/+$/, '')
|
||||||
|
return dir ? `${dir}/${name}` : name
|
||||||
|
},
|
||||||
|
|
||||||
|
getVideoPoster(url) {
|
||||||
|
if (!url) return ''
|
||||||
|
if (String(url).indexOf('x-oss-process=') > -1) return url
|
||||||
|
const connector = String(url).indexOf('?') > -1 ? '&' : '?'
|
||||||
|
return `${url}${connector}x-oss-process=video/snapshot,t_2000,f_jpg,m_fast`
|
||||||
|
},
|
||||||
|
|
||||||
|
chooseVideoFiles(sourceType, count) {
|
||||||
|
if (count <= 1 && typeof uni.chooseVideo === 'function') {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
uni.chooseVideo({
|
||||||
|
sourceType: [sourceType],
|
||||||
|
compressed: true,
|
||||||
|
maxDuration: 60,
|
||||||
|
success: (res) => resolve([{
|
||||||
|
...res,
|
||||||
|
tempFilePath: res.tempFilePath || res.path,
|
||||||
|
path: res.tempFilePath || res.path,
|
||||||
|
}]),
|
||||||
|
fail: reject,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (uni.chooseMedia) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
uni.chooseMedia({
|
||||||
|
count,
|
||||||
|
mediaType: ['video'],
|
||||||
|
sourceType: [sourceType],
|
||||||
|
maxDuration: 60,
|
||||||
|
success: (res) => {
|
||||||
|
const files = (res.tempFiles || []).map((item) => ({
|
||||||
|
...item,
|
||||||
|
tempFilePath: item.tempFilePath || item.path,
|
||||||
|
path: item.tempFilePath || item.path,
|
||||||
|
}));
|
||||||
|
resolve(files);
|
||||||
|
},
|
||||||
|
fail: reject,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
uni.chooseVideo({
|
||||||
|
sourceType: [sourceType],
|
||||||
|
compressed: true,
|
||||||
|
maxDuration: 60,
|
||||||
|
success: (res) => resolve([res]),
|
||||||
|
fail: reject,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
// 上传到OSS
|
// 上传到OSS
|
||||||
uploadToOss(file, ossConfig, name) {
|
uploadToOss(file, ossConfig, name) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// #ifdef H5
|
const objectKey = this.buildObjectKey(ossConfig, name)
|
||||||
const formData = new FormData()
|
const fileUrl = this.buildFileUrl(ossConfig, name)
|
||||||
|
const mimeType = this.getMimeType(file, name)
|
||||||
|
let formData;
|
||||||
|
// #ifdef H5
|
||||||
|
formData = new FormData()
|
||||||
formData.append('name',name);
|
formData.append('name',name);
|
||||||
formData.append('policy', ossConfig.policy);
|
formData.append('policy', ossConfig.policy);
|
||||||
formData.append('OSSAccessKeyId', ossConfig.ossAccessKeyId);
|
formData.append('OSSAccessKeyId', ossConfig.ossAccessKeyId);
|
||||||
formData.append('success_action_status', '200');
|
formData.append('success_action_status', '200');
|
||||||
formData.append('signature', ossConfig.signature);
|
formData.append('signature', ossConfig.signature);
|
||||||
formData.append('key', ossConfig.dir + name);
|
formData.append('key', objectKey);
|
||||||
|
if (mimeType) {
|
||||||
|
formData.append('Content-Type', mimeType);
|
||||||
|
formData.append('x-oss-meta-content-type', mimeType);
|
||||||
|
}
|
||||||
// file必须为最后一个表单域,除file以外的其他表单域无顺序要求。
|
// file必须为最后一个表单域,除file以外的其他表单域无顺序要求。
|
||||||
formData.append('file', file);
|
formData.append('file', file.file || file.tempFile || file);
|
||||||
fetch(ossConfig.host, { method: 'POST', body: formData},).then((res) => {
|
fetch(ossConfig.host, { method: 'POST', body: formData},).then((res) => {
|
||||||
resolve(res)
|
if (res.ok) {
|
||||||
}).catch(()=>{
|
resolve(fileUrl)
|
||||||
reject(new Error(`上传失败: ${res.statusCode}`))
|
} else {
|
||||||
|
reject(new Error(`上传失败: ${res.status}`))
|
||||||
|
}
|
||||||
|
}).catch((error)=>{
|
||||||
|
reject(error)
|
||||||
})
|
})
|
||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
// #ifdef APP-PLUS || MP-WEIXIN
|
// #ifdef APP-PLUS || MP-WEIXIN
|
||||||
let formData = {};
|
formData = this.buildOssFormData(ossConfig, objectKey, mimeType);
|
||||||
formData.name = name;
|
formData.name = name;
|
||||||
formData.policy = ossConfig.policy;
|
|
||||||
formData.OSSAccessKeyId = ossConfig.ossAccessKeyId;
|
|
||||||
formData.success_action_status = '200';
|
|
||||||
formData.signature = ossConfig.signature;
|
|
||||||
formData.key = ossConfig.dir + name;
|
|
||||||
formData.file = file;
|
|
||||||
let path;
|
let path;
|
||||||
if(this.accept == 'image/*') {
|
if(this.accept == 'image/*') {
|
||||||
path = file.path
|
path = this.getFilePath(file)
|
||||||
}else if(this.accept == 'video/*') {
|
}else if(this.accept == 'video/*') {
|
||||||
// #ifdef H5
|
// #ifdef H5
|
||||||
path = file.name
|
path = this.getFilePath(file)
|
||||||
// #endif
|
// #endif
|
||||||
// #ifdef APP-PLUS || MP-WEIXIN
|
// #ifdef APP-PLUS || MP-WEIXIN
|
||||||
path = file.tempFilePath
|
path = this.getFilePath(file)
|
||||||
// #endif
|
// #endif
|
||||||
}
|
}
|
||||||
// 使用 uni.uploadFile 上传文件(支持多平台)
|
// 使用 uni.uploadFile 上传文件(支持多平台)
|
||||||
|
|
@ -396,30 +634,89 @@ export default {
|
||||||
url: ossConfig.host,
|
url: ossConfig.host,
|
||||||
filePath: path, // 手机端文件路径
|
filePath: path, // 手机端文件路径
|
||||||
name: 'file',
|
name: 'file',
|
||||||
formData: {
|
formData,
|
||||||
key: ossConfig.dir + name, // 文件名
|
success: (res) => {
|
||||||
policy: ossConfig.policy, // 后台获取超时时间
|
const statusCode = Number(res.statusCode)
|
||||||
OSSAccessKeyId: ossConfig.ossAccessKeyId, // 后台获取临时ID
|
if (statusCode >= 200 && statusCode < 300) {
|
||||||
success_action_status: 200, // 让服务端返回200,不然,默认会返回204
|
resolve(fileUrl)
|
||||||
signature: ossConfig.signature // 后台获取签名
|
} else {
|
||||||
|
reject(new Error(`上传失败: ${statusCode} ${res.data || ''}`))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
success(res) {
|
fail(error) {
|
||||||
resolve(`${ossConfig.host}/${ossConfig.dir}${name}`)
|
reject(error)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// #endif
|
// #endif
|
||||||
})
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// 预览图片
|
// 预览图片
|
||||||
handlePreview(index) {
|
handlePreview(index) {
|
||||||
|
if (this.isDragging || this.suppressPreview) return
|
||||||
uni.previewImage({
|
uni.previewImage({
|
||||||
current: index,
|
current: index,
|
||||||
urls: this.fileList.map(item => item.url)
|
urls: this.fileList
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
previewVideo(url) {
|
||||||
|
if (!url) return
|
||||||
|
this.previewVideoUrl = url
|
||||||
|
this.showVideoPlayer = true
|
||||||
|
},
|
||||||
|
|
||||||
|
closeVideoPlayer() {
|
||||||
|
this.showVideoPlayer = false
|
||||||
|
this.previewVideoUrl = ''
|
||||||
|
},
|
||||||
|
|
||||||
|
startDrag(index) {
|
||||||
|
if (!this.sortable || this.accept !== 'image/*' || this.fileList.length < 2) return
|
||||||
|
this.dragIndex = index
|
||||||
|
this.isDragging = true
|
||||||
|
},
|
||||||
|
|
||||||
|
handleDragMove(event) {
|
||||||
|
if (!this.isDragging || this.dragIndex < 0) return
|
||||||
|
if (event.preventDefault) event.preventDefault()
|
||||||
|
if (event.stopPropagation) event.stopPropagation()
|
||||||
|
const touch = event.touches && event.touches[0]
|
||||||
|
if (!touch) return
|
||||||
|
uni.createSelectorQuery()
|
||||||
|
.in(this)
|
||||||
|
.selectAll('.image-preview-item')
|
||||||
|
.boundingClientRect((rects) => {
|
||||||
|
if (!Array.isArray(rects) || !rects.length) return
|
||||||
|
const targetIndex = rects.findIndex((rect) => {
|
||||||
|
return touch.clientX >= rect.left &&
|
||||||
|
touch.clientX <= rect.right &&
|
||||||
|
touch.clientY >= rect.top &&
|
||||||
|
touch.clientY <= rect.bottom
|
||||||
|
})
|
||||||
|
if (targetIndex < 0 || targetIndex === this.dragIndex || targetIndex >= this.fileList.length) return
|
||||||
|
const nextList = this.fileList.slice()
|
||||||
|
const moved = nextList.splice(this.dragIndex, 1)[0]
|
||||||
|
nextList.splice(targetIndex, 0, moved)
|
||||||
|
this.fileList = nextList
|
||||||
|
this.dragIndex = targetIndex
|
||||||
|
this.$emit('input', this.fileList)
|
||||||
|
this.$emit('change', this.fileList)
|
||||||
|
})
|
||||||
|
.exec()
|
||||||
|
},
|
||||||
|
|
||||||
|
endDrag() {
|
||||||
|
if (!this.isDragging) return
|
||||||
|
this.dragIndex = -1
|
||||||
|
this.suppressPreview = true
|
||||||
|
setTimeout(() => {
|
||||||
|
this.isDragging = false
|
||||||
|
this.suppressPreview = false
|
||||||
|
}, 200)
|
||||||
|
},
|
||||||
|
|
||||||
// 删除文件
|
// 删除文件
|
||||||
handleDelete(index) {
|
handleDelete(index) {
|
||||||
// uni.showModal({
|
// uni.showModal({
|
||||||
|
|
@ -434,13 +731,16 @@ export default {
|
||||||
this.$emit('change', this.fileList)
|
this.$emit('change', this.fileList)
|
||||||
this.$emit('delete', deletedFile, index)
|
this.$emit('delete', deletedFile, index)
|
||||||
}else if(this.accept == 'video/*') {
|
}else if(this.accept == 'video/*') {
|
||||||
const deletedFile = this.oneFile
|
const deleteIndex = index === undefined ? 0 : index
|
||||||
this.oneFile = ''
|
const deletedFile = this.videoList[deleteIndex]
|
||||||
this.$emit('input', this.oneFile)
|
this.videoList.splice(deleteIndex, 1)
|
||||||
this.$emit('change', this.oneFile)
|
this.oneFile = this.videoList[0] || ''
|
||||||
this.$emit('delete', deletedFile)
|
const emitValue = this.maxCount > 1 ? this.videoList : this.oneFile
|
||||||
|
this.$emit('input', emitValue)
|
||||||
|
this.$emit('change', emitValue)
|
||||||
|
this.$emit('delete', deletedFile, deleteIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// })
|
// })
|
||||||
|
|
@ -461,8 +761,11 @@ export default {
|
||||||
|
|
||||||
.preview-container {
|
.preview-container {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 200rpx);
|
||||||
justify-items: center;
|
column-gap: 18rpx;
|
||||||
|
row-gap: 20rpx;
|
||||||
|
justify-content: flex-start;
|
||||||
|
justify-items: stretch;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
@ -501,7 +804,11 @@ export default {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 200rpx;
|
width: 200rpx;
|
||||||
height: 200rpx;
|
height: 200rpx;
|
||||||
margin-top: 20rpx;
|
}
|
||||||
|
|
||||||
|
.image-preview-item.dragging {
|
||||||
|
opacity: 0.65;
|
||||||
|
transform: scale(0.96);
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-image {
|
.preview-image {
|
||||||
|
|
@ -510,19 +817,90 @@ export default {
|
||||||
border-radius: 8rpx;
|
border-radius: 8rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.video-play-mask {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 200rpx;
|
||||||
|
height: 200rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: rgba(0, 0, 0, 0.18);
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-play-circle {
|
||||||
|
width: 60rpx;
|
||||||
|
height: 60rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.55);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-play-icon {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-top: 14rpx solid transparent;
|
||||||
|
border-bottom: 14rpx solid transparent;
|
||||||
|
border-left: 22rpx solid #fff;
|
||||||
|
margin-left: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
.delete-icon {
|
.delete-icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -10rpx;
|
top: -10rpx;
|
||||||
right: -10rpx;
|
right: -10rpx;
|
||||||
width: 40rpx;
|
width: 42rpx;
|
||||||
height: 40rpx;
|
height: 42rpx;
|
||||||
background-color: #ff4d4f;
|
z-index: 5;
|
||||||
color: white;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 32rpx;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
}
|
||||||
</style>
|
|
||||||
|
.video-player-mask {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
background-color: rgba(0, 0, 0, 0.75);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-player-box {
|
||||||
|
position: relative;
|
||||||
|
width: 690rpx;
|
||||||
|
background-color: #000;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-player {
|
||||||
|
width: 690rpx;
|
||||||
|
height: 388rpx;
|
||||||
|
background-color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-player-close {
|
||||||
|
position: absolute;
|
||||||
|
top: 12rpx;
|
||||||
|
right: 12rpx;
|
||||||
|
width: 52rpx;
|
||||||
|
height: 52rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.55);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-player-close-text {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 42rpx;
|
||||||
|
line-height: 52rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
<template>
|
||||||
|
<view class="form-checkbox">
|
||||||
|
<view v-for="(item, index) in options" :key="index" class="checkbox-item" @click="onToggle(item)">
|
||||||
|
<view class="checkbox-dot" :class="{ active: isChecked(item) }"></view>
|
||||||
|
<text class="checkbox-label">{{ getOptionLabel(item) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'FormItemCheckbox',
|
||||||
|
props: {
|
||||||
|
value: { type: Array, default: function () { return []; } },
|
||||||
|
options: { type: Array, default: function () { return []; } },
|
||||||
|
maxCount: { type: Number, default: 0 }
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getOptionValue: function (item) {
|
||||||
|
if (item && typeof item === 'object') {
|
||||||
|
if (item.value !== undefined) return item.value;
|
||||||
|
if (item.id !== undefined) return item.id;
|
||||||
|
if (item.key !== undefined) return item.key;
|
||||||
|
if (item.code !== undefined) return item.code;
|
||||||
|
return item.label;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
},
|
||||||
|
getOptionLabel: function (item) {
|
||||||
|
if (item && typeof item === 'object') {
|
||||||
|
var label = item.label || item.title || item.name || item.text;
|
||||||
|
if (label !== undefined && label !== null) return label;
|
||||||
|
return String(this.getOptionValue(item));
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
},
|
||||||
|
findValueIndex: function (list, value) {
|
||||||
|
for (var i = 0; i < list.length; i++) {
|
||||||
|
if (list[i] == value) return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
},
|
||||||
|
isChecked: function (item) {
|
||||||
|
return this.findValueIndex(this.value || [], this.getOptionValue(item)) !== -1;
|
||||||
|
},
|
||||||
|
onToggle: function (item) {
|
||||||
|
var val = this.getOptionValue(item);
|
||||||
|
var newValue = (this.value || []).slice();
|
||||||
|
var index = this.findValueIndex(newValue, val);
|
||||||
|
if (index === -1) {
|
||||||
|
if (this.maxCount > 0 && newValue.length >= this.maxCount) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '最多只能选择' + this.maxCount + '项',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
newValue.push(val);
|
||||||
|
} else {
|
||||||
|
newValue.splice(index, 1);
|
||||||
|
}
|
||||||
|
this.$emit('input', newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.form-checkbox {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 40rpx;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-dot {
|
||||||
|
width: 36rpx;
|
||||||
|
height: 36rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
border: 2rpx solid #cccccc;
|
||||||
|
margin-right: 12rpx;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-dot.active {
|
||||||
|
border-color: #e8101e;
|
||||||
|
background-color: #e8101e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-dot.active::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -60%) rotate(-45deg);
|
||||||
|
width: 20rpx;
|
||||||
|
height: 12rpx;
|
||||||
|
border-left: 4rpx solid #ffffff;
|
||||||
|
border-bottom: 4rpx solid #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-label {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
<template>
|
||||||
|
<ali-oss-uploader v-model="currentValue" :max-count="maxCount" :max-size="maxSize" accept="image/*" :userId="computedUserId"
|
||||||
|
:type="computedOssType" width="100%" :sortable="true" @input="onInput" @change="onChange" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import AliOssUploader from '../ali-oss-uploader/ali-oss-uploader.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'FormItemImage',
|
||||||
|
components: { AliOssUploader },
|
||||||
|
props: {
|
||||||
|
value: { type: Array, default: function () { return []; } },
|
||||||
|
maxCount: { type: Number, default: 9 },
|
||||||
|
maxSize: { type: Number, default: 5 },
|
||||||
|
userId: { type: [Number, String], default: null },
|
||||||
|
ossType: { type: [Number, String], default: null }
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
currentValue: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
computedUserId: function () {
|
||||||
|
if (this.userId) return this.userId;
|
||||||
|
return this.$store && this.$store.state && this.$store.state.sjInfo
|
||||||
|
? this.$store.state.sjInfo.id : null;
|
||||||
|
},
|
||||||
|
computedOssType: function () {
|
||||||
|
if (this.ossType) return this.ossType;
|
||||||
|
var artisanType = getApp().globalData ? getApp().globalData.artisanType : 0;
|
||||||
|
return (artisanType || 0) + 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value: {
|
||||||
|
handler: function (val) {
|
||||||
|
this.currentValue = Array.isArray(val) ? val.slice() : [];
|
||||||
|
},
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onInput: function (list) {
|
||||||
|
this.$emit('input', list);
|
||||||
|
},
|
||||||
|
onChange: function (list) {
|
||||||
|
this.$emit('change', list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
<template>
|
||||||
|
<view class="form-number-wrap">
|
||||||
|
<input type="digit" :value="value" :placeholder="placeholder" placeholder-class="placeholder" class="form-input" @input="onInput" />
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'FormItemNumber',
|
||||||
|
props: {
|
||||||
|
value: { type: [String, Number], default: '' },
|
||||||
|
placeholder: { type: String, default: '请输入' }
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onInput: function(e) {
|
||||||
|
this.$emit('input', e.target.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.form-number-wrap {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.form-input {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333333;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.placeholder {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #999999;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
<template>
|
||||||
|
<view class="form-radio">
|
||||||
|
<view v-for="(item, index) in options" :key="index" class="radio-item" @click="onSelect(item)">
|
||||||
|
<view class="radio-dot" :class="{ active: isActive(item) }"></view>
|
||||||
|
<text class="radio-label">{{ getOptionLabel(item) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'FormItemRadio',
|
||||||
|
props: {
|
||||||
|
value: { type: [String, Number], default: '' },
|
||||||
|
options: { type: Array, default: function () { return []; } }
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getOptionValue: function (item) {
|
||||||
|
if (item && typeof item === 'object') {
|
||||||
|
if (item.value !== undefined) return item.value;
|
||||||
|
if (item.id !== undefined) return item.id;
|
||||||
|
if (item.key !== undefined) return item.key;
|
||||||
|
if (item.code !== undefined) return item.code;
|
||||||
|
return item.label;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
},
|
||||||
|
getOptionLabel: function (item) {
|
||||||
|
if (item && typeof item === 'object') {
|
||||||
|
var label = item.label || item.title || item.name || item.text;
|
||||||
|
if (label !== undefined && label !== null) return label;
|
||||||
|
return String(this.getOptionValue(item));
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
},
|
||||||
|
isActive: function (item) {
|
||||||
|
return this.value == this.getOptionValue(item);
|
||||||
|
},
|
||||||
|
onSelect: function (item) {
|
||||||
|
this.$emit('input', this.getOptionValue(item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.form-radio {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 40rpx;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-dot {
|
||||||
|
width: 36rpx;
|
||||||
|
height: 36rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2rpx solid #cccccc;
|
||||||
|
margin-right: 12rpx;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-dot.active {
|
||||||
|
border-color: #e8101e;
|
||||||
|
background-color: #e8101e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-dot.active::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 16rpx;
|
||||||
|
height: 16rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-label {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
<template>
|
||||||
|
<view class="form-richtext">
|
||||||
|
<view class="richtext-trigger" @click="goEdit">
|
||||||
|
<text class="richtext-value">{{ value ? '已设置' : '去设置' }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'FormItemRichtext',
|
||||||
|
props: {
|
||||||
|
value: { type: String, default: '' }
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
goEdit: function () {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages/shop/add-img-text?list=' + this.value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.form-richtext {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.richtext-trigger {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.richtext-value {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333333;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
<template>
|
||||||
|
<picker :range="displayOptions" @change="onChange">
|
||||||
|
<view class="form-select">
|
||||||
|
<text :class="['select-text', hasValue ? '' : 'placeholder']">
|
||||||
|
{{ hasValue ? selectedLabel : placeholder }}
|
||||||
|
</text>
|
||||||
|
<image src="https://mrrplus.oss-cn-beijing.aliyuncs.com/wxstaic/images/arrow_right.png" class="arrow-right"
|
||||||
|
mode="aspectFit" />
|
||||||
|
</view>
|
||||||
|
</picker>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'FormItemSelect',
|
||||||
|
props: {
|
||||||
|
value: { type: [String, Number], default: '' },
|
||||||
|
options: { type: Array, default: function () { return []; } },
|
||||||
|
placeholder: { type: String, default: '请选择' }
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
displayOptions: function () {
|
||||||
|
var self = this;
|
||||||
|
return this.options.map(function (item) {
|
||||||
|
return self.getOptionLabel(item);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
hasValue: function () {
|
||||||
|
return this.value !== undefined && this.value !== null && this.value !== '';
|
||||||
|
},
|
||||||
|
selectedLabel: function () {
|
||||||
|
var self = this;
|
||||||
|
var selected = this.options.find(function (item) {
|
||||||
|
return self.getOptionValue(item) == self.value;
|
||||||
|
});
|
||||||
|
return selected !== undefined ? this.getOptionLabel(selected) : this.value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getOptionValue: function (item) {
|
||||||
|
if (item && typeof item === 'object') {
|
||||||
|
if (item.value !== undefined) return item.value;
|
||||||
|
if (item.id !== undefined) return item.id;
|
||||||
|
if (item.key !== undefined) return item.key;
|
||||||
|
if (item.code !== undefined) return item.code;
|
||||||
|
return item.label;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
},
|
||||||
|
getOptionLabel: function (item) {
|
||||||
|
if (item && typeof item === 'object') {
|
||||||
|
var label = item.label || item.title || item.name || item.text;
|
||||||
|
if (label !== undefined && label !== null) return label;
|
||||||
|
return String(this.getOptionValue(item));
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
},
|
||||||
|
onChange: function (e) {
|
||||||
|
var index = e.detail.value;
|
||||||
|
var selected = this.options[index];
|
||||||
|
if (selected !== undefined) {
|
||||||
|
this.$emit('input', this.getOptionValue(selected));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.form-select {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333333;
|
||||||
|
margin-right: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #999999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-right {
|
||||||
|
width: 32rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,143 @@
|
||||||
|
<template>
|
||||||
|
<view class="form-step">
|
||||||
|
<view class="form-item">
|
||||||
|
<text :class="['form-label', required ? 'required' : '']">{{ label }}</text>
|
||||||
|
<text class="add-btn" @click="addStep">添加</text>
|
||||||
|
</view>
|
||||||
|
<view class="step" v-for="(item, i) in steps" :key="i">
|
||||||
|
<text class="step-label">第{{ i + 1 }}步</text>
|
||||||
|
<input type="text" class="step-inp" :value="item.text" placeholder="请输入步骤内容"
|
||||||
|
placeholder-class="placeholder" @input="onStepInput(i, $event)" />
|
||||||
|
<image src="https://mrrplus.oss-cn-beijing.aliyuncs.com/wxstaic/images/delete_icon.png"
|
||||||
|
class="delete-icon" mode="aspectFit" @click="deleteStep(i)"></image>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'FormItemServiceStep',
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: [Array, String],
|
||||||
|
default: function () { return []; }
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
default: '服务流程'
|
||||||
|
},
|
||||||
|
required: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
steps: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value: {
|
||||||
|
handler: function (val) {
|
||||||
|
if (typeof val === 'string') {
|
||||||
|
try {
|
||||||
|
this.steps = JSON.parse(val) || [];
|
||||||
|
} catch (e) {
|
||||||
|
this.steps = [];
|
||||||
|
}
|
||||||
|
} else if (Array.isArray(val)) {
|
||||||
|
this.steps = val.slice();
|
||||||
|
} else {
|
||||||
|
this.steps = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
addStep: function () {
|
||||||
|
var i = this.steps.length + 1;
|
||||||
|
this.steps.push({
|
||||||
|
title: '第' + i + '步',
|
||||||
|
text: '',
|
||||||
|
photo: ''
|
||||||
|
});
|
||||||
|
this._emit();
|
||||||
|
},
|
||||||
|
deleteStep: function (i) {
|
||||||
|
this.steps.splice(i, 1);
|
||||||
|
this._emit();
|
||||||
|
},
|
||||||
|
onStepInput: function (i, e) {
|
||||||
|
var value = e && e.target ? e.target.value : e;
|
||||||
|
this.steps[i].text = value;
|
||||||
|
this._emit();
|
||||||
|
},
|
||||||
|
_emit: function () {
|
||||||
|
this.$emit('input', this.steps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.form-step {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.required::before {
|
||||||
|
content: '*';
|
||||||
|
color: #ff0000;
|
||||||
|
margin-right: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-btn {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #ff4d6a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20rpx 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-label {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333333;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-inp {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333333;
|
||||||
|
margin-left: 20rpx;
|
||||||
|
padding: 0 24rpx;
|
||||||
|
background-color: rgba(230, 230, 230, 0.5);
|
||||||
|
height: 72rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-icon {
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
margin-left: 16rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
<template>
|
||||||
|
<view class="form-text-wrap">
|
||||||
|
<input type="text" :value="value" :placeholder="placeholder" placeholder-class="placeholder" class="form-input" @input="onInput" />
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'FormItemText',
|
||||||
|
props: {
|
||||||
|
value: { type: [String, Number], default: '' },
|
||||||
|
placeholder: { type: String, default: '请输入' }
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onInput: function(e) {
|
||||||
|
this.$emit('input', e.target.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.form-text-wrap {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.form-input {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333333;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.placeholder {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #999999;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
<template>
|
||||||
|
<view class="detail-textarea-wrap">
|
||||||
|
<textarea class="detail-textarea" :value="value" :placeholder="placeholder" placeholder-class="placeholder" maxlength="200" @input="onInput" />
|
||||||
|
<text class="detail-count">{{ (value || '').length }}/200</text>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'FormItemTextarea',
|
||||||
|
props: {
|
||||||
|
value: { type: String, default: '' },
|
||||||
|
placeholder: { type: String, default: '请输入' }
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onInput: function(e) {
|
||||||
|
this.$emit('input', e.target.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.detail-textarea-wrap {
|
||||||
|
position: relative;
|
||||||
|
background: #f7f7f7;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 24rpx 24rpx 52rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.detail-textarea {
|
||||||
|
width: 100%;
|
||||||
|
height: 108rpx;
|
||||||
|
min-height: 108rpx;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 40rpx;
|
||||||
|
color: #333333;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.detail-count {
|
||||||
|
position: absolute;
|
||||||
|
right: 24rpx;
|
||||||
|
bottom: 16rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #999999;
|
||||||
|
line-height: 32rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,256 @@
|
||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<view v-if="maxCount > 1" class="video-list">
|
||||||
|
<view v-for="(item, index) in currentList" :key="index" class="video-wrapper">
|
||||||
|
<image :src="getVideoPoster(item)" class="preview-video" mode="aspectFill" @click="previewVideo(item)" />
|
||||||
|
<view class="play-mask" @click="previewVideo(item)">
|
||||||
|
<view class="play-circle">
|
||||||
|
<view class="play-icon"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<image
|
||||||
|
class="delete-btn"
|
||||||
|
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/6ab3fc63-3bad-403d-bbfc-9609a93c048a.png"
|
||||||
|
mode="aspectFit"
|
||||||
|
@click="deleteVideo(index)"
|
||||||
|
></image>
|
||||||
|
</view>
|
||||||
|
<ali-oss-uploader v-if="currentList.length < maxCount"
|
||||||
|
:max-count="maxCount - currentList.length"
|
||||||
|
:max-size="maxSize" accept="video/*" :userId="uploadUserId" :type="ossType" :kind="2" width="200rpx"
|
||||||
|
:show-preview="false"
|
||||||
|
@input="onMultiInput" @change="onChange" />
|
||||||
|
</view>
|
||||||
|
<view v-else>
|
||||||
|
<ali-oss-uploader v-if="!currentValue" :max-count="1" :max-size="maxSize" accept="video/*"
|
||||||
|
:userId="uploadUserId" :type="ossType" :kind="2" width="100%" :file-string="currentValue" @input="onInput"
|
||||||
|
@change="onChange" @success="onInput" @delete="deleteSingleVideo" />
|
||||||
|
<view v-else class="video-wrapper">
|
||||||
|
<image :src="getVideoPoster(currentValue)" class="preview-video" mode="aspectFill" @click="previewVideo(currentValue)" />
|
||||||
|
<view class="play-mask" @click="previewVideo(currentValue)">
|
||||||
|
<view class="play-circle">
|
||||||
|
<view class="play-icon"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<image
|
||||||
|
class="delete-btn"
|
||||||
|
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/6ab3fc63-3bad-403d-bbfc-9609a93c048a.png"
|
||||||
|
mode="aspectFit"
|
||||||
|
@click="deleteSingleVideo"
|
||||||
|
></image>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-if="showPlayer" class="player-mask" @click="closePlayer">
|
||||||
|
<view class="player-box" @click.stop>
|
||||||
|
<video :src="playingVideo" class="player-video" controls autoplay show-fullscreen-btn />
|
||||||
|
<view class="player-close" @click="closePlayer">
|
||||||
|
<text class="player-close-text">×</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import AliOssUploader from '../ali-oss-uploader/ali-oss-uploader.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'FormItemVideo',
|
||||||
|
components: { AliOssUploader },
|
||||||
|
props: {
|
||||||
|
value: { type: [String, Array], default: '' },
|
||||||
|
maxSize: { type: Number, default: 100 },
|
||||||
|
maxCount: { type: Number, default: 1 },
|
||||||
|
userId: { type: [Number, String], default: null }
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
currentValue: '',
|
||||||
|
currentList: [],
|
||||||
|
showPlayer: false,
|
||||||
|
playingVideo: ''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
uploadUserId: function () {
|
||||||
|
if (this.userId) return this.userId;
|
||||||
|
return this.$store && this.$store.state && this.$store.state.sjInfo
|
||||||
|
? this.$store.state.sjInfo.id : null;
|
||||||
|
},
|
||||||
|
ossType: function () {
|
||||||
|
var artisanType = getApp().globalData ? getApp().globalData.artisanType : 0;
|
||||||
|
return (artisanType || 0) + 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value: {
|
||||||
|
handler: function (val) {
|
||||||
|
if (this.maxCount > 1) {
|
||||||
|
if (Array.isArray(val)) {
|
||||||
|
this.currentList = val.filter(function (v) { return !!v; });
|
||||||
|
} else if (val) {
|
||||||
|
this.currentList = [val];
|
||||||
|
} else {
|
||||||
|
this.currentList = [];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.currentValue = Array.isArray(val) ? (val[0] || '') : (val || '');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onInput: function (val) {
|
||||||
|
this.currentValue = val;
|
||||||
|
this.$emit('input', val);
|
||||||
|
},
|
||||||
|
onMultiInput: function (val) {
|
||||||
|
var arr = Array.isArray(val) ? val : [val];
|
||||||
|
var existing = this.currentList.filter(function (item) {
|
||||||
|
return arr.indexOf(item) === -1;
|
||||||
|
});
|
||||||
|
var list = existing.concat(arr).filter(function (item, index, self) {
|
||||||
|
return item && self.indexOf(item) === index;
|
||||||
|
}).slice(0, this.maxCount);
|
||||||
|
this.currentList = list;
|
||||||
|
this.$emit('input', list);
|
||||||
|
},
|
||||||
|
onChange: function (val) {
|
||||||
|
this.$emit('change', val);
|
||||||
|
},
|
||||||
|
getVideoPoster: function (url) {
|
||||||
|
if (!url) return '';
|
||||||
|
if (String(url).indexOf('x-oss-process=') > -1) return url;
|
||||||
|
var connector = String(url).indexOf('?') > -1 ? '&' : '?';
|
||||||
|
return url + connector + 'x-oss-process=video/snapshot,t_2000,f_jpg,m_fast';
|
||||||
|
},
|
||||||
|
previewVideo: function (url) {
|
||||||
|
if (!url) return;
|
||||||
|
this.playingVideo = url;
|
||||||
|
this.showPlayer = true;
|
||||||
|
},
|
||||||
|
closePlayer: function () {
|
||||||
|
this.showPlayer = false;
|
||||||
|
this.playingVideo = '';
|
||||||
|
},
|
||||||
|
deleteSingleVideo: function () {
|
||||||
|
this.currentValue = '';
|
||||||
|
this.$emit('input', '');
|
||||||
|
},
|
||||||
|
deleteVideo: function (index) {
|
||||||
|
this.currentList.splice(index, 1);
|
||||||
|
this.$emit('input', this.currentList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.video-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-wrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 200rpx;
|
||||||
|
height: 200rpx;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-video {
|
||||||
|
width: 200rpx;
|
||||||
|
height: 200rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.play-mask {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 200rpx;
|
||||||
|
height: 200rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: rgba(0, 0, 0, 0.15);
|
||||||
|
border-radius: 8rpx;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.play-circle {
|
||||||
|
width: 56rpx;
|
||||||
|
height: 56rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.55);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.play-icon {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-top: 14rpx solid transparent;
|
||||||
|
border-bottom: 14rpx solid transparent;
|
||||||
|
border-left: 22rpx solid #ffffff;
|
||||||
|
margin-left: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: -10rpx;
|
||||||
|
right: -10rpx;
|
||||||
|
width: 42rpx;
|
||||||
|
height: 42rpx;
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-mask {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.82);
|
||||||
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 32rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-box {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-video {
|
||||||
|
width: 100%;
|
||||||
|
height: 420rpx;
|
||||||
|
background-color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-close {
|
||||||
|
position: absolute;
|
||||||
|
right: -12rpx;
|
||||||
|
top: -58rpx;
|
||||||
|
width: 48rpx;
|
||||||
|
height: 48rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: rgba(255, 255, 255, 0.22);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-close-text {
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 42rpx;
|
||||||
|
line-height: 48rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,471 @@
|
||||||
|
<template>
|
||||||
|
<view class="dynamic-form">
|
||||||
|
<view v-for="(group, gi) in fields" :key="gi" class="form-group">
|
||||||
|
<!-- single -->
|
||||||
|
<block v-if="group.type === 'single'">
|
||||||
|
<view v-if="group.field.type === 'text'" class="form-item">
|
||||||
|
<text :class="['form-label', group.field.is_required === true ? 'required' : '']">{{ group.field.label
|
||||||
|
}}</text>
|
||||||
|
<input type="text" :value="formData[group.field.key]" :placeholder="group.field.placeholder"
|
||||||
|
placeholder-class="placeholder" class="form-input" @input="onInput(group.field.key, $event)" />
|
||||||
|
</view>
|
||||||
|
<view v-else-if="group.field.type === 'number'" class="form-item"
|
||||||
|
:style="group.field.tip ? 'flex-wrap: wrap;' : ''">
|
||||||
|
<text :class="['form-label', group.field.is_required === true ? 'required' : '']">{{ group.field.label
|
||||||
|
}}</text>
|
||||||
|
<input type="digit" :value="formData[group.field.key]" :placeholder="group.field.placeholder"
|
||||||
|
placeholder-class="placeholder" class="form-input" @input="onInput(group.field.key, $event)" />
|
||||||
|
<text v-if="group.field.tips && !group.field.tip" class="form-tips">{{ group.field.tips }}</text>
|
||||||
|
<view v-if="group.field.tip" class="price-tip">
|
||||||
|
<text class="price-tip-card">{{ group.field.tip }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-else-if="group.field.type === 'textarea'" class="detail-row">
|
||||||
|
<text :class="['form-label', group.field.is_required === true ? 'required' : '']">{{ group.field.label
|
||||||
|
}}</text>
|
||||||
|
<view class="detail-textarea-wrap">
|
||||||
|
<textarea class="detail-textarea" :value="formData[group.field.key]" :placeholder="group.field.placeholder"
|
||||||
|
placeholder-class="placeholder" maxlength="200" @input="onInput(group.field.key, $event)" />
|
||||||
|
<text class="detail-count">{{ (formData[group.field.key] || "").length }}/200</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-else-if="group.field.type === 'select'" class="form-item">
|
||||||
|
<text :class="['form-label', group.field.is_required === true ? 'required' : '']">{{ group.field.label
|
||||||
|
}}</text>
|
||||||
|
<form-item-select :value="formData[group.field.key]" :options="group.field.options || []"
|
||||||
|
:placeholder="group.field.placeholder" @input="onInput(group.field.key, $event)" />
|
||||||
|
</view>
|
||||||
|
<view v-else-if="group.field.type === 'single_select'" class="form-item" style="flex-wrap: wrap;">
|
||||||
|
<text :class="['form-label', group.field.is_required === true ? 'required' : '']">{{ group.field.label
|
||||||
|
}}</text>
|
||||||
|
<view class="select-options">
|
||||||
|
<form-item-radio :value="formData[group.field.key]" :options="group.field.options || []"
|
||||||
|
@input="onInput(group.field.key, $event)" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-else-if="group.field.type === 'multi_select'" class="form-item" style="flex-wrap: wrap;">
|
||||||
|
<text :class="['form-label', group.field.is_required === true ? 'required' : '']">{{ group.field.label
|
||||||
|
}}</text>
|
||||||
|
<view class="select-options">
|
||||||
|
<form-item-checkbox :value="formData[group.field.key]" :options="group.field.options || []"
|
||||||
|
:maxCount="group.field.maxCount || 0" @input="onInput(group.field.key, $event)" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-else-if="group.field.type === 'image_multi'" class="form-item-up upload-item">
|
||||||
|
<view>
|
||||||
|
<text :class="['form-label', group.field.is_required === true ? 'required' : '']">{{ group.field.label
|
||||||
|
}}</text>
|
||||||
|
<text class="upload-tip">(仅可上传{{ group.field.maxCount || 9 }}个图片)</text>
|
||||||
|
</view>
|
||||||
|
<view class="upload-content">
|
||||||
|
<form-item-image :value="formData[group.field.key]" :maxCount="group.field.maxCount || 9"
|
||||||
|
:userId="uploadUserId" @input="onInput(group.field.key, $event)" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-else-if="group.field.type === 'video'" class="form-item-up upload-item">
|
||||||
|
<view>
|
||||||
|
<text :class="['form-label', group.field.is_required === true ? 'required' : '']">{{ group.field.label
|
||||||
|
}}</text>
|
||||||
|
<text class="upload-tip">(仅可上传{{ group.field.maxCount || 1 }}个视频)</text>
|
||||||
|
</view>
|
||||||
|
<view class="upload-content">
|
||||||
|
<form-item-video :value="formData[group.field.key]" :maxCount="group.field.maxCount || 1"
|
||||||
|
:maxSize="group.field.maxSize || group.field.maxFileSize || 100"
|
||||||
|
:userId="uploadUserId"
|
||||||
|
@input="onInput(group.field.key, $event)" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-else-if="group.field.type === 'richtext_editor'" class="form-item">
|
||||||
|
<text :class="['form-label', group.field.is_required === true ? 'required' : '']">{{ group.field.label
|
||||||
|
}}</text>
|
||||||
|
<form-item-richtext :value="formData[group.field.key]" @input="onInput(group.field.key, $event)" />
|
||||||
|
</view>
|
||||||
|
<view v-else-if="group.field.type === 'service_step'" class="form-item-up upload-item">
|
||||||
|
<form-item-service-step :value="formData[group.field.key]" :label="group.field.label"
|
||||||
|
:required="group.field.is_required === true" @input="onInput(group.field.key, $event)" />
|
||||||
|
</view>
|
||||||
|
</block>
|
||||||
|
<!-- section -->
|
||||||
|
<view v-if="group.type === 'section'" class="section-card">
|
||||||
|
<view v-if="group.sectionTitle" class="section-title-bar">
|
||||||
|
<text class="section-title">{{ group.sectionTitle }}</text>
|
||||||
|
</view>
|
||||||
|
<view v-for="(field, fi) in group.fields" :key="field.key || fi" class="section-field">
|
||||||
|
<view v-if="field.type === 'text'" class="form-item no-bg">
|
||||||
|
<text :class="['form-label', field.is_required === true ? 'required' : '']">{{ field.label }}</text>
|
||||||
|
<input type="text" :value="formData[field.key]" :placeholder="field.placeholder"
|
||||||
|
placeholder-class="placeholder" class="form-input" @input="onInput(field.key, $event)" />
|
||||||
|
</view>
|
||||||
|
<view v-else-if="field.type === 'textarea'" class="detail-row">
|
||||||
|
<text :class="['form-label', field.is_required === true ? 'required' : '']">{{ field.label }}</text>
|
||||||
|
<view class="detail-textarea-wrap">
|
||||||
|
<textarea class="detail-textarea" :value="formData[field.key]" :placeholder="field.placeholder"
|
||||||
|
placeholder-class="placeholder" maxlength="200" @input="onInput(field.key, $event)" />
|
||||||
|
<text class="detail-count">{{ (formData[field.key] || "").length }}/200</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-else-if="field.type === 'number'" class="form-item no-bg"
|
||||||
|
:style="field.tip ? 'flex-wrap: wrap;' : ''">
|
||||||
|
<text :class="['form-label', field.is_required === true ? 'required' : '']">{{ field.label }}</text>
|
||||||
|
<input type="digit" :value="formData[field.key]" :placeholder="field.placeholder"
|
||||||
|
placeholder-class="placeholder" class="form-input" @input="onInput(field.key, $event)" />
|
||||||
|
<text v-if="field.tips && !field.tip" class="form-tips">{{ field.tips }}</text>
|
||||||
|
<view v-if="field.tip" class="price-tip">
|
||||||
|
<text class="price-tip-card">{{ field.tip }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-else-if="field.type === 'select'" class="form-item no-bg">
|
||||||
|
<text :class="['form-label', field.is_required === true ? 'required' : '']">{{ field.label }}</text>
|
||||||
|
<form-item-select :value="formData[field.key]" :options="field.options || []"
|
||||||
|
:placeholder="field.placeholder" @input="onInput(field.key, $event)" />
|
||||||
|
</view>
|
||||||
|
<view v-else-if="field.type === 'single_select'" class="form-item no-bg" style="flex-wrap: wrap;">
|
||||||
|
<text :class="['form-label', field.is_required === true ? 'required' : '']">{{ field.label }}</text>
|
||||||
|
<view class="select-options">
|
||||||
|
<form-item-radio :value="formData[field.key]" :options="field.options || []"
|
||||||
|
@input="onInput(field.key, $event)" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-else-if="field.type === 'multi_select'" class="form-item no-bg" style="flex-wrap: wrap;">
|
||||||
|
<text :class="['form-label', field.is_required === true ? 'required' : '']">{{ field.label }}</text>
|
||||||
|
<view class="select-options">
|
||||||
|
<form-item-checkbox :value="formData[field.key]" :options="field.options || []"
|
||||||
|
:maxCount="field.maxCount || 0" @input="onInput(field.key, $event)" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-else-if="field.type === 'image_multi'" class="form-item-up upload-item no-bg">
|
||||||
|
<view>
|
||||||
|
<text :class="['form-label', field.is_required === true ? 'required' : '']">{{ field.label }}</text>
|
||||||
|
<text class="upload-tip">(仅可上传{{ field.maxCount || 9 }}个图片)</text>
|
||||||
|
</view>
|
||||||
|
<view class="upload-content">
|
||||||
|
<form-item-image :value="formData[field.key]" :maxCount="field.maxCount || 9"
|
||||||
|
:userId="uploadUserId" @input="onInput(field.key, $event)" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-else-if="field.type === 'video'" class="form-item-up upload-item no-bg">
|
||||||
|
<view>
|
||||||
|
<text :class="['form-label', field.is_required === true ? 'required' : '']">{{ field.label }}</text>
|
||||||
|
<text class="upload-tip">(仅可上传{{ field.maxCount || 1 }}个视频)</text>
|
||||||
|
</view>
|
||||||
|
<view class="upload-content">
|
||||||
|
<form-item-video :value="formData[field.key]" :maxCount="field.maxCount || 1"
|
||||||
|
:maxSize="field.maxSize || field.maxFileSize || 100" :userId="uploadUserId"
|
||||||
|
@input="onInput(field.key, $event)" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-else-if="field.type === 'richtext_editor'" class="form-item no-bg">
|
||||||
|
<text :class="['form-label', field.is_required === true ? 'required' : '']">{{ field.label }}</text>
|
||||||
|
<form-item-richtext :value="formData[field.key]" @input="onInput(field.key, $event)" />
|
||||||
|
</view>
|
||||||
|
<view v-else-if="field.type === 'service_step'" class="form-item-up upload-item no-bg">
|
||||||
|
<form-item-service-step :value="formData[field.key]" :label="field.label"
|
||||||
|
:required="field.is_required === true" @input="onInput(field.key, $event)" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import FormItemText from './FormItemText.vue';
|
||||||
|
import FormItemTextarea from './FormItemTextarea.vue';
|
||||||
|
import FormItemNumber from './FormItemNumber.vue';
|
||||||
|
import FormItemImage from './FormItemImage.vue';
|
||||||
|
import FormItemVideo from './FormItemVideo.vue';
|
||||||
|
import FormItemSelect from './FormItemSelect.vue';
|
||||||
|
import FormItemRadio from './FormItemRadio.vue';
|
||||||
|
import FormItemCheckbox from './FormItemCheckbox.vue';
|
||||||
|
import FormItemRichtext from './FormItemRichtext.vue';
|
||||||
|
import FormItemServiceStep from './FormItemServiceStep.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'DynamicForm',
|
||||||
|
components: {
|
||||||
|
'form-item-text': FormItemText,
|
||||||
|
'form-item-textarea': FormItemTextarea,
|
||||||
|
'form-item-number': FormItemNumber,
|
||||||
|
'form-item-image': FormItemImage,
|
||||||
|
'form-item-video': FormItemVideo,
|
||||||
|
'form-item-select': FormItemSelect,
|
||||||
|
'form-item-radio': FormItemRadio,
|
||||||
|
'form-item-checkbox': FormItemCheckbox,
|
||||||
|
'form-item-richtext': FormItemRichtext,
|
||||||
|
'form-item-service-step': FormItemServiceStep
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
fields: {
|
||||||
|
type: Array,
|
||||||
|
default: function () { return []; }
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
default: function () { return {}; }
|
||||||
|
},
|
||||||
|
uploadUserId: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
formData: {}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value: {
|
||||||
|
handler: function (val) {
|
||||||
|
this.formData = Object.assign({}, val);
|
||||||
|
},
|
||||||
|
immediate: true
|
||||||
|
},
|
||||||
|
fields: {
|
||||||
|
handler: function () {
|
||||||
|
var self = this;
|
||||||
|
var data = {};
|
||||||
|
this.fields.forEach(function (group) {
|
||||||
|
if (group.type === 'single' && group.field) {
|
||||||
|
self._initFieldData(data, group.field);
|
||||||
|
} else if (group.type === 'section' && group.fields) {
|
||||||
|
group.fields.forEach(function (field) {
|
||||||
|
self._initFieldData(data, field);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.formData = Object.assign({}, this.formData, data);
|
||||||
|
},
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
_initFieldData: function (data, field) {
|
||||||
|
if (this.formData[field.key] !== undefined) {
|
||||||
|
data[field.key] = this.formData[field.key];
|
||||||
|
} else if (field.default_value !== undefined) {
|
||||||
|
data[field.key] = field.default_value;
|
||||||
|
} else if (
|
||||||
|
field.type === 'multi_select' ||
|
||||||
|
field.type === 'image_multi' ||
|
||||||
|
field.type === 'service_step' ||
|
||||||
|
(field.type === 'video' && field.maxCount > 1)
|
||||||
|
) {
|
||||||
|
data[field.key] = [];
|
||||||
|
} else {
|
||||||
|
data[field.key] = '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onInput: function (key, event) {
|
||||||
|
var value = event;
|
||||||
|
if (event && event.target && event.target.value !== undefined) {
|
||||||
|
value = event.target.value;
|
||||||
|
}
|
||||||
|
this.$set(this.formData, key, value);
|
||||||
|
this.$emit('input', this.formData);
|
||||||
|
},
|
||||||
|
getData: function () {
|
||||||
|
return Object.assign({}, this.formData);
|
||||||
|
},
|
||||||
|
validate: function () {
|
||||||
|
var self = this;
|
||||||
|
for (var i = 0; i < this.fields.length; i++) {
|
||||||
|
var group = this.fields[i];
|
||||||
|
var fieldsToCheck = [];
|
||||||
|
if (group.type === 'single' && group.field) {
|
||||||
|
fieldsToCheck.push(group.field);
|
||||||
|
} else if (group.type === 'section' && group.fields) {
|
||||||
|
fieldsToCheck = group.fields;
|
||||||
|
}
|
||||||
|
for (var j = 0; j < fieldsToCheck.length; j++) {
|
||||||
|
var field = fieldsToCheck[j];
|
||||||
|
if (field.is_required === true) {
|
||||||
|
var value = self.formData[field.key];
|
||||||
|
if (!value || (Array.isArray(value) && value.length === 0)) {
|
||||||
|
uni.showToast({
|
||||||
|
title: field.label + '不能为空',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.dynamic-form {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item {
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 24rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.required::before {
|
||||||
|
content: '*';
|
||||||
|
color: #ff0000;
|
||||||
|
margin-right: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-tips {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #333333;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-tip {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-tip-card {
|
||||||
|
padding: 8rpx 12rpx;
|
||||||
|
background: #FDF0F0;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #E8101E;
|
||||||
|
line-height: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item-up {
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 24rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column nowrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-item {
|
||||||
|
padding-bottom: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-tip {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999999;
|
||||||
|
margin-left: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-content {
|
||||||
|
margin-top: 20rpx;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-options {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-card {
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
padding: 0 24rpx 24rpx;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* section内字段间距20rpx,通过section-field包装器控制 */
|
||||||
|
.section-field+.section-field {
|
||||||
|
padding-top: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* section内字段去掉独立白色背景和底部间距 */
|
||||||
|
.section-card .form-item,
|
||||||
|
.section-card .form-item-up {
|
||||||
|
background-color: transparent;
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-card .detail-row {
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title-bar {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
padding: 24rpx 0 20rpx;
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
line-height: 40rpx;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-row {
|
||||||
|
padding: 20rpx 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-row:first-child {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-row .form-label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
line-height: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-textarea-wrap {
|
||||||
|
position: relative;
|
||||||
|
background: #f7f7f7;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 24rpx 24rpx 52rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-textarea {
|
||||||
|
width: 100%;
|
||||||
|
height: 108rpx;
|
||||||
|
min-height: 108rpx;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 40rpx;
|
||||||
|
color: #333333;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-count {
|
||||||
|
position: absolute;
|
||||||
|
right: 24rpx;
|
||||||
|
bottom: 16rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #999999;
|
||||||
|
line-height: 32rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -22,7 +22,6 @@
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
<script src="https://gosspublic.alicdn.com/aliyun-oss-sdk-6.18.0.min.js"></script>
|
|
||||||
<script>
|
<script>
|
||||||
import request from '../../utils/request';
|
import request from '../../utils/request';
|
||||||
export default {
|
export default {
|
||||||
|
|
@ -235,4 +234,4 @@ import request from '../../utils/request';
|
||||||
font-size: 32rpx;
|
font-size: 32rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -200,12 +200,13 @@ import sparkMD5 from 'spark-md5'; // 引入MD5计算库
|
||||||
const loadNext = () => {
|
const loadNext = () => {
|
||||||
const start = currentChunk * chunkSize;
|
const start = currentChunk * chunkSize;
|
||||||
const end = start + chunkSize >= file.size ? file.size : start + chunkSize;
|
const end = start + chunkSize >= file.size ? file.size : start + chunkSize;
|
||||||
// #ifdef H5
|
let reader;
|
||||||
const reader = new FileReader();
|
// #ifdef H5
|
||||||
// #endif
|
reader = new FileReader();
|
||||||
// #ifndef H5
|
// #endif
|
||||||
const reader = uni.getfilesystem;
|
// #ifndef H5
|
||||||
// #endif
|
reader = uni.getfilesystem;
|
||||||
|
// #endif
|
||||||
reader.onload = (e) => {
|
reader.onload = (e) => {
|
||||||
spark.append(e.target.result);
|
spark.append(e.target.result);
|
||||||
currentChunk++;
|
currentChunk++;
|
||||||
|
|
@ -226,12 +227,13 @@ import sparkMD5 from 'spark-md5'; // 引入MD5计算库
|
||||||
try {
|
try {
|
||||||
// 获取ArrayBuffer
|
// 获取ArrayBuffer
|
||||||
const arrayBuffer = await new Promise((resolve) => {
|
const arrayBuffer = await new Promise((resolve) => {
|
||||||
// #ifdef H5
|
let reader;
|
||||||
const reader = new FileReader();
|
// #ifdef H5
|
||||||
// #endif
|
reader = new FileReader();
|
||||||
// #ifndef H5
|
// #endif
|
||||||
const reader = uni.getfilesystem;
|
// #ifndef H5
|
||||||
// #endif
|
reader = uni.getfilesystem;
|
||||||
|
// #endif
|
||||||
reader.onload = () => resolve(reader.result);
|
reader.onload = () => resolve(reader.result);
|
||||||
reader.readAsArrayBuffer(chunk);
|
reader.readAsArrayBuffer(chunk);
|
||||||
});
|
});
|
||||||
|
|
@ -418,4 +420,4 @@ import sparkMD5 from 'spark-md5'; // 引入MD5计算库
|
||||||
font-size: 32rpx;
|
font-size: 32rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
"name" : "美融融商家",
|
"name" : "美融融商家",
|
||||||
"appid" : "__UNI__BBE6285",
|
"appid" : "__UNI__BBE6285",
|
||||||
"description" : "同城便捷到家美业“技术外卖”服务、同城预约到店服务平台美融融为同城爱美人群提供更优质的更全面的服务。",
|
"description" : "同城便捷到家美业“技术外卖”服务、同城预约到店服务平台美融融为同城爱美人群提供更优质的更全面的服务。",
|
||||||
"versionName" : "1.0.21",
|
"versionName" : "1.0.22",
|
||||||
"versionCode" : 1021,
|
"versionCode" : 1022,
|
||||||
"transformPx" : false,
|
"transformPx" : false,
|
||||||
/* 5+App特有相关 */
|
/* 5+App特有相关 */
|
||||||
"app-plus" : {
|
"app-plus" : {
|
||||||
|
|
|
||||||
16
pages.json
|
|
@ -31,6 +31,20 @@
|
||||||
"enablePullDownRefresh": true
|
"enablePullDownRefresh": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/home/growth-package",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/home/growth-package-result",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "支付结果",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/message/message",
|
"path": "pages/message/message",
|
||||||
"style": {
|
"style": {
|
||||||
|
|
@ -1069,4 +1083,4 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"uniIdRouter": {}
|
"uniIdRouter": {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,71 +26,85 @@
|
||||||
<script>
|
<script>
|
||||||
import permissionUtils from "@/utils/per";
|
import permissionUtils from "@/utils/per";
|
||||||
import locationService from "@/utils/locationService";
|
import locationService from "@/utils/locationService";
|
||||||
export default {
|
import request from "@/utils/request";
|
||||||
data() {
|
|
||||||
return {
|
export default {
|
||||||
isShowPer: false, // 控制电话权限说明视图显示
|
data() {
|
||||||
phone: '19950079687'
|
return {
|
||||||
|
isShowPer: false, // 控制电话权限说明视图显示
|
||||||
|
phone: '19950079687' // 默认号码,接口失败时使用
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoad(options) {
|
||||||
|
// 调用接口获取客服电话
|
||||||
|
this.getCustomerService();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 获取客服电话
|
||||||
|
async getCustomerService() {
|
||||||
|
try {
|
||||||
|
const res = await request.post('/sj/poster/getCustomerService');
|
||||||
|
if (res.code === 200 && res.data && res.data.phone) {
|
||||||
|
this.phone = res.data.phone;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取客服电话失败', error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLoad(options) {
|
// 拨打电话
|
||||||
this.phone = options.phone || '19950079687'
|
async callPhone() {
|
||||||
},
|
const systemInfo = uni.getSystemInfoSync();
|
||||||
methods: {
|
if (systemInfo.platform === "ios") {
|
||||||
// 拨打电话
|
uni.makePhoneCall({ phoneNumber: this.phone });
|
||||||
async callPhone() {
|
} else {
|
||||||
const systemInfo = uni.getSystemInfoSync();
|
let permission = "android.permission.CALL_PHONE";
|
||||||
if (systemInfo.platform === "ios") {
|
this.isShowPer = true;
|
||||||
uni.makePhoneCall({ phoneNumber: this.phone });
|
|
||||||
} else {
|
|
||||||
let permission = "android.permission.CALL_PHONE";
|
|
||||||
this.isShowPer = true;
|
|
||||||
|
|
||||||
// 1. 检查权限
|
// 1. 检查权限
|
||||||
const { granted } = await permissionUtils.checkPermission(
|
const { granted } = await permissionUtils.checkPermission(
|
||||||
"phone",
|
"phone",
|
||||||
"需要拨打电话权限,方便您联系客服"
|
"需要拨打电话权限,方便您联系客服"
|
||||||
);
|
);
|
||||||
if (granted) {
|
if (granted) {
|
||||||
this.isShowPer = false;
|
this.isShowPer = false;
|
||||||
uni.makePhoneCall({ phoneNumber: this.phone });
|
uni.makePhoneCall({ phoneNumber: this.phone });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isShowPer = false;
|
this.isShowPer = false;
|
||||||
// 2. 显示权限说明弹窗
|
// 2. 显示权限说明弹窗
|
||||||
const confirm = await this.showPermissionDialog(
|
const confirm = await this.showPermissionDialog(
|
||||||
"拨打电话权限申请",
|
"拨打电话权限申请",
|
||||||
"我们需要拨打电话权限,方便您联系客服"
|
"我们需要拨打电话权限,方便您联系客服"
|
||||||
);
|
);
|
||||||
if (!confirm) return;
|
if (!confirm) return;
|
||||||
|
|
||||||
// 3. 请求权限
|
// 3. 请求权限
|
||||||
const result = await permissionUtils.requestPermission(
|
const result = await permissionUtils.requestPermission(
|
||||||
"phone",
|
"phone",
|
||||||
"需要拨打电话权限,方便您联系客服"
|
"需要拨打电话权限,方便您联系客服"
|
||||||
);
|
);
|
||||||
if (result) {
|
if (result) {
|
||||||
uni.makePhoneCall({ phoneNumber: this.phone });
|
uni.makePhoneCall({ phoneNumber: this.phone });
|
||||||
} else {
|
} else {
|
||||||
locationService.openAppSettings();
|
locationService.openAppSettings();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
showPermissionDialog(title, content) {
|
},
|
||||||
|
showPermissionDialog(title, content) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
uni.showModal({
|
uni.showModal({
|
||||||
title,
|
title,
|
||||||
content,
|
content,
|
||||||
confirmText: "去开启",
|
confirmText: "去开启",
|
||||||
success(res) {
|
success(res) {
|
||||||
resolve(res.confirm);
|
resolve(res.confirm);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
||||||
|
|
@ -285,13 +285,14 @@ export default {
|
||||||
sourceType: ['album', 'camera'],
|
sourceType: ['album', 'camera'],
|
||||||
compressed: false
|
compressed: false
|
||||||
})
|
})
|
||||||
// #ifdef H5
|
let selectedFile;
|
||||||
const file = res.tempFile;
|
// #ifdef H5
|
||||||
// #endif
|
selectedFile = res.tempFile;
|
||||||
// #ifdef APP-PLUS || MP-WEIXIN
|
// #endif
|
||||||
const file = res;
|
// #ifdef APP-PLUS || MP-WEIXIN
|
||||||
// #endif
|
selectedFile = res;
|
||||||
await this.uploadFile(file)
|
// #endif
|
||||||
|
await this.uploadFile(selectedFile)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('上传出错:', error)
|
console.error('上传出错:', error)
|
||||||
|
|
@ -369,8 +370,9 @@ export default {
|
||||||
// 上传到OSS
|
// 上传到OSS
|
||||||
uploadToOss(file, ossConfig, name) {
|
uploadToOss(file, ossConfig, name) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// #ifdef H5
|
let formData;
|
||||||
const formData = new FormData()
|
// #ifdef H5
|
||||||
|
formData = new FormData()
|
||||||
formData.append('name',name);
|
formData.append('name',name);
|
||||||
formData.append('policy', ossConfig.policy);
|
formData.append('policy', ossConfig.policy);
|
||||||
formData.append('OSSAccessKeyId', ossConfig.ossAccessKeyId);
|
formData.append('OSSAccessKeyId', ossConfig.ossAccessKeyId);
|
||||||
|
|
@ -386,15 +388,15 @@ export default {
|
||||||
})
|
})
|
||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
// #ifdef APP-PLUS || MP-WEIXIN
|
// #ifdef APP-PLUS || MP-WEIXIN
|
||||||
let formData = {};
|
formData = {};
|
||||||
formData.name = name;
|
formData.name = name;
|
||||||
formData.policy = ossConfig.policy;
|
formData.policy = ossConfig.policy;
|
||||||
formData.OSSAccessKeyId = ossConfig.ossAccessKeyId;
|
formData.OSSAccessKeyId = ossConfig.ossAccessKeyId;
|
||||||
formData.success_action_status = '200';
|
formData.success_action_status = '200';
|
||||||
formData.signature = ossConfig.signature;
|
formData.signature = ossConfig.signature;
|
||||||
formData.key = ossConfig.dir + name;
|
formData.key = ossConfig.dir + name;
|
||||||
formData.file = file;
|
formData.file = file;
|
||||||
let path;
|
let path;
|
||||||
if(this.accept == 'image/*') {
|
if(this.accept == 'image/*') {
|
||||||
path = file.path
|
path = file.path
|
||||||
|
|
@ -540,4 +542,4 @@ export default {
|
||||||
font-size: 32rpx;
|
font-size: 32rpx;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,167 @@
|
||||||
|
<template>
|
||||||
|
<view class="growth-package-result-page">
|
||||||
|
<custom-navbar
|
||||||
|
title="支付结果"
|
||||||
|
:showBack="true"
|
||||||
|
backgroundColor="#F7F7F7"
|
||||||
|
borderBottom="none"
|
||||||
|
></custom-navbar>
|
||||||
|
|
||||||
|
<view class="result-main">
|
||||||
|
<view class="result-icon" :class="isFail ? 'is-fail' : 'is-success'">
|
||||||
|
<block v-if="isFail">
|
||||||
|
<view class="fail-line"></view>
|
||||||
|
<view class="fail-dot"></view>
|
||||||
|
</block>
|
||||||
|
<view v-else class="success-check"></view>
|
||||||
|
</view>
|
||||||
|
<view class="result-title">{{ resultInfo.title }}</view>
|
||||||
|
<view class="result-desc">{{ resultInfo.desc }}</view>
|
||||||
|
<view class="primary-btn" @tap="handlePrimaryTap">{{ isFail ? "重新支付" : "返回首页" }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import CustomNavbar from "@/components/custom-navbar/custom-navbar.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
CustomNavbar,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
status: "success",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isFail() {
|
||||||
|
return this.status === "fail";
|
||||||
|
},
|
||||||
|
resultInfo() {
|
||||||
|
if (this.isFail) {
|
||||||
|
return {
|
||||||
|
title: "报名失败",
|
||||||
|
desc: "最后一步出了点问题 请重新支付",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
title: "报名成功",
|
||||||
|
desc: "您已完成支付 感谢你对美融融的支持",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
onLoad(options) {
|
||||||
|
this.status = options.status === "fail" ? "fail" : "success";
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handlePrimaryTap() {
|
||||||
|
if (this.isFail) {
|
||||||
|
uni.redirectTo({
|
||||||
|
url: "/pages/home/growth-package",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uni.switchTab({
|
||||||
|
url: "/pages/home/home",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
page {
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.growth-package-result-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #ffffff;
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 246rpx 40rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-icon {
|
||||||
|
width: 104rpx;
|
||||||
|
height: 104rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-icon.is-success {
|
||||||
|
background: #12c76b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-icon.is-fail {
|
||||||
|
background: #ff4652;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-check {
|
||||||
|
width: 44rpx;
|
||||||
|
height: 24rpx;
|
||||||
|
border-left: 8rpx solid #ffffff;
|
||||||
|
border-bottom: 8rpx solid #ffffff;
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
margin-top: -8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fail-line {
|
||||||
|
width: 8rpx;
|
||||||
|
height: 46rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fail-dot {
|
||||||
|
width: 8rpx;
|
||||||
|
height: 8rpx;
|
||||||
|
margin-left: -8rpx;
|
||||||
|
margin-top: 58rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-title {
|
||||||
|
margin-top: 34rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 40rpx;
|
||||||
|
color: #333333;
|
||||||
|
line-height: 56rpx;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-desc {
|
||||||
|
margin-top: 8rpx;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #b9b9b9;
|
||||||
|
line-height: 38rpx;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-btn {
|
||||||
|
width: 350rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
margin-top: 144rpx;
|
||||||
|
border: 1rpx solid #ff4b67;
|
||||||
|
border-radius: 40rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: #ffffff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #ff4767;
|
||||||
|
line-height: 42rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,348 @@
|
||||||
|
<template>
|
||||||
|
<view class="growth-package-page">
|
||||||
|
<image class="content-image" src="@/static/images/home/growth-package/intro.png" mode="widthFix"></image>
|
||||||
|
<view class="deal-carousel">
|
||||||
|
<swiper class="deal-swiper" autoplay circular :interval="3000" previous-margin="40rpx"
|
||||||
|
next-margin="40rpx" :current="dealCurrentIndex" @change="handleDealChange">
|
||||||
|
<swiper-item v-for="(item, index) in dealSlides" :key="item">
|
||||||
|
<view class="deal-slide" :class="getDealSlideClass(index)">
|
||||||
|
<image class="deal-slide-image" :src="item" mode="aspectFit"></image>
|
||||||
|
</view>
|
||||||
|
</swiper-item>
|
||||||
|
</swiper>
|
||||||
|
<view class="deal-indicator">
|
||||||
|
<view class="indicator-track" v-for="(item, index) in dealSlides" :key="item"
|
||||||
|
:class="{ active: index === dealCurrentIndex }"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<image class="content-image" src="@/static/images/home/growth-package/plan.png" mode="widthFix"></image>
|
||||||
|
|
||||||
|
<view class="fixed-action" @tap="handlePayTap">
|
||||||
|
<image class="action-image" :src="actionImage" mode="widthFix"></image>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import request from "@/utils/request";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "GrowthPackage",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
dealCurrentIndex: 0,
|
||||||
|
isPaying: false,
|
||||||
|
isJoined: false,
|
||||||
|
dealSlides: [
|
||||||
|
"/static/images/home/growth-package/deal-1.png",
|
||||||
|
"/static/images/home/growth-package/deal-2.png",
|
||||||
|
"/static/images/home/growth-package/deal-3.png",
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
actionImage() {
|
||||||
|
return this.isJoined
|
||||||
|
? "/static/images/home/growth-package/action-success.png"
|
||||||
|
: "/static/images/home/growth-package/action.png";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
onShow() {
|
||||||
|
this.getGrowthPackageStatus();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async getGrowthPackageStatus() {
|
||||||
|
if (!uni.getStorageSync("accessToken")) {
|
||||||
|
this.isJoined = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.isPaying) return;
|
||||||
|
uni.showLoading({
|
||||||
|
title: "查询报名状态...",
|
||||||
|
mask: true,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const res = await request.post(
|
||||||
|
"/sj/growthPackage/status",
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
hideLoading: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.isJoined = !!(res && res.code === 200 && res.data && Number(res.data.is_joined) === 1);
|
||||||
|
} catch (error) {
|
||||||
|
console.log("growthPackageStatus error", error);
|
||||||
|
} finally {
|
||||||
|
uni.hideLoading();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleDealChange(event) {
|
||||||
|
this.dealCurrentIndex = event.detail.current;
|
||||||
|
},
|
||||||
|
getDealSlideClass(index) {
|
||||||
|
const length = this.dealSlides.length;
|
||||||
|
const prevIndex = (this.dealCurrentIndex + length - 1) % length;
|
||||||
|
const nextIndex = (this.dealCurrentIndex + 1) % length;
|
||||||
|
if (index === this.dealCurrentIndex) {
|
||||||
|
return "is-active";
|
||||||
|
}
|
||||||
|
if (index === prevIndex) {
|
||||||
|
return "is-prev";
|
||||||
|
}
|
||||||
|
if (index === nextIndex) {
|
||||||
|
return "is-next";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
handlePayTap() {
|
||||||
|
if (this.isPaying) return;
|
||||||
|
if (this.isJoined) return;
|
||||||
|
if (!uni.getStorageSync("accessToken")) {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: "/pages/blogPopup/blogPopup",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uni.showActionSheet({
|
||||||
|
itemList: ["微信支付", "支付宝支付"],
|
||||||
|
success: (res) => {
|
||||||
|
const payKind = res.tapIndex === 0 ? 2 : 3;
|
||||||
|
this.createGrowthPackagePay(payKind);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async createGrowthPackagePay(payKind) {
|
||||||
|
this.isPaying = true;
|
||||||
|
uni.showLoading({
|
||||||
|
title: "支付中...",
|
||||||
|
mask: true,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const payRes = await request.post(
|
||||||
|
"/sj/growthPackage/pay",
|
||||||
|
{
|
||||||
|
pay_kind: payKind,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
hideLoading: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (!payRes || payRes.code !== 200) {
|
||||||
|
uni.hideLoading();
|
||||||
|
this.isPaying = false;
|
||||||
|
if (payRes && payRes.data && (payRes.data.id || payRes.data.number)) {
|
||||||
|
this.goToPayResult("fail", payRes.data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uni.showToast({
|
||||||
|
title: (payRes && payRes.msg) || "支付请求失败",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const orderInfo = payRes.data || {};
|
||||||
|
if (Number(orderInfo.is_joined) === 1) {
|
||||||
|
uni.hideLoading();
|
||||||
|
this.isPaying = false;
|
||||||
|
this.isJoined = true;
|
||||||
|
this.goToPayResult("success", orderInfo.order || {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const paymentParams = orderInfo.pay_params || orderInfo.orderInfo;
|
||||||
|
if (!paymentParams) {
|
||||||
|
uni.hideLoading();
|
||||||
|
this.isPaying = false;
|
||||||
|
if (orderInfo.id || orderInfo.number) {
|
||||||
|
this.goToPayResult("fail", orderInfo);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uni.showToast({
|
||||||
|
title: "支付参数缺失",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uni.hideLoading();
|
||||||
|
this.requestGrowthPackagePayment(payKind, paymentParams, orderInfo);
|
||||||
|
} catch (error) {
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.showToast({
|
||||||
|
title: "支付请求失败",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
this.isPaying = false;
|
||||||
|
console.log("growthPackagePay error", error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
requestGrowthPackagePayment(payKind, paymentParams, orderInfo) {
|
||||||
|
const provider = payKind === 2 ? "wxpay" : "alipay";
|
||||||
|
let payOrderInfo = paymentParams;
|
||||||
|
if (payKind === 2 && paymentParams && typeof paymentParams === "object") {
|
||||||
|
payOrderInfo = {
|
||||||
|
...paymentParams,
|
||||||
|
};
|
||||||
|
const systemInfo = uni.getSystemInfoSync();
|
||||||
|
if (systemInfo.platform === "ios" && payOrderInfo.appid) {
|
||||||
|
payOrderInfo.appId = payOrderInfo.appid;
|
||||||
|
delete payOrderInfo.appid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uni.requestPayment({
|
||||||
|
provider,
|
||||||
|
orderInfo: payOrderInfo,
|
||||||
|
success: () => {
|
||||||
|
this.queryGrowthPackagePay(orderInfo);
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
this.isPaying = false;
|
||||||
|
console.log("growthPackage requestPayment fail", err);
|
||||||
|
this.goToPayResult("fail", orderInfo);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async queryGrowthPackagePay(orderInfo) {
|
||||||
|
try {
|
||||||
|
const queryRes = await request.post(
|
||||||
|
"/sj/growthPackage/queryPay",
|
||||||
|
{
|
||||||
|
id: orderInfo.id,
|
||||||
|
number: orderInfo.number,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
hideLoading: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const order = queryRes && queryRes.data && queryRes.data.order;
|
||||||
|
if (queryRes && queryRes.code === 200 && order && Number(order.state) === 2) {
|
||||||
|
this.isJoined = true;
|
||||||
|
this.goToPayResult("success", order);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.goToPayResult("fail", orderInfo);
|
||||||
|
} catch (error) {
|
||||||
|
console.log("growthPackageQueryPay error", error);
|
||||||
|
this.goToPayResult("fail", orderInfo);
|
||||||
|
} finally {
|
||||||
|
this.isPaying = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
goToPayResult(status, orderInfo = {}) {
|
||||||
|
const params = [`status=${status}`];
|
||||||
|
if (orderInfo.number) {
|
||||||
|
params.push(`number=${encodeURIComponent(orderInfo.number)}`);
|
||||||
|
}
|
||||||
|
if (orderInfo.pay_money || orderInfo.order_money) {
|
||||||
|
params.push(`money=${encodeURIComponent(orderInfo.pay_money || orderInfo.order_money)}`);
|
||||||
|
}
|
||||||
|
uni.redirectTo({
|
||||||
|
url: `/pages/home/growth-package-result?${params.join("&")}`,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
page {
|
||||||
|
background: #150903;
|
||||||
|
}
|
||||||
|
|
||||||
|
.growth-package-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
padding-bottom: 259rpx;
|
||||||
|
background: #150903;
|
||||||
|
|
||||||
|
.content-image {
|
||||||
|
display: block;
|
||||||
|
width: 750rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deal-carousel {
|
||||||
|
position: relative;
|
||||||
|
width: 750rpx;
|
||||||
|
height: 1342rpx;
|
||||||
|
background: #000000;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deal-swiper {
|
||||||
|
width: 750rpx;
|
||||||
|
height: 1280rpx;
|
||||||
|
background: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deal-slide {
|
||||||
|
position: relative;
|
||||||
|
width: 670rpx;
|
||||||
|
height: 1280rpx;
|
||||||
|
background: #000000;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deal-slide-image {
|
||||||
|
display: block;
|
||||||
|
width: 670rpx;
|
||||||
|
height: 1280rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deal-slide.is-prev,
|
||||||
|
.deal-slide.is-next {
|
||||||
|
height: 1169rpx;
|
||||||
|
margin-top: 56rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deal-slide.is-prev .deal-slide-image,
|
||||||
|
.deal-slide.is-next .deal-slide-image {
|
||||||
|
position: absolute;
|
||||||
|
top: -56rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deal-slide.is-prev .deal-slide-image {
|
||||||
|
right: 10rpx;
|
||||||
|
border-top-right-radius: 20rpx;
|
||||||
|
border-bottom-right-radius: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deal-slide.is-next .deal-slide-image {
|
||||||
|
left: 10rpx;
|
||||||
|
border-top-left-radius: 20rpx;
|
||||||
|
border-bottom-left-radius: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deal-indicator {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 62rpx;
|
||||||
|
column-gap: 8rpx;
|
||||||
|
background: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.indicator-track {
|
||||||
|
width: 146rpx;
|
||||||
|
height: 8rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
background: rgba(255, 255, 255, 0.42);
|
||||||
|
}
|
||||||
|
|
||||||
|
.indicator-track.active {
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixed-action {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 20;
|
||||||
|
width: 750rpx;
|
||||||
|
height: 259rpx;
|
||||||
|
background: #150903;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-image {
|
||||||
|
display: block;
|
||||||
|
width: 750rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -20,6 +20,10 @@
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<view class="home-banner" @tap="goToGrowthPackage">
|
||||||
|
<image class="home-banner-img" src="@/static/images/home/banner.png" mode="aspectFill"></image>
|
||||||
|
</view>
|
||||||
|
|
||||||
<view class="setup-modal-mask" v-if="showSetupModal" catchtouchmove="true">
|
<view class="setup-modal-mask" v-if="showSetupModal" catchtouchmove="true">
|
||||||
<view class="setup-modal-content">
|
<view class="setup-modal-content">
|
||||||
<image class="modal-bg"
|
<image class="modal-bg"
|
||||||
|
|
@ -181,7 +185,7 @@
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="notice-bar card-box">
|
<view class="notice-bar card-box" v-if="noticeList.length > 0">
|
||||||
<image class="notice-icon"
|
<image class="notice-icon"
|
||||||
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/75f624b8-a6ed-4787-9380-1a087062805b.png">
|
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/75f624b8-a6ed-4787-9380-1a087062805b.png">
|
||||||
</image>
|
</image>
|
||||||
|
|
@ -206,6 +210,9 @@
|
||||||
<brand-card class="brand-card-wrap" v-for="(item, index) in brands" :key="index" :item="item"
|
<brand-card class="brand-card-wrap" v-for="(item, index) in brands" :key="index" :item="item"
|
||||||
:tagKeys="item.tagKeys"></brand-card>
|
:tagKeys="item.tagKeys"></brand-card>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="home-empty" v-if="brands.length === 0">
|
||||||
|
<noData top="20rpx"></noData>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="skills-section card-box">
|
<view class="skills-section card-box">
|
||||||
|
|
@ -219,6 +226,9 @@
|
||||||
<view class="skill-list">
|
<view class="skill-list">
|
||||||
<skill-card v-for="(item, index) in skills" :key="index" :item="item"></skill-card>
|
<skill-card v-for="(item, index) in skills" :key="index" :item="item"></skill-card>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="home-empty" v-if="skills.length === 0">
|
||||||
|
<noData top="20rpx"></noData>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view style="height: 120rpx;"></view>
|
<view style="height: 120rpx;"></view>
|
||||||
|
|
@ -260,11 +270,7 @@ export default {
|
||||||
displayScore: 0,
|
displayScore: 0,
|
||||||
animationTimer: null,
|
animationTimer: null,
|
||||||
|
|
||||||
noticeList: [{
|
noticeList: [],
|
||||||
id: 0,
|
|
||||||
title: '',
|
|
||||||
url: ''
|
|
||||||
}],
|
|
||||||
|
|
||||||
quickActions: [{
|
quickActions: [{
|
||||||
name: '扫码验券',
|
name: '扫码验券',
|
||||||
|
|
@ -472,6 +478,11 @@ export default {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
goToGrowthPackage() {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages/home/growth-package'
|
||||||
|
});
|
||||||
|
},
|
||||||
// 弹窗中的快捷跳转功能
|
// 弹窗中的快捷跳转功能
|
||||||
closeSetupModal() {
|
closeSetupModal() {
|
||||||
this.showSetupModal = false;
|
this.showSetupModal = false;
|
||||||
|
|
@ -544,7 +555,14 @@ export default {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: '/pages/blogPopup/blogPopup'
|
url: '/pages/blogPopup/blogPopup'
|
||||||
});
|
});
|
||||||
return
|
return;
|
||||||
|
}
|
||||||
|
if (this.brands.length === 0) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '暂无数据',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: '/pages/jingxuan/selected-brands'
|
url: '/pages/jingxuan/selected-brands'
|
||||||
|
|
@ -555,7 +573,14 @@ export default {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: '/pages/blogPopup/blogPopup'
|
url: '/pages/blogPopup/blogPopup'
|
||||||
});
|
});
|
||||||
return
|
return;
|
||||||
|
}
|
||||||
|
if (this.skills.length === 0) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '暂无数据',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: '/pages/jingxuan/selected-skills'
|
url: '/pages/jingxuan/selected-skills'
|
||||||
|
|
@ -717,7 +742,11 @@ export default {
|
||||||
tagKeys: tags
|
tagKeys: tags
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
this.brands = [];
|
||||||
}
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
this.brands = [];
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -742,7 +771,11 @@ export default {
|
||||||
video: item.video || ''
|
video: item.video || ''
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
this.skills = [];
|
||||||
}
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
this.skills = [];
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -1100,7 +1133,7 @@ page {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 40rpx;
|
margin-bottom: 24rpx;
|
||||||
|
|
||||||
.user-info {
|
.user-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -1203,6 +1236,23 @@ page {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.home-banner {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
width: 710rpx;
|
||||||
|
height: 266rpx;
|
||||||
|
margin-left: -4rpx;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.home-banner-img {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.settlement-guide {
|
.settlement-guide {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
@ -1488,6 +1538,12 @@ page {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.home-empty {
|
||||||
|
height: 360rpx;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
.skills-section {
|
.skills-section {
|
||||||
.skill-list {
|
.skill-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
||||||
|
|
@ -146,9 +146,6 @@
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
|
||||||
</view>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
@ -827,4 +824,4 @@ page {
|
||||||
padding-top: calc(var(--status-bar-height) + env(safe-area-inset-top));
|
padding-top: calc(var(--status-bar-height) + env(safe-area-inset-top));
|
||||||
}
|
}
|
||||||
/* #endif */
|
/* #endif */
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -67,12 +67,10 @@
|
||||||
|
|
||||||
<view class="form-upimg">
|
<view class="form-upimg">
|
||||||
<view class="upload-box" @click="chooseImage('idcard_negative')">
|
<view class="upload-box" @click="chooseImage('idcard_negative')">
|
||||||
</image>
|
|
||||||
<view class="upload-camera img2">
|
<view class="upload-camera img2">
|
||||||
<image v-if="formData.idcard_negative" :src="formData.idcard_negative" mode="aspectFill"
|
<image v-if="formData.idcard_negative" :src="formData.idcard_negative" mode="aspectFill"
|
||||||
class="preview-image">
|
class="preview-image"></image>
|
||||||
<image v-else src="/static/images/camera.png" mode="aspectFit" class="camera-icon">
|
<image v-else src="/static/images/camera.png" mode="aspectFit" class="camera-icon"></image>
|
||||||
</image>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<text class="label required">请上传身份证反面(国徽面)</text>
|
<text class="label required">请上传身份证反面(国徽面)</text>
|
||||||
|
|
@ -126,12 +124,10 @@
|
||||||
|
|
||||||
<view class="form-upimg">
|
<view class="form-upimg">
|
||||||
<view class="upload-box" @click="chooseImage('legal_idcard_negative')">
|
<view class="upload-box" @click="chooseImage('legal_idcard_negative')">
|
||||||
</image>
|
|
||||||
<view class="upload-camera img2">
|
<view class="upload-camera img2">
|
||||||
<image v-if="formData.legal_idcard_negative" :src="formData.legal_idcard_negative"
|
<image v-if="formData.legal_idcard_negative" :src="formData.legal_idcard_negative"
|
||||||
mode="aspectFill" class="preview-image">
|
mode="aspectFill" class="preview-image"></image>
|
||||||
<image v-else src="/static/images/camera.png" mode="aspectFit" class="camera-icon">
|
<image v-else src="/static/images/camera.png" mode="aspectFit" class="camera-icon"></image>
|
||||||
</image>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<text class="label required">请上传法人身份证反面(国徽面)</text>
|
<text class="label required">请上传法人身份证反面(国徽面)</text>
|
||||||
|
|
@ -2002,4 +1998,4 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/* #endif */
|
/* #endif */
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,7 @@
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="form-item-one box-cont">
|
<view class="form-item-one box-cont">
|
||||||
<text class="label required">行业资质</text>
|
<text class="label">行业资质</text>
|
||||||
<view class="image-list">
|
<view class="image-list">
|
||||||
<view class="image-item" v-for="(img, index) in formData.qualifications_sector" :key="'industry-' + index">
|
<view class="image-item" v-for="(img, index) in formData.qualifications_sector" :key="'industry-' + index">
|
||||||
<image :src="img" mode="aspectFill" class="qualification-image"></image>
|
<image :src="img" mode="aspectFill" class="qualification-image"></image>
|
||||||
|
|
@ -1080,13 +1080,6 @@ export default {
|
||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!this.formData.qualifications_sector || this.formData.qualifications_sector.length === 0) {
|
|
||||||
uni.showToast({
|
|
||||||
title: '请上传行业资质',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1654,4 +1647,4 @@ export default {
|
||||||
.tip-btn .btn-text {
|
.tip-btn .btn-text {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,6 @@
|
||||||
<span class="detail-info-user__company__name__line"> | </span>
|
<span class="detail-info-user__company__name__line"> | </span>
|
||||||
<view class="detail-info-user__company__name__text2">
|
<view class="detail-info-user__company__name__text2">
|
||||||
{{ item.for_shop }}
|
{{ item.for_shop }}
|
||||||
</image>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
|
@ -811,4 +810,4 @@
|
||||||
padding: 0rpx;
|
padding: 0rpx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -166,39 +166,61 @@
|
||||||
</view> -->
|
</view> -->
|
||||||
|
|
||||||
<!-- 项目说明 -->
|
<!-- 项目说明 -->
|
||||||
<view class="notice-section">
|
<view class="notice-section" v-if="hasProjectInfo">
|
||||||
<view class="section-title">
|
<view class="section-title">
|
||||||
<text class="section-tit">项目说明</text>
|
<text class="section-tit">项目说明</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="notice-content">
|
<view class="notice-content">
|
||||||
<view class="notice-cont">
|
<view class="notice-cont" v-if="serviceInfo.server_time">
|
||||||
<text class="notice-title">
|
<text class="notice-title">
|
||||||
服务时长:
|
服务时长:
|
||||||
</text>
|
</text>
|
||||||
<text class="notice-text">
|
<text class="notice-text">
|
||||||
{{serviceInfo.server_time}}分钟
|
{{serviceInfo.server_time}}分钟
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
<view class="notice-cont" v-for="(item,i) in visibleDetailList" :key="'detail-' + i">
|
||||||
|
<text class="notice-title">
|
||||||
|
{{item.title}}:
|
||||||
|
</text>
|
||||||
|
<text class="notice-text">
|
||||||
|
{{item.text}}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="notice-cont" v-for="(item,i) in serviceInfo.detail">
|
|
||||||
<text class="notice-title">
|
|
||||||
{{item.title}}:
|
|
||||||
</text>
|
|
||||||
<text class="notice-text">
|
|
||||||
{{item.text}}
|
|
||||||
</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
<view class="notice-section" v-for="(section, sectionIndex) in dynamicIntroSections"
|
||||||
<!-- 图文详情 -->
|
:key="'dynamic-section-' + sectionIndex">
|
||||||
<imgAndText v-if="serviceInfo.graphic_details" :info="serviceInfo.graphic_details"></imgAndText>
|
<view class="section-title">
|
||||||
|
<text class="section-tit">{{section.title}}</text>
|
||||||
|
</view>
|
||||||
|
<view class="notice-content">
|
||||||
|
<view class="notice-cont dynamic-notice-cont" v-for="(item, itemIndex) in section.fields"
|
||||||
|
:key="'dynamic-field-' + sectionIndex + '-' + itemIndex">
|
||||||
|
<text class="notice-title">{{item.title}}:</text>
|
||||||
|
<view class="dynamic-media-list" v-if="item.images.length">
|
||||||
|
<image class="dynamic-image" v-for="(img, imgIndex) in item.images"
|
||||||
|
:key="'dynamic-img-' + imgIndex" :src="img" mode="aspectFill"
|
||||||
|
@click="previewImage(item.images, imgIndex)"></image>
|
||||||
|
</view>
|
||||||
|
<view class="dynamic-media-list" v-else-if="item.videos.length">
|
||||||
|
<video class="dynamic-video" v-for="(video, videoIndex) in item.videos"
|
||||||
|
:key="'dynamic-video-' + videoIndex" :src="video" controls></video>
|
||||||
|
</view>
|
||||||
|
<text class="notice-text" v-else>{{item.displayValue}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- 图文详情 -->
|
||||||
|
<imgAndText v-if="serviceInfo.graphic_details" :info="serviceInfo.graphic_details"></imgAndText>
|
||||||
|
|
||||||
<!-- 步骤 -->
|
<!-- 步骤 -->
|
||||||
<view class="notice-section">
|
<view class="notice-section" v-if="visibleProcessList.length">
|
||||||
<view class="section-title">
|
<view class="section-title">
|
||||||
<text class="section-tit">项目步骤</text>
|
<text class="section-tit">项目步骤</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="notice-content">
|
<view class="notice-content">
|
||||||
<view class="notice-cont" v-for="(item,i) in serviceInfo.process">
|
<view class="notice-cont" v-for="(item,i) in visibleProcessList" :key="'process-' + i">
|
||||||
<text class="notice-title">
|
<text class="notice-title">
|
||||||
{{item.title}}:
|
{{item.title}}:
|
||||||
</text>
|
</text>
|
||||||
|
|
@ -207,9 +229,9 @@
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<!-- 订购须知 -->
|
<!-- 订购须知 -->
|
||||||
<view class="notice-section">
|
<view class="notice-section" v-if="purchaseNotesText">
|
||||||
<view class="section-title">
|
<view class="section-title">
|
||||||
<text class="section-tit">订购须知</text>
|
<text class="section-tit">订购须知</text>
|
||||||
<!-- <view class="section-tit-right">
|
<!-- <view class="section-tit-right">
|
||||||
|
|
@ -218,9 +240,9 @@
|
||||||
</view> -->
|
</view> -->
|
||||||
</view>
|
</view>
|
||||||
<view class="notice-content">
|
<view class="notice-content">
|
||||||
<text class="notice-text">
|
<text class="notice-text">
|
||||||
{{serviceInfo.purchasenotes}}
|
{{purchaseNotesText}}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
|
@ -324,11 +346,30 @@
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
userAdrees() {
|
userAdrees() {
|
||||||
return uni.getStorageSync("userAdrees").addressRes || getApp().globalData.addressRes;
|
return uni.getStorageSync("userAdrees").addressRes || getApp().globalData.addressRes;
|
||||||
}
|
},
|
||||||
},
|
visibleDetailList() {
|
||||||
|
return (this.serviceInfo.detail || []).filter((item) => {
|
||||||
|
return item && item.title && item.text;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
visibleProcessList() {
|
||||||
|
return (this.serviceInfo.process || []).filter((item) => {
|
||||||
|
return item && item.text;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
hasProjectInfo() {
|
||||||
|
return !!this.serviceInfo.server_time || this.visibleDetailList.length > 0;
|
||||||
|
},
|
||||||
|
purchaseNotesText() {
|
||||||
|
return this.serviceInfo.purchasenotes || this.serviceInfo.purchase_notes || '';
|
||||||
|
},
|
||||||
|
dynamicIntroSections() {
|
||||||
|
return this.buildDynamicIntroSections(this.serviceInfo.intro);
|
||||||
|
}
|
||||||
|
},
|
||||||
onLoad(option) {
|
onLoad(option) {
|
||||||
//清除选择的信息
|
//清除选择的信息
|
||||||
this.$store.commit("clearServiceState")
|
this.$store.commit("clearServiceState")
|
||||||
|
|
@ -367,8 +408,88 @@
|
||||||
onHide() {
|
onHide() {
|
||||||
this.showVideo = false;
|
this.showVideo = false;
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async goInvite() {
|
parseMaybeJson(raw) {
|
||||||
|
if (!raw) return [];
|
||||||
|
if (typeof raw !== 'string') return raw;
|
||||||
|
try {
|
||||||
|
return JSON.parse(raw);
|
||||||
|
} catch (e) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getDynamicPlainValue(value) {
|
||||||
|
if (value === undefined || value === null) return '';
|
||||||
|
if (typeof value !== 'object') return String(value);
|
||||||
|
return value.label || value.title || value.name || value.text || value.value || '';
|
||||||
|
},
|
||||||
|
normalizeDynamicIntroField(item) {
|
||||||
|
if (!item) return null;
|
||||||
|
const type = item.type || '';
|
||||||
|
const value = item.value;
|
||||||
|
const images = [];
|
||||||
|
const videos = [];
|
||||||
|
let displayValue = '';
|
||||||
|
if (value === undefined || value === null || value === '') return null;
|
||||||
|
if (Array.isArray(value) && value.length === 0) return null;
|
||||||
|
|
||||||
|
if (type === 'image_multi') {
|
||||||
|
(value || []).forEach((img) => {
|
||||||
|
const url = typeof img === 'string' ? img : (img && (img.url || img.path));
|
||||||
|
if (url) images.push(url);
|
||||||
|
});
|
||||||
|
} else if (type === 'video') {
|
||||||
|
const list = Array.isArray(value) ? value : [value];
|
||||||
|
list.forEach((video) => {
|
||||||
|
const url = typeof video === 'string' ? video : (video && (video.url || video.path));
|
||||||
|
if (url) videos.push(url);
|
||||||
|
});
|
||||||
|
} else if (type === 'service_step' && Array.isArray(value)) {
|
||||||
|
displayValue = value.map((step, index) => {
|
||||||
|
const text = this.getDynamicPlainValue(step);
|
||||||
|
return text ? `${index + 1}. ${text}` : '';
|
||||||
|
}).filter(Boolean).join(';');
|
||||||
|
} else if (Array.isArray(value)) {
|
||||||
|
displayValue = value.map((val) => this.getDynamicPlainValue(val)).filter(Boolean).join('、');
|
||||||
|
} else if (typeof value === 'object') {
|
||||||
|
displayValue = this.getDynamicPlainValue(value);
|
||||||
|
} else {
|
||||||
|
displayValue = String(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!displayValue && images.length === 0 && videos.length === 0) return null;
|
||||||
|
return {
|
||||||
|
title: item.title || '补充信息',
|
||||||
|
type,
|
||||||
|
displayValue,
|
||||||
|
images,
|
||||||
|
videos,
|
||||||
|
sectionTitle: item.sectionTitle || '',
|
||||||
|
sectionId: item.sectionId || '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
buildDynamicIntroSections(rawIntro) {
|
||||||
|
const intro = this.parseMaybeJson(rawIntro);
|
||||||
|
if (!Array.isArray(intro)) return [];
|
||||||
|
const groups = [];
|
||||||
|
const groupMap = {};
|
||||||
|
intro.forEach((item) => {
|
||||||
|
const field = this.normalizeDynamicIntroField(item);
|
||||||
|
if (!field) return;
|
||||||
|
const title = field.sectionTitle || (field.type === 'service_step' ? '项目步骤' : '更多说明');
|
||||||
|
const key = field.sectionId || title;
|
||||||
|
if (!groupMap[key]) {
|
||||||
|
groupMap[key] = {
|
||||||
|
title,
|
||||||
|
fields: [],
|
||||||
|
};
|
||||||
|
groups.push(groupMap[key]);
|
||||||
|
}
|
||||||
|
groupMap[key].fields.push(field);
|
||||||
|
});
|
||||||
|
return groups.filter((section) => section.fields.length > 0);
|
||||||
|
},
|
||||||
|
async goInvite() {
|
||||||
let imageUrl =
|
let imageUrl =
|
||||||
'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/yh/1756713709528-717.png?x-oss-process=image/resize,p_40'
|
'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/yh/1756713709528-717.png?x-oss-process=image/resize,p_40'
|
||||||
if (this.serviceInfo.photo[0]) {
|
if (this.serviceInfo.photo[0]) {
|
||||||
|
|
@ -1136,13 +1257,33 @@
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notice-text {
|
.notice-text {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
font-size: 26rpx;
|
font-size: 26rpx;
|
||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 底部按钮 */
|
.dynamic-notice-cont {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dynamic-media-list {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dynamic-image,
|
||||||
|
.dynamic-video {
|
||||||
|
width: 160rpx;
|
||||||
|
height: 160rpx;
|
||||||
|
margin-right: 12rpx;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
background-color: #F6F6F6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 底部按钮 */
|
||||||
.bottom-bar {
|
.bottom-bar {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|
@ -1346,4 +1487,4 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/* #endif */
|
/* #endif */
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -10,25 +10,27 @@
|
||||||
<view v-else class="addTextAndImg-content">
|
<view v-else class="addTextAndImg-content">
|
||||||
<view v-for="(item, index) in list" :key="index" class="addTextAndImg-content-card">
|
<view v-for="(item, index) in list" :key="index" class="addTextAndImg-content-card">
|
||||||
<!-- <rich-text :nodes="item.value" v-if="item.type==1"></rich-text> -->
|
<!-- <rich-text :nodes="item.value" v-if="item.type==1"></rich-text> -->
|
||||||
<uv-divider dashed v-if="item.type == 1" ></uv-divider>
|
<uv-divider dashed v-if="item.type == 1"></uv-divider>
|
||||||
<view class="addTextAndImg-content-card-text" v-if="item.type==2">{{ item.value }}</view>
|
<view class="addTextAndImg-content-card-text" v-if="item.type == 2">{{ item.value }}</view>
|
||||||
<image style="width: 100%;" mode="widthFix" :src="item.value" v-else-if="item.type==3|| item.type==4"></image>
|
<image style="width: 100%;" mode="widthFix" :src="item.value"
|
||||||
|
v-else-if="item.type == 3 || item.type == 4">
|
||||||
|
</image>
|
||||||
<view class="addTextAndImg-content-card-btns">
|
<view class="addTextAndImg-content-card-btns">
|
||||||
<view class="addTextAndImg-content-card-btns-btn" v-for="item2 in btns" :key="item2.id"
|
<view class="addTextAndImg-content-card-btns-btn" v-for="item2 in btns" :key="item2.id"
|
||||||
@click.stop="handleCard(item2.id, index)" v-if="
|
@click.stop="handleCard(item2.id, index)" v-if="
|
||||||
!(index == 0 && item2.id == 2) &&
|
!(index == 0 && item2.id == 2) &&
|
||||||
!(index == list.length - 1 && item2.id == 3)
|
!(index == list.length - 1 && item2.id == 3)
|
||||||
">
|
">
|
||||||
{{ item2.text }}
|
{{ item2.text }}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="bottom-buttons" v-if="list.length != 0">
|
<view class="bottom-buttons" v-if="list.length != 0">
|
||||||
<button class="btn-submit" @click="submitForm">保存</button>
|
<button class="btn-submit" @click="submitForm">保存</button>
|
||||||
</view>
|
</view>
|
||||||
<uv-overlay :show="showPop" @click="showPop = false">
|
<uv-overlay :show="showPop" @click="showPop = false">
|
||||||
<view class="pop-cards">
|
<view class="pop-cards">
|
||||||
<view class="pop-cards-card" v-for="item in addWays" :key="item.id" @click.stop="addContent(item.id)">
|
<view class="pop-cards-card" v-for="item in addWays" :key="item.id" @click.stop="addContent(item.id)">
|
||||||
|
|
@ -60,425 +62,427 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import uploadImage from '../../utils/uploadImage'
|
import uploadImage from '../../utils/uploadImage'
|
||||||
import permissionUtils from '../../utils/per'
|
import permissionUtils from '../../utils/per'
|
||||||
import locationService from '../../utils/locationService';
|
import locationService from '../../utils/locationService';
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isReplace:false,
|
isReplace: false,
|
||||||
crIndex: null, //插入位置
|
crIndex: null, //插入位置
|
||||||
nowQer: 'xc',
|
nowQer: 'xc',
|
||||||
isShowPer: false,
|
isShowPer: false,
|
||||||
list: [],
|
list: [],
|
||||||
textareaValue: "",
|
textareaValue: "",
|
||||||
showPop: false,
|
showPop: false,
|
||||||
showTextarea: false,
|
showTextarea: false,
|
||||||
addWays: [{
|
addWays: [{
|
||||||
text: "分割线",
|
text: "分割线",
|
||||||
id: 1,
|
id: 1,
|
||||||
url: "/static/images/shop/imgAndText/fenge.png",
|
url: "/static/images/shop/imgAndText/fenge.png",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: "文本",
|
text: "文本",
|
||||||
id: 2,
|
id: 2,
|
||||||
url: "/static/images/shop/imgAndText/text.png",
|
url: "/static/images/shop/imgAndText/text.png",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: "图片",
|
text: "图片",
|
||||||
id: 3,
|
id: 3,
|
||||||
url: "/static/images/shop/imgAndText/bz.png",
|
url: "/static/images/shop/imgAndText/bz.png",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: "相机",
|
text: "相机",
|
||||||
id: 4,
|
id: 4,
|
||||||
url: "/static/images/shop/imgAndText/camera.png",
|
url: "/static/images/shop/imgAndText/camera.png",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
btns: [{
|
btns: [{
|
||||||
text: "删除",
|
text: "删除",
|
||||||
id: 1,
|
id: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: "上移",
|
text: "上移",
|
||||||
id: 2,
|
id: 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: "下移",
|
text: "下移",
|
||||||
id: 3,
|
id: 3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: "替换",
|
text: "替换",
|
||||||
id: 4,
|
id: 4,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: "插入",
|
text: "插入",
|
||||||
id: 5,
|
id: 5,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
onLoad(options) {
|
onLoad(options) {
|
||||||
console.log(options)
|
console.log(options)
|
||||||
if(options.list){
|
if (options.list && options.list !== 'null' && options.list !== 'undefined') {
|
||||||
const parsed = JSON.parse(options.list)
|
try {
|
||||||
this.list = Array.isArray(parsed) ? parsed : []
|
this.list = JSON.parse(options.list) || [];
|
||||||
|
} catch (e) {
|
||||||
|
this.list = [];
|
||||||
|
}
|
||||||
console.log(this.list)
|
console.log(this.list)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
submitForm(){
|
submitForm() {
|
||||||
uni.$emit('updateGraphicDetails', this.list);
|
uni.$emit('updateGraphicDetails', this.list);
|
||||||
uni.navigateBack()
|
uni.navigateBack()
|
||||||
},
|
},
|
||||||
// 定义一个函数,在数组的指定索引后插入数据
|
// 定义一个函数,在数组的指定索引后插入数据
|
||||||
insertAfterIndex(arr, index, data) {
|
insertAfterIndex(arr, index, data) {
|
||||||
// 检查索引是否有效
|
// 检查索引是否有效
|
||||||
if (index < 0 || index >= arr.length) {
|
if (index < 0 || index >= arr.length) {
|
||||||
console.error("索引无效");
|
console.error("索引无效");
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用splice方法在index + 1的位置插入数据
|
|
||||||
// 第二个参数0表示不删除任何元素,后面的参数是要插入的数据
|
|
||||||
arr.splice(index + 1, 0, data);
|
|
||||||
this.crIndex = null
|
|
||||||
return arr;
|
return arr;
|
||||||
},
|
}
|
||||||
// 定义一个函数,替换数组指定索引处的数据
|
|
||||||
replaceAtIndex(arr, index, data) {
|
// 使用splice方法在index + 1的位置插入数据
|
||||||
// 检查索引是否有效(有效索引范围:0 <= index < arr.length)
|
// 第二个参数0表示不删除任何元素,后面的参数是要插入的数据
|
||||||
if (index < 0 || index >= arr.length) {
|
arr.splice(index + 1, 0, data);
|
||||||
console.error("索引无效");
|
this.crIndex = null
|
||||||
return arr; // 索引无效时返回原数组
|
return arr;
|
||||||
}
|
},
|
||||||
|
// 定义一个函数,替换数组指定索引处的数据
|
||||||
// 直接替换指定索引处的数据
|
replaceAtIndex(arr, index, data) {
|
||||||
arr[index] = data;
|
// 检查索引是否有效(有效索引范围:0 <= index < arr.length)
|
||||||
|
if (index < 0 || index >= arr.length) {
|
||||||
// 保持与插入方法一致的业务逻辑(如果crIndex是你的业务变量)
|
console.error("索引无效");
|
||||||
this.crIndex = null;
|
return arr; // 索引无效时返回原数组
|
||||||
this.isReplace = false
|
}
|
||||||
|
|
||||||
return arr; // 返回修改后的数组
|
// 直接替换指定索引处的数据
|
||||||
},
|
arr[index] = data;
|
||||||
//写入文本
|
|
||||||
setTextarea() {
|
// 保持与插入方法一致的业务逻辑(如果crIndex是你的业务变量)
|
||||||
let obj = {
|
this.crIndex = null;
|
||||||
value: this.textareaValue,
|
this.isReplace = false
|
||||||
type: 2,
|
|
||||||
};
|
return arr; // 返回修改后的数组
|
||||||
if (this.textareaValue.length == 0) {
|
},
|
||||||
this.showTextarea = false
|
//写入文本
|
||||||
this.crIndex = null
|
setTextarea() {
|
||||||
|
let obj = {
|
||||||
|
value: this.textareaValue,
|
||||||
|
type: 2,
|
||||||
|
};
|
||||||
|
if (this.textareaValue.length == 0) {
|
||||||
|
this.showTextarea = false
|
||||||
|
this.crIndex = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.crIndex != null) {
|
||||||
|
if (this.isReplace) {
|
||||||
|
this.replaceAtIndex(this.list, this.crIndex, obj);
|
||||||
|
} else {
|
||||||
|
this.insertAfterIndex(this.list, this.crIndex, obj);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.list.push(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.textareaValue = ""
|
||||||
|
this.showTextarea = false
|
||||||
|
},
|
||||||
|
//操作卡片 删除,上移等
|
||||||
|
handleCard(id, index) {
|
||||||
|
switch (id) {
|
||||||
|
case 1:
|
||||||
|
this.list.splice(index, 1); // 从索引 index 开始,删除 1 个元素
|
||||||
|
this.showPop = false
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
// 保存要交换的两个元素
|
||||||
|
let temp = this.list[index - 1];
|
||||||
|
|
||||||
|
// 用 splice 交换:先替换 index-1 位置为 index 的值,再替换 index 位置为 temp
|
||||||
|
this.list.splice(index - 1, 1, this.list[index]); // 把 index-1 位置换成 index 的值
|
||||||
|
this.list.splice(index, 1, temp); // 把 index 位置换成原来 index-1 的值
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
// 保存要交换的两个元素
|
||||||
|
let temp2 = this.list[index + 1];
|
||||||
|
|
||||||
|
// 用 splice 交换:先替换 index-1 位置为 index 的值,再替换 index 位置为 temp
|
||||||
|
this.list.splice(index + 1, 1, this.list[index]); // 把 index-1 位置换成 index 的值
|
||||||
|
this.list.splice(index, 1, temp2); // 把 index 位置换成原来 index-1 的值
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
this.crIndex = index
|
||||||
|
this.isReplace = true
|
||||||
|
this.showPop = true
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
this.crIndex = index
|
||||||
|
this.showPop = true
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addContent(id) {
|
||||||
|
switch (id) {
|
||||||
|
case 1:
|
||||||
|
let obj = {
|
||||||
|
value: ``,
|
||||||
|
type: 1,
|
||||||
|
};
|
||||||
|
if (this.crIndex != null) {
|
||||||
|
if (this.isReplace) {
|
||||||
|
this.replaceAtIndex(this.list, this.crIndex, obj);
|
||||||
|
} else {
|
||||||
|
this.insertAfterIndex(this.list, this.crIndex, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.list.push(obj);
|
||||||
|
}
|
||||||
|
this.showPop = false
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
this.showTextarea = true
|
||||||
|
this.showPop = false
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
this.chooseAvatar(3)
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
this.chooseAvatar(4)
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async chooseAvatar(type) {
|
||||||
|
try {
|
||||||
|
const systemInfo = uni.getSystemInfoSync()
|
||||||
|
if (systemInfo.platform === 'ios') {
|
||||||
|
this.openCamera(type);
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (this.crIndex!=null) {
|
// 根据类型确定权限字符串和提示文本
|
||||||
if(this.isReplace){
|
const permissionKey = type === 3 ? 'photo_library' : 'camera';
|
||||||
this.replaceAtIndex(this.list, this.crIndex, obj);
|
const permissionAndroid = type === 3
|
||||||
}else{
|
? 'android.permission.READ_EXTERNAL_STORAGE,android.permission.WRITE_EXTERNAL_STORAGE'
|
||||||
this.insertAfterIndex(this.list, this.crIndex, obj);
|
: 'android.permission.CAMERA';
|
||||||
}
|
const dialogTitle = type === 3 ? '相册权限申请' : '相机权限申请';
|
||||||
} else {
|
const dialogContent = type === 3
|
||||||
this.list.push(obj);
|
? '我们需要访问您的相册以完成图片添加'
|
||||||
|
: '我们需要访问您的相机以完成图片拍摄';
|
||||||
|
const nowQer = type === 3 ? 'xc' : 'xj';
|
||||||
|
|
||||||
|
// 检查是否是首次请求(用于控制自定义视图显示)
|
||||||
|
const firstRequest = !plus.storage.getItem(`perm_${permissionKey}`);
|
||||||
|
if (firstRequest) {
|
||||||
|
this.isShowPer = true;
|
||||||
|
this.nowQer = nowQer;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.textareaValue = ""
|
// 1. 检查权限
|
||||||
this.showTextarea = false
|
const { granted } = await permissionUtils.checkPermission(permissionKey, dialogContent);
|
||||||
},
|
if (granted) {
|
||||||
//操作卡片 删除,上移等
|
|
||||||
handleCard(id, index) {
|
|
||||||
switch (id) {
|
|
||||||
case 1:
|
|
||||||
this.list.splice(index, 1); // 从索引 index 开始,删除 1 个元素
|
|
||||||
this.showPop = false
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
// 保存要交换的两个元素
|
|
||||||
let temp = this.list[index - 1];
|
|
||||||
|
|
||||||
// 用 splice 交换:先替换 index-1 位置为 index 的值,再替换 index 位置为 temp
|
|
||||||
this.list.splice(index - 1, 1, this.list[index]); // 把 index-1 位置换成 index 的值
|
|
||||||
this.list.splice(index, 1, temp); // 把 index 位置换成原来 index-1 的值
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
// 保存要交换的两个元素
|
|
||||||
let temp2 = this.list[index + 1];
|
|
||||||
|
|
||||||
// 用 splice 交换:先替换 index-1 位置为 index 的值,再替换 index 位置为 temp
|
|
||||||
this.list.splice(index + 1, 1, this.list[index]); // 把 index-1 位置换成 index 的值
|
|
||||||
this.list.splice(index, 1, temp2); // 把 index 位置换成原来 index-1 的值
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
this.crIndex = index
|
|
||||||
this.isReplace = true
|
|
||||||
this.showPop = true
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
this.crIndex = index
|
|
||||||
this.showPop = true
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
addContent(id) {
|
|
||||||
switch (id) {
|
|
||||||
case 1:
|
|
||||||
let obj = {
|
|
||||||
value: ``,
|
|
||||||
type: 1,
|
|
||||||
};
|
|
||||||
if (this.crIndex!=null) {
|
|
||||||
if(this.isReplace){
|
|
||||||
this.replaceAtIndex(this.list, this.crIndex, obj);
|
|
||||||
}else{
|
|
||||||
this.insertAfterIndex(this.list, this.crIndex, obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
this.list.push(obj);
|
|
||||||
}
|
|
||||||
this.showPop = false
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
this.showTextarea = true
|
|
||||||
this.showPop = false
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
this.chooseAvatar(3)
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
this.chooseAvatar(4)
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async chooseAvatar(type) {
|
|
||||||
try {
|
|
||||||
const systemInfo = uni.getSystemInfoSync()
|
|
||||||
if(systemInfo.platform === 'ios') {
|
|
||||||
this.openCamera(type);
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 根据类型确定权限字符串和提示文本
|
|
||||||
const permissionKey = type === 3 ? 'photo_library' : 'camera';
|
|
||||||
const permissionAndroid = type === 3
|
|
||||||
? 'android.permission.READ_EXTERNAL_STORAGE,android.permission.WRITE_EXTERNAL_STORAGE'
|
|
||||||
: 'android.permission.CAMERA';
|
|
||||||
const dialogTitle = type === 3 ? '相册权限申请' : '相机权限申请';
|
|
||||||
const dialogContent = type === 3
|
|
||||||
? '我们需要访问您的相册以完成图片添加'
|
|
||||||
: '我们需要访问您的相机以完成图片拍摄';
|
|
||||||
const nowQer = type === 3 ? 'xc' : 'xj';
|
|
||||||
|
|
||||||
// 检查是否是首次请求(用于控制自定义视图显示)
|
|
||||||
const firstRequest = !plus.storage.getItem(`perm_${permissionKey}`);
|
|
||||||
if (firstRequest) {
|
|
||||||
this.isShowPer = true;
|
|
||||||
this.nowQer = nowQer;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. 检查权限
|
|
||||||
const { granted } = await permissionUtils.checkPermission(permissionKey, dialogContent);
|
|
||||||
if (granted) {
|
|
||||||
this.isShowPer = false;
|
|
||||||
this.openCamera(type);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 隐藏自定义视图,显示系统风格弹窗
|
|
||||||
this.isShowPer = false;
|
this.isShowPer = false;
|
||||||
const confirm = await this.showPermissionDialog(dialogTitle, dialogContent);
|
this.openCamera(type);
|
||||||
if (!confirm) return;
|
return;
|
||||||
|
|
||||||
// 3. 请求权限
|
|
||||||
const result = await permissionUtils.requestPermission(permissionKey, dialogContent);
|
|
||||||
if (result) {
|
|
||||||
this.openCamera(type);
|
|
||||||
} else {
|
|
||||||
locationService.openAppSettings();
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('权限处理出错:', error);
|
|
||||||
uni.showToast({
|
|
||||||
title: '权限检查失败',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
// 显示权限说明弹窗
|
|
||||||
showPermissionDialog(title, content) {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
uni.showModal({
|
|
||||||
title,
|
|
||||||
content,
|
|
||||||
confirmText: '去开启',
|
|
||||||
success(res) {
|
|
||||||
resolve(res.confirm);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// 打开相机/相册
|
|
||||||
async openCamera(type) {
|
|
||||||
this.isShowPer = false;
|
|
||||||
let result = null
|
|
||||||
if (type == 3) {
|
|
||||||
result = await uploadImage.handleUpload(this.$store.state.sjInfo.id ? this.$store.state.sjInfo.id :
|
|
||||||
Math.floor(Math.random() * 1000), 1, ['album']);
|
|
||||||
} else {
|
|
||||||
result = await uploadImage.handleUpload(this.$store.state.sjInfo.id ? this.$store.state.sjInfo.id :
|
|
||||||
Math.floor(Math.random() * 1000), 1, ['camera']);
|
|
||||||
}
|
|
||||||
uni.hideLoading();
|
|
||||||
// this.userInfo.head_photo = result;
|
|
||||||
let obj = {
|
|
||||||
value: result,
|
|
||||||
type: type,
|
|
||||||
};
|
|
||||||
if (this.crIndex!=null) {
|
|
||||||
if(this.isReplace){
|
|
||||||
this.replaceAtIndex(this.list, this.crIndex, obj);
|
|
||||||
}else{
|
|
||||||
this.insertAfterIndex(this.list, this.crIndex, obj);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.list.push(obj);
|
|
||||||
}
|
|
||||||
this.showPop = false
|
|
||||||
},
|
|
||||||
|
|
||||||
|
// 2. 隐藏自定义视图,显示系统风格弹窗
|
||||||
|
this.isShowPer = false;
|
||||||
|
const confirm = await this.showPermissionDialog(dialogTitle, dialogContent);
|
||||||
|
if (!confirm) return;
|
||||||
|
|
||||||
|
// 3. 请求权限
|
||||||
|
const result = await permissionUtils.requestPermission(permissionKey, dialogContent);
|
||||||
|
if (result) {
|
||||||
|
this.openCamera(type);
|
||||||
|
} else {
|
||||||
|
locationService.openAppSettings();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('权限处理出错:', error);
|
||||||
|
uni.showToast({
|
||||||
|
title: '权限检查失败',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
// 显示权限说明弹窗
|
||||||
|
showPermissionDialog(title, content) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
uni.showModal({
|
||||||
|
title,
|
||||||
|
content,
|
||||||
|
confirmText: '去开启',
|
||||||
|
success(res) {
|
||||||
|
resolve(res.confirm);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 打开相机/相册
|
||||||
|
async openCamera(type) {
|
||||||
|
this.isShowPer = false;
|
||||||
|
let result = null
|
||||||
|
if (type == 3) {
|
||||||
|
result = await uploadImage.handleUpload(this.$store.state.sjInfo.id ? this.$store.state.sjInfo.id :
|
||||||
|
Math.floor(Math.random() * 1000), 1, ['album']);
|
||||||
|
} else {
|
||||||
|
result = await uploadImage.handleUpload(this.$store.state.sjInfo.id ? this.$store.state.sjInfo.id :
|
||||||
|
Math.floor(Math.random() * 1000), 1, ['camera']);
|
||||||
|
}
|
||||||
|
uni.hideLoading();
|
||||||
|
// this.userInfo.head_photo = result;
|
||||||
|
let obj = {
|
||||||
|
value: result,
|
||||||
|
type: type,
|
||||||
|
};
|
||||||
|
if (this.crIndex != null) {
|
||||||
|
if (this.isReplace) {
|
||||||
|
this.replaceAtIndex(this.list, this.crIndex, obj);
|
||||||
|
} else {
|
||||||
|
this.insertAfterIndex(this.list, this.crIndex, obj);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.list.push(obj);
|
||||||
|
}
|
||||||
|
this.showPop = false
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
.addTextAndImg {
|
.addTextAndImg {
|
||||||
padding-bottom: 110rpx;
|
padding-bottom: 110rpx;
|
||||||
.firstAdd {
|
|
||||||
position: fixed;
|
|
||||||
left: 50%;
|
|
||||||
top: 50%;
|
|
||||||
transform: translate3d(-50%, -50%, 0);
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
.firstAdd-garden {
|
.firstAdd {
|
||||||
|
position: fixed;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate3d(-50%, -50%, 0);
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.firstAdd-garden {
|
||||||
|
width: 200rpx;
|
||||||
|
height: 200rpx;
|
||||||
|
border: 3px solid #000;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-bottom: 100rpx;
|
||||||
|
|
||||||
|
.firstAdd-garden-img {
|
||||||
width: 200rpx;
|
width: 200rpx;
|
||||||
height: 200rpx;
|
|
||||||
border: 3px solid #000;
|
|
||||||
border-radius: 50%;
|
|
||||||
margin-bottom: 100rpx;
|
|
||||||
.firstAdd-garden-img{
|
|
||||||
width: 200rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.firstAdd-text {
|
|
||||||
font-size: 32rpx;
|
|
||||||
text-align: center;
|
|
||||||
white-space: nowrap;
|
|
||||||
width: 750rpx;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.addTextAndImg-content {
|
.firstAdd-text {
|
||||||
.addTextAndImg-content-card {
|
font-size: 32rpx;
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 750rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.addTextAndImg-content-card-btns {
|
.addTextAndImg-content {
|
||||||
|
.addTextAndImg-content-card {
|
||||||
|
|
||||||
|
.addTextAndImg-content-card-btns {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10rpx;
|
||||||
|
background: #f5f5f5;
|
||||||
|
padding-bottom: 10rpx;
|
||||||
|
|
||||||
|
.addTextAndImg-content-card-btns-btn {
|
||||||
|
color: rgba(232, 16, 30, 1);
|
||||||
|
border: 2rpx solid rgba(232, 16, 30, 1);
|
||||||
|
width: 80rpx;
|
||||||
|
height: 50rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: nowrap;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
justify-content: center;
|
||||||
gap: 10rpx;
|
|
||||||
background: #f5f5f5;
|
|
||||||
padding-bottom: 10rpx;
|
|
||||||
|
|
||||||
.addTextAndImg-content-card-btns-btn {
|
|
||||||
color: rgba(232, 16, 30, 1);
|
|
||||||
border: 2rpx solid rgba(232, 16, 30, 1);
|
|
||||||
width: 80rpx;
|
|
||||||
height: 50rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.addTextAndImg-content-card-text {
|
|
||||||
padding: 20rpx;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.pop-cards {
|
.addTextAndImg-content-card-text {
|
||||||
z-index: 10;
|
padding: 20rpx;
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: #fff;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
gap: 63rpx;
|
|
||||||
padding: 68rpx 40rpx;
|
|
||||||
|
|
||||||
.pop-cards-card {
|
|
||||||
.pop-cards-card-img {
|
|
||||||
width: 120rpx;
|
|
||||||
height: 120rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pop-cards-card-text {
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #333333;
|
|
||||||
line-height: 40rpx;
|
|
||||||
text-align: center;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pop-textarea {
|
|
||||||
background-color: #fff;
|
|
||||||
margin: 200rpx 20rpx 20rpx 20rpx;
|
|
||||||
border-radius: 20rpx;
|
|
||||||
padding: 20rpx;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.permission.transform {
|
.pop-cards {
|
||||||
top: calc(var(--status-bar-height) + 88rpx + 20rpx);
|
z-index: 10;
|
||||||
opacity: 1;
|
position: absolute;
|
||||||
visibility: visible;
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #fff;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
gap: 63rpx;
|
||||||
|
padding: 68rpx 40rpx;
|
||||||
|
|
||||||
|
.pop-cards-card {
|
||||||
|
.pop-cards-card-img {
|
||||||
|
width: 120rpx;
|
||||||
|
height: 120rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pop-cards-card-text {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333333;
|
||||||
|
line-height: 40rpx;
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-buttons {
|
.pop-textarea {
|
||||||
position: fixed;
|
background-color: #fff;
|
||||||
left: 0;
|
margin: 200rpx 20rpx 20rpx 20rpx;
|
||||||
right: 0;
|
border-radius: 20rpx;
|
||||||
bottom: 0;
|
padding: 20rpx;
|
||||||
background-color: #fff;
|
}
|
||||||
height: 100rpx;
|
}
|
||||||
padding: 40rpx 0 64rpx;
|
|
||||||
box-sizing: border-box;
|
.permission.transform {
|
||||||
display: flex;
|
top: calc(var(--status-bar-height) + 88rpx + 20rpx);
|
||||||
flex-flow: row nowrap;
|
opacity: 1;
|
||||||
justify-content: space-between;
|
visibility: visible;
|
||||||
align-items: center;
|
}
|
||||||
.btn-submit {
|
|
||||||
|
.bottom-buttons {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #fff;
|
||||||
|
height: 100rpx;
|
||||||
|
padding: 40rpx 0 64rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.btn-submit {
|
||||||
background-color: rgba(232, 16, 30, 1);
|
background-color: rgba(232, 16, 30, 1);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
width: 334rpx;
|
width: 334rpx;
|
||||||
height: 78rpx;
|
height: 78rpx;
|
||||||
line-height: 78rpx;
|
line-height: 78rpx;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
border-radius: 78rpx;
|
border-radius: 78rpx;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -225,7 +225,7 @@
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="form-item-image-box box-cont">
|
<view class="form-item-image-box box-cont">
|
||||||
<text class="label required">行业资质</text>
|
<text class="label">行业资质</text>
|
||||||
<view class="image-list">
|
<view class="image-list">
|
||||||
<view class="image-item" v-for="(img, index) in displayData.qualifications_sector"
|
<view class="image-item" v-for="(img, index) in displayData.qualifications_sector"
|
||||||
:key="'industry-' + index">
|
:key="'industry-' + index">
|
||||||
|
|
|
||||||
|
|
@ -152,20 +152,58 @@
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 项目说明 -->
|
<!-- 项目说明 -->
|
||||||
<view class="notice-section">
|
<view class="notice-section" v-if="hasProjectInfo">
|
||||||
<view class="section-title">
|
<view class="section-title">
|
||||||
<text class="section-tit">项目说明</text>
|
<text class="section-tit">项目说明</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="notice-content">
|
<view class="notice-content">
|
||||||
<view class="notice-cont">
|
<view class="notice-cont" v-if="serviceInfo.server_time">
|
||||||
<text class="notice-title">
|
<text class="notice-title">
|
||||||
服务时长:
|
服务时长:
|
||||||
</text>
|
</text>
|
||||||
<text class="notice-text">
|
<text class="notice-text">
|
||||||
{{serviceInfo.server_time}}分钟
|
{{serviceInfo.server_time}}分钟
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
<view class="notice-cont" v-for="(item,i) in visibleDetailList" :key="'detail-' + i">
|
||||||
|
<text class="notice-title">
|
||||||
|
{{item.title}}:
|
||||||
|
</text>
|
||||||
|
<text class="notice-text">
|
||||||
|
{{item.text}}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="notice-cont" v-for="(item,i) in serviceInfo.detail">
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="notice-section" v-for="(section, sectionIndex) in dynamicIntroSections"
|
||||||
|
:key="'dynamic-section-' + sectionIndex">
|
||||||
|
<view class="section-title">
|
||||||
|
<text class="section-tit">{{section.title}}</text>
|
||||||
|
</view>
|
||||||
|
<view class="notice-content">
|
||||||
|
<view class="notice-cont dynamic-notice-cont" v-for="(item, itemIndex) in section.fields"
|
||||||
|
:key="'dynamic-field-' + sectionIndex + '-' + itemIndex">
|
||||||
|
<text class="notice-title">{{item.title}}:</text>
|
||||||
|
<view class="dynamic-media-list" v-if="item.images.length">
|
||||||
|
<image class="dynamic-image" v-for="(img, imgIndex) in item.images"
|
||||||
|
:key="'dynamic-img-' + imgIndex" :src="img" mode="aspectFill"
|
||||||
|
@click="previewImage(item.images, imgIndex)"></image>
|
||||||
|
</view>
|
||||||
|
<view class="dynamic-media-list" v-else-if="item.videos.length">
|
||||||
|
<video class="dynamic-video" v-for="(video, videoIndex) in item.videos"
|
||||||
|
:key="'dynamic-video-' + videoIndex" :src="video" controls></video>
|
||||||
|
</view>
|
||||||
|
<text class="notice-text" v-else>{{item.displayValue}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- 步骤 -->
|
||||||
|
<view class="notice-section" v-if="visibleProcessList.length">
|
||||||
|
<view class="section-title">
|
||||||
|
<text class="section-tit">项目步骤</text>
|
||||||
|
</view>
|
||||||
|
<view class="notice-content">
|
||||||
|
<view class="notice-cont" v-for="(item,i) in visibleProcessList" :key="'process-' + i">
|
||||||
<text class="notice-title">
|
<text class="notice-title">
|
||||||
{{item.title}}:
|
{{item.title}}:
|
||||||
</text>
|
</text>
|
||||||
|
|
@ -174,25 +212,9 @@
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
|
||||||
<!-- 步骤 -->
|
|
||||||
<view class="notice-section">
|
|
||||||
<view class="section-title">
|
|
||||||
<text class="section-tit">项目步骤</text>
|
|
||||||
</view>
|
</view>
|
||||||
<view class="notice-content">
|
<!-- 订购须知 -->
|
||||||
<view class="notice-cont" v-for="(item,i) in serviceInfo.process">
|
<view class="notice-section" v-if="purchaseNotesText">
|
||||||
<text class="notice-title">
|
|
||||||
{{item.title}}:
|
|
||||||
</text>
|
|
||||||
<text class="notice-text">
|
|
||||||
{{item.text}}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<!-- 订购须知 -->
|
|
||||||
<view class="notice-section">
|
|
||||||
<view class="section-title">
|
<view class="section-title">
|
||||||
<text class="section-tit">订购须知</text>
|
<text class="section-tit">订购须知</text>
|
||||||
<!-- <view class="section-tit-right">
|
<!-- <view class="section-tit-right">
|
||||||
|
|
@ -201,9 +223,9 @@
|
||||||
</view> -->
|
</view> -->
|
||||||
</view>
|
</view>
|
||||||
<view class="notice-content">
|
<view class="notice-content">
|
||||||
<text class="notice-text">
|
<text class="notice-text">
|
||||||
{{serviceInfo.purchasenotes}}
|
{{purchaseNotesText}}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
|
@ -283,9 +305,30 @@
|
||||||
tab: '超出期待',
|
tab: '超出期待',
|
||||||
type: '双色法式美甲'
|
type: '双色法式美甲'
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLoad(option) {
|
computed: {
|
||||||
|
visibleDetailList() {
|
||||||
|
return (this.serviceInfo.detail || []).filter((item) => {
|
||||||
|
return item && item.title && item.text;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
visibleProcessList() {
|
||||||
|
return (this.serviceInfo.process || []).filter((item) => {
|
||||||
|
return item && item.text;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
hasProjectInfo() {
|
||||||
|
return !!this.serviceInfo.server_time || this.visibleDetailList.length > 0;
|
||||||
|
},
|
||||||
|
purchaseNotesText() {
|
||||||
|
return this.serviceInfo.purchasenotes || this.serviceInfo.purchase_notes || '';
|
||||||
|
},
|
||||||
|
dynamicIntroSections() {
|
||||||
|
return this.buildDynamicIntroSections(this.serviceInfo.intro);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoad(option) {
|
||||||
// Android可能需要延迟加载
|
// Android可能需要延迟加载
|
||||||
request.post('/sj/serverdetail', {
|
request.post('/sj/serverdetail', {
|
||||||
id: option.id
|
id: option.id
|
||||||
|
|
@ -315,9 +358,89 @@
|
||||||
onHide() {
|
onHide() {
|
||||||
this.showVideo = false;
|
this.showVideo = false;
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// 获取手艺人详情
|
parseMaybeJson(raw) {
|
||||||
getSyrDetail(id) {
|
if (!raw) return [];
|
||||||
|
if (typeof raw !== 'string') return raw;
|
||||||
|
try {
|
||||||
|
return JSON.parse(raw);
|
||||||
|
} catch (e) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getDynamicPlainValue(value) {
|
||||||
|
if (value === undefined || value === null) return '';
|
||||||
|
if (typeof value !== 'object') return String(value);
|
||||||
|
return value.label || value.title || value.name || value.text || value.value || '';
|
||||||
|
},
|
||||||
|
normalizeDynamicIntroField(item) {
|
||||||
|
if (!item) return null;
|
||||||
|
const type = item.type || '';
|
||||||
|
const value = item.value;
|
||||||
|
const images = [];
|
||||||
|
const videos = [];
|
||||||
|
let displayValue = '';
|
||||||
|
if (value === undefined || value === null || value === '') return null;
|
||||||
|
if (Array.isArray(value) && value.length === 0) return null;
|
||||||
|
|
||||||
|
if (type === 'image_multi') {
|
||||||
|
(value || []).forEach((img) => {
|
||||||
|
const url = typeof img === 'string' ? img : (img && (img.url || img.path));
|
||||||
|
if (url) images.push(url);
|
||||||
|
});
|
||||||
|
} else if (type === 'video') {
|
||||||
|
const list = Array.isArray(value) ? value : [value];
|
||||||
|
list.forEach((video) => {
|
||||||
|
const url = typeof video === 'string' ? video : (video && (video.url || video.path));
|
||||||
|
if (url) videos.push(url);
|
||||||
|
});
|
||||||
|
} else if (type === 'service_step' && Array.isArray(value)) {
|
||||||
|
displayValue = value.map((step, index) => {
|
||||||
|
const text = this.getDynamicPlainValue(step);
|
||||||
|
return text ? `${index + 1}. ${text}` : '';
|
||||||
|
}).filter(Boolean).join(';');
|
||||||
|
} else if (Array.isArray(value)) {
|
||||||
|
displayValue = value.map((val) => this.getDynamicPlainValue(val)).filter(Boolean).join('、');
|
||||||
|
} else if (typeof value === 'object') {
|
||||||
|
displayValue = this.getDynamicPlainValue(value);
|
||||||
|
} else {
|
||||||
|
displayValue = String(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!displayValue && images.length === 0 && videos.length === 0) return null;
|
||||||
|
return {
|
||||||
|
title: item.title || '补充信息',
|
||||||
|
type,
|
||||||
|
displayValue,
|
||||||
|
images,
|
||||||
|
videos,
|
||||||
|
sectionTitle: item.sectionTitle || '',
|
||||||
|
sectionId: item.sectionId || '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
buildDynamicIntroSections(rawIntro) {
|
||||||
|
const intro = this.parseMaybeJson(rawIntro);
|
||||||
|
if (!Array.isArray(intro)) return [];
|
||||||
|
const groups = [];
|
||||||
|
const groupMap = {};
|
||||||
|
intro.forEach((item) => {
|
||||||
|
const field = this.normalizeDynamicIntroField(item);
|
||||||
|
if (!field) return;
|
||||||
|
const title = field.sectionTitle || (field.type === 'service_step' ? '项目步骤' : '更多说明');
|
||||||
|
const key = field.sectionId || title;
|
||||||
|
if (!groupMap[key]) {
|
||||||
|
groupMap[key] = {
|
||||||
|
title,
|
||||||
|
fields: [],
|
||||||
|
};
|
||||||
|
groups.push(groupMap[key]);
|
||||||
|
}
|
||||||
|
groupMap[key].fields.push(field);
|
||||||
|
});
|
||||||
|
return groups.filter((section) => section.fields.length > 0);
|
||||||
|
},
|
||||||
|
// 获取手艺人详情
|
||||||
|
getSyrDetail(id) {
|
||||||
request.post('/sj/syrdetail', {
|
request.post('/sj/syrdetail', {
|
||||||
id
|
id
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
|
|
@ -808,13 +931,33 @@
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notice-text {
|
.notice-text {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
font-size: 26rpx;
|
font-size: 26rpx;
|
||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 底部按钮 */
|
.dynamic-notice-cont {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dynamic-media-list {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dynamic-image,
|
||||||
|
.dynamic-video {
|
||||||
|
width: 160rpx;
|
||||||
|
height: 160rpx;
|
||||||
|
margin-right: 12rpx;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
background-color: #F6F6F6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 底部按钮 */
|
||||||
.bottom-bar {
|
.bottom-bar {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|
@ -1012,4 +1155,4 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/* #endif */
|
/* #endif */
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -331,8 +331,48 @@
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 获取用户信息(原有,未变)
|
// 兼容接口里门店地址字段的不同命名
|
||||||
getUserInfo() {
|
pickFirst(data, fields) {
|
||||||
|
for (const field of fields) {
|
||||||
|
const value = data && data[field];
|
||||||
|
if (value !== undefined && value !== null && String(value).trim() !== '') {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
|
||||||
|
normalizeStoreInfo(data = {}) {
|
||||||
|
return {
|
||||||
|
head_photo: this.pickFirst(data, ['head_photo', 'shop_head_photo']),
|
||||||
|
name: this.pickFirst(data, ['name', 'shop_name']),
|
||||||
|
dependency: this.pickFirst(data, ['dependency', 'shop_dependency', 'uscc_dependency']),
|
||||||
|
address: this.pickFirst(data, ['address', 'shop_address', 'uscc_address']),
|
||||||
|
dependency_code: this.pickFirst(data, ['dependency_code', 'shop_dependency_code', 'uscc_area']),
|
||||||
|
dependency_province: this.pickFirst(data, ['dependency_province', 'shop_dependency_province', 'uscc_province']),
|
||||||
|
dependency_city: this.pickFirst(data, ['dependency_city', 'shop_dependency_city', 'uscc_city']),
|
||||||
|
longitude: this.pickFirst(data, ['longitude', 'shop_longitude']),
|
||||||
|
latitude: this.pickFirst(data, ['latitude', 'shop_latitude'])
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
mergeStoreInfoWithOriginal(data = {}) {
|
||||||
|
const storeInfo = this.normalizeStoreInfo(data);
|
||||||
|
return {
|
||||||
|
head_photo: storeInfo.head_photo || this.originalData.head_photo,
|
||||||
|
name: storeInfo.name || this.originalData.name,
|
||||||
|
dependency: storeInfo.dependency || this.originalData.dependency,
|
||||||
|
address: storeInfo.address || this.originalData.address,
|
||||||
|
dependency_code: storeInfo.dependency_code || this.originalData.dependency_code,
|
||||||
|
dependency_province: storeInfo.dependency_province || this.originalData.dependency_province,
|
||||||
|
dependency_city: storeInfo.dependency_city || this.originalData.dependency_city,
|
||||||
|
longitude: storeInfo.longitude || this.originalData.longitude,
|
||||||
|
latitude: storeInfo.latitude || this.originalData.latitude
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取用户信息
|
||||||
|
getUserInfo() {
|
||||||
console.log('开始获取用户信息...');
|
console.log('开始获取用户信息...');
|
||||||
const type = 3;
|
const type = 3;
|
||||||
|
|
||||||
|
|
@ -347,23 +387,13 @@
|
||||||
|
|
||||||
this.userId = userData.id;
|
this.userId = userData.id;
|
||||||
|
|
||||||
this.originalData = {
|
this.originalData = this.normalizeStoreInfo(userData);
|
||||||
head_photo: userData.head_photo || '',
|
|
||||||
name: userData.name || '',
|
this.dependency_code = this.originalData.dependency_code || '';
|
||||||
dependency: userData.dependency || '',
|
this.dependency_province = this.originalData.dependency_province || '';
|
||||||
address: userData.address || '',
|
this.dependency_city = this.originalData.dependency_city || '';
|
||||||
dependency_code: userData.dependency_code || '',
|
this.longitude = this.originalData.longitude || '';
|
||||||
dependency_province: userData.dependency_province || '',
|
this.latitude = this.originalData.latitude || '';
|
||||||
dependency_city: userData.dependency_city || '',
|
|
||||||
longitude: userData.longitude || '',
|
|
||||||
latitude: userData.latitude || ''
|
|
||||||
};
|
|
||||||
|
|
||||||
this.dependency_code = userData.dependency_code || '';
|
|
||||||
this.dependency_province = userData.dependency_province || '';
|
|
||||||
this.dependency_city = userData.dependency_city || '';
|
|
||||||
this.longitude = userData.longitude || '';
|
|
||||||
this.latitude = userData.latitude || '';
|
|
||||||
|
|
||||||
this.pendingData = {
|
this.pendingData = {
|
||||||
...this.originalData
|
...this.originalData
|
||||||
|
|
@ -444,18 +474,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.auditStatus === 1 || this.auditStatus === 3) {
|
if (this.auditStatus === 1 || this.auditStatus === 3) {
|
||||||
this.pendingData = {
|
this.pendingData = this.mergeStoreInfoWithOriginal(detail);
|
||||||
head_photo: detail.head_photo || this.originalData.head_photo,
|
|
||||||
name: detail.name || this.originalData.name,
|
|
||||||
dependency: detail.dependency || this.originalData.dependency,
|
|
||||||
address: detail.address || this.originalData.address,
|
|
||||||
dependency_code: detail.dependency_code || this.originalData.dependency_code,
|
|
||||||
dependency_province: detail.dependency_province || this.originalData
|
|
||||||
.dependency_province,
|
|
||||||
dependency_city: detail.dependency_city || this.originalData.dependency_city,
|
|
||||||
longitude: detail.longitude || this.originalData.longitude,
|
|
||||||
latitude: detail.latitude || this.originalData.latitude
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.auditStatus === 2) {
|
if (this.auditStatus === 2) {
|
||||||
|
|
@ -1679,4 +1698,4 @@
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
</serviecFirstTab>
|
</serviecFirstTab>
|
||||||
|
|
||||||
<view class="service-list">
|
<view class="service-list">
|
||||||
<view class="service-item flex-row-center-between" v-for="(item, index) in getList" :key="index">
|
<view class="service-item flex-row-center-between" v-for="(item, index) in getList" :key="index" v-if="(item.is_choice && isBack!=0) || isBack==0">
|
||||||
<view class="item-left flex-row-start">
|
<view class="item-left flex-row-start">
|
||||||
<image class="item-left-photo" :src="item.photo[0]" mode="aspectFill"></image>
|
<image class="item-left-photo" :src="item.photo[0]" mode="aspectFill"></image>
|
||||||
<view class="item-left-texts">
|
<view class="item-left-texts">
|
||||||
|
|
|
||||||
|
|
@ -233,6 +233,13 @@
|
||||||
},
|
},
|
||||||
// 开始服务
|
// 开始服务
|
||||||
startOrder(e, order) {
|
startOrder(e, order) {
|
||||||
|
if (order.team_buy_order_state == 1) {
|
||||||
|
uni.showToast({
|
||||||
|
title: "订单当前处于拼团中,服务将在拼团完成后才开启~",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
return
|
||||||
|
}
|
||||||
this.showInput = true;
|
this.showInput = true;
|
||||||
this.handelOrder = order;
|
this.handelOrder = order;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 486 KiB |
|
After Width: | Height: | Size: 509 KiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 3.4 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 10 MiB |
|
After Width: | Height: | Size: 18 MiB |
|
|
@ -228,8 +228,9 @@ const getOssConfig = async (type) => {
|
||||||
const uploadToOss = (file, ossConfig, name, uploadType) => {
|
const uploadToOss = (file, ossConfig, name, uploadType) => {
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// #ifdef H5
|
let formData;
|
||||||
const formData = new FormData()
|
// #ifdef H5
|
||||||
|
formData = new FormData()
|
||||||
formData.append('name', name);
|
formData.append('name', name);
|
||||||
formData.append('policy', ossConfig.policy);
|
formData.append('policy', ossConfig.policy);
|
||||||
formData.append('OSSAccessKeyId', ossConfig.ossAccessKeyId);
|
formData.append('OSSAccessKeyId', ossConfig.ossAccessKeyId);
|
||||||
|
|
@ -247,15 +248,15 @@ const uploadToOss = (file, ossConfig, name, uploadType) => {
|
||||||
reject(new Error(`上传失败: ${res.statusCode}`))
|
reject(new Error(`上传失败: ${res.statusCode}`))
|
||||||
})
|
})
|
||||||
// #endif
|
// #endif
|
||||||
// #ifdef APP-PLUS || MP-WEIXIN
|
// #ifdef APP-PLUS || MP-WEIXIN
|
||||||
let formData = {};
|
formData = {};
|
||||||
formData.name = name;
|
formData.name = name;
|
||||||
formData.policy = ossConfig.policy;
|
formData.policy = ossConfig.policy;
|
||||||
formData.OSSAccessKeyId = ossConfig.ossAccessKeyId;
|
formData.OSSAccessKeyId = ossConfig.ossAccessKeyId;
|
||||||
formData.success_action_status = '200';
|
formData.success_action_status = '200';
|
||||||
formData.signature = ossConfig.signature;
|
formData.signature = ossConfig.signature;
|
||||||
formData.key = ossConfig.dir + name;
|
formData.key = ossConfig.dir + name;
|
||||||
formData.file = file;
|
formData.file = file;
|
||||||
|
|
||||||
let path;
|
let path;
|
||||||
if (uploadType == 'image') {
|
if (uploadType == 'image') {
|
||||||
|
|
@ -314,4 +315,4 @@ const uploadToOss = (file, ossConfig, name, uploadType) => {
|
||||||
}
|
}
|
||||||
export default {
|
export default {
|
||||||
handleUpload
|
handleUpload
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export const APP_BACK_BASE_URL = "http://test.mrrwlkj.top";
|
||||||
|
|
@ -120,14 +120,14 @@ const request = async (options, isUpdate) => {
|
||||||
|
|
||||||
if (process.env.NODE_ENV === "development") {
|
if (process.env.NODE_ENV === "development") {
|
||||||
baseURL = url; // 发布到生产环境时,此处代码会被摇树移除掉。
|
baseURL = url; // 发布到生产环境时,此处代码会被摇树移除掉。
|
||||||
// baseURL = "http://test.mrrwlkj.top"; // 发布到生产环境时,此处代码会被摇树移除掉。
|
baseURL = "http://116.63.163.121:93"; // 发布到生产环境时,此处代码会被摇树移除掉。
|
||||||
//baseURL = 'http://dev.mrrwlkj.top'; // 发布到生产环境时,此处代码会被摇树移除掉。
|
// baseURL = "http://116.63.163.121:96"; // 发布到生产环境时,此处代码会被摇树移除掉。
|
||||||
baseURL = "https://app.mrrweb.com.cn";
|
// baseURL = "https://app.mrrweb.com.cn";
|
||||||
} else {
|
} else {
|
||||||
baseURL = url; // 发布到生产环境时,此处代码会被摇树移除掉。
|
baseURL = url; // 发布到生产环境时,此处代码会被摇树移除掉。
|
||||||
// baseURL = "http://test.mrrwlkj.top"; // 发布到生产环境时,此处代码会被摇树移除掉。
|
baseURL = "http://116.63.163.121:93"; // 发布到生产环境时,此处代码会被摇树移除掉。
|
||||||
//baseURL = 'http://dev.mrrwlkj.top'; // 发布到生产环境时,此处代码会被摇树移除掉。
|
// baseURL = "http://116.63.163.121:96"; // 发布到生产环境时,此处代码会被摇树移除掉。
|
||||||
baseURL = "https://app.mrrweb.com.cn";
|
// baseURL = "https://app.mrrweb.com.cn";
|
||||||
|
|
||||||
console.log("生产环境");
|
console.log("生产环境");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -148,8 +148,9 @@ const getOssConfig = async (type)=> {
|
||||||
const uploadToOss = (file, ossConfig, name)=> {
|
const uploadToOss = (file, ossConfig, name)=> {
|
||||||
console.log(ossConfig)
|
console.log(ossConfig)
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// #ifdef H5
|
let formData;
|
||||||
const formData = new FormData()
|
// #ifdef H5
|
||||||
|
formData = new FormData()
|
||||||
formData.append('name',name);
|
formData.append('name',name);
|
||||||
formData.append('policy', ossConfig.policy);
|
formData.append('policy', ossConfig.policy);
|
||||||
formData.append('OSSAccessKeyId', ossConfig.ossAccessKeyId);
|
formData.append('OSSAccessKeyId', ossConfig.ossAccessKeyId);
|
||||||
|
|
@ -165,7 +166,7 @@ const uploadToOss = (file, ossConfig, name)=> {
|
||||||
})
|
})
|
||||||
// #endif
|
// #endif
|
||||||
// #ifdef APP-PLUS || MP-WEIXIN
|
// #ifdef APP-PLUS || MP-WEIXIN
|
||||||
let formData = {};
|
formData = {};
|
||||||
formData.name = name;
|
formData.name = name;
|
||||||
formData.policy = ossConfig.policy;
|
formData.policy = ossConfig.policy;
|
||||||
formData.OSSAccessKeyId = ossConfig.ossAccessKeyId;
|
formData.OSSAccessKeyId = ossConfig.ossAccessKeyId;
|
||||||
|
|
@ -277,4 +278,4 @@ const uploadToOss = (file, ossConfig, name)=> {
|
||||||
}
|
}
|
||||||
export default {
|
export default {
|
||||||
handleUpload
|
handleUpload
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
|
|
||||||
|
import { APP_BACK_BASE_URL } from './appBackBase.js'
|
||||||
|
|
||||||
let VersionCode = '';
|
let VersionCode = '';
|
||||||
let DeviceType = '';
|
let DeviceType = '';
|
||||||
let platform = '';
|
let platform = '';
|
||||||
|
|
@ -60,30 +62,6 @@ export function setVersion() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getBaseUrl() {
|
export async function getBaseUrl() {
|
||||||
let version = uni.getStorageSync('setVersion')
|
uni.setStorageSync('baseUrl', APP_BACK_BASE_URL)
|
||||||
await uni.request({
|
return APP_BACK_BASE_URL
|
||||||
url: 'https://api.mrrwlkj.top/api/openPuc/getSjApiUrl', // 接口地址
|
}
|
||||||
method: 'GET', // 请求方法(默认 GET)
|
|
||||||
header: {
|
|
||||||
'Content-Type': 'application/json', // 请求头
|
|
||||||
'VersionCode': version.VersionCode,
|
|
||||||
'DeviceBrand': version.DeviceBrand,
|
|
||||||
'DeviceType': version.DeviceType
|
|
||||||
},
|
|
||||||
success: (res) => {
|
|
||||||
if (res.data.code == 200) {
|
|
||||||
uni.setStorageSync('baseUrl', res.data.data.url)
|
|
||||||
// store.commit('setBaseUrl', res.data.data.url)
|
|
||||||
}
|
|
||||||
return res.data.data.url;
|
|
||||||
console.log('请求成功', res.data);
|
|
||||||
},
|
|
||||||
fail: (err) => {
|
|
||||||
console.log('请求失败', err);
|
|
||||||
return 'http://60.247.146.5:93';
|
|
||||||
},
|
|
||||||
complete: () => {
|
|
||||||
console.log('请求完成(无论成功失败都会执行)');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
|
||||||