数据库设计与前端框架

news2024/11/28 20:39:06

 数据库设计与前端框架

学习目标:

理解多租户的数据库设计方案

熟练使用PowerDesigner构建数据库模型理解前端工程的基本架构和执行流程

完成前端工程企业模块开发

多租户SaaS平台的数据库方案

多租户是什么

多租户技术(Multi-TenancyTechnology)又称多重租赁技术:是一种软件架构技术,是实现如何在多用户环境下

(此处的多用户一般是面向企业用户)共用相同的系统或程序组件,并且可确保各用户间数据的隔离性。简单讲: 在一台服务器上运行单个应用实例,它为多个租户(客户)提供服务。从定义中我们可以理解:多租户是一种架 构,目的是为了让多用户环境下使用同一套程序,且保证用户间数据隔离。那么重点就很浅显易懂了,多租户的重 点就是同一套程序下实现多用户数据的隔离

需求分析

传统软件模式,指将软件产品进行买卖,是一种单纯的买卖关系,客户通过买断的方式获取软件的使用权,软件的 源码属于客户所有,因此传统软件是部署到企业内部,不同的企业各自部署一套自己的软件系统

Saas模式,指服务提供商提供的一种软件服务,应用统一部署到服务提供商的服务器上,客户可以根据自己的实际 需求按需付费。用户购买基于WEB的软件,而不是将软件安装在自己的电脑上,用户也无需对软件进行定期的维护 与管理

在SaaS平台里需要使用共用的数据中心以单一系统架构与服务提供多数客户端相同甚至可定制化的服务,并且仍可 以保障客户的数据正常使用。由此带来了新的挑战,就是如何对应用数据进行设计,以支持多租户,而这种设计的 思路,是要在数据的共享、安全隔离和性能间取得平衡。

多租户的数据库方案分析

目前基于多租户的数据库设计方案通常有如下三种:

  • 独立数据库
  • 共享数据库、独立 Schema
  • 共享数据库、共享数据表

独立数据库

独立数据库:每个租户一个数据库。

优点:为不同的租户提供独立的数据库,有助于简化数据模型的扩展设计,满足不同租户的独特需求;如果 出现故障,恢复数据比较简单。

缺点: 增多了数据库的安装数量,随之带来维护成本和购置成本的增加

这种方案与传统的一个客户、一套数据、一套部署类似,差别只在于软件统一部署在运营商那里。由此可见此方案用户数据隔离级别最高,安全性最好,但是成本较高

共享数据库、独立 Schema

(1) 什么是Schema

oracle数据库:在oracle中一个数据库可以具有多个用户,那么一个用户一般对应一个Schema,表都是建立在Schema中的,(可以简单的理解:在oracle中一个用户一套数据库表)

骚戴理解:其实oracle的Schema就相当于mysql中的database

mysql数据库:mysql数据中的schema比较特殊,并不是数据库的下一级,而是等同于数据库。比如执行create schema test 和执行create database test效果是一模一样的

共享数据库、独立 Schema:即多个或所有的租户使用同一个数据库服务(如常见的ORACLE或MYSQL数据库), 但是每个租户一个Schema。

优点: 为安全性要求较高的租户提供了一定程度的逻辑数据隔离,并不是完全隔离;每个数据库可支持更多的租户数量。

缺点: 如果出现故障,数据恢复比较困难,因为恢复数据库将牵涉到其他租户的数据; 如果需要跨租户统计数据,存在一定困难。

这种方案是方案一的变种。只需要安装一份数据库服务,通过不同的Schema对不同租户的数据进行隔离。由于数据库服务是共享的,所以成本相对低廉。

骚戴理解:共享数据库、独立 Schema方案其实就是假如我在服务器上安装了一个mysql服务端,然后每一个用户就给他创建一个数据库database,这样多个用户就只需要创建多个database数据库即可,不需要像独立数据库方案一样每一个用户我就要去搭建一个mysql服务端。共享数据库、共享数据表方案就是我就一个数据库,一套数据库表,大家都用这个,至于怎么区分到底哪条记录是哪个用户的?加个用户标识的字段就可以了!

共享数据库、共享数据表

共享数据库、共享数据表:即租户共享同一个Database,同一套数据库表(所有租户的数据都存放在一个数据库的同一套表中)。在表中增加租户ID等租户标志字段,表明该记录是属于哪个租户的。

优点:所有租户使用同一套数据库,所以成本低廉。

缺点:隔离级别最低,安全性最低,需要在设计开发时加大对安全的开发量,数据备份和恢复最困难。

这种方案和基于传统应用的数据库设计并没有任何区别,但是由于所有租户使用相同的数据库表,所以需要做好对 每个租户数据的隔离安全性处理,这就增加了系统设计和数据管理方面的复杂程度。

SAAS-HRM数据库设计

在SAAS-HRM平台中,分为了试用版和正式版。处于教学的目的,试用版采用共享数据库、共享数据表的方式设 计。正式版采用基于mysql的共享数据库、独立 Schema设计。

