清朝云资源网

作者: 清朝云

  • Ubuntu 普通用户操作 docker 提示无权限的解决办法


    共计 395 个字符,预计需要花费 1 分钟才能阅读完成。

    Docker 安装教程可以看 Linux 离线安装 Docker,但是 ubuntu 下一般都是使用普通用户,直接使用报错:

    Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get “http://%2Fvar%2Frun%2Fdocker.sock/v1.24/containers/json“: dial unix /var/run/docker.sock: connect: permission denied

    解决方式如下:

    # 添加docker用户组
    sudo groupadd docker
    # 将登陆用户加入到docker用户组中
    sudo gpasswd -a $USER docker
    # 更新用户组
    newgrp docker
    # 重启docker
    sudo systemctl restart docker

    提醒:本文发布于371天前,文中所关联的信息可能已发生改变,请知悉!

    Tips:清朝云网络工作室

  • 10月22日,星期二, 每天60秒读懂全世界!

    百度热搜新闻

    新闻来源:百度热搜榜

    1. 户籍人口第一大省支棱起来了

    2. 福建平潭发实弹射击航行警告

    3. 各地陆续进入赏秋季 金秋十月,神州大地色彩斑斓、风景如画。人们走出家门、尽赏美景,也带动了秋季旅游市场持续火热。

    4. 新郎坐300多斤实木沙发霸气入场 近日,河北一婚礼现场,新郎坐着300多斤实木沙发被伴郎抬着入场,引发关注。新郎:叫了好几声义父才答应的。

    5. 48岁奥运六金王罹患癌症晚期

    6. 中国军队严防死守中缅边境 近日,因缅甸冲突,大批缅甸民众聚集到中缅边境口岸。中国军队和武警严防死守中缅边境,誓死守卫祖国大门。

    7. iPhone16全系列降价

    8. 本轮牛市第一个10倍股出现了

    9. 3.2万种植牙咬个黄瓜片牙冠碎了 近日,一女子花费3.2万元种植牙,在咬了个黄瓜后,竟导致牙冠碎裂,想协商退款难达成一致。女子:准备通过法律途径维权。

    10. 100万房贷30年减少5.1万元

    —- 百度热搜新闻 End —-

    知乎新闻

    新闻来源:知乎日榜

    标题: 在中国古代,破产的男人的侍妾如何自处?
    链接: https://daily.zhihu.com/story/9775499
    ———————-
    标题: 之前听说增肌要做到力竭,为什么现在又提倡「不力竭」了?
    链接: https://daily.zhihu.com/story/9776310
    ———————-
    标题: 论文常用词汇 i.e.,e.g.,etc.,viz.,et al. 的前世今生
    链接: https://daily.zhihu.com/story/9776311
    ———————-
    标题: 瞎扯 · 如何正确地吐槽
    链接: https://daily.zhihu.com/story/9776346
    ———————-

    —- 知乎新闻 End —-

    IT之家新闻

    新闻来源:ITHome之家科技新闻

    标题: 雷军开法拉利 SUV 出街被拍,引发小米 SUV 设计猜想
    发布时间: 2024-10-21T15:05:18.833
    新闻简介: 近日小米公司创始人兼董事长雷军驾驶一辆红色的法拉利 Purosangue SUV 出街被拍,视频中雷军驾车正在左转待转区等待,并低头操作手机。
    ———————-
    标题: 为小米 15 手机发布铺路?“小米科技盛会”今起招募 500 位米粉
    发布时间: 2024-10-21T09:59:35.69
    新闻简介: 小米社区今日开启“小米科技盛会”米粉招募,报名时间为10月21日10时至10月25日18时, 活动具体日期待揭晓。
    ———————-
    标题: 导演张艺谋:只打算拍一部《三体》电影,目前处在剧本打磨阶段,已率先成立 AI 小组
    发布时间: 2024-10-21T11:35:37.767
    新闻简介: 对于《三体》电影的筹备进展情况,张艺谋透露目前《三体》电影还在打磨剧本,拍摄时间还不具体,计划用 4 到 5 年时间完成电影《三体》的摄制。#张艺谋首谈电影三体#
    ———————-
    标题: 日本又“拆”中国电动汽车,研究比亚迪、蔚来等便宜的秘诀
    发布时间: 2024-10-21T11:29:52.573
    新闻简介: 日本中部经济产业局10月7日举行了分享纯电动汽车技术动向的研修会,日本中部地区约70家汽车零部件企业访问了电动汽车的拆解展示设施,并学习中国产电动汽车的车身结构和零部件设计特点。
    ———————-
    标题: 《黑神话:悟空》发售两个月,仍有超十万玩家同时在线
    发布时间: 2024-10-21T10:48:48.663
    新闻简介: 根据 Steam 平台的统计数据,《黑神话:悟空》昨日最高依然有超 17 万玩家同时在线数,在人气排行榜上位列第 12 名。对于一款发布两个月的游戏而言,这样的表现已属不俗。
    ———————-
    标题: iQOO 13 手机部分配置信息公布:6150mAh 电池 + 120W 快充、自研电竞芯片 Q2
    发布时间: 2024-10-21T13:12:28.343
    新闻简介: iQOO 13 手机搭载骁龙 8 至尊版处理器 + 自研电竞芯片 Q2,使用多层石墨烯、7K 超大面积 VC 均热板进行散热;配备等效 6150mAh 电池,支持 120W 超快闪充。
    ———————-
    标题: 一加 13 手机发布会定档 10 月 31 日:“新十年”首款旗舰登场
    发布时间: 2024-10-21T08:40:40.17
    新闻简介: 一加创始人刘作虎今天上午宣布,一加“新十年的首款旗舰大作 ”——一加13手机发布定档 10 月 31 日下午 4 时。
    ———————-
    标题: OPPO Find X8 Pro 手机配备 5910mAh 冰川电池,对比上代机型容量提升 18%
    发布时间: 2024-10-21T11:44:11.737
    新闻简介: OPPO 官方今日宣布 Find X8 Pro 机型将配备 5910mAh 冰川电池,对比上代机型容量提升 18%、厚度减少 13%。
    ———————-
    标题: 《黑神话:悟空》拿下泰国 TGA 2024 年度最佳游戏,斩获多项殊荣
    发布时间: 2024-10-21T12:03:29.113
    新闻简介: 在本月 10 日至 20 日于泰国举行的 2024 年泰国游戏展(Thailand Game Show)上,《黑神话:悟空》斩获泰国 2024 游戏大奖(TGA 2024)多个重量级奖项,包括“最佳游戏指导”“最佳动作游戏”和“最佳 PC / 主机游戏”等荣誉,成最大赢家。
    ———————-
    标题: 一加 13 手机配置参数曝光:等深四微弧直屏、提供 24GB+1TB 版本
    发布时间: 2024-10-21T11:58:36.6
    新闻简介: 博主 @数码闲聊站 今日发文透露了一加 13 手机的配置参数,新机将配备等深四微弧直屏、提供 24GB+1TB 版本。
    ———————-
    标题: 微信读书 App 纯血鸿蒙原生版上架 HarmonyOS NEXT 应用市场:首个接入微信登录的鸿蒙原生应用
    发布时间: 2024-10-21T20:48:49.517
    新闻简介: IT之家从HarmonyOS NEXT 应用市场了解到,目前微信读书 App 纯血鸿蒙原生版已正式上架,安装包大小为114.4MB,版本号为0.9.0
    ———————-
    标题: 接近京东人士:“监管进驻京东金融”传闻严重失实
    发布时间: 2024-10-21T17:39:31.393
    新闻简介: 对于“京东金融遭遇监管工作组进驻,向部分银行拆借资金”等相关传闻,一位接近京东人士表示,相关信息严重失实。
    ———————-

    —- IT之家新闻 End —-

  • 10月22日,星期二, 每天60秒读懂全世界!

    百度热搜新闻

    新闻来源:百度热搜榜

    1. 户籍人口第一大省支棱起来了

    2. 福建平潭发实弹射击航行警告

    3. 各地陆续进入赏秋季 金秋十月,神州大地色彩斑斓、风景如画。人们走出家门、尽赏美景,也带动了秋季旅游市场持续火热。

    4. 新郎坐300多斤实木沙发霸气入场 近日,河北一婚礼现场,新郎坐着300多斤实木沙发被伴郎抬着入场,引发关注。新郎:叫了好几声义父才答应的。

    5. 48岁奥运六金王罹患癌症晚期

    6. 中国军队严防死守中缅边境 近日,因缅甸冲突,大批缅甸民众聚集到中缅边境口岸。中国军队和武警严防死守中缅边境,誓死守卫祖国大门。

    7. iPhone16全系列降价

    8. 本轮牛市第一个10倍股出现了

    9. 3.2万种植牙咬个黄瓜片牙冠碎了 近日,一女子花费3.2万元种植牙,在咬了个黄瓜后,竟导致牙冠碎裂,想协商退款难达成一致。女子:准备通过法律途径维权。

    10. 100万房贷30年减少5.1万元

    —- 百度热搜新闻 End —-

    知乎新闻

    新闻来源:知乎日榜

    标题: 在中国古代,破产的男人的侍妾如何自处?
    链接: https://daily.zhihu.com/story/9775499
    ———————-
    标题: 之前听说增肌要做到力竭,为什么现在又提倡「不力竭」了?
    链接: https://daily.zhihu.com/story/9776310
    ———————-
    标题: 论文常用词汇 i.e.,e.g.,etc.,viz.,et al. 的前世今生
    链接: https://daily.zhihu.com/story/9776311
    ———————-
    标题: 瞎扯 · 如何正确地吐槽
    链接: https://daily.zhihu.com/story/9776346
    ———————-

    —- 知乎新闻 End —-

    IT之家新闻

    新闻来源:ITHome之家科技新闻

    标题: 雷军开法拉利 SUV 出街被拍,引发小米 SUV 设计猜想
    发布时间: 2024-10-21T15:05:18.833
    新闻简介: 近日小米公司创始人兼董事长雷军驾驶一辆红色的法拉利 Purosangue SUV 出街被拍,视频中雷军驾车正在左转待转区等待,并低头操作手机。
    ———————-
    标题: 为小米 15 手机发布铺路?“小米科技盛会”今起招募 500 位米粉
    发布时间: 2024-10-21T09:59:35.69
    新闻简介: 小米社区今日开启“小米科技盛会”米粉招募,报名时间为10月21日10时至10月25日18时, 活动具体日期待揭晓。
    ———————-
    标题: 导演张艺谋:只打算拍一部《三体》电影,目前处在剧本打磨阶段,已率先成立 AI 小组
    发布时间: 2024-10-21T11:35:37.767
    新闻简介: 对于《三体》电影的筹备进展情况,张艺谋透露目前《三体》电影还在打磨剧本,拍摄时间还不具体,计划用 4 到 5 年时间完成电影《三体》的摄制。#张艺谋首谈电影三体#
    ———————-
    标题: 日本又“拆”中国电动汽车,研究比亚迪、蔚来等便宜的秘诀
    发布时间: 2024-10-21T11:29:52.573
    新闻简介: 日本中部经济产业局10月7日举行了分享纯电动汽车技术动向的研修会,日本中部地区约70家汽车零部件企业访问了电动汽车的拆解展示设施,并学习中国产电动汽车的车身结构和零部件设计特点。
    ———————-
    标题: 《黑神话:悟空》发售两个月,仍有超十万玩家同时在线
    发布时间: 2024-10-21T10:48:48.663
    新闻简介: 根据 Steam 平台的统计数据,《黑神话:悟空》昨日最高依然有超 17 万玩家同时在线数,在人气排行榜上位列第 12 名。对于一款发布两个月的游戏而言,这样的表现已属不俗。
    ———————-
    标题: iQOO 13 手机部分配置信息公布:6150mAh 电池 + 120W 快充、自研电竞芯片 Q2
    发布时间: 2024-10-21T13:12:28.343
    新闻简介: iQOO 13 手机搭载骁龙 8 至尊版处理器 + 自研电竞芯片 Q2,使用多层石墨烯、7K 超大面积 VC 均热板进行散热;配备等效 6150mAh 电池,支持 120W 超快闪充。
    ———————-
    标题: 一加 13 手机发布会定档 10 月 31 日:“新十年”首款旗舰登场
    发布时间: 2024-10-21T08:40:40.17
    新闻简介: 一加创始人刘作虎今天上午宣布,一加“新十年的首款旗舰大作 ”——一加13手机发布定档 10 月 31 日下午 4 时。
    ———————-
    标题: OPPO Find X8 Pro 手机配备 5910mAh 冰川电池,对比上代机型容量提升 18%
    发布时间: 2024-10-21T11:44:11.737
    新闻简介: OPPO 官方今日宣布 Find X8 Pro 机型将配备 5910mAh 冰川电池,对比上代机型容量提升 18%、厚度减少 13%。
    ———————-
    标题: 《黑神话:悟空》拿下泰国 TGA 2024 年度最佳游戏,斩获多项殊荣
    发布时间: 2024-10-21T12:03:29.113
    新闻简介: 在本月 10 日至 20 日于泰国举行的 2024 年泰国游戏展(Thailand Game Show)上,《黑神话:悟空》斩获泰国 2024 游戏大奖(TGA 2024)多个重量级奖项,包括“最佳游戏指导”“最佳动作游戏”和“最佳 PC / 主机游戏”等荣誉,成最大赢家。
    ———————-
    标题: 一加 13 手机配置参数曝光:等深四微弧直屏、提供 24GB+1TB 版本
    发布时间: 2024-10-21T11:58:36.6
    新闻简介: 博主 @数码闲聊站 今日发文透露了一加 13 手机的配置参数,新机将配备等深四微弧直屏、提供 24GB+1TB 版本。
    ———————-
    标题: 微信读书 App 纯血鸿蒙原生版上架 HarmonyOS NEXT 应用市场:首个接入微信登录的鸿蒙原生应用
    发布时间: 2024-10-21T20:48:49.517
    新闻简介: IT之家从HarmonyOS NEXT 应用市场了解到,目前微信读书 App 纯血鸿蒙原生版已正式上架,安装包大小为114.4MB,版本号为0.9.0
    ———————-
    标题: 接近京东人士:“监管进驻京东金融”传闻严重失实
    发布时间: 2024-10-21T17:39:31.393
    新闻简介: 对于“京东金融遭遇监管工作组进驻,向部分银行拆借资金”等相关传闻,一位接近京东人士表示,相关信息严重失实。
    ———————-

    —- IT之家新闻 End —-

  • Linux 终端复用工具 tmux 常用命令


    共计 1088 个字符,预计需要花费 3 分钟才能阅读完成。

    一、简介

    Tmux(Terminal Multiplexer)是一个终端复用工具,它可以创建多个会话(session),并在会话中可以创建多个窗口(window),在每个窗口又可以创建多个窗格(pane),关系如下图:

    Linux 终端复用工具 tmux 常用命令

    />

    • 会话:由tmux管理的最大单元。即使脱离了会话,会话也会继续在后台运行。

    • 窗口:这是存在于会话中的同一个标签。可以在一个会话中拥有多个窗口,可以在会话中创建和切换窗口,并在移动标签时切换整个屏幕。

    • 窗格。这是在窗口中存在的屏幕单元。可以在一个窗口中拥有多个窗格,如果把整个屏幕垂直分成两份,就会产生两个窗格。

    二、 安装

    安装非常简单。

    yum install -y tmux

    三、 基本命令

    tmux 的默认前缀(prefix)是 ctrl + b,也就是说以下相关的快捷键,需要先按下 ctrl + b 快捷键,松开后再按其他的键。

    1. 会话相关命令

    • tmux:创建新会话。
    • tmux new-session -s <会话名称>:创建具有指定名称的新会话。(tmux new -s )
    • tmux rename -t s1 s2:重命名会话s1为s2
    • tmux attach:重新连接到最后一次分离的会话。
    • tmux attach -t <会话名称>:连接到指定名称的会话。(tmux a -t)
    • tmux list-sessions:列出所有会话。(tmux ls)
    • tmux kill-session -t <会话名称>:删除会话
    • tmux kill-session -a -t s1:关闭除s1外的所有会话
    • tmux kill-server:删除所有会话
    • prefix d:分离当前会话。
    • prefix s:查看所有会话,并可以进行切换

    2. 窗口相关命令

    有多个窗口时,带有 * 的为激活的窗口,- 表示最后一个使用的窗口。

    • prefix c:创建新窗口。
    • prefix 数字键:切换到指定编号的窗口。
    • prefix p:切换到上一个窗口。
    • prefix n:切换到下一个窗口。
    • prefix l:在最后两个窗口之间切换。
    • prefix ,:重命名当前窗口。可用于标识窗口用来做什么用处
    • prefix &:删除当前窗口,并将焦点切换到上一个窗口。

    3. 窗格相关命令

    • prefix %:垂直划分当前窗格。
    • prefix “:水平划分当前窗格。
    • prefix q:输入指定编号切换窗格。
    • prefix z:某个窗格最大化最小化切换。
    • prefix w:查看所有窗格。
    • prefix 方向:在窗格之间切换焦点。
    • prefix 空格:在不同的布局中切换窗格。
    • prefix o:在当前窗口中循环切换焦点。
    • prefix &:删除所有窗格
    • exit:删除当前窗格,并将焦点切换到上一个窗格。

    更多命令可以参考:https://tmuxcheatsheet.com/

    提醒:本文发布于370天前,文中所关联的信息可能已发生改变,请知悉!

    Tips:清朝云网络工作室

  • tmux 断电自动保存会话


    共计 1049 个字符,预计需要花费 3 分钟才能阅读完成。

    tmux 可以很好的管理会话,但是重启断电的情况下会话会丢失,那有没有一种方式,能够自动保存会话,即使断电重启也能够恢复现场呢?

    答案是有的,可以通过安装 tmux-resurrect 和 tmux-continuum 插件来实现。

    • tmux-resurrect 手动保存恢复会话。prefix s 进行保存,prefix r恢复。
    • tmux-continuum 通过定时调用 tmux-resurrect 实现定时保存,和进入会话后自动恢复。

    这里涉及到插件,顺带说明一下插件如何安装和使用,注意这里需要 tmux 版本为 1.9 以上,使用如下命令查看版本:

    tmux -V

    首先,创建或修改 ~/.tmux.conf 文件,添加如下内容安装插件管理器和上面两个插件:

    # 插件管理器
    set -g @plugin 'tmux-plugins/tpm'
    # tmux优化和功能增强插件
    set -g @plugin 'tmux-plugins/tmux-sensible'
    # 会话自动保存插件
    set -g @plugin 'tmux-plugins/tmux-resurrect'
    set -g @plugin 'tmux-plugins/tmux-continuum'
    
    # 开启自动保存会话
    set -g @continuum-restore 'on'
    # 修改自动保存间隔,如果改成0则停止自动保存
    set -g @continuum-save-interval '15'
    
    # 初始化tmux插件管理器,必须放最下面一行
    run -b '~/.tmux/plugins/tpm/tpm'

    然后重新加载配置文件:

    tmux source ~/.tmux.conf

    接下来进入一个 tmux 会话。

    tmux

    在会话中使用 prefixshift + i(大写I)即可进行插件的安装,这里会发现无法输入,因为插件正在安装,需要耐心等待插件安装完成。

    tmux 插件管理器是用于管理和安装 tmux 插件的工具。它简化了插件的安装、更新和卸载过程,通常只需要几个命令即可完成安装过程。如果不是因为插件管理器,还需要先下载上面两个插件的源码,然后再进行配置才可以进行安装。

    自动保存会话固然好用,但是有时候会话太多,想要重新来过,这时候需要先清理自动保存的会话,再删除所有会话。

    # 删除所有自动保存的会话
    rm -rf ~/.local/share/tmux/resurrect
    # 删除所有当前会话
    tmux  kill-server

    如果找不到 ~/.local/share/tmux/resurrect 目录,那可能是其他目录,该目录下保存了需要 txt 文件,并以时间命名。

    提醒:本文发布于370天前,文中所关联的信息可能已发生改变,请知悉!

    Tips:清朝云网络工作室

  • Zsh 安装与配置


    共计 3684 个字符,预计需要花费 10 分钟才能阅读完成。

    一、简介

    zsh是一种Unix shell,类似于bash、sh、csh等命令行解释器。它是一种交互式的shell,可以让用户在终端中输入命令并与操作系统进行交互。zsh最初是为了扩展bash的功能而创建的,但现在已经成为了一种很受欢迎的 shell。

    与 bash 相比,zsh 有许多优点,其中包括:

    1. 自动补全:zsh 的自动补全功能比 bash 更强大。它可以根据当前光标位置和历史记录,自动推测出用户想输入的命令或参数,极大地提高了操作效率。
    2. 主题和插件:zsh 具有丰富的主题和插件库,用户可以通过这些扩展来改善其外观和功能。
    3. 强大的别名:zsh 支持复杂的别名,这些别名可以包含参数、条件和内嵌命令。
    4. 可定制性:zsh 允许用户自定义其外观、行为和键绑定,以满足个人偏好和需求。
    5. 强大的历史命令:zsh 的历史命令比 bash 更强大,可以实现更高级的搜索和过滤功能。

    二、安装

    运行如下命令即可安装。

    apt install git zsh -y

    刚安装的 zsh 是极其朴素的,并且功能也比较弱。所以需要安装 oh-my-zsh,它提供了一套预配置的 Zsh 配置文件、主题和插件,这样可以轻松地定制和管理 zsh。

    # curl 安装
    sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
    # wget 安装
    sh -c "$(wget -O- https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
    # fetch 安装
    sh -c "$(fetch -o - https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
    # 卸载
    # uninstall_oh_my_zsh 

    以上命令选择一条运行进行安装 zsh,如果网络不好可能无法下载,毕竟是 github,这个时候找一台可以访问脚本的机器把内容复制到新建的脚本,再进行安装也是可以的。

    三、配置

    所有的配置修改后都需要执行:

    source ~/.zshrc

    1. 其他配置

    为了更好的使用 zsh 的功能,准备将历史记录的条数增大一点,编辑 ~/.zshrc 文件,添加如下内容。

    # 修改zcompdump生成的文件目录
    source /etc/profile
    export ZSH_COMPDUMP=$ZSH/cache/.zcompdump-$HOST
    
    # 记录历史命令的文件
    HISTFILE=~/.history
    # 内存中记录历史命令条数
    HISTSIZE=10000
    # 历史文件中保存的历史命令数
    SAVEHIST=10000
    # 不同的终端会话历史命令合并到同一历史文件
    setopt appendhistory
    
    # 在source $ZSH/oh-my-zsh.sh之前配置升级相关内容
    # 始终回复否从不升级,可以通过 omz update 进行更新
    DISABLE_AUTO_UPDATE=true
    # 始终回复是并自动升级
    #DISABLE_UPDATE_PROMPT=true

    使用 history -f 可以查看命令运行的时间。

    2. 修改主题

    zsh 有很多主题可选择,主题链接:https://github.com/ohmyzsh/ohmyzsh/wiki/Themes

    选择 ~/.zshrc 文件,修改主题名称即可。

    #ZSH_THEME="robbyrussell"
    ZSH_THEME="agnoster"

    这里可能报错:

    (anon):12: character not in range

    ~/.zshrc 文件添加如下内容即可:

    export LC_ALL=C.UTF-8
    export LANGUAGE=C.UTF-8
    export LANG=C.UTF-8

    此外,可能还需要修改命令行的用户名或主机名。

    全部隐藏

    prompt_context(){}

    只隐藏主机

    prompt_context() {
      if [[ "$USER" != "$DEFAULT_USER" || -n "$SSH_CLIENT" ]]; then
        prompt_segment black default "%(!.%{%F{yellow}%}.)$USER"
      fi
    }

    只隐藏用户名

    prompt_context() {
      if [[ "$USER" != "$DEFAULT_USER" || -n "$SSH_CLIENT" ]]; then
        prompt_segment black default "%(!.%{%F{yellow}%}.)$HOST"
      fi
    }

    还可以限制显示的目录数,比如最大只显示三个:

    prompt_dir() {
       prompt_segment blue $CURRENT_FG '%3~'
    }

    3. 添加插件

    插件列表:https://github.com/ohmyzsh/ohmyzsh/wiki/Plugins-Overview

    这里只安装比较重要的两个插件:

    • zsh-autosuggestions插件:根据已经输入的命令,自动显示可能的补全建议
    • zsh-syntax-highlighting插件:高亮显示命令行语句
    # 安装 zsh-autosuggestions
    git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
    # 安装 zsh-syntax-highlighting
    git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting 

    然后编辑 ~/.zshrc 文件,找到 plugins 选项:

    plugins=(
            zsh-syntax-highlighting
            docker
            z
            colored-man-pages
    )

    4. 命令别名

    以下是我的重命名,请根据自己需要进行设置。

    # systemctl
    alias syse='sudo systemctl enable --now'
    alias sysd='sudo systemctl disable --now'
    alias sysr='sudo systemctl restart'
    alias syss='sudo systemctl status'
    
    # tmux
    alias tl='tmux ls'
    alias tlw='tmux list-windows'
    alias tlp='tmux list-panes'
    alias tka='tmux kill-server'
    alias tks='tmux kill-session'
    alias ta='tmux a -t dev || tmux new -t dev'
    
    # docker
    alias dp='docker ps'
    alias dr='docker restart'
    alias dl='docker logs'  
    alias dlf='docker logs -f'
    alias dei='docker exec -it'
    alias din='docker inspect -f "{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}"'
    alias runlike='docker run --rm -v /var/run/docker.sock:/var/run/docker.sock assaflavie/runlike -p'
    # 清理相关
    alias dsd='docker system df'
    alias dcp='docker container prune'
    alias dvp='docker volume prune'
    alias dip='docker image prune'
    alias dsp='docker system prune'
    alias dspa='docker system prune -a'
    
    # other
    alias dh='du -h ./ -d 1' # 查看当前目录占用
    alias psg='ps aux | grep' 
    alias ng='netstat -autnp | grep'
    alias wget='wget -c' # 支持断点续传
    alias now='date "+%Y-%m-%d %H:%M:%S"' # 查看并格式化当前时间
    alias size='f(){ du -sh $1* | sort -hr; }; f' # 查看文件/目录大小 - size /
    alias sek='f(){ find / -name $1; }; f' # 在根目录查找文件 - sek test
    alias sekc='f(){ find ./ -name $1; }; f' # 在当前目录查找文件 - sekc test
    alias ipe='curl ipinfo.io/ip' #  查看当前公网ip

    5. 清理无关文件

    # 删除历史记录文件
    rm -f .zsh_history
    # 删除zcompdump文件
    rm -f .zcompdump*

    提醒:本文发布于369天前,文中所关联的信息可能已发生改变,请知悉!

    Tips:清朝云网络工作室

  • tmux 配置文件分享


    共计 4690 个字符,预计需要花费 12 分钟才能阅读完成。

    复制了很多的 oh-my-tmux 相关配置,然后加入自己的配置。

    为了使用插件,需要先运行如下命令:

    # 安装tmp插件
    git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm

    tmp 插件时一个插件管理器,能够方便的进行其他插件的安装、升级和卸载。安装好插件管理器以后,编辑 ~/.tmux.conf,添加如下内容即可。

    # -- 通用 -------------------------------------------------------------------
    set -g default-terminal "screen-256color"
    
    setw -g xterm-keys on
    set -s escape-time 10                     # 设置按键序列的逃逸时间为 10 毫秒
    set -sg repeat-time 600                   # 设置按键重复超时时间为 600 毫秒,即按住一个键超过 600 毫秒后开始重复输入。
    set -s focus-events on                    # 开启焦点事件,以便在窗格之间切换时正确传递焦点。
    
    #set -g prefix2 C-a                        # 设置第二个前缀键为 C-a,使其与 GNU Screen 兼容。
    #bind C-a send-prefix -2                   # 绑定 C-a 键为发送前缀键(即发送第二个前缀键)。
    
    set -q -g status-utf8 on                  # 设置状态栏为 UTF-8 编码 (tmux < 2.2)
    setw -q -g utf8 on                        # 设置 tmux 内部使用的 UTF-8 编码
    
    set -g history-limit 5000                 # 设置命令历史记录的限制为 5000 条
    
    # 绑定 e 键为修改并重载 tmux.conf
    bind e new-window -n "$HOME/.tmux.conf" sh -c '${EDITOR:-vim} "$HOME/.tmux.conf" && "tmux" source "$HOME/.tmux.conf" \; display "$HOME/.tmux.conf sourced"'
    # 绑定 r 键为重载 tmux.conf
    bind r run '"tmux" source $HOME/.tmux.conf' \; display "$HOME/.tmux.conf sourced"
    
    # -- display -------------------------------------------------------------------
    
    set -g base-index 1           # 将窗口编号的起始值设置为 1  
    setw -g pane-base-index 1     # 将窗格编号的起始值设置为 1  
    
    setw -g automatic-rename on   # 启用自动重命名窗口功能,使窗口名称反映当前程序的名称。
    set -g renumber-windows on    # 在关闭窗口时重新编号其他窗口,以保持窗口编号的连续性
    
    set -g set-titles on          # 设置终端标题,使其显示当前 tmux 的会话和窗口信息。
    
    set -g display-panes-time 1500 # 设置窗格指示器显示的时间-prefix q
    set -g display-time 1000       # 设置状态消息显示的时间
    
    set -g status-interval 10     # 每隔 10 秒重新绘制状态栏,以更新状态信息。
    
    # 绑定 C-l 键为清除屏幕和历史记录的操作:
    bind -n C-l send-keys C-l \; run 'sleep 0.2' \; clear-history
    
    # 开启活动监测功能,当有活动(输入或输出)发生时,窗口会显示活动指示器。
    set -g monitor-activity on
    # 关闭可视化活动指示器,即窗口不会以可见方式显示活动指示器。
    set -g visual-activity off
    
    # -- navigation ----------------------------------------------------------------
    
    # 将 C-c 键绑定为创建新的 tmux 会话。
    bind C-c new-session
    
    # 将 C-f 键绑定为打开命令提示符,可以输入会话名称来查找并切换到指定的会话。
    bind C-f command-prompt -p find-session 'switch-client -t %%'
    
    # 将 BTab 键(即 Shift+Tab 键)绑定为切换到上一个会话。
    bind BTab switch-client -l
    
    # 将 - 键绑定为在当前窗口中水平分割窗格。
    bind - split-window -v
    # 将 _ 键绑定为在当前窗口中垂直分割窗格。
    bind _ split-window -h
    
    # 绑定 hjkl 为切换窗格,默认可以用箭头的上下左右
    bind -r h select-pane -L
    bind -r j select-pane -D
    bind -r k select-pane -U
    bind -r l select-pane -R
    bind > swap-pane -D       # 将 > 键绑定为将当前窗格与下一个窗格进行交换。
    bind < swap-pane -U       # 将 < 键绑定为将当前窗格与上一个窗格进行交换。
    
    # 将 + 键绑定为最大化当前窗格的命令。
    bind + run "cut -c3- '#{TMUX_CONF}' | sh -s _maximize_pane '#{session_name}' '#D'"
    
    # 将 HJKL 绑定为调整当前窗格大小两个单位
    bind -r H resize-pane -L 2
    bind -r J resize-pane -D 2
    bind -r K resize-pane -U 2
    bind -r L resize-pane -R 2
    
    # window navigation
    unbind n
    unbind p
    bind -r C-h previous-window # 将 Ctrl+h 键绑定为选择上一个窗口。
    bind -r C-l next-window     # 将 Ctrl+l 键绑定为选择下一个窗口。
    bind Tab last-window        # 将 Tab 键绑定为切换到上一个活动的窗口。
    
    # 将 m 键绑定为切换鼠标模式的命令
    bind m run "cut -c3- '#{TMUX_CONF}' | sh -s _toggle_mouse"
    
    # -- 窗口及状态栏 ----------------------------------------------------------------
    # default statusbar colors
    #set -g status-bg black
    #set-option -g status-left '#[bg=black,fg=green][#[fg=cyan]#S#[fg=green]]'
    #setw -g window-status-format '#[fg=brightblue] #I #W '
    #setw -g window-status-current-format '#[fg=magenta,bold] #I #W:#F'
    #setw -g window-status-activity-style bg=black
    #setw -g window-status-bell-style bg=black
    
    # -- 其他 ----------------------------------------------------------------
    set-option -g prefix M-q                  # 设置前缀键为 M-q(Alt-q)
    bind-key M-q send-prefix                  # 绑定 M-q 键为发送前缀键
    
    # 将回车键绑定为在当前窗口中水平分割窗格。
    bind-key Enter split-window -v
    # 将 | 键绑定为在当前窗口中垂直分割窗格。
    bind-key | split-window -h
    
    # tmux2.1之后启用鼠标支持
    #set-option -g mouse on
    # tmux2.1之前
    #set -g mouse-resize-pane on #鼠标调整窗格
    #set -g mouse-select-pane on #鼠标选择窗格
    #set -g mouse-select-window on #拷贝模式鼠标选择文本
    #set -g mode-mouse on #鼠标支持开关
    
    # 设置关闭当前窗格为esc键
    bind-key Escape confirm-before -p "kill-pane? (y/n)" kill-pane
    
    # 绑定 Alt-m 删除其他会话
    bind-key -n M-m run-shell "rm -rf ~/.local/share/tmux/resurrect; tmux list-sessions | grep -v 'attached' | cut -d ':' -f 1 | xargs -I {} tmux kill-session -t {}; tmux send-keys ls Enter"
    
    # -- 插件----------------------------------------------------------------
    # 插件地址:https://github.com/tmux-plugins/list
    set -g @plugin 'tmux-plugins/tpm' # 安装 shift+i、升级 shift+u、卸载 Alt+u
    set -g @plugin 'tmux-plugins/tmux-sensible'
    set -g @plugin 'tmux-plugins/tmux-resurrect' # 可以保存和恢复会话
    set -g @plugin 'tmux-plugins/tmux-continuum' # 自动保存和恢复会话
    set -g @plugin 'tmux-plugins/tmux-sidebar' # 可以使用 prefix tab 查看目录树
    set -g @plugin 'dracula/tmux'
    #set -g @plugin 'erikw/tmux-powerline'  
    
    # dracula 主题配置:https://draculatheme.com/tmux
    # 需要使用的dracula插件
    set -g @dracula-plugins "time cpu-usage ram-usage"
    # 颜色配置
    set -g @dracula-time-colors "green dark_gray"
    set -g @dracula-cpu-usage-colors "pink dark_gray"
    set -g @dracula-ram-usage-colors "cyan dark_gray"
    # 状态栏配置
    set -g @dracula-show-powerline true
    # 设置左侧icon,可以为 `session`, `smiley`, `window` 或者其他符号,默认为smiley
    set -g @dracula-show-left-icon session
    
    # 设置时间格式
    set -g @dracula-time-format "%F %R"
    # 配置分隔符,不需要配置zsh即可实现箭头样式,但是需要设置为.tmux.conf文件内容需要设置为utf-8
    #set -g @dracula-show-left-sep 
    #set -g @dracula-show-right-sep 
    set -g @dracula-show-left-sep 
    set -g @dracula-show-right-sep 
    
    # 开启自动保存会话,文件在 ~/.local/share/tmux/resurrect
    set -g @continuum-restore 'on'  
    # 修改自动保存间隔,如果改成0则停止自动保存
    set -g @continuum-save-interval '15'
    
    # 初始化tmux插件管理器,必须放最下面一行
    run -b '~/.tmux/plugins/tpm/tpm'

    添加以后,执行如下命令重载配置:

    tmux source ~/.tmux.conf

    提醒:本文发布于368天前,文中所关联的信息可能已发生改变,请知悉!

    Tips:清朝云网络工作室

  • Ubuntu 22.04 锁屏不能远程连接的解决方案


    共计 674 个字符,预计需要花费 2 分钟才能阅读完成。

    最近安装了一个 Ubuntu 22.04,偶然发现可以通过 windows 自带的 rdp 远程工具进行连接,内心狂喜。此外,还支持启动 VNC 协议,也就是默认支持了 rdp 和 vnc 连接。

    Ubuntu 22.04 锁屏不能远程连接的解决方案

    />

    看了以下,ubuntu 在用户级别下创建了一个远程桌面服务,具体文件是 /usr/lib/systemd/user/gnome-remote-desktop.service。这个服务开机是不会自动启动的,需要开机后受首次进行登录才会启动,此时才可以进行远程桌面连接。

    但是 ubuntu 在锁屏的时候为了安全,锁屏时会断开远程桌面连接,并且没有提供在锁屏时也可以远程桌面的功能。

    所以此时需要通过安装扩展来进行配置:

    sudo apt install gnome-shell-extension-manager

    安装 gnome 的扩展管理器后打开,并在 browser 页里搜索 allow locked remoted desktop,点击进行安装即可。注意,这里如果配置了代理,可能出现闪退的情况。

    Ubuntu 22.04 锁屏不能远程连接的解决方案

    />

    点注销会导致系统关闭远程服务,所以想要再次连接,需要 Win + L 锁屏,而不是注销。此外,切换用户会导致无法远程连接,因为远程桌面服务是用户级别的。

    顺便提一下,之前的 vnc 远程方案,使用 vnc viewer 直接连接会是黑屏,需要修改一下配置。选择属性,修改选项里面的图片质量,之前默认是 Auto,修改为其他选项均可。

    Ubuntu 22.04 锁屏不能远程连接的解决方案

    />

    其实,如果运行了 ssh 服务,还可以通过 ssh 运行如下面命令解锁会话:

    sudo loginctl unlock-sessions

    可以和上面的插件达到相同的效果,但是每次都需要这样操作,还是建议直接使用插件。

    提醒:本文发布于366天前,文中所关联的信息可能已发生改变,请知悉!

    Tips:清朝云网络工作室

  • Vue3 项目初始化


    共计 23833 个字符,预计需要花费 60 分钟才能阅读完成。

    1. 初始化项目

    vue3 推荐使用 vite 进行项目构建,vite 官方中文文档参考:cn.vitejs.dev/guide/

    另外包管理工具推荐 pnmp,其号称高性能的 npmpnmpnpm/yarn 衍生而来,解决了 npm/yarn 内部潜在的 bug,极大的优化了性能,扩展了使用场景。

    pnpm 安装:

    npm i -g pnpm

    项目初始化:

    pnpm create vite@latest

    过程如下:

    >pnpm create vite@latest  
    ✔ Project name: … web  
    ✔ Select a framework: › Vue  
    ✔ Select a variant: › TypeScript

    此时可以执行如下命令安装依赖:

    pnpm i

    执行如下命令可以启动项目:

    pnpm run dev

    2. 项目配置

    2.1 配置 eslint

    ESLint 是一个用于识别和修复 JavaScript 代码问题的静态代码分析工具。参考了:ESLint 9.0版本踩坑记录

    生成配置文件 eslint.config.js

    npm init @eslint/config@latest

    过程如下:

    >npx eslint --init
    You can also run this command directly using 'npm init @eslint/config@latest'.
    Need to install the following packages:
    @eslint/create-config@1.3.1
    Ok to proceed? (y) y
    @eslint/create-config: v1.3.1
    
    ✔ How would you like to use ESLint? · problems
    ✔ What type of modules does your project use? · esm
    ✔ Which framework does your project use? · vue
    ✔ Does your project use TypeScript? · typescript
    ✔ Where does your code run? · browser
    The config that you've selected requires the following dependencies:
    
    eslint, globals, @eslint/js, typescript-eslint, eslint-plugin-vue
    ✔ Would you like to install them now? · No / Yes
    ✔ Which package manager do you want to use? · pnpm
    ☕️Installing...

    如果上面选择立即安装,会安装 @eslint/jseslinteslint-plugin-vueglobalstypescript-eslint 几个库。

    eslint.config.js 初始内容为:

    import globals from "globals";
    import pluginJs from "@eslint/js";
    import tseslint from "typescript-eslint";
    import pluginVue from "eslint-plugin-vue";
    
    export default [
      { 
        files: ["**/*.{js,mjs,cjs,ts,vue}"] 
      },
      { 
        languageOptions: { 
          globals: globals.browser 
        } 
      },
      pluginJs.configs.recommended,
      ...tseslint.configs.recommended,
      ...pluginVue.configs["flat/essential"],
      { 
        files: ["**/*.vue"], 
        languageOptions: { 
          parserOptions: { 
            parser: tseslint.parser 
          } 
        } 
      },
    ];

    简单测试一下 ESLint 是否能够正常使用:

    // eslint.config.js
    export default [
      { 
        files: ["**/*.{js,mjs,cjs,ts,vue}"],
        rules: {
          semi: 2,  //`semi` 规则用于检查是否缺少分号
        },
      },
      //...
    ];

    执行如下命令:

    npx eslint src/main.ts

    控制台提示缺少分号的错误,说明 rules 配置生效。

    然后需要修改 eslint.config.js 配置文件:

    import globals from 'globals';
    import pluginJs from '@eslint/js';
    import tseslint from 'typescript-eslint';
    import pluginVue from 'eslint-plugin-vue';
    
    export default [
      {
        ignores: [
          'node_modules',
          'dist',
          'public',
        ],
      },
      {
        files: ['**/*.{js,mjs,cjs,ts,vue}'],
      },
      {
        languageOptions: {
          globals: globals.browser,
        },
      },
      pluginJs.configs.recommended,
      ...tseslint.configs.recommended,
      ...pluginVue.configs['flat/essential'],
      {
        files: ['**/*.vue'],
        languageOptions: {
          parserOptions: {
            parser: tseslint.parser,
            ecmaVersion: 'latest',
            /** 允许在 .vue 文件中使用 JSX */
            ecmaFeatures: {
              jsx: true,
            },
          },
        },
        rules: {
          // 在这里追加 Vue 规则
          'vue/no-mutating-props': [
            'error',
            {
              shallowOnly: true,
            },
          ],
          // 关闭组件命名规则
          "vue/multi-word-component-names": "off"
        },
      },
      /**
       * JavaScript 规则
       */
      {
        files: ['**/*.{js,mjs,cjs,vue}'],
        rules: {
          'no-console': 'warn',
        },
      },
      /**
       * TypeScript 规则
       */
      {
        files: ['**/*.{ts,tsx,vue}'],
        rules: {
          // 这里可以添加 TypeScript 相关的规则
        },
      },
    ];

    package.json 新增两个运行脚本:

    "scripts": {
        "lint": "eslint src",
        "fix": "eslint src --fix"
    }

    不确定是否需要安装 vite-plugin-eslint…

    2.2 配置 prettier

    上面配置的 eslint 主要检查语法问题以及少部分格式,而 prettier 则只规范格式,比如强制使用双引号,以及空格的数量,这样可以做到风格统一。

    安装依赖包:

    pnpm install -D prettier eslint-plugin-prettier

    eslint 在 8.53.0 之后就不再支持格式化规则,因此 eslint-config-prettier 作为 eslintprettier 的规则冲突解决插件,已经没用了。

    创建 prettier.config.js 配置文件:

    // prettier.config.js
    /**
     * @type {import('prettier').Config}
     * @see https://www.prettier.cn/docs/options.html
     */
    export default {
        trailingComma: 'all',
        singleQuote: true,
        semi: false,
        printWidth: 80,
        arrowParens: 'always',
        proseWrap: 'always',
        endOfLine: 'auto',
        experimentalTernaries: false,
        tabWidth: 2,
        useTabs: false,
        quoteProps: 'consistent',
        jsxSingleQuote: false,
        bracketSpacing: true,
        bracketSameLine: false,
        jsxBracketSameLine: false,
        vueIndentScriptAndStyle: false,
        singleAttributePerLine: false,
    }

    eslint.config.js 文件新增内容:

    import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
    
    export default [
      // ...
      /**
       * prettier 配置
       * 会合并根目录下的 prettier.config.js 文件
       * @see https://prettier.io/docs/en/options
       */
      eslintPluginPrettierRecommended,
    ];

    2.3 配置 stylelint

    暂略…

    2.4 配置 husky

    暂略…

    2.5 强制使用 pnpm

    可以暂略…

    注意,目前似乎 npm 有 bug,导致 preinstall 脚本 在执行 npm install 后执行。

    团队开发项目的时候,需要统一包管理器工具,因为不同包管理器工具下载同一个依赖,可能版本不一样,导致项目出现 bug 问题。

    需要在项目根目录下创建 scritps/preinstall.js 文件:

    if (!/pnpm/.test(process.env.npm_execpath || '')) {
      console.warn(
        `\u001b[33mThis repository must using pnpm as the package manager ` +
        ` for scripts to work properly.\u001b[39m\n`,
      )
      process.exit(1)
    }

    然后配置 package.json 文件:

    "scripts": {
        "preinstall": "node ./scripts/preinstall.js"
    }

    此时使用 npm 或者 yarn 安装就会报错,原理就是在 install 的时候会触发 preinstall(npm 提供的生命周期钩子)这个文件里面的代码。

    3. 项目集成

    3.1 集成 element-plus

    当然了,这里根据自己需要进行 UI 组件库的集成。

    官网地址:https://element-plus.gitee.io/zh-CN/

    安装:

    pnpm install element-plus @element-plus/icons-vue

    main.ts 进行全局安装 element-plus,并配置中文:

    // ...
    import ElementPlus from 'element-plus'
    import 'element-plus/dist/index.css'
    import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
    
    const app = createApp(App)
    app.use(ElementPlus, {
        locale: zhCn
    })
    
    app.mount('#app')

    然后在 src/vite-env.d.ts 文件新增一行:

    declare module 'element-plus/dist/locale/zh-cn.mjs';

    element-plus 全局组件类型声明:

    似乎可以忽略

    // tsconfig.json
    {
      "compilerOptions": {
        // ...
        "types": ["element-plus/global"]
      }
    }

    配置完毕可以测试 element-plus 组件与图标的使用.

    3.2 其他配置

    • src 别名配置

    开发项目的时候文件与文件关系可能很复杂,因此我们需要给 src 文件夹配置一个别名。

    配置 vite.config.ts

    import path from 'path'
    
    export default defineConfig({
        resolve: {
            alias: {
                "@": path.resolve("./src") // 相对路径别名配置,使用 @ 代替 src
            }
        }
    })

    TypeScript 编译配置,在 tsconfig.json 文件中新增内容:

    {
      "compilerOptions": {
        "baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
        "paths": { //路径映射,相对于 baseUrl
          "@/*": ["src/*"] 
        },
      },
      // 不加这个可能在 vue 中引入组件有红色波浪线
      "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
    }
    • setup 插件

    vite-plugin-vue-setup-extend 的作用是为了方便定义组件名。

    安装:

    pnpm i vite-plugin-vue-setup-extend -D

    在 vite 配置文件中引入:

    import { defineConfig } from 'vite'
    import vue from '@vitejs/plugin-vue'
    import VueSetupExtend from 'vite-plugin-vue-setup-extend'
    
    export default defineConfig({
      plugins: [
        vue(),
        VueSetupExtend(),
      ],
    })

    比如原来需要两个 script 标签:

    <script lang="ts">
    export default {
        name: 'Login'
    }
    </script>
    
    <script setup lang="ts">
    </script>

    现在可以写出这样:

    <script setup lang="ts" name="Login">
    </script>

    3.3 配置环境变量

    项目开发过程中,至少会经历开发环境、测试环境和生产环境(即正式环境)三个阶段。不同阶段请求的状态(如接口地址等)不尽相同,若手动切换接口地址是相当繁琐且易出错的,于是环境变量配置的需求就应运而生。

    项目根目录分别添加开发、生产和测试环境的文件:

    #开发环境
    .env.development
    #生产环境
    .env.production
    #测试环境
    .env.test

    文件内容:

    NODE_ENV = 'development'
    VITE_APP_TITLE = 'APP'
    VITE_APP_BASE_API = '/api'
    NODE_ENV = 'production'
    VITE_APP_TITLE = 'APP'
    VITE_APP_BASE_API = '/api'
    NODE_ENV = 'test'
    VITE_APP_TITLE = 'APP'
    VITE_APP_BASE_API = '/api'

    变量必须以 VITE_ 为前缀才能暴露给外部读取,通过 import.meta.env 获取环境变量。

    console.log(import.meta.env.VITE_APP_TITLE);

    配置运行命令:

    "scripts": {
        "dev": "vite --host 0.0.0.0 --open",
        "build:test": "vue-tsc && vite build --mode test",
        "build:pro": "vue-tsc && vite build --mode production"
    },

    3.4 svg 图标配置

    svg 即矢量图标,文件比 img 要小的很多,且放大不会失真。

    安装插件:

    pnpm install vite-plugin-svg-icons -D

    配置 vite.config.ts

    import { defineConfig } from 'vite'
    import vue from '@vitejs/plugin-vue'
    import path from 'path'
    import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
    
    export default defineConfig({
      plugins: [
        vue(),
        createSvgIconsPlugin({
          // Specify the icon folder to be cached
          iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
          // Specify symbolId format
          symbolId: 'icon-[dir]-[name]',
        }),
      ],
      resolve: {
        alias: {
          '@': path.resolve('./src'), // 相对路径别名配置,使用 @ 代替 src
        },
      },
    })

    这里需要注意的是需要创建 src/assets/icons 文件夹,并将需要使用的 svg 文件放在该目录下。

    main.ts 配置:

    import 'virtual:svg-icons-register'

    然后可以将 svg 图标放在该文件下下,使用方式如下:

    <template>
        <!-- svg:图标外层容器节点,内部需要与 use 标签结合使用 -->
        <svg>
            <!-- 指定 href 属性,属性值务必 icon-图标名字 -->
            <!-- 会引用 icon 文件夹下的 vue.svg 图标 -->
            <use href="https://www.amjun.com/2665.html#icon-vue"></use>
        </svg>
    </template>

    因为项目很多模块需要使用图标,因此可以将其封装为全局组件,新建 src/components/SvgIcon/index.vue 文件:

    <template>
      <div>
        <svg :style="{ width: width, height: height }">
          <use :href="https://www.amjun.com/prefix + name" :fill="color"></use>
        </svg>
      </div>
    </template>
    
    <script setup lang="ts">
    defineProps({
      //xlink:href 属性值的前缀
      prefix: {
        type: String,
        default: '#icon-'
      },
      //svg 矢量图的名字
      name: String,
      //svg 图标的颜色
      color: {
        type: String,
        default: ""
      },
      //svg 宽度
      width: {
        type: String,
        default: '16px'
      },
      //svg 高度
      height: {
        type: String,
        default: '16px'
      }
    
    })
    </script>
    
    <style scoped></style>

    新建 src/components/index.ts 文件,用于注册 components 文件夹内部全部全局组件。

    import SvgIcon from './SvgIcon/index.vue';
    import type { App, Component } from 'vue';
    const components: { [name: string]: Component } = { SvgIcon };
    export default {
        install(app: App) {
            Object.keys(components).forEach((key: string) => {
                app.component(key, components[key]);
            })
        }
    }

    main.ts 安装自定义插件:

    import gloablComponent from '@/components/index';
    app.use(gloablComponent)

    使用:

    <script setup lang="ts">
    import SvgIcon from '@/components/SvgIcon/index.vue'
    </script>
    
    <template>
      <svg-icon name="vue"></svg-icon>
    </template>

    3.5 集成 less

    安装:

    pnpm install less less-loader -D

    在组件内可以使用 scss 语法,加上 lang="less" 即可。

    <style scoped lang="less"></style>

    新建 src/styles/reset.less 用于重置浏览器默认样式。

    /* https://github.com/ixyzorg/ixyz-style-reset.git */
    /* General reset */
    * {
        margin: 0;
        background: none;
        padding: 0;
        border: 0;
        font-size: 100%;
        font: inherit;
        vertical-align: baseline;
    }
    
    /* HTML5 display-role reset for older browsers */
    article,
    aside,
    details,
    figcaption,
    figure,
    footer,
    header,
    hgroup,
    menu,
    nav,
    section {
        display: block;
    }
    
    body {
        /* line-height is set by :root */
        line-height: inherit;
    }
    
    ol,
    ul {
        list-style: none;
    }
    
    blockquote,
    q {
        quotes: none;
    }
    
    blockquote:before,
    blockquote:after,
    q:before,
    q:after {
        content: '';
        content: none;
    }
    
    table {
        border-collapse: collapse;
        border-spacing: 0;
    }
    
    a {
        color: inherit;
        text-decoration: none;
    }
    
    s,
    u {
        text-decoration: none;
    }
    
    select {
        appearance: none;
        -webkit-appearance: none;
    }
    
    input[type="submit"],
    button {
        width: auto;
        overflow: visible;
        cursor: pointer;
        line-height: inherit;
        color: inherit;
    
        /* Corrects inability to style clickable `input` types in iOS */
        appearance: none;
    }
    
    button::-moz-focus-inner {
        /* Remove excess padding and border in Firefox 4+ */
        border: 0;
        padding: 0;
    }
    
    /* safari requires some special resets for input type="search" */
    input[type="search"] {
        -webkit-appearance: textfield;
    }
    
    input[type="search"]::-webkit-search-decoration {
        -webkit-appearance: none;
    }
    
    /* ie 11 has it's own magic font-size rules for sub and sup */
    @media all and (-ms-high-contrast: none),
    (-ms-high-contrast: active) {
    
        sub,
        sup {
            font-size: 120%;
        }
    }
    
    /* some sensible global styles */
    :root {
        /* prevents mobile browsers from sometimes scaling text */
        text-size-adjust: 100%;
        -webkit-text-size-adjust: 100%;
    }
    
    /* sets box-sizing back to the sane border-box for all elements */
    *,
    *::before,
    *::after {
        box-sizing: border-box;
    }

    新建 src/styles/index.less 文件,项目中需要用到清除默认样式,因此需要引入 reset.less

    @import './reset.less';

    然后在 main.ts 引入 index.less

    import '@/styles/index.less'

    但是在 index.less 全局样式文件中没有办法使用变量,因此需要给项目中引入全局变量。

    新建 src/styles/variable.less 文件:

    @color: red;

    然后配置 vite.config.ts 文件:

    export default defineConfig({
      css: {
        preprocessorOptions: {
          less: {
            javascriptEnabled: true,
            additionalData: '@import "./src/styles/variable.less";',
          },
        },
      },
    })

    variable.less 后面的分号不能忽略,否则会报错。

    vue 里面使用全局变量:

    <style scoped lang="less">
    h1 {
      color: @color;
    }
    </style>

    不加 lang="less" 也会导致无法使用变量。

    3.6 配置 mock

    mock 可以模拟数据来代替真实数据,在后端接口没有开发完成的情况下非常有用。

    安装依赖:

    pnpm install -D vite-plugin-mock mockjs

    vite.config.ts 配置文件启用插件。

    import { viteMockServe } from 'vite-plugin-mock'
    export default defineConfig({
      plugins: [
        viteMockServe({
          enable: true,
        }),
      ],
    })

    这里也可以使用 export default ({ command }) => { return {…}} 方式

    根目录下创建 mock 文件夹,再创建一个 user.ts 文件:

    注意不是 src 目录下创建!!!否则请求报 404 错误

    //用户信息数据
    function createUserList() {
        return [
            {
                userId: 1,
                avatar:
                    'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
                username: 'admin',
                password: '111111',
                desc: '平台管理员',
                roles: ['平台管理员'],
                buttons: ['cuser.detail'],
                routes: ['home'],
                token: 'Admin Token',
            },
            {
                userId: 2,
                avatar:
                    'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
                username: 'system',
                password: '111111',
                desc: '系统管理员',
                roles: ['系统管理员'],
                buttons: ['cuser.detail', 'cuser.user'],
                routes: ['home'],
                token: 'System Token',
            },
        ]
    }
    
    export default [
        // 用户登录接口
        {
            url: '/api/user/login', //请求地址
            method: 'post', //请求方式
            response: ({ body }) => {
                //获取请求体携带过来的用户名与密码
                const { username, password } = body
                //调用获取用户信息函数,用于判断是否有此用户
                const checkUser = createUserList().find(
                    (item) => item.username === username && item.password === password,
                )
                //没有用户返回失败信息
                if (!checkUser) {
                    return { code: 201, msg: '账号或者密码不正确' }
                }
                //如果有返回成功信息
                const { token } = checkUser
                return { code: 200, msg: '登录成功', data: { token } }
            },
        },
        // 获取用户信息
        {
            url: '/api/user/info',
            method: 'get',
            response: (request) => {
                //获取请求头携带token
                const token = request.headers.token
                //查看用户信息是否包含有次token用户
                const checkUser = createUserList().find((item) => item.token === token)
                //没有返回失败的信息
                if (!checkUser) {
                    return { code: 201, msg: '获取用户信息失败' }
                }
                //如果有返回成功信息
                return { code: 200, msg: '获取用户信息成功', data: { checkUser } }
            },
        },
    ]

    3.7 配置 axios

    axios 用于发送网络请求。

    安装:

    pnpm install axios

    然后可以在 main.ts 编写测试代码:

    // main.ts
    import axios from 'axios';
    
    axios({
      url: '/api/user/login',
      method: 'post',
      data: {
        username: 'admin',
        password: '111111'
      }
    })
    .then(response => {
      console.log(response.data);
    })
    .catch(error => {
      console.error('Error:', error);
    });

    项目上使用 axios 一般都会进行二次封装,目的是为了使用拦截器做一些特殊的处理。

    • 请求拦截器,可以在请求拦截器中处理一些业务(开始进度条、请求头携带公共参数)

    • 响应拦截器,可以在响应拦截器中处理一些业务(进度条结束、简化服务器返回的数据、处理http网络错误)

    首先创建 src/utils 文件夹,再创建 request.ts 文件:

    import axios from 'axios'
    import { ElMessage } from 'element-plus'
    
    //创建axios实例
    const service = axios.create({
      baseURL: import.meta.env.VITE_APP_BASE_API,
      timeout: 5000,
    })
    
    // 请求拦截器
    service.interceptors.request.use(
      (config) => {
        // 可以在此处添加一些公共的请求头,比如token
        // const token = localStorage.getItem('token');
        // if (token) {
        //   config.headers.Authorization = `Bearer ${token}`;
        // }
        return config
      },
      (error) => {
        // 请求错误时的处理
        ElMessage({
          type: 'error',
          message: '请求发送失败,请重试',
        })
        return Promise.reject(error)
      },
    )
    
    // 响应拦截器
    service.interceptors.response.use(
      (response) => {
        // 检查 response 是否存在,防止空响应引发的错误
        if (!response || !response.data) {
          ElMessage({
            type: 'error',
            message: '服务器返回异常',
          })
          return Promise.reject('服务器返回异常')
        }
    
        const res = response.data
        let msg = ''
    
        if (res.code === 200) {
          return res
        } else {
          switch (res.code) {
            // 这里是后端自定义的 code
            case 401:
              msg = '未授权,请登录'
              // 可以在这里触发登出逻辑,或跳转到登录页面
              break
            case 403:
              msg = '服务器拒绝访问'
              break
            case 500:
              msg = '服务器内部错误'
              break
            default:
              msg = res.msg || '请求失败'
          }
    
          ElMessage({
            type: 'error',
            message: msg,
          })
          return Promise.reject(msg)
        }
      },
      (error) => {
        let errorMsg = '请求失败,请检查网络连接'
        if (error.response) {
          switch (error.response.status) {
            case 404:
              errorMsg = '请求地址不正确'
              break
            default:
              errorMsg = `请求失败,状态码:${error.response.status}`
          }
        } else if (error.message.includes('timeout')) {
          errorMsg = '请求超时,请重试'
        }
    
        ElMessage({
          type: 'error',
          message: errorMsg,
        })
    
        return Promise.reject(error)
      },
    )
    
    // 公共参数
    interface CommonRequestOption {
      url: string
    }
    // get 请求参数
    export interface GetRequestOption extends CommonRequestOption {
      method: 'get' | 'GET'
      params?: object
    }
    // post 请求参数
    export interface PoseRequestOption extends CommonRequestOption {
      method: 'post' | 'POST'
      data?: object
    }
    
    function request<T>(options: GetRequestOption): Promise<ApiResponseData<T>>
    
    function request<T>(options: PoseRequestOption): Promise<ApiResponseData<T>>
    
    function request<T>(options: GetRequestOption | PoseRequestOption) {
      return service(options.url, options)
        .then((res) => res)
        .catch((err) => {
          return Promise.reject(err)
        }) as Promise<ApiResponseData<T>>
    }
    
    export default request

    此时发送数据变成了:

    // main.ts
    import request from '@/utils/request'
    
    request({
      url: '/user/login',
      method: 'post',
      data: {
        username: 'admin',
        password: '111111'
      }
    })
    .then(response => {
      console.log(response.data)
    })
    .catch(error => {
      console.error('Error:', error)
    });

    3.8 配置 api 目录

    一般项目中使用到的网络请求地址都会统一放在 src/api 目录。

    新建 src/api/login/index.ts 文件:

    import { request } from "@/utils/request"
    
    /** 登录并返回 Token */
    export function loginApi(data) {
      return request({
        url: "/user/login",
        method: "post",
        data
      })
    }

    由于使用的是 typescript,所以一般还会新建 src/api/login/types 文件夹,并在该文件夹下创建 login.ts 类型声明文件。

    export interface LoginRequestData {
      /** admin 或 editor */
      username: string
      /** 密码 */
      password: string
      // 如果需要验证码可以新增字段
    }
    
    export type LoginResponseData = { token: string }

    创建 src/types 文件夹,创建 api.d.ts 类型声明文件。

    /** 所有 api 接口的响应数据都应该准守该格式 */
    interface ApiResponseData<T> {
        code: number
        msg: string
        data: T
    }

    此时 src/api/login/index.ts 文件可修改为:

    import request from '@/utils/request'
    import type { LoginRequestData, LoginResponseData } from './types/login.ts'
    
    /** 登录并返回 Token */
    export function loginApi(data: LoginRequestData) {
      return request<LoginResponseData>({
        url: '/user/login',
        method: 'post',
        data,
      })
    }

    此时可以这样使用:

    //main.ts
    import { loginApi } from '@/api/login'
    
    loginApi({
      username: 'admin',
      password: '111111',
    })
    .then((response) => {
      console.log(response.data)
    })
    .catch((error) => {
      console.error('Error:', error)
    })

    3.9 配置 pinia

    pinia 是为 Vue 设计的状态管理库,类似于 Vuex。

    安装:

    pnpm install pinia

    新建 src/store/index.ts 文件:

    import { createPinia } from 'pinia'
    
    const store = createPinia()
    
    export default store

    main.ts 进行引入:

    import store from '@/store'
    
    const app = createApp(App)
    app.use(store)
    app.mount('#app')

    新建 src/store/modules/user.ts 文件:

    import { defineStore } from 'pinia'
    import { ref } from 'vue'
    import { loginApi } from '@/api/login'
    import { type LoginRequestData } from '@/api/login/types/login'
    
    export const useUserStore = defineStore('user', () => {
      //const token = ref<string>(getToken() || "")
      const token = ref<string>('')
    
      const login = async ({ username, password }: LoginRequestData) => {
        const { data } = await loginApi({ username, password })
        token.value = data.token
        console.log(data)
      }
    
      return { token, login }
    })

    然后可以进行使用:

    // main.ts
    import store from '@/store'
    import { useUserStore } from '@/store/modules/user'
    import { type LoginRequestData } from "@/api/login/types/login"
    import { reactive } from "vue"
    
    // 需要注册 pinia 后面再进行使用 
    //...
    app.use(store)
    app.mount('#app')
    
    const loginFormData: LoginRequestData = reactive({
      username: "admin",
      password: "111111"
    })
    
    useUserStore()
    .login(loginFormData)
    .then(() => {
      console.log('login success')
      //router.push({ path: "/" })
    })
    .catch(() => {
      console.log('login error')
      loginFormData.password = ""
    })

    3.10 配置路由

    这里使用的是 vue 官方推荐的 vue-router

    安装:

    pnpm install vue-router

    然后创建 src/router/index.ts 文件:

    import { createRouter, createWebHistory } from 'vue-router'
    import constantRoutes from './constantRoutes'
    
    // 创建路由器实例
    const router = createRouter({
      history: createWebHistory(),
      routes: constantRoutes,
    })
    
    const whiteList:Array<string> = ['/login', '/404']
    
    router.beforeEach((to, from, next) => {
      const token = localStorage.getItem('token');  // 先获取 token
    
      // 如果有 token,直接继续路由,且可以在此处加入权限控制
      if (token) {
        if (to.path === '/login') {
          return next({ path: '/dashboard' });
        }
    
        return next();  // 有权限则放行
      }
    
      // 如果没有 token,则检查是否在白名单内
      if (whiteList.includes(to.path)) {
        return next();
      }
    
      // 重定向到登录页面,并附加重定向路径
      next(`/login?redirect=${to.fullPath}`);
    });
    
    export default router

    上面配置了一个路由器,以及设置了导航守卫。其中引入了静态路由文件 src/router/constantRoutes.ts,内容如下:

    const constantRoutes = [
      {
        path: '/',
        component: () => import('@/components/Layout/index.vue'),
        children: [
          {
            path: 'dashboard', // 默认子路由
            name: 'dashboard',
            component: () => import('@/views/Dashboard/index.vue'),
          },
          {
            path: 'user', // 默认子路由
            name: 'user',
            component: () => import('@/views/User/index.vue'),
          },
          {
            path: '', 
            redirect: { name: 'dashboard' } 
          }
        ],
      },
      {
        path: '/login',
        name: 'login',
        component: () => import('@/views/Login/index.vue'),
      },
      // 通配符路由匹配所有不存在的路由
      {
        path: '/:pathMatch(.*)*', // 通配符
        name: 'notFound',
        component: () => import('@/views/NotFound/index.vue'),
      },
    ]
    
    export default constantRoutes

    这里又引入了几个页面组件,如 loginlogoutuser 组件等,后面再进行展示内容。这里配置完以后,需要在 main.ts 中引入并使用:

    import router from '@/router'
    
    //...
    app.use(router)
    app.mount('#app')

    以下是 constantRoutes.ts 中用到的组件:

    src/components/Layout/index.vue:

    <script setup lang="ts" name="Layout">
    import { Expand, Fold, User, HomeFilled, UserFilled } from '@element-plus/icons-vue'
    import { ref, onMounted} from 'vue'
    import router from '@/router';
    import { useRoute } from 'vue-router'
    import { ElMessage } from 'element-plus'
    import { useUserStore } from '@/store/modules/user'
    
    // 激活的菜单项
    const defaultActive = ref<string>(router.currentRoute.value.path) 
    const isCollapse = ref(false)
    
    const handleShow = () => {
      isCollapse.value = !isCollapse.value
    }
    
    const handleSelect = (index) => {
      router.push(index)
    }
    
    const handleOpen = (key: string, keyPath: string[]) => {
      //console.log(key, keyPath)
    }
    const handleClose = (key: string, keyPath: string[]) => {
      //console.log(key, keyPath)
    }
    
    const handleLogout = () => {
      useUserStore().logout()
    }
    
    const handleModifyPassword = () => {
      ElMessage({
        message: '别急,功能还没开发呢',
        type: 'warning',
      })
    }
    </script>
    
    <template>
      <div class="layout">
        <el-container>
          <el-aside width="collapse">
            <el-menu :default-active="defaultActive" :collapse="isCollapse" @open="handleOpen" @close="handleClose" @select="handleSelect">
              <router-link to="/">
                <div class="logo">
                  <div v-if="!isCollapse">Logo</div>
                  <div v-else>L</div>
                </div>
              </router-link>
              <el-menu-item index="/dashboard">
                <el-icon>
                  <home-filled />
                </el-icon>
                <template #title>主页</template>
              </el-menu-item>
              <el-menu-item index="/user">
                <el-icon>
                  <user-filled />
                </el-icon>
                <template #title>用户管理</template>
              </el-menu-item>
            </el-menu>
          </el-aside>
          <el-container class="layout-right">
            <el-header style="text-align: right; font-size: 12px">
              <div class="hamburger" @click="handleShow">
                <div v-if="!isCollapse">
                  <el-icon style="margin-top: 1px" :size="20">
                    <fold />
                  </el-icon>
                </div>
                <div v-else>
                  <el-icon style="margin-top: 1px" :size="20">
                    <expand />
                  </el-icon>
                </div>
              </div>
              <div class="toolbar">
                <el-dropdown>
                  <div class="profile">
                    <el-icon class="el-icon--left" :size="14" >
                      <user />
                    </el-icon>
                    <div>admin</div>
                  </div>
                  <template #dropdown>
                    <el-dropdown-menu>
                      <el-dropdown-item @click="handleModifyPassword">修改密码</el-dropdown-item>
                      <el-dropdown-item @click="handleLogout">退出登录</el-dropdown-item>
                    </el-dropdown-menu>
                  </template>
                </el-dropdown>
              </div>
            </el-header>
            <div class="main-content">
              <router-view></router-view>
            </div>
          </el-container>
        </el-container>
      </div>
    </template>
    
    <style scoped lang="less">
    @aside-bgcolor: #304156;
    @submenu-bgcolor: #1f2d3d;
    
    .layout {
      height: 100vh;
    }
    
    .el-container {
      height: 100%;
    }
    
    .el-aside {
      background-color: @aside-bgcolor;
      color: var(--el-color-white);
    
      .logo {
        height: 60px;
        font-size: 30px;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
      }
    
      .el-menu {
        --el-menu-bg-color: @aside-bgcolor;
        --el-menu-text-color: var(--el-color-white);
        --el-menu-active-color: var(--el-color-primary);
        border-right-width: 0;
      }
    
      .el-menu:not(.el-menu--collapse) {
        width: 170px;
        min-height: 400px;
      }
    
      .el-sub-menu li {
        background-color: @submenu-bgcolor;
      }
    }
    
    .el-header {
      position: relative;
      background-color: var(--el-color-white);
      color: var(--el-text-color-primary);
      box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
        var(--tw-shadow);
    
      display: flex;
      align-items: center;
      justify-content: space-between;
    
      .toolbar {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 100px;
        height: 100%;
        right: 20px;
    
        .profile {
          display: flex;
          /** 去除悬浮黑框 */
          outline: none;
        }
    
      }
    }
    
    .main-content {
      height: 100%;
      background-color: #f0f2f5;
    }
    </style>

    src/views/Dashboard/index.vue:

    <script setup lang="ts" name="Dashboard"></script>
    
    <template>
      <div class="main-content">
        <h1>我是首页</h1>
      </div>
    </template>
    
    <style lang="less">
    .main-content {
      display: flex;
      justify-content: center;
      align-items: center;
    
      h1 {
        font-size: 60px;
      }
    }
    </style>

    src/views/Login/index.vue:

    <script setup lang="ts" name="Login">
    import { User, Lock } from '@element-plus/icons-vue'
    import { useUserStore } from '@/store/modules/user'
    import { type LoginRequestData } from '@/api/login/types/login'
    import { reactive } from 'vue'
    import router from '@/router'
    import { useRoute } from 'vue-router'
    
    const loginFormData: LoginRequestData = reactive({
      username: 'admin',
      password: '111111',
    })
    
    const route = useRoute();
    
    async function handleLogin() {
      await useUserStore()
        .login(loginFormData);
    
      // 获取 URL 中的重定向参数(如果存在)
      const redirectPath = route.query.redirect || '/dashboard';
      // 重定向到指定路径
      router.push(redirectPath as string);
    }
    </script>
    
    <template>
      <div class="login">
        <el-row class="login-main">
          <el-col :span="15" :xs="0" class="login-left">
            <div class="login-left-text">
              <div class="text-line1">Welcome to</div>
              <div class="text-line2">Remind</div>
            </div>
          </el-col>
          <el-col :span="9" :xs="24" class="login-right">
            <el-form label-width="auto" class="login-form" :model="loginFormData">
              <h1 class="login-form-title">登录</h1>
              <el-input
                :prefix-icon="User"
                style="width: 100%"
                placeholder="请输入账号"
                v-model="loginFormData.username"
              />
              <el-input
                :prefix-icon="Lock"
                style="width: 100%"
                placeholder="请输入密码"
                v-model="loginFormData.password"
                type="password"
                show-password
              />
              <el-button class="login-form-button" @click.prevent="handleLogin">Login</el-button>
            </el-form>
          </el-col>
        </el-row>
      </div>
    </template>
    
    <style scoped lang="less">
    @login-main-color: #07294c;
    @shadow-color: rgba(100, 104, 104, 0.5);
    
    .login {
      height: 100vh;
      width: 100%;
      padding: 4% 8%;
    }
    
    .login-main {
      height: 100%;
    }
    
    // 使用BEM命名法,将 .login-left 和 .text 提升
    .login-left {
      font-weight: 300;
      color: var(--el-color-white);
      background-color: @login-main-color;
      display: flex;
      flex-flow: column;
      justify-content: center;
      align-items: center;
    }
    
    .login-left-text {
      margin: 20px 0 0 40px;
      font-size: 80px;
    }
    
    .login-right {
      box-shadow: 5px 5px 15px @shadow-color;
    }
    
    .login-form {
      height: 100%;
      display: flex;
      flex-flow: column;
      justify-content: center;
      align-items: center;
      gap: 20px;
      padding: 0 50px;
    }
    
    .login-form-title {
      font-size: 20px;
      font-weight: bold;
    }
    
    .login-form-button {
      color: var(--el-color-white);
      background-color: @login-main-color;
      font-weight: 400;
      //align-self: flex-end;
      height: 40px;
      width: 100%;
      padding: 10px 20px;
    }
    </style>

    src/views/NotFound/index.vue:

    <script setup lang="ts" name="NotFound"></script>
    
    <template>
      <div class="not-found">
        <h1>404</h1>
        <p>请求的路径不存在.</p>
        <router-link to="/">回到首页</router-link>
      </div>
    </template>
    
    <style scoped lang="less">
    .not-found {
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      height: 100vh;
      text-align: center;
    }
    
    h1 {
      font-size: 3rem;
      color: #ff6f61;
    }
    
    p {
      font-size: 1.5rem;
    }
    
    a {
      color: #409eff;
      font-size: 1.2rem;
      text-decoration: underline;
      margin-top: 1rem;
    }
    </style>

    src/views/User/index.vue:

    <script setup lang="ts" name="Dashboard"></script>
    
    <template>
      <div class="main-content">
        <h1>我是用户管理</h1>
      </div>
    </template>
    
    <style lang="less">
    .main-content {
      display: flex;
      justify-content: center;
      align-items: center;
    
      h1 {
        font-size: 60px;
      }
    }
    </style>

    4. 最终效果

    Vue3 项目初始化

    />

    Tips:清朝云网络工作室

  • 10月21日,星期一, 每天60秒读懂全世界!

    百度热搜新闻

    新闻来源:百度热搜榜

    1. 赵丽颖二封金鹰视后激动落泪 10月20日,第32届金鹰奖颁奖典礼在长沙举办。赵丽颖凭借《风吹半夏》许半夏一角获得第32届中国电视金鹰奖最佳女主角奖。

    2. 美媒:美国绝密情报遭泄露 当地时间10月19日,美媒报道称正在调查一起“高度机密情报泄露事件”,该情报涉及以色列报复伊朗的计划。

    3. 不能让界碑移动哪怕一毫米 一家三代用一生守护祖国界碑。不能让界碑移动哪怕一毫米,成为了家训铭刻于心!盛世中华何以中国,何以中国五星出东方。

    4. 一盆臭肉为何能“熏”倒多名官员 近日,云南昆明官渡区长丰学校食堂被指提供变质肉给学生食用。食品安全无小事,校园食品安全更需高度重视。

    5. 女子把降压药掰开吃致昏迷

    6. 女兵回忆大连舰遇外舰机滋扰 在大连舰训练遭外舰机模拟攻击滋扰时,女兵表示:国土一寸不让!若有一天战争来临,要第一时间顶上去,生死较量!

    7. 北京真的下雪了 10月20日,北京延庆佛爷顶出现降雪。北京初雪日最早为10月31日,平均为12月3日,北京延庆佛爷顶的降雪未达到初雪标准。

    8. 王景春金鹰奖最佳男配角

    9. 四川自贡观测到恶性外来入侵物种

    10. 俄方称若遭侵略俄白将使用核武器

    —- 百度热搜新闻 End —-

    知乎新闻

    新闻来源:知乎日榜

    标题: 小事 ·「谢谢,这歌儿真好听,谢谢。」
    链接: https://daily.zhihu.com/story/9776246
    ———————-
    标题: 很多人都认为大学知识跟高中没有关系,那为什么还要上高中呢?为什么不能初中毕业直接上大学?
    链接: https://daily.zhihu.com/story/9776259
    ———————-
    标题: 武松血溅鸳鸯楼时,张都监的手下为什么不来帮忙?
    链接: https://daily.zhihu.com/story/9776265
    ———————-
    标题: 这世界上最毒的物质是什么?
    链接: https://daily.zhihu.com/story/9776272
    ———————-

    —- 知乎新闻 End —-

    IT之家新闻

    新闻来源:ITHome之家科技新闻

    标题: 京东 Apple 活动升级: iPhone 16 系列直减 500 元、以旧换新补贴 1100 元
    发布时间: 2024-10-20T10:06:47.477
    新闻简介: 京东 Apple 全线产品“11.11”大促,iPhone 16 系列限时领取 500 元惊喜券,指定 iPhone 16 购机赠送一年期 AppleCare + 服务,以旧换新享大额补贴等,点此前往主会场。
    ———————-
    标题: 比亚迪宣布与《黑神话:悟空》游戏科学达成全球战略合作,扫描中国古建筑制作数字资产
    发布时间: 2024-10-20T09:16:44.213
    新闻简介: 据“比亚迪汽车”官方微博,比亚迪宣布与《黑神话:悟空》达成全球战略合作,成为其唯一合作的汽车品牌,双方号称将在“弘扬中国文化”等领域展开合作。
    ———————-
    标题: 华为鸿蒙 HarmonyOS NEXT 系统花粉 / 公测版本支持取消升级
    发布时间: 2024-10-20T10:41:46.983
    新闻简介: IT之家注意到,华为官网现更新 HarmonyOS NEXT 系统花粉 / 公测版本取消升级的方法,支持删除系统安装包、取消新系统推送。
    ———————-
    标题: 中国仿徕卡 M 相机,红旗 20
    发布时间: 2024-10-20T18:38:02.663
    新闻简介: 红旗 20 相机是我国为了纪念新中国建国 20 周年而研发的仿徕卡高级旁轴相机,采用徕卡 M 口相机,并且同步研发了 3 枚镜头。
    ———————-
    标题: 华为鸿蒙推送 HarmonyOS NEXT.0.0.72 版本,新增一键锁屏服务卡片等功能
    发布时间: 2024-10-20T07:51:16.617
    新闻简介: 目前,鸿蒙 HarmonyOS NEXT0.0.72 版本已开启推送,系统包大小约 1.1GB。新版本将增加一键锁屏服务卡,同时控制中心还新增亮度条、音量条。
    ———————-
    标题: vivo X200 系列手机全渠道销售金额突破 20 亿元,预售首日全系销量是上代 150%
    发布时间: 2024-10-20T07:32:24.223
    新闻简介: 根据官方公布的 20 亿元销售额以及系列机型的最高和最低定价进行估算,vivo X200 系列手机销量约在 29.4 万~46.5 万台。
    ———————-
    标题: 消费真是降级了,巴黎车展都开始接地气
    发布时间: 2024-10-20T14:16:08.697
    新闻简介: 性价比是王道
    ———————-
    标题: 《黑神话:悟空》官宣 PS5 实体版游戏已就绪,即将推出
    发布时间: 2024-10-20T16:35:25.893
    新闻简介: 此外,比亚迪今日宣布与《黑神话:悟空》达成全球战略合作,成为其唯一合作的汽车品牌,双方将在弘扬中国文化等领域展开合作。
    ———————-
    标题: 游戏《黑神话:悟空》获 ESRB“T”评级,将登陆 Xbox Series 平台
    发布时间: 2024-10-20T07:04:50.433
    新闻简介: IT之家注意到,目前《黑神话:悟空》已经获得娱乐软件分级委员会(ESRB)的“T”评级,该游戏将在 Xbox Series 平台发行。
    ———————-
    标题: 5327 元:苹果 M2 版 MacBook Air 13 英寸京东新低(官网 7999 元)
    发布时间: 2024-10-20T19:55:59.413
    新闻简介: 5327 元:苹果 M2 版 MacBook Air 13 英寸京东新低(官网 7999 元)
    ———————-
    标题: 天玑 9300 + 处理器:Redmi K70 至尊版手机 2182 元起京东自营限时购
    发布时间: 2024-10-20T21:49:39.427
    新闻简介: 天玑 9300 + 处理器:Redmi K70 至尊版手机 2182 元起京东自营限时购
    ———————-
    标题: 腾讯会议:免费版单场时长将逐步调整为 40 分钟,2 人会议不限时
    发布时间: 2024-10-20T15:35:11.123
    新闻简介: 腾讯会议官方今日发布通知,后续将对免费版单场会议时长进行调整,将逐步调整为 40 分钟,同时将推出权益更丰富的专业版及商业版。
    ———————-

    —- IT之家新闻 End —-