商城新增功能满减送 排行榜 新人礼 商品数据统计
商城手机端版面全面优化
This commit is contained in:
wangchen147 2024-12-30 11:10:15 +08:00
parent 2a16950fbd
commit b87c0990e0
3545 changed files with 52927 additions and 18961 deletions

View File

@ -1,7 +1,5 @@
// Generated by 'unplugin-auto-import'
export {}
declare global {
const ElMessage: typeof import('element-plus/es')['ElMessage']
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
const ElNotification: typeof import('element-plus/es')['ElNotification']
}

14
admin/components.d.ts vendored
View File

@ -11,15 +11,9 @@ declare module '@vue/runtime-core' {
DiyLink: typeof import('./src/components/diy-link/index.vue')['default']
Editor: typeof import('./src/components/editor/index.vue')['default']
ElAlert: typeof import('element-plus/es')['ElAlert']
ElAside: typeof import('element-plus/es')['ElAside']
ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard']
ElCarousel: typeof import('element-plus/es')['ElCarousel']
ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem']
ElCascader: typeof import('element-plus/es')['ElCascader']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
ElCol: typeof import('element-plus/es')['ElCol']
@ -34,8 +28,6 @@ declare module '@vue/runtime-core' {
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDrawer: typeof import('element-plus/es')['ElDrawer']
ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
ElEmpty: typeof import('element-plus/es')['ElEmpty']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
@ -46,19 +38,14 @@ declare module '@vue/runtime-core' {
ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
ElLink: typeof import('element-plus/es')['ElLink']
ElMain: typeof import('element-plus/es')['ElMain']
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElOption: typeof import('element-plus/es')['ElOption']
ElOptionGroup: typeof import('element-plus/es')['ElOptionGroup']
ElPageHeader: typeof import('element-plus/es')['ElPageHeader']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElPopover: typeof import('element-plus/es')['ElPopover']
ElProgress: typeof import('element-plus/es')['ElProgress']
ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRate: typeof import('element-plus/es')['ElRate']
ElResult: typeof import('element-plus/es')['ElResult']
ElRow: typeof import('element-plus/es')['ElRow']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
@ -67,7 +54,6 @@ declare module '@vue/runtime-core' {
ElStatistic: typeof import('element-plus/es')['ElStatistic']
ElStep: typeof import('element-plus/es')['ElStep']
ElSteps: typeof import('element-plus/es')['ElSteps']
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']

View File

@ -7,7 +7,7 @@
"menuTypeButton": "按钮",
"menuDeleteTips": "删除菜单会删除当前菜单以及该菜单下所有子菜单,是否确认删除?",
"initializeMenu":"重置菜单",
"initializeMenuTipsOne":"重置菜单会将应用或插件的menu目录下的菜单配置文件中菜单配置更新到数据库一般用做开发者修改了menu菜单配置文件后,同步到数据库操作。",
"initializeMenuTipsOne":"重置菜单会将应用或插件的dict目录下的菜单配置文件中菜单配置更新到数据库一般用做开发者修改了dict菜单配置文件后,同步到数据库操作。",
"initializeMenuTipsTwo":"如果用户手动调整过以下菜单项,通常允许进行本项操作,操作会重置为原始菜单。 请谨慎使用!",
"addMenu": "添加菜单",
"updateMenu": "编辑菜单",

View File

@ -28,5 +28,6 @@
"manager": "用户",
"managerPlaceholder": "请选择用户",
"managerTips": "选择或者新增用户作为管理员",
"newAddManager": "新增用户"
}
"newAddManager": "新增用户",
"userDeleteTips": "是否要删除该管理员?"
}

View File

@ -3,7 +3,7 @@
"preview": "预览图",
"copy": "复制",
"clickVisit": "点击访问",
"PCDomainName": "pc域名",
"pCDomainName": "pc域名",
"newInfo": "最新消息",
"isOpen": "是否开启"
}

View File

@ -20,5 +20,6 @@
"customMenu": "自定义菜单",
"wechatTemplate": "模板消息",
"menusEmptyTips": "空菜单,不能保存与发布。",
"reply": "自动回复"
"reply": "自动回复",
"menuUrlErrorTips": "请输入完整的网页链接如https://www.baidu.com"
}

View File

@ -62,6 +62,8 @@
"imageUpload": "图片上传",
"imageSet": "图片设置",
"imageAdsTips": "建议上传尺寸相同的图片推荐尺寸750*350",
"imageAdsSameScreenTips": "开启沉浸式样式,请确保该图片广告组件在页面中位于最顶端;为保证体验,请不要开导航栏;沉浸式样式仅在微信小程序中生效。",
"sameScreen": "沉浸式",
"addImageAd": "添加图片",
"imageUrlTip": "请上传图片",
"imageHeight": "图片高度",
@ -86,6 +88,7 @@
"graphicNavShowStyle": "展示风格",
"graphicNavStyleFixed": "固定显示",
"graphicNavStyleSingleSlide": "单行滑动",
"graphicNavStyleMultiLine": "多行滑动",
"graphicNavStylePageSlide": "分页滑动",
"graphicNavRowCount": "每行数量",
"graphicNavPageCount": "显示方式",
@ -104,6 +107,9 @@
"styleSet": "风格设置",
"titleStyle": "标题样式",
"selectStyle": "风格选择",
"activeCubeBlockBtnText": "按钮文字",
"btnTextItalics": "斜体",
"btnTextNormal": "常规",
"styleLabel": "风格",
"styleShowTips": "风格 1 2 3仅在小程序中展示",
"titleContent": "标题内容",
@ -198,8 +204,17 @@
"carouselSearchShowWayStatic": "正常显示",
"carouselSearchShowWayFixed": "滚动至顶部固定",
"carouselSearchFixedBgColor": "置顶背景",
"carouselSearchStyleSelect": "风格选择",
"carouselSearchSet": "搜索设置",
"carouselSearchSubTitle": "副标题",
"carouselSearchSubTitleStyle": "副标题样式",
"carouselSearchPositionStyle": "定位样式",
"carouselSearchSubTitlePlaceholder": "请输入副标题内容",
"carouselSearchText": "搜索内容",
"carouselSearchTextColor": "文字颜色",
"carouselSearchBgColor": "背景颜色",
"carouselSearchBtnColor": "按钮颜色",
"carouselSearchBtnBgColor": "按钮背景色",
"carouselSearchHotWordSet": "搜索热词",
"carouselSearchHotWordInterval": "显示时间 / 秒",
"carouselSearchHotWordText": "内容",
@ -222,6 +237,7 @@
"carouselSearchSwiperInterval": "切换间隔 / 秒",
"carouselSearchSwiperTips": "建议上传尺寸相同的图片推荐尺寸750*350鼠标拖拽可调整图片顺序",
"carouselSearchTabStyle": "选项卡样式",
"carouselSearchStyle": "搜索框样式",
"noColor": "常规颜色",
"selectColor": "选中颜色",
"fixedNoColor": "下滑常规颜色",
@ -231,6 +247,7 @@
"carouselSearchSwiperStyle": "轮播样式",
"carouselSearchSwiperIndicatorStyle1": "样式1",
"carouselSearchSwiperIndicatorStyle2": "样式2",
"carouselSearchSwiperIndicatorStyle3": "样式3",
"carouselSearchSwiperIndicatorAlign": "显示位置",
"alignLeft": "居左",
"alignCenter": "居中",
@ -256,5 +273,13 @@
"rollTopStatusBarTextColor": "滚动后标题颜色",
"topStatusBarSearchName": "搜索内容",
"topStatusBarSearchNamePlaceholder": "请输入搜索关键词",
"settingTips": "点击查看如何配置"
"settingTips": "点击查看如何配置",
"pictureShowBlockOne": "模块一",
"pictureShowBlockTwo": "模块二",
"subTitleTextColor": "标题颜色",
"pictureShowBgColor": "背景颜色",
"pictureShowBtnText": "按钮文字",
"pictureShowBtnColor": "文字颜色",
"pictureShowBtnBgColor": "背景颜色",
"pictureShowBlockStyle": "模块样式"
}

View File

@ -20,6 +20,7 @@
"titlePlaceholder": "请输入页面名称",
"addDiyPage": "添加页面",
"diyPageDeleteTips": "确定要删除该自定义页面吗?",
"diyPageCopyTips": "确定要复制该自定义页面吗?",
"preview": "预览",
"share": "分享",
"shareSet": "分享设置",

View File

@ -1,4 +1,7 @@
{
"title": "插件名称",
"key": "插件标识"
"key": "插件标识",
"type": "插件类型",
"app": "应用",
"addon": "插件"
}

View File

@ -0,0 +1,291 @@
{
"templatePagePlaceholder": "选择模板",
"templatePageEmpty": "无",
"changeTemplatePageTips": "切换模板后,当前页面内容将被替换且不被保存,请谨慎操作",
"developTitle": "开发环境配置",
"wapDomain": "wap域名WAP_DOMAIN",
"wapDomainPlaceholder": "请输入wap域名",
"pageSet": "页面设置",
"tabEditContent": "内容",
"tabEditStyle": "样式",
"pageStyle": "页面样式",
"pageContent": "页面内容",
"statusBarContent": "导航栏内容",
"statusBarStyle": "导航栏样式",
"statusBarSwitchTips": "此处控制当前页面导航栏是否显示",
"bottomNavContent": "底部导航内容",
"diyPageTitle": "页面名称",
"diyPageTitlePlaceholder": "请输入页面名称",
"pageTitleTips": "页面名称用于后台显示",
"diyTitle": "页面标题",
"diyTitlePlaceholder": "请输入页面标题",
"titleTips": "页面标题用于前台显示",
"pageBgColor": "页面颜色",
"bgUrl": "背景图片",
"bgHeightScale": "高度比例",
"bgHeightScaleTip": "为0时背景高度自适应展示",
"marginSet": "边距设置",
"componentStyleTitle": "组件样式",
"bottomBgColor": "底部背景",
"bottomBgTips": "底部背景包含边距和圆角",
"componentBgColor": "组件背景色",
"componentBgUrl": "组件背景图",
"componentBgAlpha": "透明度",
"bgGradientAngle": "渐变角度",
"topToBottom": "从上到下",
"leftToRight": "从左到右",
"marginTop": "上边距",
"marginBottom": "下边距",
"marginBoth": "左右边距",
"topRounded": "上圆角",
"bottomRounded": "下圆角",
"warmPrompt": "温馨提示",
"leavePageTitleTips": "确定离开此页面?",
"leavePageContentTips": "系统可能不会保存您所做的更改。",
"decorating": "正在装修",
"preview": "保存并预览",
"moveUpComponent": "上移",
"moveDownComponent": "下移",
"copyComponent": "复制",
"delComponent": "删除",
"resetComponent": "重置",
"tabbar": "底部导航",
"tabbarSwitchTips": "此处控制当前页面底部导航菜单是否显示",
"link": "链接地址",
"delComponentTips": "确认要删除当前组件吗?",
"notCopy": "无法复制",
"componentCanOnlyAdd": "组件只能添加",
"piece": "个",
"componentNotMoved": "该组件禁止移动",
"resetComponentTips": "确认要重置组件默认数据吗?",
"image": "图片上传",
"imageUpload": "图片上传",
"imageSet": "图片设置",
"imageAdsTips": "建议上传尺寸相同的图片推荐尺寸750*350",
"imageAdsSameScreenTips": "开启沉浸式样式,请确保该图片广告组件在页面中位于最顶端;为保证体验,请不要开导航栏;沉浸式样式仅在微信小程序中生效。",
"sameScreen": "沉浸式",
"addImageAd": "添加图片",
"imageUrlTip": "请上传图片",
"imageHeight": "图片高度",
"imageHeightPlaceholder": "请输入图片高度",
"imageHeightRegNum": "图片高度格式错误,请输入数字",
"dataSources": "数据来源",
"defaultSources": "默认",
"manualSelectionSources": "手动选择",
"selectPlaceholder": "请选择",
"selected": "已选",
"graphicNavModeTitle": "导航模式",
"layoutMode": "排版模式",
"layoutModeHorizontal": "横排",
"layoutModeVertical": "竖排",
"graphicNavSelectMode": "选择模式",
"graphicNavModeGraphic": "图文导航",
"graphicNavModeImg": "图片导航",
"graphicNavModeText": "文字导航",
"graphicNavImageSet": "图片设置",
"graphicNavImageSize": "图片大小",
"graphicNavAroundRadius": "图片圆角",
"graphicNavShowStyle": "展示风格",
"graphicNavStyleFixed": "固定显示",
"graphicNavStyleSingleSlide": "单行滑动",
"graphicNavStyleMultiLine": "多行滑动",
"graphicNavStylePageSlide": "分页滑动",
"graphicNavRowCount": "每行数量",
"graphicNavPageCount": "显示方式",
"graphicNavSetLabel": "导航设置",
"singleLine": "单行",
"multiline": "多行",
"graphicNavTips": "建议上传尺寸相同的图片推荐尺寸60*60",
"graphicNavTitle": "标题",
"graphicNavTitlePlaceholder": "请输入标题",
"subGraphicNavTitle": "副标题",
"subGraphicNavTitlePlaceholder": "请输入副标题",
"subGraphicNavTitleLink": "副标题链接",
"addGraphicNav": "添加导航",
"blankHeightSet": "高度设置",
"blankHeight": "空白高度",
"styleSet": "风格设置",
"titleStyle": "标题样式",
"selectStyle": "风格选择",
"activeCubeBlockBtnText": "按钮文字",
"btnTextItalics": "斜体",
"btnTextNormal": "常规",
"styleLabel": "风格",
"styleShowTips": "风格 1 2 3仅在小程序中展示",
"titleContent": "标题内容",
"title": "标题名称",
"titlePlaceholder": "请输入标题",
"textAlign": "对齐方式",
"textAlignLeft": "居左",
"textAlignCenter": "居中",
"textSet": "文字设置",
"textFontSize": "文字大小",
"textFontWeight": "文字粗细",
"fontWeightBold": "加粗",
"fontWeightNormal": "常规",
"textColor": "文字颜色",
"subTitleStyle": "副标题样式",
"subTextBgColor": "背景色",
"subTitleContent": "标题内容",
"subTitle": "副标题",
"subTitlePlaceholder": "请输入副标题",
"moreContent": "“更多”按钮内容",
"more": "文字",
"morePlaceholder": "请输入文字",
"moreIsShow": "是否显示",
"memberStyle": "会员样式",
"template": "模板",
"imageGap": "图片间隙",
"rubikCubeStyle": "魔方样式",
"rubikCubeLayout": "魔方布局",
"hotArea": "热区",
"hotAreaSet": "热区设置",
"hotAreaBackground": "热区背景",
"addHotArea": "添加热区",
"clickSet": "点击设置",
"selectedAfterHotArea": "个热区",
"hotAreaManage": "热区管理",
"selectedHotArea": "请选择热区",
"hotAreaLink": "的链接地址",
"addonListSet": "应用设置",
"addonListTips": "应用选择",
"selectAddonTips": "请选择应用",
"addonTitle": "应用名称",
"addonDesc": "应用描述",
"addonIcon": "应用图标",
"selectAddon": "选择应用",
"addAddon": "添加应用",
"show": "显示",
"hidden": "隐藏",
"goodsCategoryTitle": "商品分类",
"customGoods": "手动选择",
"goodsNum": "商品数量",
"selectCategory": "选择分类",
"categoryName": "分类名称",
"categoryImage": "分类图片",
"selectSource": "选择数据源",
"richTextContentSet": "内容设置",
"richTextPlaceholder": "请输入富文本内容",
"activeCubeBlockContent": "板块内容",
"activeCubeTitle": "标题",
"activeCubeTitlePlaceholder": "请输入标题",
"activeCubeSubTitle": "副标题",
"activeCubeSubTitlePlaceholder": "请输入副标题",
"activeCubeButton": "按钮",
"activeCubeButtonPlaceholder": "请输入按钮文字",
"activeCubeButtonColor": "按钮颜色",
"activeListFrameColor": "框体颜色",
"activeCubeSubTitleTextColor": "文字颜色",
"activeCubeSubTitleBgColor": "背景颜色",
"activeCubeAddItem": "添加一个板块",
"activeCubeBlockStyle": "板块样式",
"activeCubeBlockTextFontWeight": "标题粗细",
"noticeStyle": "公告风格",
"noticeType": "类型",
"noticeTypeImg": "图片",
"noticeTypeText": "文字",
"noticeTypeTextPlaceholder": "请输入公告标题",
"noticeTitle": "公告标题",
"addNotice": "添加公告",
"noticeText": "公告内容",
"noticeScrollWay": "滚动方式",
"noticeUpDown": "上下滚动",
"noticeHorizontal": "横向滚动",
"noticeShowType": "点击类型",
"noticeShowPopUp": "弹出公告内容",
"noticeShowLink": "跳转链接",
"dragMouseAdjustOrder": "鼠标拖拽可调整顺序",
"noticePlaceholderText": "请输入公告内容",
"carouselSearchShowPosition": "显示设置",
"carouselSearchOpen": "开启",
"carouselSearchClose": "关闭",
"carouselSearchBgGradient": "背景渐变",
"carouselSearchShowWay": "展示方式",
"carouselSearchShowWayStatic": "正常显示",
"carouselSearchShowWayFixed": "滚动至顶部固定",
"carouselSearchFixedBgColor": "置顶背景",
"carouselSearchStyleSelect": "风格选择",
"carouselSearchSet": "搜索设置",
"carouselSearchSubTitle": "副标题",
"carouselSearchSubTitleStyle": "副标题样式",
"carouselSearchPositionStyle": "定位样式",
"carouselSearchSubTitlePlaceholder": "请输入副标题内容",
"carouselSearchText": "搜索内容",
"carouselSearchTextColor": "文字颜色",
"carouselSearchBgColor": "背景颜色",
"carouselSearchBtnColor": "按钮颜色",
"carouselSearchBtnBgColor": "按钮背景色",
"carouselSearchHotWordSet": "搜索热词",
"carouselSearchHotWordInterval": "显示时间 / 秒",
"carouselSearchHotWordText": "内容",
"carouselSearchHotWordTextPlaceholder": "请输入热词",
"carouselSearchAddHotWordItem": "添加一个热词",
"carouselSearchLogoTips": "建议尺寸70px * 30px",
"carouselSearchTextTips": "搜索内容是默认展示数据,当添加搜索热词时,搜索内容隐藏; 当没有搜索热词时,搜索内容展示",
"carouselSearchPlaceholder": "请输入搜索内容",
"carouselSearchTabSet": "选项卡设置",
"carouselSearchTabControl": "展示开关",
"carouselSearchTabCategoryText": "分类名称",
"carouselSearchTabCategoryTextPlaceholder": "请输入分类名称",
"carouselSearchAddTabItem": "添加一个选项卡",
"selectSourcesDiyPage": "选择微页面",
"selectDiyPagePlaceholder": "请选择微页面",
"diyPageTypeName": "页面类型",
"diyPageForAddon": "所属应用",
"carouselSearchSwiperSet": "轮播图设置",
"carouselSearchSwiperControl": "展示开关",
"carouselSearchSwiperInterval": "切换间隔 / 秒",
"carouselSearchSwiperTips": "建议上传尺寸相同的图片推荐尺寸750*350鼠标拖拽可调整图片顺序",
"carouselSearchTabStyle": "选项卡样式",
"carouselSearchStyle": "搜索框样式",
"noColor": "常规颜色",
"selectColor": "选中颜色",
"fixedNoColor": "下滑常规颜色",
"fixedSelectColor": "下滑选中颜色",
"carouselSearchSwiperIndicatorSet": "指示器设置",
"carouselSearchSwiperIndicatorStyle": "指示器样式",
"carouselSearchSwiperStyle": "轮播样式",
"carouselSearchSwiperIndicatorStyle1": "样式1",
"carouselSearchSwiperIndicatorStyle2": "样式2",
"carouselSearchSwiperIndicatorStyle3": "样式3",
"carouselSearchSwiperIndicatorAlign": "显示位置",
"alignLeft": "居左",
"alignCenter": "居中",
"alignRight": "居右",
"horzLineStyle": "线条风格",
"horzLineStyleSolid": "实线",
"horzLineStyleDashed": "虚线",
"horzLineBorderColor": "线条颜色",
"horzLineBorderWidth": "线条宽度",
"floatBtnBtton": "按钮位置",
"floatBtnOffset": "上下偏移",
"floatBtnImageSet": "图片设置",
"floatBtnImageSize": "图片大小",
"floatBtnAroundRadius": "图片圆角",
"floatBtnImageSuggest": "建议上传正方形图片",
"topStatusBarImg": "图片",
"topStatusBarNav": "导航栏",
"topStatusBarNavTips": "此处控制当前页面导航栏是否显示",
"topStatusBarImgTips": "宽度自适应最大150px高度28px",
"topStatusBarTextColor": "标题颜色",
"topStatusBarBgColor": "头部颜色",
"rollTopStatusBarBgColor": "滚动后头部颜色",
"rollTopStatusBarTextColor": "滚动后标题颜色",
"topStatusBarSearchName": "搜索内容",
"topStatusBarSearchNamePlaceholder": "请输入搜索关键词",
"settingTips": "点击查看如何配置",
"pictureShowBlockOne": "模块一",
"pictureShowBlockTwo": "模块二",
"subTitleTextColor": "标题颜色",
"pictureShowBgColor": "背景颜色",
"pictureShowBtnText": "按钮文字",
"pictureShowBtnColor": "文字颜色",
"pictureShowBtnBgColor": "背景颜色",
"pictureShowBlockStyle": "模块样式",
"defaultValue": "默认值",
"defaultValuePlaceholder": "请输入默认值",
"formPlaceholder": "提示语",
"formPlaceholderTips": "请输入提示语",
"isRequired": "是否必填"
}

View File

@ -0,0 +1,34 @@
{
"title": "表单名称",
"typeName": "表单类型",
"forAddon": "所属应用",
"forAddonPlaceholder": "请选择所属应用",
"addFormTips": "创建新表单",
"formTypePlaceholder": "请选择表单类型",
"nameMax": "名称不能超过12个字符",
"status": "状态",
"updateTime": "更新时间",
"statusOn": "启用",
"statusOff": "禁用",
"all": "全部",
"wapUrl": "wap链接",
"weappUrl": "小程序链接",
"shareLink": "分享链接",
"copy": "复制",
"copySuccess": "复制成功",
"titlePlaceholder": "请输入表单名称",
"addDiyForm": "添加表单",
"diyFormDeleteTips": "确定要删除该自定义表单吗?",
"diyFormCopyTips": "确定要复制该自定义表单吗?",
"preview": "预览",
"share": "分享",
"shareSet": "分享设置",
"sharePage": "分享表单",
"wechat": "微信公众号",
"weapp": "微信小程序",
"shareTitle": "分享标题",
"shareTitlePlaceholder": "请输入分享标题",
"shareDesc": "分享描述",
"shareDescPlaceholder": "请输入分享描述",
"shareImageUrl": "分享图片"
}

View File

@ -1,48 +1,50 @@
{
"orderNo":"订单编号",
"orderStatus":"订单状态",
"orderNoPlaceholder":"请输入订单编号",
"createTime":"创建时间",
"rechargeMoney":"充值金额",
"totalTransfered":"累计提现(元)",
"totalCashOuting":"提现中(元)",
"transfered":"累计提现",
"cashOuting":"提现中",
"orderMoney":"订单金额",
"member":"买家",
"orderFromName":"订单来源",
"payTypeName":"支付方式",
"startDate":"开始时间",
"endDate":"结束时间",
"namePlaceholder":"请选择",
"applyTime":"申请时间",
"cashOutStatus":"提现状态",
"actualTransferAmount":"实际转账金额",
"cashOutCommission":"提现手续费",
"applicationForWithdrawalAmount":"申请提现金额",
"cashOutMethod":"提现方式",
"cashOutAccountType":"会员账户",
"memberInfo":"会员信息",
"toBeReviewed":"待审核",
"toBeTransferred":"待转账",
"transferred":"已转账",
"turnDown":"拒绝",
"transfer": "转账",
"detail": "详情",
"auditFailure": "审核失败",
"successfulAudit": "审核成功",
"rejectionAudit": "拒绝审核",
"reasonsRefusal": "拒绝理由",
"reasonsRefusalPlaceholder": "请输入拒绝理由",
"isTransfer": "是否确认转账",
"nickname":"会员名称",
"headimg":"会员头像",
"cashOutDetail":"提现详情",
"cashOutMoney": "转账金额",
"orderNo": "订单编号",
"orderStatus": "订单状态",
"orderNoPlaceholder": "请输入订单编号",
"createTime": "创建时间",
"rechargeMoney": "充值金额",
"totalTransfered": "累计提现(元)",
"totalCashOuting": "提现中(元)",
"transfered": "累计提现",
"cashOuting": "提现中",
"orderMoney": "订单金额",
"member": "买家",
"orderFromName": "订单来源",
"payTypeName": "支付方式",
"startDate": "开始时间",
"endDate": "结束时间",
"namePlaceholder": "请选择",
"applyTime": "申请时间",
"cashOutStatus": "提现状态",
"actualTransferAmount": "实际转账金额",
"cashOutCommission": "提现手续费",
"applicationForWithdrawalAmount": "申请提现金额",
"cashOutMethod": "提现方式",
"cashOutAccountType": "会员账户",
"memberInfo": "会员信息",
"toBeReviewed": "待审核",
"toBeTransferred": "待转账",
"transferred": "已转账",
"turnDown": "拒绝",
"transfer": "转账",
"detail": "详情",
"auditFailure": "审核失败",
"successfulAudit": "审核成功",
"rejectionAudit": "拒绝审核",
"reasonsRefusal": "拒绝理由",
"reasonsRefusalPlaceholder": "请输入拒绝理由",
"isTransfer": "是否确认转账",
"nickname": "会员名称",
"headimg": "会员头像",
"cashOutDetail": "提现详情",
"cashOutMoney": "转账金额",
"auditTime": "审核时间",
"transferTime": "转账时间",
"memberInfoPlaceholder":"请输入会员名称/会员昵称/手机号",
"memberInfoPlaceholder": "请输入会员名称/会员昵称/手机号",
"cashOutNumber": "提现单号",
"cashOutNumberPlaceholder": "请输入提现单号"
"cashOutNumberPlaceholder": "请输入提现单号",
"alipayAccount": "支付宝账号",
"bankName": "银行名称",
"bankAccount": "银行卡号"
}

View File

@ -6,5 +6,11 @@
"createTime": "申请时间",
"refundNoPlaceholder": "请输入退款编号",
"startDate": "开始时间",
"endDate": "结束时间"
"endDate": "结束时间",
"outTradeNo": "交易流水号",
"refundTypeName": "退款方式",
"statusName": "退款状态",
"transfer": "转账",
"transferType": "转账方式",
"voucher": "凭证"
}

View File

@ -4,9 +4,12 @@
"isUse": "是否启用",
"signPeriod": "签到周期",
"signPeriodTip": "请输入签到周期",
"signPeriodLimitTips": "签到周期格式错误",
"signPeriodMustZeroTips": "签到周期必须大于0",
"calendarSign": "日历签到",
"periodSign": "周期签到",
"daySignAward": "日签奖励",
"daySignAwardPlaceholder": "请选择日签奖励",
"continueSignAward": "连签奖励",
"calendarSignTip": "用户根据日期进行打卡,连续签到一定天数可即可获得连签奖励。",
"periodSignTip": "用户在规定的周期内完成签到可以获得奖励;一个周期结束后将进入下一个循环周期。",

View File

@ -6,13 +6,11 @@
"createTime":"注册时间",
"lastVisitTime":"最后访问时间",
"addMember":"添加会员",
"updateMember":"编辑会员",
"nickNamePlaceholder":"请输入会员昵称",
"mobilePlaceholder":"请输入手机号",
"channelPlaceholder":"请选择注册类型",
"memberNoPlaceholder":"请选择会员编号",
"memberDeleteTips" : "确定要删除该会员吗?",
"detail": "详情",
"edit": "编辑",
"memberDelete":"删除",
"adjust":"调整",
@ -31,8 +29,8 @@
"detaBirth": "出生日期",
"sex": "性别",
"wxUnionid": "微信unionid",
"weappOpenid": "微信用户openid",
"wxOpenid": "微信小程openid",
"weappOpenid": "微信小程序openid",
"wxOpenid": "微信用户openid",
"head": "会员头像",
"username": "用户名",
"usernamePlaceholder": "请输入用户名",
@ -44,12 +42,41 @@
"mobileHint": "请输入正确的手机号!",
"memberLabelTag": "标签",
"setLabel": "标签",
"notAvailable": "暂无",
"memberLabelPlaceholder": "请选择会员标签",
"memberInfo":"会员信息",
"memberInfoPlaceholder":"请输入会员编号/昵称/手机号",
"lock": "锁定",
"normal": "正常",
"memberLevel": "会员等级",
"memberLevelPlaceholder": "请选择会员等级"
"memberLevelPlaceholder": "请选择会员等级",
"adjustType":"调整类型",
"adjustReduceBalance":"减少余额",
"adjustAddBalance":"增加余额",
"adjustReducePoint":"减少积分",
"adjustAddPoint":"增加积分",
"adjustBalance":"调整余额",
"currBalance":"当前余额",
"currPoint":"当前积分",
"adjustPlaceholder":"请输入调整数量",
"memoPlaceholder":"请输入备注",
"adjustBalancePlaceholder":"请输入调整余额",
"adjustPointPlaceholder":"请输入调整积分",
"adjustBalanceMaxAccountMessage":"调整后余额需大于0",
"adjustPointMaxAccountMessage":"调整后积分需大于0",
"birthday": "出生日期",
"birthdayTip": "请输入出生日期",
"headimg": "会员头像",
"updateMember": "编辑会员信息",
"notAvailable":"暂无",
"girlSex":"女",
"manSex":"男",
"secrecySex":"保密",
"detail":"详情",
"accumulative":"累计",
"money":"可提现余额",
"commission":"佣金",
"memberNull":"未读取到会员详情信息",
"memberLevelUpdate": "修改等级至",
"memberLevelUpdateTips": "该操作只会修改会员等级不会发放等级礼包"
}