数据库设计与建模

数据库设计的三范式

三范式

第一范式(1NF):确保每一列的原子性(做到每列不可拆分)

第二范式(2NF):在第一范式的基础上,非主字段必须依赖于主字段(一个表只做一件事)

第三范式(3NF):在第二范式的基础上,消除传递依赖

反三范式:反三范式是基于第三范式所调整的,没有冗余的数据库未必是最好的数据库,有时为了提高运行效率,就必须降低范式标准,适当保留冗余数据。

骚戴理解:第二范式其实就是假如有一个学生表,除了学生基本信息的一些字段,如果你还有课程成绩相关的字段在这个表就不符合第二范式,把课程成绩相关字段拆出来分作一个成绩表,那就符合第二范式。继续上面的例子,现在已经分成了学生表和成绩表,学生表里有个字段是成绩表的id,那么我每次查询学生的所有信息(包括成绩)我都要去查询学生表然后根据成绩id去成绩表再查一次,这样就要查询两次,所以就有了反三范式的出现,反三范式就是加冗余字段,也就是把成绩信息又加到学生表里,这样只要查询学生表就可以查询出学生的所有信息,当然这是跟第二范式冲突的,所以叫“反”三范式。至于这个三范式其实就是假如有个表,里面有单价和数量两个字段,那么就可以根据单价和数量去计算出总价,所以就可以不需要总价这个字段,这也叫传递依赖

数据库建模

了解了数据的设计思想,那对于数据库表的表设计应该怎么做呢?答案是数据库建模

数据库建模:在设计数据库时,对现实世界进行分析、抽象、并从中找出内在联系,进而确定数据库的结构。它主 要包括两部分内容:确定最基本的数据结构;对约束建模。

建模工具

对于数据模型的建模,最有名的要数PowerDesigner,PowerDesigner是在中国软件公司中非常有名的,其易用性、功能、对流行技术框架的支持、以及它的模型库的管理理念,都深受设计师们喜欢。他的优势在于:不用在使 用create table等语句创建表结构,数据库设计人员只关注如何进行数据建模即可,将来的数据库语句,可以自动生成

使用pd建模

选择新建数据库模型 打开PowerDesigner,文件->建立新模型->model types(选择类型)->Physical Data Model(物理模型)

控制面板

创建数据库表

点即面板按钮中的创建数据库按钮创建数据库模型

切换columns标签,可以对表中的所有字段进行配置

如果基于传统的数据库设计中存在外键则可以使用面版中的Reference配置多个表之间的关联关系,效果如下图

导出sql

菜单->数据库(database)->生成数据库表结构(Generate Database)

前端框架

脚手架工程

此项目采用目前比较流行的前后端分离的方式进行开发。前端是在传智播客研究院开源的前端框架(黑马Admin商用后台模板)的基础上进行的开发。

官网上提供了非常基础的脚手架,如果我们使用官网的脚手架需要自己写很多代码比如登陆界面、主界面菜单样式 等内容。 课程已经提供了功能完整的脚手架,我们可以拿过来在此基础上开发,这样可以极大节省我们开发的时间。

技术栈

  • vue 2.5++
  • elementUI 2.2.2 vuex
  • axios
  • vue-router vue-i18n

前端环境

  • node 8.++
  • npm 5.++

启动与安装

官网上提供了非常基础的脚手架,如果我们使用官网的脚手架需要自己写很多代码比如登陆界面、主界面菜单样式 等内容。 课程已经提供了功能完整的脚手架,我们可以拿过来在此基础上开发,这样可以极大节省我们开发的时间。

  • 解压提供的资源包
  • 在命令提示符进入该目录,输入命令:

cnpm install

通过淘宝镜像下载安装所有的依赖,几分钟后下载完成如果没有安装淘宝镜像,请使用npm install

  • 关闭语法检查

打开 将useEslint的值改为false。

config/index.js

useEslint: false,

此配置作用: 是否开启语法检查,语法检查是通过ESLint 来实现的。我们现在科普一下,什么是ESLint : ESLint 是一个语法规则和代码风格的检查工具,可以用来保证写出语法正确、风格统一的代码。如果我们开启了 Eslint , 也就意味着要接受它非常苛刻的语法检查,包括空格不能少些或多些,必须单引不能双引,语句后不可以写分号等等,这些规则其实是可以设置的。我们作为前端的初学者,最好先关闭这种校验,否则会浪费 很多精力在语法的规范性上。如果以后做真正的企业级开发,建议开启

  • 输入命令:

npm run dev

工程结构

整个前端工程的工程目录结构如下:

├── assets                         | 资源
├── build                           | webpack编译配置
├── config                         | 全局变量
├── src                             | 源码
│   ├── api                         | 数据请求
│   ├── assets                     | 资源
│   ├── components                 | 组件
│   ├── mixins                     | mixins
│   ├── filters                     | vue filter
│   ├── icons                       | 图标
│   ├── lang                       | 多语言
│   ├── router                     | 路由
│   ├── store                       | 数据
│   ├── styles                     | 样式
│   ├── utils                       | 工具函数库
│   ├── module-dashboard           | 框架程序
│   │   ├── assets
│   │   ├── components
│   │   ├── pages
│   │   ├── router
│   │   └── store
│   ├── module-example             | 示例程序
│   │   ├── assets
│   │   ├── components
│   │   ├── pages
│   │   ├── router
│   │   └── store
│   ├── App.vue                     | app
│   ├── main.js                     | 主引导
│   └── errorLog.js               | vue全局错误捕捉
├── dist                           | 编译发布目录
├── README.md
├── index.html                     | 页面模板
├── package.json                   | npn包配置
├── static
└── test                           | 测试
   ├── e2e
   └── unit

执行流程分析

路由和菜单

路由和菜单是组织起一个后台应用的关键骨架。本项目侧边栏和路由是绑定在一起的,所以你只有在@/router/index.js 下面配置对应的路由,侧边栏就能动态的生成了。大大减轻了手动编辑侧边栏的工作量。

当然这样就需要在配置路由的时候遵循很多的约定这里的路由分为两种, constantRouterMap 和 asyncRouterMap 。

  • constantRouterMap 代通用页面。
  • asyncRouterMap 代表那些业务中通过 addRouters 动态添加的页面。

骚戴理解:路由分为两种,公共路由和模块路由,公共路由就是下面的第二个红框画出来的,通常就是登录、注册、404页面这些公共模块的路由定义,下面module-saas-clients就是一个模块,所有模块通常都是以module开头的,那么模块下面的router路由自然就是模块路由

其实每一个模块就是下面的左侧栏,例如这个模块对应的就是SAAS企业这个左侧栏,

前端数据交互

一个完整的前端 UI 交互到服务端处理流程是这样的:

  • UI 组件交互操作;
  • 调用统一管理的 api service 请求函数;
  • 使用封装的 request.js 发送请求;
  • 获取服务端返回;
  • 更新 data;

从上面的流程可以看出,为了方便管理维护,统一的请求处理都放在 src/api 文件夹中,并且一般按照 model纬度进行拆分文件

api/
 frame.js
 menus.js
 users.js
 permissions.js
 ...

其中, src/utils/request.js 是基于 axios 的封装,便于统一处理 POST,GET 等请求参数,请求头,以及错误提示信息等。具体可以参看 request.js。 它封装了全局 request拦截器 、 respone拦截器 、 统一的错误处理 、 统一做了超时,baseURL设置等

骚戴理解:这里我一开始不理解为什么明明用的是demo却可以找到module-demo这个模块?

上面的这个main.js里面定义了模块的名称,所以你的路径里用的是demo也可以找到module-demo这个模块

企业管理

需求分析

在通用页面配置企业管理模块,完成企业的基本操作

搭建环境

新增模块

手动创建

方式一:在src目录下创建文件夹,命名规则:module-模块名称()

在文件夹下按照指定的结构配置assets,components,pages,router,store等文件

使用命令自动创建

安装命令行工具

npm install -g itheima-cli

执行命令

itheima moduleAdd saas-clients
`saas-clients` 是新模块的名字
自动创建这些目录和文件
│   ├── module-saas-clients     | saas-clients模块主目录
│   │   ├── assets             | 资源
│   │   ├── components         | 组件
│   │   ├── pages               | 页面
│   │   │   └── index.vue       | 示例
│   │   ├── router             | 路由
│   │   │   └── index.js       | 示例
│   │   └── store               | 数据
│   │       └── app.js         | 示例

每个模块所有的素材、页面、组件、路由、数据,都是独立的,方便大型项目管理, 在实际项目中会有很多子业务项目,它们之间的关系是平行的、低耦合、互不依赖。

构造模拟数据

  1. 在/src/mock中添加模拟数据company.js
import Mock from 'mockjs'
import { param2Obj } from '@/utils'
const List = []
const count = 100
for (let i = 0; i < 3; i++) {
    let data = {
        id: "1"+i,
        name: "企业"+i,
        managerId: "string",
        version: "试用版v1.0",
        renewalDate: "2018-01-01",
        expirationDate: "2019-01-01",
        companyArea: "string",
        companyAddress: "string",
        businessLicenseId: "string",
        legalRepresentative: "string",
        companyPhone: "13800138000",
        mailbox: "string",
        companySize: "string",
        industry: "string",
        remarks: "string",
        auditState: "string",
        state: "1",
        balance: "string",
        createTime: "string"
   }
  List.push(data)
}
export default {
  list: () => {
    return {
        code: 10000,
        success: true,
        message: "查询成功",
        data:List
   }
 },
  sassDetail:() => {
    return {
      code: 10000,
      success: true,
      message: "查询成功",
      data:{
        id: "10001",
        name: "测试企业",
        managerId: "string",
        version: "试用版v1.0",
        renewalDate: "2018-01-01",
        expirationDate: "2019-01-01",
        companyArea: "string",
        companyAddress: "string",
        businessLicenseId: "string",
        legalRepresentative: "string",
        companyPhone: "13800138000",
        mailbox: "string",
        companySize: "string",
        industry: "string",
        remarks: "string",
        auditState: "string",
        state: "1",
        balance: "string",
        createTime: "string"
     }
   }
 }
}
  1. 配置模拟API接口拦截规则

