Compare commits
No commits in common. 'fee7e82631d1883fbabb84a0465a266413542ea9' and '23dc177874d2351709da3469736238fbeeb42471' have entirely different histories.
fee7e82631
...
23dc177874
17 changed files with 334 additions and 3191 deletions
@ -1,498 +1,143 @@ |
|||||||
<template> |
<template> |
||||||
<view class="view"> |
<view class="jobInfo-detail"> |
||||||
<scroll-view scroll-y class="content"> |
|
||||||
|
|
||||||
<!-- 标题输入 --> |
|
||||||
<view class="block"> |
|
||||||
<view> |
|
||||||
<text style="color: #f8285a; margin-right: 4px">*</text> |
|
||||||
标题 |
|
||||||
</view> |
|
||||||
<view class="value"> |
|
||||||
<uni-easyinput |
|
||||||
v-model="jobInfoDetail.title" |
|
||||||
placeholder="请输入标题" |
|
||||||
:inputBorder="false" |
|
||||||
:clearable="false" |
|
||||||
:autoHeight="true" |
|
||||||
:adjust-position="true" |
|
||||||
cursorSpacing="32" |
|
||||||
/> |
|
||||||
</view> |
|
||||||
</view> |
|
||||||
<!-- 日期选择 --> |
|
||||||
<view class="block"> |
|
||||||
<view> |
|
||||||
<text style="color: #f8285a; margin-right: 4px">*</text> |
|
||||||
日期 |
|
||||||
</view> |
|
||||||
<view class="value" @tap="showCalendar('jobDate')"> |
|
||||||
<text v-if="jobInfoDetail.jobDate" class="input-value">{{ jobInfoDetail.jobDate }}</text> |
|
||||||
<text v-else class="input-value" style="color: #808080">请选择日期</text> |
|
||||||
<u-icon name="arrow-right"></u-icon> |
|
||||||
</view> |
|
||||||
</view> |
|
||||||
<uni-calendar |
|
||||||
ref="calendarRef" |
|
||||||
:insert="false" |
|
||||||
@confirm="confirmCalendar" |
|
||||||
/> |
|
||||||
<!-- 汇报人 --> |
|
||||||
<view class="block"> |
|
||||||
<view> |
|
||||||
<text style="color: #f8285a; margin-right: 4px">*</text> |
|
||||||
汇报人 |
|
||||||
</view> |
|
||||||
<view class="value"> |
|
||||||
<uni-easyinput |
|
||||||
v-model="jobInfoDetail.jobName" |
|
||||||
placeholder="请输入汇报人" |
|
||||||
:inputBorder="false" |
|
||||||
:clearable="false" |
|
||||||
:autoHeight="true" |
|
||||||
:adjust-position="true" |
|
||||||
cursorSpacing="32" |
|
||||||
/> |
|
||||||
</view> |
|
||||||
</view> |
|
||||||
|
|
||||||
<!-- 汇报内容 --> |
<!-- 详情内容区 --> |
||||||
<view class="block content-block"> |
|
||||||
<view> |
|
||||||
<text style="color: #f8285a; margin-right: 4px">*</text> |
|
||||||
汇报内容 |
|
||||||
</view> |
|
||||||
<view class="content-wrap"> |
<view class="content-wrap"> |
||||||
<sp-editor |
<view class="jobInfo-title">{{ jobInfoDetail.title }}</view> |
||||||
:html="jobInfoDetail.content" |
<view class="jobInfo-time">生效时间:{{ jobInfoDetail.jobDate }}</view> |
||||||
:toolbar-config="{ |
|
||||||
excludeKeys: ['direction', 'date', 'lineHeight', 'letterSpacing', 'listCheck'], |
<!-- 富文本内容 --> |
||||||
iconSize: '18px' |
<view class="jobInfo-content"> |
||||||
}" |
<rich-text :nodes="jobInfoDetail.context"></rich-text> |
||||||
:max-length="5000" |
|
||||||
:min-height="300" |
|
||||||
:placeholder="'请输入汇报内容'" |
|
||||||
@init="initEditor" |
|
||||||
@input="inputOver" |
|
||||||
@upinImage="upinImage" |
|
||||||
@overMax="overMax" |
|
||||||
@addLink="addLink" |
|
||||||
@exportHtml="exportHtml" |
|
||||||
/> |
|
||||||
</view> |
|
||||||
</view> |
</view> |
||||||
</scroll-view> |
|
||||||
<!-- 底部按钮 --> |
|
||||||
<view class="operation"> |
|
||||||
<button class="btn delete" @tap="handleDelete">删除</button> |
|
||||||
<button class="btn save" @tap="handleSave">保存</button> |
|
||||||
</view> |
</view> |
||||||
</view> |
</view> |
||||||
</template> |
</template> |
||||||
|
|
||||||
<script> |
<script> |
||||||
import { JobInfoApi } from '@/api/jobinfo/index.js' |
import { jobInfoApi } from '@/api/jobinfo/index.js' |
||||||
import { uploadFile } from '@/api/system/file.js' |
|
||||||
|
|
||||||
export default { |
export default { |
||||||
// In data section |
|
||||||
data() { |
data() { |
||||||
return { |
return { |
||||||
id: '', |
id: '', // 政策ID |
||||||
// 选择器 |
|
||||||
// 日历控件 |
|
||||||
calendar: { |
|
||||||
// 做表单中键的缓存用 |
|
||||||
key: null |
|
||||||
}, |
|
||||||
jobInfoDetail: { |
jobInfoDetail: { |
||||||
title: '', |
name: '', |
||||||
jobDate: '', |
createTime: '', |
||||||
jobName: '靖宇', |
context: '' |
||||||
content: '' |
|
||||||
} |
} |
||||||
} |
} |
||||||
}, |
}, |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ... keep other existing methods ... |
|
||||||
onLoad(options) { |
onLoad(options) { |
||||||
if (options.id) { |
if (options.id) { |
||||||
this.id = options.id |
this.id = options.id |
||||||
this.getjobInfoDetail() |
this.getjobInfoDetail() |
||||||
} |
} |
||||||
}, |
|
||||||
// 添加 onReady 生命周期 |
|
||||||
onReady() { |
|
||||||
|
|
||||||
}, |
}, |
||||||
methods: { |
methods: { |
||||||
/** |
|
||||||
* 显示日历 |
|
||||||
* @param {Object} key 表单中需要赋值的键 |
|
||||||
*/ |
|
||||||
showCalendar(key) { |
|
||||||
this.$refs.calendarRef.open() |
|
||||||
this.calendar = { |
|
||||||
key |
|
||||||
} |
|
||||||
}, |
|
||||||
/** |
|
||||||
* 日历选中之后的回调 |
|
||||||
* @param {Object} e |
|
||||||
*/ |
|
||||||
confirmCalendar(e) { |
|
||||||
this.jobInfoDetail[this.calendar.key] = e.fulldate |
|
||||||
this.closeCalendar() |
|
||||||
}, |
|
||||||
/** |
|
||||||
* 关闭日历 |
|
||||||
*/ |
|
||||||
closeCalendar() { |
|
||||||
this.calendar = { |
|
||||||
key: null |
|
||||||
} |
|
||||||
}, |
|
||||||
// 获取详情数据 |
// 获取详情数据 |
||||||
async getjobInfoDetail() { |
async getjobInfoDetail() { |
||||||
try { |
try { |
||||||
console.log(JobInfoApi) |
const res = await jobInfoApi.getjobInfo(this.id) |
||||||
const res = await JobInfoApi.getJobInfo(this.id) |
|
||||||
if (res.code === 0) { |
if (res.code === 0) { |
||||||
this.jobInfoDetail = { |
this.jobInfoDetail = { |
||||||
...res.data, |
...res.data, |
||||||
jobDate: this.formatTime(res.data.jobDate) |
jobDate: this.formatTime(res.data.jobDate), |
||||||
} |
// 处理富文本内容,确保样式正确显示 |
||||||
|
context: this.formatRichText(res.data.context) |
||||||
// 使用 nextTick 确保组件已渲染 |
|
||||||
|
|
||||||
} |
|
||||||
} catch (error) { |
|
||||||
console.log(error) |
|
||||||
|
|
||||||
} |
|
||||||
}, |
|
||||||
|
|
||||||
// 打开日期选择器 |
|
||||||
openDatePicker() { |
|
||||||
uni.showDatePicker({ |
|
||||||
current: this.jobInfoDetail.jobDate, |
|
||||||
success: (res) => { |
|
||||||
this.jobInfoDetail.jobDate = this.formatTime(res.date) |
|
||||||
} |
|
||||||
}) |
|
||||||
}, |
|
||||||
|
|
||||||
// 编辑器初始化完成 |
|
||||||
initEditor(ctx) { |
|
||||||
this.ctx = ctx |
|
||||||
// 如果有内容,设置到编辑器 |
|
||||||
if (this.jobInfoDetail.content) { |
|
||||||
this.ctx.setContents({ |
|
||||||
html: this.jobInfoDetail.content |
|
||||||
}) |
|
||||||
} |
} |
||||||
}, |
|
||||||
|
|
||||||
// 编辑器内容变化 |
|
||||||
inputOver(e) { |
|
||||||
this.jobInfoDetail.content = e.html |
|
||||||
}, |
|
||||||
|
|
||||||
// 上传图片 |
|
||||||
upinImage(tempFiles, editorCtx) { |
|
||||||
uni.showLoading({ title: '上传中...' }) |
|
||||||
console.log('上传文件',tempFiles) |
|
||||||
console.log('上传文件',editorCtx) |
|
||||||
// 这里应该调用你的图片上传API |
|
||||||
// 示例代码,需要替换为实际的上传逻辑 |
|
||||||
tempFiles.forEach(async (item) => { |
|
||||||
uni.showLoading({ |
|
||||||
title: '上传中请稍后', |
|
||||||
mask: true |
|
||||||
}) |
|
||||||
|
|
||||||
console.log('上传item',item) |
|
||||||
const res = await uploadFile({ name: `file`, filePath: item.tempFilePath }) |
|
||||||
console.log('返回图片',res) |
|
||||||
editorCtx.insertImage({ |
|
||||||
src: res.data.url, |
|
||||||
width: '80%', // 默认不建议铺满宽度100%,预留一点空隙以便用户编辑 |
|
||||||
success: function () { |
|
||||||
uni.hideLoading() |
|
||||||
} |
|
||||||
}) |
|
||||||
}) |
|
||||||
}, |
|
||||||
|
|
||||||
// 超出最大长度 |
|
||||||
overMax() { |
|
||||||
uni.showToast({ |
|
||||||
title: '内容超出最大长度限制', |
|
||||||
icon: 'none' |
|
||||||
}) |
|
||||||
}, |
|
||||||
|
|
||||||
// 添加链接 |
|
||||||
addLink(e) { |
|
||||||
// 可以在这里处理链接添加逻辑 |
|
||||||
console.log('添加链接', e) |
|
||||||
}, |
|
||||||
|
|
||||||
// 导出HTML |
|
||||||
exportHtml(e) { |
|
||||||
this.jobInfoDetail.content = e.html |
|
||||||
}, |
|
||||||
|
|
||||||
// 保存方法修改 |
|
||||||
async handleSave() { |
|
||||||
if (!this.validateForm()) return |
|
||||||
|
|
||||||
try { |
|
||||||
// 获取最新的HTML内容 |
|
||||||
if (this.ctx) { |
|
||||||
this.ctx.getContents({ |
|
||||||
success: async (res) => { |
|
||||||
this.jobInfoDetail.content = res.html |
|
||||||
|
|
||||||
// 执行保存逻辑 |
|
||||||
await this.saveContent() |
|
||||||
} |
|
||||||
}) |
|
||||||
} else { |
|
||||||
// 如果编辑器未初始化,直接保存 |
|
||||||
await this.saveContent() |
|
||||||
} |
} |
||||||
} catch (error) { |
} catch (error) { |
||||||
uni.hideLoading() |
|
||||||
uni.showToast({ |
uni.showToast({ |
||||||
title: '保存失败', |
title: '获取详情失败', |
||||||
icon: 'none' |
icon: 'none' |
||||||
}) |
}) |
||||||
} |
} |
||||||
}, |
}, |
||||||
|
|
||||||
// 抽取保存逻辑为单独方法 |
|
||||||
async saveContent() { |
|
||||||
uni.showLoading({ title: '保存中...' }) |
|
||||||
|
|
||||||
// 根据是否有ID判断是新增还是更新 |
|
||||||
const saveApi = this.id ? |
|
||||||
JobInfoApi.updateJobInfo : |
|
||||||
JobInfoApi.createJobInfo |
|
||||||
|
|
||||||
const result = await saveApi(this.jobInfoDetail) |
|
||||||
uni.hideLoading() |
|
||||||
|
|
||||||
if (result.code === 0) { |
|
||||||
uni.showToast({ |
|
||||||
title: '保存成功', |
|
||||||
icon: 'success' |
|
||||||
}) |
|
||||||
setTimeout(() => { |
|
||||||
uni.navigateBack() |
|
||||||
}, 1500) |
|
||||||
} |
|
||||||
}, |
|
||||||
// 删除 |
|
||||||
handleDelete() { |
|
||||||
if (!this.id) { |
|
||||||
uni.navigateBack() |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
uni.showModal({ |
|
||||||
title: '提示', |
|
||||||
content: '确定要删除此工作汇报吗?', |
|
||||||
success: async (res) => { |
|
||||||
if (res.confirm) { |
|
||||||
try { |
|
||||||
uni.showLoading({ title: '删除中...' }) |
|
||||||
const result = await JobInfoApi.deleteJobInfo(this.id) |
|
||||||
uni.hideLoading() |
|
||||||
|
|
||||||
if (result.code === 0) { |
|
||||||
uni.showToast({ |
|
||||||
title: '删除成功', |
|
||||||
icon: 'success' |
|
||||||
}) |
|
||||||
setTimeout(() => { |
|
||||||
uni.navigateBack() |
|
||||||
}, 1500) |
|
||||||
} |
|
||||||
} catch (error) { |
|
||||||
uni.hideLoading() |
|
||||||
uni.showToast({ |
|
||||||
title: '删除失败', |
|
||||||
icon: 'none' |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
}) |
|
||||||
}, |
|
||||||
|
|
||||||
// 格式化时间 |
// 格式化时间 |
||||||
formatTime(time) { |
formatTime(time) { |
||||||
if (!time) return '' |
if (!time) return '' |
||||||
const date = new Date(time) |
const date = new Date(time) |
||||||
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}` |
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}` |
||||||
}, |
}, |
||||||
|
// 处理富文本内容 |
||||||
// 表单验证 |
formatRichText(html) { |
||||||
validateForm() { |
if (!html) return '' |
||||||
const { title, jobDate, content } = this.jobInfoDetail |
// 添加样式以确保图片自适应 |
||||||
if (!title) { |
return html.replace(/<img/gi, '<img style="max-width:100%;height:auto;display:block;"') |
||||||
uni.showToast({ title: '请输入标题', icon: 'none' }) |
}, |
||||||
return false |
// 返回上一页 |
||||||
} |
handleBack() { |
||||||
if (!jobDate) { |
uni.navigateBack() |
||||||
uni.showToast({ title: '请选择日期', icon: 'none' }) |
|
||||||
return false |
|
||||||
} |
|
||||||
if (!content) { |
|
||||||
uni.showToast({ title: '请输入汇报内容', icon: 'none' }) |
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
return true |
|
||||||
} |
} |
||||||
} |
} |
||||||
} |
} |
||||||
</script> |
</script> |
||||||
|
|
||||||
<style lang="scss"> |
<style lang="scss"> |
||||||
.view { |
.jobInfo-detail { |
||||||
height: 100vh; |
height: 100vh; |
||||||
display: flex; |
|
||||||
flex-direction: column; |
|
||||||
background: #fff; |
background: #fff; |
||||||
|
|
||||||
} |
|
||||||
.content { |
|
||||||
flex: 1; |
|
||||||
padding: 12px; |
|
||||||
padding-bottom: calc(156rpx + 12px); // 修改底部内边距 |
|
||||||
display: flex; |
|
||||||
flex-direction: column; |
|
||||||
gap: 12px; |
|
||||||
height: calc(100vh - 156rpx - 12px); |
|
||||||
overflow-y: auto; |
overflow-y: auto; |
||||||
background: #F9F9F9; |
|
||||||
} |
.nav-bar { |
||||||
.block { |
position: fixed; |
||||||
|
top: 0; |
||||||
|
left: 0; |
||||||
|
right: 0; |
||||||
|
height: 88rpx; |
||||||
|
background: #fff; |
||||||
display: flex; |
display: flex; |
||||||
flex-flow: row nowrap; |
|
||||||
background-color: #fff; |
|
||||||
padding: 12px; |
|
||||||
border-radius: 8px; |
|
||||||
justify-content: space-between; |
|
||||||
align-items: center; |
align-items: center; |
||||||
gap: 12px; |
padding: 0 30rpx; |
||||||
margin-bottom: 12px; // 添加底部间距 |
border-bottom: 1rpx solid #eee; |
||||||
|
z-index: 100; |
||||||
|
|
||||||
&:last-child { |
.back-btn { |
||||||
margin-bottom: 0; // 最后一个block不需要底部间距 |
font-size: 28rpx; |
||||||
|
color: #333; |
||||||
} |
} |
||||||
|
|
||||||
.value { |
.title { |
||||||
flex: 1; |
|
||||||
display: flex; |
|
||||||
flex-flow: row nowrap; |
|
||||||
justify-content: space-between; |
|
||||||
gap: 4px; |
|
||||||
|
|
||||||
.input-value { |
|
||||||
flex: 1; |
flex: 1; |
||||||
text-align: right; |
text-align: center; |
||||||
white-space: nowrap; |
font-size: 32rpx; |
||||||
text-overflow: ellipsis; |
font-weight: bold; |
||||||
cursor: auto; |
padding-right: 60rpx; |
||||||
display: block; |
|
||||||
font-family: UICTFontTextStyleBody; |
|
||||||
height: 1.4rem; |
|
||||||
min-height: 1.4rem; |
|
||||||
overflow: hidden; |
|
||||||
} |
} |
||||||
} |
} |
||||||
} |
|
||||||
|
|
||||||
.content-block { |
|
||||||
flex-direction: column; |
|
||||||
align-items: flex-start; |
|
||||||
|
|
||||||
.content-wrap { |
.content-wrap { |
||||||
width: 100%; |
padding: 30rpx 30rpx 30rpx; |
||||||
margin-top: 12px; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
::v-deep .uni-easyinput__content-input { |
|
||||||
flex: 1; |
|
||||||
text-align: right; |
|
||||||
white-space: nowrap; |
|
||||||
text-overflow: ellipsis; |
|
||||||
cursor: auto; |
|
||||||
display: block; |
|
||||||
font-family: UICTFontTextStyleBody; |
|
||||||
height: 1.4rem; |
|
||||||
min-height: 1.4rem; |
|
||||||
overflow: hidden; |
|
||||||
padding-right: 0; |
|
||||||
} |
|
||||||
|
|
||||||
::v-deep .uni-easyinput__placeholder-class { |
.jobInfo-title { |
||||||
flex: 1; |
font-size: 36rpx; |
||||||
text-align: right; |
font-weight: bold; |
||||||
white-space: nowrap; |
color: #333; |
||||||
text-overflow: ellipsis; |
margin-bottom: 20rpx; |
||||||
cursor: auto; |
} |
||||||
display: block; |
|
||||||
font-family: UICTFontTextStyleBody; |
|
||||||
height: 1.4rem; |
|
||||||
min-height: 1.4rem; |
|
||||||
font-size: 14px; |
|
||||||
overflow: hidden; |
|
||||||
} |
|
||||||
|
|
||||||
.operation { |
.jobInfo-time { |
||||||
height: 112rpx; // 调整实际按钮区域高度 |
font-size: 24rpx; |
||||||
position: fixed; |
color: #999; |
||||||
left: 0; |
margin-bottom: 40rpx; |
||||||
right: 0; |
} |
||||||
bottom: 34px; |
|
||||||
padding: 12px; |
|
||||||
display: flex; |
|
||||||
align-items: center; |
|
||||||
justify-content: center; |
|
||||||
gap: 12px; |
|
||||||
background: #FFFFFF; |
|
||||||
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05); |
|
||||||
margin-top: 12px; // 添加顶部间距 |
|
||||||
|
|
||||||
.btn { |
.jobInfo-content { |
||||||
flex: 1; |
|
||||||
height: 88rpx; |
|
||||||
line-height: 88rpx; |
|
||||||
font-size: 28rpx; |
font-size: 28rpx; |
||||||
border-radius: 8px; |
line-height: 1.8; |
||||||
text-align: center; |
color: #333; |
||||||
} |
|
||||||
|
|
||||||
.delete { |
:deep(img) { |
||||||
background: #F8285A; |
max-width: 100%; |
||||||
color: #fff; |
height: auto; |
||||||
} |
} |
||||||
|
|
||||||
.save { |
:deep(p) { |
||||||
background: #17C653; |
margin-bottom: 20rpx; |
||||||
color: #fff; |
} |
||||||
|
} |
||||||
} |
} |
||||||
} |
|
||||||
|
|
||||||
::v-deep .editor-container { |
|
||||||
border-radius: 8px; |
|
||||||
background-color: var(--LightMode-Grey-Grey-100, #F9F9F9); |
|
||||||
} |
} |
||||||
</style> |
</style> |
@ -0,0 +1,143 @@ |
|||||||
|
<template> |
||||||
|
<view class="jobInfo-detail"> |
||||||
|
|
||||||
|
|
||||||
|
<!-- 详情内容区 --> |
||||||
|
<view class="content-wrap"> |
||||||
|
<view class="jobInfo-title">{{ jobInfoDetail.title }}</view> |
||||||
|
<view class="jobInfo-time">生效时间:{{ jobInfoDetail.jobDate }}</view> |
||||||
|
|
||||||
|
<!-- 富文本内容 --> |
||||||
|
<view class="jobInfo-content"> |
||||||
|
<rich-text :nodes="jobInfoDetail.context"></rich-text> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import { jobInfoApi } from '@/api/jobinfo/index.js' |
||||||
|
|
||||||
|
export default { |
||||||
|
data() { |
||||||
|
return { |
||||||
|
id: '', // 政策ID |
||||||
|
jobInfoDetail: { |
||||||
|
name: '', |
||||||
|
createTime: '', |
||||||
|
context: '' |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
onLoad(options) { |
||||||
|
if (options.id) { |
||||||
|
this.id = options.id |
||||||
|
this.getjobInfoDetail() |
||||||
|
} |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
// 获取详情数据 |
||||||
|
async getjobInfoDetail() { |
||||||
|
try { |
||||||
|
const res = await jobInfoApi.getjobInfo(this.id) |
||||||
|
if (res.code === 0) { |
||||||
|
this.jobInfoDetail = { |
||||||
|
...res.data, |
||||||
|
jobDate: this.formatTime(res.data.jobDate), |
||||||
|
// 处理富文本内容,确保样式正确显示 |
||||||
|
context: this.formatRichText(res.data.context) |
||||||
|
} |
||||||
|
} |
||||||
|
} catch (error) { |
||||||
|
uni.showToast({ |
||||||
|
title: '获取详情失败', |
||||||
|
icon: 'none' |
||||||
|
}) |
||||||
|
} |
||||||
|
}, |
||||||
|
// 格式化时间 |
||||||
|
formatTime(time) { |
||||||
|
if (!time) return '' |
||||||
|
const date = new Date(time) |
||||||
|
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}` |
||||||
|
}, |
||||||
|
// 处理富文本内容 |
||||||
|
formatRichText(html) { |
||||||
|
if (!html) return '' |
||||||
|
// 添加样式以确保图片自适应 |
||||||
|
return html.replace(/<img/gi, '<img style="max-width:100%;height:auto;display:block;"') |
||||||
|
}, |
||||||
|
// 返回上一页 |
||||||
|
handleBack() { |
||||||
|
uni.navigateBack() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style lang="scss"> |
||||||
|
.jobInfo-detail { |
||||||
|
height: 100vh; |
||||||
|
background: #fff; |
||||||
|
overflow-y: auto; |
||||||
|
|
||||||
|
.nav-bar { |
||||||
|
position: fixed; |
||||||
|
top: 0; |
||||||
|
left: 0; |
||||||
|
right: 0; |
||||||
|
height: 88rpx; |
||||||
|
background: #fff; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
padding: 0 30rpx; |
||||||
|
border-bottom: 1rpx solid #eee; |
||||||
|
z-index: 100; |
||||||
|
|
||||||
|
.back-btn { |
||||||
|
font-size: 28rpx; |
||||||
|
color: #333; |
||||||
|
} |
||||||
|
|
||||||
|
.title { |
||||||
|
flex: 1; |
||||||
|
text-align: center; |
||||||
|
font-size: 32rpx; |
||||||
|
font-weight: bold; |
||||||
|
padding-right: 60rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.content-wrap { |
||||||
|
padding: 30rpx 30rpx 30rpx; |
||||||
|
|
||||||
|
.jobInfo-title { |
||||||
|
font-size: 36rpx; |
||||||
|
font-weight: bold; |
||||||
|
color: #333; |
||||||
|
margin-bottom: 20rpx; |
||||||
|
} |
||||||
|
|
||||||
|
.jobInfo-time { |
||||||
|
font-size: 24rpx; |
||||||
|
color: #999; |
||||||
|
margin-bottom: 40rpx; |
||||||
|
} |
||||||
|
|
||||||
|
.jobInfo-content { |
||||||
|
font-size: 28rpx; |
||||||
|
line-height: 1.8; |
||||||
|
color: #333; |
||||||
|
|
||||||
|
:deep(img) { |
||||||
|
max-width: 100%; |
||||||
|
height: auto; |
||||||
|
} |
||||||
|
|
||||||
|
:deep(p) { |
||||||
|
margin-bottom: 20rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
</style> |
@ -1,131 +0,0 @@ |
|||||||
## 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组件改版封装,所以官方有的功能都有,官方能兼容的也都兼容 |
|
||||||
|
|
@ -1,825 +0,0 @@ |
|||||||
<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> |
|
@ -1,140 +0,0 @@ |
|||||||
<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> |
|
@ -1,152 +0,0 @@ |
|||||||
<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> |
|
@ -1,852 +0,0 @@ |
|||||||
<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', // 两端对齐 |
|
||||||
'lineHeight', // 行间距 |
|
||||||
'letterSpacing', // 字间距 |
|
||||||
'marginTop', // 段前距 |
|
||||||
'marginBottom', // 段后距 |
|
||||||
'fontFamily', // 字体 |
|
||||||
'fontSize', // 字号 |
|
||||||
'color', // 文字颜色 |
|
||||||
'backgroundColor', // 背景颜色 |
|
||||||
'date', // 日期 |
|
||||||
'listCheck', // 待办 |
|
||||||
'listOrdered', // 有序列表 |
|
||||||
'listBullet', // 无序列表 |
|
||||||
'indentInc', // 增加缩进 |
|
||||||
'indentDec', // 减少缩进 |
|
||||||
'divider', // 分割线 |
|
||||||
'scriptSub', // 下标 |
|
||||||
'scriptSuper', // 上标 |
|
||||||
'direction', // 文本方向 |
|
||||||
'image', // 图片 |
|
||||||
'video', // 视频 |
|
||||||
'link', // 超链接 |
|
||||||
'undo', // 撤销 |
|
||||||
'redo', // 重做 |
|
||||||
'removeFormat', // 清除格式 |
|
||||||
'clear', // 清空 |
|
||||||
'export' // 导出 |
|
||||||
], |
|
||||||
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 '@/uni_modules/sp-editor/icons/editor-icon.css'; |
|
||||||
@import '@/uni_modules/sp-editor/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> |
|
@ -1,24 +0,0 @@ |
|||||||
@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
@ -1,83 +0,0 @@ |
|||||||
{ |
|
||||||
"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" |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,19 +0,0 @@ |
|||||||
# 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
@ -1,132 +0,0 @@ |
|||||||
// 标识必须独一无二 - 标识是为了使用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; |
|
||||||
}); |
|
||||||
} |
|
Loading…
Reference in new issue