Merge remote-tracking branch 'origin/main'
# Conflicts: # apps/web-antd/src/views/alert/index.vue
This commit is contained in:
@@ -21,3 +21,8 @@ VITE_INJECT_APP_LOADING=true
|
|||||||
|
|
||||||
# 打包后是否生成dist.zip
|
# 打包后是否生成dist.zip
|
||||||
VITE_ARCHIVER=true
|
VITE_ARCHIVER=true
|
||||||
|
|
||||||
|
# 默认登录用户名
|
||||||
|
VITE_APP_DEFAULT_USERNAME=
|
||||||
|
# 默认登录密码
|
||||||
|
VITE_APP_DEFAULT_PASSWORD=
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ export namespace CommentApi {
|
|||||||
content?: string; // 评论内容
|
content?: string; // 评论内容
|
||||||
children?: Comment[]; // 子评论
|
children?: Comment[]; // 子评论
|
||||||
projectName?: string; // 项目名称
|
projectName?: string; // 项目名称
|
||||||
|
status?: string;
|
||||||
|
serialNo?: string;
|
||||||
|
businessOwner?: string;
|
||||||
|
technicalOwnerA?: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export namespace CustomerApi {
|
|||||||
projectCount: number;
|
projectCount: number;
|
||||||
licenseCount: number;
|
licenseCount: number;
|
||||||
userCount: number;
|
userCount: number;
|
||||||
|
contractCount: number;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,5 +20,7 @@
|
|||||||
"name": "Project Name",
|
"name": "Project Name",
|
||||||
"envInfoFile": "Environment Info Attachment",
|
"envInfoFile": "Environment Info Attachment",
|
||||||
"list": "Project List",
|
"list": "Project List",
|
||||||
"progress": "Progress"
|
"progress": "Comment",
|
||||||
|
"updateTime": "Last Modified Time",
|
||||||
|
"commentNum": "Comment Count"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"project": "项目",
|
"project": "项目",
|
||||||
"operation": "操作",
|
"operation": "操作",
|
||||||
"creationTime": "创建时间",
|
"creationTime": "创建时间",
|
||||||
"remarks": "备注",
|
"remarks": "软件版本信息",
|
||||||
"envInfo": "环境信息",
|
"envInfo": "环境信息",
|
||||||
"status": "项目状态",
|
"status": "项目状态",
|
||||||
"endTime": "项目结束时间",
|
"endTime": "项目结束时间",
|
||||||
@@ -20,5 +20,7 @@
|
|||||||
"name": "项目名称",
|
"name": "项目名称",
|
||||||
"envInfoFile": "环境信息附件",
|
"envInfoFile": "环境信息附件",
|
||||||
"list": "项目列表",
|
"list": "项目列表",
|
||||||
"progress": "进展"
|
"progress": "评论",
|
||||||
|
"updateTime": "最后修改时间",
|
||||||
|
"commentNum": "评论数"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, onMounted, ref } from 'vue';
|
import { computed, onMounted, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useAccess } from '@vben/access';
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
import { Button, Card, message, Space } from 'ant-design-vue';
|
import { Button, Card, message, Space } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenForm } from '#/adapter/form';
|
import { useVbenForm } from '#/adapter/form';
|
||||||
import { getAlertPage, updateAlert } from '#/api/alert/alert';
|
import { getAlertPage, updateAlert } from '#/api/alert/alert';
|
||||||
|
import { getSimpleUserList } from '#/api/system/user';
|
||||||
|
|
||||||
|
const { hasAccessByCodes } = useAccess();
|
||||||
import { getUserPage } from '#/api/system/user';
|
import { getUserPage } from '#/api/system/user';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
@@ -49,6 +53,10 @@ const formSchema = computed(() => [
|
|||||||
mode: 'multiple',
|
mode: 'multiple',
|
||||||
allowClear: true,
|
allowClear: true,
|
||||||
showSearch: true,
|
showSearch: true,
|
||||||
|
fieldNames: {
|
||||||
|
label: 'nickname',
|
||||||
|
value: 'id',
|
||||||
|
},
|
||||||
filterOption: (input: string, option: any) => {
|
filterOption: (input: string, option: any) => {
|
||||||
return option?.label?.toLowerCase().includes(input.toLowerCase());
|
return option?.label?.toLowerCase().includes(input.toLowerCase());
|
||||||
},
|
},
|
||||||
@@ -140,15 +148,7 @@ function handleReset() {
|
|||||||
async function loadUserList() {
|
async function loadUserList() {
|
||||||
try {
|
try {
|
||||||
// 参考部门管理界面的做法,使用getUserPage接口
|
// 参考部门管理界面的做法,使用getUserPage接口
|
||||||
const { list } = await getUserPage({
|
userOptions.value = await getSimpleUserList();
|
||||||
pageNo: 1,
|
|
||||||
pageSize: 100, // 获取前100个用户
|
|
||||||
});
|
|
||||||
|
|
||||||
userOptions.value = list.map((user) => ({
|
|
||||||
label: user.nickname || user.username,
|
|
||||||
value: user.id,
|
|
||||||
}));
|
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,14 +178,26 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page auto-content-height>
|
<Page auto-content-height>
|
||||||
<Card :title="$t('alert.alerttitle')">
|
<Card>
|
||||||
<Form />
|
<Form />
|
||||||
<div class="mt-4 flex justify-end">
|
<div class="mt-4 flex justify-end">
|
||||||
<Space>
|
<Space>
|
||||||
<Button type="primary" @click="handleSubmit">{{$t('alert.save')}}</Button>
|
<Button
|
||||||
|
type="primary"
|
||||||
|
@click="handleSubmit"
|
||||||
|
v-if="hasAccessByCodes(['license:alert:update'])"
|
||||||
|
>
|
||||||
|
{{$t('alert.save')}}
|
||||||
|
</Button>
|
||||||
<Button @click="handleReset"> {{ $t('alert.reset') }} </Button>
|
<Button @click="handleReset"> {{ $t('alert.reset') }} </Button>
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
|
<template #title>
|
||||||
|
<span class="text-lg font-semibold">{{ $t('alert.alerttitle') }} </span>
|
||||||
|
<a-tooltip title="每天零点发送邮件提醒">
|
||||||
|
<span class="icon-[mdi--information-outline] text-gray-500"></span>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -4,12 +4,6 @@ import type { AnalysisOverviewItem, WorkbenchTrendItem } from '@vben/common-ui';
|
|||||||
import { onMounted, shallowRef } from 'vue';
|
import { onMounted, shallowRef } from 'vue';
|
||||||
|
|
||||||
import { AnalysisOverview, WorkbenchTrends } from '@vben/common-ui';
|
import { AnalysisOverview, WorkbenchTrends } from '@vben/common-ui';
|
||||||
import {
|
|
||||||
SvgBellIcon,
|
|
||||||
SvgCakeIcon,
|
|
||||||
SvgCardIcon,
|
|
||||||
SvgDownloadIcon,
|
|
||||||
} from '@vben/icons';
|
|
||||||
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||||
@@ -17,6 +11,8 @@ import relativeTime from 'dayjs/plugin/relativeTime';
|
|||||||
import { getLatestCommentList } from '#/api/license/comment';
|
import { getLatestCommentList } from '#/api/license/comment';
|
||||||
import { dashboard } from '#/api/license/customer';
|
import { dashboard } from '#/api/license/customer';
|
||||||
import { getLicenseExpiry } from '#/api/license/license';
|
import { getLicenseExpiry } from '#/api/license/license';
|
||||||
|
// import { DictTag } from '#/components/dict-tag';
|
||||||
|
import { DICT_TYPE, getDictObj } from '#/utils';
|
||||||
|
|
||||||
const overviewItems = shallowRef<AnalysisOverviewItem[]>([]);
|
const overviewItems = shallowRef<AnalysisOverviewItem[]>([]);
|
||||||
const trendItems = shallowRef<WorkbenchTrendItem[]>([]);
|
const trendItems = shallowRef<WorkbenchTrendItem[]>([]);
|
||||||
@@ -28,28 +24,35 @@ onMounted(async () => {
|
|||||||
|
|
||||||
overviewItems.value = [
|
overviewItems.value = [
|
||||||
{
|
{
|
||||||
icon: SvgCardIcon,
|
icon: 'icon-[streamline-plump-color--user-pin-flat] size-8 flex-shrink-0',
|
||||||
title: '用户量',
|
title: '用户量',
|
||||||
totalTitle: '总用户量',
|
totalTitle: '总用户量',
|
||||||
totalValue: data.userCount || 0,
|
totalValue: data.userCount || 0,
|
||||||
value: data.userCount || 0,
|
value: data.userCount || 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: SvgCakeIcon,
|
icon: 'icon-[streamline-color--information-desk-customer] size-8 flex-shrink-0',
|
||||||
title: '客户量',
|
title: '客户量',
|
||||||
totalTitle: '总客户量',
|
totalTitle: '总客户量',
|
||||||
totalValue: data?.customerCount || 0,
|
totalValue: data?.customerCount || 0,
|
||||||
value: data?.customerCount || 0,
|
value: data?.customerCount || 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: SvgBellIcon,
|
icon: 'icon-[streamline-sharp-color--laptop-project-screen-flat] size-8 flex-shrink-0',
|
||||||
title: '项目量',
|
title: '项目量',
|
||||||
totalTitle: '总项目量',
|
totalTitle: '总项目量',
|
||||||
totalValue: data?.projectCount || 0,
|
totalValue: data?.projectCount || 0,
|
||||||
value: data?.projectCount || 0,
|
value: data?.projectCount || 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: SvgDownloadIcon,
|
icon: 'icon-[material-icon-theme--folder-contract-open] size-8 flex-shrink-0',
|
||||||
|
title: '合同量',
|
||||||
|
totalTitle: '总合同量',
|
||||||
|
totalValue: data?.contractCount || 0,
|
||||||
|
value: data?.contractCount || 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'icon-[streamline-color--key-flat] size-8 flex-shrink-0',
|
||||||
title: 'License量',
|
title: 'License量',
|
||||||
totalTitle: '总License量',
|
totalTitle: '总License量',
|
||||||
totalValue: data?.licenseCount || 0,
|
totalValue: data?.licenseCount || 0,
|
||||||
@@ -61,19 +64,38 @@ onMounted(async () => {
|
|||||||
trendItems.value = licenses.map((item) => {
|
trendItems.value = licenses.map((item) => {
|
||||||
return {
|
return {
|
||||||
avatar: '',
|
avatar: '',
|
||||||
content: `客户【<b>${item.customerName}</b>】项目【<b>${item.projectName}</b>】的License将在 <b>${dayjs(item.expiryDate).fromNow(true)}</b> 后到期`,
|
content: `客户【<b>${item.customerName}</b>】项目【<b>${item.projectName}</b>】的License即将在 <b>${dayjs(item.expiryDate).fromNow(true)}</b> 后到期`,
|
||||||
date: dayjs(item.expiryDate).format('YYYY-MM-DD HH:mm:ss') || '',
|
date: dayjs(item.expiryDate).format('YYYY-MM-DD HH:mm:ss') || '',
|
||||||
title: item.serialNo || '',
|
title: item.serialNo || '',
|
||||||
|
status: '',
|
||||||
|
info: '',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const comments = await getLatestCommentList();
|
const comments = await getLatestCommentList();
|
||||||
|
|
||||||
projectProgressItems.value = comments.map((item) => {
|
projectProgressItems.value = comments.map((item) => {
|
||||||
|
// 获取字典对象
|
||||||
|
const dict = getDictObj(DICT_TYPE.LIC_PROJECT_STATUS, String(item.status));
|
||||||
|
|
||||||
|
let dataContent = item.content || '';
|
||||||
|
if (dataContent) {
|
||||||
|
const position = dataContent.indexOf('>') + 1;
|
||||||
|
const originalString = dataContent;
|
||||||
|
const stringToInsert = `<span style="font-size: 12px; color: #32363973">${item.author}:</span>`;
|
||||||
|
dataContent =
|
||||||
|
originalString.slice(0, position) +
|
||||||
|
stringToInsert +
|
||||||
|
originalString.slice(position);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
avatar: '',
|
avatar: '',
|
||||||
content: item.content || '',
|
content: dataContent,
|
||||||
date: dayjs(item.updateTime).format('YYYY-MM-DD HH:mm:ss') || '',
|
date: dayjs(item.updateTime).format('YYYY-MM-DD HH:mm:ss') || '',
|
||||||
title: item.projectName || '',
|
title: `${item.projectName ?? ''} `,
|
||||||
|
status: dict,
|
||||||
|
info: `${item.serialNo ?? ''} ${item.businessOwner ?? ''} ${item.technicalOwnerA ?? ''}`,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -98,14 +120,14 @@ onMounted(async () => {
|
|||||||
<WorkbenchTrends
|
<WorkbenchTrends
|
||||||
:items="trendItems"
|
:items="trendItems"
|
||||||
class="mt-5"
|
class="mt-5"
|
||||||
title="License最先到期"
|
title="License即将到期"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="mr-4 w-full lg:w-1/2">
|
<div class="mr-4 w-full lg:w-1/2">
|
||||||
<WorkbenchTrends
|
<WorkbenchTrends
|
||||||
:items="projectProgressItems"
|
:items="projectProgressItems"
|
||||||
class="mt-5"
|
class="mt-5"
|
||||||
title="项目进展"
|
title="项目评论"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { CustomerApi } from '#/api/license/customer';
|
import type { CustomerApi } from '#/api/license/customer';
|
||||||
|
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
import { useAccess } from '@vben/access';
|
|
||||||
|
|
||||||
import { z } from '#/adapter/form';
|
import { z } from '#/adapter/form';
|
||||||
import {
|
import {
|
||||||
isCustomerCodeUnique,
|
isCustomerCodeUnique,
|
||||||
@@ -15,8 +13,6 @@ import { getAreaTree } from '#/api/system/area';
|
|||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
|
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
|
||||||
|
|
||||||
const { hasAccessByCodes } = useAccess();
|
|
||||||
|
|
||||||
export const formData = ref<CustomerApi.Customer>();
|
export const formData = ref<CustomerApi.Customer>();
|
||||||
/** 新增/修改的表单 */
|
/** 新增/修改的表单 */
|
||||||
export function useFormSchema(): VbenFormSchema[] {
|
export function useFormSchema(): VbenFormSchema[] {
|
||||||
@@ -193,9 +189,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 列表的字段 */
|
/** 列表的字段 */
|
||||||
export function useGridColumns(
|
export function useGridColumns(): VxeTableGridOptions<CustomerApi.Customer>['columns'] {
|
||||||
onActionClick?: OnActionClickFn<CustomerApi.Customer>,
|
|
||||||
): VxeTableGridOptions<CustomerApi.Customer>['columns'] {
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
field: 'name',
|
field: 'name',
|
||||||
@@ -258,26 +252,7 @@ export function useGridColumns(
|
|||||||
minWidth: 200,
|
minWidth: 200,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
headerAlign: 'center',
|
slots: { default: 'actions' },
|
||||||
showOverflow: false,
|
|
||||||
cellRender: {
|
|
||||||
attrs: {
|
|
||||||
nameField: 'name',
|
|
||||||
nameTitle: $t('customer.customer'),
|
|
||||||
onClick: onActionClick,
|
|
||||||
},
|
|
||||||
name: 'CellOperation',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
code: 'edit',
|
|
||||||
show: hasAccessByCodes(['license:customer:update']),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'delete',
|
|
||||||
show: hasAccessByCodes(['license:customer:delete']),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type {
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
OnActionClickParams,
|
|
||||||
VxeTableGridOptions,
|
|
||||||
} from '#/adapter/vxe-table';
|
|
||||||
import type { CustomerApi } from '#/api/license/customer';
|
import type { CustomerApi } from '#/api/license/customer';
|
||||||
|
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
@@ -78,29 +75,12 @@ function handleImport() {
|
|||||||
importModalApi.open();
|
importModalApi.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 表格操作按钮的回调函数 */
|
|
||||||
function onActionClick({
|
|
||||||
code,
|
|
||||||
row,
|
|
||||||
}: OnActionClickParams<CustomerApi.Customer>) {
|
|
||||||
switch (code) {
|
|
||||||
case 'delete': {
|
|
||||||
onDelete(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'edit': {
|
|
||||||
onEdit(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
formOptions: {
|
formOptions: {
|
||||||
schema: useGridFormSchema(),
|
schema: useGridFormSchema(),
|
||||||
},
|
},
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
columns: useGridColumns(onActionClick),
|
columns: useGridColumns(),
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
pagerConfig: {
|
pagerConfig: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@@ -159,12 +139,34 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
label: $t('ui.actionTitle.import', [$t('customer.customer')]),
|
label: $t('ui.actionTitle.import', [$t('customer.customer')]),
|
||||||
type: 'primary',
|
type: 'primary',
|
||||||
icon: ACTION_ICON.UPLOAD,
|
icon: ACTION_ICON.UPLOAD,
|
||||||
auth: ['license:customer:export'],
|
auth: ['license:customer:import'],
|
||||||
onClick: handleImport,
|
onClick: handleImport,
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<TableAction
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: $t('common.edit'),
|
||||||
|
type: 'link',
|
||||||
|
auth: ['license:customer:update'],
|
||||||
|
onClick: onEdit.bind(null, row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: $t('common.delete'),
|
||||||
|
type: 'link',
|
||||||
|
danger: true,
|
||||||
|
auth: ['license:customer:delete'],
|
||||||
|
popConfirm: {
|
||||||
|
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
|
||||||
|
confirm: onDelete.bind(null, row),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Modal :title="getTitle" class="w-[800px]">
|
<Modal :title="getTitle" class="w-1/2">
|
||||||
<Form class="mx-4" />
|
<Form class="mx-4" />
|
||||||
</Modal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
label: $t('license.applyAction', ['License']),
|
label: $t('license.applyAction', ['License']),
|
||||||
type: 'primary',
|
type: 'primary',
|
||||||
icon: ACTION_ICON.ADD,
|
icon: ACTION_ICON.ADD,
|
||||||
auth: ['license:license:create'],
|
auth: ['license:license:apply'],
|
||||||
onClick: onCreate,
|
onClick: onCreate,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -186,7 +186,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
label: $t('ui.actionTitle.import', ['License']),
|
label: $t('ui.actionTitle.import', ['License']),
|
||||||
type: 'primary',
|
type: 'primary',
|
||||||
icon: ACTION_ICON.UPLOAD,
|
icon: ACTION_ICON.UPLOAD,
|
||||||
auth: ['license:license:export'],
|
auth: ['license:license:import'],
|
||||||
onClick: handleImport,
|
onClick: handleImport,
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
@@ -199,6 +199,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
{
|
{
|
||||||
label: $t('license.detail'),
|
label: $t('license.detail'),
|
||||||
type: 'link',
|
type: 'link',
|
||||||
|
auth: ['license:license:query'],
|
||||||
onClick: onDetail.bind(null, row),
|
onClick: onDetail.bind(null, row),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -231,7 +232,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
label: $t('license.reapply'),
|
label: $t('license.reapply'),
|
||||||
type: 'link',
|
type: 'link',
|
||||||
ifShow: row.status === 3,
|
ifShow: row.status === 3,
|
||||||
auth: ['license:license:query'],
|
auth: ['license:license:reapply'],
|
||||||
onClick: onReapply.bind(null, row),
|
onClick: onReapply.bind(null, row),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -83,8 +83,8 @@ const formatContent = computed(() => {
|
|||||||
</a-avatar>
|
</a-avatar>
|
||||||
</template>
|
</template>
|
||||||
<template #datetime>
|
<template #datetime>
|
||||||
<a-tooltip :title="dayjs(data.updateTime).format('YYYY-MM-DD HH:mm:ss')">
|
<a-tooltip :title="dayjs(data.updateTime)">
|
||||||
<span>{{ dayjs(data.updateTime).fromNow() }}</span>
|
<span>{{ dayjs(data.updateTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
|
|||||||
@@ -21,6 +21,33 @@ const sortByTime = () => {
|
|||||||
isSortAsc.value = !isSortAsc.value;
|
isSortAsc.value = !isSortAsc.value;
|
||||||
emit('getCommentByProjectId', props.projectId, isSortAsc.value);
|
emit('getCommentByProjectId', props.projectId, isSortAsc.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function countNodes(tree: CommentApi.Comment) {
|
||||||
|
if (tree === null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let count = 1; // 当前节点
|
||||||
|
if (!tree.children || tree.children.length === 0 || (tree?.depth ?? 0) > 1) {
|
||||||
|
return count; // 如果没有子节点,直接返回当前节点数量
|
||||||
|
}
|
||||||
|
for (let i = 0; i < tree.children.length; i++) {
|
||||||
|
if (tree.children[i] !== undefined) {
|
||||||
|
count += countNodes(tree.children[i] as CommentApi.Comment); // 递归计算子节点的数量
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCommentCount(comments?: CommentApi.Comment[]) {
|
||||||
|
if (!comments || comments.length === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let count = 0;
|
||||||
|
for (const comment of comments) {
|
||||||
|
count += countNodes(comment);
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div id="article-comment" style="padding: 5px">
|
<div id="article-comment" style="padding: 5px">
|
||||||
@@ -30,7 +57,7 @@ const sortByTime = () => {
|
|||||||
{{ $t('comment.hotComment') }}
|
{{ $t('comment.hotComment') }}
|
||||||
</span>
|
</span>
|
||||||
<span style="padding: 0 15px 0 5px; color: #9499a0">{{
|
<span style="padding: 0 15px 0 5px; color: #9499a0">{{
|
||||||
comments?.length ?? 0
|
getCommentCount(comments)
|
||||||
}}</span>
|
}}</span>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||||||
label: $t('project.contractCode'),
|
label: $t('project.contractCode'),
|
||||||
rules: 'required',
|
rules: 'required',
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
|
help: '没有合同的项目请填0',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldName: 'businessStatus',
|
fieldName: 'businessStatus',
|
||||||
@@ -183,6 +184,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||||||
fieldName: 'remark',
|
fieldName: 'remark',
|
||||||
label: $t('project.remarks'),
|
label: $t('project.remarks'),
|
||||||
component: 'Textarea',
|
component: 'Textarea',
|
||||||
|
rules: 'required',
|
||||||
formItemClass: 'col-span-2',
|
formItemClass: 'col-span-2',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -422,11 +424,22 @@ export function useGridColumns(
|
|||||||
visible: false,
|
visible: false,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
field: 'commentNum',
|
||||||
|
title: $t('project.commentNum'),
|
||||||
|
minWidth: 70,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'updateTime',
|
||||||
|
title: $t('project.updateTime'),
|
||||||
|
minWidth: 130,
|
||||||
|
sortable: true,
|
||||||
|
formatter: 'formatDateTime',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
field: 'createTime',
|
field: 'createTime',
|
||||||
title: $t('project.creationTime'),
|
title: $t('project.creationTime'),
|
||||||
// visible: false,
|
minWidth: 130,
|
||||||
minWidth: 120,
|
|
||||||
formatter: 'formatDateTime',
|
formatter: 'formatDateTime',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -452,7 +465,7 @@ export function useGridColumns(
|
|||||||
{
|
{
|
||||||
code: 'progress',
|
code: 'progress',
|
||||||
text: $t('project.progress'),
|
text: $t('project.progress'),
|
||||||
show: hasAccessByCodes(['license:project:update']),
|
show: hasAccessByCodes(['license:project:comment']),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'delete',
|
code: 'delete',
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
label: $t('ui.actionTitle.import', [$t('project.project')]),
|
label: $t('ui.actionTitle.import', [$t('project.project')]),
|
||||||
type: 'primary',
|
type: 'primary',
|
||||||
icon: ACTION_ICON.UPLOAD,
|
icon: ACTION_ICON.UPLOAD,
|
||||||
auth: ['license:project:export'],
|
auth: ['license:project:import'],
|
||||||
onClick: handleImport,
|
onClick: handleImport,
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const [Form, formApi] = useVbenForm({
|
|||||||
class: 'w-full',
|
class: 'w-full',
|
||||||
},
|
},
|
||||||
// formItemClass: 'col-span-2',
|
// formItemClass: 'col-span-2',
|
||||||
labelWidth: 80,
|
labelWidth: 100,
|
||||||
},
|
},
|
||||||
layout: 'horizontal',
|
layout: 'horizontal',
|
||||||
schema: useFormSchema(),
|
schema: useFormSchema(),
|
||||||
@@ -90,7 +90,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Modal :title="getTitle" class="w-[800px]">
|
<Modal :title="getTitle" class="w-1/2">
|
||||||
<Form class="mx-4" />
|
<Form class="mx-4" />
|
||||||
</Modal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
VbenCountToAnimator,
|
VbenCountToAnimator,
|
||||||
VbenIcon,
|
|
||||||
} from '@vben-core/shadcn-ui';
|
} from '@vben-core/shadcn-ui';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -25,7 +24,7 @@ withDefaults(defineProps<Props>(), {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4">
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-5">
|
||||||
<template v-for="item in items" :key="item.title">
|
<template v-for="item in items" :key="item.title">
|
||||||
<Card :title="item.title" class="w-full">
|
<Card :title="item.title" class="w-full">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
@@ -39,7 +38,8 @@ withDefaults(defineProps<Props>(), {
|
|||||||
class="text-xl"
|
class="text-xl"
|
||||||
prefix=""
|
prefix=""
|
||||||
/>
|
/>
|
||||||
<VbenIcon :icon="item.icon" class="size-8 flex-shrink-0" />
|
<!-- <VbenIcon :icon="item.icon" class="size-8 flex-shrink-0" /> -->
|
||||||
|
<span :class="item.icon"></span>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardFooter class="justify-between">
|
<CardFooter class="justify-between">
|
||||||
<span>{{ item.totalTitle }}</span>
|
<span>{{ item.totalTitle }}</span>
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ interface WorkbenchTrendItem {
|
|||||||
content: string;
|
content: string;
|
||||||
date: string;
|
date: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
status: any;
|
||||||
|
info: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface WorkbenchTodoItem {
|
interface WorkbenchTodoItem {
|
||||||
|
|||||||
@@ -43,7 +43,22 @@ withDefaults(defineProps<Props>(), {
|
|||||||
/>
|
/>
|
||||||
<div class="min-w-0 flex-auto">
|
<div class="min-w-0 flex-auto">
|
||||||
<p class="text-foreground text-sm font-semibold leading-6">
|
<p class="text-foreground text-sm font-semibold leading-6">
|
||||||
{{ item.title }}
|
<span
|
||||||
|
class="text-foreground mr-2 text-sm font-semibold leading-6"
|
||||||
|
>{{ item.title }}
|
||||||
|
</span>
|
||||||
|
<a-tag
|
||||||
|
v-if="item.status"
|
||||||
|
:color="
|
||||||
|
item.status.colorType
|
||||||
|
? item.status.colorType
|
||||||
|
: item.status.cssClass
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ item.status.label }}
|
||||||
|
</a-tag>
|
||||||
|
<!-- eslint-disable vue/no-v-html -->
|
||||||
|
<span v-html="item.info"></span>
|
||||||
</p>
|
</p>
|
||||||
<!-- eslint-disable vue/no-v-html -->
|
<!-- eslint-disable vue/no-v-html -->
|
||||||
<p
|
<p
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
"password": "Password",
|
"password": "Password",
|
||||||
"nickname": "Nickname",
|
"nickname": "Nickname",
|
||||||
"tenant": "Tenant",
|
"tenant": "Tenant",
|
||||||
"usernameTip": "Please enter username",
|
"usernameTip": "Please enter username or email",
|
||||||
"passwordErrorTip": "Password is incorrect",
|
"passwordErrorTip": "Password is incorrect",
|
||||||
"passwordTip": "Please enter password",
|
"passwordTip": "Please enter password",
|
||||||
"nicknameTip": "Please enter nickname",
|
"nicknameTip": "Please enter nickname",
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
"password": "密码",
|
"password": "密码",
|
||||||
"nickname": "昵称",
|
"nickname": "昵称",
|
||||||
"tenant": "租户",
|
"tenant": "租户",
|
||||||
"usernameTip": "请输入用户名",
|
"usernameTip": "请输入用户名或邮箱",
|
||||||
"passwordTip": "请输入密码",
|
"passwordTip": "请输入密码",
|
||||||
"nicknameTip": "请输入昵称",
|
"nicknameTip": "请输入昵称",
|
||||||
"tenantTip": "请选择租户",
|
"tenantTip": "请选择租户",
|
||||||
|
|||||||
@@ -8,18 +8,6 @@
|
|||||||
"name": "@vben/web-antd",
|
"name": "@vben/web-antd",
|
||||||
"path": "apps/web-antd",
|
"path": "apps/web-antd",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "@vben/web-ele",
|
|
||||||
"path": "apps/web-ele",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben/web-naive",
|
|
||||||
"path": "apps/web-naive",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@vben/docs",
|
|
||||||
"path": "docs",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "@vben/commitlint-config",
|
"name": "@vben/commitlint-config",
|
||||||
"path": "internal/lint-configs/commitlint-config",
|
"path": "internal/lint-configs/commitlint-config",
|
||||||
@@ -156,10 +144,6 @@
|
|||||||
"name": "@vben/utils",
|
"name": "@vben/utils",
|
||||||
"path": "packages/utils",
|
"path": "packages/utils",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "@vben/playground",
|
|
||||||
"path": "playground",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "@vben/turbo-run",
|
"name": "@vben/turbo-run",
|
||||||
"path": "scripts/turbo-run",
|
"path": "scripts/turbo-run",
|
||||||
|
|||||||
Reference in New Issue
Block a user