vue3 + mark.js | 实现文字标注功能

页面效果

mark-demo.gif

具体实现

新增

  • 1、监听鼠标抬起事件,通过window.getSelection()方法获取鼠标用户选择的文本范围或光标的当前位置。
  • 2、通过 选中的文字长度是否大于0window.getSelection().isCollapsed (返回一个布尔值用于描述选区的起始点和终止点是否位于一个位置,即是否框选了)来判断是否展示标签选择的弹窗。
  • 3、标签选择的弹窗采用 子绝父相 的定位方式,通过鼠标抬起的位置确认弹窗的 topleft 值。
    const TAG_WIDTH = 280 //自定义最大范围,以保证不超过内容的最大宽度const tagInfo = ref({visible: false,top: 0,left: 0,})const el = document.getElementById('text-container')//鼠标抬起el?.addEventListener('mouseup', (e) => {const text = window?.getSelection()?.toString() || ''if (text.length > 0) {const left = e.offsetX < TAG_WIDTH ? 0 : e.offsetX - 300tagInfo.value = {visible: true,top: e.offsetY + 40,left: left,}getSelectedTextData()} else {tagInfo.value.visible = false}//清空重选/取消数据resetEditTag()
  const selectedText = reactive({start: 0,end: 0,content: '',})//获取选取的文字数据const getSelectedTextData = () => {const select = window?.getSelection() as anyconsole.log('selectselectselectselect', select)const nodeValue = select.focusNode?.nodeValueconst anchorOffset = select.anchorOffsetconst focusOffset = select.focusOffsetconst nodeValueSatrtIndex = markContent.value?.indexOf(nodeValue)selectedText.content = select.toString()if (anchorOffset < focusOffset) {//从左到右标注selectedText.start = nodeValueSatrtIndex + anchorOffsetselectedText.end = nodeValueSatrtIndex + focusOffset} else {//从右到左selectedText.start = nodeValueSatrtIndex + focusOffsetselectedText.end = nodeValueSatrtIndex + anchorOffset}}

javascript操作光标和选区详情可参考文档:https://blog.51cto.com/u_14524391/3712814

  • 4、选中标签后,采用markjs的markRanges()方式去创建一个选中的元素并为其添加样式和绑定事件。
  • 5、定义一个响应式的文字列表,专门记录标记的内容,添加完元素后可追加一条已标记的数据。
