【wiki知识库】06.文档管理页面的添加--前端Vue部分

news2024/11/25 22:46:17

  📝个人主页:哈__

期待您的关注 

目录

一、🔥今日目标

 二、🐻前端Vue模块的改造

BUG修改

1.wangeditor无法展示问题

2.弹窗无法正常关闭问题

2.1 添加admin-doc.vue

2.1.1 点击admin-ebook中的路由跳转到admin-doc

 2.2.2 进入到admin-doc中调用初始化查询方法

2.2.3 文档编辑中的一些细节

2.2.4 文档的删除

2.2.5 admin-doc.vue全部代码

2.2 添加DocView.vue

2.3 添加新的路由规则


一、🔥今日目标

上一次带大家把前端的分类管理模块做了出来,我们可以实现网站的分类功能,以及分类的树形结构展示功能。到此为止已经带大家做了电子书管理模块、分类模块,那么只要再把文档管理模块也做出来,我们就可以初步实现电子书这整个一套流程了。我们可以编辑添加电子书,实现分类,并且真正的往电子书的文档模块中添加内容。【wiki知识库】05.分类管理实现--前端Vue模块-CSDN博客

我们今天就要实现下方图片中箭头指向的功能

 除此之外,还有主页的展示功能。

 二、🐻前端Vue模块的改造

在此之前我要要说一件事情,我在做这个模块的时候出现了问题,一个是我们之后要使用的文本编辑器wangeditor无法正常展示,还有一个是弹窗无法关闭的问题。这里我把解决方法告诉大家。

BUG修改

1.wangeditor无法展示问题

出现这个问题可能是版本的问题,进入到我们的web目录中,打开终端窗口然后输入下方指令。重新安装wangeditor。

npm i wangeditor@4.6.3 --save

2.弹窗无法正常关闭问题

这个问题是wangeditor和vue版本兼容的问题。我们需要修改一下package.json。将vue版本改成下方图中所示,注意前边的符号。


2.1 添加admin-doc.vue

2.1.1 点击admin-ebook中的路由跳转到admin-doc

还记得我当初在admin-ebook.vue中写的一个router吗?在我们点击文档管理跳转到对应的组件的时候,我们是有传一个ebookId进来的,我们这里使用的是路由传参。


 2.2.2 进入到admin-doc中调用初始化查询方法

进入到这个页面呢调用了两个方法,一个是editor.create(),用于加载我们的文本编辑器,另外一个方法调用的是handleQuery()方法,向后端发送查询请求。

onMounted(() => {
        editor.create();
        handleQuery();
      });

 这个方法进入的时候,修改了一个变量loading,我们在进行信息查询的时候会给用户一个反馈,告诉用户稍等,我们修改为true之后就会有页面数据加载的效果。

level1变量我们之前也说过,用于保存树形结构的数据。之后呢就会发送一个ajax请求,等我们的数据返回来之后就要把loading改为false了。

至于下边的treeSelectData,在我们修改一个文档的父文档的时候,如果我们要把这个文档作为根文档,也就是一级文档,我们需要把这个文档的父文档设置为无,但是我们的level中存储的是数据库中查出来的数据,没有无这个选项,所以我们新加一个变量来存储level1的结果和无,这样不会影响我们查出来的数据。

const handleQuery = () => {
        loading.value = true;
        // 如果不清空现有数据,则编辑保存重新加载数据后,再点编辑,则列表显示的还是编辑前的数据
        level1.value = [];
        axios.get("/doc/all/" + route.query.ebookId).then((response) => {
          loading.value = false;
          const data = response.data;
          if (data.success) {
            docs.value = data.content;
            console.log("原始数组:", docs.value);

            level1.value = [];
            level1.value = Tool.array2Tree(docs.value, 0);
            console.log("树形结构:", level1);

            // 父文档下拉框初始化,相当于点击新增
            treeSelectData.value = Tool.copy(level1.value) || [];
            // 为选择树添加一个"无"
            treeSelectData.value.unshift({id: 0, name: '无'});
          } else {
            message.error(data.message);
          }
        });
      };

2.2.3 文档编辑中的一些细节

在我们进行文档编辑的时候,我们不可能把该文档的父文档改为自己,或者改为它的子文档,这是一个循环错误。所以我们在修改一个文档的时候要把这个文档的子文档和自己设置为不可选中。

 来看看这个方法,我们把树形结构的数据,还有我们要编辑的文档的id传进来,首先进行for循环去找到这个结点,然后我们把这个节点设置为不可见,然后我们去遍历这个节点的子节点,递归调用。

