javascript
<template>
<ax-page class="privateCustom">
<gui-page :customHeader="true" ref="guipage">
<template #gHeader>
<aHeader title="个性定制" :showTitle="true" back="2">
</aHeader>
</template>
<template v-slot:gBody>
<view class="privateCustom-body">
<form @submit="submit">
<view class="gui-bg-white gui-dark-bg-level-3 gui-padding-x">
<view class="gui-form-item gui-border-b">
<text class="gui-form-label"
>{{ pageIndex == 0 ? '上传图片' : '已选画作' }} |</text
>
<view class="rowContentDiv">
<view v-if="pageIndex == 0">
<view
@tap="uploadImgClick"
class="uploadDiv"
v-if="!formData.image"
>
<image class="addImg" :src="addPImg" mode="scaleToFill" />
<text>图片</text>
</view>
<image
v-else
:src="formData.image"
class="uploadDiv"
mode="aspectFit"
@tap="uploadImgClick"
></image>
</view>
<image
:src="canvasSize.imgSrc || formData.image"
class="posterImg"
mode="aspectFit"
@tap="previewImage(canvasSize.imgSrc || formData.image)"
></image>
</view>
</view>
<!-- <view class="gui-form-item gui-border-b">
<text class="gui-form-label">画作名称 |</text>
<view class="frame-box content">
<input
type="text"
class="gui-form-input"
v-model="formData.name"
name="name"
placeholder="请输入内容"
/>
</view>
</view> -->
<view class="gui-form-item gui-border-b" v-if="pageIndex == 0">
<text class="gui-form-label">画框选择 |</text>
<view class="frame-box content">
<view
style="display: flex; align-items: center"
v-for="(item, index) in selectFrame"
:key="index"
:class="[
'frame-item',
{ 'frameImg-active': index == formData.frame }
]"
@tap="frameClick(index)"
>
<image
:src="$App.ossImage(item.url)"
class="frameImg"
mode="aspectFit"
/>
<text>{{ item.name }}</text>
</view>
</view>
</view>
<view class="gui-form-item gui-border-b" v-if="pageIndex == 0">
<text class="gui-form-label">选择画纸 |</text>
<view class="frame-box content">
<view
style="display: flex; align-items: center"
v-for="(item, index) in papers"
:key="index"
:class="[
'frame-item',
{ 'frameImg-active': index == formData.paper }
]"
@tap="paperClick(index)"
>
<image
:src="$App.ossImage(item.url)"
class="frameImg"
mode="aspectFit"
/>
<text>{{ item.name }}</text>
</view>
</view>
</view>
<view class="gui-form-item gui-border-b" v-if="pageIndex == 0">
<text class="gui-form-label">画框尺寸 |</text>
<view class="frame-box content">
<picker
@change="selectSize"
:value="currentSize"
range-key="key"
v-if="selectSizeMenu.length > 0"
:range="selectSizeMenu"
>
<view class="uni-input">{{
selectSizeMenu[currentSize].key
}}</view>
</picker>
</view>
</view>
<view
class="gui-form-item gui-border-b"
v-if="
currentSize == selectSizeMenu.length - 1 && pageIndex == 0
"
>
<text class="gui-form-label">自定义尺寸</text>
<view class="frame-box content">
<input
type="number"
class="gui-form-input"
v-model="formData.size"
name="size"
placeholder="最小10"
@blur="getImageSize"
:min="1"
/>
</view>
</view>
<view class="gui-form-item gui-border-b" v-if="pageIndex == 0">
<text class="gui-form-label">卡纸宽度 |</text>
<view class="frame-box content">
<picker
@change="selectWidths"
:value="formData.width"
range-key="w"
v-if="widths.length > 0"
:range="widths"
>
<view class="uni-input"
>{{ widths[formData.width].w }}cm</view
>
</picker>
</view>
</view>
<view class="gui-form-item gui-border-b" v-if="pageIndex == 0">
<text class="gui-form-label">选择数量 |</text>
<view class="frame-box content">
<gui-step-box
:inputClass="[
'gui-step-box-input',
'gui-border-radius',
'gui-bg-gray',
'gui-dark-bg-level-2'
]"
@change="stepChange"
:value="formData.num"
:minNum="1"
></gui-step-box>
</view>
</view>
<view class="gui-form-item gui-border-b" v-if="pageIndex == 0">
<text class="gui-form-label">指导价格 |</text>
<view class="price-box content">
<image :src="pricePImg" class="pricePImg" />
<text class="price-text">{{
Utils.amountS(formData.price)
}}</text>
</view>
</view>
<view class="gui-form-item gui-border-b" v-if="pageIndex == 1">
<text class="gui-form-label">作品备注 |</text>
<view class="frame-box content">
<input
type="text"
class="gui-form-input"
v-model="formData.mark"
name="mark"
placeholder="请输入备注"
/>
</view>
</view>
<view class="gui-form-item gui-border-b" v-if="pageIndex == 1">
<text class="gui-form-label">收货地址 |</text>
<view class="frame-box content" @tap="goAddressList">
<view
class="left-part"
v-if="Object.keys(formData.address).length > 0"
>
<view class="name">
{{ formData.address.recipient }}
{{ formData.address.phone }}
</view>
<view class="address-detail">
{{
`${formData.address.address}${formData.address.detail}`
}}
</view>
</view>
<view class="left-part" v-else>
<view class="name"> 请选择收货地址 </view>
</view>
<!-- <view class="right-part">
<text
class="gui-list-arrow-right gui-icons gui-color-gray-light"
></text
>
</view> -->
</view>
</view>
<view class="gui-form-item gui-border-b" v-if="pageIndex == 1">
<text class="gui-form-label">提示 |</text>
<view class="frame-box content" style="color: red">
定制商品,非商品自身问题,暂不支持退换服务,请您在确认订单前仔细核对商品信息,如有任何疑问,请及时联系我们。感谢您的理解与支持!
</view>
</view>
</view>
<button
class="gui-button gui-bg-primary gui-noborder submitBtn"
formType="submit"
>
<text class="gui-color-white gui-button-text">{{
pageIndex == 0 ? '去下单' : '确认下单'
}}</text>
</button>
<view style="height: 60rpx"></view>
</form>
<view class="canvas-in">
<canvas
v-if="canvasSize.heightIn > 0"
:style="{
width: canvasSize.widthIn + 'px',
height: canvasSize.heightIn + 'px',
opacity: 0
}"
canvas-id="graceCanvas"
class="grace-canvas"
></canvas>
</view>
</view>
</template>
</gui-page>
</ax-page>
</template>
<script setup lang="ts">
import App from '@/script/module/App'
import PublicImg from '@/components/publicImage.vue'
import User from '@/script/module/User'
import { axj } from '@/script/sdk/ms-store'
import axConfig from '@/script/AxConfig'
import {
onBackPress,
onLoad,
onPageScroll,
onReachBottom,
onShow,
onUnload
} from '@dcloudio/uni-app'
import { computed, nextTick, onMounted, reactive, ref } from 'vue'
import Page from '@/script/module/Page'
import Utils from '@/script/util/Utils'
import Artist from '@/script/module/Artist'
import addPImg from '../images/addP.png'
import pricePImg from '../images/priceP.png'
import SdkActs from '@/script/module/SdkActs'
const pageIndex = ref(0)
const selectSizeMenu = ref([])
const sizeList = ref([])
const selectFrame = ref([])
const papers = ref([])
const widths = ref([])
const formData = reactive({
name: '',
mark: '',
image: '',
frame: 0,
num: 1,
price: <number | string>'',
address: User.state.address,
size: <any>10,
width: 0,
paper: 0
})
const currentSize = ref(0)
const customOrderCfg = ref(<axj.DtDCustomOrderCfg>undefined)
const imgInfo = ref({
width: 0,
height: 0,
aspectRatio: 0
})
const canvasSize = reactive({
widthIn: 0, // 自动计算转换为 px
heightIn: 0, // 自动计算转换为 px
bgColor: '#ffffff', // 背景颜色
bgImg: '',
contentImg: '',
imgSrc: '',
multiple: 1, // 将画布放大 2.0 - 2.9 倍(支持小数,过大app端会出现无法渲染的问题),保存的图片更清晰
bw: 0,
iw: 0,
ih: 0,
w: 0,
h: 0
})
const context = ref(<any>null)
const imagesPath = ref(<any>[])
// 选择图片
const uploadImgClick = () => {
uni.chooseImage({
count: 1,
extension: ['jpeg', 'jpg', 'png'],
success: (res) => {
console.log('[ getImageInfo ] >', res)
formData.image = res.tempFilePaths[0]
getImageInfo(res.tempFilePaths[0])
formData.name = res?.tempFiles[0]?.name || ''
}
})
}
// 获取图片信息
const getImageInfo = (tempFilePath) => {
uni.getImageInfo({
src: tempFilePath,
success: (info) => {
const width = info.width
const height = info.height
// 获取宽高比,用于自定义尺寸时候计算宽度
const aspectRatio = width / height
imgInfo.value = {
width: width,
height: height,
aspectRatio: aspectRatio
}
initSize()
customOrder()
}
})
}
// 自定义尺寸重绘画布
const getImageSize = () => {
customOrder()
initSize()
}
// 上传图片
const uploadFun = (
filePath,
name: string = new Date().getTime() + 'image',
back: () => any
) => {
Artist.fileUpload(filePath, name, 'png', (url) => {
uni.hideLoading()
formData.image = url
if (back) {
back()
}
})
}
// 选择画框
const frameClick = (index) => {
formData.frame = index
initSize()
customOrder()
}
const paperClick = (index) => {
formData.paper = index
customOrder()
}
// 定做数量
const stepChange = (e) => {
if (e && e[0]) {
formData.num = e[0]
customOrder()
}
}
// 选择尺寸
const selectSize = (e) => {
// console.log(e)
currentSize.value = e.detail.value
if (currentSize.value == selectSizeMenu.value.length - 1) {
formData.size = selectSizeMenu.value[0].w
} else {
formData.size = selectSizeMenu.value[e.detail.value].w
initSize()
customOrder()
}
}
// 选择卡纸宽度
const selectWidths = (e) => {
formData.width = e.detail.value
initSize()
customOrder()
}
// 选择地址
const goAddressList = () => {
SdkActs.address(null, null, null)
}
// 提交订单
const submit = (e) => {
if (formData.image.length == 0) {
App.toast('请上传图片')
return
}
if (pageIndex.value == 1) {
if (!formData.address) {
App.toast('请选择收货地址')
return
}
uni.showModal({
title: '提示',
content: '确认要提交订单吗?',
success: (res) => {
if (res.confirm) {
// 上传图片后回调提交订单
uploadFun(formData.image, formData.name, () => {
customOrder(true)
})
} else if (res.cancel) {
// 用户点击取消按钮
console.log('用户点击取消')
}
}
})
} else {
pageIndex.value = 1
}
}
/**
* 自定义画框订单(包括预览逻辑) confirm 确认订单(下单)
* 流程 : 提交订单-订单详情-支付
*/
const customOrder = (confirm?: boolean) => {
if (formData.image.length == 0) {
App.toast('请上传图片')
return
}
// if (!formData.name) {
// App.toast('请输入画作名称')
// return
// }
let sizes = selectSizeMenu.value[currentSize.value]
let params = {
image: formData?.image, // 画芯
// name: formData?.name, // 画作名称
frameIdx: formData.frame, // 画框索引
paperIdx: formData.paper, // 画纸索引
widthIdx: formData.width, // 卡纸宽度索引
// attName: selectFrame.value[formData.frame]?.name, // 画框名称
w: sizes?.w, // 宽(cm)
h: sizes?.h, // 高(cm)
num: formData.num, // 数量
mark: formData.mark, // 备注
confirm: confirm || false, // 确认订单(下单)
address: formData.address // 收货地址
}
App.client.store.Api_order.customOrder(
1,
[params],
(err: any, res: axj.DtDOrderRep) => {
console.log('[ customOrder ] >', err, res)
formData.price = res.amount
if (res?.orderId && confirm) {
// uni.redirectTo({
// url: `/pagesShop/shop/cashierDesk?orderId=${res.orderId}`
// })
Page.navUri(`/pagesShop/order/orderDetail?orderId=${res.orderId}`)
} else {
// initSize()
}
}
)
}
// 获取初始化信息
const getCustomOrderCfg = () => {
App.client.store.Api_order.customOrderCfg(
1,
[],
(err: any, res: axj.DtDCustomOrderCfg) => {
customOrderCfg.value = res
if (res) {
if (res.sizes) {
// selectSizeMenu.value = ['其他']
// 拼接尺寸显示内容
for (let i = 0; i < res.sizes.length; i++) {
const el = res.sizes[i]
if (el.open) {
el.key = el.w + 'cm*' + el.h + 'cm'
// selectSizeMenu.value.unshift(size)
}
}
selectSizeMenu.value = res.sizes
// 增加自定义尺寸选项
let obj = {
name: '其他',
w: res.sizes[0].w || 10,
h: res.sizes[0].h || 10,
open: true,
key: '其他'
}
formData.size = obj.w
//@ts-ignore
selectSizeMenu.value.push(obj)
}
if (res.frames) {
selectFrame.value = res.frames
}
if (res.papers) {
papers.value = res.papers
}
if (res.widths) {
widths.value = res.widths
}
}
}
)
}
// 判断是否存在相同图片,存在则直接显示,不存在则生成
const checkNameExists = (back: (res: any) => any) => {
let imgPath = imagesPath.value
let name =
selectSizeMenu.value[currentSize.value].key +
'_' +
selectSizeMenu.value[currentSize.value].w +
'_' +
formData.name +
selectFrame.value[formData.frame].name +
widths.value[formData.width].w
const existingImage = imgPath.find((image) => image.name === name)
if (existingImage) {
canvasSize.imgSrc = existingImage.url
back(existingImage)
} else {
back(null)
}
}
// w h 图片+画框的宽高 ,bw 画框尺寸 ,iw ih 图片宽高
const initSize = () => {
if (!formData.image) {
return
}
// 如果是自定义宽度,根据图片宽高比,计算高度
if (currentSize.value == selectSizeMenu.value.length - 1) {
formData.size = parseInt(formData.size)
formData.size = Math.max(formData.size, 10)
if (formData.size) {
const width = formData.size
const height = width / (imgInfo.value.aspectRatio || 1)
selectSizeMenu.value[selectSizeMenu.value.length - 1].w = width.toFixed(2)
selectSizeMenu.value[selectSizeMenu.value.length - 1].h =
height.toFixed(2)
}
}
checkNameExists((res) => {
if (!res) {
let width = selectSizeMenu.value[currentSize.value].w
let height = selectSizeMenu.value[currentSize.value].h
let imgWidth = imgInfo.value.width
let imgHeight = imgInfo.value.height
let cardBoardWidth = widths.value[formData.width].w
if (imgWidth >= imgHeight != width >= height) {
// 画框方向和画面方向一致
let swap = width
width = height
height = swap
}
let imgW_H = imgWidth / imgHeight
if (imgW_H >= width / height) {
imgWidth = width - (cardBoardWidth + 2) * 2
imgHeight = imgWidth / imgW_H
} else {
imgHeight = height - (cardBoardWidth + 2) * 2
imgWidth = imgHeight * imgW_H
}
let w = width
let h = height
let bw = 2
let iw = imgWidth
let ih = imgHeight
console.log('[ initSize ] >', w, h, iw, bw, ih)
if (w >= h !== iw >= ih) {
var t = w
w = h
h = t
}
var s = 750 / w
if (!s) {
return
}
canvasSize.w = 750
canvasSize.h = h * s
canvasSize.bw = bw * s
canvasSize.iw = iw * s
canvasSize.ih = ih * s
canvasSize.widthIn = canvasSize.w
canvasSize.heightIn = canvasSize.h
setTimeout(() => {
draw()
}, 1000)
}
})
}
// 绘制画框图
const draw = () => {
uni.showLoading({ title: '正在生成画框图...' })
step01()
let img = selectFrame.value[formData.frame].url
if (formData.image != '') {
drawBGIMG(App.ossImage(img), () => {
step03()
})
} else {
step03()
}
}
const step01 = () => {
context.value.setFillStyle(canvasSize.bgColor)
context.value.fillRect(0, 0, canvasSize.widthIn, canvasSize.heightIn)
}
// 绘制边框
const drawBGIMG = (img, callback) => {
uni.downloadFile({
url: img,
success: (res) => {
if (res.statusCode == 200) {
// 绘制
uni.getImageInfo({
src: res.tempFilePath,
success: (res2) => {
var pattern = context.value.createPattern(
res.tempFilePath,
'repeat'
)
context.value.fillStyle = pattern
var scale = canvasSize.bw / res2.height
var w_2 = canvasSize.w * 0.5
var h_2 = canvasSize.h * 0.5
// 顶部边框
context.value.save()
context.value.rotate(0)
context.value.scale(scale, scale)
context.value.fillRect(
0,
0,
canvasSize.w / scale,
canvasSize.bw / scale
)
context.value.restore()
// 底部边框
context.value.save()
context.value.translate(w_2, h_2)
context.value.rotate(Math.PI)
context.value.translate(-w_2, -h_2)
context.value.scale(scale, scale)
context.value.fillRect(
0,
0,
canvasSize.w / scale,
canvasSize.bw / scale
)
context.value.restore()
// 右侧边框
context.value.save()
context.value.translate(w_2, h_2)
context.value.rotate(Math.PI * 0.5)
context.value.translate(-h_2, -w_2)
context.value.scale(scale, scale)
// 开始绘制梯形路;
context.value.beginPath()
context.value.moveTo(0, 0)
context.value.lineTo(canvasSize.h / scale, 0)
context.value.lineTo(
canvasSize.h / scale - canvasSize.bw / scale,
canvasSize.bw / scale
)
context.value.lineTo(canvasSize.bw / scale, canvasSize.bw / scale)
context.value.closePath()
// 填充梯形区域
context.value.fill()
context.value.restore()
// 左侧边框
context.value.save()
context.value.translate(w_2, h_2)
context.value.rotate(-Math.PI * 0.5)
context.value.translate(-h_2, -w_2)
context.value.scale(scale, scale)
// 开始绘制梯形路;
context.value.beginPath()
context.value.moveTo(0, 0)
context.value.lineTo(canvasSize.h / scale, 0)
context.value.lineTo(
canvasSize.h / scale - canvasSize.bw / scale,
canvasSize.bw / scale
)
context.value.lineTo(canvasSize.bw / scale, canvasSize.bw / scale)
context.value.closePath()
// 填充梯形区域
context.value.fill()
context.value.restore()
callback()
}
})
}
},
fail: function (e) {
uni.hideLoading()
console.log(e)
}
})
}
// 绘制图片
const step03 = () => {
uni.downloadFile({
url: formData.image.startsWith('tmp/')
? App.ossImage(formData.image)
: formData.image,
success: (res) => {
if (res.statusCode === 200) {
context.value.drawImage(
res.tempFilePath,
(canvasSize.w - canvasSize.iw) / 2,
(canvasSize.h - canvasSize.ih) / 2,
canvasSize.iw,
canvasSize.ih
)
// 在最后一步执行 drawIt 完整最终的绘制
drawIt()
}
},
fail: function (e) {
console.log(e)
}
})
}
const drawIt = () => {
context.value.draw(true, () => {
setTimeout(() => {
uni.canvasToTempFilePath({
x: 0,
y: 0,
width: canvasSize.widthIn,
height: canvasSize.heightIn,
destWidth: canvasSize.widthIn,
destHeight: canvasSize.heightIn,
canvasId: 'graceCanvas',
success: (res) => {
// 在H5平台下,tempFilePath 为 base64
canvasSize.imgSrc = res.tempFilePath
let name =
selectSizeMenu.value[currentSize.value].key +
'_' +
selectSizeMenu.value[currentSize.value].w +
'_' +
formData.name +
selectFrame.value[formData.frame].name +
widths.value[formData.width].w
checkNameExists((res) => {
if (!res) {
imagesPath.value.push({
name: name,
url: canvasSize.imgSrc
})
}
})
uni.hideLoading()
}
})
}, 1000)
})
}
const previewImage = (url) => {
;(url = url.startsWith('tmp/') ? App.ossImage(url) : url),
uni.previewImage({
urls: [url],
current: 0
})
}
const kf = () => {
Page.openHelp(JSON.stringify(data))
}
onShow(() => {
// 必须放在show防止登录后返回不触发
getCustomOrderCfg()
})
onLoad((option) => {
if (!User.state?.logined) {
Page.navLogin()
}
// 监听选择地址返回数据
uni.$on('addressSelectBack', (res) => {
formData.address = res
})
context.value = uni.createCanvasContext('graceCanvas')
})
const goBack = () => {
if (pageIndex.value == 1) {
pageIndex.value = 0
} else {
// 物理返回
nextTick(() => {
Page.navBack()
})
}
}
onBackPress((e: any) => {
if (e && e.from === 'backbutton') {
goBack()
return true
}
})
onUnload(() => {
uni.$off('addressSelectBack')
})
</script>
<style scoped lang="scss">
.privateCustom {
background: #fbfbfb;
position: relative;
.privateCustom-body {
position: relative;
// margin-top: 38rpx;
}
// .prvate-line {
// width: 2rpx;
// height: 20rpx;
// background: #000;
// margin-right: 44rpx;
// margin-left: 15rpx;
// margin-top: 6rpx;
// }
.content {
width: 500rpx;
}
.rowContentDiv {
display: flex;
justify-content: space-around;
width: 500rpx;
}
.posterImg {
height: 200rpx;
width: 200rpx;
}
.uploadDiv {
height: 200rpx;
width: 200rpx;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
font-weight: 400;
font-size: 26rpx;
color: #a1a1a1;
border: 2rpx dashed black;
.addImg {
width: 46rpx;
height: 46rpx;
flex: none;
}
}
:deep(.gui-form-label) {
font-weight: 400;
font-size: 24rpx;
color: #323232;
margin-right: 20rpx !important;
// line-height: normal !important;
}
:deep(.gui-form-item) {
// align-items: flex-start !important;
height: auto !important;
// line-height: normal !important;
width: auto !important;
// margin-top: 80rpx;
// margin-top: 55rpx;
margin-top: 15rpx;
}
.frame-box {
flex-wrap: wrap;
display: flex;
.frame-item {
border: 2rpx solid #fff;
padding: 4rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-right: 4px;
}
.frameImg {
width: 50rpx;
height: 50rpx;
margin-bottom: 18rpx;
}
// .frameImg:nth-child(2n - 1) {
// margin-right: 20rpx;
// }
.frameImg-active {
border-color: #961912 !important;
}
}
.price-box {
display: flex;
font-weight: 400;
font-size: 37rpx;
color: #ff0000;
line-height: 37rpx;
height: 48rpx;
align-items: center;
.pricePImg {
width: 48rpx;
height: 48rpx;
}
text {
margin-left: 9rpx;
}
}
}
.submitBtn {
width: 702rpx;
height: 90rpx;
background-color: #e4a428 !important;
font-weight: 400;
font-size: 52rpx;
color: #ffffff;
line-height: 90rpx;
margin-left: 24rpx !important;
margin-top: 40rpx !important;
}
.canvas-in {
width: 750rpx;
overflow: hidden;
position: absolute;
z-index: 1;
left: 0;
top: -5000px;
}
</style>
customOrderCfg数据格式
javascript
[
1,
{
"frames": [
{
"price": 1000,
"name": "画框一",
"bName": "这两个是红色的画框",
"url": "ms-admin/2024/08/15/00i7ihmuzlxdh0mh.png",
"open": true,
"bname": "这两个是红色的画框"
},
{
"price": 1000,
"name": "画框二",
"url": "ms-admin/2024/08/15/00u8ihmuzlrf8nzr.png",
"open": true
},
{
"price": 1000,
"name": "画框三",
"url": "ms-admin/2024/08/15/00x9ihmuzlj3ey2q.png",
"open": true
}
],
"papers": [
{
"price": 10000,
"name": "宣纸",
"url": "ms-admin/2024/08/15/00uxedxuzlba8ebp.png",
"open": true
},
{
"price": 20000,
"name": "油画纸",
"url": "ms-admin/2024/08/15/0040fdxuzlcxwifk.png",
"open": true
}
],
"sizes": [
{
"name": "尺寸一",
"w": 43.0,
"h": 77.0,
"open": true
},
{
"name": "尺寸二",
"w": 50.0,
"h": 50.0,
"open": true
},
{
"name": "尺寸三",
"w": 36.0,
"h": 96.0,
"open": true
},
{
"name": "尺寸四",
"w": 77.0,
"h": 43.0,
"open": true
}
],
"widths": [
{
"name": "大框大画",
"w": 1.0,
"open": true
},
{
"name": "大框中画",
"w": 10.0,
"open": true
},
{
"name": "大框小画",
"w": 15.0,
"open": true
}
]
}
]