Vue--》Vue3打造可扩展的项目管理系统后台的完整指南(十)

news2024/11/16 3:20:38

今天开始使用 vue3 + ts 搭建一个项目管理的后台,因为文章会将项目的每一个地方代码的书写都会讲解到,所以本项目会分成好几篇文章进行讲解,我会在最后一篇文章中会将项目代码开源到我的GithHub上,大家可以自行去进行下载运行,希望本文章对有帮助的朋友们能多多关注本专栏,学习更多前端vue知识,然后开篇先简单介绍一下本项目用到的技术栈都有哪几个方面(阅读本文章能够学习到的技术):

vite:快速轻量且功能丰富的前端构建工具,帮助开发人员更高效构建现代Web应用程序。

pnpm:高性能、轻量级npm替代品,帮助开发人员更加高效地处理应用程序的依赖关系。

Vue3:Vue.js最新版本的用于构建用户界面的渐进式JavaScript框架。

TypeScript:JavaScript的超集,提供了静态类型检查,使得代码更加健壮。

Animate:基于JavaScript的动画框架,它使开发者可以轻松创建各种炫酷的动画效果。

vue-router:Vue.js官方提供的路由管理器与Vue.js紧密耦合,非常方便与Vue.js一同使用。

Pinia:Vue3构建的Vuex替代品,具有响应式能力,提供非常简单的 API,进行状态管理。

element-plus:基于Vue.js 3.0的UI组件库,用于构建高品质的响应式Web应用程序。

axios:基于Promise的HTTP客户端,可以在浏览器和node.js中使用。

three:基于JavaScript的WebGL库,开发者可以编写高性能、高质量的3D场景呈现效果。

echarts:基于JavaScript的可视化图表库,支持多种类型的图表,可根据需要自行安装。

当然还有许多其他的需要安装的第三方库,这里就不再一一介绍了,在项目中用到的地方自行会进行讲解,大家自行学习即可,现在就让我们走进vue3+ts的实战项目吧。

目录

角色管理模块静态搭建

实现添加与更新业务

实现分配角色权限业务

实现删除业务

实现菜单模块搭建

实现菜单模块的添加与更新业务

实现菜单模块的删除业务


角色管理模块静态搭建

实现角色管理模块的搭建,方式很简单也是通过调用相关接口函数获取数据,然后通过element提供的模板进行数据的呈现展示,如下编写接口:

// 角色管理模块的接口
import request from '@/utils/request'
import type { RoleResponseData } from './type'
// 枚举地址
enum API {
  // 获取全部的职位的接口
  ALLROLE_URL = '/admin/acl/role/',
}
// 获取全部的角色
export const reqAllRoleList = (page: number, limit: number, roleName: string) =>
  request.get<any, RoleResponseData>(API.ALLROLE_URL + `${page}/${limit}/?roleName=${roleName}`)

编写完接口函数之后,接下来就可以进行样式的搭建了,具体搭建过程就不再讲解了,前几篇文章都有重复性,如下给出代码:

<template>
  <div>
    <el-card style="height: 80px">
      <el-form :inline="true" class="form">
        <el-form-item label="职位搜索">
          <el-input placeholder="请你输入搜索职位关键字" v-model="keyword"></el-input>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" size="default" :disabled="keyword ? false : true" @click="search">搜索</el-button>
          <el-button type="primary" size="default" @click="reset">重置</el-button>
        </el-form-item>
      </el-form>
    </el-card>
    <el-card style="margin: 10px 0px">
      <el-button type="primary" size="default" icon="Plus">添加职位</el-button>
      <el-table border style="margin: 10px 0px" :data="allRole">
        <el-table-column type="index" label="#" align="center"></el-table-column>
        <el-table-column label="ID" align="center" prop="id"></el-table-column>
        <el-table-column label="职位名称" align="center" show-overflow-tooltip prop="roleName"></el-table-column>
        <el-table-column label="创建时间" align="center" show-overflow-tooltip prop="createTime"></el-table-column>
        <el-table-column label="更新时间" align="center" show-overflow-tooltip prop="updateTime"></el-table-column>
        <el-table-column label="操作" width="350px" align="center">
          <!-- row:已有的职位对象 -->
          <template #default="{ row }">
            <el-button type="primary" size="default" icon="User">分配权限</el-button>
            <el-button type="primary" size="default" icon="Edit">编辑</el-button>
            <el-button type="primary" size="default" icon="Delete">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <el-pagination
        v-model:current-page="pageNo"
        v-model:page-size="pageSize"
        :page-sizes="[10, 20, 30, 40]"
        :background="true"
        layout="prev, pager, next, jumper, -> ,sizes, total"
        :total="total"
        @current-change="getHasRole"
        @size-change="sizeChange"
      />
    </el-card>
  </div>
