目录
[一、HTML 篇](#一、HTML 篇)
[1、设置 loading 状态](#1、设置 loading 状态)
[二、TypeScript 篇](#二、TypeScript 篇)
[1、function 之 void](#1、function 之 void)
[2、Promise 之 finally](#2、Promise 之 finally)
[3、TS 类型约束](#3、TS 类型约束)
[三、CSS 篇](#三、CSS 篇)
背景:
#创作灵感#
- 记录工作实践、项目所学
- 写技术笔记巩固新学知识点
由于上一家公司项目全都是使用 Vue2 和 element 组件库 进行的开发,所以一直没有真正使用 Vue3 和 ElementPlus 开发过项目,虽说有接触简单学习了解过,但跟实际上手开发 Vue3 和 ElementPlus 项目肯定还是有差别的。但好巧不巧,新入职公司接手的第一个项目就是用 Vue3 ➕ ElementPlus 开发,而且项目还是用 pnpm 管理,这些都是头一次接触,但是没办法也只能硬着头皮必须顶上去,想想以前不也都靠 "度娘" 撑过来了,更何况现在还有了技术更发达的 AI 可以辅助开发了,相信自己 "船到桥头自然直",加油干就完了!
一、HTML 篇
1、设置 loading 状态
问题:
怎么给 img 标签添加 loading 加载状态呢?用的是 elementplus 组件库,现在直接加:loading 不生效,img 标签外层套的 el-tooltip 标签,可以在这上面加吗?给我个实现方案
Element Plus 图片加载 loading 实现方案
问题原因
原生
img没有:loading属性,el-loading指令 / 组件只能加在 有固定宽高、可定位的容器元素 上,直接绑 img 不生效;外层el-tooltip是悬浮提示组件,不能直接绑定 loading 指令,需要 给图片包一层固定宽高的容器,在容器上加 loading。方案一:el-loading 指令(推荐,简单易用)
给 img 外层套 div 容器,容器设置和图片一致的宽高,在容器上使用
v-loading指令,加载完成后关闭 loading
html<template> <!-- 外层tooltip不动,tooltip内部包一层loading容器 --> <el-tooltip content="提示文字" placement="top"> <!-- 加载容器:必须设置宽高,和图片尺寸保持一致 --> <div v-loading="isImgLoading" element-loading-text="图片加载中" style="width: 200px; height: 200px;" > <img src="你的图片地址" alt="图片" @load="isImgLoading = false" @error="isImgLoading = false" style="width:100%;height:100%;object-fit:cover" /> </div> </el-tooltip> </template> <script setup> import { ref } from 'vue' // 初始化开启加载 const isImgLoading = ref(true) </script>方案二:el-loading 组件形式(自定义更强)
使用独立
<el-loading>组件包裹容器,适合需要自定义加载图标、遮罩样式的场景
html<template> <el-tooltip content="提示文字"> <div style="width: 200px; height: 200px; position: relative;"> <el-loading v-model="isImgLoading" text="加载中..." background="rgba(255,255,255,0.7)" > <img src="图片地址" alt="" @load="isImgLoading = false" @error="isImgLoading = false" style="width:100%;height:100%" /> </el-loading> </div> </el-tooltip> </template> <script setup> import { ref } from 'vue' const isImgLoading = ref(true) </script>方案三:图片不确定宽高自适应场景
如果图片宽高不固定,容器设置为
inline-block适配图片尺寸,配合默认占位图:
html<el-tooltip content="提示"> <div v-loading="isImgLoading" style="display: inline-block;" > <!-- 加载前显示默认占位图 --> <img :src="imgSrc" alt="" @load="isImgLoading = false" @error="isImgLoading = false" width="auto" /> </div> </el-tooltip>关键注意点
- el-tooltip 不能直接加 loading:tooltip 只是悬浮层容器,渲染逻辑特殊,loading 必须加在 tooltip 内部的图片父容器
- 必须监听
@load和@error:无论加载成功失败都关闭 loading,避免永久加载状态- 容器一定要设置宽高:否则 loading 遮罩没有定位基准,会全屏遮罩
- 全局配置 loading:可以在 main.js 全局修改 loading 样式,统一项目加载效果
javascript// main.js 全局配置el-loading import { ElLoading } from 'element-plus' app.use(ElLoading, { text: '图片加载中', spinner: '自定义图标类名' })
问题:
不能给 img 标签设定宽高,然后设置 v-loading 吗?
结论:仅给 img 设宽高,直接绑 v-loading 依然大概率不生效
核心两个原因
1、img 是行内替换元素
v-loading的遮罩是 绝对定位 ,需要父级 / 自身position: relative做定位基准; 原生 img 默认是display: inline,哪怕写了宽高,不主动改成block/inline-block+relative,遮罩会直接跑出图片区域,甚至全屏显示 loading。2、v-loading 会把遮罩插入当前元素的 子节点 里
<img>是 自闭合标签,没有子元素,loading 的 DOM 结构根本无法插入到 img 内部,这是最根本的限制。 div 这类容器可以内部插入遮罩 DOM,img 不能放子标签,指令无从挂载内部遮罩。
如果你坚持想直接写在 img 上:必须补全 3 个强制样式(不推荐,有坑)
html<el-tooltip content="提示"> <img v-loading="isLoading" src="xxx" style=" width: 200px; height:200px; display: inline-block; /* 转为行内块 */ position: relative; /* 给遮罩定位基准 */ " @load="isLoading=false" @error="isLoading=false" /> </el-tooltip>缺陷:部分浏览器 / 版本 el-plus,自闭合标签挂载 v-loading 会渲染异常,loading 转圈偏移、消失不掉,不稳定。
最优最简方案(只套一层空 div,不影响布局)
只包一个 inline-block 的容器,容器自适应图片大小,不需要固定宽高,完全不改动原图样式,el-tooltip 套在最外层即可:
html<template> <el-tooltip content="悬浮提示"> <!-- 容器自适应图片尺寸,不破坏原有布局 --> <div v-loading="isImgLoading" style="display: inline-block;" > <img :src="imgUrl" alt="图片" @load="isImgLoading = false" @error="isImgLoading = false" /> </div> </el-tooltip> </template> <script setup> import { ref } from 'vue' const isImgLoading = ref(true) const imgUrl = ref('你的图片地址') </script>
display:inline-block容器会自动贴合 img 的宽高,不用手动写尺寸- loading 挂载在 div 容器,DOM 结构合法,遮罩永远精准盖住图片
- el-tooltip 包裹整个容器,悬浮提示不受任何影响
额外备选:直接用 Element Plus 自带的 el-image 组件
el-image 原生自带加载 loading、加载失败兜底,完美适配 tooltip,无需自己写加载逻辑:
html<el-tooltip content="提示"> <el-image src="图片地址" fit="cover" /> </el-tooltip>
2、判断文件选中操作
问题:
前端能实现点击页面按钮,弹出选择文件夹弹窗,然后点确定后调用后端接口这样的操作吗?
结论先行
纯前端浏览器环境,无法直接弹出系统原生的「选择文件夹」弹窗并获取本地文件夹路径,受浏览器安全沙箱限制;但有两套可行方案实现需求,分场景选择:
一、现代浏览器原生方案(推荐,无需插件)
HTML5 提供了 webkitdirectory 文件夹选择属性 ,可以弹出文件选择框、限制只能选择整个文件夹,能拿到文件夹内所有文件,再把文件 / 文件夹信息传给后端
1. 前端代码示例
html<!-- 隐藏input,按钮点击触发它 --> <button id="selectFolderBtn">选择文件夹</button> <input type="file" id="folderInput" webkitdirectory multiple style="display:none"> <script> const btn = document.getElementById('selectFolderBtn') const input = document.getElementById('folderInput') btn.onclick = () => input.click() // 选中文件夹后的回调 input.onchange = async (e) => { const files = Array.from(e.target.files) if(!files.length) return // files 里包含该文件夹下所有子文件,可读取文件名、相对路径、文件内容 const formData = new FormData() files.forEach(file => { // 关键:保留文件在文件夹内的相对路径,后端可还原目录结构 formData.append('files', file, file.webkitRelativePath) }) // 调用后端接口上传/提交文件夹数据 const res = await fetch('/api/upload-folder', { method: 'POST', body: formData }) console.log('提交完成', await res.json()) } </script>优缺点
✅ 纯前端、无第三方依赖、Chrome / Edge / Firefox / 新版 Safari 全支持
❌ 拿不到本地绝对物理路径(浏览器安全禁止,只能获取相对目录);只能上传文件夹内文件,不能直接把本地文件夹地址发给后端
二、想要获取本地文件夹绝对路径(仅本地环境可用)
只有 非浏览器环境才能读取系统文件夹真实路径,适用于 Electron 桌面端、本地客户端:
1. Electron 桌面应用(前端技术写桌面程序)
Electron 可以调用 Node.js 的
dialog.showOpenDialog直接弹出 系统原生文件夹选择框,拿到完整本地路径,再传给内置后端或远程接口
javascript// 主进程代码 const { dialog } = require('electron') // 渲染进程通知主进程打开文件夹选择 dialog.showOpenDialog({ properties: ['openDirectory'] // 只选文件夹 }).then(result => { const folderPath = result.filePaths[0] // 拿到本地绝对路径 C:/xxx/xxx // 把路径发送给渲染进程,由渲染进程调用后端接口 })适用场景:内部管理系统、本地工具类软件
三、区分两种需求帮你选型
- 公网网页、给普通用户使用 :只能用第一种
webkitdirectory上传文件夹内文件,无法获取本地路径- 内部工具、只在自己电脑 / 公司电脑使用:打包成 Electron 桌面程序,随意获取文件夹本地路径
- 老旧 IE 浏览器:无原生文件夹选择,只能用 ActiveX(已废弃,不推荐)
补充后端接收说明
后端接收
FormData格式的文件,通过文件名里的webkitRelativePath可以还原用户选择的文件夹目录结构,保存到服务器指定文件夹。
3、
二、TypeScript篇
1、function 之 void
问题:
function handleOpen(): void { } 的 void 啥意思?
TypeScript 里
void的含义
void写在函数返回值位置,代表 这个函数 没有返回值、不返回 任何数据。1. 基础解释
TypeScript// 声明:调用 handleOpen 后,不会返回任何东西 function handleOpen(): void { }
- 普通函数默认可以
return 123、return 'abc'把值抛出去给外面接收- 标记
void:约定 本函数不写 return 返回任何有效值 ,只能:
- 不写
return- 只写单独
return;(不带值)2. 和不写返回值的区别
JS 原生(无类型)
javascript// JS 不写返回,默认自动返回 undefined function handleOpen() {} const res = handleOpen() // res === undefinedTS 写
void
TypeScriptfunction handleOpen(): void {} let a = handleOpen() a = 1 // 报错:void 类型不能赋值数字TS 用
void限制语义:我设计这个函数就是只执行动作,不产出返回值,禁止你去接收、使用它的返回结果。3. 小误区:void 函数也能写 return,但不能带值
TypeScript// 合法,只是中断函数,没有返回数据 function fn(): void { if (xxx) return console.log('执行') } // 非法!不能返回具体数值 function fn(): void { return 123 // TS直接报错 }4. 常见场景
点击事件、打开弹窗 这类 只做动作、不需要返回数据 的函数,都会标
void,就是你代码里handleOpen打开弹窗这类操作的标准写法。
2、Promise 之 finally
问题:
.finally(() => { }) 这个 finally 是啥?
Promise 的
.finally()方法它是 异步 Promise 专属 的 回调方法,写在
.then()、.catch()后面
finally直译:最终、无论如何最后都要执行核心特点
不管前面的异步请求 成功走了 then ,还是 失败报错走了 catch ,
finally里面的代码 一定会执行基础结构
javascriptfetch("/api") .then(res => console.log('请求成功')) .catch(err => console.log('请求失败')) .finally(() => { // 请求成功 OR 失败,这里的代码必定执行 console.log('请求结束,收尾工作') })最常用场景(日常开发高频)
加载 loading 动画:发送请求时打开 loading,不管请求成功还是失败,最后都要关闭 loading
javascript// 打开加载 loading.show() axios.get('/getData') .then(data => setData(data)) .catch(e => showError(e)) .finally(() => { loading.hide() // 成功失败都会关掉loading,不用在then和catch各写一次关闭 })补充细节
finally的回调 没有参数,拿不到成功的数据,也拿不到错误信息,它只负责收尾清理- 不会改变 Promise 的返回值,不影响前面 then 接收到的数据
- 同步代码没有 finally,只配合异步 Promise 使用
3、TS 类型约束
<el-upload>标签上的 :before-upload 红色波浪线提示了,帮我看一下怎么回事?
bashType '(file: any) => true | "xls"' is not assignable to type '(rawFile: UploadRawFile) => Awaitable<boolean | void | File | Blob>'. Type 'true | "xls"' is not assignable to type 'Awaitable<boolean | void | File | Blob>'. Type '"xls"' is not assignable to type 'Awaitable<boolean | void | File | Blob>'. ts-plugin(2322) upload.vue.d.ts(274, 14): The expected type comes from property 'beforeUpload' which is declared here on type 'Partial<{ readonly data: unknown; readonly disabled: boolean; readonly drag: boolean; readonly multiple: boolean; readonly name: string; readonly onChange: (uploadFile: UploadFile, uploadFiles: UploadFiles) => void; ... 15 more ...; readonly onExceed: (files: File[], uploadFiles: UploadUserFile[]) => void; }> & Omit...' (property) beforeUpload?: (rawFile: UploadRawFile) => Awaitable<boolean | void | File | Blob>问题原因
Element Plus 的
before-upload钩子 有严格的 TS 类型约束 : 函数必须返回Promise<boolean | File | Blob> / boolean / void,不能返回字符串 。 你的handleBeforeUpload函数里大概率存在:符合条件返回true,不符合直接返回了"xls"这类字符串,TS 校验报错。报错翻译:
类型
(file: any) => true | "xls"不能赋值给beforeUpload要求的类型 不能把字符串"xls"赋值给布尔 / 文件类型修复方案
1. 修改 handleBeforeUpload 函数(核心)
只允许返回布尔值、Promise、或者不 return(void),校验失败只做提示,返回 false 拒绝上传 示例修正写法:
TypeScriptconst handleBeforeUpload = (rawFile: UploadRawFile) => { // 拿到文件后缀 const suffix = rawFile.name.split('.').pop() const allowType = ['xlsx', 'xls'] if (allowType.includes(suffix!)) { return true // 符合格式,放行上传 } else { ElMessage.error('只能上传 xls/xlsx 格式文件') return false // 格式错误,拒绝上传,不要return "xls"字符串 } }2. 补充细节
- 删掉函数里所有返回字符串的逻辑,校验失败 只提示 + 返回 false
- 可以给函数补全 TS 类型
(rawFile: UploadRawFile): boolean更规范- 你标签上已经写了
accept=".xlsx,.xls",前端双重校验更稳妥3. 补充如果是异步校验
如果函数内部有异步操作,要返回 Promise:
TypeScriptconst handleBeforeUpload = async (rawFile: UploadRawFile) => { // 异步逻辑... return true }改完函数,红色波浪线就会消失。
4、依赖包无TS类型声明文件
引入树组件 vue3-treeselect,为何红色波浪线提示了?我明明 pnpm install -w vue3-treeselect,下载了,并且 package.json 的 dependencies 里面也有了"vue3-treeselect":"^0.1.10",帮我看一下怎么回事?
bashCould not find a declaration file for module 'vue3-treeselect'. 'D:/GitBlit/Program/WEB/node_modules/.pnpm/vue3-treeselect@0.1.10_vue@3.5.13_typescript@5.8.3_/node_modules/vue3-treeselect/dist/vue3-treeselect.common.js' implicitly has an 'any' type. Try `npm i --save-dev @types/vue3-treeselect` if it exists or add a new declaration (.d.ts) file containing `declare module 'vue3-treeselect';` ts-plugin(7016)逐句翻译
英文原文 中文翻译 Could not find a declaration file for module 'vue3-treeselect'.找不到模块 "vue3-treeselect" 的声明文件。 'D:/Git/node_modules/vue3-treeselect/dist/vue3-treeselect.common.js' implicitly has an 'any' type.路径 D:/Git/node_modules/vue3-treeselect/dist/vue3-treeselect.common.js隐式具有 "any" 类型。Trynpm i --save-dev @types/vue3-treeselectif it exists or add a new declaration (.d.ts) file containingdeclare module 'vue3-treeselect';ts-plugin(7016)如果存在 @types/vue3-treeselect类型包,请尝试执行npm i --save-dev @types/vue3-treeselect;或者新建一个声明文件(.d.ts),内容为declare module 'vue3-treeselect';。错误码:ts-plugin (7016)报错原因
vue3-treeselect这个包 本身没有自带 TypeScript 类型声明文件(.d.ts) ,并且 npm 也没有官方维护的@types/vue3-treeselect类型包,所以 TS 找不到模块类型定义,抛出 7016 的找不到声明文件错误,和包有没有安装成功无关。两种解决办法,推荐第一种
方案 1:新建类型声明文件(一劳永逸,规范 TS 项目)
1、在你的项目
src目录下,新建typings或者直接新建src/shims-vue3-treeselect.d.ts文件2、文件内写入模块声明,告诉 TS 忽略这个包的类型检查:
TypeScript// shims-vue3-treeselect.d.ts declare module 'vue3-treeselect' { import { DefineComponent } from 'vue' const TreeSelect: DefineComponent<any, any, any> export default TreeSelect } declare module 'vue3-treeselect/dist/vue3-treeselect.css'3、保存文件,重启 VSCode 的 TS 服务:快捷键
Ctrl+Shift+P输入Restart TypeScript Service重启,红波浪线就会消失。方案 2:单行忽略(临时快速解决)
直接在引入这一行的上方加 ts 忽略注释,跳过该行的类型校验:
TypeScript// @ts-ignore import TreeSelect from "vue3-treeselect"; import "vue3-treeselect/dist/vue3-treeselect.css";缺点:只对当前这一行生效,多处引入就要多次加忽略,不推荐长期使用。
补充排查小提示
- 确认
tsconfig.json里的include配置包含了你新建的 d.ts 文件,一般默认包含src/**/*就没问题- 已经 pnpm 安装成功包,运行项目
pnpm dev是可以正常运行组件的,只是 VSCode 的 TS 语法检查报错,不影响实际代码执行- 这个包停止维护很久了,没有官方类型,只能自己补声明
5、TS校验接口参数问题
问题:
queryExcel 接口名红色波浪线提示了,帮我看一下原因
bashExpected 1 arguments, but got 0. ts-plugin(2554) ban.ts(76, 28): An argument for 'params' was not provided. (alias) queryExcel(params: any): Promise<AxiosResponse<any, any>> import queryExcel逐句翻译与说明
英文原文 中文翻译 补充说明 Expected 1 arguments, but got 0. ts-plugin(2554)预期传入 1 个参数,但实际传入了 0 个。(错误码:ts-plugin (2554)) TypeScript 核心报错:调用函数时参数数量不匹配。 ban.ts(76, 28): An argument for 'params' was not provided.ban.ts文件的第 76 行第 28 列:未提供params参数。精准定位:错误发生的位置,明确指出缺失的参数名。 (alias) queryExcel(params: any): Promise<AxiosResponse<any, any>>(别名) queryExcel(params: any): Promise<AxiosResponse<any, any>>函数的类型声明: queryExcel接收一个params参数,返回值是一个 Axios 响应的 Promise 对象。import queryExcelimport queryExcel提示该函数是通过 import 引入的。
报错原因
queryExcel这个函数 定义时声明了必须传入一个名为params的必填参数 ,函数签名:queryExcel(params: any)但是你调用时写的是queryExcel(),一个参数都没传,TS 校验不通过,抛出 2554 缺少入参的错误。两种解决方式
方式 1:传空对象(接口允许不传参时使用)
如果这个接口不需要传任何查询条件,调用时传一个空对象即可:
TypeScriptqueryExcel({}) .then(res => { templateInfo.value = res.data.data; console.log("res", res.data.data); })方式 2:修改接口定义,把参数改为可选(根源修复)
打开
queryExcel的定义文件,给参数加问号?,变成可选参数:
TypeScript// 原代码类似这样 // export const queryExcel = (params: any) => request(...) // 修改为可选参数 export const queryExcel = (params?: any) => request(...)改完之后就可以直接
queryExcel()不传参调用了。补充说明
- 报错只是 TypeScript 静态类型检查,如果你不传参直接运行项目,后端接口如果能接收空请求,代码其实可以跑通,只是 VSCode 会一直标红
- 优先推荐 传空对象,不改动公共接口定义;只有你确定这个接口大部分场景都不需要传参,再去修改原函数的类型
问题:
接口参数 params 红色波浪线提示了
bashParameter 'params' implicitly has an 'any' type. ts(7006) (parameter) params: any逐句翻译
英文原文 中文翻译 Parameter 'params' implicitly has an 'any' type. ts(7006)参数 params隐式具有any类型。(错误码:ts (7006))(parameter) params: any(参数) params: any
报错原因
TS 报错
ts(7006):参数params没有写类型注解,TypeScript 自动隐式推断为any类型 ,你的项目开启了noImplicitAny强制类型检查,不允许隐式any,因此标红。三种解决方法
方法 1:临时直接标注 any(快速消红,简单粗暴)
给参数手动写上
: any,明确声明类型,消除隐式 any 报错
TypeScriptexport function addUserApi(params: any) { return request({ url: 'api/addUser', method: 'post', data: params, }) }方法 2:定义专属接口类型(规范 TS 项目,推荐)
根据接口实际需要传递的字段,定义 TS 类型,替换 any,类型更安全
TypeScript// 先定义请求参数类型,放到顶部或单独的type文件 interface AddUserParams { id: number permIds: number[] // 新增接口的参数字段 } // 使用自定义类型 export function addUserApi(params: AddUserParams) { return request({ url: 'api/addUser', method: 'post', data: params, }) }方法 3:修改 tsconfig.json 关闭隐式 any 校验(全局关闭,不推荐)
找到项目
tsconfig.json,把noImplicitAny设为false,会全局取消这类报错
javascript{ "compilerOptions": { "noImplicitAny": false } }建议
日常开发优先用方法 1 快速消除波浪线;长期维护项目建议用方法 2写具体类型,充分利用 TS 的类型校验。
6、
三、CSS 篇
1、gap
问:CSS 样式里的,gap: 8px; 啥意思?
gap: 8px 就是 设置元素之间的间距为 8 像素 ,主要用在 Flex、Grid、多列布局 里。
简单拆解:
- 作用 :控制 子元素之间的空隙,不用再给 margin 算间距,更简洁。
- 简写规则
gap: 8px:行、列间距都 = 8pxgap: 10px 5px:行间距 10px,列间距 5px- 细分写法(等价)
- row
-gap: 8px只改 行间距- column
-gap: 8px只改 列间距举个直观例子
css.box { display: flex; flex-wrap: wrap; gap: 8px; /* 所有子盒子之间空出 8px */ }里面每个子元素,上下、左右都会自动隔开 8px。
CSS布局中Gap与Padding的区别
【智能总结】
- 区别:CSS 布局中,Gap 和 Padding 都用于 控制间距,但作用对象与场景不同,新手易混淆
1、Padding:内边距,作用于单个元素,控制内容与边框距离,对任何元素生效,设置后会扩大元素盒模型,增加总尺寸
2、Gap:元素间距,仅适用于 Flex 或 Grid 布局容器,控制 子元素 间空隙,不影响元素自身尺寸
- 应用场景
1、Padding:适合 单个 元素
2、Gap:适合 多元素 排列
掌握此区别可精准控制布局间距。
【原文】
在 CSS 布局中,Gap 和 Padding 都用于 控制间距,但作用对象与场景不同。新手易混淆。
Padding 是 内边距 ,作用于 单个元素,控制其内容与边框的距离。
Gap 是 元素间距,仅适用于 Flex 弹性布局 或 Grid 网格布局容器,控制子元素之间的空隙。简言之,Padding 是元素内部留白,Gap 是元素之间空隙。
Padding 对任何元素生效,设置后会扩大元素盒模型。例如,给 div 设置 padding:30px,其内容会距离边框各30px,且总尺寸增加------若原宽高200px,设置后实际宽高为260px。
Gap 是依赖 Flex 或 Grid 布局,在容器中设置后,子元素间会产生空隙,但不影响元素自身尺寸。比如两个200px 宽的子元素,设置 gap:30px 后,总占用宽度为430px,单个元素尺寸不变。
Padding 区域会继承背景色,被边框包裹,并扩大可点击区域,让按钮更饱满。
Gap 只是纯粹空白,不继承背景,不可点击,仅用于分隔子元素。
实际使用时,padding 适合单个元素内部留白,如 按钮、卡片,Gap 适合多元素排列间距,如网格卡片。掌握这一区别,就能精准控制布局间距,避免踩坑。
2、样式穿透
问题:
/deep/ 开头的 / ,红色波浪线提示了,帮我看一下
bashat-rule or selector expected css(css-ruleorselectorexpected)预期 at 规则或选择器(css 错误码:css-ruleorselectorexpected)
补充说明
- at-rule :指 CSS 中以
@开头的规则,比如@media、@keyframes、@import等。- selector :指 CSS 选择器,比如类选择器
.class、ID 选择器#id、元素选择器div等。- 这个报错的意思是:你的 CSS 代码中,语法结构不正确,解析器在本该出现
@规则或选择器的位置,没有找到有效的内容,导致解析失败。比如你写的/deep/或::v-deep语法格式不对,就会触发这个错误。常见错误示例 & 修正
css/* ❌ 错误写法(/deep/ 前面缺少选择器) */ /deep/ ::-webkit-scrollbar-thumb:hover { background: #aaa; } /* ✅ 正确写法(给 /deep/ 前面加上父选择器) */ .wrapper /deep/ ::-webkit-scrollbar-thumb:hover { background: #aaa; } /* ✅ 更推荐的写法(Vue3 推荐使用 :deep() 伪类) */ :deep(.wrapper) ::-webkit-scrollbar-thumb:hover { background: #aaa; }报错原因
- Vue3 已经 废弃了
/deep/深度穿透语法,VSCode 的 CSS 校验器不识别这个旧语法,直接标红报错;- Vue3 推荐使用
:deep()函数式写法做样式穿透,不再支持/deep/、>>>这类 Vue2 的老穿透符号。修复方案(把所有
/deep/替换为:deep())改你两行代码:
css/* 原来的错误写法 */ /deep/ ::-webkit-scrollbar-thumb:hover {} /* Vue3 正确写法 */ :deep(::webkit-scrollbar-thumb:hover) { } /* 第二行同理替换 */ /* /deep/ .el-upload__tip {} */ :deep(.el-upload__tip) { margin-top: 0; margin-left: 1.5vh; font-size: 1.5vh; }补充说明
1、你的
<style>标签必须 带有scoped属性 ,:deep()才会生效:
html<style scoped> /* 这里写 :deep() 样式 */ </style>2、只是 VSCode 编辑器语法标红,Vue2 项目里
/deep/运行是正常的;但 Vue3 项目必须换成:deep(),既消除报错,又符合新版本规范。3、滚动条伪类写在
:deep()内,可以穿透 scoped 修改子组件 / 全局滚动条样式。
3、