/**
       * 将某节点及其子孙节点全部置为disabled
       */
      const setDisable = (treeSelectData: any, id: any) => {
        // console.log(treeSelectData, id);
        // 遍历数组,即遍历某一层节点
        for (let i = 0; i < treeSelectData.length; i++) {
          const node = treeSelectData[i];
          if (node.id === id) {
            // 如果当前节点就是目标节点
            console.log("disabled", node);
            // 将目标节点设置为disabled
            node.disabled = true;

            // 遍历所有子节点,将所有子节点全部都加上disabled
            const children = node.children;
            if (Tool.isNotEmpty(children)) {
              for (let j = 0; j < children.length; j++) {
                setDisable(children, children[j].id)
              }
            }
          } else {
            // 如果当前节点不是目标节点,则到其子节点再找找看。
            const children = node.children;
            if (Tool.isNotEmpty(children)) {
              setDisable(children, id);
            }
          }
        }
      };

2.2.4 文档的删除

文档的删除并不只是该文档简单删除就完了,这个文档删掉之后,这个文档的所有子文档都要删除。我们之前分类管理模块也处理过这样的删除,但是我们是后端处理的删除逻辑,后端处理起来呢比较麻烦,这里我们可以使用前端处理一下。既然要删除子分支,我们就把这个要删除的文档的子文档的id都查出来一起传到后端。

/**
       * 查找整根树枝
       */
      const getDeleteIds = (treeSelectData: any, id: any) => {
        // console.log(treeSelectData, id);
        // 遍历数组,即遍历某一层节点
        for (let i = 0; i < treeSelectData.length; i++) {
          const node = treeSelectData[i];
          if (node.id === id) {
            // 如果当前节点就是目标节点
            console.log("delete", node);
            // 将目标ID放入结果集ids
            // node.disabled = true;
            deleteIds.push(id);
            deleteNames.push(node.name);

            // 遍历所有子节点
            const children = node.children;
            if (Tool.isNotEmpty(children)) {
              for (let j = 0; j < children.length; j++) {
                getDeleteIds(children, children[j].id)
              }
            }
          } else {
            // 如果当前节点不是目标节点,则到其子节点再找找看。
            const children = node.children;
            if (Tool.isNotEmpty(children)) {
              getDeleteIds(children, id);
            }
          }
        }
      };

2.2.5 admin-doc.vue全部代码

<template>
  <a-layout>
    <a-layout-content
      :style="{ background: '#fff', padding: '24px', margin: 0, minHeight: '280px' }"
    >
      <a-row :gutter="24">
        <a-col :span="8">
          <p>
            <a-form layout="inline" :model="param">
              <a-form-item>
                <a-button type="primary" @click="handleQuery()">
                  查询
                </a-button>
              </a-form-item>
              <a-form-item>
                <a-button type="primary" @click="add()">
                  新增
                </a-button>
              </a-form-item>
            </a-form>
          </p>
          <a-table
            v-if="level1.length > 0"
            :columns="columns"
            :row-key="record => record.id"
            :data-source="level1"
            :loading="loading"
            :pagination="false"
            size="small"
            :defaultExpandAllRows="true"
          >
            <template #name="{ text, record }">
              {{record.sort}} {{text}}
            </template>
            <template v-slot:action="{ text, record }">
              <a-space size="small">
                <a-button type="primary" @click="edit(record)" size="small">
                  编辑
                </a-button>
                <a-popconfirm
                  title="删除后不可恢复,确认删除?"
                  ok-text="是"
                  cancel-text="否"
                  @confirm="handleDelete(record.id)"
                >
                  <a-button type="danger" size="small">
                    删除
                  </a-button>
                </a-popconfirm>
              </a-space>
            </template>
          </a-table>
        </a-col>
        <a-col :span="16">
          <p>
            <a-form layout="inline" :model="param">
              <a-form-item>
                <a-button type="primary" @click="handleSave()">
                  保存
                </a-button>
              </a-form-item>
            </a-form>
          </p>
          <a-form :model="doc" layout="vertical">
            <a-form-item>
              <a-input v-model:value="doc.name" placeholder="名称"/>
            </a-form-item>
            <a-form-item>
              <a-tree-select
                v-model:value="doc.parent"
                style="width: 100%"
                :dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
                :tree-data="treeSelectData"
                placeholder="请选择父文档"
                tree-default-expand-all
                :replaceFields="{title: 'name', key: 'id', value: 'id'}"
              >
              </a-tree-select>
            </a-form-item>
            <a-form-item>
              <a-input v-model:value="doc.sort" placeholder="顺序"/>
            </a-form-item>
            <a-form-item>
              <a-button type="primary" @click="handlePreviewContent()">
                <EyeOutlined /> 内容预览
              </a-button>
            </a-form-item>
            <a-form-item>
              <div id="content"></div>
            </a-form-item>
          </a-form>
        </a-col>
      </a-row>

      <a-drawer width="900" placement="right" :closable="false" :visible="drawerVisible" @close="onDrawerClose">
        <div class="wangeditor" :innerHTML="previewHtml"></div>
      </a-drawer>

    </a-layout-content>
  </a-layout>