View File

@ -2,5 +2,7 @@
"title": "协议标题",
"type": "协议类型",
"titlePlaceholder": "请输入协议标题",
"contentPlaceholder": "请填写协议内容",
"contentMaxTips": "协议内容字符数应在550000之间",
"content": "内容"
}

View File

@ -1,16 +1,23 @@
{
"logonMode": "普通注册方式",
"isUsername": "账号密码登录",
"isMobile": "手机验证码登录",
"isAuthRegister": "第三方自动注册",
"isBindMobile": "强制绑定手机",
"isUsernameTip": "开启之后可以使用账号+密码进行注册和登录",
"isMobileTip": "开启之后可以使用手机+验证码进行注册和登录",
"isAuthRegisterTip": "开启之后,微信公众号、小程序等等第三方平台可以自动注册会员。方便会员自动登录",
"isBindMobileTip": "开启之后,会员通过账号或者第三方注册账户会强制绑定手机号,方便商家进行管理,同时方便会员在不同端口统一账号",
"agreement": "政策协议",
"agreementTips": "注册时服务协议和隐私协议是否进行展示",
"mobileOrUsernameNoEmpty":"普通注册方式至少需启用一种",
"commonSetting": "通用设置",
"tripartiteSetting": "第三方设置"
"commonSetting": "通用设置",
"logonMode": "普通注册方式",
"isUsername": "账号密码登录",
"isUsernameTip": "开启之后可以使用账号+密码进行注册和登录",
"isMobile": "手机验证码登录",
"isMobileTip": "开启之后可以使用手机+验证码进行注册和登录或者快捷登录/注册",
"isBindMobile": "强制绑定手机",
"isBindMobileTip": "开启之后,会员注册时会强制绑定手机号,并且在相关页面也会引导会员强制绑定手机账号,否则将影响功能正常使用,方便会员在不同端口统一账号,也方便商家进行管理",
"agreement": "政策协议",
"agreementTips": "注册时服务协议和隐私协议是否进行展示",
"tripartiteSetting": "第三方设置",
"isAuthRegister": "自动注册会员",
"isAuthRegisterTip": "开启之后,微信公众号、小程序等第三方平台可以自动注册会员或者快捷登录/注册会员,方便会员自动登录",
"isForceAccessUserInfo": "强制获取用户信息",
"isForceAccessUserInfoTip": "开启之后,将强制获取用户头像、昵称等信息,需要用户同意后,才能注册成功",
"mobileOrUsernameNoEmpty": "普通注册方式至少需启用一种",
"loginPageSet": "界面设置",
"bgUrl": "背景图",
"bgUrlPlaceholder": "建议图片尺寸750*669像素图片格式jpg、png、jpeg",
"desc": "描述",
"descPlaceholder": "请输入描述"
}

View File

@ -4,8 +4,8 @@
"isOpen": "定位开关",
"validTime": "定位有效期",
"minutes": "分钟",
"validTimeTips": "过期后将重新获取定位信息0为不过期",
"validTimeTips": "过期后将重新获取定位信息",
"validTimePlaceholder": "请输入定位有效期",
"validTimeFormatTips": "格式输入错误",
"validTimeNotZeroTips": "定位有效期不能小于0"
"validTimeNotZeroTips": "定位有效期不能小于5分钟"
}

View File

@ -13,9 +13,9 @@
"mchId": "商户号",
"mchIdPlaceholder": "请输入商户号",
"mchIdTips": "微信支付商户号MCHID",
"mchSecretKey": "API密钥",
"mchSecretKeyPlaceholder": "请输入API密钥",
"mchSecretKeyTips": "微信支付商户API密钥paySignKey",
"mchSecretKey": "APIv3密钥",
"mchSecretKeyPlaceholder": "请输入APIv3密钥",
"mchSecretKeyTips": "微信支付商户APIv3密钥paySignKey",
"mchSecretCert": "商户私钥",
"mchSecretCertPlaceholder": "请上传商户私钥",
"mchSecretCertTips": "微信支付API证书apiclient_key.pem",
@ -57,5 +57,13 @@
"collectionNamePlaceholder": "请输入收款账户名称",
"collectionBankPlaceholder": "请输入收款银行",
"collectionAccountPlaceholder": "请输入收款账号",
"collectionDescPlaceholder": "请输入转账说明"
"collectionDescPlaceholder": "请输入转账说明",
"jsapiDir": "JSAPI支付授权目录",
"jsapiDirTips": "需在微信商户号>产品中心>开发配置>支付配置 支付授权目录中添加该链接",
"h5Domain": "H5支付域名",
"h5DomainTips": "需在微信商户号>产品中心>开发配置>支付配置 H5支付域名中添加该域名",
"nativeDomain": "Native支付回调链接",
"nativeDomainTips": "需在微信商户号>产品中心>开发配置>支付配置 Native支付回调链接中添加该链接",
"wechatpayPublicCert": "微信支付公钥",
"wechatpayPublicCertId": "微信支付公钥ID"
}

View File

@ -4,9 +4,9 @@
"mchId": "商户号",
"mchIdPlaceholder": "请输入商户号",
"mchIdTips": "微信支付商户号MCHID",
"mchSecretKey": "API密钥",
"mchSecretKeyPlaceholder": "请输入API密钥",
"mchSecretKeyTips": "微信支付商户API密钥paySignKey",
"mchSecretKey": "APIv3密钥",
"mchSecretKeyPlaceholder": "请输入APIv3密钥",
"mchSecretKeyTips": "微信支付商户APIv3密钥paySignKey",
"mchSecretCert": "商户私钥",
"mchSecretCertPlaceholder": "请上传商户私钥",
"mchSecretCertTips": "微信支付API证书apiclient_key.pem",
@ -28,4 +28,4 @@
"alipayPublicCertPathTips": "上传alipayCertPublicKey文件",
"alipayRootCertPathTips": "上传alipayRootCert文件",
"operationTip": "温馨提示:打款设置用于会员提现转账,发放红包等场景"
}
}

View File

@ -33,5 +33,6 @@
"siteDomain": "站点域名",
"siteDomainPlaceholder": "请输入站点域名",
"siteDomainTips": "站点域名的配置是针对站点的wap和web端",
"siteDomainTipsTwo": "需要将域名配置到您的服务器,同时域名需要解析您的服务器才可生效"
"siteDomainTipsTwo": "需要将域名配置到您的服务器,同时域名需要解析您的服务器才可生效",
"siteDomainTipsThree": "站点域名不需要加http或者https末尾不需要加/"
}

View File

@ -7,7 +7,7 @@
"businessHours":"营业时间",
"createTime":"创建时间",
"expireTime":"到期时间",
"siteNamePlaceholder":"请输入站点名称",
"siteNamePlaceholder":"请输入站点名称/编号",
"createTimePlaceholder":"请输入创建时间",
"addSite":"添加站点",
"editSite":"编辑站点",
@ -52,5 +52,6 @@
"siteDomainTipsTwo": "需要将域名配置到您的服务器,同时域名需要解析您的服务器才可生效",
"siteDomainTipsThree": "站点域名不需要加http或者https末尾不需要加/",
"toSite": "访问站点",
"noPermission": "您没有该站点的管理权限"
"noPermission": "您没有该站点的管理权限",
"closeSiteTips": "是否要停止该站点?"
}

View File

@ -5,11 +5,11 @@
"lastLoginIP": "最后登录IP",
"userNamePlaceholder": "请输入账号/用户名",
"detail": "详情",
"siteNum": "站点数量",
"endDate": "结束时间",
"startDate": "开始时间",
"loginTime": "最后登录时间",
"addUser": "添加用户",
"updateUser": "编辑用户",
"username": "账号",
"passwordPlaceholder": "请输入用户密码",
"usernamePlaceholder": "请输入用户账号",
@ -19,10 +19,12 @@
"userCreateSiteLimit": "套餐权限",
"siteGroup": "站点套餐",
"siteMonth": "站点效期限",
"siteNum": "站数量",
"siteNum": "数量",
"month": "月",
"siteMonthPlaceholder": "请输入站点效期限",
"siteNumPlaceholder": "请输入建站数量",
"siteMonthCannotLtOne": "站点效期限不能小于1",
"siteNumCannotLtOne": "建站数量不能小于1"
"siteNumCannotLtOne": "建站数量不能小于1",
"userDeleteTips": "确定要删除该用户吗?",
"createSiteNum": "可创建站点数量"
}

View File

@ -26,5 +26,6 @@
"monthPlaceholder": "请输入站点创建有效期限",
"numCannotLtZero": "站点创建数量不能小于0",
"monthCannotLtZero": "站点创建有效期不能小于0",
"createSiteTimeLimitDeleteTips": "确认要删除吗?"
"createSiteTimeLimitDeleteTips": "确认要删除吗?",
"siteEmpty": "站点不存在"
}

View File

@ -61,6 +61,8 @@
"formCheckbox":"复选框",
"formDateTime":"日期",
"formImageSelect":"图片上传",
"formFileSelect":"文件上传",
"formVideoSelect":"视频上传",
"formEditor":"富文本",
"formNumber":"数字框",
"pk":"主键",
@ -140,4 +142,4 @@
"selectType":"下拉类型"
}
}

View File

@ -29,5 +29,7 @@
"addCron": "添加任务",
"cronTimeTips": "任务周期时间不能为空",
"cronTipsOne": "启动计划任务方式:",
"cronTipsTwo": "1、使用命令启动php think cron:schedule 如果更改了任务周期、状态、删除任务等操作后,需要重新启动下 php think cron:schedule 确保生效"
}
"cronTipsTwo": "1、使用命令启动php think workerman 如果更改了任务周期、状态、删除任务等操作后,需要重新启动下 php think workerman 确保生效",
"doOne": "执行一次",
"cronLog": "执行日志"
}

View File

@ -0,0 +1,17 @@
{
"id": "日志编号",
"name": "任务名称",
"key": "key",
"class": "调用目标字符串",
"executeResult": "日志信息",
"status": "执行状态",
"executeTime": "执行时间",
"info": "详情",
"cronInfo": "日志详情",
"batchDelete": "批量删除",
"clearAll": "清空",
"batchEmptySelectedCronLogTips": "请选择要删除的日志",
"batchDeleteTips": "确定要删除选中的日志吗?",
"clearAllTips": "确定要清空所有日志吗?",
"deleteTips": "确定要删除该条日志吗?"
}

View File

