21 changed files with 4742 additions and 939 deletions
File diff suppressed because it is too large
@ -0,0 +1,236 @@ |
|||
<template> |
|||
<view class="session-page"> |
|||
<view class="header" :style="{ paddingTop: statusBarTop + 'px' }"> |
|||
<view class="back" @tap="back"><uni-icons type="left" size="22" color="#15362f"></uni-icons></view> |
|||
<view class="title">我的咨询</view> |
|||
<view class="subtitle">我咨询的和咨询我的都在这里</view> |
|||
</view> |
|||
|
|||
<scroll-view scroll-y class="session-list" @scrolltolower="loadMore"> |
|||
<view class="session-card" v-for="item in list" :key="item.id" @tap="openChat(item)"> |
|||
<image class="cover" :src="item.goodsCover || defaultCover" mode="aspectFill"></image> |
|||
<view class="main"> |
|||
<view class="top-row"> |
|||
<view class="goods-title">{{item.goodsTitle || '校园二手商品'}}</view> |
|||
<view class="role-tag" :class="{ seller: item.role == 'seller' }">{{item.roleText}}</view> |
|||
</view> |
|||
<view class="goods-meta"> |
|||
<text v-if="item.goodsPrice !== ''">¥{{item.goodsPrice}}</text> |
|||
<text>{{item.lastChatTime || item.firstConsultTime || item.createTime || ''}}</text> |
|||
</view> |
|||
<view class="session-desc">{{item.desc}}</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="empty" v-if="!list.length && !loading">暂无咨询记录</view> |
|||
<view class="load-more" v-if="loading">加载中...</view> |
|||
<view class="load-more" v-else-if="list.length && pageNum >= totalPages">没有更多了</view> |
|||
<view class="bottom-space"></view> |
|||
</scroll-view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import { |
|||
pageMyFishSessions, |
|||
getFishGoodsDetail |
|||
} from '@/common/fishMarketApi.js' |
|||
|
|||
export default { |
|||
data() { |
|||
return { |
|||
statusBarTop: 24, |
|||
list: [], |
|||
pageNum: 1, |
|||
pageSize: 10, |
|||
totalPages: 1, |
|||
loading: false, |
|||
selfId: String(uni.getStorageSync('id') || ''), |
|||
defaultCover: '/static/images/img/songshu.png' |
|||
} |
|||
}, |
|||
onLoad() { |
|||
if (uni.getMenuButtonBoundingClientRect) { |
|||
this.statusBarTop = uni.getMenuButtonBoundingClientRect().top || this.statusBarTop |
|||
} |
|||
this.load() |
|||
}, |
|||
methods: { |
|||
back() { |
|||
uni.navigateBack() |
|||
}, |
|||
loadMore() { |
|||
if (this.loading || this.pageNum >= this.totalPages) return |
|||
this.pageNum += 1 |
|||
this.load() |
|||
}, |
|||
load() { |
|||
if (this.loading) return |
|||
this.loading = true |
|||
pageMyFishSessions({ |
|||
pageNum: this.pageNum, |
|||
pageSize: this.pageSize |
|||
}).then(async page => { |
|||
const records = page && (page.records || page.content) ? (page.records || page.content) : [] |
|||
this.totalPages = Number(page && (page.pages || page.totalPages)) || 1 |
|||
const normalized = await this.normalizeSessions(records) |
|||
this.list = this.pageNum == 1 ? normalized : this.list.concat(normalized) |
|||
}).finally(() => { |
|||
this.loading = false |
|||
}) |
|||
}, |
|||
async normalizeSessions(records) { |
|||
const result = [] |
|||
for (let i = 0; i < records.length; i += 1) { |
|||
const session = records[i] |
|||
let goods = null |
|||
if (session.goodsId) { |
|||
goods = await getFishGoodsDetail(session.goodsId).catch(() => null) |
|||
} |
|||
const role = String(session.sellerId) == this.selfId ? 'seller' : 'buyer' |
|||
const imageList = goods && goods.imageList ? goods.imageList : [] |
|||
result.push({ |
|||
...session, |
|||
role, |
|||
roleText: role == 'seller' ? '咨询我的' : '我咨询的', |
|||
goodsTitle: goods ? goods.title : '', |
|||
goodsCover: goods ? (goods.coverImage || imageList[0] || '') : '', |
|||
goodsPrice: goods && goods.price !== undefined ? goods.price : '', |
|||
desc: role == 'seller' ? '买家正在咨询这件商品' : '你正在咨询这件商品' |
|||
}) |
|||
} |
|||
return result |
|||
}, |
|||
openChat(item) { |
|||
const sessionId = item.sessionId || item.id |
|||
if (!sessionId) return |
|||
uni.navigateTo({ |
|||
url: `/package2/IdleTrad/chat?sessionId=${sessionId}&goodsId=${item.goodsId || ''}` |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
page { |
|||
background: #f7f9f7; |
|||
} |
|||
|
|||
.session-page { |
|||
min-height: 100vh; |
|||
background: linear-gradient(180deg, #e8fbf3 0%, #f7f9f7 300rpx); |
|||
color: #15362f; |
|||
} |
|||
|
|||
.header { |
|||
padding: 0 28rpx 24rpx; |
|||
} |
|||
|
|||
.back { |
|||
width: 68rpx; |
|||
height: 68rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
|
|||
.title { |
|||
margin-top: 8rpx; |
|||
font-size: 42rpx; |
|||
font-weight: 900; |
|||
} |
|||
|
|||
.subtitle { |
|||
margin-top: 8rpx; |
|||
color: #71857f; |
|||
font-size: 23rpx; |
|||
} |
|||
|
|||
.session-list { |
|||
height: calc(100vh - 190rpx); |
|||
} |
|||
|
|||
.session-card { |
|||
margin: 18rpx 24rpx 0; |
|||
padding: 18rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
border-radius: 26rpx; |
|||
background: #fff; |
|||
box-shadow: 0 10rpx 26rpx rgba(20, 60, 48, 0.06); |
|||
} |
|||
|
|||
.cover { |
|||
width: 132rpx; |
|||
height: 132rpx; |
|||
border-radius: 20rpx; |
|||
background: #edf4f1; |
|||
flex-shrink: 0; |
|||
} |
|||
|
|||
.main { |
|||
flex: 1; |
|||
min-width: 0; |
|||
margin-left: 18rpx; |
|||
} |
|||
|
|||
.top-row { |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
|
|||
.goods-title { |
|||
flex: 1; |
|||
overflow: hidden; |
|||
font-size: 28rpx; |
|||
font-weight: 900; |
|||
text-overflow: ellipsis; |
|||
white-space: nowrap; |
|||
} |
|||
|
|||
.role-tag { |
|||
margin-left: 12rpx; |
|||
padding: 6rpx 14rpx; |
|||
border-radius: 18rpx; |
|||
background: #e8fbf3; |
|||
color: #18a878; |
|||
font-size: 20rpx; |
|||
font-weight: 900; |
|||
} |
|||
|
|||
.role-tag.seller { |
|||
background: #fff2df; |
|||
color: #df8618; |
|||
} |
|||
|
|||
.goods-meta { |
|||
margin-top: 10rpx; |
|||
display: flex; |
|||
gap: 18rpx; |
|||
color: #8a9994; |
|||
font-size: 21rpx; |
|||
} |
|||
|
|||
.goods-meta text:first-child { |
|||
color: #ef4c3f; |
|||
font-weight: 900; |
|||
} |
|||
|
|||
.session-desc { |
|||
margin-top: 10rpx; |
|||
color: #61736e; |
|||
font-size: 23rpx; |
|||
} |
|||
|
|||
.empty, |
|||
.load-more { |
|||
padding: 70rpx 0; |
|||
color: #9aa8a4; |
|||
text-align: center; |
|||
font-size: 24rpx; |
|||
} |
|||
|
|||
.bottom-space { |
|||
height: 80rpx; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,211 @@ |
|||
<template> |
|||
<view v-if="visible" class="auth-mask" @touchmove.stop.prevent="noop" @tap.stop="cancel"> |
|||
<view class="auth-card" @tap.stop> |
|||
<view class="auth-glow auth-glow-a"></view> |
|||
<view class="auth-glow auth-glow-b"></view> |
|||
<view class="auth-badge">{{ badge }}</view> |
|||
<view class="auth-title">{{ title }}</view> |
|||
<view class="auth-content">{{ content }}</view> |
|||
<view class="auth-steps" v-if="steps && steps.length"> |
|||
<view class="auth-step" v-for="(step, index) in steps" :key="step"> |
|||
<view class="step-dot">{{ index + 1 }}</view> |
|||
<view>{{ step }}</view> |
|||
</view> |
|||
</view> |
|||
<view class="auth-actions"> |
|||
<view class="auth-btn ghost" @tap="cancel">{{ cancelText }}</view> |
|||
<view class="auth-btn primary" @tap="confirm">{{ confirmText }}</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'IeAuthDialog', |
|||
data() { |
|||
return { |
|||
visible: false, |
|||
badge: 'i/e 安全认证', |
|||
title: '', |
|||
content: '', |
|||
confirmText: '继续', |
|||
cancelText: '暂不继续', |
|||
steps: [], |
|||
resolver: null |
|||
} |
|||
}, |
|||
created() { |
|||
uni.$on('ie-auth-dialog:show', this.show) |
|||
}, |
|||
beforeDestroy() { |
|||
uni.$off('ie-auth-dialog:show', this.show) |
|||
}, |
|||
methods: { |
|||
show(options = {}) { |
|||
if (typeof options.handled === 'function') { |
|||
options.handled() |
|||
} |
|||
this.badge = options.badge || 'i/e 安全认证' |
|||
this.title = options.title || '进阶实名认证' |
|||
this.content = options.content || '' |
|||
this.confirmText = options.confirmText || '继续' |
|||
this.cancelText = options.cancelText || '暂不继续' |
|||
this.steps = options.steps || [] |
|||
this.resolver = options.resolve || null |
|||
this.visible = true |
|||
}, |
|||
confirm() { |
|||
this.close(true) |
|||
}, |
|||
cancel() { |
|||
this.close(false) |
|||
}, |
|||
close(value) { |
|||
this.visible = false |
|||
if (this.resolver) { |
|||
this.resolver(value) |
|||
} |
|||
this.resolver = null |
|||
}, |
|||
noop() {} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.auth-mask { |
|||
position: fixed; |
|||
top: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
left: 0; |
|||
z-index: 99998; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 48rpx; |
|||
background: rgba(13, 18, 34, 0.48); |
|||
backdrop-filter: blur(18rpx); |
|||
} |
|||
|
|||
.auth-card { |
|||
position: relative; |
|||
width: 100%; |
|||
overflow: hidden; |
|||
padding: 46rpx 38rpx 34rpx; |
|||
border-radius: 46rpx; |
|||
background: linear-gradient(180deg, rgba(255,255,255,.98), rgba(245,249,255,.96)); |
|||
border: 1rpx solid rgba(255,255,255,.88); |
|||
box-shadow: 0 34rpx 90rpx rgba(28, 35, 74, .28); |
|||
} |
|||
|
|||
.auth-glow { |
|||
position: absolute; |
|||
border-radius: 999rpx; |
|||
filter: blur(4rpx); |
|||
opacity: .72; |
|||
} |
|||
|
|||
.auth-glow-a { |
|||
right: -80rpx; |
|||
top: -90rpx; |
|||
width: 230rpx; |
|||
height: 230rpx; |
|||
background: rgba(169,255,231,.78); |
|||
} |
|||
|
|||
.auth-glow-b { |
|||
left: -70rpx; |
|||
bottom: 120rpx; |
|||
width: 190rpx; |
|||
height: 190rpx; |
|||
background: rgba(255,126,179,.22); |
|||
} |
|||
|
|||
.auth-badge { |
|||
position: relative; |
|||
display: inline-flex; |
|||
padding: 10rpx 18rpx; |
|||
border-radius: 999rpx; |
|||
color: #6c55d8; |
|||
background: rgba(122,77,255,.09); |
|||
font-size: 22rpx; |
|||
font-weight: 800; |
|||
} |
|||
|
|||
.auth-title { |
|||
position: relative; |
|||
margin-top: 22rpx; |
|||
color: #11162a; |
|||
font-size: 42rpx; |
|||
line-height: 54rpx; |
|||
font-weight: 900; |
|||
} |
|||
|
|||
.auth-content { |
|||
position: relative; |
|||
margin-top: 16rpx; |
|||
color: rgba(17,22,42,.62); |
|||
font-size: 26rpx; |
|||
line-height: 42rpx; |
|||
} |
|||
|
|||
.auth-steps { |
|||
position: relative; |
|||
margin-top: 26rpx; |
|||
padding: 22rpx; |
|||
border-radius: 28rpx; |
|||
background: rgba(241,247,255,.82); |
|||
} |
|||
|
|||
.auth-step { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 16rpx; |
|||
min-height: 48rpx; |
|||
color: rgba(17,22,42,.72); |
|||
font-size: 24rpx; |
|||
font-weight: 700; |
|||
} |
|||
|
|||
.step-dot { |
|||
width: 34rpx; |
|||
height: 34rpx; |
|||
line-height: 34rpx; |
|||
text-align: center; |
|||
border-radius: 50%; |
|||
color: #11162a; |
|||
background: #a9ffe7; |
|||
font-size: 20rpx; |
|||
font-weight: 900; |
|||
} |
|||
|
|||
.auth-actions { |
|||
position: relative; |
|||
display: flex; |
|||
gap: 18rpx; |
|||
margin-top: 34rpx; |
|||
} |
|||
|
|||
.auth-btn { |
|||
flex: 1; |
|||
height: 84rpx; |
|||
line-height: 84rpx; |
|||
text-align: center; |
|||
border-radius: 999rpx; |
|||
font-size: 27rpx; |
|||
font-weight: 900; |
|||
} |
|||
|
|||
.auth-btn.ghost { |
|||
color: rgba(17,22,42,.56); |
|||
background: rgba(17,22,42,.06); |
|||
} |
|||
|
|||
.auth-btn.primary { |
|||
color: #11162a; |
|||
background: linear-gradient(135deg, #a9ffe7, #ffcf7b); |
|||
box-shadow: 0 16rpx 32rpx rgba(76, 209, 184, .22); |
|||
} |
|||
</style> |
|||
@ -0,0 +1,131 @@ |
|||
## 1.5.0(2024-11-01) |
|||
1. 更新弹窗中使用的示例 |
|||
## 1.4.9(2024-08-30) |
|||
1. 修复在app端和小程序端插入超链接可能会回显出部分特殊标识的bug |
|||
## 1.4.8(2024-08-16) |
|||
1. 有群友反馈,通过wangEditor添加的含有图片的富文本的宽高是在style内联样式中的,但是uni-editor不能解析img标签的内联宽高,因此我封装一个工具方法convertImgStylesToAttributes,处理一下富文本字符串即可,详见示例一 |
|||
## 1.4.7(2024-08-12) |
|||
1. 新增标题、字体、字体大小、字间距段前后距等工具子级悬浮工具栏 |
|||
2. 对于字体样式:可在组件的data中的fabTools.fontFamily中自行引入添加你本地的自定义字体(app中对于部分css自带的字体例如微软雅黑等不支持,因为app中不自带这些字体,你可能需要自行引入字体文件) |
|||
## 1.4.6(2024-08-08) |
|||
1. 修复字体工具栏无法隐藏的bug |
|||
## 1.4.5(2024-07-21) |
|||
1. 解决video图标冲突问题 |
|||
## 1.4.4(2024-07-20) |
|||
1. 新增视频插入功能 |
|||
2. 更新示例一(关于视频插入参考请见示例一) |
|||
## 1.4.3(2024-07-16) |
|||
1. 更新示例工程 |
|||
## 1.4.2(2024-07-16) |
|||
1. 更新示例工程 |
|||
## 1.4.1(2024-06-14) |
|||
1. 更新示例工程 |
|||
## 1.4.0(2024-05-31) |
|||
1. 更新示例工程 |
|||
## 1.3.9(2024-05-31) |
|||
1. 修复调色板无法正常选色的问题 |
|||
## 1.3.8(2024-05-13) |
|||
1. 更新示例三(微信小程序上使用setContents造成聚焦滚动的处理) |
|||
## 1.3.7(2024-05-10) |
|||
1. 修复添加超链接后,不触发input更新当前最新内容的bug |
|||
## 1.3.6(2024-05-09) |
|||
1. 文档迁移 |
|||
## 1.3.5(2024-05-09) |
|||
1. 所有事件携带编辑器id参数,以便循环时能区分处理 |
|||
2. 更新示例工程 |
|||
## 1.3.4(2024-05-08) |
|||
1. 更新示例工程 |
|||
2. 新增editorId参数 |
|||
## 1.3.3(2024-03-22) |
|||
1. 修复微信小程序长按无法粘贴的问题 |
|||
## 1.3.2(2024-03-14) |
|||
1. 更新了toolbar样式与配置,见文档 |
|||
2. 更新示例工程,媒体查询响应式写法 |
|||
3. 优化了只读模式效果,开启只读模式后,文章内容的超链接可正常点击并跳转 |
|||
## 1.3.1(2024-03-14) |
|||
1. 优化了只读功能,开启只读后自动隐藏工具栏 |
|||
2. 更新示例工程 |
|||
## 1.3.0(2024-03-07) |
|||
1. 新增addLink的emit事件 |
|||
## 1.2.9(2024-02-23) |
|||
1. 更新文档 |
|||
## 1.2.8(2024-02-23) |
|||
1. 新增了添加超链接的工具,toolbar中link字段,默认开启 |
|||
2. 优化了部分逻辑 |
|||
3. 更新文档、更新示例工程 |
|||
## 1.2.7(2024-02-23) |
|||
1. 更新文档,更新示例工程 |
|||
2. 添加toolbar中图标字体大小可配置项 |
|||
## 1.2.6(2024-02-22) |
|||
1. 添加导出工具按钮,可将当前已编辑的html导出至页面解析 |
|||
2. 超链接工具按钮正在尝试开发中(貌似目前官方不支持) |
|||
## 1.2.5(2024-02-19) |
|||
1. 更新示例工程(吸顶写法) |
|||
2. 完善调色板功能 |
|||
## 1.2.4(2024-02-18) |
|||
1. 修复工具栏颜色按钮底色动态切换问题 |
|||
## 1.2.3(2024-02-18) |
|||
1. 更新示例工程 |
|||
## 1.2.2(2024-02-18) |
|||
1. 删除log调试打印 |
|||
## 1.2.1(2024-02-18) |
|||
1. 修复了颜色图标不会动态切换的问题 |
|||
## 1.2.0(2024-02-18) |
|||
1. 修复选择颜色时会将所选文字删除的bug |
|||
## 1.1.9(2024-02-04) |
|||
1. 更新示例工程 |
|||
## 1.1.8(2024-02-04) |
|||
1. 文档修改 |
|||
## 1.1.7(2024-02-04) |
|||
1. 新增toolbar配置项,可自由配置工具栏工具列表 |
|||
2. 移除组件内原templates属性,默认初始化编辑器内容请看文档使用方式示例 |
|||
3. 更新文档 |
|||
## 1.1.6(2024-01-31) |
|||
1. 更好的兼容vue2了,修复在vue2下高度可能超出的问题 |
|||
2. 示例工程兼容vue2 |
|||
## 1.1.5(2024-01-30) |
|||
1. 修复工具栏字体按钮无效的问题 |
|||
## 1.1.4(2024-01-30) |
|||
1. 解决默认初始化内容时前缀空格或缩进无效的问题 |
|||
2. 解决点击工具栏高亮状态后输入内容时便失去高亮的bug |
|||
3. 更新示例工程 |
|||
## 1.1.3(2024-01-23) |
|||
1. 重写高度动态计算逻辑,现在对不同屏幕尺寸的适应性更强了 |
|||
## 1.1.2(2024-01-17) |
|||
1. 修复分割线会生成多条的问题 |
|||
## 1.1.1(2024-01-15) |
|||
1. 更新文档 |
|||
## 1.1.0(2024-01-15) |
|||
1. insertText方法在插入内容的时候会移动光标聚焦,导致焦点回滚到视口处 |
|||
2. 更新示例工程 |
|||
## 1.0.9(2024-01-04) |
|||
1. 更新文档 |
|||
## 1.0.8(2024-01-04) |
|||
1. 修复h5端官方cdn请求失败的问题,详见问答贴:https://ask.dcloud.net.cn/article/40900 |
|||
## 1.0.7(2024-01-03) |
|||
1. 移除v-bind="$attrs",该写法在微信小程序不支持 |
|||
## 1.0.6(2023-12-29) |
|||
1. 更新文档 |
|||
## 1.0.5(2023-12-29) |
|||
1. 更新了init方法,可以使用返回的editor实例尽情自定义 |
|||
2. 组件在<editor>上添加v-bind="$attrs"属性穿透,可以使用原editor参数,官方文档:https://uniapp.dcloud.net.cn/component/editor.html |
|||
## 1.0.4(2023-12-29) |
|||
1. 优化了切换文字和背景颜色是,可能会导致切换后不生效的问题 |
|||
2. 修复在部分设备上的微信小程序中可能会存在颜色版无法正常滑动的问题 |
|||
3. 更友好的交互体验:添加图标悬停字样描述、添加格式化文本弹窗确认 |
|||
4. 有插入视频的需求,暂时可能无法实现,官方给予的回复是:目前各端的eidtor组件都不能直接插入视频,编辑时可以采用视频封面占位,并在图片中保存视频信息,在预览时再还原为视频。 |
|||
## 1.0.3(2023-10-13) |
|||
1. 更新readme文档 |
|||
2. 更新调整组件示例项目,添加插件代码中部分注释 |
|||
## 1.0.2(2023-10-13) |
|||
1. 更新uni_modules规范,可一键导入组件 |
|||
2. 更新组件示例项目(包括使用uniCloud.uploadFile多选上传图片示例方法) |
|||
## 1.0.1(2023-10-12) |
|||
1. 修复小程序中自动聚焦滚动到富文本组件区域的bug |
|||
2. 略微调整了富文本上方toolbar工具栏中按钮的大小尺寸 |
|||
## 1.0.0(2023-9-19) |
|||
1. 新增字体与背景颜色板 |
|||
2. 可自定义预设内容模板 |
|||
3. 解决官方样例在小程序和app部分报错不兼容的问题 |
|||
4. 可配合云存储上传富文本中插入的图片 本质上是基于官方内置富文本editor组件改版封装,所以官方有的功能都有,官方能兼容的也都兼容 |
|||
|
|||
@ -0,0 +1,825 @@ |
|||
<template> |
|||
<view v-if="show" class="t-wrapper" @touchmove.stop.prevent="moveHandle"> |
|||
<view class="t-mask" :class="{ active: active }" @click.stop="close"></view> |
|||
<view class="t-box" :class="{ active: active }"> |
|||
<view class="t-header"> |
|||
<view class="t-header-button" @click="close">取消</view> |
|||
<view class="t-header-button" @click="confirm">确认</view> |
|||
</view> |
|||
<view class="t-color__box" :style="{ background: 'rgb(' + bgcolor.r + ',' + bgcolor.g + ',' + bgcolor.b + ')' }"> |
|||
<view |
|||
class="t-background boxs" |
|||
@touchstart="touchstart($event, 0)" |
|||
@touchmove="touchmove($event, 0)" |
|||
@touchend="touchend($event, 0)" |
|||
> |
|||
<view class="t-color-mask"></view> |
|||
<view class="t-pointer" :style="{ top: site[0].top - 8 + 'px', left: site[0].left - 8 + 'px' }"></view> |
|||
</view> |
|||
</view> |
|||
<view class="t-control__box"> |
|||
<view class="t-control__color"> |
|||
<view |
|||
class="t-control__color-content" |
|||
:style="{ background: 'rgba(' + rgba.r + ',' + rgba.g + ',' + rgba.b + ',' + rgba.a + ')' }" |
|||
></view> |
|||
</view> |
|||
<view class="t-control-box__item"> |
|||
<view |
|||
class="t-controller boxs" |
|||
@touchstart="touchstart($event, 1)" |
|||
@touchmove="touchmove($event, 1)" |
|||
@touchend="touchend($event, 1)" |
|||
> |
|||
<view class="t-hue"> |
|||
<view class="t-circle" :style="{ left: site[1].left - 12 + 'px' }"></view> |
|||
</view> |
|||
</view> |
|||
<view |
|||
class="t-controller boxs" |
|||
@touchstart="touchstart($event, 2)" |
|||
@touchmove="touchmove($event, 2)" |
|||
@touchend="touchend($event, 2)" |
|||
> |
|||
<view class="t-transparency"> |
|||
<view class="t-circle" :style="{ left: site[2].left - 12 + 'px' }"></view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<view class="t-result__box"> |
|||
<view v-if="mode" class="t-result__item"> |
|||
<view class="t-result__box-input">{{ hex }}</view> |
|||
<view class="t-result__box-text">HEX</view> |
|||
</view> |
|||
<template v-else> |
|||
<view class="t-result__item"> |
|||
<view class="t-result__box-input">{{ rgba.r }}</view> |
|||
<view class="t-result__box-text">R</view> |
|||
</view> |
|||
<view class="t-result__item"> |
|||
<view class="t-result__box-input">{{ rgba.g }}</view> |
|||
<view class="t-result__box-text">G</view> |
|||
</view> |
|||
<view class="t-result__item"> |
|||
<view class="t-result__box-input">{{ rgba.b }}</view> |
|||
<view class="t-result__box-text">B</view> |
|||
</view> |
|||
<view class="t-result__item"> |
|||
<view class="t-result__box-input">{{ rgba.a }}</view> |
|||
<view class="t-result__box-text">A</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<view class="t-result__item t-select" @click="select"> |
|||
<view class="t-result__box-input"> |
|||
<view>切换</view> |
|||
<view>模式</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<view class="t-alternative"> |
|||
<view class="t-alternative__item" v-for="(item, index) in colorList" :key="index"> |
|||
<view |
|||
class="t-alternative__item-content" |
|||
:style="{ background: 'rgba(' + item.r + ',' + item.g + ',' + item.b + ',' + item.a + ')' }" |
|||
@click="selectColor(item)" |
|||
></view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
props: { |
|||
color: { |
|||
type: Object, |
|||
default: () => { |
|||
return { |
|||
r: 0, |
|||
g: 0, |
|||
b: 0, |
|||
a: 0 |
|||
} |
|||
} |
|||
}, |
|||
spareColor: { |
|||
type: Array, |
|||
default() { |
|||
return [] |
|||
} |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
show: false, |
|||
active: false, |
|||
// rgba 颜色 |
|||
rgba: { |
|||
r: 0, |
|||
g: 0, |
|||
b: 0, |
|||
a: 1 |
|||
}, |
|||
// hsb 颜色 |
|||
hsb: { |
|||
h: 0, |
|||
s: 0, |
|||
b: 0 |
|||
}, |
|||
site: [ |
|||
{ |
|||
top: 0, |
|||
left: 0 |
|||
}, |
|||
{ |
|||
left: 0 |
|||
}, |
|||
{ |
|||
left: 0 |
|||
} |
|||
], |
|||
index: 0, |
|||
bgcolor: { |
|||
r: 255, |
|||
g: 0, |
|||
b: 0, |
|||
a: 1 |
|||
}, |
|||
hex: '#000000', |
|||
mode: true, |
|||
colorList: [ |
|||
{ |
|||
r: 244, |
|||
g: 67, |
|||
b: 54, |
|||
a: 1 |
|||
}, |
|||
{ |
|||
r: 233, |
|||
g: 30, |
|||
b: 99, |
|||
a: 1 |
|||
}, |
|||
{ |
|||
r: 156, |
|||
g: 39, |
|||
b: 176, |
|||
a: 1 |
|||
}, |
|||
{ |
|||
r: 103, |
|||
g: 58, |
|||
b: 183, |
|||
a: 1 |
|||
}, |
|||
{ |
|||
r: 63, |
|||
g: 81, |
|||
b: 181, |
|||
a: 1 |
|||
}, |
|||
{ |
|||
r: 33, |
|||
g: 150, |
|||
b: 243, |
|||
a: 1 |
|||
}, |
|||
{ |
|||
r: 3, |
|||
g: 169, |
|||
b: 244, |
|||
a: 1 |
|||
}, |
|||
{ |
|||
r: 0, |
|||
g: 188, |
|||
b: 212, |
|||
a: 1 |
|||
}, |
|||
{ |
|||
r: 0, |
|||
g: 150, |
|||
b: 136, |
|||
a: 1 |
|||
}, |
|||
{ |
|||
r: 76, |
|||
g: 175, |
|||
b: 80, |
|||
a: 1 |
|||
}, |
|||
{ |
|||
r: 139, |
|||
g: 195, |
|||
b: 74, |
|||
a: 1 |
|||
}, |
|||
{ |
|||
r: 205, |
|||
g: 220, |
|||
b: 57, |
|||
a: 1 |
|||
}, |
|||
{ |
|||
r: 255, |
|||
g: 235, |
|||
b: 59, |
|||
a: 1 |
|||
}, |
|||
{ |
|||
r: 255, |
|||
g: 193, |
|||
b: 7, |
|||
a: 1 |
|||
}, |
|||
{ |
|||
r: 255, |
|||
g: 152, |
|||
b: 0, |
|||
a: 1 |
|||
}, |
|||
{ |
|||
r: 255, |
|||
g: 87, |
|||
b: 34, |
|||
a: 1 |
|||
}, |
|||
{ |
|||
r: 121, |
|||
g: 85, |
|||
b: 72, |
|||
a: 1 |
|||
}, |
|||
{ |
|||
r: 158, |
|||
g: 158, |
|||
b: 158, |
|||
a: 1 |
|||
}, |
|||
{ |
|||
r: 0, |
|||
g: 0, |
|||
b: 0, |
|||
a: 0.5 |
|||
}, |
|||
{ |
|||
r: 0, |
|||
g: 0, |
|||
b: 0, |
|||
a: 0 |
|||
} |
|||
] |
|||
} |
|||
}, |
|||
created() { |
|||
this.ready() |
|||
}, |
|||
methods: { |
|||
ready() { |
|||
this.rgba = this.color |
|||
if (this.spareColor.length !== 0) { |
|||
this.colorList = this.spareColor |
|||
} |
|||
}, |
|||
/** |
|||
* 初始化 |
|||
*/ |
|||
init() { |
|||
// hsb 颜色 |
|||
this.hsb = this.rgbToHex(this.rgba) |
|||
// this.setColor(); |
|||
this.setValue(this.rgba) |
|||
}, |
|||
moveHandle() {}, |
|||
open() { |
|||
this.show = true |
|||
this.$nextTick(() => { |
|||
this.init() |
|||
setTimeout(() => { |
|||
this.active = true |
|||
setTimeout(() => { |
|||
this.getSelectorQuery() |
|||
}, 350) |
|||
}, 50) |
|||
}) |
|||
}, |
|||
close() { |
|||
this.active = false |
|||
this.$nextTick(() => { |
|||
setTimeout(() => { |
|||
this.show = false |
|||
}, 500) |
|||
}) |
|||
}, |
|||
confirm() { |
|||
this.close() |
|||
this.$emit('confirm', { |
|||
rgba: this.rgba, |
|||
hex: this.hex |
|||
}) |
|||
}, |
|||
// 选择模式 |
|||
select() { |
|||
this.mode = !this.mode |
|||
}, |
|||
// 常用颜色选择 |
|||
selectColor(item) { |
|||
this.setColorBySelect(item) |
|||
}, |
|||
touchstart(e, index) { |
|||
const { pageX, pageY, clientX, clientY } = e.touches[0] |
|||
// 部分机型可能没有pageX或clientX,因此此处需要做兼容 |
|||
this.moveX = clientX || pageX |
|||
this.moveY = clientY || pageY |
|||
this.setPosition(this.moveX, this.moveY, index) |
|||
}, |
|||
touchmove(e, index) { |
|||
const { pageX, pageY, clientX, clientY } = e.touches[0] |
|||
this.moveX = clientX || pageX |
|||
this.moveY = clientY || pageY |
|||
this.setPosition(this.moveX, this.moveY, index) |
|||
}, |
|||
touchend(e, index) {}, |
|||
/** |
|||
* 设置位置 |
|||
*/ |
|||
setPosition(x, y, index) { |
|||
this.index = index |
|||
const { top, left, width, height } = this.position[index] |
|||
// 设置最大最小值 |
|||
|
|||
this.site[index].left = Math.max(0, Math.min(parseInt(x - left), width)) |
|||
if (index === 0) { |
|||
this.site[index].top = Math.max(0, Math.min(parseInt(y - top), height)) |
|||
// 设置颜色 |
|||
this.hsb.s = parseInt((100 * this.site[index].left) / width) |
|||
this.hsb.b = parseInt(100 - (100 * this.site[index].top) / height) |
|||
this.setColor() |
|||
this.setValue(this.rgba) |
|||
} else { |
|||
this.setControl(index, this.site[index].left) |
|||
} |
|||
}, |
|||
/** |
|||
* 设置 rgb 颜色 |
|||
*/ |
|||
setColor() { |
|||
const rgb = this.HSBToRGB(this.hsb) |
|||
this.rgba.r = rgb.r |
|||
this.rgba.g = rgb.g |
|||
this.rgba.b = rgb.b |
|||
}, |
|||
/** |
|||
* 设置二进制颜色 |
|||
* @param {Object} rgb |
|||
*/ |
|||
setValue(rgb) { |
|||
this.hex = '#' + this.rgbToHex(rgb) |
|||
}, |
|||
setControl(index, x) { |
|||
const { top, left, width, height } = this.position[index] |
|||
|
|||
if (index === 1) { |
|||
this.hsb.h = parseInt((360 * x) / width) |
|||
this.bgcolor = this.HSBToRGB({ |
|||
h: this.hsb.h, |
|||
s: 100, |
|||
b: 100 |
|||
}) |
|||
this.setColor() |
|||
} else { |
|||
this.rgba.a = (x / width).toFixed(1) |
|||
} |
|||
this.setValue(this.rgba) |
|||
}, |
|||
/** |
|||
* rgb 转 二进制 hex |
|||
* @param {Object} rgb |
|||
*/ |
|||
rgbToHex(rgb) { |
|||
let hex = [rgb.r.toString(16), rgb.g.toString(16), rgb.b.toString(16)] |
|||
hex.map(function (str, i) { |
|||
if (str.length == 1) { |
|||
hex[i] = '0' + str |
|||
} |
|||
}) |
|||
return hex.join('') |
|||
}, |
|||
setColorBySelect(getrgb) { |
|||
const { r, g, b, a } = getrgb |
|||
let rgb = {} |
|||
rgb = { |
|||
r: r ? parseInt(r) : 0, |
|||
g: g ? parseInt(g) : 0, |
|||
b: b ? parseInt(b) : 0, |
|||
a: a ? a : 0 |
|||
} |
|||
this.rgba = rgb |
|||
this.hsb = this.rgbToHsb(rgb) |
|||
this.changeViewByHsb() |
|||
}, |
|||
changeViewByHsb() { |
|||
const [a, b, c] = this.position |
|||
this.site[0].left = parseInt((this.hsb.s * a.width) / 100) |
|||
this.site[0].top = parseInt(((100 - this.hsb.b) * a.height) / 100) |
|||
this.setColor(this.hsb.h) |
|||
this.setValue(this.rgba) |
|||
this.bgcolor = this.HSBToRGB({ |
|||
h: this.hsb.h, |
|||
s: 100, |
|||
b: 100 |
|||
}) |
|||
|
|||
this.site[1].left = (this.hsb.h / 360) * b.width |
|||
this.site[2].left = this.rgba.a * c.width |
|||
}, |
|||
/** |
|||
* hsb 转 rgb |
|||
* @param {Object} 颜色模式 H(hues)表示色相,S(saturation)表示饱和度,B(brightness)表示亮度 |
|||
*/ |
|||
HSBToRGB(hsb) { |
|||
let rgb = {} |
|||
let h = Math.round(hsb.h) |
|||
let s = Math.round((hsb.s * 255) / 100) |
|||
let v = Math.round((hsb.b * 255) / 100) |
|||
if (s == 0) { |
|||
rgb.r = rgb.g = rgb.b = v |
|||
} else { |
|||
let t1 = v |
|||
let t2 = ((255 - s) * v) / 255 |
|||
let t3 = ((t1 - t2) * (h % 60)) / 60 |
|||
if (h == 360) h = 0 |
|||
if (h < 60) { |
|||
rgb.r = t1 |
|||
rgb.b = t2 |
|||
rgb.g = t2 + t3 |
|||
} else if (h < 120) { |
|||
rgb.g = t1 |
|||
rgb.b = t2 |
|||
rgb.r = t1 - t3 |
|||
} else if (h < 180) { |
|||
rgb.g = t1 |
|||
rgb.r = t2 |
|||
rgb.b = t2 + t3 |
|||
} else if (h < 240) { |
|||
rgb.b = t1 |
|||
rgb.r = t2 |
|||
rgb.g = t1 - t3 |
|||
} else if (h < 300) { |
|||
rgb.b = t1 |
|||
rgb.g = t2 |
|||
rgb.r = t2 + t3 |
|||
} else if (h < 360) { |
|||
rgb.r = t1 |
|||
rgb.g = t2 |
|||
rgb.b = t1 - t3 |
|||
} else { |
|||
rgb.r = 0 |
|||
rgb.g = 0 |
|||
rgb.b = 0 |
|||
} |
|||
} |
|||
return { |
|||
r: Math.round(rgb.r), |
|||
g: Math.round(rgb.g), |
|||
b: Math.round(rgb.b) |
|||
} |
|||
}, |
|||
rgbToHsb(rgb) { |
|||
let hsb = { |
|||
h: 0, |
|||
s: 0, |
|||
b: 0 |
|||
} |
|||
let min = Math.min(rgb.r, rgb.g, rgb.b) |
|||
let max = Math.max(rgb.r, rgb.g, rgb.b) |
|||
let delta = max - min |
|||
hsb.b = max |
|||
hsb.s = max != 0 ? (255 * delta) / max : 0 |
|||
if (hsb.s != 0) { |
|||
if (rgb.r == max) hsb.h = (rgb.g - rgb.b) / delta |
|||
else if (rgb.g == max) hsb.h = 2 + (rgb.b - rgb.r) / delta |
|||
else hsb.h = 4 + (rgb.r - rgb.g) / delta |
|||
} else hsb.h = -1 |
|||
hsb.h *= 60 |
|||
if (hsb.h < 0) hsb.h = 0 |
|||
hsb.s *= 100 / 255 |
|||
hsb.b *= 100 / 255 |
|||
return hsb |
|||
}, |
|||
getSelectorQuery() { |
|||
const views = uni.createSelectorQuery().in(this) |
|||
views |
|||
.selectAll('.boxs') |
|||
.boundingClientRect((data) => { |
|||
if (!data || data.length === 0) { |
|||
setTimeout(() => this.getSelectorQuery(), 20) |
|||
return |
|||
} |
|||
this.position = data |
|||
// this.site[0].top = data[0].height; |
|||
// this.site[0].left = 0; |
|||
// this.site[1].left = data[1].width; |
|||
// this.site[2].left = data[2].width; |
|||
this.setColorBySelect(this.rgba) |
|||
}) |
|||
.exec() |
|||
}, |
|||
hex2Rgb(hexColor, alpha = 1) { |
|||
const color = hexColor.slice(1) |
|||
const r = parseInt(color.slice(0, 2), 16) |
|||
const g = parseInt(color.slice(2, 4), 16) |
|||
const b = parseInt(color.slice(4, 6), 16) |
|||
return { |
|||
r: r, |
|||
g: g, |
|||
b: b, |
|||
a: alpha |
|||
} |
|||
} |
|||
}, |
|||
watch: { |
|||
spareColor(newVal) { |
|||
this.colorList = newVal |
|||
}, |
|||
color(newVal) { |
|||
this.ready() |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
.t-wrapper { |
|||
position: fixed; |
|||
top: 0; |
|||
bottom: 0; |
|||
left: 0; |
|||
width: 100%; |
|||
box-sizing: border-box; |
|||
z-index: 9999; |
|||
} |
|||
|
|||
.t-box { |
|||
width: 100%; |
|||
position: absolute; |
|||
bottom: 0; |
|||
padding: 30upx 0; |
|||
padding-top: 0; |
|||
background: #fff; |
|||
transition: all 0.3s; |
|||
transform: translateY(100%); |
|||
} |
|||
|
|||
.t-box.active { |
|||
transform: translateY(0%); |
|||
} |
|||
|
|||
.t-header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
width: 100%; |
|||
height: 100upx; |
|||
border-bottom: 1px #eee solid; |
|||
box-shadow: 1px 0 2px rgba(0, 0, 0, 0.1); |
|||
background: #fff; |
|||
} |
|||
|
|||
.t-header-button { |
|||
display: flex; |
|||
align-items: center; |
|||
width: 150upx; |
|||
height: 100upx; |
|||
font-size: 30upx; |
|||
color: #666; |
|||
padding-left: 20upx; |
|||
} |
|||
|
|||
.t-header-button:last-child { |
|||
justify-content: flex-end; |
|||
padding-right: 20upx; |
|||
} |
|||
|
|||
.t-mask { |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
background: rgba(0, 0, 0, 0.6); |
|||
z-index: -1; |
|||
transition: all 0.3s; |
|||
opacity: 0; |
|||
} |
|||
|
|||
.t-mask.active { |
|||
opacity: 1; |
|||
} |
|||
|
|||
.t-color__box { |
|||
position: relative; |
|||
height: 400upx; |
|||
background: rgb(255, 0, 0); |
|||
overflow: hidden; |
|||
box-sizing: border-box; |
|||
margin: 0 20upx; |
|||
margin-top: 20upx; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
.t-background { |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
background: linear-gradient(to right, #fff, rgba(255, 255, 255, 0)); |
|||
} |
|||
|
|||
.t-color-mask { |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
width: 100%; |
|||
height: 400upx; |
|||
background: linear-gradient(to top, #000, rgba(0, 0, 0, 0)); |
|||
} |
|||
|
|||
.t-pointer { |
|||
position: absolute; |
|||
bottom: -8px; |
|||
left: -8px; |
|||
z-index: 2; |
|||
width: 15px; |
|||
height: 15px; |
|||
border: 1px #fff solid; |
|||
border-radius: 50%; |
|||
} |
|||
|
|||
.t-show-color { |
|||
width: 100upx; |
|||
height: 50upx; |
|||
} |
|||
|
|||
.t-control__box { |
|||
margin-top: 50upx; |
|||
width: 100%; |
|||
display: flex; |
|||
padding-left: 20upx; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
.t-control__color { |
|||
flex-shrink: 0; |
|||
width: 100upx; |
|||
height: 100upx; |
|||
border-radius: 50%; |
|||
background-color: #fff; |
|||
background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee), |
|||
linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee); |
|||
background-size: 36upx 36upx; |
|||
background-position: 0 0, 18upx 18upx; |
|||
border: 1px #eee solid; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
.t-control__color-content { |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
|
|||
.t-control-box__item { |
|||
display: flex; |
|||
flex-direction: column; |
|||
justify-content: space-between; |
|||
width: 100%; |
|||
padding: 0 30upx; |
|||
} |
|||
|
|||
.t-controller { |
|||
position: relative; |
|||
width: 100%; |
|||
height: 16px; |
|||
background-color: #fff; |
|||
background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee), |
|||
linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee); |
|||
background-size: 32upx 32upx; |
|||
background-position: 0 0, 16upx 16upx; |
|||
} |
|||
|
|||
.t-hue { |
|||
width: 100%; |
|||
height: 100%; |
|||
background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%); |
|||
} |
|||
|
|||
.t-transparency { |
|||
width: 100%; |
|||
height: 100%; |
|||
background: linear-gradient(to right, rgba(0, 0, 0, 0) 0%, rgb(0, 0, 0)); |
|||
} |
|||
|
|||
.t-circle { |
|||
position: absolute; |
|||
/* right: -10px; */ |
|||
top: -2px; |
|||
width: 20px; |
|||
height: 20px; |
|||
box-sizing: border-box; |
|||
border-radius: 50%; |
|||
background: #fff; |
|||
box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.1); |
|||
} |
|||
|
|||
.t-result__box { |
|||
margin-top: 20upx; |
|||
padding: 10upx; |
|||
width: 100%; |
|||
display: flex; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
.t-result__item { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 10upx; |
|||
width: 100%; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
.t-result__box-input { |
|||
padding: 10upx 0; |
|||
width: 100%; |
|||
font-size: 28upx; |
|||
box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.1); |
|||
color: #999; |
|||
text-align: center; |
|||
background: #fff; |
|||
} |
|||
|
|||
.t-result__box-text { |
|||
margin-top: 10upx; |
|||
font-size: 28upx; |
|||
line-height: 2; |
|||
} |
|||
|
|||
.t-select { |
|||
flex-shrink: 0; |
|||
width: 150upx; |
|||
padding: 0 30upx; |
|||
} |
|||
|
|||
.t-select .t-result__box-input { |
|||
border-radius: 10upx; |
|||
border: none; |
|||
color: #999; |
|||
box-shadow: 1px 1px 2px 1px rgba(0, 0, 0, 0.1); |
|||
background: #fff; |
|||
} |
|||
|
|||
.t-select .t-result__box-input:active { |
|||
box-shadow: 0px 0px 1px 0px rgba(0, 0, 0, 0.1); |
|||
} |
|||
|
|||
.t-alternative { |
|||
display: flex; |
|||
flex-wrap: wrap; |
|||
/* justify-content: space-between; */ |
|||
width: 100%; |
|||
padding-right: 10upx; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
.t-alternative__item { |
|||
margin-left: 12upx; |
|||
margin-top: 10upx; |
|||
width: 50upx; |
|||
height: 50upx; |
|||
border-radius: 10upx; |
|||
background-color: #fff; |
|||
background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee), |
|||
linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee); |
|||
background-size: 36upx 36upx; |
|||
background-position: 0 0, 18upx 18upx; |
|||
border: 1px #eee solid; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
.t-alternative__item-content { |
|||
width: 50upx; |
|||
height: 50upx; |
|||
background: rgba(255, 0, 0, 0.5); |
|||
} |
|||
|
|||
.t-alternative__item:active { |
|||
transition: all 0.3s; |
|||
transform: scale(1.1); |
|||
} |
|||
</style> |
|||
@ -0,0 +1,140 @@ |
|||
<template> |
|||
<view class="fab-tool"> |
|||
<view id="toolfab"> |
|||
<slot></slot> |
|||
</view> |
|||
<view class="fab-tool-content" :style="placementStyle" id="placementfab"> |
|||
<slot name="content" v-if="visible"></slot> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
props: { |
|||
visible: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
placement: { |
|||
type: String, |
|||
default: 'auto' // 'auto' | 'top-start' | 'top-center' | 'top-end' | 'bottom-start' | 'bottom-center' | 'bottom-end' |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
placementHeight: '0', |
|||
placementType: '' |
|||
} |
|||
}, |
|||
watch: { |
|||
visible(newVal) { |
|||
if (newVal) { |
|||
const { screenWidth } = uni.getSystemInfoSync() |
|||
|
|||
this.$nextTick(() => { |
|||
let placementWidth = 0 |
|||
uni |
|||
.createSelectorQuery() |
|||
.in(this) |
|||
.select('#placementfab') |
|||
.boundingClientRect((res) => { |
|||
this.placementHeight = -res.height + 'px' |
|||
placementWidth = res.width |
|||
}) |
|||
.exec() |
|||
// 开启自动模式后 |
|||
if (this.placement == 'auto') { |
|||
uni |
|||
.createSelectorQuery() |
|||
.in(this) |
|||
.select('#toolfab') |
|||
.boundingClientRect((res) => { |
|||
let leftRemain = res.left |
|||
let rightRemain = screenWidth - leftRemain |
|||
if (rightRemain > placementWidth) { |
|||
this.placementType = 'bottom-start' |
|||
} else if (leftRemain > placementWidth) { |
|||
this.placementType = 'bottom-end' |
|||
} else { |
|||
this.placementType = 'bottom-center' |
|||
} |
|||
}) |
|||
.exec() |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
}, |
|||
mounted() { |
|||
this.placementType = this.placement |
|||
}, |
|||
computed: { |
|||
placementStyle() { |
|||
let position = {} |
|||
switch (this.placementType) { |
|||
case 'top-start': |
|||
position = { |
|||
top: this.placementHeight, |
|||
left: 0 |
|||
} |
|||
break |
|||
case 'top-center': |
|||
position = { |
|||
top: this.placementHeight, |
|||
left: '50%', |
|||
transform: 'translateX(-50%)' |
|||
} |
|||
break |
|||
case 'top-end': |
|||
position = { |
|||
top: this.placementHeight, |
|||
right: 0 |
|||
} |
|||
break |
|||
case 'bottom-start': |
|||
position = { |
|||
bottom: this.placementHeight, |
|||
left: 0 |
|||
} |
|||
break |
|||
case 'bottom-center': |
|||
position = { |
|||
bottom: this.placementHeight, |
|||
left: '50%', |
|||
transform: 'translateX(-50%)' |
|||
} |
|||
break |
|||
case 'bottom-end': |
|||
position = { |
|||
bottom: this.placementHeight, |
|||
right: 0 |
|||
} |
|||
break |
|||
default: |
|||
break |
|||
} |
|||
return position |
|||
} |
|||
}, |
|||
methods: { |
|||
// |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
.fab-tool { |
|||
position: relative; |
|||
|
|||
.fab-tool-content { |
|||
position: absolute; |
|||
z-index: 999; |
|||
|
|||
background-color: #ffffff; |
|||
box-shadow: -2px -2px 4px rgba(0, 0, 0, 0.05), 2px 2px 4px rgba(0, 0, 0, 0.05); |
|||
border-radius: 12rpx; |
|||
box-sizing: border-box; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,152 @@ |
|||
<template> |
|||
<view class="link-edit-container" v-if="showPopup"> |
|||
<view class="link-edit"> |
|||
<view class="title">添加链接</view> |
|||
<view class="edit"> |
|||
<view class="description"> |
|||
链接描述: |
|||
<input v-model="descVal" type="text" class="input" placeholder="请输入链接描述" /> |
|||
</view> |
|||
<view class="address"> |
|||
链接地址: |
|||
<input v-model="addrVal" type="text" class="input" placeholder="请输入链接地址" /> |
|||
</view> |
|||
</view> |
|||
<view class="control"> |
|||
<view class="cancel" @click="close">取消</view> |
|||
<view class="confirm" @click="onConfirm">确认</view> |
|||
</view> |
|||
</view> |
|||
<view class="mask"></view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
showPopup: false, |
|||
descVal: '', |
|||
addrVal: '' |
|||
} |
|||
}, |
|||
methods: { |
|||
open() { |
|||
this.showPopup = true |
|||
this.$emit('open') |
|||
}, |
|||
close() { |
|||
this.showPopup = false |
|||
this.descVal = '' |
|||
this.addrVal = '' |
|||
this.$emit('close') |
|||
}, |
|||
onConfirm() { |
|||
if (!this.descVal) { |
|||
uni.showToast({ |
|||
title: '请输入链接描述', |
|||
icon: 'none' |
|||
}) |
|||
return |
|||
} |
|||
if (!this.addrVal) { |
|||
uni.showToast({ |
|||
title: '请输入链接地址', |
|||
icon: 'none' |
|||
}) |
|||
return |
|||
} |
|||
this.$emit('confirm', { |
|||
text: this.descVal, |
|||
href: this.addrVal |
|||
}) |
|||
this.close() |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
.link-edit-container { |
|||
.link-edit { |
|||
width: 80%; |
|||
position: absolute; |
|||
top: 50%; |
|||
left: 50%; |
|||
transform: translate(-50%, -50%); |
|||
background-color: #ffffff; |
|||
box-shadow: -2px -2px 4px rgba(0, 0, 0, 0.05), 2px 2px 4px rgba(0, 0, 0, 0.05); |
|||
border-radius: 12rpx; |
|||
box-sizing: border-box; |
|||
z-index: 999; |
|||
font-size: 14px; |
|||
|
|||
.title { |
|||
height: 80rpx; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
|
|||
.edit { |
|||
padding: 24rpx; |
|||
border-top: 1px solid #eeeeee; |
|||
border-bottom: 1px solid #eeeeee; |
|||
box-sizing: border-box; |
|||
|
|||
.input { |
|||
flex: 1; |
|||
padding: 4px; |
|||
font-size: 14px; |
|||
border: 1px solid #eeeeee; |
|||
border-radius: 8rpx; |
|||
|
|||
.uni-input-placeholder { |
|||
color: #dddddd; |
|||
} |
|||
} |
|||
|
|||
.description { |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
.address { |
|||
display: flex; |
|||
align-items: center; |
|||
margin-top: 24rpx; |
|||
} |
|||
} |
|||
|
|||
.control { |
|||
height: 80rpx; |
|||
display: flex; |
|||
cursor: pointer; |
|||
|
|||
.cancel { |
|||
flex: 1; |
|||
color: #dd524d; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
.confirm { |
|||
border-left: 1px solid #eeeeee; |
|||
flex: 1; |
|||
color: #007aff; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
} |
|||
} |
|||
.mask { |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
background-color: rgba(0, 0, 0, 0.05); |
|||
z-index: 998; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,828 @@ |
|||
<template> |
|||
<view class="sp-editor" :style="{ '--icon-size': iconSize, '--icon-columns': iconColumns }"> |
|||
<view class="sp-editor-toolbar" v-if="!readOnly" @tap="format"> |
|||
<!-- 标题栏 --> |
|||
<fab-tool v-if="toolbarList.includes('header')" :visible="curFab == 'header'"> |
|||
<view |
|||
:class="formats.header ? 'ql-active' : ''" |
|||
class="iconfont icon-header" |
|||
title="标题" |
|||
data-name="header" |
|||
@click.stop="fabTap('header')" |
|||
></view> |
|||
<template #content> |
|||
<view class="fab-tools" @click.stop="fabTapSub($event, 'header')"> |
|||
<view v-for="item in fabTools.header" :key="item.value"> |
|||
<view |
|||
v-if="toolbarList.includes(item.name)" |
|||
class="fab-sub" |
|||
:class="[formats.header === item.value ? 'ql-active' : '', item.icon ? 'iconfont' : '', item.icon]" |
|||
:title="item.title" |
|||
data-name="header" |
|||
:data-value="item.value" |
|||
></view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
</fab-tool> |
|||
<view |
|||
v-if="toolbarList.includes('bold')" |
|||
:class="formats.bold ? 'ql-active' : ''" |
|||
class="iconfont icon-zitijiacu" |
|||
title="加粗" |
|||
data-name="bold" |
|||
></view> |
|||
<view |
|||
v-if="toolbarList.includes('italic')" |
|||
:class="formats.italic ? 'ql-active' : ''" |
|||
class="iconfont icon-zitixieti" |
|||
title="斜体" |
|||
data-name="italic" |
|||
></view> |
|||
<view |
|||
v-if="toolbarList.includes('underline')" |
|||
:class="formats.underline ? 'ql-active' : ''" |
|||
class="iconfont icon-zitixiahuaxian" |
|||
title="下划线" |
|||
data-name="underline" |
|||
></view> |
|||
<view |
|||
v-if="toolbarList.includes('strike')" |
|||
:class="formats.strike ? 'ql-active' : ''" |
|||
class="iconfont icon-zitishanchuxian" |
|||
title="删除线" |
|||
data-name="strike" |
|||
></view> |
|||
<!-- 对齐方式 --> |
|||
<fab-tool v-if="toolbarList.includes('align')" :visible="curFab == 'align'"> |
|||
<view |
|||
:class="formats.align ? 'ql-active' : ''" |
|||
class="iconfont icon-zuoyouduiqi" |
|||
title="对齐方式" |
|||
data-name="align" |
|||
@click.stop="fabTap('align')" |
|||
></view> |
|||
<template #content> |
|||
<view class="fab-tools" @click.stop="fabTapSub($event, 'align')"> |
|||
<view v-for="item in fabTools.align" :key="item.value"> |
|||
<view |
|||
v-if="toolbarList.includes(item.name)" |
|||
class="fab-sub" |
|||
:class="[formats.align === item.value ? 'ql-active' : '', item.icon ? 'iconfont' : '', item.icon]" |
|||
:title="item.title" |
|||
data-name="align" |
|||
:data-value="item.value" |
|||
></view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
</fab-tool> |
|||
<!-- 行间距 --> |
|||
<fab-tool v-if="toolbarList.includes('lineHeight')" :visible="curFab == 'lineHeight'"> |
|||
<view |
|||
:class="formats.lineHeight ? 'ql-active' : ''" |
|||
class="iconfont icon-line-height" |
|||
title="行间距" |
|||
data-name="lineHeight" |
|||
@click.stop="fabTap('lineHeight')" |
|||
></view> |
|||
<template #content> |
|||
<view class="fab-tools" @click.stop="fabTapSub($event, 'lineHeight')"> |
|||
<view v-for="item in fabTools.lineHeight" :key="item.value"> |
|||
<view |
|||
class="fab-sub" |
|||
:class="[formats.lineHeight === item.value ? 'ql-active' : '', item.icon ? 'iconfont' : '', item.icon]" |
|||
:title="item.title" |
|||
data-name="lineHeight" |
|||
:data-value="item.value" |
|||
> |
|||
{{ item.name }} |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
</fab-tool> |
|||
<!-- 字间距 --> |
|||
<fab-tool v-if="toolbarList.includes('letterSpacing')" :visible="curFab == 'letterSpacing'"> |
|||
<view |
|||
:class="formats.letterSpacing ? 'ql-active' : ''" |
|||
class="iconfont icon-Character-Spacing" |
|||
title="字间距" |
|||
data-name="letterSpacing" |
|||
@click.stop="fabTap('letterSpacing')" |
|||
></view> |
|||
<template #content> |
|||
<view class="fab-tools" @click.stop="fabTapSub($event, 'letterSpacing')"> |
|||
<view v-for="item in fabTools.space" :key="item.value"> |
|||
<view |
|||
class="fab-sub" |
|||
:class="[ |
|||
formats.letterSpacing === item.value ? 'ql-active' : '', |
|||
item.icon ? 'iconfont' : '', |
|||
item.icon |
|||
]" |
|||
:title="item.title" |
|||
data-name="letterSpacing" |
|||
:data-value="item.value" |
|||
> |
|||
{{ item.name }} |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
</fab-tool> |
|||
<!-- 段前距 --> |
|||
<fab-tool v-if="toolbarList.includes('marginTop')" :visible="curFab == 'marginTop'"> |
|||
<view |
|||
:class="formats.marginTop ? 'ql-active' : ''" |
|||
class="iconfont icon-722bianjiqi_duanqianju" |
|||
title="段前距" |
|||
data-name="marginTop" |
|||
@click.stop="fabTap('marginTop')" |
|||
></view> |
|||
<template #content> |
|||
<view class="fab-tools" @click.stop="fabTapSub($event, 'marginTop')"> |
|||
<view v-for="item in fabTools.space" :key="item.value"> |
|||
<view |
|||
class="fab-sub" |
|||
:class="[formats.marginTop === item.value ? 'ql-active' : '', item.icon ? 'iconfont' : '', item.icon]" |
|||
:title="item.title" |
|||
data-name="marginTop" |
|||
:data-value="item.value" |
|||
> |
|||
{{ item.name }} |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
</fab-tool> |
|||
<!-- 段后距 --> |
|||
<fab-tool v-if="toolbarList.includes('marginBottom')" :visible="curFab == 'marginBottom'"> |
|||
<view |
|||
:class="formats.marginBottom ? 'ql-active' : ''" |
|||
class="iconfont icon-723bianjiqi_duanhouju" |
|||
title="段后距" |
|||
data-name="marginBottom" |
|||
@click.stop="fabTap('marginBottom')" |
|||
></view> |
|||
<template #content> |
|||
<view class="fab-tools" @click.stop="fabTapSub($event, 'marginBottom')"> |
|||
<view v-for="item in fabTools.space" :key="item.value"> |
|||
<view |
|||
class="fab-sub" |
|||
:class="[ |
|||
formats.marginBottom === item.value ? 'ql-active' : '', |
|||
item.icon ? 'iconfont' : '', |
|||
item.icon |
|||
]" |
|||
:title="item.title" |
|||
data-name="marginBottom" |
|||
:data-value="item.value" |
|||
> |
|||
{{ item.name }} |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
</fab-tool> |
|||
<!-- 字体栏 --> |
|||
<fab-tool v-if="toolbarList.includes('fontFamily')" :visible="curFab == 'fontFamily'"> |
|||
<view |
|||
:class="formats.fontFamily ? 'ql-active' : ''" |
|||
class="iconfont icon-font" |
|||
title="字体" |
|||
data-name="fontFamily" |
|||
@click.stop="fabTap('fontFamily')" |
|||
></view> |
|||
<template #content> |
|||
<view class="fab-tools" @click.stop="fabTapSub($event, 'fontFamily')"> |
|||
<view v-for="item in fabTools.fontFamily" :key="item.value"> |
|||
<view |
|||
class="fab-sub" |
|||
:class="[formats.fontFamily === item.value ? 'ql-active' : '', item.icon ? 'iconfont' : '', item.icon]" |
|||
:title="item.title" |
|||
data-name="fontFamily" |
|||
:data-value="item.value" |
|||
> |
|||
{{ item.name }} |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
</fab-tool> |
|||
<!-- 字体大小栏 --> |
|||
<fab-tool v-if="toolbarList.includes('fontSize')" :visible="curFab == 'fontSize'"> |
|||
<view |
|||
:class="formats.fontSize ? 'ql-active' : ''" |
|||
class="iconfont icon-fontsize" |
|||
title="字号" |
|||
data-name="fontSize" |
|||
@click.stop="fabTap('fontSize')" |
|||
></view> |
|||
<template #content> |
|||
<view class="fab-tools" @click.stop="fabTapSub($event, 'fontSize')"> |
|||
<view v-for="item in fabTools.fontSize" :key="item.value"> |
|||
<view |
|||
class="fab-sub" |
|||
:class="[formats.fontSize === item.value ? 'ql-active' : '', item.icon ? 'iconfont' : '', item.icon]" |
|||
:title="item.title" |
|||
data-name="fontSize" |
|||
:data-value="item.value" |
|||
> |
|||
{{ item.name }} |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
</fab-tool> |
|||
<view |
|||
v-if="toolbarList.includes('color')" |
|||
:style="{ color: formats.color ? textColor : 'initial' }" |
|||
class="iconfont icon-text_color" |
|||
title="文字颜色" |
|||
data-name="color" |
|||
:data-value="textColor" |
|||
></view> |
|||
<view |
|||
v-if="toolbarList.includes('backgroundColor')" |
|||
:style="{ color: formats.backgroundColor ? backgroundColor : 'initial' }" |
|||
class="iconfont icon-fontbgcolor" |
|||
title="背景颜色" |
|||
data-name="backgroundColor" |
|||
:data-value="backgroundColor" |
|||
></view> |
|||
<view v-if="toolbarList.includes('date')" class="iconfont icon-date" title="日期" @tap="insertDate"></view> |
|||
<view |
|||
v-if="toolbarList.includes('listCheck')" |
|||
class="iconfont icon--checklist" |
|||
title="待办" |
|||
data-name="list" |
|||
data-value="check" |
|||
></view> |
|||
<view |
|||
v-if="toolbarList.includes('listOrdered')" |
|||
:class="formats.list === 'ordered' ? 'ql-active' : ''" |
|||
class="iconfont icon-youxupailie" |
|||
title="有序列表" |
|||
data-name="list" |
|||
data-value="ordered" |
|||
></view> |
|||
<view |
|||
v-if="toolbarList.includes('listBullet')" |
|||
:class="formats.list === 'bullet' ? 'ql-active' : ''" |
|||
class="iconfont icon-wuxupailie" |
|||
title="无序列表" |
|||
data-name="list" |
|||
data-value="bullet" |
|||
></view> |
|||
<view |
|||
v-if="toolbarList.includes('divider')" |
|||
class="iconfont icon-fengexian" |
|||
title="分割线" |
|||
@click="insertDivider" |
|||
></view> |
|||
<view |
|||
v-if="toolbarList.includes('indentDec')" |
|||
class="iconfont icon-outdent" |
|||
title="减少缩进" |
|||
data-name="indent" |
|||
data-value="-1" |
|||
></view> |
|||
<view |
|||
v-if="toolbarList.includes('indentInc')" |
|||
class="iconfont icon-indent" |
|||
title="增加缩进" |
|||
data-name="indent" |
|||
data-value="+1" |
|||
></view> |
|||
<view |
|||
v-if="toolbarList.includes('scriptSub')" |
|||
:class="formats.script === 'sub' ? 'ql-active' : ''" |
|||
class="iconfont icon-zitixiabiao" |
|||
title="下标" |
|||
data-name="script" |
|||
data-value="sub" |
|||
></view> |
|||
<view |
|||
v-if="toolbarList.includes('scriptSuper')" |
|||
:class="formats.script === 'super' ? 'ql-active' : ''" |
|||
class="iconfont icon-zitishangbiao" |
|||
title="上标" |
|||
data-name="script" |
|||
data-value="super" |
|||
></view> |
|||
<view |
|||
v-if="toolbarList.includes('direction')" |
|||
:class="formats.direction === 'rtl' ? 'ql-active' : ''" |
|||
class="iconfont icon-direction-rtl" |
|||
title="文本方向" |
|||
data-name="direction" |
|||
data-value="rtl" |
|||
></view> |
|||
<view |
|||
v-if="toolbarList.includes('image')" |
|||
class="iconfont icon-charutupian" |
|||
title="图片" |
|||
@tap="insertImage" |
|||
></view> |
|||
<view v-if="toolbarList.includes('video')" class="iconfont icon-video" title="视频" @tap="insertVideo"></view> |
|||
<view |
|||
v-if="toolbarList.includes('link')" |
|||
class="iconfont icon-charulianjie" |
|||
title="超链接" |
|||
@tap="insertLink" |
|||
></view> |
|||
<view v-if="toolbarList.includes('undo')" class="iconfont icon-undo" title="撤销" @tap="undo"></view> |
|||
<view v-if="toolbarList.includes('redo')" class="iconfont icon-redo" title="重做" @tap="redo"></view> |
|||
<view |
|||
v-if="toolbarList.includes('removeFormat')" |
|||
class="iconfont icon-clearedformat" |
|||
title="清除格式" |
|||
@tap="removeFormat" |
|||
></view> |
|||
<view v-if="toolbarList.includes('clear')" class="iconfont icon-shanchu" title="清空" @tap="clear"></view> |
|||
<view v-if="toolbarList.includes('export')" class="iconfont icon-baocun" title="导出" @tap="exportHtml"></view> |
|||
</view> |
|||
|
|||
<!-- 自定义功能组件 --> |
|||
<!-- 调色板 --> |
|||
<color-picker |
|||
v-if="toolbarList.includes('color') || toolbarList.includes('backgroundColor')" |
|||
ref="colorPickerRef" |
|||
:color="defaultColor" |
|||
@confirm="confirmColor" |
|||
></color-picker> |
|||
<!-- 添加链接的操作弹窗 --> |
|||
<link-edit v-if="toolbarList.includes('link') && !readOnly" ref="linkEditRef" @confirm="confirmLink"></link-edit> |
|||
<view class="sp-editor-wrapper" @longpress="eLongpress"> |
|||
<editor |
|||
:id="editorId" |
|||
class="ql-editor editor-container" |
|||
:class="{ 'ql-image-overlay-none': readOnly }" |
|||
show-img-size |
|||
show-img-toolbar |
|||
show-img-resize |
|||
:placeholder="placeholder" |
|||
:read-only="readOnly" |
|||
@statuschange="onStatusChange" |
|||
@ready="onEditorReady" |
|||
@input="onEditorInput" |
|||
></editor> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import ColorPicker from './color-picker.vue' |
|||
import LinkEdit from './link-edit.vue' |
|||
import FabTool from './fab-tool.vue' |
|||
import { addLink, linkFlag } from '../../utils' |
|||
|
|||
export default { |
|||
components: { |
|||
ColorPicker, |
|||
LinkEdit, |
|||
FabTool |
|||
}, |
|||
props: { |
|||
// 编辑器id可传入,以便循环组件使用,防止id重复 |
|||
editorId: { |
|||
type: String, |
|||
default: 'editor' |
|||
}, |
|||
placeholder: { |
|||
type: String, |
|||
default: '写点什么吧 ~' |
|||
}, |
|||
// 是否只读 |
|||
readOnly: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
// 最大字数限制,-1不限 |
|||
maxlength: { |
|||
type: Number, |
|||
default: -1 |
|||
}, |
|||
// 工具栏配置 |
|||
toolbarConfig: { |
|||
type: Object, |
|||
default: () => { |
|||
return { |
|||
keys: [], // 要显示的工具,优先级最大 |
|||
excludeKeys: [], // 除这些指定的工具外,其他都显示 |
|||
iconSize: '18px', // 工具栏字体大小 |
|||
iconColumns: 10 // 工具栏列数 |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
watch: { |
|||
toolbarConfig: { |
|||
deep: true, |
|||
immediate: true, |
|||
handler(newToolbar) { |
|||
/** |
|||
* 若工具栏配置中keys存在,则以keys为准 |
|||
* 否则以excludeKeys向toolbarAllList中排查 |
|||
* 若keys与excludeKeys皆为空,则以toolbarAllList为准 |
|||
*/ |
|||
if (newToolbar.keys?.length > 0) { |
|||
this.toolbarList = newToolbar.keys |
|||
} else { |
|||
this.toolbarList = |
|||
newToolbar.excludeKeys?.length > 0 |
|||
? this.toolbarAllList.filter((item) => !newToolbar.excludeKeys.includes(item)) |
|||
: this.toolbarAllList |
|||
} |
|||
this.iconSize = newToolbar.iconSize || '18px' |
|||
this.iconColumns = newToolbar.iconColumns || 10 |
|||
} |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
formats: {}, |
|||
curFab: '', // 当前悬浮工具栏 |
|||
fabXY: {}, |
|||
textColor: '', |
|||
backgroundColor: '', |
|||
curColor: '', |
|||
defaultColor: { r: 0, g: 0, b: 0, a: 1 }, // 调色板默认颜色 |
|||
iconSize: '20px', // 工具栏图标字体大小 |
|||
iconColumns: 10, // 工具栏列数 |
|||
toolbarList: [], |
|||
toolbarAllList: [ |
|||
'header', // 标题 |
|||
'H1', // 一级标题 |
|||
'H2', // 二级标题 |
|||
'H3', // 三级标题 |
|||
'H4', // 四级标题 |
|||
'H5', // 五级标题 |
|||
'H6', // 六级标题 |
|||
'bold', // 加粗 |
|||
'italic', // 斜体 |
|||
'underline', // 下划线 |
|||
'strike', // 删除线 |
|||
'align', // 对齐方式 |
|||
'alignLeft', // 左对齐 |
|||
'alignCenter', // 居中对齐 |
|||
'alignRight', // 右对齐 |
|||
'alignJustify', // 两端对齐 |
|||
'fontSize', // 字号 |
|||
'image', // 图片 |
|||
], |
|||
fabTools: { |
|||
header: [ |
|||
{ title: '一级标题', name: 'H1', value: 1, icon: 'icon-format-header-1' }, |
|||
{ title: '二级标题', name: 'H2', value: 2, icon: 'icon-format-header-2' }, |
|||
{ title: '三级标题', name: 'H3', value: 3, icon: 'icon-format-header-3' }, |
|||
{ title: '四级标题', name: 'H4', value: 4, icon: 'icon-format-header-4' }, |
|||
{ title: '五级标题', name: 'H5', value: 5, icon: 'icon-format-header-5' }, |
|||
{ title: '六级标题', name: 'H6', value: 6, icon: 'icon-format-header-6' } |
|||
], |
|||
fontFamily: [ |
|||
{ title: '宋体', name: '宋', value: '宋体', icon: '' }, |
|||
{ title: '黑体', name: '黑', value: '黑体', icon: '' }, |
|||
{ title: '楷体', name: '楷', value: '楷体', icon: '' }, |
|||
{ title: '仿宋', name: '仿', value: '仿宋', icon: '' }, |
|||
{ title: '华文隶书', name: '隶', value: 'STLiti', icon: '' }, |
|||
{ title: '华文行楷', name: '行', value: 'STXingkai', icon: '' }, |
|||
{ title: '幼圆', name: '圆', value: 'YouYuan', icon: '' } |
|||
], |
|||
fontSize: [ |
|||
{ title: '12', name: '12', value: '12px', icon: '' }, |
|||
{ title: '14', name: '14', value: '14px', icon: '' }, |
|||
{ title: '16', name: '16', value: '16px', icon: '' }, |
|||
{ title: '18', name: '18', value: '18px', icon: '' }, |
|||
{ title: '20', name: '20', value: '20px', icon: '' }, |
|||
{ title: '22', name: '22', value: '22px', icon: '' }, |
|||
{ title: '24', name: '24', value: '24px', icon: '' } |
|||
], |
|||
align: [ |
|||
{ title: '左对齐', name: 'alignLeft', value: 'left', icon: 'icon-zuoduiqi' }, |
|||
{ title: '居中对齐', name: 'alignCenter', value: 'center', icon: 'icon-juzhongduiqi' }, |
|||
{ title: '右对齐', name: 'alignRight', value: 'right', icon: 'icon-youduiqi' }, |
|||
{ title: '两端对齐', name: 'alignJustify', value: 'justify', icon: 'icon-zuoyouduiqi' } |
|||
], |
|||
lineHeight: [ |
|||
{ title: '1倍', name: '1', value: '1', icon: '' }, |
|||
{ title: '1.5倍', name: '1.5', value: '1.5', icon: '' }, |
|||
{ title: '2倍', name: '2', value: '2', icon: '' }, |
|||
{ title: '2.5倍', name: '2.5', value: '2.5', icon: '' }, |
|||
{ title: '3倍', name: '3', value: '3', icon: '' } |
|||
], |
|||
// 字间距/段前距/段后距 |
|||
space: [ |
|||
{ title: '0.5倍', name: '0.5', value: '0.5em', icon: '' }, |
|||
{ title: '1倍', name: '1', value: '1em', icon: '' }, |
|||
{ title: '1.5倍', name: '1.5', value: '1.5em', icon: '' }, |
|||
{ title: '2倍', name: '2', value: '2em', icon: '' }, |
|||
{ title: '2.5倍', name: '2.5', value: '2.5em', icon: '' }, |
|||
{ title: '3倍', name: '3', value: '3em', icon: '' } |
|||
] |
|||
} |
|||
} |
|||
}, |
|||
methods: { |
|||
onEditorReady() { |
|||
uni |
|||
.createSelectorQuery() |
|||
.in(this) |
|||
.select('#' + this.editorId) |
|||
.context((res) => { |
|||
this.editorCtx = res.context |
|||
this.$emit('init', this.editorCtx, this.editorId) |
|||
}) |
|||
.exec() |
|||
}, |
|||
undo() { |
|||
this.editorCtx.undo() |
|||
}, |
|||
redo() { |
|||
this.editorCtx.redo() |
|||
}, |
|||
format(e) { |
|||
let { name, value } = e.target.dataset |
|||
if (!name) return |
|||
switch (name) { |
|||
case 'color': |
|||
case 'backgroundColor': |
|||
this.curColor = name |
|||
this.showPicker() |
|||
break |
|||
default: |
|||
this.editorCtx.format(name, value) |
|||
break |
|||
} |
|||
}, |
|||
// 悬浮工具点击 |
|||
fabTap(fabType) { |
|||
if (this.curFab != fabType) { |
|||
this.curFab = fabType |
|||
} else { |
|||
this.curFab = '' |
|||
} |
|||
}, |
|||
// 悬浮工具子集点击 |
|||
fabTapSub(e, fabType) { |
|||
this.format(e) |
|||
this.fabTap(fabType) |
|||
}, |
|||
showPicker() { |
|||
switch (this.curColor) { |
|||
case 'color': |
|||
this.defaultColor = this.textColor |
|||
? this.$refs.colorPickerRef.hex2Rgb(this.textColor) |
|||
: { r: 0, g: 0, b: 0, a: 1 } |
|||
break |
|||
case 'backgroundColor': |
|||
this.defaultColor = this.backgroundColor |
|||
? this.$refs.colorPickerRef.hex2Rgb(this.backgroundColor) |
|||
: { r: 0, g: 0, b: 0, a: 0 } |
|||
break |
|||
} |
|||
this.$refs.colorPickerRef.open() |
|||
}, |
|||
confirmColor(e) { |
|||
switch (this.curColor) { |
|||
case 'color': |
|||
this.textColor = e.hex |
|||
this.editorCtx.format('color', this.textColor) |
|||
break |
|||
case 'backgroundColor': |
|||
this.backgroundColor = e.hex |
|||
this.editorCtx.format('backgroundColor', this.backgroundColor) |
|||
break |
|||
} |
|||
}, |
|||
onStatusChange(e) { |
|||
if (e.detail.color) { |
|||
this.textColor = e.detail.color |
|||
} |
|||
if (e.detail.backgroundColor) { |
|||
this.backgroundColor = e.detail.backgroundColor |
|||
} |
|||
this.formats = e.detail |
|||
}, |
|||
insertDivider() { |
|||
this.editorCtx.insertDivider() |
|||
}, |
|||
clear() { |
|||
uni.showModal({ |
|||
title: '清空编辑器', |
|||
content: '确定清空编辑器吗?', |
|||
success: ({ confirm }) => { |
|||
if (confirm) { |
|||
this.editorCtx.clear() |
|||
} |
|||
} |
|||
}) |
|||
}, |
|||
removeFormat() { |
|||
uni.showModal({ |
|||
title: '文本格式化', |
|||
content: '确定要清除所选择部分文本块格式吗?', |
|||
showCancel: true, |
|||
success: ({ confirm }) => { |
|||
if (confirm) { |
|||
this.editorCtx.removeFormat() |
|||
} |
|||
} |
|||
}) |
|||
}, |
|||
insertDate() { |
|||
const date = new Date() |
|||
const formatDate = `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}` |
|||
this.editorCtx.insertText({ text: formatDate }) |
|||
}, |
|||
insertLink() { |
|||
this.$refs.linkEditRef.open() |
|||
}, |
|||
/** |
|||
* 确认添加链接 |
|||
* @param {Object} e { text: '链接描述', href: '链接地址' } |
|||
*/ |
|||
confirmLink(e) { |
|||
this.$refs.linkEditRef.close() |
|||
addLink(this.editorCtx, e, () => { |
|||
// 修复添加超链接后,不触发input更新当前最新内容的bug,这里做一下手动更新 |
|||
this.editorCtx.getContents({ |
|||
success: (res) => { |
|||
this.$emit('input', { html: res.html, text: res.text }, this.editorId) |
|||
} |
|||
}) |
|||
}) |
|||
this.$emit('addLink', e, this.editorId) |
|||
}, |
|||
insertImage() { |
|||
// #ifdef APP-PLUS || H5 |
|||
uni.chooseImage({ |
|||
// count: 1, // 默认9 |
|||
success: (res) => { |
|||
const { tempFiles } = res |
|||
// 将文件和编辑器示例抛出,由开发者自行上传和插入图片 |
|||
this.$emit('upinImage', tempFiles, this.editorCtx, this.editorId) |
|||
}, |
|||
fail() { |
|||
uni.showToast({ |
|||
title: '未授权访问相册权限,请授权后使用', |
|||
icon: 'none' |
|||
}) |
|||
} |
|||
}) |
|||
// #endif |
|||
|
|||
// #ifdef MP-WEIXIN |
|||
// 微信小程序从基础库 2.21.0 开始, wx.chooseImage 停止维护,请使用 uni.chooseMedia 代替。 |
|||
uni.chooseMedia({ |
|||
// count: 1, // 默认9 |
|||
mediaType: ['image'], |
|||
success: (res) => { |
|||
// 同上chooseImage处理 |
|||
const { tempFiles } = res |
|||
this.$emit('upinImage', tempFiles, this.editorCtx, this.editorId) |
|||
}, |
|||
fail() { |
|||
uni.showToast({ |
|||
title: '未授权访问相册权限,请授权后使用', |
|||
icon: 'none' |
|||
}) |
|||
} |
|||
}) |
|||
// #endif |
|||
}, |
|||
insertVideo() { |
|||
uni.chooseVideo({ |
|||
sourceType: ['camera', 'album'], |
|||
success: (res) => { |
|||
const { tempFilePath } = res |
|||
// 将文件和编辑器示例抛出,由开发者自行上传和插入图片 |
|||
this.$emit('upinVideo', tempFilePath, this.editorCtx, this.editorId) |
|||
}, |
|||
fail() { |
|||
uni.showToast({ |
|||
title: '未授权访问媒体权限,请授权后使用', |
|||
icon: 'none' |
|||
}) |
|||
} |
|||
}) |
|||
}, |
|||
onEditorInput(e) { |
|||
// 注意不要使用getContents获取html和text,会导致重复触发onStatusChange从而失去toolbar工具的高亮状态 |
|||
// 复制粘贴的时候detail会为空,此时应当直接return |
|||
if (Object.keys(e.detail).length <= 0) return |
|||
const { html, text } = e.detail |
|||
// 识别到标识立即return |
|||
if (text.indexOf(linkFlag) !== -1) return |
|||
|
|||
const maxlength = parseInt(this.maxlength) |
|||
const textStr = text.replace(/[ \t\r\n]/g, '') |
|||
if (textStr.length > maxlength && maxlength != -1) { |
|||
uni.showModal({ |
|||
content: `超过${maxlength}字数啦~`, |
|||
confirmText: '确定', |
|||
showCancel: false, |
|||
success: () => { |
|||
this.$emit('overMax', { html, text }, this.editorId) |
|||
} |
|||
}) |
|||
} else { |
|||
this.$emit('input', { html, text }, this.editorId) |
|||
} |
|||
}, |
|||
// 导出 |
|||
exportHtml() { |
|||
this.editorCtx.getContents({ |
|||
success: (res) => { |
|||
this.$emit('exportHtml', res.html, this.editorId) |
|||
} |
|||
}) |
|||
}, |
|||
eLongpress() { |
|||
/** |
|||
* 微信小程序官方editor的长按事件有bug,需要重写覆盖,不需做任何逻辑,可见下面小程序社区问题链接 |
|||
* @tutorial https://developers.weixin.qq.com/community/develop/doc/000c04b3e1c1006f660065e4f61000 |
|||
*/ |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
@import '../../icons/editor-icon.css'; |
|||
@import '../../icons/custom-icon.css'; |
|||
|
|||
.sp-editor { |
|||
height: 100%; |
|||
display: flex; |
|||
flex-direction: column; |
|||
position: relative; |
|||
} |
|||
|
|||
.sp-editor-toolbar { |
|||
box-sizing: border-box; |
|||
padding: calc(var(--icon-size) / 4) 0; |
|||
border-bottom: 1px solid #e4e4e4; |
|||
font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif; |
|||
display: grid; |
|||
grid-template-columns: repeat(var(--icon-columns), 1fr); |
|||
} |
|||
|
|||
.iconfont { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
width: 100%; |
|||
height: calc(var(--icon-size) * 1.8); |
|||
cursor: pointer; |
|||
font-size: var(--icon-size); |
|||
} |
|||
|
|||
.sp-editor-wrapper { |
|||
flex: 1; |
|||
overflow: hidden; |
|||
position: relative; |
|||
} |
|||
|
|||
.editor-container { |
|||
padding: 8rpx 16rpx; |
|||
box-sizing: border-box; |
|||
width: 100%; |
|||
height: 100%; |
|||
font-size: 16px; |
|||
line-height: 1.5; |
|||
} |
|||
|
|||
.ql-image-overlay-none { |
|||
::v-deep .ql-image-overlay { |
|||
pointer-events: none; |
|||
opacity: 0; |
|||
} |
|||
} |
|||
|
|||
::v-deep .ql-editor.ql-blank::before { |
|||
font-style: normal; |
|||
color: #cccccc; |
|||
} |
|||
|
|||
::v-deep .ql-container { |
|||
min-height: unset; |
|||
} |
|||
|
|||
.ql-active { |
|||
color: #66ccff; |
|||
} |
|||
|
|||
.fab-tools { |
|||
display: flex; |
|||
padding: 0 10rpx; |
|||
box-sizing: border-box; |
|||
|
|||
.fab-sub { |
|||
width: auto; |
|||
height: auto; |
|||
margin: 10rpx; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,24 @@ |
|||
@font-face { |
|||
font-family: 'iconfont'; |
|||
src: url('data:font/ttf;charset=utf-8;base64,AAEAAAANAIAAAwBQRkZUTahWRN8AAAeEAAAAHEdERUYAKQALAAAHZAAAAB5PUy8yPElI7QAAAVgAAABgY21hcOY96ckAAAHMAAABSmdhc3D//wADAAAHXAAAAAhnbHlmPxK+mgAAAyQAAAF4aGVhZCjWXYcAAADcAAAANmhoZWEHngOFAAABFAAAACRobXR4DIgAQAAAAbgAAAASbG9jYQCkALwAAAMYAAAADG1heHABGQB9AAABOAAAACBuYW1lXoIBAgAABJwAAAKCcG9zdENa1TcAAAcgAAAAOQABAAAAAQAA20yLC18PPPUACwQAAAAAAOLbjMwAAAAA4tuMzABA/78DwAMzAAAACAACAAAAAAAAAAEAAAOA/4AAXAQAAAAAAAPAAAEAAAAAAAAAAAAAAAAAAAAEAAEAAAAFAHEACgAAAAAAAgAAAAoACgAAAP8AAAAAAAAABAQAAZAABQAAAokCzAAAAI8CiQLMAAAB6wAyAQgAAAIABQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUGZFZADA5hTmHgOA/4AAAAPcAIAAAAABAAAAAAAAAAAAAAAgAAEEAAAAAAAAAAQAAAAEAABAAIgAAAAAAAMAAAADAAAAHAABAAAAAABEAAMAAQAAABwABAAoAAAABgAEAAEAAuYU5h7//wAA5hTmHv//Ge8Z5gABAAAAAAAAAAABBgAAAQAAAAAAAAABAgAAAAIAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApAC8AAoAQABAA8ACwAARABQAGAAoADQAQABMAFgAZABwAAABHgEUBg8BBiIuAT0BND4BMh8BFTcBESERBSEiJjURNDYzITIWFREUBgE0NjIWFREUBiImNRMyFhQGKwEiJjQ2MxcyFhQGKwEiJjQ2MwE0NjIWFREUBiImNRMiJjQ2OwEyFhQGIwciJjQ2OwEyFhQGIwJwCAgICKAHEQ8JCQ8RBxBCAV79AAMA/QAbJSUbAwAbJSX9JRMaExMaEyANExMNgA0TEw2ADRMTDYANExMNAqATGhMTGhMgDRMTDYANExMNgA0TEw2ADRMTDQGcBQ8QDwVgBAgPCcAJDwgEVU4n/wACAP4AQCUbAgAbJSUb/gAbJQJgDRMTDf3gDRMTDQGAExoTExoTwBMaExMaEwFgDRMTDf3gDRMTDQFAExoTExoTwBMaExMaEwABAIj/vwNpAzMACwAAEzMRIREzESMRIREjiJABwZCQ/j+QAzP+jwFx/IwBiP54AAAAABIA3gABAAAAAAAAABMAKAABAAAAAAABAAgATgABAAAAAAACAAcAZwABAAAAAAADAAgAgQABAAAAAAAEAAgAnAABAAAAAAAFAAsAvQABAAAAAAAGAAgA2wABAAAAAAAKACsBPAABAAAAAAALABMBkAADAAEECQAAACYAAAADAAEECQABABAAPAADAAEECQACAA4AVwADAAEECQADABAAbwADAAEECQAEABAAigADAAEECQAFABYApQADAAEECQAGABAAyQADAAEECQAKAFYA5AADAAEECQALACYBaABDAHIAZQBhAHQAZQBkACAAYgB5ACAAaQBjAG8AbgBmAG8AbgB0AABDcmVhdGVkIGJ5IGljb25mb250AABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABSAGUAZwB1AGwAYQByAABSZWd1bGFyAABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABWAGUAcgBzAGkAbwBuACAAMQAuADAAAFZlcnNpb24gMS4wAABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABHAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAHMAdgBnADIAdAB0AGYAIABmAHIAbwBtACAARgBvAG4AdABlAGwAbABvACAAcAByAG8AagBlAGMAdAAuAABHZW5lcmF0ZWQgYnkgc3ZnMnR0ZiBmcm9tIEZvbnRlbGxvIHByb2plY3QuAABoAHQAdABwADoALwAvAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAABodHRwOi8vZm9udGVsbG8uY29tAAAAAAIAAAAAAAAACgAAAAAAAQAAAAAAAAAAAAAAAAAAAAAABQAAAAEAAgECAQMFdmlkZW8GaGVhZGVyAAAAAAAAAf//AAIAAQAAAAwAAAAWAAAAAgABAAMABAABAAQAAAACAAAAAAAAAAEAAAAA4p8rRgAAAADi24zMAAAAAOLbjMw=') format('truetype'); |
|||
font-weight: normal; |
|||
font-style: normal; |
|||
font-display: swap; |
|||
} |
|||
|
|||
.iconfont { |
|||
font-family: "iconfont" !important; |
|||
font-size: 16px; |
|||
font-style: normal; |
|||
-webkit-font-smoothing: antialiased; |
|||
-moz-osx-font-smoothing: grayscale; |
|||
} |
|||
|
|||
.icon-header:before { |
|||
content: "\e61e"; |
|||
} |
|||
|
|||
.icon-video:before { |
|||
content: "\e614"; |
|||
} |
|||
|
|||
File diff suppressed because one or more lines are too long
@ -0,0 +1,83 @@ |
|||
{ |
|||
"id": "sp-editor", |
|||
"displayName": "官方富文本编辑器editor组件改良扩展优化版", |
|||
"version": "1.5.0", |
|||
"description": "基于官方的富文本编辑器editor组件,进行改良扩展优化版,添加了调色板,添加超链接等功能,可自定义扩展工具,快来试试吧~", |
|||
"keywords": [ |
|||
"富文本", |
|||
"editor", |
|||
"编辑器" |
|||
], |
|||
"repository": "", |
|||
"engines": { |
|||
}, |
|||
"dcloudext": { |
|||
"type": "component-vue", |
|||
"sale": { |
|||
"regular": { |
|||
"price": "0.00" |
|||
}, |
|||
"sourcecode": { |
|||
"price": "0.00" |
|||
} |
|||
}, |
|||
"contact": { |
|||
"qq": "" |
|||
}, |
|||
"declaration": { |
|||
"ads": "无", |
|||
"data": "插件不采集任何数据", |
|||
"permissions": "无" |
|||
}, |
|||
"npmurl": "" |
|||
}, |
|||
"uni_modules": { |
|||
"dependencies": [], |
|||
"encrypt": [], |
|||
"platforms": { |
|||
"cloud": { |
|||
"tcb": "y", |
|||
"aliyun": "y", |
|||
"alipay": "n" |
|||
}, |
|||
"client": { |
|||
"Vue": { |
|||
"vue2": "y", |
|||
"vue3": "y" |
|||
}, |
|||
"App": { |
|||
"app-vue": "y", |
|||
"app-nvue": "y" |
|||
}, |
|||
"H5-mobile": { |
|||
"Safari": "y", |
|||
"Android Browser": "y", |
|||
"微信浏览器(Android)": "y", |
|||
"QQ浏览器(Android)": "y" |
|||
}, |
|||
"H5-pc": { |
|||
"Chrome": "y", |
|||
"IE": "y", |
|||
"Edge": "y", |
|||
"Firefox": "y", |
|||
"Safari": "y" |
|||
}, |
|||
"小程序": { |
|||
"微信": "y", |
|||
"阿里": "u", |
|||
"百度": "u", |
|||
"字节跳动": "u", |
|||
"QQ": "u", |
|||
"钉钉": "u", |
|||
"快手": "u", |
|||
"飞书": "u", |
|||
"京东": "u" |
|||
}, |
|||
"快应用": { |
|||
"华为": "u", |
|||
"联盟": "u" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
# sp-editor |
|||
|
|||
### 视频插入功能 |
|||
|
|||
`于2024-7-20日v1.4.4版本更新视频插入功能(属实是鸽🕊了太久了)` |
|||
|
|||
- 实现方案:先以图片占位,在导出时,将携带视频链接的图片转换成视频标签。 |
|||
- 更多请参考示例一 |
|||
- 如果该插件有帮助到您,还望能点赞好评一下,谢谢!🌟 |
|||
|
|||
### 文档迁移 |
|||
|
|||
> 防止文档失效,提供下列五个地址,内容一致 |
|||
|
|||
- [地址一](https://sonvee.github.io/sv-app-docs/docs-github/src/plugins/sp-editor/sp-editor.html) |
|||
- [地址二](https://sv-app-docs.pages.dev/src/plugins/sp-editor/sp-editor.html) |
|||
- [地址三](https://sv-app-docs.4everland.app/src/plugins/sp-editor/sp-editor.html) |
|||
- [地址四](https://sv-app-docs.vercel.app/src/plugins/sp-editor/sp-editor.html) (需要梯子) |
|||
- [地址五](https://static-mp-74bfcbac-6ba6-4f39-8513-8831390ff75a.next.bspapp.com/docs-uni/src/plugins/sp-editor/sp-editor.html) (有IP限制) |
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,132 @@ |
|||
// 标识必须独一无二 - 标识是为了使用insertText插入标识文本后,查找到标识所在delta位置的索引
|
|||
export const linkFlag = '#-*=*-*=*-*=*@-link超链接标识link-@*=*-*=*-*=*-#' |
|||
|
|||
export function addLink(editorCtx, attr, callback) { |
|||
// 先插入一段文本内容
|
|||
editorCtx.insertText({ |
|||
text: linkFlag |
|||
}) |
|||
// 获取全文delta内容
|
|||
editorCtx.getContents({ |
|||
success(res) { |
|||
let options = res.delta.ops |
|||
const findex = options.findIndex(item => { |
|||
return item.insert && typeof item.insert !== 'object' && item.insert?.indexOf(linkFlag) !== -1 |
|||
}) |
|||
// 根据标识查找到插入的位置
|
|||
if (findex > -1) { |
|||
const findOption = options[findex] |
|||
const findAttributes = findOption.attributes |
|||
// 将该findOption分成三部分:前内容 要插入的link 后内容
|
|||
const [prefix, suffix] = findOption.insert.split(linkFlag); |
|||
const handleOps = [] |
|||
// 前内容
|
|||
if (prefix) { |
|||
const prefixOps = findAttributes ? { |
|||
insert: prefix, |
|||
attributes: findAttributes |
|||
} : { |
|||
insert: prefix |
|||
} |
|||
handleOps.push(prefixOps) |
|||
} |
|||
// 插入的link
|
|||
const linkOps = { |
|||
insert: attr.text, |
|||
attributes: { |
|||
link: attr.href, |
|||
textDecoration: attr.textDecoration || 'none', // 下划线
|
|||
color: attr.color || '#007aff' |
|||
} |
|||
} |
|||
handleOps.push(linkOps) |
|||
// 后内容
|
|||
if (suffix) { |
|||
const suffixOps = findAttributes ? { |
|||
insert: suffix, |
|||
attributes: findAttributes |
|||
} : { |
|||
insert: suffix |
|||
} |
|||
handleOps.push(suffixOps) |
|||
} |
|||
// 删除原options[findex]并在findex位置插入上述三个ops
|
|||
options.splice(findex, 1); |
|||
options.splice(findex, 0, ...handleOps); |
|||
// 最后重新初始化内容,注意该方法会导致光标重置到最开始位置
|
|||
editorCtx.setContents({ |
|||
delta: { |
|||
ops: options |
|||
} |
|||
}) |
|||
// 所以最后建议使富文本光标失焦,让用户手动聚焦光标
|
|||
editorCtx.blur() |
|||
|
|||
// 后续回调操作
|
|||
if (callback) callback() |
|||
} |
|||
} |
|||
}) |
|||
|
|||
} |
|||
|
|||
/** |
|||
* 将含有特殊图片形式视频的富文本转换成正常视频的富文本 |
|||
* @param {String} html 要进行处理的富文本字符串 |
|||
* @returns {String} 返回处理结果 |
|||
*/ |
|||
export function handleHtmlWithVideo(html) { |
|||
// 正则表达式用于匹配img标签中带有alt属性且alt属性值为视频链接的模式
|
|||
const regex = /<img\s+src="[^"]*"\s+alt="([^"]*)"[^>]*>/g |
|||
// 使用replace方法和一个函数回调来替换匹配到的内容
|
|||
return html.replace(regex, (match, videoUrl) => { |
|||
// 替换为video标签,并添加controls属性以便用户可以控制播放
|
|||
return `<video width="80%" controls><source src="${videoUrl}" type="video/mp4"></video>` |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 将img标签中内联style属性中的宽高样式提取出标签width与height属性 |
|||
* @param {Object} html 要处理的富文本字符串 |
|||
* @returns {Object} 返回处理结果 |
|||
*/ |
|||
export function convertImgStylesToAttributes(html) { |
|||
return html.replace(/<img\s+([^>]+)\s*>/g, function(match, attributes) { |
|||
// 分割属性
|
|||
const attrs = attributes.split(/\s+/); |
|||
|
|||
// 找到style属性的位置
|
|||
const styleIndex = attrs.findIndex(attr => attr.startsWith('style=')); |
|||
if (styleIndex === -1) return match; // 如果没有找到style属性,则返回原样
|
|||
|
|||
// 提取style属性值
|
|||
const styleAttr = attrs.splice(styleIndex, 1)[0]; |
|||
const style = styleAttr.match(/"([^"]*)"/)[1]; |
|||
|
|||
// 解析 style 属性
|
|||
const styleObj = {}; |
|||
style.split(';').forEach(function(part) { |
|||
if (part) { |
|||
const [name, value] = part.split(':'); |
|||
styleObj[name.trim()] = value.trim(); |
|||
} |
|||
}); |
|||
|
|||
// 创建新的 img 标签
|
|||
let newTag = '<img'; |
|||
if (styleObj.width) { |
|||
newTag += ` width="${styleObj.width}"`; |
|||
} |
|||
if (styleObj.height) { |
|||
newTag += ` height="${styleObj.height}"`; |
|||
} |
|||
|
|||
// 添加原有的属性,包括修改过的style属性
|
|||
newTag += ` ${styleAttr} ${attrs.join(' ')}`; |
|||
|
|||
// 关闭 img 标签
|
|||
newTag += '>'; |
|||
|
|||
return newTag; |
|||
}); |
|||
} |
|||
@ -0,0 +1,306 @@ |
|||
<template> |
|||
<view class="live-page"> |
|||
<view class="top-safe" :style="{ height: menuButtonInfo.top + 'px' }"></view> |
|||
<view class="nav"> |
|||
<view class="back" @tap="back">‹</view> |
|||
<view class="title">{{title}}</view> |
|||
<view class="ghost"></view> |
|||
</view> |
|||
<view class="content" v-if="liveUrl"> |
|||
<view class="shop-card"> |
|||
<view class="shop-title">{{title}}</view> |
|||
<view class="shop-sub">商家已接入明厨亮灶,可实时查看后厨画面</view> |
|||
</view> |
|||
<view class="live-card"> |
|||
<view class="card-head"> |
|||
<view class="head-left"> |
|||
<view class="head-dot"></view> |
|||
<text>后厨实时画面</text> |
|||
</view> |
|||
<view class="head-status">可查看</view> |
|||
</view> |
|||
<view class="preview-box" @tap="playerUrl ? '' : playLive()"> |
|||
<video v-if="playerUrl" id="openKitchenPlayer" class="live-player" :src="playerUrl" :controls="true" |
|||
:autoplay="false" object-fit="contain" @play="isPlaying = true" @pause="isPlaying = false" |
|||
@fullscreenchange="onFullscreenChange" @error="onPlayerError"></video> |
|||
<view v-else class="preview-placeholder"> |
|||
<view class="preview-top"> |
|||
<view class="camera-dot"></view> |
|||
<text>LIVE</text> |
|||
</view> |
|||
<view class="preview-play">▶</view> |
|||
<view class="preview-text">点击查看直播</view> |
|||
</view> |
|||
</view> |
|||
<view class="play-entry" @tap="playLive"> |
|||
查看直播画面 |
|||
</view> |
|||
<view class="entry-tip">默认不自动播放,点击后全屏观看</view> |
|||
</view> |
|||
</view> |
|||
<view class="empty" v-else>明厨亮灶地址为空</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
menuButtonInfo: { top: 44 }, |
|||
title: '明厨亮灶', |
|||
liveUrl: '', |
|||
playerUrl: '', |
|||
isPlaying: false |
|||
} |
|||
}, |
|||
onLoad(options) { |
|||
if (uni.getMenuButtonBoundingClientRect) this.menuButtonInfo = uni.getMenuButtonBoundingClientRect() |
|||
const cacheKey = (options && options.key) || '' |
|||
this.liveUrl = cacheKey ? (uni.getStorageSync(cacheKey) || '') : decodeURIComponent((options && options.url) || '') |
|||
this.title = decodeURIComponent((options && options.name) || '') || '明厨亮灶' |
|||
}, |
|||
methods: { |
|||
playLive() { |
|||
if (!this.playerUrl) { |
|||
this.playerUrl = this.liveUrl |
|||
} |
|||
this.$nextTick(() => { |
|||
const context = uni.createVideoContext('openKitchenPlayer', this) |
|||
if (context && context.requestFullScreen) { |
|||
context.requestFullScreen({ |
|||
direction: 0 |
|||
}) |
|||
} |
|||
if (context && context.play) context.play() |
|||
}) |
|||
}, |
|||
onFullscreenChange(event) { |
|||
const fullScreen = event && event.detail ? event.detail.fullScreen : false |
|||
if (!fullScreen) this.isPlaying = false |
|||
}, |
|||
onPlayerError() { |
|||
uni.showToast({ |
|||
title: '直播画面加载失败', |
|||
icon: 'none' |
|||
}) |
|||
}, |
|||
back() { |
|||
uni.navigateBack() |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
page { |
|||
background: #f5f6f7; |
|||
} |
|||
|
|||
.live-page { |
|||
min-height: 100vh; |
|||
box-sizing: border-box; |
|||
color: #222; |
|||
background: #f5f6f7; |
|||
} |
|||
|
|||
.nav { |
|||
height: 88rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
padding: 0 24rpx; |
|||
box-sizing: border-box; |
|||
background: #fff; |
|||
border-bottom: 1rpx solid #f0f0f0; |
|||
} |
|||
|
|||
.back, |
|||
.ghost { |
|||
width: 72rpx; |
|||
color: #333; |
|||
font-size: 56rpx; |
|||
} |
|||
|
|||
.title { |
|||
flex: 1; |
|||
text-align: center; |
|||
font-size: 30rpx; |
|||
font-weight: 800; |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
white-space: nowrap; |
|||
} |
|||
|
|||
.content { |
|||
min-height: calc(100vh - 150rpx); |
|||
padding: 24rpx; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
.shop-card, |
|||
.live-card { |
|||
background: #fff; |
|||
border-radius: 20rpx; |
|||
padding: 28rpx; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
.shop-title { |
|||
font-size: 34rpx; |
|||
font-weight: 900; |
|||
color: #222; |
|||
line-height: 44rpx; |
|||
} |
|||
|
|||
.shop-sub { |
|||
margin-top: 10rpx; |
|||
font-size: 24rpx; |
|||
line-height: 34rpx; |
|||
color: #777; |
|||
} |
|||
|
|||
.live-card { |
|||
margin-top: 20rpx; |
|||
} |
|||
|
|||
.card-head { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
margin-bottom: 22rpx; |
|||
} |
|||
|
|||
.head-left { |
|||
display: flex; |
|||
align-items: center; |
|||
font-size: 28rpx; |
|||
font-weight: 900; |
|||
color: #222; |
|||
} |
|||
|
|||
.head-dot { |
|||
width: 12rpx; |
|||
height: 12rpx; |
|||
border-radius: 50%; |
|||
background: #21b37b; |
|||
margin-right: 10rpx; |
|||
} |
|||
|
|||
.head-status { |
|||
height: 42rpx; |
|||
line-height: 42rpx; |
|||
padding: 0 16rpx; |
|||
border-radius: 999rpx; |
|||
background: #e9fff5; |
|||
color: #18a66f; |
|||
font-size: 22rpx; |
|||
font-weight: 800; |
|||
} |
|||
|
|||
.preview-box { |
|||
position: relative; |
|||
height: 360rpx; |
|||
border-radius: 18rpx; |
|||
background: #202326; |
|||
overflow: hidden; |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.preview-placeholder { |
|||
position: relative; |
|||
width: 100%; |
|||
height: 100%; |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.preview-box::before { |
|||
content: ''; |
|||
position: absolute; |
|||
left: 0; |
|||
right: 0; |
|||
top: 0; |
|||
height: 80rpx; |
|||
background: linear-gradient(180deg, rgba(255, 255, 255, .08), transparent); |
|||
} |
|||
|
|||
.preview-top { |
|||
position: absolute; |
|||
left: 20rpx; |
|||
top: 18rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
height: 34rpx; |
|||
line-height: 34rpx; |
|||
padding: 0 12rpx; |
|||
border-radius: 999rpx; |
|||
background: rgba(0, 0, 0, .38); |
|||
color: #fff; |
|||
font-size: 18rpx; |
|||
font-weight: 900; |
|||
} |
|||
|
|||
.camera-dot { |
|||
width: 10rpx; |
|||
height: 10rpx; |
|||
border-radius: 50%; |
|||
background: #ff4d4f; |
|||
margin-right: 8rpx; |
|||
} |
|||
|
|||
.preview-play { |
|||
width: 104rpx; |
|||
height: 104rpx; |
|||
line-height: 104rpx; |
|||
border-radius: 50%; |
|||
text-align: center; |
|||
background: rgba(255, 255, 255, .92); |
|||
color: #19a974; |
|||
font-size: 44rpx; |
|||
padding-left: 8rpx; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
.preview-text { |
|||
margin-top: 18rpx; |
|||
font-size: 24rpx; |
|||
font-weight: 800; |
|||
color: rgba(255, 255, 255, .72); |
|||
} |
|||
|
|||
.play-entry { |
|||
margin-top: 28rpx; |
|||
height: 84rpx; |
|||
line-height: 84rpx; |
|||
border-radius: 999rpx; |
|||
text-align: center; |
|||
background: linear-gradient(90deg, #dcff9c, #baffec); |
|||
color: #073d33; |
|||
font-size: 28rpx; |
|||
font-weight: 900; |
|||
} |
|||
|
|||
.entry-tip { |
|||
margin-top: 20rpx; |
|||
text-align: center; |
|||
font-size: 22rpx; |
|||
color: #999; |
|||
} |
|||
|
|||
.live-player { |
|||
width: 100%; |
|||
height: 100%; |
|||
background: #000; |
|||
} |
|||
|
|||
.empty { |
|||
margin-top: 200rpx; |
|||
text-align: center; |
|||
color: rgba(255, 255, 255, .56); |
|||
font-size: 26rpx; |
|||
} |
|||
</style> |
|||
@ -1,9 +1,388 @@ |
|||
<template> |
|||
<!-- 我的发布 --> |
|||
<view class="my-fish-page"> |
|||
<view class="header" :style="{ paddingTop: statusBarTop + 'px' }"> |
|||
<view class="back" @tap="back"><uni-icons type="left" size="22"></uni-icons></view> |
|||
<view class="title">我的校园二手</view> |
|||
</view> |
|||
<view class="tabs"> |
|||
<view class="tab" :class="{ active: activeTab == item.key }" v-for="item in tabs" :key="item.key" @tap="switchTab(item.key)"> |
|||
{{item.name}} |
|||
</view> |
|||
</view> |
|||
<view class="search-bar" v-if="canManageCurrentTab"> |
|||
<uni-icons type="search" size="18" color="#8d9693"></uni-icons> |
|||
<input |
|||
v-model.trim="titleKeyword" |
|||
class="search-input" |
|||
confirm-type="search" |
|||
placeholder="按标题搜索" |
|||
@confirm="handleSearch" |
|||
/> |
|||
<view class="search-clear" v-if="titleKeyword" @tap="clearSearch">清空</view> |
|||
<view class="search-btn" @tap="handleSearch">搜索</view> |
|||
</view> |
|||
<scroll-view scroll-y class="list" :class="{ 'list--with-search': canManageCurrentTab }" @scrolltolower="loadMore"> |
|||
<u-swipe-action v-if="canManageCurrentTab"> |
|||
<u-swipe-action-item |
|||
v-for="(item,index) in list" |
|||
:key="item.id" |
|||
:name="index" |
|||
:options="swipeOptions(item)" |
|||
@click="handleSwipeAction" |
|||
> |
|||
<view class="card" @tap="openItem(item)"> |
|||
<image class="cover" :src="item.coverImage || item.pic || defaultCover" mode="aspectFill"></image> |
|||
<view class="main"> |
|||
<view class="name">{{nameText(item)}}</view> |
|||
<view class="desc">{{descText(item)}}</view> |
|||
<view class="meta">{{item.createTime || item.lastBrowseTime || item.lastChatTime || ''}}</view> |
|||
</view> |
|||
<view class="status" :class="{ 'status--off': isOffShelf(item) }">{{statusText(item)}}</view> |
|||
</view> |
|||
</u-swipe-action-item> |
|||
</u-swipe-action> |
|||
<block v-if="!canManageCurrentTab"> |
|||
<view class="card" v-for="item in list" :key="item.id" @tap="openItem(item)"> |
|||
<image class="cover" :src="item.coverImage || item.pic || defaultCover" mode="aspectFill"></image> |
|||
<view class="main"> |
|||
<view class="name">{{nameText(item)}}</view> |
|||
<view class="desc">{{descText(item)}}</view> |
|||
<view class="meta">{{item.createTime || item.lastBrowseTime || item.lastChatTime || ''}}</view> |
|||
</view> |
|||
<view class="status" :class="{ 'status--off': isOffShelf(item) }">{{statusText(item)}}</view> |
|||
</view> |
|||
</block> |
|||
<view class="empty" v-if="!list.length && !loading">暂无数据</view> |
|||
<view class="load-more" v-if="loading">加载中...</view> |
|||
<view class="bottom-space"></view> |
|||
</scroll-view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import { |
|||
pageMyFishGoods, |
|||
pageMyFishWants, |
|||
updateMyFishGoodsStatus, |
|||
updateMyFishWantStatus, |
|||
pageMyFishFavorites, |
|||
pageMyFishComments, |
|||
pageMyFishLikes, |
|||
pageMyFishComplaints |
|||
} from '@/common/fishMarketApi.js' |
|||
|
|||
export default { |
|||
data() { |
|||
return { |
|||
statusBarTop: 24, |
|||
activeTab: 'goods', |
|||
list: [], |
|||
pageNum: 1, |
|||
pageSize: 10, |
|||
totalPages: 1, |
|||
loading: false, |
|||
titleKeyword: '', |
|||
defaultCover: '/static/images/img/songshu.png', |
|||
tabs: [ |
|||
{ key: 'goods', name: '我的发布' }, |
|||
{ key: 'wants', name: '我的求购' }, |
|||
{ key: 'favorites', name: '我的收藏' }, |
|||
{ key: 'comments', name: '我的评论' }, |
|||
{ key: 'likes', name: '我的点赞' }, |
|||
{ key: 'complaints', name: '我的举报' } |
|||
] |
|||
} |
|||
}, |
|||
computed: { |
|||
canManageCurrentTab() { |
|||
return this.activeTab == 'goods' || this.activeTab == 'wants' |
|||
} |
|||
}, |
|||
onLoad() { |
|||
if (uni.getMenuButtonBoundingClientRect) { |
|||
this.statusBarTop = uni.getMenuButtonBoundingClientRect().top || this.statusBarTop |
|||
} |
|||
this.load() |
|||
}, |
|||
methods: { |
|||
back() { |
|||
uni.navigateBack() |
|||
}, |
|||
swipeOptions(item) { |
|||
const offShelf = this.isOffShelf(item) |
|||
return [{ |
|||
text: offShelf ? '上架' : '下架', |
|||
style: { |
|||
backgroundColor: offShelf ? '#24b979' : '#ff9f2d', |
|||
color: '#fff', |
|||
fontSize: '26rpx' |
|||
} |
|||
}, { |
|||
text: '删除', |
|||
style: { |
|||
backgroundColor: '#ef4c3f', |
|||
color: '#fff', |
|||
fontSize: '26rpx' |
|||
} |
|||
}] |
|||
}, |
|||
handleSwipeAction(e) { |
|||
const item = this.list[Number(e.name)] |
|||
if (!item) return |
|||
if (e.index == 0) { |
|||
this.toggleShelf(item) |
|||
return |
|||
} |
|||
this.deleteItem(item) |
|||
}, |
|||
isOffShelf(item) { |
|||
return Number(item.status) == 2 || Number(item.offShelf) == 1 |
|||
}, |
|||
updateItemStatus(item, status) { |
|||
const request = this.activeTab == 'goods' |
|||
? updateMyFishGoodsStatus({ goodsId: item.id, status }) |
|||
: updateMyFishWantStatus({ wantId: item.id, status }) |
|||
return request.then(res => { |
|||
if (!res && res !== true) return |
|||
uni.showToast({ title: status == 3 ? '已删除' : (status == 2 ? '已下架' : '已上架'), icon: 'none' }) |
|||
this.reload() |
|||
}) |
|||
}, |
|||
toggleShelf(item) { |
|||
const nextStatus = this.isOffShelf(item) ? 1 : 2 |
|||
this.updateItemStatus(item, nextStatus) |
|||
}, |
|||
deleteItem(item) { |
|||
uni.showModal({ |
|||
title: '删除确认', |
|||
content: '删除后不可恢复,确定删除吗?', |
|||
confirmText: '删除', |
|||
confirmColor: '#ef4c3f', |
|||
success: (res) => { |
|||
if (!res.confirm) return |
|||
this.updateItemStatus(item, 3) |
|||
} |
|||
}) |
|||
}, |
|||
switchTab(key) { |
|||
this.activeTab = key |
|||
this.titleKeyword = '' |
|||
this.reload() |
|||
}, |
|||
handleSearch() { |
|||
uni.hideKeyboard() |
|||
this.reload() |
|||
}, |
|||
clearSearch() { |
|||
this.titleKeyword = '' |
|||
this.reload() |
|||
}, |
|||
reload() { |
|||
this.pageNum = 1 |
|||
this.totalPages = 1 |
|||
this.list = [] |
|||
this.load() |
|||
}, |
|||
loadMore() { |
|||
if (this.loading || this.pageNum >= this.totalPages) return |
|||
this.pageNum += 1 |
|||
this.load() |
|||
}, |
|||
load() { |
|||
this.loading = true |
|||
const query = { |
|||
pageNum: this.pageNum, |
|||
pageSize: this.pageSize, |
|||
titleKeyword: this.canManageCurrentTab ? this.titleKeyword : '' |
|||
} |
|||
const map = { |
|||
goods: pageMyFishGoods, |
|||
wants: pageMyFishWants, |
|||
favorites: pageMyFishFavorites, |
|||
comments: pageMyFishComments, |
|||
likes: pageMyFishLikes, |
|||
complaints: pageMyFishComplaints |
|||
} |
|||
map[this.activeTab](query).then(page => { |
|||
const records = page && (page.records || page.content) ? (page.records || page.content) : [] |
|||
this.totalPages = Number(page && (page.pages || page.totalPages)) || 1 |
|||
this.list = this.pageNum == 1 ? records : this.list.concat(records) |
|||
}).finally(() => { |
|||
this.loading = false |
|||
}) |
|||
}, |
|||
openItem(item) { |
|||
const goodsId = item.goodsId || (this.activeTab == 'goods' ? item.id : '') |
|||
if (goodsId) uni.navigateTo({ url: '/package2/IdleTrad/detail?id=' + goodsId }) |
|||
}, |
|||
statusText(item) { |
|||
if (this.activeTab == 'likes') return item.likeType == 'comment' ? '评论点赞' : '商品点赞' |
|||
if (this.activeTab == 'complaints') return item.status == 1 ? '已通过' : (item.status == 2 ? '已驳回' : '待处理') |
|||
if (item.saleStatus == 2) return '已预定' |
|||
if (item.saleStatus == 3) return '已卖出' |
|||
if (item.status == 2) return '已下架' |
|||
return '正常' |
|||
}, |
|||
nameText(item) { |
|||
if (item.title) return item.title |
|||
if (this.activeTab == 'comments') return '我发表的评论' |
|||
if (this.activeTab == 'likes') return item.likeType == 'comment' ? '我点赞的评论' : '我点赞的商品' |
|||
if (this.activeTab == 'complaints') return '我的举报记录' |
|||
return item.goodsId || item.id || '校园二手' |
|||
}, |
|||
descText(item) { |
|||
if (this.activeTab == 'favorites') return '商品ID:' + item.goodsId |
|||
if (this.activeTab == 'comments') return item.content || '评论内容' |
|||
if (this.activeTab == 'likes') return '关联商品:' + (item.goodsId || '评论 ' + (item.commentId || '')) |
|||
if (this.activeTab == 'complaints') return item.reason || '举报内容' |
|||
return item.content || '' |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
</style> |
|||
<style lang="scss"> |
|||
page { |
|||
background: #f7f9f7; |
|||
} |
|||
.my-fish-page { |
|||
min-height: 100vh; |
|||
background: #f7f9f7; |
|||
color: #17362f; |
|||
} |
|||
.header { |
|||
padding-left: 26rpx; |
|||
padding-right: 26rpx; |
|||
background: linear-gradient(180deg, #e5fbf3, #f7f9f7); |
|||
} |
|||
.back { |
|||
width: 68rpx; |
|||
height: 68rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
.title { |
|||
padding: 10rpx 0 24rpx; |
|||
font-size: 40rpx; |
|||
font-weight: 900; |
|||
} |
|||
.tabs { |
|||
display: flex; |
|||
overflow-x: scroll; |
|||
padding: 0 22rpx 18rpx; |
|||
white-space: nowrap; |
|||
} |
|||
.tab { |
|||
flex-shrink: 0; |
|||
margin-right: 18rpx; |
|||
padding: 14rpx 24rpx; |
|||
border-radius: 28rpx; |
|||
background: #fff; |
|||
color: #758782; |
|||
font-size: 24rpx; |
|||
} |
|||
.tab.active { |
|||
background: #24b979; |
|||
color: #fff; |
|||
font-weight: 900; |
|||
} |
|||
.search-bar { |
|||
height: 70rpx; |
|||
margin: 0 24rpx 16rpx; |
|||
padding: 0 12rpx 0 22rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
border-radius: 38rpx; |
|||
background: #fff; |
|||
box-shadow: 0 8rpx 22rpx rgba(20, 60, 48, 0.045); |
|||
box-sizing: border-box; |
|||
} |
|||
.search-input { |
|||
flex: 1; |
|||
height: 68rpx; |
|||
margin-left: 10rpx; |
|||
color: #293632; |
|||
font-size: 24rpx; |
|||
line-height: 68rpx; |
|||
} |
|||
.search-clear { |
|||
padding: 0 12rpx; |
|||
color: #9aa8a4; |
|||
font-size: 22rpx; |
|||
} |
|||
.search-btn { |
|||
width: 86rpx; |
|||
height: 54rpx; |
|||
border-radius: 28rpx; |
|||
background: linear-gradient(135deg, #38c897, #19af7e); |
|||
color: #fff; |
|||
font-size: 23rpx; |
|||
font-weight: 800; |
|||
line-height: 54rpx; |
|||
text-align: center; |
|||
} |
|||
.list { |
|||
height: calc(100vh - 210rpx); |
|||
} |
|||
.list--with-search { |
|||
height: calc(100vh - 296rpx); |
|||
} |
|||
.card { |
|||
margin: 18rpx 24rpx 0; |
|||
padding: 18rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
border-radius: 24rpx; |
|||
background: #fff; |
|||
box-shadow: 0 8rpx 22rpx rgba(20, 60, 48, 0.05); |
|||
} |
|||
.cover { |
|||
width: 126rpx; |
|||
height: 126rpx; |
|||
border-radius: 18rpx; |
|||
background: #edf3ef; |
|||
} |
|||
.main { |
|||
flex: 1; |
|||
min-width: 0; |
|||
margin-left: 18rpx; |
|||
} |
|||
.name { |
|||
overflow: hidden; |
|||
font-size: 28rpx; |
|||
font-weight: 900; |
|||
text-overflow: ellipsis; |
|||
white-space: nowrap; |
|||
} |
|||
.desc { |
|||
margin-top: 8rpx; |
|||
overflow: hidden; |
|||
color: #71817c; |
|||
font-size: 23rpx; |
|||
text-overflow: ellipsis; |
|||
white-space: nowrap; |
|||
} |
|||
.meta { |
|||
margin-top: 8rpx; |
|||
color: #a0aaa6; |
|||
font-size: 20rpx; |
|||
} |
|||
.status { |
|||
margin-left: 12rpx; |
|||
color: #22a879; |
|||
font-size: 22rpx; |
|||
font-weight: 800; |
|||
} |
|||
.status--off { |
|||
color: #9aa8a4; |
|||
} |
|||
.empty, |
|||
.load-more { |
|||
padding: 80rpx 0; |
|||
color: #9aa8a4; |
|||
text-align: center; |
|||
} |
|||
.bottom-space { |
|||
height: 120rpx; |
|||
} |
|||
</style> |
|||
|
|||
Loading…
Reference in new issue