</template>

<script lang="ts">
  import { defineComponent, onMounted, ref, createVNode } from 'vue';
  import axios from 'axios';
  import {message, Modal} from 'ant-design-vue';
  import {Tool} from "@/util/tool";
  import {useRoute} from "vue-router";
  import ExclamationCircleOutlined from "@ant-design/icons-vue/ExclamationCircleOutlined";
  import E from 'wangeditor'

  export default defineComponent({
    name: 'AdminDoc',
    setup() {
      const route = useRoute();
      console.log("路由:", route);
      console.log("route.path:", route.path);
      console.log("route.query:", route.query);
      console.log("route.param:", route.params);
      console.log("route.fullPath:", route.fullPath);
      console.log("route.name:", route.name);
      console.log("route.meta:", route.meta);
      const param = ref();
      param.value = {};
      const docs = ref();
      const loading = ref(false);
      // 因为树选择组件的属性状态,会随当前编辑的节点而变化,所以单独声明一个响应式变量
      const treeSelectData = ref();
      treeSelectData.value = [];

      const columns = [
        {
          title: '名称',
          dataIndex: 'name',
          slots: { customRender: 'name' }
        },
        {
          title: 'Action',
          key: 'action',
          slots: { customRender: 'action' }
        }
      ];

     
      const level1 = ref(); // 一级文档树,children属性就是二级文档
      level1.value = [];

      /**
       * 数据查询
       **/
      const handleQuery = () => {
        loading.value = true;
        // 如果不清空现有数据,则编辑保存重新加载数据后,再点编辑,则列表显示的还是编辑前的数据
        level1.value = [];
        axios.get("/doc/all/" + route.query.ebookId).then((response) => {
          loading.value = false;
          const data = response.data;
          if (data.success) {
            docs.value = data.content;
            console.log("原始数组:", docs.value);

            level1.value = [];
            level1.value = Tool.array2Tree(docs.value, 0);
            console.log("树形结构:", level1);

            // 父文档下拉框初始化,相当于点击新增
            treeSelectData.value = Tool.copy(level1.value) || [];
            // 为选择树添加一个"无"
            treeSelectData.value.unshift({id: 0, name: '无'});
          } else {
            message.error(data.message);
          }
        });
      };

      // -------- 表单 ---------
      const doc = ref();
      doc.value = {
        ebookId: route.query.ebookId
      };
      const modalVisible = ref(false);
      const modalLoading = ref(false);
      const editor = new E('#content');
      editor.config.zIndex = 0;
      // 显示上传图片按钮,转成Base64存储,同时也支持拖拽图片
     
      editor.config.uploadImgShowBase64 = true;

      const handleSave = () => {
        modalLoading.value = true;
        doc.value.content = editor.txt.html();
        axios.post("/doc/save", doc.value).then((response) => {
          modalLoading.value = false;
          const data = response.data; // data = commonResp
          if (data.success) {
            // modalVisible.value = false;
            message.success("保存成功!");

            // 重新加载列表
            handleQuery();
          } else {
            message.error(data.message);
          }
        });
      };

      /**
       * 将某节点及其子孙节点全部置为disabled
       */
      const setDisable = (treeSelectData: any, id: any) => {
        // console.log(treeSelectData, id);
        // 遍历数组,即遍历某一层节点
        for (let i = 0; i < treeSelectData.length; i++) {
          const node = treeSelectData[i];
          if (node.id === id) {
            // 如果当前节点就是目标节点
            console.log("disabled", node);
            // 将目标节点设置为disabled
            node.disabled = true;

            // 遍历所有子节点,将所有子节点全部都加上disabled
            const children = node.children;
            if (Tool.isNotEmpty(children)) {
              for (let j = 0; j < children.length; j++) {
                setDisable(children, children[j].id)
              }
            }
          } else {
            // 如果当前节点不是目标节点,则到其子节点再找找看。
            const children = node.children;
            if (Tool.isNotEmpty(children)) {
              setDisable(children, id);
            }
          }
        }
      };

      const deleteIds: Array<string> = [];
      const deleteNames: Array<string> = [];
      /**
       * 查找整根树枝
       */
      const getDeleteIds = (treeSelectData: any, id: any) => {
        // console.log(treeSelectData, id);
        // 遍历数组,即遍历某一层节点
        for (let i = 0; i < treeSelectData.length; i++) {
          const node = treeSelectData[i];
          if (node.id === id) {
            // 如果当前节点就是目标节点
            console.log("delete", node);
            // 将目标ID放入结果集ids
            // node.disabled = true;
            deleteIds.push(id);
            deleteNames.push(node.name);

            // 遍历所有子节点
            const children = node.children;
            if (Tool.isNotEmpty(children)) {
              for (let j = 0; j < children.length; j++) {
                getDeleteIds(children, children[j].id)
              }
            }
          } else {
            // 如果当前节点不是目标节点,则到其子节点再找找看。
            const children = node.children;
            if (Tool.isNotEmpty(children)) {
              getDeleteIds(children, id);
            }
          }
        }
      };

      /**
       * 内容查询
       **/
      const handleQueryContent = () => {
        axios.get("/doc/find-content/" + doc.value.id).then((response) => {
          const data = response.data;
          if (data.success) {
            editor.txt.html(data.content)
          } else {
            message.error(data.message);
          }
        });
      };

      /**
       * 编辑
       */
      const edit = (record: any) => {
        // 清空富文本框
        editor.txt.html("");
        modalVisible.value = true;
        doc.value = Tool.copy(record);
        handleQueryContent();

        // 不能选择当前节点及其所有子孙节点,作为父节点,会使树断开
        treeSelectData.value = Tool.copy(level1.value);
        setDisable(treeSelectData.value, record.id);

        // 为选择树添加一个"无"
        treeSelectData.value.unshift({id: 0, name: '无'});
      };

      /**
       * 新增
       */
      const add = () => {
        // 清空富文本框
        editor.txt.html("");
        modalVisible.value = true;
        doc.value = {
          ebookId: route.query.ebookId
        };

        treeSelectData.value = Tool.copy(level1.value) || [];

        // 为选择树添加一个"无"
        treeSelectData.value.unshift({id: 0, name: '无'});
      };

      const handleDelete = (id: number) => {
        // console.log(level1, level1.value, id)
        // 清空数组,否则多次删除时,数组会一直增加
        deleteIds.length = 0;
        deleteNames.length = 0;
        getDeleteIds(level1.value, id);
        Modal.confirm({
          title: '重要提醒',
          icon: createVNode(ExclamationCircleOutlined),
          content: '将删除:【' + deleteNames.join(",") + "】删除后不可恢复,确认删除?",
          onOk() {
            // console.log(ids)
            axios.delete("/doc/delete/" + deleteIds.join(",")).then((response) => {
              const data = response.data; // data = commonResp
              if (data.success) {
                // 重新加载列表
                handleQuery();
              } else {
                message.error(data.message);
              }
            });
          },
        });
      };

      // ----------------富文本预览--------------
      const drawerVisible = ref(false);
      const previewHtml = ref();
      const handlePreviewContent = () => {
        const html = editor.txt.html();
        previewHtml.value = html;
        drawerVisible.value = true;
      };
      const onDrawerClose = () => {
        drawerVisible.value = false;
      };

      onMounted(() => {
        editor.create();
        handleQuery();
      });

      return {
        param,
        // docs,
        level1,
        columns,
        loading,
        handleQuery,

        edit,
        add,

        doc,
        modalVisible,
        modalLoading,
        handleSave,

        handleDelete,

        treeSelectData,

        drawerVisible,
        previewHtml,
        handlePreviewContent,
        onDrawerClose,
      }
    }
  });