@ -11,21 +11,24 @@
<ArrowRight />
</el-icon>
</el-form-item>
<el-form-item :label="t('title')">
<el-input v-model.trim="diyStore.editComponent.text" :placeholder="t('titlePlaceholder')" clearable maxlength="10" show-word-limit />
</el-form-item>
<el-form-item :label="t('link')">
<diy-link v-model="diyStore.editComponent.textLink"/>
</el-form-item>
<el-form-item :label="t('subTitle')">
<el-input v-model.trim="diyStore.editComponent.subTitle.text" :placeholder="t('subTitlePlaceholder')" clearable maxlength="8" show-word-limit />
</el-form-item>
<el-form-item :label="t('link')">
<diy-link v-model="diyStore.editComponent.subTitle.link"/>
</el-form-item>
<el-form-item :label="t('title')" v-if="diyStore.editComponent && diyStore.editComponent.titleStyle && diyStore.editComponent.titleStyle.value != 'style-5'">
<el-input v-model.trim="diyStore.editComponent.text" :placeholder="t('titlePlaceholder')" clearable maxlength="10" show-word-limit />
</el-form-item>
<el-form-item :label="t('image')" v-else>
<upload-image v-model="diyStore.editComponent.textImg" :limit="1"/>
</el-form-item>
<el-form-item :label="t('link')">
<diy-link v-model="diyStore.editComponent.textLink"/>
</el-form-item>
<el-form-item :label="t('subTitle')">
<el-input v-model.trim="diyStore.editComponent.subTitle.text" :placeholder="t('subTitlePlaceholder')" clearable maxlength="8" show-word-limit />
</el-form-item>
<el-form-item :label="t('link')">
<diy-link v-model="diyStore.editComponent.subTitle.link"/>
</el-form-item>
</el-form>
<el-dialog v-model="showTitleDialog" :title="t('selectStyle')" width="500px">
<el-dialog v-model="showTitleDialog" :title="t('selectStyle')" width="460px">
<div class="flex flex-wrap">
<template v-for="(item,index) in titleStyleList" :key="index">
@ -87,15 +90,15 @@
<el-input v-model.trim="item.title.text" :placeholder="t('activeCubeTitlePlaceholder')" clearable maxlength="4" show-word-limit/>
</el-form-item>
<el-form-item :label="t('activeCubeSubTitleTextColor')" v-show="selectBlockStyle.value == 'style-3'">
<el-form-item :label="t('activeCubeSubTitleTextColor')" v-show="diyStore.editComponent.blockStyle.value == 'style-3'">
<el-color-picker v-model="item.title.textColor" show-alpha :predefine="diyStore.predefineColors" />
</el-form-item>
<el-form-item :label="t('activeCubeSubTitle')" v-if="selectBlockStyle.value != 'style-3'">
<el-input v-model.trim="item.subTitle.text" :placeholder="t('activeCubeSubTitlePlaceholder')" clearable :maxlength="(selectBlockStyle.value != 'style-4' ? '6' : '4')" show-word-limit/>
<el-form-item :label="t('activeCubeSubTitle')" v-if="diyStore.editComponent.blockStyle.value != 'style-3'">
<el-input v-model.trim="item.subTitle.text" :placeholder="t('activeCubeSubTitlePlaceholder')" clearable maxlength="6" show-word-limit/>
</el-form-item>
<div v-show="selectBlockStyle.value == 'style-4'">
<div v-show="diyStore.editComponent.blockStyle.value == 'style-4'">
<el-form-item :label="t('activeCubeSubTitleTextColor')">
<el-color-picker v-model="item.subTitle.textColor" show-alpha :predefine="diyStore.predefineColors" />
</el-form-item>
@ -104,14 +107,15 @@
<icon name="iconfont iconmap-connect" size="20px" class="block !text-gray-400 mx-[5px]"/>
<el-color-picker v-model="item.subTitle.endColor" show-alpha :predefine="diyStore.predefineColors"/>
</el-form-item>
<el-form-item :label="t('activeListFrameColor')">
<el-color-picker v-model="item.listFrame.startColor" show-alpha :predefine="diyStore.predefineColors" />
<icon name="iconfont iconmap-connect" size="20px" class="block !text-gray-400 mx-[5px]"/>
<el-color-picker v-model="item.listFrame.endColor" show-alpha :predefine="diyStore.predefineColors"/>
</el-form-item>
</div>
<div v-show="selectBlockStyle.value != 'style-4' && selectBlockStyle.value != 'style-3'">
<el-form-item :label="t('activeListFrameColor')">
<el-color-picker v-model="item.listFrame.startColor" show-alpha :predefine="diyStore.predefineColors" />
<icon name="iconfont iconmap-connect" size="20px" class="block !text-gray-400 mx-[5px]"/>
<el-color-picker v-model="item.listFrame.endColor" show-alpha :predefine="diyStore.predefineColors"/>
</el-form-item>
<div v-show="diyStore.editComponent.blockStyle.value != 'style-4' && diyStore.editComponent.blockStyle.value != 'style-3'">
<el-form-item :label="t('activeCubeButton')">
<el-input v-model.trim="item.moreTitle.text" :placeholder="t('activeCubeButtonPlaceholder')" clearable maxlength="3" show-word-limit/>
</el-form-item>
@ -143,7 +147,7 @@
<!-- 样式 -->
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
<div class="edit-attr-item-wrap">
<div class="edit-attr-item-wrap" v-if="selectTitleStyle.value != 'style-5'">
<h3 class="mb-[10px]">{{ t('titleStyle') }}</h3>
<el-form label-width="90px" class="px-[10px]">
<el-form-item :label="t('textColor')">
@ -175,6 +179,18 @@
<el-radio :label="'bold'">{{t('fontWeightBold')}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('activeCubeBlockBtnText')" class="flex">
<el-radio-group v-model="diyStore.editComponent.blockStyle.btnText">
<el-radio :label="'normal'">{{t('btnTextNormal')}}</el-radio>
<el-radio :label="'italics'">{{t('btnTextItalics')}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('topRounded')">
<el-slider v-model="diyStore.editComponent.topElementRounded" show-input size="small" class="ml-[10px] diy-nav-slider" :max="50" />
</el-form-item>
<el-form-item :label="t('bottomRounded')">
<el-slider v-model="diyStore.editComponent.bottomElementRounded" show-input size="small" class="ml-[10px] diy-nav-slider" :max="50" />
</el-form-item>
</el-form>
</div>
@ -242,6 +258,8 @@ diyStore.editComponent.list.forEach((item: any) => {
const showTitleDialog = ref(false)
const showTitleStyle = () => {
selectTitleStyle.title = diyStore.editComponent.titleStyle.title;
selectTitleStyle.value = diyStore.editComponent.titleStyle.value;
showTitleDialog.value = true
}
@ -262,6 +280,10 @@ const titleStyleList = reactive([
url : 'static/resource/images/diy/active_cube/title_style5.png',
title:'风格4',
value:'style-4'
},{
url : 'static/resource/images/diy/active_cube/title_style6.png',
title:'风格5',
value:'style-5'
}
])
@ -278,9 +300,39 @@ const changeTitleStyle = (item:any) => {
const confirmTitleStyle = () => {
diyStore.editComponent.titleStyle.title = selectTitleStyle.title;
diyStore.editComponent.titleStyle.value = selectTitleStyle.value;
initTitleStyle(diyStore.editComponent.titleStyle.value);
showTitleDialog.value = false
}
const initTitleStyle = (style)=>{
if(diyStore.editComponent.titleStyle.value == 'style-1'){
diyStore.editComponent.titleColor = "#F91700";
diyStore.editComponent.subTitle.textColor = "#FFFFFF";
diyStore.editComponent.subTitle.startColor = "#FB792F";
diyStore.editComponent.subTitle.endColor = "#F91700";
}else if(diyStore.editComponent.titleStyle.value == 'style-2'){
diyStore.editComponent.titleColor = "#F91700";
diyStore.editComponent.subTitle.textColor = "#FFFFFF";
diyStore.editComponent.subTitle.startColor = "#FB792F";
diyStore.editComponent.subTitle.endColor = "#F91700";
}else if(diyStore.editComponent.titleStyle.value == 'style-3'){
diyStore.editComponent.titleColor = "#F91700";
diyStore.editComponent.subTitle.textColor = "#FFFFFF";
diyStore.editComponent.subTitle.startColor = "#FB792F";
diyStore.editComponent.subTitle.endColor = "#F91700";
}else if(diyStore.editComponent.titleStyle.value == 'style-4'){
diyStore.editComponent.titleColor = "#FFFFFF";
diyStore.editComponent.subTitle.textColor = "#333333";
diyStore.editComponent.subTitle.startColor = "#FFFFFF";
diyStore.editComponent.subTitle.endColor = "#FFFFFF";
}else if(diyStore.editComponent.titleStyle.value == 'style-5'){
diyStore.editComponent.titleColor = "";
diyStore.editComponent.subTitle.textColor = "#999999";
diyStore.editComponent.subTitle.startColor = "#FFFFFF";
diyStore.editComponent.subTitle.endColor = "#FFFFFF";
}
}
//
const showListDialog = ref(false)
@ -324,9 +376,174 @@ const changeBlockStyle = (item:any) => {
const confirmBlockStyle = () => {
diyStore.editComponent.blockStyle.title = selectBlockStyle.title;
diyStore.editComponent.blockStyle.value = selectBlockStyle.value;
initBlockStyle(diyStore.editComponent.blockStyle.value);
showListDialog.value = false
}
const initBlockStyle = (style: any)=>{
if(style == 'style-1'){
diyStore.editComponent.blockStyle.fontWeight = "normal";
diyStore.editComponent.blockStyle.btnText = "normal";
diyStore.editComponent.list[0].title.textColor = "#303133";
diyStore.editComponent.list[0].subTitle.textColor = "#999999";
diyStore.editComponent.list[0].subTitle.startColor = "";
diyStore.editComponent.list[0].subTitle.endColor = "";
diyStore.editComponent.list[0].moreTitle.startColor = "#FEA715";
diyStore.editComponent.list[0].moreTitle.endColor = "#FE1E00";
diyStore.editComponent.list[0].listFrame.startColor = "#FFFAF5";
diyStore.editComponent.list[0].listFrame.endColor = "#FFFFFF";
diyStore.editComponent.list[1].title.textColor = "#303133";
diyStore.editComponent.list[1].subTitle.textColor = "#999999";
diyStore.editComponent.list[1].subTitle.startColor = "";
diyStore.editComponent.list[1].subTitle.endColor = "";
diyStore.editComponent.list[1].moreTitle.startColor = "#FFBF50";
diyStore.editComponent.list[1].moreTitle.endColor = "#FF9E03";
diyStore.editComponent.list[1].listFrame.startColor = "#FFFAF5";
diyStore.editComponent.list[1].listFrame.endColor = "#FFFFFF";
diyStore.editComponent.list[2].title.textColor = "#303133";
diyStore.editComponent.list[2].subTitle.textColor = "#999999";
diyStore.editComponent.list[2].subTitle.startColor = "";
diyStore.editComponent.list[2].subTitle.endColor = "";
diyStore.editComponent.list[2].moreTitle.startColor = "#A2E792";
diyStore.editComponent.list[2].moreTitle.endColor = "#49CD2D";
diyStore.editComponent.list[2].listFrame.startColor = "#FFFAF5";
diyStore.editComponent.list[2].listFrame.endColor = "#FFFFFF";
diyStore.editComponent.list[3].title.textColor = "#303133";
diyStore.editComponent.list[3].subTitle.textColor = "#999999";
diyStore.editComponent.list[3].subTitle.startColor = "";
diyStore.editComponent.list[3].subTitle.endColor = "";
diyStore.editComponent.list[3].moreTitle.startColor = "#4AC1FF";
diyStore.editComponent.list[3].moreTitle.endColor = "#1D7CFF";
diyStore.editComponent.list[3].listFrame.startColor = "#FFFAF5";
diyStore.editComponent.list[3].listFrame.endColor = "#FFFFFF";
}else if(style == 'style-2'){
diyStore.editComponent.blockStyle.fontWeight = "normal";
diyStore.editComponent.blockStyle.btnText = "normal";
diyStore.editComponent.blockStyle.fontWeight = "bold";
diyStore.editComponent.blockStyle.btnText = "italics";
diyStore.editComponent.list[0].title.textColor = "#303133";
diyStore.editComponent.list[0].subTitle.textColor = "#999999";
diyStore.editComponent.list[0].subTitle.startColor = "";
diyStore.editComponent.list[0].subTitle.endColor = "";
diyStore.editComponent.list[0].moreTitle.startColor = "#FFC051";
diyStore.editComponent.list[0].moreTitle.endColor = "#FF9C00";
diyStore.editComponent.list[0].listFrame.startColor = "#FFF1DB";
diyStore.editComponent.list[0].listFrame.endColor = "#FFFBF4";
diyStore.editComponent.list[1].title.textColor = "#303133";
diyStore.editComponent.list[1].subTitle.textColor = "#999999";
diyStore.editComponent.list[1].subTitle.startColor = "";
diyStore.editComponent.list[1].subTitle.endColor = "";
diyStore.editComponent.list[1].moreTitle.startColor = "#A4E894";
diyStore.editComponent.list[1].moreTitle.endColor = "#45CC2A";
diyStore.editComponent.list[1].listFrame.startColor = "#E6F6E2";
diyStore.editComponent.list[1].listFrame.endColor = "#F5FDF3";
diyStore.editComponent.list[2].title.textColor = "#303133";
diyStore.editComponent.list[2].subTitle.textColor = "#999999";
diyStore.editComponent.list[2].subTitle.startColor = "";
diyStore.editComponent.list[2].subTitle.endColor = "";
diyStore.editComponent.list[2].moreTitle.startColor = "#4BC2FF";
diyStore.editComponent.list[2].moreTitle.endColor = "#1F7DFF";
diyStore.editComponent.list[2].listFrame.startColor = "#E2F6FF";
diyStore.editComponent.list[2].listFrame.endColor = "#F2FAFF";
diyStore.editComponent.list[3].title.textColor = "#303133";
diyStore.editComponent.list[3].subTitle.textColor = "#999999";
diyStore.editComponent.list[3].subTitle.startColor = "";
diyStore.editComponent.list[3].subTitle.endColor = "";
diyStore.editComponent.list[3].moreTitle.startColor = "#FB792F";
diyStore.editComponent.list[3].moreTitle.endColor = "#F91700";
diyStore.editComponent.list[3].listFrame.startColor = "#FFEAEA";
diyStore.editComponent.list[3].listFrame.endColor = "#FFFCFB";
}else if(style == 'style-3'){
diyStore.editComponent.blockStyle.fontWeight = "normal";
diyStore.editComponent.blockStyle.btnText = "normal";
diyStore.editComponent.list[0].title.textColor = "#FF1128";
diyStore.editComponent.list[0].subTitle.textColor = "";
diyStore.editComponent.list[0].subTitle.startColor = "";
diyStore.editComponent.list[0].subTitle.endColor = "";
diyStore.editComponent.list[0].moreTitle.startColor = "";
diyStore.editComponent.list[0].moreTitle.endColor = "";
diyStore.editComponent.list[0].listFrame.startColor = "";
diyStore.editComponent.list[0].listFrame.endColor = "";
diyStore.editComponent.list[1].title.textColor = "#303133";
diyStore.editComponent.list[1].subTitle.textColor = "";
diyStore.editComponent.list[1].subTitle.startColor = "";
diyStore.editComponent.list[1].subTitle.endColor = "";
diyStore.editComponent.list[1].moreTitle.startColor = "";
diyStore.editComponent.list[1].moreTitle.endColor = "";
diyStore.editComponent.list[1].listFrame.startColor = "";
diyStore.editComponent.list[1].listFrame.endColor = "";
diyStore.editComponent.list[2].title.textColor = "#303133";
diyStore.editComponent.list[2].subTitle.textColor = "";
diyStore.editComponent.list[2].subTitle.startColor = "";
diyStore.editComponent.list[2].subTitle.endColor = "";
diyStore.editComponent.list[2].moreTitle.startColor = "";
diyStore.editComponent.list[2].moreTitle.endColor = "";
diyStore.editComponent.list[2].listFrame.startColor = "";
diyStore.editComponent.list[2].listFrame.endColor = "";
diyStore.editComponent.list[3].title.textColor = "#303133";
diyStore.editComponent.list[3].subTitle.textColor = "";
diyStore.editComponent.list[3].subTitle.startColor = "";
diyStore.editComponent.list[3].subTitle.endColor = "";
diyStore.editComponent.list[3].moreTitle.startColor = "";
diyStore.editComponent.list[3].moreTitle.endColor = "";
diyStore.editComponent.list[3].listFrame.startColor = "";
diyStore.editComponent.list[3].listFrame.endColor = "";
}else if(style == 'style-4'){
diyStore.editComponent.blockStyle.fontWeight = "bold";
diyStore.editComponent.blockStyle.btnText = "normal";
diyStore.editComponent.list[0].title.textColor = "#303133";
diyStore.editComponent.list[0].subTitle.textColor = "#ED6E00";
diyStore.editComponent.list[0].subTitle.startColor = "#FFE4D9";
diyStore.editComponent.list[0].subTitle.endColor = "#FFE4D9";
diyStore.editComponent.list[0].moreTitle.startColor = "";
diyStore.editComponent.list[0].moreTitle.endColor = "";
diyStore.editComponent.list[0].listFrame.startColor = "#FFAD4D";
diyStore.editComponent.list[0].listFrame.endColor = "#F93D02";
diyStore.editComponent.list[1].title.textColor = "#303133";
diyStore.editComponent.list[1].subTitle.textColor = "#2E59E9";
diyStore.editComponent.list[1].subTitle.startColor = "#CAD7F8";
diyStore.editComponent.list[1].subTitle.endColor = "#CAD7F8";
diyStore.editComponent.list[1].moreTitle.startColor = "";
diyStore.editComponent.list[1].moreTitle.endColor = "";
diyStore.editComponent.list[1].listFrame.startColor = "#7CA7F4";
diyStore.editComponent.list[1].listFrame.endColor = "#2B56E9";
diyStore.editComponent.list[2].title.textColor = "#303133";
diyStore.editComponent.list[2].subTitle.textColor = "#F62F55";
diyStore.editComponent.list[2].subTitle.startColor = "#FCD6D9";
diyStore.editComponent.list[2].subTitle.endColor = "#FCD6D9";
diyStore.editComponent.list[2].moreTitle.startColor = "";
diyStore.editComponent.list[2].moreTitle.endColor = "";
diyStore.editComponent.list[2].listFrame.startColor = "#FF7F48";
diyStore.editComponent.list[2].listFrame.endColor = "#EE335B";
diyStore.editComponent.list[3].title.textColor = "#303133";
diyStore.editComponent.list[3].subTitle.textColor = "#139B3C";
diyStore.editComponent.list[3].subTitle.startColor = "#D3F1DA";
diyStore.editComponent.list[3].subTitle.endColor = "#D3F1DA";
diyStore.editComponent.list[3].moreTitle.startColor = "";
diyStore.editComponent.list[3].moreTitle.endColor = "";
diyStore.editComponent.list[3].listFrame.startColor = "#90D48C";
diyStore.editComponent.list[3].listFrame.endColor = "#299F4F";
}
}
const addItem = () => {
diyStore.editComponent.list.push({
id: diyStore.generateRandom(),

View File

@ -26,9 +26,18 @@
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('carouselSearchSet') }}</h3>
<el-form label-width="100px" class="px-[10px]">
<div class="text-sm text-gray-400 mb-[10px]">{{ t('carouselSearchLogoTips') }}</div>
<el-form-item :label="t('selectStyle')" class="flex">
<span class="text-primary flex-1 cursor-pointer" @click="showSearchStyle">{{ diyStore.editComponent.search.styleName }}</span>
<el-icon>
<ArrowRight />
</el-icon>
</el-form-item>
<el-form-item :label="t('carouselSearchSubTitle')" v-if="diyStore.editComponent.search.style == 'style-2'">
<el-input v-model.trim="diyStore.editComponent.search.subTitle.text" :placeholder="t('carouselSearchSubTitlePlaceholder')" clearable maxlength="10" show-word-limit />
</el-form-item>
<el-form-item :label="t('logo')">
<upload-image v-model="diyStore.editComponent.search.logo" :limit="1" />
<div class="text-sm text-gray-400 mb-[10px]">{{ t('carouselSearchLogoTips') }}</div>
</el-form-item>
<el-form-item :label="t('carouselSearchText')">
<div>
@ -39,16 +48,34 @@
<el-form-item :label="t('link')">
<diy-link v-model="diyStore.editComponent.search.link"/>
</el-form-item>
</el-form>
</div>
<div class="edit-attr-item-wrap">
<el-dialog v-model="showSearchDialog" :title="t('selectStyle')" width="500px">
<div class="flex flex-wrap">
<template v-for="(item,index) in searchStyleList" :key="index">
<div :class="{ 'border-primary': selectSearchStyle.value == item.value }" @click="changeSearchStyle(item)" class="flex items-center justify-center overflow-hidden w-[200px] h-[100px] m-[6px] cursor-pointer border bg-[#eee]">
<img :src="img(item.url)" />
</div>
</template>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="showSearchDialog = false">{{ t('cancel') }}</el-button>
<el-button type="primary" @click="confirmSearchStyle">{{ t('confirm') }}</el-button>
</span>
</template>
</el-dialog>
</div>
<div class="edit-attr-item-wrap mb-[20px]">
<h3 class="mb-[10px]">{{ t('carouselSearchHotWordSet') }}</h3>
<el-form label-width="100px" class="px-[10px]">
<el-form-item :label="t('carouselSearchHotWordInterval')">
<el-slider v-model="diyStore.editComponent.search.hotWord.interval" show-input size="small" class="ml-[10px] horz-blank-slider" :min="1" :max="10"/>
<el-slider v-model="diyStore.editComponent.search.hotWord.interval" show-input size="small" class="ml-[10px] diy-nav-slider" :min="1" :max="10"/>
</el-form-item>
<p class="text-sm text-gray-400 mb-[10px]">{{ t('dragMouseAdjustOrder') }}</p>
@ -70,109 +97,148 @@
</el-form>
</div>
<el-collapse v-model="activeNames" @change="handleChange" class="collapse-wrap">
<el-collapse-item :title="t('carouselSearchTabSet')" name="tab">
<div class="edit-attr-item-wrap">
<el-form label-width="100px" class="px-[10px]">
<el-form-item :label="t('carouselSearchTabControl')">
<el-switch v-model="diyStore.editComponent.tab.control" />
</el-form-item>
<el-collapse-item :title="t('carouselSearchTabSet')" name="tab">
<div class="edit-attr-item-wrap">
<el-form label-width="100px" class="px-[10px]">
<el-form-item :label="t('carouselSearchTabControl')">
<el-switch v-model="diyStore.editComponent.tab.control" />
</el-form-item>
<p class="text-sm text-gray-400 mb-[10px]">{{ t('dragMouseAdjustOrder') }}</p>
<p class="text-sm text-gray-400 mb-[10px]">{{ t('dragMouseAdjustOrder') }}</p>
<div ref="tabBoxRef">
<div v-for="(item,index) in diyStore.editComponent.tab.list" :key="item.id" class="item-wrap p-[10px] pb-0 relative border border-dashed border-gray-300 mb-[16px]">
<div ref="tabBoxRef">
<div v-for="(item,index) in diyStore.editComponent.tab.list" :key="item.id" class="item-wrap p-[10px] pb-0 relative border border-dashed border-gray-300 mb-[16px]">
<el-form-item :label="t('carouselSearchTabCategoryText')">
<el-input v-model.trim="item.text" :placeholder="t('carouselSearchTabCategoryTextPlaceholder')" clearable maxlength="4" show-word-limit/>
</el-form-item>
<el-form-item :label="t('carouselSearchTabCategoryText')">
<el-input v-model.trim="item.text" :placeholder="t('carouselSearchTabCategoryTextPlaceholder')" clearable maxlength="4" show-word-limit/>
</el-form-item>
<el-form-item :label="t('dataSources')">
<el-input v-model="item.diy_title" :placeholder="t('selectDiyPagePlaceholder')" readonly class="select-diy-page-input" @click="diyPageShowDialogOpen(index)">
<template #suffix>
<div @click.stop="tabClear(index)">
<el-icon v-if="item.diy_title">
<Close />
</el-icon>
<el-icon v-else>
<ArrowRight />
</el-icon>
</div>
</template>
</el-input>
</el-form-item>
<el-form-item :label="t('dataSources')">
<el-input v-model.trim="item.diy_title" :placeholder="t('selectDiyPagePlaceholder')" readonly class="select-diy-page-input" @click="diyPageShowDialogOpen(index)">
<template #suffix>
<div @click.stop="tabClear(index)">
<el-icon v-if="item.diy_title">
<Close />
</el-icon>
<el-icon v-else>
<ArrowRight />
</el-icon>
</div>
</template>
</el-input>
</el-form-item>
<div class="del absolute cursor-pointer z-[2] top-[-8px] right-[-8px]" v-show="diyStore.editComponent.tab.list.length > 1" @click="diyStore.editComponent.tab.list.splice(index,1)">
<icon name="element CircleCloseFilled" color="#bbb" size="20px"/>
</div>
<div class="del absolute cursor-pointer z-[2] top-[-8px] right-[-8px]" v-show="diyStore.editComponent.tab.list.length > 1" @click="diyStore.editComponent.tab.list.splice(index,1)">
<icon name="element CircleCloseFilled" color="#bbb" size="20px"/>
</div>
</div>
<el-button v-show="diyStore.editComponent.tab.list.length < 50" class="w-full" @click="addTabItem">{{ t('carouselSearchAddTabItem') }}</el-button>
</div>
</div>
<el-button v-show="diyStore.editComponent.tab.list.length < 50" class="w-full" @click="addTabItem">{{ t('carouselSearchAddTabItem') }}</el-button>
</div>
<!-- 选择微页面弹出框 -->
<el-dialog v-model="diyPageShowDialog" :title="t('selectSourcesDiyPage')" width="1000px" :close-on-press-escape="false" :destroy-on-close="true" :close-on-click-modal="false">
<el-table :data="diyPageTable.data" ref="diyPageTableRef" size="large" v-loading="diyPageTable.loading" height="490px" @current-change="handleCurrentDiyPageChange" row-key="id" highlight-current-row>
<template #empty>
<span>{{ !diyPageTable.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column prop="page_title" :label="t('diyPageTitle')" min-width="120" />
<el-table-column prop="addon_name" :label="t('diyPageTypeName')" min-width="80" />
<el-table-column prop="type_name" :label="t('diyPageForAddon')" min-width="80" />
</el-table>
<div class="mt-[16px] flex justify-end">
<el-pagination v-model:current-page="diyPageTable.page" v-model:page-size="diyPageTable.limit"
layout="total, sizes, prev, pager, next, jumper" :total="diyPageTable.total"
@size-change="loadDiyPageList" @current-change="loadDiyPageList" />
</div>
<div class="flex items-center justify-end mt-[15px]">
<el-button type="primary" @click="saveDiyPageId">{{ t('confirm') }}</el-button>
<el-button @click="diyPageShowDialog = false">{{ t('cancel') }}</el-button>
</div>
</el-dialog>
<!-- 选择微页面弹出框 -->
<el-dialog v-model="diyPageShowDialog" :title="t('selectSourcesDiyPage')" width="1000px" :close-on-press-escape="false" :destroy-on-close="true" :close-on-click-modal="false">
<el-table :data="diyPageTable.data" ref="diyPageTableRef" size="large" v-loading="diyPageTable.loading" height="490px" @current-change="handleCurrentDiyPageChange" row-key="id" highlight-current-row>
<template #empty>
<span>{{ !diyPageTable.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column prop="page_title" :label="t('diyPageTitle')" min-width="120" />
<el-table-column prop="addon_name" :label="t('diyPageTypeName')" min-width="80" />
<el-table-column prop="type_name" :label="t('diyPageForAddon')" min-width="80" />
</el-table>
<div class="mt-[16px] flex justify-end">
<el-pagination v-model:current-page="diyPageTable.page" v-model:page-size="diyPageTable.limit"
layout="total, sizes, prev, pager, next, jumper" :total="diyPageTable.total"
@size-change="loadDiyPageList" @current-change="loadDiyPageList" />
</div>
<div class="flex items-center justify-end mt-[15px]">
<el-button type="primary" @click="saveDiyPageId">{{ t('confirm') }}</el-button>
<el-button @click="diyPageShowDialog = false">{{ t('cancel') }}</el-button>
</div>
</el-dialog>
</el-form>
</div>
</el-collapse-item>
<el-collapse-item :title="t('carouselSearchSwiperSet')" name="swiper">
<el-form label-width="100px" class="px-[10px]">
</el-form>
</div>
</el-collapse-item>
<el-collapse-item :title="t('carouselSearchSwiperSet')" name="swiper">
<el-form label-width="100px" class="px-[10px]">
<el-form-item :label="t('carouselSearchSwiperControl')">
<el-switch v-model="diyStore.editComponent.swiper.control" />
</el-form-item>
<el-form-item :label="t('carouselSearchSwiperInterval')">
<el-slider v-model="diyStore.editComponent.swiper.interval" show-input size="small" class="ml-[10px] diy-nav-slider" :min="1" :max="10"/>
</el-form-item>
<el-form-item :label="t('carouselSearchSwiperControl')">
<el-switch v-model="diyStore.editComponent.swiper.control" />
</el-form-item>
<el-form-item :label="t('carouselSearchSwiperInterval')">
<el-slider v-model="diyStore.editComponent.swiper.interval" show-input size="small" class="ml-[10px] horz-blank-slider" :min="1" :max="10"/>
</el-form-item>
<div class="text-sm text-gray-400 mb-[10px]">{{ t('carouselSearchSwiperTips') }}</div>
<div class="text-sm text-gray-400 mb-[10px]">{{ t('carouselSearchSwiperTips') }}</div>
<div ref="imageBoxRef">
<div v-for="(item,index) in diyStore.editComponent.swiper.list" :key="item.id" class="item-wrap p-[10px] pb-0 relative border border-dashed border-gray-300 mb-[16px]">
<el-form-item :label="t('image')">
<upload-image v-model="item.imageUrl" :limit="1" @change="selectImg" />
</el-form-item>
<div ref="imageBoxRef">
<div v-for="(item,index) in diyStore.editComponent.swiper.list" :key="item.id" class="item-wrap p-[10px] pb-0 relative border border-dashed border-gray-300 mb-[16px]">
<el-form-item :label="t('image')">
<upload-image v-model="item.imageUrl" :limit="1" @change="selectImg" />
</el-form-item>
<div class="del absolute cursor-pointer z-[2] top-[-8px] right-[-8px]" v-show="diyStore.editComponent.swiper.list.length > 1" @click="diyStore.editComponent.swiper.list.splice(index,1)">
<icon name="element CircleCloseFilled" color="#bbb" size="20px"/>
</div>
<div class="del absolute cursor-pointer z-[2] top-[-8px] right-[-8px]" v-show="diyStore.editComponent.swiper.list.length > 1" @click="diyStore.editComponent.swiper.list.splice(index,1)">
<icon name="element CircleCloseFilled" color="#bbb" size="20px"/>
</div>
<el-form-item :label="t('link')">
<diy-link v-model="item.link"/>
</el-form-item>
</div>
</div>
<el-form-item :label="t('link')">
<diy-link v-model="item.link"/>
</el-form-item>
</div>
</div>
<el-button v-show="diyStore.editComponent.swiper.list.length < 10" class="w-full" @click="addImageAd">{{ t('addImageAd') }}</el-button>
<el-button v-show="diyStore.editComponent.swiper.list.length < 10" class="w-full" @click="addImageAd">{{ t('addImageAd') }}</el-button>
</el-form>
</el-collapse-item>
</el-collapse>
</el-form>
</el-collapse-item>
</el-collapse>
</div>
<!-- 样式 -->
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
<div class="edit-attr-item-wrap" v-if="diyStore.editComponent.search.style == 'style-2'">
<h3 class="mb-[10px]">{{ t('carouselSearchPositionStyle') }}</h3>
<el-form label-width="100px" class="px-[10px]">
<el-form-item :label="t('carouselSearchTextColor')">
<el-color-picker v-model="diyStore.editComponent.search.positionColor" show-alpha :predefine="diyStore.predefineColors"/>
</el-form-item>
</el-form>
</div>
<div class="edit-attr-item-wrap" v-if="diyStore.editComponent.search.style == 'style-2'">
<h3 class="mb-[10px]">{{ t('carouselSearchSubTitleStyle') }}</h3>
<el-form label-width="100px" class="px-[10px]">
<el-form-item :label="t('carouselSearchTextColor')">
<el-color-picker v-model="diyStore.editComponent.search.subTitle.textColor" show-alpha :predefine="diyStore.predefineColors"/>
</el-form-item>
<el-form-item :label="t('carouselSearchBgColor')">
<el-color-picker v-model="diyStore.editComponent.search.subTitle.startColor" :predefine="diyStore.predefineColors" show-alpha/>
<icon name="iconfont iconmap-connect" size="20px" class="block !text-gray-400 mx-[5px]"/>
<el-color-picker v-model="diyStore.editComponent.search.subTitle.endColor" :predefine="diyStore.predefineColors" show-alpha/>
</el-form-item>
</el-form>
</div>
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('carouselSearchStyle') }}</h3>
<el-form label-width="100px" class="px-[10px]">
<el-form-item :label="t('carouselSearchTextColor')">
<el-color-picker v-model="diyStore.editComponent.search.color" show-alpha :predefine="diyStore.predefineColors"/>
</el-form-item>
<el-form-item :label="t('carouselSearchBgColor')">
<el-color-picker v-model="diyStore.editComponent.search.bgColor" show-alpha :predefine="diyStore.predefineColors"/>
</el-form-item>
<el-form-item :label="t('carouselSearchBtnColor')">
<el-color-picker v-model="diyStore.editComponent.search.btnColor" show-alpha :predefine="diyStore.predefineColors"/>
</el-form-item>
<el-form-item :label="t('carouselSearchBtnBgColor')">
<el-color-picker v-model="diyStore.editComponent.search.btnBgColor" show-alpha :predefine="diyStore.predefineColors"/>
</el-form-item>
</el-form>
</div>
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('carouselSearchTabStyle') }}</h3>
@ -199,13 +265,14 @@
<el-radio-group v-model="diyStore.editComponent.swiper.swiperStyle">
<el-radio label="style-1">{{ t('carouselSearchSwiperIndicatorStyle1') }}</el-radio>
<el-radio label="style-2">{{ t('carouselSearchSwiperIndicatorStyle2') }}</el-radio>
<el-radio label="style-3">{{ t('carouselSearchSwiperIndicatorStyle3') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('topRounded')">
<el-slider v-model="diyStore.editComponent.swiper.topRounded" show-input size="small" class="ml-[10px] horz-blank-slider" :max="50" />
<el-slider v-model="diyStore.editComponent.swiper.topRounded" show-input size="small" class="ml-[10px] diy-nav-slider" :max="50" />
</el-form-item>
<el-form-item :label="t('bottomRounded')">
<el-slider v-model="diyStore.editComponent.swiper.bottomRounded" show-input size="small" class="ml-[10px] horz-blank-slider" :max="50" />
<el-slider v-model="diyStore.editComponent.swiper.bottomRounded" show-input size="small" class="ml-[10px] diy-nav-slider" :max="50" />
</el-form-item>
</el-form>
</div>
@ -217,6 +284,7 @@
<el-radio-group v-model="diyStore.editComponent.swiper.indicatorStyle">
<el-radio label="style-1">{{ t('carouselSearchSwiperIndicatorStyle1') }}</el-radio>
<el-radio label="style-2">{{ t('carouselSearchSwiperIndicatorStyle2') }}</el-radio>
<el-radio label="style-3">{{ t('carouselSearchSwiperIndicatorStyle3') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('carouselSearchSwiperIndicatorAlign')">
@ -248,7 +316,7 @@ import useDiyStore from '@/stores/modules/diy'
import { ref, reactive, watch, onMounted, nextTick } from 'vue'
import { ElTable } from 'element-plus'
import Sortable from 'sortablejs'
import { range } from 'lodash-es'
import { range,cloneDeep } from 'lodash-es'
import { getDiyPageListByCarouselSearch } from '@/app/api/diy'
@ -280,17 +348,59 @@ diyStore.editComponent.verify = (index: number) => {
// }
});
diyStore.value[index].swiper.list.forEach((item: any) => {
if(item.imageUrl == ''){
res.code = false
res.message = t('imageUrlTip')
return res
}
});
if(diyStore.value[index].swiper.control){
diyStore.value[index].swiper.list.forEach((item: any) => {
if(item.imageUrl == ''){
res.code = false
res.message = t('imageUrlTip')
return res
}
});
}
return res
}
/************** 搜索框样式选择-start ********************/
const selectSearchStyle = reactive({
title: diyStore.editComponent.search.styleName,
value: diyStore.editComponent.search.style
})
const showSearchDialog = ref(false)
const showSearchStyle = () => {
showSearchDialog.value = true
selectSearchStyle.title = diyStore.editComponent.search.styleName;
selectSearchStyle.value = diyStore.editComponent.search.style;
}
const changeSearchStyle = (item:any) => {
selectSearchStyle.title = item.title;
selectSearchStyle.value = item.value;
}
const confirmSearchStyle = () => {
diyStore.editComponent.search.styleName = selectSearchStyle.title;
diyStore.editComponent.search.style = selectSearchStyle.value;
showSearchDialog.value = false
}
const searchStyleList = reactive([
{
url: 'static/resource/images/diy/carousel_search/style_1.png',
title: '风格1',
value: 'style-1'
},
{
url: 'static/resource/images/diy/carousel_search/style_2.png',
title: '风格2',
value: 'style-2'
}
])
/************** 搜索框样式选择-end ********************/
diyStore.editComponent.search.hotWord.list.forEach((item: any) => {
if (!item.id) item.id = diyStore.generateRandom()
})
@ -427,6 +537,8 @@ const loadDiyPageList = (page: number = 1) => {
newData.push(data[i]);
}
}
} else {
newData = cloneDeep(data); //
}
if (isExistCount) {
res.data.total = res.data.total - isExistCount;

View File

@ -3,7 +3,7 @@
<div class="content-wrap float-btn" v-show="diyStore.editTab == 'content'">
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('selectStyle') }}</h3>
<!-- <h3 class="mb-[10px]">{{ t('selectStyle') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('selectStyle')" class="flex">
<span class="text-primary flex-1 cursor-pointer" @click="showCouponStyle">{{ diyStore.editComponent.styleName }}</span>
@ -27,7 +27,7 @@
</span>
</template>
</el-dialog>
</el-dialog> -->
<h3 class="mb-[10px]">{{ t('floatBtnBtton') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('floatBtnBtton')">
@ -41,7 +41,7 @@
</ul>
</el-form-item>
<el-form-item :label="t('floatBtnOffset')">
<el-slider v-model="diyStore.editComponent.offset" show-input size="small" class="ml-[10px] horz-blank-slider" :max="100"/>
<el-slider v-model="diyStore.editComponent.offset" show-input size="small" class="ml-[10px] diy-nav-slider" :max="100"/>
</el-form-item>
</el-form>
</div>
@ -49,10 +49,10 @@
<h3 class="mb-[10px]">{{ t('floatBtnImageSet') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('floatBtnImageSize')">
<el-slider v-model="diyStore.editComponent.imageSize" show-input size="small" class="ml-[10px] horz-blank-slider" :min="30" :max="100"/>
<el-slider v-model="diyStore.editComponent.imageSize" show-input size="small" class="ml-[10px] diy-nav-slider" :min="30" :max="100"/>
</el-form-item>
<el-form-item :label="t('floatBtnAroundRadius')">
<el-slider v-model="diyStore.editComponent.aroundRadius" show-input size="small" class="ml-[10px] graphic-nav-slider" :max="50"/>
<el-slider v-model="diyStore.editComponent.aroundRadius" show-input size="small" class="ml-[10px] diy-nav-slider" :max="50"/>
</el-form-item>
<div class="text-[12px] text-[#999] mb-[15px] mt-[5px]">{{t('floatBtnImageSuggest')}}</div>
@ -94,7 +94,7 @@ import { img } from '@/utils/common'
import { range } from 'lodash-es'
const diyStore = useDiyStore()
diyStore.editComponent.ignore = ['pageBgColor','marginTop','marginBottom','marginBoth'] //
diyStore.editComponent.ignore = ['pageBgColor','marginTop','marginBottom','marginBoth','componentBgUrl'] //
//
diyStore.editComponent.verify = (index: number) => {
@ -121,18 +121,18 @@ const showCouponStyle = () => {
selectCouponStyle.title = diyStore.editComponent.styleName;
selectCouponStyle.value = diyStore.editComponent.style;
}
const couponStyleList = reactive([
{
url: 'addon/shop/diy/goods_coupon/style-1.png',
title: '风格1',
value: 'style-1'
},
{
url: 'addon/shop/diy/goods_coupon/style-2.png',
title: '风格2',
value: 'style-2'
}
])
// const couponStyleList = reactive([
// {
// url: 'addon/shop/diy/goods_coupon/style-1.png',
// title: '1',
// value: 'style-1'
// },
// {
// url: 'addon/shop/diy/goods_coupon/style-2.png',
// title: '2',
// value: 'style-2'
// }
// ])
//
const changeCouponStyle = (item:any) => {
selectCouponStyle.title = item.title;
@ -174,7 +174,7 @@ const templateList = ref([
}
])
let selectTemplate = ref({})
const selectTemplate = ref({})
templateList.value.forEach((item) => {
if (item.className == diyStore.editComponent.bottomPosition) {
selectTemplate.value = item
@ -222,5 +222,5 @@ defineExpose({})
</script>
<style lang="scss" scoped>
</style>
</style>

View File

@ -32,7 +32,7 @@
<el-form-item :label="t('graphicNavShowStyle')">
<el-radio-group v-model="diyStore.editComponent.showStyle" @change="changeShowStyle">
<el-radio :label="'fixed'">{{t('graphicNavStyleFixed')}}</el-radio>
<el-radio :label="'singleSlide'">{{t('graphicNavStyleSingleSlide')}}</el-radio>
<el-radio :label="'singleSlide'">{{diyStore.editComponent.pageCount == 2 ? t('graphicNavStyleMultiLine') : t('graphicNavStyleSingleSlide')}}</el-radio>
<el-radio :label="'pageSlide'">{{t('graphicNavStylePageSlide')}}</el-radio>
</el-radio-group>
</el-form-item>
@ -45,7 +45,6 @@
</el-radio-group>
</el-form-item>
</view>
</el-form>
</div>
<div class="edit-attr-item-wrap">
@ -74,7 +73,7 @@
</div>
</div>
<el-button v-show="diyStore.editComponent.list.length < 10" class="w-full" @click="addGraphicNav">{{ t('addGraphicNav') }}</el-button>
<el-button v-show="diyStore.editComponent.list.length < 50" class="w-full" @click="addGraphicNav">{{ t('addGraphicNav') }}</el-button>
</el-form>
</div>
@ -86,10 +85,10 @@
<h3 class="mb-[10px]">{{ t('graphicNavImageSet') }}</h3>
<el-form label-width="90px" class="px-[10px]">
<el-form-item :label="t('graphicNavImageSize')">
<el-slider v-model="diyStore.editComponent.imageSize" show-input size="small" class="ml-[10px] graphic-nav-slider" :min="20" :max="60"/>
<el-slider v-model="diyStore.editComponent.imageSize" show-input size="small" class="ml-[10px] diy-nav-slider" :min="20" :max="60"/>
</el-form-item>
<el-form-item :label="t('graphicNavAroundRadius')">
<el-slider v-model="diyStore.editComponent.aroundRadius" show-input size="small" class="ml-[10px] graphic-nav-slider" :max="50"/>
<el-slider v-model="diyStore.editComponent.aroundRadius" show-input size="small" class="ml-[10px] diy-nav-slider" :max="50"/>
</el-form-item>
</el-form>
</div>
@ -97,7 +96,7 @@
<h3 class="mb-[10px]">{{ t('textSet') }}</h3>
<el-form label-width="90px" class="px-[10px]">
<el-form-item :label="t('textFontSize')">
<el-slider v-model="diyStore.editComponent.font.size" show-input size="small" class="ml-[10px] graphic-nav-slider" :min="12" :max="16"/>
<el-slider v-model="diyStore.editComponent.font.size" show-input size="small" class="ml-[10px] diy-nav-slider" :min="12" :max="16"/>
</el-form-item>
<el-form-item :label="t('textFontWeight')">
<el-radio-group v-model="diyStore.editComponent.font.weight">
@ -111,6 +110,31 @@
</el-form>
</div>
<div class="edit-attr-item-wrap" v-show="diyStore.editComponent.showStyle == 'pageSlide' && diyStore.editComponent.layout == 'horizontal'">
<h3 class="mb-[10px]">{{ t('carouselSearchSwiperIndicatorSet') }}</h3>
<el-form label-width="100px" class="px-[10px]">
<el-form-item :label="t('carouselSearchSwiperIndicatorStyle')">
<el-radio-group v-model="diyStore.editComponent.swiper.indicatorStyle">
<el-radio label="style-1">{{ t('carouselSearchSwiperIndicatorStyle1') }}</el-radio>
<el-radio label="style-2">{{ t('carouselSearchSwiperIndicatorStyle2') }}</el-radio>
<el-radio label="style-3">{{ t('carouselSearchSwiperIndicatorStyle3') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('carouselSearchSwiperIndicatorAlign')">
<el-radio-group v-model="diyStore.editComponent.swiper.indicatorAlign">
<el-radio label="left">{{ t('alignLeft') }}</el-radio>
<el-radio label="center">{{ t('alignCenter') }}</el-radio>
<el-radio label="right">{{ t('alignRight') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('noColor')">
<el-color-picker v-model="diyStore.editComponent.swiper.indicatorColor" show-alpha :predefine="diyStore.predefineColors"/>
</el-form-item>
<el-form-item :label="t('selectColor')">
<el-color-picker v-model="diyStore.editComponent.swiper.indicatorActiveColor" show-alpha :predefine="diyStore.predefineColors"/>
</el-form-item>
</el-form>
</div>
<!-- 组件样式 -->
<slot name="style"></slot>
@ -130,7 +154,6 @@ import useDiyStore from '@/stores/modules/diy'
const diyStore = useDiyStore()
diyStore.editComponent.ignore = [] //
//
diyStore.editComponent.verify = (index: number) => {
const res = { code: true, message: '' }
@ -215,19 +238,12 @@ const changePageCount = (value:any)=>{
}else if(value == '2'){
diyStore.editComponent.showStyle = 'fixed';
}
}
}
defineExpose({})
</script>
<style lang="scss">
.graphic-nav-slider {
.el-slider__input {
width: 100px;
}
}
</style>
<style lang="scss" scoped>
.edit-graphic-nav {

View File

@ -5,7 +5,7 @@
<h3 class="mb-[10px]">{{ t('blankHeightSet') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('blankHeight')">
<el-slider v-model="diyStore.editComponent.height" show-input size="small" max="200" class="ml-[10px] horz-blank-slider"/>
<el-slider v-model="diyStore.editComponent.height" show-input size="small" max="200" class="ml-[10px] diy-nav-slider"/>
</el-form-item>
</el-form>
</div>
@ -31,11 +31,4 @@ defineExpose({})
</script>
<style lang="scss">
.horz-blank-slider {
.el-slider__input {
width: 100px;
}
}
</style>
<style lang="scss" scoped></style>

View File

@ -14,7 +14,7 @@
<el-color-picker v-model="diyStore.editComponent.borderColor" show-alpha :predefine="diyStore.predefineColors"/>
</el-form-item>
<el-form-item :label="t('horzLineBorderWidth')">
<el-slider v-model="diyStore.editComponent.borderWidth" show-input size="small" class="ml-[10px] horz-blank-slider" :min="1" :max="10"/>
<el-slider v-model="diyStore.editComponent.borderWidth" show-input size="small" class="ml-[10px] diy-nav-slider" :min="1" :max="10"/>
</el-form-item>
</el-form>
</div>
@ -40,11 +40,4 @@ defineExpose({})
</script>
<style lang="scss">
.horz-blank-slider {
.el-slider__input {
width: 100px;
}
}
</style>
<style lang="scss" scoped></style>

View File

@ -38,7 +38,7 @@ import useDiyStore from '@/stores/modules/diy'
import { img } from '@/utils/common'
const diyStore = useDiyStore()
diyStore.editComponent.ignore = [] //
diyStore.editComponent.ignore = ['componentBgUrl'] //
//
diyStore.editComponent.verify = (index: number) => {

View File

@ -4,6 +4,11 @@
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('imageSet') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('sameScreen')" v-if="diyStore.currentIndex == 0">
<el-switch v-model="diyStore.editComponent.isSameScreen" />
<div class="text-sm text-gray-400 leading-[1.4]">{{ t('imageAdsSameScreenTips') }}</div>
</el-form-item>
<el-form-item :label="t('imageHeight')" class="display-block">
<el-input v-model.trim="diyStore.editComponent.imageHeight" :placeholder="t('imageHeightPlaceholder')" clearable maxlength="10" @blur="blurImageHeight">
@ -52,7 +57,7 @@ import { range } from 'lodash-es'
import useDiyStore from '@/stores/modules/diy'
const diyStore = useDiyStore()
diyStore.editComponent.ignore = [] //
diyStore.editComponent.ignore = ['componentBgUrl'] //
//
diyStore.editComponent.verify = (index: number) => {
@ -124,7 +129,7 @@ const handleHeight = (isCalcHeight:boolean = false)=> {
}
const blurImageHeight = () => {
diyStore.editComponent.imageHeight = parseInt(diyStore.editComponent.imageHeight)
diyStore.editComponent.imageHeight = diyStore.editComponent.imageHeight ? parseInt(diyStore.editComponent.imageHeight) : 0
}
const imageBoxRef = ref()

View File

@ -1,3 +1,4 @@
<template>
<!-- 内容 -->
<div class="content-wrap" v-show="diyStore.editTab == 'content'">
@ -12,11 +13,11 @@
</el-form-item>
</el-form>
<el-dialog v-model="showDialog" :title="t('selectStyle')" width="500px">
<el-dialog v-model="showDialog" :title="t('selectStyle')" width="660px">
<div class="flex flex-wrap">
<template v-for="(item,index) in styleList" :key="index">
<div :class="{ 'border-primary': selectStyle.value == item.value }" @click="changeStyle(item)" class="flex items-center justify-center overflow-hidden w-[200px] h-[100px] mr-[12px] cursor-pointer border bg-gray-50">
<div :class="{ 'border-primary': selectStyle.value == item.value, '!mr-[0]': [(index+1)%3] == 0 }" @click="changeStyle(item)" class="flex my-[5px] items-center justify-center overflow-hidden w-[200px] h-[100px] mr-[12px] cursor-pointer border bg-gray-50">
<img :src="img(item.url)" />
</div>
</template>
@ -80,6 +81,16 @@ const styleList = reactive([
url: 'static/resource/images/diy/member/member_level_style3.jpg',
title: '风格3',
value: 'style-3'
},
{
url: 'static/resource/images/diy/member/member_level_style4.png',
title: '风格4',
value: 'style-4'
},
{
url: 'static/resource/images/diy/member/member_level_style5.png',
title: '风格5',
value: 'style-5'
}
])
@ -91,9 +102,31 @@ const changeStyle = (item:any) => {
const confirmStyle = () => {
diyStore.editComponent.styleName = selectStyle.title;
diyStore.editComponent.style = selectStyle.value;
initStyle(diyStore.editComponent.style);
showDialog.value = false
}
const initStyle = (style: any) => {
if (style == 'style-1') {
diyStore.editComponent.bottomRounded = 0;
diyStore.editComponent.topRounded = 12;
} else if (style == 'style-2') {
diyStore.editComponent.bottomRounded = 0;
diyStore.editComponent.topRounded = 12;
} else if (style == 'style-3') {
diyStore.editComponent.bottomRounded = 12;
diyStore.editComponent.topRounded = 12;
} else if (style == 'style-4') {
diyStore.editComponent.bottomRounded = 12;
diyStore.editComponent.topRounded = 12;
} else if (style == 'style-5') {
diyStore.editComponent.bottomRounded = 12;
diyStore.editComponent.topRounded = 12;
}
}
defineExpose({})
</script>

View File

@ -11,14 +11,14 @@
</el-radio-group>
</el-form-item>
<div class="flex items-center flex-wrap py-[8px] px-[10px] bg-[#f4f3f7] rounded mb-[18px] mx-[18px]" v-show="diyStore.editComponent.noticeType == 'img'">
<div :class="['mr-[10px] rounded cursor-pointer', {'border-[1px] border-solid border-[var(--el-color-primary)]': diyStore.editComponent.systemUrl == 'style_1' && diyStore.editComponent.imgType == 'system'}]">
<div :class="['mr-[10px] rounded cursor-pointer border-[1px] border-solid', {'border-[var(--el-color-primary)]': diyStore.editComponent.systemUrl == 'style_1' && diyStore.editComponent.imgType == 'system'}]">
<img src="@/app/assets/images/diy/notice/style_1.png" :class="['h-[28px] px-[10px] py-[5px]']" @click="changeStyle('style_1')"/>
</div>
<div :class="['mr-[10px] rounded cursor-pointer w-[100px]', {'border-[1px] border-solid border-[var(--el-color-primary)]': diyStore.editComponent.systemUrl == 'style_2' && diyStore.editComponent.imgType == 'system'}]">
<div :class="['mr-[10px] rounded cursor-pointer w-[100px] border-[1px] border-solid', {'border-[var(--el-color-primary)]': diyStore.editComponent.systemUrl == 'style_2' && diyStore.editComponent.imgType == 'system'}]">
<img src="@/app/assets/images/diy/notice/style_2.png" class="px-[10px] py-[5px]" @click="changeStyle('style_2')"/>
</div>
<div @click.stop="diyStore.editComponent.imgType = 'diy'" :class="['mr-[10px] rounded cursor-pointer diy-upload-img', {'border-[1px] border-solid border-[var(--el-color-primary)]': (diyStore.editComponent.imageUrl && diyStore.editComponent.imgType == 'diy') }]">
<div @click.stop="diyStore.editComponent.imgType = 'diy'" :class="['mr-[10px] rounded cursor-pointer diy-upload-img border-[1px] border-solid', {'border-[var(--el-color-primary)]': (diyStore.editComponent.imageUrl && diyStore.editComponent.imgType == 'diy') }]">
<upload-image v-model="diyStore.editComponent.imageUrl" :limit="1"/>
</div>
</div>
@ -80,7 +80,7 @@
<h3 class="mb-[10px]">{{ t('textSet') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('textFontSize')">
<el-slider v-model="diyStore.editComponent.fontSize" show-input size="small" class="ml-[10px] article-slider" :min="12" :max="20"/>
<el-slider v-model="diyStore.editComponent.fontSize" show-input size="small" class="ml-[10px] diy-nav-slider" :min="12" :max="20"/>
</el-form-item>
<el-form-item :label="t('textFontWeight')">
<el-radio-group v-model="diyStore.editComponent.fontWeight">

View File

@ -104,7 +104,7 @@
</el-radio-group>
</el-form-item>
<el-form-item :label="t('bgHeightScale')">
<el-slider v-model="diyStore.global.bgHeightScale" show-input size="small" class="ml-[10px] horz-blank-slider"/>
<el-slider v-model="diyStore.global.bgHeightScale" show-input size="small" class="ml-[10px] diy-nav-slider"/>
</el-form-item>
<div class="text-sm text-gray-400 ml-[80px] mb-[10px]">{{ t('bgHeightScaleTip') }}</div>
<el-form-item :label="t('bgUrl')">
@ -133,7 +133,7 @@
<h3 class="mb-[10px]">{{ t('marginSet') }}</h3>
<el-form label-width="115px" class="px-[10px]">
<el-form-item :label="t('marginBoth')">
<el-slider v-model="diyStore.global.template.margin.both" show-input size="small" @input="inputBoth" class="ml-[10px] horz-blank-slider"/>
<el-slider v-model="diyStore.global.template.margin.both" show-input size="small" @input="inputBoth" class="ml-[10px] diy-nav-slider"/>
</el-form-item>
</el-form>
</div>

View File

@ -0,0 +1,156 @@
<template>
<!-- 内容 -->
<div class="content-wrap" v-show="diyStore.editTab == 'content'">
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('pictureShowBlockOne') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('image')">
<upload-image v-model="diyStore.editComponent.moduleOne.head.textImg" :limit="1"/>
</el-form-item>
<el-form-item :label="t('subTitle')">
<el-input v-model.trim="diyStore.editComponent.moduleOne.head.subText" :placeholder="t('subTitlePlaceholder')" clearable maxlength="8" show-word-limit />
</el-form-item>
<el-form-item :label="t('subTitleTextColor')">
<el-color-picker v-model="diyStore.editComponent.moduleOne.head.subTextColor" show-alpha />
</el-form-item>
<el-form-item :label="t('pictureShowBgColor')">
<el-color-picker v-model="diyStore.editComponent.moduleOne.listFrame.startColor" show-alpha />
<icon name="iconfont iconmap-connect" size="20px" class="block !text-gray-400 mx-[5px]"/>
<el-color-picker v-model="diyStore.editComponent.moduleOne.listFrame.endColor" show-alpha />
</el-form-item>
<div v-for="(item,index) in diyStore.editComponent.moduleOne.list" :key="item.id" class="item-wrap p-[10px] pb-0 relative border border-dashed border-gray-300 mb-[16px]">
<el-form-item :label="t('image')">
<upload-image v-model="item.imageUrl" :limit="1"/>
</el-form-item>
<el-form-item :label="t('pictureShowBtnText')">
<el-input v-model.trim="item.btnTitle.text" :placeholder="t('activeCubeTitlePlaceholder')" clearable maxlength="4" show-word-limit/>
</el-form-item>
<el-form-item :label="t('pictureShowBtnColor')">
<el-color-picker v-model="item.btnTitle.color" show-alpha />
</el-form-item>
<el-form-item :label="t('pictureShowBtnBgColor')">
<el-color-picker v-model="item.btnTitle.startColor" show-alpha />
<icon name="iconfont iconmap-connect" size="20px" class="block !text-gray-400 mx-[5px]"/>
<el-color-picker v-model="item.btnTitle.endColor" show-alpha />
</el-form-item>
<el-form-item :label="t('link')">
<diy-link v-model="item.link"/>
</el-form-item>
</div>
</el-form>
</div>
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('pictureShowBlockTwo') }}</h3>
<el-form label-width="90px" class="px-[10px]">
<el-form-item :label="t('image')">
<upload-image v-model="diyStore.editComponent.moduleTwo.head.textImg" :limit="1"/>
</el-form-item>
<el-form-item :label="t('subTitle')">
<el-input v-model.trim="diyStore.editComponent.moduleTwo.head.subText" :placeholder="t('subTitlePlaceholder')" clearable maxlength="8" show-word-limit />
</el-form-item>
<el-form-item :label="t('subTitleTextColor')">
<el-color-picker v-model="diyStore.editComponent.moduleTwo.head.subTextColor" show-alpha />
</el-form-item>
<el-form-item :label="t('pictureShowBgColor')">
<el-color-picker v-model="diyStore.editComponent.moduleTwo.listFrame.startColor" show-alpha />
<icon name="iconfont iconmap-connect" size="20px" class="block !text-gray-400 mx-[5px]"/>
<el-color-picker v-model="diyStore.editComponent.moduleTwo.listFrame.endColor" show-alpha />
</el-form-item>
<div v-for="(item,index) in diyStore.editComponent.moduleTwo.list" :key="item.id" class="item-wrap p-[10px] pb-0 relative border border-dashed border-gray-300 mb-[16px]">
<el-form-item :label="t('image')">
<upload-image v-model="item.imageUrl" :limit="1"/>
</el-form-item>
<el-form-item :label="t('pictureShowBtnText')">
<el-input v-model.trim="item.btnTitle.text" :placeholder="t('activeCubeTitlePlaceholder')" clearable maxlength="4" show-word-limit/>
</el-form-item>
<el-form-item :label="t('pictureShowBtnColor')">
<el-color-picker v-model="item.btnTitle.color" show-alpha />
</el-form-item>
<el-form-item :label="t('pictureShowBtnBgColor')">
<el-color-picker v-model="item.btnTitle.startColor" show-alpha />
<icon name="iconfont iconmap-connect" size="20px" class="block !text-gray-400 mx-[5px]"/>
<el-color-picker v-model="item.btnTitle.endColor" show-alpha />
</el-form-item>
<el-form-item :label="t('link')">
<diy-link v-model="item.link"/>
</el-form-item>
</div>
</el-form>
</div>
</div>
<!-- 样式 -->
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
<div class="edit-attr-item-wrap">
<h3 class="mb-[10px]">{{ t('pictureShowBlockStyle') }}</h3>
<el-form label-width="90px" class="px-[10px]">
<el-form-item :label="t('topRounded')">
<el-slider v-model="diyStore.editComponent.moduleRounded.topRounded" show-input size="small" class="ml-[10px] diy-nav-slider" :max="100"/>
</el-form-item>
<el-form-item :label="t('bottomRounded')">
<el-slider v-model="diyStore.editComponent.moduleRounded.bottomRounded" show-input size="small" class="ml-[10px] diy-nav-slider" :max="100"/>
</el-form-item>
</el-form>
</div>
<!-- 组件样式 -->
<slot name="style"></slot>
</div>
</template>
<script lang="ts" setup>
import { t } from '@/lang'
import useDiyStore from '@/stores/modules/diy'
import { img } from '@/utils/common'
import { ref, reactive } from 'vue'
const diyStore = useDiyStore()
diyStore.editComponent.ignore = ['componentBgUrl'] //
//
diyStore.editComponent.verify = (index: number) => {
const res = { code: true, message: '' }
diyStore.value[index].moduleOne.list.forEach((item: any) => {
if (item.imageUrl === '') {
res.code = false
res.message = t('imageUrlTip')
return res
}
})
diyStore.value[index].moduleTwo.list.forEach((item: any) => {
if (item.imageUrl === '') {
res.code = false
res.message = t('imageUrlTip')
return res
}
})
return res
}
defineExpose({})
</script>
<style lang="scss" scoped>
</style>

View File

@ -54,13 +54,13 @@
<h3 class="mb-[10px]">{{ t('rubikCubeStyle') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('imageGap')">
<el-slider v-model="diyStore.editComponent.imageGap" show-input size="small" class="ml-[10px] horz-blank-slider" :max="30"/>
<el-slider v-model="diyStore.editComponent.imageGap" show-input size="small" class="ml-[10px] diy-nav-slider" :max="30"/>
</el-form-item>
<el-form-item :label="t('topRounded')">
<el-slider v-model="diyStore.editComponent.topElementRounded" show-input size="small" class="ml-[10px] horz-blank-slider" :max="50"/>
<el-slider v-model="diyStore.editComponent.topElementRounded" show-input size="small" class="ml-[10px] diy-nav-slider" :max="50"/>
</el-form-item>
<el-form-item :label="t('bottomRounded')">
<el-slider v-model="diyStore.editComponent.bottomElementRounded" show-input size="small" class="ml-[10px] horz-blank-slider" :max="50"/>
<el-slider v-model="diyStore.editComponent.bottomElementRounded" show-input size="small" class="ml-[10px] diy-nav-slider" :max="50"/>
</el-form-item>
</el-form>
</div>

View File

@ -37,7 +37,7 @@
<el-input v-model.trim="diyStore.editComponent.subTitle.text" :placeholder="t('subTitlePlaceholder')" clearable maxlength="30" show-word-limit />
</el-form-item>
<el-form-item :label="t('textFontSize')">
<el-slider v-model="diyStore.editComponent.subTitle.fontSize" show-input size="small" class="ml-[10px] article-slider" :min="12" :max="16" />
<el-slider v-model="diyStore.editComponent.subTitle.fontSize" show-input size="small" class="ml-[10px] diy-nav-slider" :min="12" :max="16" />
</el-form-item>
<el-form-item :label="t('textColor')">
<el-color-picker v-model="diyStore.editComponent.subTitle.color" />
@ -91,7 +91,7 @@
<h3 class="mb-[10px]">{{ t('titleStyle') }}</h3>
<el-form label-width="80px" class="px-[10px]">
<el-form-item :label="t('textFontSize')">
<el-slider v-model="diyStore.editComponent.fontSize" show-input size="small" class="ml-[10px] article-slider" :min="12" :max="30" />
<el-slider v-model="diyStore.editComponent.fontSize" show-input size="small" class="ml-[10px] diy-nav-slider" :min="12" :max="30" />
</el-form-item>
<el-form-item :label="t('textFontWeight')">
<el-radio-group v-model="diyStore.editComponent.fontWeight">

View File

@ -54,12 +54,12 @@
<el-button class="page-btn absolute right-[20px]" @click="diyStore.changeCurrentIndex(-99)">{{ t('pageSet') }}</el-button>
<div class="diy-view-wrap w-[375px] shadow-lg mx-auto">
<div class="preview-head bg-no-repeat bg-center bg-cover cursor-pointer h-[64px]" :class="[diyStore.global.topStatusBar.style]" :style="{backgroundColor :diyStore.global.topStatusBar.bgColor}" @click="diyStore.changeCurrentIndex(-99)">
<div v-if="diyStore.global.topStatusBar.style == 'style-1'" class="content-wrap">
<div v-if="diyStore.global.topStatusBar.style == 'style-1' && diyStore.global.topStatusBar.isShow" class="content-wrap">
<div class="title-wrap" :style="{ fontSize: '14px', color: diyStore.global.topStatusBar.textColor, textAlign: diyStore.global.topStatusBar.textAlign }">
{{ diyStore.global.title }}
</div>
</div>
<div v-if="diyStore.global.topStatusBar.style == 'style-2'" class="content-wrap">
<div v-if="diyStore.global.topStatusBar.style == 'style-2' && diyStore.global.topStatusBar.isShow" class="content-wrap">
<div class="title-wrap" :style="{ color: diyStore.global.topStatusBar.textColor }">
<div class="h-[28px] max-w-[150px] mr-[8px]" v-if="diyStore.global.topStatusBar.imgUrl">
<img class="max-w-[100%] max-h-[100%]" :src="img(diyStore.global.topStatusBar.imgUrl)" mode="heightFix" />
@ -67,16 +67,16 @@
<div :style="{ color: diyStore.global.topStatusBar.textColor }">{{ diyStore.global.title }}</div>
</div>
</div>
<div v-if="diyStore.global.topStatusBar.style == 'style-3'" class="content-wrap">
<div v-if="diyStore.global.topStatusBar.style == 'style-3' && diyStore.global.topStatusBar.isShow" class="content-wrap">
<div class="title-wrap" v-if="diyStore.global.topStatusBar.imgUrl">
<img class="max-w-[100%] max-h-[100%]" :src="img(diyStore.global.topStatusBar.imgUrl)" />
</div>
<div class="search">
<span class="iconfont iconsousuo absolute left-[10px]"></span>
<span class="text-[14px]">{{diyStore.global.topStatusBar.inputPlaceholder}}</span>
<span class="nc-iconfont nc-icon-sousuo-duanV6xx1 !text-[12px] absolute left-[10px]"></span>
<span class="text-[12px]">{{diyStore.global.topStatusBar.inputPlaceholder}}</span>
</div>
</div>
<div v-if="diyStore.global.topStatusBar.style == 'style-4'" class="content-wrap">
<div v-if="diyStore.global.topStatusBar.style == 'style-4' && diyStore.global.topStatusBar.isShow" class="content-wrap">
<span class="iconfont iconxiazai19 !text-[14px]" :style="{ color: diyStore.global.topStatusBar.textColor }"></span>
<div class="title-wrap" :style="{ color: diyStore.global.topStatusBar.textColor }">我的位置</div>
<span class="iconfont iconxiangyoujiantou !text-[12px]" :style="{ color: diyStore.global.topStatusBar.textColor }"></span>
@ -109,7 +109,7 @@
<div class="font-bold text-xl mb-[40px]">{{ t('developTitle') }}</div>
<div class="mb-[20px] flex flex-col">
<text class="mb-[10px]">{{ t('wapDomain') }}</text>
<el-input v-model="wapDomain" :placeholder="t('wapDomainPlaceholder')" clearable />
<el-input v-model.trim="wapDomain" :placeholder="t('wapDomainPlaceholder')" clearable />
</div>
<el-button type="primary" @click="saveWapDomain">{{ t('confirm') }}</el-button>
<el-button type="primary" @click="settingTips()" plain>{{ t('settingTips') }}</el-button>
@ -165,7 +165,7 @@
</el-form-item>
<el-form-item :label="t('componentBgAlpha')" v-if="diyStore.editComponent.ignore.indexOf('componentBgUrl') == -1 && diyStore.editComponent.componentBgUrl">
<el-slider v-model="diyStore.editComponent.componentBgAlpha" show-input size="small" :min="0" :max="10" class="ml-[10px] horz-blank-slider" />
<el-slider v-model="diyStore.editComponent.componentBgAlpha" show-input size="small" :min="0" :max="10" class="ml-[10px] diy-nav-slider" />
</el-form-item>
<el-form-item :label="t('componentBgColor')" v-if="diyStore.editComponent.ignore.indexOf('componentBgColor') == -1">
@ -182,19 +182,19 @@
</el-form-item>
<el-form-item :label="t('marginTop')" v-if="diyStore.editComponent.ignore.indexOf('marginTop') == -1">
<el-slider v-model="diyStore.editComponent.margin.top" show-input size="small" :min="-100" class="ml-[10px] horz-blank-slider" />
<el-slider v-model="diyStore.editComponent.margin.top" show-input size="small" :min="-100" class="ml-[10px] diy-nav-slider" />
</el-form-item>
<el-form-item :label="t('marginBottom')" v-if="diyStore.editComponent.ignore.indexOf('marginBottom') == -1">
<el-slider v-model="diyStore.editComponent.margin.bottom" show-input size="small" class="ml-[10px] horz-blank-slider" />
<el-slider v-model="diyStore.editComponent.margin.bottom" show-input size="small" class="ml-[10px] diy-nav-slider" />
</el-form-item>
<el-form-item :label="t('marginBoth')" v-if="diyStore.editComponent.ignore.indexOf('marginBoth') == -1">
<el-slider v-model="diyStore.editComponent.margin.both" show-input size="small" class="ml-[10px] horz-blank-slider" />
<el-slider v-model="diyStore.editComponent.margin.both" show-input size="small" class="ml-[10px] diy-nav-slider" />
</el-form-item>
<el-form-item :label="t('topRounded')" v-if="diyStore.editComponent.ignore.indexOf('topRounded') == -1">
<el-slider v-model="diyStore.editComponent.topRounded" show-input size="small" class="ml-[10px] horz-blank-slider" :max="100" />
<el-slider v-model="diyStore.editComponent.topRounded" show-input size="small" class="ml-[10px] diy-nav-slider" :max="100" />
</el-form-item>
<el-form-item :label="t('bottomRounded')" v-if="diyStore.editComponent.ignore.indexOf('bottomRounded') == -1">
<el-slider v-model="diyStore.editComponent.bottomRounded" show-input size="small" class="ml-[10px] horz-blank-slider" :max="100" />
<el-slider v-model="diyStore.editComponent.bottomRounded" show-input size="small" class="ml-[10px] diy-nav-slider" :max="100" />
</el-form-item>
</el-form>
</div>
@ -237,6 +237,8 @@ route.query.type = route.query.type || '' // 页面模板,新页面传入
route.query.title = route.query.title || ''
route.query.back = route.query.back || '/site/diy/list'
console.log('route',route.path)
const backPath = route.query.back
const template = ref('');
const oldTemplate = ref('');
@ -298,6 +300,9 @@ const modulesFiles = import.meta.glob('./components/*.vue', { eager: true })
const addonModulesFiles = import.meta.glob('@/addon/**/views/diy/components/*.vue', { eager: true })
addonModulesFiles && Object.assign(modulesFiles, addonModulesFiles)
// todo 便
// todo 使
const modules = {}
for (const [key, value] of Object.entries(modulesFiles)) {
const moduleName = key.split('/').pop()
@ -337,15 +342,14 @@ const changeTemplatePage = (value:any)=> {
type: 'warning'
}).then(() => {
diyStore.changeCurrentIndex(-99)
diyStore.init(); //
if (value) {
let data = templatePages[value].data;
let data = cloneDeep(templatePages[value].data);
diyStore.global = data.global;
if (data.value.length) {
diyStore.value = data.value
}
} else {
//
diyStore.init();
if (route.query.title) diyStore.global.title = diyStore.typeName
}
}).catch(() => {
@ -353,15 +357,14 @@ const changeTemplatePage = (value:any)=> {
template.value = oldTemplate.value;
});
} else {
diyStore.init(); //
if (value) {
let data = templatePages[value].data;
let data = cloneDeep(templatePages[value].data);
diyStore.global = data.global;
if (data.value.length) {
diyStore.value = data.value
}
} else {
//
diyStore.init();
if (route.query.title) diyStore.global.title = diyStore.typeName
}
}
@ -681,6 +684,11 @@ const settingTips = () => {
}
}
}
.diy-nav-slider {
.el-slider__input {
width: 100px;
}
}
</style>
<style lang="scss" scoped>
.full-container {
@ -793,6 +801,7 @@ const settingTips = () => {
align-items: center;
margin-right: 105px;
overflow: hidden;
box-sizing: border-box;
span {
overflow: hidden; //

View File

@ -12,7 +12,7 @@
<div class="font-bold text-xl mb-[40px]">{{ t('developTitle') }}</div>
<div class="mb-[20px] flex flex-col">
<text class="mb-[10px]">{{ t('wapDomain') }}</text>
<el-input v-model="wapDomain" :placeholder="t('wapDomainPlaceholder')" clearable />
<el-input v-model.trim="wapDomain" :placeholder="t('wapDomainPlaceholder')" clearable />
</div>
<div class="flex">
<el-button type="primary" @click="saveDomain()">{{ t('confirm') }}</el-button>
@ -31,7 +31,7 @@
<div class="w-[700px]">
<div class="flex flex-wrap">
<diy-link v-model="link" :ignore="['DIY_LINK']" @success="changePage">
<diy-link v-model="link" :ignore="['DIY_LINK','DIY_JUMP_OTHER_APPLET','DIY_MAKE_PHONE_CALL']" @success="changePage">
<el-button type="primary">{{ t('changePage') }}</el-button>
</diy-link>
<el-button type="primary" @click="toDecorate()" v-show="page.use_template.action == 'decorate'" class="ml-[12px]">{{ t('decorate') }}</el-button>
@ -80,7 +80,7 @@ const page: any = reactive({})
const router = useRouter()
const wapDomain = ref('')
const wapImage = ref('')
const link = ref({
const link: any = ref({
name: ''
})

View File

@ -10,7 +10,7 @@
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
<el-form :inline="true" :model="diyPageTableData.searchParam" ref="searchFormDiyPageRef">
<el-form-item :label="t('title')" prop="title">
<el-input v-model="diyPageTableData.searchParam.title" :placeholder="t('titlePlaceholder')" />
<el-input v-model.trim="diyPageTableData.searchParam.title" :placeholder="t('titlePlaceholder')" />
</el-form-item>
<el-form-item :label="t('forAddon')" prop="addon_name">
<el-select v-model="diyPageTableData.searchParam.addon_name" :placeholder="t('forAddonPlaceholder')" @change="handleSelectAddonChange">
@ -57,6 +57,7 @@
<el-button v-if="row.type == 'DIY_PAGE'" type="primary" link @click="openShare(row)">{{ t('shareSet') }}</el-button>
<el-button type="primary" link @click="editEvent(row)">{{ t('edit') }}</el-button>
<el-button v-if="row.is_default == 0 || row.type == 'DIY_PAGE'" type="primary" link @click="deleteEvent(row.id)">{{ t('delete') }}</el-button>
<el-button type="primary" link @click="copyEvent(row.id)">{{ t('copy') }}</el-button>
</template>
</el-table-column>
@ -70,14 +71,14 @@
</el-card>
<!--添加页面-->
<el-dialog v-model="dialogVisible" :title="t('addPageTips')" width="25%">
<el-dialog v-model="dialogVisible" :title="t('addPageTips')" width="350px">
<el-form :model="formData" label-width="90px" ref="formRef" :rules="formRules">
<el-form-item :label="t('title')" prop="title">
<el-input v-model="formData.title" :placeholder="t('titlePlaceholder')" clearable maxlength="12" show-word-limit class="w-full" />
<el-input v-model.trim="formData.title" :placeholder="t('titlePlaceholder')" clearable maxlength="12" show-word-limit class="w-full" />
</el-form-item>
<el-form-item :label="t('typeName')" prop="type">
<el-select v-model="formData.type" :placeholder="t('pageTypePlaceholder')" class="w-full">
<el-select v-model="formData.type" :placeholder="t('pageTypePlaceholder')" class="!w-full">
<el-option v-for="(item, key) in pageType" :label="item.title" :value="key" :key="key"/>
</el-select>
</el-form-item>
@ -102,10 +103,10 @@
<span>{{ sharePage }}</span>
</el-form-item>
<el-form-item :label="t('shareTitle')" prop="title">
<el-input v-model="shareFormData[tabShareType].title" :placeholder="t('shareTitlePlaceholder')" clearable maxlength="30" show-word-limit />
<el-input v-model.trim="shareFormData[tabShareType].title" :placeholder="t('shareTitlePlaceholder')" clearable maxlength="30" show-word-limit />
</el-form-item>
<el-form-item :label="t('shareDesc')" prop="desc" v-if="tabShareType == 'wechat'">
<el-input v-model="shareFormData[tabShareType].desc" :placeholder="t('shareDescPlaceholder')" type="textarea" rows="4" clearable maxlength="100" show-word-limit />
<el-input v-model.trim="shareFormData[tabShareType].desc" :placeholder="t('shareDescPlaceholder')" type="textarea" rows="4" clearable maxlength="100" show-word-limit />
</el-form-item>
<el-form-item :label="t('shareImageUrl')" prop="url">
<upload-image v-model="shareFormData[tabShareType].url" :limit="1" />
@ -129,6 +130,7 @@ import { t } from '@/lang'
import { getApps,getDiyPageList, deleteDiyPage, getDiyTemplate, editDiyPageShare, setUseDiyPage } from '@/app/api/diy'
import { ElMessageBox, FormInstance } from 'element-plus'
import { useRoute, useRouter } from 'vue-router'
import { setTablePageStorage,getTablePageStorage } from "@/utils/common";
const route = useRoute()
const router = useRouter()
@ -234,12 +236,13 @@ const loadDiyPageList = (page: number = 1) => {
diyPageTableData.loading = false
diyPageTableData.data = res.data.data
diyPageTableData.total = res.data.total
setTablePageStorage(diyPageTableData.page, diyPageTableData.limit, diyPageTableData.searchParam);
}).catch(() => {
diyPageTableData.loading = false
})
}
loadDiyPageList()
loadDiyPageList(getTablePageStorage(diyPageTableData.searchParam).page);
//
const editEvent = (data: any) => {
@ -256,6 +259,30 @@ const setUse = (id: any) => {
loadDiyPageList()
})
}
const repeat = ref(false)
//
const copyEvent = (id: any) => {
ElMessageBox.confirm(t('diyPageCopyTips'), t('warning'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
}
).then(() => {
if (repeat.value) return
repeat.value = true
copyDiy({id: id}).then((res: any) => {
if (res.code == 1) {
loadDiyPageList()
}
repeat.value = false
}).catch(err => {
repeat.value = false
})
})
}
//
const deleteEvent = (id: number) => {

View File

@ -11,7 +11,7 @@
<div class="font-bold text-xl mb-[40px]">{{ t('developTitle') }}</div>
<div class="mb-[20px] flex flex-col">
<text class="mb-[10px]">{{ t('wapDomain') }}</text>
<el-input v-model="wapDomain" :placeholder="t('wapDomainPlaceholder')" clearable />
<el-input v-model.trim="wapDomain" :placeholder="t('wapDomainPlaceholder')" clearable />
</div>
<div class="flex">
<el-button type="primary" @click="saveDomain()">{{ t('confirm') }}</el-button>
@ -105,7 +105,7 @@ const page: any = reactive({})
const router = useRouter()
const wapDomain = ref('')
const wapImage = ref('')
const link = ref({
const link: any = ref({
name: ''
})

View File

@ -9,7 +9,7 @@
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
<el-form :inline="true" :model="diyRouteTableData.searchParam" ref="searchFormDiyRouteRef">
<el-form-item :label="t('title')" prop="title">
<el-input v-model="diyRouteTableData.searchParam.title" :placeholder="t('titlePlaceholder')" />
<el-input v-model.trim="diyRouteTableData.searchParam.title" :placeholder="t('titlePlaceholder')" />
</el-form-item>
<el-form-item :label="t('forAddon')" prop="addon_name">
<el-select v-model="diyRouteTableData.searchParam.addon_name" :placeholder="t('forAddonPlaceholder')">
@ -54,7 +54,7 @@
</el-table-column>
</el-table>
<div class="mt-[16px] flex justify-end">
<el-pagination v-model:current-page="diyRouteTableData.page" v-model:page-size="diyRouteTableData.limit" layout="total, sizes, prev, pager, next, jumper" :total="diyRouteTableData.total" @size-change="loadDiyRouteList()" @current-change="loadDiyRouteList" />
<el-pagination v-model:current-page="diyRouteTableData.page" v-model:page-size="diyRouteTableData.limit" layout="total, sizes, prev, pager, next, jumper" :total="diyRouteTableData.total" @size-change="getDiyRouteListFn" @current-change="loadDiyRouteList" />
</div>
</el-card>
@ -70,10 +70,10 @@
<span>{{ sharePage }}</span>
</el-form-item>
<el-form-item :label="t('shareTitle')" prop="title">
<el-input v-model="shareFormData[tabShareType].title" :placeholder="t('shareTitlePlaceholder')" clearable maxlength="30" show-word-limit />
<el-input v-model.trim="shareFormData[tabShareType].title" :placeholder="t('shareTitlePlaceholder')" clearable maxlength="30" show-word-limit />
</el-form-item>
<el-form-item :label="t('shareDesc')" prop="desc" v-if="tabShareType == 'wechat'">
<el-input v-model="shareFormData[tabShareType].desc" :placeholder="t('shareDescPlaceholder')" type="textarea" rows="4" clearable maxlength="100" show-word-limit />
<el-input v-model.trim="shareFormData[tabShareType].desc" :placeholder="t('shareDescPlaceholder')" type="textarea" rows="4" clearable maxlength="100" show-word-limit />
</el-form-item>
<el-form-item :label="t('shareImageUrl')" prop="url">
<upload-image v-model="shareFormData[tabShareType].url" :limit="1" />
@ -99,6 +99,7 @@ import { ElMessage, FormInstance } from 'element-plus'
import { useRoute, useRouter } from 'vue-router'
import { useClipboard } from '@vueuse/core'
import { getUrl } from '@/app/api/sys'
import { cloneDeep } from 'lodash-es'
const route = useRoute()
const router = useRouter()
@ -121,6 +122,8 @@ const diyRouteTableData = reactive({
}
})
const diyRouteList: any = ref([])
const wapDomain = ref('')
const getDomain = async () => {
wapDomain.value = (await getUrl()).data.wap_url
@ -130,42 +133,60 @@ getDomain()
const apps: any = reactive({}) //
getDiyRouteAppList().then(res=>{
if(res.data){
getDiyRouteAppList().then(res=> {
if (res.data) {
for (const key in res.data) {
apps[key] = res.data[key];
}
}
});
const getDiyRouteListFn = () => {
getDiyRouteList({}).then(res => {
diyRouteTableData.loading = false
diyRouteList.value = cloneDeep(res.data)
loadDiyRouteList(diyRouteTableData.page)
}).catch(() => {
diyRouteTableData.loading = false
});
}
getDiyRouteListFn();
/**
* 获取自定义路由列表
*/
const loadDiyRouteList = (page: number = 1) => {
diyRouteTableData.loading = true
diyRouteTableData.page = page
getDiyRouteList({
page: diyRouteTableData.page,
limit: diyRouteTableData.limit,
...diyRouteTableData.searchParam
}).then(res => {
diyRouteTableData.loading = false
const tempData = cloneDeep(diyRouteList.value)
const data: any = [];
const len = Math.ceil(res.data.length / diyRouteTableData.limit)
const data = JSON.parse(JSON.stringify(res.data))
const dataGather = []
for (let i = 0; i < len; i++) {
dataGather[i] = data.splice(0, diyRouteTableData.limit)
//
for (let i = 0; i < tempData.length; i++) {
let isAdd = true;
if (diyRouteTableData.searchParam.title && tempData[i].title.indexOf(diyRouteTableData.searchParam.title) == -1) {
isAdd = false;
}
diyRouteTableData.data = dataGather[diyRouteTableData.page - 1]
diyRouteTableData.total = res.data.length
}).catch(() => {
diyRouteTableData.loading = false
})
if (diyRouteTableData.searchParam.addon_name && tempData[i].addon_info && tempData[i].addon_info.key != diyRouteTableData.searchParam.addon_name) {
isAdd = false;
}
if (isAdd) {
data.push(tempData[i]);
}
}
diyRouteTableData.total = data.length
const len = Math.ceil(data.length / diyRouteTableData.limit)
const dataGather = []
for (let i = 0; i < len; i++) {
dataGather[i] = data.splice(0, diyRouteTableData.limit)
}
diyRouteTableData.data = dataGather[diyRouteTableData.page - 1]
}
loadDiyRouteList()
//
getDiyTemplate({}).then(res => {
@ -232,7 +253,7 @@ const openShare = async (row: any) => {
name: row.name
})).data
if (info && info.title) {
if (info.title) {
row.id = info.id
row.title = info.title
row.name = info.name
@ -273,7 +294,7 @@ const shareEvent = async (formEl: FormInstance | undefined) => {
share: JSON.stringify(shareFormData),
...diyRouteData
}).then(() => {
loadDiyRouteList()
getDiyRouteListFn()
shareDialogVisible.value = false
}).catch(() => {
})
@ -284,7 +305,7 @@ const shareEvent = async (formEl: FormInstance | undefined) => {
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
loadDiyRouteList()
getDiyRouteListFn()
}
</script>

View File

@ -17,7 +17,13 @@
</template>
</el-table-column>
<el-table-column prop="key" :label="t('key')" min-width="80"/>
<el-table-column prop="key" :label="t('key')" min-width="120"/>
<el-table-column :label="t('type')" min-width="120">
<template #default="{ row }">
<span>{{ row.info.type === 'app' ? t('app') : t('addon') }}</span>
</template>
</el-table-column>
<el-table-column :label="t('operation')" fixed="right" align="right" min-width="160">
<template #default="{ row }">
@ -42,6 +48,7 @@
import { t } from '@/lang'
import { getDiyBottomList } from '@/app/api/diy'
import { useRoute, useRouter } from 'vue-router'
import { cloneDeep } from 'lodash-es'
const route = useRoute()
const router = useRouter()
@ -64,7 +71,7 @@
bottomNavTableData.loading = false
const len = Math.ceil(res.data.length / bottomNavTableData.limit)
const data = JSON.parse(JSON.stringify(res.data))
const data = cloneDeep(res.data)
const dataGather = []
for (let i = 0; i < len; i++) {
dataGather[i] = data.splice(0, bottomNavTableData.limit)

View File

@ -1,7 +1,7 @@
<template>
<div class="main-container">
<el-card class="card !border-none" shadow="never">
<el-page-header :content="pageName" :icon="ArrowLeft" @back="$router.back()" />
<el-page-header :content="pageName" :icon="ArrowLeft" @back="back()" />
</el-card>
<el-card class="box-card mt-[15px] !border-none" shadow="never" v-loading="loading">
@ -31,7 +31,7 @@
<el-tabs v-model="activeName" class="demo-tabs mt-[15px]">
<el-tab-pane :label="t('navImage')" name="navPicture">
<div ref="navItemRef">
<div v-for="(item,index) in diyBottomData.value.list" :key="'a'+index" :data-id="index" class="item-wrap border-2 border-dashed pt-[18px] m-[10px] mb-[15px] relative list-item">
<div v-for="(item,index) in diyBottomData.value.list" :key="'a'+index" :data-id="index" class="item-wrap border-2 border-dashed pt-[18px] m-[10px] mb-[15px] relative list-item" :class="{ 'not-sort': useDrag }">
<el-form-item :label="t('navIconOne')">
<div class="flex align-center">
<div class="flex flex-col justify-center items-center">
@ -45,10 +45,10 @@
</div>
</el-form-item>
<el-form-item :label="t('navTitleOne')">
<el-input class="!w-[215px]" v-model="item.text" :placeholder="t('titleContent')" maxlength="5" show-word-limit />
<el-input class="!w-[215px]" v-model.trim="item.text" :placeholder="t('titleContent')" maxlength="5" show-word-limit />
</el-form-item>
<el-form-item :label="t('navLinkOne')">
<diy-link v-model="item.link"/>
<diy-link v-model="item.link" @confirm="diyLinkFn" />
</el-form-item>
<el-icon class="close-icon cursor-pointer -top-[11px] -right-[8px]" @click="deleteNav(index)">
<CircleCloseFilled />
@ -69,21 +69,21 @@
<el-form-item :label="t('textColor')">
<div class="flex align-center">
<el-color-picker v-model="diyBottomData.value.textColor" />
<el-input class="ml-[10px]" v-model="diyBottomData.value.textColor" disabled />
<el-input class="ml-[10px]" v-model.trim="diyBottomData.value.textColor" disabled />
<el-button class="ml-[10px]" type="primary" @click="diyBottomData.value.textColor = '#333333'">{{ t('reset') }}</el-button>
</div>
</el-form-item>
<el-form-item :label="t('textSelectColor')">
<div class="flex align-center">
<el-color-picker v-model="diyBottomData.value.textHoverColor" />
<el-input class="ml-[10px]" v-model="diyBottomData.value.textHoverColor" disabled />
<el-input class="ml-[10px]" v-model.trim="diyBottomData.value.textHoverColor" disabled />
<el-button class="ml-[10px]" type="primary" @click="diyBottomData.value.textHoverColor = '#333333'">{{ t('reset') }}</el-button>
</div>
</el-form-item>
<el-form-item :label="t('backgroundColor')">
<div class="flex align-center">
<el-color-picker v-model="diyBottomData.value.backgroundColor" />
<el-input class="ml-[10px]" v-model="diyBottomData.value.backgroundColor" disabled />
<el-input class="ml-[10px]" v-model.trim="diyBottomData.value.backgroundColor" disabled />
<el-button class="ml-[10px]" type="primary" @click="diyBottomData.value.backgroundColor = '#FFFFFF'">{{ t('reset') }}</el-button>
</div>
</el-form-item>
@ -238,6 +238,7 @@ onMounted(() => {
const sortable = Sortable.create(navItemRef.value, {
group: 'item-wrap',
animation: 200,
filter: '.not-sort', // .not-sort
onEnd: event => {
const temp = diyBottomData.value.list[event.oldIndex!]
diyBottomData.value.list.splice(event.oldIndex!, 1)
@ -253,6 +254,10 @@ onMounted(() => {
})
})
const useDrag = ref(false)
const diyLinkFn = (val) => {
useDrag.value = val
}
</script>
<style lang="scss" scoped>
.close-icon {

View File

@ -0,0 +1,406 @@
<template>
<el-drawer v-model="showDialog" :title="popTitle" direction="rtl" :before-close="handleClose" class="member-detail-drawer">
<div class="main-container" v-loading="loading">
<div class="bg-page py-[20px] pr-[30px] relative flex">
<div class="member-info w-[250px]">
<div class="flex items-center">
<div class="text-[14px] min-w-[110px] text-right mr-[20px]">{{ t('headimg') }}</div>
<div class="flex items-end text-[14px]">
<div class="w-[50px] h-[50px] flex items-center justify-center">
<img class="max-w-[50px] max-h-[50px] inline-block" v-if="formData.headimg" :src="img(formData.headimg)" alt="">
<img class="max-w-[50px] max-h-[50px] inline-block rounded-full" v-else src="@/app/assets/images/member_head.png" alt="">
</div>
<el-icon @click="editMemberInfo('headimg')" class="-bottom-[2px] -right-[4px] cursor-pointer">
<EditPen color="#273CE2" />
</el-icon>
</div>
</div>
<div class="flex items-center mt-[20px]">
<span class="text-[14px] min-w-[110px] text-right mr-[20px]">UID</span>
<span class="member-info-item text-[14px] text-[#666666] font-bold w-[150px]">
{{ formData.member_no || t('notAvailable') }}
</span>
</div>
</div>
<div class="flex flex-1 justify-between">
<div class="statistic-card">
<el-statistic :value="formData.point">
<template #title>
<div style="display: inline-flex; align-items: center">
<span class="text-[14px]">
{{ t('point') }}
</span>
<el-tooltip effect="dark" :content="t('adjust')" placement="top">
<el-icon @click="adjustPoint(formData)" class="ml-2 cursor-pointer" :size="12">
<EditPen color="#273CE2" />
</el-icon>
</el-tooltip>
<el-tooltip effect="dark" :content="t('detail')" placement="top">
<el-icon @click="infoPoint(formData)" class="ml-2 cursor-pointer" :size="12">
<View />
</el-icon>
</el-tooltip>
</div>
</template>
</el-statistic>
<div class="statistic-footer">
<div class="footer-item text-[14px] text-secondary">
<span>{{ t('accumulative') }}</span>
<span class="green ml-1">
{{ formData.point_get }}
</span>
</div>
</div>
</div>
<div class="statistic-card">
<el-statistic :value="formData.balance">
<template #title>
<div style="display: inline-flex; align-items: center">
<span class="text-[14px]">
{{ t('balance') }}
</span>
<el-tooltip effect="dark" :content="t('adjust')" placement="top">
<el-icon @click="adjustBalance(formData)" class="ml-2 cursor-pointer" :size="12">
<EditPen color="#273CE2" />
</el-icon>
</el-tooltip>
<el-tooltip effect="dark" :content="t('detail')" placement="top">
<el-icon @click="infoBalance(formData)" class="ml-2 cursor-pointer" :size="12">
<View />
</el-icon>
</el-tooltip>
</div>
</template>
</el-statistic>
<div class="statistic-footer">
<div class="footer-item text-[14px] text-secondary">
<span>{{ t('accumulative') }}</span>
<span class="red ml-1">
{{ formData.balance_get }}
</span>
</div>
</div>
</div>
<div class="statistic-card">
<el-statistic :value="formData.growth">
<template #title>
<div style="display: inline-flex; align-items: center">
<span class="text-[14px]">
{{ t('growth') }}
</span>
<el-tooltip effect="dark" :content="t('detail')" placement="top">
<el-icon @click="infoGrowth(formData)" class="ml-2 cursor-pointer" :size="12">
<View />
</el-icon>
</el-tooltip>
</div>
</template>
</el-statistic>
</div>
<div class="statistic-card">
<el-statistic :value="formData.money" title="New transactions today">
<template #title>
<div style="display: inline-flex; align-items: center">
<span class="text-[14px]">
{{ t("money") }}
</span>
<el-tooltip effect="dark" :content="t('detail')" placement="top">
<el-icon @click="infoBalance(formData)" class="ml-2 cursor-pointer" :size="12">
<View />
</el-icon>
</el-tooltip>
</div>
</template>
</el-statistic>
<div class="statistic-footer">
<div class="footer-item text-[14px] text-secondary">
<span>{{ t('accumulative') }}</span>
<span class="green ml-1">
{{ formData.money_get }}
</span>
</div>
</div>
</div>
<div class="statistic-card">
<el-statistic :value="formData.commission" title="New transactions today">
<template #title>
<div style="display: inline-flex; align-items: center ">
<span class="text-[14px]">
{{ t("commission") }}
</span>
<el-tooltip effect="dark" :content="t('detail')" placement="top">
<el-icon @click="infoCommission(formData)" class="ml-2 cursor-pointer" :size="12">
<View />
</el-icon>
</el-tooltip>
</div>
</template>
</el-statistic>
<div class="statistic-footer">
<div class="footer-item text-[14px] text-secondary">
<span>{{ t('accumulative') }}</span>
<span class="green ml-1">
{{ formData.commission_get }}
</span>
</div>
</div>
</div>
</div>
</div>
<div class="flex flex-wrap mt-[20px]">
<div class="flex items-center w-[33.3%] mt-[15px]">
<span class="text-[14px] w-[130px] text-right flex-shrink-0 mr-[20px]">{{ t('urserName') }}</span>
<span class="text-[14px] text-[#666666]">
{{ formData.username || t('notAvailable') }}
</span>
</div>
<div class="flex mt-[15px] w-[33.3%] break-all leading-[21px]">
<span class="text-[14px] w-[130px] text-right flex-shrink-0 mr-[20px]">{{ t('nickname') }}</span>
<span class="text-[14px] text-[#666666]">
{{ formData.nickname || t('notAvailable') }}
</span>
</div>
<div class="flex mt-[15px] w-[33.3%] break-all leading-[21px]">
<span class="text-[14px] w-[130px] text-right flex-shrink-0 mr-[20px]">{{ t('mobile') }}</span>
<span class="text-[14px] text-[#666666]">
{{ formData.mobile || t('notAvailable') }}
</span>
</div>
<div class="flex mt-[15px] w-[33.3%] break-all leading-[21px]">
<span class="text-[14px] w-[130px] text-right flex-shrink-0 mr-[20px]">{{ t('memberLevel') }}</span>
<span class="text-[14px] text-[#666666]">
{{ formData.member_level_name || t('notAvailable') }}<el-icon @click="editMemberInfo('member_level')" class="-bottom-[2px] -right-[4px] cursor-pointer">
<EditPen color="#273CE2" />
</el-icon>
</span>
</div>
<div class="flex mt-[15px] w-[33.3%] break-all leading-[21px]">
<span class="text-[14px] w-[130px] text-right flex-shrink-0 mr-[20px]">{{ t('memberLabel') }}</span>
<span class="text-[14px] text-[#666666]">
{{ formData.member_label_name&&formData.member_label_name.toString() || t('notAvailable') }}<el-icon @click="editMemberInfo('member_label')" class="-bottom-[2px] -right-[4px] cursor-pointer">
<EditPen color="#273CE2" />
</el-icon>
</span>
</div>
<div class="flex mt-[15px] w-[33.3%] break-all leading-[21px]">
<span class="text-[14px] w-[130px] text-right flex-shrink-0 mr-[20px]">{{ t('birthday') }}</span>
<span class="text-[14px] text-[#666666]">
{{ formData.birthday || t('notAvailable') }}<el-icon @click="editMemberInfo('birthday')" class="-bottom-[2px] -right-[4px] cursor-pointer">
<EditPen color="#273CE2" />
</el-icon>
</span>
</div>
<div class="flex mt-[15px] w-[33.3%] break-all leading-[21px]">
<span class="text-[14px] w-[130px] text-right flex-shrink-0 mr-[20px]">{{ t('sex') }}</span>
<span class="text-[14px] text-[#666666]">
{{ formData.sex == 1 && t('manSex') || formData.sex == 2 && t('girlSex') || t('secrecySex') }}<el-icon @click="editMemberInfo('sex')" class="-bottom-[2px] -right-[4px] cursor-pointer">
<EditPen color="#273CE2" />
</el-icon>
</span>
</div>
<div class="flex mt-[15px] w-[33.3%] break-all leading-[21px]">
<span class="text-[14px] w-[130px] text-right flex-shrink-0 mr-[20px]">{{ t('wxUnionid') }}</span>
<span class="text-[14px] text-[#666666]">
{{ formData.wx_unionid || t('notAvailable') }}
</span>
</div>
<div class="flex mt-[15px] w-[33.3%] break-all leading-[21px]">
<span class="text-[14px] w-[130px] text-right flex-shrink-0 mr-[20px]">{{ t('weappOpenid') }}</span>
<span class="text-[14px] text-[#666666]">
{{ formData.weapp_openid || t('notAvailable') }}
</span>
</div>
<div class="flex mt-[15px] w-[33.3%] break-all leading-[21px]">
<span class="text-[14px] w-[130px] text-right flex-shrink-0 mr-[20px]">{{ t('wxOpenid') }}</span>
<span class="text-[14px] text-[#666666]">
{{ formData.wx_openid || t('notAvailable') }}
</span>
</div>
<div class="flex mt-[15px] w-[33.3%] break-all leading-[21px]">
<span class="text-[14px] w-[130px] text-right flex-shrink-0 mr-[20px]">{{ t('registeredSource') }}</span>
<span class="text-[14px] text-[#666666]">
{{ formData.register_channel_name || t('notAvailable') }}
</span>
</div>
<div class="flex mt-[15px] w-[33.3%] break-all leading-[21px]">
<span class="text-[14px] w-[130px] text-right flex-shrink-0 mr-[20px]">{{ t('createTime') }}</span>
<span class="text-[14px] text-[#666666]">
{{ formData.create_time || t('notAvailable') }}
</span>
</div>
<div class="flex mt-[15px] w-[33.3%] break-all leading-[21px]">
<span class="text-[14px] w-[130px] text-right flex-shrink-0 mr-[20px]">{{ t('lastVisitTime') }}</span>
<span class="text-[14px] text-[#666666]">
{{ formData.last_visit_time || t('notAvailable') }}
</span>
</div>
</div>
</div>
<point-edit ref="pointDialog" @complete="getMemberInfoFn" />
<balance-edit ref="balanceDialog" @complete="getMemberInfoFn" />
<edit-member ref="editMemberDialog" @complete="getMemberInfoFn()" />
</el-drawer>
</template>
<script lang="ts" setup>
import { reactive, ref, computed } from 'vue'
import { t } from '@/lang'
import { addMember, getMemberList, getMemberNo, getMemberInfo } from '@/app/api/member'
import { FormInstance, ElMessage } from 'element-plus'
import { ArrowLeft } from '@element-plus/icons-vue'
import { useRouter, useRoute } from 'vue-router'
import { img, filterNumber } from '@/utils/common'
import PointEdit from '@/app/views/member/components/member-point-edit.vue'
import BalanceEdit from '@/app/views/member/components/member-balance-edit.vue'
import EditMember from '@/app/views/member/components/edit-member.vue'
import useAppStore from '@/stores/modules/app'
const showDialog = ref(false)
const loading = ref(false)
const repeat = ref(false)
let popTitle: string = '会员详情'
let memberNo: string = ''
let id = '';
const route = useRoute()
const router = useRouter()
const emit = defineEmits(['load'])
const nickname_name_input = ref(true)
const password_input = ref(true)
const password_copy_input = ref(true)
const handleClose = (done: () => void) => {
showDialog.value = false;
}
/**
* 表单数据
*/
const initialFormData = {
member_id: '',
nickname: '',
member_no: '',
init_member_no: '',
mobile: '',
password: '',
password_copy: ''
}
const formData: Record<string, any> = reactive({ ...initialFormData })
const formRef = ref<FormInstance>()
const pointDialog: Record<string, any> | null = ref(null)
const balanceDialog: Record<string, any> | null = ref(null)
const editMemberDialog: Record<string, any> | null = ref(null)
/**
* 修改会员信息
*/
const editMemberInfo = (type: any) => {
const data = ref({
type,
id,
data: formData
})
editMemberDialog.value.setDialogType(data.value)
editMemberDialog.value.showDialog = true
}
/**
* 调整积分
*/
const adjustPoint = (data: any) => {
pointDialog.value.setFormData(data)
pointDialog.value.showDialog = true
}
/**
* 调整余额
*/
const adjustBalance = (data: any) => {
balanceDialog.value.setFormData(data)
balanceDialog.value.showDialog = true
}
/**
* 积分详情
*/
const infoPoint = () => {
router.push(`/member/point?id=${id}`)
}
/**
* 余额详情
*/
const infoBalance = () => {
router.push(`/member/balance?id=${id}`)
}
/**
* 成长值明细
*/
const infoGrowth = () => {
router.push(`/member/growth?id=${id}`)
}
/**
* 佣金详情
*/
const infoCommission = () => {
router.push(`/member/commission?id=${id}`)
}
const getMemberInfoFn = async (bool=false) => {
loading.value = true
if (id) {
const data = await (await getMemberInfo(id)).data
if (!data || Object.keys(data).length == 0) {
ElMessage.error(t('memberNull'))
setTimeout(() => {
router.go(-1)
}, 2000)
return false
}
Object.keys(data).forEach((item) => {
formData[item] = data[item]
})
if (formData?.member_label_array && Object.keys(formData.member_label_array)?.length) {
formData.member_label = Object.values(formData.member_label_array).map((item: any, index) => {
return item.label_id
})
formData.member_label_name = Object.values(formData.member_label_array).map((item: any, index) => {
return item.label_name
})
}
loading.value = false
} else {
loading.value = false
}
if(!bool){
emit('load');
}
}
const setFormData = async (row: any = null) => {
id = row.id;
Object.assign(formData, initialFormData)
getMemberInfoFn(true);
}
defineExpose({
showDialog,
setFormData
})
</script>
<style lang="scss">
.member-detail-drawer{
width: 1000px !important;
}
</style>

View File

@ -6,19 +6,19 @@
<h3 class="panel-title !text-sm">{{ t('commonSetting') }}</h3>
<el-form-item :label="t('logonMode')" prop="type">
<el-checkbox v-model="formData.is_username" :label="t('isUsername')" @change="switchChange($event, 'is_username')" />
<div class="form-tip">{{ t('isUsernameTip') }}</div>
<el-checkbox v-model="formData.is_mobile" :label="t('isMobile')" @change="switchChange($event, 'is_mobile')" />
<el-checkbox v-model="formData.is_username" :true-value="1" :false-value="0" :label="t('isUsername')" />
<div class="form-tip mb-[10px]">{{ t('isUsernameTip') }}</div>
<el-checkbox v-model="formData.is_mobile" :true-value="1" :false-value="0" :label="t('isMobile')" />
<div class="form-tip">{{ t('isMobileTip') }}</div>
</el-form-item>
<el-form-item :label="t('isBindMobile')" prop="formData.is_bind_mobile">
<el-switch v-model="formData.is_bind_mobile" @change="switchChange($event, 'is_bind_mobile')" />
<el-switch v-model="formData.is_bind_mobile" :active-value="1" :inactive-value="0" />
<div class="form-tip">{{ t('isBindMobileTip') }}</div>
</el-form-item>
<el-form-item :label="t('agreement')" prop="formData.agreement_show">
<el-switch v-model="formData.agreement_show" @change="switchChange($event, 'agreement_show')" />
<el-switch v-model="formData.agreement_show" :active-value="1" :inactive-value="0" />
<div class="form-tip">{{ t('agreementTips') }}</div>
</el-form-item>
</el-card>
@ -27,9 +27,28 @@
<h3 class="panel-title !text-sm">{{ t('tripartiteSetting') }}</h3>
<el-form-item :label="t('isAuthRegister')" prop="formData.is_auth_register">
<el-switch v-model="formData.is_auth_register" @change="switchChange($event, 'is_auth_register')" />
<el-switch v-model="formData.is_auth_register" :active-value="1" :inactive-value="0" />
<div class="form-tip">{{ t('isAuthRegisterTip') }}</div>
</el-form-item>
<el-form-item :label="t('isForceAccessUserInfo')" prop="formData.is_force_access_user_info" v-show="formData.is_auth_register == 1">
<el-switch v-model="formData.is_force_access_user_info" :active-value="1" :inactive-value="0" />
<div class="form-tip">{{ t('isForceAccessUserInfoTip') }}</div>
</el-form-item>
</el-card>
<el-card class="box-card mt-[15px] !border-none" shadow="never">
<h3 class="panel-title !text-sm">{{ t('loginPageSet') }}</h3>
<el-form-item :label="t('bgUrl')">
<div>
<upload-image v-model="formData.bg_url" />
<p class="text-[12px] text-[#a9a9a9]">{{ t('bgUrlPlaceholder') }}</p>
</div>
</el-form-item>
<el-form-item :label="t('desc')">
<el-input v-model.trim="formData.desc" :placeholder="t('descPlaceholder')" class="input-width" clearable maxlength="20" show-word-limit />
</el-form-item>
</el-card>
</el-form>
@ -47,59 +66,57 @@ import { t } from '@/lang'
import { getLoginConfig, setLoginConfig } from '@/app/api/member'
import { FormInstance } from 'element-plus'
import { useRoute } from 'vue-router'
import { cloneDeep } from 'lodash-es'
const route = useRoute()
const pageName = route.meta.title
const loading = ref(true)
const ruleFormRef = ref<FormInstance>()
const formData = reactive<Record<string, number | boolean>>({
const formData:any = reactive({
is_username: 0,
is_mobile: 0,
is_auth_register: 0,
is_force_access_user_info: 0,
is_bind_mobile: 0,
agreement_show: 0
agreement_show: 0,
bg_url: '',
logo: '',
desc: ''
})
const formRules = computed(() => {
return {
type: [
{
required: true,
trigger: 'change',
validator: (rule: any, value: any, callback: any) => {
if (!formData.is_mobile && !formData.is_username) {
callback(new Error(t('mobileOrUsernameNoEmpty')))
} else {
callback()
}
}
}
]
// type: [
// {
// required: true,
// trigger: 'change',
// validator: (rule: any, value: any, callback: any) => {
// if (!formData.is_mobile && !formData.is_username) {
// callback(new Error(t('mobileOrUsernameNoEmpty')))
// } else {
// callback()
// }
// }
// }
// ]
}
})
const setFormData = async (id: number = 0) => {
const setFormData = async () => {
const data = await (await getLoginConfig()).data
Object.keys(formData).forEach((key: string) => {
if (data[key] != undefined) formData[key] = Boolean(Number(data[key]))
if (data[key] != undefined) formData[key] = data[key]
})
loading.value = false
}
setFormData()
const switchChange = (val, key) => {
formData[key] = val
}
const onSave = async (formEl: FormInstance | undefined) => {
if (loading.value || !formEl) return
await formEl.validate((valid) => {
if (valid) {
const save = JSON.parse(JSON.stringify(formData))
Object.keys(save).forEach((key) => {
save[key] = Number(save[key])
})
const save = cloneDeep(formData)
setLoginConfig(save).then(() => {
loading.value = false

View File

@ -88,7 +88,8 @@
"detectionLoginOperation": "确定",
"detectionLoginContent": "已检测到有其他账号登录,需要刷新后才能继续操作。",
"detectionLoginTip": "提示",
"layoutStyle": "布局风格"
"layoutStyle": "布局风格",
"tab": "开启标签栏"
},
"axios": {
"unknownError": "未知错误",
@ -176,18 +177,5 @@
"write": "可写",
"cloudbuildSuccess": "编译完成",
"showDialogCloseTips": "编译任务尚未完成,关闭将取消编译,是否要继续关闭?"
},
"generateNumber": "请输入数字",
"generateEmail": "请输入正确的邮箱",
"generateIdCard": "请输入正确的身份证号",
"generateMobile": "请输入正确的手机号",
"generateNumberOrFloat": "只能输入整数或小数",
"generateMax": "最大输入字符不能超出",
"generateMin": "输入字符不能小于",
"generateUnit": "位",
"startDate": "开始时间",
"endDate": "结束时间",
"newcomerNumberColor": "数字颜色",
"newcomerNumberBg": "数字背景色",
"newcomerOtherColor": "文字颜色"
}
}

View File

@ -48,7 +48,6 @@ import useSystemStore from '@/stores/modules/system'
import useUserStore from '@/stores/modules/user'
import { ADMIN_ROUTE,findFirstValidRoute } from "@/router/routers"
import { img, isUrl } from '@/utils/common'
import { getWebsite } from '@/app/api/sys'
import menuItem from './menu-item.vue'
const route = useRoute()
@ -79,17 +78,6 @@ watch(route, () => {
oneMenuActive.value = route.matched[1].name == ADMIN_ROUTE.children[0].name ? route.matched[2].name : route.matched[1].name
}, { immediate: true })
// const logoShow = ref<boolean>(false)
// const getWebsiteFn = ()=>{
// getWebsite().then((res:any)=>{
// logoUrl.value = res.data.icon
// console.log(res)
// logoShow.value = true
// }).catch(()=>{
// logoShow.value = true
// })
// }
// getWebsiteFn()
</script>
<style lang="scss">

View File

@ -19,7 +19,7 @@
<script lang="ts" setup>
import { t } from '@/lang'
import { useRoute, useRouter } from 'vue-router'
import { useRouter } from 'vue-router'
import { ref, computed } from 'vue'
import menuItem from './menu-item.vue'
import { CollectionTag } from '@element-plus/icons-vue'

View File

@ -17,8 +17,6 @@ import { useRoute, useRouter } from 'vue-router'
import useSystemStore from '@/stores/modules/system'
import useUserStore from '@/stores/modules/user'
import menuItem from './menu-item.vue'
import storage from '@/utils/storage'
import { img } from '@/utils/common'
const logo = ref('@/app/assets/images/login_logo.png')
const systemStore = useSystemStore()

View File

@ -18,6 +18,17 @@
<el-color-picker v-model="theme" />
</div>
</div>
<!-- 布局风格 -->
<div class="setting-item mb-[10px]">
<div class="title text-base text-tx-secondary">{{ t('layout.layoutStyle') }}</div>
<div class="flex mt-[10px] layout-style flex-wrap">
<div class="relative w-[125px] h-[100px] border mr-[10px] mb-[10px] hover:border-primary"
:class="{ 'border-primary': currLayout == item.key }" v-for="(item, index) in layouts"
@click="handleSetLayout(item.key)">
<img :src="item.image" alt="" class="w-full h-full">
</div>
</div>
</div>
</el-scrollbar>
</el-drawer>
</div>
@ -31,6 +42,12 @@ import { setThemeColor, img } from '@/utils/common'
import { t } from '@/lang'
import Storage from '@/utils/storage'
const layouts = ref([
{ key: 'admin', image: img('static/resource/images/system/layout_bussiness.png') },
{ key: 'admin_simplicity', image: img('static/resource/images/system/layout_darkside.png') }
])
const currLayout = ref(Storage.get('admin_layout') || 'admin')
const drawer = ref(false)
const systemStore = useSystemStore()
@ -67,6 +84,11 @@ const theme = computed({
setThemeColor(systemStore.theme, systemStore.dark ? 'dark' : 'light')
}
})
const handleSetLayout = (key: string) => {
Storage.set({ key: 'admin_layout', data: key })
location.reload()
}
</script>
<style lang="scss" scoped>

View File

@ -2,7 +2,8 @@
<div>
<el-dropdown @command="clickEvent" :tabindex="1">
<div class="userinfo flex h-full items-center">
<el-avatar :size="25" :icon="UserFilled" :src="userStore.userInfo.head_img ? img(userStore.userInfo.head_img) : ''"/>
<el-avatar v-if="userStore.userInfo.head_img" :size="25" :icon="UserFilled" :src="img(userStore.userInfo.head_img)"/>
<img v-else src="@/app/assets/images/member_head.png" class="w-[25px] rounded-full" />
<div class="user-name pl-[8px]">{{ userStore.userInfo.username }}</div>
<icon name="element ArrowDown" class="ml-[5px]" />
</div>
@ -93,10 +94,10 @@ const getUserInfoFn = ()=>{
userInfoEditRef.value?.open()
}
// --- start
let changePasswordDialog = ref(false)
const changePasswordDialog = ref(false)
const formRef = ref<FormInstance>();
//
let saveInfo = reactive({
const saveInfo = reactive({
original_password: '',
password: '',
password_copy: ''
@ -137,7 +138,6 @@ const submitForm = (formEl: FormInstance | undefined) => {
}
});
}
// --- end
</script>
<style lang="scss" scoped>

View File

@ -0,0 +1,65 @@
<template>
<el-aside class="layout-aside dark w-auto">
<side class="hidden-xs-only slide" />
</el-aside>
</template>
<script lang="ts" setup>
import { watch, computed } from 'vue'
import { useRoute } from 'vue-router'
import side from './side.vue'
import useSystemStore from '@/stores/modules/system'
const systemStore = useSystemStore()
const dark = computed(() => {
return systemStore.dark
})
const route = useRoute()
watch(route, () => {
systemStore.$patch(state => {
state.menuDrawer = false
})
})
</script>
<style lang="scss">
.layout-aside {
//--side-dark-color: #141414;
//background-color: var(--side-dark-color, var(--el-bg-color));
&.bright {
background-color: #F5F7F9;
li {
background-color: #F5F7F9;
&.is-active:not(.is-opened) {
position: relative;
color: #333;
background-color: #fff;
&::after {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 2px;
//background-color: var(--el-menu-active-color);
}
}
}
}
.slide {
border-right: 1px solid var(--el-border-color-extra-light);
}
}
.aside-drawer {
.el-drawer__body {
padding: 0px !important;
}
}
</style>

View File

@ -0,0 +1,88 @@
<template>
<template v-if="meta.show">
<el-sub-menu v-if="meta.type == 0 && routes.children" :index="String(routes.name)">
<template #title>
<div class="w-[16px] h-[16px] relative flex items-center" v-if="props.level == 1">
<icon v-if="meta.icon" :name="meta.icon" class="absolute !w-auto" />
</div>
<span class="ml-[10px]">{{ meta.title }}</span>
</template>
<menu-item v-for="(route, index) in routes.children" :routes="route" :key="index" :level="props.level + 1" />
</el-sub-menu>
<template v-else>
<el-menu-item :index="String(routes.name)" @click="router.push({ name: routes.name })">
<div class="w-[16px] h-[16px] relative flex items-center" v-if="props.level == 1">
<icon v-if="meta.icon" :name="meta.icon" class="absolute !w-auto" />
</div>
<template #title>
<span class="ml-[10px]">{{ meta.title }}</span>
</template>
</el-menu-item>
</template>
</template>
</template>
<script lang="ts" setup>
import { useRouter, useRoute } from 'vue-router'
import { ref, computed, watch } from 'vue'
import { img } from '@/utils/common'
import menuItem from './menu-item.vue'
import useSystemStore from '@/stores/modules/system'
import useUserStore from '@/stores/modules/user'
const router = useRouter()
const route = useRoute()
const userStore = useUserStore()
const routers = useUserStore().routers
const props = defineProps({
routes: {
type: Object,
required: true
},
level: {
type: Number,
default: 1
}
})
const systemStore = useSystemStore()
const meta = computed(() => props.routes.meta)
const addons = computed(() => {
const addons:Record<string, any> = {}
userStore.siteInfo?.apps.forEach((item: any) => { addons[item.key] = item })
userStore.siteInfo?.site_addons.forEach((item: any) => { addons[item.key] = item })
return addons
})
const systemAddonKeys = computed(() => {
return userStore.siteInfo?.site_addons.map((item: any) => item.key)
})
const addonRouters: Record<string, any> = {}
routers.forEach(item => {
item.original_name = item.name
if (item.meta.addon) {
addonRouters[item.meta.addon] = item
}
})
const addonsMenus = ref(null)
watch(route, () => {
if (props.routes.name != 'addon_list') return
if (systemAddonKeys.value.includes(route.meta.addon) && addonRouters[route.meta.addon]) {
addonsMenus.value = addonRouters[route.meta.addon]
} else {
addonsMenus.value = null
}
}, { immediate: true })
</script>
<style lang="scss">
.el-sub-menu{
.el-icon{
width: auto;
}
}
</style>

View File

@ -0,0 +1,125 @@
<template>
<el-container class="h-screen flex flex-col">
<el-main class="menu-wrap">
<el-header class="logo-wrap flex items-center justify-center h-[64px] w-[var(--aside-width)]">
<div class="flex justify-center items-center h-[64px] w-full px-[10px]" v-if="Object.keys(website).length">
<el-image :src="img(website.icon)" class="w-[44px] h-[44px] rounded-[50%]" @error="website.icon = img('static/resource/images/niucloud_icon.jpg')"></el-image>
<div class="flex-1 w-0 overflow-text truncate ml-[10px] text-white" v-if="!systemStore.menuIsCollapse">
<el-tooltip
effect="dark"
:content="website.site_name"
placement="top"
>
{{ website.site_name }}
</el-tooltip>
</div>
</div>
</el-header>
<el-scrollbar class="menu-scrollbar flex-1 h-0">
<el-menu :default-active="route.name" :router="true" :unique-opened="false" :collapse="systemStore.menuIsCollapse" background-color="#1f2531" text-color="#fff" active-text-color="#fff">
<menu-item v-for="(route, index) in menuData" :routes="route" :key="index" />
</el-menu>
<div class="h-[48px]"></div>
</el-scrollbar>
</el-main>
</el-container>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue'
import { useRoute } from 'vue-router'
import useSystemStore from '@/stores/modules/system'
import useUserStore from '@/stores/modules/user'
import menuItem from './menu-item.vue'
import { img, isUrl } from '@/utils/common'
import { findFirstValidRoute } from '@/router/routers'
const systemStore = useSystemStore()
const userStore = useUserStore()
const route = useRoute()
const siteInfo = userStore.siteInfo
const routers = userStore.routers
const addonIndexRoute = userStore.addonIndexRoute
const menuData = ref<Record<string, any>[]>([])
const addonRouters: Record<string, any> = {}
const website = computed(() => {
return systemStore.website
})
routers.forEach(item => {
item.original_name = item.name
if (item.meta.addon == '') {
if (item.children && item.children.length) {
item.name = findFirstValidRoute(item.children)
}
menuData.value.push(item)
} else if (item.meta.addon != '' && siteInfo?.apps.length == 1 && siteInfo?.apps[0].key == item.meta.addon) {
if (item.children) {
item.children.forEach((citem: Record<string, any>) => {
citem.original_name = citem.name
if (citem.children && citem.children.length) {
citem.name = findFirstValidRoute(citem.children)
}
})
menuData.value.unshift(...item.children)
} else {
menuData.value.unshift(item)
}
} else {
addonRouters[item.meta.addon] = item
}
})
//
if (siteInfo?.apps.length > 1) {
const routers:Record<string, any>[] = []
siteInfo?.apps.forEach((item: Record<string, any>) => {
if (addonRouters[item.key]) {
addonRouters[item.key].name = addonIndexRoute[item.key]
routers.push(addonRouters[item.key])
}
})
menuData.value.unshift(...routers)
}
</script>
<style lang="scss">
.logo-wrap {
background: #1f2531;
transition: transform getCssVar('transition-duration');
}
:root{
--aside-width: 200px;
}
.menu-wrap {
padding: 0!important;
background: #1f2531;
display: flex;
flex-direction: column;
.el-menu {
border-right: 0!important;
&:not(.el-menu--collapse) {
width: var(--aside-width);
}
.el-menu-item, .el-sub-menu__title {
--el-menu-item-height: 40px;
}
.el-sub-menu .el-menu-item {
--el-menu-sub-item-height: 40px;
}
.el-menu-item.is-active {
background: var(--el-color-primary);
}
&.el-menu--inline {
background: #282e3a;
}
}
}
</style>

View File

@ -0,0 +1,140 @@
<template>
<el-container class="h-[64px] layout-admin flex items-center justify-between px-[15px]" >
<!-- :class="['h-full px-[10px]',{'layout-header border-b border-color': !dark}]" -->
<div class="left-panel flex items-center text-[14px] leading-[1]">
<div class="navbar-item flex items-center h-full cursor-pointer" @click="toggleMenuCollapse">
<icon name="element Expand" v-if="systemStore.menuIsCollapse" />
<icon name="element Fold" v-else />
</div>
<!-- 刷新当前页 -->
<div class="navbar-item flex items-center h-full cursor-pointer" @click="refreshRouter">
<icon name="element Refresh" />
</div>
<!-- 面包屑导航 -->
<div class="flex items-center h-full pl-[10px] hidden-xs-only">
<el-breadcrumb separator="/">
<el-breadcrumb-item v-for="(route, index) in breadcrumb" :key="index">{{route.meta.title }}</el-breadcrumb-item>
</el-breadcrumb>
</div>
</div>
<div class="right-panel h-full flex items-center justify-end">
<div class="navbar-item flex items-center h-full cursor-pointer">
<layout-setting />
</div>
<!-- 用户信息 -->
<div class="navbar-item flex items-center h-full cursor-pointer">
<user-info />
</div>
</div>
<input type="hidden" v-model="comparisonToken">
<input type="hidden" v-model="comparisonSiteId">
<el-dialog v-model="detectionLoginDialog" :title="t('layout.detectionLoginTip')" width="30%" :close-on-click-modal="false" :close-on-press-escape="false" :show-close="false">
<span>{{ t('layout.detectionLoginContent') }}</span>
<template #footer>
<span class="dialog-footer">
<el-button @click="detectionLoginFn">{{ t('layout.detectionLoginOperation') }}</el-button>
</span>
</template>
</el-dialog>
<el-dialog v-model="showDialog" :title="t('indexTemplate')" width="550px" :destroy-on-close="true" >
<div class="flex flex-wrap">
<div v-for="(items, index) in indexList" :key="index" v-if="index_path == ''">
<div @click="index_path = items.view_path" class="index-item py-[5px] px-[10px] mr-[10px] rounded-[3px] cursor-pointer" :class="items.is_use == 1 ? 'bg-primary text-[#fff]' : '' ">
<span >{{ items.name }}</span>
</div>
</div>
<div v-for="(itemTo, indexTo) in indexList" :key="indexTo" v-else>
<div @click="index_path = itemTo.view_path" class="index-item py-[5px] px-[10px] mr-[10px] rounded-[3px] cursor-pointer" :class="index_path == itemTo.view_path ? 'bg-primary text-[#fff]' : '' ">
<span >{{ itemTo.name }}</span>
</div>
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button type="primary" @click="submitIndex">{{ t('confirm') }}</el-button>
</span>
</template>
</el-dialog>
</el-container>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue'
import useUserStore from '@/stores/modules/user'
import useAppStore from '@/stores/modules/app'
import { useRoute } from 'vue-router'
import { t } from '@/lang'
import storage from '@/utils/storage'
import userInfo from './user-info.vue'
import layoutSetting from './layout-setting.vue'
import useSystemStore from "@/stores/modules/system";
const route = useRoute()
const appStore = useAppStore()
const userStore = useUserStore()
const systemStore = useSystemStore()
// start
const detectionLoginDialog = ref(false)
const comparisonToken = ref('')
const comparisonSiteId = ref('')
if (storage.get('comparisonTokenStorage')) {
comparisonToken.value = storage.get('comparisonTokenStorage')
}
if (storage.get('comparisonSiteIdStorage')) {
comparisonSiteId.value = storage.get('comparisonSiteIdStorage')
}
//
document.addEventListener('visibilitychange', e => {
if (document.visibilityState === 'visible' && (comparisonSiteId.value != storage.get('siteId') || comparisonToken.value != storage.get('token'))) {
detectionLoginDialog.value = true
}
})
systemStore.toggleMenuCollapse(storage.get('menuiscollapse') || false)
const detectionLoginFn = () => {
detectionLoginDialog.value = false
location.reload()
}
// end
//
const refreshRouter = () => {
if (!appStore.routeRefreshTag) return
appStore.refreshRouterView()
}
//
const breadcrumb = computed(() => {
const matched = route.matched.filter(item => { return item.meta.title })
if (matched[0] && matched[0].path == '/') matched.splice(0, 1)
return matched
})
storage.set({ key: 'currHeadMenuName', data: "" })
const toggleMenuCollapse = () => {
systemStore.toggleMenuCollapse(!systemStore.menuIsCollapse)
}
</script>
<style lang="scss" scoped>
.layout-header{
position: relative;
z-index: 5;
border-bottom: 1px solid #e8e9eb;
}
.navbar-item {
padding: 0 8px;
}
.index-item {
border: 1px solid;
border-color: var(--el-color-primary);
&:hover {
color: #fff;
background-color: var(--el-color-primary);
}
}
</style>

View File

@ -0,0 +1,104 @@
<template>
<div class="flex">
<icon name="element Setting" @click="drawer = true" />
<el-drawer v-model="drawer" :title="t('layout.layoutSetting')" size="300px">
<el-scrollbar>
<!-- 黑暗模式 -->
<div class="setting-item flex items-center justify-between mb-[10px]">
<div class="title text-base text-tx-secondary">{{ t('layout.darkMode') }}</div>
<div>
<el-switch v-model="dark" :active-value="true" :inactive-value="false" />
</div>
</div>
<!-- 主题颜色 -->
<div class="setting-item flex items-center justify-between mb-[10px]">
<div class="title text-base text-tx-secondary">{{ t('layout.themeColor') }}</div>
<div>
<el-color-picker v-model="theme" />
</div>
</div>
<!-- 布局风格 -->
<div class="setting-item mb-[10px]">
<div class="title text-base text-tx-secondary">{{ t('layout.layoutStyle') }}</div>
<div class="flex mt-[10px] layout-style flex-wrap">
<div class="relative w-[125px] h-[100px] border mr-[10px] mb-[10px] hover:border-primary"
:class="{ 'border-primary': currLayout == item.key }" v-for="(item, index) in layouts"
@click="handleSetLayout(item.key)">
<img :src="item.image" alt="" class="w-full h-full">
</div>
</div>
</div>
</el-scrollbar>
</el-drawer>
</div>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue'
import useSystemStore from '@/stores/modules/system'
import { useDark, useToggle } from '@vueuse/core'
import { setThemeColor, img } from '@/utils/common'
import { t } from '@/lang'
import Storage from '@/utils/storage'
const layouts = ref([
{ key: 'admin', image: img('static/resource/images/system/layout_bussiness.png') },
{ key: 'admin_simplicity', image: img('static/resource/images/system/layout_darkside.png') }
])
const currLayout = ref(Storage.get('admin_layout') || 'admin')
const drawer = ref(false)
const systemStore = useSystemStore()
const isDark = useDark()
const toggleDark = useToggle(isDark)
const dark = computed({
get() {
return systemStore.dark
},
set(val) {
systemStore.setTheme('dark', val)
toggleDark(val)
setThemeColor(systemStore.theme, systemStore.dark ? 'dark' : 'light')
}
})
const sidebar = computed({
get() {
return systemStore.sidebar
},
set(val) {
systemStore.setTheme('sidebar', val)
setThemeColor(systemStore.theme, systemStore.dark ? 'dark' : 'light')
}
})
const theme = computed({
get() {
return systemStore.theme
},
set(val) {
systemStore.setTheme('theme', val)
setThemeColor(systemStore.theme, systemStore.dark ? 'dark' : 'light')
}
})
const handleSetLayout = (key: string) => {
Storage.set({ key: 'admin_layout', data: key })
location.reload()
}
</script>
<style lang="scss" scoped>
:deep(.el-drawer__header) {
margin-bottom: 0 !important;
}
.layout-style {
&>div:nth-child(2n+2) {
margin-right: 0;
}
}
</style>

View File

@ -0,0 +1,32 @@
<template>
<el-dropdown @command="switchLang" :tabindex="1">
<icon name="iconfont iconfanyi" />
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="zh-cn" :disabled="systemStore.lang == 'zh-cn'">简体中文</el-dropdown-item>
<el-dropdown-item command="en" :disabled="systemStore.lang == 'en'">English</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script lang="ts" setup>
import useSystemStore from '@/stores/modules/system'
import { language } from '@/lang'
import { useRoute } from 'vue-router'
import storage from '@/utils/storage'
const route = useRoute()
const systemStore = useSystemStore()
const switchLang = (command: string) => {
systemStore.$patch((state) => {
state.lang = command
storage.set({ key: 'lang', data: command })
})
language.loadLocaleMessages(route.path, systemStore.lang)
location.reload()
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,147 @@
<template>
<div>
<el-dropdown @command="clickEvent" :tabindex="1">
<div class="userinfo flex h-full items-center">
<el-avatar v-if="userStore.userInfo.head_img" :size="25" :icon="UserFilled" :src="img(userStore.userInfo.head_img)"/>
<img v-else src="@/app/assets/images/member_head.png" class="w-[25px] rounded-full" />
<div class="user-name pl-[8px]">{{ userStore.userInfo.username }}</div>
<icon name="element ArrowDown" class="ml-[5px]" />
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="getUserInfoFn">
<!-- <router-link to="/user/center"> -->
<div class="flex items-center leading-[1] py-[5px]">
<span class="iconfont iconshezhi1 ml-[4px] !text-[14px] mr-[10px]"></span>
<span class="text-[14px]">账号设置</span>
</div>
<!-- </router-link> -->
</el-dropdown-item>
<el-dropdown-item>
<router-link to="/tools/authorize">
<div class="flex items-center leading-[1] py-[5px]">
<span class="iconfont iconshouquanxinxi2 ml-[4px] !text-[14px] mr-[10px]"></span>
<span class="text-[14px]">授权信息</span>
</div>
</router-link>
</el-dropdown-item>
<el-dropdown-item @click="changePasswordDialog=true">
<div class="flex items-center leading-[1] py-[5px]">
<span class="iconfont iconxiugai ml-[4px] !text-[14px] mr-[10px]"></span>
<span class="text-[14px]">修改密码</span>
</div>
</el-dropdown-item>
<el-dropdown-item @click="logout">
<div class="flex items-center leading-[1] py-[5px]">
<span class="iconfont icontuichudenglu ml-[4px] !text-[14px] mr-[10px]"></span>
<span class="text-[14px]">退出登录</span>
</div>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-dialog v-model="changePasswordDialog" width="450px" title="修改密码">
<div>
<el-form :model="saveInfo" label-width="90px" ref="formRef" :rules="formRules" class="page-form">
<el-form-item :label="t('originalPassword')" prop="original_password">
<el-input v-model="saveInfo.original_password" type="password" :placeholder="t('originalPasswordPlaceholder')" clearable class="input-width" />
</el-form-item>
<el-form-item :label="t('newPassword')" prop="password">
<el-input v-model="saveInfo.password" type="password" :placeholder="t('passwordPlaceholder')" clearable class="input-width" />
<div class="form-tip">{{t('passwordTip')}}</div>
</el-form-item>
<el-form-item :label="t('passwordCopy')" prop="password_copy">
<el-input v-model="saveInfo.password_copy" type="password" :placeholder="t('passwordPlaceholder')" clearable class="input-width" />
</el-form-item>
</el-form>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="changePasswordDialog = false">{{t('cancel')}}</el-button>
<el-button type="primary" @click="submitForm(formRef)">{{t('save')}}</el-button>
</span>
</template>
</el-dialog>
<user-info-edit ref="userInfoEditRef" />
</div>
</template>
<script lang="ts" setup>
import { UserFilled } from '@element-plus/icons-vue'
import { reactive, ref } from 'vue'
import type { FormInstance, FormRules, ElNotification } from 'element-plus'
import useUserStore from '@/stores/modules/user'
import { setUserInfo } from '@/app/api/personal'
import { t } from '@/lang'
import { img } from '@/utils/common'
import userInfoEdit from '@/app/components/user-info-edit/index.vue'
const userStore = useUserStore()
const clickEvent = (command: string) => {
switch (command) {
case 'logout':
userStore.logout()
break
}
}
const logout = () => {
userStore.logout();
}
const userInfoEditRef = ref(null)
const getUserInfoFn = ()=>{
userInfoEditRef.value?.open()
}
// --- start
const changePasswordDialog = ref(false)
const formRef = ref<FormInstance>();
//
const saveInfo = reactive({
original_password: '',
password: '',
password_copy: ''
});
//
const formRules = reactive<FormRules>({
original_password: [
{ required: true, message: t("originalPasswordPlaceholder"), trigger: "blur" },
],
password: [
{ required: true, message: t("passwordPlaceholder"), trigger: "blur" },
],
password_copy: [
{ required: true, message: t("passwordPlaceholder"), trigger: "blur" },
]
});
const submitForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.validate((valid) => {
if (valid) {
let msg = "";
if (saveInfo.password && !saveInfo.original_password) msg = t('originalPasswordHint');
if (saveInfo.password && saveInfo.original_password && !saveInfo.password_copy) msg = t('newPasswordHint');
if (saveInfo.password && saveInfo.original_password && saveInfo.password_copy && saveInfo.password != saveInfo.password_copy) msg = t('doubleCipherHint');
if (msg) {
ElNotification({
type: 'error',
message: msg,
})
return;
}
setUserInfo(saveInfo).then((res: any) => {
changePasswordDialog.value = false;
})
} else {
return false
}
});
}
</script>
<style lang="scss" scoped>
.el-popper .el-dropdown-menu{
width: 150px;
}
</style>

View File

@ -0,0 +1,136 @@
<template>
<div class="tab-wrap w-full px-[16px]">
<el-tabs :closable="tabbarStore.tabLength > 1" :model-value="route.path" @tab-click="tabClick" @tab-remove="removeTab">
<el-tab-pane v-for="(tab, key, index) in tabbarStore.tabs" :name="tab.path" :key="index">
<template #label>
<el-dropdown trigger="contextmenu" placement="bottom-start">
<span :class="{ 'text-primary': route.path == tab.path }" class="tab-name">{{ tab.title }}</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item icon="Back" :disabled="index == 0" @click="closeLeft(tab.path)">{{t('tabs.closeLeft') }}</el-dropdown-item>
<el-dropdown-item icon="Right" :disabled="index == (tabbarStore.tabLength - 1)" @click="closeRight(tab.path)">{{t('tabs.closeRight') }}</el-dropdown-item>
<el-dropdown-item icon="Close" :disabled="tabbarStore.tabLength == 1" @click="closeOther(tab.path)">{{t('tabs.closeOther') }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script lang="ts" setup>
import { watch, onMounted } from 'vue'
import useTabbarStore from '@/stores/modules/tabbar'
import { useRoute, useRouter } from 'vue-router'
import { t } from '@/lang'
const tabbarStore = useTabbarStore()
const route = useRoute()
const router = useRouter()
onMounted(() => {
tabbarStore.addTab(route)
})
watch(route, (nval: any) => {
tabbarStore.addTab(nval)
})
/**
* 添加tab
* @param content
*/
const tabClick = (content: any) => {
const tabRoute = tabbarStore.tabs[content.props.name]
router.push({ path: tabRoute.path, query: tabRoute.query })
}
/**
* 移除tab
* @param content
*/
const removeTab = (content: any) => {
if (route.path == content) {
const tabs = Object.keys(tabbarStore.tabs)
router.push({ path: tabs[tabs.indexOf(content) - 1] })
}
tabbarStore.removeTab(content)
}
/**
* 关闭左侧
* @param path
*/
const closeLeft = (path: string) => {
const tabs = Object.keys(tabbarStore.tabs)
for (let i = tabs.indexOf(path) - 1; i >= 0; i--) {
delete tabbarStore.tabs[tabs[i]]
}
router.push({ path })
}
/**
* 关闭右侧
* @param path
*/
const closeRight = (path: string) => {
const tabs = Object.keys(tabbarStore.tabs)
for (let i = tabs.indexOf(path) + 1; i < tabs.length; i++) {
delete tabbarStore.tabs[tabs[i]]
}
router.push({ path })
}
/**
* 关闭其他
* @param path
*/
const closeOther = (path: string) => {
const tabs = Object.keys(tabbarStore.tabs)
tabs.forEach((key: string) => { key != path && delete tabbarStore.tabs[key] })
router.push({ path })
}
</script>
<style lang="scss" scoped>
:deep(.el-tabs) {
.el-tabs--border-card {
border: none;
}
.el-tabs__header {
margin: 0;
}
.el-tabs__nav-wrap {
margin-bottom: 0;
&::after {
display: none;
}
}
.el-tabs__content {
display: none;
}
.el-tabs__item {
display: inline-flex !important;
padding: 0 20px !important;
align-items: center;
.tab-name:focus {
outline: none !important;
}
}
.el-tabs__active-bar {
display: none;
}
.el-tabs__item.is-active {
background-color: var(--el-color-primary-light-9);
}
}
</style>

View File

@ -0,0 +1,41 @@
<template>
<div class="flex w-full h-screen">
<!-- 左侧边栏 -->
<layout-aside></layout-aside>
<!-- 左侧边栏 end -->
<el-container>
<!-- 顶部 -->
<el-header>
<layout-header></layout-header>
</el-header>
<!-- 顶部 end -->
<!-- 主体 -->
<el-main class="h-full p-0 bg-page">
<el-scrollbar>
<div class="p-[15px]">
<router-view v-slot="{ Component, route }" v-if="appStore.routeRefreshTag">
<keep-alive :include="tabbarStore.tabNames">
<component :is="Component" :key="route.fullPath" />
</keep-alive>
</router-view>
</div>
</el-scrollbar>
</el-main>
<!-- 主体 end -->
</el-container>
</div>
</template>
<script lang="ts" setup>
import layoutHeader from './components/header/index.vue'
import layoutAside from './components/aside/index.vue'
import useAppStore from '@/stores/modules/app'
import useTabbarStore from '@/stores/modules/tabbar'
const appStore = useAppStore()
const tabbarStore = useTabbarStore()
</script>
<style lang="scss" scoped></style>

View File

@ -49,7 +49,7 @@ import { useRoute, useRouter } from 'vue-router'
import useSystemStore from '@/stores/modules/system'
import useUserStore from '@/stores/modules/user'
import menuItem from './menu-item.vue'
import { img, isUrl } from '@/utils/common'
import { img } from '@/utils/common'
import { findFirstValidRoute } from '@/router/routers'
const systemStore = useSystemStore()

View File

@ -1,7 +1,7 @@
<template>
<el-container :class="['h-full px-[10px]',{'layout-header border-b border-color': !dark}]" >
<el-row class="w-100 h-full w-full">
<el-col :span="12">
<el-col :span="10">
<div class="left-panel h-full flex items-center">
<!-- 左侧菜单折叠 -->
<!-- <div class="navbar-item flex items-center h-full cursor-pointer" @click="toggleMenuCollapse">
@ -20,8 +20,30 @@
</div>
</div>
</el-col>
<el-col :span="12">
<el-col :span="14">
<div class="right-panel h-full flex items-center justify-end">
<div class="flex items-center flex-shrink-0 hidden-xs-only">
<el-dropdown trigger="hover" :hide-on-click="false" popper-class="site-info-wrap" class="mr-[8px]">
<!-- 状态 -->
<div class="mx-[8px] bg-[#f6f6f6] border-[1px] border-solid border-[#eee] rounded-[4px] px-[9px] py-[6px] flex items-center">
<span class="mr-[6px] text-[12px] !text-[#333]">{{siteInfo.site_name}}</span>
<span class="!text-[10px] text-[#f56c6c]" :class="{'!text-[#67c23a]': siteInfo.status == 1, '!text-[#f56c6c]': siteInfo.status == 3}">{{ siteInfo.status_name }}</span>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>
<!-- 站点id -->
<div class="text-[14px]">站点编号{{siteInfo.site_id}}</div>
</el-dropdown-item>
<el-dropdown-item>
<!-- 到期时间 -->
<div v-if="siteInfo.expire_time == 0" class="text-[14px]">到期时间永久</div>
<div v-else class="text-[14px]">到期时间{{ siteInfo.expire_time }}</div>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<!-- 预览 只有站点时展示-->
<i class="iconfont iconicon_huojian1 cursor-pointer px-[8px]" :title="t('visitWap')" @click="toPreview"></i>
<i class="iconfont iconlingdang-xianxing cursor-pointer px-[8px]" :title="t('newInfo')" v-if="appType == 'site'"></i>
@ -65,8 +87,9 @@ import layoutSetting from './layout-setting.vue'
import userInfo from './user-info.vue'
import { useFullscreen } from '@vueuse/core'
import useSystemStore from '@/stores/modules/system'
import useUserStore from '@/stores/modules/user'
import useAppStore from '@/stores/modules/app'
import { useRoute,useRouter } from 'vue-router'
import { useRoute, useRouter } from 'vue-router'
import { t } from '@/lang'
import storage from '@/utils/storage'
@ -78,6 +101,11 @@ const route = useRoute()
const router = useRouter()
const screenWidth = ref(window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth)
const userStore = useUserStore()
const siteInfo:any = computed(() => {
return userStore.siteInfo
})
const dark = computed(() => {
return systemStore.dark
})
@ -154,7 +182,7 @@ const toPreview = () => {
const url = router.resolve({
path: '/preview/wap',
query: {
page:'/'
page: '/'
}
})
window.open(url.href)
@ -182,5 +210,10 @@ const toPreview = () => {
background-color: var(--el-color-primary);
}
}
:deep(.el-dropdown-menu__item) {
&:focus {
background-color: transparent !important;
color: #333 !important;
}
}
</style>

View File

@ -18,6 +18,13 @@
<el-color-picker v-model="theme" />
</div>
</div>
<!-- 标签栏 -->
<div class="setting-item flex items-center justify-between mb-[10px]">
<div class="title text-base text-tx-secondary">{{ t('layout.tab') }}</div>
<div>
<el-switch v-model="tab" :active-value="true" :inactive-value="false" />
</div>
</div>
</el-scrollbar>
</el-drawer>
</div>
@ -29,6 +36,7 @@ import useSystemStore from '@/stores/modules/system'
import { useDark, useToggle } from '@vueuse/core'
import { setThemeColor } from '@/utils/common'
import { t } from '@/lang'
import storage from "@/utils/storage";
const drawer = ref(false)
const systemStore = useSystemStore()
@ -47,6 +55,18 @@ const dark = computed({
}
})
const tab = computed({
get () {
return systemStore.tab
},
set (val) {
systemStore.$patch((state) => {
state.tab = val
storage.set({ key: 'tab', data: val })
})
}
})
const theme = computed({
get () {
return systemStore.theme

View File

@ -2,7 +2,8 @@
<div>
<el-dropdown @command="clickEvent" :tabindex="1">
<div class="userinfo flex h-full items-center">
<el-avatar :size="25" :icon="UserFilled" :src="userStore.userInfo.head_img ? img(userStore.userInfo.head_img) : ''"/>
<el-avatar v-if="userStore.userInfo.head_img" :size="25" :icon="UserFilled" :src="img(userStore.userInfo.head_img)"/>
<img v-else src="@/app/assets/images/member_head.png" class="w-[25px] rounded-full" />
<div class="user-name pl-[8px]">{{ userStore.userInfo.username }}</div>
<icon name="element ArrowDown" class="ml-[5px]" />
</div>
@ -95,10 +96,10 @@ const getUserInfoFn = ()=>{
userInfoEditRef.value?.open()
}
// --- start
let changePasswordDialog = ref(false)
const changePasswordDialog = ref(false)
const formRef = ref<FormInstance>();
//
let saveInfo = reactive({
const saveInfo = reactive({
original_password: '',
password: '',
password_copy: ''
@ -139,7 +140,6 @@ const submitForm = (formEl: FormInstance | undefined) => {
}
});
}
// --- end
</script>
<style lang="scss" scoped>

View File

@ -1,15 +1,15 @@
<template>
<div class="tab-wrap w-full px-[16px]">
<el-tabs :closable="tabbarStore.tabLength > 1" :model-value="route.path" @tab-click="tabClick" @tab-remove="removeTab">
<el-tab-pane v-for="(tab, key, index) in tabbarStore.tabs" :name="tab.path" :key="index">
<div class="tab-wrap w-full px-[16px]" v-show="systemStore.tab">
<el-tabs :closable="tabbarStore.tabLength > 1" :model-value="route.name" @tab-click="tabClick" @tab-remove="removeTab">
<el-tab-pane v-for="(tab, key, index) in tabbarStore.tabs" :name="tab.name" :key="index">
<template #label>
<el-dropdown trigger="contextmenu" placement="bottom-start">
<span :class="{ 'text-primary': route.path == tab.path }" class="tab-name">{{ tab.title }}</span>
<span :class="{ 'text-primary': route.name == tab.name }" class="tab-name">{{ tab.title }}</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item icon="Back" :disabled="index == 0" @click="closeLeft(tab.path)">{{t('tabs.closeLeft') }}</el-dropdown-item>
<el-dropdown-item icon="Right" :disabled="index == (tabbarStore.tabLength - 1)" @click="closeRight(tab.path)">{{t('tabs.closeRight') }}</el-dropdown-item>
<el-dropdown-item icon="Close" :disabled="tabbarStore.tabLength == 1" @click="closeOther(tab.path)">{{t('tabs.closeOther') }}</el-dropdown-item>
<el-dropdown-item icon="Back" :disabled="index == 0" @click="closeLeft(tab.name)">{{t('tabs.closeLeft') }}</el-dropdown-item>
<el-dropdown-item icon="Right" :disabled="index == (tabbarStore.tabLength - 1)" @click="closeRight(tab.name)">{{t('tabs.closeRight') }}</el-dropdown-item>
<el-dropdown-item icon="Close" :disabled="tabbarStore.tabLength == 1" @click="closeOther(tab.name)">{{t('tabs.closeOther') }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
@ -22,10 +22,12 @@
<script lang="ts" setup>
import { watch, onMounted } from 'vue'
import useTabbarStore from '@/stores/modules/tabbar'
import useSystemStore from '@/stores/modules/system'
import { useRoute, useRouter } from 'vue-router'
import { t } from '@/lang'
const tabbarStore = useTabbarStore()
const systemStore = useSystemStore()
const route = useRoute()
const router = useRouter()
@ -43,7 +45,7 @@ watch(route, (nval: any) => {
*/
const tabClick = (content: any) => {
const tabRoute = tabbarStore.tabs[content.props.name]
router.push({ path: tabRoute.path, query: tabRoute.query })
router.push({ name: tabRoute.name, query: tabRoute.query })
}
/**
@ -51,45 +53,49 @@ const tabClick = (content: any) => {
* @param content
*/
const removeTab = (content: any) => {
if (route.path == content) {
if (route.name == content) {
const tabs = Object.keys(tabbarStore.tabs)
router.push({ path: tabs[tabs.indexOf(content) - 1] })
if (tabs.indexOf(content) == 0) {
router.push({ name: tabs[1] })
} else {
router.push({ name: tabs[tabs.indexOf(content) - 1] })
}
}
tabbarStore.removeTab(content)
}
/**
* 关闭左侧
* @param path
* @param name
*/
const closeLeft = (path: string) => {
const closeLeft = (name: string) => {
const tabs = Object.keys(tabbarStore.tabs)
for (let i = tabs.indexOf(path) - 1; i >= 0; i--) {
for (let i = tabs.indexOf(name) - 1; i >= 0; i--) {
delete tabbarStore.tabs[tabs[i]]
}
router.push({ path })
router.push({ name })
}
/**
* 关闭右侧
* @param path
* @param name
*/
const closeRight = (path: string) => {
const closeRight = (name: string) => {
const tabs = Object.keys(tabbarStore.tabs)
for (let i = tabs.indexOf(path) + 1; i < tabs.length; i++) {
for (let i = tabs.indexOf(name) + 1; i < tabs.length; i++) {
delete tabbarStore.tabs[tabs[i]]
}
router.push({ path })
router.push({ name })
}
/**
* 关闭其他
* @param path
* @param name
*/
const closeOther = (path: string) => {
const closeOther = (name: string) => {
const tabs = Object.keys(tabbarStore.tabs)
tabs.forEach((key: string) => { key != path && delete tabbarStore.tabs[key] })
router.push({ path })
tabs.forEach((key: string) => { key != name && delete tabbarStore.tabs[key] })
router.push({ name })
}
</script>

View File

@ -11,6 +11,8 @@
</el-header>
<!-- 顶部 end -->
<layout-tab />
<!-- 主体 -->
<el-main class="h-full p-0 bg-page">
<el-scrollbar>
@ -32,6 +34,7 @@
import { computed } from 'vue'
import layoutHeader from './components/header/index.vue'
import layoutAside from './components/aside/index.vue'
import layoutTab from './components/tabs.vue'
import useAppStore from '@/stores/modules/app'
import useTabbarStore from '@/stores/modules/tabbar'
import useSystemStore from '@/stores/modules/system'

View File

@ -25,7 +25,7 @@
<script lang="ts" setup>
import { ref, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useRoute } from 'vue-router'
import useSystemStore from '@/stores/modules/system'
import useUserStore from '@/stores/modules/user'
import menuItem from './menu-item.vue'

View File

@ -1,7 +1,7 @@
<template>
<el-container :class="['h-full px-[10px]',{'layout-header border-b border-color': !dark}]" >
<el-row class="w-100 h-full w-full">
<el-col :span="12">
<el-col :span="10">
<div class="left-panel h-full flex items-center">
<!-- 左侧菜单折叠 -->
<div class="hidden-sm-and-up navbar-item flex items-center h-full cursor-pointer" @click="toggleMenuCollapse">
@ -20,8 +20,30 @@
</div>
</div>
</el-col>
<el-col :span="12">
<el-col :span="14">
<div class="right-panel h-full flex items-center justify-end">
<div class="flex items-center flex-shrink-0 hidden-xs-only">
<el-dropdown trigger="hover" :hide-on-click="false" popper-class="site-info-wrap" class="mr-[8px]">
<!-- 状态 -->
<div class="mx-[8px] bg-[#f6f6f6] border-[1px] border-solid border-[#eee] rounded-[4px] px-[9px] py-[6px] flex items-center">
<span class="mr-[6px] text-[12px] !text-[#333]">{{siteInfo.site_name}}</span>
<span class="!text-[10px] text-[#f56c6c]" :class="{'!text-[#67c23a]': siteInfo.status == 1, '!text-[#f56c6c]': siteInfo.status == 3}">{{ siteInfo.status_name }}</span>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>
<!-- 站点id -->
<div class="text-[14px]">站点编号{{siteInfo.site_id}}</div>
</el-dropdown-item>
<el-dropdown-item>
<!-- 到期时间 -->
<div v-if="siteInfo.expire_time == 0" class="text-[14px]">到期时间永久</div>
<div v-else class="text-[14px]">到期时间{{ siteInfo.expire_time }}</div>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<!-- 预览 只有站点时展示-->
<i class="iconfont iconicon_huojian1 cursor-pointer px-[8px]" :title="t('visitWap')" @click="toPreview"></i>
<i class="iconfont iconlingdang-xianxing cursor-pointer px-[8px]" :title="t('newInfo')" v-if="appType == 'site'"></i>
@ -65,8 +87,9 @@ import layoutSetting from './layout-setting.vue'
import userInfo from './user-info.vue'
import { useFullscreen } from '@vueuse/core'
import useSystemStore from '@/stores/modules/system'
import useUserStore from '@/stores/modules/user'
import useAppStore from '@/stores/modules/app'
import { useRoute,useRouter } from 'vue-router'
import { useRoute, useRouter } from 'vue-router'
import { t } from '@/lang'
import storage from '@/utils/storage'
@ -78,6 +101,11 @@ const route = useRoute()
const router = useRouter()
const screenWidth = ref(window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth)
const userStore = useUserStore()
const siteInfo:any = computed(() => {
return userStore.siteInfo
})
const dark = computed(() => {
return systemStore.dark
})
@ -182,5 +210,11 @@ const toPreview = () => {
background-color: var(--el-color-primary);
}
}
:deep(.el-dropdown-menu__item) {
&:focus {
background-color: transparent !important;
color: #333 !important;
}
}
</style>

View File

@ -18,6 +18,13 @@
<el-color-picker v-model="theme" />
</div>
</div>
<!-- 标签栏 -->
<div class="setting-item flex items-center justify-between mb-[10px]">
<div class="title text-base text-tx-secondary">{{ t('layout.tab') }}</div>
<div>
<el-switch v-model="tab" :active-value="true" :inactive-value="false" />
</div>
</div>
</el-scrollbar>
</el-drawer>
</div>
@ -29,6 +36,7 @@ import useSystemStore from '@/stores/modules/system'
import { useDark, useToggle } from '@vueuse/core'
import { setThemeColor } from '@/utils/common'
import { t } from '@/lang'
import storage from "@/utils/storage";
const drawer = ref(false)
const systemStore = useSystemStore()
@ -47,6 +55,18 @@ const dark = computed({
}
})
const tab = computed({
get () {
return systemStore.tab
},
set (val) {
systemStore.$patch((state) => {
state.tab = val
storage.set({ key: 'tab', data: val })
})
}
})
const theme = computed({
get () {
return systemStore.theme

View File

@ -2,7 +2,8 @@
<div>
<el-dropdown @command="clickEvent" :tabindex="1">
<div class="userinfo flex h-full items-center">
<el-avatar :size="25" :icon="UserFilled" :src="userStore.userInfo.head_img ? img(userStore.userInfo.head_img) : ''"/>
<el-avatar v-if="userStore.userInfo.head_img" :size="25" :icon="UserFilled" :src="img(userStore.userInfo.head_img)"/>
<img v-else src="@/app/assets/images/member_head.png" class="w-[25px] rounded-full" />
<div class="user-name pl-[8px]">{{ userStore.userInfo.username }}</div>
<icon name="element ArrowDown" class="ml-[5px]" />
</div>
@ -66,7 +67,7 @@
<script lang="ts" setup>
import { UserFilled } from '@element-plus/icons-vue'
import { reactive, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useRouter } from 'vue-router'
import type { FormInstance, FormRules, ElNotification } from 'element-plus'
import useUserStore from '@/stores/modules/user'
import { setUserInfo } from '@/app/api/personal'
@ -95,10 +96,10 @@ const getUserInfoFn = ()=>{
userInfoEditRef.value?.open()
}
// --- start
let changePasswordDialog = ref(false)
const changePasswordDialog = ref(false)
const formRef = ref<FormInstance>();
//
let saveInfo = reactive({
const saveInfo = reactive({
original_password: '',
password: '',
password_copy: ''
@ -139,7 +140,6 @@ const submitForm = (formEl: FormInstance | undefined) => {
}
});
}
// --- end
</script>
<style lang="scss" scoped>

View File

@ -1,15 +1,15 @@
<template>
<div class="tab-wrap w-full px-[16px]">
<el-tabs :closable="tabbarStore.tabLength > 1" :model-value="route.path" @tab-click="tabClick" @tab-remove="removeTab">
<el-tab-pane v-for="(tab, key, index) in tabbarStore.tabs" :name="tab.path" :key="index">
<div class="tab-wrap w-full px-[16px]" v-show="systemStore.tab">
<el-tabs :closable="tabbarStore.tabLength > 1" :model-value="route.name" @tab-click="tabClick" @tab-remove="removeTab">
<el-tab-pane v-for="(tab, key, index) in tabbarStore.tabs" :name="tab.name" :key="index">
<template #label>
<el-dropdown trigger="contextmenu" placement="bottom-start">
<span :class="{ 'text-primary': route.path == tab.path }" class="tab-name">{{ tab.title }}</span>
<span :class="{ 'text-primary': route.name == tab.name }" class="tab-name">{{ tab.title }}</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item icon="Back" :disabled="index == 0" @click="closeLeft(tab.path)">{{t('tabs.closeLeft') }}</el-dropdown-item>
<el-dropdown-item icon="Right" :disabled="index == (tabbarStore.tabLength - 1)" @click="closeRight(tab.path)">{{t('tabs.closeRight') }}</el-dropdown-item>
<el-dropdown-item icon="Close" :disabled="tabbarStore.tabLength == 1" @click="closeOther(tab.path)">{{t('tabs.closeOther') }}</el-dropdown-item>
<el-dropdown-item icon="Back" :disabled="index == 0" @click="closeLeft(tab.name)">{{t('tabs.closeLeft') }}</el-dropdown-item>
<el-dropdown-item icon="Right" :disabled="index == (tabbarStore.tabLength - 1)" @click="closeRight(tab.name)">{{t('tabs.closeRight') }}</el-dropdown-item>
<el-dropdown-item icon="Close" :disabled="tabbarStore.tabLength == 1" @click="closeOther(tab.name)">{{t('tabs.closeOther') }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
@ -22,10 +22,12 @@
<script lang="ts" setup>
import { watch, onMounted } from 'vue'
import useTabbarStore from '@/stores/modules/tabbar'
import useSystemStore from '@/stores/modules/system'
import { useRoute, useRouter } from 'vue-router'
import { t } from '@/lang'
const tabbarStore = useTabbarStore()
const systemStore = useSystemStore()
const route = useRoute()
const router = useRouter()
@ -43,7 +45,7 @@ watch(route, (nval: any) => {
*/
const tabClick = (content: any) => {
const tabRoute = tabbarStore.tabs[content.props.name]
router.push({ path: tabRoute.path, query: tabRoute.query })
router.push({ name: tabRoute.name, query: tabRoute.query })
}
/**
@ -51,45 +53,49 @@ const tabClick = (content: any) => {
* @param content
*/
const removeTab = (content: any) => {
if (route.path == content) {
if (route.name == content) {
const tabs = Object.keys(tabbarStore.tabs)
router.push({ path: tabs[tabs.indexOf(content) - 1] })
if (tabs.indexOf(content) == 0) {
router.push({ name: tabs[1] })
} else {
router.push({ name: tabs[tabs.indexOf(content) - 1] })
}
}
tabbarStore.removeTab(content)
}
/**
* 关闭左侧
* @param path
* @param name
*/
const closeLeft = (path: string) => {
const closeLeft = (name: string) => {
const tabs = Object.keys(tabbarStore.tabs)
for (let i = tabs.indexOf(path) - 1; i >= 0; i--) {
for (let i = tabs.indexOf(name) - 1; i >= 0; i--) {
delete tabbarStore.tabs[tabs[i]]
}
router.push({ path })
router.push({ name })
}
/**
* 关闭右侧
* @param path
* @param name
*/
const closeRight = (path: string) => {
const closeRight = (name: string) => {
const tabs = Object.keys(tabbarStore.tabs)
for (let i = tabs.indexOf(path) + 1; i < tabs.length; i++) {
for (let i = tabs.indexOf(name) + 1; i < tabs.length; i++) {
delete tabbarStore.tabs[tabs[i]]
}
router.push({ path })
router.push({ name })
}
/**
* 关闭其他
* @param path
* @param name
*/
const closeOther = (path: string) => {
const closeOther = (name: string) => {
const tabs = Object.keys(tabbarStore.tabs)
tabs.forEach((key: string) => { key != path && delete tabbarStore.tabs[key] })
router.push({ path })
tabs.forEach((key: string) => { key != name && delete tabbarStore.tabs[key] })
router.push({ name })
}
</script>

View File

@ -11,6 +11,8 @@
</el-header>
<!-- 顶部 end -->
<layout-tab />
<!-- 主体 -->
<el-main class="h-full p-0 bg-page">
<el-scrollbar>
@ -32,6 +34,7 @@
import { computed } from 'vue'
import layoutHeader from './components/header/index.vue'
import layoutAside from './components/aside/index.vue'
import layoutTab from './components/tabs.vue'
import useAppStore from '@/stores/modules/app'
import useTabbarStore from '@/stores/modules/tabbar'
import useSystemStore from '@/stores/modules/system'

View File

@ -25,11 +25,11 @@
<script lang="ts" setup>
import { ref, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useRoute } from 'vue-router'
import useSystemStore from '@/stores/modules/system'
import useUserStore from '@/stores/modules/user'
import menuItem from './menu-item.vue'
import { img, isUrl } from '@/utils/common'
import { img } from '@/utils/common'
import { findFirstValidRoute } from '@/router/routers'
const systemStore = useSystemStore()

View File

@ -1,7 +1,7 @@
<template>
<el-container :class="['h-full px-[10px]',{'layout-header border-b border-color': !dark}]" >
<el-row class="w-100 h-full w-full">
<el-col :span="12">
<el-col :span="10">
<div class="left-panel h-full flex items-center">
<!-- 左侧菜单折叠 -->
<div class="hidden-sm-and-up navbar-item flex items-center h-full cursor-pointer" @click="toggleMenuCollapse">
@ -20,8 +20,30 @@
</div>
</div>
</el-col>
<el-col :span="12">
<el-col :span="14">
<div class="right-panel h-full flex items-center justify-end">
<div class="flex items-center flex-shrink-0 hidden-xs-only">
<el-dropdown trigger="hover" :hide-on-click="false" popper-class="site-info-wrap" class="mr-[8px]">
<!-- 状态 -->
<div class="mx-[8px] bg-[#f6f6f6] border-[1px] border-solid border-[#eee] rounded-[4px] px-[9px] py-[6px] flex items-center">
<span class="mr-[6px] text-[12px] !text-[#333]">{{siteInfo.site_name}}</span>
<span class="!text-[10px] text-[#f56c6c]" :class="{'!text-[#67c23a]': siteInfo.status == 1, '!text-[#f56c6c]': siteInfo.status == 3}">{{ siteInfo.status_name }}</span>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>
<!-- 站点id -->
<div class="text-[14px]">站点编号{{siteInfo.site_id}}</div>
</el-dropdown-item>
<el-dropdown-item>
<!-- 到期时间 -->
<div v-if="siteInfo.expire_time == 0" class="text-[14px]">到期时间永久</div>
<div v-else class="text-[14px]">到期时间{{ siteInfo.expire_time }}</div>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<!-- 预览 只有站点时展示-->
<i class="iconfont iconicon_huojian1 cursor-pointer px-[8px]" :title="t('visitWap')" @click="toPreview"></i>
<i class="iconfont iconlingdang-xianxing cursor-pointer px-[8px]" :title="t('newInfo')" v-if="appType == 'site'"></i>
@ -65,8 +87,9 @@ import layoutSetting from './layout-setting.vue'
import userInfo from './user-info.vue'
import { useFullscreen } from '@vueuse/core'
import useSystemStore from '@/stores/modules/system'
import useUserStore from '@/stores/modules/user'
import useAppStore from '@/stores/modules/app'
import { useRoute,useRouter } from 'vue-router'
import { useRoute, useRouter } from 'vue-router'
import { t } from '@/lang'
import storage from '@/utils/storage'
@ -78,6 +101,11 @@ const route = useRoute()
const router = useRouter()
const screenWidth = ref(window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth)
const userStore = useUserStore()
const siteInfo:any = computed(() => {
return userStore.siteInfo
})
const dark = computed(() => {
return systemStore.dark
})
@ -181,5 +209,10 @@ const toPreview = () => {
background-color: var(--el-color-primary);
}
}
:deep(.el-dropdown-menu__item) {
&:focus {
background-color: transparent !important;
color: #333 !important;
}
}
</style>

View File

@ -18,6 +18,13 @@
<el-color-picker v-model="theme" />
</div>
</div>
<!-- 标签栏 -->
<div class="setting-item flex items-center justify-between mb-[10px]">
<div class="title text-base text-tx-secondary">{{ t('layout.tab') }}</div>
<div>
<el-switch v-model="tab" :active-value="true" :inactive-value="false" />
</div>
</div>
</el-scrollbar>
</el-drawer>
</div>
@ -29,6 +36,7 @@ import useSystemStore from '@/stores/modules/system'
import { useDark, useToggle } from '@vueuse/core'
import { setThemeColor } from '@/utils/common'
import { t } from '@/lang'
import storage from "@/utils/storage";
const drawer = ref(false)
const systemStore = useSystemStore()
@ -47,6 +55,18 @@ const dark = computed({
}
})
const tab = computed({
get () {
return systemStore.tab
},
set (val) {
systemStore.$patch((state) => {
state.tab = val
storage.set({ key: 'tab', data: val })
})
}
})
const theme = computed({
get () {
return systemStore.theme

View File

@ -2,7 +2,8 @@
<div>
<el-dropdown @command="clickEvent" :tabindex="1">
<div class="userinfo flex h-full items-center">
<el-avatar :size="25" :icon="UserFilled" :src="userStore.userInfo.head_img ? img(userStore.userInfo.head_img) : ''"/>
<el-avatar v-if="userStore.userInfo.head_img" :size="25" :icon="UserFilled" :src="img(userStore.userInfo.head_img)"/>
<img v-else src="@/app/assets/images/member_head.png" class="w-[25px] rounded-full" />
<div class="user-name pl-[8px]">{{ userStore.userInfo.username }}</div>
<icon name="element ArrowDown" class="ml-[5px]" />
</div>
@ -66,7 +67,7 @@
<script lang="ts" setup>
import { UserFilled } from '@element-plus/icons-vue'
import { reactive, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useRouter } from 'vue-router'
import type { FormInstance, FormRules, ElNotification } from 'element-plus'
import useUserStore from '@/stores/modules/user'
import { setUserInfo } from '@/app/api/personal'
@ -95,10 +96,10 @@ const getUserInfoFn = ()=>{
userInfoEditRef.value?.open()
}
// --- start
let changePasswordDialog = ref(false)
const changePasswordDialog = ref(false)
const formRef = ref<FormInstance>();
//
let saveInfo = reactive({
const saveInfo = reactive({
original_password: '',
password: '',
password_copy: ''
@ -139,7 +140,6 @@ const submitForm = (formEl: FormInstance | undefined) => {
}
});
}
// --- end
</script>
<style lang="scss" scoped>

View File

@ -1,15 +1,15 @@
<template>
<div class="tab-wrap w-full px-[16px]">
<el-tabs :closable="tabbarStore.tabLength > 1" :model-value="route.path" @tab-click="tabClick" @tab-remove="removeTab">
<el-tab-pane v-for="(tab, key, index) in tabbarStore.tabs" :name="tab.path" :key="index">
<div class="tab-wrap w-full px-[16px]" v-show="systemStore.tab">
<el-tabs :closable="tabbarStore.tabLength > 1" :model-value="route.name" @tab-click="tabClick" @tab-remove="removeTab">
<el-tab-pane v-for="(tab, key, index) in tabbarStore.tabs" :name="tab.name" :key="index">
<template #label>
<el-dropdown trigger="contextmenu" placement="bottom-start">
<span :class="{ 'text-primary': route.path == tab.path }" class="tab-name">{{ tab.title }}</span>
<span :class="{ 'text-primary': route.name == tab.name }" class="tab-name">{{ tab.title }}</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item icon="Back" :disabled="index == 0" @click="closeLeft(tab.path)">{{t('tabs.closeLeft') }}</el-dropdown-item>
<el-dropdown-item icon="Right" :disabled="index == (tabbarStore.tabLength - 1)" @click="closeRight(tab.path)">{{t('tabs.closeRight') }}</el-dropdown-item>
<el-dropdown-item icon="Close" :disabled="tabbarStore.tabLength == 1" @click="closeOther(tab.path)">{{t('tabs.closeOther') }}</el-dropdown-item>
<el-dropdown-item icon="Back" :disabled="index == 0" @click="closeLeft(tab.name)">{{t('tabs.closeLeft') }}</el-dropdown-item>
<el-dropdown-item icon="Right" :disabled="index == (tabbarStore.tabLength - 1)" @click="closeRight(tab.name)">{{t('tabs.closeRight') }}</el-dropdown-item>
<el-dropdown-item icon="Close" :disabled="tabbarStore.tabLength == 1" @click="closeOther(tab.name)">{{t('tabs.closeOther') }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
@ -22,10 +22,12 @@
<script lang="ts" setup>
import { watch, onMounted } from 'vue'
import useTabbarStore from '@/stores/modules/tabbar'
import useSystemStore from '@/stores/modules/system'
import { useRoute, useRouter } from 'vue-router'
import { t } from '@/lang'
const tabbarStore = useTabbarStore()
const systemStore = useSystemStore()
const route = useRoute()
const router = useRouter()
@ -43,7 +45,7 @@ watch(route, (nval: any) => {
*/
const tabClick = (content: any) => {
const tabRoute = tabbarStore.tabs[content.props.name]
router.push({ path: tabRoute.path, query: tabRoute.query })
router.push({ name: tabRoute.name, query: tabRoute.query })
}
/**
@ -51,9 +53,13 @@ const tabClick = (content: any) => {
* @param content
*/
const removeTab = (content: any) => {
if (route.path == content) {
if (route.name == content) {
const tabs = Object.keys(tabbarStore.tabs)
router.push({ path: tabs[tabs.indexOf(content) - 1] })
if (tabs.indexOf(content) == 0) {
router.push({ name: tabs[1] })
} else {
router.push({ name: tabs[tabs.indexOf(content) - 1] })
}
}
tabbarStore.removeTab(content)
}
@ -62,34 +68,34 @@ const removeTab = (content: any) => {
* 关闭左侧
* @param path
*/
const closeLeft = (path: string) => {
const closeLeft = (name: string) => {
const tabs = Object.keys(tabbarStore.tabs)
for (let i = tabs.indexOf(path) - 1; i >= 0; i--) {
for (let i = tabs.indexOf(name) - 1; i >= 0; i--) {
delete tabbarStore.tabs[tabs[i]]
}
router.push({ path })
router.push({ name })
}
/**
* 关闭右侧
* @param path
*/
const closeRight = (path: string) => {
const closeRight = (name: string) => {
const tabs = Object.keys(tabbarStore.tabs)
for (let i = tabs.indexOf(path) + 1; i < tabs.length; i++) {
for (let i = tabs.indexOf(name) + 1; i < tabs.length; i++) {
delete tabbarStore.tabs[tabs[i]]
}
router.push({ path })
router.push({ name })
}
/**
* 关闭其他
* @param path
*/
const closeOther = (path: string) => {
const closeOther = (name: string) => {
const tabs = Object.keys(tabbarStore.tabs)
tabs.forEach((key: string) => { key != path && delete tabbarStore.tabs[key] })
router.push({ path })
tabs.forEach((key: string) => { key != name && delete tabbarStore.tabs[key] })
router.push({ name })
}
</script>

View File

@ -11,6 +11,8 @@
</el-header>
<!-- 顶部 end -->
<layout-tab />
<!-- 主体 -->
<el-main class="h-full p-0 bg-page">
<el-scrollbar>
@ -32,6 +34,7 @@
import { computed } from 'vue'
import layoutHeader from './components/header/index.vue'
import layoutAside from './components/aside/index.vue'
import layoutTab from './components/tabs.vue'
import useAppStore from '@/stores/modules/app'
import useTabbarStore from '@/stores/modules/tabbar'
import useSystemStore from '@/stores/modules/system'

View File

@ -7,6 +7,7 @@ import { ref, markRaw, defineAsyncComponent, provide } from 'vue'
import { getAppType } from '@/utils/common'
import useUserStore from '@/stores/modules/user'
import useSystemStore from '@/stores/modules/system'
import Storage from '@/utils/storage'
const sysLayout = import.meta.glob('./*/index.vue')
const addonLayout = import.meta.glob('@/addon/**/layout/index.vue')
@ -15,7 +16,7 @@ const modules = Object.assign(sysLayout, addonLayout)
let siteLayout = 'default'
switch (getAppType()) {
case 'admin':
siteLayout = 'admin'
siteLayout = Storage.get('admin_layout') || 'admin'
break
default:
const siteInfo = useUserStore().siteInfo

View File

@ -61,6 +61,7 @@ import useUserStore from '@/stores/modules/user'
import { img, isUrl } from '@/utils/common'
import { findFirstValidRoute } from '@/router/routers'
import menuItem from './menu-item.vue'
import { cloneDeep } from 'lodash-es'
const route = useRoute()
const router = useRouter()
@ -141,11 +142,11 @@ watch(route, () => {
}, { immediate: true })
//
let menuOption = ref([])
const menuOption = ref([])
watch(twoMenuData.value, () => {
menuOption.value = [];
if(twoMenuData.value && Object.values(twoMenuData.value).length){
let data = JSON.parse(JSON.stringify(twoMenuData.value));
let data = cloneDeep(twoMenuData.value);
for(let key in data){
menuOption.value.push(data[key].name);
}

View File

@ -1,7 +1,7 @@
<template>
<el-container :class="['h-full px-[10px]',{'layout-header border-b border-color': !dark}]" >
<el-row class="w-100 h-full w-full">
<el-col :span="12">
<el-col :span="10">
<div class="left-panel h-full flex items-center">
<!-- 左侧菜单折叠 -->
<!-- <div class="navbar-item flex items-center h-full cursor-pointer" @click="toggleMenuCollapse">
@ -20,8 +20,30 @@
</div>
</div>
</el-col>
<el-col :span="12">
<el-col :span="14">
<div class="right-panel h-full flex items-center justify-end">
<div class="flex items-center flex-shrink-0 hidden-xs-only">
<el-dropdown trigger="hover" :hide-on-click="false" popper-class="site-info-wrap" class="mr-[8px]">
<!-- 状态 -->
<div class="mx-[8px] bg-[#f6f6f6] border-[1px] border-solid border-[#eee] rounded-[4px] px-[9px] py-[6px] flex items-center">
<span class="mr-[6px] text-[12px] !text-[#333]">{{siteInfo.site_name}}</span>
<span class="!text-[10px] text-[#f56c6c]" :class="{'!text-[#67c23a]': siteInfo.status == 1, '!text-[#f56c6c]': siteInfo.status == 3}">{{ siteInfo.status_name }}</span>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>
<!-- 站点id -->
<div class="text-[14px]">站点编号{{siteInfo.site_id}}</div>
</el-dropdown-item>
<el-dropdown-item>
<!-- 到期时间 -->
<div v-if="siteInfo.expire_time == 0" class="text-[14px]">到期时间永久</div>
<div v-else class="text-[14px]">到期时间{{ siteInfo.expire_time }}</div>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<!-- 预览 只有站点时展示-->
<i class="iconfont iconicon_huojian1 cursor-pointer px-[8px]" :title="t('visitWap')" @click="toPreview"></i>
<i class="iconfont iconlingdang-xianxing cursor-pointer px-[8px]" :title="t('newInfo')" v-if="appType == 'site'"></i>
@ -65,8 +87,9 @@ import layoutSetting from './layout-setting.vue'
import userInfo from './user-info.vue'
import { useFullscreen } from '@vueuse/core'
import useSystemStore from '@/stores/modules/system'
import useUserStore from '@/stores/modules/user'
import useAppStore from '@/stores/modules/app'
import { useRoute,useRouter } from 'vue-router'
import { useRoute, useRouter } from 'vue-router'
import { t } from '@/lang'
import storage from '@/utils/storage'
@ -78,6 +101,11 @@ const route = useRoute()
const router = useRouter()
const screenWidth = ref(window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth)
const userStore = useUserStore()
const siteInfo:any = computed(() => {
return userStore.siteInfo
})
const dark = computed(() => {
return systemStore.dark
})
@ -181,5 +209,10 @@ const toPreview = () => {
background-color: var(--el-color-primary);
}
}
:deep(.el-dropdown-menu__item) {
&:focus {
background-color: transparent !important;
color: #333 !important;
}
}
</style>

View File

@ -18,6 +18,13 @@
<el-color-picker v-model="theme" />
</div>
</div>
<!-- 标签栏 -->
<div class="setting-item flex items-center justify-between mb-[10px]">
<div class="title text-base text-tx-secondary">{{ t('layout.tab') }}</div>
<div>
<el-switch v-model="tab" :active-value="true" :inactive-value="false" />
</div>
</div>
</el-scrollbar>
</el-drawer>
</div>
@ -29,6 +36,7 @@ import useSystemStore from '@/stores/modules/system'
import { useDark, useToggle } from '@vueuse/core'
import { setThemeColor } from '@/utils/common'
import { t } from '@/lang'
import storage from "@/utils/storage";
const drawer = ref(false)
const systemStore = useSystemStore()
@ -47,6 +55,18 @@ const dark = computed({
}
})
const tab = computed({
get () {
return systemStore.tab
},
set (val) {
systemStore.$patch((state) => {
state.tab = val
storage.set({ key: 'tab', data: val })
})
}
})
const theme = computed({
get () {
return systemStore.theme

View File

@ -2,7 +2,8 @@
<div>
<el-dropdown @command="clickEvent" :tabindex="1">
<div class="userinfo flex h-full items-center">
<el-avatar :size="25" :icon="UserFilled" :src="userStore.userInfo.head_img ? img(userStore.userInfo.head_img) : ''"/>
<el-avatar v-if="userStore.userInfo.head_img" :size="25" :icon="UserFilled" :src="img(userStore.userInfo.head_img)"/>
<img v-else src="@/app/assets/images/member_head.png" class="w-[25px] rounded-full" />
<div class="user-name pl-[8px]">{{ userStore.userInfo.username }}</div>
<icon name="element ArrowDown" class="ml-[5px]" />
</div>
@ -66,7 +67,7 @@
<script lang="ts" setup>
import { UserFilled } from '@element-plus/icons-vue'
import { reactive, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useRouter } from 'vue-router'
import type { FormInstance, FormRules, ElNotification } from 'element-plus'
import useUserStore from '@/stores/modules/user'
import { setUserInfo } from '@/app/api/personal'
@ -95,10 +96,10 @@ const getUserInfoFn = ()=>{
userInfoEditRef.value?.open()
}
// --- start
let changePasswordDialog = ref(false)
const changePasswordDialog = ref(false)
const formRef = ref<FormInstance>();
//
let saveInfo = reactive({
const saveInfo = reactive({
original_password: '',
password: '',
password_copy: ''
@ -139,7 +140,6 @@ const submitForm = (formEl: FormInstance | undefined) => {
}
});
}
// --- end
</script>
<style lang="scss" scoped>

View File

@ -1,15 +1,15 @@
<template>
<div class="tab-wrap w-full px-[16px]">
<el-tabs :closable="tabbarStore.tabLength > 1" :model-value="route.path" @tab-click="tabClick" @tab-remove="removeTab">
<el-tab-pane v-for="(tab, key, index) in tabbarStore.tabs" :name="tab.path" :key="index">
<div class="tab-wrap w-full px-[16px]" v-show="systemStore.tab">
<el-tabs :closable="tabbarStore.tabLength > 1" :model-value="route.name" @tab-click="tabClick" @tab-remove="removeTab">
<el-tab-pane v-for="(tab, key, index) in tabbarStore.tabs" :name="tab.name" :key="index">
<template #label>
<el-dropdown trigger="contextmenu" placement="bottom-start">
<span :class="{ 'text-primary': route.path == tab.path }" class="tab-name">{{ tab.title }}</span>
<span :class="{ 'text-primary': route.name == tab.name }" class="tab-name">{{ tab.title }}</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item icon="Back" :disabled="index == 0" @click="closeLeft(tab.path)">{{t('tabs.closeLeft') }}</el-dropdown-item>
<el-dropdown-item icon="Right" :disabled="index == (tabbarStore.tabLength - 1)" @click="closeRight(tab.path)">{{t('tabs.closeRight') }}</el-dropdown-item>
<el-dropdown-item icon="Close" :disabled="tabbarStore.tabLength == 1" @click="closeOther(tab.path)">{{t('tabs.closeOther') }}</el-dropdown-item>
<el-dropdown-item icon="Back" :disabled="index == 0" @click="closeLeft(tab.name)">{{t('tabs.closeLeft') }}</el-dropdown-item>
<el-dropdown-item icon="Right" :disabled="index == (tabbarStore.tabLength - 1)" @click="closeRight(tab.name)">{{t('tabs.closeRight') }}</el-dropdown-item>
<el-dropdown-item icon="Close" :disabled="tabbarStore.tabLength == 1" @click="closeOther(tab.name)">{{t('tabs.closeOther') }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
@ -22,10 +22,12 @@
<script lang="ts" setup>
import { watch, onMounted } from 'vue'
import useTabbarStore from '@/stores/modules/tabbar'
import useSystemStore from '@/stores/modules/system'
import { useRoute, useRouter } from 'vue-router'
import { t } from '@/lang'
const tabbarStore = useTabbarStore()
const systemStore = useSystemStore()
const route = useRoute()
const router = useRouter()
@ -43,7 +45,7 @@ watch(route, (nval: any) => {
*/
const tabClick = (content: any) => {
const tabRoute = tabbarStore.tabs[content.props.name]
router.push({ path: tabRoute.path, query: tabRoute.query })
router.push({ name: tabRoute.name, query: tabRoute.query })
}
/**
@ -51,45 +53,49 @@ const tabClick = (content: any) => {
* @param content
*/
const removeTab = (content: any) => {
if (route.path == content) {
if (route.name == content) {
const tabs = Object.keys(tabbarStore.tabs)
router.push({ path: tabs[tabs.indexOf(content) - 1] })
if (tabs.indexOf(content) == 0) {
router.push({ name: tabs[1] })
} else {
router.push({ name: tabs[tabs.indexOf(content) - 1] })
}
}
tabbarStore.removeTab(content)
}
/**
* 关闭左侧
* @param path
* @param name
*/
const closeLeft = (path: string) => {
const closeLeft = (name: string) => {
const tabs = Object.keys(tabbarStore.tabs)
for (let i = tabs.indexOf(path) - 1; i >= 0; i--) {
for (let i = tabs.indexOf(name) - 1; i >= 0; i--) {
delete tabbarStore.tabs[tabs[i]]
}
router.push({ path })
router.push({ name })
}
/**
* 关闭右侧
* @param path
* @param name
*/
const closeRight = (path: string) => {
const closeRight = (name: string) => {
const tabs = Object.keys(tabbarStore.tabs)
for (let i = tabs.indexOf(path) + 1; i < tabs.length; i++) {
for (let i = tabs.indexOf(name) + 1; i < tabs.length; i++) {
delete tabbarStore.tabs[tabs[i]]
}
router.push({ path })
router.push({ name })
}
/**
* 关闭其他
* @param path
* @param name
*/
const closeOther = (path: string) => {
const closeOther = (name: string) => {
const tabs = Object.keys(tabbarStore.tabs)
tabs.forEach((key: string) => { key != path && delete tabbarStore.tabs[key] })
router.push({ path })
tabs.forEach((key: string) => { key != name && delete tabbarStore.tabs[key] })
router.push({ name })
}
</script>

View File

@ -11,6 +11,8 @@
</el-header>
<!-- 顶部 end -->
<layout-tab />
<!-- 主体 -->
<el-main class="h-full p-0 bg-page">
<el-scrollbar>
@ -32,6 +34,7 @@
import { computed } from 'vue'
import layoutHeader from './components/header/index.vue'
import layoutAside from './components/aside/index.vue'
import layoutTab from './components/tabs.vue'
import useAppStore from '@/stores/modules/app'
import useTabbarStore from '@/stores/modules/tabbar'
import useSystemStore from '@/stores/modules/system'

View File

@ -13,7 +13,8 @@ interface System {
sidebarStyle: string,
currHeadMenuName: any,
website: Object,
layoutConfig: Object
layoutConfig: Object,
tab: Boolean
}
const theme = storage.get('theme') ?? {}
@ -30,7 +31,8 @@ const useSystemStore = defineStore('system', {
sidebarStyle: theme.sidebarStyle ?? 'threeType',
currHeadMenuName: '',
website: {},
layoutConfig: {}
layoutConfig: {},
tab: storage.get('tab') ?? false
}
},
actions: {

View File

@ -1,10 +1,10 @@
import { defineStore } from 'pinia'
import type { RouteLocationNormalizedLoaded } from 'vue-router'
import type { RouteLocationNormalizedLoaded, RouteRecordName } from 'vue-router'
interface Tabbar {
curr: string,
tabs: {
[key: string]: any
[key: RouteRecordName]: any
}
}
@ -18,11 +18,11 @@ const useTabbarStore = defineStore('tabbar', {
actions: {
addTab(roter: RouteLocationNormalizedLoaded) {
if (roter.meta && roter.meta.type != 1) return
if (this.tabs[roter.path]) {
this.tabs[roter.path].query = roter.query || {}
if (this.tabs[roter.name]) {
this.tabs[roter.name].query = roter.query || {}
return
}
this.tabs[roter.path] = {
this.tabs[roter.name] = {
path: roter.path,
title: roter.meta ? roter.meta.title : '',
name: roter.name,
@ -48,4 +48,4 @@ const useTabbarStore = defineStore('tabbar', {
}
})
export default useTabbarStore
export default useTabbarStore

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 3883393 */
src: url('//at.alicdn.com/t/c/font_3883393_dha1lk9gv4.woff2?t=1720083366330') format('woff2'),
url('//at.alicdn.com/t/c/font_3883393_dha1lk9gv4.woff?t=1720083366330') format('woff'),
url('//at.alicdn.com/t/c/font_3883393_dha1lk9gv4.ttf?t=1720083366330') format('truetype');
src: url('//at.alicdn.com/t/c/font_3883393_xso0134odj.woff2?t=1732777083024') format('woff2'),
url('//at.alicdn.com/t/c/font_3883393_xso0134odj.woff?t=1732777083024') format('woff'),
url('//at.alicdn.com/t/c/font_3883393_xso0134odj.ttf?t=1732777083024') format('truetype');
}
.iconfont {
@ -13,6 +13,106 @@
-moz-osx-font-smoothing: grayscale;
}
.iconshujutongji:before {
content: "\e835";
}
.iconshangpinguanli1:before {
content: "\e837";
}
.icondingdanguanli:before {
content: "\e838";
}
.icona-tupianzhanbopc302:before {
content: "\e83c";
}
.icona-jingxuantuijianpc302:before {
content: "\e83d";
}
.icona-jingxuantuijianpc30-12:before {
content: "\e83e";
}
.icona-baokuantuijianpc30:before {
content: "\e836";
}
.icona-shangpintuijianpc30:before {
content: "\e839";
}
.icona-paihangbangpc30:before {
content: "\e83a";
}
.icona-xinrenzhuanxiangpc30:before {
content: "\e83b";
}
.icona-lipinkatupianpc30:before {
content: "\e82c";
}
.icona-lipinkayouxiaoqipc30:before {
content: "\e82d";
}
.icona-lipinkazhufuyupc30:before {
content: "\e833";
}
.icona-lipinkamingchengpc30:before {
content: "\e834";
}
.iconshezhiV6xx:before {
content: "\e82e";
}
.iconshezhiV6xx-2:before {
content: "\e82f";
}
.iconshezhiV6xx-1:before {
content: "\e830";
}
.iconshezhiV6xx1:before {
content: "\e831";
}
.iconshezhi-1V6xx:before {
content: "\e832";
}
.icondiduiqi1:before {
content: "\e7b0";
}
.icondingduiqi1:before {
content: "\e827";
}
.iconchuizhijuzhong1:before {
content: "\e828";
}
.iconzuoduiqi1:before {
content: "\e829";
}
.iconshuipingjuzhong1:before {
content: "\e82a";
}
.iconyouduiqi1:before {
content: "\e82b";
}
.iconxiaochengxu2:before {
content: "\e63d";
}
@ -1113,10 +1213,6 @@
content: "\e628";
}
.icongouwuche1:before {
content: "\e680";
}
.icongonggao:before {
content: "\e629";
}
@ -2198,7 +2294,7 @@
}
.iconwenhao:before {
content: "\e890";
content: "\f1e3";
}
.iconmofang-liangzuoliangyou:before {

File diff suppressed because it is too large Load Diff

View File

@ -14,13 +14,6 @@
<name>niucloud-addon</name>
<description>niucloud 应用管理模块</description>
<modules>
<module>tour</module>
<module>sms-email</module>
<module>recharge</module>
<module>cms</module>
<module>shop</module>
<module>shop_giftcard</module>
<module>shop_fenxiao</module>
</modules>
<dependencies>
<dependency>

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