import Mark from 'mark.js'
import {ref} from 'vue
import { nanoid } from 'nanoid'const selectedTextList = ref([])const handleSelectLabel = (t) => {const marker = new Mark(document.getElementById('text-container'))const { tag_color, tag_name, tag_id } = tconst markId = nanoid(10)marker.markRanges([{start: selectedText.start, //必填length: selectedText.content.length, //必填},],{className: 'text-selected',element: 'span',each: (element: any) => {//为元素添加样式和属性element.setAttribute('id', markId)element.style.borderBottom = `2px solid ${t.tag_color}` //添加下划线element.style.color = t.tag_color//绑定事件element.onclick = function (e: any) {//}},})selectedTextList.value.push({tag_color,tag_name,tag_id,start: selectedText.start,end: selectedText.end,mark_content:selectedText.content,mark_id: markId,})
}

删除

image.png

点击已进行标记的文字————>重选/取消弹窗显示————>点击取消

如何判断点击的文字是否已标记,通过在创建的标记元素中绑定点击事件,触发则表示已标记。

  1. 在点击事件中记录该标记的相关内容,如颜色,文字,起始位置,以及唯一标识id(新建时给元素添加一个id属性,点击时即可通过e.target.id获取)
      import { nanoid } from 'nanoid'//选择标签后const markId = nanoid(10)marker.markRanges([{start: isReset ? editTag.value.start : selectedText.start,length: isReset ? editTag.value.content.length : selectedText.content.length,},],{className: 'text-selected',element: 'span',each: (element: any) => {element.setAttribute('id', markId)//绑定事件element.onclick = function (e: any) {e.preventDefault()if (!e.target.id) returnconst left = e.offsetX < TAG_WIDTH ? 0 : e.offsetX - 300const item = selectedTextList.value?.find?.((t) => t.mark_id == e.target.id) as anyconst { mark_content, tag_id, start, end } = item || {}editTag.value = {visible: true,top: e.offsetY + 40,left: e.offsetX,mark_id: e.target.id,content: mark_content || '',tag_id: tag_id || '',start: start,end: end,}tagInfo.value = {visible: false,top: e.offsetY + 40,left: left,}}},})
  1. 点击取消后,获取在此前记录的id,根据id查询相关的标记元素
  • 使用markjs.unmark()方法即可删除此元素。
  • 绑定的响应式数据,可使用findIndexsplice()删除
  1. 编辑弹窗隐藏
const handleCancel = () => {if (!editTag.value.mark_id) returnconst markEl = new Mark(document.getElementById(editTag.value.mark_id))markEl.unmark()selectedTextList.value.splice(selectedTextList.value?.findIndex((t) => t.mark_id == editTag.value.mark_id),1)tagInfo.value = {visible: false,top: 0,left: 0,}resetEditTag()}const resetEditTag = () => {editTag.value = {visible: false,top: 0,left: 0,mark_id: '',content: '',tag_id: '',start: 0,end: 0,}}

重选

image.png

和取消的步骤一样,只不过在点击重选后,先弹出标签弹窗,选择标签后,需要先删除选中的元素,然后再新增一个标记元素。由于在标签选择,在标签选择中判断一下是否是重选,是重选的话就需删除后再创建元素,不是的话就代表是新增,直接新增标记元素(综上所述)。

  const handleSelectLabel = (t: TTag) => {tagInfo.value.visible = falseconst { tag_color, tag_name, tag_id } = tconst marker = new Mark(document.getElementById('text-container'))const markId = nanoid(10)const isReset = selectedTextList.value?.map((j) => j.mark_id).includes(editTag.value.mark_id)? 1: 0 // 1:重选 0:新增if (isReset) {//如若重选,则删除后再新增标签const markEl = new Mark(document.getElementById(editTag.value.mark_id))markEl.unmark()selectedTextList.value.splice(selectedTextList.value?.findIndex((t) => t.mark_id == editTag.value.mark_id),1)}marker.markRanges([{start: isReset ? editTag.value.start : selectedText.start,length: isReset ? editTag.value.content.length : selectedText.content.length,},],{className: 'text-selected',element: 'span',each: (element: any) => {element.setAttribute('id', markId)element.style.borderBottom = `2px solid ${t.tag_color}`element.style.color = t.tag_colorelement.style.userSelect = 'none'element.style.paddingBottom = '6px'element.onclick = function (e: any) {e.preventDefault()if (!e.target.id) returnconst left = e.offsetX < TAG_WIDTH ? 0 : e.offsetX - 300const item = selectedTextList.value?.find?.((t) => t.mark_id == e.target.id) as anyconst { mark_content, tag_id, start, end } = item || {}editTag.value = {visible: true,top: e.offsetY + 40,left: e.offsetX,mark_id: e.target.id,content: mark_content || '',tag_id: tag_id || '',start: start,end: end,}tagInfo.value = {visible: false,top: e.offsetY + 40,left: left,}}},})selectedTextList.value.push({tag_color,tag_name,tag_id,start: isReset ? editTag.value.start : selectedText.start,end: isReset ? editTag.value.end : selectedText.end,mark_content: isReset ? editTag.value.content : selectedText.content,mark_id: markId,})}

清空标记

image.png

const handleAllDelete = () => {selectedTextList.value = []const marker = new Mark(document.getElementById('text-container'))marker.unmark()}

完整代码

<script setup lang="ts">import { ref, onMounted, reactive } from 'vue'import Mark from 'mark.js'import { nanoid } from 'nanoid'type TTag = {tag_name: stringtag_id: stringtag_color: string}type TSelectText = {tag_id: stringtag_name: stringtag_color: stringstart: numberend: numbermark_content: stringmark_id: string}const TAG_WIDTH = 280const selectedTextList = ref<TSelectText[]>([])const selectedText = reactive({start: 0,end: 0,content: '',})const markContent = ref('这是标注的内容有业绩还是我我很快就很快就开完如突然好几个地方各级很大功夫数据库二极管捍卫国家和我回家很晚十九世纪俄国激活工具和丈母娘环境和颠覆国家的高房价奥苏爱哦因为i以太网图的还是觉得好看啊空间函数调用加快速度还是饥渴的发货可是磕碰日俄和那那么会就开始开会的数据库和也会觉得讲故事的而黄金九二额呵呵三角函数的吧合乎实际的和尽快核实当升科技看交互的接口和送二ui为人开朗少女都被你们进货金额麦当娜表面上的')const tagInfo = ref({visible: false,top: 0,left: 0,})const editTag = ref({visible: false,top: 0,left: 0,mark_id: '',content: '',tag_id: '',start: 0,end: 0,})const tagList: TTag[] = [{tag_name: '标签一',tag_color: `#DE050CFF`,tag_id: 'tag_id1',},{tag_name: '标签二',tag_color: `#6ADE05FF`,tag_id: 'tag_id2',},{tag_name: '标签三',tag_color: `#DE058BFF`,tag_id: 'tag_id3',},{tag_name: '标签四',tag_color: `#9205DEFF`,tag_id: 'tag_id4',},{tag_name: '标签五',tag_color: `#DE5F05FF`,tag_id: 'tag_id5',},]const handleAllDelete = () => {selectedTextList.value = []const marker = new Mark(document.getElementById('text-container'))marker.unmark()}const handleCancel = () => {if (!editTag.value.mark_id) returnconst markEl = new Mark(document.getElementById(editTag.value.mark_id))markEl.unmark()selectedTextList.value.splice(selectedTextList.value?.findIndex((t) => t.mark_id == editTag.value.mark_id),1)tagInfo.value = {visible: false,top: 0,left: 0,}resetEditTag()}const handleReset = () => {editTag.value.visible = falsetagInfo.value.visible = true}const handleSave = () => {console.log('标注的数据', selectedTextList.value)}const handleSelectLabel = (t: TTag) => {const { tag_color, tag_name, tag_id } = ttagInfo.value.visible = falseconst marker = new Mark(document.getElementById('text-container'))const markId = nanoid(10)const isReset = selectedTextList.value?.map((j) => j.mark_id).includes(editTag.value.mark_id)? 1: 0 // 1:重选 0:新增if (isReset) {//如若重选,则删除后再新增标签const markEl = new Mark(document.getElementById(editTag.value.mark_id))markEl.unmark()selectedTextList.value.splice(selectedTextList.value?.findIndex((t) => t.mark_id == editTag.value.mark_id),1)}marker.markRanges([{start: isReset ? editTag.value.start : selectedText.start,length: isReset ? editTag.value.content.length : selectedText.content.length,},],{className: 'text-selected',element: 'span',each: (element: any) => {element.setAttribute('id', markId)element.style.borderBottom = `2px solid ${t.tag_color}`element.style.color = t.tag_colorelement.style.userSelect = 'none'element.style.paddingBottom = '6px'element.onclick = function (e: any) {e.preventDefault()if (!e.target.id) returnconst left = e.offsetX < TAG_WIDTH ? 0 : e.offsetX - 300const item = selectedTextList.value?.find?.((t) => t.mark_id == e.target.id) as anyconst { mark_content, tag_id, start, end } = item || {}editTag.value = {visible: true,top: e.offsetY + 40,left: e.offsetX,mark_id: e.target.id,content: mark_content || '',tag_id: tag_id || '',start: start,end: end,}tagInfo.value = {visible: false,top: e.offsetY + 40,left: left,}}},})selectedTextList.value.push({tag_color,tag_name,tag_id,start: isReset ? editTag.value.start : selectedText.start,end: isReset ? editTag.value.end : selectedText.end,mark_content: isReset ? editTag.value.content : selectedText.content,mark_id: markId,})}/*** 获取选取的文字数据*/const getSelectedTextData = () => {const select = window?.getSelection() as anyconst nodeValue = select.focusNode?.nodeValueconst anchorOffset = select.anchorOffsetconst focusOffset = select.focusOffsetconst nodeValueSatrtIndex = markContent.value?.indexOf(nodeValue)selectedText.content = select.toString()if (anchorOffset < focusOffset) {//从左到右标注selectedText.start = nodeValueSatrtIndex + anchorOffsetselectedText.end = nodeValueSatrtIndex + focusOffset} else {//从右到左selectedText.start = nodeValueSatrtIndex + focusOffsetselectedText.end = nodeValueSatrtIndex + anchorOffset}}const resetEditTag = () => {editTag.value = {visible: false,top: 0,left: 0,mark_id: '',content: '',tag_id: '',start: 0,end: 0,}}const drawMark = () => {//模拟后端返回的数据const res = [{start: 2, //必备end: 6,tag_color: '#DE050CFF',tag_id: 'tag_id1',tag_name: '标签一',mark_content: '标注的内容',mark_id: 'mark_id1',},{start: 39,end: 41,tag_color: '#6ADE05FF',tag_id: 'tag_id2',tag_name: '标签二',mark_content: '二极管',mark_id: 'mark_id2',},{start: 58,end: 61,tag_color: '#DE058BFF',tag_id: 'tag_id3',tag_name: '标签三',mark_content: '激活工具',mark_id: 'mark_id3',},]selectedTextList.value = res?.map((t) => ({tag_id: t.tag_id,tag_name: t.tag_name,tag_color: t.tag_color,start: t.start,end: t.end,mark_content: t.mark_content,mark_id: t.mark_id,}))const markList =selectedTextList.value?.map((j) => ({...j,start: j.start, //必备length: j.end - j.start + 1, //必备})) || []const marker = new Mark(document.getElementById('text-container'))markList?.forEach?.(function (m: any) {marker.markRanges([m], {element: 'span',className: 'text-selected',each: (element: any) => {element.setAttribute('id', m.mark_id)element.style.borderBottom = `2px solid ${m.tag_color}`element.style.color = m.tag_colorelement.style.userSelect = 'none'element.style.paddingBottom = '6px'element.onclick = function (e: any) {console.log('cccccc', m)const left = e.offsetX < TAG_WIDTH ? 0 : e.offsetX - 300editTag.value = {visible: true,top: e.offsetY + 40,left: e.offsetX,mark_id: m.mark_id,content: m.mark_content,tag_id: m.tag_id,start: m.start,end: m.end,}tagInfo.value = {visible: false,top: e.offsetY + 40,left: left,}}},})})}//页面初始化onMounted(() => {const el = document.getElementById('text-container')//鼠标抬起el?.addEventListener('mouseup', (e) => {const text = window?.getSelection()?.toString() || ''if (text.length > 0) {const left = e.offsetX < TAG_WIDTH ? 0 : e.offsetX - 300tagInfo.value = {visible: true,top: e.offsetY + 40,left: left,}getSelectedTextData()} else {tagInfo.value.visible = false}//清空重选/取消数据resetEditTag()})//从后端获取标注数据,进行初始化标注drawMark()})
</script><template><header><n-buttontype="primary":disabled="selectedTextList.length == 0 ? true : false"ghost@click="handleAllDelete">清空标记</n-button><n-buttontype="primary":disabled="selectedTextList.length == 0 ? true : false"@click="handleSave">保存</n-button></header><main><div id="text-container" class="text">{{ markContent }}</div><!-- 标签选择 --><divv-if="tagInfo.visible && tagList.length > 0":class="['tag-box p-4 ']":style="{ top: tagInfo.top + 'px', left: tagInfo.left + 'px' }"><div v-for="i in tagList" :key="i.tag_id" class="tag-name" @click="handleSelectLabel(i)"><n-space><p>{{ i.tag_name }}</p><n-button v-if="i.tag_id == editTag.tag_id" text type="primary"></n-button></n-space><div:class="['w-4 h-4']":style="{background: i.tag_color,}"></div></div></div><!-- 重选/取消 --><divv-if="editTag.visible"class="edit-tag":style="{ top: editTag.top + 'px', left: editTag.left + 'px' }"><div class="py-1 bg-gray-100 text-center" @click="handleCancel">取 消</div><div class="py-1 bg-gray-100 mt-2 text-center" @click="handleReset">重 选</div></div></main>
</template><style lang="less" scoped>header {display: flex;justify-content: space-between;align-items: center;padding: 0 24px;height: 80px;border-bottom: 1px solid #e5e7eb;user-select: none;background: #fff;}main {background: #fff;margin: 24px;height: 80vh;padding: 24px;overflow-y: auto;position: relative;box-shadow: 0 3px 8px 0 rgb(0 0 0 / 13%);.text {color: #333;font-weight: 500;font-size: 16px;line-height: 50px;}.tag-box {position: absolute;z-index: 10;width: 280px;max-height: 40vh;overflow-y: auto;background: #fff;border-radius: 4px;box-shadow: 0 9px 28px 8px rgb(0 0 0 / 3%), 0 6px 16px 4px rgb(0 0 0 / 9%),0 3px 6px -2px rgb(0 0 0 / 20%);user-select: none;.tag-name {width: 100%;background: rgba(243, 244, 246, var(--tw-bg-opacity));font-size: 14px;cursor: pointer;display: flex;justify-content: space-between;align-items: center;padding: 4px 8px;margin-top: 8px;}.tag-name:nth-of-type(1) {margin-top: 0;}}.edit-tag {position: absolute;z-index: 20;padding: 16px;cursor: pointer;width: 100px;background: #fff;border-radius: 4px;box-shadow: 0 9px 28px 8px rgb(0 0 0 / 3%), 0 6px 16px 4px rgb(0 0 0 / 9%),0 3px 6px -2px rgb(0 0 0 / 20%);user-select: none;}::selection {background: rgb(51 51 51 / 20%);}}
</style>

结束语

目前功能实现比较简单,还有很多发挥的空间,先小小的记录一下,最后~,预祝大家,双节快乐!!

markjs

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/88771.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2.(vue3.x+vite)组件注册并调用

前端技术社区总目录(订阅之前请先查看该博客) 关联博客 1.(vue3.x+vite)封装组件 一:umd调用方式 1:引入umd.js <script src="./public/myvue5.umd.js"></script>2:编写代码调用 (1)umd方式,根据“5

爬虫 — 多线程

目录 一、多任务概念二、实现多任务方式1、多进程 &#xff08;Multiprocessing&#xff09;2、多线程&#xff08;Multithreading&#xff09;3、协程&#xff08;Coroutine&#xff09; 三、多线程执行顺序四、多线程的方法1、join()2、setDaemon()3、threading.enumerate() …

Vue+ElementUI实现动态树和表格数据的分页模糊查询

目录 前言 一、动态树的实现 1.数据表 2.编写后端controller层 3.定义前端发送请求路径 4.前端左侧动态树的编写 4.1.发送请求获取数据 4.2.遍历左侧菜单 5.实现左侧菜单点击展示右边内容 5.1.定义组件 5.2.定义组件与路由的对应关系 5.3.渲染组件内容 5.4.通过动态…

Denoising diffusion implicit models 阅读笔记

Denoising diffusion probabilistic models (DDPMs)从马尔科夫链中采样生成样本&#xff0c;需要迭代多次&#xff0c;速度较慢。Denoising diffusion implicit models (DDIMs)的提出是为了加速采样过程&#xff0c;减少迭代的次数&#xff0c;并且要求DDIM可以复用DDPM训练的网…

lv5 嵌入式开发-7 有名管道和无名管道

目录 1 进程间通信介绍 2 无名管道 2.1 无名管道特点 ​编辑 2.2 读无名管道 2.3 写无名管道 3 有名管道 3.1 有名管道特点 3.2 写有名管道 3.3 读有名管道 掌握&#xff1a;进程间通信方式介绍、无名管道特点、无名管道创建、无名管道读写特性&#xff1b;有名管道…

深入浅出DAX:SELECTEDVALUE()

深入浅出DAX&#xff1a;SELECTEDVALUE() SELECTEDVALUE()&#xff0c;如果筛选 columnName 的上下文后仅剩下一个非重复值&#xff0c;则返回该值。否则返回alternateResult&#xff0c;语法如下&#xff1a; SELECTEDVALUE(<columnName>[, <alternateResult>] …

Rabbit消息的可靠性

生产者重连 消费者重试 Confirm模式简介 消息的confirm确认机制&#xff0c;是指生产者投递消息后&#xff0c;到达了消息服务器Broker里面的exchange交换机&#xff0c;则会给生产者一个应答&#xff0c;生产者接收到应答&#xff0c;用来确定这条消息是否正常的发送到Broker…

【大数据】Doris 构建实时数仓落地方案详解(三):Doris 实时数仓设计

本系列包含&#xff1a; Doris 构建实时数仓落地方案详解&#xff08;一&#xff09;&#xff1a;实时数据仓库概述Doris 构建实时数仓落地方案详解&#xff08;二&#xff09;&#xff1a;Doris 核心功能解读Doris 构建实时数仓落地方案详解&#xff08;三&#xff09;&#…

数据库存储引擎和数据类型详细介绍

目录 一、数据库存储引擎&#xff08;了解&#xff09;1.了解MySQL体系结构2.存储引擎&#xff08;了解&#xff09;2.1.存储引擎的介绍2.2.存储引擎分类2.3.如何选择引擎&#xff1f; 3.事务控制语言(TCL)事务的四个特性(ACID) 二、数据类型&#xff08;了解&#xff09;1.整型…

Servlet操作与用法(保姆式教学)

Servlet介绍 什么是servlet Servlet&#xff08;Servlet Applet的缩写&#xff0c;全称 Java Servlet&#xff09;&#xff1a;适用于Java编写的服务器程序&#xff0c;其主要功能是在于交互式的浏览和修改数据&#xff0c;生成动态Web内容。狭义的Servlet是指Java语言实现的…

Xmake v2.8.3 发布,改进 Wasm 并支持 Xmake 源码调试

Xmake 是一个基于 Lua 的轻量级跨平台构建工具。 它非常的轻量&#xff0c;没有任何依赖&#xff0c;因为它内置了 Lua 运行时。 它使用 xmake.lua 维护项目构建&#xff0c;相比 makefile/CMakeLists.txt&#xff0c;配置语法更加简洁直观&#xff0c;对新手非常友好&#x…

ARM day1

1.复习今日内容 2.搭建汇编环境 下发资料-》工具软件 -》汇编环境搭建 3.安装Ubuntu下的交叉编译工具链 思维导图&#xff1a;

latexocr安装过程中遇到的问题解决办法

环境要求&#xff1a;需要Python版本3.7&#xff0c;并安装相应依赖文件 具体的详细安装步骤可见我上次写的博文&#xff1a;Mathpix替代者|科研人必备公式识别插件|latexocr安装教程 ‘latexocr‘ 不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件的相关解决办…

十五、异常(2)

本章概要 自定义异常 异常与记录日志 异常声明 自定义异常 不必拘泥于 Java 已有的异常类型。Java异常体系不可能预见你将报告的所有错误&#xff0c;所以你可以创建自己的异常类&#xff0c;来表示你的程序中可能遇到的问题。 要自己定义异常类&#xff0c;必须从已有的异…

什么是 Redis?

Redis 是一种基于内存的数据库&#xff0c;对数据的读写操作都是在内存中完成的&#xff0c;因此读写速度非常快&#xff0c;常用于缓存&#xff0c;消息队列&#xff0c;分布式锁等场景。 Redis 提供了多种数据类型来支持不同的业务场景&#xff0c;比如 String(字符串)、Has…

C语言环境搭建(Win)

一、C语言简介 1、 C语言简介 C语言是一门通用的、面向过程式的编译型语言&#xff0c;它的运行速度极快&#xff0c;仅次于汇编语言。 C语言是计算机产业的核心程序设计语言&#xff0c;操作系统、硬件驱动、关键组件、数据库等都离不开C语言&#xff0c;广泛应用于底层开发。…

CompletableFuture-链式语法和join方法介绍

2.4 案例精讲-从电商网站的比价需求展开 2.4.1 函数式编程已成为主流 Lambda表达式Stream流式调用Chain链式调用Java8函数式编程 函数式接口&#xff1a; 小结&#xff1a; 函数式接口&#xff1a; Java8新特性_四大内置核心函数式接口_java8 内置核心接口_ZHOU_VIP的博客-…

Nginx之memcached_module模块解读

目录 基本介绍 安装添加模块 模块配置指令 基本介绍 nginx的memcached_module模块可以直接从memcached服务器中读取内容后输出&#xff0c;后续的请求不再经过应用程序处理&#xff0c;如php-fpm、django&#xff0c;大大的提升动态页面的速度。nginx只负责从memcach…

学习路之PHP--lumen安装配置

一、下载lumen源码 composer create-project --prefer-dist laravel/lumen blog 安装lumen-generator composer require flipbox/lumen-generator 二、配置 bootstrap\app.php 97行 $app->register(Flipbox\LumenGenerator\LumenGeneratorServiceProvider::class);三、生成…

【IDEA】IDEA 单行注释开头添加空格

操作 打开 IDEA 的 Settings 对话框&#xff08;快捷键为CtrlAltS&#xff09;&#xff1b;在左侧面板中选择Editor -> Code Style -> Java&#xff1b;在右侧面板中选择Code Generation选项卡&#xff1b;将Line comment at first column选项设置为false使注释加在行开…