本文最后更新于:3 个月前
前言 Ant Design Pro
2023 年 7 月 21 日
什么是 Ant Design Pro?按我的理解,这是一个前端框架,却又不仅仅是一个前端框架
Ant Design Pro 的语言是 React,它基于 Ant Design 设计体系,提供了丰富的页面模板、组件和功能,非常适合快速搭建前台页面
Ant Design 组件库:分页 Pagination - Ant Design
Ant Design Pro 脚手架:文档总览 - Ant Design Pro
下面是比较官方的定义:
1 Ant Design Pro 是一个开箱即用的企业级中后台前端/设计解决方案,基于 Ant Design 设计体系,内置了丰富的页面模板、组件和功能,旨在帮助开发者快速搭建企业级中后台应用。
1 2 3 4 5 Ant Design Pro 提供了完整的前端开发工作流和最佳实践,具有以下特点和优势: 1. 高度定制化:Ant Design Pro 提供了可灵活配置的模板,可以根据项目需求自定义布局和功能,非常适合快速定制企业级应用。 2. 丰富的模板和组件:Ant Design Pro 内置了多个精心设计的页面模板和丰富的 UI 组件,包括表单、表格、图表、地图等,使开发者可 以快速构建复杂的中后台应用。 3. 系统化的工作流程:Ant Design Pro 提供了完善的开发工作流程,包括开发环境、测试环境和生产环境的配置、代码检查、测试和构建 等,使开发者能够便捷地进行前端开发。 4. 功能丰富的插件集成:Ant Design Pro 集成了常用的中后台应用所需的功能,包括菜单权限管理、数据请求、国际化、登录认证等,可 以快速构建完整的、稳定的企业级应用。
Ant Design Vue
2023 年 7 月 21 日
官网:快速上手 - Ant Design Vue (antdv.com)
Vite
2023 年 7 月 21 日
官网:为什么选 Vite | Vite 中文网 (vitejs.cn)
写作目标
2023 年 7 月 21 日
脚手架的搭建固然简单,在脚手架的基础上,我们可以更方便地改造代码、对接后端代码、编写我们自己的业务逻辑
但前提是我们要理清框架的目录结构,以及相关的逻辑代码
Ant Design Pro 内置了独特的登录页面、欢迎页面、表格页面,我们要理清登录校验逻辑、发送请求到自己的后端接口、重定向到 login 页的逻辑、管理员校验、页面路由配置等等,这些都是必须熟练掌握的
参考官网:启动项目 - Ant Design Pro
将来开发个人网站,或者开发个人项目,都可以基于 Ant Design Pro 框架,更快捷、更高效
正文 JS 基础 推荐阅读:
Vite 开发思路
使用 vite 工具快速构建 Vue 开发框架来从零开发,要做什么?一定是做到以下几点:(2023/08/01 午)
vite 快速构建框架,启动项目
配置启动端口
全局 axios 配置,开发全局请求拦截和相应拦截器,做好跨域请求处理
快速搭建
修改 tsconfig.json 文件,添加如下配置:
1 "moduleResolution" : "Node" ,
如果依赖安装失败,可能是 npm 镜像源的问题,两种解决方法:
1 npm set registry https://registry.npmjs.org/
启动项目:(2023/08/01 早)
启动端口 vite+vue3项目修改启动的端口号为localhost_vue3端口号在哪里改-CSDN博客
1 2 3 4 5 6 7 export default defineConfig ({ plugins : [vue ()], server : { port : 7073 , }, });
全局 axios 配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import axios from "axios" ;const myAxios = axios.create ({ baseURL : "http://localhost:8083/api" , withCredentials : true , }); myAxios.interceptors .request .use ( (config ) => { return config; }, (error ) => { return Promise .reject (error); } ); myAxios.interceptors .response .use ( (response ) => { return response.data ; }, (error ) => { return Promise .reject (error); } );export default myAxios;
组件样式 BUG
在做 Memory-伙伴匹配时,一直有一个问题困扰着我:组件样式有问题
这次使用 Vue 从零开发壁纸网站时,终于彻查了这个不起眼的问题:
1 2 3 4 5 6 import { createApp } from "vue" ;import "vant/lib/index.css" ;import Vant from "vant" ;import App from "./App.vue" ;createApp (App ).use (Vant ).mount ("#app" );
没错,只需在 main.ts 下,引入 “vant/lib/index.css” 样式即可(2023/08/02 早)
请求头设置无效
这个真恶心到我了,POST 请求的请求头设置无效(2023/08/01 晚)
本来是打算做一个前端上传图片文件,后端接收的功能,但这涉及到前端如何发送,后端如何接收的问题
做了大量的尝试,只有这种方法,才能在后端正常接收到图片文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const res = await myAxios .post ("/wallpaper/upload" , file, { headers : { "Content-Type" : "multipart/form-data" , }, }) .then (function (response ) { if (response.data ) { return response.data ; } }) .catch (function (error ) { console .log (error); });
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @PostMapping("/upload") public String uploadFile (@RequestParam("file") MultipartFile file) { if (file.isEmpty()) { return "上传的文件为空" ; } try { byte [] bytes = file.getBytes(); return "文件上传成功" ; } catch (IOException e) { e.printStackTrace(); } return "文件上传失败" ; }
2024 年 8 月 22 日
Vue 网络处理 - axios 异步请求的使用,请求响应拦截器(最佳实践)_vue axios-CSDN博客
Ant Design Pro 快速搭建
我们可以按以下步骤,快速搭建 Ant Design Pro 脚手架:
在正确安装 Node.js 环境下的前提下,依次执行以下命令:
使用 npm 全局安装 pro-cli 构建工具(脚手架):
1 npm i @ant-design/pro-cli -g
至此,前端项目框架搭建完成,启动项目:(2023/07/21 晚)
页面路由报错
在 config/routes.ts 下设置好页面路由后,一定要在对应组件下添加 index.tsx 文件:
1 2 3 4 5 6 7 { name : '接口详情' , icon : 'user' , path : '/interfaceInfo/info' , component : './TableList/entireCommend' , },
否则项目启动后会报错:(2023/08/05 晚)
页面路由跳转(1)
使用 umi/router 实现路由跳转:
全局安装 umi:
1 2 3 4 5 import router from "umi/router" ;function goToListPage ( ) { router.push ("/list" ); }
1 2 3 import Link from "umi/link" ;export default () => <Link to ="/list" > Go to list page</Link > ;
OpenAI 生成接口
前端 config/config.ts 下的配置:
1 2 3 4 5 6 7 openAPI : [ { requestLibPath : "import { request } from '@umijs/max'" , schemaPath : 'http://localhost:8104/api/v2/api-docs?group=壁纸分享' , projectName : 'pic-memories' , }, ],
执行以下命令,一键生成接口:(2023/08/09 午)
最简页面样式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 import { listInterfaceInfoByPageUsingPOST } from "@/services/memory-api/interfaceInfoController" ;import { ProList } from "@ant-design/pro-components" ;import { Button , Space } from "antd" ;import React , { useEffect, useState } from "react" ;import { useHistory } from "react-router-dom" ;const InterfaceInfoList : React .FC = (props ) => { const [interfaceInfo, setInterfaceInfo] = useState<API .InterfaceInfo []>([]); const fetchData = async (params: { pageSize?: number ; current?: number } ) => { try { const res = await listInterfaceInfoByPageUsingPOST ({ ...params, }); console .log ("res = " + res.data ?.records ); if (res.data ) { setInterfaceInfo (res.data .records ); } } catch (error) { console .error (error); } }; const history = useHistory (); const handleButtonClick = ( ) => { history.push ("/TableList/entireCommend" ); }; return ( useEffect (() => { console .log ("啊啊啊" ); fetchData (1 , 5 ); }, []), ( <ProList <any > rowKey="name" headerTitle="基础列表" tooltip="基础列表的配置" dataSource={interfaceInfo} metas={{ title : { dataIndex : "name" , }, description : { dataIndex : "description" , }, actions : { render : (text, row ) => { return ( <Space > <div > <Button onClick ={handleButtonClick} > 跳转到目标页面</Button > </div > </Space > ); }, }, }} /> ) ); };export default InterfaceInfoList ;
改造框架
今天我把 API 接口开放平台的前端代码拉下来了,给 PicMemories 做一个管理员后台
怎么快速改造呢?不要惊慌,做到三点即可:(2023/08/11 晚)
修改接口文档地址,重新生成接口
改造获取用户登录态接口
改造登录接口
改造获取表单信息接口
路由配置错误
1 2 3 4 5 6 7 8 9 10 11 12 PS D:\Project\星球项目\memory-api\memory-api-frontend> yarn start:dev yarn run v1.22.19 $ cross-env REACT_APP_ENV=dev MOCK=none UMI_ENV=dev max dev at Proxy.getRoutes (D:\Project\星球项目\memory-api\memory-api-frontend\node_modules\@umijs\preset-umi\dist\features\tmpFiles\routes.js:61:46) at Hook.fn (D:\Project\星球项目\memory-api\memory-api-frontend\node_modules\@umijs\preset-umi\dist\features\appData\appData.js:51:35) at D:\Project\星球项目\memory-api\memory-api-frontend\node_modules\@umijs\core\dist\service\service.js:136:38 { code: 'MODULE_NOT_FOUND' } fatal - A complete log of this run can be found in : fatal - D:\Project\星球项目\memory-api\memory-api-frontend\node_modules\.cache\logger\umi.log fatal - Consider reporting a GitHub issue on https://github.com/umijs/umi/issues Done in 3.83s.
肯定是路由配置错误了,检查检查吧
好像上面的栏目提到过。。(2023/08/16 午)
可视化数据图表
进入官网,找到相中的图表,在线调试,复制代码:(2023/08/25 晚)
最简页面 2.0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import {PageContainer} from '@ant-design/pro-components'; import '@umijs/max'; import ReactECharts from 'echarts-for-react'; import * as echarts from 'echarts'; /** * 接口分析 * @constructor */ const testAnalysis: React.FC = () => { const option = {} return ( <PageContainer> <ReactECharts option={option}/> </PageContainer> ); }; export default testAnalysis;
有时间就学学 React 框架语法了,挺有意思的,到时候总结点东西再记录下 react 的页面开发技巧(2023/08/25 晚)
区分管理员页面和普通用户页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 { path : '/' , access : 'canAdmin' , name : 'admin' , icon : 'smile' , routes : [ { name : '用户信息' , access : 'canAdmin' , icon : 'user' , path : '/user/list' , component : './TableList/Admin/User' , }, { name : '接口信息' , access : 'canAdmin' , icon : 'user' , path : '/interfaceInfo/list' , component : './TableList/Admin/InterfaceInfo' , }, { name : '接口分析' , icon : 'user' , path : '/admin/interface_analysis' , component : './TableList/Admin/InterfaceAnalysis' }, ], },
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 { path : '/welcome' , name : 'welcome' , icon : 'smile' , component : './Welcome' , }, { path : '/' , redirect : '/welcome' , }, { path : '/' , access : 'canAdmin' , name : 'admin' , .................... },
如上,这里的重定向一定要写在管理员页面路由的前面,这样才能正常跳转至/welcome(2023/09/27 午)
Prettier 美化配置
配置生效文件 ,以及快速格式化时 ,使用 Prettier 帮助快速美化代码 (2023/10/08 早)
一条龙完成 Ant Design Pro 初始化
框架初始化
1 npm i @ant-design/pro-cli -g
至此,前端项目框架搭建完成,启动项目:(2023/10/03 晚)
框架瘦身
国际化移出 mock 移出 swagger 移出 logo 修改 移出 tests types 移出 prettier 美化配置 jest config 测试框架 删除 ds 全局替换 Ant Design Pro / Ant Design
框架改造
登录注册 snap login.test 删除 删除 lang Subtitle 支持 a 标签 targetblank initial Value 自动登录其他登录 删除 手机号登录 全部干掉 删除 忘记密码干掉 未登录?去注册 传参 userAccount userPassword openApi 快速生成后端请求接口
登录请求 成功返回 失败捕获 获取当前登录用户 fetch 删除未引用的
保存全局状态 只留 current user 改类型 改调用方法 当前不是登录页面 返回 currenter 否则返回空
修改头像 水印 前端传递 cokkie 修改请求拦截器 直接返回 config 注册页面开发
1 2 3 4 5 6 7 8 9 10 11 const fetchUserInfo = async ( ) => { const userInfo = await getLoginUserUsingGET (); if (userInfo) { flushSync (() => { setInitialState ((s ) => ({ ...s, currentUser : userInfo, })); }); } };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 const handleSubmit = async (values: API.UserLoginRequest ) => { try { const res = await userLoginUsingPOST ({ ...values, type }); if (res.code === 0 ) { const defaultLoginSuccessMessage = intl.formatMessage ({ id : "pages.login.success" , defaultMessage : "登录成功!" , }); message.success (defaultLoginSuccessMessage); await fetchUserInfo (); const urlParams = new URL (window .location .href ).searchParams ; history.push (urlParams.get ("redirect" ) || "/" ); return ; } } catch (error) { const defaultLoginFailureMessage = "登录失败,请重试!" ; console .log (error); message.error (defaultLoginFailureMessage); } };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 export async function getInitialState ( ): Promise <{ currentUser?: API .LoginUserVO ; }> { const fetchUserInfo = async ( ) => { try { const res = await getLoginUserUsingGET ({ skipErrorHandler : true , }); return res.data ; } catch (error) { history.push (loginPath); } return undefined ; }; const { location } = history; if (location.pathname !== loginPath) { const currentUser = await fetchUserInfo (); return { currentUser, }; } return {}; }
1 2 3 4 5 6 7 8 export default function access ( initialState: { currentUser?: API.LoginUserVO } | undefined ) { const { currentUser } = initialState ?? {}; return { canAdmin : currentUser && currentUser.userRole === "0" , }; }
1 2 3 4 5 6 7 8 9 openAPI : [ { requestLibPath : "import { request } from '@umijs/max'" , schemaPath : "http://localhost:8101/api/v2/api-docs" , projectName : 'memory-bi' , mock : false , }, ],
这里踩了个很恶心的 BUG,执行完成 run openapi 之后,有些方法调用的路径会发生紊乱:
1 import { outLogin } from '@//services/ant-design-pro/login' ;
开发一个页面的步骤
创建路由和页面(2023/10/11 晚)
获取需要的数据,定义 state 变量来存储数据,用于页面展示
先把最简单 、最直观 的数据展示给前端,再去调样式
引入 Ant Design 的 List 组件,复制示例代码
调整 List 组件中的内容为自己的(注意,获取用户头像可以从初始化状态中获取)
1 2 const { initialState } = useModel ("@@initialState" );const { currentUser } = initialState ?? {};
针对样式,对数据进行一些处理,比如统一隐藏自身的 title
增加分页 (当前页码、每页展示数、总记录数 )、增加展示列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <List grid={{ gutter: 16, xs: 1, sm: 1, md: 1, lg: 2, xl: 2, xxl: 2, }} itemLayout="vertical" size="large" pagination={{ onChange: (page) => { setSearchParams({ ...searchParams, current: page, }) }, current: searchParams.current, pageSize: searchParams.pageSize, total: total, }} dataSource={chartList} .......................... )} />
1 2 3 4 5 6 7 8 9 10 11 12 <Search className="margin-bottom-16" placeholder="请输入图表名称" enterButton loading={loading} onSearch={(value) => { // 设置搜索条件 setSearchParams({ ...initSearchParams, name: value, }) }}/>
1 const [loading, setLoading] = useState<boolean >(false );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const loadData = async ( ) => { setLoading (true ); try { const res = await listChartByPageUsingPOST (searchParams); if (res.data ) { setChartList (res.data ?.records ?? []); setTotal (res.data ?.total ?? 0 ); setLoading (false ); } else { message.error ("获取我的图标失败" ); } } catch (e : any ) { message.error ("获取我的图标失败" + e.message ); } };
这里添加了一个 loading 效果,在执行搜索过程 中(即按下搜索后,到数据成功返回的时间段 ),搜索按钮的状态为转圈圈
模拟低网速运行状态
选中对应的网络状态,就可以模仿在该网络环境下发送请求 了 (2023/12/07 晚)
引入外部页面组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import {PageContainer } from "@ant-design/pro-components" ;import {CaretRightFilled } from "@ant-design/icons" ;import {Button , Form , message, Input , Tag , Table } from 'antd' ;import {ColumnsType } from "antd/es/table" ;import React , {useState} from "react" ;import initialState from "@@/plugin-initialState/@@initialState" ;import {invokeInterfaceInfoUsingPOST} from "@/services/memory-api/interfaceInfoController" ;import {useParams} from "@@/exports" ;const RandomPoem : React .FC = () => { ..................... return ( <PageContainer > ..................... </PageContainer > ); };export default RandomPoem ;
1 import RandomPoem from "@/pages/TableList/InterfaceInfo/RandomPoem" ;
Ant Design Vue
快速上手
🍖 推荐阅读:Home | Vue CLI (vuejs.org) (2024/01/15 晚)
1 2 3 npm install -g @vue/cli OR yarn global add @vue/cli
1 npm uninstall -g @vue/cli
1 npx @vue/cli create antd-demo
引入 Ant Design 组件
1 npm i --save ant-design-vue
在 main.ts 中如此配置,引入 Ant Design 组件:
1 2 3 4 5 6 import { createApp } from "vue" ;import App from "./App.vue" ;import router from "./router" ;import Antd from "ant-design-vue" ;createApp (App ).use (router).use (Antd ).mount ("#app" );
配置路由 1 npm install vue-router@4
1 2 3 4 5 6 7 8 9 10 11 12 13 const routes : Array <RouteRecordRaw > = [ { path : "/" , name : "index" , component : IndexPage , }, { path : "/about" , name : "about" , component : () => import ( "../views/AboutView.vue" ), }, ];
封装全局 axios axios中文网|axios API 中文文档 | axios (axios-js.com)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import axios from "axios" ;const instance = axios.create ({ baseURL : "http://localhost:8102/api" , timeout : 10000 , headers : {}, }); instance.interceptors .response .use ( function (response ) { const data = response.data ; if (data.code === 0 ) { return data.data ; } console .error ("request error" , data); return response.data ; }, function (error ) { return Promise .reject (error); } );export default instance;
路由嵌套
app.vue 中如此配置:(2023/08/27 早)
1 2 3 <template> <router-view/> </template>
1 2 3 4 5 在Ant Design Vue中,App.vue是项目的根组件,是所有页面组件的父容器。在这段代码中,`<router-view/>`是一个特殊的组件标签,它用于显示路由器(router)根据当前路由匹配的页面组件。 `<router-view/>`是Vue Router提供的一个占位符标签,它会根据当前的URL路径匹配到的路由,动态地渲染对应的组件内容。 具体来说,当用户访问不同的URL路径时,Vue Router会根据定义的路由规则来确定显示哪个组件。然后,这个组件会被动态地渲染到App.vue中的`<router-view/>`标签中。
比如说当前 URL 中的 path 路径为:/about
而我们配置的路由中:
1 2 3 4 5 { path : '/about' , name : 'about' , component : () => import ( '../views/AboutView.vue' ) }
那么就会展示/about 下的组件内容了
我在主页面中引入了标签页组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <!--标签页--> <a-tabs v-model:activeKey="activeKey"> <a-tab-pane key="文章" tab="文章"> <PostList :post-list="postList"/> </a-tab-pane> <a-tab-pane key="图片" tab="图片"> 图片 </a-tab-pane> <a-tab-pane key="用户" tab="用户"> 用户 </a-tab-pane> </a-tabs>
可以看到在 tab 栏下,我引入了 PostList 组件,并传入了 postList 参数
那么 PostList 组件就会展示在该标签栏下,效果如下:(2023/08/27 早)
子页面嵌套
纠正以下上面说明的页面组件的嵌套方法,正确的嵌套方法应该是这样的:
在 index 页面下引入子组件:
1 2 3 4 <script lang="ts" setup="ts" > import UserListPage from "@/components/userListPage.vue" ; ....................... </script>
1 2 3 4 5 6 7 8 9 <template> <div>嘿嘿嘿</div> </template> <script> export default { name: "UserListPage", }; </script>
直接如此引用子组件(还可传递参数 ,可参考上面 路由嵌套 的实现方法)
1 2 3 4 <a-tab-pane key="1" tab="用户列表"> 在线用户列表 <UserListPage/> </a-tab-pane>
1 2 3 4 5 <a-list item-layout="horizontal" :grid="{ gutter: 16, xs: 1, sm: 2, md: 4, lg: 4, xl: 6, xxl: 6 }" :data-source="props.userInfoList" >
1 2 3 4 5 6 7 8 9 10 11 12 <script setup lang="ts" >import {withDefaults, defineProps} from "vue" ;interface Props { userInfoList : any []; }const props = withDefaults (defineProps<Props >(), { userInfoList : () => [], name : "UserListPage" }) </script>
完成,页面显示如下,子组件已成功显示在 index 页面中:
配置启动端口
在 vue.config.js 下,进行如下配置:(2023/09/09 午)
1 2 3 4 5 6 7 const { defineConfig } = require ("@vue/cli-service" );module .exports = defineConfig ({ transpileDependencies : true , devServer : { port : 7071 , }, });
双向绑定
做登录页面时,一个简单的表单和数据双向绑定搞了半天,这里直接给出代码示例了:(2023/09/10 午)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <a-form @submit="handleSubmit"> <a-form-item label="用户名" name="username" :rules="[{ required: true, message: '请输入用户名' }]"> <a-input :placeholder="'请输入用户名'" v-model:value="userAccount"/> </a-form-item> <a-form-item label="密码" name="userPassword" :rules="[{ required: true, message: '请输入密码' }]"> <a-input :type="'password'" :placeholder="'请输入密码'" v-model:value="userPassword" /> </a-form-item> <a-form-item> <a-button type="primary" html-type="submit">登录</a-button> </a-form-item> </a-form>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const userAccount = ref ("" );const userPassword = ref ("" );const handleSubmit = ( ) => { MyAxios .post ("/user/login" , { userAccount : userAccount.value , userPassword : userPassword.value , }) .then ((values ) => { console .log ("表单数据:" , values); }) .catch ((error ) => { console .error ("表单验证失败:" , error); }); };
日常犯傻
使用 Vue 的 ref() 语法时,容易忘记取.value(2023/09/13 晚)
1 const currentUserId = currentUser.value .id ;
1 const socketUrl = `ws://localhost:8081/api/websocket/${currentUserId} ` ;
处理 JSON 字符串
前台获取服务器转发的消息后,需要解析出接收者,再判断接收者是否为当前用户,是则为该用户展示具体消息(2023/09/14 晚)
1 2 3 4 5 6 7 8 9 10 11 12 13 socket.onmessage = function (msg ) { receiveMsg.value = msg.data ; console .log ("这条消息是发给: " + receiveMsg.value + " 的" ); console .log ("这条消息是发给: " + receiveMsg.value .receiverId + " 的" ); const content = receiveMsg.value .content ; if (receiverId === currentUserId) { setMessage ("服务端回应: " + content + "发给: " + receiverId); } };
前端返回的 mes 是 JSON 字符串,我看见控制台输出的内容是,以为是个对象 ,结果取到的属性值是 undefined:
1 {"senderId" :"1657284783190523906" ,"receiverId" :"1657284893320364034" ,"content" :"阿发" ,"sendTime" :"2023-09-14T13:29:11.309Z" }
调试了半天,检查了下 mes 的类型,才发现是个JSON 字符串 ,奶奶的
1 console.log("type of " + typeof (receiveMsg.value))
所以要将 msg JSON 字符串解析为对象后,才可以正常拿到属性值
1 receiveMsg.value = JSON .parse (msg.data );
最后优化代码,同时处理两种情况:服务器首次连接成功后的响应(普通字符串 ) / 服务器转发的消息(JSON 字符串 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 socket.onmessage = function (msg ) { if (typeof msg === String ) { receiveMsg.value = JSON .parse (msg.data ); } receiveMsg.value = msg.data ; console .log ("这条消息是发给: " + receiveMsg.value .receiverId + " 的" ); const content = receiveMsg.value .content ; if (receiverId === currentUserId) { setMessage ("服务端回应: " + content + "发给: " + receiverId); } };
各种坑都能踩到,不过好在花点时间排错,都能解决掉 👏👏👏(2023/09/14 晚)
页面路由跳转(2)
1 2 3 4 5 { path : '/chat' , name : 'chat' , component : () => import ( '../pages/chatPage.vue' ) },
包含 path(页面路由)、name(页面别名)、component(页面展示组件)
那么我们可以如此实现精准的页面跳转 和参数传递:
1 2 3 4 5 6 7 8 9 10 11 12 import router from "@/router" ; router.push ({ name : "chat" , path : "/chat" , query : { chatUserId : 12345 , }, params : { id : "12345" , }, });
也可以简单的改变当前页面的路由参数 ,而不进行页面跳转 :(适用于 Tab 页之间切换时,标记 tab 页 )
1 2 3 4 5 6 7 import router from "@/router" ; router.push ({ query : { name : "abab" , }, });
那么跳转过去的页面该如何获取当前页面的路由地址 、携带的参数 等信息呢?引入 Vue Router 组件 :(2023/09/16 早)
1 2 3 4 5 6 7 8 import { useRoute } from "vue-router" ;const route = useRoute ();onMounted (() => { console .log ("携带参数:" , route.query ); console .log ("路由地址:" , route.path ); console .log ("传递参数:" , route.params ); });
动态新增 tab 标签页
1 2 3 4 5 6 <a-tabs v-model :activeKey="activeKey" tab-position="left" @change ="handleTabChange" > <a-tab-pane v-for ="tab in tabs" :key ="tab.key" :tab ="tab.tab.chatTabName" > {{ tab.content }} </a-tab-pane > </a-tabs>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 onMounted (() => { addTab (); });const addTab = ( ) => { chatTabName.value = route.query ; const newTab = { key : Date .now ().toString (), tab : chatTabName.value , content : "新标签页内容" , }; tabs.value .push (newTab); activeKey.value = newTab.key ; };
这样就轻松实现了(MemoryChat 开放过程中的废弃想法 )(2023/09/16 午)
icon 图标的使用
看官网,首先下载安装相关依赖:(2023/09/17 晚)
1 npm install --save @ant-design/icons-vue
引入相关 icon 图标 组件 (这步操作,,WebStorm 是不会自动给你引入这个组件的,怪不得我 tab 标签页 的页面图标不显示,一个星期都没解决掉,这编译器害人不浅)
1 import { StarOutlined , StarFilled , StarTwoTone } from "@ant-design/icons-vue" ;
1 2 3 4 5 <div> <StarOutlined /> <StarFilled /> <StarTwoTone twoToneColor="#eb2f96" /> </div>
监听选中的 Tab 标签页
我们选中 Tab 标签页后,如果绑定了 activeKey,就会将该 Tab 页的 Key 值,给到 activeKey
1 2 3 4 5 6 7 8 9 <a-tab-pane v-model:activeKey="activeKey" v-for="tab in friendList" :key="tab.id" :tab="tab.username" @click="handleTabChange" > ......................... </a-tab-pane>
1 2 const activeKey = ref (currentUserId);
1 const activeKey = ref (currentUserId);
我们现在要实现:刷新页面后,仍然选中刚才选中的 Tab 标签页,即 activeKey 值需要更新,更新为刚才的 key 值
但是,当页面刷新后 activeKey 的值就丢失了,所以我们要对 active 进行监听 ,随时保存它的值,如下:
1 2 3 4 watch (activeKey, (value ) => { localStorage .setItem ("activeKey" , value); });
1 2 3 4 5 6 7 onMounted (() => { ........................ activeKey.value = localStorage .getItem ('activeKey' ) ..................... })
点击跳转不同的页面
1 2 3 4 <!--博文--> <a-card hoverable @click="goToRead(item.id)"> ...................... </a-card>
1 2 3 4 5 6 7 8 9 10 11 const goToRead = (id: never ) => { router.push ({ name : "blogRead" , path : "/blog/read" , query : { articleId : id, }, }); };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <template> <div class="blog"> {{ articleId }} </div> </template> <script lang="ts" setup="ts"> import { useRoute } from "vue-router"; import { ref } from "vue"; const route = useRoute(); const articleId = ref(); // 获取id值 articleId.value = route.query.articleId; </script>
就这样简单地记录下思路吧(2023/09/24 午)
网页中支持 Markdown 语法写博客
然后,在你的组件中,你可以导入并实例化markdown-it
,将 Markdown 文本作为输入,使用.render()
方法将其转换为 HTML 并展示在网页上
1 2 3 4 5 6 7 import MarkdownIt from "markdown-it" ;const parsedContent = ref ();const md = new MarkdownIt (); parsedContent.value = md.render (articleInfo.value .content );
1 2 3 4 5 <div v-html="parsedContent" style="position: absolute; margin-left: 10px; margin-right: 10px; margin-top: 20px;" > </div>
子组件绑定自己独有的属性
Eslint 优化导致 git 代码推送失败
这是我在今晚推送 Memory API 接口开放平台时 遇到的报错。详情看下图: (2023/10/18 晚)
如上图所示,使用 git 推送代码竟然报错了,这是什么原因呢?
我们简单罗列出以上的报错信息:
这些错误和警告是由静态代码分析工具 ESLint 提供的。根据报错信息,您遇到了一些问题:
File ignored because of a matching ignore pattern. Use "--no-ignore" to override
:这是一个警告,意味着某些文件被忽略了,因为它们匹配了 .eslintignore
文件中设定的忽略模式。您可以使用 --no-ignore
参数来覆盖这个忽略,让这些文件被检查。
'loading' is assigned a value but never used @typescript-eslint/no-unused-vars
:这是一个错误,提示您在 InterfaceInfo/index.tsx
文件中定义了 loading
变量,但是从未使用过。您可以删除没被使用的变量,或者使用变量来解决您需要的逻辑。
Missing "key" prop for element in array react/jsx-key
:这是两个错误,它们指出在您的组件 InterfaceInfo
的 renderDataSource
方法中,两个元素没有提供 key
属性。React 要求在渲染组件数组时,每个元素都要提供一个唯一的 key
属性,以便在更新列表时进行准确的 diff 操作。您需要为这两个元素添加一个适当的 key
属性。
1 git commit -m "接口调用页面优化 接口参数信息 接口文档预开发" --no -verify
这里其实还有很多有关 ESLint 的配置管理 ,我就暂时不深究了,日后有机会再展开聊聊
安装依赖
导入外部插件时,要选择这个选项,嗖嗖嗖就安装好了(可能这个安装默认是 yarn 的缘故)(2023/11/11 晚)
但是点第一个或者执行以下命令就得卡住好一会儿,还下载安装失败:
快速入门 Vue 指南
🍖 推荐阅读:写给后端大忙人看的 vue 入门指南 - 掘金 (juejin.cn)
还是非常推荐阅读的,我第一次学 Vue 就是跟着黑马的教程学的,这些操作也都过了一遍的
经常使用脚手架 (vite 、vue/cli 等)直接构建 Vue 项目 ,很方便很快捷
有时间再实操一遍吧,后端开发人员多了解一些 Vue 还是很有必要的(2023/11/11 晚)
栅格化布局
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <div style="margin-top: 30px"> <a-row :gutter="16"> <a-col :push="2" :span="4"> <div style="background-color: #4801"> dd </div> </a-col> <a-col :push="2" :span="15"> <div style="background-color: #4801"> dd </div> </a-col> </a-row> </div>
引入外部组件页面
编写 ComprehensiveArticle.vue
页面:
1 2 3 4 5 <template> <div>综合文章</div> </template> <script setup lang="ts"></script>
在 ArticleList.vue
中导入该页面组件:
1 2 3 4 5 6 7 8 9 <div style="width: 500px"> <a-tabs v-model:activeKey="activeKey" tab-position="left"> <a-tab-pane key="1" tab="Tab 1"> <CompreArticleList/> </a-tab-pane> <a-tab-pane key="2" tab="Tab 2">Content of Tab 2</a-tab-pane> <a-tab-pane key="3" tab="Tab 3">Content of Tab 3</a-tab-pane> </a-tabs> </div>
1 2 3 4 <script setup lang="ts" > import CompreArticleList from "@/components/CompreArticleList.vue" ; ....................... </script>
引入外部组件页面并传参
外部页面组件 ArticleList
携带参数,参数名为 compreList
,参数值为 articleList
1 2 3 4 5 6 7 <a-tabs v-model:activeKey="activeKey" tab-position="left"> <a-tab-pane key="1" tab="综合"> <CompreArticleList :compre-list="articleList"/> </a-tab-pane> <a-tab-pane key="2" tab="后端"> ............................ </a-tabs>
编写内部组件 CompreArticleList
,接收参数 compreList
,使用 a-list
组件展示
1 2 3 4 5 6 7 8 <a-list class="demo-loadmore-list" item-layout="horizontal" :data-source="props.compreList" style="width: 400%" > ................................ </a-list>
在 CompreArticleList
下指明接收参数名(2023/12/22 早)
1 2 3 4 5 6 7 8 9 10 11 12 13 <script setup lang="ts" >import {defineProps, withDefaults} from "vue/dist/vue" ; ..........................................interface Props { compreList : any []; }const props = withDefaults (defineProps<Props >(), { compreList : () => [], }); </script>
自定义 icon 图标
1 npm install --save @ant-design/icons-vue
1 import Icon from "@ant-design/icons-vue" ;
1 2 3 4 5 6 7 <Icon :style="{ color: 'hotpink' }"> <template #component> <svg t="1705158531565" class="icon" viewBox="0 0 1024 1024" version="1.1" .................................... </svg> </template> </Icon>
组件 textarea 高度动态变化
1 2 3 4 5 6 7 8 9 @Data public class Result { private int height; .................................. }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public Result getResultCommon (double result, double discount, String resultStr, int height) { double afterDis = result * discount; Result calResult = new Result (); calResult.setBeforeDis(0.0D ); calResult.setAfterDis(0.0D ); calResult.setBeforeDis(result); calResult.setAfterDis(afterDis); calResult.setProcedure(resultStr); calResult.setHeight(height); return calResult; }
使用 a-textarea 文本框组件,高度随接收参数值 height
动态变化:(2024/01/14 晚)
1 2 3 4 5 6 <a-form-item label="计算过程"> <a-textarea v-model:value="result.procedure" placeholder="Basic usage" :rows="result.height" style="font-size: medium;font-weight: bold"> </a-textarea> </a-form-item>
1 2 3 4 5 6 const result = ref<ResultState >({ beforeDis : 0 , afterDis : 0 , procedure : "" , height : 5 , });
CSS 选择器巩固 1 2 3 4 5 6 7 8 9 Element leftZhankai = doc.getElementById("leftZhankai" ); Elements heads = leftZhankai.select(".sons .cont div:nth-of-type(2)" ); for (Element head : heads) { Elements title = head.select(">p:nth-of-type(2)" ); System.out.println("hhh" + title.text()); }
熟悉 CSS 选择器 之后,解析 HTML 文档 获取标题、诗人和内容 就很轻松了:
存在多个标签,随机选择五个:(2024/01/16 晚)
1 2 List<Element> selectedItems = galleryItem.subList(0 , 5 );
1 2 3 4 5 6 7 8 9 10 11 12 13 for (Element item : selectedItems) { Elements img = item.select("> a > img" ); String src = "https:" + img.attr("data-src" ); String title = img.attr("title" ); Picture picture = new Picture (); picture.setCategory(category); picture.setTitle(title); picture.setUrl(src); pictureList.add(picture); }
MarkDown 编辑器 官方文档:bytedance/bytemd: ByteMD v1 repository (github.com)
1 npm i @bytemd/plugin-highlight @bytemd/plugin-gfm
新建 Markdown 编辑器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 <template> <Editor :value ="value" :plugins ="plugins" @change ="handleChange" /> </template><script setup lang ="ts" > import gfm from "@bytemd/plugin-gfm" ;import highlight from "@bytemd/plugin-highlight" ;import { Editor } from "@bytemd/vue-next" ;import { withDefaults, defineProps, ref } from "vue" ;interface Props { value : string; mode?: string; handleChange : (v: string ) => void ; } const plugins = [ gfm (), highlight (), ]; const value = ref ("" );const handleChange = (v: string ) => { value.value = v; }; </script > <style > </style >
在 main.ts 下,引入样式:
1 import "bytemd/dist/index.css" ;
代码编辑器 可视化图表
2024 年 8 月 28 日
在项目中引入 ECharts - 入门篇 - 使用手册 - Apache ECharts
1 npm install echarts --save
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 <template > <div > <h1 > {{ msg }}</h1 > <div id ="main" > </div > <a-card title ="热门搜索词" style ="margin: 10px 20px" > <div class ="main" id ="main" :style ="{ float: 'left', width: '100%', height: '400px' }" > ???图表呢? </div > </a-card > <p class ="read-the-docs" > Click on the Vite and Vue logos to learn more</p > </div > </template > <script setup lang ="ts" > import * as echarts from 'echarts' ;import {onMounted} from 'vue' onMounted (() => { initEcharts () }) const initEcharts = ( ) => { const option = { title : [ { text : 'Tangential Polar Bar Label Position (middle)' } ], polar : { radius : [30 , '80%' ] }, angleAxis : { max : 4 , startAngle : 75 }, radiusAxis : { type : 'category' , data : ['a' , 'b' , 'c' , 'd' ] }, tooltip : {}, series : { type : 'bar' , data : [2 , 1.2 , 2.4 , 3.6 ], coordinateSystem : 'polar' , label : { show : true , position : 'middle' , formatter : '{b}: {c}' } } }; const myChart = echarts.init (document .getElementById ("main" )); myChart.setOption (option); } function defineProps<T>() {} defineProps<{ msg : string }>() </script >
妈的,一晚上啥也没干成。。图表就是不显示。前端真的麻烦。
2024 年 8 月 29 日
1 2 3 4 5 6 7 <template> <div> <h1>{{ msg }}</h1> <div class="main" id="main" style="float: left ;width: 100%; height: 400px "></div> <p class="read-the-docs">Click on the Vite and Vue logos to learn more</p> </div> </template>
原来必须指定标签宽高才能正常显示统计表的,我之前怎么没遇到过这样的问题呢。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 import * as echarts from 'echarts' ;const initEcharts = ( ) => { const option = { title : [ { text : "Tangential Polar Bar Label Position (middle)" , }, ], polar : { radius : [30 , "80%" ], }, angleAxis : { max : 4 , startAngle : 75 , }, radiusAxis : { type : "category" , data : ["a" , "b" , "c" , "d" ], }, tooltip : {}, series : { type : "bar" , data : [2 , 1.2 , 2.4 , 3.6 ], coordinateSystem : "polar" , label : { show : true , position : "middle" , formatter : "{b}: {c}" , }, }, }; const myChart = echarts.init (document .getElementById ("main" )); myChart.setOption (option); };
Layout 布局
2024 年 9 月 1 日
这个组件可太好用了,直接就能拿来做后台管理页面:
Ant Design Vue (antdv.com)
不过源码里都有 BUG ,这里简单修改下就可以:
1 2 3 4 5 6 7 8 .logo { height : 32px ; margin : 16px ; background : rgba (255 , 255 , 255 , 0.3 ); font-size : large; color : lavender; font-weight : bold; }
我根据功能模块很快就把基本的框架搭建完成:
昨天还在研究这个页头和侧边导航菜单怎么搭配才好看,页面做了个大概不太好看,但睡觉之前总算研究出点击导航栏的交互逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <a-menu theme="dark" v-model:selectedKeys="selectedKeys" v-model:openKeys="openKeys" mode="inline" @click="handleClick" > <a-sub-menu key="institution" @titleClick="titleClick"> <template #title> <span> <user-outlined /> <span>组织机构</span> </span> </template> <a-menu-item key="institution_1">机构管理</a-menu-item> <a-menu-item key="institution_2">角色管理</a-menu-item> <a-menu-item key="institution_3">用户管理</a-menu-item> <a-menu-item key="institution_4">岗位管理</a-menu-item> </a-sub-menu>
1 2 3 4 5 6 7 8 9 const handleClick = (e: Event ) => { message.info ("handleClick" + JSON .stringify (e)); }const titleClick = (e: Event ) => { message.info ("titleClick" + JSON .stringify (e)); };
这样就可以很轻松地实现点击菜单 / 点击展开菜单后的交互逻辑:
我之前做过的 Vue.js 项目中,用路由和基本布局组件(BasicLayoutView)实现了主体内容根据路由发生变化,就是 content 这块内容:
怎么实现呢?App.vue 文件下这样写:
1 2 3 4 5 6 7 8 9 10 <template> <template v-if="route.path.startsWith('/user')"> <router-view /> </template> <template v-else> <div id="app"> <BasicLayout /> </div> </template> </template>
也就是除了登录页面,其余所有页面均展示 BasicLayout 组件内容,即登录页面是这样的:
在 BasicLayout 的 content 下这样写,当然就实现了根据当前路由,页面局部展示组件了:
1 2 3 4 5 6 7 8 9 10 11 12 13 <!--正文--> <a-layout-content style="margin: 0 16px"> <a-breadcrumb style="margin: 16px 0"> <a-breadcrumb-item>User</a-breadcrumb-item> <a-breadcrumb-item>Bill</a-breadcrumb-item> </a-breadcrumb> <div :style="{ padding: '24px', background: '#fff', minHeight: '360px' }" > <router-view /> </div> </a-layout-content>
只要路由配置好以后,路由对应的组件显示就不会有问题。
值得一提的是,点击菜单后接收到的 event ,我尝试转为 json 对象,打印出来后是 json 字符串:
1 message.info ("handleClick" + JSON .stringify (e));
然后转换为 JS 对象,就可以直接取到点击的菜单 key 值了,如果把 key 值设置为跳转路由,就可以根据路由跳转到指定页面了:
我还写了段测试代码反复比对,原来一个是 JS 对象,一个是 json 字符串,这能一样吗,还得用 JSON.parse(jsonString) 转 JS 对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const handleClick = (e: Event ) => { message.info ("handleClick" + JSON .stringify (e)); const event = JSON .stringify (e); const eventData = { key : "one_2" , eventKey : "menu_item_1322_$$_one_2" , keyPath : ["one" , "one_2" ], eventKeyPath : ["sub_menu_367_$$_one" , "menu_item_1322_$$_one_2" ], domEvent : { isTrusted : true , _vts : 1725183565175 }, item : { disabled : false , danger : false }, }; console .log (event === eventData); console .log (JSON .parse (event)); console .log (eventData); const key = eventData.key ; console .log (key); };
1 2 3 4 5 6 7 8 <script setup lang="ts" >function defineProps<T>(): void { console .log ("" ); } defineProps<{ msg : string }>(); </script>
包管理器混合
2024 年 9 月 2 日
Layui
2024 年 8 月 21 日
layui: 一套遵循原生态开发模式的 Web UI 组件库,采用自身轻量级模块化规范,极易上手,可以更简单快速地构建网页界面。 (gitee.com)
layui介绍及入门基础(看我这篇就会了!!!)-CSDN博客
看了两篇文章,基本了解了 laiui,Bootstrap,esasyui 是怎么一回事了,再拉取个开源项目玩玩:springboot-manager: 基于SpringBoot + Mybatis Plus + SaToken + Thymeleaf + Layui的后台管理系统 (gitee.com)
进入官网在线体验地址看了下,这个开源项目我好像用过,好熟悉的页面:
有时间就可以研究这个项目了,SpringBoot + Mybatis Plus + SaToken + thymeleaf + Layui 的技术栈,对我而言还是有一定的吸引力。
接下来进入实操环节,我尝试使用 layui 搭建几个好看的页面:
🥩 官网:layui: 一套遵循原生态开发模式的 Web UI 组件库,采用自身轻量级模块化规范,极易上手,可以更简单快速地构建网页界面。 (gitee.com)
下载安装了 layui 源码包,按照官网文档提供的使用步骤成,成功在本地新搭建的 Spring Boot 前后端不分离项目应用了样式。
下载资源包,放在 resource 目录下:
在 static 目录下新增 index.html 文件,组件什么的直接官网里找就行了,复制粘贴过来即可:开始使用 - Layui 文档
一定要保证正确引入 .css 和 .js 文件,这样引入:
1 2 3 4 5 6 <head > <meta charset ="utf-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1" > <title > Demo</title > <link href ="./layui/css/layui.css" rel ="stylesheet" > </head >
1 <script src ="./layui/layui.js" > </script >
导入了常见的组件,看看效果。
作为一个资深的后端开发人员 ,我还是不习惯使用 layui 来开发 Web 项目,无论是全新的组件库还是 Ajax 请求发送流程,都需要学习。
一个简单的登陆页面,使用 layui 封装的 JQurey,向后台服务器发送请求:
1 2 3 <div class ="layui-form-item" > <button class ="layui-btn layui-btn layui-btn-normal layui-btn-fluid" lay-submit ="" lay-filter ="login" > 登 入</button > </div >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 <script> console .log ("test" ); layui.use (function ( ) { var form = layui.form , layer = layui.layer , $ = layui.jquery ; $(document ).ready (function ( ){ $('.layui-container' ).particleground ({ dotColor :'#7ec7fd' , lineColor :'#7ec7fd' }); }); form.on ('submit(login)' , function (data ) { data = data.field ; if (data.captcha == '' ) { layer.msg ('请输入验证码' ); return false ; } if (data.username == '' ) { layer.msg ('用户名不能为空' ); return false ; } if (data.password == '' ) { layer.msg ('密码不能为空' ); return false ; } $.ajax ({ type : 'POST' , url : ctx + 'sys/user/login' , dataType : "JSON" , data : JSON .stringify (data), contentType : "application/json" , success : function (res ) { if (res.code == 0 ) { layer.msg ('登录成功' , {time : 1000 }, function ( ) { window .location = ctx + 'index/home' ; }); } else { layer.msg (res.msg ); $("#img" ).click (); } } }) return false ; }); }); </script>
这就是今天学习 layui 的全部心得,将来一有时间我就会在这里写一些新的体会,总觉得前端代码看多了以后,有种释怀的感觉。
thymeleaf
2024 年 8 月 20 日
今晚完善了自己的 Spring Boot 初始框架:memory-springboot-init: Spring Boot 初始框架 (gitee.com)
以后会慢慢学习和完善。
我想要做一个练习:前后端不分离与前后端分离的Java Web开发对比介绍_java 前后端不分离-CSDN博客
快速地构建一个 Spring Boot 项目,确保导入以下依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <optional > true</optional > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-thymeleaf</artifactId > </dependency >
做好相关配置 appication.xml:
1 2 3 4 5 6 7 spring: application: name: spring-demo server: port: 8080 thymeleaf: cache: false
新增 Controller 层,处理 Web 请求:
1 2 3 4 5 6 7 8 @Controller public class HelloController { @GetMapping("/hello") public String sayHello (Model model) { model.addAttribute("message" , "Hello, World!" ); return "hello" ; } }
新增 hello.html:
1 2 3 4 5 6 7 8 9 10 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Title</title > </head > <body > <h1 th:text ="${message}" > </h1 > </body > </html >
最终项目结构:
启动项目,访问:Title ,得到以下结果:
2024 年 8 月 21 日
跟之前学习 layui 的心得是一样的,我希望能更多地接触到新的开源项目,学习那些平常时间里基本没有用到的技术栈。
我总算明白到这样的事实:前后端分离和不分离的开发模式其实相差不大,主要还是纠结前端用什么框架搭建页面比较合适。
像 layui,Bootstrap,easyui 这些,都可以理解为一种 UI 渲染框架 ,我觉得下面这段内容将三者之间的关系缕得很清晰:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 2 .1 layui和bootstrap对比(这两个都属于UI渲染框架) 1 .layui是国人开发的一套框架,2016 年出来的,现在已更新到2 .X版本了。比较新,轻量级,样式简单好看(目前官网已下架,开源了) 2 .bootstrap 相对来说是比较成熟的一个框架,现在已经更新到4 .X版本。是一个很成熟的框架,这个大部分人一般都用过 1 )适用范围不一样 1 .layui 其实更偏向与后端开发人员使用,在服务端页面上有非常好的效果。 2 .适合做后台框架 3 .layui是提供给后端开发人员最好的ui框架,基于DOM驱动,在实现前端交互上比较麻烦,页面的增删改查都需要查询DOM元素。所以在不需要交互的时候,用layui还是不错的(说这句话的人,只能说明你对layui不了解) 4 .bootstrap 在前端响应式方面做得很好,PC端和移动端表现都不错。 5 .适合做网站 6 .如果是类似官网,且需要同时满足PC端和移动端效果,bootstrap 表现很好,但是如果是要交互的网站,比如商城之类,layui显然更好,前后端分离 2 )大小不一样 1 .layui 轻量级 2 .bootsrap 因为成熟,所以使用方便,但是同时也是因为成熟,就显得有些冗余
1 2 3 4 5 6 7 2 .2 layui和easyui对比 1 .easyui 是非开源的,有需要解决的问题的话,就只能等官方更新了 2 .layui是开源的,社区比较活跃,解决问题还是比较快的 3 .easyui诞生的早些,所以功能相对完善一些,很多功能都能是比较健全的 4 .layui就相对来说少一些了,不过,功能都是像官网说的,精雕细琢 5 .layui更符合现在的审美
那到底什么是 thymeleaf 呢?这东西用来干嘛的。其实只要将 thymeleaf 和 vue 比较下,就能很轻松地了解了:
vue和thymeleaf区别_百度知道 (baidu.com)
vue 主要用于构建用户界面和单页面应用,更加侧重于前端开发和视图层的处理,支持响应式编程、声明式渲染和组件化开发等,通过 js 实现前后端数据交互和页面渲染。
thymeleaf 是一个用于 Web 和独立环境的服务器端 Java 模板引擎,能够直接生成 HTML 页面,可以与 Spring 等后端框架无缝集成,主要通过 Java 代码和模板结合实现服务端页面渲染,天生用来实践前后端不分离开发模式。
总结