</template>

form表单的样式给出如下代码:

编写的接口不仅能够获取数据,还能进行数据的搜索,这里对输入框进行双向数据绑定,当然还有获取数据的接口函数的调用,以及声明了一些响应式的变量进行数据的存储与重新赋值:

<script setup lang="ts">
import { ref, onMounted } from 'vue'
// 接口函数方法
import { reqAllRoleList } from '@/api/acl/role'
import type { RoleResponseData, Records } from '@/api/acl/role/type'
// 引入仓库
import useLayOutSettingStore from '@/store/settings'
let settingStore = useLayOutSettingStore()
// 当前的页面
let pageNo = ref<number>(1)
// 当前页面展示的数据条数
let pageSize = ref<number>(10)
// 搜索职位的关键字
let keyword = ref<string>('')
// 存储全部已有的职位
let allRole = ref<Records>([])
// 职位的总个数
let total = ref<number>(0)

// 组件挂载完毕发起请求
onMounted(() => {
  // 获取职位的请求
  getHasRole()
})
// 获取全部用户信息的方法|分页器当前页码发生变化的回调
const getHasRole = async (pager = 1) => {
  // 修改当前页面
  pageNo.value = pager
  let result: RoleResponseData = await reqAllRoleList(pageNo.value, pageSize.value, keyword.value)
  if (result.code == 200) {
    total.value = result.data.total
    allRole.value = result.data.records
  }
}
// 下拉菜单的回调
const sizeChange = () => {
  getHasRole()
}
// 搜索按钮的回调
const search = () => {
  // 再次发起请求根据关键字
  getHasRole()
  keyword.value = ''
}
// 重置按钮的回调
const reset = () => {
  settingStore.refsh = !settingStore.refsh
}
</script>

最终的结果如下:

实现添加与更新业务

实现添加和更新业务在前几篇文章都有讲解过,可谓是老生常谈了,这里就不再详细的讲解,大部分是给出具体的代码,看看具体的实现,接口代码如下:

实现添加和更新业务这里都借助了对话框来实现,如下:

这里通过rules对对话框的表单进行一个规则校验:

const validatorRoleName = (_rule: any, value: any, callBack: any) => {
  if (value.trim().length >= 2) {
    callBack()
  } else {
    callBack(new Error('职位名称至少两位!'))
  }
}
// 职位相关的校验规则
const rules = {
  roleName: [{ required: true, trigger: 'blur', validator: validatorRoleName }],
}

给添加职位和修改职位的按钮绑定点击事件进行处理,如下先进行对话框的显示然后进行数据清除和绑定,这里清除校验错误的话需要借助nextTick进行处理,如下借助表单给出特定的方法进行处理:

// 添加职位按钮的回调
const addRole = () => {
  // 对话框显示出来
  dialogVisite.value = true
  // 清空数据
  Object.assign(RoleParams, {
    roleName: '',
    id: 0,
  })
  // 清空上一次表单校验的错误结果
  nextTick(() => {
    form.value.clearValidate('roleName')
  })
}
// 更新职位按钮的回调
const updateRole = (row: RoleData) => {
  // 对话框显示出来
  dialogVisite.value = true
  // 存储已有的职位——带有ID的
  Object.assign(RoleParams, row)
}

给确定按钮绑定点击事件,调用接口函数来实现添加或修改业务的实现,如下:

