者 | 大澈
大家好,我是大澈!
今天的问题,来自于上周末问题留言的朋友 嘻嘻哈哈。
欢迎大家在周末的问题留言推文中,积极进行问题留言,把这周工作日遇到的问题,分享给大家瞧瞧,或者直接进问答群,一起交流唠唠。
遇到难题,遇到有共鸣的问题,一起讨论,一起沉淀,一起成长。
ONE
需求分析,问题描述
一、需求
使用富文本进行内容编辑,要求自定义工具栏菜单顺序及其分组,并且要求自定义选择图片、自定义选择视频。
二、问题
1、如何配置开始使用?
2、如何自定义工具栏菜单的展示?
3、如何自定义工具栏内置菜单的功能?
4、如何自定义扩展新功能菜单?
TWO
解决问题,答案速览
实现代码如下,复制粘贴即可直接使用。
如果你有时间,具体问题梳理、代码分析、知识总结,可见第三部分。
一、配置开始使用
1、下载依赖
npm i @wangeditor/editor @wangeditor/editor-for-vue
2、引入css和内置组件
// 引入 css
import '@wangeditor/editor/dist/css/style.css'
// 引入 组件
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
// 引入 接口类型
import { IDomEditor, IEditorConfig } from "@wangeditor/editor";
3、使用
<template>
<div style="border: 1px solid #ccc">
<Toolbar
style="border-bottom: 1px solid #ccc"
:editor="editorRef"
:defaultConfig="toolbarConfig"
:mode="mode"
/>
<Editor
style="height: 500px; overflow-y: hidden;"
v-model="valueHtml"
:defaultConfig="editorConfig"
:mode="mode"
@onCreated="handleCreated"
/>
</div>
</template>
<script lang="ts" setup>
import "@wangeditor/editor/dist/css/style.css";
import { Editor, Toolbar } from "@wangeditor/editor-for-vue";
import { IDomEditor, IEditorConfig } from "@wangeditor/editor";
import { onBeforeUnmount, ref, shallowRef, onMounted } from 'vue'
// 编辑器实例,必须用 shallowRef
const editorRef=shallowRef()
// 内容 HTML
const valueHtml=ref('<p>hello</p>')
// 模拟 ajax 异步获取内容
onMounted(()=> {
setTimeout(()=> {
valueHtml.value='<p>模拟 Ajax 异步设置内容</p>'
}, 1500)
})
// 工具栏配置
const toolbarConfig={
toolbarKeys: []
}
// 编辑器配置
const editorConfig={
placeholder: '请输入内容...',
MENU_CONF: {}
}
// 组件销毁时,也及时销毁编辑器
onBeforeUnmount(()=> {
const editor=editorRef.value
if (editor==null) return
editor.destroy()
})
// 组件创建时
const handleCreated=(editor)=> {
editorRef.value=editor // 记录 editor 实例,重要!
}
</script>
二、自定义工具栏菜单的展示
工具栏菜单的展示,如菜单的顺序和分组、添加删除。
如果想要自定义,只需修改工具栏配置对象的toolbarKeys属性。toolbarKeys属性值是一个数组,内部填写菜单的key,使用官方API接口editor.getAllMenuKeys()可查询全部内置菜单的key。
// 工具栏配置
const toolbarConfig={
toolbarKeys: [
// 一些常用的菜单 key
'bold', // 加粗
'italic', // 斜体
'through', // 删除线
'underline', // 下划线
'bulletedList', // 无序列表
'numberedList', // 有序列表
'color', // 文字颜色
'insertLink', // 插入链接
'fontSize', // 字体大小
'lineHeight', // 行高
'uploadImage', // 上传图片
'uploadVideo',//上传视频
'delIndent', // 缩进
'indent', // 增进
'deleteImage',//删除图片
'divider', // 分割线
'insertTable', // 插入表格
'justifyCenter', // 居中对齐
'justifyJustify', // 两端对齐
'justifyLeft', // 左对齐
'justifyRight', // 右对齐
'undo', // 撤销
'redo', // 重做
'clearStyle', // 清除格式
'fullScreen' // 全屏
]
}
三、自定义工具栏内置菜单的功能
工具栏菜单的功能,如链接、上传图片、上传视频。
如果想要自定义,只需修改编辑器配置对象的MENU_CONF属性。不同的功能都对应着不同的MENU_CONF属性值,这里我以问题提出者的问题为示例,具体请参考官方文档,写的非常不错的,放下面吧。
https://www.wangeditor.com/v5/menu-config.html
// 编辑器配置
const editorConfig={
placeholder: '请输入内容...',
MENU_CONF: {
// 上传图片
uploadImage: {
// 自定义选择图片
async customBrowseAndUpload(insertFn: InsertFnType) {
// 打开图片素材库
photoGalleryDialogVisible.value=true
},
},
// 上传视频
uploadVideo: {
// 自定义选择视频
async customBrowseAndUpload(insertFn: InsertFnType) {
// 打开视频素材库
videoGalleryDialogVisible.value=true
},
},
}
}
四、自定义扩展新功能菜单
1、定义菜单class
目前可以自定义扩展的功能菜单有按钮、下拉、下拉面板、模态框。
新建myButtonMenu.ts文件,把下面代码放进去。
import { IButtonMenu, IDomEditor } from '@wangeditor/editor'
class MyButtonMenu implements IButtonMenu { // TS 语法
// class MyButtonMenu { // JS 语法
constructor() {
this.title='My menu title' // 自定义菜单标题
// this.iconSvg='<svg>...</svg>' // 可选
this.tag='button'
}
// 获取菜单执行时的 value ,用不到则返回空 字符串或 false
getValue(editor: IDomEditor): string | boolean { // TS 语法
// getValue(editor) { // JS 语法
return ' hello '
}
// 菜单是否需要激活(如选中加粗文本,“加粗”菜单会激活),用不到则返回 false
isActive(editor: IDomEditor): boolean { // TS 语法
// isActive(editor) { // JS 语法
return false
}
// 菜单是否需要禁用(如选中 H1 ,“引用”菜单被禁用),用不到则返回 false
isDisabled(editor: IDomEditor): boolean { // TS 语法
// isDisabled(editor) { // JS 语法
return false
}
// 点击菜单时触发的函数
exec(editor: IDomEditor, value: string | boolean) { // TS 语法
// exec(editor, value) { // JS 语法
if (this.isDisabled(editor)) return
editor.insertText(value) // value 即 this.value(editor) 的返回值
}
}
2、注册和插入菜单
定义菜单key时,要保证key唯一,不能和内置菜单key重复。插入菜单时,将对应的菜单key放在toolbarKeys想要的顺序位置即可。
import { Boot } from '@wangeditor/editor'
import MyButtonMenu from './myButtonMenu'
// 工具栏配置
const toolbarConfig={
toolbarKeys: [
// 插入菜单key
'menu1',
]
}
// 组件创建时
const handleCreated=(editor: IDomEditor)=> {
editorRef.value=editor;
const menu1Conf={
key: 'menu1', // 定义 menu key :要保证唯一、不重复(重要)
factory() {
return new MyButtonMenu() // 替换为你菜单的 class
},
}
// 注册菜单
Boot.registerMenu(menu1Conf)
};
THREE
问题解析,知识总结
一、如何配置开始使用?
补充几点注意事项吧,这也是官方提醒:
1、editorRef 必须用 shallowRef定义。具体原因待研究,用ref似乎也行。
2、组件销毁时,要及时销毁编辑器。
二、如何自定义工具栏菜单的展示?
这里补充几个官方API:
1、editor.getAllMenuKeys() 查询编辑器注册的所有菜单 key (可能有的不在工具栏上)
2、toolbar.getConfig().toolbarKeys 查看当前菜单排序和分组
3、这块其它API挺鸡肋的,不用看了就,写上搞得代码很乱,直接在工具栏配置对象里操作就好了,比较清晰,自我感觉哈。
import { DomEditor } from '@wangeditor/editor'
const toolbar=DomEditor.getToolbar(editor)
const toolbarConfig=toolbar.getConfig()
// 查看当前菜单排序和分组
console.log( toolbarConfig.toolbarKeys )
三、如何自定义工具栏内置菜单的功能?
这里有通用的操作,参考官方,分两步实现:
四、如何自定义扩展新功能菜单?
这里官方提供了三处参考:定义新菜单、定义插件、定义新元素。
我认为定义新菜单是比较常用的,这样其实已经就很灵活了。至于其他两项有需求的朋友,可以自行研究一番,可能比较烧哈。
最后插一句,WangEditor这个库,写的真是挺不错的!
- END -
里对于设置支付宝账号和配置就不做说明,网上很多配置信息和应用上线的文章
做第三方接口首先得看官方文档,支付宝的官方文档非常清晰:
首先登录支付宝的开放文档,下载支付宝SDK和demo,链接:
https://docs.open.alipay.com/54/106370/
将jar包导入项目,在Java服务端生成订单信息:
//实例化客户端
AlipayClient alipayClient=new DefaultAlipayClient("https://openapi.alipay.com/gateway.do", APP_ID, APP_PRIVATE_KEY, "json", CHARSET, ALIPAY_PUBLIC_KEY, "RSA2");
//实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.pay
AlipayTradeAppPayRequest request=new AlipayTradeAppPayRequest();
//SDK已经封装掉了公共参数,这里只需要传入业务参数。以下方法为sdk的model入参方式(model和biz_content同时存在的情况下取biz_content)。
AlipayTradeAppPayModel model=new AlipayTradeAppPayModel();
model.setBody("我是测试数据");
model.setSubject("App支付测试Java");
model.setOutTradeNo(outtradeno);//更换为自己的订单编号
model.setTimeoutExpress("30m");
model.setTotalAmount("0.01");//订单价格
model.setProductCode("QUICK_MSECURITY_PAY");
request.setBizModel(model);
request.setNotifyUrl("商户外网可以访问的异步地址");//回调地址不可以带参数
String orderStr="";
try {
//这里和普通的接口调用不同,使用的是sdkExecute
AlipayTradeAppPayResponse response=alipayClient.sdkExecute(request);
orderStr=response.getBody();
System.out.println(orderStr);//就是orderString 可以直接给客户端请求,无需再做处理。
} catch (AlipayApiException e) {
e.printStackTrace();
}
说的简直太详细,只需要替换自己的订单ID和价格就可以了,这里我们只需要把orderStr返回给客户端就可以了,我是通过json返回的,当然客户端完成支付之后,服务端还需要知道支付结果,或者将支付的订单信息存入数据库,那么我们需要设置回调地址,即setNotifyUrl,支付宝服务端通过这个地址将支付结果返回给后台服务端,在这里我们可以更改订单状态,插入订单信息等等。(注:这个的回调地址必须是外网可以访问的,不然收不到支付宝返回的信息,这个地址即我们在开发配置中设置的授权回调地址)
回调页面内容严格按照支付宝规定:
<%@ page language="java" contentType="text/html; charset=gbk" pageEncoding="gbk"%>
<%@page import="com.alipay.api.internal.util.AlipaySignature"%>
<%@ page import="java.util.*"%>
<%@ page import="java.util.Map"%>
<%@ page import="com.alipay.*"%>
<%@ page import="com.alipay.api.*"%>
<%
//获取支付宝POST过来反馈信息
Map<String,String> params=new HashMap<String,String>();
System.out.println("异步通知参数:");
Map<String,String[]> requestParams=request.getParameterMap();
for (Iterator<String> iter=requestParams.keySet().iterator(); iter.hasNext();) {
String name=(String) iter.next();
String[] values=(String[]) requestParams.get(name);
String valueStr="";
for (int i=0; i < values.length; i++) {
valueStr=(i==values.length - 1) ? valueStr + values[i]
: valueStr + values[i] + ",";
}
if(name.equals("trade_status")){
System.out.println("交易状态为:"+valueStr);
}
//乱码解决,这段代码在出现乱码时使用。如果mysign和sign不相等也可以使用这段代码转化
//valueStr=new String(valueStr.getBytes("ISO-8859-1"), "gbk");
params.put(name, valueStr);
}
System.out.println("params"+params);
String pk="自己的支付宝公钥";
//获取支付宝的通知返回参数
//商户订单号
String out_trade_no=new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"),"UTF-8");
//支付宝交易号
String trade_no=new String(request.getParameter("trade_no").getBytes("ISO-8859-1"),"UTF-8");
//客户订单编号
String auth_app_id=new String(request.getParameter("auth_app_id").getBytes("ISO-8859-1"),"UTF-8");
//买家登录支付宝id
String buyer_logon_id=new String(request.getParameter("buyer_logon_id").getBytes("ISO-8859-1"),"UTF-8");
//交易时间
String gmt_payment=new String(request.getParameter("gmt_payment").getBytes("ISO-8859-1"),"UTF-8");
//交易金额
String invoice_amount=new String(request.getParameter("invoice_amount").getBytes("ISO-8859-1"),"UTF-8");
boolean verify_result=AlipaySignature.rsaCheckV1(params,pk, "UTF-8", "RSA2");
//这里可以做处理修改订单状态
if(verify_result){//验证成功
//支付成功只需要返回success
System.out.println(1);
out.println("success"); //请不要修改或删除
}else{//验证失败
System.out.println(2);
out.println("fail");
}
%>
需要修改订单状态时在回调页面根据out_trade_no改变订单状态就可以。
xcel 中除了使用查找替换功能批量替换字符外,还可以使用文本替换类函数将字符串中的部分或全部内容替换成新的字符串。文本替换类函数包括SUBSTITUTE函数、REPLACE函数,以及用于区分双字节字符的REPLACEB函数。
一、 字符替换函数SUBSTITUTE
SUBSTITUTE函数用于将字符串中指定的字符替换为新的文本字符串。函数语法如下SUBSTITUTE(text,old text,new_text,[instance_num])
第一参数text是必需参数,为需要替换其中字符的原始文本或单元格引用。
第二参数old_text是必需参数,为需要被替换的“旧文本”。如果第一参数的字符串中入包含该参数的字符串,则返回原始文本。
第三参数new_text是必需参数,为用于替换的“新文本”。如果该参数为空文本或省路参数的值仅保留参数之前的逗号时,相当于将需要替换的“旧文本”删除。
第四参数instance_num是可选参数,表示替换第几个“旧文本”。如果省略该参数,所有“旧文本”都会被替换。示例如下:
例:使用SUBSTITUTE函数计算平均分
上图中,B列的数据记录不规范,有的单元格中仅包含数字,有的单元格最后包含“分”字,需要在D2单元格中计算平均分。
在D2单元格中输入以下数组公式,按<Ctrl+Shift+Enter>组合键。
{=ROUND(AVERAGE(--SUBSTITUTE(B2:B7,"分",)),2)}
公式中SUBSTITUTE函数的第三参数省略,表示将B2:B7单元格中的“分”字替换为空。不包含“分”字的单元格将不受影响,返回原有的内容。使用“--”(两个负号)将SUBSTITUTE函数的结果由文本转换为数值,再由AVERAGE函数进行求平均数,用ROUND函数保留二位小数。
提示:
本例仅作为SUBSTITUTE函数的一项使用方法说明,不代表所有不规范的数据都能够通过函数的方法完成计算。实际输入数据时可将不同类别的数据单独一列存放,数值后面不加文本。如果使用类似“1箱54只”“3包22个”的数据输入形式,将对后续的汇总带来极大的麻烦。
例:使用SUBSTITUTE函数计算部门人数
下图为某单位人员名单的部分内容,B列中每个单元格中有多个人员编号,中间用半角逗号作为分隔。需要在C列计算每个部门的人数。
在C2单元格中输入以下公式,向下复制到C6单元格。
=LEN(B2)-LEN(SUBSTITUTE(B2,”,",))+1
公式第一部分LEN(B2)用于计算B2单元格中的字符数。
公式第二部分先用SUBSTITUTE函数将B2单元格中的逗号“,”替换为空文本,等于将所有逗号“,”从原始文本中删除,再用LEN函数计算替换后的文本字符数。
最后用第一部分的原始字符数减去逗号“,”被删数后的字符数,即得到原始字符串中一共有几个逗号“,”。由于每个单元格中的人数总是比逗号数量多一个,因此,最后再在公式中加1,即得到每个部门的人数。
例:借助SUBSTITUTE函数提取产品批号中的工厂码
下图为某公司产品的批号,其格式为字母数字的组合,中间用“_”分隔不同的信息。其中第二个和第三个“_”之间的内容为产品的工厂码,需要将其提取至B列。
在B2单元格中输入以下公式,向下复制到B5单元格。
=TRIM(MID(SUBSTITUTE(A2,"-",REPT(” ",99)),99*2,99))
REPT函数将指定文本重复多次组成字符串,基本语法如下。
REPT(text,number_times)
第一参数text为需要重复的内容,可以是字符串或单元格引用
第二参数number_times为将第一参数重复的次数。
公式中REPT(“ "99)的作用是生成99个空格。
用SUBSTITUTE函数将A2单元格中的“_”替换为99个空格。这里用99个空格的目的是将原始字符中的各段文本用足够多的空格分开构成新的字符串。新的字符串相当于如下。
DSF (99个空格) 25 (99个空格) D24 (99个空格) 50
因为要提取的字符在原始文本的第3段,所以用MID函数在以上字符串中第99*2个字符开始,提取99个字符,返回的结果相当于如下字符串。
(9个空格) D24 (87个空格)
最后再用TRIM函数清除文本两端多余的空格,即得到需要的工厂码。
二、用REPLACE函数替换字符串
REPLACE函数用于将部分文本字符串替换为新的字符串,与SUBSTITUTE函数的区别是,SUBSTITUTE函数是针对字符串中的指定字符内容进行替换,REPLACE函数是针对符串中的指定字符位置进行替换,其语法如下:
REPLACE(old_text,start_num,num_chars,new_text)
第一参数old_text表示要替换其部分字符的源文本。
第二参数start_num指定源文本中要替换为新字符的位置。
第三参数num_chars表示使用新字符串替换源字符串中的字符数,如果该参数为0或省略参数值,可以实现类似插入字符(串)的功能。
第四参数new_text表示用于替换源文本中字符的文本。
例:使用REPLACE函数隐藏手机号码中间4位
在下图中需要将B列手机号码中间4位数字用星号“****”隐藏。
在C2单元格中输入以下公式,向下复制到C10单元格。
=REPLACE(B2,4,4,”****”)
公式中使用REPLACE函数从B2单元格的第4个字符起,将4个字符替换为“****”
例:使用REPLACE函数将银行卡号分段显示
下图为某单位客户收款的银行卡号,为便于读取和核对,需要将银行卡号数字分段显示。
在B2单元格中输入以下公式,向下复制到B10单元格.
=REPLACE(REPLACE(REPLACE(REPLACE(A2,5,0," "),10,0," "),15,0," "),20,0," ")
REPLACE函数第三参数为0表示插入字符。公式中使用了四次REPLACE函数,第一次先用REPLACE函数在A2单元格第5个字符前插入一个空格。生成的新字符串再作为第二个REPLACE函数的第一参数,在新字符串的第10个字符前再次插入一个空格。依此类推,形成每四个数字为一组的分段显示方式。
三、用CLEAN函数和TRIM函数清理非打印字符和多余空格
部分从网页、ERP系统或从其他软件中导出的文本会存在一些非打印字符,影响正常数据查找及汇总计算。此外,由于数据录入时的疏忽,可能会在英文单词或中文姓名之间入多个空格。
CLEAN函数用于删除文本中的部分非打印字符(ASCII码的值为0~31)
使用TRIM函数能够清除文本中除了单词之间的单个空格外的所有空格。
它们的语法别为:
CLEAN(text)
TRIM(text)
四、使用NUMBERVALUE 函数转换不规范数字
在整理表格数据的过程中,经常会有一些不规范的数字影响数据的汇总分析。例如,在数字中混有空格,或者夹杂有全角数字及文本型数字等。对于文本数据,可以使用TRIM函数清理多余空格,使用ASC函数将全角字符转换为半角字符。对于数字内容,使用NUMBERVALUE函数可以兼容以上两种功能。
NUMBERVALUE函数较VALUE函数在功能上有一定的提升。该函数不仅可以实现VALUE函数日期转换为数值序列、文本型数字转换为数值型数字、全角数字转换为半角数字等功能,还可以处理混杂空格的数值及符号混乱等特殊情况。
对于“7430%”这样的数据,NUMBERVALUE函数能够将其转换为74.3而使用VALUE函数,则返回错误值#VALUE!。
*请认真填写需求信息,我们会在24小时内与您取得联系。