Write the Code. Change the World.

7月 11

AI 对话项目,会用到流式返回。一般客户端不会直接调用 AI 对话,而是调用自己的服务,自己的服务再调用 AI. 服务端可以通过 websocket 的方式来达到这个效果,也可以通过 SSE 的方式来达到这个效果。而 AI 也可能会提供这两种方式来提供服务。这样一来,项目就可能有四种组合方式来进行 AI 对话。如下:

客户端 服务端 AI
APP、浏览器 websocket websocket
APP、浏览器 websocket SSE
APP、浏览器 SSE websocket
APP、浏览器 SSE SSE

https://developer.mozilla.org/zh-CN/docs/Web/API/Server-sent_events/Using_server-sent_events

找 AI

websocket 阵营

https://www.xfyun.cn/doc/spark/Web.html#_1-%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E

SSE 阵营

https://agents.baidu.com/docs/develop/out-deployment/conversation/

尝试实现四种组合

从最简单的开始。服务端通过 SSE 的方式, AI 也是 SSE 的方式。

先封装一个 curl 工具包,创建 internal/pkg/curl/curl.go,内容如下。

package curl

import (
    "bytes"
    "context"
    "fmt"
    "io"
    "net/http"

    "github.com/gogf/gf/v2/frame/g"
    "github.com/gogf/gf/v2/net/gclient"
)

type HttpClient struct {
    client *gclient.Client
}

func NewHttpClient() *HttpClient {
    return &HttpClient{
        client: g.Client(),
    }
}

func (h *HttpClient) Get(ctx context.Context, url string, headers map[string]string) (string, error) {
    res, err := h.client.Get(ctx, url, headers)
    if err != nil {
        return "", err
    }
    defer res.Close()

    return res.ReadAllString(), nil
}

func (h *HttpClient) Post(ctx context.Context, url string, data interface{}, headers map[string]string) (string, error) {
    for k, v := range headers {
        h.client.SetHeader(k, v)
    }

    res, err := h.client.Post(ctx, url, data, headers)
    if err != nil {
        return "", err
    }
    defer res.Close()
    return res.ReadAllString(), nil
}

func (h *HttpClient) PostStream(ctx context.Context, url string, data interface{}, headers map[string]string) (io.ReadCloser, error) {
    // 将 data 转换为 io.Reader
    var body io.Reader
    switch v := data.(type) {
    case string:
        body = bytes.NewReader([]byte(v))
    case []byte:
        body = bytes.NewReader(v)
    default:
        return nil, fmt.Errorf("unsupported data type: %T", v)
    }

    // 创建 HTTP 请求
    req, err := http.NewRequestWithContext(ctx, "POST", url, body)
    if err != nil {
        return nil, err
    }

    // 设置请求头
    for k, v := range headers {
        req.Header.Set(k, v)
    }

    // 发起请求
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, err
    }

    // 检查响应状态码
    if resp.StatusCode != http.StatusOK {
        return nil, fmt.Errorf("request failed with status code: %d", resp.StatusCode)
    }

    return resp.Body, nil
}

现在来完善 chat 接口。

7月 11

AI 聊天,会用到流式返回的接口。用 goframe 框架尝试尝试。

开始

初始化项目

gf init stream.demo -u

cd stream.demo

gf run main.go

默认路由信息是这样的。

build: main.go
go build -o .\main.exe  main.go
.\main.exe
build running pid: 44364
2025-07-11T13:44:19.160+08:00 [INFO] pid[44364]: http server started listening on [:8000]
2025-07-11T13:44:19.160+08:00 [INFO] {ac79854f201c51188f16b244dba07b30} swagger ui is serving at address: http://127.0.0.1:8000/swagger/
2025-07-11T13:44:19.161+08:00 [INFO] {ac79854f201c51188f16b244dba07b30} openapi specification is serving at address: http://127.0.0.1:8000/api.json

  ADDRESS | METHOD |   ROUTE    |                           HANDLER                           |           MIDDLEWARE
