build: 优化docke构建速度
This commit is contained in:
parent
3adf14eac9
commit
8dedce3ca5
53
Dockerfile
53
Dockerfile
|
@ -1,45 +1,34 @@
|
||||||
|
|
||||||
# https://stackoverflow.com/questions/53681522/share-variable-in-multi-stage-dockerfile-arg-before-from-not-substituted
|
FROM node:20-slim as base
|
||||||
ARG PROJECT_DIR=/huaxin-base-frontend
|
ENV PROJECT_DIR=/huaxin-front \
|
||||||
|
PNPM_HOME="/pnpm" \
|
||||||
FROM node:20-slim as builder
|
PATH="$PNPM_HOME:$PATH"
|
||||||
ARG PROJECT_DIR
|
|
||||||
WORKDIR $PROJECT_DIR
|
WORKDIR $PROJECT_DIR
|
||||||
|
COPY ./ $PROJECT_DIR
|
||||||
|
|
||||||
# 安装pnpm
|
|
||||||
RUN npm install -g pnpm
|
|
||||||
|
|
||||||
COPY . ./
|
|
||||||
# 安装依赖
|
|
||||||
# 若网络不通,可以使用淘宝源
|
# 若网络不通,可以使用淘宝源
|
||||||
# RUN pnpm config set registry https://registry.npmmirror.com
|
# RUN pnpm config set registry https://registry.npmmirror.com
|
||||||
# see https://pnpm.io/docker
|
# 若不存在安装pnpm
|
||||||
FROM builder AS prod-deps
|
RUN npm install -g pnpm
|
||||||
|
|
||||||
|
# 构建项目
|
||||||
|
# 安装生成依赖
|
||||||
|
FROM base AS prod-deps
|
||||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile
|
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile
|
||||||
|
|
||||||
FROM builder AS build
|
# 基于prod-deps生成的依赖执行构建
|
||||||
|
FROM base AS builder
|
||||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
||||||
RUN pnpm run build
|
RUN pnpm run build
|
||||||
|
|
||||||
|
# 将prod-deps生成的依赖拷贝到base/$PROJECT_DIR中,开始在builder构建阶段build,最后拷贝build生成的dist到base/$PROJECT_DIR中
|
||||||
# mirror acceleration
|
FROM base AS result
|
||||||
# RUN npm config set registry https://registry.npmmirror.com
|
|
||||||
# RUN pnpm config set registry https://registry.npmmirror.com
|
|
||||||
# RUN npm config rm proxy && npm config rm https-proxy
|
|
||||||
|
|
||||||
FROM builder
|
|
||||||
COPY --from=prod-deps $PROJECT_DIR/node_modules $PROJECT_DIR/node_modules
|
COPY --from=prod-deps $PROJECT_DIR/node_modules $PROJECT_DIR/node_modules
|
||||||
COPY --from=build $PROJECT_DIR/dist $PROJECT_DIR/dist
|
COPY --from=builder $PROJECT_DIR/dist $PROJECT_DIR/dist
|
||||||
|
|
||||||
# 构建项目
|
|
||||||
ENV VITE_BASE_URL=/
|
|
||||||
RUN pnpm build
|
|
||||||
|
|
||||||
|
|
||||||
|
# 构建nginx,并且在result之后拷贝dist到nginx中,其中包含了自定义nginx.conf
|
||||||
FROM nginx:alpine as production
|
FROM nginx:alpine as production
|
||||||
ARG PROJECT_DIR
|
ENV PROJECT_DIR=/huaxin-front
|
||||||
|
COPY --from=result $PROJECT_DIR/dist/ /usr/share/nginx/html
|
||||||
COPY --from=builder $PROJECT_DIR/dist/ /usr/share/nginx/html
|
COPY --from=result $PROJECT_DIR/nginx.conf /etc/nginx/nginx.conf
|
||||||
COPY --from=builder $PROJECT_DIR/nginx.conf /etc/nginx/nginx.conf
|
EXPOSE 80
|
||||||
|
|
||||||
EXPOSE 80
|
|
|
@ -5,7 +5,7 @@
|
||||||
<meta name="referrer" content="origin" />
|
<meta name="referrer" content="origin" />
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
<meta name="viewport" content="user-scalable=yes" />
|
<meta name="viewport" content="user-scalable=yes" />
|
||||||
<title>华信Admin</title>
|
<title>华信办公系统</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
import { request, type RequestOptions } from '@/utils/request';
|
||||||
|
|
||||||
|
/** 获取原材料盘点列表 GET /api/contract */
|
||||||
|
export async function materialsInventoryList(params: API.MaterialsInventoryListParams, options?: RequestOptions) {
|
||||||
|
return request<{
|
||||||
|
items?: API.ContractEntity[];
|
||||||
|
meta?: {
|
||||||
|
itemCount?: number;
|
||||||
|
totalItems?: number;
|
||||||
|
itemsPerPage?: number;
|
||||||
|
totalPages?: number;
|
||||||
|
currentPage?: number;
|
||||||
|
};
|
||||||
|
}>('/api/contract', {
|
||||||
|
method: 'GET',
|
||||||
|
params: {
|
||||||
|
// page has a default value: 1
|
||||||
|
page: '1',
|
||||||
|
// pageSize has a default value: 10
|
||||||
|
pageSize: '10',
|
||||||
|
|
||||||
|
...params,
|
||||||
|
},
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 新增原材料盘点 POST /api/contract */
|
||||||
|
export async function contractCreate(body: API.ContractDto, options?: RequestOptions) {
|
||||||
|
return request<any>('/api/contract', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
data: body,
|
||||||
|
...(options || { successMsg: '创建成功' }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取原材料盘点信息 GET /api/contract/${param0} */
|
||||||
|
export async function contractInfo(
|
||||||
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
params: API.ContractInfoParams,
|
||||||
|
options?: RequestOptions,
|
||||||
|
) {
|
||||||
|
const { id: param0, ...queryParams } = params;
|
||||||
|
return request<API.ContractEntity>(`/api/contract/${param0}`, {
|
||||||
|
method: 'GET',
|
||||||
|
params: { ...queryParams },
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 解除原材料盘点和附件关联 PUT /api/contract/unlink-attachments/${param0} */
|
||||||
|
export async function unlinkAttachments(
|
||||||
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
params: API.ContractUpdateParams,
|
||||||
|
body: API.ContractUpdateDto,
|
||||||
|
options?: RequestOptions,
|
||||||
|
) {
|
||||||
|
const { id: param0, ...queryParams } = params;
|
||||||
|
return request<any>(`/api/contract/unlink-attachments/${param0}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
params: { ...queryParams },
|
||||||
|
data: body,
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 更新原材料盘点 PUT /api/contract/${param0} */
|
||||||
|
export async function contractUpdate(
|
||||||
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
params: API.ContractUpdateParams,
|
||||||
|
body: API.ContractUpdateDto,
|
||||||
|
options?: RequestOptions,
|
||||||
|
) {
|
||||||
|
const { id: param0, ...queryParams } = params;
|
||||||
|
return request<any>(`/api/contract/${param0}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
params: { ...queryParams },
|
||||||
|
data: body,
|
||||||
|
...(options || { successMsg: '更新成功' }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除原材料盘点 DELETE /api/contract/${param0} */
|
||||||
|
export async function contractDelete(
|
||||||
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
params: API.ContractDeleteParams,
|
||||||
|
options?: RequestOptions,
|
||||||
|
) {
|
||||||
|
const { id: param0, ...queryParams } = params;
|
||||||
|
return request<any>(`/api/contract/${param0}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
params: { ...queryParams },
|
||||||
|
...(options || { successMsg: '删除成功' }),
|
||||||
|
});
|
||||||
|
}
|
|
@ -1352,4 +1352,56 @@ declare namespace API {
|
||||||
type ContractInfoParams = {
|
type ContractInfoParams = {
|
||||||
id: number;
|
id: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type MaterialsInventoryListParams = {
|
||||||
|
page?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
field?: string;
|
||||||
|
order?: 'ASC' | 'DESC';
|
||||||
|
_t?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ContractEntity = {
|
||||||
|
/** 公司名称 */
|
||||||
|
companyName: string;
|
||||||
|
/** 产品名称(字典) */
|
||||||
|
product: number;
|
||||||
|
/** 单位(字典) */
|
||||||
|
unit: number;
|
||||||
|
/** 之前的库存数量 */
|
||||||
|
previousInventoryQuantity: number;
|
||||||
|
/** 之前的单价 */
|
||||||
|
previousUnitPrice: number;
|
||||||
|
/** 之前的金额 */
|
||||||
|
previousAmount: number;
|
||||||
|
/** 入库时间 */
|
||||||
|
inventoryTime: Date;
|
||||||
|
/** 入库数量 */
|
||||||
|
inventoryQuantity: number;
|
||||||
|
/** 入库单价*/
|
||||||
|
inventoryUnitPrice: number;
|
||||||
|
/** 入库金额 */
|
||||||
|
inventoryAmount: number;
|
||||||
|
/** 出库时间 */
|
||||||
|
outime: Date;
|
||||||
|
/** 出库数量 */
|
||||||
|
outQuantity: number;
|
||||||
|
/** 出库单价 */
|
||||||
|
outUnitPrice: number;
|
||||||
|
/** 出库金额 */
|
||||||
|
outAmount: number;
|
||||||
|
/** 现在的结存数量 */
|
||||||
|
currentInventoryQuantity: number;
|
||||||
|
/** 现在的单价 */
|
||||||
|
currentUnitPrice: number;
|
||||||
|
/** 现在的金额 */
|
||||||
|
currentAmount: number;
|
||||||
|
/** 经办人 */
|
||||||
|
agent: string;
|
||||||
|
/** 附件 */
|
||||||
|
files?: any[];
|
||||||
|
id: number;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
import type { TableColumn } from '@/components/core/dynamic-table';
|
||||||
|
import { ContractStatusEnum } from '@/enums/contractEnum';
|
||||||
|
import { formatToDate } from '@/utils/dateUtil';
|
||||||
|
import { Tag, Button } from 'ant-design-vue';
|
||||||
|
|
||||||
|
export type TableListItem = API.ContractEntity;
|
||||||
|
export type TableColumnItem = TableColumn<TableListItem>;
|
||||||
|
|
||||||
|
export const baseColumns = (ctx: {
|
||||||
|
contractTypes: API.DictItemEntity[];
|
||||||
|
dynamicTableInstance;
|
||||||
|
}): TableColumnItem[] => {
|
||||||
|
const { contractTypes } = ctx;
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: '编号',
|
||||||
|
width: 100,
|
||||||
|
maxWidth: 100,
|
||||||
|
fixed: 'left',
|
||||||
|
dataIndex: 'contractNumber',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '标题',
|
||||||
|
width: 180,
|
||||||
|
dataIndex: 'title',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '类型',
|
||||||
|
width: 80,
|
||||||
|
formItemProps: {
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
options: contractTypes.map(({ label, id }) => ({ value: id, label })),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataIndex: 'type',
|
||||||
|
customRender: ({ record }) => {
|
||||||
|
return contractTypes?.length
|
||||||
|
? contractTypes.find((item) => item.id === record.type)?.label || ''
|
||||||
|
: '';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '甲方',
|
||||||
|
width: 120,
|
||||||
|
dataIndex: 'partyA',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '乙方',
|
||||||
|
width: 120,
|
||||||
|
dataIndex: 'partyB',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '签订时间',
|
||||||
|
width: 60,
|
||||||
|
maxWidth: 60,
|
||||||
|
hideInSearch: true,
|
||||||
|
dataIndex: 'signingDate',
|
||||||
|
customRender: ({ record }) => {
|
||||||
|
return formatToDate(record.signingDate);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '交付期限',
|
||||||
|
width: 60,
|
||||||
|
maxWidth: 60,
|
||||||
|
hideInSearch: true,
|
||||||
|
dataIndex: 'deliveryDeadline',
|
||||||
|
customRender: ({ record }) => {
|
||||||
|
return formatToDate(record.deliveryDeadline);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '审核结果',
|
||||||
|
dataIndex: 'status',
|
||||||
|
maxWidth: 60,
|
||||||
|
width: 60,
|
||||||
|
fixed:'right',
|
||||||
|
formItemProps: {
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
options: Object.values(ContractStatusEnum)
|
||||||
|
.filter((value) => typeof value === 'number')
|
||||||
|
.map((item) => formatStatus(item as ContractStatusEnum)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
customRender: ({ record }) => {
|
||||||
|
const { color, label } = formatStatus(record.status);
|
||||||
|
return <Tag color={color}>{label}</Tag>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function formatStatus(status: ContractStatusEnum): {
|
||||||
|
color: string;
|
||||||
|
label: string;
|
||||||
|
value: number;
|
||||||
|
} {
|
||||||
|
switch (status) {
|
||||||
|
case ContractStatusEnum.Pending:
|
||||||
|
return {
|
||||||
|
color: '#ccc',
|
||||||
|
label: '待审核',
|
||||||
|
value: ContractStatusEnum.Pending,
|
||||||
|
};
|
||||||
|
case ContractStatusEnum.Approved:
|
||||||
|
return {
|
||||||
|
color: 'green',
|
||||||
|
label: '已通过',
|
||||||
|
value: ContractStatusEnum.Approved,
|
||||||
|
};
|
||||||
|
case ContractStatusEnum.Rejected:
|
||||||
|
return {
|
||||||
|
color: 'red',
|
||||||
|
label: '已拒绝',
|
||||||
|
value: ContractStatusEnum.Rejected,
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
color: '#ccc',
|
||||||
|
label: '待审核',
|
||||||
|
value: ContractStatusEnum.Pending,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
import type { FormSchema } from '@/components/core/schema-form/';
|
||||||
|
import { ContractStatusEnum } from '@/enums/contractEnum';
|
||||||
|
import { formatStatus } from './columns';
|
||||||
|
|
||||||
|
export const contractSchemas = (
|
||||||
|
contractTypes: API.DictItemEntity[],
|
||||||
|
): FormSchema<API.ContractEntity>[] => [
|
||||||
|
{
|
||||||
|
field: 'contractNumber',
|
||||||
|
component: 'Input',
|
||||||
|
label: '合同编号',
|
||||||
|
rules: [{ required: true, type: 'string' }],
|
||||||
|
colProps: {
|
||||||
|
span: 12,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'title',
|
||||||
|
component: 'Input',
|
||||||
|
label: '合同标题',
|
||||||
|
rules: [{ required: true, type: 'string' }],
|
||||||
|
colProps: {
|
||||||
|
span: 12,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
field: 'partyA',
|
||||||
|
component: 'Input',
|
||||||
|
label: '甲方',
|
||||||
|
rules: [{ required: true, type: 'string' }],
|
||||||
|
colProps: {
|
||||||
|
span: 12,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'partyB',
|
||||||
|
component: 'Input',
|
||||||
|
label: '乙方',
|
||||||
|
rules: [{ required: true, type: 'string' }],
|
||||||
|
colProps: {
|
||||||
|
span: 12,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
field: 'signingDate',
|
||||||
|
label: '签订时间',
|
||||||
|
component: 'DatePicker',
|
||||||
|
// defaultValue: new Date(),
|
||||||
|
colProps: { span: 12 },
|
||||||
|
componentProps: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'deliveryDeadline',
|
||||||
|
label: '交付期限',
|
||||||
|
component: 'DatePicker',
|
||||||
|
// defaultValue: new Date(),
|
||||||
|
colProps: { span: 12 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'type',
|
||||||
|
label: '合同类型',
|
||||||
|
component: 'Select',
|
||||||
|
required: true,
|
||||||
|
colProps: {
|
||||||
|
span: 12,
|
||||||
|
},
|
||||||
|
componentProps: {
|
||||||
|
options: contractTypes.map(({ label, id }) => ({ value: id, label })),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'status',
|
||||||
|
label: '审核结果',
|
||||||
|
component: 'Select',
|
||||||
|
required:true,
|
||||||
|
defaultValue: 0,
|
||||||
|
colProps: {
|
||||||
|
span: 12,
|
||||||
|
},
|
||||||
|
componentProps: {
|
||||||
|
allowClear: false,
|
||||||
|
options: Object.values(ContractStatusEnum)
|
||||||
|
.filter((value) => typeof value === 'number')
|
||||||
|
.map((item) => formatStatus(item as ContractStatusEnum)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// field: 'remark',
|
||||||
|
// component: 'InputTextArea',
|
||||||
|
// label: '备注',
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// field: 'menuIds',
|
||||||
|
// component: 'Tree',
|
||||||
|
// label: '菜单权限',
|
||||||
|
// componentProps: {
|
||||||
|
// checkable: true,
|
||||||
|
// vModelKey: 'checkedKeys',
|
||||||
|
// fieldNames: {
|
||||||
|
// title: 'name',
|
||||||
|
// key: 'id',
|
||||||
|
// },
|
||||||
|
// style: {
|
||||||
|
// height: '350px',
|
||||||
|
// paddingTop: '5px',
|
||||||
|
// overflow: 'auto',
|
||||||
|
// borderRadius: '6px',
|
||||||
|
// border: '1px solid #dcdfe6',
|
||||||
|
// resize: 'vertical',
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
];
|
|
@ -1,11 +1,221 @@
|
||||||
<template>
|
<template>
|
||||||
<div>Meterials Inventory</div>
|
<div v-if="columns?.length">
|
||||||
|
<DynamicTable
|
||||||
|
row-key="id"
|
||||||
|
header-title="合同管理"
|
||||||
|
title-tooltip=""
|
||||||
|
:data-request="Api.contract.contractList"
|
||||||
|
:columns="columns"
|
||||||
|
bordered
|
||||||
|
:scroll="{ x: 1920 }"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<template #toolbar>
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
:disabled="!$auth('system:role:create')"
|
||||||
|
@click="openEditModal({})"
|
||||||
|
>
|
||||||
|
新增
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</DynamicTable>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="tsx">
|
||||||
|
import { useTable } from '@/components/core/dynamic-table';
|
||||||
|
import { baseColumns, type TableColumnItem, type TableListItem } from './columns';
|
||||||
|
import Api from '@/api/';
|
||||||
|
import { useDictStore } from '@/store/modules/dict';
|
||||||
|
import { onMounted, ref, type FunctionalComponent } from 'vue';
|
||||||
|
import { DictEnum } from '@/enums/dictEnum';
|
||||||
|
import { useFormModal, useModal } from '@/hooks/useModal';
|
||||||
|
import { contractSchemas } from './formSchemas';
|
||||||
|
import { formatToDate } from '@/utils/dateUtil';
|
||||||
|
import { Button } from 'ant-design-vue';
|
||||||
|
import AttachmentManage from '@/components/business/attachment-manage/index.vue';
|
||||||
|
import AttachmentUpload from '@/components/business/attachment-upload/index.vue';
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'MeterialsInventory',
|
name: 'MeterialsInventory',
|
||||||
});
|
});
|
||||||
|
const [DynamicTable, dynamicTableInstance] = useTable();
|
||||||
|
const [showModal] = useFormModal();
|
||||||
|
const [fnModal] = useModal();
|
||||||
|
const { getDictItemsByCode } = useDictStore();
|
||||||
|
const contractTypes = ref<API.DictItemEntity[]>([]);
|
||||||
|
|
||||||
|
const getContractTypes = async () => {
|
||||||
|
contractTypes.value = await getDictItemsByCode(DictEnum.ContractType);
|
||||||
|
};
|
||||||
|
const isUploadPopupVisiable = ref(false);
|
||||||
|
|
||||||
|
// contractList;
|
||||||
|
let columns = ref<TableColumnItem[]>();
|
||||||
|
onMounted(() => {
|
||||||
|
getContractTypes().then((res) => {
|
||||||
|
columns.value = [
|
||||||
|
...baseColumns({
|
||||||
|
dynamicTableInstance,
|
||||||
|
contractTypes: contractTypes.value,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
title: '附件',
|
||||||
|
width: 50,
|
||||||
|
maxWidth: 50,
|
||||||
|
hideInSearch: true,
|
||||||
|
fixed: 'right',
|
||||||
|
dataIndex: 'files',
|
||||||
|
customRender: ({ record }) => <FilesRender {...record} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
maxWidth: 80,
|
||||||
|
width: 80,
|
||||||
|
minWidth: 80,
|
||||||
|
dataIndex: 'ACTION',
|
||||||
|
hideInSearch: true,
|
||||||
|
fixed: 'right',
|
||||||
|
actions: ({ record }) => [
|
||||||
|
{
|
||||||
|
icon: 'ant-design:edit-outlined',
|
||||||
|
tooltip: '编辑',
|
||||||
|
auth: {
|
||||||
|
perm: 'app:contract:update',
|
||||||
|
effect: 'disable',
|
||||||
|
},
|
||||||
|
onClick: () => openEditModal(record),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'ant-design:delete-outlined',
|
||||||
|
color: 'red',
|
||||||
|
tooltip: '删除此合同',
|
||||||
|
auth: 'app:contract:delete',
|
||||||
|
popConfirm: {
|
||||||
|
title: '你确定要删除吗?',
|
||||||
|
placement: 'left',
|
||||||
|
onConfirm: () => delRowConfirm(record.id),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'ant-design:cloud-upload-outlined',
|
||||||
|
tooltip: '上传附件',
|
||||||
|
onClick: () => openAttachmentUploadModal(record),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const openAttachmentUploadModal = async (record: API.ContractEntity) => {
|
||||||
|
isUploadPopupVisiable.value = true;
|
||||||
|
fnModal.show({
|
||||||
|
width: 800,
|
||||||
|
title: `合同编号: ${record.contractNumber}`,
|
||||||
|
content: () => {
|
||||||
|
return (
|
||||||
|
<AttachmentUpload
|
||||||
|
onClose={handleUploadClose}
|
||||||
|
afterUploadCallback={(files) => {
|
||||||
|
afterUploadCallback(files, record.id);
|
||||||
|
}}
|
||||||
|
></AttachmentUpload>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
destroyOnClose: true,
|
||||||
|
open: isUploadPopupVisiable.value,
|
||||||
|
footer: null,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const handleUploadClose = (hasSuccess: boolean) => {
|
||||||
|
fnModal.hide();
|
||||||
|
isUploadPopupVisiable.value = false;
|
||||||
|
};
|
||||||
|
const afterUploadCallback = async (
|
||||||
|
files: { filename: { path: string; id: number } }[],
|
||||||
|
id: number,
|
||||||
|
) => {
|
||||||
|
await Api.contract.contractUpdate({ id }, { fileIds: files.map((item) => item.filename.id) });
|
||||||
|
dynamicTableInstance?.reload();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 打开新增/编辑弹窗
|
||||||
|
*/
|
||||||
|
const openEditModal = async (record: Partial<TableListItem>) => {
|
||||||
|
const [formRef] = await showModal({
|
||||||
|
modalProps: {
|
||||||
|
title: `${record.id ? '编辑' : '新增'}合同`,
|
||||||
|
width: '50%',
|
||||||
|
onFinish: async (values) => {
|
||||||
|
const params = {
|
||||||
|
...values,
|
||||||
|
signingDate: formatToDate(values.signingDate),
|
||||||
|
deliveryDeadline: formatToDate(values.deliveryDeadline),
|
||||||
|
};
|
||||||
|
if (record.id) {
|
||||||
|
await Api.contract.contractUpdate({ id: record.id }, params);
|
||||||
|
} else {
|
||||||
|
await Api.contract.contractCreate(params);
|
||||||
|
}
|
||||||
|
dynamicTableInstance?.reload();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
formProps: {
|
||||||
|
labelWidth: 100,
|
||||||
|
schemas: contractSchemas(contractTypes.value),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 如果是编辑的话,需要获取角色详情
|
||||||
|
if (record.id) {
|
||||||
|
const info = await Api.contract.contractInfo({ id: record.id });
|
||||||
|
formRef?.setFieldsValue({
|
||||||
|
...info,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const delRowConfirm = async (record) => {
|
||||||
|
await Api.contract.contractDelete({ id: record });
|
||||||
|
dynamicTableInstance?.reload();
|
||||||
|
};
|
||||||
|
|
||||||
|
const FilesRender: FunctionalComponent<TableListItem> = (contract: API.ContractEntity) => {
|
||||||
|
const [fnModal] = useModal();
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
onClick={() => {
|
||||||
|
openFilesManageModal(fnModal, contract);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{contract.files?.length || 0}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const openFilesManageModal = (fnModal, contract: API.ContractEntity) => {
|
||||||
|
const fileIds = contract.files?.map((item) => item.id) || [];
|
||||||
|
fnModal.show({
|
||||||
|
width: 1200,
|
||||||
|
title: `附件管理`,
|
||||||
|
content: () => {
|
||||||
|
return (
|
||||||
|
<AttachmentManage
|
||||||
|
fileIds={fileIds}
|
||||||
|
onDelete={(unlinkIds) => unlinkAttachments(contract.id, unlinkIds)}
|
||||||
|
></AttachmentManage>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
destroyOnClose: true,
|
||||||
|
footer: null,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const unlinkAttachments = async (id: number, unlinkIds: number[]) => {
|
||||||
|
await Api.contract.unlinkAttachments({ id }, { fileIds: unlinkIds });
|
||||||
|
dynamicTableInstance?.reload();
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped></style>
|
<style lang="less" scoped></style>
|
||||||
|
|
|
@ -0,0 +1,182 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Use this script to test if a given TCP host/port are available
|
||||||
|
|
||||||
|
WAITFORIT_cmdname=${0##*/}
|
||||||
|
|
||||||
|
echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi }
|
||||||
|
|
||||||
|
usage()
|
||||||
|
{
|
||||||
|
cat << USAGE >&2
|
||||||
|
Usage:
|
||||||
|
$WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args]
|
||||||
|
-h HOST | --host=HOST Host or IP under test
|
||||||
|
-p PORT | --port=PORT TCP port under test
|
||||||
|
Alternatively, you specify the host and port as host:port
|
||||||
|
-s | --strict Only execute subcommand if the test succeeds
|
||||||
|
-q | --quiet Don't output any status messages
|
||||||
|
-t TIMEOUT | --timeout=TIMEOUT
|
||||||
|
Timeout in seconds, zero for no timeout
|
||||||
|
-- COMMAND ARGS Execute command with args after the test finishes
|
||||||
|
USAGE
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for()
|
||||||
|
{
|
||||||
|
if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then
|
||||||
|
echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT"
|
||||||
|
else
|
||||||
|
echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout"
|
||||||
|
fi
|
||||||
|
WAITFORIT_start_ts=$(date +%s)
|
||||||
|
while :
|
||||||
|
do
|
||||||
|
if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then
|
||||||
|
nc -z $WAITFORIT_HOST $WAITFORIT_PORT
|
||||||
|
WAITFORIT_result=$?
|
||||||
|
else
|
||||||
|
(echo -n > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1
|
||||||
|
WAITFORIT_result=$?
|
||||||
|
fi
|
||||||
|
if [[ $WAITFORIT_result -eq 0 ]]; then
|
||||||
|
WAITFORIT_end_ts=$(date +%s)
|
||||||
|
echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
return $WAITFORIT_result
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_wrapper()
|
||||||
|
{
|
||||||
|
# In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692
|
||||||
|
if [[ $WAITFORIT_QUIET -eq 1 ]]; then
|
||||||
|
timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT &
|
||||||
|
else
|
||||||
|
timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT &
|
||||||
|
fi
|
||||||
|
WAITFORIT_PID=$!
|
||||||
|
trap "kill -INT -$WAITFORIT_PID" INT
|
||||||
|
wait $WAITFORIT_PID
|
||||||
|
WAITFORIT_RESULT=$?
|
||||||
|
if [[ $WAITFORIT_RESULT -ne 0 ]]; then
|
||||||
|
echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT"
|
||||||
|
fi
|
||||||
|
return $WAITFORIT_RESULT
|
||||||
|
}
|
||||||
|
|
||||||
|
# process arguments
|
||||||
|
while [[ $# -gt 0 ]]
|
||||||
|
do
|
||||||
|
case "$1" in
|
||||||
|
*:* )
|
||||||
|
WAITFORIT_hostport=(${1//:/ })
|
||||||
|
WAITFORIT_HOST=${WAITFORIT_hostport[0]}
|
||||||
|
WAITFORIT_PORT=${WAITFORIT_hostport[1]}
|
||||||
|
shift 1
|
||||||
|
;;
|
||||||
|
--child)
|
||||||
|
WAITFORIT_CHILD=1
|
||||||
|
shift 1
|
||||||
|
;;
|
||||||
|
-q | --quiet)
|
||||||
|
WAITFORIT_QUIET=1
|
||||||
|
shift 1
|
||||||
|
;;
|
||||||
|
-s | --strict)
|
||||||
|
WAITFORIT_STRICT=1
|
||||||
|
shift 1
|
||||||
|
;;
|
||||||
|
-h)
|
||||||
|
WAITFORIT_HOST="$2"
|
||||||
|
if [[ $WAITFORIT_HOST == "" ]]; then break; fi
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--host=*)
|
||||||
|
WAITFORIT_HOST="${1#*=}"
|
||||||
|
shift 1
|
||||||
|
;;
|
||||||
|
-p)
|
||||||
|
WAITFORIT_PORT="$2"
|
||||||
|
if [[ $WAITFORIT_PORT == "" ]]; then break; fi
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--port=*)
|
||||||
|
WAITFORIT_PORT="${1#*=}"
|
||||||
|
shift 1
|
||||||
|
;;
|
||||||
|
-t)
|
||||||
|
WAITFORIT_TIMEOUT="$2"
|
||||||
|
if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--timeout=*)
|
||||||
|
WAITFORIT_TIMEOUT="${1#*=}"
|
||||||
|
shift 1
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
WAITFORIT_CLI=("$@")
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
--help)
|
||||||
|
usage
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echoerr "Unknown argument: $1"
|
||||||
|
usage
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then
|
||||||
|
echoerr "Error: you need to provide a host and port to test."
|
||||||
|
usage
|
||||||
|
fi
|
||||||
|
|
||||||
|
WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15}
|
||||||
|
WAITFORIT_STRICT=${WAITFORIT_STRICT:-0}
|
||||||
|
WAITFORIT_CHILD=${WAITFORIT_CHILD:-0}
|
||||||
|
WAITFORIT_QUIET=${WAITFORIT_QUIET:-0}
|
||||||
|
|
||||||
|
# Check to see if timeout is from busybox?
|
||||||
|
WAITFORIT_TIMEOUT_PATH=$(type -p timeout)
|
||||||
|
WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH)
|
||||||
|
|
||||||
|
WAITFORIT_BUSYTIMEFLAG=""
|
||||||
|
if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then
|
||||||
|
WAITFORIT_ISBUSY=1
|
||||||
|
# Check if busybox timeout uses -t flag
|
||||||
|
# (recent Alpine versions don't support -t anymore)
|
||||||
|
if timeout &>/dev/stdout | grep -q -e '-t '; then
|
||||||
|
WAITFORIT_BUSYTIMEFLAG="-t"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
WAITFORIT_ISBUSY=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $WAITFORIT_CHILD -gt 0 ]]; then
|
||||||
|
wait_for
|
||||||
|
WAITFORIT_RESULT=$?
|
||||||
|
exit $WAITFORIT_RESULT
|
||||||
|
else
|
||||||
|
if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then
|
||||||
|
wait_for_wrapper
|
||||||
|
WAITFORIT_RESULT=$?
|
||||||
|
else
|
||||||
|
wait_for
|
||||||
|
WAITFORIT_RESULT=$?
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $WAITFORIT_CLI != "" ]]; then
|
||||||
|
if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then
|
||||||
|
echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess"
|
||||||
|
exit $WAITFORIT_RESULT
|
||||||
|
fi
|
||||||
|
exec "${WAITFORIT_CLI[@]}"
|
||||||
|
else
|
||||||
|
exit $WAITFORIT_RESULT
|
||||||
|
fi
|
Loading…
Reference in New Issue