赞
踩
第一章=>
1、ElementUI
2、布局与主体
3、增删改查
4、路由
5、播放组件
第二章=>
6、分页
7、代码生成
8、导入导出
9、用户登录
10、注册与异常处理
第三章=>
11、JWT
12、文件上传
13、权限管理
14、Redis
第四章=>
15、Echats
16、百度地图
17、Markdwon
18、WangEditor
第五章=>
19、前台页面
20、视频播放
21、多级评论
22、支付宝
第六章=>
23、购物车
24、借书管理
25、百度地图
26、聊天室
第七章=>
27、考试系统
28、邮箱登录
29、活动预约
30、电商系统
****************************************************************************************************************************************************************************
- 1、ElementUI
- 【1】软件安装,开发环境配置。
- JDK1.8、mysql5.7、node、navicat、idea2021
- *************************************************************************
- 【2】安装vue-cli
- npm i -g @vue/cli
- *************************************************************************
- vue -V
- *************************************************************************
- vue create white
- *************************************************************************
- 用white记录模板创建即可。
- *************************************************************************
- npm config set registry https://registry.npmmirror.com/
- npm config get registry
- *************************************************************************
- 用WebStorm打开
- *************************************************************************
- 【3】项目结构
- http://localhost:8080/
- App.vue通过路由展示了Home与About页面
- *************************************************************************
- 【4】安装element
- npm i element-ui -S
- *************************************************************************使用
- <el-button type="primary">主要点击</el-button>
****************************************************************************************************************************************************************************
- 2、布局与主体
- 【1】安装less
- npm i less-loader less -S
- ************************************************************************样式调整而已
- 链接:https://pan.baidu.com/s/15YwRICJKS7aCiBqWcYeAKw
- 提取码:iwr9
- 完成主页面样式设计
****************************************************************************************************************************************************************************
- 5、Springboot框架搭建
- 【1】配置Lombok、Spring web、MyBatis Framework、MySQL Driver
- 【2】配置阿里云下载
- <!--配置阿里云仓库下载-->
- <repositories>
- <repository>
- <id>nexus-aliyun</id>
- <name>nexus-aliyun</name>
- <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
- <releases>
- <enabled>true</enabled>
- </releases>
- <snapshots>
- <enabled>false</enabled>
- </snapshots>
- </repository>
- </repositories>
- <pluginRepositories>
- <pluginRepository>
- <id>public</id>
- <name>nexus-aliyun</name>
- <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
- <releases>
- <enabled>true</enabled>
- </releases>
- <snapshots>
- <enabled>false</enabled>
- </snapshots>
- </pluginRepository>
- </pluginRepositories>
- 【3】启动报错
- Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.
- *****************************************************************************
- #数据库驱动
- spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
- #东八区+数据库连接配置
- spring.datasource.url=jdbc:mysql://wdfgdzx.top:3306/black?serverTimezone=GMT2b%8
- spring.datasource.username=root
- spring.datasource.password=s19911009!
- *****************************************************************************
- black utf8mb4 utf8mb4_unicode_ci两个配置
- *****************************************************************************
- #0代表黑,1代表白。前端是8001
- server.port=8000
****************************************************************************************************************************************************************************
- 6、Mybatis实现数据增删改查
- 【1】动态sql
- 【2】File -> Settings -> Editor -> File encodings --> 设置properties的编码
- 【3】mybatis配置
- #指定mybatis配置
- mybatis.mapper-locations=classpath:mapper/*.xml
- *****************************************************************************properties
- #0代表黑,1代表白,前端是8001
- server.port=8000
- #数据库驱动
- spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
- #数据库配置
- spring.datasource.url=jdbc:mysql://wdfgdzx.top:3306/black?serverTimezone=GMT%2b8&allowMultiQueries=true&useAffectedRows=true
- spring.datasource.username=root
- spring.datasource.password=s19911009!
- #指定mybatis配置
- mybatis.mapper-locations=classpath:mapper/*.xml
- 【4】详细代码
- 链接:https://pan.baidu.com/s/1Jz80if48Z5pannN_TOAUTQ
- 提取码:fq9j
****************************************************************************************************************************************************************************
- 8、分页查询实现
- 【1】控制器的路基
- @PostMapping("list_page")
- public HashMap list_page(@RequestBody User user) {
- Integer totalNum = userMapper.total();
- MyUtils.selectByPageManage(totalNum, user);
- HashMap hashMap = new HashMap();
- hashMap.put("total", totalNum);
- hashMap.put("data", userMapper.list_page(user));
- return hashMap;
- }
- 【2】跨域问题的处理
- package com.black.config;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.web.cors.CorsConfiguration;
- import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
- import org.springframework.web.filter.CorsFilter;
- @Configuration
- public class Cross {
- // 当前跨域请求最大有效时长,默认1天
- private static final long MAX_DAY = 24 * 60 * 60;
-
- @Bean
- public CorsFilter buildCorsFilter() {
- CorsConfiguration corsConfiguration = new CorsConfiguration();
- corsConfiguration.addAllowedOrigin("*"); // 访问源地址
- corsConfiguration.addAllowedHeader("*");// 访问源请求头
- corsConfiguration.addAllowedMethod("*"); // 访问源请求方法
- corsConfiguration.setMaxAge(MAX_DAY);// 设置最大有效时长
-
- UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
- urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration); // 对接口配置跨域设置
- return new CorsFilter(urlBasedCorsConfigurationSource);
- }
- }
- 【3】配置axios
- npm i axios -S
- ************************************************************************
- // main.js
- import axios from "axios"; // 导入axios
-
- Vue.prototype.$http = axios //在Vue的原型上添加一个$http属性,该属性保存了axios
- axios.defaults.baseURL = 'http://localhost:8000'
****************************************************************************************************************************************************************************
- 9、MybatisPlus与SwaggerUI
- 【1】pom配置
- <!--mybatis-plus-->
- <dependency>
- <groupId>com.baomidou</groupId>
- <artifactId>mybatis-plus-boot-starter</artifactId>
- <version>3.5.1</version>
- </dependency>
- 【2】MybaitsPlus配置
- package com.black.config;
-
- import com.baomidou.mybatisplus.annotation.DbType;
- import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
- import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
- import org.mybatis.spring.annotation.MapperScan;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
-
- @Configuration
- @MapperScan("com.black.mapper") // !!!!!!!!!!!!!!
- public class MybatisPlus {
- @Bean
- public MybatisPlusInterceptor buildMybatisPlusInterceptor() {
- MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
- mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
- return mybatisPlusInterceptor;
- }
- }
- 【3】简化了UserMapper.interface与UserMapper.xml(只需要写复杂的即可,简单的MP代理了)
- package com.black.mapper;
-
- import com.baomidou.mybatisplus.core.mapper.BaseMapper;
- import com.black.pojo.User;
- import org.apache.ibatis.annotations.Param;
-
- import java.util.List;
-
- public interface UserMapper extends BaseMapper<User> { // !!!!!!!!!!
- Integer total(@Param("user") User user); //总条数
-
- List<User> list_page(@Param("user") User user); //分页数据
- }
- ******************************************************************************
- MybatisPlus让数据库操作变得更简单...
- ******************************************************************************
- @TableId(value = "id", type = IdType.AUTO) // 这个会影响是否按顺序增加!!!!!!!
- @TableId(type = IdType.AUTO) // 当代码里的字段与数据库不同时,可以通过value="xxx"对应数据库字段
- type = IdType.AUTO// 这个影响自增,所以很重要
- 【4】SwaggerUI简化postman的测试
- <!-- swagger接口文档 -->
- <dependency>
- <groupId>io.springfox</groupId>
- <artifactId>springfox-boot-starter</artifactId>
- <version>3.0.0</version>
- </dependency>
- ******************************************************************************
- # properties
- # 引入swagger3.0时加入的配置 http://localhost:8000/swagger-ui/index.html
- spring.mvc.pathmatch.matching-strategy=ant_path_matcher
- ******************************************************************************
- package com.black.config;
-
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import springfox.documentation.builders.ApiInfoBuilder;
- import springfox.documentation.builders.PathSelectors;
- import springfox.documentation.builders.RequestHandlerSelectors;
- import springfox.documentation.service.ApiInfo;
- import springfox.documentation.service.Contact;
- import springfox.documentation.spi.DocumentationType;
- import springfox.documentation.spring.web.plugins.Docket;
- import springfox.documentation.swagger2.annotations.EnableSwagger2;
-
- @Configuration
- @EnableSwagger2
- public class Swagger {
- @Bean
- public Docket restAPI() {
- return new Docket(DocumentationType.SWAGGER_2)
- .groupName("HIT的接口")
- .apiInfo(APIInfo())
- .useDefaultResponseMessages(true)
- .forCodeGeneration(false)
- .select()
- .apis(RequestHandlerSelectors.basePackage("com.black.controller")) // !!!!!!!!!!!!
- .paths(PathSelectors.any())
- .build();
- }
-
- private ApiInfo APIInfo() {
- return new ApiInfoBuilder()
- .title("RESTful")
- .description("http://wdfgdzx.top:8001/")
- .termsOfServiceUrl("http://wdfgdzx.top:8001/")
- .contact(new Contact("HIT", "http://wdfgdzx.top:8001/", "wdfgdzx@163.com"))
- .version("V1.0")
- .build();
- }
- }
- ******************************************************************************看自己收藏的CSDN一样的
- http://localhost:8000/swagger-ui/index.html
****************************************************************************************************************************************************************************
- 10、VUE实现增删改查
- 【1】配置xml SQL查询
- Editor->Inspections->SQL->No data sources configured 和 SQL dialect detection
- 看收藏的CSDN帖子
- ***************************************************************************
- 【2】安装配置axios
- npm i axios -S
- ***************************************************************************
- import axios from "axios";// 导入axios
-
- /*1、配置后台请求接口*/
- const $http = axios.create({ //在Vue的原型上添加一个$http属性,该属性保存了axios
- baseURL: "http://localhost:8000",
- timeout: 5000
- })
-
- /*2、请求拦截器,对发送请求钱做一些处理,比如统一加token/对请求参数统一加密等...*/
- $http.interceptors.request.use(config => {
- config.headers['Content-Type'] = "application/json;charset=utf-8" // 定义网络文件的类型和网页的编码,决定文件接收方将以什么形式、什么编码读取这个文件
- return config
- })
-
- /*3、响应拦截器,在接口响应后统一处理结果*/
- $http.interceptors.response.use(res => {
- return res;
- })
-
- export default $http
- ***************************************************************************
- import $http from './util/axios.js' // 引入定义的axios工具
- Vue.prototype.$http = $http //引入util/axios.js暴露的$http来使用
- ***************************************************************************
- 【3】行数据脏改
- /*修改窗口*/
- updateWindow(row) {
- this.userFormFlag = true
- this.userForm = JSON.parse(JSON.stringify(row)) // !!!!!!!!!!!!
- },
****************************************************************************************************************************************************************************
- 11、SpringBoot代码生成
- 【1】引入依赖
- <!--代码生产-->
- <dependency>
- <groupId>org.apache.velocity</groupId>
- <artifactId>velocity</artifactId>
- <version>1.7</version>
- </dependency>
- <dependency>
- <groupId>com.baomidou</groupId>
- <artifactId>mybatis-plus-generator</artifactId>
- <version>3.5.1</version>
- </dependency>
- **********************************************************************我只想说打扰了,哥哥
- 这么简单的代码,不至于生成,还覆盖了我原有的代码,过!!!!
- 不如直接复制+替换关键字不香吗???
****************************************************************************************************************************************************************************
- 12、VUE使用路由展示左侧栏
- 【1】props: ['fff_top_foldData', 'fff_top_foldClick'],/*说明props还可以接受函数,我日尼玛哦*/
- 【2】路由守卫
- *************************************************************************使用vuex解决导航路径问题
- npm i vuex -S --force
- *************************************************************************myVuex.js
- import Vue from 'vue'
- import Vuex from 'vuex'
-
- Vue.use(Vuex)
-
- const myVuex = new Vuex.Store({
- state: {
- currentPathName: '' // 当前路径名称
- },
- mutations: {
- setStateCurrentPathName(state) {
- state.currentPathName = localStorage.getItem("currentPathName")
- }
- }
- })
-
- export default myVuex
- *************************************************************************main.js
- import Vue from 'vue'
- import App from './App.vue'
- import router from './router/router.js'
- import ElementUI from 'element-ui'
- import 'element-ui/lib/theme-chalk/index.css'
- import './assets/main.css' // 全局样式表
- import $http from './util/axios.js' // 引入定义的axios工具
- import myVuex from "@/store/myVuex.js"; // 引入vuex管理全局存储
-
-
- Vue.prototype.$http = $http //引入util/axios.js暴露的$http来使用
-
- Vue.config.productionTip = false
- Vue.use(ElementUI, {size: 'mini'}) // 挂载elementUI
-
- new Vue({
- router,
- myVuex,
- render: h => h(App)
- }).$mount('#app')
- *************************************************************************router.js
- import Vue from 'vue'
- import VueRouter from 'vue-router'
- import Manage from "@/views/Manage.vue";
- import User from "@/views/User.vue";
- import Home from "@/views/Home.vue";
- import myVuex from "@/store/myVuex";
-
- Vue.use(VueRouter)
-
- const routes = [
- {
- path: '/',
- name: 'manage',
- component: Manage,
- redirect: "/home",
- children: [
- {path: 'user', name: '用户管理', component: User},
- {path: 'home', name: '首页', component: Home}
- ]
- },
- {
- path: '/about',
- name: 'about',
- // route level code-splitting
- // this generates a separate chunk (about.[hash].js) for this route
- // which is lazy-loaded when the route is visited.
- component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
- }
- ]
-
- const router = new VueRouter({
- mode: 'history',
- base: process.env.BASE_URL,
- routes
- })
-
- /*路由守卫*/
- router.beforeEach((to, from, next) => {
- // console.log(to)
- localStorage.setItem("setStateCurrentPathName", to.name) // 设置即将访问的路由名称,为了在Header中使用
- myVuex.commit("setPath") // 触发myVuex里的数据更新
- next()
- })
-
- export default router
- *************************************************************************Top.vue
****************************************************************************************************************************************************************************
- 13、SpringBoot实现导入导出
- 【1】说明学只能是学。但是和军队管理的用还差的很多,还是要多用,做项目来成长。
- @GetMapping("/list_export")
- public String list_export(HttpServletResponse httpServletResponse) throws Exception {
- //1、从数据查询
- List<User> userList = userMapper.selectList(null);
- ExcelWriter excelWriter = ExcelUtil.getWriter(true);
- //2、自定义标题
- excelWriter.addHeaderAlias("id", "序号");
- excelWriter.addHeaderAlias("name", "姓名");
- excelWriter.addHeaderAlias("nick", "昵称");
- excelWriter.addHeaderAlias("email", "邮箱");
- excelWriter.addHeaderAlias("phone", "手机");
- excelWriter.addHeaderAlias("address", "地址");
- excelWriter.setOnlyAlias(true); // 仅写出指定字段
- excelWriter.write(userList, true);
- //3、设置浏览器响应的格式
- httpServletResponse.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
- String fileName = URLEncoder.encode("用户信息", "UTF-8");
- httpServletResponse.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");
- //4、拿到输出流并刷新
- ServletOutputStream servletOutputStream = httpServletResponse.getOutputStream();
- excelWriter.flush(servletOutputStream, true);
- servletOutputStream.close();
- excelWriter.close();
- return "导出成功";
- }
- 【2】学用需要结合,这样才真正掌握某一项技能。军队管理!!!!!
- 【3】批量插入的mybatis的写法,重要!!!!!!!!!!!!!!!!!!!!!
- <insert id="list_insert" parameterType="java.util.List">
- 【4】导入的第二种方式
- ********************************************************************忽略表头中文
- List<CsvRow> rowList = data.getRows(); // 忽略表头文件,直接读取内容
- rowList.remove(0);
- row.get(5)
- ********************************************************************
- 写死了,唯一的优点是不用管表头名字
- 【5】前端调用
- ********************************************************************
- /*导出*/
- list_export() {
- window.open("http://localhost:8000/user/list_export")
- },
- ********************************************************************
- <el-upload style="display: inline-block"
- action="http://localhost:8000/user/list_import"
- name="multipartFile" // !!!!!!!!!!!!!多看文档
- :show-file-list="false"
- accept="xlsx"
- :on-success="onImportSuccess">
- <el-button type="primary" class="ml-5">导入<i class="el-icon-upload2"></i></el-button>
- </el-upload>
****************************************************************************************************************************************************************************
- 14、Springboot+VUE实现登录
- 【1】登录页的路由+布局
- 【2】后台写登录接口
- @RestController
- @RequestMapping("/big")
- public class BigController {
- @Resource
- UserMapper userMapper;
-
- @PostMapping("login")
- public String login(@RequestBody User user) {
- // 拿到用户输入的账号密码
- User existUser = userMapper.selectUserByNamePassword(user);
- if (existUser == null) {
- return "账号或密码错误"; // 好简单!!!!!!!!!!!
- }
- return "登录成功";
- }
- }
- *************************************************************************
- 400报错,如果后端要求的传参没有传递也会报错400。比如big/login要求传user,如果没传则报错400
- *************************************************************************
- <el-form :rules="ruleList" :model="user"><!--用来校验表单-->
- *************************************************************************
- <el-form-item prop="name"><!--必须el-form-item prop="xxx"才能生效-->
- 再次说明学以致用最重要!!!!!!!!!!!!!!!!!!!!!!!!
- *************************************************************************校验规则先通过才发送请求
- ref="userForm"
- *************************************************************************用到了引用ref
- loginClick() {
- this.$refs["userForm"].validate(valid => {
- if (valid) { // 表单校验合法
- this.$http.post("/big/login", this.user).then(res => {
- if (res.data !== "登录成功") {
- this.$message.error("用户名或密码错误")
- } else {
- this.$router.push("/")
- }
- })
- } else {
- return false
- }
- })
- }
- 【3】假如数据库存在脏数据,就是有重复的用户名+密码,怎么处理?
- 用List判断数据是否存在
- *************************************************************************
- 用try catch,捕获到异常然后再处理
****************************************************************************************************************************************************************************
- 15、Springboot+Vue实现注册与异常处理
- 【1】登录信息存储
- package com.black.util;
-
- // 常量类
- public class Constants {
- public static final String CODE_200 = "200"; // 成功
- public static final String CODE_400 = "400"; // 参数错误
- public static final String CODE_401 = "401"; // 权限不足
- public static final String CODE_500 = "500"; // 系统错误
- public static final String CODE_600 = "600"; // 其他错误
- }
- *******************************************************************************
- package com.black.util;
-
- import lombok.AllArgsConstructor;
- import lombok.Data;
- import lombok.NoArgsConstructor;
-
- // 返回结果包装类
- @Data
- @NoArgsConstructor
- @AllArgsConstructor
- public class Res {
- private String code;
- private String message;
- private Object object;
-
- // 成功的两个方法
- public static Res success() {
- return new Res(Constants.CODE_200, "", null);
- }
-
- public static Res success(Object object) {
- return new Res(Constants.CODE_200, "", object);
- }
-
- // 不成功的方法
- public static Res error() {
- return new Res(Constants.CODE_500, "系统错误", null);
- }
-
- public static Res error(String code, String message) {
- return new Res(code, message, null);
- }
- }
- 【2】全局异常处理,最终还是用到了Res,这个先保留了解下...
- 【3】前端的处理
- localStorage.setItem("user", JSON.stringify(res.data.object)) // 存储用户信息到浏览器
- *******************************************************************************
- user: localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")) : {}
- *******************************************************************************
- logoutClick() {
- this.$router.push("/login")
- localStorage.removeItem("user") // 移出浏览器用户信息
- this.$message.success("退出成功")
- }
- 【4】注册页面与接口的改造,统一返回封装的Res
****************************************************************************************************************************************************************************
- 16、Springboot使用JWT
- 【1】JWT全称是json web token
- 它将用户信息加密到token里,服务器不保存任何用户信息。
- 服务器通过使用保存的密匙验证token的正确性,只要正确即可通过验证。
- ***************************************************************************
- 优点:
- 简洁:通过URL POST参数或者在http header发送,数据量小,传输速度也快。
- ***************************************************************************
- 自包含,避免了多次的数据库查询。
- ***************************************************************************
- 因为token是以json加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持。
- ***************************************************************************
- 不需要在服务端保存会话信息,特别适用于分布式的---微服务---。
- ***************************************************************************
- 缺点:
- 无法作废已颁布的令牌;
- 不易于应对数据过期;
- 【2】组成,一个token是三个组成部分。
- header头部;载荷payload;签证singature。用.分割
- ***************************************************************************
- <!--JWT-->
- <dependency>
- <groupId>com.auth0</groupId>
- <artifactId>java-jwt</artifactId>
- <version>3.10.3</version>
- </dependency>
- ***************************************************************************
- package com.black.util;
-
- import cn.hutool.core.date.DateUtil;
- import com.auth0.jwt.JWT;
- import com.auth0.jwt.algorithms.Algorithm;
- import com.black.pojo.User;
-
- import java.util.Date;
-
- public class Token {
- public static String productToken(User user) {
- return JWT.create().withAudience(user.getId() + "") // user.id作为载荷
- .withExpiresAt(DateUtil.offsetHour(new Date(), 2)) //2小时后token过期
- .sign(Algorithm.HMAC256(user.getPassword())); // 以 password 作为 token 的密钥
- }
- }
- 【3】token的重要作用
- /*2、请求拦截器,对发送请求钱做一些处理,比如统一加token/对请求参数统一加密等...*/
- $http.interceptors.request.use(config => {
- config.headers['Content-Type'] = "application/json;charset=utf-8" // 定义网络文件的类型和网页的编码,决定文件接收方将以什么形式、什么编码读取这个文件
-
- // 从本地存储拿,设置请求头!!!!!!!!!!!!
- let user = localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")) : {}
- if (user) {
- config.headers['token'] = user.token; // 设置
- }
-
- return config
- })
- ***************************************************************************后台拦截器
- 权限验证不同过,前端提示
- /*3、响应拦截器,在接口响应后统一处理结果*/
- $http.interceptors.response.use(res => {
- //alert(JSON.stringify(res))
- if (res.data.code === "401") {
- elementUI.Message({
- message: res.data.message,
- type: "error"
- })
- }
- return res;
- })
- 【4】后台获取当前用户
- private static UserMapper staticUserMapper; // 因为@Resource不能定义为静态,这里是为了转存
- @Resource
- private UserMapper userMapper; // 因为有个静态方法引用了
-
- @PostConstruct
- public void setUserMapper() {
- staticUserMapper = userMapper;
- }
- ***************************************************************************
- // 获取当前用户
- public static User getNowUser() {
- try {
- HttpServletRequest httpServletRequest = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(); //拿到httpServletRequest
- String token = httpServletRequest.getHeader("token");//拿到token
- if (StrUtil.isNotBlank(token)) {
- String id = JWT.decode(token).getAudience().get(0); //拿到user.id
- return staticUserMapper.selectById(id);//拿到user
- }
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- }
- return null;
- }
- ***************************************************************************
- System.err.println(JWTInterceptor.getNowUser()); // 全局可以获取
- 任何方法,任何Controller去使用
****************************************************************************************************************************************************************************
- 17、文件上传
- 【1】swagger-ui的配置,在放行代码指定这些,JWT拦截里放行。牛批!!!!!
- "/swagger**/**",
- "/webjars/**",
- "/v2/**",
- "/doc.html"
- **************************************************************************文件操作都放行
- "/document/**"
- **************************************************************************上传不要@RequestBody
- public String upload(Document document) throws Exception
- **************************************************************************
- #上传文件大小指定(单个)
- spring.servlet.multipart.max-file-size=100MB
- **************************************************************************
- File uploadFile = new File(uploadDir + "/" + IdUtil.fastSimpleUUID() + "." + type);
- **************************************************************************
- 【2】同样的图片去重43分钟,利用md5值是否相同来比较,这个是自己想的解决办法,必须赞!!!!!!!!!!!!
- @PostMapping("/upload")
- public String upload(Document document) throws Exception {
- String originName = document.getMultipartFile().getOriginalFilename();
- String type = FileUtil.extName(originName);
- long size = document.getMultipartFile().getSize();
- // 1、存到磁盘、存储数据库
- File uploadDir = new File(MyUtils.getFinalPath());
- System.err.println(uploadDir);
- if (!uploadDir.exists()) {
- System.out.println(uploadDir.mkdir()); //不存在创建新目录
- }
- // 2、最终文件路径
- String fileUUID = IdUtil.fastSimpleUUID() + "." + type;
- String url = "http://localhost:8000/document/" + fileUUID;
- File uploadFile = new File(uploadDir + "/" + fileUUID);
- //3、获取md5
- if (!uploadFile.exists()) {
- document.getMultipartFile().transferTo(uploadFile); // 这时候才存磁盘!!!!!
- }
- String md5 = SecureUtil.md5(uploadFile);
- // 4、判断数据库里md5是否存在
- document.setMd5(md5);
- List<Document> documentList = documentMapper.md5_list(document); // 数据库查询到相同md5的对象
- System.out.println(documentList);
- if (documentList.size() != 0) {
- url = documentList.get(0).getUrl(); // 存在的话,url直接从已存在数据库信息拿到
- System.out.println(uploadFile.delete());// 删除本地存储的文件
- }
- // 5、存数据库
- Document insertDocument = new Document();
- insertDocument.setUrl(url);
- insertDocument.setName(originName);
- insertDocument.setType(type);
- insertDocument.setSize(size / 1024);
- insertDocument.setMd5(md5);
- documentMapper.insert(insertDocument);
- return url;
- }
- **************************************************************************
- 这里青戈处理了很久....我尼玛!!!!!!!!
- 而且青戈处理的不对....浪费良久后青戈处理对了...
- 还是谦虚点,还是有比我牛批的地方...
- 【3】上传图像,这个name很重要的!!!!!!!!!!!
- <!--头像-->
- <el-upload
- name="multipartFile"
- class="avatar-uploader"
- action="http://localhost:8000/document/upload"
- :show-file-list="false"
- :on-success="onSuccess">
- <img v-if="userForm.avatar" :src="userForm.avatar" class="avatar">
- <i v-else class="el-icon-plus avatar-uploader-icon"></i>
- </el-upload>
- 【4】处理保存后,左上角头像的更新问题(初步处理,没有一键触发!!!!)
- 子传父、父传子
- **************************************************************************Person.vue
- // 触发父级更新user方法
- this.$emit("person_fff_user", this.user)
- **************************************************************************Composite.vue
- data() {
- return {
- foldData: 'foldClass el-icon-s-fold',// 折叠类
- collapseFlag: false, // 是否折叠
- sideWidth: 200, // 左侧边栏宽度
- logoTextShowFlag: true,
- user: localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")) : {}
- // 这个不取,第一次展示就是空了!!!!!!!!!
- }
- },
- methods: {
- /*子传父*/
- async person_fff_user(val) { //这里val就是person传过来的user
- const {data: res} = await this.$http.post("user/select", val) // 从后台获取数据
- // console.log(res.object)
- this.user = res.object
- },
-
- <Top :fff_top_fold-data="foldData"
- :fff_top_foldClick="foldClick"
- :fff_top_user="user"> <!--向person传递user-->
- </Top>
-
- **************************************************************************Top.vue
- <Top :fff_top_fold-data="foldData"
- :fff_top_foldClick="foldClick"
- :fff_top_user="user"> <!--向person传递user-->
- </Top>
-
- props: ['fff_top_foldData', 'fff_top_foldClick', 'fff_top_pathName', 'fff_top_user'],/*说明props还可以接受函数,我日尼玛哦*/
-
- <div style="display: inline-block;">
- <img :src="fff_top_user.avatar"
- style="width: 30px;border-radius: 10%;position: relative;top:5px;right: 5px;">
- <span>{{fff_top_user.name}}</span><i class="el-icon-arrow-down" style="margin-left: 5px"></i>
- </div>
- **************************************************************************
- Person----Composite---Top
- 子----------父------------子
****************************************************************************************************************************************************************************
- 18、整合Echarts
- 【1】配置
- npm i echarts -S
- ************************************************************************
- import * as echarts from 'echarts'
- 折线图 柱状图 饼图
- ************************************************************************
- this.$http.post("/echarts/select").then(res => {
- // this.$http.post("/echarts/vip").then(res => {
- // console.log(res)
- // alert(JSON.stringify(res.data.object.x))
- // alert(JSON.stringify(res.data.object.y))
- option.xAxis.data = res.data.object.x;
- // alert(JSON.stringify(res))
- // option.xAxis.data = ["第一季度", "第二季度", "第三季度", "第四季度",]; // 演示从数据库查询的
- option.series[0].data = res.data.object.y;
- option && myChart.setOption(option);
- })
- 请求后台更改数据,主要是属性的更改,与后台json结合,加上option的位置要在更改后放置!!!
- 【2】概览部分
- <!--头部-->
- <el-row :gutter="10">
- <el-col :span="6">
- <el-card style="color: #409eff">
- <div><i class="el-icon-user-solid"></i> 用户总数</div>
- <div style="text-align: center;padding: 10px 0;font-weight: bold;font-size: 20px">
- 100
- </div>
- </el-card>
- </el-col>
- <el-col :span="6">
- <el-card style="color: #67C23A">
- <div><i class="el-icon-money"></i> 销售总量</div>
- <div style="text-align: center;padding: 10px 0;font-weight: bold;font-size: 20px">
- 100
- </div>
- </el-card>
- </el-col>
- <el-col :span="6">
- <el-card style="color: #E6A23C">
- <div><i class="el-icon-bank-card"></i> 收益总额</div>
- <div style="text-align: center;padding: 10px 0;font-weight: bold;font-size: 20px">
- ¥100000,00
- </div>
- </el-card>
- </el-col>
- <el-col :span="6">
- <el-card style="color: #F56C6C">
- <div><i class="el-icon-s-shop"></i> 门店总数</div>
- <div style="text-align: center;padding: 10px 0;font-weight: bold;font-size: 20px">
- 20
- </div>
- </el-card>
- </el-col>
- </el-row>
- ************************************************************************
- option = {
- title: {
- left: 'center' // 标题属性
- },
- tooltip: { // 鼠标移入显示数据标签
- trigger: 'item'
- },
- legend: { // 显示说明左侧
- orient: 'vertical',
- left: 'left'
- },
- xAxis: {
- type: 'category',
- data: []
- },
- yAxis: {
- type: 'value'
- },
- series: [
- {
- name: 'member', // 和legend是绑定的
- data: [],
- type: 'line'
- }
- ]
- };
****************************************************************************************************************************************************************************
- 20、权限管理
- 【1】Role
- 复制前端的Role,全局替换。修改下细节字段即可
- *****************************************************************************
- 复制后端的Role、RoleMapper.interface、RoleMapper.xml、RoleController
- *****************************************************************************
- 修改下细节字段即可
- 【2】菜单分配页实现新功能 32分钟
- <!--树形控件-->
- <el-tree
- :data="menu.data"
- show-checkbox
- node-key="id"
- :default-expanded-keys="[2]"
- :default-checked-keys="[3]"
- @check-change="onCheckChange">
- </el-tree>
- *****************************************************************************
- 如果是PostMapping,用post必须指定body里一个{}空对象查询
- 如果要求登录注意header参数的token设置
- *****************************************************************************关键代码
- /*找到所有*/
- @PostMapping("/list_all")
- public Res list_all(@RequestBody Menu menu) {
- List<Menu> menuList = menuMapper.selectList(null); // 查询所有
- List<Menu> fatherMenuList = menuList.stream().filter(temp -> temp.getFather() == null).collect(Collectors.toList());// 找出father为null的一级菜单
- for (Menu item : fatherMenuList) { // 找出每一个一级菜单的子菜单,并设置给item(父菜单)
- List<Menu> childrenList = menuList.stream().filter(temp -> item.getId().equals(temp.getFather())).collect(Collectors.toList());
- item.setChildren(childrenList);
- }
- return Res.success(fatherMenuList);
- }
- 【2】角色拥有的菜单,一个字段搞定的事情,青戈非要再用一个表+实体类....
- private String menuIds; // 拥有的菜单集合
- @TableField(exist = false)
- private List<Integer> menuIdList;
- *****************************************************************************这个问题处理
- 确实有点难度,就在于如果传参与获取的类型不一致,尽量前后端不要绑定,
- 绑定就很难处理,调用会报错400。分开后就不存在这个问题,特别是参与获取的类型不一致
- menuIdList: [], 前端需要的是数组!!!!!!!!!
- this.roleForm.menuIds ,后端需要的参数是字符串!!!!!!!!!!!!!!!!!!!
- 【3】动态路由
- 给用户一个角色role
- *****************************************************************************
- role.name不允许重复设置。
- 索引-角色名称/name/UNIQUE/BTREE---后端捕获异常,返回给前端提示...
- *****************************************************************************多测试,空指针bug
- @PostMapping("/select")
- public Res select(@RequestBody Role role) {
- Role existRole = roleMapper.selectById(role.getId());
- // 处理成前端需要的格式menuIds ->menuIdList 提供前端需要的格式!!!!!!!!!!!!!!!!!!!
- List<Integer> menuIdList = new ArrayList<>();
- if (existRole.getMenuIds() != null) {
- String menuIds = existRole.getMenuIds().replace("[", "").replace("]", "");
- String[] tempArray = menuIds.split(",");
- for (String temp : tempArray) {
- menuIdList.add(Integer.parseInt(temp));
- }
- }
- List<Menu> menuList = menuMapper.selectBatchIds(menuIdList);
- existUser.setMenuList(menuList);// !!!!!!!!!都是为了这个
- return Res.success(existRole);// 需要返回对象
- }
- *****************************************************************************
- 我的方法节省了很大的功夫,同时也有很多细节BUG需要耐心排查。
- *****************************************************************************30分钟
- removeIf()等,我都不需要写这些东西,因为存的好,表建的好!!!!!!!!!
- *****************************************************************************都浓缩成这个方法了!!!!!!!
- @PostMapping("/login")
- public Res login(@RequestBody User user) {
- // 拿到用户输入的账号密码
- User existUser;
- try {
- existUser = userMapper.selectUserByNamePassword(user);
- } catch (Exception e) { // 如果系统中存在多条等异常情况
- e.printStackTrace();
- return Res.error(Constants.CODE_500, "系统错误");
- }
- if (existUser == null) {
- return Res.error(Constants.CODE_400, "用户名或密码错误");
- }
- // 设置token,保证existUser要用id与password
- String token = Token.productToken(existUser);
- existUser.setToken(token);
- existUser.setPassword("xxx"); // 拿到token后,保护用户密码
-
- Role role = new Role();
- role.setName(existUser.getRole());
- //根据用户角色,查到角色完整信息
- Role existRole = roleMapper.selectRoleByName(role);
- //根据角色的菜单,生成菜单List,设置到existUser中
- List<Integer> menuIdList = new ArrayList<>();
- if (existRole.getMenuIds() != null) { // 存在菜单
- String menuIds = existRole.getMenuIds().replace("[", "").replace("]", "");
- String[] tempArray = menuIds.split(",");
- for (String temp : tempArray) {
- menuIdList.add(Integer.parseInt(temp));
- }
- }
- // 不存在菜单,设置为空
- List<Menu> menuList = menuMapper.selectBatchIds(menuIdList);
- List<Menu> fatherMenuList = menuList.stream().filter(temp -> temp.getFather() == null).collect(Collectors.toList());// 找出father为null的一级菜单
- for (Menu item : fatherMenuList) { // 找出每一个一级菜单的子菜单,并设置给item(父菜单)
- List<Menu> childrenList = menuList.stream().filter(temp -> item.getId().equals(temp.getFather())).collect(Collectors.toList());
- item.setChildren(childrenList);
- }
- existUser.setMenuList(fatherMenuList);// !!!!!!!!!都是为了这个
-
- return Res.success(existUser);// 这里返回existUser 是因为虽然用户输入的是名+密码,但是实际拿到还有图标等信息
- }
- *****************************************************************************实现动态菜单
- 确实有省功夫的地方,但是青戈的这个思路还是很值得学习的!!!!!!
- <div v-for="item in menuList" :key="item.id">
- <!--有path一级菜单,全靠login Controller返回的父子关系数据-->
- <div v-if="item.path">
- <el-menu-item :index="item.path">
- <i :class="item.icon"></i><!--这里如果不放在上面,收缩时会看不到-->
- <template slot="title">
- <span>{{item.name}}</span>
- </template>
- </el-menu-item>
- </div>
- <!--二级菜单-->
- <div v-else>
- <el-submenu :index="item.id+''">
- <template slot="title">
- <i :class="item.icon"></i>
- <span>{{item.name}}</span>
- </template>
- <div v-for="subItem in item.children" :key="subItem.id">
- <el-menu-item :index="subItem.path">
- <i :class="subItem.icon"></i>
- <span>{{subItem.name}}</span>
- </el-menu-item>
- </div>
- </el-submenu>
- </div>
- </div>
- *****************************************************************************
- <el-menu :default-openeds="openList"
- background-color="rgb(48,65,86)"
- text-color="white"
- active-text-color="yellow"
- :collapse-transition="false"
- :collapse="fff_left_collapseFlag"
- router>
- *****************************************************************************
- openList: localStorage.getItem("menuList") ? JSON.parse(localStorage.getItem("menuList")).map(v => v.id + '') : []
- 【4】动态路由终版起飞
- export const setRoutes = () => {
- const menuList = JSON.parse(localStorage.getItem("menuList"));
- if (menuList) {
- const finalRoute = { // 动态的添加进去
- path: '/', component: Composite,
- redirect: "/login",
- children: []
- }
- menuList.forEach(item => {
- if (item.path) { // 一级路由
- let menu = {
- path: item.path.replace("/", ""),
- name: item.name,
- component: () => import('../views/' + item.vue + '.vue')
- }
- finalRoute.children.push(menu)
- } else if (item.children.length) {
- item.children.forEach(subItem => { // 二级路由
- if (subItem.path) {
- let subMenu = {
- path: subItem.path.replace("/", ""),
- name: subItem.name,
- component: () => import('../views/' + subItem.vue + '.vue')
- }
- finalRoute.children.push(subMenu)
- }
- })
- }
- })
- // 动态添加到现有路由
- router.addRoute(finalRoute)
- }
- }
- *****************************************************************************Login.vue
- import {setRoutes} from '../router/router.js'
- setRoutes() // 动态设置当前用户路由
- *****************************************************************************处理其他细节问题
- router.js里调下自己!!!!!!!!!!!!!!!
- setRoutes();// 刷新页面的时候刷新路由
- *****************************************************************************报错重复路由...
- vue-router.esm.js?3423:16 [vue-router] Duplicate named routes definition: { name: "主页", path: "/home" }
- *****************************************************************************解决,没有的时候才添加
- // 动态添加到现有路由
- const routeNameList = router.getRoutes().map(v => v.name)
- if (!routeNameList.includes('composite')) {
- router.addRoute(finalRoute)
- }
- *****************************************************************************访问其他页面提示404
- {path: '*', name: '404', component: NotFound},
- *****************************************************************************
- <template>
- <div style="text-align: center;margin-top: 300px;">
- <h1>404 NotFound 请您检查路由地址是否正确</h1>
- <br>
- <h2>您也可以联系微信:15921291928</h2>
- </div>
- </template>
-
- <script>
- export default {
- name: "NotFound"
- }
- </script>
-
- <style scoped>
-
- </style>
- *****************************************************************************自己的逻辑又解决了一个问题!!!!
- //后台处理下,选择主页1 选择用户管理3,但是没传过来2系统管理的问题。就是记录了子菜单,但是没有父菜单,动态路由的时候有问题
- List<Integer> tempList = new ArrayList<>(); // 用另一个容器存储,防止遍历时出现问题
- for (int i = 0; i < menuIdList.size(); i++) {
- tempList.add(menuIdList.get(i));
- }
- for (Integer menuId : menuIdList) {
- Menu menu = menuMapper.selectById(menuId);
- if (menu.getFather() != null && !tempList.contains(menu.getFather())) {// 这个就是有孩子,但是集合id里面没有对应父级id
- tempList.add(menu.getFather()); // 临时容器存储
- }
- }
- // 不存在菜单,设置为空
- List<Menu> menuList = menuMapper.selectBatchIds(tempList);
-
- List<Menu> fatherMenuList = menuList.stream().filter(temp -> temp.getFather() == null).collect(Collectors.toList());// 找出father为null的一级菜单
- for (Menu item : fatherMenuList) { // 找出每一个一级菜单的子菜单,并设置给item(父菜单)
- List<Menu> childrenList = menuList.stream().filter(temp -> item.getId().equals(temp.getFather())).collect(Collectors.toList());
- item.setChildren(childrenList);
- }
- existUser.setMenuList(fatherMenuList);// !!!!!!!!!都是为了这个
-
- return Res.success(existUser);// 这里返回existUser 是因为虽然用户输入的是名+密码,但是实际拿到还有图标等信息
- *****************************************************************************分配完毕的重新登录
- setRoutes()// 动态设置当前用户路由
- localStorage.setItem("user", JSON.stringify(res.data.object)) // 存储用户信息到浏览器
- localStorage.setItem("menuList", JSON.stringify(res.data.object.menuList)) // 存储菜单信息
- *****************************************************************************
- if (!localStorage.getItem("user")) { // 首次登录必须刷新路由,否则存在权限不对应问题
- router.addRoute(finalRoute)
- }
- *****************************************************************************这个可以处理,也有弊端
- created() {
- if (window.location.href.indexOf("#reloaded") == -1) {
- window.location.href = window.location.href + "#reloaded";
- window.location.reload();
- }
- },
- *****************************************************************************全局搜索
- Ctrl + Shift + R
- myVuex.commit("userLogout") 全局退出登录
- *****************************************************************************操作完管理员角色需要重新登录
- giveMenu(row) {
- this.roleForm.id = row.id; // 为了分配菜单用的
- this.roleForm.name = row.name
- *****************************************************************************
- if (this.$refs.tree !== undefined && this.roleForm.id !== undefined) {
- if (this.roleForm.name === "管理员") {
- myVuex.commit("userLogout")
- }
- }
- *****************************************************************************
- 父级菜单的处理,还真不能存到数据库。就后台动态返回就完事了...
- 【5】解决一些系统BUG
- 访问http://localhost:8080/ 提示404问题解决
- *****************************************************************************404与登录页面处理
- if (to.matched.length === 0) { // 未找到路由的情况
- const storeMenuList = localStorage.getItem("menuList")
- if (storeMenuList) {
- next("/404")
- } else {
- next("/login") // 如果没有菜单信息跳转到登录页面
- }
- }
- next() // 放行
- *****************************************************************************牛批
- 网友水友...发现了我发现的问题,青戈说的好像也对
- 知识的学习,重要的在于运用,我日TM
- 404BUG的处理。根源还是没有添加路由,还是上次的路由。如何处理呢?
- 重置路由就解决了
- // 重置路由的方法
- export const clearRouter = () => {
- router.matcher = new VueRouter({
- mode: 'history',
- base: process.env.BASE_URL,
- routes
- })
- }
- *****************************************************************************
- userLogout(state) { // 全局退出方法
- // 清空缓存
- localStorage.removeItem("user")
- localStorage.removeItem("menuList")
- router.push("/login")
- // 重置清空路由
- clearRouter()
- }
- *****************************************************************************
- 终于终于搞定了 牛批的版本
****************************************************************************************************************************************************************************
- 24、服务器部署
- 【1】自己动手解决,系统菜单收缩后仍展示问题
- 买阿里云、开端口号
- ******************************************************************************
- firewall-cmd --zone=public --list-ports #查看历史开启端口
- systemctl status firewalld #查看防火墙状态
- systemctl start firewalld #开启防火墙
- firewall-cmd --zone=public --add-port=端口号/tcp --permanent #开启新的端口号
- firewall-cmd --reload #重启防火墙
- iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 端口号 #将80映射到某个端口
- firewall-cmd --zone=public --remove-port=端口号/tcp --permanent # 关闭端口号
- 【2】nginx的配置
- #把默认的80端口改成8002端口。代理dist目录/root/site/white/dist
- cd /usr/local/nginx/conf
- vim nginx.conf
- ******************************************************************************
- cd /usr/local/nginx/sbin
- ./nginx
- ps -ef | grep nginx
- ******************************************************************************
- netstat -lnp|grep 8002
- kill -9 21458
- ******************************************************************************
- 访问http://wdfgdzx.top:8002/
- ******************************************************************************代理如下目录
- location / {
- root /root/site/white/dist;
- index index.html index.htm;
- try_files $uri $uri/ /index.html;
- }
- ******************************************************************************重启
- cd /usr/local/nginx/sbin
- ./nginx -s reload
- 【3】配置动态whiteIp blackIp地址 25-36分钟
- // 线上前端IP
- export const whiteIp = "wdfgdzx.top:8000"
- ******************************************************************************前端
- localhost.js white.js
- ******************************************************************************后端
- localhost.properties black.properties
- 【4】使用的技术
- 前端:VUE2 Vue-Router Vuex ElementUI Axios
- 后端:SpringBoot Hutool Poi Lombok MybatisPlus
****************************************************************************************************************************************************************************
- 26、1V1、1对多、多V多查询
- 【1】以老师、学生教学为示例
- *****************************************************************************41
- 一个表四个复制
- 【2】主要是数据库的查询练习,看着学习吧。1V1查询
- *****************************************************************************
- <!--1.2查询分页数据-->
- *****************************************************************************每次select的时候
- 用了left join
- 用teacher_id去left join user.id,从而获得user.nick as 当做teacherName去使用。在前端展示
- !!!!!!!!!!!!!!!牛批,悟透了!!!!!!! 哈哈哈哈哈
- *****************************************************************************
- 从科目到讲课老师:是1V1
- 从讲课老师到多个科目:是1V多 老师是1 课程是多
- 【3】老师查看自己教授的课程。1V多
- 【4】学生选课 多V多
- <!--设置学生选课信息,先删除后设置-->
****************************************************************************************************************************************************************************
- 32、个人中心、修改头像、数据联动/修改密码
- 【1】个人中心还需要讲吗?.............
- 一个个人中心讲解了30分钟....
- 【2】修改密码,autocomplete="new-password"这个好用,不会自动填充密码
- <el-form-item label="用户名">
- <el-input v-model="user.name" disabled autocomplete="off"></el-input>
- </el-form-item>
-
- <el-form-item label="新密码" prop="newPassword">
- <el-input v-model="passwordForm.newPassword" autocomplete="new-password" show-password></el-input>
- </el-form-item>
-
- <el-form-item label="确认新密码" prop="confirmPassword">
- <el-input v-model="passwordForm.confirmPassword" autocomplete="new-password"
- show-password></el-input>
- </el-form-item>
****************************************************************************************************************************************************************************
- 33、集成wangeditor
- 【1】安装
- npm i wangeditor -S --force
- ********************************************************************************
- @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
- private Date recordTime;
- private String modifyPerson;
- @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
- private Date modifyTime;
- ********************************************************************************
- Ctrl+R 区分大小写后,批量替换,直接起飞!!!!!!!!!!!!!
- ********************************************************************************
- Error in v-on handler: "Error: 无效的节点选择器:content"
- nextTick解决这个问题
- ********************************************************************************
- // 新增的时候开始操作wangEditor
- this.$nextTick(() => {
- const editor = new E('#content')
- editor.create()
- })
- ********************************************************************************23分
- 对于wangEditor的打开窗口显示,我解决了一个历史性难题,牛批!!!!!!!!!!!
- ********************************************************************************
- @PostMapping("/editor")
- public JSON editor(MultipartFile multipartFile) throws IOException { // 这里是三方WangEditor发起的请求,所以不能用包装对象,emmm!!!!!!
- String originName = multipartFile.getOriginalFilename();
- String type = FileUtil.extName(originName);
- long size = multipartFile.getSize();
- // 1、存到磁盘、存储数据库
- File uploadDir = new File(MyUtils.getFinalPath());
- System.err.println(uploadDir);
- if (!uploadDir.exists()) {
- System.out.println(uploadDir.mkdir()); //不存在创建新目录
- }
- // 2、最终文件路径
- String fileUUID = IdUtil.fastSimpleUUID() + "." + type;
- String url = blackIp + "/document/" + fileUUID;
- File uploadFile = new File(uploadDir + "/" + fileUUID);
- //3、获取md5
- if (!uploadFile.exists()) {
- multipartFile.transferTo(uploadFile); // 这时候才存磁盘!!!!!
- }
- String md5 = SecureUtil.md5(uploadFile);
- Document document = new Document();
- // 4、判断数据库里md5是否存在
- document.setMd5(md5);
- List<Document> documentList = documentMapper.md5_list(document); // 数据库查询到相同md5的对象
- System.out.println(documentList);
- if (documentList.size() != 0) {
- url = documentList.get(0).getUrl(); // 存在的话,url直接从已存在数据库信息拿到
- System.out.println(uploadFile.delete());// 删除本地存储的文件
- }
- // 5、存数据库
- Document insertDocument = new Document();
- insertDocument.setUrl(url);
- insertDocument.setName(originName);
- insertDocument.setType(type);
- insertDocument.setSize(size / 1024);
- insertDocument.setMd5(md5);
- documentMapper.insert(insertDocument);
-
- JSONObject jsonObject = new JSONObject();
- // 第一个设置
- jsonObject.set("errno", 0);
- JSONObject data = new JSONObject();
- data.set("url", url);
- JSONArray jsonArray = new JSONArray();
- jsonArray.add(data);
- // 第二个设置
- jsonObject.set("data", jsonArray);
-
- return jsonObject;
- }
****************************************************************************************************************************************************************************
- 19、前台页面
- 【1】先跟着老师实现吧,整合后面自己思考下!!!!
- 还是很多收获的...
- *************************************************************************************
- 不管是否前台后台一起写,组件名最好不要重名,懂吧!!!!!!!!!!!!
- // 配置前台页面路由
- {
- path: '/white',
- name: 'White',
- component: White, // 这是综合管理页面,访问每个子页面的时候它都会展示
- children: [
- {path: '/main', name: 'Main', component: Main},
- ]
- },
- *************************************************************************************
- 注意在这里加了判断:控制路由跳转
- if (res.data.object.role === "学生") {
- this.$router.push("/main") // 跳转前台首页
- } else { // 跳到后台首页
- this.$router.push("/home")
- this.$message.success("登录成功,欢迎您")
- }
- *************************************************************************************
- 登录什么的可以复用原有的管理端....
- *************************************************************************************
- <el-menu :default-active="'1'" class="el-menu-demo" mode="horizontal" router>
- 这个router让我排查了一会!!!!!!!!!!!!!!!!!!!!!!!!!!
- 最后记得带router,日尼玛
- *************************************************************************************
- 青戈真是哥,1小时的视频,我学习捉摸了很久才get到精髓所在。
- *************************************************************************************
- 个人信息页面、修改密码加回来 emmm,卧槽了!!!!!!!!!!!
****************************************************************************************************************************************************************************
- 14、实现Redis缓存效果
- 【1】缓存数据,让用户体验更好,速度更快,避免对数据库的多次请求。
- spring自带的也有cache缓存,但是我们重点学习redis
- ****************************************************************************************
- 单机测试:发现,没有缓存,接口会频繁请求数据库,会有打满风险。
- 【2】主角:Springboot集成Redis
- Redis的学习安装什么的自己在学习一遍吧!!!!!!!!!!!!!!
- <!--Redis-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-redis</artifactId>
- </dependency>
- ****************************************************************************************
- // 刷新Redis 其实是直接清空!!!!!!!!!!!!
- // 如果用到了,在增删改的时候都需要调用下flushRedis(Constants.LIST_ALL_KEY);
- private void flushRedis(String key) {
- stringRedisTemplate.delete(key);
- }
- ****************************************************************************************
- 实现了减轻数据库压力的作用。
- 当然这样比较粗放,可以更精细化,比如删除那一条,重新从数据库查询,重新set
- 感觉也差不多!!!!!!!!!!!!!!!!!!
- ****************************************************************************************
- 更优的方法,是去除之前的缓存json--->变成List--->操作List的增删改(和数据库保持一致的
- 的同时,不用二次查询数据库...)!!!!!!!!!!!!!!!!!!!
- --->NoSQL???好像也还行......
****************************************************************************************************************************************************************************
- 20、视频播放
- 【1】先实现后台文件的预览功能,配置使用kkfileview(不好用 算了)
- 写个Video页面,然后再写Detail页面
- *************************************************************************************
- npm i vue-video-player -S --force
- *************************************************************************************
- npm i video.js -S --force
- *************************************************************************************
- <template>
- <div class="Detail-container" style="margin-top: 5px;">
- <div id="player">
- <video-player class="video-player vjs-big-play-centered"
- ref="videoPlayer"
- :playsinline="false"
- :options="playerOptions">
- </video-player>
- </div>
- </div>
- </template>
-
- <script>
- // 在组件中引入
- import {videoPlayer} from 'vue-video-player'
- import 'vue-video-player/src/custom-theme.css'
- import 'video.js/dist/video-js.css'
-
- export default {
- name: "Detail",
- // 注册
- components: {videoPlayer},
- data() {
- return {
- // video: {},
- // 视频控制设置
- playerOptions: {
- playbackRates: [0.5, 1.0, 1.5, 2.0], // 可选的播放速度
- autoplay: false, // 如果为true,浏览器准备好时开始回放。
- muted: false, // 默认情况下将会消除任何音频。
- loop: false, // 是否视频一结束就重新开始。
- preload: "auto", // 建议浏览器在<video>加载元素后是否应该开始下载视频数据。auto浏览器选择最佳行为,立即开始加载视频(如果浏览器支持)
- language: "zh-CN",
- aspectRatio: "16:9", // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3")
- fluid: true, // 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器。
- sources: [
- {
- type: "video/mp4", // 类型
- src: "", // url地址
- },
- ],
- poster: "", // 封面地址
- notSupportedMessage: "此视频暂无法播放,请稍后再试", // 允许覆盖Video.js无法播放媒体源时显示的默认信息。
- controlBar: {
- timeDivider: true, // 当前时间和持续时间的分隔符
- durationDisplay: true, // 显示持续时间
- remainingTimeDisplay: true, // 是否显示剩余时间功能
- fullscreenToggle: true, // 是否显示全屏按钮
- }
- }
- // -------
- }
- },
- created() {
- let id = this.$route.query.id // 从上个路径拿到的id
- this.$http.post("/document/select_id", {id: id}).then(res => {
- // console.log(res)
- if (res.data.code = "200") {
- this.video = res.data.object;
- this.playerOptions.sources[0].src = res.data.object.url // 赋值视频地址
- } else {
- this.$message.error(res.data.code)
- }
- })
- }
- }
- </script>
-
- <style scoped lang="less">
-
- </style>
****************************************************************************************************************************************************************************
- 21、多级评论
- 【1】新建novel表和写对应的后端代码!!!!!!!!!!!
- 【2】处理前端和安装相关插件
- npm i mavon-editor@2.10.4 -S --force
- ***************************************************************************************
- import mavonEditor from 'mavon-editor'
- import 'mavon-editor/dist/css/index.css'
-
- Vue.use(mavonEditor) // 挂载
- ***************************************************************************************
- import axios from "axios";
- ***************************************************************************************
- imgAdd(pos, $file) {
- let $vm = this.$refs.md
- // 第一步.将图片上传到服务器.
- const formData = new FormData();
- formData.append('file', $file);
- axios({
- url: `http://${whiteIp}/document/upload`, // 这是自己后台的接口
- method: 'post',
- data: formData,
- headers: {'Content-Type': 'multipart/form-data'},
- }).then((res) => {
- // 第二步.将返回的url替换到文本原位置![...](./0) -> ![...](url)
- /**
- * $vm 指为mavonEditor实例,可以通过如下两种方式获取
- * 1. 通过引入对象获取: `import {mavonEditor} from ...` 等方式引入后,
- * `$vm`为`mavonEditor`
- * 2. 通过$refs获取: html声明ref : `<mavon-editor ref=md ></mavon-editor>,
- * `$vm`为 `this.$refs.md`
- */
- $vm.$img2Url(pos, res.data);
- })
- },
- ***************************************************************************************
- 处理小说内容查看的方式 30分钟
- 【3】最最中重点:小说评论功能的实现
- @PostMapping("/select_tree_by_novel_id/{novelId}")
- public Res select_tree_by_novel_id(@PathVariable Integer novelId) {
- List<Discuss> discussList = discussMapper.selectListByNovelId(novelId);
- List<Discuss> rootList = discussList.stream().filter(discuss -> discuss.getRootId() == null).collect(Collectors.toList()); // 所有没有父级的
- for (Discuss discuss : rootList) {
- List<Discuss> childrenList = discussList.stream().filter(temp -> temp.getRootId() != null && temp.getRootId().equals(discuss.getId())).collect(Collectors.toList());
- // 设置父级评论的用户昵称+用户ID
- childrenList.forEach(children -> {
- Discuss tempDisCuss = discussMapper.selectById(children.getFatherId()); // 根据父id查到对应的评论!!!!注意父id和根id的区别!!!!!!!!
- User tempUser = userMapper.selectById(tempDisCuss.getUserId()); // 用用户ID查询到用户
- // 设置父级用户ID+昵称
- children.setFatherUserId(tempUser.getId());
- children.setFatherUserNick(tempUser.getNick());
- });
- discuss.setDiscussChildrenList(childrenList);
- }
- return Res.success(rootList); // 注意返回的是rootList!!!!!!!!!!!!!!!!
- }
- ***************************************************************************************
- @PostMapping("/insertOrUpdate")
- public Res insertOrUpdate(@RequestBody Discuss discuss) { // @RequestBody很重要
- if (discuss.getId() != null) {
- discussMapper.updateById(discuss);
- } else {
- if (discuss.getFatherId() != null) {
- Discuss fatherDiscuss = discussMapper.selectById(discuss.getFatherId()); // 找到父节点
- if (fatherDiscuss.getFatherId() != null) { // 说明父节点,也有父节点,这时候要操作了!!!!!!!!!!!!
- discuss.setRootId(fatherDiscuss.getRootId()); // 设置和父节点同样的祖宗ID!!!!!!!!!!!!!!!!!
- // 这里和childrenList用rootId筛选就形成了妙处!!!!!!!!不然就陷入死循环了,秒秒秒呀!!!!!!!!!!!!!!!
- // 这里和childrenList用rootId筛选就形成了妙处!!!!!!!!不然就陷入死循环了,秒秒秒呀!!!!!!!!!!!!!!!
- // 这里和childrenList用rootId筛选就形成了妙处!!!!!!!!不然就陷入死循环了,秒秒秒呀!!!!!!!!!!!!!!!
- // 这里和childrenList用rootId筛选就形成了妙处!!!!!!!!不然就陷入死循环了,秒秒秒呀!!!!!!!!!!!!!!!
- // 这里和childrenList用rootId筛选就形成了妙处!!!!!!!!不然就陷入死循环了,秒秒秒呀!!!!!!!!!!!!!!!
- // 这里和childrenList用rootId筛选就形成了妙处!!!!!!!!不然就陷入死循环了,秒秒秒呀!!!!!!!!!!!!!!!
- }
- }
- discussMapper.insert(discuss);
- }
- return Res.success(null);
- }
****************************************************************************************************************************************************************************
- 22、支付宝
- 【1】生成公钥私钥的工具
- https://opendocs.alipay.com/common/02kipk?pathHash=0d20b438
- 【2】沙箱环境
- https://openhome.alipay.com/develop/sandbox/app
- 27分钟,orderMapper内容看不到!!!!!!!!!!!!!!!
- 【3】图书管理、订单管理页面...25分钟
- package com.black.controller;
- import com.alipay.easysdk.factory.Factory;
- import com.alipay.easysdk.payment.page.models.AlipayTradePagePayResponse;
- import com.black.mapper.OrdersMapper;
- import com.black.pojo.Alipay;
- import org.springframework.web.bind.annotation.*;
- import javax.annotation.Resource;
- import javax.servlet.http.HttpServletRequest;
- import java.net.URLEncoder;
- import java.util.HashMap;
- import java.util.Map;
- @RestController
- @RequestMapping("/alipay")
- public class AlipayController {
- @Resource
- private OrdersMapper ordersMapper;
-
- @GetMapping("/pay")
- public String pay(Alipay aliPay) {
- AlipayTradePagePayResponse alipayTradePagePayResponse = null;
- try {
- // 发起API调用请求,以创建首付款二维码为例
- alipayTradePagePayResponse = Factory.Payment.Page().pay(aliPay.getSubject(),
- aliPay.getTraceNo(),
- aliPay.getTotalAmount(),
- "");
- } catch (Exception e) {
- e.printStackTrace();
- }
- return alipayTradePagePayResponse.getBody();
- }
-
- @PostMapping("/notify")
- public String notify(HttpServletRequest httpServletRequest) throws Exception {
- if (httpServletRequest.getParameter("trade_status").equals("TRADE_SUCCESS")) {
- System.out.println("---支付宝异步回调---");
- Map<String, String> paramsMap = new HashMap<>();
- Map<String, String[]> requestParamsMap = httpServletRequest.getParameterMap();
- for (String name : requestParamsMap.keySet()) {
- paramsMap.put(name, httpServletRequest.getParameter(name));
- }
- String tradeNo = paramsMap.get("out_trade_no");
- String gmtPayment = paramsMap.get("gmt_payment");
- String alipayNo = paramsMap.get("trade_no");
- // 支付宝验签
- if (Factory.Payment.Common().verifyNotify(paramsMap)) {
- System.out.println("交易名称" + paramsMap.get("subject"));
- System.out.println("交易状态" + paramsMap.get("trade_status"));
- System.out.println("支付宝交易凭证" + paramsMap.get("trade_no"));
- System.out.println("商户订单号" + paramsMap.get("out_trade_no"));
- System.out.println("交易金额" + paramsMap.get("total_amount"));
- System.out.println("买家在支付宝唯一id" + paramsMap.get("buyer_id"));
- System.out.println("买家付款时间" + paramsMap.get("gmt_payment"));
- System.out.println("买家付款金额" + paramsMap.get("gmt_pay_amount"));
- // 更新订单为已支付
- ordersMapper.updateState(tradeNo, "已支付", gmtPayment, alipayNo);
- }
- }
- return "success";
- }
- }
- ************************************************************************************
- <update id="updateState">
- update orders
- set state=#{state},
- payment_time=#{gmtPayment},
- alipay_no=#{alipayNo}
- where no =#{tradeNo}
- </update>
- 【4】退款功能
- <!--支付宝-->
- <!-- https://mvnrepository.com/artifact/com.alipay.sdk/alipay-easysdk -->
- <dependency>
- <groupId>com.alipay.sdk</groupId>
- <artifactId>alipay-sdk-java</artifactId>
- <version>4.22.110.ALL</version>
- </dependency>
- ************************************************************************************
- 用这个包
- @GetMapping("/return_pay")
- public Res return_pay(Alipay alipay) throws Exception {
- // 7天无理由退款
- String now = DateUtil.now();
- Orders orders = ordersMapper.getByNo(alipay.getTraceNo());
- if (orders != null) {
- // hutool工具类,判断时间间隔
- long between = DateUtil.between(DateUtil.parseDateTime(orders.getPaymentTime()), DateUtil.parseDateTime(now), DateUnit.DAY);
- if (between > 7) {
- return Res.error("-1", "该订单已超过7天,不支持退款");
- }
- }
-
- // 1. 创建Client,通用SDK提供的Client,负责调用支付宝的API
- AlipayClient alipayClient = new DefaultAlipayClient(GATEWAY_URL,
- alipayConfig.getAppId(), alipayConfig.getAppPrivateKey(), FORMAT, CHARSET,
- alipayConfig.getAlipayPublicKey(), SIGN_TYPE);
- // 2. 创建 Request,设置参数
- AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
- JSONObject bizContent = new JSONObject();
- bizContent.set("trade_no", alipay.getAlipayTraceNo()); // 支付宝回调的订单流水号
- bizContent.set("refund_amount", alipay.getTotalAmount()); // 订单的总金额
- bizContent.set("out_request_no", alipay.getTraceNo()); // 我的订单编号
-
- request.setBizContent(bizContent.toString());
-
- // 3. 执行请求
- AlipayTradeRefundResponse response = alipayClient.execute(request);
- if (response.isSuccess()) { // 退款成功,isSuccess 为true
- System.out.println("调用成功");
-
- // 4. 更新数据库状态
- ordersMapper.updatePayState(alipay.getTraceNo(), "已退款", now);
- return Res.success();
- } else { // 退款失败,isSuccess 为false
- System.out.println(response.getBody());
- return Res.error(response.getCode(), response.getBody());
- }
- }
****************************************************************************************************************************************************************************
- 23、购物车 1小时10分钟
- 【1】商品
- 【2】购物车
- 【3】订单中心
****************************************************************************************************************************************************************************
24、在线考试
****************************************************************************************************************************************************************************
·
****************************************************************************************************************************************************************************
26、聊天室
- 【1】两个浏览器,两个账号!!!
- WsConfig
- WsUtil
- ***************************************************************************************
- 设置拦截器:
- public void addInterceptors(InterceptorRegistry interceptorRegistry) {
- interceptorRegistry.addInterceptor(jwtInterceptor()) // 调用@Bean返回的
- .addPathPatterns("/**")// 拦截的所有请求,判断token是否合法来决定是否需要登录。
- .excludePathPatterns(
- "/big/login",
- "/big/register",
- "/document/**",
- "/**/list_export",
- "/**/list_import",
- "/swagger**/**",
- "/webjars/**",
- "/v2/**",
- "/doc.html",
- "/alipay/**",
- "ws_server/**"); // 需要配置4个swagger放行
- }
- ***************************************************************************************
- 学习之,改进之!!!!!!!!!!!!!!!!!!
- <template>
- <div class="Chat-container" style="padding: 10px;margin-bottom:50px; ">
- <el-row>
- <!--在线人数区域-->
- <el-col :span="4">
- <el-card style="width: 300px;height: 300px;color: #333333">
- <div style="padding-bottom: 10px;border-bottom: 1px solid #cccccc">
- 在线用户<span style="font-size: 12px;">(点击聊天气泡开始聊天)</span>
- </div>
- <div style="padding: 10px 0;" v-for="item in userList" :key="item.name">
- <span>{{item.name}}</span>
- <i class="el-icon-chat-dot-round" style="margin-left: 10px;font-size: 16px;cursor: pointer"
- @click="clickChatUser(item)"></i>
- <span style="font-size: 12px;color: limegreen;margin-left: 5px;" v-if="item.name===chatUser">chatting...</span>
- </div>
- </el-card>
- </el-col>
- <!--聊天窗口-->
- <el-col :span="20">
- <div style="width: 800px;margin: 0 auto;background-color: white;border-radius: 5px;box-shadow: 0 0 10px #cccccc">
- <div style="text-align: center;line-height: 50px;">
- WsWeb聊天室<span style="color: limegreen" v-if="chatUser">(与{{chatUser}}聊天中...)</span>
- </div>
- <div style="height: 350px;overflow: auto;border-top:1px solid #ccc" v-html="content"></div>
- <div style="height: 200px;">
- <textarea v-model="text"
- style="height: 160px;width: 100%;padding: 20px; border: none;border-top: 1px solid #ccc;border-bottom: 1px solid #ccc;outline: none">
- </textarea>
- <div style="text-align: right;padding-right: 10px">
- <el-button type="primary" size="mini" @click="send">发送</el-button>
- </div>
- </div>
- </div>
- </el-col>
- </el-row>
- </div>
- </template>
-
- <script>
- import {whiteIp} from "../../public/config";
-
- let socket;
- export default {
- name: "Chat",
- data() {
- return {
- user: {},
- userList: [],
- chatUser: '',
- chatUserAvatar: '',
- text: '',
- messageList: [],
- content: ''
- }
- },
- created() {
- this.wsInit()
- },
- methods: {
- clickChatUser(item) {
- this.chatUser = item.name;
- this.$http.post("/user/select", item).then(res => {
- if (res.data.code = "200") {
- // console.log(res)
- this.chatUserAvatar = res.data.object.avatar;
- }
- })
- },
- wsInit() {
- this.user = localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")) : {} // 获取本地存储用户
- let userName = this.user.name
- let _this = this;
- if (typeof (WebSocket) == 'undefined') {
- console.log('您的浏览器不支持ws...')
- } else {
- console.log('您的浏览器支持ws!!!')
- let socketUrl = "ws://" + whiteIp + "/ws_server/" + userName
- if (socket != null) {
- socket.close()
- socket = null
- }
- socket = new WebSocket(socketUrl);
- // ws的几个事件,在vue中定义
- socket.onopen = function () {
- console.log('ws已经打开...')
- }
- socket.onmessage = function (message) {
- console.log('收到数据===' + message.data)
- let data = JSON.parse(message.data)
- if (data.userList) {
- _this.userList = data.userList.filter(item => item.name !== userName)
- } else {
- // if (data.from === _this.chatUser)
- if (data.from) {
- // console.log(data.from)
- _this.chatUser = data.from;
- _this.$http.post("/user/select", {name: data.from}).then(res => {
- if (res.data.code = "200") {
- // console.log(res)
- _this.chatUserAvatar = res.data.object.avatar;
- _this.messageList.push(data)
- // 构建发送的消息
- _this.createContent(data.from, null, data.text)
- }
- })
- }
- }
- }
- // 关闭事件
- socket.onclose = function () {
- console.log('ws已关闭...')
- }
- socket.onerror = function () {
- console.log('发生错误...')
- }
- }
- },
- send() {
- if (!this.chatUser) {
- this.$message({type: 'warning', message: "请选择聊天对象"})
- return;
- }
- if (!this.text) {
- this.$message({type: 'warning', message: "请输入内容"})
- } else {
- if (typeof (WebSocket) == "undefined") {
- console.log("您的浏览器不支持WebSocket");
- } else {
- console.log("您的浏览器支持WebSocket");
- // 组装待发送的消息 json
- // {"from": "zhang", "to": "admin", "text": "聊天文本"}
- let message = {from: this.user.name, to: this.chatUser, text: this.text}
- socket.send(JSON.stringify(message)); // 将组装好的json发送给服务端,由服务端进行转发
- this.messageList.push({user: this.user.name, text: this.text})
- // 构建消息内容,本人消息
- this.createContent(null, this.user.name, this.text)
- this.text = '';
- }
- }
- },
- createContent(remoteUser, nowUser, text) { // 这个方法是用来将 json的聊天消息数据转换成 html的。
- let html
- // 当前用户消息
- if (nowUser) { // nowUser 表示是否显示当前用户发送的聊天消息,绿色气泡
- html = "<div class=\"el-row\" style=\"padding: 5px 0\">\n" +
- " <div class=\"el-col el-col-22\" style=\"text-align: right; padding-right: 10px\">\n" +
- " <div class=\"tip left\">" + text + "</div>\n" +
- " </div>\n" +
- " <div class=\"el-col el-col-2\">\n" +
- " <span class=\"el-avatar el-avatar--circle\" style=\"height: 40px; width: 40px; line-height: 40px;\">\n" +
- " <img src=\"" + this.user.avatar + "\" style=\"object-fit: cover;\">\n" +
- " </span>\n" +
- " </div>\n" +
- "</div>";
- } else if (remoteUser) { // remoteUser表示远程用户聊天消息,蓝色的气泡
- html = "<div class=\"el-row\" style=\"padding: 5px 0\">\n" +
- " <div class=\"el-col el-col-2\" style=\"text-align: right\">\n" +
- " <span class=\"el-avatar el-avatar--circle\" style=\"height: 40px; width: 40px; line-height: 40px;\">\n" +
- " <img src=\"" + this.chatUserAvatar + "\" style=\"object-fit: cover;\">\n" +
- " </span>\n" +
- " </div>\n" +
- " <div class=\"el-col el-col-22\" style=\"text-align: left; padding-left: 10px\">\n" +
- " <div class=\"tip right\">" + text + "</div>\n" +
- " </div>\n" +
- "</div>";
- }
- console.log(html)
- this.content += html;
- }
- }
- }
- </script>
-
- <style>
- .tip {
- color: white;
- text-align: center;
- border-radius: 10px;
- font-family: sans-serif;
- padding: 10px;
- width: auto;
- display: inline-block !important;
- display: inline;
- }
-
- .right {
- background-color: deepskyblue;
- }
-
- .left {
- background-color: forestgreen;
- }
- </style>
****************************************************************************************************************************************************************************
- 30、电商系统与主流
- 【1】电商系统:
- 在终端执行这两个就能运行起来了:
- set NODE_OPTIONS=--openssl-legacy-provider
- npm run serve
- 【2】博客系统:
- set NODE_OPTIONS=--openssl-legacy-provider
- npm run serve
- ****************************************************************************************
- npm i --force
- set NODE_OPTIONS=--openssl-legacy-provider
- npm run dev
- 【3】电影系统
- Solution1:
- npm install -g npm-check-updates
- ncu -u
- npm install
****************************************************************************************************************************************************************************
- 方式一:安装python解决(正确配置系统环境变量),python(v2.7 recommended,
- v3.x.x is not supported) - 推荐
- 下载:http://www.python.org/ftp/python/2.7.3/python-2.7.3.msi
- 自行下载
- npm install --python=python2.7 #先下载
- npm config set python python2.7 #再设置环境
- **********************************************************************************************
- # npm install node-sass@latest
- npm install node-sass@4.12 --force 好使 卧槽
- **********************************************************************************************
- 删除node_modules文件
- 卸载webpack npm remove webpack-dev-server
- 重装指定版本webpack npm install webpack-dev-server@2.9.1
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。