feat:
1. 更新框架 2. 修复框架更新后导致的问题 3. 消息系统升级
This commit is contained in:
@@ -40,6 +40,20 @@ async function initSetupVbenForm() {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 大陆手机号:必填 + 11 位且号段 13-19(与后端 UtilsService::checkPhone 一致)
|
||||||
|
* 表单字段 rules: 'mobile' 即可,无需再叠 required
|
||||||
|
*/
|
||||||
|
mobile: (value, _params, ctx) => {
|
||||||
|
const phone = typeof value === 'string' ? value.trim() : value;
|
||||||
|
if (phone === undefined || phone === null || phone === '') {
|
||||||
|
return $t('ui.formRules.required', [ctx.label]);
|
||||||
|
}
|
||||||
|
if (!/^1[3456789]\d{9}$/.test(String(phone))) {
|
||||||
|
return $t('ui.formRules.mobile', [ctx.label]);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
/**
|
/**
|
||||||
* 诊所列表 VIP 单元格:徽标+会员名,点击打开 VIP 入口弹窗
|
* 诊所列表 VIP 单元格:徽标+会员名+到期时间,点击打开 VIP 入口弹窗
|
||||||
*/
|
*/
|
||||||
import VipBadgeCombo from '#/components/vip/VipBadgeCombo.vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
defineProps<{
|
import VipBadgeCombo from '#/components/vip/VipBadgeCombo.vue';
|
||||||
|
import { formatVipDurationExpireLine } from '#/utils/vip';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
vip?: Record<string, any> | null;
|
vip?: Record<string, any> | null;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
click: [];
|
click: [];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
/** 副行展示时长类型与到期,方便列表一眼看到过期时间 */
|
||||||
|
const subText = computed(() => formatVipDurationExpireLine(props.vip));
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
@@ -29,9 +35,7 @@ const emit = defineEmits<{
|
|||||||
/>
|
/>
|
||||||
<div class="store-vip-cell__text">
|
<div class="store-vip-cell__text">
|
||||||
<div class="store-vip-cell__name">{{ vip?.level_name || '普通会员' }}</div>
|
<div class="store-vip-cell__name">{{ vip?.level_name || '普通会员' }}</div>
|
||||||
<div class="store-vip-cell__sub">
|
<div class="store-vip-cell__sub">{{ subText }}</div>
|
||||||
{{ vip?.duration_label || '点击管理' }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -56,14 +60,14 @@ const emit = defineEmits<{
|
|||||||
.store-vip-cell__name {
|
.store-vip-cell__name {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--foreground, #1f1f1f);
|
color: hsl(var(--foreground));
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
.store-vip-cell__sub {
|
.store-vip-cell__sub {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: #8c8c8c;
|
color: hsl(var(--muted-foreground));
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@@ -72,12 +76,4 @@ html.dark .store-vip-cell:hover,
|
|||||||
.dark .store-vip-cell:hover {
|
.dark .store-vip-cell:hover {
|
||||||
background: rgba(106, 205, 187, 0.2);
|
background: rgba(106, 205, 187, 0.2);
|
||||||
}
|
}
|
||||||
html.dark .store-vip-cell__name,
|
|
||||||
.dark .store-vip-cell__name {
|
|
||||||
color: #f4f4f5;
|
|
||||||
}
|
|
||||||
html.dark .store-vip-cell__sub,
|
|
||||||
.dark .store-vip-cell__sub {
|
|
||||||
color: #a1a1aa;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { useVbenModal } from '@vben/common-ui';
|
|||||||
import VipBadgeCombo from '#/components/vip/VipBadgeCombo.vue';
|
import VipBadgeCombo from '#/components/vip/VipBadgeCombo.vue';
|
||||||
import VipStoreRecordsModal from '#/components/vip/VipStoreRecordsModal.vue';
|
import VipStoreRecordsModal from '#/components/vip/VipStoreRecordsModal.vue';
|
||||||
import VipUpgradeModal from '#/components/vip/VipUpgradeModal.vue';
|
import VipUpgradeModal from '#/components/vip/VipUpgradeModal.vue';
|
||||||
|
import { formatVipExpireAt } from '#/utils/vip';
|
||||||
|
|
||||||
const gridApi = ref<any>();
|
const gridApi = ref<any>();
|
||||||
const onSuccess = ref<null | (() => void)>(null);
|
const onSuccess = ref<null | (() => void)>(null);
|
||||||
@@ -21,6 +22,7 @@ const title = computed(
|
|||||||
() => `VIP管理 - ${storeName.value || storeId.value || ''}`,
|
() => `VIP管理 - ${storeName.value || storeId.value || ''}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/** 当前等级 + 时长类型 */
|
||||||
const currentLevelText = computed(() => {
|
const currentLevelText = computed(() => {
|
||||||
const v = vip.value;
|
const v = vip.value;
|
||||||
if (!v) return '当前:普通会员';
|
if (!v) return '当前:普通会员';
|
||||||
@@ -32,6 +34,20 @@ const currentLevelText = computed(() => {
|
|||||||
return duration ? `当前:${level} · ${duration}` : `当前:${level}`;
|
return duration ? `当前:${level} · ${duration}` : `当前:${level}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** 到期时间独立一行,打开弹窗时一眼可见 */
|
||||||
|
const currentExpireText = computed(() => {
|
||||||
|
const v = vip.value;
|
||||||
|
if (!v) return '';
|
||||||
|
const code = String(v.level_code || 'V0').trim() || 'V0';
|
||||||
|
if (code === 'V0') return '';
|
||||||
|
const expire = formatVipExpireAt(v, {
|
||||||
|
lifetimeText: '终身',
|
||||||
|
withPrefix: false,
|
||||||
|
});
|
||||||
|
if (!expire) return '';
|
||||||
|
return expire === '终身' ? '到期时间:终身' : `到期时间:${expire}`;
|
||||||
|
});
|
||||||
|
|
||||||
const [UpgradeModalComp, upgradeModalApi] = useVbenModal({
|
const [UpgradeModalComp, upgradeModalApi] = useVbenModal({
|
||||||
connectedComponent: VipUpgradeModal,
|
connectedComponent: VipUpgradeModal,
|
||||||
});
|
});
|
||||||
@@ -107,7 +123,12 @@ function openRecords() {
|
|||||||
:level-name="vip?.level_name"
|
:level-name="vip?.level_name"
|
||||||
:duration-label="vip?.duration_label"
|
:duration-label="vip?.duration_label"
|
||||||
/>
|
/>
|
||||||
<div class="vip-hub-current__text">{{ currentLevelText }}</div>
|
<div class="vip-hub-current__meta">
|
||||||
|
<div class="vip-hub-current__text">{{ currentLevelText }}</div>
|
||||||
|
<div v-if="currentExpireText" class="vip-hub-current__expire">
|
||||||
|
{{ currentExpireText }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="vip-hub-grid">
|
<div class="vip-hub-grid">
|
||||||
<button type="button" class="vip-hub-card" @click="openUpgrade">
|
<button type="button" class="vip-hub-card" @click="openUpgrade">
|
||||||
@@ -140,11 +161,21 @@ function openRecords() {
|
|||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
background: hsl(var(--muted) / 0.25);
|
background: hsl(var(--muted) / 0.25);
|
||||||
}
|
}
|
||||||
|
.vip-hub-current__meta {
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
.vip-hub-current__text {
|
.vip-hub-current__text {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: hsl(var(--foreground));
|
color: hsl(var(--foreground));
|
||||||
}
|
}
|
||||||
|
.vip-hub-current__expire {
|
||||||
|
font-size: 12px;
|
||||||
|
color: hsl(var(--muted-foreground));
|
||||||
|
}
|
||||||
.vip-hub-grid {
|
.vip-hub-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import { notification } from 'ant-design-vue';
|
|||||||
import { Bell, Users } from 'lucide-vue-next';
|
import { Bell, Users } from 'lucide-vue-next';
|
||||||
|
|
||||||
import DoctorTransferFloat from '#/components/doctor-transfer-float/DoctorTransferFloat.vue';
|
import DoctorTransferFloat from '#/components/doctor-transfer-float/DoctorTransferFloat.vue';
|
||||||
|
import HeaderVipBadge from '#/components/vip/HeaderVipBadge.vue';
|
||||||
import SwitchAccountModal from '#/layouts/components/SwitchAccountModal.vue';
|
import SwitchAccountModal from '#/layouts/components/SwitchAccountModal.vue';
|
||||||
import { useAuthStore } from '#/store';
|
import { useAuthStore } from '#/store';
|
||||||
import { formatTimeToRelative } from '#/util/tool';
|
import { formatTimeToRelative } from '#/util/tool';
|
||||||
@@ -262,15 +263,19 @@ watch(
|
|||||||
<DoctorTransferFloat v-if="showDoctorTransfer" />
|
<DoctorTransferFloat v-if="showDoctorTransfer" />
|
||||||
</template>
|
</template>
|
||||||
<template #user-dropdown>
|
<template #user-dropdown>
|
||||||
<UserDropdown
|
<!-- VIP 徽标放在头像左侧,与用户下拉并排 -->
|
||||||
:avatar
|
<div class="flex items-center">
|
||||||
:menus
|
<HeaderVipBadge />
|
||||||
:text="userStore.userInfo?.nick_name"
|
<UserDropdown
|
||||||
:description="userStore.userInfo?.email"
|
:avatar
|
||||||
:tag-text="userStore.userInfo?.roles?.name"
|
:menus
|
||||||
@clear-preferences-and-logout="handleLogout"
|
:text="userStore.userInfo?.nick_name"
|
||||||
@logout="handleLogout"
|
:description="userStore.userInfo?.email"
|
||||||
/>
|
:tag-text="userStore.userInfo?.roles?.name"
|
||||||
|
@clear-preferences-and-logout="handleLogout"
|
||||||
|
@logout="handleLogout"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #notification>
|
<template #notification>
|
||||||
<Notification
|
<Notification
|
||||||
|
|||||||
@@ -144,3 +144,58 @@ export function useVipPermission(
|
|||||||
export function useVipGate() {
|
export function useVipGate() {
|
||||||
return useVipPermission();
|
return useVipPermission();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化 VIP 到期时间文案(列表/弹窗共用)
|
||||||
|
* @param vip 门店 VIP 信息
|
||||||
|
* @param options.emptyText 普通会员/无数据时的文案,默认空串
|
||||||
|
* @param options.lifetimeText 终身文案,默认「终身」
|
||||||
|
* @param options.withPrefix 是否加「至」前缀(非终身时)
|
||||||
|
*/
|
||||||
|
export function formatVipExpireAt(
|
||||||
|
vip?: StoreVipInfo | Record<string, any> | null,
|
||||||
|
options?: {
|
||||||
|
emptyText?: string;
|
||||||
|
lifetimeText?: string;
|
||||||
|
withPrefix?: boolean;
|
||||||
|
},
|
||||||
|
): string {
|
||||||
|
const emptyText = options?.emptyText ?? '';
|
||||||
|
const lifetimeText = options?.lifetimeText ?? '终身';
|
||||||
|
const withPrefix = options?.withPrefix ?? false;
|
||||||
|
if (!vip) return emptyText;
|
||||||
|
const code = String(vip.level_code || 'V0').trim() || 'V0';
|
||||||
|
if (code === 'V0') return emptyText;
|
||||||
|
if (vip.is_lifetime) return lifetimeText;
|
||||||
|
const raw = vip.expire_at as unknown;
|
||||||
|
if (raw == null || raw === '' || raw === 0) {
|
||||||
|
// 无到期日且非 V0:按终身展示(与列表列逻辑一致)
|
||||||
|
return lifetimeText;
|
||||||
|
}
|
||||||
|
// 后端偶发已格式化成 Y-m-d H:i:s
|
||||||
|
if (typeof raw === 'string' && Number.isNaN(Number(raw))) {
|
||||||
|
return withPrefix ? `至 ${raw}` : raw;
|
||||||
|
}
|
||||||
|
const n = Number(raw);
|
||||||
|
if (!(n > 0)) return lifetimeText;
|
||||||
|
const d = new Date(n * 1000);
|
||||||
|
if (Number.isNaN(d.getTime())) return emptyText;
|
||||||
|
const pad = (x: number) => String(x).padStart(2, '0');
|
||||||
|
const text = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
||||||
|
return withPrefix ? `至 ${text}` : text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 列表副文案:时长类型 + 到期(如「月卡 · 至 2026-08-10 …」)
|
||||||
|
*/
|
||||||
|
export function formatVipDurationExpireLine(
|
||||||
|
vip?: StoreVipInfo | Record<string, any> | null,
|
||||||
|
): string {
|
||||||
|
if (!vip) return '点击管理';
|
||||||
|
const code = String(vip.level_code || 'V0').trim() || 'V0';
|
||||||
|
if (code === 'V0') return '点击管理';
|
||||||
|
const duration = String(vip.duration_label || '').trim();
|
||||||
|
const expire = formatVipExpireAt(vip, { withPrefix: true, lifetimeText: '终身' });
|
||||||
|
if (duration && expire) return `${duration} · ${expire}`;
|
||||||
|
return duration || expire || '点击管理';
|
||||||
|
}
|
||||||
|
|||||||
@@ -50,9 +50,10 @@ export const salespersonModalFormProps: VbenFormProps = {
|
|||||||
component: 'Input',
|
component: 'Input',
|
||||||
fieldName: 'phone',
|
fieldName: 'phone',
|
||||||
label: '手机号',
|
label: '手机号',
|
||||||
rules: 'required',
|
// 与后端 checkPhone 一致:必填 + 大陆 11 位号段
|
||||||
|
rules: 'mobile',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
minLength: 11,
|
maxlength: 11,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -182,7 +182,8 @@ export const infoModalFormProps: VbenFormProps = {
|
|||||||
fieldName: 'mobile',
|
fieldName: 'mobile',
|
||||||
formItemClass: 'col-span-6',
|
formItemClass: 'col-span-6',
|
||||||
label: '手机号码',
|
label: '手机号码',
|
||||||
rules: 'required',
|
// 与后端 checkPhone 一致:必填 + 大陆 11 位号段
|
||||||
|
rules: 'mobile',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: 'VbenInput',
|
component: 'VbenInput',
|
||||||
|
|||||||
@@ -157,7 +157,8 @@ export const infoModalFormProps: VbenFormProps = {
|
|||||||
fieldName: 'mobile',
|
fieldName: 'mobile',
|
||||||
formItemClass: 'col-span-6',
|
formItemClass: 'col-span-6',
|
||||||
label: '手机号码',
|
label: '手机号码',
|
||||||
rules: 'required',
|
// 与后端 checkPhone 一致:必填 + 大陆 11 位号段
|
||||||
|
rules: 'mobile',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: 'VbenInput',
|
component: 'VbenInput',
|
||||||
|
|||||||
@@ -140,7 +140,8 @@ export function createAdminModalFormProps(
|
|||||||
},
|
},
|
||||||
fieldName: 'phone',
|
fieldName: 'phone',
|
||||||
label: '手机号',
|
label: '手机号',
|
||||||
rules: 'required',
|
// 与后端 checkPhone 一致:必填 + 大陆 11 位号段
|
||||||
|
rules: 'mobile',
|
||||||
formItemClass: 'col-span-6',
|
formItemClass: 'col-span-6',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -59,7 +59,8 @@ export const modalFormProps: VbenFormProps = {
|
|||||||
},
|
},
|
||||||
fieldName: 'phone',
|
fieldName: 'phone',
|
||||||
label: '手机号',
|
label: '手机号',
|
||||||
rules: 'required',
|
// 与后端 checkPhone 一致:必填 + 大陆 11 位号段
|
||||||
|
rules: 'mobile',
|
||||||
formItemClass: 'col-span-6',
|
formItemClass: 'col-span-6',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -67,7 +67,8 @@ export const modalFormProps: VbenFormProps = {
|
|||||||
},
|
},
|
||||||
fieldName: 'phone',
|
fieldName: 'phone',
|
||||||
label: '手机号',
|
label: '手机号',
|
||||||
rules: 'required',
|
// 与后端 checkPhone 一致:必填 + 大陆 11 位号段
|
||||||
|
rules: 'mobile',
|
||||||
help: '默认初始密码 Xk123456@',
|
help: '默认初始密码 Xk123456@',
|
||||||
dependencies: {
|
dependencies: {
|
||||||
if({ id }: { id?: number | string }) {
|
if({ id }: { id?: number | string }) {
|
||||||
|
|||||||
@@ -96,7 +96,8 @@ export const modalFormProps: VbenFormProps = {
|
|||||||
fieldName: 'mobile',
|
fieldName: 'mobile',
|
||||||
formItemClass: 'col-span-6',
|
formItemClass: 'col-span-6',
|
||||||
label: '手机号码',
|
label: '手机号码',
|
||||||
rules: 'required',
|
// 与后端 checkPhone 一致:必填 + 大陆 11 位号段
|
||||||
|
rules: 'mobile',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: 'VbenInput',
|
component: 'VbenInput',
|
||||||
|
|||||||
@@ -152,7 +152,8 @@ export const modalFormProps: VbenFormProps = {
|
|||||||
},
|
},
|
||||||
fieldName: 'mobile',
|
fieldName: 'mobile',
|
||||||
label: '联系人手机号',
|
label: '联系人手机号',
|
||||||
rules: 'required',
|
// 与后端 checkPhone 一致:必填 + 大陆 11 位号段
|
||||||
|
rules: 'mobile',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// 编辑时可选:同步修改药店管理员后台登录手机号
|
// 编辑时可选:同步修改药店管理员后台登录手机号
|
||||||
|
|||||||
@@ -137,7 +137,8 @@ export const modalFormProps: VbenFormProps = {
|
|||||||
},
|
},
|
||||||
fieldName: 'mobile',
|
fieldName: 'mobile',
|
||||||
label: '联系人手机号',
|
label: '联系人手机号',
|
||||||
rules: 'required',
|
// 与后端 checkPhone 一致:必填 + 大陆 11 位号段
|
||||||
|
rules: 'mobile',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: 'PromoterPicker',
|
component: 'PromoterPicker',
|
||||||
|
|||||||
@@ -177,7 +177,8 @@ export const modalFormProps: VbenFormProps = {
|
|||||||
},
|
},
|
||||||
fieldName: 'mobile',
|
fieldName: 'mobile',
|
||||||
label: '联系人手机号',
|
label: '联系人手机号',
|
||||||
rules: 'required',
|
// 与后端 checkPhone 一致:必填 + 大陆 11 位号段
|
||||||
|
rules: 'mobile',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// 编辑时可选:同步修改诊所管理员后台登录手机号
|
// 编辑时可选:同步修改诊所管理员后台登录手机号
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ export const gridOptions: VxeGridProps<RowType> = {
|
|||||||
field: 'vip',
|
field: 'vip',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
title: 'VIP会员',
|
title: 'VIP会员',
|
||||||
width: 160,
|
minWidth: 200,
|
||||||
|
width: 240,
|
||||||
slots: { default: 'vip' },
|
slots: { default: 'vip' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||||
|
|
||||||
|
import { formatVipExpireAt } from '#/utils/vip';
|
||||||
|
|
||||||
import { getVipStoreList } from '../api';
|
import { getVipStoreList } from '../api';
|
||||||
|
|
||||||
export const gridOptions: VxeGridProps<any> = {
|
export const gridOptions: VxeGridProps<any> = {
|
||||||
@@ -32,21 +34,10 @@ export const gridOptions: VxeGridProps<any> = {
|
|||||||
{
|
{
|
||||||
field: 'expire_at',
|
field: 'expire_at',
|
||||||
title: '到期时间',
|
title: '到期时间',
|
||||||
width: 170,
|
width: 180,
|
||||||
formatter: ({ row }) => {
|
formatter: ({ row }) =>
|
||||||
if (!row.vip) return '-';
|
formatVipExpireAt(row.vip, { emptyText: '-', lifetimeText: '终身' }) ||
|
||||||
if (row.vip.is_lifetime) return '终身';
|
'-',
|
||||||
const expireAt = Number(row.vip.expire_at || 0);
|
|
||||||
if (!expireAt) {
|
|
||||||
return row.vip.level_code === 'V0' ? '-' : '终身';
|
|
||||||
}
|
|
||||||
if (typeof row.vip.expire_at === 'string' && String(row.vip.expire_at).includes('-')) {
|
|
||||||
return row.vip.expire_at;
|
|
||||||
}
|
|
||||||
const d = new Date(expireAt * 1000);
|
|
||||||
const pad = (n: number) => String(n).padStart(2, '0');
|
|
||||||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{ title: '操作', slots: { default: 'action' }, width: 100, fixed: 'right' },
|
{ title: '操作', slots: { default: 'action' }, width: 100, fixed: 'right' },
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
"formRules": {
|
"formRules": {
|
||||||
"required": "Please enter {0}",
|
"required": "Please enter {0}",
|
||||||
"selectRequired": "Please select {0}",
|
"selectRequired": "Please select {0}",
|
||||||
|
"mobile": "Please enter a valid {0}",
|
||||||
"minLength": "{0} must be at least {1} characters",
|
"minLength": "{0} must be at least {1} characters",
|
||||||
"maxLength": "{0} can be at most {1} characters",
|
"maxLength": "{0} can be at most {1} characters",
|
||||||
"length": "{0} must be {1} characters long",
|
"length": "{0} must be {1} characters long",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
"formRules": {
|
"formRules": {
|
||||||
"required": "请输入{0}",
|
"required": "请输入{0}",
|
||||||
"selectRequired": "请选择{0}",
|
"selectRequired": "请选择{0}",
|
||||||
|
"mobile": "请输入正确的{0}",
|
||||||
"minLength": "{0}至少{1}个字符",
|
"minLength": "{0}至少{1}个字符",
|
||||||
"maxLength": "{0}最多{1}个字符",
|
"maxLength": "{0}最多{1}个字符",
|
||||||
"length": "{0}长度必须为{1}个字符",
|
"length": "{0}长度必须为{1}个字符",
|
||||||
|
|||||||
Reference in New Issue
Block a user