设计图-设计图外包-北京设计图外包

搜 索

Search:设计图


js微信小程序纯文本实现@功能js大全

前言前言前言大家肯定对@功能不陌生,在如今的各大社交软件中它是一种不可或缺的功能。实现@人的功能并不复杂,只需将@人员的id传给后端,后端下发通知即可。主要的复杂点在于一键删除功能与变色功能,web端前言前言前言大家肯定对@功能不陌生,在如今的各大社交软件中它是一种不可或缺的功能。实现@人的功能并不复杂,只需将@人员的id传给后端,后端下发通知即可。主要的复杂点在于一键删除功能与变色功能,web端可以使用现成库 caret.js 或者 At.js 来实现。但笔者需要在小程序中实现这个功能,而且在 textarea 标签里实现,当然@人名的变色功能自然而然就砍掉了。 caret.jstextarea 准备工作准备工作准备工作怎么来实现一键删除呢?首先想到对@人名前后用特殊符号标记+正则来实现,但结果不是很理想,扩展性也比较差,如果还要匹配话题之类的就得多写一套代码,所以就试着找其他方法解决。发现 wx.getSelectedTextRange 可以获取文本框聚焦时的光标,这样就可以将@人员插入文本指定位置。文本框事件 @input 的可以获取到变化的数据与位置,那就可以根据变化的位置与变化的数据来判断是否命中@人员,@人员的位置可以通过计算获取。 wx.getSelectedTextRange // bindinput事件返回值 // value为变化后的值 cursor为变化的位置 keyCode为触发的键值 const {value, cursor, keyCode} = event.detail // 获取光标位置,聚焦时生效 wx.getSelectedTextRange({complete: res => {console.log('光标位置:', res.start, res.end)}})// bindinput事件返回值 // value为变化后的值 cursor为变化的位置 keyCode为触发的键值 const {value, cursor, keyCode} = event.detail // 获取光标位置,聚焦时生效 wx.getSelectedTextRange({complete: res => {console.log('光标位置:', res.start, res.end)}})准备工作做好了就进入实践环节,毕竟实践是检验真理的唯一标准。设计图呈现:通过点击@按钮到人员列表页面,选择人员后返回,具体如下图。这里涉及页面之间的通信问题,可以通过状态管理器、数据缓存、获取页面栈设置数据等来实现,本例中使用数据缓存。 数据组装数据组装数据组装从人员列表返回用 wx.navigateBack ,会触发 onShow 这个生命周期,所以需要在 onShow 里组装@数据。获取到的@人员根据光标位置对文本进行字符串截取组装,若未获取到光标位置则直接将@人员添加到文本末尾。然后对@人员数据、文本数据等进行备份,用于后续的计算。 wx.navigateBack initAtFn() {// 获取@人员数据const me = thisconst initMemberList = wx.getStorageSync('atMemberList')const atMemberArr = initMemberList ? initMemberList : []// 赋值后清除@人员数据wx.removeStorageSync('atMemberList')// 获取上一次光标的位置const preCursor = wx.getStorageSync('blurCursor') ? parseInt(wx.getStorageSync('blurCursor')) : me.content.length// 将 @人员数据 并入内容区域if (atMemberArr.length > 0) {// 获取人员名称const atMemberName = `@${atMemberArr[0].name}`// 如果上次光标有记录 就根据光标分割字符串 并入@人员名称if (preCursor.toString().length !== me.content.length) {const start = me.content.substring(0, preCursor)const end = me.content.substring(preCursor)me.content = `${start}${atMemberName}${end}`} else {me.content += `${atMemberName}`}me.atArr = me.atArr.concat(atMemberArr) // 合并人员wx.setStorageSync('blurCursor', preCursor + atMemberName.length)}else {wx.setStorageSync('blurCursor', me.content.length)}me.focus = trueme.copyContent = me.contentme.executeArr = me.getAtMemberPosFn() // 获取@人员位置}initAtFn() {// 获取@人员数据const me = thisconst initMemberList = wx.getStorageSync('atMemberList')const atMemberArr = initMemberList ? initMemberList : []// 赋值后清除@人员数据wx.removeStorageSync('atMemberList')// 获取上一次光标的位置const preCursor = wx.getStorageSync('blurCursor') ? parseInt(wx.getStorageSync('blurCursor')) : me.content.length// 将 @人员数据 并入内容区域if (atMemberArr.length > 0) {// 获取人员名称const atMemberName = `@${atMemberArr[0].name}`// 如果上次光标有记录 就根据光标分割字符串 并入@人员名称if (preCursor.toString().length !== me.content.length) {const start = me.content.substring(0, preCursor)const end = me.content.substring(preCursor)me.content = `${start}${atMemberName}${end}`} else {me.content += `${atMemberName}`}me.atArr = me.atArr.concat(atMemberArr) // 合并人员wx.setStorageSync('blurCursor', preCursor + atMemberName.length)}else {wx.setStorageSync('blurCursor', me.content.length)}me.focus = trueme.copyContent = me.contentme.executeArr = me.getAtMemberPosFn() // 获取@人员位置}计算@人员位置计算@人员位置计算@人员位置对@人员数组进行遍历,计算@人员在文本中的位置区间。通过indexOf来获取起点(这里有一个缺陷,也是需要优化的点,当手动输入的内容中有和@人员名字相同的字段时,那么位置靠前的那一个将会生效),终点为起点+名字长度。这里有个问题:如果重复@相同的人员,删除时怎么区分呢?笔者想当然的使用了时间戳,结果发现在遍历中使用时间戳并不准确,只有规规矩矩生成唯一值。计算时收集了人员位置的最值区间,在这个范围之外增减文本不会影响@人员的完整性。下面是代码:getAtMemberPosFn() {const me = thisconst [tipArr, left, right] = [ [], [], [] ]// 根据@人员的数组来匹配计算所处位置me.atArr.map(item => {const name = item.nameconst userId = item.userId// 此处有一个缺陷 如果手输入的和获取的@人名字相同 第一个会生效 第二个不会生效let start = me.copyContent.indexOf(name)if (tipArr.length > 0) {const _arr = tipArr.filter(v => v.name.includes(name))if (_arr.length > 0) {start = me.copyContent.indexOf(name, _arr[_arr.length - 1].end)}}const end = name.length + start // endleft.push(start)right.push(end)// 获取唯一标识 是用于重复@的区分const guid = me.createGuidFn()const tipObj = {start: start - 1, // @ - 1end,name,atName: `@${name}`,type: item.userId, userId: userId,code: guid}tipArr.push(tipObj)})// 获取区间左右最值right.length > 0 ? me.maxAt = Math.max(...right) : me.maxAt = 0left.length > 0 ? me.minAt = Math.min(...left) : me.minAt = 0me.atArr = tipArrreturn tipArr}getAtMemberPosFn() {const me = thisconst [tipArr, left, right] = [ [], [], [] ]// 根据@人员的数组来匹配计算所处位置me.atArr.map(item => {const name = item.nameconst userId = item.userId// 此处有一个缺陷 如果手输入的和获取的@人名字相同 第一个会生效 第二个不会生效let start = me.copyContent.indexOf(name)if (tipArr.length > 0) {const _arr = tipArr.filter(v => v.name.includes(name))if (_arr.length > 0) {start = me.copyContent.indexOf(name, _arr[_arr.length - 1].end)}}const end = name.length + start // endleft.push(start)right.push(end)// 获取唯一标识 是用于重复@的区分const guid = me.createGuidFn()const tipObj = {start: start - 1, // @ - 1end,name,atName: `@${name}`,type: item.userId, userId: userId,code: guid}tipArr.push(tipObj)})// 获取区间左右最值right.length > 0 ? me.maxAt = Math.max(...right) : me.maxAt = 0left.length > 0 ? me.minAt = Math.min(...left) : me.minAt = 0me.atArr = tipArrreturn tipArr}一键删除功能一键删除功能一键删除功能@人员的位置区间已经计算出来了,接下来监听输入框的内容变化实现一键删除功能,当输入框文本内容变化,会触发 @input 事件,它会返回变化后的值 value ,变化的位置 cursor ,我们将利用这两个数据作为是否 命中@人员的判断依据 。将情况分为以下几种:变化后的value为空,即清空了输入框。数据变化的光标位置大于@人员位置最值区间的最大值,即不影响@人员位置。当数据变化影响@人员时,这里对增加减少内容做了区分处理:增加时,如果增加位置小于最值的最小值,则直接重新计算位置。如果增加值的位置命中@人员位置,则过滤掉失效人员,再重新计算。这里需要注意,移动端输入法会有一次性输入多个字符,变化的位置不再是返回的光标位置,而是以光标位置减去变化前后数据的差值。删除时,获取删除的起始位置 (A,B) ,然后与@人员位置 (start, end) 作比较。 当 !(A end) 时,则为命中,将命中的@人员过滤掉即可。changeFn(txt) {const me = thisconst { value, cursor, keyCode } = txt.detail // 改变后的值,改变的位置,按键// 如果改变后的值为'', 就直接返回if(!value) {me.content = valueme.copyContent = valueme.atArr = []return false}// 判断值改变的增减const changeLen = value.length - me.copyContent.length// 值改变的光标位置 不影响@人员的则不管if (cursor > me.maxAt) {me.copyContent = me.contentreturn false}// 判断为 增加值if (changeLen > 0) {const addCursor = cursor - changeLen // 重新计算增加位置 防止移动端一次性粘贴导致失效问题me.copyContent = me.content// 增加值的位置 小于左区间最值 则重新计算位置if(addCursor me.executeArr = me.getAtMemberPosFn()return false}me.executeArr.map(item => {const { start, end, name, code } = itemif (addCursor start) {// 删除命中人员,则该人员失效me.atArr = me.atArr.filter(v => v.code !== code)}})// 需要重新计算位置me.executeArr = me.getAtMemberPosFn()} else {let replaceStr = '' // 应被删除的字段const left = [] // 删除左值集合const right = [] // 删除右值集合const delLen = cursor - changeLen // 本身删除的长度const deleteString = me.copyContent.substring(cursor, delLen) // 本身删除的字段 [cursor, changeLen)// 获取应被删除的左右位置function pushArrEvent(s, e) {left.push(s)right.push(e)}me.executeArr.map(item => {let { start, end, name, code } = item// D大 = B大// 命中部分为 删除部分与@人员的交集if (!(delLen = end)) {// 命中判定,命中位置在名字区间 左边/右边/之间/或者多选中删除的if (delLen = start) {pushArrEvent(start, end)} else {if (cursor > start) {if (delLen > end) {pushArrEvent(start, delLen)} else {pushArrEvent(start, end)}} else if (cursor if (delLen > end) {pushArrEvent(cursor, delLen)} else {pushArrEvent(cursor, end)}} else {pushArrEvent(cursor, delLen)}}// 获取一键删除区间 const del_left = Math.min(...left)const del_right = Math.max(...right)// 根据区间获取一键删除字段replaceStr = me.copyContent.substring(del_left, del_right)// 删除后的赋值me.content = me.copyContent.substring(0, del_left) + me.copyContent.substring(del_right)// @人员数组生成me.atArr = me.atArr.filter(v => v.code !== code)}})// 执行完后 重新赋值计算me.copyContent = me.contentme.executeArr = me.getAtMemberPosFn()}}changeFn(txt) {const me = thisconst { value, cursor, keyCode } = txt.detail // 改变后的值,改变的位置,按键// 如果改变后的值为'', 就直接返回if(!value) {me.content = valueme.copyContent = valueme.atArr = []return false}// 判断值改变的增减const changeLen = value.length - me.copyContent.length// 值改变的光标位置 不影响@人员的则不管if (cursor > me.maxAt) {me.copyContent = me.contentreturn false}// 判断为 增加值if (changeLen > 0) {const addCursor = cursor - changeLen // 重新计算增加位置 防止移动端一次性粘贴导致失效问题me.copyContent = me.content// 增加值的位置 小于左区间最值 则重新计算位置if(addCursor me.executeArr = me.getAtMemberPosFn()return false}me.executeArr.map(item => {const { start, end, name, code } = itemif (addCursor start) {// 删除命中人员,则该人员失效me.atArr = me.atArr.filter(v => v.code !== code)}})// 需要重新计算位置me.executeArr = me.getAtMemberPosFn()} else {let replaceStr = '' // 应被删除的字段const left = [] // 删除左值集合const right = [] // 删除右值集合const delLen = cursor - changeLen // 本身删除的长度const deleteString = me.copyContent.substring(cursor, delLen) // 本身删除的字段 [cursor, changeLen)// 获取应被删除的左右位置function pushArrEvent(s, e) {left.push(s)right.push(e)}me.executeArr.map(item => {let { start, end, name, code } = item// D大 = B大// 命中部分为 删除部分与@人员的交集if (!(delLen = end)) {// 命中判定,命中位置在名字区间 左边/右边/之间/或者多选中删除的if (delLen = start) {pushArrEvent(start, end)} else {if (cursor > start) {if (delLen > end) {pushArrEvent(start, delLen)} else {pushArrEvent(start, end)}} else if (cursor if (delLen > end) {pushArrEvent(cursor, delLen)} else {pushArrEvent(cursor, end)}} else {pushArrEvent(cursor, delLen)}}// 获取一键删除区间 const del_left = Math.min(...left)const del_right = Math.max(...right)// 根据区间获取一键删除字段replaceStr = me.copyContent.substring(del_left, del_right)// 删除后的赋值me.content = me.copyContent.substring(0, del_left) + me.copyContent.substring(del_right)// @人员数组生成me.atArr = me.atArr.filter(v => v.code !== code)}})// 执行完后 重新赋值计算me.copyContent = me.contentme.executeArr = me.getAtMemberPosFn()}}添加标签添加标签添加标签我们还差最后一步,那就是给@人名添加标签,用于显示时与一般文本做区分。这里踩了一个坑,用正则替换时,如果名字与名字之间存在包含关系,则会失效,所以用记录位置的方式来对文本进行截取组装。submitTxtFn() {const copyTxt = this.contentconst arr = JSON.parse(JSON.stringify(this.atArr))const atUserIds = [...new Set(arr.map(v=>v.userId))] // 获取@人员idlet targetContent = ''let count = 0// 给@人员添加wxml标签,此处用了jyf-Parser富文本解析插件,href里面的值用于点击传参if(arr.length > 0) {arr.forEach((item, index)=>{let _tip = ''const txt = copyTxt.substring(count, item.start)// 加空格_tip = `${txt}${item.atName} `targetContent += _tip// 处理最后一个标签后面的文本if(index + 1 === arr.length) {if(item.end targetContent += copyTxt.substring(item.end)}}count = item.end})}else {targetContent = this.content}// 目标数据const targetObj = {content: targetContent,atIds: atUserIds}this.submitData = targetObjreturn targetObj}submitTxtFn() {const copyTxt = this.contentconst arr = JSON.parse(JSON.stringify(this.atArr))const atUserIds = [...new Set(arr.map(v=>v.userId))] // 获取@人员idlet targetContent = ''let count = 0// 给@人员添加wxml标签,此处用了jyf-Parser富文本解析插件,href里面的值用于点击传参if(arr.length > 0) {arr.forEach((item, index)=>{let _tip = ''const txt = copyTxt.substring(count, item.start)// 加空格_tip = `${txt}${item.atName} `targetContent += _tip// 处理最后一个标签后面的文本if(index + 1 === arr.length) {if(item.end targetContent += copyTxt.substring(item.end)}}count = item.end})}else {targetContent = this.content}// 目标数据const targetObj = {content: targetContent,atIds: atUserIds}this.submitData = targetObjreturn targetObj}以上就实现了纯文本的@功能,通过计算位置来实现的优点是具有扩展性,比如一套代码可以实现#话题功能和@功能共存,只需加个type作为区分即可。缺点是一键删除时体验不是很好,并且删除后不能控制光标位置,不能实现人员名称变色等。虽然功能比较ZZ,但也比较有趣,所以就分享给大家,如果大家有更好的解决方案,评论区有请。完整代码请移步 语雀语雀总结总结总结
标签:

