This commit is contained in:
wangchen147 2024-01-25 17:48:14 +08:00
parent 34671d6c3d
commit 026d5fa334
840 changed files with 3381 additions and 2830 deletions

View File

@ -5,7 +5,7 @@ import request from '@/utils/request'
* @returns
*/
export function getAddonLocal(params: Record<string, any>) {
return request.get('addon/local', params, {showSuccessMessage: true})
return request.get('addon/local', params, { showSuccessMessage: true })
}
/**

View File

@ -9,6 +9,13 @@ export function login(params: Record<string, any>, app_type: string) {
return request.get(`login/${app_type}`, {params})
}
/**
* 退
*/
export function logout() {
return request.put('auth/logout', {}, { showErrorMessage: false })
}
/**
*
* @returns

View File

@ -5,7 +5,7 @@ import request from '@/utils/request'
* @param addon
*/
export function cloudBuild() {
return request.post('niucloud/build')
return request.post('niucloud/build', {}, { timeout: 0 })
}
/**

View File

@ -108,6 +108,13 @@ export function getDiyTemplate(params: Record<string, any>) {
return request.get(`diy/template`, {params})
}
/**
*
*/
export function getDiyTemplatePages(params: Record<string, any>) {
return request.get(`diy/template/pages`, {params})
}
/**
*
* @param params
@ -152,10 +159,10 @@ export function changeTemplate(params: Record<string, any>) {
}
/**
*
*
* @param params
* @returns
*/
export function getPreviewData(params: Record<string, any>) {
return request.put(`diy/preview`, params, {showSuccessMessage: false})
export function getApps(params: Record<string, any>) {
return request.get(`diy/apps`)
}

View File

@ -166,6 +166,14 @@ export function getWebConfig() {
return request.get('sys/web/website')
}
/**
*
* @returns
*/
export function getWebCopyright() {
return request.get('sys/web/copyright')
}
/**
*
* @param params

View File

@ -29,7 +29,7 @@ export function upgradeAddon(addon: string = '') {
*
*/
export function executeUpgrade() {
return request.post('upgrade/execute')
return request.post('upgrade/execute', {}, { timeout: 0 })
}
/**

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -293,6 +293,8 @@ watch(() => showDialog.value, () => {
})
const clearUpgradeTaskFn = () => {
active.value = 'content'
uploading.value = false
upgradeTask.value = null
upgradeLog = []
flashInterval && clearInterval(flashInterval)

View File

@ -1,4 +1,7 @@
{
"templatePagePlaceholder": "选择模板",
"templatePageEmpty": "无",
"changeTemplatePageTips":"切换模板后,当前页面内容将被替换且不被保存,请谨慎操作",
"developTitle": "开发环境配置",
"wapDomain": "wap域名WAP_DOMAIN",
"wapDomainPlaceholder": "请输入wap域名",

View File

@ -1,24 +1,12 @@
{
"decorate": "装修",
"pageDecorate": "页面装修",
"changeTemplate": "选择页面",
"changePage": "切换",
"templateName": "模板名称",
"changePage": "设置",
"preview": "预览",
"hopeBeforeTip": "我希望把",
"hopeAfterTip": "切换成其他页面",
"changeTemplateTip": "选择",
"template": "模板",
"changeMyPageTip": "将 微页面 设为",
"changeOtherPageTip": "将 其他页面 设为",
"createPage": "创建微页面",
"myPage": "我的微页面",
"refreshPage": "刷新",
"placeholderTemplate": "请选择一个模板",
"placeholderMyPage": "请选择一个微页面",
"placeholderOtherPage": "请选择一个页面",
"developTitle": "开发环境配置",
"wapDomain": "wap域名WAP_DOMAIN",
"wapDomainPlaceholder": "请输入wap域名",
"settingTips": "点击查看如何配置"
"settingTips": "点击查看如何配置",
"link": "链接",
"copy": "复制",
"scanQRCodeOnRight": "扫描右侧二维码查看"
}

View File

@ -1,12 +1,10 @@
{
"title": "页面名称",
"typeName": "页面模板",
"templateName": "模板名称",
"addType": "页面类型",
"typeName": "页面类型",
"forAddon": "所属插件",
"addPageTips": "创建新页面",
"pageTypePlaceholder": "请选择页面模板",
"pageTypePlaceholder": "请选择页面类型",
"nameMax": "名称不能超过12个字符",
"emptyTemplate": "空模板",
"status": "状态",
"updateTime": "更新时间",
"use": "使用",

View File

@ -0,0 +1,11 @@
{
"decorate": "装修",
"preview": "预览",
"developTitle": "开发环境配置",
"wapDomain": "wap域名WAP_DOMAIN",
"wapDomainPlaceholder": "请输入wap域名",
"settingTips": "点击查看如何配置",
"link": "链接",
"copy": "复制",
"scanQRCodeOnRight": "扫描右侧二维码查看"
}

View File

@ -1,28 +1,13 @@
{
"title": "页面名称",
"forAddon": "所属插件",
"typeName": "页面模板",
"addPageTips": "创建新页面",
"pageTemplatePlaceholder": "请选择页面模板",
"nameMax": "名称不能超过12个字符",
"templateName": "模板名称",
"empty": "空白",
"status": "状态",
"updateTime": "更新时间",
"use": "使用",
"isUse": "使用中",
"unused": "未使用",
"all": "全部",
"basicRoute": "基础页面",
"diyPage": "自定义页面",
"wapUrl": "wap链接",
"weappUrl": "小程序链接",
"shareLink": "分享链接",
"copy": "复制",
"copySuccess": "复制成功",
"titlePlaceholder": "请输入页面名称",
"addDiyPage": "添加页面",
"diyPageDeleteTips": "确定要删除该自定义页面吗?",
"promote": "推广",
"share": "分享",
"shareSet": "分享设置",

View File

@ -17,5 +17,7 @@
"remark": "套餐说明",
"reset": "重置",
"search": "搜索",
"foldText":"展开/折叠"
}
"foldText":"展开/折叠",
"appName": "套餐内含应用",
"addonName": "套餐内含插件"
}

View File

@ -27,7 +27,9 @@
"keywordsPlaceholder": "网站关键字",
"logoPlaceholder": "网站Logo",
"descPlaceholder": "网站简介",
"phonePlaceholder": "客服电话"
}
"phonePlaceholder": "客服电话",
"app" : "站点应用",
"addon" : "站点插件"
}

View File

@ -1,5 +1,5 @@
{
"userRealName": "用户名",
"userRealName": "用户真实",
"lastLoginTime": "最后登录时间",
"createTime": "注册时间",
"lastLoginIP": "最后登录IP",
@ -9,4 +9,4 @@
"endDate": "结束时间",
"startDate": "开始时间",
"loginTime": "最后登录时间"
}
}

View File

@ -154,16 +154,12 @@ const formRules = computed(() => {
trigger: 'blur'
}
],
// return /^([a-zA-Z_$])([a-zA-Z0-9_$])*$/.test(val)
router_path: [
{ required: formData.menu_type != 2, message: t('routePathPlaceholder'), trigger: 'blur' }
],
view_path: [
{ required: formData.menu_type == 1, message: t('viewPathPlaceholder'), trigger: 'blur' }
],
icon: [
{ required: formData.menu_type != 2, message: t('selectIconPlaceholder'), trigger: 'blur' }
],
api_url: [
{ required: formData.menu_type == 2, message: t('authIdPlaceholder'), trigger: 'blur' }
]
@ -214,7 +210,6 @@ const confirm = async (formEl: FormInstance | undefined) => {
loading.value = true
const data = formData
data.api_url = data.api_url ? `${data.api_url}/${formData.methods}` : ''
save(data).then(res => {
loading.value = false

View File

@ -1,8 +1,8 @@
<template>
<div class="main-container">
<el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between items-center mb-[20px]">
<span class="text-[20px]"></span>
<div class="flex justify-between items-center">
<span class="text-[20px]">{{ pageName }}</span>
<el-button type="primary" class="w-[100px]" @click="addEvent">
{{ t('addMenu') }}
</el-button>
@ -98,10 +98,11 @@ import { getMenus, deleteMenu } from '@/app/api/sys'
import { t } from '@/lang'
import { ElMessageBox } from 'element-plus'
import EditMenu from '@/app/views/auth/components/edit-menu.vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const active = ref('system')
const pageName = route.meta.title
const menusTableData = reactive<Record<string, any>>({
loading: true,

View File

@ -43,7 +43,7 @@ const pageName = route.meta.title
const loading = ref(true)
const formData = reactive<Record<string, string | boolean | any>>({
is_open: false,
is_open: true,
request_url: ''
})

View File

@ -5,7 +5,7 @@
<h3 class="mb-[10px]">{{ t('addonListSet') }}</h3>
<el-form label-width="100px" class="px-[10px]">
<div ref="addonBoxRef">
<div v-for="(item,index) in diyStore.editComponent.list" :key="item.id" class="item-wrap !cursor-move p-[10px] pb-0 relative border border-dashed border-gray-300 mb-[16px]">
<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]">
<div v-show="item.title" class="flex items-center pb-[10px]">
<img class="w-[60px] h-[60px] rounded-md" :src="img(item.icon)" />
<div class="flex flex-col justify-center ml-[10px] leading-[1]">

View File

@ -67,7 +67,7 @@
<p class="text-sm text-gray-400 mb-[10px]">{{ t('graphicNavTips') }}</p>
<div ref="imageBoxRef">
<div v-for="(item,index) in diyStore.editComponent.list" :key="item.id" class="item-wrap !cursor-move p-[10px] pb-0 relative border border-dashed border-gray-300 mb-[16px]">
<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')" v-show="diyStore.editComponent.mode === 'graphic' || diyStore.editComponent.mode === 'img'">
<upload-image v-model="item.imageUrl" :limit="1"/>
</el-form-item>

View File

@ -13,7 +13,7 @@
</el-form-item>
<div ref="imageBoxRef">
<div v-for="(item,index) in diyStore.editComponent.list" :key="item.id" class="item-wrap !cursor-move p-[10px] pb-0 relative border border-dashed border-gray-300 mb-[16px]">
<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" @change="selectImg" />
</el-form-item>

View File

@ -5,8 +5,7 @@
<h3 class="mb-[10px]">{{ t('styleSet') }}</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="showStyle">{{ diyStore.editComponent.styleName
}}</span>
<span class="text-primary flex-1 cursor-pointer" @click="showStyle">{{ diyStore.editComponent.styleName }}</span>
<el-icon>
<ArrowRight />
</el-icon>
@ -17,8 +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="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" />
@ -36,12 +34,10 @@
<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="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" />
<el-slider v-model="diyStore.editComponent.subTitle.fontSize" show-input size="small" class="ml-[10px] article-slider" :min="12" :max="16" />
</el-form-item>
<el-form-item :label="t('textColor')">
<el-color-picker v-model="diyStore.editComponent.subTitle.color" />
@ -53,8 +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="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" />
@ -71,12 +66,10 @@
<el-dialog v-model="showDialog" :title="t('selectStyle')" width="40%">
<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'">
<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] mr-[12px] 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>
@ -98,8 +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="20" />
</el-form-item>
<el-form-item :label="t('textFontWeight')">
<el-radio-group v-model="diyStore.editComponent.fontWeight">

View File

@ -7,10 +7,18 @@
</el-icon>
<span class="pl-[5px]">{{ t('back') }}</span>
</div>
<div class="text-white ml-[10px] flex items-center">
<div class="text-white ml-[10px] mr-[20px] flex items-center">
<span class="mr-[5px]"> {{ 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-option :label="t('templatePageEmpty')" value="" />
<el-option v-for="(item, key) in templatePages" :label="item.title" :value="key" :key="key"/>
</el-select>
</div>
<div class="flex-1"></div>
<el-button @click="preview()">{{ t('preview') }}</el-button>
<el-button @click="save()">{{ t('save') }}</el-button>
@ -25,9 +33,7 @@
<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)">
<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>
@ -41,43 +47,33 @@
<div class="preview-wrap flex-1 relative mt-[20px]">
<el-scrollbar>
<el-button class="page-btn absolute right-[20px]" @click="diyStore.changeCurrentIndex(-99)">{{
t('pageSet') }}
</el-button>
<el-button class="page-btn absolute right-[20px]" @click="diyStore.changeCurrentIndex(-99)">{{ t('pageSet') }}</el-button>
<div class="diy-view-wrap w-[375px] shadow-lg mx-auto">
<div class="preview-head bg-no-repeat bg-center bg-cover" @click="diyStore.changeCurrentIndex(-99)">
<span class="text-base block text-center truncate cursor-pointer h-[64px] leading-[84px]">{{
diyStore.global.title }}</span>
<span class="text-base block text-center truncate cursor-pointer h-[64px] leading-[84px]">{{ diyStore.global.title }}</span>
</div>
<div class="preview-block relative">
<ul
class="quick-action absolute text-center -right-[70px] top-[20px] w-[42px] rounded shadow-md">
<ul class="quick-action absolute text-center -right-[70px] top-[20px] w-[42px] rounded shadow-md">
<el-tooltip effect="light" :content="t('moveUpComponent')" placement="right">
<icon name="iconfont-iconjiantoushang" size="20px"
class="block cursor-pointer leading-[40px]" @click="diyStore.moveUpComponent" />
<icon name="iconfont-iconjiantoushang" size="20px" class="block cursor-pointer leading-[40px]" @click="diyStore.moveUpComponent" />
</el-tooltip>
<el-tooltip effect="light" :content="t('moveDownComponent')" placement="right">
<icon name="iconfont-iconjiantouxia" size="20px"
class="block cursor-pointer leading-[40px]" @click="diyStore.moveDownComponent" />
<icon name="iconfont-iconjiantouxia" size="20px" class="block cursor-pointer leading-[40px]" @click="diyStore.moveDownComponent" />
</el-tooltip>
<el-tooltip effect="light" :content="t('copyComponent')" placement="right">
<icon name="iconfont-iconcopy-line" size="20px"
class="block cursor-pointer leading-[40px]" @click="diyStore.copyComponent" />
<icon name="iconfont-iconcopy-line" size="20px" class="block cursor-pointer leading-[40px]" @click="diyStore.copyComponent" />
</el-tooltip>
<el-tooltip effect="light" :content="t('delComponent')" placement="right">
<icon name="iconfont-icondelete-line" size="20px"
class="block cursor-pointer leading-[40px]" @click="diyStore.delComponent" />
<icon name="iconfont-icondelete-line" size="20px" class="block cursor-pointer leading-[40px]" @click="diyStore.delComponent" />
</el-tooltip>
<el-tooltip effect="light" :content="t('resetComponent')" placement="right">
<icon name="iconfont-iconloader-line" size="20px"
class="block cursor-pointer leading-[40px]" @click="diyStore.resetComponent" />
<icon name="iconfont-iconloader-line" size="20px" class="block cursor-pointer leading-[40px]" @click="diyStore.resetComponent" />
</el-tooltip>
</ul>
<!-- 组件预览渲染区域 -->
<iframe id="previewIframe" v-show="loadingIframe" :src="wapPreview" frameborder="0"
class="preview-iframe w-[375px]"></iframe>
<iframe id="previewIframe" v-show="loadingIframe" :src="wapPreview" frameborder="0" class="preview-iframe w-[375px]"></iframe>
<div v-show="loadingDev" class="preview-iframe w-[375px] pt-[20px] px-[20px]">
<div class="font-bold text-xl mb-[40px]">{{ t('developTitle') }}</div>
@ -101,63 +97,42 @@
<el-card class="box-card" shadow="never">
<template #header>
<div class="card-header flex justify-between items-center">
<span class="title flex-1">{{ diyStore.currentIndex == -99 ? t('pageSet') :
diyStore.editComponent.componentTitle }}</span>
<div class="tab-wrap flex rounded-[50px] bg-gray-100 text-[14px]"
v-if="diyStore.currentComponent">
<span class="cursor-pointer rounded-[50px] py-[5px] px-[15px]"
:class="{ 'bg-primary text-white': diyStore.editTab == 'content' }"
@click="diyStore.editTab = 'content'">{{ t('tabEditContent') }}</span>
<span class="cursor-pointer rounded-[50px] py-[5px] px-[15px]"
:class="{ 'bg-primary text-white': diyStore.editTab == 'style' }"
@click="diyStore.editTab = 'style'">{{ t('tabEditStyle') }}</span>
<span class="title flex-1">{{ diyStore.currentIndex == -99 ? t('pageSet') : diyStore.editComponent.componentTitle }}</span>
<div class="tab-wrap flex rounded-[50px] bg-gray-100 text-[14px]" v-if="diyStore.currentComponent">
<span class="cursor-pointer rounded-[50px] py-[5px] px-[15px]" :class="{ 'bg-primary text-white': diyStore.editTab == 'content' }" @click="diyStore.editTab = 'content'">{{ t('tabEditContent') }}</span>
<span class="cursor-pointer rounded-[50px] py-[5px] px-[15px]" :class="{ 'bg-primary text-white': diyStore.editTab == 'style' }" @click="diyStore.editTab = 'style'">{{ t('tabEditStyle') }}</span>
</div>
</div>
</template>
<div class="edit-component-wrap">
<component v-if="diyStore.currentComponent" :is="modules[diyStore.currentComponent]"
:value="diyStore.value[diyStore.currentIndex]">
<component v-if="diyStore.currentComponent" :is="modules[diyStore.currentComponent]" :value="diyStore.value[diyStore.currentIndex]">
<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" />
<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 :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 :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 :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 :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 :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 :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>
@ -166,41 +141,27 @@
<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" />
<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 :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 :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 :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 :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 :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 :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>
@ -217,15 +178,18 @@
</template>
<script lang="ts" setup>
import { ref, reactive, toRaw, watch } from 'vue'
import { ref, reactive, toRaw, watch, inject } from 'vue'
import { t } from '@/lang'
import { addDiyPage, editDiyPage, initPage } from '@/app/api/diy'
import { getDiyTemplatePages, addDiyPage, editDiyPage, initPage } from '@/app/api/diy'
import { useRoute, useRouter } from 'vue-router'
import { cloneDeep } from 'lodash-es'
import { ElMessage, ElMessageBox } from 'element-plus'
import useDiyStore from '@/stores/modules/diy'
import storage from '@/utils/storage'
const setLayout = inject('setLayout')
setLayout('decorate')
const diyStore = useDiyStore()
const route = useRoute()
const router = useRouter()
@ -234,10 +198,12 @@ route.query.id = route.query.id || 0
route.query.name = route.query.name || ''
route.query.url = route.query.url || '' //
route.query.type = route.query.type || '' //
route.query.template = route.query.template || '' //
route.query.title = route.query.title || ''
route.query.back = route.query.back || '/website/diy/list'
route.query.back = route.query.back || '/site/diy/list'
const backPath = route.query.back
const template = ref('');
const oldTemplate = ref('');
const wapUrl = ref('')
const wapDomain = ref('')
const wapPreview = ref('')
@ -247,7 +213,6 @@ const loadingDev = ref(false) // 加载开发环境配置
const timeIframe = ref(0) // iframe
const difference = ref(0) // 1000wap
const backPath = route.query.back
const component = ref([])
const componentType: string[] = reactive([])
const page = ref('')
@ -271,6 +236,7 @@ const originData = reactive({
const isChange = ref(true) // truefalse
const goBack = () => {
if (isChange.value) {
location.href = `${location.origin}${backPath}`;
router.push(backPath)
} else {
//
@ -284,7 +250,7 @@ const goBack = () => {
autofocus: false
}
).then(() => {
router.push(backPath)
location.href = `${location.origin}${backPath}`;
}).catch(() => {
})
}
@ -300,6 +266,67 @@ for (const [key, value] of Object.entries(modulesFiles)) {
modules[name] = value.default
}
//
const templatePages: any = reactive({})
const loadDiyTemplatePages = (type:any)=>{
getDiyTemplatePages({
type,
mode: 'diy'
}).then(res => {
for (const key in res.data) {
templatePages[key] = res.data[key];
}
});
}
//
watch(
() => template.value,
(newValue, oldValue) => {
oldTemplate.value = oldValue;
}
)
//
const changeTemplatePage = (value:any)=> {
//
if(diyStore.value.length) {
ElMessageBox.confirm(t('changeTemplatePageTips'), t('warning'), {
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
}).then(() => {
if (value) {
let data = templatePages[value].data;
diyStore.global = data.global;
if (data.value.length) {
diyStore.value = data.value
}
} else {
//
diyStore.init();
}
if(route.query.title) diyStore.global.title = route.query.title
}).catch(() => {
//
template.value = oldTemplate.value;
});
}else{
if (value) {
let data = templatePages[value].data;
diyStore.global = data.global;
if (data.value.length) {
diyStore.value = data.value
}
} else {
//
diyStore.init();
}
if(route.query.title) diyStore.global.title = route.query.title
}
};
//
watch(
() => diyStore,
@ -326,7 +353,6 @@ initPage({
name: route.query.name,
url: route.query.url,
type: route.query.type,
template: route.query.template,
title: route.query.title
}).then(async (res) => {
const data = res.data
@ -393,6 +419,8 @@ initPage({
}
}
loadDiyTemplatePages(data.type);
//
wapDomain.value = data.domain_url.wap_domain
wapUrl.value = data.domain_url.wap_url
@ -501,6 +529,8 @@ const save = (callback: any) => {
if (isRepeat.value) return
isRepeat.value = true
diyStore.templateName = template.value;
let data = {
id: diyStore.id,
name: diyStore.name,
@ -522,7 +552,7 @@ const save = (callback: any) => {
if (diyStore.id) {
isRepeat.value = false //
} else {
router.push(backPath)
location.href = `${location.origin}${backPath}`;
}
if (callback) callback(res.data.id)
}
@ -566,7 +596,6 @@ const preview = () => {
}
.edit-component-wrap {
.content-wrap,
.style-wrap {
.edit-attr-item-wrap {

View File

@ -1,14 +1,11 @@
<template>
<div class="flex flex-wrap">
<div class="page-item relative bg-no-repeat ml-[20px] mr-[40px] mt-[20px] bg-[#f7f7f7] w-[300px] pt-[80px] pb-[20px]"
:class="{ 'cursor-pointer': !item.isDisabledPop }" v-for="(item, key) in page" :key="key">
<p class="absolute top-[46px] left-[50%] translate-x-[-50%] text-[14px] truncate w-[130px] text-center">
{{ item.use_template.title }}</p>
<div class="flex flex-wrap mt-[20px] min-w-[1000px]" 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>
<div v-show="item.use_template.url" class="w-[282px] h-[493px] mx-auto">
<iframe :id="'previewIframe_' + key" v-show="item.loadingIframe" class="w-[282px] h-[493px] mx-auto"
:src="item.use_template.wapPreview" frameborder="0"></iframe>
<div v-show="item.loadingDev" class="w-[282px] h-[493px] mx-auto bg-body pt-[20px] px-[20px]">
<div v-show="page.use_template.url" class="w-[320px] h-[550px] mx-auto">
<iframe :id="'previewIframe_' + key" v-show="page.loadingIframe" class="w-[320px] h-[550px] mx-auto" :src="page.use_template.wapPreview" frameborder="0"></iframe>
<div v-show="page.loadingDev" class="w-[320px] h-[550px] mx-auto bg-body pt-[20px] px-[20px]">
<div class="font-bold text-xl mb-[40px]">{{ t('developTitle') }}</div>
<div class="mb-[20px] flex flex-col">
<text class="mb-[10px]">{{ t('wapDomain') }}</text>
@ -21,80 +18,50 @@
</div>
</div>
<div v-show="!item.use_template.wapPreview" class="overflow-hidden w-[282px] h-[493px] mx-auto">
<img class="max-w-full" v-if="item.use_template.cover" :src="img(item.use_template.cover)" />
<div v-show="!page.use_template.wapPreview" class="overflow-hidden w-[320px] h-[550px] mx-auto">
<img class="max-w-full" v-if="page.use_template.cover" :src="img(page.use_template.cover)" />
</div>
<p class="text-[12px] text-[#999] mt-[10px] mx-auto truncate text-center w-[250px]">
{{ item.use_template.desc }}</p>
<div class="popup-wrap absolute inset-x-0 inset-y-0 select-none" :class="{ 'disabled': page.isDisabledPop }"></div>
<div class="item-hide absolute inset-x-0 inset-y-0 bg-black bg-opacity-50 text-center"
:class="{ 'disabled': item.isDisabledPop }">
<div
class="item-btn-box absolute top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] flex flex-col flex-wrap">
<el-button @click="show(key, item)">{{ t('changePage') }}</el-button>
<el-button @click="toDecorate(item.use_template)"
v-show="item.use_template.mode != 'other' || item.use_template.action == 'decorate'">{{
t('decorate') }}
</el-button>
<el-button @click="toPreview(item.use_template)">{{ t('preview') }}</el-button>
</div>
<div class="w-[500px]">
<div class="flex flex-wrap">
<diy-link v-model="link" :ignore="['DIY_LINK']" @success="changePage">
<el-button type="primary">{{ t('changePage') }}</el-button>
</diy-link>
<el-button type="primary" @click="toDecorate()" v-show="page.use_template.action == 'decorate'" class="ml-[12px]">{{ t('decorate') }}</el-button>
</div>
<div class="info-wrap">
<div class="mt-[20px] bg-[#F7F8FA] p-[20px] flex items-center justify-between">
<div>
<div class="font-bold">{{ t('H5') }}</div>
<el-form label-width="40px" class="mt-[5px]">
<el-form-item :label="t('link')" v-show="page.use_template.wapPreview" class="mb-[5px]">
<el-input readonly :value="page.use_template.wapPreview">
<template #append>
<el-button @click="copyEvent(page.use_template.wapPreview)" class="bg-primary copy">{{ t('copy') }}</el-button>
</template>
</el-input>
</el-form-item>
</el-form>
<div class="text-[#999] text-base">{{ t('scanQRCodeOnRight') }}</div>
</div>
<div class="text-center">
<el-image class="w-[100px] h-[100px] mb-[5px]" :src="wapImage" />
<div @click="toPreview()" class="text-primary text-base cursor-pointer">{{ t('preview') }}</div>
</div>
</div>
</div>
</div>
</div>
<el-dialog v-model="showDialog" :title="t('changeTemplate')" width="400px" :close-on-press-escape="false"
:destroy-on-close="true" :close-on-click-modal="false">
<el-form :model="form" label-width="0px" v-if="formData.type">
<el-form-item label="">
<div>{{ t('hopeBeforeTip') }}<span class="text-primary px-[5px]">{{ page[formData.type].title
}}</span>{{ t('hopeAfterTip') }}
</div>
</el-form-item>
<el-form-item label="">
<el-select v-model="hope" class="w-full">
<el-option :label="t('changeTemplateTip') + ' ' + page[formData.type].title + ' ' + t('template')"
value="template" />
<el-option :label="t('changeMyPageTip') + ' ' + page[formData.type].title" value="diy" />
<el-option :label="t('changeOtherPageTip') + ' ' + page[formData.type].title" value="other" />
</el-select>
</el-form-item>
<el-form-item label="" v-show="hope == 'template'">
<el-select v-model="formData.template" class="w-full">
<el-option v-for="(item, key) in page[formData.type].template" :label="item.title" :value="key" :key="key"/>
</el-select>
</el-form-item>
<el-form-item label="" v-show="hope == 'diy'">
<el-select v-model="formData.id" class="w-full">
<el-option v-for="(item, index) in page[formData.type].my_page" :label="item.title" :value="item.id" :key="index" />
</el-select>
<div class="mt-[10px]">
<span class="cursor-pointer text-primary mr-[10px]" @click="toDiyList">{{ t('createPage') }}</span>
<span class="cursor-pointer text-primary" @click="refreshMyPage">{{ t('refreshPage') }}</span>
</div>
</el-form-item>
<el-form-item label="" v-show="hope == 'other'">
<el-select v-model="formData.page" class="w-full">
<el-option v-for="(item, index) in page[formData.type].other_page" :label="item.title"
:value="item.page" :key="index" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="showDialog = false">{{ t('cancel') }}</el-button>
<el-button type="primary" @click="save">{{ t('confirm') }}</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
@ -103,22 +70,25 @@ import { t } from '@/lang'
import { img } from '@/utils/common'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { getDecoratePage, getDiyList, changeTemplate } from '@/app/api/diy'
import { getDecoratePage, changeTemplate } from '@/app/api/diy'
import storage from '@/utils/storage'
import QRCode from 'qrcode'
import { useClipboard } from '@vueuse/core'
const type: any = ref('DIY_INDEX');
const page: any = reactive({})
const showDialog = ref(false)
const router = useRouter()
const hope = ref('template')
const wapDomain = ref('')
const wapImage = ref('')
const link = ref({
name: ''
})
//
const formData = reactive({
type: '',
name: '',
mode: '',
template: '',
id: '',
parent:'',
page: '',
title: '',
action: ''
@ -126,43 +96,41 @@ const formData = reactive({
//
const refreshData = () => {
formData.type = ''
formData.name = ''
formData.mode = ''
formData.template = ''
formData.id = ''
formData.page = ''
formData.title = ''
formData.action = ''
getDecoratePage({}).then(res => {
getDecoratePage({
type : type.value
}).then(res => {
for (const key in res.data) {
page[key] = res.data[key]
}
for (const key in page) {
if (page[key].use_template.url) {
page[key].loadingIframe = false // iframe
page[key].loadingDev = false //
page[key].isDisabledPop = false //
page[key].difference = 0 // 1000wap
link.value.name = page.use_template.name;
link.value.title = page.use_template.title;
link.value.page = page.use_template.page;
link.value.action = page.use_template.action;
link.value.parent = page.use_template.parent;
wapDomain.value = page[key].domain_url.wap_domain
page[key].wapUrl = page[key].domain_url.wap_url
if (page.use_template.url) {
page.loadingIframe = false // iframe
page.loadingDev = false //
page.isDisabledPop = false //
page.difference = 0 // 1000wap
if (import.meta.env.MODE == 'development') {
// wap
if (wapDomain.value) {
page[key].wapUrl = wapDomain.value + '/wap'
setDomain(key)
}
if (storage.get('wap_domain')) {
page[key].wapUrl = storage.get('wap_domain')
setDomain(key)
}
wapDomain.value = page.domain_url.wap_domain
page.wapUrl = page.domain_url.wap_url
if (import.meta.env.MODE == 'development') {
// wap
if (wapDomain.value) {
page.wapUrl = wapDomain.value + '/wap'
setDomain()
}
if (storage.get('wap_domain')) {
page.wapUrl = storage.get('wap_domain')
setDomain()
}
setDomain(key)
}
setDomain()
}
})
}
@ -174,36 +142,32 @@ window.addEventListener('message', (event) => {
try {
const data = JSON.parse(event.data)
if (['appOnLaunch', 'appOnReady'].indexOf(data.type) != -1) {
for (const key in page) {
page[key].loadingDev = false //
page[key].loadingIframe = true // iframe
const loadTime = new Date().getTime()
page[key].difference = loadTime - page[key].timeIframe
page[key].isDisabledPop = false //
}
page.loadingDev = false //
page.loadingIframe = true // iframe
const loadTime = new Date().getTime()
page.difference = loadTime - page.timeIframe
page.isDisabledPop = false //
}
} catch (e) {
for (const key in page) {
initLoad(key)
}
initLoad()
console.log('后台接受数据错误', e)
}
}, false)
// uniapp
const postMessage = (key: string) => {
const postMessage = () => {
const diyData = JSON.stringify({
type: 'appOnReady',
message: '加载完成'
})
if (window['previewIframe_' + key]) window['previewIframe_' + key].contentWindow.postMessage(diyData, '*')
if (window['previewIframe_' + type.value]) window['previewIframe_' + type.value].contentWindow.postMessage(diyData, '*')
}
//
const initLoad = (key: string) => {
page[key].loadingDev = true
page[key].isDisabledPop = true
page[key].loadingIframe = false
const initLoad = () => {
page.loadingDev = true
page.isDisabledPop = true
page.loadingIframe = false
}
const saveDomain = () => {
@ -215,21 +179,17 @@ const saveDomain = () => {
return
}
const wapUrl = wapDomain.value + '/wap'
storage.set({ key: 'wap_domain', data: wapUrl })
storage.set({key: 'wap_domain', data: wapUrl})
for (const key in page) {
if (page[key].use_template.url) {
page[key].wapUrl = wapUrl
setDomain(key)
}
if (page.use_template.url) {
page.wapUrl = wapUrl
setDomain()
}
setTimeout(() => {
for (const key in page) {
if (page[key].use_template.url) {
page[key].loadingIframe = true // iframe
page[key].loadingDev = false //
page[key].isDisabledPop = false //
}
if (page.use_template.url) {
page.loadingIframe = true // iframe
page.loadingDev = false //
page.isDisabledPop = false //
}
}, 100 * 3)
}
@ -238,46 +198,29 @@ const settingTips = () => {
window.open('https://www.kancloud.cn/niucloud/niucloud-admin-develop/3213393')
}
const setDomain = (key: string) => {
page[key].use_template.wapPreview = page[key].wapUrl + page[key].use_template.url
page[key].timeIframe = new Date().getTime()
postMessage(key)
const setDomain = () => {
page.use_template.wapPreview = page.wapUrl + page.use_template.url
QRCode.toDataURL(page.use_template.wapPreview, { errorCorrectionLevel: 'L', margin: 0, width: 100 }).then(url => {
wapImage.value = url
})
page.timeIframe = new Date().getTime()
postMessage()
setTimeout(() => {
if (page[key].difference == 0) initLoad(key)
if (page.difference == 0) initLoad()
}, 1000 * 2)
}
const show = (key: string, data: any) => {
//
showDialog.value = true
hope.value = data.use_template.hope
formData.type = key
formData.name = data.use_template.name
formData.mode = data.use_template.mode
formData.action = data.use_template.action
if (hope.value == 'template') {
formData.template = data.use_template.template
} else if (hope.value == 'diy') {
formData.id = data.use_template.id
} else if (hope.value == 'other') {
formData.page = data.use_template.page
formData.title = data.use_template.title
}
}
//
const toDecorate = (data: any) => {
const toDecorate = () => {
const query: any = {
back: '/site/diy/index'
}
if (data.id) {
query.id = data.id
} else if (data.name) {
query.name = data.name
} else if (data.url) {
query.url = data.url
if (page.use_template.id) {
query.id = page.use_template.id
} else if (page.use_template.type) {
query.name = page.use_template.type
} else if (page.use_template.url) {
query.url = page.use_template.url
}
const url = router.resolve({
path: '/decorate/edit',
@ -287,128 +230,30 @@ const toDecorate = (data: any) => {
}
//
const toPreview = (data: any) => {
let page = data.page
if (data.url) {
page = data.url
} else if (data.id) {
page += '?id=' + data.id
const toPreview = () => {
let value = page.use_template.page
if (page.use_template.url) {
value = page.use_template.url
} else if (page.use_template.id) {
value += '?id=' + page.use_template.id
}
const url = router.resolve({
path: '/preview/wap',
query: {
page
page:value
}
})
window.open(url.href)
}
//
const toDiyList = (data: any) => {
const url = router.resolve({
path: '/diy/list'
})
window.open(url.href)
}
//
const refreshMyPage = () => {
getDiyList({ type: formData.type }).then((res) => {
let isExist = true //
for (let i = 0; i < res.data.length; i++) {
if (formData.id == res.data[i].id) {
isExist = false
break
}
}
if (isExist) {
formData.id = ''
}
page[formData.type].my_page = {}
Object.assign(page[formData.type].my_page, res.data)
})
}
watch(
() => hope.value,
(newValue, oldValue) => {
//
if (newValue == 'template') {
//
formData.id = ''
formData.page = ''
formData.action = 'decorate'
formData.name = formData.type
} else if (newValue == 'diy') {
//
formData.mode = 'diy'
formData.template = ''
formData.page = ''
formData.action = 'decorate'
formData.name = formData.type
} else if (newValue == 'other') {
//
formData.mode = 'other'
formData.template = ''
formData.id = ''
}
}
)
//
watch(
() => formData.template,
(newValue, oldValue) => {
if (newValue) {
formData.mode = page[formData.type].template[newValue].mode
}
}
)
//
watch(
() => formData.page,
(newValue, oldValue) => {
if (newValue) {
for (let i = 0; i < page[formData.type].other_page.length; i++) {
if (page[formData.type].other_page[i].page == newValue) {
formData.name = page[formData.type].other_page[i].name
formData.title = page[formData.type].other_page[i].title
formData.action = page[formData.type].other_page[i].action
break
}
}
}
}
)
const isRepeat = ref(false)
const save = () => {
if (hope.value == 'template') {
if (formData.template == '') {
ElMessage({
type: 'warning',
message: `${t('placeholderTemplate')}`
})
return
}
} else if (hope.value == 'diy') {
if (formData.id == '') {
ElMessage({
type: 'warning',
message: `${t('placeholderMyPage')}`
})
return
}
} else if (hope.value == 'other') {
if (formData.page == '') {
ElMessage({
type: 'warning',
message: `${t('placeholderOtherPage')}`
})
return
}
}
const changePage = ()=>{
formData.type = type.value;
formData.name = link.value.name;
formData.page = link.value.url;
formData.title = link.value.title;
formData.action = link.value.action;
formData.parent = link.value.parent;
if (isRepeat.value) return
isRepeat.value = true
@ -417,39 +262,44 @@ const save = () => {
...formData
}).then((res) => {
isRepeat.value = false
showDialog.value = false
refreshData()
})
}
//
const { copy, isSupported, copied } = useClipboard()
const copyEvent = (text: string) => {
if (!isSupported.value) {
ElMessage({
message: t('notSupportCopy'),
type: 'warning'
})
}
copy(text)
}
watch(copied, () => {
if (copied.value) {
ElMessage({
message: t('copySuccess'),
type: 'success'
})
}
})
</script>
<style lang="scss" scoped>
.page-item {
background-image: url(@/app/assets/images/iphone_bg.png);
background-color: var(--el-bg-color);
background-size: 100%;
.item-hide {
.popup-wrap {
display: none;
.item-btn-box {
button {
height: 35px;
width: 100px;
&~button {
margin-top: 15px;
margin-left: 0;
}
}
}
}
&:hover {
.item-hide:not(.disabled) {
.popup-wrap:not(.disabled) {
display: block !important;
}
}

View File

@ -3,9 +3,7 @@
<el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between items-center">
<span class="text-[20px]">{{ pageName }}</span>
<el-button type="primary" class="w-[100px]" @click="dialogVisible = true">
{{ t('addDiyPage') }}
</el-button>
<el-button type="primary" class="w-[100px]" @click="dialogVisible = true">{{ t('addDiyPage') }}</el-button>
</div>
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
@ -13,6 +11,12 @@
<el-form-item :label="t('title')" prop="title">
<el-input v-model="diyPageTableData.searchParam.title" :placeholder="t('titlePlaceholder')" />
</el-form-item>
<el-form-item :label="t('forAddon')" prop="addon_name">
<el-select v-model="diyPageTableData.searchParam.addon_name" :placeholder="t('pageTypePlaceholder')">
<el-option :label="t('all')" value="" />
<el-option v-for="(item, key) in apps" :label="item.title" :value="key" :key="key"/>
</el-select>
</el-form-item>
<el-form-item :label="t('typeName')" prop="type">
<el-select v-model="diyPageTableData.searchParam.type" :placeholder="t('pageTypePlaceholder')">
<el-option :label="t('all')" value="" />
@ -33,26 +37,25 @@
</template>
<el-table-column prop="title" :label="t('title')" min-width="120" />
<el-table-column prop="addon_name" :label="t('forAddon')" min-width="80" />
<el-table-column prop="type_name" :label="t('typeName')" min-width="80" />
<!-- <el-table-column :label="t('status')" min-width="80">-->
<!-- <template #default="{ row }">-->
<!-- <span v-if="row.type == 'DIY_PAGE'">-</span>-->
<!-- <template v-else>-->
<!-- <span v-if="row.is_default == 1" class="text-primary">{{ t('isUse') }}</span>-->
<!-- <span v-else>{{ t('unused') }}</span>-->
<!-- </template>-->
<!-- </template>-->
<!-- </el-table-column>-->
<el-table-column :label="t('status')" min-width="80">
<template #default="{ row }">
<span v-if="row.type == 'DIY_PAGE'">-</span>
<template v-else>
<span v-if="row.is_default == 1" class="text-primary">{{ t('isUse') }}</span>
<span v-else>{{ t('unused') }}</span>
</template>
</template>
</el-table-column>
<el-table-column prop="update_time" :label="t('updateTime')" min-width="120" />
<el-table-column :label="t('operation')" fixed="right" align="right" min-width="160">
<template #default="{ row }">
<el-button type="primary" link @click="toPreview(row)">{{ t('promote') }}</el-button>
<el-button v-if="row.type == 'DIY_PAGE'" type="primary" link @click="openShare(row)">{{
t('shareSet') }}
</el-button>
<el-button v-if="row.is_default == 0" type="primary" link @click="setUse(row.id)">{{ t('use') }}</el-button>
<el-button v-if="row.type == 'DIY_PAGE'" type="primary" link @click="openShare(row)">{{ t('shareSet') }}</el-button>
<el-button type="primary" link @click="editEvent(row)">{{ t('edit') }}</el-button>
<!-- <el-button v-if="row.type == 'DIY_PAGE' || row.is_default == 0" type="danger" link @click="deleteEvent(row.id)">{{ t('delete') }}</el-button>-->
<el-button type="primary" link @click="deleteEvent(row.id)">{{ t('delete') }}</el-button>
</template>
</el-table-column>
@ -71,20 +74,13 @@
<el-form :model="formData" label-width="90px" ref="formRef" :rules="formRules">
<el-form-item :label="t('title')" prop="title">
<el-input v-model="formData.title" :placeholder="t('titlePlaceholder')" clearable maxlength="12"
show-word-limit class="w-full" />
<el-input v-model="formData.title" :placeholder="t('titlePlaceholder')" clearable maxlength="12" show-word-limit class="w-full" />
</el-form-item>
<el-form-item :label="t('addType')" prop="type">
<el-form-item :label="t('typeName')" prop="type">
<el-select v-model="formData.type" :placeholder="t('pageTypePlaceholder')" class="w-full">
<el-option v-for="(item, key) in pageType" :label="item.title" :value="key" :key="key"/>
</el-select>
</el-form-item>
<el-form-item :label="t('templateName')" prop="template" v-show="pageTypeData">
<el-select v-model="formData.template" class="w-full">
<el-option :label="t('emptyTemplate')" value="" />
<el-option v-for="(item, key) in pageTypeData" :label="item.title" :value="key" :key="key"/>
</el-select>
</el-form-item>
</el-form>
<template #footer>
@ -106,12 +102,10 @@
<span>{{ sharePage }}</span>
</el-form-item>
<el-form-item :label="t('shareTitle')" prop="title">
<el-input v-model="shareFormData[tabShareType].title" :placeholder="t('shareTitlePlaceholder')"
clearable maxlength="30" show-word-limit />
<el-input v-model="shareFormData[tabShareType].title" :placeholder="t('shareTitlePlaceholder')" clearable maxlength="30" show-word-limit />
</el-form-item>
<el-form-item :label="t('shareDesc')" prop="desc" v-if="tabShareType == 'wechat'">
<el-input v-model="shareFormData[tabShareType].desc" :placeholder="t('shareDescPlaceholder')"
type="textarea" rows="4" clearable maxlength="100" show-word-limit />
<el-input v-model="shareFormData[tabShareType].desc" :placeholder="t('shareDescPlaceholder')" type="textarea" rows="4" clearable maxlength="100" show-word-limit />
</el-form-item>
<el-form-item :label="t('shareImageUrl')" prop="url">
<upload-image v-model="shareFormData[tabShareType].url" :limit="1" />
@ -132,7 +126,7 @@
<script lang="ts" setup>
import { reactive, ref, computed } from 'vue'
import { t } from '@/lang'
import { getDiyPageList, deleteDiyPage, getDiyTemplate, editDiyPageShare } from '@/app/api/diy'
import { getApps,getDiyPageList, deleteDiyPage, getDiyTemplate, editDiyPageShare, setUseDiyPage } from '@/app/api/diy'
import { ElMessageBox, FormInstance } from 'element-plus'
import { useRoute, useRouter } from 'vue-router'
import { getUrl } from '@/app/api/sys'
@ -140,13 +134,12 @@ import { getUrl } from '@/app/api/sys'
const router = useRouter()
const route = useRoute()
const pageName = route.meta.title
const pageType: any = reactive({}) //
const pageType: any = reactive({}) //
//
const formData = reactive({
title: '',
type: '',
template: ''
type: ''
})
//
@ -161,15 +154,6 @@ const formRules = computed(() => {
}
})
const pageTypeData = computed(() => {
let data: any = ''
formData.template = ''
if (formData.type) {
data = pageType[formData.type].template
}
return data
})
const formRef = ref<FormInstance>()
const dialogVisible = ref(false)
const addEvent = async (formEl: FormInstance | undefined) => {
@ -178,9 +162,11 @@ const addEvent = async (formEl: FormInstance | undefined) => {
await formEl.validate(async (valid) => {
if (valid) {
dialogVisible.value = false
let url = `/decorate/edit?type=${formData.type}&title=${formData.title}`
if (formData.template) url += `&template=${formData.template}`
router.push(url)
const query = { type: formData.type, title: formData.title }
router.push({
path: '/decorate/edit',
query
})
}
})
}
@ -189,15 +175,26 @@ const wapDomain = ref('')
const getDomain = async () => {
wapDomain.value = (await getUrl()).data.wap_url
}
getDomain()
//
//
getDiyTemplate({ mode: '' }).then(res => {
for (const key in res.data) {
pageType[key] = res.data[key]
}
})
const apps: any = reactive({}) //
getApps({}).then(res=>{
if(res.data){
for (const key in res.data) {
apps[key] = res.data[key];
}
}
});
const diyPageTableData: any = reactive({
page: 1,
limit: 10,
@ -207,7 +204,8 @@ const diyPageTableData: any = reactive({
searchParam: {
title: '',
type: '',
mode: ''
mode: '',
addon_name: ''
}
})
@ -242,6 +240,13 @@ const editEvent = (data: any) => {
window.open(url.href)
}
// 使
const setUse = (id: any) => {
setUseDiyPage({id}).then(() => {
loadDiyPageList()
})
}
//
const deleteEvent = (id: number) => {
ElMessageBox.confirm(t('diyPageDeleteTips'), t('warning'),
@ -265,7 +270,7 @@ const toPreview = (data: any) => {
query: {
page: data.type_page + '?id=' + data.id
}
})
});
window.open(url.href)
}
@ -283,6 +288,7 @@ const shareFormData = reactive({
url: ''
}
})
const shareDialogVisible = ref(false)
const shareFormRules = computed(() => {
return {}
@ -329,9 +335,5 @@ const resetForm = (formEl: FormInstance | undefined) => {
</script>
<style lang="scss">
.copy {
background: var(--el-color-primary) !important;
color: var(--el-color-white) !important;
}
</style>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,304 @@
<template>
<div class="flex flex-wrap mt-[20px] min-w-[1000px]" 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>
<div v-show="page.use_template.url" class="w-[320px] h-[550px] mx-auto">
<iframe :id="'previewIframe_' + key" v-show="page.loadingIframe" class="w-[320px] h-[550px] mx-auto" :src="page.use_template.wapPreview" frameborder="0"></iframe>
<div v-show="page.loadingDev" class="w-[320px] h-[550px] mx-auto bg-body pt-[20px] px-[20px]">
<div class="font-bold text-xl mb-[40px]">{{ t('developTitle') }}</div>
<div class="mb-[20px] flex flex-col">
<text class="mb-[10px]">{{ t('wapDomain') }}</text>
<el-input v-model="wapDomain" :placeholder="t('wapDomainPlaceholder')" clearable />
</div>
<div class="flex">
<el-button type="primary" @click="saveDomain()">{{ t('confirm') }}</el-button>
<el-button type="primary" @click="settingTips()" plain>{{ t('settingTips') }}</el-button>
</div>
</div>
</div>
<div v-show="!page.use_template.wapPreview" class="overflow-hidden w-[320px] h-[550px] mx-auto">
<img class="max-w-full" v-if="page.use_template.cover" :src="img(page.use_template.cover)" />
</div>
<div class="popup-wrap absolute inset-x-0 inset-y-0 select-none" :class="{ 'disabled': page.isDisabledPop }"></div>
</div>
<div class="w-[500px]">
<div class="flex flex-wrap">
<el-button type="primary" @click="toDecorate()" v-show="page.use_template.action == 'decorate'" class="mr-[12px]">{{ t('decorate') }}</el-button>
</div>
<div class="info-wrap">
<div class="mt-[20px] bg-[#F7F8FA] p-[20px] flex items-center justify-between">
<div>
<div class="font-bold">{{ t('H5') }}</div>
<el-form label-width="40px" class="mt-[5px]">
<el-form-item :label="t('link')" v-show="page.use_template.wapPreview" class="mb-[5px]">
<el-input readonly :value="page.use_template.wapPreview">
<template #append>
<el-button @click="copyEvent(page.use_template.wapPreview)" class="bg-primary copy">{{ t('copy') }}</el-button>
</template>
</el-input>
</el-form-item>
</el-form>
<div class="text-[#999] text-base">{{ t('scanQRCodeOnRight') }}</div>
</div>
<div class="text-center">
<el-image class="w-[100px] h-[100px] mb-[5px]" :src="wapImage" />
<div @click="toPreview()" class="text-primary text-base cursor-pointer">{{ t('preview') }}</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, watch } from 'vue'
import { t } from '@/lang'
import { img } from '@/utils/common'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { getDecoratePage, changeTemplate } from '@/app/api/diy'
import storage from '@/utils/storage'
import QRCode from 'qrcode'
import { useClipboard } from '@vueuse/core'
const type: any = ref('DIY_MEMBER_INDEX');
const page: any = reactive({})
const router = useRouter()
const wapDomain = ref('')
const wapImage = ref('')
const link = ref({
name: ''
})
//
const formData = reactive({
type: '',
name: '',
parent:'',
page: '',
title: '',
action: ''
})
//
const refreshData = () => {
getDecoratePage({
type : type.value
}).then(res => {
for (const key in res.data) {
page[key] = res.data[key]
}
link.value.name = page.use_template.name;
link.value.title = page.use_template.title;
link.value.page = page.use_template.page;
link.value.action = page.use_template.action;
link.value.parent = page.use_template.parent;
if (page.use_template.url) {
page.loadingIframe = false // iframe
page.loadingDev = false //
page.isDisabledPop = false //
page.difference = 0 // 1000wap
wapDomain.value = page.domain_url.wap_domain
page.wapUrl = page.domain_url.wap_url
if (import.meta.env.MODE == 'development') {
// wap
if (wapDomain.value) {
page.wapUrl = wapDomain.value + '/wap'
setDomain()
}
if (storage.get('wap_domain')) {
page.wapUrl = storage.get('wap_domain')
setDomain()
}
}
setDomain()
}
})
}
refreshData()
// uni-app
window.addEventListener('message', (event) => {
try {
const data = JSON.parse(event.data)
if (['appOnLaunch', 'appOnReady'].indexOf(data.type) != -1) {
page.loadingDev = false //
page.loadingIframe = true // iframe
const loadTime = new Date().getTime()
page.difference = loadTime - page.timeIframe
page.isDisabledPop = false //
}
} catch (e) {
initLoad()
console.log('后台接受数据错误', e)
}
}, false)
// uniapp
const postMessage = () => {
const diyData = JSON.stringify({
type: 'appOnReady',
message: '加载完成'
})
if (window['previewIframe_' + type.value]) window['previewIframe_' + type.value].contentWindow.postMessage(diyData, '*')
}
//
const initLoad = () => {
page.loadingDev = true
page.isDisabledPop = true
page.loadingIframe = false
}
const saveDomain = () => {
if (wapDomain.value.trim().length == 0) {
ElMessage({
type: 'warning',
message: `${t('wapDomainPlaceholder')}`,
})
return
}
const wapUrl = wapDomain.value + '/wap'
storage.set({key: 'wap_domain', data: wapUrl})
if (page.use_template.url) {
page.wapUrl = wapUrl
setDomain()
}
setTimeout(() => {
if (page.use_template.url) {
page.loadingIframe = true // iframe
page.loadingDev = false //
page.isDisabledPop = false //
}
}, 100 * 3)
}
const settingTips = () => {
window.open('https://www.kancloud.cn/niucloud/niucloud-admin-develop/3213393')
}
const setDomain = () => {
page.use_template.wapPreview = page.wapUrl + page.use_template.url
QRCode.toDataURL(page.use_template.wapPreview, { errorCorrectionLevel: 'L', margin: 0, width: 100 }).then(url => {
wapImage.value = url
})
page.timeIframe = new Date().getTime()
postMessage()
setTimeout(() => {
if (page.difference == 0) initLoad()
}, 1000 * 2)
}
//
const toDecorate = () => {
const query: any = {
back: '/site/diy/member'
}
if (page.use_template.id) {
query.id = page.use_template.id
} else if (page.use_template.type) {
query.name = page.use_template.type
} else if (page.use_template.url) {
query.url = page.use_template.url
}
const url = router.resolve({
path: '/decorate/edit',
query
})
window.open(url.href)
}
//
const toPreview = () => {
let value = page.use_template.page
if (page.use_template.url) {
value = page.use_template.url
} else if (page.use_template.id) {
value += '?id=' + page.use_template.id
}
const url = router.resolve({
path: '/preview/wap',
query: {
page:value
}
})
window.open(url.href)
}
const isRepeat = ref(false)
const changePage = ()=>{
formData.type = type.value;
formData.name = link.value.name;
formData.page = link.value.url;
formData.title = link.value.title;
formData.action = link.value.action;
formData.parent = link.value.parent;
if (isRepeat.value) return
isRepeat.value = true
changeTemplate({
...formData
}).then((res) => {
isRepeat.value = false
refreshData()
})
}
//
const { copy, isSupported, copied } = useClipboard()
const copyEvent = (text: string) => {
if (!isSupported.value) {
ElMessage({
message: t('notSupportCopy'),
type: 'warning'
})
}
copy(text)
}
watch(copied, () => {
if (copied.value) {
ElMessage({
message: t('copySuccess'),
type: 'success'
})
}
})
</script>
<style lang="scss" scoped>
.page-item {
background-image: url(@/app/assets/images/iphone_bg.png);
background-color: var(--el-bg-color);
background-size: 100%;
.popup-wrap {
display: none;
}
&:hover {
.popup-wrap:not(.disabled) {
display: block !important;
}
}
}
</style>

View File

@ -1,204 +0,0 @@
<template>
<div class="main-container w-[375px] mx-auto my-[20px] relative">
<div class="flex h-full" v-show="loading">
<iframe v-show="loadingIframe" class="w-[375px] border border-slate-100 bg-gray-100" :src="wapPreview" frameborder="0" id="previewIframe" @load="loadIframe"></iframe>
<div v-show="loadingDev" class="w-[375px] border border-slate-100 bg-body pt-[20px] px-[20px]">
<div class="font-bold text-xl mb-[40px]">{{t('developTitle')}}</div>
<div class="mb-[20px] flex flex-col">
<text class="mb-[10px]">{{ t('wapDomain') }}</text>
<el-input v-model="wapDomain" :placeholder="t('wapDomainPlaceholder')" clearable/>
</div>
<el-button type="primary" @click="save">{{ t('confirm') }}</el-button>
</div>
<div class="w-[400px] absolute bg-body top-[10%] -right-[450px]" v-if="loadingIframe">
<div class="info-wrap mt-[20px]">
<div class="px-[20px] pb-[10px] font-bold">{{t('h5')}}</div>
<el-form label-width="40px" class="px-[20px]">
<el-form-item :label="t('link')" v-show="wapPreview">
<el-input readonly :value="wapPreview">
<template #append>
<el-button @click="copyEvent(wapPreview)" class="bg-primary copy">{{ t('copy') }}</el-button>
</template>
</el-input>
</el-form-item>
<el-form-item label=" " v-show="wapImage">
<el-image :src="wapImage"/>
</el-form-item>
</el-form>
<div class="px-[20px] pb-[10px] font-bold mt-[40px]">{{t('weapp')}}</div>
<el-form label-width="40px" class="px-[20px]">
<el-form-item label=" " v-if="weappConfig.qr_code">
<el-image class="w-[100px] h-[100px]" :src="img(weappConfig.qr_code)"/>
</el-form-item>
<el-form-item label=" " v-else>
<span class="text-gray-400">{{t('weappNotSet')}}</span>
</el-form-item>
</el-form>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue'
import { t } from '@/lang'
import { useRoute } from 'vue-router'
import { getWeappConfig } from '@/app/api/weapp'
import { getUrl } from '@/app/api/sys'
import { useClipboard } from '@vueuse/core'
import { ElMessage } from 'element-plus'
import { img } from '@/utils/common'
import QRCode from 'qrcode'
import storage from '@/utils/storage'
import { getPreviewData } from '@/app/api/diy'
const wapUrl = ref('')
const wapDomain = ref('')
const wapImage = ref('')
const wapPreview = ref('')
const loading = ref(false)
const loadingIframe = ref(false) // iframe
const loadingDev = ref(false) //
const timeFrame = ref(0)
let time = new Date().getTime()
const route = useRoute()
route.query.id = route.query.id || 0
route.query.name = route.query.name || ''
getUrl().then((res: any) => {
wapDomain.value = res.data.wap_domain
wapUrl.value = res.data.wap_url
setDomain()
//
if (import.meta.env.MODE == 'production') return
// envwap
if (wapDomain.value) return
let wap_domain_storage = storage.get('wap_domain')
if (wap_domain_storage) {
wapUrl.value = wap_domain_storage
setDomain()
return
}
timeFrame.value = new Date().getTime()
})
const save = () => {
if (wapDomain.value.trim().length == 0) {
ElMessage({
type: 'warning',
message: `${t('wapDomainPlaceholder')}`
})
return
}
wapUrl.value = wapDomain.value + '/wap'
setDomain()
storage.set({ key: 'wap_domain', data: wapUrl.value })
loadingIframe.value = true
loadingDev.value = false
}
const setDomain = () => {
getPreviewData({
id: route.query.id,
name: route.query.name
}).then((res: any) => {
const data = res.data
wapPreview.value = `${wapUrl.value}/${data.page}`
// id
if (import.meta.env.MODE == 'development') {
const siteId = storage.get('siteId') || 0
wapPreview.value += `&site_id=${siteId}`
}
QRCode.toDataURL(wapPreview.value, { errorCorrectionLevel: 'L', margin: 0, width: 100 }).then(url => {
wapImage.value = url
})
})
}
// iframe
const loadIframe = () => {
if (!wapPreview.value) return
const loadTime = new Date().getTime()
const difference = loadTime - timeFrame.value
// 1000wap
if (difference < 1000) {
loadingDev.value = true
loadingIframe.value = false
wapPreview.value = ''
wapImage.value = ''
} else {
loadingDev.value = false
loadingIframe.value = true
}
loading.value = true
}
const weappConfig = reactive({
qr_code: ''
})
const previewMode = ref('weapp')
//
getWeappConfig().then((res: any) => {
if (res.code == 1) {
const data = res.data
weappConfig.qr_code = data.qr_code
}
})
//
const { copy, isSupported, copied } = useClipboard()
const copyEvent = (text: string) => {
if (!isSupported.value) {
ElMessage({
message: t('notSupportCopy'),
type: 'warning'
})
}
copy(text)
}
watch(copied, () => {
if (copied.value) {
ElMessage({
message: t('copySuccess'),
type: 'success'
})
}
})
</script>
<style lang="scss">
body {
background: #edf0f3;
}
.copy {
background: var(--el-color-primary) !important;
color: var(--el-color-white) !important;
}
</style>
<style lang="scss" scoped>
</style>

View File

@ -264,10 +264,4 @@ const resetForm = (formEl: FormInstance | undefined) => {
}
</script>
<style lang="scss">
.copy {
background: var(--el-color-primary) !important;
color: var(--el-color-white) !important;
}
</style>
<style lang="scss" scoped></style>

View File

@ -25,7 +25,7 @@
<el-tabs v-model="activeName" class="demo-tabs mt-[15px]">
<el-tab-pane :label="t('navImage')" name="navPicture">
<div ref="navItemRef">
<div v-for="(item,index) in diyBottomData.list" :key="'a'+index" :data-id="index" class="item-wrap !cursor-move border-2 border-dashed pt-[18px] m-[10px] mb-[15px] relative list-item">
<div v-for="(item,index) in diyBottomData.list" :key="'a'+index" :data-id="index" class="item-wrap border-2 border-dashed pt-[18px] m-[10px] mb-[15px] relative list-item">
<el-form-item :label="t('navIconOne')">
<div class="flex align-center">
<div class="flex flex-col justify-center items-center">

View File

@ -54,7 +54,7 @@
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue'
import { ref, reactive, watch, inject } from 'vue'
import { t } from '@/lang'
import { useRoute } from 'vue-router'
import { getWeappConfig } from '@/app/api/weapp'
@ -78,6 +78,9 @@ const difference = ref(0) // 检测页面加载差异小于1000毫秒
const route = useRoute()
route.query.page = route.query.page || '' //
const setLayout = inject('setLayout')
setLayout('decorate')
getUrl().then((res: any) => {
wapUrl.value = res.data.wap_url
setDomain()
@ -118,7 +121,6 @@ const save = () => {
const setDomain = () => {
if (route.query.page) {
wapPreview.value = `${wapUrl.value}${route.query.page}`
console.log(wapPreview.value)
// errorCorrectionLevelLH()
QRCode.toDataURL(wapPreview.value, { errorCorrectionLevel: 'L', margin: 0, width: 100 }).then(url => {
wapImage.value = url

File diff suppressed because one or more lines are too long

View File

@ -29,8 +29,7 @@
<el-table v-if="localList[activeName].length" :data="info[activeName]" size="large" class="pt-[5px]">
<el-table-column :label="t('appName')" align="left" width="320">
<template #default="{ row }">
<div class="flex items-center" @click = "handleTips"
:class="{ 'cursor-pointer': row.type == 'app' && Object.keys(row.install_info).length }">
<div class="flex items-center cursor-pointer" @click = "handleTips">
<el-image class="w-[54px] h-[54px]" :src="row.icon" fit="contain">
<template #error>
<div class="flex items-center w-full h-full">

View File

@ -65,7 +65,7 @@
</div>
<img src="@/app/assets/images/tools/app_auth.png" class="w-[256px] h-[128px]" />
</div>
<div class="w-[256px] tools-item-shadow mb-[24px] mx-[14px] rounded-[8px] flex flex-col cursor-pointer leading-[1]" @click="toLink('/admin/tools/setting/admin')">
<div class="w-[256px] tools-item-shadow mb-[24px] mx-[14px] rounded-[8px] flex flex-col cursor-pointer leading-[1]" @click="toLink('/admin/tools/admin_menu')">
<div class="flex-1 pt-[18px] pb-[14px] px-[24px] flex flex-col">
<span class="text-[16px] text-[#222] font-bold">平台菜单</span>
<div class="text-[13px] text-[#6D7278] leading-[18px] mt-[8px] truncate">
@ -74,7 +74,7 @@
</div>
<img src="@/app/assets/images/tools/official_market.png" class="w-[256px] h-[128px]" />
</div>
<div class="w-[256px] tools-item-shadow mb-[24px] mx-[14px] rounded-[8px] flex flex-col cursor-pointer leading-[1]" @click="toLink('/admin/tools/setting/site')">
<div class="w-[256px] tools-item-shadow mb-[24px] mx-[14px] rounded-[8px] flex flex-col cursor-pointer leading-[1]" @click="toLink('/admin/tools/site_menu')">
<div class="flex-1 pt-[18px] pb-[14px] px-[24px] flex flex-col">
<span class="text-[16px] text-[#222] font-bold">站点菜单</span>
<div class="text-[13px] text-[#6D7278] leading-[18px] mt-[8px] truncate">

View File

@ -1,6 +1,6 @@
<template>
<el-container
:class="['w-full h-screen bg-page', { 'login-wrap': loginType == 'admin' }, { 'site-login-wrap': loginType == 'site' }]">
:class="['w-full h-screen bg-page flex flex-col', { 'login-wrap': loginType == 'admin' }, { 'site-login-wrap': loginType == 'site' }]">
<!-- 平台端登录 -->
<el-main class="login-main items-center justify-center" v-if="!imgLoading && loginType == 'admin'">
<div class="flex rounded-2xl overflow-hidden">
@ -86,6 +86,20 @@
</div>
</el-main>
<div class="flex items-center justify-center mt-[20px] text-[#999] text-sm pb-10 " v-if="copyright">
<a :href="copyright.gov_url" v-if="copyright.gov_record" class="flex" target="_blank">
<img src="@/app/assets/images/gov_icon.png" alt="" class="h-[20px] mr-1">
<span class="mr-3">公安备案号:{{ copyright.gov_record }}</span>
</a>
<a href="https://beian.miit.gov.cn/" target="_blank" v-if="copyright.icp">
<span class="mr-3">备案号:{{ copyright.icp }}</span>
</a>
<a :href="copyright.copyright_link" target="_blank">
<span class="mr-3" v-if="copyright.company_name">{{ copyright.company_name }}</span>
<span class="mr-3" v-if="copyright.copyright_desc">©{{ copyright.copyright_desc }}</span>
</a>
</div>
<!-- 验证组件 -->
<verify @success="success" :mode="pop" captchaType="blockPuzzle" :imgSize="{ width: '330px', height: '155px' }"
ref="verifyRef"></verify>
@ -102,13 +116,18 @@ import storage from '@/utils/storage'
import { getLoginConfig } from '@/app/api/auth'
import useUserStore from '@/stores/modules/user'
import { setWindowTitle, img, getAppType } from '@/utils/common'
import { getWebConfig } from '@/app/api/sys'
import { getWebConfig, getWebCopyright } from '@/app/api/sys'
const loading = ref(false)
const imgLoading = ref(false)
const userStore = useUserStore()
const route = useRoute()
const router = useRouter()
const copyright = ref(null)
getWebCopyright().then(({ data }) => {
copyright.value = data
})
route.redirectedFrom && (route.query.redirect = route.redirectedFrom.path)

View File

@ -124,13 +124,11 @@ checkPayConfigList()
//
const setConfigInfo = (data:any) => {
console.log(data)
payConfigData.value[data.channel].pay_type.forEach(element => {
if (element.key == data.type) {
element.config = data.config
}
})
console.log(payConfigData.value)
}
//

View File

@ -82,7 +82,6 @@ const weappTableData = reactive({
last_time: ''
}
})
// const searchFormRef = ref<FormInstance>()
/**
* 获取任务列表
@ -105,8 +104,6 @@ const loadWeappTemplateList = (page: number = 1) => {
}
loadWeappTemplateList()
// const router = useRouter()
const showDialog = ref(false)
const initialFormData = {
id: 0,
@ -119,10 +116,10 @@ const formData: Record<string, any> = reactive({ ...initialFormData })
const formRef = ref<FormInstance>()
const showEvent = () => {
formData.id = 0,
formData.desc = '',
formData.path = '',
formData.version = '',
formData.id = 0
formData.desc = ''
formData.path = ''
formData.version = ''
showDialog.value = true
}
@ -134,7 +131,7 @@ const formRules = computed(() => {
],
path: [
{ required: true, validator: validatePass, trigger: 'blur' }
],
]
}
})
@ -144,34 +141,34 @@ const validatePass = (rule: any, value: any, callback: any) => {
}
return callback()
}
const save_type = ref(false)
const saveType = ref(false)
const addEvent = async (formEl: FormInstance | undefined) => {
if (save_type.value || !formEl) return
if (saveType.value || !formEl) return
await formEl.validate(async (valid) => {
if (valid) {
save_type.value = true
saveType.value = true
const data = formData
const save = formData.id > 0 ? editVersion : addVersion
save(data).then(res => {
save_type.value = false
saveType.value = false
showDialog.value = false
loadWeappTemplateList()
}).catch(() => {
save_type.value = false
saveType.value = false
})
}
})
}
const editEvent = (item:any) => {
formData.id = item.id,
formData.desc = item.desc,
formData.id = item.id
formData.desc = item.desc
formData.path = item.path
formData.version = item.version
showDialog.value = true
}
const deleteEvent = (id: number) => {
const deleteEvent = (id: string) => {
ElMessageBox.confirm(t('weappVersionDeleteTips'), t('warning'),
{
confirmButtonText: t('confirm'),

View File

@ -30,7 +30,18 @@
<el-table-column prop="group_name" :label="t('groupName')" />
<el-table-column prop="group_desc" :label="t('remark')"></el-table-column>
<el-table-column prop="group_name" :label="t('appName')" :show-overflow-tooltip="true">
<template #default="{ row }">
<el-tag class="mr-1" size="small" v-for="name in row.app_name">{{ name }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="group_name" :label="t('addonName')" :show-overflow-tooltip="true">
<template #default="{ row }">
<el-tag class="mr-1" size="small" v-for="name in row.addon_name">{{ name }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="create_time" :label="t('createTime')"></el-table-column>
<el-table-column prop="group_roles" :label="t('operation')" align="right" fixed="right" width="130">
<template #default="{ row }">

View File

@ -29,7 +29,7 @@
<div class="w-full">
<div class="flex">
<div class="w-[60px] h-[60px] mr-[10px] rounded-md overflow-hidden">
<el-image :src="img(item.cover)" v-if="item.cover"
<el-image :src="img(item.icon)" v-if="item.icon"
class="w-full h-full" />
<el-image v-else class="w-full h-full">
<template #error>
@ -63,7 +63,7 @@
<div class="w-full">
<div class="flex">
<div class="w-[60px] h-[60px] mr-[10px] rounded-md overflow-hidden">
<el-image :src="img(item.cover)" v-if="item.cover"
<el-image :src="img(item.icon)" v-if="item.icon"
class="w-full h-full" />
<el-image v-else class="w-full h-full">
<template #error>
@ -125,8 +125,28 @@ const formData: Record<string, any> = ref({
addon: []
})
let installAddon = []
const getInstalledAddonListFn = async () => {
await getInstalledAddonList().then(({ data }) => {
const apps: any[] = []
const addons: any[] = []
Object.keys(data).forEach(key => {
installAddon.push(key)
const item = data[key]
item.type == 'addon' ? addons.push(item) : apps.push(item)
})
appList.value = apps
addonList.value = addons
}).catch()
}
getInstalledAddonListFn()
if (route.query.id) {
getSiteGroupInfo(route.query.id).then(({ data }) => {
data.app = data.app.filter((key: string) => installAddon.includes(key))
data.addon = data.addon.filter((key: string) => installAddon.includes(key))
formData.value = data
loading.value = false
}).catch()
@ -138,19 +158,6 @@ const back = () => {
router.push('/admin/site/group')
}
getInstalledAddonList().then(({ data }) => {
const apps: any[] = []
const addons: any[] = []
Object.keys(data).forEach(key => {
const item = data[key]
item.type == 'addon' ? addons.push(item) : apps.push(item)
})
appList.value = apps
addonList.value = addons
}).catch()
const formRef = ref<FormInstance>()
//

View File

@ -1,5 +1,5 @@
<template>
<div class="main-container">
<div class="main-container" v-loading="loading">
<div class="detail-head !mb-[10px]">
<div class="left" @click="router.push({ path: '/admin/site/list' })">
<span class="iconfont iconxiangzuojiantou !text-xs"></span>
@ -43,6 +43,54 @@
<el-form-item :label="t('expireTime')">
<div class="input-width">{{ formData.expire_time || '' }}</div>
</el-form-item>
<el-form-item :label="t('app')">
<div class="flex flex-wrap">
<template v-for="item in formData.site_addons">
<div class="flex w-[300px] border border-solid p-[10px] !mr-[10px] !mb-[10px] rounded-md" v-if="item.type == 'app'">
<div class="w-[60px] h-[60px] mr-[10px] rounded-md overflow-hidden">
<el-image :src="img(item.icon)" v-if="item.icon"
class="w-full h-full" />
<el-image v-else class="w-full h-full">
<template #error>
<div class="image-error">
<el-icon><icon-picture /></el-icon>
</div>
</template>
</el-image>
</div>
<div class="flex-1 w-0 flex flex-col justify-center leading-tight">
<div class="font-bold truncate">{{ item.title }}</div>
<div class="text-gray-400 mt-[10px] truncate" :title="item.desc">{{ item.desc }}</div>
</div>
</div>
</template>
</div>
</el-form-item>
<el-form-item :label="t('addon')">
<div class="flex flex-wrap">
<template v-for="item in formData.site_addons">
<div class="flex w-[300px] border border-solid p-[10px] !mr-[10px] !mb-[10px] rounded-md" v-if="item.type == 'addon'">
<div class="w-[60px] h-[60px] mr-[10px] rounded-md overflow-hidden">
<el-image :src="img(item.icon)" v-if="item.icon"
class="w-full h-full" />
<el-image v-else class="w-full h-full">
<template #error>
<div class="image-error">
<el-icon><icon-picture /></el-icon>
</div>
</template>
</el-image>
</div>
<div class="flex-1 w-0 flex flex-col justify-center leading-tight">
<div class="font-bold truncate">{{ item.title }}</div>
<div class="text-gray-400 mt-[10px] truncate" :title="item.desc">{{ item.desc }}</div>
</div>
</div>
</template>
</div>
</el-form-item>
</el-card>
</el-form>
@ -97,7 +145,8 @@ const initialFormData = {
phone: '',
group_name: '',
status: 0,
create_time: 0
create_time: 0,
site_addons: []
}
const formData: Record<string, any> = reactive({ ...initialFormData })

View File

@ -29,7 +29,7 @@
:placeholder="t('groupIdPlaceholder')" class="input-width">
<el-option :label="t('selectPlaceholder')" value="" />
<el-option :label="item['group_name']" :value="item['group_id']"
v-for="(item, index) in groupList[siteTableData.searchParam.app]" :key="index"/>
v-for="(item, index) in groupList.all" :key="index"/>
</el-select>
</el-form-item>

View File

@ -76,7 +76,7 @@
</template>
</el-table-column>
<el-table-column prop="expire_time" :label="t('expireTime')" />
<el-table-column :label="t('operation')" min-width="250" align="right" fixed="right">
<el-table-column :label="t('operation')" align="right" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="siteInfo(row)">{{ t('info') }}</el-button>
</template>

View File

@ -8,7 +8,7 @@
</el-radio-group>
</el-form-item>
<el-form-item :label="t('dictType')" v-if="formData.select_type == 1">
<el-form-item :label="t('dictType')" v-if="formData.select_type == 1" prop="dict_type">
<el-select class="input-width" :placeholder="t('dictTypePlaceholder')" v-model="formData.dict_type" filterable remote clearable>
<el-option :label="item.name" :value="item.key" v-for="item in dicList" :key="item.key" />
</el-select>
@ -121,7 +121,66 @@ const modelChange = (val:any) => {
const formRules = computed(() => {
return {
dict_type: [
{ required: true, message: t('dictTypePlaceholder'), trigger: 'change' }
{
validator: (rule: any, value: any, callback: any) => {
console.log(formData.value.select_type)
if (formData.value.select_type == 1 && formData.value.dict_type == '') {
callback(new Error(t('dictTypePlaceholder')))
}else{
callback()
}
},
trigger: 'blur'
}
],
addon: [
{
validator: (rule: any, value: any, callback: any) => {
if (formData.value.select_type == 2 && formData.value.addon == '') {
callback(new Error(t('addonsPlaceholder')))
}else{
callback()
}
},
trigger: 'blur'
}
],
model: [
{
validator: (rule: any, value: any, callback: any) => {
console.log(formData.value.model);
if (formData.value.select_type == 2 && formData.value.model == '') {
callback(new Error(t('associatedModelPlaceholder')))
}else{
callback()
}
},
trigger: 'blur'
}
],
value_key: [
{
validator: (rule: any, value: any, callback: any) => {
if (formData.value.select_type == 2 && formData.value.value_key == '') {
callback(new Error(t('remotePullDownValuePlaceholder')))
}else{
callback()
}
},
trigger: 'blur'
}
],
label_key: [
{
validator: (rule: any, value: any, callback: any) => {
if (formData.value.select_type == 2 && formData.value.label_key == '') {
callback(new Error(t('remotePullDownLabelPlaceholder')))
}else{
callback()
}
},
trigger: 'blur'
}
]
}

View File

@ -55,7 +55,7 @@
<el-table :data="formData.table_column" size="large" ref="tableRef" :key="toggleIndex">
<el-table-column align="center" label="操作" width="80">
<template #default>
<i class="iconfont icontuodong vues-rank cursor-move"></i>
<i class="iconfont icontuodong vues-rank"></i>
</template>
</el-table-column>
<el-table-column :label="t('columnName')" prop="column_name" min-width="130px" />

View File

@ -1,13 +1,13 @@
<template>
<div class="main-container attachment-container" v-if="systemService">
<el-card class="box-card !border-none" shadow="never">
<div class="main-container attachment-container min-h-[80vh]" v-loading="loading">
<el-card class="box-card !border-none" shadow="never" v-if="Object.keys(systemService).length">
<div class="flex justify-between items-center">
<span class="text-[20px]">{{ pageName }}</span>
</div>
<div class="bg-[#fff] pb-[20px] mb-3">
<p class="pt-[20px] pb-[10px] text-sm">{{ t('serverInformation') }}</p>
<div class="text-[14px]">
<el-table :data="systemService.server" size="large" v-loading="loadingArr.server_load">
<el-table :data="systemService.server" size="large">
<el-table-column prop="name" :label="t('environment')" align="left" min-width="200" />
<el-table-column prop="server" :label="t('version')" align="left" min-width="140" />
</el-table>
@ -16,7 +16,7 @@
<div class="bg-[#fff] pb-[20px] mb-3">
<p class="py-[20px] text-sm">{{ t('systemDemand') }}</p>
<div class="text-[14px]">
<el-table :data="systemService.server_version" size="large" v-loading="loadingArr.server_version_load">
<el-table :data="systemService.server_version" size="large">
<el-table-column prop="name" :label="t('environment')" align="left" min-width="200" />
<el-table-column prop="demand" :label="t('demand')" align="left" min-width="140" />
<el-table-column prop="server" :label="t('version')" align="left" min-width="140" />
@ -26,7 +26,7 @@
<div class="bg-[#fff] pb-[20px] mb-3">
<p class="py-[20px] text-sm">{{ t('authorityStatus') }}</p>
<div class="text-[14px]">
<el-table :data="systemService.system_variables" size="large" v-loading="loadingArr.system_variables_load">
<el-table :data="systemService.system_variables" size="large">
<el-table-column prop="name" :label="t('name')" align="left" min-width="200" />
<el-table-column prop="need" :label="t('demand')" align="left" min-width="140" />
<el-table-column :label="t('status')" align="left" min-width="140">
@ -46,7 +46,7 @@
<div class="bg-[#fff] pb-[20px] mb-3">
<p class="py-[20px] text-sm">{{ t('process') }}</p>
<div class="text-[14px]">
<el-table :data="systemService.process" size="large" v-loading="loadingArr.process_load">
<el-table :data="systemService.process" size="large">
<el-table-column prop="name" :label="t('name')" align="left" min-width="200" />
<el-table-column prop="need" :label="t('demand')" align="left" min-width="140" />
<el-table-column :label="t('status')" align="left" min-width="140">
@ -75,24 +75,12 @@ import { useRoute } from 'vue-router'
const route = useRoute()
const pageName = route.meta.title
const systemService = ref([])
const loadingArr = reactive({
server_load: true,
server_version_load: true,
system_variables_load: true,
process_load: true
})
const systemService = ref({})
let loading = ref(true);
const getSystemService = () => {
loadingArr.server_load = true
loadingArr.server_version_load = true
loadingArr.system_variables_load = true
loadingArr.process_load = true
getSystem().then(res => {
systemService.value = res.data
loadingArr.server_load = false
loadingArr.server_version_load = false
loadingArr.system_variables_load = false
loadingArr.process_load = false
loading.value = false
})
}
getSystemService()

View File

@ -1,13 +1,13 @@
<template>
<div class="main-container h-[500px] w-full p-5 bg-white" v-loading="loading">
<div class="flex flex-wrap px-2 plug-list pb-10">
<!-- <div class="flex items-center bg-[#F7F8FA] p-3 w-[295px] relative plug-item mr-4 mb-4 cursor-pointer">-->
<!-- <div class="flex flex-col ml-2">-->
<!-- <span class="text-sm truncate w-[190px]">{{t('refreshMenu')}}</span>-->
<!-- <span class="text-xs text-gray-400 mt-1 truncate w-[190px]" :title="t('refreshMenuDesc')">{{t('refreshMenuDesc')}}</span>-->
<!-- </div>-->
<!-- <span class="plug-item-operate" @click="refreshMenu()">{{t('refresh')}}</span>-->
<!-- </div>-->
<div class="flex items-center bg-[#F7F8FA] p-3 w-[295px] relative plug-item mr-4 mb-4 cursor-pointer">
<div class="flex flex-col ml-2">
<span class="text-sm truncate w-[190px]">{{t('refreshMenu')}}</span>
<span class="text-xs text-gray-400 mt-1 truncate w-[190px]" :title="t('refreshMenuDesc')">{{t('refreshMenuDesc')}}</span>
</div>
<span class="plug-item-operate" @click="refreshMenu()">{{t('refresh')}}</span>
</div>
<div class="flex items-center bg-[#F7F8FA] p-3 w-[295px] relative plug-item mr-4 mb-4 cursor-pointer">
<div class="flex flex-col ml-2">

View File

@ -32,14 +32,12 @@
<template v-if="parentLinkName == 'DIY_LINK'">
<div class="mb-[16px]">
<el-form-item :label="t('diyLinkName')">
<el-input v-model="selectLink.title" :placeholder="t('diyLinkNamePlaceholder')"
class="w-6/12" />
<el-input v-model="selectLink.title" :placeholder="t('diyLinkNamePlaceholder')" class="w-6/12" />
</el-form-item>
</div>
<div class="mb-[16px]">
<el-form-item :label="t('diyLinkUrl')">
<el-input v-model="selectLink.url" :placeholder="t('diyLinkUrlPlaceholder')"
class="w-6/12" />
<el-input v-model="selectLink.url" :placeholder="t('diyLinkUrlPlaceholder')" class="w-6/12" />
</el-form-item>
</div>
<el-form-item label=" ">
@ -80,6 +78,10 @@ const prop = defineProps({
modelValue: {
type: String,
default: ''
},
ignore:{
type:Array,
default:[]
}
})
@ -109,6 +111,11 @@ const show = () => {
if (value.value.name != '') {
selectLink.value = cloneDeep(value.value)
parentLinkName.value = selectLink.value.parent
for (let key in link.value){
if(link.value[key].name == parentLinkName.value){
changeParentLink(link.value[key]);
}
}
}
showDialog.value = true
}
@ -116,6 +123,17 @@ const show = () => {
getLink({}).then((res: any) => {
link.value = res.data
if(prop.ignore && prop.ignore.length){
for (let key in link.value){
for(let i=0;i<prop.ignore.length;i++){
if(key == prop.ignore[i]){
delete link.value[key];
break;
}
}
}
}
childList.value = Object.values(link.value)[0].child_list
if (value.value.name != '') {
selectLink.value = cloneDeep(value.value)
@ -149,16 +167,14 @@ const clear = () => {
}
const save = () => {
//
if (parentLinkName.value === 'DIY_LINK') {
selectLink.value.parent = parentLinkName.value
selectLink.value.name = parentLinkName.value
//
if (!selectLink.value.title) {
ElMessage({
message: t('diyLinkNameNotEmpty'),
type: 'warning'
})
});
return
}
@ -166,13 +182,24 @@ const save = () => {
ElMessage({
message: t('diyLinkUrlNotEmpty'),
type: 'warning'
})
});
return
}
selectLink.value.parent = parentLinkName.value
selectLink.value.name = parentLinkName.value
selectLink.value.page = selectLink.value.url;
selectLink.value.action = '';
}else if(parentLinkName.value == 'DIY_PAGE'){
//
selectLink.value.parent = parentLinkName.value
selectLink.value.action = 'decorate';
}
value.value = cloneDeep(selectLink.value)
showDialog.value = false
emit('success')
}
defineExpose({

View File

@ -15,7 +15,7 @@
<div class="content-box relative bg-cover bg-gray-100 border border-dashed border-gray-500"
:style="{ backgroundImage: 'url(' + img(value.imageUrl) + ')', width: contentBoxWidth + 'px', height: contentBoxHeight + 'px' }">
<div v-for="(item, index) in dragBoxArr" :id="'box_' + index" :key="index"
class="area-box cursor-move border border-solid border-[#ccc] w-[100px] h-[100px] absolute top-0 left-0 select-none p-[5px]"
class="area-box border border-solid border-[#ccc] w-[100px] h-[100px] absolute top-0 left-0 select-none p-[5px]"
:style="{ left: item.left + item.unit, top: item.top + item.unit, width: item.width + item.unit, height: item.height + item.unit }"
@mousedown="mouseDown($event, index)">
<span>{{ index + 1 }}</span>

View File

@ -12,7 +12,7 @@
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue'
import {computed, ref, watch} from 'vue'
import { t } from '@/lang'
import { ElMessage } from 'element-plus'
@ -21,7 +21,7 @@ const prop = defineProps({
type: String,
default: '350px'
},
value: {
modelValue: {
type: String,
default: ''
},
@ -39,14 +39,21 @@ const prop = defineProps({
}
})
const value = ref(prop.value)
const visible = ref(false)
watch(visible, () => {
if (!visible.value) {
value.value = ''
const value = computed({
get () {
return prop.modelValue
},
set (value) {
emit('update:modelValue', value)
}
})
const visible = ref(false)
// watch(visible, () => {
// if (!visible.value) {
// value.value = ''
// }
// })
const emit = defineEmits(['confirm'])

View File

@ -25,7 +25,7 @@
<el-dropdown-menu>
<el-dropdown-item class="text-center">
<popover-input :placeholder="t('upload.attachmentCategoryPlaceholder')"
@confirm="updateAttachmentCategory($event, index)" :value="item.name">
@confirm="updateAttachmentCategory($event, index)" v-model="item.name">
<span>{{ t('edit') }}</span>
</popover-input>
</el-dropdown-item>
@ -40,7 +40,7 @@
</el-scrollbar>
</div>
<!-- 添加分组 -->
<popover-input :placeholder="t('upload.attachmentCategoryPlaceholder')" @confirm="addAttachmentCategory"
<popover-input :placeholder="t('upload.attachmentCategoryPlaceholder')" @confirm="addAttachmentCategory" v-model="attachmentCategoryName"
v-if="prop.type != 'icon'">
<el-button>{{ t('upload.addAttachmentCategory') }}</el-button>
</popover-input>
@ -219,6 +219,7 @@ import { debounce, img, getToken } from '@/utils/common'
import { ElMessage, UploadFile, UploadFiles, ElMessageBox } from 'element-plus'
import storage from '@/utils/storage'
const attachmentCategoryName = ref('')
const operate = ref(false)
const prop = defineProps({
//
@ -333,6 +334,7 @@ const addAttachmentCategory = (name: string) => {
type: prop.type,
name
}).then(res => {
attachmentCategoryName.value = ''
getAttachmentCategoryList(1)
}).catch(() => {
@ -536,6 +538,7 @@ defineExpose({
<style lang="scss" scoped>
.group-list {
.group-item {
height: 32px;
margin-top: 3px;
.operate {

View File

@ -4,6 +4,16 @@ import Language from "./language"
import zhCn from "./zh-cn/common.json";
import en from "./en/common.json"
const addonZhCnCommon = import.meta.globEager('@/addon/**/lang/zh-cn/common.json')
const addonEnCommon = import.meta.globEager('@/addon/**/lang/en/common.json')
for (let key in addonZhCnCommon) {
Object.assign(zhCn, addonZhCnCommon[key].default)
}
for (let key in addonEnCommon) {
Object.assign(en, addonEnCommon[key].default)
}
//创建实例
let i18n = createI18n({
datetimeFormats: {},
@ -19,4 +29,4 @@ let i18n = createI18n({
const language = new Language(i18n);
export { language };
export default i18n;
export default i18n;

View File

@ -2,9 +2,10 @@
<el-container class="h-[60px] bg-[#2B303B] layout-admin flex items-center justify-between px-[15px] text-white" >
<!-- :class="['h-full px-[10px]',{'layout-header border-b border-color': !dark}]" -->
<div class="flex items-center text-[14px] leading-[1]">
<span class="iconfont icontuodong !text-[25px] cursor-pointer mr-[6px]" @click="toLink('/admin/index')"></span>
<span class="iconfont icontuodong !text-[25px] mr-[6px]"></span>
<span class="cursor-pointer" @click="toLink('/admin/index')">首页</span>
<span class="mx-2 text-[#4F5563] mx-[15px]">|</span>
<span class="cursor-pointer" @click="toLink('/admin/setting/website/system','setting_manage')">控制台</span>
<span class="cursor-pointer" @click="toLink('/admin/setting/website/system','setting_manage')">配置</span>
<span class="mx-2 text-[#4F5563] mx-[15px]">|</span>
<span class="cursor-pointer" @click="toLink('/admin/site/list','site_manage')">站点</span>
<template v-if="app_debug">

View File

@ -2,18 +2,12 @@
<template v-if="meta.show">
<el-sub-menu v-if="routes.children" :index="String(routes.name)">
<template #title>
<div v-if="meta.icon" class="w-[16px] h-[16px] relative flex items-center">
<icon v-if="meta.icon" :name="meta.icon" class="absolute !w-auto" />
</div>
<span :class="['ml-[10px]']">{{ meta.title }}</span>
</template>
<menu-item v-for="(route, index) in routes.children" :routes="route" :key="index" />
</el-sub-menu>
<template v-else>
<el-menu-item :index="String(routes.name)" @click="router.push({ name: routes.name })" v-if="meta.addon && meta.parent_route && meta.parent_route.addon == ''">
<div class="w-[16px] h-[16px] relative flex justify-center">
<el-image class="w-[16px] h-[16px] rounded-[50%] overflow-hidden" :src="img(addons[meta.addon].icon)" fit="fill"/>
</div>
<template #title>
<el-tooltip placement="right" effect="light">
<template #content>
@ -24,9 +18,6 @@
</template>
</el-menu-item>
<el-menu-item :index="String(routes.name)" @click="router.push({ name: routes.name })" v-else>
<div class="w-[16px] h-[16px] relative flex justify-center">
<icon :name="meta.icon" class="absolute top-[50%] -translate-y-[50%]" v-if="meta.icon"/>
</div>
<template #title>
<span :class="[{'text-[15px]': routes.meta.class == 1}, {'text-[14px]': routes.meta.class != 1}, {'ml-[10px]': routes.meta.class == 2, 'ml-[15px]': routes.meta.class == 3}]">{{ meta.title }}</span>
</template>

View File

@ -20,8 +20,8 @@
<icon :name="item.meta.icon" class="absolute top-[50%] -translate-y-[50%]" v-else />
</div>
<template #title>
<div class="relative">
<span :class="['ml-[10px]']">{{ item.meta.title }}</span>
<div class="relative flex-1 w-0">
<span class="ml-[10px] w-full truncate">{{ item.meta.short_title || item.meta.title }}</span>
</div>
</template>
</el-menu-item>
@ -32,7 +32,7 @@
</div>
<el-scrollbar v-if="twoMenuData.length" class="two-menu w-[140px]">
<div class="w-[140px] h-[64px] flex items-center justify-center text-[16px] border-0 border-b-[1px] border-solid border-[#eee]">{{ route.matched[1].meta.title }}</div>
<el-menu :default-active="route.name" :router="true" class="aside-menu" unique-opened="true" :collapse="systemStore.menuIsCollapse">
<el-menu :default-active="route.name" :router="true" class="aside-menu" :collapse="systemStore.menuIsCollapse">
<menu-item v-for="(route, index) in twoMenuData" :routes="route" :key="index" />
</el-menu>
<div class="h-[48px]"></div>
@ -68,7 +68,7 @@ routers.forEach(item => {
item.name = findFirstValidRoute(item.children)
}
oneMenuData.value.push(item)
} else if (item.meta.addon != '' && siteInfo?.apps.length <= 1 && siteInfo?.app[0] == item.meta.addon) {
} else if (item.meta.addon != '' && siteInfo?.apps.length <= 1 && siteInfo?.apps[0].key == item.meta.addon) {
if (item.children) {
item.children.forEach((citem: Record<string, any>) => {
citem.path = `${item.path}/${citem.path}`
@ -92,7 +92,7 @@ if (siteInfo?.apps.length > 1) {
routers.push({
path: addonRouters[item.key] ? addonRouters[item.key].path : '',
meta: {
icon: img(item.icon),
icon: addonRouters[item.key]?.meta.icon || 'element-Setting',
addon: item.key,
title: item.title,
app: item.app,
@ -116,7 +116,7 @@ watch(route, () => {
if (route.meta.addon == '') {
oneMenuActive.value = route.matched[1].path
twoMenuData.value = route.matched[1].children ?? []
} else if (route.meta.addon && route.meta.addon != siteInfo?.app[0]) {
} else if (route.meta.addon && route.meta.addon != siteInfo?.apps[0].key) {
oneMenuActive.value = '/site/app'
twoMenuData.value = route.matched[1].children ?? []
} else {

View File

@ -3,7 +3,7 @@
</template>
<script lang="ts" setup>
import { ref, markRaw, defineAsyncComponent } from 'vue'
import { ref, markRaw, defineAsyncComponent, provide } from 'vue'
import { getAppType } from '@/utils/common'
import useUserStore from '@/stores/modules/user'
@ -31,6 +31,17 @@ Object.keys(modules).forEach(key => {
})
!layout.value && (layout.value = markRaw(defineAsyncComponent(modules['./default/index.vue'])))
/**
* 监听某些页面需要独立配置布局
*/
provide('setLayout', (name: any) => {
if (siteLayout == name) return
siteLayout = name
Object.keys(modules).forEach(key => {
key.indexOf(name) !== -1 && (layout.value = markRaw(defineAsyncComponent(modules[key])))
})
})
</script>
<style lang="scss" scoped></style>

View File

@ -1,7 +1,7 @@
import { createRouter, createWebHistory, RouteLocationRaw, RouteLocationNormalizedLoaded } from 'vue-router'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { STATIC_ROUTES, NO_LOGIN_ROUTES, ROOT_ROUTER, ADMIN_ROUTE, HOME_ROUTE, SITE_ROUTE, DECORATE_ROUTER, findFirstValidRoute } from './routers'
import { STATIC_ROUTES, NO_LOGIN_ROUTES, ROOT_ROUTER, ADMIN_ROUTE, HOME_ROUTE, SITE_ROUTE, findFirstValidRoute } from './routers'
import { language } from '@/lang'
import useSystemStore from '@/stores/modules/system'
import useUserStore from '@/stores/modules/user'
@ -48,12 +48,17 @@ router.beforeEach(async (to, from, next) => {
const userStore = useUserStore()
const siteInfo = userStore.siteInfo
const appType = getAppType()
if (!siteInfo && getAppType() != 'home') {
let title: string = to.meta.title ?? ''
if (!siteInfo && appType != 'home') {
await userStore.getSiteInfo()
}
let title = (to.meta.title ? (to.meta.title + ' - ') : '') + (siteInfo ? siteInfo.site_name : '')
if (siteInfo) {
title += '-' + siteInfo.site_name
}
// 设置网站标题
setWindowTitle(title)
@ -66,7 +71,7 @@ router.beforeEach(async (to, from, next) => {
if (matched && matched.length && matched[0].path != '/:pathMatch(.*)*') {
matched = matched[0].path;
} else {
matched = getAppType();
matched = appType
}
const loginPath = to.path == '/' ? '/admin/login' : `${matched}/login`
@ -112,13 +117,6 @@ router.beforeEach(async (to, from, next) => {
// 添加动态路由
userStore.routers.forEach(route => {
// 页面装修
if (route.path == DECORATE_ROUTER.path) {
DECORATE_ROUTER.children = route.children
router.addRoute(DECORATE_ROUTER)
return
}
if (!route.children) {
if (route.meta.app == 'admin') {
router.addRoute(ADMIN_ROUTE.children[0].name, route)

View File

@ -135,7 +135,6 @@ export const SITE_ROUTE: RouteRecordRaw = {
]
}
// 装修路由
export const DECORATE_ROUTER: RouteRecordRaw = {
path: '/decorate',
component: Decorate,

View File

@ -182,9 +182,15 @@ 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;

View File

@ -1,6 +1,6 @@
import { defineStore } from 'pinia'
import { getToken, setToken, removeToken, getAppType } from '@/utils/common'
import { login, getAuthMenus, getSiteInfo } from '@/app/api/auth'
import { login, logout, getAuthMenus, getSiteInfo } from '@/app/api/auth'
import storage from '@/utils/storage'
import router from '@/router'
import { formatRouters, findFirstValidRoute } from '@/router/routers'
@ -58,12 +58,14 @@ const useSystemStore = defineStore('user', {
})
},
logout() {
if (!this.token) return
this.token = ''
this.userInfo = {}
this.siteInfo = {}
removeToken()
storage.remove(['userinfo', 'siteId', 'siteInfo'])
this.routers = []
logout()
// 清除tabbar
useTabbarStore().clearTab()
const login = getAppType() == 'admin' ? '/admin/login' : '/site/login'

View File

@ -172,7 +172,7 @@ select:-webkit-autofill {
// 温馨提示样式
.warm-prompt {
background-color: var(--el-color-primary-light-9) !important;
.el-icon, p {
.el-icon,p,li{
color: var(--el-color-primary-light-3);
}
.el-alert__content{
@ -185,7 +185,7 @@ html.dark {
.el-icon, p {
color: var(--el-color-primary-dark-2);
}
}
}
}
.app-item {
background: #f7f7f7;
@ -193,7 +193,7 @@ html.dark {
html.dark {
.app-item {
background: #191a23;
}
}
}
// 详情的头部
@ -245,15 +245,15 @@ html.dark {
-webkit-box-orient: vertical;
}
// 滚动条
.el-scrollbar__bar.is-vertical{
width: 10px !important;
}
.el-scrollbar__bar.is-vertical .el-scrollbar__thumb{
background-color: #8b8b8b !important;
opacity: 1 !important;
width: 9px !important;
}
.el-scrollbar__bar.is-vertical .el-scrollbar__thumb:hover{
background-color: #636363 !important;
opacity: 1 !important;
}
//.el-scrollbar__bar.is-vertical{
// width: 10px !important;
//}
//.el-scrollbar__bar.is-vertical .el-scrollbar__thumb{
// background-color: #8b8b8b !important;
// opacity: 1 !important;
// width: 9px !important;
//}
//.el-scrollbar__bar.is-vertical .el-scrollbar__thumb:hover{
// background-color: #636363 !important;
// opacity: 1 !important;
//}

View File

@ -28,7 +28,7 @@ class Request {
baseURL: import.meta.env.VITE_APP_BASE_URL.substr(-1) == '/' ? import.meta.env.VITE_APP_BASE_URL : `${import.meta.env.VITE_APP_BASE_URL}/`,
timeout: 30000,
headers: {
'Content-Type': 'application/x-www-form-urlencoded;',
'Content-Type': 'application/json',
'lang': storage.get('lang') ?? 'zh-cn'
}
});
@ -176,9 +176,6 @@ class Request {
case 401:
useUserStore().logout()
break;
case 400:
useUserStore().logout()
break;
}
}
}

View File

@ -102,7 +102,7 @@ class ExceptionHandle extends Handle
return fail($e->getMessage());
} else if($e instanceof UnexpectedValueException){
return fail($e->getMessage(), [], 401);
}else if($e instanceof AuthException || $e instanceof AdminException){
}else if($e instanceof AuthException){
return fail($e->getMessage(), [], $e->getCode() ?: 400);
}else if($e instanceof ServerException){
return fail($e->getMessage(), http_code:$e->getCode());

View File

@ -11,6 +11,7 @@
namespace app\adminapi\controller\diy;
use app\dict\diy\PagesDict;
use app\service\admin\diy\DiyService;
use core\base\BaseAdminController;
use Exception;
@ -34,9 +35,10 @@ class Diy extends BaseAdminController
public function lists()
{
$data = $this->request->params([
["title", ""],
["type", ""],
['mode', '']
[ "title", "" ],
[ "type", "" ],
[ 'mode', '' ],
[ 'addon_name', '' ]
]);
return success((new DiyService())->getPage($data));
}
@ -51,9 +53,9 @@ class Diy extends BaseAdminController
public function getList()
{
$data = $this->request->params([
["title", ""],
["type", ""],
['mode', '']
[ "title", "" ],
[ "type", "" ],
[ 'mode', '' ]
]);
return success((new DiyService())->getList($data));
}
@ -75,19 +77,19 @@ class Diy extends BaseAdminController
public function add()
{
$data = $this->request->params([
["title", ""],
["name", ""],
["type", ""],
['template', ''],
['mode', 'diy'], // 页面展示模式diy自定义fixed固定
["value", ""],
['is_default', 0],
['is_change', '']
[ "title", "" ],
[ "name", "" ],
[ "type", "" ],
[ 'template', '' ],
[ 'mode', 'diy' ], // 页面展示模式diy自定义fixed固定
[ "value", "" ],
[ 'is_default', 0 ],
[ 'is_change', '' ]
]);
$this->validate($data, 'app\validate\diy\Diy.add');
$id = (new DiyService())->add($data);
return success('ADD_SUCCESS', ['id' => $id]);
return success('ADD_SUCCESS', [ 'id' => $id ]);
}
/**
@ -98,10 +100,10 @@ class Diy extends BaseAdminController
public function edit($id)
{
$data = $this->request->params([
["title", ""],
["name", ""],
["value", ""],
['is_change', '']
[ "title", "" ],
[ "name", "" ],
[ "value", "" ],
[ 'is_change', '' ]
]);
$this->validate($data, 'app\validate\diy\Diy.edit');
(new DiyService())->edit($id, $data);
@ -138,11 +140,10 @@ class Diy extends BaseAdminController
public function getPageInit()
{
$params = $this->request->params([
['id', ""],
["name", ""],
["type", ""],
['template', ''],
["title", ""],
[ 'id', "" ],
[ "name", "" ],
[ "type", "" ],
[ "title", "" ],
]);
$diy_service = new DiyService();
@ -165,9 +166,9 @@ class Diy extends BaseAdminController
public function getTemplate()
{
$params = $this->request->params([
['type', ""], // 页面类型模板
['action', ''], // 页面是否装修标识为空标识不装修decorate装修
['mode', ''] // 页面展示模式diy自定义fixed固定
[ 'type', '' ], // 页面类型模板
[ 'action', '' ], // 页面是否装修标识为空标识不装修decorate装修
[ 'mode', '' ] // 页面展示模式diy自定义fixed固定
]);
$diy_service = new DiyService();
return success($diy_service->getTemplate($params));
@ -181,7 +182,7 @@ class Diy extends BaseAdminController
public function modifyShare(int $id)
{
$data = $this->request->params([
["share", ""],
[ "share", "" ],
]);
(new DiyService())->modifyShare($id, $data);
return success('MODIFY_SUCCESS');
@ -192,7 +193,10 @@ class Diy extends BaseAdminController
*/
public function getDecoratePage()
{
return success((new DiyService())->getDecoratePage());
$params = $this->request->params([
[ 'type', '' ],
]);
return success((new DiyService())->getDecoratePage($params));
}
/**
@ -201,28 +205,37 @@ class Diy extends BaseAdminController
public function changeTemplate()
{
$data = $this->request->params([
[ "id", "" ],
[ 'type', '' ], // 页面类型
[ 'name', '' ], // 页面名称标识
[ 'mode', '' ], // 页面展示模式diy自定义fixed固定other其他页面
[ 'template', '' ], // 模板名称
[ 'page', '' ], // 页面路由
[ 'title', '' ], // 页面标题
[ 'name', '' ], // 链接名称标识
[ 'parent', '' ], // 链接父级名称标识
[ 'page', '' ], // 链接路由
[ 'title', '' ], // 链接标题
[ 'action', '' ] // 是否存在操作decorate 表示支持装修
]);
return success(( new DiyService() )->changeTemplate($data));
(new DiyService())->changeTemplate($data);
return success('MODIFY_SUCCESS');
}
/**
* 获取页面预览数据
* 获取模板页面列表
* @return Response
*/
public function getPreviewData()
public function getTemplatePages()
{
$data = $this->request->params([
["id", ""],
['name', '']
$params = $this->request->params([
[ 'type', '' ],
[ 'mode', '' ]
]);
$res = (new DiyService())->getPreviewData($data);
return success($res);
$pages = PagesDict::getPages($params);
return success($pages);
}
/**
* 获取模板页面的应用插件列表
* @return Response
*/
public function getApps()
{
return success((new DiyService())->getApps());
}
}

View File

@ -30,7 +30,8 @@ class DiyRoute extends BaseAdminController
public function lists()
{
$data = $this->request->params([
["title", ""],
[ 'title', '' ],
[ 'addon_name', '' ]
]);
return success((new DiyRouteService())->getList($data));
}
@ -62,15 +63,15 @@ class DiyRoute extends BaseAdminController
public function add()
{
$data = $this->request->params([
["title", ""],
["name", ""],
["page", ""],
["share", ""],
["is_share", ""]
[ "title", "" ],
[ "name", "" ],
[ "page", "" ],
[ "share", "" ],
[ "is_share", "" ]
]);
$this->validate($data, 'app\validate\diy\DiyRoute.add');
$id = (new DiyRouteService())->add($data);
return success('ADD_SUCCESS', ['id' => $id]);
return success('ADD_SUCCESS', [ 'id' => $id ]);
}
/**
@ -81,11 +82,11 @@ class DiyRoute extends BaseAdminController
public function edit($id)
{
$data = $this->request->params([
["title", ""],
["name", ""],
["page", ""],
["share", ""],
["is_share", ""]
[ "title", "" ],
[ "name", "" ],
[ "page", "" ],
[ "share", "" ],
[ "is_share", "" ]
]);
$this->validate($data, 'app\validate\diy\DiyRoute.edit');
(new DiyRouteService())->edit($id, $data);
@ -109,12 +110,12 @@ class DiyRoute extends BaseAdminController
public function modifyShare()
{
$data = $this->request->params([
['share', ''],
['title', ''],
['name', ''],
['page', ''],
['is_share', 0],
['sort', 0]
[ 'share', '' ],
[ 'title', '' ],
[ 'name', '' ],
[ 'page', '' ],
[ 'is_share', 0 ],
[ 'sort', 0 ]
]);
(new DiyRouteService())->modifyShare($data);
return success('MODIFY_SUCCESS');

View File

@ -11,12 +11,15 @@
namespace app\adminapi\controller\login;
use addon\vipcard\app\service\core\CoreOrderRefundService;
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\menu\CoreMenuService;
use app\service\core\upload\CoreFileService;
use core\base\BaseAdminController;
use think\facade\Db;
use think\Response;
class Login extends BaseAdminController
@ -64,7 +67,9 @@ class Login extends BaseAdminController
}
public function test(){
(new CoreMenuService())->refreshAddonMenu('shop');
// (new CoreMenuService())->refreshAddonMenu('shop');
// (new CoreAddonDevelopBuildService())->build('shop');
// (new CoreFileService())->driver(0);
dd((new CoreOrderRefundService())->refundSuccess('20240118393625662873600'));
}
}

View File

@ -32,6 +32,7 @@ class Site extends BaseAdminController
['create_time', []],
['expire_time', []],
['app', ''],
// ['']
]);
return success((new SiteService())->getPage($data));
}

View File

@ -56,6 +56,9 @@ Route::group('diy', function() {
// 获取页面模板
Route::get('template', 'diy.Diy/getTemplate');
// 获取模板页面列表
Route::get('template/pages', 'diy.Diy/getTemplatePages');
// 自定义路由列表
Route::get('route', 'diy.DiyRoute/lists');
@ -68,6 +71,9 @@ Route::group('diy', function() {
// 编辑自定义页面分享内容
Route::put('diy/share', 'diy.Diy/modifyShare');
// 获取模板页面的应用插件列表
Route::get('apps', 'diy.Diy/getApps');
/***************************************************** 配置相关 *****************************************************/
// 底部导航查询

View File

@ -218,4 +218,6 @@ Route::group('sys', function () {
//系统环境(不效验登录状态)
Route::group('sys', function () {
Route::get('web/website', 'sys.Config/getWebsite');
// 获取版权信息
Route::get('web/copyright', 'sys.Config/getCopyright');
});

View File

@ -34,6 +34,22 @@ class ComponentDict
'support_page' => [], // 支持页面
'uses' => 0, // 最大添加数量
'sort' => 10001,
// 组件属性
'template' => [
"textColor" => "#303133", // 文字颜色
"pageBgColor" => "", // 底部背景颜色
"componentBgColor" => '', // 组件背景颜色
"topRounded" => 0, // 组件上圆角
"bottomRounded" => 0, // 组件下圆角
"elementBgColor" => '', // 元素背景颜色
"topElementRounded" => 0,// 元素上圆角
"bottomElementRounded" => 0, // 元素下圆角
"margin" => [
"top" => 0, // 上边距
"bottom" => 0, // 下边距
"both" => 0 // 左右边距
],
],
'value' => [
"style" => "style-1",
"styleName" => "风格1",
@ -60,15 +76,15 @@ class ComponentDict
"name" => ""
],
"color" => "#999999"
]
],
]
],
'ImageAds' => [
'title' => '图片广告',
'icon' => 'iconfont-icontupianguanggao1',
'path' => 'edit-image-ads', // 编辑组件属性
'support_page' => [], // 支持页面
'uses' => 0, // 最大添加数量
'path' => 'edit-image-ads',
'support_page' => [],
'uses' => 0,
'sort' => 10002,
'value' => [
"imageHeight" => 180,
@ -279,7 +295,7 @@ class ComponentDict
],
],
];
return ( new DictLoader("UniappComponent") )->load($system_components);
return (new DictLoader("UniappComponent"))->load($system_components);
}
}

View File

@ -15,7 +15,7 @@ use core\dict\DictLoader;
/**
* 页面数据
* Class TemplateDict
* Class PagesDict
* @package app\dict\diy
*/
class PagesDict

View File

@ -41,29 +41,45 @@ class TemplateDict
]
];
$pages = ( new DictLoader("UniappTemplate") )->load($system_pages);
if (!empty($params[ 'type' ])) {
$temp = [];
foreach ($params[ 'type' ] as $k => $v) {
if (!empty($pages[ $v ])) {
$temp[ $v ] = $pages[ $v ];
// 查询存在模板页面的应用插件列表
if (!empty($params[ 'query' ]) && $params[ 'query' ] == 'addon') {
$system = [
'app' => [
'title' => '系统',
'key' => 'app',
'list' => $system_pages
]
];
$addon = (new DictLoader("UniappTemplate"))->load($params);
$app = array_merge($system, $addon);
return $app;
} else {
// 查询应用插件下的模板页面数据
$pages = (new DictLoader("UniappTemplate"))->load($system_pages);
if (!empty($params[ 'type' ])) {
$temp = [];
foreach ($params[ 'type' ] as $k => $v) {
if (!empty($pages[ $v ])) {
$temp[ $v ] = $pages[ $v ];
}
}
return $temp;
}
return $temp;
}
if (!empty($params[ 'action' ])) {
$temp = [];
foreach ($pages as $k => $v) {
if (isset($v[ 'action' ]) && $params[ 'action' ] == $v[ 'action' ]) {
$temp[ $k ] = $v;
if (!empty($params[ 'action' ])) {
$temp = [];
foreach ($pages as $k => $v) {
if (isset($v[ 'action' ]) && $params[ 'action' ] == $v[ 'action' ]) {
$temp[ $k ] = $v;
}
}
return $temp;
}
return $temp;
}
return $pages;
return $pages;
}
}
}

View File

@ -144,7 +144,7 @@ return
'router_path' => 'user',
'view_path' => 'site/user',
'methods' => 'get',
'sort' => 100,
'sort' => 90,
'status' => 1,
'is_show' => 1,
],
@ -157,7 +157,7 @@ return
'router_path' => 'user_info',
'view_path' => 'site/user_info',
'methods' => 'get',
'sort' => 99,
'sort' => 80,
'status' => 1,
'is_show' => 0,
],
@ -188,7 +188,7 @@ return
'is_show' => 1,
],
[
'menu_name' => '站点套餐',
'menu_name' => '站点套餐编辑',
'menu_key' => 'site_group_edit',
'menu_type' => 1,
'icon' => 'element-PriceTag',
@ -564,153 +564,138 @@ return
'is_show' => 1,
],
[
'menu_name' => '系统菜单',
'menu_key' => 'system_setting',
'menu_type' => 0,
'icon' => 'element-Setting',
'api_url' => '',
'router_path' => 'setting',
'view_path' => '',
'methods' => '',
'menu_name' => '平台菜单',
'menu_key' => 'platform_menu',
'menu_type' => 1,
'icon' => '',
'api_url' => 'sys/menu',
'router_path' => 'admin_menu',
'view_path' => 'auth/menu',
'methods' => 'get',
'sort' => 49,
'status' => 1,
'is_show' => 1,
'children' => [
[
'menu_name' => '平台菜单',
'menu_key' => 'platform_menu',
'menu_type' => 1,
'menu_name' => '新增',
'menu_key' => 'auth_menu_add',
'menu_type' => 2,
'icon' => '',
'api_url' => 'sys/menu',
'router_path' => 'admin',
'view_path' => 'auth/menu',
'methods' => 'get',
'sort' => 100,
'router_path' => '',
'view_path' => '',
'methods' => 'post',
'sort' => 1,
'status' => 1,
'is_show' => 1,
'children' => [
[
'menu_name' => '新增',
'menu_key' => 'auth_menu_add',
'menu_type' => 2,
'icon' => '',
'api_url' => 'sys/menu',
'router_path' => '',
'view_path' => '',
'methods' => 'post',
'sort' => 1,
'status' => 1,
'is_show' => 1,
],
[
'menu_name' => '编辑',
'menu_key' => 'auth_menu_update',
'menu_type' => 2,
'icon' => '',
'api_url' => 'sys/menu/<menu_key>',
'router_path' => '',
'view_path' => '',
'methods' => 'put',
'sort' => 1,
'status' => 1,
'is_show' => 1,
],
[
'menu_name' => '删除',
'menu_key' => 'auth_menu_del',
'menu_type' => 2,
'icon' => '',
'api_url' => 'sys/menu',
'router_path' => '',
'view_path' => '',
'methods' => 'delete',
'sort' => 1,
'status' => 1,
'is_show' => 1,
],
[
'menu_name' => '详情',
'menu_key' => 'auth_menu_info',
'menu_type' => 2,
'icon' => '',
'api_url' => 'sys/menu/<menu_key>',
'router_path' => '',
'view_path' => '',
'methods' => 'get',
'sort' => 0,
'status' => 1,
'is_show' => 1,
],
],
],
[
'menu_name' => '站点菜单',
'menu_key' => 'site_menu',
'menu_type' => 1,
'menu_name' => '编辑',
'menu_key' => 'auth_menu_update',
'menu_type' => 2,
'icon' => '',
'api_url' => 'sys/menu',
'router_path' => 'site',
'view_path' => 'auth/site_menu',
'methods' => 'get',
'sort' => 90,
'api_url' => 'sys/menu/<menu_key>',
'router_path' => '',
'view_path' => '',
'methods' => 'put',
'sort' => 1,
'status' => 1,
'is_show' => 1,
'children' => [
[
'menu_name' => '新增',
'menu_key' => 'auth_site_menu_add',
'menu_type' => 2,
'icon' => '',
'api_url' => 'sys/menu',
'router_path' => '',
'view_path' => '',
'methods' => 'post',
'sort' => 1,
'status' => 1,
'is_show' => 1,
],
[
'menu_name' => '编辑',
'menu_key' => 'auth_site_menu_update',
'menu_type' => 2,
'icon' => '',
'api_url' => 'sys/menu/<menu_key>',
'router_path' => '',
'view_path' => '',
'methods' => 'put',
'sort' => 1,
'status' => 1,
'is_show' => 1,
],
[
'menu_name' => '删除',
'menu_key' => 'auth_site_menu_del',
'menu_type' => 2,
'icon' => '',
'api_url' => 'sys/menu',
'router_path' => '',
'view_path' => '',
'methods' => 'delete',
'sort' => 1,
'status' => 1,
'is_show' => 1,
],
[
'menu_name' => '详情',
'menu_key' => 'auth_site_menu_info',
'menu_type' => 2,
'icon' => '',
'api_url' => 'sys/menu/<menu_key>',
'router_path' => '',
'view_path' => '',
'methods' => 'get',
'sort' => 0,
'status' => 1,
'is_show' => 1,
],
],
]
]
],
[
'menu_name' => '删除',
'menu_key' => 'auth_menu_del',
'menu_type' => 2,
'icon' => '',
'api_url' => 'sys/menu',
'router_path' => '',
'view_path' => '',
'methods' => 'delete',
'sort' => 1,
'status' => 1,
'is_show' => 1,
],
[
'menu_name' => '详情',
'menu_key' => 'auth_menu_info',
'menu_type' => 2,
'icon' => '',
'api_url' => 'sys/menu/<menu_key>',
'router_path' => '',
'view_path' => '',
'methods' => 'get',
'sort' => 0,
'status' => 1,
'is_show' => 1,
],
],
],
[
'menu_name' => '站点菜单',
'menu_key' => 'site_menu',
'menu_type' => 1,
'icon' => '',
'api_url' => 'sys/menu',
'router_path' => 'site_menu',
'view_path' => 'auth/site_menu',
'methods' => 'get',
'sort' => 48,
'status' => 1,
'is_show' => 1,
'children' => [
[
'menu_name' => '新增',
'menu_key' => 'auth_site_menu_add',
'menu_type' => 2,
'icon' => '',
'api_url' => 'sys/menu',
'router_path' => '',
'view_path' => '',
'methods' => 'post',
'sort' => 1,
'status' => 1,
'is_show' => 1,
],
[
'menu_name' => '编辑',
'menu_key' => 'auth_site_menu_update',
'menu_type' => 2,
'icon' => '',
'api_url' => 'sys/menu/<menu_key>',
'router_path' => '',
'view_path' => '',
'methods' => 'put',
'sort' => 1,
'status' => 1,
'is_show' => 1,
],
[
'menu_name' => '删除',
'menu_key' => 'auth_site_menu_del',
'menu_type' => 2,
'icon' => '',
'api_url' => 'sys/menu',
'router_path' => '',
'view_path' => '',
'methods' => 'delete',
'sort' => 1,
'status' => 1,
'is_show' => 1,
],
[
'menu_name' => '详情',
'menu_key' => 'auth_site_menu_info',
'menu_type' => 2,
'icon' => '',
'api_url' => 'sys/menu/<menu_key>',
'router_path' => '',
'view_path' => '',
'methods' => 'get',
'sort' => 0,
'status' => 1,
'is_show' => 1,
],
],
],
[
'menu_name' => '计划任务',

View File

@ -16,8 +16,8 @@ return
'is_show' => 1,
'children' => [
[
'menu_name' => '面装修',
'menu_key' => 'diy_page_decorate',
'menu_name' => '启动页',
'menu_key' => 'diy_page_decorate_index',
'menu_type' => 1,
'icon' => 'element-House',
'api_url' => '',
@ -28,6 +28,19 @@ return
'status' => 1,
'is_show' => 1,
],
[
'menu_name' => '个人中心',
'menu_key' => 'diy_page_decorate_member_index',
'menu_type' => 1,
'icon' => 'iconfont-iconhuiyuanliebiao', // element-House
'api_url' => '',
'router_path' => 'member',
'view_path' => 'diy/member',
'methods' => '',
'sort' => 99,
'status' => 1,
'is_show' => 1,
],
[
'menu_name' => '保存',
'menu_key' => 'diy_page_update',
@ -852,7 +865,19 @@ return
'sort' => 100,
'status' => 1,
'is_show' => 1,
],
[
'menu_name' => '版权设置',
'menu_key' => 'setting_copyright',
'menu_type' => 1,
'icon' => 'iconfont-iconbanquan1',
'api_url' => 'sys/config/copyright',
'router_path' => 'copyright',
'view_path' => 'setting/copyright',
'methods' => 'get',
'sort' => 90,
'status' => 1,
'is_show' => 1,
],
[
'menu_name' => '协议管理',
@ -1146,7 +1171,20 @@ return
'sort' => 99,
'status' => 1,
'is_show' => 1,
]
],
[
'menu_name' => '存储设置',
'menu_key' => 'setting_storage',
'menu_type' => 1,
'icon' => 'element-FolderChecked',
'api_url' => 'sys/storage',
'router_path' => 'storage',
'view_path' => 'setting/storage',
'methods' => 'get',
'sort' => 30,
'status' => 1,
'is_show' => 1
],
]
]
];

View File

@ -11,6 +11,8 @@
namespace app\dict\sys;
use think\facade\Log;
/**
* 协议相关枚举类
* Class AgreementDict
@ -18,10 +20,6 @@ namespace app\dict\sys;
*/
class AgreementDict
{
//服务协议
const SERVICE = 'service';
//隐私协议
const PRIVACY = 'privacy';
/**
* 获取协议类型
@ -29,10 +27,18 @@ class AgreementDict
*/
public static function getType()
{
return [
self::SERVICE => get_lang('dict_agreement.service'),//服务协议,
self::PRIVACY => get_lang('dict_agreement.privacy'),//隐私协议
$data = [
'service' => get_lang('dict_agreement.service'),//服务协议,
'privacy' => get_lang('dict_agreement.privacy'),//隐私协议
];
$addon_data = event("AgreementType");
Log::write("检测数据");
Log::write($addon_data);
foreach ($addon_data as $k => $v)
{
$data = array_merge($data, $v);
}
return $data;
}
}

View File

@ -46,6 +46,8 @@ $system_event = [
'AppManage' => [
'app\listener\system\AppManageListener'
],
//协议类型加载
'AgreementType' => [],
//站点首页加载
'SiteIndex' => [
'app\listener\system\SiteIndexListener'

View File

@ -675,7 +675,7 @@ CREATE TABLE `sys_menu` (
`router_path` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '菜单路由地址 前端使用',
`view_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '菜单文件地址',
`methods` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '提交方式POST GET PUT DELETE',
`sort` tinyint(4) NOT NULL DEFAULT 1 COMMENT '排序',
`sort` int NOT NULL DEFAULT 1 COMMENT '排序',
`status` tinyint(3) UNSIGNED NOT NULL DEFAULT 1 COMMENT '正常,禁用(禁用后不允许访问)',
`is_show` tinyint(4) NOT NULL DEFAULT 1 COMMENT '是否显示',
`create_time` int(11) NOT NULL DEFAULT 0,
@ -732,8 +732,8 @@ CREATE TABLE `sys_notice_sms_log` (
`sms_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '发送关键字(注册、找回密码)',
`key` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '发送关键字(注册、找回密码)',
`template_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
`content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '发送内容',
`params` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '数据参数',
`content` text NOT NULL COMMENT '发送内容',
`params` text NOT NULL COMMENT '数据参数',
`status` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'sending' COMMENT '发送状态sending-发送中success-发送成功fail-发送失败',
`result` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '短信结果',
`create_time` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间',
@ -800,7 +800,7 @@ CREATE TABLE `sys_user_log` (
`uid` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '管理员id',
`username` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '管理员姓名',
`url` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '链接',
`params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '参数',
`params` longtext DEFAULT NULL COMMENT '参数',
`type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '请求方式',
`create_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '操作时间',
PRIMARY KEY (`id`) USING BTREE

View File

@ -52,6 +52,10 @@ return [
'UNIAPP_DIR_NOT_EXIST' => '未找到uni-app源码所在目录, <a style="text-decoration: underline;" href="https://www.kancloud.cn/niucloud/niucloud-admin-develop/3213544" target="blank">点击查看相关手册</a>',
'OPEN_BASEDIR_ERROR' => '请关闭防跨站攻击, 具体操作方法<a style="text-decoration: underline;" href="https://www.kancloud.cn/niucloud/niucloud-admin-develop/3213393" target="blank">点击查看相关手册</a>',
'ADDON_DOWNLOAD_VERSION_EMPTY' => '该插件还没有发布过版本',
'ADDON_IS_NOT_EXIST' => '插件不存在',
'ADDON_KEY_IS_EXIST' => '已存在相同插件标识的应用',
'ADDON_IS_INSTALLED_NOT_ALLOW_DEL' => '已安装的插件不允许删除',
'ADDON_ZIP_ERROR' => '插件压缩失败',
//登录注册重置账号....
'LOGIN_SUCCESS' => '登录成功',
@ -241,7 +245,6 @@ return [
// 授权相关
'AUTH_NOT_EXISTS' => '未获取到授权信息',
/********************************************************* home端专用 **************************************/
'USER_ROLE_NOT_HAS_SITE' => '当前登录用户下没有此项站点',

View File

@ -207,7 +207,7 @@ return [
'component_type_basic' => '基础组件',
'system_title' => '系统',
'system_link' => '系统页面',
'system_link' => '启动页',
'system_link_index' => '首页',
'member_link' => '会员页面',

View File

@ -41,6 +41,29 @@ class Diy extends BaseModel
*/
protected $name = 'diy_page';
/**
* 状态字段转化:所属插件名称
* @param $value
* @param $data
* @return string
*/
public function getAddonNameAttr($value, $data)
{
$addon_name = '';
$template = TemplateDict::getTemplate([
'query' => 'addon'
]);
foreach ($template as $k => $v) {
foreach ($v[ 'list' ] as $ck => $cv) {
if ($ck == $data[ 'type' ]) {
$addon_name = $v[ 'title' ];
break;
}
}
}
return $addon_name;
}
/**
* 状态字段转化
* @param $value
@ -108,6 +131,28 @@ class Diy extends BaseModel
return $data[ 'share' ] ?? '';
}
/**
* 搜索器:自定义页面标识
* @param $query
* @param $value
* @param $data
*/
public function searchAddonNameAttr($query, $value, $data)
{
if ($value) {
$list = [];
$template = TemplateDict::getTemplate([
'query' => 'addon'
]);
foreach ($template as $k => $v) {
if ($k == $value) {
$list = array_keys($v[ 'list' ]);
}
}
$query->where("type", 'in', $list);
}
}
/**
* 搜索器:自定义页面
* @param $query
@ -121,7 +166,6 @@ class Diy extends BaseModel
}
}
/**
* 搜索器:自定义页面名称
* @param $query

View File

@ -49,12 +49,26 @@ class SiteGroup extends BaseModel
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getGroupRolesDataAttr($value, $data)
public function getAppNameAttr($value, $data)
{
if (empty($data['addon']))
if (empty($data['app']))
return [];
return (new Addon())->where([['key', 'in', $data['addon']]])->select();
return (new Addon())->where([['key', 'in', $data['app']]])->column('title');
}
/**
* 关联插件
* @param $value
* @param $data
* @return array
*/
public function getAddonNameAttr($value, $data)
{
if (empty($data['app']))
return [];
return (new Addon())->where([['key', 'in', $data['addon']]])->column('title');
}
/**
* 关键字搜索
* @param $query

View File

@ -64,7 +64,7 @@ class LoginService extends BaseAdminService
$user_service = new UserService();
$userinfo = $user_service->getUserInfoByUsername($username);
if (empty($userinfo)) return false;
if ($userinfo->isEmpty()) return false;
if (!check_password($password, $userinfo->password)) return false;

View File

@ -43,12 +43,16 @@ class DiyRouteService extends BaseAdminService
if (!empty($v[ 'child_list' ])) {
foreach ($v[ 'child_list' ] as $ck => $cv) {
if (!empty($cv[ 'url' ])) {
if (empty($where[ 'title' ]) || ( !empty($where[ 'title' ]) && str_contains($cv['title'], $where['title']))) {
if (empty($where[ 'title' ]) || (!empty($where[ 'title' ]) && str_contains($cv[ 'title' ], $where[ 'title' ]))) {
$diy_route_list[] = [
'key' => $v[ 'key' ] ?? '',
'addon_title' => $v[ 'addon_title' ] ?? '',
'title' => $cv[ 'title' ],
'name' => $cv[ 'name' ],
'parent' => $k,
'page' => $cv[ 'url' ],
'is_share' => $cv[ 'is_share' ],
'action' => $cv[ 'action' ] ?? '',
'sort' => ++$sort
];
}
@ -149,4 +153,4 @@ class DiyRouteService extends BaseAdminService
return true;
}
}
}

View File

@ -17,6 +17,7 @@ use app\dict\diy\PagesDict;
use app\dict\diy\TemplateDict;
use app\model\diy\Diy;
use app\service\admin\sys\SystemService;
use app\service\core\addon\CoreAddonService;
use core\base\BaseAdminService;
use core\exception\AdminException;
use Exception;
@ -49,7 +50,7 @@ class DiyService extends BaseAdminService
$where[] = [ 'site_id', '=', $this->site_id ];
$field = 'id,site_id,title,name,template,type,mode,is_default,share,visit_count,create_time,update_time';
$order = "update_time desc";
$search_model = $this->model->where([ [ 'site_id', '=', $this->site_id ] ])->withSearch([ "title", "type", 'mode' ], $where)->field($field)->order($order)->append([ 'type_name' ]);
$search_model = $this->model->where([ [ 'site_id', '=', $this->site_id ] ])->withSearch([ "title", "type", 'mode', 'addon_name' ], $where)->field($field)->order($order)->append([ 'type_name', 'type_page', 'addon_name' ]);
return $this->pageQuery($search_model);
}
@ -161,7 +162,7 @@ class DiyService extends BaseAdminService
$this->model->where([ [ 'id', '=', $id ], [ 'site_id', '=', $this->site_id ] ])->update([ 'is_default' => 1, 'update_time' => time() ]);
Db::commit();
return true;
} catch ( Exception $e) {
} catch (Exception $e) {
Db::rollback();
throw new AdminException($e->getMessage());
}
@ -185,8 +186,35 @@ class DiyService extends BaseAdminService
$data = $this->getInfoByName($params[ 'name' ]);
}
if (!empty($params[ 'name' ])) {
// 查询启动页配置
$diy_config_service = new DiyConfigService();
$start_up_page = $diy_config_service->getStartUpPageConfig($params[ 'name' ]);
if (!empty($start_up_page)) {
if ($start_up_page[ 'parent' ] == 'DIY_PAGE') {
$id = str_replace('/app/pages/index/diy?id=', '', $start_up_page[ 'page' ]);
$data = $this->getInfo($id);
if (!empty($data)) {
$params[ 'name' ] = $data[ 'name' ];
$params[ 'type' ] = $data[ 'type' ];
}
} else {
foreach ($template as $k => $v) {
if ($start_up_page[ 'page' ] == $v[ 'page' ]) {
$data = $this->getInfoByName($k);
$params[ 'name' ] = $k;
$params[ 'type' ] = $k;
break;
}
}
}
}
}
if (!empty($data)) {
// 编辑赋值
if (isset($template[ $data[ 'type' ] ])) {
$page = $template[ $data[ 'type' ] ];
$data[ 'type_name' ] = $page[ 'title' ];
@ -195,11 +223,11 @@ class DiyService extends BaseAdminService
} else {
// 新页面赋值
$title = $params[ 'title' ] ?: '页面' . $time;
$type = $params[ 'type' ] ?: 'DIY_PAGE';
$title = $params[ 'title' ] ? : '页面' . $time;
$type = $params[ 'type' ] ? : 'DIY_PAGE';
$name = $type == 'DIY_PAGE' ? 'DIY_PAGE_RANDOM_' . $time : $type;
$type_name = '';
$template_name = $params[ 'template' ] ?? ''; // 页面模板名称
$template_name = ''; // 页面模板名称
$page_route = ''; // 页面路径
$mode = 'diy'; // 页面模式diy自定义fixed固定
$value = '';
@ -233,14 +261,6 @@ class DiyService extends BaseAdminService
$is_default = 1;
}
if (!empty($params[ 'template' ])) {
$page_template = $page[ 'template' ][ $params[ 'template' ] ];
$mode = $page_template[ 'mode' ];
$page_data = $page_template[ 'data' ];
$page_data[ 'global' ][ 'title' ] = $title;
$value = json_encode($page_data, JSON_UNESCAPED_UNICODE);
}
}
$data = [
@ -256,8 +276,12 @@ class DiyService extends BaseAdminService
];
}
$data[ 'component' ] = $this->getComponentList($data[ 'type' ]);
$data[ 'domain_url' ] = ( new SystemService() )->getUrl();
$data[ 'domain_url' ] = (new SystemService())->getUrl();
// 查询已安装的有效的应用
$data[ 'addon_list' ] = (new CoreAddonService())->getInstallAddonList();
return $data;
}
@ -274,13 +298,13 @@ class DiyService extends BaseAdminService
$sort_arr = [];
foreach ($v[ 'list' ] as $ck => $cv) {
$support_page = $cv[ 'support_page' ];
if (!( count($support_page) == 0 || in_array($name, $support_page) )) {
if (!(count($support_page) == 0 || in_array($name, $support_page))) {
unset($data[ $k ][ 'list' ][ $ck ]);
continue;
}
$sort_arr [] = $cv[ 'sort' ];
unset($data[$k]['list'][$ck]['sort'], $data[$k]['list'][$ck]['support_page']);
unset($data[ $k ][ 'list' ][ $ck ][ 'sort' ], $data[ $k ][ 'list' ][ $ck ][ 'support_page' ]);
}
array_multisort($sort_arr, SORT_ASC, $data[ $k ][ 'list' ]); //排序,根据 sort 排序
}
@ -314,7 +338,7 @@ class DiyService extends BaseAdminService
$link[ $k ][ 'child_list' ][] = [
'name' => $cv[ 'name' ],
'title' => $cv[ 'title' ],
'url' => '/pages/index/diy?id=' . $cv[ 'id' ]
'url' => '/app/pages/index/diy?id=' . $cv[ 'id' ]
];
}
@ -368,7 +392,7 @@ class DiyService extends BaseAdminService
public function getPageData($type, $name)
{
$pages = PagesDict::getPages([ 'type' => $type ]);
return $pages[$name] ?? [];
return $pages[ $name ] ?? [];
}
/**
@ -391,269 +415,117 @@ class DiyService extends BaseAdminService
/**
* 获取页面装修列表
* @param $params
* @return array
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function getDecoratePage()
public function getDecoratePage($params)
{
// 查询可装修的页面
$template = $this->getTemplate([ 'action' => 'decorate', 'type' => [ 'DIY_INDEX', 'DIY_MEMBER_INDEX' ] ]);
$specific_page = array_column($template, 'page');
// 查询当前装修的页面信息
$template = $this->getTemplate([ 'action' => 'decorate', 'type' => [ $params[ 'type' ] ] ])[ $params[ 'type' ] ];
// 查询其他页面,排除特定页面
$other_page = ( new DiyRouteService() )->getList();
foreach ($other_page as $ck => $cv) {
if (in_array(substr($cv[ 'page' ], 1), $specific_page) || $cv[ 'is_share' ] == 0) {
unset($other_page[ $ck ]);
}
}
$other_page = array_values($other_page);
$template[ 'domain_url' ] = (new SystemService())->getUrl();
// 查询默认页面数据
$default_page_data = $this->getFirstPageData($params[ 'type' ]);
// 查询其他页面,排除特定页面(不能分享的页面)
$other_page = (new DiyRouteService())->getList();
$use_template = [
'type' => $params[ 'type' ], // 页面类型标识
'name' => '', // 链接标识
'parent' => '', // 链接标识
'title' => $default_page_data[ 'title' ], // 模板名称
'cover' => $default_page_data[ 'cover' ], // 封面图
'url' => '', // 自定义页面链接,实时预览效果
'page' => $template[ 'page' ], // 页面地址
'action' => $template[ 'action' ] // 是否存在操作decorate 表示支持装修
];
// 查询启动页配置
$diy_config_service = new DiyConfigService();
$start_up_page = $diy_config_service->getStartUpPageConfig($params[ 'type' ]);
// 遍历查询页面数据,使用了那套模板
foreach ($template as $k => $v) {
// 查询页面数据
$info = $this->getInfoByName($params[ 'type' ]);
$template[ $k ][ 'domain_url' ] = ( new SystemService() )->getUrl();
if (!empty($start_up_page)) {
$use_template[ 'title' ] = $start_up_page[ 'title' ] ?? '';
$use_template[ 'name' ] = $start_up_page[ 'name' ] ?? '';
$use_template[ 'page' ] = $start_up_page[ 'page' ] ?? '';
$use_template[ 'action' ] = $start_up_page[ 'action' ] ?? '';
$use_template[ 'url' ] = $use_template[ 'page' ];
$use_template[ 'parent' ] = $start_up_page[ 'parent' ] ?? '';
} elseif (!empty($info)) {
$use_template[ 'id' ] = $info[ 'id' ];
$use_template[ 'title' ] = $info[ 'title' ];
// 查询我的微页面
$template[ $k ][ 'my_page' ] = $this->getList([ 'type' => $k ], 'id,title,name,template,type,is_default,mode');
// 查询其他页面,排除特定页面
$template[ $k ][ 'other_page' ] = $other_page;
// 查询默认页面数据
$default_page_data = $this->getFirstPageData($k);
$use_template = [
'id' => 0,
'name' => $k,
'title' => $default_page_data[ 'title' ], // 模板名称
'template' => $default_page_data[ 'template' ], // 模板标识
'cover' => $default_page_data[ 'cover' ], // 封面图
'preview' => $default_page_data[ 'preview' ], // 预览图
'desc' => $default_page_data[ 'desc' ], // 模板描述
'mode' => $default_page_data[ 'mode' ], // 页面模式diy自定义fixed固定
'hope' => 'template', // 默认选中 模板
'url' => '', // 自定义页面链接,实时预览效果
'page' => $v[ 'page' ], // 页面地址
'action' => '' // 是否存在操作decorate 表示支持装修
];
// 查询启动页配置
$start_up_page = $diy_config_service->getStartUpPageConfig($k);
if (!empty($start_up_page)) {
$use_template[ 'name' ] = $k;
if ($start_up_page[ 'mode' ] == 'other') {
if (empty($start_up_page[ 'name' ])) {
// 查询页面的名称标识
foreach ($other_page as $ck => $cv) {
if ($cv[ 'page' ] == $start_up_page[ 'page' ]) {
$use_template[ 'name' ] = $cv['name'];
break;
}
}
}
}
$use_template[ 'hope' ] = $start_up_page[ 'mode' ];
$use_template[ 'mode' ] = $start_up_page[ 'mode' ];
$use_template[ 'page' ] = $start_up_page[ 'page' ];
$use_template[ 'action' ] = $start_up_page[ 'action' ] ?? '';
if ($use_template[ 'hope' ] == 'other') {
// 其他页面没有预览图
$use_template[ 'url' ] = $use_template[ 'page' ];
$use_template[ 'cover' ] = ''; // 默认图
$use_template[ 'template' ] = '';
$use_template[ 'desc' ] = '将 ' . $start_up_page[ 'title' ] . ' 设为首页';
// 查询链接的名称标识
foreach ($other_page as $ck => $cv) {
if ($cv[ 'page' ] == $use_template[ 'page' ]) {
$use_template[ 'name' ] = $cv[ 'name' ];
$use_template[ 'parent' ] = $cv[ 'parent' ];
break;
}
}
// 查询模板页面数据
$page_data = $this->getPageData($params[ 'type' ], $info[ 'template' ]);
if (!empty($page_data)) {
$use_template[ 'url' ] = $template[ 'page' ] . '?id=' . $info[ 'id' ];
// $use_template[ 'cover' ] = $page_data[ 'cover' ]; // 默认图
} else {
// 查询页面数据
$info = $this->getInfoByName($k);
if (!empty($info)) {
$use_template[ 'id' ] = $info[ 'id' ];
$use_template[ 'name' ] = $info[ 'name' ];
$use_template[ 'title' ] = $info[ 'title' ];
$use_template[ 'template' ] = $info[ 'template' ];
$use_template[ 'mode' ] = $info[ 'mode' ];
$use_template[ 'hope' ] = $info[ 'mode' ] == 'fixed' ? 'template' : $info[ 'mode' ];
$use_template[ 'preview' ] = ''; // 默认图
$use_template[ 'desc' ] = '通过自定义装修的页面';
// 查询模板页面数
$page_data = $this->getPageData($k, $use_template[ 'template' ]);
if (!empty($page_data)) {
if ($info[ 'is_change' ] == 1) {
// 修改过模板,预览实际内容
$use_template[ 'url' ] = $v[ 'page' ] . '?id=' . $info[ 'id' ];
} else {
$use_template[ 'cover' ] = $page_data[ 'cover' ]; // 默认图
$use_template[ 'desc' ] = $page_data[ 'desc' ];
if (empty($page_data[ 'cover' ])) {
$use_template[ 'url' ] = $v[ 'page' ] . '?id=' . $info[ 'id' ];
}
}
} else {
// 自定义页面,实时预览效果
$use_template[ 'url' ] = '/app/pages/index/diy?id=' . $info[ 'id' ];
// 清空模板信息
$use_template[ 'cover' ] = ''; // 默认图
$use_template[ 'template' ] = '';
$use_template[ 'mode' ] = 'diy';
$use_template[ 'hope' ] = $use_template[ 'mode' ];
}
}
// 自定义页面,实时预览效果
$use_template[ 'url' ] = '/app/pages/index/diy?id=' . $info[ 'id' ];
// 清空模板信息
$use_template[ 'cover' ] = ''; // 默认图
}
// 如果没有预览图,并且没有地址,则赋值
if (empty($use_template[ 'cover' ]) && empty($use_template[ 'url' ])) {
$use_template[ 'url' ] = $v[ 'page' ];
}
$template[ $k ][ 'use_template' ] = $use_template;
}
// 如果没有预览图,并且没有地址,则赋值默认页面地址
if (empty($use_template[ 'cover' ]) && empty($use_template[ 'url' ])) {
$use_template[ 'url' ] = $template[ 'page' ];
}
$template[ 'use_template' ] = $use_template;
return $template;
}
/**
* 设置启动页
* @param array $params
* @return \app\model\sys\SysConfig|bool|\think\Model
*/
public function changeTemplate(array $params = [])
{
$info = [];
if ($params[ 'mode' ] == 'diy') {
// 自定义页面
// 查询
if (!empty($params[ 'id' ])) {
// 使用了微页面
$info = $this->getInfo($params[ 'id' ]);
if (!empty($info)) {
// 状态 变为 使用中
$this->setUse($info[ 'id' ]);
}
} elseif ($params[ 'template' ]) {
// 查询表中未修改的模板数据
$field = 'id,title,type';
$condition = [
[ 'site_id', '=', $this->site_id ],
[ 'type', '=', $params[ 'type' ] ],
[ 'template', '=', $params[ 'template' ] ],
[ 'mode', '=', $params[ 'mode' ] ],
[ 'is_change', '=', 0 ]
];
$info = $this->model->field($field)->where($condition)->findOrEmpty()->toArray();
if (!empty($info)) {
// 状态 变为 使用中
$this->setUse($info[ 'id' ]);
} else {
// 新增 数据
// 查询模板信息
$page_data = $this->getPageData($params[ 'type' ], $params[ 'template' ]);
$data = [
'title' => $page_data[ 'title' ],
'name' => $params[ 'type' ],
'type' => $params[ 'type' ],
'value' => json_encode($page_data[ 'data' ], JSON_UNESCAPED_UNICODE),
'template' => $params[ 'template' ],
'mode' => $params[ 'mode' ]
];
$res = $this->add($data);
$this->setUse($res);
$info = $data;
}
}
$page_template = TemplateDict::getTemplate([ 'type' => [ $info[ 'type' ] ] ])[ $info[ 'type' ] ];
$info[ 'page' ] = $page_template[ 'page' ];
} elseif ($params[ 'mode' ] == 'fixed') {
// 固定模板
// 查询模板信息
$page_data = $this->getPageData($params[ 'type' ], $params[ 'template' ]);
// 检查表里是否存在数据
$field = 'id,title,type';
$condition = [
[ 'type', '=', $params[ 'type' ] ],
[ 'template', '=', $params[ 'template' ] ],
[ 'mode', '=', $params[ 'mode' ] ]
];
$info = $this->model->field($field)->where($condition)->findOrEmpty()->toArray();
if (!empty($info)) {
// 状态 变为 使用中
$this->setUse($info[ 'id' ]);
} else {
// 新增 数据
$data = [
'title' => $page_data[ 'title' ],
'name' => $params[ 'type' ],
'type' => $params[ 'type' ],
'value' => json_encode($page_data[ 'data' ], JSON_UNESCAPED_UNICODE),
'template' => $params[ 'template' ],
'mode' => $params[ 'mode' ]
];
$res = $this->add($data);
$this->setUse($res);
$info = $data;
}
$page_template = TemplateDict::getTemplate([ 'type' => [ $info[ 'type' ] ] ])[ $info[ 'type' ] ];
$info[ 'page' ] = $page_template[ 'page' ];
} else if ($params[ 'mode' ] == 'other') {
// 其他页面
$info[ 'title' ] = $params[ 'title' ];
$info[ 'page' ] = $params[ 'page' ];
}
// 设置启动页
$start_up_page_data = [
'type' => $params[ 'type' ], // 页面类型
'name' => $params[ 'name' ], // 页面名称标识
'mode' => $params[ 'mode' ], // 模式diy自定义页面fixed模板other其他页面
'title' => $info[ 'title' ],
'page' => $info[ 'page' ],
'action' => $params[ 'action' ]
'name' => $params[ 'name' ], // 链接名称标识
'parent' => $params[ 'parent' ], // 链接父级名称标识
'page' => $params[ 'page' ], // 链接路由
'title' => $params[ 'title' ], // 链接标题
'action' => $params[ 'action' ] // 是否存在操作decorate 表示支持装修
];
$diy_config_service = new DiyConfigService();
$diy_config_service->setStartUpPageConfig($start_up_page_data);
return $info;
}
/**
* 获取页面预览数据
* @param array $params
* @return array
*/
public function getPreviewData(array $params = [])
{
$info = [];
if (!empty($params[ 'id' ])) {
$info = $this->getInfo($params[ 'id' ]);
} elseif (!empty($params[ 'name' ])) {
$info = $this->getInfoByName($params[ 'name' ]);
}
$res = [
'page' => $this->getTemplate([ 'type' => 'DIY_PAGE' ])[ 'DIY_PAGE' ][ 'page' ]
];
if (!empty($info)) {
if ($info[ 'is_default' ] == 1) {
$template = $this->getTemplate([ 'type' => $info[ 'type' ] ])[ $info[ 'type' ] ];
$res[ 'page' ] = $template[ 'page' ] . '?id=' . $info[ 'id' ];
}
}
$res = $diy_config_service->setStartUpPageConfig($start_up_page_data);
return $res;
}
/**
* 获取模板页面的应用插件列表
* @return array
*/
public function getApps()
{
$page_template = TemplateDict::getTemplate([
'query' => 'addon'
]);
return $page_template;
}
}

View File

@ -326,11 +326,11 @@ class ServiceGenerator extends BaseGenerator
$search_field = implode(',', $search_field);
if(empty($with))
{
$content.= '$this->model->where([ [' ." 'site_id' ". ',"=", $this->site_id ] ])->withSearch(['."'$search_field'".'], $where)->field('.'$field'.')->order('.'$order'.')';
$content.= '$this->model->where([ [' ." 'site_id' ". ',"=", $this->site_id ] ])->withSearch(['."'$search_field'".'], $where)->field('.'$field'.')->order('.'$order'.');';
}else{
$with = implode(',', $with);
$content.= '$this->model->where([ [' ." 'site_id' ". ',"=", $this->site_id ] ])->withSearch(['."'$search_field'".'], $where)->with('."'$with'".')->field('.'$field'.')->order('.'$order'.')';
$content.= '$this->model->where([ [' ." 'site_id' ". ',"=", $this->site_id ] ])->withSearch(['."'$search_field'".'], $where)->with('."'$with'".')->field('.'$field'.')->order('.'$order'.');';
}
return $content;

View File

@ -3,7 +3,7 @@
<el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between items-center">
<span class="text-[20px]">{{pageName}}</span>
<span class="text-lg">{{pageName}}</span>
<el-button type="primary" @click="addEvent">
{{ t('add{UCASE_NAME}') }}
</el-button>

View File

@ -49,7 +49,7 @@ class SiteGroupService extends BaseAdminService
public function getPage(array $where = [])
{
$field = 'group_id, group_name, group_desc, app, addon, create_time, update_time';
$search_model = $this->model->withSearch(['keywords'],$where)->field($field)->order('create_time desc');
$search_model = $this->model->withSearch(['keywords'],$where)->field($field)->append(['app_name', 'addon_name'])->order('create_time desc');
$list = $this->pageQuery($search_model);
return $list;
}

View File

@ -83,7 +83,7 @@ class SiteService extends BaseAdminService
$info = $this->model->where([ [ 'site_id', '=', $site_id ] ])->with([ 'groupName' ])->field($field)->append([ 'status_name' ])->findOrEmpty()->toArray();
if (!empty($info)) {
$site_addons = (new CoreSiteService())->getAddonKeysBySiteId($site_id);
$info['site_addons'] = (new Addon())->where([ ['key', 'in', $site_addons]])->field('key,title,desc,icon')->select()->toArray();
$info['site_addons'] = (new Addon())->where([ ['key', 'in', $site_addons]])->field('key,title,desc,icon,type')->select()->toArray();
}
return $info;
}

View File

@ -39,7 +39,7 @@ class ConfigService extends BaseAdminService
*/
public function getCopyright()
{
return (new CoreSysConfigService())->getCopyright();
return (new CoreSysConfigService())->getCopyright($this->site_id);
}
/**
@ -59,7 +59,7 @@ class ConfigService extends BaseAdminService
'copyright_link' => $value['copyright_link'],
'copyright_desc' => $value['copyright_desc']
];
return $this->core_config_service->setConfig(0,'COPYRIGHT', $data);
return $this->core_config_service->setConfig($this->site_id,'COPYRIGHT', $data);
}
/**

View File

@ -141,10 +141,19 @@ class SystemService extends BaseAdminService
$secret = uniqid('', true);
$file = root_path('runtime') . $secret . '.job';
try {
CheckJob::invoke([ 'file' => $file ]);
CheckJob::dispatch([ 'file' => $file ]);
} catch ( Throwable $e) {
return false;
}
// $timeout = 0;
// while($timeout < 5){
// if (file_exists($file)) {
// @unlink($file);
// return true;
// }
// $timeout++;
// sleep(1);
// }
sleep(3);
if (file_exists($file)) {
@unlink($file);

View File

@ -52,7 +52,7 @@ class StorageConfigService extends BaseAdminService
$info = (new CoreConfigService())->getConfig($this->site_id, 'STORAGE');
if(empty($info))
{
$config_type = ['default' => ''];//初始化
$config_type = ['default' => StorageDict::LOCAL];//初始化
}else
$config_type = $info['value'];
@ -113,4 +113,4 @@ class StorageConfigService extends BaseAdminService
}
}

View File

@ -31,4 +31,13 @@ class DiyConfigService extends BaseApiService
return (new CoreDiyConfigService())->getBottomConfig($this->site_id);
}
/**
* 获取启动页配置
* @return array
*/
public function getStartUpPageConfig($type)
{
return (new CoreDiyConfigService())->getStartUpPageConfig($this->site_id, $type);
}
}

View File

@ -12,6 +12,7 @@
namespace app\service\api\diy;
use app\dict\diy\PagesDict;
use app\dict\diy\TemplateDict;
use app\model\diy\Diy;
use core\base\BaseApiService;
@ -36,40 +37,57 @@ class DiyService extends BaseApiService
*/
public function getInfo(array $params = [])
{
$condition = [
[ 'site_id', '=', $this->site_id ]
];
if (!empty($params[ 'id' ])) {
$condition[] = [ 'id', '=', $params[ 'id' ] ];
} elseif (!empty($params[ 'name' ])) {
$condition[] = [ 'name', '=', $params[ 'name' ] ];
$condition[] = [ 'is_default', '=', 1 ];
$start_up_page = [];
$page_template = [];
if (!empty($params[ 'name' ])) {
// 查询启动页
$diy_config_service = new DiyConfigService();
$start_up_page = $diy_config_service->getStartUpPageConfig($params[ 'name' ]);
$page_template = TemplateDict::getTemplate([ 'type' => [ $params[ 'name' ] ] ])[ $params[ 'name' ] ];
}
$field = 'id,site_id,title,name,type,template, mode,value,is_default,share,visit_count';
if (!empty($start_up_page) && !empty($page_template) && !empty($start_up_page[ 'page' ]) && $start_up_page[ 'page' ] != $page_template[ 'page' ]) {
$info = $start_up_page;
return $info;
} else {
$condition = [
[ 'site_id', '=', $this->site_id ]
];
if (!empty($params[ 'id' ])) {
$condition[] = [ 'id', '=', $params[ 'id' ] ];
} elseif (!empty($params[ 'name' ])) {
$condition[] = [ 'name', '=', $params[ 'name' ] ];
$condition[] = [ 'is_default', '=', 1 ];
}
$info = $this->model->field($field)->where($condition)->findOrEmpty()->toArray();
if (empty($info)) {
// 查询默认页面数据
if (!empty($params[ 'name' ])) {
$page_data = $this->getFirstPageData($params[ 'name' ]);
if (!empty($page_data)) {
$info = [
'site_id' => $this->site_id,
'title' => $page_data[ 'title' ],
'name' => $page_data[ 'type' ],
'type' => $page_data[ 'type' ],
'template' => $page_data[ 'template' ],
'mode' => $page_data[ 'mode' ],
'value' => json_encode($page_data[ 'data' ], JSON_UNESCAPED_UNICODE),
'is_default' => 1,
'share' => '',
'visit_count' => 0
];
$field = 'id,site_id,title,name,type,template, mode,value,is_default,share,visit_count';
$info = $this->model->field($field)->where($condition)->findOrEmpty()->toArray();
if (empty($info)) {
// 查询默认页面数据
if (!empty($params[ 'name' ])) {
$page_data = $this->getFirstPageData($params[ 'name' ]);
if (!empty($page_data)) {
$info = [
'site_id' => $this->site_id,
'title' => $page_data[ 'title' ],
'name' => $page_data[ 'type' ],
'type' => $page_data[ 'type' ],
'template' => $page_data[ 'template' ],
'mode' => $page_data[ 'mode' ],
'value' => json_encode($page_data[ 'data' ], JSON_UNESCAPED_UNICODE),
'is_default' => 1,
'share' => '',
'visit_count' => 0
];
}
}
}
return $info;
}
return $info;
}
/**

View File

@ -36,7 +36,7 @@ class ConfigService extends BaseApiService
*/
public function getCopyright()
{
return (new CoreSysConfigService())->getCopyright();
return (new CoreSysConfigService())->getCopyright($this->site_id);
}
/**

View File

@ -47,30 +47,20 @@ class CoreAddonDevelopBuildService extends BaseCoreService
{
$this->addon = $addon;
$this->addon_path = root_path() . 'addon' . DIRECTORY_SEPARATOR . $addon . DIRECTORY_SEPARATOR;
$this->build_path = runtime_path() . 'build' . DIRECTORY_SEPARATOR . $addon . DIRECTORY_SEPARATOR;
if (!is_dir($this->addon_path)) throw new AddonException('ADDON_IS_NOT_EXIST');//当前目录中不存在此项插件
if (is_dir($this->build_path)) {
del_target_dir($this->build_path, true);
} else {
dir_mkdir($this->build_path);
}
// 拷贝插件文件
dir_copy($this->addon_path, $this->build_path, exclude_dirs:['admin', 'uni-app', 'web']);
$this->admin();
$this->uniapp();
$this->buildUniappPagesJson();
$this->web();
$this->resource();
$this->menu('admin');
$this->menu('site');
$zip_file = runtime_path() . 'build' . DIRECTORY_SEPARATOR . $addon . '.zip';
$zip_file = runtime_path() . $addon . '.zip';
if (file_exists($zip_file)) unlink($zip_file);
(new CoreAddonDevelopDownloadService(''))->compressToZip($this->build_path, $zip_file);
// 删除打包文件
del_target_dir($this->build_path, true);
(new CoreAddonDevelopDownloadService(''))->compressToZip($this->addon_path, $zip_file);
return true;
}
@ -81,7 +71,7 @@ class CoreAddonDevelopBuildService extends BaseCoreService
* @return \think\response\File
*/
public function download(string $addon) {
$zip_file = runtime_path() . 'build' . DIRECTORY_SEPARATOR . $addon . '.zip';
$zip_file = runtime_path() . $addon . '.zip';
if (!file_exists($zip_file)) throw new AddonException('ADDON_ZIP_ERROR');//下载失败
$content = file_get_contents($zip_file);
@ -99,21 +89,19 @@ class CoreAddonDevelopBuildService extends BaseCoreService
*/
public function menu(string $app_type) {
$where = [ ['app_type', '=', $app_type], ['addon', '=', $this->addon] ];
$field = 'menu_name,menu_key,menu_type,icon,api_url,router_path,view_path,methods,sort,status,is_show,parent_key';
$field = 'menu_name,menu_key,parent_key,menu_type,icon,api_url,router_path,view_path,methods,sort,status,is_show';
$menu = (new SysMenu())->where($where)->field($field)->order('sort', 'desc')->select()->toArray();
if (!empty($menu)) {
$menu = (new MenuService())->menuToTree($menu, 'menu_key', 'parent_key', 'children');
(new SysMenu())->where($where)->update(['source' => MenuDict::SYSTEM]);
}
$build_dict = $this->build_path . 'app' . DIRECTORY_SEPARATOR . 'dict' . DIRECTORY_SEPARATOR . 'menu' . DIRECTORY_SEPARATOR . $app_type . '.php';
$addon_dict = $this->addon_path . 'app' . DIRECTORY_SEPARATOR . 'dict' . DIRECTORY_SEPARATOR . 'menu' . DIRECTORY_SEPARATOR . $app_type . '.php';
$content = '<?php' . PHP_EOL;
$content .= 'return [' . PHP_EOL;
$content .= $this->arrayFormat($menu);
$content .= '];';
file_put_contents($build_dict, $content);
file_put_contents($addon_dict, $content);
return true;
@ -126,6 +114,7 @@ class CoreAddonDevelopBuildService extends BaseCoreService
}
$content = '';
foreach ($array as $k => $v) {
if (in_array($k, ['status_name', 'menu_type_name'])) continue;
if (is_array($v)) {
$content .= $tab;
if (is_string($k)) {
@ -147,8 +136,12 @@ class CoreAddonDevelopBuildService extends BaseCoreService
public function admin()
{
$admin_path = $this->root_path . 'admin' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'addon' . DIRECTORY_SEPARATOR . $this->addon . DIRECTORY_SEPARATOR;
$build_admin_path = $this->build_path . 'admin' . DIRECTORY_SEPARATOR;
dir_copy($admin_path, $build_admin_path);
if (!is_dir($admin_path)) return true;
$addon_admin_path = $this->addon_path . 'admin' . DIRECTORY_SEPARATOR;
if (is_dir($addon_admin_path)) del_target_dir($addon_admin_path, true);
dir_copy($admin_path, $addon_admin_path);
return true;
}
@ -159,8 +152,37 @@ class CoreAddonDevelopBuildService extends BaseCoreService
public function uniapp()
{
$uniapp_path = $this->root_path . 'uni-app' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'addon' . DIRECTORY_SEPARATOR . $this->addon . DIRECTORY_SEPARATOR;
$build_uniapp_path = $this->build_path . 'uni-app' . DIRECTORY_SEPARATOR;
dir_copy($uniapp_path, $build_uniapp_path);
if (!is_dir($uniapp_path)) return true;
$addon_uniapp_path = $this->addon_path . 'uni-app' . DIRECTORY_SEPARATOR;
if (is_dir($addon_uniapp_path)) del_target_dir($addon_uniapp_path, true);
dir_copy($uniapp_path, $addon_uniapp_path);
return true;
}
public function buildUniappPagesJson() {
$pages_json = file_get_contents($this->root_path . 'uni-app' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'pages.json');
$code_begin = strtoupper($this->addon) . '_PAGE_BEGIN' . PHP_EOL;
$code_end = strtoupper($this->addon) . '_PAGE_END' . PHP_EOL;
if(strpos($pages_json, $code_begin) !== false && strpos($pages_json, $code_end) !== false)
{
$pattern = "/\/\/\s+{$code_begin}([\S\s]+)\/\/\s+{$code_end}?/";
preg_match($pattern, $pages_json, $match);
if (!empty($match)) {
$addon_pages = $match[1];
$content = '<?php' . PHP_EOL;
$content .= 'return [' . PHP_EOL . " 'pages' => <<<EOT" . PHP_EOL . ' // PAGE_BEGIN' . PHP_EOL;
$content .= $addon_pages;
$content .= '// PAGE_END' . PHP_EOL . 'EOT' . PHP_EOL . '];';
if (!is_dir($this->addon_path . 'package')) dir_mkdir($this->addon_path . 'package');
file_put_contents($this->addon_path . 'package' . DIRECTORY_SEPARATOR . 'uni-app-pages.php', $content);
}
}
return true;
}
@ -170,6 +192,36 @@ class CoreAddonDevelopBuildService extends BaseCoreService
*/
public function web()
{
$web_path = $this->root_path . 'web' . DIRECTORY_SEPARATOR . 'addon' . DIRECTORY_SEPARATOR . $this->addon . DIRECTORY_SEPARATOR;
if (!is_dir($web_path)) return true;
$addon_web_path = $this->addon_path . 'web' . DIRECTORY_SEPARATOR;
if (is_dir($addon_web_path)) del_target_dir($addon_web_path, true);
dir_copy($web_path, $addon_web_path);
$layout = $this->root_path . 'web' . DIRECTORY_SEPARATOR . 'layouts' . DIRECTORY_SEPARATOR . $this->addon;
if (is_dir($layout)) {
$layout_dir = $addon_web_path . 'layouts' . DIRECTORY_SEPARATOR . $this->addon;
if (is_dir($layout_dir)) del_target_dir($layout_dir, true);
else dir_mkdir($layout_dir);
dir_copy($layout, $layout_dir);
}
return true;
}
/**
* 打包资源文件
* @return true
*/
public function resource() {
$resource_path = public_path() . 'addon' . DIRECTORY_SEPARATOR . $this->addon . DIRECTORY_SEPARATOR;
if (!is_dir($resource_path)) return true;
$addon_resource_path = $this->addon_path . 'resource' . DIRECTORY_SEPARATOR;
if (is_dir($addon_resource_path)) del_target_dir($addon_resource_path, true);
dir_copy($resource_path, $addon_resource_path);
return true;
}
}

View File

@ -369,7 +369,7 @@ class CoreAddonDevelopService extends CoreAddonBaseService
if (empty($image)) return true;
if (check_file_is_remote($image)) {
try {
(new CoreFetchService())->setRootPath($dir)->setRename($name)->image($image, '', FileDict::LOCAL);
(new CoreFetchService())->setRootPath($dir)->setRename($name)->image($image, 0, FileDict::LOCAL);
} catch ( UploadFileException $e ) {
return true;
}

View File

@ -282,7 +282,7 @@ class CoreAddonInstallService extends CoreAddonBaseService
// 放入的文件
$to_admin_dir = $this->root_path . 'admin' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'addon' . DIRECTORY_SEPARATOR . $this->addon . DIRECTORY_SEPARATOR;
$to_web_dir = $this->root_path . 'web' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . $this->addon . DIRECTORY_SEPARATOR;
$to_web_dir = $this->root_path . 'web' . DIRECTORY_SEPARATOR . 'addon' . DIRECTORY_SEPARATOR . $this->addon . DIRECTORY_SEPARATOR;
$to_wap_dir = $this->root_path . 'uni-app' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'addon' . DIRECTORY_SEPARATOR . $this->addon . DIRECTORY_SEPARATOR;
$to_resource_dir = public_path() . 'addon' . DIRECTORY_SEPARATOR . $this->addon . DIRECTORY_SEPARATOR;
@ -295,6 +295,12 @@ class CoreAddonInstallService extends CoreAddonBaseService
// 安装电脑端
if (file_exists($from_web_dir)) {
// 安装布局文件
$layout = $from_web_dir . 'layouts';
if (is_dir($layout)) {
dir_copy($layout, $this->root_path . 'web' . DIRECTORY_SEPARATOR . 'layouts');
del_target_dir($layout, true);
}
dir_copy($from_web_dir, $to_web_dir, $this->files['web']);
}
@ -575,7 +581,8 @@ class CoreAddonInstallService extends CoreAddonBaseService
{
// 将要删除的根目录
$to_admin_dir = $this->root_path . 'admin' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'addon' . DIRECTORY_SEPARATOR . $this->addon . DIRECTORY_SEPARATOR;
$to_web_dir = $this->root_path . 'web' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . $this->addon . DIRECTORY_SEPARATOR;
$to_web_dir = $this->root_path . 'web' . DIRECTORY_SEPARATOR . 'addon' . DIRECTORY_SEPARATOR . $this->addon . DIRECTORY_SEPARATOR;
$to_web_layouts = $this->root_path . 'web' . DIRECTORY_SEPARATOR . 'layouts' . DIRECTORY_SEPARATOR . $this->addon . DIRECTORY_SEPARATOR;
$to_wap_dir = $this->root_path . 'uni-app' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'addon' . DIRECTORY_SEPARATOR . $this->addon . DIRECTORY_SEPARATOR;
$to_resource_dir = public_path() . 'addon' . DIRECTORY_SEPARATOR . $this->addon . DIRECTORY_SEPARATOR;
@ -586,6 +593,7 @@ class CoreAddonInstallService extends CoreAddonBaseService
// 卸载pc端
if (is_dir($to_web_dir)) del_target_dir($to_web_dir, true);
if (is_dir($to_web_layouts)) del_target_dir($to_web_layouts, true);
// 卸载手机端
if (is_dir($to_wap_dir)) del_target_dir($to_wap_dir, true);

View File

@ -33,7 +33,7 @@ trait WapTrait
$content = "<template>\n";
$content .= " <view class=\"diy-group\" id=\"componentList\">\n";
$content .= " <view v-for=\"(component, index) in data.value\" :key=\"component.id\"\n";
$content .= " @click=\"diyStore.changeCurrentIndex(index, component)\" class=\"draggable-element relative cursor-move\"\n";
$content .= " @click=\"diyStore.changeCurrentIndex(index, component)\" class=\"draggable-element relative\"\n";
$content .= " :class=\"{ selected: diyStore.currentIndex == index,decorate : diyStore.mode == 'decorate' }\" :style=\"component.pageStyle\">\n";
$root_path = $compile_path . str_replace('/', DIRECTORY_SEPARATOR, 'app/components/diy'); // 系统自定义组件根目录

View File

@ -1,6 +1,6 @@
export default [
{
path: "/{key}/hello_world/index",
component: () => import('~/{key}/pages/hello_world/index.vue')
component: () => import('~/addon/{key}/pages/hello_world/index.vue')
}
]
]

View File

@ -4,7 +4,7 @@
<script lang="ts" setup>
import { ref } from 'vue'
import { getHelloWorld } from '@/{key}/api/hello_world'
import { getHelloWorld } from '@/addon/{key}/api/hello_world'
const hello_world_text = ref('');
@ -12,4 +12,4 @@ getHelloWorld().then(res => {
hello_world_text.value = res.data;
})
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped></style>

View File

@ -31,9 +31,8 @@ class CoreDiyConfigService extends BaseCoreService
*/
public function getBottomConfig(int $site_id)
{
$info = ( new CoreConfigService() )->getConfig($site_id, ConfigKeyDict::DIY_BOTTOM)[ 'value' ] ?? [];
$info = (new CoreConfigService())->getConfig($site_id, ConfigKeyDict::DIY_BOTTOM)[ 'value' ] ?? [];
if (empty($info)) {
$info = [
'backgroundColor' => '#ffffff',
'textColor' => '#606266',
@ -76,7 +75,7 @@ class CoreDiyConfigService extends BaseCoreService
*/
public function setBottomConfig(int $site_id, array $data)
{
return ( new CoreConfigService() )->setConfig($site_id, ConfigKeyDict::DIY_BOTTOM, $data);
return (new CoreConfigService())->setConfig($site_id, ConfigKeyDict::DIY_BOTTOM, $data);
}
/**
@ -86,7 +85,7 @@ class CoreDiyConfigService extends BaseCoreService
*/
public function setStartUpPageConfig(int $site_id, array $data)
{
return ( new CoreConfigService() )->setConfig($site_id,'START_UP_PAGE_' . strtoupper($data[ 'type' ]), $data);
return (new CoreConfigService())->setConfig($site_id, 'START_UP_PAGE_' . strtoupper($data[ 'type' ]), $data);
}
/**
@ -96,7 +95,7 @@ class CoreDiyConfigService extends BaseCoreService
*/
public function getStartUpPageConfig(int $site_id, string $type)
{
$info = ( new CoreConfigService() )->getConfig($site_id, 'START_UP_PAGE_' . strtoupper($type))[ 'value' ] ?? [];
$info = (new CoreConfigService())->getConfig($site_id, 'START_UP_PAGE_' . strtoupper($type))[ 'value' ] ?? [];
if (!empty($info)) {
$info[ 'name' ] = isset($info[ 'name' ]) ? $info[ 'name' ] : '';
}

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