在/src/mock/index.js中配置模拟数据接口拦截规则

import Mock from 'mockjs'
import TableAPI from './table'
import ProfileAPI from './profile'
import LoginAPI from './login'
import CompanyAPI from './company'
Mock.setup({
  //timeout: '1000'
})
//如果发送请求的api路径匹配,拦截
//第一个参数匹配的请求api路径,第二个参数匹配请求的方式,第三个参数相应数据如何替换
Mock.mock(/\/table\/list\.*/, 'get', TableAPI.list)
//获取用户信息
Mock.mock(/\/frame\/profile/, 'post', ProfileAPI.profile)
Mock.mock(/\/frame\/login/, 'post', LoginAPI.login)
//配置模拟数据接口
// /company/12
Mock.mock(/\/company\/+/, 'get', CompanyAPI.sassDetail)//根据id查询
Mock.mock(/\/company/, 'get', CompanyAPI.list)  //访问企业列表

注册模块

编辑src/main.js

...
/*
* 注册 - 业务模块
*/
import dashboard from '@/module-dashboard/' // 面板
import saasClients from '@/module-saas-clients/' //刚新添加的 企业管理
Vue.use(dashboard, store)
Vue.use(saasClients, store)
...

配置路由菜单

打开刚才自动创建的/src/module-saas-clients/router/index.js

import Layout from '@/module-dashboard/pages/layout'
const _import = require('@/router/import_' + process.env.NODE_ENV)
export default [
 {
    path: '/saas-clients',
    component: Layout,
    redirect: 'noredirect',
    name: 'saas-clients',
    meta: {
      title: 'SaaS企业管理',
      icon: 'international'
   },
    root: true,    
    children: [
     {
        path: 'index',
        name: 'saas-clients-index',
        component: _import('saas-clients/pages/index'),
        meta: {title: 'SaaS企业', icon: 'international', noCache: true}
     }
   ]
 }
]

骚戴理解: path: '/saas-clients'是父路径, component: _import('saas-clients/pages/index')可不是子路径, path: 'index'才是子路径,我一开始以为 component: _import('saas-clients/pages/index')是子路径,其实 component: _import('saas-clients/pages/index') 只是说从根据这个路径去找Vue视图页面

编写业务页面

创建/src/module-saas-clients/pages/index.vue

<template>
  <div class="dashboard-container">
    saas企业管理
  </div>
</template>
<script>
export default {
  name: 'saasClintList',
  components: {},
  data() {
    return {
   }
 },
  computed: {
 },
  created() {
 }
}
</script>

注意文件名 驼峰格式 首字小写

页面请放在目录 /src/module-saas-clients/pages/

组件请放在目录 /src/module-saas-clients/components/

页面路由请修改 /src/module-saas-clients/router/index.js

企业操作

创建api

在api/base目录下创建企业数据交互的API(saasClient.js)

import {createAPI, createFormAPI} from '@/utils/request'
export const list = data => createAPI('/company', 'get', data)
export const detail = data => createAPI(`/company/${data.id}`, 'get', data)

骚戴理解:`/company/${data.id}`这里注意带参数的写法,然后注意是梵引号不是单引号

企业列表

在src\module-saas-clients\pages\index.vue里面引用上面的API

<template>
  <div class="dashboard-container">
    <div class="app-container">
      <el-card shadow="never">
        <!--elementui的table组件
          data:数据模型
         -->
            <el-table :data="dataList"  border style="width: 100%">
            <!--el-table-column : 构造表格中的每一列 
              prop: 数组中每个元素对象的属性名
            -->
            <el-table-column  type="index" label="序号"  width="50"></el-table-column>
            <el-table-column  prop="name" label="企业名称" ></el-table-column>
            <el-table-column  prop="version" label="版本" ></el-table-column>
            <el-table-column  prop="companyphone" label="联系电话" ></el-table-column>
            <el-table-column  prop="expirationDate" label="截至时间"  ></el-table-column>
            <el-table-column  prop="state" label="状态" >
              <!--scope:传递当前行的所有数据 -->
              <template slot-scope="scope">
              <!--开关组件
                  active-value:激活的数据值
                  active-color:激活的颜色
                  inactive-value:未激活
                  inactive-color:未激活的颜色
               -->
              <el-switch
                  v-model="scope.row.state"
                  inactive-value="0"    
                  active-value="1"
                  disabled
                  active-color="#13ce66"
                  inactive-color="#ff4949">
                </el-switch>
              </template>
            </el-table-column>
            <el-table-column fixed="right" label="操作" width="150">
              <template slot-scope="scope">
                <router-link :to="'/saas-clients/details/'+scope.row.id">查看</router-link>
              </template>
            </el-table-column>
          </el-table>
      </el-card>
     </div>
  </div>
