React项目全球新闻发布管理系统 - 新版问题解决方式整理及部署网站至 Netlify

article/2025/11/4 5:18:02

整理了一下新版的变化以及遇到的坑的解决办法,最后也会分享将网站及接口部署的方式。

千锋前端-React全家桶_React项目全球新闻发布管理系统
https://www.bilibili.com/video/BV1fw411d7R5

文章目录

    • P4
    • P5
    • P6
    • P11
    • P15
    • P17
    • P18
    • P22
    • P29
    • P30
    • P34
    • P38
    • P41
    • P43
    • P45
    • P50
    • P67
    • 进阶: 多语系网站
    • 接口数据
    • 部署网站及接口

P4

反向代理 setupProxy.js 改为:

// setupProxy.js
const { createProxyMiddleware } = require('http-proxy-middleware');module.exports = function(app) {app.use('/api',createProxyMiddleware({target: 'https://i.maoyan.com',changeOrigin: true,}));
};

App.js 中 axios 请求地址改为:

// App.js
useEffect(() => {axios.get("/api/mmdb/movie/v3/list/hot.json?ct=%E7%B9%81%E6%98%8C%E5%8C%BA&ci=774&channelId=4").then((res) => console.log(res.data));}, []);

因为我没有看到视频中用的 m.maoyan.com/ajax 那个地址,所以我自己抓了一个,反正是练习用的就不必那么讲究了,测试一下 middleware 能不能使就行。

P5

15 分处

  • HashRouter 里面包 Switch 会出错,请改为包 Routes (Routes 和 Switch 的功用是一样的,都能做到精准匹配)
  • Route 的 component 属性改为 element,并且 element 中请使用 <> 包裹组件名称
// IndexRouter.js
import React from "react";
import { HashRouter, Routes, Route } from "react-router-dom";
import Login from "../views/login/Login";
import NewsSandBox from "../views/newssandbox/NewsSandBox";export default function IndexRouter () {return (<HashRouter><Routes><Route path="/login" element={<Login/>} /><Route path="/" element={<NewsSandBox/>} /></Routes></HashRouter>);
};

20 分处重定向部分

For react-router-dom v6, simply replace Redirect with Navigate

所以我们需要将 Redirect 改为 Navigate,并且一样使用的是 element 而不是 render

<Route path="/*" element={localStorage.getItem("token") ?  <NewsSandBox/> : <Navigate to="/login"/>} />

P6

14 分处,一样将所有 Switch 改为 Routes, component 改为 element,Redirect 改为 Navigate

//NewsSandBox.js
<Routes><Route path="home" element={<Home />} /><Route path="user-manage/list" element={<UserList />} /><Route path="right-manage/role/list" element={<RoleList />} /><Route path="right-manage/right/list" element={<RightList />} /><Route path="/" element={<Navigate replace from="/" to="home"/>} /><Route path="/*" element={<NoPermission/>} />
</Routes>

并且要记住的是 Navigate 只能包裹在 Route 中,Routes 中只能有 Route 或者 Fragment

P11

使用 withRouter 会报错:

‘withRouter’ is not exported from ‘react-router-dom’.

因为 V6 之后没有 withRouter 了,所以直接改用 useNavigate 会更方便,完整的 Code 我直接放出来吧:

import React from "react";
import { Layout, Menu } from "antd";
import { UserOutlined, HomeOutlined, CrownOutlined } from "@ant-design/icons";
import SubMenu from "antd/lib/menu/SubMenu";
import { useNavigate } from "react-router";const { Sider } = Layout;const menuList = [{key: "/home",title: "首页",icon: <HomeOutlined />,},{key: "/user-manage",title: "用户管理",icon: <UserOutlined />,children: [{key: "/user-manage/list",title: "用户列表",icon: <UserOutlined />,},],},{key: "/right-manage",title: "权限管理",icon: <CrownOutlined />,children: [{key: "/right-manage/role/list",title: "角色列表",icon: <CrownOutlined />,},{key: "/right-manage/right/list",title: "权限列表",icon: <CrownOutlined />,},],},
];export default function SideMenu({ collapsed }) {let navigate = useNavigate();// Menuconst renderMenu = (menuList) => {return menuList.map((item) => {if (item.children) {return (<SubMenu key={item.key} icon={item.icon} title={item.title}>{renderMenu(item.children)}</SubMenu>);}return (<Menu.Itemkey={item.key}icon={item.icon}onClick={() => navigate(item.key)}>{item.title}</Menu.Item>);});};return (<Sider trigger={null} collapsible collapsed={collapsed}><div className="logo" /><Menu theme="dark" mode="inline" defaultSelectedKeys={["1"]}>{renderMenu(menuList)}</Menu></Sider>);
}