</script>

<style scoped>
  img {
    width: 50px;
    height: 50px;
  }
</style>

2.2 添加DocView.vue

这个组件呢就是为了在主页展示文档数据。但是其中的一些功能后端还未实现。

<template>
  <a-layout>
    <a-layout-content :style="{ background: '#fff', padding: '24px', margin: 0, minHeight: '280px' }">
      <h3 v-if="level1.length === 0">对不起,找不到相关文档!</h3>
      <a-row>
        <a-col :span="6">
          <a-tree
            v-if="level1.length > 0"
            :tree-data="level1"
            @select="onSelect"
            :replaceFields="{title: 'name', key: 'id', value: 'id'}"
            :defaultExpandAll="true"
            :defaultSelectedKeys="defaultSelectedKeys"
          >
          </a-tree>
        </a-col>
        <a-col :span="18">
          <div>
            <h2>{{doc.name}}</h2>
            <div>
              <span>阅读数:{{doc.viewCount}}</span> &nbsp; &nbsp;
              <span>点赞数:{{doc.voteCount}}</span>
            </div>
            <a-divider style="height: 2px; background-color: #9999cc"/>
          </div>
          <div class="wangeditor" :innerHTML="html"></div>
          <div class="vote-div">
            <a-button type="primary" shape="round" :size="'large'" @click="vote">
              <template #icon><LikeOutlined /> &nbsp;点赞:{{doc.voteCount}} </template>
            </a-button>
          </div>
        </a-col>
      </a-row>
    </a-layout-content>
  </a-layout>
