Compare commits

..

207 Commits

Author SHA1 Message Date
b23c21d05b Merge pull request 'fix/lang' (#4) from fix/lang into main
Reviewed-on: #4
2026-02-04 09:12:58 +00:00
65a24f25bb fix(lang): 提取getCurrentLocale为独立函数并优化代码格式
将getCurrentLocale方法提取为独立函数以避免重复代码
移除多余的空格并保持代码风格一致
2026-02-04 17:10:12 +08:00
e2b89c348f refactor(diy-tab): 优化标签组件结构和默认配置
- 使用 tab.id 作为 v-for 的 key 提高渲染性能
- 移除默认标签数据,改为空数组
- 添加 activeTabIndex 配置项
- 调整代码格式和注释
2026-01-30 17:41:23 +08:00
475edc93a6 refactor(diy-tab): 将默认配置拆分为多个独立对象以提高可维护性
将原本单一的defaults对象拆分为多个独立的配置对象(tabsConfig、baseConfig等),并通过展开运算符合并。这样修改使代码结构更清晰,便于后续维护和扩展。
2026-01-27 14:48:40 +08:00
cb86cba389 feat(components): 新增可自定义的diy-tab组件,支持多种样式和布局
添加一个高度可配置的tab组件,支持顶部、底部、左侧、右侧四种布局方式
提供默认、下划线和卡片三种tab样式,支持自定义颜色、间距、指示器等样式
组件包含导航栏和内容区域,支持动态切换和动画效果
- 添加自定义样式配置功能,允许通过 customStyles 完全覆盖组件样式
- 重构代码结构,使用计算属性合并默认值和传入值
- 优化样式处理逻辑,增加 mixin 复用
- 完善注释和文档说明
- 改进响应式动画效果
- 新增 getTabTitle 方法,支持根据当前语言环境显示对应的标签标题。该方法处理对象形式的标题(按语言键值匹配)和字符串形式的标题(支持国际化键翻译),提升组件的多语言适配能力。
2026-01-27 14:36:42 +08:00
0dc4dec616 refactor(diy-components): 统一使用@tap.stop替换@click事件处理
将组件中的@click事件统一替换为@tap.stop,避免在小程序环境中可能出现的点击事件冒泡问题,提升交互体验的一致性。同时移除冗余的@tap事件绑定,保持代码简洁。

主要修改包括:
- 表单提交按钮
- 分享功能
- 热区点击
- 富文本点击
- 视频播放
- 直播入口
- 商品品牌
- 客服功能
- 快捷导航
- 公告弹窗
- 文章列表
- 底部导航
- 商品列表
- 浮动按钮
- 优惠券
- 搜索功能
- 店铺相关
- 文本组件
- 分类页面
- 魔方组件
- 秒杀功能
- 拼团功能
- 预售功能
- 砍价功能
- 分销商品
- 图片广告
- 支付二维码
- 图片导航
- 音频控制
- 地图导航
- 笔记功能
- 商户列表
- 图片组件
- 会员订单
- 图文导航
- 首页分类
- 商品推荐
- 团购功能
- 限时折扣
- 商品分类
- 商品列表
- 优惠券
- 搜索功能
- 店铺相关
- 文本组件
- 分类页面
- 魔方组件
- 秒杀功能
- 拼团功能
- 预售功能
- 砍价功能
- 分销商品
- 图片广告
- 支付二维码
- 图片导航
- 音频控制
- 地图导航
- 笔记功能
- 商户列表
- 图片组件
- 会员订单
- 图文导航
- 首页分类
- 商品推荐
- 团购功能
- 限时折扣
- 商品分类
2026-01-26 15:05:36 +08:00
2a5214df11 feat(图片预览): 将图片预览功能提取到mixin并优化点击处理
重构图片预览功能,将其从diy-picture组件移至mixin实现,便于复用
优化diy-rubik-cube组件的点击处理逻辑,支持无链接时预览图片
统一处理图片预览和页面跳转的条件判断
2026-01-26 14:07:45 +08:00
2a0935b581 Merge pull request 'docs: 更新readme文档结构和内容' (#2) from dev/1.0 into main
Reviewed-on: #2
2026-01-24 09:41:36 +00:00
09e410df00 docs: 更新readme文档结构和内容
重构readme文档结构,使用更清晰的markdown格式和emoji图标
添加详细的开发调试说明和发布流程
补充构建脚本说明和版本历史记录
2026-01-24 17:40:40 +08:00
b31197a8b4 Merge pull request 'dev/1.0' (#1) from dev/1.0 into main
Reviewed-on: #1
2026-01-24 09:24:05 +00:00
Zhukj
75dfe80bde Merge remote-tracking branch 'remotes/origin/dev/1.0' into zhu-kaijiie/dev-1.0 2026-01-24 16:59:15 +08:00
Zhukj
2a1f33323c chore:修改readme.md小程序和快应用发布说明文档 2026-01-24 16:51:31 +08:00
Zhukj
b441c46993 chore:更新小程序发布说明文档 2026-01-24 11:19:53 +08:00
38ade75046 fix(diy-map): 替换cover-view为div解决z-index失效问题
移除ns-login组件中不必要的z-index设置
2026-01-24 09:20:01 +08:00
1e51abd7cd docs: 更新本地配置示例文件添加IVD数商模式配置
添加2812环境的IVD数商模式配置,并增加本地开发测试平台选项
2026-01-23 17:09:42 +08:00
0939449aa7 fix(ns-login): 修复complete-info-wrap弹窗层级问题 2026-01-23 17:09:05 +08:00
29b5cfda6f fix: 在页面加载时统一隐藏首页按钮
在多个页面的onLoad生命周期中添加this.$util.hideHomeButton()调用,确保页面加载时即隐藏首页按钮,避免显示不一致问题。同时调整category.vue中onShow的逻辑顺序。
2026-01-23 11:54:31 +08:00
ceca4e5956 fix: 在多个页面添加隐藏首页按钮功能
为统一处理首页按钮显示逻辑,在会员页、联系页和商品分类页的onShow生命周期中添加了hideHomeButton调用
2026-01-23 11:36:15 +08:00
aa9d2e64d2 feat(util): 添加隐藏微信小程序首页按钮功能
添加 hideHomeButton 方法用于在微信小程序环境中隐藏首页按钮,提升用户体验
2026-01-23 11:18:55 +08:00
e8ccb87266 feat(脚本): 增强微信小程序补丁脚本功能
- 添加新的 npm scripts 支持不同模式运行补丁脚本
- 支持命令行参数 --no-zip 和 --mode 控制 ZIP 文件生成和运行模式
- 自动复制项目配置文件到目标目录
- 更新使用说明文档
2026-01-23 10:12:30 +08:00
153c84266a chore: 更新项目配置文件和微信小程序名称
- 将项目名称从'lucky_shop'改为'mp-weixin'
- 在project.config.json中添加include数组和多个新配置项
- 启用enhance功能并添加编译相关设置
2026-01-23 09:25:58 +08:00
7ae7a1d3bd chore(uniapp): 增加拷贝文本超时检测提示功能 2026-01-17 15:30:02 +08:00
1ebb94e9e2 chore: 调整隐私协议及注册服务展示样式 2026-01-17 13:52:19 +08:00
9b53540f91 Merge branch 'dev/1.0' of http://git.aigc-quickapp.com/Uniapp/lucky_shop into dev/1.0 2026-01-16 18:11:00 +08:00
2a36fa2b60 chore(build): 调整默认小程序AppID及site.js文件 2026-01-16 18:07:59 +08:00
2223636184 Merge branch 'dev/1.0' of http://git.aigc-quickapp.com/Uniapp/lucky_shop into dev/1.0 2026-01-16 18:00:31 +08:00
6144dc72b8 chore:非 h5 平台 :key 不支持表达式的问题 2026-01-16 18:00:03 +08:00
47e1c2372d chore(sass): 编译器从node-sass转移成dart-sass 2026-01-16 17:52:33 +08:00
5b9bef9214 chore(build): 调整个别组件的位置,减少对主包尺寸的影响 2026-01-16 15:34:04 +08:00
08880a15df fix(电子名片): 再使用视频号组件的时候,严格转换数据类型 2026-01-16 10:47:52 +08:00
e40e6e73b2 chore(build): 调整ai-chat-message组件的位置,减少对主包尺寸的影响 2026-01-16 10:34:00 +08:00
dd4176998b Merge branch 'feat/personnel_channel' into dev/1.0 2026-01-16 10:16:40 +08:00
2896817435 chore: 删除不需要的页面及demo组件 2026-01-16 08:49:46 +08:00
dc956761a0 Merge branch 'feat/personnel_channel' into dev/1.0 2026-01-15 18:14:50 +08:00
36fd0621fd fix(视频号组件): padding 影响布局 2026-01-15 18:05:30 +08:00
1e6cd55f0a chore:中英文切换按钮能切换英文 2026-01-15 15:10:46 +08:00
f7fcf7fb27 chore:点击英文按钮,页面不是空白了 2026-01-15 15:01:39 +08:00
28359f2f16 fix(视频号组件): 整体控制是否显示观看次数没有生效 2026-01-15 14:58:36 +08:00
b3cc86d088 chore: .local.config.js.example 2026-01-15 14:37:39 +08:00
c6a8bc04f2 chore:解决了siteInfo,bgUrl错误 2026-01-15 14:08:32 +08:00
0ed0789528 chore(视频号组件): 支持轮播模式 2026-01-15 11:40:34 +08:00
16d1a3a945 feat(电子名片): 支持通过后台控制显示样式及封面尺寸 2026-01-15 09:44:54 +08:00
82df9ddbac chore:加了中英文切换按钮 2026-01-14 18:24:42 +08:00
392a8e2952 revert(视频号组件): 精简代码,调整样式以和后台设计统一 2026-01-14 16:37:44 +08:00
db7a8d7202 chore(视频号组件): 与后端设计器同步样式布局 2026-01-14 13:46:47 +08:00
bb87f4b1d2 fix(视频号组件): 用户取消的监听错误,不弹出错误提示 2026-01-14 09:41:41 +08:00
fe039ed126 revert(视频号组件): 精简代码,去除不用的组件 2026-01-14 09:31:22 +08:00
03c1a8d71a fix(视频号组件): 修复视频标题全是数字字符串不会自动换行的问题 2026-01-14 08:56:25 +08:00
e8fb35b310 chore:智能体客服正常运行 2026-01-13 18:26:11 +08:00
286fcc2142 chore:合并了之前智能客服的代码 2026-01-13 16:42:06 +08:00
2280ff68fa Merge branch 'feat-ai-agent-kefu' into xindeznkf
chore:合并了提交
2026-01-13 15:57:35 +08:00
9e0cab7ec4 chore(视频号组件): 针对微信视频号资源,点击播放的时候吧,弹出不能播放的原因 2026-01-13 14:51:09 +08:00
a31c004508 chore(视频号组件): 去除不需要的属性 2026-01-13 14:47:46 +08:00
ee8c24cdf8 chore(util): 格式化util.js 2026-01-13 14:39:22 +08:00
b043601a77 chore(视频号组件): 不仅支持16:9的封面,还支持3:4的 2026-01-13 14:26:10 +08:00
e20968aee2 chore(电子名片): 支持点击视频号视频,非嵌入模式可以播放了 2026-01-13 11:27:59 +08:00
8ceeca33b4 chore(manifest.json): "lazyCodeLoading": "requiredComponents", 2026-01-13 08:33:24 +08:00
02e9cfa313 chore(电子名片): 支持点击视频号视频,观看次数+1 2026-01-12 17:46:15 +08:00
3f894bcb20 chore(电子名片): 针对上传的封面及头像,转换图片路径 2026-01-12 16:25:02 +08:00
8d5d6e3da8 chore(视频号组件): 是否支持嵌入式播放,由使用者决定 2026-01-12 16:24:14 +08:00
ce1ea6eadf chore(电子名片): 更新企业文件及企业视频相关代码 2026-01-12 16:11:53 +08:00
191c803f66 chore(组件): 更新ns-video-player-popup 组件 2026-01-12 16:05:25 +08:00
4e1448d2c4 chore(js): 新增common/js/share.js,专注分享 2026-01-12 15:35:23 +08:00
ce26a9be5f feat(页面): 新增文件预览页面 2026-01-12 15:28:04 +08:00
8b01d8f8ec chore(视频号组件): 非微信小程序环境,发送消息,让父组件决定来怎么操作 2026-01-12 15:03:28 +08:00
6ab2834fdc feat(组件): 新增ns-video-player-popup 组件 2026-01-12 15:02:28 +08:00
85816e416c chore(视频号组件): 支持rowCount=1 的布局 2026-01-12 13:50:26 +08:00
bf8e177c30 chore(视频号组件): 支持2-3-4列布局 2026-01-12 13:47:13 +08:00
57111a00c2 fix(视频号组件): 让原生channel-video 组件样式与普通模式组件模式保持一致 2026-01-12 11:56:18 +08:00
aa7c7cbe60 chore(电子名片): 添加企业文件及企业视频部分模板及样式代码 2026-01-12 11:33:10 +08:00
d012c7fff7 chore(视频号组件): 优化视频号组件展示及播放 2026-01-12 11:32:31 +08:00
128d75b921 chore(视频号组件): 增加对auto-play 的控制 2026-01-12 08:57:07 +08:00
7e0d61c7e5 chore: 根据后端API设置相关模块的显示 2026-01-10 18:05:54 +08:00
26af083839 chore(电子名片): 支持展示视频号 2026-01-10 17:38:55 +08:00
68bc853dff chore(视频号组件): 更新文档及注释内容 2026-01-10 17:38:13 +08:00
13166132c7 chore(视频号组件): 更新视频号样式,控制标题行数,播放按钮等样式 2026-01-10 17:03:41 +08:00
f87c7d963e chore(视频号组件): 更新视频号样式 2026-01-10 16:12:36 +08:00
6f6899deaa chore(视频号组件): 重构视频号组件,不单单是微信视频号 2026-01-10 15:14:44 +08:00
7f54666629 chore(util): 增加蛇形命名转驼峰命名(递归处理对象中的所有属性名) 2026-01-10 13:44:50 +08:00
5747b256c6 chore:改了webd的基础路径为common 2026-01-09 17:54:18 +08:00
09b285016c chore:修改了快应用发行文档 2026-01-09 17:34:06 +08:00
72c12a79fd chore:修改了快应用的发行文档 2026-01-09 17:18:43 +08:00
84f4ad83c2 chore:增加了快应用打包步骤 2026-01-09 15:45:19 +08:00
1512c60109 chore:修改快应用发布 2026-01-09 11:52:29 +08:00
5b41dee46c Merge remote-tracking branch 'remotes/origin/dev/1.0' into dev/1.0 2026-01-09 11:34:19 +08:00
9ed337b59a chore(build): 增加对老服务器xcx20.5g-quickapp.com 网站的支持 2026-01-09 11:17:10 +08:00
8fedeafe63 chore:修改了小程序打包步骤 2026-01-09 10:41:45 +08:00
31336f9a80 chore:增加了快应用的打包过程 2026-01-09 10:33:50 +08:00
2f7c9bf0af Merge remote-tracking branch 'remotes/origin/dev/1.0' into dev/1.0 2026-01-08 16:50:24 +08:00
f8257361b5 chore:新加了快应用发行的文档 2026-01-08 16:08:00 +08:00
ec05534ff1 Merge branch 'dev/1.0' of https://git.aigc-quickapp.com/Uniapp/lucky_shop into dev/1.0 2026-01-08 15:36:57 +08:00
4f9c353e9a chore(用户中心): 账号退出或者注销,清理用户隐私协议授权设置 2026-01-08 14:58:47 +08:00
52b5f5b006 chore:重新合并了dev 2026-01-08 14:58:10 +08:00
5a8663eb48 fix: 解决在快应用环境中不显示用户隐私提示窗体的问题 2026-01-08 14:52:01 +08:00
8108802204 chore:手动和dev合并 2026-01-08 11:30:35 +08:00
6fe3bceb4b chore:手动合并和dev的代码 2026-01-08 11:18:58 +08:00
ee4fbb277c test(隐私): 测试 2026-01-08 09:39:15 +08:00
6718010ceb chore: common/js/util.js 增加判断是快应用的函数 2026-01-08 09:04:36 +08:00
faf1f6d3d8 chore: 增加判断是快应用环境的API函数 2026-01-07 18:28:51 +08:00
2d98a32efc fix(微信视频号组件): 宏导致的H5环境编译出错 2026-01-07 17:43:23 +08:00
7fde30063b chore(微信视频号): 优化channel-video组件的显示,更新技术文档 2026-01-07 17:27:42 +08:00
10465b7cf2 chore(微信视频号): 添加MP-WEIXIN 宏限制channel-video 2026-01-07 17:17:24 +08:00
c7e9b497ae chore(微信视频号): 优化微信视频号的配置及显示 2026-01-07 17:12:38 +08:00
de63cd4c6a feat(微信视频号): 增加微信视频号和微信视频号列表组件 2026-01-07 16:42:28 +08:00
2f230d3b2c chore: 使用常量来处理登录的路由页面路径 2026-01-07 15:51:24 +08:00
6f01b3c1c6 chore: 使用常量来处理首页的路由页面路径 2026-01-07 15:40:13 +08:00
b10fcf02d4 chore: 使用常量来处理会员中心的路由页面路径判断 2026-01-07 15:09:49 +08:00
6956841c8b chore(会员中心): 购物车、足迹、关注,优化副标题 2026-01-07 14:24:02 +08:00
90820ade30 fix(路由): 修复订单列表页面,验证登录页面,而没办法后退 2026-01-07 14:02:03 +08:00
8df59abd03 fix(关注/足迹): 优化交互逻辑 2026-01-07 11:35:28 +08:00
d1b9e528f8 fix(页面前缀): 分包造成的路径对应更新缺失 2026-01-06 18:14:35 +08:00
618babf5bf fix(多语言): 将售后待付款组件的翻译放到zh-cn/common文件中 2026-01-06 17:57:48 +08:00
9a63b5ff4e chore(主题样式): 更新主题列表,补全'pink', 'gold', 'purple', 'yellow', 'black' 2026-01-06 17:03:30 +08:00
d7b07c974d chore(加载主题样式): 只使用异步方式,保留一个函数loadTheme 2026-01-06 17:01:03 +08:00
6358102cae chore(浮动导航按钮样式): 统一使用灰底黑字 2026-01-06 16:46:57 +08:00
6023b5b65e chore(颜色): 明确颜色设置规则 2026-01-06 16:15:04 +08:00
b96b5f0bc3 chore: 更新本地调试配置示例 2026-01-06 15:35:49 +08:00
35aadc7014 fix(电子名片): 修复及调整复制按钮及一键拨打按钮的宽度及样式 2026-01-06 15:05:07 +08:00
4bdbfff7d4 chore(微信小程序权限): 默认申请剪贴板功能 2026-01-06 14:53:56 +08:00
5d34f00b3e chore(build): 只有生产模式下才开启vue.config.js 2026-01-06 14:33:29 +08:00
548aeed7b7 chore(电子名片): 有电子邮箱或者有电话的时候,才显示对应的内容 2026-01-06 14:32:50 +08:00
ce13661826 chore: wx.getSystemInfoSync is deprecated
wx.getSystemInfoSync is deprecated.Please use wx.getSystemSetting/wx.getAppAuthorizeSetting/wx.getDeviceInfo/wx.getWindowInfo/wx.getAppBaseInfo instead.
2026-01-06 14:02:34 +08:00
d9f0d1987e feat(ns-login): 增加隐私条款及用户服务协议 2026-01-06 11:34:19 +08:00
afd002fad3 chore(组件): 解决uni-popup-*组件注册问题及开发环境配置 2026-01-06 10:18:01 +08:00
531dc347f7 chore(组件): 更新依赖 2026-01-05 18:31:42 +08:00
405f37a3f6 chore(组件): privacyPopup 组件全部使用注册机制 2026-01-05 18:11:14 +08:00
73f9cb8820 chore(组件): 全部使用easycom来处理组件问题 2026-01-05 17:24:49 +08:00
b1ebb38c06 chore: .local.config.js.example 2026-01-05 15:58:31 +08:00
60a0e5133e revert: 所有代码与custom/common分支同 2026-01-05 15:56:49 +08:00
8ef6975ee1 chore(组件): 由于easycom会自动注册components和uni_modules 目录下的组件,所以去除引用部分 2026-01-05 15:27:34 +08:00
be51b30fb4 chore: 暂时保存 2026-01-05 11:22:38 +08:00
b24f77be1a chore(组件): components-diy 必须使用easyncom系统,不然在微信上无法渲染 2026-01-05 09:29:19 +08:00
311efe1ecd chore(分包): 拆分pages_tooll子包 2026-01-04 16:02:31 +08:00
db8fb25da8 chore(分包): 拆分pages_promotion子包 2026-01-04 15:27:31 +08:00
9415f397d2 chore(组件): 组件尽量使用异步导入模式 2026-01-04 14:40:00 +08:00
4da852944e chore(代码合并): 代码从Custom/common分支合并 2026-01-04 09:40:09 +08:00
26f8537d24 fix(会员中心): 样式未定义触发异常 2025-12-30 09:22:51 +08:00
44dd7f30cf chore(分包): tarBar达到分包前效果 2025-12-30 09:13:29 +08:00
366c4e4cd0 fix(底部TabBar): 点击后,按钮激活状态下,不变颜色 2025-12-29 17:47:32 +08:00
d5b46b4086 chore(TabBar): 默认systemTabBarList,只保留4个选项 2025-12-29 15:55:51 +08:00
30403871e7 chore(test): 测试代码 2025-12-29 15:54:00 +08:00
6b5a268955 chore(多语言): 优化登录页面的中英文展示 2025-12-29 11:23:32 +08:00
9f25cb5336 chore(多语言): 支持登录页面多语言 2025-12-29 11:11:52 +08:00
74c7eaf497 chore(多语言): 部分多语言采用 isEnEnv 变量了判断,后面移植到语言包中 2025-12-29 10:56:04 +08:00
f9973d9546 fix(ns-login): privacy-popup 没有正确引入 2025-12-29 10:53:29 +08:00
1886616bb4 chore(多语言): 补充英文翻译 2025-12-29 10:51:38 +08:00
332350cfab chore(多语言): 部分语言暂时使用 isEnEnv 计算属性来判断 2025-12-26 18:11:15 +08:00
31623e6667 chore(多语言): 将isEnEnv计算属性挂载到全局Vue组件上,针对商品名称优化英文显示 2025-12-26 17:54:20 +08:00
d36e7f6bba fix(组件): 修复组件错误 2025-12-26 16:50:02 +08:00
40f5f63096 chore(组件): 引入DiyMinx的js及修复组件错误 2025-12-26 16:35:15 +08:00
d1778c1fd7 chore(组件): 将diy组件独立出来 2025-12-26 16:14:06 +08:00
8e2e817a4d chore(组件): 组件移动方案实施可行性最高的方案 2025-12-26 15:23:28 +08:00
6744bd2806 chore(组件): MescrollUni 组件是引用的@/components/mescroll/my-list-mescroll.vue 2025-12-26 14:39:12 +08:00
86831b8551 chore(组件): 全局组件改为按需引用 2025-12-26 14:29:38 +08:00
e949b14090 chore(分包): 目录结构调整 2025-12-26 11:03:16 +08:00
b01de93eee fix(ns-login): 修复H5页面在微信App中点击登录无反应的问题 2025-12-25 17:45:52 +08:00
2c3786546c chore: 使用宏来控制自定义H5网站部署发布 2025-12-25 17:00:10 +08:00
0e60dd7d0f fix(hover-nav): 修复按钮非正圆形状的问题 2025-12-25 10:01:23 +08:00
01b620fcb3 chore(config): 支持从本地文件中获取.loacl.config.js 获取 2025-12-25 09:56:29 +08:00
7d00f9d0f8 fix(member): 修复pages_tool/member/invite_friends.vue组件样式错误 2025-12-25 09:55:06 +08:00
be0f4be0ed chore: 调整在H5端上的浮动按钮位置,上移80px 2025-12-24 16:53:27 +08:00
e5d5c961fb chore(ui): 微调 2025-12-24 16:04:10 +08:00
25337be57f chore(浮动按钮): 样式定制 2025-12-24 15:22:18 +08:00
43e66b1798 fix(ns-goods-action-icon): 客服联系按钮英文状态下,点击无效 2025-12-24 14:49:13 +08:00
d8d6c5631f feat(lang): 实现浮动按钮切换语言功能 2025-12-24 14:45:17 +08:00
d2da799dfc fix(diy-article): display-multiple-items 不能大于 swiper-item 数量 2025-12-23 17:59:23 +08:00
d54a7e9f13 fix(code): 修正代码错误及不严谨的地方 2025-12-23 17:56:20 +08:00
5588165f2c fix(code): 修正代码错误及不严谨的地方 2025-12-23 16:59:14 +08:00
68ecdd6e3c feat: 增加文件查询及展示的模块 2025-12-23 15:33:22 +08:00
306a6af89c feat: 增加介质材料模块及案例展示模块 2025-12-23 15:09:34 +08:00
bccb572552 feat(weapp): 增加小程序发货页面 2025-12-23 14:05:10 +08:00
92db148237 chore(diy-article): 支持diy-article 组件关于滚动控制的参数化设置 2025-12-23 11:45:03 +08:00
96049bbcae feat(diy-article): 支持diy-article 组件内的内容自动垂直滚动 2025-12-23 11:22:29 +08:00
1d7629168c feat: 将底部导航、分类项、分类、图标作为组件添加Diy面板中 2025-12-23 10:19:07 +08:00
b91d76f2d1 feat: 新增视频列表diy-video-lis 组件 2025-12-23 10:08:31 +08:00
3758dbcb03 feat: 新增数字展示diy-digit组件 2025-12-23 10:04:57 +08:00
e49b1ae8f7 feat: 新增图片导航diy-image-nav 组件 2025-12-23 10:02:53 +08:00
8e2f8ef4e8 chore(contact): 增加滚动到顶端的按钮 2025-12-23 09:36:49 +08:00
66a58fd8e8 chore: 调整首页的回到顶端的按钮的样式 2025-12-23 09:35:41 +08:00
f835fafeec chore(h5): 去掉不需要的注释内容 2025-12-22 17:53:32 +08:00
82581efb10 chore: common/js/config.js 更新配置,方便调整 2025-12-22 17:52:23 +08:00
dd7191d3ef chore: 增加快应用/纯H5的如何获取uniacid的处理说明 2025-12-22 17:18:45 +08:00
521c460b2e chore: App.vue 增加快应用/纯H5的如何获取uniacid的处理说明 2025-12-22 17:18:08 +08:00
ed8d3ebd18 chore(h5): 如果是H5环境,首页不强制必须登录 2025-12-22 17:16:42 +08:00
171779455f fix: 商品列表图标按钮切换,页面会跳转 2025-12-22 14:50:00 +08:00
0340c3b67c feat: 支持商品列表样式切换 2025-12-22 14:42:11 +08:00
8cc804bc6b chore(event-bus.js): 去除多余的注释内容 2025-12-20 17:31:51 +08:00
dd2006dd8c fix: 解决首页需要登录才能看到元素 2025-12-20 17:30:01 +08:00
8a934348a6 fix(event-bus.js): defaultAsyncHandler 的返回值,被截 2025-12-20 17:02:24 +08:00
973c8dddd0 fix(index): 修复首页在已登录状态下,不能点击的问题 2025-12-20 16:52:29 +08:00
3556691b56 chore(store): 更新store定义 2025-12-20 15:57:36 +08:00
3c795256e8 chore(js): 新增navigation.js 2025-12-20 15:57:11 +08:00
72b2087204 chore(components): 只增加组件 2025-12-20 15:51:44 +08:00
53a71d7afd chore(js): 新增ai、客服、安全事件、wxwork-jssdk.js 2025-12-20 15:50:04 +08:00
3cbe0263ea chore: 修改遗留代码单词拼写错误 2025-12-20 15:41:32 +08:00
f3bb6a1782 chore:智能体客服和原来的客服都在 2025-12-17 15:43:37 +08:00
2a0489d4b2 chore:能正常运行 2025-12-17 11:17:50 +08:00
ca74d4f8e5 chore: 要明确集成微信及支付宝原生客服 2025-12-17 09:19:19 +08:00
d9c9599cb2 chore: 集成统一客服服务 2025-12-16 16:54:39 +08:00
142a97d65c revert: pages/contact/contact.vue 回退到最初状态 2025-12-16 15:52:18 +08:00
b945583857 chore: 优化公共端都支持打开企业微信客服的方法 2025-12-16 15:41:15 +08:00
88debacf8c chore: 使用全局的wxworkConfig配置作为参数 2025-12-16 14:41:45 +08:00
9d12b02463 chore: 将wxworkConfig放到全局配置中 2025-12-16 14:41:15 +08:00
23e81114e9 chore: 后端init接口数据为wxwork_config 2025-12-16 10:54:57 +08:00
08583aa8aa chore: 企业微信客服组件完全独立 2025-12-15 15:04:45 +08:00
5e536afeae feat: 新增打开企业微信客服组件 2025-12-15 14:26:01 +08:00
1854a85394 chore: update comments 2025-11-28 17:38:35 +08:00
549 changed files with 68587 additions and 52341 deletions

47
.local.config.js Normal file
View File

@@ -0,0 +1,47 @@
// 本地调试配置示例文件
// 复制此文件并重命名为 local.config.js 以使用自定义本地配置
const localDevConfig = ({
'460': { // 制氧设备平台
uniacid: 460,
domain: 'https://xcx30.5g-quickapp.com/',
},
'576-xcx30.5g': { // 活性石灰装备
uniacid: 576,
domain: 'https://xcx30.5g-quickapp.com/',
},
'2285': { // 数码喷墨墨水
uniacid: 2285,
domain: 'https://xcx.aigc-quickapp.com/',
},
'2811': { // POCT检测分析平台
uniacid: 2811,
domain: 'https://xcx6.aigc-quickapp.com/',
},
'2724': { // 生物菌肥
uniacid: 2724,
domain: 'https://xcx.aigc-quickapp.com/',
},
'2505': { // 煤矿钻机
uniacid: 2505,
domain: 'https://xcx.aigc-quickapp.com/',
},
'2777': { // 养老服务
uniacid: 2777,
domain: 'https://xcx.aigc-quickapp.com/',
},
'1': { // 开发平台
uniacid: 1,
domain: 'https://dev.aigc-quickapp.com',
},
'1-test': { // 测试平台
uniacid: 1,
domain: 'https://test.aigc-quickapp.com',
},
'local-2': { // 测试平台
uniacid: 2,
domain: 'http://localhost:8050/',
},
})['2811']; // 选择要使用的环境配置
export default localDevConfig;

55
.local.config.js.example Normal file
View File

@@ -0,0 +1,55 @@
// 本地调试配置示例文件
// 复制此文件并重命名为 local.config.js 以使用自定义本地配置
const localDevConfig = ({
'460': { // 制氧设备平台
uniacid: 460,
domain: 'https://xcx30.5g-quickapp.com/',
},
'576-xcx30.5g': { // 活性石灰装备
uniacid: 576,
domain: 'https://xcx30.5g-quickapp.com/',
},
'2285': { // 数码喷墨墨水
uniacid: 2285,
domain: 'https://xcx.aigc-quickapp.com/',
},
'2811': { // POCT检测分析平台
uniacid: 2811,
domain: 'https://xcx6.aigc-quickapp.com/',
},
'2812': { // IVD数商模式
uniacid: 2812,
domain: 'https://xcx6.aigc-quickapp.com/',
},
'2724': { // 生物菌肥
uniacid: 2724,
domain: 'https://xcx.aigc-quickapp.com/',
},
'2505': { // 煤矿钻机
uniacid: 2505,
domain: 'https://xcx.aigc-quickapp.com/',
},
'2777': { // 养老服务
uniacid: 2777,
domain: 'https://xcx.aigc-quickapp.com/',
},
'1': { // 开发平台
uniacid: 1,
domain: 'https://dev.aigc-quickapp.com',
},
'1-test': { // 测试平台
uniacid: 1,
domain: 'https://test.aigc-quickapp.com',
},
'local-2': { // 测试平台
uniacid: 2,
domain: 'http://localhost:8050/',
},
'local-2-dev': { // 本地开发测试平台
uniacid: 2,
domain: 'http://localhost:8050/',
},
})['2812']; // 选择要使用的环境配置
export default localDevConfig;

23
App.vue
View File

@@ -1,17 +1,17 @@
<script>
import auth from 'common/js/auth.js';
import colorList from 'common/js/style_color.js'
import configExternal from 'common/js/config-external.js'
import {
Weixin
} from 'common/js/wx-jssdk.js';
export default {
mixins: [auth],
onLaunch: function(options) {
// console.log(options.query.uniacid)
onLaunch: async function(options) {
// 方式支持快应用从url中query部分获取uniacid或useragent中获取uniacid
if(options.query.uniacid){
uni.setStorageSync('uniacid', options.query.uniacid);
console.log(uni.getStorageSync('uniacid'))
}
uni.hideTabBar();
@@ -40,7 +40,7 @@
// #endif
// #ifdef H5
if (uni.getSystemInfoSync().platform == 'ios') {
if (this.$util.getDeviceInfo().platform == 'ios') {
uni.setStorageSync('initUrl', location.href);
}
// #endif
@@ -61,7 +61,9 @@
// 主题风格
if (uni.getStorageSync('themeStyle')) {
this.$store.commit('setThemeStyle', colorList[uni.getStorageSync('themeStyle')]);
const themeData = await configExternal.loadTheme(uni.getStorageSync('themeStyle'));
this.$store.commit('setThemeStyle', themeData);
this.$store.dispatch('themeColorSet');
}
// 插件是否存在
@@ -98,6 +100,11 @@
if (uni.getStorageSync('servicerConfig')) {
this.$store.commit('setServicerConfig', uni.getStorageSync('servicerConfig'));
}
// 企业微信配置
if (uni.getStorageSync('wxworkConfig')) {
this.$store.commit('setWxworkConfig', uni.getStorageSync('wxworkConfig'));
}
// 版权信息
if (uni.getStorageSync('copyright')) {
@@ -296,7 +303,7 @@
this.shareConfig();
}
},
// 代表在wacth里声明了firstName这个方法之后立即先去执行handler方法
// 代表在watch里声明了firstName这个方法之后立即先去执行handler方法
immediate: true
}
}
@@ -307,7 +314,7 @@
@import './common/css/iconfont.css';
@import './common/css/icondiy.css'; // 自定义图标库
@import './common/css/icon/extend.css'; // 扩展图标库
page{
page {
background: #f4f6fa;
}
</style>

View File

@@ -123,7 +123,7 @@ image {
}
.choose-store {
/deep/ .uni-popup__wrapper{
::v-deep .uni-popup__wrapper{
background: none!important;
}
}

View File

@@ -870,13 +870,13 @@
// 海报
// .uni-popup__wrapper-box
.poster-layer {
/deep/ .uni-popup__wrapper.center {
::v-deep .uni-popup__wrapper.center {
width: 100vw!important;
height: 100vh!important;
background: none!important;
}
/deep/ .uni-popup__wrapper.uni-custom.center .uni-popup__wrapper-box {
::v-deep .uni-popup__wrapper.uni-custom.center .uni-popup__wrapper-box {
max-width: 100vw!important;
max-height: 100vh!important;
background: none!important;

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,4 @@
// 修复图片垂直对齐问题,解决两张图片上下有空白缝隙问题
::v-deep ._img {
vertical-align: top;
}

View File

@@ -377,7 +377,7 @@ view {
}
}
/deep/ .goods-form {
::v-deep .goods-form {
display: flex;
align-items: center;
position: relative;
@@ -1354,7 +1354,7 @@ view {
border-bottom: 2rpx solid #F4F4F6;
}
/deep/ .form-wrap {
::v-deep .form-wrap {
margin: 0 24rpx;
.icon-right {

View File

@@ -1,356 +1,434 @@
import http from './http.js'
import store from '@/store/index.js'
export default {
/**
* 发送消息到Dify API
* @param {string} message 用户消息内容
* @param {Object} options 配置选项
* @returns {Promise}
*/
async sendMessage(message, options = {}) {
try {
// 获取AI配置
const aiConfig = store.getters.globalAIAgentConfig
// 构建Dify API请求参数
const params = {
url: '/api/ai/chat', // 后端代理接口
data: {
message: message,
conversation_id: options.conversationId || this.generateConversationId(),
user_id: store.state.memberInfo?.id || 'anonymous',
stream: options.stream || false, // 是否流式响应
// Dify API参数
inputs: {},
query: message,
response_mode: options.stream ? 'streaming' : 'blocking',
user: store.state.memberInfo?.id || 'anonymous'
},
header: {
'Content-Type': 'application/json'
}
}
// 如果有Dify配置添加API密钥
if (aiConfig?.difyApiKey) {
params.header['Authorization'] = `Bearer ${aiConfig.difyApiKey}`
}
if (aiConfig?.difyBaseUrl) {
params.header['X-Dify-Url'] = aiConfig.difyBaseUrl
}
// 发送请求
const response = await http.sendRequest({
...params,
async: false // 使用Promise方式
})
return this.handleResponse(response, options)
} catch (error) {
console.error('Dify API请求失败:', error)
throw new Error('AI服务暂时不可用请稍后重试')
}
},
/**
* 流式消息处理
* @param {string} message 用户消息
* @param {Function} onChunk 流式数据回调
* @param {Function} onComplete 完成回调
*/
async sendStreamMessage(message, onChunk, onComplete) {
try {
const aiConfig = store.getters.globalAIAgentConfig
// 检查配置
if (!aiConfig?.difyBaseUrl || !aiConfig?.difyApiKey) {
throw new Error('未配置Dify服务')
}
// 创建WebSocket连接或使用Server-Sent Events
if (aiConfig?.difyWsUrl) {
return this.connectWebSocket(message, onChunk, onComplete)
} else {
// 使用HTTP流式请求
return this.sendHttpStream(message, onChunk, onComplete)
}
} catch (error) {
console.error('流式消息发送失败:', error)
throw error
}
},
/**
* WebSocket连接
*/
connectWebSocket(message, onChunk, onComplete) {
return new Promise((resolve, reject) => {
const aiConfig = store.getters.globalAIAgentConfig
const wsUrl = aiConfig.difyWsUrl
if (!wsUrl) {
reject(new Error('未配置WebSocket地址'))
return
}
// #ifdef H5
const ws = new WebSocket(wsUrl)
ws.onopen = () => {
// 发送消息
ws.send(JSON.stringify({
message: message,
user_id: store.state.memberInfo?.id || 'anonymous',
conversation_id: this.generateConversationId()
}))
}
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data)
if (data.type === 'chunk' && onChunk) {
onChunk(data.content)
} else if (data.type === 'complete' && onComplete) {
onComplete(data.content)
ws.close()
resolve(data.content)
}
} catch (e) {
console.error('WebSocket消息解析失败:', e)
}
}
ws.onerror = (error) => {
console.error('WebSocket连接错误:', error)
reject(error)
}
ws.onclose = () => {
console.log('WebSocket连接关闭')
}
// #endif
// #ifdef MP-WEIXIN || APP-PLUS
// 小程序和APP使用uni.connectSocket
uni.connectSocket({
url: wsUrl,
success: () => {
uni.onSocketOpen(() => {
uni.sendSocketMessage({
data: JSON.stringify({
message: message,
user_id: store.state.memberInfo?.id || 'anonymous',
conversation_id: this.generateConversationId()
})
})
})
uni.onSocketMessage((res) => {
try {
const data = JSON.parse(res.data)
if (data.type === 'chunk' && onChunk) {
onChunk(data.content)
} else if (data.type === 'complete' && onComplete) {
onComplete(data.content)
uni.closeSocket()
resolve(data.content)
}
} catch (e) {
console.error('WebSocket消息解析失败:', e)
}
})
uni.onSocketError((error) => {
console.error('WebSocket连接错误:', error)
reject(error)
})
},
fail: (error) => {
reject(error)
}
})
// #endif
})
},
/**
* HTTP流式请求
*/
async sendHttpStream(message, onChunk, onComplete) {
const aiConfig = store.getters.globalAIAgentConfig
const params = {
url: '/api/ai/chat-stream',
data: {
message: message,
conversation_id: this.generateConversationId(),
user_id: store.state.memberInfo?.id || 'anonymous',
stream: true
},
header: {
'Content-Type': 'application/json'
}
}
if (aiConfig?.difyApiKey) {
params.header['Authorization'] = `Bearer ${aiConfig.difyApiKey}`
}
// 使用fetch API进行流式请求H5环境
// #ifdef H5
try {
const response = await fetch(`${aiConfig.difyBaseUrl}/chat-messages`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${aiConfig.difyApiKey}`
},
body: JSON.stringify({
inputs: {},
query: message,
response_mode: 'streaming',
user: store.state.memberInfo?.id || 'anonymous'
})
})
const reader = response.body.getReader()
const decoder = new TextDecoder()
let content = ''
while (true) {
const { done, value } = await reader.read()
if (done) break
const chunk = decoder.decode(value)
const lines = chunk.split('\n')
for (const line of lines) {
if (line.startsWith('data: ')) {
try {
const data = JSON.parse(line.slice(6))
if (data.event === 'text_message' && data.text) {
content += data.text
if (onChunk) onChunk(data.text)
}
} catch (e) {
// 忽略解析错误
}
}
}
}
if (onComplete) onComplete(content)
return content
} catch (error) {
console.error('HTTP流式请求失败:', error)
throw error
}
// #endif
// 非H5环境使用普通请求模拟流式效果
// #ifndef H5
const response = await http.sendRequest({
...params,
async: false
})
// 模拟流式效果
if (response.success && response.data) {
const content = response.data
const chunkSize = 3
let index = 0
const streamInterval = setInterval(() => {
if (index < content.length) {
const chunk = content.substring(index, index + chunkSize)
index += chunkSize
if (onChunk) onChunk(chunk)
} else {
clearInterval(streamInterval)
if (onComplete) onComplete(content)
}
}, 100)
}
// #endif
},
/**
* 处理API响应
*/
handleResponse(response, options) {
if (response.code === 0 || response.success) {
return {
success: true,
content: response.data?.answer || response.data?.content || response.data,
conversationId: response.data?.conversation_id,
messageId: response.data?.message_id
}
} else {
throw new Error(response.message || 'AI服务返回错误')
}
},
/**
* 生成会话ID
*/
generateConversationId() {
return 'conv_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9)
},
/**
* 获取AI服务状态
*/
async getServiceStatus() {
try {
const aiConfig = store.getters.globalAIAgentConfig
if (!aiConfig?.difyBaseUrl || !aiConfig?.difyApiKey) {
return {
available: false,
reason: '未配置Dify服务'
}
}
// 简单的健康检查
const response = await http.sendRequest({
url: '/api/ai/health',
async: false
})
return {
available: response.success,
reason: response.success ? '服务正常' : '服务异常'
}
} catch (error) {
return {
available: false,
reason: '服务检查失败'
}
}
},
/**
* 清除会话历史
*/
async clearConversation(conversationId) {
try {
const response = await http.sendRequest({
url: '/api/ai/clear-conversation',
data: { conversation_id: conversationId },
async: false
})
return response.success
} catch (error) {
console.error('清除会话失败:', error)
return false
}
}
}
import Config from './config.js'
import http from './http.js'
import store from '@/store/index.js'
let currentConversationId = null;
const CONVERSATION_KEY = 'ai_conversation_id';
// 初始化时从本地读取会话ID
try {
const saved = uni.getStorageSync(CONVERSATION_KEY);
if (saved) {
currentConversationId = saved;
}
} catch (e) {
console.warn('读取会话ID失败:', e);
}
export default {
/**
* 发送普通消息blocking 模式)
*/
async sendMessage(message, options = {}) {
try {
const aiConfig = store.getters.globalAIKefuConfig;
const params = {
url: '/api/kefu/chat',
data: {
user_id: store.state.memberInfo?.id || 'anonymous',
stream: false,
inputs: {},
query: message,
response_mode: 'blocking', // 强制 blocking
user: store.state.memberInfo?.id || 'anonymous',
conversation_id: this.getConversationId() || ''
},
header: {
'Content-Type': 'application/json'
}
};
console.log('Sending blocking request to:', params.url);
console.log('Request data:', params.data);
const response = await http.sendRequest({
...params,
async: false
});
const result = this.handleResponse(response);
if (result.conversationId) {
this.setConversationId(result.conversationId);
}
return result;
} catch (error) {
console.error('Dify API请求失败:', error);
throw new Error('AI服务暂时不可用请稍后重试');
}
},
/**
* 流式消息入口(自动适配平台)
*/
async sendStreamMessage(message, onChunk, onComplete) {
// #ifdef MP-WEIXIN
// 微信小程序:降级为普通请求 + 前端打字模拟
try {
const result = await this.sendMessage(message);
const content = result.content || '';
const conversationId = result.conversationId || '';
// 保存会话ID确保连续对话
if (conversationId) {
this.setConversationId(conversationId);
}
// 模拟打字效果
let index = 0;
const chunkSize = 2; // 每次显示2个字符
return new Promise((resolve) => {
const timer = setInterval(() => {
if (index < content.length) {
const chunk = content.substring(index, index + chunkSize);
index += chunkSize;
if (onChunk) onChunk(chunk);
} else {
clearInterval(timer);
if (onComplete) {
onComplete({
content: content,
conversation_id: conversationId
});
}
resolve({ content, conversation_id: conversationId });
}
}, 80); // 打字速度80ms/次
});
} catch (error) {
console.error('小程序流式消息降级失败:', error);
if (onComplete) {
onComplete({ error: error.message || '发送失败' });
}
throw error;
}
// #endif
// #ifdef H5
// H5使用真实流式EventSource / Fetch
return this.sendHttpStream(message, onChunk, onComplete);
// #endif
},
/**
* HTTP 流式请求(仅 H5 使用)
*/
async sendHttpStream(message, onChunk, onComplete) {
// #ifdef H5
try {
const url = Config.baseUrl + `/api/kefu/chat`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'text/event-stream',
},
body: JSON.stringify({
uniacid: store.state.uniacid || '1',
inputs: {},
query: message,
response_mode: 'streaming',
user_id: store.state.memberInfo?.id || 'anonymous',
conversation_id: this.getConversationId() || ''
})
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
if (!response.body) {
throw new Error('响应体不可用');
}
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
let buffer = '';
let content = '';
let conversationId = '';
function processBuffer(buf, callback) {
const lines = buf.split('\n');
buf = lines.pop() || '';
for (const line of lines) {
const trimmed = line.trim();
if (trimmed.startsWith('data:')) {
const jsonStr = trimmed.slice(5).trim();
if (jsonStr) {
try {
const data = JSON.parse(jsonStr);
if (data.event === 'message') {
const text = data.answer || data.text || '';
content += text;
callback(text);
}
if (data.conversation_id) {
conversationId = data.conversation_id;
}
if (data.event === 'message_end') {
// 可选:提前完成
}
} catch (e) {
console.warn('解析流数据失败:', e);
}
}
}
}
return buf;
}
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
buffer = processBuffer(buffer, (chunk) => {
if (onChunk) onChunk(chunk);
});
}
if (onComplete) {
onComplete({
content,
conversation_id: conversationId
});
}
return { content, conversation_id: conversationId };
} catch (error) {
console.error('H5 流式请求失败:', error);
throw error;
}
// #endif
// #ifdef MP-WEIXIN
// 理论上不会执行到这里,但防止 fallback
return this.sendStreamMessage(message, onChunk, onComplete);
// #endif
},
/**
* 处理API响应统一格式
*/
handleResponse(response) {
if (response.code === 0 || response.success) {
return {
success: true,
content: response.data?.answer || response.data?.content || response.data?.reply || response.data,
conversationId: response.data?.conversation_id,
messageId: response.data?.message_id
};
} else {
throw new Error(response.message || 'AI服务返回错误');
}
},
/**
* 获取当前会话ID
*/
getConversationId() {
return currentConversationId;
},
/**
* 设置并持久化会话ID
*/
setConversationId(id) {
if (id && id !== currentConversationId) {
currentConversationId = id;
try {
uni.setStorageSync(CONVERSATION_KEY, id);
} catch (e) {
console.error('保存会话ID失败:', e);
}
}
},
/**
* 清除会话ID
*/
clearConversationId() {
currentConversationId = null;
try {
uni.removeStorageSync(CONVERSATION_KEY);
} catch (e) {
console.error('清除会话ID失败:', e);
}
},
/**
* 生成新会话(如需)
*/
async generateConversationId() {
const params = {
url: '/api/kefu/createConversation',
data: {
uniacid: store.state.uniacid,
user_id: store.state.memberInfo?.id || 'anonymous',
member_id: store.state.memberInfo?.id || '',
},
header: {
'Content-Type': 'application/json'
}
};
const response = await http.sendRequest({
...params,
async: false
});
return this.handleResponse(response);
},
/**
* 获取服务状态
*/
async getServiceStatus() {
try {
const response = await http.sendRequest({
url: '/api/kefu/health',
async: false,
data: {}
});
const available = [response?.data?.status, response?.data?.components?.ai_service_config?.status]
.filter(item => item === 'healthy').length > 0;
return {
available,
reason: available ? '服务正常' : '服务异常'
};
} catch (error) {
return {
available: false,
reason: '服务检查失败'
};
}
},
/**
* 清除会话历史(调用后端接口)
*/
async clearConversation(conversationId) {
try {
const response = await http.sendRequest({
url: '/api/kefu/clear-conversation',
data: { conversation_id: conversationId },
async: false
});
return response.success;
} catch (error) {
console.error('清除会话失败:', error);
return false;
}
},
/**
* 获取会话历史
*/
async getConversationHistory(params = {}) {
try {
if (!params.conversation_id) {
throw new Error('会话IDconversation_id是必填参数');
}
const requestData = {
uniacid: params.uniacid || store.state.uniacid || '1',
conversation_id: params.conversation_id,
user_id: params.user_id || store.state.memberInfo?.id || 'anonymous',
limit: params.limit || 20,
offset: params.offset || 0,
member_id: params.member_id || store.state.memberInfo?.id || '',
token: params.token || ''
};
const response = await http.sendRequest({
url: '/api/kefu/getHistory',
data: requestData,
header: {
'Content-Type': 'application/json'
},
async: false
});
if (response.code === 0 || response.success) {
return {
success: true,
data: response.data,
messages: response.data?.messages || [],
total: response.data?.total || 0,
page_info: response.data?.page_info || { limit: 20, offset: 0 }
};
} else {
throw new Error(response.message || '获取会话历史失败');
}
} catch (error) {
console.error('获取会话历史失败:', error);
throw new Error(error.message || '获取会话历史时发生错误,请稍后重试');
}
},
// ================================
// 以下方法仅用于 H5小程序无效
// ================================
/**
* [H5 Only] EventSource 流式聊天(备用)
*/
chatWithEventSource(message, conversationId = '', onMessage, onComplete, onError) {
// #ifdef H5
if (window.currentEventSource) {
window.currentEventSource.close();
}
const params = new URLSearchParams({
uniacid: store.state.uniacid || '1',
user_id: store.state.memberInfo?.id || '123456',
query: message,
conversation_id: conversationId || '',
stream: 'true'
});
const url = `${Config.baseUrl}/api/kefu/chat?${params.toString()}`;
try {
const eventSource = new EventSource(url);
window.currentEventSource = eventSource;
let aiMessage = '';
eventSource.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
if (data.event === 'message') {
aiMessage += data.answer || '';
if (onMessage) onMessage(data.answer || '');
}
if (data.event === 'message_end') {
if (onComplete) onComplete({
conversation_id: data.conversation_id,
message: aiMessage
});
}
} catch (error) {
console.error('解析消息失败:', error);
}
};
eventSource.onerror = (error) => {
console.error('EventSource错误:', error);
if (onError) onError({ error: '连接失败' });
window.currentEventSource = null;
};
return eventSource;
} catch (error) {
console.error('创建EventSource失败:', error);
if (onError) onError({ error: error.message });
return null;
}
// #endif
// #ifdef MP-WEIXIN
console.warn('chatWithEventSource 不支持微信小程序');
return null;
// #endif
},
/**
* [H5 Only] Fetch 流式聊天(备用)
*/
async chatWithFetchStream(message, conversationId = '', onMessage, onComplete, onError) {
// #ifdef H5
// 实际逻辑已在 sendHttpStream 中实现,此处可复用或留空
return this.sendHttpStream(message, onMessage, (res) => {
onComplete?.({ message: res.content, conversation_id: res.conversation_id });
});
// #endif
// #ifdef MP-WEIXIN
console.warn('chatWithFetchStream 不支持微信小程序');
return null;
// #endif
}
};

View File

@@ -0,0 +1,132 @@
// 外置配置管理
// 用于管理需要外置的配置项,如语言包、主题配置等
// 语言包配置
export const langConfig = {
// 语言列表
langList: ['zh-cn', 'en-us'],
// 默认语言
defaultLang: 'zh-cn',
// 语言包加载方式:'lazy'(按需加载)或 'preload'(预加载)
loadMode: 'lazy'
};
// 主题配置
export const themeConfig = {
// 主题列表
themeList: ['default', 'red', 'green', 'blue', 'pink', 'gold', 'purple', 'yellow', 'black'],
// 默认主题
defaultTheme: 'default'
};
// API配置
export const apiConfig = {
// API请求超时时间
timeout: 30000,
// 是否开启重试
retry: true,
// 重试次数
retryCount: 3,
// 重试间隔时间(毫秒)
retryDelay: 1000
};
// 配置外置管理类
class ConfigExternal {
constructor() {
this.loadedConfigs = {};
this.loadPromises = {};
}
/**
* 加载语言包
* @param {string} lang - 语言类型
* @returns {Promise} - 加载结果
*/
async loadLang(lang = langConfig.defaultLang) {
if (this.loadedConfigs[`lang_${lang}`]) {
return this.loadedConfigs[`lang_${lang}`];
}
if (this.loadPromises[`lang_${lang}`]) {
return this.loadPromises[`lang_${lang}`];
}
this.loadPromises[`lang_${lang}`] = new Promise((resolve, reject) => {
try {
// 动态加载语言包
const langData = require(`@/lang/${lang}/common.js`).lang;
this.loadedConfigs[`lang_${lang}`] = langData;
resolve(langData);
} catch (error) {
console.error(`加载语言包 ${lang} 失败:`, error);
reject(error);
}
});
return this.loadPromises[`lang_${lang}`];
}
/**
* 加载页面语言包
* @param {string} lang - 语言类型
* @param {string} pagePath - 页面路径
* @returns {Promise} - 加载结果
*/
async loadPageLang(lang = langConfig.defaultLang, pagePath) {
const key = `page_${lang}_${pagePath}`;
if (this.loadedConfigs[key]) {
return this.loadedConfigs[key];
}
if (this.loadPromises[key]) {
return this.loadPromises[key];
}
this.loadPromises[key] = new Promise((resolve, reject) => {
try {
// 动态加载页面语言包
const pageLangData = require(`@/lang/${lang}/${pagePath}.js`).lang;
this.loadedConfigs[key] = pageLangData;
resolve(pageLangData);
} catch (error) {
console.error(`加载页面语言包 ${lang}/${pagePath} 失败:`, error);
resolve({});
}
});
return this.loadPromises[key];
}
/**
* 加载主题配置(异步方式)
* @param {string} theme - 主题名称
* @returns {Promise} - 加载结果
*/
async loadTheme(theme = themeConfig.defaultTheme) {
if (this.loadedConfigs[`theme_${theme}`]) {
return this.loadedConfigs[`theme_${theme}`];
}
if (this.loadPromises[`theme_${theme}`]) {
return this.loadPromises[`theme_${theme}`];
}
this.loadPromises[`theme_${theme}`] = new Promise((resolve, reject) => {
try {
// 动态加载主题配置
const themeData = require(`@/common/js/style_color.js`)['default'][theme];
// console.log('async themeData => ', themeData);
this.loadedConfigs[`theme_${theme}`] = themeData;
resolve(themeData);
} catch (error) {
console.error(`加载主题 ${theme} 失败:`, error);
reject(error);
}
});
return this.loadPromises[`theme_${theme}`];
}
}
export default new ConfigExternal();

View File

@@ -1,32 +1,92 @@
// 发行版本,配置说明
let releaseCfg = undefined;
// #ifdef APP-PLUS || MP-WEIXIN || MP-ALIPAY || MP-BAIDU || MP-QQ || MP-TOUTIAO
try {
if (site) {
if (site) { // 从site.js文件中获取配置, 针对微信小程序类
releaseCfg = {
baseUrl: site.baseUrl,
imgDomain: site.baseUrl,
h5Domain: site.baseUrl,
uniacid: site.uniacid,
}
} else {
console.warn('小程序发行环境下必须配置site.js文件, 不然将使用默认配置,影响小程序定制分发!');
}
} catch (e) {}
// #endif
// 调试版本,配置说明
const devCfg = {
// 线上版本部署域名, 注意: 必须以 '/' 结尾,适用于小程序/H5/快应用等场景
// 默认域名, 自定义发行时可以修改
let defaultDomain = '';
// #ifdef H5_XCX_5G_QUICKAPP_COM
defaultDomain = 'https://xcx20.5g-quickapp.com';
// #endif
// #ifdef H5_XCX_AIGC_QUICKAPP_COM
defaultDomain = 'https://xcx.aigc-quickapp.com/';
// #endif
// #ifdef H5_5G_AIGC_QUICKAPP_COM
defaultDomain = 'https://5g.aigc-quickapp.com/';
// #endif
// #ifdef H5_TEST_AIGC_QUICKAPP_COM
defaultDomain = 'https://test.aigc-quickapp.com/';
// #endif
// 确保域名以 '/' 结尾
if (defaultDomain && !defaultDomain.endsWith('/')) {
defaultDomain += '/';
}
// 本地调试配置示例文件
// 复制.local.config.js.example 并重命名为 .local.config.js 以使用自定义本地配置
let localDevConfig = { uniacid: 0, domain: defaultDomain };
// #ifndef PRODUCTION
// 尝试动态加载本地配置文件
if (process?.env?.NODE_ENV === 'development') {
try {
// 尝试从项目根目录加载本地配置文件
// 使用相对路径的方式,确保在不同平台下都能正确解析
let customConfig = require('@/.local.config.js').default;
if (customConfig) {
localDevConfig = customConfig;
}
} catch (e) {
// 如果本地配置文件不存在或加载失败,保持使用默认配置
// 只在开发模式下输出提示信息
// #ifdef WEB
console.log('本地配置文件 .local.config.js 不存在,使用默认配置');
// #endif
}
}
// #endif
const { uniacid = 0, domain = defaultDomain } = localDevConfig ?? { uniacid: 0, domain: defaultDomain }
export default {
/**
* 1.开发调试模式
* 去掉注释 ...defaultCfg;
* 注释掉 ...releaseCfg,
* 2.发行/发布模式,例如通过`HBuilder>发行>小程序微信`的时候,原理是:
* 然后将 `import site from "../site.js";`追加到 `unpackage\dist\build\mp-weixin\common\vendor.js` 文件内容开头部分
* 然后将 site.js 文件放到 `unpackage\dist\build\mp-weixin\` 目录下面
*/
// 商户ID
uniacid: 1, //825
uniacid: uniacid, //825
//api请求地址
baseUrl: 'https://dev.aigc-quickapp.com/',
// baseUrl: 'http://localhost:8010/',
baseUrl: domain,
// 图片域名
imgDomain: 'https://dev.aigc-quickapp.com/',
//imgDomain: 'http://localhost:8010/',
imgDomain: domain,
// H5端域名
h5Domain: 'https://dev.aigc-quickapp.com/',
// h5Domain: 'http://localhost:8010/',
h5Domain: domain,
// // api请求地址
// baseUrl: 'https://tsaas.liveplatform.cn/',
@@ -45,31 +105,20 @@ const devCfg = {
// // H5端域名
// h5Domain: 'http://saas.cn/',
};
var config = {
/**
* 1.开发调试模式
* 去掉注释 ...devCfg;
* 注释掉 ...releaseCfg,
* 2.发行/发布模式,例如通过`HBuilder>发行>小程序微信`的时候,原理是:
* 然后将 `import site from "../site.js";`追加到 `unpackage\dist\build\mp-weixin\common\vendor.js` 文件内容开头部分
* 然后将 site.js 文件放到 `unpackage\dist\build\mp-weixin\` 目录下面
*/
...(releaseCfg ?? devCfg),
// 发行版本配置
...(releaseCfg || {}),
// 腾讯地图key
mpKey: 'TUHBZ-CNWKU-UHAVP-GZQ26-HNZFO-3YBF4',
//客服地址
// 客服地址
webSocket: '{{$webSocket}}',
//本地端主动给服务器ping的时间, 0 则不开启 , 单位秒
// 本地端主动给服务器ping的时间, 0 则不开启 , 单位秒
pingInterval: 1500,
// 版本号
version: '1.0'
};
export default config;
};

View File

@@ -0,0 +1,545 @@
/**
* 客服统一处理服务
* 整合各种客服方式,提供统一的调用接口
*/
class CustomerService {
constructor(vueInstance, externalConfig = {}) {
if (!vueInstance.$lang) {
throw new Error('CustomerService 必须在 Vue 实例中初始化');
}
this.vm = vueInstance;
this.externalConfig = externalConfig; // 外部传入的最新配置(优先级最高)
this.latestPlatformConfig = null;
}
getSupoortKeFuList() {
if (!this.vm) return [];
const vm = this.vm;
return [
{
id: 'weixin-official',
name: vm.$lang('customer.weChatKefu'),
isOfficial: true,
type: 'weapp'
},
{
id: 'custom-kefu',
name: vm.$lang('customer.systemKefu'),
isOfficial: false
},
{
id: 'qyweixin-kefu',
name: vm.$lang('customer.weChatWorkKefu'),
isOfficial: false
},
]
}
/**
* 打开客服选择弹窗
*/
openCustomerSelectPopupDialog() {
const kefu_list = this.getSupoortKeFuList();
const kefuNames = kefu_list.map(item => item.name);
uni.showActionSheet({
itemList: kefuNames,
success: (res) => {
const kefu = kefu_list[res.tapIndex];
this.externalConfig = kefu ?? this.externalConfig ?? {};
if (kefu.isOfficial) {
uni.openCustomerServiceConversation({
sessionFrom: 'weapp',
showMessageCard: true
});
} else if (kefu.id === 'custom-kefu') {
this.handleCustomerClick();
} else if (kefu.id === 'qyweixin-kefu') {
this.handleQyWeixinKefuClick();
}
}
});
}
/**
* 强制刷新配置(支持传入外部配置)
* @param {Object} externalConfig 外部最新配置
*/
refreshConfig(externalConfig = null) {
this.externalConfig = externalConfig || this.externalConfig;
this.latestPlatformConfig = null;
return this.getPlatformConfig();
}
/**
* 获取平台配置
* @returns {Object} 平台对应的客服配置
*/
getPlatformConfig() {
if (this.latestPlatformConfig) {
return this.latestPlatformConfig;
}
// 优先级:外部传入的最新配置 > vuex配置 > 空对象
const servicerConfig = this.externalConfig || this.vm.$store.state.servicerConfig || {};
console.log(`【实时客服配置】`, servicerConfig);
let platformConfig = null;
// #ifdef H5
platformConfig = servicerConfig.h5 ? (typeof servicerConfig.h5 === 'object' ? servicerConfig.h5 : null) : null;
// #endif
// #ifdef MP-WEIXIN
platformConfig = servicerConfig.weapp ? (typeof servicerConfig.weapp === 'object' ? servicerConfig.weapp : null) : null;
// #endif
// #ifdef MP-ALIPAY
platformConfig = servicerConfig.aliapp ? (typeof servicerConfig.aliapp === 'object' ? servicerConfig.aliapp : null) : null;
// #endif
// #ifdef PC
platformConfig = servicerConfig.pc ? (typeof servicerConfig.pc === 'object' ? servicerConfig.pc : null) : null;
// #endif
// 处理空数组情况你的配置中pc/aliapp是空数组转为null
if (Array.isArray(platformConfig)) {
platformConfig = null;
}
this.latestPlatformConfig = platformConfig;
return platformConfig;
}
/**
* 获取企业微信配置
* @returns {Object} 企业微信配置
*/
getWxworkConfig() {
return this.vm.$store.state.wxworkConfig || {};
}
/**
* 检查客服配置是否可用
* @returns {boolean} 是否有可用配置
*/
isConfigAvailable() {
const config = this.getPlatformConfig();
return config && typeof config === 'object' && config.type;
}
/**
* 验证客服配置完整性
* @returns {Object} 验证结果
*/
validateConfig() {
const config = this.getPlatformConfig();
const wxworkConfig = this.getWxworkConfig();
const result = {
isValid: true,
errors: [],
warnings: []
};
if (!config) {
result.isValid = false;
result.errors.push('客服配置不存在');
return result;
}
if (config.type === 'aikefu') {
return result;
}
if (!config.type) {
result.isValid = false;
result.errors.push('客服类型未配置');
}
if (config.type === 'wxwork') {
if (!wxworkConfig) {
result.isValid = false;
result.errors.push('企业微信配置不存在');
} else {
if (!wxworkConfig.enable) {
result.warnings.push('企业微信功能未启用');
}
if (!wxworkConfig.contact_url) {
result.warnings.push('企业微信活码链接未配置,将使用原有客服方式');
}
}
}
return result;
}
/**
* 跳转到AI客服页面
*/
openAIKeFuService() {
const vm = this.vm;
vm.$util.redirectTo(vm.$util.AI_CHAT_PAGE_URL);
}
/**
* 处理客服点击事件
* @param {Object} options 选项参数
*/
handleCustomerClick(options = {}) {
const validation = this.validateConfig();
if (!validation.isValid) {
console.error('客服配置验证失败:', validation.errors);
this.showConfigErrorPopup(validation.errors);
return;
}
if (validation.warnings.length > 0) {
console.warn('客服配置警告:', validation.warnings);
}
const config = this.getPlatformConfig();
const { niushop = {}, sendMessageTitle = '', sendMessagePath = '', sendMessageImg = '' } = options || {};
if (config.type === 'none') {
this.showNoServicePopup();
return;
}
// 核心分支根据最新的type处理
switch (config.type) {
case 'aikefu':
this.openAIKeFuService();
break;
case 'wxwork':
this.openWxworkService(false, config, options);
break;
case 'third':
this.openThirdService(config);
break;
case 'niushop':
this.openNiushopService(niushop);
break;
case 'weapp':
this.openWeappService(config, options);
break;
case 'aliapp':
this.openAliappService(config);
break;
default:
this.makePhoneCall();
}
}
/**
* 打开企业微信客服
* @param {boolean} useOriginalService 是否使用原有客服方式
* @param {Object} servicerConfig 客服配置
* @param {Object} options 选项参数
*/
openWxworkService(useOriginalService = false, servicerConfig = null, options = {}) {
const config = servicerConfig || this.getPlatformConfig();
const wxworkConfig = this.getWxworkConfig();
const { sendMessageTitle = '', sendMessagePath = '', sendMessageImg = '' } = options || {};
// #ifdef MP-WEIXIN
if (wxworkConfig?.enable && wxworkConfig?.contact_url && !useOriginalService) {
wx.navigateToMiniProgram({
appId: 'wxeb490c6f9b154ef9',
path: `pages/contacts/externalContactDetail?url=${encodeURIComponent(wxworkConfig.contact_url)}`,
success: () => console.log('跳转企业微信成功'),
fail: (err) => {
console.error('跳转企业微信失败:', err);
this.fallbackToOriginalService();
}
});
} else {
wx.openCustomerServiceChat({
extInfo: { url: config.wxwork_url },
corpId: config.corpid,
showMessageCard: true,
sendMessageTitle,
sendMessagePath,
sendMessageImg
});
}
// #endif
// #ifdef H5
if (wxworkConfig?.enable && wxworkConfig?.contact_url) {
window.location.href = wxworkConfig.contact_url;
} else if (config.wxwork_url) {
location.href = config.wxwork_url;
} else {
this.fallbackToPhoneCall();
}
// #endif
}
/**
* 打开第三方客服
* @param {Object} config 客服配置
*/
openThirdService(config) {
if (config.third_url) {
window.location.href = config.third_url;
} else {
this.fallbackToPhoneCall();
}
}
/**
* 打开牛商客服
* @param {Object} niushop 牛商参数
*/
openNiushopService(niushop) {
if (Object.keys(niushop).length > 0) {
this.vm.$util.redirectTo('/pages_tool/chat/room', niushop);
} else {
this.makePhoneCall();
}
}
/**
* 打开微信小程序客服
* @param {Object} config 客服配置
* @param {Object} options 选项参数
*/
openWeappService(config, options = {}) {
if (!this.shouldUseCustomService(config)) {
console.log('使用官方微信小程序客服');
return;
}
console.log('使用自定义微信小程序客服');
this.handleCustomWeappService(config, options);
}
/**
* 处理自定义微信小程序客服
* @param {Object} config 客服配置
* @param {Object} options 选项参数
*/
handleCustomWeappService(config, options = {}) {
const { sendMessageTitle = '', sendMessagePath = '', sendMessageImg = '' } = options || {};
if (config.customServiceUrl) {
let url = config.customServiceUrl;
const params = [];
if (sendMessageTitle) params.push(`title=${encodeURIComponent(sendMessageTitle)}`);
if (sendMessagePath) params.push(`path=${encodeURIComponent(sendMessagePath)}`);
if (sendMessageImg) params.push(`img=${encodeURIComponent(sendMessageImg)}`);
if (params.length > 0) {
url += (url.includes('?') ? '&' : '?') + params.join('&');
}
uni.navigateTo({
url: url,
fail: (err) => {
console.error('跳转自定义客服页面失败:', err);
this.tryThirdPartyService(config, options);
}
});
return;
}
this.tryThirdPartyService(config, options);
}
/**
* 尝试使用第三方客服
* @param {Object} config 客服配置
* @param {Object} options 选项参数
*/
tryThirdPartyService(config, options = {}) {
if (config.thirdPartyServiceUrl) {
// #ifdef H5
window.open(config.thirdPartyServiceUrl, '_blank');
// #endif
// #ifdef MP-WEIXIN
if (config.thirdPartyMiniAppId) {
wx.navigateToMiniProgram({
appId: config.thirdPartyMiniAppId,
path: config.thirdPartyMiniAppPath || '',
fail: (err) => {
console.error('跳转第三方小程序失败:', err);
this.fallbackToPhoneCall();
}
});
} else {
uni.setClipboardData({
data: config.thirdPartyServiceUrl,
success: () => {
uni.showModal({
title: '客服链接已复制',
content: '客服链接已复制到剪贴板,请在浏览器中粘贴访问',
showCancel: false
});
}
});
}
// #endif
return;
}
this.fallbackToPhoneCall();
}
/**
* 降级到电话客服
*/
fallbackToPhoneCall() {
uni.showModal({
title: '联系客服',
content: '在线客服暂时不可用,是否拨打电话联系客服?',
success: (res) => {
if (res.confirm) {
this.makePhoneCall();
}
}
});
}
/**
* 打开支付宝小程序客服
* @param {Object} config 客服配置
*/
openAliappService(config) {
console.log('支付宝小程序客服', config);
switch (config.type) {
case 'aikefu':
this.openAIKeFuService();
break;
case 'third':
this.openThirdService(config);
break;
default:
console.log('使用支付宝官方客服');
break;
}
}
/**
* 拨打电话
*/
makePhoneCall(mobileNumber) {
if (mobileNumber) {
return uni.makePhoneCall({
phoneNumber: mobileNumber
});
}
// 从缓存中获取电话信息
uni.getStorage({
key: 'shopInfo',
success: (res) => {
const shopInfo = res.data;
const mobile = shopInfo?.mobile ?? '';
if (mobile) {
uni.makePhoneCall({
phoneNumber: mobile
});
} else {
uni.showToast({
title: '暂无客服电话',
icon: 'none'
});
}
}
});
}
/**
* 显示无客服弹窗
*/
showNoServicePopup() {
const siteInfo = this.vm.$store.state.siteInfo || {};
const message = siteInfo?.site_tel
? `请联系客服,客服电话是${siteInfo.site_tel}`
: '抱歉,商家暂无客服,请线下联系';
uni.showModal({
title: '联系客服',
content: message,
showCancel: false
});
}
/**
* 显示配置错误弹窗
* @param {Array} errors 错误列表
*/
showConfigErrorPopup(errors) {
const message = errors.join('\n');
uni.showModal({
title: '配置错误',
content: `客服配置有误:\n${message}`,
showCancel: false
});
}
/**
* 降级处理:使用原有客服方式
*/
fallbackToOriginalService() {
uni.showModal({
title: '提示',
content: '无法直接添加企业微信客服,是否使用其他方式联系客服?',
success: (res) => {
if (res.confirm) {
this.openWxworkService(true);
}
}
});
}
/**
* 获取客服按钮配置
* @returns {Object} 按钮配置
*/
getButtonConfig() {
const config = this.getPlatformConfig();
if (!config) return { openType: '' };
let openType = '';
// #ifdef MP-WEIXIN
if (config.type === 'weapp') {
openType = config.useOfficial !== false ? 'contact' : '';
}
// #endif
// #ifdef MP-ALIPAY
if (config.type === 'aliapp') openType = 'contact';
// #endif
return { ...config, openType };
}
/**
* 判断是否应该使用自定义客服处理
* @param {Object} config 客服配置
* @returns {boolean} 是否使用自定义客服
*/
shouldUseCustomService(config) {
// #ifdef MP-WEIXIN
if (config?.type === 'weapp') {
return config.useOfficial === false;
}
// #endif
return true;
}
}
/**
* 创建客服服务实例
* @param {Object} vueInstance Vue实例
* @param {Object} externalConfig 外部最新配置
* @returns {CustomerService} 客服服务实例
*/
export function createCustomerService(vueInstance, externalConfig = {}) {
return new CustomerService(vueInstance, externalConfig);
}

View File

@@ -1,7 +1,9 @@
import WxMap from 'common/js/map-wx-jssdk.js';
import Config from '@/common/js/config.js';
import util from '@/common/js/util.js';
let systemInfo = util.getDeviceInfo();
let systemInfo = uni.getSystemInfoSync();
export default {
data() {
return {
@@ -334,7 +336,11 @@ export default {
let diyDataValue = res.data;
if (diyDataValue.value) {
this.diyData = JSON.parse(diyDataValue.value);
this.$langConfig.title(this.diyData.global.title);
// 导航栏标题要根据语言环境切换
const title = this.isEnEnv ? this.diyData?.global?.en_title : this.diyData?.global?.title;
if (title) this.$langConfig.title(title);
this.mpCollect = this.diyData.global.mpCollect;
this.setPublicShare();
/* if (this.diyData.global.popWindow && this.diyData.global.popWindow.imageUrl) {
@@ -603,10 +609,10 @@ export default {
},
// 分享给好友
onShareAppMessage() {
return this.mpShareData.appMessage;
return this.mpShareData?.appMessage;
},
// 分享到朋友圈
onShareTimeline() {
return this.mpShareData.timeLine;
return this.mpShareData?.timeLine;
}
}

View File

@@ -110,7 +110,7 @@ class EventBus {
// 异步触发事件,支持取消传播,并支持在监听处理后调用异步函数 defaultAsyncHandler
// 使用方式await eventBus.emit(eventName, data, defaultAsyncHandler)
// defaultAsyncHandler 会在每个监听器执行后被调用,签名为 defaultAsyncHandler(event, handler, handlerResult)
// defaultAsyncHandler 会在监听器执行后被调用,签名为 defaultAsyncHandler(event, handler, handlerResult)
// 如果 defaultAsyncHandler 返回 Promise会等待其 resolve
// 如果 allowContinuePropagation 为 false事件触发后将立即返回不继续传播
// 如果 stopOnError 为 true在任意监听器抛出错误后立即停止传播
@@ -123,6 +123,7 @@ class EventBus {
// helper to call defaultAsyncHandler only when allowed
const callDefaultAsyncHandler = async (evt, handler, handlerResult) => {
// console.log('callDefaultAsyncHandler', {evt, handler, handlerResult});
if (typeof defaultAsyncHandler !== 'function' || !allowAsyncHandlerRun) return
try {
const res = defaultAsyncHandler(evt, handler, handlerResult)
@@ -152,7 +153,7 @@ class EventBus {
return !event.defaultPrevented
}
// 否则继续走普通 EventBus 处理fallthrough
}
}
// 普通 EventBus 触发
const handlers = this.events.get(eventName)
@@ -189,6 +190,7 @@ class EventBus {
}
// 在每个监听器执行后,如果允许并且提供了 defaultAsyncHandler就调用并等待它
// console.log('==> callDefaultAsyncHandler', {event, handler, awaitedResult})
await callDefaultAsyncHandler(event, handler, awaitedResult)
} catch (error) {
console.error(`EventBus ${eventName} error:`, error)

View File

@@ -1,25 +1,19 @@
export default {
data() {
return {
// 页面样式,动态设置主色调
themeColor: '' //''--base-color:#fa5d14;--base-help-color:#ff7e00;'
}
},
onLoad() {},
onLoad() { },
onShow() {
// 刷新多语言
this.$langConfig.refresh();
let time = setInterval(() => {
let theme = this.themeStyle;
if (theme && theme.main_color) {
this.themeColorSet();
clearInterval(time);
}
}, 50);
},
computed: {
// 是否是英文环境
isEnEnv() {
return this.$langConfig.getCurrentLocale() === 'en-us';
},
themeStyle() {
return this.$store.state.themeStyle;
},
themeColor() {
return this.$store.state.themeColor;
},
// 插件是否存在
addonIsExist() {
@@ -75,6 +69,10 @@ export default {
servicerConfig() {
return this.$store.state.servicerConfig;
},
// 企业微信配置
wxworkConfig() {
return this.$store.state.wxworkConfig;
},
diySeckillInterval() {
return this.$store.state.diySeckillInterval;
},
@@ -107,25 +105,7 @@ export default {
}
},
methods: {
themeColorSet() {
let theme = this.themeStyle;
this.themeColor = `--base-color:${theme.main_color};--base-help-color:${theme.aux_color};`;
if (this.tabBarHeight != '56px') this.themeColor += `--tab-bar-height:${this.tabBarHeight};`
Object.keys(theme).forEach(key => {
let data = theme[key];
if (typeof(data) == "object") {
Object.keys(data).forEach(k => {
this.themeColor += '--' + k.replace(/_/g, "-") + ':' + data[k] + ';';
});
} else if (typeof(key) == "string" && key) {
this.themeColor += '--' + key.replace(/_/g, "-") + ':' + data + ';';
}
});
for (let i = 9; i >= 5; i--) {
let color = this.$util.colourBlend(theme.main_color, '#ffffff', (i / 10));
this.themeColor += `--base-color-light-${i}:${color};`;
}
},
// 颜色变浅(>0、变深函数<0
lightenDarkenColor(color, amount) {
@@ -169,7 +149,7 @@ export default {
if (isJump && route != 'pages/index/index') {
uni.setStorageSync('manual_change_store', true); // 手动切换门店
this.$store.dispatch('getCartNumber'); //重新获取购物车数据
this.$util.redirectTo('/pages/index/index');
this.$util.redirectTo(this.$util.INDEX_PAGE_URL);
}
}
},

View File

@@ -183,7 +183,7 @@ export default {
};
if (this.$refs.goodsPromotion) this.$refs.goodsPromotion.refresh(this.goodsSkuDetail.goods_promotion);
if (this.goodsRoute != '/pages/goods/detail') this.setPublicShare();
if (this.goodsRoute != '/pages_goods/detail') this.setPublicShare();
// this.getBarrageData();
if (this.addonIsExist.form) {
@@ -240,11 +240,11 @@ export default {
},
goHome() {
if (this.preview) return; // 开启预览,禁止任何操作和跳转
this.$util.redirectTo('/pages/index/index');
this.$util.redirectTo(this.$util.INDEX_PAGE_URL);
},
goCart() {
if (this.preview) return; // 开启预览,禁止任何操作和跳转
this.$util.redirectTo('/pages/goods/cart');
this.$util.redirectTo('/pages_goods/cart');
},
//-------------------------------------关注-------------------------------------
//更新商品信息

View File

@@ -49,7 +49,7 @@ export default {
}
var method = params.data != undefined ? 'POST' : 'GET', // 请求方式
url = Config.baseUrl + params.url, // 请求路径
url = (Config.baseUrl + params.url).replace(/(?<!:)\/+/g, '/'), // 请求路径
data = {
app_type,
app_type_name
@@ -57,7 +57,8 @@ export default {
// token
data.token = store.state.token || '';
data.uniacid = Config.uniacid
data.uniacid = uni.getStorageSync('uniacid') || Config.uniacid; // 从缓存中获取uniacid或使用默认uniacid支持快应用
// 门店id
var default_store_info = store.state.defaultStoreInfo;
if (default_store_info) {

View File

@@ -1,52 +1,125 @@
const langList = ['zh-cn', 'en-us'];
import { langConfig } from './config-external.js';
var locale = uni.getStorageSync('lang') || "zh-cn"; //设置语言
// 缓存已加载的语言包
const loadedLangPacks = {};
// 处理页面目录映射
function processRoutePath(route) {
let routeParts = route.split("/");
// ---- 处理页面目录映射 <begin> 分包造成的,需要根据实际目录结构进行映射----
// 先处理特殊的分包路径
if (routeParts[0] === 'pages_tool') {
// pages_tool 分包下的页面,直接使用子目录作为语言包路径
routeParts = [routeParts[1], ...routeParts.slice(2)];
} else if (routeParts[0] === 'pages_goods') {
// pages_goods 分包映射到 goods 目录
routeParts[0] = 'goods';
} else if (routeParts[0] === 'pages_member') {
// pages_member 分包映射到 member 目录
routeParts[0] = 'member';
} else if (routeParts[0] === 'pages_order') {
// pages_order 分包映射到 order 目录
routeParts[0] = 'order';
} else if (routeParts[0] === 'pages_promotion') {
// pages_promotion 分包特殊处理
const promotionModules = ['point', 'fenxiao', 'merch'];
if (routeParts[1] && promotionModules.includes(routeParts[1])) {
routeParts = [routeParts[1], ...routeParts.slice(2)];
}
}
// ---- 处理页面目录映射 <end>----
// 去掉pages目录只保留子目录
if (routeParts[0] === 'pages') {
routeParts = routeParts.slice(1);
}
return routeParts.join("/");
}
// 加载语言包(同步方式)
function loadLangPackSync(lang, path) {
try {
if (loadedLangPacks[`${lang}_${path}`]) {
return loadedLangPacks[`${lang}_${path}`];
}
const langData = require(`@/lang/${lang}/${path}.js`).lang;
loadedLangPacks[`${lang}_${path}`] = langData;
return langData;
} catch (error) {
console.error(`加载语言包 ${lang}/${path} 失败:`, error);
return {};
}
}
/**
* 获得当前本地语言
* @returns
*/
function getCurrentLocale() {
return uni.getStorageSync('lang') || "zh-cn";
}
export default {
langList: ['zh-cn', 'en-us'],
langList: langConfig.langList,
/**
* 获得当前本地语言
* @returns
*/
getCurrentLocale,
/**
* * 解析多语言
* @param {Object} field
*/
lang(field) {
let _this = getCurrentPages()[getCurrentPages().length - 1];
if (!_this) return;
let _page = getCurrentPages()[getCurrentPages().length - 1];
if (!_page) return;
const locale = getCurrentLocale(); // 获得当前本地语言
let value = ''; // 存放解析后的语言值
let langPath = ''; // 存放当前页面语言包路径
var value = '';
let newRoute;
try {
//公共语言包
var lang = require('../../lang/' + locale + '/common.js').lang;
//当前页面语言包
let route = _this.route.split("/");
newRoute = route.slice(1, route.length);
let currentPageLang = require('../../lang/' + locale + '/' + newRoute.join("/") + '.js').lang;
//公共语言包(同步加载)
var lang = loadLangPackSync(locale, 'common');
for (let f in currentPageLang) {
lang[f] = currentPageLang[f];
}
//当前页面语言包(同步加载)
let route = _page.route;
langPath = processRoutePath(route);
// 加载当前页面语言包
let currentPageLang = loadLangPackSync(locale, langPath);
// 合并语言包
let mergedLang = { ...lang, ...currentPageLang };
// 解析字段
var arr = field.split(".");
if (arr.length > 1) {
for (let i in arr) {
var next = parseInt(i) + 1;
if (next < arr.length) {
value = lang[arr[i]][arr[next]];
// 处理嵌套属性,如 common.currencySymbol
let temp = mergedLang;
let found = true;
for (let key of arr) {
if (temp[key] !== undefined) {
temp = temp[key];
} else {
found = false;
break;
}
}
value = found ? temp : field;
} else {
value = lang[field];
value = mergedLang[field] !== undefined ? mergedLang[field] : field;
}
} catch (e) {
if (field.indexOf("common.") != -1 || field.indexOf("tabBar.") != -1) {
value = lang[field];
} else {
value = field;
}
console.error('解析语言包失败:', e, { langPath, field, locale });
value = field;
}
if (arguments.length > 1) {
@@ -56,28 +129,46 @@ export default {
}
}
if (value == undefined || (value == 'title' && field == 'title')) value = ''; // field
// 多语言调试,注释后可以关闭控制台输出
if (field == value) {
console.warn(`警告: 字段 ${field} 在语言包 ${langPath} 中未找到对应值,使用默认值 ${field} 当前语言: ${locale}`);
}
return value;
},
//切换语言
change(value) {
let _this = getCurrentPages()[getCurrentPages().length - 1];
if (!_this) return;
/**
* * 切换语言
* @param {String} value 语言值
* @param {String} url 切换后跳转的页面url
*/
change(value, url = '/pages_tool/member/index') {
let _page = getCurrentPages()[getCurrentPages().length - 1];
if (!_page) return;
uni.setStorageSync("lang", value);
locale = uni.getStorageSync('lang') || "zh-cn"; //设置语言
const locale = getCurrentLocale();
// 清空已加载的语言包缓存
for (let key in loadedLangPacks) {
if (!key.startsWith(locale)) {
delete loadedLangPacks[key];
}
}
this.refresh();
uni.reLaunch({
url: '/pages/member/index'
});
if (url) {
uni.reLaunch({ url: url });
}
},
//刷新标题、tabbar
refresh() {
let _this = getCurrentPages()[getCurrentPages().length - 1];
if (!_this) return;
locale = uni.getStorageSync('lang') || "zh-cn"; //设置语言
let _page = getCurrentPages()[getCurrentPages().length - 1];
if (!_page) return;
const locale = getCurrentLocale();
this.title(this.lang("title"));
//设置tabbar的文字语言
@@ -114,15 +205,16 @@ export default {
var list = [];
try {
//公共语言包
for (var i = 0; i < langList.length; i++) {
let item = require('../../lang/' + langList[i] + '/common.js').lang;
for (var i = 0; i < langConfig.langList.length; i++) {
let langType = langConfig.langList[i];
let item = loadLangPackSync(langType, 'common');
list.push({
name: item.common.name,
value: langList[i]
name: item.common ? item.common.name : langType,
value: langType
});
}
} catch (e) {
// "没有找到语言包:", '../../lang/' + locale + '/common.js'
console.error('获取语言包列表失败:', e);
}
return list;
}

View File

@@ -1,3 +1,4 @@
import util from '@/common/js/util.js'
import TransformCoordinate from './transformCoordinate.js'
function openMapByDefault(latitude, longitude, name) {
@@ -85,7 +86,8 @@ export default {
openMap(latitude, longitude, name, coord_type = 'gcj02') {
let arr = getCoordByType(longitude, latitude, coord_type)
// #ifdef APP-PLUS
switch (uni.getSystemInfoSync().platform) {
let platform = util.getDeviceInfo().platform;
switch (platform) {
case 'android':
console.log('运行Android上')
openMapByAndroid(arr[1], arr[0], name)

View File

@@ -1,4 +1,5 @@
import { EventSafety } from './event-safety'
import { EventSafety } from '@/common/js/event-safety.js'
import util from '@/common/js/util.js'
export class NavigationHelper {
constructor() {
@@ -59,7 +60,7 @@ export class NavigationHelper {
// 微信小程序精确计算
try {
const menuButtonInfo = wx.getMenuButtonBoundingClientRect()
const systemInfo = uni.getSystemInfoSync()
let systemInfo = util.getDeviceInfo();
const height = menuButtonInfo.bottom +
(menuButtonInfo.top - systemInfo.statusBarHeight)
@@ -119,7 +120,7 @@ export class NavigationHelper {
// 获取状态栏高度
getStatusBarHeight() {
// #ifdef MP-WEIXIN
const systemInfo = uni.getSystemInfoSync()
let systemInfo = util.getDeviceInfo();
return systemInfo.statusBarHeight || 20
// #endif
// #ifdef H5
@@ -138,7 +139,7 @@ export class NavigationHelper {
// 获取安全区域
getSafeAreaInsets() {
try {
const systemInfo = uni.getSystemInfoSync()
let systemInfo = util.getDeviceInfo();
return systemInfo.safeArea || {
top: 0,
bottom: 0,

100
common/js/share.js Normal file
View File

@@ -0,0 +1,100 @@
// 文件分享工具函数
export default {
/**
* 生成文件预览链接
* @param {Object} file - 文件对象,包含 name 和 url 属性
* @returns {string} - 生成的文件预览链接
*/
generateFilePreviewUrl(file) {
// 推断文件类型
let fileType = '';
if (file.name) {
const ext = file.name.split('.').pop().toLowerCase();
if (['pdf'].includes(ext)) {
fileType = 'pdf';
} else if (['doc', 'docx'].includes(ext)) {
fileType = 'word';
} else if (['mp4', 'avi', 'mov', 'wmv', 'flv', 'mkv'].includes(ext)) {
fileType = 'video';
}
}
// 构建文件预览链接
const previewUrl = `/pages_tool/file-preview/file-preview?fileName=${encodeURIComponent(file.name)}&fileUrl=${encodeURIComponent(file.url || '')}&fileType=${encodeURIComponent(fileType)}`;
return previewUrl;
},
/**
* 分享文件
* @param {Object} file - 文件对象,包含 name 和 url 属性
* @param {string} title - 分享标题,默认使用文件名
* @param {string} desc - 分享描述,默认使用固定文案
*/
shareFile(file, title = file.name, desc = '查看企业文件:' + file.name) {
// 生成文件预览链接
const filePreviewUrl = this.generateFilePreviewUrl(file);
// #ifdef H5
// H5 平台分享
if (navigator.share) {
// 使用 Web Share API
navigator.share({
title: title,
text: desc,
url: filePreviewUrl
}).catch(err => {
console.error('分享失败:', err);
uni.showToast({ title: '分享失败', icon: 'none' });
});
} else {
// 不支持 Web Share API 的浏览器
uni.setClipboardData({
data: filePreviewUrl,
success: () => {
uni.showToast({ title: '链接已复制,请粘贴分享', icon: 'success' });
},
fail: () => {
uni.showToast({ title: '复制失败', icon: 'none' });
}
});
}
// #endif
// #ifdef MP-WEIXIN
// 微信小程序分享
uni.showActionSheet({
itemList: ['发送给朋友', '分享到朋友圈'],
success: (res) => {
if (res.tapIndex === 0) {
// 发送给朋友
uni.shareAppMessage({
title: title,
path: filePreviewUrl,
success: () => {
uni.showToast({ title: '分享成功', icon: 'success' });
},
fail: () => {
uni.showToast({ title: '分享失败', icon: 'none' });
}
});
} else if (res.tapIndex === 1) {
// 分享到朋友圈
uni.shareTimeline({
title: desc,
path: filePreviewUrl,
success: () => {
uni.showToast({ title: '分享成功', icon: 'success' });
},
fail: () => {
uni.showToast({ title: '分享失败', icon: 'none' });
}
});
}
},
fail: () => {
// 用户取消分享
}
});
// #endif
}
};

View File

@@ -1,419 +1,460 @@
export default {
'default': {
//红
name: 'default',
main_color: '#F4391c',
aux_color: '#F7B500',
bg_color: '#FF4646',//主题背景
bg_color_shallow: '#FF4646',//主题背景渐变浅色
promotion_color: '#FF4646',//活动背景
promotion_aux_color: '#F7B500',//活动背景辅色
main_color_shallow: '#FFF4F4',//淡背景
price_color: 'rgb(252,82,39)',//价格颜
btn_text_color: '#FFFFFF',//按钮文字颜色
goods_detail: {
goods_price: 'rgb(252,82,39,1)',//价格
promotion_tag: '#FF4646',
goods_card_bg: '#201A18',//会员卡背景
goods_card_bg_shallow: '#7C7878',//会员卡背景浅色
goods_card_color: '#FFD792',
goods_coupon: '#FC5227',
goods_cart_num_corner: '#FC5227',//购物车数量角标
goods_btn_color: '#FF4646',//按钮颜色
goods_btn_color_shallow: '#F7B500',//副按钮颜色
},
pintuan: {
pintuan_label_bg: '#F7B500',
pintuan_label_color: '#FFFFFF',
pintuan_color: '#FA6400',
pintuan_promotion_color: '#FA3A1D',//活动背景
pintuan_promotion_aux_color: '#FD9A01',//活动背景辅色
},
super_member: {
super_member_start_bg: '#7c7878',
super_member_end_bg: '#201a18',
super_member_start_text_color: '#FFDBA6',
super_member_end_text_color: '#FFEBCA',
},
bargain: {
bargain_promotion_color: '#F0353E',//活动背景
bargain_promotion_aux_color: '#FD9A01',//活动辅色
},
seckill: {
seckill_promotion_color: '#F83530',//活动背景
seckill_promotion_aux_color: '#FD9A01',//活动辅色
},
giftcard: {
giftcard_promotion_color: '#FF3369',//活动背景
giftcard_promotion_aux_color: '#F7B500',//活动辅色
},
groupby: {
groupby_promotion_color: '#E64136',//活动背景
groupby_promotion_aux_color: '#F7B500',//活动辅色
},
},
'green': {
name: 'green',
main_color: '#19C650',
aux_color: '#FA6400',
bg_color: '#19C650',
bg_color_shallow: '#19C650',
promotion_color: '#19C650',
promotion_aux_color: '#FA6400',
main_color_shallow: '#F0FFF5',//淡背景
price_color: 'rgba(252,82,39,1)',//价格颜色
btn_text_color: '#FFFFFF',//按钮文字颜色
goods_detail: {
goods_price: 'rgba(252,82,39,1)',//价格
promotion_tag: '#19C650',
goods_card_bg: '#201A18',//会员卡背景
goods_card_bg_shallow: '#7C7878',//会员卡背景浅色
goods_card_color: '#FFD792',
goods_coupon: '#FC5227',
goods_cart_num_corner: '#FC5227',//购物车数量角标
goods_btn_color: '#19C650',//按钮颜色
goods_btn_color_shallow: '#FA6400',//副按钮颜色
},
pintuan: {
pintuan_label_bg: '#F7B500',
pintuan_label_color: '#FFFFFF',
pintuan_color: '#FA6400',
pintuan_promotion_color: '#FA3A1D',//活动背景
pintuan_promotion_aux_color: '#FD9A01',//活动背景辅色
},
super_member: {
super_member_start_bg: '#7c7878',
super_member_end_bg: '#201a18',
super_member_start_text_color: '#FFDBA6',
super_member_end_text_color: '#FFEBCA',
},
bargain: {
bargain_promotion_color: '#F0353E',//活动背景
bargain_promotion_aux_color: '#FD9A01',//活动辅色
},
seckill: {
seckill_promotion_color: '#F83530',//活动背景
seckill_promotion_aux_color: '#FD9A01',//活动辅色
},
giftcard: {
giftcard_promotion_color: '#FF3369',//活动背景
giftcard_promotion_aux_color: '#F7B500',//活动辅色
},
groupby: {
groupby_promotion_color: '#E64136',//活动背景
groupby_promotion_aux_color: '#F7B500',//活动辅色
},
},
'blue': {
name: 'blue',
main_color: '#36ABFF',
aux_color: '#FA6400',
bg_color: '#36ABFF',
bg_color_shallow: '#36ABFF',
promotion_color: '#36ABFF ',
promotion_aux_color: '#FA6400',
main_color_shallow: '#E2F3FF',
price_color: 'rgba(252,82,39,1)',//价格颜色
btn_text_color: '#FFFFFF',//按钮文字颜
goods_detail: {
goods_price: 'rgba(252,82,39,1)',//价格
promotion_tag: '#36ABFF',
goods_card_bg: '#201A18',//会员卡背景
goods_card_bg_shallow: '#7C7878',//会员卡背景浅色
goods_card_color: '#FFD792',
goods_coupon: '#FC5227',
goods_cart_num_corner: '#FC5227',//购物车数量角标
goods_btn_color: '#36ABFF',//按钮颜色
goods_btn_color_shallow: '#FA6400',//副按钮颜色
},
pintuan: {
pintuan_label_bg: '#F7B500',
pintuan_label_color: '#FFFFFF',
pintuan_color: '#FA6400',
pintuan_promotion_color: '#FA3A1D',//活动背景
pintuan_promotion_aux_color: '#FD9A01',//活动背景辅
},
super_member: {
super_member_start_bg: '#7c7878',
super_member_end_bg: '#201a18',
super_member_start_text_color: '#FFDBA6',
super_member_end_text_color: '#FFEBCA',
},
bargain: {
bargain_promotion_color: '#F0353E',//活动背景
bargain_promotion_aux_color: '#FD9A01',//活动辅
},
seckill: {
seckill_promotion_color: '#F83530',//活动背景
seckill_promotion_aux_color: '#FD9A01',//活动辅色
},
giftcard: {
giftcard_promotion_color: '#FF3369',//活动背景
giftcard_promotion_aux_color: '#F7B500',//活动辅色
},
groupby: {
groupby_promotion_color: '#E64136',//活动背景
groupby_promotion_aux_color: '#F7B500',//活动辅色
},
},
'pink': {
name: 'pink',
main_color: '#FF407E',
aux_color: '#F7B500',
bg_color: '#FF407E',//主题背景
bg_color_shallow: '#FF407E',//主题背景渐变浅色
promotion_color: '#FF407E',//活动背景
promotion_aux_color: '#F7B500',//活动背景辅色
main_color_shallow: '#FFF5F8',//淡背景
price_color: 'rgba(252,82,39,1)',//价格颜色
btn_text_color: '#FFFFFF',//按钮文字颜
goods_detail: {
goods_price: 'rgba(252,82,39,1)',//价格
promotion_tag: '#FF407E',
goods_card_bg: '#201A18',//会员卡背景
goods_card_bg_shallow: '#7C7878',//会员卡背景浅色
goods_card_color: '#FFD792',
goods_coupon: '#FC5227',
goods_cart_num_corner: '#FC5227',//购物车数量角标
goods_btn_color: '#FF407E',//按钮颜色
goods_btn_color_shallow: '#F7B500',//副按钮颜色
},
pintuan: {
pintuan_label_bg: '#F7B500',
pintuan_label_color: '#FFFFFF',
pintuan_color: '#FA6400',
pintuan_promotion_color: '#FA3A1D',//活动背景
pintuan_promotion_aux_color: '#FD9A01',//活动背景辅色
},
super_member: {
super_member_start_bg: '#7c7878',
super_member_end_bg: '#201a18',
super_member_start_text_color: '#FFDBA6',
super_member_end_text_color: '#FFEBCA',
},
bargain: {
bargain_promotion_color: '#F0353E',//活动背景
bargain_promotion_aux_color: '#FD9A01',//活动辅色
},
seckill: {
seckill_promotion_color: '#F83530',//活动背景
seckill_promotion_aux_color: '#FD9A01',//活动辅
},
giftcard: {
giftcard_promotion_color: '#FF3369',//活动背景
giftcard_promotion_aux_color: '#F7B500',//活动辅色
},
groupby: {
groupby_promotion_color: '#E64136',//活动背景
groupby_promotion_aux_color: '#F7B500',//活动辅色
},
},
'gold': {
name: 'gold',
main_color: '#CFAF70',
aux_color: '#444444',
bg_color: '#CFAF70',//主题背景
bg_color_shallow: '#CFAF70',//主题背景渐变浅色
promotion_color: '#CFAF70',//活动背景
promotion_aux_color: '#444444',//活动背景辅色
main_color_shallow: '#FFFAF1',//淡背景
price_color: 'rgba(252,82,39,1)',//价格颜色
btn_text_color: '#FFFFFF',//按钮文字颜
goods_detail: {
goods_price: 'rgba(252,82,39,1)',//价格
promotion_tag: '#CFAF70',
goods_card_bg: '#201A18',//会员卡背景
goods_card_bg_shallow: '#7C7878',//会员卡背景浅色
goods_card_color: '#FFD792',
goods_coupon: '#FC5227',
goods_cart_num_corner: '#FC5227',//购物车数量角标
goods_btn_color: '#CFAF70',//按钮颜色
goods_btn_color_shallow: '#444444',//副按钮颜色
},
pintuan: {
pintuan_label_bg: '#F7B500',
pintuan_label_color: '#FFFFFF',
pintuan_color: '#FA6400',
pintuan_promotion_color: '#FA3A1D',//活动背景
pintuan_promotion_aux_color: '#FD9A01',//活动背景辅色
},
super_member: {
super_member_start_bg: '#7c7878',
super_member_end_bg: '#201a18',
super_member_start_text_color: '#FFDBA6',
super_member_end_text_color: '#FFEBCA',
},
bargain: {
bargain_promotion_color: '#F0353E',//活动背景
bargain_promotion_aux_color: '#FD9A01',//活动辅色
},
seckill: {
seckill_promotion_color: '#F83530',//活动背景
seckill_promotion_aux_color: '#FD9A01',//活动辅色
},
giftcard: {
giftcard_promotion_color: '#FF3369',//活动背景
giftcard_promotion_aux_color: '#F7B500',//活动辅
},
groupby: {
groupby_promotion_color: '#E64136',//活动背景
groupby_promotion_aux_color: '#F7B500',//活动辅色
},
},
'purple': {
name: 'purple',
main_color: '#A253FF',
aux_color: '#222222',
bg_color: '#A253FF',//主题背景
bg_color_shallow: '#A253FF',//主题背景渐变浅色
promotion_color: '#A253FF',//活动背景
promotion_aux_color: '#222222',//活动背景辅色
main_color_shallow: '#F8F3FF',//淡背景
price_color: 'rgba(252,82,39,1)',//价格颜色
btn_text_color: '#FFFFFF',//按钮文字颜
goods_detail: {
goods_price: 'rgba(252,82,39,1)',//价格
promotion_tag: '#A253FF',
goods_card_bg: '#201A18',//会员卡背景
goods_card_bg_shallow: '#7C7878',//会员卡背景浅色
goods_card_color: '#FFD792',
goods_coupon: '#FC5227',
goods_cart_num_corner: '#FC5227',//购物车数量角标
goods_btn_color: '#A253FF',//按钮颜色
goods_btn_color_shallow: '#222222',//副按钮颜色
},
pintuan: {
pintuan_label_bg: '#F7B500',
pintuan_label_color: '#FFFFFF',
pintuan_color: '#FA6400',
pintuan_promotion_color: '#FA3A1D',//活动背景
pintuan_promotion_aux_color: '#FD9A01',//活动背景辅色
},
super_member: {
super_member_start_bg: '#7c7878',
super_member_end_bg: '#201a18',
super_member_start_text_color: '#FFDBA6',
super_member_end_text_color: '#FFEBCA',
},
bargain: {
bargain_promotion_color: '#F0353E',//活动背景
bargain_promotion_aux_color: '#FD9A01',//活动辅色
},
seckill: {
seckill_promotion_color: '#F83530',//活动背景
seckill_promotion_aux_color: '#FD9A01',//活动辅色
},
giftcard: {
giftcard_promotion_color: '#FF3369',//活动背景
giftcard_promotion_aux_color: '#F7B500',//活动辅色
},
groupby: {
groupby_promotion_color: '#E64136',//活动背景
groupby_promotion_aux_color: '#F7B500',//活动辅
},
},
'yellow': {
name: 'yellow',
main_color: '#FFD009',
aux_color: '#1D262E',
bg_color: '#FFD009',//主题背景
bg_color_shallow: '#FFD009',//主题背景渐变浅色
promotion_color: '#FFD009',//活动背景
promotion_aux_color: '#1D262E',//活动背景辅色
main_color_shallow: '#FFFBEF',//淡背景
price_color: 'rgba(252,82,39,1)',//价格颜色
btn_text_color: '#303133',//按钮文字颜色
goods_detail: {
goods_price: 'rgba(252,82,39,1)',//价格
promotion_tag: '#FFD009',
goods_card_bg: '#201A18',//会员卡背景
goods_card_bg_shallow: '#7C7878',//会员卡背景浅色
goods_card_color: '#FFD792',
goods_coupon: '#FC5227',
goods_cart_num_corner: '#FC5227',//购物车数量角标
goods_btn_color: '#FFD009',//按钮颜色
goods_btn_color_shallow: '#1D262E',//副按钮颜色
},
pintuan: {
pintuan_label_bg: '#F7B500',
pintuan_label_color: '#FFFFFF',
pintuan_color: '#FA6400',
pintuan_promotion_color: '#FA3A1D',//活动背景
pintuan_promotion_aux_color: '#FD9A01',//活动背景辅色
},
super_member: {
super_member_start_bg: '#7c7878',
super_member_end_bg: '#201a18',
super_member_start_text_color: '#FFDBA6',
super_member_end_text_color: '#FFEBCA',
},
bargain: {
bargain_promotion_color: '#F0353E',//活动背景
bargain_promotion_aux_color: '#FD9A01',//活动辅色
},
seckill: {
seckill_promotion_color: '#F83530',//活动背景
seckill_promotion_aux_color: '#FD9A01',//活动辅色
},
giftcard: {
giftcard_promotion_color: '#FF3369',//活动背景
giftcard_promotion_aux_color: '#F7B500',//活动辅色
},
groupby: {
groupby_promotion_color: '#E64136',//活动背景
groupby_promotion_aux_color: '#F7B500',//活动辅色
},
},
'black': {
name: 'black',
main_color: '#222222',
aux_color: '#FFFFFF',
bg_color: '#222222',//主题背景
bg_color_shallow: '#333333',//主题背景渐变浅色
promotion_color: '#222222',//活动背景
promotion_aux_color: '#FA8B00',//活动背景辅色
main_color_shallow: '#efefef',//淡背景
price_color: 'rgba(255,0,0,1)',//价格颜色
btn_text_color: '#FFFFFF',//按钮文字颜色
goods_detail: {
goods_price: 'rgba(255,0,0,1)',//价格
promotion_tag: '#222222',
goods_card_bg: '#201A18',//会员卡背景
goods_card_bg_shallow: '#7C7878',//会员卡背景浅色
goods_card_color: '#FFD792',
goods_coupon: '#222222',
goods_cart_num_corner: '#FF0000',//购物车数量角标
goods_btn_color: '#222222',//按钮颜色
goods_btn_color_shallow: '#FA8B00',//副按钮颜色
},
pintuan: {
pintuan_label_bg: '#F7B500',
pintuan_label_color: '#FFFFFF',
pintuan_color: '#FA6400',
pintuan_promotion_color: '#FA3A1D',//活动背景
pintuan_promotion_aux_color: '#FD9A01',//活动背景辅色
},
super_member: {
super_member_start_bg: '#fadcb5',
super_member_end_bg: '#f6bd74',
super_member_start_text_color: '#ab6126',
super_member_end_text_color: '#d19336',
},
bargain: {
bargain_promotion_color: '#F0353E',//活动背景
bargain_promotion_aux_color: '#FD9A01',//活动辅色
},
seckill: {
seckill_promotion_color: '#F83530',//活动背景
seckill_promotion_aux_color: '#FD9A01',//活动辅色
},
giftcard: {
giftcard_promotion_color: '#FF3369',//活动背景
giftcard_promotion_aux_color: '#F7B500',//活动辅色
},
groupby: {
groupby_promotion_color: '#E64136',//活动背景
groupby_promotion_aux_color: '#F7B500',//活动辅色
},
}
}
/**
* 颜色配置
* 包含默认颜色和其他颜
* 特别注意:
* 1. 分组不会被计算成Key分组下的属性会被计算成Key
* 2. 分组下的属性如果有相同的Key会被覆盖
* 3. 例如hoverNavhoverNav_bg_color和hoverNav_text_color, 不会生成hoverNav_hoverNav_bg_color和hoverNav_hoverNav_text_color
* 问题原因:历史遗留
*/
export default {
'default': {
//红
name: 'default',
main_color: '#F4391c',
aux_color: '#F7B500',
bg_color: '#FF4646',//主题背景
bg_color_shallow: '#FF4646',//主题背景渐变浅色
promotion_color: '#FF4646',//活动背景
promotion_aux_color: '#F7B500',//活动背景辅色
main_color_shallow: '#FFF4F4',//淡背景
price_color: 'rgb(252,82,39)',//价格颜色
btn_text_color: '#FFFFFF',//按钮文字颜色
...{
goods_price: 'rgb(252,82,39,1)',//价格
promotion_tag: '#FF4646',
goods_card_bg: '#201A18',//会员卡背景
goods_card_bg_shallow: '#7C7878',//会员卡背景浅色
goods_card_color: '#FFD792',
goods_coupon: '#FC5227',
goods_cart_num_corner: '#FC5227',//购物车数量角标
goods_btn_color: '#FF4646',//按钮颜色
goods_btn_color_shallow: '#F7B500',//副按钮颜色
},
...{
pintuan_label_bg: '#F7B500',
pintuan_label_color: '#FFFFFF',
pintuan_color: '#FA6400',
pintuan_promotion_color: '#FA3A1D',//活动背景
pintuan_promotion_aux_color: '#FD9A01',//活动背景辅色
},
...{
super_member_start_bg: '#7c7878',
super_member_end_bg: '#201a18',
super_member_start_text_color: '#FFDBA6',
super_member_end_text_color: '#FFEBCA',
},
...{
bargain_promotion_color: '#F0353E',//活动背景
bargain_promotion_aux_color: '#FD9A01',//活动辅色
},
...{
seckill_promotion_color: '#F83530',//活动背景
seckill_promotion_aux_color: '#FD9A01',//活动辅色
},
...{
giftcard_promotion_color: '#FF3369',//活动背景
giftcard_promotion_aux_color: '#F7B500',//活动辅色
},
...{
groupby_promotion_color: '#E64136',//活动背景
groupby_promotion_aux_color: '#F7B500',//活动辅色
},
...{
hover_nav_bg_color: '#FFFC', //背景色: 红色 '#c6251b', 白色: '#FFFC'
hover_nav_text_color: '#000' // 文字颜色: 白色 '#FFFFFF', 黑色: '#000'
},
},
'green': {
name: 'green',
main_color: '#19C650',
aux_color: '#FA6400',
bg_color: '#19C650',
bg_color_shallow: '#19C650',
promotion_color: '#19C650',
promotion_aux_color: '#FA6400',
main_color_shallow: '#F0FFF5',//淡背景
price_color: 'rgba(252,82,39,1)',//价格颜色
btn_text_color: '#FFFFFF',//按钮文字颜色
...{
goods_price: 'rgba(252,82,39,1)',//价格
promotion_tag: '#19C650',
goods_card_bg: '#201A18',//会员卡背景
goods_card_bg_shallow: '#7C7878',//会员卡背景浅色
goods_card_color: '#FFD792',
goods_coupon: '#FC5227',
goods_cart_num_corner: '#FC5227',//购物车数量角标
goods_btn_color: '#19C650',//按钮颜色
goods_btn_color_shallow: '#FA6400',//副按钮颜色
},
...{
pintuan_label_bg: '#F7B500',
pintuan_label_color: '#FFFFFF',
pintuan_color: '#FA6400',
pintuan_promotion_color: '#FA3A1D',//活动背景
pintuan_promotion_aux_color: '#FD9A01',//活动背景辅色
},
...{
super_member_start_bg: '#7c7878',
super_member_end_bg: '#201a18',
super_member_start_text_color: '#FFDBA6',
super_member_end_text_color: '#FFEBCA',
},
...{
bargain_promotion_color: '#F0353E',//活动背景
bargain_promotion_aux_color: '#FD9A01',//活动辅色
},
...{
seckill_promotion_color: '#F83530',//活动背景
seckill_promotion_aux_color: '#FD9A01',//活动辅色
},
...{
giftcard_promotion_color: '#FF3369',//活动背景
giftcard_promotion_aux_color: '#F7B500',//活动辅色
},
...{
groupby_promotion_color: '#E64136',//活动背景
groupby_promotion_aux_color: '#F7B500',//活动辅
},
...{
hover_nav_bg_color: '#19C650',//背景色
hover_nav_text_color: '#FFFFFF'
},
},
'blue': {
name: 'blue',
main_color: '#36ABFF',
aux_color: '#FA6400',
bg_color: '#36ABFF',
bg_color_shallow: '#36ABFF',
promotion_color: '#36ABFF ',
promotion_aux_color: '#FA6400',
main_color_shallow: '#E2F3FF',
price_color: 'rgba(252,82,39,1)',//价格颜色
btn_text_color: '#FFFFFF',//按钮文字颜
...{
goods_price: 'rgba(252,82,39,1)',//价格
promotion_tag: '#36ABFF',
goods_card_bg: '#201A18',//会员卡背景
goods_card_bg_shallow: '#7C7878',//会员卡背景浅色
goods_card_color: '#FFD792',
goods_coupon: '#FC5227',
goods_cart_num_corner: '#FC5227',//购物车数量角标
goods_btn_color: '#36ABFF',//按钮颜色
goods_btn_color_shallow: '#FA6400',//副按钮颜
},
...{
pintuan_label_bg: '#F7B500',
pintuan_label_color: '#FFFFFF',
pintuan_color: '#FA6400',
pintuan_promotion_color: '#FA3A1D',//活动背景
pintuan_promotion_aux_color: '#FD9A01',//活动背景辅色
},
...{
super_member_start_bg: '#7c7878',
super_member_end_bg: '#201a18',
super_member_start_text_color: '#FFDBA6',
super_member_end_text_color: '#FFEBCA',
},
...{
bargain_promotion_color: '#F0353E',//活动背景
bargain_promotion_aux_color: '#FD9A01',//活动辅色
},
...{
seckill_promotion_color: '#F83530',//活动背景
seckill_promotion_aux_color: '#FD9A01',//活动辅色
},
...{
giftcard_promotion_color: '#FF3369',//活动背景
giftcard_promotion_aux_color: '#F7B500',//活动辅
},
...{
groupby_promotion_color: '#E64136',//活动背景
groupby_promotion_aux_color: '#F7B500',//活动辅色
},
...{
hover_nav_bg_color: '#36ABFF',//背景色
hover_nav_text_color: '#FFFFFF'
},
},
'pink': {
name: 'pink',
main_color: '#FF407E',
aux_color: '#F7B500',
bg_color: '#FF407E',//主题背景
bg_color_shallow: '#FF407E',//主题背景渐变浅色
promotion_color: '#FF407E',//活动背景
promotion_aux_color: '#F7B500',//活动背景辅色
main_color_shallow: '#FFF5F8',//淡背景
price_color: 'rgba(252,82,39,1)',//价格颜色
btn_text_color: '#FFFFFF',//按钮文字颜色
...{
goods_price: 'rgba(252,82,39,1)',//价格
promotion_tag: '#FF407E',
goods_card_bg: '#201A18',//会员卡背景
goods_card_bg_shallow: '#7C7878',//会员卡背景浅色
goods_card_color: '#FFD792',
goods_coupon: '#FC5227',
goods_cart_num_corner: '#FC5227',//购物车数量角标
goods_btn_color: '#FF407E',//按钮颜色
goods_btn_color_shallow: '#F7B500',//副按钮颜
},
...{
pintuan_label_bg: '#F7B500',
pintuan_label_color: '#FFFFFF',
pintuan_color: '#FA6400',
pintuan_promotion_color: '#FA3A1D',//活动背景
pintuan_promotion_aux_color: '#FD9A01',//活动背景辅色
},
...{
super_member_start_bg: '#7c7878',
super_member_end_bg: '#201a18',
super_member_start_text_color: '#FFDBA6',
super_member_end_text_color: '#FFEBCA',
},
...{
bargain_promotion_color: '#F0353E',//活动背景
bargain_promotion_aux_color: '#FD9A01',//活动辅色
},
...{
seckill_promotion_color: '#F83530',//活动背景
seckill_promotion_aux_color: '#FD9A01',//活动辅
},
...{
giftcard_promotion_color: '#FF3369',//活动背景
giftcard_promotion_aux_color: '#F7B500',//活动辅色
},
...{
groupby_promotion_color: '#E64136',//活动背景
groupby_promotion_aux_color: '#F7B500',//活动辅色
},
...{
hover_nav_bg_color: '#FF407E',//背景色
hover_nav_text_color: '#FFFFFF'
},
},
'gold': {
name: 'gold',
main_color: '#CFAF70',
aux_color: '#444444',
bg_color: '#CFAF70',//主题背景
bg_color_shallow: '#CFAF70',//主题背景渐变浅色
promotion_color: '#CFAF70',//活动背景
promotion_aux_color: '#444444',//活动背景辅色
main_color_shallow: '#FFFAF1',//淡背景
price_color: 'rgba(252,82,39,1)',//价格颜色
btn_text_color: '#FFFFFF',//按钮文字颜色
...{
goods_price: 'rgba(252,82,39,1)',//价格
promotion_tag: '#CFAF70',
goods_card_bg: '#201A18',//会员卡背景
goods_card_bg_shallow: '#7C7878',//会员卡背景浅色
goods_card_color: '#FFD792',
goods_coupon: '#FC5227',
goods_cart_num_corner: '#FC5227',//购物车数量角标
goods_btn_color: '#CFAF70',//按钮颜色
goods_btn_color_shallow: '#444444',//副按钮颜
},
...{
pintuan_label_bg: '#F7B500',
pintuan_label_color: '#FFFFFF',
pintuan_color: '#FA6400',
pintuan_promotion_color: '#FA3A1D',//活动背景
pintuan_promotion_aux_color: '#FD9A01',//活动背景辅色
},
...{
super_member_start_bg: '#7c7878',
super_member_end_bg: '#201a18',
super_member_start_text_color: '#FFDBA6',
super_member_end_text_color: '#FFEBCA',
},
...{
bargain_promotion_color: '#F0353E',//活动背景
bargain_promotion_aux_color: '#FD9A01',//活动辅
},
...{
seckill_promotion_color: '#F83530',//活动背景
seckill_promotion_aux_color: '#FD9A01',//活动辅色
},
...{
giftcard_promotion_color: '#FF3369',//活动背景
giftcard_promotion_aux_color: '#F7B500',//活动辅色
},
...{
groupby_promotion_color: '#E64136',//活动背景
groupby_promotion_aux_color: '#F7B500',//活动辅色
},
...{
hover_nav_bg_color: '#CFAF70',//背景色
hover_nav_text_color: '#FFFFFF'
},
},
'purple': {
name: 'purple',
main_color: '#A253FF',
aux_color: '#222222',
bg_color: '#A253FF',//主题背景
bg_color_shallow: '#A253FF',//主题背景渐变浅色
promotion_color: '#A253FF',//活动背景
promotion_aux_color: '#222222',//活动背景辅色
main_color_shallow: '#F8F3FF',//淡背景
price_color: 'rgba(252,82,39,1)',//价格颜色
btn_text_color: '#FFFFFF',//按钮文字颜色
...{
goods_price: 'rgba(252,82,39,1)',//价格
promotion_tag: '#A253FF',
goods_card_bg: '#201A18',//会员卡背景
goods_card_bg_shallow: '#7C7878',//会员卡背景浅色
goods_card_color: '#FFD792',
goods_coupon: '#FC5227',
goods_cart_num_corner: '#FC5227',//购物车数量角标
goods_btn_color: '#A253FF',//按钮颜色
goods_btn_color_shallow: '#222222',//副按钮颜
},
...{
pintuan_label_bg: '#F7B500',
pintuan_label_color: '#FFFFFF',
pintuan_color: '#FA6400',
pintuan_promotion_color: '#FA3A1D',//活动背景
pintuan_promotion_aux_color: '#FD9A01',//活动背景辅色
},
...{
super_member_start_bg: '#7c7878',
super_member_end_bg: '#201a18',
super_member_start_text_color: '#FFDBA6',
super_member_end_text_color: '#FFEBCA',
},
...{
bargain_promotion_color: '#F0353E',//活动背景
bargain_promotion_aux_color: '#FD9A01',//活动辅色
},
...{
seckill_promotion_color: '#F83530',//活动背景
seckill_promotion_aux_color: '#FD9A01',//活动辅色
},
...{
giftcard_promotion_color: '#FF3369',//活动背景
giftcard_promotion_aux_color: '#F7B500',//活动辅色
},
...{
groupby_promotion_color: '#E64136',//活动背景
groupby_promotion_aux_color: '#F7B500',//活动辅色
},
...{
hover_nav_bg_color: '#A253FF',//背景色
hover_nav_text_color: '#FFFFFF'
},
},
'yellow': {
name: 'yellow',
main_color: '#FFD009',
aux_color: '#1D262E',
bg_color: '#FFD009',//主题背景
bg_color_shallow: '#FFD009',//主题背景渐变浅色
promotion_color: '#FFD009',//活动背景
promotion_aux_color: '#1D262E',//活动背景辅色
main_color_shallow: '#FFFBEF',//淡背景
price_color: 'rgba(252,82,39,1)',//价格颜色
btn_text_color: '#303133',//按钮文字颜色
...{
goods_price: 'rgba(252,82,39,1)',//价格
promotion_tag: '#FFD009',
goods_card_bg: '#201A18',//会员卡背景
goods_card_bg_shallow: '#7C7878',//会员卡背景浅色
goods_card_color: '#FFD792',
goods_coupon: '#FC5227',
goods_cart_num_corner: '#FC5227',//购物车数量角标
goods_btn_color: '#FFD009',//按钮颜色
goods_btn_color_shallow: '#1D262E',//副按钮颜色
},
...{
pintuan_label_bg: '#F7B500',
pintuan_label_color: '#FFFFFF',
pintuan_color: '#FA6400',
pintuan_promotion_color: '#FA3A1D',//活动背景
pintuan_promotion_aux_color: '#FD9A01',//活动背景辅色
},
...{
super_member_start_bg: '#7c7878',
super_member_end_bg: '#201a18',
super_member_start_text_color: '#FFDBA6',
super_member_end_text_color: '#FFEBCA',
},
...{
bargain_promotion_color: '#F0353E',//活动背景
bargain_promotion_aux_color: '#FD9A01',//活动辅色
},
...{
seckill_promotion_color: '#F83530',//活动背景
seckill_promotion_aux_color: '#FD9A01',//活动辅色
},
...{
giftcard_promotion_color: '#FF3369',//活动背景
giftcard_promotion_aux_color: '#F7B500',//活动辅色
},
...{
groupby_promotion_color: '#E64136',//活动背景
groupby_promotion_aux_color: '#F7B500',//活动辅色
},
...{
hover_nav_bg_color: '#FFD009',//背景色
hover_nav_text_color: '#303133'
},
},
'black': {
name: 'black',
main_color: '#222222',
aux_color: '#FFFFFF',
bg_color: '#222222',//主题背景
bg_color_shallow: '#333333',//主题背景渐变浅色
promotion_color: '#222222',//活动背景
promotion_aux_color: '#FA8B00',//活动背景辅色
main_color_shallow: '#efefef',//淡背景
price_color: 'rgba(255,0,0,1)',//价格颜色
btn_text_color: '#FFFFFF',//按钮文字颜色
...{
goods_price: 'rgba(255,0,0,1)',//价格
promotion_tag: '#222222',
goods_card_bg: '#201A18',//会员卡背景
goods_card_bg_shallow: '#7C7878',//会员卡背景浅色
goods_card_color: '#FFD792',
goods_coupon: '#222222',
goods_cart_num_corner: '#FF0000',//购物车数量角标
goods_btn_color: '#222222',//按钮颜色
goods_btn_color_shallow: '#FA8B00',//副按钮颜色
},
...{
pintuan_label_bg: '#F7B500',
pintuan_label_color: '#FFFFFF',
pintuan_color: '#FA6400',
pintuan_promotion_color: '#FA3A1D',//活动背景
pintuan_promotion_aux_color: '#FD9A01',//活动背景辅色
},
...{
super_member_start_bg: '#fadcb5',
super_member_end_bg: '#f6bd74',
super_member_start_text_color: '#ab6126',
super_member_end_text_color: '#d19336',
},
...{
bargain_promotion_color: '#F0353E',//活动背景
bargain_promotion_aux_color: '#FD9A01',//活动辅色
},
...{
seckill_promotion_color: '#F83530',//活动背景
seckill_promotion_aux_color: '#FD9A01',//活动辅色
},
...{
giftcard_promotion_color: '#FF3369',//活动背景
giftcard_promotion_aux_color: '#F7B500',//活动辅色
},
...{
groupby_promotion_color: '#E64136',//活动背景
groupby_promotion_aux_color: '#F7B500',//活动辅色
},
...{
hover_nav_bg_color: '#222222',//背景色
hover_nav_text_color: '#FFFFFF'
},
}
}

148
common/js/uniapp.utils.js Normal file
View File

@@ -0,0 +1,148 @@
/**
* 将常用的Uniapp提供的函数存放到这里按需引用
*/
/**
* 显示错误信息
* @param {Exception} err
* @param {Boolean} useModal
*/
const showError = (err, useModal = false) => {
const content = err?.message || err?.errMsg || err?.toString();
if (!useModal) {
uni.showToast({
title: content,
icon: 'none',
duration: 3000
});
} else {
uni.showModal({
title: '错误提示',
content,
showCancel: false,
})
}
}
/**
* 打电话
* @param {string} mobile 电话号码
*/
export const makePhoneCall = (mobile) => {
try {
uni.makePhoneCall({
phoneNumber: `${mobile}`,
success(e) {
console.log(e);
}
});
} catch (err) {
showError(err);
}
}
/**
* 拷贝文本(返回 Promise
* @param {*} text
* @param {*} options
* @returns {Promise} 返回 Promise成功时 resolve失败时 reject
*/
export const copyTextAsync = (text, { copySuccess = '', copyFailed = '' } = {}) => {
return new Promise((resolve, reject) => {
// 输入验证
if (!text && text !== '') {
const error = new Error('复制文本不能为空');
showError(error);
reject(error);
return;
}
// 超时监测
const timeoutId = setTimeout(() => {
let error = new Error('复制操作长时间无响应,请检查相关权限及配置是否正确');
// #ifdef MP-WEIXIN
error = new Error([
'复制操作长时间无响应!',
'原因:',
'1.微信平台->用户隐私保护指引->"剪贴板"功能未添加或审核未通过;',
'2.微信平台对剪贴板API调用频率有限制'
].join('\r\n'));
// #endif
showError(error, true);
reject(error);
}, 5000);
try {
uni.setClipboardData({
data: `${text}`,
success: (res) => {
clearTimeout(timeoutId);
try {
if (copySuccess) {
uni.showToast({
title: copySuccess,
icon: 'success',
duration: 2000
});
}
} catch (e) {
showError(e);
}
resolve(res);
},
fail: (err) => {
clearTimeout(timeoutId);
try {
uni.showToast({
title: err.message || err.errMsg || copyFailed || '复制失败',
icon: 'none',
duration: 2000
});
} catch (e) {
showError(e);
}
reject(err);
}
});
} catch (err) {
clearTimeout(timeoutId);
showError(err);
reject(err);
}
});
}
/**
* 拷贝文本(回调形式,兼容旧代码)
* @param {*} text
* @param {*} options
* @param {Function} callback 回调函数,接收 (success, error) 参数
*/
export const copyText = (text, options = {}, callback) => {
copyTextAsync(text, options)
.then(res => {
if (callback) callback(true, null);
})
.catch(err => {
if (callback) callback(false, err);
});
}
/**
* 打开定位
* @param {Object} options
*/
export const openLocation = ({ latitude, longitude, name } = {}) => {
try {
uni.openLocation({
latitude,
longitude,
name,
});
} catch (err) {
showError(err);
}
}

View File

@@ -5,16 +5,137 @@ import {
Weixin
} from 'common/js/wx-jssdk.js';
// 关于页面路径,使用常量导出
export const INDEX_PAGE_URL = '/pages/index/index';
export const CATEGORY_PAGE_URL = '/pages_goods/category';
export const CONTACT_PAGE_URL = '/pages_tool/contact/contact';
export const MEMBER_PAGE_URL = '/pages_tool/member/index';
export const LOGIN_PAGE_URL = '/pages_tool/login/login';
export const AI_CHAT_PAGE_URL = '/pages_tool/ai-chat/index';
// 当前最新的tabBar.list (参见pages.json 中的tabBar.list 配置)
export const systemTabBarList = [
INDEX_PAGE_URL,
CATEGORY_PAGE_URL,
CONTACT_PAGE_URL,
MEMBER_PAGE_URL
];
/**
* 适配子包路径
* @param {string} url
* @returns
*/
export const adaptSubpackageUrl = (url) => {
/**
* 特别注意:
* 由于老版本或者后台系统服务未同步更新可以作为tabBarList的最新分包路径。历史遗留问题需要与当前最新分包机制版本保持一致。
* 系统当前的支持tarbarList包含动态tabBar,注意这是实际分包后的路径
* 根据匹配映射关系修改url为实际分包后的路径
*/
// 定义前缀映射表
const urlPrefixMap = {
'/pages/promotion/': '/pages_promotion/',
'/pages/order/': '/pages_order/',
'/pages/goods/': '/pages_goods/',
'/pages/contact/': '/pages_tool/contact/',
'/pages/member/': '/pages_tool/member/',
'/pages/login/': '/pages_tool/login/',
'/pages/agreement/': '/pages_tool/agreement/',
'/pages/article/': '/pages_tool/article/',
'/pages/cases/': '/pages_tool/cases/',
'/pages/contact/': '/pages_tool/contact/',
'/pages/files/': '/pages_tool/files/',
'/pages/form/': '/pages_tool/form/',
'/pages/help/': '/pages_tool/help/',
'/pages/notice/': '/pages_tool/notice/',
'/pages/pay/': '/pages_tool/pay/',
'/pages/recharge/': '/pages_tool/recharge/',
'/pages/seal/': '/pages_tool/seal/',
'/pages/storeclose/': '/pages_tool/storeclose/',
'/pages/vr/': '/pages_tool/vr/',
'/pages/weapp/': '/pages_tool/weapp/',
'/pages/webview/': '/pages_tool/webview/',
};
// 构建正则表达式
const regex = new RegExp(
Object.keys(urlPrefixMap).map(key =>
key.replace(/\//g, '\\/').replace(/\*/g, '.*')
).join('|'),
'g'
);
// 替换函数
function replacePrefix(str) {
return str.replace(regex, match => {
for (const [oldPrefix, newPrefix] of Object.entries(urlPrefixMap)) {
if (match.startsWith(oldPrefix)) {
return match.replace(oldPrefix, newPrefix);
}
}
return match; // 默认返回原匹配
});
}
return replacePrefix(url);
}
/**
* 检查tabBar是否激活
* @param {string} linkUrl 链接路径 /pages/goods/category
* @param {string} currentPageRoute 当前页面路径 /pages_goods/category
* @returns {boolean} 是否激活
*/
export const checkTabBarActive = (linkUrl, currentPageRoute) => {
const linkUrlParts = adaptSubpackageUrl(linkUrl).split('/');
const currentPageRouteParts = currentPageRoute.split('/');
// console.log('diy-bottom-nav verify:', { linkUrlParts, currentPageRouteParts});
try {
// 二级页面
if (linkUrlParts[2] === currentPageRouteParts[2]) {
return true;
}
// 一级页面限定只能是goods相关
if (linkUrlParts[1] === currentPageRouteParts[1] && linkUrlParts[1] === 'pages_goods') {
return true;
}
} catch (error) {
console.error('diy-bottom-nav verify error:', error);
}
return false;
}
export default {
/** 导出页面URL常量 */
MEMBER_PAGE_URL,
CATEGORY_PAGE_URL,
CONTACT_PAGE_URL,
INDEX_PAGE_URL,
LOGIN_PAGE_URL,
AI_CHAT_PAGE_URL,
/**
* 页面跳转
* @param {string} to 跳转链接 /pages/idnex/index
* @param {Object} param 参数 {key : value, ...}
* @param {string} mode 模式
* @param {Object} param 参数 {key : value, ...}, 默认{}
* @param {string} mode 模式 navigateTo、redirectTo、reLaunch、tabbar, 默认navigateTo
*/
redirectTo(to, param, mode) {
redirectTo(to, param = {}, mode = 'navigateTo') {
let url = to;
let tabbarList = ['/pages/index/index', '/pages/goods/category', '/pages/vr/index', '/pages/contact/contact', '/pages/member/index'];
// 替换url中的前缀
console.log('页面跳转 redirectTo', to, param, mode);
url = adaptSubpackageUrl(url);
console.log('adaptSubpackageUrl', url);
if (param != undefined) {
Object.keys(param).forEach(function (key) {
if (url.indexOf('?') != -1) {
@@ -24,35 +145,40 @@ export default {
}
});
}
for (let i = 0; i < tabbarList.length; i++) {
if (url.indexOf(tabbarList[i]) == 0) {
uni.switchTab({
url
});
// 针对tabBar.list中的路径直接切换tabBar
for (let i = 0; i < systemTabBarList.length; i++) {
const tabBarUrl = systemTabBarList[i];
if (url.indexOf(tabBarUrl) == 0) {
// 首页特殊处理采用switchTab, 其他页面采用redirectTo, 不能返回,不能退回
(tabBarUrl == INDEX_PAGE_URL ? uni.switchTab : uni.redirectTo)({ url });
return;
}
}
// 如果url不包含tabBarList中的路径根据mode判断跳转方式
switch (mode) {
case 'tabbar':
// 跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面。
// 跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面。重置页面栈,仅保留 tabBar 页面
uni.switchTab({
url
});
break;
case 'redirectTo':
// 关闭当前页面,跳转到应用内的某个页面。
// 关闭当前页面,跳转到应用内的某个页面。替换栈顶页面长度不变上限10层
uni.redirectTo({
url
});
break;
case 'reLaunch':
// 关闭所有页面,打开到应用内的某个页面。
// 关闭所有页面,打开到应用内的某个页面。清空栈后打开新页面,长度=1
uni.reLaunch({
url
});
break;
default:
// 保留当前页面,跳转到应用内的某个页面
// 保留当前页面,跳转到应用内的某个页面,新增页面到栈顶,长度+1上限10层
console.log('保留当前页面navigateTo跳转到应用内的某个页面', url);
uni.navigateTo({
url
});
@@ -64,8 +190,6 @@ export default {
* @param {Object} params 参数针对商品、相册里面的图片区分大中小size: big、mid、small
*/
img(img_path, params) {
var path = "";
if (img_path != undefined && img_path != "") {
if (img_path.split(',').length > 1) {
@@ -81,7 +205,7 @@ export default {
// if(img_path.indexOf('attachment') == -1){
// img_path = arr.join(".");
// }
}
if (img_path.indexOf("http://") == -1 && img_path.indexOf("https://") == -1) {
path = Config.imgDomain + "/" + img_path;
@@ -90,7 +214,7 @@ export default {
path = img_path;
}
}
// path += '?t=' + parseInt(new Date().getTime() / 1000);
return path;
},
@@ -379,6 +503,36 @@ export default {
return false;
}
},
/**
* 判断是否在微信小程序的web-view中打开H5
* @returns {boolean} 是否在微信小程序的web-view中
*/
isWeChatMiniProgram() {
const userAgent = navigator.userAgent.toLowerCase();
// 微信小程序的web-view User-Agent会包含以下特征之一
const miniProgramIndicators = [
'miniprogram', // 普通微信小程序
'wxwork', // 企业微信小程序
'micromessenger/[0-9]+\.[0-9]+\.[0-9]+ nettype/wifi wxwebviewtype/1'
];
return miniProgramIndicators.some(indicator => {
if (typeof indicator === 'string' && indicator.includes('/')) {
// 正则表达式匹配
const regex = new RegExp(indicator, 'i');
return regex.test(userAgent);
}
return userAgent.includes(indicator);
});
},
/**
* 判断是否在微信浏览器中打开H5非小程序的web-view
* @returns {boolean} 是否在微信浏览器中打开的H5
*/
isWeChatBrowser() {
return isWeiXin() && !isWeChatMiniProgram();
},
/**
* 显示消息提示框
* @param {Object} params 参数
@@ -395,7 +549,7 @@ export default {
* 检测苹果X以上的手机
*/
isIPhoneX() {
let res = uni.getSystemInfoSync();
let res = this.getDeviceInfo();
if (res.model.search('iPhone X') != -1) {
return true;
}
@@ -403,13 +557,33 @@ export default {
},
//判断安卓还是iOS
isAndroid() {
let platform = uni.getSystemInfoSync().platform
let platform = this.getDeviceInfo().platform;
if (platform == 'ios') {
return false;
} else if (platform == 'android') {
return true;
}
},
/**
* 获取设备信息(包含降级处理)
*/
getDeviceInfo() {
try {
return uni.getDeviceInfo();
} catch (e) {
return uni.getSystemInfoSync();
}
},
/**
* 获取窗口信息(包含降级处理)
*/
getWindowInfo() {
try {
return uni.getWindowInfo();
} catch (e) {
return uni.getSystemInfoSync();
}
},
/**
* 深度拷贝对象
* @param {Object} obj
@@ -431,16 +605,46 @@ export default {
}
return cloneObj
},
/**
* 打开微信企业客服
* @param {Function} fallbackFunc 降级处理函数
*/
openWxWorkServiceChat(fallbackFunc) {
// #ifdef MP-WEIXIN
let wxworkConfig = store.state?.wxworkConfig
if (wxworkConfig?.enable && wxworkConfig?.contact_url) {
// 直接使用活码链接跳转
wx.navigateToMiniProgram({
appId: 'wxeb490c6f9b154ef9', // 是企业微信官方小程序的AppID固定值, 由腾讯企业微信团队维护, 不需要修改,用于展示"添加企业微信联系人"的官方页面)
path: `pages/contacts/externalContactDetail?url=${encodeURIComponent(wxworkConfig?.contact_url)}`,
success: () => {
console.log('跳转企业微信成功');
},
fail: (err) => {
console.error('跳转企业微信失败:', err);
// 降级处理:使用原有客服方式
fallbackFunc && fallbackFunc();
}
});
} else {
fallbackFunc && fallbackFunc();
}
// #endif
},
/**
* 自定义模板的跳转链接
* @param {Object} link
*/
diyRedirectTo(link) {
//if (link == null || Object.keys(link).length == 1) return;
// 外部链接
if (link.wap_url && link.wap_url.indexOf('http') != -1 || link.wap_url && link.wap_url.indexOf('http') != -1) {
if (link.wap_url && link.wap_url.indexOf('http') != -1) {
// #ifdef H5
window.location.href = link.wap_url;
// #endif
@@ -453,7 +657,6 @@ export default {
} else if (link.appid) {
// 跳转其他小程序
uni.navigateToMiniProgram({
appId: link.appid,
path: link.page
@@ -461,7 +664,6 @@ export default {
} else if (link.name == 'MOBILE' && !link.wap_url) {
// 拨打电话
uni.makePhoneCall({
phoneNumber: link.mobile,
success: (res) => {
@@ -494,16 +696,19 @@ export default {
// #endif
// #ifdef MP-WEIXIN
wx.openCustomerServiceChat({
extInfo: {
url: config.wxwork_url
},
corpId: config.corpid,
showMessageCard: true,
sendMessageTitle: 'this.sendMessageTitle',
sendMessagePath: 'this.sendMessagePath',
sendMessageImg: 'this.sendMessageImg'
this.openWxWorkServiceChat(() => {
wx.openCustomerServiceChat({
extInfo: {
url: config.wxwork_url
},
corpId: config.corpid,
showMessageCard: true,
sendMessageTitle: 'this.sendMessageTitle',
sendMessagePath: 'this.sendMessagePath',
sendMessageImg: 'this.sendMessageImg'
});
});
// #endif
break;
case 'third':
@@ -554,7 +759,7 @@ export default {
*/
uniappIsIPhoneX() {
let isIphoneX = false;
let systemInfo = uni.getSystemInfoSync();
let systemInfo = this.getDeviceInfo();
// #ifdef MP
if (systemInfo.model.search('iPhone X') != -1 || systemInfo.model.search('iPhone 11') != -1 || systemInfo.model.search('iPhone 12') != -1 || systemInfo.model.search('iPhone 13') != -1) {
isIphoneX = true;
@@ -581,7 +786,7 @@ export default {
*/
uniappIsIPhone11() {
let isIphone11 = false;
let systemInfo = uni.getSystemInfoSync();
let systemInfo = this.getDeviceInfo();
// #ifdef MP
if (systemInfo.model.search('iPhone 11') != -1) {
isIphone11 = true;
@@ -592,7 +797,7 @@ export default {
// #ifdef H5
//判断该浏览器是否为safaria浏览器
isSafari() {
let res = uni.getSystemInfoSync();
let res = this.getDeviceInfo();
var ua = navigator.userAgent.toLowerCase();
if (ua.indexOf('applewebkit') > -1 && ua.indexOf('mobile') > -1 && ua.indexOf('safari') > -1 &&
ua.indexOf('linux') === -1 && ua.indexOf('android') === -1 && ua.indexOf('chrome') === -1 &&
@@ -631,13 +836,22 @@ export default {
let routes = getCurrentPages(); // 获取当前打开过的页面路由数组
return routes.length ? routes[routes.length - 1].route : '';
},
goBack(backUrl = '/pages/index/index') {
goBack(backUrl = INDEX_PAGE_URL) {
if (getCurrentPages().length == 1) {
this.redirectTo(backUrl);
} else {
uni.navigateBack();
}
},
/**
* 隐藏“返回首页/小房子”按钮
* 这个函数用到页面show, onshow 的生命周期时
*/
hideHomeButton() {
// #ifdef MP-WEIXIN
wx.hideHomeButton();
// #endif
},
/**
*
* @param val 转化时间字符串 (转化时分秒)
@@ -707,11 +921,12 @@ export default {
* 微信订阅消息
*/
subscribeMessage(keywords) {
let url = ""
// #ifdef MP-WEIXIN
let url = '/weapp/api/weapp/messagetmplids';
url = '/weapp/api/weapp/messagetmplids';
// #endif
// #ifdef MP-ALIPAY
let url = '/aliapp/api/aliapp/messagetmplids';
url = '/aliapp/api/aliapp/messagetmplids';
// #endif
Http.sendRequest({
url,
@@ -755,7 +970,7 @@ export default {
if (!path) {
let route = this.getCurrentRoute();
path = route.path;
if (path == '/pages/member/index') {
if (path == '/pages_tool/member/index') {
return new Promise((resolve, reject) => {
resolve({})
});
@@ -920,11 +1135,11 @@ export default {
var points_E = [];
const DIST_AB = Math.sqrt(Math.pow(points[1]['x'] - points[0]['x'], 2) + Math.pow(points[1]['y'] - points[0][
'y'
], 2));
], 2));
// 邻控制BC点间距
const DIST_BC = Math.sqrt(Math.pow(points[2]['x'] - points[1]['x'], 2) + Math.pow(points[2]['y'] - points[1][
'y'
], 2));
], 2));
// D每次在AB方向上移动的距离
if (points[0]['x'] > points[2]['x']) {
var EACH_MOVE_AD = -(DIST_AB / times);
@@ -986,5 +1201,74 @@ export default {
verifyMobile(mobile) {
var parse = /^\d{11}$/.test(mobile);
return parse;
},
/**
* 判断是否为快应用环境
* @returns {boolean} 是否为快应用
*/
isQuickApp() {
// #ifndef QUICKAPP-WEBVIEW
let _isQuickApp = false;
try {
const ua = navigator?.userAgent?.toLowerCase();
// console.log('ua = ', ua);
_isQuickApp = ua.indexOf('quickapp') !== -1;
if (!_isQuickApp) {
// 特殊说明由于网站是嵌入到快应用的Webview组件中<web>UA中不会包含quickapp字符串
// 但是会包含uniacid参数所以这里通过uniacid参数来判断是否为快应用环境
_isQuickApp = ua.indexOf('uniacid=') !== -1;
}
} catch (e) { }
if (!_isQuickApp) {
const systemInfo = this.getDeviceInfo();
_isQuickApp = systemInfo?.platform === 'quickapp' || process?.env?.UNI_PLATFORM === 'quickapp';
}
return _isQuickApp;
// #endif
return true;
},
/**
* 判断是否为华为快应用环境
* @returns {boolean} 是否为华为快应用
*/
isHuaweiQuickApp() {
if (!this.isQuickApp()) return false;
// #ifndef QUICKAPP-WEBVIEW-HUAWEI
const systemInfo = this.getDeviceInfo();
return systemInfo.brand === 'HUAWEI' || systemInfo.manufacturer === 'HUAWEI' ||
(typeof qh !== 'undefined' && qh.platform === 'huawei');
// #endif
return true;
},
/**
* 蛇形命名转驼峰命名
* @param {string} str 蛇形命名字符串
* @returns {string} 驼峰命名字符串
*/
snakeToCamel(str) {
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
},
/**
* 蛇形命名转驼峰命名(递归处理对象中的所有属性名)
* @param {Object} obj 包含蛇形命名字符串属性的对象
* @returns {Object} 驼峰命名字符串属性的对象
*/
snakeToCamelForObj(obj) {
if (typeof obj !== 'object' || obj === null) return obj;
if (Array.isArray(obj)) return obj.map(this.snakeToCamelForObj.bind(this));
const newObj = {};
for (const key in obj) {
if (Object.hasOwnProperty.call(obj, key)) {
const newKey = this.snakeToCamel(key);
newObj[newKey] = this.snakeToCamelForObj(obj[key]);
}
}
return newObj;
}
}

187
common/js/wxwork-jssdk.js Normal file
View File

@@ -0,0 +1,187 @@
/**
* 企业微信JS-SDK调用
*/
let WxWork = function () {
// 企业微信JS-SDK
this.wxwork = null;
/**
* 初始化企业微信JS-SDK
* @param {Object} params - 初始化参数
* @param {string} params.corpId - 企业ID
* @param {string} params.agentId - 应用ID
* @param {string} params.timestamp - 时间戳
* @param {string} params.nonceStr - 随机字符串
* @param {string} params.signature - 签名
* @param {Array} params.jsApiList - 需要使用的JS接口列表
*/
this.init = function (params) {
if (typeof wx !== 'undefined' && wx.config) {
// 小程序环境下的企业微信
this.wxwork = wx;
} else if (typeof WWOpenData !== 'undefined') {
// H5环境下的企业微信
this.wxwork = WWOpenData;
} else {
console.error('企业微信JS-SDK未加载');
return false;
}
this.wxwork.config({
beta: true, // 必须这么写否则wx.invoke调用形式的jsapi会有问题
debug: false, // 开启调试模式
corpId: params.corpId, // 必填,企业号的唯一标识
agentId: params.agentId, // 必填企业微信应用ID
timestamp: params.timestamp, // 必填,生成签名的时间戳
nonceStr: params.nonceStr, // 必填,生成签名的随机串
signature: params.signature, // 必填,签名
jsApiList: params.jsApiList || [
'openUserProfile',
'openEnterpriseChat',
'getContext',
'getCurExternalContact',
'openExistedChatWithMsg'
] // 必填需要使用的JS接口列表
});
return true;
};
/**
* 添加企业微信联系人
* @param {Object} params - 参数
* @param {string} params.userId - 用户ID
* @param {Function} success - 成功回调
* @param {Function} fail - 失败回调
*/
this.addContact = function (params, success, fail) {
if (!this.wxwork) {
console.error('企业微信JS-SDK未初始化');
if (fail) fail('企业微信JS-SDK未初始化');
return;
}
this.wxwork.ready(() => {
this.wxwork.invoke('openUserProfile', {
type: 'external', // 外部联系人
userId: params.userId // 用户ID
}, (res) => {
if (res.err_msg === 'openUserProfile:ok') {
if (success) success(res);
} else {
console.error('打开用户资料失败:', res);
if (fail) fail(res.err_msg);
}
});
});
};
/**
* 打开企业微信客服会话
* @param {Object} params - 参数
* @param {string} params.corpId - 企业ID
* @param {string} params.url - 客服URL
* @param {string} params.name - 会话名称
* @param {Function} success - 成功回调
* @param {Function} fail - 失败回调
*/
this.openCustomerService = function (params, success, fail) {
if (!this.wxwork) {
console.error('企业微信JS-SDK未初始化');
if (fail) fail('企业微信JS-SDK未初始化');
return;
}
this.wxwork.ready(() => {
// #ifdef MP-WEIXIN
if (typeof wx !== 'undefined' && wx.openCustomerServiceChat) {
// 微信小程序环境
wx.openCustomerServiceChat({
extInfo: {
url: params.url
},
corpId: params.corpId,
showMessageCard: true,
sendMessageTitle: params.sendMessageTitle || '',
sendMessagePath: params.sendMessagePath || '',
sendMessageImg: params.sendMessageImg || ''
});
if (success) success();
}
// #endif
// #ifdef H5
else if (typeof WWOpenData !== 'undefined') {
// H5环境
window.location.href = params.url;
if (success) success();
}
// #endif
else {
// 直接跳转链接
window.location.href = params.url;
if (success) success();
}
});
};
/**
* 生成企业微信活码链接
* @param {Object} params - 参数
* @param {string} params.configId - 活码配置ID
* @param {string} params.userId - 用户ID
* @returns {string} 活码链接
*/
this.generateContactUrl = function (params) {
// 企业微信活码链接格式
const baseUrl = 'https://work.weixin.qq.com/kfid';
if (params.configId) {
return `${baseUrl}/${params.configId}`;
}
return null;
};
/**
* 检查环境是否支持企业微信
* @returns {boolean} 是否支持
*/
this.isSupported = function () {
// #ifdef MP-WEIXIN
return typeof wx !== 'undefined' && wx.openCustomerServiceChat;
// #endif
// #ifdef H5
return typeof WWOpenData !== 'undefined' || /wxwork/i.test(navigator.userAgent);
// #endif
return false;
};
/**
* 获取当前环境信息
* @returns {Object} 环境信息
*/
this.getEnvironment = function () {
// #ifdef MP-WEIXIN
return {
platform: 'miniprogram',
isWxWork: false,
supportContact: typeof wx !== 'undefined' && wx.openCustomerServiceChat
};
// #endif
// #ifdef H5
const isWxWork = /wxwork/i.test(navigator.userAgent);
return {
platform: 'h5',
isWxWork: isWxWork,
supportContact: isWxWork || typeof WWOpenData !== 'undefined'
};
// #endif
return {
platform: 'unknown',
isWxWork: false,
supportContact: false
};
};
}
export {
WxWork
}

View File

@@ -0,0 +1,143 @@
// 公共微信视频号样式
// CSS 变量
:root {
// 尺寸变量
--channel-play-btn-size: 80rpx;
--channel-play-btn-small-size: 60rpx;
--channel-play-btn-icon-size: 40rpx;
--channel-play-btn-icon-small-size: 30rpx;
--channel-border-radius: 12rpx;
--channel-stats-padding: 10rpx;
--channel-info-wrap-padding: 10rpx 0;
// 字体变量
--channel-name-font-size: 28rpx;
--video-title-font-size: 28rpx;
--video-title-small-font-size: 24rpx;
--video-stats-font-size: 24rpx;
--channel-stats-font-size: 20rpx;
// 颜色变量
--channel-name-color: #333;
--video-title-color: #333;
--video-title-small-color: #666;
--video-stats-color: #999;
--channel-stats-color: #fff;
--channel-border-color: #f0f0f0;
--channel-play-btn-bg: rgba(0, 0, 0, 0.4);
--channel-stats-bg: linear-gradient(transparent, rgba(0, 0, 0, 0.6));
// 间距变量
--channel-name-margin-bottom: 4rpx;
--video-title-margin-bottom: 8rpx;
}
// 响应式设计
@media (max-width: 375px) {
:root {
--channel-play-btn-size: 70rpx;
--channel-play-btn-small-size: 50rpx;
--channel-play-btn-icon-size: 35rpx;
--channel-play-btn-icon-small-size: 25rpx;
--channel-name-font-size: 24rpx;
--video-title-font-size: 24rpx;
}
}
@media (min-width: 750px) {
:root {
--channel-play-btn-size: 90rpx;
--channel-play-btn-small-size: 70rpx;
--channel-play-btn-icon-size: 45rpx;
--channel-play-btn-icon-small-size: 35rpx;
--channel-name-font-size: 32rpx;
--video-title-font-size: 32rpx;
}
}
// 播放按钮样式
.channel-play-btn {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
align-items: center;
justify-content: center;
width: var(--channel-play-btn-size);
height: var(--channel-play-btn-size);
background-color: var(--channel-play-btn-bg);
border-radius: 50%;
.play-icon {
width: var(--channel-play-btn-icon-size);
height: var(--channel-play-btn-icon-size);
}
}
// 小尺寸播放按钮(用于列表)
.channel-play-btn.small {
width: var(--channel-play-btn-small-size);
height: var(--channel-play-btn-small-size);
.play-icon {
width: var(--channel-play-btn-icon-small-size);
height: var(--channel-play-btn-icon-small-size);
}
}
// 视频统计信息
.channel-stats {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: var(--channel-stats-bg);
padding: var(--channel-stats-padding);
color: var(--channel-stats-color);
font-size: var(--channel-stats-font-size);
}
// 视频信息容器
.channel-info-wrap {
display: flex;
flex-direction: column;
flex: 1;
padding: var(--channel-info-wrap-padding);
.channel-name {
margin-bottom: var(--channel-name-margin-bottom);
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
font-size: var(--channel-name-font-size);
font-weight: 500;
color: var(--channel-name-color);
}
.video-title {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
font-size: var(--video-title-small-font-size);
color: var(--video-title-small-color);
}
}
// 视频标题
.video-title {
font-size: var(--video-title-font-size);
font-weight: 500;
color: var(--video-title-color);
margin-bottom: var(--video-title-margin-bottom);
line-height: 1.4;
}
// 视频统计信息(非绝对定位版本)
.video-stats {
font-size: var(--video-stats-font-size);
color: var(--video-stats-color);
}

View File

@@ -0,0 +1,265 @@
<template>
<x-skeleton data-component-name="diy-article" type="list" :loading="loading" :configs="skeletonConfig">
<view class="article-wrap" :style="warpCss">
<view :class="['list-wrap', value.style]" :style="warpCss">
<!-- 自动垂直滚动 -->
<swiper class="auto-scroll-swiper" :style="[swiperStyle]" :vertical="swiperConfig.vertical !== false"
:autoplay="swiperConfig.autoplay !== false" :circular="swiperConfig.circular !== false"
:interval="swiperConfig.interval || 3000" :duration="swiperConfig.duration || 500"
:display-multiple-items="safeDisplayMultipleItems">
<swiper-item v-for="(item, index) in list" :key="index" @tap.stop="toDetail(item)">
<view class="swiper-item-content">
<view :class="['item', value.ornament.type]" :style="itemCss">
<view class="article-img">
<image class="cover-img" :src="$util.img(item.cover_img)" mode="widthFix"
@error="imgError(index)" />
</view>
<view class="info-wrap">
<text class="title">{{ item.article_title }}</text>
<text class="desc"
style="color:#888;font-size: 24rpx; display: -webkit-box;-webkit-box-orient: vertical;-webkit-line-clamp: 2;overflow: hidden;text-overflow: ellipsis;">{{
item.article_abstract }}</text>
<view class="read-wrap">
<block v-if="item.category_name">
<text class="category-icon"></text>
<text>{{ item.category_name }}</text>
</block>
<text class="date">{{ $util.timeStampTurnTime(item.create_time, 'date')
}}</text>
</view>
</view>
</view>
</view>
</swiper-item>
</swiper>
</view>
</view>
</x-skeleton>
</template>
<script>
import DiyMinx from './minx.js'
// 文章
export default {
name: 'diy-article',
props: {
value: {
type: Object
}
},
mixins: [DiyMinx],
data() {
return {
list: [],
loading: true,
skeletonConfig: {
gridRows: 1,
gridRowsGap: '40rpx',
headWidth: '160rpx',
headHeight: '160rpx',
textRows: 2
}
};
},
created() {
this.getList();
},
watch: {
// 组件刷新监听
componentRefresh: function (nval) {
this.getList();
}
},
computed: {
// 获取swiper配置默认空对象
swiperConfig() {
return this.value?.swiperConfig || {};
},
// 安全的display-multiple-items值确保不大于swiper-item数量
safeDisplayMultipleItems() {
const displayCount = this.swiperConfig.displayMultipleItems || 3;
const actualCount = this.list.length;
return actualCount > 0 ? Math.min(displayCount, actualCount) : 1;
},
warpCss() {
var obj = '';
obj += 'background-color:' + this.value.componentBgColor + ';';
if (this.value.componentAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
}
return obj;
},
// swiper容器样式
swiperStyle() {
// 从swiperConfig获取高度默认600rpx
const height = this.swiperConfig.swiperHeight || 600;
return {
height: height + 'rpx',
width: '100%'
};
},
// 子项样式
itemCss() {
var obj = '';
obj += 'background-color:' + this.value.elementBgColor + ';';
if (this.value.elementAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
}
if (this.value.ornament.type == 'shadow') {
obj += 'box-shadow:' + '0 0 10rpx ' + this.value.ornament.color;
}
if (this.value.ornament.type == 'stroke') {
obj += 'border:' + '2rpx solid ' + this.value.ornament.color;
}
return obj;
}
},
methods: {
getList() {
var data = {
num: this.value.count
};
if (this.value.sources == 'diy') {
data.num = 0;
data.article_id_arr = this.value.articleIds.toString();
}
this.$api.sendRequest({
url: '/api/article/lists',
data: data,
success: res => {
if (res.code == 0 && res.data) {
let data = res.data;
this.list = data;
}
this.loading = false;
}
});
},
toDetail(item) {
this.$util.redirectTo('/pages_tool/article/detail', {
article_id: item.article_id
});
},
imgError(index) {
if (this.list[index]) this.list[index].cover_img = this.$util.getDefaultImage().article;
}
}
};
</script>
<style lang="scss">
.article-wrap {
.list-wrap {
.auto-scroll-swiper {
height: 600rpx;
/* 可以根据需要调整高度 */
width: 100%;
.swiper-item-content {
padding: 10rpx 20rpx;
box-sizing: border-box;
}
}
.item {
margin: 0;
margin-bottom: 24rpx;
}
.style-1 .item {
padding: 20rpx;
}
&.style-1 {
.item {
display: flex;
margin-top: 0;
&:first-of-type {
margin-top: 0;
}
.article-img {
margin-right: 20rpx;
width: 160rpx;
height: 160rpx;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
image {
width: 100%;
}
}
.info-wrap {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
.title {
font-weight: bold;
margin-bottom: 10rpx;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
font-size: 30rpx;
line-height: 1.5;
}
.abstract {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
font-size: $font-size-tag;
}
.read-wrap {
display: flex;
color: #999ca7;
justify-content: flex-start;
align-items: center;
margin-top: 10rpx;
line-height: 1;
text {
font-size: $font-size-tag;
}
.iconfont {
font-size: 36rpx;
vertical-align: bottom;
margin-right: 10rpx;
}
.category-icon {
width: 8rpx;
height: 8rpx;
border-radius: 50%;
background: $base-color;
margin-right: 10rpx;
}
.date {
margin-left: 20rpx;
}
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,453 @@
<template>
<view data-component-name="diy-audio">
<view class="fui-audio style1" :style="{ background: value.background }" v-if="value.type == 'style-2'">
<view class="content" style="padding-top: 20rpx;">
<view class="name" :style="{ color: value.textcolor }">{{ value.text }}</view>
<view class="author" :style="{ color: value.subtitlecolor }">{{ value.desc }}----{{ value.id }}</view>
</view>
<view class="progress">
<view class="progressBar" :style="{ width: audiowidth }"></view>
</view>
<view class="time" :style="{ color: value.timecolor }">
{{ audiotime }}
</view>
<view @tap.stop="play()" class="start" :class="status ? 'iconj icon-07zanting' : 'iconj icon-bofang'"
style="padding-top: 18rpx"></view>
</view>
<view class="fui-audio style3" :style="{ background: value.background }" v-else>
<!-- <audio src="/static/audio/bgm.mp3" controls loop></audio> -->
<view class="img">
<image :src="$util.img(value.imageUrl)"></image>
</view>
<view class="content">
<view class="name" :style="{ color: value.textcolor }">{{ value.text }}</view>
<view class="author" :style="{ color: value.subtitlecolor }">{{ value.desc }}</view>
</view>
<view class="progress">
<view class="progressBar" :style="{ width: audiowidth }"></view>
</view>
<view class="time" :style="{ color: value.timecolor }">
<!-- {{audios[value.id].audiotime}} -->
{{ audiotime }}
</view>
<view @tap.stop="play()" class="start" :class="status ? 'iconj icon-07zanting' : 'iconj icon-bofang'"></view>
</view>
</view>
</template>
<script>
import DiyMinx from './minx.js'
// 音频
export default {
name: 'diy-audio',
props: {
value: {
type: Object
}
},
mixins: [DiyMinx],
data() {
return {
audiosObj: [],
audios: {},
audioContext: null,
audiotime: '00:01',
audiowidth: 0,
status: 0,//1播放0停止
};
},
created() {
// console.log(this.value)
this.audios[this.value.id] = {
audiotime: '00:01',
audiowidth: 0
}
},
watch: {
// 组件刷新监听
componentRefresh: function (nval) { }
},
computed: {
videoWarpCss: function () {
var obj = '';
if (this.value.componentAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
}
return obj;
}
},
methods: {
play() {
var t = this.value.id, a = this.audiosObj[t] || !1;
var e = {
audio: this.$util.img(this.value.audioUrl),
}
if (!a) {
a = uni.createInnerAudioContext("audio_" + t);
var i = this.audiosObj;
i[t] = a, this.audiosObj = i
// uni.setStorageSync('audio_list',audio_list)
var audio_list = uni.getStorageSync('audio_list') ? uni.getStorageSync('audio_list') : []
if (audio_list.includes(t) == false) {
audio_list.push(t)
uni.setStorageSync('audio_list', audio_list)
}
}
console.log(uni.getStorageSync('audio_list'))
var n = this;
// console.log(a)
a.onPlay(function () {
var e = setInterval(function () {
var i = a.currentTime / a.duration * 100 + "%", s = Math.floor(Math.ceil(a.currentTime) / 60), o = (Math.ceil(a.currentTime) % 60 / 100).toFixed(2).slice(-2), r = Math.ceil(a.currentTime);
s < 10 && (s = "0" + s);
var u = s + ":" + o, c = n.audios;
// console.log(i)
c[t].audiowidth = i, c[t].Time = e, c[t].audiotime = u, c[t].seconds = r, n.audios = c;
}, 1e3);
});
var s = n.$util.img(n.value.audioUrl), o = n.audios[n.value.id].seconds || 0, r = 0, u = 1;
0 == u && a.onEnded(function (e) {
c[t].status = !1, n.status = !1, c[t].seconds = 0, console.log(c), n.audios = c;
});
var c = n.audios;
c[t] || (c[t] = {}), a.paused && 0 == o ? (a.src = s, a.play(), 1 == u && (a.loop = !0),
c[t].status = !0, n.status = !0, n.pauseOther(t)) : a.paused && o > 0 ? (a.play(), 0 == r ? a.seek(o) : a.seek(0),
c[t].status = !0, n.status = !0, n.pauseOther(t)) : (a.pause(), c[t].status = !1, n.status = !1), n.audios = c;
console.log(n.audios)
},
pauseOther: function (e) {
var t = this;
// console.log(this.audiosObj[this.value.id]);
var i = this.audiosObj[this.value.id], a = this.value.id
// console.log(i)
// console.log(a)
// if (a != e) {
// i.pause();
// var n = t.audios;
// n[a] && (n[a].status = !1, this.audios=n);
// }
var audios = document.getElementsByTagName("audio");
// 暂停函数
function pauseAll() {
var self = this;
[].forEach.call(audios, function (i) {
// 将audios中其他的audio全部暂停
i !== self && i.pause();
})
}
// 给play事件绑定暂停函数
[].forEach.call(audios, function (i) {
i.addEventListener("play", pauseAll.bind(i));
})
// var audio_list = uni.getStorageSync('audio_list')
// audio_list.forEach(function(value, index) {
// if (value != e) {
// console.log(e)
// uni.createInnerAudioContext("audio_" + value).pause();
// }
// });
// this.audiosObj.forEach(function(value, index) {
// console.log(value);
// });
// this.each(this.audiosObj, function(a, i) {
// if (a != e) {
// i.pause();
// var n = t.data.audios;
// n[a] && (n[a].status = !1, t.setData({
// audios: n
// }));
// }
// });
},
play_bak() {
var t = this.value.id
this.audioContext = uni.createInnerAudioContext("audio_" + this.value.id);
this.audioContext.src = this.$util.img(this.value.audioUrl);
var that = this
if (this.status == 1) {
this.audioContext.pause();
this.status = 0
return false
}
this.audioContext.play();
this.status = 1
this.audioContext.onCanplay(function (s) {
var e = setInterval(function () {
var i = parseFloat(that.audioContext.currentTime) / parseFloat(that.audioContext.duration) * 100 + "%", s = Math.floor(Math.ceil(that.audioContext.currentTime) / 60), o = (Math.ceil(that.audioContext.currentTime) % 60 / 100).toFixed(2).slice(-2), r = Math.ceil(that.audioContext.currentTime);
s < 10 && (s = "0" + s);
var u = s + ":" + o, c = that.audios;
c[t].audiowidth = i, c[t].Time = e, c[t].audiotime = u, c[t].seconds = r
that.audios = c
// console.log(c)
console.log(that.audios[that.value.id].audiotime)
that.audiotime = that.audios[that.value.id].audiotime
that.audiowidth = that.audios[that.value.id].audiowidth
console.log(i)
that.lyg = i
}, 1e3);
});
this.audioContext.onEnded(() => {
console.log('播放结束');
this.status = 0
});
}
}
};
</script>
<style scoped>
.fui-audio {
width: 100%;
border: 1rpx solid #eeeeee;
padding: 0 30rpx 0 20rpx;
box-sizing: border-box;
position: relative;
overflow: hidden;
background: #fff;
}
.fui-audio .img {
width: 100rpx;
height: 100rpx;
background: #000;
}
.fui-audio .img image {
width: 100%;
height: 100%;
}
.fui-audio .name {
font-size: 26rpx;
color: #333;
}
.fui-audio .author {
font-size: 26rpx;
color: #666;
}
.fui-audio .time {
font-size: 24rpx;
color: #999;
}
.fui-audio .start {
border: 0;
padding: 0;
margin: 0;
font-size: 28rpx;
}
.progressBar {
height: 2rpx;
width: 0;
background: #333;
}
.fui-audio.style1 {
height: 86rpx;
line-height: 82rpx;
}
.fui-audio.style1 .img,
.fui-audio.style2 .img {
display: none;
}
.fui-audio.style1 .name,
.fui-audio.style2 .name {
float: left;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 300rpx;
}
.fui-audio.style1 .author,
.fui-audio.style2 .author {
float: left;
margin-left: 12rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 200rpx;
}
.fui-audio.style1 .time,
.fui-audio.style4 .time {
display: none;
}
.fui-audio.style1 .start {
position: absolute;
top: 0rpx;
right: 40rpx;
width: 40rpx;
height: 40rpx;
color: #000;
}
.fui-audio.style1 .progress {
position: absolute;
bottom: 0;
left: 0;
right: 0;
}
.fui-audio.style2 {
height: 86rpx;
line-height: 82rpx;
}
.fui-audio.style2 .img {
display: none;
}
.fui-audio.style2 .time {
position: absolute;
top: 0;
right: 30rpx;
}
.fui-audio.style2 .name {
margin-left: 70rpx;
}
.fui-audio.style2 .start {
position: absolute;
top: 0rpx;
left: 30rpx;
width: 30rpx;
height: 30rpx;
color: #000;
}
.fui-audio.style2 .progress,
.fui-audio.style3 .progress {
display: none;
}
.fui-audio.style3 {
padding: 8rpx;
}
.fui-audio.style3 .start {
position: absolute;
top: 30rpx;
left: 28rpx;
width: 56rpx;
height: 56rpx;
color: #fff;
/* border: 2rpx solid #fff; */
border-radius: 50%;
text-indent: 18rpx;
line-height: 56rpx;
}
.fui-audio.style3 .img,
.fui-audio.style4 .img {
float: left;
margin-right: 20rpx;
}
.fui-audio.style3 .content {
width: 468rpx;
}
.fui-audio.style3 .content,
.fui-audio.style4 .content {
float: left;
height: 100rpx;
display: flex;
flex-direction: column;
justify-content: center;
}
.fui-audio.style3 .content .name {
height: 40rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.fui-audio.style3 .content .author {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.fui-audio.style3 .time {
position: absolute;
top: 40rpx;
right: 30rpx;
}
.fui-audio.style4 {
padding: 10rpx;
}
.fui-audio.style4 .content {
padding-bottom: 18rpx;
height: 82rpx;
width: 500rpx;
}
.fui-audio.style4 .start {
position: absolute;
top: 32rpx;
right: 30rpx;
width: 30rpx;
height: 30rpx;
color: #000;
}
.fui-audio.style4 .name {
line-height: 1.2;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.fui-audio.style4 .author {
line-height: 1.2;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.fui-audio.style4 .progress {
background: #f5f5f5;
height: 4rpx;
position: absolute;
bottom: 24rpx;
left: 130rpx;
right: 30rpx;
border-radius: 2rpx;
overflow: hidden;
}
.fui-audio.style4 .progressBar {
height: 4rpx;
}
.diy-audio>>>.uni-video-container {
background-color: transparent;
}
</style>

View File

@@ -0,0 +1,777 @@
<template>
<x-skeleton data-component-name="diy-bargain" :type="skeletonType" :loading="loading" :configs="skeletonConfig">
<view class="diy-bargain" :class="[value.template, value.style]" :style="warpCss">
<!-- 商品头部 -->
<view v-if="value.titleStyle.isShow && list && list.length"
:class="[value.titleStyle.style, 'bargain-head']"
:style="{ backgroundImage: 'url(' + $util.img(value.titleStyle.backgroundImage) + '), linear-gradient(to right,' + value.titleStyle.bgColorStart + ',' + value.titleStyle.bgColorEnd + ')' }">
<view v-if="value.titleStyle.leftStyle == 'text'" class="left-text"
:style="{ fontSize: value.titleStyle.fontSize * 2 + 'rpx', color: value.titleStyle.textColor, fontWeight: value.titleStyle.fontWeight ? 'bold' : '' }">
{{ value.titleStyle.leftText }}
</view>
<image v-else class="left-img" :src="$util.img(value.titleStyle.leftImg)" mode="heightFix"></image>
<view class="head-content" v-if="value.titleStyle.style == 'style-1'"
:style="{ color: value.titleStyle.textColor }">低至0元免费拿</view>
<view class="head-right"
:style="{ fontSize: value.titleStyle.moreFontSize * 2 + 'rpx', color: value.titleStyle.moreColor }"
@tap.stop="$util.redirectTo('/pages_promotion/bargain/list')">
<text>{{ value.titleStyle.more }}</text>
<text class="iconfont icon-right"></text>
</view>
</view>
<!-- 商品列表 -->
<template v-if="value.template == 'row1-of1'">
<view class="item" v-for="(item, index) in list" :key="index" @tap.stop="toDetail(item)"
:class="[value.ornament.type]" :style="goodsItemCss">
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix"
@error="imageError(index)" />
</view>
<view class="content"
v-if="value.goodsNameStyle.control || value.priceStyle.mainControl || value.btnStyle.control">
<view v-if="value.goodsNameStyle.control" class="goods-name"
:style="{ color: value.theme == 'diy' ? value.goodsNameStyle.color : '', fontWeight: value.goodsNameStyle.fontWeight ? 'bold' : '' }"
:class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]">
{{ isEnEnv ? item.en_goods_name : item.goods_name }}
</view>
<view class="progress" v-if="value.style == 'style-2'">
<view class="bg">
<view class="curr" :style="{ width: progress(item) * 2 + 'rpx' }">
<image class="progress-bar" mode="widthFix"
:src="$util.img('public/uniapp/bargain/progress_bar_01.png')" />
</view>
</view>
<view class="num" v-if="item.is_bargaining">
已砍
<text>{{ (item.price - item.curr_price).toFixed(2) }}</text>
仅差
<text>{{ item.curr_price }}</text>
</view>
<view class="num" v-else>
最低可砍至
<text>{{ item.floor_price }}</text>
</view>
</view>
<view class="progress" v-if="value.style == 'style-3'">
最低可砍至
<text class="num">{{ item.floor_price }}</text>
</view>
<view class="price-wrap">
<view class="discount-price" v-if="value.priceStyle.mainControl">
<text class="unit price-style small"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">¥</text>
<text class="price price-style large"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">{{
item.price.split('.')[0] }}</text>
<text class="unit price-style small"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">.{{
item.price.split('.')[1] }}</text>
</view>
<button v-if="value.btnStyle.control" :style="{
background: value.btnStyle.theme == 'diy' ? 'linear-gradient(to right,' + value.btnStyle.bgColorStart + ',' + value.btnStyle.bgColorEnd + ')' : '',
color: value.btnStyle.theme == 'diy' ? value.btnStyle.textColor : '',
borderRadius: value.btnStyle.aroundRadius * 2 + 'rpx'
}">
{{ item.is_bargaining ? '继续砍价' : value.btnStyle.text }}
</button>
</view>
</view>
</view>
</template>
<template v-if="value.template == 'horizontal-slide'">
<scroll-view v-if="value.slideMode == 'scroll'" class="scroll" :scroll-x="true" :show-scrollbar="false">
<view class="item" v-for="(item, index) in list" :key="index" @tap.stop="toDetail(item)"
:class="[value.ornament.type]" :style="goodsItemCss">
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix"
@error="imageError(index)" :lazy-load="true" />
<image class="bg"
v-if="value.saleStyle.control && value.template == 'horizontal-slide' && value.style != 'style-2'"
:src="$util.img('public/uniapp/bargain/bg.png')" mode="widthFix" />
<view class="num"
v-if="value.saleStyle.control && value.template == 'horizontal-slide' && value.style != 'style-2'"
:style="{ color: value.theme == 'diy' ? value.saleStyle.color : '' }">
已砍{{ item.sale_num }}
</view>
</view>
<view :class="['content', { 'multi-content': value.nameLineMode == 'multiple' }]"
v-if="value.goodsNameStyle.control || value.priceStyle.mainControl || value.priceStyle.lineControl">
<view v-if="value.goodsNameStyle.control" class="goods-name"
:style="{ color: value.theme == 'diy' ? value.goodsNameStyle.color : '', fontWeight: value.goodsNameStyle.fontWeight ? 'bold' : '' }"
:class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]">
{{ isEnEnv ? item.en_goods_name : item.goods_name }}
</view>
<view class="discount-price"
v-if="value.priceStyle.mainControl && value.template == 'horizontal-slide' && value.style != 'style-2'">
<text class="unit price-style small"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">¥</text>
<text class="price price-style large"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">{{
item.floor_price.split('.')[0] }}</text>
<text class="unit price-style small"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">{{
'.' + item.floor_price.split('.')[1] }}</text>
</view>
<view class="original-price price-font" v-if="value.priceStyle.lineControl"
:style="{ color: value.theme == 'diy' ? value.priceStyle.lineColor : '' }">
¥{{ item.price }}
</view>
</view>
</view>
</scroll-view>
<swiper v-if="value.slideMode == 'slide'" :autoplay="false" class="swiper"
:style="{ height: swiperHeight }">
<swiper-item v-for="(pageItem, pageIndex) in page" :key="pageIndex"
:class="['swiper-item', (list.length && [list[pageIndex].length / 3] >= 1) && 'flex-between']">
<view class="item" v-for="(item, dataIndex) in list[pageIndex]" :key="dataIndex"
@tap.stop="toDetail(item)" :class="[value.ornament.type]" :style="goodsItemCss">
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix"
@error="imageError(dataIndex)" :lazy-load="true" />
<image class="bg"
v-if="value.saleStyle.control && value.template == 'horizontal-slide' && value.style != 'style-2'"
:src="$util.img('public/uniapp/bargain/bg.png')" mode="widthFix" />
<view class="num"
v-if="value.saleStyle.control && value.template == 'horizontal-slide' && value.style != 'style-2'"
:style="{ color: value.theme == 'diy' ? value.saleStyle.color : '' }">
已砍{{ item.sale_num }}
</view>
</view>
<view :class="['content', { 'multi-content': value.nameLineMode == 'multiple' }]"
v-if="value.goodsNameStyle.control || value.priceStyle.mainControl || value.priceStyle.lineControl">
<view v-if="value.goodsNameStyle.control" class="goods-name"
:style="{ color: value.theme == 'diy' ? value.goodsNameStyle.color : '', fontWeight: value.goodsNameStyle.fontWeight ? 'bold' : '' }"
:class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]">
{{ isEnEnv ? item.en_goods_name : item.goods_name }}
</view>
<view class="discount-price"
v-if="value.priceStyle.mainControl && value.template == 'horizontal-slide' && value.style != 'style-2'">
<text class="unit price-style small"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">¥</text>
<text class="price price-style large"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">{{
item.floor_price.split('.')[0] }}</text>
<text class="unit price-style small"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">{{
'.' + item.floor_price.split('.')[1] }}</text>
</view>
<view class="original-price price-font" v-if="value.priceStyle.lineControl"
:style="{ color: value.theme == 'diy' ? value.priceStyle.lineColor : '' }">
¥{{ item.price }}
</view>
</view>
</view>
</swiper-item>
</swiper>
</template>
</view>
</x-skeleton>
</template>
<script>
import DiyMinx from './minx.js'
// 砍价商品
export default {
name: 'diy-bargain',
props: {
value: {
type: Object
}
},
mixins: [DiyMinx],
data() {
return {
list: [],
page: 1,
loading: true,
skeletonType: '',
skeletonConfig: {}
};
},
components: {},
async created() {
this.initSkeleton();
if (this.value.template == 'row1-of1' && this.value.style == 'style-2') await this.getDataing();
this.getData();
},
watch: {
// 组件刷新监听
componentRefresh: async function (nval) {
if (this.value.template == 'row1-of1' && this.value.style == 'style-2') await this.getDataing();
this.getData();
}
},
computed: {
warpCss() {
var obj = '';
if (this.value.componentBgColor) obj += 'background:' + this.value.componentBgColor + ';';
if (this.value.componentAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
}
return obj;
},
// 商品项样式
goodsItemCss() {
var obj = '';
obj += 'background-color:' + this.value.elementBgColor + ';';
if (this.value.elementAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
}
if (this.value.ornament.type == 'shadow') {
obj += 'box-shadow:' + '0 0 10rpx ' + this.value.ornament.color + ';';
}
if (this.value.ornament.type == 'stroke') {
obj += 'border:' + '2rpx solid ' + this.value.ornament.color + ';';
}
const screenWidth = uni.getSystemInfoSync().windowWidth;
if (this.value.template == 'horizontal-slide') {
var width = '';
if (this.value.slideMode == 'scroll' && this.value.goodsMarginType == 'diy') width = this.rpxUpPx(this
.value.goodsMarginNum * 2);
else width = [screenWidth - this.rpxUpPx(20) * 2 - this.rpxUpPx(200) * 3 - this.rpxUpPx(this.value
.margin.both * 2) * 2] / 6;
obj += 'margin-left:' + width + 'px;';
obj += 'margin-right:' + width + 'px;';
}
return obj;
},
swiperHeight() {
if (this.value.nameLineMode == 'multiple') {
if (this.value.ornament.type == 'shadow') return '420rpx';
else return '400rpx';
}
if (this.value.ornament.type == 'shadow') return '386rpx';
else return '378rpx';
}
},
methods: {
initSkeleton() {
if (this.value.template == 'row1-of1') {
// 单列 风格
this.skeletonType = 'list';
this.skeletonConfig = {
textRows: 2
};
} else if (this.value.template == 'horizontal-slide') {
// 横向滑动 风格
this.skeletonType = 'waterfall';
this.skeletonConfig = {
gridRows: 1,
gridColumns: 3,
headHeight: '200rpx',
textRows: 2,
textWidth: ['100%', '80%']
};
}
},
rpxUpPx(res) {
const screenWidth = uni.getSystemInfoSync().windowWidth;
var data = (screenWidth * parseInt(res)) / 750;
return Math.floor(data);
},
// 查找自己参与的砍价
async getDataing() {
var res = await this.$api.sendRequest({
url: '/bargain/api/goods/bargainingList',
data: {},
async: false
});
res.data &&
res.data.forEach((item, index) => {
item.is_bargaining = 1;
});
this.list = res.data || [];
this.loading = false;
},
// 查找可砍价的商品
getData() {
var data = {
num: this.value.count,
is_exclude_bargaining: 1
};
if (this.value.sources == 'diy') {
data.num = 0;
data.id_arr = this.value.goodsId.toString();
}
this.$api.sendRequest({
url: '/bargain/api/goods/lists',
data: data,
success: res => {
if (res.code == 0) {
if (this.value.template == 'row1-of1' && this.value.style == 'style-2') this.list =
this.list.concat(res.data).splice(0, this.value.count);
else this.list = res.data;
// 切屏滚动,每页显示固定数量
if (this.value.template == 'horizontal-slide' && this.value.slideMode == 'slide') {
let size = 3;
let temp = [];
this.page = Math.ceil(this.list.length / size);
for (var i = 0; i < this.page; i++) {
temp[i] = [];
for (var j = i * size; j < this.list.length; j++) {
if (temp[i].length == size) break;
temp[i].push(this.list[j]);
}
}
this.list = temp;
}
}
this.loading = false;
}
});
},
progress(data) {
// 214 表示当前进度条的宽度
let progress = (((parseFloat(data.price) - parseFloat(data.curr_price)) / parseFloat(data.price)) * 214)
.toFixed();
if (progress == 'NaN') {
progress = 0;
}
return progress;
},
toDetail(e) {
this.$util.redirectTo('/pages_promotion/bargain/detail', {
b_id: e.bargain_id
});
},
imageError(index) {
this.list[index].goods_image = this.$util.getDefaultImage().goods;
this.$forceUpdate();
}
}
};
</script>
<style lang="scss">
::v-deep .uni-scroll-view ::-webkit-scrollbar {
/* 隐藏滚动条,但依旧具备可以滚动的功能 */
display: none;
width: 0;
height: 0;
color: transparent;
background: transparent;
}
::v-deep ::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
color: transparent;
background: transparent;
}
scroll-view ::-webkit-scrollbar {
width: 0;
height: 0;
background-color: transparent;
touch-action: none;
}
.diy-bargain {
overflow: hidden;
// 头部
.bargain-head {
&.style-1 {
display: flex;
justify-content: space-between;
align-items: center;
height: 88rpx;
box-sizing: border-box;
padding: 0 20rpx;
background-repeat: no-repeat;
background-size: cover;
margin-bottom: 20rpx;
border-radius: 18rpx 18rpx 0 0;
.left-img {
height: 40rpx;
width: 156rpx;
}
.head-content {
position: relative;
color: #fff;
font-size: $font-size-tag;
margin-right: auto;
margin-left: 20rpx;
line-height: 1;
&::after {
content: '';
position: absolute;
width: 2rpx;
height: 24rpx;
background-color: #fff;
top: 50%;
transform: translateY(-50%);
left: -12rpx;
}
}
.head-right {
display: flex;
align-items: center;
font-size: $font-size-sub;
color: #fff;
}
}
&.style-2 {
display: flex;
justify-content: space-between;
align-items: center;
height: 88rpx;
box-sizing: border-box;
padding: 0 20rpx;
background-repeat: no-repeat;
background-size: cover;
margin-bottom: 20rpx;
border-radius: 18rpx 18rpx 0 0;
.left-img {
height: 40rpx;
width: 156rpx;
}
.head-right {
display: flex;
align-items: center;
justify-content: center;
height: 36rpx;
background: linear-gradient(270deg, #ffbd5b 0%, #fd882e 100%);
border-radius: 24rpx;
padding: 2rpx;
text:nth-child(1) {
position: relative;
left: 6rpx;
transform: scale(0.9);
}
text:nth-child(2) {
padding: 6rpx 4rpx 4rpx;
background-color: #fff;
color: #ffbd5b;
border-radius: 50%;
transform: scale(0.6);
font-weight: bold;
line-height: 1;
}
}
}
}
// 商品列表
&.row1-of1 {
.item {
display: flex;
margin-bottom: 20rpx;
padding: 16rpx;
&.shadow {
margin: 8rpx 8rpx 20rpx 8rpx;
}
&:last-child {
margin-bottom: 0;
padding-bottom: 20rpx;
}
.img-wrap {
width: 200rpx;
height: 200rpx;
>image {
width: 200rpx;
}
}
.content {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 6rpx 0 6rpx 20rpx;
.goods-name {
&.multi-hidden {
line-height: 1.3;
}
}
.price-wrap {
display: flex;
justify-content: space-between;
align-items: center;
}
.discount-price {
white-space: nowrap;
font-weight: bold;
line-height: 1;
.unit {
font-size: $font-size-tag;
margin-right: 4rpx;
color: var(--price-color);
}
.price {
font-size: $font-size-toolbar;
color: var(--price-color);
}
}
button {
margin: 0;
padding: 0 20rpx;
color: var(--btn-text-color);
background-color: $base-color;
color: #fff;
min-width: 112rpx;
height: 52rpx;
line-height: 52rpx;
font-size: $font-size-tag;
font-weight: bold;
}
}
}
&.style-2 {
.discount-price {
position: relative;
}
.progress {
display: flex;
flex-direction: column;
margin-top: 10rpx;
margin-right: 16rpx;
.bg {
margin-left: 6rpx;
width: auto;
height: 20rpx;
border-radius: 20rpx;
background-color: #ffeadb;
position: relative;
&::after {
content: '';
width: 26rpx;
height: 26rpx;
border-radius: 50%;
background-color: #fa1a1a;
position: absolute;
top: 50%;
transform: translateY(-50%);
right: -18rpx;
}
.curr {
width: 0;
height: 20rpx;
border-radius: 20rpx;
background-color: #fa1a1a;
position: relative;
.progress-bar {
position: absolute;
right: -20rpx;
width: 30rpx;
height: 30rpx;
max-width: inherit !important;
max-height: inherit !important;
top: 50%;
transform: translateY(-50%);
z-index: 1;
}
}
}
.num {
font-size: $font-size-tag;
margin-top: 12rpx;
line-height: 1;
text {
color: #fa1a1a;
}
}
}
}
&.style-3 {
.progress {
display: flex;
color: $color-tip;
font-size: $font-size-sub;
.num {
color: var(--price-color);
}
}
.item {
.content {
justify-content: space-around;
}
.img-wrap {
overflow: hidden;
}
}
}
}
&.horizontal-slide {
.scroll {
width: calc(100% - 40rpx);
padding: 20rpx;
line-height: 1;
white-space: nowrap;
.item.shadow {
margin-bottom: 8rpx;
}
}
.flex-between {
justify-content: space-between;
}
.item {
display: inline-block;
width: 200rpx;
overflow: hidden;
box-sizing: border-box;
&:nth-child(3n + 3) {
width: 198rpx;
}
&.shadow {
margin-top: 8rpx;
}
.img-wrap {
width: 200rpx;
height: 200rpx;
position: relative;
overflow: hidden;
margin: 0 auto;
>image {
width: 100%;
}
.bg {
position: absolute;
width: 100%;
height: 60rpx;
bottom: 0;
left: 0;
z-index: 2;
}
.num {
width: 180rpx;
position: absolute;
bottom: 10rpx;
padding-left: 20rpx;
font-size: 20rpx;
line-height: 1;
color: #ffffff;
z-index: 3;
}
}
.content {
padding: 10rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
&.multi-content {
height: 160rpx;
box-sizing: border-box;
}
.goods-name {
line-height: 1.3;
&.multi-hidden {
white-space: break-spaces;
}
}
.discount-price {
white-space: nowrap;
margin-top: auto;
font-weight: bold;
line-height: 1;
.unit {
font-size: $font-size-tag;
margin-right: 4rpx;
color: var(--price-color);
}
.price {
font-size: $font-size-toolbar;
color: var(--price-color);
}
}
.original-price {
margin-top: 4rpx;
font-size: $font-size-tag;
color: $color-tip;
line-height: 1;
text-decoration: line-through;
}
}
}
.swiper {
padding: 20rpx;
width: 100%;
white-space: nowrap;
box-sizing: border-box;
.swiper-item {
display: flex;
align-items: center;
}
.item {
width: 200rpx;
box-sizing: border-box;
}
}
}
}
</style>

View File

@@ -0,0 +1,318 @@
<template>
<view data-component-name="diy-bottom-nav" v-if="tabBarList && tabBarList.list">
<view class="tab-bar" :style="{ backgroundColor: tabBarList.backgroundColor }">
<view class="tabbar-border"></view>
<view class="item" v-for="(item, index) in tabBarList.list" :key="item.id" @tap.stop="redirectTo(item.link)">
<view class="bd">
<block v-if="item.link.wap_url == '/pages_goods/cart'">
<view class="icon" v-if="tabBarList.type == 1 || tabBarList.type == 2"
:animation="cartAnimation" id="tabbarCart">
<block v-if="verify(item.link)">
<image v-if="item.selected_icon_type == 'img'"
:src="$util.img(item.selectedIconPath)" />
<diy-icon v-if="item.selected_icon_type == 'icon'" :icon="item.selectedIconPath"
:value="item.selected_style ? item.selected_style : null"></diy-icon>
</block>
<block v-else>
<image v-if="item.icon_type == 'img'" :src="$util.img(item.iconPath)" />
<diy-icon v-if="item.icon_type == 'icon'" :icon="item.iconPath"
:value="item.style ? item.style : null"></diy-icon>
</block>
<view class="cart-count-mark font-size-activity-tag"
:class="{ max: item.link.wap_url == '/pages_goods/cart' && cartNumber > 99 }"
:style="{ background: 'var(--price-color)' }" v-if="cartNumber > 0">
{{ cartNumber > 99 ? '99+' : cartNumber }}
</view>
</view>
</block>
<block v-else>
<view class="icon" v-if="tabBarList.type == 1 || tabBarList.type == 2">
<block v-if="verify(item.link)">
<image v-if="item.selected_icon_type == 'img'"
:src="$util.img(item.selectedIconPath)" />
<diy-icon v-if="item.selected_icon_type == 'icon'" :icon="item.selectedIconPath"
:value="item.selected_style ? item.selected_style : null"></diy-icon>
</block>
<block v-else>
<image v-if="item.icon_type == 'img'" :src="$util.img(item.iconPath)" />
<diy-icon v-if="item.icon_type == 'icon'" :icon="item.iconPath"
:value="item.style ? item.style : null"></diy-icon>
</block>
</view>
</block>
<view class="label"
v-if="(tabBarList.type == 1 || tabBarList.type == 3) && tabBarList.theme == 'diy'"
:style="{ color: verify(item.link) ? tabBarList.textHoverColor : tabBarList.textColor }">
{{ lang == 'en-us' ? item.en_text : item.text }}
</view>
<view class="label"
v-if="(tabBarList.type == 1 || tabBarList.type == 3) && tabBarList.theme == 'default'"
:style="{ color: verify(item.link) ? 'var(--base-color)' : '#333333' }">
{{ lang == 'en-us' ? item.en_text : item.text }}
</view>
</view>
</view>
</view>
<!-- 解决fixed定位后底部导航栏塌陷问题 -->
<view class="tab-bar-placeholder"></view>
</view>
</template>
<script>
import { adaptSubpackageUrl, checkTabBarActive } from '@/common/js/util.js'
import DiyMinx from './minx.js'
// 底部导航栏
export default {
name: 'diy-bottom-nav',
props: {
value: {
type: Object
},
name: {
type: String,
default: ''
}
},
mixins: [DiyMinx],
data() {
return {
lang: uni.getStorageSync("lang"),
currentRoute: '', //当前页面路径
jumpFlag: true, //是否可以跳转,防止重复点击
cartAnimation: {}
};
},
mounted() {
this.updateCurrentRoute();
this.$nextTick(() => {
if (!this.$store.state.cartPosition) {
let query = uni.createSelectorQuery().in(this);
query.select('#tabbarCart')
.boundingClientRect(data => {
if (data) this.$store.commit('setCartPosition', data);
}).exec();
query.select('.tab-bar')
.boundingClientRect(data => {
if (data) this.$store.commit('setTabBarHeight', data.height +
'px');
}).exec();
}
});
// 监听页面显示事件,更新当前路由
this.$on('hook:onShow', () => {
this.updateCurrentRoute();
});
},
computed: {
cartChange() {
return this.$store.state.cartChange;
}
},
watch: {
cartChange: function (nval, oval) {
if (nval > oval) {
let animation = uni.createAnimation({
duration: 200,
timingFunction: 'ease'
});
animation.scale(1.2).step();
this.cartAnimation = animation.export();
setTimeout(() => {
animation.scale(1).step();
this.cartAnimation = animation.export();
}, 300);
}
}
},
methods: {
// 更新当前路由
updateCurrentRoute() {
let currentPage = getCurrentPages()[getCurrentPages().length - 1];
if (currentPage && currentPage.route) {
this.currentRoute = currentPage.route;
}
},
redirectTo(link) {
this.$emit('callback');
this.$util.diyRedirectTo(link);
},
verify(link) {
if (link == null || link == '' || !link.wap_url) return false;
// 标准化路径格式,确保比较的一致性
let currentPageRoute = this.currentRoute ? '/' + this.currentRoute : '';
let linkUrl = link.wap_url;
// 首页特殊处理
if (linkUrl === this.$util.INDEX_PAGE_URL && this.name === 'DIY_VIEW_INDEX') {
return true;
}
// 精确匹配当前路径
if (adaptSubpackageUrl(linkUrl) === currentPageRoute) {
return true;
}
return checkTabBarActive(linkUrl, currentPageRoute);
}
}
};
</script>
<style lang="scss">
.placeholder {
height: 112rpx;
&.bluge {
height: 180rpx;
}
}
.safe-area {
padding-bottom: 0;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
.tab-bar {
background-color: #fff;
box-sizing: border-box;
position: fixed;
left: 0;
bottom: 0;
width: 100%;
z-index: 998;
display: flex;
border-top: 2rpx solid #f5f5f5;
padding-bottom: 0;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
.tabbar-border {
background-color: rgba(255, 255, 255, 0.329412);
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 2rpx;
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
}
.item {
display: flex;
align-items: center;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
flex: 1;
flex-direction: column;
padding-bottom: 10rpx;
box-sizing: border-box;
.bd {
position: relative;
height: 100rpx;
flex-direction: column;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
.icon {
position: relative;
display: inline-block;
margin-top: 10rpx;
width: 40rpx;
height: 40rpx;
font-size: 40rpx;
line-height: 40rpx;
image {
width: 100%;
height: 100%;
}
>view {
height: inherit;
display: flex;
align-items: center;
}
.bar-icon {
font-size: 42rpx;
}
}
.label {
position: relative;
text-align: center;
font-size: 24rpx;
line-height: 1;
margin-top: 12rpx;
}
}
&.bulge {
.bd {
position: relative;
height: 100rpx;
flex-direction: column;
text-align: center;
.icon {
margin-top: -60rpx;
margin-bottom: 4rpx;
border-radius: 50%;
width: 100rpx;
height: 102rpx;
padding: 10rpx;
border-top: 2rpx solid #f5f5f5;
background-color: #fff;
box-sizing: border-box;
image {
width: 100%;
height: 100%;
border-radius: 50%;
}
}
.label {
position: relative;
text-align: center;
font-size: 24rpx;
height: 40rpx;
line-height: 40rpx;
}
}
}
.cart-count-mark {
position: absolute;
top: -8rpx;
right: -18rpx;
width: 24rpx;
height: 24rpx !important;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
padding: 6rpx;
border-radius: 50%;
z-index: 99;
&.max {
width: 40rpx;
border-radius: 24rpx;
right: -28rpx;
}
}
}
}
.tab-bar-placeholder {
padding-bottom: calc(constant(safe-area-inset-bottom) + 112rpx);
padding-bottom: calc(env(safe-area-inset-bottom) + 112rpx);
}
</style>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,645 @@
<template>
<view class="channel-list-container" :style="[componentStyle, { '--row-count': value.rowCount }]">
<!-- 轮播模式 -->
<swiper v-if="value.showStyle == 'carousel' && carouselConfig.type != 'hide'" :indicator-dots="isIndicatorDots"
:autoplay="carouselConfig.autoplay || false" :interval="carouselConfig.interval || 3000"
:duration="carouselConfig.duration || 500" :circular="carouselConfig.circular || false"
:style="swiperHeight" class="channel-swiper">
<swiper-item v-for="(slide, slideIndex) in swiperSlides" :key="slideIndex">
<view :class="['swiper-slide-content', 'row1-of' + value.rowCount]">
<view v-for="(item, index) in slide" :key="index" :class="['channel-item', value.mode]">
<!-- 视频号视频卡片轮播模式 -->
<diy-channel-video :value="item" @video-play="playVideo" :list-mode="true"
:title-line-clamp="value.titleLineClamp" :show-play-btn="value.showPlayBtn"
:show-view-count="value.showViewCount" :cover-style="computedCoverStyle"
:play-btn-style="value.playBtnStyle" :aspect-ratio="value.aspectRatio" />
</view>
</view>
</swiper-item>
</swiper>
<!-- 固定布局模式 -->
<view v-else-if="value.showStyle == 'fixed'" :class="['channel-list', 'row1-of' + value.rowCount]">
<view v-for="(item, index) in value.list" :key="index" class="channel-item">
<!-- 视频号视频卡片列表模式 -->
<diy-channel-video :value="item" @video-play="playVideo" :list-mode="true"
:title-line-clamp="value.titleLineClamp" :show-play-btn="value.showPlayBtn"
:show-view-count="value.showViewCount" :cover-style="computedCoverStyle"
:play-btn-style="value.playBtnStyle" :aspect-ratio="value.aspectRatio" />
</view>
</view>
<!-- 其他布局模式如滚动布局 -->
<scroll-view v-else
:class="['channel-nav', value.showStyle == 'fixed' ? 'fixed-layout' : 'singleSlide', 'row1-of' + value.rowCount]"
:scroll-x="true"
:scroll-y="false"
:enhanced="true"
:bounces="false">
<view class="uni-scroll-view-content">
<view v-for="(item, index) in value.list" :key="index"
:class="['channel-nav-item', value.mode, 'row1-of' + value.rowCount]">
<!-- 视频号视频卡片滚动模式 -->
<diy-channel-video :value="item" @video-play="playVideo" :list-mode="true"
:title-line-clamp="value.titleLineClamp" :show-play-btn="value.showPlayBtn"
:show-view-count="value.showViewCount" :cover-style="computedCoverStyle"
:play-btn-style="value.playBtnStyle" :aspect-ratio="value.aspectRatio" />
</view>
</view>
</scroll-view>
</view>
</template>
<script>
/**
* 微信视频号列表组件
* 支持多种布局模式,包括固定布局和滚动布局
* 可配置列数、视频高度、标题行数等属性
* 用于在页面中展示多个微信视频号视频
*/
import DiyMinx from './minx.js'
import { wechatChannelUtil, wechatChannelConfig } from './js/wechat-channel.js'
export default {
name: 'diy-channel-list',
props: {
/**
* 组件配置数据
* @type {Object}
* @default () => ({})
* @property {string} showStyle - 显示样式可选值carousel, fixed, singleSlide
* @property {number} rowCount - 每行显示的视频数量
* @property {Array} list - 视频列表数据
* @property {string} componentBgColor - 组件背景色
* @property {string} componentAngle - 组件圆角类型可选值round
* @property {number} topAroundRadius - 顶部圆角半径
* @property {number} bottomAroundRadius - 底部圆角半径
* @property {Object} ornament - 装饰效果配置
* @property {boolean} showViewCount - 是否显示播放量可选值true, false
* @property {number} titleLineClamp - 标题显示行数
* @property {string} aspectRatio - 视频比例可选值16:9, 3:4
* @property {boolean} showPlayBtn - 是否显示播放按钮
* @property {Object} coverStyle - 视频封面图样式
* @property {Object} playBtnStyle - 播放按钮样式
* @property {string} mode - 显示模式, 可选值video, text, image, graphic
* @property {number} imageSize - 图片尺寸(仅在特定模式下使用)
* @property {number} pageCount - 每页显示的视频数量
* @property {Object} carousel - 轮播配置
* @property {string} carousel.type - 轮播类型可选值default, hide
* @property {boolean} carousel.autoplay - 是否自动播放默认false
* @property {number} carousel.interval - 自动播放间隔默认3000ms
* @property {number} carousel.duration - 切换动画时长默认500ms
* @property {boolean} carousel.circular - 是否循环播放默认false
*/
value: {
type: Object,
default: () => ({})
}
},
mixins: [DiyMinx],
data() {
return {
pageWidth: '', // 页面宽度
indicatorDots: false, // 是否显示轮播指示器
swiperCurrent: 0 // 当前轮播索引
}
},
created() {
// 组件创建时的逻辑
// 可以在这里进行初始化操作,如获取页面宽度等
},
mounted() {
// 组件挂载后添加鼠标拖拽滚动功能
if (!['fixed', 'carousel'].includes(this.value?.showStyle)) {
this.$nextTick(() => {
this.addMouseDragScroll();
});
}
},
watch: {
/**
* 组件刷新监听
* 当组件需要刷新时触发
* @param {*} newValue - 新值
*/
componentRefresh(newValue) {
// 监听组件刷新
// 可以在这里处理组件刷新时的逻辑
}
},
computed: {
carouselConfig() {
return this.value?.carousel || {
type: 'default',
autoplay: false,
interval: 3000,
duration: 500,
circular: true
}
},
/**
* 组件样式
* 根据配置动态生成样式字符串,包括背景色、圆角、阴影和边框
* @returns {string} 样式字符串
*/
componentStyle() {
const style = {};
// 背景色
if (this.value?.componentBgColor) {
style.backgroundColor = this.value?.componentBgColor;
}
// 圆角样式
if (this.value?.componentAngle == 'round') {
style.borderTopLeftRadius = (2 * this.value?.topAroundRadius) + 'rpx';
style.borderTopRightRadius = (2 * this.value?.topAroundRadius) + 'rpx';
style.borderBottomLeftRadius = (2 * this.value?.bottomAroundRadius) + 'rpx';
style.borderBottomRightRadius = (2 * this.value?.bottomAroundRadius) + 'rpx';
}
// 装饰效果:阴影
style.boxShadow = this.value?.ornament?.type == 'shadow' ? '0 0 10rpx ' + this.value?.ornament?.color : '';
// 装饰效果:边框
style.border = this.value?.ornament?.type == 'stroke' ? '2rpx solid ' + this.value?.ornament?.color : '';
return style;
},
/**
* 轮播高度
* 根据模式和配置计算轮播高度
* @returns {string} 轮播高度样式
*/
swiperHeight() {
let height = 0;
const pageCount = this.value?.pageCount || 1;
// 根据不同模式计算高度
if (this.value?.mode == 'graphic') {
height = (49 + this.value?.imageSize) * pageCount;
} else if (this.value?.mode == 'img') {
height = (22 + this.value?.imageSize) * pageCount;
} else if (this.value?.mode == 'text') {
height = 43 * pageCount;
} else { // 默认视频模式
if (this.value?.aspectRatio == '16:9') {
height = [250, 160, 140, 120][this.value.rowCount - 1];
} else {
height = [510, 280, 220, 180][this.value.rowCount - 1];
}
}
return 'height:' + (2 * height) + 'rpx';
},
/**
* 是否显示指示器
* 根据轮播配置和列表长度判断是否显示指示器
* @returns {boolean} 是否显示指示器
*/
isIndicatorDots() {
// 当轮播类型不是隐藏,且视频数量超过一页时显示指示器
const pageCount = this.value?.pageCount || 1;
return this.value?.carousel?.type != 'hide' && Math.ceil(this.value?.list?.length / (pageCount * this.value?.rowCount)) > 1
},
/**
* 轮播幻灯片数据
* 将视频列表分割成轮播所需的幻灯片数据
* @returns {Array} 轮播幻灯片数据
*/
swiperSlides() {
const slides = [];
const list = this.value?.list || [];
const pageSize = (this.value?.pageCount || 1) * (this.value?.rowCount || 1);
// 将列表数据分割成每页显示的数量
for (let i = 0; i < list.length; i += pageSize) {
slides.push(list.slice(i, i + pageSize));
}
return slides;
},
/**
* 视频封面样式
* 根据aspectRatio属性动态计算封面样式
* @returns {Object} 封面样式对象
*/
computedCoverStyle() {
const aspectRatio = this.value?.aspectRatio || '16:9';
let paddingTop = '56.25%'; // 默认 16:9 比例
if (aspectRatio === '3:4') {
paddingTop = '133.33%'; // 3:4 比例
}
return {
width: '100%',
height: '0',
paddingTop: paddingTop
};
}
},
methods: {
/**
* 播放视频
* 触发 video-play 事件,并在微信小程序中调用视频播放接口
* @param {Object} item - 视频数据对象
*/
async playVideo(item) {
await this.__$emitEvent({
eventName: 'video-play',
data: item,
promiseCallback: async (event, handler, awaitedResult) => {
if (!awaitedResult) return;
try {
// 发送视频被点击播放事件
this.$emit('channel-video-click-play', item);
console.log('播放视频:', item);
// #ifdef MP-WEIXIN
// 在微信小程序环境中调用视频播放接口
await wechatChannelUtil.playVideo(item);
// #endif
// #ifndef MP-WEIXIN
if (item?.channelType === 'wechat') {
uni.showToast({
title: '视频号仅支持在微信小程序环境中播放',
icon: 'none',
duration: 2000
});
} else {
// 在非微信小程序环境中,直接触发事件
this.$emit('video-play', item);
}
// #endif
} catch (err) {
console.error('打开视频号失败', err);
}
}
})
},
/**
* 图片加载错误处理
* 当图片加载失败时,设置默认图片
* @param {number} index - 图片索引
*/
imgError(index) {
// 图片加载失败的处理逻辑
console.log('图片加载失败:', index);
// 为失败的图片设置默认图片
const item = this.value.list[index];
if (item) {
// 使用默认图片替代加载失败的图片
// #ifdef MP-WEIXIN
item.coverUrl = wechatChannelConfig.video.defaultCoverUrl;
// #endif
}
},
/**
* 添加鼠标拖拽滚动功能
* 在Web环境中实现与微信小程序相同的拖拽滚动效果
*/
addMouseDragScroll() {
// 只在Web环境中添加
// #ifndef MP
console.log('addMouseDragScroll called');
let isDragging = false;
let startX = 0;
let startScrollLeft = 0;
let currentScrollElement = null;
// 查找最近的可滚动祖先元素
const findScrollableParent = (element) => {
while (element && element !== document) {
const style = window.getComputedStyle(element);
if (style.overflowX === 'auto' || style.overflowX === 'scroll') {
return element;
}
element = element.parentElement;
}
return null;
};
// 鼠标按下事件
const handleMouseDown = (e) => {
// 检查是否点击在组件内
if (this.$el.contains(e.target)) {
console.log('mousedown event in component:', e);
// 查找可滚动元素
currentScrollElement = findScrollableParent(e.target);
if (currentScrollElement) {
console.log('Found scrollable element:', currentScrollElement);
isDragging = true;
startX = e.pageX;
startScrollLeft = currentScrollElement.scrollLeft;
currentScrollElement.style.cursor = 'grabbing';
}
}
};
// 鼠标移动事件
const handleMouseMove = (e) => {
if (!isDragging || !currentScrollElement) return;
console.log('mousemove event:', e);
e.preventDefault();
const dx = e.pageX - startX;
currentScrollElement.scrollLeft = startScrollLeft - dx;
console.log('scrollLeft:', currentScrollElement.scrollLeft);
};
// 鼠标释放事件
const handleMouseUp = () => {
if (isDragging && currentScrollElement) {
console.log('mouseup event');
currentScrollElement.style.cursor = 'grab';
}
isDragging = false;
currentScrollElement = null;
};
// 添加全局事件监听器
document.addEventListener('mousedown', handleMouseDown);
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
document.addEventListener('mouseleave', handleMouseUp);
console.log('Global mouse event listeners added');
// 组件销毁时移除事件监听器
this.$once('hook:beforeDestroy', () => {
document.removeEventListener('mousedown', handleMouseDown);
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
document.removeEventListener('mouseleave', handleMouseUp);
console.log('Global mouse event listeners removed');
});
// 为所有.channel-nav元素添加必要的样式
setTimeout(() => {
const channelNavs = document.querySelectorAll('.channel-nav');
console.log('Found channel-nav elements:', channelNavs.length);
channelNavs.forEach(element => {
element.style.overflowX = 'auto';
element.style.overflowY = 'hidden';
element.style.whiteSpace = 'nowrap';
element.style.width = '100%';
element.style.maxWidth = '100%';
element.style.cursor = 'grab';
element.style.userSelect = 'none'; // 防止文本选择
console.log('Added styles to channel-nav:', element);
});
}, 100); // 延迟执行确保DOM已完全渲染
console.log('Mouse drag scroll setup completed');
// #endif
}
}
}
</script>
<style lang="scss" scoped>
@import './css/common-channel.scss';
.channel-list-container {
padding: 16px 16px 0px;
}
/**
* 列表布局样式
*/
.channel-list {
display: grid;
grid-template-columns: repeat(var(--row-count, 2), 1fr);
gap: 8px;
}
/**
* 导航布局样式
* 支持固定布局和滚动布局
*/
.channel-nav {
box-sizing: border-box;
padding: 16rpx;
// 确保在H5环境中可以水平滚动
overflow-x: auto;
white-space: nowrap;
// 隐藏滚动条但保留滚动功能
::-webkit-scrollbar {
display: none;
}
scrollbar-width: none;
-ms-overflow-style: none;
// 启用触摸滚动和鼠标拖拽滚动
-webkit-overflow-scrolling: touch;
scroll-behavior: smooth;
.uni-scroll-view-content {
/* #ifdef MP-WEIXIN */
display: flex;
flex-wrap: nowrap;
gap: 16rpx;
/* #endif */
/* #ifndef MP-WEIXIN */
display: grid;
grid-template-columns: repeat(var(--row-count, 2), 1fr);
gap: 16rpx;
/* #endif */
}
// 单滑动模式
&.singleSlide {
.uni-scroll-view-content {
display: flex;
flex-wrap: nowrap;
gap: 16rpx;
white-space: nowrap;
}
.channel-nav-item {
flex-shrink: 0;
width: 280rpx;
}
}
.channel-nav-item {
display: flex;
flex-direction: column;
box-sizing: border-box;
/* #ifdef MP-WEIXIN */
&.row1-of1 {
width: 100%;
}
&.row1-of2 {
width: calc(50% - 8rpx);
}
&.row1-of3 {
width: calc(33.333% - 10.67rpx);
}
&.row1-of4 {
width: calc(25% - 12rpx);
}
/* #endif */
}
// 1 列布局
&.row1-of1 {
.uni-scroll-view-content {
// #ifndef MP-WEIXIN
grid-template-columns: 1fr;
// #endif
}
}
// 2 列布局
&.row1-of2 {
.uni-scroll-view-content {
// #ifndef MP-WEIXIN
grid-template-columns: repeat(2, 1fr);
// #endif
}
}
// 3 列布局
&.row1-of3 {
.uni-scroll-view-content {
// #ifndef MP-WEIXIN
grid-template-columns: repeat(3, 1fr);
// #endif
}
}
// 4 列布局
&.row1-of4 {
.uni-scroll-view-content {
// #ifndef MP-WEIXIN
grid-template-columns: repeat(4, 1fr);
// #endif
}
}
}
/**
* 确保所有视频卡片高度一致
*/
.channel-item,
.channel-nav-item {
display: flex;
flex-direction: column;
}
/**
* 轮播样式
* 支持轮播模式的布局和样式
*/
.channel-swiper {
width: 100%;
box-sizing: border-box;
.swiper-slide-content {
display: grid;
grid-template-columns: repeat(var(--row-count, 2), 1fr);
gap: 16rpx;
box-sizing: border-box;
.channel-item {
display: flex;
flex-direction: column;
box-sizing: border-box;
}
// 轮播模式下的 1 列布局
&.row1-of1 {
grid-template-columns: 1fr;
}
// 轮播模式下的 2 列布局
&.row1-of2 {
grid-template-columns: repeat(2, 1fr);
}
// 轮播模式下的 3 列布局
&.row1-of3 {
grid-template-columns: repeat(3, 1fr);
}
// 轮播模式下的 4 列布局
&.row1-of4 {
grid-template-columns: repeat(4, 1fr);
}
}
}
/**
* 响应式调整
* 在小屏幕设备上调整布局和间距
*/
@media (max-width: 375px) {
.channel-list,
.channel-nav,
.channel-swiper .swiper-slide-content {
gap: 12rpx;
padding: 12rpx;
}
// 小屏幕上的单滑动模式
.channel-nav {
&.singleSlide {
.uni-scroll-view-content {
gap: 12rpx;
}
.channel-nav-item {
width: 240rpx;
}
}
}
}
/**
* 视频比例样式调整
* 根据不同的视频比例调整布局
*/
// // 3:4 比例的视频卡片样式
// .channel-video.ratio-3-4 {
// // 调整视频卡片的整体高度
// ::v-deep .video-cover-wrap {
// padding-top: 133.33%; // 3:4 比例
// }
// // 列表模式下的3:4比例调整
// &.list-mode {
// ::v-deep .video-cover-wrap {
// padding-top: 133.33%; // 3:4 比例
// }
// }
// }
// // 16:9 比例的视频卡片样式(默认)
// .channel-video.ratio-16-9 {
// // 保持默认的16:9比例
// ::v-deep .video-cover-wrap {
// padding-top: 56.25%; // 16:9 比例
// }
// // 列表模式下的16:9比例保持默认
// &.list-mode {
// ::v-deep .video-cover-wrap {
// padding-top: 56.25%; // 16:9 比例
// }
// }
// }</style>

View File

@@ -0,0 +1,489 @@
<template>
<view class="channel-video" :class="{ 'list-mode': listMode }">
<!-- 嵌入式视频播放 -->
<view v-if="canUseEmbedMode" class="embed-video-container">
<view class="video-cover-wrap" :style="[coverStyle]">
<!-- #ifdef MP-WEIXIN -->
<native-component>
<!-- 嵌入式视频播放组件 -->
<channel-video :feed-id="value.feedId" :finder-user-name="value.finderUserName"
:feed-token="value.feedToken" :auto-play="autoPlay">
</channel-video>
</native-component>
<!-- #endif -->
</view>
<view class="video-info">
<!-- 视频标题支持多行显示控制 -->
<view class="video-title" :style="{ '--title-line-clamp': titleLineClamp }">{{ value.videoTitle }}
</view>
<!-- 视频统计信息 -->
<view class="video-stats" v-if="showViewCount && value.showViewCount">{{ value.viewCount }}次观看</view>
</view>
</view>
<!-- 跳转式视频播放 -->
<view v-else @tap.stop="playVideo" class="video-container">
<view class="video-cover-wrap" :style="[coverStyle]">
<image class="video-cover" :src="$util.img(value.coverUrl)" mode="aspectFill"></image>
<view class="channel-play-btn" v-if="showPlayBtn" :style="[playBtnStyle]">
<view class="play-icon-css"></view>
</view>
<view class="video-stats-overlay" v-if="showViewCount && value.showViewCount">
{{ value.viewCount }}次观看
</view>
</view>
<view class="video-info">
<!-- 视频标题支持多行显示控制 -->
<view class="video-title" :style="{ '--title-line-clamp': titleLineClamp }">{{ value.videoTitle }}
</view>
<!-- 视频统计信息非列表模式下显示 -->
<view class="video-stats" v-if="showViewCount && value.showViewCount && !listMode">{{ value.viewCount }}次观看</view>
</view>
</view>
</view>
</template>
<script>
import { wechatChannelUtil, wechatChannelConfig } from './js/wechat-channel.js'
/**
* 微信视频号视频卡片组件
* 支持嵌入式播放和跳转式播放两种模式
* 可配置列表模式、视频高度、标题行数等属性
*/
export default {
name: 'diy-channel-video',
props: {
/**
* 视频数据对象
* @type {Object}
* @required
* @property {string} feedId - 视频 feedId
* @property {string} feedToken - 视频 token
* @property {string} coverUrl - 视频封面图
* @property {string} videoTitle - 视频标题
* @property {number} viewCount - 观看次数
* @property {boolean} showViewCount - 是否显示观看次数
* @property {boolean} embedMode - 是否启用嵌入式播放
*/
value: {
type: Object,
required: true
},
/**
* 是否为列表模式
* @type {boolean}
* @default false
*/
listMode: {
type: Boolean,
default: false
},
/** 是否显示观看次数,由父组件可以整体配置 */
showViewCount: {
type: Boolean,
default: true
},
/**
* 标题显示行数
* @type {number}
* @default 1
*/
titleLineClamp: {
type: Number,
default: 1
},
/** 是否显示播放按钮 */
showPlayBtn: {
type: Boolean,
default: true
},
/**
* 视频封面图样式
* 采用 16:9 比例的响应式高度
*/
coverStyle: {
type: Object,
default: () => ({
width: '100%',
height: '0',
paddingTop: '56.25%' // 16:9 比例
})
},
/**
* 播放按钮样式
*/
playBtnStyle: {
type: Object,
default: () => ({
width: '60rpx',
height: '60rpx',
borderRadius: '30rpx',
backgroundColor: 'rgba(0, 0, 0, 0.6)'
})
},
/** 是否自动播放 */
autoPlay: {
type: Boolean,
default: false
},
},
computed: {
/**
* 是否支持嵌入式播放
* @returns {boolean}
*/
canUseEmbedMode() {
// #ifdef MP-WEIXIN
const enableEmbedMode = Boolean(this.value?.embedMode)
&& Boolean(this.value?.feedToken)
&& wechatChannelUtil.isEmbedModeSupported();
console.log('enableEmbedMode', enableEmbedMode);
return enableEmbedMode;
// #endif
return false
}
},
methods: {
/**
* 播放视频
* 触发 video-play 事件,由父组件处理具体播放逻辑
*/
async playVideo() {
this.$emit('video-play', this.value);
}
}
};
</script>
<style scoped lang="scss">
@import './css/common-channel.scss';
/**
* 视频卡片容器样式
* 包含卡片基础样式、悬停效果和列表模式样式
*/
.channel-video {
position: relative;
background-color: #fff;
border-radius: 12rpx;
overflow: hidden;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
flex-direction: column;
height: 100%;
// 悬停效果
&:hover {
transform: translateY(-4rpx);
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.15);
}
// 列表模式样式调整
&.list-mode {
border-radius: 10rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.08);
.video-info {
padding: 14rpx;
.video-title {
font-size: 24rpx;
margin-bottom: 6rpx;
}
}
}
}
/**
* 视频容器样式
* 用于跳转式播放模式
*/
.video-container {
display: flex;
flex-direction: column;
height: 100%;
}
/**
* 嵌入式视频容器样式
* 与视频容器样式保持一致
*/
.embed-video-container {
display: flex;
flex-direction: column;
height: 100%;
}
/**
* 视频封面容器
*/
.video-cover-wrap {
position: relative;
width: 100%;
padding-top: 56.25%;
/* 16:9 比例 */
height: 0;
overflow: hidden;
border-radius: 12rpx 12rpx 0 0;
transition: transform 0.3s ease;
// 悬停时缩放效果
.channel-video:hover & {
transform: scale(1.02);
}
// 列表模式下的边框圆角调整
.channel-video.list-mode & {
border-radius: 10rpx 10rpx 0 0;
}
}
/**
* 视频封面图片
* 绝对定位填充整个容器
*/
.video-cover {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
/**
* 视频统计信息遮罩
* 显示在视频封面底部
*/
.video-stats-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
padding: 12rpx 16rpx;
color: #fff;
font-size: 20rpx;
text-align: right;
font-weight: 500;
transition: opacity 0.3s ease;
// 悬停时的透明度变化
.channel-video:hover & {
opacity: 1;
}
.channel-video:not(:hover) & {
opacity: 0.8;
}
}
/**
* 视频信息区域
* 包含标题和统计信息
*/
.video-info {
padding: 16rpx;
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
background-color: #fff;
transition: background-color 0.3s ease;
// 悬停时的背景色变化
.channel-video:hover & {
background-color: #fafafa;
}
}
/**
* 视频标题
* 支持多行显示控制和渐变遮罩效果
*/
.video-title {
font-size: 28rpx;
font-weight: 600;
color: #222;
margin-bottom: 10rpx;
line-height: 1.4;
display: -webkit-box;
-webkit-line-clamp: var(--title-line-clamp, 2);
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
transition: color 0.3s ease;
position: relative;
word-break: break-all;
word-wrap: break-word;
white-space: normal;
// 悬停时的颜色变化
.channel-video:hover & {
color: #000;
}
// 列表模式下的样式调整
.channel-video.list-mode & {
-webkit-line-clamp: var(--title-line-clamp, 1);
font-size: 24rpx;
margin-bottom: 6rpx;
font-weight: 500;
color: #333;
line-height: 1.3;
}
// 添加渐变遮罩效果,让过长的文字有柔和的结束
&::after {
content: '';
position: absolute;
bottom: 0;
right: 0;
width: 40rpx;
height: 1.4em;
background: linear-gradient(to right, transparent, #fff 90%);
pointer-events: none;
}
// 列表模式下的遮罩高度调整
.channel-video.list-mode &::after {
height: 1.3em;
}
// 根据行数调整遮罩高度
&[style*="--title-line-clamp: 1"]::after {
height: 1.4em;
}
&[style*="--title-line-clamp: 3"]::after {
height: 4.2em;
}
&[style*="--title-line-clamp: 4"]::after {
height: 5.6em;
}
// 列表模式下的多行遮罩高度调整
.channel-video.list-mode &[style*="--title-line-clamp: 1"]::after {
height: 1.3em;
}
.channel-video.list-mode &[style*="--title-line-clamp: 3"]::after {
height: 3.9em;
}
.channel-video.list-mode &[style*="--title-line-clamp: 4"]::after {
height: 5.2em;
}
}
/**
* 视频统计信息
* 显示观看次数等数据
*/
.video-stats {
font-size: 22rpx;
color: #999;
margin-top: 6rpx;
transition: color 0.3s ease;
// 悬停时的颜色变化
.channel-video:hover & {
color: #666;
}
}
/**
* 列表模式下的播放按钮样式
* 更小的尺寸和悬停效果
*/
.channel-video.list-mode .channel-play-btn {
width: 50rpx;
height: 50rpx;
background-color: rgba(0, 0, 0, 0.5);
transition: all 0.3s ease;
// 悬停效果
&:hover {
background-color: rgba(0, 0, 0, 0.7);
transform: scale(1.1);
}
// 播放图标尺寸
.play-icon {
width: 25rpx;
height: 25rpx;
}
}
/**
* 通用播放按钮样式优化
* 添加悬停效果
*/
.channel-play-btn {
transition: all 0.3s ease;
// 悬停效果
&:hover {
background-color: rgba(0, 0, 0, 0.6);
transform: scale(1.05);
}
}
/**
* 纯 CSS 播放按钮图标
*/
.play-icon-css {
width: 0;
height: 0;
border-top: 12rpx solid transparent;
border-bottom: 12rpx solid transparent;
border-left: 18rpx solid #fff;
margin-left: 4rpx;
transition: transform 0.3s ease;
// 悬停时的缩放效果
.channel-play-btn:hover & {
transform: scale(1.1);
}
// 列表模式下的尺寸调整
.channel-video.list-mode & {
border-top: 10rpx solid transparent;
border-bottom: 10rpx solid transparent;
border-left: 15rpx solid #fff;
margin-left: 3rpx;
}
}
/**
* 原生组件样式控制
* 确保 native-component 和 channel-video 正确填充容器
*/
native-component {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
}
/**
* channel-video 组件样式控制
* 避免受到 wx-channel-video 全局样式的影响
*/
channel-video {
width: 100%;
height: 100%;
min-width: 100%;
min-height: 100%;
max-width: 100%;
max-height: 100%;
object-fit: cover;
}
</style>

View File

@@ -4,6 +4,7 @@
<script>
//
import DiyMinx from './minx.js'
export default {
name: 'diy-comp-extend',
props: {
@@ -11,11 +12,12 @@ export default {
type: Object
}
},
mixins: [DiyMinx],
data() {
return {};
},
computed: {},
created() {},
created() { },
methods: {}
};
</script>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,261 @@
<template>
<view :style="componentStyle" data-component-name="diy-digit">
<scroll-view :class="['graphic-nav', value.showStyle == 'fixed' ? 'fixed-layout' : value.showStyle]"
:scroll-x="value.showStyle == 'singleSlide'">
<view class="uni-scroll-view-content">
<view v-for="(item, index) in value.list" :key="index"
:class="['graphic-nav-item', value.mode, value.mode === 'text' ? 'newright' : '']"
:style="{ width: (100 / value.rowCount + '%') + ';' }">
<view style="display:flex;">
<view :style="{
'line-height': '1.2;',
'font-size': (value.font.titlesize * 2 + 'rpx') + ';',
'font-weight': '600;',
'color': value.font.titlecolor + ';'
}">
<uv-count-to :ref="`countTo-${index}`" :autoplay="true" :startVal="30" :endVal="item.title"
:decimals="getvalue(item.title)" decimal="."></uv-count-to>
<text :style="{
'margin-left': '4rpx;',
'font-size': (value.font.unitsize * 2 + 'rpx') + ';',
'font-weight': value.font.weight + ';',
'color': value.font.unitcolor + ';'
}">{{ item.unit }}</text>
</view>
</view>
<view class="graphic-text">
<text :style="{
'font-size': (value.font.descsize * 2 + 'rpx') + ';',
'font-weight': value.font.weight + ';',
'color': value.font.desccolor + ';'
}">{{ item.desc }}</text>
</view>
</view>
</view>
</scroll-view>
<ns-login ref="login"></ns-login>
</view>
</template>
<script>
import DiyMinx from './minx.js'
// 自定义数字展示
export default {
name: 'diy-digit',
props: {
value: {
type: Object,
default: () => ({})
}
},
mixins: [DiyMinx],
data() {
return {
pageWidth: '',
indicatorDots: false,
swiperCurrent: 0
}
},
created() {
// 组件创建时的逻辑
},
watch: {
componentRefresh(newValue) {
// 监听组件刷新
}
},
computed: {
componentStyle() {
let style = '';
style += 'background-image:url(' + this.$util.img(this.value.imageUrl) + ');background-size:100% 100%;';
if (this.value.componentAngle == 'round') {
style += 'border-top-left-radius:' + (2 * this.value.topAroundRadius) + 'rpx;';
style += 'border-top-right-radius:' + (2 * this.value.topAroundRadius) + 'rpx;';
style += 'border-bottom-left-radius:' + (2 * this.value.bottomAroundRadius) + 'rpx;';
style += 'border-bottom-right-radius:' + (2 * this.value.bottomAroundRadius) + 'rpx;';
}
style += 'box-shadow:' + (this.value.ornament.type == 'shadow' ? '0 0 10rpx ' + this.value.ornament.color : '') + ';';
style += 'border:' + (this.value.ornament.type == 'stroke' ? '2rpx solid ' + this.value.ornament.color : '') + ';';
return style;
}
},
methods: {
// 获取小数位数
getvalue(value) {
return value % 1 !== 0 ? 2 : 0;
},
// 页面跳转
redirectTo(item) {
if (!item.wap_url || this.$util.getCurrRoute() != this.$util.MEMBER_PAGE_URL || this.storeToken) {
console.log(item);
this.$util.diyRedirectTo(item);
} else {
this.$refs.login.open(item.wap_url);
}
},
// 轮播切换
swiperChange(event) {
this.swiperCurrent = event.detail.current;
}
}
}
</script>
<style lang="scss" scoped>
.graphic-nav {
padding: 16rpx;
box-sizing: border-box;
&.fixed-layout {
.uni-scroll-view-content {
display: flex;
flex-wrap: wrap;
}
}
&.singleSlide {
.uni-scroll-view-content {
display: flex;
}
.graphic-nav-item {
flex-shrink: 0;
}
}
&.pageSlide {
position: relative;
.uni-swiper-dots-horizontal {
bottom: 0rpx;
}
&.straightLine {
.uni-swiper-dot {
width: 30rpx;
border-radius: 0;
height: 8rpx;
}
}
&.circle {
.uni-swiper-dot {
width: 14rpx;
height: 14rpx;
}
}
}
.graphic-nav-wrap {
display: flex;
flex-wrap: wrap;
width: 100%;
height: 100%;
}
.graphic-nav-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 14rpx 0;
box-sizing: border-box;
.graphic-text {
line-height: 1.3;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
width: 100%;
text-align: center;
&.alone {
padding-top: 0;
}
}
&.text {
.graphic-text {
padding-top: 0;
}
}
.graphic-img {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100rpx;
height: 100rpx;
font-size: 90rpx;
.tag {
position: absolute;
top: -10rpx;
right: -24rpx;
color: #fff;
border-radius: 24rpx;
border-bottom-left-radius: 0;
-webkit-transform: scale(0.8);
transform: scale(0.8);
padding: 8rpx 16rpx;
line-height: 1;
font-size: 24rpx;
}
.icon {
font-size: 50rpx;
color: #606266;
}
}
}
&.pageSlide {
.graphic-nav-item {
flex-shrink: 0;
}
}
}
.newright {
margin-right: 16rpx;
}
.swiper-dot-box {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
margin-top: -20rpx;
padding-bottom: 8rpx;
.swiper-dot {
background-color: rgba(0, 0, 0, 0.3);
margin: 8rpx;
&.active {
background-color: #000;
}
}
&.straightLine {
.swiper-dot {
width: 30rpx;
border-radius: 0;
height: 8rpx;
}
}
&.circle {
.swiper-dot {
width: 15rpx;
border-radius: 50%;
height: 15rpx;
}
}
}
</style>

View File

@@ -1,47 +1,50 @@
<template>
<view data-component-name="diy-fenxiao-goods-list" class="diy-fenxiao" v-if="list.length" :class="['goods-list', value.template, value.style]" :style="goodsListWarpCss">
<view class="goods-item" v-for="(item, index) in list" :key="index" @click="toDetail(item)" :class="[value.ornament.type]" :style="goodsItemCss">
<view data-component-name="diy-fenxiao-goods-list" class="diy-fenxiao" v-if="list.length"
:class="['goods-list', value.template, value.style]" :style="goodsListWarpCss">
<view class="goods-item" v-for="(item, index) in list" :key="index" @tap.stop="toDetail(item)"
:class="[value.ornament.type]" :style="goodsItemCss">
<view class="goods-img" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }" :src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix" @error="imgError(index)"/>
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix" @error="imgError(index)" />
</view>
<view class="info-wrap" v-if="value.goodsNameStyle.control || value.priceStyle.mainControl || value.priceStyle.lineControl || value.btnStyle.control">
<view class="info-wrap"
v-if="value.goodsNameStyle.control || value.priceStyle.mainControl || value.priceStyle.lineControl || value.btnStyle.control">
<view class="name-wrap">
<view v-if="value.goodsNameStyle.control" class="goods-name"
:style="{ color: value.theme == 'diy' ? value.goodsNameStyle.color : '', fontWeight: value.goodsNameStyle.fontWeight ? 'bold' : '' }"
:class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]"
>
{{ item.goods_name }}
:class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]">
{{ isEnEnv ? item.en_goods_name : item.goods_name }}
</view>
</view>
<view class="pro-info">
<view class="discount-price">
<view class="price-wrap" v-if="value.priceStyle.mainControl">
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor : '' }"> </text>
<text class="price price-style large" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor : '' }">{{ item.commission_money.split('.')[0] }}</text>
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor : '' }">{{ '.' + item.commission_money.split('.')[1] }}</text>
<text class="unit price-style small"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor : '' }"> </text>
<text class="price price-style large"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor : '' }">{{
item.commission_money.split('.')[0] }}</text>
<text class="unit price-style small"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor : '' }">{{ '.' +
item.commission_money.split('.')[1] }}</text>
</view>
<view class="sale-btn" v-if="value.btnStyle.control && item.is_collect == 0"
:style="{
background: value.btnStyle.theme == 'diy' ? 'linear-gradient(to right,' + value.btnStyle.bgColorStart + ',' + value.btnStyle.bgColorEnd + ')' : '',
color: value.btnStyle.theme == 'diy' ? value.btnStyle.textColor : '',
borderRadius: value.btnStyle.aroundRadius * 2 + 'rpx'
}"
@click.stop="followGoods(item, index)"
>
<view class="sale-btn" v-if="value.btnStyle.control && item.is_collect == 0" :style="{
background: value.btnStyle.theme == 'diy' ? 'linear-gradient(to right,' + value.btnStyle.bgColorStart + ',' + value.btnStyle.bgColorEnd + ')' : '',
color: value.btnStyle.theme == 'diy' ? value.btnStyle.textColor : '',
borderRadius: value.btnStyle.aroundRadius * 2 + 'rpx'
}" @tap.stop="followGoods(item, index)">
关注
</view>
<view class="sale-btn" v-if="value.btnStyle.control && item.is_collect == 1"
:style="{
background: value.btnStyle.theme == 'diy' ? 'linear-gradient(to right,' + value.btnStyle.bgColorStart + ',' + value.btnStyle.bgColorEnd + ')' : '',
color: value.btnStyle.theme == 'diy' ? value.btnStyle.textColor : '',
borderRadius: value.btnStyle.aroundRadius * 2 + 'rpx'
}"
@click.stop="delFollowTip(item, index)"
>
<view class="sale-btn" v-if="value.btnStyle.control && item.is_collect == 1" :style="{
background: value.btnStyle.theme == 'diy' ? 'linear-gradient(to right,' + value.btnStyle.bgColorStart + ',' + value.btnStyle.bgColorEnd + ')' : '',
color: value.btnStyle.theme == 'diy' ? value.btnStyle.textColor : '',
borderRadius: value.btnStyle.aroundRadius * 2 + 'rpx'
}" @tap.stop="delFollowTip(item, index)">
取消关注
</view>
</view>
<view class="delete-price" v-if="value.priceStyle.lineControl" :style="{ color: value.theme == 'diy' ? value.priceStyle.lineColor : '' }">
<view class="delete-price" v-if="value.priceStyle.lineControl"
:style="{ color: value.theme == 'diy' ? value.priceStyle.lineColor : '' }">
{{ item.discount_price }}
</view>
</view>
@@ -51,6 +54,8 @@
</template>
<script>
//
import DiyMinx from './minx.js'
export default {
name: 'diy-fenxiao-goods-list',
props: {
@@ -58,6 +63,7 @@ export default {
type: Object
}
},
mixins: [DiyMinx],
data() {
return {
list: [],
@@ -69,7 +75,7 @@ export default {
this.currentRoute = '/' + currentPage.route;
if (!this.storeToken) {
this.$util.redirectTo(
'/pages_tool/login/login',
this.$util.LOGIN_PAGE_URL,
{
back: this.currentRoute
},
@@ -80,7 +86,7 @@ export default {
},
watch: {
//
componentRefresh: function(nval) {
componentRefresh: function (nval) {
this.getData();
}
},
@@ -118,7 +124,7 @@ export default {
methods: {
//
toDetail(e) {
this.$util.redirectTo('/pages/goods/detail', { goods_id: e.goods_id });
this.$util.redirectTo('/pages_goods/detail', { goods_id: e.goods_id });
},
//
followGoods(e, index) {
@@ -209,8 +215,7 @@ export default {
</script>
<style lang="scss">
.diy-fenxiao {
}
.diy-fenxiao {}
//
.goods-list.row1-of1 {
@@ -218,9 +223,11 @@ export default {
background-color: #fff;
display: flex;
margin-bottom: 20rpx;
&:last-of-type {
margin-bottom: 0;
}
&.shadow {
margin: 8rpx 8rpx 20rpx 8rpx;
}
@@ -229,6 +236,7 @@ export default {
width: 180rpx;
overflow: hidden;
margin-right: 20rpx;
image {
width: 100%;
}
@@ -244,9 +252,11 @@ export default {
.name-wrap {
flex: 1;
margin-bottom: 10rpx;
.goods-name {
font-size: $font-size-base;
line-height: 1.3;
&.multi-hidden {
height: 72rpx;
}
@@ -257,25 +267,31 @@ export default {
display: flex;
flex-direction: column;
justify-content: space-between;
.sale {
font-size: 20rpx;
line-height: 1;
color: #999;
}
.discount-price {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10rpx;
.price-wrap {
white-space: nowrap;
.unit {
font-size: $font-size-tag;
color: $base-color;
}
.price {
font-size: $font-size-toolbar;
}
text {
font-weight: bold;
color: $base-color;
@@ -291,6 +307,7 @@ export default {
font-size: $font-size-activity-tag;
}
}
.sale-btn {
position: absolute;
right: 20rpx;
@@ -310,6 +327,7 @@ export default {
.goods-list.row1-of2 {
display: flex;
flex-wrap: wrap;
.goods-item {
position: relative;
background: #fff;
@@ -317,24 +335,30 @@ export default {
margin-right: 20rpx;
margin-top: 20rpx;
width: calc(50% - 10rpx);
&:nth-child(2n + 2) {
width: calc(50% - 11rpx);
margin-right: 0;
}
&:nth-of-type(1),
&:nth-of-type(2) {
margin-top: 0;
}
&.shadow {
width: calc(50% - 18rpx);
&:nth-child(2n-1) {
margin-left: 8rpx;
}
&:nth-of-type(1),
&:nth-of-type(2) {
margin-top: 8rpx;
}
}
.goods-img {
position: relative;
overflow: hidden;
@@ -355,9 +379,11 @@ export default {
.name-wrap {
margin-bottom: 10rpx;
.goods-name {
font-size: $font-size-base;
line-height: 1.3;
&.multi-hidden {
height: 72rpx;
}
@@ -369,24 +395,30 @@ export default {
display: flex;
flex-direction: column;
justify-content: space-between;
.sale {
font-size: 20rpx;
line-height: 1;
color: #999;
}
.discount-price {
display: flex;
justify-content: space-between;
align-items: center;
.price-wrap {
white-space: nowrap;
.unit {
font-size: $font-size-tag;
color: $base-color;
}
.price {
font-size: $font-size-toolbar;
}
text {
font-weight: bold;
color: $base-color;
@@ -402,6 +434,7 @@ export default {
font-size: $font-size-activity-tag;
}
}
.sale-btn {
position: absolute;
right: 20rpx;

View File

@@ -0,0 +1,123 @@
<template>
<view data-component-name="diy-float-btn" class="float-btn"
:class="{ left_top: value.bottomPosition == 1, right_top: value.bottomPosition == 2, left_bottom: value.bottomPosition == 3, right_bottom: value.bottomPosition == 4 }"
:style="style">
<block v-for="(item, index) in value.list" :key="index">
<view class="button-box" @tap.stop="$util.diyRedirectTo(item.link)"
:style="{ width: value.imageSize + 'px', height: value.imageSize + 'px', fontSize: value.imageSize + 'px' }">
<image v-if="!item.iconType || item.iconType == 'img'" :src="$util.img(item.imageUrl)" mode="aspectFit"
:show-menu-by-longpress="true" />
<diy-icon v-else-if="item.iconType && item.iconType == 'icon'" :icon="item.icon"
:value="item.style ? item.style : null"></diy-icon>
</view>
</block>
</view>
</template>
<script>
// 自定义浮动按钮
import DiyMinx from './minx.js'
// 获取系统状态栏的高度
let systemInfo = uni.getSystemInfoSync();
// 自定义浮动按钮
export default {
name: 'diy-float-btn',
props: {
value: {
type: Object,
default: () => {
return {};
}
}
},
mixins: [DiyMinx],
data() {
return {
navHeight: 0,
statusBarHeight: systemInfo.statusBarHeight
};
},
created() { },
watch: {
// 组件刷新监听
componentRefresh: function (nval) { }
},
components: {},
methods: {},
computed: {
style() {
let style = {},
height = 54;
// #ifdef MP
height = systemInfo.platform == 'ios' ? 54 : 58;
// #endif
switch (parseInt(this.value.bottomPosition)) {
case 1:
style.top = (this.navHeight + this.statusBarHeight + parseInt(this.value.btnBottom)) * 2 + 'rpx';
break;
case 2:
style.top = (this.navHeight + this.statusBarHeight + parseInt(this.value.btnBottom)) * 2 + 'rpx';
break;
case 3:
style.bottom = (100 + parseInt(this.value.btnBottom)) * 2 + 'rpx';
break;
case 4:
style.bottom = (100 + parseInt(this.value.btnBottom)) * 2 + 'rpx';
break;
}
return this.$util.objToStyle(style);
}
}
};
</script>
<style lang="scss">
.float-btn {
position: fixed;
bottom: 20%;
right: 40rpx;
z-index: 990;
&.left_top {
top: 100rpx;
left: 30rpx;
}
&.right_top {
top: 100rpx;
right: 30rpx;
}
&.left_bottom {
bottom: 160rpx;
left: 30rpx;
padding-bottom: constant(safe-area-inset-bottom);
/*兼容 IOS<11.2*/
padding-bottom: env(safe-area-inset-bottom);
/*兼容 IOS>11.2*/
}
&.right_bottom {
bottom: 160rpx;
right: 30rpx;
padding-bottom: constant(safe-area-inset-bottom);
/*兼容 IOS<11.2*/
padding-bottom: env(safe-area-inset-bottom);
/*兼容 IOS>11.2*/
}
.button-box {
margin-bottom: 20rpx;
&:last-child {
margin-bottom: 0;
}
image {
width: 100%;
height: 100%;
}
}
}
</style>

View File

@@ -0,0 +1,31 @@
<template>
<!-- #ifdef MP -->
<view data-component-name="diy-follow-official-account" v-if="value.isShow">
<official-account></official-account>
</view>
<!--#endif -->
</template>
<script>
// 关注公众号展示
import DiyMinx from './minx.js'
export default {
name: 'diy-follow-official-account',
props: {
value: {
type: Object
}
},
mixins: [DiyMinx],
data() {
return {};
},
watch: {
// 组件刷新监听
componentRefresh: function (nval) { }
},
methods: {}
};
</script>
<style></style>

114
components-diy/diy-form.vue Normal file
View File

@@ -0,0 +1,114 @@
<template>
<view data-component-name="diy-form" class="diy-from" :style="style">
<view class="fui-cell-group">
<view class="fui-cell ">
<view class="fui-cell-label ">您的姓名</view>
<view class="fui-cell-info">
<input v-model="Form.realname" class="fui-input" placeholder="请输入您的姓名" value=""></input>
</view>
</view>
<view class="fui-cell ">
<view class="fui-cell-label">手机号码</view>
<view class="fui-cell-info">
<input v-model="Form.mobile" class="fui-input" maxlength="11" placeholder="请输入您的手机号"
type="number"></input>
</view>
</view>
<view class="fui-cell ">
<view class="fui-cell-label">您的邮箱</view>
<view class="fui-cell-info">
<input v-model="Form.mailbox" class="fui-input" placeholder="请输入您的邮箱" type="text"></input>
</view>
</view>
<view class="fui-cell">
<view class="fui-cell-label">所在城市</view>
<view class="fui-cell-info">
<input v-model="Form.citys" class="fui-input" placeholder="请输入您的所在地" value=""></input>
</view>
</view>
<view class="fui-cell">
<view class="fui-cell-label">备注</view>
<view class="fui-cell-info">
<input v-model="Form.remark" class="fui-input" placeholder="请输入备注" value=""></input>
</view>
</view>
</view>
<view @tap.stop="submitform" class="fui-btn btn-danger block mtop">提交信息</view>
</view>
</template>
<script>
// 自定义表单
import DiyMinx from './minx.js'
export default {
name: 'diy-from',
props: {
value: {
type: Object
}
},
mixins: [DiyMinx],
data() {
return {
loading: true,
markers: [],
Form: {
realname: '',
mobile: '',
mailbox: '',
citys: '',
remark: '',
}
};
},
created() {
},
watch: {
// 组件刷新监听
componentRefresh: function (nval) {
// this.getDataList();
}
},
computed: {
markerst() {
return [{
id: 1,
latitude: this.value.list[0].lat,
longitude: this.value.list[0].lng
}]
},
style() {
var css = '';
css += 'background-color:' + this.value.contentBgColor + ';';
if (this.value.elementAngle == 'round') {
css += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
css += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
css += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
css += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
}
return css;
}
},
methods: {
submitform() {
this.$api.sendRequest({
url: '/api/member/information',
data: this.Form,
success: res => {
this.$util.showToast({
title: res.message
});
}
});
}
}
};
</script>
<style lang="scss">
.diy-from {
background: #fff;
padding-bottom: 20rpx;
border-radius: 10rpx;
}
</style>

View File

@@ -0,0 +1,166 @@
<template>
<x-skeleton data-component-name="diy-goods-brand" type="waterfall" :loading="loading" :configs="skeletonConfig">
<view :class="['brand-wrap', value.ornament.type]" :style="warpCss">
<view :class="[value.style]">
<view class="title-wrap" v-show="value.title"
:style="{ color: value.textColor, fontWeight: value.fontWeight ? 'bold' : '' }">{{ value.title }}
</view>
<view class="ul-wrap">
<view class="li-item" v-for="(item, index) in list" :key="index">
<image class="brand-pic" :src="$util.img(item.image_url)" mode="aspectFit"
@tap.stop="handlerClick(item)" @error="imgError(index)"
:style="itemCss" />
</view>
</view>
</view>
</view>
</x-skeleton>
</template>
<script>
// 商品品牌
import DiyMinx from './minx.js'
// 自定义商品品牌展示
export default {
name: 'diy-goods-brand',
props: {
value: {
type: Object
}
},
data() {
return {
list: [],
loading: true,
skeletonConfig: {
gridRows: 2,
gridColumns: 4,
gridRowsGap: '20rpx',
headWidth: '120rpx',
headHeight: '120rpx',
textShow: false
}
};
},
created() {
this.getBrandList();
},
mixins: [DiyMinx],
watch: {
// 组件刷新监听
componentRefresh: function (nval) {
this.getBrandList();
}
},
computed: {
warpCss() {
var obj = '';
obj += 'background-color:' + this.value.componentBgColor + ';';
if (this.value.componentAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
}
if (this.value.ornament.type == 'shadow') {
obj += 'box-shadow:' + '0 0 10rpx ' + this.value.ornament.color;
}
if (this.value.ornament.type == 'stroke') {
obj += 'border:' + '2rpx solid ' + this.value.ornament.color;
}
return obj;
},
// 子项样式
itemCss() {
var obj = '';
if (this.value.elementAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
}
return obj;
}
},
methods: {
getBrandList() {
var data = {
page: 1,
page_size: this.value.count
};
if (this.value.sources == 'diy') {
data.page_size = 0;
data.brand_id_arr = this.value.brandIds.toString();
}
this.$api.sendRequest({
url: '/api/goodsbrand/page',
data: data,
success: res => {
if (res.code == 0 && res.data) {
let data = res.data;
this.list = data.list;
}
this.loading = false;
}
});
},
toDetail(item) {
this.$util.redirectTo('/pages_goods/list', {
brand_id: item.brand_id
});
},
imgError(index) {
if (this.list[index]) this.list[index].image_url = this.$util.getDefaultImage().goods;
},
async handlerClick(item) {
await this.__$emitEvent({
eventName: 'goods-brand-tap', data: item, promiseCallback: (event, handler, awaitedResult) => {
if (!awaitedResult) return;
this.toDetail(item);
}
})
},
}
};
</script>
<style lang="scss">
.brand-wrap {
&.shadow {
margin-left: 8rpx;
margin-right: 8rpx;
margin-top: 8rpx;
margin-bottom: 8rpx;
}
.style-1 {
.title-wrap {
text-align: center;
padding: 20rpx 0 10rpx;
}
.ul-wrap {
display: flex;
flex-wrap: wrap;
padding: 20rpx;
.li-item {
display: flex;
align-items: center;
justify-content: center;
width: calc(100% / 4 - 20rpx) !important;
height: 124rpx;
margin: 10rpx;
background-color: #fff;
.brand-pic {
width: 100%;
height: 100%;
}
}
}
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,626 @@
<template>
<x-skeleton data-component-name="diy-goods-recommend" type="waterfall" :loading="loading" :configs="skeletonConfig">
<view v-if="list.length" :class="['goods-list', goodsValue.style]" :style="goodsListWarpCss">
<view class="top-wrap" v-if="goodsValue.topStyle.support">
<text :class="['js-icon', goodsValue.topStyle.icon.value]"
:style="{ backgroundColor: goodsValue.topStyle.icon.bgColor, color: goodsValue.topStyle.icon.color }"></text>
<text class="title" :style="{ color: goodsValue.topStyle.color }">{{ goodsValue.topStyle.title }}</text>
<text class="line" :style="{ color: goodsValue.topStyle.subColor }"></text>
<text class="sub" :style="{ color: goodsValue.topStyle.subColor }">{{ goodsValue.topStyle.subTitle
}}</text>
</view>
<swiper :autoplay="false" class="swiper" :style="{ height: swiperHeight }">
<swiper-item v-for="(item, index) in page" :key="index"
:class="['swiper-item', [list[index].length / 3] >= 1 && 'flex-between']">
<view class="goods-item" v-for="(dataItem, dataIndex) in list[index]" :key="dataIndex"
@tap.stop="toDetail(dataItem)" :class="[goodsValue.ornament.type]" :style="goodsItemCss">
<div class="goods-img-wrap">
<image class="goods-img" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
:src="$util.img(dataItem.goods_image, { size: 'mid' })" mode="widthFix"
@error="imgError(index, dataIndex)" :lazy-load="true" />
<view class="sell-out" v-if="dataItem.stock <= 0">
<text class="iconfont icon-shuqing"></text>
</view>
</div>
<view :class="['info-wrap', { 'multi-content': value.nameLineMode == 'multiple' }]"
v-if="goodsValue.goodsNameStyle.control || goodsValue.priceStyle.mainControl || goodsValue.priceStyle.lineControl || goodsValue.labelStyle.support">
<view v-if="goodsValue.goodsNameStyle.control" class="goods-name"
:style="{ color: goodsValue.theme == 'diy' ? goodsValue.goodsNameStyle.color : '', fontWeight: goodsValue.goodsNameStyle.fontWeight ? 'bold' : '' }"
:class="[{ 'using-hidden': goodsValue.nameLineMode == 'single' }, { 'multi-hidden': goodsValue.nameLineMode == 'multiple' }]">
{{ isEnEnv ? dataItem.en_goods_name : dataItem.goods_name }}
</view>
<view class="pro-info">
<view class="label-wrap" v-if="goodsValue.labelStyle.support"
:style="{ background: goodsValue.labelStyle.bgColor, color: goodsValue.labelStyle.color }">
<image :src="$util.img('app/component/view/goods_recommend/img/label.png')"
mode="widthFix" />
<text>{{ goodsValue.labelStyle.title }}</text>
</view>
<view class="discount-price">
<view class="price-wrap" v-if="goodsValue.priceStyle.mainControl">
<text class="unit price-style small"
:style="{ color: goodsValue.theme == 'diy' ? goodsValue.priceStyle.mainColor + '!important' : '' }"></text>
<text class="price price-style large"
:style="{ color: goodsValue.theme == 'diy' ? goodsValue.priceStyle.mainColor + '!important' : '' }">{{
showPrice(dataItem).split('.')[0] }}</text>
<text class="unit price-style small"
:style="{ color: goodsValue.theme == 'diy' ? goodsValue.priceStyle.mainColor + '!important' : '' }">{{
'.' + showPrice(dataItem).split('.')[1] }}</text>
</view>
<view v-if="goodsValue.priceStyle.lineControl && showMarketPrice(dataItem)"
class="delete-price price-font"
:style="{ color: goodsValue.theme == 'diy' ? goodsValue.priceStyle.lineColor : '' }">
{{ showMarketPrice(dataItem) }}</view>
<view class="sale" v-if="goodsValue.saleStyle.control"
:style="{ color: goodsValue.theme == 'diy' ? goodsValue.saleStyle.color : '' }">
{{ dataItem.sale_num }}{{ dataItem.unit ? dataItem.unit : '件' }}
</view>
</view>
</view>
</view>
</view>
</swiper-item>
</swiper>
</view>
</x-skeleton>
</template>
<script>
// 自定义商品推荐展示
import DiyMinx from './minx.js'
export default {
name: 'diy-goods-recommend',
props: {
value: {
type: Object,
default: () => {
return {};
}
}
},
mixins: [DiyMinx],
data() {
return {
loading: true,
skeletonConfig: {
gridRows: 1,
gridColumns: 3,
headWidth: '200rpx',
headHeight: '200rpx',
textRows: 2,
textWidth: ['100%', '60%'],
},
list: [],
goodsValue: {},
page: 1
};
},
created() {
this.goodsValue = this.value;
this.getGoodsList();
},
watch: {
'globalStoreInfo.store_id': {
handler(nval, oval) {
if (nval != oval) {
this.getGoodsList();
}
},
deep: true
},
// 组件刷新监听
componentRefresh: function (nval) {
this.getGoodsList();
}
},
computed: {
goodsListWarpCss() {
var obj = '';
obj += 'background-color:' + this.goodsValue.componentBgColor + ';';
if (this.goodsValue.componentAngle == 'round') {
obj += 'border-top-left-radius:' + this.goodsValue.topAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.goodsValue.topAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.goodsValue.bottomAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.goodsValue.bottomAroundRadius * 2 + 'rpx;';
}
if (this.goodsValue.bgUrl) {
obj += `background-image: url('${this.$util.img(this.goodsValue.bgUrl)}');`;
}
return obj;
},
// 商品项样式
goodsItemCss() {
var obj = '';
obj += 'background-color:' + this.value.elementBgColor + ';';
if (this.goodsValue.elementAngle == 'round') {
obj += 'border-top-left-radius:' + this.goodsValue.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.goodsValue.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.goodsValue.bottomElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.goodsValue.bottomElementAroundRadius * 2 + 'rpx;';
}
if (this.goodsValue.ornament.type == 'shadow') {
obj += 'box-shadow:' + '0 0 10rpx ' + this.goodsValue.ornament.color + ';';
}
if (this.goodsValue.ornament.type == 'stroke') {
obj += 'border:' + '2rpx solid ' + this.goodsValue.ornament.color + ';';
}
const screenWidth = uni.getSystemInfoSync().windowWidth;
var width = '';
if (this.goodsValue.style != 'style-2') {
width = [screenWidth - this.rpxUpPx(20) * 2 - this.rpxUpPx(200) * 3 - this.rpxUpPx(this.value.margin
.both * 2) * 2] / 6;
} else {
width = [screenWidth - this.rpxUpPx(20) * 2 - this.rpxUpPx(20) * 2 - this.rpxUpPx(200) * 3 - this
.rpxUpPx(this.value.margin.both * 2) * 2
] / 6;
}
obj += 'margin-left:' + width + 'px;';
obj += 'margin-right:' + width + 'px;';
return obj;
},
swiperHeight() {
if (this.goodsValue.style == 'style-3') {
return '330rpx';
} else if (this.goodsValue.style != 'style-2') {
if (this.value.nameLineMode == 'multiple') {
return '348rpx';
}
return '312rpx';
} else {
if (this.value.nameLineMode == 'multiple') {
return '360rpx';
}
return '320rpx';
}
}
},
methods: {
rpxUpPx(res) {
const screenWidth = uni.getSystemInfoSync().windowWidth;
var data = (screenWidth * parseInt(res)) / 750;
return Math.floor(data);
},
getGoodsList() {
var data = {
num: this.goodsValue.count
};
if (this.goodsValue.sources == 'category') {
data.category_id = this.goodsValue.categoryId;
data.category_level = 1;
} else if (this.goodsValue.sources == 'diy') {
data.num = 0;
data.goods_id_arr = this.goodsValue.goodsId.toString();
}
data.order = this.goodsValue.sortWay;
this.$api.sendRequest({
url: '/api/goodssku/components',
data: data,
success: res => {
if (res.code == 0 && res.data) {
let data = res.data;
this.list = data;
// 切屏滚动,每页显示固定数量
let size = 3;
let temp = [];
this.page = Math.ceil(this.list.length / size);
for (var i = 0; i < this.page; i++) {
temp[i] = [];
for (var j = i * size; j < this.list.length; j++) {
if (temp[i].length == size) break;
temp[i].push(this.list[j]);
}
}
this.list = temp;
}
this.loading = false;
}
});
},
toDetail(item) {
this.$util.redirectTo('/pages_goods/detail', {
goods_id: item.goods_id
});
},
imgError(pageIndex, index) {
this.list[pageIndex][index].goods_image = this.$util.getDefaultImage().goods;
},
showPrice(data) {
let price = data.discount_price;
if (data.member_price && parseFloat(data.member_price) < parseFloat(price)) price = data.member_price;
return price;
},
showMarketPrice(item) {
let price = this.showPrice(item);
if (item.market_price > 0) {
return item.market_price;
} else if (item.price > price) {
return item.price;
}
return '';
},
}
};
</script>
<style lang="scss" scoped>
.goods-list {
.goods-item {
line-height: 1;
.sale {
line-height: 1;
color: $color-tip;
font-size: $font-size-activity-tag;
}
.info-wrap {
.goods-name {
margin-bottom: 10rpx;
line-height: 1.3;
}
}
.sell-out {
position: absolute;
z-index: 1;
width: 100%;
height: 100%;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.5);
text {
color: #fff;
font-size: 180rpx;
}
}
}
}
// 商品列表横向滚动样式
.goods-list.style-1 {
width: 100%;
white-space: nowrap;
background-repeat: round;
.top-wrap {
display: flex;
align-items: center;
padding: 20rpx 0;
.js-icon {
border-radius: 50%;
font-size: 40rpx;
margin-right: 10rpx;
width: 70rpx;
height: 70rpx;
text-align: center;
line-height: 70rpx;
}
.line {
height: 28rpx;
margin: 0 10rpx;
border: 2rpx solid;
}
.title {
font-weight: bold;
font-size: $font-size-toolbar;
}
.sub {
font-size: $font-size-tag;
}
}
.flex-between {
justify-content: space-between;
}
.swiper {
display: flex;
flex-wrap: wrap;
margin: 0 20rpx;
.swiper-item {
display: flex;
align-items: center;
}
}
.goods-item {
overflow: hidden;
width: 200rpx;
display: inline-block;
box-sizing: border-box;
&:nth-child(3n + 3) {
width: 198rpx;
}
&.shadow {
margin-top: 8rpx;
}
.goods-img,
.goods-img-wrap {
position: relative;
width: 100%;
height: 196rpx;
}
.info-wrap {
display: flex;
flex-direction: column;
padding: 10rpx;
&.multi-content {
height: 130rpx;
box-sizing: border-box;
}
.goods-name {
font-size: $font-size-sub;
&.multi-hidden {
white-space: break-spaces;
}
}
.pro-info {
margin-top: auto;
display: flex;
flex-direction: column;
justify-content: space-between;
.discount-price {
display: flex;
justify-content: space-between;
align-items: center;
.price-wrap {
line-height: 1;
white-space: nowrap;
.unit {
font-size: $font-size-tag;
color: $base-color;
}
.price {
font-size: $font-size-toolbar;
}
text {
font-weight: bold;
color: $base-color;
}
}
}
.delete-price {
margin-left: 10rpx;
text-decoration: line-through;
flex: 1;
line-height: 28rpx;
color: $color-tip;
font-size: $font-size-activity-tag;
}
}
}
}
}
// 商品列表横向滚动样式
.goods-list.style-2 {
width: 100%;
white-space: nowrap;
background-repeat: round;
padding-bottom: 20rpx;
.top-wrap {
display: flex;
align-items: center;
padding: 20rpx;
.js-icon {
border-radius: 50%;
font-size: 40rpx;
margin-right: 20rpx;
width: 70rpx;
height: 70rpx;
text-align: center;
line-height: 70rpx;
}
.line {
height: 28rpx;
margin: 0 10rpx;
border: 2rpx solid;
}
.title {
font-weight: bold;
font-size: $font-size-toolbar;
}
.sub {
font-size: $font-size-tag;
}
}
.swiper {
display: flex;
flex-wrap: wrap;
margin: 0 20rpx;
padding: 20rpx;
border-radius: 20rpx;
background-color: #fff;
}
.goods-item {
overflow: hidden;
width: 200rpx;
display: inline-block;
box-sizing: border-box;
&.shadow {
margin-top: 8rpx;
width: 200rpx;
}
.goods-img,
.goods-img-wrap {
position: relative;
width: 100%;
height: 200rpx;
}
.info-wrap {
padding: 10rpx;
.goods-name {
line-height: 1;
&.multi-hidden {
line-height: 1.3;
height: 68rpx;
white-space: break-spaces;
}
}
.pro-info {
display: flex;
flex-direction: column;
justify-content: space-between;
.discount-price {
display: flex;
justify-content: space-between;
align-items: center;
.price-wrap {
line-height: 1.3;
.unit {
font-size: $font-size-tag;
color: $base-color;
}
text {
font-weight: bold;
color: $base-color;
&:last-of-type {
font-size: 32rpx;
}
}
}
}
.delete-price {
margin-left: 10rpx;
text-decoration: line-through;
flex: 1;
line-height: 28rpx;
color: $color-tip;
font-size: $font-size-activity-tag;
}
}
}
}
}
.goods-list.style-3 {
background-position: bottom;
.swiper {
display: flex;
flex-wrap: wrap;
margin: 0 20rpx;
padding: 10rpx 0;
.swiper-item {
display: flex;
align-items: center;
}
}
.goods-item {
overflow: hidden;
width: 200rpx;
display: inline-block;
box-sizing: border-box;
&.shadow {
// margin-top: 20rpx;
}
.goods-img,
.goods-img-wrap {
position: relative;
width: 100%;
height: 200rpx;
}
.info-wrap {
display: flex;
flex-direction: column;
padding: 10rpx;
.pro-info {
text-align: center;
.label-wrap {
border-radius: 40rpx;
display: inline-block;
margin: 10rpx 0;
position: relative;
padding-left: 52rpx;
padding-right: 16rpx;
line-height: 1.7;
image {
position: absolute;
top: -2rpx;
left: -2rpx;
width: 46rpx;
height: 46rpx;
}
text {
font-size: $font-size-tag;
}
}
.discount-price {
.price-wrap {
line-height: 1;
white-space: nowrap;
.unit {
font-size: $font-size-tag;
color: $base-color;
}
.price {
font-size: $font-size-toolbar;
}
text {
font-weight: bold;
color: $base-color;
}
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,318 @@
<template>
<view data-component-name="diy-graphic-nav" :style="componentStyle">
<block v-if="value.showStyle == 'pageSlide'">
<swiper :class="['graphic-nav', 'pageSlide', value.carousel.type]" circular :indicator-dots="false"
:style="swiperHeight" @change="swiperChange">
<swiper-item class="graphic-nav-wrap"
v-for="(numItem, numIndex) in Math.ceil(value.list.length / (value.pageCount * value.rowCount))">
<!-- #ifdef MP -->
<view class="graphic-nav-item" :class="[value.mode]" v-for="(item, index) in value.list"
:key="index"
v-if="index >= [(numItem) * (value.pageCount * value.rowCount)] && index < [(numItem + 1) * (value.pageCount * value.rowCount)]"
:style="{ width: 100 / value.rowCount + '%' }" @tap.stop="redirectTo(item.link)">
<!-- #endif -->
<!-- #ifdef H5 -->
<view class="graphic-nav-item" :class="[value.mode]" v-for="(item, index) in value.list"
:key="index"
v-if="index >= [(numItem - 1) * (value.pageCount * value.rowCount)] && index < [numItem * (value.pageCount * value.rowCount)]"
:style="{ width: 100 / value.rowCount + '%' }" @tap.stop="redirectTo(item.link)">
<!-- #endif -->
<view class="graphic-img" v-if="value.mode != 'text'"
:style="{ fontSize: value.imageSize * 2 + 'rpx', width: value.imageSize * 2 + 'rpx', height: value.imageSize * 2 + 'rpx' }">
<image v-if="item.iconType == 'img'"
:src="$util.img(item.imageUrl) || $util.img('public/uniapp/default_img/goods.png')"
mode="aspectFill"
:style="{ maxWidth: value.imageSize * 2 + 'rpx', maxHeight: value.imageSize * 2 + 'rpx', borderRadius: value.aroundRadius * 2 + 'rpx' }"
:show-menu-by-longpress="true" />
<diy-icon v-if="item.iconType == 'icon'" :icon="item.icon"
:value="item.style ? item.style : null"
:style="{ maxWidth: value.imageSize * 2 + 'rpx', maxHeight: value.imageSize * 2 + 'rpx', width: '100%', height: '100%' }"></diy-icon>
<text class="tag" v-if="item.label.control"
:style="{ color: item.label.textColor, backgroundImage: 'linear-gradient(' + item.label.bgColorStart + ',' + item.label.bgColorEnd + ')' }">
{{ item.label.text }}
</text>
</view>
<text v-if="value.mode != 'img'" class="graphic-text"
:style="{ fontSize: value.font.size * 2 + 'rpx', fontWeight: value.font.weight, color: value.font.color }">
{{ item.title }}
</text>
<!-- #ifdef H5 -->
</view>
<!-- #endif -->
<!-- #ifdef MP -->
</view>
<!-- #endif -->
</swiper-item>
</swiper>
<view class="swiper-dot-box" v-if="isIndicatorDots" :class="value.carousel.type">
<view v-for="(numItem, numIndex) in Math.ceil(value.list.length / (value.pageCount * value.rowCount))"
:key="numIndex">
<view class="swiper-dot" :class="{ 'active': numIndex == swiperCurrent }"></view>
</view>
</view>
</block>
<scroll-view v-else :scroll-x="value.showStyle == 'singleSlide'"
:class="['graphic-nav', value.showStyle == 'fixed' ? 'fixed-layout' : value.showStyle]">
<!-- #ifdef MP -->
<view class="uni-scroll-view-content">
<!-- #endif -->
<view class="graphic-nav-item" :class="[value.mode]" v-for="(item, index) in value.list" :key="index"
:style="{ width: 100 / value.rowCount + '%' }" @tap.stop="redirectTo(item.link)">
<view class="graphic-img" v-if="value.mode != 'text'"
:style="{ fontSize: value.imageSize * 2 + 'rpx', width: value.imageSize * 2 + 'rpx', height: value.imageSize * 2 + 'rpx' }">
<image v-if="item.iconType == 'img'"
:src="$util.img(item.imageUrl) || $util.img('public/uniapp/default_img/goods.png')"
mode="aspectFill"
:style="{ maxWidth: value.imageSize * 2 + 'rpx', maxHeight: value.imageSize * 2 + 'rpx', borderRadius: value.aroundRadius * 2 + 'rpx' }"
:show-menu-by-longpress="true" />
<diy-icon v-if="item.iconType == 'icon'" :icon="item.icon"
:value="item.style ? item.style : null"
:style="{ maxWidth: value.imageSize * 2 + 'rpx', maxHeight: value.imageSize * 2 + 'rpx', width: '100%', height: '100%' }"></diy-icon>
<text :class="['tag', { alone: value.mode == 'text' }]" v-if="item.label.control"
:style="{ color: item.label.textColor, backgroundImage: 'linear-gradient(' + item.label.bgColorStart + ',' + item.label.bgColorEnd + ')' }">
{{ item.label.text }}
</text>
</view>
<text v-if="value.mode != 'img'" class="graphic-text"
:style="{ fontSize: value.font.size * 2 + 'rpx', fontWeight: value.font.weight, color: value.font.color }">
{{ item.title }}
</text>
</view>
<!-- #ifdef MP -->
</view>
<!-- #endif -->
</scroll-view>
<ns-login ref="login"></ns-login>
</view>
</template>
<script>
// 自定义图形导航
import DiyMinx from './minx.js'
export default {
name: 'diy-graphic-nav',
props: {
value: {
type: Object
}
},
mixins: [DiyMinx],
data() {
return {
pageWidth: '',
indicatorDots: false,
swiperCurrent: 0
};
},
created() { },
watch: {
// 组件刷新监听
componentRefresh: function (nval) { }
},
computed: {
componentStyle() {
var css = '';
css += 'background-color:' + this.value.componentBgColor + ';';
if (this.value.componentAngle == 'round') {
css += 'border-top-left-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
css += 'border-top-right-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
css += 'border-bottom-left-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
css += 'border-bottom-right-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
}
css += 'box-shadow:' + (this.value.ornament.type == 'shadow' ? '0 0 10rpx ' + this.value.ornament
.color :
'') + ';';
css += 'border:' + (this.value.ornament.type == 'stroke' ? '2rpx solid ' + this.value.ornament.color :
'') + ';';
return css;
},
// 滑块容器的高度
swiperHeight() {
var css = '';
var height = 0;
if (this.value.mode == 'graphic') {
height = (21 + 6 + 14 + 8 + this.value.imageSize) * this.value
.pageCount; // 21 = 文字高度8 = 文字上边距14 = 上下内边距8 = 外边距
} else if (this.value.mode == 'img') {
height = (14 + 8 + this.value.imageSize) * this.value.pageCount; // 14 = 上下内边距8 = 外边距
} else if (this.value.mode == 'text') {
height = (21 + 14 + 8) * this.value.pageCount; // 21 = 文字高度14 = 上下内边距8 = 外边距
}
css += 'height:' + height * 2 + 'rpx';
return css;
},
// 是否显示轮播点
isIndicatorDots() {
var bool = true;
bool = this.value.carousel.type == 'hide' || Math.ceil(this.value.list.length / (this.value.pageCount * this.value.rowCount)) == 1 ? false : true;
return bool;
}
},
methods: {
redirectTo(link) {
if (link.wap_url) {
if (this.$util.getCurrRoute() == this.$util.MEMBER_PAGE_URL && !this.storeToken) {
this.$refs.login.open(link.wap_url);
return;
}
}
console.log(link)
this.$util.diyRedirectTo(link);
},
swiperChange(e) {
this.swiperCurrent = e.detail.current
}
}
};
</script>
<style>
/* 固定显示 */
.graphic-nav.fixed-layout>>>.uni-scroll-view-content {
display: flex;
flex-wrap: wrap;
}
/* 单行滑动 */
.graphic-nav.singleSlide>>>.uni-scroll-view-content {
display: flex;
}
.graphic-nav.pageSlide>>>.uni-swiper-dots-horizontal {
bottom: 0rpx;
}
.graphic-nav.pageSlide.straightLine>>>.uni-swiper-dot {
width: 30rpx;
border-radius: 0;
height: 8rpx;
}
.graphic-nav.pageSlide.circle>>>.uni-swiper-dot {
width: 14rpx;
height: 14rpx;
}
</style>
<style lang="scss">
.graphic-nav {
padding: 16rpx;
box-sizing: border-box;
&.singleSlide {
.graphic-nav-item {
flex-shrink: 0;
}
}
&.pageSlide {
position: relative;
.graphic-nav-wrap {
display: flex;
flex-wrap: wrap;
width: 100%;
height: 100%;
}
}
.graphic-nav-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 14rpx 0;
box-sizing: border-box;
.graphic-text {
padding-top: 12rpx;
line-height: 1.5;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
width: 100%;
text-align: center;
&.alone {
padding-top: 0;
}
}
&.text {
.graphic-text {
padding-top: 0;
}
}
.graphic-img {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100rpx;
height: 100rpx;
font-size: 90rpx;
.tag {
position: absolute;
top: -10rpx;
right: -24rpx;
color: #fff;
border-radius: 24rpx;
border-bottom-left-radius: 0;
transform: scale(0.8);
padding: 8rpx 16rpx;
line-height: 1;
font-size: 24rpx;
}
.icon {
font-size: 50rpx;
color: $color-sub;
}
}
}
}
.swiper-dot-box {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
margin-top: -20rpx;
padding-bottom: 8rpx;
.swiper-dot {
background-color: rgba(0, 0, 0, .3);
margin: 8rpx;
&.active {
background-color: rgba(0, 0, 0, 1);
}
}
&.straightLine {
.swiper-dot {
width: 30rpx;
border-radius: 0;
height: 8rpx;
}
}
&.circle {
.swiper-dot {
width: 15rpx;
border-radius: 50%;
height: 15rpx;
}
}
}
</style>

View File

@@ -5,34 +5,34 @@
<!-- 门店展示 -->
<diy-store :value="item"></diy-store>
</view>
<template v-if="item.componentName == 'Kefu'">
<!-- 客服按钮 -->
<diy-kefu :value="item"></diy-kefu>
</template>
<template v-if="item.componentName == 'Form'">
<!-- 表单组件 -->
<diy-form :value="item"></diy-form>
</template>
<template v-if="addonIsExist.store && item.componentName == 'StoreLabel'">
<!-- 门店标签 -->
<diy-store-label :value="item"></diy-store-label>
</template>
<template v-if="item.componentName == 'Picture'">
<!-- 单图组组件 -->
<diy-picture :value="item"></diy-picture>
</template>
<template v-if="item.componentName == 'Listmenu'">
<!-- 列表按钮组件 -->
<diy-listmenu :value="item"></diy-listmenu>
</template>
<template v-if="item.componentName == 'Text'">
<!-- 文本 -->
<diy-text :value="item"></diy-text>
@@ -55,7 +55,8 @@
<template v-if="item.componentName == 'Search'">
<!-- 搜索 -->
<diy-search :value="item" :topNavColor="topNavColor" :global="diyGlobalData.global" :haveTopCategory="haveTopCategory" :followOfficialAccount="followOfficialAccount"></diy-search>
<diy-search :value="item" :topNavColor="topNavColor" :global="diyGlobalData.global"
:haveTopCategory="haveTopCategory" :followOfficialAccount="followOfficialAccount"></diy-search>
</template>
<template v-if="item.componentName == 'RichText'">
@@ -85,7 +86,8 @@
<template v-if="item.componentName == 'ManyGoodsList'">
<!-- 多商品组 -->
<diy-many-goods-list :value="item" :global="diyGlobalData.global" :scrollTop="scrollTop"></diy-many-goods-list>
<diy-many-goods-list :value="item" :global="diyGlobalData.global"
:scrollTop="scrollTop"></diy-many-goods-list>
</template>
<template v-if="item.componentName == 'RubikCube'">
@@ -97,8 +99,8 @@
<!-- 视频 -->
<diy-video :value="item"></diy-video>
</template>
<template v-if="item.componentName == 'Seckill' && addonIsExist.seckill">
@@ -167,7 +169,7 @@
<!-- 文章 -->
<diy-article :value="item"></diy-article>
</template>
<template v-if="item.componentName == 'MerchList'">
<!-- 商户列表 -->
<diy-merch-list :value="item"></diy-merch-list>
@@ -202,20 +204,63 @@
<!-- 关注公众号 -->
<diy-follow-official-account :value="item"></diy-follow-official-account>
</template>
<template v-if="item.componentName == 'Map'">
<!-- 地图组件 -->
<diy-map :value="item"></diy-map>
</template>
<template v-if="item.componentName == 'Audio'">
<!-- 音频 -->
<diy-audio :value="item"></diy-audio>
</template>
<template v-if="item.componentName == 'ImageNav'">
<!-- 图片导航 -->
<diy-image-nav :value="item"></diy-image-nav>
</template>
<template v-if="item.componentName == 'Digit'">
<!-- 数字 -->
<diy-digit :value="item"></diy-digit>
</template>
<template v-if="item.componentName == 'VideoList'">
<!-- 视频列表 -->
<diy-video-list :value="item"></diy-video-list>
</template>
<template v-if="item.componentName == 'BottomNav'">
<!-- 底部导航 -->
<diy-bottom-nav :value="item"></diy-bottom-nav>
</template>
<template v-if="item.componentName == 'CategoryItem'">
<!-- 分类项 -->
<diy-category-item :value="item"></diy-category-item>
</template>
<template v-if="item.componentName == 'Category'">
<!-- 分类 -->
<diy-category :value="item"></diy-category>
</template>
<template v-if="item.componentName == 'Icon'">
<!-- 图标 -->
<diy-icon :value="item"></diy-icon>
</template>
<template v-if="item.componentName == 'Tab'">
<!-- Tab 组件 -->
<diy-tab :value="item" :diyGlobal="diyGlobalData"></diy-tab>
</template>
<template v-if="['ChannelList', 'WechatChannel'].includes(item.componentName)">
<!-- 视频号列表 -->
<diy-channel-list :value="item"></diy-channel-list>
</template>
<!-- 自定义扩展组件 -->
<diy-comp-extend :value="item"></diy-comp-extend>
</view>
@@ -223,95 +268,105 @@
</template>
<script>
export default {
components: {},
props: {
diyData: {
type: Object
},
scrollTop: {
type: [String, Number],
default: '0'
},
haveTopCategory: {
type: Boolean
},
followOfficialAccount: {
type: Object
},
//
import DiyMinx from './minx.js'
export default {
name: 'diy-group',
props: {
diyData: {
type: Object
},
data() {
return {
diyGlobalData: null
};
scrollTop: {
type: [String, Number],
default: '0'
},
created() {
this.diyGlobalData = JSON.parse(JSON.stringify(this.diyData));
haveTopCategory: {
type: Boolean
},
computed: {
topNavColor() {
var color = '';
if (this.diyData.global.topNavBg) {
color = 'transparent';
if (this.scrollTop > 20) {
color = this.diyData.global.topNavColor;
} else {
color = 'transparent';
}
} else {
followOfficialAccount: {
type: Object
},
},
mixins: [DiyMinx],
data() {
return {
diyGlobalData: null
};
},
created() {
this.diyGlobalData = JSON.parse(JSON.stringify(this.diyData));
},
computed: {
topNavColor() {
var color = '';
if (this.diyData.global.topNavBg) {
color = 'transparent';
if (this.scrollTop > 20) {
color = this.diyData.global.topNavColor;
} else {
color = 'transparent';
}
return color;
},
//
setPagestyle() {
this.diyGlobalData.value.forEach((item, index) => {
item.pageStyle = '';
//
item.moduleIndex = index + 1;
} else {
color = this.diyData.global.topNavColor;
}
return color;
},
//
setPagestyle() {
this.diyGlobalData.value.forEach((item, index) => {
item.pageStyle = '';
//
item.moduleIndex = index + 1;
//
if (item.componentName == 'Search' && item.positionWay == 'fixed') {
// item.pageStyle = 'background-color:' + item.pageBgColor + ';';
return false;
}
//
if (item.componentName == 'Search' && item.positionWay == 'fixed') {
// item.pageStyle = 'background-color:' + item.pageBgColor + ';';
return false;
}
item.pageStyle += 'background-color:' + item.pageBgColor + ';';
if (item.margin) {
item.pageStyle += 'padding-top:' + item.margin.top * 2 + 'rpx' + ';';
item.pageStyle += 'padding-bottom:' + item.margin.bottom * 2 + 'rpx' + ';';
item.pageStyle += 'padding-right:' + item.margin.both * 2 + 'rpx' + ';';
item.pageStyle += 'padding-left:' + item.margin.both * 2 + 'rpx' + ';';
item.pageStyle += 'background-color:' + item.pageBgColor + ';';
if (item.margin) {
item.pageStyle += 'padding-top:' + item.margin.top * 2 + 'rpx' + ';';
item.pageStyle += 'padding-bottom:' + item.margin.bottom * 2 + 'rpx' + ';';
item.pageStyle += 'padding-right:' + item.margin.both * 2 + 'rpx' + ';';
item.pageStyle += 'padding-left:' + item.margin.both * 2 + 'rpx' + ';';
}
});
return this.diyGlobalData.value;
},
//
diyDataArray() {
let data = [],
showModuleData = this.$store.state.diyGroupShowModule ? JSON.parse(this.$store.state
.diyGroupShowModule) : '';
if (showModuleData.length) {
if (showModuleData.includes('null')) return [];
let diyDataArr = this.setPagestyle;
diyDataArr.forEach((item, index) => {
if (showModuleData.includes(item.componentName)) {
data.push(item);
}
});
} else data = this.setPagestyle;
return this.diyGlobalData.value;
},
//
diyDataArray() {
let data = [],
showModuleData = this.$store.state.diyGroupShowModule ? JSON.parse(this.$store.state
.diyGroupShowModule) : '';
if (showModuleData.length) {
if (showModuleData.includes('null')) return [];
let diyDataArr = this.setPagestyle;
diyDataArr.forEach((item, index) => {
if (showModuleData.includes(item.componentName)) {
data.push(item);
}
});
} else data = this.setPagestyle;
return data;
}
},
methods: {}
};
console.log(`diy-group ['diyDataArray'] = `, {
data: data,
diyData: this.diyData,
diyGlobalData: this.diyGlobalData,
})
return data;
}
},
methods: {}
};
</script>
<style lang="scss">
.diy-group {
width: 100%;
}
.diy-group {
width: 100%;
}
</style>

View File

@@ -0,0 +1,500 @@
<template>
<x-skeleton data-component-name="diy-groupbuy" :type="skeletonType" :loading="loading" :configs="skeletonConfig">
<view class="diy-groupbuy" :class="[value.template, value.style]" :style="warpCss">
<template v-if="value.template == 'row1-of1'">
<view class="item" v-for="(item, index) in list" :key="index" @tap.stop="toDetail(item)"
:class="[value.ornament.type]" :style="goodsItemCss">
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix"
@error="imageError(index)">
</image>
</view>
<view class="content"
v-if="value.goodsNameStyle.control || value.priceStyle.mainControl || value.priceStyle.lineControl || value.btnStyle.control">
<view v-if="value.goodsNameStyle.control" class="goods-name"
:style="{ color: value.theme == 'diy' ? value.goodsNameStyle.color : '', fontWeight: value.goodsNameStyle.fontWeight ? 'bold' : '' }"
:class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]">
{{ isEnEnv ? item.en_goods_name : item.goods_name }}
</view>
<view class="discount-price" v-if="value.priceStyle.mainControl">
<text class="unit price-style small"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">¥</text>
<text class="price price-style large"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">{{
item.groupbuy_price.split('.')[0] }}</text>
<text class="unit price-style small"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">{{
'.' + item.groupbuy_price.split('.')[1] }}</text>
</view>
<button v-if="value.btnStyle.control" :style="{
background: value.btnStyle.theme == 'diy' ? 'linear-gradient(to right,' + value.btnStyle.bgColorStart + ',' + value.btnStyle.bgColorEnd + ')' : '',
color: value.btnStyle.theme == 'diy' ? value.btnStyle.textColor : '',
borderRadius: value.btnStyle.aroundRadius * 2 + 'rpx'
}">
{{ value.btnStyle.text }}
</button>
</view>
</view>
</template>
<template v-if="value.template == 'horizontal-slide'">
<scroll-view v-if="value.slideMode == 'scroll'" class="scroll" :scroll-x="true" :show-scrollbar="false">
<view class="item" v-for="(item, index) in list" :key="index" @tap.stop="toDetail(item)"
:class="[value.ornament.type]" :style="goodsItemCss">
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix"
@error="imageError(index)" />
<image class="bg" v-if="value.saleStyle.control"
:src="$util.img('public/uniapp/groupbuy/bg.png')" mode="widthFix" />
<view class="num" v-if="value.saleStyle.control"
:style="{ color: value.theme == 'diy' ? value.saleStyle.color : '' }">
已团{{ item.sell_num }}
</view>
</view>
<view :class="['content', { 'multi-content': value.nameLineMode == 'multiple' }]"
v-if="value.goodsNameStyle.control || value.priceStyle.mainControl || value.priceStyle.lineControl">
<view v-if="value.goodsNameStyle.control" class="goods-name"
:style="{ color: value.theme == 'diy' ? value.goodsNameStyle.color : '', fontWeight: value.goodsNameStyle.fontWeight ? 'bold' : '' }"
:class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]">
{{ isEnEnv ? item.en_goods_name : item.goods_name }}
</view>
<view class="discount-price" v-if="value.priceStyle.mainControl">
<text class="unit price-style small"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">¥</text>
<text class="price price-style large"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">{{
item.groupbuy_price.split('.')[0] }}</text>
<text class="unit price-style small"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">{{
'.' + item.groupbuy_price.split('.')[1] }}</text>
</view>
<view class="original-price price-font" v-if="value.priceStyle.lineControl"
:style="{ color: value.theme == 'diy' ? value.priceStyle.lineColor : '' }">¥{{
item.price }}</view>
</view>
</view>
</scroll-view>
<swiper v-if="value.slideMode == 'slide'" :autoplay="false" class="swiper"
:style="{ height: swiperHeight }">
<swiper-item v-for="(pageItem, pageIndex) in page" :key="pageIndex"
:class="['swiper-item', (list.length && [list[pageIndex].length / 3] >= 1) && 'flex-between']">
<view class="item" v-for="(item, dataIndex) in list[pageIndex]" :key="dataIndex"
@tap.stop="toDetail(item)" :class="[value.ornament.type]" :style="goodsItemCss">
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix"
@error="imageError(dataIndex)" />
<image class="bg" v-if="value.saleStyle.control"
:src="$util.img('public/uniapp/groupbuy/bg.png')" mode="widthFix" />
<view class="num" v-if="value.saleStyle.control"
:style="{ color: value.theme == 'diy' ? value.saleStyle.color : '' }">已团{{
item.sell_num }}</view>
</view>
<view :class="['content', { 'multi-content': value.nameLineMode == 'multiple' }]"
v-if="value.goodsNameStyle.control || value.priceStyle.mainControl || value.priceStyle.lineControl">
<view v-if="value.goodsNameStyle.control" class="goods-name"
:style="{ color: value.theme == 'diy' ? value.goodsNameStyle.color : '', fontWeight: value.goodsNameStyle.fontWeight ? 'bold' : '' }"
:class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]">
{{ isEnEnv ? item.en_goods_name : item.goods_name }}
</view>
<view class="discount-price" v-if="value.priceStyle.mainControl">
<text class="unit price-style small"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">¥</text>
<text class="price price-style large"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">{{
item.groupbuy_price.split('.')[0] }}</text>
<text class="unit price-style small"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">{{
'.' + item.groupbuy_price.split('.')[1] }}</text>
</view>
<view class="original-price price-font" v-if="value.priceStyle.lineControl"
:style="{ color: value.theme == 'diy' ? value.priceStyle.lineColor : '' }">¥{{
item.price }}</view>
</view>
</view>
</swiper-item>
</swiper>
</template>
</view>
</x-skeleton>
</template>
<script>
// 自定义团购展示
import DiyMinx from './minx.js'
export default {
name: 'diy-groupbuy',
props: {
value: {
type: Object
}
},
mixins: [DiyMinx],
data() {
return {
loading: true,
skeletonType: '',
skeletonConfig: {},
list: [],
page: 1
};
},
created() {
this.initSkeleton();
this.getData();
},
watch: {
// 组件刷新监听
componentRefresh: function (nval) {
this.getData();
}
},
computed: {
warpCss() {
var obj = '';
obj += 'background-color:' + this.value.componentBgColor + ';';
if (this.value.componentAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
}
return obj;
},
// 商品项样式
goodsItemCss() {
var obj = '';
obj += 'background-color:' + this.value.elementBgColor + ';';
if (this.value.elementAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
}
if (this.value.ornament.type == 'shadow') {
obj += 'box-shadow:' + '0 0 10rpx ' + this.value.ornament.color + ';';
}
if (this.value.ornament.type == 'stroke') {
obj += 'border:' + '2rpx solid ' + this.value.ornament.color + ';';
}
const screenWidth = uni.getSystemInfoSync().windowWidth;
if (this.value.template == 'horizontal-slide') {
var width = '';
if (this.value.slideMode == 'scroll' && this.value.goodsMarginType == 'diy') width = this.rpxUpPx(this
.value.goodsMarginNum * 2);
else width = [screenWidth - this.rpxUpPx(20) * 2 - this.rpxUpPx(200) * 3 - this.rpxUpPx(this.value
.margin.both * 2) * 2] / 6;
obj += 'margin-left:' + width + 'px;';
obj += 'margin-right:' + width + 'px;';
}
return obj;
},
swiperHeight() {
if (this.value.nameLineMode == 'multiple') return this.value.ornament.type == 'shadow' ? '404rpx' :
'392rpx';
return this.value.ornament.type == 'shadow' ? '376rpx' : '368rpx';
}
},
methods: {
initSkeleton() {
if (this.value.template == 'row1-of1') {
// 单列 风格
this.skeletonType = 'list';
this.skeletonConfig = {
textRows: 2
};
} else if (this.value.template == 'horizontal-slide') {
// 横向滑动 风格
this.skeletonType = 'waterfall';
this.skeletonConfig = {
gridRows: 1,
gridColumns: 3,
headHeight: '200rpx',
textRows: 2,
textWidth: ['100%', '80%']
};
}
},
rpxUpPx(res) {
const screenWidth = uni.getSystemInfoSync().windowWidth;
var data = (screenWidth * parseInt(res)) / 750;
return Math.floor(data);
},
getData() {
var data = {
num: this.value.count
};
if (this.value.sources == 'diy') {
data.num = 0;
data.goods_id_arr = this.value.goodsId.toString();
}
this.$api.sendRequest({
url: '/groupbuy/api/goods/lists',
data: data,
success: res => {
if (res.code == 0) {
this.list = res.data;
// 切屏滚动,每页显示固定数量
if (this.value.template == 'horizontal-slide' && this.value.slideMode == 'slide') {
let size = 3;
let temp = [];
this.page = Math.ceil(this.list.length / size);
for (var i = 0; i < this.page; i++) {
temp[i] = [];
for (var j = i * size; j < this.list.length; j++) {
if (temp[i].length == size) break;
temp[i].push(this.list[j]);
}
}
this.list = temp;
}
this.loading = false;
}
}
});
},
toDetail(e) {
this.$util.redirectTo('/pages_promotion/groupbuy/detail', {
groupbuy_id: e.groupbuy_id
});
},
imageError(index) {
this.list[index].goods_image = this.$util.getDefaultImage().goods;
this.$forceUpdate();
}
}
};
</script>
<style lang="scss">
::v-deep .uni-scroll-view ::-webkit-scrollbar {
/* 隐藏滚动条,但依旧具备可以滚动的功能 */
display: none;
width: 0;
height: 0;
color: transparent;
background: transparent;
}
::v-deep ::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
color: transparent;
background: transparent;
}
scroll-view ::-webkit-scrollbar {
width: 0;
height: 0;
background-color: transparent;
touch-action: none;
}
.diy-groupbuy {
&.row1-of1 {
.item {
display: flex;
margin-bottom: 20rpx;
padding: 16rpx;
&.shadow {
margin: 8rpx 8rpx 20rpx 8rpx;
}
&:last-child {
margin-bottom: 0;
padding-bottom: 20rpx;
}
.img-wrap {
width: 200rpx;
height: 200rpx;
>image {
width: 200rpx;
}
}
.goods-name {
margin-top: 6rpx;
line-height: 1.5;
}
.content {
flex: 1;
margin-left: 20rpx;
position: relative;
.discount-price {
white-space: nowrap;
font-weight: bold;
position: absolute;
bottom: 20rpx;
left: 0;
display: flex;
align-items: baseline;
line-height: 1;
.unit {
font-size: $font-size-tag;
margin-right: 4rpx;
color: $base-color;
}
.price {
font-size: $font-size-toolbar;
color: $base-color;
}
}
button {
position: absolute;
bottom: 10rpx;
right: 20rpx;
margin: 0;
padding: 0 20rpx;
background-color: $base-color;
color: #fff;
min-width: 112rpx;
height: 52rpx;
line-height: 52rpx;
font-size: $font-size-tag;
}
}
}
}
&.horizontal-slide {
.scroll {
width: calc(100% - 40rpx);
padding: 20rpx;
line-height: 1;
white-space: nowrap;
.item.shadow {
margin-bottom: 8rpx;
}
}
.flex-between {
justify-content: space-between;
}
.item {
display: inline-block;
width: 200rpx;
overflow: hidden;
box-sizing: border-box;
&:nth-child(3n + 3) {
width: 198rpx;
}
&.shadow {
margin-top: 8rpx;
}
.img-wrap {
width: 200rpx;
height: 200rpx;
position: relative;
overflow: hidden;
margin: 0 auto;
>image {
width: 200rpx;
}
.bg {
position: absolute;
width: 100%;
height: 60rpx;
bottom: 0;
left: 0;
z-index: 2;
}
.num {
width: 180rpx;
position: absolute;
bottom: 10rpx;
padding-left: 20rpx;
font-size: 20rpx;
line-height: 1;
color: #ffffff;
z-index: 3;
}
}
.content {
padding: 10rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
&.multi-content {
height: 158rpx;
box-sizing: border-box;
}
.goods-name {
line-height: 1.3;
&.multi-hidden {
white-space: break-spaces;
}
}
.discount-price {
white-space: nowrap;
margin-top: auto;
font-weight: bold;
line-height: 1;
.unit {
font-size: $font-size-tag;
margin-right: 4rpx;
color: $base-color;
}
.price {
font-size: $font-size-toolbar;
color: $base-color;
}
}
.original-price {
font-size: $font-size-tag;
color: $color-tip;
line-height: 1;
text-decoration: line-through;
}
}
}
.swiper {
width: 100%;
white-space: nowrap;
padding: 20rpx;
box-sizing: border-box;
.swiper-item {
display: flex;
align-items: center;
}
.item {
width: 200rpx;
}
}
}
}
</style>

View File

@@ -4,6 +4,7 @@
<script>
//
import DiyMinx from './minx.js'
export default {
name: 'diy-horz-blank',
props: {
@@ -11,15 +12,16 @@ export default {
type: Object
}
},
mixins: [DiyMinx],
data() {
return {};
},
watch: {
//
componentRefresh: function(nval) {}
componentRefresh: function (nval) { }
},
computed: {
horzBlankGaugeWrap: function() {
horzBlankGaugeWrap: function () {
var obj = '';
obj += 'background-color:' + this.value.componentBgColor + ';';
if (this.value.componentAngle == 'round') {
@@ -32,7 +34,7 @@ export default {
return obj;
}
},
created() {},
created() { },
methods: {}
};
</script>

View File

@@ -1,9 +1,11 @@
<template>
<view data-component-name="diy-horz-line" :style="{ borderTop: '2rpx ' + value.borderStyle + ' ' + value.color }"></view>
<view data-component-name="diy-horz-line" :style="{ borderTop: '2rpx ' + value.borderStyle + ' ' + value.color }">
</view>
</template>
<script>
// 线
import DiyMinx from './minx.js'
export default {
name: 'diy-horz-line',
props: {
@@ -11,12 +13,13 @@ export default {
type: Object
}
},
mixins: [DiyMinx],
data() {
return {};
},
watch: {
//
componentRefresh: function(nval) {}
componentRefresh: function (nval) { }
},
methods: {}
};

View File

@@ -0,0 +1,77 @@
<template>
<view data-component-name="diy-hot-area" :style="hotAreaWarp" class="hot-area-box">
<view class="simple-graph-wrap">
<image :style="{ height: value.imgHeight }" :src="$util.img(value.imageUrl)" mode="widthFix"
:show-menu-by-longpress="true" />
<!-- 热区功能 -->
<view class="heat-map" v-for="(mapItem, mapIndex) in value.heatMapData" :key="mapIndex" :style="{
width: mapItem.width + '%',
height: mapItem.height + '%',
left: mapItem.left + '%',
top: mapItem.top + '%'
}" @tap.stop="$util.diyRedirectTo(mapItem.link)"></view>
</view>
</view>
</template>
<script>
// 热区展示
import DiyMinx from './minx.js'
export default {
name: 'diy-hot-area',
props: {
value: {
type: Object,
default: () => {
return {};
}
}
},
mixins: [DiyMinx],
data() {
return {};
},
created() { },
watch: {
// 组件刷新监听
componentRefresh: function (nval) { }
},
computed: {
hotAreaWarp: function () {
var obj = '';
obj = 'background-color:' + this.value.componentBgColor + ';';
if (this.value.componentAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
}
return obj;
}
},
methods: {}
};
</script>
<style lang="scss" scoped>
.hot-area-box {
position: relative;
width: 100%;
overflow: hidden;
box-sizing: border-box;
}
.simple-graph-wrap {
line-height: 0;
overflow: hidden;
position: relative;
image {
width: 100%;
}
.heat-map {
position: absolute;
}
}
</style>

View File

@@ -0,0 +1,88 @@
<template>
<view data-component-name="diy-icon" class="diy-icon" :style="iconBgStyle">
<text class="js-icon" :class="iconClass" :style="iconStyle"></text>
</view>
</template>
<script>
// 图标展示
import DiyMinx from './minx.js'
export default {
name: 'diy-icon',
props: {
icon: {
type: String,
default: ''
},
value: {
type: Object,
default: function () {
return null;
}
}
},
mixins: [DiyMinx],
computed: {
iconClass() {
var _class = ' ' + this.icon;
if (this.value && this.value.iconColor.length > 1) _class += ' gradient';
return _class;
},
iconBgStyle() {
if (!this.value) return {};
var style = {
'border-radius': this.value.bgRadius + '%',
'background': ''
};
if (this.value.iconBgImg) style['background'] += 'url(' + this.$util.img(this.value.iconBgImg) + ') no-repeat bottom / contain'
if (this.value.iconBgColor.length) {
if (style.background) style.background += ',';
if (this.value.iconBgColor.length == 1) {
style.background += this.value.iconBgColor[0];
} else {
style['background'] += 'linear-gradient(' + this.value.iconBgColorDeg + 'deg, ' + this.value.iconBgColor.join(',') + ')';
}
}
return this.$util.objToStyle(style);
},
iconStyle() {
if (!this.value) return {};
var style = {
'font-size': this.value.fontSize + '%'
}
if (this.value.iconColor.length == 1) {
style.color = this.value.iconColor[0];
} else {
style['background'] = 'linear-gradient(' + this.value.iconColorDeg + 'deg, ' + this.value.iconColor.join(',') + ')';
}
return this.$util.objToStyle(style);
}
}
}
</script>
<style lang="scss">
.diy-icon {
width: 100%;
height: 100%;
font-size: 100%;
color: #000;
display: flex;
align-items: center;
justify-content: center;
.js-icon {
font-size: 50%;
line-height: 1;
padding: 1rpx;
&.gradient {
-webkit-background-clip: text !important;
-webkit-text-fill-color: transparent;
}
}
}
</style>

View File

@@ -0,0 +1,262 @@
<template>
<view :style="componentStyle">
<scroll-view :class="['image-nav', value.showStyle == 'fixed' ? 'fixed-layout' : value.showStyle]"
:scroll-x="value.showStyle == 'singleSlide'">
<view class="uni-scroll-view-content">
<view v-for="(item, index) in value.list" :key="index" :class="['image-nav-item', value.mode]"
style="margin-right: 28rpx;">
<!-- 图片部分 -->
<view v-if="value.mode != 'text'" class="image-img" :style="{
'font-size': (value.imageSize * 2 + 'rpx') + ';',
'width': (item.imgWidth / 2 + 'rpx') + ';',
'height': (item.imgHeight / 2 + 'rpx') + ';'
}">
<image v-if="item.link.wap_url" :style="{
'width': (item.imgWidth / 2 + 'rpx') + ';',
'height': (item.imgHeight / 2 + 'rpx') + ';'
}" :src="$util.img(item.imageUrl) || $util.img('public/uniapp/default_img/goods.png')"
:show-menu-by-longpress="true" @tap.stop="redirectTo(item.link)"></image>
<image v-else :style="{
'width': (item.imgWidth / 2 + 'rpx') + ';',
'height': (item.imgHeight / 2 + 'rpx') + ';'
}" :src="$util.img(item.imageUrl) || $util.img('public/uniapp/default_img/goods.png')"
:show-menu-by-longpress="true" @tap.stop="previewImg(item.imageUrl)"></image>
</view>
<!-- 文字部分 -->
<text class="image-text" :style="{
'width': (item.imgWidth / 2 + 'rpx') + ';',
'font-size': (value.font.size * 2 + 'rpx') + ';',
'font-weight': value.font.weight + ';',
'color': value.font.color + ';'
}">{{ item.title }}</text>
</view>
</view>
</scroll-view>
<ns-login ref="login"></ns-login>
</view>
</template>
<script>
import DiyMinx from './minx.js'
export default {
name: 'diy-image-nav',
props: {
value: {
type: Object,
default: () => ({})
}
},
mixins: [DiyMinx],
data() {
return {
pageWidth: '',
indicatorDots: false,
swiperCurrent: 0
}
},
created() {
// 组件创建时的逻辑
},
watch: {
componentRefresh(newValue) {
// 监听组件刷新
}
},
computed: {
componentStyle() {
let style = '';
style += 'background-color:' + this.value.componentBgColor + ';';
if (this.value.componentAngle == 'round') {
style += 'border-top-left-radius:' + (2 * this.value.topAroundRadius) + 'rpx;';
style += 'border-top-right-radius:' + (2 * this.value.topAroundRadius) + 'rpx;';
style += 'border-bottom-left-radius:' + (2 * this.value.bottomAroundRadius) + 'rpx;';
style += 'border-bottom-right-radius:' + (2 * this.value.bottomAroundRadius) + 'rpx;';
}
style += 'box-shadow:' + (this.value.ornament.type == 'shadow' ? '0 0 10rpx ' + this.value.ornament.color : '') + ';';
style += 'border:' + (this.value.ornament.type == 'stroke' ? '2rpx solid ' + this.value.ornament.color : '') + ';';
return style;
},
swiperHeight() {
let height = 0;
if (this.value.mode == 'graphic') {
height = (49 + this.value.imageSize) * this.value.pageCount;
} else if (this.value.mode == 'img') {
height = (22 + this.value.imageSize) * this.value.pageCount;
} else if (this.value.mode == 'text') {
height = 43 * this.value.pageCount;
}
return 'height:' + (2 * height) + 'rpx';
},
isIndicatorDots() {
return this.value.carousel.type != 'hide' &&
1 != Math.ceil(this.value.list.length / (this.value.pageCount * this.value.rowCount));
}
},
methods: {
// 页面跳转
redirectTo(link) {
if (!link.wap_url || this.$util.getCurrRoute() != this.$util.MEMBER_PAGE_URL || this.storeToken) {
this.$util.diyRedirectTo(link);
} else {
this.$refs.login.open(link.wap_url);
}
},
// 轮播切换
swiperChange(event) {
this.swiperCurrent = event.detail.current;
}
}
}
</script>
<style lang="scss" scoped>
.image-nav {
padding: 16rpx;
box-sizing: border-box;
&.fixed-layout {
.uni-scroll-view-content {
display: flex;
flex-wrap: wrap;
}
}
&.singleSlide {
.uni-scroll-view-content {
display: flex;
}
.image-nav-item {
flex-shrink: 0;
}
}
&.pageSlide {
position: relative;
.uni-swiper-dots-horizontal {
bottom: 0rpx;
}
&.straightLine {
.uni-swiper-dot {
width: 30rpx;
border-radius: 0;
height: 8rpx;
}
}
&.circle {
.uni-swiper-dot {
width: 14rpx;
height: 14rpx;
}
}
}
.image-nav-wrap {
display: flex;
flex-wrap: wrap;
width: 100%;
height: 100%;
}
.image-nav-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 14rpx 0;
box-sizing: border-box;
.image-text {
padding-top: 12rpx;
line-height: 1.5;
text-overflow: ellipsis;
overflow: hidden;
text-align: center;
&.alone {
padding-top: 0;
}
}
&.text {
.image-text {
padding-top: 0;
}
}
.image-img {
position: relative;
display: flex;
align-items: center;
justify-content: center;
font-size: 90rpx;
.tag {
position: absolute;
top: -10rpx;
right: -24rpx;
color: #fff;
border-radius: 24rpx;
border-bottom-left-radius: 0;
-webkit-transform: scale(0.8);
transform: scale(0.8);
padding: 8rpx 16rpx;
line-height: 1;
font-size: 24rpx;
}
.icon {
font-size: 50rpx;
color: #606266;
}
}
}
}
.swiper-dot-box {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
margin-top: -20rpx;
padding-bottom: 8rpx;
.swiper-dot {
background-color: rgba(0, 0, 0, 0.3);
margin: 8rpx;
&.active {
background-color: #000;
}
}
&.straightLine {
.swiper-dot {
width: 30rpx;
border-radius: 0;
height: 8rpx;
}
}
&.circle {
.swiper-dot {
width: 15rpx;
border-radius: 50%;
height: 15rpx;
}
}
}
</style>

View File

@@ -0,0 +1,317 @@
<template>
<view data-component-name="diy-img-ads" class="single-graph">
<view :style="imgAdsMarginWarp" class="swiper-box">
<block v-if="imgAdsValue.list.length == 1">
<view class="simple-graph-wrap" :style="imgAdsSwiper" @tap.stop="handlerClick(imgAdsValue.list[0].link)">
<image :style="{ height: imgAdsValue.list[0].imgHeight }"
:src="$util.img(imgAdsValue.list[0].imageUrl)" mode="widthFix" :show-menu-by-longpress="true" />
</view>
</block>
<swiper v-else class="swiper" :style="{ height: swiperHeight }" :class="{
'swiper-left': imgAdsValue.indicatorLocation == 'left',
'swiper-right': imgAdsValue.indicatorLocation == 'right',
'ns-indicator-dots': imgAdsValue.carouselStyle == 'line'
}" :autoplay="true" :interval="imgAdsValue.interval" circular="true" :indicator-dots="isDots"
indicator-color="rgba(130, 130, 130, .5)" :indicator-active-color="imgAdsValue.indicatorColor"
@change="swiperChange">
<swiper-item class="swiper-item" :style="imgAdsSwiper" v-for="(item, index) in imgAdsValue.list"
:key="index" v-if="item.imageUrl" @tap.stop="handlerClick(item.link)">
<view class="item" :style="imgAdsSwiper + 'height: ' + item.imgHeight">
<image :src="$util.img(item.imageUrl)" :mode="item.imageMode || 'scaleToFill'"
:show-menu-by-longpress="true" />
</view>
</swiper-item>
</swiper>
<!-- #ifdef MP-WEIXIN -->
<view v-if="imgAdsValue.list.length > 1 && value.indicatorIsShow" :class="[
'swiper-dot-box',
{ straightLine: imgAdsValue.carouselStyle == 'line' },
{ 'swiper-left': imgAdsValue.indicatorLocation == 'left' },
{ 'swiper-right': imgAdsValue.indicatorLocation == 'right' }
]">
<view v-for="(numItem, numIndex) in imgAdsValue.list.length" :key="numIndex"
:class="['swiper-dot', { active: numIndex == swiperIndex }]"
:style="[numIndex == swiperIndex && { backgroundColor: imgAdsValue.indicatorColor }]"></view>
</view>
<!-- #endif -->
</view>
</view>
</template>
<script>
// 图片广告
import DiyMinx from './minx.js'
export default {
name: 'diy-img-ads',
props: {
value: {
type: Object,
default: () => {
return {};
}
}
},
data() {
return {
isDots: true,
swiperHeight: 0,
imgAdsValue: null, // 深拷贝一遍数据,防止动态计算图片展示尺寸的时候,影响到父级的数据,导致二次渲染的时候,数据错误
swiperIndex: 0
};
},
created() {
this.calcSingleRow();
},
mixins: [DiyMinx],
watch: {
// 组件刷新监听
componentRefresh: function (nval) { }
},
computed: {
imgAdsMarginWarp: function () {
var obj = '';
obj = 'background-color:' + this.value.componentBgColor + ';';
return obj;
},
imgAdsSwiper: function () {
var obj = '';
if (this.value.componentAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
}
return obj;
},
singleGraphBg: function () {
var imgArr = [];
for (let i = 0; i < this.imgAdsValue.list.length; i++) {
let item = this.imgAdsValue.list[i];
imgArr[i] = parseFloat(item.imgHeight);
}
imgArr.sort(function (a, b) {
return b - a;
});
var obj = '';
obj += 'background-color:' + this.imgAdsValue.backgroundColor + ';';
obj += 'height:' + imgArr[0] * (this.imgAdsValue.backgroundHeight / 100) * 2 + 'rpx;';
return obj;
}
},
methods: {
swiperChange(e) {
this.swiperIndex = e.detail.current;
},
calcSingleRow() {
let minHeight = 0;
let systemInfo = uni.getSystemInfoSync()
// 深拷贝一层数据,防止数据更改越权
this.imgAdsValue = JSON.parse(JSON.stringify(this.value));
this.imgAdsValue.list.forEach((item, index) => {
var ratio = item.imgHeight / item.imgWidth;
item.imgWidth = systemInfo.windowWidth;
item.imgWidth -= this.value.margin.both * 2;
item.imgHeight = item.imgWidth * ratio;
// 获取最大高度 if (maxHeight == 0 || maxHeight < item.imgHeight) maxHeight = item.imgHeight;
if (minHeight == 0 || minHeight > item.imgHeight) minHeight = item.imgHeight;
});
this.imgAdsValue.list.forEach((item, index) => {
item.imgHeight = minHeight + 'px';
this.swiperHeight = minHeight + 'px';
});
this.imgAdsValue.indicatorColor = this.imgAdsValue.indicatorColor || '#fff';
if (this.value.indicatorIsShow === undefined) {
this.value.indicatorIsShow = true; // 控制指示点是否展示
}
// 是否显示指示器
if (this.imgAdsValue.list.length <= 1) {
this.isDots = false;
}
// #ifdef H5
this.isDots = this.value.indicatorIsShow;
// #endif
// #ifdef MP-WEIXIN
this.isDots = false;
// #endif
},
async handlerClick(link) {
await this.__$emitEvent({
eventName: 'img-ads-tap', data: link, promiseCallback: (event, handler, awaitedResult) => {
if (!awaitedResult) return;
this.$util.diyRedirectTo(link);
}
})
},
}
};
</script>
<style lang="scss" scoped>
.single-graph {
width: 100%;
line-height: 0;
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
box-sizing: border-box;
}
.simple-graph-wrap {
line-height: 0;
overflow: hidden;
position: relative;
image {
width: 100%;
}
.heat-map {
position: absolute;
}
}
.item.active text {
background: rgba(0, 0, 0, 0.3);
position: absolute;
bottom: 0;
color: #ffffff;
font-size: $font-size-tag;
width: 100%;
left: 0;
line-height: 40rpx;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
padding: 0 10rpx;
text-align: center;
}
.swiper-box {
position: relative;
width: 100%;
overflow: hidden;
box-sizing: border-box;
}
.swiper {
margin: 0 auto;
overflow: hidden;
}
.swiper-item {
width: 100%;
height: auto !important;
display: flex;
justify-content: center;
flex-direction: column;
// position: relative;
overflow: hidden;
.item {
width: 100%;
height: auto;
text-align: center;
position: relative;
overflow: hidden;
image {
width: 100%;
max-width: 100%;
height: 100%;
will-change: transform;
}
.heat-map {
position: absolute;
}
}
}
.swiper-dot-box {
position: absolute;
bottom: 20rpx;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 0 40rpx 8rpx;
box-sizing: border-box;
&.swiper-left {
justify-content: flex-start;
}
&.swiper-right {
justify-content: flex-end;
}
.swiper-dot {
background-color: #b2b2b2;
width: 15rpx;
border-radius: 50%;
height: 15rpx;
margin: 8rpx;
&.active {
background-color: rgba(0, 0, 0, 1);
}
}
&.straightLine {
.swiper-dot {
width: 18rpx;
height: 6rpx;
border-radius: 4rpx;
&.active {
width: 36rpx;
background-color: rgba(0, 0, 0, 1);
}
}
}
}
/* 隐藏滚动条,但依旧具备可以滚动的功能 */
::v-deep .uni-scroll-view::-webkit-scrollbar {
display: none;
}
.swiper ::v-deep .uni-swiper-dots-horizontal {
bottom: 25rpx;
}
.swiper-left ::v-deep .uni-swiper-dots-horizontal {
left: 40rpx;
transform: translate(0);
}
.swiper-right ::v-deep .uni-swiper-dots-horizontal {
right: 40rpx;
display: flex;
justify-content: flex-end;
transform: translate(0);
}
.carousel-angle ::v-deep .uni-swiper-dots-horizontal .uni-swiper-dot {
width: 24rpx;
border-radius: 0;
height: 8rpx;
}
.swiper.ns-indicator-dots ::v-deep .uni-swiper-dot {
width: 18rpx;
height: 6rpx;
border-radius: 4rpx;
}
.swiper.ns-indicator-dots ::v-deep .uni-swiper-dot-active {
width: 36rpx;
}
</style>

View File

@@ -0,0 +1,863 @@
<template>
<view data-component-name="diy-index-page">
<view class="bg" :style="warpCss">
<view class="index-page-content">
<view class="nav-top-category" :style="categoryCss">
<scroll-view v-if="value" scroll-with-animation class="diyIndex" scroll-x="true"
:scroll-into-view="'a' + pageIndex"
:style="{ background: value.backgroundColor ? value.backgroundColor : '', width: 'calc(100% - 48rpx)' }"
@touchmove.stop>
<view class="item" :id="'a' + index" v-for="(item, index) in cateList" :key="index"
@tap.stop="changePageIndex(index)" :class="{ fill: value.styleType == 'fill' }"
:style="{ background: index == pageIndex && value.styleType == 'fill' ? value.selectColor : '' }">
<view class="text-con" :class="index == pageIndex ? 'active' : ''" :style="{
color: index == pageIndex ? '' : value.noColor
}" v-if="value.styleType == 'fill'">
{{ item.short_name ? item.short_name : item.category_name }}
</view>
<view class="text-con" :class="index == pageIndex ? 'active' : ''"
:style="{ color: index == pageIndex ? value.selectColor : value.noColor }" v-else>
{{ item.short_name ? item.short_name : item.category_name }}
</view>
<view class="color-base-bg line" v-if="index == pageIndex && value.styleType != 'fill'"
:style="{ background: value.selectColor ? value.selectColor + '!important' : 'rgba(0,0,0,0)' + '!important' }">
</view>
</view>
</scroll-view>
<text class="iconfont icon-unfold unfold-arrows" :style="{ color: value.moreColor }"
@tap.stop="unfoldMenu"></text>
</view>
<uni-popup ref="navTopCategoryPop" type="top" :top="uniPopTop">
<view class="nav-topcategory-pop">
<text v-for="(item, index) in cateList" :key="index"
:class="['category-item', { 'color-base-text color-base-border active': pageIndex == index }]"
@tap.stop="changePageIndex(index)">
{{ item.short_name ? item.short_name : item.category_name }}
</text>
</view>
</uni-popup>
<view class="nav_top_category-fill" :style="{ height: moduleHeight }"></view>
<block v-if="pageIndex == 0">
<slot name="components"></slot>
<slot></slot>
</block>
<block v-else>
<slot name="components"></slot>
<view class="index-category-box">
<view class="category-goods" v-show="!isloading">
<mescroll-uni :top="uniPopTop" ref="mescroll" @getData="getGoodsList"
:background="'url(' + $util.img(bgUrl) + ') 0px -50px / 100% no-repeat'"
:paddingBoth="'30rpx'" @touchmove.prevent.stop>
<block slot="list">
<!-- 二级分类 -->
<view class="twoCategorylist"
v-if="twoCategorylist != 'undefined' && twoCategorylist && twoCategorylist.length > 0">
<view class="twoCategory min" v-if="twoCategorylist.length <= 5">
<view class="twoCategory-page">
<view class="swiper-item" v-for="(item, index) in twoCategorylist"
:key="index" @tap.stop="toCateGoodsList(item.category_id_2, 2)">
<view class="item-box">
<image :src="$util.img(item.image)" v-if="item.image"
mode="aspectFill" />
<image :src="$util.getDefaultImage().goods" v-else
mode="aspectFill" />
<view>{{ item.category_name }}</view>
</view>
</view>
</view>
</view>
<view class="twoCategory base"
v-if="twoCategorylist.length > 5 && twoCategorylist.length <= 10">
<view class="twoCategory-page">
<view class="swiper-item" v-for="(item, index) in twoCategorylist"
:key="index" @tap.stop="toCateGoodsList(item.category_id_2, 2)">
<view class="item-box">
<image :src="$util.img(item.image)" v-if="item.image"
mode="aspectFill" />
<image :src="$util.getDefaultImage().goods" v-else
mode="aspectFill" />
<view>{{ item.category_name }}</view>
</view>
</view>
</view>
</view>
<swiper class="twoCategory big" :duration="500"
v-if="twoCategorylist.length > 10" @change="swiperTocategoryChange">
<swiper-item class="twoCategory-page" v-for="page in maxPage" :key="page">
<view class="swiper-item" v-for="(item, index) in twoCategorylist"
:key="index" v-if="index >= (page - 1) * 10 && index < page * 10"
@tap.stop="toCateGoodsList(item.category_id_2, 2)">
<view class="item-box">
<image :src="item.image" mode="aspectFill" />
<view>{{ item.category_name }}</view>
</view>
</view>
</swiper-item>
</swiper>
<view class="dot-box">
<view class="dot-item" v-for="page in maxPage" v-if="maxPage > 1"
:key="page"
:class="twoCategorylistId == page - 1 ? 'active color-base-bg' : ''">
</view>
</view>
</view>
<!-- 分类广告 -->
<image class="category_adv" v-if="cateList[pageIndex].image_adv"
:src="$util.img(cateList[pageIndex].image_adv)" mode="widthFix" />
<view class="goods-list double-column" v-if="goodsList[pageIndex].list.length">
<view class="goods-item" v-for="(item, index) in goodsList[pageIndex].list"
:key="index" @tap.stop="toDetail(item)">
<view class="goods-img">
<image :src="goodsImg(item.goods_image)" mode="widthFix"
@error="imgError(index)" />
<view class="color-base-bg goods-tag"
v-if="value.goodsTag == 'default' && goodsTag(item) != ''">{{
goodsTag(item) }}</view>
<view class="goods-tag-img" v-if="value.goodsTag == 'diy'">
<image :src="$util.img(value.tagImg.imageUrl)" />
</view>
</view>
<view class="info-wrap">
<view class="name-wrap">
<view class="goods-name">{{ isEnEnv ? item.en_goods_name : item.goods_name }}</view>
</view>
<view class="lineheight-clear">
<view class="discount-price">
<text class="unit color-base-text font-size-tag">{{
$lang('common.currencySymbol') }}</text>
<text class="price color-base-text font-size-toolbar">{{
showPrice(item) }}</text>
</view>
<view class="member-price-tag"
v-if="item.member_price && item.member_price == showPrice(item)">
<image :src="$util.img('public/uniapp/index/VIP.png')"
mode="widthFix" />
</view>
<view class="member-price-tag" v-else-if="item.promotion_type == 1">
<image :src="$util.img('public/uniapp/index/discount.png')"
mode="widthFix" />
</view>
</view>
<view class="pro-info">
<view class="delete-price font-size-activity-tag color-tip"
v-if="showMarketPrice(item)">
<text class="unit">{{ $lang('common.currencySymbol') }}</text>
<text>{{ showMarketPrice(item) }}</text>
</view>
<view class="sale font-size-activity-tag color-tip">已售{{
item.sale_num }}{{ item.unit ? item.unit : '件' }}</view>
</view>
</view>
</view>
</view>
<view v-if="!isloading && goodsList[pageIndex].list.length == 0">
<ns-empty text="该分类下暂无商品" :isIndex="false"></ns-empty>
</view>
</block>
</mescroll-uni>
<!-- <ns-empty v-else-if="!isloading" :isIndex="false" text="该分类下暂无商品"></ns-empty> -->
</view>
<view class="loading" v-show="isloading"><ns-loading ref="loading"></ns-loading></view>
</view>
</block>
</view>
</view>
</view>
</template>
<script>
import DiyMinx from './minx.js'
export default {
name: 'diy-index-page',
props: {
value: {
type: Object
},
bgUrl: {
type: String
},
scrollTop: {
type: [String, Number],
default: '0'
},
diyGlobal: {
type: Object
}
},
mixins: [DiyMinx],
data() {
return {
pageIndex: 0, //当前选中分类id
cateList: [{
//header分类
category_name: '首页'
}],
twoCategorylist: [], //二级分类
twoCategorylistId: 0, //二级分类所在的swiper
goodsList: {},
isloading: true,
top: 0,
isUnfold: true, //是否展开菜单
moduleHeight: '' //组件高度
};
},
computed: {
warpCss() {
var obj = '';
obj += (this.bgUrl ? 'background:' + 'url(' + this.$util.img(this.bgUrl) + ') no-repeat 0 0/100%' : '') + ';';
return obj;
},
categoryCss() {
var obj = '';
obj += 'top:' + this.fixedTop + ';';
// obj += 'background-color:' + (this.value.componentBgColor || this.value.pageBgColor) + ';';
obj += 'background-color:' + this.topNavColor + ';';
return obj;
},
maxPage() {
let num = 0;
if (this.twoCategorylist && this.twoCategorylist.length) {
num = Math.ceil(this.twoCategorylist.length / 10);
}
return num;
},
type() {
if (this.value) {
return true;
} else {
return false;
}
},
topNavColor() {
var color = this.value.componentBgColor || this.value.pageBgColor;
if (this.diyGlobal.topNavBg && this.scrollTop > 20) color = this.diyGlobal.topNavColor;
return color;
},
fixedTop() {
let diyPositionObj = this.$store.state.diyGroupPositionObj;
let data = 0;
if (diyPositionObj.diySearch && diyPositionObj.diyIndexPage && diyPositionObj.nsNavbar) {
if (diyPositionObj.diySearch.moduleIndex > diyPositionObj.diyIndexPage.moduleIndex) data =
diyPositionObj.nsNavbar.originalVal + 'px';
else data = diyPositionObj.nsNavbar.originalVal + diyPositionObj.diySearch.originalVal + 'px';
} else if (diyPositionObj.diyIndexPage && diyPositionObj.nsNavbar) {
data = diyPositionObj.nsNavbar.originalVal + 'px';
}
return data;
},
// 分类导航展开菜单的位置
uniPopTop() {
let diyPositionObj = this.$store.state.diyGroupPositionObj;
let data = '0';
if (this.fixedTop && diyPositionObj.diyIndexPage)
data = Number.parseFloat(this.fixedTop) + diyPositionObj.diyIndexPage.originalVal + 'px';
return data;
}
},
watch: {
type(newVal, oldVal) {
if (newVal) {
this.getCategoryList();
}
}
},
mounted() {
this.getCategoryList();
setTimeout(() => {
// 获取组件的高度默认高度为4545是在375屏幕上的高度
const query = uni.createSelectorQuery();
// #ifdef H5
let cssSelect = '.page-header .u-navbar';
this.top = 100;
// #endif
// #ifdef MP
let cssSelect = '.page-header >>> .u-navbar';
this.top = 20;
// #endif
query
.select(cssSelect)
.boundingClientRect(data => {
let height;
if (this.diyGlobal.navBarSwitch) {
height = data ? data.height : 45;
} else {
height = data ? data.height : 0;
}
// #ifdef H5
this.top += height * 2;
// #endif
// #ifdef MP
this.top += height;
// #endif
})
.exec();
});
this.setModuleLocationFn();
},
methods: {
initPageIndex() {
this.pageIndex = 0;
this.showModuleFn();
},
//请求分类列表
getCategoryList() {
let url = '/api/goodscategory/tree';
let data = {
level: 3
};
this.$api.sendRequest({
url: url,
data: data,
success: res => {
if (res.code >= 0) {
let arr = [];
let obj = {
list: []
};
obj.category_name = this.value.title ? this.value.title : '首页';
arr.push(obj);
this.cateList = arr.concat(res.data);
Object.keys(this.cateList).forEach((key, index) => {
this.goodsList[key] = {
page: 1,
list: []
};
});
this.twoCategorylist = this.cateList[this.pageIndex].child_list;
}
}
});
},
//修改当前页面id
changePageIndex(e) {
this.isloading = true;
this.pageIndex = e;
this.showModuleFn();
if (e == 0) return;
this.twoCategorylist = this.cateList[this.pageIndex].child_list;
if (this.cateList[this.pageIndex].child_list) {
this.twoCategorylist = this.cateList[this.pageIndex].child_list;
this.twoCategorylist.forEach(v => {
if (v.image) {
v.image = this.$util.img(v.image);
} else {
v.image = this.$util.getDefaultImage().goods;
}
});
} else {
this.twoCategorylist = false;
}
if (this.$refs.mescroll) {
this.$refs.mescroll.refresh();
this.$refs.mescroll.myScrollTo(0);
}
},
//监听二级分类 页面切换
swiperTocategoryChange(e) {
this.twoCategorylistId = e.detail.current;
},
toDetail(item) {
this.$util.redirectTo('/pages_goods/detail', {
goods_id: item.goods_id
});
},
getGoodsList(mescroll) {
let id = this.pageIndex;
var data = {
page: mescroll.num,
page_size: mescroll.size
};
data.category_id = this.cateList[this.pageIndex].category_id_1;
data.category_level = 1;
this.$api.sendRequest({
url: '/api/goodssku/page',
data: data,
success: res => {
this.isloading = false;
let newArr = [];
let msg = res.message;
if (res.code == 0 && res.data) {
this.count = res.data.count;
newArr = res.data.list;
} else {
this.$util.showToast({
title: msg
});
}
mescroll.endSuccess(newArr.length);
//设置列表数据
if (mescroll.num == 1) this.goodsList[id].list = []; //如果是第一页需手动制空列表
this.goodsList[id].list = this.goodsList[id].list.concat(newArr); //追加新数据
if (this.$refs.loadingCover) this.$refs.loadingCover.hide();
this.$forceUpdate();
}
});
},
toCateGoodsList(e, f) {
this.$util.redirectTo('/pages_goods/list', {
category_id: e,
category_level: f
});
},
goodsImg(imgStr) {
let imgs = imgStr.split(',');
return imgs[0] ? this.$util.img(imgs[0], {
size: 'mid'
}) : this.$util.getDefaultImage().goods;
},
imgError(index) {
this.goodsList[index].goods_image = this.$util.getDefaultImage().goods;
},
showPrice(data) {
let price = data.discount_price;
if (data.member_price && parseFloat(data.member_price) < parseFloat(price)) price = data.member_price;
return price;
},
showMarketPrice(item) {
if (item.market_price_show) {
let price = this.showPrice(item);
if (item.market_price > 0) {
return item.market_price;
} else if (parseFloat(item.price) > parseFloat(price)) {
return item.price;
}
}
return '';
},
goodsTag(data) {
return data.label_name || '';
},
// 控制菜单展开关闭
unfoldMenu() {
if (this.isUnfold) this.$refs.navTopCategoryPop.open();
else this.$refs.navTopCategoryPop.close();
this.isUnfold = !this.isUnfold;
},
// 向vuex中的diyIndexPositionObj增加分类导航组件定位位置
setModuleLocationFn() {
const query = uni.createSelectorQuery().in(this);
query.select('.nav-top-category')
.boundingClientRect(data => {
let diyIndexPage = {
originalVal: data.height || 0, //自身高度 px
moduleIndex: this.value.moduleIndex //组件在diy-group的位置
};
this.moduleHeight = (data.height || 0) + 'px';
this.$store.commit('setDiyGroupPositionObj', {
diyIndexPage: diyIndexPage
});
}).exec();
},
showModuleFn() {
let searchModule = this.$root.diyData.value.filter((item, index) => {
return item.componentName == 'Search';
});
// setDiyGroupShowModule值为【】表示显示所有组件,为【null】则什么组件也不显示
if (this.pageIndex == 0) this.$store.commit('setDiyGroupShowModule', JSON.stringify([]));
else {
if (searchModule[0].positionWay == 'fixed') this.$store.commit('setDiyGroupShowModule', JSON.stringify(
['Search']));
else this.$store.commit('setDiyGroupShowModule', JSON.stringify(['null']));
}
// 特殊处理,切换分类导航导致页面无法上下滚动
// #ifdef H5
if (this.pageIndex == 0) {
// 标记当前页使用了mescroll (需延时,确保page已切换)
setTimeout(function () {
let uniPageDom = document.getElementsByTagName('uni-page')[0];
uniPageDom && uniPageDom.removeAttribute('use_mescroll');
}, 30);
}
// #endif
}
}
};
</script>
<style lang="scss">
.bg {
width: 100%;
height: 100%;
}
.nav-top-category {
display: flex;
align-items: center;
position: fixed;
z-index: 999;
transition: background 0.3s;
width: 100%;
padding: 0 24rpx;
box-sizing: border-box;
.text-fiexd {
.text-con {
line-height: 60rpx;
}
}
.unfold-arrows {
position: relative;
margin-left: 4rpx;
padding-left: 16rpx;
height: 80rpx;
line-height: 80rpx;
text-align: center;
color: #fff;
}
}
.nav-topcategory-pop {
padding: 0 10rpx 20rpx;
display: flex;
flex-wrap: wrap;
background-color: #fff;
.category-item {
padding: 0 30rpx;
height: 60rpx;
text-align: center;
line-height: 56rpx;
border-radius: 40rpx;
background-color: #f0f0f0;
margin: 20rpx 10rpx 0;
font-size: 24rpx;
border: 2rpx solid transparent;
box-sizing: border-box;
&.active {
background-color: transparent;
}
}
}
.diyIndex {
width: 100%;
height: 100rpx;
white-space: nowrap;
padding: 20rpx 0 0;
box-sizing: border-box;
&.widthAuto {
width: auto;
}
.item {
position: relative;
margin-right: 40rpx;
display: inline-block;
line-height: 80rpx;
font-size: $font-size-base;
text-align: center;
.text-con {
height: 30px;
line-height: 30px;
&.active {
font-size: $font-size-base;
font-weight: bold;
}
}
.text-con.active {
font-size: $font-size-base;
font-weight: bold;
}
.line {
position: absolute;
left: calc(50% - 14rpx);
width: 28rpx;
height: 5rpx;
border-radius: 5rpx;
}
&.fill {
border-radius: 50rpx;
padding: 0 10rpx;
.text-con.active {
font-size: $font-size-base;
font-weight: 600;
color: #fff;
}
}
}
}
.index-page-box {
width: 100%;
height: calc(100vh - 288rpx);
}
.index-page-content {
width: 100%;
// height: calc(100vh - 144px);
}
.index-category-box.active {
padding-bottom: 160rpx;
padding-bottom: calc(160rpx + constant(safe-area-inset-bottom));
padding-bottom: calc(160rpx + env(safe-area-inset-bottom));
}
.index-category-box {
width: 100%;
padding-bottom: 110rpx;
padding-bottom: calc(110rpx + constant(safe-area-inset-bottom));
padding-bottom: calc(110rpx + env(safe-area-inset-bottom));
.twoCategorylist {
position: relative;
}
.twoCategory.min {
height: 160rpx;
}
.twoCategory.big {
height: 340rpx;
}
.twoCategory {
width: 100%;
background: #ffffff;
border-radius: 15rpx;
overflow: hidden;
margin-top: 20rpx;
.twoCategory-page {
width: 100%;
height: 100%;
padding: 20rpx;
box-sizing: border-box;
}
.swiper-item {
width: 120rpx;
height: 120rpx;
display: inline-block;
margin-right: calc((100% - 120rpx * 5) / 4);
overflow: hidden;
.item-box {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
image {
width: 88rpx;
height: 88rpx;
}
view {
width: 100%;
font-size: 22rpx;
line-height: 1;
margin-top: 10rpx;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
overflow: hidden;
text-align: center;
}
}
}
.swiper-item:nth-child(5n) {
margin-right: 0;
}
.swiper-item:nth-child(10n + 6) {
margin-top: 15rpx;
}
}
.dot-box {
width: calc(100% - 40rpx);
height: 50rpx;
position: absolute;
bottom: 0rpx;
left: 20rpx;
background: rgba($color: #000000, $alpha: 0);
display: flex;
justify-content: center;
align-items: center;
.dot-item {
width: 12rpx;
height: 12rpx;
background: #cccccc;
border-radius: 6rpx;
margin-right: 10rpx;
}
.dot-item.active {
width: 24rpx;
}
}
.category_adv {
width: 100%;
margin: 20rpx 0;
border-radius: 15rpx;
}
.category-goods {
width: 100%;
}
}
.loading {
width: 100%;
height: 50rpx;
margin-top: 100rpx;
}
::v-deep .uni-scroll-view::-webkit-scrollbar {
/* 隐藏滚动条,但依旧具备可以滚动的功能 */
display: none;
}
.goods-list.double-column {
display: flex;
flex-wrap: wrap;
margin-top: $margin-updown;
.goods-item {
flex: 1;
position: relative;
background-color: #fff;
flex-basis: 48%;
max-width: calc((100% - 30rpx) / 2);
margin-right: $margin-both;
margin-bottom: $margin-updown;
border-radius: $border-radius;
&:nth-child(2n) {
margin-right: 0;
}
.goods-img {
position: relative;
overflow: hidden;
padding-top: 100%;
border-top-left-radius: $border-radius;
border-top-right-radius: $border-radius;
image {
width: 100%;
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%);
}
}
.goods-tag {
color: #fff;
line-height: 1;
padding: 8rpx 16rpx;
position: absolute;
border-bottom-right-radius: $border-radius;
top: 0;
left: 0;
font-size: $font-size-goods-tag;
}
.goods-tag-img {
position: absolute;
border-top-left-radius: $border-radius;
width: 80rpx;
height: 80rpx;
top: 0;
left: 0;
z-index: 5;
overflow: hidden;
image {
width: 100%;
height: 100%;
}
}
.info-wrap {
padding: 0 26rpx 26rpx 26rpx;
}
.goods-name {
font-size: $font-size-base;
line-height: 1.3;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
margin-top: 20rpx;
height: 68rpx;
}
.discount-price {
display: inline-block;
font-weight: bold;
line-height: 1;
margin-top: 16rpx;
.unit {
margin-right: 6rpx;
}
}
.pro-info {
display: flex;
margin-top: 16rpx;
.delete-price {
text-decoration: line-through;
flex: 1;
.unit {
margin-right: 6rpx;
}
}
&>view {
line-height: 1;
&:nth-child(2) {
text-align: right;
}
}
}
.member-price-tag {
display: inline-block;
width: 60rpx;
line-height: 1;
margin-left: 6rpx;
image {
width: 100%;
}
}
}
}
</style>

View File

@@ -0,0 +1,74 @@
<template>
<view data-component-name="diy-kefu" class="diy-kefu" :style="style">
<view class="fui-list-group merchgroup" v-for="(item, index) in value.list" :key="index">
<view class="fui-list jump" v-if="index == 0">
<view class="fui-list-media">
<image class="round" :src="$util.img(item.imageUrl)" style="border-radius:6rpx"></image>
</view>
<view class="fui-list-inner">
<view class="title" style="height: 75rpx;">
<text style="font-weight:600" :style="{ color: item.textColor }">{{ item.title }}</text>
<view class="subtitle" style="font-size:24rpx" :style="{ color: item.textColor }">{{ item.desc }}
</view>
</view>
</view>
<view class="fui-remark jump" style="padding-right: 20rpx; text-align: center; line-height: 140rpx;">
<span style="font-size:24rpx;padding: 14rpx 18rpx;border-radius:8rpx"
:style="{ background: item.BtBgColor, color: item.BtColor }" @tap.stop="previewSqs()">立即添加</span>
</view>
</view>
</view>
</view>
</template>
<script>
// 客服展示
import DiyMinx from './minx.js'
export default {
name: 'diy-kefu',
props: {
value: {
type: Object
}
},
mixins: [DiyMinx],
data() {
return {
loading: true,
};
},
created() {
// this.getDataList();
},
watch: {
// 组件刷新监听
componentRefresh: function (nval) {
// this.getDataList();
}
},
computed: {
style() {
var css = '';
css += 'background-color:' + this.value.contentBgColor + ';';
if (this.value.elementAngle == 'round') {
css += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
css += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
css += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
css += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
}
return css;
}
},
methods: {
previewSqs() {
var img = this.$util.img(this.value.list[1].imageUrl)
uni.previewImage({
current: img,
urls: [img]
})
}
}
};
</script>
<style lang="scss"></style>

View File

@@ -0,0 +1,80 @@
<template>
<view data-component-name="diy-listmenu" class="diy-listmenu" :style="style">
<view class="fui-cell-group">
<!-- <image mode="widthFix" style="width: 100%;" :src="$util.img(item.imageUrl)"></image> -->
<view v-for="(item, index) in value.list" @tap.stop="redirectTo(item.link)" class="fui-cell"
:class="item.iconType == 'img' ? 'img-cell' : ''">
<view class="fui-cell-icon" :style="{ 'color': item.style ? item.style.iconColor : '#333' }">
<diy-icon v-if="item.iconType == 'icon'" :icon="item.icon" :value="item.style ? item.style : null"
:style="{ maxWidth: value.imageSize * 2 + 'rpx', maxHeight: value.imageSize * 2 + 'rpx', width: '100%', height: '100%' }"></diy-icon>
<image v-if="item.iconType == 'img'" mode="widthFix" :src="$util.img(item.imageUrl)"
style="border-radius:6rpx;width: 60rpx;"></image>
</view>
<view class="fui-cell-text" style="color:#333;">{{ item.title }}</view>
<view class="fui-cell-remark" style="font-size: 24rpx;">{{ lang == 'en-us' ? 'view' : '查看' }}</view>
</view>
</view>
</view>
</template>
<script>
// 自定义列表菜单展示
import DiyMinx from './minx.js'
export default {
name: 'diy-listmenu',
props: {
value: {
type: Object
}
},
mixins: [DiyMinx],
data() {
return {
loading: true,
lang: uni.getStorageSync("lang")//en-us 英文
};
},
created() {
// this.getDataList();
},
watch: {
// 组件刷新监听
componentRefresh: function (nval) {
// this.getDataList();
}
},
computed: {
style() {
var css = '';
css += 'background-color:' + this.value.contentBgColor + ';';
if (this.value.elementAngle == 'round') {
css += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
css += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
css += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
css += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
}
if (this.value.margin.top > 0) css += 'margin-top:' + this.value.margin.top * 2 + 'rpx;';
return css;
}
},
methods: {
redirectTo(link) {
if (link.wap_url) {
if (this.$util.getCurrRoute() == this.$util.MEMBER_PAGE_URL && !this.storeToken) {
this.$refs?.login?.open(link.wap_url);
return;
}
}
this.$util.diyRedirectTo(link);
},
}
};
</script>
<style lang="scss">
.img-cell {
padding: 0 16rpx !important;
}
</style>

259
components-diy/diy-live.vue Normal file
View File

@@ -0,0 +1,259 @@
<template>
<x-skeleton data-component-name="diy-live" type="banner" :loading="loading" :configs="skeletonConfig">
<view class="live-wrap" @tap.stop="handlerClick(liveInfo.roomid)"
v-if="liveInfo">
<view class="banner-wrap">
<image
:src="liveInfo.banner != '' ? $util.img(liveInfo.banner) : $util.img('public/uniapp/live/live_default_banner.png')"
mode="widthFix"
@error="liveInfo.banner = $util.img('public/uniapp/live/live_default_banner.png')" />
<view class="shade"></view>
<view class="wrap">
<view class="room-name">
<text class="status-name font-size-base"
:class="{ 'color-base-bg': liveInfo.live_status == '101' }">
<text class="iconfont icon-zhibozhong font-size-sub"
v-if="liveInfo.live_status == '101'"></text>
<text class="iconfont icon-zhibojieshu font-size-sub" v-else></text>
{{ liveInfo.status_name }}
</text>
{{ liveInfo.name }}
</view>
</view>
</view>
<view class="room-info" v-if="value.isShowAnchorInfo || value.isShowLiveGood">
<block v-if="value.isShowAnchorInfo">
<image
:src="liveInfo.anchor_img != '' ? $util.img(liveInfo.anchor_img) : $util.getDefaultImage().head"
class="anchor-img" @error="liveInfo.anchor_img = $util.getDefaultImage().head" />
<text class="anchor-name">主播{{ liveInfo.anchor_name }}</text>
</block>
<text class="separate" v-if="value.isShowAnchorInfo && value.isShowLiveGood">|</text>
<block v-if="value.isShowLiveGood">
<text class="goods-text">直播商品{{ liveInfo.goods.length }}</text>
</block>
</view>
</view>
</x-skeleton>
</template>
<script>
// 直播
import DiyMinx from './minx.js'
export default {
components: {},
name: 'diy-live',
props: {
value: {
type: Object,
default: () => {
return {};
}
}
},
data() {
return {
loading: true,
skeletonConfig: {
headHeight: '200rpx'
},
liveInfo: {
banner: '',
anchor_img: ''
}
};
},
created() {
this.getLiveInfo();
},
mixins: [DiyMinx],
watch: {
// 组件刷新监听
componentRefresh: function (nval) {
this.getLiveInfo();
}
},
methods: {
getLiveInfo() {
this.$api.sendRequest({
url: '/live/api/live/info',
success: res => {
if (res.code == 0 && res.data) {
this.liveInfo = res.data;
this.getLiveStatus();
} else {
this.liveInfo = null;
}
this.loading = false;
}
});
},
entryRoom(roomId) {
// #ifdef MP-WEIXIN
wx.navigateTo({
url: `plugin-private://wx2b03c6e691cd7370/pages/live-player-plugin?room_id=${roomId}`
});
// #endif
},
getLiveStatus() {
// #ifdef MP-WEIXIN
let livePlayer = requirePlugin('live-player-plugin');
livePlayer.getLiveStatus({
room_id: this.liveInfo.roomid
}).then(res => {
const liveStatus = res.liveStatus;
if (liveStatus && liveStatus != this.liveInfo.live_status) {
this.changeLiveStatus(liveStatus);
}
})
.catch(err => {
console.log('get live status', err);
});
// 往后间隔1分钟或更慢的频率去轮询获取直播状态
var timer = setInterval(() => {
livePlayer
.getLiveStatus({
room_id: this.liveInfo.roomid
})
.then(res => {
const liveStatus = res.liveStatus;
if (liveStatus && liveStatus != this.liveInfo.live_status) {
this.changeLiveStatus(liveStatus);
}
if (this.$util.inArray(liveStatus, [103, 104, 106, 107])) {
clearInterval(timer);
}
})
.catch(err => {
console.log('get live status', err);
});
}, 60000);
// #endif
},
changeLiveStatus(status) {
this.$api.sendRequest({
url: '/live/api/live/modifyLiveStatus',
data: {
room_id: this.liveInfo.roomid,
status: status
},
success: res => {
if (res.code == 0) {
this.getLiveInfo();
}
}
});
},
async handlerClick(roomid) {
await this.__$emitEvent({
eventName: 'live-tap', data: roomid, promiseCallback: (event, handler, awaitedResult) => {
if (!awaitedResult) return;
this.entryRoom(roomid);
}
})
},
}
};
</script>
<style lang="scss">
.live-wrap {
background: #fff;
border-radius: 16rpx;
overflow: hidden;
}
.banner-wrap {
width: 100%;
position: relative;
line-height: 1;
display: flex;
image {
width: 100%;
}
.shade {
width: 100%;
height: 100%;
position: absolute;
background: rgba($color: #888, $alpha: 0.3);
left: 0;
top: 0;
z-index: 5;
}
.wrap {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
z-index: 10;
padding: 26rpx 20rpx;
box-sizing: border-box;
.room-name {
font-size: $font-size-toolbar;
color: #fff;
line-height: 1;
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: flex;
align-items: center;
.status-name {
display: inline-block;
font-size: $font-size-activity-tag;
color: #fff;
padding: 8rpx 12rpx;
background-color: rgba(0, 0, 0, 0.6);
border-radius: 36rpx;
margin-right: 20rpx;
.icon-zhibozhong {
font-size: $font-size-activity-tag;
color: #fff;
margin-right: 9rpx;
}
}
}
}
}
.room-info {
padding: 20rpx 30rpx;
background: #fff;
display: flex;
.anchor-img {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
overflow: hidden;
margin-right: 20rpx;
}
.anchor-name,
.goods-text {
font-size: $font-size-base;
line-height: 60rpx;
}
.separate {
color: #808080;
margin: 0 10rpx;
line-height: 56rpx;
}
}
</style>
<style scoped>
.coupon-all>>>.uni-scroll-view::-webkit-scrollbar {
display: none;
}
</style>

View File

@@ -0,0 +1,260 @@
<template>
<view data-component-name="diy-many-goods-list" class="many-goods-list">
<scroll-view scroll-x="true" class="many-goods-list-head" :scroll-into-view="'a' + cateIndex"
:style="manyWrapCss">
<view v-for="(item, index) in value.list" class="scroll-item" :class="{ active: index == cateIndex }"
:id="'a' + index" :key="index" @tap.stop="handlerClick({ item, index })">
<view class="split-line" v-if="index > 0"></view>
<view class="cate">
<view class="name" :style="{ color: value.headStyle.titleColor }">{{ item.title }}</view>
<view class="desc" :class="{ 'color-base-bg': index == cateIndex && item.desc }">{{ item.desc }}
</view>
</view>
</view>
</scroll-view>
<view class="many-goods-list-fill" :style="{ 'height': manyInfo.height }" v-if="fixedTop"></view>
<diy-goods-list class="many-goods-list-body" v-if="goodsValue" :value="goodsValue"
ref="diyGoodsList"></diy-goods-list>
</view>
</template>
<script>
import DiyMinx from './minx.js'
export default {
name: 'diy-many-goods-list',
props: {
value: {
type: Object,
default: () => {
return {};
}
},
scrollTop: {
type: [Number, String]
},
global: {
type: Object,
default: () => {
return {};
}
}
},
data() {
return {
cateIndex: 0, // 当前选中的分类id
goodsValue: null, // 商品列表数据
manyInfo: {
bodyHeight: 0,
bodyTop: 0,
height: 0,
top: 0
}
};
},
created() {
this.changeCateIndex(this.value.list[0], 0, true);
},
mixins: [DiyMinx],
watch: {
// 组件刷新监听
componentRefresh: function (nval) {
this.changeCateIndex(this.value.list[0], 0, true);
},
scrollTop: function (nval) {
const query = uni.createSelectorQuery().in(this);
query
.select('.many-goods-list')
.boundingClientRect(data => {
if (data) {
this.manyInfo.top = data.top;
}
})
.exec();
query
.select('.many-goods-list .many-goods-list-body')
.boundingClientRect(data => {
if (data) {
this.manyInfo.bodyHeight = (data.height || 0);
this.manyInfo.bodyTop = (data.top || 0);
}
})
.exec();
}
},
computed: {
fixedTop() {
let diyPositionObj = JSON.parse(JSON.stringify(this.$store.state.diyGroupPositionObj));
let positionHeight = 0;
let height = 0;
delete diyPositionObj.diyManyGoodsList;
if (diyPositionObj) {
let arr = Object.values(diyPositionObj);
arr.forEach((item, index) => {
positionHeight += item.originalVal; //定位的高度【搜索框+导航分类+自定义头部】
});
}
if (this.manyInfo.top < positionHeight && (this.manyInfo.bodyTop + this.manyInfo.bodyHeight >
positionHeight + Number.parseFloat(this.manyInfo.height))) {
height = positionHeight;
}
return height;
},
manyWrapCss() {
let html = '';
html += `position: ${this.fixedTop ? 'fixed' : 'initial'};`
html += `top: ${this.fixedTop}px;`
if (!this.global.topNavBg)
html += `background-color: #fff;`
else
html += `background-color: ${this.fixedTop ? this.global.topNavColor : 'transparent'};`
return html;
}
},
mounted() {
const query = uni.createSelectorQuery().in(this);
query
.select('.many-goods-list .many-goods-list-head')
.boundingClientRect(data => {
if (data) {
this.manyInfo.height = (data.height || 0) + 'px';
// 向vuex中的diyIndexPositionObj增加多商品组件定位位置
let diyManyGoodsList = {
originalVal: data.height || 0 //自身高度 px
}
this.$store.commit('setDiyGroupPositionObj', {
diyManyGoodsList: diyManyGoodsList
});
}
})
.exec();
},
methods: {
changeCateIndex(item, index, isFirst) {
this.cateIndex = index;
this.goodsValue = {
sources: item.sources,
categoryId: item.categoryId,
categoryName: item.categoryName,
goodsId: item.goodsId,
componentBgColor: this.value.componentBgColor,
componentAngle: this.value.componentAngle,
topAroundRadius: this.value.topAroundRadius,
bottomAroundRadius: this.value.bottomAroundRadius,
elementBgColor: this.value.elementBgColor,
elementAngle: this.value.elementAngle,
topElementAroundRadius: this.value.topElementAroundRadius,
bottomElementAroundRadius: this.value.bottomElementAroundRadius,
count: this.value.count,
nameLineMode: this.value.nameLineMode,
template: this.value.template,
style: this.value.style,
ornament: this.value.ornament,
sortWay: this.value.sortWay,
saleStyle: this.value.saleStyle,
tag: this.value.tag,
btnStyle: this.value.btnStyle,
goodsNameStyle: this.value.goodsNameStyle,
theme: this.value.theme,
priceStyle: this.value.priceStyle,
slideMode: this.value.slideMode,
imgAroundRadius: this.value.imgAroundRadius,
margin: this.value.margin,
goodsMarginType: this.value.goodsMarginType,
goodsMarginNum: this.value.goodsMarginNum
};
// 如果是第一次加载,不需要执行下面代码
if (isFirst) return;
this.$refs.diyGoodsList.goodsValue = this.goodsValue;
this.$refs.diyGoodsList.getGoodsList();
},
async handlerClick({ item, index }) {
await this.__$emitEvent({
eventName: 'many-goods-list-tap', data: { item, index }, promiseCallback: (event, handler, awaitedResult) => {
if (!awaitedResult) return;
this.changeCateIndex(item, index, false);
}
})
},
}
};
</script>
<style lang="scss" scoped>
.many-goods-list-head {
left: 0;
right: 0;
z-index: 5;
background-color: #fff;
}
scroll-view {
width: 100%;
white-space: nowrap;
box-sizing: border-box;
padding: 20rpx 0;
.scroll-item {
display: inline-block;
text-align: center;
vertical-align: top;
width: calc(25% - 40rpx);
position: relative;
padding: 0 20rpx;
&:first-child {
width: calc(25% - 20rpx);
padding-left: 0;
}
.split-line {
display: inline-block;
width: 1rpx;
height: 30rpx;
background-color: #e5e5e5;
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
}
&.active {
.name {
font-weight: bold;
}
.desc {
color: #ffffff;
border-radius: 20rpx;
}
}
.name {
font-size: 32rpx;
color: $color-title;
line-height: 1;
}
.cate {
display: inline-block;
}
.desc {
font-size: $font-size-tag;
color: $color-tip;
height: 36rpx;
line-height: 36rpx;
margin-top: 10rpx;
min-width: 120rpx;
max-width: 220rpx;
overflow: hidden;
text-overflow: ellipsis;
padding: 0 10rpx;
}
}
}
</style>

View File

@@ -0,0 +1,89 @@
<template>
<view data-component-name="diy-map" class="diy-map" :style="style">
<view class="fui-list-group merchgroup" style="margin-top:0" v-for="(item, index) in value.list">
<map id="map" style="width: 100%; height:600rpx" scale="12" :markers="markerst" bindupdated="bindupdated"
:longitude="item.lng" :latitude="item.lat" show-location>
<!-- <cover-view
style="position:absolute;right:10px;bottom:30rpx;z-index:99999;background:#4390FF;padding:5px 10px;wxcs_style_padding:10rpx 20rpx;border-radius:8rpx;color: #fff;"
@tap.stop="handlerClick(item)" @tap="handlerClick(item)">
<cover-view style="font-size:24rpx">一键导航</cover-view>
</cover-view> -->
<!-- 使用非原生cover-view, 解决原生cover-view组件渲染机制z-index失效的问题 -->
<div
style="position:absolute;right:12rpx;bottom:48rpx;z-index:1;background:#4390FF;padding:0rpx 20rpx;border-radius:8rpx;color: #fff;"
@tap.stop="handlerClick(item)">
<span style="font-size:24rpx;color: #fff;">一键导航</span>
</div>
</map>
</view>
</view>
</template>
<script>
// 地图
import DiyMinx from './minx.js'
export default {
name: 'diy-map',
props: {
value: {
type: Object
}
},
data() {
return {
loading: true,
markers: []
};
},
created() {
},
mixins: [DiyMinx],
watch: {
// 组件刷新监听
componentRefresh: function (nval) {
// this.getDataList();
}
},
computed: {
markerst() {
return [{
id: 1,
latitude: this.value.list[0].lat,
longitude: this.value.list[0].lng
}]
},
style() {
var css = '';
css += 'background-color:' + this.value.contentBgColor + ';';
if (this.value.elementAngle == 'round') {
css += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
css += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
css += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
css += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
}
return css;
}
},
methods: {
tomap(item) {
uni.openLocation({
latitude: parseFloat(item.lat),
longitude: parseFloat(item.lng),
name: "一键导航",
})
},
async handlerClick(item) {
await this.__$emitEvent({
eventName: 'map-tap', data: item, promiseCallback: (event, handler, awaitedResult) => {
if (!awaitedResult) return;
this.tomap(item);
}
})
}
}
};
</script>
<style lang="scss"></style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,279 @@
<template>
<view data-component-name="diy-member-my-order" class="common-wrap" :style="warpCss">
<view class="order-wrap">
<view class="status-wrap">
<view class="item-wrap" @tap.stop="redirect('/pages_order/list?status=waitpay')"
style="margin-right: 10rpx;">
<view class="icon-block">
<template v-if="value.style == 3">
<image :src="$util.img('public/uniapp/member/order/wait_pay.png')" mode="widthFix" />
<view class="icon-shade"
:style="'-webkit-mask-image: url(' + $util.img('public/uniapp/member/order/wait_pay_shade.png') + ')'">
</view>
</template>
<template v-else>
<diy-icon :icon="value.icon.waitPay.icon" v-if="value.icon.waitPay"
:value="value.icon.waitPay.style ? value.icon.waitPay.style : null"></diy-icon>
</template>
<text v-if="orderNum.waitpay > 0" class="order-num color-base-bg price-font">{{ orderNum.waitpay
> 99 ? '99+' :
orderNum.waitpay }}</text>
</view>
<view class="title">{{ $lang('waitpay') }}</view>
</view>
<view class="item-wrap" @tap.stop="redirect('/pages_order/list?status=waitsend')"
style="margin-right: 10rpx;">
<view class="icon-block">
<template v-if="value.style == 3">
<image :src="$util.img('public/uniapp/member/order/wait_send.png')" mode="widthFix"></image>
<view class="icon-shade"
:style="'-webkit-mask-image: url(' + $util.img('public/uniapp/member/order/wait_send_shade.png') + ')'">
</view>
</template>
<template v-else>
<diy-icon :icon="value.icon.waitSend.icon" v-if="value.icon.waitSend"
:value="value.icon.waitSend.style ? value.icon.waitSend.style : null"></diy-icon>
</template>
<text v-if="orderNum.waitsend > 0" class="order-num color-base-bg price-font">{{
orderNum.waitsend > 99 ? '99+'
: orderNum.waitsend }}</text>
</view>
<view class="title">{{ $lang('waitsend') }}</view>
</view>
<view class="item-wrap" @tap.stop="redirect('/pages_order/list?status=waitconfirm')"
style="margin-right: 10rpx;">
<view class="icon-block">
<template v-if="value.style == 3">
<image :src="$util.img('public/uniapp/member/order/wait_confirm.png')" mode="widthFix" />
<view class="icon-shade"
:style="'-webkit-mask-image: url(' + $util.img('public/uniapp/member/order/wait_confirm_shade.png') + ')'">
</view>
</template>
<template v-else>
<diy-icon :icon="value.icon.waitConfirm.icon" v-if="value.icon.waitConfirm"
:value="value.icon.waitConfirm.style ? value.icon.waitConfirm.style : null"></diy-icon>
</template>
<text v-if="orderNum.waitconfirm > 0" class="order-num color-base-bg price-font">{{
orderNum.waitconfirm > 99 ?
'99+' : orderNum.waitconfirm }}</text>
</view>
<view class="title">{{ $lang('waitconfirm') }}</view>
</view>
<view class="item-wrap" @tap.stop="redirect('/pages_order/list?status=waitrate')"
style="margin-right: 10rpx;">
<view class="icon-block">
<template v-if="value.style == 3">
<image :src="$util.img('public/uniapp/member/order/wait_use.png')" mode="widthFix" />
<view class="icon-shade"
:style="'-webkit-mask-image: url(' + $util.img('public/uniapp/member/order/wait_rate_shade.png') + ')'">
</view>
</template>
<template v-else>
<diy-icon :icon="value.icon.waitUse.icon" v-if="value.icon.waitUse"
:value="value.icon.waitUse.style ? value.icon.waitUse.style : null"></diy-icon>
</template>
<!-- <text v-if="orderNum.wait_use > 0" class="order-num color-base-bg price-font">{{ orderNum.wait_use > 99 ? '99+' : orderNum.wait_use }}</text> -->
</view>
<view class="title">{{ $lang('completed') }}</view>
</view>
<view class="item-wrap" @tap.stop="redirect('/pages_tool/order/activist')">
<view class="icon-block">
<template v-if="value.style == 3">
<image :src="$util.img('public/uniapp/member/order/refunding.png')" mode="widthFix" />
<view class="icon-shade"
:style="'-webkit-mask-image: url(' + $util.img('public/uniapp/member/order/refunding_shade.png') + ')'">
</view>
</template>
<template v-else>
<diy-icon :icon="value.icon.refunding.icon" v-if="value.icon.refunding"
:value="value.icon.refunding.style ? value.icon.refunding.style : null"></diy-icon>
</template>
<text v-if="orderNum.refunding > 0" class="order-num color-base-bg price-font">{{
orderNum.refunding > 99 ?
'99+' : orderNum.refunding }}</text>
</view>
<view class="title">{{ $lang('activist') }}</view>
</view>
</view>
</view>
<ns-login ref="login"></ns-login>
</view>
</template>
<script>
// 自定义会员中心——我的订单展示
import DiyMinx from './minx.js'
export default {
name: 'diy-member-my-order',
props: {
value: {
type: Object
}
},
mixins: [DiyMinx],
data() {
return {
orderNum: {
waitpay: 0,
waitsend: 0,
waitconfirm: 0,
refunding: 0,
wait_use: 0,
waitrate: 0
}
};
},
created() {
this.init();
},
watch: {
storeToken(nVal, oVal) {
this.init();
},
// 组件刷新监听
componentRefresh: function (nval) {
this.init();
}
},
computed: {
warpCss() {
var obj = '';
obj += 'background-color:' + this.value.componentBgColor + ';';
if (this.value.componentAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
}
return obj;
}
},
methods: {
init() {
if (this.storeToken) {
this.getOrderNum();
} else {
this.orderNum = {
waitpay: 0,
waitsend: 0,
waitconfirm: 0,
refunding: 0,
wait_use: 0,
waitrate: 0
};
}
},
/**
* 获取订单数量
*/
getOrderNum() {
this.$api.sendRequest({
url: '/api/order/num',
data: {
order_status: 'waitpay,waitsend,waitconfirm,refunding,wait_use,waitrate'
},
success: res => {
if (res.code == 0) {
this.orderNum = res.data;
} else {
this.orderNum = {
waitpay: 0,
waitsend: 0,
waitconfirm: 0,
refunding: 0,
wait_use: 0,
waitrate: 0
};
}
}
});
},
/**
* 跳转
* @param {Object} url
*/
redirect(url) {
if (this.storeToken) {
this.$util.redirectTo(url);
} else {
this.$refs.login.open(url);
}
}
}
};
</script>
<style lang="scss">
.common-wrap {
width: 100%;
box-sizing: border-box;
}
.order-wrap {
.status-wrap {
display: flex;
padding: 30rpx 0;
align-items: center;
justify-content: center;
color: #333;
}
.item-wrap {
flex: 1;
text-align: center;
background: #f6f7f9;
padding: 20rpx 0;
.icon-block {
width: 60rpx;
height: 60rpx;
font-size: 60rpx;
margin: 4rpx auto;
position: relative;
&>image {
position: absolute;
top: 5%;
right: 5%;
width: 90%;
height: 90%;
z-index: 5;
}
.icon-shade {
width: 90%;
height: 90%;
position: absolute;
z-index: 4;
top: 5%;
right: 5%;
background: $base-color;
-webkit-mask: no-repeat center / contain;
}
}
.order-num {
position: absolute;
top: 0;
right: 0;
transform: translate(50%, -50%);
box-sizing: border-box;
color: #ffffff;
line-height: 1.2;
text-align: center;
font-size: 24rpx;
padding: 0 6rpx;
min-width: 30rpx;
border-radius: 16rpx;
height: 30rpx;
display: flex;
align-items: center;
justify-content: center;
}
.title {
font-size: 26rpx;
}
}
}
</style>

View File

@@ -0,0 +1,365 @@
<template>
<view class="diy-merch-list">
<x-skeleton type="list" :loading="loading" :configs="skeletonConfig" v-if="value.ornament.type == 'default'">
<view class="merch-wrap" :style="warpCss">
<view :class="['list-wrap', value.style]" :style="warpCss">
<view :class="['item', value.ornament.type]" v-for="(item, index) in list" :key="index"
:style="itemCss" @tap.stop="handlerClick(item)">
<view class="merch-img">
<image class="cover-img" :src="$util.img(item.merch_image)" mode="widthFix"
@error="imgError(index)" />
</view>
<view class="info-wrap">
<text class="title">{{ item.merch_name }}</text>
<text class="desc">{{ item.desc }}</text>
<view class="read-wrap"></view>
</view>
</view>
</view>
</view>
</x-skeleton>
<view v-else :style="warpCss">
<scroll-view :scroll-x="true" :class="['merch-nav', 'singleSlide']">
<!-- #ifdef MP -->
<view class="uni-scroll-view-content">
<!-- #endif -->
<view class="merch-nav-item graphic" v-for="(item, index) in list" :key="index"
:style="{ width: 100 / 4 + '%' }" @tap.stop="handlerClick(item)">
<view class="graphic-img" v-if="value.mode != 'text'"
:style="{ fontSize: value.imageSize * 2 + 'rpx', width: value.imageSize * 2 + 'rpx', height: value.imageSize * 2 + 'rpx' }">
<image
:src="$util.img(item.merch_image) || $util.img('public/uniapp/default_img/goods.png')"
mode="aspectFill" :show-menu-by-longpress="true"
style="max-width: 80rpx; max-height: 80rpx; border-radius: 50rpx;" />
</view>
<text class="graphic-text"
style="font-size: 28rpx; font-weight: normal; color: rgb(48, 49, 51);">
{{ item.merch_name }}
</text>
</view>
<!-- #ifdef MP -->
</view>
<!-- #endif -->
</scroll-view>
</view>
</view>
</template>
<script>
// 文章
import DiyMinx from './minx.js'
export default {
name: 'diy-article',
props: {
value: {
type: Object
}
},
data() {
return {
list: [
/*{
merch_name:'品牌',
merch_image:'http://saas.cn//upload/4/common/images/20240824/20240824040223172448654356147.jpg'
},
{
merch_name:'品牌',
merch_image:'http://saas.cn//upload/4/common/images/20240824/20240824040223172448654356147.jpg'
},
{
merch_name:'品牌',
merch_image:'http://saas.cn//upload/4/common/images/20240824/20240824040223172448654356147.jpg'
},
{
merch_name:'品牌',
merch_image:'http://saas.cn//upload/4/common/images/20240824/20240824040223172448654356147.jpg'
},
{
merch_name:'品牌',
merch_image:'http://saas.cn//upload/4/common/images/20240824/20240824040223172448654356147.jpg'
},
{
merch_name:'品牌',
merch_image:'http://saas.cn//upload/4/common/images/20240824/20240824040223172448654356147.jpg'
},
{
merch_name:'品牌',
merch_image:'http://saas.cn//upload/4/common/images/20240824/20240824040223172448654356147.jpg'
}*/
],
loading: true,
skeletonConfig: {
gridRows: 1,
gridRowsGap: '40rpx',
headWidth: '160rpx',
headHeight: '160rpx',
textRows: 2
}
};
},
created() {
this.getList();
},
mixins: [DiyMinx],
watch: {
// 组件刷新监听
componentRefresh: function (nval) {
this.getList();
}
},
computed: {
warpCss() {
var obj = '';
obj += 'background-color:' + this.value.componentBgColor + ';';
if (this.value.componentAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
}
console.log(obj)
return obj;
},
// 子项样式
itemCss() {
var obj = '';
obj += 'background-color:' + this.value.elementBgColor + ';';
if (this.value.elementAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
}
if (this.value.ornament.type == 'shadow') {
obj += 'box-shadow:' + '0 0 10rpx ' + this.value.ornament.color;
}
if (this.value.ornament.type == 'stroke') {
obj += 'border:' + '2rpx solid ' + this.value.ornament.color;
}
return obj;
}
},
methods: {
getList() {
console.log(121)
var data = {
num: this.value.count
};
console.log(this.value)
if (this.value.sources == 'diy') {
data.num = 0;
data.merch_id_arr = this.value.merchIds.toString();
}
this.$api.sendRequest({
url: '/merch/api/merch/lists',
data: data,
success: res => {
if (res.code == 0 && res.data) {
let data = res.data;
this.list = data;
}
this.loading = false;
}
});
},
toDetail(item) {
this.$util.redirectTo('/pages_promotion/merch/detail', {
merch_id: item.merch_id
});
},
imgError(index) {
if (this.list[index]) this.list[index].merch_image = this.$util.getDefaultImage().article;
},
async handlerClick(item) {
await this.__$emitEvent({
eventName: 'merch-list-tap', data: item, promiseCallback: (event, handler, awaitedResult) => {
if (!awaitedResult) return;
this.toDetail(item);
}
})
}
}
};
</script>
<style>
/* 单行滑动 */
.merch-nav.singleSlide>>>.uni-scroll-view-content {
display: flex;
}
</style>
<style lang="scss">
.merch-nav {
padding: 16rpx;
box-sizing: border-box;
&.singleSlide {
.merch-nav-item {
flex-shrink: 0;
}
}
&.pageSlide {
position: relative;
.merch-nav-wrap {
display: flex;
flex-wrap: wrap;
width: 100%;
height: 100%;
}
}
.merch-nav-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 14rpx 0;
box-sizing: border-box;
.graphic-text {
padding-top: 12rpx;
line-height: 1.5;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
width: 100%;
text-align: center;
&.alone {
padding-top: 0;
}
}
&.text {
.graphic-text {
padding-top: 0;
}
}
.graphic-img {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100rpx;
height: 100rpx;
font-size: 24rpx;
.tag {
position: absolute;
top: -10rpx;
right: -24rpx;
color: #fff;
border-radius: 24rpx;
border-bottom-left-radius: 0;
transform: scale(0.8);
padding: 8rpx 16rpx;
line-height: 1;
font-size: 24rpx;
}
.icon {
font-size: 50rpx;
color: $color-sub;
}
}
}
}
.merch-wrap {
.list-wrap {
&.style-1 {
.item {
display: flex;
padding: 20rpx;
margin-top: 24rpx;
&:first-of-type {
margin-top: 0;
}
.merch-img {
margin-right: 20rpx;
width: 160rpx;
height: 160rpx;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
image {
width: 100%;
}
}
.info-wrap {
// flex: 1;
// display: flex;
// flex-direction: column;
// justify-content: space-between;
.desc {
color: #888
}
.title {
font-weight: bold;
// margin-bottom: 10rpx;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
font-size: 30rpx;
line-height: 1.5;
}
.abstract {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
font-size: $font-size-tag;
}
.read-wrap {
display: flex;
color: #999ca7;
justify-content: flex-start;
align-items: center;
margin-top: 10rpx;
line-height: 1;
text {
font-size: $font-size-tag;
}
.iconfont {
font-size: 36rpx;
vertical-align: bottom;
margin-right: 10rpx;
}
.category-icon {
width: 8rpx;
height: 8rpx;
border-radius: 50%;
background: $base-color;
margin-right: 10rpx;
}
.date {
margin-left: 20rpx;
}
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,369 @@
<template>
<x-skeleton data-component-name="diy-notes" type="list" :loading="loading" :configs="skeletonConfig">
<view class="diy-notes" :style="{ backgroundColor: value.componentBgColor }">
<view class="diy-notes-top">
<view class="notes-title" :style="{ color: value.titleTextColor }">{{ value.title }}</view>
<view class="notes-more" @tap.stop="toMore()" :style="{ color: value.moreTextColor }">{{ value.more }}
</view>
</view>
<scroll-view class="diy-notes-box" scroll-x="true" show-scrollbar="true">
<view class="notes-box-item" v-for="(item, i) in dataList" :key="i" @tap.stop="handlerClick(item)" :style="notesItemStyle">
<view class="notes-item" v-if="item.status == 1">
<view class="notes-item-con">
<view class="notes-title">{{ item.note_title }}</view>
<view class="notes-highlights-list" v-if="value.notesLabel == 1 && item.goods_highlights">
<text class="color-base-bg" v-for="(labelItem, labelIndex) in item.label"
:key="labelIndex">{{ labelItem }}</text>
</view>
<view class="notes-intro">
<text class="notes-label color-base-text">#{{ item.note_type == 'goods_item' ? '单品介绍' :
'掌柜说' }}#</text>
{{ item.note_abstract }}
</view>
</view>
<view class="notes-img-wrap" :class="{ 'notes-img-wrap-list': item.cover_type == 1 }">
<image v-if="item.cover_type == 0" :src="$util.img(item.img)" @error="imageError(i)"
mode="aspectFill" class="notes-item-image" />
<image v-else v-for="(imgItem, imgIndex) in item.img" :key="imgIndex"
:src="$util.img(imgItem)" @error="imageError(i)" mode="aspectFit"
class="notes-item-image-li" />
</view>
<view class="notes-item-con">
<view class="notes-info"
v-if="(value.readNum == 1 && item.is_show_read_num == 1) || (value.uploadTime == 1 && item.is_show_release_time == 1)">
<view class="notes-num">
<text v-show="value.uploadTime == 1 && item.is_show_release_time == 1">{{
item.update_time_day }}</text>
</view>
<view class="notes-num">
<text v-show="value.readNum == 1 && item.is_show_read_num == 1">阅读 {{
item.initial_read_num + item.read_num }}</text>
</view>
</view>
</view>
</view>
</view>
</scroll-view>
<ns-login ref="login"></ns-login>
</view>
</x-skeleton>
</template>
<script>
import DiyMinx from './minx.js'
export default {
name: 'diy-notes',
props: {
value: {
type: Object
}
},
data() {
return {
loading: true,
skeletonConfig: {
itemDirection: 'column',
headWidth: '100%',
headHeight: '320rpx',
textRows: 2,
textWidth: ['100%', '80%']
},
dataList: [],
giveLikeFlag: false
};
},
created() {
this.getDataList();
},
mixins: [DiyMinx],
watch: {
// 组件刷新监听
componentRefresh: function (nval) {
this.getDataList();
}
},
computed: {
notesItemStyle() {
var css = '';
css += 'background-color:' + this.value.contentBgColor + ';';
if (this.value.elementAngle == 'round') {
css += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
css += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
css += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
css += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
}
return css;
}
},
methods: {
refresh() {
this.getDataList();
},
getDataList() {
var data = {
num: this.value.count
};
if (this.value.sources == 'diy') {
data.num = 0;
data.note_id_arr = this.value.noteId.toString();
}
this.$api.sendRequest({
url: '/notes/api/notes/lists',
data: data,
success: res => {
var data = res.data;
this.dataList = [];
if (data) {
for (var i = 0; i < data.length; i++) {
var item = {};
item = data[i];
if (data[i].cover_type == 1) {
item.img = data[i].cover_img.split(',');
} else {
item.img = data[i].cover_img;
}
if (data[i].upload_time) {
item.update_time_day = this.$util.timeStampTurnTime(data[i].upload_time)
.split(' ')[0];
} else {
item.update_time_day = this.$util.timeStampTurnTime(data[i].create_time)
.split(' ')[0];
}
item.label = data[i].goods_highlights.split(',');
this.dataList.push(item);
}
}
this.loading = false;
}
});
},
toMore() {
this.$util.redirectTo('/pages_tool/store_notes/note_list');
},
toDetail(id) {
this.$util.redirectTo('/pages_tool/store_notes/note_detail', {
note_id: id
});
},
/* 点赞 */
giveLike(noteId, index) {
if (!this.storeToken) {
this.$refs.login.open(this.$util.INDEX_PAGE_URL);
return;
}
if (this.giveLikeFlag) return false;
this.giveLikeFlag = true;
var url = this.dataList[index].is_dianzan == 1 ? '/notes/api/record/delete' : '/notes/api/record/add';
this.$api.sendRequest({
url: url,
data: {
note_id: noteId
},
success: res => {
this.giveLikeFlag = false;
if (res.code == 0 && res.data > 0) {
if (this.noteType != 'goods_item')
this.dataList[index].dianzan_num = this.dataList[index].is_dianzan == 1 ? this.dataList[index].dianzan_num - 1 : this.dataList[index].dianzan_num + 1;
else {
this.dataList[index].dianzan_num = this.dataList[index].is_dianzan == 1 ? this.dataList[index].dianzan_num - 1 : this.dataList[index].dianzan_num + 1;
}
this.dataList[index].is_dianzan = this.dataList[index].is_dianzan == 1 ? 0 : 1;
} else {
this.$util.showToast({
title: res.message
});
}
}
});
},
imageError(index) {
this.dataList[index].img = this.$util.getDefaultImage().goods;
this.$forceUpdate();
},
async handlerClick(item) {
await this.__$emitEvent({
eventName: 'notes-tap', data: item, promiseCallback: (event, handler, awaitedResult) => {
if (!awaitedResult) return;
this.toDetail(item.note_id);
}
})
}
}
};
</script>
<style lang="scss">
scroll-view ::-webkit-scrollbar {
width: 0;
height: 0;
background-color: transparent;
touch-action: none;
}
.diy-notes {
width: 100%;
box-sizing: border-box;
}
.diy-notes-top {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
box-sizing: border-box;
.notes-title {
width: 70%;
font-size: 28rpx;
font-weight: 600;
}
.notes-more {
font-size: $font-size-tag;
color: #858585;
}
.notes-more::after {
font-family: 'iconfont';
content: '\e6a3';
font-size: $font-size-tag;
line-height: 1;
position: relative;
margin-left: 4rpx;
}
}
.diy-notes-box {
width: 100%;
padding: 30rpx 0 20rpx;
}
.notes-box-item {
width: calc(100% - 8rpx);
margin: 0 auto;
height: 100%;
margin-bottom: 30rpx;
border-radius: 10rpx;
overflow: hidden;
-moz-box-shadow: 0 0 20rpx rgba(0, 0, 0, 0.02);
-webkit-box-shadow: 0 0 20rpx rgba(0, 0, 0, 0.02);
box-shadow: 0 0 20rpx rgba(0, 0, 0, 0.02);
.notes-item {
width: 100%;
height: 100%;
padding: 20rpx;
box-sizing: border-box;
}
.notes-img-wrap {
position: relative;
height: 300rpx;
.notes-item-image {
width: 100%;
height: 300rpx;
object-fit: cover;
}
.notes-label {
display: inline-block;
position: absolute;
left: 20rpx;
bottom: 20rpx;
max-width: calc(100vh - 40rpx);
background-color: #ffffff;
line-height: 36rpx;
padding: 0 10rpx 0 4rpx;
}
}
.notes-img-wrap-list {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
height: auto;
image {
width: calc((100% - 40rpx) / 3);
height: 210rpx;
margin-top: 20rpx;
&:nth-child(-n + 3) {
margin-top: 0;
}
}
&:after {
content: '';
width: calc((100% - 40rpx) / 3);
}
}
.notes-item-con {
.notes-title {
font-size: 30rpx;
font-weight: 600;
line-height: 44rpx;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.notes-highlights-list {
text {
display: inline-block;
color: #ffffff;
font-size: 24rpx;
line-height: 36rpx;
padding: 0 10rpx;
border-radius: 4rpx;
margin: 0 5rpx;
}
}
.notes-intro {
margin: 4rpx 0 8rpx;
line-height: 40rpx;
overflow: hidden;
text {
float: left;
margin-right: 16rpx;
}
}
.notes-info {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20rpx;
.notes-num {
>text {
display: flex;
align-items: center;
font-size: $font-size-tag;
color: #969799;
text.iconfont {
font-size: 26rpx;
margin-right: 6rpx;
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,309 @@
<template>
<view data-component-name="diy-notice" class="diy-notice">
<view :class="['notice', value.contentStyle]" :style="noticeWrapCss">
<image v-if="value.iconType == 'img'" class="notice-img" :src="$util.img(value.imageUrl)"
mode="heightFix" />
<diy-icon v-if="value.iconType == 'icon'" :icon="value.icon" :value="value.style ? value.style : 'null'"
:style="{ maxWidth: 30 * 2 + 'rpx', maxHeight: 30 * 2 + 'rpx', width: '100%', height: '100%' }"></diy-icon>
<view class="notice-xian"></view>
<view class="main-wrap">
<!-- 横向滚动 -->
<view class="horizontal-wrap" v-if="value.scrollWay == 'horizontal'">
<view class="marquee-wrap">
<view class="marquee" :style="marqueeStyle">
<text v-for="(item, index) in list" :key="index"
:style="{ color: value.textColor, fontSize: value.fontSize * 2 + 'rpx', fontWeight: value.fontWeight }">{{
item.title }}</text>
</view>
<view class="marquee" :style="marqueeAgainStyle">
<text v-for="(item, index) in list" :key="index"
:style="{ color: value.textColor, fontSize: value.fontSize * 2 + 'rpx', fontWeight: value.fontWeight }">{{
item.title }}</text>
</view>
</view>
</view>
<!-- 上下滚动 -->
<template v-if="value.scrollWay == 'upDown'">
<swiper :vertical="true" :duration="500" autoplay="true" circular="true">
<swiper-item v-for="(item, index) in list" :key="index" @touchmove.prevent.stop>
<text class="beyond-hiding using-hidden"
:style="{ color: value.textColor, fontSize: value.fontSize * 2 + 'rpx', fontWeight: value.fontWeight }">
{{ item.title }}
</text>
</swiper-item>
</swiper>
</template>
</view>
</view>
<view @touchmove.prevent.stop>
<uni-popup ref="noticePopup" type="center">
<view class="notice-popup">
<view class="head-wrap" @tap.stop="closeNoticePopup">
<text>公告</text>
<text class="iconfont icon-close"></text>
</view>
<view class="content-wrap">{{ notice }}</view>
<button type="primary" @tap.stop="closeNoticePopup">我知道了</button>
</view>
</uni-popup>
</view>
</view>
</template>
<script>
// 公告展示
import DiyMinx from './minx.js'
export default {
name: 'diy-notice',
props: {
value: {
type: Object
}
},
mixins: [DiyMinx],
data() {
return {
list: [],
notice: '', // 当前点击的弹框内容
marqueeWrapWidth: 0, // 容器宽度
marqueeWidth: 0, // 公告内容累加宽度
marqueeStyle: '', // 横向滚动样式
marqueeAgainStyle: '', // 横向滚动复制样式
time: 0, // 滚动完成时间
delayTime: 1000 // 动画延迟时间
};
},
watch: {
// 组件刷新监听
componentRefresh: function (nval) {
if (this.value.sources == 'initial') this.getData();
}
},
created() { },
mounted() {
// 数据源:公告系统
if (this.value.sources == 'initial') {
this.getData();
} else {
this.list = this.value.list;
this.bindCrossSlipEvent();
}
},
computed: {
noticeWrapCss: function () {
var obj = '';
obj += 'background-color:' + this.value.componentBgColor + ';';
if (this.value.componentAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
}
return obj;
}
},
methods: {
getData() {
var data = {
page_size: 0
};
if (this.value.sources == 'initial') {
data.page_size = this.value.count;
}
if (this.value.noticeIds.length) {
data.id_arr = this.value.noticeIds.toString();
data.page_size = 0;
}
this.$api.sendRequest({
url: '/api/notice/page',
data: data,
success: res => {
if (res.code == 0 && res.data) {
this.list = res.data.list;
this.bindCrossSlipEvent();
}
}
});
},
toLink(item) {
if (this.value.sources == 'initial') {
this.$util.redirectTo('/pages_tool/notice/detail', {
notice_id: item.id
});
} else if (!item) {
this.$util.redirectTo('/pages_tool/notice/list');
} else if (Object.keys(item.link).length > 1) {
this.$util.diyRedirectTo(item.link);
} else {
// 如果不设置跳转链接,则点击弹框展示
this.notice = item.title;
this.$refs.noticePopup.open();
}
},
closeNoticePopup() {
this.$refs.noticePopup.close();
},
// 绑定横向滚动事件
bindCrossSlipEvent() {
if (this.value.scrollWay == 'horizontal') {
setTimeout(() => {
this.$nextTick(() => {
uni.createSelectorQuery()
.in(this)
.select('.marquee-wrap')
.boundingClientRect(res => {
this.marqueeWrapWidth = res.width;
const query = uni.createSelectorQuery().in(this);
query
.select('.marquee')
.boundingClientRect(data => {
this.marqueeWidth = data.width + 30; // 30px是间距
this.time = Math.ceil(this.marqueeWidth * 10);
if (this.marqueeWrapWidth > this.marqueeWidth) {
this.marqueeStyle = `animation: none;`;
this.marqueeAgainStyle = 'display:none;';
} else {
this.marqueeStyle = `
width: ${this.marqueeWidth}px;
animation-duration: ${this.time}ms;
animation-delay: ${this.delayTime}ms;
`;
this.marqueeAgainStyle = `
width: ${this.marqueeWidth}px;
left: ${this.marqueeWidth}px;
animation-duration: ${this.time}ms;
animation-delay: ${this.delayTime}ms;
`;
}
})
.exec();
})
.exec();
});
});
}
}
}
};
</script>
<style lang="scss">
.notice {
height: 80rpx;
position: relative;
display: flex;
align-items: center;
overflow: hidden;
padding: 20rpx 0 20rpx 20rpx;
font-size: 70rpx;
box-sizing: border-box;
.notice-img {
width: 44rpx;
height: 80rpx;
}
.notice-xian {
width: 1rpx;
height: 26rpx;
background-color: #e4e4e4;
margin: 0 22rpx;
}
}
.main-wrap {
display: inline-block;
width: calc(100% - 115rpx);
position: relative;
}
swiper {
height: 50rpx;
}
.beyond-hiding {
display: inline-block;
width: 100%;
white-space: nowrap;
}
.notice-popup {
padding: 0 30rpx 40rpx;
background-color: #fff;
.head-wrap {
font-size: $font-size-toolbar;
line-height: 100rpx;
height: 100rpx;
display: block;
text-align: center;
position: relative;
border-bottom: 2rpx solid $color-line;
margin-bottom: 20rpx;
.iconfont {
position: absolute;
float: right;
right: 0;
font-size: $font-size-toolbar;
}
}
.content-wrap {
max-height: 600rpx;
overflow-y: auto;
}
button {
margin-top: 40rpx;
}
}
.horizontal-wrap {
height: 30px;
line-height: 30px;
position: relative;
overflow: hidden;
width: 100%;
}
.marquee-wrap {
display: inline-block;
width: 100%;
height: 100%;
vertical-align: middle;
overflow: hidden;
box-sizing: border-box;
position: relative;
}
.marquee {
display: flex;
position: absolute;
white-space: nowrap;
animation: marquee 0s 0s linear infinite;
text {
margin-left: 40rpx;
&:first-child {
margin-left: 0;
}
}
}
@keyframes marquee {
0% {
transform: translateX(0);
}
100% {
transform: translateX(-100%);
}
}
</style>

View File

@@ -7,11 +7,11 @@
<!-- <text class="iconfont icon-shuaxin"></text> -->
</view>
<view class="qrocde-action">
<button type="primary" @click="toLink">
<button type="primary" @tap.stop="toLink">
<text class="iconfont icon-fukuanma"></text>
<text class="action-name">付款码</text>
</button>
<button type="primary" @click="openPaymentPopup">
<button type="primary" @tap.stop="openPaymentPopup">
<text class="iconfont icon-saomafu"></text>
<text class="action-name">扫码付</text>
</button>
@@ -26,12 +26,12 @@
<view @touchmove.prevent.stop>
<uni-popup ref="paymentPopup" type="center">
<view class="payment-popup">
<view class="head-wrap" @click="closePaymentPopup">
<view class="head-wrap" @tap.stop="closePaymentPopup">
<text>提示</text>
<text class="iconfont icon-close"></text>
</view>
<view class="content-wrap">扫码付请退出程序后直接使用微信扫一扫或返回上一页使用付款码进行支付</view>
<button type="primary" @click="closePaymentPopup">我知道了</button>
<button type="primary" @tap.stop="closePaymentPopup">我知道了</button>
</view>
</uni-popup>
</view>
@@ -39,7 +39,8 @@
</template>
<script>
//
//
import DiyMinx from './minx.js'
export default {
name: 'diy-payment-qrcode',
props: {
@@ -47,10 +48,11 @@ export default {
type: Object
}
},
mixins: [DiyMinx],
data() {
return {};
},
created() {},
created() { },
computed: {
warpCss() {
var obj = '';
@@ -59,7 +61,7 @@ export default {
},
watch: {
//
componentRefresh: function(nval) {}
componentRefresh: function (nval) { }
},
methods: {
toLink() {
@@ -72,7 +74,7 @@ export default {
// #ifndef H5
// ,h5
uni.scanCode({
success: function(res) {
success: function (res) {
console.log('条码类型:' + res.scanType);
console.log('条码内容:' + res.result);
}
@@ -95,8 +97,10 @@ export default {
display: flex;
background-color: #fff;
border-radius: 16rpx;
.qrocde-left {
flex: 1;
.qrocde-desc {
margin-top: 20rpx;
margin-bottom: 26rpx;
@@ -105,16 +109,19 @@ export default {
justify-content: center;
color: $color-tip;
font-size: $font-size-tag;
.iconfont {
margin-left: 10rpx;
font-size: $font-size-tag;
font-weight: bold;
}
}
.qrocde-action {
padding-bottom: 36rpx;
display: flex;
justify-content: center;
button {
display: flex;
align-items: center;
@@ -123,22 +130,27 @@ export default {
width: 230rpx;
height: 86rpx;
border-radius: 50rpx;
&:first-of-type {
margin-right: 46rpx;
background-color: $base-color;
}
&:last-of-type {
background-color: #999;
}
.iconfont {
margin-right: 10rpx;
}
.action-name {
font-size: 30rpx;
}
}
}
}
.qrocde-right {
position: relative;
display: flex;
@@ -149,15 +161,18 @@ export default {
width: 90rpx;
z-index: 2;
box-sizing: border-box;
.name {
font-size: $font-size-sub;
writing-mode: tb-rl;
color: #fff;
letter-spacing: 6rpx;
}
.iconfont {
color: #fff;
}
&::after {
content: '';
position: absolute;
@@ -166,12 +181,13 @@ export default {
width: 500rpx;
height: 500rpx;
border-radius: 50%;
background-color: $base-color;
background-color: $base-color;
transform: translateY(-50%);
z-index: -1;
}
}
}
.payment-popup {
padding: 0 30rpx 40rpx;
background-color: #fff;
@@ -185,6 +201,7 @@ export default {
position: relative;
border-bottom: 2rpx solid $color-line;
margin-bottom: 20rpx;
.iconfont {
position: absolute;
float: right;
@@ -192,10 +209,12 @@ export default {
font-size: $font-size-toolbar;
}
}
.content-wrap {
max-height: 600rpx;
overflow-y: auto;
}
button {
margin-top: 40rpx;
}

View File

@@ -0,0 +1,83 @@
<template>
<view data-component-name="diy-picture" class="diy-picture" :style="style">
<view class="fui-picture">
<view v-for="(item, index) in value.list" style="line-height: 0;">
<image mode="widthFix" style="width: 100%;height:auto" :src="$util.img(item.imageUrl)"
v-if="item.link.wap_url" @tap.stop="handlerClick(item)"></image>
<image mode="widthFix" style="width: 100%;height:auto" :src="$util.img(item.imageUrl)" v-else
@tap.stop="handlerClick(item)"></image>
</view>
<!-- <view wx:if="{{!childitem.linkurl}}" bindtap="previewImg" data-src="{{childitem.imgurl}}" style="padding:{{diyitem.style.paddingtop==0?0:diyitem.style.paddingtop+'rpx'}} {{diyitem.style.paddingleft==0?0:diyitem.style.paddingleft+'rpx'}}" wx:for="{{diyitem.data}}" wx:for-index="childid" wx:for-item="childitem" wx:key="{{childid}}">
<image mode="widthFix" src="{{childitem.imgurl}}" style="{{bannerheight?'height:'+bannerheight+'px':'height:auto'}}"></image>
</view> -->
</view>
</view>
</template>
<script>
import DiyMinx from './minx.js'
export default {
name: 'diy-picture',
props: {
value: {
type: Object
}
},
data() {
return {
loading: true,
};
},
created() {
// this.getDataList();
},
watch: {
// 组件刷新监听
componentRefresh: function (nval) {
// this.getDataList();
}
},
mixins: [DiyMinx],
computed: {
style() {
var css = '';
css += 'background-color:' + this.value.contentBgColor + ';';
if (this.value.elementAngle == 'round') {
css += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
css += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
css += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
css += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
}
return css;
}
},
methods: {
redirectTo(link) {
if (link.wap_url) {
if (this.$util.getCurrRoute() == this.$util.MEMBER_PAGE_URL && !this.storeToken) {
this.$refs.login.open(link.wap_url);
return;
}
}
this.$util.diyRedirectTo(link);
},
async handlerClick(item) {
await this.__$emitEvent({
eventName: 'picture-tap', data: item, promiseCallback: (event, handler, awaitedResult) => {
if (!awaitedResult) return;
const link = item.link;
if (link?.name || link?.wap_url || link?.appid) {
this.redirectTo(item.link);
} else {
this.previewImg(item.imageUrl);
}
}
})
}
}
};
</script>
<style lang="scss"></style>

View File

@@ -0,0 +1,590 @@
<template>
<x-skeleton data-component-name="diy-pinfan" :type="skeletonType" :loading="loading" :configs="skeletonConfig">
<view class="diy-pinfan" :class="[value.template, value.style]" :style="warpCss">
<template v-if="value.template == 'row1-of1'">
<view class="item" v-for="(item, index) in list" :key="index" @tap.stop="toDetail(item)"
:class="[value.ornament.type]" :style="goodsItemCss">
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix"
@error="imageError(index)" />
</view>
<view class="content"
v-if="value.goodsNameStyle.control || value.priceStyle.mainControl || value.priceStyle.lineControl || value.btnStyle.control">
<view v-if="value.goodsNameStyle.control" class="goods-name"
:style="{ color: value.theme == 'diy' ? value.goodsNameStyle.color : '', fontWeight: value.goodsNameStyle.fontWeight ? 'bold' : '' }"
:class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]">
{{ isEnEnv ? item.en_goods_name : item.goods_name }}
</view>
<view class="tag-wrap" v-if="value.groupStyle.control || value.saleStyle.control">
<view v-if="value.groupStyle.control"
:style="{ color: value.theme == 'diy' ? value.groupStyle.bgColor : '', borderColor: value.theme == 'diy' ? value.groupStyle.bgColor : '' }">
<text class="iconfont icon-yonghu3"
:style="{ backgroundColor: value.theme == 'diy' ? value.groupStyle.bgColor : '' }"></text>
<text>{{ item.pintuan_num }}人团</text>
</view>
<view v-if="value.saleStyle.control"
:style="{ color: value.theme == 'diy' ? value.saleStyle.color : '', borderColor: value.theme == 'diy' ? value.saleStyle.color : '' }">
<text>已拼{{ item.order_num }}</text>
</view>
</view>
<view class="price-wrap">
<view class="discount-price" v-if="value.priceStyle.mainControl">
<text class="unit price-style small"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">¥</text>
<text class="price price-style large"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">{{
item.pintuan_price.split(".")[0] }}</text>
<text class="unit price-style small"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">{{
"." + item.pintuan_price.split(".")[1] }}</text>
</view>
<view class="original-price price-font" v-if="value.priceStyle.lineControl"
:style="{ color: value.theme == 'diy' ? value.priceStyle.lineColor : '' }">
¥{{ item.price }}
</view>
<button v-if="value.btnStyle.control" :style="{
background: value.btnStyle.theme == 'diy' ? 'linear-gradient(to right,' + value.btnStyle.bgColorStart + ',' + value.btnStyle.bgColorEnd + ')' : '',
color: value.btnStyle.theme == 'diy' ? value.btnStyle.textColor : '',
borderRadius: value.btnStyle.aroundRadius * 2 + 'rpx'
}">
<text class="text">{{ value.btnStyle.text }}</text>
<text class="fan" v-if="item.reward_type == 4">{{ item.reward_type_num }}积分</text>
<text class="fan" v-if="item.reward_type == 1 || item.reward_type == 2">{{
item.reward_type_num }}</text>
<text class="fan" v-if="item.reward_type == 3">返优惠券</text>
</button>
</view>
</view>
</view>
</template>
<template v-if="value.template == 'horizontal-slide'">
<scroll-view v-if="value.slideMode == 'scroll'" class="scroll" :scroll-x="true" :show-scrollbar="false">
<view class="item" v-for="(item, index) in list" :key="index" @tap.stop="toDetail(item)"
:class="[value.ornament.type]" :style="goodsItemCss">
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix"
@error="imageError(index)" />
</view>
<view :class="['content', { 'multi-content': value.nameLineMode == 'multiple' }]"
v-if="value.goodsNameStyle.control || value.priceStyle.mainControl">
<view v-if="value.goodsNameStyle.control" class="goods-name"
:style="{ color: value.theme == 'diy' ? value.goodsNameStyle.color : '', fontWeight: value.goodsNameStyle.fontWeight ? 'bold' : '' }"
:class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]">
{{ isEnEnv ? item.en_goods_name : item.goods_name }}
</view>
<view v-if="value.groupStyle.control" class="num">
<text class="content-tuan-box"
:style="{ color: value.theme == 'diy' ? value.groupStyle.color : '', backgroundColor: value.theme == 'diy' ? value.groupStyle.bgColor : '' }">
{{ item.pintuan_num }}人团
</text>
<text class="content-tuan-price"
:style="{ color: value.theme == 'diy' ? value.groupStyle.bgColor : '' }"
v-if="item.reward_type == 4">
{{ item.reward_type_num }}积分
</text>
<text class="content-tuan-price"
:style="{ color: value.theme == 'diy' ? value.groupStyle.bgColor : '' }"
v-if="item.reward_type == 1 || item.reward_type == 2">
{{ item.reward_type_num }}
</text>
<text class="content-tuan-price"
:style="{ color: value.theme == 'diy' ? value.groupStyle.bgColor : '' }"
v-if="item.reward_type == 3">返优惠券</text>
</view>
<view class="price-wrap" v-if="value.priceStyle.mainControl">
<text class="unit price-style small"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">¥</text>
<text class="price price-style large"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">{{
item.pintuan_price.split('.')[0] }}</text>
<text class="unit price-style small"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">{{
"." + item.pintuan_price.split('.')[1] }}</text>
</view>
</view>
</view>
</scroll-view>
<swiper v-if="value.slideMode == 'slide'" :autoplay="false" class="swiper"
:style="{ height: swiperHeight }">
<swiper-item v-for="(pageItem, pageIndex) in page" :key="pageIndex"
:class="['swiper-item', (list.length && [list[pageIndex].length / 3] >= 1) && 'flex-between']">
<view class="item" v-for="(item, dataIndex) in list[pageIndex]" :key="dataIndex"
@tap.stop="toDetail(item)" :class="[value.ornament.type]" :style="goodsItemCss">
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix"
@error="imageError(dataIndex)" />
</view>
<view :class="['content', { 'multi-content': value.nameLineMode == 'multiple' }]"
v-if="value.goodsNameStyle.control || value.priceStyle.mainControl">
<view v-if="value.goodsNameStyle.control" class="goods-name"
:style="{ color: value.theme == 'diy' ? value.goodsNameStyle.color : '', fontWeight: value.goodsNameStyle.fontWeight ? 'bold' : '' }"
:class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]">
{{ isEnEnv ? item.en_goods_name : item.goods_name }}
</view>
<view v-if="value.groupStyle.control" class="num">
<text class="content-tuan-box"
:style="{ color: value.theme == 'diy' ? value.groupStyle.color : '', backgroundColor: value.theme == 'diy' ? value.groupStyle.bgColor : '' }">
{{ item.pintuan_num }}人团
</text>
<text class="content-tuan-price"
:style="{ color: value.theme == 'diy' ? value.groupStyle.bgColor : '' }"
v-if="item.reward_type == 4">
{{ item.reward_type_num }}积分
</text>
<text class="content-tuan-price"
:style="{ color: value.theme == 'diy' ? value.groupStyle.bgColor : '' }"
v-if="item.reward_type == 1 || item.reward_type == 2">
{{ item.reward_type_num }}
</text>
<text class="content-tuan-price"
:style="{ color: value.theme == 'diy' ? value.groupStyle.bgColor : '' }"
v-if="item.reward_type == 3">
返优惠券
</text>
</view>
<view class="price-wrap" v-if="value.priceStyle.mainControl">
<text class="unit price-style small"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">¥</text>
<text class="price price-style large"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">{{
item.pintuan_price.split('.')[1] }}</text>
<text class="unit price-style small"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">{{
"." + item.pintuan_price.split('.')[1] }}</text>
</view>
</view>
</view>
</swiper-item>
</swiper>
</template>
</view>
</x-skeleton>
</template>
<script>
import DiyMinx from './minx.js'
export default {
name: 'diy-pinfan',
props: {
value: {
type: Object
}
},
mixins: [DiyMinx],
data() {
return {
loading: true,
skeletonType: '',
skeletonConfig: {},
list: [],
page: 1
};
},
created() {
this.initSkeleton();
this.getData();
},
watch: {
// 组件刷新监听
componentRefresh: function (nval) {
this.getData();
}
},
computed: {
warpCss() {
var obj = '';
obj += 'background-color:' + this.value.componentBgColor + ';';
if (this.value.componentAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
}
return obj;
},
// 商品项样式
goodsItemCss() {
var obj = '';
obj += 'background-color:' + this.value.elementBgColor + ';';
if (this.value.elementAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
}
if (this.value.ornament.type == 'shadow') {
obj += 'box-shadow:' + '0 0 10rpx ' + this.value.ornament.color + ';';
}
if (this.value.ornament.type == 'stroke') {
obj += 'border:' + '2rpx solid ' + this.value.ornament.color + ';';
}
const screenWidth = uni.getSystemInfoSync().windowWidth;
if (this.value.template == 'horizontal-slide') {
var width = "";
if (this.value.slideMode == 'scroll' && this.value.goodsMarginType == 'diy')
width = this.rpxUpPx(this.value.goodsMarginNum * 2);
else
width = [screenWidth - (this.rpxUpPx(20) * 2) - (this.rpxUpPx(200) * 3) - (this.rpxUpPx(this.value
.margin.both * 2) * 2)] / 6;
obj += 'margin-left:' + width + "px;";
obj += 'margin-right:' + width + "px;";
}
return obj;
},
swiperHeight() {
if (this.value.nameLineMode == 'multiple')
return this.value.ornament.type == 'shadow' ? '444rpx' : '432rpx';
return this.value.ornament.type == 'shadow' ? '404rpx' : '396rpx';
}
},
methods: {
initSkeleton() {
if (this.value.template == 'row1-of1') {
// 单列 风格
this.skeletonType = 'list';
this.skeletonConfig = {
textRows: 3
};
} else if (this.value.template == 'horizontal-slide') {
// 横向滑动 风格
this.skeletonType = 'waterfall';
this.skeletonConfig = {
gridRows: 1,
gridColumns: 3,
headHeight: '200rpx',
textRows: 2,
textWidth: ['100%', '80%']
};
}
},
rpxUpPx(res) {
const screenWidth = uni.getSystemInfoSync().windowWidth;
var data = screenWidth * parseInt(res) / 750;
return Math.floor(data);
},
getData() {
var data = {
num: this.value.count
};
if (this.value.sources == 'diy') {
data.num = 0;
data.goods_id_arr = this.value.goodsId.toString();
}
this.$api.sendRequest({
url: '/pinfan/api/goods/lists',
data: data,
success: res => {
if (res.code == 0 && res.data) {
this.list = res.data;
// 切屏滚动,每页显示固定数量
if (this.value.template == 'horizontal-slide' && this.value.slideMode == 'slide') {
let size = 3;
let temp = [];
this.page = Math.ceil(this.list.length / size);
for (var i = 0; i < this.page; i++) {
temp[i] = [];
for (var j = i * size; j < this.list.length; j++) {
if (temp[i].length == size) break;
temp[i].push(this.list[j]);
}
}
this.list = temp;
}
}
this.loading = false;
}
});
},
toDetail(e) {
this.$util.redirectTo('/pages_promotion/pinfan/detail', {
pinfan_id: e.pintuan_id
});
},
imageError(index) {
this.list[index].goods_image = this.$util.getDefaultImage().goods;
this.$forceUpdate();
}
}
};
</script>
<style lang="scss">
::v-deep .uni-scroll-view ::-webkit-scrollbar {
/* 隐藏滚动条,但依旧具备可以滚动的功能 */
display: none;
width: 0;
height: 0;
color: transparent;
background: transparent;
}
::v-deep ::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
color: transparent;
background: transparent;
}
scroll-view ::-webkit-scrollbar {
width: 0;
height: 0;
background-color: transparent;
touch-action: none;
}
.diy-pinfan {
&.row1-of1 {
.item {
display: flex;
margin-bottom: 20rpx;
padding: 16rpx;
&.shadow {
margin: 8rpx 8rpx 20rpx 8rpx;
}
&:last-child {
margin-bottom: 0;
}
.img-wrap {
width: 200rpx;
height: 200rpx;
image {
width: 200rpx;
}
}
.content {
flex: 1;
margin-left: 20rpx;
position: relative;
display: flex;
justify-content: space-between;
flex-direction: column;
padding: 6rpx 0;
.goods-name {
line-height: 1.3;
}
.tag-wrap {
display: flex;
align-items: center;
margin-top: 4rpx;
>view {
height: 30rpx;
line-height: 30rpx;
border: 2rpx solid $base-color;
border-radius: 6rpx;
margin-right: 10rpx;
font-size: $font-size-activity-tag;
color: $base-color;
.iconfont {
display: inline-block;
width: 30rpx;
height: 30rpx;
font-size: $font-size-activity-tag;
color: #ffffff;
text-align: center;
margin-right: -6rpx;
background: $base-color;
}
text:last-child {
padding: 0 10rpx;
}
}
}
.price-wrap {
display: flex;
align-items: flex-end;
>view:last-of-type {
flex: 1;
}
.discount-price {
white-space: nowrap;
font-weight: bold;
.unit {
font-size: $font-size-tag;
color: $base-color;
}
.price {
font-size: $font-size-toolbar;
color: $base-color;
}
}
.original-price {
font-size: $font-size-tag;
color: $color-tip;
text-decoration: line-through;
margin: 0 10rpx 4rpx 10rpx;
}
}
button {
bottom: 10rpx;
height: 90rpx;
line-height: 40rpx;
padding: 0 16rpx;
margin: 0;
text-align: center;
background-color: $base-color;
color: #ffffff;
.text {
display: block;
margin-top: 6rpx;
font-size: $font-size-tag;
}
.fan {
font-size: $font-size-activity-tag;
}
}
}
}
}
&.horizontal-slide {
display: flex;
justify-content: space-around;
.scroll {
width: calc(100% - 40rpx);
padding: 20rpx;
line-height: 1;
white-space: nowrap;
.item.shadow {
margin-bottom: 8rpx;
}
}
.flex-between {
justify-content: space-between;
}
.item {
display: inline-block;
width: 200rpx;
overflow: hidden;
box-sizing: border-box;
&:nth-child(3n+3) {
width: 198rpx;
}
&.shadow {
margin-top: 8rpx;
}
.img-wrap {
width: 200rpx;
height: 200rpx;
position: relative;
overflow: hidden;
margin: 0 auto;
>image {
width: 200rpx;
}
}
.content {
padding: 10rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
&.multi-content {
height: 188rpx;
box-sizing: border-box;
}
.goods-name {
line-height: 1.3;
&.multi-hidden {
white-space: break-spaces;
}
}
.num {
margin-top: auto;
font-size: $font-size-activity-tag;
.content-tuan-box {
border-radius: 4rpx;
color: #ffffff;
background-color: $base-color;
padding: 6rpx;
}
.content-tuan-price {
border-radius: 4rpx;
padding: 4rpx 6rpx;
border: 2rpx solid $base-color;
color: $base-color;
margin-left: -2rpx;
}
}
.price-wrap {
white-space: nowrap;
margin-top: 10rpx;
font-weight: bold;
line-height: 1;
.unit {
font-size: $font-size-tag;
height: 32rpx;
color: $base-color;
}
.price {
font-size: $font-size-toolbar;
color: $base-color;
}
}
}
}
.swiper {
width: 100%;
white-space: nowrap;
padding: 20rpx;
box-sizing: border-box;
.swiper-item {
display: flex;
align-items: center;
}
.item {
width: 200rpx;
}
}
}
}
</style>

View File

@@ -0,0 +1,812 @@
<template>
<x-skeleton data-component-name="diy-pintuan" :type="skeletonType" :loading="loading" :configs="skeletonConfig">
<view class="diy-pintuan" :class="[value.template, value.style]" :style="warpCss">
<view v-if="value.titleStyle.isShow && list && list.length"
:class="[value.titleStyle.style, 'pintuan-head']" :style="{
backgroundImage:
'url(' + $util.img(value.titleStyle.backgroundImage) + '), linear-gradient(to right,' + value.titleStyle.bgColorStart + ',' + value.titleStyle.bgColorEnd + ')'
}">
<view v-if="value.titleStyle.leftStyle == 'text'" class="left-text"
:style="{ fontSize: value.titleStyle.fontSize * 2 + 'rpx', color: value.titleStyle.textColor, fontWeight: value.titleStyle.fontWeight ? 'bold' : '' }">
{{ value.titleStyle.leftText }}
</view>
<image v-else class="left-img" :src="$util.img(value.titleStyle.leftImg)" mode="heightFix" />
<view class="head-content">
<view class="img-warp">
<block v-if="headData && headData.member_list && headData.member_list.length">
<image v-for="(item, index) in headData.member_list" :key="index"
:src="$util.img(item.member_img || 'public/static/img/default_img/square.png')"
mode="aspectFill" @error="headImageError(index)" />
</block>
<block v-else>
<image :src="$util.img('public/static/img/default_img/square.png')" mode="aspectFill" />
<image :src="$util.img('public/static/img/default_img/square.png')" mode="aspectFill" />
<image :src="$util.img('public/static/img/default_img/square.png')" mode="aspectFill" />
</block>
</view>
<view :style="{ color: value.titleStyle.textColor }">
<text>{{ value.titleStyle.virtualNum || headData.pintuan_count }}</text>
<text>{{ value.titleStyle.style == 'style-2' ? '人参与' : '人拼团成功' }}</text>
</view>
</view>
<view class="head-right"
:style="{ fontSize: value.titleStyle.moreFontSize * 2 + 'rpx', color: value.titleStyle.moreColor }"
@tap.stop="$util.redirectTo('/pages_promotion/pintuan/list')">
<text>{{ value.titleStyle.more }}</text>
<text class="iconfont icon-right"></text>
</view>
</view>
<template v-if="value.template == 'row1-of1'">
<view class="item" v-for="(item, index) in list" :key="index" @tap.stop="toDetail(item)"
:class="[value.ornament.type]" :style="goodsItemCss">
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix"
@error="imageError(index)" />
</view>
<view class="content"
v-if="value.goodsNameStyle.control || value.priceStyle.mainControl || value.priceStyle.lineControl || value.btnStyle.control">
<view v-if="value.goodsNameStyle.control" class="goods-name"
:style="{ color: value.theme == 'diy' ? value.goodsNameStyle.color : '', fontWeight: value.goodsNameStyle.fontWeight ? 'bold' : '' }"
:class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]">
{{ isEnEnv ? item.en_goods_name : item.goods_name }}
</view>
<view class="tag-wrap" v-if="value.groupStyle.control || value.saleStyle.control">
<view v-if="value.groupStyle.control" :style="{
color: value.theme == 'diy' ? value.groupStyle.bgColorStart : '',
borderColor: value.theme == 'diy' ? value.groupStyle.bgColorStart : ''
}">
<text class="iconfont icon-yonghu3"
:style="{ backgroundColor: value.theme == 'diy' ? value.groupStyle.bgColorStart : '' }"></text>
<text>{{ item.pintuan_num }}人团</text>
</view>
<view v-if="value.saleStyle.control"
:style="{ color: value.theme == 'diy' ? value.saleStyle.color : '', borderColor: value.theme == 'diy' ? value.saleStyle.color : '' }">
<text>已拼{{ item.order_num }}</text>
</view>
</view>
<view class="bottom-wrap">
<view class="price-wrap">
<view class="discount-price" v-if="value.priceStyle.mainControl">
<text class="unit price-style small"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">¥</text>
<text class="price price-style large"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">
{{ item.pintuan_price.split('.')[0] }}
</text>
<text class="unit price-style small"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">
{{ '.' + item.pintuan_price.split('.')[1] }}
</text>
</view>
<view class="original-price price-font" v-if="value.priceStyle.lineControl"
:style="{ color: value.theme == 'diy' ? value.priceStyle.lineColor : '' }">
¥{{ item.price }}
</view>
</view>
<button v-if="value.btnStyle.control" :style="{
background: value.btnStyle.theme == 'diy' ? 'linear-gradient(to right,' + value.btnStyle.bgColorStart + ',' + value.btnStyle.bgColorEnd + ')' : '',
color: value.btnStyle.theme == 'diy' ? value.btnStyle.textColor : '',
borderRadius: value.btnStyle.aroundRadius * 2 + 'rpx'
}">
{{ value.btnStyle.text }}
</button>
</view>
</view>
</view>
</template>
<template v-if="value.template == 'horizontal-slide'">
<scroll-view v-if="value.slideMode == 'scroll'" class="scroll" :scroll-x="true" :show-scrollbar="false">
<view class="item" v-for="(item, index) in list" :key="index" @tap.stop="toDetail(item)"
:class="[value.ornament.type]" :style="goodsItemCss">
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix"
@error="imageError(index)" />
<view v-if="value.groupStyle.control && value.style == 'style-1'" class="num" :style="{
color: value.theme == 'diy' ? value.groupStyle.color : '',
background: value.theme == 'diy' ? 'linear-gradient(to right,' + value.groupStyle.bgColorStart + ',' + value.groupStyle.bgColorEnd + ')' : ''
}">
{{ item.pintuan_num }}人团
</view>
</view>
<view :class="['content', { 'multi-content': value.nameLineMode == 'multiple' }]"
v-if="value.goodsNameStyle.control || value.priceStyle.mainControl">
<view
v-if="value.goodsNameStyle.control && (value.style == 'style-1' || value.style == 'style-3')"
class="goods-name"
:style="{ color: value.theme == 'diy' ? value.goodsNameStyle.color : '', fontWeight: value.goodsNameStyle.fontWeight ? 'bold' : '' }"
:class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]">
{{ isEnEnv ? item.en_goods_name : item.goods_name }}
</view>
<view class="price-wrap" v-if="value.priceStyle.mainControl && value.style != 'style-3'">
<text class="unit price-style small"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">¥</text>
<text class="price price-style large"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">
{{ item.pintuan_price.split('.')[0] }}
</text>
<text class="unit price-style small"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">
{{ '.' + item.pintuan_price.split('.')[1] }}
</text>
</view>
<view class="other-info-wrap" v-if="value.style == 'style-3'">
<view class="price-wrap" v-if="value.priceStyle.mainControl">
<text class="unit price-style small"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">¥</text>
<text class="price price-style large"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">
{{ item.pintuan_price.split('.')[0] }}
</text>
<text class="unit price-style small"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">
{{ '.' + item.pintuan_price.split('.')[1] }}
</text>
</view>
<view class="sale-action">去参团</view>
</view>
<view class="sale-num" v-if="value.style == 'style-2'">已拼{{ item.sale_num }}</view>
</view>
</view>
</scroll-view>
<swiper v-if="value.slideMode == 'slide'" :autoplay="false" class="swiper"
:style="{ height: swiperHeight }">
<swiper-item v-for="(pageItem, pageIndex) in page" :key="pageIndex"
:class="['swiper-item', (list.length && [list[pageIndex].length / 3] >= 1) && 'flex-between']">
<view class="item" v-for="(item, dataIndex) in list[pageIndex]" :key="dataIndex"
@tap.stop="toDetail(item)" :class="[value.ornament.type]" :style="goodsItemCss">
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix"
@error="imageError(dataIndex)" />
<view v-if="value.groupStyle.control && value.style != 'style-2'" class="num" :style="{
color: value.theme == 'diy' ? value.groupStyle.color : '',
background: value.theme == 'diy' ? 'linear-gradient(to right,' + value.groupStyle.bgColorStart + ',' + value.groupStyle.bgColorEnd + ')' : ''
}">
{{ item.pintuan_num }}人团
</view>
</view>
<view :class="['content', { 'multi-content': value.nameLineMode == 'multiple' }]"
v-if="value.goodsNameStyle.control || value.priceStyle.mainControl">
<view v-if="value.goodsNameStyle.control && value.style != 'style-2'" class="goods-name"
:style="{ color: value.theme == 'diy' ? value.goodsNameStyle.color : '', fontWeight: value.goodsNameStyle.fontWeight ? 'bold' : '' }"
:class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]">
{{ isEnEnv ? item.en_goods_name : item.goods_name }}
</view>
<view class="price-wrap" v-if="value.priceStyle.mainControl">
<text class="unit price-style small"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">¥</text>
<text class="price price-style large"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">
{{ item.pintuan_price.split('.')[0] }}
</text>
<text class="unit price-style small"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">
{{ '.' + item.pintuan_price.split('.')[1] }}
</text>
</view>
<view class="sale-num" v-if="value.style == 'style-2'">已拼{{ item.sale_num }}</view>
</view>
</view>
</swiper-item>
</swiper>
</template>
</view>
</x-skeleton>
</template>
<script>
import DiyMinx from './minx.js'
export default {
name: 'diy-pintuan',
props: {
value: {
type: Object
}
},
mixins: [DiyMinx],
data() {
return {
loading: true,
skeletonType: '',
skeletonConfig: {},
list: [],
page: 1,
scrollWidth: 0,
headData: []
};
},
created() {
this.initSkeleton();
this.getData();
this.getHeadData();
},
watch: {
// 组件刷新监听
componentRefresh: function (nval) {
this.getData();
this.getHeadData();
}
},
computed: {
warpCss() {
var obj = '';
obj += 'background-color:' + this.value.componentBgColor + ';';
if (this.value.componentAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
}
return obj;
},
// 商品项样式
goodsItemCss() {
var obj = '';
obj += 'background-color:' + this.value.elementBgColor + ';';
if (this.value.elementAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
}
if (this.value.ornament.type == 'shadow') {
obj += 'box-shadow:' + '0 0 10rpx ' + this.value.ornament.color + ';';
}
if (this.value.ornament.type == 'stroke') {
obj += 'border:' + '2rpx solid ' + this.value.ornament.color + ';';
}
const screenWidth = uni.getSystemInfoSync().windowWidth;
if (this.value.template == 'horizontal-slide') {
var width = '';
if (this.value.slideMode == 'scroll' && this.value.goodsMarginType == 'diy') width = this.rpxUpPx(this
.value.goodsMarginNum * 2);
else if (this.value.template == 'horizontal-slide' && this.value.style == 'style-2')
width = [screenWidth - this.rpxUpPx(212) * 3 - this.rpxUpPx(this.value.margin.both * 2) * 2] / 6;
else width = [screenWidth - this.rpxUpPx(20) * 2 - this.rpxUpPx(200) * 3 - this.rpxUpPx(this.value
.margin.both * 2) * 2] / 6;
obj += 'margin-left:' + width + 'px;';
obj += 'margin-right:' + width + 'px;';
}
return obj;
},
swiperHeight() {
if (this.value.template == 'horizontal-slide' && this.value.style == 'style-2') {
if (this.value.nameLineMode == 'multiple') {
if (this.value.ornament.type == 'shadow') return '360rpx';
else return '342rpx';
}
if (this.value.ornament.type == 'shadow') return '324rpx';
else return '308rpx';
} else {
if (this.value.nameLineMode == 'multiple') {
if (this.value.ornament.type == 'shadow') return '400rpx';
else return '382rpx';
}
if (this.value.ornament.type == 'shadow') return '364rpx';
else return '348rpx';
}
}
},
methods: {
initSkeleton() {
if (this.value.template == 'row1-of1') {
// 单列 风格
this.skeletonType = 'list';
this.skeletonConfig = {
textRows: 3
};
} else if (this.value.template == 'horizontal-slide') {
// 横向滑动 风格
this.skeletonType = 'waterfall';
this.skeletonConfig = {
gridRows: 1,
gridColumns: 3,
headHeight: '200rpx',
textRows: 2,
textWidth: ['100%', '80%']
};
}
},
rpxUpPx(res) {
const screenWidth = uni.getSystemInfoSync().windowWidth;
var data = (screenWidth * parseInt(res)) / 750;
return Math.floor(data);
},
getHeadData() {
var data = {
num: 3
};
this.$api.sendRequest({
url: '/pintuan/api/order/pintuanmember',
data: data,
success: res => {
if (res.code == 0 && res.data) {
this.headData = res.data;
}
}
});
},
getData() {
var data = {
num: this.value.count
};
if (this.value.sources == 'diy') {
data.num = 6;
data.goods_id_arr = this.value.goodsId.toString();
}
this.$api.sendRequest({
url: '/pintuan/api/goods/lists',
data: data,
success: res => {
if (res.code == 0 && res.data) {
this.list = res.data;
// 切屏滚动,每页显示固定数量
if (this.value.template == 'horizontal-slide' && this.value.slideMode == 'slide') {
let size = 3;
let temp = [];
this.page = Math.ceil(this.list.length / size);
for (var i = 0; i < this.page; i++) {
temp[i] = [];
for (var j = i * size; j < this.list.length; j++) {
if (temp[i].length == size) break;
temp[i].push(this.list[j]);
}
}
this.list = temp;
}
}
this.loading = false;
}
});
},
toDetail(e) {
this.$util.redirectTo('/pages_promotion/pintuan/detail', {
pintuan_id: e.pintuan_id
});
},
imageError(index) {
if (this.list[index]) this.list[index].goods_image = this.$util.getDefaultImage().goods;
this.$forceUpdate();
},
headImageError(index) {
this.headData.member_list[index].member_img = this.$util.img('public/static/img/default_img/square.png');
this.$forceUpdate();
}
}
};
</script>
<style lang="scss">
::v-deep .uni-scroll-view ::-webkit-scrollbar {
/* 隐藏滚动条,但依旧具备可以滚动的功能 */
display: none;
width: 0;
height: 0;
color: transparent;
background: transparent;
}
::v-deep ::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
color: transparent;
background: transparent;
}
scroll-view ::-webkit-scrollbar {
width: 0;
height: 0;
background-color: transparent;
touch-action: none;
}
.diy-pintuan {
overflow: hidden;
.pintuan-head {
padding: 0 24rpx;
display: flex;
justify-content: space-between;
align-items: center;
height: 88rpx;
box-sizing: border-box;
background-repeat: no-repeat;
background-size: cover;
margin-bottom: 20rpx;
border-radius: 18rpx 18rpx 0 0;
.left-img {
width: 174rpx;
height: 40rpx;
}
.head-content {
display: flex;
align-items: center;
margin-left: 50rpx;
margin-right: auto;
color: #fff;
.img-warp {
position: relative;
margin-right: 8rpx;
line-height: 1;
image {
width: 40rpx;
height: 40rpx;
border-radius: 50%;
border: 2rpx solid #ff3d3d;
margin-left: -14rpx;
}
&:after {
content: '';
width: 2rpx;
height: 28rpx;
background-color: #fff;
position: absolute;
left: -32rpx;
top: 50%;
transform: translateY(-50%);
}
}
}
.head-right {
display: flex;
align-items: center;
font-size: $font-size-sub;
color: #fff;
line-height: 1;
}
&.style-2 {
.head-right {
display: flex;
align-items: center;
justify-content: center;
height: 36rpx;
background: linear-gradient(270deg, #ffbd5b 0%, #fd882e 100%);
border-radius: 24rpx;
padding: 2rpx;
text:nth-child(1) {
position: relative;
left: 6rpx;
transform: scale(0.9);
}
text:nth-child(2) {
padding: 6rpx 4rpx 4rpx;
background-color: #fff;
color: #ffbd5b;
border-radius: 50%;
transform: scale(0.6);
font-weight: bold;
line-height: 1;
}
}
}
}
&.row1-of1 {
.item {
display: flex;
margin-bottom: 20rpx;
padding: 16rpx;
&.shadow {
margin: 8rpx 8rpx 20rpx 8rpx;
}
&:last-child {
margin-bottom: 0;
}
.img-wrap {
width: 200rpx;
height: 200rpx;
image {
width: 200rpx;
}
}
.content {
flex: 1;
margin-left: 20rpx;
position: relative;
display: flex;
justify-content: space-between;
flex-direction: column;
padding: 6rpx 0;
.tag-wrap {
display: flex;
align-items: center;
>view {
height: 30rpx;
line-height: 30rpx;
border: 2rpx solid $base-color;
border-radius: 6rpx;
margin-right: 10rpx;
font-size: $font-size-activity-tag;
color: $base-color;
.iconfont {
display: inline-block;
width: 30rpx;
height: 30rpx;
font-size: $font-size-activity-tag;
color: #ffffff;
text-align: center;
margin-right: -6rpx;
background: $base-color;
}
text:last-child {
padding: 0 10rpx;
}
}
}
.goods-name {
line-height: 1.3;
}
.bottom-wrap {
display: flex;
align-items: center;
justify-content: space-between;
}
.price-wrap {
overflow: hidden;
width: 260rpx;
display: flex;
align-items: flex-end;
flex-wrap: wrap;
>view:last-of-type {
flex: 1;
}
.discount-price {
white-space: nowrap;
font-weight: bold;
line-height: 1;
.unit {
font-size: $font-size-tag;
color: var(--price-color);
}
.price {
font-size: $font-size-toolbar;
color: var(--price-color);
}
}
.original-price {
font-size: $font-size-tag;
color: $color-tip;
line-height: 1;
text-decoration: line-through;
margin: 0 10rpx;
}
}
button {
margin-right: 20rpx;
min-width: 112rpx;
height: 52rpx;
line-height: 52rpx;
padding: 0 20rpx;
margin: 0;
color: var(--btn-text-color);
background-color: $base-color;
color: #fff;
font-size: 24rpx;
}
}
}
}
&.horizontal-slide {
display: flex;
flex-direction: column;
.flex-between {
justify-content: space-between;
}
.scroll {
width: calc(100% - 40rpx);
padding: 20rpx;
line-height: 1;
white-space: nowrap;
.item.shadow {
margin-bottom: 4rpx;
}
}
.item {
display: inline-block;
width: 200rpx;
overflow: hidden;
box-sizing: border-box;
flex-shrink: 0;
&:nth-child(3n + 3) {
width: 198rpx;
}
&.shadow {
margin-top: 8rpx;
}
.img-wrap {
width: 200rpx;
height: 200rpx;
position: relative;
overflow: hidden;
margin: 0 auto;
>image {
width: 200rpx;
}
.num {
width: 80rpx;
position: absolute;
bottom: 0;
font-size: $font-size-tag;
line-height: 1;
color: #ffffff;
text-align: center;
border-top-left-radius: 20rpx;
border-top-right-radius: 20rpx;
padding: 10rpx;
transform: translate(50%);
background: $base-color;
}
}
.content {
padding: 10rpx 10rpx 16rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
&.multi-content {
height: 142rpx;
box-sizing: border-box;
}
.goods-name {
line-height: 1.3;
font-size: $font-size-base;
&.multi-hidden {
white-space: break-spaces;
}
}
.price-wrap {
line-height: 1;
white-space: nowrap;
margin-top: 10rpx;
font-weight: bold;
.unit {
font-size: $font-size-tag;
height: 32rpx;
color: var(--price-color);
}
.price {
font-size: $font-size-toolbar;
color: var(--price-color);
}
}
}
}
.swiper {
padding: 20rpx;
width: 100%;
white-space: nowrap;
box-sizing: border-box;
.swiper-item {
display: flex;
align-items: center;
}
.item {
width: 200rpx;
box-sizing: border-box;
}
}
&.style-2 {
.item {
width: 212rpx;
&:nth-child(3n + 3) {
width: 210rpx;
}
.img-wrap {
width: 212rpx;
height: 212rpx;
>image {
width: 212rpx !important;
}
}
.price-wrap {
text-align: center;
margin-top: 0 !important;
margin-bottom: 10rpx;
}
.sale-num {
text-align: center;
color: #666666;
line-height: 1;
}
}
.scroll {
padding: 0;
width: auto;
}
.swiper {
padding: 0;
}
}
&.style-3 {
.other-info-wrap {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
}
.sale-action {
font-size: $font-size-tag;
width: 94rpx;
height: 44rpx;
background: linear-gradient(131deg, #3edb73 0%, #1db576 100%);
border-radius: 8rpx;
color: #fff;
text-align: center;
line-height: 44rpx;
transform: scale(0.9);
}
}
}
}
</style>

View File

@@ -0,0 +1,468 @@
<template>
<x-skeleton data-component-name="diy-presale" :type="skeletonType" :loading="loading" :configs="skeletonConfig">
<view class="diy-presale" v-if="list.length" :class="[value.template, value.style]" :style="warpCss">
<template v-if="value.template == 'row1-of1'">
<view class="item" v-for="(item, index) in list" :key="index" @tap.stop="handlerClick(item)"
:class="[value.ornament.type]" :style="goodsItemCss">
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix"
@error="imageError(index)" />
</view>
<view class="content"
v-if="value.goodsNameStyle.control || value.priceStyle.mainControl || value.btnStyle.control">
<view v-if="value.goodsNameStyle.control" class="goods-name"
:style="{ color: value.theme == 'diy' ? value.goodsNameStyle.color : '', fontWeight: value.goodsNameStyle.fontWeight ? 'bold' : '' }"
:class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]">
{{ isEnEnv ? item.en_goods_name : item.goods_name }}
</view>
<view class="discount-price" v-if="value.priceStyle.mainControl">
<text class="unit price-style small"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">¥</text>
<text class="price price-style large"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">
{{ showPrice(item).split('.')[0] }}
</text>
<text class="unit price-style small"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">
{{ '.' + showPrice(item).split('.')[1] }}
</text>
</view>
<button v-if="value.btnStyle.control" :style="{
background: value.btnStyle.theme == 'diy' ? 'linear-gradient(to right,' + value.btnStyle.bgColorStart + ',' + value.btnStyle.bgColorEnd + ')' : '',
color: value.btnStyle.theme == 'diy' ? value.btnStyle.textColor : '',
borderRadius: value.btnStyle.aroundRadius * 2 + 'rpx'
}">
{{ value.btnStyle.text }}
</button>
</view>
</view>
</template>
<template v-if="value.template == 'horizontal-slide'">
<scroll-view v-if="value.slideMode == 'scroll'" class="scroll" :scroll-x="true" :show-scrollbar="false">
<view class="item" v-for="(item, index) in list" :key="index" @tap.stop="handlerClick(item)"
:class="[value.ornament.type]" :style="goodsItemCss">
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix"
@error="imageError(index)" />
</view>
<view :class="['content', { 'multi-content': value.nameLineMode == 'multiple' }]"
v-if="value.goodsNameStyle.control || value.priceStyle.mainControl">
<view v-if="value.goodsNameStyle.control" class="goods-name"
:style="{ color: value.theme == 'diy' ? value.goodsNameStyle.color : '', fontWeight: value.goodsNameStyle.fontWeight ? 'bold' : '' }"
:class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]">
{{ isEnEnv ? item.en_goods_name : item.goods_name }}
</view>
<view class="discount-price" v-if="value.priceStyle.mainControl">
<text class="unit price-style small"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">¥</text>
<text class="price price-style large"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">
{{ showPrice(item).split('.')[0] }}
</text>
<text class="unit price-style small"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">
{{ '.' + showPrice(item).split('.')[1] }}
</text>
</view>
</view>
</view>
</scroll-view>
<swiper v-if="value.slideMode == 'slide'" :autoplay="false" class="swiper"
:style="{ height: swiperHeight }">
<swiper-item v-for="(pageItem, pageIndex) in page" :key="pageIndex"
:class="['swiper-item', (list.length && [list[pageIndex].length / 3] >= 1) && 'flex-between']">
<view class="item" v-for="(item, dataIndex) in list[pageIndex]" :key="dataIndex"
@tap.stop="handlerClick(item)" :class="[value.ornament.type]" :style="goodsItemCss">
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix"
@error="imageError(dataIndex)" />
</view>
<view :class="['content', { 'multi-content': value.nameLineMode == 'multiple' }]"
v-if="value.goodsNameStyle.control || value.priceStyle.mainControl">
<view v-if="value.goodsNameStyle.control" class="goods-name"
:style="{ color: value.theme == 'diy' ? value.goodsNameStyle.color : '', fontWeight: value.goodsNameStyle.fontWeight ? 'bold' : '' }"
:class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]">
{{ isEnEnv ? item.en_goods_name : item.goods_name }}
</view>
<view class="discount-price" v-if="value.priceStyle.mainControl">
<text class="unit price-style small"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">¥</text>
<text class="price price-style large"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">
{{ showPrice(item).split('.')[0] }}
</text>
<text class="unit price-style small"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">
{{ '.' + showPrice(item).split('.')[1] }}
</text>
</view>
</view>
</view>
</swiper-item>
</swiper>
</template>
</view>
</x-skeleton>
</template>
<script>
import DiyMinx from './minx.js'
export default {
name: 'diy-presale',
props: {
value: {
type: Object
}
},
data() {
return {
loading: true,
skeletonType: '',
skeletonConfig: {},
list: [],
page: 1
};
},
created() {
this.initSkeleton();
this.getData();
},
mixins: [DiyMinx],
watch: {
// 组件刷新监听
componentRefresh: function (nval) {
this.getData();
}
},
computed: {
warpCss() {
var obj = '';
obj += 'background-color:' + this.value.componentBgColor + ';';
if (this.value.componentAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
}
return obj;
},
// 商品项样式
goodsItemCss() {
var obj = '';
obj += 'background-color:' + this.value.elementBgColor + ';';
if (this.value.elementAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
}
if (this.value.ornament.type == 'shadow') {
obj += 'box-shadow:' + '0 0 10rpx ' + this.value.ornament.color + ';';
}
if (this.value.ornament.type == 'stroke') {
obj += 'border:' + '2rpx solid ' + this.value.ornament.color + ';';
}
const screenWidth = uni.getSystemInfoSync().windowWidth;
if (this.value.template == 'horizontal-slide') {
var width = '';
if (this.value.slideMode == 'scroll' && this.value.goodsMarginType == 'diy') width = this.rpxUpPx(this
.value.goodsMarginNum * 2);
else width = [screenWidth - this.rpxUpPx(20) * 2 - this.rpxUpPx(200) * 3 - this.rpxUpPx(this.value
.margin.both * 2) * 2] / 6;
obj += 'margin-right:' + width + 'px;';
obj += 'margin-left:' + width + 'px;';
}
return obj;
},
swiperHeight() {
if (this.value.nameLineMode == 'multiple') {
return this.value.ornament.type == 'shadow' ? '390rpx' : '382rpx';
}
return this.value.ornament.type == 'shadow' ? '364rpx' : '348rpx';
}
},
methods: {
initSkeleton() {
if (this.value.template == 'row1-of1') {
// 单列 风格
this.skeletonType = 'list';
this.skeletonConfig = {
textRows: 2
};
} else if (this.value.template == 'horizontal-slide') {
// 横向滑动 风格
this.skeletonType = 'waterfall';
this.skeletonConfig = {
gridRows: 1,
gridColumns: 3,
headHeight: '200rpx',
textRows: 2,
textWidth: ['100%', '80%']
};
}
},
rpxUpPx(res) {
const screenWidth = uni.getSystemInfoSync().windowWidth;
var data = (screenWidth * parseInt(res)) / 750;
return Math.floor(data);
},
getData() {
var data = {
num: this.value.count
};
if (this.value.sources == 'diy') {
data.num = 0;
data.goods_id_arr = this.value.goodsId.toString();
}
this.$api.sendRequest({
url: '/presale/api/goods/lists',
data: data,
success: res => {
if (res.code == 0 && res.data) {
this.list = res.data;
// 切屏滚动,每页显示固定数量
if (this.value.template == 'horizontal-slide' && this.value.slideMode == 'slide') {
let size = 3;
let temp = [];
this.page = Math.ceil(this.list.length / size);
for (var i = 0; i < this.page; i++) {
temp[i] = [];
for (var j = i * size; j < this.list.length; j++) {
if (temp[i].length == size) break;
temp[i].push(this.list[j]);
}
}
this.list = temp;
}
}
this.loading = false;
}
});
},
toDetail(e) {
this.$util.redirectTo('/pages_promotion/presale/detail', {
id: e.presale_id
});
},
imageError(index) {
this.list[index].goods_image = this.$util.getDefaultImage().goods;
this.$forceUpdate();
},
showPrice(data) {
let price = data.price;
if (data.member_price && parseFloat(data.member_price) < parseFloat(price)) price = data.member_price;
return price;
},
async handlerClick(item) {
await this.__$emitEvent({
eventName: 'presale-tap', data: item, promiseCallback: (event, handler, awaitedResult) => {
if (!awaitedResult) return;
this.toDetail(item);
}
})
}
}
};
</script>
<style lang="scss">
::v-deep .uni-scroll-view ::-webkit-scrollbar {
/* 隐藏滚动条,但依旧具备可以滚动的功能 */
display: none;
width: 0;
height: 0;
color: transparent;
background: transparent;
}
::v-deep ::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
color: transparent;
background: transparent;
}
scroll-view ::-webkit-scrollbar {
width: 0;
height: 0;
background-color: transparent;
touch-action: none;
}
.diy-presale {
&.row1-of1 {
.item {
display: flex;
margin-bottom: 20rpx;
padding: 16rpx;
&.shadow {
margin: 8rpx 8rpx 20rpx 8rpx;
}
&:last-child {
margin-bottom: 0;
}
.img-wrap {
width: 200rpx;
height: 200rpx;
>image {
width: 200rpx;
}
}
.content {
flex: 1;
margin-left: 20rpx;
position: relative;
padding: 6rpx 0;
.goods-name {
line-height: 1.5;
}
.discount-price {
white-space: nowrap;
font-weight: bold;
position: absolute;
bottom: 10rpx;
left: 0;
.unit {
font-size: $font-size-tag;
margin-right: 4rpx;
color: $base-color;
}
.price {
font-size: $font-size-toolbar;
color: $base-color;
}
}
button {
position: absolute;
bottom: 10rpx;
right: 20rpx;
margin: 0;
padding: 0 20rpx;
background-color: $base-color;
color: #fff;
min-width: 112rpx;
height: 52rpx;
line-height: 52rpx;
font-size: $font-size-tag;
}
}
}
}
&.horizontal-slide {
.scroll {
width: calc(100% - 40rpx);
padding: 20rpx;
line-height: 1;
white-space: nowrap;
.item.shadow {
margin-bottom: 8rpx;
}
}
.flex-between {
justify-content: space-between;
}
.item {
display: inline-block;
width: 200rpx;
overflow: hidden;
box-sizing: border-box;
&:nth-child(3n + 3) {
width: 198rpx;
}
&.shadow {
margin-top: 8rpx;
}
.img-wrap {
width: 200rpx;
height: 200rpx;
margin: 0 auto;
>image {
width: 200rpx;
}
}
.content {
padding: 10rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
&.multi-content {
height: 134rpx;
box-sizing: border-box;
}
.goods-name {
line-height: 1.3;
&.multi-hidden {
white-space: break-spaces;
}
}
.discount-price {
white-space: nowrap;
margin-top: auto;
font-weight: bold;
line-height: 1;
.unit {
font-size: $font-size-tag;
margin-right: 4rpx;
color: $base-color;
}
.price {
font-size: $font-size-toolbar;
color: $base-color;
}
}
}
}
.swiper {
padding: 20rpx;
width: 100%;
white-space: nowrap;
box-sizing: border-box;
.swiper-item {
display: flex;
align-items: center;
}
.item {
width: 200rpx;
}
}
}
}
</style>

View File

@@ -0,0 +1,128 @@
<template>
<view data-component-name="diy-quick-nav" :style="componentStyle">
<scroll-view class="quick-nav" scroll-x="true">
<!-- #ifdef MP -->
<view class="uni-scroll-view-content">
<!-- #endif -->
<view class="quick-nav-item" v-for="(item, index) in value.list" :key="index"
@tap.stop="handlerClick(item)"
:style="{ background: 'linear-gradient(to right,' + item.bgColorStart ? item.bgColorStart : '' + ',' + item.bgColorEnd ? item.bgColorEnd : '' + ')' }">
<view class="quick-img" v-if="item.imageUrl || item.icon">
<image v-if="item.iconType == 'img'"
:src="$util.img(item.imageUrl) || $util.img('public/uniapp/default_img/goods.png')"
mode="heightFix" :show-menu-by-longpress="true"></image>
<diy-icon v-if="item.iconType == 'icon'" :icon="item.icon"
:value="item.style ? item.style : null" :style="{ fontSize: '60rpx' }"></diy-icon>
</view>
<text class="quick-text" :style="{ color: item.textColor }">{{ item.title }}</text>
</view>
<!-- #ifdef MP -->
</view>
<!-- #endif -->
</scroll-view>
<ns-login ref="login"></ns-login>
</view>
</template>
<script>
import DiyMinx from './minx.js'
export default {
name: 'diy-quick-nav',
props: {
value: {
type: Object
}
},
data() {
return {};
},
created() { },
mixins: [DiyMinx],
watch: {
// 组件刷新监听
componentRefresh: function (nval) { }
},
computed: {
componentStyle() {
var css = '';
css += 'background-color:' + this.value.componentBgColor + ';';
if (this.value.componentAngle == 'round') {
css += 'border-top-left-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
css += 'border-top-right-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
css += 'border-bottom-left-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
css += 'border-bottom-right-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
}
css += 'box-shadow:' + (this.value.ornament.type == 'shadow' ? '0 0 10rpx ' + this.value.ornament.color : '') + ';';
css += 'border:' + (this.value.ornament.type == 'stroke' ? '2rpx solid ' + this.value.ornament.color : '') + ';';
return css;
}
},
methods: {
redirectTo(link) {
if (link.wap_url) {
if (this.$util.getCurrRoute() == this.$util.MEMBER_PAGE_URL && !this.storeToken) {
this.$refs.login.open(link.wap_url);
return;
}
}
this.$util.diyRedirectTo(link);
},
async handlerClick(item) {
await this.__$emitEvent({
eventName: 'quick-nav-tap', data: item, promiseCallback: (event, handler, awaitedResult) => {
if (!awaitedResult) return;
this.redirectTo(item.link);
}
})
}
}
};
</script>
<style>
.quick-nav>>>.uni-scroll-view-content {
display: flex;
}
</style>
<style lang="scss">
.quick-nav {
.quick-nav-item {
display: flex;
align-items: center;
padding: 0 18rpx;
box-sizing: border-box;
flex-shrink: 0;
border-radius: 40rpx;
margin-right: 20rpx;
height: 48rpx;
&:first-of-type {
padding-left: 12rpx;
}
&:last-child {
margin-right: 0;
}
.quick-img {
margin-right: 6rpx;
height: 30rpx;
line-height: 1;
image {
width: 30rpx;
height: 30rpx;
}
}
.quick-text {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
width: 100%;
font-size: 24rpx;
line-height: 1;
}
}
}
</style>

View File

@@ -1,6 +1,6 @@
<template>
<view data-component-name="diy-rich-text" class="rich-text-box" :style="richTextWarpCss">
<rich-text :nodes="html" @click="handlerClick" @tap="handlerClick"></rich-text>
<rich-text :nodes="html" @tap.stop="handlerClick"></rich-text>
</view>
</template>
@@ -26,10 +26,10 @@ export default {
mixins: [DiyMinx],
watch: {
//
componentRefresh: function(nval) {}
componentRefresh: function (nval) { }
},
computed: {
richTextWarpCss: function() {
richTextWarpCss: function () {
var obj = '';
obj += 'background-color:' + this.value.componentBgColor + ';';
if (this.value.componentAngle == 'round') {
@@ -41,12 +41,14 @@ export default {
return obj;
}
},
mounted() {},
mounted() { },
methods: {
async handlerClick(item) {
await this.__$emitEvent({eventName: 'rich-text-tap', data: item, promiseCallback: (event, handler, awaitedResult) => {
if (!awaitedResult) return;
}})
await this.__$emitEvent({
eventName: 'rich-text-tap', data: item, promiseCallback: (event, handler, awaitedResult) => {
if (!awaitedResult) return;
}
})
}
}
};

View File

@@ -0,0 +1,541 @@
<template>
<view data-component-name="diy-rubik-cube">
<!-- 自定义 -->
<view v-if="value.mode == 'custom-rubik-cube'">
<view style="position: relative;"><rich-text :nodes="customHtml"></rich-text></view>
</view>
<view v-else :class="['rubik-cube', value.mode]" :style="rubikCubeWrapCss">
<!-- 1左2右 -->
<template v-if="value.mode == 'row1-lt-of2-rt'">
<view class="template-left">
<view :class="['item', value.mode]" @tap.stop="handlerClick(value.list[0].link, value.list[0].imageUrl)"
:style="{ marginRight: value.imageGap * 2 + 'rpx', width: list[0].imgWidth, height: list[0].imgHeight + 'px' }">
<image :src="$util.img(value.list[0].imageUrl)" :mode="list[0].imageMode || 'scaleToFill'"
:style="list[0].pageItemStyle" :show-menu-by-longpress="true" />
</view>
</view>
<view class="template-right">
<template v-for="(item, index) in list">
<template v-if="index > 0">
<view :key="index" :class="['item', value.mode]" @tap.stop="handlerClick(item.link, item.imageUrl)"
:style="{ marginBottom: value.imageGap * 2 + 'rpx', width: item.imgWidth, height: item.imgHeight + 'px' }">
<image :src="$util.img(item.imageUrl)" :mode="item.imageMode || 'scaleToFill'"
:style="item.pageItemStyle" :show-menu-by-longpress="true" />
</view>
</template>
</template>
</view>
</template>
<!-- 1左3右 -->
<template v-else-if="value.mode == 'row1-lt-of1-tp-of2-bm'">
<view class="template-left">
<view :class="['item', value.mode]"
:style="{ marginRight: value.imageGap * 2 + 'rpx', width: list[0].imgWidth, height: list[0].imgHeight + 'px' }"
@tap.stop="handlerClick(value.list[0].link, value.list[0].imageUrl)">
<image :src="$util.img(value.list[0].imageUrl)" :mode="list[0].imageMode || 'scaleToFill'"
:style="list[0].pageItemStyle" :show-menu-by-longpress="true" />
</view>
</view>
<view class="template-right">
<view :class="['item', value.mode]"
:style="{ marginBottom: value.imageGap * 2 + 'rpx', width: list[1].imgWidth, height: list[1].imgHeight + 'px' }"
@tap.stop="handlerClick(value.list[1].link, value.list[1].imageUrl)">
<image :src="$util.img(value.list[1].imageUrl)" :mode="list[1].imageMode || 'scaleToFill'"
:style="list[1].pageItemStyle" :show-menu-by-longpress="true" />
</view>
<view class="template-bottom">
<template v-for="(item, index) in list">
<template v-if="index > 1">
<view :key="index" :class="['item', value.mode]" @tap.stop="handlerClick(item.link, item.imageUrl)" :style="{
marginRight: value.imageGap * 2 + 'rpx',
width: item.imgWidth,
height: item.imgHeight + 'px'
}">
<image :src="$util.img(item.imageUrl)" :mode="item.imageMode || 'scaleToFill'"
:style="item.pageItemStyle" :show-menu-by-longpress="true" />
</view>
</template>
</template>
</view>
</view>
</template>
<template v-else>
<view :class="['item', value.mode]" v-for="(item, index) in list" :key="index"
@tap.stop="handlerClick(item.link, item.imageUrl)"
:style="{ marginRight: value.imageGap * 2 + 'rpx', marginBottom: value.imageGap * 2 + 'rpx', width: item.widthStyle, height: item.imgHeight + 'px' }">
<image :src="$util.img(item.imageUrl)" :mode="item.imageMode || 'scaleToFill'"
:style="item.pageItemStyle" :show-menu-by-longpress="true" />
</view>
</template>
</view>
</view>
</template>
<script>
// 魔方、橱窗
import htmlParser from '@/common/js/html-parser';
import DiyMinx from './minx.js'
export default {
name: 'diy-rubik-cube',
props: {
value: {
type: Object,
default: () => {
return {};
}
}
},
data() {
return {
customHtml: ''
};
},
created() {
if (this.value.mode == 'custom-rubik-cube') {
this.value.diyHtml = this.value.diyHtml.replace(/&quot;/g, '"');
this.customHtml = htmlParser(this.value.diyHtml);
} else {
var singleRow = {
'row1-of2': {
ratio: 2,
width: 'calc((100% - ' + uni.upx2px(this.value.imageGap * 2) + 'px) / 2)'
},
'row1-of3': {
ratio: 3,
width: 'calc((100% - ' + uni.upx2px(this.value.imageGap * 4) + 'px) / 3)'
},
'row1-of4': {
ratio: 4,
width: 'calc((100% - ' + uni.upx2px(this.value.imageGap * 6) + 'px) / 4)'
}
};
if (singleRow[this.value.mode]) {
this.calcSingleRow(singleRow[this.value.mode]);
} else if (this.value.mode == 'row2-lt-of2-rt') {
this.calcFourSquare();
} else if (this.value.mode == 'row1-lt-of2-rt') {
this.calcRowOneLeftOfTwoRight();
} else if (this.value.mode == 'row1-tp-of2-bm') {
this.calcRowOneTopOfTwoBottom();
} else if (this.value.mode == 'row1-lt-of1-tp-of2-bm') {
this.calcRowOneLeftOfOneTopOfTwoBottom();
}
}
},
mixins: [DiyMinx],
watch: {
// 组件刷新监听
componentRefresh: function (nval) { }
},
computed: {
list() {
var arr = JSON.parse(JSON.stringify(this.value.list));
arr.forEach((item, index) => {
item.pageItemStyle = this.countBorderRadius(this.value.mode, index);
});
return arr;
},
rubikCubeWrapCss() {
var obj = '';
obj += 'background-color:' + this.value.componentBgColor + ';';
if (this.value.componentAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
}
return obj;
}
},
methods: {
/**
* 魔方:单行多个,平分宽度
* 公式:
* 宽度:屏幕宽度/2示例375/2=187.5
* 比例:原图高/原图宽示例322/690=0.46
* 高度:宽度*比例示例187.5*0.46=86.25
*/
calcSingleRow(params) {
uni.getSystemInfo({
success: res => {
let maxHeight = 0;
this.list.forEach((item, index) => {
var ratio = item.imgHeight / item.imgWidth;
let width = res.windowWidth - uni.upx2px(this.value.margin.both *
2); // 减去左右间距
if (this.value.imageGap > 0) {
width -= uni.upx2px(params.ratio * this.value.imageGap * 2); // 减去间隙
}
item.imgWidth = width / params.ratio;
item.imgHeight = item.imgWidth * ratio;
if (maxHeight == 0 || maxHeight < item.imgHeight) maxHeight = item
.imgHeight;
})
this.list.forEach((item, index) => {
item.widthStyle = params.width;
item.imgHeight = maxHeight;
});
}
})
},
/**
* 魔方四方型各占50%
*/
calcFourSquare() {
uni.getSystemInfo({
success: res => {
let maxHeightFirst = 0;
let maxHeightTwo = 0;
this.list.forEach((item, index) => {
var ratio = item.imgHeight / item.imgWidth;
item.imgWidth = res.windowWidth;
item.imgWidth -= uni.upx2px(this.value.margin.both * 4);
if (this.value.imageGap > 0) {
item.imgWidth -= uni.upx2px(this.value.imageGap * 2);
}
item.imgWidth = item.imgWidth / 2;
item.imgHeight = item.imgWidth * ratio;
// 获取每行最大高度
if (index <= 1) {
if (maxHeightFirst == 0 || maxHeightFirst < item.imgHeight) {
maxHeightFirst = item.imgHeight;
}
} else if (index > 1) {
if (maxHeightTwo == 0 || maxHeightTwo < item.imgHeight) {
maxHeightTwo = item.imgHeight;
}
}
});
this.list.forEach((item, index) => {
item.imgWidth = 'calc((100% - ' + uni.upx2px(this.value.imageGap * 2) +
'px) / 2)';
item.widthStyle = item.imgWidth;
if (index <= 1) {
item.imgHeight = maxHeightFirst;
} else if (index > 1) {
item.imgHeight = maxHeightTwo;
}
});
}
});
},
/**
* 魔方1左2右
*/
calcRowOneLeftOfTwoRight() {
let rightHeight = 0; // 右侧两图平分高度
let divide = 'left'; // 划分规则leftright
if (this.list[1].imgWidth === this.list[2].imgWidth) divide = 'right';
uni.getSystemInfo({
success: res => {
this.list.forEach((item, index) => {
if (index == 0) {
var ratio = item.imgHeight / item.imgWidth; // 获取左图的尺寸比例
item.imgWidth = res.windowWidth - uni.upx2px(this.value.margin.both * 4) - uni.upx2px(this.value.imageGap * 2);
item.imgWidth = item.imgWidth / 2;
item.imgHeight = item.imgWidth * ratio;
rightHeight = (item.imgHeight - uni.upx2px(this.value.imageGap * 2)) / 2;
item.imgWidth += 'px';
} else {
item.imgWidth = this.list[0].imgWidth;
item.imgHeight = rightHeight;
}
});
}
});
},
/**
* 魔方1上2下
*/
calcRowOneTopOfTwoBottom() {
var maxHeight = 0;
uni.getSystemInfo({
success: res => {
this.list.forEach((item, index) => {
var ratio = item.imgHeight / item.imgWidth; // 获取左图的尺寸比例
if (index == 0) {
item.imgWidth = res.windowWidth - uni.upx2px(this.value.margin.both * 4);
} else if (index > 0) {
item.imgWidth = res.windowWidth - uni.upx2px(this.value.margin.both * 4) - uni.upx2px(this.value.imageGap * 2);
item.imgWidth = item.imgWidth / 2;
}
item.imgHeight = item.imgWidth * ratio;
// 获取最大高度
if (index > 0 && (maxHeight == 0 || maxHeight < item.imgHeight))
maxHeight = item.imgHeight;
});
this.list.forEach((item, index) => {
item.imgWidth += 'px';
item.widthStyle = item.imgWidth;
if (index > 0) item.imgHeight = maxHeight;
});
}
});
},
/**
* 魔方1左3右
*/
calcRowOneLeftOfOneTopOfTwoBottom() {
uni.getSystemInfo({
success: res => {
this.list.forEach((item, index) => {
// 左图
if (index == 0) {
var ratio = item.imgHeight / item.imgWidth; // 获取左图的尺寸比例
item.imgWidth = res.windowWidth - uni.upx2px(this.value.margin.both * 4) - uni.upx2px(this.value.imageGap * 2);
item.imgWidth = item.imgWidth / 2;
item.imgHeight = item.imgWidth * ratio;
} else if (index == 1) {
item.imgWidth = this.list[0].imgWidth;
item.imgHeight = (this.list[0].imgHeight - uni.upx2px(this.value.imageGap * 2)) / 2;
} else if (index > 1) {
item.imgWidth = (this.list[0].imgWidth - uni.upx2px(this.value.imageGap * 2)) / 2;
item.imgHeight = this.list[1].imgHeight;
}
});
this.list.forEach((item, index) => {
item.imgWidth += 'px';
item.imgHeight;
});
}
});
},
countBorderRadius(type, index) {
var obj = '';
if (this.value.elementAngle == 'right') {
return obj;
}
var defaultData = {
'row1-lt-of2-rt': [
['border-top-right-radius', 'border-bottom-right-radius'],
['border-top-left-radius', 'border-bottom-left-radius', 'border-bottom-right-radius'],
['border-top-left-radius', 'border-bottom-left-radius', 'border-top-right-radius']
],
'row1-lt-of1-tp-of2-bm': [
['border-top-right-radius', 'border-bottom-right-radius'],
['border-top-left-radius', 'border-bottom-left-radius', 'border-bottom-right-radius'],
['border-radius'],
['border-top-left-radius', 'border-bottom-left-radius', 'border-top-right-radius']
],
'row1-tp-of2-bm': [
['border-bottom-left-radius', 'border-bottom-right-radius'],
['border-top-left-radius', 'border-bottom-right-radius', 'border-top-right-radius'],
['border-top-left-radius', 'border-bottom-left-radius', 'border-top-right-radius']
],
'row2-lt-of2-rt': [
['border-top-right-radius', 'border-bottom-left-radius', 'border-bottom-right-radius'],
['border-top-left-radius', 'border-bottom-right-radius', 'border-bottom-left-radius'],
['border-top-left-radius', 'border-bottom-right-radius', 'border-top-right-radius'],
['border-top-left-radius', 'border-bottom-left-radius', 'border-top-right-radius']
],
'row1-of4': [
['border-top-right-radius', 'border-bottom-right-radius'],
['border-radius'],
['border-radius'],
['border-top-left-radius', 'border-bottom-left-radius']
],
'row1-of3': [
['border-top-right-radius', 'border-bottom-right-radius'],
['border-radius'],
['border-top-left-radius', 'border-bottom-left-radius']
],
'row1-of2': [
['border-top-right-radius', 'border-bottom-right-radius'],
['border-top-left-radius', 'border-bottom-left-radius']
]
};
defaultData[type][index].forEach((item, index) => {
// obj += item + ':' + this.value.aroundRadius * 2 + 'rpx;';
obj += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
});
return obj;
},
async handlerClick(link, imageUrl) {
await this.__$emitEvent({
eventName: 'rubik-cube-tap', data: link, promiseCallback: (event, handler, awaitedResult) => {
if (!awaitedResult) return;
if (link?.name || link?.wap_url || link?.appid) {
this.$util.diyRedirectTo(link);
} else if (imageUrl){
this.previewImg(imageUrl);
}
}
})
}
}
};
</script>
<style lang="scss">
.rubik-cube {
overflow: hidden;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.rubik-cube .item {
text-align: center;
line-height: 0;
overflow: hidden;
}
.rubik-cube .item image {
width: 100%;
max-width: 100%;
height: 100%;
}
// 一行两个
.rubik-cube .item.row1-of2 {
box-sizing: border-box;
margin-top: 0 !important;
margin-bottom: 0 !important;
}
.rubik-cube .item.row1-of2:nth-child(1) {
margin-left: 0 !important;
}
.rubik-cube .item.row1-of2:nth-child(2) {
margin-right: 0 !important;
}
// 一行三个
.rubik-cube .item.row1-of3 {
box-sizing: border-box;
margin-top: 0 !important;
margin-bottom: 0 !important;
}
.rubik-cube .item.row1-of3:nth-child(1) {
margin-left: 0 !important;
}
.rubik-cube .item.row1-of3:nth-child(3) {
margin-right: 0 !important;
}
// 一行四个
.rubik-cube .item.row1-of4 {
box-sizing: border-box;
margin-top: 0 !important;
margin-bottom: 0 !important;
}
.rubik-cube .item.row1-of4:nth-child(1) {
margin-left: 0 !important;
}
.rubik-cube .item.row1-of4:nth-child(4) {
margin-right: 0 !important;
}
// 两左两右
.rubik-cube .item.row2-lt-of2-rt {
// width: 50%;
display: inline-block;
box-sizing: border-box;
}
.rubik-cube .item.row2-lt-of2-rt:nth-child(1) {
margin-left: 0 !important;
margin-top: 0 !important;
}
.rubik-cube .item.row2-lt-of2-rt:nth-child(2) {
margin-right: 0 !important;
margin-top: 0 !important;
}
.rubik-cube .item.row2-lt-of2-rt:nth-child(3) {
margin-left: 0 !important;
margin-bottom: 0 !important;
}
.rubik-cube .item.row2-lt-of2-rt:nth-child(4) {
margin-right: 0 !important;
margin-bottom: 0 !important;
}
// 一左两右
.rubik-cube .template-left,
.rubik-cube .template-right {
// width: 50%;
box-sizing: border-box;
}
.rubik-cube .template-left .item.row1-lt-of2-rt:nth-child(1) {
margin-bottom: 0;
}
.rubik-cube .template-right .item.row1-lt-of2-rt:nth-child(2) {
margin-bottom: 0 !important;
}
.rubik-cube.row1-lt-of2-rt .template-right {
display: flex;
flex-direction: column;
justify-content: space-between;
}
// 一上两下
.rubik-cube .item.row1-tp-of2-bm:nth-child(1) {
width: 100%;
box-sizing: border-box;
margin-top: 0 !important;
margin-left: 0 !important;
margin-right: 0 !important;
}
.rubik-cube .item.row1-tp-of2-bm:nth-child(2) {
// width: 50%;
box-sizing: border-box;
margin-left: 0 !important;
margin-bottom: 0 !important;
}
.rubik-cube .item.row1-tp-of2-bm:nth-child(3) {
// width: 50%;
box-sizing: border-box;
margin-right: 0 !important;
margin-bottom: 0 !important;
}
// 一左三右
.rubik-cube .template-left .item.row1-lt-of1-tp-of2-bm {
width: 100%;
box-sizing: border-box;
}
.rubik-cube .template-bottom {
display: flex;
align-items: center;
justify-content: space-between;
}
.rubik-cube .template-bottom .item:nth-child(2) {
margin-right: 0 !important;
}
</style>

View File

@@ -0,0 +1,368 @@
<template>
<view data-component-name="diy-search" class="diy-search">
<view class="diy-search-wrap" :class="value.positionWay" :style="fixedCss">
<view :class="['search-box', 'search-box-' + value.searchStyle]" :style="searchWrapCss"
@tap.stop="handlerSearchClick">
<block v-if="[1, 2].includes(value.searchStyle)">
<view class="img" v-if="value.searchStyle == 2 && value.iconType == 'img'">
<image :src="$util.img(value.imageUrl)" mode="heightFix" />
</view>
<diy-icon class="icon" v-if="value.searchStyle == 2 && value.iconType == 'icon'" :icon="value.icon"
:value="value.style ? value.style : 'null'"
:style="{ maxWidth: 30 * 2 + 'rpx', maxHeight: 30 * 2 + 'rpx' }"></diy-icon>
<view class="search-content" :style="inputStyle">
<input type="text" class="uni-input ns-font-size-base" maxlength="50" :placeholder="value.title"
v-model="searchText" @confirm="handlerSearchClick" disabled="true"
:placeholderStyle="placeholderStyle" />
<text class="iconfont icon-sousuo3" @tap.stop="handlerSearchClick"
:style="{ color: value.textColor ? value.textColor : 'rgba(0,0,0,0)' }"></text>
</view>
</block>
<block v-if="value.searchStyle == 3">
<view class="search-content" :style="inputStyle"
@tap.stop="handlerSearchClick">
<text class="iconfont icon-sousuo3"
:style="{ color: value.textColor ? value.textColor : 'rgba(0,0,0,0)' }"></text>
<input type="text" class="uni-input ns-font-size-base" maxlength="50" :placeholder="value.title"
v-model="searchText" @confirm="handlerSearchClick" disabled="true"
@tap.stop="handlerSearchClick"
:placeholderStyle="placeholderStyle" />
<text class="search-content-btn" @tap.stop="handlerSearchClick"
:style="{ 'backgroundColor': value.pageBgColor ? value.pageBgColor : 'rgba(0,0,0,0)' }">搜索</text>
</view>
<view class="img" v-if="value.iconType == 'img'"
@tap.stop="handlerRedirectToClick(value.searchLink)">
<image :src="$util.img(value.imageUrl)" mode="heightFix" />
</view>
<diy-icon class="icon" v-if="value.iconType == 'icon'" :icon="value.icon"
:value="value.style ? value.style : 'null'"
:style="{ maxWidth: 30 * 2 + 'rpx', maxHeight: 30 * 2 + 'rpx' }"
@tap.stop="handlerRedirectToClick(value.searchLink)"></diy-icon>
</block>
</view>
</view>
<!-- 解决fixed定位后导航栏塌陷的问题 -->
<view v-if="value.positionWay == 'fixed'" class="u-navbar-placeholder"
:style="{ width: '100%', paddingTop: moduleHeight }"></view>
<ns-login ref="login"></ns-login>
</view>
</template>
<script>
// 获取系统状态栏的高度
let systemInfo = uni.getSystemInfoSync();
let menuButtonInfo = {};
// 如果是小程序,获取右上角胶囊的尺寸信息,避免导航栏右侧内容与胶囊重叠(支付宝小程序非本API尚未兼容)
// #ifdef MP-WEIXIN || MP-BAIDU || MP-TOUTIAO || MP-QQ
menuButtonInfo = uni.getMenuButtonBoundingClientRect();
// #endif
import DiyMinx from './minx.js'
// 搜索
export default {
name: 'diy-search',
props: {
value: {
type: Object,
default: () => {
return {};
}
},
topNavColor: String,
global: {
type: Object,
default: () => {
return {};
}
},
haveTopCategory: {
type: Boolean
},
followOfficialAccount: {
type: Object
},
},
data() {
return {
searchText: '',
menuButtonInfo: menuButtonInfo,
height: 0,
placeholderHeight: 0,
moduleHeight: 0
};
},
mixins: [DiyMinx],
computed: {
fixedCss() {
var obj = '';
if (this.value.positionWay == 'fixed') {
let top = this.fixedTop;
// 固定定位
if (this.global.topNavBg)
obj += 'background-color:' + (this.topNavColor == 'transparent' ? this.value.pageBgColor : this
.topNavColor) + ';';
else
obj += 'background-color:' + this.value.pageBgColor + ';';
obj += 'top:' + top + ';';
obj += 'padding-top:' + this.value.margin.top * 2 + 'rpx;';
obj += 'padding-left:' + this.value.margin.both * 2 + 'rpx;';
obj += 'padding-right:' + this.value.margin.both * 2 + 'rpx;';
obj += 'padding-bottom:' + this.value.margin.bottom * 2 + 'rpx;';
}
return obj;
},
searchWrapCss() {
var obj = '';
obj += 'background-color:' + this.value.componentBgColor + ';';
obj += 'text-align:' + this.value.textAlign + ';';
return obj;
},
inputStyle() {
var obj = '';
obj += 'background-color:' + this.value.elementBgColor + ';';
if (this.value.borderType == 2) {
obj += 'border-radius:' + '40rpx;';
}
return obj;
},
placeholderStyle() {
var obj = '';
if (this.value.textColor) {
obj += 'color:' + this.value.textColor;
} else {
obj += 'color: rgba(0,0,0,0)';
}
return obj;
},
fixedTop() {
let diyPositionObj = this.$store.state.diyGroupPositionObj;
let data = 0
if (diyPositionObj.diySearch && diyPositionObj.diyIndexPage && diyPositionObj.nsNavbar) {
if (diyPositionObj.diySearch.moduleIndex > diyPositionObj.diyIndexPage.moduleIndex) {
data = diyPositionObj.nsNavbar.originalVal + diyPositionObj.diyIndexPage.originalVal;
} else {
data = diyPositionObj.nsNavbar.originalVal;
}
} else if (diyPositionObj.diySearch && diyPositionObj.nsNavbar) {
data = diyPositionObj.nsNavbar.originalVal;
}
data += 'px';
return data;
}
},
watch: {
// 组件刷新监听
componentRefresh: function (nval) {
}
},
created() {
setTimeout(() => {
// 获取组件的高度默认高度为4545是在375屏幕上的高度
const query = uni.createSelectorQuery();
// #ifdef H5
let cssSelect = '.page-header .u-navbar';
// #endif
// #ifdef MP
let cssSelect = '.page-header >>> .u-navbar';
// #endif
query
.select(cssSelect)
.boundingClientRect(data => {
if (this.global.navBarSwitch) {
this.height = data ? data.height : 45;
} else {
this.height = data ? data.height : 0;
}
// 如果存在分类导航组件,则追加该组件的高度
if (this.haveTopCategory) {
this.height += 49;
}
})
.exec();
});
if (this.value.positionWay == 'fixed') this.navbarPlaceholderHeight();
},
mounted() {
if (this.value.positionWay == 'fixed')
this.setModuleLocationFn();
},
methods: {
search() {
this.$util.redirectTo('/pages_tool/goods/search');
},
redirectTo(link) {
if (link.wap_url) {
if (this.$util.getCurrRoute() == this.$util.MEMBER_PAGE_URL && !this.storeToken) {
this.$refs.login.open(link.wap_url);
return;
}
}
this.$util.diyRedirectTo(link);
},
navbarPlaceholderHeight() {
let height = 0;
setTimeout(() => {
const query = uni.createSelectorQuery().in(this);
query.select('.diy-search-wrap')
.boundingClientRect(data => {
// 获取搜索框自身高度
this.placeholderHeight = data.height;
// 通过搜索框自身高度 - 定位模式下的多出的padding-bottom高度
if (this.placeholderHeight) this.placeholderHeight -= this.value.margin.bottom;
}).exec();
});
},
// 向vuex中的diyIndexPositionObj增加搜索组件定位位置
setModuleLocationFn() {
this.$nextTick(() => {
const query = uni.createSelectorQuery().in(this);
query.select('.diy-search-wrap')
.boundingClientRect(data => {
let diySearch = {
originalVal: data.height || 0, //自身高度 px
currVal: 0, //定位高度
moduleIndex: this.value.moduleIndex //组件在diy-group的位置
};
this.moduleHeight = (data.height || 0) + 'px';
this.$store.commit('setDiyGroupPositionObj', {
'diySearch': diySearch
});
}).exec();
})
},
async handlerRedirectToClick(link) {
await this.__$emitEvent({
eventName: 'search-tap', data: link, promiseCallback: (event, handler, awaitedResult) => {
if (!awaitedResult) return;
this.redirectTo(link);
}
})
},
async handlerSearchClick(item) {
await this.__$emitEvent({
eventName: 'search-tap', data: item, promiseCallback: (event, handler, awaitedResult) => {
if (!awaitedResult) return;
this.search();
}
})
}
}
};
</script>
<style lang="scss">
::v-deep .uni-input-placeholder {
overflow: initial;
}
.diy-search-wrap {
overflow: hidden;
}
.fixed {
position: fixed;
left: 0;
right: 0;
top: 0;
z-index: 991;
transition: background 0.3s;
}
.search-box {
position: relative;
display: flex;
align-items: center;
.img {
height: 60rpx;
margin-right: 20rpx;
image {
width: 100%;
height: 100%;
}
}
.icon {
width: 170rpx;
height: 60rpx;
margin-right: 20rpx;
}
}
.search-box-3 {
.search-content {
display: flex;
align-items: center;
height: 68rpx;
.iconfont {
position: initial;
transform: translateY(0);
width: auto;
margin-left: 26rpx;
margin-right: 12rpx;
font-size: $font-size-base;
line-height: 1;
}
.uni-input {
flex: 1;
padding-left: 0;
height: 68rpx;
}
.search-content-btn {
margin-right: 8rpx;
width: 116rpx;
height: 54rpx;
line-height: 54rpx;
text-align: center;
color: #fff;
border-radius: 30rpx;
}
}
.diy-icon {
margin-left: 20rpx;
margin-right: 0;
width: auto;
font-size: 74rpx;
}
.img {
margin-left: 20rpx;
margin-right: 0;
}
}
.search-content {
flex: 1;
}
.search-content input {
box-sizing: border-box;
display: block;
height: 64rpx;
width: 100%;
padding: 0 20rpx 0 40rpx;
color: #333333;
background: none;
}
.search-content .iconfont {
position: absolute;
top: 50%;
right: 4rpx;
transform: translateY(-50%);
font-size: 30rpx;
z-index: 10;
width: 80rpx;
font-weight: bold;
text-align: center;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,180 @@
<template>
<x-skeleton data-component-name="diy-store-label" type="banner" :loading="loading" :configs="skeletonConfig">
<view class="diy-store-label">
<block v-if="businessConfig.store_business == 'store'">
<scroll-view scroll-x="true" :class="[value.contentStyle, { between: list.length == 3 }]"
:style="storeLabelWrapCss" :enable-flex="true">
<view v-for="(item, index) in storeLabel" :key="index" :class="['item']">
<diy-icon v-if="value.icon" class="icon-box" :icon="value.icon"
:value="value.style ? value.style : 'null'"></diy-icon>
<text class="label-name"
:style="{ color: value.textColor, fontSize: value.fontSize * 2 + 'rpx', fontWeight: value.fontWeight }">{{
item }}</text>
</view>
</scroll-view>
</block>
<block v-else>
<scroll-view scroll-x="true" :class="[value.contentStyle, { between: list.length == 3 }]"
:style="storeLabelWrapCss" :enable-flex="true">
<view v-for="(item, index) in list" :class="['item']">
<diy-icon v-if="value.icon" class="icon-box" :icon="value.icon"
:value="value.style ? value.style : 'null'"></diy-icon>
<text class="label-name"
:style="{ color: value.textColor, fontSize: value.fontSize * 2 + 'rpx', fontWeight: value.fontWeight }">{{
item.label_name }}</text>
</view>
</scroll-view>
</block>
</view>
</x-skeleton>
</template>
<script>
// 门店标签
import DiyMinx from './minx.js'
export default {
name: 'diy-store-label',
props: {
value: {
type: Object
}
},
mixins: [DiyMinx],
data() {
return {
loading: true,
skeletonConfig: {
gridRows: 1,
gridRowsGap: '20rpx',
headHeight: '40rpx',
headBorderRadius: '0'
},
list: [],
notice: '',
storeLabel: [],
businessConfig: ""
};
},
created() {
this.getData();
this.getStoreConfig();
},
watch: {
// 组件刷新监听
componentRefresh: function (nval) {
this.getData();
}
},
computed: {
storeLabelWrapCss: function () {
var obj = '';
obj += 'background-color:' + this.value.componentBgColor + ';';
if (this.value.componentAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
}
return obj;
}
},
methods: {
getData() {
var data = {
page: 1,
page_size: 0
};
if (this.value.sources == 'initial') {
data.page_size = this.value.count;
} else if (this.value.sources == 'diy') {
data.label_id_arr = this.value.labelIds.toString();
}
this.$api.sendRequest({
url: '/store/api/store/labelPage',
data: data,
success: res => {
if (res.code == 0 && res.data) {
this.list = res.data.list;
}
this.loading = false;
}
});
},
getStoreConfig() {
this.$api.sendRequest({
url: '/store/api/config/config',
success: res => {
if (res.code >= 0) {
this.businessConfig = res.data.business_config;
if (res.data.business_config.store_business == "store") {
this.getStoreInfo();
}
}
}
});
},
getStoreInfo() {
this.$api.sendRequest({
url: '/api/store/info',
success: res => {
if (res.data) {
let label_arr = res.data.label_name.split(",");
let label_count = 3;
if (this.value.sources == 'initial') label_count = this.value.count;
for (let i = 0; i < label_arr.length; i++) {
if (this.storeLabel.length < label_count && label_arr[i] != '') {
this.storeLabel.push(label_arr[i])
}
}
}
}
});
},
}
};
</script>
<style lang="scss">
.diy-store-label {
.style-1 {
display: flex;
align-items: baseline;
::v-deep .uni-scroll-view-content {
display: flex;
align-items: center;
}
&.between {
justify-content: space-between;
::v-deep .uni-scroll-view-content {
justify-content: space-between;
}
}
.item {
flex-shrink: 0;
display: flex;
align-items: center;
padding-right: 20rpx;
.icon-box {
font-size: 50rpx;
width: 40rpx;
height: 40rpx;
margin-right: 10rpx;
margin-top: 2rpx;
}
.label-name {
line-height: 40rpx;
}
&:last-of-type {
padding-right: 0;
}
}
}
}
</style>

View File

@@ -3,7 +3,7 @@
<block v-if="value.style == 1">
<view class="store-box store-one">
<view class="store-info">
<view class="info-box" :style="{ color: value.textColor }" @click="toStoreList()">
<view class="info-box" :style="{ color: value.textColor }" @tap.stop="toStoreList()">
<block v-if="globalStoreInfo && globalStoreInfo.store_id">
<text class="title">{{ globalStoreInfo.store_name }}</text>
<text>
@@ -15,22 +15,25 @@
</view>
<view class="address-wrap" :style="{ color: value.textColor }">
<text class="iconfont icon-dizhi"></text>
<text v-if="globalStoreInfo && globalStoreInfo.store_id" @click="mapRoute" class="address">{{ globalStoreInfo.show_address }}</text>
<text v-if="globalStoreInfo && globalStoreInfo.store_id" @tap.stop="mapRoute" class="address">{{
globalStoreInfo.show_address }}</text>
<text v-else>获取当前位置...</text>
</view>
</view>
<view class="store-image" @click="selectStore()">
<image :src="$util.img(globalStoreInfo.store_image)" v-if="globalStoreInfo && globalStoreInfo.store_image" mode="aspectFill"></image>
<view class="store-image" @tap.stop="selectStore()">
<image :src="$util.img(globalStoreInfo.store_image)"
v-if="globalStoreInfo && globalStoreInfo.store_image" mode="aspectFill"></image>
<image :src="$util.getDefaultImage().store" v-else mode="aspectFill"></image>
</view>
</view>
</block>
<block v-if="value.style == 2">
<view class="store-box store-three" @click="toStoreList()">
<view class="store-box store-three" @tap.stop="toStoreList()">
<view class="store-info">
<view class="store-image" @click="selectStore()">
<image :src="$util.img(globalStoreInfo.store_image)" v-if="globalStoreInfo && globalStoreInfo.store_image" mode="aspectFill"></image>
<view class="store-image" @tap.stop="selectStore()">
<image :src="$util.img(globalStoreInfo.store_image)"
v-if="globalStoreInfo && globalStoreInfo.store_image" mode="aspectFill"></image>
<image :src="$util.getDefaultImage().store" v-else mode="aspectFill"></image>
</view>
<view class="info-box" :style="{ color: value.textColor }">
@@ -44,12 +47,13 @@
<text class="title" v-else>定位中...</text>
</view>
</view>
<view class="store-icon" @click.stop="search()"><text class="iconfont icon-sousuo3" :style="{ color: value.textColor }"></text></view>
<view class="store-icon" @tap.stop="search()"><text class="iconfont icon-sousuo3"
:style="{ color: value.textColor }"></text></view>
</view>
</block>
<block v-if="value.style == 3">
<view class="store-box store-four" @click="toStoreList()">
<view class="store-box store-four" @tap.stop="toStoreList()">
<view class="store-left-wrap">
<block v-if="globalStoreInfo && globalStoreInfo.store_id">
<text class="iconfont icon-weizhi" :style="{ color: value.textColor }"></text>
@@ -59,8 +63,9 @@
<text class="title" v-else>定位中...</text>
</view>
<view class="store-right-search">
<input type="text" class="uni-input font-size-tag" disabled placeholder="商品搜索" @click.stop="search()" />
<text class="iconfont icon-sousuo3" @click.stop="search()"></text>
<input type="text" class="uni-input font-size-tag" disabled placeholder="商品搜索"
@tap.stop="search()" />
<text class="iconfont icon-sousuo3" @tap.stop="search()"></text>
</view>
</view>
</block>
@@ -70,6 +75,7 @@
<script>
//
import Map from '@/common/js/map/openMap.js';
import DiyMinx from './minx.js'
export default {
name: 'diy-store',
props: {
@@ -77,15 +83,16 @@ export default {
type: Object
}
},
mixins: [DiyMinx],
data() {
return {};
},
computed: {},
watch: {
//
componentRefresh: function(nval) {}
componentRefresh: function (nval) { }
},
created() {},
created() { },
methods: {
//
toStoreList() {
@@ -117,6 +124,7 @@ export default {
justify-content: space-between;
align-items: center;
}
.store-info {
height: 100rpx;
display: flex;
@@ -151,6 +159,7 @@ export default {
font-size: $font-size-goods-tag;
}
}
.address-wrap {
line-height: 1.2;
font-size: $font-size-goods-tag;
@@ -193,17 +202,21 @@ export default {
align-self: flex-start;
margin-right: 14rpx;
}
.info-box {
margin-bottom: 0 !important;
}
.store-info {
height: 106rpx;
}
.switchover {
display: flex;
width: 120rpx;
}
}
.store-three {
.store-info {
height: auto;
@@ -211,10 +224,12 @@ export default {
flex-direction: inherit;
align-items: center;
}
.info-box {
margin-left: 18rpx;
margin-bottom: 0 !important;
}
.store-icon text {
font-size: 36rpx;
color: #fff;
@@ -228,6 +243,7 @@ export default {
display: flex;
align-items: center;
line-height: 1;
.icon-weizhi {
margin-right: 6rpx;
font-size: 28rpx;

834
components-diy/diy-tab.vue Normal file
View File

@@ -0,0 +1,834 @@
<template>
<!-- DIY 标签页组件 - 支持多种样式和位置的标签页切换 -->
<view data-component-name="diy-tab" class="diy-tab" :class="'tab-position-' + mergedValue.tabPosition"
:style="[getCustomStyle('container')]">
<!-- 标签导航栏 -->
<view class="tab-nav" :style="[tabNavStyle, getCustomStyle('nav')]">
<!-- 标签项循环渲染 -->
<view v-for="(tab, index) in mergedValue.tabs" :key="tab.id || index"
:class="['tab-item', mergedValue.tabStyle, { active: activeTab === index }]" @tap="switchTab(index)"
:style="[tabItemStyle(index), getCustomStyle('tabItem'), activeTab === index ? getCustomStyle('tabItemActive') : {}]">
<!-- 标签文本 -->
<text
:style="[tabTextStyle(index), getCustomStyle('tabText'), activeTab === index ? getCustomStyle('tabTextActive') : {}]">{{
getTabTitle(tab.title) }}</text>
<!-- 标签指示器底部线条 -->
<view v-if="mergedValue.showIndicator" class="tab-indicator"
:style="[tabIndicatorStyle(index), getCustomStyle('indicator')]"></view>
</view>
</view>
<!-- 标签内容区域 -->
<view class="tab-content" :style="[tabContentStyle, getCustomStyle('content')]">
<!-- 标签面板循环渲染 -->
<view v-for="(tab, index) in mergedValue.tabs" :key="index"
:class="['tab-panel', { active: activeTab === index }]"
:style="[tabPanelStyle(index), getCustomStyle('panel'), activeTab === index ? getCustomStyle('panelActive') : {}]">
<!-- 渲染每个标签下的组件 -->
<diy-group v-if="tab.components" :diyData="{ value: tab.components, global: diyGlobal }"
:scrollTop="tab.scrollTop || 0" />
</view>
</view>
</view>
</template>
<script>
// 导入 DIY 混入
import DiyMinx from './minx.js'
export default {
name: 'diy-tab',
// 组件注册 - 使用懒加载解决循环依赖
components: {
diyGroup: () => import('./diy-group.vue')
},
// 组件属性定义
props: {
// 标签页配置对象
value: {
type: Object,
default: () => ({})
},
// 全局配置对象
diyGlobal: {
type: Object,
default: () => ({})
}
},
// 混入
mixins: [DiyMinx],
// 组件数据
data() {
return {
activeTab: this.value?.activeTabIndex ?? 0, // 设置当前激活的标签索引
};
},
// 组件创建钩子
created() {
console.log(`diy-tab-create`, {
value: this.mergedValue,
tabs: this.mergedValue.tabs
});
},
// 计算属性
computed: {
// 合并默认值和传入值
mergedValue() {
// 标签页数据配置
const tabsConfig = {
/**
* 标签页数据配置
* @type {Array<{title: string|Object, scrollTop: number, components: Array}>}
* @property {string} id 标签唯一标识
* @property {string|Object} title - 标签标题
* • 字符串: 普通文本或国际化键(如 'tab.home'
* • 对象: 多语言映射(如 { 'zh-cn': '首页', 'en-us': 'Home' }
* @property {number} scrollTop - 标签滚动位置
* @property {Array} components - 标签下的组件列表
*/
tabs: []
};
// 基础配置
const baseConfig = {
/**
* 是否显示指示器
* @type {boolean}
* @default true
*/
showIndicator: true,
/**
* 激活的标签索引
* @type {number}
* @default 0
*/
activeTabIndex: 0,
/**
* 标签样式
* @type {string}
* @default 'default'
* @values 'default', 'underline', 'card'
*/
tabStyle: 'default',
/**
* 标签位置
* @type {string}
* @default 'top'
* @values 'top', 'bottom', 'left', 'right'
*/
tabPosition: 'top'
};
// 导航栏样式
const navConfig = {
/**
* 标签栏高度
* @type {number|string}
* @default 24
* @unit 像素(当为数字时)
* @range 建议值20-80
* @format
* • 数字: 像素值(如 24
* • 字符串: CSS长度值如 '24px', '3rem', '4em'
* • CSS变量: 'var(--tab-height)'
* • 百分比: '10%' (相对父元素高度)
*/
tabHeight: 24,
/**
* 标签栏背景色
* @type {string}
* @default '#ffffff'
* @format CSS颜色值
*/
tabBgColor: '#ffffff',
/**
* 标签栏内边距
* @type {string}
* @default '0'
* @format CSS padding值
* @examples
* • 单个值: '0', '10px', '1rem' (四向相同)
* • 两个值: '10px 20px' (上下 左右)
* • 三个值: '10px 20px 30px' (上 左右 下)
* • 四个值: '10px 20px 30px 40px' (上 右 下 左)
* • CSS变量: 'var(--tab-padding)'
* • 百分比: '5% 10%' (相对父元素宽度)
* @note 卡片样式下会忽略此配置,自动使用基于 tabGap 的内边距
*/
tabPadding: '0'
};
// 标签项样式
const tabItemConfig = {
/**
* 标签间距
* @type {number|string}
* @default 10
* @unit 像素(当为数字时)
* @range 建议值0-30
* @format
* • 数字: 像素值(如 10
* • 字符串: CSS长度值如 '10px', '0.5rem', '1em'
* • CSS变量: 'var(--tab-gap)'
* • 百分比: '5%' (相对父元素宽度)
*/
tabGap: 10,
/**
* 字体大小
* @type {number|string}
* @default 14
* @unit 像素(当为数字时)
* @range 建议值10-20
* @format
* • 数字: 像素值(如 14
* • 字符串: CSS长度值如 '14px', '0.875rem', '1.4em'
* • CSS变量: 'var(--font-size)'
*/
fontSize: 14,
/**
* 激活状态颜色
* @type {string}
* @default '#ff4444'
* @format CSS颜色值
*/
activeColor: '#ff4444',
/**
* 非激活状态颜色
* @type {string}
* @default '#666666'
* @format CSS颜色值
*/
inactiveColor: '#666666'
};
// 卡片样式
const cardConfig = {
/**
* 卡片默认背景色
* @type {string}
* @default '#f5f5f5'
* @format CSS颜色值
*/
cardBgColor: '#f5f5f5',
/**
* 卡片激活背景色
* @type {string}
* @default '#ff4444'
* @format CSS颜色值
*/
cardActiveBgColor: '#ff4444',
/**
* 卡片默认文字颜色
* @type {string}
* @default '#666666'
* @format CSS颜色值
*/
cardTextColor: '#666666',
/**
* 卡片激活文字颜色
* @type {string}
* @default '#ffffff'
* @format CSS颜色值
*/
cardActiveTextColor: '#ffffff',
/**
* 卡片圆角大小
* @type {string}
* @default '16px'
* @format CSS长度值
*/
cardBorderRadius: '16px',
/**
* 卡片外边距
* @type {string}
* @default '0 5px'
* @format CSS margin值
*/
cardMargin: '0 5px',
/**
* 卡片内边距
* @type {string}
* @default '0 10px'
* @format CSS padding值
*/
cardPadding: '0 10px'
};
// 下划线样式
const underlineConfig = {
/**
* 下划线颜色
* @type {string}
* @default '#ff4444'
* @format CSS颜色值
*/
underlineColor: '#ff4444',
/**
* 下划线高度
* @type {number}
* @default 2
* @unit 像素
*/
underlineHeight: 2,
/**
* 下划线圆角大小
* @type {string}
* @default '1px'
* @format CSS长度值
*/
underlineBorderRadius: '1px',
/**
* 下划线左右边距
* @type {number}
* @default 10
* @unit 像素
*/
underlineMargin: 10
};
// 指示器样式
const indicatorConfig = {
/**
* 指示器颜色
* @type {string}
* @default '#ff4444'
* @format CSS颜色值
*/
indicatorColor: '#ff4444',
/**
* 指示器高度
* @type {number}
* @default 2
* @unit 像素
*/
indicatorHeight: 2
};
// 内容区域样式
const contentConfig = {
/**
* 内容区内边距
* @type {number|string}
* @default 10
* @unit 像素(当为数字时)
* @range 建议值0-50
* @format
* • 数字: 像素值(如 10
* • 字符串: CSS长度值如 '10px', '1rem', '2em'
* • CSS变量: 'var(--content-padding)'
* • 百分比: '5%' (相对父元素宽度)
*/
contentPadding: 10,
/**
* 内容区背景色
* @type {string}
* @default '#f5f5f5'
* @format CSS颜色值
* @examples
* • 十六进制: '#ffffff', '#f5f5f5'
* • RGB: 'rgb(255, 255, 255)'
* • RGBA: 'rgba(255, 255, 255, 0.5)'
* • HSL: 'hsl(0, 0%, 100%)'
* • CSS变量: 'var(--content-bg-color)'
* • 预定义颜色: 'white', 'black', 'gray'
*/
contentBgColor: '#f5f5f5',
/**
* 内容区最小高度
* @type {number|string}
* @default 200
* @unit 像素(当为数字时)
* @range 建议值50-1000
* @format
* • 数字: 像素值(如 200
* • 字符串: CSS长度值如 '200px', '20vh', '5rem'
* • CSS变量: 'var(--content-min-height)'
* • 百分比: '50%' (相对父元素高度)
*/
contentMinHeight: 200
};
// 自定义样式配置
const customStylesConfig = {
/**
* 自定义样式配置
* @type {Object}
* @description 允许外部通过完整的 CSS 样式对象完全覆盖各个部分的样式
* @property {Object} container - 容器样式
* @example { width: '100%', height: '500px', backgroundColor: '#f0f0f0' }
* @property {Object} nav - 导航栏样式
* @example { backgroundColor: '#ffffff', borderBottom: '1px solid #e0e0e0' }
* @property {Object} tabItem - 标签项样式(非激活状态)
* @example { padding: '10px 20px', borderRadius: '4px' }
* @property {Object} tabItemActive - 标签项激活样式
* @example { backgroundColor: '#ff4444', color: '#ffffff' }
* @property {Object} tabText - 标签文本样式(非激活状态)
* @example { fontSize: '14px', color: '#666666' }
* @property {Object} tabTextActive - 标签文本激活样式
* @example { fontSize: '16px', color: '#ffffff', fontWeight: 'bold' }
* @property {Object} indicator - 指示器样式
* @example { backgroundColor: '#ff4444', height: '3px' }
* @property {Object} content - 内容区域样式
* @example { padding: '20px', backgroundColor: '#f9f9f9' }
* @property {Object} panel - 标签面板样式(非激活状态)
* @example { opacity: 0.5, transform: 'translateY(10px)' }
* @property {Object} panelActive - 标签面板激活样式
* @example { opacity: 1, transform: 'translateY(0)' }
*/
customStyles: {
container: {},
nav: {},
tabItem: {},
tabItemActive: {},
tabText: {},
tabTextActive: {},
indicator: {},
content: {},
panel: {},
panelActive: {}
}
};
// 合并所有配置
const defaults = {
...tabsConfig,
...baseConfig,
...navConfig,
...tabItemConfig,
...cardConfig,
...underlineConfig,
...indicatorConfig,
...contentConfig,
...customStylesConfig
};
// 使用展开运算符合并默认值和传入值
return { ...defaults, ...this.value };
},
// 判断是否为水平布局(顶部或底部)
isHorizontal() {
return ['top', 'bottom'].includes(this.mergedValue.tabPosition);
},
// 判断是否为垂直布局(左侧或右侧)
isVertical() {
return ['left', 'right'].includes(this.mergedValue.tabPosition);
},
// 判断是否为卡片样式
isCardStyle() {
return this.mergedValue.tabStyle === 'card';
},
// 判断是否为下划线样式
isUnderlineStyle() {
return this.mergedValue.tabStyle === 'underline';
},
// 计算标签导航栏样式
tabNavStyle() {
const style = {
backgroundColor: this.mergedValue.tabBgColor || '#ffffff'
};
// 根据布局方向设置尺寸
if (this.isHorizontal) {
// 水平布局:设置高度
style.height = this.mergedValue.tabHeight + 'px';
} else {
// 垂直布局:设置宽度和高度
style.width = this.mergedValue.tabHeight + 'px';
style.height = '100%';
style.flexDirection = 'column';
}
// 设置导航栏内边距
if (this.mergedValue.tabPadding) {
style.padding = this.mergedValue.tabPadding;
}
// 卡片样式下使用标签间距作为内边距
if (this.isCardStyle) {
style.padding = this.getPadding(this.mergedValue.tabGap);
}
return style;
},
// 计算标签项样式(返回函数)
tabItemStyle() {
return (index) => {
const style = {};
if (!this.isCardStyle) {
// 非卡片样式:设置内边距
style.padding = this.getPadding(this.mergedValue.tabGap);
} else {
// 卡片样式:设置外边距、内边距、圆角和背景色
style.margin = this.getCardMargin();
style.padding = this.getCardPadding();
style.borderRadius = this.mergedValue.cardBorderRadius || '16px';
// 根据激活状态设置不同的背景色
style.backgroundColor = index === this.activeTab
? (this.mergedValue.cardActiveBgColor || this.mergedValue.activeColor)
: (this.mergedValue.cardBgColor || '#f5f5f5');
}
return style;
};
},
// 计算标签内容区域样式
tabContentStyle() {
return {
padding: this.mergedValue.contentPadding + 'px',
backgroundColor: this.mergedValue.contentBgColor || '#f5f5f5',
minHeight: (this.mergedValue.contentMinHeight || 200) + 'px'
};
},
// 计算标签文本样式(返回函数)
tabTextStyle() {
return (index) => ({
color: this.activeColor(index),
fontSize: (this.mergedValue.fontSize || 14) + 'px'
});
},
// 计算标签指示器样式(返回函数)
tabIndicatorStyle() {
return (index) => {
const style = {
backgroundColor: this.mergedValue.indicatorColor || this.mergedValue.activeColor,
transform: index === this.activeTab ? 'scaleX(1)' : 'scaleX(0)',
opacity: index === this.activeTab ? 1 : 0
};
// 卡片样式下隐藏指示器
if (this.isCardStyle) {
style.display = 'none';
} else if (this.isUnderlineStyle) {
// 下划线样式使用下划线颜色
style.backgroundColor = this.mergedValue.underlineColor || this.mergedValue.activeColor;
}
// 根据样式类型选择指示器尺寸
const indicatorSize = (this.isUnderlineStyle ? this.mergedValue.underlineHeight : this.mergedValue.indicatorHeight) || 2;
// 根据布局方向设置指示器样式
if (this.isHorizontal) {
// 水平布局:设置高度,使用 scaleX 动画
style.height = indicatorSize + 'px';
style.transform = index === this.activeTab ? 'scaleX(1)' : 'scaleX(0)';
} else {
// 垂直布局:设置宽度,使用 scaleY 动画
style.width = indicatorSize + 'px';
style.height = 'auto';
style.transform = index === this.activeTab ? 'scaleY(1)' : 'scaleY(0)';
}
return style;
};
},
// 计算标签面板样式(返回函数)
tabPanelStyle() {
return (index) => {
const isActive = index === this.activeTab;
const style = {
display: isActive ? 'block' : 'none',
opacity: isActive ? 1 : 0
};
// 根据标签位置定义不同的动画效果
const transformMap = {
top: ['translateY(0)', 'translateY(10px)'], // 顶部:从下往上滑入
bottom: ['translateY(0)', 'translateY(-10px)'], // 底部:从上往下滑入
left: ['translateX(0)', 'translateX(10px)'], // 左侧:从右往左滑入
right: ['translateX(0)', 'translateX(-10px)'] // 右侧:从左往右滑入
};
// 获取对应的变换值,默认使用无变换
const transforms = transformMap[this.mergedValue.tabPosition] || ['translate(0)', 'translate(0)'];
// 根据激活状态应用不同的变换
style.transform = isActive ? transforms[0] : transforms[1];
return style;
};
}
},
// 组件方法
methods: {
// 获取自定义样式
getCustomStyle(type) {
const customStyles = this.mergedValue.customStyles || {};
return customStyles[type] || {};
},
// 获取标签文字颜色
activeColor(index) {
if (this.isCardStyle) {
// 卡片样式:使用卡片文字颜色
return index === this.activeTab
? (this.mergedValue.cardActiveTextColor || '#ffffff')
: (this.mergedValue.cardTextColor || this.mergedValue.inactiveColor);
}
// 其他样式:使用通用文字颜色
return index === this.activeTab ? this.mergedValue.activeColor : this.mergedValue.inactiveColor;
},
// 根据布局方向获取内边距
getPadding(gap) {
return this.isHorizontal ? `0 ${gap}px` : `${gap}px 0`;
},
// 根据布局方向获取外边距
getMargin(gap) {
return this.isHorizontal ? `0 ${gap}px` : `${gap}px 0`;
},
// 获取卡片外边距
getCardMargin() {
return this.mergedValue.cardMargin || this.getMargin(this.mergedValue.tabGap / 2);
},
// 获取卡片内边距
getCardPadding() {
return this.mergedValue.cardPadding || (this.isHorizontal ? '0 10px' : '10px 0');
},
// 切换标签
switchTab(index) {
this.activeTab = index;
},
// 国际化方法:获取标签标题
getTabTitle(title) {
const locale = this.$langConfig.getCurrentLocale();
// 如果 title 是对象,根据当前语言返回对应值
if (typeof title === 'object' && title !== null) {
return title[locale] || title['zh-cn'] || Object.values(title)[0] || '';
}
// 如果 title 是字符串,保持原有逻辑
if (typeof title === 'string' && title.includes('.')) {
// 包含点号的标题视为国际化键,使用全局挂载的 $lang 方法翻译
return this.$lang ? this.$lang(title) : title;
}
// 不包含点号的标题直接返回
return title;
}
}
}
</script>
<style lang="scss" scoped>
// ===== 标签布局 Mixin =====
// 用于定义不同位置标签的布局方式
@mixin tab-layout($direction, $nav-order, $content-order) {
flex-direction: $direction;
.tab-nav {
order: $nav-order;
}
.tab-content {
order: $content-order;
flex: 1;
}
}
// ===== 指示器位置 Mixin =====
// 用于定义不同位置指示器的定位
@mixin indicator-position($position, $start, $end) {
#{$position}: 0;
#{$start}: 0;
#{$end}: 0;
width: 2px;
height: auto;
}
// ===== 主容器样式 =====
.diy-tab {
width: 100%;
height: 100%;
overflow: hidden;
display: flex;
// 默认顶部布局
@include tab-layout(column, 1, 2);
// 底部布局
&.tab-position-bottom {
@include tab-layout(column, 2, 1);
}
// 左侧布局
&.tab-position-left {
@include tab-layout(row, 1, 2);
}
// 右侧布局
&.tab-position-right {
@include tab-layout(row, 2, 1);
}
// ===== 标签导航栏样式 =====
.tab-nav {
display: flex;
align-items: center;
justify-content: flex-start;
overflow-x: auto; // 水平滚动
overflow-y: hidden; // 禁止垂直滚动
white-space: nowrap; // 不换行
position: relative;
// 隐藏滚动条Webkit 浏览器)
&::-webkit-scrollbar {
display: none;
}
// 隐藏滚动条IE/Edge
-ms-overflow-style: none;
// 隐藏滚动条Firefox
scrollbar-width: none;
}
// ===== 标签项样式 =====
.tab-item {
display: inline-flex;
align-items: center;
justify-content: center;
position: relative;
padding: 0 16px;
height: 100%;
transition: all 0.3s ease;
// 激活状态
&.active {
font-weight: 500;
}
// 减少动画效果(针对偏好减少动画的用户)
@media (prefers-reduced-motion: reduce) {
transition: none;
}
}
// 左右布局下的标签项样式
&.tab-position-left .tab-item,
&.tab-position-right .tab-item {
width: 100%;
height: auto;
padding: 10px 0;
white-space: normal; // 允许换行
}
// ===== 标签文本样式 =====
.tab-text {
font-size: 14px;
transition: color 0.3s ease;
}
// ===== 标签指示器样式 =====
.tab-indicator {
position: absolute;
bottom: 0;
left: 16px;
right: 16px;
transition: all 0.3s ease;
transform-origin: center;
}
// 左侧布局的指示器
&.tab-position-left .tab-indicator {
@include indicator-position(left, top, bottom);
}
// 右侧布局的指示器
&.tab-position-right .tab-indicator {
@include indicator-position(right, top, bottom);
}
// ===== 标签内容区域样式 =====
.tab-content {
width: 100%;
overflow: hidden;
position: relative;
}
// ===== 标签面板样式 =====
.tab-panel {
width: 100%;
min-height: 200px;
transition: all 0.3s ease;
// 减少动画效果
@media (prefers-reduced-motion: reduce) {
transition: none;
}
}
// ===== 默认和下划线样式 =====
.tab-item.default,
.tab-item.underline {
padding: 0 10px;
transition: all 0.3s ease;
&.active {
color: #ff4444;
font-weight: 500;
}
}
// 下划线样式的伪元素
.tab-item.underline.active::after {
content: '';
position: absolute;
bottom: 0;
left: 10px;
right: 10px;
transition: all 0.3s ease;
}
// ===== 卡片样式 =====
.tab-item.card {
border-radius: 16px;
transition: all 0.3s ease;
&.active {
font-weight: 500;
}
}
}
</style>

View File

@@ -1,17 +1,20 @@
<template>
<view data-component-name="diy-text" class="diy-text" @click="handlerClick(value.link)" @tap="handlerClick(value.link)" :style="warpCss">
<view :class="value.style == 'style-8' ? 'title2' : 'title'" :style="{ fontSize: value.fontSize * 2 + 'rpx', color: value.textColor }">
<view data-component-name="diy-text" class="diy-text" @tap="handlerClick(value.link)" :style="warpCss">
<view :class="value.style == 'style-8' ? 'title2' : 'title'"
:style="{ fontSize: value.fontSize * 2 + 'rpx', color: value.textColor }">
<block v-if="value.style == 'style-0'" style="height: 40rpx; line-height: 40rpx;">
<text :style="{ fontWeight: value.fontWeight}">{{ value.text }}</text>
<text :style="{ fontWeight: value.fontWeight }">{{ value.text }}</text>
</block>
<block v-if="value.style == 'style-1'" style="height: 40rpx; line-height: 40rpx;">
<view class="text-left" :style="{ color: value.textColor}"><text></text></view>
<text :style="{ fontWeight: value.fontWeight}">{{ value.text }}</text>
<view class="text-right" :style="{ color: value.textColor}"><text></text></view>
<view class="text-left" :style="{ color: value.textColor }"><text></text></view>
<text :style="{ fontWeight: value.fontWeight }">{{ value.text }}</text>
<view class="text-right" :style="{ color: value.textColor }"><text></text></view>
</block>
<block v-else-if="value.style == 'style-2'">
<view class="style2">
<text :style="{ color: value.textColor, fontSize: value.fontSize * 2 + 'rpx', fontWeight: value.fontWeight }">{{ value.text }}</text>
<text
:style="{ color: value.textColor, fontSize: value.fontSize * 2 + 'rpx', fontWeight: value.fontWeight }">{{
value.text }}</text>
<view class="xian" :style="{ background: value.textColor }">
<view class="line-triangle" :style="{ borderColor: value.textColor }"></view>
</view>
@@ -19,13 +22,19 @@
</block>
<block v-else-if="value.style == 'style-3'">
<view class="style3">
<text :style="{ color: value.textColor, fontSize: value.fontSize * 2 + 'rpx' ,fontWeight: value.fontWeight}">{{ value.text }}</text>
<view class="inner-line" :style="{ background: value.textColor }"><view class="line-short" :style="{ background: value.textColor }"></view></view>
<text
:style="{ color: value.textColor, fontSize: value.fontSize * 2 + 'rpx', fontWeight: value.fontWeight }">{{
value.text }}</text>
<view class="inner-line" :style="{ background: value.textColor }">
<view class="line-short" :style="{ background: value.textColor }"></view>
</view>
</view>
</block>
<block v-else-if="value.style == 'style-4'">
<view class="style4">
<text :style="{ color: value.textColor, fontSize: value.fontSize * 2 + 'rpx',fontWeight: value.fontWeight }">{{ value.text }}</text>
<text
:style="{ color: value.textColor, fontSize: value.fontSize * 2 + 'rpx', fontWeight: value.fontWeight }">{{
value.text }}</text>
<view class="line-box">
<view class="line-left" :style="{ background: value.textColor }"></view>
<view class="line-center" :style="{ borderColor: value.textColor }"></view>
@@ -36,7 +45,9 @@
<block v-else-if="value.style == 'style-5'">
<view class="style5">
<view class="style5-box" :style="{ color: value.textColor }">
<text class="style5-title" :style="{ color: value.textColor, fontSize: value.fontSize * 2 + 'rpx' ,fontWeight: value.fontWeight}">{{ value.text }}</text>
<text class="style5-title"
:style="{ color: value.textColor, fontSize: value.fontSize * 2 + 'rpx', fontWeight: value.fontWeight }">{{
value.text }}</text>
<view class="line-left" :style="{ background: value.textColor }"></view>
<view class="line-right" :style="{ background: value.textColor }"></view>
</view>
@@ -45,7 +56,9 @@
<block v-else-if="value.style == 'style-6'">
<view class="style6">
<view class="style6-box" :style="{ color: value.textColor }">
<text class="style6-title" :style="{ color: value.textColor, fontSize: value.fontSize * 2 + 'rpx',fontWeight: value.fontWeight }">{{ value.text }}</text>
<text class="style6-title"
:style="{ color: value.textColor, fontSize: value.fontSize * 2 + 'rpx', fontWeight: value.fontWeight }">{{
value.text }}</text>
<view class="style6-wrap" :style="{ color: value.textColor }"></view>
</view>
</view>
@@ -53,7 +66,9 @@
<block v-else-if="value.style == 'style-7'">
<view class="style7">
<view class="style7-box" :style="{ color: value.textColor }">
<text class="style7-title" :style="{ background: value.textColor, fontSize: value.fontSize * 2 + 'rpx',fontWeight: value.fontWeight }">{{ value.text }}</text>
<text class="style7-title"
:style="{ background: value.textColor, fontSize: value.fontSize * 2 + 'rpx', fontWeight: value.fontWeight }">{{
value.text }}</text>
<view class="style7-wrap" :style="{ color: value.textColor }"></view>
</view>
</view>
@@ -61,8 +76,11 @@
<block v-else-if="value.style == 'style-8'">
<view class="style8">
<view class="style8-box" :style="{ color: value.textColor }">
<text class="style8-title" :style="{ color: value.textColor, fontSize: value.fontSize * 2 + 'rpx',fontWeight: value.fontWeight }">{{ value.text }}</text>
<view class="style8-wrap" :style="{ background: value.textColor, height: value.fontSize * 2 + 'rpx' }"></view>
<text class="style8-title"
:style="{ color: value.textColor, fontSize: value.fontSize * 2 + 'rpx', fontWeight: value.fontWeight }">{{
value.text }}</text>
<view class="style8-wrap"
:style="{ background: value.textColor, height: value.fontSize * 2 + 'rpx' }"></view>
</view>
</view>
</block>
@@ -70,17 +88,24 @@
<view class="style9">
<view class="style9-box">
<view class="style9-center">
<view class="left-img"><image :src="$util.img('public/uniapp/diy/style9-1.png')"/></view>
<text class="style9-title" :style="{ fontSize: value.fontSize * 2 + 'rpx', color: value.textColor, fontWeight: value.fontWeight }">
<view class="left-img">
<image :src="$util.img('public/uniapp/diy/style9-1.png')" />
</view>
<text class="style9-title"
:style="{ fontSize: value.fontSize * 2 + 'rpx', color: value.textColor, fontWeight: value.fontWeight }">
{{ value.text }}
</text>
<view class="right-img"><image :src="$util.img('public/uniapp/diy/style9-2.png')"/></view>
<view class="style9-more" v-if="value.more.isShow" :style="{ color: value.more.color }" @click.stop="handlerClick(value.more.link)" @tap="handlerClick(value.more.link)">
<view class="right-img">
<image :src="$util.img('public/uniapp/diy/style9-2.png')" />
</view>
<view class="style9-more" v-if="value.more.isShow" :style="{ color: value.more.color }"
@tap.stop="handlerClick(value.more.link)">
{{ value.more.text }}
<view class="iconfont icon-right" :style="{ color: value.more.color }"></view>
</view>
</view>
<text class="sub-title" :style="{ color: value.subTitle.color }">{{ value.subTitle.text }}</text>
<text class="sub-title" :style="{ color: value.subTitle.color }">{{ value.subTitle.text
}}</text>
</view>
</view>
</block>
@@ -88,29 +113,31 @@
<view class="style10">
<view class="style10-box">
<view class="style10-center">
<view class="left-img"><image :src="$util.img('public/uniapp/diy/style10-1.png')"></image></view>
<text
class="style10-title"
v-if="$util.img('public/uniapp/diy/style10-3.png')"
:style="{
backgroundImage: 'url(' + $util.img('public/uniapp/diy/style10-3.png') + ')',
backgroundSize: 100 + '%',
backgroundPositionY: 93 + '%',
backgroundRepeat: 'no-repeat',
fontSize: value.fontSize * 2 + 'rpx',
color: value.textColor,
fontWeight: value.fontWeight
}"
>
<view class="left-img">
<image :src="$util.img('public/uniapp/diy/style10-1.png')"></image>
</view>
<text class="style10-title" v-if="$util.img('public/uniapp/diy/style10-3.png')" :style="{
backgroundImage: 'url(' + $util.img('public/uniapp/diy/style10-3.png') + ')',
backgroundSize: 100 + '%',
backgroundPositionY: 93 + '%',
backgroundRepeat: 'no-repeat',
fontSize: value.fontSize * 2 + 'rpx',
color: value.textColor,
fontWeight: value.fontWeight
}">
{{ value.text }}
</text>
<view class="right-img"><image :src="$util.img('public/uniapp/diy/style10-2.png')"></image></view>
<view class="style10-more" v-if="value.more.isShow" :style="{ color: value.more.color }" @click.stop="handlerClick(value.more.link)" @tap="handlerClick(value.more.link)">
<view class="right-img">
<image :src="$util.img('public/uniapp/diy/style10-2.png')"></image>
</view>
<view class="style10-more" v-if="value.more.isShow" :style="{ color: value.more.color }"
@tap.stop="handlerClick(value.more.link)">
{{ value.more.text }}
<view class="iconfont icon-right" :style="{ color: value.more.color }"></view>
</view>
</view>
<text class="sub-title" :style="{ color: value.subTitle.color }">{{ value.subTitle.text }}</text>
<text class="sub-title" :style="{ color: value.subTitle.color }">{{ value.subTitle.text
}}</text>
</view>
</view>
</block>
@@ -120,56 +147,55 @@
<view class="style11-conter">
<view class="style11-conter-box">
<view class="left">
<view class="style11-title"
:style="{
fontSize: value.fontSize * 2 + 'rpx',
color: value.textColor,
fontWeight: value.fontWeight
}"
>
<view class="style11-title" :style="{
fontSize: value.fontSize * 2 + 'rpx',
color: value.textColor,
fontWeight: value.fontWeight
}">
{{ value.text }}
</view>
<view class="style11-sub" :style="{ color: value.subTitle.color }">{{ value.subTitle.text }}</view>
<view class="style11-sub" :style="{ color: value.subTitle.color }">{{
value.subTitle.text }}</view>
</view>
<view class="style11-more" v-if="value.more.isShow" :style="{ color: value.more.color }" @click.stop="$util.diyRedirectTo(value.more.link)">
<view class="style11-more" v-if="value.more.isShow" :style="{ color: value.more.color }"
@tap.stop="$util.diyRedirectTo(value.more.link)">
{{ value.more.text }}
<view class="iconfont icon-right" :style="{ color: value.more.color }"></view>
</view>
<image class="center-img" :src="$util.img('public/uniapp/diy/style11-1.png')" mode="widthFix"></image>
<image class="center-img" :src="$util.img('public/uniapp/diy/style11-1.png')"
mode="widthFix"></image>
<image class="right-img" :src="$util.img('public/uniapp/diy/style11-2.png')" mode="widthFix"></image>
<image class="right-img" :src="$util.img('public/uniapp/diy/style11-2.png')"
mode="widthFix"></image>
</view>
</view>
</view>
</view>
</block>
<view class="style12" v-if="value.style == 'style-12'">
<view class="style12-title"
:style="{
fontSize: value.fontSize * 2 + 'rpx',
color: value.textColor,
fontWeight: value.fontWeight
}"
>
<view class="style12-title" :style="{
fontSize: value.fontSize * 2 + 'rpx',
color: value.textColor,
fontWeight: value.fontWeight
}">
{{ value.text }}
</view>
<text class="style12-sub-title" :style="{ color: value.subTitle.color }">{{ value.subTitle.text }}</text>
<view class="style12-more" v-if="value.more.isShow" :style="{ color: value.more.color }" @click.stop="$util.diyRedirectTo(value.more.link)">
<text class="style12-sub-title" :style="{ color: value.subTitle.color }">{{ value.subTitle.text
}}</text>
<view class="style12-more" v-if="value.more.isShow" :style="{ color: value.more.color }"
@tap.stop="$util.diyRedirectTo(value.more.link)">
<text>{{ value.more.text }}</text>
<view class="iconfont icon-right" :style="{ color: value.more.color }"></view>
</view>
</view>
<view class="style13" v-else-if="value.style == 'style-13'">
<image class="left-img" :src="$util.img('public/uniapp/diy/style13-1.png')" mode="widthFix"></image>
<view
class="style13-title"
:style="{
fontSize: value.fontSize * 2 + 'rpx',
color: value.textColor,
fontWeight: value.fontWeight
}"
>
<view class="style13-title" :style="{
fontSize: value.fontSize * 2 + 'rpx',
color: value.textColor,
fontWeight: value.fontWeight
}">
{{ value.text }}
</view>
<image class="right-img" :src="$util.img('public/uniapp/diy/style13-1.png')" mode="widthFix"></image>
@@ -177,19 +203,21 @@
<view class="style14" v-else-if="value.style == 'style-14'">
<view class="title-wrap">
<view class="text">
<text :style="{ fontSize: value.fontSize * 2 + 'rpx', color: value.textColor, fontWeight: value.fontWeight }">
<text
:style="{ fontSize: value.fontSize * 2 + 'rpx', color: value.textColor, fontWeight: value.fontWeight }">
{{ value.text }}
</text>
<text class="zone" :style="{ fontSize: value.fontSize * 2 + 'rpx', fontWeight: value.fontWeight }">专区</text>
<text class="zone"
:style="{ fontSize: value.fontSize * 2 + 'rpx', fontWeight: value.fontWeight }">专区</text>
</view>
<text class="iconfont icon-danxuan-xuanzhong" :style="{ color: value.textColor, fontWeight: value.fontWeight }"></text>
<text class="iconfont icon-danxuan-xuanzhong" :style="{ color: value.textColor, fontWeight: value.fontWeight }"></text>
<text class="iconfont icon-danxuan-xuanzhong" :style="{ color: value.textColor, fontWeight: value.fontWeight }"></text>
<view
class="sub-title"
v-show="value.subTitle.text"
:style="{ fontSize: value.subTitle.fontSize * 2 + 'rpx', color: value.subTitle.color }"
>
<text class="iconfont icon-danxuan-xuanzhong"
:style="{ color: value.textColor, fontWeight: value.fontWeight }"></text>
<text class="iconfont icon-danxuan-xuanzhong"
:style="{ color: value.textColor, fontWeight: value.fontWeight }"></text>
<text class="iconfont icon-danxuan-xuanzhong"
:style="{ color: value.textColor, fontWeight: value.fontWeight }"></text>
<view class="sub-title" v-show="value.subTitle.text"
:style="{ fontSize: value.subTitle.fontSize * 2 + 'rpx', color: value.subTitle.color }">
{{ value.subTitle.text }}
</view>
</view>
@@ -206,48 +234,50 @@
<text class="line" :style="{ color: value.textColor, fontWeight: value.fontWeight }"></text>
<text class="line" :style="{ color: value.textColor, fontWeight: value.fontWeight }"></text>
<text class="my">
<text class="yuan" :style="{ backgroundColor: value.textColor, fontWeight: value.fontWeight }"></text>
<text class="vertical" :style="{ color: value.textColor, fontWeight: value.fontWeight }"></text>
<text class="yuan"
:style="{ backgroundColor: value.textColor, fontWeight: value.fontWeight }"></text>
<text class="vertical"
:style="{ color: value.textColor, fontWeight: value.fontWeight }"></text>
</text>
</view>
<text :style="{ fontSize: value.fontSize * 2 + 'rpx', color: value.textColor, fontWeight: value.fontWeight }">
<text
:style="{ fontSize: value.fontSize * 2 + 'rpx', color: value.textColor, fontWeight: value.fontWeight }">
{{ value.text }}
</text>
<view class="ornament" style="margin-left: 40rpx;">
<text class="line" :style="{ color: value.textColor, fontWeight: value.fontWeight }"></text>
<text class="line" :style="{ color: value.textColor, fontWeight: value.fontWeight }"></text>
<text class="my">
<text class="yuan" :style="{ backgroundColor: value.textColor, fontWeight: value.fontWeight }"></text>
<text class="vertical" :style="{ color: value.textColor, fontWeight: value.fontWeight }"></text>
<text class="yuan"
:style="{ backgroundColor: value.textColor, fontWeight: value.fontWeight }"></text>
<text class="vertical"
:style="{ color: value.textColor, fontWeight: value.fontWeight }"></text>
</text>
</view>
</view>
<view
class="sub-title"
v-show="value.subTitle.text"
:style="{ fontSize: value.subTitle.fontSize * 2 + 'rpx', color: value.subTitle.color }"
>
<view class="sub-title" v-show="value.subTitle.text"
:style="{ fontSize: value.subTitle.fontSize * 2 + 'rpx', color: value.subTitle.color }">
{{ value.subTitle.text }}
</view>
</view>
<!-- 图十六 -->
<view class="style16" v-if="value.style == 'style-16'">
<view
class="style16-title"
:style="{
fontSize: value.fontSize * 2 + 'rpx',
color: value.textColor,
fontWeight: value.fontWeight
}"
>
<view class="style16-title" :style="{
fontSize: value.fontSize * 2 + 'rpx',
color: value.textColor,
fontWeight: value.fontWeight
}">
{{ value.text }}
</view>
<view class="style16-sub-title" v-show="value.subTitle.text" :style="{ color: value.subTitle.color, backgroundColor: value.subTitle.bgColor }">
<text :class="['js-icon', value.subTitle.icon]" :style="{ backgroundColor: value.subTitle.bgColor }"></text>
<view class="style16-sub-title" v-show="value.subTitle.text"
:style="{ color: value.subTitle.color, backgroundColor: value.subTitle.bgColor }">
<text :class="['js-icon', value.subTitle.icon]"
:style="{ backgroundColor: value.subTitle.bgColor }"></text>
<text :style="{ fontWeight: value.subTitle.fontWeight }">{{ value.subTitle.text }}</text>
</view>
<view class="style16-more" v-if="value.more.isShow" :style="{ color: value.more.color }" @click.stop="$util.diyRedirectTo(value.more.link)">
<view class="style16-more" v-if="value.more.isShow" :style="{ color: value.more.color }"
@tap.stop="$util.diyRedirectTo(value.more.link)">
<text>{{ value.more.text }}</text>
<view class="iconfont icon-right" :style="{ color: value.more.color }"></view>
</view>
@@ -269,11 +299,11 @@ export default {
data() {
return {};
},
created() {},
created() { },
mixins: [DiyMinx],
watch: {
//
componentRefresh: function(nval) {}
componentRefresh: function (nval) { }
},
computed: {
warpCss() {
@@ -290,10 +320,12 @@ export default {
},
methods: {
async handlerClick(link) {
await this.__$emitEvent({eventName: 'text-tap', data: link, promiseCallback: (event, handler, awaitedResult) => {
if (!awaitedResult) return;
$util.diyRedirectTo(link);
}})
await this.__$emitEvent({
eventName: 'text-tap', data: link, promiseCallback: (event, handler, awaitedResult) => {
if (!awaitedResult) return;
this.$util.diyRedirectTo(link);
}
})
}
}
};
@@ -323,7 +355,7 @@ export default {
width: 100%;
}
.diy-text .title > text {
.diy-text .title>text {
padding: 0 15rpx;
}
@@ -541,9 +573,11 @@ export default {
.style8-box {
text-align: left !important;
.style8-title{
.style8-title {
margin-left: 20rpx;
}
.style8-wrap {
height: 100%;
position: absolute;
@@ -821,7 +855,8 @@ export default {
font-size: 24rpx;
margin-right: 8rpx;
}
.iconfont{
.iconfont {
font-size: 24rpx;
}
}
@@ -849,6 +884,7 @@ export default {
transform: rotateY(180deg);
}
}
.style14 {
display: flex;
justify-content: space-between;
@@ -856,28 +892,36 @@ export default {
align-items: center;
flex: 1;
padding: 0 20rpx;
text {
padding: 0 !important;
}
.title-wrap {
.text {
display: inline-block;
}
.iconfont {
font-size: 24rpx;
&:nth-of-type(1) {
margin-left: 20rpx;
}
&:nth-of-type(2) {
opacity: 0.6;
}
&:nth-of-type(3) {
opacity: 0.4;
}
}
.sub-title {
opacity: 0.6;
}
.more {
text:first-child {
font-size: 28rpx;
@@ -885,20 +929,25 @@ export default {
}
}
}
.style15 {
flex: 1;
text {
padding: 0 !important;
line-height: 1;
}
.title-wrap {
display: flex;
justify-content: center;
align-items: center;
}
.ornament {
display: flex;
align-items: flex-end;
.line {
border-left: 8rpx solid;
transform: rotate(40deg);
@@ -906,6 +955,7 @@ export default {
height: 28rpx;
display: block;
margin-right: 12rpx;
&:nth-of-type(2) {
height: 40rpx;
margin-right: 10rpx;
@@ -913,11 +963,13 @@ export default {
top: 4rpx;
}
}
.my {
transform: rotate(40deg);
line-height: 0;
position: relative;
top: 4rpx;
.yuan {
font-size: 100rpx;
border-radius: 50%;
@@ -925,6 +977,7 @@ export default {
height: 8rpx;
display: block;
}
.vertical {
border-left: 8rpx solid;
height: 20rpx;
@@ -934,27 +987,32 @@ export default {
}
}
}
.sub-title {
opacity: 0.6;
text-align: center;
}
}
.style16 {
display: flex;
align-items: center;
width: 100%;
padding: 20rpx;
.style16-more {
display: flex;
align-items: center;
margin-left: auto;
& > text {
&>text {
max-width: 200rpx;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
}
.style16-title {
text-align: left;
max-width: 200rpx;
@@ -962,6 +1020,7 @@ export default {
text-overflow: ellipsis;
overflow: hidden;
}
.style16-sub-title {
margin-left: 20rpx;
text-align: left;
@@ -976,7 +1035,8 @@ export default {
align-items: center;
padding: 0 20rpx;
position: relative;
&> .js-icon {
&>.js-icon {
padding: 8rpx;
background-image: radial-gradient(transparent 30%, #fff);
border-radius: 50%;

View File

@@ -0,0 +1,671 @@
<template>
<view :style="componentStyle">
<!-- 固定布局模式 -->
<view v-if="value.showStyle == 'fixed'" :class="['goods-list', 'row1-of' + value.rowCount]"
style="padding:20rpx;">
<view v-for="(item, index) in value.list" :key="index" class="goods-item" @tap="showVideo(item)">
<view class="goods-img-wrap">
<image class="goods-img" style="border-radius:10rpx 10rpx 0 0;" :src="$util.img(item.imageUrl)"
mode="widthFix" @error="imgError(index)"></image>
<view style="position:absolute;top:10rpx;right:10rpx;">
<image style="width:30rpx;" :src="$util.img('addon/personnel/shop/view/enterprise/play.png')"
mode="widthFix"></image>
</view>
</view>
<view class="info-wrap">
<view class="goods-name" :style="{
'font-size': (value.font.size * 2 + 'rpx') + ';',
'font-weight': value.font.weight + ';',
'color': value.font.color + ';'
}">{{ item.title }}</view>
</view>
</view>
</view>
<!-- 其他布局模式 -->
<scroll-view v-else :class="['video-nav', value.showStyle == 'fixed' ? 'fixed-layout' : value.showStyle]"
:scroll-x="value.showStyle == 'singleSlide'">
<view class="uni-scroll-view-content">
<view v-for="(item, index) in value.list" :key="index" :class="['video-nav-item', value.mode]"
:style="{ width: (100 / value.rowCount + '%') + ';' }" @tap="showVideo(item)">
<view class="video-img">
<image v-if="item.iconType == 'img'" :style="{
'max-width': '200rpx;',
'max-height': '200rpx;',
'border-radius': '8rpx;'
}" :src="$util.img(item.imageUrl) || $util.img('public/uniapp/default_img/goods.png')" mode="widthFix"
:show-menu-by-longpress="true"></image>
<view style="position:absolute;top:10rpx;right:10rpx;">
<image style="width:30rpx;"
:src="$util.img('addon/personnel/shop/view/enterprise/play.png')" mode="widthFix">
</image>
</view>
</view>
<view class="video-text" :style="{
'margin-left': '16rpx;',
'font-size': (value.font.size * 2 + 'rpx') + ';',
'font-weight': value.font.weight + ';',
'color': value.font.color + ';'
}">{{ item.title }}</view>
</view>
</view>
</scroll-view>
<!-- 视频播放弹窗 -->
<uni-popup ref="videoPopup" type="center" style="background:transparent;width:100%;height:100%;">
<view class="video-container" style="position:fixed;top:30%;width:100%;left:0;">
<video class="adaptive-video" :autoPauseIfNavigate="true" :autoPauseIfOpenNative="true"
:autoplay="false" :enableAutoRotation="true" id="myVideo" :src="video_url" :controls="true"></video>
</view>
</uni-popup>
</view>
</template>
<script>
import DiyMinx from './minx.js'
export default {
name: 'diy-video-list',
props: {
value: {
type: Object,
default: () => ({})
}
},
mixins: [DiyMinx],
data() {
return {
pageWidth: '',
indicatorDots: false,
swiperCurrent: 0,
video_url: ''
}
},
created() {
// 组件创建时的逻辑
},
watch: {
componentRefresh(newValue) {
// 监听组件刷新
}
},
computed: {
componentStyle() {
let style = '';
style += 'background-color:' + this.value.componentBgColor + ';';
if (this.value.componentAngle == 'round') {
style += 'border-top-left-radius:' + (2 * this.value.topAroundRadius) + 'rpx;';
style += 'border-top-right-radius:' + (2 * this.value.topAroundRadius) + 'rpx;';
style += 'border-bottom-left-radius:' + (2 * this.value.bottomAroundRadius) + 'rpx;';
style += 'border-bottom-right-radius:' + (2 * this.value.bottomAroundRadius) + 'rpx;';
}
style += 'box-shadow:' + (this.value.ornament.type == 'shadow' ? '0 0 10rpx ' + this.value.ornament.color : '') + ';';
style += 'border:' + (this.value.ornament.type == 'stroke' ? '2rpx solid ' + this.value.ornament.color : '') + ';';
console.log(this.value);
return style;
},
swiperHeight() {
let height = 0;
if (this.value.mode == 'graphic') {
height = (49 + this.value.imageSize) * this.value.pageCount;
} else if (this.value.mode == 'img') {
height = (22 + this.value.imageSize) * this.value.pageCount;
} else if (this.value.mode == 'text') {
height = 43 * this.value.pageCount;
}
return 'height:' + (2 * height) + 'rpx';
},
isIndicatorDots() {
return this.value.carousel.type != 'hide' &&
1 != Math.ceil(this.value.list.length / (this.value.pageCount * this.value.rowCount));
}
},
methods: {
// 显示视频播放弹窗
showVideo(item) {
this.video_url = item.videoUrl;
this.$refs.videoPopup.open();
},
// 图片加载错误处理
imgError(index) {
// 图片加载失败的处理逻辑
console.log('图片加载失败:', index);
}
}
}
</script>
<style lang="scss" scoped>
.goods-name {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.goods-list {
&.row1-of3 {
display: flex;
flex-wrap: wrap;
.goods-item {
position: relative;
display: flex;
flex-direction: column;
margin-top: 20rpx;
width: calc(33.3333333% - 14rpx);
box-sizing: border-box;
&:nth-child(3n + 3) {
width: calc(33.33% - 15rpx);
}
&:nth-of-type(1),
&:nth-of-type(2),
&:nth-of-type(3) {
margin-top: 0;
}
&:nth-child(3n) {
width: calc(33.3333333% - 15rpx);
}
&:nth-child(3n-1) {
margin-left: 20rpx;
margin-right: 20rpx;
}
&.shadow {
width: calc(33.3333333% - 18rpx);
&:nth-of-type(1),
&:nth-of-type(2),
&:nth-of-type(3) {
margin-top: 8rpx;
}
&:nth-child(1n) {
margin-left: 8rpx;
}
&:nth-child(3n-1) {
margin-left: 20rpx;
margin-right: 20rpx;
}
&:nth-child(3n) {
margin-right: 0;
margin-left: 0;
}
}
.goods-img-wrap {
position: relative;
overflow: hidden;
height: 220rpx;
}
.goods-img {
width: 100%;
}
.info-wrap {
display: flex;
flex-direction: column;
flex: 1;
.pro-info {
margin-top: auto;
display: flex;
flex-direction: column;
justify-content: space-between;
.discount-price {
display: flex;
justify-content: space-between;
align-items: center;
.price-wrap {
white-space: nowrap;
.unit {
font-size: 24rpx !important;
}
.price {
font-size: 32rpx;
text {
font-weight: 700;
}
}
}
}
.delete-price {
text-decoration: line-through;
flex: 1;
line-height: 28rpx;
color: #909399;
font-size: 20rpx;
}
}
}
}
&.style-1 {
.pro-info {
.price-wrap {
line-height: 1;
}
.discount-price {
justify-content: unset !important;
align-items: baseline !important;
flex-wrap: wrap;
}
.delete-price {
margin-left: 10rpx;
}
}
}
&.style-2 {
.pro-info {
position: relative;
flex-direction: row !important;
align-items: center;
.price-wrap {
line-height: 1;
}
.discount-price {
align-items: flex-end !important;
flex-wrap: wrap;
justify-content: unset !important;
}
.delete-price {
margin: 20rpx 0;
flex-basis: 100% !important;
}
.buy-btn {
min-width: 112rpx;
height: 52rpx;
padding: 0 20rpx;
line-height: 52rpx;
text-align: center;
box-sizing: border-box;
}
}
}
.sell-out {
text {
font-size: 150rpx;
}
}
}
&.row1-of2 {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
.goods-item {
position: relative;
margin-top: 20rpx;
width: calc(50% - 10rpx);
display: flex;
flex-direction: column;
box-sizing: border-box;
&:nth-child(2n) {
margin-right: 0 !important;
}
&:nth-of-type(1),
&:nth-of-type(2) {
margin-top: 0;
}
&.shadow {
width: calc(50% - 18rpx);
&:nth-child(2n-1) {
margin-left: 8rpx;
}
&:nth-child(2n) {
margin-right: 8rpx !important;
}
&:nth-of-type(1),
&:nth-of-type(2) {
margin-top: 8rpx;
}
}
.goods-img-wrap {
position: relative;
overflow: hidden;
height: 340rpx;
}
.goods-img {
width: 100%;
}
.info-wrap {
display: flex;
flex-direction: column;
flex: 1;
.sale {
flex-basis: 100%;
}
.pro-info {
margin-top: auto;
display: flex;
flex-direction: row;
justify-content: space-between;
.discount-price {
.price-wrap {
white-space: nowrap;
.unit {
font-weight: 700;
font-size: 24rpx !important;
}
.price {
font-weight: 700;
font-size: 32rpx !important;
}
}
}
.delete-price {
text-decoration: line-through;
flex: 1;
line-height: 28rpx;
color: #909399;
font-size: 20rpx;
}
}
}
}
&.style-1 {
.pro-info {
.discount-price {
display: flex;
flex-wrap: wrap;
align-items: baseline;
.price-wrap {
display: inline-block;
text {
font-weight: 700;
}
line-height: 1;
}
}
.delete-price {
margin-top: 6rpx;
flex-basis: 100% !important;
}
}
}
&.style-2 {
.pro-info {
position: relative;
align-items: center;
.price-wrap {
line-height: 1;
}
.discount-price {
display: flex;
flex-wrap: wrap;
align-items: baseline;
}
.delete-price {
margin-top: 4rpx;
flex-basis: 100% !important;
}
.sale {
line-height: 1;
margin-top: 10rpx;
}
.buy-btn {
min-width: 140rpx;
height: 52rpx;
padding: 0 20rpx;
line-height: 52rpx;
text-align: center;
box-sizing: border-box;
}
}
}
&.style-3 {
.pro-info {
.member-price {
margin-right: auto;
align-self: flex-end;
margin-bottom: 4rpx;
}
.sale {
line-height: 1;
align-self: center;
margin-top: 8rpx;
}
.discount-price {
display: flex;
flex-wrap: wrap;
flex: 1;
align-content: center;
.price-wrap {
display: flex;
align-items: baseline;
line-height: 1;
align-self: center;
}
}
}
.swiper {
padding: 20rpx 0;
}
}
.sell-out {
text {
font-size: 250rpx;
}
}
}
}
.video-container {
width: 100%;
height: 300px;
position: relative;
}
.adaptive-video {
width: 100%;
height: 100%;
object-fit: contain;
position: absolute;
top: 0;
left: 0;
}
.video-nav {
padding: 16rpx;
box-sizing: border-box;
&.fixed-layout {
.uni-scroll-view-content {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
}
&.singleSlide {
.uni-scroll-view-content {
display: flex;
}
.video-nav-item {
flex-shrink: 0;
}
}
&.pageSlide {
position: relative;
.uni-swiper-dots-horizontal {
bottom: 0rpx;
}
&.straightLine {
.uni-swiper-dot {
width: 30rpx;
border-radius: 0;
height: 8rpx;
}
}
&.circle {
.uni-swiper-dot {
width: 14rpx;
height: 14rpx;
}
}
}
.video-nav-wrap {
display: flex;
flex-wrap: wrap;
width: 100%;
height: 100%;
}
.video-nav-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 14rpx 0;
box-sizing: border-box;
.video-text {
padding-top: 12rpx;
line-height: 1.5;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
width: 100%;
text-align: center;
&.alone {
padding-top: 0;
}
}
&.text {
.video-text {
padding-top: 0;
}
}
.video-img {
position: relative;
display: flex;
align-items: center;
justify-content: center;
font-size: 90rpx;
.tag {
position: absolute;
top: -10rpx;
right: -24rpx;
color: #fff;
border-radius: 24rpx;
border-bottom-left-radius: 0;
-webkit-transform: scale(0.8);
transform: scale(0.8);
padding: 8rpx 16rpx;
line-height: 1;
font-size: 24rpx;
}
.icon {
font-size: 50rpx;
color: #606266;
}
}
}
}
.swiper-dot-box {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
margin-top: -20rpx;
padding-bottom: 8rpx;
.swiper-dot {
background-color: rgba(0, 0, 0, 0.3);
margin: 8rpx;
&.active {
background-color: #000;
}
}
&.straightLine {
.swiper-dot {
width: 30rpx;
border-radius: 0;
height: 8rpx;
}
}
&.circle {
.swiper-dot {
width: 15rpx;
border-radius: 50%;
height: 15rpx;
}
}
}
</style>

View File

@@ -0,0 +1,58 @@
<template>
<video data-component-name="diy-video" class="diy-video" :src="$util.img(value.videoUrl)"
:poster="$util.img(value.imageUrl)" :style="videoWarpCss" objectFit="cover"
@tap="handlerClick(value.videoUrl)"></video>
</template>
<script>
// 视频
import DiyMinx from './minx.js'
export default {
name: 'diy-video',
props: {
value: {
type: Object
}
},
data() {
return {};
},
created() { },
mixins: [DiyMinx],
watch: {
// 组件刷新监听
componentRefresh: function (nval) { }
},
computed: {
videoWarpCss: function () {
var obj = '';
if (this.value.componentAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
}
return obj;
}
},
methods: {
async handlerClick(videoUrl) {
await this.__$emitEvent({
eventName: 'video-tap', data: videoUrl, promiseCallback: (event, handler, awaitedResult) => {
if (!awaitedResult) return;
}
})
}
}
};
</script>
<style scoped>
video {
width: 100%;
}
.diy-video>>>.uni-video-container {
background-color: transparent;
}
</style>

View File

@@ -0,0 +1,188 @@
// 微信视频号组件配置
export const wechatChannelConfig = {
// 图标相关
icon: {
defaultSize: 80, // 默认图标尺寸rpx
smallSize: 60, // 小图标尺寸rpx
channelArrowSize: 24, // 频道箭头图标尺寸rpx
channelArrow: 'addon/personnel/shop/view/enterprise/arrow.png', // 频道箭头图标路径
},
// 视频相关配置
video: {
defaultHeight: 320, // 默认视频高度rpx
minHeight: 200, // 最小视频高度rpx
maxHeight: 500, // 最大视频高度rpx
defaultCoverUrl: 'addon/personnel/shop/view/enterprise/default-video-cover.png', // 默认视频封面
},
// 播放按钮配置
playButton: {
size: 80, // 标准尺寸rpx
smallSize: 60, // 小尺寸rpx
iconSize: 40, // 标准图标尺寸rpx
smallIconSize: 30, // 小图标尺寸rpx
background: 'rgba(0, 0, 0, 0.4)', // 背景颜色
},
// 布局配置
layout: {
borderRadius: 12, // 圆角rpx
padding: 16, // 内边距rpx
margin: 10, // 外边距rpx
},
// 字体配置
font: {
channelNameSize: 28, // 频道名称字体大小rpx
videoTitleSize: 28, // 视频标题字体大小rpx
statsSize: 24, // 统计信息字体大小rpx
},
// 颜色配置
color: {
channelName: '#333', // 频道名称颜色
videoTitle: '#333', // 视频标题颜色
stats: '#999', // 统计信息颜色
border: '#f0f0f0', // 边框颜色
},
// 微信相关配置
wechat: {
minSdkVersion: '2.19.2', // 最小微信基础库版本
embedComponent: 'channel-video', // 嵌入式视频组件名称
},
// 错误提示配置
error: {
notWechat: '当前环境不是微信小程序',
lowVersion: '当前微信基础库版本过低,需要 2.19.2 或以上版本',
notSupported: '当前环境不支持微信视频号',
missingFields: '缺少必要字段: {field}',
},
};
// 获取配置项的辅助函数
export const getwechatChannelConfig = (key, defaultValue = null) => {
const keys = key.split('.');
let config = wechatChannelConfig;
for (const k of keys) {
if (config[k] === undefined) {
return defaultValue;
}
config = config[k];
}
return config;
};
// 微信视频号工具函数
export const wechatChannelUtil = {
// 版本比较
versionCompare(v1, v2) {
const arr1 = v1.split('.');
const arr2 = v2.split('.');
for (let i = 0; i < Math.max(arr1.length, arr2.length); i++) {
const num1 = parseInt(arr1[i] || 0);
const num2 = parseInt(arr2[i] || 0);
if (num1 > num2) return 1;
if (num1 < num2) return -1;
}
return 0;
},
// 检查是否支持嵌入式播放
isEmbedModeSupported() {
return typeof wx !== 'undefined' && wx.canIUse(wechatChannelConfig.wechat.embedComponent);
},
// 获取默认视频封面
getDefaultCoverUrl() {
return wechatChannelConfig.video.defaultCoverUrl;
},
// 数据校验
validateVideoData(item) {
const requiredFields = ['feedId', 'finderUserName'];
for (const field of requiredFields) {
if (!item[field]) {
throw new Error(`缺少必要字段: ${field}`);
}
}
return true;
},
// 播放视频
playVideo(item) {
return new Promise((resolve, reject) => {
try {
// 数据校验
this.validateVideoData(item);
} catch (err) {
this.handleError(err, item);
reject(err);
return;
}
// 检查微信环境
if (typeof wx === 'undefined') {
const err = new Error(wechatChannelConfig.error.notWechat);
this.handleError(err, item);
reject(err);
return;
}
// 检查基础库版本
const systemInfo = wx.getSystemInfoSync();
const SDKVersion = systemInfo.SDKVersion;
if (this.versionCompare(SDKVersion, wechatChannelConfig.wechat.minSdkVersion) < 0) {
const err = new Error(wechatChannelConfig.error.lowVersion);
this.handleError(err, item);
reject(err);
return;
}
// 调用微信视频号播放API
if (wx.openChannelsActivity) {
wx.openChannelsActivity({
feedId: item.feedId,
finderUserName: item.finderUserName,
success: (res) => {
console.log('打开视频号成功', res);
resolve(res);
},
fail: (err) => {
this.handleError(err, item);
reject(err);
}
});
} else {
const err = new Error(wechatChannelConfig.error.notSupported);
this.handleError(err, item);
reject(err);
}
});
},
// 统一错误处理
handleError(err, item) {
console.error('微信视频号错误:', err);
const { message = '', errMsg = '微信视频播放失败!', errCode = -1 } = err;
// 错误码5: 表示用户点击了取消. openChannelsActivity:fail cancel
if ([5].includes(errCode)) {
return;
}
const showErrorToast = (otherMsgs = []) => {
uni.showToast({
title: [errMsg, message,...otherMsgs].join('\n'),
icon: 'none',
duration: 2000
});
}
showErrorToast();
}
};

22
components-diy/minx.js Normal file
View File

@@ -0,0 +1,22 @@
export default {
methods: {
// 异步触发事件
async __$emitEvent(payload = {eventName: '__unnamedEvent', data: {}, promiseCallback: null}) {
// console.log('__$emitEvent', payload)
await this.$eventBus.emit(payload.eventName, payload.data, payload.promiseCallback)
},
// 预览图片
previewImg(img) {
// #ifdef MP-WEIXIN
uni.previewImage({
current: 0,
urls: [this.$util.img(img)],
success: function (res) { },
fail: function (res) { },
complete: function (res) { },
})
// #endif
},
}
}

View File

@@ -1,271 +0,0 @@
# AI智能客服组件
一个功能完整的AI智能客服对话组件支持多种消息类型和交互功能。
## 功能特性
- ✅ 支持对话上下文管理
- ✅ 支持多种消息类型文本、Markdown、文件、音频、视频、链接、商品卡片
- ✅ 支持语音输入和录音
- ✅ 支持图片、文件、位置等附件发送
- ✅ 支持消息操作按钮(点赞、踩等)
- ✅ 支持历史消息加载
- ✅ 响应式设计,适配多端
## 安装使用
### 1. 引入组件
`pages.json` 中注册组件:
```json
{
"usingComponents": {
"ai-chat-message": "/components/ai-chat-message/ai-chat-message"
}
}
```
### 2. 在页面中使用
```vue
<template>
<view class="container">
<ai-chat-message
ref="chat"
:initial-messages="messages"
@message-sent="onMessageSent"
@ai-response="onAIResponse"
@action-click="onActionClick" />
</view>
</template>
<script>
export default {
data() {
return {
messages: [
{
id: 1,
role: 'ai',
type: 'text',
content: '您好我是AI智能客服有什么可以帮助您的吗',
timestamp: Date.now()
}
]
}
},
methods: {
onMessageSent(message) {
console.log('用户发送消息:', message)
},
onAIResponse(message) {
console.log('AI回复消息:', message)
},
onActionClick({ action, message }) {
console.log('操作点击:', action, message)
}
}
}
</script>
```
## Props 配置
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| initialMessages | Array | [] | 初始消息列表 |
| userAvatar | String | '/static/images/default-avatar.png' | 用户头像 |
| aiAvatar | String | '/static/images/ai-avatar.png' | AI头像 |
| showLoadMore | Boolean | true | 是否显示加载更多 |
| maxMessages | Number | 100 | 最大消息数量 |
## Events 事件
| 事件名 | 参数 | 说明 |
|--------|------|------|
| message-sent | message | 用户发送消息 |
| ai-response | message | AI回复消息 |
| action-click | {action, message} | 操作按钮点击 |
| history-loaded | messages | 历史消息加载完成 |
| file-preview | message | 文件预览 |
| audio-play | message | 音频播放 |
| audio-pause | message | 音频暂停 |
| video-play | message | 视频播放 |
| video-pause | message | 视频暂停 |
| link-open | message | 链接打开 |
| product-view | message | 商品查看 |
| input-change | value | 输入内容变化 |
## 消息类型格式
### 文本消息
```javascript
{
id: 1,
role: 'user', // 或 'ai'
type: 'text',
content: '消息内容',
timestamp: Date.now()
}
```
### Markdown消息
```javascript
{
id: 2,
role: 'ai',
type: 'markdown',
content: '# 标题\n**粗体** *斜体* `代码`',
timestamp: Date.now()
}
```
### 文件消息
```javascript
{
id: 3,
role: 'ai',
type: 'file',
fileName: '文档.pdf',
fileSize: 1024000,
url: '文件地址',
timestamp: Date.now()
}
```
### 音频消息
```javascript
{
id: 4,
role: 'ai',
type: 'audio',
title: '语音消息',
duration: 60, // 秒
url: '音频地址',
timestamp: Date.now()
}
```
### 视频消息
```javascript
{
id: 5,
role: 'ai',
type: 'video',
title: '产品介绍',
duration: 120, // 秒
url: '视频地址',
cover: '封面图',
timestamp: Date.now()
}
```
### 链接消息
```javascript
{
id: 6,
role: 'ai',
type: 'link',
title: '帮助文档',
description: '详细的使用说明',
url: 'https://example.com',
image: '缩略图',
timestamp: Date.now()
}
```
### 商品卡片
```javascript
{
id: 7,
role: 'ai',
type: 'product',
title: '商品名称',
price: 299,
description: '商品描述',
image: '商品图片',
timestamp: Date.now()
}
```
## 方法
通过 ref 调用组件方法:
```javascript
// 添加消息
this.$refs.chat.addMessage(message)
// 清空消息
this.$refs.chat.clearMessages()
// 滚动到底部
this.$refs.chat.scrollToBottom()
```
## 样式定制
组件使用 SCSS 编写,可以通过 CSS 变量进行主题定制:
```css
.ai-chat-container {
--primary-color: #ff4544;
--bg-color: #f8f8f8;
--text-color: #333;
}
```
## 方法
通过 ref 调用组件方法:
```javascript
// 添加消息
this.$refs.chat.addMessage(message)
// 清空消息
this.$refs.chat.clearMessages()
// 滚动到底部
this.$refs.chat.scrollToBottom()
// 开始语音输入
this.$refs.chat.startVoiceInput()
// 停止语音输入
this.$refs.chat.stopVoiceInput()
```
## 样式定制
组件使用 SCSS 编写,可以通过 CSS 变量进行主题定制:
```css
.ai-chat-container {
--primary-color: #ff4544;
--bg-color: #f8f8f8;
--text-color: #333;
--border-color: #eeeeee;
--user-bg: #e6f7ff;
--ai-bg: #f6f6f6;
}
```
## 图标字体
组件使用自定义图标字体,需要在页面中引入:
```html
<style>
@import url('/components/ai-chat-message/iconfont.css');
</style>
```
## 注意事项
1. 组件已适配项目中已有的 `ns-loading` 组件
2. 需要配置对应的图标字体文件
3. 音频播放功能在 H5 和 APP 端支持较好
4. 文件预览功能依赖平台能力
5. 语音输入功能需要用户授权麦克风权限

View File

@@ -1,6 +0,0 @@
{
"component": true,
"usingComponents": {
"ns-loading": "../ns-loading/ns-loading"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,288 +0,0 @@
<template>
<view class="demo-container">
<view class="demo-header">
<text class="demo-title">AI智能客服组件演示</text>
</view>
<ai-chat-message
ref="chat"
:initial-messages="demoMessages"
user-avatar="/static/images/demo-user.png"
ai-avatar="/static/images/demo-ai.png"
@message-sent="onMessageSent"
@ai-response="onAIResponse"
@action-click="onActionClick"
@file-preview="onFilePreview"
@audio-play="onAudioPlay"
@video-play="onVideoPlay"
@link-open="onLinkOpen"
@product-view="onProductView" />
<view class="demo-controls">
<button class="control-btn" @click="addTextMessage">添加文本消息</button>
<button class="control-btn" @click="addMarkdownMessage">添加Markdown</button>
<button class="control-btn" @click="addFileMessage">添加文件</button>
<button class="control-btn" @click="addAudioMessage">添加音频</button>
<button class="control-btn" @click="addVideoMessage">添加视频</button>
<button class="control-btn" @click="addLinkMessage">添加链接</button>
<button class="control-btn" @click="addProductMessage">添加商品</button>
<button class="control-btn" @click="clearMessages">清空消息</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
demoMessages: [
{
id: 1,
role: 'ai',
type: 'text',
content: '您好我是AI智能客服演示程序我可以展示多种消息类型。请点击下方的按钮体验不同功能',
timestamp: Date.now() - 300000,
actions: [
{ id: 1, text: '开始体验', type: 'like' }
]
}
]
}
},
methods: {
// 添加文本消息
addTextMessage() {
const message = {
id: Date.now(),
role: 'ai',
type: 'text',
content: '这是一个文本消息示例,支持**粗体**、*斜体*和`代码`格式。也可以包含换行符\n这是第二行内容。',
timestamp: Date.now(),
actions: [
{ id: 1, text: '有帮助', type: 'like' },
{ id: 2, text: '没帮助', type: 'dislike' }
]
}
this.$refs.chat.messages.push(message)
},
// 添加Markdown消息
addMarkdownMessage() {
const message = {
id: Date.now(),
role: 'ai',
type: 'markdown',
content: `# Markdown文档示例
## 二级标题
这是一个支持**粗体**、*斜体*和\`代码\`的Markdown消息。
### 功能特性
- ✅ 支持标题层级
- ✅ 支持列表展示
- ✅ 支持代码高亮
- ✅ 支持链接跳转
[查看详细文档](https://example.com)\n\n**注意:** 这是一个演示内容,实际使用时可以集成真实的文档系统。`,
timestamp: Date.now(),
actions: [
{ id: 1, text: '查看文档', type: 'like' }
]
}
this.$refs.chat.messages.push(message)
},
// 添加文件消息
addFileMessage() {
const message = {
id: Date.now(),
role: 'ai',
type: 'file',
fileName: '产品介绍文档.pdf',
fileSize: 2048000,
url: 'https://example.com/document.pdf',
timestamp: Date.now()
}
this.$refs.chat.messages.push(message)
},
// 添加音频消息
addAudioMessage() {
const message = {
id: Date.now(),
role: 'ai',
type: 'audio',
title: '产品功能介绍',
duration: 89,
url: 'https://example.com/audio.mp3',
timestamp: Date.now(),
playing: false,
currentTime: 0
}
this.$refs.chat.messages.push(message)
},
// 添加视频消息
addVideoMessage() {
const message = {
id: Date.now(),
role: 'ai',
type: 'video',
title: '产品演示视频',
duration: 180,
url: 'https://example.com/video.mp4',
cover: '/static/images/video-cover.jpg',
timestamp: Date.now()
}
this.$refs.chat.messages.push(message)
},
// 添加链接消息
addLinkMessage() {
const message = {
id: Date.now(),
role: 'ai',
type: 'link',
title: '帮助中心',
description: '详细的产品使用说明和常见问题解答',
url: 'https://example.com/help',
image: '/static/images/link-thumbnail.jpg',
timestamp: Date.now()
}
this.$refs.chat.messages.push(message)
},
// 添加商品消息
addProductMessage() {
const message = {
id: Date.now(),
role: 'ai',
type: 'product',
title: '智能手表 X1',
price: 1299,
description: '全新一代智能手表,支持心率监测、运动追踪、消息提醒等功能',
image: '/static/images/product-demo.jpg',
timestamp: Date.now(),
actions: [
{ id: 1, text: '立即购买', type: 'like' },
{ id: 2, text: '查看详情', type: 'dislike' }
]
}
this.$refs.chat.messages.push(message)
},
// 清空消息
clearMessages() {
this.$refs.chat.messages = [
{
id: 1,
role: 'ai',
type: 'text',
content: '消息已清空,可以重新开始体验!',
timestamp: Date.now()
}
]
},
// 事件处理
onMessageSent(message) {
console.log('用户发送消息:', message)
uni.showToast({
title: '消息发送成功',
icon: 'success'
})
},
onAIResponse(message) {
console.log('AI回复消息:', message)
},
onActionClick({ action, message }) {
console.log('操作点击:', action, message)
uni.showToast({
title: `点击了: ${action.text}`,
icon: 'none'
})
},
onFilePreview(message) {
console.log('文件预览:', message)
uni.showToast({
title: '正在打开文件...',
icon: 'none'
})
},
onAudioPlay(message) {
console.log('音频播放:', message)
},
onVideoPlay(message) {
console.log('视频播放:', message)
},
onLinkOpen(message) {
console.log('链接打开:', message)
uni.showToast({
title: '正在打开链接...',
icon: 'none'
})
},
onProductView(message) {
console.log('商品查看:', message)
uni.showToast({
title: '正在查看商品...',
icon: 'none'
})
}
}
}
</script>
<style lang="scss" scoped>
.demo-container {
height: 100vh;
display: flex;
flex-direction: column;
background-color: #f5f5f5;
}
.demo-header {
background-color: #ff4544;
color: white;
padding: 30rpx;
text-align: center;
.demo-title {
font-size: 32rpx;
font-weight: bold;
}
}
.demo-controls {
background-color: white;
padding: 20rpx;
border-top: 2rpx solid #eeeeee;
display: flex;
flex-wrap: wrap;
gap: 10rpx;
.control-btn {
flex: 1;
min-width: 150rpx;
height: 60rpx;
background-color: #f8f8f8;
border-radius: 30rpx;
font-size: 24rpx;
color: #333;
border: none;
&:active {
background-color: #eeeeee;
}
}
}
</style>

View File

@@ -71,11 +71,7 @@
</template>
<script>
import nsChatGoods from '@/pages_tool/components/ns-chat/ns-chat-goods.vue';
import nsChatOrder from '@/pages_tool/components/ns-chat/ns-chat-order.vue';
import nsChatReceiveGoods from '@/pages_tool/components/ns-chat/ns-chat-receiveGoods.vue';
import htmlParser from '@/common/js/html-parser';
import uniPopup from '@/components/uni-popup/uni-popup.vue';
import emjoy from '@/common/js/emjoy.js';
export default {
@@ -98,12 +94,6 @@
currImg: ''
};
},
components: {
nsChatGoods,
nsChatOrder,
uniPopup,
nsChatReceiveGoods
},
mounted() {
this.avatar = this.$util.img(this.siteInfo.logo_square);
this.myHeadImg = this.$util.img(this.memberInfo.headimg);
@@ -166,11 +156,11 @@
</script>
<style lang="scss">
/deep/.uni-popup__wrapper.uni-custom .uni-popup__wrapper-box {
::v-deep .uni-popup__wrapper.uni-custom .uni-popup__wrapper-box {
background-color: #000;
}
/deep/.uni-popup__wrapper.uni-custom.center .uni-popup__wrapper-box {
::v-deep .uni-popup__wrapper.uni-custom.center .uni-popup__wrapper-box {
max-width: 100%;
width: 100%;
}

View File

@@ -1,638 +0,0 @@
<template>
<view class="order-container" :class="{ 'safe-area': isIphoneX }">
<!-- #ifdef MP-WEIXIN -->
<view class="payment-navbar" :style="{ 'padding-top': menuButtonBounding.top + 'px', height: menuButtonBounding.height + 'px' }">
<view class="nav-wrap">
<text class="iconfont icon-back_light" @click="back"></text>
<view class="navbar-title">确认订单</view>
</view>
</view>
<view class="payment-navbar-block" :style="{ height: menuButtonBounding.bottom + 'px' }"></view>
<!-- #endif -->
<scroll-view scroll-y="true" class="order-scroll-container">
<view class="payment-navbar-block"></view>
<template v-if="paymentData">
<template v-if="paymentData.is_virtual">
<!-- 虚拟商品联系方式 -->
<view class="mobile-wrap">
<view class="tips color-base-text">
<text class="iconfont icongantanhao"></text>
购买虚拟类商品需填写手机号方便商家与您联系
</view>
<view class="form-group">
<text class="icon">
<image :src="$util.img('public/uniapp/order/icon-mobile.png')" mode="widthFix"></image>
</text>
<text class="text">手机号码</text>
<input type="number" maxlength="11" placeholder="请输入您的手机号码" placeholder-class="color-tip placeholder" class="input" v-model="orderCreateData.member_address.mobile" />
</view>
</view>
</template>
<template v-else>
<!-- 配送方式 -->
<view class="delivery-mode" v-if="goodsData.delivery.express_type.length > 1">
<view class="action">
<view :class="{ active: item.name == orderCreateData.delivery.delivery_type }" v-for="(item, index) in goodsData.delivery.express_type" :key="index" @click="selectDeliveryType(item)">
{{ item.title }}
<!-- 外圆角 -->
<view class="out-radio"></view>
</view>
</view>
</view>
<view class="address-box" :class="{ 'not-delivery-type': goodsData.delivery.express_type.length <= 1 }" v-if="orderCreateData.delivery.delivery_type == 'express'">
<view class="info-wrap" v-if="memberAddress" @click="selectAddress">
<view class="content">
<text class="name">{{ memberAddress.name ? memberAddress.name : '' }}</text>
<text class="mobile">{{ memberAddress.mobile ? memberAddress.mobile : '' }}</text>
<view class="desc-wrap">
{{ memberAddress.full_address ? memberAddress.full_address : '' }}
{{ memberAddress.address ? memberAddress.address : '' }}
</view>
</view>
<text class="cell-more iconfont icon-right"></text>
</view>
<view class="empty-wrap" v-else @click="selectAddress">
<view class="info">请设置收货地址</view>
<view class="cell-more">
<view class="iconfont icon-right"></view>
</view>
</view>
<image class="address-line" :src="$util.img('public/uniapp/order/address-line.png')"></image>
</view>
<view class="address-box" :class="{ 'not-delivery-type': goodsData.delivery.express_type.length <= 1 }" v-if="orderCreateData.delivery.delivery_type == 'local'">
<view v-if="localMemberAddress">
<block v-if="storeList && Object.keys(storeList).length > 1">
<view class="local-delivery-store" v-if="storeInfo" @click="openPopup('deliveryPopup')">
<view class="info">
<text class="store-name">{{ storeInfo.store_name }}</text>
提供配送
</view>
<view class="cell-more">
<text>点击切换</text>
<text class="iconfont icon-right"></text>
</view>
</view>
<view v-else class="local-delivery-store">
<view class="info">
<text class="store-name">您的附近没有可配送的门店请选择其他配送方式</text>
</view>
</view>
</block>
<view class="info-wrap local" @click="selectAddress">
<view class="content">
<text class="name">{{ localMemberAddress.name ? localMemberAddress.name : '' }}
</text>
<text class="mobile">{{ localMemberAddress.mobile ? localMemberAddress.mobile : '' }}
</text>
<view class="desc-wrap">
{{ localMemberAddress.full_address ? localMemberAddress.full_address : '' }}
{{ localMemberAddress.address ? localMemberAddress.address : '' }}
</view>
</view>
<text class="cell-more iconfont icon-right"></text>
</view>
<view class="local-box" v-if="calculateGoodsData.config.local && calculateGoodsData.delivery.local.info.time_is_open == 1">
<view class="pick-block" @click="localtime('')">
<view class="title font-size-base">送达时间</view>
<view class="time-picker">
<text :class="{ 'color-tip': !deliveryTime }">{{ deliveryTime ? deliveryTime : '请选择送达时间' }}</text>
<text class="iconfont icon-right cell-more"></text>
</view>
</view>
</view>
</view>
<view class="empty-wrap" v-else @click="selectAddress">
<view class="info">请设置收货地址</view>
<view class="cell-more">
<view class="iconfont icon-right"></view>
</view>
</view>
<image class="address-line" :src="$util.img('public/uniapp/order/address-line.png')"></image>
</view>
<!-- 门店信息 -->
<view class="store-box" :class="{ 'not-delivery-type': goodsData.delivery.express_type.length <= 1 }" v-if="orderCreateData.delivery.delivery_type == 'store'">
<block v-if="storeInfo">
<view @click="openPopup('deliveryPopup')" class="store-info">
<view class="store-address-info">
<view class="info-wrap">
<view class="title">
<text>{{ storeInfo.store_name }}</text>
</view>
<view class="store-detail">
<view v-if="storeInfo.open_date">营业时间{{ storeInfo.open_date }}</view>
<view class="address">{{ storeInfo.full_address }} {{ storeInfo.address }}
</view>
</view>
</view>
<view class="cell-more iconfont icon-right" v-if="storeList && Object.keys(storeList).length > 1"></view>
</view>
</view>
<view class="mobile-wrap store-mobile" v-if="orderCreateData.member_address">
<view class="form-group">
<text class="text">姓名</text>
<input type="text" placeholder-class="color-tip placeholder" class="input" disabled v-model="orderCreateData.member_address.name" />
</view>
</view>
<view class="mobile-wrap store-mobile" v-if="orderCreateData.member_address">
<view class="form-group">
<text class="text">预留手机</text>
<input type="number" maxlength="11" placeholder="请输入您的手机号码" placeholder-class="color-tip placeholder" class="input" v-model="orderCreateData.member_address.mobile" />
</view>
</view>
<view class="store-time" @click="storetime('')">
<view class="left">提货时间</view>
<view class="right">
{{ deliveryTime }}
<text class="iconfont icon-right"></text>
</view>
</view>
</block>
<view v-else class="empty">当前无自提门店请选择其它配送方式</view>
<image class="address-line" :src="$util.img('public/uniapp/order/address-line.png')"></image>
</view>
</template>
<!-- 店铺 -->
<view class="site-wrap order-goods" v-if="calculateGoodsData">
<view class="site-header">
<view class="iconfont icon-dianpu"></view>
<text class="site-name">门店</text>
</view>
<view class="site-body">
<!-- 商品 -->
<view class="goods-item" v-for="(goodsItem, goodsIndex) in calculateGoodsData.goods_list" :key="goodsIndex">
<view class="goods-wrap">
<view class="goods-img" @click="$util.redirectTo('/pages/goods/detail', { goods_id: goodsItem.goods_id })">
<image :src="$util.img(goodsItem.sku_image, { size: 'mid' })" @error="imageError(goodsIndex)" mode="aspectFill"/>
</view>
<view class="goods-info">
<view class="top-wrap">
<view @click="$util.redirectTo('/pages/goods/detail', { goods_id: goodsItem.goods_id })" class="goods-name">{{ goodsItem.sku_name }}</view>
<view class="sku" v-if="goodsItem.sku_spec_format">
<view class="goods-spec">
<block v-for="(x, i) in goodsItem.sku_spec_format" :key="i">
<view>{{ x.spec_value_name }}</view>
</block>
</view>
</view>
<block v-if="goodsItem.is_virtual == 0">
<view class="error-tips" v-if="orderCreateData.delivery &&
orderCreateData.delivery.delivery_type &&
goodsItem.support_trade_type &&
goodsItem.support_trade_type.indexOf(orderCreateData.delivery.delivery_type) == -1
">
<text class="iconfont icon-gantanhao"></text>
<text>该商品不支持{{ orderCreateData.delivery.delivery_type_name }}</text>
</view>
</block>
<view class="error-tips" v-if="goodsItem.error && goodsItem.error.message">
<text class="iconfont icon-gantanhao"></text>
<text>{{ goodsItem.error.message }}</text>
</view>
</view>
<view class="goods-sub-section">
<view class="color-base-text">
<text class="unit price-style small">{{ $lang('common.currencySymbol') }}</text>
<text class="goods-price price-style large">{{ parseFloat(goodsItem.price).toFixed(2).split('.')[0] }}</text>
<text class="unit price-style small">.{{ parseFloat(goodsItem.price).toFixed(2).split('.')[1] }}</text>
</view>
<view>
<text class="font-size-tag">x</text>
<text class="font-size-base">{{ goodsItem.num }}</text>
</view>
</view>
</view>
</view>
<view class="member-goods-card order-cell" v-if="calculateGoodsData.goods_list[goodsIndex].member_card_list" @click="selectMemberGoodsCard(goodsIndex)">
<text class="tit">次卡抵扣</text>
<view class="box text-overflow">
<block v-if="calculateGoodsData.goods_list[goodsIndex].card_promotion_money">
<text class="text">次卡抵扣{{ calculateGoodsData.goods_list[goodsIndex].card_use_num }}张/{{ calculateGoodsData.goods_list[goodsIndex].card_use_num }}次</text>
<text class="price-font">-¥{{ calculateGoodsData.goods_list[goodsIndex].card_promotion_money | moneyFormat }}</text>
</block>
<text class="color-tip" v-else>请选择次卡</text>
</view>
<text class="iconfont icon-right"></text>
</view>
<view class="goods-form" v-if="goodsData.goods_list[goodsIndex].goods_form" @click="editForm(goodsIndex)">
<ns-form :data="goodsData.goods_list[goodsIndex].goods_form.json_data" ref="goodsForm" :custom-attr="{ sku_id: goodsItem.sku_id, form_id: goodsData.goods_list[goodsIndex].goods_form.id }"/>
<text class="cell-more iconfont icon-right"></text>
<view class="shade"></view>
</view>
</view>
</view>
</view>
<view class="site-wrap buyer-message">
<view class="order-cell">
<text class="tit">买家留言</text>
<view class="box text-overflow " @click="openPopup('buyerMessagePopup')">
<text v-if="orderCreateData.buyer_message">{{ orderCreateData.buyer_message }}</text>
<text class="color-sub" v-else>无留言</text>
</view>
<text class="iconfont icon-right"></text>
</view>
</view>
<view v-if="paymentData.system_form" class="system-form-wrap">
<view class="order-cell">
<text class="tit">{{ paymentData.system_form.form_name }}</text>
</view>
<ns-form :data="paymentData.system_form.json_data" ref="form"/>
</view>
<view class="site-wrap" v-if="calculateGoodsData || promotionInfo || (calculateGoodsData && calculateGoodsData.max_usable_point > 0) || goodsData.invoice">
<view class="site-footer">
<view class="order-cell coupon" v-if="modules.indexOf('coupon') != -1">
<text class="tit">优惠券</text>
<view class="box text-overflow" @click="openPopup('couponPopup')">
<template v-if="orderCreateData.coupon && orderCreateData.coupon.coupon_id">
<text>已使用优惠券,优惠</text>
<text class="unit price-font">{{ $lang('common.currencySymbol') }}</text>
<text class="money price-font">{{ (calculateData && calculateData.coupon_money ? calculateData.coupon_money : 0) | moneyFormat }}</text>
</template>
<text v-else>不使用优惠券</text>
</view>
<text class="iconfont icon-right"></text>
</view>
<view class="order-cell" v-if="promotionInfo">
<text class="tit">活动优惠</text>
<view class="box text-overflow" @click="openPopup('promotionPopup')">
<text>{{ promotionInfo.title }}</text>
</view>
<text class="iconfont icon-right"></text>
</view>
<view class="order-cell point" v-if="calculateGoodsData && calculateGoodsData.max_usable_point > 0">
<text class="tit">
<text>使用{{ parseInt(calculateGoodsData.max_usable_point) }}积分可抵扣</text>
<text class="unit price-font">{{ $lang('common.currencySymbol') }}</text>
<text class="money price-font">{{ calculateData.point_money | moneyFormat }}</text>
</text>
<view class="box"></view>
<ns-switch class="balance-switch" @change="usePoint" :checked="orderCreateData.is_point == 1"/>
</view>
<view class="order-cell order-invoice-cell" v-if="goodsData.invoice.invoice_status == 1">
<text class="tit">发票</text>
<view class="box text-overflow" @click="openPopup('invoicePopup')">
<text v-if="orderCreateData.is_invoice == 1">{{ orderCreateData.invoice_type == 1 ? '纸质' : '电子' }}发票({{ orderCreateData.invoice_content }})</text>
<text v-else>无需发票</text>
</view>
<text class="iconfont icon-right"></text>
</view>
</view>
</view>
<view class="site-wrap box member-card-wrap" v-if="paymentData.recommend_member_card && Object.keys(paymentData.recommend_member_card).length > 0">
<view class="head" @click="selectMemberCard">
<text class="iconfont icon-huiyuan"></text>
<view class="info">
开通{{ paymentData.recommend_member_card.level_name }}
<text>本单预计可省</text>
<text class="price-color">{{ paymentData.recommend_member_card.discount_money | moneyFormat }}</text>
<text>元</text>
</view>
<text class="iconfont" :class="orderCreateData.is_open_card == 1 ? 'icon-yuan_checked color-base-text' : 'icon-yuan_checkbox'"></text>
</view>
<view class="body" v-if="orderCreateData.is_open_card">
<view class="item" :class="{ 'active color-base-border': item.key == orderCreateData.member_card_unit }" v-for="(item, index) in cardChargeType" :key="index" @click="selectMembercardUnit(item.key)">
<view class="title">{{ item.title }}</view>
<view class="price price-font">{{ $lang('common.currencySymbol') }}{{ parseFloat(item.value) }}/{{ item.unit }}</view>
<text class="iconfont icon-icon color-base-text price-font identify" v-if="item.key == orderCreateData.member_card_unit"></text>
</view>
</view>
</view>
<!-- 订单金额 -->
<template v-if="calculateData">
<view class="order-money">
<view class="order-cell">
<text class="tit">商品金额</text>
<view class="box">
<text class="unit color-title price-font">{{ $lang('common.currencySymbol') }}</text>
<text class="money color-title price-font">{{ calculateData.goods_money | moneyFormat }}</text>
</view>
</view>
<view class="order-cell" v-if="calculateData.is_virtual == 0 && calculateData.delivery_money > 0">
<text class="tit">运费</text>
<view class="box color-base-text">
<text class="operator">+</text>
<text class="unit price-font">{{ $lang('common.currencySymbol') }}</text>
<text class="money price-font">{{ calculateData.delivery_money | moneyFormat }}</text>
</view>
</view>
<view class="order-cell" v-if="orderCreateData.is_invoice && calculateData.invoice_money > 0">
<text class="tit">
<text>税费</text>
<text class="color-base-text font-bold price-font">({{ goodsData.invoice.invoice_rate }}%)</text>
</text>
<view class="box color-base-text">
<text class="operator">+</text>
<text class="unit price-font">{{ $lang('common.currencySymbol') }}</text>
<text class="money price-font">{{ calculateData.invoice_money | moneyFormat }}</text>
</view>
</view>
<view class="order-cell" v-if="orderCreateData.is_invoice && calculateData.invoice_delivery_money > 0">
<text class="tit">发票邮寄费</text>
<view class="box color-base-text">
<text class="operator">+</text>
<text class="unit price-font">{{ $lang('common.currencySymbol') }}</text>
<text class="money price-font">{{ calculateData.invoice_delivery_money | moneyFormat }}</text>
</view>
</view>
<view class="order-cell" v-if="calculateData.promotion_money > 0">
<text class="tit">优惠</text>
<view class="box color-base-text">
<text class="operator">-</text>
<text class="unit price-font">{{ $lang('common.currencySymbol') }}</text>
<text class="money price-font">{{ calculateData.promotion_money | moneyFormat }}</text>
</view>
</view>
<view class="order-cell" v-if="calculateData.coupon_money">
<text class="tit">优惠券</text>
<view class="box color-base-text">
<text class="operator">-</text>
<text class="unit price-font">{{ $lang('common.currencySymbol') }}</text>
<text class="money price-font">{{ calculateData.coupon_money | moneyFormat }}</text>
</view>
</view>
<view class="order-cell" v-if="calculateData.point_money > 0">
<text class="tit">积分抵扣</text>
<view class="box color-base-text">
<text class="operator">-</text>
<text class="unit price-font">{{ $lang('common.currencySymbol') }}</text>
<text class="money price-font">{{ calculateData.point_money | moneyFormat }}</text>
</view>
</view>
<view class="order-cell" v-if="calculateData.member_card_money > 0">
<text class="tit">会员卡</text>
<view class="box color-base-text">
<text class="operator">+</text>
<text class="unit price-font">{{ $lang('common.currencySymbol') }}</text>
<text class="money price-font">{{ calculateData.member_card_money | moneyFormat }}
</text>
</view>
</view>
</view>
<view v-if="transactionAgreement.title && transactionAgreement.content" class="agreement">购买前请先阅读<text @click="$refs.agreementPopup.open()">《{{ transactionAgreement.title }}》</text>,下单即代表同意该协议</view>
<view class="order-submit bottom-safe-area">
<view class="order-settlement-info">
<text class="font-size-base color-tip margin-right">共{{ calculateData.goods_num }}件</text>
<text class="font-size-base">合计:</text>
<text class=" unit price-font">{{ $lang('common.currencySymbol') }}</text>
<text class=" money price-font">{{ parseFloat(calculateData.pay_money).toFixed(2).split('.')[0] }}</text>
<text class=" unit price-font">.{{ parseFloat(calculateData.pay_money).toFixed(2).split('.')[1] }}</text>
</view>
<view class="submit-btn">
<button type="primary" class="mini" size="mini" @click="create()" v-if="!surplusStartMoney()">提交订单</button>
<button v-else class="no-submit mini" size="mini">差{{ surplusStartMoney() | moneyFormat }}起送</button>
</view>
</view>
<view class="order-submit-block"></view>
<payment ref="choosePaymentPopup" @close="payClose" v-if="calculateData"></payment>
</template>
<!-- 活动优惠弹窗 -->
<uni-popup ref="promotionPopup" type="bottom" v-if="promotionInfo">
<view class="promotion-popup popup">
<view class="popup-header">
<text class="tit">活动优惠</text>
<text class="iconfont icon-close" @click="closePopup('promotionPopup')"></text>
</view>
<scroll-view scroll-y="true" class="popup-body" :class="{ 'safe-area': isIphoneX }">
<view class="order-cell" style="align-items: baseline;">
<view class="tit">
<text class="promotion-mark ns-gradient-promotionpages-payment">{{ promotionInfo.title }}
</text>
</view>
<view class="promotion-content">
<view class="tit tit-content" style="white-space: pre-line;" v-html="promotionInfo.content"></view>
</view>
</view>
</scroll-view>
<view class="popup-footer" :class="{ 'bottom-safe-area': isIphoneX }">
<view class="confirm-btn color-base-bg" @click="closePopup('promotionPopup')">确定</view>
</view>
</view>
</uni-popup>
<!-- 门店列表弹窗 -->
<uni-popup ref="deliveryPopup" type="bottom">
<view class="delivery-popup popup">
<view class="popup-header">
<text class="tit">已为您甄选出附近所有相关门店</text>
<text class="iconfont icon-close" @click="closePopup('deliveryPopup')"></text>
</view>
<view class="popup-body store-popup" :class="{ 'safe-area': isIphoneX }">
<mescroll-uni @getData="getStore" ref="mescroll" top="50px">
<block slot="list">
<view class="delivery-content">
<block v-if="storeData">
<view class="item-wrap" v-for="(item, index) in storeData" :key="index" @click="selectPickupPoint(item)">
<view class="detail">
<view class="name" :class="item.store_id == orderCreateData.delivery.store_id ? 'color-base-text' : ''">
<text>{{ item.store_name }}</text>
<text v-if="item.distance">({{ item.distance }}km)</text>
</view>
<view class="info">
<view :class="item.store_id == orderCreateData.delivery.store_id ? 'color-base-text' : ''" class="font-size-goods-tag">营业时间:{{ item.open_date }}</view>
<view :class="item.store_id == orderCreateData.delivery.store_id ? 'color-base-text' : ''" class="font-size-goods-tag">地址:{{ item.full_address }}{{ item.address }}</view>
</view>
</view>
<view class="icon" v-if="item.store_id == orderCreateData.delivery.store_id">
<text class="iconfont icon-yuan_checked color-base-text"></text>
</view>
</view>
</block>
<view v-else class="empty-wrap">
<ns-empty text="所选择收货地址附近没有可以自提的门店" :isIndex="false"></ns-empty>
</view>
</view>
</block>
</mescroll-uni>
</view>
</view>
</uni-popup>
<!-- 留言弹窗 -->
<uni-popup ref="buyerMessagePopup" type="bottom">
<view style="height: auto;" class="buyermessag-popup popup" @touchmove.prevent.stop>
<view class="popup-header">
<text class="tit">买家留言</text>
<text class="iconfont icon-close" @click="closePopup('buyerMessagePopup')"></text>
</view>
<scroll-view scroll-y="true" class="popup-body" :class="{ 'safe-area': isIphoneX }">
<view>
<view class="buyermessag-cell">
<view class="buyermessag-form-group">
<textarea type="text" maxlength="100" placeholder="留言前建议先与商家协调一致" placeholder-class="color-tip" v-model="orderCreateData.buyer_message"/>
</view>
</view>
</view>
</scroll-view>
<view class="popup-footer" @click="saveBuyerMessage" :class="{ 'bottom-safe-area': isIphoneX }">
<view class="confirm-btn color-base-bg">确定</view>
</view>
</view>
</uni-popup>
<!-- 优惠券弹窗 -->
<uni-popup ref="couponPopup" type="bottom" v-if="calculateGoodsData" :mask-click="false">
<view class="coupon-popup popup" @touchmove.prevent.stop>
<view class="popup-header">
<text class="tit">优惠券</text>
<text class="iconfont icon-close" @click="closePopup('couponPopup')"></text>
</view>
<scroll-view scroll-y="true" class="popup-body" :class="{ 'safe-area': isIphoneX }">
<view v-if="coupon_list.length > 0">
<view class="coupon-item" v-for="(couponItem, couponIndex) in coupon_list" :key="couponIndex" @click="selectCoupon(couponItem)">
<view class="coupon-info" :style="{ backgroundColor: 'var(--main-color-shallow)' }">
<view class="info-wrap">
<image class="coupon-line" mode="heightFix" :src="$util.img('public/uniapp/coupon/coupon_line.png')"/>
<view class="coupon-money">
<template v-if="couponItem.type == 'divideticket'">
<text class="unit">{{ $lang('common.currencySymbol') }}</text>
<text class="money">{{ parseFloat(couponItem.money) }}</text>
</template>
<template v-else-if="couponItem.type == 'reward'">
<text class="unit">{{ $lang('common.currencySymbol') }}</text>
<text class="money">{{ parseFloat(couponItem.money) }}</text>
</template>
<template v-else-if="couponItem.type == 'discount'">
<text class="money">{{ parseFloat(couponItem.discount) }}</text>
<text class="unit">折</text>
</template>
<view class="at-least">
<template v-if="couponItem.at_least > 0">
满{{ couponItem.at_least }}可用
</template>
<template v-else>
无门槛
</template>
</view>
</view>
</view>
<view class="desc-wrap">
<view class="coupon-name">{{ couponItem.coupon_name }}</view>
<view v-if="couponItem.type == 'discount' && couponItem.discount_limit > 0" class="limit">最多可抵¥{{ couponItem.discount_limit }}</view>
<view class="time font-size-goods-tag">有效期:{{ couponItem.end_time ? $util.timeStampTurnTime(couponItem.end_time) : '长期有效' }}</view>
</view>
<view class="iconfont" :class="orderCreateData.coupon.coupon_id == couponItem.coupon_id ? 'icon-yuan_checked color-base-text' : 'icon-yuan_checkbox'"></view>
</view>
</view>
</view>
<view v-else class="coupon-empty">暂无可用的优惠券</view>
</scroll-view>
<view class="popup-footer" :class="{ 'bottom-safe-area': isIphoneX }">
<view class="confirm-btn color-base-bg" @click="useCpopon">确定</view>
</view>
</view>
</uni-popup>
<!-- 交易协议 -->
<view @touchmove.prevent>
<uni-popup ref="agreementPopup" type="center" :maskClick="false">
<view class="agreement-conten-box">
<view class="close">
<text class="iconfont icon-close" @click="$refs.agreementPopup.close()"></text>
</view>
<view class="title">{{ transactionAgreement.title }}</view>
<view class="con">
<scroll-view scroll-y="true" class="con">
<rich-text :nodes="transactionAgreement.content"></rich-text>
</scroll-view>
</view>
</view>
</uni-popup>
</view>
<!-- 表单修改弹窗 -->
<uni-popup ref="editFormPopup" type="bottom">
<view style="height: auto;" class="form-popup popup" @touchmove.prevent.stop>
<view class="popup-header">
<text class="tit">买家信息</text>
<text class="iconfont icon-close" @click="$refs.editFormPopup.close()"></text>
</view>
<scroll-view scroll-y="true" class="popup-body" :class="{ 'safe-area': isIphoneX }">
<ns-form v-if="tempFormData" :data="tempFormData.json_data" ref="tempForm"></ns-form>
</scroll-view>
<view class="popup-footer" @click="saveForm" :class="{ 'bottom-safe-area': isIphoneX }">
<view class="confirm-btn color-base-bg">确定</view>
</view>
</view>
</uni-popup>
<uni-popup ref="memberGoodsCardPopup" type="bottom">
<view class="member-card-popup popup" @touchmove.prevent.stop>
<view class="popup-header">
<text class="tit">选择次卡</text>
<text class="iconfont icon-close" @click="$refs.memberGoodsCardPopup.close()"></text>
</view>
<scroll-view scroll-y="true" class="popup-body" :class="{ 'safe-area': isIphoneX }">
<view v-for="(item, index) in selectGoodsCard.cardList" class="card-item" @click="selectGoodsCard.click(item.item_id)">
<view class="content">
<view class="title">{{ item.goods_name }}</view>
<view class="info">
<text v-if="item.card_type == 'timecard'">不限次数</text>
<text v-if="item.card_type == 'oncecard'">剩余{{ item.num - item.use_num }}次</text>
<text v-if="item.card_type == 'commoncard'">剩余{{ item.total_num - item.total_use_num }}次</text>
<text>|</text>
<text>{{ item.end_time ? $util.timeStampTurnTime(item.end_time) : '长期有效' }}</text>
</view>
</view>
<view class="iconfont" :class="selectGoodsCard.itemId == item.item_id ? 'icon-yuan_checked color-base-text' : 'icon-yuan_checkbox'"></view>
</view>
</scroll-view>
<view class="popup-footer" @click="saveMemberGoodsCard" :class="{ 'bottom-safe-area': isIphoneX }">
<view class="confirm-btn color-base-bg">确定</view>
</view>
</view>
</uni-popup>
</template>
</scroll-view>
<!-- 门店自提时间 -->
<ns-select-time @selectTime="selectPickupTime" ref="timePopup"></ns-select-time>
<ns-login ref="login"></ns-login>
<loading-cover ref="loadingCover"></loading-cover>
</view>
</template>
<script>
import payment from './payment.js';
export default {
name: 'common-payment',
data() {
return {};
},
props: {
api: Object,
createDataKey: String
},
mixins: [payment]
};
</script>
<style lang="scss">
@import '@/common/css/order_parment.scss';
.order-cell .promotion-content {
flex: 1;
}
</style>

View File

@@ -1,211 +0,0 @@
<template>
<x-skeleton data-component-name="diy-article" type="list" :loading="loading" :configs="skeletonConfig">
<view class="article-wrap" :style="warpCss">
<view :class="['list-wrap', value.style]" :style="warpCss">
<view :class="['item', value.ornament.type]" v-for="(item, index) in list" :key="index" @click="toDetail(item)" :style="itemCss">
<view class="article-img">
<image class="cover-img" :src="$util.img(item.cover_img)" mode="widthFix" @error="imgError(index)" />
</view>
<view class="info-wrap">
<text class="title">{{ item.article_title }}</text>
<text class="desc" style="color:#888;font-size: 24rpx; display: -webkit-box;-webkit-box-orient: vertical;-webkit-line-clamp: 2;overflow: hidden;text-overflow: ellipsis;">{{ item.article_abstract }}</text>
<view class="read-wrap">
<block v-if="item.category_name">
<text class="category-icon"></text>
<text>{{ item.category_name }}</text>
</block>
<text class="date">{{ $util.timeStampTurnTime(item.create_time, 'date') }}</text>
</view>
</view>
</view>
</view>
</view>
</x-skeleton>
</template>
<script>
// 文章
export default {
name: 'diy-article',
props: {
value: {
type: Object
}
},
data() {
return {
list: [],
loading: true,
skeletonConfig: {
gridRows: 1,
gridRowsGap: '40rpx',
headWidth: '160rpx',
headHeight: '160rpx',
textRows: 2
}
};
},
created() {
this.getList();
},
watch: {
// 组件刷新监听
componentRefresh: function (nval) {
this.getList();
}
},
computed: {
warpCss() {
var obj = '';
obj += 'background-color:' + this.value.componentBgColor + ';';
if (this.value.componentAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
}
return obj;
},
// 子项样式
itemCss() {
var obj = '';
obj += 'background-color:' + this.value.elementBgColor + ';';
if (this.value.elementAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
}
if (this.value.ornament.type == 'shadow') {
obj += 'box-shadow:' + '0 0 10rpx ' + this.value.ornament.color;
}
if (this.value.ornament.type == 'stroke') {
obj += 'border:' + '2rpx solid ' + this.value.ornament.color;
}
return obj;
}
},
methods: {
getList() {
var data = {
num: this.value.count
};
if (this.value.sources == 'diy') {
data.num = 0;
data.article_id_arr = this.value.articleIds.toString();
}
this.$api.sendRequest({
url: '/api/article/lists',
data: data,
success: res => {
if (res.code == 0 && res.data) {
let data = res.data;
this.list = data;
}
this.loading = false;
}
});
},
toDetail(item) {
this.$util.redirectTo('/pages_tool/article/detail', {
article_id: item.article_id
});
},
imgError(index) {
if (this.list[index]) this.list[index].cover_img = this.$util.getDefaultImage().article;
}
}
};
</script>
<style lang="scss">
.article-wrap {
.list-wrap {
&.style-1 {
.item {
display: flex;
padding: 20rpx;
margin-top: 24rpx;
&:first-of-type {
margin-top: 0;
}
.article-img {
margin-right: 20rpx;
width: 160rpx;
height: 160rpx;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
image {
width: 100%;
}
}
.info-wrap {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
.title {
font-weight: bold;
margin-bottom: 10rpx;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
font-size: 30rpx;
line-height: 1.5;
}
.abstract {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
font-size: $font-size-tag;
}
.read-wrap {
display: flex;
color: #999ca7;
justify-content: flex-start;
align-items: center;
margin-top: 10rpx;
line-height: 1;
text {
font-size: $font-size-tag;
}
.iconfont {
font-size: 36rpx;
vertical-align: bottom;
margin-right: 10rpx;
}
.category-icon {
width: 8rpx;
height: 8rpx;
border-radius: 50%;
background: $base-color;
margin-right: 10rpx;
}
.date {
margin-left: 20rpx;
}
}
}
}
}
}
}
</style>

View File

@@ -1,444 +0,0 @@
<template>
<view data-component-name="diy-audio">
<view class="fui-audio style1" :style="{background:value.background}" v-if="value.type == 'style-2'">
<view class="content" style="padding-top: 20rpx;">
<view class="name" :style="{color:value.textcolor}">{{value.text}}</view>
<view class="author" :style="{color:value.subtitlecolor}">{{value.desc}}----{{value.id}}</view>
</view>
<view class="progress">
<view class="progressBar" :style="{width:audiowidth}"></view>
</view>
<view class="time" :style="{color:value.timecolor}">
{{audiotime}}
</view>
<view @click="play()" class="start" :class="status?'iconj icon-07zanting':'iconj icon-bofang'" style="padding-top: 18rpx"></view>
</view>
<view class="fui-audio style3" :style="{background:value.background}" v-else>
<!-- <audio src="/static/audio/bgm.mp3" controls loop></audio> -->
<view class="img">
<image :src="$util.img(value.imageUrl)"></image>
</view>
<view class="content">
<view class="name" :style="{color:value.textcolor}">{{value.text}}</view>
<view class="author" :style="{color:value.subtitlecolor}">{{value.desc}}</view>
</view>
<view class="progress">
<view class="progressBar" :style="{width:audiowidth}"></view>
</view>
<view class="time" :style="{color:value.timecolor}">
<!-- {{audios[value.id].audiotime}} -->
{{audiotime}}
</view>
<view @click="play()" class="start" :class="status?'iconj icon-07zanting':'iconj icon-bofang'"></view>
</view>
</view>
</template>
<script>
// 视频
export default {
name: 'diy-audio',
props: {
value: {
type: Object
}
},
data() {
return {
audiosObj:[],
audios: {},
audioContext:null,
audiotime:'00:01',
audiowidth:0,
status:0,//1播放0停止
};
},
created() {
// console.log(this.value)
this.audios[this.value.id] = {
audiotime:'00:01',
audiowidth:0
}
},
watch: {
// 组件刷新监听
componentRefresh: function(nval) {}
},
computed: {
videoWarpCss: function() {
var obj = '';
if (this.value.componentAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
}
return obj;
}
},
methods: {
play(){
var t = this.value.id, a = this.audiosObj[t] || !1;
var e = {
audio:this.$util.img(this.value.audioUrl),
}
if (!a) {
a = uni.createInnerAudioContext("audio_" + t);
var i = this.audiosObj;
i[t] = a, this.audiosObj = i
// uni.setStorageSync('audio_list',audio_list)
var audio_list = uni.getStorageSync('audio_list')?uni.getStorageSync('audio_list'):[]
if(audio_list.includes(t) == false){
audio_list.push(t)
uni.setStorageSync('audio_list',audio_list)
}
}
console.log(uni.getStorageSync('audio_list'))
var n = this;
// console.log(a)
a.onPlay(function() {
var e = setInterval(function() {
var i = a.currentTime / a.duration * 100 + "%", s = Math.floor(Math.ceil(a.currentTime) / 60), o = (Math.ceil(a.currentTime) % 60 / 100).toFixed(2).slice(-2), r = Math.ceil(a.currentTime);
s < 10 && (s = "0" + s);
var u = s + ":" + o, c = n.audios;
// console.log(i)
c[t].audiowidth = i, c[t].Time = e, c[t].audiotime = u, c[t].seconds = r, n.audios = c;
}, 1e3);
});
var s = n.$util.img(n.value.audioUrl), o = n.audios[n.value.id].seconds || 0, r = 0, u = 1;
0 == u && a.onEnded(function(e) {
c[t].status = !1,n.status=!1,c[t].seconds = 0,console.log(c),n.audios = c;
});
var c = n.audios;
c[t] || (c[t] = {}), a.paused && 0 == o ? (a.src = s, a.play(), 1 == u && (a.loop = !0),
c[t].status = !0,n.status=!0, n.pauseOther(t)) : a.paused && o > 0 ? (a.play(), 0 == r ? a.seek(o) : a.seek(0),
c[t].status = !0,n.status=!0, n.pauseOther(t)) : (a.pause(), c[t].status = !1,n.status=!1),n.audios = c;
console.log(n.audios)
},
pauseOther: function(e) {
var t = this;
// console.log(this.audiosObj[this.value.id]);
var i = this.audiosObj[this.value.id],a = this.value.id
// console.log(i)
// console.log(a)
// if (a != e) {
// i.pause();
// var n = t.audios;
// n[a] && (n[a].status = !1, this.audios=n);
// }
var audios = document.getElementsByTagName("audio");
// 暂停函数
function pauseAll() {
var self = this;
[].forEach.call(audios, function (i) {
// 将audios中其他的audio全部暂停
i !== self && i.pause();
})
}
// 给play事件绑定暂停函数
[].forEach.call(audios, function (i) {
i.addEventListener("play", pauseAll.bind(i));
})
// var audio_list = uni.getStorageSync('audio_list')
// audio_list.forEach(function(value, index) {
// if (value != e) {
// console.log(e)
// uni.createInnerAudioContext("audio_" + value).pause();
// }
// });
// this.audiosObj.forEach(function(value, index) {
// console.log(value);
// });
// this.each(this.audiosObj, function(a, i) {
// if (a != e) {
// i.pause();
// var n = t.data.audios;
// n[a] && (n[a].status = !1, t.setData({
// audios: n
// }));
// }
// });
},
play_bak(){
var t = this.value.id
this.audioContext = uni.createInnerAudioContext("audio_" + this.value.id);
this.audioContext.src = this.$util.img(this.value.audioUrl);
var that = this
if(this.status == 1){
this.audioContext.pause();
this.status = 0
return false
}
this.audioContext.play();
this.status = 1
this.audioContext.onCanplay(function(s){
var e = setInterval(function() {
var i = parseFloat(that.audioContext.currentTime) / parseFloat(that.audioContext.duration) * 100 + "%", s = Math.floor(Math.ceil(that.audioContext.currentTime) / 60), o = (Math.ceil(that.audioContext.currentTime) % 60 / 100).toFixed(2).slice(-2), r = Math.ceil(that.audioContext.currentTime);
s < 10 && (s = "0" + s);
var u = s + ":" + o, c = that.audios;
c[t].audiowidth = i, c[t].Time = e, c[t].audiotime = u, c[t].seconds = r
that.audios = c
// console.log(c)
console.log(that.audios[that.value.id].audiotime)
that.audiotime = that.audios[that.value.id].audiotime
that.audiowidth = that.audios[that.value.id].audiowidth
console.log(i)
that.lyg = i
}, 1e3);
});
this.audioContext.onEnded(() => {
console.log('播放结束');
this.status = 0
});
}
}
};
</script>
<style scoped>
.fui-audio {
width: 100%;
border: 1rpx solid #eeeeee;
padding: 0 30rpx 0 20rpx;
box-sizing: border-box;
position: relative;
overflow: hidden;
background: #fff;
}
.fui-audio .img {
width: 100rpx;
height: 100rpx;
background: #000;
}
.fui-audio .img image {
width: 100%;
height: 100%;
}
.fui-audio .name {
font-size: 26rpx;
color: #333;
}
.fui-audio .author {
font-size: 26rpx;
color: #666;
}
.fui-audio .time {
font-size: 24rpx;
color: #999;
}
.fui-audio .start {
border: 0;
padding: 0;
margin: 0;
font-size: 28rpx;
}
.progressBar {
height: 2rpx;
width: 0;
background: #333;
}
.fui-audio.style1 {
height: 86rpx;
line-height: 82rpx;
}
.fui-audio.style1 .img,.fui-audio.style2 .img {
display: none;
}
.fui-audio.style1 .name,.fui-audio.style2 .name {
float: left;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 300rpx;
}
.fui-audio.style1 .author,.fui-audio.style2 .author {
float: left;
margin-left: 12rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 200rpx;
}
.fui-audio.style1 .time,.fui-audio.style4 .time {
display: none;
}
.fui-audio.style1 .start {
position: absolute;
top: 0rpx;
right: 40rpx;
width: 40rpx;
height: 40rpx;
color: #000;
}
.fui-audio.style1 .progress {
position: absolute;
bottom: 0;
left: 0;
right: 0;
}
.fui-audio.style2 {
height: 86rpx;
line-height: 82rpx;
}
.fui-audio.style2 .img {
display: none;
}
.fui-audio.style2 .time {
position: absolute;
top: 0;
right: 30rpx;
}
.fui-audio.style2 .name {
margin-left: 70rpx;
}
.fui-audio.style2 .start {
position: absolute;
top: 0rpx;
left: 30rpx;
width: 30rpx;
height: 30rpx;
color: #000;
}
.fui-audio.style2 .progress,.fui-audio.style3 .progress {
display: none;
}
.fui-audio.style3 {
padding: 8rpx;
}
.fui-audio.style3 .start {
position: absolute;
top: 30rpx;
left: 28rpx;
width: 56rpx;
height: 56rpx;
color: #fff;
/* border: 2rpx solid #fff; */
border-radius: 50%;
text-indent: 18rpx;
line-height: 56rpx;
}
.fui-audio.style3 .img,.fui-audio.style4 .img {
float: left;
margin-right: 20rpx;
}
.fui-audio.style3 .content {
width: 468rpx;
}
.fui-audio.style3 .content,.fui-audio.style4 .content {
float: left;
height: 100rpx;
display: flex;
flex-direction: column;
justify-content: center;
}
.fui-audio.style3 .content .name {
height: 40rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.fui-audio.style3 .content .author {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.fui-audio.style3 .time {
position: absolute;
top: 40rpx;
right: 30rpx;
}
.fui-audio.style4 {
padding: 10rpx;
}
.fui-audio.style4 .content {
padding-bottom: 18rpx;
height: 82rpx;
width: 500rpx;
}
.fui-audio.style4 .start {
position: absolute;
top: 32rpx;
right: 30rpx;
width: 30rpx;
height: 30rpx;
color: #000;
}
.fui-audio.style4 .name {
line-height: 1.2;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.fui-audio.style4 .author {
line-height: 1.2;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.fui-audio.style4 .progress {
background: #f5f5f5;
height: 4rpx;
position: absolute;
bottom: 24rpx;
left: 130rpx;
right: 30rpx;
border-radius: 2rpx;
overflow: hidden;
}
.fui-audio.style4 .progressBar {
height: 4rpx;
}
.diy-audio>>>.uni-video-container {
background-color: transparent;
}
</style>

View File

@@ -1,723 +0,0 @@
<template>
<x-skeleton data-component-name="diy-bargain" :type="skeletonType" :loading="loading" :configs="skeletonConfig">
<view class="diy-bargain" :class="[value.template, value.style]" :style="warpCss">
<!-- 商品头部 -->
<view v-if="value.titleStyle.isShow && list && list.length" :class="[value.titleStyle.style, 'bargain-head']" :style="{ backgroundImage: 'url(' + $util.img(value.titleStyle.backgroundImage) + '), linear-gradient(to right,' + value.titleStyle.bgColorStart + ',' + value.titleStyle.bgColorEnd + ')' }">
<view v-if="value.titleStyle.leftStyle == 'text'" class="left-text" :style="{ fontSize: value.titleStyle.fontSize * 2 + 'rpx', color: value.titleStyle.textColor, fontWeight: value.titleStyle.fontWeight ? 'bold' : '' }">
{{ value.titleStyle.leftText }}
</view>
<image v-else class="left-img" :src="$util.img(value.titleStyle.leftImg)" mode="heightFix"></image>
<view class="head-content" v-if="value.titleStyle.style == 'style-1'" :style="{ color: value.titleStyle.textColor }">低至0元免费拿</view>
<view class="head-right" :style="{ fontSize: value.titleStyle.moreFontSize * 2 + 'rpx', color: value.titleStyle.moreColor }" @click="$util.redirectTo('/pages_promotion/bargain/list')">
<text>{{ value.titleStyle.more }}</text>
<text class="iconfont icon-right"></text>
</view>
</view>
<!-- 商品列表 -->
<template v-if="value.template == 'row1-of1'">
<view class="item" v-for="(item, index) in list" :key="index" @click="toDetail(item)" :class="[value.ornament.type]" :style="goodsItemCss">
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }" :src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix" @error="imageError(index)"/>
</view>
<view class="content"
v-if="value.goodsNameStyle.control || value.priceStyle.mainControl || value.btnStyle.control">
<view v-if="value.goodsNameStyle.control" class="goods-name" :style="{ color: value.theme == 'diy' ? value.goodsNameStyle.color : '', fontWeight: value.goodsNameStyle.fontWeight ? 'bold' : '' }" :class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]">
{{ item.goods_name }}
</view>
<view class="progress" v-if="value.style == 'style-2'">
<view class="bg">
<view class="curr" :style="{ width: progress(item) * 2 + 'rpx' }">
<image class="progress-bar" mode="widthFix" :src="$util.img('public/uniapp/bargain/progress_bar_01.png')"/>
</view>
</view>
<view class="num" v-if="item.is_bargaining">
已砍
<text>{{ (item.price - item.curr_price).toFixed(2) }}</text>
仅差
<text>{{ item.curr_price }}</text>
</view>
<view class="num" v-else>
最低可砍至
<text>{{ item.floor_price }}</text>
</view>
</view>
<view class="progress" v-if="value.style == 'style-3'">
最低可砍至
<text class="num">{{ item.floor_price }}</text>
</view>
<view class="price-wrap">
<view class="discount-price" v-if="value.priceStyle.mainControl">
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">¥</text>
<text class="price price-style large" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">{{ item.price.split('.')[0] }}</text>
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">.{{ item.price.split('.')[1] }}</text>
</view>
<button v-if="value.btnStyle.control" :style="{
background: value.btnStyle.theme == 'diy' ? 'linear-gradient(to right,' + value.btnStyle.bgColorStart + ',' + value.btnStyle.bgColorEnd + ')' : '',
color: value.btnStyle.theme == 'diy' ? value.btnStyle.textColor : '',
borderRadius: value.btnStyle.aroundRadius * 2 + 'rpx'
}">
{{ item.is_bargaining ? '继续砍价' : value.btnStyle.text }}
</button>
</view>
</view>
</view>
</template>
<template v-if="value.template == 'horizontal-slide'">
<scroll-view v-if="value.slideMode == 'scroll'" class="scroll" :scroll-x="true" :show-scrollbar="false">
<view class="item" v-for="(item, index) in list" :key="index" @click="toDetail(item)" :class="[value.ornament.type]" :style="goodsItemCss">
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }" :src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix" @error="imageError(index)" :lazy-load="true"/>
<image class="bg" v-if="value.saleStyle.control && value.template == 'horizontal-slide' && value.style != 'style-2'" :src="$util.img('public/uniapp/bargain/bg.png')" mode="widthFix"/>
<view class="num" v-if="value.saleStyle.control && value.template == 'horizontal-slide' && value.style != 'style-2'" :style="{ color: value.theme == 'diy' ? value.saleStyle.color : '' }">
已砍{{ item.sale_num }}
</view>
</view>
<view :class="['content', { 'multi-content': value.nameLineMode == 'multiple' }]"
v-if="value.goodsNameStyle.control || value.priceStyle.mainControl || value.priceStyle.lineControl">
<view v-if="value.goodsNameStyle.control" class="goods-name" :style="{ color: value.theme == 'diy' ? value.goodsNameStyle.color : '', fontWeight: value.goodsNameStyle.fontWeight ? 'bold' : '' }" :class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]">
{{ item.goods_name }}
</view>
<view class="discount-price"
v-if="value.priceStyle.mainControl && value.template == 'horizontal-slide' && value.style != 'style-2'">
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">¥</text>
<text class="price price-style large" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">{{ item.floor_price.split('.')[0] }}</text>
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">{{ '.' + item.floor_price.split('.')[1] }}</text>
</view>
<view class="original-price price-font" v-if="value.priceStyle.lineControl" :style="{ color: value.theme == 'diy' ? value.priceStyle.lineColor : '' }">
¥{{ item.price }}
</view>
</view>
</view>
</scroll-view>
<swiper v-if="value.slideMode == 'slide'" :autoplay="false" class="swiper" :style="{ height: swiperHeight }">
<swiper-item v-for="(pageItem, pageIndex) in page" :key="pageIndex" :class="['swiper-item', (list.length && [list[pageIndex].length / 3] >= 1) && 'flex-between']">
<view class="item" v-for="(item, dataIndex) in list[pageIndex]" :key="dataIndex" @click="toDetail(item)" :class="[value.ornament.type]" :style="goodsItemCss">
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }" :src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix" @error="imageError(dataIndex)" :lazy-load="true"/>
<image class="bg" v-if="value.saleStyle.control && value.template == 'horizontal-slide' && value.style != 'style-2'" :src="$util.img('public/uniapp/bargain/bg.png')" mode="widthFix"/>
<view class="num" v-if="value.saleStyle.control && value.template == 'horizontal-slide' && value.style != 'style-2'" :style="{ color: value.theme == 'diy' ? value.saleStyle.color : '' }">
已砍{{ item.sale_num }}
</view>
</view>
<view :class="['content', { 'multi-content': value.nameLineMode == 'multiple' }]" v-if="value.goodsNameStyle.control || value.priceStyle.mainControl || value.priceStyle.lineControl">
<view v-if="value.goodsNameStyle.control" class="goods-name" :style="{ color: value.theme == 'diy' ? value.goodsNameStyle.color : '', fontWeight: value.goodsNameStyle.fontWeight ? 'bold' : '' }" :class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]">
{{ item.goods_name }}
</view>
<view class="discount-price" v-if="value.priceStyle.mainControl && value.template == 'horizontal-slide' && value.style != 'style-2'">
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">¥</text>
<text class="price price-style large" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">{{ item.floor_price.split('.')[0] }}</text>
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">{{ '.' + item.floor_price.split('.')[1] }}</text>
</view>
<view class="original-price price-font" v-if="value.priceStyle.lineControl" :style="{ color: value.theme == 'diy' ? value.priceStyle.lineColor : '' }">
¥{{ item.price }}
</view>
</view>
</view>
</swiper-item>
</swiper>
</template>
</view>
</x-skeleton>
</template>
<script>
export default {
name: 'diy-bargain',
props: {
value: {
type: Object
}
},
data() {
return {
list: [],
page: 1,
loading: true,
skeletonType: '',
skeletonConfig: {}
};
},
components: {},
async created() {
this.initSkeleton();
if (this.value.template == 'row1-of1' && this.value.style == 'style-2') await this.getDataing();
this.getData();
},
watch: {
// 组件刷新监听
componentRefresh: async function(nval) {
if (this.value.template == 'row1-of1' && this.value.style == 'style-2') await this.getDataing();
this.getData();
}
},
computed: {
warpCss() {
var obj = '';
if (this.value.componentBgColor) obj += 'background:' + this.value.componentBgColor + ';';
if (this.value.componentAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
}
return obj;
},
// 商品项样式
goodsItemCss() {
var obj = '';
obj += 'background-color:' + this.value.elementBgColor + ';';
if (this.value.elementAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
}
if (this.value.ornament.type == 'shadow') {
obj += 'box-shadow:' + '0 0 10rpx ' + this.value.ornament.color + ';';
}
if (this.value.ornament.type == 'stroke') {
obj += 'border:' + '2rpx solid ' + this.value.ornament.color + ';';
}
const screenWidth = uni.getSystemInfoSync().windowWidth;
if (this.value.template == 'horizontal-slide') {
var width = '';
if (this.value.slideMode == 'scroll' && this.value.goodsMarginType == 'diy') width = this.rpxUpPx(this
.value.goodsMarginNum * 2);
else width = [screenWidth - this.rpxUpPx(20) * 2 - this.rpxUpPx(200) * 3 - this.rpxUpPx(this.value
.margin.both * 2) * 2] / 6;
obj += 'margin-left:' + width + 'px;';
obj += 'margin-right:' + width + 'px;';
}
return obj;
},
swiperHeight() {
if (this.value.nameLineMode == 'multiple') {
if (this.value.ornament.type == 'shadow') return '420rpx';
else return '400rpx';
}
if (this.value.ornament.type == 'shadow') return '386rpx';
else return '378rpx';
}
},
methods: {
initSkeleton() {
if (this.value.template == 'row1-of1') {
// 单列 风格
this.skeletonType = 'list';
this.skeletonConfig = {
textRows: 2
};
} else if (this.value.template == 'horizontal-slide') {
// 横向滑动 风格
this.skeletonType = 'waterfall';
this.skeletonConfig = {
gridRows: 1,
gridColumns: 3,
headHeight: '200rpx',
textRows: 2,
textWidth: ['100%', '80%']
};
}
},
rpxUpPx(res) {
const screenWidth = uni.getSystemInfoSync().windowWidth;
var data = (screenWidth * parseInt(res)) / 750;
return Math.floor(data);
},
// 查找自己参与的砍价
async getDataing() {
var res = await this.$api.sendRequest({
url: '/bargain/api/goods/bargainingList',
data: {},
async: false
});
res.data &&
res.data.forEach((item, index) => {
item.is_bargaining = 1;
});
this.list = res.data || [];
this.loading = false;
},
// 查找可砍价的商品
getData() {
var data = {
num: this.value.count,
is_exclude_bargaining: 1
};
if (this.value.sources == 'diy') {
data.num = 0;
data.id_arr = this.value.goodsId.toString();
}
this.$api.sendRequest({
url: '/bargain/api/goods/lists',
data: data,
success: res => {
if (res.code == 0) {
if (this.value.template == 'row1-of1' && this.value.style == 'style-2') this.list =
this.list.concat(res.data).splice(0, this.value.count);
else this.list = res.data;
// 切屏滚动,每页显示固定数量
if (this.value.template == 'horizontal-slide' && this.value.slideMode == 'slide') {
let size = 3;
let temp = [];
this.page = Math.ceil(this.list.length / size);
for (var i = 0; i < this.page; i++) {
temp[i] = [];
for (var j = i * size; j < this.list.length; j++) {
if (temp[i].length == size) break;
temp[i].push(this.list[j]);
}
}
this.list = temp;
}
}
this.loading = false;
}
});
},
progress(data) {
// 214 表示当前进度条的宽度
let progress = (((parseFloat(data.price) - parseFloat(data.curr_price)) / parseFloat(data.price)) * 214)
.toFixed();
if (progress == 'NaN') {
progress = 0;
}
return progress;
},
toDetail(e) {
this.$util.redirectTo('/pages_promotion/bargain/detail', {
b_id: e.bargain_id
});
},
imageError(index) {
this.list[index].goods_image = this.$util.getDefaultImage().goods;
this.$forceUpdate();
}
}
};
</script>
<style lang="scss">
/deep/.uni-scroll-view ::-webkit-scrollbar {
/* 隐藏滚动条,但依旧具备可以滚动的功能 */
display: none;
width: 0;
height: 0;
color: transparent;
background: transparent;
}
/deep/::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
color: transparent;
background: transparent;
}
scroll-view ::-webkit-scrollbar {
width: 0;
height: 0;
background-color: transparent;
touch-action: none;
}
.diy-bargain {
overflow: hidden;
// 头部
.bargain-head {
&.style-1 {
display: flex;
justify-content: space-between;
align-items: center;
height: 88rpx;
box-sizing: border-box;
padding: 0 20rpx;
background-repeat: no-repeat;
background-size: cover;
margin-bottom: 20rpx;
border-radius: 18rpx 18rpx 0 0;
.left-img {
height: 40rpx;
width: 156rpx;
}
.head-content {
position: relative;
color: #fff;
font-size: $font-size-tag;
margin-right: auto;
margin-left: 20rpx;
line-height: 1;
&::after {
content: '';
position: absolute;
width: 2rpx;
height: 24rpx;
background-color: #fff;
top: 50%;
transform: translateY(-50%);
left: -12rpx;
}
}
.head-right {
display: flex;
align-items: center;
font-size: $font-size-sub;
color: #fff;
}
}
&.style-2 {
display: flex;
justify-content: space-between;
align-items: center;
height: 88rpx;
box-sizing: border-box;
padding: 0 20rpx;
background-repeat: no-repeat;
background-size: cover;
margin-bottom: 20rpx;
border-radius: 18rpx 18rpx 0 0;
.left-img {
height: 40rpx;
width: 156rpx;
}
.head-right {
display: flex;
align-items: center;
justify-content: center;
height: 36rpx;
background: linear-gradient(270deg, #ffbd5b 0%, #fd882e 100%);
border-radius: 24rpx;
padding: 2rpx;
text:nth-child(1) {
position: relative;
left: 6rpx;
transform: scale(0.9);
}
text:nth-child(2) {
padding: 6rpx 4rpx 4rpx;
background-color: #fff;
color: #ffbd5b;
border-radius: 50%;
transform: scale(0.6);
font-weight: bold;
line-height: 1;
}
}
}
}
// 商品列表
&.row1-of1 {
.item {
display: flex;
margin-bottom: 20rpx;
padding: 16rpx;
&.shadow {
margin: 8rpx 8rpx 20rpx 8rpx;
}
&:last-child {
margin-bottom: 0;
padding-bottom: 20rpx;
}
.img-wrap {
width: 200rpx;
height: 200rpx;
>image {
width: 200rpx;
}
}
.content {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 6rpx 0 6rpx 20rpx;
.goods-name {
&.multi-hidden {
line-height: 1.3;
}
}
.price-wrap {
display: flex;
justify-content: space-between;
align-items: center;
}
.discount-price {
white-space: nowrap;
font-weight: bold;
line-height: 1;
.unit {
font-size: $font-size-tag;
margin-right: 4rpx;
color: var(--price-color);
}
.price {
font-size: $font-size-toolbar;
color: var(--price-color);
}
}
button {
margin: 0;
padding: 0 20rpx;
color: var(--btn-text-color);
background-color: $base-color;
color: #fff;
min-width: 112rpx;
height: 52rpx;
line-height: 52rpx;
font-size: $font-size-tag;
font-weight: bold;
}
}
}
&.style-2 {
.discount-price {
position: relative;
}
.progress {
display: flex;
flex-direction: column;
margin-top: 10rpx;
margin-right: 16rpx;
.bg {
margin-left: 6rpx;
width: auto;
height: 20rpx;
border-radius: 20rpx;
background-color: #ffeadb;
position: relative;
&::after {
content: '';
width: 26rpx;
height: 26rpx;
border-radius: 50%;
background-color: #fa1a1a;
position: absolute;
top: 50%;
transform: translateY(-50%);
right: -18rpx;
}
.curr {
width: 0;
height: 20rpx;
border-radius: 20rpx;
background-color: #fa1a1a;
position: relative;
.progress-bar {
position: absolute;
right: -20rpx;
width: 30rpx;
height: 30rpx;
max-width: inherit !important;
max-height: inherit !important;
top: 50%;
transform: translateY(-50%);
z-index: 1;
}
}
}
.num {
font-size: $font-size-tag;
margin-top: 12rpx;
line-height: 1;
text {
color: #fa1a1a;
}
}
}
}
&.style-3 {
.progress {
display: flex;
color: $color-tip;
font-size: $font-size-sub;
.num {
color: var(--price-color);
}
}
.item {
.content {
justify-content: space-around;
}
.img-wrap {
overflow: hidden;
}
}
}
}
&.horizontal-slide {
.scroll {
width: calc(100% - 40rpx);
padding: 20rpx;
line-height: 1;
white-space: nowrap;
.item.shadow {
margin-bottom: 8rpx;
}
}
.flex-between {
justify-content: space-between;
}
.item {
display: inline-block;
width: 200rpx;
overflow: hidden;
box-sizing: border-box;
&:nth-child(3n + 3) {
width: 198rpx;
}
&.shadow {
margin-top: 8rpx;
}
.img-wrap {
width: 200rpx;
height: 200rpx;
position: relative;
overflow: hidden;
margin: 0 auto;
>image {
width: 100%;
}
.bg {
position: absolute;
width: 100%;
height: 60rpx;
bottom: 0;
left: 0;
z-index: 2;
}
.num {
width: 180rpx;
position: absolute;
bottom: 10rpx;
padding-left: 20rpx;
font-size: 20rpx;
line-height: 1;
color: #ffffff;
z-index: 3;
}
}
.content {
padding: 10rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
&.multi-content {
height: 160rpx;
box-sizing: border-box;
}
.goods-name {
line-height: 1.3;
&.multi-hidden {
white-space: break-spaces;
}
}
.discount-price {
white-space: nowrap;
margin-top: auto;
font-weight: bold;
line-height: 1;
.unit {
font-size: $font-size-tag;
margin-right: 4rpx;
color: var(--price-color);
}
.price {
font-size: $font-size-toolbar;
color: var(--price-color);
}
}
.original-price {
margin-top: 4rpx;
font-size: $font-size-tag;
color: $color-tip;
line-height: 1;
text-decoration: line-through;
}
}
}
.swiper {
padding: 20rpx;
width: 100%;
white-space: nowrap;
box-sizing: border-box;
.swiper-item {
display: flex;
align-items: center;
}
.item {
width: 200rpx;
box-sizing: border-box;
}
}
}
}
</style>

View File

@@ -1,293 +0,0 @@
<template>
<view data-component-name="diy-bottom-nav" v-if="tabBarList && tabBarList.list">
<view class="tab-bar" :style="{ backgroundColor: tabBarList.backgroundColor }">
<view class="tabbar-border"></view>
<view class="item" v-for="(item, index) in tabBarList.list" :key="item.id" @click="redirectTo(item.link)">
<view class="bd">
<block v-if="item.link.wap_url == '/pages/goods/cart'">
<view class="icon" v-if="tabBarList.type == 1 || tabBarList.type == 2" :animation="cartAnimation" id="tabbarCart">
<block v-if="verify(item.link)">
<image v-if="item.selected_icon_type == 'img'" :src="$util.img(item.selectedIconPath)" />
<diy-icon v-if="item.selected_icon_type == 'icon'" :icon="item.selectedIconPath" :value="item.selected_style ? item.selected_style : null"></diy-icon>
</block>
<block v-else>
<image v-if="item.icon_type == 'img'" :src="$util.img(item.iconPath)" />
<diy-icon v-if="item.icon_type == 'icon'" :icon="item.iconPath" :value="item.style ? item.style : null"></diy-icon>
</block>
<view class="cart-count-mark font-size-activity-tag"
:class="{ max: item.link.wap_url == '/pages/goods/cart' && cartNumber > 99 }"
:style="{ background: 'var(--price-color)' }" v-if="cartNumber > 0">
{{ cartNumber > 99 ? '99+' : cartNumber }}
</view>
</view>
</block>
<block v-else>
<view class="icon" v-if="tabBarList.type == 1 || tabBarList.type == 2">
<block v-if="verify(item.link)">
<image v-if="item.selected_icon_type == 'img'" :src="$util.img(item.selectedIconPath)" />
<diy-icon v-if="item.selected_icon_type == 'icon'" :icon="item.selectedIconPath"
:value="item.selected_style ? item.selected_style : null"></diy-icon>
</block>
<block v-else>
<image v-if="item.icon_type == 'img'" :src="$util.img(item.iconPath)" />
<diy-icon v-if="item.icon_type == 'icon'" :icon="item.iconPath" :value="item.style ? item.style : null"></diy-icon>
</block>
</view>
</block>
<view class="label" v-if="(tabBarList.type == 1 || tabBarList.type == 3) && tabBarList.theme == 'diy'" :style="{ color: verify(item.link) ? tabBarList.textHoverColor : tabBarList.textColor }">
{{ lang =='en-us'?item.en_text:item.text }}
</view>
<view class="label" v-if="(tabBarList.type == 1 || tabBarList.type == 3) && tabBarList.theme == 'default'" :style="{ color: verify(item.link) ? 'var(--base-color)' : '#333333' }">
{{ lang =='en-us'?item.en_text:item.text }}
</view>
</view>
</view>
</view>
<!-- 解决fixed定位后底部导航栏塌陷问题 -->
<view class="tab-bar-placeholder"></view>
</view>
</template>
<script>
export default {
name: 'diy-bottom-nav',
props: {
value: {
type: Object
},
name: {
type: String,
default: ''
}
},
data() {
return {
lang:uni.getStorageSync("lang"),
currentRoute: '', //当前页面路径
jumpFlag: true, //是否可以跳转,防止重复点击
cartAnimation: {}
};
},
mounted() {
let currentPage = getCurrentPages()[getCurrentPages().length - 1];
if (currentPage && currentPage.route) {
this.currentRoute = currentPage.route;
}
this.$nextTick(() => {
if (!this.$store.state.cartPosition) {
let query = uni.createSelectorQuery().in(this);
query.select('#tabbarCart')
.boundingClientRect(data => {
if (data) this.$store.commit('setCartPosition', data);
}).exec();
query.select('.tab-bar')
.boundingClientRect(data => {
if (data) this.$store.commit('setTabBarHeight', data.height +
'px');
}).exec();
}
});
},
computed: {
cartChange() {
return this.$store.state.cartChange;
}
},
watch: {
cartChange: function (nval, oval) {
if (nval > oval) {
let animation = uni.createAnimation({
duration: 200,
timingFunction: 'ease'
});
animation.scale(1.2).step();
this.cartAnimation = animation.export();
setTimeout(() => {
animation.scale(1).step();
this.cartAnimation = animation.export();
}, 300);
}
}
},
methods: {
redirectTo(link) {
this.$emit('callback');
this.$util.diyRedirectTo(link);
},
verify(link) {
if (link == null || link == '' || !link.wap_url) return false;
if (this.name) {
var url = this.currentRoute + '?name=' + this.name;
} else {
var url = this.currentRoute;
}
// 首页特殊处理
if (link.wap_url == '/pages/index/index' && this.name == 'DIY_VIEW_INDEX') {
return true;
} else if (url && link.wap_url.indexOf(url) != -1) {
return true;
}
return false;
}
}
};
</script>
<style lang="scss">
.placeholder {
height: 112rpx;
&.bluge {
height: 180rpx;
}
}
.safe-area {
padding-bottom: 0;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
.tab-bar {
background-color: #fff;
box-sizing: border-box;
position: fixed;
left: 0;
bottom: 0;
width: 100%;
z-index: 998;
display: flex;
border-top: 2rpx solid #f5f5f5;
padding-bottom: 0;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
.tabbar-border {
background-color: rgba(255, 255, 255, 0.329412);
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 2rpx;
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
}
.item {
display: flex;
align-items: center;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
flex: 1;
flex-direction: column;
padding-bottom: 10rpx;
box-sizing: border-box;
.bd {
position: relative;
height: 100rpx;
flex-direction: column;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
.icon {
position: relative;
display: inline-block;
margin-top: 10rpx;
width: 40rpx;
height: 40rpx;
font-size: 40rpx;
line-height: 40rpx;
image {
width: 100%;
height: 100%;
}
>view {
height: inherit;
display: flex;
align-items: center;
}
.bar-icon {
font-size: 42rpx;
}
}
.label {
position: relative;
text-align: center;
font-size: 24rpx;
line-height: 1;
margin-top: 12rpx;
}
}
&.bulge {
.bd {
position: relative;
height: 100rpx;
flex-direction: column;
text-align: center;
.icon {
margin-top: -60rpx;
margin-bottom: 4rpx;
border-radius: 50%;
width: 100rpx;
height: 102rpx;
padding: 10rpx;
border-top: 2rpx solid #f5f5f5;
background-color: #fff;
box-sizing: border-box;
image {
width: 100%;
height: 100%;
border-radius: 50%;
}
}
.label {
position: relative;
text-align: center;
font-size: 24rpx;
height: 40rpx;
line-height: 40rpx;
}
}
}
.cart-count-mark {
position: absolute;
top: -8rpx;
right: -18rpx;
width: 24rpx;
height: 24rpx !important;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
padding: 6rpx;
border-radius: 50%;
z-index: 99;
&.max {
width: 40rpx;
border-radius: 24rpx;
right: -28rpx;
}
}
}
}
.tab-bar-placeholder {
padding-bottom: calc(constant(safe-area-inset-bottom) + 112rpx);
padding-bottom: calc(env(safe-area-inset-bottom) + 112rpx);
}
</style>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,114 +0,0 @@
<template>
<view data-component-name="diy-float-btn" class="float-btn" :class="{ left_top: value.bottomPosition == 1, right_top: value.bottomPosition == 2, left_bottom: value.bottomPosition == 3, right_bottom: value.bottomPosition == 4 }" :style="style">
<block v-for="(item, index) in value.list" :key="index">
<view class="button-box" @click="$util.diyRedirectTo(item.link)" :style="{ width: value.imageSize + 'px', height: value.imageSize + 'px', fontSize: value.imageSize + 'px' }">
<image v-if="!item.iconType || item.iconType == 'img'" :src="$util.img(item.imageUrl)" mode="aspectFit" :show-menu-by-longpress="true"/>
<diy-icon v-else-if="item.iconType && item.iconType == 'icon'" :icon="item.icon"
:value="item.style ? item.style : null"></diy-icon>
</view>
</block>
</view>
</template>
<script>
// 获取系统状态栏的高度
let systemInfo = uni.getSystemInfoSync();
export default {
name: 'diy-float-btn',
props: {
value: {
type: Object,
default: () => {
return {};
}
}
},
data() {
return {
navHeight: 0,
statusBarHeight: systemInfo.statusBarHeight
};
},
created() {},
watch: {
// 组件刷新监听
componentRefresh: function(nval) {}
},
components: {},
methods: {},
computed: {
style() {
let style = {},
height = 54;
// #ifdef MP
height = systemInfo.platform == 'ios' ? 54 : 58;
// #endif
switch (parseInt(this.value.bottomPosition)) {
case 1:
style.top = (this.navHeight + this.statusBarHeight + parseInt(this.value.btnBottom)) * 2 + 'rpx';
break;
case 2:
style.top = (this.navHeight + this.statusBarHeight + parseInt(this.value.btnBottom)) * 2 + 'rpx';
break;
case 3:
style.bottom = (100 + parseInt(this.value.btnBottom)) * 2 + 'rpx';
break;
case 4:
style.bottom = (100 + parseInt(this.value.btnBottom)) * 2 + 'rpx';
break;
}
return this.$util.objToStyle(style);
}
}
};
</script>
<style lang="scss">
.float-btn {
position: fixed;
bottom: 20%;
right: 40rpx;
z-index: 990;
&.left_top {
top: 100rpx;
left: 30rpx;
}
&.right_top {
top: 100rpx;
right: 30rpx;
}
&.left_bottom {
bottom: 160rpx;
left: 30rpx;
padding-bottom: constant(safe-area-inset-bottom);
/*兼容 IOS<11.2*/
padding-bottom: env(safe-area-inset-bottom);
/*兼容 IOS>11.2*/
}
&.right_bottom {
bottom: 160rpx;
right: 30rpx;
padding-bottom: constant(safe-area-inset-bottom);
/*兼容 IOS<11.2*/
padding-bottom: env(safe-area-inset-bottom);
/*兼容 IOS>11.2*/
}
.button-box {
margin-bottom: 20rpx;
&:last-child {
margin-bottom: 0;
}
image {
width: 100%;
height: 100%;
}
}
}
</style>

View File

@@ -1,29 +0,0 @@
<template>
<!-- #ifdef MP -->
<view data-component-name="diy-follow-official-account" v-if="value.isShow">
<official-account></official-account>
</view>
<!--#endif -->
</template>
<script>
// 关注公众号
export default {
name: 'diy-follow-official-account',
props: {
value: {
type: Object
}
},
data() {
return {};
},
watch: {
// 组件刷新监听
componentRefresh: function(nval) {}
},
methods: {}
};
</script>
<style></style>

View File

@@ -1,110 +0,0 @@
<template>
<view data-component-name="diy-form" class="diy-from" :style="style">
<view class="fui-cell-group">
<view class="fui-cell ">
<view class="fui-cell-label ">您的姓名</view>
<view class="fui-cell-info">
<input v-model="Form.realname" class="fui-input" placeholder="请输入您的姓名" value=""></input>
</view>
</view>
<view class="fui-cell ">
<view class="fui-cell-label">手机号码</view>
<view class="fui-cell-info">
<input v-model="Form.mobile" class="fui-input" maxlength="11" placeholder="请输入您的手机号" type="number" ></input>
</view>
</view>
<view class="fui-cell ">
<view class="fui-cell-label">您的邮箱</view>
<view class="fui-cell-info">
<input v-model="Form.mailbox" class="fui-input" placeholder="请输入您的邮箱" type="text" ></input>
</view>
</view>
<view class="fui-cell">
<view class="fui-cell-label">所在城市</view>
<view class="fui-cell-info">
<input v-model="Form.citys" class="fui-input" placeholder="请输入您的所在地" value=""></input>
</view>
</view>
<view class="fui-cell">
<view class="fui-cell-label">备注</view>
<view class="fui-cell-info">
<input v-model="Form.remark" class="fui-input" placeholder="请输入备注" value=""></input>
</view>
</view>
</view>
<view @click="submitform" class="fui-btn btn-danger block mtop">提交信息</view>
</view>
</template>
<script>
export default {
name: 'diy-from',
props: {
value: {
type: Object
}
},
data() {
return {
loading: true,
markers:[],
Form:{
realname:'',
mobile:'',
mailbox:'',
citys:'',
remark:'',
}
};
},
created() {
},
watch: {
// 组件刷新监听
componentRefresh: function(nval) {
// this.getDataList();
}
},
computed: {
markerst(){
return [{
id:1,
latitude:this.value.list[0].lat,
longitude:this.value.list[0].lng
}]
},
style() {
var css = '';
css += 'background-color:' + this.value.contentBgColor + ';';
if (this.value.elementAngle == 'round') {
css += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
css += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
css += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
css += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
}
return css;
}
},
methods: {
submitform(){
this.$api.sendRequest({
url: '/api/member/information',
data: this.Form,
success: res => {
this.$util.showToast({
title: res.message
});
}
});
}
}
};
</script>
<style lang="scss">
.diy-from{
background: #fff;
padding-bottom: 20rpx;
border-radius: 10rpx;
}
</style>

View File

@@ -1,167 +0,0 @@
<template>
<x-skeleton data-component-name="diy-goods-brand" type="waterfall" :loading="loading" :configs="skeletonConfig">
<view :class="['brand-wrap', value.ornament.type]" :style="warpCss">
<view :class="[value.style]">
<view class="title-wrap" v-show="value.title" :style="{ color: value.textColor, fontWeight: value.fontWeight ? 'bold' : '' }">{{ value.title }}
</view>
<view class="ul-wrap">
<view class="li-item" v-for="(item, index) in list" :key="index">
<image class="brand-pic" :src="$util.img(item.image_url)" mode="aspectFit" @click="handlerClick(item)" @tap="handlerClick(item)" @error="imgError(index)" :style="itemCss"/>
</view>
</view>
</view>
</view>
</x-skeleton>
</template>
<script>
// 商品品牌
import uniGrid from '@/components/uni-grid/uni-grid.vue';
import uniGridItem from '@/components/uni-grid-item/uni-grid-item.vue';
import DiyMinx from './minx.js'
export default {
name: 'diy-goods-brand',
props: {
value: {
type: Object
}
},
components: {
uniGrid,
uniGridItem
},
data() {
return {
list: [],
loading: true,
skeletonConfig: {
gridRows: 2,
gridColumns: 4,
gridRowsGap: '20rpx',
headWidth: '120rpx',
headHeight: '120rpx',
textShow: false
}
};
},
created() {
this.getBrandList();
},
mixins: [DiyMinx],
watch: {
// 组件刷新监听
componentRefresh: function(nval) {
this.getBrandList();
}
},
computed: {
warpCss() {
var obj = '';
obj += 'background-color:' + this.value.componentBgColor + ';';
if (this.value.componentAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
}
if (this.value.ornament.type == 'shadow') {
obj += 'box-shadow:' + '0 0 10rpx ' + this.value.ornament.color;
}
if (this.value.ornament.type == 'stroke') {
obj += 'border:' + '2rpx solid ' + this.value.ornament.color;
}
return obj;
},
// 子项样式
itemCss() {
var obj = '';
if (this.value.elementAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
}
return obj;
}
},
methods: {
getBrandList() {
var data = {
page: 1,
page_size: this.value.count
};
if (this.value.sources == 'diy') {
data.page_size = 0;
data.brand_id_arr = this.value.brandIds.toString();
}
this.$api.sendRequest({
url: '/api/goodsbrand/page',
data: data,
success: res => {
if (res.code == 0 && res.data) {
let data = res.data;
this.list = data.list;
}
this.loading = false;
}
});
},
toDetail(item) {
this.$util.redirectTo('/pages/goods/list', {
brand_id: item.brand_id
});
},
imgError(index) {
if (this.list[index]) this.list[index].image_url = this.$util.getDefaultImage().goods;
},
async handlerClick(item) {
await this.__$emitEvent({eventName: 'goods-brand-tap', data: item, promiseCallback: (event, handler, awaitedResult) => {
if (!awaitedResult) return;
this.toDetail(item);
}})
},
}
};
</script>
<style lang="scss">
.brand-wrap {
&.shadow {
margin-left: 8rpx;
margin-right: 8rpx;
margin-top: 8rpx;
margin-bottom: 8rpx;
}
.style-1 {
.title-wrap {
text-align: center;
padding: 20rpx 0 10rpx;
}
.ul-wrap {
display: flex;
flex-wrap: wrap;
padding: 20rpx;
.li-item {
display: flex;
align-items: center;
justify-content: center;
width: calc(100% / 4 - 20rpx) !important;
height: 124rpx;
margin: 10rpx;
background-color: #fff;
.brand-pic {
width: 100%;
height: 100%;
}
}
}
}
}
</style>

File diff suppressed because it is too large Load Diff

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