| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- æç´¢åºå --> |
| | | <div class="search_form"> |
| | | <el-form :model="searchForm" :inline="true" label-width="auto"> |
| | | <el-form-item label="客æ·åç§°"> |
| | | <el-input |
| | | v-model="searchForm.customerName" |
| | | placeholder="请è¾å
¥å®¢æ·åç§°" |
| | | clearable |
| | | prefix-icon="Search" |
| | | style="width: 200px" |
| | | @change="handleQuery" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="å½å
¥æ¥æï¼"> |
| | | <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange" |
| | | placeholder="è¯·éæ©" clearable @change="changeDaterange" /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleQuery">æç´¢</el-button> |
| | | <el-button @click="resetQuery">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div class="actions"> |
| | | <el-button type="primary" @click="handleAdd">æ°å»º</el-button> |
| | | <el-button type="danger" plain @click="handleDelete">å é¤</el-button> |
| | | </div> |
| | | </div> |
| | | <!-- è¡¨æ ¼åºå --> |
| | | <div class="table_list"> |
| | | <el-table |
| | | :data="tableData" |
| | | border |
| | | v-loading="tableLoading" |
| | | @selection-change="handleSelectionChange" |
| | | :row-key="(row) => row.id" |
| | | height="calc(100vh - 18.5em)" |
| | | stripe |
| | | > |
| | | <el-table-column align="center" type="selection" width="55" /> |
| | | <el-table-column align="center" label="åºå·" type="index" width="60" /> |
| | | <el-table-column label="ç¶æ" prop="status" width="120"> |
| | | <template #default="{ row }"> |
| | | <el-tag |
| | | :type="getStatusTagType(row.status)" |
| | | effect="light" |
| | | > |
| | | {{ getStatusText(row.status) }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="ç份" prop="province" show-overflow-tooltip width="120" /> |
| | | <el-table-column label="客æ·åç§°" prop="customerName" show-overflow-tooltip width="230" /> |
| | | <el-table-column label="åæºæ¥æº" prop="businessSource" show-overflow-tooltip width="150" /> |
| | | <el-table-column label="å®¢æ·æè¿°" prop="description" show-overflow-tooltip min-width="200" /> |
| | | <el-table-column label="å½å
¥äºº" prop="entryPerson" show-overflow-tooltip width="120" /> |
| | | <el-table-column label="æ´æ°æ¥æ" prop="updateTime" width="120"> |
| | | <template #default="{ row }"> |
| | | {{ formatDate(row.updateTime) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" fixed="right" width="130" align="center"> |
| | | <template #default="{ row }"> |
| | | <el-button |
| | | link |
| | | type="primary" |
| | | size="small" |
| | | @click="handleEdit(row)" |
| | | > |
| | | ç¼è¾ |
| | | </el-button> |
| | | <el-button |
| | | link |
| | | type="primary" |
| | | size="small" |
| | | @click="handleDetail(row)" |
| | | > |
| | | 详æ
|
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <!-- å页ç»ä»¶ --> |
| | | <pagination |
| | | v-show="total > 0" |
| | | :total="total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="page.current" |
| | | :limit="page.size" |
| | | @pagination="paginationChange" |
| | | /> |
| | | </div> |
| | | |
| | | <!-- æ°å¢/ç¼è¾å¯¹è¯æ¡ --> |
| | | <el-dialog |
| | | v-model="dialogFormVisible" |
| | | :title="operationType === 'add' ? 'æ°å»ºåæº' : operationType === 'edit' ? 'ç¼è¾åæº' : 'åæºè¯¦æ
'" |
| | | width="600px" |
| | | @close="closeDialog" |
| | | > |
| | | <el-form |
| | | :model="form" |
| | | :rules="rules" |
| | | ref="formRef" |
| | | label-width="100px" |
| | | label-position="left" |
| | | > |
| | | <el-form-item label="ç¶æ" prop="status"> |
| | | <el-select v-model="form.status" placeholder="è¯·éæ©ç¶æ" style="width: 100%" :disabled="operationType === 'detail'"> |
| | | <el-option |
| | | v-for="item in statusOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="ç份" prop="province"> |
| | | <el-select v-model="form.province" filterable placeholder="è¯·éæ©ç份" style="width: 100%" :disabled="operationType === 'detail'"> |
| | | <el-option |
| | | v-for="item in provinceOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="客æ·åç§°" prop="customerName"> |
| | | <el-select v-model="form.customerName" placeholder="è¯·éæ©" clearable :disabled="operationType === 'detail'"> |
| | | <el-option v-for="item in customerOption" :key="item.customerName" :label="item.customerName" :value="item.customerName"> |
| | | {{ |
| | | item.customerName + "ââ" + item.taxpayerIdentificationNumber |
| | | }} |
| | | </el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="åæºæ¥æº" prop="businessSource"> |
| | | <el-input v-model="form.businessSource" placeholder="请è¾å
¥åæºæ¥æº" :disabled="operationType === 'detail'" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="å®¢æ·æè¿°" prop="description"> |
| | | <el-input |
| | | v-model="form.description" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请è¾å
¥å®¢æ·æè¿°" |
| | | maxlength="500" |
| | | show-word-limit |
| | | :disabled="operationType === 'detail'" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å½å
¥äºº" prop="entryPerson"> |
| | | <el-select v-model="form.entryPerson" placeholder="è¯·éæ©" clearable @change="changs" :disabled="operationType === 'detail'"> |
| | | <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" :value="item.nickName" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å½å
¥æ¥æ" prop="entryDateStart"> |
| | | <el-date-picker style="width: 100%" v-model="form.entryDateStart" value-format="YYYY-MM-DD" format="YYYY-MM-DD" |
| | | type="date" placeholder="è¯·éæ©" clearable :disabled="operationType === 'detail'" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="closeDialog">åæ¶</el-button> |
| | | <el-button type="primary" @click="submitForm" v-if="operationType !== 'detail'"> |
| | | ç¡®å® |
| | | </el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, getCurrentInstance } from 'vue' |
| | | import { Search } from '@element-plus/icons-vue' |
| | | import { ElMessageBox, ElMessage } from 'element-plus' |
| | | import pagination from '@/components/PIMTable/Pagination.vue' |
| | | import useUserStore from '@/store/modules/user' |
| | | import dayjs from 'dayjs' |
| | | import { |
| | | opportunityListPage, |
| | | addOpportunity, |
| | | updateOpportunity, |
| | | delOpportunity |
| | | } from '@/api/salesManagement/opportunityManagement.js' |
| | | import { userListNoPage } from '@/api/system/user.js' |
| | | import { customerList } from '@/api/salesManagement/salesLedger.js' |
| | | |
| | | const { proxy } = getCurrentInstance() |
| | | const userStore = useUserStore() |
| | | |
| | | // è¡¨æ ¼æ°æ® |
| | | const tableData = ref([]) |
| | | const selectedRows = ref([]) |
| | | const tableLoading = ref(false) |
| | | const userList = ref([]) |
| | | const customerOption = ref([]) |
| | | |
| | | // å页é
ç½® |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | }) |
| | | const total = ref(0) |
| | | |
| | | // æç´¢è¡¨å |
| | | const searchForm = reactive({ |
| | | customerName: '', |
| | | entryDate: [], |
| | | entryDateStart: '', |
| | | entryDateEnd: '' |
| | | }) |
| | | |
| | | // å¯¹è¯æ¡ç¸å
³ |
| | | const dialogFormVisible = ref(false) |
| | | const operationType = ref('') // add, detail |
| | | const formRef = ref() |
| | | const form = reactive({ |
| | | id: undefined, |
| | | status: 'new', |
| | | province: '', |
| | | customerName: '', |
| | | businessSource: '', |
| | | description: '', |
| | | entryPerson: userStore.nickName, |
| | | entryDateStart: dayjs().format('YYYY-MM-DD') |
| | | }) |
| | | |
| | | // 表åéªè¯è§å |
| | | const rules = reactive({ |
| | | customerName: [ |
| | | { required: true, message: 'è¯·éæ©å®¢æ·', trigger: 'change' } |
| | | ], |
| | | status: [ |
| | | { required: true, message: 'è¯·éæ©ç¶æ', trigger: 'change' } |
| | | ], |
| | | entryPerson: [ |
| | | { required: true, message: 'è¯·éæ©å½å
¥äºº', trigger: 'change' } |
| | | ], |
| | | entryDateStart: [ |
| | | { required: true, message: 'è¯·éæ©å½å
¥æ¥æ', trigger: 'change' } |
| | | ] |
| | | }) |
| | | |
| | | // ç¶æé项 |
| | | const statusOptions = [ |
| | | { value: 'new', label: 'æ°å»º' }, |
| | | { value: 'tracking', label: '项ç®è·è¸ª' }, |
| | | { value: 'contract', label: 'ååç¾çº¦' }, |
| | | { value: 'delivery', label: '项ç®äº¤ä»' }, |
| | | { value: 'acceptance', label: '项ç®éªæ¶' } |
| | | ] |
| | | |
| | | // ç份é项ï¼ç¤ºä¾ï¼ |
| | | const provinceOptions = [ |
| | | { value: 'beijing', label: 'å京å¸' }, |
| | | { value: 'tianjin', label: '天津å¸' }, |
| | | { value: 'hebei', label: 'æ²³åç' }, |
| | | { value: 'shanxi', label: '山西ç' }, |
| | | { value: 'neimenggu', label: 'å
èå¤èªæ²»åº' }, |
| | | { value: 'liaoning', label: 'è¾½å®ç' }, |
| | | { value: 'jilin', label: 'åæç' }, |
| | | { value: 'heilongjiang', label: 'é»é¾æ±ç' }, |
| | | { value: 'shanghai', label: '䏿µ·å¸' }, |
| | | { value: 'jiangsu', label: 'æ±èç' }, |
| | | { value: 'zhejiang', label: 'æµæ±ç' }, |
| | | { value: 'anhui', label: 'å®å¾½ç' }, |
| | | { value: 'fujian', label: 'ç¦å»ºç' }, |
| | | { value: 'jiangxi', label: 'æ±è¥¿ç' }, |
| | | { value: 'shandong', label: 'å±±ä¸ç' }, |
| | | { value: 'henan', label: 'æ²³åç' }, |
| | | { value: 'hubei', label: 'æ¹åç' }, |
| | | { value: 'hunan', label: 'æ¹åç' }, |
| | | { value: 'guangdong', label: '广ä¸ç' }, |
| | | { value: 'guangxi', label: '广西壮æèªæ²»åº' }, |
| | | { value: 'hainan', label: 'æµ·åç' }, |
| | | { value: 'chongqing', label: 'éåºå¸' }, |
| | | { value: 'sichuan', label: 'åå·ç' }, |
| | | { value: 'guizhou', label: 'è´µå·ç' }, |
| | | { value: 'yunnan', label: 'äºåç' }, |
| | | { value: 'xizang', label: '西èèªæ²»åº' }, |
| | | { value: 'shaanxi', label: 'é西ç' }, |
| | | { value: 'gansu', label: 'çèç' }, |
| | | { value: 'qinghai', label: 'éæµ·ç' }, |
| | | { value: 'ningxia', label: 'å®å¤åæèªæ²»åº' }, |
| | | { value: 'xinjiang', label: 'æ°çç»´å¾å°èªæ²»åº' }, |
| | | { value: 'taiwan', label: 'å°æ¹¾ç' }, |
| | | { value: 'xianggang', label: '馿¸¯ç¹å«è¡æ¿åº' }, |
| | | { value: 'aomen', label: 'æ¾³é¨ç¹å«è¡æ¿åº' } |
| | | ] |
| | | |
| | | // è·åç¶ææ ç¾ç±»å |
| | | const getStatusTagType = (status) => { |
| | | const typeMap = { |
| | | 'new': 'info', |
| | | 'tracking': 'primary', |
| | | 'contract': 'warning', |
| | | 'delivery': 'success', |
| | | 'acceptance': 'success' |
| | | } |
| | | return typeMap[status] || 'info' |
| | | } |
| | | |
| | | // è·åç¶æææ¬ |
| | | const getStatusText = (status) => { |
| | | const textMap = { |
| | | 'new': 'æ°å»º', |
| | | 'tracking': '项ç®è·è¸ª', |
| | | 'contract': 'ååç¾çº¦', |
| | | 'delivery': '项ç®äº¤ä»', |
| | | 'acceptance': '项ç®éªæ¶' |
| | | } |
| | | return textMap[status] || 'æªç¥' |
| | | } |
| | | |
| | | // æ ¼å¼åæ¥æ |
| | | const formatDate = (date) => { |
| | | if (!date) return '' |
| | | return dayjs(date).format('YYYY-MM-DD') |
| | | } |
| | | |
| | | // æ¥è¯¢å表 |
| | | const handleQuery = () => { |
| | | page.current = 1 |
| | | getList() |
| | | } |
| | | |
| | | // éç½®æ¥è¯¢ |
| | | const resetQuery = () => { |
| | | Object.assign(searchForm, { |
| | | customerName: '', |
| | | entryDate: [], |
| | | entryDateStart: '', |
| | | entryDateEnd: '' |
| | | }) |
| | | handleQuery() |
| | | } |
| | | |
| | | // æ¥æèå´åå |
| | | const changeDaterange = (val) => { |
| | | if (val && val.length === 2) { |
| | | searchForm.entryDateStart = val[0] |
| | | searchForm.entryDateEnd = val[1] |
| | | } else { |
| | | searchForm.entryDateStart = '' |
| | | searchForm.entryDateEnd = '' |
| | | } |
| | | handleQuery() |
| | | } |
| | | |
| | | // è·ååè¡¨æ°æ® |
| | | const getList = () => { |
| | | tableLoading.value = true |
| | | |
| | | // å建æ¥è¯¢åæ°ï¼æé¤entryDateåæ®µï¼åªä½¿ç¨entryDateStartåentryDateEnd |
| | | const { entryDate, ...queryParams } = searchForm |
| | | const params = { |
| | | ...queryParams, |
| | | ...page |
| | | } |
| | | |
| | | // å é¤ç©ºå¼åæ° |
| | | Object.keys(params).forEach(key => { |
| | | if (params[key] === '' || params[key] === null || params[key] === undefined) { |
| | | delete params[key] |
| | | } |
| | | }) |
| | | |
| | | opportunityListPage(params).then(res => { |
| | | tableData.value = res.data.records || [] |
| | | total.value = res.data.total || 0 |
| | | }).catch(err => { |
| | | console.error('è·ååæºå表失败:', err) |
| | | tableData.value = [] |
| | | total.value = 0 |
| | | }).finally(() => { |
| | | tableLoading.value = false |
| | | }) |
| | | } |
| | | |
| | | // å页åå |
| | | const paginationChange = (pagination) => { |
| | | page.current = pagination.page |
| | | page.size = pagination.limit |
| | | getList() |
| | | } |
| | | |
| | | // éæ©åå |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection |
| | | } |
| | | |
| | | // æ°å»ºåæº |
| | | const handleAdd = async () => { |
| | | operationType.value = 'add' |
| | | resetForm() |
| | | |
| | | // å è½½ç¨æ·å表å客æ·å表 |
| | | let userLists = await userListNoPage() |
| | | userList.value = userLists.data |
| | | customerList().then((res) => { |
| | | customerOption.value = res |
| | | }) |
| | | |
| | | dialogFormVisible.value = true |
| | | } |
| | | |
| | | // æ¥ç详æ
|
| | | const handleDetail = async (row) => { |
| | | operationType.value = 'detail' |
| | | |
| | | // å è½½ç¨æ·å表å客æ·å表 |
| | | let userLists = await userListNoPage() |
| | | userList.value = userLists.data |
| | | customerList().then((res) => { |
| | | customerOption.value = res |
| | | }) |
| | | |
| | | // 使ç¨updateTimeä½ä¸ºå½å
¥æ¶é´åæ¾ |
| | | Object.assign(form, row, { |
| | | entryDateStart: row.updateTime || row.entryDateStart |
| | | }) |
| | | dialogFormVisible.value = true |
| | | } |
| | | |
| | | // ç¼è¾åæº |
| | | const handleEdit = async (row) => { |
| | | operationType.value = 'edit' |
| | | |
| | | // å è½½ç¨æ·å表å客æ·å表 |
| | | let userLists = await userListNoPage() |
| | | userList.value = userLists.data |
| | | customerList().then((res) => { |
| | | customerOption.value = res |
| | | }) |
| | | |
| | | // 使ç¨updateTimeä½ä¸ºå½å
¥æ¶é´åæ¾ |
| | | Object.assign(form, row, { |
| | | entryDateStart: row.updateTime || row.entryDateStart |
| | | }) |
| | | dialogFormVisible.value = true |
| | | } |
| | | |
| | | // å½å
¥äººååå¤ç |
| | | const changs = (value) => { |
| | | // å¯ä»¥æ ¹æ®éè¦æ·»å å¤çé»è¾ |
| | | } |
| | | |
| | | // æäº¤è¡¨å |
| | | const submitForm = () => { |
| | | formRef.value.validate(valid => { |
| | | if (valid) { |
| | | const api = operationType.value === 'add' ? addOpportunity : updateOpportunity |
| | | |
| | | api(form).then(res => { |
| | | if (res.code === 200) { |
| | | proxy.$modal.msgSuccess(operationType.value === 'add' ? 'æ°å»ºæå' : 'ä¿®æ¹æå') |
| | | closeDialog() |
| | | getList() |
| | | } else { |
| | | proxy.$modal.msgError(res.msg || 'æä½å¤±è´¥') |
| | | } |
| | | }).catch(err => { |
| | | proxy.$modal.msgError('æä½å¤±è´¥') |
| | | }) |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // å é¤åæº |
| | | const handleDelete = () => { |
| | | if (selectedRows.value.length === 0) { |
| | | proxy.$modal.msgWarning('è¯·éæ©è¦å é¤çåæº') |
| | | return |
| | | } |
| | | |
| | | ElMessageBox.confirm('ç¡®å®å é¤éä¸çåæºåï¼', 'æç¤º', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | const ids = selectedRows.value.map(item => item.id) |
| | | delOpportunity(ids).then(res => { |
| | | if (res.code === 200) { |
| | | proxy.$modal.msgSuccess('å 餿å') |
| | | getList() |
| | | } else { |
| | | proxy.$modal.msgError(res.msg || 'å é¤å¤±è´¥') |
| | | } |
| | | }).catch(err => { |
| | | proxy.$modal.msgError('å é¤å¤±è´¥') |
| | | }) |
| | | }).catch(() => { |
| | | // ç¨æ·åæ¶å é¤ |
| | | }) |
| | | } |
| | | |
| | | |
| | | |
| | | // é置表å |
| | | const resetForm = () => { |
| | | Object.assign(form, { |
| | | id: undefined, |
| | | status: 'new', |
| | | province: '', |
| | | customerName: '', |
| | | businessSource: '', |
| | | description: '', |
| | | entryPerson: userStore.nickName, |
| | | entryDateStart: dayjs().format('YYYY-MM-DD') |
| | | }) |
| | | |
| | | if (formRef.value) { |
| | | formRef.value.clearValidate() |
| | | } |
| | | } |
| | | |
| | | // å
³éå¯¹è¯æ¡ |
| | | const closeDialog = () => { |
| | | dialogFormVisible.value = false |
| | | resetForm() |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getList() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | .search_form { |
| | | display: flex; |
| | | align-items: flex-start; |
| | | justify-content: space-between; |
| | | } |
| | | |
| | | .table_list { |
| | | margin-top: unset; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | |
| | | :deep(.el-form-item__label) { |
| | | font-weight: 500; |
| | | } |
| | | |
| | | :deep(.el-table) { |
| | | .el-table__header-wrapper { |
| | | th { |
| | | background-color: #f0f2f5; |
| | | color: #333; |
| | | font-weight: 600; |
| | | } |
| | | } |
| | | } |
| | | </style> |