</template>

<script>
import {list} from '@/api/base/saasClient'
export default {
  name: 'saas-clients-index',
  data () {
    return {
      dataList:[]
    }
  },
  methods: {
    getList() {
      //调用API发起请求
      //res=响应数据
      list().then(res => {
        this.dataList = res.data.data
      })
    }
  },
  // 创建完毕状态
  created() {
    this.getList()
  }
}
</script>

<style rel="stylesheet/scss" lang="scss" scoped>
.alert {
  margin: 10px 0px 0px 0px;
}
.pagination {
  margin-top: 10px;
  text-align: right;
}
</style>

骚戴理解:import {list} from '@/api/base/saasClient'通过这句引用API,在getList方法里面去用list(),这里也并没有直接发请求给后端,而是演示了一下mock怎样模拟数据,在src\mock\company.js填写模拟数据,然后注册到mock里面进行拦截,在src\mock\index.js这里面注册模拟的mockAPI,然后设置拦截路径,拦截对应的请求然后返回模拟数据

骚戴理解:

/\/company\/+/ 表示一个正则表达式,匹配包含 /company/ 后面跟着一个或多个字符的字符串,比如:

  • /company/apple
  • /company/google/maps
  • /company/microsoft/windows/excel

但是不匹配以下字符串:

  • /company/
  • /company
  • /companyapple

而 /\/company/ 则表示一个正则表达式,匹配所有以 "/company" 开头的字符串,包括:

  • /company
  • /company/apple
  • /company/google/maps
  • /company/microsoft/windows/excel

它也会匹配包含 /company 例如 /companyapple , 因为该正则表达式只匹配 字符串开始部分的 /company。

import Mock from 'mockjs'
import { param2Obj } from '@/utils'

const List = []
const count = 100

for (let i = 0; i < 3; i++) {
    let data = {
        id: "1"+i,
        name: "企业"+i,
        managerId: "string",
        version: "试用版v1.0",
        renewalDate: "2018-01-01",
        expirationDate: "2019-01-01",
        companyArea: "string",
        companyAddress: "string",
        businessLicenseId: "string",
        legalRepresentative: "string",
        companyPhone: "13800138000",
        mailbox: "string",
        companySize: "string",
        industry: "string",
        remarks: "string",
        auditState: "string",
        state: "1",
        balance: "string",
        createTime: "string"
    }
  List.push(data)
}

export default {
  list: () => {
    return {
        code: 10000,
        success: true,
        message: "查询成功",
        data:List
    }
  },
  sassDetail:() => {
    return {
      code: 10000,
      success: true,
      message: "查询成功",
      data:{
        id: "10001",
        name: "测试企业",
        managerId: "string",
        version: "试用版v1.0",
        renewalDate: "2018-01-01",
        expirationDate: "2019-01-01",
        companyArea: "string",
        companyAddress: "string",
        businessLicenseId: "string",
        legalRepresentative: "string",
        companyPhone: "13800138000",
        mailbox: "string",
        companySize: "string",
        industry: "string",
        remarks: "string",
        auditState: "string",
        state: "1",
        balance: "string",
        createTime: "string"
      }
    }
  }
}

骚戴理解: 这里两个前端的点,通过以下的语句来实现序号排列数据,注意这个序号不是数据库里的id

<el-table-column fixed type="index" label="序号" width="50"></el-table-column>

通过下面的代码块来实现按钮, disabled直接设置按钮可不可设置

   <el-table-column fixed prop="state" label="状态"  width="150">
              <!--scope:传递当前行的所有数据 -->
              <template slot-scope="scope">
              <!--开关组件
                  active-value:激活的数据值
                  active-color:激活的颜色
                  inactive-value:未激活
                  inactive-color:未激活的颜色
               -->
              <el-switch
                  v-model="scope.row.state"
                  inactive-value="0"    
                  active-value="1"
                  disabled
                  active-color="#13ce66"
                  inactive-color="#ff4949">
                </el-switch>
              </template>
            </el-table-column>

骚戴理解:这里我抽出来主要是要学会通过Vue的方式去发请求,注意是单引号去拼接请求的url路径

<template slot-scope="scope">
    <router-link :to="'/saas-clients/details/'+scope.row.id">查看</router-link>
</template>

企业详情

(1)配置路由

在 /src/module-saas-clients/router/index.js 添加新的子路由配置

 {
       path: 'details/:id',
       name: 'saas-clients-details',
       component: _import('saas-clients/pages/sass-details'),
       meta: {title: 'SaaS企业详情', icon: 'international', noCache: false}
     }

(2)定义公共组件在/src/module-saas-clients/components/下创建公共的组件页面enterprise-info.vue

