复盘工作-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>

 

posted on 2024-07-02 07:56  平凡力量  阅读(14)  评论(0编辑  收藏  举报