立项废除功能;选商标段信息和供应商信息功能对接完善

This commit is contained in:
z9130 2024-10-11 12:22:43 +08:00
parent 589ddf5954
commit 35e4de39fa
9 changed files with 524 additions and 111 deletions

View File

@ -48,6 +48,8 @@ export enum DICT_TYPE {
payment_nature = 'payment_nature',
/** 合同立项节点流程 */
contract_approval_flow_node = 'contract_approval_flow_node',
/** 合同选商节点流程 */
contract_business_flow_node = 'contract_business_flow_node',
/** 合同立项节点流程 */
contract_abolish_flow_node = 'contract_abolish_flow_node',
/** 合同授权类型 */

View File

@ -77,6 +77,14 @@ export default {
{ label: '待废除审批', value: 'abolishDepartmentAudit' },
]),
contract_business_flow_node: createEntry('合同选商节点流程', [
{ label: '待提交', value: 'edit' },
{ label: '待部门审核', value: 'departmentAudit' },
{ label: '已结束', value: 'end' },
{ label: '废除发起', value: 'abolishEdit' },
{ label: '待废除审批', value: 'abolishDepartmentAudit' },
]),
contract_abolish_flow_node: createEntry('合同立项节点流程', [
{ label: '待提交', value: 'edit' },
{ label: '待部门审核', value: 'departmentAudit' },

View File

@ -149,19 +149,129 @@ function removeRow(row) {
}
}
function handleAbolish() {
Modal.confirm({
title: '提示',
content: '是否确认作废该合同?确认后该合同无法再次使用',
okType: 'danger',
onOk: async () => {
// await Apis.contractBaseInfo.post_abolish({
// data: {
// guid: currData.value['guid'],
// },
// });
},
});
const userModalOpenType = ref<'abolish' | 'audit'>('audit');
const currAuditType = ref<'abolish' | 'audit'>('audit');
const userListByAbolish = ref([]);
async function handleAbolish(type: 'openModal' | 'confirm') {
userModalOpenType.value = 'abolish';
if (type === 'openModal') {
Modal.confirm({
title: '提示',
content: '是否确认作废该合同?确认后该合同无法再次使用',
okType: 'danger',
onOk: async () => {
chooseUserModalApi.setData({
title: '选择立项废除审批人',
userIds: [],
});
chooseUserModalApi.open();
},
});
}
if (type === 'confirm') {
const hideLoading = message.loading('提交中...', 0);
try {
await Apis.contractBaseInfo.post_abolishFlowStart({
data: {
guid: currData.value['guid'],
assigneeList: userListByAbolish.value.map((item) => item.ACCOUNT_ID),
},
});
} catch (error) {
logger.error('立项废除提交失败', error);
} finally {
hideLoading();
}
}
}
async function handleAbolishAudit(
type: 'accessConfirm' | 'reject' | 'rejectConfirm',
data?: any,
) {
console.log(type);
if (type === 'accessConfirm') {
Modal.confirm({
title: '提示',
content: '是否确认立项废除审核通过?',
onOk: async () => {
try {
await Apis.contractBaseInfo.post_abolishSubmit({
params: {
guid: id.value,
},
data: {
// appId: id.value,
taskId: currData.value.taskId,
nodeId: '',
comment: '通过',
},
});
message.success('审核通过');
back();
} catch (error) {
logger.error('审核通过失败', error);
message.error('审核通过失败,请稍候再试');
}
},
});
}
if (type === 'reject') {
Modal.confirm({
title: '提示',
content: '是否确认拒绝废除该立项?',
onOk: () => {
temporaryFormModalApi.setData({
title: '退回原因',
schema: {
columns: {
comment: {
title: '',
key: 'comment',
col: { span: 24 },
colon: false,
component: {
name: 'a-textarea',
vModel: 'value',
autoSize: { minRows: 4, maxRows: 6 },
placeholder: '请输入',
},
rules: [{ required: true, message: '请输入退回原因' }],
},
},
},
});
temporaryFormModalApi.open();
},
});
}
if (type === 'rejectConfirm') {
const comment = data.comment;
try {
await Apis.contractBaseInfo.post_rollback({
params: {
guid: id.value,
},
data: {
appId: id.value,
taskId: currData.value.taskId,
nodeId: '',
comment,
},
});
temporaryFormModalApi.close();
message.success('退回成功');
back();
} catch (error) {
logger.error('合同立项退回失败', error);
message.error('退回失败,请稍候再试');
}
}
}
function handleDelete() {
@ -195,6 +305,11 @@ function handleChooseConfirm(rows) {
async function handleChooseUserConfirm(e) {
chooseUserModalApi.close();
isLoading.value = true;
if (userModalOpenType.value === 'abolish') {
userListByAbolish.value = e;
handleAbolish('confirm');
return;
}
try {
await Apis.contractBaseInfo.post_applyFlowStart({
data: {
@ -302,9 +417,11 @@ async function handleSave() {
}
async function handleSubmit() {
userModalOpenType.value = 'audit';
// isLoading.value = true
chooseUserModalApi.setData({
title: '选择审批人',
userIds: [],
});
chooseUserModalApi.open();
}
@ -316,6 +433,10 @@ async function handleAudit(
console.log(type);
if (type === 'accessConfirm') {
if (currAuditType.value === 'abolish') {
await handleAbolish('confirm');
return;
}
Modal.confirm({
title: '提示',
content: '是否确认审核通过?',
@ -535,7 +656,7 @@ onMounted(async () => {
currData.inputUserId === userInfo!.userId
"
variant="destructive"
@click="handleAbolish()"
@click="handleAbolish('openModal')"
>
废除
</vben-button>
@ -546,6 +667,41 @@ onMounted(async () => {
>
删除
</vben-button>
<vben-button
v-if="
['abolishDepartmentAudit'].includes(currData.step) &&
currData.taskId
"
:disabled="!auditId"
variant="warning"
@click="handleAbolishAudit('accessConfirm')"
>
废除通过
</vben-button>
<vben-button
v-if="
['abolishDepartmentAudit'].includes(currData.step) &&
currData.taskId
"
:disabled="!auditId"
variant="destructive"
@click="handleAbolishAudit('accessConfirm')"
>
废除拒绝
</vben-button>
<vben-button
v-if="
['departmentAudit'].includes(currData.step) && currData.taskId
"
:disabled="!auditId"
variant="primary"
@click="handleAudit('accessConfirm')"
>
通过
</vben-button>
<vben-button variant="secondary" @click="handleBack()">
返回
</vben-button>

View File

@ -2,16 +2,10 @@ import type { VxeGridPropTypes } from 'vxe-table';
export function getBidColumns(_params?: any): VxeGridPropTypes.Columns {
const columns: VxeGridPropTypes.Columns = [
{
field: 'operate',
title: '操作',
width: 120,
slots: { default: 'operate-slot' },
},
{
type: 'seq',
width: 50,
},
// {
// type: 'seq',
// width: 50,
// },
{
type: 'expand',
width: '0px',
@ -35,6 +29,13 @@ export function getBidColumns(_params?: any): VxeGridPropTypes.Columns {
minWidth: 300,
slots: { default: 'phasedesc-slot' },
},
{
field: 'operate',
title: '操作',
width: 120,
fixed: 'right',
slots: { default: 'operate-slot' },
},
];
return columns;
@ -42,12 +43,6 @@ export function getBidColumns(_params?: any): VxeGridPropTypes.Columns {
export function getProviderColumns(_params?: any): VxeGridPropTypes.Columns {
const columns: VxeGridPropTypes.Columns = [
{
field: 'operate',
title: '操作',
width: 120,
slots: { default: 'operate-slot' },
},
{
field: 'providerName',
title: '供应商名称',
@ -77,7 +72,13 @@ export function getProviderColumns(_params?: any): VxeGridPropTypes.Columns {
minWidth: 200,
slots: { default: 'remarks-slot' },
},
{
field: 'operate',
title: '操作',
width: 120,
fixed: 'right',
slots: { default: 'operate-slot' },
},
];
return columns;

View File

@ -17,27 +17,6 @@ const [ChooseCompanyModal, chooseCompanyModalApi] = useVbenModal({
const xGridRefs = ref<Map<string, any>>(new Map());
const gridRefs = ref<Map<string, any>>(new Map());
export function getExtraData() {
// let tempBiddingList = xGridRef.value.getTableData().fullData;
// console.log(gridRefs.value);
for (const [key, value] of xGridRefs.value) {
console.log(`Key: ${key}, Value: ${value}`);
let xRef = value;
console.log(xRef.getTableData().fullData);
}
for (const [key, value] of gridRefs.value) {
console.log(`Key: ${key}, Value: ${value}`);
let xRef = value;
console.log(xRef.getTableData().fullData);
}
return {
biddingList: [],
};
}
export function getFormSchema(params: any = {}) {
let { formRef, dictMap = {}, bidList = [] } = params;
@ -299,7 +278,7 @@ export function getFormSchema(params: any = {}) {
<a-input v-model:value={row.contactPerson} />
),
'contactPhone-slot': ({ row }: any) => (
<a-input v-model:value={row.ContactPhone} />
<a-input v-model:value={row.contactPhone} />
),
'remarks-slot': ({ row }: any) => (
<a-textarea v-model:value={row.remarks} />

View File

@ -23,7 +23,13 @@ import chooseUserModal from '#/views/system/user/choose-user-modal.vue';
import { getColumns } from '../../approval/signing-basis/columns';
import { getFormSchema as getFormSchemaByBaseInfo } from './basic-info-curd';
import { getFormSchema, getExtraData } from './curd';
import { getFormSchema } from './curd';
import { getBidColumns, getProviderColumns } from './bid-columns';
import chooseCompanyModal from '../../company/list/choose-company-modal.vue';
const [ChooseCompanyModal, chooseCompanyModalApi] = useVbenModal({
connectedComponent: chooseCompanyModal,
});
const [ChooseUserModal, chooseUserModalApi] = useVbenModal({
connectedComponent: chooseUserModal,
@ -52,6 +58,9 @@ const isLoading = ref(false);
const contractTypeData = ref<any[]>([]);
const currData = ref<any>({});
const bidGridRefs = ref<Map<string, any>>(new Map());
const providerGridRefs = ref<Map<string, any>>(new Map());
const formBindingByBaseInfo = ref({
col: { span: 24 },
initialForm: {},
@ -80,6 +89,43 @@ const gridOptions = reactive(
}),
);
const gridOptionsByBid = ref({
height: 100,
showOverflow: true,
columns: getBidColumns({ readOnly: false }),
data: [],
toolbarConfig: {
enabled: false,
},
pagerConfig: {
enabled: false,
},
rowConfig: {
useKey: true,
isCurrent: false,
keyField: 'id',
},
expandConfig: {
expandAll: true,
reserve: true,
showIcon: false,
},
});
const gridOptionsByProvider = ref({
columns: getProviderColumns({ readOnly: false }),
toolbarConfig: {
enabled: false,
},
pagerConfig: {
enabled: false,
},
rowConfig: {
useKey: true,
isCurrent: false,
},
});
const fileList = ref<UploadFile[]>([]);
function handleBack() {
@ -137,6 +183,7 @@ function handleFold() {
function loadDataByContractTypeData() {}
const selectUsers = ref([]);
async function handleChooseUserConfirm(e) {
selectUsers.value = e;
chooseUserModalApi.close();
@ -144,6 +191,77 @@ async function handleChooseUserConfirm(e) {
handleSubmit('submit');
}
const currBid = ref();
function handleChooseCompanyConfirm(e) {
console.log(currBid.value);
const gridRef = providerGridRefs.value.get(currBid.value.phaseSeq); // Assuming you have a main grid ref
//
let bidRef = bidGridRefs.value.get(currBid.value.phaseSeq);
let bidFullData = bidRef.getTableData().fullData;
for (const item of bidFullData) {
if (item.phaseSeq === currBid.value.phaseSeq) {
item.providerList = item.providerList || [];
item.providerList.push(...e);
bidRef.loadData(bidFullData);
break;
}
}
console.log(bidRef.getTableData());
gridRef && gridRef.insertAt(e, -1);
}
const bidList = ref([]);
function onBidNumChange(value) {
let fullData = bidList.value;
const currentLength = fullData.length;
let rowsToAdd = [];
if (value < currentLength) {
// value
fullData.splice(value); //
} else if (value > currentLength) {
//
rowsToAdd = Array.from({ length: value - currentLength }).map((_, i) => ({
phaseSeq: currentLength + i + 1 + '',
phaseName: `${currentLength + i + 1}标段`,
phaseMoney: null,
phaseDesc: '',
providerList: [],
}));
// fullData
fullData.push(...rowsToAdd); // 使 push
}
bidList.value = fullData;
setTimeout(() => {
console.log(bidGridRefs.value);
console.log(rowsToAdd);
for (const item of rowsToAdd) {
console.log(item.phaseSeq, bidGridRefs.value.get(item.phaseSeq + ''));
bidGridRefs.value.get(item.phaseSeq + '').insertAt(item, -1);
}
}, 300);
}
function onChooseProvider(row) {
currBid.value = row;
const gridRef = providerGridRefs.value.get(currBid.value.phaseSeq); // Assuming you have a main grid ref
const tableFullData = gridRef?.getTableData()?.fullData || [];
chooseCompanyModalApi.setData({
title: '选择供应商',
limitMultipleNum: 5,
guids: tableFullData.map((item) => item.guid) || [],
});
chooseCompanyModalApi.open();
}
function removeProvider(row) {
const gridRef = providerGridRefs.value.get(currBid.value.phaseSeq);
}
async function handleAudit(
type: 'accessConfirm' | 'reject' | 'rejectConfirm',
data?: any,
@ -155,23 +273,24 @@ async function handleAudit(
title: '提示',
content: '是否确认审核通过?',
onOk: async () => {
const [error, _] = await Apis.selectMerchantsBasicInfo.post_submit({
params: {
guid: selectMerchantsBasicInfoId.value,
},
data: {
appId: id.value,
taskId: currData.value.taskId,
nodeId: '',
comment: '通过',
},
});
if (error) {
try {
await Apis.selectMerchantsBasicInfo.post_submit({
params: {
guid: selectMerchantsBasicInfoId.value,
},
data: {
appId: id.value,
taskId: currData.value.taskId,
nodeId: '',
comment: '通过',
},
});
message.success('审核通过');
} catch (error) {
logger.error('审核通过失败', error);
message.error('审核通过失败,请稍候再试');
return;
}
message.success('审核通过');
},
});
}
@ -231,22 +350,60 @@ async function handleAudit(
}
async function handleSave() {
console.log(formRefByBaseInfo.value.form);
try {
await formRef.value.submit();
await formRefByBaseInfo.value.submit();
} catch {
message.error('请完成必填项的填写');
return;
}
console.log(formRef.value.form);
let tempBidList: any = [];
let tempProviderList: any = [];
for (const item of bidList.value) {
const gridRef = bidGridRefs.value.get(item.phaseSeq);
let fullData = gridRef.getTableData().fullData;
if (fullData.length > 0) {
let bidInfo = fullData[0];
if (!bidInfo.phaseName) {
message.error(`【标段${item.phaseSeq}】中的标段名称不能为空`);
return;
}
if (!bidInfo.phaseMoney) {
message.error(`${item.phaseName}】中的预算金额不能为空`);
return;
}
bidInfo.id = undefined;
tempBidList.push(bidInfo);
for (const item2 of bidInfo.providerList || []) {
item2.phaseSeq = item.phaseSeq;
if (!item2.aptitudeName) {
message.error(`【标段${item.phaseSeq}】中供应商资质名称不能为空`);
return;
}
if (!item2.contactPerson) {
message.error(`【标段${item.phaseSeq}】中供应商联系人不能为空`);
return;
}
if (!item2.contactPhone) {
message.error(`【标段${item.phaseSeq}】中供应商联系人电话不能为空`);
return;
}
tempProviderList.push(item2);
}
}
}
console.log(tempBidList);
console.log(tempProviderList);
// return;
console.log(getExtraData());
return;
isLoading.value = true;
try {
try {
await formRef.value.submit();
await formRefByBaseInfo.value.submit();
} catch {
message.error('请完成必填项的填写');
return;
}
const contractForm = formRefByBaseInfo.value.form;
const bussinessForm = formRef.value.form;
@ -295,7 +452,8 @@ async function handleSave() {
const form = {
contractBaseInfo: contractForm,
selectMerchantsBasicInfo: bussinessForm,
biddingList: [],
biddingList: tempBidList,
contractRecommendProviderList: tempProviderList,
};
// if (form.selectMerchantsBasicInfo.isBid) {
@ -517,6 +675,11 @@ onMounted(async () => {
@confirm="handleChooseUserConfirm"
/>
<ChooseCompanyModal
class="w-[950px]"
@confirm="handleChooseCompanyConfirm"
></ChooseCompanyModal>
<TemporaryFormModal
class="w-[950px]"
@confirm="handleAudit('rejectConfirm', $event)"
@ -626,9 +789,106 @@ onMounted(async () => {
/>
</a-form-item>
<div class="w-2"></div>
<a-select v-if="form.isBid === '1'">
</a-select>
<a-form-item-rest>
<div class="!mb-0 flex flex-row items-center">
<span class="ml-2 mr-4 w-full">标段数量</span>
<a-select
v-if="form.isBid === '1'"
v-model:value="form.bidNum"
class="min-w-[180px]"
:options="dictMap[DICT_TYPE.section_num]"
@change="onBidNumChange"
>
</a-select>
</div>
</a-form-item-rest>
</div>
<div
:style="{
display:
form.isBid === '1' && form.bidNum > 0
? 'block'
: 'none',
}"
>
<a-form-item-rest>
<div v-for="item in bidList" :key="item.phaseSeq">
<a-divider>标段{{ item.phaseSeq }}</a-divider>
<VxeGrid
:ref="
(el) =>
el && bidGridRefs.set(item.phaseSeq + '', el)
"
v-bind="gridOptionsByBid"
>
<!-- 插槽配置 -->
<template v-slot:bidname-slot="{ row }">
<a-input v-model:value="row.phaseName" />
</template>
<template v-slot:phasemoney-slot="{ row }">
<a-input-number
addon-after="元"
v-model:value="row.phaseMoney"
/>
</template>
<template v-slot:phasedesc-slot="{ row }">
<a-input v-model:value="row.phaseDesc" />
</template>
<!-- 操作的插槽 -->
<template v-slot:operate-slot="{ row }">
<a-button
size="small"
type="primary"
@click="onChooseProvider(row)"
>
选择供应商
</a-button>
</template>
</VxeGrid>
<VxeGrid
:ref="
(el) =>
el && providerGridRefs.set(item.phaseSeq + '', el)
"
v-bind="gridOptionsByProvider"
>
<!-- 插槽配置 -->
<template v-slot:qualification-slot="{ row }">
<a-input v-model:value="row.aptitudeName" />
</template>
<template v-slot:contactPerson-slot="{ row }">
<a-input v-model:value="row.contactPerson" />
</template>
<template v-slot:contactPhone-slot="{ row }">
<a-input v-model:value="row.contactPhone" />
</template>
<template v-slot:bidname-slot="{ row }">
<a-input v-model:value="row.phaseName" />
</template>
<template v-slot:remarks-slot="{ row }">
<a-input v-model:value="row.remarks" />
</template>
<!-- 操作的插槽 -->
<template v-slot:operate-slot="{ row }">
<a-button
type="text"
size="small"
class="text-red-500"
@click="removeProvider(row)"
>
移除
</a-button>
</template>
</VxeGrid>
</div>
</a-form-item-rest>
</div>
</div>
</template>

View File

@ -8,6 +8,7 @@ import Apis from '#/api';
import { useVxeTable } from '#/hooks/vxeTable';
import { getTodoColumns } from '#/views/contract/schema';
import { toDetailPage } from '#/views/contract/utils';
import { logger } from 'common-utils';
const props = withDefaults(
defineProps<{
@ -121,19 +122,17 @@ const grid2Options = reactive(
);
function toDetail(type: string, id: string, row?: any) {
switch (type) {
case 'contractSetup': {
router.push(`/contract/approval/edit/${id}`);
break;
}
case 'selectMerchant': {
router.push(`/contract/business/edit/${row.contractId}`);
break;
}
default: {
break;
}
console.log(type, row);
if (['contractSetup', 'contractSetupAbolish'].includes(type)) {
router.push(`/contract/approval/edit/${id}`);
return;
}
if (['selectMerchant'].includes(type)) {
router.push(`/contract/business/edit/${row.contractId}`);
return;
}
logger.error('toDetail not support type:' + type, row);
}
onMounted(() => {});

View File

@ -5,6 +5,7 @@ import { h } from 'vue';
import { Tooltip } from 'ant-design-vue';
import { DICT_TYPE, getDictObj } from '#/utils/dict';
import { useRender } from '#/hooks/useRender';
export const PrimaryKey = 'guid';
@ -25,13 +26,20 @@ export function getTodoColumns(_params: any = {}): VxeGridPropTypes.Columns {
width: 120,
slots: {
default: ({ row }) => {
return (
getDictObj(DICT_TYPE.contract_todo_type, row.module)?.label || ''
);
return row.flowName;
},
},
},
{
field: 'taskName',
title: '任务',
width: 150,
slots: {
default: ({ row }) => {
return row.taskName;
},
},
},
{ field: 'taskName', title: '任务', width: 200 },
{ field: 'createTime', title: '分配时间', width: 150 },
{ field: 'inputDepartName', title: '承办单位/部门', width: 150 },
{ field: 'assigneeName', title: '承办人', width: 100 },

View File

@ -30,35 +30,35 @@ export default defineConfig(async () => {
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/app/, '/'),
// target: `http://10.71.220.24:8083/rl`,
target: `http://192.168.148.88:8083/rl`,
target: `http://192.168.147.164:8083/rl`,
ws: true,
},
'/api/flowCenter': {
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/flowCenter/, '/flowCenter'),
// target: `http://10.71.220.24:8083/rl`,
target: `http://192.168.148.88:19007`,
target: `http://192.168.147.164:19007`,
ws: true,
},
'/api/czg/flowCenter': {
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/flowCenter/, '/flowCenter'),
// target: `http://10.71.220.24:8083/rl`,
target: `http://192.168.148.88:19007`,
target: `http://192.168.147.164:19007`,
ws: true,
},
'/api/zp/flowCenter': {
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/flowCenter/, '/flowCenter'),
// target: `http://10.71.220.24:8083/rl`,
target: `http://192.168.148.88:19007`,
target: `http://192.168.147.164:19007`,
ws: true,
},
'/api/zzz/flowCenter': {
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/flowCenter/, '/flowCenter'),
// target: `http://10.71.220.24:8083/rl`,
target: `http://192.168.148.88:19007`,
target: `http://192.168.147.164:19007`,
ws: true,
},
'/api/czg/app': {
@ -71,15 +71,15 @@ export default defineConfig(async () => {
'/api/czg/uc': {
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/czg\/uc/, '/'),
target: `http://192.168.148.88:8082`,
// target: `http://192.168.148.88:8082`,
target: `http://192.168.147.164:8082`,
// target: `http://192.168.147.164:8082`,
ws: true,
},
'/api/uc': {
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/uc/, '/'),
// target: `http://10.71.220.24:8082`,
target: `http://192.168.148.88:8082`,
target: `http://192.168.147.164:8082`,
ws: true,
},
'/api/xmh/app': {
@ -93,7 +93,7 @@ export default defineConfig(async () => {
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/xmh\/uc/, '/'),
// mock代理目标地址
target: `http://192.168.148.88:8082`,
target: `http://192.168.147.164:8082`,
ws: true,
},
'/api/zp/app': {
@ -107,21 +107,21 @@ export default defineConfig(async () => {
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/zp\/uc/, '/'),
// mock代理目标地址
target: `http://192.168.148.88:8082`,
target: `http://192.168.147.164:8082`,
ws: true,
},
'/api/zzz/app': {
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/zzz\/app/, '/'),
// target: `http://192.168.0.193:8083/rl`,
target: `http://192.168.148.88:8089/rl`,
target: `http://192.168.147.164:8089/rl`,
ws: true,
},
'/api/zzz/uc': {
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/zzz\/uc/, '/'),
target: `http://192.168.148.88:8082`,
// target: `http://192.168.148.88:8082`,
target: `http://192.168.147.164:8082`,
// target: `http://192.168.147.164:8082`,
ws: true,
},
},