复盘工作-2024-07
2024-07-01
1.练习模拟按钮点击事件,同时练习输出时间
<template> <!-- js触发按钮点击事件并输出当前时间 --> <button id="time-btn" @click="onTimeBtnClicked()"></button> </template> <script> export default { name: "Business.vue", mounted(){ // js触发按钮点击事件 // 第一:仅用于测试,不建议在mounted() // 钩子函数里模拟dom元素的click方法。 // 第二:我一开始写的是错的: // this.getElementById('time-btn').click(); // 会报错:TypeError: this.getElementById // is not a function // 在vue中this关键词通常指向vue实例, // 而不是dom元素或全局的window对象 // vue实例并没有getElementById方法 // getElementById是dom api的一部分, // 它通常是在全局的document对象上调用的。 this.simulateClick(); }, methods: { // 模拟按钮点击事件 simulateClick() { let timeBtn = document.getElementById('time-btn'); if (timeBtn) { timeBtn.click(); } }, // time-btn按钮点击事件 onTimeBtnClicked() { const _self = this; let date = new Date(); let time = _self.getTime(date); alert(time); }, // 输出当前时间 getTime(date) { let d = new Date(date); // String()方法,作为类型转换函数来用 let month = String(d.getMonth() + 1); let day = String(d.getDate()); let year = String(d.getFullYear()); if (month.length < 2) { month = '0' + month; } if (day.length < 2) { day = '0' + day; } let hours = String(d.getHours()); let minute = String(d.getMinutes()); let seconds = String(d.getSeconds()); if (hours.length < 2) { hours = '0' + hours; } if (minute.length < 2) { minute = '0' + minute; } if (seconds.length < 2) { seconds = '0' + seconds; } // .join('')返回 // 将数组元素以参数字符串为分隔 // 拼接成的字符串 // 2024-07-01 20:47:33 return [year, month, day].join('-') + ' ' + [hours, minute, seconds].join(':'); } } } </script>
2024-07-02
1.通过对framework7的action选择组件自带的遮罩层添加点击事件监听(通过类名,通过js的document.getElementsByClassName获取数组,取第一个),触发监听后再销毁该组件,从而避免点击遮罩层关闭action(没有修改父组件isShowWfOptionListPop导致继续点击按钮无法再次打开弹窗的问题。
练习业务页面:Business.vue:
<template> <f7-page> <button @click="openWfOptionListPop()">打开工作流选项组件</button> <wf-option-list-pop v-if="isShowWfOptionListPop" @handleCloseOptionPop="handleCloseOptionPop"></wf-option-list-pop> </f7-page> </template> <script> import WfOptionListPop from "@/views/common/WfOptionListPop"; export default { name: "Business", components: { WfOptionListPop }, data() { return { isShowWfOptionListPop: false } }, methods: { handleCloseOptionPop() { this.isShowWfOptionListPop = false; }, openWfOptionListPop() { this.isShowWfOptionListPop = true; } } } </script>
练习含有f7 action组件的工作流选择组件:WfOptionListPop.vue:
<template> <f7-page name="about"> <f7-block strong> <f7-button class="col" fill actions-open="#actions-two-groups" id="btn-open-actions" >办理 </f7-button> </f7-block> <!-- Two Groups --> <f7-actions style="text-align: center" id="actions-two-groups"> <f7-actions-group> <f7-actions-label>操作列表</f7-actions-label> <f7-actions-button v-for="(button,index) in buttonGroup" :bold="index===0" :key="button.type" @click="alertSelectText(button.text)" > {{button.text}} </f7-actions-button> </f7-actions-group> <f7-actions-group> <f7-actions-button color="red" @click="onCloseBtnClicked()">关闭</f7-actions-button> </f7-actions-group> </f7-actions> </f7-page> </template> <script> export default { name: "WfOptionListPop", data() { return { buttonGroup: [ { type: 'approve', text: '同意' }, { type: 'reject', text: '驳回' } ] } }, mounted() { document.getElementById('btn-open-actions').click(); /** * 为解决点击页面除“操作列表”外其他区域后, * 会关闭弹窗,但再次点击 * “打开工作流选项组件”无法打开弹窗的问题 * * 解决方案: * 对页面除“操作列表”外其他区域, * 加上点击事件监听, * (根据F12获取到的class * 来选择到元素进行监听, * class='actions-backdrop backdrop-in') * * 我一开始写错了: * document.getElementsByClassName * ('actions-backdrop backdrop-in') * .addEventListener * 控制台报错: * TypeError: document.getElementsByClassName(...) * .addEventListener is not a function * 因为document.getElementsByClassName返回的是一个数组 */ // 验证document.getElementsByClassName // 确实是返回数组 // let arr = document.getElementsByClassName // ('actions-backdrop backdrop-in'); // if (arr && arr.length > 0) { // alert('document.getElementsByClassName()' // + '确实返回一个数组,数组长度为' // + arr.length); // } /** * 我一开始写错不好使, * type写为onclick, * 后来写的也不对clicked * * 解析: * 点击事件的监听, * addEventListener的参数type, * 必须是'click', * 否则无法监听到。 * * 我一开始监听触发的函数里写的是 * document.getElementsByClassName ('actions-backdrop backdrop-in') [0].addEventListener('click', function() { debugger; this.$emit('handleCloseOptionPop'); }) * 运行时控制台报错 * Uncaught TypeError: this.$emit is not a function * at HTMLDivElement.eval * 需要改为在这里调用函数, * 然后在函数里去执行this.$emit('handleCloseOptionPop'); * * 后来我改为 * document.getElementsByClassName ('actions-backdrop backdrop-in') [0].addEventListener('click', function() { debugger; this.destoryOptionPop(); }) * 测试控制台报错: * Uncaught TypeError: * this.destoryOptionPop is not a function * at HTMLDivElement.eval * * 解决方案: * addEventListener('click',) * 第二个参数listener * 应该写为函数this.destoryOptionPop */ document.getElementsByClassName ('actions-backdrop backdrop-in') [0].addEventListener ('click', this.destoryOptionPop); }, methods: { alertSelectText(text) { alert(text); this.destoryOptionPop(); }, onCloseBtnClicked() { // alert('点击了关闭'); this.destoryOptionPop(); }, // 销毁该组件 destoryOptionPop() { // debugger; // 触发父组件监听,销毁该wfOptionListPop组件 this.$emit('handleCloseOptionPop'); } } } </script> <style scoped> </style>
2024-07-03
1.如何理解点击事件的监听,addEventListener的参数type必须是'click',而不是onclick,也不是clicked,否则无法监听到:
1.1addEventListener是js中用于添加事件监听的方法,一般有两个参数,type(事件类型)、listener(当事件发生时调用的函数)
1.2对于点击事件,事件类型type参数必须取click,这是浏览器和DOM标准中定义的用于表示鼠标点击事件的字符串
2.js的setInterval函数及setTimeout函数
2.1vue环境下使用练习:
<script> export default { name: "Business", mounted() { /** * setInterval * (函数名或代码块, * 时间间隔) * 设置每隔多长时间 * 执行指定的函数, * 函数返回值为 * intervalId标识, * 可以通过 * clearInterval * (intervalId) * 来结束定时执行 * * 我一开始写错了: * setInterval * (this.hello(), 2000); * hello方法只会被执行一遍。 * * 解析原因: * setInterval需要 * 一个函数作为第一个参数, * 而我写的this.hello() * 会被直接执行,hello函数 * 无返回值,所以setInterval * 第一个参数收到了undefined。 * * 正确写法是 * 传递一个函数引用 * 作为第一个参数给setInterval * 即setInterval(this.hello, 2000); * 或使用箭头函数或匿名函数 * 来包装this.hello * setInterval * (() => this.hello(), 2000); * 或 * setInterval * (function() { * this.hello(); * }.bind(this), 2000); * 若不加.bind(this) * 会报错Uncaught TypeError: * this.hello is not a function at eval * 通过.bind(this)来确保 * this在函数内部正确地 * 指向vue组件实例 */ // let intervalId = setInterval(this.hello, 2000); // let intervalId = setInterval // (() => this.hello(), 2000); let intervalId = setInterval (function () { this.hello(); }.bind(this), 2000); /** * setInterval后面有可选参数, * 用于作为第一个参数函数 * 的入参 */ let sayGoodMorningIntervalId = setInterval (this.sayGoodMorning, 5000, 'Shandong', 'QingDao'); /** * setTimeout * (函数名或代码块, * 延迟执行的时间) * 延后多长时间后, * 执行某函数 * * 第一个参数为函数名, * 第二个参数为延迟时间 */ setTimeout(() => { clearInterval(intervalId); console.log('interval stopped!'); }, 20000); /** * setTimeout后面有可选参数, * 用于作为第一个参数函数 * 的入参 */ setTimeout( this.sayGoodMorning, 5000, 'ShanDong', 'YanTai'); }, methods: { hello() { debugger; console.log('============================== hello, world! =============================='); }, sayGoodMorning(provinceName, cityName) { console.log('good morning, ' + provinceName + ' ' + cityName + '!'); } } } </script>
2.2JSP环境下练习:
function hello() { console.log('============================== hello, world! =============================='); } /* let intervalId = setInterval(hello, 2000); */ /* let intervalId = setInterval(() => hello(), 2000); */ let intervalId = setInterval(function() { hello(); }, 2000); setTimeout(() => { clearInterval(intervalId); console.log('interval stopped!'); }, 10000);
2024-07-04
1.JSP环境下练习怎样调用getElementById:
<input id="say-hello" onclick="sayHello()">打招呼</input> <script> function sayHello() { alert('hello, world!'); } /** * 我一开始的错误写法: * getElementById('say-hello').click(); * 浏览器控制台报错: * Uncaught ReferenceError: * getElementById is not defined * * 分析: * js里getElementById是一个全局方法, * 它属于document对象。 * document是必须的, * 因为它指明了 * getElementById方法的作用域 * * 即getElementById不是 * 全局作用域下的 * 一个独立函数, * 而是绑定在document对象上的。 * 这意味着,要调用getElementById * 方法,需要通过 * document对象来访问它 * * 而全局作用域下的独立函数, * 例如alert() */ document.getElementById('say-hello').click(); </script>
2024-07-05
1.div的id的命名规范:
1.1简洁性:尽量保持简洁,过长的id会增加代码复杂性,降低可读性
1.2描述性:便于他人理解该元素在页面上的用途或内容
1.3语义化:尽量能反映元素的功能或内容。例如一个包含导航链接的div,可将其命名为nav或main-nav
1.4使用驼峰命名或小写加连字符
1.5与项目保持使用一致的命名规范
1.6避免使用过于通用的名称:例如container、box、wrapper。尽量用更描述性和独特性的名字。
2024-07-06
1.练习在页面底部三分之一幅度展示子组件(弹窗),同时梳理记忆4种定位方式:static:默认值(html默认元素位置定位)、relative:相对定位(基于其在文档流中默认位置,进行偏移)、absolute:绝对定位(基于相对于其最近的已定位(非默认定位的)祖先元素,进行偏移)、fixed:固定定位(基于浏览器窗口,进行偏移)
业务页面:Business.vue:
<template> <!-- 移动端首页 --> <f7-page name="mobilehome" id="homepage" > <button @click="openWfOptionListPop()">打开操作列表弹窗</button> <div id="wf-option-div" v-if="isShowWfOptionListPop"> <wf-option-list-pop v-if="isShowWfOptionListPop"></wf-option-list-pop> </div> </f7-page> </template> <script> import WfOptionListPop from "../common/WfOptionListPop"; export default { name: "Business", components: {WfOptionListPop }, data() { return{ isShowWfOptionListPop: false } }, methods: { openWfOptionListPop() { this.isShowWfOptionListPop = true; } } } </script> <style lang="scss" scoped> @import '../../mobilecss/todolist/WfOption.css'; </style>
对子组件展示区域的样式定义:WfOptions.css:
#wf-option-div{ /* position位置 几种取值分析: 1.static(默认的): 元素会按照正常的 文档流进行布局, 即元素的位置通过 正常的HTML布局决定, 不能通过top、bottom、 left、right属性 进行偏移。 2.relative: 相对定位。 元素首先会放置在 其原本应在文档流中的 初始位置, 然后可以通过top bottom left right 等属性来调整其位置进行偏移。 这些偏移是相对于元素 在文档流中的原始位置进行的。 这意味着元素仍然 占据它原本的空间, 但它可以从那个位置“移动” 到新的位置,同时可能会 覆盖住其他元素, 也可能会被其他元素覆盖。 3.absolute: 绝对定位。 元素完全脱离文档流, 即 它不占据空间, 也不会影响其他元素的布局。 相对于其最近的已定位 (即position不是static) 的祖先元素进行定位, 通过top bottom left right 等设置进行偏移。 若没有非默认定位的 祖先元素,则根据 视口或<html>元素 位置进行定位。 4.fixed: 固定定位。 元素完全脱离文档流, 即不占据空间, 不影响其他元素的布局。 但与absolute不同的是: fixed基于浏览器窗口进行定位, 通过top bottom left right 等设置进行偏移。 相对于浏览器窗口进行定位, 即使页面滚动, 它也会始终停留在窗口 的指定的同一位置(这里 由bottom: 0;设置的是底部) bottom:距底部偏移 bottom: 0; 元素将被定位到其包含块的底部, 元素底部边缘将与 其包含块底部边缘 对齐 width: 100%; 设置宽度为其包含块的100% z-index: 999; 确保了该元素在z轴 (堆叠顺序)上 位于其他绝大多数元素之上, 除非有其他元素z-index 值更高*/ position: fixed; bottom: 0; height: 180px; width: 100%; z-index: 999; }
在子组件中怎样设置按钮不显示出来:
<template> <f7-page name="about"> <f7-button class="col" fill actions-open="#actions-two-groups" id="btn-open-actions" >测试隐藏掉该办理按钮 </f7-button> </f7-page> </template> <style scoped> /* 控制按钮不显示 */ #btn-open-actions{ display: none; } </style>
2024-07-07
1.通过val()方法获取dom元素的value属性;通过val(参数值)设置dom元素的value属性值
<input id="messageTitle"> <script> function practiceGetSetVal() { // 通过val()获取dom元素的value属性 let messageTitleOriginal = $('#messageTitle').val(); alert(messageTitleOriginal); $('#messageTitle').val('练习通过.val(参数值)设置dom的value值'); // 通过val(参数值)设置dom元素的value属性 let messageTitleAfterSetValue = $('#messageTitle').val(); alert(messageTitleAfterSetValue); } </script>
2024-07-08
1.jsp项目,input和button差异分析:
1.1按钮样式不同;
1.2默认是否触发form提交事件不同:
1.2.1input type="button"不会触发表达的提交方法
1.2.2button 默认type="submit",会触发表达的提交方法
1.2.3button 指定type="button"不会触发表达的提交方法
<form action="sysMessageInfoController.do?doAddOrUpdate"> <!-- input type="button"不会触发表达的提交方法 --> <input type="button" onclick="test()" value="1-input-type-button"></input> <!-- button 默认type="submit",会触发表达的提交方法 --> <button onclick="test()" value="2-button-type-default-即为submit"></button> <!-- button 指定type="button"不会触发表达的提交方法 --> <button type="button" onclick="test()" value="3-button-type-button-手动定义为button"></button> </form>
2024-07-09
1.vue计算属性的使用
/* vue加载时,
data(){}先于created()
钩子函数执行
所以不能在created函数中
根据路由设置fruitCode
参数后,再在data()里
条件表达式算fruitNameLabel
的值。
正确方案:
将fruitNameLabel作为
计算属性。
computed属性:
定义计算属性。
其中每个属性都是一个
函数,函数名为计算属性值,
当其计算逻辑中的data()中
变量变化时,
该函数会自动算出新的
fruitNameLabel属性。
这一过程是vue自动实现的。
computed: {
fruitNameLabel() {
// 计算逻辑
// 基于fruitCode
// 的值计算出
// fruitNameLabel
return '';
}
}
*/
2024-07-10
1.练习computed属性
首页放验证跳转链接:Business.vue:
<template> <f7-page> <button @click="goFruitDetail('banana')">香蕉</button> <button @click="goFruitDetail('orange')">橘子</button> <button @click="goFruitDetail('pear')">莱阳梨</button> <button @click="goFruitDetail('apple')">苹果</button> </f7-page> </template> <script> export default { name: "Business", methods: { goFruitDetail(fruitCode) { debugger this.$f7router.navigate('/fruitDetail/' + fruitCode); } } } </script>
路由定义:
import FruitDetail from "@/views/fruit/FruitDetail"; { path: '/fruitDetail/:fruitCode', component: FruitDetail }
水果详情页面:
<template> <!-- 这里必须是f7-page 我一开始定义最外层 为div。有问题: 打断点跳转能调到 该页面created方法, 浏览器上url也是其url, 但页面没刷新。仍是首页 --> <f7-page> <span>水果名:</span> <input :value="fruitNameLabel"></input> </f7-page> </template> <script> export default { name: "FruitDetail", /* vue加载时, data(){}先于created() 钩子函数执行 所以不能在created函数中 根据路由设置fruitCode 参数后,再在data()里 条件表达式算fruitNameLabel 的值。 正确方案: 将fruitNameLabel作为 计算属性。 computed属性: 定义计算属性。 其中每个属性都是一个 函数,函数名为计算属性值, 当其计算逻辑中的data()中 变量变化时, 该函数会自动算出新的 fruitNameLabel属性。 这一过程是vue自动实现的。 computed: { fruitNameLabel() { // 计算逻辑 // 基于fruitCode // 的值计算出 // fruitNameLabel return ''; } } */ /* 一开始我如下写的, * 报错 data functions * should return an object: * data() { fruitCode: '' },*/ data() { return { fruitCode: '' } }, created() { debugger; this.initParams(); }, computed: { fruitNameLabel() { return this.fruitCode === 'apple' ? '苹果' : this.fruitCode === 'orange' ? '橘子' : this.fruitCode === 'banana' ? '香蕉' : '其他水果'; } }, methods: { initParams() { this.fruitCode = this.$f7route.params.fruitCode; } } } </script> <style scoped> </style>
2024-07-12
1.常量定义 PracticeConstant.java:为什么定义为接口:
/** * 分析:为什么定义常量, * 这里定义为接口, * 而不是普通Java类? * * 1.在Java8之前, * 接口里的常量,都是 * public static final * 的。这与我们期望的常量 * 定义是一致的。 * 2.接口不能被实例化, * 而普通Java类可以被实例化, * 访问接口的常量,直接通过 * 接口名.常量名 * 来访问,很方便。 * * 通过接口来定义常量 * 是一种很普遍的用法, * 很合适。 * * @author KongLC * */ public interface PracticeConstant { public static final String Dealer_Code_Yt_Learn = "Yt_Learn"; public static final String Dealer_pwd_Yt_Learn = "Yt_Learn_pwd"; }
2024-07-12
1.练习事例:经销商请求获取token接口
BookDealerAuthController.java:
/** * 书籍经销商获取token处理器 * * “@Controller”: * 声明SpringMVC控制器 * 方法默认返回一个字符串 * 表示的视图名 * “@ResponseBody”: * 写在Controller上, * 用于将控制器所有方法 * 返回的对象,通过适当的 * HttpMessageConverter * 转换为JSON或XML等格式, * 并写入到HTTP响应体中。 * 这意味着方法返回值不会被 * 视为视图名,而是直接作为 * HTTP响应的内容 * “@RestController”: * 相当于在控制器上同时加 * “@Contoller”和 * “@ResponseBody”, * 自动将声明该控制器所有方法 * 的返回值序列化为 * JSON或XML格式(而不是视图) * @author KongLC * */ @RestController @RequestMapping("bookDealerAuthController") public class BookDealerAuthController { /** * 获取token接口 * * 分析入参: * HttpServletRequest request * HttpServletResponse response * SpringMVC中Spring会自动 * 将HttpServletRequest对象 * 作为参数注入到控制器每个方法中 * request含有请求头、 * 请求参数等信息, * 方法体中可以从中获取信息; * 可以给response设置 * 返回状态码和信息 * * 分析返回值: * 1.ResponseEntity<?> * SpringMVC定义的 * 用于构建HTTP响应的类。 * 其相对于response * 更便于设置响应码及信息, * 是SpringMVC中 * 更推荐的返回格式。 * 2.<?>定义泛型 * 声明了该函数返回的 * ResponseEntity可以是 * 任何类型。提高了接口的 * 通用性。例如可以返回一个 * User对象,也可以返回一个 * String字符串。 */ @RequestMapping(params = "getToken") public ResponseEntity<?> getToken( HttpServletRequest request, HttpServletResponse response) { try { /** * 从request中读取 * 请求体,即一个json * 字符串。然后借助 * 工具类实现json字符串 * 转Java对象 */ StringBuffer sb = new StringBuffer(); BufferedReader reader = request.getReader(); // 定义为null怎么样? String line = null; while((line = reader.readLine()) != null) { sb.append(line); } String jsonStr = sb.toString(); BookDealerDTO dealerDto = JSONHelper.fromJsonToObject( jsonStr, BookDealerDTO.class); /** * 验证经销商信息, * 通过则生成token返回 * * jdk7环境, * 使用jjwt进行token生成 * * Jwts.builder() * 返回一个JwtBuilder * JwtBuilder有如下 * api可进行操作 * * JwtBuilder setSubject(String) * 设置主题 * * JwtBuilder setIssuedAt(Date) * 设置签发日期 * * JwtBuilder setExpiration(Date) * 设置过期时间 * 参数为过期的 * 那个时间点 * 这里设置为: * new Date( * System.currentTimeMillis() * + 1000 * 60 * 60 * 2) * 当前时间延后2小时 * * JwtBuilder signWith( * SignatureAlgorithm * paramSignatureAlgorithm, * String paramString) * 签名 * 参数:签名算法、密钥 * * * String compact() * 将token缩减, * 规范化 * * 函数返回时, * new ResponseEntity<>( * jwt, HttpStatus.OK); * <>构建泛型? * 是如下形式的简化写法, * 使代码更简明 * new ResponseEntiy<String>( * jwt, HttpStatus.OK); * 编译器会根据 * 第一个参数的类型 * (这里是String) * 来判定类型。 */ String dealerCode = dealerDto.getDealerCode(); String pwd = dealerDto.getPwd(); if (PracticeConstant.Dealer_Code_Yt_Learn .equals(dealerCode) && PracticeConstant.Dealer_pwd_Yt_Learn .equals(pwd)) { String jwt = Jwts.builder() .setSubject(dealerCode) .setIssuedAt(new Date()) .setExpiration( new Date( System.currentTimeMillis() + 1000 * 60 * 60 * 2)) .signWith( SignatureAlgorithm.HS512, PracticeConstant.Dealer_Secret_Key_Yt_Learn.getBytes()) .compact(); return new ResponseEntity<>( jwt, HttpStatus.OK); } else { return new ResponseEntity<>( "Authentication failed", HttpStatus.UNAUTHORIZED); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); return new ResponseEntity<>( "Authentication failed", HttpStatus.UNAUTHORIZED); } } }
BookDealerDTO.java:
/** * 经销商认证参数DTO * @author KongLC * */ public class BookDealerDTO { // 经销商账号 private String dealerCode; // 经销商密码 private String pwd; public String getDealerCode() { return dealerCode; } public void setDealerCode(String dealerCode) { this.dealerCode = dealerCode; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } }
PracticeConstant.java:
/** * 分析:为什么定义常量, * 这里定义为接口, * 而不是普通Java类? * * 1.在Java8之前, * 接口里的常量,都是 * public static final * 的。这与我们期望的常量 * 定义是一致的。 * 2.接口不能被实例化, * 而普通Java类可以被实例化, * 访问接口的常量,直接通过 * 接口名.常量名 * 来访问,很方便。 * * 通过接口来定义常量 * 是一种很普遍的用法, * 很合适。 * * @author KongLC * */ public interface PracticeConstant { public static final String Dealer_Code_Yt_Learn = "Yt_Learn_Code"; public static final String Dealer_pwd_Yt_Learn = "Yt_Learn_Pwd"; public static final String Dealer_Secret_Key_Yt_Learn = "Yt_Learn_Secret_Key"; }
拦截器设置白名单:
<value>bookDealerAuthController.do?getToken</value>
2024-07-13
今天代码暂未实际测试,可能有问题,下一步全弄完后测试
1.练习:经销商获取书籍库存接口:
BookInventoryController.java:
@RestController @RequestMapping("bookInventoryController") public class BookInventoryController { @RequestMapping(params = "getBookInventoryByName") public ResponseEntity<?> getBookInventoryByName(HttpServletRequest request) { /** * 一开始我右边的两种写法都不对: * List<BookInventoryDTO> inventories = new ArrayList<>; * List<BookInventoryDTO> inventories = new ArrayList(); * * 正确写法: * List<BookInventoryDTO> inventories = new ArrayList<>(); * * 分析: * new ArrayList<>(); * 是jdk7及以上引入的钻石操作符(diamond operator)。 * 当使用<>时,Java编译器会自动推断出ArrayList的泛型类型,基于左边的变量声明List<BookInventoryDTO>。 * 因此new ArrayList<>()在这里时合法的,并且编译器会将其视为new ArrayList<BookInventoryDTO>() * * new ArrayList<>; * 是不完整的,因为其缺少了一对括号()。在Java中,创建对象时,必须使用括号来调用类的构造器(即使是默认的无参构造)。 * * new ArrayList(); * 没有指定泛型类型。这意味着创建了一个ArrayList的原始类型实例。从Java5开始,推荐使用泛型来避免类型安全问题。 * 所以通常不推荐使用原始类型的集合。 */ List<BookInventoryDTO> inventories = new ArrayList<>(); inventories.add(new BookInventoryDTO("Java", "1-2-1", 10)); inventories.add(new BookInventoryDTO("Spring", "1-2-2", 5)); inventories.add(new BookInventoryDTO("VUE", "1-2-3", 6)); try { StringBuffer sb = new StringBuffer(); String line = null; BufferedReader reader = request.getReader(); while((line = reader.readLine()) != null) { sb.append(line); } String jsonStr = sb.toString(); BookInventoryDTO inParamDto = JSONHelper.fromJsonToObject(jsonStr, BookInventoryDTO.class); String searchName = inParamDto.getBookName(); BookInventoryDTO result = null; if (StringUtils.isNotEmpty(searchName)) { result = new BookInventoryDTO(searchName, "", 0); for(BookInventoryDTO inventory: inventories) { if (searchName.equals(inventory.getBookName())) { result = inventory;
// 一开始我记错了。正确的应该是: // break退出循环,继续执行循环后面的代码 // continue退出当次循环,继续执行下一次循环 continue; } } return new ResponseEntity<>(result, HttpStatus.OK); } else { throw new Exception("请求参数有误"); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); // bad request 400 return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST); } } }
书籍库存DTO:BookInventoryDTO.java:
/** * 书籍库存DTO * @author KongLC * */ public class BookInventoryDTO { private String bookName; private String isbn; private int bookInventoryQuantity; public BookInventoryDTO() { super(); } public BookInventoryDTO(String bookName, String isbn, int bookInventoryQuantity) { super(); this.bookName = bookName; this.isbn = isbn; this.bookInventoryQuantity = bookInventoryQuantity; } public String getBookName() { return bookName; } public void setBookName(String bookName) { this.bookName = bookName; } public String getIsbn() { return isbn; } public void setIsbn(String isbn) { this.isbn = isbn; } public int getBookInventoryQuantity() { return bookInventoryQuantity; } public void setBookInventoryQuantity(int bookInventoryQuantity) { this.bookInventoryQuantity = bookInventoryQuantity; } }
2024-07-14
1.经销商获取库存接口
昨天写的获取库存接口有误(break和continue)同时controller映射修改,重新列出如下:BookInventoryController.java:
@RestController @RequestMapping("/bookDealer/bookInventoryController") public class BookInventoryController { @RequestMapping(params = "getBookInventoryByName") public ResponseEntity<?> getBookInventoryByName(HttpServletRequest request) { /** * 一开始我右边的两种写法都不对: * List<BookInventoryDTO> inventories = new ArrayList<>; * List<BookInventoryDTO> inventories = new ArrayList(); * * 正确写法: * List<BookInventoryDTO> inventories = new ArrayList<>(); * * 分析: * new ArrayList<>(); * 是jdk7及以上引入的钻石操作符(diamond operator)。 * 当使用<>时,Java编译器会自动推断出ArrayList的泛型类型,基于左边的变量声明List<BookInventoryDTO>。 * 因此new ArrayList<>()在这里时合法的,并且编译器会将其视为new ArrayList<BookInventoryDTO>() * * new ArrayList<>; * 是不完整的,因为其缺少了一对括号()。在Java中,创建对象时,必须使用括号来调用类的构造器(即使是默认的无参构造)。 * * new ArrayList(); * 没有指定泛型类型。这意味着创建了一个ArrayList的原始类型实例。从Java5开始,推荐使用泛型来避免类型安全问题。 * 所以通常不推荐使用原始类型的集合。 */ List<BookInventoryDTO> inventories = new ArrayList<>(); inventories.add(new BookInventoryDTO("Java", "1-2-1", 10)); inventories.add(new BookInventoryDTO("Spring", "1-2-2", 5)); inventories.add(new BookInventoryDTO("VUE", "1-2-3", 6)); try { StringBuffer sb = new StringBuffer(); String line = null; BufferedReader reader = request.getReader(); while((line = reader.readLine()) != null) { sb.append(line); } String jsonStr = sb.toString(); BookInventoryDTO inParamDto = JSONHelper.fromJsonToObject(jsonStr, BookInventoryDTO.class); String searchName = inParamDto.getBookName(); BookInventoryDTO result = null; if (StringUtils.isNotEmpty(searchName)) { result = new BookInventoryDTO(searchName, "", 0); for(BookInventoryDTO inventory: inventories) { if (searchName.equals(inventory.getBookName())) { result = inventory; // 我一开始记错了。正确的应该是: // break退出循环,继续执行循环后面的代码 // continue退出当次循环,继续执行下一次循环 break; } } return new ResponseEntity<>(result, HttpStatus.OK); } else { throw new Exception("请求参数有误"); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); // bad request 400 return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST); } } }
同时把获取token接口里密钥由PracticeConstant.Dealer_Secret_Key_Yt_Learn改为PracticeConstant.Dealer_Secret_Key_Yt_Learn.getBytes():BookDealerAuthController.java:
/** * 书籍经销商获取token处理器 * * “@Controller”: * 声明SpringMVC控制器 * 方法默认返回一个字符串 * 表示的视图名 * “@ResponseBody”: * 写在Controller上, * 用于将控制器所有方法 * 返回的对象,通过适当的 * HttpMessageConverter * 转换为JSON或XML等格式, * 并写入到HTTP响应体中。 * 这意味着方法返回值不会被 * 视为视图名,而是直接作为 * HTTP响应的内容 * “@RestController”: * 相当于在控制器上同时加 * “@Contoller”和 * “@ResponseBody”, * 自动将声明该控制器所有方法 * 的返回值序列化为 * JSON或XML格式(而不是视图) * @author KongLC * */ @RestController @RequestMapping("bookDealerAuthController") public class BookDealerAuthController { /** * 获取token接口 * * 分析入参: * HttpServletRequest request * HttpServletResponse response * SpringMVC中Spring会自动 * 将HttpServletRequest对象 * 作为参数注入到控制器每个方法中 * request含有请求头、 * 请求参数等信息, * 方法体中可以从中获取信息; * 可以给response设置 * 返回状态码和信息 * * 分析返回值: * 1.ResponseEntity<?> * SpringMVC定义的 * 用于构建HTTP响应的类。 * 其相对于response * 更便于设置响应码及信息, * 是SpringMVC中 * 更推荐的返回格式。 * 2.<?>定义泛型 * 声明了该函数返回的 * ResponseEntity可以是 * 任何类型。提高了接口的 * 通用性。例如可以返回一个 * User对象,也可以返回一个 * String字符串。 */ @RequestMapping(params = "getToken") public ResponseEntity<?> getToken( HttpServletRequest request, HttpServletResponse response) { try { /** * 从request中读取 * 请求体,即一个json * 字符串。然后借助 * 工具类实现json字符串 * 转Java对象 */ StringBuffer sb = new StringBuffer(); BufferedReader reader = request.getReader(); // 定义为null怎么样? String line = null; while((line = reader.readLine()) != null) { sb.append(line); } String jsonStr = sb.toString(); BookDealerDTO dealerDto = JSONHelper.fromJsonToObject( jsonStr, BookDealerDTO.class); /** * 验证经销商信息, * 通过则生成token返回 * * jdk7环境, * 使用jjwt进行token生成 * * Jwts.builder() * 返回一个JwtBuilder * JwtBuilder有如下 * api可进行操作 * * JwtBuilder setSubject(String) * 设置主题 * * JwtBuilder setIssuedAt(Date) * 设置签发日期 * * JwtBuilder setExpiration(Date) * 设置过期时间 * 参数为过期的 * 那个时间点 * 这里设置为: * new Date( * System.currentTimeMillis() * + 1000 * 60 * 60 * 2) * 当前时间延后2小时 * * JwtBuilder signWith( * SignatureAlgorithm * paramSignatureAlgorithm, * String paramString) * 签名 * 参数:签名算法、密钥 * * * String compact() * 将token缩减, * 规范化 * * 函数返回时, * new ResponseEntity<>( * jwt, HttpStatus.OK); * <>构建泛型? * 是如下形式的简化写法, * 使代码更简明 * new ResponseEntiy<String>( * jwt, HttpStatus.OK); * 编译器会根据 * 第一个参数的类型 * (这里是String) * 来判定类型。 */ String dealerCode = dealerDto.getDealerCode(); String pwd = dealerDto.getPwd(); if (PracticeConstant.Dealer_Code_Yt_Learn .equals(dealerCode) && PracticeConstant.Dealer_pwd_Yt_Learn .equals(pwd)) { String jwt = Jwts.builder() .setSubject(dealerCode) .setIssuedAt(new Date()) .setExpiration( new Date( System.currentTimeMillis() + 1000 * 60 * 60 * 2)) .signWith( SignatureAlgorithm.HS512, PracticeConstant.Dealer_Secret_Key_Yt_Learn.getBytes()) .compact(); return new ResponseEntity<>( jwt, HttpStatus.OK); } else { return new ResponseEntity<>( "Authentication failed", HttpStatus.UNAUTHORIZED); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); return new ResponseEntity<>( "Authentication failed", HttpStatus.UNAUTHORIZED); } } }
经销商接口拦截器:BookDealerInterceptor.java:
/** * 经销商接口拦截器 * * 要实现拦截器,要继承HandlerInterceptor接口 * public interface HandlerInterceptor{} 说明是接口 * 实现接口 implements关键字 * @author KongLC * */ public class BookDealerInterceptor implements HandlerInterceptor { @Override public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3) throws Exception { // TODO Auto-generated method stub } @Override public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3) throws Exception { // TODO Auto-generated method stub } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // TODO Auto-generated method stub String token = request.getHeader("Authorization"); if (StringUtils.isEmpty(token) || !isTokenValid(token)) { // 一开始这里我忘记了:返回false前,要设置response状态,否则前端接收到还是200响应码, response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.getWriter().write("Invalid token"); return false; } else { return true; } } /** * 认证token * * Jwts的API: * JwtParser parser(); // 静态方法,创建JWT解析器的实例 * * JwtParser的API: * JwtParser setSigningKey(byte[] paramArrayOfbyte); // 设置签名key,入参:签名key * Jwt<Header, Claims> parseClaimsJwt(String paramString) // 将jwt解析成Claims * * Jwt<Header, Claims>的API: * Claims getBody(); // 获取Claims对象 * * Claims的API: * String getSubject(); // 获取subject字段 * * @param token * @return */ private boolean isTokenValid(String token) { try { Claims claims = // 静态方法,创建JWT解析器的实例 Jwts.parser() // 设置签名key,入参:签名key .setSigningKey(PracticeConstant.Dealer_Secret_Key_Yt_Learn.getBytes()) // 将jwt解析成Claims .parseClaimsJws(token) // 获取Claims对象 .getBody(); // 测试打断点claims是:{sub=Yt_Learn_Code, iat=1720957434, exp=1720964634} // 获取subject字段 String dealerCode = claims.getSubject(); if (PracticeConstant.Dealer_Code_Yt_Learn.equals(dealerCode)) { return true; } else { return false; } } catch (Exception e) { return false; } } }
拦截器配置:spring-mvc.xml:
对原拦截器设置白名单:
<!-- 经销商获取token接口 --> <value>bookDealerAuthController.do?getToken</value> <!-- 经销商业务接口 --> <value>bookDealer/bookInventoryController.do?getBookInventoryByName</value>
设置经销商接口拦截器:
<!-- 经销商请求接口拦截器 --> <mvc:interceptor> <mvc:mapping path="/bookDealer/**"/> <bean class="org.xxx.interceptors.BookDealerInterceptor" /> </mvc:interceptor>
2024-07-15
1.前端输出函数执行时间,同时学习异步函数。
业务页面:Business.vue:
<script> export default { name: "Business", mounted(){ this.practiceCountTime(); }, methods: { /** * 获取网页内容 * * 异步函数 * * async声明这是个异步函数 * * 使用fetch API异步获取网页内容,返回一个Promise对象 * 这个Promise在响应到达时被解决(resolve)。 * 我们通过await关键字等待这个Promise被解决,并将解决的结果(即响应对象)赋值给response变量。 * * 我们检查响应的ok属性来确定请求是否成功。如果请求没有成功(即状态码不是200~299),我们抛出一个错误。 * * 如果请求成功,我们使用response.text()方法将响应体转换为文本,并等待这个转换的Promise被解决。 * * 最终用try...catch来捕获并处理可能发生的任何错误,这是处理异步操作中可能发生的异常的一种常见方式。 * * await关键字只能在async函数内部使用 * * response.text()读取响应的内容,并将其转换为文本。也返回一个Promise对象。 */ async fetchPageContent(url) { try { // 使用fetch API异步获取网页内容,返回一个Promise对象 // 这个Promise在响应到达时被解决(resolve)。 // 我们通过await关键字等待这个Promise被解决,并将解决的结果(即响应对象)赋值给response变量。 let response = await fetch(url); // 我们检查响应的ok属性来确定请求是否成功。如果请求没有成功(即状态码不是200~299),我们抛出一个错误。 if (!response.ok) { debugger; console.log('HTTP error! Status: ${response.status}'); return; } // 如果请求成功,我们使用response.text()方法将响应体转换为文本,并等待这个转换的Promise被解决。 let content = await response.text(); console.log('content: ' + content); } catch(error) { // 最终用try...catch来捕获并处理可能发生的任何错误,这是处理异步操作中可能发生的异常的一种常见方式。 console.error('A problem occurs when getPageContent: ' + error); } }, /** * 练习输出函数执行时间 * * 一开始我的错误代码: * this.getPageContent(url).then( * let end = new Date(); * let spendTimeSecond = (end - begin) / 1000; * alert('spendTimeSecond: ' + spendTimeSecond + ' seconds'); * ); * 错误解析:js的.then()需要一个函数作为参数。而我直接在.then()后面写了语句,是不正确的。 * * 正确写法:可以给.then()传入一个箭头函数() => {} * * 同时.then()之后加上.catch(error => {})对可能出现的异常进行捕获处理 */ practiceCountTime() { // let url = 'https://www.baidu.com'; let url = 'http://localhost:8080'; let begin = new Date(); this.fetchPageContent(url).then(() => { let end = new Date(); let spendTimeSecond = (end - begin) / 1000; alert('spendTimeSecond: ' + spendTimeSecond + ' seconds'); }).catch(error => { console.log('error occured in practiceCountTime method: ' + error); }); } } } </script>
2024-07-16
1.关于数据库入库乱码问题分析:
前端用的framework7+vue,请求是axios,我就记住默认结论就行:
1.1使用某个库(例如axios)来发送请求,对于JSON类型的请求(这是最常见的),Content-Type会被(自动)设置为application/json,而JSON格式的数据默认使用UTF-8编码。
1.2大多数现代Web服务器和框架都期望接收UTF-8编码的数据。
1.3后端练习:
String originalStr = "tom测试"; // 模拟前端传来ISO8859-1编码的字符串 String iso88591Str = new String(originalStr.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1); // 模拟前端传来UTF-8编码的字符串 String utfStr = new String(originalStr.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8); // 模拟后端将iso编码格式的字符串,以utf8解码,会导致乱码 String errorStrParsingIsoToUtf = new String(iso88591Str.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8); // 模拟后端将utf8编码格式的字符串,以utf8解码,不会导致乱码 String correctStrParsingUtfToUtf = new String(utfStr.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8); // 模拟后端将iso编码格式的字符串,以iso解码,不会导致乱码 String errorStrParsingIsoToIso = new String(iso88591Str.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
2024-07-17~07-19
1.练习将多表级联查询改为单表获取,相关的数据组装;同步练习展开语法(...三个英文句号)。
设计示例:实体:蔬菜种植标准检查项、蔬菜种植细节地点、蔬菜种植大型基地、蔬菜种植检查(本地)主记录、蔬菜种植(本地)检查结果记录
<script> /** * 练习:(模拟)对多个单表查询的结果,组装数据。 */ searchSingleTableThenCombine() { /** * 如下为简化示例,给出5个数组,模拟都是通过单表查询本地库得到的结果 */ // 标准检查项 let standardCheckItemArr = [ { 'id': '1', 'largeBaseId': '1', 'detailLocaitonId': '1', /* 检查项 */ 'checkContent': '植株高度' }, { 'id': '2', 'largeBaseId': '1', 'detailLocaitonId': '1', 'checkContent': '叶片颜色是否偏黄' }, { 'id': '3', 'largeBaseId': '1', 'detailLocaitonId': '1', 'checkContent': '植株是否倒伏' } ]; // 细节种植地点(是大型基地的下级) let detailLocationArr = [ { 'id': '1', 'largeBaseId': '1', 'name': '姜家疃1号芹菜农田' } ]; // 大型基地 let largeBaseArr = [ { 'id': '1', 'name': '黄务基地' } ]; // (主)检查记录 let checkRecordMainArr = [ { 'id': '1', 'largeBaseId': '1', // 检查人 'checkUserName': 'tom', // 检查时间 'checkDate': '2024-07-17 08:00:00' } ]; // 具体检查(项)结果记录 let checkItemRecordArr = [ { 'id': '1', 'standardCheckItemId': '1', // 加上largeBaseId、detailLocaitonId这俩字段,有点冗余,不过便于使用中查询。 'largeBaseId': '1', 'detailLocaitonId': '1', // 这里我一开始写错了,这里无需再来一份“检查内容checkContent”了,冗余。直接关联标准检查项表就可以取到。 // 'checkContent': '植株高度', 'isNormal': 'N', // 检查结果描述 'checkResultDesc': '高度过低', // 具体缺陷描述 'specificDefectDesc': '高度低于正常标准', 'remark': '需多浇水' }, { 'id': '2', 'standardCheckItemId': '2', 'largeBaseId': '1', 'detailLocaitonId': '1', 'isNormal': 'N', 'checkResultDesc': '叶片发黄', 'specificDefectDesc': '叶片发黄', 'remark': '需补充解决叶片发黄相关肥料' }, { 'id': '3', 'standardCheckItemId': '3', 'largeBaseId': '1', 'detailLocaitonId': '1', 'isNormal': 'N', 'checkResultDesc': '存在倒伏现象', 'specificDefectDesc': '存在倒伏现象', 'remark': '需补充抗倒伏相关肥料' } ]; // 最终期待组装完成的检查项列表 let resultArr = []; /** * 以下是用于组装的中间过程变量 */ // 细节种植地点(是大型基地的下级) let detailLocation = {}; // 大型基地 let largeBase = {}; // (主)检查记录 let checkRecordMain = {}; // 具体检查(项)结果记录 let checkItemRecord = {}; // js遍历数组 for (let item of arr) {} for (let standardCheckItem of standardCheckItemArr) { detailLocation[standardCheckItem.id] = { 'detailLocationId': detailLocationArr[0].id, 'detailLocationName': detailLocationArr[0].name } largeBase[standardCheckItem.id] = { 'largeBaseId': largeBaseArr[0].id, 'largeBaseName': largeBaseArr[0].name } checkRecordMain[standardCheckItem.id] = { 'checkRecordMainId': checkRecordMainArr[0].id, 'checkUserName': checkRecordMainArr[0].checkUserName, 'checkDate': checkRecordMainArr[0].checkDate } } // 用双层for循环来模拟查本地库的过程 for (let i = 0; i < checkItemRecordArr.length; i++) { for (let j = 0; j < standardCheckItemArr.length; j++) { if (checkItemRecordArr[i].standardCheckItemId === standardCheckItemArr[j].id) { checkItemRecord[standardCheckItemArr[j].id] = { 'checkItemRecordId': checkItemRecordArr[i].id, 'checkResultDesc': checkItemRecordArr[i].checkResultDesc, 'specificDefectDesc': checkItemRecordArr[i].specificDefectDesc, 'remark': checkItemRecordArr[i].remark } } } } /** * 1.先通过map进行映射:对standardCheckItemArr的每个item,映射成(后面箭头函数返回的)新对象 * 2.这里通过...展开。 * ...将每个对象(的属性)展开, * 然后由于{}的存在,将它们合并为一个对象,最终返回 * 这里必须是item => ({}),这是隐式返回 * 如果是item => {},即不写()的话,是显式返回,而我又没写return,是不会有正常效果的。(编译就会报错) */ resultArr = standardCheckItemArr.map(item => ({ // 一开始我这里写错了:写的是item。其实应该是...item // item是数组的一个元素,应该把其属性也给展开 ...item, ...detailLocation[item.id], ...largeBase[item.id], ...checkRecordMain[item.id], ...checkItemRecord[item.id], ...detailLocation[item.id] })) /** * 最终输出结果: * * { "id": "1", "largeBaseId": "1", "detailLocaitonId": "1", "checkContent": "植株高度", "detailLocationId": "1", "detailLocationName": "姜家疃1号芹菜农田", "largeBaseName": "黄务基地", "checkRecordMainId": "1", "checkUserName": "tom", "checkDate": "2024-07-17 08:00:00", "checkItemRecordId": "1", "checkResultDesc": "高度过低", "specificDefectDesc": "高度低于正常标准", "remark": "需多浇水" } * * { "id": "2", "largeBaseId": "1", "detailLocaitonId": "1", "checkContent": "叶片颜色是否偏黄", "detailLocationId": "1", "detailLocationName": "姜家疃1号芹菜农田", "largeBaseName": "黄务基地", "checkRecordMainId": "1", "checkUserName": "tom", "checkDate": "2024-07-17 08:00:00", "checkItemRecordId": "2", "checkResultDesc": "叶片发黄", "specificDefectDesc": "叶片发黄", "remark": "需补充解决叶片发黄相关肥料" } * * { "id": "3", "largeBaseId": "1", "detailLocaitonId": "1", "checkContent": "植株是否倒伏", "detailLocationId": "1", "detailLocationName": "姜家疃1号芹菜农田", "largeBaseName": "黄务基地", "checkRecordMainId": "1", "checkUserName": "tom", "checkDate": "2024-07-17 08:00:00", "checkItemRecordId": "3", "checkResultDesc": "存在倒伏现象", "specificDefectDesc": "存在倒伏现象", "remark": "需补充抗倒伏相关肥料" } */ for (let result of resultArr) { console.log(result); } } </script>
复盘工作2024-07-20
1.js...(三个英文点)展开语法。spread [spred] syntax [ˈsɪnˌtæks]
可以展开数组、对象。
<script> methods: { practiceSpreadSyntax() { // 展开数组 let arr1 = ['1', '2']; let arr2 = ['3', '4']; let arr3 = [...arr1, ...arr2, '5']; // 结果:输出:["1","2","3","4","5"] console.log(arr3); // 展开对象 let apple = { 'color': 'red', 'weight': '0.5' } let info = { 'locationProvince': 'ShanDong', 'locationCity': 'YanTai' } let type = { 'typeName': 'HongFuShi' } let appleFullInfo = {...apple, ...info, ...type}; /** * 结果:输出: * { "color": "red", "weight": "0.5", "locationProvince": "ShanDong", "locationCity": "YanTai", "typeName": "HongFuShi" } */ console.log(appleFullInfo); } } </script>
复盘工作2024-07-21
1.v-if、v-else-if、v-else一起使用依次控制各元素是否显示
<template> <f7-page> <button @click="changeWorkflowStageShowDiffDiv('design')">展示内容为“设计”的div</button> <button @click="changeWorkflowStageShowDiffDiv('buildBasePlatform')">展示内容为“整体搭建”的div</button> <button @click="changeWorkflowStageShowDiffDiv('buildFeature')">展示内容为“开发功能”的div</button> <!-- 当workflowStage等于design时,展示该div--> <div v-if="workflowStage === 'design'"> <div class="practive-v-if-else-if">设计</div> </div> <!-- 当workflowStage不等于design,且等于buildBasePlatform时,展示该div--> <div v-else-if="workflowStage === 'buildBasePlatform'"> <div class="practive-v-if-else-if">整体搭建</div> </div> <!-- 当workflowStage不等于design,且不等于buildBasePlatform时,展示该div--> <div v-else> <div class="practive-v-if-else-if">开发功能</div> </div> </f7-page> </template> <script> export default { name: "Business", data() { return{ workflowStage: 'design', // 流程阶段 } }, methods: { // 改变workflowStage的值进而改变展示哪个div changeWorkflowStageShowDiffDiv(stage) { this.workflowStage = stage; } } } </script> <style lang="scss" scoped> .practive-v-if-else-if{ color: red; } </style>
复盘工作2024-07-23
1.Java后端获取时间戳:
// System.currentTimeMillis()获取时间戳,返回自1970年1月1日 00:00:00 UTC以来的毫秒数(即时间戳) long currentTimeMillis = System.currentTimeMillis(); // 输出1721709183848,即时间:2024-07-23 12:33:03 System.out.println(currentTimeMillis);
2.js前端获取时间戳:
<script> export default { mounted(){ this.printTimestamp(); }, methods: { /** * js获取时间戳:new Date().getTime() * 返回自1970年1月1号00:00:00 UTC(协调世界时)至当前时间的毫秒数(即时间戳) */ printTimestamp() { let timestamp = new Date().getTime(); // 输出######################### 1721711461385 ############################## // 即2024-07-23 13:11:01 console.log("######################### " + timestamp + " ##############################"); } } } </script>
复盘工作2024-07-24
1.js里,动态拼接字段,然后获取对象的该字段,用[]
<template> <f7-page> <button @click="setObjectPropertyByParam('Front')">改变书前侧照片附件id</button> <button @click="setObjectPropertyByParam('Back')">改变书后侧照片附件id</button> </f7-page> </template> <script> export default { methods: { /** * 动态拼接字段,然后获取对象的该字段,用[] * * 这里要注意:对象属性名,经测试发现,是严格区分大小写的 */ setObjectPropertyByParam(position) { const _self = this; _self.bookDetail['book' + position + 'PhotoAttachId'] = null; /** * 直接点击“改变书前侧照片附件id”后输出: * { "bookFrontPhotoAttachId": null, "bookLeftPhotoAttachId": "bookLeftPhotoAttachId456", "bookRightPhotoAttachId": "bookRightPhotoAttachId665", "bookBackPhotoAttachId": "bookBackPhotoAttachId928" } */ console.log(_self.bookDetail); } } } </script>