This commit is contained in:
wangchen147 2024-03-29 19:05:27 +08:00
parent e77e3bd75c
commit 5955273e1c
3479 changed files with 149627 additions and 79652 deletions

View File

@ -22,6 +22,7 @@
"rules": {
"no-tabs":"off",
"indent": [1, 4, { "SwitchCase": 1 }],
"eqeqeq":"off"
"eqeqeq":"off",
"vue/multi-word-component-names": "off"
}
}

View File

@ -33,6 +33,7 @@ const solve = () => {
const fc = fs.readFileSync(fn, 'utf-8')
let text = new String(fc)
text = text.replaceAll('./assets/', '/admin/assets/')
text = text.replace('./niucloud.ico', '/admin/niucloud.ico')
fs.writeFileSync(fn, text, 'utf8')
}

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -18,7 +18,7 @@
<el-scrollbar class="flex-1 h-0 mt-[20px]">
<div class="mt-[20px]" v-for="(item, index) in upgradeContent.version_list" :key="index">
<div class="font-bold text-lg">{{ item.version_no }}</div>
<div class="mt-[5px]">{{ item.release_time }}</div>
<div class="mt-[5px]" v-if="item.release_time">{{ item.release_time }}</div>
<div class="mt-[10px] p-[10px] rounded bg-[#f4f4f5] whitespace-pre" v-if="item.upgrade_log" v-html="item.upgrade_log"></div>
</div>
</el-scrollbar>

View File

@ -14,11 +14,18 @@
"pageNamePlaceholder": "请输入页面名称",
"pageBgColor": "页面颜色",
"bgUrl": "背景图片",
"bgHeightScale": "高度比例",
"bgHeightScaleTip": "为0时背景高度自适应展示",
"marginSet": "边距设置",
"componentStyleTitle": "组件样式",
"bottomBgColor": "底部背景",
"bottomBgTips": "底部背景包含边距和圆角",
"componentBgColor": "组件背景",
"componentBgColor": "组件背景色",
"componentBgUrl": "组件背景图",
"componentBgAlpha": "透明度",
"bgGradientAngle": "渐变角度",
"topToBottom": "从上到下",
"leftToRight": "从左到右",
"marginTop": "上边距",
"marginBottom": "下边距",
"marginBoth": "左右边距",
@ -41,6 +48,7 @@
"notCopy": "无法复制",
"componentCanOnlyAdd": "组件只能添加",
"piece": "个",
"componentNotMoved": "该组件禁止移动",
"resetComponentTips": "确认要重置组件默认数据吗?",
"image": "图片上传",
"imageUpload": "图片上传",
@ -72,9 +80,10 @@
"graphicNavStyleSingleSlide": "单行滑动",
"graphicNavStylePageSlide": "分页滑动",
"graphicNavRowCount": "每行数量",
"graphicNavPageCount": "每行数量",
"graphicNavPageCount": "显示方式",
"graphicNavSetLabel": "导航设置",
"line": "行",
"singleLine": "单行",
"multiline": "多行",
"graphicNavTips": "建议上传尺寸相同的图片推荐尺寸60*60",
"graphicNavTitle": "标题",
"graphicNavTitlePlaceholder": "请输入标题",
@ -87,6 +96,7 @@
"styleSet": "风格设置",
"titleStyle": "标题样式",
"selectStyle": "风格选择",
"styleLabel": "风格",
"titleContent": "标题内容",
"title": "标题名称",
"titlePlaceholder": "请输入标题",
@ -99,7 +109,8 @@
"fontWeightBold": "加粗",
"fontWeightNormal": "常规",
"textColor": "文字颜色",
"subTextColor": "副标题颜色",
"subTitleStyle": "副标题样式",
"subTextBgColor": "背景色",
"subTitleContent": "标题内容",
"subTitle": "副标题",
"subTitlePlaceholder": "请输入副标题",
@ -137,5 +148,93 @@
"selectCategory":"选择分类",
"categoryName": "分类名称",
"categoryImage": "分类图片",
"selectSource": "选择数据源"
"selectSource": "选择数据源",
"richTextContentSet": "内容设置",
"richTextPlaceholder": "请输入富文本内容",
"activeCubeBlockContent": "板块内容",
"activeCubeTitle": "标题",
"activeCubeTitlePlaceholder": "请输入标题",
"activeCubeSubTitle": "副标题",
"activeCubeSubTitlePlaceholder": "请输入副标题",
"activeCubeButton": "按钮",
"activeCubeButtonPlaceholder": "请输入按钮文字",
"activeCubeButtonColor": "按钮颜色",
"activeListFrameColor": "框体颜色",
"activeCubeSubTitleTextColor": "文字颜色",
"activeCubeSubTitleBgColor": "背景颜色",
"activeCubeAddItem": "添加一个板块",
"activeCubeBlockStyle": "板块样式",
"activeCubeBlockTextFontWeight": "标题粗细",
"noticeStyle": "公告风格",
"noticeType": "类型",
"noticeTypeImg": "图片",
"noticeTypeText": "文字",
"noticeTypeTextPlaceholder": "请输入公告标题",
"noticeTitle": "公告标题",
"addNotice": "添加公告",
"noticeText": "公告内容",
"noticeScrollWay": "滚动方式",
"noticeUpDown": "上下滚动",
"noticeHorizontal": "横向滚动",
"noticeShowType": "点击类型",
"noticeShowPopUp": "弹出公告内容",
"noticeShowLink": "跳转链接",
"dragMouseAdjustOrder": "鼠标拖拽可调整顺序",
"noticePlaceholderText": "请输入公告内容",
"carouselSearchShowPosition": "显示设置",
"carouselSearchOpen": "开启",
"carouselSearchClose": "关闭",
"carouselSearchBgGradient": "背景渐变",
"carouselSearchShowWay": "展示方式",
"carouselSearchShowWayStatic": "正常显示",
"carouselSearchShowWayFixed": "滚动至顶部固定",
"carouselSearchFixedBgColor": "置顶背景",
"carouselSearchSet": "搜索设置",
"carouselSearchText": "搜索内容",
"carouselSearchHotWordSet": "搜索热词",
"carouselSearchHotWordInterval": "显示时间 / 秒",
"carouselSearchHotWordText": "内容",
"carouselSearchHotWordTextPlaceholder": "请输入热词",
"carouselSearchAddHotWordItem": "添加一个热词",
"carouselSearchLogoTips": "建议尺寸70px * 30px",
"carouselSearchPlaceholder": "请输入搜索内容",
"carouselSearchTabSet": "选项卡设置",
"carouselSearchTabControl": "展示开关",
"carouselSearchTabCategoryText": "分类名称",
"carouselSearchTabCategoryTextPlaceholder": "请输入分类名称",
"carouselSearchAddTabItem": "添加一个选项卡",
"selectSourcesDiyPage": "选择微页面",
"selectDiyPagePlaceholder": "请选择微页面",
"diyPageTitle": "页面名称",
"diyPageTypeName": "页面类型",
"diyPageForAddon": "所属应用",
"carouselSearchSwiperSet": "轮播图设置",
"carouselSearchSwiperControl": "展示开关",
"carouselSearchSwiperInterval": "切换间隔 / 秒",
"carouselSearchSwiperTips": "建议上传尺寸相同的图片推荐尺寸750*350鼠标拖拽可调整图片顺序",
"carouselSearchTabStyle": "选项卡样式",
"noColor": "常规颜色",
"selectColor": "选中颜色",
"fixedNoColor": "下滑常规颜色",
"fixedSelectColor": "下滑选中颜色",
"carouselSearchSwiperIndicatorSet": "指示器设置",
"carouselSearchSwiperIndicatorStyle": "指示器样式",
"carouselSearchSwiperStyle": "轮播样式",
"carouselSearchSwiperIndicatorStyle1": "样式1",
"carouselSearchSwiperIndicatorStyle2": "样式2",
"carouselSearchSwiperIndicatorAlign": "显示位置",
"alignLeft": "居左",
"alignCenter": "居中",
"alignRight": "居右",
"horzLineStyle": "线条风格",
"horzLineStyleSolid": "实线",
"horzLineStyleDashed": "虚线",
"horzLineBorderColor": "线条颜色",
"horzLineBorderWidth": "线条宽度",
"floatBtnBtton": "按钮位置",
"floatBtnOffset": "上下偏移",
"floatBtnImageSet": "图片设置",
"floatBtnImageSize": "图片大小",
"floatBtnAroundRadius": "图片圆角",
"floatBtnImageSuggest": "建议上传正方形图片"
}

View File

@ -28,5 +28,8 @@
"visitTimePlaceholder":"请输入访问时间",
"createTimePlaceholder":"请输入消息时间",
"buyerNotice": "会员消息",
"messageKey":"消息模版",
"messageInfo":"发送记录详情",
"smsType":"短信类型",
"sellerNotice":"平台消息"
}

View File

@ -9,6 +9,7 @@
"siteLogo": "店铺LOGO",
"expireTime":"到期时间",
"groupName": "店铺套餐",
"statusExpire":"已到期",
"siteNamePlaceholder":"请输入站点名称",
"groupIdPlaceholder":"请输入套餐",
"addSite":"添加站点",

View File

@ -15,12 +15,12 @@
<h3 class="panel-title !text-sm">{{ t('wechatInfo') }}</h3>
<el-form-item :label="t('wechatName')" prop="wechat_name">
<el-input v-model="formData.wechat_name" :placeholder="t('wechatNamePlaceholder')" class="input-width"
<el-input v-model.trim="formData.wechat_name" :placeholder="t('wechatNamePlaceholder')" class="input-width"
clearable />
</el-form-item>
<el-form-item :label="t('wechatOriginal')" prop="wechat_original">
<el-input v-model="formData.wechat_original" :placeholder="t('wechatOriginalPlaceholder')"
<el-input v-model.trim="formData.wechat_original" :placeholder="t('wechatOriginalPlaceholder')"
class="input-width" clearable />
</el-form-item>
@ -35,13 +35,13 @@
<h3 class="panel-title !text-sm">{{ t('wechatDevelopInfo') }}</h3>
<el-form-item :label="t('wechatAppid')" prop="app_id">
<el-input v-model="formData.app_id" :placeholder="t('appidPlaceholder')" class="input-width"
<el-input v-model.trim="formData.app_id" :placeholder="t('appidPlaceholder')" class="input-width"
clearable />
<div class="form-tip">{{ t('wechatAppidTips') }}</div>
</el-form-item>
<el-form-item :label="t('wechatAppsecret')" prop="app_secret">
<el-input v-model="formData.app_secret" :placeholder="t('appSecretPlaceholder')" class="input-width"
<el-input v-model.trim="formData.app_secret" :placeholder="t('appSecretPlaceholder')" class="input-width"
clearable />
<div class="form-tip">{{ t('wechatAppsecretTips') }}</div>
</el-form-item>
@ -61,13 +61,13 @@
</el-form-item>
<el-form-item label="Token" prop="token">
<el-input v-model="formData.token" :placeholder="t('tokenPlaceholder')" class="input-width"
<el-input v-model.trim="formData.token" :placeholder="t('tokenPlaceholder')" class="input-width"
maxlength="32" show-word-limit clearable />
<div class="form-tip">{{ t('tokenTips') }}</div>
</el-form-item>
<el-form-item label="EncodingAESKey" prop="encoding_aes_key">
<el-input v-model="formData.encoding_aes_key" :placeholder="t('encodingAesKeyPlaceholder')"
<el-input v-model.trim="formData.encoding_aes_key" :placeholder="t('encodingAesKeyPlaceholder')"
class="input-width" maxlength="43" show-word-limit clearable />
<div class="form-tip">{{ t('encodingAESKeyTips') }}</div>
</el-form-item>

View File