jsAnt design vue table 单击行选中 勾选checkbox教程js大全

最近了解Ant design 设计table 单击行选中checkedbox功能,相比于element的 @row-click 再触发toggleRowSelection,ant design的api最近了解Ant design 设计table 单击行选中checkedbox功能,相比于element的 @row-click 再触发toggleRowSelection,ant design的api就没那么清晰了,言归正传期望:Ant design table 单击行选中 勾选checkedbox期望:实现:实现:单选: 单选: onClickRow(record) { return { on: {click: () => {let keys = [];keys.push(record.id); this.selectedRowKeys = keys;} } }}onClickRow(record) { return { on: {click: () => {let keys = [];keys.push(record.id); this.selectedRowKeys = keys;} } }}多选:多选:onClickRowMulti(record) { return { on: { click: () => { let rowKeys=this.selectedRowKeysif(rowKeys.length>0 && rowKeys.includes(record.id)){rowKeys.splice(rowKeys.indexOf(record.id),1)}else{rowKeys.push(record.id)}this.selectedRowKeys = rowKeys; } } } }onClickRowMulti(record) { return { on: { click: () => { let rowKeys=this.selectedRowKeysif(rowKeys.length>0 && rowKeys.includes(record.id)){rowKeys.splice(rowKeys.indexOf(record.id),1)}else{rowKeys.push(record.id)}this.selectedRowKeys = rowKeys; } } } }补充知识:使用Ant Design的Table和Checkbox模拟Tree补充知识:补充知识:使用Ant Design的Table和Checkbox模拟Tree一、小功能大需求先看下设计图:需求如下:1、一级选中(取消选中),该一级下的二级全部选中(取消选中)2、二级全选,对应的一级选中,二级未全选中,对应的一级不选中3、支持搜索,只搜索二级数据,并且只展示搜索到的数据以及对应的一级title,如:搜索“店员”,此时一级只展示咖啡厅....其他一级隐藏,二级只展示店员,其他二级隐藏4、搜索出来的数据,一级不可选中,即不允许全选,搜索框清空时,回归初始化状态5、搜索后,自动展开所有二级,默认情况下收起所有二级看到图的时候,第一反应就是使用Tree就能搞定,但是翻阅了文档后,发现Tree并不能全部完成,所以就只能使用其他组件进行拼装,最后发现使用Table和Checkbox可以完美实现。二、逐步完成需求如果不想看这些,可直接到最后,有完整代码。。。。。。1、页面构建1、页面构建这个就不用多说,只是一个简单的Table嵌套Checkbox,具体可去查看文档,直接贴代码,因为是布局,所有可以忽略代码中的事件。注意一点:因为搜索时,会改变数据,所以需要将初始化的数据进行保存。import React, { useState, useRef, useEffect } from "react";import { Table, Input, Checkbox } from "antd";const { Search } = Input;export default () => { const initialData: any = useRef([]); //使用useRef创建initialData const [data, setData] = useState([ { key: 1, title: "普通餐厅(中餐/日料/西餐厅)", checkboxData: [ { key: 12, title: "普通服务员" }, { key: 13, title: "收银" }, { key: 14, title: "迎宾/接待" }, ], }, { key: 2, title: "零售/快消/服装", checkboxData: [ { key: 17, title: "基础店员" }, { key: 19, title: "收银员" }, { key: 20, title: "理货员" }, ], }, ]); useEffect(() => { initialData.current = [...data]; //设置初始化值 }, []); const [checkedJob, setCheckedJob] = useState([]); //设置子级中选择的类 const [selectedRowKeys, setSelectedRowKeys] = useState([]); //设置选择的行 const expandedRowRender = (record: any) => { return ( 请选择岗位,或勾选类别全选岗位 {record.checkboxData.map((item: any) => {return (value={item.key}key={item.key}onChange={checkChange}>{item.title});})} ); }; const rowSelection = { selectedRowKeys, }; return ( style={{ background: "#fff", padding: 24, boxSizing: "border-box", width: 982, }} > placeholder="请输入岗位名称" onSearch={(value) => {console.log(loop(value)); }} /> showHeader={false} columns={columns} expandable={{expandedRowRender, }} dataSource={data} pagination={false} rowSelection={rowSelection} /> );};const columns = [{ title: "title", dataIndex: "title", key: "title" }];import React, { useState, useRef, useEffect } from "react";import { Table, Input, Checkbox } from "antd";const { Search } = Input;export default () => { const initialData: any = useRef([]); //使用useRef创建initialData const [data, setData] = useState([ { key: 1, title: "普通餐厅(中餐/日料/西餐厅)", checkboxData: [ { key: 12, title: "普通服务员" }, { key: 13, title: "收银" }, { key: 14, title: "迎宾/接待" }, ], }, { key: 2, title: "零售/快消/服装", checkboxData: [ { key: 17, title: "基础店员" }, { key: 19, title: "收银员" }, { key: 20, title: "理货员" }, ], }, ]); useEffect(() => { initialData.current = [...data]; //设置初始化值 }, []); const [checkedJob, setCheckedJob] = useState([]); //设置子级中选择的类 const [selectedRowKeys, setSelectedRowKeys] = useState([]); //设置选择的行 const expandedRowRender = (record: any) => { return ( 请选择岗位,或勾选类别全选岗位 {record.checkboxData.map((item: any) => {return (value={item.key}key={item.key}onChange={checkChange}>{item.title});})} ); }; const rowSelection = { selectedRowKeys, }; return ( style={{ background: "#fff", padding: 24, boxSizing: "border-box", width: 982, }} > placeholder="请输入岗位名称" onSearch={(value) => {console.log(loop(value)); }} /> showHeader={false} columns={columns} expandable={{expandedRowRender, }} dataSource={data} pagination={false} rowSelection={rowSelection} /> );};const columns = [{ title: "title", dataIndex: "title", key: "title" }];2、一级选中(取消全选)2、一级选中(取消全选)当一级选中(取消全选)时,需要更新对应二级选项的状态。在antd文档中,使用rowSelection的onSelect,可以设置选择/取消选择某行的回调。onSelect:(record,selected)=> record:操作当前行的数据,selected:true:全选,false:取消全选注意:当全选时,不能直接添加当前一级下的所有二级,需要过滤掉当前已经选中的二级具体逻辑如下代码://首选在rowSelection配置中添加onSelectconst rowSelection = { selectedRowKeys, onSelect };//一级全选或者取消的逻辑const onSelect = (record: any, selected: any) => { //因为存在搜索,所以需要使用我们的初始化数据,找到当前record.key在初始化数据中对应的数据 let initialParent = initialData.current.find((d: any) => d.key === record.key );//初始化数据中对应的二级数据 let selectParentData = initialParent.checkboxData? initialParent.checkboxData.map((d: any) => d.key): []; if (selected) { //全选//向selectRowKeys添加选中的值setSelectedRowKeys([...selectedRowKeys, record.key]);//更新child数组,将selectParentData中的数据全部过滤添加setCheckedJob(Array.from(new Set([...checkedJob, ...selectParentData]))); } else { //取消全选//从父级数组中移除key值setSelectedRowKeys([...selectedRowKeys].filter((d: any) => d !== record.key));//更新child数组,将selectParentData中的数据全部过滤掉let newArr: any = [];[...checkedJob].forEach((v) => {if (selectParentData.indexOf(v) === -1) {newArr.push(v);}});setCheckedJob(newArr); } };//首选在rowSelection配置中添加onSelectconst rowSelection = { selectedRowKeys, onSelect };//一级全选或者取消的逻辑const onSelect = (record: any, selected: any) => { //因为存在搜索,所以需要使用我们的初始化数据,找到当前record.key在初始化数据中对应的数据 let initialParent = initialData.current.find((d: any) => d.key === record.key );//初始化数据中对应的二级数据 let selectParentData = initialParent.checkboxData? initialParent.checkboxData.map((d: any) => d.key): []; if (selected) { //全选//向selectRowKeys添加选中的值setSelectedRowKeys([...selectedRowKeys, record.key]);//更新child数组,将selectParentData中的数据全部过滤添加setCheckedJob(Array.from(new Set([...checkedJob, ...selectParentData]))); } else { //取消全选//从父级数组中移除key值setSelectedRowKeys([...selectedRowKeys].filter((d: any) => d !== record.key));//更新child数组,将selectParentData中的数据全部过滤掉let newArr: any = [];[...checkedJob].forEach((v) => {if (selectParentData.indexOf(v) === -1) {newArr.push(v);}});setCheckedJob(newArr); } };3、二级选中或取消选中逻辑3、二级选中或取消选中逻辑二级选中或者取消比较简单,只要注意在选中时,如何去考虑是否所有二级全部选中即可。具体代码如下。//判断b数组中的数据是否全部在a数组中 const isContained = (a: any, b: any) => { if (!(a instanceof Array) || !(b instanceof Array)) return false; if (a.length var aStr = a.toString(); for (var i = 0, len = b.length; i if (aStr.indexOf(b[i]) == -1) return false; } return true; };//设置checkbox的onChange事件 const checkChange = (e: any) => { let praentRowsKey: any; //找到选中的二级对应的父级key initialData.current.forEach((v: any) => {if (v.checkboxData.find((d: any) => d.key === e.target.value)) {praentRowsKey = v.key;} }); if (e.target.checked) {//选中时 设置当前的check数组let newCheckedJob = [...checkedJob, e.target.value];setCheckedJob(newCheckedJob);//判断当前二级的内容是否全部被选中,如果全部选中,则需要设置selectedRowKeys//praentRowsKey下的所有子元素let childArr = initialData.current.find((d: any) => d.key === praentRowsKey)?.checkboxData?.map((i: any) => i.key);// 为当前选择之后的新数组if (isContained(newCheckedJob, childArr)) {//全部包含,设置父级setSelectedRowKeys([...selectedRowKeys, praentRowsKey]);} } else {//取消选中 设置当前的child数组setCheckedJob([...checkedJob].filter((d: number) => d !== e.target.value));//判断当前父级中是否存在praentRowsKey,存在则去除if (!!~selectedRowKeys.indexOf(praentRowsKey)) {setSelectedRowKeys([...selectedRowKeys].filter((d: any) => d !== praentRowsKey));} } };//判断b数组中的数据是否全部在a数组中 const isContained = (a: any, b: any) => { if (!(a instanceof Array) || !(b instanceof Array)) return false; if (a.length var aStr = a.toString(); for (var i = 0, len = b.length; i if (aStr.indexOf(b[i]) == -1) return false; } return true; };//设置checkbox的onChange事件 const checkChange = (e: any) => { let praentRowsKey: any; //找到选中的二级对应的父级key initialData.current.forEach((v: any) => {if (v.checkboxData.find((d: any) => d.key === e.target.value)) {praentRowsKey = v.key;} }); if (e.target.checked) {//选中时 设置当前的check数组let newCheckedJob = [...checkedJob, e.target.value];setCheckedJob(newCheckedJob);//判断当前二级的内容是否全部被选中,如果全部选中,则需要设置selectedRowKeys//praentRowsKey下的所有子元素let childArr = initialData.current.find((d: any) => d.key === praentRowsKey)?.checkboxData?.map((i: any) => i.key);// 为当前选择之后的新数组if (isContained(newCheckedJob, childArr)) {//全部包含,设置父级setSelectedRowKeys([...selectedRowKeys, praentRowsKey]);} } else {//取消选中 设置当前的child数组setCheckedJob([...checkedJob].filter((d: number) => d !== e.target.value));//判断当前父级中是否存在praentRowsKey,存在则去除if (!!~selectedRowKeys.indexOf(praentRowsKey)) {setSelectedRowKeys([...selectedRowKeys].filter((d: any) => d !== praentRowsKey));} } };4、搜索过滤4、搜索过滤前3步骤完成后,目前来说,正常的一级二级联动已经完成,现在进行第4步,搜索过滤。简单的说,搜索的时候,只要改变我们的data,就可以重新渲染Table,这样就可以达成搜索过滤的效果。具体代码如下//Search组件搜索时,触发更改data placeholder="请输入岗位名称" onSearch={(value) => {setData(loop(value)); }}/> //搜索岗位时,进行过滤 const loop = (searchValue: any) => { let loopData = initialData.current?.map((item: any) => {//判断一级是否包含该搜索内容let parentKey = !!~item.title.indexOf(searchValue);let childrenData: any = [];if (item.checkboxData) {//如果存在二级,则进行二级的循环,过滤出搜索到的valuechildrenData = item.checkboxData.filter((d: any) => !!~d.title.indexOf(searchValue));}//如果一级有,二级没有,则展示一级下所有的二级//如果一级没有,二级有,则只展示存在的二级以及对应的一级//如果一级有,二级有,则展示存在的二级以及对应的一级//如果一级没有,二级也没有,则不展示if(parentKey&&!childrenData.length){return {title:item.title,key:item.key,checkboxData:item.checkboxData}}else if((!parentKey || parentKey)&&childrenData.length){return{title:item.title,key:item.key,checkboxData:childrenData}}else{} });//搜索的值不为空时,返回搜索过滤后都数据(因为map出来的数据中有undefined,所以需要再次进行过滤),为空时返回初始化数据 return searchValue ? loopData.filter((d: any) => d) : initialData.current; };//Search组件搜索时,触发更改data placeholder="请输入岗位名称" onSearch={(value) => {setData(loop(value)); }}/> //搜索岗位时,进行过滤 const loop = (searchValue: any) => { let loopData = initialData.current?.map((item: any) => {//判断一级是否包含该搜索内容let parentKey = !!~item.title.indexOf(searchValue);let childrenData: any = [];if (item.checkboxData) {//如果存在二级,则进行二级的循环,过滤出搜索到的valuechildrenData = item.checkboxData.filter((d: any) => !!~d.title.indexOf(searchValue));}//如果一级有,二级没有,则展示一级下所有的二级//如果一级没有,二级有,则只展示存在的二级以及对应的一级//如果一级有,二级有,则展示存在的二级以及对应的一级//如果一级没有,二级也没有,则不展示if(parentKey&&!childrenData.length){return {title:item.title,key:item.key,checkboxData:item.checkboxData}}else if((!parentKey || parentKey)&&childrenData.length){return{title:item.title,key:item.key,checkboxData:childrenData}}else{} });//搜索的值不为空时,返回搜索过滤后都数据(因为map出来的数据中有undefined,所以需要再次进行过滤),为空时返回初始化数据 return searchValue ? loopData.filter((d: any) => d) : initialData.current; };5、搜索后,禁止一级全选和取消全选5、搜索后,禁止一级全选和取消全选动态控制table的选择功能,需要使用rowSelection的getCheckboxProps。具体代码如下。const [selectAllDisabled, setSelectAllDisabled] = useState(false); //声明一个变量,控制是否允许选择,默认为false//在rowSelection中添加getCheckboxPropsconst rowSelection = { selectedRowKeys, onSelect, getCheckboxProps: (record: any) => ({ disabled: selectAllDisabled,//true:禁止,false:允许 }), };//在搜索的时候设置 const loop = (searchValue: any) => { ... setSelectAllDisabled(searchValue ? true : false);//当搜索内容为空时,因为回到的是初始值,所以需要它允许选择,搜索内容不为空时,禁止选择 ... };const [selectAllDisabled, setSelectAllDisabled] = useState(false); //声明一个变量,控制是否允许选择,默认为false//在rowSelection中添加getCheckboxPropsconst rowSelection = { selectedRowKeys, onSelect, getCheckboxProps: (record: any) => ({ disabled: selectAllDisabled,//true:禁止,false:允许 }), };//在搜索的时候设置 const loop = (searchValue: any) => { ... setSelectAllDisabled(searchValue ? true : false);//当搜索内容为空时,因为回到的是初始值,所以需要它允许选择,搜索内容不为空时,禁止选择 ... };6、设置自动展开6、设置自动展开前5步完成后,如果不需要设置自动展开,则该功能就可以到此结束。设置自动展开,需要用到expandable中的onExpand以及expandedRowKeysexpandedRowKeys:展开的行,控制属性onExpand:点击展开图标时触发,(expanded,record)=> expanded:true:展开,false:收起。record:操作的当前行的数据具体代码如下:const [expandedRowKeys, setExpandedRowKeys] = useState([]); //声明变量设置展开的行,默认全都收起//table的 expandable添加 onExpand,expandedRowKeys expandable={{ expandedRowRender, onExpand, expandedRowKeys, }}/>//搜索时改变状态const loop = (searchValue: any) => { ... //有数据时自动展开所有搜索到的,无数据的时候默认全部收起 setExpandedRowKeys(searchValue ? initialData.current.map((d: any) => d.key) : [] ); ... };//控制表格的展开收起 const onExpand = (expanded: any, record: any) => { if (expanded) {setExpandedRowKeys([...expandedRowKeys, record.key]); //展开时,将需要展开的key添加到数组中 } else {setExpandedRowKeys([...expandedRowKeys].filter((d: any) => d !== record.key)//收起时,将该key移除数组); } };const [expandedRowKeys, setExpandedRowKeys] = useState([]); //声明变量设置展开的行,默认全都收起//table的 expandable添加 onExpand,expandedRowKeys expandable={{ expandedRowRender, onExpand, expandedRowKeys, }}/>//搜索时改变状态const loop = (searchValue: any) => { ... //有数据时自动展开所有搜索到的,无数据的时候默认全部收起 setExpandedRowKeys(searchValue ? initialData.current.map((d: any) => d.key) : [] ); ... };//控制表格的展开收起 const onExpand = (expanded: any, record: any) => { if (expanded) {setExpandedRowKeys([...expandedRowKeys, record.key]); //展开时,将需要展开的key添加到数组中 } else {setExpandedRowKeys([...expandedRowKeys].filter((d: any) => d !== record.key)//收起时,将该key移除数组); } };三、优化三、优化一级选择框有三种状态,全选,二级选中某些个,未选中,三种状态对应不同的样式,如下图所示。这种优化,就需要设置rowSelection的renderCell(注意,rendercell在antd的4.1+版本才能生效),配合Checkbox进行更改。具体代码如下。1、设置renderCell1、设置renderCell将我们在第二步和第五步设置的onSelect以及getCheckboxProps隐藏,再配置renderCellconst rowSelection = { selectedRowKeys, // onSelect, // getCheckboxProps: (record: any) => ({ // disabled: selectAllDisabled, // }), renderCell: (checked: any, record: any) => {//当前record.key对应大初始化数据的一级所有数据let parentArr = initialData?.current?.find((d: any) => d.key === record.key);//从所有已经选择过的数据中过滤出在parentArr中的数据let checkArr = parentArr?.checkboxData?.filter((item: any) => checkedJob.indexOf(item.key) > -1);return (indeterminate={parentArr?.checkboxData &&!!checkArr?.length &&checkArr.length ? true: false} //比较 当过滤后选中数据的长度 onClick={(e) => onClick(e, record)}checked={checked}disabled={selectAllDisabled}>); }, };const rowSelection = { selectedRowKeys, // onSelect, // getCheckboxProps: (record: any) => ({ // disabled: selectAllDisabled, // }), renderCell: (checked: any, record: any) => {//当前record.key对应大初始化数据的一级所有数据let parentArr = initialData?.current?.find((d: any) => d.key === record.key);//从所有已经选择过的数据中过滤出在parentArr中的数据let checkArr = parentArr?.checkboxData?.filter((item: any) => checkedJob.indexOf(item.key) > -1);return (indeterminate={parentArr?.checkboxData &&!!checkArr?.length &&checkArr.length ? true: false} //比较 当过滤后选中数据的长度 onClick={(e) => onClick(e, record)}checked={checked}disabled={selectAllDisabled}>); }, };2、设置onClick事件2、设置onClick事件onClick事件其实就是原来的onSelect,具体代码如下const onClick = (e: any, record: any) => { //存在搜索时,需要进行处理selectParentData let initialParent = initialData.current.find((d: any) => d.key === record.key ); let selectParentData = initialParent.checkboxData? initialParent.checkboxData.map((d: any) => d.key): []; if (e.target.checked) {//向选中数组中添加key值setSelectedRowKeys([...selectedRowKeys, record.key]);//更新child数组,将selectParentData中的数据全部过滤添加setCheckedJob(Array.from(new Set([...checkedJob, ...selectParentData]))); } else {//从父级数组中移除key值setSelectedRowKeys([...selectedRowKeys].filter((d: any) => d !== record.key));//更新child数组,将selectParentData中的数据全部过滤掉let newArr: any = [];[...checkedJob].forEach((v) => {if (selectParentData.indexOf(v) === -1) {newArr.push(v);}});setCheckedJob(newArr); } }; const onClick = (e: any, record: any) => { //存在搜索时,需要进行处理selectParentData let initialParent = initialData.current.find((d: any) => d.key === record.key ); let selectParentData = initialParent.checkboxData? initialParent.checkboxData.map((d: any) => d.key): []; if (e.target.checked) {//向选中数组中添加key值setSelectedRowKeys([...selectedRowKeys, record.key]);//更新child数组,将selectParentData中的数据全部过滤添加setCheckedJob(Array.from(new Set([...checkedJob, ...selectParentData]))); } else {//从父级数组中移除key值setSelectedRowKeys([...selectedRowKeys].filter((d: any) => d !== record.key));//更新child数组,将selectParentData中的数据全部过滤掉let newArr: any = [];[...checkedJob].forEach((v) => {if (selectParentData.indexOf(v) === -1) {newArr.push(v);}});setCheckedJob(newArr); } }; 四、完整代码Table+Checkbox模拟Tree完整代码import React, { useState, useRef, useEffect } from "react";import { Table, Input, Checkbox } from "antd";const { Search } = Input;export default () => { const initialData: any = useRef([]); const [data, setData] = useState([ {key: 1,title: "普通餐厅(中餐/日料/西餐厅)",checkboxData: [{ key: 12, title: "普通服务员" },{ key: 13, title: "收银" },{ key: 14, title: "迎宾/接待" },], }, {key: 2,title: "零售/快消/服装",checkboxData: [{ key: 17, title: "基础店员" },{ key: 19, title: "收银员" },{ key: 20, title: "理货员" },], }, ]); useEffect(() => { initialData.current = [...data]; //设置初始化值 }, []); const [checkedJob, setCheckedJob] = useState([12]); //设置选择的二级 const [selectedRowKeys, setSelectedRowKeys] = useState([]); //设置选择的行 const [expandedRowKeys, setExpandedRowKeys] = useState([]); //设置展开的行 const [selectAllDisabled, setSelectAllDisabled] = useState(false); //选择的时候,禁止全选 //搜索岗位时,进行过滤 const loop = (searchValue: any) => { let loopData = initialData.current?.map((item: any) => {let parentKey = !!~item.title.indexOf(searchValue);let childrenData: any = [];if (item.checkboxData) {//如果存在二级,则进行二级的循环,过滤出搜索到的valuechildrenData = item.checkboxData.filter((d: any) => !!~d.title.indexOf(searchValue));}//1.如果一级有,二级没有,则展示一级下所有的二级//2.如果一级没有,二级有,则只展示存在的二级以及对应的一级//3.如果一级有,二级有,则展示则存在的二级以及对应的一级//4.如果一级没有,二级也没有,则不展示if (parentKey && !childrenData.length) {return {title: item.title,key: item.key,checkboxData: item.checkboxData,};} else if ((!parentKey || parentKey) && childrenData.length) {return {title: item.title,key: item.key,checkboxData: childrenData,};} else {} }); setSelectAllDisabled(searchValue ? true : false); //有数据时自动展开所有搜索到的,无数据的时候默认全部收起 setExpandedRowKeys(searchValue ? initialData.current.map((d: any) => d.key) : [] ); return searchValue ? loopData.filter((d: any) => d) : initialData.current; }; const isContained = (a: any, b: any) => { if (!(a instanceof Array) || !(b instanceof Array)) return false; if (a.length var aStr = a.toString(); for (var i = 0, len = b.length; i if (aStr.indexOf(b[i]) == -1) return false; } return true; }; const checkChange = (e: any) => { let praentRowsKey: any; //找到点击child到一级key initialData.current.forEach((v: any) => {if (v.checkboxData.find((d: any) => d.key === e.target.value)) {praentRowsKey = v.key;} }); if (e.target.checked) {//选中时 设置当前的child数组let newCheckedJob = [...checkedJob, e.target.value];setCheckedJob(newCheckedJob);//判断当前child的内容是否全部被选中,如果全部选中,则需要设置selectedRowKeys//praentRowsKey下的所有子元素let childArr = initialData.current.find((d: any) => d.key === praentRowsKey)?.checkboxData?.map((i: any) => i.key);// 为当前选择之后的新数组if (isContained(newCheckedJob, childArr)) {//全部包含,设置父级setSelectedRowKeys([...selectedRowKeys, praentRowsKey]);} } else {//取消选中 设置当前的child数组setCheckedJob([...checkedJob].filter((d: number) => d !== e.target.value));//判断当前父级中是否存在praentRowsKey,存在则去除if (!!~selectedRowKeys.indexOf(praentRowsKey)) {setSelectedRowKeys([...selectedRowKeys].filter((d: any) => d !== praentRowsKey));} } }; //父节点变化时,进行的操作 // const onSelect = (record: any, selected: any) => { ////存在搜索时,需要进行处理selectParentData //let initialParent = initialData.current.find( //(d: any) => d.key === record.key //); //let selectParentData = initialParent.checkboxData //? initialParent.checkboxData.map((d: any) => d.key) //: []; //if (selected) { ////向选中数组中添加key值 //setSelectedRowKeys([...selectedRowKeys, record.key]); ////更新child数组,将selectParentData中的数据全部过滤添加 //setCheckedJob(Array.from(new Set([...checkedJob, ...selectParentData]))); //} else { ////从父级数组中移除key值 //setSelectedRowKeys( //[...selectedRowKeys].filter((d: any) => d !== record.key) //); ////更新child数组,将selectParentData中的数据全部过滤掉 //let newArr: any = []; //[...checkedJob].forEach((v) => { //if (selectParentData.indexOf(v) === -1) { //newArr.push(v); //} //}); //setCheckedJob(newArr); //} // }; //控制表格的展开收起 const onExpand = (expanded: any, record: any) => { //expanded: true展开,false:关闭 if (expanded) {setExpandedRowKeys([...expandedRowKeys, record.key]); } else {setExpandedRowKeys([...expandedRowKeys].filter((d: any) => d !== record.key)); } }; const onClick = (e: any, record: any) => { //存在搜索时,需要进行处理selectParentData let initialParent = initialData.current.find((d: any) => d.key === record.key ); let selectParentData = initialParent.checkboxData? initialParent.checkboxData.map((d: any) => d.key): []; if (e.target.checked) {//向选中数组中添加key值setSelectedRowKeys([...selectedRowKeys, record.key]);//更新child数组,将selectParentData中的数据全部过滤添加setCheckedJob(Array.from(new Set([...checkedJob, ...selectParentData]))); } else {//从父级数组中移除key值setSelectedRowKeys([...selectedRowKeys].filter((d: any) => d !== record.key));//更新child数组,将selectParentData中的数据全部过滤掉let newArr: any = [];[...checkedJob].forEach((v) => {if (selectParentData.indexOf(v) === -1) {newArr.push(v);}});setCheckedJob(newArr); } }; const expandedRowRender = (record: any) => { return (请选择岗位,或勾选类别全选岗位{record.checkboxData.map((item: any) => {return (value={item.key}key={item.key}onChange={checkChange}>{item.title});})} ); }; const rowSelection = { selectedRowKeys, // onSelect, // getCheckboxProps: (record: any) => ({ // disabled: selectAllDisabled, // }), renderCell: (checked: any, record: any) => {//当前record.key对应大初始化数据的一级所有数据let parentArr = initialData?.current?.find((d: any) => d.key === record.key);//从所有已经选择过的数据中过滤出在parentArr中的数据let checkArr = parentArr?.checkboxData?.filter((item: any) => checkedJob.indexOf(item.key) > -1);return (indeterminate={parentArr?.checkboxData &&!!checkArr?.length &&checkArr.length ? true: false} //比较 当过滤后选中数据的长度 onClick={(e) => onClick(e, record)}checked={checked}disabled={selectAllDisabled}>); }, }; return ( style={{background: "#fff",padding: 24,boxSizing: "border-box",width: 982,}} >placeholder="请输入岗位名称"onSearch={(value) => {console.log(loop(value));setData(loop(value));}}/>showHeader={false}columns={columns}expandable={{expandedRowRender,onExpand,expandedRowKeys,}}dataSource={data}pagination={false}rowSelection={rowSelection}/> );};const columns = [{ title: "title", dataIndex: "title", key: "title" }];import React, { useState, useRef, useEffect } from "react";import { Table, Input, Checkbox } from "antd";const { Search } = Input;export default () => { const initialData: any = useRef([]); const [data, setData] = useState([ {key: 1,title: "普通餐厅(中餐/日料/西餐厅)",checkboxData: [{ key: 12, title: "普通服务员" },{ key: 13, title: "收银" },{ key: 14, title: "迎宾/接待" },], }, {key: 2,title: "零售/快消/服装",checkboxData: [{ key: 17, title: "基础店员" },{ key: 19, title: "收银员" },{ key: 20, title: "理货员" },], }, ]); useEffect(() => { initialData.current = [...data]; //设置初始化值 }, []); const [checkedJob, setCheckedJob] = useState([12]); //设置选择的二级 const [selectedRowKeys, setSelectedRowKeys] = useState([]); //设置选择的行 const [expandedRowKeys, setExpandedRowKeys] = useState([]); //设置展开的行 const [selectAllDisabled, setSelectAllDisabled] = useState(false); //选择的时候,禁止全选 //搜索岗位时,进行过滤 const loop = (searchValue: any) => { let loopData = initialData.current?.map((item: any) => {let parentKey = !!~item.title.indexOf(searchValue);let childrenData: any = [];if (item.checkboxData) {//如果存在二级,则进行二级的循环,过滤出搜索到的valuechildrenData = item.checkboxData.filter((d: any) => !!~d.title.indexOf(searchValue));}//1.如果一级有,二级没有,则展示一级下所有的二级//2.如果一级没有,二级有,则只展示存在的二级以及对应的一级//3.如果一级有,二级有,则展示则存在的二级以及对应的一级//4.如果一级没有,二级也没有,则不展示if (parentKey && !childrenData.length) {return {title: item.title,key: item.key,checkboxData: item.checkboxData,};} else if ((!parentKey || parentKey) && childrenData.length) {return {title: item.title,key: item.key,checkboxData: childrenData,};} else {} }); setSelectAllDisabled(searchValue ? true : false); //有数据时自动展开所有搜索到的,无数据的时候默认全部收起 setExpandedRowKeys(searchValue ? initialData.current.map((d: any) => d.key) : [] ); return searchValue ? loopData.filter((d: any) => d) : initialData.current; }; const isContained = (a: any, b: any) => { if (!(a instanceof Array) || !(b instanceof Array)) return false; if (a.length var aStr = a.toString(); for (var i = 0, len = b.length; i if (aStr.indexOf(b[i]) == -1) return false; } return true; }; const checkChange = (e: any) => { let praentRowsKey: any; //找到点击child到一级key initialData.current.forEach((v: any) => {if (v.checkboxData.find((d: any) => d.key === e.target.value)) {praentRowsKey = v.key;} }); if (e.target.checked) {//选中时 设置当前的child数组let newCheckedJob = [...checkedJob, e.target.value];setCheckedJob(newCheckedJob);//判断当前child的内容是否全部被选中,如果全部选中,则需要设置selectedRowKeys//praentRowsKey下的所有子元素let childArr = initialData.current.find((d: any) => d.key === praentRowsKey)?.checkboxData?.map((i: any) => i.key);// 为当前选择之后的新数组if (isContained(newCheckedJob, childArr)) {//全部包含,设置父级setSelectedRowKeys([...selectedRowKeys, praentRowsKey]);} } else {//取消选中 设置当前的child数组setCheckedJob([...checkedJob].filter((d: number) => d !== e.target.value));//判断当前父级中是否存在praentRowsKey,存在则去除if (!!~selectedRowKeys.indexOf(praentRowsKey)) {setSelectedRowKeys([...selectedRowKeys].filter((d: any) => d !== praentRowsKey));} } }; //父节点变化时,进行的操作 // const onSelect = (record: any, selected: any) => { ////存在搜索时,需要进行处理selectParentData //let initialParent = initialData.current.find( //(d: any) => d.key === record.key //); //let selectParentData = initialParent.checkboxData //? initialParent.checkboxData.map((d: any) => d.key) //: []; //if (selected) { ////向选中数组中添加key值 //setSelectedRowKeys([...selectedRowKeys, record.key]);

js如何基于viewport vm适配移动端页面js大全

前言前言作为一个小前端,经常要和H5打交道,这就面临着不同终端的适配问题。Flexible方案通过Hack手段来根据设备的dpr值相应改变标签中viewport的值前言前言作为一个小前端,经常要和H5打交道,这就面临着不同终端的适配问题。Flexible方案通过Hack手段来根据设备的dpr值相应改变标签中viewport的值,给我更贴切的体会就是通过js脚本根据设备的dpr和设计图的宽度来计算出html的font-size值,然后就运用rem单位开发可以等比例缩放的H5页面。但是!Flexible已经完成了他自身的历史使命,我们可以放下Flexible,拥抱新的变化。做到适配要解决的问题在移动端布局,我们需要面对两个最为重要的问题:各终端下的适配问题Retina屏的细节处理在移动端布局,我们需要面对两个最为重要的问题:各终端下的适配问题Retina屏的细节处理不同的终端,我们面对的屏幕分辨率、DPR、1px、2x图等一系列的问题。那么这个布局方案也是针对性的解决这些问题,只不过解决这些问题不再是使用Hack手段来处理,而是直接使用原生的CSS技术来处理的。适配终端以前的Flexible方案是通过JavaScript来模拟vw的特性,那么到今天为止,vw已经得到了众多浏览器的支持,也就是说,可以直接考虑将vw单位运用于我们的适配布局中。vw是基于Viewport视窗的长度单位,这里的视窗(Viewport)指的就是浏览器可视化的区域,而这个可视区域是window.innerWidth/window.innerHeight的大小。用下图简单的来示意一下:蓝色区域就是 window.innerWidth 和 window.innerHeight在CSS Values and Units Module Level 3中和Viewport相关的单位有四个,分别为vw、vh、vmin和vmax。CSS Values and Units Module Level 3vw:是Viewport's width的简写,1vw等于window.innerWidth的1%vh:和vw类似,是Viewport's height的简写,1vh等于window.innerHeihgt的1%vmin:vmin的值是当前vw和vh中较小的值vmax:vmax的值是当前vw和vh中较大的值vw:是Viewport's width的简写,1vw等于window.innerWidth的1%vh:和vw类似,是Viewport's height的简写,1vh等于window.innerHeihgt的1%vmin:vmin的值是当前vw和vh中较小的值vmax:vmax的值是当前vw和vh中较大的值如果设计稿用750px宽度的,100vw = 750px,即1vw = 7.5px。那么我们可以根据设计图上的px值直接转换成对应的vw值。如果不想自己计算,我们可以使用PostCSS的插件postcss-px-to-viewport,让我们可以直接在代码中写px。postcss-px-to-viewport以上就是本文的全部内容,希望对大家的学习有所帮助。
标签:

前端切图外包-什么是切图?

 什么是切图? 我觉得百度的解释就很准确了,以下来自百度: 切图是指将设计稿切成便于制作成页面的图片,并完成html+css布局的静态页面,有利于交互,形成良好的视觉感。通俗来讲,把一张设计图利用到切片工具 把自己所需的切成一张张小图,然后用DIV+CSS完成静态页面书写,完成CSS布局。 ... 什么是切图? 我觉得百度的解释就很准确了,以下来自百度: 切图是指将设计稿切成便于制作成页面的图片,并完成html+css布局的静态页面,有利于交互,形成良好的视觉感。通俗来讲,把一张设计图利用到切片工具 把自己所需的切成一张张小图,然后用DIV+CSS完成静态页面书写,完成CSS布局。 在以前,这就是前端er的全部工作内容,只不过,现在的前端已经变成了大前端,除了切图还有各种JS框架,数据等等。 误区:切图不在重要了? 很多人认为切图不重要了,其实从切图网的项目来看,就知道切图依然重要,标准的规范的前端页面切图依然有市场需求,中小企业做网站仍然需要切图,而vue,node,angular通常是在大型的项目中才会用到,所以在此也提醒一下初学web前端,并且想靠前端就业的人, 不要忽略了切图,除非你学出来,就直接保送到大公司做大项目开发,那么可以学以致用,否则就很危险。 不然,怎么还有那么多人没找到工作?原因在于定位
标签:

前端开发外包工作模式

以前老的方式是:1.产品经历/领导/客户提出需求2.UI做出设计图3.前端工程师做html页面4.后端工程师将html页面套成jsp/php页面(前后端强依赖,后端必须要等前端的html做好才能套jsp/php。如果html发生变更,开发效率低)5.集成出现问题...以前老的方式是:1.产品经历/领导/客户提出需求2.UI做出设计图3.前端工程师做html页面4.后端工程师将html页面套成jsp/php页面(前后端强依赖,后端必须要等前端的html做好才能套jsp/php。如果html发生变更,开发效率低)5.集成出现问题6.前端返工7.后端返工8.二次集成9.集成成功10.交付 新的方式是:1.产品经历/领导/客户提出需求2.UI做出设计图3.前后端约定接口&数据&参数4.前后端并行开发(无强依赖,可前后端并行开发,如果需求变更,只要接口&参数不变,就不用两边都修改代码,开发效率高)5.前后端集成6.前端页面调整7.集成成功8.交付  请求方式 以前老的方式是:1.客户端请求2.服务端的servlet或controller接收请求(后端控制路由与渲染页面,整个项目开发的权重大部分在后端)3.调用service,dao代码完成业务逻辑4.返回jsp/php5.jsp/php展现一些动态的代码 新的方式是:1.浏览器发送请求2.直接到达html页面(前端控制路由与渲染页面,整个项目开发的权重前移)3.html页面负责调用服务端接口产生数据(通过ajax等等,后台返回json格式数据,json数据格式因为简洁高效而取代xml)4.填充html,展现动态效果,在页面上进行解析并操作DOM。(可以访问一下阿里巴巴等大型网站,然后按一下F12,监控一下刷新一次页面,大多数都是单独请求后台数据,使用json传输数据,而不是一个大而全的http请求把整个页面包括动+静全部返回过来)
标签: