diff --git a/Dockerfile b/Dockerfile index f5a8d30..0c670f9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,45 +1,34 @@ -# https://stackoverflow.com/questions/53681522/share-variable-in-multi-stage-dockerfile-arg-before-from-not-substituted -ARG PROJECT_DIR=/huaxin-base-frontend - -FROM node:20-slim as builder -ARG PROJECT_DIR +FROM node:20-slim as base +ENV PROJECT_DIR=/huaxin-front \ + PNPM_HOME="/pnpm" \ + PATH="$PNPM_HOME:$PATH" WORKDIR $PROJECT_DIR +COPY ./ $PROJECT_DIR -# 安装pnpm -RUN npm install -g pnpm - -COPY . ./ -# 安装依赖 # 若网络不通,可以使用淘宝源 # RUN pnpm config set registry https://registry.npmmirror.com -# see https://pnpm.io/docker -FROM builder AS prod-deps +# 若不存在安装pnpm +RUN npm install -g pnpm + +# 构建项目 +# 安装生成依赖 +FROM base AS prod-deps 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 pnpm run build - -# mirror acceleration -# 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 +# 将prod-deps生成的依赖拷贝到base/$PROJECT_DIR中,开始在builder构建阶段build,最后拷贝build生成的dist到base/$PROJECT_DIR中 +FROM base AS result COPY --from=prod-deps $PROJECT_DIR/node_modules $PROJECT_DIR/node_modules -COPY --from=build $PROJECT_DIR/dist $PROJECT_DIR/dist - -# 构建项目 -ENV VITE_BASE_URL=/ -RUN pnpm build - +COPY --from=builder $PROJECT_DIR/dist $PROJECT_DIR/dist +# 构建nginx,并且在result之后拷贝dist到nginx中,其中包含了自定义nginx.conf FROM nginx:alpine as production -ARG PROJECT_DIR - -COPY --from=builder $PROJECT_DIR/dist/ /usr/share/nginx/html -COPY --from=builder $PROJECT_DIR/nginx.conf /etc/nginx/nginx.conf - -EXPOSE 80 +ENV PROJECT_DIR=/huaxin-front +COPY --from=result $PROJECT_DIR/dist/ /usr/share/nginx/html +COPY --from=result $PROJECT_DIR/nginx.conf /etc/nginx/nginx.conf +EXPOSE 80 \ No newline at end of file diff --git a/index.html b/index.html index ca8fe44..516b2b9 100644 --- a/index.html +++ b/index.html @@ -5,7 +5,7 @@ - 华信Admin + 华信办公系统
diff --git a/src/api/backend/api/materials_inventory.ts b/src/api/backend/api/materials_inventory.ts new file mode 100644 index 0000000..5f378b9 --- /dev/null +++ b/src/api/backend/api/materials_inventory.ts @@ -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('/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/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(`/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(`/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(`/api/contract/${param0}`, { + method: 'DELETE', + params: { ...queryParams }, + ...(options || { successMsg: '删除成功' }), + }); +} diff --git a/src/api/backend/api/typings.d.ts b/src/api/backend/api/typings.d.ts index 33e7ec2..bd5297b 100644 --- a/src/api/backend/api/typings.d.ts +++ b/src/api/backend/api/typings.d.ts @@ -1352,4 +1352,56 @@ declare namespace API { type ContractInfoParams = { 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; + }; } diff --git a/src/views/meterials-inventory/columns.tsx b/src/views/meterials-inventory/columns.tsx new file mode 100644 index 0000000..b1113d3 --- /dev/null +++ b/src/views/meterials-inventory/columns.tsx @@ -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; + +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 {label}; + }, + }, + + ]; +}; + +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, + }; + } +} diff --git a/src/views/meterials-inventory/formSchemas.ts b/src/views/meterials-inventory/formSchemas.ts new file mode 100644 index 0000000..2b30f17 --- /dev/null +++ b/src/views/meterials-inventory/formSchemas.ts @@ -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[] => [ + { + 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', + // }, + // }, + // }, +]; diff --git a/src/views/meterials-inventory/index.vue b/src/views/meterials-inventory/index.vue index 1f9cffd..cdbdae7 100644 --- a/src/views/meterials-inventory/index.vue +++ b/src/views/meterials-inventory/index.vue @@ -1,11 +1,221 @@ - diff --git a/wait-for-it.sh b/wait-for-it.sh new file mode 100644 index 0000000..3974640 --- /dev/null +++ b/wait-for-it.sh @@ -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 \ No newline at end of file