数据库设计与前端框架
数据库设计与前端框架
学习目标:
理解多租户的数据库设计方案
熟练使用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.jsmenus.jsusers.jspermissions.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 | 示例
每个模块所有的素材、页面、组件、路由、数据,都是独立的,方便大型项目管理, 在实际项目中会有很多子业务项目,它们之间的关系是平行的、低耦合、互不依赖。
构造模拟数据
- 在/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"}}}
}
- 配置模拟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-switchv-model="scope.row.state"inactive-value="0" active-value="1"disabledactive-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 = 100for (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-switchv-model="scope.row.state"inactive-value="0" active-value="1"disabledactive-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 流转化为可用 srcimgHandle(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.dataconsole.log(id)console.log(this.formData)})}},// 创建完毕状态created() {var id = this.$route.params.idthis.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/"'为后端端口的地址,这样请求才会走后端
相关文章:
数据库设计与前端框架
数据库设计与前端框架 学习目标: 理解多租户的数据库设计方案 熟练使用PowerDesigner构建数据库模型理解前端工程的基本架构和执行流程 完成前端工程企业模块开发 多租户SaaS平台的数据库方案 多租户是什么 多租户技术(Multi-TenancyTechnology&a…...
技术探秘:揭秘Bean Factory与FactoryBean的区别!
大家好,我是小米,一个热衷于技术分享的29岁小编。今天,我们来聊一聊在Spring框架中常用的两个概念:beanFactory和FactoryBean。它们虽然看似相似,但实际上有着不同的用途和作用。让我们一起来揭开它们的神秘面纱吧&…...
MD-MTSP:遗传算法GA求解多仓库多旅行商问题(提供MATLAB代码,可以修改旅行商个数及起点)
一、多仓库多旅行商问题 多旅行商问题(Multiple Traveling Salesman Problem, MTSP)是著名的旅行商问题(Traveling Salesman Problem, TSP)的延伸,多旅行商问题定义为:给定一个𝑛座城市的城市集…...
技术面试的终极指南:助你取得成功的关键步骤
背景 技术面试是许多求职者最关键的一环,因为它评估了你在特定领域的知识和技能。无论你是刚毕业的大学应届生,还是有多年工作经验的职场老兵,准备充分是成功面试的关键。 这篇文章将提供一系列关键步骤,帮助你充分准备和展现自己…...
Nautilus Chain 测试网第二阶段,推出忠诚度计划及广泛空投
随着更多的公链底层面向市场,通过参与早期测试在主网上线后获得激励成为了行业的一个热点话题,在 Apots、Arbitrum One、Optimism等陆续发放了测试空投后,以 Layer3为主要特性的 Nautilus Chain 也在前不久明确表示将会有空投,引发…...
Python爬虫(三):BeautifulSoup库
BeautifulSoup 是一个可以从 HTML 或 XML 文件中提取数据的 Python 库,它能够将 HTML 或 XML 转化为可定位的树形结构,并提供了导航、查找、修改功能,它会自动将输入文档转换为 Unicode 编码,输出文档转换为 UTF-8 编码。 Beauti…...
Python使用CV2库捕获、播放和保存摄像头视频
Python使用CV2库捕获、播放和保存摄像头视频 特别提示:CV2指的是OpenCV2(Open Source Computer Vision Library),安装的时候是 opencv_python,但在导入的时候采用 import cv2。 若想使用cv2库必须先安装,P…...
[数据结构 -- C语言] 栈(Stack)
目录 1、栈 1.1 栈的概念及结构 2、栈的实现 2.1 接口 3、接口的实现 3.1 初始化 3.2 入栈/压栈 3.3 出栈 3.4 获取栈顶元素 3.5 获取栈中有效元素个数 3.6.1 bool 类型接口 3.6.2 int 类型接口 3.7 销毁栈 4、完整代码 5、功能测试 1、栈 1.1 栈的概念及结构 …...
【我的C++入门之旅】(上)
前言 C的发展史 1979年,贝尔实验室的Bjarne等人试图分析unix内核的时候,试图将内核模块化,但是发现C语言有很多的不足之处,于是在C语言的基础上进行扩展,增加了类的机制,完成了一个可以运行的预处理程序&…...
dcdc降压电路原理及仿真
在之前的文章 DCDC 降压芯片基本原理及选型主要参数介绍 中已经大致讲解了dcdc降压电路的工作原理,今天再结合仿真将buck电路工作过程讲一讲。 基本拓扑 上图为buck电路的基本拓扑结构,开关打到1,电感充电;开关打到0,…...
搭建Redis主从集群+哨兵+代理predixy
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、Redis是什么?二、搭建Redis集群步骤1.环境和版本2.Redis 安装部署3.主从同步配置4.哨兵模式配置5.代理predixy配置 总结 前言 提示:…...
Syncthing文件同步 - 免费搭建开源的文件自动同步服务器并公网远程访问【私人云盘】
文章目录 1. 前言2. Syncthing网站搭建2.1 Syncthing下载和安装2.2 Syncthing网页测试2.3 注册安装cpolar内网穿透 3. 本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4. 公网访问测试5. 结语 1. 前言 在数据爆炸的当下,每天都会产生海量的数据,这些…...
SQL——索引
💡 索引 在关系型数据库中,索引是一种单独的、物理上的对数据库表中的一列或多列的值进行排序的一种存储结构,他是某个表中的一列或着若干列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单(类似于图书目录&#x…...
Java代码组成部分
一、构造函数与默认构造函数 构造函数,是一种特殊方法。主要用来在创建对象时初始化对象,即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。 /** * 矩形 */ class Rectangle {/*** 构造函数*/public Rectangle(int leng…...
vue2和vue3有啥区别,vue3的优点有哪些?
Vue.js 是一种流行的 JavaScript 框架,用于开发现代 Web 应用程序。Vue.js 具有简单易用、高效和灵活等特点,能够极大地提高开发效率并改进用户体验。Vue.js 一直在不断更新和改进,它的最新版本是 Vue 3。 在本文中,我们将探讨 V…...
就业内推 | 上市公司招网工,最高25k*14薪,六险一金
01 锐捷网络 招聘岗位:网络工程师 职责描述: 1、承接本产品线(无线或数通)所有咨询、故障、网络变更等业务,响应内外部客户的业务响应需求,需要值班。 2、同时作为产品线技术力的核心,需要负责…...
低代码让开发变得不再复杂
文章目录 前言低代码 VS 传统开发为什么选择IVX?平台比对总结 前言 在数字化的时代背景下,企业都面临巨大的数字化转型的挑战。为了应对这样的挑战,企业软件开发工具和平台也在不断革新和发展。低代码开发平台随之应运而生,成为了…...
【前端客栈】使用CSS实现畅销书排行榜页面
📬📫hello,各位小伙伴们,我是小浪。大家都知道,我最近是在更新各大厂的软件测试开发的面试真题,也是得到了很大的反馈和好评,几位小伙伴也是成功找到了测开的实习,非常不错。如果能前…...
【周末闲谈】超越ChatGPT?科大讯飞星火认知大模型
个人主页:【😊个人主页】 系列专栏:【❤️周末闲谈】 ✨第一周 二进制VS三进制 ✨第二周 文心一言,模仿还是超越? ✨第二周 畅想AR 文章目录 前言星火名字的由来科大讯飞星火落地应用演示赶超ChatGPT的底气在哪里?“硬…...
第N2周:中文文本分类-Pytorch实现
目录 一、前言二、准备工作三、数据预处理1.加载数据2.构建词典3.生成数据批次和迭代器 三、模型构建1. 搭建模型2. 初始化模型3. 定义训练与评估函数 四、训练模型1. 拆分数据集并运行模型 一、前言 🍨 本文为🔗365天深度学习训练营 中的学习记录博客 …...
日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...
《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》
在注意力分散、内容高度同质化的时代,情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现,消费者对内容的“有感”程度,正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中࿰…...
五年级数学知识边界总结思考-下册
目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解:由来、作用与意义**一、知识点核心内容****二、知识点的由来:从生活实践到数学抽象****三、知识的作用:解决实际问题的工具****四、学习的意义:培养核心素养…...
【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...
【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习
禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...
云原生玩法三问:构建自定义开发环境
云原生玩法三问:构建自定义开发环境 引言 临时运维一个古董项目,无文档,无环境,无交接人,俗称三无。 运行设备的环境老,本地环境版本高,ssh不过去。正好最近对 腾讯出品的云原生 cnb 感兴趣&…...
Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?
Redis 的发布订阅(Pub/Sub)模式与专业的 MQ(Message Queue)如 Kafka、RabbitMQ 进行比较,核心的权衡点在于:简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...
安宝特案例丨Vuzix AR智能眼镜集成专业软件,助力卢森堡医院药房转型,赢得辉瑞创新奖
在Vuzix M400 AR智能眼镜的助力下,卢森堡罗伯特舒曼医院(the Robert Schuman Hospitals, HRS)凭借在无菌制剂生产流程中引入增强现实技术(AR)创新项目,荣获了2024年6月7日由卢森堡医院药剂师协会࿰…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