<template>
  <div class="boxInfo">
    <!-- 表单内容 -->
    <div class="formInfo">
      <div>
        <div class="boxMain">
          <el-form ref="form" :model="formData" label-width="215px" labelposition="right">
              <el-form-item class="formInfo" label="公司名称:">
                <el-input v-model="formData.name" class="inputW" disabled></el-input>
              </el-form-item>
              <el-form-item class="formInfo" label="公司地区:">
                <el-input v-model="formData.companyArea" class="inputW" disabled></elinput>
              </el-form-item>
              <el-form-item class="formInfo" label="公司地址:">
                <el-input v-model="formData.companyAddress" class="inputW" disabled>
</el-input>
              </el-form-item>
              <el-form-item class="formInfo" label="审核状态:">
                <el-input v-model="formData.auditState" class="inputW" disabled></elinput>
              </el-form-item>
              <el-form-item class="formInfo" label="营业执照:">
                <span v-for="item in fileList" :key='item.id' class="fileImg">
                  <img :src="item.url">
                </span>
              </el-form-item>
              <el-form-item class="formInfo" label="法人代表:">
                <el-input v-model="formData.legalRepresentative" class="inputW"
disabled></el-input>
              </el-form-item>
              <el-form-item class="formInfo" label="公司电话:">
                <el-input v-model="formData.companyPhone" class="inputW" disabled></elinput>
              </el-form-item>
              <el-form-item class="formInfo" label="邮箱:">
                <el-input v-model="formData.mailbox" class="inputW" disabled></elinput>
              </el-form-item>
              <el-form-item class="formInfo" label="公司规模:">
                <el-input v-model="formData.companySize" class="inputW" disabled></elinput>
              </el-form-item>
              <el-form-item class="formInfo" label="所属行业:">
                <el-input v-model="formData.industry" class="inputW" disabled></elinput>
              </el-form-item>
              <el-form-item class="formInfo" label="备注:">
                <el-input type="textarea" v-model="formData.remarks" class="inputW">
</el-input>
              </el-form-item>
          </el-form>
          <div slot="footer" class="dialog-footer">
            <el-button type="primary" @click="handleSub('1')">审核</el-button>
            <el-button @click="handleSub('2')">拒绝</el-button>
            
          </div>
        </div>
      </div>
    </div>
    </div>
</template>
<script>
import { auditDetail } from '@/api/base/sassClients'
import { imgDownload } from '@/api/base/baseApi'
var _this = null
export default {
  name: 'userInfo',
  components: {},
  props: ['formData'],
  data() {
    return {
      fileList: []
   }
 },
  methods: {
    // 业务方法
    // 界面交互
    handleSub(state) {
      auditDetail({
        id: this.formData.id,
        remarks: this.formData.remarks,
        state: state
     }).then(() => {
        if (state === '1') {
          this.$message.success('恭喜你,审核成功!')
       }
        if (state === '2') {
          this.$message.success('已拒绝审核!')
       }
        this.$emit('getObjInfo', this.formData)
     })
   },
    // 图片 blob 流转化为可用 src
    imgHandle(obj) {
      return window.URL.createObjectURL(obj)
   },
    // 图片下载
    fillDownload(fid) {
   }
 },
  // 挂载结束
  mounted: function() {},
  // 创建完毕状态
  created: function() {
    _this = this
 },
  // 组件更新
  updated: function() {
    // this.imgDownInfo()
    if (
      this.formData.businessLicense !== null
   ) {
      this.fillDownload(this.formData.businessLicense)
   }
 }
}
</script>
<style rel="stylesheet/scss" lang="scss">
</style>
<style rel="stylesheet/scss" lang="scss" scoped>
.fileImg{
  img{
    width:20%;
 }
}
</style>

(3)完成详情展示在在/src/module-saas-clients/pages/下创建企业详情视图details.vue

<template>
  <div class="dashboard-container">
    <div class="app-container">
      <el-card shadow="never">
            <el-tabs v-model="activeName">
                <el-tab-pane label="企业信息" name="first">
                  <!--form表单
                      model : 双向绑定的数据对象
                  -->
                  <el-form ref="form" :model="formData" label-width="200px">
                    <el-form-item label="企业名称" >
                      <el-input v-model="formData.name" style="width:250px" disabled>
</el-input>
                    </el-form-item>
                    <el-form-item label="公司地址">
                      <el-input v-model="formData.companyAddress"  style="width:250px"
disabled></el-input>
                    </el-form-item>
                    <el-form-item label="公司电话">
                      <el-input v-model="formData.companyPhone"  style="width:250px"
disabled></el-input>
                    </el-form-item>
                    <el-form-item label="邮箱">
                      <el-input v-model="formData.mailbox"  style="width:250px"
disabled></el-input>
                    </el-form-item>
                    <el-form-item label="备注">
                      <el-input v-model="formData.remark"  style="width:250px" ></elinput>
                    </el-form-item>
                    <el-form-item>
                      <el-button type="primary">审核</el-button>
                      <el-button>拒绝</el-button>
                    </el-form-item>
                  </el-form>
                </el-tab-pane>
                <el-tab-pane label="账户信息" name="second">账户信息</el-tab-pane>
                <el-tab-pane label="交易记录" name="third">交易记录</el-tab-pane>
            </el-tabs>
      </el-card>
     </div>
  </div>