@ -0,0 +1,383 @@
<template>
<!-- 内容 -->
<div class="content-wrap" v-show="diyStore.editTab == 'content'">
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('titleContent') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('selectStyle')" class="flex">
<span class="text-primary flex-1 cursor-pointer" @click="showTitleStyle">{{ diyStore.editComponent.titleStyle.title }}</span>
<el-icon>
<ArrowRight />
</el-icon>
</el-form-item>
<el-form-item :label="t('title')">
<el-input v-model.trim="diyStore.editComponent.text" :placeholder="t('titlePlaceholder')" clearable maxlength="10" show-word-limit />
</el-form-item>
<el-form-item :label="t('link')">
<diy-link v-model="diyStore.editComponent.textLink"/>
</el-form-item>
<el-form-item :label="t('subTitle')">
<el-input v-model.trim="diyStore.editComponent.subTitle.text" :placeholder="t('subTitlePlaceholder')" clearable maxlength="8" show-word-limit />
</el-form-item>
<el-form-item :label="t('link')">
<diy-link v-model="diyStore.editComponent.subTitle.link"/>
</el-form-item>
</el-form>
<el-dialog v-model="showTitleDialog" :title="t('selectStyle')" width="500px">
<div class="flex flex-wrap">
<template v-for="(item,index) in titleStyleList" :key="index">
<div :class="{ 'border-primary': selectTitleStyle.value == item.value }" @click="changeTitleStyle(item)" class="flex items-center justify-center overflow-hidden w-[200px] h-[100px] mr-[12px] mb-[12px] cursor-pointer border bg-[#eee]">
<img :src="img(item.url)" />
</div>
</template>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="showTitleDialog = false">{{ t('cancel') }}</el-button>
<el-button type="primary" @click="confirmTitleStyle">{{ t('confirm') }}</el-button>
</span>
</template>
</el-dialog>
</div>
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('activeCubeBlockContent') }}</h3>
<el-form label-width="90px" class="px-[10px]">
<el-form-item :label="t('selectStyle')" class="flex">
<span class="text-primary flex-1 cursor-pointer" @click="showBlockStyle">{{ diyStore.editComponent.blockStyle.title }}</span>
<el-icon>
<ArrowRight />
</el-icon>
</el-form-item>
<el-dialog v-model="showListDialog" :title="t('selectStyle')" width="600px">
<div class="flex flex-wrap">
<template v-for="(item,index) in blockStyleList" :key="index">
<div :class="{ 'border-primary': selectBlockStyle.value == item.value }" @click="changeBlockStyle(item)" class="flex items-center justify-center overflow-hidden w-[250px] h-[150px] mr-[12px] mb-[12px] cursor-pointer border bg-[#eee]">
<img :src="img(item.url)" />
</div>
</template>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="showListDialog = false">{{ t('cancel') }}</el-button>
<el-button type="primary" @click="confirmBlockStyle">{{ t('confirm') }}</el-button>
</span>
</template>
</el-dialog>
<p class="text-sm text-gray-400 mb-[10px]">{{ t('dragMouseAdjustOrder') }}</p>
<div ref="blockBoxRef">
<div v-for="(item,index) in diyStore.editComponent.list" :key="item.id" class="item-wrap p-[10px] pb-0 relative border border-dashed border-gray-300 mb-[16px]">
<el-form-item :label="t('image')">
<upload-image v-model="item.imageUrl" :limit="1"/>
</el-form-item>
<el-form-item :label="t('activeCubeTitle')">
<el-input v-model.trim="item.title.text" :placeholder="t('activeCubeTitlePlaceholder')" clearable maxlength="4" show-word-limit/>
</el-form-item>
<el-form-item :label="t('activeCubeSubTitleTextColor')" v-show="selectBlockStyle.value == 'style-3'">
<el-color-picker v-model="item.title.textColor" show-alpha :predefine="diyStore.predefineColors" />
</el-form-item>
<el-form-item :label="t('activeCubeSubTitle')" v-if="selectBlockStyle.value != 'style-3'">
<el-input v-model.trim="item.subTitle.text" :placeholder="t('activeCubeSubTitlePlaceholder')" clearable :maxlength="(selectBlockStyle.value != 'style-4' ? '6' : '4')" show-word-limit/>
</el-form-item>
<div v-show="selectBlockStyle.value == 'style-4'">
<el-form-item :label="t('activeCubeSubTitleTextColor')">
<el-color-picker v-model="item.subTitle.textColor" show-alpha :predefine="diyStore.predefineColors" />
</el-form-item>
<el-form-item :label="t('activeCubeSubTitleBgColor')">
<el-color-picker v-model="item.subTitle.startColor" show-alpha :predefine="diyStore.predefineColors" />
<icon name="iconfont-iconmap-connect" size="20px" class="block !text-gray-400 mx-[5px]"/>
<el-color-picker v-model="item.subTitle.endColor" show-alpha :predefine="diyStore.predefineColors"/>
</el-form-item>
<el-form-item :label="t('activeListFrameColor')">
<el-color-picker v-model="item.listFrame.startColor" show-alpha :predefine="diyStore.predefineColors" />
<icon name="iconfont-iconmap-connect" size="20px" class="block !text-gray-400 mx-[5px]"/>
<el-color-picker v-model="item.listFrame.endColor" show-alpha :predefine="diyStore.predefineColors"/>
</el-form-item>
</div>
<div v-show="selectBlockStyle.value != 'style-4' && selectBlockStyle.value != 'style-3'">
<el-form-item :label="t('activeCubeButton')">
<el-input v-model.trim="item.moreTitle.text" :placeholder="t('activeCubeButtonPlaceholder')" clearable maxlength="3" show-word-limit/>
</el-form-item>
<el-form-item :label="t('activeCubeButtonColor')">
<el-color-picker v-model="item.moreTitle.startColor" show-alpha :predefine="diyStore.predefineColors" />
<icon name="iconfont-iconmap-connect" size="20px" class="block !text-gray-400 mx-[5px]"/>
<el-color-picker v-model="item.moreTitle.endColor" show-alpha :predefine="diyStore.predefineColors"/>
</el-form-item>
</div>
<el-form-item :label="t('link')">
<diy-link v-model="item.link"/>
</el-form-item>
<div class="del absolute cursor-pointer z-[2] top-[-8px] right-[-8px]" v-show="diyStore.editComponent.list.length > 1" @click="diyStore.editComponent.list.splice(index,1)">
<icon name="element-CircleCloseFilled" color="#bbb" size="20px"/>
</div>
</div>
</div>
<el-button v-show="diyStore.editComponent.list.length < 10" class="w-full" @click="addItem">{{ t('activeCubeAddItem') }}</el-button>
</el-form>
</div>
</div>
<!-- 样式 -->
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('titleStyle') }}</h3>
<el-form label-width="90px" class="px-[10px]">
<el-form-item :label="t('textColor')">
<el-color-picker v-model="diyStore.editComponent.titleColor" show-alpha :predefine="diyStore.predefineColors"/>
</el-form-item>
</el-form>
</div>
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('subTitleStyle') }}</h3>
<el-form label-width="90px" class="px-[10px]">
<el-form-item :label="t('textColor')">
<el-color-picker v-model="diyStore.editComponent.subTitle.textColor" show-alpha :predefine="diyStore.predefineColors"/>
</el-form-item>
<el-form-item :label="t('subTextBgColor')">
<el-color-picker v-model="diyStore.editComponent.subTitle.startColor" show-alpha :predefine="diyStore.predefineColors"/>
<icon name="iconfont-iconmap-connect" size="20px" class="block !text-gray-400 mx-[5px]"/>
<el-color-picker v-model="diyStore.editComponent.subTitle.endColor" show-alpha :predefine="diyStore.predefineColors"/>
</el-form-item>
</el-form>
</div>
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('activeCubeBlockStyle') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('activeCubeBlockTextFontWeight')">
<el-radio-group v-model="diyStore.editComponent.blockStyle.fontWeight">
<el-radio :label="'normal'">{{t('fontWeightNormal')}}</el-radio>
<el-radio :label="'bold'">{{t('fontWeightBold')}}</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
</div>
<!-- 组件样式 -->
<slot name="style"></slot>
</div>
</template>
<script lang="ts" setup>
import { t } from '@/lang'
import useDiyStore from '@/stores/modules/diy'
import { img } from '@/utils/common'
import { ref, reactive, onMounted, nextTick } from 'vue'
import Sortable from 'sortablejs'
import { range } from 'lodash-es'
const diyStore = useDiyStore()
diyStore.editComponent.ignore = [] //
//
diyStore.editComponent.verify = (index: number) => {
const res = { code: true, message: '' }
if(diyStore.value[index].text == ''){
res.code = false
res.message = t('activeCubeTitlePlaceholder')
return res
}
diyStore.value[index].list.forEach((item: any) => {
if (item.imageUrl === '') {
res.code = false
res.message = t('imageUrlTip')
return res
}
if (item.title.text === '') {
res.code = false
res.message = t('activeCubeTitlePlaceholder')
return res
}
if(['style-1','style-2','style-4'].indexOf(diyStore.value[index].blockStyle.value) != -1){
if (item.subTitle.text === '') {
res.code = false
res.message = t('activeCubeSubTitlePlaceholder')
return res
}
}
if(['style-1','style-2'].indexOf(diyStore.value[index].blockStyle.value) != -1){
if (item.moreTitle.text === '') {
res.code = false
res.message = t('activeCubeButtonPlaceholder')
return res
}
}
})
return res
}
diyStore.editComponent.list.forEach((item: any) => {
if (!item.id) item.id = diyStore.generateRandom()
})
//
const showTitleDialog = ref(false)
const showTitleStyle = () => {
showTitleDialog.value = true
}
const titleStyleList = reactive([
{
url : 'static/resource/images/diy/active_cube/title_style1.png',
title:'风格1',
value:'style-1'
},{
url : 'static/resource/images/diy/active_cube/title_style2.png',
title:'风格2',
value:'style-2'
},{
url : 'static/resource/images/diy/active_cube/title_style3.png',
title:'风格3',
value:'style-3'
},{
url : 'static/resource/images/diy/active_cube/title_style5.png',
title:'风格4',
value:'style-4'
}
])
const selectTitleStyle = reactive({
title: diyStore.editComponent.titleStyle.title,
value: diyStore.editComponent.titleStyle.value
})
const changeTitleStyle = (item:any) => {
selectTitleStyle.title = item.title;
selectTitleStyle.value = item.value;
}
const confirmTitleStyle = () => {
diyStore.editComponent.titleStyle.title = selectTitleStyle.title;
diyStore.editComponent.titleStyle.value = selectTitleStyle.value;
showTitleDialog.value = false
}
//
const showListDialog = ref(false)
const showBlockStyle = () => {
showListDialog.value = true
}
const blockStyleList = reactive([
{
url : 'static/resource/images/diy/active_cube/block_style1.png',
title:'风格1',
value:'style-1'
},
{
url : 'static/resource/images/diy/active_cube/block_style2.png',
title:'风格2',
value:'style-2'
},
{
url : 'static/resource/images/diy/active_cube/block_style3.png',
title:'风格3',
value:'style-3'
},
{
url : 'static/resource/images/diy/active_cube/block_style4.png',
title:'风格4',
value:'style-4'
}
])
const selectBlockStyle = reactive({
title: diyStore.editComponent.blockStyle.title,
value: diyStore.editComponent.blockStyle.value
})
const changeBlockStyle = (item:any) => {
selectBlockStyle.title = item.title;
selectBlockStyle.value = item.value;
}
const confirmBlockStyle = () => {
diyStore.editComponent.blockStyle.title = selectBlockStyle.title;
diyStore.editComponent.blockStyle.value = selectBlockStyle.value;
showListDialog.value = false
}
const addItem = () => {
diyStore.editComponent.list.push({
id: diyStore.generateRandom(),
title:{
title:'标题',
textColor: "#000000"
},
subTitle:{
text:'副标题',
textColor: "#999999",
startColor:'',
endColor:''
},
listFrame: {
startColor: "#4AC1FF",
endColor: "#1D7CFF"
},
moreTitle:{
text:'去看看',
startColor:'#FEA715',
endColor:'#FE1E00'
},
imageUrl: '',
link: { name: '' }
})
}
const blockBoxRef = ref()
onMounted(() => {
nextTick(() => {
const sortable = Sortable.create(blockBoxRef.value, {
group: 'item-wrap',
animation: 200,
onEnd: event => {
const temp = diyStore.editComponent.list[event.oldIndex!]
diyStore.editComponent.list.splice(event.oldIndex!, 1)
diyStore.editComponent.list.splice(event.newIndex!, 0, temp)
sortable.sort(
range(diyStore.editComponent.list.length).map(value => {
return value.toString()
})
)
}
})
})
})
defineExpose({})
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,526 @@
<template>
<!-- 内容 -->
<div class="content-wrap" v-show="diyStore.editTab == 'content'">
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('carouselSearchShowPosition') }}</h3>
<el-form label-width="100px" class="px-[10px]">
<el-form-item :label="t('carouselSearchShowWay')">
<el-radio-group v-model="diyStore.editComponent.positionWay">
<el-radio label="static">{{ t('carouselSearchShowWayStatic') }}</el-radio>
<el-radio label="fixed">{{ t('carouselSearchShowWayFixed') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('carouselSearchFixedBgColor')" v-show="diyStore.editComponent.positionWay == 'fixed'">
<el-color-picker v-model="diyStore.editComponent.fixedBgColor" show-alpha :predefine="diyStore.predefineColors"/>
</el-form-item>
<el-form-item :label="t('carouselSearchBgGradient')">
<el-radio-group v-model="diyStore.editComponent.bgGradient">
<el-radio :label="true">{{ t('carouselSearchOpen') }}</el-radio>
<el-radio :label="false">{{ t('carouselSearchClose') }}</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
</div>
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('carouselSearchSet') }}</h3>
<el-form label-width="100px" class="px-[10px]">
<div class="text-sm text-gray-400 mb-[10px]">{{ t('carouselSearchLogoTips') }}</div>
<el-form-item :label="t('logo')">
<upload-image v-model="diyStore.editComponent.search.logo" :limit="1" />
</el-form-item>
<el-form-item :label="t('carouselSearchText')">
<el-input v-model.trim="diyStore.editComponent.search.text" :placeholder="t('carouselSearchPlaceholder')" clearable maxlength="20" show-word-limit />
</el-form-item>
<el-form-item :label="t('link')">
<diy-link v-model="diyStore.editComponent.search.link"/>
</el-form-item>
</el-form>
</div>
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('carouselSearchHotWordSet') }}</h3>
<el-form label-width="100px" class="px-[10px]">
<el-form-item :label="t('carouselSearchHotWordInterval')">
<el-slider v-model="diyStore.editComponent.search.hotWord.interval" show-input size="small" class="ml-[10px] horz-blank-slider" :min="1" :max="10"/>
</el-form-item>
<p class="text-sm text-gray-400 mb-[10px]">{{ t('dragMouseAdjustOrder') }}</p>
<div ref="searchHotWordTabBoxRef">
<div v-for="(item,index) in diyStore.editComponent.search.hotWord.list" :key="item.id" class="item-wrap p-[10px] relative border border-dashed border-gray-300 mb-[16px]">
<el-form-item :label="t('carouselSearchHotWordText')" class="!mb-0">
<el-input v-model.trim="item.text" :placeholder="t('carouselSearchHotWordTextPlaceholder')" clearable maxlength="4" show-word-limit/>
</el-form-item>
<div class="del absolute cursor-pointer z-[2] top-[-8px] right-[-8px]" @click="diyStore.editComponent.search.hotWord.list.splice(index,1)">
<icon name="element-CircleCloseFilled" color="#bbb" size="20px"/>
</div>
</div>
<el-button v-show="diyStore.editComponent.search.hotWord.list.length < 50" class="w-full" @click="addHotWordItem">{{ t('carouselSearchAddHotWordItem') }}</el-button>
</div>
</el-form>
</div>
<el-collapse v-model="activeNames" @change="handleChange" class="collapse-wrap">
<el-collapse-item :title="t('carouselSearchTabSet')" name="tab">
<div class="edit-attr-item-wrap">
<el-form label-width="100px" class="px-[10px]">
<el-form-item :label="t('carouselSearchTabControl')">
<el-switch v-model="diyStore.editComponent.tab.control" />
</el-form-item>
<p class="text-sm text-gray-400 mb-[10px]">{{ t('dragMouseAdjustOrder') }}</p>
<div ref="tabBoxRef">
<div v-for="(item,index) in diyStore.editComponent.tab.list" :key="item.id" class="item-wrap p-[10px] pb-0 relative border border-dashed border-gray-300 mb-[16px]">
<el-form-item :label="t('carouselSearchTabCategoryText')">
<el-input v-model.trim="item.text" :placeholder="t('carouselSearchTabCategoryTextPlaceholder')" clearable maxlength="4" show-word-limit/>
</el-form-item>
<el-form-item :label="t('dataSources')">
<el-input v-model="item.diy_title" :placeholder="t('selectDiyPagePlaceholder')" readonly class="select-diy-page-input" @click="diyPageShowDialogOpen(index)">
<template #suffix>
<div @click.stop="tabClear(index)">
<el-icon v-if="item.diy_title">
<Close />
</el-icon>
<el-icon v-else>
<ArrowRight />
</el-icon>
</div>
</template>
</el-input>
</el-form-item>
<div class="del absolute cursor-pointer z-[2] top-[-8px] right-[-8px]" v-show="diyStore.editComponent.tab.list.length > 1" @click="diyStore.editComponent.tab.list.splice(index,1)">
<icon name="element-CircleCloseFilled" color="#bbb" size="20px"/>
</div>
</div>
<el-button v-show="diyStore.editComponent.tab.list.length < 50" class="w-full" @click="addTabItem">{{ t('carouselSearchAddTabItem') }}</el-button>
</div>
<!-- 选择微页面弹出框 -->
<el-dialog v-model="diyPageShowDialog" :title="t('selectSourcesDiyPage')" width="1000px" :close-on-press-escape="false" :destroy-on-close="true" :close-on-click-modal="false">
<el-table :data="diyPageTable.data" ref="diyPageTableRef" size="large" v-loading="diyPageTable.loading" height="490px" @current-change="handleCurrentDiyPageChange" row-key="id" highlight-current-row>
<template #empty>
<span>{{ !diyPageTable.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column prop="title" :label="t('diyPageTitle')" min-width="120" />
<el-table-column prop="addon_name" :label="t('diyPageTypeName')" min-width="80" />
<el-table-column prop="type_name" :label="t('diyPageForAddon')" min-width="80" />
</el-table>
<div class="mt-[16px] flex justify-end">
<el-pagination v-model:current-page="diyPageTable.page" v-model:page-size="diyPageTable.limit"
layout="total, sizes, prev, pager, next, jumper" :total="diyPageTable.total"
@size-change="loadDiyPageList" @current-change="loadDiyPageList" />
</div>
<div class="flex items-center justify-end mt-[15px]">
<el-button type="primary" @click="saveDiyPageId">{{ t('confirm') }}</el-button>
<el-button @click="diyPageShowDialog = false">{{ t('cancel') }}</el-button>
</div>
</el-dialog>
</el-form>
</div>
</el-collapse-item>
<el-collapse-item :title="t('carouselSearchSwiperSet')" name="swiper">
<el-form label-width="100px" class="px-[10px]">
<el-form-item :label="t('carouselSearchSwiperControl')">
<el-switch v-model="diyStore.editComponent.swiper.control" />
</el-form-item>
<el-form-item :label="t('carouselSearchSwiperInterval')">
<el-slider v-model="diyStore.editComponent.swiper.interval" show-input size="small" class="ml-[10px] horz-blank-slider" :min="1" :max="10"/>
</el-form-item>
<div class="text-sm text-gray-400 mb-[10px]">{{ t('carouselSearchSwiperTips') }}</div>
<div ref="imageBoxRef">
<div v-for="(item,index) in diyStore.editComponent.swiper.list" :key="item.id" class="item-wrap p-[10px] pb-0 relative border border-dashed border-gray-300 mb-[16px]">
<el-form-item :label="t('image')">
<upload-image v-model="item.imageUrl" :limit="1" @change="selectImg" />
</el-form-item>
<div class="del absolute cursor-pointer z-[2] top-[-8px] right-[-8px]" v-show="diyStore.editComponent.swiper.list.length > 1" @click="diyStore.editComponent.swiper.list.splice(index,1)">
<icon name="element-CircleCloseFilled" color="#bbb" size="20px"/>
</div>
<el-form-item :label="t('link')">
<diy-link v-model="item.link"/>
</el-form-item>
</div>
</div>
<el-button v-show="diyStore.editComponent.swiper.list.length < 10" class="w-full" @click="addImageAd">{{ t('addImageAd') }}</el-button>
</el-form>
</el-collapse-item>
</el-collapse>
</div>
<!-- 样式 -->
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('carouselSearchTabStyle') }}</h3>
<el-form label-width="100px" class="px-[10px]">
<el-form-item :label="t('noColor')">
<el-color-picker v-model="diyStore.editComponent.tab.noColor" show-alpha :predefine="diyStore.predefineColors"/>
</el-form-item>
<el-form-item :label="t('selectColor')">
<el-color-picker v-model="diyStore.editComponent.tab.selectColor" show-alpha :predefine="diyStore.predefineColors"/>
</el-form-item>
<el-form-item :label="t('fixedNoColor')">
<el-color-picker v-model="diyStore.editComponent.tab.fixedNoColor" show-alpha :predefine="diyStore.predefineColors"/>
</el-form-item>
<el-form-item :label="t('fixedSelectColor')">
<el-color-picker v-model="diyStore.editComponent.tab.fixedSelectColor" show-alpha :predefine="diyStore.predefineColors"/>
</el-form-item>
</el-form>
</div>
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('carouselSearchSwiperSet') }}</h3>
<el-form label-width="100px" class="px-[10px]">
<el-form-item :label="t('carouselSearchSwiperStyle')" @change="changeSwiperStyle">
<el-radio-group v-model="diyStore.editComponent.swiper.swiperStyle">
<el-radio label="style-1">{{ t('carouselSearchSwiperIndicatorStyle1') }}</el-radio>
<el-radio label="style-2">{{ t('carouselSearchSwiperIndicatorStyle2') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('topRounded')">
<el-slider v-model="diyStore.editComponent.swiper.topRounded" show-input size="small" class="ml-[10px] horz-blank-slider" :max="50" />
</el-form-item>
<el-form-item :label="t('bottomRounded')">
<el-slider v-model="diyStore.editComponent.swiper.bottomRounded" show-input size="small" class="ml-[10px] horz-blank-slider" :max="50" />
</el-form-item>
</el-form>
</div>
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('carouselSearchSwiperIndicatorSet') }}</h3>
<el-form label-width="100px" class="px-[10px]">
<el-form-item :label="t('carouselSearchSwiperIndicatorStyle')">
<el-radio-group v-model="diyStore.editComponent.swiper.indicatorStyle">
<el-radio label="style-1">{{ t('carouselSearchSwiperIndicatorStyle1') }}</el-radio>
<el-radio label="style-2">{{ t('carouselSearchSwiperIndicatorStyle2') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('carouselSearchSwiperIndicatorAlign')">
<el-radio-group v-model="diyStore.editComponent.swiper.indicatorAlign">
<el-radio label="left">{{ t('alignLeft') }}</el-radio>
<el-radio label="center">{{ t('alignCenter') }}</el-radio>
<el-radio label="right">{{ t('alignRight') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('noColor')">
<el-color-picker v-model="diyStore.editComponent.swiper.indicatorColor" show-alpha :predefine="diyStore.predefineColors"/>
</el-form-item>
<el-form-item :label="t('selectColor')">
<el-color-picker v-model="diyStore.editComponent.swiper.indicatorActiveColor" show-alpha :predefine="diyStore.predefineColors"/>
</el-form-item>
</el-form>
</div>
<!-- 组件样式 -->
<!-- <slot name="style"></slot> -->
</div>
</template>
<script lang="ts" setup>
import { t } from '@/lang'
import { img } from '@/utils/common'
import useDiyStore from '@/stores/modules/diy'
import { ref, reactive, watch, onMounted, nextTick } from 'vue'
import { ElTable } from 'element-plus'
import Sortable from 'sortablejs'
import { range } from 'lodash-es'
import { getDiyPageList } from '@/app/api/diy'
const diyStore = useDiyStore()
diyStore.editComponent.ignore = ['componentBgColor','componentBgUrl','marginTop','marginBottom','topRounded','bottomRounded','pageBgColor','marginBoth'] //
//
diyStore.editComponent.verify = (index: number) => {
const res = { code: true, message: '' }
diyStore.value[index].search.hotWord.list.forEach((item: any) => {
if(item.text == ''){
res.code = false
res.message = t('carouselSearchHotWordTextPlaceholder')
return res
}
});
diyStore.value[index].tab.list.forEach((item: any) => {
if(item.text == ''){
res.code = false
res.message = t('carouselSearchTabCategoryTextPlaceholder')
return res
}
// if(item.diy_id == ''){
// res.code = false
// res.message = t('selectDiyPagePlaceholder')
// return res
// }
});
diyStore.value[index].swiper.list.forEach((item: any) => {
if(item.imageUrl == ''){
res.code = false
res.message = t('imageUrlTip')
return res
}
});
return res
}
diyStore.editComponent.search.hotWord.list.forEach((item: any) => {
if (!item.id) item.id = diyStore.generateRandom()
})
diyStore.editComponent.tab.list.forEach((item: any) => {
if (!item.id) item.id = diyStore.generateRandom()
})
diyStore.editComponent.swiper.list.forEach((item: any) => {
if (!item.id) item.id = diyStore.generateRandom()
})
const activeNames = ref(['tab', 'swiper'])
const handleChange = (val: string[]) => {}
onMounted(() => {
loadDiyPageList()
})
const addHotWordItem = () => {
diyStore.editComponent.search.hotWord.list.push({
id: diyStore.generateRandom(),
text : '关键词',
})
}
const tabClear = (index:any) => {
diyStore.editComponent.tab.list[index].diy_id = 0;
diyStore.editComponent.tab.list[index].diy_title = '';
}
const addTabItem = () => {
diyStore.editComponent.tab.list.push({
id: diyStore.generateRandom(),
text : '分类名称', // 4
source : 'diy_page', // diy_page
diy_id : '',
diy_title : ''
})
}
const searchHotWordTabBoxRef = ref()
const tabBoxRef = ref()
const imageBoxRef = ref()
onMounted(() => {
nextTick(() => {
const hotWordSortable = Sortable.create(searchHotWordTabBoxRef.value, {
group: 'item-wrap',
animation: 200,
onEnd: event => {
const temp = diyStore.editComponent.search.hotWord.list[event.oldIndex!]
diyStore.editComponent.search.hotWord.list.splice(event.oldIndex!, 1)
diyStore.editComponent.search.hotWord.list.splice(event.newIndex!, 0, temp)
tabSortable.sort(
range(diyStore.editComponent.search.hotWord.list.length).map(value => {
return value.toString()
})
)
}
})
const tabSortable = Sortable.create(tabBoxRef.value, {
group: 'item-wrap',
animation: 200,
onEnd: event => {
const temp = diyStore.editComponent.tab.list[event.oldIndex!]
diyStore.editComponent.tab.list.splice(event.oldIndex!, 1)
diyStore.editComponent.tab.list.splice(event.newIndex!, 0, temp)
tabSortable.sort(
range(diyStore.editComponent.tab.list.length).map(value => {
return value.toString()
})
)
}
})
const imageSortable = Sortable.create(imageBoxRef.value, {
group: 'item-wrap',
animation: 200,
onEnd: event => {
const temp = diyStore.editComponent.swiper.list[event.oldIndex!]
diyStore.editComponent.swiper.list.splice(event.oldIndex!, 1)
diyStore.editComponent.swiper.list.splice(event.newIndex!, 0, temp)
imageSortable.sort(
range(diyStore.editComponent.swiper.list.length).map(value => {
return value.toString()
})
)
handleHeight(true)
}
})
})
})
const diyPageShowDialog = ref(false)
const diyPageTable = reactive({
page: 1,
limit: 10,
total: 0,
loading: true,
data: [],
searchParam: {
type: 'DIY_PAGE' // todo
}
})
const diyPageTableRef = ref<InstanceType<typeof ElTable>>()
/**
* 获取自定义页面列表
*/
const loadDiyPageList = (page: number = 1) => {
diyPageTable.loading = true
diyPageTable.page = page
getDiyPageList({
page: diyPageTable.page,
limit: diyPageTable.limit,
...diyPageTable.searchParam
}).then(res => {
diyPageTable.loading = false
let data = res.data.data;
let newData: any = [];
let isExistCount = 0;
//
if (diyStore.id) {
for (let i = 0; i < data.length; i++) {
if (data[i].id == diyStore.id || data[i].value.indexOf('top_fixed') != -1) {
isExistCount++;
} else {
newData.push(data[i]);
}
}
}
if (isExistCount) {
res.data.total = res.data.total - isExistCount;
}
diyPageTable.data = newData
diyPageTable.total = res.data.total
}).catch(() => {
diyPageTable.loading = false
})
}
//
let currDiyPage:any = {}
let currTabIndexForDiyPage = 0;
const handleCurrentDiyPageChange = (val: string | any[]) => {
currDiyPage = val
}
const saveDiyPageId = () => {
diyStore.editComponent.tab.list[currTabIndexForDiyPage].diy_id = currDiyPage.id;
diyStore.editComponent.tab.list[currTabIndexForDiyPage].diy_title = currDiyPage.title;
diyPageShowDialog.value = false
}
const diyPageShowDialogOpen = (index:any) => {
diyPageShowDialog.value = true
currTabIndexForDiyPage = index;
if (currDiyPage) {
setTimeout(() => {
diyPageTableRef.value!.setCurrentRow(currDiyPage)
}, 200)
}
}
watch(
() => diyStore.editComponent.swiper.list,
(newValue, oldValue) => {
//
handleHeight()
},
{ deep: true }
)
const addImageAd = () => {
diyStore.editComponent.swiper.list.push({
id: diyStore.generateRandom(),
imageUrl: '',
imgWidth: 0,
imgHeight: 0,
link: { name: '' }
})
}
const selectImg = (url:string) => {
handleHeight(true)
}
const changeSwiperStyle = (value:any) => {
handleHeight(true)
}
//
const handleHeight = (isCalcHeight:boolean = false)=> {
diyStore.editComponent.swiper.list.forEach((item: any, index: number) => {
const image = new Image()
image.src = img(item.imageUrl)
image.onload = async () => {
item.imgWidth = image.width
item.imgHeight = image.height
//
if (isCalcHeight && index == 0) {
const ratio = item.imgHeight / item.imgWidth
if(diyStore.editComponent.swiper.swiperStyle == 'style-1') {
item.width = 375 * 0.92 // 0.92
}else{
item.width = 355
}
item.height = item.width * ratio
diyStore.editComponent.swiper.imageHeight = parseInt(item.height)
}
}
})
}
defineExpose({})
</script>
<style lang="scss" scoped></style>
<style lang="scss">
.select-diy-page-input .el-input__inner{
cursor: pointer;
}
.collapse-wrap{
.el-collapse-item__header{
font-size: 16px;
}
}
</style>

View File

@ -0,0 +1,157 @@
<template>
<!-- 内容 -->
<div class="content-wrap float-btn" v-show="diyStore.editTab == 'content'">
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('floatBtnBtton') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('floatBtnBtton')">
<span>{{ selectTemplate.name }}</span>
<ul class="ml-[10px] flex items-center">
<li v-for="(item,i) in templateList" :key="i" :class="['w-[50px] h-[32px] flex items-center justify-center border-solid border-[1px] border-[#eee] cursor-pointer', {'border-r-transparent': templateList.length != (i+1)}, (item.className == diyStore.editComponent.bottomPosition) ? '!border-[var(--el-color-primary)]' : '' ]" @click="changeTemplateList(item)">
<span :class="['iconfont', item.src]"></span>
</li>
</ul>
</el-form-item>
<el-form-item :label="t('floatBtnOffset')">
<el-slider v-model="diyStore.editComponent.offset" show-input size="small" class="ml-[10px] horz-blank-slider" :max="100"/>
</el-form-item>
</el-form>
</div>
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('floatBtnImageSet') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('floatBtnImageSize')">
<el-slider v-model="diyStore.editComponent.imageSize" show-input size="small" class="ml-[10px] horz-blank-slider" :min="40" :max="100"/>
</el-form-item>
<el-form-item :label="t('floatBtnAroundRadius')">
<el-slider v-model="diyStore.editComponent.aroundRadius" show-input size="small" class="ml-[10px] graphic-nav-slider" :max="50"/>
</el-form-item>
<div class="text-[12px] text-[#999] mb-[15px] mt-[5px]">{{t('floatBtnImageSuggest')}}</div>
<div ref="imageBoxRef">
<div v-for="(item,index) in diyStore.editComponent.list" :key="item.id" class="item-wrap p-[10px] pb-0 relative border border-dashed border-gray-300 mb-[16px]">
<el-form-item :label="t('image')">
<upload-image v-model="item.imageUrl" :limit="1" />
</el-form-item>
<div class="del absolute cursor-pointer z-[2] top-[-8px] right-[-8px]" v-show="diyStore.editComponent.list.length > 1" @click="diyStore.editComponent.list.splice(index,1)">
<icon name="element-CircleCloseFilled" color="#bbb" size="20px"/>
</div>
<el-form-item :label="t('link')">
<diy-link v-model="item.link"/>
</el-form-item>
</div>
</div>
</el-form>
<el-button v-show="diyStore.editComponent.list.length < 3" class="w-full" @click="addImageAd">{{ t('addImageAd') }}</el-button>
</div>
</div>
<!-- 样式 -->
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
<!-- 组件样式 -->
<slot name="style"></slot>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, onMounted, nextTick } from 'vue'
import { t } from '@/lang'
import Sortable from 'sortablejs'
import useDiyStore from '@/stores/modules/diy'
import { img } from '@/utils/common'
import { range } from 'lodash-es'
const diyStore = useDiyStore()
diyStore.editComponent.ignore = [] //
//
diyStore.editComponent.verify = (index: number) => {
const res = { code: true, message: '' }
diyStore.value[index].list.forEach((item: any) => {
if (item.imageUrl === '') {
res.code = false
res.message = t('imageUrlTip')
return res
}
})
return res
}
const templateList = ref([
{
name: '左上',
src: 'iconzuoshangjiao',
className: 'upperLeft'
},
{
name: '右上',
src: 'iconyoushangjiao',
className: 'upperRight'
},
{
name: '左下',
src: 'iconzuoxiajiao',
className: 'lowerLeft'
},
{
name: '右下',
src: 'iconyouxiajiao',
className: 'lowerRight'
}
])
let selectTemplate = ref({})
templateList.value.forEach((item) => {
if (item.className == diyStore.editComponent.bottomPosition) {
selectTemplate.value = item
}
})
const changeTemplateList = (data: any) => {
selectTemplate.value = data;
diyStore.editComponent.bottomPosition = data.className
}
const addImageAd = () => {
diyStore.editComponent.list.push({
id: diyStore.generateRandom(),
imageUrl: '',
link: { name: '' }
})
}
const imageBoxRef = ref();
diyStore.editComponent.list.forEach((item: any) => {
if (!item.id) item.id = diyStore.generateRandom()
})
onMounted(() => {
nextTick(() => {
const imageSortable = Sortable.create(imageBoxRef.value, {
group: 'item-wrap',
animation: 200,
onEnd: event => {
const temp = diyStore.editComponent.list[event.oldIndex!]
diyStore.editComponent.list.splice(event.oldIndex!, 1)
diyStore.editComponent.list.splice(event.newIndex!, 0, temp)
imageSortable.sort(
range(diyStore.editComponent.list.length).map(value => {
return value.toString()
})
)
}
})
})
})
defineExpose({})
</script>
<style lang="scss" scoped>
</style>

View File

@ -12,18 +12,6 @@
</el-radio-group>
</el-form-item>
<el-form-item :label="t('graphicNavTitle')">
<el-input v-model="diyStore.editComponent.navTitle" :placeholder="t('graphicNavTitlePlaceholder')" clearable maxlength="20" show-word-limit/>
</el-form-item>
<el-form-item :label="t('subGraphicNavTitle')">
<el-input v-model="diyStore.editComponent.subNavTitle" :placeholder="t('subGraphicNavTitlePlaceholder')" clearable maxlength="20" show-word-limit/>
</el-form-item>
<el-form-item :label="t('subGraphicNavTitleLink')">
<diy-link v-model="diyStore.editComponent.subNavTitleLink"/>
</el-form-item>
<el-form-item :label="t('graphicNavSelectMode')">
<el-radio-group v-model="diyStore.editComponent.mode">
<el-radio :label="'graphic'">{{t('graphicNavModeGraphic')}}</el-radio>
@ -34,8 +22,15 @@
<view v-show="diyStore.editComponent.layout == 'horizontal'">
<el-form-item :label="t('graphicNavPageCount')">
<el-radio-group v-model="diyStore.editComponent.pageCount" @change="changePageCount">
<el-radio :label="1">{{t('singleLine')}}</el-radio>
<el-radio :label="2">{{t('multiline')}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('graphicNavShowStyle')">
<el-radio-group v-model="diyStore.editComponent.showStyle">
<el-radio-group v-model="diyStore.editComponent.showStyle" @change="changeShowStyle">
<el-radio :label="'fixed'">{{t('graphicNavStyleFixed')}}</el-radio>
<el-radio :label="'singleSlide'">{{t('graphicNavStyleSingleSlide')}}</el-radio>
<el-radio :label="'pageSlide'">{{t('graphicNavStylePageSlide')}}</el-radio>
@ -49,13 +44,6 @@
<el-radio :label="5">5{{t('piece')}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('graphicNavPageCount')">
<el-radio-group v-model="diyStore.editComponent.pageCount">
<el-radio :label="1">1{{t('line')}}</el-radio>
<el-radio :label="2">2{{t('line')}}</el-radio>
</el-radio-group>
</el-form-item>
</view>
</el-form>
@ -73,7 +61,7 @@
</el-form-item>
<el-form-item :label="t('graphicNavTitle')" v-show="diyStore.editComponent.mode === 'graphic' || diyStore.editComponent.mode === 'text'">
<el-input v-model="item.title" :placeholder="t('graphicNavTitlePlaceholder')" clearable maxlength="20" show-word-limit/>
<el-input v-model.trim="item.title" :placeholder="t('graphicNavTitlePlaceholder')" clearable maxlength="20" show-word-limit/>
</el-form-item>
<div class="del absolute cursor-pointer z-[2] top-[-8px] right-[-8px]" v-show="diyStore.editComponent.list.length > 1" @click="diyStore.editComponent.list.splice(index,1)">
@ -120,9 +108,6 @@
<el-form-item :label="t('textColor')">
<el-color-picker v-model="diyStore.editComponent.font.color" show-alpha :predefine="diyStore.predefineColors"/>
</el-form-item>
<el-form-item :label="t('subTextColor')">
<el-color-picker v-model="diyStore.editComponent.subNavColor" show-alpha :predefine="diyStore.predefineColors"/>
</el-form-item>
</el-form>
</div>
@ -224,6 +209,14 @@ onMounted(() => {
})
})
const changePageCount = (value:any)=>{
if(value == '1'){
diyStore.editComponent.showStyle = 'singleSlide';
}else if(value == '2'){
diyStore.editComponent.showStyle = 'fixed';
}
}
defineExpose({})
</script>

View File

@ -25,7 +25,7 @@ import { t } from '@/lang'
import useDiyStore from '@/stores/modules/diy'
const diyStore = useDiyStore()
diyStore.editComponent.ignore = ['pageBgColor', 'topRounded', 'bottomRounded', 'marginTop', 'marginBottom', 'marginBoth'] //
diyStore.editComponent.ignore = ['pageBgColor', 'componentBgUrl'] //
defineExpose({})

View File

@ -0,0 +1,50 @@
<template>
<!-- 内容 -->
<div class="content-wrap" v-show="diyStore.editTab == 'content'">
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('horzLineStyle') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('styleLabel')">
<el-radio-group v-model="diyStore.editComponent.borderStyle">
<el-radio label="solid">{{ t('horzLineStyleSolid') }}</el-radio>
<el-radio label="dashed">{{ t('horzLineStyleDashed') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('horzLineBorderColor')">
<el-color-picker v-model="diyStore.editComponent.borderColor" show-alpha :predefine="diyStore.predefineColors"/>
</el-form-item>
<el-form-item :label="t('horzLineBorderWidth')">
<el-slider v-model="diyStore.editComponent.borderWidth" show-input size="small" class="ml-[10px] horz-blank-slider" :min="1" :max="10"/>
</el-form-item>
</el-form>
</div>
</div>
<!-- 样式 -->
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
<!-- 组件样式 -->
<slot name="style"></slot>
</div>
</template>
<script lang="ts" setup>
import { t } from '@/lang'
import useDiyStore from '@/stores/modules/diy'
const diyStore = useDiyStore()
diyStore.editComponent.ignore = ['pageBgColor','componentBgColor', 'componentBgUrl', 'topRounded', 'bottomRounded'] //
defineExpose({})
</script>
<style lang="scss">
.horz-blank-slider {
.el-slider__input {
width: 100px;
}
}
</style>
<style lang="scss" scoped></style>

View File

@ -6,7 +6,7 @@
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('imageHeight')" class="display-block">
<el-input v-model="diyStore.editComponent.imageHeight" :placeholder="t('imageHeightPlaceholder')" clearable maxlength="10" @blur="blurImageHeight">
<el-input v-model.trim="diyStore.editComponent.imageHeight" :placeholder="t('imageHeightPlaceholder')" clearable maxlength="10" @blur="blurImageHeight">
<template #append>px</template>
</el-input>
<div class="text-sm text-gray-400 mb-[10px]">{{ t('imageAdsTips') }}</div>
@ -115,7 +115,7 @@ const handleHeight = (isCalcHeight:boolean = false)=> {
//
if (isCalcHeight && index == 0) {
const ratio = item.imgHeight / item.imgWidth
item.width = 375
item.width = 375 - (diyStore.editComponent.margin.both*2)
item.height = item.width * ratio
diyStore.editComponent.imageHeight = parseInt(item.height)
}

View File

@ -32,7 +32,7 @@ import { t } from '@/lang'
import useDiyStore from '@/stores/modules/diy'
const diyStore = useDiyStore()
diyStore.editComponent.ignore = [] //
diyStore.editComponent.ignore = ['componentBgUrl'] //
defineExpose({})

View File

@ -1,45 +1,83 @@
<template>
<!-- 内容 -->
<div class="content-wrap" v-show="diyStore.editTab == 'content'">
<div class="content-wrap notice-content-wrap" v-show="diyStore.editTab == 'content'">
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">公告图标</h3>
<div class="px-[22px] pb-[20px]">
<el-radio-group v-model="diyStore.editComponent.iconType" class="mb-[18px]">
<el-radio label="system">系统图标</el-radio>
<el-radio label="custom">自定义图标</el-radio>
</el-radio-group>
<div v-if="diyStore.editComponent.iconType == 'system'" class="flex items-center flex-wrap py-[8px] px-[10px] bg-[#f4f3f7] rounded">
<img src="@/app/assets/images/diy/notice/style_01.png" :class="['h-[28px] px-[10px] py-[5px] mr-[10px] rounded cursor-pointer', {'border-[1px] border-solid border-[var(--el-color-primary)]': diyStore.editComponent.systemIcon == 'style_01'}]" @click="diyStore.editComponent.systemIcon = 'style_01'" alt="">
</div>
<div v-if="diyStore.editComponent.iconType == 'custom'">
<upload-image v-model="diyStore.editComponent.imageUrl" :limit="1"/>
</div>
</div>
</div>
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">公告内容</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('title')">
<el-input v-model="diyStore.editComponent.list.text" :placeholder="t('titlePlaceholder')" clearable show-word-limit/>
</el-form-item>
<el-form-item label="点击类型">
<el-radio-group v-model="diyStore.editComponent.showType" class="mb-[18px]">
<el-radio label="popup">弹出公告内容</el-radio>
<el-radio label="link">跳出链接</el-radio>
<h3 class="mb-[10px]">{{ t('noticeStyle') }}</h3>
<el-form label-width="90px" class="px-[10px]">
<el-form-item :label="t('noticeType')">
<el-radio-group v-model="diyStore.editComponent.noticeType">
<el-radio label="img">{{ t('noticeTypeImg') }}</el-radio>
<el-radio label="text">{{ t('noticeTypeText') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('link')" class="!mb-0" v-if="diyStore.editComponent.showType == 'link'">
<diy-link v-model="diyStore.editComponent.list.link"/>
<div class="flex items-center flex-wrap py-[8px] px-[10px] bg-[#f4f3f7] rounded mb-[18px] mx-[18px]" v-show="diyStore.editComponent.noticeType == 'img'">
<div :class="['mr-[10px] rounded cursor-pointer', {'border-[1px] border-solid border-[var(--el-color-primary)]': diyStore.editComponent.systemUrl == 'style_1' && diyStore.editComponent.imgType == 'system'}]">
<img src="@/app/assets/images/diy/notice/style_1.png" :class="['h-[28px] px-[10px] py-[5px]']" @click="changeStyle('style_1')"/>
</div>
<div :class="['mr-[10px] rounded cursor-pointer w-[100px]', {'border-[1px] border-solid border-[var(--el-color-primary)]': diyStore.editComponent.systemUrl == 'style_2' && diyStore.editComponent.imgType == 'system'}]">
<img src="@/app/assets/images/diy/notice/style_2.png" class="px-[10px] py-[5px]" @click="changeStyle('style_2')"/>
</div>
<div @click="diyStore.editComponent.imgType = 'diy'" :class="['mr-[10px] rounded cursor-pointer diy-upload-img', {'border-[1px] border-solid border-[var(--el-color-primary)]': (diyStore.editComponent.imageUrl && diyStore.editComponent.imgType == 'diy') }]">
<upload-image v-model="diyStore.editComponent.imageUrl" :limit="1"/>
</div>
</div>
<el-form-item :label="t('noticeTitle')" v-show="diyStore.editComponent.noticeType == 'text'">
<el-input v-model.trim="diyStore.editComponent.noticeTitle" :placeholder="t('titlePlaceholder')" clearable maxlength="20" show-word-limit/>
</el-form-item>
</el-form>
</div>
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('noticeText') }}</h3>
<el-form label-width="90px" class="px-[10px]">
<el-form-item :label="t('noticeScrollWay')">
<el-radio-group v-model="diyStore.editComponent.scrollWay">
<el-radio label="upDown">{{ t('noticeUpDown') }}</el-radio>
<el-radio label="horizontal">{{ t('noticeHorizontal') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('noticeShowType')">
<el-radio-group v-model="diyStore.editComponent.showType">
<el-radio label="popup">{{ t('noticeShowPopUp') }}</el-radio>
<el-radio label="link">{{ t('noticeShowLink') }}</el-radio>
</el-radio-group>
</el-form-item>
<p class="text-sm text-gray-400 mb-[10px]">{{ t('dragMouseAdjustOrder') }}</p>
<div ref="noticeBoxRef">
<div v-for="(item,index) in diyStore.editComponent.list" :key="item.id" class="item-wrap p-[10px] pb-0 relative border border-dashed border-gray-300 mb-[16px]">
<el-form-item :label="t('noticeText')">
<el-input v-model.trim="item.text" :placeholder="t('noticePlaceholderText')" clearable maxlength="40" show-word-limit/>
</el-form-item>
<div class="del absolute cursor-pointer z-[2] top-[-8px] right-[-8px]" v-show="diyStore.editComponent.list.length > 1" @click="diyStore.editComponent.list.splice(index,1)">
<icon name="element-CircleCloseFilled" color="#bbb" size="20px"/>
</div>
<el-form-item :label="t('link')" v-if="diyStore.editComponent.showType == 'link'">
<diy-link v-model="item.link"/>
</el-form-item>
</div>
</div>
<el-button class="w-full" @click="addNotice">{{ t('addNotice') }}</el-button>
</el-form>
</div>
</div>
<!-- 样式 -->
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('titleStyle') }}</h3>
<h3 class="mb-[10px]">{{ t('textSet') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('textFontSize')">
<el-slider v-model="diyStore.editComponent.fontSize" show-input size="small" class="ml-[10px] article-slider" :min="12" :max="20"/>
@ -64,47 +102,112 @@
<script lang="ts" setup>
import { t } from '@/lang'
import useDiyStore from '@/stores/modules/diy'
import { ref } from 'vue'
import { ref, watch, onMounted, nextTick } from 'vue'
import { range } from 'lodash-es'
import Sortable from 'sortablejs'
const diyStore = useDiyStore()
diyStore.editComponent.ignore = [] //
const showDialog = ref(false)
//
diyStore.editComponent.verify = (index: number) => {
const res = { code: true, message: '' }
const showStyle = () => {
showDialog.value = true
if(diyStore.value[index].noticeType == 'text'){
if(diyStore.value[index].noticeTitle == ''){
res.code = false
res.message = t('noticeTypeTextPlaceholder')
return res
}
}
diyStore.value[index].list.forEach((item: any) => {
if (item.text == '') {
res.code = false
res.message = t('noticePlaceholderText')
return res
}
})
return res
}
diyStore.editComponent.list.forEach((item: any) => {
if (!item.id) item.id = diyStore.generateRandom()
})
const selectStyle = ref(diyStore.editComponent.style)
const changeStyle = () => {
switch (selectStyle.value) {
case 'style-1':
diyStore.editComponent.subTitle.control = false
diyStore.editComponent.more.control = false
diyStore.editComponent.styleName = '风格1'
break
case 'style-2':
diyStore.editComponent.subTitle.control = true
diyStore.editComponent.more.control = true
diyStore.editComponent.styleName = '风格2'
break
}
diyStore.editComponent.style = selectStyle.value
showDialog.value = false
const changeStyle = (value :any) => {
diyStore.editComponent.systemUrl = value;
diyStore.editComponent.imgType = 'system';
}
watch(
() => diyStore.editComponent.imageUrl,
(newValue, oldValue) => {
if(newValue){
diyStore.editComponent.imgType = 'diy';
}else{
diyStore.editComponent.imgType = 'system';
}
}
)
const addNotice = () => {
diyStore.editComponent.list.push({
id: diyStore.generateRandom(),
text: '公告',
link: { name: '' },
})
}
const noticeBoxRef = ref()
onMounted(() => {
nextTick(() => {
const sortable = Sortable.create(noticeBoxRef.value, {
group: 'item-wrap',
animation: 200,
onEnd: event => {
const temp = diyStore.editComponent.list[event.oldIndex!]
diyStore.editComponent.list.splice(event.oldIndex!, 1)
diyStore.editComponent.list.splice(event.newIndex!, 0, temp)
sortable.sort(
range(diyStore.editComponent.list.length).map(value => {
return value.toString()
})
)
}
})
})
})
defineExpose({})
</script>
<style lang="scss">
.horz-blank-slider {
.el-slider__input {
width: 100px;
.notice-content-wrap {
.add-notice-width {
width: calc(100% - 20px);
}
.diy-upload-img {
.image-wrap {
width: 50px !important;
height: 50px !important;
margin-right: 0 !important;
background: #fff;
}
.content-wrap {
div {
display: none;
}
}
.operation{
display: none !important;
}
}
}
.add-notice-width{
width: calc(100% - 20px);
}
</style>
<style lang="scss" scoped></style>

View File

@ -5,7 +5,7 @@
<h3 class="mb-[10px]">{{ t('pageContent') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('pageName')">
<el-input v-model="diyStore.global.title" :placeholder="t('pageNamePlaceholder')" clearable maxlength="12" show-word-limit/>
<el-input v-model.trim="diyStore.global.title" :placeholder="t('pageNamePlaceholder')" clearable maxlength="12" show-word-limit/>
</el-form-item>
<el-form-item :label="t('tabbar')" class="display-block">
<el-switch v-model="diyStore.global.bottomTabBarSwitch"/>
@ -21,8 +21,20 @@
<h3 class="mb-[10px]">{{ t('pageStyle') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('pageBgColor')">
<el-color-picker v-model="diyStore.global.pageBgColor" show-alpha :predefine="diyStore.predefineColors"/>
<el-color-picker v-model="diyStore.editComponent.pageStartBgColor" show-alpha :predefine="diyStore.predefineColors" />
<icon name="iconfont-iconmap-connect" size="20px" class="block !text-gray-400 mx-[5px]"/>
<el-color-picker v-model="diyStore.editComponent.pageEndBgColor" show-alpha :predefine="diyStore.predefineColors" />
</el-form-item>
<el-form-item :label="t('bgGradientAngle')">
<el-radio-group v-model="diyStore.editComponent.pageGradientAngle">
<el-radio label="to bottom">{{ t('topToBottom') }}</el-radio>
<el-radio label="to right">{{ t('leftToRight') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('bgHeightScale')">
<el-slider v-model="diyStore.global.bgHeightScale" show-input size="small" class="ml-[10px] horz-blank-slider"/>
</el-form-item>
<div class="text-sm text-gray-400 ml-[80px] mb-[10px]">{{ t('bgHeightScaleTip') }}</div>
<el-form-item :label="t('bgUrl')">
<upload-image v-model="diyStore.global.bgUrl" :limit="1"/>
</el-form-item>

View File

@ -0,0 +1,42 @@
<template>
<!-- 内容 -->
<div class="content-wrap" v-show="diyStore.editTab == 'content'">
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('richTextContentSet') }}</h3>
<editor v-model="diyStore.editComponent.html" height="600px" class="editor-width" />
</div>
</div>
<!-- 样式 -->
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
<!-- 组件样式 -->
<slot name="style"></slot>
</div>
</template>
<script lang="ts" setup>
import { t } from '@/lang'
import useDiyStore from '@/stores/modules/diy'
const diyStore = useDiyStore()
diyStore.editComponent.ignore = [] //
//
diyStore.editComponent.verify = (index: number) => {
const res = { code: true, message: '' }
if(diyStore.value[index].html == '<p><br></p>'){
res.code = false
res.message = t('richTextPlaceholder')
return res
}
return res
}
defineExpose({})
</script>
<style lang="scss" scoped></style>

View File

@ -16,7 +16,7 @@
<h3 class="mb-[10px]">{{ t('titleContent') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('title')">
<el-input v-model="diyStore.editComponent.text" :placeholder="t('titlePlaceholder')" clearable maxlength="15" show-word-limit />
<el-input v-model.trim="diyStore.editComponent.text" :placeholder="t('titlePlaceholder')" clearable maxlength="15" show-word-limit />
</el-form-item>
<el-form-item :label="t('link')">
<diy-link v-model="diyStore.editComponent.link" />
@ -34,7 +34,7 @@
<h3 class="mb-[10px]">{{ t('subTitleContent') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('subTitle')">
<el-input v-model="diyStore.editComponent.subTitle.text" :placeholder="t('subTitlePlaceholder')" clearable maxlength="30" show-word-limit />
<el-input v-model.trim="diyStore.editComponent.subTitle.text" :placeholder="t('subTitlePlaceholder')" clearable maxlength="30" show-word-limit />
</el-form-item>
<el-form-item :label="t('textFontSize')">
<el-slider v-model="diyStore.editComponent.subTitle.fontSize" show-input size="small" class="ml-[10px] article-slider" :min="12" :max="16" />
@ -49,7 +49,7 @@
<h3 class="mb-[10px]">{{ t('moreContent') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('more')">
<el-input v-model="diyStore.editComponent.more.text" :placeholder="t('morePlaceholder')" clearable maxlength="8" show-word-limit />
<el-input v-model.trim="diyStore.editComponent.more.text" :placeholder="t('morePlaceholder')" clearable maxlength="8" show-word-limit />
</el-form-item>
<el-form-item :label="t('link')">
<diy-link v-model="diyStore.editComponent.more.link" />
@ -63,13 +63,13 @@
</el-form>
</div>
<el-dialog v-model="showDialog" :title="t('selectStyle')" width="40%">
<el-dialog v-model="showDialog" :title="t('selectStyle')" width="620px">
<div class="flex flex-wrap">
<div class="flex items-center justify-center overflow-hidden w-[280px] h-[100px] mr-[12px] cursor-pointer border bg-gray-50" :class="{ 'border-primary': selectStyle == 'style-1' }" @click="selectStyle = 'style-1'">
<img class="max-w-[280px] max-h-[220px]" src="@/app/assets/images/diy/text/style1.png" />
</div>
<div class="flex items-center justify-center overflow-hidden w-[280px] h-[100px] mr-[12px] cursor-pointer border bg-gray-50" :class="{ 'border-primary': selectStyle == 'style-2' }" @click="selectStyle = 'style-2'">
<div class="flex items-center justify-center overflow-hidden w-[280px] h-[100px] cursor-pointer border bg-gray-50" :class="{ 'border-primary': selectStyle == 'style-2' }" @click="selectStyle = 'style-2'">
<img class="max-w-[280px] max-h-[220px]" src="@/app/assets/images/diy/text/style2.png" />
</div>
</div>
@ -91,7 +91,7 @@
<h3 class="mb-[10px]">{{ t('titleStyle') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('textFontSize')">
<el-slider v-model="diyStore.editComponent.fontSize" show-input size="small" class="ml-[10px] article-slider" :min="12" :max="20" />
<el-slider v-model="diyStore.editComponent.fontSize" show-input size="small" class="ml-[10px] article-slider" :min="12" :max="30" />
</el-form-item>
<el-form-item :label="t('textFontWeight')">
<el-radio-group v-model="diyStore.editComponent.fontWeight">

View File

@ -5,16 +5,17 @@
<el-icon size="14">
<ArrowLeft />
</el-icon>
<span class="pl-[5px]">{{ t('back') }}</span>
<span class="pl-[5px] text-[14px]">{{ t('back') }}</span>
</div>
<div class="text-white ml-[10px] mr-[20px] flex items-center">
<span class="mr-[5px]"> {{ t('decorating') }}{{ diyStore.typeName }}</span>
<span class="mr-[5px] text-[rgba(255,255,255,.5)]"></span>
<span class="mr-[5px] text-[14px]">{{ t('decorating') }}{{ diyStore.typeName }}</span>
<!--<el-icon class="font-bold"><EditPen /></el-icon>-->
</div>
<div v-if="diyStore.type && diyStore.type != 'DIY_PAGE'">
<span class="text-white mr-[10px] text-base">{{ t('templatePagePlaceholder') }}</span>
<el-select v-model="template" class="w-[180px]" :placeholder="t('templatePagePlaceholder')" @change="changeTemplatePage">
<el-select size="small" v-model="template" class="w-[180px]" :placeholder="t('templatePagePlaceholder')" @change="changeTemplatePage">
<el-option :label="t('templatePageEmpty')" value="" />
<el-option v-for="(item, key) in templatePages" :label="item.title" :value="key" :key="key"/>
</el-select>
@ -33,9 +34,10 @@
<el-collapse v-model="activeNames" @change="handleChange">
<el-collapse-item v-for="(item, key) in component" :key="key" :title="item.title" :name="key">
<ul class="flex flex-row flex-wrap">
<li v-for="(compItem, compKey) in item.list" :key="compKey" class="w-2/6 text-center cursor-pointer h-[75px]" :title="compItem.title" @click="diyStore.addComponent(compKey, compItem)">
<icon :name="compItem.icon" size="23px" />
<span class="block text-base truncate">{{ compItem.title }}</span>
<li v-for="(compItem, compKey) in item.list" :key="compKey" class="w-2/6 text-center cursor-pointer h-[65px]" :title="compItem.title" @click="diyStore.addComponent(compKey, compItem)">
<icon v-if="compItem.icon" :name="compItem.icon" size="20px" class="inline-block mt-[3px]" />
<icon v-else name="iconfont-iconkaifazujian" size="20px" class="inline-block mt-[3px]" />
<span class="block text-[12px] truncate">{{ compItem.title }}</span>
</li>
</ul>
</el-collapse-item>
@ -111,14 +113,45 @@
<template #style>
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('componentStyleTitle') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('bottomBgColor')" class="display-block" v-if="diyStore.editComponent.ignore.indexOf('pageBgColor') == -1">
<el-color-picker v-model="diyStore.editComponent.pageBgColor" show-alpha :predefine="diyStore.predefineColors" />
<div class="text-sm text-gray-400">{{ t('bottomBgTips') }}</div>
<el-form label-width="90px" class="px-[10px]">
<template v-if="diyStore.editComponent.ignore.indexOf('pageBgColor') == -1">
<el-form-item :label="t('bottomBgColor')">
<el-color-picker v-model="diyStore.editComponent.pageStartBgColor" show-alpha :predefine="diyStore.predefineColors" />
<icon name="iconfont-iconmap-connect" size="20px" class="block !text-gray-400 mx-[5px]"/>
<el-color-picker v-model="diyStore.editComponent.pageEndBgColor" show-alpha :predefine="diyStore.predefineColors" />
</el-form-item>
<div class="text-sm text-gray-400 ml-[80px] mb-[10px]">{{ t('bottomBgTips') }}</div>
</template>
<el-form-item :label="t('bgGradientAngle')" v-if="diyStore.editComponent.ignore.indexOf('pageBgColor') == -1">
<el-radio-group v-model="diyStore.editComponent.pageGradientAngle">
<el-radio label="to bottom">{{ t('topToBottom') }}</el-radio>
<el-radio label="to right">{{ t('leftToRight') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('componentBgUrl')" v-if="diyStore.editComponent.ignore.indexOf('componentBgUrl') == -1">
<upload-image v-model="diyStore.editComponent.componentBgUrl" :limit="1"/>
</el-form-item>
<el-form-item :label="t('componentBgAlpha')" v-if="diyStore.editComponent.ignore.indexOf('componentBgUrl') == -1 && diyStore.editComponent.componentBgUrl">
<el-slider v-model="diyStore.editComponent.componentBgAlpha" show-input size="small" :min="0" :max="10" class="ml-[10px] horz-blank-slider" />
</el-form-item>
<el-form-item :label="t('componentBgColor')" v-if="diyStore.editComponent.ignore.indexOf('componentBgColor') == -1">
<el-color-picker v-model="diyStore.editComponent.componentBgColor" show-alpha :predefine="diyStore.predefineColors" />
<el-color-picker v-model="diyStore.editComponent.componentStartBgColor" show-alpha :predefine="diyStore.predefineColors" />
<icon name="iconfont-iconmap-connect" size="20px" class="block !text-gray-400 mx-[5px]"/>
<el-color-picker v-model="diyStore.editComponent.componentEndBgColor" show-alpha :predefine="diyStore.predefineColors" />
</el-form-item>
<el-form-item :label="t('bgGradientAngle')" v-if="diyStore.editComponent.ignore.indexOf('componentBgColor') == -1">
<el-radio-group v-model="diyStore.editComponent.componentGradientAngle">
<el-radio label="to bottom">{{ t('topToBottom') }}</el-radio>
<el-radio label="to right">{{ t('leftToRight') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('marginTop')" v-if="diyStore.editComponent.ignore.indexOf('marginTop') == -1">
<el-slider v-model="diyStore.editComponent.margin.top" show-input size="small" :min="0" class="ml-[10px] horz-blank-slider" />
</el-form-item>
@ -138,33 +171,6 @@
</div>
</template>
</component>
<div class="edit-attr-item-wrap" v-else>
<h3 class="mb-[10px]">{{ t('componentStyleTitle') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('bottomBgColor')" class="display-block" v-if="diyStore.editComponent.ignore.indexOf('pageBgColor') == -1">
<el-color-picker v-model="diyStore.editComponent.pageBgColor" show-alpha :predefine="diyStore.predefineColors" />
<div class="text-sm text-gray-400">{{ t('bottomBgTips') }}</div>
</el-form-item>
<el-form-item :label="t('componentBgColor')" v-if="diyStore.editComponent.ignore.indexOf('componentBgColor') == -1">
<el-color-picker v-model="diyStore.editComponent.componentBgColor" show-alpha :predefine="diyStore.predefineColors" />
</el-form-item>
<el-form-item :label="t('marginTop')" v-if="diyStore.editComponent.ignore.indexOf('marginTop') == -1">
<el-slider v-model="diyStore.editComponent.margin.top" show-input size="small" :min="0" class="ml-[10px] horz-blank-slider" />
</el-form-item>
<el-form-item :label="t('marginBottom')" v-if="diyStore.editComponent.ignore.indexOf('marginBottom') == -1">
<el-slider v-model="diyStore.editComponent.margin.bottom" show-input size="small" class="ml-[10px] horz-blank-slider" />
</el-form-item>
<el-form-item :label="t('marginBoth')" v-if="diyStore.editComponent.ignore.indexOf('marginBoth') == -1">
<el-slider v-model="diyStore.editComponent.margin.both" show-input size="small" class="ml-[10px] horz-blank-slider" />
</el-form-item>
<el-form-item :label="t('topRounded')" v-if="diyStore.editComponent.ignore.indexOf('topRounded') == -1">
<el-slider v-model="diyStore.editComponent.topRounded" show-input size="small" class="ml-[10px] horz-blank-slider" :max="50" />
</el-form-item>
<el-form-item :label="t('bottomRounded')" v-if="diyStore.editComponent.ignore.indexOf('bottomRounded') == -1">
<el-slider v-model="diyStore.editComponent.bottomRounded" show-input size="small" class="ml-[10px] horz-blank-slider" :max="50" />
</el-form-item>
</el-form>
</div>
</div>
@ -299,6 +305,7 @@ const changeTemplatePage = (value:any)=> {
cancelButtonText: t('cancel'),
type: 'warning'
}).then(() => {
diyStore.changeCurrentIndex(-99)
if (value) {
let data = templatePages[value].data;
diyStore.global = data.global;
@ -427,28 +434,46 @@ initPage({
wapDomain.value = data.domain_url.wap_domain
wapUrl.value = data.domain_url.wap_url
page.value = data.page
setDomain()
//
if (import.meta.env.MODE == 'production') return
let repeat = true; //
// envwap
if (wapDomain.value) {
wapUrl.value = wapDomain.value + '/wap'
//
if (import.meta.env.MODE == 'development') {
// envwap
if (wapDomain.value) {
wapUrl.value = wapDomain.value + '/wap'
repeat = false
setDomain()
}
let wap_domain_storage = storage.get('wap_domain')
if (wap_domain_storage) {
wapUrl.value = wap_domain_storage
repeat = false
setDomain()
}
}
if(repeat) {
setDomain()
}
let wap_domain_storage = storage.get('wap_domain')
if (wap_domain_storage) {
wapUrl.value = wap_domain_storage
setDomain()
}
})
const uniAppLoadStatus = ref(false) // uni-app truefalse
// uni-app
window.addEventListener('message', (event) => {
try {
const data = JSON.parse(event.data)
let data = {
type: ''
};
if(typeof event.data == 'string') {
data = JSON.parse(event.data)
}else if(typeof event.data == 'object') {
data = event.data
}
if (!data.type) return
switch (data.type) {
@ -459,6 +484,7 @@ window.addEventListener('message', (event) => {
loadingIframe.value = true
let loadTime = new Date().getTime()
difference.value = loadTime - timeIframe.value
uniAppLoadStatus.value = true //
break
case 'init':
// uniapp
@ -477,7 +503,7 @@ window.addEventListener('message', (event) => {
break
}
} catch (e) {
console.log('后台接受数据错误', e)
console.log('diy edit 后台接受数据错误', e)
}
}, false)
@ -499,11 +525,32 @@ const saveWapDomain = () => {
const setDomain = () => {
wapPreview.value = `${wapUrl.value}${page.value}?mode=decorate` // decorate 访
timeIframe.value = new Date().getTime()
postMessage()
const send = ()=>{
timeIframe.value = new Date().getTime()
postMessage()
}
//
send()
// uni-app
let sendCount = 0;
let timeInterVal = setInterval(()=>{
// uni-app 50
if(uniAppLoadStatus.value || sendCount >= 50){
clearInterval(timeInterVal)
return
}
send()
sendCount++;
},200)
// 10
setTimeout(() => {
if (difference.value == 0) initLoad()
}, 1000 * 2)
}, 1000 * 10)
}
// uniapp

View File

@ -1,5 +1,5 @@
<template>
<div class="flex flex-wrap mt-[20px] min-w-[1000px]" v-if="page.use_template">
<div class="flex flex-wrap mt-[20px] min-w-[1200px]" v-if="page.use_template">
<div class="page-item relative bg-no-repeat ml-[20px] mr-[40px] bg-[#f7f7f7] w-[340px] pt-[90px] pb-[20px]">
<p class="absolute top-[54px] left-[50%] translate-x-[-50%] text-[14px] truncate w-[130px] text-center">{{ page.use_template.title }}</p>
@ -26,7 +26,7 @@
</div>
<div class="w-[500px]">
<div class="w-[700px]">
<div class="flex flex-wrap">
<diy-link v-model="link" :ignore="['DIY_LINK']" @success="changePage">
<el-button type="primary">{{ t('changePage') }}</el-button>
@ -41,7 +41,7 @@
<div class="font-bold">{{ t('H5') }}</div>
<el-form label-width="40px" class="mt-[5px]">
<el-form-item :label="t('link')" class="mb-[5px]">
<el-input readonly :value="page.shareUrl">
<el-input readonly :value="page.shareUrl" class="!w-[390px]">
<template #append>
<el-button @click="copyEvent(page.shareUrl)" class="bg-primary copy">{{ t('copy') }}</el-button>
</template>
@ -118,39 +118,55 @@ const refreshData = () => {
wapDomain.value = page.domain_url.wap_domain
page.wapUrl = page.domain_url.wap_url
let repeat = true; //
if (import.meta.env.MODE == 'development') {
// wap
if (wapDomain.value) {
page.wapUrl = wapDomain.value + '/wap'
repeat = false
setDomain()
}
if (storage.get('wap_domain')) {
page.wapUrl = storage.get('wap_domain')
repeat = false
setDomain()
}
}
setDomain()
if(repeat) {
setDomain()
}
}
})
}
refreshData()
const uniAppLoadStatus = ref(false) // uni-app truefalse
// uni-app
window.addEventListener('message', (event) => {
try {
const data = JSON.parse(event.data)
if (['appOnLaunch', 'appOnReady'].indexOf(data.type) != -1) {
let data = {
type :''
};
if(typeof event.data == 'string') {
data = JSON.parse(event.data)
}else if(typeof event.data == 'object') {
data = event.data
}
if (data.type && ['appOnLaunch', 'appOnReady'].indexOf(data.type) != -1) {
page.loadingDev = false //
page.loadingIframe = true // iframe
const loadTime = new Date().getTime()
let loadTime = new Date().getTime()
page.difference = loadTime - page.timeIframe
page.isDisabledPop = false //
uniAppLoadStatus.value = true //
}
} catch (e) {
initLoad()
console.log('后台接受数据错误', e)
console.log('diy index 后台接受数据错误', e)
}
}, false)
@ -204,11 +220,32 @@ const setDomain = () => {
QRCode.toDataURL(page.shareUrl, { errorCorrectionLevel: 'L', margin: 0, width: 100 }).then(url => {
wapImage.value = url
})
page.timeIframe = new Date().getTime()
postMessage()
const send = ()=>{
page.timeIframe = new Date().getTime()
postMessage()
}
//
send()
// uni-app
let sendCount = 0;
let timeInterVal = setInterval(()=>{
// uni-app 50
if(uniAppLoadStatus.value || sendCount >= 50){
clearInterval(timeInterVal)
return
}
send()
sendCount++;
},200)
// 10
setTimeout(() => {
if (page.difference == 0) initLoad()
}, 1000 * 2)
}, 1000 * 10)
}
//

View File

@ -1,5 +1,5 @@
<template>
<div class="flex flex-wrap mt-[20px] min-w-[1000px]" v-if="page.use_template">
<div class="flex flex-wrap mt-[20px] min-w-[1200px]" v-if="page.use_template">
<div class="page-item relative bg-no-repeat ml-[20px] mr-[40px] bg-[#f7f7f7] w-[340px] pt-[90px] pb-[20px]">
<p class="absolute top-[54px] left-[50%] translate-x-[-50%] text-[14px] truncate w-[130px] text-center">{{ page.use_template.title }}</p>
@ -26,7 +26,7 @@
</div>
<div class="w-[500px]">
<div class="w-[700px]">
<div class="flex flex-wrap">
<!-- 多应用切换启动页 -->
<el-button type="primary" @click="showDialog = true" v-if="siteApps.length > 1">{{ t('changePage') }}</el-button>
@ -40,7 +40,7 @@
<div class="font-bold">{{ t('H5') }}</div>
<el-form label-width="40px" class="mt-[5px]">
<el-form-item :label="t('link')" class="mb-[5px]">
<el-input readonly :value="page.shareUrl">
<el-input readonly :value="page.shareUrl" class="!w-[400px]">
<template #append>
<el-button @click="copyEvent(page.shareUrl)" class="bg-primary copy">{{ t('copy') }}</el-button>
</template>
@ -152,39 +152,56 @@ const refreshData = () => {
wapDomain.value = page.domain_url.wap_domain
page.wapUrl = page.domain_url.wap_url
let repeat = true; //
if (import.meta.env.MODE == 'development') {
// wap
if (wapDomain.value) {
page.wapUrl = wapDomain.value + '/wap'
repeat = false
setDomain()
}
if (storage.get('wap_domain')) {
page.wapUrl = storage.get('wap_domain')
repeat = false
setDomain()
}
}
setDomain()
if(repeat) {
setDomain()
}
}
})
}
refreshData()
const uniAppLoadStatus = ref(false) // uni-app truefalse
// uni-app
window.addEventListener('message', (event) => {
try {
const data = JSON.parse(event.data)
if (['appOnLaunch', 'appOnReady'].indexOf(data.type) != -1) {
let data = {
type: ''
};
if(typeof event.data == 'string') {
data = JSON.parse(event.data)
}else if(typeof event.data == 'object') {
data = event.data
}
if (data.type && ['appOnLaunch', 'appOnReady'].indexOf(data.type) != -1) {
page.loadingDev = false //
page.loadingIframe = true // iframe
const loadTime = new Date().getTime()
let loadTime = new Date().getTime()
page.difference = loadTime - page.timeIframe
page.isDisabledPop = false //
uniAppLoadStatus.value = true //
}
} catch (e) {
initLoad()
console.log('后台接受数据错误', e)
console.log('diy member 后台接受数据错误', e)
}
}, false)
@ -238,11 +255,32 @@ const setDomain = () => {
QRCode.toDataURL(page.shareUrl, { errorCorrectionLevel: 'L', margin: 0, width: 100 }).then(url => {
wapImage.value = url
})
page.timeIframe = new Date().getTime()
postMessage()
const send = ()=>{
page.timeIframe = new Date().getTime()
postMessage()
}
//
send()
// uni-app
let sendCount = 0;
let timeInterVal = setInterval(()=>{
// uni-app 50
if(uniAppLoadStatus.value || sendCount >= 50){
clearInterval(timeInterVal)
return
}
send()
sendCount++;
},200)
// 10
setTimeout(() => {
if (page.difference == 0) initLoad()
}, 1000 * 2)
}, 1000 * 10)
}
//

View File

@ -9,8 +9,7 @@
<el-row class="flex">
<el-col :span="8" class="min-w-[100px]">
<div class="statistic-card">
<el-statistic
:value="accountStat.pay ? accountStat.pay.toFixed(2) : '0.00'"></el-statistic>
<el-statistic :value="accountStat.pay ? accountStat.pay.toFixed(2) : '0.00'"></el-statistic>
<div class="statistic-footer">
<div class="footer-item text-[14px] text-[#666]">
<span>{{ t('totalPay') }}</span>
@ -20,8 +19,7 @@
</el-col>
<el-col :span="8" class="min-w-[100px]">
<div class="statistic-card">
<el-statistic
:value="accountStat.refund ? accountStat.refund.toFixed(2) : '0.00'"></el-statistic>
<el-statistic :value="accountStat.refund ? accountStat.refund.toFixed(2) : '0.00'"></el-statistic>
<div class="statistic-footer">
<div class="footer-item text-[14px] text-[#666]">
<span>{{ t('totalRefund') }}</span>
@ -31,8 +29,7 @@
</el-col>
<el-col :span="8" class="min-w-[100px]">
<div class="statistic-card">
<el-statistic
:value="accountStat.transfer ? accountStat.transfer.toFixed(2) : '0.00'"></el-statistic>
<el-statistic :value="accountStat.transfer ? accountStat.transfer.toFixed(2) : '0.00'"></el-statistic>
<div class="statistic-footer">
<div class="footer-item text-[14px] text-[#666]">
<span>{{ t('totalTransfer') }}</span>
@ -45,15 +42,13 @@
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
<el-form :inline="true" :model="siteAccountLogTable.searchParam" ref="searchFormRef">
<el-form-item :label="t('type')" class="items-center" prop="type">
<el-select v-model="siteAccountLogTable.searchParam.type" class="m-2"
:placeholder="t('accountType')">
<el-select v-model="siteAccountLogTable.searchParam.type" class="m-2" :placeholder="t('accountType')">
<el-option :label="t('all')" value="" />
<el-option v-for="(item, index) in accountType" :key="index" :label="item" :value="index" />
</el-select>
</el-form-item>
<el-form-item :label="t('tradeNo')" prop="trade_no">
<el-input v-model="siteAccountLogTable.searchParam.trade_no"
:placeholder="t('tradeNoPlaceholder')" />
<el-input v-model="siteAccountLogTable.searchParam.trade_no" :placeholder="t('tradeNoPlaceholder')" />
</el-form-item>
<el-form-item :label="t('createTime')" prop="create_time">
<el-date-picker v-model="siteAccountLogTable.searchParam.create_time" type="datetimerange"
@ -100,9 +95,9 @@
<el-dialog v-model="showDialog" :title="t('accountDetail')" width="550px" :destroy-on-close="true">
<el-form :model="formData" label-width="110px" ref="formRef" class="page-form">
<el-form-item :label="t('tradeNo')">
<div class="input-width"> {{ formData.trade_no }} </div>
</el-form-item>
<!-- <el-form-item :label="t('tradeNo')">-->
<!-- <div class="input-width"> {{ formData.trade_no }} </div>-->
<!-- </el-form-item>-->
<el-form-item :label="t('type')">
<div class="input-width"> {{ formData.type_name }} </div>
</el-form-item>
@ -173,7 +168,6 @@
import { reactive, ref } from 'vue'
import { t } from '@/lang'
import { getAccountList, getAccountStat, getAccountType } from '@/app/api/site'
// import { img } from '@/utils/common'
import type { FormInstance } from 'element-plus'
import { useRoute } from 'vue-router'

View File

@ -53,7 +53,7 @@
</el-select>
</el-form-item>
<el-form-item :label="t('cashOutStatus')" prop="order_from">
<el-form-item :label="t('cashOutStatus')" prop="status">
<el-select v-model="orderTableData.searchParam.status" clearable class="input-width">
<el-option :label="t('selectPlaceholder')" value="" />
<el-option :label="item" :value="key" v-for="(item, key) in cashOutStatusList" :key="key"/>

View File

@ -1,7 +1,7 @@
<template>
<div class="main-container">
<div class="detail-head">
<div class="left" @click="router.push({ path: '/member/refund' })">
<div class="left" @click="router.push({ path: '/finance/refund' })">
<span class="iconfont iconxiangzuojiantou !text-xs"></span>
<span class="ml-[1px]">{{ t('returnToPreviousPage') }}</span>
</div>

View File

@ -83,24 +83,33 @@ setLayout('decorate')
getUrl().then((res: any) => {
wapUrl.value = res.data.wap_url
setDomain()
//
if (import.meta.env.MODE == 'production') return
let repeat = true; //
wapDomain.value = res.data.wap_domain
//
if (import.meta.env.MODE == 'development') {
// envwap
if (wapDomain.value) {
wapUrl.value = wapDomain.value + '/wap'
wapDomain.value = res.data.wap_domain
// envwap
if (wapDomain.value) {
wapUrl.value = wapDomain.value + '/wap'
repeat = false
setDomain()
}
const wap_domain_storage = storage.get('wap_domain')
if (wap_domain_storage) {
wapUrl.value = wap_domain_storage
repeat = false
setDomain()
}
}
if(repeat) {
setDomain()
}
const wap_domain_storage = storage.get('wap_domain')
if (wap_domain_storage) {
wapUrl.value = wap_domain_storage
setDomain()
}
})
const save = () => {
@ -125,26 +134,58 @@ const setDomain = () => {
QRCode.toDataURL(wapPreview.value, { errorCorrectionLevel: 'L', margin: 0, width: 100 }).then(url => {
wapImage.value = url
})
timeIframe.value = new Date().getTime()
const send = ()=>{
timeIframe.value = new Date().getTime()
postMessage()
}
//
send()
// uni-app
let sendCount = 0;
let timeInterVal = setInterval(()=>{
// uni-app 50
if(uniAppLoadStatus.value || sendCount >= 50){
clearInterval(timeInterVal)
return
}
send()
sendCount++;
},200)
// 10
setTimeout(() => {
if (difference.value == 0) initLoad()
}, 1000 * 2)
}, 1000 * 10)
}
}
const uniAppLoadStatus = ref(false) // uni-app truefalse
// uni-app
window.addEventListener('message', (event) => {
try {
const data = JSON.parse(event.data)
if (['appOnLaunch', 'appOnReady'].indexOf(data.type) != -1) {
let data = {
type :''
};
if(typeof event.data == 'string') {
data = JSON.parse(event.data)
}else if(typeof event.data == 'object') {
data = event.data
}
if (data.type && ['appOnLaunch', 'appOnReady'].indexOf(data.type) != -1) {
loadingDev.value = false
loadingIframe.value = true
const loadTime = new Date().getTime()
let loadTime = new Date().getTime()
uniAppLoadStatus.value = true //
difference.value = loadTime - timeIframe.value
}
} catch (e) {
initLoad()
console.log('后台接受数据错误', e)
console.log('preview 后台接受数据错误', e)
}
}, false)

View File

@ -2,13 +2,13 @@
<el-dialog v-model="showDialog" :title="popTitle" width="500px" :destroy-on-close="true">
<el-form :model="formData" label-width="90px" ref="formRef" :rules="formRules" class="page-form" v-loading="loading">
<el-form-item :label="t('labelName')" prop="label_name">
<el-input v-model="formData.label_name" clearable :placeholder="t('labelNamePlaceholder')" class="input-width" />
<el-input v-model.trim="formData.label_name" clearable :placeholder="t('labelNamePlaceholder')" class="input-width" />
</el-form-item>
<el-form-item :label="t('memo')">
<el-input v-model="formData.memo" type="textarea" rows="4" clearable :placeholder="t('memoPlaceholder')" class="input-width"/>
<el-input v-model.trim="formData.memo" type="textarea" rows="4" clearable :placeholder="t('memoPlaceholder')" class="input-width" maxlength="200" />
</el-form-item>
<el-form-item :label="t('sort')" prop="sort">
<el-input v-model="formData.sort" clearable :placeholder="t('sortPlaceholder')" class="input-width" @keyup="filterNumber($event)" />
<el-input v-model.trim="formData.sort" clearable :placeholder="t('sortPlaceholder')" class="input-width" @keyup="filterNumber($event)" />
</el-form-item>
</el-form>

View File

@ -9,7 +9,7 @@
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
<el-form :inline="true" :model="memberLabelTableData.searchParam" ref="searchFormRef">
<el-form-item :label="t('labelName')" prop="label_name">
<el-input v-model="memberLabelTableData.searchParam.label_name" :placeholder="t('labelNamePlaceholder')" />
<el-input v-model.trim="memberLabelTableData.searchParam.label_name" :placeholder="t('labelNamePlaceholder')" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadMemberLabelList()">{{ t('search') }}</el-button>

View File

@ -1,111 +0,0 @@
<template>
<div class="main-container">
<el-form :model="formData" label-width="150px" ref="formRef" class="page-form" v-loading="loading">
<el-card class="box-card !border-none relative" shadow="never" v-if="formData">
<h3 class="panel-title">{{ t('orderInfo') }}</h3>
<el-form-item :label="t('orderNo')">
<div class="input-width">{{ formData.order_no }}</div>
</el-form-item>
<el-form-item :label="t('orderMoney')">
<div class="input-width">{{ formData.order_money }}</div>
</el-form-item>
<el-form-item :label="t('orderDiscountMoney')">
<div class="input-width">{{ formData.order_discount_money }}</div>
</el-form-item>
<el-form-item :label="t('member')">
<div class="input-width">
<div class="flex flex flex-col cursor-pointer" @click="toMember(formData.member_id)">
<span>{{ formData.member.nickname || '' }}</span>
<span>{{ formData.member.mobile || '' }}</span>
</div>
</div>
</el-form-item>
<el-form-item :label="t('ip')">
<div class="input-width">{{ formData.ip }}</div>
</el-form-item>
<el-form-item :label="t('orderFromName')">
<div class="input-width">{{ formData.order_from_name }}</div>
</el-form-item>
<el-form-item :label="t('orderStatus')">
<div class="input-width">{{ formData.order_status_info.name }}</div>
</el-form-item>
<el-form-item :label="t('payTypeName')">
<div class="input-width">{{ formData.pay_type_name }}</div>
</el-form-item>
<el-form-item :label="t('createTime')">
<div class="input-width">{{ formData.create_time || '' }}</div>
</el-form-item>
<el-form-item :label="t('payTime')">
<div class="input-width">{{ formData.pay_time || '' }}</div>
</el-form-item>
<el-form-item :label="t('remark')">
<div class="input-width">{{ formData.remark || '' }}</div>
</el-form-item>
<el-form-item :label="t('memberMessage')">
<div class="input-width">{{ formData.member_message || '' }}</div>
</el-form-item>
</el-card>
</el-form>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { t } from '@/lang'
import type { FormInstance } from 'element-plus'
import { getRechargeOrderInfo } from '@/app/api/order'
import { useRoute, useRouter } from 'vue-router'
import useTabbarStore from '@/stores/modules/tabbar'
// import useAppStore from '@/stores/modules/app'
const tabbarStore = useTabbarStore()
const route = useRoute()
const router = useRouter()
const orderId: number = parseInt(route.query.order_id)
const loading = ref(true)
const formData: Record<string, any> | null = ref(null)
const setFormData = async (orderId: number = 0) => {
loading.value = true
formData.value = null
await getRechargeOrderInfo(orderId).then(({data}) => {
formData.value = data
}).catch(() => {
})
loading.value = false
}
if (orderId) setFormData(orderId)
else loading.value = false
const formRef = ref<FormInstance>()
const back = () => {
tabbarStore.removeTab(route.path)
router.push({ path: '/finance/recharge' })
}
/**
* 会员详情
*/
const toMember = (memberId: number) => {
router.push(`/member/detail?id=${memberId}`)
}
</script>
<style lang="scss" scoped></style>

View File

@ -1,290 +0,0 @@
<template>
<div class="main-container">
<el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between items-center mb-[5px]">
<span class="text-page-title">{{ pageName }}</span>
</div>
<el-card class="box-card !border-none table-search-wra base-bg !px-[35px]" shadow="never">
<el-row class="flex">
<el-col :span="12" class="min-w-[100px]">
<el-statistic
:value="rechargeStatistics.recharge_money ? Number.parseFloat(rechargeStatistics.recharge_money).toFixed(2) : '0.00'">
<template #title>
<div class="text-[14px] mb-[9px]">{{ t('totalRechargeMoney') }}</div>
</template>
</el-statistic>
</el-col>
<el-col :span="12" class="min-w-[100px]">
<el-statistic
:value="rechargeStatistics.recharge_refund_money ? Number.parseFloat(rechargeStatistics.recharge_refund_money).toFixed(2) : '0.00'">
<template #title>
<div class="text-[14px] mb-[9px]">{{ t('totalRechargeRefundMoney') }}</div>
</template>
</el-statistic>
</el-col>
</el-row>
</el-card>
<el-card class="box-card !border-none mb-[10px] table-search-wrap" shadow="never">
<el-form :inline="true" :model="orderTableData.searchParam" ref="searchFormRef">
<el-form-item :label="t('rechargeNo')" prop="order_no">
<el-input v-model="orderTableData.searchParam.order_no" :placeholder="t('rechargeNoPlaceholder')" />
</el-form-item>
<el-form-item :label="t('orderFromName')" prop="order_from">
<el-select v-model="orderTableData.searchParam.order_from" clearable class="input-width">
<el-option :label="t('selectPlaceholder')" value="" />
<el-option :label="item" :value="key" v-for="(item, key) in channelList" :key="key"/>
</el-select>
</el-form-item>
<el-form-item :label="t('orderStatus')" prop="order_status">
<el-select v-model="orderTableData.searchParam.order_status" clearable class="input-width">
<el-option :label="t('selectPlaceholder')" value="" />
<el-option :label="item['name']" :value="item['status']" v-for="(item,index) in statusList" :key="index"/>
</el-select>
</el-form-item>
<el-form-item :label="t('createTime')" prop="create_time">
<el-date-picker v-model="orderTableData.searchParam.create_time" type="datetimerange"
value-format="YYYY-MM-DD HH:mm:ss" :start-placeholder="t('startDate')"
:end-placeholder="t('endDate')" />
</el-form-item>
<el-form-item :label="t('rechargeMoney')">
<div class="region-input">
<el-form-item prop="start_money">
<input type="text" :placeholder="t('startMoney')" v-model="orderTableData.searchParam.start_money">
</el-form-item>
<span class="separator">-</span>
<el-form-item prop="end_money">
<input type="text" :placeholder="t('endMoney')" v-model="orderTableData.searchParam.end_money">
</el-form-item>
</div>
</el-form-item>
<el-form-item :label="t('payTime')">
<el-date-picker v-model="orderTableData.searchParam.pay_time" type="datetimerange"
value-format="YYYY-MM-DD HH:mm:ss" :start-placeholder="t('startDate')"
:end-placeholder="t('endDate')" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadOrderList()">{{ t('search') }}</el-button>
<el-button @click="searchFormRef?.resetFields()">{{ t('reset') }}</el-button>
</el-form-item>
</el-form>
</el-card>
<div class="mt-[16px]">
<el-table :data="orderTableData.data" size="large" v-loading="orderTableData.loading">
<template #empty>
<span>{{ !orderTableData.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column :show-overflow-tooltip="true" :label="t('member')" align="left" min-width="140">
<template #default="{ row }">
<div class="flex items-center cursor-pointer " @click="toMember(row.member.member_id)">
<img class="w-[50px] h-[50px] mr-[10px]" v-if="row.member.headimg" :src="img(row.member.headimg)" alt="">
<img class="w-[50px] h-[50px] mr-[10px]" v-else src="@/app/assets/images/default_headimg.png" alt="">
<div class="flex flex flex-col">
<span>{{ row.member.nickname || '' }}</span>
<span>{{ row.member.mobile || '' }}</span>
</div>
</div>
</template>
</el-table-column>
<el-table-column prop="order_no" :show-overflow-tooltip="true" :label="t('rechargeNo')" align="center" min-width="140" />
<el-table-column prop="order_money" :label="t('rechargeMoney')" align="center" min-width="140" />
<el-table-column prop="order_from_name" :label="t('orderFromName')" align="center" min-width="140" />
<el-table-column :label="t('orderStatus')" min-width="120" align="center">
<template #default="{ row }">
{{ row.order_status_info.name }}
</template>
</el-table-column>
<el-table-column prop="pay_type_name" :label="t('payTypeName')" align="center" min-width="140" />
<el-table-column :label="t('createTime')" min-width="180" align="center">
<template #default="{ row }">
{{ row.create_time || '' }}
</template>
</el-table-column>
<el-table-column :label="t('payTime')" min-width="180" align="center">
<template #default="{ row }">
{{ row.pay_time || '' }}
</template>
</el-table-column>
<el-table-column :label="t('operation')" align="right" fixed="right" width="130">
<template #default="{ row }">
<el-button type="primary" link @click="infoEvent(row)">{{ t('info') }}</el-button>
<el-button
v-if="[1, 10].includes(row.order_status_info.status) && row.is_enable_refund && row.refund_status == 0"
type="primary" link @click="refundFn(row)">{{ t('refundBtn') }}</el-button>
<template v-for="(item, index) in row.order_status_info.action" :key="index">
<el-button type="primary" link @click="orderEvent(row, item.class)">{{ item.name }}</el-button>
</template>
</template>
</el-table-column>
</el-table>
<div class="mt-[16px] flex justify-end">
<el-pagination v-model:current-page="orderTableData.page" v-model:page-size="orderTableData.limit"
layout="total, sizes, prev, pager, next, jumper" :total="orderTableData.total"
@size-change="loadOrderList()" @current-change="loadOrderList" />
</div>
</div>
</el-card>
<!-- 是否退款 -->
<el-dialog v-model="refundShowDialog" :title="t('refundBtn')" width="500px" :destroy-on-close="true">
<p>{{ t('refundContent') }}</p>
<template #footer>
<span class="dialog-footer">
<el-button @click="refundShowDialog = false">{{ t('cancel') }}</el-button>
<el-button type="primary" @click="confirmRefund" :loading="refundLoading">{{ t('confirm') }}</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue'
import { t } from '@/lang'
import { getRechargeOrderStatusList, getRechargeOrderList, rechargeRefund, getRechargeStat } from '@/app/api/order'
import { getChannelType } from '@/app/api/sys'
import { useRouter, useRoute } from 'vue-router'
import { AnyObject } from '@/types/global'
import type { FormInstance } from 'element-plus'
import { img } from '@/utils/common'
const route = useRoute()
const router = useRouter()
const pageName = route.meta.title
const memberId: number = parseInt(route.query.id || 0)
const channelList = ref([])
const setChannelList = async () => {
channelList.value = await (await getChannelType({})).data
}
setChannelList()
const orderTableData = reactive({
page: 1,
limit: 10,
total: 0,
loading: true,
data: [],
searchParam: {
order_no: '',
order_status: '',
member_id: memberId,
create_time: [],
pay_time: [],
order_from: '',
start_money: '',
end_money: ''
}
})
const rechargeStatistics = ref([])
const checkRechargeInfo = () => {
getRechargeStat({
member_id: memberId
}).then(res => {
rechargeStatistics.value = res.data
})
}
checkRechargeInfo()
const statusList = ref([])
const searchFormRef = ref<FormInstance>()
const setCategoryList = async () => {
statusList.value = await (await getRechargeOrderStatusList()).data
}
setCategoryList()
/**
* 获取文章列表
*/
const loadOrderList = (page: number = 1) => {
orderTableData.loading = true
orderTableData.page = page
getRechargeOrderList({
page: orderTableData.page,
limit: orderTableData.limit,
...orderTableData.searchParam
}).then(res => {
orderTableData.loading = false
orderTableData.data = res.data.data
orderTableData.total = res.data.total
}).catch(() => {
orderTableData.loading = false
})
}
loadOrderList()
/**
* 订单详情
* @param data
*/
const infoEvent = (data: any) => {
router.push(`/order/recharge/detail?order_id=${data.order_id}`)
}
/**
* 订单操作
*/
const orderEvent = (data: any, type: string) => {
}
/**
* 退款操作
*/
const refundShowDialog = ref(false)
let refundData: AnyObject | null = null
const refundLoading = ref(false)
const refundFn = (data: AnyObject) => {
refundShowDialog.value = true
refundData = data
}
/**
* 确认退款
*/
const confirmRefund = () => {
if (refundLoading.value) return
refundLoading.value = true
rechargeRefund(refundData?.order_id).then(res => {
refundShowDialog.value = false
refundLoading.value = false
}).catch(() => {
refundLoading.value = false
})
}
/**
* 会员详情
*/
const toMember = (memberId: number) => {
router.push(`/member/detail?id=${memberId}`)
}
</script>
<style lang="scss" scoped></style>

View File

@ -1,238 +0,0 @@
<template>
<div class="main-container">
<el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between items-center mb-[5px]">
<span class="text-page-title">{{ pageName }}</span>
</div>
<el-card class="box-card !border-none table-search-wra base-bg !px-[35px]" shadow="never">
<el-row class="flex">
<el-col :span="12">
<div class="statistic-card">
<el-statistic :value="refundStat.refund_all_money"></el-statistic>
<div class="statistic-footer">
<div class="footer-item text-[14px] text-[#666]">
<span>{{ t('accumulateRefundMoney') }}</span>
</div>
</div>
</div>
</el-col>
<el-col :span="12">
<div class="statistic-card">
<el-statistic :value="refundStat.refund_have_money"></el-statistic>
<div class="statistic-footer">
<div class="footer-item text-[14px] text-[#666]">
<span>{{ t('haveRefundMoney') }}</span>
</div>
</div>
</div>
</el-col>
</el-row>
</el-card>
<el-card class="box-card !border-none mb-[10px] table-search-wrap" shadow="never">
<el-form :inline="true" :model="refundTableData.searchParam" ref="searchFormRef">
<el-form-item :label="t('memberInfo')" prop="keywords">
<el-input v-model="refundTableData.searchParam.keywords" class="w-[240px]" :placeholder="t('memberInfoPlaceholder')" />
</el-form-item>
<el-form-item :label="t('refundNumber')" prop="refund_no">
<el-input v-model="refundTableData.searchParam.refund_no" class="w-[240px]" :placeholder="t('refundNumberPlaceholder')" />
</el-form-item>
<el-form-item :label="t('orderNumber')" prop="order_no">
<el-input v-model="refundTableData.searchParam.order_no" class="w-[240px]" :placeholder="t('orderNumberPlaceholder')" />
</el-form-item>
<el-form-item :label="t('refundStatus')" prop="status">
<el-select v-model="refundTableData.searchParam.status" clearable class="input-width">
<el-option :label="t('selectPlaceholder')" value="" />
<el-option :label="item.name" :value="key" v-for="(item, key) in refundList" :key="key" />
</el-select>
</el-form-item>
<el-form-item :label="t('refundTime')" prop="create_time">
<el-date-picker v-model="refundTableData.searchParam.create_time" type="datetimerange" value-format="YYYY-MM-DD HH:mm:ss" :start-placeholder="t('startDate')" :end-placeholder="t('endDate')" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadRefundList()">{{ t('search') }}</el-button>
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
</el-form-item>
</el-form>
</el-card>
<div>
<el-table :data="refundTableData.data" size="large" v-loading="refundTableData.loading">
<template #empty>
<span>{{ !refundTableData.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column :show-overflow-tooltip="true" :label="t('memberInfo')" align="left" min-width="140">
<template #default="{ row }">
<div class="flex items-center cursor-pointer " @click="toMember(row.member.member_id)">
<img class="w-[50px] h-[50px] mr-[10px]" v-if="row.member.headimg" :src="img(row.member.headimg)" alt="">
<img class="w-[50px] h-[50px] mr-[10px]" v-else src="@/app/assets/images/default_headimg.png" alt="">
<div class="flex flex flex-col">
<span>{{ row.member.nickname || '' }}</span>
<span>{{ row.member.mobile || '' }}</span>
</div>
</div>
</template>
</el-table-column>
<el-table-column prop="refund_no" :label="t('refundNumber')" align="center" min-width="200" />
<el-table-column prop="item.item_name" :label="t('refundSource')" align="center" min-width="140" />
<el-table-column prop="money" :label="t('refundAmount')" align="center" min-width="140" />
<el-table-column :label="t('refundTime')" min-width="180" align="center">
<template #default="{ row }">
{{ row.create_time || '' }}
</template>
</el-table-column>
<el-table-column :label="t('statusName')" min-width="180" align="center">
<template #default="{ row }">
{{ row.status_name || '' }}
</template>
</el-table-column>
<el-table-column :label="t('operation')" align="right" fixed="right" width="130">
<template #default="{ row }">
<el-button type="primary" link @click="infoEvent(row)">{{ t('info') }}</el-button>
</template>
</el-table-column>
</el-table>
<div class="mt-[16px] flex justify-end">
<el-pagination v-model:current-page="refundTableData.page" v-model:page-size="refundTableData.limit"
layout="total, sizes, prev, pager, next, jumper" :total="refundTableData.total"
@size-change="loadRefundList()" @current-change="loadRefundList" />
</div>
</div>
</el-card>
<el-dialog v-model="refundInfoShowDialog" :title="t('refundDetail')" width="500px" :destroy-on-close="true">
<el-form :model="refundInfo" label-width="120px" ref="formRef" :rules="formRules" class="page-form">
<el-form-item :label="t('nickname')">
<div class="input-width"> {{ refundInfo.member.nickname }} </div>
</el-form-item>
<el-form-item :label="t('refundSource')">
<div class="input-width"> {{ refundInfo.item.item_name }} </div>
</el-form-item>
<el-form-item :label="t('refundAmount')">
<div class="input-width"> {{ refundInfo.money }} </div>
</el-form-item>
<el-form-item :label="t('orderNumber')">
<div class="input-width"> {{ refundInfo.item.order_no }} </div>
</el-form-item>
<el-form-item :label="t('refundNumber')">
<div class="input-width"> {{ refundInfo.refund_no }} </div>
</el-form-item>
<el-form-item :label="t('refundTime')">
<div class="input-width"> {{ refundInfo.create_time }} </div>
</el-form-item>
<el-form-item :label="t('statusName')">
<div class="input-width"> {{ refundInfo.status_name }} </div>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button type="primary" @click="refundInfoShowDialog = false">{{ t('confirm') }}</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue'
import { t } from '@/lang'
import { img } from '@/utils/common'
import { useRouter, useRoute } from 'vue-router'
import { getRechargeRefund, getRechargeRefundStatus, getRechargeRefundStat } from '@/app/api/order'
import { FormInstance } from 'element-plus'
const router = useRouter()
const route = useRoute()
const pageName = route.meta.title
const searchFormRef = ref<FormInstance>()
const refundTableData = reactive({
page: 1,
limit: 10,
total: 0,
loading: true,
data: [],
searchParam: {
refund_no: '',
// member_id,
create_time: [],
status: '',
keywords: '',
order_no: ''
}
})
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
loadRefundList()
}
/**
* 获取退款列表
*/
const loadRefundList = (page: number = 1) => {
refundTableData.loading = true
refundTableData.page = page
getRechargeRefund({
page: refundTableData.page,
limit: refundTableData.limit,
...refundTableData.searchParam
}).then(res => {
refundTableData.loading = false
refundTableData.data = res.data.data
refundTableData.total = res.data.total
}).catch(() => {
refundTableData.loading = false
})
}
loadRefundList()
const refundList = ref([])
const checkRefundList = () => {
getRechargeRefundStatus().then(res => {
refundList.value = res.data
})
}
checkRefundList()
const refundStat = reactive({
refund_all_money: 0.00,
refund_have_money: 0.00,
refund_Success_money: 0.00,
refund_fail_moey: 0.00
})
const checkRefundStat = () => {
getRechargeRefundStat().then(res => {
refundStat.refund_all_money = res.data.all.money
refundStat.refund_have_money = res.data.have.money
refundStat.refund_Success_money = res.data['3'].money
refundStat.refund_fail_moey = res.data['-1'].money
})
}
checkRefundStat()
const refundInfoShowDialog = ref(false)
const refundInfo = ref({})
const infoEvent = (info:any) => {
refundInfo.value = info
refundInfoShowDialog.value = true
}
/**
* 会员详情
*/
const toMember = (memberId: number) => {
router.push(`/member/detail?id=${memberId}`)
}
</script>
<style lang="scss" scoped></style>

View File

@ -11,10 +11,10 @@
<el-card class="box-card !border-none" shadow="never" v-loading="loading">
<el-form :model="formData" label-width="90px" ref="formRef" :rules="formRules" class="page-form">
<el-form-item :label="t('type')">
<el-input v-model="formData.agreement_key_name" readonly class="input-width" />
<el-input v-model.trim="formData.agreement_key_name" readonly class="input-width" />
</el-form-item>
<el-form-item :label="t('title')" prop="title">
<el-input v-model="formData.title" clearable :placeholder="t('titlePlaceholder')" class="input-width" maxlength="20" />
<el-input v-model.trim="formData.title" clearable :placeholder="t('titlePlaceholder')" class="input-width" maxlength="20" />
</el-form-item>
<el-form-item :label="t('content')">
<editor v-model="formData.content" />

View File

@ -6,22 +6,20 @@
<div class="input-width"> {{ formData.name }} </div>
</el-form-item>
<el-form-item :label="t('messageType')">
<div v-if="formData.message_type == 'sms'">{{ t('sms') }}</div>
<div v-if="formData.message_type == 'wechat'">{{ t('wechat') }}</div>
<div v-if="formData.message_type == 'weapp'">{{ t('weapp') }}</div>
<el-form-item :label="t('smsType')">
<div class="input-width"> {{ formData.sms_type_name }} </div>
</el-form-item>
<el-form-item :label="t('messageData')">
<!-- <el-form-item :label="t('messageData')">
<div class="input-width"> {{ formData.message_data }} </div>
</el-form-item>
</el-form-item> -->
<el-form-item :label="t('nickname')">
<!-- <el-form-item :label="t('nickname')">
<div class="input-width"> {{ formData.nickname }} </div>
</el-form-item>
</el-form-item> -->
<el-form-item :label="t('receiver')">
<div class="input-width"> {{ formData.receiver }} </div>
<div class="input-width"> {{ formData.mobile }} </div>
</el-form-item>
<el-form-item :label="t('createTime')">
@ -57,7 +55,8 @@ const initialFormData = {
message_type: '',
name: '',
nickname: '',
receiver: ''
mobile: '',
sms_type_name: ''
}
const formData: Record<string, any> = reactive({ ...initialFormData })

View File

@ -3,7 +3,7 @@
<el-form :model="formData" label-width="110px" ref="formRef" :rules="formRules" class="page-form" v-loading="loading">
<el-form-item :label="t('appId')" prop="config.app_id">
<el-input v-model="formData.config.app_id" :placeholder="t('appIdPlaceholder')" class="input-width" maxlength="32" show-word-limit clearable />
<el-input v-model.trim="formData.config.app_id" :placeholder="t('appIdPlaceholder')" class="input-width" maxlength="32" show-word-limit clearable />
<div class="form-tip">{{ t('appIdTips') }}</div>
</el-form-item>
<el-form-item :label="t('appSecretCert')" prop="config.app_secret_cert">
@ -12,7 +12,7 @@
<el-form-item :label="t('appPublicCertPath')" prop="config.app_public_cert_path">
<div class="input-width">
<upload-file v-model="formData.config.app_public_cert_path" api="sys/document/aliyun" />
<upload-file v-model.trim="formData.config.app_public_cert_path" api="sys/document/aliyun" />
</div>
</el-form-item>

View File

@ -4,13 +4,13 @@
v-loading="loading">
<el-form-item :label="t('mchId')" prop="config.mch_id">
<el-input v-model="formData.config.mch_id" :placeholder="t('mchIdPlaceholder')" class="input-width"
<el-input v-model.trim="formData.config.mch_id" :placeholder="t('mchIdPlaceholder')" class="input-width"
maxlength="32" show-word-limit clearable />
<div class="form-tip">{{ t('mchIdTips') }}</div>
</el-form-item>
<el-form-item :label="t('mchSecretKey')" prop="config.mch_secret_key">
<el-input v-model="formData.config.mch_secret_key" :placeholder="t('mchSecretKeyPlaceholder')"
<el-input v-model.trim="formData.config.mch_secret_key" :placeholder="t('mchSecretKeyPlaceholder')"
class="input-width" maxlength="32" show-word-limit clearable />
<div class="form-tip">{{ t('mchSecretKeyTips') }}</div>
</el-form-item>

View File

@ -52,7 +52,9 @@ const initialFormData = {
sign: '',
access_key: '',
secret_key: '',
is_use: ''
is_use: '',
app_id:'',
secret_id:'',
}
const formData: Record<string, any> = reactive({ ...initialFormData })

View File

@ -4,7 +4,7 @@
<el-card class="box-card !border-none" shadow="never">
<h3 class="panel-title">{{ t('mapSetting') }}</h3>
<el-form-item :label="t('mapKey')" prop="site_name">
<el-input v-model="formData.key" class="input-width" clearable />
<el-input v-model.trim="formData.key" class="input-width" clearable />
<span class="ml-2 cursor-pointer tutorial-btn" @click="tutorialFn">{{ t('clickTutorial') }}</span>
<span class="ml-2 cursor-pointer secret-btn" @click="secretlFn">{{ t('clickSecretKey') }}</span>
</el-form-item>

View File

@ -10,7 +10,7 @@
</el-form-item>
<el-form-item :label="t('length')" prop="length">
<el-input v-model="formData.length" :placeholder="t('lengthPlaceholder')" class="input-width" clearable @keyup="filterNumber($event)" @change="getMemberNo(ruleFormRef)"/>
<el-input v-model.trim="formData.length" :placeholder="t('lengthPlaceholder')" class="input-width" clearable @keyup="filterNumber($event)" @change="getMemberNo(ruleFormRef)" @blur="formData.length = $event.target.value"/>
<div class="form-tip">{{ t('lengthTips') }}</div>
</el-form-item>
@ -53,7 +53,7 @@ const formRules = reactive<FormRules>({
{ validator: prefixVerify, trigger: 'blur' }
],
length: [
{ required: true, message: t('lengthPlaceholder'), trigger: 'blur' },
{ required: true, message: t('lengthPlaceholder'), trigger: ['blur', 'change'] },
{
validator: (rule: any, value: any, callback: any) => {
if (parseInt(value) > 30 || parseInt(value) - formData.prefix.length < 4) {

View File

@ -38,16 +38,9 @@
</template>
<el-table-column prop="name" :label="t('noticeKey')" min-width="120" />
<el-table-column prop="notice_type" :label="t('noticeType')" min-width="120">
<template #default="{ row }">
<div v-if="row.notice_type == 'sms'">{{ t('sms') }}</div>
<div v-if="row.notice_type == 'wechat'">{{ t('wechat') }}</div>
<div v-if="row.notice_type == 'weapp'">{{ t('weapp') }}</div>
</template>
</el-table-column>
<el-table-column prop="sms_type_name" :label="t('smsType')" min-width="120"/>
<el-table-column prop="nickname" :label="t('nickname')" min-width="120" />
<el-table-column prop="receiver" :label="t('receiver')" min-width="120" />
<el-table-column prop="mobile" :label="t('receiver')" min-width="120" />
<el-table-column prop="create_time" :label="t('createTime')" min-width="140" />
<el-table-column :label="t('operation')" align="right" fixed="right" width="100">

View File

@ -5,7 +5,7 @@
<h3 class="panel-title !text-sm">{{ t('websiteInfo') }}</h3>
<el-form-item :label="t('siteName')" prop="site_name">
<el-input v-model="formData.site_name" :placeholder="t('siteNamePlaceholder')" class="input-width" clearable maxlength="20" />
<el-input v-model.trim="formData.site_name" :placeholder="t('siteNamePlaceholder')" class="input-width" clearable maxlength="20" show-word-limit />
</el-form-item>
<el-form-item :label="t('logo')">
@ -23,17 +23,17 @@
</el-form-item>
<el-form-item :label="t('keywords')">
<el-input v-model="formData.keywords" :placeholder="t('keywordsPlaceholder')" class="input-width" clearable maxlength="20" />
<el-input v-model="formData.keywords" :placeholder="t('keywordsPlaceholder')" class="input-width" clearable maxlength="20" show-word-limit />
</el-form-item>
<el-form-item :label="t('desc')">
<el-input v-model="formData.desc" type="textarea" rows="4" clearable :placeholder="t('descPlaceholder')" class="input-width" maxlength="100" />
<el-input v-model="formData.desc" type="textarea" rows="4" clearable :placeholder="t('descPlaceholder')" class="input-width" maxlength="100" show-word-limit />
</el-form-item>
</el-card>
<el-card class="box-card !border-none" shadow="never">
<h3 class="panel-title !text-sm">{{ t('frontEndInfo') }}</h3>
<el-form-item :label="t('frontEndName')">
<el-input v-model="formData.front_end_name" :placeholder="t('frontEndNamePlaceholder')" class="input-width" clearable maxlength="20" />
<el-input v-model="formData.front_end_name" :placeholder="t('frontEndNamePlaceholder')" class="input-width" clearable maxlength="20" show-word-limit />
</el-form-item>
<el-form-item :label="t('frontEndLogo')">
@ -43,7 +43,7 @@
<el-card class="box-card !border-none" shadow="never" v-if="app_type == 'admin'">
<h3 class="panel-title !text-sm">{{ t('serviceInformation') }}</h3>
<el-form-item :label="t('contactsTel')">
<el-input v-model="formData.tel" :placeholder="t('contactsTelPlaceholder')" class="input-width" clearable maxlength="20" />
<el-input v-model="formData.tel" :placeholder="t('contactsTelPlaceholder')" class="input-width" clearable maxlength="20" show-word-limit />
</el-form-item>
<el-form-item :label="t('wechatCode')">
<upload-image v-model="formData.wechat_code" />

View File

@ -30,9 +30,7 @@
<el-form-item :label="t('status')">
<template #default="{ }">
<el-tag class="ml-2" type="success" v-if="formData.status == 1">{{ t('statusNormal') }}</el-tag>
<el-tag class="ml-2" type="error" v-if="formData.status == 0">{{t('statusDeactivate') }}</el-tag>
<el-tag class="ml-2" type="error" v-if="formData.status == 2">{{t('statusExpire') }}</el-tag>
<el-tag class="ml-2" :type="formData.status == 1?'success':'error'">{{ formData.status_name }}</el-tag>
</template>
</el-form-item>
@ -146,7 +144,8 @@ const initialFormData = {
group_name: '',
status: 0,
create_time: 0,
site_addons: []
site_addons: [],
status_name:''
}
const formData: Record<string, any> = reactive({ ...initialFormData })

View File

@ -66,12 +66,12 @@
</el-table-column>
<el-table-column :label="t('status')" min-width="80" align="center">
<template #default="{ row }">
<el-tag class="ml-2" type="success" v-if="row.status == 1">{{ row.status_name }}</el-tag>
<el-tag class="ml-2" type="success" v-if="row.status == 1">{{ row.site_status_name }}</el-tag>
<el-tag class="ml-2" type="error" v-else-if="row.status == 3">
{{ row.status_name }}
{{ row.site_status_name }}
</el-tag>
<el-tag class="ml-2" type="error" v-else>
{{ row.status_name }}
{{ row.site_status_name }}
</el-tag>
</template>
</el-table-column>

View File

@ -1,7 +1,7 @@
<template>
<div class="border border-color">
<toolbar class="border-b border-color" :editor="editorRef" :defaultConfig="toolbarConfig" :mode="mode" />
<wang-editor :style="{ height, 'overflow-y': 'hidden', width: '100%' }" :defaultConfig="editorConfig" :mode="mode" v-model="content" @onCreated="handleCreated" />
<wang-editor :style="{ height, 'overflow-y': 'hidden', width: '100%' }" :defaultConfig="editorConfig" :mode="mode" v-model="content" @onCreated="handleCreated" @onBlur="handleBlur" />
<upload-attachment type="image" ref="imageRef" :limit="10" @confirm="imageSelect" />
<upload-attachment type="video" ref="videoRef" @confirm="videoSelect" />
</div>
@ -31,7 +31,7 @@ const prop = defineProps({
}
})
const emit = defineEmits(['update:modelValue'])
const emit = defineEmits(['update:modelValue','handleBlur'])
const imageRef: Record<string, any> | null = ref(null)
const videoRef: Record<string, any> | null = ref(null)
@ -86,7 +86,10 @@ const videoSelect = (data: Record<string, any>) => {
const handleCreated = (editor: IDomEditor) => {
editorRef.value = editor
}
// blur
const handleBlur = (editor: IDomEditor)=>{
emit('handleBlur',editor)
}
/**
* 组件销毁时也及时销毁编辑器
*/

View File

@ -12,7 +12,7 @@
</div>
</div>
<upload-attachment :limit="limit" @confirm="confirmSelect" v-else>
<div class="w-full h-full flex items-center justify-center flex-col">
<div class="w-full h-full flex items-center justify-center flex-col content-wrap">
<icon name="element-Plus" size="20px" color="var(--el-text-color-secondary)" />
<div class="leading-none text-xs mt-[10px] text-secondary">{{ imageText || t('upload.root') }}</div>
</div>
@ -33,7 +33,7 @@
</div>
<div class="rounded cursor-pointer overflow-hidden relative border border-dashed border-color" :style="style" v-if="images.data.length < limit">
<upload-attachment :limit="limit" @confirm="confirmSelect">
<div class="w-full h-full flex items-center justify-center flex-col">
<div class="w-full h-full flex items-center justify-center flex-col content-wrap">
<icon name="element-Plus" size="20px" color="var(--el-text-color-secondary)" />
<div class="leading-none text-xs mt-[10px] text-secondary">{{ imageText || t('upload.root') }}</div>
</div>

View File

@ -113,6 +113,7 @@
"preview": "预览",
"emptyApp": "暂未安装任何应用",
"newInfo": "最新消息",
"visitWap": "访问店铺",
"mapSetting": "地图设置",
"mapKey": "腾讯地图KEY",
"indexTemplate": "首页模版",

View File

@ -66,7 +66,6 @@ const props = defineProps({
type: String
}
})
console.log("props",props.routePath)
const meta = computed(() => props.routes.meta)

View File

@ -23,6 +23,7 @@
<el-col :span="12">
<div class="right-panel h-full flex items-center justify-end">
<!-- 预览 只有站点时展示-->
<i class="iconfont iconicon_huojian1 cursor-pointer px-[8px]" :title="t('visitWap')" @click="toPreview"></i>
<i class="iconfont iconlingdang-xianxing cursor-pointer px-[8px]" :title="t('newInfo')" v-if="appType == 'site'"></i>
<!-- 切换语言 -->
<!-- <div class="navbar-item flex items-center h-full cursor-pointer">
@ -65,7 +66,7 @@ import userInfo from './user-info.vue'
import { useFullscreen } from '@vueuse/core'
import useSystemStore from '@/stores/modules/system'
import useAppStore from '@/stores/modules/app'
import { useRoute } from 'vue-router'
import { useRoute,useRouter } from 'vue-router'
import { t } from '@/lang'
import storage from '@/utils/storage'
@ -74,6 +75,7 @@ const { toggle: toggleFullscreen } = useFullscreen()
const systemStore = useSystemStore()
const appStore = useAppStore()
const route = useRoute()
const router = useRouter()
const screenWidth = ref(window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth)
const dark = computed(() => {
@ -147,6 +149,17 @@ const breadcrumb = computed(() => {
return matched
})
//
const toPreview = () => {
const url = router.resolve({
path: '/preview/wap',
query: {
page:'/'
}
})
window.open(url.href)
}
//
// const backFn = () => {
// router.go(-1)

View File

@ -37,7 +37,6 @@ const route = useRoute()
const siteInfo = userStore.siteInfo
const menuActive = computed(() => String(route.name))
console.log("userStore.routers",userStore.routers)
userStore.routers.forEach((item, index) => {
item.meta.class = 1
if (item.children) {

View File

@ -8,6 +8,7 @@
<icon name="element-Refresh" />
</div>
<!-- 预览 只有站点时展示-->
<i class="iconfont iconicon_huojian1 cursor-pointer px-[8px]" :title="t('visitWap')" @click="toPreview"></i>
<i class="iconfont iconlingdang-xianxing cursor-pointer px-[8px]" :title="t('newInfo')" v-if="appType == 'site'"></i>
<!-- 切换首页 -->
<div class="navbar-item flex items-center h-full cursor-pointer" v-if="appType == 'site'" @click="checkIndexList">
@ -159,6 +160,17 @@ const breadcrumb = computed(() => {
return matched
})
//
const toPreview = () => {
const url = router.resolve({
path: '/preview/wap',
query: {
page:'/'
}
})
window.open(url.href)
}
//
const backFn = () => {
router.go(-1)

View File

@ -138,7 +138,6 @@ router.beforeEach(async (to, from, next) => {
}
} catch (err) {
console.log(err)
next({ path: loginPath, query: { redirect: to.fullPath } })
}
}

View File

@ -39,11 +39,15 @@ const useDiyStore = defineStore('diy', {
'#c7158577'
],
components: [], // 组件集合
position: ['fixed', 'top_fixed','right_fixed','bottom_fixed','left_fixed'],
global: {
title: "页面", // 页面标题
pageBgColor: "", // 页面背景颜色
pageStartBgColor: "", // 页面背景颜色(开始)
pageEndBgColor: "", // 页面背景颜色(结束)
pageGradientAngle: 'to bottom', // 渐变角度从上到下to bottom、从左到右to right
bgUrl: '', // 页面背景图片
bgHeightScale: 0, // 页面背景高度比例,单位%0为高度自适应
imgWidth: '', // 页面背景图片宽度
imgHeight: '', // 页面背景图片高度
@ -74,9 +78,15 @@ const useDiyStore = defineStore('diy', {
// 公共模板属性,所有组件都继承,无需重复定义,组件内部根据业务自行调用
template: {
textColor: "#303133", // 文字颜色
pageBgColor: '', // 底部背景颜色
pageStartBgColor: "", // 组件底部背景颜色(开始)
pageEndBgColor: "", // 组件底部背景颜色(结束)
pageGradientAngle: 'to bottom', // 渐变角度从上到下to bottom、从左到右to right
componentBgColor: '', // 组件背景颜色
componentBgUrl: '', // 组件背景图片
componentBgAlpha: 2, // 组件背景图片的透明度0~10
componentStartBgColor: '', // 组件背景颜色(开始)
componentEndBgColor: '', // 组件背景颜色(结束)
componentGradientAngle: 'to bottom', // 渐变角度从上到下to bottom、从左到右to right
topRounded: 0, // 组件上圆角
bottomRounded: 0, // 组件下圆角
@ -111,8 +121,11 @@ const useDiyStore = defineStore('diy', {
this.global = {
title: "页面", // 页面标题
pageBgColor: "", // 页面背景颜色
pageStartBgColor: "", // 页面背景颜色(开始)
pageEndBgColor: "", // 页面背景颜色(结束)
pageGradientAngle: 'to bottom', // 渐变角度从上到下to bottom、从左到右to right
bgUrl: '', // 页面背景图片
bgHeightScale: 100, // 页面背景高度比例,单位%
imgWidth: '', // 页面背景图片宽度
imgHeight: '', // 页面背景图片高度
@ -143,9 +156,15 @@ const useDiyStore = defineStore('diy', {
// 公共模板属性,所有组件都继承,无需重复定义,组件内部根据业务自行调用
template: {
textColor: "#303133", // 文字颜色
pageBgColor: '', // 底部背景颜色
pageStartBgColor: "", // 组件底部背景颜色(开始)
pageEndBgColor: "", // 组件底部背景颜色(结束)
pageGradientAngle: 'to bottom', // 渐变角度从上到下to bottom、从左到右to right
componentBgColor: '', // 组件背景颜色
componentBgUrl: '', // 组件背景图片
componentBgAlpha: 2, // 组件背景图片的透明度
componentStartBgColor: '', // 组件背景颜色(开始)
componentEndBgColor: '', // 组件背景颜色(结束)
componentGradientAngle: 'to bottom', // 渐变角度从上到下to bottom、从左到右to right
topRounded: 0, // 组件上圆角
bottomRounded: 0, // 组件下圆角
@ -182,25 +201,36 @@ const useDiyStore = defineStore('diy', {
delete component.type;
delete component.icon;
// 默认继承全局属性
let template = cloneDeep(this.global.template);
Object.assign(component, template);
if(component.template){
// 按照组件初始的属性加载
// 按照组件初始的属性加载覆盖
Object.assign(component, component.template);
delete component.template;
}else{
// 默认继承全局属性
let template = cloneDeep(this.global.template);
Object.assign(component, template);
}
if (!this.checkComponentIsAdd(component)) return;
if (this.currentIndex === -99) {
// 置顶组件,只能在第一个位置中添加
if(component.position && this.position.indexOf(component.position) != -1){
this.currentIndex = 0;
// 指定位置添加组件
this.value.splice(0, 0, component);
}else if (this.currentIndex === -99) {
this.value.push(component);
// 添加组件后(不是编辑调用的),选择最后一个
this.currentIndex = this.value.length - 1;
} else {
// 指定位置添加组件
this.value.splice(++this.currentIndex, 0, component);
}
this.currentComponent = component.path;
@ -271,6 +301,14 @@ const useDiyStore = defineStore('diy', {
var temp2 = cloneDeep(this.value[prevIndex]); // 上个组件
temp2.id = this.generateRandom(); // 更新id刷新组件数据
if(temp.position && this.position.indexOf(temp.position) != -1){
ElMessage({
type: 'warning',
message: `${t('componentNotMoved')}`,
});
return;
}
this.value[this.currentIndex] = temp2;
this.value[prevIndex] = temp;
@ -288,6 +326,14 @@ const useDiyStore = defineStore('diy', {
var temp2 = cloneDeep(this.value[nextIndex]); // 下个组件
temp2.id = this.generateRandom(); // 更新id刷新组件数据
if(temp.position && this.position.indexOf(temp.position) != -1){
ElMessage({
type: 'warning',
message: `${t('componentNotMoved')}`,
});
return;
}
this.value[this.currentIndex] = temp2;
this.value[nextIndex] = temp;
@ -309,6 +355,14 @@ const useDiyStore = defineStore('diy', {
return;
}
if(component.position && this.position.indexOf(component.position) != -1){
ElMessage({
type: 'warning',
message: `${t('notCopy')}${component.componentTitle}${t('componentCanOnlyAdd')}1${t('piece')}`,
});
return;
}
var index = this.currentIndex + 1;
this.value.splice(index, 0, component);

View File

@ -257,3 +257,7 @@ html.dark {
// background-color: #636363 !important;
// opacity: 1 !important;
//}
:root input:-webkit-autofill, textarea:-webkit-autofill, select:-webkit-autofill {
box-shadow: 0 0 50px 50px white inset;
}

View File

@ -1 +1,2 @@
/* addon icon */
@import "addon/tourism/iconfont.css";
@import "addon/o2o/iconfont.css";

View File

@ -0,0 +1,38 @@
@font-face {
font-family: "o2o"; /* Project id 4412516 */
src: url('//at.alicdn.com/t/c/font_4412516_cacqsbew46.woff2?t=1705720131974') format('woff2'),
url('//at.alicdn.com/t/c/font_4412516_cacqsbew46.woff?t=1705720131974') format('woff'),
url('//at.alicdn.com/t/c/font_4412516_cacqsbew46.ttf?t=1705720131974') format('truetype');
}
.o2o {
font-family: "o2o" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.o2o-icon-danhanghuadong:before {
content: "\e66f";
}
.o2o-icon-yuanjiao:before {
content: "\e6c0";
}
.o2o-icon-gl-square:before {
content: "\ea92";
}
.o2o-icon-sousuo12:before {
content: "\e699";
}
.o2o-icon-sousuo11:before {
content: "\e6d4";
}
.o2o-icon-jishi:before {
content: "\e600";
}

View File

@ -0,0 +1,51 @@
{
"id": "4412516",
"name": "上门服务",
"font_family": "o2o",
"css_prefix_text": "o2o-icon-",
"description": "",
"glyphs": [
{
"icon_id": "30621139",
"name": "单行滑动",
"font_class": "danhanghuadong",
"unicode": "e66f",
"unicode_decimal": 58991
},
{
"icon_id": "7149037",
"name": "圆角",
"font_class": "yuanjiao",
"unicode": "e6c0",
"unicode_decimal": 59072
},
{
"icon_id": "7594344",
"name": "20gl-square",
"font_class": "gl-square",
"unicode": "ea92",
"unicode_decimal": 60050
},
{
"icon_id": "10133070",
"name": "搜索",
"font_class": "sousuo12",
"unicode": "e699",
"unicode_decimal": 59033
},
{
"icon_id": "14652583",
"name": "搜索",
"font_class": "sousuo11",
"unicode": "e6d4",
"unicode_decimal": 59092
},
{
"icon_id": "6818781",
"name": "技师",
"font_class": "jishi",
"unicode": "e600",
"unicode_decimal": 58880
}
]
}

View File

@ -0,0 +1,58 @@
@font-face {
font-family: "tourism"; /* Project id 4137250 */
src: url('//at.alicdn.com/t/c/font_4137250_st1ha9l0k1e.woff2?t=1687685028672') format('woff2'),
url('//at.alicdn.com/t/c/font_4137250_st1ha9l0k1e.woff?t=1687685028672') format('woff'),
url('//at.alicdn.com/t/c/font_4137250_st1ha9l0k1e.ttf?t=1687685028672') format('truetype');
}
.tourism {
font-family: "tourism" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.tourism-icon-feiji:before {
content: "\e600";
}
.tourism-icon-lvyou:before {
content: "\e6a9";
}
.tourism-icon-lvyouchanpin:before {
content: "\e63b";
}
.tourism-icon-lvyou1:before {
content: "\e623";
}
.tourism-icon-lvyou2:before {
content: "\e601";
}
.tourism-icon-lvyou3:before {
content: "\e60c";
}
.tourism-icon-lvyoubaochedingdan:before {
content: "\e612";
}
.tourism-icon-lvyou4:before {
content: "\e653";
}
.tourism-icon-lvyou5:before {
content: "\e610";
}
.tourism-icon-lvyouguanguang:before {
content: "\e87e";
}
.tourism-icon-lvyou6:before {
content: "\e642";
}

View File

@ -0,0 +1,86 @@
{
"id": "4137250",
"name": "旅游业",
"font_family": "tourism",
"css_prefix_text": "tourism-icon-",
"description": "",
"glyphs": [
{
"icon_id": "1443",
"name": "飞机",
"font_class": "feiji",
"unicode": "e600",
"unicode_decimal": 58880
},
{
"icon_id": "446824",
"name": "旅游",
"font_class": "lvyou",
"unicode": "e6a9",
"unicode_decimal": 59049
},
{
"icon_id": "1167173",
"name": "旅游产品",
"font_class": "lvyouchanpin",
"unicode": "e63b",
"unicode_decimal": 58939
},
{
"icon_id": "1354920",
"name": "旅游",
"font_class": "lvyou1",
"unicode": "e623",
"unicode_decimal": 58915
},
{
"icon_id": "1505555",
"name": "旅游",
"font_class": "lvyou2",
"unicode": "e601",
"unicode_decimal": 58881
},
{
"icon_id": "2121726",
"name": "旅游",
"font_class": "lvyou3",
"unicode": "e60c",
"unicode_decimal": 58892
},
{
"icon_id": "2357494",
"name": "旅游包车订单",
"font_class": "lvyoubaochedingdan",
"unicode": "e612",
"unicode_decimal": 58898
},
{
"icon_id": "3944019",
"name": "旅游",
"font_class": "lvyou4",
"unicode": "e653",
"unicode_decimal": 58963
},
{
"icon_id": "4838220",
"name": "旅游",
"font_class": "lvyou5",
"unicode": "e610",
"unicode_decimal": 58896
},
{
"icon_id": "7444178",
"name": "旅游观光",
"font_class": "lvyouguanguang",
"unicode": "e87e",
"unicode_decimal": 59518
},
{
"icon_id": "9748082",
"name": "旅游",
"font_class": "lvyou6",
"unicode": "e642",
"unicode_decimal": 58946
}
]
}

View File

@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 3883393 */
src: url('//at.alicdn.com/t/c/font_3883393_46xoryy5l7v.woff2?t=1701067532618') format('woff2'),
url('//at.alicdn.com/t/c/font_3883393_46xoryy5l7v.woff?t=1701067532618') format('woff'),
url('//at.alicdn.com/t/c/font_3883393_46xoryy5l7v.ttf?t=1701067532618') format('truetype');
src: url('//at.alicdn.com/t/c/font_3883393_au7p2pnufj8.woff2?t=1711684151959') format('woff2'),
url('//at.alicdn.com/t/c/font_3883393_au7p2pnufj8.woff?t=1711684151959') format('woff'),
url('//at.alicdn.com/t/c/font_3883393_au7p2pnufj8.ttf?t=1711684151959') format('truetype');
}
.iconfont {
@ -13,6 +13,94 @@
-moz-osx-font-smoothing: grayscale;
}
.iconfenxiang:before {
content: "\e655";
}
.iconico:before {
content: "\e63f";
}
.iconhuojian1:before {
content: "\e633";
}
.iconkaifazujian:before {
content: "\e640";
}
.iconzuoshangjiao:before {
content: "\e700";
}
.iconyoushangjiao:before {
content: "\e701";
}
.iconyouxiajiao:before {
content: "\e702";
}
.iconzuoxiajiao:before {
content: "\e703";
}
.iconfudonganniu1:before {
content: "\e630";
}
.iconshangpinliebiaohengxianghuadong:before {
content: "\e926";
}
.iconhengxianghuadong:before {
content: "\e63e";
}
.iconduoshangpinzu:before {
content: "\e650";
}
.iconsousuokuang:before {
content: "\e65b";
}
.iconicon_congzuodaoyou:before {
content: "\e7eb";
}
.iconicon_congshangdaoxia:before {
content: "\e7ec";
}
.iconmap-connect:before {
content: "\ea02";
}
.iconmap-link:before {
content: "\ea01";
}
.iconfuwenben1:before {
content: "\e62f";
}
.iconfuzhuxian1:before {
content: "\e646";
}
.iconyouhuiquan1:before {
content: "\e665";
}
.iconjishi:before {
content: "\e749";
}
.iconbanbenqiehuan:before {
content: "\e792";
}
.icontuichudenglu:before {
content: "\e64a";
}

View File

@ -5,6 +5,188 @@
"css_prefix_text": "icon",
"description": "系统图标",
"glyphs": [
{
"icon_id": "2672195",
"name": "分享",
"font_class": "fenxiang",
"unicode": "e655",
"unicode_decimal": 58965
},
{
"icon_id": "6360240",
"name": "火箭",
"font_class": "ico",
"unicode": "e63f",
"unicode_decimal": 58943
},
{
"icon_id": "12084141",
"name": "开发组件",
"font_class": "kaifazujian",
"unicode": "e640",
"unicode_decimal": 58944
},
{
"icon_id": "10879698",
"name": "左上角",
"font_class": "zuoshangjiao",
"unicode": "e700",
"unicode_decimal": 59136
},
{
"icon_id": "10879728",
"name": "右上角",
"font_class": "youshangjiao",
"unicode": "e701",
"unicode_decimal": 59137
},
{
"icon_id": "10879730",
"name": "右下角",
"font_class": "youxiajiao",
"unicode": "e702",
"unicode_decimal": 59138
},
{
"icon_id": "10879766",
"name": "左下角",
"font_class": "zuoxiajiao",
"unicode": "e703",
"unicode_decimal": 59139
},
{
"icon_id": "30454123",
"name": "浮动按钮",
"font_class": "fudonganniu1",
"unicode": "e630",
"unicode_decimal": 58928
},
{
"icon_id": "26585483",
"name": "商品列表横向滑动",
"font_class": "shangpinliebiaohengxianghuadong",
"unicode": "e926",
"unicode_decimal": 59686
},
{
"icon_id": "30244188",
"name": "横向滑动",
"font_class": "hengxianghuadong",
"unicode": "e63e",
"unicode_decimal": 58942
},
{
"icon_id": "30454129",
"name": "多商品组",
"font_class": "duoshangpinzu",
"unicode": "e650",
"unicode_decimal": 58960
},
{
"icon_id": "30454142",
"name": "搜索框",
"font_class": "sousuokuang",
"unicode": "e65b",
"unicode_decimal": 58971
},
{
"icon_id": "8960779",
"name": "icon_从左到右",
"font_class": "icon_congzuodaoyou",
"unicode": "e7eb",
"unicode_decimal": 59371
},
{
"icon_id": "8960781",
"name": "icon_从上到下",
"font_class": "icon_congshangdaoxia",
"unicode": "e7ec",
"unicode_decimal": 59372
},
{
"icon_id": "18170999",
"name": "地图连线,连线,连接",
"font_class": "map-connect",
"unicode": "ea02",
"unicode_decimal": 59906
},
{
"icon_id": "18170996",
"name": "地图连线竖,连接,连线",
"font_class": "map-link",
"unicode": "ea01",
"unicode_decimal": 59905
},
{
"icon_id": "30454117",
"name": "富文本",
"font_class": "fuwenben1",
"unicode": "e62f",
"unicode_decimal": 58927
},
{
"icon_id": "30454130",
"name": "icon-kfckfc",
"font_class": "fuzhuxian1",
"unicode": "e646",
"unicode_decimal": 58950
},
{
"icon_id": "30454145",
"name": "优惠券",
"font_class": "youhuiquan1",
"unicode": "e665",
"unicode_decimal": 58981
},
{
"icon_id": "5616072",
"name": "技师",
"font_class": "jishi",
"unicode": "e749",
"unicode_decimal": 59209
},
{
"icon_id": "435890",
"name": "版本切换",
"font_class": "banbenqiehuan",
"unicode": "e792",
"unicode_decimal": 59282
},
{
"icon_id": "1391488",
"name": "退出登录",
"font_class": "tuichudenglu",
"unicode": "e64a",
"unicode_decimal": 58954
},
{
"icon_id": "37930768",
"name": "火箭",
"font_class": "huojian",
"unicode": "e6ff",
"unicode_decimal": 59135
},
{
"icon_id": "37942158",
"name": "icon_火箭",
"font_class": "icon_huojian1",
"unicode": "e6fe",
"unicode_decimal": 59134
},
{
"icon_id": "35348714",
"name": "日历",
"font_class": "rili1",
"unicode": "e62e",
"unicode_decimal": 58926
},
{
"icon_id": "29941067",
"name": "日历",
"font_class": "rili",
"unicode": "e664",
"unicode_decimal": 58980
},
{
"icon_id": "37970163",
"name": "授权列表",

View File

@ -283,4 +283,12 @@ export function filterDigit(event:any){
*/
export function filterNumber(event:any){
event.target.value = event.target.value.replace(/[^\d]/g,'');
}
/**
*
* @param event
*/
export function filterSpecial(event:any){
event.target.value = event.target.value.replace(/[^\u4e00-\u9fa5a-zA-Z0-9]/g, '')
event.target.value = event.target.value.replace(/[`~!@#$%^&*()_\-+=<>?:"{}|,.\/;'\\[\]·~@#¥%……&*()——\-+={}|《》?:“”【】、;‘’,。、]/g,'')
}

View File

@ -60,7 +60,8 @@ module.exports = {
'6xl': '30px',
'7xl': '36px',
'8xl': '48px',
'9xl': '60px'
'9xl': '60px',
'page-title': '16px'
},
spacing: {
px: '1px',

View File

@ -1 +1 @@
APP_DEBUG = true [APP] DEFAULT_TIMEZONE = Asia/Shanghai AUTH_KEY = {auth_key} [DATABASE] TYPE = mysql HOSTNAME = {dbhost} DATABASE = {dbname} USERNAME = {dbuser} PASSWORD = {dbpwd} HOSTPORT = {dbport} PREFIX = {dbprefix} CHARSET = utf8 DEBUG = false [REDIS] REDIS_HOSTNAME = 127.0.0.1 PORT = 6379 REDIS_PASSWORD = SELECT = 0 [LANG] default_lang = zh-cn [SYSTEM] ADMIN_TOKEN_NAME = token API_TOKEN_NAME = token ADMIN_SITE_ID_NAME = site-id API_SITE_ID_NAME = site-id ADMIN_TOKEN_EXPIRE_TIME = 604800 API_TOKEN_EXPIRE_TIME = 86400 LANG_NAME = lang CHANNEL_NAME = channel ADMIN_DOMAIN = WAP_DOMAIN = WEB_DOMAIN = [NIUCLOUD] code = secret =
APP_DEBUG = true [APP] DEFAULT_TIMEZONE = Asia/Shanghai AUTH_KEY = {auth_key} [DATABASE] TYPE = mysql HOSTNAME = {dbhost} DATABASE = {dbname} USERNAME = {dbuser} PASSWORD = {dbpwd} HOSTPORT = {dbport} PREFIX = {dbprefix} CHARSET = utf8mb4 DEBUG = false [REDIS] REDIS_HOSTNAME = 127.0.0.1 PORT = 6379 REDIS_PASSWORD = SELECT = 0 [QUEUE] state = false [LANG] default_lang = zh-cn [SYSTEM] ADMIN_TOKEN_NAME = token API_TOKEN_NAME = token ADMIN_SITE_ID_NAME = site-id API_SITE_ID_NAME = site-id ADMIN_TOKEN_EXPIRE_TIME = 604800 API_TOKEN_EXPIRE_TIME = 86400 LANG_NAME = lang CHANNEL_NAME = channel ADMIN_DOMAIN = WAP_DOMAIN = WEB_DOMAIN = [NIUCLOUD] code = secret =

View File

@ -2,8 +2,6 @@
return [
'HELLO_WORLD_LINK' => [
'key' => 'hello_world',
'addon_title' => get_lang('dict_diy.hello_world_title'),
'title' => get_lang('dict_diy.hello_world_link'),
'child_list' => [
[

View File

@ -12,15 +12,21 @@ return [
"data" => [
"global" => [
"title" => "hello world首页页面",
"pageBgColor" => "#F8F8F8",
'pageStartBgColor' => '#F8F8F8',
'pageEndBgColor' => '',
'pageGradientAngle' => 'to bottom',
'bgUrl' => '',
'imgWidth' => '',
'imgHeight' => '',
"bottomTabBarSwitch" => true,
"template" => [
'textColor' => "#303133",
"pageBgColor" => "",
"componentBgColor" => "",
'pageStartBgColor' => '',
'pageEndBgColor' => '',
'pageGradientAngle' => 'to bottom',
'componentStartBgColor' => '',
'componentEndBgColor' => '',
'componentGradientAngle' => 'to bottom',
"topRounded" => 0,
"bottomRounded" => 0,
"elementBgColor" => "",
@ -70,8 +76,12 @@ return [
]
],
"ignore" => [],
"pageBgColor" => "",
"componentBgColor" => "",
'pageStartBgColor' => '',
'pageEndBgColor' => '',
'pageGradientAngle' => 'to bottom',
'componentStartBgColor' => '',
'componentEndBgColor' => '',
'componentGradientAngle' => 'to bottom',
"topRounded" => 0,
"bottomRounded" => 0,
"elementBgColor" => "",

View File

@ -3,12 +3,12 @@
return [
'DIY_HELLO_WORLD_INDEX' => [
'title' => get_lang('dict_diy.page_hello_world_index'),
'page' => '/hello_world/pages/index',
'page' => '/addon/hello_world/pages/index',
'action' => ''
],
'DIY_HELLO_WORLD_INFO' => [
'title' => get_lang('dict_diy.page_hello_world_info'),
'page' => '/hello_world/pages/info',
'page' => '/addon/hello_world/pages/info',
'action' => ''
],
];

View File

@ -1,17 +1,3 @@
<?php
return [
[
'menu_name' => '插件菜单',
'menu_key' => 'niucloud_hello',
'menu_type' => 1,
'icon' => 'iconfont-iconyingyongshichang',
'api_url' => '',
'router_path' => 'hello_world',
'view_path' => 'hello_world/index',
'methods' => '',
'sort' => 90,
'status' => 1,
'is_show' => 1,
],
];
return [
];

View File

@ -1,36 +1,16 @@
<?php
return [
[
'menu_name' => '插件站点菜单',
'menu_key' => 'niucloud_site_hello',
'menu_type' => 1,
'icon' => 'iconfont-iconyingyongshichang',
'api_url' => '',
'router_path' => 'hello_world',
'view_path' => 'hello_world/site',
'methods' => '',
'sort' => 100,
'status' => 1,
'is_show' => 1,
],
/* [
'menu_name' => '会员列表',
'menu_key' => 'member_list',
'parent_key' => 'member',
'menu_type' => 1,
'icon' => 'iconfont-iconhuiyuanliebiao',
'api_url' => 'member/member',
'router_path' => 'hello_world_member_list',
'view_path' => 'hello_world/member',
'methods' => 'get',
'sort' => 100,
'status' => 1,
'is_show' => 1,
'children' => [
]
],
"delete" => "member" //针对修改系统菜单处理方式可以删除系统菜单设置对应key值也可删除不需要的菜单处理
*/
];
return [
[
'menu_name' => '插件站点菜单',
'menu_key' => 'niucloud_site_hello',
'menu_type' => 1,
'icon' => 'iconfont-iconyingyongshichang',
'api_url' => '',
'router_path' => 'hello_world',
'view_path' => 'hello_world/site',
'methods' => '',
'sort' => 100,
'status' => 1,
'is_show' => 1,
]
];

View File

@ -2,15 +2,15 @@
return [
'pages' => <<<EOT
// PAGE_BEGIN
{
"path": "addon/{{addon_name}}/pages/index",
"style": {
// #ifdef H5
"navigationStyle": "custom",
// #endif
"navigationBarTitleText": "%{{addon_name}}.pages.index%"
}
}
{
"path": "hello_world/pages/index",
"style": {
// #ifdef H5
"navigationStyle": "custom",
// #endif
"navigationBarTitleText": "%{{addon_name}}.pages.index%"
}
},
// PAGE_END
EOT
];

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 673 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -22,7 +22,8 @@
const warpCss = computed(() => {
var style = '';
style += 'background-color:' + diyComponent.value.componentBgColor + ';';
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`;
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';';
style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';

View File

@ -1,3 +1,3 @@
{
"hello_world.pages.index": "hello_world首页"
}
"pages.index": "hello_world首页"
}

View File

@ -58,7 +58,7 @@ class Request extends \think\Request
if (!$param || !$filter || !is_string($param)) return $param;
// 把数据过滤
$filter_rule = [
"/<(\\/?)(script|i?frame|style|html|body|title|link|meta|object|\\?|\\%)([^>]*?)>/isU",
"/<(\\/?)(script|i?frame|style|html|body|title|link|metaf|alert|font|object|\\?|\\%)([^>]*?)>/isU",
"/(<[^>]*)on[a-zA-Z]+\s*=([^>]*>)/isU",
"/select|join|where|drop|like|modify|rename|insert|update|table|database|alter|truncate|\'|\/\*|\.\.\/|\.\/|union|into|load_file|outfile/is"
];

View File

@ -82,7 +82,7 @@ class Diy extends BaseAdminController
[ "type", "" ],
[ 'template', '' ],
[ 'mode', 'diy' ], // 页面展示模式diy自定义fixed固定
[ "value", "" ],
[ "value", "", false ],
[ 'is_default', 0 ],
[ 'is_change', '' ]
]);
@ -102,9 +102,10 @@ class Diy extends BaseAdminController
$data = $this->request->params([
[ "title", "" ],
[ "name", "" ],
[ "value", "" ],
[ "value", "", false ],
[ 'is_change', '' ]
]);
$this->validate($data, 'app\validate\diy\Diy.edit');
( new DiyService() )->edit($id, $data);
return success('MODIFY_SUCCESS');
@ -158,6 +159,7 @@ class Diy extends BaseAdminController
$diy_service = new DiyService();
return success($diy_service->getLink());
}
/**
* 获取页面模板
* @return Response

View File

@ -16,16 +16,16 @@ use app\service\admin\auth\ConfigService;
use app\service\admin\auth\LoginService;
use app\service\admin\upgrade\UpgradeService;
use app\service\core\addon\CoreAddonDevelopBuildService;
use app\service\core\addon\WapTrait;
use app\service\core\menu\CoreMenuService;
use app\service\core\upload\CoreFileService;
use app\service\core\weapp\CoreWeappCloudService;
use app\upgrade\v011\Upgrade;
use core\base\BaseAdminController;
use think\facade\Db;
use think\Response;
class Login extends BaseAdminController
{
/**
* 登录
* @return Response
@ -69,6 +69,6 @@ class Login extends BaseAdminController
}
public function test(){
(new CoreWeappCloudService())->test();
}
}

View File

@ -27,10 +27,9 @@ class MemberLabel extends BaseAdminController
public function lists()
{
$data = $this->request->params([
['label_name', ''],
[ 'label_name', '' ],
]);
return success((new MemberLabelService())->getPage($data));
return success(( new MemberLabelService() )->getPage($data));
}
/**
@ -40,7 +39,7 @@ class MemberLabel extends BaseAdminController
*/
public function info(int $id)
{
return success((new MemberLabelService())->getInfo($id));
return success(( new MemberLabelService() )->getInfo($id));
}
/**
@ -51,27 +50,27 @@ class MemberLabel extends BaseAdminController
{
$data = $this->request->params([
['label_name', ''],
['memo', ''],
['sort', 0],
[ 'label_name', '' ],
[ 'memo', '' ],
[ 'sort', 0 ],
]);
$this->validate($data, 'app\validate\member\MemberLabel.add');
$id = (new MemberLabelService())->add($data);
return success('ADD_SUCCESS', ['label_id' => $id]);
$id = ( new MemberLabelService() )->add($data);
return success('ADD_SUCCESS', [ 'label_id' => $id ]);
}
/**
* 菜单或接口更新
* 编辑会员标签
*/
public function edit($id)
{
$data = $this->request->params([
['label_name', ''],
['memo', ''],
['sort', 0],
[ 'label_name', '' ],
[ 'memo', '' ],
[ 'sort', 0 ],
]);
$this->validate($data, 'app\validate\member\MemberLabel.edit');
(new MemberLabelService())->edit($id, $data);
( new MemberLabelService() )->edit($id, $data);
return success('EDIT_SUCCESS');
}
@ -82,8 +81,7 @@ class MemberLabel extends BaseAdminController
*/
public function del(int $id)
{
(new MemberLabelService())->del($id);
( new MemberLabelService() )->del($id);
return success('DELETE_SUCCESS');
}
@ -96,7 +94,7 @@ class MemberLabel extends BaseAdminController
*/
public function getAll()
{
return success((new MemberLabelService())->getAll());
return success(( new MemberLabelService() )->getAll());
}
}

View File

@ -1,119 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的saas管理平台
// +----------------------------------------------------------------------
// | 官方网址https://www.niucloud-admin.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace app\adminapi\controller\order;
use app\service\admin\order\RechargeOrderRefundService;
use app\service\admin\order\RechargeOrderService;
use core\base\BaseAdminController;
use think\Response;
class Recharge extends BaseAdminController
{
/**
* 充值订单列表
* @return Response
*/
public function lists()
{
$data = $this->request->params([
['order_no', ''],
['order_status', ''],
['order_from', ''],
['create_time', []],
['pay_time', []],
['member_id', ''],
['start_money', 0],
['end_money', 0]
]);
return success((new RechargeOrderService())->getPage($data));
}
/**
* 充值订单详情
* @param int $order_id
* @return Response
*/
public function detail(int $order_id)
{
return success((new RechargeOrderService())->getDetail($order_id));
}
public function status()
{
return success((new RechargeOrderService())->getStatus());
}
public function refund($order_id)
{
$res = (new RechargeOrderRefundService())->create($order_id);
if ($res === true) return success();
return fail($res);
}
/**
* 退款列表
* @return Response
*/
public function refundLists()
{
$data = $this->request->params([
['create_time', []],
['member_id', ''],
['refund_no', ''],
['status', ''],
['keywords', ''],
['order_no', ''],
]);
return success((new RechargeOrderRefundService())->getPage($data));
}
/**
* 退款详情
* @param int $refund_id
* @return Response
*/
public function refundDetail(int $refund_id)
{
return success((new RechargeOrderRefundService())->getDetail($refund_id));
}
/**
* 查询退款状态
* @return Response
*/
public function refundStatus()
{
return success((new RechargeOrderRefundService())->getStatus());
}
/**
* 退款统计
*/
public function refundStat()
{
return success((new RechargeOrderRefundService())->stat());
}
/**
* 充值统计
*/
public function stat()
{
$data = $this->request->params([
['member_id', ''],
]);
$res = (new RechargeOrderService())->stat($data);
return success($res);
}
}

View File

@ -31,7 +31,7 @@ class Template extends BaseAdminController
['keys', []]
]);
$wechat_template_service = new WechatTemplateService();
return success($wechat_template_service->syncAll($data['keys']));
return success(data:$wechat_template_service->syncAll($data['keys']));
}
/**

View File

@ -1,45 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的saas管理平台
// +----------------------------------------------------------------------
// | 官方网址https://www.niucloud-admin.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
use app\adminapi\middleware\AdminCheckRole;
use app\adminapi\middleware\AdminCheckToken;
use app\adminapi\middleware\AdminLog;
use think\facade\Route;
/**
* 订单相关路由
*/
Route::group('order', function () {
/***************************************************** 充值订单 *************************************************/
//订单列表
Route::get('recharge', 'order.Recharge/lists');
//订单详情
Route::get('recharge/:order_id', 'order.Recharge/detail');
//订单状态
Route::get('recharge/status', 'order.Recharge/status');
//订单统计
Route::get('recharge/stat', 'order.Recharge/stat');
// 订单发起退款
Route::put('recharge/refund/:order_id', 'order.Recharge/refund');
//退款订单列表
Route::get('recharge/refund', 'order.Recharge/refundLists');
//退款订单详情
Route::get('recharge/refund/:refund_id', 'order.Recharge/refundDetail');
//退款订单状态
Route::get('recharge/refund/status', 'order.Recharge/refundStatus');
//退款订单统计
Route::get('recharge/refund/stat', 'order.Recharge/refundStat');
})->middleware([
AdminCheckToken::class,
AdminCheckRole::class,
AdminLog::class
]);

View File

@ -1,52 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的saas管理平台
// +----------------------------------------------------------------------
// | 官方网址https://www.niucloud-admin.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace app\api\controller\order;
use app\service\api\order\RechargeOrderService;
use core\base\BaseApiController;
use think\Response;
class Recharge extends BaseApiController
{
/**
* 充值订单创建
* @return Response
*/
public function create()
{
//['order_from' => 'h5', 'ip' => '127.0.0.1', 'member_message' => '','recharge_money' => 12.00]
$data = $this->request->params([
['member_message', ''],
['recharge_money', 0]
]);
$res = (new RechargeOrderService())->recharge($data);
return success($res);
}
public function lists(){
$data = $this->request->params([
['order_status', '']
]);
$res = (new RechargeOrderService())->getPage($data);
return success($res);
}
/**
* 查询充值订单详情
* @param int $order_id
* @return Response
*/
public function detail(int $order_id){
$res = (new RechargeOrderService())->getDetail($order_id);
return success($res);
}
}

View File

@ -1,32 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的saas管理平台
// +----------------------------------------------------------------------
// | 官方网址https://www.niucloud-admin.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
use app\api\middleware\ApiChannel;
use app\api\middleware\ApiCheckToken;
use app\api\middleware\ApiLog;
use think\facade\Route;
/**
* 会员个人信息管理
*/
Route::group('order', function () {
/***************************************************** 充值订单相关 *************************************************/
//充值订单创建
Route::post('recharge', 'order.Recharge/create');
// 充值订单列表
Route::get('recharge', 'order.Recharge/lists');
// 充值订单详情
Route::get('recharge/:order_id', 'order.Recharge/detail');
})->middleware(ApiChannel::class)
->middleware(ApiCheckToken::class, true)
->middleware(ApiLog::class);

View File

@ -0,0 +1,42 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的saas管理平台
// +----------------------------------------------------------------------
// | 官方网址https://www.niucloud-admin.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace app\command;
use core\exception\CommonException;
use core\job\Dispatch;
use think\console\Input;
use think\console\Output;
use think\queue\Job;
/**
* worker 兼容think自定义指令
*/
trait WorkerCommand
{
public function resetCli(Input $input, Output $output){
// 指令输出
$action = $input->getArgument('action');
$mode = $input->getOption('mode');
// 重新构造命令行参数,以便兼容workerman的命令
global $argv;
$argv = [];
array_unshift($argv, 'think', $action);
if ($mode == 'd') {
$argv[] = '-d';
} else if ($mode == 'g') {
$argv[] = '-g';
}
}
}

View File

@ -0,0 +1,114 @@
<?php
namespace app\command\queue;
use app\command\WorkerCommand;
use app\model\sys\SysSchedule;
use app\service\core\addon\CoreAddonService;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;
use think\facade\Log;
use Workerman\RedisQueue\Client;
use Workerman\Timer;
use Workerman\Worker;
class Queue extends Command
{
use WorkerCommand;
public function configure()
{
// 指令配置
$this->setName('queue:listen')
->addArgument('action', Argument::OPTIONAL, "start|stop|restart|reload|status|connections", 'start')
->addOption('mode', 'm', Option::VALUE_OPTIONAL, 'Run the workerman server in daemon mode.')
->setDescription('基于Redis的消息队列支持消息延迟处理。');
}
/**
* 执行任务
* @return void
*/
protected function execute(Input $input, Output $output)
{
$this->resetCli($input, $output);
Worker::$pidFile = runtime_path() .'workerman_queue.pid';
Worker::$logFile = runtime_path().'workerman.log';
$worker = new Worker();
$worker->name = 'queue_work';
// $worker->count = 3;
$worker->onWorkerStart = function () use($output){
// 定时每10秒一次
Timer::add(30, function()use($output)
{
(new SysSchedule())->select();
});
$redis_option = [
'connect_timeout' => 10,
'max_attempts' => 3,
'retry_seconds' => 5,
];
if(!empty(env('redis.redis_password'))){
$redis_option['auth'] = env('redis.redis_password');
}
$redis_option['db'] = env('redis.select');
$client = new Client('redis://'.env('redis.redis_hostname').':'.env('redis.port'), $redis_option);
$queue_list = $this->getAllQueue();
foreach ($queue_list as $queue_class_name){
$queue_class_name = str_replace('.php', '', $queue_class_name);
// 订阅
$client->subscribe($queue_class_name, function($data) use($queue_class_name, $output){
echo "\n".'['.date('Y-m-d H:i:s').']'." Processing:" . $queue_class_name;
try{
$class_name = '\\' .$queue_class_name;
$class = new $class_name();
$class->fire($data);
} catch (\Throwable $e) {
Log::write(date('Y-m-d H:i:s').',队列有错误:'.$queue_class_name.'_'.$e->getMessage().'_'.$e->getFile().'_'.$e->getLine());
}
echo "\n".'['.date('Y-m-d H:i:s').']'." Processed:" . $queue_class_name;
});
}
// 消费失败触发的回调(可选)
$client->onConsumeFailure(function (\Throwable $exception, $package) use($output){
echo "\n"."队列 " . $package['queue'] . " 消费失败,".$exception->getMessage();
});
};
Worker::runAll();
}
/**
* 捕获所有队列任务
* @return array
*/
public function getAllQueue(){
$class_list = [];
$system_dir = root_path() . 'app' . DIRECTORY_SEPARATOR . 'job';
$addon_dir = root_path() . 'addon' . DIRECTORY_SEPARATOR;
if(is_dir($system_dir)){
search_dir($system_dir, $app_data, root_path());
$class_list = array_merge($class_list, $app_data);
}
$addons = (new CoreAddonService())->getInstallAddonList();
foreach ($addons as $v) {
$addon_path = $addon_dir .$v['key']. DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR. 'job';
if (is_dir($addon_path)) {
search_dir($addon_path, $addon_data, root_path());
$class_list = array_merge($class_list, $addon_data);
}
}
foreach($class_list as &$v){
$v = str_replace('.php', '', $v);
$v = str_replace('/', '\\', $v);
}
return $class_list;
}
}

View File

@ -2,51 +2,110 @@
namespace app\command\schedule;
use app\command\WorkerCommand;
use app\dict\schedule\ScheduleDict;
use app\service\core\addon\CoreAddonService;
use app\service\core\schedule\CoreScheduleService;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;
use think\helper\Str;
use Workerman\Crontab\Crontab;
use Workerman\RedisQueue\Client;
use Workerman\Worker;
use yunwuxin\cron\Task;
class Schedule extends Task
class Schedule extends Command
{
use WorkerCommand;
public function configure()
{
$this->expression($this->getCrontab($this->vars['time']));
// 指令配置
$this->setName('cron:schedule')
->addArgument('action', Argument::OPTIONAL, "start|stop|restart|reload|status|connections", 'start')
->addOption('mode', 'm', Option::VALUE_OPTIONAL, 'Run the workerman server in daemon mode.')
->setDescription('定时任务类似linux的crontab。支持秒级别定时。');
}
/**
* 执行任务
* @return void
*/
protected function execute(Input $input, Output $output)
{
$this->resetCli($input, $output);
Worker::$pidFile = runtime_path() .'workerman_schedule.pid';
$worker = new Worker();
$worker->name = 'schedule_work';
$worker->count = 1;
$output->writeln('['.date('Y-m-d H:i:s').']'." Schedule Starting...");
// 设置时区,避免运行结果与预期不一致
date_default_timezone_set('PRC');
$worker->onWorkerStart = function () use($output){
// // 每分钟的第1秒执行.用于计划任务是否仍在执行
new Crontab('*/10 * * * * *', function(){
$file = root_path('runtime').'.schedule';
file_put_contents($file, time());
});
$core_schedule_service = new CoreScheduleService();
//查询所有的计划任务
$task_list = $core_schedule_service->getList(['status' => ScheduleDict::ON]);
$output->writeln('['.date('Y-m-d H:i:s').']'." Schedule Started.");
foreach ($task_list as $item) {
//获取定时任务时间字符串
new Crontab($this->getCrontab($item['time']), function () use ($core_schedule_service, $item, $output) {
$core_schedule_service->execute($item, $output);
});
}
};
Worker::runAll();
}
/**
* 获取计划任务所需的时间字符串
* 0 1 2 3 4 5
* | | | | | |
* | | | | | +------ day of week (0 - 6) (Sunday=0)
* | | | | +------ month (1 - 12)
* | | | +-------- day of month (1 - 31)
* | | +---------- hour (0 - 23)
* | +------------ min (0 - 59)
* +-------------- sec (0-59)[可省略如果没有0位,则最小时间粒度是分钟]
* @param $data
* @return string
*/
protected function getCrontab($data): string
{
$sec = $data['sec'] ?? '*';
$min = $data['min'] ?? '*';
$hour = $data['hour'] ?? '*';
$day = $data['day'] ?? '*';
$week = $data['week'] ?? '*';
$type = $data['type'] ?? '';
switch ($type) {
case 'sec':// 每隔几秒
$crontab = '*/' . $sec . ' * * * * *';
break;
case 'min':// 每隔几分
$crontab = '*/' . $min . ' * * * *';
$crontab = '0 */' . $min . ' * * * *';
break;
case 'hour':// 每隔几时第几分钟执行
$crontab = $min . ' */' . $hour . ' * * *';
$crontab = '0 ' . $min . ' */' . $hour . ' * * *';
break;
case 'day':// 每隔几日几时几分几秒执行
$crontab = $min . ' ' . $hour . ' */' . $day . ' * *';
case 'day':// 每隔几日第几小时第几分钟执行
$crontab = '0 ' . $min . ' ' . $hour . ' */' . $day . ' * *';
break;
case 'week':// 每周一次,周几具体时间执行
$crontab = $min . ' ' . $hour . ' * * ' . $week;
$crontab = '0 ' .$min . ' ' . $hour . ' * * ' . $week;
break;
case 'month':// 每月一次,某日具体时间执行
$crontab = $min . ' ' . $hour . ' ' . $day . ' * *';
$crontab = '0 ' .$min . ' ' . $hour . ' ' . $day . ' * *';
break;
}
return $crontab ?? '* * * * *';
}
/**
* 执行任务
* @return void
*/
protected function execute()
{
//...具体的任务执行
(new CoreScheduleService())->execute($this->vars);
return $crontab ?? '* */1 * * * *';
}
}

View File

@ -467,14 +467,18 @@ function array_merge2(array $array1, array $array2)
{
foreach ($array2 as $array2_k => $array2_v) {
if (array_key_exists($array2_k, $array1)) {
foreach ($array2_v as $array2_kk => $array2_vv) {
if (array_key_exists($array2_kk, $array1[$array2_k])) {
if (is_array($array2_vv)) {
$array1[$array2_k][$array2_kk] = array_merge($array1[$array2_k][$array2_kk], $array2_vv);
if (is_array($array2_v)) {
foreach ($array2_v as $array2_kk => $array2_vv) {
if (array_key_exists($array2_kk, $array1[$array2_k])) {
if (is_array($array2_vv)) {
$array1[$array2_k][$array2_kk] = array_merge($array1[$array2_k][$array2_kk], $array2_vv);
}
} else {
$array1[$array2_k][$array2_kk] = $array2_vv;
}
} else {
$array1[$array2_k][$array2_kk] = $array2_vv;
}
} else {
$array1[$array2_k] = $array2_v;
}
} else {
$array1[$array2_k] = $array2_v;

View File

@ -34,11 +34,18 @@ class ComponentDict
'support_page' => [], // 支持页面
'uses' => 0, // 最大添加数量
'sort' => 10001,
'position' => '', // 组件置顶标识不能拖拽可选值fixed、top_fixed、right_fixed、bottom_fixed、left_fixed
// 组件属性
'template' => [
"textColor" => "#303133", // 文字颜色
"pageBgColor" => "", // 底部背景颜色
"componentBgColor" => '', // 组件背景颜色
'pageStartBgColor' => '', // 底部背景颜色(开始)
'pageEndBgColor' => '', // 底部背景颜色(结束)
'pageGradientAngle' => 'to bottom', // 渐变角度从上到下to bottom、从左到右to right
'componentBgUrl' => '', // 组件背景图片
'componentBgAlpha' => 2, // 组件背景图片的透明度0~10
"componentStartBgColor" => '', // 组件背景颜色(开始)
"componentEndBgColor" => '', // 组件背景颜色(结束)
"componentGradientAngle" => 'to bottom', // 渐变角度上下to bottom、左右to right
"topRounded" => 0, // 组件上圆角
"bottomRounded" => 0, // 组件下圆角
"elementBgColor" => '', // 元素背景颜色
@ -109,12 +116,6 @@ class ComponentDict
'sort' => 10003,
'value' => [
"layout" => "horizontal",
"navTitle" => "",
"subNavTitle" => "",
"subNavTitleLink" => [
"name" => ""
],
"subNavColor" => "#999999",
"mode" => "graphic",
"type" => "img",
"showStyle" => "fixed",
@ -221,24 +222,13 @@ class ComponentDict
]
],
],
'HorzBlank' => [
'title' => '辅助空白',
'icon' => 'iconfont-iconfuzhukongbai1',
'path' => 'edit-horz-blank',
'support_page' => [],
'uses' => 0,
'sort' => 10005,
'value' => [
'height' => 20
],
],
'HotArea' => [
'title' => '热区',
'icon' => 'iconfont-iconrequ',
'path' => 'edit-hot-area',
'support_page' => [],
'uses' => 0,
'sort' => 10006,
'sort' => 10007,
'value' => [
"imageUrl" => "",
"imgWidth" => 0,
@ -269,22 +259,328 @@ class ComponentDict
'uses' => 0,
'sort' => 10010,
'value' => [
"noticeType" => 'img',
'imgType' => 'system',
"systemUrl" => "style_1", // 系统定义的图片
"imageUrl" => "", // 上传自定义图片
"showType" => "popup", // 点击类型 弹出框,跳转
"scrollWay" => "upDown", // 滚动方式 upDown上下滚动horizontal横向滚动
"fontSize" => 14,
"fontWeight" => "normal",
"noticeTitle" => "公告", // 公告标题文字
"list" => [
[
"text" => "公告",
"link" => [
"name" => ""
]
]
],
],
],
'RichText' => [
'title' => '富文本',
'icon' => 'iconfont-iconfuwenben1',
'path' => 'edit-rich-text',
'support_page' => [],
'uses' => 0,
'sort' => 10011,
'value' => [
"html" => ""
],
],
'ActiveCube' => [
'title' => '活动魔方',
'icon' => 'iconfont-iconmofang1',
'path' => 'edit-active-cube',
'support_page' => [],
'uses' => 0,
'sort' => 10012,
'value' => [
"titleStyle" => [
'title' => '风格1',
'value' => 'style-1'
],
'text' => '超值爆款',
"textLink" => [
"name" => ""
],
"titleColor" => "#F91700",
"subTitle" => [
"text" => "为您精选爆款",
"textColor" => "#FFFFFF",
"startColor" => "#FB792F",
"endColor" => "#F91700",
"link" => [
"name" => ""
],
"text" => ""
],
"iconType" => "system",
"systemIcon" => "style_01",
"showType" => "popup",
"imageUrl" => ""
"blockStyle" => [
'title' => '风格1',
'value' => 'style-1',
'fontWeight' => 'normal'
],
'list' => [
[
"title" => [
"text" => "今日推荐",
"textColor" => "#303133"
],
"subTitle" => [
"text" => "诚意推荐",
"textColor" => "#999999",
"startColor" => "",
"endColor" => "",
],
"moreTitle" => [
"text" => "去看看",
"startColor" => "#FEA715",
"endColor" => "#FE1E00",
],
"listFrame" => [
"startColor" => "#FEA715",
"endColor" => "#FE1E00",
],
"link" => [
"name" => ""
],
"imageUrl" => "static/resource/images/diy/active_cube/active_cube_goods1.png",
],
[
"title" => [
"text" => "优惠好物",
"textColor" => "#303133"
],
"subTitle" => [
"text" => "领券更优惠",
"textColor" => "#999999",
"startColor" => "",
"endColor" => "",
],
"moreTitle" => [
"text" => "去看看",
"startColor" => "#FFBF50",
"endColor" => "#FF9E03",
],
"listFrame" => [
"startColor" => "#FFBF50",
"endColor" => "#FF9E03",
],
"link" => [
"name" => ""
],
"imageUrl" => "static/resource/images/diy/active_cube/active_cube_goods2.png",
],
[
"title" => [
"text" => "热销推荐",
"textColor" => "#303133"
],
"subTitle" => [
"text" => "本周热销商品",
"textColor" => "#999999",
"startColor" => "",
"endColor" => "",
],
"moreTitle" => [
"text" => "去看看",
"startColor" => "#A2E792",
"endColor" => "#49CD2D",
],
"listFrame" => [
"startColor" => "#A2E792",
"endColor" => "#49CD2D",
],
"link" => [
"name" => ""
],
"imageUrl" => "static/resource/images/diy/active_cube/active_cube_goods3.png",
],
[
"title" => [
"text" => "书桌好物",
"textColor" => "#303133"
],
"subTitle" => [
"text" => "办公好物推荐",
"textColor" => "#999999",
"startColor" => "",
"endColor" => "",
],
"moreTitle" => [
"text" => "去看看",
"startColor" => "#4AC1FF",
"endColor" => "#1D7CFF",
],
"listFrame" => [
"startColor" => "#4AC1FF",
"endColor" => "#1D7CFF",
],
"link" => [
"name" => ""
],
"imageUrl" => "static/resource/images/diy/active_cube/active_cube_goods4.png",
],
],
// 组件属性
'template' => [
"textColor" => "#303133", // 文字颜色
'pageStartBgColor' => '', // 底部背景颜色(开始)
'pageEndBgColor' => '', // 底部背景颜色(结束)
'pageGradientAngle' => 'to bottom', // 渐变角度从上到下to bottom、从左到右to right
'componentBgUrl' => '', // 组件背景图片
'componentBgAlpha' => 2, // 组件背景图片的透明度0~10
"componentStartBgColor" => '', // 组件背景颜色(开始)
"componentEndBgColor" => '', // 组件背景颜色(结束)
"componentGradientAngle" => 'to bottom', // 渐变角度上下to bottom、左右to right
"topRounded" => 12, // 组件上圆角
"bottomRounded" => 12, // 组件下圆角
"elementBgColor" => '#FFFAF5', // 元素背景颜色
"topElementRounded" => 0,// 元素上圆角
"bottomElementRounded" => 0, // 元素下圆角
"margin" => [
"top" => 10, // 上边距
"bottom" => 10, // 下边距
"both" => 10 // 左右边距
],
],
],
]
],
'CarouselSearch' => [
'title' => '轮播搜索',
'icon' => 'iconfont-iconsousuokuang',
'path' => 'edit-carousel-search',
'support_page' => [],
'uses' => 1,
'sort' => 10013,
'position' => 'top_fixed', // 组件置顶标识,不能拖拽
'value' => [
'positionWay' => 'static',
'fixedBgColor' => '',
'bgGradient' => false,
// 搜索设置
'search' => [
'logo' => '',
'text' => '请输入搜索关键词',
"link" => [
"name" => ""
],
'hotWord' => [
"interval" => 3,
'list' => []
]
],
// 选项卡设置
'tab' => [
'control' => true, // 控制显示隐藏
'noColor' => '', // 未选中颜色
'selectColor' => '', // 选中颜色
'fixedNoColor' => '', // 下滑未选中颜色
'fixedSelectColor' => '', // 下滑选中颜色
'list' => [
[
'text' => '分类名称', // 最多4个字
'source' => 'diy_page',
'diy_id' => '',
'diy_title' => ''
],
[
'text' => '分类名称',
'source' => 'diy_page',
'diy_id' => '',
'diy_title' => ''
],
[
'text' => '分类名称',
'source' => 'diy_page',
'diy_id' => '',
'diy_title' => ''
],
[
'text' => '分类名称',
'source' => 'diy_page',
'diy_id' => '',
'diy_title' => ''
]
]
],
// 轮播图设置
'swiper' => [
'control' => true, // 控制显示隐藏
"interval" => 5,
'indicatorColor' => 'rgba(0, 0, 0, 0.3)', // 未选中颜色
"indicatorActiveColor" => '#FF0E0E',
'indicatorStyle' => 'style-1',
'indicatorAlign' => 'center',
'swiperStyle' => 'style-1',
'imageHeight' => 168,
'topRounded' => 0,
'bottomRounded' => 0,
'list' => [
[
"imageUrl" => "",
"imgWidth" => 690,
"imgHeight" => 330,
"link" => [
"name" => ""
]
]
]
]
],
],
'FloatBtn' => [
'title' => '浮动按钮',
'icon' => 'iconfont-iconfudonganniu1',
'path' => 'edit-float-btn',
'support_page' => [],
'uses' => 1,
'sort' => 10014,
'position' => 'fixed',
'value' => [
"imageSize" => 40,
"aroundRadius" => 0,
"bottomPosition" => "lowerRight", // 左上upperLeft右上upperRight左下lowerLeft右下lowerRight
"list" => [
[
"imageUrl" => "",
"link" => [
"name" => ""
]
]
],
"offset" => 0 // 偏移量
],
],
'HorzBlank' => [
'title' => '辅助空白',
'icon' => 'iconfont-iconfuzhukongbai1',
'path' => 'edit-horz-blank',
'support_page' => [],
'uses' => 0,
'sort' => 10015,
'value' => [
'height' => 20
],
],
'HorzLine' => [
'title' => '辅助线',
'icon' => 'iconfont-iconfuzhuxian1',
'path' => 'edit-horz-line',
'support_page' => [],
'uses' => 0,
'sort' => 10016,
'value' => [
'borderWidth' => 1,
'borderColor' => '#303133',
'borderStyle' => 'solid'
],
],
],
],
];
return (new DictLoader("UniappComponent"))->load($system_components);
return ( new DictLoader("UniappComponent") )->load($system_components);
}
}

View File

@ -31,12 +31,6 @@ class PagesDict
"componentTitle" => "图文导航",
"uses" => 0,
"layout" => "horizontal",
"navTitle" => "",
"subNavTitle" => "",
"subNavTitleLink" => [
"name" => ""
],
"subNavColor" => "#999999",
"mode" => "graphic",
"showStyle" => "fixed",
"rowCount" => 4,
@ -52,8 +46,14 @@ class PagesDict
"weight" => "normal",
"color" => "#303133"
],
"pageBgColor" => "",
"componentBgColor" => "rgba(255, 255, 255, 1)",
'pageStartBgColor' => '', // 底部背景颜色(开始)
'pageEndBgColor' => '', // 底部背景颜色(结束)
'pageGradientAngle' => 'to bottom', // 渐变角度从上到下to bottom、从左到右to right
'componentBgUrl' => '',
'componentBgAlpha' => 2,
"componentStartBgColor" => "rgba(255, 255, 255, 1)",
"componentEndBgColor" => "",
"componentGradientAngle" => "to bottom",
"topRounded" => 9,
"bottomRounded" => 9,
"elementBgColor" => "",
@ -120,15 +120,24 @@ class PagesDict
"data" => [
"global" => [
"title" => "首页",
"pageBgColor" => "#F8F8F8",
'pageStartBgColor' => '#F8F8F8',
'pageEndBgColor' => '',
'pageGradientAngle' => 'to bottom',
'bgUrl' => '',
'bgHeightScale' => 0,
'imgWidth' => '',
'imgHeight' => '',
"bottomTabBarSwitch" => true,
"template" => [
'textColor' => "#303133",
"pageBgColor" => "",
"componentBgColor" => "",
'pageStartBgColor' => '',
'pageEndBgColor' => '',
'pageGradientAngle' => 'to bottom',
'componentBgUrl' => '',
'componentBgAlpha' => 2,
"componentStartBgColor" => "",
"componentEndBgColor" => "",
"componentGradientAngle" => "to bottom",
"topRounded" => 0,
"bottomRounded" => 0,
"elementBgColor" => "",
@ -176,15 +185,24 @@ class PagesDict
"data" => [
"global" => [
"title" => "个人中心(风格一)",
"pageBgColor" => "#F8F8F8",
'pageStartBgColor' => '#F8F8F8',
'pageEndBgColor' => '',
'pageGradientAngle' => 'to bottom',
'bgUrl' => '',
'bgHeightScale' => 0,
'imgWidth' => '',
'imgHeight' => '',
"bottomTabBarSwitch" => true,
"template" => [
'textColor' => "#303133",
"pageBgColor" => "",
"componentBgColor" => "",
'pageStartBgColor' => '',
'pageEndBgColor' => '',
'pageGradientAngle' => 'to bottom',
'componentBgUrl' => '',
'componentBgAlpha' => 2,
"componentStartBgColor" => "",
"componentEndBgColor" => "",
"componentGradientAngle" => "to bottom",
"topRounded" => 0,
"bottomRounded" => 0,
"elementBgColor" => "",
@ -223,8 +241,14 @@ class PagesDict
"componentTitle" => "会员信息",
"uses" => 0,
"ignore" => [],
"pageBgColor" => "",
"componentBgColor" => "",
'pageStartBgColor' => '',
'pageEndBgColor' => '',
'pageGradientAngle' => 'to bottom',
'componentBgUrl' => '',
'componentBgAlpha' => 2,
"componentStartBgColor" => "",
"componentEndBgColor" => "",
"componentGradientAngle" => "to bottom",
"topRounded" => 9,
"bottomRounded" => 9,
"elementBgColor" => "",
@ -242,6 +266,90 @@ class PagesDict
"bgColorStart" => "",
"bgColorEnd" => ""
],
[
"path" => "edit-horz-blank",
"uses" => 0,
"id" => "2da0xqyo8zms",
"componentName" => "HorzBlank",
"componentTitle" => "辅助空白",
"ignore" => [
"pageBgColor",
"componentBgUrl"
],
"height" => 10,
"textColor" => "#303133",
"pageStartBgColor" => "",
"pageEndBgColor" => "",
"pageGradientAngle" => "to bottom",
"componentBgUrl" => "",
"componentBgAlpha" => 2,
"componentStartBgColor" => "rgba(255, 255, 255, 1)",
"componentEndBgColor" => "",
"componentGradientAngle" => "to bottom",
"topRounded" => 9,
"bottomRounded" => 0,
"elementBgColor" => "",
"topElementRounded" => 0,
"bottomElementRounded" => 0,
"margin" => [
"top" => 6,
"bottom" => 0,
"both" => 16
]
],
[
"path" => "edit-text",
"uses" => 0,
"position" => "",
"id" => "1puhgfus8www",
"componentName" => "Text",
"componentTitle" => "标题",
"ignore" => [],
"style" => "style-2",
"styleName" => "风格2",
"text" => "我的服务",
"link" => [
"name" => "",
],
"textColor" => "#303133",
"fontSize" => 16,
"fontWeight" => "normal",
"textAlign" => "center",
"subTitle" => [
"text" => "",
"color" => "#999999",
"fontSize" => 14,
"control" => true,
"fontWeight" => "normal"
],
"more" => [
"text" => "全部",
"control" => true,
"isShow" => true,
"link" => [
"name" => ""
],
"color" => "#999999"
],
'pageStartBgColor' => '',
'pageEndBgColor' => '',
'pageGradientAngle' => 'to bottom',
'componentBgUrl' => '',
'componentBgAlpha' => 2,
"componentStartBgColor" => "rgba(255, 255, 255, 1)",
"componentEndBgColor" => "",
"componentGradientAngle" => "to bottom",
"topRounded" => 0,
"bottomRounded" => 0,
"elementBgColor" => "",
"topElementRounded" => 0,
"bottomElementRounded" => 0,
"margin" => [
"top" => 0,
"bottom" => 0,
"both" => 16
]
],
[
"path" => "edit-graphic-nav",
"id" => "62b7d7hl4ok",
@ -249,12 +357,6 @@ class PagesDict
"componentTitle" => "图文导航",
"uses" => 0,
"layout" => "horizontal",
"navTitle" => "我的服务",
"subNavTitle" => "",
"subNavTitleLink" => [
"name" => ""
],
"subNavColor" => "#999999",
"mode" => "graphic",
"showStyle" => "fixed",
"rowCount" => 4,
@ -270,15 +372,21 @@ class PagesDict
"weight" => "bold",
"color" => "#303133"
],
"pageBgColor" => "",
"componentBgColor" => "rgba(255, 255, 255, 1)",
"topRounded" => 9,
'pageStartBgColor' => '',
'pageEndBgColor' => '',
'pageGradientAngle' => 'to bottom',
'componentBgUrl' => '',
'componentBgAlpha' => 2,
"componentStartBgColor" => "rgba(255, 255, 255, 1)",
"componentEndBgColor" => "",
"componentGradientAngle" => "to bottom",
"topRounded" => 0,
"bottomRounded" => 9,
"elementBgColor" => "",
"topElementRounded" => 0,
"bottomElementRounded" => 0,
"margin" => [
"top" => 6,
"top" => 0,
"bottom" => 6,
"both" => 16
],
@ -371,12 +479,6 @@ class PagesDict
"componentTitle" => "图文导航",
"ignore" => [],
"layout" => "vertical",
"navTitle" => "",
"subNavTitle" => "",
"subNavTitleLink" => [
"name" => ""
],
"subNavColor" => "#999999",
"mode" => "graphic",
"showStyle" => "fixed",
"rowCount" => 4,
@ -488,8 +590,14 @@ class PagesDict
]
]
],
"pageBgColor" => "",
"componentBgColor" => "rgba(255, 255, 255, 1)",
'pageStartBgColor' => '',
'pageEndBgColor' => '',
'pageGradientAngle' => 'to bottom',
'componentBgUrl' => '',
'componentBgAlpha' => 2,
"componentStartBgColor" => "rgba(255, 255, 255, 1)",
"componentEndBgColor" => "",
"componentGradientAngle" => "to bottom",
"topRounded" => 9,
"bottomRounded" => 9,
"elementBgColor" => "",
@ -514,15 +622,24 @@ class PagesDict
"data" => [
"global" => [
"title" => "个人中心(风格二)",
"pageBgColor" => "#F8F8F8",
'pageStartBgColor' => '#F8F8F8',
'pageEndBgColor' => '',
'pageGradientAngle' => 'to bottom',
"bgUrl" => "static/resource/images/diy/member_style2_bg.png",
'bgHeightScale' => 0,
'imgWidth' => 750,
'imgHeight' => 403,
"bottomTabBarSwitch" => true,
"template" => [
'textColor' => "#303133",
"pageBgColor" => "",
"componentBgColor" => "",
'pageStartBgColor' => '',
'pageEndBgColor' => '',
'pageGradientAngle' => 'to bottom',
'componentBgUrl' => '',
'componentBgAlpha' => 2,
"componentStartBgColor" => "",
"componentEndBgColor" => "",
"componentGradientAngle" => "to bottom",
"topRounded" => 0,
"bottomRounded" => 0,
"elementBgColor" => "",
@ -561,8 +678,14 @@ class PagesDict
"componentTitle" => "会员信息",
"uses" => 0,
"ignore" => [],
"pageBgColor" => "",
"componentBgColor" => "",
'pageStartBgColor' => '',
'pageEndBgColor' => '',
'pageGradientAngle' => 'to bottom',
'componentBgUrl' => '',
'componentBgAlpha' => 2,
"componentStartBgColor" => "",
"componentEndBgColor" => "",
"componentGradientAngle" => "to bottom",
"topRounded" => 0,
"bottomRounded" => 0,
"elementBgColor" => "",
@ -580,6 +703,90 @@ class PagesDict
"bgColorStart" => "",
"bgColorEnd" => ""
],
[
"path" => "edit-horz-blank",
"uses" => 0,
"id" => "5fo173bx5840",
"componentName" => "HorzBlank",
"componentTitle" => "辅助空白",
"ignore" => [
"pageBgColor",
"componentBgUrl"
],
"height" => 10,
"textColor" => "#303133",
"pageStartBgColor" => "",
"pageEndBgColor" => "",
"pageGradientAngle" => "to bottom",
"componentBgUrl" => "",
"componentBgAlpha" => 2,
"componentStartBgColor" => "rgba(255, 255, 255, 1)",
"componentEndBgColor" => "",
"componentGradientAngle" => "to bottom",
"topRounded" => 9,
"bottomRounded" => 0,
"elementBgColor" => "",
"topElementRounded" => 0,
"bottomElementRounded" => 0,
"margin" => [
"top" => 0,
"bottom" => 0,
"both" => 16
]
],
[
"path" => "edit-text",
"uses" => 0,
"position" => "",
"id" => "629cgb1ygcw0",
"componentName" => "Text",
"componentTitle" => "标题",
"ignore" => [],
"style" => "style-1",
"styleName" => "风格1",
"text" => "我的服务",
"link" => [
"name" => ""
],
"textColor" => "#303133",
"fontSize" => 16,
"fontWeight" => "normal",
"textAlign" => "left",
"subTitle" => [
"text" => "",
"color" => "#999999",
"fontSize" => 14,
"control" => false,
"fontWeight" => "normal"
],
"more" => [
"text" => "查看更多",
"control" => false,
"isShow" => false,
"link" => [
"name" => ""
],
"color" => "#999999"
],
'pageStartBgColor' => '',
'pageEndBgColor' => '',
'pageGradientAngle' => 'to bottom',
'componentBgUrl' => '',
'componentBgAlpha' => 2,
"componentStartBgColor" => "rgba(255, 255, 255, 1)",
"componentEndBgColor" => "",
"componentGradientAngle" => "to bottom",
"topRounded" => 0,
"bottomRounded" => 0,
"elementBgColor" => "",
"topElementRounded" => 0,
"bottomElementRounded" => 0,
"margin" => [
"top" => 0,
"bottom" => 0,
"both" => 16
]
],
[
"path" => "edit-graphic-nav",
"id" => "62b7d7hl4ok",
@ -587,12 +794,6 @@ class PagesDict
"componentTitle" => "图文导航",
"uses" => 0,
"layout" => "horizontal",
"navTitle" => "我的服务",
"subNavTitle" => "",
"subNavTitleLink" => [
"name" => ""
],
"subNavColor" => "#999999",
"mode" => "graphic",
"showStyle" => "fixed",
"rowCount" => 4,
@ -608,9 +809,15 @@ class PagesDict
"weight" => "bold",
"color" => "#303133"
],
"pageBgColor" => "",
"componentBgColor" => "rgba(255, 255, 255, 1)",
"topRounded" => 9,
'pageStartBgColor' => '',
'pageEndBgColor' => '',
'pageGradientAngle' => 'to bottom',
'componentBgUrl' => '',
'componentBgAlpha' => 2,
"componentStartBgColor" => "rgba(255, 255, 255, 1)",
"componentEndBgColor" => "",
"componentGradientAngle" => "to bottom",
"topRounded" => 0,
"bottomRounded" => 9,
"elementBgColor" => "",
"topElementRounded" => 0,
@ -709,12 +916,6 @@ class PagesDict
"componentTitle" => "图文导航",
"ignore" => [],
"layout" => "vertical",
"navTitle" => "",
"subNavTitle" => "",
"subNavTitleLink" => [
"name" => ""
],
"subNavColor" => "#999999",
"mode" => "graphic",
"showStyle" => "fixed",
"rowCount" => 4,
@ -826,8 +1027,14 @@ class PagesDict
]
]
],
"pageBgColor" => "",
"componentBgColor" => "rgba(255, 255, 255, 1)",
'pageStartBgColor' => '',
'pageEndBgColor' => '',
'pageGradientAngle' => 'to bottom',
'componentBgUrl' => '',
'componentBgAlpha' => 2,
"componentStartBgColor" => "rgba(255, 255, 255, 1)",
"componentEndBgColor" => "",
"componentGradientAngle" => "to bottom",
"topRounded" => 9,
"bottomRounded" => 9,
"elementBgColor" => "",

View File

@ -1,132 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的saas管理平台
// +----------------------------------------------------------------------
// | 官方网址https://www.niucloud-admin.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace app\dict\order;
use app\dict\pay\PayDict;
/**
*充值订单相关枚举类
* Class RechargeOrderDict
* @package app\dict\order
*/
class RechargeOrderDict
{
//订单状态
//待支付
const WAIT_PAY = 0;
//已完成
const FINISH = 10;
//已关闭
const CLOSE = -1;
// 退款相关状态
// 未申请
const NOT_APPLAY = 0;
// 退款中
const REFUNDING = 1;
// 退款完成
const REFUND_COMPLETED = 2;
// 退款失败
const REFUND_FAIL = -1;
/**
* 当前订单支持的支付方式
*/
const ALLOW_PAY = [
PayDict::WECHATPAY,
PayDict::ALIPAY,
PayDict::OFFLINEPAY,
];
/**
* 订单类型以及名称
* @return array
*/
public static function getOrderType()
{
return [
'type' => 'recharge',
'name' => get_lang('dict_order.order_type_recharge')
];
}
public static function getStatus($status = '')
{
$data = [
self::WAIT_PAY => [
'name' => '待支付',
'status' => self::WAIT_PAY,
'is_refund' => 0,
'action' => [],
'member_action' => [
[
'name' => '支付',
'class' => '',
'params' => ''
],
],
],
self::FINISH => [
'name' => '已完成',
'status' => self::FINISH,
'is_refund' => 0,
'action' => [],
'member_action' => [
],
],
self::CLOSE => [
'name' => '已关闭',
'status' => self::CLOSE,
'is_refund' => 0,
'action' => [],
'member_action' => [
],
]
];
if ($status == '') {
return $data;
}
return $data[$status] ?? '';
}
/**
* 获取退款状态
* @param string $status
* @return array|array[]|string
*/
public static function getRefundStatus(string $status = '')
{
$data = [
self::REFUNDING => [
'name' => get_lang('dict_order_refund.refunding'),
'status' => self::REFUNDING
],
self::REFUND_COMPLETED => [
'name' => get_lang('dict_order_refund.refund_complete'),
'status' => self::REFUND_COMPLETED
],
self::REFUND_FAIL => [
'name' => get_lang('dict_order_refund.refund_fail'),
'status' => self::REFUND_FAIL
]
];
if ($status == '') {
return $data;
}
return $data[$status] ?? '';
}
}

View File

@ -55,6 +55,9 @@ class Index extends BaseInstall
//fileinfo
$fileinfo = extension_loaded('fileinfo');
$system_variables[] = [ "name" => "fileinfo", "need" => "开启", "status" => $fileinfo ];
//sodium
$sodium = extension_loaded('sodium');
$system_variables[] = [ "name" => "sodium", "need" => "开启", "status" => $sodium ];
$root_path = str_replace("\\", DIRECTORY_SEPARATOR, dirname(__FILE__, 4));
$root_path = str_replace("../", DIRECTORY_SEPARATOR, $root_path);

View File

@ -393,6 +393,7 @@ CREATE TABLE `pay_refund` (
`refund_type` varchar(255) NOT NULL DEFAULT '' COMMENT '退款方式',
`main_type` varchar(255) NOT NULL DEFAULT '' COMMENT '操作人类型',
`main_id` int NOT NULL DEFAULT 0 COMMENT '操作人',
`pay_refund_no` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '外部支付方式的退款单号',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '支付记录表' ROW_FORMAT = Dynamic;

View File

@ -44,18 +44,6 @@
</div>
<a href="javascript:void(0)" class="other-links-text" >站点后台</a>
</li>
<li class="other-links-item" onclick="window.open('{$root_url}/web/100000/')">
<div class="other-links-pic">
<img src="INSTALL_IMG/site_web.png" alt="">
</div>
<a href="javascript:void(0)" class="other-links-text" >站点电脑端</a>
</li>
<li class="other-links-item" onclick="window.open('{$root_url}/wap/100000/')">
<div class="other-links-pic">
<img src="INSTALL_IMG/site_h5.png" alt="">
</div>
<a href="javascript:void(0)" class="other-links-text" >站点手机端</a>
</li>
<li class="other-links-item" onclick="window.open('https://www.niucloud.com')">
<div class="other-links-pic">
<img src="INSTALL_IMG/admin.jpg" alt="">
@ -91,4 +79,4 @@
</script>
{/block}
{/block}

View File

@ -26,11 +26,11 @@ class PayReturnTo extends BaseJob
* @param $data
* @return true
*/
protected function doJob($data)
protected function doJob($site_id, $out_trade_no)
{
Log::write('pay_log_' . json_encode($data));
$res = (new CorePayService())->returnTo($data['site_id'], $data['out_trade_no']);
Log::write('pay_log_' . $site_id.'_'.$out_trade_no);
$res = (new CorePayService())->returnTo($site_id, $out_trade_no);
return true;
}

View File

@ -43,8 +43,13 @@ class MemberLabel extends BaseModel
*/
public function getMemberNumAttr($value, $data)
{
if (isset($data['label_id'])) {
return (new Member())->where([['member_label', 'like', '%"' . $data['label_id'] . '"%']])->count();
if (isset($data[ 'label_id' ])) {
$like_arr = [
'[' . $data[ 'label_id' ] . ']',
'[' . $data[ 'label_id' ] . ',%',
'%,' . $data[ 'label_id' ] . ']'
];
return ( new Member() )->where([ [ 'member_label', "like", $like_arr, 'or' ] ])->count();
} else
return 0;
}

View File

@ -1,225 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的saas管理平台
// +----------------------------------------------------------------------
// | 官方网址https://www.niucloud-admin.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace app\model\order;
use app\dict\common\ChannelDict;
use app\model\member\Member;
use app\model\pay\Pay;
use core\base\BaseModel;
use think\model\relation\HasMany;
use think\model\relation\HasOne;
/**
* 充值订单模型
* Class RechargeOrder
* @package app\model\order
*/
class RechargeOrder extends BaseModel
{
/**
* 数据表主键
* @var string
*/
protected $pk = 'order_id';
/**
* 模型名称
* @var string
*/
protected $name = 'recharge_order';
//类型
protected $type = [
'pay_time' => 'timestamp',
'close_time' => 'timestamp',
];
/**
* 登录渠道字段转化
* @param $value
* @param $data
* @return mixed
*/
public function getOrderFromNameAttr($value, $data)
{
if (isset($data['order_from'])) {
return ChannelDict::getType()[$data['order_from']] ?? '';
}
}
/**
* 会员id搜索
* @param $query
* @param $value
* @param $data
*/
public function searchMemberIdAttr($query, $value, $data)
{
if ($value) {
$query->where('member_id', '=', $value);
}
}
/**
* 订单来源
* @param $query
* @param $value
* @param $data
*/
public function searchOrderFromAttr($query, $value, $data)
{
if ($value) {
$query->where('order_from', '=', $value);
}
}
/**
* 订单类型
* @param $query
* @param $value
* @param $data
*/
public function searchOrderTypeAttr($query, $value, $data)
{
if ($value) {
$query->where('order_type', '=', $value);
}
}
/**
* 支付流水号
* @param $query
* @param $value
* @param $data
*/
public function searchOutTradeNoAttr($query, $value, $data)
{
if ($value) {
$query->where('out_trade_no', '=', $value);
}
}
/**
* 订单号
* @param $query
* @param $value
* @param $data
*/
public function searchOrderNoAttr($query, $value, $data)
{
if ($value) {
$query->where('order_no', '=', $value);
}
}
/**
* 订单金额
* @param $query
* @param $value
* @param $data
* @return void
*/
public function searchOrderMoneyAttr($query, $value, $data)
{
if (!empty($data['start_money']) && !empty($data['end_money'])) {
$money = [$data['start_money'], $data['end_money']];
sort($money);
$query->where('order_money', 'between', $money);
} else if (!empty($data['start_money'])) {
$query->where('order_money', '>=', $data['start_money']);
} else if (!empty($data['end_money'])) {
$query->where('order_money', '<=', $data['end_money']);
}
}
/**
* 订单状态
* @param $query
* @param $value
* @param $data
*/
public function searchOrderStatusAttr($query, $value, $data)
{
if ($value != '') {
$query->where('order_status', '=', $value);
}
}
/**
* 创建时间搜索器
* @param $query
* @param $value
* @param $data
*/
public function searchCreateTimeAttr($query, $value, $data)
{
$start_time = empty($value[0]) ? 0 : strtotime($value[0]);
$end_time = empty($value[1]) ? 0 : strtotime($value[1]);
if ($start_time > 0 && $end_time > 0) {
$query->whereBetweenTime('create_time', $start_time, $end_time);
} else if ($start_time > 0 && $end_time == 0) {
$query->where([['create_time', '>=', $start_time]]);
} else if ($start_time == 0 && $end_time > 0) {
$query->where([['create_time', '<=', $end_time]]);
}
}
/**
* 支付时间筛选
* @param $query
* @param $value
* @param $data
* @return void
*/
public function searchPayTimeAttr($query, $value, $data)
{
$start_time = empty($value[0]) ? 0 : strtotime($value[0]);
$end_time = empty($value[1]) ? 0 : strtotime($value[1]);
if ($start_time > 0 && $end_time > 0) {
$query->whereBetweenTime('pay_time', $start_time, $end_time);
} else if ($start_time > 0 && $end_time == 0) {
$query->where([['pay_time', '>=', $start_time]]);
} else if ($start_time == 0 && $end_time > 0) {
$query->where([['pay_time', '<=', $end_time]]);
}
}
/**
* 订单项目
* @return HasMany
*/
public function item()
{
return $this->hasMany(RechargeOrderItem::class, 'order_id', 'order_id');
}
/**
* 订单会员
* @return HasOne
*/
public function member()
{
return $this->hasOne(Member::class, 'member_id', 'member_id');
}
/**
* 支付记录
* @return HasOne
*/
public function pay()
{
return $this->hasOne(Pay::class, 'out_trade_no', 'out_trade_no')->bind(['pay_type_name' => 'type_name']);
}
}

View File

@ -1,66 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的saas管理平台
// +----------------------------------------------------------------------
// | 官方网址https://www.niucloud-admin.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace app\model\order;
use core\base\BaseModel;
use think\model\relation\HasOne;
/**
* 订单项目模型
* Class OrderItem
* @package app\model\order
*/
class RechargeOrderItem extends BaseModel
{
/**
* 数据表主键
* @var string
*/
protected $pk = 'order_item_id';
/**
* 模型名称
* @var string
*/
protected $name = 'recharge_order_item';
/**
* @return HasOne
*/
public function orderNo()
{
return $this->hasOne(RechargeOrder::class, 'order_id', 'order_id')->joinType('left')->withField('order_id, order_no')->bind(['order_no' => 'order_no']);
}
/**
* 数量字段处理
* @param $value
* @param $data
* @return string|void
*/
public function getNumAttr($value, $data)
{
if (isset($data['num'])) {
return number_format($data['num']);
}
}
/**
* 关联订单主表
* @return HasOne
*/
public function ordermain()
{
return $this->hasOne(RechargeOrder::class, 'order_id')->joinType('inner');
}
}

View File

@ -1,216 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的saas管理平台
// +----------------------------------------------------------------------
// | 官方网址https://www.niucloud-admin.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace app\model\order;
use app\dict\order\RechargeOrderDict;
use app\model\member\Member;
use app\model\pay\Refund;
use core\base\BaseModel;
use think\model\relation\HasOne;
/**
* 订单项目模型
* Class OrderItem
* @package app\model\order
*/
class RechargeOrderItemRefund extends BaseModel
{
//类型
protected $type = [
'create_time' => 'timestamp',
'audit_time' => 'timestamp',
'transfer_time' => 'timestamp',
];
/**
* 数据表主键
* @var string
*/
protected $pk = 'refund_id';
/**
* 模型名称
* @var string
*/
protected $name = 'recharge_order_item_refund';
/**
* 退款状态字段处理
* @param $value
* @param $data
* @return mixed
*/
public function getStatusNameAttr($value, $data)
{
if (empty($data['status']))
return '';
$temp = RechargeOrderDict::getRefundStatus()[$data['status']] ?? [];
return $temp['name'] ?? '';
}
/**
*
* @return HasOne
*/
public function item()
{
return $this->hasOne(RechargeOrderItem::class, 'order_item_id', 'order_item_id')->joinType('inner');
}
/**
* 订单会员
* @return HasOne
*/
public function member()
{
return $this->hasOne(Member::class, 'member_id', 'member_id')->withField('member_id, username, mobile, nickname, headimg')->joinType('left');
}
/**
* 关联退款支付记录表
* @return HasOne
*/
public function payrefund()
{
return $this->hasOne(Refund::class, 'refund_no', 'refund_no');
}
/**
* 会员id搜索
* @param $query
* @param $value
* @param $data
*/
public function searchRefundNoAttr($query, $value, $data)
{
if ($value) {
$query->where('refund_no', '=', $value);
}
}
/**
* 订单号搜索
* @param $query
* @param $value
* @param $data
*/
public function searchOrderNoAttr($query, $value, $data)
{
if ($value) {
$query->where('order_no', '=', $value);
}
}
/**
* 订单号搜索(用于关联)
* @param $query
* @param $value
* @param $data
*/
public function searchJoinOrderNoAttr($query, $value, $data)
{
if ($value) {
$query->where('recharge_order_item_refund.order_no', '=', $value);
}
}
/**
* 会员id搜索
* @param $query
* @param $value
* @param $data
*/
public function searchMemberIdAttr($query, $value, $data)
{
if ($value) {
$query->where('member_id', '=', $value);
}
}
/**
* 会员id搜索
* @param $query
* @param $value
* @param $data
*/
public function searchJoinMemberIdAttr($query, $value, $data)
{
if ($value) {
$query->where('recharge_order_item_refund.member_id', '=', $value);
}
}
/**
* 退款状态
* @param $query
* @param $value
* @param $data
*/
public function searchStatusAttr($query, $value, $data)
{
if ($value != '') {
$query->where('status', '=', $value);
}
}
/**
* 退款状态
* @param $query
* @param $value
* @param $data
*/
public function searchJoinStatusAttr($query, $value, $data)
{
if ($value != '') {
$query->where('recharge_order_item_refund.status', '=', $value);
}
}
/**
* 创建时间搜索器
* @param $query
* @param $value
* @param $data
*/
public function searchCreateTimeAttr($query, $value, $data)
{
$start_time = empty($value[0]) ? 0 : strtotime($value[0]);
$end_time = empty($value[1]) ? 0 : strtotime($value[1]);
if ($start_time > 0 && $end_time > 0) {
$query->whereBetweenTime('create_time', $start_time, $end_time);
} else if ($start_time > 0 && $end_time == 0) {
$query->where([['create_time', '>=', $start_time]]);
} else if ($start_time == 0 && $end_time > 0) {
$query->where([['create_time', '<=', $end_time]]);
}
}
/**
* 创建时间搜索器
* @param $query
* @param $value
* @param $data
*/
public function searchJoinCreateTimeAttr($query, $value, $data)
{
$start_time = empty($value[0]) ? 0 : strtotime($value[0]);
$end_time = empty($value[1]) ? 0 : strtotime($value[1]);
if ($start_time > 0 && $end_time > 0) {
$query->whereBetweenTime('recharge_order_item_refund.create_time', $start_time, $end_time);
} else if ($start_time > 0 && $end_time == 0) {
$query->where([['recharge_order_item_refund.create_time', '>=', $start_time]]);
} else if ($start_time == 0 && $end_time > 0) {
$query->where([['recharge_order_item_refund.create_time', '<=', $end_time]]);
}
}
}

View File

@ -1,37 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的saas管理平台
// +----------------------------------------------------------------------
// | 官方网址https://www.niucloud-admin.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace app\model\order;
use core\base\BaseModel;
/**
* 订单操作日志表
* Class OrderLog
* @package app\model\order
*/
class RechargeOrderLog extends BaseModel
{
/**
* 数据表主键
* @var string
*/
protected $pk = 'id';
/**
* 模型名称
* @var string
*/
protected $name = 'recharge_order_log';
}

Some files were not shown because too many files have changed in this diff Show More