src/api/projectManagement/project.js
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,95 @@ import request from '@/utils/request' export function listProject(data) { return request({ url: '/projectManagement/info/listPage', method: 'post', data: data }) } export function getProject(id) { return request({ url: `/projectManagement/info/${id}`, method: 'post' }) } export function addProject(data) { return request({ url: '/projectManagement/info/save', method: 'post', data: data }) } export function updateProject(data) { return request({ url: '/projectManagement/info/save', method: 'post', data: data }) } export function delProject(ids) { return request({ url: '/projectManagement/info/remove', method: 'delete', data: ids }) } export function updateStatus(data) { return request({ url: '/projectManagement/info/updateStatus', method: 'post', data: data }) } export function submitProject(data) { return request({ url: '/projectManagement/info/updateStatus', method: 'post', data: { ...data, reviewStatus: 0 } }) } export function auditProject(data) { return request({ url: '/projectManagement/info/updateStatus', method: 'post', data: { ...data, reviewStatus: 1 } }) } export function reverseAuditProject(data) { return request({ url: '/projectManagement/info/updateStatus', method: 'post', data: { ...data, reviewStatus: 0 } }) } export function listPlan(data) { return request({ url: '/projectManagement/plan/listPage', method: 'post', data: data }) } export function addPlan(data) { return request({ url: '/projectManagement/plan/save', method: 'post', data: data }) } export function delPlan(id) { return request({ url: `/projectManagement/plan/delete/${id}`, method: 'post' }) } src/components/Dialog/FormDialog.vue
@@ -8,14 +8,18 @@ <slot></slot> <template #footer> <div class="dialog-footer"> <el-button v-if="showConfirm" type="primary" @click="handleConfirm" > 确认 </el-button> <el-button @click="handleCancel">åæ¶</el-button> <!-- èªå®ä¹æé®ææ§½ --> <slot name="footer"> <!-- é»è®¤æé® --> <el-button v-if="showConfirm" type="primary" @click="handleConfirm" > 确认 </el-button> <el-button @click="handleCancel">åæ¶</el-button> </slot> </div> </template> </el-dialog> src/components/SearchPanel/index.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,257 @@ <template> <div class="search-panel-container"> <el-form ref="formRef" :model="modelValue" class="search-form" label-width="0" > <el-row :gutter="10" class="form-row"> <!-- 渲æè¡¨å项 --> <el-col v-for="(item, index) in visibleSchema" :key="item.prop || index" :xs="24" :sm="12" :md="8" :lg="4" :xl="4" class="search-col" > <el-form-item :prop="item.prop" :rules="item.rules" class="search-form-item"> <!-- èªå®ä¹ææ§½ --> <slot v-if="item.slot" :name="item.slot" :item="item"></slot> <!-- é»è®¤æ¸²æç±»å --> <template v-else> <!-- è¾å ¥æ¡ --> <el-input v-if="item.type === 'input'" v-model="modelValue[item.prop]" :placeholder="item.placeholder || '请è¾å ¥'" clearable class="full-width" v-bind="item.props" @keyup.enter="handleSearch" /> <!-- ä¸ææ¡ --> <el-select v-else-if="item.type === 'select'" v-model="modelValue[item.prop]" :placeholder="item.placeholder || 'è¯·éæ©'" clearable class="full-width" v-bind="item.props" > {{ item || 'è¯·éæ©' }} <!-- <el-option v-for="(opt,idx) in getOptions(item)" :key="idx" :label="opt.label" :value="opt.value" /> --> </el-select> <!-- æ¥æéæ©å¨ --> <el-date-picker v-else-if="item.type === 'date'" v-model="modelValue[item.prop]" type="date" :placeholder="item.placeholder || 'éæ©æ¥æ'" style="width: 100%" value-format="YYYY-MM-DD" class="full-width" v-bind="item.props" /> <!-- æ¥æèå´éæ©å¨ --> <el-date-picker v-else-if="item.type === 'daterange'" v-model="modelValue[item.prop]" type="daterange" range-separator="è³" start-placeholder="å¼å§æ¥æ" end-placeholder="ç»ææ¥æ" value-format="YYYY-MM-DD" class="full-width" v-bind="item.props" /> </template> </el-form-item> </el-col> <!-- æé®åºå --> <el-col :xs="24" :sm="12" :md="8" :lg="4" :xl="4" class="search-actions-col"> <el-form-item class="search-actions"> <el-button style="background: #002FA7; color: white;" icon="Search" @click="handleSearch">æç´¢</el-button> <el-button icon="Refresh" @click="handleReset">éç½®</el-button> </el-form-item> </el-col> </el-row> <!-- å±å¼/æ¶èµ·æé® --> <div v-if="schema.length > 5" class="expand-toggle" @click="toggleExpand"> <span>{{ isExpanded ? 'æ¶èµ·' : 'å±å¼' }}</span> <el-icon :class="{ 'is-reverse': isExpanded }"> <ArrowDown /> </el-icon> </div> </el-form> </div> </template> <script setup name="SearchPanel"> import { ref, reactive, computed, getCurrentInstance, onMounted } from 'vue'; import { ArrowDown, Search, Refresh } from '@element-plus/icons-vue'; const { proxy } = getCurrentInstance(); const props = defineProps({ // è¡¨åæ°æ®å¯¹è±¡ modelValue: { type: Object, required: true }, // 表åé 置项 schema: { type: Array, default: () => [] } }); const emit = defineEmits(['update:modelValue', 'search', 'reset']); // æ¯å¦å±å¼ const isExpanded = ref(false); const formRef = ref(null); const dictMap = reactive({}); // 计ç®å¯è§ç schema 项 const visibleSchema = computed(() => { if (isExpanded.value || props.schema.length <= 5) { return props.schema; } return props.schema.slice(0, 5); }); // åå§ååå ¸æ°æ® onMounted(() => { const dicts = props.schema.filter(item => item.dict).map(item => item.dict); if (dicts.length > 0 && proxy.useDict) { const dictData = proxy.useDict(...dicts); Object.keys(dictData).forEach(key => { dictMap[key] = dictData[key]; }); } }); // è·å䏿é项 (æ¯æéæ options å åå ¸ dict) function getOptions(item) { if (item.options) return item.options; if (item.dict && dictMap[item.dict]) { return dictMap[item.dict].value || []; } return []; } // æç´¢ function handleSearch() { emit('search', props.modelValue); } // éç½® function handleReset() { if (formRef.value) { formRef.value.resetFields(); } const keys = props.schema.map(item => item.prop).filter(Boolean); keys.forEach(key => { props.modelValue[key] = undefined; }); emit('update:modelValue', props.modelValue); emit('reset'); } // 忢å±å¼/æ¶èµ· function toggleExpand() { isExpanded.value = !isExpanded.value; } </script> <style scoped lang="scss"> .search-panel-container { background: #fff; padding: 15px 15px 5px; border-radius: 8px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05); margin-bottom: 15px; .search-form { .form-row { width: 100%; } .search-col { margin-bottom: 10px; } .search-form-item { margin-right: 0; margin-bottom: 0; width: 100%; :deep(.el-form-item__content) { width: 100%; } } .full-width { width: 100% !important; } .search-actions-col { margin-left: auto; display: flex; justify-content: flex-end; margin-bottom: 10px; } .search-actions { margin-bottom: 0; margin-right: 0; :deep(.el-button--primary) { background-color: #409eff; border-color: #409eff; } } .expand-toggle { display: flex; align-items: center; justify-content: center; gap: 4px; font-size: 13px; color: #909399; cursor: pointer; padding: 5px 0; user-select: none; width: 100%; border-top: 1px solid #f0f2f5; margin-top: 5px; &:hover { color: #409eff; } .el-icon { transition: transform 0.3s; &.is-reverse { transform: rotate(180deg); } } } } } </style> src/layout/components/Sidebar/Logo.vue
@@ -2,11 +2,11 @@ <div class="sidebar-logo-container" :class="{ 'collapse': collapse }"> <transition name="sidebarLogoFade"> <router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/"> <!-- <img v-if="logoUrl" :src="logoUrl" class="sidebar-logo" @error="handleImageError" alt="å ¬å¸Logo" />--> <img v-if="logoUrl" :src="logoUrl" class="sidebar-logo" @error="handleImageError" alt="å ¬å¸Logo" /> <h1 class="sidebar-title">{{ title }}</h1> </router-link> <router-link v-else key="expand" class="sidebar-logo-link" to="/"> <!-- <img v-if="logoUrl" :src="logoUrl" class="sidebar-logo" @error="handleImageError" alt="å ¬å¸Logo" />--> <img v-if="logoUrl" :src="logoUrl" class="sidebar-logo" @error="handleImageError" alt="å ¬å¸Logo" /> <h1 class="sidebar-title">{{ title }}</h1> </router-link> </transition> src/views/basicData/product/ProductSelectDialog.vue
@@ -32,10 +32,10 @@ </div> <template #footer> <el-button @click="close()">åæ¶</el-button> <el-button type="primary" :disabled="multipleSelection.length === 0" @click="onConfirm"> ç¡®å® </el-button> <el-button @click="close()">åæ¶</el-button> </template> </el-dialog> </template> src/views/financialManagement/salesRefund/components/ReceiptandRefundPopupWindow.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,226 @@ <template> <el-dialog v-model="visible" title="æ¶æ¬¾/鿬¾" width="90%" append-to-body> <div class="section"> <div class="section-title descriptions">åºç¡èµæ</div> <el-form :model="form" label-width="100px"> <el-row :gutter="20"> <el-col :span="6"> <el-form-item label="åæ®ç¼å·"> <el-input v-model="form.billNo" placeholder="使ç¨ç³»ç»ç¼å·" /> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="客æ·"> <el-select v-model="form.customerId" placeholder="è¯·éæ©"> <el-option v-for="c in customerOptions" :key="c.value" :label="c.label" :value="c.value" /> </el-select> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="å¶å人"> <el-select v-model="form.makerId" placeholder="è¯·éæ©"> <el-option v-for="u in userOptions" :key="u.value" :label="u.label" :value="u.value" /> </el-select> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="å¶åæ¥æ"> <el-date-picker v-model="form.makeDate" type="date" value-format="YYYY-MM-DD" /> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="ç³è¯·é¨é¨"> <el-select v-model="form.applyDeptId" placeholder="è¯·éæ©"> <el-option v-for="d in deptOptions" :key="d.value" :label="d.label" :value="d.value" /> </el-select> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="夿³¨"> <el-input v-model="form.remark" maxlength="100" show-word-limit placeholder="请è¾å ¥" /> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="éä»¶"> <el-upload :action="uploadUrl" :headers="uploadHeaders" name="files" :on-success="onUploadSuccess"> <el-button>ä¸ä¼ æä»¶</el-button> </el-upload> </el-form-item> </el-col> </el-row> </el-form> </div> <div class="section"> <div class="toolbar"> <div class="section-title descriptions">仿¬¾å表</div> <el-input v-model="form.discountAmount" placeholder="伿 éé¢" style="width:240px" /> </div> <el-table :data="form.paymentList" border> <el-table-column label="仿¬¾è´¦å·" minWidth="160"> <template #default="scope"> <el-input v-model="scope.row.accountNo" placeholder="请è¾å ¥" /> </template> </el-table-column> <el-table-column label="仿¬¾è´¦å·åç§°" minWidth="180"> <template #default="scope"> <el-select v-model="scope.row.accountName" placeholder="è¯·éæ©"> <el-option v-for="a in accountOptions" :key="a.value" :label="a.label" :value="a.label" /> </el-select> </template> </el-table-column> <el-table-column label="仿¬¾æ¹å¼" minWidth="140"> <template #default="scope"> <el-select v-model="scope.row.payMethod" placeholder="è¯·éæ©"> <el-option v-for="m in payMethodOptions" :key="m.value" :label="m.label" :value="m.value" /> </el-select> </template> </el-table-column> <el-table-column label="å®é 仿¬¾éé¢" minWidth="160"> <template #default="scope"> <el-input v-model="scope.row.amount" placeholder="请è¾å ¥" /> </template> </el-table-column> <el-table-column label="æç»è´¹" minWidth="140"> <template #default="scope"> <el-input v-model="scope.row.fee" placeholder="请è¾å ¥" /> </template> </el-table-column> <el-table-column label="交æå·/票æ®å·" minWidth="180"> <template #default="scope"> <el-input v-model="scope.row.txNo" placeholder="请è¾å ¥" /> </template> </el-table-column> <el-table-column label="夿³¨" minWidth="200"> <template #default="scope"> <el-input v-model="scope.row.remark" maxlength="30" show-word-limit placeholder="请è¾å ¥" /> </template> </el-table-column> <el-table-column label="æä½" minWidth="120" fixed="right"> <template #default="scope"> <el-button link type="primary" @click="addPayment">æ°å¢ä¸è¡</el-button> <el-button link type="danger" @click="removePayment(scope.$index)">å é¤</el-button> </template> </el-table-column> </el-table> <div class="summary">å计</div> </div> <div class="section"> <div class="section-container"> <div class="section-title descriptions">æºåä¿¡æ¯</div> <div class="source-toolbar"> <el-button @click="clearSource">æ¸ ç©º</el-button> <el-button @click="selectSource">éæ©æºå</el-button> <el-button type="primary" @click="autoWriteOff">èªå¨æ ¸é</el-button> </div> </div> <el-table :data="form.sourceList" border> <el-table-column label="åæ®æ¥æ" minWidth="160" prop="billDate" /> <el-table-column label="åæ®ç±»å" minWidth="160" prop="billType" /> <el-table-column label="åæ®ç¼å·" minWidth="200" prop="billNo" /> <el-table-column label="åæ®éé¢" minWidth="120" prop="billAmount" /> <el-table-column label="å·²æ ¸ééé¢" minWidth="120" prop="wroteAmount" /> <el-table-column label="æªæ ¸ééé¢" minWidth="120" prop="unWroteAmount" /> <el-table-column label="æ¬æ¬¡æ ¸ééé¢" minWidth="160"> <template #default="scope"> <el-input v-model="scope.row.thisWriteOffAmount" /> </template> </el-table-column> <el-table-column label="æä½" width="100" fixed="right"> <template #default="scope"> <el-button link type="danger" @click="removeSource(scope.$index)">å é¤</el-button> </template> </el-table-column> </el-table> <div class="summary">å计</div> </div> <template #footer> <el-button type="primary" @click="submit">确认</el-button> <el-button @click="visible=false">åæ¶</el-button> </template> </el-dialog> </template> <script setup> import { ref } from 'vue'; import { getToken } from '@/utils/auth'; const visible = ref(false); const form = ref({ billNo: '', customerId: undefined, makerId: undefined, makeDate: '', applyDeptId: undefined, remark: '', discountAmount: '', paymentList: [{ accountNo: '', accountName: '', payMethod: '', amount: '', fee: '', txNo: '', remark: '' }], sourceList: [{ billDate: '', billType: '', billNo: '', billAmount: 0, wroteAmount: 0, unWroteAmount: 0, thisWriteOffAmount: '' }] }); const customerOptions = ref([]); const userOptions = ref([]); const deptOptions = ref([]); const accountOptions = ref([]); const payMethodOptions = ref([]); const uploadUrl = import.meta.env.VITE_APP_BASE_API + '/basic/customer-follow/upload'; const uploadHeaders = { Authorization: 'Bearer ' + getToken() }; function addPayment() { form.value.paymentList.push({ accountNo: '', accountName: '', payMethod: '', amount: '', fee: '', txNo: '', remark: '' }); } function removePayment(i) { form.value.paymentList.splice(i, 1); } function removeSource(i) { form.value.sourceList.splice(i, 1); } function clearSource() { form.value.sourceList = []; } function selectSource() {} function autoWriteOff() {} function onUploadSuccess() {} function open(payload) { visible.value = true; } function submit() { visible.value = false; emit('submitted'); } defineExpose({ open }); </script> <style scoped> .section { background: #fff; border-radius: 8px; box-shadow: 0 2px 12px 0 rgba(0,0,0,0.05); padding: 16px; margin-bottom: 16px; } .section-title { font-weight: 600; margin-bottom: 12px; } .descriptions { margin-bottom: 20px; display: inline-block; font-size: 1rem; font-weight: 600; padding-left: 12px; position: relative; } .descriptions::before { content: ""; position: absolute; left: 0; top: 50%; transform: translateY(-50%); width: 4px; height: 1rem; background-color: #002FA7; border-radius: 2px; } .toolbar { margin-bottom: 10px; display: flex; justify-content: space-between; align-items: center; } .source-toolbar { margin-bottom: 10px; display: flex; gap: 8px; } .summary { padding: 8px 12px; background: #fff7e6; color: #ad6800; } .section-container{display: flex;align-items: center;justify-content: space-between; } </style> src/views/financialManagement/salesRefund/index.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,134 @@ <template> <div class="app-container"> <!-- 使ç¨å ¬å ±æç´¢ç»ä»¶ --> <SearchPanel v-model="queryParams" :schema="searchSchema" @search="handleQuery" @reset="resetQuery" /> <!-- è¡¨æ ¼åºå --> <el-card class="table-card"> <el-table :data="refundList" v-loading="loading" border> <el-table-column label="éè´§åå·" prop="returnManagementNo" align="center" /> <el-table-column label="客æ·åç§°" prop="customerName" align="center" /> <el-table-column label="éå®åå·" prop="salesContractNo" align="center" /> <el-table-column label="åºé款éé¢" prop="refundAmount" align="center" /> <el-table-column label="已鿬¾éé¢" prop="refundedAmount" align="center" /> <el-table-column label="æªé款éé¢" prop="notRefundedAmount" align="center" /> <el-table-column label="ç¶æ" prop="status" align="center"> <template #default="scope"> <dict-tag :options="dictRef.sales_refund_status.value" :value="scope.row.status" /> </template> </el-table-column> <el-table-column label="å建人" prop="createUserName" align="center" /> <el-table-column label="å建æ¶é´" prop="createTime" align="center" /> <el-table-column label="æä½" align="center" width="150"> <template #default="scope"> <el-button link type="primary" @click="openDetail(scope.row)">详æ </el-button> <el-button link type="primary" @click="openConfirm(scope.row)">确认</el-button> </template> </el-table-column> </el-table> <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" /> </el-card> <ReceiptandRefundPopupWindow ref="popupRef" @submitted="getList" /> </div> </template> <script setup name="SalesRefund"> import { ref, reactive, onMounted, computed, getCurrentInstance } from 'vue'; const { proxy } = getCurrentInstance(); import { listPage, add, update, del } from '@/api/financialManagement/salesRefund'; import SearchPanel from '@/components/SearchPanel/index.vue'; import ReceiptandRefundPopupWindow from './components/ReceiptandRefundPopupWindow.vue'; // æ¥è¯¢åæ° const queryParams = reactive({ pageNum: 1, pageSize: 10, returnManagementNo: undefined, customerName: undefined, salesContractNo: undefined, createUserName: undefined, status: undefined }); const dictRef = proxy.useDict('sales_refund_status'); const salesRefundStatusOptions = computed(() => dictRef.sales_refund_status.value || []); // æç´¢æ é ç½® const searchSchema = [ { type: 'input', prop: 'returnManagementNo', placeholder: 'éè´§åå·' }, { type: 'input', prop: 'customerName', placeholder: '客æ·åç§°' }, { type: 'input', prop: 'salesContractNo', placeholder: 'éå®åå·' }, { type: 'input', prop: 'createUserName', placeholder: 'å建人åç§°' }, { type: 'select', prop: 'status', placeholder: 'ç¶æ', options: salesRefundStatusOptions } ]; const loading = ref(false); const total = ref(0); const refundList = ref([]); const popupRef = ref(null); /** æ¥è¯¢å表 */ function getList() { loading.value = true; const { pageNum, pageSize, ...filters } = queryParams; listPlan({ current: pageNum, size: pageSize, ...filters }) .then(res => { refundList.value = res?.data?.records || res?.rows || []; total.value = res?.data?.total || res?.total || 0; }) .finally(() => { loading.value = false; }); } /** æç´¢æé®æä½ */ function handleQuery() { queryParams.pageNum = 1; getList(); } /** éç½®æé®æä½ */ function resetQuery() { handleQuery(); } function openDetail(row) { if (popupRef.value) { popupRef.value.open({ mode: 'detail', row }); } } function openConfirm(row) { if (popupRef.value) { popupRef.value.open({ mode: 'confirm', row }); } } onMounted(() => { getList(); }); </script> <style scoped lang="scss"> .table-card { border-radius: 8px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05); } </style> <!-- keep-alive child --> src/views/personnelManagement/monthlyStatistics/components/auditDia.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,216 @@ <template> <FormDialog v-model="dialogVisible" title="å·¥èµå®¡æ ¸" width="900px" @close="handleClose" > <!-- å·¥èµè¡¨åºç¡ä¿¡æ¯ --> <el-card shadow="never" style="margin-bottom: 16px;"> <template #header> <span>å·¥èµè¡¨ä¿¡æ¯</span> </template> <el-descriptions :column="3" border> <el-descriptions-item label="å·¥èµä¸»é¢">{{ auditData?.salaryTitle || '-' }}</el-descriptions-item> <el-descriptions-item label="å·¥èµæä»½">{{ auditData?.salaryMonth || '-' }}</el-descriptions-item> <el-descriptions-item label="å·¥èµæ»é¢">Â¥ {{ formatMoney(auditData?.totalSalary) }}</el-descriptions-item> <el-descriptions-item label="æ¯ä»é¶è¡">{{ auditData?.payBank || '-' }}</el-descriptions-item> <el-descriptions-item label="å®¡æ ¸äºº">{{ auditData?.auditUserName || '-' }}</el-descriptions-item> <el-descriptions-item label="夿³¨">{{ auditData?.remark || '-' }}</el-descriptions-item> </el-descriptions> </el-card> <!-- åå·¥å·¥èµæç» --> <el-card shadow="never" style="margin-bottom: 16px;"> <template #header> <span>åå·¥å·¥èµæç»</span> </template> <div v-if="!employeeList || employeeList.length === 0" style="text-align: center; padding: 20px; color: #909399;"> <div>ææ åå·¥å·¥èµæç»æ°æ®</div> <div style="font-size: 12px; margin-top: 5px;">åå·¥æç»æ°æ®éè¦å¨å·¥èµè¡¨çææç¼è¾æ¶æä¼ä¿å</div> </div> <div v-else> <el-table :data="employeeList" border max-height="300" style="width: 100%"> <el-table-column prop="staffName" label="åå·¥å§å" width="100" /> <el-table-column prop="deptName" label="é¨é¨" width="120" /> <el-table-column prop="basicSalary" label="åºæ¬å·¥èµ" width="100" align="right"> <template #default="{ row }">Â¥ {{ formatMoney(row.basicSalary) }}</template> </el-table-column> <el-table-column prop="pieceSalary" label="计件工èµ" width="100" align="right"> <template #default="{ row }">Â¥ {{ formatMoney(row.pieceSalary) }}</template> </el-table-column> <el-table-column prop="hourlySalary" label="计æ¶å·¥èµ" width="100" align="right"> <template #default="{ row }">Â¥ {{ formatMoney(row.hourlySalary) }}</template> </el-table-column> <el-table-column prop="otherIncome" label="å ¶ä»æ¶å ¥" width="100" align="right"> <template #default="{ row }">Â¥ {{ formatMoney(row.otherIncome) }}</template> </el-table-column> <el-table-column prop="socialPersonal" label="社ä¿ä¸ªäºº" width="100" align="right"> <template #default="{ row }">Â¥ {{ formatMoney(row.socialPersonal) }}</template> </el-table-column> <el-table-column prop="fundPersonal" label="å ¬ç§¯é个人" width="120" align="right"> <template #default="{ row }">Â¥ {{ formatMoney(row.fundPersonal) }}</template> </el-table-column> <el-table-column prop="salaryTax" label="å·¥èµä¸ªç¨" width="100" align="right"> <template #default="{ row }">Â¥ {{ formatMoney(row.salaryTax) }}</template> </el-table-column> <el-table-column prop="netSalary" label="å®åå·¥èµ" width="100" align="right" fixed="right"> <template #default="{ row }">Â¥ {{ formatMoney(row.netSalary) }}</template> </el-table-column> </el-table> <div style="margin-top: 10px; text-align: right; font-weight: bold;"> å·¥èµæ»é¢ï¼Â¥ {{ formatMoney(totalSalary) }} </div> </div> </el-card> <!-- å®¡æ ¸æä½ --> <el-form label-position="top"> <el-form-item label="å®¡æ ¸ç»æ" required> <el-radio-group v-model="auditResult"> <el-radio :value="4">éè¿</el-radio> <el-radio :value="2">ä¸éè¿</el-radio> </el-radio-group> </el-form-item> </el-form> <template #footer> <el-button type="primary" :loading="loading" @click="handleConfirm"> ç¡®å® </el-button> <el-button @click="handleClose">åæ¶</el-button> </template> </FormDialog> </template> <script setup> import { ref, computed, reactive, toRefs, getCurrentInstance, watch } from "vue"; import { ElMessage } from "element-plus"; import Cookies from "js-cookie"; import FormDialog from "@/components/Dialog/FormDialog.vue"; import { staffSalaryMainUpdate } from "@/api/personnelManagement/staffSalaryMain.js"; const emit = defineEmits(["update:modelValue", "close", "success"]); const props = defineProps({ modelValue: { type: Boolean, default: false }, row: { type: Object, default: () => ({}) }, }); const { proxy } = getCurrentInstance(); const dialogVisible = computed({ get: () => props.modelValue, set: (val) => emit("update:modelValue", val), }); const loading = ref(false); const auditResult = ref(4); // é»è®¤éè¿ const auditData = ref({}); const employeeList = ref([]); // çå¬rowæ°æ®åå watch(() => props.row, (newRow) => { if (newRow && Object.keys(newRow).length > 0) { loadAuditData(newRow); } }, { immediate: true }); // æ ¼å¼åéé¢ const formatMoney = (value) => { const num = Number(value) || 0; return num.toFixed(2); }; // 计ç®å·¥èµæ»é¢ const totalSalary = computed(() => { return employeeList.value.reduce((sum, e) => { const salary = Number(e.netSalary) || 0; return sum + salary; }, 0); }); // å è½½å®¡æ ¸æ°æ® const loadAuditData = (row) => { auditData.value = row || {}; auditResult.value = 4; // é»è®¤éæ©éè¿ // å è½½åå·¥å·¥èµæç»æ°æ® if (row?.staffSalaryDetailList && Array.isArray(row.staffSalaryDetailList)) { employeeList.value = row.staffSalaryDetailList.map((e) => ({ staffName: e.staffName ?? "", deptName: e.deptName ?? "", basicSalary: Number(e.basicSalary) || 0, pieceSalary: Number(e.pieceSalary) || 0, hourlySalary: Number(e.hourlySalary) || 0, otherIncome: Number(e.otherIncome) || 0, socialPersonal: Number(e.socialPersonal) || 0, fundPersonal: Number(e.fundPersonal) || 0, salaryTax: Number(e.salaryTax) || 0, netSalary: Number(e.netSalary) || 0, })); } else { console.log('æ²¡ææ¾å°åå·¥æç»æ°æ®'); employeeList.value = []; } }; // æå¼å¼¹çª const openDialog = (row) => { loadAuditData(row); dialogVisible.value = true; }; // å ³éå¼¹çª const handleClose = () => { dialogVisible.value = false; emit("close"); }; // ç¡®è®¤å®¡æ ¸ const handleConfirm = () => { try { const row = auditData.value; if (!row?.id) { ElMessage.warning("æ°æ®å¼å¸¸ï¼è¯·éè¯"); return; } const username = Cookies.get("username") || ""; const userIdRaw = Cookies.get("userId"); const auditUserId = userIdRaw ? Number(userIdRaw) : undefined; // æå»ºå®¡æ ¸æ°æ® const submitData = { id: row.id, status: Number(auditResult.value) === 2 ? 2 : 4, // 2=ä¸éè¿ 4=éè¿(å¾ åæ¾) auditUserId, auditUserName: username, }; loading.value = true; staffSalaryMainUpdate(submitData) .then(() => { ElMessage.success("å®¡æ ¸æå"); dialogVisible.value = false; emit("success"); }) .catch((error) => { console.error('å®¡æ ¸å¤±è´¥:', error) }) .finally(() => { loading.value = false; }); } catch (error) { console.error('å®¡æ ¸å¤çå¼å¸¸:', error); loading.value = false; } }; defineExpose({ openDialog }); </script> <style scoped> :deep(.el-descriptions__label) { width: 100px; } </style> src/views/personnelManagement/monthlyStatistics/components/formDia.vue
@@ -4,9 +4,12 @@ :title="operationType === 'add' ? 'æ°å»ºå·¥èµè¡¨' : 'ç¼è¾å·¥èµè¡¨'" width="90%" @close="closeDia" @confirm="submitForm" @cancel="closeDia" > <template #footer> <el-button type="info" @click="saveDraft">ä¿åè稿</el-button> <el-button type="primary" @click="submitForm">确认æäº¤</el-button> <el-button @click="closeDia">åæ¶</el-button> </template> <div class="form-dia-body"> <!-- åºç¡èµæ --> <el-card class="form-card" shadow="never"> @@ -34,7 +37,6 @@ placeholder="è¯·éæ©" clearable multiple collapse-tags collapse-tags-tooltip style="width: 100%" > @@ -89,6 +91,24 @@ </el-select> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="å®¡æ ¸äºº" prop="auditUserId"> <el-select v-model="form.auditUserId" placeholder="è¯·éæ©å®¡æ ¸äºº" clearable filterable style="width: 100%" > <el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId" /> </el-select> </el-form-item> </el-col> </el-row> </el-form> </el-card> @@ -96,10 +116,7 @@ <!-- æä½æé® --> <div class="toolbar"> <el-button type="primary" @click="handleGenerate">çæå·¥èµè¡¨</el-button> <el-button @click="handleExport">导åº</el-button> <el-button @click="handleImport">å¯¼å ¥</el-button> <el-button @click="handleClear">æ¸ ç©º</el-button> <el-button @click="openAddPerson">æ°å¢äººå</el-button> <el-button @click="handleBatchDelete">å é¤</el-button> <el-button @click="handleTaxForm">个ç¨è¡¨</el-button> </div> @@ -182,6 +199,70 @@ /> </template> </el-table-column> <el-table-column label="å ¶ä»æ¯åº" minWidth="110"> <template #default="{ row }"> <el-input v-model.number="row.otherDeduct" type="number" placeholder="0" size="small" @input="row.otherDeduct = parseNum(row.otherDeduct)" /> </template> </el-table-column> <el-table-column label="å·¥èµä¸ªç¨" minWidth="110"> <template #default="{ row }"> <el-input v-model.number="row.salaryTax" type="number" placeholder="0" size="small" @input="row.salaryTax = parseNum(row.salaryTax)" /> </template> </el-table-column> <el-table-column label="åºåå·¥èµ" minWidth="110"> <template #default="{ row }"> <el-input v-model.number="row.grossSalary" type="number" placeholder="0" size="small" @input="row.grossSalary = parseNum(row.grossSalary)" /> </template> </el-table-column> <el-table-column label="åºæ£å·¥èµ" minWidth="110"> <template #default="{ row }"> <el-input v-model.number="row.deductSalary" type="number" placeholder="0" size="small" @input="row.deductSalary = parseNum(row.deductSalary)" /> </template> </el-table-column> <el-table-column label="å®åå·¥èµ" minWidth="110"> <template #default="{ row }"> <el-input v-model.number="row.netSalary" type="number" placeholder="0" size="small" @input="row.netSalary = parseNum(row.netSalary)" /> </template> </el-table-column> <el-table-column label="夿³¨" minWidth="120"> <template #default="{ row }"> <el-input v-model="row.remark" placeholder="请è¾å ¥" size="small" /> </template> </el-table-column> <el-table-column label="æä½" width="80" align="center" fixed="right"> <template #default="{ row }"> <el-button type="primary" link @click="removeEmployee(row)">å é¤</el-button> @@ -189,6 +270,10 @@ </el-table-column> </el-table> <div v-if="!employeeList.length" class="table-empty">ææ æ°æ®</div> <div v-else class="salary-total"> <span class="total-label">å·¥èµæ»é¢ï¼</span> <span class="total-value">Â¥ {{ totalSalary.toFixed(2) }}</span> </div> </div> </div> @@ -262,6 +347,8 @@ staffSalaryMainUpdate, staffSalaryMainCalculateSalary, } from "@/api/personnelManagement/staffSalaryMain.js"; import { userListNoPageByTenantId } from "@/api/system/user.js"; const emit = defineEmits(["update:modelValue", "close"]); const props = defineProps({ @@ -287,6 +374,7 @@ const employeeList = ref([]); const selectedEmployees = ref([]); const bankOptions = ref([]); const userList = ref([]); const taxTableData = ref([ { level: 1, range: "ä¸è¶ è¿36000å ", rate: 3, quickDeduction: 0 }, { level: 2, range: "è¶ è¿36000-144000å ", rate: 10, quickDeduction: 2520 }, @@ -312,14 +400,28 @@ salaryMonth: "", remark: "", payBank: "", auditUserId: undefined, }, rules: { salaryTitle: [{ required: true, message: "请è¾å ¥å·¥èµä¸»é¢", trigger: "blur" }], deptIds: [{ required: true, message: "è¯·éæ©é¨é¨", trigger: "change" }], salaryMonth: [{ required: true, message: "è¯·éæ©å·¥èµæä»½", trigger: "change" }], auditUserId: [{ required: true, message: "è¯·éæ©å®¡æ ¸äºº", trigger: "change" }], }, }); const { form, rules } = toRefs(data); // 计ç®å·¥èµæ»é¢ï¼ææåå·¥å®åå·¥èµä¹åï¼ const totalSalary = computed(() => { return employeeList.value.reduce((sum, e) => sum + parseNum(e.netSalary), 0); }); // æ ¹æ®å®¡æ ¸äººIDè·åå®¡æ ¸äººåç§° const auditUserName = computed(() => { if (!form.value.auditUserId) return ""; const user = userList.value.find(u => u.userId === form.value.auditUserId); return user ? user.nickName : ""; }); const loadBankOptions = () => { return bankList().then((res) => { @@ -327,6 +429,12 @@ bankOptions.value = list .map((b) => (b?.bankName == null ? "" : String(b.bankName).trim())) .filter((v) => v !== ""); }); }; const loadUserList = () => { return userListNoPageByTenantId().then((res) => { userList.value = res.data || []; }); }; @@ -389,6 +497,7 @@ nextTick(() => { loadDeptOptions(); loadBankOptions(); loadUserList(); employeeList.value = []; Object.assign(form.value, { id: undefined, @@ -397,6 +506,7 @@ salaryMonth: "", remark: "", payBank: "", auditUserId: undefined, }); // ç¼è¾ï¼å表页已è¿åä¸»è¡¨åæ®µï¼è¿éåªååæ¾ï¼æç»ç±âçæå·¥èµè¡¨/计ç®å·¥èµâå¾å°ï¼ if (type === "edit" && row?.id) { @@ -409,6 +519,30 @@ form.value.salaryMonth = row.salaryMonth ?? ""; form.value.remark = row.remark ?? ""; form.value.payBank = row.payBank ?? ""; form.value.auditUserId = row.auditUserId ?? undefined; // 妿æåå·¥æç»æ°æ®ï¼ç´æ¥åæ¾ if (row.staffSalaryDetailList && row.staffSalaryDetailList.length > 0) { employeeList.value = row.staffSalaryDetailList.map((e) => ({ staffOnJobId: e.staffOnJobId ?? e.staffId ?? e.id, id: e.staffOnJobId ?? e.staffId ?? e.id, staffName: e.staffName ?? "", postName: e.postName ?? "", deptName: e.deptName ?? "", basicSalary: parseNum(e.basicSalary), pieceSalary: parseNum(e.pieceSalary), hourlySalary: parseNum(e.hourlySalary), otherIncome: parseNum(e.otherIncome), socialPersonal: parseNum(e.socialPersonal), fundPersonal: parseNum(e.fundPersonal), otherDeduct: parseNum(e.otherDeduct), salaryTax: parseNum(e.salaryTax), grossSalary: parseNum(e.grossSalary), deductSalary: parseNum(e.deductSalary), netSalary: parseNum(e.netSalary), remark: e.remark ?? "", })); } } }); }; @@ -522,14 +656,6 @@ }); }; const handleExport = () => { proxy.$modal.msgInfo("导åºåè½é对æ¥å端"); }; const handleImport = () => { proxy.$modal.msgInfo("å¯¼å ¥åè½é对æ¥å端"); }; const handleClear = () => { proxy.$modal.confirm("ç¡®å®æ¸ 空å½ååå·¥å表åï¼").then(() => { employeeList.value = []; @@ -543,44 +669,58 @@ const submitForm = () => { formRef.value?.validate((valid) => { if (!valid) return; const payload = { id: form.value.id, salaryTitle: form.value.salaryTitle, deptIds: form.value.deptIds?.length ? form.value.deptIds.join(",") : "", salaryMonth: form.value.salaryMonth, remark: form.value.remark, payBank: form.value.payBank, staffSalaryDetailList: employeeList.value.map((e) => ({ staffOnJobId: e.staffOnJobId ?? e.staffId ?? e.id, staffName: e.staffName, postName: e.postName ?? "", deptName: e.deptName ?? "", basicSalary: parseNum(e.basicSalary), pieceSalary: parseNum(e.pieceSalary), hourlySalary: parseNum(e.hourlySalary), otherIncome: parseNum(e.otherIncome), socialPersonal: parseNum(e.socialPersonal), fundPersonal: parseNum(e.fundPersonal), otherDeduct: parseNum(e.otherDeduct), salaryTax: parseNum(e.salaryTax), grossSalary: parseNum(e.grossSalary), deductSalary: parseNum(e.deductSalary), netSalary: parseNum(e.netSalary), remark: e.remark ?? "", })), }; if (props.operationType === "add") { staffSalaryMainAdd({ ...payload, status: 1 }).then(() => { proxy.$modal.msgSuccess("æ°å¢æå"); closeDia(); }); } else { staffSalaryMainUpdate(payload).then(() => { proxy.$modal.msgSuccess("ä¿®æ¹æå"); closeDia(); }); } saveData(3); // 确认æäº¤ï¼ç¶æä¸º3ï¼å¾ å®¡æ ¸ï¼ }); }; const saveDraft = () => { formRef.value?.validate((valid) => { if (!valid) return; saveData(1); // ä¿åè稿ï¼ç¶æä¸º1ï¼èç¨¿ï¼ }); }; const saveData = (status) => { const payload = { id: form.value.id, salaryTitle: form.value.salaryTitle, deptIds: form.value.deptIds?.length ? form.value.deptIds.join(",") : "", salaryMonth: form.value.salaryMonth, remark: form.value.remark, payBank: form.value.payBank, auditUserId: form.value.auditUserId, auditUserName: auditUserName.value, totalSalary: totalSalary.value, staffSalaryDetailList: employeeList.value.map((e) => ({ staffOnJobId: e.staffOnJobId ?? e.staffId ?? e.id, staffName: e.staffName, postName: e.postName ?? "", deptName: e.deptName ?? "", basicSalary: parseNum(e.basicSalary), pieceSalary: parseNum(e.pieceSalary), hourlySalary: parseNum(e.hourlySalary), otherIncome: parseNum(e.otherIncome), socialPersonal: parseNum(e.socialPersonal), fundPersonal: parseNum(e.fundPersonal), otherDeduct: parseNum(e.otherDeduct), salaryTax: parseNum(e.salaryTax), grossSalary: parseNum(e.grossSalary), deductSalary: parseNum(e.deductSalary), netSalary: parseNum(e.netSalary), remark: e.remark ?? "", })), }; if (props.operationType === "add") { staffSalaryMainAdd({ ...payload, status }).then(() => { proxy.$modal.msgSuccess(status === 1 ? "è稿ä¿åæå" : "æäº¤æå"); closeDia(); }); } else { staffSalaryMainUpdate({ ...payload, status }).then(() => { proxy.$modal.msgSuccess(status === 1 ? "è稿ä¿åæå" : "æäº¤æå"); closeDia(); }); } }; const closeDia = () => { @@ -644,4 +784,21 @@ .dialog-footer { text-align: right; } .salary-total { margin-top: 16px; padding: 12px 16px; background-color: #f5f7fa; border-radius: 4px; text-align: right; font-size: 16px; } .salary-total .total-label { color: #606266; margin-right: 8px; } .salary-total .total-value { color: #f56c6c; font-weight: bold; font-size: 18px; } </style> src/views/personnelManagement/monthlyStatistics/index.vue
@@ -4,7 +4,7 @@ <div> <span class="search_title">主é¢ï¼</span> <el-input v-model="searchForm.title" v-model="searchForm.salaryTitle" style="width: 240px" placeholder="请è¾å ¥ä¸»é¢" clearable @@ -81,28 +81,18 @@ </el-form-item> </el-form> <template #footer> <el-button @click="issueDialogVisible = false">åæ¶</el-button> <el-button type="primary" :loading="issueLoading" @click="confirmIssue"> ç¡®å® </el-button> <el-button @click="issueDialogVisible = false">åæ¶</el-button> </template> </el-dialog> <el-dialog v-model="auditDialogVisible" title="å·¥èµå®¡æ ¸" width="720px"> <el-form label-position="top"> <el-form-item label="å®¡æ ¸ç»æ" required> <el-radio-group v-model="auditForm.result"> <el-radio :value="4">éè¿</el-radio> <el-radio :value="2">ä¸éè¿</el-radio> </el-radio-group> </el-form-item> </el-form> <template #footer> <el-button @click="auditDialogVisible = false">åæ¶</el-button> <el-button type="primary" :loading="auditLoading" @click="confirmAudit"> ç¡®å® </el-button> </template> </el-dialog> <audit-dia v-model="auditDialogVisible" :row="auditRow" @close="auditDialogVisible = false" @success="handleAuditSuccess" /> </div> </template> @@ -120,6 +110,7 @@ import Cookies from "js-cookie"; import FormDia from "./components/formDia.vue"; import BankSettingDia from "./components/bankSettingDia.vue"; import AuditDia from "./components/auditDia.vue"; import PIMTable from "@/components/PIMTable/PIMTable.vue"; import { bankList } from "@/api/personnelManagement/bank.js"; import { @@ -130,7 +121,7 @@ const data = reactive({ searchForm: { title: "", salaryTitle: "", status: "", salaryMonth: "", }, @@ -140,7 +131,22 @@ const tableColumn = ref([ { label: "å·¥èµä¸»é¢", prop: "salaryTitle", minWidth: 140 }, { label: "å·¥èµæä»½", prop: "salaryMonth", width: 120 }, { label: "ç¶æ", prop: "statusName", width: 110 }, { label: "ç¶æ", prop: "statusName", width: 110, dataType: "tag", formatType: (status) => { const statusMap = { "è稿": "info", "å®¡æ ¸æªéè¿": "danger", "å¾ å®¡æ ¸": "warning", "å¾ åæ¾": "primary", "已忾": "success" }; return statusMap[status] || "info"; } }, { label: "å·¥èµæ»é¢", prop: "totalSalary", width: 120 }, { label: "æ¯ä»é¶è¡", prop: "payBank", width: 120 }, { label: "å®¡æ ¸äºº", prop: "auditUserName", width: 110 }, @@ -155,19 +161,19 @@ { name: "ç¼è¾", type: "text", showHide: (row) => Number(row?.status) === 1 || Number(row?.status) === 2, disabled: (row) => Number(row?.status) !== 1 && Number(row?.status) !== 2, clickFun: (row) => openForm("edit", row), }, { name: "å®¡æ ¸", type: "text", showHide: (row) => Number(row?.status) === 3, disabled: (row) => Number(row?.status) !== 3, clickFun: (row) => openAudit(row), }, { name: "åæ¾", type: "text", showHide: (row) => Number(row?.status) === 4, disabled: (row) => Number(row?.status) !== 4, clickFun: (row) => openIssue(row), }, ], @@ -195,9 +201,8 @@ const issueRow = ref(null); const issueForm = reactive({ bank: "" }); const auditDialogVisible = ref(false); const auditLoading = ref(false); const auditRow = ref(null); const auditForm = reactive({ result: 4 }); // 4=éè¿(å¾ åæ¾) 2=ä¸éè¿ const auditDiaRef = ref(null); const issueBankOptions = computed(() => { const options = Array.isArray(bankSetting.value?.options) ? bankSetting.value.options : []; @@ -235,7 +240,7 @@ }; const handleReset = () => { searchForm.value.title = ""; searchForm.value.salaryTitle = ""; searchForm.value.status = ""; searchForm.value.salaryMonth = ""; page.current = 1; @@ -258,6 +263,7 @@ .then((res) => { tableLoading.value = false; const records = res.data?.records ?? res.data?.list ?? []; console.log('å表æ¥å£è¿åæ°æ®:', records); // å ¼å®¹åç«¯åæ®µï¼è¥æ¥å£ä»è¿åå°è´¦ç»æï¼å¯å¨æ¤åæ å° tableData.value = records.map((item) => ({ ...item, @@ -293,9 +299,16 @@ }; const openAudit = (row) => { console.log('æå¼å®¡æ ¸ï¼ä¼ å ¥çæ°æ®:', row); auditRow.value = row || null; auditForm.result = 4; auditDialogVisible.value = true; nextTick(() => { auditDiaRef.value?.openDialog(row); }); }; const handleAuditSuccess = () => { getList(); }; const openIssue = (row) => { @@ -309,31 +322,7 @@ issueDialogVisible.value = true; }; const confirmAudit = () => { const row = auditRow.value; if (!row?.id) { auditDialogVisible.value = false; return; } const username = Cookies.get("username") || ""; const userIdRaw = Cookies.get("userId"); const auditUserId = userIdRaw ? Number(userIdRaw) : undefined; auditLoading.value = true; staffSalaryMainUpdate({ id: row.id, status: Number(auditForm.result) === 2 ? 2 : 4, auditUserId, auditUserName: username, }) .then(() => { proxy?.$modal?.msgSuccess?.("å®¡æ ¸æå"); auditDialogVisible.value = false; getList(); }) .finally(() => { auditLoading.value = false; }); }; const confirmIssue = () => { const bank = issueForm.bank ? String(issueForm.bank).trim() : ""; src/views/personnelManagement/socialSecuritySet/components/formDia.vue
@@ -200,6 +200,7 @@ { label: "失ä¸ä¿é©", value: "失ä¸ä¿é©" }, { label: "工伤ä¿é©", value: "工伤ä¿é©" }, { label: "çè²ä¿é©", value: "çè²ä¿é©" }, { label: "å ¬ç§¯é", value: "å ¬ç§¯é" }, ]; const defaultBenefit = () => ({ src/views/procurementManagement/purchaseReturnOrder/New.vue
@@ -3,10 +3,14 @@ <el-dialog v-model="isShow" title="æ°å¢éè´éè´§" width="1200" width="1600" @close="closeModal" > <el-form label-width="140px" :model="formState" label-position="top" ref="formRef" :inline="true"> <div class="section-title"> <span class="title-dot"></span> <span class="title-text">åºæ¬ä¿¡æ¯</span> </div> <el-form-item label="éæåå·" prop="no" @@ -231,78 +235,138 @@ > <el-input v-model="formState.remark" type="textarea" placeholder="请è¾å ¥å¤æ³¨"/> </el-form-item> </el-form> <el-button type="primary" size="small" style="margin-bottom: 10px;" @click="isShowProductsModal = true" :disabled="!formState.purchaseLedgerId">æ·»å 产å</el-button> <el-table :data="products" border> <el-table-column align="center" type="selection" width="55" /> <el-table-column align="center" label="åºå·" type="index" width="60" /> <el-table-column label="产å大类" prop="productCategory" /> <el-table-column label="è§æ ¼åå·" prop="specificationModel" /> <el-table-column label="åä½" prop="unit" width="70" /> <el-table-column label="æ°é" prop="quantity" width="70" /> <el-table-column label="åºåé¢è¦æ°é" prop="warnNum" width="120" show-overflow-tooltip /> <el-table-column label="ç¨ç(%)" prop="taxRate" width="80" /> <el-table-column label="å«ç¨åä»·(å )" prop="taxInclusiveUnitPrice" :formatter="formattedNumber" width="150" /> <el-table-column label="å«ç¨æ»ä»·(å )" prop="taxInclusiveTotalPrice" :formatter="formattedNumber" width="150" /> <el-table-column label="ä¸å«ç¨æ»ä»·(å )" prop="taxExclusiveTotalPrice" :formatter="formattedNumber" width="150" /> <el-table-column label="æ¯å¦è´¨æ£" prop="isChecked" width="150"> <template #default="scope"> <el-tag :type="scope.row.isChecked ? 'success' : 'info'"> {{ scope.row.isChecked ? 'æ¯' : 'å¦' }} </el-tag> </template> </el-table-column> <el-table-column fixed="right" label="æä½" width="120" align="center"> <template #default="scope"> <el-button link type="primary" size="small" > ç¼è¾ </el-button> <el-button link type="danger" size="small" @click="delProduct(scope.$index)" > å é¤ </el-button> </template> </el-table-column> </el-table> <div style="margin: 20px 0;"> <div class="section-title"> <span class="title-dot"></span> <span class="title-text">产åå表</span> </div> <el-button type="primary" size="small" style="margin-bottom: 20px" @click="isShowProductsModal = true" :disabled="!formState.purchaseLedgerId">æ·»å 产å</el-button> <el-table :data="formState.purchaseReturnOrderProductsDtos" border max-height="400" :scroll-y="true" show-summary :summary-method="summarizeChildrenTable"> <el-table-column align="center" type="selection" width="55" /> <el-table-column align="center" label="åºå·" type="index" width="60" /> <el-table-column label="产å大类" prop="productCategory" /> <el-table-column label="è§æ ¼åå·" prop="specificationModel" /> <el-table-column label="åä½" prop="unit" width="70" /> <el-table-column label="æ°é" prop="quantity" width="70" /> <el-table-column label="éè´§æ°é" prop="returnQuantity" width="180"> <template #default="scope"> <el-input-number v-model="scope.row.returnQuantity" controls-position="right" :step="1" :min="1" :max="scope.row.quantity" required placeholder="请è¾å ¥éè´§æ°é" /> </template> </el-table-column> <el-table-column label="åºåé¢è¦æ°é" prop="warnNum" width="120" show-overflow-tooltip /> <el-table-column label="ç¨ç(%)" prop="taxRate" width="80" /> <el-table-column label="å«ç¨åä»·(å )" prop="taxInclusiveUnitPrice" :formatter="formattedNumber" width="150" /> <el-table-column label="å«ç¨æ»ä»·(å )" prop="taxInclusiveTotalPrice" :formatter="formattedNumber" width="150" /> <el-table-column label="ä¸å«ç¨æ»ä»·(å )" prop="taxExclusiveTotalPrice" :formatter="formattedNumber" width="150" /> <el-table-column label="æ¯å¦è´¨æ£" prop="isChecked" width="150"> <template #default="scope"> <el-tag :type="scope.row.isChecked ? 'success' : 'info'"> {{ scope.row.isChecked ? 'æ¯' : 'å¦' }} </el-tag> </template> </el-table-column> <el-table-column fixed="right" label="æä½" width="100" align="center"> <template #default="scope"> <el-button link type="danger" size="small" @click="delProduct(scope.$index)" > å é¤ </el-button> </template> </el-table-column> </el-table> </div> <div class="section-title"> <span class="title-dot"></span> <span class="title-text">è´¹ç¨ä¿¡æ¯</span> </div> <el-form-item label="æ´åææ£é¢ï¼" prop="totalDiscountAmount" > <el-input-number v-model="formState.totalDiscountAmount" controls-position="right" :step="0.01" :precision="2" style="width: 100%;" @change="handleChangeTotalDiscountAmount" placeholder="请è¾å ¥æ´åææ£é¢"/> </el-form-item> <el-form-item label="æ´åææ£çï¼" prop="totalDiscountAmount" > <el-input-number v-model="formState.totalDiscountRate" controls-position="right" :step="0.01" :precision="2" style="width: 100%;" placeholder="请è¾å ¥æ´åææ£ç"/> </el-form-item> <el-form-item label="æäº¤éé¢ï¼" prop="totalAmount" > <el-input-number v-model="formState.totalAmount" controls-position="right" :step="0.01" :precision="2" style="width: 100%;" @change="handleChangeTotalAmount" placeholder="请è¾å ¥æäº¤éé¢"/> </el-form-item> </el-form> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="handleSubmit">确认</el-button> @@ -349,6 +413,10 @@ preparedUserId: undefined, returnUserId: undefined, purchaseLedgerId: undefined, purchaseReturnOrderProductsDtos: [], totalDiscountAmount: 0, totalDiscountRate: undefined, totalAmount: 0, }); // ä¾åºåé项 const supplierOptions = ref([]) @@ -381,8 +449,6 @@ const userOptions = ref([]) // éè´å°è´¦é项 const purchaseLedgerOptions = ref([]) // 产ååè¡¨æ°æ® const products = ref([]) // æ¯å¦å±ç¤ºäº§ååè¡¨æ°æ® const isShowProductsModal = ref(false) @@ -402,6 +468,31 @@ const closeModal = () => { isShow.value = false; }; const summarizeChildrenTable = (param) => { return proxy.summarizeTable( param, [ "quantity", "returnQuantity", "taxInclusiveUnitPrice", "taxInclusiveTotalPrice", "taxExclusiveTotalPrice", ], { quantity: { noDecimal: true }, // ä¸ä¿çå°æ° returnQuantity: { noDecimal: true }, // ä¸ä¿çå°æ° } ); }; const handleChangeTotalDiscountAmount= () => { formState.value.totalAmount = formState.value.totalDiscountAmount * -1 } const handleChangeTotalAmount= () => { formState.value.totalDiscountAmount = formState.value.totalAmount * -1 } // è·åä¾åºåé项 const fetchSupplierOptions = () => { @@ -449,7 +540,7 @@ // å¤çæ¹åéè´å°è´¦æ°æ® const handleChangePurchaseLedgerId = () => { products.value = [] formState.value.purchaseReturnOrderProductsDtos = [] } // å¤çæ¹åæ¯å¦é»è®¤ç¼å· @@ -461,16 +552,29 @@ // å¢å 产å const handleAddProduct = (selectedRows) => { products.value.push(...selectedRows) const existingIds = new Set(formState.value.purchaseReturnOrderProductsDtos.map(item => item.id)); const newProducts = selectedRows.filter(item => !existingIds.has(item.id)).map(item => ({ ...item, returnQuantity: undefined, salesLedgerProductId: item.id, })); formState.value.purchaseReturnOrderProductsDtos.push(...newProducts); } // å é¤å项产å const delProduct = (index) => { products.value.splice(index, 1) formState.value.purchaseReturnOrderProductsDtos.splice(index, 1) } // æäº¤è¡¨å const handleSubmit = () => { // éªè¯éè´§æ°é const hasEmptyReturnQuantity = formState.value.purchaseReturnOrderProductsDtos.some(item => !item.returnQuantity || item.returnQuantity <= 0); if (hasEmptyReturnQuantity) { proxy.$modal.msgError("请为ææäº§åå¡«åéè´§æ°é"); return; } proxy.$refs["formRef"].validate(valid => { if (valid) { createPurchaseReturnOrder(formState.value).then(res => { @@ -490,3 +594,25 @@ isShow, }); </script> <style scoped lang="scss"> .section-title { display: flex; align-items: center; margin-bottom: 20px; font-size: 16px; font-weight: 600; color: #303133; width: 100%; clear: both; } .title-dot { display: inline-block; width: 8px; height: 8px; background-color: #409EFF; border-radius: 50%; margin-right: 8px; } </style> src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -202,8 +202,8 @@ </el-form> <template #footer> <el-button @click="closeDialog">åæ¶</el-button> <el-button type="primary" @click="handleSubmit" :loading="submitLoading">ç¡®å®</el-button> <el-button @click="closeDialog">åæ¶</el-button> </template> </el-dialog> src/views/productionManagement/productStructure/index.vue
@@ -34,8 +34,8 @@ </el-form-item> </el-form> <template #footer> <el-button @click="closeDialog">åæ¶</el-button> <el-button type="primary" @click="handleSubmit">ç¡®å®</el-button> <el-button @click="closeDialog">åæ¶</el-button> </template> </el-dialog> src/views/productionManagement/productionOrder/index.vue
@@ -83,10 +83,10 @@ </el-form> <template #footer> <span class="dialog-footer"> <el-button @click="bindRouteDialogVisible = false">å æ¶</el-button> <el-button type="primary" :loading="bindRouteSaving" @click="handleBindRouteConfirm">ç¡® 认</el-button> <el-button @click="bindRouteDialogVisible = false">å æ¶</el-button> </span> </template> </el-dialog> src/views/projectManagement/Management/components/formDia.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,1503 @@ <template> <el-dialog v-model="dialogVisible" :title="dialogTitle" width="95%" top="5vh" destroy-on-close @close="closeDialog" > <el-form ref="formRef" :model="form" :rules="rules" label-position="top" label-width="120px" :disabled="isView" > <div class="section"> <div class="section-header" @click="toggleSection('base')"> <div class="section-title"> <span class="section-bar" /> <span>åºç¡èµæ</span> </div> <el-icon class="toggle-icon"> <ArrowDown v-if="sectionCollapsed.base" /> <ArrowUp v-else /> </el-icon> </div> <div v-show="!sectionCollapsed.base" class="section-body"> <el-row :gutter="20"> <el-col :span="6"> <el-form-item label="åæ®ç¼å·" prop="billNo"> <el-input v-model="form.billNo" placeholder="ç³»ç»çæ" disabled /> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="项ç®åç§°" prop="projectName"> <el-input v-model="form.projectName" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="客æ·åç§°" prop="customerName"> <el-input v-model="form.customerName" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="6"> <el-form-item label="ç«é¡¹æ¥æ" prop="setupDate"> <el-date-picker v-model="form.setupDate" type="date" value-format="YYYY-MM-DD" format="YYYY-MM-DD" placeholder="è¯·éæ©" style="width: 100%" clearable /> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="é¡¹ç®æ¥æº" prop="projectSource"> <el-input v-model="form.projectSource" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="ç«é¡¹äºº" prop="creatorName"> <el-input v-model="form.creatorName" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="6"> <el-form-item label="é¢è®¡å·¥æ(天)" prop="estimatedDays"> <el-input-number v-model="form.estimatedDays" :min="0" controls-position="right" style="width: 100%" /> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="计åå¼å§æ¥æ" prop="planStartDate"> <el-date-picker v-model="form.planStartDate" type="date" value-format="YYYY-MM-DD" format="YYYY-MM-DD" placeholder="è¯·éæ©" style="width: 100%" clearable /> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="计åå®ææ¥æ" prop="planEndDate"> <el-date-picker v-model="form.planEndDate" type="date" value-format="YYYY-MM-DD" format="YYYY-MM-DD" placeholder="è¯·éæ©" style="width: 100%" clearable /> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="6"> <el-form-item label="项ç®ç±»å" prop="projectManagementPlanId"> <el-select v-model="form.projectManagementPlanId" placeholder="è¯·éæ©" clearable style="width: 100%"> <el-option v-for="opt in projectTypeOptions" :key="opt.value" :label="opt.label" :value="opt.value" /> </el-select> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="项ç®éé¢" prop="projectAmount"> <el-input-number v-model="form.projectAmount" :min="0" controls-position="right" style="width: 100%" /> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="å®¡æ ¸ç¶æ" prop="auditStatus"> <el-select v-model="form.auditStatus" placeholder="è¯·éæ©" clearable style="width: 100%"> <el-option v-for="d in project_management" :key="d.value" :label="d.label" :value="d.value" /> </el-select> </el-form-item> </el-col> </el-row> <el-row :gutter="10" > <el-col :span="24"> <el-upload v-model:file-list="fileList" :action="upload.url" :headers="upload.headers" multiple :disabled="isView" :before-upload="beforeUpload" :on-success="handleUploadSuccess" :on-error="handleUploadError" name="files" :on-remove="handleRemove" > <el-button type="primary" :disabled="isView">ä¸ä¼ æä»¶</el-button> </el-upload> <div v-if="existingAttachments.length > 0" class="attachment-list"> <div v-for="(att, idx) in existingAttachments" :key="att.id || att.url || idx" class="attachment-item" > <el-icon><Document /></el-icon> <span class="attachment-name">{{ att.name || att.fileName || att.url || 'éä»¶' }}</span> <el-button link type="primary" size="small" @click="downloadAttachment(att)">ä¸è½½</el-button> </div> </div> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="24"> <el-form-item label="夿³¨" prop="remark"> <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请è¾å ¥" maxlength="100" show-word-limit /> </el-form-item> </el-col> </el-row> </div> </div> <div class="section"> <div class="section-header" @click="toggleSection('product')"> <div class="section-title"> <span class="section-bar" /> <span>产åä¿¡æ¯</span> </div> <div class="section-actions" @click.stop> <el-button v-if="!isView" type="primary" @click="openProductForm('add')">æ·»å </el-button> <el-button v-if="!isView" plain type="danger" @click="deleteProduct">å é¤</el-button> <el-icon class="toggle-icon" @click="toggleSection('product')"> <ArrowDown v-if="sectionCollapsed.product" /> <ArrowUp v-else /> </el-icon> </div> </div> <div v-show="!sectionCollapsed.product" class="section-body"> <el-table :data="productData" border show-summary :summary-method="summarizeProductTable" @selection-change="productSelected" > <el-table-column v-if="!isView" align="center" type="selection" width="55" /> <el-table-column align="center" label="åºå·" type="index" width="60" /> <el-table-column label="产å大类" prop="productCategory" /> <el-table-column label="è§æ ¼åå·" prop="specificationModel" /> <el-table-column label="åä½" prop="unit" /> <el-table-column label="æ°é" prop="quantity" /> <el-table-column label="ç¨ç(%)" prop="taxRate" /> <el-table-column label="å«ç¨åä»·(å )" prop="taxInclusiveUnitPrice" :formatter="formattedNumber" /> <el-table-column label="å«ç¨æ»ä»·(å )" prop="taxInclusiveTotalPrice" :formatter="formattedNumber" /> <el-table-column label="ä¸å«ç¨æ»ä»·(å )" prop="taxExclusiveTotalPrice" :formatter="formattedNumber" /> <el-table-column v-if="!isView" fixed="right" label="æä½" min-width="60" align="center"> <template #default="scope"> <el-button link type="primary" size="small" @click="openProductForm('edit', scope.row, scope.$index)">ç¼è¾</el-button> </template> </el-table-column> </el-table> </div> </div> <div class="section"> <div class="section-header" @click="toggleSection('team')"> <div class="section-title"> <span class="section-bar" /> <span>项ç®å¢é</span> </div> <div class="section-actions" @click.stop> <el-button v-if="!isView" type="primary" :icon="Plus" @click="addTeamRow">æ°å¢è¡</el-button> <el-icon class="toggle-icon" @click="toggleSection('team')"> <ArrowDown v-if="sectionCollapsed.team" /> <ArrowUp v-else /> </el-icon> </div> </div> <div v-show="!sectionCollapsed.team" class="section-body"> <PIMTable :column="teamColumns" :tableData="form.teamList" :tableLoading="false" :isSelection="false" :isShowPagination="false" height="220" > <template #memberId="{ row }"> <el-select v-model="row.memberId" placeholder="è¯·éæ©" filterable clearable style="width: 100%" :disabled="isView"> <el-option v-for="u in userOptions" :key="u.value" :label="u.label" :value="u.value" /> </el-select> </template> <template #roleId="{ row }"> <el-select v-model="row.roleId" placeholder="è¯·éæ©" clearable style="width: 100%" :disabled="isView"> <el-option v-for="r in roleOptions" :key="r.value" :label="r.label" :value="r.value" /> </el-select> </template> <template #enterDate="{ row }"> <el-date-picker v-model="row.enterDate" type="date" value-format="YYYY-MM-DD" format="YYYY-MM-DD" placeholder="è¯·éæ©" style="width: 100%" clearable :disabled="isView" /> </template> <template #leaveDate="{ row }"> <el-date-picker v-model="row.leaveDate" type="date" value-format="YYYY-MM-DD" format="YYYY-MM-DD" placeholder="è¯·éæ©" style="width: 100%" clearable :disabled="isView" /> </template> <template #phone="{ row }"> <el-input v-model="row.phone" placeholder="请è¾å ¥" clearable :disabled="isView" /> </template> <template #teamRemark="{ row }"> <el-input v-model="row.remark" placeholder="请è¾å ¥" clearable :disabled="isView" /> </template> <template #teamAction="{ row, index }"> <el-button v-if="!isView" link type="danger" :icon="Delete" @click="removeTeamRow(index)">å é¤</el-button> <span v-else>â</span> </template> </PIMTable> </div> </div> <!-- <div class="section"> <div class="section-header" @click="toggleSection('phase')"> <div class="section-title"> <span class="section-bar" /> <span>项ç®é¶æ®µ</span> </div> <div class="section-actions" @click.stop> <el-button v-if="!isView" type="primary" :icon="Plus" @click="addPhaseRow">æ°å¢è¡</el-button> <el-icon class="toggle-icon" @click="toggleSection('phase')"> <ArrowDown v-if="sectionCollapsed.phase" /> <ArrowUp v-else /> </el-icon> </div> </div> <div v-show="!sectionCollapsed.phase" class="section-body"> <PIMTable :column="phaseColumns" :tableData="form.phaseList" :tableLoading="false" :isSelection="false" :isShowPagination="false" height="240" > <template #phaseName="{ row }"> <el-input v-model="row.phaseName" placeholder="请è¾å ¥" clearable :disabled="isView" /> </template> <template #phaseDesc="{ row }"> <el-input v-model="row.description" placeholder="请è¾å ¥" clearable :disabled="isView" /> </template> <template #ownerId="{ row }"> <el-select v-model="row.ownerId" placeholder="è¯·éæ©" filterable clearable style="width: 100%" :disabled="isView"> <el-option v-for="u in userOptions" :key="u.value" :label="u.label" :value="u.value" /> </el-select> </template> <template #planDays="{ row }"> <el-input-number v-model="row.planDays" :min="0" controls-position="right" style="width: 100%" :disabled="isView" /> </template> <template #planStart="{ row }"> <el-date-picker v-model="row.planStartDate" type="date" value-format="YYYY-MM-DD" format="YYYY-MM-DD" placeholder="è¯·éæ©" style="width: 100%" clearable :disabled="isView" /> </template> <template #planEnd="{ row }"> <el-date-picker v-model="row.planEndDate" type="date" value-format="YYYY-MM-DD" format="YYYY-MM-DD" placeholder="è¯·éæ©" style="width: 100%" clearable :disabled="isView" /> </template> <template #progress="{ row }"> <el-input-number v-model="row.progress" :min="0" :max="100" controls-position="right" style="width: 100%" :disabled="isView" /> </template> <template #actualStart="{ row }"> <el-date-picker v-model="row.actualStartDate" type="date" value-format="YYYY-MM-DD" format="YYYY-MM-DD" placeholder="è¯·éæ©" style="width: 100%" clearable :disabled="isView" /> </template> <template #actualEnd="{ row }"> <el-date-picker v-model="row.actualEndDate" type="date" value-format="YYYY-MM-DD" format="YYYY-MM-DD" placeholder="è¯·éæ©" style="width: 100%" clearable :disabled="isView" /> </template> <template #overdueDays="{ row }"> <el-input-number v-model="row.overdueDays" :min="0" controls-position="right" style="width: 100%" :disabled="isView" /> </template> <template #completion="{ row }"> <el-input v-model="row.completionRemark" placeholder="请è¾å ¥" clearable :disabled="isView" /> </template> <template #phaseAction="{ row, index }"> <el-button v-if="!isView" link type="danger" :icon="Delete" @click="removePhaseRow(index)">å é¤</el-button> <span v-else>â</span> </template> </PIMTable> </div> </div> --> <div class="section"> <div class="section-header" @click="toggleSection('address')"> <div class="section-title"> <span class="section-bar" /> <span>æ¶è´§å°å</span> </div> <div class="section-actions" @click.stop> <el-button v-if="!isView" type="primary" :icon="Plus" @click="addAddressRow">æ°å¢è¡</el-button> <el-icon class="toggle-icon" @click="toggleSection('address')"> <ArrowDown v-if="sectionCollapsed.address" /> <ArrowUp v-else /> </el-icon> </div> </div> <div v-show="!sectionCollapsed.address" class="section-body"> <PIMTable :column="addressColumns" :tableData="form.addressList" :tableLoading="false" :isSelection="false" :isShowPagination="false" height="200" > <template #receiver="{ row }"> <el-input v-model="row.receiver" placeholder="请è¾å ¥" clearable :disabled="isView" /> </template> <template #receiverPhone="{ row }"> <el-input v-model="row.phone" placeholder="请è¾å ¥" clearable :disabled="isView" /> </template> <template #receiverAddress="{ row }"> <el-input v-model="row.address" placeholder="请è¾å ¥" clearable :disabled="isView" /> </template> <template #addressAction="{ row, index }"> <el-button v-if="!isView" link type="danger" :icon="Delete" @click="removeAddressRow(index)">å é¤</el-button> <span v-else>â</span> </template> </PIMTable> </div> </div> <div class="section"> <div class="section-header" @click="toggleSection('contact')"> <div class="section-title"> <span class="section-bar" /> <span>è系信æ¯</span> </div> <el-icon class="toggle-icon"> <ArrowDown v-if="sectionCollapsed.contact" /> <ArrowUp v-else /> </el-icon> </div> <div v-show="!sectionCollapsed.contact" class="section-body"> <el-row :gutter="20"> <el-col :span="6"> <el-form-item label="è系人å§å" prop="contactName"> <el-input v-model="form.contactName" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="æ§å«" prop="contactGender"> <el-select v-model="form.contactGender" placeholder="è¯·éæ©" clearable style="width: 100%"> <el-option label="ç·" value="1" /> <el-option label="女" value="2" /> </el-select> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="çæ¥" prop="contactBirthday"> <el-date-picker v-model="form.contactBirthday" type="date" value-format="YYYY-MM-DD" format="YYYY-MM-DD" placeholder="è¯·éæ©" style="width: 100%" clearable /> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="é®ç®±" prop="contactEmail"> <el-input v-model="form.contactEmail" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="6"> <el-form-item label="é¨é¨" prop="contactDept"> <el-input v-model="form.contactDept" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="èå¡" prop="contactJob"> <el-input v-model="form.contactJob" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="ææºå·ç " prop="contactMobile"> <el-input v-model="form.contactMobile" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="微信å·ç " prop="contactWechat"> <el-input v-model="form.contactWechat" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="6"> <el-form-item label="QQ" prop="contactQq"> <el-input v-model="form.contactQq" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="ä¼ä¸å¾®ä¿¡" prop="contactWorkWechat"> <el-input v-model="form.contactWorkWechat" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="å°å" prop="contactAddress"> <el-input v-model="form.contactAddress" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="24"> <el-form-item label="夿³¨" prop="contactRemark"> <el-input v-model="form.contactRemark" type="textarea" :rows="2" placeholder="请è¾å ¥" maxlength="200" show-word-limit /> </el-form-item> </el-col> </el-row> </div> </div> </el-form> <template #footer> <div class="dialog-footer"> <el-button v-if="!isView" type="primary" @click="submitForm">确认</el-button> <el-button @click="closeDialog">{{ isView ? 'å ³é' : 'åæ¶' }}</el-button> </div> </template> </el-dialog> <FormDialog v-model="productFormVisible" :title="productOperationType === 'add' ? 'æ°å¢äº§å' : 'ç¼è¾äº§å'" :width="'40%'" :operation-type="productOperationType" @close="closeProductDia" @confirm="submitProduct" @cancel="closeProductDia" > <el-form ref="productFormRef" :model="productForm" label-width="140px" label-position="top" :rules="productRules"> <el-row :gutter="30"> <el-col :span="24"> <el-form-item label="产å大类ï¼" prop="productCategoryId"> <el-tree-select v-model="productForm.productCategoryId" placeholder="è¯·éæ©" clearable check-strictly :data="productCategoryOptions" :render-after-expand="false" style="width: 100%" @change="getModels" /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="24"> <el-form-item label="è§æ ¼åå·ï¼" prop="productModelId"> <el-select v-model="productForm.productModelId" placeholder="è¯·éæ©" clearable filterable @change="getProductModel"> <el-option v-for="item in modelOptions" :key="item.id" :label="item.model" :value="item.id" /> </el-select> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="åä½ï¼" prop="unit"> <el-input v-model="productForm.unit" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="ç¨ç(%)ï¼" prop="taxRate"> <el-select v-model="productForm.taxRate" placeholder="è¯·éæ©" clearable @change="calculateFromTaxRate"> <el-option label="1" value="1" /> <el-option label="6" value="6" /> <el-option label="13" value="13" /> </el-select> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="å«ç¨åä»·(å )ï¼" prop="taxInclusiveUnitPrice"> <el-input-number v-model="productForm.taxInclusiveUnitPrice" :step="0.01" :min="0" :precision="2" style="width: 100%" placeholder="请è¾å ¥" clearable @change="calculateFromUnitPrice" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="æ°éï¼" prop="quantity"> <el-input-number v-model="productForm.quantity" :step="0.1" :min="0" :precision="2" style="width: 100%" placeholder="请è¾å ¥" clearable @change="calculateFromQuantity" /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="å«ç¨æ»ä»·(å )ï¼" prop="taxInclusiveTotalPrice"> <el-input v-model="productForm.taxInclusiveTotalPrice" placeholder="请è¾å ¥" clearable @change="calculateFromTotalPrice" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="ä¸å«ç¨æ»ä»·(å )ï¼" prop="taxExclusiveTotalPrice"> <el-input v-model="productForm.taxExclusiveTotalPrice" placeholder="请è¾å ¥" clearable @change="calculateFromExclusiveTotalPrice" /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="å票类åï¼" prop="invoiceType"> <el-select v-model="productForm.invoiceType" placeholder="è¯·éæ©" clearable> <el-option label="墿®ç¥¨" value="墿®ç¥¨" /> <el-option label="å¢ä¸ç¥¨" value="å¢ä¸ç¥¨" /> </el-select> </el-form-item> </el-col> </el-row> </el-form> </FormDialog> </template> <script setup name="ProjectManagementFormDia"> import { computed, getCurrentInstance, reactive, ref, toRefs } from 'vue' import { ArrowDown, ArrowUp, Delete, Plus, Document } from '@element-plus/icons-vue' import { ElMessage } from 'element-plus' import { getToken } from '@/utils/auth' import PIMTable from '@/components/PIMTable/PIMTable.vue' import FormDialog from '@/components/Dialog/FormDialog.vue' import { listPlan } from '@/api/projectManagement/projectType' import { findRoleListPage } from '@/api/projectManagement/role' import { userListAll } from '@/api/publicApi' import { addProject, getProject, updateProject } from '@/api/projectManagement/project' import { modelList, productTreeList } from '@/api/basicData/product' import { delProduct as delSalesProduct } from '@/api/salesManagement/salesLedger' const emit = defineEmits(['completed']) const { proxy } = getCurrentInstance() const { bill_status, project_management, plan_status } = proxy.useDict('bill_status', 'project_management', 'plan_status') const dialogVisible = ref(false) const operationType = ref('add') const formRef = ref() const fileList = ref([]) const existingAttachments = ref([]) const upload = reactive({ url: import.meta.env.VITE_APP_BASE_API + '/basic/customer-follow/upload', headers: { Authorization: 'Bearer ' + getToken() } }) const projectTypeOptions = ref([]) const roleOptions = ref([]) const userOptions = ref([]) const productData = ref([]) const productSelectedRows = ref([]) const productCategoryOptions = ref([]) const modelOptions = ref([]) const productFormVisible = ref(false) const productOperationType = ref('add') const productFormRef = ref() const productIndex = ref(0) const isCalculating = ref(false) const productFormData = reactive({ productForm: { productCategoryId: undefined, productCategory: '', productModelId: undefined, specificationModel: '', unit: '', quantity: '', taxInclusiveUnitPrice: '', taxRate: '', taxInclusiveTotalPrice: '', taxExclusiveTotalPrice: '', invoiceType: '' }, productRules: { productCategoryId: [{ required: true, message: 'è¯·éæ©', trigger: 'change' }], productModelId: [{ required: true, message: 'è¯·éæ©', trigger: 'change' }], unit: [{ required: true, message: '请è¾å ¥', trigger: 'blur' }], quantity: [{ required: true, message: '请è¾å ¥', trigger: 'blur' }], taxInclusiveUnitPrice: [{ required: true, message: '请è¾å ¥', trigger: 'blur' }], taxRate: [{ required: true, message: 'è¯·éæ©', trigger: 'change' }], taxInclusiveTotalPrice: [{ required: true, message: '请è¾å ¥', trigger: 'blur' }], taxExclusiveTotalPrice: [{ required: true, message: '请è¾å ¥', trigger: 'blur' }], invoiceType: [{ required: true, message: 'è¯·éæ©', trigger: 'change' }] } }) const { productForm, productRules } = toRefs(productFormData) const data = reactive({ form: { id: undefined, clientId: undefined, parentProjectId: undefined, projectManagementPlanId: undefined, managerId: undefined, salesmanId: undefined, salesmanName: '', actualStartDate: '', actualEndDate: '', departmentId: undefined, departmentName: '', orderDate: '', billNo: '', projectName: '', customerName: '', parentProjectName: '', setupDate: '', projectSource: '', creatorName: '', billStatus: '', projectStage: '', estimatedDays: 0, planStartDate: '', planEndDate: '', projectManagementPlanId: undefined, projectAmount: 0, auditStatus: '', remark: '', attachmentIds: [], teamList: [], phaseList: [], addressList: [], contactName: '', contactGender: '', contactBirthday: '', contactEmail: '', contactDept: '', contactJob: '', contactMobile: '', contactWechat: '', contactQq: '', contactWorkWechat: '', contactAddress: '', contactRemark: '' }, rules: { projectName: [{ required: true, message: '请è¾å ¥é¡¹ç®åç§°', trigger: 'blur' }] } }) const { form, rules } = toRefs(data) const sectionCollapsed = reactive({ base: false, product: false, team: false, phase: false, address: false, contact: false }) const isView = computed(() => operationType.value === 'view') const dialogTitle = computed(() => { if (operationType.value === 'add') return 'æ°å¢é¡¹ç®' if (operationType.value === 'edit') return 'ç¼è¾é¡¹ç®' return '项ç®è¯¦æ ' }) const teamColumns = [ { label: 'å§å', prop: 'memberId', align: 'center', width: 180, dataType: 'slot', slot: 'memberId' }, { label: '项ç®ç»è§è²', prop: 'roleId', align: 'center', width: 160, dataType: 'slot', slot: 'roleId' }, { label: 'è¿å ¥æ¥æ', prop: 'enterDate', align: 'center', width: 160, dataType: 'slot', slot: 'enterDate' }, { label: 'ç¦»å¼æ¥æ', prop: 'leaveDate', align: 'center', width: 160, dataType: 'slot', slot: 'leaveDate' }, { label: 'èç³»æ¹å¼', prop: 'phone', align: 'center', width: 180, dataType: 'slot', slot: 'phone' }, { label: '夿³¨', prop: 'remark', align: 'center', dataType: 'slot', slot: 'teamRemark' }, { label: 'æä½', prop: 'action', align: 'center', width: 100, dataType: 'slot', slot: 'teamAction', fixed: 'right' } ] const phaseColumns = [ { label: 'é¶æ®µåç§°', prop: 'phaseName', align: 'center', width: 160, dataType: 'slot', slot: 'phaseName' }, { label: 'æè¿°', prop: 'description', align: 'center', width: 200, dataType: 'slot', slot: 'phaseDesc' }, { label: 'è´è´£äºº', prop: 'ownerId', align: 'center', width: 160, dataType: 'slot', slot: 'ownerId' }, { label: 'é¢è®¡å·¥æ(天)', prop: 'planDays', align: 'center', width: 140, dataType: 'slot', slot: 'planDays' }, { label: '计åå¼å§æ¥æ', prop: 'planStartDate', align: 'center', width: 160, dataType: 'slot', slot: 'planStart' }, { label: '计åç»ææ¥æ', prop: 'planEndDate', align: 'center', width: 160, dataType: 'slot', slot: 'planEnd' }, { label: 'è¿åº¦(%)', prop: 'progress', align: 'center', width: 120, dataType: 'slot', slot: 'progress' }, { label: 'å®é å¼å§æ¥æ', prop: 'actualStartDate', align: 'center', width: 160, dataType: 'slot', slot: 'actualStart' }, { label: 'å®é ç»ææ¥æ', prop: 'actualEndDate', align: 'center', width: 160, dataType: 'slot', slot: 'actualEnd' }, { label: '龿天æ°', prop: 'overdueDays', align: 'center', width: 120, dataType: 'slot', slot: 'overdueDays' }, { label: '宿æ åµ', prop: 'completionRemark', align: 'center', width: 200, dataType: 'slot', slot: 'completion' }, { label: 'æä½', prop: 'action', align: 'center', width: 100, dataType: 'slot', slot: 'phaseAction', fixed: 'right' } ] const addressColumns = [ { label: 'æ¶è´§äºº', prop: 'receiver', align: 'center', width: 180, dataType: 'slot', slot: 'receiver' }, { label: 'èç³»æ¹å¼', prop: 'phone', align: 'center', width: 180, dataType: 'slot', slot: 'receiverPhone' }, { label: 'æ¶è´§å°å', prop: 'address', align: 'center', dataType: 'slot', slot: 'receiverAddress' }, { label: 'æä½', prop: 'action', align: 'center', width: 100, dataType: 'slot', slot: 'addressAction', fixed: 'right' } ] function toggleSection(key) { sectionCollapsed[key] = !sectionCollapsed[key] } function resetFormData() { Object.assign(form.value, { id: undefined, clientId: undefined, parentProjectId: undefined, projectManagementPlanId: undefined, managerId: undefined, salesmanId: undefined, salesmanName: '', actualStartDate: '', actualEndDate: '', departmentId: undefined, departmentName: '', orderDate: '', billNo: '', projectName: '', customerName: '', parentProjectName: '', setupDate: '', projectSource: '', creatorName: '', billStatus: '', projectStage: '', estimatedDays: 0, planStartDate: '', planEndDate: '', projectManagementPlanId: undefined, projectAmount: 0, auditStatus: '', remark: '', attachmentIds: [], teamList: [], phaseList: [], addressList: [], contactName: '', contactGender: '', contactBirthday: '', contactEmail: '', contactDept: '', contactJob: '', contactMobile: '', contactWechat: '', contactQq: '', contactWorkWechat: '', contactAddress: '', contactRemark: '' }) fileList.value = [] productData.value = [] } function formattedNumber(row, column, cellValue) { const val = Number(cellValue ?? 0) return Number.isFinite(val) ? val.toFixed(2) : '0.00' } function summarizeProductTable(param) { return proxy.summarizeTable(param, ['taxInclusiveTotalPrice', 'taxExclusiveTotalPrice']) } function productSelected(selection) { productSelectedRows.value = selection } function convertIdToValue(data) { return (Array.isArray(data) ? data : []).map(item => { const { id, children, ...rest } = item const newItem = { ...rest, value: id } if (children && children.length > 0) { newItem.children = convertIdToValue(children) } return newItem }) } function findNodeById(nodes, productId) { for (let i = 0; i < (nodes || []).length; i++) { if (nodes[i].value === productId) { return nodes[i].label } if (nodes[i].children && nodes[i].children.length > 0) { const foundNode = findNodeById(nodes[i].children, productId) if (foundNode) return foundNode } } return null } function findNodeIdByLabel(nodes, label) { if (!label) return null for (let i = 0; i < (nodes || []).length; i++) { const node = nodes[i] if (node.label === label) return node.value if (node.children && node.children.length > 0) { const found = findNodeIdByLabel(node.children, label) if (found !== null && found !== undefined) return found } } return null } function getProductOptions() { return productTreeList().then(res => { const list = res?.data || res productCategoryOptions.value = convertIdToValue(list) return productCategoryOptions.value }) } function getModels(value) { const categoryLabel = findNodeById(productCategoryOptions.value, value) productForm.value.productCategory = categoryLabel || '' modelList({ id: value }).then(res => { modelOptions.value = res?.data || res || [] }) } function getProductModel(value) { const index = (modelOptions.value || []).findIndex(item => item.id === value) if (index !== -1) { productForm.value.specificationModel = modelOptions.value[index].model productForm.value.unit = modelOptions.value[index].unit } else { productForm.value.specificationModel = '' productForm.value.unit = '' } } async function openProductForm(type, row, index) { productOperationType.value = type productIndex.value = index || 0 productForm.value = {} proxy.resetForm('productFormRef') if (!productCategoryOptions.value || productCategoryOptions.value.length === 0) { await getProductOptions() } if (type === 'edit' && row) { productForm.value = { ...row } try { const categoryId = findNodeIdByLabel(productCategoryOptions.value, productForm.value.productCategory) if (categoryId) { productForm.value.productCategoryId = categoryId const models = await modelList({ id: categoryId }) modelOptions.value = models?.data || models || [] const currentModel = (modelOptions.value || []).find(m => m.model === productForm.value.specificationModel) if (currentModel) { productForm.value.productModelId = currentModel.id } } } catch {} } else { productForm.value = { productCategoryId: undefined, productCategory: '', productModelId: undefined, specificationModel: '', unit: '', quantity: '', taxInclusiveUnitPrice: '', taxRate: '', taxInclusiveTotalPrice: '', taxExclusiveTotalPrice: '', invoiceType: '' } } productFormVisible.value = true } function closeProductDia() { proxy.resetForm('productFormRef') productFormVisible.value = false } function submitProduct() { productFormRef.value?.validate?.(valid => { if (!valid) return const payload = { ...productForm.value } if (productOperationType.value === 'add') { productData.value.push(payload) } else { productData.value[productIndex.value] = payload } closeProductDia() }) } function deleteProduct() { if (!productSelectedRows.value || productSelectedRows.value.length === 0) { proxy.$modal?.msgWarning?.('è¯·éæ©æ°æ®') return } const selectedIds = productSelectedRows.value.map(r => r?.id).filter(Boolean) if (operationType.value !== 'add' && selectedIds.length > 0) { delSalesProduct(selectedIds) .then(() => { proxy.$modal?.msgSuccess?.('å 餿å') productData.value = productData.value.filter(row => !selectedIds.includes(row?.id)) productSelectedRows.value = [] }) .catch(() => {}) return } productData.value = productData.value.filter(row => !productSelectedRows.value.includes(row)) productSelectedRows.value = [] } function calculateFromTotalPrice() { if (isCalculating.value) return const totalPrice = parseFloat(productForm.value.taxInclusiveTotalPrice) const quantity = parseFloat(productForm.value.quantity) if (!totalPrice || !quantity || quantity <= 0) return isCalculating.value = true productForm.value.taxInclusiveUnitPrice = (totalPrice / quantity).toFixed(2) if (productForm.value.taxRate) { productForm.value.taxExclusiveTotalPrice = proxy.calculateTaxExclusiveTotalPrice(totalPrice, productForm.value.taxRate) } isCalculating.value = false } function calculateFromExclusiveTotalPrice() { if (!productForm.value.taxRate) { proxy.$modal?.msgWarning?.('请å éæ©ç¨ç') return } if (isCalculating.value) return const exclusiveTotalPrice = parseFloat(productForm.value.taxExclusiveTotalPrice) const quantity = parseFloat(productForm.value.quantity) const taxRate = parseFloat(productForm.value.taxRate) if (!exclusiveTotalPrice || !quantity || quantity <= 0 || !taxRate) return isCalculating.value = true const taxRateDecimal = taxRate / 100 const inclusiveTotalPrice = exclusiveTotalPrice / (1 - taxRateDecimal) productForm.value.taxInclusiveTotalPrice = inclusiveTotalPrice.toFixed(2) productForm.value.taxInclusiveUnitPrice = (inclusiveTotalPrice / quantity).toFixed(2) isCalculating.value = false } function calculateFromQuantity() { if (!productForm.value.taxRate) { proxy.$modal?.msgWarning?.('请å éæ©ç¨ç') return } if (isCalculating.value) return const quantity = parseFloat(productForm.value.quantity) const unitPrice = parseFloat(productForm.value.taxInclusiveUnitPrice) if (!quantity || quantity <= 0 || !unitPrice) return isCalculating.value = true productForm.value.taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2) if (productForm.value.taxRate) { productForm.value.taxExclusiveTotalPrice = proxy.calculateTaxExclusiveTotalPrice( productForm.value.taxInclusiveTotalPrice, productForm.value.taxRate ) } isCalculating.value = false } function calculateFromUnitPrice() { if (!productForm.value.taxRate) { proxy.$modal?.msgWarning?.('请å éæ©ç¨ç') return } if (isCalculating.value) return const quantity = parseFloat(productForm.value.quantity) const unitPrice = parseFloat(productForm.value.taxInclusiveUnitPrice) if (!quantity || quantity <= 0 || !unitPrice) return isCalculating.value = true productForm.value.taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2) if (productForm.value.taxRate) { productForm.value.taxExclusiveTotalPrice = proxy.calculateTaxExclusiveTotalPrice( productForm.value.taxInclusiveTotalPrice, productForm.value.taxRate ) } isCalculating.value = false } function calculateFromTaxRate() { if (!productForm.value.taxRate) { proxy.$modal?.msgWarning?.('请å éæ©ç¨ç') return } if (isCalculating.value) return const inclusiveTotalPrice = parseFloat(productForm.value.taxInclusiveTotalPrice) const taxRate = parseFloat(productForm.value.taxRate) if (!inclusiveTotalPrice || !taxRate) return isCalculating.value = true productForm.value.taxExclusiveTotalPrice = proxy.calculateTaxExclusiveTotalPrice(inclusiveTotalPrice, taxRate) isCalculating.value = false } async function loadProjectTypeOptions() { try { const res = await listPlan({ current: 1, size: 999 }) const records = res?.data?.records || res?.records || res?.rows || [] projectTypeOptions.value = records.map(item => ({ label: item.name, value: item.id })) } catch { projectTypeOptions.value = [] } } async function loadRoleOptions() { try { const res = await findRoleListPage({ pageNum: 1, pageSize: 999 }) const records = res?.data?.records || res?.rows || res?.records || [] roleOptions.value = records.map(item => ({ label: item.roleName || item.name, value: item.id })) } catch { roleOptions.value = [] } } async function loadUserOptions() { try { const res = await userListAll() const list = res?.data || res?.rows || res || [] userOptions.value = (Array.isArray(list) ? list : []).map(u => ({ label: u.nickName || u.userName || u.username || u.name, value: u.userId || u.id })) } catch { userOptions.value = [] } } function addTeamRow() { form.value.teamList.push({ memberId: undefined, roleId: undefined, enterDate: '', leaveDate: '', phone: '', remark: '' }) } function removeTeamRow(index) { if (index > -1) form.value.teamList.splice(index, 1) } function addPhaseRow() { form.value.phaseList.push({ phaseName: '', description: '', ownerId: undefined, planDays: 0, planStartDate: '', planEndDate: '', progress: 0, actualStartDate: '', actualEndDate: '', overdueDays: 0, completionRemark: '' }) } function removePhaseRow(index) { if (index > -1) form.value.phaseList.splice(index, 1) } function addAddressRow() { form.value.addressList.push({ receiver: '', phone: '', address: '' }) } function removeAddressRow(index) { if (index > -1) form.value.addressList.splice(index, 1) } function beforeUpload() { if (isView.value) return false proxy.$modal?.loading?.('æ£å¨ä¸ä¼ æä»¶ï¼è¯·ç¨å...') return true } function handleUploadError() { proxy.$modal?.closeLoading?.() ElMessage.error('ä¸ä¼ æä»¶å¤±è´¥') } function handleUploadSuccess(res, file) { console.log(res, file) proxy.$modal?.closeLoading?.() if (res?.code !== 200) { ElMessage.error(res?.msg || 'ä¸ä¼ 失败') return } const attachmentId = res?.data?.[0]?.id ?? "" if (!attachmentId) return form.value.attachmentIds.push(attachmentId) console.log(form.value.attachmentIds) ElMessage.success('ä¸ä¼ æå') } function handleRemove(file) { const attachmentId = file?.attachmentId if (!attachmentId) return form.value.attachmentIds = (form.value.attachmentIds || []).filter(id => id !== attachmentId) } async function openDialog(payload = {}) { operationType.value = payload.operationType || 'add' resetFormData() await Promise.all([loadProjectTypeOptions(), loadRoleOptions(), loadUserOptions(), getProductOptions()]) if (payload.row?.id) { try { const res = await getProject(payload.row.id) const detail = res?.data?.data ?? res?.data ?? res const info = detail?.info || {} const shippingAddress = detail?.shippingAddress || {} const contractInfo = detail?.contractInfo || {} const normalizeId = v => { if (v === undefined || v === null || v === '') return undefined const n = Number(v) return Number.isNaN(n) ? v : n } const normalizeDictValue = v => { if (v === undefined || v === null || v === '') return '' return String(v) } const computeEstimatedDays = (start, end) => { if (!start || !end) return 0 const startTime = new Date(`${start}T00:00:00`).getTime() const endTime = new Date(`${end}T00:00:00`).getTime() if (!Number.isFinite(startTime) || !Number.isFinite(endTime)) return 0 if (endTime < startTime) return 0 return Math.floor((endTime - startTime) / (24 * 60 * 60 * 1000)) + 1 } Object.assign(form.value, { id: info.id, billNo: info.no ?? '', projectManagementPlanId: info.projectManagementPlanId ?? '', estimatedDays: Number(info.estimatedDays) || computeEstimatedDays(info.planStartTime, info.planEndTime) || 0, projectName: info.title ?? '', customerName: info.clientName ?? '', parentProjectName: info.projectManagementInfoParentName ?? '', setupDate: info.establishTime ?? '', projectSource: info.source ?? '', creatorName: info.managerName ?? '', billStatus: normalizeDictValue(info.status), projectStage: normalizeDictValue(info.stage ?? info.projectStage), planStartDate: info.planStartTime ?? '', planEndDate: info.planEndTime ?? '', projectAmount: info.orderAmount ?? 0, auditStatus: normalizeDictValue(info.reviewStatus), remark: info.remark ?? '', attachmentIds: Array.isArray(info.attachmentIds) ? info.attachmentIds : [], teamList: Array.isArray(info.teamList) ? info.teamList.map(t => ({ memberId: normalizeId(t.userId), roleId: normalizeId(t.userRoleId), enterDate: t.joinTime, leaveDate: t.departTime, phone: t.contact, remark: t.remark })) : [], addressList: shippingAddress?.address ? [{ receiver: shippingAddress.consignee, phone: shippingAddress.contract, address: shippingAddress.address }] : [], contactName: contractInfo.name ?? '', contactGender: contractInfo.sex === 'ç·' ? '1' : contractInfo.sex === '女' ? '2' : '', contactBirthday: contractInfo.birthday ?? '', contactDept: contractInfo.department ?? '', contactJob: contractInfo.job ?? '', contactMobile: contractInfo.phoneNumber ?? '', contactEmail: contractInfo.email ?? '', contactQq: contractInfo.qq ?? '', contactWechat: contractInfo.wx ?? '', contactWorkWechat: contractInfo.lineaFissa ?? '', contactAddress: contractInfo.origineEtnica ?? '', contactRemark: contractInfo.rappresentanteLegale ?? '' }) existingAttachments.value = Array.isArray(info.attachmentList) ? info.attachmentList.map(a => ({ id: a.id ?? a.fileId, name: a.fileName ?? a.name, url: a.url ?? a.fileUrl ?? a.path })) : [] const rawPhaseList = detail?.phaseList || detail?.projectPhaseList || detail?.projectStageList || info?.phaseList || info?.projectPhaseList || [] form.value.phaseList = Array.isArray(rawPhaseList) ? rawPhaseList.map(p => ({ phaseName: p.phaseName ?? p.name ?? p.title ?? '', description: p.description ?? p.workContent ?? p.desc ?? '', ownerId: normalizeId(p.ownerId ?? p.leaderId ?? p.userId), planDays: Number(p.planDays ?? p.estimatedDuration ?? p.estimatedDays) || 0, planStartDate: p.planStartDate ?? p.planStartTime ?? p.startDate ?? '', planEndDate: p.planEndDate ?? p.planEndTime ?? p.endDate ?? '', progress: Number(p.progress ?? p.schedule) || 0, actualStartDate: p.actualStartDate ?? p.actualStartTime ?? '', actualEndDate: p.actualEndDate ?? p.actualEndTime ?? '', overdueDays: Number(p.overdueDays ?? p.overDays) || 0, completionRemark: p.completionRemark ?? p.remark ?? '' })) : [] productData.value = detail?.salesLedgerProductList || detail?.productData || [] } catch {} } if (form.value.teamList.length === 0 && !isView.value) addTeamRow() if (form.value.phaseList.length === 0 && !isView.value) addPhaseRow() dialogVisible.value = true } function downloadAttachment(att) { if (att?.name) { try { proxy.$download.name(att.url); return } catch (e) {} } ElMessage.warning('éä»¶ææ ä¸è½½å°å') } function closeDialog() { dialogVisible.value = false } async function submitForm() { if (isView.value) { closeDialog() return } await formRef.value?.validate?.() if (!productData.value || productData.value.length === 0) { proxy.$modal?.msgWarning?.('请添å 产åä¿¡æ¯') return } const findLabel = (list, value) => (list || []).find(i => String(i.value) === String(value))?.label const teamList = (form.value.teamList || []).map(t => ({ userId: t.memberId, userName: findLabel(userOptions.value, t.memberId), userRoleId: t.roleId, userRoleName: findLabel(roleOptions.value, t.roleId), joinTime: t.enterDate, departTime: t.leaveDate, contact: t.phone, remark: t.remark })) const shippingRow = (form.value.addressList || [])[0] || {} const shippingAddress = { id: undefined, consignee: shippingRow.receiver, contract: shippingRow.phone, address: shippingRow.address } const contractInfo = { id: undefined, name: form.value.contactName, sex: form.value.contactGender === '1' ? 'ç·' : form.value.contactGender === '2' ? '女' : '', birthday: form.value.contactBirthday, department: form.value.contactDept, job: form.value.contactJob, phoneNumber: form.value.contactMobile, email: form.value.contactEmail, qq: form.value.contactQq, lineaFissa: form.value.contactWorkWechat, wx: form.value.contactWechat, origineEtnica: form.value.contactAddress, rappresentanteLegale: form.value.contactRemark } const info = { id: form.value.id ?? null, no: form.value.billNo, title: form.value.projectName, clientId: form.value.clientId ?? null, clientName: form.value.customerName, projectManagementInfoParentId: form.value.parentProjectId ?? null, projectManagementPlanId: form.value.projectManagementPlanId ?? null, establishTime: form.value.setupDate, source: form.value.projectSource, managerId: form.value.managerId ?? null, managerName: form.value.creatorName, salesmanId: form.value.salesmanId ?? null, salesmanName: form.value.salesmanName ?? '', planStartTime: form.value.planStartDate, planEndTime: form.value.planEndDate, actualStartTime: form.value.actualStartDate, actualEndTime: form.value.actualEndDate, status: form.value.billStatus === '' || form.value.billStatus === undefined || form.value.billStatus === null ? null : Number(form.value.billStatus), departmentId: form.value.departmentId ?? null, departmentName: form.value.departmentName ?? '', orderDate: form.value.orderDate, orderAmount: form.value.projectAmount, reviewStatus: form.value.auditStatus === '' || form.value.auditStatus === undefined || form.value.auditStatus === null ? null : Number(form.value.auditStatus), stage: form.value.projectStage === '' || form.value.projectStage === undefined || form.value.projectStage === null ? null : Number(form.value.projectStage), remark: form.value.remark, attachmentIds: Array.isArray(form.value.attachmentIds) ? form.value.attachmentIds : [], teamList } const payload = { info, shippingAddress, contractInfo, salesLedgerProductList: productData.value } const req = operationType.value === 'edit' ? updateProject : addProject const res = await req(payload) if (res?.code === 200) { ElMessage.success('ä¿åæå') closeDialog() emit('completed') return } ElMessage.error(res?.msg || 'ä¿å失败') } defineExpose({ openDialog }) </script> <style scoped lang="scss"> .section { border: 1px solid #ebeef5; border-radius: 8px; margin-bottom: 14px; background: #fff; } .section-header { display: flex; align-items: center; justify-content: space-between; padding: 12px 14px; cursor: pointer; } .section-title { display: flex; align-items: center; gap: 8px; font-weight: 600; color: #303133; } .section-bar { width: 3px; height: 14px; background: #e61e1e; border-radius: 2px; } .section-actions { display: flex; align-items: center; gap: 10px; } .toggle-icon { color: #909399; } .section-body { padding: 0 14px 14px; } .dialog-footer { display: flex; justify-content: center; gap: 12px; } .attachment-upload{ } </style> src/views/projectManagement/Management/index.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,333 @@ <template> <div class="app-container"> <SearchPanel v-model="queryParams" :schema="searchSchema" @search="handleQuery" @reset="resetQuery" > <template #billStatus="{ item }"> <el-select v-model="queryParams[item.prop]" placeholder="è¯·éæ©åæ®ç¶æ" clearable style="width: 100%"> <el-option v-for="dict in bill_status" :key="dict.value" :label="dict.label" :value="dict.value" /> </el-select> </template> <template #auditStatus="{ item }"> <el-select v-model="queryParams[item.prop]" placeholder="è¯·éæ©è®¡åç¶æ" clearable style="width: 100%"> <el-option v-for="dict in project_management" :key="dict.value" :label="dict.label" :value="dict.value" /> </el-select> </template> <template #projectStage="{ item }"> <el-select v-model="queryParams[item.prop]" placeholder="è¯·éæ©å®¡æ ¸ç¶æ" clearable style="width: 100%"> <el-option v-for="dict in plan_status" :key="dict.value" :label="dict.label" :value="dict.value" /> </el-select> </template> </SearchPanel> <div class="table-container"> <div class="table-actions"> <el-button style="background-color: #002FA7; color: #fff" @click="handleAdd">æ°å¢</el-button> <!-- <el-dropdown split-button type="default" @command="handleGenerateBill" style="margin-left: 10px;"> çæåæ® <template #dropdown> <el-dropdown-menu> <el-dropdown-item command="1">çæåæ®1</el-dropdown-item> <el-dropdown-item command="2">çæåæ®2</el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown> --> <el-button @click="handleSubmit">æäº¤</el-button> <el-button @click="handleAudit">å®¡æ ¸</el-button> <el-button @click="handleReverseAudit">åå®¡æ ¸</el-button> <el-button @click="handleDelete">å é¤</el-button> </div> <PIMTable :column="columns" :tableData="tableData" :page="pagination" :tableLoading="loading" :isSelection="true" @selection-change="handleSelectionChange" @pagination="handlePagination" > <template #auditStatus="{ row }"> <dict-tag :options="project_management" :value="row.auditStatus" /> </template> <template #projectStage="{ row }"> <dict-tag :options="plan_status" :value="row.projectStage" /> </template> <template #action="{ row }"> <el-button link type="primary" @click="handleEdit(row)">ç¼è¾</el-button> <el-button link type="primary" @click="handleProgressReport(row)">è¿åº¦æ±æ¥</el-button> <el-button link type="primary" @click="handleDiscussProgress(row)">æ´½è°è¿å±</el-button> <el-button link type="primary" @click="handleDetail(row)">详æ </el-button> </template> </PIMTable> </div> <FormDia ref="formDiaRef" @completed="getList" /> </div> </template> <script setup name="ProjectManagement"> import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue' import SearchPanel from '@/components/SearchPanel/index.vue' import PIMTable from '@/components/PIMTable/PIMTable.vue' import FormDia from './components/formDia.vue' import { listProject, delProject, submitProject, auditProject, reverseAuditProject } from '@/api/projectManagement/project' import { ElMessage, ElMessageBox } from 'element-plus' const { proxy } = getCurrentInstance() const { bill_status, project_management, plan_status } = proxy.useDict('bill_status', 'project_management', 'plan_status') const loading = ref(false) const ids = ref([]) const tableData = ref([]) const formDiaRef = ref() const data = reactive({ queryParams: { projectNameOrCode: undefined, customerName: undefined, billStatus: undefined, projectStage: undefined, auditStatus: undefined, salesperson: undefined, pageNum: 1, pageSize: 10 }, pagination: { current: 1, size: 10, total: 0, layout: 'total, sizes, prev, pager, next, jumper' } }) const { queryParams, pagination } = toRefs(data) const searchSchema = [ { prop: 'projectNameOrCode', label: '项ç®åç§°/ç¼å·', type: 'input', placeholder: '请è¾å ¥é¡¹ç®åç§°/ç¼å·' }, { prop: 'customerName', label: '客æ·åç§°', type: 'input', placeholder: '请è¾å ¥å®¢æ·åç§°' }, { prop: 'billStatus', label: 'åæ®ç¶æ', slot: 'billStatus' }, { prop: 'projectStage', label: '计åç¶æ', slot: 'projectStage' }, { prop: 'auditStatus', label: 'å®¡æ ¸ç¶æ', slot: 'auditStatus' }, { prop: 'salesperson', label: 'ä¸å¡äººå', type: 'input', placeholder: '请è¾å ¥ä¸å¡äººå' } ] const columns = [ { label: 'åæ®ç¼å·', prop: 'billNo', align: 'center', width: '150' }, { label: '项ç®åç§°', prop: 'projectName', align: 'center' }, { label: 'å®¡æ ¸ç¶æ', prop: 'auditStatus', align: 'center', dataType: 'slot', slot: 'auditStatus' }, { label: '客æ·åç§°', prop: 'customerName', align: 'center' }, { label: 'ç«é¡¹æ¥æ', prop: 'setupDate', align: 'center', width: '120' }, { label: 'é¡¹ç®æ¥æº', prop: 'projectSource', align: 'center' }, { label: '项ç®åç±»', prop: 'projectClassification', align: 'center' }, { label: 'æä½', prop: 'action', align: 'center', width: '250', dataType: 'slot', slot: 'action', fixed: 'right' } ] function getList() { loading.value = true const params = { noOrName: queryParams.value.projectNameOrCode, clientName: queryParams.value.customerName, salesmanName: queryParams.value.salesperson, reviewStatus: queryParams.value.auditStatus, stage: queryParams.value.projectStage, current: queryParams.value.pageNum, size: queryParams.value.pageSize } listProject(params) .then(response => { const records = response?.data?.records || response?.rows || response?.records || [] const billFilter = queryParams.value.billStatus const filtered = billFilter === undefined || billFilter === null || billFilter === '' ? records : records.filter(r => String(r.billStatus ?? r.status) === String(billFilter)) tableData.value = filtered.map(r => ({ id: r.id, billNo: r.no ?? r.billNo, projectName: r.title ?? r.projectName, billStatus: r.billStatus ?? r.status, auditStatus: r.reviewStatus ?? r.auditStatus, projectStage: r.stage ?? r.projectStage, customerName: r.clientName ?? r.customerName, parentProject: r.parentTitle ?? r.parentName ?? r.parentProject, setupDate: r.establishTime ?? r.setupDate, projectType: r.planName ?? r.projectType, projectSource: r.source ?? r.projectSource, projectClassification: r.departmentName ?? r.projectClassification, raw: r })) pagination.value.total = response?.total || response?.data?.total || 0 }) .finally(() => { loading.value = false }) } function handleQuery() { queryParams.value.pageNum = 1 pagination.value.current = 1 getList() } function resetQuery() { queryParams.value = { projectNameOrCode: undefined, customerName: undefined, billStatus: undefined, projectStage: undefined, auditStatus: undefined, salesperson: undefined, pageNum: 1, pageSize: 10 } handleQuery() } function handleSelectionChange(selection) { ids.value = selection.map(item => item.id) } function handlePagination({ page, limit }) { queryParams.value.pageNum = page queryParams.value.pageSize = limit pagination.value.current = page pagination.value.size = limit getList() } function handleAdd() { formDiaRef.value?.openDialog({ operationType: 'add' }) } function handleDelete() { const delIds = ids.value if (delIds.length === 0) { ElMessage.warning('è¯·éæ©è¦å é¤çæ°æ®é¡¹') return } ElMessageBox.confirm('æ¯å¦ç¡®è®¤å 餿鿰æ®é¡¹?', 'è¦å', { confirmButtonText: 'ç¡®å®', cancelButtonText: 'åæ¶', type: 'warning' }) .then(() => delProject(delIds)) .then(() => { getList() ElMessage.success('å 餿å') }) .catch(() => {}) } function handleSubmit() { const submitIds = ids.value if (submitIds.length === 0) { ElMessage.warning('è¯·éæ©è¦æäº¤çæ°æ®é¡¹') return } ElMessageBox.confirm('æ¯å¦ç¡®è®¤æäº¤æéæ°æ®é¡¹?', 'æç¤º', { confirmButtonText: 'ç¡®å®', cancelButtonText: 'åæ¶', type: 'warning' }) .then(async () => { await Promise.all(submitIds.map(id => submitProject({ id }))) }) .then(() => { getList() ElMessage.success('æäº¤æå') }) .catch(() => {}) } function handleAudit() { const auditIds = ids.value if (auditIds.length === 0) { ElMessage.warning('è¯·éæ©è¦å®¡æ ¸çæ°æ®é¡¹') return } ElMessageBox.confirm('æ¯å¦ç¡®è®¤å®¡æ ¸æéæ°æ®é¡¹?', 'æç¤º', { confirmButtonText: 'ç¡®å®', cancelButtonText: 'åæ¶', type: 'warning' }) .then(async () => { await Promise.all(auditIds.map(id => auditProject({ id }))) }) .then(() => { getList() ElMessage.success('å®¡æ ¸æå') }) .catch(() => {}) } function handleReverseAudit() { const reverseAuditIds = ids.value if (reverseAuditIds.length === 0) { ElMessage.warning('è¯·éæ©è¦åå®¡æ ¸çæ°æ®é¡¹') return } ElMessageBox.confirm('æ¯å¦ç¡®è®¤åå®¡æ ¸æéæ°æ®é¡¹?', 'æç¤º', { confirmButtonText: 'ç¡®å®', cancelButtonText: 'åæ¶', type: 'warning' }) .then(async () => { await Promise.all(reverseAuditIds.map(id => reverseAuditProject({ id }))) }) .then(() => { getList() ElMessage.success('åå®¡æ ¸æå') }) .catch(() => {}) } function handleGenerateBill(command) { ElMessage.info(`çæåæ®: ${command}`) } function handleProgressReport(row) { formDiaRef.value?.openDialog({ operationType: 'view', row }) } function handleDiscussProgress(row) { formDiaRef.value?.openDialog({ operationType: 'view', row }) } function handleDetail(row) { formDiaRef.value?.openDialog({ operationType: 'view', row }) } function handleEdit(row) { formDiaRef.value?.openDialog({ operationType: 'edit', row }) } onMounted(() => { getList() }) </script> <style scoped lang="scss"> .app-container { padding: 20px; } .table-container { background-color: #fff; padding: 20px; border-radius: 4px; } .table-actions { margin-bottom: 15px; display: flex; align-items: center; gap: 10px; } </style> src/views/salesManagement/returnOrder/index.vue
@@ -95,7 +95,7 @@ cancelButtonText: "åæ¶", type: "warning", }).then(() => { returnManagementDel([row.id]).then(() => { returnManagementDel({ ids: [row.id] }).then(() => { proxy.$modal.msgSuccess("å 餿å"); getList(); });