</template>
<script>
import {detail} from '@/api/base/saasClient'
export default {
  name: 'saas-clients-detail',
  data () {
    return {
        activeName: 'first',
        formData:{}
   }
 },
  methods: {
    detail(id) {
      detail({id:id}).then(res => {
        this.formData = res.data.data
        console.log(id)
        console.log(this.formData)
     })
   }
 },
  // 创建完毕状态
  created() {
    var id = this.$route.params.id
    this.detail(id);
 }
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.alert {
  margin: 10px 0px 0px 0px;
}
.pagination {
  margin-top: 10px;
  text-align: right;
}
</style>

骚戴理解: activeName: 'first'是默认加载first这个tab,如下所示

this.$route.params.id表示获取当前页面url路由参数中为"id"的值,并将其赋值给变量“id”。

具体来说,假设url为example.com/page/123,那么在vue.js应用程序中,如果当前页面的路由配置为{ path: '/page/:id', component: mycomponent },则this.$route.params.id的值将为"123"。因此,以上代码将把"123"赋值给变量“id”。

接口对接

(1)启动企业微服务服务

(2)在 config/dev.env.js 中配置请求地址

'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
  NODE_ENV: '"development"',
  BASE_API: '"http://localhost:9001/"'
})

骚戴理解:如果要前后端联调的话就需要把src\mock\index.js路径下的mock给禁用掉,然后修改上面的BASE_API: '"http://localhost:9001/"'为后端端口的地址,这样请求才会走后端

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

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

相关文章

【密码算法 之六】CCM 浅析

CCM模式浅析 1. 综述2. 加密2.1 前置条件&#xff08;Prerequisites&#xff09;2.2 输入&#xff08;Input&#xff09;2.3 输出&#xff08;Output&#xff09;2.4 加密流程&#xff08;Steps&#xff09; 3. 解密3.1 前置条件&#xff08;Prerequisites&#xff09;3.2 输入…

Orangepi Zero2 全志H616简介

为什么学 学习目标依然是Linux 系统 &#xff0c;平台是 ARM 架构 蜂巢快递柜&#xff0c;配送机器人&#xff0c;这些应用场景用C51,STM32单片机无法实现 第三方介入库的局限性&#xff0c;比如刷脸支付和公交车收费设备需要集成支付宝SDK&#xff0c;提供的libalipay.so 是…

【VM服务管家】VM4.0平台SDK_2.2 模块API类

目录 2.2.1 方案保存&#xff1a;方案高速保存的方法2.2.2 Group模块&#xff1a;Group输入输出图像数据的方法2.2.3 模块操作类&#xff1a;设置输入图像、参数和ROI的方法2.2.4 图像源&#xff1a;通过图像源模块接口设置图像输入的方法2.2.5 图像源&#xff1a;通过SDK传入相…

go 语言环境安装(Windows 系统下安装)

go 语言官网:The Go Programming Language 下载 go 安装包的网址&#xff1a;All releases - The Go Programming Language go 支持很多种操作系统 Windows 系统下 - 安装和配置SDK 一、SDK 介绍 SDK 的全称是 Software Development Kit &#xff0c;即 软件开发工具包 二…

STM32物联网实战开发(1)——全新的程序框架

现在STM32公司主推的是HAL库的开发&#xff0c;标准库已经不再更新。通过STM32cubeMX的图形界面生成代码非常的方便。 一、程序框架的构想 1、STM32cubeMX 生成的代码与添加的应用代码分离&#xff1b; 2、利用 STM32cubeMX 重新生成代码&#xff0c;不影响应用代码&#xf…

zabbix配置钉钉告警(附含钉钉告警脚本 · 实战亲测无任何问题)

&#x1f341;博主简介 &#x1f3c5;云计算领域优质创作者   &#x1f3c5;华为云开发者社区专家博主   &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社区&#xff1a;运维交流社区 欢迎大家的加入&#xff01; 文章目录 钉钉上操作&#xff08;钉钉告警以关…

密码学新进展:基于同态加密的数据保护是否会成为未来的趋势?

第一章&#xff1a;引言 在今天这个数字时代&#xff0c;数据的重要性不断增加&#xff0c;已经成为了现代社会最宝贵的财富之一。各种机构和企业在日常运营中都会处理大量的数据&#xff0c;包括用户信息、财务数据、产品销售数据等。这些数据的安全性非常重要&#xff0c;因…

2.5 定点运算器的组成

学习目标&#xff1a; 具体包括以下几点&#xff1a; 了解定点运算器的基本概念和分类&#xff0c;包括定点运算器的分类、常见的定点运算器类型等&#xff1b;学习定点运算器的基本组成部分&#xff0c;包括输入/输出接口、寄存器、算术逻辑单元(ALU)、控制单元等&#xff0…

