Merge branch 'dev' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 into dev
This commit is contained in:
commit
df1c565cd9
19
.env.base
19
.env.base
@ -1,19 +0,0 @@
|
|||||||
# 本地开发环境
|
|
||||||
NODE_ENV=development
|
|
||||||
|
|
||||||
VITE_DEV=true
|
|
||||||
|
|
||||||
# 请求路径
|
|
||||||
VITE_BASE_URL='http://127.0.0.1:48080'
|
|
||||||
|
|
||||||
# 上传路径
|
|
||||||
VITE_UPLOAD_URL='http://127.0.0.1:48080/admin-api/infra/file/upload'
|
|
||||||
|
|
||||||
# 接口前缀
|
|
||||||
VITE_API_BASEPATH=/dev-api
|
|
||||||
|
|
||||||
# 接口地址
|
|
||||||
VITE_API_URL=/admin-api
|
|
||||||
|
|
||||||
# 打包路径
|
|
||||||
VITE_BASE_PATH=/
|
|
24
.env.dev
24
.env.dev
@ -1,13 +1,13 @@
|
|||||||
# 开发环境
|
# 开发环境:本地只启动前端项目,依赖开发环境(后端、APP)
|
||||||
NODE_ENV=development
|
NODE_ENV=development
|
||||||
|
|
||||||
VITE_DEV=false
|
VITE_DEV=true
|
||||||
|
|
||||||
# 请求路径
|
# 请求路径
|
||||||
VITE_BASE_URL='http://localhost:48080'
|
VITE_BASE_URL='http://api-dashboard.yudao.iocoder.cn'
|
||||||
|
|
||||||
# 上传路径
|
# 上传路径
|
||||||
VITE_UPLOAD_URL='http://localhost:48080/admin-api/infra/file/upload'
|
VITE_UPLOAD_URL='http://api-dashboard.yudao.iocoder.cn/admin-api/infra/file/upload'
|
||||||
|
|
||||||
# 接口前缀
|
# 接口前缀
|
||||||
VITE_API_BASEPATH=/dev-api
|
VITE_API_BASEPATH=/dev-api
|
||||||
@ -15,17 +15,23 @@ VITE_API_BASEPATH=/dev-api
|
|||||||
# 接口地址
|
# 接口地址
|
||||||
VITE_API_URL=/admin-api
|
VITE_API_URL=/admin-api
|
||||||
|
|
||||||
# 打包路径
|
|
||||||
VITE_BASE_PATH=/
|
|
||||||
|
|
||||||
# 是否删除debugger
|
# 是否删除debugger
|
||||||
VITE_DROP_DEBUGGER=true
|
VITE_DROP_DEBUGGER=false
|
||||||
|
|
||||||
# 是否删除console.log
|
# 是否删除console.log
|
||||||
VITE_DROP_CONSOLE=false
|
VITE_DROP_CONSOLE=false
|
||||||
|
|
||||||
# 是否sourcemap
|
# 是否sourcemap
|
||||||
VITE_SOURCEMAP=false
|
VITE_SOURCEMAP=true
|
||||||
|
|
||||||
|
# 打包路径
|
||||||
|
VITE_BASE_PATH=/
|
||||||
|
|
||||||
# 输出路径
|
# 输出路径
|
||||||
VITE_OUT_DIR=dist
|
VITE_OUT_DIR=dist
|
||||||
|
|
||||||
|
# 商城H5会员端域名
|
||||||
|
VITE_MALL_H5_DOMAIN='http://mall.yudao.iocoder.cn'
|
||||||
|
|
||||||
|
# 验证码的开关
|
||||||
|
VITE_APP_CAPTCHA_ENABLE=false
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
# 本地开发环境
|
# 本地开发环境:本地启动所有项目(前端、后端、APP)时使用,不依赖外部环境
|
||||||
NODE_ENV=development
|
NODE_ENV=development
|
||||||
|
|
||||||
VITE_DEV=true
|
VITE_DEV=true
|
||||||
|
|
||||||
# 请求路径
|
# 请求路径
|
||||||
VITE_BASE_URL='http://api-dashboard.yudao.iocoder.cn'
|
VITE_BASE_URL='http://localhost:48080'
|
||||||
|
|
||||||
# 上传路径
|
# 上传路径
|
||||||
VITE_UPLOAD_URL='http://api-dashboard.yudao.iocoder.cn/admin-api/infra/file/upload'
|
VITE_UPLOAD_URL='http://localhost:48080/admin-api/infra/file/upload'
|
||||||
|
|
||||||
# 接口前缀
|
# 接口前缀
|
||||||
VITE_API_BASEPATH=/dev-api
|
VITE_API_BASEPATH=/dev-api
|
||||||
@ -15,12 +15,6 @@ VITE_API_BASEPATH=/dev-api
|
|||||||
# 接口地址
|
# 接口地址
|
||||||
VITE_API_URL=/admin-api
|
VITE_API_URL=/admin-api
|
||||||
|
|
||||||
# 打包路径
|
|
||||||
VITE_BASE_PATH=/
|
|
||||||
|
|
||||||
# 项目本地运行端口号, 与.vscode/launch.json配合
|
|
||||||
VITE_PORT=80
|
|
||||||
|
|
||||||
# 是否删除debugger
|
# 是否删除debugger
|
||||||
VITE_DROP_DEBUGGER=false
|
VITE_DROP_DEBUGGER=false
|
||||||
|
|
||||||
@ -28,7 +22,13 @@ VITE_DROP_DEBUGGER=false
|
|||||||
VITE_DROP_CONSOLE=false
|
VITE_DROP_CONSOLE=false
|
||||||
|
|
||||||
# 是否sourcemap
|
# 是否sourcemap
|
||||||
VITE_SOURCEMAP=true
|
VITE_SOURCEMAP=false
|
||||||
|
|
||||||
|
# 打包路径
|
||||||
|
VITE_BASE_PATH=/
|
||||||
|
|
||||||
|
# 商城H5会员端域名
|
||||||
|
VITE_MALL_H5_DOMAIN='http://localhost:3000'
|
||||||
|
|
||||||
# 验证码的开关
|
# 验证码的开关
|
||||||
VITE_APP_CAPTCHA_ENABLE=false
|
VITE_APP_CAPTCHA_ENABLE=false
|
@ -1,4 +1,4 @@
|
|||||||
# 生产环境
|
# 生产环境:只在打包时使用
|
||||||
NODE_ENV=production
|
NODE_ENV=production
|
||||||
|
|
||||||
VITE_DEV=false
|
VITE_DEV=false
|
||||||
@ -28,4 +28,7 @@ VITE_SOURCEMAP=false
|
|||||||
VITE_BASE_PATH=/
|
VITE_BASE_PATH=/
|
||||||
|
|
||||||
# 输出路径
|
# 输出路径
|
||||||
VITE_OUT_DIR=dist-pro
|
VITE_OUT_DIR=dist-prod
|
||||||
|
|
||||||
|
# 商城H5会员端域名
|
||||||
|
VITE_MALL_H5_DOMAIN='http://mall.yudao.iocoder.cn'
|
@ -1,4 +1,4 @@
|
|||||||
# 生产环境
|
# 预发布环境:只在打包时使用
|
||||||
NODE_ENV=production
|
NODE_ENV=production
|
||||||
|
|
||||||
VITE_DEV=false
|
VITE_DEV=false
|
||||||
@ -29,3 +29,6 @@ VITE_BASE_PATH='http://static-vue3.yudao.iocoder.cn/'
|
|||||||
|
|
||||||
# 输出路径
|
# 输出路径
|
||||||
VITE_OUT_DIR=dist-stage
|
VITE_OUT_DIR=dist-stage
|
||||||
|
|
||||||
|
# 商城H5会员端域名
|
||||||
|
VITE_MALL_H5_DOMAIN='http://mall.yudao.iocoder.cn'
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# 开发环境
|
# 测试环境:只在打包时使用
|
||||||
NODE_ENV=production
|
NODE_ENV=production
|
||||||
|
|
||||||
VITE_DEV=false
|
VITE_DEV=false
|
||||||
@ -28,4 +28,7 @@ VITE_SOURCEMAP=false
|
|||||||
VITE_BASE_PATH=/admin-ui-vue3/
|
VITE_BASE_PATH=/admin-ui-vue3/
|
||||||
|
|
||||||
# 输出路径
|
# 输出路径
|
||||||
VITE_OUT_DIR=dist-dev
|
VITE_OUT_DIR=dist-test
|
||||||
|
|
||||||
|
# 商城H5会员端域名
|
||||||
|
VITE_MALL_H5_DOMAIN='http://mall.yudao.iocoder.cn'
|
15
package.json
15
package.json
@ -6,18 +6,17 @@
|
|||||||
"private": false,
|
"private": false,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"i": "pnpm install",
|
"i": "pnpm install",
|
||||||
"dev": "vite --mode base",
|
"local-dev": "vite --mode local-dev",
|
||||||
"front": "vite --mode front",
|
"dev": "vite --mode dev",
|
||||||
"ts:check": "vue-tsc --noEmit",
|
"ts:check": "vue-tsc --noEmit",
|
||||||
"build:pro": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode pro",
|
"build:local-dev": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode local-dev",
|
||||||
"build:dev": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode dev",
|
"build:dev": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode dev",
|
||||||
"build:base": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode base",
|
"build:test": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode test",
|
||||||
"build:stage": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode stage",
|
"build:stage": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode stage",
|
||||||
"build:static": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode static",
|
"build:prod": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode prod",
|
||||||
"build:front": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode front",
|
|
||||||
"serve:pro": "vite preview --mode pro",
|
|
||||||
"serve:dev": "vite preview --mode dev",
|
"serve:dev": "vite preview --mode dev",
|
||||||
"preview": "pnpm build:base && vite preview",
|
"serve:prod": "vite preview --mode prod",
|
||||||
|
"preview": "pnpm build:local-dev && vite preview",
|
||||||
"clean": "npx rimraf node_modules",
|
"clean": "npx rimraf node_modules",
|
||||||
"clean:cache": "npx rimraf node_modules/.cache",
|
"clean:cache": "npx rimraf node_modules/.cache",
|
||||||
"lint:eslint": "eslint --fix --ext .js,.ts,.vue ./src",
|
"lint:eslint": "eslint --fix --ext .js,.ts,.vue ./src",
|
||||||
|
@ -29,10 +29,11 @@
|
|||||||
:key="appLinkIndex"
|
:key="appLinkIndex"
|
||||||
:content="appLink.path"
|
:content="appLink.path"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
|
:show-after="300"
|
||||||
>
|
>
|
||||||
<el-button
|
<el-button
|
||||||
class="m-b-8px m-r-8px m-l-0px!"
|
class="m-b-8px m-r-8px m-l-0px!"
|
||||||
:type="isSameLink(appLink.path, activeAppLink) ? 'primary' : 'default'"
|
:type="isSameLink(appLink.path, activeAppLink.path) ? 'primary' : 'default'"
|
||||||
@click="handleAppLinkSelected(appLink)"
|
@click="handleAppLinkSelected(appLink)"
|
||||||
>
|
>
|
||||||
{{ appLink.name }}
|
{{ appLink.name }}
|
||||||
@ -63,7 +64,7 @@
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { APP_LINK_GROUP_LIST, APP_LINK_TYPE_ENUM } from './data'
|
import { APP_LINK_GROUP_LIST, APP_LINK_TYPE_ENUM, AppLink } from './data'
|
||||||
import { ButtonInstance, ScrollbarInstance } from 'element-plus'
|
import { ButtonInstance, ScrollbarInstance } from 'element-plus'
|
||||||
import { split } from 'lodash-es'
|
import { split } from 'lodash-es'
|
||||||
import ProductCategorySelect from '@/views/mall/product/category/components/ProductCategorySelect.vue'
|
import ProductCategorySelect from '@/views/mall/product/category/components/ProductCategorySelect.vue'
|
||||||
@ -74,17 +75,23 @@ defineOptions({ name: 'AppLinkSelectDialog' })
|
|||||||
// 选中的分组,默认选中第一个
|
// 选中的分组,默认选中第一个
|
||||||
const activeGroup = ref(APP_LINK_GROUP_LIST[0].name)
|
const activeGroup = ref(APP_LINK_GROUP_LIST[0].name)
|
||||||
// 选中的 APP 链接
|
// 选中的 APP 链接
|
||||||
const activeAppLink = ref('')
|
const activeAppLink = ref({} as AppLink)
|
||||||
|
|
||||||
/** 打开弹窗 */
|
/** 打开弹窗 */
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
const open = (link: string) => {
|
const open = (link: string) => {
|
||||||
activeAppLink.value = link
|
activeAppLink.value.path = link
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
|
|
||||||
// 滚动到当前的链接
|
// 滚动到当前的链接
|
||||||
const group = APP_LINK_GROUP_LIST.find((group) =>
|
const group = APP_LINK_GROUP_LIST.find((group) =>
|
||||||
group.links.some((linkItem) => isSameLink(linkItem.path, link))
|
group.links.some((linkItem) => {
|
||||||
|
const sameLink = isSameLink(linkItem.path, link)
|
||||||
|
if (sameLink) {
|
||||||
|
activeAppLink.value = { ...linkItem, path: link }
|
||||||
|
}
|
||||||
|
return sameLink
|
||||||
|
})
|
||||||
)
|
)
|
||||||
if (group) {
|
if (group) {
|
||||||
// 使用 nextTick 的原因:可能 Dom 还没生成,导致滚动失败
|
// 使用 nextTick 的原因:可能 Dom 还没生成,导致滚动失败
|
||||||
@ -94,9 +101,9 @@ const open = (link: string) => {
|
|||||||
defineExpose({ open })
|
defineExpose({ open })
|
||||||
|
|
||||||
// 处理 APP 链接选中
|
// 处理 APP 链接选中
|
||||||
const handleAppLinkSelected = (appLink: any) => {
|
const handleAppLinkSelected = (appLink: AppLink) => {
|
||||||
if (!isSameLink(appLink.path, activeAppLink.value)) {
|
if (!isSameLink(appLink.path, activeAppLink.value.path)) {
|
||||||
activeAppLink.value = appLink.path
|
activeAppLink.value = appLink
|
||||||
}
|
}
|
||||||
switch (appLink.type) {
|
switch (appLink.type) {
|
||||||
case APP_LINK_TYPE_ENUM.PRODUCT_CATEGORY_LIST:
|
case APP_LINK_TYPE_ENUM.PRODUCT_CATEGORY_LIST:
|
||||||
@ -104,7 +111,7 @@ const handleAppLinkSelected = (appLink: any) => {
|
|||||||
detailSelectDialog.value.type = appLink.type
|
detailSelectDialog.value.type = appLink.type
|
||||||
// 返显
|
// 返显
|
||||||
detailSelectDialog.value.id =
|
detailSelectDialog.value.id =
|
||||||
getUrlNumberValue('id', 'http://127.0.0.1' + activeAppLink.value) || undefined
|
getUrlNumberValue('id', 'http://127.0.0.1' + activeAppLink.value.path) || undefined
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
@ -114,10 +121,12 @@ const handleAppLinkSelected = (appLink: any) => {
|
|||||||
// 处理绑定值更新
|
// 处理绑定值更新
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
change: [link: string]
|
change: [link: string]
|
||||||
|
appLinkChange: [appLink: AppLink]
|
||||||
}>()
|
}>()
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
dialogVisible.value = false
|
dialogVisible.value = false
|
||||||
emit('change', activeAppLink.value)
|
emit('change', activeAppLink.value.path)
|
||||||
|
emit('appLinkChange', activeAppLink.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分组标题引用列表
|
// 分组标题引用列表
|
||||||
@ -127,7 +136,7 @@ const groupTitleRefs = ref<HTMLInputElement[]>([])
|
|||||||
* @param scrollTop 滚动条的位置
|
* @param scrollTop 滚动条的位置
|
||||||
*/
|
*/
|
||||||
const handleScroll = ({ scrollTop }: { scrollTop: number }) => {
|
const handleScroll = ({ scrollTop }: { scrollTop: number }) => {
|
||||||
const titleEl = groupTitleRefs.value.find((titleEl) => {
|
const titleEl = groupTitleRefs.value.find((titleEl: HTMLInputElement) => {
|
||||||
// 获取标题的位置信息
|
// 获取标题的位置信息
|
||||||
const { offsetHeight, offsetTop } = titleEl
|
const { offsetHeight, offsetTop } = titleEl
|
||||||
// 判断标题是否在可视范围内
|
// 判断标题是否在可视范围内
|
||||||
@ -146,7 +155,7 @@ const linkScrollbar = ref<ScrollbarInstance>()
|
|||||||
// 处理分组选中
|
// 处理分组选中
|
||||||
const handleGroupSelected = (group: string) => {
|
const handleGroupSelected = (group: string) => {
|
||||||
activeGroup.value = group
|
activeGroup.value = group
|
||||||
const titleRef = groupTitleRefs.value.find((item) => item.textContent === group)
|
const titleRef = groupTitleRefs.value.find((item: HTMLInputElement) => item.textContent === group)
|
||||||
if (titleRef) {
|
if (titleRef) {
|
||||||
// 滚动分组标题
|
// 滚动分组标题
|
||||||
linkScrollbar.value?.setScrollTop(titleRef.offsetTop)
|
linkScrollbar.value?.setScrollTop(titleRef.offsetTop)
|
||||||
@ -160,8 +169,8 @@ const groupBtnRefs = ref<ButtonInstance[]>([])
|
|||||||
// 自动滚动分组按钮,确保分组按钮保持在可视区域内
|
// 自动滚动分组按钮,确保分组按钮保持在可视区域内
|
||||||
const scrollToGroupBtn = (group: string) => {
|
const scrollToGroupBtn = (group: string) => {
|
||||||
const groupBtn = groupBtnRefs.value
|
const groupBtn = groupBtnRefs.value
|
||||||
.map((btn) => btn['ref'])
|
.map((btn: ButtonInstance) => btn['ref'])
|
||||||
.find((ref) => ref.textContent === group)
|
.find((ref: Node) => ref.textContent === group)
|
||||||
if (groupBtn) {
|
if (groupBtn) {
|
||||||
groupScrollbar.value?.setScrollTop(groupBtn.offsetTop)
|
groupScrollbar.value?.setScrollTop(groupBtn.offsetTop)
|
||||||
}
|
}
|
||||||
@ -184,11 +193,11 @@ const detailSelectDialog = ref<{
|
|||||||
})
|
})
|
||||||
// 处理详情选择
|
// 处理详情选择
|
||||||
const handleProductCategorySelected = (id: number) => {
|
const handleProductCategorySelected = (id: number) => {
|
||||||
const url = new URL(activeAppLink.value, 'http://127.0.0.1')
|
const url = new URL(activeAppLink.value.path, 'http://127.0.0.1')
|
||||||
// 修改 id 参数
|
// 修改 id 参数
|
||||||
url.searchParams.set('id', `${id}`)
|
url.searchParams.set('id', `${id}`)
|
||||||
// 排除域名
|
// 排除域名
|
||||||
activeAppLink.value = `${url.pathname}${url.search}`
|
activeAppLink.value.path = `${url.pathname}${url.search}`
|
||||||
// 关闭对话框
|
// 关闭对话框
|
||||||
detailSelectDialog.value.visible = false
|
detailSelectDialog.value.visible = false
|
||||||
// 重置 id
|
// 重置 id
|
||||||
|
@ -1,3 +1,20 @@
|
|||||||
|
// APP 链接分组
|
||||||
|
export interface AppLinkGroup {
|
||||||
|
// 分组名称
|
||||||
|
name: string
|
||||||
|
// 链接列表
|
||||||
|
links: AppLink[]
|
||||||
|
}
|
||||||
|
// APP 链接
|
||||||
|
export interface AppLink {
|
||||||
|
// 链接名称
|
||||||
|
name: string
|
||||||
|
// 链接地址
|
||||||
|
path: string
|
||||||
|
// 链接的类型
|
||||||
|
type?: APP_LINK_TYPE_ENUM
|
||||||
|
}
|
||||||
|
|
||||||
// APP 链接类型(需要特殊处理,例如商品详情)
|
// APP 链接类型(需要特殊处理,例如商品详情)
|
||||||
export const enum APP_LINK_TYPE_ENUM {
|
export const enum APP_LINK_TYPE_ENUM {
|
||||||
// 拼团活动
|
// 拼团活动
|
||||||
@ -243,4 +260,4 @@ export const APP_LINK_GROUP_LIST = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
] as AppLinkGroup[]
|
||||||
|
@ -37,7 +37,7 @@ const emit = defineEmits<{
|
|||||||
'update:modelValue': [link: string]
|
'update:modelValue': [link: string]
|
||||||
}>()
|
}>()
|
||||||
watch(
|
watch(
|
||||||
() => appLink,
|
() => appLink.value,
|
||||||
() => emit('update:modelValue', appLink.value)
|
() => emit('update:modelValue', appLink.value)
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
@ -82,7 +82,9 @@ watch(
|
|||||||
|
|
||||||
// 克隆组件
|
// 克隆组件
|
||||||
const handleCloneComponent = (component: DiyComponent<any>) => {
|
const handleCloneComponent = (component: DiyComponent<any>) => {
|
||||||
return cloneDeep(component)
|
const instance = cloneDeep(component)
|
||||||
|
instance.uid = new Date().getTime()
|
||||||
|
return instance
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -39,87 +39,60 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-card>
|
</el-card>
|
||||||
<el-card header="内容设置" class="property-group" shadow="never">
|
<el-card header="内容设置" class="property-group" shadow="never">
|
||||||
<el-text type="info" size="small"> 拖动左上角的小圆点可对其排序 </el-text>
|
<Draggable v-model="formData.items" :empty-item="{ type: 'img' }">
|
||||||
<template v-if="formData.items[0]">
|
<template #default="{ element }">
|
||||||
<draggable
|
<el-form-item label="类型" prop="type" class="m-b-8px!" label-width="40px">
|
||||||
:list="formData.items"
|
<el-radio-group v-model="element.type">
|
||||||
:force-fallback="true"
|
<el-radio label="img">图片</el-radio>
|
||||||
:animation="200"
|
<el-radio label="video">视频</el-radio>
|
||||||
handle=".drag-icon"
|
</el-radio-group>
|
||||||
class="m-t-8px"
|
</el-form-item>
|
||||||
item-key="index"
|
<el-form-item
|
||||||
>
|
label="图片"
|
||||||
<template #item="{ element, index }">
|
class="m-b-8px!"
|
||||||
<div class="content mb-4px flex flex-col gap-4px rounded bg-gray-50 p-8px">
|
label-width="40px"
|
||||||
<div
|
v-if="element.type === 'img'"
|
||||||
class="m--8px m-b-8px flex flex-row items-center justify-between bg-gray-100 p-8px"
|
>
|
||||||
>
|
<UploadImg
|
||||||
<Icon icon="ic:round-drag-indicator" class="drag-icon cursor-move" />
|
v-model="element.imgUrl"
|
||||||
<Icon
|
draggable="false"
|
||||||
icon="ep:delete"
|
height="80px"
|
||||||
class="cursor-pointer text-red-5"
|
width="100%"
|
||||||
@click="handleDeleteImage(index)"
|
class="min-w-80px"
|
||||||
v-if="formData.items.length > 1"
|
/>
|
||||||
/>
|
</el-form-item>
|
||||||
</div>
|
<template v-else>
|
||||||
<el-form-item label="类型" prop="type" class="m-b-8px!" label-width="50px">
|
<el-form-item label="封面" class="m-b-8px!" label-width="40px">
|
||||||
<el-radio-group v-model="element.type">
|
<UploadImg
|
||||||
<el-radio label="img">图片</el-radio>
|
v-model="element.imgUrl"
|
||||||
<el-radio label="video">视频</el-radio>
|
draggable="false"
|
||||||
</el-radio-group>
|
height="80px"
|
||||||
</el-form-item>
|
width="100%"
|
||||||
<el-form-item
|
class="min-w-80px"
|
||||||
label="图片"
|
/>
|
||||||
class="m-b-8px!"
|
</el-form-item>
|
||||||
label-width="50px"
|
<el-form-item label="视频" class="m-b-8px!" label-width="40px">
|
||||||
v-if="element.type === 'img'"
|
<UploadFile
|
||||||
>
|
v-model="element.videoUrl"
|
||||||
<UploadImg
|
:file-type="['mp4']"
|
||||||
v-model="element.imgUrl"
|
:limit="1"
|
||||||
draggable="false"
|
:file-size="100"
|
||||||
height="80px"
|
class="min-w-80px"
|
||||||
width="100%"
|
/>
|
||||||
class="min-w-80px"
|
</el-form-item>
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<template v-else>
|
|
||||||
<el-form-item label="封面" class="m-b-8px!" label-width="50px">
|
|
||||||
<UploadImg
|
|
||||||
v-model="element.imgUrl"
|
|
||||||
draggable="false"
|
|
||||||
height="80px"
|
|
||||||
width="100%"
|
|
||||||
class="min-w-80px"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="视频" class="m-b-8px!" label-width="50px">
|
|
||||||
<UploadFile
|
|
||||||
v-model="element.videoUrl"
|
|
||||||
:file-type="['mp4']"
|
|
||||||
:limit="1"
|
|
||||||
:file-size="100"
|
|
||||||
class="min-w-80px"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</template>
|
|
||||||
<el-form-item label="链接" class="m-b-8px!" label-width="50px">
|
|
||||||
<AppLinkInput v-model="element.url" />
|
|
||||||
</el-form-item>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</draggable>
|
<el-form-item label="链接" class="m-b-8px!" label-width="40px">
|
||||||
</template>
|
<AppLinkInput v-model="element.url" />
|
||||||
<el-button @click="handleAddImage" type="primary" plain class="w-full">
|
</el-form-item>
|
||||||
添加图片
|
</template>
|
||||||
</el-button>
|
</Draggable>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-form>
|
</el-form>
|
||||||
</ComponentContainerProperty>
|
</ComponentContainerProperty>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import draggable from 'vuedraggable' //拖拽组件
|
import { CarouselProperty } from './config'
|
||||||
import { CarouselItemProperty, CarouselProperty } from './config'
|
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { usePropertyForm } from '@/components/DiyEditor/util'
|
||||||
|
|
||||||
// 轮播图属性面板
|
// 轮播图属性面板
|
||||||
@ -128,15 +101,6 @@ defineOptions({ name: 'CarouselProperty' })
|
|||||||
const props = defineProps<{ modelValue: CarouselProperty }>()
|
const props = defineProps<{ modelValue: CarouselProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const { formData } = usePropertyForm(props.modelValue, emit)
|
||||||
|
|
||||||
// 添加图片
|
|
||||||
const handleAddImage = () => {
|
|
||||||
formData.value.items.push({} as CarouselItemProperty)
|
|
||||||
}
|
|
||||||
// 删除图片
|
|
||||||
const handleDeleteImage = (index: number) => {
|
|
||||||
formData.value.items.splice(index, 1)
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
import { DiyComponent } from '@/components/DiyEditor/util'
|
||||||
|
|
||||||
|
// 悬浮按钮属性
|
||||||
|
export interface FloatingActionButtonProperty {
|
||||||
|
// 展开方向
|
||||||
|
direction: 'horizontal' | 'vertical'
|
||||||
|
// 是否显示文字
|
||||||
|
showText: boolean
|
||||||
|
// 按钮列表
|
||||||
|
list: FloatingActionButtonItemProperty[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 悬浮按钮项属性
|
||||||
|
export interface FloatingActionButtonItemProperty {
|
||||||
|
// 图片地址
|
||||||
|
imgUrl: string
|
||||||
|
// 跳转连接
|
||||||
|
url: string
|
||||||
|
// 文字
|
||||||
|
text: string
|
||||||
|
// 文字颜色
|
||||||
|
textColor: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义组件
|
||||||
|
export const component = {
|
||||||
|
id: 'FloatingActionButton',
|
||||||
|
name: '悬浮按钮',
|
||||||
|
icon: 'tabler:float-right',
|
||||||
|
position: 'fixed',
|
||||||
|
property: {
|
||||||
|
direction: 'vertical',
|
||||||
|
showText: true,
|
||||||
|
list: [{ textColor: '#fff' }]
|
||||||
|
}
|
||||||
|
} as DiyComponent<FloatingActionButtonProperty>
|
@ -0,0 +1,74 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
:class="[
|
||||||
|
'absolute bottom-32px right-[calc(50%-375px/2+32px)] flex z-12 gap-12px items-center',
|
||||||
|
{
|
||||||
|
'flex-row': property.direction === 'horizontal',
|
||||||
|
'flex-col': property.direction === 'vertical'
|
||||||
|
}
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<template v-if="expanded">
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in property.list"
|
||||||
|
:key="index"
|
||||||
|
class="flex flex-col items-center"
|
||||||
|
@click="handleActive(index)"
|
||||||
|
>
|
||||||
|
<el-image :src="item.imgUrl" fit="contain" class="h-27px w-27px">
|
||||||
|
<template #error>
|
||||||
|
<div class="h-full w-full flex items-center justify-center">
|
||||||
|
<Icon icon="ep:picture" :color="item.textColor" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-image>
|
||||||
|
<span v-if="property.showText" class="mt-4px text-12px" :style="{ color: item.textColor }">
|
||||||
|
{{ item.text }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<!-- todo: @owen 使用APP主题色 -->
|
||||||
|
<el-button type="primary" size="large" circle @click="handleToggleFab">
|
||||||
|
<Icon icon="ep:plus" :class="['fab-icon', { active: expanded }]" />
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<!-- 模态背景:展开时显示,点击后折叠 -->
|
||||||
|
<div v-if="expanded" class="modal-bg" @click="handleToggleFab"></div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { FloatingActionButtonProperty } from './config'
|
||||||
|
|
||||||
|
/** 悬浮按钮 */
|
||||||
|
defineOptions({ name: 'FloatingActionButton' })
|
||||||
|
// 定义属性
|
||||||
|
defineProps<{ property: FloatingActionButtonProperty }>()
|
||||||
|
|
||||||
|
// 是否展开
|
||||||
|
const expanded = ref(true)
|
||||||
|
// 处理展开/折叠
|
||||||
|
const handleToggleFab = () => {
|
||||||
|
expanded.value = !expanded.value
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
/* 模态背景 */
|
||||||
|
.modal-bg {
|
||||||
|
position: absolute;
|
||||||
|
left: calc(50% - 375px / 2);
|
||||||
|
top: 0;
|
||||||
|
z-index: 11;
|
||||||
|
width: 375px;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(#000000, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fab-icon {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
transition: transform 0.3s;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
transform: rotate(135deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,44 @@
|
|||||||
|
<template>
|
||||||
|
<el-form label-width="80px" :model="formData">
|
||||||
|
<el-card header="按钮配置" class="property-group" shadow="never">
|
||||||
|
<el-form-item label="展开方向" prop="direction">
|
||||||
|
<el-radio-group v-model="formData.direction">
|
||||||
|
<el-radio label="vertical">垂直</el-radio>
|
||||||
|
<el-radio label="horizontal">水平</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="显示文字" prop="showText">
|
||||||
|
<el-switch v-model="formData.showText" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-card>
|
||||||
|
<el-card header="按钮列表" class="property-group" shadow="never">
|
||||||
|
<Draggable v-model="formData.list" :empty-item="{ textColor: '#fff' }">
|
||||||
|
<template #default="{ element, index }">
|
||||||
|
<el-form-item label="图标" :prop="`list[${index}].imgUrl`">
|
||||||
|
<UploadImg v-model="element.imgUrl" height="56px" width="56px" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="文字" :prop="`list[${index}].text`">
|
||||||
|
<InputWithColor v-model="element.text" v-model:color="element.textColor" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="跳转链接" :prop="`list[${index}].url`">
|
||||||
|
<AppLinkInput v-model="element.url" />
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
</Draggable>
|
||||||
|
</el-card>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { FloatingActionButtonProperty } from './config'
|
||||||
|
import { usePropertyForm } from '@/components/DiyEditor/util'
|
||||||
|
|
||||||
|
// 悬浮按钮属性面板
|
||||||
|
defineOptions({ name: 'FloatingActionButtonProperty' })
|
||||||
|
|
||||||
|
const props = defineProps<{ modelValue: FloatingActionButtonProperty }>()
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
const { formData } = usePropertyForm(props.modelValue, emit)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
@ -0,0 +1,143 @@
|
|||||||
|
import { HotZoneItemProperty } from '@/components/DiyEditor/components/mobile/HotZone/config'
|
||||||
|
import { StyleValue } from 'vue'
|
||||||
|
|
||||||
|
// 热区的最小宽高
|
||||||
|
export const HOT_ZONE_MIN_SIZE = 100
|
||||||
|
|
||||||
|
// 控制的类型
|
||||||
|
export enum CONTROL_TYPE_ENUM {
|
||||||
|
LEFT,
|
||||||
|
TOP,
|
||||||
|
WIDTH,
|
||||||
|
HEIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义热区的控制点
|
||||||
|
export interface ControlDot {
|
||||||
|
position: string
|
||||||
|
types: CONTROL_TYPE_ENUM[]
|
||||||
|
style: StyleValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 热区的8个控制点
|
||||||
|
export const CONTROL_DOT_LIST = [
|
||||||
|
{
|
||||||
|
position: '左上角',
|
||||||
|
types: [
|
||||||
|
CONTROL_TYPE_ENUM.LEFT,
|
||||||
|
CONTROL_TYPE_ENUM.TOP,
|
||||||
|
CONTROL_TYPE_ENUM.WIDTH,
|
||||||
|
CONTROL_TYPE_ENUM.HEIGHT
|
||||||
|
],
|
||||||
|
style: { left: '-5px', top: '-5px', cursor: 'nwse-resize' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
position: '上方中间',
|
||||||
|
types: [CONTROL_TYPE_ENUM.TOP, CONTROL_TYPE_ENUM.HEIGHT],
|
||||||
|
style: { left: '50%', top: '-5px', cursor: 'n-resize', transform: 'translateX(-50%)' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
position: '右上角',
|
||||||
|
types: [CONTROL_TYPE_ENUM.TOP, CONTROL_TYPE_ENUM.WIDTH, CONTROL_TYPE_ENUM.HEIGHT],
|
||||||
|
style: { right: '-5px', top: '-5px', cursor: 'nesw-resize' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
position: '右侧中间',
|
||||||
|
types: [CONTROL_TYPE_ENUM.WIDTH],
|
||||||
|
style: { right: '-5px', top: '50%', cursor: 'e-resize', transform: 'translateX(-50%)' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
position: '右下角',
|
||||||
|
types: [CONTROL_TYPE_ENUM.WIDTH, CONTROL_TYPE_ENUM.HEIGHT],
|
||||||
|
style: { right: '-5px', bottom: '-5px', cursor: 'nwse-resize' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
position: '下方中间',
|
||||||
|
types: [CONTROL_TYPE_ENUM.HEIGHT],
|
||||||
|
style: { left: '50%', bottom: '-5px', cursor: 's-resize', transform: 'translateX(-50%)' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
position: '左下角',
|
||||||
|
types: [CONTROL_TYPE_ENUM.LEFT, CONTROL_TYPE_ENUM.WIDTH, CONTROL_TYPE_ENUM.HEIGHT],
|
||||||
|
style: { left: '-5px', bottom: '-5px', cursor: 'nesw-resize' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
position: '左侧中间',
|
||||||
|
types: [CONTROL_TYPE_ENUM.LEFT, CONTROL_TYPE_ENUM.WIDTH],
|
||||||
|
style: { left: '-5px', top: '50%', cursor: 'w-resize', transform: 'translateX(-50%)' }
|
||||||
|
}
|
||||||
|
] as ControlDot[]
|
||||||
|
|
||||||
|
//region 热区的缩放
|
||||||
|
// 热区的缩放比例
|
||||||
|
export const HOT_ZONE_SCALE_RATE = 2
|
||||||
|
// 缩小:缩回适合手机屏幕的大小
|
||||||
|
export const zoomOut = (list?: HotZoneItemProperty[]) => {
|
||||||
|
return (
|
||||||
|
list?.map((hotZone) => ({
|
||||||
|
...hotZone,
|
||||||
|
left: (hotZone.left /= HOT_ZONE_SCALE_RATE),
|
||||||
|
top: (hotZone.top /= HOT_ZONE_SCALE_RATE),
|
||||||
|
width: (hotZone.width /= HOT_ZONE_SCALE_RATE),
|
||||||
|
height: (hotZone.height /= HOT_ZONE_SCALE_RATE)
|
||||||
|
})) || []
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// 放大:作用是为了方便在电脑屏幕上编辑
|
||||||
|
export const zoomIn = (list?: HotZoneItemProperty[]) => {
|
||||||
|
return (
|
||||||
|
list?.map((hotZone) => ({
|
||||||
|
...hotZone,
|
||||||
|
left: (hotZone.left *= HOT_ZONE_SCALE_RATE),
|
||||||
|
top: (hotZone.top *= HOT_ZONE_SCALE_RATE),
|
||||||
|
width: (hotZone.width *= HOT_ZONE_SCALE_RATE),
|
||||||
|
height: (hotZone.height *= HOT_ZONE_SCALE_RATE)
|
||||||
|
})) || []
|
||||||
|
)
|
||||||
|
}
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 封装热区拖拽
|
||||||
|
*
|
||||||
|
* 注:为什么不使用vueuse的useDraggable。在本场景下,其使用方式比较复杂
|
||||||
|
* @param hotZone 热区
|
||||||
|
* @param downEvent 鼠标按下事件
|
||||||
|
* @param callback 回调函数
|
||||||
|
*/
|
||||||
|
export const useDraggable = (
|
||||||
|
hotZone: HotZoneItemProperty,
|
||||||
|
downEvent: MouseEvent,
|
||||||
|
callback: (
|
||||||
|
left: number,
|
||||||
|
top: number,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
moveWidth: number,
|
||||||
|
moveHeight: number
|
||||||
|
) => void
|
||||||
|
) => {
|
||||||
|
// 阻止事件冒泡
|
||||||
|
downEvent.stopPropagation()
|
||||||
|
|
||||||
|
// 移动前的鼠标坐标
|
||||||
|
const { clientX: startX, clientY: startY } = downEvent
|
||||||
|
// 移动前的热区坐标、大小
|
||||||
|
const { left, top, width, height } = hotZone
|
||||||
|
|
||||||
|
// 监听鼠标移动
|
||||||
|
document.onmousemove = (e) => {
|
||||||
|
// 移动宽度
|
||||||
|
const moveWidth = e.clientX - startX
|
||||||
|
// 移动高度
|
||||||
|
const moveHeight = e.clientY - startY
|
||||||
|
// 移动回调
|
||||||
|
callback(left, top, width, height, moveWidth, moveHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 松开鼠标后,结束拖拽
|
||||||
|
document.onmouseup = () => {
|
||||||
|
document.onmousemove = null
|
||||||
|
document.onmouseup = null
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,236 @@
|
|||||||
|
<template>
|
||||||
|
<Dialog v-model="dialogVisible" title="设置热区" width="780" @close="handleClose">
|
||||||
|
<div ref="container" class="relative h-full w-750px">
|
||||||
|
<el-image :src="imgUrl" class="pointer-events-none h-full w-750px select-none" />
|
||||||
|
<div
|
||||||
|
v-for="(item, hotZoneIndex) in formData"
|
||||||
|
:key="hotZoneIndex"
|
||||||
|
class="hot-zone"
|
||||||
|
:style="{
|
||||||
|
width: `${item.width}px`,
|
||||||
|
height: `${item.height}px`,
|
||||||
|
top: `${item.top}px`,
|
||||||
|
left: `${item.left}px`
|
||||||
|
}"
|
||||||
|
@mousedown="handleMove(item, $event)"
|
||||||
|
@dblclick="handleShowAppLinkDialog(item)"
|
||||||
|
>
|
||||||
|
<span class="pointer-events-none select-none">{{ item.name || '双击选择链接' }}</span>
|
||||||
|
<Icon icon="ep:close" class="delete" :size="14" @click="handleRemove(item)" />
|
||||||
|
|
||||||
|
<!-- 8个控制点 -->
|
||||||
|
<span
|
||||||
|
class="ctrl-dot"
|
||||||
|
v-for="(dot, dotIndex) in CONTROL_DOT_LIST"
|
||||||
|
:key="dotIndex"
|
||||||
|
:style="dot.style"
|
||||||
|
@mousedown="handleResize(item, dot, $event)"
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="handleAdd" type="primary" plain>
|
||||||
|
<Icon icon="ep:plus" class="mr-5px" />
|
||||||
|
添加热区
|
||||||
|
</el-button>
|
||||||
|
<el-button @click="handleSubmit" type="primary" plain>
|
||||||
|
<Icon icon="ep:check" class="mr-5px" />
|
||||||
|
确定
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
<AppLinkSelectDialog ref="appLinkDialogRef" @app-link-change="handleAppLinkChange" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { HotZoneItemProperty } from '@/components/DiyEditor/components/mobile/HotZone/config'
|
||||||
|
import { array, string } from 'vue-types'
|
||||||
|
import {
|
||||||
|
CONTROL_DOT_LIST,
|
||||||
|
CONTROL_TYPE_ENUM,
|
||||||
|
ControlDot,
|
||||||
|
HOT_ZONE_MIN_SIZE,
|
||||||
|
useDraggable,
|
||||||
|
zoomIn,
|
||||||
|
zoomOut
|
||||||
|
} from './controller'
|
||||||
|
import { AppLink } from '@/components/AppLinkInput/data'
|
||||||
|
import { remove } from 'lodash-es'
|
||||||
|
|
||||||
|
/** 热区编辑对话框 */
|
||||||
|
defineOptions({ name: 'HotZoneEditDialog' })
|
||||||
|
|
||||||
|
// 定义属性
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: array<HotZoneItemProperty>(),
|
||||||
|
imgUrl: string().def('')
|
||||||
|
})
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
const formData = ref<HotZoneItemProperty[]>([])
|
||||||
|
|
||||||
|
// 弹窗的是否显示
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
// 打开弹窗
|
||||||
|
const open = () => {
|
||||||
|
// 放大
|
||||||
|
formData.value = zoomIn(props.modelValue)
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
// 提供 open 方法,用于打开弹窗
|
||||||
|
defineExpose({ open })
|
||||||
|
|
||||||
|
// 热区容器
|
||||||
|
const container = ref<HTMLDivElement>()
|
||||||
|
|
||||||
|
// 增加热区
|
||||||
|
const handleAdd = () => {
|
||||||
|
formData.value.push({
|
||||||
|
width: HOT_ZONE_MIN_SIZE,
|
||||||
|
height: HOT_ZONE_MIN_SIZE,
|
||||||
|
top: 0,
|
||||||
|
left: 0
|
||||||
|
} as HotZoneItemProperty)
|
||||||
|
}
|
||||||
|
// 删除热区
|
||||||
|
const handleRemove = (hotZone: HotZoneItemProperty) => {
|
||||||
|
remove(formData.value, hotZone)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移动热区
|
||||||
|
const handleMove = (item: HotZoneItemProperty, e: MouseEvent) => {
|
||||||
|
useDraggable(item, e, (left, top, _, __, moveWidth, moveHeight) => {
|
||||||
|
setLeft(item, left + moveWidth)
|
||||||
|
setTop(item, top + moveHeight)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调整热区大小、位置
|
||||||
|
const handleResize = (item: HotZoneItemProperty, ctrlDot: ControlDot, e: MouseEvent) => {
|
||||||
|
useDraggable(item, e, (left, top, width, height, moveWidth, moveHeight) => {
|
||||||
|
ctrlDot.types.forEach((type) => {
|
||||||
|
switch (type) {
|
||||||
|
case CONTROL_TYPE_ENUM.LEFT:
|
||||||
|
setLeft(item, left + moveWidth)
|
||||||
|
break
|
||||||
|
case CONTROL_TYPE_ENUM.TOP:
|
||||||
|
setTop(item, top + moveHeight)
|
||||||
|
break
|
||||||
|
case CONTROL_TYPE_ENUM.WIDTH:
|
||||||
|
{
|
||||||
|
// 上移时,高度为减少
|
||||||
|
const direction = ctrlDot.types.includes(CONTROL_TYPE_ENUM.LEFT) ? -1 : 1
|
||||||
|
setWidth(item, width + moveWidth * direction)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case CONTROL_TYPE_ENUM.HEIGHT:
|
||||||
|
{
|
||||||
|
// 左移时,宽度为减少
|
||||||
|
const direction = ctrlDot.types.includes(CONTROL_TYPE_ENUM.TOP) ? -1 : 1
|
||||||
|
setHeight(item, height + moveHeight * direction)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置X轴坐标
|
||||||
|
const setLeft = (item: HotZoneItemProperty, left: number) => {
|
||||||
|
// 不能超出容器
|
||||||
|
if (left >= 0 && left <= container.value!.offsetWidth - item.width) {
|
||||||
|
item.left = left
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 设置Y轴坐标
|
||||||
|
const setTop = (item: HotZoneItemProperty, top: number) => {
|
||||||
|
// 不能超出容器
|
||||||
|
if (top >= 0 && top <= container.value!.offsetHeight - item.height) {
|
||||||
|
item.top = top
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 设置宽度
|
||||||
|
const setWidth = (item: HotZoneItemProperty, width: number) => {
|
||||||
|
// 不能小于最小宽度 && 不能超出容器右边
|
||||||
|
if (width >= HOT_ZONE_MIN_SIZE && item.left + width <= container.value!.offsetWidth) {
|
||||||
|
item.width = width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 设置高度
|
||||||
|
const setHeight = (item: HotZoneItemProperty, height: number) => {
|
||||||
|
// 不能小于最小高度 && 不能超出容器底部
|
||||||
|
if (height >= HOT_ZONE_MIN_SIZE && item.top + height <= container.value!.offsetHeight) {
|
||||||
|
item.height = height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理对话框关闭
|
||||||
|
const handleSubmit = () => {
|
||||||
|
// 会自动触发handleClose
|
||||||
|
dialogVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理对话框关闭
|
||||||
|
const handleClose = () => {
|
||||||
|
// 缩小
|
||||||
|
const list = zoomOut(formData.value)
|
||||||
|
emit('update:modelValue', list)
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeHotZone = ref<HotZoneItemProperty>()
|
||||||
|
const appLinkDialogRef = ref()
|
||||||
|
const handleShowAppLinkDialog = (hotZone: HotZoneItemProperty) => {
|
||||||
|
activeHotZone.value = hotZone
|
||||||
|
appLinkDialogRef.value.open(hotZone.url)
|
||||||
|
}
|
||||||
|
const handleAppLinkChange = (appLink: AppLink) => {
|
||||||
|
if (!appLink || !activeHotZone.value) return
|
||||||
|
activeHotZone.value.name = appLink.name
|
||||||
|
activeHotZone.value.url = appLink.path
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.hot-zone {
|
||||||
|
position: absolute;
|
||||||
|
background: var(--el-color-primary-light-7);
|
||||||
|
opacity: 0.8;
|
||||||
|
border: 1px solid var(--el-color-primary);
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
font-size: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: move;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
/* 控制点 */
|
||||||
|
.ctrl-dot {
|
||||||
|
position: absolute;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: inherit;
|
||||||
|
background-color: #fff;
|
||||||
|
z-index: 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 2px 2px 6px 6px;
|
||||||
|
background-color: var(--el-color-primary);
|
||||||
|
border-radius: 0 0 0 80%;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #fff;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.delete {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
42
src/components/DiyEditor/components/mobile/HotZone/config.ts
Normal file
42
src/components/DiyEditor/components/mobile/HotZone/config.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
|
||||||
|
|
||||||
|
/** 热区属性 */
|
||||||
|
export interface HotZoneProperty {
|
||||||
|
// 图片地址
|
||||||
|
imgUrl: string
|
||||||
|
// 导航菜单列表
|
||||||
|
list: HotZoneItemProperty[]
|
||||||
|
// 组件样式
|
||||||
|
style: ComponentStyle
|
||||||
|
}
|
||||||
|
/** 热区项目属性 */
|
||||||
|
export interface HotZoneItemProperty {
|
||||||
|
// 链接的名称
|
||||||
|
name: string
|
||||||
|
// 链接
|
||||||
|
url: string
|
||||||
|
// 宽
|
||||||
|
width: number
|
||||||
|
// 高
|
||||||
|
height: number
|
||||||
|
// 上
|
||||||
|
top: number
|
||||||
|
// 左
|
||||||
|
left: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义组件
|
||||||
|
export const component = {
|
||||||
|
id: 'HotZone',
|
||||||
|
name: '热区',
|
||||||
|
icon: 'tabler:hand-click',
|
||||||
|
property: {
|
||||||
|
imgUrl: '',
|
||||||
|
list: [] as HotZoneItemProperty[],
|
||||||
|
style: {
|
||||||
|
bgType: 'color',
|
||||||
|
bgColor: '#fff',
|
||||||
|
marginBottom: 8
|
||||||
|
} as ComponentStyle
|
||||||
|
}
|
||||||
|
} as DiyComponent<HotZoneProperty>
|
42
src/components/DiyEditor/components/mobile/HotZone/index.vue
Normal file
42
src/components/DiyEditor/components/mobile/HotZone/index.vue
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<template>
|
||||||
|
<div class="relative h-full min-h-30px w-full">
|
||||||
|
<el-image :src="property.imgUrl" class="pointer-events-none h-full w-full select-none" />
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in property.list"
|
||||||
|
:key="index"
|
||||||
|
class="hot-zone"
|
||||||
|
:style="{
|
||||||
|
width: `${item.width}px`,
|
||||||
|
height: `${item.height}px`,
|
||||||
|
top: `${item.top}px`,
|
||||||
|
left: `${item.left}px`
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ item.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { HotZoneProperty } from './config'
|
||||||
|
|
||||||
|
/** 热区 */
|
||||||
|
defineOptions({ name: 'HotZone' })
|
||||||
|
const props = defineProps<{ property: HotZoneProperty }>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.hot-zone {
|
||||||
|
position: absolute;
|
||||||
|
background: var(--el-color-primary-light-7);
|
||||||
|
opacity: 0.8;
|
||||||
|
border: 1px solid var(--el-color-primary);
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
font-size: 14px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: move;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,63 @@
|
|||||||
|
<template>
|
||||||
|
<ComponentContainerProperty v-model="formData.style">
|
||||||
|
<!-- 表单 -->
|
||||||
|
<el-form label-width="80px" :model="formData" class="m-t-8px">
|
||||||
|
<el-form-item label="上传图片" prop="imgUrl">
|
||||||
|
<UploadImg v-model="formData.imgUrl" height="50px" width="auto" class="min-w-80px">
|
||||||
|
<template #tip>
|
||||||
|
<el-text type="info" size="small"> 推荐宽度 750</el-text>
|
||||||
|
</template>
|
||||||
|
</UploadImg>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<el-button type="primary" plain class="w-full" @click="handleOpenEditDialog">
|
||||||
|
设置热区
|
||||||
|
</el-button>
|
||||||
|
</ComponentContainerProperty>
|
||||||
|
<!-- 热区编辑对话框 -->
|
||||||
|
<HotZoneEditDialog ref="editDialogRef" v-model="formData.list" :img-url="formData.imgUrl" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { usePropertyForm } from '@/components/DiyEditor/util'
|
||||||
|
import { HotZoneProperty } from '@/components/DiyEditor/components/mobile/HotZone/config'
|
||||||
|
import HotZoneEditDialog from './components/HotZoneEditDialog/index.vue'
|
||||||
|
|
||||||
|
/** 热区属性面板 */
|
||||||
|
defineOptions({ name: 'HotZoneProperty' })
|
||||||
|
|
||||||
|
const props = defineProps<{ modelValue: HotZoneProperty }>()
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
const { formData } = usePropertyForm(props.modelValue, emit)
|
||||||
|
|
||||||
|
// 热区编辑对话框
|
||||||
|
const editDialogRef = ref()
|
||||||
|
// 打开热区编辑对话框
|
||||||
|
const handleOpenEditDialog = () => {
|
||||||
|
editDialogRef.value.open()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.hot-zone {
|
||||||
|
position: absolute;
|
||||||
|
background: #409effbf;
|
||||||
|
border: 1px solid var(--el-color-primary);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: move;
|
||||||
|
|
||||||
|
/* 控制点 */
|
||||||
|
.ctrl-dot {
|
||||||
|
position: absolute;
|
||||||
|
width: 4px;
|
||||||
|
height: 4px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -9,72 +9,50 @@
|
|||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-text tag="p"> 菜单设置 </el-text>
|
<el-card header="菜单设置" class="property-group" shadow="never">
|
||||||
<el-text type="info" size="small"> 拖动左侧的小圆点可以调整顺序 </el-text>
|
<Draggable v-model="formData.list" :empty-item="EMPTY_MENU_GRID_ITEM_PROPERTY">
|
||||||
<template v-if="formData.list.length">
|
<template #default="{ element }">
|
||||||
<VueDraggable
|
<el-form-item label="图标" prop="iconUrl">
|
||||||
class="m-t-8px"
|
<UploadImg v-model="element.iconUrl" height="80px" width="80px">
|
||||||
:list="formData.list"
|
<template #tip> 建议尺寸:44 * 44 </template>
|
||||||
item-key="index"
|
</UploadImg>
|
||||||
handle=".drag-icon"
|
</el-form-item>
|
||||||
:forceFallback="true"
|
<el-form-item label="标题" prop="title">
|
||||||
:animation="200"
|
<InputWithColor v-model="element.title" v-model:color="element.titleColor" />
|
||||||
>
|
</el-form-item>
|
||||||
<template #item="{ element, index }">
|
<el-form-item label="副标题" prop="subtitle">
|
||||||
<div class="mb-4px flex flex-col gap-4px rounded bg-gray-100 p-8px">
|
<InputWithColor v-model="element.subtitle" v-model:color="element.subtitleColor" />
|
||||||
<div class="flex flex-row justify-between">
|
</el-form-item>
|
||||||
<Icon icon="ic:round-drag-indicator" class="drag-icon cursor-move" />
|
<el-form-item label="链接" prop="url">
|
||||||
<Icon icon="ep:delete" class="text-red-500" @click="handleDeleteMenu(index)" />
|
<AppLinkInput v-model="element.url" />
|
||||||
</div>
|
</el-form-item>
|
||||||
<el-form-item label="图标" prop="iconUrl">
|
<el-form-item label="显示角标" prop="badge.show">
|
||||||
<UploadImg v-model="element.iconUrl" height="80px" width="80px">
|
<el-switch v-model="element.badge.show" />
|
||||||
<template #tip> 建议尺寸:44 * 44 </template>
|
</el-form-item>
|
||||||
</UploadImg>
|
<template v-if="element.badge.show">
|
||||||
|
<el-form-item label="角标内容" prop="badge.text">
|
||||||
|
<InputWithColor
|
||||||
|
v-model="element.badge.text"
|
||||||
|
v-model:color="element.badge.textColor"
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="标题" prop="title">
|
<el-form-item label="背景颜色" prop="badge.bgColor">
|
||||||
<InputWithColor v-model="element.title" v-model:color="element.titleColor" />
|
<ColorInput v-model="element.badge.bgColor" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="副标题" prop="subtitle">
|
</template>
|
||||||
<InputWithColor v-model="element.subtitle" v-model:color="element.subtitleColor" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="链接" prop="url">
|
|
||||||
<AppLinkInput v-model="element.url" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="显示角标" prop="badge.show">
|
|
||||||
<el-switch v-model="element.badge.show" />
|
|
||||||
</el-form-item>
|
|
||||||
<template v-if="element.badge.show">
|
|
||||||
<el-form-item label="角标内容" prop="badge.text">
|
|
||||||
<InputWithColor
|
|
||||||
v-model="element.badge.text"
|
|
||||||
v-model:color="element.badge.textColor"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="背景颜色" prop="badge.bgColor">
|
|
||||||
<ColorInput v-model="element.badge.bgColor" />
|
|
||||||
</el-form-item>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</VueDraggable>
|
</Draggable>
|
||||||
</template>
|
</el-card>
|
||||||
<el-form-item label-width="0">
|
|
||||||
<el-button @click="handleAddMenu" type="primary" plain class="m-t-8px w-full">
|
|
||||||
<Icon icon="ep:plus" class="mr-5px" /> 添加菜单
|
|
||||||
</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
</el-form>
|
||||||
</ComponentContainerProperty>
|
</ComponentContainerProperty>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import VueDraggable from 'vuedraggable'
|
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { usePropertyForm } from '@/components/DiyEditor/util'
|
||||||
import {
|
import {
|
||||||
EMPTY_MENU_GRID_ITEM_PROPERTY,
|
EMPTY_MENU_GRID_ITEM_PROPERTY,
|
||||||
MenuGridProperty
|
MenuGridProperty
|
||||||
} from '@/components/DiyEditor/components/mobile/MenuGrid/config'
|
} from '@/components/DiyEditor/components/mobile/MenuGrid/config'
|
||||||
import { cloneDeep } from 'lodash-es'
|
|
||||||
|
|
||||||
/** 宫格导航属性面板 */
|
/** 宫格导航属性面板 */
|
||||||
defineOptions({ name: 'MenuGridProperty' })
|
defineOptions({ name: 'MenuGridProperty' })
|
||||||
@ -82,15 +60,6 @@ defineOptions({ name: 'MenuGridProperty' })
|
|||||||
const props = defineProps<{ modelValue: MenuGridProperty }>()
|
const props = defineProps<{ modelValue: MenuGridProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const { formData } = usePropertyForm(props.modelValue, emit)
|
||||||
|
|
||||||
/* 添加菜单 */
|
|
||||||
const handleAddMenu = () => {
|
|
||||||
formData.value.list.push(cloneDeep(EMPTY_MENU_GRID_ITEM_PROPERTY))
|
|
||||||
}
|
|
||||||
/* 删除菜单 */
|
|
||||||
const handleDeleteMenu = (index: number) => {
|
|
||||||
formData.value.list.splice(index, 1)
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
|
@ -5,55 +5,34 @@
|
|||||||
|
|
||||||
<!-- 表单 -->
|
<!-- 表单 -->
|
||||||
<el-form label-width="60px" :model="formData" class="m-t-8px">
|
<el-form label-width="60px" :model="formData" class="m-t-8px">
|
||||||
<div v-if="formData.list.length">
|
<Draggable v-model="formData.list" :empty-item="EMPTY_MENU_LIST_ITEM_PROPERTY">
|
||||||
<VueDraggable
|
<template #default="{ element }">
|
||||||
:list="formData.list"
|
<el-form-item label="图标" prop="iconUrl">
|
||||||
item-key="index"
|
<UploadImg v-model="element.iconUrl" height="80px" width="80px">
|
||||||
handle=".drag-icon"
|
<template #tip> 建议尺寸:44 * 44 </template>
|
||||||
:forceFallback="true"
|
</UploadImg>
|
||||||
:animation="200"
|
</el-form-item>
|
||||||
>
|
<el-form-item label="标题" prop="title">
|
||||||
<template #item="{ element, index }">
|
<InputWithColor v-model="element.title" v-model:color="element.titleColor" />
|
||||||
<div class="mb-4px flex flex-col gap-4px rounded bg-gray-100 p-8px">
|
</el-form-item>
|
||||||
<div class="flex flex-row justify-between">
|
<el-form-item label="副标题" prop="subtitle">
|
||||||
<Icon icon="ic:round-drag-indicator" class="drag-icon cursor-move" />
|
<InputWithColor v-model="element.subtitle" v-model:color="element.subtitleColor" />
|
||||||
<Icon icon="ep:delete" class="text-red-500" @click="handleDeleteMenu(index)" />
|
</el-form-item>
|
||||||
</div>
|
<el-form-item label="链接" prop="url">
|
||||||
<el-form-item label="图标" prop="iconUrl">
|
<AppLinkInput v-model="element.url" />
|
||||||
<UploadImg v-model="element.iconUrl" height="80px" width="80px">
|
</el-form-item>
|
||||||
<template #tip> 建议尺寸:44 * 44 </template>
|
</template>
|
||||||
</UploadImg>
|
</Draggable>
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="标题" prop="title">
|
|
||||||
<InputWithColor v-model="element.title" v-model:color="element.titleColor" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="副标题" prop="subtitle">
|
|
||||||
<InputWithColor v-model="element.subtitle" v-model:color="element.subtitleColor" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="链接" prop="url">
|
|
||||||
<AppLinkInput v-model="element.url" />
|
|
||||||
</el-form-item>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</VueDraggable>
|
|
||||||
</div>
|
|
||||||
<el-form-item label-width="0">
|
|
||||||
<el-button @click="handleAddMenu" type="primary" plain class="m-t-8px w-full">
|
|
||||||
<Icon icon="ep:plus" class="mr-5px" /> 添加菜单
|
|
||||||
</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
</el-form>
|
||||||
</ComponentContainerProperty>
|
</ComponentContainerProperty>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import VueDraggable from 'vuedraggable'
|
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { usePropertyForm } from '@/components/DiyEditor/util'
|
||||||
import {
|
import {
|
||||||
EMPTY_MENU_LIST_ITEM_PROPERTY,
|
EMPTY_MENU_LIST_ITEM_PROPERTY,
|
||||||
MenuListProperty
|
MenuListProperty
|
||||||
} from '@/components/DiyEditor/components/mobile/MenuList/config'
|
} from '@/components/DiyEditor/components/mobile/MenuList/config'
|
||||||
import { cloneDeep } from 'lodash-es'
|
|
||||||
|
|
||||||
/** 列表导航属性面板 */
|
/** 列表导航属性面板 */
|
||||||
defineOptions({ name: 'MenuListProperty' })
|
defineOptions({ name: 'MenuListProperty' })
|
||||||
@ -61,15 +40,6 @@ defineOptions({ name: 'MenuListProperty' })
|
|||||||
const props = defineProps<{ modelValue: MenuListProperty }>()
|
const props = defineProps<{ modelValue: MenuListProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const { formData } = usePropertyForm(props.modelValue, emit)
|
||||||
|
|
||||||
/* 添加菜单 */
|
|
||||||
const handleAddMenu = () => {
|
|
||||||
formData.value.list.push(cloneDeep(EMPTY_MENU_LIST_ITEM_PROPERTY))
|
|
||||||
}
|
|
||||||
/* 删除菜单 */
|
|
||||||
const handleDeleteMenu = (index: number) => {
|
|
||||||
formData.value.list.splice(index, 1)
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
<!-- 标题 -->
|
<!-- 标题 -->
|
||||||
<span
|
<span
|
||||||
v-if="property.layout === 'iconText'"
|
v-if="property.layout === 'iconText'"
|
||||||
class="text-14px"
|
class="text-12px"
|
||||||
:style="{
|
:style="{
|
||||||
color: item.titleColor,
|
color: item.titleColor,
|
||||||
height: `${TITLE_HEIGHT}px`,
|
height: `${TITLE_HEIGHT}px`,
|
||||||
@ -51,7 +51,7 @@ const props = defineProps<{ property: MenuSwiperProperty }>()
|
|||||||
// 标题的高度
|
// 标题的高度
|
||||||
const TITLE_HEIGHT = 20
|
const TITLE_HEIGHT = 20
|
||||||
// 图标的高度
|
// 图标的高度
|
||||||
const ICON_SIZE = 50
|
const ICON_SIZE = 42
|
||||||
// 垂直间距:一行上下的间距
|
// 垂直间距:一行上下的间距
|
||||||
const SPACE_Y = 16
|
const SPACE_Y = 16
|
||||||
|
|
||||||
|
@ -22,63 +22,42 @@
|
|||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-text tag="p"> 菜单设置 </el-text>
|
<el-card header="菜单设置" class="property-group" shadow="never">
|
||||||
<el-text type="info" size="small"> 拖动左侧的小圆点可以调整顺序 </el-text>
|
<Draggable v-model="formData.list" :empty-item="cloneDeep(EMPTY_MENU_SWIPER_ITEM_PROPERTY)">
|
||||||
<template v-if="formData.list.length">
|
<template #default="{ element }">
|
||||||
<VueDraggable
|
<el-form-item label="图标" prop="iconUrl">
|
||||||
class="m-t-8px"
|
<UploadImg v-model="element.iconUrl" height="80px" width="80px">
|
||||||
:list="formData.list"
|
<template #tip> 建议尺寸:98 * 98 </template>
|
||||||
item-key="index"
|
</UploadImg>
|
||||||
handle=".drag-icon"
|
</el-form-item>
|
||||||
:forceFallback="true"
|
<el-form-item label="标题" prop="title">
|
||||||
:animation="200"
|
<InputWithColor v-model="element.title" v-model:color="element.titleColor" />
|
||||||
>
|
</el-form-item>
|
||||||
<template #item="{ element, index }">
|
<el-form-item label="链接" prop="url">
|
||||||
<div class="mb-4px flex flex-col gap-4px rounded bg-gray-100 p-8px">
|
<AppLinkInput v-model="element.url" />
|
||||||
<div class="flex flex-row justify-between">
|
</el-form-item>
|
||||||
<Icon icon="ic:round-drag-indicator" class="drag-icon cursor-move" />
|
<el-form-item label="显示角标" prop="badge.show">
|
||||||
<Icon icon="ep:delete" class="text-red-500" @click="handleDeleteMenu(index)" />
|
<el-switch v-model="element.badge.show" />
|
||||||
</div>
|
</el-form-item>
|
||||||
<el-form-item label="图标" prop="iconUrl">
|
<template v-if="element.badge.show">
|
||||||
<UploadImg v-model="element.iconUrl" height="80px" width="80px">
|
<el-form-item label="角标内容" prop="badge.text">
|
||||||
<template #tip> 建议尺寸:98 * 98 </template>
|
<InputWithColor
|
||||||
</UploadImg>
|
v-model="element.badge.text"
|
||||||
|
v-model:color="element.badge.textColor"
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="标题" prop="title">
|
<el-form-item label="背景颜色" prop="badge.bgColor">
|
||||||
<InputWithColor v-model="element.title" v-model:color="element.titleColor" />
|
<ColorInput v-model="element.badge.bgColor" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="链接" prop="url">
|
</template>
|
||||||
<AppLinkInput v-model="element.url" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="显示角标" prop="badge.show">
|
|
||||||
<el-switch v-model="element.badge.show" />
|
|
||||||
</el-form-item>
|
|
||||||
<template v-if="element.badge.show">
|
|
||||||
<el-form-item label="角标内容" prop="badge.text">
|
|
||||||
<InputWithColor
|
|
||||||
v-model="element.badge.text"
|
|
||||||
v-model:color="element.badge.textColor"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="背景颜色" prop="badge.bgColor">
|
|
||||||
<ColorInput v-model="element.badge.bgColor" />
|
|
||||||
</el-form-item>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</VueDraggable>
|
</Draggable>
|
||||||
</template>
|
</el-card>
|
||||||
<el-form-item label-width="0">
|
|
||||||
<el-button @click="handleAddMenu" type="primary" plain class="m-t-8px w-full">
|
|
||||||
<Icon icon="ep:plus" class="mr-5px" /> 添加菜单
|
|
||||||
</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
</el-form>
|
||||||
</ComponentContainerProperty>
|
</ComponentContainerProperty>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import VueDraggable from 'vuedraggable'
|
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { usePropertyForm } from '@/components/DiyEditor/util'
|
||||||
import {
|
import {
|
||||||
EMPTY_MENU_SWIPER_ITEM_PROPERTY,
|
EMPTY_MENU_SWIPER_ITEM_PROPERTY,
|
||||||
@ -92,15 +71,6 @@ defineOptions({ name: 'MenuSwiperProperty' })
|
|||||||
const props = defineProps<{ modelValue: MenuSwiperProperty }>()
|
const props = defineProps<{ modelValue: MenuSwiperProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const { formData } = usePropertyForm(props.modelValue, emit)
|
||||||
|
|
||||||
/* 添加菜单 */
|
|
||||||
const handleAddMenu = () => {
|
|
||||||
formData.value.list.push(cloneDeep(EMPTY_MENU_SWIPER_ITEM_PROPERTY))
|
|
||||||
}
|
|
||||||
/* 删除菜单 */
|
|
||||||
const handleDeleteMenu = (index: number) => {
|
|
||||||
formData.value.list.splice(index, 1)
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { DiyComponent } from '@/components/DiyEditor/util'
|
import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
|
||||||
|
|
||||||
/** 公告栏属性 */
|
/** 公告栏属性 */
|
||||||
export interface NoticeBarProperty {
|
export interface NoticeBarProperty {
|
||||||
@ -10,6 +10,8 @@ export interface NoticeBarProperty {
|
|||||||
backgroundColor: string
|
backgroundColor: string
|
||||||
// 文字颜色
|
// 文字颜色
|
||||||
textColor: string
|
textColor: string
|
||||||
|
// 组件样式
|
||||||
|
style: ComponentStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 内容属性 */
|
/** 内容属性 */
|
||||||
@ -34,6 +36,11 @@ export const component = {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
backgroundColor: '#fff',
|
backgroundColor: '#fff',
|
||||||
textColor: '#333'
|
textColor: '#333',
|
||||||
|
style: {
|
||||||
|
bgType: 'color',
|
||||||
|
bgColor: '#fff',
|
||||||
|
marginBottom: 8
|
||||||
|
} as ComponentStyle
|
||||||
}
|
}
|
||||||
} as DiyComponent<NoticeBarProperty>
|
} as DiyComponent<NoticeBarProperty>
|
||||||
|
@ -1,58 +1,37 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-form label-width="80px" :model="formData" :rules="rules">
|
<ComponentContainerProperty v-model="formData.style">
|
||||||
<el-form-item label="公告图标" prop="iconUrl">
|
<el-form label-width="80px" :model="formData" :rules="rules">
|
||||||
<UploadImg v-model="formData.iconUrl" height="48px">
|
<el-form-item label="公告图标" prop="iconUrl">
|
||||||
<template #tip>建议尺寸:24 * 24</template>
|
<UploadImg v-model="formData.iconUrl" height="48px">
|
||||||
</UploadImg>
|
<template #tip>建议尺寸:24 * 24</template>
|
||||||
</el-form-item>
|
</UploadImg>
|
||||||
<el-form-item label="背景颜色" prop="backgroundColor">
|
</el-form-item>
|
||||||
<ColorInput v-model="formData.backgroundColor" />
|
<el-form-item label="背景颜色" prop="backgroundColor">
|
||||||
</el-form-item>
|
<ColorInput v-model="formData.backgroundColor" />
|
||||||
<el-form-item label="文字颜色" prop="文字颜色">
|
</el-form-item>
|
||||||
<ColorInput v-model="formData.textColor" />
|
<el-form-item label="文字颜色" prop="文字颜色">
|
||||||
</el-form-item>
|
<ColorInput v-model="formData.textColor" />
|
||||||
<el-text tag="p"> 公告内容 </el-text>
|
</el-form-item>
|
||||||
<el-text type="info" size="small"> 拖动左上角的小圆点可以调整热词顺序 </el-text>
|
|
||||||
<template v-if="formData.contents.length">
|
<el-card header="公告内容" class="property-group" shadow="never">
|
||||||
<VueDraggable
|
<Draggable v-model="formData.contents">
|
||||||
:list="formData.contents"
|
<template #default="{ element }">
|
||||||
item-key="index"
|
<el-form-item label="公告" prop="text" label-width="40px">
|
||||||
handle=".drag-icon"
|
|
||||||
:forceFallback="true"
|
|
||||||
:animation="200"
|
|
||||||
class="m-t-8px"
|
|
||||||
>
|
|
||||||
<template #item="{ element, index }">
|
|
||||||
<div class="mb-4px flex flex-row gap-4px rounded bg-gray-100 p-8px">
|
|
||||||
<div class="flex flex-col items-start justify-between">
|
|
||||||
<Icon icon="ic:round-drag-indicator" class="drag-icon cursor-move" />
|
|
||||||
<Icon
|
|
||||||
icon="ep:delete"
|
|
||||||
class="cursor-pointer text-red-5"
|
|
||||||
@click="handleDeleteContent(index)"
|
|
||||||
v-if="formData.contents.length > 1"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="w-full flex flex-col gap-8px">
|
|
||||||
<el-input v-model="element.text" placeholder="请输入公告" />
|
<el-input v-model="element.text" placeholder="请输入公告" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="链接" prop="url" label-width="40px">
|
||||||
<AppLinkInput v-model="element.url" />
|
<AppLinkInput v-model="element.url" />
|
||||||
</div>
|
</el-form-item>
|
||||||
</div>
|
</template>
|
||||||
</template>
|
</Draggable>
|
||||||
</VueDraggable>
|
</el-card>
|
||||||
</template>
|
</el-form>
|
||||||
<el-form-item label-width="0">
|
</ComponentContainerProperty>
|
||||||
<el-button @click="handleAddContent" type="primary" plain class="m-t-8px w-full">
|
|
||||||
添加内容
|
|
||||||
</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { NoticeBarProperty, NoticeContentProperty } from './config'
|
import { NoticeBarProperty } from './config'
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { usePropertyForm } from '@/components/DiyEditor/util'
|
||||||
import VueDraggable from 'vuedraggable'
|
|
||||||
// 通知栏属性面板
|
// 通知栏属性面板
|
||||||
defineOptions({ name: 'NoticeBarProperty' })
|
defineOptions({ name: 'NoticeBarProperty' })
|
||||||
// 表单校验
|
// 表单校验
|
||||||
@ -63,15 +42,6 @@ const rules = {
|
|||||||
const props = defineProps<{ modelValue: NoticeBarProperty }>()
|
const props = defineProps<{ modelValue: NoticeBarProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const { formData } = usePropertyForm(props.modelValue, emit)
|
||||||
|
|
||||||
/* 添加公告 */
|
|
||||||
const handleAddContent = () => {
|
|
||||||
formData.value.contents.push({} as NoticeContentProperty)
|
|
||||||
}
|
|
||||||
/* 删除公告 */
|
|
||||||
const handleDeleteContent = (index: number) => {
|
|
||||||
formData.value.contents.splice(index, 1)
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
|
26
src/components/DiyEditor/components/mobile/Popover/config.ts
Normal file
26
src/components/DiyEditor/components/mobile/Popover/config.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { DiyComponent } from '@/components/DiyEditor/util'
|
||||||
|
|
||||||
|
/** 弹窗广告属性 */
|
||||||
|
export interface PopoverProperty {
|
||||||
|
list: PopoverItemProperty[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PopoverItemProperty {
|
||||||
|
// 图片地址
|
||||||
|
imgUrl: string
|
||||||
|
// 跳转连接
|
||||||
|
url: string
|
||||||
|
// 显示类型:仅显示一次、每次启动都会显示
|
||||||
|
showType: 'once' | 'always'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义组件
|
||||||
|
export const component = {
|
||||||
|
id: 'Popover',
|
||||||
|
name: '弹窗广告',
|
||||||
|
icon: 'carbon:popup',
|
||||||
|
position: 'fixed',
|
||||||
|
property: {
|
||||||
|
list: [{ showType: 'once' }]
|
||||||
|
}
|
||||||
|
} as DiyComponent<PopoverProperty>
|
38
src/components/DiyEditor/components/mobile/Popover/index.vue
Normal file
38
src/components/DiyEditor/components/mobile/Popover/index.vue
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in property.list"
|
||||||
|
:key="index"
|
||||||
|
class="absolute bottom-50% right-50% h-454px w-292px border-1px border-gray border-rounded-4px border-solid bg-white p-1px"
|
||||||
|
:style="{
|
||||||
|
zIndex: 100 + index + (activeIndex === index ? 100 : 0),
|
||||||
|
marginRight: `${-146 - index * 20}px`,
|
||||||
|
marginBottom: `${-227 - index * 20}px`
|
||||||
|
}"
|
||||||
|
@click="handleActive(index)"
|
||||||
|
>
|
||||||
|
<el-image :src="item.imgUrl" fit="contain" class="h-full w-full">
|
||||||
|
<template #error>
|
||||||
|
<div class="h-full w-full flex items-center justify-center">
|
||||||
|
<Icon icon="ep:picture" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-image>
|
||||||
|
<div class="absolute right-1 top-1 text-12px">{{ index + 1 }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { PopoverProperty } from './config'
|
||||||
|
|
||||||
|
/** 弹窗广告 */
|
||||||
|
defineOptions({ name: 'Popover' })
|
||||||
|
// 定义属性
|
||||||
|
defineProps<{ property: PopoverProperty }>()
|
||||||
|
|
||||||
|
// 处理选中
|
||||||
|
const activeIndex = ref(0)
|
||||||
|
const handleActive = (index: number) => {
|
||||||
|
activeIndex.value = index
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
@ -0,0 +1,38 @@
|
|||||||
|
<template>
|
||||||
|
<el-form label-width="80px" :model="formData">
|
||||||
|
<Draggable v-model="formData.list" :empty-item="{ showType: 'once' }">
|
||||||
|
<template #default="{ element, index }">
|
||||||
|
<el-form-item label="图片" :prop="`list[${index}].imgUrl`">
|
||||||
|
<UploadImg v-model="element.imgUrl" height="56px" width="56px" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="跳转链接" :prop="`list[${index}].url`">
|
||||||
|
<AppLinkInput v-model="element.url" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="显示次数" :prop="`list[${index}].showType`">
|
||||||
|
<el-radio-group v-model="element.showType">
|
||||||
|
<el-tooltip content="只显示一次,下次打开时不显示" placement="bottom">
|
||||||
|
<el-radio label="once">一次</el-radio>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip content="每次打开时都会显示" placement="bottom">
|
||||||
|
<el-radio label="always">不限</el-radio>
|
||||||
|
</el-tooltip>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
</Draggable>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { PopoverProperty } from './config'
|
||||||
|
import { usePropertyForm } from '@/components/DiyEditor/util'
|
||||||
|
|
||||||
|
// 弹窗广告属性面板
|
||||||
|
defineOptions({ name: 'PopoverProperty' })
|
||||||
|
|
||||||
|
const props = defineProps<{ modelValue: PopoverProperty }>()
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
const { formData } = usePropertyForm(props.modelValue, emit)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
@ -1,81 +1,64 @@
|
|||||||
<template>
|
<template>
|
||||||
<ComponentContainerProperty v-model="formData.style">
|
<ComponentContainerProperty v-model="formData.style">
|
||||||
<el-text tag="p"> 搜索热词 </el-text>
|
|
||||||
<el-text type="info" size="small"> 拖动左侧的小圆点可以调整热词顺序 </el-text>
|
|
||||||
|
|
||||||
<!-- 表单 -->
|
<!-- 表单 -->
|
||||||
<el-form label-width="80px" :model="formData" class="m-t-8px">
|
<el-form label-width="80px" :model="formData" class="m-t-8px">
|
||||||
<div v-if="formData.hotKeywords.length">
|
<el-card header="搜索热词" class="property-group" shadow="never">
|
||||||
<VueDraggable
|
<Draggable v-model="formData.hotKeywords" :empty-item="''">
|
||||||
:list="formData.hotKeywords"
|
<template #default="{ index }">
|
||||||
item-key="index"
|
<el-input v-model="formData.hotKeywords[index]" placeholder="请输入热词" />
|
||||||
handle=".drag-icon"
|
|
||||||
:forceFallback="true"
|
|
||||||
:animation="200"
|
|
||||||
>
|
|
||||||
<template #item="{ index }">
|
|
||||||
<div class="mb-4px flex flex-row items-center gap-4px rounded bg-gray-100 p-8px">
|
|
||||||
<Icon icon="ic:round-drag-indicator" class="drag-icon cursor-move" />
|
|
||||||
<el-input v-model="formData.hotKeywords[index]" placeholder="请输入热词" />
|
|
||||||
<Icon icon="ep:delete" class="text-red-500" @click="deleteHotWord(index)" />
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</VueDraggable>
|
</Draggable>
|
||||||
</div>
|
</el-card>
|
||||||
<el-form-item label-width="0">
|
<el-card header="搜索样式" class="property-group" shadow="never">
|
||||||
<el-button @click="handleAddHotWord" type="primary" plain class="m-t-8px w-full">
|
<el-form-item label="框体样式">
|
||||||
添加热词
|
<el-radio-group v-model="formData!.borderRadius">
|
||||||
</el-button>
|
<el-tooltip content="方形" placement="top">
|
||||||
</el-form-item>
|
<el-radio-button :label="0">
|
||||||
<el-form-item label="框体样式">
|
<Icon icon="tabler:input-search" />
|
||||||
<el-radio-group v-model="formData!.borderRadius">
|
</el-radio-button>
|
||||||
<el-tooltip content="方形" placement="top">
|
</el-tooltip>
|
||||||
<el-radio-button :label="0">
|
<el-tooltip content="圆形" placement="top">
|
||||||
<Icon icon="tabler:input-search" />
|
<el-radio-button :label="10">
|
||||||
</el-radio-button>
|
<Icon icon="iconoir:input-search" />
|
||||||
</el-tooltip>
|
</el-radio-button>
|
||||||
<el-tooltip content="圆形" placement="top">
|
</el-tooltip>
|
||||||
<el-radio-button :label="10">
|
</el-radio-group>
|
||||||
<Icon icon="iconoir:input-search" />
|
</el-form-item>
|
||||||
</el-radio-button>
|
<el-form-item label="提示文字" prop="placeholder">
|
||||||
</el-tooltip>
|
<el-input v-model="formData.placeholder" />
|
||||||
</el-radio-group>
|
</el-form-item>
|
||||||
</el-form-item>
|
<el-form-item label="文本位置" prop="placeholderPosition">
|
||||||
<el-form-item label="提示文字" prop="placeholder">
|
<el-radio-group v-model="formData!.placeholderPosition">
|
||||||
<el-input v-model="formData.placeholder" />
|
<el-tooltip content="居左" placement="top">
|
||||||
</el-form-item>
|
<el-radio-button label="left">
|
||||||
<el-form-item label="文本位置" prop="placeholderPosition">
|
<Icon icon="ant-design:align-left-outlined" />
|
||||||
<el-radio-group v-model="formData!.placeholderPosition">
|
</el-radio-button>
|
||||||
<el-tooltip content="居左" placement="top">
|
</el-tooltip>
|
||||||
<el-radio-button label="left">
|
<el-tooltip content="居中" placement="top">
|
||||||
<Icon icon="ant-design:align-left-outlined" />
|
<el-radio-button label="center">
|
||||||
</el-radio-button>
|
<Icon icon="ant-design:align-center-outlined" />
|
||||||
</el-tooltip>
|
</el-radio-button>
|
||||||
<el-tooltip content="居中" placement="top">
|
</el-tooltip>
|
||||||
<el-radio-button label="center">
|
</el-radio-group>
|
||||||
<Icon icon="ant-design:align-center-outlined" />
|
</el-form-item>
|
||||||
</el-radio-button>
|
<el-form-item label="扫一扫" prop="showScan">
|
||||||
</el-tooltip>
|
<el-switch v-model="formData!.showScan" />
|
||||||
</el-radio-group>
|
</el-form-item>
|
||||||
</el-form-item>
|
<el-form-item label="框体高度" prop="height">
|
||||||
<el-form-item label="扫一扫" prop="showScan">
|
<el-slider v-model="formData!.height" :max="50" :min="28" show-input input-size="small" />
|
||||||
<el-switch v-model="formData!.showScan" />
|
</el-form-item>
|
||||||
</el-form-item>
|
<el-form-item label="框体颜色" prop="backgroundColor">
|
||||||
<el-form-item label="框体高度" prop="height">
|
<ColorInput v-model="formData.backgroundColor" />
|
||||||
<el-slider v-model="formData!.height" :max="50" :min="28" show-input input-size="small" />
|
</el-form-item>
|
||||||
</el-form-item>
|
<el-form-item class="lef" label="文本颜色" prop="textColor">
|
||||||
<el-form-item label="框体颜色" prop="backgroundColor">
|
<ColorInput v-model="formData.textColor" />
|
||||||
<ColorInput v-model="formData.backgroundColor" />
|
</el-form-item>
|
||||||
</el-form-item>
|
</el-card>
|
||||||
<el-form-item class="lef" label="文本颜色" prop="textColor">
|
|
||||||
<ColorInput v-model="formData.textColor" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
</el-form>
|
||||||
</ComponentContainerProperty>
|
</ComponentContainerProperty>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import VueDraggable from 'vuedraggable'
|
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { usePropertyForm } from '@/components/DiyEditor/util'
|
||||||
import { SearchProperty } from '@/components/DiyEditor/components/mobile/SearchBar/config'
|
import { SearchProperty } from '@/components/DiyEditor/components/mobile/SearchBar/config'
|
||||||
|
|
||||||
@ -85,15 +68,6 @@ defineOptions({ name: 'SearchProperty' })
|
|||||||
const props = defineProps<{ modelValue: SearchProperty }>()
|
const props = defineProps<{ modelValue: SearchProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const { formData } = usePropertyForm(props.modelValue, emit)
|
||||||
|
|
||||||
/* 添加热词 */
|
|
||||||
const handleAddHotWord = () => {
|
|
||||||
formData.value.hotKeywords.push('')
|
|
||||||
}
|
|
||||||
/* 删除热词 */
|
|
||||||
const deleteHotWord = (index: number) => {
|
|
||||||
formData.value.hotKeywords.splice(index, 1)
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
|
@ -12,7 +12,13 @@
|
|||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<div v-for="(item, index) in property.items" :key="index" class="tab-bar-item">
|
<div v-for="(item, index) in property.items" :key="index" class="tab-bar-item">
|
||||||
<img :src="index === 0 ? item.activeIconUrl : item.iconUrl" alt="" />
|
<el-image :src="index === 0 ? item.activeIconUrl : item.iconUrl">
|
||||||
|
<template #error>
|
||||||
|
<div class="h-full w-full flex items-center justify-center">
|
||||||
|
<Icon icon="ep:picture" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-image>
|
||||||
<span :style="{ color: index === 0 ? property.style.activeColor : property.style.color }">
|
<span :style="{ color: index === 0 ? property.style.activeColor : property.style.color }">
|
||||||
{{ item.text }}
|
{{ item.text }}
|
||||||
</span>
|
</span>
|
||||||
@ -48,7 +54,8 @@ defineProps<{ property: TabBarProperty }>()
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
img {
|
:deep(img),
|
||||||
|
.el-icon {
|
||||||
width: 26px;
|
width: 26px;
|
||||||
height: 26px;
|
height: 26px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
@ -42,80 +42,44 @@
|
|||||||
|
|
||||||
<el-text tag="p">图标设置</el-text>
|
<el-text tag="p">图标设置</el-text>
|
||||||
<el-text type="info" size="small"> 拖动左上角的小圆点可对其排序, 图标建议尺寸 44*44 </el-text>
|
<el-text type="info" size="small"> 拖动左上角的小圆点可对其排序, 图标建议尺寸 44*44 </el-text>
|
||||||
<draggable
|
<Draggable v-model="formData.items" :limit="5">
|
||||||
:list="formData!.items"
|
<template #default="{ element }">
|
||||||
item-key="index"
|
<div class="m-b-8px flex items-center justify-around">
|
||||||
:forceFallback="true"
|
<div class="flex flex-col items-center justify-between">
|
||||||
:animation="200"
|
<UploadImg
|
||||||
handle=".drag-icon"
|
v-model="element.iconUrl"
|
||||||
class="m-t-8px"
|
width="40px"
|
||||||
>
|
height="40px"
|
||||||
<template #item="{ element, index }">
|
:show-delete="false"
|
||||||
<div class="mb-4px flex flex-row gap-4px rounded bg-gray-100 p-8px">
|
:show-btn-text="false"
|
||||||
<div class="flex flex-col items-start justify-between">
|
|
||||||
<Icon icon="ic:round-drag-indicator" class="drag-icon cursor-move" />
|
|
||||||
<Icon
|
|
||||||
icon="ep:delete"
|
|
||||||
class="cursor-pointer text-red-5"
|
|
||||||
@click="handleDeleteItem(index)"
|
|
||||||
v-if="formData.items.length > 1"
|
|
||||||
/>
|
/>
|
||||||
|
<el-text size="small">未选中</el-text>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full flex flex-col">
|
<div>
|
||||||
<div class="m-b-8px flex items-center justify-around">
|
<UploadImg
|
||||||
<div class="flex flex-col items-center justify-between">
|
v-model="element.activeIconUrl"
|
||||||
<UploadImg
|
width="40px"
|
||||||
v-model="element.iconUrl"
|
height="40px"
|
||||||
width="40px"
|
:show-delete="false"
|
||||||
height="40px"
|
:show-btn-text="false"
|
||||||
:show-delete="false"
|
/>
|
||||||
:show-btn-text="false"
|
<el-text>已选中</el-text>
|
||||||
/>
|
|
||||||
<el-text size="small">默认图片</el-text>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<UploadImg
|
|
||||||
v-model="element.activeIconUrl"
|
|
||||||
width="40px"
|
|
||||||
height="40px"
|
|
||||||
:show-delete="false"
|
|
||||||
:show-btn-text="false"
|
|
||||||
/>
|
|
||||||
<el-text>选中图片</el-text>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<el-form-item prop="text" label-width="0" class="m-b-8px!">
|
|
||||||
<el-input v-model="element.text" placeholder="请输入文字" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item prop="url" label-width="0" class="m-b-0!">
|
|
||||||
<AppLinkInput v-model="element.url" />
|
|
||||||
</el-form-item>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<el-form-item prop="text" label="文字" label-width="48px" class="m-b-8px!">
|
||||||
|
<el-input v-model="element.text" placeholder="请输入文字" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="url" label="链接" label-width="48px" class="m-b-0!">
|
||||||
|
<AppLinkInput v-model="element.url" />
|
||||||
|
</el-form-item>
|
||||||
</template>
|
</template>
|
||||||
</draggable>
|
</Draggable>
|
||||||
|
|
||||||
<el-form-item label-width="0">
|
|
||||||
<!-- 添加导航按钮 -->
|
|
||||||
<el-tooltip content="最多添加5个">
|
|
||||||
<el-button
|
|
||||||
@click="handleAddItem"
|
|
||||||
class="m-b-16px w-full"
|
|
||||||
type="primary"
|
|
||||||
plain
|
|
||||||
:disabled="formData!.items.length >= 5"
|
|
||||||
>
|
|
||||||
添加导航
|
|
||||||
</el-button>
|
|
||||||
</el-tooltip>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import draggable from 'vuedraggable' //拖拽组件
|
import { TabBarProperty, THEME_LIST } from './config'
|
||||||
import { TabBarItemProperty, TabBarProperty, THEME_LIST } from './config'
|
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { usePropertyForm } from '@/components/DiyEditor/util'
|
||||||
// 底部导航栏
|
// 底部导航栏
|
||||||
defineOptions({ name: 'TabBarProperty' })
|
defineOptions({ name: 'TabBarProperty' })
|
||||||
@ -124,15 +88,6 @@ const props = defineProps<{ modelValue: TabBarProperty }>()
|
|||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const { formData } = usePropertyForm(props.modelValue, emit)
|
||||||
|
|
||||||
/** 添加导航项 */
|
|
||||||
const handleAddItem = () => {
|
|
||||||
formData?.value?.items?.push({} as TabBarItemProperty)
|
|
||||||
}
|
|
||||||
/** 删除导航项 */
|
|
||||||
const handleDeleteItem = (index: number) => {
|
|
||||||
formData?.value?.items?.splice(index, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 要的主题
|
// 要的主题
|
||||||
const handleThemeChange = () => {
|
const handleThemeChange = () => {
|
||||||
const theme = THEME_LIST.find((theme) => theme.id === formData.value.theme)
|
const theme = THEME_LIST.find((theme) => theme.id === formData.value.theme)
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
import { DiyComponent } from '@/components/DiyEditor/util'
|
import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
|
||||||
|
|
||||||
/** 标题栏属性 */
|
/** 标题栏属性 */
|
||||||
export interface TitleBarProperty {
|
export interface TitleBarProperty {
|
||||||
|
// 背景图
|
||||||
|
bgImgUrl: string
|
||||||
|
// 偏移
|
||||||
|
marginLeft: number
|
||||||
|
// 显示位置
|
||||||
|
textAlign: 'left' | 'center'
|
||||||
// 主标题
|
// 主标题
|
||||||
title: string
|
title: string
|
||||||
// 副标题
|
// 副标题
|
||||||
@ -12,18 +18,12 @@ export interface TitleBarProperty {
|
|||||||
descriptionSize: number
|
descriptionSize: number
|
||||||
// 标题粗细
|
// 标题粗细
|
||||||
titleWeight: number
|
titleWeight: number
|
||||||
// 显示位置
|
|
||||||
position: 'left' | 'center'
|
|
||||||
// 描述粗细
|
// 描述粗细
|
||||||
descriptionWeight: number
|
descriptionWeight: number
|
||||||
// 标题颜色
|
// 标题颜色
|
||||||
titleColor: string
|
titleColor: string
|
||||||
// 描述颜色
|
// 描述颜色
|
||||||
descriptionColor: string
|
descriptionColor: string
|
||||||
// 背景颜色
|
|
||||||
backgroundColor: string
|
|
||||||
// 底部分割线
|
|
||||||
showBottomBorder: false
|
|
||||||
// 查看更多
|
// 查看更多
|
||||||
more: {
|
more: {
|
||||||
// 是否显示查看更多
|
// 是否显示查看更多
|
||||||
@ -35,6 +35,8 @@ export interface TitleBarProperty {
|
|||||||
// 链接
|
// 链接
|
||||||
url: string
|
url: string
|
||||||
}
|
}
|
||||||
|
// 组件样式
|
||||||
|
style: ComponentStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
// 定义组件
|
// 定义组件
|
||||||
@ -48,18 +50,20 @@ export const component = {
|
|||||||
titleSize: 16,
|
titleSize: 16,
|
||||||
descriptionSize: 12,
|
descriptionSize: 12,
|
||||||
titleWeight: 400,
|
titleWeight: 400,
|
||||||
position: 'left',
|
textAlign: 'left',
|
||||||
descriptionWeight: 200,
|
descriptionWeight: 200,
|
||||||
titleColor: 'rgba(50, 50, 51, 10)',
|
titleColor: 'rgba(50, 50, 51, 10)',
|
||||||
descriptionColor: 'rgba(150, 151, 153, 10)',
|
descriptionColor: 'rgba(150, 151, 153, 10)',
|
||||||
backgroundColor: 'rgba(255, 255, 255, 10)',
|
|
||||||
showBottomBorder: false,
|
|
||||||
more: {
|
more: {
|
||||||
//查看更多
|
//查看更多
|
||||||
show: false,
|
show: false,
|
||||||
type: 'icon',
|
type: 'icon',
|
||||||
text: '查看更多',
|
text: '查看更多',
|
||||||
url: ''
|
url: ''
|
||||||
}
|
},
|
||||||
|
style: {
|
||||||
|
bgType: 'color',
|
||||||
|
bgColor: '#fff'
|
||||||
|
} as ComponentStyle
|
||||||
}
|
}
|
||||||
} as DiyComponent<TitleBarProperty>
|
} as DiyComponent<TitleBarProperty>
|
||||||
|
@ -1,19 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div class="title-bar">
|
||||||
class="title-bar"
|
<el-image v-if="property.bgImgUrl" :src="property.bgImgUrl" fit="cover" class="w-full" />
|
||||||
:style="{
|
<div class="absolute left-0 top-0 w-full">
|
||||||
background: property.backgroundColor,
|
|
||||||
borderBottom: property.showBottomBorder ? '1px solid #F9F9F9' : '1px solid #fff'
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<!-- 标题 -->
|
<!-- 标题 -->
|
||||||
<div
|
<div
|
||||||
:style="{
|
:style="{
|
||||||
fontSize: `${property.titleSize}px`,
|
fontSize: `${property.titleSize}px`,
|
||||||
fontWeight: property.titleWeight,
|
fontWeight: property.titleWeight,
|
||||||
color: property.titleColor,
|
color: property.titleColor,
|
||||||
textAlign: property.position
|
textAlign: property.textAlign
|
||||||
}"
|
}"
|
||||||
v-if="property.title"
|
v-if="property.title"
|
||||||
>
|
>
|
||||||
@ -25,7 +20,7 @@
|
|||||||
fontSize: `${property.descriptionSize}px`,
|
fontSize: `${property.descriptionSize}px`,
|
||||||
fontWeight: property.descriptionWeight,
|
fontWeight: property.descriptionWeight,
|
||||||
color: property.descriptionColor,
|
color: property.descriptionColor,
|
||||||
textAlign: property.position
|
textAlign: property.textAlign
|
||||||
}"
|
}"
|
||||||
class="m-t-8px"
|
class="m-t-8px"
|
||||||
v-if="property.description"
|
v-if="property.description"
|
||||||
@ -38,10 +33,10 @@
|
|||||||
class="more"
|
class="more"
|
||||||
v-show="property.more.show"
|
v-show="property.more.show"
|
||||||
:style="{
|
:style="{
|
||||||
color: property.more.type === 'text' ? '#38f' : ''
|
color: property.descriptionColor
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
{{ property.more.type === 'icon' ? '' : property.more.text }}
|
<span v-if="property.more.type !== 'icon'"> {{ property.more.text }} </span>
|
||||||
<Icon icon="ep:arrow-right" v-if="property.more.type !== 'text'" />
|
<Icon icon="ep:arrow-right" v-if="property.more.type !== 'text'" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -59,8 +54,6 @@ defineProps<{ property: TitleBarProperty }>()
|
|||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 20px;
|
min-height: 20px;
|
||||||
padding: 8px 16px;
|
|
||||||
border: 2px solid #fff;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
/* 更多 */
|
/* 更多 */
|
||||||
|
@ -1,102 +1,108 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="title-bar">
|
<ComponentContainerProperty v-model="formData.style">
|
||||||
<el-form label-width="85px" :model="formData" :rules="rules">
|
<el-form label-width="85px" :model="formData" :rules="rules">
|
||||||
<el-form-item label="主标题" prop="title">
|
<el-card header="风格" class="property-group" shadow="never">
|
||||||
<el-input
|
<el-form-item label="背景图片" prop="bgImgUrl">
|
||||||
v-model="formData.title"
|
<UploadImg v-model="formData.bgImgUrl" width="100%" height="40px">
|
||||||
placeholder="请输入主标题"
|
<template #tip>建议尺寸 750*80</template>
|
||||||
show-word-limit
|
</UploadImg>
|
||||||
maxlength="20"
|
</el-form-item>
|
||||||
/>
|
<el-form-item label="标题位置" prop="textAlign">
|
||||||
</el-form-item>
|
<el-radio-group v-model="formData!.textAlign">
|
||||||
<el-form-item label="副标题" prop="description">
|
<el-tooltip content="居左" placement="top">
|
||||||
<el-input
|
<el-radio-button label="left">
|
||||||
type="textarea"
|
<Icon icon="ant-design:align-left-outlined" />
|
||||||
v-model="formData.description"
|
</el-radio-button>
|
||||||
placeholder="请输入副标题"
|
</el-tooltip>
|
||||||
maxlength="50"
|
<el-tooltip content="居中" placement="top">
|
||||||
show-word-limit
|
<el-radio-button label="center">
|
||||||
/>
|
<Icon icon="ant-design:align-center-outlined" />
|
||||||
</el-form-item>
|
</el-radio-button>
|
||||||
<el-form-item label="显示位置" prop="position">
|
</el-tooltip>
|
||||||
<el-radio-group v-model="formData!.position">
|
|
||||||
<el-tooltip content="居左" placement="top">
|
|
||||||
<el-radio-button label="left">
|
|
||||||
<Icon icon="ant-design:align-left-outlined" />
|
|
||||||
</el-radio-button>
|
|
||||||
</el-tooltip>
|
|
||||||
<el-tooltip content="居中" placement="top">
|
|
||||||
<el-radio-button label="center">
|
|
||||||
<Icon icon="ant-design:align-center-outlined" />
|
|
||||||
</el-radio-button>
|
|
||||||
</el-tooltip>
|
|
||||||
</el-radio-group>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="标题大小" prop="titleSize">
|
|
||||||
<el-slider v-model="formData.titleSize" :max="60" :min="10" show-input input-size="small" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="副标题大小" prop="descriptionSize">
|
|
||||||
<el-slider
|
|
||||||
v-model="formData.descriptionSize"
|
|
||||||
:max="60"
|
|
||||||
:min="10"
|
|
||||||
show-input
|
|
||||||
input-size="small"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="标题粗细" prop="titleWeight">
|
|
||||||
<el-slider
|
|
||||||
v-model="formData.titleWeight"
|
|
||||||
:min="100"
|
|
||||||
:max="900"
|
|
||||||
:step="100"
|
|
||||||
show-input
|
|
||||||
input-size="small"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="副标题粗细" prop="descriptionWeight">
|
|
||||||
<el-slider
|
|
||||||
v-model="formData.descriptionWeight"
|
|
||||||
:min="100"
|
|
||||||
:max="900"
|
|
||||||
:step="100"
|
|
||||||
show-input
|
|
||||||
input-size="small"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="标题颜色" prop="titleColor">
|
|
||||||
<ColorInput v-model="formData.titleColor" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="副标题颜色" prop="descriptionColor">
|
|
||||||
<ColorInput v-model="formData.descriptionColor" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="背景颜色" prop="backgroundColor">
|
|
||||||
<ColorInput v-model="formData.backgroundColor" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="底部分割线" prop="showBottomBorder">
|
|
||||||
<el-switch v-model="formData!.showBottomBorder" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="查看更多" prop="more.show">
|
|
||||||
<el-checkbox v-model="formData.more.show" />
|
|
||||||
</el-form-item>
|
|
||||||
<!-- 更多样式选择 -->
|
|
||||||
<template v-if="formData.more.show">
|
|
||||||
<el-form-item label="样式" prop="more.type">
|
|
||||||
<el-radio-group v-model="formData.more.type">
|
|
||||||
<el-radio label="text">文字</el-radio>
|
|
||||||
<el-radio label="icon">图标</el-radio>
|
|
||||||
<el-radio label="all">文字+图标</el-radio>
|
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="更多文字" prop="more.text" v-show="formData.more.type !== 'icon'">
|
</el-card>
|
||||||
<el-input v-model="formData.more.text" />
|
<el-card header="主标题" class="property-group" shadow="never">
|
||||||
|
<el-form-item label="文字" prop="title" label-width="40px">
|
||||||
|
<InputWithColor
|
||||||
|
v-model="formData.title"
|
||||||
|
v-model:color="formData.titleColor"
|
||||||
|
show-word-limit
|
||||||
|
maxlength="20"
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="跳转链接" prop="more.url">
|
<el-form-item label="大小" prop="titleSize" label-width="40px">
|
||||||
<AppLinkInput v-model="formData.more.url" />
|
<el-slider
|
||||||
|
v-model="formData.titleSize"
|
||||||
|
:max="60"
|
||||||
|
:min="10"
|
||||||
|
show-input
|
||||||
|
input-size="small"
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</template>
|
<el-form-item label="粗细" prop="titleWeight" label-width="40px">
|
||||||
|
<el-slider
|
||||||
|
v-model="formData.titleWeight"
|
||||||
|
:min="100"
|
||||||
|
:max="900"
|
||||||
|
:step="100"
|
||||||
|
show-input
|
||||||
|
input-size="small"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-card>
|
||||||
|
<el-card header="副标题" class="property-group" shadow="never">
|
||||||
|
<el-form-item label="文字" prop="description" label-width="40px">
|
||||||
|
<InputWithColor
|
||||||
|
v-model="formData.description"
|
||||||
|
v-model:color="formData.descriptionColor"
|
||||||
|
show-word-limit
|
||||||
|
maxlength="50"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="大小" prop="descriptionSize" label-width="40px">
|
||||||
|
<el-slider
|
||||||
|
v-model="formData.descriptionSize"
|
||||||
|
:max="60"
|
||||||
|
:min="10"
|
||||||
|
show-input
|
||||||
|
input-size="small"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="粗细" prop="descriptionWeight" label-width="40px">
|
||||||
|
<el-slider
|
||||||
|
v-model="formData.descriptionWeight"
|
||||||
|
:min="100"
|
||||||
|
:max="900"
|
||||||
|
:step="100"
|
||||||
|
show-input
|
||||||
|
input-size="small"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-card>
|
||||||
|
<el-card header="查看更多" class="property-group" shadow="never">
|
||||||
|
<el-form-item label="是否显示" prop="more.show">
|
||||||
|
<el-checkbox v-model="formData.more.show" />
|
||||||
|
</el-form-item>
|
||||||
|
<!-- 更多按钮的 样式选择 -->
|
||||||
|
<template v-if="formData.more.show">
|
||||||
|
<el-form-item label="样式" prop="more.type">
|
||||||
|
<el-radio-group v-model="formData.more.type">
|
||||||
|
<el-radio label="text">文字</el-radio>
|
||||||
|
<el-radio label="icon">图标</el-radio>
|
||||||
|
<el-radio label="all">文字+图标</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="更多文字" prop="more.text" v-show="formData.more.type !== 'icon'">
|
||||||
|
<el-input v-model="formData.more.text" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="跳转链接" prop="more.url">
|
||||||
|
<AppLinkInput v-model="formData.more.url" />
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
</el-card>
|
||||||
</el-form>
|
</el-form>
|
||||||
</section>
|
</ComponentContainerProperty>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { TitleBarProperty } from './config'
|
import { TitleBarProperty } from './config'
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
<Icon icon="system-uicons:reset-alt" :size="24" />
|
<Icon icon="system-uicons:reset-alt" :size="24" />
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip content="预览">
|
<el-tooltip content="预览" v-if="previewUrl">
|
||||||
<el-button @click="handlePreview">
|
<el-button @click="handlePreview">
|
||||||
<Icon icon="ep:view" :size="24" />
|
<Icon icon="ep:view" :size="24" />
|
||||||
</el-button>
|
</el-button>
|
||||||
@ -47,6 +47,18 @@
|
|||||||
class="cursor-pointer!"
|
class="cursor-pointer!"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 绝对定位的组件:例如 弹窗、浮动按钮等 -->
|
||||||
|
<div
|
||||||
|
v-for="(component, index) in pageComponents"
|
||||||
|
:key="index"
|
||||||
|
@click="handleComponentSelected(component, index)"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
v-if="component.position === 'fixed' && selectedComponent?.uid === component.uid"
|
||||||
|
:is="component.id"
|
||||||
|
:property="component.property"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<!-- 手机页面编辑区域 -->
|
<!-- 手机页面编辑区域 -->
|
||||||
<el-scrollbar
|
<el-scrollbar
|
||||||
height="100%"
|
height="100%"
|
||||||
@ -70,6 +82,7 @@
|
|||||||
>
|
>
|
||||||
<template #item="{ element, index }">
|
<template #item="{ element, index }">
|
||||||
<ComponentContainer
|
<ComponentContainer
|
||||||
|
v-if="!element.position || element.position === 'center'"
|
||||||
:component="element"
|
:component="element"
|
||||||
:active="selectedComponentIndex === index"
|
:active="selectedComponentIndex === index"
|
||||||
:can-move-up="index > 0"
|
:can-move-up="index > 0"
|
||||||
@ -91,6 +104,33 @@
|
|||||||
@click="handleTabBarSelected"
|
@click="handleTabBarSelected"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 固定布局的组件 操作按钮区 -->
|
||||||
|
<div class="fixed-component-action-group">
|
||||||
|
<el-tag
|
||||||
|
v-if="showPageConfig"
|
||||||
|
size="large"
|
||||||
|
:effect="selectedComponent?.uid === pageConfigComponent.uid ? 'dark' : 'plain'"
|
||||||
|
:type="selectedComponent?.uid === pageConfigComponent.uid ? '' : 'info'"
|
||||||
|
@click="handleComponentSelected(pageConfigComponent)"
|
||||||
|
>
|
||||||
|
<Icon :icon="pageConfigComponent.icon" :size="12" />
|
||||||
|
<span>{{ pageConfigComponent.name }}</span>
|
||||||
|
</el-tag>
|
||||||
|
<template v-for="(component, index) in pageComponents" :key="index">
|
||||||
|
<el-tag
|
||||||
|
v-if="component.position === 'fixed'"
|
||||||
|
size="large"
|
||||||
|
closable
|
||||||
|
:effect="selectedComponent?.uid === component.uid ? 'dark' : 'plain'"
|
||||||
|
:type="selectedComponent?.uid === component.uid ? '' : 'info'"
|
||||||
|
@click="handleComponentSelected(component)"
|
||||||
|
@close="handleDeleteComponent(index)"
|
||||||
|
>
|
||||||
|
<Icon :icon="component.icon" :size="12" />
|
||||||
|
<span>{{ component.name }}</span>
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 右侧属性面板 -->
|
<!-- 右侧属性面板 -->
|
||||||
<el-aside class="editor-right" width="350px" v-if="selectedComponent?.property">
|
<el-aside class="editor-right" width="350px" v-if="selectedComponent?.property">
|
||||||
@ -102,8 +142,8 @@
|
|||||||
<!-- 组件名称 -->
|
<!-- 组件名称 -->
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex items-center gap-8px">
|
<div class="flex items-center gap-8px">
|
||||||
<Icon :icon="selectedComponent.icon" color="gray" />
|
<Icon :icon="selectedComponent?.icon" color="gray" />
|
||||||
<span>{{ selectedComponent.name }}</span>
|
<span>{{ selectedComponent?.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<el-scrollbar
|
<el-scrollbar
|
||||||
@ -111,7 +151,8 @@
|
|||||||
view-class="p-[var(--el-card-padding)] p-b-[calc(var(--el-card-padding)+var(--el-card-padding))] property"
|
view-class="p-[var(--el-card-padding)] p-b-[calc(var(--el-card-padding)+var(--el-card-padding))] property"
|
||||||
>
|
>
|
||||||
<component
|
<component
|
||||||
:is="selectedComponent.id + 'Property'"
|
:key="selectedComponent?.uid || selectedComponent?.id"
|
||||||
|
:is="selectedComponent?.id + 'Property'"
|
||||||
v-model="selectedComponent.property"
|
v-model="selectedComponent.property"
|
||||||
/>
|
/>
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
@ -119,6 +160,19 @@
|
|||||||
</el-aside>
|
</el-aside>
|
||||||
</el-container>
|
</el-container>
|
||||||
</el-container>
|
</el-container>
|
||||||
|
<!-- 预览弹框 -->
|
||||||
|
<Dialog v-model="previewDialogVisible" title="预览" width="700">
|
||||||
|
<div class="flex justify-around">
|
||||||
|
<IFrame
|
||||||
|
class="w-375px border-4px border-rounded-8px border-solid p-2px h-667px!"
|
||||||
|
:src="previewUrl"
|
||||||
|
/>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<el-text>手机扫码预览</el-text>
|
||||||
|
<Qrcode :text="previewUrl" logo="/logo.gif" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
// 注册所有的组件
|
// 注册所有的组件
|
||||||
@ -137,12 +191,12 @@ import { component as TAB_BAR_COMPONENT } from './components/mobile/TabBar/confi
|
|||||||
import { isString } from '@/utils/is'
|
import { isString } from '@/utils/is'
|
||||||
import { DiyComponent, DiyComponentLibrary, PageConfig } from '@/components/DiyEditor/util'
|
import { DiyComponent, DiyComponentLibrary, PageConfig } from '@/components/DiyEditor/util'
|
||||||
import { componentConfigs } from '@/components/DiyEditor/components/mobile'
|
import { componentConfigs } from '@/components/DiyEditor/components/mobile'
|
||||||
|
import { array, oneOfType } from 'vue-types'
|
||||||
|
import { propTypes } from '@/utils/propTypes'
|
||||||
|
|
||||||
/** 页面装修详情页 */
|
/** 页面装修详情页 */
|
||||||
defineOptions({ name: 'DiyPageDetail' })
|
defineOptions({ name: 'DiyPageDetail' })
|
||||||
|
|
||||||
// 消息弹窗
|
|
||||||
const message = useMessage()
|
|
||||||
// 左侧组件库
|
// 左侧组件库
|
||||||
const componentLibrary = ref()
|
const componentLibrary = ref()
|
||||||
// 页面设置组件
|
// 页面设置组件
|
||||||
@ -159,20 +213,22 @@ const selectedComponentIndex = ref<number>(-1)
|
|||||||
// 组件列表
|
// 组件列表
|
||||||
const pageComponents = ref<DiyComponent<any>[]>([])
|
const pageComponents = ref<DiyComponent<any>[]>([])
|
||||||
// 定义属性
|
// 定义属性
|
||||||
const props = defineProps<{
|
const props = defineProps({
|
||||||
// 页面配置,支持Json字符串
|
// 页面配置,支持Json字符串
|
||||||
modelValue: string | PageConfig
|
modelValue: oneOfType<string | PageConfig>([String, Object]).isRequired,
|
||||||
// 标题
|
// 标题
|
||||||
title: string
|
title: propTypes.string.def(''),
|
||||||
// 组件库
|
// 组件库
|
||||||
libs: DiyComponentLibrary[]
|
libs: array<DiyComponentLibrary>(),
|
||||||
// 是否显示顶部导航栏
|
// 是否显示顶部导航栏
|
||||||
showNavigationBar: boolean
|
showNavigationBar: propTypes.bool.def(true),
|
||||||
// 是否显示底部导航菜单
|
// 是否显示底部导航菜单
|
||||||
showTabBar: boolean
|
showTabBar: propTypes.bool.def(false),
|
||||||
// 是否显示页面配置
|
// 是否显示页面配置
|
||||||
showPageConfig: boolean
|
showPageConfig: propTypes.bool.def(true),
|
||||||
}>()
|
// 预览地址:提供了预览地址,才会显示预览按钮
|
||||||
|
previewUrl: propTypes.string.def('')
|
||||||
|
})
|
||||||
|
|
||||||
// 监听传入的页面配置
|
// 监听传入的页面配置
|
||||||
watch(
|
watch(
|
||||||
@ -281,6 +337,7 @@ const handleMoveComponent = (index: number, direction: number) => {
|
|||||||
/** 复制组件 */
|
/** 复制组件 */
|
||||||
const handleCopyComponent = (index: number) => {
|
const handleCopyComponent = (index: number) => {
|
||||||
const component = cloneDeep(pageComponents.value[index])
|
const component = cloneDeep(pageComponents.value[index])
|
||||||
|
component.uid = new Date().getTime()
|
||||||
pageComponents.value.splice(index + 1, 0, component)
|
pageComponents.value.splice(index + 1, 0, component)
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -306,14 +363,18 @@ const handleDeleteComponent = (index: number) => {
|
|||||||
|
|
||||||
// 工具栏操作
|
// 工具栏操作
|
||||||
const emits = defineEmits(['reset', 'preview', 'save', 'update:modelValue'])
|
const emits = defineEmits(['reset', 'preview', 'save', 'update:modelValue'])
|
||||||
|
|
||||||
|
// 注入无感刷新页面函数
|
||||||
|
const reload = inject<() => void>('reload')
|
||||||
// 重置
|
// 重置
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
message.warning('开发中~')
|
if (reload) reload()
|
||||||
emits('reset')
|
emits('reset')
|
||||||
}
|
}
|
||||||
// 预览
|
// 预览
|
||||||
|
const previewDialogVisible = ref(false)
|
||||||
const handlePreview = () => {
|
const handlePreview = () => {
|
||||||
message.warning('开发中~')
|
previewDialogVisible.value = true
|
||||||
emits('preview')
|
emits('preview')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -464,6 +525,31 @@ $toolbar-height: 42px;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 固定布局的组件 操作按钮区 */
|
||||||
|
.fixed-component-action-group {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
:deep(.el-tag) {
|
||||||
|
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
border: none;
|
||||||
|
.el-tag__content {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
.el-icon {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,12 +5,23 @@ import { TabBarProperty } from '@/components/DiyEditor/components/mobile/TabBar/
|
|||||||
|
|
||||||
// 页面装修组件
|
// 页面装修组件
|
||||||
export interface DiyComponent<T> {
|
export interface DiyComponent<T> {
|
||||||
|
// 用于区分同一种组件的不同实例
|
||||||
|
uid: number
|
||||||
// 组件唯一标识
|
// 组件唯一标识
|
||||||
id: string
|
id: string
|
||||||
// 组件名称
|
// 组件名称
|
||||||
name: string
|
name: string
|
||||||
// 组件图标
|
// 组件图标
|
||||||
icon: string
|
icon: string
|
||||||
|
/*
|
||||||
|
组件位置:
|
||||||
|
top: 固定于手机顶部,例如 顶部的导航栏
|
||||||
|
bottom: 固定于手机底部,例如 底部的菜单导航栏
|
||||||
|
center: 位于手机中心,每个组件占一行,顺序向下排列
|
||||||
|
空:同center
|
||||||
|
fixed: 由组件自己决定位置,如弹窗位于手机中心、浮动按钮一般位于手机右下角
|
||||||
|
*/
|
||||||
|
position: 'top' | 'bottom' | 'center' | '' | 'fixed'
|
||||||
// 组件属性
|
// 组件属性
|
||||||
property: T
|
property: T
|
||||||
}
|
}
|
||||||
@ -100,12 +111,28 @@ export const PAGE_LIBS = [
|
|||||||
{
|
{
|
||||||
name: '基础组件',
|
name: '基础组件',
|
||||||
extended: true,
|
extended: true,
|
||||||
components: ['SearchBar', 'NoticeBar', 'MenuSwiper', 'MenuGrid', 'MenuList']
|
components: [
|
||||||
|
'SearchBar',
|
||||||
|
'NoticeBar',
|
||||||
|
'MenuSwiper',
|
||||||
|
'MenuGrid',
|
||||||
|
'MenuList',
|
||||||
|
'Popover',
|
||||||
|
'FloatingActionButton'
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '图文组件',
|
name: '图文组件',
|
||||||
extended: true,
|
extended: true,
|
||||||
components: ['ImageBar', 'Carousel', 'TitleBar', 'VideoPlayer', 'Divider', 'MagicCube']
|
components: [
|
||||||
|
'ImageBar',
|
||||||
|
'Carousel',
|
||||||
|
'TitleBar',
|
||||||
|
'VideoPlayer',
|
||||||
|
'Divider',
|
||||||
|
'MagicCube',
|
||||||
|
'HotZone'
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{ name: '商品组件', extended: true, components: ['ProductCard', 'ProductList'] },
|
{ name: '商品组件', extended: true, components: ['ProductCard', 'ProductList'] },
|
||||||
{
|
{
|
||||||
|
77
src/components/Draggable/index.vue
Normal file
77
src/components/Draggable/index.vue
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<template>
|
||||||
|
<el-text type="info" size="small"> 拖动左上角的小圆点可对其排序 </el-text>
|
||||||
|
<VueDraggable
|
||||||
|
:list="formData"
|
||||||
|
:force-fallback="true"
|
||||||
|
:animation="200"
|
||||||
|
handle=".drag-icon"
|
||||||
|
class="m-t-8px"
|
||||||
|
item-key="index"
|
||||||
|
>
|
||||||
|
<template #item="{ element, index }">
|
||||||
|
<div
|
||||||
|
class="mb-4px flex flex-col gap-4px border border-gray-2 border-rounded rounded border-solid p-8px"
|
||||||
|
>
|
||||||
|
<!-- 操作按钮区 -->
|
||||||
|
<div class="m--8px m-b-4px flex flex-row items-center justify-between bg-gray-1 p-8px">
|
||||||
|
<el-tooltip content="拖动排序">
|
||||||
|
<Icon icon="ic:round-drag-indicator" class="drag-icon cursor-move" />
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip content="删除">
|
||||||
|
<Icon
|
||||||
|
icon="ep:delete"
|
||||||
|
class="cursor-pointer text-red-5"
|
||||||
|
v-if="formData.length > 1"
|
||||||
|
@click="handleDelete(index)"
|
||||||
|
/>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
<!-- 内容区 -->
|
||||||
|
<slot :element="element" :index="index"></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</VueDraggable>
|
||||||
|
<el-tooltip :disabled="limit < 1" :content="`最多添加${limit}个`">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
plain
|
||||||
|
class="m-t-4px w-full"
|
||||||
|
:disabled="limit > 0 && formData.length >= limit"
|
||||||
|
@click="handleAdd"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:plus" /><span>添加</span>
|
||||||
|
</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// 拖拽组件
|
||||||
|
import VueDraggable from 'vuedraggable'
|
||||||
|
import { usePropertyForm } from '@/components/DiyEditor/util'
|
||||||
|
import { any, array } from 'vue-types'
|
||||||
|
import { propTypes } from '@/utils/propTypes'
|
||||||
|
import { cloneDeep } from 'lodash-es'
|
||||||
|
|
||||||
|
// 拖拽组件封装
|
||||||
|
defineOptions({ name: 'Draggable' })
|
||||||
|
|
||||||
|
// 定义属性
|
||||||
|
const props = defineProps({
|
||||||
|
// 绑定值
|
||||||
|
modelValue: array<any>().isRequired,
|
||||||
|
// 空的元素:点击添加按钮时,创建元素并添加到列表;默认为空对象
|
||||||
|
emptyItem: any<unknown>().def({}),
|
||||||
|
// 数量限制:默认为0,表示不限制
|
||||||
|
limit: propTypes.number.def(0)
|
||||||
|
})
|
||||||
|
// 定义事件
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
const { formData } = usePropertyForm(props.modelValue, emit)
|
||||||
|
|
||||||
|
// 处理添加
|
||||||
|
const handleAdd = () => formData.value.push(cloneDeep(props.emptyItem || {}))
|
||||||
|
// 处理删除
|
||||||
|
const handleDelete = (index: number) => formData.value.splice(index, 1)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
@ -20,6 +20,17 @@ const getCaches = computed((): string[] => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const tagsView = computed(() => appStore.getTagsView)
|
const tagsView = computed(() => appStore.getTagsView)
|
||||||
|
|
||||||
|
//region 无感刷新
|
||||||
|
const routerAlive = ref(true)
|
||||||
|
// 无感刷新,防止出现页面闪烁白屏
|
||||||
|
const reload = () => {
|
||||||
|
routerAlive.value = false
|
||||||
|
nextTick(() => (routerAlive.value = true))
|
||||||
|
}
|
||||||
|
// 为组件后代提供刷新方法
|
||||||
|
provide('reload', reload)
|
||||||
|
//endregion
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -49,7 +60,7 @@ const tagsView = computed(() => appStore.getTagsView)
|
|||||||
}
|
}
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<router-view>
|
<router-view v-if="routerAlive">
|
||||||
<template #default="{ Component, route }">
|
<template #default="{ Component, route }">
|
||||||
<keep-alive :include="getCaches">
|
<keep-alive :include="getCaches">
|
||||||
<component :is="Component" :key="route.fullPath" />
|
<component :is="Component" :key="route.fullPath" />
|
||||||
|
@ -2,10 +2,12 @@
|
|||||||
<div>
|
<div>
|
||||||
<el-card shadow="never">
|
<el-card shadow="never">
|
||||||
<el-skeleton :loading="loading" animated>
|
<el-skeleton :loading="loading" animated>
|
||||||
<el-row :gutter="20" justify="space-between">
|
<el-row :gutter="16" justify="space-between">
|
||||||
<el-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
|
<el-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<img :src="avatar" alt="" class="mr-20px h-70px w-70px rounded-[50%]" />
|
<el-avatar :src="avatar" :size="70" class="mr-16px">
|
||||||
|
<img src="@/assets/imgs/avatar.gif" alt="" />
|
||||||
|
</el-avatar>
|
||||||
<div>
|
<div>
|
||||||
<div class="text-20px">
|
<div class="text-20px">
|
||||||
{{ t('workplace.welcome') }} {{ username }} {{ t('workplace.happyDay') }}
|
{{ t('workplace.welcome') }} {{ username }} {{ t('workplace.happyDay') }}
|
||||||
@ -19,7 +21,7 @@
|
|||||||
<el-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
|
<el-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
|
||||||
<div class="h-70px flex items-center justify-end lt-sm:mt-10px">
|
<div class="h-70px flex items-center justify-end lt-sm:mt-10px">
|
||||||
<div class="px-8px text-right">
|
<div class="px-8px text-right">
|
||||||
<div class="mb-20px text-14px text-gray-400">{{ t('workplace.project') }}</div>
|
<div class="mb-16px text-14px text-gray-400">{{ t('workplace.project') }}</div>
|
||||||
<CountTo
|
<CountTo
|
||||||
class="text-20px"
|
class="text-20px"
|
||||||
:start-val="0"
|
:start-val="0"
|
||||||
@ -29,7 +31,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<el-divider direction="vertical" />
|
<el-divider direction="vertical" />
|
||||||
<div class="px-8px text-right">
|
<div class="px-8px text-right">
|
||||||
<div class="mb-20px text-14px text-gray-400">{{ t('workplace.toDo') }}</div>
|
<div class="mb-16px text-14px text-gray-400">{{ t('workplace.toDo') }}</div>
|
||||||
<CountTo
|
<CountTo
|
||||||
class="text-20px"
|
class="text-20px"
|
||||||
:start-val="0"
|
:start-val="0"
|
||||||
@ -39,7 +41,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<el-divider direction="vertical" border-style="dashed" />
|
<el-divider direction="vertical" border-style="dashed" />
|
||||||
<div class="px-8px text-right">
|
<div class="px-8px text-right">
|
||||||
<div class="mb-20px text-14px text-gray-400">{{ t('workplace.access') }}</div>
|
<div class="mb-16px text-14px text-gray-400">{{ t('workplace.access') }}</div>
|
||||||
<CountTo
|
<CountTo
|
||||||
class="text-20px"
|
class="text-20px"
|
||||||
:start-val="0"
|
:start-val="0"
|
||||||
@ -54,8 +56,8 @@
|
|||||||
</el-card>
|
</el-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-row class="mt-5px" :gutter="20" justify="space-between">
|
<el-row class="mt-8px" :gutter="8" justify="space-between">
|
||||||
<el-col :xl="16" :lg="16" :md="24" :sm="24" :xs="24" class="mb-10px">
|
<el-col :xl="16" :lg="16" :md="24" :sm="24" :xs="24" class="mb-8px">
|
||||||
<el-card shadow="never">
|
<el-card shadow="never">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="h-3 flex justify-between">
|
<div class="h-3 flex justify-between">
|
||||||
@ -76,11 +78,11 @@
|
|||||||
>
|
>
|
||||||
<el-card shadow="hover">
|
<el-card shadow="hover">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<Icon :icon="item.icon" :size="25" class="mr-10px" />
|
<Icon :icon="item.icon" :size="25" class="mr-8px" />
|
||||||
<span class="text-16px">{{ item.name }}</span>
|
<span class="text-16px">{{ item.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-15px text-14px text-gray-400">{{ t(item.message) }}</div>
|
<div class="mt-16px text-14px text-gray-400">{{ t(item.message) }}</div>
|
||||||
<div class="mt-20px flex justify-between text-12px text-gray-400">
|
<div class="mt-16px flex justify-between text-12px text-gray-400">
|
||||||
<span>{{ item.personal }}</span>
|
<span>{{ item.personal }}</span>
|
||||||
<span>{{ formatTime(item.time, 'yyyy-MM-dd') }}</span>
|
<span>{{ formatTime(item.time, 'yyyy-MM-dd') }}</span>
|
||||||
</div>
|
</div>
|
||||||
@ -90,18 +92,18 @@
|
|||||||
</el-skeleton>
|
</el-skeleton>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<el-card shadow="never" class="mt-5px">
|
<el-card shadow="never" class="mt-8px">
|
||||||
<el-skeleton :loading="loading" animated>
|
<el-skeleton :loading="loading" animated>
|
||||||
<el-row :gutter="20" justify="space-between">
|
<el-row :gutter="20" justify="space-between">
|
||||||
<el-col :xl="10" :lg="10" :md="24" :sm="24" :xs="24">
|
<el-col :xl="10" :lg="10" :md="24" :sm="24" :xs="24">
|
||||||
<el-card shadow="hover" class="mb-10px">
|
<el-card shadow="hover" class="mb-8px">
|
||||||
<el-skeleton :loading="loading" animated>
|
<el-skeleton :loading="loading" animated>
|
||||||
<Echart :options="pieOptionsData" :height="280" />
|
<Echart :options="pieOptionsData" :height="280" />
|
||||||
</el-skeleton>
|
</el-skeleton>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :xl="14" :lg="14" :md="24" :sm="24" :xs="24">
|
<el-col :xl="14" :lg="14" :md="24" :sm="24" :xs="24">
|
||||||
<el-card shadow="hover" class="mb-10px">
|
<el-card shadow="hover" class="mb-8px">
|
||||||
<el-skeleton :loading="loading" animated>
|
<el-skeleton :loading="loading" animated>
|
||||||
<Echart :options="barOptionsData" :height="280" />
|
<Echart :options="barOptionsData" :height="280" />
|
||||||
</el-skeleton>
|
</el-skeleton>
|
||||||
@ -111,7 +113,7 @@
|
|||||||
</el-skeleton>
|
</el-skeleton>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :xl="8" :lg="8" :md="24" :sm="24" :xs="24" class="mb-10px">
|
<el-col :xl="8" :lg="8" :md="24" :sm="24" :xs="24" class="mb-8px">
|
||||||
<el-card shadow="never">
|
<el-card shadow="never">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="h-3 flex justify-between">
|
<div class="h-3 flex justify-between">
|
||||||
@ -120,9 +122,9 @@
|
|||||||
</template>
|
</template>
|
||||||
<el-skeleton :loading="loading" animated>
|
<el-skeleton :loading="loading" animated>
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col v-for="item in shortcut" :key="`team-${item.name}`" :span="8" class="mb-10px">
|
<el-col v-for="item in shortcut" :key="`team-${item.name}`" :span="8" class="mb-8px">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<Icon :icon="item.icon" class="mr-10px" />
|
<Icon :icon="item.icon" class="mr-8px" />
|
||||||
<el-link type="default" :underline="false" @click="setWatermark(item.name)">
|
<el-link type="default" :underline="false" @click="setWatermark(item.name)">
|
||||||
{{ item.name }}
|
{{ item.name }}
|
||||||
</el-link>
|
</el-link>
|
||||||
@ -131,7 +133,7 @@
|
|||||||
</el-row>
|
</el-row>
|
||||||
</el-skeleton>
|
</el-skeleton>
|
||||||
</el-card>
|
</el-card>
|
||||||
<el-card shadow="never" class="mt-10px">
|
<el-card shadow="never" class="mt-8px">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="h-3 flex justify-between">
|
<div class="h-3 flex justify-between">
|
||||||
<span>{{ t('workplace.notice') }}</span>
|
<span>{{ t('workplace.notice') }}</span>
|
||||||
@ -141,14 +143,16 @@
|
|||||||
<el-skeleton :loading="loading" animated>
|
<el-skeleton :loading="loading" animated>
|
||||||
<div v-for="(item, index) in notice" :key="`dynamics-${index}`">
|
<div v-for="(item, index) in notice" :key="`dynamics-${index}`">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<img :src="avatar" alt="" class="mr-20px h-35px w-35px rounded-[50%]" />
|
<el-avatar :src="avatar" :size="35" class="mr-16px">
|
||||||
|
<img src="@/assets/imgs/avatar.gif" alt="" />
|
||||||
|
</el-avatar>
|
||||||
<div>
|
<div>
|
||||||
<div class="text-14px">
|
<div class="text-14px">
|
||||||
<Highlight :keys="item.keys.map((v) => t(v))">
|
<Highlight :keys="item.keys.map((v) => t(v))">
|
||||||
{{ item.type }} : {{ item.title }}
|
{{ item.type }} : {{ item.title }}
|
||||||
</Highlight>
|
</Highlight>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-15px text-12px text-gray-400">
|
<div class="mt-16px text-12px text-gray-400">
|
||||||
{{ formatTime(item.date, 'yyyy-MM-dd') }}
|
{{ formatTime(item.date, 'yyyy-MM-dd') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -167,7 +171,6 @@ import { formatTime } from '@/utils'
|
|||||||
|
|
||||||
import { useUserStore } from '@/store/modules/user'
|
import { useUserStore } from '@/store/modules/user'
|
||||||
import { useWatermark } from '@/hooks/web/useWatermark'
|
import { useWatermark } from '@/hooks/web/useWatermark'
|
||||||
import avatarImg from '@/assets/imgs/avatar.gif'
|
|
||||||
import type { WorkplaceTotal, Project, Notice, Shortcut } from './types'
|
import type { WorkplaceTotal, Project, Notice, Shortcut } from './types'
|
||||||
import { pieOptions, barOptions } from './echarts-data'
|
import { pieOptions, barOptions } from './echarts-data'
|
||||||
|
|
||||||
@ -177,7 +180,7 @@ const { t } = useI18n()
|
|||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const { setWatermark } = useWatermark()
|
const { setWatermark } = useWatermark()
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const avatar = userStore.getUser.avatar ? userStore.getUser.avatar : avatarImg
|
const avatar = userStore.getUser.avatar
|
||||||
const username = userStore.getUser.nickname
|
const username = userStore.getUser.nickname
|
||||||
const pieOptionsData = reactive<EChartsOption>(pieOptions) as EChartsOption
|
const pieOptionsData = reactive<EChartsOption>(pieOptions) as EChartsOption
|
||||||
// 获取统计数
|
// 获取统计数
|
||||||
|
@ -10,7 +10,10 @@
|
|||||||
class="h-20 w-20% flex flex-col cursor-pointer items-center justify-center gap-2"
|
class="h-20 w-20% flex flex-col cursor-pointer items-center justify-center gap-2"
|
||||||
@click="handleMenuClick(menu.routerName)"
|
@click="handleMenuClick(menu.routerName)"
|
||||||
>
|
>
|
||||||
<div :class="menu.bgColor" class="rounded p-3 text-white">
|
<div
|
||||||
|
:class="menu.bgColor"
|
||||||
|
class="h-48px w-48px flex items-center justify-center rounded text-white"
|
||||||
|
>
|
||||||
<Icon :icon="menu.icon" class="text-7.5!" />
|
<Icon :icon="menu.icon" class="text-7.5!" />
|
||||||
</div>
|
</div>
|
||||||
<span>{{ menu.name }}</span>
|
<span>{{ menu.name }}</span>
|
||||||
|
@ -4,9 +4,6 @@
|
|||||||
v-model="formData.property"
|
v-model="formData.property"
|
||||||
:title="formData.name"
|
:title="formData.name"
|
||||||
:libs="PAGE_LIBS"
|
:libs="PAGE_LIBS"
|
||||||
:show-page-config="true"
|
|
||||||
:show-navigation-bar="true"
|
|
||||||
:show-tab-bar="false"
|
|
||||||
@save="submitForm"
|
@save="submitForm"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
@ -7,7 +7,9 @@
|
|||||||
:show-page-config="selectedTemplateItem !== 0"
|
:show-page-config="selectedTemplateItem !== 0"
|
||||||
:show-tab-bar="selectedTemplateItem === 0"
|
:show-tab-bar="selectedTemplateItem === 0"
|
||||||
:show-navigation-bar="selectedTemplateItem !== 0"
|
:show-navigation-bar="selectedTemplateItem !== 0"
|
||||||
|
:preview-url="previewUrl"
|
||||||
@save="submitForm"
|
@save="submitForm"
|
||||||
|
@reset="handleEditorReset"
|
||||||
>
|
>
|
||||||
<template #toolBarLeft>
|
<template #toolBarLeft>
|
||||||
<el-radio-group
|
<el-radio-group
|
||||||
@ -29,6 +31,7 @@ import * as DiyTemplateApi from '@/api/mall/promotion/diy/template'
|
|||||||
import * as DiyPageApi from '@/api/mall/promotion/diy/page'
|
import * as DiyPageApi from '@/api/mall/promotion/diy/page'
|
||||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||||
import { DiyComponentLibrary, PAGE_LIBS } from '@/components/DiyEditor/util'
|
import { DiyComponentLibrary, PAGE_LIBS } from '@/components/DiyEditor/util'
|
||||||
|
import { toNumber } from 'lodash-es'
|
||||||
|
|
||||||
/** 装修模板表单 */
|
/** 装修模板表单 */
|
||||||
defineOptions({ name: 'DiyTemplateDecorate' })
|
defineOptions({ name: 'DiyTemplateDecorate' })
|
||||||
@ -48,6 +51,8 @@ const formData = ref<DiyTemplateApi.DiyTemplatePropertyVO>()
|
|||||||
const formRef = ref() // 表单 Ref
|
const formRef = ref() // 表单 Ref
|
||||||
// 当前编辑的属性
|
// 当前编辑的属性
|
||||||
const currentFormData = ref<DiyTemplateApi.DiyTemplatePropertyVO | DiyPageApi.DiyPageVO>()
|
const currentFormData = ref<DiyTemplateApi.DiyTemplatePropertyVO | DiyPageApi.DiyPageVO>()
|
||||||
|
// 商城H5预览地址
|
||||||
|
const previewUrl = ref('')
|
||||||
|
|
||||||
// 获取详情
|
// 获取详情
|
||||||
const getPageDetail = async (id: any) => {
|
const getPageDetail = async (id: any) => {
|
||||||
@ -55,6 +60,10 @@ const getPageDetail = async (id: any) => {
|
|||||||
try {
|
try {
|
||||||
formData.value = await DiyTemplateApi.getDiyTemplateProperty(id)
|
formData.value = await DiyTemplateApi.getDiyTemplateProperty(id)
|
||||||
currentFormData.value = formData.value
|
currentFormData.value = formData.value
|
||||||
|
|
||||||
|
// 拼接手机预览链接
|
||||||
|
const domain = import.meta.env.VITE_MALL_H5_DOMAIN
|
||||||
|
previewUrl.value = `${domain}/#/pages/index/index?templateId=${formData.value.id}`
|
||||||
} finally {
|
} finally {
|
||||||
formLoading.value = false
|
formLoading.value = false
|
||||||
}
|
}
|
||||||
@ -115,17 +124,43 @@ const resetForm = () => {
|
|||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 重置时记录当前编辑的页面
|
||||||
|
const handleEditorReset = () => storePageIndex()
|
||||||
|
|
||||||
|
//#region 无感刷新
|
||||||
|
// 记录标识
|
||||||
|
const DIY_PAGE_INDEX_KEY = 'diy_page_index'
|
||||||
|
// 1. 记录
|
||||||
|
const storePageIndex = () =>
|
||||||
|
sessionStorage.setItem(DIY_PAGE_INDEX_KEY, `${selectedTemplateItem.value}`)
|
||||||
|
// 2. 恢复
|
||||||
|
const recoverPageIndex = () => {
|
||||||
|
// 恢复重置前的页面,默认是第一个页面
|
||||||
|
const pageIndex = toNumber(sessionStorage.getItem(DIY_PAGE_INDEX_KEY)) || 0
|
||||||
|
// 移除标记
|
||||||
|
sessionStorage.removeItem(DIY_PAGE_INDEX_KEY)
|
||||||
|
// 切换页面
|
||||||
|
if (pageIndex !== selectedTemplateItem.value) {
|
||||||
|
selectedTemplateItem.value = pageIndex
|
||||||
|
handleTemplateItemChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
/** 初始化 **/
|
/** 初始化 **/
|
||||||
const { currentRoute } = useRouter() // 路由
|
const { currentRoute } = useRouter() // 路由
|
||||||
const { delView } = useTagsViewStore() // 视图操作
|
const { delView } = useTagsViewStore() // 视图操作
|
||||||
const route = useRoute()
|
onMounted(async () => {
|
||||||
onMounted(() => {
|
|
||||||
resetForm()
|
resetForm()
|
||||||
if (!route.params.id) {
|
if (!currentRoute.value.params.id) {
|
||||||
message.warning('参数错误,页面编号不能为空!')
|
message.warning('参数错误,页面编号不能为空!')
|
||||||
delView(unref(currentRoute))
|
delView(unref(currentRoute))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
getPageDetail(route.params.id)
|
|
||||||
|
// 查询详情
|
||||||
|
await getPageDetail(currentRoute.value.params.id)
|
||||||
|
// 恢复重置前的页面
|
||||||
|
recoverPageIndex()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
Loading…
Reference in New Issue
Block a user