----------|--------|------------|-------------------------------------------------------------|----------------------------------
  :8000   | ALL    | /api.json  | github.com/gogf/gf/v2/net/ghttp.(*Server).openapiSpec       |
----------|--------|------------|-------------------------------------------------------------|----------------------------------
  :8000   | GET    | /hello     | stream.demo/internal/controller/hello.(*ControllerV1).Hello | ghttp.MiddlewareHandlerResponse
----------|--------|------------|-------------------------------------------------------------|----------------------------------
  :8000   | ALL    | /swagger/* | github.com/gogf/gf/v2/net/ghttp.(*Server).swaggerUI         | HOOK_BEFORE_SERVE
----------|--------|------------|-------------------------------------------------------------|----------------------------------

提交版本

git init -b main
git add .
git commit -m 'initialize'

定义接口,创建控制器,定义路由

新建 api/chat/v1/chat.go 文件,定义以下请求和返回

package v1

import "github.com/gogf/gf/v2/frame/g"

type ChatReq struct {
    g.Meta  `path:"/chat" method:"post" tags:"聊天"  summary:"聊天请求"`
    Uid     uint64 `v:"required#用户ID不能为空" json:"uid" dc:"用户ID"`
    Message string `v:"required#消息内容不能为空" json:"message" dc:"消息内容"`
}

type ChatRes struct {
    IsEnd bool `json:"is_end" dc:"是否结束"`
}

执行 gf gen ctrl 生成控制器。修改 internal/cmd/cmd.go,增加路由配置。

import (
    ……
    "stream.demo/internal/controller/hello"
)

……
s.Group("/api", func(group *ghttp.RouterGroup) {
    group.Bind(
        chat.NewV1(),
    )
})

再运行 gf run main.go,把项目跑起来看看。发现 chat 路由已经好了。

build: main.go
go build -o .\main.exe  main.go
.\main.exe
build running pid: 44148
2025-07-11T14:13:29.676+08:00 [INFO] pid[44148]: http server started listening on [:8000]
2025-07-11T14:13:29.676+08:00 [INFO] {5065f8ccb71d5118823d646fdde40224} swagger ui is serving at address: http://127.0.0.1:8000/swagger/
2025-07-11T14:13:29.677+08:00 [INFO] {5065f8ccb71d5118823d646fdde40224} openapi specification is serving at address: http://127.0.0.1:8000/api.json

  ADDRESS | METHOD |   ROUTE    |                           HANDLER                           |           MIDDLEWARE
----------|--------|------------|-------------------------------------------------------------|----------------------------------
  :8000   | ALL    | /api.json  | github.com/gogf/gf/v2/net/ghttp.(*Server).openapiSpec       |
----------|--------|------------|-------------------------------------------------------------|----------------------------------
  :8000   | POST   | /api/chat      | stream.demo/internal/controller/chat.(*ControllerV1).Chat   | ghttp.MiddlewareHandlerResponse
----------|--------|------------|-------------------------------------------------------------|----------------------------------
  :8000   | GET    | /hello     | stream.demo/internal/controller/hello.(*ControllerV1).Hello | ghttp.MiddlewareHandlerResponse
----------|--------|------------|-------------------------------------------------------------|----------------------------------
  :8000   | ALL    | /swagger/* | github.com/gogf/gf/v2/net/ghttp.(*Server).swaggerUI         | HOOK_BEFORE_SERVE
----------|--------|------------|-------------------------------------------------------------|----------------------------------

开始流式 demo

编辑 internal/controller/chat/chat_v1_chat.go

package chat

import (
    "context"
    "fmt"
    "time"

    "github.com/gogf/gf/v2/net/ghttp"

    v1 "stream.demo/api/chat/v1"
)

func (c *ControllerV1) Chat(ctx context.Context, req *v1.ChatReq) (res *v1.ChatRes, err error) {
    r := ghttp.RequestFromCtx(ctx)
    r.Response.Header().Set("Content-Type", "text/event-stream")
    r.Response.Header().Set("Cache-Control", "no-cache")
    r.Response.Header().Set("Connection", "keep-alive")
    r.Response.Header().Set("Content-Encoding", "identity")

    for i := range 10 {
        data := fmt.Sprintf(`{"uid":%d, "message":"%s","index":%d}`, req.Uid, req.Message, i)
        r.Response.Writefln("data: %s\n\n", data)
        r.Response.Flush()
        time.Sleep(time.Millisecond * 1000)
    }

    fmt.Println("发送结束")
    res = &v1.ChatRes{}
    res.IsEnd = true
    return
}

重新运行项目。

测试

可以使用 curl 命令,可以使用 apifox 这种工具,也可以使用浏览器。 这里使用浏览器加 js 的方式来搞。

使用 chrome 打开 http://127.0.0.1:8000 ,然后打开控制台。输入以下测试代码,回车。

fetch("http://127.0.0.1:8000/api/chat", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    message: "舞低杨柳楼心月,歌尽桃花扇底风",
    uid: 1,
  }),
})
  .then(readStream)
  .catch(console.error);

async function readStream(res) {
  const reader = res.body.getReader();
  const decoder = new TextDecoder("utf-8");
  let buffer = "";

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    buffer += decoder.decode(value, { stream: true });

    let lines = buffer.split("\n\n");
    buffer = lines.pop();

    for (const chunk of lines) {
      const dataLine = chunk.trim();
      if (dataLine.startsWith("data: ")) {
        const data = dataLine.slice(6);
        console.log(getNowTime(), "消息:", data);
      }
    }
  }
}

function getNowTime() {
  const date = new Date();
  return date.toLocaleTimeString("zh-CN", {
    hour12: false,
    timeZone: "Asia/Shanghai",
  });
}

截图如下:

提交版本控制。

git add .
git commit -m '增加 chat 接口,并测试流式响应'

nginx 处理

如果走 nginx 代理,需要处理关闭相应缓冲

location /api/ {
    proxy_pass http://你的后端服务地址;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_buffering off;
    proxy_cache off;
    proxy_request_buffering off;
    chunked_transfer_encoding on;
}
7月 10

后台管理系统,个人审美还是更喜欢 element-plus 多一点。如果组件更丰富点就更好了。

https://element-plus.org/zh-CN/guide/installation.html

安装

# 安装
pnpm add element-plus

# 自动导入,安装unplugin-vue-components 和 unplugin-auto-import 这两款插件
pnpm add -D unplugin-vue-components unplugin-auto-import

配置自动导入
编辑 vite.config.ts

import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default defineConfig({
  // ...
  plugins: [
    // ...
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
})

追加 AutoImportComponents 配置。完整的配置文件如下:

import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

// https://vite.dev/config/
export default defineConfig({
  base: '/admin/',
  build: {
    outDir: 'admin',
    emptyOutDir: true,
    chunkSizeWarningLimit: 3000,
    rollupOptions: {
      output: {
        entryFileNames: 'assets/[name].[hash].js', // 入口文件名
        chunkFileNames: 'assets/[name].[hash].js', // chunk 文件名
        assetFileNames: 'assets/[name].[hash].[ext]', // 静态资源文件名
      },
    },
  },
  plugins: [
    vue(),
    vueDevTools(),
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url)),
    },
  },
})

下边进行尝试使用

使用

编辑 src/views/home/index.vue,增加按钮组件,尝试下效果。

<template>
  <div>
    <h1>HI, {{ user.name }} , Welcome to the Home Page</h1>
    <h2>Email: {{ user.email }}</h2>
    <h2>Api: {{ api }}</h2>
    <div>
      <el-button type="primary" @click="test">Click Me</el-button>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref } from 'vue'

const user = ref<User>({
  id: 1,
  name: 'John Doe',
  email: 'john.doe@example.com',
  createdAt: new Date(),
})

const api = ref(import.meta.env.VITE_API_BASE_URL)

function test() {
  alert('Button Clicked!')
}
</script>

<style scoped>
h1 {
  color: #42b983;
  font-size: 2em;
}
</style>

运行起来,效果如下图所示。

7月 09

项目打包,如果访问位置非对应根目录,是需要配置的。打包输出目录也是需要配置的(默认是 dist目录),chunk 也需要配置。

pnpm lint 执行检查的时候,应该对打包的文件忽略检查。这个也需要配置的。现在一个一个的来。

配置打包

import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'

// https://vite.dev/config/
export default defineConfig({
  base: '/admin/',
  build: {
    outDir: 'admin',
    emptyOutDir: true,
    chunkSizeWarningLimit: 3000,
    rollupOptions: {
      output: {
        entryFileNames: 'assets/[name].[hash].js', // 入口文件名
        chunkFileNames: 'assets/[name].[hash].js', // chunk 文件名
        assetFileNames: 'assets/[name].[hash].[ext]', // 静态资源文件名
      },
    },
  },
  plugins: [vue(), vueDevTools()],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url)),
    },
  },
})

这里输出目录是 admin,基础路径也是 admin。 配置的警官文件大小,设置了打包输出的文件名等。

执行打包 pnpm build,会将项目打包到 admin 目录下。

但是,当执行 pnpm lint 检查的时候,会对 admin 目录下的代码进行检查。这个是不需要检查的。所以需要配置 eslint.confg.ts 来控制该行为。

配置 eslint.confg.ts 来忽略检查

import { globalIgnores } from 'eslint/config'
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
import pluginVue from 'eslint-plugin-vue'
import pluginOxlint from 'eslint-plugin-oxlint'
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'

// To allow more languages other than `ts` in `.vue` files, uncomment the following lines:
// import { configureVueProject } from '@vue/eslint-config-typescript'
// configureVueProject({ scriptLangs: ['ts', 'tsx'] })
// More info at https://github.com/vuejs/eslint-config-typescript/#advanced-setup

export default defineConfigWithVueTs(
  {
    name: 'app/files-to-lint',
    files: ['**/*.{ts,mts,tsx,vue}'],
  },

  globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']),

  pluginVue.configs['flat/essential'],
  vueTsConfigs.recommended,
  ...pluginOxlint.configs['flat/recommended'],
  skipFormatting,
  {
    name: 'app/rules',
    rules: {
      'vue/multi-word-component-names': 'off',
    },
  },
  {
    name: 'app/ignore',
    ignores: ['admin/**'],
  },
)

