dsh-design-studio 0.7.0 → 0.8.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 +20 -104
- package/README.md +20 -184
- package/cordis.patch.yml +2 -2
- package/lib/client.js +169 -37
- package/lib/core.js +4 -3
- package/lib/handoff.js +7 -0
- package/lib/index.js +5 -5
- package/lib/look.js +24 -13
- package/lib/picker.js +576 -33
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +2 -2
- package/skills/ui-design/SKILL.md +13 -6
- package/skills/ui-design/references/miniprogram.md +27 -0
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], 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', 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
|
|
|
@@ -194,7 +194,7 @@ export async function measureAll(browser, { screens, sizes, kind, dark, fetcher
|
|
|
194
194
|
* 截图:每屏一张(at = top 顶上一屏 / bottom 滚到底 / full 从上到下几屏),selector 只截那个元素。
|
|
195
195
|
* @returns [{ key, label, w, h, at, part, of, jpg, note }]
|
|
196
196
|
*/
|
|
197
|
-
export async function shootAll(browser, { screens, size, at, selector, dark, max = MAX_SHOTS }) {
|
|
197
|
+
export async function shootAll(browser, { screens, size, at, selector, dark, kind = '', max = MAX_SHOTS }) {
|
|
198
198
|
const [w, h] = size
|
|
199
199
|
const scale = scaleFor(w, h)
|
|
200
200
|
const out = []
|
|
@@ -205,6 +205,11 @@ export async function shootAll(browser, { screens, size, at, selector, dark, max
|
|
|
205
205
|
try {
|
|
206
206
|
await browser.withPage({ url: sc.url, w, h, scale, dark }, async (page) => {
|
|
207
207
|
const { info, darkOk } = await prepare(page, dark)
|
|
208
|
+
// 小程序:每次截图前重画胶囊(滚动后顶栏可能变了色,胶囊黑白跟着重量)
|
|
209
|
+
const shoot = async (opts) => {
|
|
210
|
+
if (kind === 'miniprogram') await page.eval("window.__dshDesign.capsule('solid')").catch(() => null)
|
|
211
|
+
return page.shot(opts)
|
|
212
|
+
}
|
|
208
213
|
const title = info && info.title ? String(info.title).slice(0, 40) : ''
|
|
209
214
|
const note = { page: info ? info.page : 0, title, noDark: dark && !darkOk }
|
|
210
215
|
if (selector) {
|
|
@@ -217,12 +222,12 @@ export async function shootAll(browser, { screens, size, at, selector, dark, max
|
|
|
217
222
|
const x1 = Math.min(vx + vw, r.x + r.w + pad), y1 = Math.min(vy + vh, r.y + r.h + pad)
|
|
218
223
|
if (x1 - x0 < 1 || y1 - y0 < 1) { shots.push({ ...base, ...note, at: 'selector', error: `「${selector}」不在屏幕里` }); return }
|
|
219
224
|
const clip = { x: x0, y: y0, width: x1 - x0, height: y1 - y0 }
|
|
220
|
-
shots.push({ ...base, ...note, at: 'selector', jpg: await
|
|
225
|
+
shots.push({ ...base, ...note, at: 'selector', jpg: await shoot({ clip }) })
|
|
221
226
|
return
|
|
222
227
|
}
|
|
223
228
|
if (at === 'bottom') {
|
|
224
229
|
await page.eval("window.__dshDesign.scroll('end')")
|
|
225
|
-
shots.push({ ...base, ...note, at, jpg: await
|
|
230
|
+
shots.push({ ...base, ...note, at, jpg: await shoot() })
|
|
226
231
|
return
|
|
227
232
|
}
|
|
228
233
|
if (at === 'full') {
|
|
@@ -233,11 +238,11 @@ export async function shootAll(browser, { screens, size, at, selector, dark, max
|
|
|
233
238
|
const parts = of <= cap ? [...Array(of).keys()] : [...[...Array(cap - 1).keys()], of - 1]
|
|
234
239
|
for (const i of parts) {
|
|
235
240
|
if (i) await page.eval(`window.__dshDesign.scroll(${i})`)
|
|
236
|
-
shots.push({ ...base, ...note, at, part: i + 1, of, jpg: await
|
|
241
|
+
shots.push({ ...base, ...note, at, part: i + 1, of, jpg: await shoot() })
|
|
237
242
|
}
|
|
238
243
|
return
|
|
239
244
|
}
|
|
240
|
-
shots.push({ ...base, ...note, at: 'top', jpg: await
|
|
245
|
+
shots.push({ ...base, ...note, at: 'top', jpg: await shoot() })
|
|
241
246
|
})
|
|
242
247
|
} catch (e) {
|
|
243
248
|
shots.push({ ...base, at, error: String((e && e.message) || e).slice(0, 160) })
|
|
@@ -248,7 +253,7 @@ export async function shootAll(browser, { screens, size, at, selector, dark, max
|
|
|
248
253
|
}
|
|
249
254
|
|
|
250
255
|
// 值得附图的问题(严重的、看图比看数字明白),越靠前越先截
|
|
251
|
-
const CROP_ORDER = ['covered', 'overlap', 'clipped', 'overflow', 'hscroll', 'img', 'contrast']
|
|
256
|
+
const CROP_ORDER = ['covered', 'capsule', 'overlap', 'clipped', 'overflow', 'hscroll', 'img', 'contrast']
|
|
252
257
|
|
|
253
258
|
/** 两条问题挨得近(同一屏同一尺寸,元素上下差不到 48px、左右差不到 160px):截一张图就都看得到 */
|
|
254
259
|
function nearby(a, b) {
|
|
@@ -305,6 +310,7 @@ export function pickCrops(rows, max = MAX_CROPS) {
|
|
|
305
310
|
function labelOf(it) {
|
|
306
311
|
switch (it.type) {
|
|
307
312
|
case 'covered': return `压住 ${it.px}px`
|
|
313
|
+
case 'capsule': return `挡住 ${it.px}px`
|
|
308
314
|
case 'overlap': return it.px ? `叠了 ${it.px}px` : ''
|
|
309
315
|
case 'clipped': return `切掉 ${it.px}px`
|
|
310
316
|
case 'overflow': return `伸出 ${it.px}px`
|
|
@@ -319,7 +325,7 @@ function labelOf(it) {
|
|
|
319
325
|
* 截不成的(元素没了、页面变了)就不附。
|
|
320
326
|
* @returns [{ n, k, ks, key, w, h, type, jpg }](n 从 1 起,按挑的顺序;ks 是图里看得到的几条问题)
|
|
321
327
|
*/
|
|
322
|
-
export async function cropIssues(browser, rows, { screens, dark, max = MAX_CROPS }) {
|
|
328
|
+
export async function cropIssues(browser, rows, { screens, dark, kind = '', max = MAX_CROPS }) {
|
|
323
329
|
const picks = pickCrops(rows, max)
|
|
324
330
|
const urlOf = new Map(screens.map((s) => [s.key, s.url]))
|
|
325
331
|
const groups = new Map()
|
|
@@ -339,6 +345,8 @@ export async function cropIssues(browser, rows, { screens, dark, max = MAX_CROPS
|
|
|
339
345
|
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
346
|
const at = await page.eval(`window.__dshDesign.crop ? window.__dshDesign.crop(${JSON.stringify(how)}) : null`).catch(() => null)
|
|
341
347
|
if (!at || !(at.w >= 1 && at.h >= 1)) continue
|
|
348
|
+
// 小程序:滚到问题那儿以后再画胶囊(半透明,透出被挡住的东西;黑白按这时的顶栏)
|
|
349
|
+
if (kind === 'miniprogram') await page.eval("window.__dshDesign.capsule('ghost')").catch(() => null)
|
|
342
350
|
c.jpg = await page.shot({ clip: { x: at.x, y: at.y, width: at.w, height: at.h } })
|
|
343
351
|
// 挨着的那几条:真在图里才算「看图几」
|
|
344
352
|
c.ks = [c.k, ...c.also.filter((_, i) => at.inside && at.inside[i]).map((x) => x.k)]
|
|
@@ -356,6 +364,7 @@ const TYPE = {
|
|
|
356
364
|
hscroll: '超出屏幕', clipped: '文字被截断', overflow: '文字超出框', overlap: '文字叠在一起', contrast: '对比度不够', tap: '点击区太小',
|
|
357
365
|
tiny: '字太小', img: '图片没加载出来', fonts: '字号太杂', distort: '图片被拉变形', long: '一行太长', wrap: '文字被挤断行',
|
|
358
366
|
covered: '被固定栏压住', tight: '离固定栏太近', jserr: '页面脚本报错', missing: '文件没载出来', deadlink: '跳到的页面不存在',
|
|
367
|
+
capsule: '被小程序胶囊挡住',
|
|
359
368
|
}
|
|
360
369
|
const EDGE = { top: '顶栏', bottom: '底栏' }
|
|
361
370
|
const WHAT = { css: '样式表', script: '脚本', font: '字体', link: '文件', video: '视频', audio: '音频', source: '媒体', iframe: '内嵌页' }
|
|
@@ -397,6 +406,7 @@ export function detailOf(it) {
|
|
|
397
406
|
case 'wrap': return `折成了 ${it.lines} 行(给它更多宽度、white-space: nowrap 或缩短文字)`
|
|
398
407
|
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
408
|
case 'tight': return `离${EDGE[it.edge] || ''} ${it.bar} 只有 ${it.px}px(留 16px 以上)`
|
|
409
|
+
case 'capsule': return `伸进右上角胶囊 ${it.px}px(胶囊是微信画的、页面盖不住,在 y 48–80、x ${it.x} 以右:顶栏右边空出这块,标题居中或靠左)`
|
|
400
410
|
case 'jserr': return `${it.msg}${it.src ? `(${it.src}${it.line ? `:${it.line}` : ''})` : ''}`
|
|
401
411
|
case 'missing': return `${WHAT[it.what] || '文件'} ${it.src} 载不出来(路径写错了,或者文件不在)`
|
|
402
412
|
default: return ''
|
|
@@ -476,7 +486,7 @@ export function formatMeasure(rows, { sizes, where, missing = [], more = 0, dark
|
|
|
476
486
|
: '没发现问题:下面每屏每种尺寸都量过、都通过了(要滚动的屏看「滚到底时离内容」,是正数就没被压住)。'
|
|
477
487
|
const lines = [`用无头浏览器按真实尺寸量了 ${keys.length} 屏 × ${sizes.length} 种尺寸(${sz}${dark ? ',深色' : ''})${where ? `,设计目录 ${where}` : ''}。${status}`]
|
|
478
488
|
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)。`)
|
|
489
|
+
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
490
|
if (missing.length) lines.push(`没找到这几屏:${missing.join('、')}`)
|
|
481
491
|
if (more) lines.push(`还有 ${more} 屏这次没量(一次最多 ${MAX_SCREENS} 屏,用 screens 挑着量)`)
|
|
482
492
|
for (const key of keys) {
|
|
@@ -524,7 +534,7 @@ export function formatMeasure(rows, { sizes, where, missing = [], more = 0, dark
|
|
|
524
534
|
}
|
|
525
535
|
|
|
526
536
|
/** 截图的说明:第几张是哪屏、什么尺寸、哪一段 */
|
|
527
|
-
export function formatShots(shots, { where, missing = [], more = 0 }) {
|
|
537
|
+
export function formatShots(shots, { where, missing = [], more = 0, kind = '' }) {
|
|
528
538
|
const AT = { top: '顶上一屏', bottom: '滚到底', selector: '元素' }
|
|
529
539
|
const lines = [`无头浏览器按真实尺寸截的图(真实渲染的像素)${where ? `,设计目录 ${where}` : ''}:`]
|
|
530
540
|
let n = 0
|
|
@@ -538,6 +548,7 @@ export function formatShots(shots, { where, missing = [], more = 0 }) {
|
|
|
538
548
|
const skipped = mine[0].of - mine.length
|
|
539
549
|
if (skipped > 0) lines.push(`${key} 一共 ${mine[0].of} 段,中间 ${skipped} 段没截(要看就单截这一屏,或用 selector 截那一块)`)
|
|
540
550
|
}
|
|
551
|
+
if (kind === 'miniprogram' && n) lines.push('右上角画上了微信的胶囊(真机上它盖在页面上面,页面里别再画)。')
|
|
541
552
|
if (missing.length) lines.push(`没找到这几屏:${missing.join('、')}`)
|
|
542
553
|
if (more) lines.push(`还有 ${more} 屏没截(一次最多 ${MAX_SHOTS} 张,用 screens 挑着截)`)
|
|
543
554
|
return lines.join('\n')
|