Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
admin
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
df1c565c
authored
Dec 16, 2023
by
YunaiV
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'dev' of
https://gitee.com/yudaocode/yudao-ui-admin-vue3
into dev
parents
be4750a1
1abff21e
Hide whitespace changes
Inline
Side-by-side
Showing
43 changed files
with
1532 additions
and
699 deletions
+1532
-699
.env.base
+0
-19
.env.dev
+15
-9
.env.local-dev
+10
-10
.env.prod
+5
-2
.env.stage
+4
-1
.env.test
+5
-2
package.json
+7
-8
src/components/AppLinkInput/AppLinkSelectDialog.vue
+25
-16
src/components/AppLinkInput/data.ts
+18
-1
src/components/AppLinkInput/index.vue
+1
-1
src/components/DiyEditor/components/ComponentLibrary.vue
+3
-1
src/components/DiyEditor/components/mobile/Carousel/property.vue
+47
-83
src/components/DiyEditor/components/mobile/FloatingActionButton/config.ts
+36
-0
src/components/DiyEditor/components/mobile/FloatingActionButton/index.vue
+74
-0
src/components/DiyEditor/components/mobile/FloatingActionButton/property.vue
+44
-0
src/components/DiyEditor/components/mobile/HotZone/components/HotZoneEditDialog/controller.ts
+143
-0
src/components/DiyEditor/components/mobile/HotZone/components/HotZoneEditDialog/index.vue
+236
-0
src/components/DiyEditor/components/mobile/HotZone/config.ts
+42
-0
src/components/DiyEditor/components/mobile/HotZone/index.vue
+42
-0
src/components/DiyEditor/components/mobile/HotZone/property.vue
+63
-0
src/components/DiyEditor/components/mobile/MenuGrid/property.vue
+31
-62
src/components/DiyEditor/components/mobile/MenuList/property.vue
+18
-48
src/components/DiyEditor/components/mobile/MenuSwiper/index.vue
+2
-2
src/components/DiyEditor/components/mobile/MenuSwiper/property.vue
+28
-58
src/components/DiyEditor/components/mobile/NoticeBar/config.ts
+9
-2
src/components/DiyEditor/components/mobile/NoticeBar/property.vue
+27
-57
src/components/DiyEditor/components/mobile/Popover/config.ts
+26
-0
src/components/DiyEditor/components/mobile/Popover/index.vue
+38
-0
src/components/DiyEditor/components/mobile/Popover/property.vue
+38
-0
src/components/DiyEditor/components/mobile/SearchBar/property.vue
+51
-77
src/components/DiyEditor/components/mobile/TabBar/index.vue
+9
-2
src/components/DiyEditor/components/mobile/TabBar/property.vue
+28
-73
src/components/DiyEditor/components/mobile/TitleBar/config.ts
+15
-11
src/components/DiyEditor/components/mobile/TitleBar/index.vue
+7
-14
src/components/DiyEditor/components/mobile/TitleBar/property.vue
+98
-92
src/components/DiyEditor/index.vue
+102
-16
src/components/DiyEditor/util.ts
+29
-2
src/components/Draggable/index.vue
+77
-0
src/layout/components/AppView.vue
+12
-1
src/views/Home/Index.vue
+24
-21
src/views/mall/home/components/ShortcutCard.vue
+4
-1
src/views/mall/promotion/diy/page/decorate.vue
+0
-3
src/views/mall/promotion/diy/template/decorate.vue
+39
-4
No files found.
.env.base
deleted
100644 → 0
View file @
be4750a1
# 本地开发环境
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=/
.env.dev
View file @
df1c565c
# 开发环境
# 开发环境
:本地只启动前端项目,依赖开发环境(后端、APP)
NODE_ENV=development
NODE_ENV=development
VITE_DEV=
fals
e
VITE_DEV=
tru
e
# 请求路径
# 请求路径
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
...
@@ -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=
tru
e
VITE_DROP_DEBUGGER=
fals
e
# 是否删除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
.env.
front
→
.env.
local-dev
View file @
df1c565c
# 本地开发环境
# 本地开发环境
:本地启动所有项目(前端、后端、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
...
@@ -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
...
@@ -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
.env.pro
→
.env.pro
d
View file @
df1c565c
# 生产环境
# 生产环境
:只在打包时使用
NODE_ENV=production
NODE_ENV=production
VITE_DEV=false
VITE_DEV=false
...
@@ -28,4 +28,7 @@ VITE_SOURCEMAP=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'
.env.stage
View file @
df1c565c
#
生产环境
#
预发布环境:只在打包时使用
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/'
...
@@ -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'
.env.
static
→
.env.
test
View file @
df1c565c
#
开发环境
#
测试环境:只在打包时使用
NODE_ENV
=
production
NODE_ENV
=
production
VITE_DEV
=
false
VITE_DEV
=
false
...
@@ -28,4 +28,7 @@ VITE_SOURCEMAP=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'
package.json
View file @
df1c565c
...
@@ -6,18 +6,17 @@
...
@@ -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"
,
...
...
src/components/AppLinkInput/AppLinkSelectDialog.vue
View file @
df1c565c
...
@@ -29,10 +29,11 @@
...
@@ -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 @@
...
@@ -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' })
...
@@ -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) => {
...
@@ -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) => {
...
@@ -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) => {
...
@@ -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[]>([])
...
@@ -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>()
...
@@ -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[]>([])
...
@@ -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<{
...
@@ -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
...
...
src/components/AppLinkInput/data.ts
View file @
df1c565c
// 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 = [
...
@@ -243,4 +260,4 @@ export const APP_LINK_GROUP_LIST = [
}
}
]
]
}
}
]
]
as
AppLinkGroup
[]
src/components/AppLinkInput/index.vue
View file @
df1c565c
...
@@ -37,7 +37,7 @@ const emit = defineEmits<{
...
@@ -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
>
src/components/DiyEditor/components/ComponentLibrary.vue
View file @
df1c565c
...
@@ -82,7 +82,9 @@ watch(
...
@@ -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
>
...
...
src/components/DiyEditor/components/mobile/Carousel/property.vue
View file @
df1c565c
...
@@ -39,87 +39,60 @@
...
@@ -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' })
...
@@ -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
>
src/components/DiyEditor/components/mobile/FloatingActionButton/config.ts
0 → 100644
View file @
df1c565c
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
>
src/components/DiyEditor/components/mobile/FloatingActionButton/index.vue
0 → 100644
View file @
df1c565c
<
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
>
src/components/DiyEditor/components/mobile/FloatingActionButton/property.vue
0 → 100644
View file @
df1c565c
<
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
>
src/components/DiyEditor/components/mobile/HotZone/components/HotZoneEditDialog/controller.ts
0 → 100644
View file @
df1c565c
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
}
}
src/components/DiyEditor/components/mobile/HotZone/components/HotZoneEditDialog/index.vue
0 → 100644
View file @
df1c565c
<
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
>
src/components/DiyEditor/components/mobile/HotZone/config.ts
0 → 100644
View file @
df1c565c
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
>
src/components/DiyEditor/components/mobile/HotZone/index.vue
0 → 100644
View file @
df1c565c
<
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
>
src/components/DiyEditor/components/mobile/HotZone/property.vue
0 → 100644
View file @
df1c565c
<
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
:
#409eff
bf
;
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
>
src/components/DiyEditor/components/mobile/MenuGrid/property.vue
View file @
df1c565c
...
@@ -9,72 +9,50 @@
...
@@ -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.title
Color"
/>
<
ColorInput
v-model=
"element.badge.bg
Color"
/>
</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' })
...
@@ -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
>
src/components/DiyEditor/components/mobile/MenuList/property.vue
View file @
df1c565c
...
@@ -5,55 +5,34 @@
...
@@ -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' })
...
@@ -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
>
src/components/DiyEditor/components/mobile/MenuSwiper/index.vue
View file @
df1c565c
...
@@ -28,7 +28,7 @@
...
@@ -28,7 +28,7 @@
<!-- 标题 -->
<!-- 标题 -->
<span
<span
v-if=
"property.layout === 'iconText'"
v-if=
"property.layout === 'iconText'"
class=
"text-1
4
px"
class=
"text-1
2
px"
: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 }>()
...
@@ -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
...
...
src/components/DiyEditor/components/mobile/MenuSwiper/property.vue
View file @
df1c565c
...
@@ -22,63 +22,42 @@
...
@@ -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.title
Color"
/>
<
ColorInput
v-model=
"element.badge.bg
Color"
/>
</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' })
...
@@ -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
>
src/components/DiyEditor/components/mobile/NoticeBar/config.ts
View file @
df1c565c
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 {
...
@@ -10,6 +10,8 @@ export interface NoticeBarProperty {
backgroundColor
:
string
backgroundColor
:
string
// 文字颜色
// 文字颜色
textColor
:
string
textColor
:
string
// 组件样式
style
:
ComponentStyle
}
}
/** 内容属性 */
/** 内容属性 */
...
@@ -34,6 +36,11 @@ export const component = {
...
@@ -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
>
src/components/DiyEditor/components/mobile/NoticeBar/property.vue
View file @
df1c565c
<
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 = {
...
@@ -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
>
src/components/DiyEditor/components/mobile/Popover/config.ts
0 → 100644
View file @
df1c565c
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
>
src/components/DiyEditor/components/mobile/Popover/index.vue
0 → 100644
View file @
df1c565c
<
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
>
src/components/DiyEditor/components/mobile/Popover/property.vue
0 → 100644
View file @
df1c565c
<
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
>
src/components/DiyEditor/components/mobile/SearchBar/property.vue
View file @
df1c565c
<
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' })
...
@@ -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
>
src/components/DiyEditor/components/mobile/TabBar/index.vue
View file @
df1c565c
...
@@ -12,7 +12,13 @@
...
@@ -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 }>()
...
@@ -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
;
...
...
src/components/DiyEditor/components/mobile/TabBar/property.vue
View file @
df1c565c
...
@@ -42,80 +42,44 @@
...
@@ -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 }>()
...
@@ -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
)
...
...
src/components/DiyEditor/components/mobile/TitleBar/config.ts
View file @
df1c565c
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 {
...
@@ -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 {
...
@@ -35,6 +35,8 @@ export interface TitleBarProperty {
// 链接
// 链接
url
:
string
url
:
string
}
}
// 组件样式
style
:
ComponentStyle
}
}
// 定义组件
// 定义组件
...
@@ -48,18 +50,20 @@ export const component = {
...
@@ -48,18 +50,20 @@ export const component = {
titleSize
:
16
,
titleSize
:
16
,
descriptionSize
:
12
,
descriptionSize
:
12
,
titleWeight
:
400
,
titleWeight
:
400
,
positio
n
:
'left'
,
textAlig
n
:
'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
>
src/components/DiyEditor/components/mobile/TitleBar/index.vue
View file @
df1c565c
<
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.
positio
n
textAlign: property.
textAlig
n
}"
}"
v-if="property.title"
v-if="property.title"
>
>
...
@@ -25,7 +20,7 @@
...
@@ -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.
positio
n
textAlign: property.
textAlig
n
}"
}"
class="m-t-8px"
class="m-t-8px"
v-if="property.description"
v-if="property.description"
...
@@ -38,10 +33,10 @@
...
@@ -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 }>()
...
@@ -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
;
/* 更多 */
/* 更多 */
...
...
src/components/DiyEditor/components/mobile/TitleBar/property.vue
View file @
df1c565c
<
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
label=
"大小"
prop=
"titleSize"
label-width=
"40px"
>
<el-slider
v-model=
"formData.titleSize"
:max=
"60"
:min=
"10"
show-input
input-size=
"small"
/>
</el-form-item>
<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-form-item>
<el-form-item
label=
"跳转链接"
prop=
"more.url"
>
</el-card>
<AppLinkInput
v-model=
"formData.more.url"
/>
<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>
</el-form-item>
</
template
>
<!-- 更多按钮的 样式选择 -->
<
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'
...
...
src/components/DiyEditor/index.vue
View file @
df1c565c
...
@@ -15,7 +15,7 @@
...
@@ -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 @@
...
@@ -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 @@
...
@@ -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 @@
...
@@ -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 @@
...
@@ -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 @@
...
@@ -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 @@
...
@@ -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
...
@@ -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)
...
@@ -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) => {
...
@@ -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) => {
...
@@ -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;
...
@@ -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
;
}
}
}
}
}
}
}
}
}
}
...
...
src/components/DiyEditor/util.ts
View file @
df1c565c
...
@@ -5,12 +5,23 @@ import { TabBarProperty } from '@/components/DiyEditor/components/mobile/TabBar/
...
@@ -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 = [
...
@@ -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'
]
},
{
{
...
...
src/components/Draggable/index.vue
0 → 100644
View file @
df1c565c
<
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
>
src/layout/components/AppView.vue
View file @
df1c565c
...
@@ -20,6 +20,17 @@ const getCaches = computed((): string[] => {
...
@@ -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)
...
@@ -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"
/>
...
...
src/views/Home/Index.vue
View file @
df1c565c
...
@@ -2,10 +2,12 @@
...
@@ -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 @@
...
@@ -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-
20
px text-14px text-gray-400"
>
{{
t
(
'workplace.project'
)
}}
</div>
<div
class=
"mb-
16
px 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 @@
...
@@ -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-
20
px text-14px text-gray-400"
>
{{
t
(
'workplace.toDo'
)
}}
</div>
<div
class=
"mb-
16
px 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 @@
...
@@ -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-
20
px text-14px text-gray-400"
>
{{
t
(
'workplace.access'
)
}}
</div>
<div
class=
"mb-
16
px 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 @@
...
@@ -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-
10
px"
>
<el-col
:xl=
"16"
:lg=
"16"
:md=
"24"
:sm=
"24"
:xs=
"24"
class=
"mb-
8
px"
>
<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 @@
...
@@ -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-
10
px"
/>
<Icon
:icon=
"item.icon"
:size=
"25"
class=
"mr-
8
px"
/>
<span
class=
"text-16px"
>
{{ item.name }}
</span>
<span
class=
"text-16px"
>
{{ item.name }}
</span>
</div>
</div>
<div
class=
"mt-1
5
px text-14px text-gray-400"
>
{{ t(item.message) }}
</div>
<div
class=
"mt-1
6
px text-14px text-gray-400"
>
{{ t(item.message) }}
</div>
<div
class=
"mt-
20
px flex justify-between text-12px text-gray-400"
>
<div
class=
"mt-
16
px 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 @@
...
@@ -90,18 +92,18 @@
</el-skeleton>
</el-skeleton>
</el-card>
</el-card>
<el-card
shadow=
"never"
class=
"mt-
5
px"
>
<el-card
shadow=
"never"
class=
"mt-
8
px"
>
<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-
10
px"
>
<el-card
shadow=
"hover"
class=
"mb-
8
px"
>
<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-
10
px"
>
<el-card
shadow=
"hover"
class=
"mb-
8
px"
>
<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 @@
...
@@ -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-
10
px"
>
<el-col
:xl=
"8"
:lg=
"8"
:md=
"24"
:sm=
"24"
:xs=
"24"
class=
"mb-
8
px"
>
<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 @@
...
@@ -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-
10
px"
>
<el-col
v-for=
"item in shortcut"
:key=
"`team-${item.name}`"
:span=
"8"
class=
"mb-
8
px"
>
<div
class=
"flex items-center"
>
<div
class=
"flex items-center"
>
<Icon
:icon=
"item.icon"
class=
"mr-
10
px"
/>
<Icon
:icon=
"item.icon"
class=
"mr-
8
px"
/>
<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 @@
...
@@ -131,7 +133,7 @@
</el-row>
</el-row>
</el-skeleton>
</el-skeleton>
</el-card>
</el-card>
<el-card
shadow=
"never"
class=
"mt-
10
px"
>
<el-card
shadow=
"never"
class=
"mt-
8
px"
>
<
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 @@
...
@@ -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-1
5
px text-12px text-gray-400"
>
<div
class=
"mt-1
6
px 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'
...
@@ -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()
...
@@ -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
// 获取统计数
// 获取统计数
...
...
src/views/mall/home/components/ShortcutCard.vue
View file @
df1c565c
...
@@ -10,7 +10,10 @@
...
@@ -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>
...
...
src/views/mall/promotion/diy/page/decorate.vue
View file @
df1c565c
...
@@ -4,9 +4,6 @@
...
@@ -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
>
...
...
src/views/mall/promotion/diy/template/decorate.vue
View file @
df1c565c
...
@@ -7,7 +7,9 @@
...
@@ -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'
...
@@ -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>()
...
@@ -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) => {
...
@@ -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 = () => {
...
@@ -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
(
!
rout
e
.
params
.
id
)
{
if
(
!
currentRoute
.
valu
e
.
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
>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment