feat(design): 主色改靛蓝 #4F46E5,Web 与安卓及 Logo 同步
CI / backend (push) Canceled after 0s

- primary 换成 Indigo 阶(600 即 #4F46E5),中性色转 Slate 轴、纸面更冷更白
- 实心主色面(主按钮、工作区选中)用 600,链接/浅底文字仍用 700 保对比度
- 信息色 sky 调深到 #16697C,与靛蓝同屏能分清
- Logo / favicon / PWA 图标 / 安卓自适应图标统一为靛蓝底 + 白线 + 淡紫点,tools/make-icons.py 重新生成
- 文档与 e2e 断言同步(主题色 #4f46e5)
This commit is contained in:
2026-11-02 15:33:26 +08:00
parent 288e27e465
commit d7c9670880
22 changed files with 78 additions and 78 deletions
+3 -3
View File
@@ -359,7 +359,7 @@
重做不是换配色,而是**先立设计系统、再让页面去用它**,否则下次还是东拼西凑。
### 4.1 设计系统(两端共用一套 token)
- [x] 色板:纸白 `paper`、景泰蓝 `primary`、陶土橙 `clay`,语义色 sage/wheat/rust/sky(Web `tailwind.config.js`,安卓 `ui/Theme.kt` 同名同值)(2026-11-01 主色由墨绿改蓝系,语义色不变)
- [x] 色板:冷白 `paper`、靛蓝 `primary`(`#4F46E5`)、陶土橙 `clay`,语义色 sage/wheat/rust/sky(Web `tailwind.config.js`,安卓 `ui/Theme.kt` 同名同值)(2026-11-01 主色由墨绿改蓝系;2026-11-02 再由沉靛蓝改为高饱和靛蓝 `#4F46E5`,中性色转 Slate 轴,语义色不变)
- [x] 字号阶:11/12/13/14/15/18/22/26/36,正文 14、辅助 11/12;细描边 + 极轻投影 + 14dp 圆角
- [x] 组件:卡片 / 按钮(primary、ghost、subtle、clay、danger)/ 状态 pill / 输入 / 空态 / 骨架屏 / 抽屉
- [x] 图标:Web 用自绘线性图标集(`components/icons.tsx`),安卓用 Material Icons + 少量自绘,线宽一致
@@ -436,7 +436,7 @@
- [x] `ui/UpdateDialog.kt`:当前/新版本号、更新说明、下载进度、失败重试;「允许安装未知应用」只跳系统设置,不代点
- [x] 权限回来那一跳要接住:用户在系统设置里开完开关回到 App,再点同一个按钮时先复查权限,开了就直接装——否则按钮点了没反应
- [x] 启动时自动查一次;首页底部有「检查更新」手动入口,结果用一句话回给用户
- [x] 图标换成与 Web 的 `<Logo/>` 同一套图形(景泰蓝圆角底 + 三条浅蓝白圆头线 + 亮蓝点),生成 `mipmap-*/ic_launcher{,_round}.png` 与自适应图标 `mipmap-anydpi-v26/ic_launcher{,_round}.xml`,512px 预览存 `2.Design/screens/android-icon-512.png`
- [x] 图标换成与 Web 的 `<Logo/>` 同一套图形(靛蓝圆角底 + 三条白色圆头线 + 淡紫点),生成 `mipmap-*/ic_launcher{,_round}.png` 与自适应图标 `mipmap-anydpi-v26/ic_launcher{,_round}.xml`,512px 预览存 `2.Design/screens/android-icon-512.png`
- [x] 版本 `versionCode = 2` / `versionName = "0.2.0"`;发布签名从 `android/keystore.properties` 读,无该文件时回落调试签名(保证别人 clone 下来也能 `assembleRelease`)
### 6.3 构建环境(本机踩过的坑)
@@ -520,7 +520,7 @@
### 8.3 实现
| 位置 | 内容 |
|---|---|
| `web/vite.config.ts` | manifest(standalone、主题色 `#274b73`、三个图标)+ workbox(`navigateFallback` 回退 `index.html`,`/vditor/` 走 CacheFirst,`/api/` NetworkOnly) |
| `web/vite.config.ts` | manifest(standalone、主题色 `#4f46e5`、三个图标)+ workbox(`navigateFallback` 回退 `index.html`,`/vditor/` 走 CacheFirst,`/api/` NetworkOnly) |
| `web/index.html` | favicon 改成同款 Logo(内联 SVG)、`theme-color`、`apple-touch-icon`、`viewport-fit=cover` |
| `web/src/lib/pwa.ts` | 生产环境注册 SW、`dadao:pwa-update-ready` 事件、`applyServiceWorkerUpdate()`、`isStandalone()` |
| `web/src/components/UpdatePrompt.tsx` | 底部卡片「有新版了」+ 稍后 / 刷新(**居中用 flex 包一层**:`animate-slide-up` 的 keyframes 自带 transform,会和 `-translate-x-1/2` 抢属性,手机窄屏上卡片会被推出屏幕右侧) |
Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 21 KiB

+1 -1
View File
@@ -28,7 +28,7 @@
| 安卓端任务 / 看板 / 工单 / 知识 / 成员五个页面 | ✅ 编译成 APK(10.1 MB),模拟器上逐页跑通,截图见 [`2.Design/screens/`](2.Design/screens) |
| 工单:升级不搬数据、受理→办理→提交→验收、驳回、改派、站内通知 | ✅ 后端 + Web + 安卓已通,双人 e2e 8 项全绿 |
| 知识:工单验收自动生成草稿、核对后发布、来源可追、手记 | ✅ 后端 + Web + 安卓已通,e2e 7 项全绿 |
| 界面:两端统一设计系统(纸白 / 景泰蓝 / 陶土橙,语义色、字号阶、组件库) | ✅ 已重做,见 [`2.Design/ROADMAP-M0-M1.md`](2.Design/ROADMAP-M0-M1.md) 第 4 节 |
| 界面:两端统一设计系统(冷白 / 靛蓝 #4F46E5 / 陶土橙,语义色、字号阶、组件库) | ✅ 已重做,见 [`2.Design/ROADMAP-M0-M1.md`](2.Design/ROADMAP-M0-M1.md) 第 4 节 |
| 富文本:备注 / 工单说明 / 知识正文支持图文混排与附件 | ✅ Web 用 Vditor 编辑、安卓用 Markwon 预览编辑(e2e 15 项全绿);接口见 [`2.Design/API.md`](2.Design/API.md) 第 6 节 |
| 知识切片与语义检索(混合打分、只搜已发布、带来源) | ✅ 已跑通:`real[]` + SQL 余弦(pgvector 待独立实例),`pytest` 5 个用例 + `e2e/retrieval.mjs` 6 项 |
| 后端测试 | ✅ 79 passed,ruff 干净 |
+1 -1
View File
@@ -90,7 +90,7 @@ app/src/main/java/com/dadao/task/
│ ├── Api.kt OkHttp + kotlinx.serialization;信封解包、业务码抛 ApiException
│ └── TokenStore.kt 令牌本地存储(TODO:换 EncryptedSharedPreferences)
└── ui/
├── Theme.kt 与 Web 一致的景泰蓝 + 陶土色板
├── Theme.kt 与 Web 一致的靛蓝 #4F46E5 + 陶土色板
├── LoginScreen.kt 手机号 + 验证码
├── HomeScreen.kt 工作区列表 / 新建 / 生成邀请码
├── TaskListScreen.kt 任务视图切换、快速新建、勾选完成、详情与评论
@@ -329,7 +329,7 @@ private fun KnowledgeBody(text: String) {
setTextIsSelectable(true)
textSize = 14f
setLineSpacing(0f, 1.5f)
setTextColor(android.graphics.Color.parseColor("#262D39"))
setTextColor(android.graphics.Color.parseColor("#1E293B"))
markwon.setMarkdown(this, text)
}
},
@@ -350,7 +350,7 @@ private fun PreviewBody(
textSize = 15f
setPadding(0, 0, 0, 0)
setLineSpacing(0f, 1.5f)
setTextColor(android.graphics.Color.parseColor("#4E5867"))
setTextColor(android.graphics.Color.parseColor("#475569"))
markwon.setMarkdown(this, text)
}
},
@@ -13,22 +13,22 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
/**
* 与 Web 端 tailwind.config.js 同一套色板:暖白纸面 + 景泰蓝主色 + 陶土橙强调。
* 与 Web 端 tailwind.config.js 同一套色板:冷白纸面 + 靛蓝主色(#4F46E5)+ 陶土橙强调。
* 两端看同一张截图时,颜色应该能对上,所以这里的值不要单独调。
*/
val Paper = Color(0xFFF9FAFC)
val PaperSunken = Color(0xFFF3F5F9)
val Line = Color(0xFFE2E6EC)
val LineStrong = Color(0xFFD3D9E1)
val Paper = Color(0xFFFAFAFC)
val PaperSunken = Color(0xFFF4F5F9)
val Line = Color(0xFFE3E7EF)
val LineStrong = Color(0xFFD5DAE4)
val Ink = Color(0xFF262D39)
val InkSoft = Color(0xFF4E5867)
val InkMuted = Color(0xFF5F6B7C)
val Ink = Color(0xFF1E293B)
val InkSoft = Color(0xFF475569)
val InkMuted = Color(0xFF64748B)
val Primary = Color(0xFF274B73)
val PrimaryDeep = Color(0xFF1F3B5B)
val PrimarySoft = Color(0xFFDCE9F7)
val PrimaryTint = Color(0xFFB9D3EC)
val Primary = Color(0xFF4F46E5)
val PrimaryDeep = Color(0xFF4338CA)
val PrimarySoft = Color(0xFFE0E7FF)
val PrimaryTint = Color(0xFFC7D2FE)
val Clay = Color(0xFFC2703D)
val ClaySoft = Color(0xFFFBEADF)
@@ -39,7 +39,7 @@ val Wheat = Color(0xFFA97C25)
val WheatSoft = Color(0xFFF8EED7)
val Rust = Color(0xFFB04A32)
val RustSoft = Color(0xFFFBE5DF)
val Sky = Color(0xFF1E7F94)
val Sky = Color(0xFF16697C)
val SkySoft = Color(0xFFE6F4F6)
private val colors = lightColorScheme(
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 自适应图标前景:与 Web Logo 同一套比例(108 画布,安全区 66 居中) -->
<!-- 自适应图标前景:与 Web Logo 同一套比例(108 画布,安全区 66 居中;靛蓝底上的白线与淡紫点) -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
@@ -7,20 +7,20 @@
android:viewportHeight="108">
<path
android:pathData="M36.3,42.6 L71.7,42.6"
android:strokeColor="#EAF3FB"
android:strokeColor="#FFFFFF"
android:strokeWidth="5.06"
android:strokeLineCap="round" />
<path
android:pathData="M36.3,54 L59.6,54"
android:strokeColor="#EAF3FB"
android:strokeColor="#FFFFFF"
android:strokeWidth="5.06"
android:strokeLineCap="round" />
<path
android:pathData="M36.3,65.4 L50.8,65.4"
android:strokeColor="#EAF3FB"
android:strokeColor="#FFFFFF"
android:strokeWidth="5.06"
android:strokeLineCap="round" />
<path
android:fillColor="#5CA8E8"
android:fillColor="#C7D2FE"
android:pathData="M71.7,65.4 m-7.6,0 a7.6,7.6 0 1,0 15.2,0 a7.6,7.6 0 1,0 -15.2,0" />
</vector>
+2 -2
View File
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- 与 Web 端 Logo(web/src/components/icons.tsx 的 <Logo/>)同一个景泰蓝 -->
<color name="ic_launcher_background">#274B73</color>
<!-- 与 Web 端 Logo(web/src/components/icons.tsx 的 <Logo/>)同一个靛蓝 -->
<color name="ic_launcher_background">#4F46E5</color>
</resources>
+2 -2
View File
@@ -2,7 +2,7 @@
<resources>
<!-- Compose 自己管配色,这里只提供无 ActionBar 的容器主题 -->
<style name="Theme.DadaoTask" parent="android:Theme.Material.Light.NoActionBar">
<item name="android:statusBarColor">#274B73</item>
<item name="android:windowBackground">#F9FAFC</item>
<item name="android:statusBarColor">#4F46E5</item>
<item name="android:windowBackground">#FAFAFC</item>
</style>
</resources>
+4 -4
View File
@@ -1,4 +1,4 @@
"""按 Web Logo(icons.tsx 的 <Logo/>)重绘 PWA 图标:景泰蓝底 + 三条浅蓝白线 + 一点亮蓝。
"""按 Web Logo(icons.tsx 的 <Logo/>)重绘 PWA 图标:靛蓝底 + 三条白线 + 一点淡紫。
用法:python tools/make-icons.py
输出覆盖 web/public/icons/*.png(与 SVG 同一套比例:64 画布,rx=18,线宽 4,点半径 6)。
@@ -13,9 +13,9 @@ from PIL import Image, ImageDraw
ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "web" / "public" / "icons"
BG = "#274B73"
LINE = "#EAF3FB"
DOT = "#5CA8E8"
BG = "#4F46E5"
LINE = "#FFFFFF"
DOT = "#C7D2FE"
SS = 8 # 超采样倍数,边缘才不毛
+1 -1
View File
@@ -118,7 +118,7 @@ async function main() {
check("页面骨架已预缓存", shellCached);
const themeColor = await page.getAttribute('meta[name="theme-color"]', "content");
check("主题色是景泰蓝", themeColor === "#274b73", String(themeColor));
check("主题色是品牌靛蓝", themeColor === "#4f46e5", String(themeColor));
check("桌面尺寸没有控制台报错", errors.length === 0, errors.join(" | "));
await desktop.close();
+3 -3
View File
@@ -5,13 +5,13 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<title>达道Task</title>
<meta name="description" content="达道Task:任务、工单、知识库,一条道走到底" />
<!-- 和 App 内左上角、安卓桌面同一枚图标:三段浅蓝白线 + 一点亮蓝 -->
<!-- 和 App 内左上角、安卓桌面同一枚图标:靛蓝底 + 三条白线 + 一点淡紫 -->
<link
rel="icon"
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='18' fill='%23274B73'/%3E%3Cg stroke='%23EAF3FB' stroke-width='4' stroke-linecap='round'%3E%3Cpath d='M18 23h28'/%3E%3Cpath d='M18 32h19'/%3E%3Cpath d='M18 41h12'/%3E%3C/g%3E%3Ccircle cx='46' cy='41' r='6' fill='%235CA8E8'/%3E%3C/svg%3E"
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='18' fill='%234F46E5'/%3E%3Cg stroke='%23FFFFFF' stroke-width='4' stroke-linecap='round'%3E%3Cpath d='M18 23h28'/%3E%3Cpath d='M18 32h19'/%3E%3Cpath d='M18 41h12'/%3E%3C/g%3E%3Ccircle cx='46' cy='41' r='6' fill='%23C7D2FE'/%3E%3C/svg%3E"
/>
<!-- 装到手机主屏之后:状态栏跟品牌色走,iOS 用不透明底图 -->
<meta name="theme-color" content="#274b73" />
<meta name="theme-color" content="#4f46e5" />
<link rel="apple-touch-icon" href="/icons/apple-touch-icon.png" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-title" content="达道Task" />
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

+3 -3
View File
@@ -290,13 +290,13 @@ export const Spinner = ({ size = 16, className = "" }: { size?: number; classNam
export const Logo = ({ size = 28 }: { size?: number }) => (
<svg width={size} height={size} viewBox="0 0 32 32" aria-hidden>
<rect width="32" height="32" rx="9" fill="#274B73" />
<rect width="32" height="32" rx="9" fill="#4F46E5" />
<path
d="M9 11.5h14M9 16h9.5M9 20.5h6"
stroke="#EAF3FB"
stroke="#FFFFFF"
strokeWidth="2"
strokeLinecap="round"
/>
<circle cx="23" cy="20.5" r="3" fill="#5CA8E8" />
<circle cx="23" cy="20.5" r="3" fill="#C7D2FE" />
</svg>
);
+4 -4
View File
@@ -72,7 +72,7 @@
disabled:pointer-events-none disabled:opacity-40;
}
.btn-primary {
@apply btn bg-primary-700 text-white shadow-sm hover:bg-primary-800 active:translate-y-px;
@apply btn bg-primary-600 text-white shadow-sm hover:bg-primary-700 active:translate-y-px;
}
.btn-ghost {
@apply btn border border-line-200 bg-white text-ink-700 hover:border-line-300 hover:bg-paper-50;
@@ -157,8 +157,8 @@
scrollbar-width: none;
}
.grid-lines {
background-image: linear-gradient(to right, rgba(22, 28, 26, 0.04) 1px, transparent 1px),
linear-gradient(to bottom, rgba(22, 28, 26, 0.04) 1px, transparent 1px);
background-image: linear-gradient(to right, rgba(15, 23, 42, 0.04) 1px, transparent 1px),
linear-gradient(to bottom, rgba(15, 23, 42, 0.04) 1px, transparent 1px);
background-size: 32px 32px;
}
}
@@ -443,6 +443,6 @@
.rich-text .vditor-hint {
border-color: theme("colors.line.200");
border-radius: 10px;
box-shadow: 0 12px 32px -12px rgba(31, 42, 40, 0.28);
box-shadow: 0 12px 32px -12px rgba(15, 23, 42, 0.28);
font-size: 0.8rem;
}
+1 -1
View File
@@ -157,7 +157,7 @@ export function WorkspacePage() {
<span
className={`pill ${
m.role === "owner"
? "bg-primary-700 text-white"
? "bg-primary-600 text-white"
: m.role === "admin"
? "bg-primary-50 text-primary-700"
: "bg-paper-200 text-ink-600"
+31 -31
View File
@@ -4,44 +4,44 @@ export default {
theme: {
extend: {
colors: {
// 底色:暖白纸面,不用纯白,避免刺眼、也让卡片有层次
// 底色:近白的冷调纸面,不用纯白,避免刺眼、也让卡片有层次
paper: {
50: "#F9FAFC",
100: "#F3F5F9",
200: "#E9EDF3",
300: "#DFE5ED",
50: "#FAFAFC",
100: "#F4F5F9",
200: "#EBEDF3",
300: "#E0E4EC",
},
// 分隔线与描边(冷灰,跟蓝主色同一个中性轴)
// 分隔线与描边(冷灰,跟靛蓝同一个中性轴)
line: {
100: "#EDEFF3",
200: "#E2E6EC",
300: "#D3D9E1",
100: "#EEF0F5",
200: "#E3E7EF",
300: "#D5DAE4",
},
// 文字与中性
// 文字与中性(Slate 轴:比纯灰更冷一点,和靛蓝同屏不打架)
ink: {
50: "#F6F8FA",
100: "#E9EEF3",
200: "#D7DEE7",
300: "#B8C2CE",
400: "#929EAD",
500: "#5F6B7C",
600: "#4E5867",
700: "#3A4352",
800: "#262D39",
900: "#161C25",
400: "#94A3B8",
500: "#64748B",
600: "#475569",
700: "#334155",
800: "#1E293B",
900: "#0F172A",
},
// 主色:景泰蓝(沉一点的靛蓝),克制、有"工作台"气质,避开任务工具常见的荧光蓝
// 主色:靛蓝 #4F46E5(Indigo 600)。饱和度足、明度高,压在按钮/链接/选中态上不发灰。
primary: {
50: "#EFF5FB",
100: "#DCE9F7",
200: "#B9D3EC",
300: "#8FB6DD",
400: "#6094C8",
500: "#3E76AC",
600: "#2E5D8D",
700: "#274B73",
800: "#1F3B5B",
900: "#1A2F48",
50: "#EEF2FF",
100: "#E0E7FF",
200: "#C7D2FE",
300: "#A5B4FC",
400: "#818CF8",
500: "#6366F1",
600: "#4F46E5",
700: "#4338CA",
800: "#3730A3",
900: "#312E81",
},
// 强调色:陶土橙,只用在"需要人注意"的地方
clay: {
@@ -59,8 +59,8 @@ export default {
sage: { 500: "#2F7D5E", 50: "#EAF4EF" },
wheat: { 500: "#A97C25", 50: "#F8F1E2" },
rust: { 500: "#B04A32", 50: "#FAEDE9" },
// 信息色:往青绿方向拉开一档,和 primary 的「进行中」在同一屏里也能分清
sky: { 500: "#1E7F94", 50: "#E6F4F6" },
// 信息色:往青绿方向拉开一大档,和靛蓝主色在同一屏里一眼分得清
sky: { 500: "#16697C", 50: "#E6F4F6" },
},
fontFamily: {
sans: [
@@ -94,8 +94,8 @@ export default {
"3xl": "24px",
},
boxShadow: {
card: "0 1px 2px rgba(20,27,36,0.04), 0 8px 24px -16px rgba(20,27,36,0.35)",
pop: "0 2px 6px rgba(20,27,36,0.06), 0 20px 48px -24px rgba(20,27,36,0.35)",
card: "0 1px 2px rgba(15,23,42,0.04), 0 8px 24px -16px rgba(15,23,42,0.30)",
pop: "0 2px 6px rgba(15,23,42,0.06), 0 20px 48px -24px rgba(15,23,42,0.32)",
inset: "inset 0 1px 0 rgba(255,255,255,0.6)",
},
keyframes: {
+2 -2
View File
@@ -22,8 +22,8 @@ export default defineConfig({
scope: "/",
display: "standalone",
orientation: "any",
background_color: "#f3f5f9",
theme_color: "#274b73",
background_color: "#f4f5f9",
theme_color: "#4f46e5",
categories: ["productivity", "business"],
icons: [
{ src: "/icons/icon-192.png", sizes: "192x192", type: "image/png", purpose: "any" },