Files
console-dev/app/pages/security/domains/index.vue
T
cneicy 42dfead9cd
前端验证 / verify (push) Failing after 15m3s
feat(ui): 优化控制台响应式布局与视觉体验
- 统一页面标题、圆角、字体和响应式断点,调整暗色主题及图表配色
- 新增表格列标签插件,在窄屏下将表格转换为卡片布局,优化实例、订单、工单等列表展示
- 支持移动端点击订单行进入详情,并完善订单凭据的打印布局
- 在财务中心展示本月及累计消费,补充充值关闭时的提示
- 优化工单详情布局,将附件上传入口整合至富文本编辑器工具栏
- 调整实例操作按钮、SSH 密钥操作及安全菜单图标
- 为多个页面显式导入 definePageMeta,完善页面元数据声明
2026-11-07 00:09:57 +08:00

1177 lines
43 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script>
/**
* 域名白名单:按主机及审核状态查询记录;提交前必须加载可用主机、实名认证状态和当前主机对应的动态表单。
*/
import { definePageMeta } from "#imports";
import "~/assets/styles/domains.GiD6NSti.css";
definePageMeta({
alias: [`/security/domain-whitelist`],
});
import { toDisplayString } from "vue";
import { renderList } from "vue";
import { onMounted } from "vue";
import { onBeforeUnmount } from "vue";
import { createBlock } from "vue";
import { ref } from "vue";
import { withCtx } from "vue";
import { createCommentVNode } from "vue";
import { computed } from "vue";
import { isRef } from "vue";
import { createElementBlock } from "vue";
import { Fragment } from "vue";
import { createElementVNode } from "vue";
import { createTextVNode } from "vue";
import { unref } from "vue";
import { defineComponent } from "vue";
import { createVNode } from "vue";
import { openBlock } from "vue";
import { useRoute } from "#app";
import { NuxtLink } from "#components";
import { sanitizeMessage } from "~/lib/auth.js";
import { useApi } from "~/lib/auth.js";
import dialog from "primevue/dialog";
import button from "primevue/button";
import { t as _plugin_vue_export_helper_t } from "~/lib/_plugin-vue_export-helper.js";
import message from "primevue/message";
import inputtext from "primevue/inputtext";
import { n as product_name_n } from "~/lib/product-name.js";
import { r as dict_r } from "~/lib/dict.js";
import datatable from "primevue/datatable";
import paginator from "primevue/paginator";
import select from "primevue/select";
import column from "primevue/column";
import ListEmpty from "~/components/ListEmpty.vue";
import { t as useListPage_t } from "~/lib/useListPage.js";
import textarea from "primevue/textarea";
import tag from "primevue/tag";
import { t as composables_t } from "~/lib/composables-2.js";
var ve = {
class: `list-page`,
},
ye = {
class: `list-head`,
},
be = {
class: `list-head-actions`,
},
xe = {
class: `list-toolbar`,
},
Se = {
class: `list-toolbar-right`,
},
Ce = {
class: `list-table`,
},
we = {
class: `col-mono`,
},
Te = {
class: `sub`,
},
Ee = {
key: 0,
},
De = {
key: 1,
},
Oe = {
key: 6,
class: `list-foot`,
},
ke = {
class: `form`,
},
Ae = {
class: `field`,
},
je = {
key: 0,
class: `req`,
},
Me = {
key: 3,
class: `tip`,
},
Ne = {
key: 1,
class: `tip`,
},
Pe = {
key: 2,
class: `tip`,
},
Fe = {
class: `confirm-text`,
},
b = _plugin_vue_export_helper_t(
defineComponent({
__name: `index`,
setup(h) {
composables_t({
alias: `/security/domain-whitelist`,
});
let y = useApi(),
b = useRoute(),
x = ref(Number(b.query.host_id) || null),
S = ref(null),
{
list: Ie,
total: C,
loading: w,
error: T,
page: E,
limit: D,
onPage: Le,
reload: O,
} = useListPage_t(`/domain_whitelist/record`, {
limit: 10,
params: () => ({
host_id: x.value ?? ``,
status: S.value ?? ``,
}),
}),
k = computed(() => x.value !== null || S.value !== null);
// 清除主机和状态筛选后重新查询记录。
function Re() {
((x.value = null), (S.value = null), O());
}
let A = ref([]),
j = ref(!0),
M = ref(``),
N = ref(``),
P = ref(!1),
F = ref(!1),
I = !1,
L = 0,
R = 0;
onBeforeUnmount(() => {
((I = !0), L++, R++, Y++);
});
let ze = computed(() => [
{
label: `全部主机`,
value: null,
},
...A.value.map((e) => ({
label: `${e.name}(${product_name_n(e.product_name)})`,
value: e.id,
})),
]),
Be = [
{
label: `全部状态`,
value: null,
},
{
label: `待处理`,
value: 0,
},
{
label: `已通过`,
value: 1,
},
{
label: `已驳回`,
value: -1,
},
];
// 独立加载可申请白名单的主机列表,并校验响应为数组;旧请求或离页响应不回写。
async function Ve() {
let e = ++L;
((P.value = !0), (M.value = ``));
try {
let t = await y.get(`/domain_whitelist/host`);
if (I || e !== L) return;
if (!Array.isArray(t?.list)) throw Error(`可用主机响应无效`);
A.value = t.list;
} catch (t) {
if (I || e !== L) return;
((A.value = []),
(M.value = t instanceof Error ? t.message : `可用主机加载失败`));
} finally {
!I && e === L && (P.value = !1);
}
}
// 明确验证服务端实名标记的允许格式,不能把未知响应默认为已实名。
async function He() {
let e = ++R;
((F.value = !0), (N.value = ``));
try {
let t = await y.get(`/certification/info`);
if (I || e !== R) return;
if (![!0, !1, 1, 0, `1`, `0`].includes(t?.is_certification))
throw Error(`实名状态响应无效`);
j.value =
t.is_certification === !0 || Number(t.is_certification) === 1;
} catch (t) {
if (I || e !== R) return;
N.value = t instanceof Error ? t.message : `实名状态加载失败`;
} finally {
!I && e === R && (F.value = !1);
}
}
// 用户文案区分已通过、已驳回、人工审核与自动处理中。
function Ue(e) {
return e.status === 1
? `已通过`
: e.status === -1
? `已驳回`
: e.need_review
? `等待人工审核`
: `处理中`;
}
// 状态颜色与审核结果保持对应,不以有记录就表示已生效。
function We(e) {
return e.status === 1
? `success`
: e.status === -1
? `danger`
: e.need_review
? `warn`
: `info`;
}
let z = ref(``),
Ge = ref(`success`);
// 统一设置操作反馈与级别,避免撤销失败仍显示成功样式。
function B(e, t = `success`) {
((z.value = e), (Ge.value = t));
}
let V = ref(!1),
H = ref(null),
U = ref(null),
W = ref(!1),
G = ref({}),
K = ref(``),
q = ref(!1),
J = ref(null),
Y = 0;
// 打开申请并选定主机,表单字段必须重新按该主机加载。
function X(e) {
if (!q.value)
return (
(V.value = !0),
(K.value = ``),
(H.value = e ?? x.value ?? null),
Ke()
);
}
// 每次切换主机先清空旧字段,响应必须同时匹配请求序号与主机 ID 才可提交。
async function Ke() {
let e = ++Y,
t = H.value;
if (
((W.value = !!t),
(K.value = ``),
(U.value = null),
(J.value = null),
(G.value = {}),
t)
)
try {
let n = await y.get(`/domain_whitelist/form`, {
host_id: t,
});
if (e !== Y || t !== H.value) return;
((U.value = n), (J.value = t));
let r = {};
for (let e of n.fields ?? []) r[e.name] = ``;
G.value = r;
} catch (n) {
if (e !== Y || t !== H.value) return;
K.value =
n instanceof Error ? n.message : `该主机暂不支持域名过白`;
} finally {
e === Y && (W.value = !1);
}
}
// 只提交当前已加载主机表单,且实名/主机前置查询无错误;校验动态必填项后创建记录。
async function qe() {
if (!(
q.value ||
W.value ||
P.value ||
F.value ||
M.value ||
N.value ||
!U.value ||
!H.value ||
J.value !== H.value ||
!j.value
)) {
K.value = ``;
for (let e of U.value.fields ?? [])
if (e.required && !String(G.value[e.name] ?? ``).trim()) {
K.value = `请填写${e.title}`;
return;
}
q.value = !0;
try {
let e = await y.post(`/domain_whitelist/record`, {
host_id: H.value,
...G.value,
});
((V.value = !1),
B(e.pending ? `已提交,等待人工审核` : `提交成功`),
O());
} catch (e) {
K.value = e instanceof Error ? e.message : `提交失败`;
} finally {
q.value = !1;
}
}
}
let Z = ref(!1),
Q = ref(null),
$ = ref(!1);
// 打开撤销确认并保存目标,不立即删除。
function Je(e) {
((Q.value = e), (Z.value = !0));
}
// 仅对服务端允许撤销的记录发送删除,完成后刷新列表。
async function Ye() {
if (!Q.value || $.value || !Q.value.can_remove) return;
let e = Q.value;
$.value = !0;
try {
(await y.del(`/domain_whitelist/record/${e.id}`),
(Z.value = !1),
B(`已撤销`),
O());
} catch (e) {
((Z.value = !1),
B(e instanceof Error ? e.message : `撤销失败`, `error`));
} finally {
$.value = !1;
}
}
return (
onMounted(async () => {
(He(), await Ve(), x.value && X(x.value));
}),
// 记录列表、主机实名前置状态和申请动态字段分别展示;申请字段必须属于当前选择主机。
(n, r) => {
let a = NuxtLink,
c = message,
h = button,
v = select,
ne = ListEmpty,
y = column,
me = tag,
_e = datatable,
b = paginator,
I = textarea,
L = inputtext,
R = dialog;
return (
openBlock(),
createElementBlock(`div`, ve, [
createVNode(
c,
{
severity: `info`,
closable: !1,
class: `domain-notice`,
},
{
default: withCtx(() => [
(r[16] ||= createElementVNode(
`strong`,
null,
`注意事项`,
-1,
)),
createElementVNode(`p`, null, [
(r[10] ||= createTextVNode(
`1. 在中国大陆机房提供 HTTP/HTTPS 服务前,请先完成域名备案并添加白名单。`,
-1,
)),
(r[11] ||= createElementVNode(`br`, null, null, -1)),
(r[12] ||= createTextVNode(
`2. 关联产品逾期或删除后,请及时核对域名白名单状态。`,
-1,
)),
(r[13] ||= createElementVNode(`br`, null, null, -1)),
(r[14] ||= createTextVNode(
`3. 如需了解域名备案,可前往`,
-1,
)),
createVNode(
a,
{
to: `/help`,
},
{
default: withCtx(() => [
...(r[9] ||= [createTextVNode(`帮助中心`, -1)]),
]),
_: 1,
},
),
(r[15] ||= createTextVNode(`。`, -1)),
]),
]),
_: 1,
},
),
createElementVNode(`div`, ye, [
(r[17] ||= createElementVNode(`h2`, null, `域名白名单`, -1)),
createElementVNode(`div`, be, [
createVNode(
h,
{
label: `添加域名白名单`,
icon: `pi pi-plus`,
disabled:
!unref(j) ||
unref(A).length === 0 ||
unref(P) ||
unref(F) ||
!!unref(M) ||
!!unref(N),
onClick: (r[0] ||= (e) => X()),
},
null,
8,
[`disabled`],
),
]),
]),
unref(M)
? (openBlock(),
createBlock(
c,
{
key: 0,
severity: `error`,
closable: !1,
},
{
default: withCtx(() => [
createTextVNode(toDisplayString(unref(M)) + ` `, 1),
createVNode(h, {
label: `重试可用主机`,
text: ``,
onClick: Ve,
}),
]),
_: 1,
},
))
: createCommentVNode(``, !0),
unref(N)
? (openBlock(),
createBlock(
c,
{
key: 1,
severity: `error`,
closable: !1,
},
{
default: withCtx(() => [
createTextVNode(toDisplayString(unref(N)) + ` `, 1),
createVNode(h, {
label: `重试实名状态`,
text: ``,
onClick: He,
}),
]),
_: 1,
},
))
: createCommentVNode(``, !0),
!unref(N) && !unref(F) && !unref(j)
? (openBlock(),
createBlock(
c,
{
key: 2,
severity: `warn`,
closable: !1,
},
{
default: withCtx(() => [
(r[19] ||= createTextVNode(
` 提交域名过白前请先完成实名认证,前往`,
-1,
)),
createVNode(
a,
{
to: `/account/realname`,
class: `cert-link`,
},
{
default: withCtx(() => [
...(r[18] ||= [
createTextVNode(`实名认证`, -1),
]),
]),
_: 1,
},
),
(r[20] ||= createTextVNode(`。 `, -1)),
]),
_: 1,
},
))
: !unref(M) && !unref(P) && unref(A).length === 0
? (openBlock(),
createBlock(
c,
{
key: 3,
severity: `info`,
closable: !1,
},
{
default: withCtx(() => [
...(r[21] ||= [
createTextVNode(
` 暂无可提交过白的主机(仅支持已开通对应机房过白服务的产品)。 `,
-1,
),
]),
]),
_: 1,
},
))
: createCommentVNode(``, !0),
createElementVNode(`div`, xe, [
createElementVNode(`div`, Se, [
createVNode(
v,
{
modelValue: unref(x),
"onUpdate:modelValue": (r[1] ||= (e) =>
isRef(x) ? (x.value = e) : null),
class: `list-select`,
options: unref(ze),
optionLabel: `label`,
optionValue: `value`,
onChange: unref(O),
},
null,
8,
[`modelValue`, `options`, `onChange`],
),
createVNode(
v,
{
modelValue: unref(S),
"onUpdate:modelValue": (r[2] ||= (e) =>
isRef(S) ? (S.value = e) : null),
class: `list-select`,
options: Be,
optionLabel: `label`,
optionValue: `value`,
onChange: unref(O),
},
null,
8,
[`modelValue`, `onChange`],
),
unref(k)
? (openBlock(),
createBlock(h, {
key: 0,
label: `清除筛选`,
icon: `pi pi-filter-slash`,
severity: `secondary`,
text: ``,
size: `small`,
onClick: Re,
}))
: createCommentVNode(``, !0),
]),
]),
unref(z)
? (openBlock(),
createBlock(
c,
{
key: 4,
severity: unref(Ge),
closable: !1,
},
{
default: withCtx(() => [
createTextVNode(toDisplayString(unref(z)), 1),
]),
_: 1,
},
8,
[`severity`],
))
: createCommentVNode(``, !0),
createElementVNode(`div`, Ce, [
createVNode(
_e,
{
value: unref(Ie),
loading: unref(w),
lazy: ``,
rows: unref(D),
totalRecords: unref(C),
first: (unref(E) - 1) * unref(D),
dataKey: `id`,
},
{
empty: withCtx(() => [
createVNode(
ne,
{
icon: `pi pi-shield`,
title: unref(w) ? `加载中…` : `还没有过白记录`,
description: unref(k)
? `换个筛选条件试试`
: `在中国大陆机房提供网站服务前,需要先把域名加入机房白名单`,
},
{
default: withCtx(() => [
!unref(k) && unref(j) && unref(A).length
? (openBlock(),
createBlock(h, {
key: 0,
label: `添加域名白名单`,
icon: `pi pi-plus`,
size: `small`,
onClick: (r[3] ||= (e) => X()),
}))
: createCommentVNode(``, !0),
]),
_: 1,
},
8,
[`title`, `description`],
),
]),
default: withCtx(() => [
createVNode(
y,
{
header: `ID`,
style: {
width: `70px`,
},
},
{
body: withCtx(({ data: t }) => [
createElementVNode(
`span`,
we,
`#` + toDisplayString(t.id),
1,
),
]),
_: 1,
},
),
createVNode(
y,
{
header: `主机`,
style: {
"min-width": `170px`,
},
},
{
body: withCtx(({ data: t }) => [
createElementVNode(
`div`,
null,
toDisplayString(t.host_name || `—`),
1,
),
createElementVNode(
`div`,
Te,
`#` +
toDisplayString(t.host_id) +
` · ` +
toDisplayString(
unref(product_name_n)(t.product_name) ||
`—`,
),
1,
),
]),
_: 1,
},
),
createVNode(
y,
{
header: `域名`,
style: {
"min-width": `190px`,
},
},
{
body: withCtx(({ data: n }) => [
(openBlock(!0),
createElementBlock(
Fragment,
null,
renderList(
n.domains,
(t) => (
openBlock(),
createElementBlock(
`div`,
{
key: t,
class: `col-mono`,
},
toDisplayString(t),
1,
)
),
),
128,
)),
n.domains?.length
? createCommentVNode(``, !0)
: (openBlock(),
createElementBlock(`span`, Ee, `—`)),
]),
_: 1,
},
),
createVNode(
y,
{
header: `状态`,
style: {
width: `120px`,
},
},
{
body: withCtx(({ data: e }) => [
createVNode(
me,
{
value: Ue(e),
severity: We(e),
},
null,
8,
[`value`, `severity`],
),
]),
_: 1,
},
),
createVNode(
y,
{
header: `驳回原因`,
style: {
"min-width": `160px`,
},
},
{
body: withCtx(({ data: t }) => [
createTextVNode(
toDisplayString(
unref(sanitizeMessage)(t.reject_reason) ||
`—`,
),
1,
),
]),
_: 1,
},
),
createVNode(
y,
{
header: `备注`,
style: {
"min-width": `120px`,
},
},
{
body: withCtx(({ data: t }) => [
createTextVNode(
toDisplayString(t.remark || `—`),
1,
),
]),
_: 1,
},
),
createVNode(
y,
{
header: `提交时间`,
style: {
"min-width": `160px`,
},
},
{
body: withCtx(({ data: t }) => [
createTextVNode(
toDisplayString(
(`formatTime` in n
? n.formatTime
: unref(dict_r))(t.create_time),
),
1,
),
]),
_: 1,
},
),
createVNode(
y,
{
header: `操作`,
style: {
width: `90px`,
},
},
{
body: withCtx(({ data: e }) => [
e.can_remove
? (openBlock(),
createBlock(
h,
{
key: 0,
label: `撤销`,
size: `small`,
severity: `danger`,
text: ``,
onClick: (t) => Je(e),
},
null,
8,
[`onClick`],
))
: (openBlock(),
createElementBlock(`span`, De, `—`)),
]),
_: 1,
},
),
]),
_: 1,
},
8,
[`value`, `loading`, `rows`, `totalRecords`, `first`],
),
]),
unref(T)
? (openBlock(),
createBlock(
c,
{
key: 5,
severity: `error`,
closable: !1,
},
{
default: withCtx(() => [
createTextVNode(toDisplayString(unref(T)), 1),
]),
_: 1,
},
))
: createCommentVNode(``, !0),
unref(C) > 0
? (openBlock(),
createElementBlock(`div`, Oe, [
(r[22] ||= createElementVNode(
`div`,
{
class: `list-bulk`,
},
null,
-1,
)),
createVNode(
b,
{
rows: unref(D),
totalRecords: unref(C),
first: (unref(E) - 1) * unref(D),
rowsPerPageOptions: [10, 20, 50],
onPage: unref(Le),
},
null,
8,
[`rows`, `totalRecords`, `first`, `onPage`],
),
]))
: createCommentVNode(``, !0),
createVNode(
R,
{
visible: unref(V),
"onUpdate:visible": (r[6] ||= (e) =>
isRef(V) ? (V.value = e) : null),
header: `添加域名白名单`,
modal: ``,
style: {
width: `640px`,
},
},
{
footer: withCtx(() => [
createVNode(h, {
label: `取消`,
severity: `secondary`,
text: ``,
onClick: (r[5] ||= (e) => (V.value = !1)),
}),
createVNode(
h,
{
label: `提交`,
loading: unref(q),
disabled:
unref(W) ||
!unref(U) ||
unref(J) !== unref(H) ||
!unref(j),
onClick: qe,
},
null,
8,
[`loading`, `disabled`],
),
]),
default: withCtx(() => [
createElementVNode(`div`, ke, [
createElementVNode(`div`, Ae, [
(r[23] ||= createElementVNode(
`label`,
null,
`主机`,
-1,
)),
createVNode(
v,
{
modelValue: unref(H),
"onUpdate:modelValue": (r[4] ||= (e) =>
isRef(H) ? (H.value = e) : null),
disabled: unref(q),
loading: unref(W),
options: unref(A),
optionLabel: `name`,
optionValue: `id`,
placeholder: `选择主机`,
filter: ``,
onChange: Ke,
},
null,
8,
[`modelValue`, `disabled`, `loading`, `options`],
),
]),
unref(U)
? (openBlock(!0),
createElementBlock(
Fragment,
{
key: 0,
},
renderList(
unref(U).fields,
(t) => (
openBlock(),
createElementBlock(
`div`,
{
key: t.name,
class: `field`,
},
[
createElementVNode(`label`, null, [
createTextVNode(
toDisplayString(t.title),
1,
),
t.required
? (openBlock(),
createElementBlock(`span`, je, `*`))
: createCommentVNode(``, !0),
]),
t.type === `select`
? (openBlock(),
createBlock(
v,
{
key: 0,
modelValue: unref(G)[t.name],
"onUpdate:modelValue": (e) =>
(unref(G)[t.name] = e),
options: t.options || [],
optionLabel: `label`,
optionValue: `value`,
},
null,
8,
[
`modelValue`,
`onUpdate:modelValue`,
`options`,
],
))
: t.type === `textarea`
? (openBlock(),
createBlock(
I,
{
key: 1,
modelValue: unref(G)[t.name],
"onUpdate:modelValue": (e) =>
(unref(G)[t.name] = e),
rows: 3,
autoResize: ``,
},
null,
8,
[
`modelValue`,
`onUpdate:modelValue`,
],
))
: (openBlock(),
createBlock(
L,
{
key: 2,
modelValue: unref(G)[t.name],
"onUpdate:modelValue": (e) =>
(unref(G)[t.name] = e),
},
null,
8,
[
`modelValue`,
`onUpdate:modelValue`,
],
)),
t.tip
? (openBlock(),
createElementBlock(
`div`,
Me,
toDisplayString(t.tip),
1,
))
: createCommentVNode(``, !0),
],
)
),
),
128,
))
: createCommentVNode(``, !0),
unref(U)?.ips?.length
? (openBlock(),
createElementBlock(
`div`,
Ne,
` 将为主机 IP ` +
toDisplayString(unref(U).ips.join(`、`)) +
` 提交过白。 `,
1,
))
: createCommentVNode(``, !0),
unref(U)?.need_review
? (openBlock(),
createElementBlock(
`div`,
Pe,
` 该机房需要人工审核:提交后由客服处理,处理进度会更新在列表里。 `,
))
: createCommentVNode(``, !0),
unref(K)
? (openBlock(),
createBlock(
c,
{
key: 3,
severity: `error`,
closable: !1,
},
{
default: withCtx(() => [
createTextVNode(toDisplayString(unref(K)), 1),
]),
_: 1,
},
))
: createCommentVNode(``, !0),
]),
]),
_: 1,
},
8,
[`visible`],
),
createVNode(
R,
{
visible: unref(Z),
"onUpdate:visible": (r[8] ||= (e) =>
isRef(Z) ? (Z.value = e) : null),
header: `撤销过白`,
modal: ``,
style: {
width: `420px`,
},
},
{
footer: withCtx(() => [
createVNode(h, {
label: `取消`,
severity: `secondary`,
text: ``,
onClick: (r[7] ||= (e) => (Z.value = !1)),
}),
createVNode(
h,
{
label: `确认撤销`,
severity: `danger`,
loading: unref($),
onClick: Ye,
},
null,
8,
[`loading`],
),
]),
default: withCtx(() => [
createElementVNode(
`p`,
Fe,
` 确认撤销域名 ` +
toDisplayString(unref(Q)?.domains?.join(`、`)) +
` 的白名单?撤销后该域名将无法通过此机房访问。 `,
1,
),
]),
_: 1,
},
8,
[`visible`],
),
])
);
}
);
},
}),
[[`__scopeId`, `data-v-0a28aca5`]],
);
export {};
export default b;
</script>