Upgrade Tailwind CSS from 3 to 4 with CSS-first config migration

Migrate tailwind.config.js to @theme in CSS, replace PostCSS plugins
with @tailwindcss/postcss, add @reference to 12 Vue scoped styles using
@apply, and remove autoprefixer (now built into Tailwind 4).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 11:29:03 +08:00
parent 08688793ac
commit 4e22643999
17 changed files with 866 additions and 1499 deletions

2192
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -16,6 +16,7 @@
},
"dependencies": {
"@primevue/themes": "^4.5.4",
"@tailwindcss/postcss": "^4.2.1",
"axios": "^1.13.6",
"chart.js": "^4.5.1",
"chartjs-adapter-moment": "^1.0.1",
@@ -55,7 +56,6 @@
"@vitejs/plugin-vue": "^5.2.4",
"@vue/eslint-config-prettier": "^10.2.0",
"@vue/test-utils": "^2.4.6",
"autoprefixer": "^10.4.27",
"cypress": "^15.11.0",
"cypress-xpath": "^2.0.1",
"eslint": "^9.39.3",
@@ -66,7 +66,7 @@
"prettier": "^3.8.1",
"sass": "^1.97.3",
"start-server-and-test": "^2.1.5",
"tailwindcss": "^3.4.19",
"tailwindcss": "^4.2.1",
"ts-node": "^10.9.2",
"typescript": "^5.9.3",
"vite": "^6.4.1",

View File

@@ -1,6 +1,5 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
"@tailwindcss/postcss": {},
},
};

View File