这个追加了以下配置。

  {
    name: 'app/ignore',
    ignores: ['admin/**'],
  },

最后,编辑 .gitignore 文件, 对 git 进行配置。让 git 忽略对 admin 目录里的文件进行版本控制。

7月 09

初始化项目时,在根目录会有一个 .vscode 目录。

里边有一个 extensions.json 文件,配置了推荐的 vscode 插件。

里边有一个 settings.json 文件,配置了 vscode 一些行为控制。比如保存时,自动格式化,使用什么插件格式化等。可以单独设置 vue,json,css 等文件的格式化插件。如下:

{
  "explorer.fileNesting.enabled": true,
  "explorer.fileNesting.patterns": {
    "tsconfig.json": "tsconfig.*.json, env.d.ts",
    "vite.config.*": "jsconfig*, vitest.config.*, cypress.config.*, playwright.config.*",
    "package.json": "package-lock.json, pnpm*, .yarnrc*, yarn*, .eslint*, eslint*, .oxlint*, oxlint*, .prettier*, prettier*, .editorconfig"
  },
  "editor.codeActionsOnSave": {
    "source.fixAll": "explicit"
  },
  "editor.formatOnSave": true,
  "[vue]": {
    "editor.defaultFormatter": "Vue.volar"
  },
  "[jsonc]": {
    "editor.defaultFormatter": "vscode.json-language-features"
  },
}