</template>

<script lang="ts">
  import { defineComponent, onMounted, ref, createVNode } from 'vue';
  import axios from 'axios';
  import {message} from 'ant-design-vue';
  import {Tool} from "@/util/tool";
  import {useRoute} from "vue-router";

  export default defineComponent({
    name: 'Doc',
    setup() {
      const route = useRoute();
      const docs = ref();
      const html = ref();
      const defaultSelectedKeys = ref();
      defaultSelectedKeys.value = [];
      // 当前选中的文档
      const doc = ref();
      doc.value = {};

     
      const level1 = ref(); // 一级文档树,children属性就是二级文档
      level1.value = [];

      /**
       * 内容查询
       **/
      const handleQueryContent = (id: number) => {
        axios.get("/doc/find-content/" + id).then((response) => {
          const data = response.data;
          if (data.success) {
            html.value = data.content;
          } else {
            message.error(data.message);
          }
        });
      };

      /**
       * 数据查询
       **/
      const handleQuery = () => {
        axios.get("/doc/all/" + route.query.ebookId).then((response) => {
          const data = response.data;
          if (data.success) {
            docs.value = data.content;

            level1.value = [];
            level1.value = Tool.array2Tree(docs.value, 0);

            if (Tool.isNotEmpty(level1)) {
              defaultSelectedKeys.value = [level1.value[0].id];
              handleQueryContent(level1.value[0].id);
              // 初始显示文档信息
              doc.value = level1.value[0];
            }
          } else {
            message.error(data.message);
          }
        });
      };

      const onSelect = (selectedKeys: any, info: any) => {
        console.log('selected', selectedKeys, info);
        if (Tool.isNotEmpty(selectedKeys)) {
          // 选中某一节点时,加载该节点的文档信息
          doc.value = info.selectedNodes[0].props;
          // 加载内容
          handleQueryContent(selectedKeys[0]);
        }
      };

      // 点赞
      const vote = () => {
        axios.get('/doc/vote/' + doc.value.id).then((response) => {
          const data = response.data;
          if (data.success) {
            doc.value.voteCount++;
          } else {
            message.error(data.message);
          }
        });
      };

      onMounted(() => {
        handleQuery();
      });

      return {
        level1,
        html,
        onSelect,
        defaultSelectedKeys,
        doc,
        vote
      }
    }
  });
</script>

