纸白底改冷调与主色同轴;语义色 sage/wheat/rust 不变,sky 提亮与主色区分。Logo 三段线改浅蓝白、圆点改亮蓝;favicon/PWA 图标/安卓自适应图标/状态栏一并重绘,新增 tools/make-icons.py 作为唯一出处。
@@ -59,3 +59,6 @@ backend/pytest-of-*/
|
||||
# Vditor 运行时资源(lute/i18n/icons,约 3.8 MB)
|
||||
# 由 web/scripts/vendor-vditor.mjs 从 node_modules 生成,不进版本库
|
||||
web/public/vditor/
|
||||
|
||||
# 本机部署/调试的临时脚本与缓存
|
||||
.tmp/
|
||||
|
||||
@@ -359,7 +359,7 @@
|
||||
重做不是换配色,而是**先立设计系统、再让页面去用它**,否则下次还是东拼西凑。
|
||||
|
||||
### 4.1 设计系统(两端共用一套 token)
|
||||
- [x] 色板:纸白 `paper`、墨绿 `primary`、陶土橙 `clay`,语义色 sage/wheat/rust/sky(Web `tailwind.config.js`,安卓 `ui/Theme.kt` 同名同值)
|
||||
- [x] 色板:纸白 `paper`、景泰蓝 `primary`、陶土橙 `clay`,语义色 sage/wheat/rust/sky(Web `tailwind.config.js`,安卓 `ui/Theme.kt` 同名同值)(2026-11-01 主色由墨绿改蓝系,语义色不变)
|
||||
- [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 构建环境(本机踩过的坑)
|
||||
|
||||
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
@@ -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 节 |
|
||||
| 界面:两端统一设计系统(纸白 / 景泰蓝 / 陶土橙,语义色、字号阶、组件库) | ✅ 已重做,见 [`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 干净 |
|
||||
|
||||
@@ -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 一致的景泰蓝 + 陶土色板
|
||||
├── 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("#33443F"))
|
||||
setTextColor(android.graphics.Color.parseColor("#262D39"))
|
||||
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("#4A5854"))
|
||||
setTextColor(android.graphics.Color.parseColor("#4E5867"))
|
||||
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 同一套色板:暖白纸面 + 景泰蓝主色 + 陶土橙强调。
|
||||
* 两端看同一张截图时,颜色应该能对上,所以这里的值不要单独调。
|
||||
*/
|
||||
val Paper = Color(0xFFFBF9F6)
|
||||
val PaperSunken = Color(0xFFF3F0EA)
|
||||
val Line = Color(0xFFE4E0D8)
|
||||
val LineStrong = Color(0xFFD3CEC2)
|
||||
val Paper = Color(0xFFF9FAFC)
|
||||
val PaperSunken = Color(0xFFF3F5F9)
|
||||
val Line = Color(0xFFE2E6EC)
|
||||
val LineStrong = Color(0xFFD3D9E1)
|
||||
|
||||
val Ink = Color(0xFF1F2A28)
|
||||
val InkSoft = Color(0xFF4A5854)
|
||||
val InkMuted = Color(0xFF7C8985)
|
||||
val Ink = Color(0xFF262D39)
|
||||
val InkSoft = Color(0xFF4E5867)
|
||||
val InkMuted = Color(0xFF6C7788)
|
||||
|
||||
val Primary = Color(0xFF2A5A50)
|
||||
val PrimaryDeep = Color(0xFF1E443C)
|
||||
val PrimarySoft = Color(0xFFE3EDE8)
|
||||
val PrimaryTint = Color(0xFFD2E3DB)
|
||||
val Primary = Color(0xFF274B73)
|
||||
val PrimaryDeep = Color(0xFF1F3B5B)
|
||||
val PrimarySoft = Color(0xFFDCE9F7)
|
||||
val PrimaryTint = Color(0xFFB9D3EC)
|
||||
|
||||
val Clay = Color(0xFFC2703D)
|
||||
val ClaySoft = Color(0xFFFBEADF)
|
||||
@@ -39,8 +39,8 @@ val Wheat = Color(0xFFA97C25)
|
||||
val WheatSoft = Color(0xFFF8EED7)
|
||||
val Rust = Color(0xFFB04A32)
|
||||
val RustSoft = Color(0xFFFBE5DF)
|
||||
val Sky = Color(0xFF3E6F91)
|
||||
val SkySoft = Color(0xFFE4EEF5)
|
||||
val Sky = Color(0xFF2F86B0)
|
||||
val SkySoft = Color(0xFFE8F4FA)
|
||||
|
||||
private val colors = lightColorScheme(
|
||||
primary = Primary,
|
||||
|
||||
@@ -7,20 +7,20 @@
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:pathData="M36.3,42.6 L71.7,42.6"
|
||||
android:strokeColor="#EFF5F3"
|
||||
android:strokeColor="#EAF3FB"
|
||||
android:strokeWidth="5.06"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:pathData="M36.3,54 L59.6,54"
|
||||
android:strokeColor="#EFF5F3"
|
||||
android:strokeColor="#EAF3FB"
|
||||
android:strokeWidth="5.06"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:pathData="M36.3,65.4 L50.8,65.4"
|
||||
android:strokeColor="#EFF5F3"
|
||||
android:strokeColor="#EAF3FB"
|
||||
android:strokeWidth="5.06"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:fillColor="#C2703D"
|
||||
android:fillColor="#5CA8E8"
|
||||
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>
|
||||
|
||||
@@ -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">#2F6054</color>
|
||||
<!-- 与 Web 端 Logo(web/src/components/icons.tsx 的 <Logo/>)同一个景泰蓝 -->
|
||||
<color name="ic_launcher_background">#274B73</color>
|
||||
</resources>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<resources>
|
||||
<!-- Compose 自己管配色,这里只提供无 ActionBar 的容器主题 -->
|
||||
<style name="Theme.DadaoTask" parent="android:Theme.Material.Light.NoActionBar">
|
||||
<item name="android:statusBarColor">#3F5A68</item>
|
||||
<item name="android:windowBackground">#F7F8F8</item>
|
||||
<item name="android:statusBarColor">#274B73</item>
|
||||
<item name="android:windowBackground">#F9FAFC</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
"""按 Web Logo(icons.tsx 的 <Logo/>)重绘 PWA 图标:景泰蓝底 + 三条浅蓝白线 + 一点亮蓝。
|
||||
|
||||
用法:python tools/make-icons.py
|
||||
输出覆盖 web/public/icons/*.png(与 SVG 同一套比例:64 画布,rx=18,线宽 4,点半径 6)。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
OUT = ROOT / "web" / "public" / "icons"
|
||||
|
||||
BG = "#274B73"
|
||||
LINE = "#EAF3FB"
|
||||
DOT = "#5CA8E8"
|
||||
|
||||
SS = 8 # 超采样倍数,边缘才不毛
|
||||
|
||||
|
||||
def draw(size: int, *, rounded: bool, inset: float = 0.0) -> Image.Image:
|
||||
"""在 size×size 上画 logo。inset 是安全区留白比例(maskable 图标要留)。"""
|
||||
big = size * SS
|
||||
img = Image.new("RGBA", (big, big), (0, 0, 0, 0))
|
||||
d = ImageDraw.Draw(img)
|
||||
|
||||
if rounded:
|
||||
d.rounded_rectangle([0, 0, big - 1, big - 1], radius=big * 18 / 64, fill=BG)
|
||||
else:
|
||||
d.rectangle([0, 0, big, big], fill=BG)
|
||||
|
||||
# 64 画布坐标 → 像素;inset 让图形整体缩小居中(给 maskable 留安全区)
|
||||
scale = big / 64 * (1 - 2 * inset)
|
||||
off = big * inset
|
||||
|
||||
def box(x: float, y: float) -> tuple[float, float]:
|
||||
return off + x * scale, off + y * scale
|
||||
|
||||
stroke = max(1, round(4 * scale))
|
||||
for y, x2 in ((23, 46), (32, 37), (41, 30)):
|
||||
x1, yy = box(18, y)
|
||||
d.line([x1, yy, *box(x2, y)], fill=LINE, width=stroke)
|
||||
# 圆头:PIL 的 line 没有 cap,用小圆补两端
|
||||
r = stroke / 2
|
||||
for cx, cy in ((x1, yy), box(x2, y)):
|
||||
d.ellipse([cx - r, cy - r, cx + r, cy + r], fill=LINE)
|
||||
|
||||
cx, cy = box(46, 41)
|
||||
r = 6 * scale
|
||||
d.ellipse([cx - r, cy - r, cx + r, cy + r], fill=DOT)
|
||||
|
||||
return img.resize((size, size), Image.LANCZOS)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
# 圆角版:favicon 之外的常规 PWA 图标
|
||||
draw(192, rounded=True).save(OUT / "icon-192.png")
|
||||
draw(512, rounded=True).save(OUT / "icon-512.png")
|
||||
# iOS 会自己裁圆角,底图必须是方的
|
||||
draw(180, rounded=False).save(OUT / "apple-touch-icon.png")
|
||||
# maskable:图形缩进 12% 安全区
|
||||
draw(512, rounded=False, inset=0.12).save(OUT / "maskable-512.png")
|
||||
# 文档里引用的安卓图标预览
|
||||
preview = ROOT / "2.Design" / "screens" / "android-icon-512.png"
|
||||
preview.parent.mkdir(parents=True, exist_ok=True)
|
||||
draw(512, rounded=True).save(preview)
|
||||
for p in sorted(OUT.glob("*.png")):
|
||||
print(p.relative_to(ROOT), Image.open(p).size)
|
||||
print(preview.relative_to(ROOT), Image.open(preview).size)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -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='%232F6054'/%3E%3Cg stroke='%23EFF5F3' 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='%23C2703D'/%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='%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"
|
||||
/>
|
||||
<!-- 装到手机主屏之后:状态栏跟品牌色走,iOS 用不透明底图 -->
|
||||
<meta name="theme-color" content="#2f6054" />
|
||||
<meta name="theme-color" content="#274b73" />
|
||||
<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" />
|
||||
|
||||
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 9.7 KiB |
@@ -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="#2F6054" />
|
||||
<rect width="32" height="32" rx="9" fill="#274B73" />
|
||||
<path
|
||||
d="M9 11.5h14M9 16h9.5M9 20.5h6"
|
||||
stroke="#EFF5F3"
|
||||
stroke="#EAF3FB"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<circle cx="23" cy="20.5" r="3" fill="#C2703D" />
|
||||
<circle cx="23" cy="20.5" r="3" fill="#5CA8E8" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
@@ -6,42 +6,42 @@ export default {
|
||||
colors: {
|
||||
// 底色:暖白纸面,不用纯白,避免刺眼、也让卡片有层次
|
||||
paper: {
|
||||
50: "#FCFCFA",
|
||||
100: "#F7F7F3",
|
||||
200: "#F0F0EA",
|
||||
300: "#E6E6DE",
|
||||
50: "#F9FAFC",
|
||||
100: "#F3F5F9",
|
||||
200: "#E9EDF3",
|
||||
300: "#DFE5ED",
|
||||
},
|
||||
// 分隔线与描边
|
||||
// 分隔线与描边(冷灰,跟蓝主色同一个中性轴)
|
||||
line: {
|
||||
100: "#ECECE6",
|
||||
200: "#E1E1D9",
|
||||
300: "#D2D2C8",
|
||||
100: "#EDEFF3",
|
||||
200: "#E2E6EC",
|
||||
300: "#D3D9E1",
|
||||
},
|
||||
// 文字与中性
|
||||
ink: {
|
||||
50: "#F6F8F7",
|
||||
100: "#E9EDEB",
|
||||
200: "#D7DDDA",
|
||||
300: "#BAC3C0",
|
||||
400: "#96A19D",
|
||||
500: "#6D7874",
|
||||
600: "#4E5956",
|
||||
700: "#39443F",
|
||||
800: "#252E2A",
|
||||
900: "#161C1A",
|
||||
50: "#F6F8FA",
|
||||
100: "#E9EEF3",
|
||||
200: "#D7DEE7",
|
||||
300: "#B8C2CE",
|
||||
400: "#929EAD",
|
||||
500: "#6C7788",
|
||||
600: "#4E5867",
|
||||
700: "#3A4352",
|
||||
800: "#262D39",
|
||||
900: "#161C25",
|
||||
},
|
||||
// 主色:墨松绿,克制、有"工作台"气质,避开任务工具常见的荧光蓝
|
||||
// 主色:景泰蓝(沉一点的靛蓝),克制、有"工作台"气质,避开任务工具常见的荧光蓝
|
||||
primary: {
|
||||
50: "#EFF5F3",
|
||||
100: "#DBE9E4",
|
||||
200: "#B7D2C9",
|
||||
300: "#8CB6A9",
|
||||
400: "#619688",
|
||||
500: "#41796A",
|
||||
600: "#2F6054",
|
||||
700: "#264E44",
|
||||
800: "#1E3E36",
|
||||
900: "#172F29",
|
||||
50: "#EFF5FB",
|
||||
100: "#DCE9F7",
|
||||
200: "#B9D3EC",
|
||||
300: "#8FB6DD",
|
||||
400: "#6094C8",
|
||||
500: "#3E76AC",
|
||||
600: "#2E5D8D",
|
||||
700: "#274B73",
|
||||
800: "#1F3B5B",
|
||||
900: "#1A2F48",
|
||||
},
|
||||
// 强调色:陶土橙,只用在"需要人注意"的地方
|
||||
clay: {
|
||||
@@ -59,7 +59,8 @@ export default {
|
||||
sage: { 500: "#2F7D5E", 50: "#EAF4EF" },
|
||||
wheat: { 500: "#A97C25", 50: "#F8F1E2" },
|
||||
rust: { 500: "#B04A32", 50: "#FAEDE9" },
|
||||
sky: { 500: "#3E6F91", 50: "#EBF2F7" },
|
||||
// 信息色:比主色更亮更青一档,免得和 primary 的「进行中」糊在一起
|
||||
sky: { 500: "#2F86B0", 50: "#E8F4FA" },
|
||||
},
|
||||
fontFamily: {
|
||||
sans: [
|
||||
@@ -93,8 +94,8 @@ export default {
|
||||
"3xl": "24px",
|
||||
},
|
||||
boxShadow: {
|
||||
card: "0 1px 2px rgba(22,28,26,0.04), 0 8px 24px -16px rgba(22,28,26,0.35)",
|
||||
pop: "0 2px 6px rgba(22,28,26,0.06), 0 20px 48px -24px rgba(22,28,26,0.35)",
|
||||
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)",
|
||||
inset: "inset 0 1px 0 rgba(255,255,255,0.6)",
|
||||
},
|
||||
keyframes: {
|
||||
|
||||