// 确定按钮的回调
const save = async () => {
  // 表单校验结果,结果通过再发请求,结果没有通过不应该再发请求
  await form.value.validate()
  // 添加|更新职位的请求
  let result: any = await reqAddOrUpdateRole(RoleParams)
  if (result.code == 200) {
    // 提示文字
    ElMessage({ type: 'success', message: RoleParams.id ? '更新成功!' : '添加成功!' })
    // 对话框显示
    dialogVisite.value = false
    // 再次获取全部的已有的职位
    getHasRole(RoleParams.id ? pageNo.value : 1)
  }
}

结果如下:

实现分配角色权限业务

实现分配权限的业务需要两个接口的实现,一个是获取所有职位的菜单数据;另一个是根据点击菜单选项下发分配权限的任务,首先我们先将相应的接口进行实现,如下:

编写完接口之后,接下来需要对分配权限的按钮设置点击事件,如下:

我们这里也是采用的element提供的抽屉样式进行展示和修改数据,并在抽屉中书写树形组件,关于树形组件其函数代表什么意思,这里就不再赘述,感兴趣的可以去官方文档进行查阅:

获取相应的数值很简单,这里比较难以理解的是需要我们如何获取点击复选框获取到的数据:

// 分配权限按钮的回调
const setPermisstion = async (row: RoleData) => {
  drawer.value = true // 抽屉显示出来
  // 收集当前要分类权限的职位的数据
  Object.assign(RoleParams, row)
  // 根据职位去获取权限的数据
  let result: MenuResponseData = await reqAllMenuList(RoleParams.id as number)
  if (result.code == 200) {
    menuArr.value = result.data
    selectArr.value = filterSelectArr(menuArr.value, [])
  }
}
// 树形控件的测试数据
const defaultProps = {
  children: 'children',
  label: 'name',
}
const filterSelectArr = (allData: any, initArr: any) => {
  allData.forEach((item: any) => {
    if (item.select && item.level == 4) {
      initArr.push(item.id)
    }
    if (item.children && item.children.length > 0) {
      filterSelectArr(item.children, initArr)
    }
  })
  return initArr
}

最后通过收集相应的数据然后调用相关接口就可以实现分配权限的任务了:

// 抽屉确定按钮的回调
const handler = async () => {
  // 职位的Id
  const roleId = RoleParams.id as number
  // 选中节点的Id
  let arr = tree.value.getCheckedKeys()
  // 半选的Id
  let arr1 = tree.value.getHalfCheckedKeys()
  let permissionId = arr.concat(arr1)
  // 下发权限
  let result: any = await reqSetPermisstion(roleId, permissionId)
  if (result.code == 200) {
    drawer.value = false // 抽屉关闭
    // 提示消息
    ElMessage({ type: 'success', message: '分配权限成功' })
    // 页面刷新
    window.location.reload()
  } else {
    ElMessage({ type: 'error', message: '分配权限失败' })
  }
}

最终结果如下:

实现删除业务

实现删除业务,前几篇文章讲解的可谓是不厌其烦了,很简单调接口穿id即可,如下:

设置气泡确认框然后通过confirm事件来进行传递数据

接下来通过调用相关接口即可删除数据

// 删除已有的职位
const removeRole = async (id: number) => {
  let result: any = await reqRemoveRole(id)
  if (result.code == 200) {
    ElMessage({ type: 'success', message: '删除成功' })
    getHasRole(allRole.value.length > 1 ? pageNo.value : pageNo.value - 1)
  } else {
    ElMessage({ type: 'error', message: '删除失败' })
  }
}

结果如下:

实现菜单模块搭建

接下来对菜单模块的路由组件样式进行搭建,和前文一样,借助element组件库提供的表格样式进行快速的样式搭建处理,首先我们先编写获取菜单模块的相应数据接口:

import request from '@/utils/request'
import type { PermissionResponseData } from './type'
// 枚举地址
enum API {
  // 获取全部菜单与按钮的标识数据
  ALLPERMISSTION_URL = '/admin/acl/permission',
}
// 获取菜单数据
export const reqAllPermisstion = () => request.get<any, PermissionResponseData>(API.ALLPERMISSTION_URL)

接下来通过借助element中表格样式进行样式快速搭建,如下:

<template>
  <div>
    <el-table :data="PermisstionArr" style="width: 100%; margin-bottom: 20px" row-key="id" border>
      <el-table-column label="名称" prop="name"></el-table-column>
      <el-table-column label="权限值" prop="code"></el-table-column>
      <el-table-column label="修改时间" prop="updateTime"></el-table-column>
      <el-table-column label="操作">
        <template #default="{ row }">
          <el-button type="primary" size="small" :disabled="row.level == 4 ? true : false">
            {{ row.level == 3 ? '添加功能' : '添加菜单' }}
          </el-button>
          <el-button type="primary" size="small" :disabled="row.level == 1 ? true : false">编辑</el-button>
          <el-button type="primary" size="small" :disabled="row.level == 1 ? true : false">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>

搭建完静态内容之后,我们通过调用相关接口函数,获取的数据赋予响应式ref变量中,然后在进行相应的引用,js代码如下:

<script setup lang="ts">
import { ref, onMounted } from 'vue'
// 引入获取菜单请求的API
import { reqAllPermisstion } from '@/api/acl/menu'
// 引入ts类型
import type { PermissionResponseData, PermissionList } from '@/api/acl/menu/type'
// 存储菜单数据
let PermisstionArr = ref<PermissionList>([])
// 组件挂载完毕
onMounted(() => {
  getHasPermisstion()
})
// 获取菜单数据的方法
const getHasPermisstion = async () => {
  let result: PermissionResponseData = await reqAllPermisstion()
  if (result.code == 200) {
    PermisstionArr.value = result.data
  }
}
</script>

最终实现的结果如下:

实现菜单模块的添加与更新业务

关于添加与更新业务,我们在前几篇文章已经讲解过好多次了,这次就大概讲解一下实现的过程,首先我们先编写添加与更新的接口,因为添加和更新的接口在于是否有id值,所有可以将这两个接口合并成为一个接口,如下:

添加和更新的页面展示,这里采用element的对话框来实现,如下:

<!-- 对话框组件:添加或更新已有的菜单的数据结构 -->
<el-dialog v-model="dislogVisible" :title="menuData.id ? '更新菜单' : '添加菜单'">
  <!-- 表单组件:收集新增与已有的菜单的数据 -->
  <el-form>
    <el-form-item label="名称">
      <el-input placeholder="请你输入菜单名称" v-model="menuData.name"></el-input>
    </el-form-item>
    <el-form-item label="权限">
      <el-input placeholder="请你输入权限数值" v-model="menuData.code"></el-input>
    </el-form-item>
  </el-form>
  <template #footer>
    <span class="dialog-footer">
      <el-button @click="dislogVisible = false">取消</el-button>
      <el-button type="primary" @click="save">确定</el-button>
    </span>
  </template>
</el-dialog>

设置完对话框样式之后,接下来就可以给添加和更新的按钮时候点击函数,如下:

// 添加菜单按钮的回调
const addPermisstion = (row: Permission) => {
  // 清空数据
  Object.assign(menuData, {
    code: '',
    level: 0,
    name: '',
    pid: 0,
  })
  dislogVisible.value = true // 对话框显示
  // 收集新增菜单的level值
  menuData.level = row.level + 1
  // 给谁新增子菜单
  menuData.pid = row.id as number
}
// 编辑已有的菜单
const updatePermisstion = (row: Permission) => {
  dislogVisible.value = true // 对话框显示
  // 点击修改按钮:收集已有的菜单的数据进行更新
  Object.assign(menuData, row)
}

设置完电话函数之后,我们在对话框的确定按钮处,设置点击事件来调用相关的接口实现添加和修改效果,如下:

// 确定按钮的回调
const save = async () => {
  let result: any = await reqAddOrUpdateMenu(menuData)
  if (result.code == 200) {
    dislogVisible.value = false // 对话框隐藏
    // 提示消息
    ElMessage({ type: 'success', message: menuData.id ? '更新成功' : '添加成功' })
    // 再次获取全部数据
    getHasPermisstion()
  }
}

最终的结果如下:

实现菜单模块的删除业务

实现删除业务很简单,只需要调用相应的删除接口,然后将要删除的数据的id作为参数传递给接口函数即可,接口函数编写如下:

编写完接口函数之后,给删除按钮设置一个气泡确认框用来实现删除业务,如下:

给confirm事件设置函数,将row.id作为参数传递过去,如下:

// 删除按钮的回调
const removeMenu = async (id: number) => {
  let result: any = await reqRemoveMenu(id)
  if (result.code == 200) {
    ElMessage({ type: 'success', message: '删除成功' })
    // 再次获取数据
    getHasPermisstion()
  }
}

删除业务完成,最终呈现的效果如下:

本项目的角色管理和菜单管理页面功能的搭建就讲解到这,下一篇文章将继续讲解其它模块的主体内容,关注博主学习更多前端vue知识,您的支持就是博主创作的最大动力!

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

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

相关文章

【Linux之拿捏信号1】什么是信号以及信号的产生

文章目录 生活角度的信号技术应用角度的信号系统中的信号信号函数signal调用系统函数向进程发信号由软件条件产生信号由硬件异常产生信号总结 生活角度的信号 在我们的生活中&#xff0c;什么可以被称为信号呢&#xff1f; 那可太多啦&#xff0c;有红绿灯&#xff0c;闹钟&…

通过NFS与Windows共享文件

通过NFS与Windows共享文件 1. 连接同一wifi网络 将开发板子连接至和PC同网络&#xff0c;并保证能互相ping通 2.启动Windows 端NFS Server 在电脑端启动NFS server&#xff08;下载链接&#xff1a; https://www.hanewin.net/nfs-e.htm &#xff09; 3. 双击nfssrv-64.exe…

轻创数字人集团一文论述如何助力中小型企业数字化转型 ?

随着互联网技术的不断发展&#xff0c;人工智能已经成为了许多企业数字化转型的重要工具。然而&#xff0c;在人工智能领域中&#xff0c;不乏一些大型公司在研发和应用方面占据着主导地位。例如&#xff0c;以基础技术和软硬件设施为主的上游层&#xff0c;以英伟达、Meta、Ep…

基于 DDR3 的native接口串口传图帧缓存系统设计实现(整体设计)

文章目录 前言一、串口传图顶层系统设计框图二、各模块说明三、uart_ddr3_native_tft四、 uart_ddr3_native_tft仿真模块五、fifo_ddr3_native_fifo 模块六、fifo_ddr3_native_fifo 仿真模块七、传图展示 前言 结合串口接收模块和 tft 显示屏控制模块&#xff0c;设计一个基于…

sklearn房价预测(随机森林)

文章目录 一、前言二、实现方法一、前言 任务目标:根据统计在csv中的房屋属性相关数据,预测房屋最终成交价格数据集:《住宅属性数据集》,自取https://download.csdn.net/download/weixin_43721000/87785277数据集字段解释: 这个文件中有字段详细说明↓ 二、实现方法 # pi…

ARM_key1按键按下,打印key1

include/key.h #ifndef __KEY_H__ #define __KEY_H__#include "stm32mp1xx_exti.h" #include "stm32mp1xx_gic.h" #include "stm32mp1xx_rcc.h" #include "stm32mp1xx_gpio.h"//对RCC/GPIO/EXTI进行初始化 void hal_key1_exit_init()…

路径规划算法:基于类电磁机制优化的路径规划算法- 附代码

路径规划算法&#xff1a;基于类电磁机制优化的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于类电磁机制优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用智能…

面试交流会

面试交流 目录&#xff1a; 01&#xff1a;关于人生目标 02&#xff1a;关于心态 03&#xff1a;关于选择 04&#xff1a;关于学习 05&#xff1a;关于简历 06&#xff1a;关于找工作 1&#xff1a;关于人生目标 1.01、自己想成为什么样的人&#xff1f; 1.02、自己的人生目标是…

sqoop 详解

1 sqoop原理 1.1 sqoop介绍# Sqoop是Apache旗下的一款“hadoop和关系型数据库服务器之间传送数据”的工具。   导入数据&#xff1a;MySQL、Oracle导入数据到hadoop的hdfs、hive、hbase等数据存储系统。   导出数据&#xff1a;从hadoop的文件系统中导出数据到关系型数据…

Mysql数据库——表操作的练习

题目一 &#xff08;1&#xff09; mysql> create database Market; &#xff08;2&#xff09; mysql> use Market; Database changed mysql> create table customers(->c_num INT(11) primary key auto_increment,->c_name varchar(50),->c_contact varcha…

chatGPT之100个例子-从体验到精通

简介 本博文演示了100个chatGPT在各行各业的使用例子,全部看完有助于培养chatGPT解决问题的思维。 在人工智能时代,智能软件并不会淘汰人类,淘汰人类的是会使用人工智能的人! 我们直接使用openAI官方chatGPT,生动演示了chatGPT的一些妙用! 请仔细看完,一定会有收获! 每…

Python 之 基本概述

这里写目录标题 一、Python 简介1. 历史背景2. 特点3. 优缺点4. 应用领域 二、Python 解释器1. 解释器是什么&#xff1f;2. 解释器的构成及其各部分功能3. 解释器的执行过程 三、Python 环境安装四、第一个 Python 程序1. 实际操作2. 常见问题 五、Python 开发环境 一、Python…

Ubuntu 22.04.2 LTS 安装搜狗输入法后,修改区域格式Regional Format crash 崩溃 ,改用bash 指令修改

故障 系统已经升级到最新 基于Ubuntu 20.04 LTS apt upgrade升级而来。 yeqiangyeqiang-MS-7B23:~$ cat /etc/os-release PRETTY_NAME"Ubuntu 22.04.2 LTS" NAME"Ubuntu" VERSION_ID"22.04" VERSION"22.04.2 LTS (Jammy Jellyfish)"…

【LangChain】Document篇

概述 这些是处理文档的核心链。它们对于总结文档、回答文档问题、从文档中提取信息等很有用。 这些链都实现了一个通用接口&#xff1a; class BaseCombineDocumentsChain(Chain, ABC):"""Base interface for chains combining documents."""a…

Vue:Elemenu-Plus递归型菜单组件封装

前端开发中&#xff0c;经常遇到需要与后端配置&#xff0c;前端动态渲染菜单的应用场景&#xff0c;而究其本质&#xff0c;就是菜单组件的应用&#xff0c;只是在不确定菜单级数的情况下&#xff0c;我们需要对组件做一个递归处理&#xff0c;让它能够适应大多数应用场景。 递…

9.17UEC++代码段、编码和字符串

1. 编码定义&#xff1a; 自行转码&#xff1a; 字符串&#xff1a; FName&#xff1a;名称&#xff0c;访问快&#xff0c;用FName做键值。&#xff08;键值容器&#xff09;资产名称基本都是FName。 FText&#xff1a;一般是和UI有关&#xff0c;专门对接UI&#xff0c;也是…

IDEA动态调试WebLogic

IDEA动态调试WebLogic 环境&#xff1a;Windows 10 Windows7(192.168.52.181) Idea WebLogic12.2.1.4 Java8102 0x01 安装weblogic 安装成功后&#xff0c;在domains下的bin目录下有个startWebLogic.cmd文件 0x02 配置被调试端 0x0201 添加调试参数 2.1.1 方式一 在…

基于单片机智能衣柜 智能衣橱 换气除湿制系统紫外线消毒的设计与实现

功能介绍 以51单片机作为主控系统&#xff1b;液晶显示当前衣柜温湿度和柜门开启关闭状态&#xff1b;按键设置当前衣柜湿度上限值、衣柜门打开和关闭&#xff0c;杀菌消毒&#xff1b;当湿度超过设置上限&#xff0c;继电器闭合开启风扇进行除湿&#xff1b;进行杀菌消毒时&am…

7.5 cloneGitHub项目到服务器端/wget/print/spikes_max

在机器学习和深度学习中&#xff0c;位量化&#xff08;Bit Quantization&#xff09;是一种将模型参数或激活值表示为较低精度的二进制数的技术。通常情况下&#xff0c;模型的参数和激活值是以浮点数形式存储和计算的&#xff0c;占用较大的存储空间和计算资源。位量化通过减…

找不到vcruntime140.dll无法继续执行代码的解决方法

计算机提示找不到vcruntime140.dll无法继续执行代码是什么原因呢&#xff1f;在我们运行photoshop&#xff0c;数据库的时候&#xff0c;电脑提示找不到vcruntime140.dll是由于电脑系统中的Microsoft Visual C Redistributable包损坏&#xff0c;vcruntime140.dll是Microsoft V…