@@ -1,17 +1,75 @@
/* 引入 Google fonts */
@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap');
@tailwind base;
@tailwind components;
@tailwind utilities;
@import "tailwindcss" layer(tailwind-base);
@layer tailwind-base, primevue;
@layer tailwind-base {
@tailwind base;
@theme {
--color-*: initial;
--breakpoint-*: initial;
--font-size-*: initial;
--color-transparent: transparent;
--color-current: currentColor;
--color-primary: #0099FF;
--color-secondary: #FFAA44;
--color-cfm-primary: #0099FF;
--color-cfm-secondary: #FFAA44;
--color-neutral-10: #ffffff;
--color-neutral-50: #f8fafc;
--color-neutral-100: #f1f5f9;
--color-neutral-200: #e2e8f0;
--color-neutral-300: #cbd5e1;
--color-neutral-400: #94a3b8;
--color-neutral-500: #64748b;
--color-neutral-600: #475569;
--color-neutral-700: #334155;
--color-neutral-800: #1e293b;
--color-neutral-900: #0f172a;
--color-danger: #FF3366;
--container-center: true;
--container-padding: 16px;
--font-size-xs: 12px;
--font-size-xs--line-height: 1;
--font-size-sm: 14px;
--font-size-sm--line-height: 1;
--font-size-base: 16px;
--font-size-base--line-height: 1;
--font-size-lg: 18px;
--font-size-lg--line-height: 1;
--font-size-xl: 20px;
--font-size-xl--line-height: 1;
--font-size-2xl: 24px;
--font-size-2xl--line-height: 1;
--font-size-3xl: 30px;
--font-size-3xl--line-height: 1;
--font-size-4xl: 36px;
--font-size-4xl--line-height: 1;
--breakpoint-2xl: 1536px;
--animate-fadein: fadein 1s ease-in-out;
--animate-fadeout: fadeout 1s ease-in-out;
--animate-edit: edit 0.6s ease;
}
@layer tailwind-utilities {
@tailwind components;
@tailwind utilities;
@keyframes fadein {
0% { opacity: 0; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
@keyframes fadeout {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 0; }
}
@keyframes edit {
0%, 100% { transform: rotate(0deg); }
25% { transform: rotate(9deg); }
75% { transform: rotate(-9deg); }
}

View File

@@ -130,6 +130,7 @@ export default {
}
</script>
<style scoped>
@reference "../../../../assets/tailwind.css";
.ghostSelected {
@apply bg-primary/20
}

View File

@@ -277,6 +277,7 @@ export default {
}
</script>
<style scoped>
@reference "../../../assets/tailwind.css";
/* 進度條顏色 */
:deep(.p-progressbar .p-progressbar-value) {
@apply bg-primary

View File

@@ -158,6 +158,7 @@ export default {
}
</script>
<style scoped>
@reference "../../../../assets/tailwind.css";
.ghostSelected {
@apply shadow-[0px_0px_100px_-10px_inset] shadow-neutral-200
}

View File

@@ -726,6 +726,7 @@ export default {
}
</script>
<style scoped>
@reference "../../../../assets/tailwind.css";
:deep(table tbody td:nth-child(2)) {
@apply whitespace-nowrap break-keep overflow-hidden text-ellipsis max-w-0
}

View File

@@ -125,6 +125,7 @@ export default {
</script>
<style scoped>
@reference "../../../../assets/tailwind.css";
/* TimeLine */
:deep(.p-timeline) {
@apply leading-none my-4

View File

@@ -358,6 +358,7 @@ export default {
</script>
<style scoped>
@reference "../../../../assets/tailwind.css";
/* Table set */
:deep(.p-datatable-thead) {
@apply sticky top-0 left-0 z-10 bg-neutral-10

View File

@@ -370,6 +370,7 @@ export default {
</script>
<style scoped>
@reference "../../../assets/tailwind.css";
:deep(.p-progressbar .p-progressbar-value) {
@apply bg-primary
}

View File

@@ -278,6 +278,7 @@ export default {
</script>
<style scoped>
@reference "../../../assets/tailwind.css";
/* 進度條顏色 */
:deep(.p-progressbar .p-progressbar-value) {
@apply bg-primary

View File

@@ -163,6 +163,7 @@ export default {
}
</script>
<style scoped>
@reference "../../assets/tailwind.css";
:deep(.p-progressbar .p-progressbar-value) {
@apply bg-neutral-900
}

View File

@@ -1312,6 +1312,7 @@ export default {
}
</script>
<style scoped>
@reference "../../../assets/tailwind.css";
.active {
@apply text-primary
}

View File

@@ -941,6 +941,7 @@ export default {
}
</script>
<style scoped>
@reference "../../../assets/tailwind.css";
.active {
@apply text-primary
}

View File

@@ -623,6 +623,7 @@
}
</script>
<style scoped>
@reference "../../assets/tailwind.css";
:deep(thead) {
@apply sticky top-0 bg-neutral-10 after:border-b after:border-neutral-500 after:w-full after:left-0 after:bottom-0 after:absolute table table-fixed w-full
}

View File

@@ -1,78 +0,0 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx,vue}",
"./src/**/**/*.{js,ts,jsx,tsx,vue}",
"./src/**/**/**/*.{js,ts,jsx,tsx,vue}"],
theme: {
colors: {
transparent: 'transparent',
current: 'currentColor',
'primary': '#0099FF',
'secondary': '#FFAA44',
'cfm-primary': '#0099FF',
'cfm-secondary': '#FFAA44',
'neutral': {
10: '#ffffff',
50: '#f8fafc',
100: '#f1f5f9',
200: '#e2e8f0',
300: '#cbd5e1',
400: '#94a3b8',
500: '#64748b',
600: '#475569',
700: '#334155',
800: '#1e293b',
900: '#0f172a'
},
'danger': '#FF3366',
},
container: {
center: true,
padding: '16px',
},
fontSize: {
xs: ['12px', 1],
sm: ['14px', 1],
base: ['16px', 1],
lg: ['18px', 1],
xl: ['20px', 1],
'2xl': ['24px', 1],
'3xl': ['30px', 1],
'4xl': ['36px', 1],
},
screens: {
// 'sm': '640px',
// 'md': '768px',
// 'lg': '1024px',
// 'xl': '1280px',
'2xl': '1536px',
},
extend: {
keyframes: {
'fadein': {
'0%': { opacity: '0' },
'50%': { opacity: '0.5' },
'100%': { opacity: '1' },
},
'fadeout': {
'0%': { opacity: '1' },
'50%': { opacity: '0.5' },
'100%': { opacity: '0' },
},
'edit': {
'0%, 100%': { transform: 'rotate(0deg)' },
'25%': { transform: 'rotate(9deg)' },
'75%': { transform: 'rotate(-9deg)' },
},
},
animation: {
'fadein': 'fadein 1s ease-in-out',
'fadeout': 'fadeout 1s ease-in-out',
'edit': 'edit 0.6s ease'
},
},
},
};