Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8023e2e472 | ||
|
|
918ca371be |
No files matched your search
@@ -85,6 +85,8 @@ export const permissionsSchemas = {
|
||||
{ label: '每日签到', value: 'signin' },
|
||||
{ label: '前台投稿', value: 'front_submit' },
|
||||
{ label: '打赏用户', value: 'tip_user' },
|
||||
{ label: '申请售后', value: 'after_sale_apply' },
|
||||
{ label: '撤销售后', value: 'after_sale_cancel' },
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
+89
-10
@@ -6496,7 +6496,8 @@ body.aksrt-modal-runtime-open {
|
||||
底色走 --main-radius / --main-bg-color;移动端由 --bottom 底部抽屉接管。 */
|
||||
.aksrt-after-sale-apply-modal:not(.aksrt-modal-runtime--bottom) .aksrt-modal-runtime__dialog,
|
||||
.aksrt-after-sale-detail-modal:not(.aksrt-modal-runtime--bottom) .aksrt-modal-runtime__dialog,
|
||||
.aksrt-after-sale-expired-modal:not(.aksrt-modal-runtime--bottom) .aksrt-modal-runtime__dialog {
|
||||
.aksrt-after-sale-expired-modal:not(.aksrt-modal-runtime--bottom) .aksrt-modal-runtime__dialog,
|
||||
.aksrt-after-sale-withdraw-modal:not(.aksrt-modal-runtime--bottom) .aksrt-modal-runtime__dialog {
|
||||
width: min(400px, calc(100vw - 32px));
|
||||
border-radius: var(--main-radius, 8px);
|
||||
background: var(--main-bg-color, #fff);
|
||||
@@ -6504,7 +6505,8 @@ body.aksrt-modal-runtime-open {
|
||||
|
||||
.aksrt-after-sale-apply-modal .aksrt-modal-runtime__skeleton,
|
||||
.aksrt-after-sale-detail-modal .aksrt-modal-runtime__skeleton,
|
||||
.aksrt-after-sale-expired-modal .aksrt-modal-runtime__skeleton {
|
||||
.aksrt-after-sale-expired-modal .aksrt-modal-runtime__skeleton,
|
||||
.aksrt-after-sale-withdraw-modal .aksrt-modal-runtime__skeleton {
|
||||
padding: 24px 18px;
|
||||
}
|
||||
|
||||
@@ -6513,9 +6515,13 @@ body.aksrt-modal-runtime-open {
|
||||
.aksrt-after-sale-apply-modal .modal-colorful-header,
|
||||
.aksrt-after-sale-detail-modal .modal-colorful-header,
|
||||
.aksrt-after-sale-expired-modal .modal-colorful-header,
|
||||
.aksrt-after-sale-withdraw-modal .modal-colorful-header,
|
||||
.aksrt-order-detail-modal .modal-colorful-header,
|
||||
.aksrt-after-sale-apply-modal .colorful-bg,
|
||||
.aksrt-after-sale-detail-modal .colorful-bg,
|
||||
.aksrt-after-sale-expired-modal .colorful-bg {
|
||||
.aksrt-after-sale-expired-modal .colorful-bg,
|
||||
.aksrt-after-sale-withdraw-modal .colorful-bg,
|
||||
.aksrt-order-detail-modal .colorful-bg {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
color: var(--this-color);
|
||||
@@ -6525,7 +6531,9 @@ body.aksrt-modal-runtime-open {
|
||||
|
||||
.aksrt-after-sale-apply-modal .modal-colorful-header,
|
||||
.aksrt-after-sale-detail-modal .modal-colorful-header,
|
||||
.aksrt-after-sale-expired-modal .modal-colorful-header {
|
||||
.aksrt-after-sale-expired-modal .modal-colorful-header,
|
||||
.aksrt-after-sale-withdraw-modal .modal-colorful-header,
|
||||
.aksrt-order-detail-modal .modal-colorful-header {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
@@ -6539,13 +6547,17 @@ body.aksrt-modal-runtime-open {
|
||||
|
||||
.aksrt-after-sale-apply-modal .modal-colorful-header + *,
|
||||
.aksrt-after-sale-detail-modal .modal-colorful-header + *,
|
||||
.aksrt-after-sale-expired-modal .modal-colorful-header + * {
|
||||
.aksrt-after-sale-expired-modal .modal-colorful-header + *,
|
||||
.aksrt-after-sale-withdraw-modal .modal-colorful-header + *,
|
||||
.aksrt-order-detail-modal .modal-colorful-header + * {
|
||||
margin-top: 120px;
|
||||
}
|
||||
|
||||
.aksrt-after-sale-apply-modal .modal-colorful-header > .close,
|
||||
.aksrt-after-sale-detail-modal .modal-colorful-header > .close,
|
||||
.aksrt-after-sale-expired-modal .modal-colorful-header > .close {
|
||||
.aksrt-after-sale-expired-modal .modal-colorful-header > .close,
|
||||
.aksrt-after-sale-withdraw-modal .modal-colorful-header > .close,
|
||||
.aksrt-order-detail-modal .modal-colorful-header > .close {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
@@ -6562,8 +6574,14 @@ body.aksrt-modal-runtime-open {
|
||||
.aksrt-after-sale-detail-modal .colorful-bg > .colorful-make::before,
|
||||
.aksrt-after-sale-detail-modal .colorful-bg > .colorful-make::after,
|
||||
.aksrt-after-sale-expired-modal .colorful-bg > .colorful-make,
|
||||
.aksrt-after-sale-withdraw-modal .colorful-bg > .colorful-make,
|
||||
.aksrt-order-detail-modal .colorful-bg > .colorful-make,
|
||||
.aksrt-after-sale-expired-modal .colorful-bg > .colorful-make::before,
|
||||
.aksrt-after-sale-expired-modal .colorful-bg > .colorful-make::after {
|
||||
.aksrt-after-sale-withdraw-modal .colorful-bg > .colorful-make::before,
|
||||
.aksrt-order-detail-modal .colorful-bg > .colorful-make::before,
|
||||
.aksrt-after-sale-expired-modal .colorful-bg > .colorful-make::after,
|
||||
.aksrt-after-sale-withdraw-modal .colorful-bg > .colorful-make::after,
|
||||
.aksrt-order-detail-modal .colorful-bg > .colorful-make::after {
|
||||
position: absolute;
|
||||
color: var(--this-color);
|
||||
background: var(--this-bg);
|
||||
@@ -6573,7 +6591,9 @@ body.aksrt-modal-runtime-open {
|
||||
|
||||
.aksrt-after-sale-apply-modal .colorful-bg > .colorful-make,
|
||||
.aksrt-after-sale-detail-modal .colorful-bg > .colorful-make,
|
||||
.aksrt-after-sale-expired-modal .colorful-bg > .colorful-make {
|
||||
.aksrt-after-sale-expired-modal .colorful-bg > .colorful-make,
|
||||
.aksrt-after-sale-withdraw-modal .colorful-bg > .colorful-make,
|
||||
.aksrt-order-detail-modal .colorful-bg > .colorful-make {
|
||||
top: 60%;
|
||||
bottom: 0;
|
||||
width: 323px;
|
||||
@@ -6583,7 +6603,9 @@ body.aksrt-modal-runtime-open {
|
||||
|
||||
.aksrt-after-sale-apply-modal .colorful-bg > .colorful-make::before,
|
||||
.aksrt-after-sale-detail-modal .colorful-bg > .colorful-make::before,
|
||||
.aksrt-after-sale-expired-modal .colorful-bg > .colorful-make::before {
|
||||
.aksrt-after-sale-expired-modal .colorful-bg > .colorful-make::before,
|
||||
.aksrt-after-sale-withdraw-modal .colorful-bg > .colorful-make::before,
|
||||
.aksrt-order-detail-modal .colorful-bg > .colorful-make::before {
|
||||
top: -325px;
|
||||
right: -558px;
|
||||
bottom: 0;
|
||||
@@ -6595,7 +6617,9 @@ body.aksrt-modal-runtime-open {
|
||||
|
||||
.aksrt-after-sale-apply-modal .colorful-bg > .colorful-make::after,
|
||||
.aksrt-after-sale-detail-modal .colorful-bg > .colorful-make::after,
|
||||
.aksrt-after-sale-expired-modal .colorful-bg > .colorful-make::after {
|
||||
.aksrt-after-sale-expired-modal .colorful-bg > .colorful-make::after,
|
||||
.aksrt-after-sale-withdraw-modal .colorful-bg > .colorful-make::after,
|
||||
.aksrt-order-detail-modal .colorful-bg > .colorful-make::after {
|
||||
top: -170px;
|
||||
bottom: 0;
|
||||
left: -262px;
|
||||
@@ -6669,6 +6693,61 @@ body.aksrt-modal-runtime-open {
|
||||
.aksrt-after-sale-status.is-rejected,
|
||||
.aksrt-after-sale-status.is-cancelled { color: var(--color-error, #ef4444); }
|
||||
|
||||
/* 状态行右侧单元格:状态徽章 + 「撤销售后」入口(仅待审核工单渲染)。 */
|
||||
.aksrt-after-sale-detail__statusCell { display: inline-flex; align-items: center; gap: 10px; }
|
||||
.aksrt-after-sale-withdraw { display: inline-flex; align-items: center; gap: 3px; padding: 0; border: 0; background: transparent; color: var(--modal-danger, #ef4444); font-size: 12px; font-weight: 600; line-height: 1.4; cursor: pointer; transition: color .15s ease; }
|
||||
.aksrt-after-sale-withdraw .fa { font-size: 12px; }
|
||||
.aksrt-after-sale-withdraw:hover { color: color-mix(in srgb, var(--modal-danger) 78%, var(--modal-ink)); }
|
||||
|
||||
/* 售后提示弹窗(撤销售后确认)正文。 */
|
||||
.aksrt-after-sale-withdraw__body { margin-bottom: 16px; color: var(--color-ink-2, #4e5358); line-height: 1.7; }
|
||||
|
||||
/* 售后弹窗底部按钮:全宽平铺贴底(负 margin 抵消 __content 内边距)+ 分隔线,
|
||||
子按钮去独立背景/边框/圆角(文字色区分主次),覆盖上方全局按钮网格的胶囊观感;
|
||||
仅在售后弹窗家族生效,其他 runtime 弹窗仍走全局网格样式。 */
|
||||
.aksrt-after-sale-apply-modal .but-average.modal-buts,
|
||||
.aksrt-after-sale-detail-modal .but-average.modal-buts,
|
||||
.aksrt-after-sale-expired-modal .but-average.modal-buts,
|
||||
.aksrt-after-sale-withdraw-modal .but-average.modal-buts {
|
||||
gap: 0;
|
||||
margin: 16px -18px -16px;
|
||||
padding: 12px 18px;
|
||||
border-top: 1px solid var(--modal-line-soft, rgba(50, 50, 50, 0.06));
|
||||
background: var(--main-bg-color, #fff);
|
||||
}
|
||||
|
||||
.aksrt-after-sale-apply-modal .but-average.modal-buts > .but,
|
||||
.aksrt-after-sale-detail-modal .but-average.modal-buts > .but,
|
||||
.aksrt-after-sale-expired-modal .but-average.modal-buts > .but,
|
||||
.aksrt-after-sale-withdraw-modal .but-average.modal-buts > .but {
|
||||
min-height: 38px;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
background: transparent;
|
||||
color: var(--modal-muted, #777);
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
--this-bg: transparent;
|
||||
--this-border: transparent;
|
||||
--this-shadow: none;
|
||||
}
|
||||
|
||||
.aksrt-after-sale-apply-modal .but-average.modal-buts > .but.c-blue,
|
||||
.aksrt-after-sale-detail-modal .but-average.modal-buts > .but.c-blue,
|
||||
.aksrt-after-sale-expired-modal .but-average.modal-buts > .but.c-blue,
|
||||
.aksrt-after-sale-withdraw-modal .but-average.modal-buts > .but.c-blue {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: var(--modal-primary, #f04494);
|
||||
}
|
||||
|
||||
/* 单按钮 footer 占满整行(全局网格是 1fr 1fr,单按钮会只占一半)。 */
|
||||
.aksrt-after-sale-apply-modal .but-average.modal-buts > .but:only-child,
|
||||
.aksrt-after-sale-detail-modal .but-average.modal-buts > .but:only-child,
|
||||
.aksrt-after-sale-expired-modal .but-average.modal-buts > .but:only-child,
|
||||
.aksrt-after-sale-withdraw-modal .but-average.modal-buts > .but:only-child { grid-column: 1 / -1; }
|
||||
|
||||
/* source: assets/css/frontend/notyf.css */
|
||||
/**
|
||||
* legacy-compatible frontend notifications.
|
||||
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
:root{--color-primary: #f04494;--color-primary-hover: #e0307e;--color-primary-active: #c9206d;--color-primary-soft: rgba(240, 68, 148, .1);--color-primary-softer: rgba(240, 68, 148, .05);--color-on-primary: #FFFFFF;--color-ink: #333333;--color-ink-2: #4e5358;--color-ink-3: #777777;--color-ink-4: #999999;--surface-page: #f5f6f7;--surface-1: #FFFFFF;--surface-2: #eeeeee;--surface-3: #e9ecef;--surface-4: #f8f8f8;--color-line: rgba(50, 50, 50, .06);--color-line-soft: rgba(0, 0, 0, .03);--color-line-stronger: rgba(50, 50, 50, .12);--color-success: #10B981;--color-warning: #F59E0B;--color-error: #EF4444;--color-info: #3B82F6;--color-accent: #18a058;--color-accent-soft: rgba(24, 160, 88, .12);--color-accent-shadow: rgba(24, 160, 88, .28);--font-display: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;--font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;--font-ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;--font-mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;--text-xs: .75rem;--text-sm: .875rem;--text-base: .875rem;--text-md: 1.125rem;--text-lg: 1.25rem;--text-xl: 1.5rem;--text-2xl: 1.875rem;--text-3xl: 2.25rem;--text-4xl: 3rem;--text-5xl: 3.75rem;--leading-tight: 1.2;--leading-snug: 1.4;--leading-normal: 1.6;--leading-relaxed: 1.6;--space-1: 4px;--space-2: 8px;--space-3: 12px;--space-4: 16px;--space-5: 20px;--space-6: 24px;--space-7: 32px;--space-8: 40px;--space-9: 48px;--space-10: 64px;--space-11: 80px;--space-12: 96px;--radius-xs: 3px;--radius-sm: 4px;--radius: 8px;--radius-lg: 8px;--radius-xl: 12px;--radius-2xl: 16px;--radius-pill: 100px;--radius-avatar: 50%;--shadow-xs: 0 1px 2px rgba(0, 0, 0, .03);--shadow-sm: 0 0 10px rgba(116, 116, 116, .08);--shadow: 0 0 10px rgba(116, 116, 116, .12);--shadow-lg: 0 12px 32px rgba(18, 18, 18, .1);--shadow-drawer: 0 18px 60px rgba(0, 0, 0, .2);--nav-height: 60px;--container: 1200px;--container-wide: 1440px;--container-read: 760px;--container-page: 880px;--sidebar-width: 311px;--content-gap: 24px;--section-gap: 24px;--sidebar-bp: 992px;--ease: cubic-bezier(.4, 0, .2, 1);--ease-out: cubic-bezier(.16, 1, .3, 1);--ease-spring: cubic-bezier(.34, 1.56, .64, 1);--dur-fast: .15s;--dur: .25s;--dur-slow: .4s;--focus-ring: 0 0 2px 3px rgba(253, 83, 161, .4);--z-dropdown: 100;--z-sticky: 200;--z-fixed: 300;--z-overlay: 400;--z-modal: 500;--z-popover: 600;--z-toast: 700;--z-tooltip: 800;--radius-btn: 4px;--btn-sm-h: 28px;--btn-md-h: 34px;--btn-lg-h: 44px;--card-padding: 15px;--card-padding-sm: 10px;--card-gap: 16px;--card-gap-lg: 20px;--color-primary-light: var(--color-primary-soft);--color-surface: var(--surface-1);--color-line-strong: var(--color-line);--color-bg-hover: var(--surface-2);--color-border-light: var(--color-line-soft);--color-fill: var(--surface-2);--color-warning-bg: #fffbe6;--color-warning-text: #ad6800;--text-secondary: var(--color-ink-3);--text-tertiary: var(--color-ink-4);--surface-active: var(--surface-2);--surface-card: var(--surface-1);--surface-hover: var(--surface-2);--surface-secondary: var(--surface-2);--radius-code: var(--radius);--radius-full: 100px;--shadow-xl: 0 0 10px rgba(116, 116, 116, .12);--space-16: 64px;--z-floating: 900;--transition-fast: .15s ease;--transition-base: .25s ease;--header-bg: var(--blur-bg);--header-color: var(--color-ink-2);--footer-bg: var(--surface-1);--footer-color: var(--color-ink-4);--blur-bg: rgba(255, 255, 255, .8);--float-btn-bg: rgba(200, 200, 200, .4);--font-heading: var(--font-ui);--radius-card: var(--radius);--radius-md: var(--radius);--color-primary-50: var(--color-primary-soft);--color-primary-dark: var(--color-primary-active);--shadow-md: var(--shadow);--mini-radius: calc(var(--radius) / 1.4);--aksrt-avatar-size-xs: 24px;--aksrt-avatar-size-sm: 32px;--aksrt-avatar-size: 40px;--aksrt-avatar-size-lg: 56px;--aksrt-avatar-size-xl: 80px;--aksrt-card-thumb-scale: 56.25%;--aksrt-rose: #f43f5e;--aksrt-rose-soft: rgba(244, 63, 94, .12);--aksrt-blue: #3b82f6;--aksrt-blue-soft: rgba(59, 130, 246, .12);--aksrt-green: #10b981;--aksrt-green-soft: rgba(16, 185, 129, .12);--aksrt-orange: #f97316;--aksrt-orange-soft:rgba(249, 115, 22, .12);--aksrt-purple: #8b5cf6;--aksrt-purple-soft:rgba(139, 92, 246, .12);--aksrt-cyan: #06b6d4;--aksrt-cyan-soft: rgba(6, 182, 212, .12);--aksrt-yellow: #eab308;--aksrt-yellow-soft:rgba(234, 179, 8, .12);--aksrt-red: #ef4444;--aksrt-red-soft: rgba(239, 68, 68, .12);--aksrt-teal: #14b8a6;--aksrt-teal-soft: rgba(20, 184, 166, .12);--aksrt-indigo: #6366f1;--aksrt-indigo-soft:rgba(99, 102, 241, .12);--aksrt-pink: #ec4899;--aksrt-pink-soft: rgba(236, 72, 153, .12);--aksrt-vip-gold-1: #f6d365;--aksrt-vip-gold-2: #fda085;--aksrt-grad-sunset-1: #f093fb;--aksrt-grad-sunset-2: #f5576c;--aksrt-graLine truncated
|
||||
:root{--color-primary: #f04494;--color-primary-hover: #e0307e;--color-primary-active: #c9206d;--color-primary-soft: rgba(240, 68, 148, .1);--color-primary-softer: rgba(240, 68, 148, .05);--color-on-primary: #FFFFFF;--color-ink: #333333;--color-ink-2: #4e5358;--color-ink-3: #777777;--color-ink-4: #999999;--surface-page: #f5f6f7;--surface-1: #FFFFFF;--surface-2: #eeeeee;--surface-3: #e9ecef;--surface-4: #f8f8f8;--color-line: rgba(50, 50, 50, .06);--color-line-soft: rgba(0, 0, 0, .03);--color-line-stronger: rgba(50, 50, 50, .12);--color-success: #10B981;--color-warning: #F59E0B;--color-error: #EF4444;--color-info: #3B82F6;--color-accent: #18a058;--color-accent-soft: rgba(24, 160, 88, .12);--color-accent-shadow: rgba(24, 160, 88, .28);--font-display: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;--font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;--font-ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;--font-mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;--text-xs: .75rem;--text-sm: .875rem;--text-base: .875rem;--text-md: 1.125rem;--text-lg: 1.25rem;--text-xl: 1.5rem;--text-2xl: 1.875rem;--text-3xl: 2.25rem;--text-4xl: 3rem;--text-5xl: 3.75rem;--leading-tight: 1.2;--leading-snug: 1.4;--leading-normal: 1.6;--leading-relaxed: 1.6;--space-1: 4px;--space-2: 8px;--space-3: 12px;--space-4: 16px;--space-5: 20px;--space-6: 24px;--space-7: 32px;--space-8: 40px;--space-9: 48px;--space-10: 64px;--space-11: 80px;--space-12: 96px;--radius-xs: 3px;--radius-sm: 4px;--radius: 8px;--radius-lg: 8px;--radius-xl: 12px;--radius-2xl: 16px;--radius-pill: 100px;--radius-avatar: 50%;--shadow-xs: 0 1px 2px rgba(0, 0, 0, .03);--shadow-sm: 0 0 10px rgba(116, 116, 116, .08);--shadow: 0 0 10px rgba(116, 116, 116, .12);--shadow-lg: 0 12px 32px rgba(18, 18, 18, .1);--shadow-drawer: 0 18px 60px rgba(0, 0, 0, .2);--nav-height: 60px;--container: 1200px;--container-wide: 1440px;--container-read: 760px;--container-page: 880px;--sidebar-width: 311px;--content-gap: 24px;--section-gap: 24px;--sidebar-bp: 992px;--ease: cubic-bezier(.4, 0, .2, 1);--ease-out: cubic-bezier(.16, 1, .3, 1);--ease-spring: cubic-bezier(.34, 1.56, .64, 1);--dur-fast: .15s;--dur: .25s;--dur-slow: .4s;--focus-ring: 0 0 2px 3px rgba(253, 83, 161, .4);--z-dropdown: 100;--z-sticky: 200;--z-fixed: 300;--z-overlay: 400;--z-modal: 500;--z-popover: 600;--z-toast: 700;--z-tooltip: 800;--radius-btn: 4px;--btn-sm-h: 28px;--btn-md-h: 34px;--btn-lg-h: 44px;--card-padding: 15px;--card-padding-sm: 10px;--card-gap: 16px;--card-gap-lg: 20px;--color-primary-light: var(--color-primary-soft);--color-surface: var(--surface-1);--color-line-strong: var(--color-line);--color-bg-hover: var(--surface-2);--color-border-light: var(--color-line-soft);--color-fill: var(--surface-2);--color-warning-bg: #fffbe6;--color-warning-text: #ad6800;--text-secondary: var(--color-ink-3);--text-tertiary: var(--color-ink-4);--surface-active: var(--surface-2);--surface-card: var(--surface-1);--surface-hover: var(--surface-2);--surface-secondary: var(--surface-2);--radius-code: var(--radius);--radius-full: 100px;--shadow-xl: 0 0 10px rgba(116, 116, 116, .12);--space-16: 64px;--z-floating: 900;--transition-fast: .15s ease;--transition-base: .25s ease;--header-bg: var(--blur-bg);--header-color: var(--color-ink-2);--footer-bg: var(--surface-1);--footer-color: var(--color-ink-4);--blur-bg: rgba(255, 255, 255, .8);--float-btn-bg: rgba(200, 200, 200, .4);--font-heading: var(--font-ui);--radius-card: var(--radius);--radius-md: var(--radius);--color-primary-50: var(--color-primary-soft);--color-primary-dark: var(--color-primary-active);--shadow-md: var(--shadow);--mini-radius: calc(var(--radius) / 1.4);--aksrt-avatar-size-xs: 24px;--aksrt-avatar-size-sm: 32px;--aksrt-avatar-size: 40px;--aksrt-avatar-size-lg: 56px;--aksrt-avatar-size-xl: 80px;--aksrt-card-thumb-scale: 56.25%;--aksrt-rose: #f43f5e;--aksrt-rose-soft: rgba(244, 63, 94, .12);--aksrt-blue: #3b82f6;--aksrt-blue-soft: rgba(59, 130, 246, .12);--aksrt-green: #10b981;--aksrt-green-soft: rgba(16, 185, 129, .12);--aksrt-orange: #f97316;--aksrt-orange-soft:rgba(249, 115, 22, .12);--aksrt-purple: #8b5cf6;--aksrt-purple-soft:rgba(139, 92, 246, .12);--aksrt-cyan: #06b6d4;--aksrt-cyan-soft: rgba(6, 182, 212, .12);--aksrt-yellow: #eab308;--aksrt-yellow-soft:rgba(234, 179, 8, .12);--aksrt-red: #ef4444;--aksrt-red-soft: rgba(239, 68, 68, .12);--aksrt-teal: #14b8a6;--aksrt-teal-soft: rgba(20, 184, 166, .12);--aksrt-indigo: #6366f1;--aksrt-indigo-soft:rgba(99, 102, 241, .12);--aksrt-pink: #ec4899;--aksrt-pink-soft: rgba(236, 72, 153, .12);--aksrt-vip-gold-1: #f6d365;--aksrt-vip-gold-2: #fda085;--aksrt-grad-sunset-1: #f093fb;--aksrt-grad-sunset-2: #f5576c;--aksrt-graLine truncated
|
||||
File diff suppressed because it is too large.
Load diff
+208
-204
@@ -1,204 +1,208 @@
|
||||
/* Knowledge-payment order center */
|
||||
.aksrt-orders-page {
|
||||
--orders-ink: var(--color-ink, #1d2738);
|
||||
--orders-muted: var(--color-ink-3, #758096);
|
||||
--orders-line: color-mix(in srgb, var(--color-line, #e1e6ef) 88%, transparent);
|
||||
padding: 0;
|
||||
color: var(--orders-ink);
|
||||
}
|
||||
|
||||
.uc-orders-panel__head {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(285px, 42%);
|
||||
gap: 28px;
|
||||
align-items: end;
|
||||
padding: 4px 0 22px;
|
||||
border-bottom: 1px solid var(--orders-line);
|
||||
}
|
||||
|
||||
.aksrt-orders-page__eyebrow {
|
||||
margin: 0 0 7px;
|
||||
color: var(--color-primary, #3b82f6);
|
||||
font-size: 11px;
|
||||
font-weight: 750;
|
||||
letter-spacing: .14em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.uc-orders-panel__title { display: flex; align-items: center; gap: 9px; font-size: 21px; }
|
||||
.uc-orders-panel__title-icon { color: var(--color-primary, #3b82f6); font-size: 17px; }
|
||||
.uc-orders-panel__title-wrap > p:last-child { margin-bottom: 0; }
|
||||
|
||||
.aksrt-orders-page__stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
border: 1px solid var(--orders-line);
|
||||
border-radius: 14px;
|
||||
background: var(--surface-1, #fff);
|
||||
overflow: hidden;
|
||||
}
|
||||
.aksrt-orders-page__stat { display: grid; gap: 5px; padding: 13px 15px; border-right: 1px solid var(--orders-line); }
|
||||
.aksrt-orders-page__stat:last-child { border-right: 0; }
|
||||
.aksrt-orders-page__stat span { color: var(--orders-muted); font-size: 11px; font-weight: 600; }
|
||||
.aksrt-orders-page__stat strong { font-size: 21px; line-height: 1; font-variant-numeric: tabular-nums; }
|
||||
.aksrt-orders-page__stat strong.is-pending { color: var(--color-warning, #c87819); }
|
||||
.aksrt-orders-page__stat strong.is-paid { color: var(--color-success, #168560); }
|
||||
|
||||
.aksrt-orders-page__search { display: flex; gap: 8px; align-items: center; margin: 20px 0 12px; padding: 7px; border: 1px solid var(--orders-line); border-radius: 12px; background: var(--surface-1, #fff); }
|
||||
.aksrt-orders-page__search > i { margin-left: 7px; color: var(--orders-muted); font-size: 13px; }
|
||||
.aksrt-orders-page__search input { flex: 1; min-width: 0; border: 0; outline: 0; background: transparent; color: var(--orders-ink); font-size: 13px; }
|
||||
.aksrt-orders-page__search button, .aksrt-orders-page__card-actions .btn-primary, .aksrt-orders-page__card-actions .btn-outline, .aksrt-orders-page__card-actions .btn-ghost, .aksrt-orders-page__empty .btn-outline { display: inline-flex; align-items: center; justify-content: center; min-height: 34px; padding: 0 13px; border-radius: 8px; border: 1px solid transparent; font-size: 12px; font-weight: 650; text-decoration: none; cursor: pointer; }
|
||||
.aksrt-orders-page .btn-primary { background: var(--color-primary, #3b82f6); color: var(--color-on-primary, #fff); }
|
||||
.aksrt-orders-page .btn-outline { border-color: var(--orders-line); background: var(--surface-1, #fff); color: var(--orders-ink); }
|
||||
.aksrt-orders-page .btn-ghost { background: transparent; color: var(--orders-muted); }
|
||||
.aksrt-orders-page__filter-toggle { background: var(--surface-2, #f4f6f9); color: var(--orders-ink); white-space: nowrap; }
|
||||
|
||||
.aksrt-orders-page__filter-panel { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: -2px 0 14px; padding: 12px; border-radius: 10px; background: var(--surface-2, #f4f6f9); }
|
||||
.aksrt-orders-page__filter-panel[hidden] { display: none !important; }
|
||||
.aksrt-orders-page__filter-group { display: flex; flex: 1 1 100%; gap: 10px; align-items: center; }
|
||||
.aksrt-orders-page__filter-label { flex: 0 0 58px; color: var(--orders-muted); font-size: 12px; font-weight: 650; }
|
||||
.aksrt-orders-page__status-filters, .aksrt-orders-page__type-filters { display: flex; flex-wrap: wrap; gap: 6px; }
|
||||
.aksrt-orders-page__filter-chip { display: inline-flex; gap: 5px; align-items: center; padding: 5px 9px; border: 1px solid var(--orders-line); border-radius: 999px; background: var(--surface-1, #fff); color: var(--orders-muted); font: inherit; font-size: 12px; text-decoration: none; cursor: pointer; }
|
||||
.aksrt-orders-page__filter-chip.is-active { border-color: var(--color-primary, #3b82f6); background: color-mix(in srgb, var(--color-primary, #3b82f6) 10%, var(--surface-1, #fff)); color: var(--color-primary, #3b82f6); font-weight: 700; }
|
||||
.aksrt-orders-page__filter-count { display: inline-grid; min-width: 17px; height: 17px; padding: 0 4px; place-items: center; border-radius: 999px; background: var(--color-warning, #c87819); color: #fff; font-size: 10px; line-height: 1; }
|
||||
.aksrt-orders-page__clear { margin-left: auto; padding: 0; border: 0; background: transparent; color: var(--color-primary, #3b82f6); font: inherit; font-size: 12px; text-decoration: none; cursor: pointer; }
|
||||
|
||||
.aksrt-orders-page__list { display: grid; gap: 10px; }
|
||||
.aksrt-orders-page__card { overflow: hidden; border: 1px solid var(--orders-line); border-radius: 13px; background: var(--surface-1, #fff); transition: border-color .18s ease, transform .18s ease, box-shadow .18s ease; }
|
||||
.aksrt-orders-page__card:hover { border-color: color-mix(in srgb, var(--color-primary, #3b82f6) 36%, var(--orders-line)); box-shadow: 0 8px 22px rgba(30, 48, 79, .07); transform: translateY(-1px); }
|
||||
.aksrt-orders-page__card-rail { display: flex; gap: 10px; align-items: center; padding: 9px 14px; background: var(--surface-2, #f7f8fb); color: var(--orders-muted); font-size: 11px; }
|
||||
.aksrt-orders-page__card-rail time { margin-left: auto; font-variant-numeric: tabular-nums; }
|
||||
.aksrt-orders-page__card-status { font-weight: 700; }
|
||||
.aksrt-orders-page__card-status.is-pending { color: var(--color-warning, #c87819); }
|
||||
.aksrt-orders-page__card-status.is-paid { color: var(--color-success, #168560); }
|
||||
.aksrt-orders-page__card-main { display: grid; grid-template-columns: 52px minmax(0, 1fr) auto; gap: 13px; align-items: center; padding: 14px; }
|
||||
.aksrt-orders-page__card-img, .aksrt-orders-page__card-art { width: 52px; height: 52px; border-radius: 10px; object-fit: cover; }
|
||||
.aksrt-orders-page__card-art { display: grid; place-items: center; color: var(--color-primary, #3b82f6); background: color-mix(in srgb, var(--color-primary, #3b82f6) 10%, var(--surface-2, #f4f6f9)); }
|
||||
.aksrt-orders-page__card-copy { min-width: 0; }
|
||||
.aksrt-orders-page__card-copy h3 { display: -webkit-box; margin: 0 0 5px; overflow: hidden; color: var(--orders-ink); font-size: 14px; line-height: 1.35; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
|
||||
.aksrt-orders-page__card-copy p { margin: 0; color: var(--orders-muted); font-family: var(--font-mono, monospace); font-size: 11px; }
|
||||
.aksrt-orders-page__card-value { display: grid; gap: 3px; text-align: right; }
|
||||
.aksrt-orders-page__card-value strong { color: var(--orders-ink); font-size: 15px; font-variant-numeric: tabular-nums; }
|
||||
.aksrt-orders-page__card-value span { color: var(--orders-muted); font-size: 11px; }
|
||||
.aksrt-orders-page__card-actions { display: flex; gap: 7px; align-items: center; justify-content: flex-end; padding: 9px 14px; border-top: 1px solid var(--orders-line); }
|
||||
.aksrt-orders-page__countdown { margin-right: auto; color: var(--color-warning, #c87819); font-size: 11px; font-variant-numeric: tabular-nums; }
|
||||
|
||||
.aksrt-orders-page__pagination { display: flex; justify-content: center; gap: 6px; margin-top: 20px; }
|
||||
.aksrt-orders-page__page-chip { display: grid; width: 31px; height: 31px; padding: 0; place-items: center; border: 1px solid var(--orders-line); border-radius: 8px; background: var(--surface-1, #fff); color: var(--orders-muted); font: inherit; font-size: 12px; text-decoration: none; cursor: pointer; }
|
||||
.aksrt-orders-page__page-chip.is-active { border-color: var(--color-primary, #3b82f6); background: var(--color-primary, #3b82f6); color: #fff; }
|
||||
.aksrt-orders-page [data-aksrt-order-list], .aksrt-orders-page__stats, .aksrt-orders-page__pagination { transition: opacity .16s ease; }
|
||||
.aksrt-orders-page.is-loading [data-aksrt-order-list], .aksrt-orders-page.is-loading .aksrt-orders-page__stats, .aksrt-orders-page.is-loading .aksrt-orders-page__pagination { opacity: .55; pointer-events: none; }
|
||||
.aksrt-orders-page__empty { padding: 52px 20px; border: 1px dashed var(--orders-line); border-radius: 13px; background: var(--surface-2, #f7f8fb); text-align: center; }
|
||||
.aksrt-orders-page__empty i { display: block; margin-bottom: 12px; color: var(--color-primary, #3b82f6); font-size: 30px; }
|
||||
.aksrt-orders-page__empty h3 { margin: 0 0 7px; font-size: 15px; }.aksrt-orders-page__empty p { margin: 0 0 16px; color: var(--orders-muted); font-size: 13px; }
|
||||
|
||||
/* ---- Order detail modal (front-end order center) ---- */
|
||||
/* 弹窗 portal 到 <body>,读不到页面上的 `--orders-*` 别名,因此把本模块的局部
|
||||
别名映射到 modal-runtime.css 声明的共享 portal 基座 `--modal-*`;
|
||||
路由文件不应再自行推导调色板值。 */
|
||||
.aksrt-order-detail-modal {
|
||||
--detail-ink: var(--modal-ink);
|
||||
--detail-muted: var(--modal-muted);
|
||||
--detail-line: var(--modal-line);
|
||||
--detail-surface: var(--modal-surface);
|
||||
--detail-surface-2: var(--modal-surface-2);
|
||||
--detail-surface-3: var(--modal-surface-3);
|
||||
--detail-primary: var(--modal-primary);
|
||||
--detail-warning: var(--modal-warning);
|
||||
--detail-success: var(--modal-success);
|
||||
}
|
||||
|
||||
/* 订单详情弹窗:与售后弹窗统一为 modal-mini 几何(400px);标题区改用蓝色炫彩头,运行时不再渲染默认标题栏。 */
|
||||
.aksrt-order-detail-modal:not(.aksrt-modal-runtime--bottom) .aksrt-modal-runtime__dialog {
|
||||
width: min(400px, calc(100vw - 32px));
|
||||
border-radius: var(--main-radius, 8px);
|
||||
background: var(--main-bg-color, #fff);
|
||||
}
|
||||
.aksrt-order-detail-modal .aksrt-modal-runtime__content { display: flex; flex-direction: column; gap: 18px; padding: 16px 18px 0; }
|
||||
|
||||
.aksrt-order-detail__skeleton { display: grid; gap: 12px; padding: 4px 0; }
|
||||
|
||||
/* Status banner — a single local variable drives the whole colour scheme. */
|
||||
.aksrt-order-detail__hero { --hero-accent: var(--detail-primary); --hero-text: color-mix(in srgb, var(--hero-accent) 72%, var(--detail-ink)); display: flex; align-items: center; gap: 13px; padding: 15px; border: 1px solid color-mix(in srgb, var(--hero-accent) 20%, transparent); border-radius: 14px; background: color-mix(in srgb, var(--hero-accent) 12%, var(--detail-surface)); }
|
||||
.aksrt-order-detail__hero.is-pending { --hero-accent: var(--detail-warning); }
|
||||
.aksrt-order-detail__hero.is-paid { --hero-accent: var(--detail-success); }
|
||||
.aksrt-order-detail__hero.is-refunded, .aksrt-order-detail__hero.is-closed { --hero-accent: var(--detail-muted); }
|
||||
.aksrt-order-detail__hero-icon { display: grid; flex: 0 0 auto; width: 42px; height: 42px; place-items: center; border-radius: 12px; background: color-mix(in srgb, var(--hero-accent) 16%, transparent); color: var(--hero-text); font-size: 19px; }
|
||||
.aksrt-order-detail__hero-copy { display: grid; flex: 1 1 auto; gap: 4px; min-width: 0; }
|
||||
.aksrt-order-detail__status { color: var(--hero-text); font-size: 15px; font-weight: 750; line-height: 1.2; }
|
||||
.aksrt-order-detail__desc { margin: 0; color: var(--detail-muted); font-size: 12px; line-height: 1.45; }
|
||||
.aksrt-order-detail__countdown { color: var(--hero-text); font-size: 12px; font-variant-numeric: tabular-nums; }
|
||||
.aksrt-order-detail__hero-amount { display: grid; flex: 0 0 auto; gap: 3px; justify-items: end; text-align: right; }
|
||||
.aksrt-order-detail__hero-amount strong { color: var(--detail-ink); font-size: 21px; font-weight: 750; line-height: 1.1; letter-spacing: -.01em; font-variant-numeric: tabular-nums; }
|
||||
.aksrt-order-detail__hero-amount span { color: var(--detail-muted); font-size: 11px; }
|
||||
|
||||
.aksrt-order-detail__group { display: grid; gap: 10px; }
|
||||
.aksrt-order-detail__group-title { display: flex; align-items: center; gap: 8px; margin: 0; color: var(--detail-ink); font-size: 13px; font-weight: 700; line-height: 1.3; }
|
||||
.aksrt-order-detail__group-title::before { content: ""; width: 3px; height: 13px; border-radius: 2px; background: var(--detail-primary); }
|
||||
|
||||
.aksrt-order-detail__product { display: grid; grid-template-columns: 54px minmax(0, 1fr); gap: 12px; align-items: center; padding: 12px; border: 1px solid var(--detail-line); border-radius: 12px; background: var(--detail-surface); }
|
||||
.aksrt-order-detail__thumb { width: 54px; height: 54px; border-radius: 10px; object-fit: cover; }
|
||||
.aksrt-order-detail__thumb--empty { display: grid; place-items: center; background: var(--detail-surface-2); color: var(--detail-primary); font-size: 19px; }
|
||||
.aksrt-order-detail__product-copy { display: grid; gap: 6px; min-width: 0; }
|
||||
.aksrt-order-detail__product-title { display: -webkit-box; overflow: hidden; color: var(--detail-ink); font-size: 14px; line-height: 1.45; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
|
||||
.aksrt-order-detail__product-meta { display: inline-flex; justify-self: start; padding: 2px 8px; border-radius: 999px; background: color-mix(in srgb, var(--detail-primary) 9%, var(--detail-surface-2)); color: var(--detail-primary); font-size: 11px; font-weight: 650; line-height: 1.5; }
|
||||
|
||||
.aksrt-order-detail__list { display: grid; margin: 0; overflow: hidden; border: 1px solid var(--detail-line); border-radius: 12px; background: var(--detail-surface); }
|
||||
.aksrt-order-detail__list > div { display: grid; grid-template-columns: 84px minmax(0, 1fr); align-items: center; gap: 12px; padding: 11px 13px; border-bottom: 1px solid var(--detail-line); }
|
||||
.aksrt-order-detail__list > div:last-child { border-bottom: 0; }
|
||||
.aksrt-order-detail__list dt { color: var(--detail-muted); font-size: 12.5px; }
|
||||
.aksrt-order-detail__list dd { display: flex; align-items: center; justify-content: flex-end; gap: 8px; min-width: 0; margin: 0; color: var(--detail-ink); font-size: 13px; font-variant-numeric: tabular-nums; text-align: right; }
|
||||
.aksrt-order-detail__value-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.aksrt-order-detail__value-text.is-mono { font-family: var(--font-mono, monospace); font-size: 12.5px; letter-spacing: .01em; }
|
||||
.aksrt-order-detail__copy { display: inline-grid; flex: 0 0 auto; width: 24px; height: 24px; padding: 0; place-items: center; border: 1px solid var(--detail-line); border-radius: 7px; background: var(--detail-surface-3); color: var(--detail-muted); font-size: 11px; line-height: 1; cursor: pointer; transition: color .15s ease, border-color .15s ease; }
|
||||
.aksrt-order-detail__copy:hover { border-color: color-mix(in srgb, var(--detail-primary) 45%, var(--detail-line)); color: var(--detail-primary); }
|
||||
.aksrt-order-detail__copy.is-copied { border-color: color-mix(in srgb, var(--detail-success) 45%, var(--detail-line)); color: var(--detail-success); }
|
||||
|
||||
.aksrt-order-detail__refresh-state { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: 10px; padding: 12px 13px; border: 1px solid color-mix(in srgb, var(--detail-warning) 26%, transparent); border-radius: 12px; background: color-mix(in srgb, var(--detail-warning) 10%, var(--detail-surface-2)); color: var(--detail-ink); font-size: 12.5px; }
|
||||
.aksrt-order-detail__refresh-state > i { color: var(--detail-warning); font-size: 15px; }
|
||||
.aksrt-order-detail__refresh-state p { margin: 0; }
|
||||
.aksrt-order-detail__refresh-state button { grid-column: 2; justify-self: start; }
|
||||
|
||||
/* 底部操作区:按钮排布沿用主题 .but-average.modal-buts(禁封申诉弹窗同式),此处仅保留吸底定位与通栏背景。
|
||||
主题 modal-buts 自带 margin-top:16px,吸底场景下用更高优先级清掉,避免内容与吸底条之间出现空隙。 */
|
||||
.aksrt-order-detail-modal .aksrt-order-detail__actions { position: sticky; bottom: 0; z-index: 2; margin: 0 -18px; padding: 13px 18px calc(13px + env(safe-area-inset-bottom)); border-top: 1px solid var(--detail-line); background: var(--detail-surface); }
|
||||
.aksrt-order-detail-modal .aksrt-order-detail__actions.but-average.modal-buts { margin-top: 0; }
|
||||
|
||||
/* Orders extensions */
|
||||
.aksrt-orders-page__extra-nav { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 12px; }
|
||||
.aksrt-orders-page__extra-link { padding: 6px 12px; border-radius: 999px; background: var(--surface-2, #f3f4f6); color: inherit; text-decoration: none; font-size: 13px; }
|
||||
.aksrt-orders-page__extra-link.is-active { background: var(--focus-color, var(--color-primary)); color: #fff; }
|
||||
.aksrt-as-list { display: grid; gap: 12px; }
|
||||
.aksrt-as-card { padding: 14px; border: 1px solid var(--main-border-color, #eee); border-radius: 10px; background: var(--main-bg-color, #fff); }
|
||||
.aksrt-as-card__head { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-bottom: 6px; }
|
||||
.aksrt-as-card__status { margin-left: auto; color: var(--focus-color, #2f6df6); font-weight: 600; }
|
||||
.aksrt-as-timeline { margin: 10px 0 0; padding-left: 18px; color: var(--muted-2-color, #888); font-size: 12px; }
|
||||
.aksrt-as-card__actions { display: flex; gap: 8px; margin-top: 10px; }
|
||||
.aksrt-coupon-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 12px; }
|
||||
.aksrt-coupon-card { padding: 12px; border: 1px dashed var(--focus-color, #2f6df6); border-radius: 10px; background: color-mix(in srgb, var(--focus-color, #2f6df6) 6%, #fff); }
|
||||
.aksrt-coupon-card__value { color: var(--focus-color, #2f6df6); font-size: 20px; font-weight: 700; }
|
||||
.aksrt-coupon-card__code { margin: 6px 0; font-family: ui-monospace, monospace; }
|
||||
.aksrt-coupon-card__meta { margin-bottom: 4px; color: var(--muted-2-color, #888); font-size: 12px; }
|
||||
.aksrt-orders-page__field { margin-bottom: 12px; }
|
||||
.aksrt-orders-page__input, .aksrt-orders-page__select, .aksrt-orders-page__textarea { box-sizing: border-box; width: 100%; padding: 8px 10px; border: 1px solid var(--main-border-color, #ddd); border-radius: 6px; }
|
||||
@media (max-width: 767px) { .uc-orders-panel__head { grid-template-columns: 1fr; gap: 17px; }.aksrt-orders-page__stats { max-width: none; }.aksrt-orders-page__search { flex-wrap: wrap; }.aksrt-orders-page__search input { min-width: 150px; }.aksrt-orders-page__search .btn-primary { margin-left: auto; }.aksrt-orders-page__filter-toggle { margin-left: auto; }.aksrt-orders-page__card-rail time { width: 100%; margin-left: 0; order: 3; }.aksrt-orders-page__card-main { grid-template-columns: 48px minmax(0, 1fr); }.aksrt-orders-page__card-img, .aksrt-orders-page__card-art { width: 48px; height: 48px; }.aksrt-orders-page__card-value { grid-column: 2; text-align: left; }.aksrt-orders-page__card-actions > :not(.aksrt-orders-page__countdown) { flex: 1 1 auto; }.aksrt-orders-page__countdown { width: 100%; }}
|
||||
html.dark .aksrt-orders-page__stat, html.dark .aksrt-orders-page__card { background: var(--surface-1, #1a1d24); } html.dark .aksrt-orders-page__card-rail, html.dark .aksrt-orders-page__filter-panel { background: var(--surface-2, #12151b); }
|
||||
|
||||
/* ---- 售后记录卡片(订单详情弹窗) ---- */
|
||||
.aksrt-order-detail__after-sales { display: grid; gap: 10px; margin: 0; padding: 0; list-style: none; }
|
||||
.aksrt-order-detail__after-sale { --as-accent: var(--detail-muted); display: grid; gap: 10px; padding: 13px 14px; border: 1px solid var(--detail-line); border-left: 3px solid var(--as-accent); border-radius: 12px; background: var(--detail-surface); }
|
||||
.aksrt-order-detail__after-sale.is-pending { --as-accent: var(--detail-warning); }
|
||||
.aksrt-order-detail__after-sale.is-processing { --as-accent: var(--detail-primary); }
|
||||
.aksrt-order-detail__after-sale.is-approved, .aksrt-order-detail__after-sale.is-completed { --as-accent: var(--detail-success); }
|
||||
.aksrt-order-detail__after-sale.is-rejected, .aksrt-order-detail__after-sale.is-cancelled { --as-accent: var(--modal-danger, #ef4444); }
|
||||
.aksrt-order-detail__after-sale-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
|
||||
.aksrt-order-detail__after-sale-badge { padding: 3px 11px; border-radius: 999px; background: color-mix(in srgb, var(--as-accent) 12%, transparent); color: var(--as-accent); font-size: 12px; font-weight: 700; line-height: 1.5; }
|
||||
.aksrt-order-detail__after-sale-head time { color: var(--detail-muted); font-size: 11.5px; font-variant-numeric: tabular-nums; }
|
||||
.aksrt-order-detail__after-sale-reason { margin: 0; color: var(--detail-ink); font-size: 13px; line-height: 1.65; white-space: pre-wrap; word-break: break-word; }
|
||||
.aksrt-order-detail__after-sale-meta { display: grid; margin: 0; overflow: hidden; border: 1px solid var(--detail-line); border-radius: 10px; background: var(--detail-surface-3); }
|
||||
.aksrt-order-detail__after-sale-meta > div { display: grid; grid-template-columns: 76px minmax(0, 1fr); gap: 10px; padding: 9px 12px; border-bottom: 1px solid var(--detail-line); font-size: 12.5px; }
|
||||
.aksrt-order-detail__after-sale-meta > div:last-child { border-bottom: 0; }
|
||||
.aksrt-order-detail__after-sale-meta dt { color: var(--detail-muted); }
|
||||
.aksrt-order-detail__after-sale-meta dd { margin: 0; color: var(--detail-ink); font-size: 12.5px; text-align: right; word-break: break-word; }
|
||||
.aksrt-order-detail__after-sale-field { color: var(--detail-muted); }
|
||||
.aksrt-order-detail__after-sale-foot { display: flex; flex-wrap: wrap; gap: 8px; }
|
||||
/* Knowledge-payment order center */
|
||||
.aksrt-orders-page {
|
||||
--orders-ink: var(--color-ink, #1d2738);
|
||||
--orders-muted: var(--color-ink-3, #758096);
|
||||
--orders-line: color-mix(in srgb, var(--color-line, #e1e6ef) 88%, transparent);
|
||||
padding: 0;
|
||||
color: var(--orders-ink);
|
||||
}
|
||||
|
||||
.uc-orders-panel__head {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(285px, 42%);
|
||||
gap: 28px;
|
||||
align-items: end;
|
||||
padding: 4px 0 22px;
|
||||
border-bottom: 1px solid var(--orders-line);
|
||||
}
|
||||
|
||||
.aksrt-orders-page__eyebrow {
|
||||
margin: 0 0 7px;
|
||||
color: var(--color-primary, #3b82f6);
|
||||
font-size: 11px;
|
||||
font-weight: 750;
|
||||
letter-spacing: .14em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.uc-orders-panel__title { display: flex; align-items: center; gap: 9px; font-size: 21px; }
|
||||
.uc-orders-panel__title-icon { color: var(--color-primary, #3b82f6); font-size: 17px; }
|
||||
.uc-orders-panel__title-wrap > p:last-child { margin-bottom: 0; }
|
||||
|
||||
.aksrt-orders-page__stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
border: 1px solid var(--orders-line);
|
||||
border-radius: 14px;
|
||||
background: var(--surface-1, #fff);
|
||||
overflow: hidden;
|
||||
}
|
||||
.aksrt-orders-page__stat { display: grid; gap: 5px; padding: 13px 15px; border-right: 1px solid var(--orders-line); }
|
||||
.aksrt-orders-page__stat:last-child { border-right: 0; }
|
||||
.aksrt-orders-page__stat span { color: var(--orders-muted); font-size: 11px; font-weight: 600; }
|
||||
.aksrt-orders-page__stat strong { font-size: 21px; line-height: 1; font-variant-numeric: tabular-nums; }
|
||||
.aksrt-orders-page__stat strong.is-pending { color: var(--color-warning, #c87819); }
|
||||
.aksrt-orders-page__stat strong.is-paid { color: var(--color-success, #168560); }
|
||||
|
||||
.aksrt-orders-page__search { display: flex; gap: 8px; align-items: center; margin: 20px 0 12px; padding: 7px; border: 1px solid var(--orders-line); border-radius: 12px; background: var(--surface-1, #fff); }
|
||||
.aksrt-orders-page__search > i { margin-left: 7px; color: var(--orders-muted); font-size: 13px; }
|
||||
.aksrt-orders-page__search input { flex: 1; min-width: 0; border: 0; outline: 0; background: transparent; color: var(--orders-ink); font-size: 13px; }
|
||||
.aksrt-orders-page__search button, .aksrt-orders-page__card-actions .btn-primary, .aksrt-orders-page__card-actions .btn-outline, .aksrt-orders-page__card-actions .btn-ghost, .aksrt-orders-page__empty .btn-outline { display: inline-flex; align-items: center; justify-content: center; min-height: 34px; padding: 0 13px; border-radius: 8px; border: 1px solid transparent; font-size: 12px; font-weight: 650; text-decoration: none; cursor: pointer; }
|
||||
.aksrt-orders-page .btn-primary { background: var(--color-primary, #3b82f6); color: var(--color-on-primary, #fff); }
|
||||
.aksrt-orders-page .btn-outline { border-color: var(--orders-line); background: var(--surface-1, #fff); color: var(--orders-ink); }
|
||||
.aksrt-orders-page .btn-ghost { background: transparent; color: var(--orders-muted); }
|
||||
.aksrt-orders-page__filter-toggle { background: var(--surface-2, #f4f6f9); color: var(--orders-ink); white-space: nowrap; }
|
||||
|
||||
.aksrt-orders-page__filter-panel { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: -2px 0 14px; padding: 12px; border-radius: 10px; background: var(--surface-2, #f4f6f9); }
|
||||
.aksrt-orders-page__filter-panel[hidden] { display: none !important; }
|
||||
.aksrt-orders-page__filter-group { display: flex; flex: 1 1 100%; gap: 10px; align-items: center; }
|
||||
.aksrt-orders-page__filter-label { flex: 0 0 58px; color: var(--orders-muted); font-size: 12px; font-weight: 650; }
|
||||
.aksrt-orders-page__status-filters, .aksrt-orders-page__type-filters { display: flex; flex-wrap: wrap; gap: 6px; }
|
||||
.aksrt-orders-page__filter-chip { display: inline-flex; gap: 5px; align-items: center; padding: 5px 9px; border: 1px solid var(--orders-line); border-radius: 999px; background: var(--surface-1, #fff); color: var(--orders-muted); font: inherit; font-size: 12px; text-decoration: none; cursor: pointer; }
|
||||
.aksrt-orders-page__filter-chip.is-active { border-color: var(--color-primary, #3b82f6); background: color-mix(in srgb, var(--color-primary, #3b82f6) 10%, var(--surface-1, #fff)); color: var(--color-primary, #3b82f6); font-weight: 700; }
|
||||
.aksrt-orders-page__filter-count { display: inline-grid; min-width: 17px; height: 17px; padding: 0 4px; place-items: center; border-radius: 999px; background: var(--color-warning, #c87819); color: #fff; font-size: 10px; line-height: 1; }
|
||||
.aksrt-orders-page__clear { margin-left: auto; padding: 0; border: 0; background: transparent; color: var(--color-primary, #3b82f6); font: inherit; font-size: 12px; text-decoration: none; cursor: pointer; }
|
||||
|
||||
.aksrt-orders-page__list { display: grid; gap: 10px; }
|
||||
.aksrt-orders-page__card { overflow: hidden; border: 1px solid var(--orders-line); border-radius: 13px; background: var(--surface-1, #fff); transition: border-color .18s ease, transform .18s ease, box-shadow .18s ease; }
|
||||
.aksrt-orders-page__card:hover { border-color: color-mix(in srgb, var(--color-primary, #3b82f6) 36%, var(--orders-line)); box-shadow: 0 8px 22px rgba(30, 48, 79, .07); transform: translateY(-1px); }
|
||||
.aksrt-orders-page__card-rail { display: flex; gap: 10px; align-items: center; padding: 9px 14px; background: var(--surface-2, #f7f8fb); color: var(--orders-muted); font-size: 11px; }
|
||||
.aksrt-orders-page__card-rail time { margin-left: auto; font-variant-numeric: tabular-nums; }
|
||||
.aksrt-orders-page__card-status { font-weight: 700; }
|
||||
.aksrt-orders-page__card-status.is-pending { color: var(--color-warning, #c87819); }
|
||||
.aksrt-orders-page__card-status.is-paid { color: var(--color-success, #168560); }
|
||||
.aksrt-orders-page__card-main { display: grid; grid-template-columns: 52px minmax(0, 1fr) auto; gap: 13px; align-items: center; padding: 14px; }
|
||||
.aksrt-orders-page__card-img, .aksrt-orders-page__card-art { width: 52px; height: 52px; border-radius: 10px; object-fit: cover; }
|
||||
.aksrt-orders-page__card-art { display: grid; place-items: center; color: var(--color-primary, #3b82f6); background: color-mix(in srgb, var(--color-primary, #3b82f6) 10%, var(--surface-2, #f4f6f9)); }
|
||||
.aksrt-orders-page__card-copy { min-width: 0; }
|
||||
.aksrt-orders-page__card-copy h3 { display: -webkit-box; margin: 0 0 5px; overflow: hidden; color: var(--orders-ink); font-size: 14px; line-height: 1.35; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
|
||||
.aksrt-orders-page__card-copy p { margin: 0; color: var(--orders-muted); font-family: var(--font-mono, monospace); font-size: 11px; }
|
||||
.aksrt-orders-page__card-value { display: grid; gap: 3px; text-align: right; }
|
||||
.aksrt-orders-page__card-value strong { color: var(--orders-ink); font-size: 15px; font-variant-numeric: tabular-nums; }
|
||||
.aksrt-orders-page__card-value span { color: var(--orders-muted); font-size: 11px; }
|
||||
.aksrt-orders-page__card-actions { display: flex; gap: 7px; align-items: center; justify-content: flex-end; padding: 9px 14px; border-top: 1px solid var(--orders-line); }
|
||||
.aksrt-orders-page__countdown { margin-right: auto; color: var(--color-warning, #c87819); font-size: 11px; font-variant-numeric: tabular-nums; }
|
||||
|
||||
.aksrt-orders-page__pagination { display: flex; justify-content: center; gap: 6px; margin-top: 20px; }
|
||||
.aksrt-orders-page__page-chip { display: grid; width: 31px; height: 31px; padding: 0; place-items: center; border: 1px solid var(--orders-line); border-radius: 8px; background: var(--surface-1, #fff); color: var(--orders-muted); font: inherit; font-size: 12px; text-decoration: none; cursor: pointer; }
|
||||
.aksrt-orders-page__page-chip.is-active { border-color: var(--color-primary, #3b82f6); background: var(--color-primary, #3b82f6); color: #fff; }
|
||||
.aksrt-orders-page [data-aksrt-order-list], .aksrt-orders-page__stats, .aksrt-orders-page__pagination { transition: opacity .16s ease; }
|
||||
.aksrt-orders-page.is-loading [data-aksrt-order-list], .aksrt-orders-page.is-loading .aksrt-orders-page__stats, .aksrt-orders-page.is-loading .aksrt-orders-page__pagination { opacity: .55; pointer-events: none; }
|
||||
.aksrt-orders-page__empty { padding: 52px 20px; border: 1px dashed var(--orders-line); border-radius: 13px; background: var(--surface-2, #f7f8fb); text-align: center; }
|
||||
.aksrt-orders-page__empty i { display: block; margin-bottom: 12px; color: var(--color-primary, #3b82f6); font-size: 30px; }
|
||||
.aksrt-orders-page__empty h3 { margin: 0 0 7px; font-size: 15px; }.aksrt-orders-page__empty p { margin: 0 0 16px; color: var(--orders-muted); font-size: 13px; }
|
||||
|
||||
/* ---- Order detail modal (front-end order center) ---- */
|
||||
/* 弹窗 portal 到 <body>,读不到页面上的 `--orders-*` 别名,因此把本模块的局部
|
||||
别名映射到 modal-runtime.css 声明的共享 portal 基座 `--modal-*`;
|
||||
路由文件不应再自行推导调色板值。 */
|
||||
.aksrt-order-detail-modal {
|
||||
--detail-ink: var(--modal-ink);
|
||||
--detail-muted: var(--modal-muted);
|
||||
--detail-line: var(--modal-line);
|
||||
--detail-surface: var(--modal-surface);
|
||||
--detail-surface-2: var(--modal-surface-2);
|
||||
--detail-surface-3: var(--modal-surface-3);
|
||||
--detail-primary: var(--modal-primary);
|
||||
--detail-warning: var(--modal-warning);
|
||||
--detail-success: var(--modal-success);
|
||||
}
|
||||
|
||||
/* 订单详情弹窗:与售后弹窗统一为 modal-mini 几何(400px);标题区改用蓝色炫彩头,运行时不再渲染默认标题栏。 */
|
||||
.aksrt-order-detail-modal:not(.aksrt-modal-runtime--bottom) .aksrt-modal-runtime__dialog {
|
||||
width: min(400px, calc(100vw - 32px));
|
||||
border-radius: var(--main-radius, 8px);
|
||||
background: var(--main-bg-color, #fff);
|
||||
}
|
||||
/* 滚动发生在 dialog 本体(modal-runtime 的 overflow:auto),内容超高时右缘会露出
|
||||
滚动栏;与售后/申诉弹窗口径一致:完全隐藏滚动条(含细滚动条),滚动行为不变。 */
|
||||
.aksrt-order-detail-modal .aksrt-modal-runtime__dialog { -ms-overflow-style: none; scrollbar-width: none; }
|
||||
.aksrt-order-detail-modal .aksrt-modal-runtime__dialog::-webkit-scrollbar { display: none; }
|
||||
.aksrt-order-detail-modal .aksrt-modal-runtime__content { display: flex; flex-direction: column; gap: 18px; padding: 16px 18px 0; }
|
||||
|
||||
.aksrt-order-detail__skeleton { display: grid; gap: 12px; padding: 4px 0; }
|
||||
|
||||
/* Status banner — a single local variable drives the whole colour scheme. */
|
||||
.aksrt-order-detail__hero { --hero-accent: var(--detail-primary); --hero-text: color-mix(in srgb, var(--hero-accent) 72%, var(--detail-ink)); display: flex; align-items: center; gap: 13px; padding: 15px; border: 1px solid color-mix(in srgb, var(--hero-accent) 20%, transparent); border-radius: 14px; background: color-mix(in srgb, var(--hero-accent) 12%, var(--detail-surface)); }
|
||||
.aksrt-order-detail__hero.is-pending { --hero-accent: var(--detail-warning); }
|
||||
.aksrt-order-detail__hero.is-paid { --hero-accent: var(--detail-success); }
|
||||
.aksrt-order-detail__hero.is-refunded, .aksrt-order-detail__hero.is-closed { --hero-accent: var(--detail-muted); }
|
||||
.aksrt-order-detail__hero-icon { display: grid; flex: 0 0 auto; width: 42px; height: 42px; place-items: center; border-radius: 12px; background: color-mix(in srgb, var(--hero-accent) 16%, transparent); color: var(--hero-text); font-size: 19px; }
|
||||
.aksrt-order-detail__hero-copy { display: grid; flex: 1 1 auto; gap: 4px; min-width: 0; }
|
||||
.aksrt-order-detail__status { color: var(--hero-text); font-size: 15px; font-weight: 750; line-height: 1.2; }
|
||||
.aksrt-order-detail__desc { margin: 0; color: var(--detail-muted); font-size: 12px; line-height: 1.45; }
|
||||
.aksrt-order-detail__countdown { color: var(--hero-text); font-size: 12px; font-variant-numeric: tabular-nums; }
|
||||
.aksrt-order-detail__hero-amount { display: grid; flex: 0 0 auto; gap: 3px; justify-items: end; text-align: right; }
|
||||
.aksrt-order-detail__hero-amount strong { color: var(--detail-ink); font-size: 21px; font-weight: 750; line-height: 1.1; letter-spacing: -.01em; font-variant-numeric: tabular-nums; }
|
||||
.aksrt-order-detail__hero-amount span { color: var(--detail-muted); font-size: 11px; }
|
||||
|
||||
.aksrt-order-detail__group { display: grid; gap: 10px; }
|
||||
.aksrt-order-detail__group-title { display: flex; align-items: center; gap: 8px; margin: 0; color: var(--detail-ink); font-size: 13px; font-weight: 700; line-height: 1.3; }
|
||||
.aksrt-order-detail__group-title::before { content: ""; width: 3px; height: 13px; border-radius: 2px; background: var(--detail-primary); }
|
||||
|
||||
.aksrt-order-detail__product { display: grid; grid-template-columns: 54px minmax(0, 1fr); gap: 12px; align-items: center; padding: 12px; border: 1px solid var(--detail-line); border-radius: 12px; background: var(--detail-surface); }
|
||||
.aksrt-order-detail__thumb { width: 54px; height: 54px; border-radius: 10px; object-fit: cover; }
|
||||
.aksrt-order-detail__thumb--empty { display: grid; place-items: center; background: var(--detail-surface-2); color: var(--detail-primary); font-size: 19px; }
|
||||
.aksrt-order-detail__product-copy { display: grid; gap: 6px; min-width: 0; }
|
||||
.aksrt-order-detail__product-title { display: -webkit-box; overflow: hidden; color: var(--detail-ink); font-size: 14px; line-height: 1.45; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
|
||||
.aksrt-order-detail__product-meta { display: inline-flex; justify-self: start; padding: 2px 8px; border-radius: 999px; background: color-mix(in srgb, var(--detail-primary) 9%, var(--detail-surface-2)); color: var(--detail-primary); font-size: 11px; font-weight: 650; line-height: 1.5; }
|
||||
|
||||
.aksrt-order-detail__list { display: grid; margin: 0; overflow: hidden; border: 1px solid var(--detail-line); border-radius: 12px; background: var(--detail-surface); }
|
||||
.aksrt-order-detail__list > div { display: grid; grid-template-columns: 84px minmax(0, 1fr); align-items: center; gap: 12px; padding: 11px 13px; border-bottom: 1px solid var(--detail-line); }
|
||||
.aksrt-order-detail__list > div:last-child { border-bottom: 0; }
|
||||
.aksrt-order-detail__list dt { color: var(--detail-muted); font-size: 12.5px; }
|
||||
.aksrt-order-detail__list dd { display: flex; align-items: center; justify-content: flex-end; gap: 8px; min-width: 0; margin: 0; color: var(--detail-ink); font-size: 13px; font-variant-numeric: tabular-nums; text-align: right; }
|
||||
.aksrt-order-detail__value-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.aksrt-order-detail__value-text.is-mono { font-family: var(--font-mono, monospace); font-size: 12.5px; letter-spacing: .01em; }
|
||||
.aksrt-order-detail__copy { display: inline-grid; flex: 0 0 auto; width: 24px; height: 24px; padding: 0; place-items: center; border: 1px solid var(--detail-line); border-radius: 7px; background: var(--detail-surface-3); color: var(--detail-muted); font-size: 11px; line-height: 1; cursor: pointer; transition: color .15s ease, border-color .15s ease; }
|
||||
.aksrt-order-detail__copy:hover { border-color: color-mix(in srgb, var(--detail-primary) 45%, var(--detail-line)); color: var(--detail-primary); }
|
||||
.aksrt-order-detail__copy.is-copied { border-color: color-mix(in srgb, var(--detail-success) 45%, var(--detail-line)); color: var(--detail-success); }
|
||||
|
||||
.aksrt-order-detail__refresh-state { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: 10px; padding: 12px 13px; border: 1px solid color-mix(in srgb, var(--detail-warning) 26%, transparent); border-radius: 12px; background: color-mix(in srgb, var(--detail-warning) 10%, var(--detail-surface-2)); color: var(--detail-ink); font-size: 12.5px; }
|
||||
.aksrt-order-detail__refresh-state > i { color: var(--detail-warning); font-size: 15px; }
|
||||
.aksrt-order-detail__refresh-state p { margin: 0; }
|
||||
.aksrt-order-detail__refresh-state button { grid-column: 2; justify-self: start; }
|
||||
|
||||
/* 底部操作区:按钮排布沿用主题 .but-average.modal-buts(禁封申诉弹窗同式),此处仅保留吸底定位与通栏背景。
|
||||
主题 modal-buts 自带 margin-top:16px,吸底场景下用更高优先级清掉,避免内容与吸底条之间出现空隙。 */
|
||||
.aksrt-order-detail-modal .aksrt-order-detail__actions { position: sticky; bottom: 0; z-index: 2; margin: 0 -18px; padding: 13px 18px calc(13px + env(safe-area-inset-bottom)); border-top: 1px solid var(--detail-line); background: var(--detail-surface); }
|
||||
.aksrt-order-detail-modal .aksrt-order-detail__actions.but-average.modal-buts { margin-top: 0; }
|
||||
|
||||
/* Orders extensions */
|
||||
.aksrt-orders-page__extra-nav { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 12px; }
|
||||
.aksrt-orders-page__extra-link { padding: 6px 12px; border-radius: 999px; background: var(--surface-2, #f3f4f6); color: inherit; text-decoration: none; font-size: 13px; }
|
||||
.aksrt-orders-page__extra-link.is-active { background: var(--focus-color, var(--color-primary)); color: #fff; }
|
||||
.aksrt-as-list { display: grid; gap: 12px; }
|
||||
.aksrt-as-card { padding: 14px; border: 1px solid var(--main-border-color, #eee); border-radius: 10px; background: var(--main-bg-color, #fff); }
|
||||
.aksrt-as-card__head { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-bottom: 6px; }
|
||||
.aksrt-as-card__status { margin-left: auto; color: var(--focus-color, #2f6df6); font-weight: 600; }
|
||||
.aksrt-as-timeline { margin: 10px 0 0; padding-left: 18px; color: var(--muted-2-color, #888); font-size: 12px; }
|
||||
.aksrt-as-card__actions { display: flex; gap: 8px; margin-top: 10px; }
|
||||
.aksrt-coupon-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 12px; }
|
||||
.aksrt-coupon-card { padding: 12px; border: 1px dashed var(--focus-color, #2f6df6); border-radius: 10px; background: color-mix(in srgb, var(--focus-color, #2f6df6) 6%, #fff); }
|
||||
.aksrt-coupon-card__value { color: var(--focus-color, #2f6df6); font-size: 20px; font-weight: 700; }
|
||||
.aksrt-coupon-card__code { margin: 6px 0; font-family: ui-monospace, monospace; }
|
||||
.aksrt-coupon-card__meta { margin-bottom: 4px; color: var(--muted-2-color, #888); font-size: 12px; }
|
||||
.aksrt-orders-page__field { margin-bottom: 12px; }
|
||||
.aksrt-orders-page__input, .aksrt-orders-page__select, .aksrt-orders-page__textarea { box-sizing: border-box; width: 100%; padding: 8px 10px; border: 1px solid var(--main-border-color, #ddd); border-radius: 6px; }
|
||||
@media (max-width: 767px) { .uc-orders-panel__head { grid-template-columns: 1fr; gap: 17px; }.aksrt-orders-page__stats { max-width: none; }.aksrt-orders-page__search { flex-wrap: wrap; }.aksrt-orders-page__search input { min-width: 150px; }.aksrt-orders-page__search .btn-primary { margin-left: auto; }.aksrt-orders-page__filter-toggle { margin-left: auto; }.aksrt-orders-page__card-rail time { width: 100%; margin-left: 0; order: 3; }.aksrt-orders-page__card-main { grid-template-columns: 48px minmax(0, 1fr); }.aksrt-orders-page__card-img, .aksrt-orders-page__card-art { width: 48px; height: 48px; }.aksrt-orders-page__card-value { grid-column: 2; text-align: left; }.aksrt-orders-page__card-actions > :not(.aksrt-orders-page__countdown) { flex: 1 1 auto; }.aksrt-orders-page__countdown { width: 100%; }}
|
||||
html.dark .aksrt-orders-page__stat, html.dark .aksrt-orders-page__card { background: var(--surface-1, #1a1d24); } html.dark .aksrt-orders-page__card-rail, html.dark .aksrt-orders-page__filter-panel { background: var(--surface-2, #12151b); }
|
||||
|
||||
/* ---- 售后记录卡片(订单详情弹窗) ---- */
|
||||
.aksrt-order-detail__after-sales { display: grid; gap: 10px; margin: 0; padding: 0; list-style: none; }
|
||||
.aksrt-order-detail__after-sale { --as-accent: var(--detail-muted); display: grid; gap: 10px; padding: 13px 14px; border: 1px solid var(--detail-line); border-left: 3px solid var(--as-accent); border-radius: 12px; background: var(--detail-surface); }
|
||||
.aksrt-order-detail__after-sale.is-pending { --as-accent: var(--detail-warning); }
|
||||
.aksrt-order-detail__after-sale.is-processing { --as-accent: var(--detail-primary); }
|
||||
.aksrt-order-detail__after-sale.is-approved, .aksrt-order-detail__after-sale.is-completed { --as-accent: var(--detail-success); }
|
||||
.aksrt-order-detail__after-sale.is-rejected, .aksrt-order-detail__after-sale.is-cancelled { --as-accent: var(--modal-danger, #ef4444); }
|
||||
.aksrt-order-detail__after-sale-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
|
||||
.aksrt-order-detail__after-sale-badge { padding: 3px 11px; border-radius: 999px; background: color-mix(in srgb, var(--as-accent) 12%, transparent); color: var(--as-accent); font-size: 12px; font-weight: 700; line-height: 1.5; }
|
||||
.aksrt-order-detail__after-sale-head time { color: var(--detail-muted); font-size: 11.5px; font-variant-numeric: tabular-nums; }
|
||||
.aksrt-order-detail__after-sale-reason { margin: 0; color: var(--detail-ink); font-size: 13px; line-height: 1.65; white-space: pre-wrap; word-break: break-word; }
|
||||
.aksrt-order-detail__after-sale-meta { display: grid; margin: 0; overflow: hidden; border: 1px solid var(--detail-line); border-radius: 10px; background: var(--detail-surface-3); }
|
||||
.aksrt-order-detail__after-sale-meta > div { display: grid; grid-template-columns: 76px minmax(0, 1fr); gap: 10px; padding: 9px 12px; border-bottom: 1px solid var(--detail-line); font-size: 12.5px; }
|
||||
.aksrt-order-detail__after-sale-meta > div:last-child { border-bottom: 0; }
|
||||
.aksrt-order-detail__after-sale-meta dt { color: var(--detail-muted); }
|
||||
.aksrt-order-detail__after-sale-meta dd { margin: 0; color: var(--detail-ink); font-size: 12.5px; text-align: right; word-break: break-word; }
|
||||
.aksrt-order-detail__after-sale-field { color: var(--detail-muted); }
|
||||
.aksrt-order-detail__after-sale-foot { display: flex; flex-wrap: wrap; gap: 8px; }
|
||||
+663
-546
File diff suppressed because it is too large.
Load diff
+737
-756
File diff suppressed because it is too large.
Load diff
@@ -150,4 +150,13 @@ $status_options = [
|
||||
<div class="aksrt-orders-page__empty" data-aksrt-order-list><i class="fa fas fa-file-alt" aria-hidden="true"></i><h3><?php esc_html_e('还没有购买记录', 'aksrt'); ?></h3><p><?php esc_html_e('购买内容或开通权益后,订单会出现在这里。', 'aksrt'); ?></p></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
// 售后人机验证挂载点(申请 / 撤销):场景未启用时 aksrt_render_captcha 无输出,
|
||||
// 前端 order-after-sale.js 以挂载点存在与否决定是否先过验证。
|
||||
if (function_exists('aksrt_render_captcha')) {
|
||||
aksrt_render_captcha('after_sale_apply');
|
||||
aksrt_render_captcha('after_sale_cancel');
|
||||
}
|
||||
?>
|
||||
|
||||
</div>
|
||||
+14
-12
@@ -363,18 +363,20 @@ function aksrt_captcha_sanitize_provider_config(string $provider, $raw): array {
|
||||
*/
|
||||
function aksrt_captcha_scenes(): array {
|
||||
return [
|
||||
'comment' => __('发表评论', 'aksrt'),
|
||||
'login' => __('用户登录', 'aksrt'),
|
||||
'register' => __('用户注册', 'aksrt'),
|
||||
'bind' => __('绑定第三方', 'aksrt'),
|
||||
'bind_phone' => __('绑定手机', 'aksrt'),
|
||||
'lostpassword' => __('找回密码', 'aksrt'),
|
||||
'change_password' => __('修改密码', 'aksrt'),
|
||||
'change_email' => __('更改邮箱', 'aksrt'),
|
||||
'download' => __('资源下载', 'aksrt'),
|
||||
'signin' => __('每日签到', 'aksrt'),
|
||||
'front_submit' => __('前台投稿', 'aksrt'),
|
||||
'tip_user' => __('打赏用户', 'aksrt'),
|
||||
'comment' => __('发表评论', 'aksrt'),
|
||||
'login' => __('用户登录', 'aksrt'),
|
||||
'register' => __('用户注册', 'aksrt'),
|
||||
'bind' => __('绑定第三方', 'aksrt'),
|
||||
'bind_phone' => __('绑定手机', 'aksrt'),
|
||||
'lostpassword' => __('找回密码', 'aksrt'),
|
||||
'change_password' => __('修改密码', 'aksrt'),
|
||||
'change_email' => __('更改邮箱', 'aksrt'),
|
||||
'download' => __('资源下载', 'aksrt'),
|
||||
'signin' => __('每日签到', 'aksrt'),
|
||||
'front_submit' => __('前台投稿', 'aksrt'),
|
||||
'tip_user' => __('打赏用户', 'aksrt'),
|
||||
'after_sale_apply' => __('申请售后', 'aksrt'),
|
||||
'after_sale_cancel' => __('撤销售后', 'aksrt'),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -81,6 +81,8 @@ class SchemaPermissions {
|
||||
['label' => '每日签到', 'value' => 'signin'],
|
||||
['label' => '前台投稿', 'value' => 'front_submit'],
|
||||
['label' => '打赏用户', 'value' => 'tip_user'],
|
||||
['label' => '申请售后', 'value' => 'after_sale_apply'],
|
||||
['label' => '撤销售后', 'value' => 'after_sale_cancel'],
|
||||
]],
|
||||
]],
|
||||
['key' => 'providers', 'title' => '服务商配置', 'dependsOn' => ['key' => 'enabled', 'value' => true], 'fields' => $provider_fields],
|
||||
|
||||
+44
-44
@@ -1,44 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Core;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class DBMigrator {
|
||||
use DBMigratorPart2;
|
||||
use DBMigratorPart3;
|
||||
use DBMigratorIdentity;
|
||||
use DBMigratorVip;
|
||||
|
||||
/** Current schema version; changing it re-runs dbDelta. */
|
||||
// 2.5.1: order_aftersale 新增 images 列(售后举证图片)。dbDelta 仅在版本变化时
|
||||
// 重跑,新增/调整列必须同步升版本,否则老安装不会执行 ALTER TABLE。
|
||||
// 2.5.2: order_aftersale 新增 apply_form 列(结构化申请表单:预置原因 / 申请资料 / 申请说明)。
|
||||
const SCHEMA_VERSION = '2.5.2';
|
||||
|
||||
const OPT_KEY = 'aksrt_db_schema_version';
|
||||
|
||||
/**
|
||||
* Register hooks.
|
||||
*/
|
||||
public static function init(): void {
|
||||
add_action('after_switch_theme', [self::class, 'maybe_migrate']);
|
||||
add_action('admin_init', [self::class, 'maybe_migrate']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run table schema upgrade when version mismatches.
|
||||
*/
|
||||
public static function maybe_migrate(): void {
|
||||
$current = get_option(self::OPT_KEY, '');
|
||||
if ($current === self::SCHEMA_VERSION) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::run();
|
||||
update_option(self::OPT_KEY, self::SCHEMA_VERSION);
|
||||
}
|
||||
}
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Core;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class DBMigrator {
|
||||
use DBMigratorPart2;
|
||||
use DBMigratorPart3;
|
||||
use DBMigratorIdentity;
|
||||
use DBMigratorVip;
|
||||
|
||||
/** Current schema version; changing it re-runs dbDelta. */
|
||||
// 2.5.1: order_aftersale 新增 images 列(售后举证图片)。dbDelta 仅在版本变化时
|
||||
// 重跑,新增/调整列必须同步升版本,否则老安装不会执行 ALTER TABLE。
|
||||
// 2.5.2: order_aftersale 新增 apply_form 列(结构化申请表单:预置原因 / 申请资料 / 申请说明)。
|
||||
const SCHEMA_VERSION = '2.5.2';
|
||||
|
||||
const OPT_KEY = 'aksrt_db_schema_version';
|
||||
|
||||
/**
|
||||
* Register hooks.
|
||||
*/
|
||||
public static function init(): void {
|
||||
add_action('after_switch_theme', [self::class, 'maybe_migrate']);
|
||||
add_action('admin_init', [self::class, 'maybe_migrate']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run table schema upgrade when version mismatches.
|
||||
*/
|
||||
public static function maybe_migrate(): void {
|
||||
$current = get_option(self::OPT_KEY, '');
|
||||
if ($current === self::SCHEMA_VERSION) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::run();
|
||||
update_option(self::OPT_KEY, self::SCHEMA_VERSION);
|
||||
}
|
||||
}
|
||||
+564
-564
File diff suppressed because it is too large.
Load diff
+697
-680
File diff suppressed because it is too large.
Load diff
+552
-552
File diff suppressed because it is too large.
Load diff
@@ -1,389 +1,424 @@
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* 「售后策略」功能契约:
|
||||
* 1) 后台 schema(PHP SchemaPayment after_sale 分区)与 SPA JS schema 的键、顺序、
|
||||
* 类型、默认值 1:1 对齐,且 schema 与侧边页均已注册(/payment/after-sale 可达);
|
||||
* 2) 运行时(PaymentRuntime)缺键回退 schema 默认、HTML 走 kses、时效边界正确;
|
||||
* 3) 售后工单:images 列、create 图片清洗与上限、上传路由权限、latest_for_order;
|
||||
* 4) 订单视图模型输出 after_sale 状态(按钮矩阵的唯一数据源);
|
||||
* 5) 前台:申请/超时效/售后详细三个独立弹窗、modal-mini + 蓝色炫彩头、placeholder 与
|
||||
* 图片上传配置化、不可再申请提示原文、订单详情弹窗不再内嵌售后表单(死代码清理)。
|
||||
*/
|
||||
|
||||
const assert = require('node:assert/strict')
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
const { spawnSync } = require('node:child_process')
|
||||
const test = require('node:test')
|
||||
|
||||
const root = path.resolve(__dirname, '..', '..')
|
||||
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8')
|
||||
|
||||
const FIELD_ORDER = [
|
||||
'after_sale_s',
|
||||
'after_sale_time_limit',
|
||||
'after_sale_expired_text',
|
||||
'after_sale_reasons',
|
||||
'after_sale_desc',
|
||||
'after_sale_materials',
|
||||
'after_sale_reason_placeholder',
|
||||
'after_sale_img_s',
|
||||
]
|
||||
|
||||
/** Extract the balanced [...] block assigned to a PHP array key inside a section body. */
|
||||
function phpSection(src, sectionKey) {
|
||||
const start = src.indexOf(`'${sectionKey}' => [`)
|
||||
assert.notEqual(start, -1, `PHP schema has a ${sectionKey} section`)
|
||||
const open = src.indexOf('[', start)
|
||||
let depth = 0
|
||||
for (let i = open; i < src.length; i++) {
|
||||
const ch = src[i]
|
||||
if (ch === "'" || ch === '"') {
|
||||
const quote = ch
|
||||
i++
|
||||
while (i < src.length) {
|
||||
if (src[i] === '\\') { i += 2; continue }
|
||||
if (src[i] === quote) break
|
||||
i++
|
||||
}
|
||||
continue
|
||||
}
|
||||
if (ch === '[') depth++
|
||||
else if (ch === ']') {
|
||||
depth--
|
||||
if (depth === 0) return src.slice(open, i + 1)
|
||||
}
|
||||
}
|
||||
throw new Error(`unbalanced PHP section: ${sectionKey}`)
|
||||
}
|
||||
|
||||
test('after-sale schema is 1:1 between PHP and SPA', () => {
|
||||
const php = phpSection(read('src/Admin/Schema/SchemaPayment.php'), 'after_sale')
|
||||
const js = read('assets/admin/src/schema/sections/payment/afterSale.js')
|
||||
|
||||
assert.match(php, /'key'\s*=>\s*'after_sale'/)
|
||||
assert.match(php, /'title'\s*=>\s*'售后策略'/)
|
||||
assert.match(php, /'option_key'\s*=>\s*'aksrt_settings'/)
|
||||
|
||||
// 字段键、顺序、类型一致(JS 侧 defaults 与 groups 同序,按首次出现去重)
|
||||
const phpKeys = [...php.matchAll(/'key'\s*=>\s*'([a-z0-9_]+)'/g)].map((m) => m[1])
|
||||
const jsKeys = [...js.matchAll(/key:\s*'([a-z0-9_]+)'/g)].map((m) => m[1])
|
||||
const sectionKeys = (keys) => [...new Set(keys.filter((key) => FIELD_ORDER.includes(key)))]
|
||||
assert.deepEqual(sectionKeys(phpKeys), FIELD_ORDER)
|
||||
assert.deepEqual(sectionKeys(jsKeys), FIELD_ORDER)
|
||||
|
||||
for (const [phpType, jsType] of [
|
||||
['switch', 'switch'],
|
||||
['number', 'number'],
|
||||
['textarea', 'textarea'],
|
||||
['group', 'group'],
|
||||
['input', 'input'],
|
||||
]) {
|
||||
assert.ok(php.includes(`'type' => '${phpType}'`), `missing PHP field type ${phpType}`)
|
||||
assert.ok(js.includes(`type: '${jsType}'`), `missing JS field type ${jsType}`)
|
||||
}
|
||||
|
||||
// 默认值一致(开关/时效/占位文案/预置原因/所需材料)
|
||||
assert.match(php, /'after_sale_s', 'label' => '售后功能', 'type' => 'switch', 'default' => true/)
|
||||
assert.match(js, /after_sale_s:\s*true/)
|
||||
assert.match(js, /key: 'after_sale_s', label: '售后功能', type: 'switch', default: true/)
|
||||
assert.match(php, /'after_sale_time_limit', 'label' => '申请售后时效\(天\)', 'type' => 'number', 'default' => 7, 'min' => 0, 'max' => 3650/)
|
||||
assert.match(js, /key: 'after_sale_time_limit', label: '申请售后时效\(天\)', type: 'number', default: 7, min: 0, max: 3650/)
|
||||
assert.match(php, /'after_sale_reason_placeholder', 'label' => '申请说明占位文案', 'type' => 'input', 'default' => '必填,请您详细填写申请原因'/)
|
||||
assert.match(js, /key: 'after_sale_reason_placeholder', label: '申请说明占位文案', type: 'input', default: '必填,请您详细填写申请原因'/)
|
||||
assert.match(php, /'after_sale_img_s', 'label' => '申请售后可上传图片', 'type' => 'switch', 'default' => true/)
|
||||
assert.match(js, /key: 'after_sale_img_s', label: '申请售后可上传图片', type: 'switch', default: true/)
|
||||
|
||||
for (const reason of ['质量问题', '假冒品牌', '其他原因']) {
|
||||
assert.ok(php.includes(`'t' => '${reason}'`), `missing PHP preset reason ${reason}`)
|
||||
assert.ok(js.includes(`{ t: '${reason}' }`), `missing JS preset reason ${reason}`)
|
||||
}
|
||||
for (const material of ['联系邮箱', '联系电话']) {
|
||||
assert.ok(php.includes(`'t' => '${material}'`), `missing PHP material ${material}`)
|
||||
assert.ok(js.includes(`{ t: '${material}' }`), `missing JS material ${material}`)
|
||||
}
|
||||
|
||||
// HTML 配置项(超时效提示 / 申请说明)走 kses 通道
|
||||
assert.match(php, /'after_sale_expired_text', 'label' => '超过时效提示', 'type' => 'textarea', 'sanitize_type' => 'html'/)
|
||||
assert.match(php, /'after_sale_desc', 'label' => '申请售后说明', 'type' => 'textarea', 'sanitize_type' => 'html'/)
|
||||
assert.match(js, /key: 'after_sale_expired_text', label: '超过时效提示', type: 'textarea', sanitize_type: 'html'/)
|
||||
assert.match(js, /key: 'after_sale_desc', label: '申请售后说明', type: 'textarea', sanitize_type: 'html'/)
|
||||
})
|
||||
|
||||
test('after-sale schema and sidebar page are registered under 支付&付费', () => {
|
||||
const schemaIndex = read('assets/admin/src/schema/sections/payment/index.js')
|
||||
assert.match(schemaIndex, /import \{ afterSaleSchema \} from '\.\/afterSale\.js'/)
|
||||
assert.match(schemaIndex, /after_sale:\s*afterSaleSchema/)
|
||||
|
||||
const section = read('assets/admin/src/constants/sections/payment.js')
|
||||
assert.match(section, /key: 'payment-after-sale'/)
|
||||
assert.match(section, /name: 'PaymentAfterSale'/)
|
||||
assert.match(section, /label: '售后策略'/)
|
||||
assert.match(section, /route: '\/payment\/after-sale'/)
|
||||
assert.match(section, /canonicalDomain: 'payment'/)
|
||||
assert.match(section, /section: 'after_sale'/)
|
||||
|
||||
// 侧栏条目位于提现策略之后(支付&付费末位)
|
||||
assert.ok(section.indexOf("key: 'payment-after-sale'") > section.indexOf("key: 'payment-withdrawal'"))
|
||||
|
||||
const schemaRegistry = read('assets/admin/src/schema/index.js')
|
||||
assert.match(schemaRegistry, /paymentSchemas/)
|
||||
})
|
||||
|
||||
test('after-sale runtime resolves defaults, overrides and the expiry boundary matrix', () => {
|
||||
const result = spawnSync(process.env.PHP_BINARY || 'php', [path.join(__dirname, 'after-sale-policy-harness.php')], {
|
||||
cwd: root,
|
||||
encoding: 'utf8',
|
||||
})
|
||||
assert.equal(result.status, 0, result.stderr || result.stdout)
|
||||
const payload = JSON.parse(result.stdout)
|
||||
|
||||
assert.deepEqual(payload.defaults, {
|
||||
enabled: true,
|
||||
time_limit: 7,
|
||||
expired_text: '<div>抱歉,您的订单已超过售后申请时效</div>',
|
||||
reasons: [
|
||||
{ slug: 'c440cdf71825', name: '质量问题' },
|
||||
{ slug: '2ead35a456a0', name: '假冒品牌' },
|
||||
{ slug: 'fba26eab6b2b', name: '其他原因' },
|
||||
],
|
||||
materials: ['联系邮箱', '联系电话'],
|
||||
desc: '',
|
||||
img_enabled: true,
|
||||
reason_placeholder: '必填,请您详细填写申请原因',
|
||||
})
|
||||
|
||||
assert.equal(payload.overridden.enabled, false)
|
||||
assert.equal(payload.overridden.time_limit, 30)
|
||||
assert.equal(payload.overridden.img_enabled, false)
|
||||
assert.equal(payload.overridden.reason_placeholder, '请描述问题')
|
||||
assert.deepEqual(payload.overridden.reasons, [
|
||||
{ slug: 'c440cdf71825', name: '质量问题' },
|
||||
{ slug: '327e0ab68567', name: '发错货' },
|
||||
])
|
||||
assert.deepEqual(payload.overridden.materials, ['联系邮箱'])
|
||||
// HTML 配置项剥离 script,保留 div
|
||||
assert.equal(payload.overridden.expired_text, '<div>已超过时效</div>')
|
||||
assert.equal(payload.overridden.desc, '<div>请保持商品完好</div>')
|
||||
|
||||
assert.deepEqual(payload.expired_ok, {
|
||||
within: true,
|
||||
exact_boundary: true,
|
||||
just_expired: true,
|
||||
long_expired: true,
|
||||
no_limit: true,
|
||||
missing_paid: true,
|
||||
})
|
||||
})
|
||||
|
||||
test('after-sale tickets persist evidence images and expose the latest row', () => {
|
||||
const schema = read('src/Core/DBMigratorPart2.php')
|
||||
assert.match(schema, /CREATE TABLE \{\$prefix\}aksrt_order_aftersale[\s\S]*?images LONGTEXT DEFAULT NULL[\s\S]*?refund_amount/)
|
||||
|
||||
// images 列随 schema 2.5.1 引入:dbDelta 只在版本变化时重跑,列变更必须同步升版本,
|
||||
// 否则老安装不会 ALTER TABLE,写入直接报 Unknown column 'images'。
|
||||
const migrator = read('src/Core/DBMigrator.php')
|
||||
const version = migrator.match(/const SCHEMA_VERSION = '(\d+)\.(\d+)\.(\d+)'/)
|
||||
assert.ok(version, 'DBMigrator exposes a numeric SCHEMA_VERSION')
|
||||
const [major, minor, patch] = version.slice(1).map(Number)
|
||||
assert.ok(
|
||||
major > 2 || (major === 2 && minor > 5) || (major === 2 && minor === 5 && patch >= 1),
|
||||
`SCHEMA_VERSION must be at least 2.5.1 after adding order_aftersale.images, got ${major}.${minor}.${patch}`
|
||||
)
|
||||
|
||||
const service = read('src/Payment/AfterSaleService.php')
|
||||
assert.match(service, /public function create\(int \$user_id, string \$order_number, string \$reason, array \$images = \[\], array \$apply_form = \[\]\)/)
|
||||
// 结构化申请表单:预置原因 / 申请资料 / 申请说明独立入库(reason 融合串保持原样)
|
||||
assert.match(service, /\$apply_form = \$this->sanitize_apply_form\(\$apply_form\);/)
|
||||
assert.match(service, /private function sanitize_apply_form\(array \$form\): array/)
|
||||
assert.match(service, /'apply_form' => \$apply_form === \[\] \? null : wp_json_encode\(\$apply_form/)
|
||||
assert.match(service, /\$images = \$this->sanitize_images\(\$images\);/)
|
||||
assert.match(service, /'images' => wp_json_encode\(\$images\)/)
|
||||
assert.match(service, /private function sanitize_images\(array \$images\): array \{[\s\S]*?count\(\$clean\) >= 3[\s\S]*?esc_url_raw/)
|
||||
assert.match(service, /public function latest_for_order\(string \$order_number\): \?array \{[\s\S]*?ORDER BY id DESC LIMIT 1/)
|
||||
// normalize 输出 images 数组(坏 JSON / 空串回落空数组)
|
||||
assert.match(service, /json_decode\(\(string\) \(\$row\['images'\] \?\? ''\), true\)/)
|
||||
|
||||
// 图片上传路由:登录 + nonce + 售后/图片开关 + 订单归属;回调委托评论图片上传
|
||||
assert.match(service, /\/orders\/' \. \$number \. '\/after-sale\/upload-image'/)
|
||||
assert.match(service, /public function require_after_sale_image\(\\WP_REST_Request \$request\)/)
|
||||
assert.match(service, /get_after_sale_settings\(\)[\s\S]*?empty\(\$settings\['enabled'\]\)[\s\S]*?empty\(\$settings\['img_enabled'\]\)/)
|
||||
assert.match(service, /get_user_order\(get_current_user_id\(\), \$number\)/)
|
||||
assert.match(service, /UserController::get_instance\(\)->rest_upload_comment_image\(\$request\)/)
|
||||
assert.match(service, /'images' => \['required' => false, 'type' => 'array'[\s\S]*?count\(\$value\) <= 3/)
|
||||
|
||||
// 提交 REST 透传 images
|
||||
// rest_submit 同步上报结构化申请表单(reason_label / materials / description)
|
||||
assert.match(service, /'reason_label' => \(string\) \$request->get_param\('reason_label'\),/)
|
||||
assert.match(service, /'materials' +\=> is_array\(\$materials\) \? \$materials : \[\],/)
|
||||
assert.match(service, /'description' +\=> \(string\) \$request->get_param\('description'\),/)
|
||||
|
||||
// 既有售后资产契约(锁 / 退款 / 完成)不受影响
|
||||
assert.match(service, /GET_LOCK\(%s, 5\)/)
|
||||
assert.match(service, /Order::STATUS_PAID/)
|
||||
})
|
||||
|
||||
test('order view model exposes the after-sale button matrix', () => {
|
||||
const userOrders = read('src/Payment/UserOrders.php')
|
||||
assert.match(userOrders, /private function after_sale_state\(string \$group, string \$order_number, string \$paid_at, \?AfterSaleService \$service, array \$records = \[\]\): array \{/)
|
||||
|
||||
assert.match(userOrders, /'after_sale' => \$after_sale_state,/)
|
||||
assert.match(userOrders, /'enabled'\s*=> \$enabled,/)
|
||||
assert.match(userOrders, /'expired'\s*=> \(bool\) \$expired,/)
|
||||
assert.match(userOrders, /'has_application' => \$has_application,/)
|
||||
assert.match(userOrders, /'open'\s*=> \$open,/)
|
||||
assert.match(userOrders, /'can_apply'\s*=> \$enabled && !\$expired && !\$open,/)
|
||||
// 可再次申请售后按工单状态推理:rejected(被拒绝)/ cancelled(已取消)允许再次申请;
|
||||
// pending / processing / approved(流转中)与 completed(已完结)不允许
|
||||
assert.match(userOrders, /\$reapplyable_statuses = \[AfterSaleService::STATUS_REJECTED, AfterSaleService::STATUS_CANCELLED\];/)
|
||||
assert.match(userOrders, /'can_reapply'\s*=> \$enabled && !\$expired && \$has_application && in_array\(\$latest_status, \$reapplyable_statuses, true\),/)
|
||||
assert.match(userOrders, /'latest'\s*=> \$has_application \? \[/)
|
||||
assert.match(userOrders, /'apply_form'\s*=> is_array\(\$latest\['apply_form'\] \?\? null\) \? \$latest\['apply_form'\] : null,/)
|
||||
|
||||
// 仅已支付订单开放;开关关闭整体不处理售后
|
||||
assert.match(userOrders, /\$enabled = \$group === OrderCenterQuery::STATUS_PAID && !empty\(\$settings\['enabled'\]\);/)
|
||||
// 时效以支付时间为准
|
||||
assert.match(userOrders, /\$this->after_sale_state\(\$group, \$order_number, \(string\) \(\$order\['paid_at'\] \?\? ''\), \$after_sale_service, \$after_sales\)/)
|
||||
// 列表模式单行查询,详情模式复用全量工单
|
||||
assert.match(userOrders, /\$after_sales = \$detail && \$after_sale_service \? \$after_sale_service->list_for_order\(\$order_number\) : \[\];/)
|
||||
assert.match(userOrders, /\$latest = \$service->latest_for_order\(\$order_number\);/)
|
||||
assert.match(userOrders, /\$view\['after_sales'\] = \$after_sales;/)
|
||||
|
||||
// 旧 can_after_sale 双路已移除
|
||||
assert.ok(!userOrders.includes('can_after_sale'), 'can_after_sale must be replaced by the after_sale state')
|
||||
})
|
||||
|
||||
test('frontend after-sale modals are standalone and config-driven', () => {
|
||||
const js = read('assets/js/order-after-sale.js')
|
||||
|
||||
// 三个独立弹窗 + modal-mini 几何 + 蓝色炫彩标题区
|
||||
assert.match(js, /className: 'aksrt-after-sale-apply-modal'/)
|
||||
assert.match(js, /className: 'aksrt-after-sale-detail-modal'/)
|
||||
assert.match(js, /className: 'aksrt-after-sale-expired-modal'/)
|
||||
assert.match(js, /modal-colorful-header colorful-bg jb-blue/)
|
||||
assert.match(js, /AksrtModalRuntime\.open\(\{/)
|
||||
|
||||
// 滚动容器与禁封申诉弹窗同口径:no-scrollbar 完全隐藏滚动条(含细滚动条),
|
||||
// 避免 modal-mini 弹窗边缘出现可见滚动栏。
|
||||
assert.match(js, /'<div class="scroll-y no-scrollbar max-vh5">'/)
|
||||
assert.match(js, /'<div class="scroll-y no-scrollbar max-vh5 aksrt-after-sale-detail__body">'/)
|
||||
assert.ok(!js.includes('mini-scrollbar'), 'after-sale modals must not render a visible scrollbar')
|
||||
|
||||
// 申请表单:预置原因徽章、所需资料、必填申请说明(placeholder 取后台配置)
|
||||
assert.match(js, /reasonOptions\(\)/)
|
||||
assert.match(js, /materialOptions\(\)/)
|
||||
assert.match(js, /data-aksrt-after-sale-reason=/)
|
||||
assert.match(js, /data-aksrt-after-sale-material=/)
|
||||
assert.match(js, /var placeholder = String\(cfg\.reasonPlaceholder \|\| '必填,请您详细填写申请原因'\)/)
|
||||
assert.match(js, /name="reason"/)
|
||||
assert.match(js, /notyf\('请填写申请说明', 'danger'\)/)
|
||||
assert.match(js, /notyf\('请选择售后原因', 'danger'\)/)
|
||||
|
||||
// 图片上传:开关控制渲染,最多 3 张,上传/删除/提交
|
||||
assert.match(js, /if \(cfg\.imgEnabled\) \{/)
|
||||
assert.match(js, /MAX_IMAGES = 3/)
|
||||
assert.match(js, /\/after-sale\/upload-image'/)
|
||||
assert.match(js, /data-aksrt-after-sale-image-remove/)
|
||||
assert.match(js, /images: collectImages\(shell\)/)
|
||||
assert.match(js, /reason_label: selectedReasonLabel\(shell\)/)
|
||||
assert.match(js, /materials: collectMaterials\(shell\)/)
|
||||
assert.match(js, /description: reasonText,/)
|
||||
|
||||
// 申请说明(HTML)+ 超时效提示(HTML)由后台配置直出
|
||||
assert.match(js, /var desc = String\(cfg\.desc \|\| ''\)/)
|
||||
assert.match(js, /var text = String\(settings\(\)\.expiredText \|\| '抱歉,您的订单已超过售后申请时效'\)/)
|
||||
|
||||
// 售后详细:状态徽章 / 明细行 / 图片缩略图;不可再申请提示原文 + 我知道了
|
||||
// 申请表单分行展示:原因 / 申请资料 / 申请说明各自独立成行(与申请时间同格式);
|
||||
// 老数据无 apply_form 时兜底展示融合串
|
||||
assert.match(js, /detailRow\('原因', String\(applyForm\.reason_label \|\| ''\)\)/)
|
||||
assert.match(js, /detailRow\('申请说明', String\(applyForm\.description \|\| ''\)\)/)
|
||||
assert.match(js, /STATUS_LABELS = \{[\s\S]*?pending: '待审核'[\s\S]*?completed: '已完成',/)
|
||||
// 不可再申请:不再展示阻断文案,仅保留关闭入口;可再申请:仅保留单个主按钮(无「关闭」)
|
||||
assert.ok(!js.includes('暂不支持再次申请售后'), 'blocked notice copy must be removed')
|
||||
assert.ok(!js.includes('REAPPLY_BLOCKED_TEXT'), 'blocked text constant must be removed')
|
||||
assert.ok(!js.includes('>关闭</button>'), 'detail modal must not render a 关闭 button')
|
||||
assert.match(js, /data-aksrt-modal-close>我知道了</)
|
||||
assert.match(js, /data-aksrt-after-sale-reapply data-aksrt-after-sale-payload="/)
|
||||
|
||||
// 提交后关闭弹窗并刷新订单列表
|
||||
assert.match(js, /AKSRTOrderCenter\.refresh\(orderNumber\)/)
|
||||
assert.match(js, /window\.AksrtModalRuntime\.close\('submit'\)/)
|
||||
|
||||
// 入口委托:申请 / 售后详细
|
||||
assert.match(js, /data-aksrt-after-sale-apply/)
|
||||
assert.match(js, /data-aksrt-after-sale-detail/)
|
||||
assert.match(js, /auto_fun\.aksrt-after-sale/)
|
||||
})
|
||||
|
||||
test('order center drops the embedded after-sale form and exposes refresh()', () => {
|
||||
const js = read('assets/js/order-center.js')
|
||||
|
||||
// 内嵌售后表单与两个 submit 处理器已彻底移除(死代码清理)
|
||||
assert.ok(!js.includes('data-aksrt-after-sale-form'), 'embedded after-sale form must be removed')
|
||||
assert.ok(!js.includes('提交售后申请'), 'embedded after-sale submit copy must be removed')
|
||||
assert.ok(!js.includes('onDocumentSubmit'), 'document submit handler for the after-sale form must be removed')
|
||||
|
||||
// 列表卡片与订单详情弹窗底部共用新的售后按钮
|
||||
assert.match(js, /function afterSaleButtons\(order\)/)
|
||||
assert.match(js, /function afterSalePayload\(order\)/)
|
||||
assert.match(js, /data-aksrt-after-sale-detail="' \+ payload \+ '"/)
|
||||
assert.match(js, /data-aksrt-after-sale-apply="' \+ payload \+ '"/)
|
||||
assert.match(js, /if \(afterSale\.enabled\) html \+= afterSaleButtons\(order\);/)
|
||||
assert.ok(!js.includes('\n html += afterSaleButtons(order);\n'), 'detail modal footer must not render the shared after-sale buttons')
|
||||
// 底部按钮与禁封申诉弹窗同式(but-average modal-buts);「售后详细」入口收拢到记录卡片
|
||||
assert.match(js, /aksrt-order-detail__actions but-average modal-buts/)
|
||||
assert.match(js, /data-aksrt-after-sale-apply="' \+ afterSalePayload\(order\) \+ '"/)
|
||||
assert.ok(js.includes('查看详细</button>'), 'record card has a view-detail entry')
|
||||
assert.match(js, /data-aksrt-after-sale-detail="' \+ recordPayload \+ '"/)
|
||||
// 记录卡片:原因 / 申请理由分行(新数据走 apply_form,老数据兜底融合串)
|
||||
assert.ok(js.includes('aksrt-order-detail__after-sale-field">原因:'), 'record card shows 原因:')
|
||||
assert.ok(js.includes('aksrt-order-detail__after-sale-field">申请理由:'), 'record card shows 申请理由:')
|
||||
assert.match(js, /data-aksrt-after-sale-detail="/)
|
||||
assert.match(js, /data-aksrt-after-sale-apply="/)
|
||||
|
||||
// 售后记录区与取消申请保留在订单详情弹窗
|
||||
assert.match(js, /售后记录/)
|
||||
assert.match(js, /data-aksrt-after-sale-cancel/)
|
||||
|
||||
// refresh 供售后弹窗提交后刷新列表与统计
|
||||
assert.match(js, /function refresh\(orderNumber\) \{/)
|
||||
assert.match(js, /window\.AKSRTOrderCenter = \{ init: init, destroy: destroy, refresh: refresh \}/)
|
||||
assert.match(js, /liveStates\.forEach\(function \(state\) \{/)
|
||||
})
|
||||
|
||||
test('after-sale assets, template buttons and runtime data are wired', () => {
|
||||
const scripts = read('src/Core/ScriptsPart1b.php')
|
||||
assert.match(scripts, /'aksrt-order-after-sale'/)
|
||||
assert.match(scripts, /\/assets\/js\/order-after-sale\.js'/)
|
||||
|
||||
const scripts1c = read('src/Core/ScriptsPart1c.php')
|
||||
assert.match(scripts1c, /'afterSale'\s*=> \(function \(\) \{/)
|
||||
assert.match(scripts1c, /PaymentRuntime::get_after_sale_settings\(\)/)
|
||||
assert.match(scripts1c, /'maxImages'\s*=> 3/)
|
||||
|
||||
const template = read('inc/helpers/user-center-renderers/profile-orders.php')
|
||||
assert.match(template, /\$after_sale = is_array\(\$order\['after_sale'\] \?\? null\) \? \$order\['after_sale'\] : \[\];/)
|
||||
// 载荷随按钮渲染(order-after_sale.js 同步消费),点击即弹窗、无前置请求
|
||||
assert.match(template, /\$after_sale_payload = wp_json_encode\(\[/)
|
||||
assert.match(template, /'after_sale'\s*=> \$after_sale,/)
|
||||
assert.match(template, /data-aksrt-after-sale-detail="<\?php echo esc_attr\(\$after_sale_payload\);/)
|
||||
assert.match(template, /data-aksrt-after-sale-apply="<\?php echo esc_attr\(\$after_sale_payload\);/)
|
||||
assert.match(template, /esc_html_e\('售后详细', 'aksrt'\)/)
|
||||
assert.ok(!template.includes('can_after_sale'), 'template must not use the removed can_after_sale flag')
|
||||
|
||||
const css = read('assets/css/frontend/modal-runtime.css')
|
||||
assert.match(css, /\.aksrt-after-sale-apply-modal:not\(\.aksrt-modal-runtime--bottom\) \.aksrt-modal-runtime__dialog/)
|
||||
assert.match(css, /\.aksrt-after-sale-detail-modal:not\(\.aksrt-modal-runtime--bottom\) \.aksrt-modal-runtime__dialog/)
|
||||
assert.match(css, /\.aksrt-after-sale-expired-modal:not\(\.aksrt-modal-runtime--bottom\) \.aksrt-modal-runtime__dialog/)
|
||||
assert.match(css, /width: min\(400px, calc\(100vw - 32px\)\)/)
|
||||
assert.match(css, /\.aksrt-after-sale-apply-modal \.modal-colorful-header/)
|
||||
assert.match(css, /\.aksrt-after-sale-apply-modal \.colorful-bg > \.colorful-make/)
|
||||
|
||||
const field = read('assets/admin/src/components/form/AksField.vue')
|
||||
assert.match(field, /import draggable from 'vuedraggable'/)
|
||||
assert.match(field, /v-model="repeaterDragList"/)
|
||||
assert.match(field, /handle="\.aksrt-field__repeater-handle"/)
|
||||
assert.match(field, /function repeaterDragKey\(item\)/)
|
||||
})
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* 「售后策略」功能契约:
|
||||
* 1) 后台 schema(PHP SchemaPayment after_sale 分区)与 SPA JS schema 的键、顺序、
|
||||
* 类型、默认值 1:1 对齐,且 schema 与侧边页均已注册(/payment/after-sale 可达);
|
||||
* 2) 运行时(PaymentRuntime)缺键回退 schema 默认、HTML 走 kses、时效边界正确;
|
||||
* 3) 售后工单:images 列、create 图片清洗与上限、上传路由权限、latest_for_order;
|
||||
* 4) 订单视图模型输出 after_sale 状态(按钮矩阵的唯一数据源);
|
||||
* 5) 前台:申请/超时效/售后详细三个独立弹窗、modal-mini + 蓝色炫彩头、placeholder 与
|
||||
* 图片上传配置化、不可再申请提示原文、订单详情弹窗不再内嵌售后表单(死代码清理)。
|
||||
*/
|
||||
|
||||
const assert = require('node:assert/strict')
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
const { spawnSync } = require('node:child_process')
|
||||
const test = require('node:test')
|
||||
|
||||
const root = path.resolve(__dirname, '..', '..')
|
||||
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8')
|
||||
|
||||
const FIELD_ORDER = [
|
||||
'after_sale_s',
|
||||
'after_sale_time_limit',
|
||||
'after_sale_expired_text',
|
||||
'after_sale_reasons',
|
||||
'after_sale_desc',
|
||||
'after_sale_materials',
|
||||
'after_sale_reason_placeholder',
|
||||
'after_sale_img_s',
|
||||
]
|
||||
|
||||
/** Extract the balanced [...] block assigned to a PHP array key inside a section body. */
|
||||
function phpSection(src, sectionKey) {
|
||||
const start = src.indexOf(`'${sectionKey}' => [`)
|
||||
assert.notEqual(start, -1, `PHP schema has a ${sectionKey} section`)
|
||||
const open = src.indexOf('[', start)
|
||||
let depth = 0
|
||||
for (let i = open; i < src.length; i++) {
|
||||
const ch = src[i]
|
||||
if (ch === "'" || ch === '"') {
|
||||
const quote = ch
|
||||
i++
|
||||
while (i < src.length) {
|
||||
if (src[i] === '\\') { i += 2; continue }
|
||||
if (src[i] === quote) break
|
||||
i++
|
||||
}
|
||||
continue
|
||||
}
|
||||
if (ch === '[') depth++
|
||||
else if (ch === ']') {
|
||||
depth--
|
||||
if (depth === 0) return src.slice(open, i + 1)
|
||||
}
|
||||
}
|
||||
throw new Error(`unbalanced PHP section: ${sectionKey}`)
|
||||
}
|
||||
|
||||
test('after-sale schema is 1:1 between PHP and SPA', () => {
|
||||
const php = phpSection(read('src/Admin/Schema/SchemaPayment.php'), 'after_sale')
|
||||
const js = read('assets/admin/src/schema/sections/payment/afterSale.js')
|
||||
|
||||
assert.match(php, /'key'\s*=>\s*'after_sale'/)
|
||||
assert.match(php, /'title'\s*=>\s*'售后策略'/)
|
||||
assert.match(php, /'option_key'\s*=>\s*'aksrt_settings'/)
|
||||
|
||||
// 字段键、顺序、类型一致(JS 侧 defaults 与 groups 同序,按首次出现去重)
|
||||
const phpKeys = [...php.matchAll(/'key'\s*=>\s*'([a-z0-9_]+)'/g)].map((m) => m[1])
|
||||
const jsKeys = [...js.matchAll(/key:\s*'([a-z0-9_]+)'/g)].map((m) => m[1])
|
||||
const sectionKeys = (keys) => [...new Set(keys.filter((key) => FIELD_ORDER.includes(key)))]
|
||||
assert.deepEqual(sectionKeys(phpKeys), FIELD_ORDER)
|
||||
assert.deepEqual(sectionKeys(jsKeys), FIELD_ORDER)
|
||||
|
||||
for (const [phpType, jsType] of [
|
||||
['switch', 'switch'],
|
||||
['number', 'number'],
|
||||
['textarea', 'textarea'],
|
||||
['group', 'group'],
|
||||
['input', 'input'],
|
||||
]) {
|
||||
assert.ok(php.includes(`'type' => '${phpType}'`), `missing PHP field type ${phpType}`)
|
||||
assert.ok(js.includes(`type: '${jsType}'`), `missing JS field type ${jsType}`)
|
||||
}
|
||||
|
||||
// 默认值一致(开关/时效/占位文案/预置原因/所需材料)
|
||||
assert.match(php, /'after_sale_s', 'label' => '售后功能', 'type' => 'switch', 'default' => true/)
|
||||
assert.match(js, /after_sale_s:\s*true/)
|
||||
assert.match(js, /key: 'after_sale_s', label: '售后功能', type: 'switch', default: true/)
|
||||
assert.match(php, /'after_sale_time_limit', 'label' => '申请售后时效\(天\)', 'type' => 'number', 'default' => 7, 'min' => 0, 'max' => 3650/)
|
||||
assert.match(js, /key: 'after_sale_time_limit', label: '申请售后时效\(天\)', type: 'number', default: 7, min: 0, max: 3650/)
|
||||
assert.match(php, /'after_sale_reason_placeholder', 'label' => '申请说明占位文案', 'type' => 'input', 'default' => '必填,请您详细填写申请原因'/)
|
||||
assert.match(js, /key: 'after_sale_reason_placeholder', label: '申请说明占位文案', type: 'input', default: '必填,请您详细填写申请原因'/)
|
||||
assert.match(php, /'after_sale_img_s', 'label' => '申请售后可上传图片', 'type' => 'switch', 'default' => true/)
|
||||
assert.match(js, /key: 'after_sale_img_s', label: '申请售后可上传图片', type: 'switch', default: true/)
|
||||
|
||||
for (const reason of ['质量问题', '假冒品牌', '其他原因']) {
|
||||
assert.ok(php.includes(`'t' => '${reason}'`), `missing PHP preset reason ${reason}`)
|
||||
assert.ok(js.includes(`{ t: '${reason}' }`), `missing JS preset reason ${reason}`)
|
||||
}
|
||||
for (const material of ['联系邮箱', '联系电话']) {
|
||||
assert.ok(php.includes(`'t' => '${material}'`), `missing PHP material ${material}`)
|
||||
assert.ok(js.includes(`{ t: '${material}' }`), `missing JS material ${material}`)
|
||||
}
|
||||
|
||||
// HTML 配置项(超时效提示 / 申请说明)走 kses 通道
|
||||
assert.match(php, /'after_sale_expired_text', 'label' => '超过时效提示', 'type' => 'textarea', 'sanitize_type' => 'html'/)
|
||||
assert.match(php, /'after_sale_desc', 'label' => '申请售后说明', 'type' => 'textarea', 'sanitize_type' => 'html'/)
|
||||
assert.match(js, /key: 'after_sale_expired_text', label: '超过时效提示', type: 'textarea', sanitize_type: 'html'/)
|
||||
assert.match(js, /key: 'after_sale_desc', label: '申请售后说明', type: 'textarea', sanitize_type: 'html'/)
|
||||
})
|
||||
|
||||
test('after-sale schema and sidebar page are registered under 支付&付费', () => {
|
||||
const schemaIndex = read('assets/admin/src/schema/sections/payment/index.js')
|
||||
assert.match(schemaIndex, /import \{ afterSaleSchema \} from '\.\/afterSale\.js'/)
|
||||
assert.match(schemaIndex, /after_sale:\s*afterSaleSchema/)
|
||||
|
||||
const section = read('assets/admin/src/constants/sections/payment.js')
|
||||
assert.match(section, /key: 'payment-after-sale'/)
|
||||
assert.match(section, /name: 'PaymentAfterSale'/)
|
||||
assert.match(section, /label: '售后策略'/)
|
||||
assert.match(section, /route: '\/payment\/after-sale'/)
|
||||
assert.match(section, /canonicalDomain: 'payment'/)
|
||||
assert.match(section, /section: 'after_sale'/)
|
||||
|
||||
// 侧栏条目位于提现策略之后(支付&付费末位)
|
||||
assert.ok(section.indexOf("key: 'payment-after-sale'") > section.indexOf("key: 'payment-withdrawal'"))
|
||||
|
||||
const schemaRegistry = read('assets/admin/src/schema/index.js')
|
||||
assert.match(schemaRegistry, /paymentSchemas/)
|
||||
})
|
||||
|
||||
test('after-sale runtime resolves defaults, overrides and the expiry boundary matrix', () => {
|
||||
const result = spawnSync(process.env.PHP_BINARY || 'php', [path.join(__dirname, 'after-sale-policy-harness.php')], {
|
||||
cwd: root,
|
||||
encoding: 'utf8',
|
||||
})
|
||||
assert.equal(result.status, 0, result.stderr || result.stdout)
|
||||
const payload = JSON.parse(result.stdout)
|
||||
|
||||
assert.deepEqual(payload.defaults, {
|
||||
enabled: true,
|
||||
time_limit: 7,
|
||||
expired_text: '<div>抱歉,您的订单已超过售后申请时效</div>',
|
||||
reasons: [
|
||||
{ slug: 'c440cdf71825', name: '质量问题' },
|
||||
{ slug: '2ead35a456a0', name: '假冒品牌' },
|
||||
{ slug: 'fba26eab6b2b', name: '其他原因' },
|
||||
],
|
||||
materials: ['联系邮箱', '联系电话'],
|
||||
desc: '',
|
||||
img_enabled: true,
|
||||
reason_placeholder: '必填,请您详细填写申请原因',
|
||||
})
|
||||
|
||||
assert.equal(payload.overridden.enabled, false)
|
||||
assert.equal(payload.overridden.time_limit, 30)
|
||||
assert.equal(payload.overridden.img_enabled, false)
|
||||
assert.equal(payload.overridden.reason_placeholder, '请描述问题')
|
||||
assert.deepEqual(payload.overridden.reasons, [
|
||||
{ slug: 'c440cdf71825', name: '质量问题' },
|
||||
{ slug: '327e0ab68567', name: '发错货' },
|
||||
])
|
||||
assert.deepEqual(payload.overridden.materials, ['联系邮箱'])
|
||||
// HTML 配置项剥离 script,保留 div
|
||||
assert.equal(payload.overridden.expired_text, '<div>已超过时效</div>')
|
||||
assert.equal(payload.overridden.desc, '<div>请保持商品完好</div>')
|
||||
|
||||
assert.deepEqual(payload.expired_ok, {
|
||||
within: true,
|
||||
exact_boundary: true,
|
||||
just_expired: true,
|
||||
long_expired: true,
|
||||
no_limit: true,
|
||||
missing_paid: true,
|
||||
})
|
||||
})
|
||||
|
||||
test('after-sale tickets persist evidence images and expose the latest row', () => {
|
||||
const schema = read('src/Core/DBMigratorPart2.php')
|
||||
assert.match(schema, /CREATE TABLE \{\$prefix\}aksrt_order_aftersale[\s\S]*?images LONGTEXT DEFAULT NULL[\s\S]*?refund_amount/)
|
||||
|
||||
// images 列随 schema 2.5.1 引入:dbDelta 只在版本变化时重跑,列变更必须同步升版本,
|
||||
// 否则老安装不会 ALTER TABLE,写入直接报 Unknown column 'images'。
|
||||
const migrator = read('src/Core/DBMigrator.php')
|
||||
const version = migrator.match(/const SCHEMA_VERSION = '(\d+)\.(\d+)\.(\d+)'/)
|
||||
assert.ok(version, 'DBMigrator exposes a numeric SCHEMA_VERSION')
|
||||
const [major, minor, patch] = version.slice(1).map(Number)
|
||||
assert.ok(
|
||||
major > 2 || (major === 2 && minor > 5) || (major === 2 && minor === 5 && patch >= 1),
|
||||
`SCHEMA_VERSION must be at least 2.5.1 after adding order_aftersale.images, got ${major}.${minor}.${patch}`
|
||||
)
|
||||
|
||||
const service = read('src/Payment/AfterSaleService.php')
|
||||
assert.match(service, /public function create\(int \$user_id, string \$order_number, string \$reason, array \$images = \[\], array \$apply_form = \[\]\)/)
|
||||
// 结构化申请表单:预置原因 / 申请资料 / 申请说明独立入库(reason 融合串保持原样)
|
||||
assert.match(service, /\$apply_form = \$this->sanitize_apply_form\(\$apply_form\);/)
|
||||
assert.match(service, /private function sanitize_apply_form\(array \$form\): array/)
|
||||
assert.match(service, /'apply_form' => \$apply_form === \[\] \? null : wp_json_encode\(\$apply_form/)
|
||||
assert.match(service, /\$images = \$this->sanitize_images\(\$images\);/)
|
||||
assert.match(service, /'images' => wp_json_encode\(\$images\)/)
|
||||
assert.match(service, /private function sanitize_images\(array \$images\): array \{[\s\S]*?count\(\$clean\) >= 3[\s\S]*?esc_url_raw/)
|
||||
assert.match(service, /public function latest_for_order\(string \$order_number\): \?array \{[\s\S]*?ORDER BY id DESC LIMIT 1/)
|
||||
// normalize 输出 images 数组(坏 JSON / 空串回落空数组)
|
||||
assert.match(service, /json_decode\(\(string\) \(\$row\['images'\] \?\? ''\), true\)/)
|
||||
|
||||
// 图片上传路由:登录 + nonce + 售后/图片开关 + 订单归属;回调委托评论图片上传
|
||||
assert.match(service, /\/orders\/' \. \$number \. '\/after-sale\/upload-image'/)
|
||||
assert.match(service, /public function require_after_sale_image\(\\WP_REST_Request \$request\)/)
|
||||
assert.match(service, /get_after_sale_settings\(\)[\s\S]*?empty\(\$settings\['enabled'\]\)[\s\S]*?empty\(\$settings\['img_enabled'\]\)/)
|
||||
assert.match(service, /get_user_order\(get_current_user_id\(\), \$number\)/)
|
||||
assert.match(service, /UserController::get_instance\(\)->rest_upload_comment_image\(\$request\)/)
|
||||
assert.match(service, /'images' => \['required' => false, 'type' => 'array'[\s\S]*?count\(\$value\) <= 3/)
|
||||
|
||||
// 提交 REST 透传 images
|
||||
// rest_submit 同步上报结构化申请表单(reason_label / materials / description)
|
||||
assert.match(service, /'reason_label' => \(string\) \$request->get_param\('reason_label'\),/)
|
||||
assert.match(service, /'materials' +\=> is_array\(\$materials\) \? \$materials : \[\],/)
|
||||
assert.match(service, /'description' +\=> \(string\) \$request->get_param\('description'\),/)
|
||||
|
||||
// 既有售后资产契约(锁 / 退款 / 完成)不受影响
|
||||
assert.match(service, /GET_LOCK\(%s, 5\)/)
|
||||
assert.match(service, /Order::STATUS_PAID/)
|
||||
})
|
||||
|
||||
test('order view model exposes the after-sale button matrix', () => {
|
||||
const userOrders = read('src/Payment/UserOrders.php')
|
||||
assert.match(userOrders, /private function after_sale_state\(string \$group, string \$order_number, string \$paid_at, \?AfterSaleService \$service, array \$records = \[\]\): array \{/)
|
||||
|
||||
assert.match(userOrders, /'after_sale' => \$after_sale_state,/)
|
||||
assert.match(userOrders, /'enabled'\s*=> \$enabled,/)
|
||||
assert.match(userOrders, /'expired'\s*=> \(bool\) \$expired,/)
|
||||
assert.match(userOrders, /'has_application' => \$has_application,/)
|
||||
assert.match(userOrders, /'open'\s*=> \$open,/)
|
||||
assert.match(userOrders, /'can_apply'\s*=> \$enabled && !\$expired && !\$open,/)
|
||||
// 可再次申请售后按工单状态推理:rejected(被拒绝)/ cancelled(已取消)允许再次申请;
|
||||
// pending / processing / approved(流转中)与 completed(已完结)不允许
|
||||
assert.match(userOrders, /\$reapplyable_statuses = \[AfterSaleService::STATUS_REJECTED, AfterSaleService::STATUS_CANCELLED\];/)
|
||||
assert.match(userOrders, /'can_reapply'\s*=> \$enabled && !\$expired && \$has_application && in_array\(\$latest_status, \$reapplyable_statuses, true\),/)
|
||||
assert.match(userOrders, /'latest'\s*=> \$has_application \? \[/)
|
||||
assert.match(userOrders, /'apply_form'\s*=> is_array\(\$latest\['apply_form'\] \?\? null\) \? \$latest\['apply_form'\] : null,/)
|
||||
|
||||
// 仅已支付订单开放;开关关闭整体不处理售后
|
||||
assert.match(userOrders, /\$enabled = \$group === OrderCenterQuery::STATUS_PAID && !empty\(\$settings\['enabled'\]\);/)
|
||||
// 时效以支付时间为准
|
||||
assert.match(userOrders, /\$this->after_sale_state\(\$group, \$order_number, \(string\) \(\$order\['paid_at'\] \?\? ''\), \$after_sale_service, \$after_sales\)/)
|
||||
// 列表模式单行查询,详情模式复用全量工单
|
||||
assert.match(userOrders, /\$after_sales = \$detail && \$after_sale_service \? \$after_sale_service->list_for_order\(\$order_number\) : \[\];/)
|
||||
assert.match(userOrders, /\$latest = \$service->latest_for_order\(\$order_number\);/)
|
||||
assert.match(userOrders, /\$view\['after_sales'\] = \$after_sales;/)
|
||||
|
||||
// 旧 can_after_sale 双路已移除
|
||||
assert.ok(!userOrders.includes('can_after_sale'), 'can_after_sale must be replaced by the after_sale state')
|
||||
})
|
||||
|
||||
test('frontend after-sale modals are standalone and config-driven', () => {
|
||||
const js = read('assets/js/order-after-sale.js')
|
||||
|
||||
// 四个独立弹窗 + modal-mini 几何 + 蓝色炫彩标题区
|
||||
assert.match(js, /className: 'aksrt-after-sale-apply-modal'/)
|
||||
assert.match(js, /className: 'aksrt-after-sale-detail-modal'/)
|
||||
assert.match(js, /className: 'aksrt-after-sale-expired-modal'/)
|
||||
assert.match(js, /className: 'aksrt-after-sale-withdraw-modal'/)
|
||||
assert.match(js, /modal-colorful-header colorful-bg jb-blue/)
|
||||
assert.match(js, /AksrtModalRuntime\.open\(\{/)
|
||||
|
||||
// 滚动容器与禁封申诉弹窗同口径:no-scrollbar 完全隐藏滚动条(含细滚动条),
|
||||
// 避免 modal-mini 弹窗边缘出现可见滚动栏。
|
||||
assert.match(js, /'<div class="scroll-y no-scrollbar max-vh5">'/)
|
||||
assert.match(js, /'<div class="scroll-y no-scrollbar max-vh5 aksrt-after-sale-detail__body">'/)
|
||||
assert.ok(!js.includes('mini-scrollbar'), 'after-sale modals must not render a visible scrollbar')
|
||||
|
||||
// 申请表单:预置原因徽章、所需资料、必填申请说明(placeholder 取后台配置)
|
||||
assert.match(js, /reasonOptions\(\)/)
|
||||
assert.match(js, /materialOptions\(\)/)
|
||||
assert.match(js, /data-aksrt-after-sale-reason=/)
|
||||
assert.match(js, /data-aksrt-after-sale-material=/)
|
||||
assert.match(js, /var placeholder = String\(cfg\.reasonPlaceholder \|\| '必填,请您详细填写申请原因'\)/)
|
||||
assert.match(js, /name="reason"/)
|
||||
assert.match(js, /notyf\('请填写申请说明', 'danger'\)/)
|
||||
assert.match(js, /notyf\('请选择售后原因', 'danger'\)/)
|
||||
|
||||
// 图片上传:开关控制渲染,最多 3 张,上传/删除/提交
|
||||
assert.match(js, /if \(cfg\.imgEnabled\) \{/)
|
||||
assert.match(js, /MAX_IMAGES = 3/)
|
||||
assert.match(js, /\/after-sale\/upload-image'/)
|
||||
assert.match(js, /data-aksrt-after-sale-image-remove/)
|
||||
assert.match(js, /images: collectImages\(shell\)/)
|
||||
assert.match(js, /reason_label: selectedReasonLabel\(shell\)/)
|
||||
assert.match(js, /materials: collectMaterials\(shell\)/)
|
||||
assert.match(js, /description: reasonText,/)
|
||||
|
||||
// 申请说明(HTML)+ 超时效提示(HTML)由后台配置直出
|
||||
assert.match(js, /var desc = String\(cfg\.desc \|\| ''\)/)
|
||||
assert.match(js, /var text = String\(settings\(\)\.expiredText \|\| '抱歉,您的订单已超过售后申请时效'\)/)
|
||||
|
||||
// 售后详细:状态徽章 / 明细行 / 图片缩略图;不可再申请提示原文 + 我知道了
|
||||
// 申请表单分行展示:原因 / 申请资料 / 申请说明各自独立成行(与申请时间同格式);
|
||||
// 老数据无 apply_form 时兜底展示融合串
|
||||
assert.match(js, /detailRow\('原因', String\(applyForm\.reason_label \|\| ''\)\)/)
|
||||
assert.match(js, /detailRow\('申请说明', String\(applyForm\.description \|\| ''\)\)/)
|
||||
assert.match(js, /STATUS_LABELS = \{[\s\S]*?pending: '待审核'[\s\S]*?completed: '已完成',/)
|
||||
// 不可再申请:不再展示阻断文案,仅保留关闭入口;可再申请:仅保留单个主按钮(无「关闭」)
|
||||
assert.ok(!js.includes('暂不支持再次申请售后'), 'blocked notice copy must be removed')
|
||||
assert.ok(!js.includes('REAPPLY_BLOCKED_TEXT'), 'blocked text constant must be removed')
|
||||
assert.ok(!js.includes('>关闭</button>'), 'detail modal must not render a 关闭 button')
|
||||
assert.match(js, /data-aksrt-modal-close>我知道了</)
|
||||
assert.match(js, /data-aksrt-after-sale-reapply data-aksrt-after-sale-payload="/)
|
||||
|
||||
// 撤销售后:入口仅随待审核状态渲染,点击先过二次确认弹窗,确认后 DELETE 工单
|
||||
assert.match(js, /status === 'pending'[\s\S]*?data-aksrt-after-sale-withdraw="' \+ escapeHtml\(\(order && order\.order_number\) \|\| ''\)/)
|
||||
assert.match(js, /buildWithdrawHtml\(orderNumber\)/)
|
||||
assert.match(js, /className: 'aksrt-after-sale-withdraw-modal'/)
|
||||
assert.match(js, /data-aksrt-after-sale-withdraw-confirm="' \+ escapeHtml\(orderNumber\) \+ '"/)
|
||||
assert.match(js, /method: 'DELETE'/)
|
||||
assert.match(js, /AksrtModalRuntime\.close\('withdraw'\)/)
|
||||
assert.match(js, /notyf\('售后申请已撤销', 'success'\)/)
|
||||
|
||||
// 人机验证:提交与撤销前先过验证(场景未启用时挂载点缺失,run 直接放行)
|
||||
assert.match(js, /function runCaptcha\(scene\)/)
|
||||
assert.match(js, /runCaptcha\('after_sale_apply'\)/)
|
||||
assert.match(js, /runCaptcha\('after_sale_cancel'\)/)
|
||||
assert.match(js, /AKSRTCaptcha\.run\(scene\)/)
|
||||
|
||||
// 提交后关闭弹窗并刷新订单列表
|
||||
assert.match(js, /AKSRTOrderCenter\.refresh\(orderNumber\)/)
|
||||
assert.match(js, /window\.AksrtModalRuntime\.close\('submit'\)/)
|
||||
|
||||
// 入口委托:申请 / 售后详细
|
||||
assert.match(js, /data-aksrt-after-sale-apply/)
|
||||
assert.match(js, /data-aksrt-after-sale-detail/)
|
||||
assert.match(js, /auto_fun\.aksrt-after-sale/)
|
||||
})
|
||||
|
||||
test('order center drops the embedded after-sale form and exposes refresh()', () => {
|
||||
const js = read('assets/js/order-center.js')
|
||||
|
||||
// 内嵌售后表单与两个 submit 处理器已彻底移除(死代码清理)
|
||||
assert.ok(!js.includes('data-aksrt-after-sale-form'), 'embedded after-sale form must be removed')
|
||||
assert.ok(!js.includes('提交售后申请'), 'embedded after-sale submit copy must be removed')
|
||||
assert.ok(!js.includes('onDocumentSubmit'), 'document submit handler for the after-sale form must be removed')
|
||||
|
||||
// 列表卡片与订单详情弹窗底部共用新的售后按钮
|
||||
assert.match(js, /function afterSaleButtons\(order\)/)
|
||||
assert.match(js, /function afterSalePayload\(order\)/)
|
||||
assert.match(js, /data-aksrt-after-sale-detail="' \+ payload \+ '"/)
|
||||
assert.match(js, /data-aksrt-after-sale-apply="' \+ payload \+ '"/)
|
||||
assert.match(js, /if \(afterSale\.enabled\) html \+= afterSaleButtons\(order\);/)
|
||||
assert.ok(!js.includes('\n html += afterSaleButtons(order);\n'), 'detail modal footer must not render the shared after-sale buttons')
|
||||
// 底部按钮与禁封申诉弹窗同式(but-average modal-buts);「售后详细」入口收拢到记录卡片
|
||||
assert.match(js, /aksrt-order-detail__actions but-average modal-buts/)
|
||||
assert.match(js, /data-aksrt-after-sale-apply="' \+ afterSalePayload\(order\) \+ '"/)
|
||||
assert.ok(js.includes('查看详细</button>'), 'record card has a view-detail entry')
|
||||
assert.match(js, /data-aksrt-after-sale-detail="' \+ recordPayload \+ '"/)
|
||||
// 记录卡片:原因 / 申请理由分行(新数据走 apply_form,老数据兜底融合串)
|
||||
assert.ok(js.includes('aksrt-order-detail__after-sale-field">原因:'), 'record card shows 原因:')
|
||||
assert.ok(js.includes('aksrt-order-detail__after-sale-field">申请理由:'), 'record card shows 申请理由:')
|
||||
assert.match(js, /data-aksrt-after-sale-detail="/)
|
||||
assert.match(js, /data-aksrt-after-sale-apply="/)
|
||||
|
||||
// 售后记录区保留在订单详情弹窗:记录卡片带申请时间,「查看详细」逐条入口;
|
||||
// 取消申请按钮与处理器已移除(撤销入口移入售后详细弹窗)。
|
||||
assert.match(js, /售后记录/)
|
||||
assert.match(js, /<time>" \+ escapeHtml\(item\.created_at\)/)
|
||||
assert.ok(!js.includes('data-aksrt-after-sale-cancel'), 'cancel entry moved into the after-sale detail modal')
|
||||
assert.ok(!js.includes('取消申请'), 'order detail must not render the legacy cancel button')
|
||||
|
||||
// refresh 供售后弹窗提交后刷新列表与统计
|
||||
assert.match(js, /function refresh\(orderNumber\) \{/)
|
||||
assert.match(js, /window\.AKSRTOrderCenter = \{ init: init, destroy: destroy, refresh: refresh \}/)
|
||||
assert.match(js, /liveStates\.forEach\(function \(state\) \{/)
|
||||
})
|
||||
|
||||
test('after-sale assets, template buttons and runtime data are wired', () => {
|
||||
const scripts = read('src/Core/ScriptsPart1b.php')
|
||||
assert.match(scripts, /'aksrt-order-after-sale'/)
|
||||
assert.match(scripts, /\/assets\/js\/order-after-sale\.js'/)
|
||||
|
||||
const scripts1c = read('src/Core/ScriptsPart1c.php')
|
||||
assert.match(scripts1c, /'afterSale'\s*=> \(function \(\) \{/)
|
||||
assert.match(scripts1c, /PaymentRuntime::get_after_sale_settings\(\)/)
|
||||
assert.match(scripts1c, /'maxImages'\s*=> 3/)
|
||||
|
||||
const template = read('inc/helpers/user-center-renderers/profile-orders.php')
|
||||
assert.match(template, /\$after_sale = is_array\(\$order\['after_sale'\] \?\? null\) \? \$order\['after_sale'\] : \[\];/)
|
||||
// 载荷随按钮渲染(order-after_sale.js 同步消费),点击即弹窗、无前置请求
|
||||
assert.match(template, /\$after_sale_payload = wp_json_encode\(\[/)
|
||||
assert.match(template, /'after_sale'\s*=> \$after_sale,/)
|
||||
assert.match(template, /data-aksrt-after-sale-detail="<\?php echo esc_attr\(\$after_sale_payload\);/)
|
||||
assert.match(template, /data-aksrt-after-sale-apply="<\?php echo esc_attr\(\$after_sale_payload\);/)
|
||||
assert.match(template, /esc_html_e\('售后详细', 'aksrt'\)/)
|
||||
assert.ok(!template.includes('can_after_sale'), 'template must not use the removed can_after_sale flag')
|
||||
|
||||
// 售后人机验证挂载点(申请 / 撤销):场景未启用时 aksrt_render_captcha 无输出
|
||||
assert.match(template, /function_exists\('aksrt_render_captcha'\)/)
|
||||
assert.match(template, /aksrt_render_captcha\('after_sale_apply'\)/)
|
||||
assert.match(template, /aksrt_render_captcha\('after_sale_cancel'\)/)
|
||||
|
||||
const css = read('assets/css/frontend/modal-runtime.css')
|
||||
assert.match(css, /\.aksrt-after-sale-apply-modal:not\(\.aksrt-modal-runtime--bottom\) \.aksrt-modal-runtime__dialog/)
|
||||
assert.match(css, /\.aksrt-after-sale-detail-modal:not\(\.aksrt-modal-runtime--bottom\) \.aksrt-modal-runtime__dialog/)
|
||||
assert.match(css, /\.aksrt-after-sale-expired-modal:not\(\.aksrt-modal-runtime--bottom\) \.aksrt-modal-runtime__dialog/)
|
||||
assert.match(css, /\.aksrt-after-sale-withdraw-modal:not\(\.aksrt-modal-runtime--bottom\) \.aksrt-modal-runtime__dialog/)
|
||||
assert.match(css, /width: min\(400px, calc\(100vw - 32px\)\)/)
|
||||
assert.match(css, /\.aksrt-after-sale-apply-modal \.modal-colorful-header/)
|
||||
assert.match(css, /\.aksrt-after-sale-apply-modal \.colorful-bg > \.colorful-make/)
|
||||
|
||||
// 弹窗底部按钮:四个售后弹窗统一全宽平铺贴底,子按钮去独立胶囊观感(覆盖全局按钮网格)
|
||||
for (const modal of ['apply', 'detail', 'expired', 'withdraw']) {
|
||||
assert.match(css, new RegExp(`\\.aksrt-after-sale-${modal}-modal \\.but-average\\.modal-buts[\\s\\S]*?margin: 16px -18px -16px`))
|
||||
assert.match(css, new RegExp(`\\.aksrt-after-sale-${modal}-modal \\.but-average\\.modal-buts > \\.but[\\s\\S]*?border-radius: 0`))
|
||||
}
|
||||
assert.match(css, /\.but:only-child \{ grid-column: 1 \/ -1; \}/)
|
||||
// 撤销售后入口:待审核状态行内的文字式入口按钮
|
||||
assert.match(css, /\.aksrt-after-sale-detail__statusCell/)
|
||||
assert.match(css, /\.aksrt-after-sale-withdraw \{[^}]*background: transparent/)
|
||||
|
||||
const field = read('assets/admin/src/components/form/AksField.vue')
|
||||
assert.match(field, /import draggable from 'vuedraggable'/)
|
||||
assert.match(field, /v-model="repeaterDragList"/)
|
||||
assert.match(field, /handle="\.aksrt-field__repeater-handle"/)
|
||||
assert.match(field, /function repeaterDragKey\(item\)/)
|
||||
})
|
||||
@@ -61,3 +61,28 @@ test('legacy public SMS-code AJAX surface is removed while SMS service remains',
|
||||
assert.doesNotMatch(sms, /function ajax_send_code\s*\(/);
|
||||
assert.match(sms, /public function send_code\(string \$phone, string \$code\): array/);
|
||||
});
|
||||
|
||||
test('after-sale submit and withdraw endpoints verify captcha once per request', () => {
|
||||
const service = read('src/Payment/AfterSaleService.php');
|
||||
|
||||
// 两个新场景已在场景白名单与后台「启用场景」勾选列表同步注册
|
||||
const scenes = read('inc/modules/captcha.php');
|
||||
assert.match(scenes, /'after_sale_apply'\s*=> __\('申请售后', 'aksrt'\)/);
|
||||
assert.match(scenes, /'after_sale_cancel'\s*=> __\('撤销售后', 'aksrt'\)/);
|
||||
const permissions = read('src/Admin/Schema/SchemaPermissions.php');
|
||||
assert.match(permissions, /'label' => '申请售后', 'value' => 'after_sale_apply'/);
|
||||
assert.match(permissions, /'label' => '撤销售后', 'value' => 'after_sale_cancel'/);
|
||||
const adminSchema = read('assets/admin/src/schema/sections/permissions/index.js');
|
||||
assert.match(adminSchema, /label: '申请售后', value: 'after_sale_apply'/);
|
||||
assert.match(adminSchema, /label: '撤销售后', value: 'after_sale_cancel'/);
|
||||
|
||||
// REST 提交 / 撤销各过一次场景校验(统一入口 helper 内恰一次 aksrt_require_captcha);
|
||||
// 场景未启用时 aksrt_require_captcha 直接放行
|
||||
const submit = service.slice(service.indexOf('public function rest_submit'), service.indexOf('public function rest_list'));
|
||||
const cancel = service.slice(service.indexOf('public function rest_cancel'), service.indexOf('public function create'));
|
||||
assert.match(submit, /require_captcha\(\$request, 'after_sale_apply'\)/);
|
||||
assert.match(cancel, /require_captcha\(\$request, 'after_sale_cancel'\)/);
|
||||
assert.equal((service.match(/require_captcha\(\$request, '/g) || []).length, 2);
|
||||
assert.equal((service.match(/aksrt_require_captcha\(/g) || []).length, 1);
|
||||
assert.match(service, /function_exists\('aksrt_require_captcha'\)/);
|
||||
});
|
||||
@@ -592,6 +592,10 @@ test('profile routes load versioned leaf styles and settings dialogs use the sha
|
||||
assert.match(orderCenterJs, /data-aksrt-order-filter/);
|
||||
assert.match(orderCenterJs, /data-aksrt-order-page/);
|
||||
assert.doesNotMatch(orderCenterJs, /new URL\(window\.location\.href\)/);
|
||||
// 订单详情弹窗滚动发生在 runtime dialog 本体:滚动条必须完全隐藏(含细滚动条),滚动行为不变。
|
||||
assert.match(ordersCss, /\.aksrt-order-detail-modal \.aksrt-modal-runtime__dialog\s*\{[^}]*scrollbar-width:\s*none/);
|
||||
assert.match(ordersCss, /\.aksrt-order-detail-modal \.aksrt-modal-runtime__dialog::-webkit-scrollbar\s*\{\s*display:\s*none;\s*\}/);
|
||||
assert.doesNotMatch(ordersCss, /aksrt-order-detail-modal[^{}]*mini-scrollbar/);
|
||||
assert.doesNotMatch(userCenterJs, /\['order_status', 'order_type', 'order_number', 'order_page'\]/);
|
||||
assert.match(userCenterJs, /initialTab === 'orders'[\s\S]*?window\.history\.replaceState/);
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user