首页
归档
留言
广告合作
友链
美女主播
Search
1
博瑞GE车机升级/降级
5,146 阅读
2
Mac打印机设置黑白打印
4,517 阅读
3
修改elementUI中el-table树形结构图标
4,516 阅读
4
Mac客户端添加腾讯企业邮箱方法
4,351 阅读
5
intelliJ Idea 2022.2.X破解
4,060 阅读
Java
HarmonyOS Next
Web前端
微信开发
开发辅助
App开发
数据库
随笔日记
登录
/
注册
Search
标签搜索
Spring Boot
Java
Spring Cloud
Mac
mybatis
WordPress
Nacos
Spring Cloud Alibaba
Mybatis-Plus
jQuery
Java Script
asp.net
微信小程序
Sentinel
UniApp
MySQL
asp.net core
IntelliJ IDEA
Jpa
树莓派
Laughing
累计撰写
570
篇文章
累计收到
1,424
条评论
首页
栏目
Java
HarmonyOS Next
Web前端
微信开发
开发辅助
App开发
数据库
随笔日记
页面
归档
留言
广告合作
友链
美女主播
搜索到
104
篇与
的结果
2024-07-21
提取Html内容生成文章目录
最近在开发一款在线知识库系统,为了方便使用,提供了两种编辑器,一种是基于markdown的,一种是基于富文本的。基于markdown的编辑器,我们使用了MavonEditor插件,同时也是使用该插件进行markdown内容的展示,这个插件自带了目录功能,我们可以直接使用,感兴趣的可以看一下Vue使用mavon-editor插件实现Markdown文件编辑及预览为了保证使用体验的一致性,基于富文本编辑器的内容,我们也给他添加一个目录功能,目录结构的展示我们使用elementUI框架的el-tree组件。template代码template代码比较简单,因为是基于elementUI框架的el-tree组件的,所以需要先安装elementUI。<el-tree class="toc-tree" ref="tree" node-key="uuid" :data="tocTreeData" :props="tocDefaultProps" v-if="tocTreeData && tocTreeData.length>0" default-expand-all> <div class="custom-tree-node" slot-scope="{ node, data }"> <div @click="toDiv(data)">{{ data.text }}</div> </div> </el-tree>data代码其实看到上面template的代码,我们大概能猜测到data里面需要的内容tocTreeData: [], tocDefaultProps: { label: 'text', children: 'children' },tocTreeData是我们存储的目录树形的结构tocDefaultProps是el-tree组件用来配置展示内容及子节点的。js代码getCatalog()是入口方法,当我们在前端完成富文本内容的渲染后,就可以调用此方法,创建目录数据,进行展示。当然,提取目录的层级是根据我们的<h>标签进行提取的,可以自己定义提取的深度,比如我这里提取到了<h6>。toDiv()是点击目录时,用于页面滚动到对应位置的方法。getCatalog() { const h = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] const elements = document.querySelectorAll(h) let hElements = [] for (const key of elements) { if (h.indexOf(key.localName) > -1) { let text if (key.children && key.children.length) { text = this.getText(key.children) } else { text = key.innerHTML } hElements.push({ hLevel: parseInt(key.localName[1]), text, id: key.localName, uuid: key.id, offsetTop: key.offsetTop }) } } this.tocTreeData = this.toTree(hElements) }, getText(arr) { let result = null if (!arr.length) return for (let i = 0; i < arr.length; i++) { if (arr[i].children && arr[i].children.length) { result = this.getText(arr[i].children) } else { result = arr[i].innerHTML } } return result }, toTree(flatArr) { const tree = [] const copyArr = flatArr.map(function(item) { return item }) // 根据指定级别查找该级别的子孙级,并删除掉已经查找到的子孙级 const getChildrenByLevel = function(currentLevelItem, arr, level) { if (!currentLevelItem) { return } // 将level值转成负数,再进行比较 const minusCurrentLevel = -currentLevelItem.hLevel const children = [] let i = 0, len = arr.length for (; i < len; i++) { const levelItem = arr[i] if (-levelItem.hLevel < minusCurrentLevel) { children.push(levelItem) } else { // 只找最近那些子孙级 break } } // 从数组中删除已经找到的那些子孙级,以免影响到其他子孙级的查找 if (children.length > 0) { arr.splice(0, children.length) } return children } const getTree = function(result, arr, level) { // 首先将数组第一位移除掉,并添加到结果集中 let currentItem = arr.shift() currentItem.level = level result.push(currentItem) while (arr.length > 0) { if (!currentItem) { return } // 根据当前级别获取它的子孙级 const children = getChildrenByLevel(currentItem, arr, level) // 如果当前级别没有子孙级则开始下一个 if (children.length === 0) { currentItem = arr.shift() currentItem.level = level if (currentItem) { result.push(currentItem) } continue } currentItem.children = [] // 查找到的子孙级继续查找子孙级 getTree(currentItem.children, children, level + 1) } } getTree(tree, copyArr, 1) return tree }, toDiv(data) { document.getElementById('contentMainId').scrollTop = data.offsetTop - 20 },
2024年07月21日
829 阅读
0 评论
1 点赞
2024-07-19
150款CSS实现的加载中动画
每个里面可能都有多种动画,可以直接运行查看150款CSS实现的加载中动画.zip
2024年07月19日
822 阅读
0 评论
0 点赞
2024-07-17
Vue使用mavon-editor插件实现Markdown文件编辑及预览
因为开发在线知识库系统,因为主要面向研发人员,所以在文档编辑器上,更加倾向于使用markdown,基于vue的markdown编辑器有很多,在经过实际体验后,从易用性、美观性等综合考量,最重选择了mavon-editor。MavonEditor简介MavonEditor是一款基于Vue的Markdown编辑器,它结合了Markdown语法和Typora的实时预览功能,提供了一个所见即所得(WYSIWYG)的编辑体验。MavonEditor不仅支持基本的Markdown语法,还支持LaTeX公式、代码高亮、目录生成、自动目录锚点链接等功能,使其成为撰写技术文档、博客文章等的理想工具。但是MavonEditor也有一个缺点,就是它依赖的highlightjs、katex插件,默认是基于CDN的,当然我们可以改成基于本地的,这个后面再详细介绍。安装插件npm install mavon-editor --save引入因为我涉及在后端编辑,前端预览,所以直接选择了全局引入,编辑main.js// mavonEditor全局注册 import mavonEditor from 'mavon-editor' import 'mavon-editor/dist/css/index.css' Vue.use(mavonEditor)后端编辑页面<mavon-editor ref="md" v-model="form.content" @imgAdd="markdownImageAdd" :toolbars="toolbars" @imgDel="markdownImageDelete" class="content-show" codeStyle="docco" :ishljs="true" :externalLink="externalLink"/>v-model是绑定的内容imgAdd是图片上传后的回调,mavon-editor上传图片后,会转成base64,在回调方法内,我们可以拿到base64编码的图片信息,上传到服务器或OSS后进行存储。imgDel是删除图片后的回调,这里需要注意一点,如果我们是将图片上传到自己服务器,需要有个记录表记录一下,因为在后期编辑文档的时候,工具栏是没有删除图片按钮的,如果我们需要删除图片的话,就需要一个专门维护的地方。toolbars是配置工具栏按钮codeStyle配置代码高亮样式。可选的配色方案可以参考https://hinesboy.github.io/mavonEditor/src/lib/core/hljs/lang.hljs.css.jsishljs是否开启代码高亮。externalLink配置外链应用,如果我们不使用CDN时,需要配置这个data() { return { toolbars: { bold: true, // 粗体 italic: true, // 斜体 header: true, // 标题 underline: true, // 下划线 strikethrough: true, // 中划线 mark: true, // 标记 superscript: true, // 上角标 subscript: true, // 下角标 quote: true, // 引用 ol: true, // 有序列表 ul: true, // 无序列表 link: true, // 链接 imagelink: true, // 图片链接 code: true, // code table: true, // 表格 fullscreen: true, // 全屏编辑 readmodel: false, // 沉浸式阅读 htmlcode: true, // 展示html源码 help: false, // 帮助 /* 1.3.5 */ undo: true, // 上一步 redo: true, // 下一步 trash: true, // 清空 save: false, // 保存(触发events中的save事件) /* 1.4.2 */ navigation: false, // 导航目录 /* 2.1.8 */ alignleft: true, // 左对齐 aligncenter: true, // 居中 alignright: true, // 右对齐 /* 2.2.1 */ subfield: true, // 单双栏模式 preview: true, // 预览 boxShadow: false }, //加载本地资源 externalLink: { markdown_css: function() { // 这是你的markdown css文件路径 return '/markdown/github-markdown.min.css' }, hljs_js: function() { // 这是你的hljs文件路径 return '/highlightjs/highlight.min.js' }, hljs_css: function(css) { // 这是你的代码高亮配色文件路径 return '/highlightjs/styles/' + css + '.min.css' }, hljs_lang: function(lang) { // 这是你的代码高亮语言解析路径 return '/highlightjs/languages/' + lang + '.min.js' }, katex_css: function() { // 这是你的katex配色方案路径路径 return '/katex/katex.min.css' }, katex_js: function() { // 这是你的katex.js路径 return '/katex/katex.min.js' } }, } },methods: { //编辑器图片删除 markdownImageDelete(file) { let filePath = file[0] if (filePath.indexOf(this.baseApi) === 0) { filePath = filePath.replace(this.baseApi, '') } delImage(filePath).then(response => { if (response.code === '200') { this.$modal.msgSuccess('图片删除成功') } }) }, //编辑器插入图片 markdownImageAdd(fileIndex, file) { if (!file.articleId) { file.articleId = this.form.articleId } uploadImage(JSON.stringify(file)).then((res) => { if (res.code === 200) { this.$refs.md.$img2Url(fileIndex, this.baseApi + res.data) this.$modal.msgSuccess('图片上传成功') } else { this.$refs.md.$img2Url(fileIndex, '') } }).catch(() => { this.$refs.md.$img2Url(fileIndex, '') }) } }至此,我们后端编辑功能基本完成了前端预览界面<mavon-editor ref="mavonEditor" :editable="false" v-model="article.content" :defaultOpen="'preview'" :subfield="false" :toolbarsFlag="false" :navigation="true" codeStyle="docco" :ishljs="true" :scroll-style="true" :box-shadow="false" preview-background="#ffffff" :externalLink="externalLink"/>编辑页面相同的属性就不介绍了,介绍一下其他的。editable是否可编辑,因为我们是预览界面,所以设置成不可编辑defaultOpen在单栏(subfield=false)时默认展示区域, edit: 默认展示编辑区域,preview: 默认展示预览区域。subfield单栏还是双栏暂时,true: 双栏(编辑预览同屏), false: 单栏(编辑预览分屏)toolbarsFlag是否显示工具栏navigation是否展示目录scroll-style开启滚动条样式box-shadow开启边框阴影preview-background预览框背景颜色配置本地外链加载如果你想自己引入而不希望mavon-editor加载的话,可以将externalLink设置为false.如果想本地按需加载,你需要安装copy-webpack-plugin插件(npm install copy-webpack-plugin -D) 配置webpack如下所示: (假定webpack配置文件位于项目的/webpack/webpack.js, 而你希望将hljs以及markdown相关文件导出位于项目的/dist/highlightjs以及/dist/markdown目录之下, katex和上面一样)var CopyWebpackPlugin = require('copy-webpack-plugin'); module.exports = { // ... plugins: [ // ... new CopyWebpackPlugin([{ from: 'node_modules/mavon-editor/dist/highlightjs', to: path.resolve(__dirname, '../dist/highlightjs'), // 插件将会把文件导出于/dist/highlightjs之下 }, { from: 'node_modules/mavon-editor/dist/markdown', to: path.resolve(__dirname, '../dist/markdown'), // 插件将会把文件导出于/dist/markdown之下 }, { from: 'node_modules/mavon-editor/dist/katex', // 插件将会把文件导出 to: path.resolve(__dirname, '../dist/katex') }]), // ... ], // ... }然后你需要给mavon-editor设置externalLink 相关代码如下所示: (假定你的web根目录位于项目的/dist/, 你的网站是www.site.com, 那么 markdown, hljs_js, hljs_css, hljs_lang, katex_css, katex_js返回的是你的网站对应文件位置, 比如www.site.com/markdown/github-markdown.min.css 对应的文件应该位于项目的/dist/markdown/github-markdown.min.css)<template> <div id="app"> <mavon-editor :subfield = "subfield" :code_style="code_style" :ishljs="true" :externalLink="externalLink" ></mavon-editor> </div> </template> <script> export default { data () { return { subfield: true, code_style: 'solarized-dark', externalLink: { markdown_css: function() { // 这是你的markdown css文件路径 return '/markdown/github-markdown.min.css'; }, hljs_js: function() { // 这是你的hljs文件路径 return '/highlightjs/highlight.min.js'; }, hljs_css: function(css) { // 这是你的代码高亮配色文件路径 return '/highlightjs/styles/' + css + '.min.css'; }, hljs_lang: function(lang) { // 这是你的代码高亮语言解析路径 return '/highlightjs/languages/' + lang + '.min.js'; }, katex_css: function() { // 这是你的katex配色方案路径路径 return '/katex/katex.min.css'; }, katex_js: function() { // 这是你的katex.js路径 return '/katex/katex.min.js'; }, } } }, } </script>Notice: 如果你想禁用mavon-editor的自动加载, 可以将externalLink设置为false或externalLink中的某函数值设置为false 如:export default { // ... data() { return { externalLink: false, // 这里只能为`true`/`false`和一个`Object`, 如果为`true`代表全使用外链且自动加载, 如果为`false`则禁用,如果为`Object`则如上所示 } } // ... }或者:export default { // ... data() { return { externalLink: { hljs_css: function(css) { // 这是你的代码高亮配色文件路径 return '/highlightjs/styles/' + css + '.min.css'; }, katex_css: false, // `false`表示禁用自动加载,它也可以是个函数,如果它是个函数,那么这个函数应该返回一个可访问的`katex`的css路径字符串 // 我们没有设置`katex_js`, `hljs_js`, `hljs_lang`, `markdown_css`, `mavon-editor`会认为它的值为`true`,它会默认使用`cdnjs`相关外链加载 }, } } // ... }
2024年07月17日
940 阅读
0 评论
0 点赞
2024-07-01
推荐几款可以当做在线知识库的系统
在这个信息爆炸的时代,知识成为推动社会进步与个人成长的重要力量。而在线知识库,作为信息时代的智慧宝库,正日益成为学习、研究与创新不可或缺的平台。无论是企业内部的知识管理,还是面向公众的知识分享,一个结构化、易访问、内容丰富的在线知识库都是关键。本文将深入探讨如何高效构建和维护一个在线知识库,以促进知识的有效传播与利用。这篇文章,我们简要的介绍几款免费的可以当做在线知识库的系统,这里面有些是免费开源的,有些不是开源的还有一些有开源版本及收费版本,后续我们会针对每一个系统的使用方式进行详细的介绍。MrDoc觅思文档第一款要介绍的就是MrDoc觅思文档,这是我个人非常推荐的一个在线知识库系统,支持自托管、私有部署的。觅思文档分为两个版本【开源版】还有【专业版】,个人觉得开源版已经能够满足我们日常使用,开源版是基于GPLv3协议开源的。觅思文档的优点:支持创建在线文本文档、在线表格文档、在线 Office 文档(OnlyOffice),文档的创作简便且高效。丰富的权限控制。支持私有化部署。支持素材管理,也与第三方OSS支持集成。界面美观。觅思文档的缺点:不支持评论。Python版本较低,不知道现在有没有升级,当时因为低版本Python存在漏洞,被公司扫描出来,高版本的Python部署报错,因此放弃的。docsify这个在之前的文章中有介绍过,这里就不过多介绍了,感兴趣的可以移步dosify一个神奇的文档网站生成工具WordPressWordPress虽然不是专业的知识库系统,但是WordPress胜在丰富的插件支持,我们可以借助插件将WordPress打造成一个在线知识库系统。下面为大家分享几大常见的WordPress知识库插件BetterDocs是一个功能强大的WordPress知识库插件,易于使用且功能全面;带有可立即使用的WordPress知识库模板。BetterDocs还包括对带有小工具的页面构建器的支持。您还可以使用简码创建自己的布局,并在任何地方插入知识库文章。BetterDocs包括功能强大的搜索,浮动目录,一个知识库机器人,该机器人自动尝试查找答案以减少支持请求。该插件还包括基于用户角色的控制和见解,以查看用户正在寻找的内容。Heroic KB是一个易于使用的知识库WordPress插件,功能强大且功能齐全;可以在现有站点中添加可搜索的知识库,以便访问者可以轻松找到问题的答案。大家可以从功能强大的知识库插件获得所需的所有功能,例如内容分类,拖放内容排序,文章附件,显示小部件等。Heroic KB内置分析和用户反馈功能可以收集见解以改进现有文档,从而可以减少支持,节省时间并提高销量。weDocs是另一个出色的WordPress知识库和文档插件,易于使用,并允许您将文档页面分为小节,分层文档和标签;具有一个更简单的界面,可从一个屏幕管理所有文档。该插件不包括其他模板,但可以与所有流行的WordPress主题配合使用。weDocs知识库的布局简单而有效,并带有侧边栏导航和面包屑菜单。Echo Knowledge Base是 WordPress 最好的知识库插件之一,带有一个简单的设置向导,可以引导您完成插件设置;还可以获得多种布局供您选择,并提供自定义各个方面的选项。大家可以从基本、选项卡或以类别为中心的布局中进行选择,每个布局顶部都有一个快速搜索栏,可帮助用户快速找到答案;可以进一步按类别和标签组织文章。Encyclopedia / Glossary / Wiki是一个灵活的 WordPress 知识库插件,允许以词汇表格式组织文档;可以按字母顺序或按类别和标签组织文档和文章。Encyclopedia / Glossary / Wiki包括一个简单的模板来列出所有词汇表项,可以添加搜索小部件,为个别文章启用评论,并添加过滤索引以按字母表对项目进行排序。插件提供了一个很好的选项,可以以词汇表或百科全书的形式组织支持页面;支持WPML,可用于多语言网站。BasePress是一个有用的WordPress知识库和文档管理插件,可以轻松地为产品或服务构建支持页面和文档部分。BasePress带有三个模板,并带有一个高级即时搜索栏,可以帮助用户快速找到答案;可以使用简单的拖放界面组织文章,类别和标签中的文章,以对文章重新排序。BasePress可以为每个部分添加图像和描述,从而创建一个非常整洁的索引页面供用户浏览,还提供了简单的自定义选项,可以控制文章页面的外观。wiki.jsWiki.js 是一个基于 NodeJS 的现代、轻量级和强大的 wiki 应用程序。支持全文检索、用户权限管理、页面定制等功能,还具备良好的用户体验。其灵活的扩展性使得我们可以根据项目的实际需求对其进行定制和扩展。这个我没实际体验过,这里只大概介绍一下,感兴趣的可以安装体验一下。MinDoc基于beeego开发,原作者已经不再维护,基于MinDoc比较出名的站点我觉得可能就是书栈网了,并且书栈网也已经开源了。感兴趣的可以研究一下https://gitee.com/truthhun/BookStackWCPWCP 是一套BS架构的开源知识管理系统、知识库系统。它能提供团队知识库建设的一整套功能,从知识创建、知识更新、知识推送到知识评价、知识激励、知识统计以及基于以上功能权限控制等功能。WCP开源版本采用springMVC、spring、hibernate框架实现主要架构功能,由lucene提供全文检索功能,并使用了其他若干主流开源项目。数据库管理系统默认使用mysql。可以部署在tomcat等主流中间件服务器上。WCP可以通过对大语言模型接口的调用来为用户提供智能问答服务,该服务可以依赖知识库中的知识进行问题回答。其他CMS系统其实像网钛CMS、帝国CMS这些内容管理系统,我觉得都可以作为在线知识库使用。
2024年07月01日
309 阅读
0 评论
0 点赞
2024-05-30
ElementUI中一个页面多个el-descriptions列不对齐问题处理
使用ElementUI框架时,如果我们希望列表形式展示多个字段。可能经常使用Descriptions描述列表,如果一个页面只有一个Descriptions时,列宽显示是没有问题的,但是如果有多个Descriptions,你会发现,各个Descriptions之间的列宽,可能显示的不一致。为了保持各列宽度一致,我们可以设置contentStyle属性。<el-descriptions border :label-style="{ width: '110px' }" :contentStyle="content_style"> </el-descriptions> 然后在data里面,设置具体的content_stylecontent_style: { // 居左 'text-align': 'left', // 设置长度 width: '400px', // 排列第二行 'word-break': 'break-all' }
2024年05月30日
833 阅读
0 评论
1 点赞
2024-05-29
ElementUI中el-input-number实时监听值变化
在前端开发过程中,我们经常会遇到将金额转大写的情况,我们要实现的效果,是只要输入就实时计算大写,而不是等全部输入完成失去焦点后。如果使用ElementUI框架的el-input-number控件的@input事件,是无法达到实时显示效果的。html代码 <el-col :span="24"> <el-form-item label="本次报价" prop="quotationPrice"> <el-input-number :precision="2" :step="100" :max="10000000" :min="0" v-model="form.quotationPrice" style="width: 100%" controls-position="right" data-unit="元" placeholder="请输入本次报价" ref="refQuotationPrice" @input.native="convertPrice2CnyMoney" @change="convertPrice2CnyMoney4Change" /> </el-form-item> </el-col> <el-col :span="24"> <el-form-item label="总金额" prop="cnyMoney"> <div class="cnyMoney">{{ cnyMoney }}</div> </el-form-item> </el-col> JavaScript代码//本次报价金额实时转大写 convertPrice2CnyMoney(event) { let value = this.$refs.refQuotationPrice.displayValue; this.cnyMoney = numToCny(value); }, //本次报价金额实时转大写 convertPrice2CnyMoney4Change(val) { this.cnyMoney = numToCny(val); }
2024年05月29日
773 阅读
0 评论
0 点赞
2024-03-22
若依系统上传图片压缩
虽然标题里面有若依,实际上所有的Vue项目都能够适用的。最近基于若依系统做点东西,若依系统本身封装了一个图片上传组件ImageUpload,但是这个组件对我来说不太适用,最主要的问题就是这个组件是自动上传的,这样就会导致我们业务数据跟附件无法做到同步,比如我们新增一个单据,点了上传图片,此时图片已经上传到服务器,但是我并没有保存单据,这样就造成了部分垃圾数据。当然不是说不能处理,只是来说更加麻烦。先来说下我们目前做的功能需求吧:基于ElementUI的el-upload上传图片为了减少图片占用的空间(目前基于阿里云OSS,这个后期介绍),我们在前端上传图片之前在不影响图片展示质量的前提下,对图片进行压缩表单数据与图片文件同步上传可以单张图片上传也可以同时上传多张图片(以下我们以多张图片上传来说明)壹、提取图片压缩公共方法在ruoyi.js公共方法中,封装图片压缩方法/** 图片压缩,默认同比例压缩 * @param {Object} fileObj * 图片对象 * 回调函数有一个参数,base64的字符串数据 */ export function compress(fileObj, callback) { // console.log('压缩前文件大小', fileObj.size) try { const image = new Image() image.src = URL.createObjectURL(fileObj) image.onload = function () { const that = this // 默认按比例压缩 let w = that.width let h = that.height const scale = w / h w = fileObj.width || w h = fileObj.height || (w / scale) let quality = 0.5 // 默认图片质量为0.7 // 生成canvas const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d') // 创建属性节点 const anw = document.createAttribute('width') anw.nodeValue = w const anh = document.createAttribute('height') anh.nodeValue = h canvas.setAttributeNode(anw) canvas.setAttributeNode(anh) ctx.drawImage(that, 0, 0, w, h) // 图像质量 if (fileObj.quality && fileObj.quality <= 1 && fileObj.quality > 0) { quality = fileObj.quality } // quality值越小,所绘制出的图像越模糊 const data = canvas.toDataURL('image/jpeg', quality) // 压缩完成执行回调 const newFile = convertBase64UrlToBlob(data) callback(newFile) // console.log('压缩后文件信息', newFile) } } catch (e) { console.log('压缩失败!') } } // Base64 => 二进制(Blob) function convertBase64UrlToBlob(urlData) { // 去掉url的头,并转换为byte const bytes = window.atob(urlData.split(',')[1]) // 处理异常,将ascii码小于0的转换为大于0 const ab = new ArrayBuffer(bytes.length) const ia = new Uint8Array(ab) for (let i = 0; i < bytes.length; i++) { ia[i] = bytes.charCodeAt(i) } return new Blob([ab], {type: 'image/png'}) } 在main.js中挂载公共方法,方便在后面使用Vue.prototype.compress = compress贰、页面增加上传图片功能在表单中,增加上传图片功能<el-upload action="#" list-type="picture-card" :auto-upload="false" multiple :headers="headers" ref="carPhotoListRef" :on-preview="handlePictureCardPreview" :on-remove="handleRemove" :on-change="handleChange" :file-list="imageFileList" :disabled="form.inquiryStatus !== 'inquirying'" :class="{ hide: form.inquiryStatus !== 'inquirying' }" accept="image/bmp,image/jpg,image/png,image/svg,image/psd,image/webp,image/jpeg"> <i class="el-icon-plus"></i> </el-upload> <el-dialog :visible.sync="dialogImageVisible" :append-to-body="true"> <img width="100%" :src="dialogImageUrl" alt=""/> </el-dialog>因为我们要手工上传图片,所以需要对el-upload的属性进行配置action因为我们要收工上传,所以action需要设置成#auto-upload设置成false禁止自动上传headers是基于若依的认证,传递头部信息的on-preview预览图片on-remove删除图片同步处理我们后端绑定数据on-change图片改变的钩子,因为我们要手工上传图片,因此通过这个钩子完成图片的压缩其他的一些属性基本设置图片预览,限制上传图片类型的,按需设置即可。data里面主要设置图片预览、绑定图片列表等属性data() { return { headers: { Authorization: "Bearer " + getToken() }, //预览图片窗口是否可见 dialogImageVisible: false, //预览图片Url dialogImageUrl: null, imageFileList: [], } }method方法如下//删除图片 handleRemove(file, fileList) { this.imageFileList = fileList if (this.form.carPhotoList) { let index = -1 for (let i = 0; i < this.form.carPhotoList.length; i++) { if (this.form.carPhotoList[i].photoId === file.photoId) { index = i } } if (index !== -1) { this.$delete(this.form.carPhotoList, index) } } }, //图片改变 handleChange(file, fileList) { let that = this // 调用自定义的压缩方法 compress(file.raw, function (val) { // 图片格式: blob => file let newFile = new window.File([val], file.name, {type: file.raw.type}); // 新增属性(file)并赋值 let fileObj = {} fileObj.raw = newFile fileObj.name = file.name fileObj.uid = file.uid; fileObj.url = URL.createObjectURL(file.raw); that.imageFileList.push(fileObj) }) // this.imageFileList = fileList }, //预览图片 handlePictureCardPreview(file) { this.dialogImageUrl = file.url this.dialogImageVisible = true },实现图片压缩主要是通过on-change钩子,选择图片后,通过on-change钩子调用handleChange方法,我们拿到图片文件后,调用compress方法,通过回调函数,将压缩后的图片绑定到imageFileList表单保存/** 提交按钮 */ submitForm() { if (this.imageFileList.length <= 0) { this.msgInfo('请上传图片') return } const loading = this.$loading({ lock: true,//lock的修改符--默认是false text: "保存中",//显示在加载图标下方的加载文案 spinner: "el-icon-loading",//自定义加载图标类名 background: "rgba(0, 0, 0, 0.7)",//遮罩层颜色 target: document.querySelector("#table")//loadin覆盖的dom元素节点 }) this.$refs["form"].validate(valid => { if (valid) { let formData = new FormData() this.imageFileList.forEach(file => { formData.append("imageFileList", file.raw)// 图片列表 }) formData.append("form", JSON.stringify(this.form))//表单 if (this.form.inquiryId != null) { updateInquiry(formData).then(response => { loading.close() this.msgSuccess("修改成功") this.open = false this.getList() }).catch(error => { loading.close() }) } else { addInquiry(formData).then(response => { loading.close() this.msgSuccess("新增成功") this.open = false this.getList() }).catch(error => { loading.close() }) } } else { loading.close() } }) },在表单保存方法中,我们通过FormData将图片文件与表单数据,一起保存。
2024年03月22日
560 阅读
0 评论
0 点赞
2023-05-15
是否有必要白嫖jsDelivr
壹、jsDelivr是做什么的jsDelivr是一款公共免费的CDN,提供稳定的CDN,可在流量巨大的流行网站上进行使用,没有带宽限制,任何人都可以完全免费使用。jsDelivr在中国大陆也拥有超过数百个节点,因为jsDelivr拥有正规的ICP备案,解决了中国大陆的访问速度优化,实现真正的全球极速低延迟体验。贰、是否有必要白嫖我个人建议是谨慎使用。数据或者说文件,不掌握在自己手里,你永远无法得知是否由于未知的原因导致文件丢失。如果使用第三方托管的代码,如果托管的代码出现问题(比如感染木马等),可能会给自己造成不可逆的伤害。第三方库无法完全保证稳定性,就好比jsDelivr在21年还是22年底在大陆备案失效,导致大面积网站访问出现异常(这里不是说无法访问哈,主要是网站脚本、样式啥的都加载不出来)。现在像又拍云(需要加入联盟,挂个链接)、七牛云等免费CDN,对于个人网站基本也够用,没必要使用第三方的CDN。叁、个人博客的处理我之所以想替换网站整体的jsDelivr,是因为公司网络限制,导致jsDelivr无法访问,所以我也是不得不更换。目前测试下来,整站替换之后再配合上CDN,整体访问速度上感觉没有太大的变化。
2023年05月15日
824 阅读
0 评论
0 点赞
2022-09-11
纯js实现html添加水印
有时候出于保密等需要,我们可能需要在前端展示页面添加一些水印信息。window.onload = function () { const element = document.body; watermark(element); } // window.onresize = function () { // const element = document.body; // watermark(element); // } function watermark(element, config) { let chArr = document.body.getElementsByClassName("watermark-item"); for (i = 0; i < chArr.length; i++) { //删除元素 元素.parentNode.removeChild(元素); if (chArr[i] != null) chArr[i].parentNode.removeChild(chArr[i]); } // 获取元素的坐标 function getOffset(el) { if (el.offsetParent) { return { x: el.offsetLeft + getOffset(el.offsetParent).x, y: el.offsetTop + getOffset(el.offsetParent).y, }; } return { x: el.offsetLeft, y: el.offsetTop, }; } if (!element) return; // 默认配置 const _config = { text1: '内部文档', //文本1 text2: '注意保密', // 文本2 start_x: 0, // x轴起始位置 start_y: 0, // y轴起始位置 space_x: 100, // x轴间距 space_y: 50, // y轴间距 width: 210, // 宽度 height: 80, // 长度 fontSize: 20, // 字体 color: '#aaa', // 字色 alpha: 0.4, // 透明度 rotate: 15, // 倾斜度 }; // 替换默认配置 if (arguments.length === 2 && typeof arguments[1] === "object") { const src = arguments[1] || {}; for (let key in src) { if (src[key] && _config[key] && src[key] === _config[key]) { continue; } else if (src[key]) { _config[key] = src[key]; } } } // 节点的总宽度 const total_width = element.scrollWidth; // 节点的总高度 const total_height = element.scrollHeight; // 创建文本碎片,用于包含所有的插入节点 const mark = document.createDocumentFragment(); // 水印节点的起始坐标 const position = getOffset(element); let x = position.x + _config.start_x, y = position.y + _config.start_y; // 先循环y轴插入水印 do { // 再循环x轴插入水印 do { // 创建单个水印节点 const item = document.createElement('div'); item.className = 'watermark-item'; // 设置节点的样式 item.style.position = "absolute"; item.style.zIndex = 99999; item.style.left = `${x}px`; item.style.top = `${y}px`; item.style.width = `${_config.width}px`; item.style.height = `${_config.height}px`; item.style.fontSize = `${_config.fontSize}px`; item.style.color = _config.color; item.style.textAlign = 'center'; item.style.opacity = _config.alpha; item.style.filter = `alpha(opacity=${_config.alpha * 100})`; // item.style.filter = `opacity(${_config.alpha * 100}%)`; item.style.webkitTransform = `rotate(-${_config.rotate}deg)`; item.style.MozTransform = `rotate(-${_config.rotate}deg)`; item.style.msTransform = `rotate(-${_config.rotate}deg)`; item.style.OTransform = `rotate(-${_config.rotate}deg)`; item.style.transform = `rotate(-${_config.rotate}deg)`; item.style.pointerEvents = 'none'; //让水印不遮挡页面的点击事件 // 创建text1水印节点 const text1 = document.createElement('div'); text1.appendChild(document.createTextNode(_config.text1)); item.append(text1); // 创建text2水印节点 const text2 = document.createElement('div'); text2.appendChild(document.createTextNode(_config.text2)); item.append(text2); // 添加水印节点到文本碎片 mark.append(item); // x坐标递增 x = x + _config.width + _config.space_x; // 超出文本右侧坐标停止插入 } while (total_width + position.x > x + _config.width); // 重置x初始坐标 x = position.x + _config.start_x; // y坐标递增 y = y + _config.height + _config.space_y; // 超出文本底部坐标停止插入 } while (total_height + position.y > y + _config.height); // 插入文档碎片 element.append(mark); }
2022年09月11日
1,083 阅读
0 评论
0 点赞
2022-05-08
Vue2.X过渡系统
Vue2.X过渡系统过渡系统是Vue.js为DOM动画效果提供的一个特性,它能在元素从DOM中插入或移除时触发你的CSS过渡(transition)和动画(animation),也就是说在DOM元素发生变化时为其添加特尔顶的class类名,从而产生过渡效果。一、Vue2.X定义过渡动画的方法不同于Vue1.X,Vue2.x过渡系统取消了v-transion指令,新增了<transition/>内置标签,用法如下<transition name="hello"> <h1 v-if="show">你好</h1> </transition>transition标签为一个抽象标签,并不会额外渲染一个DOM元素,仅仅时用于包裹过渡元素及触发过渡行为。v-if、v-show等指令仍旧标记在内容元素上。二、transition参数2.1、nameVue根据name自动生成对应的对应的类名。name-enter、name-enter-active、name-enter-to、name-leave、name-leave-active、name-leave-to六种状态类名说明name-enter:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。name-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。name-enter-to: 2.1.8版及以上 定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 name-enter 被移除),在过渡/动画完成之后移除。name-leave: 定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。name-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。name-leave-to: 2.1.8版及以上 定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 name-leave 被删除),在过渡/动画完成之后移除。2.2、appear元素首次渲染的时候是否启用transition,默认为false。即v-if绑定值初始为true时,是否渲染时调用transition效果。2.3、css如果设置为true,则只监听钩子函数的调用。2.4、type设置监听CSS动画结束事件的类型。2.5、mode控制过渡插入/移除的先后顺序,主要用于元素切换时。可选择的值有out-in、in-out,如果不设置则同时调用。三、钩子函数与类名类似,也可以使用对应的钩子函数,包括@before-enter、@enter、@after-enter、@before-leave、@leave、@after-leave、四、transition-grouptransition-group主要用于将过渡动画应用到多个DOM元素上。五、Demo<style rel="stylesheet" type="text/css"> /* 动画退出类名 */ .hello-leave-active { animation: amt1 0.5s linear reverse; } /* 定义动画 */ @keyframes amt1 { from { transform: translateX(-100%); } to { transform: translateX(0px); } } /* 进入的起点、离开的终点 */ .hello-enter, .hello-leave-to { transform: translateX(-100%); } /* 进入的过程 */ .hello-enter-active, .hello-leave-active { transition: 0.5s linear; } /* 进入的终点、离开的起点 */ .hello-enter-to, .hello-leave { transform: translateX(0); } </style> <!-- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" /> --> </head> <script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js" type="text/javascript"></script> <body> <div id='app'> <button v-on:click="show = !show"> Toggle </button> <transition name="hello" @before-enter="beforeEnter"> <h1 v-if="show">你好</h1> </transition> <!-- <transition-group appear name="animate__animated animate__bounce" enter-active-class="animate__swing" leave-active-class="animate__backOutUp"> <h1 v-show="!show" key="1">你好啊!</h1> <h1 v-show="show" key="2">迪丽热巴!</h1> </transition-group> --> </div> <script> let vm = new Vue({ el: '#app', data: { show: false }, methods: { beforeEnter: function () { console.log('beforeEnter'); } } }) </script>
2022年05月08日
1,194 阅读
0 评论
0 点赞
2022-05-04
Vue自定义指令
Vue除了内置的v-bind、v-model、v-once、v-pre等内置指令外,我们还可以注册自定义指令,以便封装对DOM元素的重复处理行为,提高代码的复用率。指令的注册自定义指令分为两种,全局自定义指令及组件(局部)自定义指令。全局自定义指令在所有组件中都可以使用,组件(局部)自定义指令顾名思义,只能在当前组件中使用。全局自定义指令全局自定义指令通过Vue.directive(id,definition)进行注册。如下,我们定义一个自动获取焦点的自定义组件。实现功能,当页面打开时,让<input>控件自动获取焦点。<html> <head> <title></title> </head> <script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js" type="text/javascript"></script> <body> <div id='app'> <input type="text" v-g-focus="'1111'"/> </div> <script> Vue.directive('gFocus', { inserted: function (el,binding) { el.focus(); } }) let vm = new Vue({ el: '#app' }) </script> </body> </html>组件(局部)自定义指令组件(局部)自定义指令通过组件的directives进行注册,如下let comp = Vue.extend({ directives: { 'localDirective': {} } })该指令只能在当前组件内调用,其他组件无法使用。指令的定义对象我们在注册指令的同时,可以传入definition定义对象,对指令赋予一些特殊的功能。这些对象主要包括五个钩子函数。bind:只被调用一次,在指令第一次绑定到元素上时调用。inserted:指令所绑定的元素插入到DOM中时调用update:指令在绑定之后以初始值作为参数进行第一次调用,之后每次当绑定值发生变化时调用。描述的是组件更新前的状态。componentUpdated:被绑定元素所在模板完成一次更新周期时调用。描述的是组件更新后的状态unbind:只被调用一次,当指令与元素解绑时调用。我们可以通过以下代码简单看一下使用方法<div id='app'> <input type="text" v-g-focus="param" v-if="exist" /> </div> <script> Vue.directive('gFocus', { bind:function(el, binding){ console.log('bind:' + binding.value) }, inserted: function (el, binding) { el.focus(); console.log('inserted:' + binding.value) }, update: function (el, binding) { console.log('update:' + binding.value) }, unbind:function(el, binding){ console.log('unbind:' + binding.value) }, componentUpdated:function(el, binding){ console.log('componentUpdated:' + binding.value) }, }) let vm = new Vue({ el: '#app', data: { param: 'first', exist: true } }) </script>当我们第一次打开页面时,查看控制台,会看到如下输出我们在控制台输入vm.param='second',可以看到如下输出我们在控制台输入vm.exist=false,可以看到如下输出钩子函数参数钩子函数参数 (即 el、binding、vnode 和 oldVnode)指令钩子函数会被传入以下参数:el:指令所绑定的元素,可以用来直接操作DOM。binding:一个对象,包含以下属性:`name`:指令名,不包括`v-`前缀。 `value`:指令的绑定值,例如:我们上面代码传递的`param`。 `oldValue`:指令绑定的前一个值,仅在`update`和`componentUpdated`钩子中可用。无论值是否改变都可用。 `expression`:字符串形式的指令表达式。例如`v-my-directive="1 + 1"`中,表达式为`1 + 1`。 `arg`:传给指令的参数,可选。例如`v-my-directive:foo`中,参数为`foo`。 `modifiers`:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。 `vnode`:Vue 编译生成的虚拟节点。oldVnode:上一个虚拟节点,仅在update和componentUpdated钩子中可用。
2022年05月04日
922 阅读
0 评论
0 点赞
2021-11-23
JS !(非运算)详解
逻辑非运算!是布尔取反操作(NOT)。作为一元运算符,直接放在操作数之前,把操作数的值转换为布尔值,然后取反并返回。下面是一些特殊操作数的逻辑非运算返回值。console.log( ! {} ); //如果操作数是对象,则返回false console.log( ! 0 ); //如果操作数是0,则返回true console.log( ! (n = 5)); //如果操作数是非零的任何数字,则返回false console.log( ! null ); //如果操作数是null,则返回true console.log( ! NaN ); //如果操作数是NaN,则返回true console.log( ! Infinity ); //如果操作数是Infinity,则返回false console.log( ! ( - Infinity )); //如果操作数是-Infinity,则返回false console.log( ! undefined ); //如果操作数是undefined,则返回true如果对操作数执行两次逻辑非运算操作,就相当于把操作数转换为布尔值。console.log( ! 0 ); //返回true console.log( ! ! 0 ); //返回false逻辑与和逻辑或运算的返回值不必是布尔值,但是逻辑非运算的返回值一定是布尔值。
2021年11月23日
936 阅读
0 评论
0 点赞
1
2
...
9