修复官网选购咨询入口与图片失败降级
This commit is contained in:
1 parent
d8224ec5b7
commit
08ffc51894
5 files changed
+438
-64
No files matched your search
@@ -0,0 +1,25 @@
|
|||||||
|
<script setup>
|
||||||
|
/** 官网可配置图片统一处理加载失败;替代内容由调用处提供,不伪造头像或二维码。 */
|
||||||
|
import { ref, watch } from "vue";
|
||||||
|
|
||||||
|
defineOptions({ inheritAttrs: false });
|
||||||
|
const props = defineProps({ src: String, alt: String });
|
||||||
|
const failed = ref(false);
|
||||||
|
watch(
|
||||||
|
() => props.src,
|
||||||
|
() => {
|
||||||
|
failed.value = false;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<img
|
||||||
|
v-if="src && !failed"
|
||||||
|
v-bind="$attrs"
|
||||||
|
:src="src"
|
||||||
|
:alt="alt"
|
||||||
|
@error="failed = true"
|
||||||
|
/>
|
||||||
|
<slot v-else />
|
||||||
|
</template>
|
||||||
@@ -22,6 +22,7 @@ import { createVNode } from "vue";
|
|||||||
import { openBlock } from "vue";
|
import { openBlock } from "vue";
|
||||||
import { normalizeStyle } from "vue";
|
import { normalizeStyle } from "vue";
|
||||||
import { NuxtLink } from "#components";
|
import { NuxtLink } from "#components";
|
||||||
|
import OfficialImage from "~/components/OfficialImage.vue";
|
||||||
import { i as useOfficialSiteContent_i } from "~/lib/useOfficialSiteContent.js";
|
import { i as useOfficialSiteContent_i } from "~/lib/useOfficialSiteContent.js";
|
||||||
import { n as useOfficialSiteContent_n } from "~/lib/useOfficialSiteContent.js";
|
import { n as useOfficialSiteContent_n } from "~/lib/useOfficialSiteContent.js";
|
||||||
import { r as useOfficialSiteContent_r } from "~/lib/useOfficialSiteContent.js";
|
import { r as useOfficialSiteContent_r } from "~/lib/useOfficialSiteContent.js";
|
||||||
@@ -32,7 +33,6 @@ var S = {
|
|||||||
C = {
|
C = {
|
||||||
class: `official-header-inner`,
|
class: `official-header-inner`,
|
||||||
},
|
},
|
||||||
w = [`src`, `alt`],
|
|
||||||
T = {
|
T = {
|
||||||
key: 1,
|
key: 1,
|
||||||
class: `official-brand-mark`,
|
class: `official-brand-mark`,
|
||||||
@@ -69,7 +69,6 @@ var S = {
|
|||||||
I = {
|
I = {
|
||||||
class: `official-footer-brand`,
|
class: `official-footer-brand`,
|
||||||
},
|
},
|
||||||
te = [`src`, `alt`],
|
|
||||||
L = {
|
L = {
|
||||||
key: 1,
|
key: 1,
|
||||||
class: `official-brand-mark`,
|
class: `official-brand-mark`,
|
||||||
@@ -94,7 +93,6 @@ var S = {
|
|||||||
key: 3,
|
key: 3,
|
||||||
class: `official-footer-qr`,
|
class: `official-footer-qr`,
|
||||||
},
|
},
|
||||||
K = [`src`, `alt`],
|
|
||||||
q = {
|
q = {
|
||||||
class: `official-footer-bottom`,
|
class: `official-footer-bottom`,
|
||||||
},
|
},
|
||||||
@@ -149,28 +147,26 @@ var S = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
default: withCtx(() => [
|
default: withCtx(() => [
|
||||||
unref($)
|
createVNode(
|
||||||
? (openBlock(),
|
OfficialImage,
|
||||||
createElementBlock(
|
|
||||||
`img`,
|
|
||||||
{
|
{
|
||||||
key: 0,
|
|
||||||
class: `official-brand-image`,
|
class: `official-brand-image`,
|
||||||
src: unref($),
|
src: unref($),
|
||||||
alt: unref(Y).brand.name,
|
alt: unref(Y).brand.name,
|
||||||
},
|
},
|
||||||
null,
|
{
|
||||||
8,
|
default: withCtx(() => [
|
||||||
w,
|
createElementVNode(`span`, T, [
|
||||||
))
|
createElementVNode(`span`),
|
||||||
: (openBlock(),
|
createElementVNode(`span`),
|
||||||
createElementBlock(`span`, T, [
|
createElementVNode(`span`),
|
||||||
...(i[3] ||= [
|
|
||||||
createElementVNode(`span`, null, null, -1),
|
|
||||||
createElementVNode(`span`, null, null, -1),
|
|
||||||
createElementVNode(`span`, null, null, -1),
|
|
||||||
]),
|
]),
|
||||||
])),
|
]),
|
||||||
|
_: 1,
|
||||||
|
},
|
||||||
|
8,
|
||||||
|
[`src`, `alt`],
|
||||||
|
),
|
||||||
createElementVNode(`span`, E, [
|
createElementVNode(`span`, E, [
|
||||||
createElementVNode(
|
createElementVNode(
|
||||||
`strong`,
|
`strong`,
|
||||||
@@ -337,28 +333,26 @@ var S = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
default: withCtx(() => [
|
default: withCtx(() => [
|
||||||
unref($)
|
createVNode(
|
||||||
? (openBlock(),
|
OfficialImage,
|
||||||
createElementBlock(
|
|
||||||
`img`,
|
|
||||||
{
|
{
|
||||||
key: 0,
|
|
||||||
class: `official-brand-image`,
|
class: `official-brand-image`,
|
||||||
src: unref($),
|
src: unref($),
|
||||||
alt: unref(Y).brand.name,
|
alt: unref(Y).brand.name,
|
||||||
},
|
},
|
||||||
null,
|
{
|
||||||
8,
|
default: withCtx(() => [
|
||||||
te,
|
createElementVNode(`span`, L, [
|
||||||
))
|
createElementVNode(`span`),
|
||||||
: (openBlock(),
|
createElementVNode(`span`),
|
||||||
createElementBlock(`span`, L, [
|
createElementVNode(`span`),
|
||||||
...(i[5] ||= [
|
|
||||||
createElementVNode(`span`, null, null, -1),
|
|
||||||
createElementVNode(`span`, null, null, -1),
|
|
||||||
createElementVNode(`span`, null, null, -1),
|
|
||||||
]),
|
]),
|
||||||
])),
|
]),
|
||||||
|
_: 1,
|
||||||
|
},
|
||||||
|
8,
|
||||||
|
[`src`, `alt`],
|
||||||
|
),
|
||||||
createElementVNode(`span`, R, [
|
createElementVNode(`span`, R, [
|
||||||
createElementVNode(
|
createElementVNode(
|
||||||
`strong`,
|
`strong`,
|
||||||
@@ -523,17 +517,26 @@ var S = {
|
|||||||
unref(Y).footer.qrImage
|
unref(Y).footer.qrImage
|
||||||
? (openBlock(),
|
? (openBlock(),
|
||||||
createElementBlock(`div`, G, [
|
createElementBlock(`div`, G, [
|
||||||
createElementVNode(
|
createVNode(
|
||||||
`img`,
|
OfficialImage,
|
||||||
{
|
{
|
||||||
src: unref(useOfficialSiteContent_i)(
|
src: unref(useOfficialSiteContent_i)(
|
||||||
unref(Y).footer.qrImage,
|
unref(Y).footer.qrImage,
|
||||||
),
|
),
|
||||||
alt: unref(Y).footer.qrCaption,
|
alt: unref(Y).footer.qrCaption,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
default: withCtx(() => [
|
||||||
|
createElementVNode(
|
||||||
|
`span`,
|
||||||
null,
|
null,
|
||||||
|
`二维码暂时无法显示,请使用工单咨询`,
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
_: 1,
|
||||||
|
},
|
||||||
8,
|
8,
|
||||||
K,
|
[`src`, `alt`],
|
||||||
),
|
),
|
||||||
createElementVNode(
|
createElementVNode(
|
||||||
`span`,
|
`span`,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ var n = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: `订购产品`,
|
label: `订购产品`,
|
||||||
href: `/cart`,
|
href: `/products`,
|
||||||
is_show: !0,
|
is_show: !0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -68,7 +68,7 @@ var n = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
buttonLabel: `浏览产品服务`,
|
buttonLabel: `浏览产品服务`,
|
||||||
buttonHref: `/login?redirect=%2Fproducts`,
|
buttonHref: `/products`,
|
||||||
secondaryLabel: `了解基础设施`,
|
secondaryLabel: `了解基础设施`,
|
||||||
secondaryHref: `#infrastructure`,
|
secondaryHref: `#infrastructure`,
|
||||||
metric: {
|
metric: {
|
||||||
@@ -124,7 +124,7 @@ var n = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
buttonLabel: `查看云服务器`,
|
buttonLabel: `查看云服务器`,
|
||||||
href: `/login?redirect=%2Fcloud-servers`,
|
href: `/products/cloud-server`,
|
||||||
is_show: !0,
|
is_show: !0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -149,7 +149,7 @@ var n = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
buttonLabel: `查看物理服务器`,
|
buttonLabel: `查看物理服务器`,
|
||||||
href: `/login?redirect=%2Fdedicated-servers`,
|
href: `/products/dedicated-server`,
|
||||||
is_show: !0,
|
is_show: !0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -173,8 +173,8 @@ var n = {
|
|||||||
is_show: !0,
|
is_show: !0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
buttonLabel: `了解机柜托管`,
|
buttonLabel: `登录咨询机柜托管`,
|
||||||
href: `/login?redirect=%2Fcolocations`,
|
href: `/tickets/create`,
|
||||||
is_show: !0,
|
is_show: !0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -251,7 +251,7 @@ var n = {
|
|||||||
title: `为你的下一个项目选好基础设施`,
|
title: `为你的下一个项目选好基础设施`,
|
||||||
description: `浏览可用产品方案,或进入客户中心配置服务、管理实例与订单。`,
|
description: `浏览可用产品方案,或进入客户中心配置服务、管理实例与订单。`,
|
||||||
buttonLabel: `前往产品目录`,
|
buttonLabel: `前往产品目录`,
|
||||||
buttonHref: `/login?redirect=%2Fproducts`,
|
buttonHref: `/products`,
|
||||||
secondaryLabel: `登录客户中心`,
|
secondaryLabel: `登录客户中心`,
|
||||||
secondaryHref: `/login`,
|
secondaryHref: `/login`,
|
||||||
},
|
},
|
||||||
@@ -263,17 +263,17 @@ var n = {
|
|||||||
links: [
|
links: [
|
||||||
{
|
{
|
||||||
label: `云服务器`,
|
label: `云服务器`,
|
||||||
href: `/login?redirect=%2Fcloud-servers`,
|
href: `/products/cloud-server`,
|
||||||
is_show: !0,
|
is_show: !0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: `物理服务器`,
|
label: `物理服务器`,
|
||||||
href: `/login?redirect=%2Fdedicated-servers`,
|
href: `/products/dedicated-server`,
|
||||||
is_show: !0,
|
is_show: !0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: `机柜托管`,
|
label: `机柜托管`,
|
||||||
href: `/login?redirect=%2Fcolocations`,
|
href: `/tickets/create`,
|
||||||
is_show: !0,
|
is_show: !0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -359,8 +359,44 @@ function i(e, t) {
|
|||||||
function a() {
|
function a() {
|
||||||
return JSON.parse(JSON.stringify(n));
|
return JSON.parse(JSON.stringify(n));
|
||||||
}
|
}
|
||||||
|
// 只迁移已确认的站内旧购买目的地;CMS 自定义外链、文案及其余账号入口保持原样。
|
||||||
|
const purchaseDestinations = new Map([
|
||||||
|
["/products", "/products"],
|
||||||
|
["/cloud-servers", "/products/cloud-server"],
|
||||||
|
["/dedicated-servers", "/products/dedicated-server"],
|
||||||
|
["/colocations", "/tickets/create"],
|
||||||
|
]);
|
||||||
|
function purchaseHref(value) {
|
||||||
|
if (typeof value !== "string" || !/^\/(?!\/)/.test(value)) return value;
|
||||||
|
try {
|
||||||
|
const url = new URL(value, window.location.origin);
|
||||||
|
const target =
|
||||||
|
url.pathname === "/login"
|
||||||
|
? new URL(
|
||||||
|
url.searchParams.get("redirect") || "/",
|
||||||
|
window.location.origin,
|
||||||
|
)
|
||||||
|
: url;
|
||||||
|
if (target.origin !== window.location.origin) return value;
|
||||||
|
const destination = purchaseDestinations.get(target.pathname);
|
||||||
|
return destination ? destination + target.search + target.hash : value;
|
||||||
|
} catch {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 在 CMS 入模处处理所有链接字段,头尾、商品卡及主按钮不会各自保留不同的旧规则。
|
||||||
|
function normalizeLinks(value) {
|
||||||
|
if (Array.isArray(value)) return value.map(normalizeLinks);
|
||||||
|
if (!r(value)) return value;
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(value).map(([key, entry]) => [
|
||||||
|
key,
|
||||||
|
/href$/i.test(key) ? purchaseHref(entry) : normalizeLinks(entry),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
function o(e) {
|
function o(e) {
|
||||||
return i(n, e);
|
return normalizeLinks(i(n, e));
|
||||||
}
|
}
|
||||||
// 链接允许站内路径、锚点、HTTP(S)/mailto/tel;未知协议和反斜杠回落为不可跳转的 #。
|
// 链接允许站内路径、锚点、HTTP(S)/mailto/tel;未知协议和反斜杠回落为不可跳转的 #。
|
||||||
function s(e) {
|
function s(e) {
|
||||||
|
|||||||
+41
-15
@@ -25,6 +25,7 @@ import { normalizeStyle } from "vue";
|
|||||||
import { useHead } from "#app";
|
import { useHead } from "#app";
|
||||||
import { NuxtLink } from "#components";
|
import { NuxtLink } from "#components";
|
||||||
import { Icon } from "#components";
|
import { Icon } from "#components";
|
||||||
|
import OfficialImage from "~/components/OfficialImage.vue";
|
||||||
import { i as useOfficialSiteContent_i } from "~/lib/useOfficialSiteContent.js";
|
import { i as useOfficialSiteContent_i } from "~/lib/useOfficialSiteContent.js";
|
||||||
import { n as useOfficialSiteContent_n } from "~/lib/useOfficialSiteContent.js";
|
import { n as useOfficialSiteContent_n } from "~/lib/useOfficialSiteContent.js";
|
||||||
import { r as useOfficialSiteContent_r } from "~/lib/useOfficialSiteContent.js";
|
import { r as useOfficialSiteContent_r } from "~/lib/useOfficialSiteContent.js";
|
||||||
@@ -162,7 +163,6 @@ var x = {
|
|||||||
he = {
|
he = {
|
||||||
class: `official-review-person`,
|
class: `official-review-person`,
|
||||||
},
|
},
|
||||||
ge = [`src`, `alt`],
|
|
||||||
_e = {
|
_e = {
|
||||||
key: 1,
|
key: 1,
|
||||||
class: `official-review-avatar`,
|
class: `official-review-avatar`,
|
||||||
@@ -639,6 +639,13 @@ var x = {
|
|||||||
1032,
|
1032,
|
||||||
[`to`],
|
[`to`],
|
||||||
),
|
),
|
||||||
|
n.href === `/tickets/create`
|
||||||
|
? createElementVNode(
|
||||||
|
`p`,
|
||||||
|
null,
|
||||||
|
`机柜托管及定制方案请登录后提交工单咨询`,
|
||||||
|
)
|
||||||
|
: createCommentVNode(``, !0),
|
||||||
],
|
],
|
||||||
4,
|
4,
|
||||||
)
|
)
|
||||||
@@ -922,25 +929,17 @@ var x = {
|
|||||||
1,
|
1,
|
||||||
),
|
),
|
||||||
createElementVNode(`div`, he, [
|
createElementVNode(`div`, he, [
|
||||||
unref(
|
createVNode(
|
||||||
useOfficialSiteContent_i,
|
OfficialImage,
|
||||||
)(t.avatar)
|
|
||||||
? (openBlock(),
|
|
||||||
createElementBlock(
|
|
||||||
`img`,
|
|
||||||
{
|
{
|
||||||
key: 0,
|
|
||||||
src: unref(
|
src: unref(
|
||||||
useOfficialSiteContent_i,
|
useOfficialSiteContent_i,
|
||||||
)(t.avatar),
|
)(t.avatar),
|
||||||
alt: t.author,
|
alt: t.author,
|
||||||
},
|
},
|
||||||
null,
|
{
|
||||||
8,
|
default: withCtx(() => [
|
||||||
ge,
|
createElementVNode(
|
||||||
))
|
|
||||||
: (openBlock(),
|
|
||||||
createElementBlock(
|
|
||||||
`span`,
|
`span`,
|
||||||
_e,
|
_e,
|
||||||
toDisplayString(
|
toDisplayString(
|
||||||
@@ -950,7 +949,13 @@ var x = {
|
|||||||
) || `客`,
|
) || `客`,
|
||||||
),
|
),
|
||||||
1,
|
1,
|
||||||
)),
|
),
|
||||||
|
]),
|
||||||
|
_: 2,
|
||||||
|
},
|
||||||
|
1032,
|
||||||
|
[`src`, `alt`],
|
||||||
|
),
|
||||||
createElementVNode(
|
createElementVNode(
|
||||||
`div`,
|
`div`,
|
||||||
null,
|
null,
|
||||||
@@ -1011,6 +1016,27 @@ var x = {
|
|||||||
),
|
),
|
||||||
1,
|
1,
|
||||||
),
|
),
|
||||||
|
!unref(X).footer.phone &&
|
||||||
|
!unref(X).footer.email &&
|
||||||
|
!unref(X).footer.address
|
||||||
|
? createElementVNode(`p`, null, [
|
||||||
|
createTextVNode(
|
||||||
|
`机柜托管及定制方案:`,
|
||||||
|
),
|
||||||
|
createVNode(
|
||||||
|
f,
|
||||||
|
{ to: `/tickets/create` },
|
||||||
|
{
|
||||||
|
default: withCtx(() => [
|
||||||
|
createTextVNode(
|
||||||
|
`登录后提交工单咨询`,
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
_: 1,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
])
|
||||||
|
: createCommentVNode(``, !0),
|
||||||
]),
|
]),
|
||||||
createElementVNode(`div`, xe, [
|
createElementVNode(`div`, xe, [
|
||||||
createVNode(
|
createVNode(
|
||||||
|
|||||||
@@ -0,0 +1,284 @@
|
|||||||
|
/** 官网回归使用真实 CMS 信封和图片字节边界,不替换内部模型或手工触发图片事件。 */
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
import test from "node:test";
|
||||||
|
import {
|
||||||
|
appOrigin,
|
||||||
|
backendOrigin,
|
||||||
|
eventually,
|
||||||
|
loginAccount,
|
||||||
|
loginPassword,
|
||||||
|
scenario,
|
||||||
|
} from "./helpers/console-browser.mjs";
|
||||||
|
|
||||||
|
// 与线上 CMS 的字段形状一致:旧入口仍由 CMS 覆盖,不能只测试默认文案。
|
||||||
|
function cmsContent() {
|
||||||
|
return {
|
||||||
|
header: {
|
||||||
|
navigation: [
|
||||||
|
{ label: "产品服务", href: "#products", is_show: true },
|
||||||
|
{
|
||||||
|
label: "客户自定义方案",
|
||||||
|
href: "https://customer.example.invalid/plan",
|
||||||
|
is_show: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
hero: {
|
||||||
|
title: "为客户保留的自定义标题",
|
||||||
|
buttonLabel: "浏览产品服务",
|
||||||
|
buttonHref: "/login?redirect=%2Fproducts",
|
||||||
|
},
|
||||||
|
products: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: "客户自定义云方案",
|
||||||
|
buttonLabel: "查看云服务器",
|
||||||
|
href: "/login?redirect=%2Fcloud-servers",
|
||||||
|
features: [],
|
||||||
|
is_show: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "物理服务器",
|
||||||
|
buttonLabel: "查看物理服务器",
|
||||||
|
href: "/login?redirect=%2Fdedicated-servers",
|
||||||
|
features: [],
|
||||||
|
is_show: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "机柜托管",
|
||||||
|
buttonLabel: "了解机柜托管",
|
||||||
|
href: "/login?redirect=%2Fcolocations",
|
||||||
|
features: [],
|
||||||
|
is_show: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
phone: "",
|
||||||
|
email: "",
|
||||||
|
address: "",
|
||||||
|
linkGroups: [
|
||||||
|
{
|
||||||
|
title: "产品服务",
|
||||||
|
links: [
|
||||||
|
{ label: "云服务器", href: "/login?redirect=%2Fcloud-servers" },
|
||||||
|
{ label: "机柜托管", href: "/login?redirect=%2Fcolocations" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function officialBoundary(page, content = {}) {
|
||||||
|
const imageErrors = [];
|
||||||
|
await page
|
||||||
|
.context()
|
||||||
|
.route(`${appOrigin}/official-test-images/**`, (route) => {
|
||||||
|
if (route.request().url().endsWith("valid-avatar.svg")) {
|
||||||
|
return route.fulfill({
|
||||||
|
contentType: "image/svg+xml",
|
||||||
|
body: '<svg xmlns="http://www.w3.org/2000/svg" width="2" height="2"><rect width="2" height="2" fill="#0052d9"/></svg>',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// HTTP 成功但图片不可解码,真实触发 img error;不放宽共享 console 错误门禁。
|
||||||
|
imageErrors.push(new URL(route.request().url()).pathname);
|
||||||
|
return route.fulfill({
|
||||||
|
contentType: "image/png",
|
||||||
|
body: "invalid PNG bytes",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
await page.context().route(`${backendOrigin}/console/v1/**`, (route) => {
|
||||||
|
const path = new URL(route.request().url()).pathname.replace(
|
||||||
|
/^\/console\/v1/,
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
const reply = (data) =>
|
||||||
|
route.fulfill({ json: { status: 200, msg: "", data } });
|
||||||
|
if (path === "/official_site_cms/content") return reply({ content });
|
||||||
|
if (path === "/product/group/first") {
|
||||||
|
return reply({
|
||||||
|
list: [
|
||||||
|
{ id: 1, name: "云服务器" },
|
||||||
|
{ id: 2, name: "物理服务器" },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (path === "/product/group/second") {
|
||||||
|
return reply({ list: [{ id: 11, parent_id: 1, name: "公开云线路" }] });
|
||||||
|
}
|
||||||
|
if (path === "/product") return reply({ list: [], count: 0 });
|
||||||
|
if (path === "/ticket/config") return reply({ ticket_notice_open: 0 });
|
||||||
|
if (["/ticket/type", "/ticket/department"].includes(path))
|
||||||
|
return reply({ list: [] });
|
||||||
|
return route.fallback();
|
||||||
|
});
|
||||||
|
return { imageErrors };
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [label, destination, content] of [
|
||||||
|
["浏览产品服务", "/products", {}],
|
||||||
|
["浏览产品服务", "/products", cmsContent()],
|
||||||
|
["查看云服务器", "/products/cloud-server", cmsContent()],
|
||||||
|
["查看物理服务器", "/products/dedicated-server", cmsContent()],
|
||||||
|
]) {
|
||||||
|
test(
|
||||||
|
`official purchase CTA opens ${destination} anonymously with ${Object.keys(content).length ? "CMS" : "default"} content`,
|
||||||
|
{ timeout: 30000 },
|
||||||
|
(t) =>
|
||||||
|
scenario(t, {}, async (page) => {
|
||||||
|
await officialBoundary(page, content);
|
||||||
|
await page.goto(`${appOrigin}/official-site`);
|
||||||
|
if (content.hero) {
|
||||||
|
await page
|
||||||
|
.getByRole("heading", { name: content.hero.title, exact: true })
|
||||||
|
.waitFor();
|
||||||
|
assert.equal(
|
||||||
|
await page
|
||||||
|
.getByRole("link", { name: "客户自定义方案", exact: true })
|
||||||
|
.getAttribute("href"),
|
||||||
|
"https://customer.example.invalid/plan",
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
await page
|
||||||
|
.getByRole("heading", { name: "客户自定义云方案", exact: true })
|
||||||
|
.count(),
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await page.getByRole("link", { name: label, exact: true }).click();
|
||||||
|
await eventually(
|
||||||
|
() => new URL(page.url()).pathname,
|
||||||
|
(path) => path === destination,
|
||||||
|
"Public purchase CTA must reach its product route without a login gate",
|
||||||
|
);
|
||||||
|
await page.locator(".public-order-layout").waitFor();
|
||||||
|
assert.equal(await page.locator("#account").count(), 0);
|
||||||
|
return {
|
||||||
|
anonymousDestination: destination,
|
||||||
|
customContentPreserved: !!content.hero,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test(
|
||||||
|
"official cabinet consultation reaches an implemented support flow",
|
||||||
|
{ timeout: 30000 },
|
||||||
|
(t) =>
|
||||||
|
scenario(t, { loginMode: "password-only" }, async (page) => {
|
||||||
|
const content = cmsContent();
|
||||||
|
content.contact = { is_show: false };
|
||||||
|
content.sectionOrder = ["products", "infrastructure", "reviews"];
|
||||||
|
await officialBoundary(page, content);
|
||||||
|
await page.goto(`${appOrigin}/official-site`);
|
||||||
|
await page
|
||||||
|
.getByRole("heading", { name: "为客户保留的自定义标题", exact: true })
|
||||||
|
.waitFor();
|
||||||
|
assert.equal(
|
||||||
|
await page.locator("#contact").count(),
|
||||||
|
0,
|
||||||
|
"CMS contact visibility must not be overridden",
|
||||||
|
);
|
||||||
|
await page
|
||||||
|
.getByText("机柜托管及定制方案请登录后提交工单咨询", { exact: true })
|
||||||
|
.waitFor();
|
||||||
|
await page
|
||||||
|
.getByRole("link", { name: "了解机柜托管", exact: true })
|
||||||
|
.click();
|
||||||
|
await page.getByRole("button", { name: "登录", exact: true }).waitFor();
|
||||||
|
assert.equal(
|
||||||
|
new URL(page.url()).searchParams.get("redirect"),
|
||||||
|
"/tickets/create",
|
||||||
|
);
|
||||||
|
await page.locator("#account").fill(loginAccount);
|
||||||
|
await page.locator("#password").fill(loginPassword);
|
||||||
|
await page.getByRole("button", { name: "登录", exact: true }).click();
|
||||||
|
await page.waitForURL((url) => url.pathname === "/tickets/create");
|
||||||
|
await page.locator(".ticket-create").waitFor();
|
||||||
|
return {
|
||||||
|
consultationAvailable: true,
|
||||||
|
supportRouteOpened: "/tickets/create",
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
"official configurable images fall back after real image decoding failures",
|
||||||
|
{ timeout: 30000 },
|
||||||
|
(t) =>
|
||||||
|
scenario(t, {}, async (page) => {
|
||||||
|
const content = cmsContent();
|
||||||
|
content.brand = {
|
||||||
|
name: "破碎工坊网络",
|
||||||
|
logoUrl: "/official-test-images/logo.png",
|
||||||
|
};
|
||||||
|
content.footer.qrImage = "/official-test-images/qr.png";
|
||||||
|
content.footer.qrCaption = "扫码咨询";
|
||||||
|
content.reviews = {
|
||||||
|
is_show: true,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
author: "Sophon",
|
||||||
|
company: "",
|
||||||
|
quote: "保留原有客户评价",
|
||||||
|
avatar: "/official-test-images/avatar.png",
|
||||||
|
is_show: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
author: "Ramdisk",
|
||||||
|
company: "",
|
||||||
|
quote: "保留可用客户头像",
|
||||||
|
avatar: "/official-test-images/valid-avatar.svg",
|
||||||
|
is_show: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const boundary = await officialBoundary(page, content);
|
||||||
|
await page.goto(`${appOrigin}/official-site`);
|
||||||
|
await page.getByText("保留原有客户评价", { exact: true }).waitFor();
|
||||||
|
await eventually(
|
||||||
|
() => [...new Set(boundary.imageErrors)].sort(),
|
||||||
|
(paths) => paths.length === 3,
|
||||||
|
"The browser must request each configured bad image",
|
||||||
|
);
|
||||||
|
await eventually(
|
||||||
|
() => page.locator(".official-brand-mark").count(),
|
||||||
|
(count) => count === 2,
|
||||||
|
"Both failed brand images must use the existing brand mark",
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
await page.locator(".official-review-avatar").textContent(),
|
||||||
|
"S",
|
||||||
|
);
|
||||||
|
await page
|
||||||
|
.getByText("二维码暂时无法显示,请使用工单咨询", { exact: true })
|
||||||
|
.waitFor();
|
||||||
|
assert.equal(
|
||||||
|
await page.locator(".official-site img").count(),
|
||||||
|
1,
|
||||||
|
"Only the successfully decoded original customer avatar should remain",
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
await page
|
||||||
|
.getByRole("img", { name: "Ramdisk", exact: true })
|
||||||
|
.evaluate((image) => image.complete && image.naturalWidth > 0),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
assert.equal(await page.getByText("Sophon", { exact: true }).count(), 1);
|
||||||
|
await page.setViewportSize({ width: 390, height: 844 });
|
||||||
|
assert.equal(
|
||||||
|
await page.evaluate(
|
||||||
|
() => document.documentElement.scrollWidth <= window.innerWidth,
|
||||||
|
),
|
||||||
|
true,
|
||||||
|
"Image fallbacks and consultation text must fit the mobile viewport",
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
brandFallbacks: 2,
|
||||||
|
customerIdentityPreserved: true,
|
||||||
|
qrReplacedByConsultationText: true,
|
||||||
|
imageFailure: "HTTP 200 with invalid PNG bytes",
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
Reference in new issue
Block a user