该配置文件不进行版本控制。可以增加一个 settings.json.example 来进行版本控制。

手动格式化和检查

# 手动格式
pnpm format

# 手动检查
pnpm lint
7月 09

https://cn.vite.dev/guide/env-and-mode

在本地运行和打包使用不同的配置的时候,环境变量的配置就很有作用。比如本地运行调用接口用的域名是 https://test.xxx.com,正式上线调用的接口是 https://www.xxx.com

先创建智能提示

编辑 env.d.ts 文件,内容如下。

/// <reference types="vite/client" />

interface ImportMetaEnv {
  readonly VITE_API_BASE_URL: string
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}

创建 env 文件

项目根目录添加 .env 文件。添加以下内容。

VITE_API_BASE_URL=https://test.xxx.com

项目根目录添加 .env.production 文件。添加以下内容。

VITE_API_BASE_URL=https://www.xxx.com

修改 ./src/home/index.vue 文件,测试

<template>
  <div>
    <h1>HI, {{ user.name }} , Welcome to the Home Page</h1>
    <h2>Email: {{ user.email }}</h2>
    <h2>Api: {{ api }}</h2>
  </div>
</template>

<script lang="ts" setup>
import { ref } from 'vue'

const user = ref<User>({
  id: 1,
  name: 'John Doe',
  email: 'john.doe@example.com',
  createdAt: new Date(),
})