<style>
  /* table 样式 */
  .wangeditor table {
    border-top: 1px solid #ccc;
    border-left: 1px solid #ccc;
  }
  .wangeditor table td,
  .wangeditor table th {
    border-bottom: 1px solid #ccc;
    border-right: 1px solid #ccc;
    padding: 3px 5px;
  }
  .wangeditor table th {
    border-bottom: 2px solid #ccc;
    text-align: center;
  }

  /* blockquote 样式 */
  .wangeditor blockquote {
    display: block;
    border-left: 8px solid #d0e5f2;
    padding: 5px 10px;
    margin: 10px 0;
    line-height: 1.4;
    font-size: 100%;
    background-color: #f1f1f1;
  }

  /* code 样式 */
  .wangeditor code {
    display: inline-block;
    *display: inline;
    *zoom: 1;
    background-color: #f1f1f1;
    border-radius: 3px;
    padding: 3px 5px;
    margin: 0 3px;
  }
  .wangeditor pre code {
    display: block;
  }

  /* ul ol 样式 */
  .wangeditor ul, ol {
    margin: 10px 0 10px 20px;
  }

  /* 和antdv p冲突,覆盖掉 */
  .wangeditor blockquote p {
    font-family:"YouYuan";
    margin: 20px 10px !important;
    font-size: 16px !important;
    font-weight:600;
  }

  /* 点赞 */
  .vote-div {
    padding: 15px;
    text-align: center;
  }

  /* 图片自适应 */
  .wangeditor img {
    max-width: 100%;
    height: auto;
  }

  /* 视频自适应 */
  .wangeditor iframe {
    width: 100%;
    height: 400px;
  }
</style>

2.3 添加新的路由规则

在router下的index.js中新增两个路由规则。

 {
    path: '/doc',
    name: 'doc',
    component:DocView
  },
 {
    path: '/admin/doc',
    name: 'AdminDoc',
    component: AdminDoc
  }

后续我会把后端的代码补上的。

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

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

相关文章

【DrissionPage】Linux上如何将https改为http

最近有个老板找我做一个自动化的程序&#xff0c;要求部署到Linux上 这是一个http协议的网站&#xff0c;chrome在默认设置下&#xff0c;会将http的网站识别成不安全的内容&#xff0c;然后自动将http转化成https访问 但是&#xff0c;这个http的网站它的加载项里既有http的…

Java | Leetcode Java题解之第132题分割回文串II

题目&#xff1a; 题解&#xff1a; class Solution {public int minCut(String s) {int n s.length();boolean[][] g new boolean[n][n];for (int i 0; i < n; i) {Arrays.fill(g[i], true);}for (int i n - 1; i > 0; --i) {for (int j i 1; j < n; j) {g[i]…

Centos X系统yum安装mysql数据库

安装之前需要将系统自带的mariadb-libs软件包删除。 检查是否存在mariadb-libs包。 yum list installed|grep mariadb-libs 删除mariadb-libs包 yum -y remove mariadb-libs 声明&#xff1a; 系统&#xff1a;CentOS-7-x86_64-DVD-2009 安装为最小化安装&#xff0c;没…

[线程与网络] Java虚拟机常考面试题(线程与网络完结)

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏:&#x1f355; Collection与数据结构 (92平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm1001.2014.3001.5482 &#x1f9c0;线程与…

MySQL: 表的增删改查(基础)

文章目录 1. 注释2. 新增(Create)3. 查询(Retrieve)3.1 全列查询3.2 指定列查询3.3 查询字段为表达式3.4 别名3.5 去重: distinct3.6 排序: order by3.7条件查询3.8 分页查询 4. 修改 (update)5. 删除(delete)6. 内容重点总结 1. 注释 注释&#xff1a;在SQL中可以使用“–空格…

【React】Redux与React - 环境准备

配套工具 在React中使用redux&#xff0c;官方要求安装俩个其他插件 - Redux Toolkit 和 react-redux 配置基础环境 使用 CRA 快速创建 React 项目 npx create-react-app react-redux安装配套工具 npm i reduxjs/toolkit react-redux启动项目 npm run start

python中while循环实现九九乘法表

i 1while i < 9: # 控制行的循环j 1while j < i: # 控制每行的输出print(f"{j}*{i}{j * i}\t", end"")j 1print()i 1运行截图&#xff1a;

图解 Python 编程(12) | 文件和编码方式

&#x1f31e;欢迎来到Python 的世界 &#x1f308;博客主页&#xff1a;卿云阁 &#x1f48c;欢迎关注&#x1f389;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f31f;本文由卿云阁原创&#xff01; &#x1f4c6;首发时间&#xff1a;&#x1f339;2024年6月9日&am…

《编译原理》期末考试复习手写笔记(二)+真题(第四、五、六章)+课后习题答案

第四章考试题型【自顶向下语法分析】 考点梳理&#xff1a; 1.语法分析程序的设计 2.确定的自顶向下分析思想2.1 FIRST集合 2.2 FOLLOW集合 2. 3 SELECT集合 2. 4 LL(1)文法 3.LL(1)文法的判别 如何消除左公因子? 如何消除左递归? 4.非LL(1)到LL(1)文法的等价变换 5.LL(1)分…

