dsh-design-studio 0.7.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +1 -1
- package/README.md +1 -1
- package/cordis.patch.yml +8 -6
- package/lib/browser.js +34 -0
- package/lib/client.js +479 -59
- package/lib/core.js +6 -4
- package/lib/handoff.js +14 -0
- package/lib/index.js +5 -5
- package/lib/look.js +39 -13
- package/lib/picker.js +951 -35
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +2 -2
- package/skills/ui-design/SKILL.md +5 -3
- package/skills/ui-design/references/app.md +1 -1
- package/skills/ui-design/references/desktop.md +24 -0
- package/skills/ui-design/references/miniprogram.md +63 -0
package/lib/core.js
CHANGED
|
@@ -31,12 +31,14 @@ const JOB_READ_TOOL = /^(job_output|bash_output|read_job|job_read)$/i
|
|
|
31
31
|
const BG_JOB = /\bbackground job\s+`?([\w.:-]{3,})/i
|
|
32
32
|
const MAX_SCREENS = 24
|
|
33
33
|
|
|
34
|
-
//
|
|
35
|
-
export const KINDS = ['web', 'app', 'tablet', 'poster', 'slides', 'email']
|
|
34
|
+
// 设计类型:决定右侧用什么画幅显示(网页框、手机、小程序、平板、桌面软件窗口、海报、幻灯片、邮件)
|
|
35
|
+
export const KINDS = ['web', 'app', 'miniprogram', 'tablet', 'desktop', 'poster', 'slides', 'email']
|
|
36
36
|
const KIND_ALIAS = {
|
|
37
|
-
website: 'web', site: 'web', page: 'web', landing: 'web',
|
|
38
|
-
phone: 'app', mobile: 'app', ios: 'app', android: 'app',
|
|
37
|
+
website: 'web', site: 'web', page: 'web', landing: 'web', dashboard: 'web',
|
|
38
|
+
phone: 'app', mobile: 'app', ios: 'app', android: 'app',
|
|
39
|
+
mini: 'miniprogram', miniapp: 'miniprogram', weapp: 'miniprogram', wechat: 'miniprogram', 小程序: 'miniprogram',
|
|
39
40
|
pad: 'tablet', ipad: 'tablet',
|
|
41
|
+
pc: 'desktop', electron: 'desktop', tauri: 'desktop', mac: 'desktop', macos: 'desktop', windows: 'desktop', win: 'desktop', 桌面: 'desktop', 桌面软件: 'desktop',
|
|
40
42
|
social: 'poster', image: 'poster', banner: 'poster', card: 'poster', flyer: 'poster', cover: 'poster',
|
|
41
43
|
slide: 'slides', deck: 'slides', ppt: 'slides', presentation: 'slides',
|
|
42
44
|
mail: 'email', newsletter: 'email', edm: 'email',
|
package/lib/handoff.js
CHANGED
|
@@ -199,6 +199,20 @@ export function handoffMd({ root, rows, kind, size, framework, dest, project, sy
|
|
|
199
199
|
lines.push(`- 框架:${fwLabel}`)
|
|
200
200
|
if (system) lines.push(`- 设计系统:${system}(设计用的是它的 tokens.css / components.css,项目里也照它)`)
|
|
201
201
|
lines.push(`- 生成时间:${now.toISOString().slice(0, 16).replace('T', ' ')}`, '')
|
|
202
|
+
if (kind === 'miniprogram') {
|
|
203
|
+
lines.push('## 小程序要点', '')
|
|
204
|
+
lines.push(`- 这是微信小程序的设计(${size[0]} 宽)。要做成真小程序:原生写 WXML / WXSS,或用 Taro、uni-app;框架选的「${fwLabel}」是做成网页的写法,用户要的是哪种先问清。`)
|
|
205
|
+
lines.push(`- 尺寸换成 rpx:设计稿 1px = ${Math.round((750 / size[0]) * 100) / 100}rpx(整屏宽 750rpx)。`)
|
|
206
|
+
lines.push('- 设计稿顶上画的状态栏不用做,微信自带;右上角的胶囊也是微信画的。导航栏用 app.json / 页面 json 的默认导航栏,或 `navigationStyle: custom` 自己画——自己画时状态栏高用 `wx.getWindowInfo().statusBarHeight`、导航栏高度和右边让开的宽度按 `wx.getMenuButtonBoundingClientRect()` 算(设计稿的 44 / 88 / 101px 只是预览手机的)。顶栏是深色的(预览里胶囊是白的),页面配置写 `navigationBarTextStyle: white`。')
|
|
207
|
+
lines.push('- 底部标签栏写进 app.json 的 `tabBar`(样式特殊就用自定义 tabBar),底部留出安全区 `env(safe-area-inset-bottom)`。', '')
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (kind === 'desktop') {
|
|
211
|
+
lines.push('## 桌面软件要点', '')
|
|
212
|
+
lines.push(`- 这是桌面软件的设计(窗口内容 ${size[0]}×${size[1]})。要做成真软件:Electron 或 Tauri 套网页,框架选的「${fwLabel}」写界面;用户要原生(SwiftUI / WinUI)就先问清。`)
|
|
213
|
+
lines.push('- 窗口按钮是系统画的:Mac 用无边框标题栏(Electron `titleBarStyle: \'hiddenInset\'`,Tauri `titleBarStyle: \'Overlay\'`),红绿灯盖在左上角,设计稿左上空出的 80×52 照留;顶栏可拖动的地方加 `-webkit-app-region: drag`,按钮、输入框加 `no-drag`。Windows 用自定义标题栏时右上角 138×32 留给系统按钮(Electron `titleBarOverlay`)。')
|
|
214
|
+
lines.push('- 整个窗口不滚,侧栏、列表、详情各自滚(`overflow: auto`);最小窗口尺寸照设计里最小的那种(多尺寸里的 1024×680)设。', '')
|
|
215
|
+
}
|
|
202
216
|
|
|
203
217
|
lines.push('## 屏幕和路由', '', '| 屏 | 设计文件 | 建议路由 | 截图(handoff/shots/) | 跳到 |', '| --- | --- | --- | --- | --- |')
|
|
204
218
|
for (const r of rows) {
|
package/lib/index.js
CHANGED
|
@@ -117,7 +117,7 @@ export const previewTool = {
|
|
|
117
117
|
name: 'preview_page',
|
|
118
118
|
description:
|
|
119
119
|
'在右侧栏「设计工作台」里实时打开一个页面,用户能边看边点选要改的地方。' +
|
|
120
|
-
'做网页、App
|
|
120
|
+
'做网页、App 界面、小程序、海报、幻灯片、邮件这类界面设计时用:写好第一个 HTML 文件、或启动开发服务器后调一次就行,之后改文件会自动刷新,不用再调。' +
|
|
121
121
|
'只是把回答做成一页(对比、表格、清单、图表、计算器)给用户看或用时,有 show_page 工具就用它,不用这里。' +
|
|
122
122
|
'本地 HTML 写文件路径;开发服务器写地址或端口(如 http://localhost:5173)。只能预览本机页面。' +
|
|
123
123
|
'kind 说明是哪类设计,右侧按它的画幅显示(App 套手机外框、幻灯片 16:9 …)。' +
|
|
@@ -130,7 +130,7 @@ export const previewTool = {
|
|
|
130
130
|
kind: {
|
|
131
131
|
type: 'string',
|
|
132
132
|
enum: KINDS,
|
|
133
|
-
description: '设计类型:web 网页(默认)、app 手机 App 界面(390×844)、tablet 平板、poster 海报 / 社媒图(1080×1350)、slides 幻灯片(16:9)、email 邮件',
|
|
133
|
+
description: '设计类型:web 网页(默认)、app 手机 App 界面(390×844)、miniprogram 微信小程序(375×812,外框带右上角胶囊)、tablet 平板、desktop 桌面软件(1280×800 的 Mac 窗口;Windows 软件在页面 meta 写 desktop windows)、poster 海报 / 社媒图(1080×1350)、slides 幻灯片(16:9)、email 邮件',
|
|
134
134
|
},
|
|
135
135
|
size: { type: 'string', description: '画幅尺寸,可选,如 1080x1920;不填按类型的默认尺寸' },
|
|
136
136
|
screens: {
|
|
@@ -244,7 +244,7 @@ export const checkTool = {
|
|
|
244
244
|
// 严重问题的局部截图(截不成就不附;附件库用不了的老版本 DSH 不截,清单照样给)
|
|
245
245
|
let crops = []
|
|
246
246
|
if (a.images !== false && ctx && typeof ctx.get === 'function' && ctx.get('attachments')) {
|
|
247
|
-
try { crops = await cropIssues(b, rows, { screens: pick.list, dark: !!a.dark }) } catch (e) { studio.log(e) }
|
|
247
|
+
try { crops = await cropIssues(b, rows, { screens: pick.list, dark: !!a.dark, kind }) } catch (e) { studio.log(e) }
|
|
248
248
|
}
|
|
249
249
|
const kept = crops.length ? await keepImages(ctx, id, crops) : null
|
|
250
250
|
if (kept && !kept.images) crops = [] // 存进附件库失败:不提图
|
|
@@ -368,9 +368,9 @@ export const lookTool = {
|
|
|
368
368
|
const at = ['top', 'bottom', 'full'].includes(a.at) ? a.at : 'top'
|
|
369
369
|
const pick = pickScreens(snap, a.screens, MAX_SHOTS)
|
|
370
370
|
if (!pick.list.length) return { text: `没找到要截的屏:${pick.missing.join('、')}。现在有:${(snap.screens || []).map((x) => x.key).join('、') || snap.focus}` }
|
|
371
|
-
const shots = await shootAll(b, { screens: pick.list, size: want, at, selector: a.selector, dark: !!a.dark, max: MAX_SHOTS })
|
|
371
|
+
const shots = await shootAll(b, { screens: pick.list, size: want, at, selector: a.selector, dark: !!a.dark, kind, max: MAX_SHOTS })
|
|
372
372
|
const good = shots.filter((s) => s.jpg).slice(0, MAX_SHOTS)
|
|
373
|
-
const text = formatShots(shots, { where: whereOf(snap), missing: pick.missing, more: pick.more })
|
|
373
|
+
const text = formatShots(shots, { where: whereOf(snap), missing: pick.missing, more: pick.more, kind })
|
|
374
374
|
if (!good.length) return { text }
|
|
375
375
|
const kept = await keepImages(ctx, id, good, true)
|
|
376
376
|
if (kept.images) return { text, images: kept.images }
|
package/lib/look.js
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
import fs from 'node:fs'
|
|
10
10
|
import path from 'node:path'
|
|
11
11
|
|
|
12
|
-
export const KIND_SIZE = { web: [1280, 800], app: [390, 844], tablet: [820, 1180], poster: [1080, 1350], slides: [1280, 720], email: [640, 960] }
|
|
12
|
+
export const KIND_SIZE = { web: [1280, 800], app: [390, 844], miniprogram: [375, 812], tablet: [820, 1180], desktop: [1280, 800], poster: [1080, 1350], slides: [1280, 720], email: [640, 960] }
|
|
13
13
|
const KIND_LIST = Object.keys(KIND_SIZE)
|
|
14
|
-
const META_ALIAS = { phone: 'app', mobile: 'app', ios: 'app', android: 'app', pad: 'tablet', ipad: 'tablet', social: 'poster', image: 'poster', slide: 'slides', deck: 'slides', ppt: 'slides', mail: 'email' }
|
|
14
|
+
const META_ALIAS = { phone: 'app', mobile: 'app', ios: 'app', android: 'app', mini: 'miniprogram', miniapp: 'miniprogram', weapp: 'miniprogram', wechat: 'miniprogram', 小程序: 'miniprogram', pad: 'tablet', ipad: 'tablet', pc: 'desktop', electron: 'desktop', tauri: 'desktop', mac: 'desktop', macos: 'desktop', windows: 'desktop', win: 'desktop', 桌面: 'desktop', 桌面软件: 'desktop', social: 'poster', image: 'poster', slide: 'slides', deck: 'slides', ppt: 'slides', mail: 'email' }
|
|
15
15
|
// 只给宽度时的高:常见真机的屏幕高;别的宽度按这一类默认的宽高比
|
|
16
16
|
const PHONE_H = { 320: 568, 360: 780, 375: 667, 390: 844, 393: 852, 402: 874, 412: 915, 414: 896, 428: 926, 430: 932, 440: 956 }
|
|
17
17
|
const TABLET_H = { 744: 1133, 768: 1024, 810: 1080, 820: 1180, 834: 1194, 1024: 1366, 1032: 1376 }
|
|
@@ -45,9 +45,9 @@ export function sizeOf(v, kind) {
|
|
|
45
45
|
const w = +m[1]
|
|
46
46
|
if (w < 200 || w > 4000) return null
|
|
47
47
|
const [dw, dh] = KIND_SIZE[kind] || KIND_SIZE.web
|
|
48
|
-
if (kind === 'app' && PHONE_H[w]) return [w, PHONE_H[w]]
|
|
48
|
+
if ((kind === 'app' || kind === 'miniprogram') && PHONE_H[w]) return [w, PHONE_H[w]]
|
|
49
49
|
if (kind === 'tablet' && TABLET_H[w]) return [w, TABLET_H[w]]
|
|
50
|
-
if (kind === 'app' || kind === 'tablet' || kind === 'poster' || kind === 'slides') return [w, Math.round((w * dh) / dw)]
|
|
50
|
+
if (kind === 'app' || kind === 'miniprogram' || kind === 'tablet' || kind === 'poster' || kind === 'slides') return [w, Math.round((w * dh) / dw)]
|
|
51
51
|
return [w, dh]
|
|
52
52
|
}
|
|
53
53
|
|
|
@@ -140,6 +140,7 @@ export async function measureAll(browser, { screens, sizes, kind, dark, fetcher
|
|
|
140
140
|
const { info, darkOk } = await prepare(page, dark)
|
|
141
141
|
if (info && info.title) row.title = String(info.title).slice(0, 40)
|
|
142
142
|
if (dark && !darkOk) row.noDark = true
|
|
143
|
+
if (page.pseudo) await page.pseudo().catch(() => 0) // 伪元素的真实框(量不到就按样式推)
|
|
143
144
|
const r = await page.eval(`window.__dshDesign.check(${JSON.stringify(kind)})`)
|
|
144
145
|
row.issues = Array.isArray(r && r.issues) ? r.issues : []
|
|
145
146
|
row.metrics = (r && r.metrics) || null
|
|
@@ -194,7 +195,7 @@ export async function measureAll(browser, { screens, sizes, kind, dark, fetcher
|
|
|
194
195
|
* 截图:每屏一张(at = top 顶上一屏 / bottom 滚到底 / full 从上到下几屏),selector 只截那个元素。
|
|
195
196
|
* @returns [{ key, label, w, h, at, part, of, jpg, note }]
|
|
196
197
|
*/
|
|
197
|
-
export async function shootAll(browser, { screens, size, at, selector, dark, max = MAX_SHOTS }) {
|
|
198
|
+
export async function shootAll(browser, { screens, size, at, selector, dark, kind = '', max = MAX_SHOTS }) {
|
|
198
199
|
const [w, h] = size
|
|
199
200
|
const scale = scaleFor(w, h)
|
|
200
201
|
const out = []
|
|
@@ -205,6 +206,12 @@ export async function shootAll(browser, { screens, size, at, selector, dark, max
|
|
|
205
206
|
try {
|
|
206
207
|
await browser.withPage({ url: sc.url, w, h, scale, dark }, async (page) => {
|
|
207
208
|
const { info, darkOk } = await prepare(page, dark)
|
|
209
|
+
// 小程序:每次截图前重画胶囊(滚动后顶栏可能变了色,胶囊黑白跟着重量)
|
|
210
|
+
const shoot = async (opts) => {
|
|
211
|
+
if (kind === 'miniprogram') await page.eval("window.__dshDesign.capsule('solid')").catch(() => null)
|
|
212
|
+
if (kind === 'desktop') await page.eval("window.__dshDesign.winctl('solid')").catch(() => null)
|
|
213
|
+
return page.shot(opts)
|
|
214
|
+
}
|
|
208
215
|
const title = info && info.title ? String(info.title).slice(0, 40) : ''
|
|
209
216
|
const note = { page: info ? info.page : 0, title, noDark: dark && !darkOk }
|
|
210
217
|
if (selector) {
|
|
@@ -217,12 +224,12 @@ export async function shootAll(browser, { screens, size, at, selector, dark, max
|
|
|
217
224
|
const x1 = Math.min(vx + vw, r.x + r.w + pad), y1 = Math.min(vy + vh, r.y + r.h + pad)
|
|
218
225
|
if (x1 - x0 < 1 || y1 - y0 < 1) { shots.push({ ...base, ...note, at: 'selector', error: `「${selector}」不在屏幕里` }); return }
|
|
219
226
|
const clip = { x: x0, y: y0, width: x1 - x0, height: y1 - y0 }
|
|
220
|
-
shots.push({ ...base, ...note, at: 'selector', jpg: await
|
|
227
|
+
shots.push({ ...base, ...note, at: 'selector', jpg: await shoot({ clip }) })
|
|
221
228
|
return
|
|
222
229
|
}
|
|
223
230
|
if (at === 'bottom') {
|
|
224
231
|
await page.eval("window.__dshDesign.scroll('end')")
|
|
225
|
-
shots.push({ ...base, ...note, at, jpg: await
|
|
232
|
+
shots.push({ ...base, ...note, at, jpg: await shoot() })
|
|
226
233
|
return
|
|
227
234
|
}
|
|
228
235
|
if (at === 'full') {
|
|
@@ -233,11 +240,11 @@ export async function shootAll(browser, { screens, size, at, selector, dark, max
|
|
|
233
240
|
const parts = of <= cap ? [...Array(of).keys()] : [...[...Array(cap - 1).keys()], of - 1]
|
|
234
241
|
for (const i of parts) {
|
|
235
242
|
if (i) await page.eval(`window.__dshDesign.scroll(${i})`)
|
|
236
|
-
shots.push({ ...base, ...note, at, part: i + 1, of, jpg: await
|
|
243
|
+
shots.push({ ...base, ...note, at, part: i + 1, of, jpg: await shoot() })
|
|
237
244
|
}
|
|
238
245
|
return
|
|
239
246
|
}
|
|
240
|
-
shots.push({ ...base, ...note, at: 'top', jpg: await
|
|
247
|
+
shots.push({ ...base, ...note, at: 'top', jpg: await shoot() })
|
|
241
248
|
})
|
|
242
249
|
} catch (e) {
|
|
243
250
|
shots.push({ ...base, at, error: String((e && e.message) || e).slice(0, 160) })
|
|
@@ -248,7 +255,7 @@ export async function shootAll(browser, { screens, size, at, selector, dark, max
|
|
|
248
255
|
}
|
|
249
256
|
|
|
250
257
|
// 值得附图的问题(严重的、看图比看数字明白),越靠前越先截
|
|
251
|
-
const CROP_ORDER = ['covered', 'overlap', 'clipped', 'overflow', 'hscroll', 'img', 'contrast']
|
|
258
|
+
const CROP_ORDER = ['covered', 'capsule', 'island', 'winctl', 'homebar', 'overlap', 'clipped', 'overflow', 'hscroll', 'img', 'contrast']
|
|
252
259
|
|
|
253
260
|
/** 两条问题挨得近(同一屏同一尺寸,元素上下差不到 48px、左右差不到 160px):截一张图就都看得到 */
|
|
254
261
|
function nearby(a, b) {
|
|
@@ -305,6 +312,10 @@ export function pickCrops(rows, max = MAX_CROPS) {
|
|
|
305
312
|
function labelOf(it) {
|
|
306
313
|
switch (it.type) {
|
|
307
314
|
case 'covered': return `压住 ${it.px}px`
|
|
315
|
+
case 'capsule': return `挡住 ${it.px}px`
|
|
316
|
+
case 'island': return `挡住 ${it.px}px`
|
|
317
|
+
case 'winctl': return `挡住 ${it.px}px`
|
|
318
|
+
case 'homebar': return `伸进 ${it.px}px`
|
|
308
319
|
case 'overlap': return it.px ? `叠了 ${it.px}px` : ''
|
|
309
320
|
case 'clipped': return `切掉 ${it.px}px`
|
|
310
321
|
case 'overflow': return `伸出 ${it.px}px`
|
|
@@ -319,7 +330,7 @@ function labelOf(it) {
|
|
|
319
330
|
* 截不成的(元素没了、页面变了)就不附。
|
|
320
331
|
* @returns [{ n, k, ks, key, w, h, type, jpg }](n 从 1 起,按挑的顺序;ks 是图里看得到的几条问题)
|
|
321
332
|
*/
|
|
322
|
-
export async function cropIssues(browser, rows, { screens, dark, max = MAX_CROPS }) {
|
|
333
|
+
export async function cropIssues(browser, rows, { screens, dark, kind = '', max = MAX_CROPS }) {
|
|
323
334
|
const picks = pickCrops(rows, max)
|
|
324
335
|
const urlOf = new Map(screens.map((s) => [s.key, s.url]))
|
|
325
336
|
const groups = new Map()
|
|
@@ -339,6 +350,9 @@ export async function cropIssues(browser, rows, { screens, dark, max = MAX_CROPS
|
|
|
339
350
|
const how = { sel: it.sel, other: it.type === 'overlap' ? it.otherSel || '' : '', edge: it.type === 'covered' ? it.edge || '' : '', hscroll: it.type === 'hscroll', also: c.also.map((x) => x.it.sel), label: labelOf(it) }
|
|
340
351
|
const at = await page.eval(`window.__dshDesign.crop ? window.__dshDesign.crop(${JSON.stringify(how)}) : null`).catch(() => null)
|
|
341
352
|
if (!at || !(at.w >= 1 && at.h >= 1)) continue
|
|
353
|
+
// 小程序:滚到问题那儿以后再画胶囊(半透明,透出被挡住的东西;黑白按这时的顶栏)
|
|
354
|
+
if (kind === 'miniprogram') await page.eval("window.__dshDesign.capsule('ghost')").catch(() => null)
|
|
355
|
+
if (kind === 'desktop') await page.eval("window.__dshDesign.winctl('ghost')").catch(() => null)
|
|
342
356
|
c.jpg = await page.shot({ clip: { x: at.x, y: at.y, width: at.w, height: at.h } })
|
|
343
357
|
// 挨着的那几条:真在图里才算「看图几」
|
|
344
358
|
c.ks = [c.k, ...c.also.filter((_, i) => at.inside && at.inside[i]).map((x) => x.k)]
|
|
@@ -356,6 +370,8 @@ const TYPE = {
|
|
|
356
370
|
hscroll: '超出屏幕', clipped: '文字被截断', overflow: '文字超出框', overlap: '文字叠在一起', contrast: '对比度不够', tap: '点击区太小',
|
|
357
371
|
tiny: '字太小', img: '图片没加载出来', fonts: '字号太杂', distort: '图片被拉变形', long: '一行太长', wrap: '文字被挤断行',
|
|
358
372
|
covered: '被固定栏压住', tight: '离固定栏太近', jserr: '页面脚本报错', missing: '文件没载出来', deadlink: '跳到的页面不存在',
|
|
373
|
+
capsule: '被小程序胶囊挡住', island: '被灵动岛挡住', homebar: '伸进底部横条区', tabbar: '标签栏项太多', navbar: '没有导航栏',
|
|
374
|
+
winctl: '被窗口按钮挡住',
|
|
359
375
|
}
|
|
360
376
|
const EDGE = { top: '顶栏', bottom: '底栏' }
|
|
361
377
|
const WHAT = { css: '样式表', script: '脚本', font: '字体', link: '文件', video: '视频', audio: '音频', source: '媒体', iframe: '内嵌页' }
|
|
@@ -397,6 +413,14 @@ export function detailOf(it) {
|
|
|
397
413
|
case 'wrap': return `折成了 ${it.lines} 行(给它更多宽度、white-space: nowrap 或缩短文字)`
|
|
398
414
|
case 'covered': return `${it.edge === 'top' ? '没滚时' : '滚到底还'}被${EDGE[it.edge] || ''} ${it.bar} 压住 ${it.px}px(给内容留出栏的高度:${it.edge === 'top' ? '顶上' : '底部'}至少再留 ${it.px + 16}px,用 ${it.edge === 'top' ? 'padding-top' : 'padding-bottom'},栏是 fixed 时也可以用 scroll-padding)`
|
|
399
415
|
case 'tight': return `离${EDGE[it.edge] || ''} ${it.bar} 只有 ${it.px}px(留 16px 以上)`
|
|
416
|
+
case 'capsule': return `伸进右上角胶囊 ${it.px}px(胶囊是微信画的、页面盖不住,在 y 48–80、x ${it.x} 以右:顶栏右边空出这块,标题居中或靠左)`
|
|
417
|
+
case 'island': return `被灵动岛挡住 ${it.px}px(灵动岛在屏幕顶上正中、y 11–45:状态栏中间空着,标题、按钮放到状态栏下面)`
|
|
418
|
+
case 'homebar': return `底栏 ${it.bar} 里的字或图标伸进底部横条区 ${it.px}px(屏幕最下面 34px 留给横条:栏的底色铺下去,内容抬到它上面,设计稿里栏写 padding-bottom: 34px——预览里 env(safe-area-inset-bottom) 是 0,靠它让不出来,做成真代码时再换成 env())`
|
|
419
|
+
case 'tabbar': return `标签栏有 ${it.n} 项(最多 5 项;小程序 tabBar 超过 5 项微信直接报错):留最常用的几项,其余放进「我的」或首页入口`
|
|
420
|
+
case 'navbar': return `顶上 y ${it.t}–${it.b}(导航栏那条)是空的:小程序每屏都有导航栏——一级页放标题或品牌 / 搜索(让开右上角胶囊),二级页标题居中、左边加返回;启动页、全屏大图可以不管`
|
|
421
|
+
case 'winctl': return it.os === 'win'
|
|
422
|
+
? `伸进右上角的最小化 / 最大化 / 关闭按钮 ${it.px}px(外框画的、盖在页面上面,在最右边 138px、y 0–32:标题栏右边空出这块)`
|
|
423
|
+
: `伸进左上角红绿灯 ${it.px}px(外框画的、盖在页面上面,在 x 20–72、y 20–32:工具栏 / 侧栏顶上左边空出 80px,标题、按钮从 x 80 以后放)`
|
|
400
424
|
case 'jserr': return `${it.msg}${it.src ? `(${it.src}${it.line ? `:${it.line}` : ''})` : ''}`
|
|
401
425
|
case 'missing': return `${WHAT[it.what] || '文件'} ${it.src} 载不出来(路径写错了,或者文件不在)`
|
|
402
426
|
default: return ''
|
|
@@ -476,7 +500,7 @@ export function formatMeasure(rows, { sizes, where, missing = [], more = 0, dark
|
|
|
476
500
|
: '没发现问题:下面每屏每种尺寸都量过、都通过了(要滚动的屏看「滚到底时离内容」,是正数就没被压住)。'
|
|
477
501
|
const lines = [`用无头浏览器按真实尺寸量了 ${keys.length} 屏 × ${sizes.length} 种尺寸(${sz}${dark ? ',深色' : ''})${where ? `,设计目录 ${where}` : ''}。${status}`]
|
|
478
502
|
if (compared) lines.push(`比上次(同样的屏和尺寸):修好 ${fixed} 个,新出 ${fresh.size} 个${fresh.size ? '(清单里标了「新」)' : ''}。`)
|
|
479
|
-
if (crops.length) lines.push(`严重的问题附了 ${crops.length} 张局部截图(图 1${crops.length > 1 ? `–${crops.length}` : ''},真实渲染,按发现时的尺寸和滚动位置截的;红框是出问题的元素,框上标了差多少;叠在一起的另一个是橙色虚线框${crops.some((c) => c.type === 'covered') ? ';被栏压住的那几张把栏调成了半透明,透出下面被压住的字' : ''};要看整屏用 look_design)。`)
|
|
503
|
+
if (crops.length) lines.push(`严重的问题附了 ${crops.length} 张局部截图(图 1${crops.length > 1 ? `–${crops.length}` : ''},真实渲染,按发现时的尺寸和滚动位置截的;红框是出问题的元素,框上标了差多少;叠在一起的另一个是橙色虚线框${crops.some((c) => c.type === 'covered') ? ';被栏压住的那几张把栏调成了半透明,透出下面被压住的字' : ''}${crops.some((c) => c.type === 'capsule') ? ';小程序右上角的胶囊画成了半透明,透出下面被挡住的东西' : ''};要看整屏用 look_design)。`)
|
|
480
504
|
if (missing.length) lines.push(`没找到这几屏:${missing.join('、')}`)
|
|
481
505
|
if (more) lines.push(`还有 ${more} 屏这次没量(一次最多 ${MAX_SCREENS} 屏,用 screens 挑着量)`)
|
|
482
506
|
for (const key of keys) {
|
|
@@ -524,7 +548,7 @@ export function formatMeasure(rows, { sizes, where, missing = [], more = 0, dark
|
|
|
524
548
|
}
|
|
525
549
|
|
|
526
550
|
/** 截图的说明:第几张是哪屏、什么尺寸、哪一段 */
|
|
527
|
-
export function formatShots(shots, { where, missing = [], more = 0 }) {
|
|
551
|
+
export function formatShots(shots, { where, missing = [], more = 0, kind = '' }) {
|
|
528
552
|
const AT = { top: '顶上一屏', bottom: '滚到底', selector: '元素' }
|
|
529
553
|
const lines = [`无头浏览器按真实尺寸截的图(真实渲染的像素)${where ? `,设计目录 ${where}` : ''}:`]
|
|
530
554
|
let n = 0
|
|
@@ -538,6 +562,8 @@ export function formatShots(shots, { where, missing = [], more = 0 }) {
|
|
|
538
562
|
const skipped = mine[0].of - mine.length
|
|
539
563
|
if (skipped > 0) lines.push(`${key} 一共 ${mine[0].of} 段,中间 ${skipped} 段没截(要看就单截这一屏,或用 selector 截那一块)`)
|
|
540
564
|
}
|
|
565
|
+
if (kind === 'miniprogram' && n) lines.push('右上角画上了微信的胶囊(真机上它盖在页面上面,页面里别再画)。')
|
|
566
|
+
if (kind === 'desktop' && n) lines.push('画上了窗口按钮(Mac 左上角红绿灯 / Windows 右上角三个按钮,真窗口里系统画的,页面里别再画)。')
|
|
541
567
|
if (missing.length) lines.push(`没找到这几屏:${missing.join('、')}`)
|
|
542
568
|
if (more) lines.push(`还有 ${more} 屏没截(一次最多 ${MAX_SHOTS} 张,用 screens 挑着截)`)
|
|
543
569
|
return lines.join('\n')
|