const api = ref(import.meta.env.VITE_API_BASE_URL)

</script>

<style scoped>
h1 {
  color: #42b983;
  font-size: 2em;
}
</style>

测试环境

pnpm dev

生产环境

pnpm dev --mode production

结果如图:

7月 09

定义基本类型

新建 ./src/types 目录,在目录下新建 global.d.ts,添加以下内容。

declare interface User {
  id: number
  name: string
  email: string
  createdAt: Date
}

修改 ./src/home/index.vue

<template>
  <div>
    <h1>HI, {{ user.name }} , Welcome to the Home Page</h1>
    <h2>Email: {{ user.email }}</h2>
  </div>
</template>

<script lang="ts" setup>
import { ref } from 'vue'

const user = ref<User>({
  id: 1,
  name: 'John Doe',
  email: 'john.doe@example.com',
  createdAt: new Date(),
})
</script>

运行查看 pnpm dev

7月 09

vite7: https://vite.dev/blog/announcing-vite7

node: https://nodejs.org/en/download

创建初始化

现在创建一个叫 forgetting 的项目

pnpm self-update

pnpm create vue@latest

# 引导构建

┌  Vue.js - The Progressive JavaScript Framework
│
◇  请输入项目名称:
│  forgetting
│
◇  请选择要包含的功能: (↑/↓ 切换,空格选择,a 全选,回车确认)
│  TypeScript, Router(单页面应用开发), Pinia(状态管理), ESLint(错误预防), Prettier(代码格式化)
│
◇  选择要包含的试验特性: (↑/↓ 切换,空格选择,a 全选,回车确认)
│  Oxlint(试验阶段), rolldown-vite(试验阶段)

正在初始化项目 C:\Users\Windows\Desktop\study\vue\forgetting...
│
└  项目初始化完成,可执行以下命令:

创建完成后,执行下边命令,运行起来。

   cd forgetting
   pnpm install
   pnpm format
   pnpm dev

提交版本。

git init -b main
git add .
git commit -m 'initialize'

删除默认页面,组件,样式