Web后端开发(请求-简单参数)(一)

原始方式&#xff1a; 在原始的web程序中&#xff0c;获取请求参数&#xff0c;需要通过HttpServletRequest 对象手动获取。 RequestMapping("/simpleParam") public String simpleParam(HttpServletRequest request){//获取请求参数String name request.getParame…

物资材料管理系统建设方案(Word)—实际项目方案

二、 项目概述 2.1 项目背景 2.2 现状分析 2.2.1 业务现状 2.2.2 系统现状 三、 总体需求 3.1 系统范围 3.2 系统功能 3.3 用户分析 3.4 假设与依赖关系 四、 功能需求 4.4.11.7 非功能性需求 五、 非功能性需求 5.1 用户界面需求 5.2 软硬件环境需求 5.3 产品质量需求 5.4 接口…

springAOP 使用aop代替SqlsessionUtil业务层操作

在Maven框架pom配置文件中导入spring相关依赖&#xff1a; <dependencies><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version></dependency><dependency…

爬虫可以不必自己写,使用ChatGPT编写抓取电影评论数据脚本

经常去新华书店看看有没有什么新书上架&#xff0c;还是更新挺及时的&#xff0c;可以反映新的技术趋势。这不&#xff0c;最近就看到了这本《巧用 ChatGPT 快速搞定数据分析》&#xff0c;作者是个大牛&#xff0c;第一次看到prompt可以这么写&#xff0c;得写这么长&#xff…

LeMeViT:具有可学习元令牌的高效ViT

本文提出使用可学习的元令牌来制定稀疏令牌&#xff0c;这有效地学习了关键信息&#xff0c;同时提高了推理速度。从技术上讲&#xff0c;主题标记首先通过交叉关注从图像标记中初始化。提出了双交叉注意&#xff08;DCA&#xff09;来促进图像令牌和元令牌之间的信息交换&…

【JS】理解闭包及其应用

历史小剧场 明朝灭亡&#xff0c;并非是简单的政治问题&#xff0c;事实上&#xff0c;这是世界经济史上的一个重要案例。 所谓没钱&#xff0c;就是没有白银。----《明朝那些事儿》 什么是闭包&#xff1f; 闭包就是指有权访问另一个函数作用域中变量的函数 闭包变量存储位置&…

数据结构【堆排序】

前言 在上一篇文章主要讲解了二叉树的基本概念和堆的概念以及接口的实现&#xff08;点此处跳转&#xff09; 我们简回顾下堆的基本概念&#xff1a; 1.堆分为大堆和小堆 大堆&#xff1a;父亲结点比左右孩子都大&#xff0c;根结点是最大的小堆&#xff1a;父亲结点比左右孩…

关于CodeCombat(沙漠)布朗噪声的攻略

关于CodeCombat(沙漠)//布朗噪声的攻略 总的来说怎么猥琐怎么来 1.走到墙角骷髅看不到的位置&#xff0c;让宠物制造噪音&#xff0c;然后英雄走过去&#xff0c;就是这样没错&#xff08;坐标之类能明白) 最后看看运行结果吧 Rec 0002 希望天天开心

CAN协议简介

协议简介 can协议是一种用于控制网络的通信协议。它是一种基于广播的多主机总线网络协议&#xff0c;常用于工业自动化和控制领域。can协议具有高可靠性、实时性强和抗干扰能力强的特点&#xff0c;被广泛应用于汽车、机械、航空等领域。 can协议采用了先进的冲突检测和错误检测…

C++系统编程篇——linux软件包管理器yum

Linux 软件包管理器yum (1)linux系统&#xff08;centos生态&#xff09; 安装方式有三种&#xff1a;源代码安装、rpm安装、yum安装&#xff08;最简单&#xff09; ls /etc/yum.repos.d/ 查看该路径下的文件 包含了用于配置 YUM 软件包管理器的仓库配置文件。这些配置文件…

QT-轻量级的笔记软件MyNote

MyNote v2.0 一个轻量级的笔记软件&#x1f4d4; Github项目地址: https://github.com/chandlerye/MyNote/tree/main 应用简介 MyNote v2.0 是一款个人笔记管理软件&#xff0c;没有复杂的功能&#xff0c;旨在提供便捷的笔记记录、管理以及云同步功能。基于Qt 6.6.3 个人开…