本文最后更新于:4 个月前
前言 Ant Design Pro
什么是Ant Design Pro?按我的理解,这是一个前端框架,却又不仅仅是一个前端框架
Ant Design Pro的语言是React,它基于Ant Design设计体系,提供了丰富的页面模板、组件和功能,非常适合快速搭建前台页面
下面是比较官方的定义:
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
Vite
写作目标
脚手架的搭建固然简单,在脚手架的基础上,我们可以更方便地改造代码、对接后端代码、编写我们自己的业务逻辑
但前提是我们要理清框架的目录结构,以及相关的逻辑代码
Ant Design Pro内置了独特的登录页面、欢迎页面、表格页面,我们要理清登录校验逻辑、发送请求到自己的后端接口、重定向到login页的逻辑、管理员校验、页面路由配置等等,这些都是必须熟练掌握的
将来开发个人网站,或者开发个人项目,都可以基于Ant Design Pro框架,更快捷、更高效
正文 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早)
启动端口 1 2 3 4 5 6 7 // https:// vitejs.dev/config/ 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 7 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 "文件上传失败" ; }
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' , },
页面路由跳转(1)
使用 umi/router 实现路由跳转:
全局安装 umi:
1 2 3 4 5 import router from 'umi/router' ;function goToListPage ( ) { router.push ('/list' ); }
1 2 3 4 5 import Link from 'umi/link' ;export default () => ( <Link to ="/list" > Go to list page</Link > );
OpenAI生成接口
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 72 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 13 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.83 s.
肯定是路由配置错误了,检查检查吧
好像上面的栏目提到过。。(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 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 vue create antd-demo #项目名
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 2 3 4 5 6 7 8 9 10 11 12 const routes : Array <RouteRecordRaw > = [ { path : '/' , name : 'index' , component : IndexPage }, { path : '/about' , name : 'about' , component : () => import ( '../views/AboutView.vue' ) } ]
封装全局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 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 5 <script lang="ts" setup="ts" >import UserListPage from "@/components/userListPage.vue" ; ....................... </script>
1 2 3 4 5 6 7 8 9 10 11 <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 <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 <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 6 7 8 9 10 <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" ;
代码编辑器 总结