rm -rf ./src/views/*
rm -rf ./src/components/*
rm -rf ./src/assets/logo.svg

新增一个首页,运行起来

touch ./src/views/home/index.vue

# 添加以下内容
<template>
    <div>
        <h1>Welcome to the Home Page</h1>
    </div>
</template>

修改 App.vue

<template>
  <RouterView />
</template>

调整路由 router.ts

import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: () => import('../views/home/index.vue'),
    },
  ],
})

export default router

去掉 assets/main.css 中的其他代码,保留。

@import './base.css';

#app {
  margin: 0;
  padding: 0;
  font-weight: normal;
}

a,
.green {
  text-decoration: none;
  color: hsla(160, 100%, 37%, 1);
  transition: 0.4s;
  padding: 3px;
}

重新运行

pnpm dev

> forgetting@0.0.0 dev C:\Users\Windows\Desktop\study\vue\forgetting
> vite

Port 5173 is in use, trying another one...

  ROLLDOWN-VITE v7.0.6  ready in 858 ms

  ➜  Local:   http://localhost:5174/
  ➜  Network: use --host to expose
  ➜  Vue DevTools: Open http://localhost:5174/__devtools__/ as a separate window
  ➜  Vue DevTools: Press Alt(⌥)+Shift(⇧)+D in App to toggle the Vue DevTools
  ➜  press h + enter to show help

可以执行代码检查

pnpm lint

> forgetting@0.0.0 lint C:\Users\Windows\Desktop\study\vue\forgetting
> run-s lint:*

> forgetting@0.0.0 lint:oxlint C:\Users\Windows\Desktop\study\vue\forgetting
> oxlint . --fix -D correctness --ignore-path .gitignore

Found 0 warnings and 0 errors.
Finished in 12ms on 8 files with 87 rules using 16 threads.

> forgetting@0.0.0 lint:eslint C:\Users\Windows\Desktop\study\vue\forgetting
> eslint . --fix

C:\Users\Windows\Desktop\study\vue\forgetting\src\views\home\index.vue
  1:1  error  Component name "index" should always be multi-word  vue/multi-word-component-names

✖ 1 problem (1 error, 0 warnings)

 ELIFECYCLE  Command failed with exit code 1.
ERROR: "lint:eslint" exited with 1.
 ELIFECYCLE  Command failed with exit code 1.

官方默认的vue文件名是以大写字母开头,多单词的形式。或以中横线连接。个人喜欢以名字+动词的形式来定义文件。即以单数名字作为文件夹名,动词作为文件名。比如,想要一个创建订单的页面可以这样:order/create.vue,如果想要一个订单列表的页面就是 order/list.vue。 这种规则和默认规则相冲,eslint 检查出错误来。所以可以手动修改该策略。

对于组件命名,还是喜欢用大写字母开头,多单词的形式。 components/MediaPlayer/MediaPlayer.vue

修改 eslint.config.ts,增加规则来允许上边所说的消息单个单词的文件命名。

  {
    rules: {
      'vue/multi-word-component-names': 'off',
    },
  }

需要扩展和覆盖规则都可以在这里进行。因为是刚创建的项目,所以配置很干净除了新增这一条,其他都是默认的,完整如下:

import { globalIgnores } from 'eslint/config'
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
import pluginVue from 'eslint-plugin-vue'
import pluginOxlint from 'eslint-plugin-oxlint'
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'

// To allow more languages other than `ts` in `.vue` files, uncomment the following lines:
// import { configureVueProject } from '@vue/eslint-config-typescript'
// configureVueProject({ scriptLangs: ['ts', 'tsx'] })
// More info at https://github.com/vuejs/eslint-config-typescript/#advanced-setup

export default defineConfigWithVueTs(
  {
    name: 'app/files-to-lint',
    files: ['**/*.{ts,mts,tsx,vue}'],
  },

  globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']),

  pluginVue.configs['flat/essential'],
  vueTsConfigs.recommended,
  ...pluginOxlint.configs['flat/recommended'],
  skipFormatting,
  {
    rules: {
      'vue/multi-word-component-names': 'off',
    },
  }
)

再执行 pnpm lint 就不会报错了。

执行 pnpm format 格式化代码。中间不规范操作,都可以通过这个来自动纠正。

提交版本。 git add . && git commit -m '删除默认页面、组件、样式,配置支持单个单词文件名的eslint规则'

7月 03

https://developer.mozilla.org/zh-CN/docs/Web/Progressive_web_apps

https://vite-pwa-org-zh.netlify.app/guide/

渐进式 Web 应用(Progressive Web App,PWA)是一个使用 web 平台技术构建的应用程序,但它提供的用户体验就像一个特定平台的应用程序。

它像网站一样,PWA 可以通过一个代码库在多个平台和设备上运行。它也像一个特定平台的应用程序一样,可以安装在设备上,可以离线和在后台运行,并且可以与设备和其他已安装的应用程序集成。

特点

  1. 体验类似原生应用,而不是浏览器打开的网站。、
  2. 离线使用部分功能。

学习参考

https://juejin.cn/post/7497868344223989794

https://juejin.cn/post/7294554207096750090

https://ionic.nodejs.cn/vue/pwa