VS Code C++ 输出窗口中文乱码问题解决

VS Code C 输出窗口中文乱码问题解决 系统cmd终端乱码 的情况&#xff1a;原因解决方法&#xff1a;&#xff08;仅针对cmd终端输出的情况&#xff09;方法一&#xff1a;更改代码文件的编码方法二 &#xff1a;更改cmd默认终端的编码方式 系统cmd终端乱码 的情况&#xff1a; …

2023年的深度学习入门指南(10) = 前端同学如何进行chatgpt开发

2023年的深度学习入门指南(10) 前端同学如何进行chatgpt开发 在第二篇&#xff0c;我们使用openai的python库封装&#xff0c;搞得它有点像之前学习的PyTorch一样的库。这一节我们专门给它正下名&#xff0c;前端就是字面意义上的前端。 给gpt4写前端 下面我们写一个最土的…

“BIM+智慧工地”精准“数字化”变身智慧工程“管家”

用手机对着满载钢筋的卡车拍照&#xff0c;手指选定一下钢筋范围&#xff0c;几秒后&#xff0c;屏幕就能迅速识别车上有多少根钢筋——这是建筑产业数字化管理智慧工程的应用领域之一。 投资1.78亿元建设的贵州民航产教融合实训基地是集实践教学、社会培训、企业生产保障和科研…

学历与就业:我对“孔乙已长衫”现象的思考

一、你认为社会对于学历和职业之间的关系认知是怎样的&#xff1f; 在当前的社会中&#xff0c;学历往往被看作是一个人能否获得好工作的重要标准。许多用人单位更愿意录取拥有更高学历的求职者&#xff0c;因为他们通常具备更广阔的知识视野和更强的理论基础。然而&#xff0…

Presto之Left Join和Right Join的实现

一. 前言 我们知道&#xff0c;在Presto中&#xff0c;与inner join相比&#xff0c;left join会保留probe表&#xff08;左表&#xff09;的所有值&#xff0c;right join会保留build表&#xff08;右表&#xff09;的所有值。inner join的是实现在文章Presto之Hash Join 数据…

攻击者可以使用HTML和CSS隐藏“外部发件人”电子邮件警告

导语&#xff1a;研究人员近日证实&#xff0c;Microsoft Outlook等客户端向电子邮件收件人显示的“外部发件人”警告可能被发件人隐藏。 研究人员近日证实&#xff0c;Microsoft Outlook等客户端向电子邮件收件人显示的“外部发件人”警告可能被发件人隐藏。 事实证明&#x…

【数据结构】一篇带你彻底玩转 链表

文章目录 链表的概念及结构链表的分类链表接口的实现链表打印链表申请节点链表尾插链表头插链表尾删链表头删链表查找链表在指定位置之后插入数据链表删除指定位置之后的数据链表在指定位置之前插入数据链表删除指定位置之前的数据链表删除指定位置的数据链表的销毁 链表的概念…

总结835

学习目标&#xff1a; 4月&#xff08;复习完高数18讲内容&#xff0c;背诵21篇短文&#xff0c;熟词僻义300词基础词&#xff09; 学习内容&#xff1a; 暴力英语&#xff1a;熟练背诵《大独裁者》&#xff0c;最后默写。抄写今后要背诵的两篇文章。 高等数学&#xff1a;做…

机器视觉各开发语言对比以及选择

机器视觉主流开发语言主要有, 一.C#,占有率极高 市面主要以Halcon,visionpro,visionmaster,opencvsharp为主。 开发人员利用 C# 能够生成在 .NET 中运行的多种安全可靠的应用程序。 二.C++,Qt 市面主要以Halcon,visionpro,visionmaster,opencv为z主。 C++ 即已成为世界上…

Arduino学习笔记5

一.直流电机控制实验 1.源代码 int dianJiPin9;//定义数字9接口接电机驱动IN1的控制口void setup() {pinMode(dianJiPin,OUTPUT);//定义电机驱动IN1的控制口为输出接口 } void loop() {digitalWrite(dianJiPin,LOW);//关闭电机delay(1000);//延时digitalWrite(dianJiPin,HIGH…

基于protobuf构建grpc服务

一、protobuf介绍 protobuf是谷歌开源的一种数据格式&#xff0c;适合高性能&#xff0c;对响应速度有要求的数据传输场景。因为profobuf是二进制数据格式&#xff0c;需要编码和解码。数据本身不具有可读性。因此只能反序列化之后得到真正可读的数据。 优势&#xff1a; 序列…

【Unity-UGUI控件全面解析】| Text文本组件详解

🎬【Unity-UGUI控件全面解析】| Text文本组件详解一、组件介绍二、组件属性面板三、代码操作组件四、组件常用方法示例4.1 改变Text文本颜色4.2 文本换行问题4.3 空格自动换行问题4.4 逐字显示效果五、组件相关扩展使用5.1 文本描边组件(Outline)5.2 阴影组件(Shadow)5.3…