P15

props.location.pathname 可以用 useLocation 钩子取代,与 props.location 是一样的用法:

import { useNavigate, useLocation } from "react-router";
// ...
let location = useLocation();
const selectKeys = [location.pathname]; // ex: ['/home']
const openKeys = ["/" + location.pathname.split("/")[1]];
// ...
<Menu theme="dark" mode="inline" selectedKeys={selectKeys} defaultOpenKeys={openKeys}>{renderMenu(menu)}
</Menu>

P17

直接写 res.data[0].children = "" 的话一旦 array 内容顺序有所变更就会错误,因此建议透过遍历的方式寻找 children 长度为 0 的元素,将它改为空字串。

useEffect(() => {axios.get("http://localhost:8000/rights?_embed=children").then((res) => {res.data.forEach((item) => item.children?.length === 0 ? item.children = "" : item.children);setDataSource(res.data);})
}, []);

P18

个人认为删除后页面要会 auto refresh,所以我改写了一下,这样只要删除权限时就会修改 refresh 的状态,而 refresh 状态一变更就会重新 call API 获取数据。

// RightList.js
const [dataSource, setDataSource] = useState([]);
const [refresh, setRefresh] = useState(false);useEffect(() => {axios.get("http://localhost:8000/rights?_embed=children").then((res) => {res.data.forEach((item) =>item.children?.length === 0 ? (item.children = "") : item.children);setDataSource(res.data);});
}, [refresh]);const columns = [{title: "ID",dataIndex: "id",},{title: "权限名称",dataIndex: "title",},{title: "权限路径",dataIndex: "key",render: (key) => {return <Tag color="volcano">{key}</Tag>;},},{title: "操作",render: (item) => {return (<div><Buttondangershape="circle"icon={<DeleteOutlined />}style={{ marginRight: 10 }}onClick={() => confirmMethod(item)}/><Button type="primary" shape="circle" icon={<EditOutlined />} /></div>);},},];const confirmMethod = (item) => {confirm({title: "你确定要删除?",icon: <ExclamationCircleOutlined />,// content: "Some descriptions",onOk() {deleteMethod(item);},onCancel() {console.log("Cancel");},});};const deleteMethod = (item) => {if (item.grade === 1) {axios.delete(`http://localhost:8000/rights/${item.id}`).then(setRefresh).catch((e) => console.log(e))} else {axios.delete(`http://localhost:8000/children/${item.id}`).then(setRefresh).catch((e) => console.log(e))}
}

P22

分配权限的写法一样是改成 auto refresh,而不是改变状态。之后同样的处理我都不会再提了。

const [refresh, setRefresh] = useState(false);
// ...
useEffect(() => {axios.get("http://localhost:8000/roles").then((res) => setDataSource(res.data)).catch((e) => console.log(e));axios.get("http://localhost:8000/rights?_embed=children").then((res) => setRightList(res.data)).catch((e) => console.log(e));}, [refresh]);const handleOk = () => {setIsModalVisible(false);axios.patch(`http://localhost:8000/roles/${currentId}`, {rights: currentRights}).then(setRefresh).catch((e) => console.log(e))};

P29

一样将 Redirect 重定向改为使用 useNavigate

// components/sandbox/TopHeader.js
import { useNavigate } from "react-router";
// ...
let navigate = useNavigate();
// ...
<Menu.Item danger onClick={() => {localStorage.removeItem("token")navigate("/login");
}}>退出
</Menu.Item>

顺便分享一个干货,浏览器的devTools (chrome预设为 F12) - Application - Local Storage 可以看到你存在本地的所有东西,也就是你使用 localStorage.setItem 所记录下来的内容,你可以直接在这里设置或清除 token,也可以在 console 写代码修改,都是可以的。

在这里插入图片描述

P30

这个粒子库安装指令:

npm i tsparticles --save

如果按照官方文档使用 npm i react-particles-js 会报 Can't resolve 'tsparticles' in 'D:\...\...\node_modules\react-particles-js\cjs' 的错误。

P34

动态路由这部分因为 Switch 改为 Routes 且 Route 里面的 component 变为 element,组件带入的方式也不一样,因此 LocalRouterMap array 的 value 要用<>包裹组件:

import React, { useState, useEffect } from "react";
import Home from "../../views/sandbox/home/Home";
import RightList from "../../views/sandbox/right-manage/RightList";
import RoleList from "../../views/sandbox/right-manage/RoleList";
import UserList from "../../views/sandbox/user-manage/UserList";
import NewsCategory from "../../views/sandbox/news-manage/NewsCategory";
import NewsAdd from "../../views/sandbox/news-manage/NewsAdd";
import NewsDraft from "../../views/sandbox/news-manage/NewsDraft";
import NoPermission from "../../views/sandbox/nopermission/NoPermission";
import Audit from "../../views/sandbox/audit-manage/Audit";
import AuditList from "../../views/sandbox/audit-manage/AuditList";
import Unpublished from "../../views/sandbox/publish-manage/Unpublished";
import Published from "../../views/sandbox/publish-manage/Published";
import Sunset from "../../views/sandbox/publish-manage/Sunset";
import { Routes, Route, Navigate } from "react-router-dom";
import axios from "axios";
const LocalRouterMap = {"/home": <Home/>,"/user-manage/list": <UserList/>,"/right-manage/role/list": <RoleList/>,"/right-manage/right/list": <RightList/>,"/news-manage/add": <NewsAdd/>,"/news-manage/draft": <NewsDraft/>,"/news-manage/category": <NewsCategory/>,"/audit-manage/audit": <Audit/>,"/audit-manage/list": <AuditList/>,"/publish-manage/unpublished": <Unpublished/>,"/publish-manage/published": <Published/>,"/publish-manage/sunset": <Sunset/>,
};export default function NewsRouter() {const [backRouteList, setbackRouteList] = useState([]);useEffect(() => {Promise.all([axios.get("http://localhost:8000/rights"),axios.get("http://localhost:8000/children"),]).then((res) => {setbackRouteList([...res[0].data, ...res[1].data]);});}, []);return (<Routes>{backRouteList.map((item) => (<Routepath={item.key}key={item.key}element={LocalRouterMap[item.key]}/>))}<Route path="/" element={<Navigate replace from="/" to="/home" />} /><Route path="*" element={<NoPermission />} /></Routes>);
}

另外视频中说要在 Route 加上 exact 精准匹配,但如果你是安装最新版 React router dom (V6↑) 就不需要加,本身就会精准匹配。

P38

我用 className 的方式不成功,如果有跟我一样用 className 没办法隐藏其他步骤内容的可以改成使用 style 直接设内联样式:

<div style={{ display: current === 0 ? "" : "none" }}>111
</div><div style={{ display: current === 1 ? "" : "none" }}>222
</div><div style={{ display: current === 2 ? "" : "none" }}>333
</div>

P41

可以把 props.history.push 改成使用 useNavigate

import { useNavigate } from "react-router";
const navigate = useNavigate();
// ...
navigate(auditState === 0 ? "/news-manage/draft" : "/audit-manage/list");

P43

如果你和我一样无法使用 props.match.params.id 可以改为使用 useParams 这个 hook:

import React, { useEffect } from "react";
import { useParams } from "react-router";
import { PageHeader, Descriptions } from "antd";export default function NewsPreview(props) {const params = useParams();useEffect(() => {console.log(params.id); // 3}, []);//...
}

P45

props.history.goBack() 可以改为 navigate(-1)

import { useNavigate } from "react-router";
const navigate = useNavigate();<PageHeaderclassName="site-page-header"title={I18n.t("AddNew")}onBack={() => navigate(-1)}
/>
</PageHeader>

P50

之前 userlist 的筛选部分好像有漏掉,...list.filter((item) => item.username === username) 是加上自己的 user data, ...list.filter((item) => item.region === region && roleObj[item.roleId] === "editor" ) 又包括自己的 user data,所以需要加一个判断 && item.username !== username 避免重复把自己的 user data 放进去。

// views/sandbox/audit-manage/Audit.js
setdataSource(roleObj[roleId] === "superadmin"? list: [...list.filter((item) => item.username === username),...list.filter((item) =>item.region === region &&roleObj[item.roleId] === "editor" &&item.username !== username // add this line),]
);

P67

不能重复点赞的部分我是用 localStorage 简单实现的,会有很多弊端,不过暂时能用就行。

// Detail.js
import { useParams } from "react-router";
// ...
const params = useParams();
const [newsInfo, setNewsInfo] = useState([]);
let star = localStorage.getItem("star") || [];
// ...
const handleStar = () => {if (!star.includes(params.id.toString())) {updateNews(params.id, {star: newsInfo.star + 1,}).then(() => {setRefresh();const arr = [...star];localStorage.setItem("star", arr.concat(params.id));}).catch((e) => console.log(e));} else {notification.info({message: I18n.t("error"),description: I18n.t("starError"),placement: "bottomRight",});}
};

进阶: 多语系网站

多语系的部分我个人是习惯用 I18n,安装指令如下:

npm install i18n-js

在 src 底下新增一个 i18n 资料夹,里面包括:i18n.jsen.jszh-cn.jszh-tw.js

// i18n.js
import I18n from "i18n-js";
import zhTW from "./zh-tw";
import zhCN from "./zh-cn";
import en from "./en";I18n.missingTranslation = (scope) => {return scope;
};
I18n.translations = {"zh-tw": zhTW,"zh-cn": zhCN,en,
};export default I18n;
// zh-cn.js
export default {// LoginTitle: "全球新闻发布管理系统",Login: "登入",Username: "帐号",Password: "密码",Remember: "记住帐号",Forgot: "忘记密码",Or: "或者",Register: "注册"
}

然后把网站中所有会使用到的文字翻译成对应的语言存放在语系档案中,接着在需要使用到翻译的地方引入 I18n 就能够转换语系:

import I18n from "../../i18n/i18n";
// for example
<h2>{I18n.t("Title")}</h2>

我的习惯是写一个切换语系的menu在使用者操作处,点击语系之后就会调用下面这个方法:

const setLanguage = (locale) => {localStorage.setItem("locale", locale);window.location.reload();};

在这里插入图片描述
如此一来本地的 locale 就会更改,然后在 App.js 中我会写一个 useEffect 用于判别目前本地有没有储存语系,如果没有就使用浏览器语系navigator.language

import React, { useEffect } from "react";
import "./App.css";
import IndexRouter from "./router/IndexRouter";
import I18n from "i18n-js";function App() {useEffect(() => {if (localStorage.getItem("locale")) {I18n.locale = localStorage.getItem("locale");} else {I18n.locale = window.navigator.language.toLowerCase();}}, []);return (<IndexRouter></IndexRouter>);
}export default App;

接口数据

接口数据部分为个人看视频手打的,不保证完全一致。

{"news": [{"id": 1,"title": "11111","author": "admin","categoryId": 1,"region": "","roleId": 1,"auditState": 2,"publishState": 1,"content": "<p></p>測試\n","createTime": 1615780184222,"star": 0,"view": 0},{"id": 2,"title": "22222","author": "admin","categoryId": 3,"region": "","roleId": 1,"auditState": 2,"publishState": 1,"content": "<p></p>測試\n","createTime": 1615780132422,"star": 0,"view": 0}],"categories": [{"id": 1,"title": "时事新闻","value": "时事新闻"},{"id": 2,"title": "环球经济","value": "环球经济"},{"id": 3,"title": "科学技术","value": "科学技术"},{"id": 4,"title": "军事世界","value": "军事世界"},{"id": 5,"title": "世界体育","value": "世界体育"},{"id": 6,"title": "生活理财","value": "生活理财"}],"regions": [{"id": 1,"title": "亚洲","value": "亚洲"},{"id": 2,"title": "欧洲","value": "欧洲"},{"id": 3,"title": "北美洲","value": "北美洲"},{"id": 4,"title": "南美洲","value": "南美洲"},{"id": 5,"title": "非洲","value": "非洲"},{"id": 6,"title": "大洋洲","value": "大洋洲"},{"id": 7,"title": "南极洲","value": "南极洲"}],"roles": [{"id": 1,"roleName": "超级管理员","roleType": 1,"rights": ["/user-manage","/right-manage","/right-manage/role/list","/right-manage/right/list","/right-manage/role/update","/right-manage/role/delete","/right-manage/right/update","/right-manage/right/delete","/news-manage","/news-manage/list","/news-manage/add","/news-manage/update/:id","/news-manage/preview/:id","/news-manage/draft","/news-manage/category","/audit-manage","/audit-manage/audit","/audit-manage/list","/publish-manage","/publish-manage/unpublished","/publish-manage/published","/publish-manage/sunset","/home","/user-manage/delete","/user-manage/add","/user-manage/update","/user-manage/list"]},{"id": 2,"roleName": "区域管理员","roleType": 2,"rights": ["/user-manage","/user-manage/add","/user-manage/delete","/user-manage/update","/user-manage/list","/news-manage","/news-manage/list","/news-manage/add","/news-manage/update/:id","/news-manage/preview/:id","/news-manage/draft","/news-manage/category","/audit-manage","/audit-manage/audit","/audit-manage/list","/publish-manage","/publish-manage/unpublished","/publish-manage/published","/publish-manage/sunset","/home"]},{"id": 3,"roleName": "区域编辑","roleType": 3,"rights": ["/news-manage","/news-manage/list","/news-manage/add","/news-manage/update/:id","/news-manage/preview/:id","/news-manage/draft","/audit-manage","/audit-manage/list","/publish-manage","/publish-manage/unpublished","/publish-manage/published","/publish-manage/sunset","/home"]}],"users": [{"id": 1,"username": "admin","password": 123456,"roleState": true,"default": true,"region": "","roleId": 1},{"id": 2,"username": "铁锤","password": 123,"roleState": true,"default": false,"region": "亚洲","roleId": 2},{"id": 3,"username": "钢弹","password": 123,"roleState": true,"default": false,"region": "南极洲","roleId": 2},{"id": 4,"username": "诸葛山珍","password": 123,"roleState": true,"default": false,"region": "","roleId": 2},{"id": 5,"username": "西门吹灯","password": 123,"roleState": true,"default": false,"region": "南极洲","roleId": 2}],"rights": [{"id": 1,"title": "首页","key": "/home","pagepermission": 1,"grade": 1},{"id": 2,"title": "用户管理","key": "/user-manage","pagepermission": 1,"grade": 1},{"id": 7,"title": "权限管理","key": "/right-manage","pagepermission": 1,"grade": 1},{"id": 14,"title": "新闻管理","key": "/news-manage","pagepermission": 1,"grade": 1},{"id": 21,"title": "审核管理","key": "/audit-manage","pagepermission": 1,"grade": 1},{"id": 24,"title": "发布管理","key": "/publish-manage","pagepermission": 1,"grade": 1}],"children": [{"id": 3,"title": "添加用户","rightId": 2,"key": "/user-manage/add","grade": 2},{"id": 4,"title": "删除用户","rightId": 2,"key": "/user-manage/delete","grade": 2},{"id": 5,"title": "修改用户","rightId": 2,"key": "/user-manage/update","grade": 2},{"id": 6,"title": "用户列表","rightId": 2,"key": "/user-manage/list","pagepermission": 1,"grade": 2},{"id": 8,"title": "角色列表","rightId": 7,"key": "/right-manage/role/list","pagepermission": 1,"grade": 2},{"id": 9,"title": "权限列表","rightId": 7,"key": "/right-manage/right/list","pagepermission": 1,"grade": 2},{"id": 10,"title": "修改角色","rightId": 7,"key": "/right-manage/role/update","grade": 2},{"id": 11,"title": "删除角色","rightId": 7,"key": "/right-manage/role/delete","grade": 2},{"id": 12,"title": "修改权限","rightId": 7,"key": "/right-manage/right/update","grade": 2},{"id": 13,"title": "删除权限","rightId": 7,"key": "/right-manage/right/delete","grade": 2},{"id": 15,"title": "新闻列表","rightId": 14,"key": "/news-manage/list","grade": 2},{"id": 16,"title": "撰写新闻","rightId": 14,"key": "/news-manage/add","pagepermission": 1,"grade": 2},{"id": 17,"title": "新闻更新","rightId": 14,"key": "/news-manage/update/:id","routepermission": 1,"grade": 2},{"id": 18,"title": "新闻预览","rightId": 14,"key": "/news-manage/preview/:id","routepermission": 1,"grade": 2},{"id": 19,"title": "草稿箱","rightId": 14,"key": "/news-manage/draft","pagepermission": 1,"grade": 2},{"id": 20,"title": "新闻分类","rightId": 14,"key": "/news-manage/category","pagepermission": 1,"grade": 2},{"id": 22,"title": "审核新闻","rightId": 21,"key": "/audit-manage/audit","pagepermission": 1,"grade": 2},{"id": 23,"title": "审核列表","rightId": 21,"key": "/audit-manage/list","pagepermission": 1,"grade": 2},{"id": 25,"title": "待发布","rightId": 24,"key": "/publish-manage/unpublished","pagepermission": 1,"grade": 2},{"id": 26,"title": "已发布","rightId": 24,"key": "/publish-manage/published","pagepermission": 1,"grade": 2},{"id": 27,"title": "已下线","rightId": 24,"key": "/publish-manage/sunset","pagepermission": 1,"grade": 2}]
}

部署网站及接口

前置:需先注册 netlify(布署网站) 及 heroku (布署接口) 帐号及新增一个 Github repo。

  1. 首先确保 package.json 中的 dependencies 有 json-server,如果没有请记得安装:

    npm i --save json-server
    

    并且将专案中所有使用 axios 请求的网址后面加上 /api 比如 http://localhost:5000/news 改为 http://localhost:5000/api/news

  2. 在 project 文件夹中新增 netlify.toml

    [build]command = "CI= npm run build"[[redirects]]from = "/api/*"to = "https://<YourAppName>.herokuapp.com/api/:splat"status = 200[[redirects]]from = "/*"to = "/index.html"status = 200
    

    如此一来接口就会被布署到 heroku 上,打开 https://<YourAppName>.herokuapp.com/api/news 就可以看到 news 接口中的数据。
    并且 netlify.toml 中会重定向请求地址包括 api 的连结到 https://<YourAppName>.herokuapp.com/api/,比如原本在本地使用 json-server 请求 news 接口需要使用 http://localhost:<port>/news 现在可以透过 https://<YourAppName>.herokuapp.com/api/news 来获取 news 接口数据。

  3. 在project根目录底下的 package.json 里面 scripts 处加上 json-server-devjson-server-prod

    "scripts": {"start": "react-scripts start","build": "react-scripts build","test": "react-scripts test","eject": "react-scripts eject","json-server-dev": "nodemon mock-api/server.js","json-server-prod": "node mock-api/server.js"},
    
  4. 在根目录底下新增 Procfile (注意没有任何副档名):

    web: npm run json-server-prod
    
  5. 在根目录底下新增 mock-api 文件夹,并把 json-server 用到的 db.json 放入,再新增一个 server.js

    const jsonServer = require("json-server");
    const server = jsonServer.create();
    const router = jsonServer.router("mock-api/db.json");
    const middlewares = jsonServer.defaults();
    const port = process.env.PORT || 8000; // 自行改为你的接口 portserver.use(middlewares);
    server.use("/api", router);server.listen(port);
    

    最后将整个 project push 到 Github 上 (ex: yourUserName/NewsSystem),然后到 netlify 新增一个 site,选择连接 Github 的 NewsSystem repo,并且选择你使用的 branch,Build Command 输入 CI= npm run buildpublish directory 写上 build,然后等待 netlify 布署完你的网站就能正常浏览了。
    在这里插入图片描述
    在这里插入图片描述
    但此时你的接口还没有布署上去,所以网站也只是个空壳,所以我们需要到 heroku Create New App,新增完之后到 Deploy - Github 连接你的 Github NewsSystem repo 并且记得修改 branch,选择完 branch 之后启用自动布署 Enable Automatic Deploys,最后按下 Deploy Branch 将接口布署到 Heroku。
    在这里插入图片描述
    /在这里插入图片描述
    /
    在这里插入图片描述
    部署成功之后就能透过 heroku app 网址后面加上 /app 来访问接口:
    在这里插入图片描述
    在这里插入图片描述
    待 heroku 和 netlify 两个都布署完毕之后,你就可以透过 netlify 提供的网址浏览器的网站啦!

    因为你开启了自动布署,所以往后你只需要将修改的内容 push 到 Github 就会自动布署到 netlify 和 heroku,就不需要再两边都重新布署,省时省力!
    在这里插入图片描述


http://chatgpt.dhexx.cn/article/EK9y6Z8N.shtml

相关文章

Coolify系列01- 从0到1超详细手把手教你上手Heroku 和 Netlify 的开源替代方案

什么是Coolify 一款超强大的开源自托管 Heroku / Netlify 替代方案coolLabs是开源、自托管和以隐私为中心的应用程序和服务的统称 为什么使用Coolify 只需单击几下即可托管你的应用、数据库或其他开源服务&#xff0c;等。它是 Heroku 和 Netlify 的一个替代方案。通过 Cool…

.NET Framework 框架

20世纪90年代以来出现的3种典型的组件技术&#xff1a; 1&#xff09;OMC&#xff08;对象组件模型&#xff09;的CORBA 2&#xff09;Microsoft的COM/DCOM 3&#xff09;Sun公司的JavaBeans 在2002年&#xff0c;微软发布了.NET框架的第一个版本&#xff0c;声称其解决了旧问…

vercel和netlify部署代码并解决接口代理转发的问题(和Nginx功能一样)

前言 部署过程就不说了,部署完成后是这样子的 然后访问链接,无法访问 解决 依次点击 Settings–>Domains&#xff0c;在输入框中输入你的域名并点击 Add 按钮。 以此域名为例子demo.gshopfront.dreamlove.top为例,点击添加 我们前往域名管理系统,记录下绿色的值以腾讯云的…

部署Netlify站点博客

Netlify站点部署静态博客 今天尝试把站点部署在Netlify上&#xff0c;因为部署在GitHub Pages上&#xff0c;国内访问速度太慢了&#xff0c;所以就尝试一下别的站点&#xff0c;部署成功之后发现速度还是不太行&#xff0c;后边继续找找原因 Netlify 部署的地址在这里 下图…

使用Hexo+github+netlify快速搭建个人博客网站

1 写在开头 倒腾了好几天&#xff0c;算是做出了一个有点样子的个人博客网站。便学各位大佬也写一个搭建教程&#xff0c;总结一下个人踩坑经验&#xff0c;也希望能对他人略有作用。 博客演示&#xff08;欢迎来留言交流&#xff09; 为什么选择Hexo&#xff1f;答&#xff1…

使用 netlify 部署你的前端应用

我前几天写了一篇文章&#xff0c; 如果你想搭建一个博客 &#xff0c;其中提到了使用 netlify 做博客托管服务。 netlify 可以为你的静态资源做托管&#xff0c;就是说它可以托管你的前端应用&#xff0c;就像 github page 那样。不过&#xff0c;它不又只像 github page 那么…

netlify国内快吗

Netlify国内快吗? Netlify vs 21云盒子 Netlify 是一家提供 JamStack(静态网站)托管的平台&#xff0c;支持自动从Github等仓库拉取代码, 按自定义构建方式进行构建&#xff0c;最后把生成的静态网站进行发布; 在这基础上同时也支持自定义域名&#xff0c;自动申请SSL证书等…

Netlify前端自动化部署工具

一、使用github或者gitlab登陆netlify 首先&#xff0c;打开netlify网站(https://app.netlify.com/) 然后使用github或者gitlab账号登录。 二、根据github/gitlab仓库创建网站 点击New site from Git按钮&#xff1a; 根据你的仓库所在平台选择&#xff0c;以下三选一&#x…

使用netlify实现自动化部署前端项目(无服务器版本)

介绍 本文以 github仓库进行介绍关联netlify的无服务前端自动化部署。用途&#xff1a;个人网站设计、小游戏等当然这只是让你入门~具体细节等待你自己去探索 实现 打开官方网站 如果没有注册过的账户&#xff0c;你需要使用 github 去进行登录。注册完成后会自动给你提示填…

Netlify静态资源托管之部署自动化

关注「WeiyiGeek」公众号 将我设为「特别关注」&#xff0c;每天带你玩转网络安全运维、应用开发、物联网IOT学习&#xff01; 0x00 基础介绍 0x01 Netlify 使用 0x00 基础介绍 Q: Netlify 是什么? Netlify 是一个提供静态资源网络托管的综合平台&#xff0c;一个直观的基于…

如何使用netlify部署vue应用程序

什么是Netlify&#xff1f; Netlify是一个现代网站自动化系统&#xff0c;其JAM架构代表了现代网站的发展趋势。所谓JAM&#xff0c;就是指基于客户端JavaScript、可重用API和预构建Markup标记语言的三者结合。 有了Netlify&#xff0c;我们只要在本机Git中写前端代码&#xff…

C语言:strlen() --- 计算字符串长度

C语言 基础开发----目录 一、strlen() 简介 1. 函数原型 unsigned int strlen(char *str);2. 参数 str - - 要计算长度字符串的地址指针 3. 功能 计算给定字符串的字节长度&#xff0c;直到空字符结束&#xff0c;但不包括空字符。 4. 头文件 #include <string.h>…

C语言 计算字符串的长度

方法一&#xff1a; #include<stdio.h> int length(char *s){char *ps;while(*p!\0){ //等同于 *pp;}return p-s; } void main(){char s[32];printf("请输入一个字符串&#xff1a;");scanf("%s",&s);printf("长度为&#xff1a;%d",l…

计算字符串长度的五种方法

方法一&#xff1b; ------------------------------------------------------------ 方法二; ------------------------------------------------------------ 方法三&#xff1b; ------------------------------------------------------------ 方法四利用sizeof()&#x…

计算字符串长度

用三种方法计算字符串的长度&#xff1a; 字符串&#xff1a;“abcd”&#xff0c;在储存的时候&#xff0c;字符串的最后一位会以一个终止符 \0&#xff0c;所以求取的就是终止符前的字符数。 1.计数器方式 通过设置一个计数器&#xff0c;然后用指针字符串从头扫描&#xf…

计算字符串长度的三种方法(库函数 指针 )【详解】

目录 求字符串长度的一般原理方法一&#xff1a;strlen函数函数原型使用方法 方法二:指针整数方法三&#xff1a;指针-指针总结&#xff1a; 求字符串长度的一般原理 求字符串长度简单来说就是计算一个字符串(字符数组)中元素的个数即从数组头部计数&#xff0c;直到遇到字符串…

微信小程序怎么开发(小程序开发文档)

微信小程序是一种全新的连接用户与服务的方式&#xff0c;它可以在微信内被便捷地获取和传播&#xff0c;同时具有出色的使用体验。 2017年1月9日~2018年1月9日&#xff0c;小程序正式上线一周年。上线以来&#xff0c;小程序不断地释放新能力&#xff0c;迭代更新&#xff0c;…

【小程序】一文带你了解微信小程序开发(小程序注册/开发工具的下载)

&#x1f41a;作者简介&#xff1a;苏凉&#xff08;专注于网络爬虫&#xff0c;数据分析&#xff0c;正在学习前端的路上&#xff09; &#x1f433;博客主页&#xff1a;苏凉.py的博客 &#x1f310;系列专栏&#xff1a;小程序开发基础教程 &#x1f451;名言警句&#xff1…

小程序设计文档

状态码介绍 {status: 状态码msg: 消息data: 响应数据 }code解释0success1error10need_login2illegal_argument 介绍 用到的接口 小程序登陆能获取的信息有 wx.request的返回为 data里面包的是后端服务器返回的值 小程序登陆时序图 登陆流程 微信小程序wx.login()拿到…

微信小程序技术文档

1.只显示用户头像和名称&#xff08;简化代码&#xff09; <!-- 如果只是展示用户头像昵称&#xff0c;可以使用 <open-data /> 组件 --> <open-data type"userAvatarUrl"></open-data> <open-data type"userNickName"><…