Merge branch 'main' of http://120.76.84.145:10101/gitblit/r/H5/xp-robotaxi-web
| | |
| | | src="/src/assets/element-img/file-icon.png" |
| | | /> |
| | | <div class="file-name">{{ file.name }}</div> |
| | | <div class="relative mr-24px"> |
| | | <div class="relative ml-24px"> |
| | | <img |
| | | class="file-look" |
| | | src="/src/assets/element-img/look.png" |
| | |
| | | src="/src/assets/element-img/file-icon.png" |
| | | /> |
| | | <div class="file-name">{{ file.name }}</div> |
| | | <div class="relative mr-24px"> |
| | | <div class="relative ml-24px"> |
| | | <img |
| | | class="file-look" |
| | | src="/src/assets/element-img/look.png" |
| | |
| | | src="/src/assets/element-img/file-icon.png" |
| | | /> |
| | | <div class="file-name">{{ file.name }}</div> |
| | | <div class="relative mr-24px"> |
| | | <div class="relative ml-24px"> |
| | | <img |
| | | class="file-look" |
| | | src="/src/assets/element-img/look.png" |
| | |
| | | const maintenanceColumns = [ |
| | | { title: '序号', key: 'index', align: 'left' as const, width: 80, render: (row: any, index: number) => (maintenancePage.value.pageNum - 1) * maintenancePage.value.pageSize + index + 1 }, |
| | | { title: '维修原因', key: 'repairReason', align: 'left' as const, width: 180 }, |
| | | { title: '停运时间', key: 'repairTime', align: 'left' as const, width: 240, render: (row: any) => `${row.startTime ? dayjs(row.startTime).format("YYYY/MM/DD HH:mm:ss") : '-'} 至 ${row.endTime ? dayjs(row.endTime).format("YYYY/MM/DD HH:mm:ss") : '-'}` }, |
| | | { title: '停运时间', key: 'repairTime', align: 'left' as const, width: 240, render: (row: any) => `${row.startTime ? dayjs(row.startTime).format("YYYY/MM/DD") : '-'} 至 ${row.endTime ? dayjs(row.endTime).format("YYYY/MM/DD") : '-'}` }, |
| | | { title: '维修总费用', key: 'repairMoney', align: 'left' as const, width: 140, render: (row: any) => (row.repairMoney != null ? `¥${row.repairMoney}` : '-') }, |
| | | { title: '送修人', key: 'operatorStartName', align: 'left' as const, width: 120 }, |
| | | { title: '送修时间', key: 'sendTime', align: 'left' as const, width: 180 , render: (row: any) => row.sendTime ? dayjs(row.sendTime).format("YYYY/MM/DD HH:mm:ss") : '-' }, |
| | |
| | | |
| | | <template> |
| | | <n-modal v-model:show="show" :mask-closable="false" preset="card" :title="mode === 'edit' ? '编辑打卡地址' : '新增打卡地址'" |
| | | class="w-900px" :auto-focus="false" :segmented="{ |
| | | class="w-600px" :auto-focus="false" :segmented="{ |
| | | content: true, |
| | | action: true, |
| | | }"> |
| | | <n-form ref="formRef" :model="formModel" :rules="rules" label-placement="top" require-mark-placement="left" |
| | | show-require-mark> |
| | | <div class="grid grid-cols-2 gap-4"> |
| | | <div class="grid grid-cols-2 gap-8"> |
| | | <n-form-item label="打卡地址" path="locationAddress"> |
| | | <n-input v-model:value="formModel.locationAddress" placeholder="请输入" clearable maxlength="50" /> |
| | | </n-form-item> |
| | |
| | | <n-form-item label="打卡范围" path="locationRange"> |
| | | <n-input-number v-model:value="formModel.locationRange" placeholder="请输入" clearable style="width: 100%" |
| | | :min="0" :precision="0" :show-button="false"> |
| | | <template #suffix> |
| | | <span>m以内</span> |
| | | </template> |
| | | </n-input-number> |
| | | <span class="flex-shrink-0 ml-8px" style="color:rgba(23, 31, 38, 0.8)">m以内</span> |
| | | </n-form-item> |
| | | </div> |
| | | <div class="grid grid-cols-2 gap-4"> |
| | | <div class="grid grid-cols-2 gap-8"> |
| | | <n-form-item label="经纬度" path="location"> |
| | | <n-input |
| | | :value="formModel.locationLon && formModel.locationLat ? `${formModel.locationLon}, ${formModel.locationLat}` : ''" |
| | |
| | | </n-form> |
| | | |
| | | <template #action> |
| | | <n-space justify="center"> |
| | | <n-space justify="end"> |
| | | <n-button @click="handleCancel">取消</n-button> |
| | | <n-button type="primary" @click="handleConfirm">保存</n-button> |
| | | </n-space> |
| | |
| | | <script setup lang="tsx"> |
| | | import type { DataTableColumns } from 'naive-ui' |
| | | import { useBoolean } from '@/hooks' |
| | | import { NButton, NSpace, NTag } from 'naive-ui' |
| | | import { getCheckLocationList, deleteCheckLocation, addCheckLocation, editCheckLocation, updateCheckLocationStatus } from '@/service/api/attendance' |
| | | import { NButton, NTag } from 'naive-ui' |
| | | import { addCheckLocation, deleteCheckLocation, editCheckLocation, getCheckLocationList, updateCheckLocationStatus } from '@/service/api/attendance' |
| | | import SearchPanel from '@/components/custom/SearchPanel.vue' |
| | | import AddressFormModal from './components/AddressFormModal.vue' |
| | | import dayjs from 'dayjs' |
| | | |
| | | const { bool: loading, setTrue: startLoading, setFalse: endLoading } = useBoolean(false) |
| | | const { t } = useI18n() |
| | | const dialog = useDialog() |
| | | |
| | | const pageParams = ref({ |
| | | pageNum: 1, |
| | | pageSize: 10, |
| | | }) |
| | | |
| | | const initialModel = { |
| | | locationAddress: '', |
| | | } |
| | | const model = ref({ ...initialModel }) |
| | | |
| | | function handleResetSearch() { |
| | | model.value = { ...initialModel } |
| | | handleSearch() |
| | |
| | | const showFormModal = ref(false) |
| | | const formMode = ref<'add' | 'edit'>('add') |
| | | |
| | | const pageParams = ref({ |
| | | pageNum: 1, |
| | | pageSize: 10, |
| | | }) |
| | | const tableScrollX = computed(() => Math.min(1600, 70 + 180 + 360 + 120 + 200)) |
| | | |
| | | const columns = computed<DataTableColumns<any>>(() =>[ |
| | | const columns = computed<DataTableColumns<any>>(() => [ |
| | | { |
| | | title: '序号', |
| | | key: 'serialNumber', |
| | | width: 70, |
| | | align: 'left', |
| | | resizable: true, |
| | | }, |
| | | { |
| | | title: '创建时间', |
| | | key: 'createTime', |
| | | width: 180, |
| | | align: 'left', |
| | | resizable: true, |
| | | render: (row) => { |
| | | return row.createTime ? dayjs(row.createTime).format('YYYY-MM-DD HH:mm') : '' |
| | | } |
| | | return row.createTime ? dayjs(row.createTime).format('YYYY/MM/DD HH:mm') : '' |
| | | }, |
| | | }, |
| | | { |
| | | title: '打卡地', |
| | | key: 'locationAddress', |
| | | width: 360, |
| | | align: 'left', |
| | | resizable: true, |
| | | minWidth: 200 |
| | | }, |
| | | { |
| | | title: '状态', |
| | | width: 300, |
| | | width: 120, |
| | | key: 'status', |
| | | align: 'left', |
| | | resizable: true, |
| | | render(row) { |
| | | return h( |
| | | NTag, |
| | | { |
| | | type: row.status === 0 ? 'default' : 'primary', |
| | | bordered: false |
| | | render(row) { |
| | | const isEnabled = row.status === 1 |
| | | return h( |
| | | NTag, |
| | | { |
| | | bordered: false, |
| | | round: true, |
| | | style: { |
| | | borderRadius: '4px', |
| | | border: 'none', |
| | | color: isEnabled ? '#0BB52D' : '#666666', |
| | | backgroundColor: isEnabled ? '#DCF5DD' : 'rgba(23, 31, 38, 0.05)', |
| | | }, |
| | | { |
| | | default: () => row.status === 0 ? '禁用中' : '启用中' |
| | | } |
| | | ) |
| | | } |
| | | }, |
| | | { |
| | | default: () => isEnabled ? '启用中' : '禁用中', |
| | | }, |
| | | ) |
| | | }, |
| | | }, |
| | | { |
| | | title: '操作', |
| | | key: 'actions', |
| | | width: 150, |
| | | width: 200, |
| | | align: 'left', |
| | | render: (row) => { |
| | | const isEnabled = row.status === 1 |
| | | return ( |
| | | <NSpace> |
| | | <NButton |
| | | text |
| | | <div class="flex items-center justify-start gap-2"> |
| | | <span |
| | | class="cursor-pointer" |
| | | v-permission="70212" |
| | | type='primary' |
| | | style={{ color: '#0068FF' }} |
| | | onClick={() => handleEdit(row)} |
| | | > |
| | | 编辑 |
| | | </NButton> |
| | | <NButton |
| | | text |
| | | </span> |
| | | <span |
| | | class="cursor-pointer" |
| | | v-permission="70214" |
| | | type='primary' |
| | | style={{ color: isEnabled ? '#0068FF' : '#0068FF' }} |
| | | onClick={() => { |
| | | dialog.create({ |
| | | title: '是否确认' + (row.status === 1 ? '禁用' : '启用') + '打卡地?', |
| | | title: `是否确认${isEnabled ? '禁用' : '启用'}打卡地?`, |
| | | content: '', |
| | | positiveText: '确定', |
| | | negativeText: '取消', |
| | | draggable: true, |
| | | onPositiveClick: () => { |
| | | updateCheckLocationStatus({ id: row.id, status: row.status === 1 ? 0 : 1 }).then(res => { |
| | | if (res.code === 200) { |
| | | window.$message.success('操作成功') |
| | | getList() |
| | | } |
| | | }) |
| | | } |
| | | updateCheckLocationStatus({ id: row.id, status: isEnabled ? 0 : 1 }).then((res) => { |
| | | if (res.code === 200) { |
| | | window.$message.success('操作成功') |
| | | getList() |
| | | } |
| | | }) |
| | | }, |
| | | }) |
| | | }} |
| | | > |
| | | { row.status === 1 ? '禁用' : '启用' } |
| | | </NButton> |
| | | <NButton |
| | | text |
| | | {isEnabled ? '禁用' : '启用'} |
| | | </span> |
| | | <span |
| | | class="cursor-pointer" |
| | | v-permission="70213" |
| | | type='error' |
| | | style={{ color: '#0068FF' }} |
| | | onClick={() => { |
| | | dialog.create({ |
| | | title: '是否确认删除数据?', |
| | | content: `确认删除该打卡地址?删除后不可恢复。`, |
| | | positiveText: '确认', |
| | | negativeText: '取消', |
| | | draggable: true, |
| | | onPositiveClick: () => { |
| | | dialog.create({ |
| | | title: '是否确认删除数据?', |
| | | content: '确认删除该打卡地址?删除后不可恢复。', |
| | | positiveText: '确认', |
| | | negativeText: '取消', |
| | | draggable: true, |
| | | onPositiveClick: () => { |
| | | deleteCheckLocation(row.id!).then((res) => { |
| | | if (res.code === 200) { |
| | | window.$message.success('删除成功') |
| | |
| | | }).finally(() => { |
| | | getList() |
| | | }) |
| | | } |
| | | }, |
| | | }) |
| | | }} |
| | | > |
| | | 删除 |
| | | </NButton> |
| | | </NSpace> |
| | | </span> |
| | | </div> |
| | | ) |
| | | }, |
| | | }, |
| | |
| | | const count = ref(0) |
| | | const listData = ref<any[]>([]) |
| | | |
| | | const handleAdd = () => { |
| | | function handleAdd() { |
| | | formMode.value = 'add' |
| | | showFormModal.value = true |
| | | } |
| | | |
| | | const handleEdit = (row: any) => { |
| | | function handleEdit(row: any) { |
| | | formMode.value = 'edit' |
| | | showFormModal.value = true |
| | | modalRef.value.openEdit(row) |
| | | } |
| | | |
| | | const handleConfirmForm = (data: any) => { |
| | | function handleConfirmForm(data: any) { |
| | | if (data.id) { |
| | | editCheckLocation(data).then((res) => { |
| | | if (res.code === 200) { |
| | |
| | | }).finally(() => { |
| | | getList() |
| | | }) |
| | | } else { |
| | | } |
| | | else { |
| | | addCheckLocation(data).then((res) => { |
| | | if (res.code === 200) { |
| | | window.$message.success('新增成功') |
| | |
| | | } |
| | | } |
| | | |
| | | |
| | | async function getList() { |
| | | startLoading() |
| | | const params = { |
| | |
| | | } |
| | | getCheckLocationList(params).then((res) => { |
| | | listData.value = res.data.records || [] |
| | | listData.value.forEach((item: any, index: number) => { |
| | | listData.value.forEach((item: any, index: number) => { |
| | | item.serialNumber = (pageParams.value.pageNum - 1) * pageParams.value.pageSize + index + 1 |
| | | }) |
| | | count.value = res.data.total || res.data.records?.length |
| | |
| | | </script> |
| | | |
| | | <template> |
| | | <n-flex class="roboTaxi"> |
| | | <NSpace vertical class="flex-1"> |
| | | <div class="roboTaxi-form-card"> |
| | | <n-form ref="formRef" :model="model" label-placement="left" inline :show-feedback="false"> |
| | | <SearchPanel> |
| | | <n-gi v-for="item in filterItems" :key="item.path"> |
| | | <n-form-item :label="item.label" :path="item.path"> |
| | | <component :is="item.render" /> |
| | | </n-form-item> |
| | | </n-gi> |
| | | <template #action> |
| | | <NButton strong @click="handleResetSearch"> |
| | | {{ $t('common.reset') }} |
| | | </NButton> |
| | | <NButton type="primary" @click="handleSearch"> |
| | | {{ $t('common.search') }} |
| | | </NButton> |
| | | </template> |
| | | </SearchPanel> |
| | | </n-form> |
| | | <div class="roboTaxi"> |
| | | <div class="roboTaxi-form-card mb-12px"> |
| | | <n-form ref="formRef" :model="model" label-placement="left" inline :show-feedback="false"> |
| | | <SearchPanel> |
| | | <n-gi v-for="item in filterItems" :key="item.path"> |
| | | <n-form-item :label="item.label" :path="item.path"> |
| | | <component :is="item.render" /> |
| | | </n-form-item> |
| | | </n-gi> |
| | | <template #action> |
| | | <NButton strong @click="handleResetSearch"> |
| | | {{ $t('common.reset') }} |
| | | </NButton> |
| | | <NButton type="primary" @click="handleSearch"> |
| | | {{ $t('common.search') }} |
| | | </NButton> |
| | | </template> |
| | | </SearchPanel> |
| | | </n-form> |
| | | </div> |
| | | |
| | | <n-card class="flex-1 robotaxi-table-card"> |
| | | <template #header> |
| | | <n-flex v-permission="70211" justify="flex-end"> |
| | | <NButton type="primary" secondary @click="handleAdd"> |
| | | 新增 |
| | | </NButton> |
| | | </n-flex> |
| | | </template> |
| | | <div class="flex-1 flex flex-col h-full"> |
| | | <n-data-table |
| | | class="flex-1" |
| | | :columns="columns" |
| | | :data="listData" |
| | | :loading="loading" |
| | | :bordered="false" |
| | | flex-height |
| | | :scroll-x="tableScrollX" |
| | | :header-height="18" |
| | | :row-height="18" |
| | | /> |
| | | <Pagination |
| | | :count="count" |
| | | :page="pageParams.pageNum" |
| | | :page-size="pageParams.pageSize" |
| | | class="robotaxi-pagination mt-12px" |
| | | @change="changePage" |
| | | /> |
| | | </div> |
| | | |
| | | <n-card class="flex-1"> |
| | | <template #header> |
| | | <n-flex justify="flex-end" v-permission="70211"> |
| | | <NButton type="primary" secondary @click="handleAdd"> |
| | | 新增打卡地 |
| | | </NButton> |
| | | </n-flex> |
| | | </template> |
| | | <NSpace vertical :size="12"> |
| | | <n-data-table :columns="columns" :data="listData" :loading="loading" :bordered="false" :header-height="18" |
| | | :row-height="18" /> |
| | | <Pagination :count="count" :page="pageParams.pageNum" :page-size="pageParams.pageSize" @change="changePage" /> |
| | | </NSpace> |
| | | |
| | | <AddressFormModal v-model:show="showFormModal" ref="modalRef" :mode="formMode" @confirm="handleConfirmForm" /> |
| | | </n-card> |
| | | </NSpace> |
| | | </n-flex> |
| | | <AddressFormModal ref="modalRef" v-model:show="showFormModal" :mode="formMode" @confirm="handleConfirmForm" /> |
| | | </n-card> |
| | | </div> |
| | | </template> |
| | |
| | | :mask-closable="false" |
| | | preset="card" |
| | | :title="mode === 'edit' ? '编辑班次' : '新增班次'" |
| | | class="w-600px" |
| | | class="w-400px" |
| | | :segmented="{ |
| | | content: true, |
| | | action: true, |
| | |
| | | require-mark-placement="left" |
| | | show-require-mark |
| | | > |
| | | <div class="flex gap-8px flex-col"> |
| | | <n-form-item label="班次" path="shiftName"> |
| | | <n-input v-model:value="formModel.shiftName" placeholder="请输入" clearable /> |
| | | </n-form-item> |
| | |
| | | <n-time-picker v-model:formatted-value="formModel.restEndTime" format="HH:mm" value-format="HH:mm" placeholder="--:--" clearable style="width: 100%" /> |
| | | </n-form-item> |
| | | </template> |
| | | </div> |
| | | </n-form> |
| | | |
| | | <template #action> |
| | | <n-space justify="center"> |
| | | <n-space justify="end"> |
| | | <n-button @click="handleCancel">取消</n-button> |
| | | <n-button type="primary" @click="handleConfirm">保存</n-button> |
| | | <n-button type="primary" @click="handleConfirm">确定</n-button> |
| | | </n-space> |
| | | </template> |
| | | </n-modal> |
| | |
| | | <script setup lang="tsx"> |
| | | import { ref, computed, h } from 'vue' |
| | | import { computed, h, ref } from 'vue' |
| | | import { useBoolean } from '@/hooks' |
| | | import type { DataTableColumns } from 'naive-ui' |
| | | import { getShiftList, addShift, editShift, deleteShift } from '@/service/api/attendance' |
| | | import { addShift, deleteShift, editShift, getShiftList } from '@/service/api/attendance' |
| | | import { NButton, NSpace } from 'naive-ui' |
| | | import ShiftFormModal from './components/ShiftFormModal.vue' |
| | | import SearchPanel from '@/components/custom/SearchPanel.vue' |
| | | import dayjs from 'dayjs' |
| | | |
| | | const dialog = useDialog() |
| | | const { bool: loading, setTrue: startLoading, setFalse: endLoading } = useBoolean(false) |
| | | |
| | | const shiftName = ref<string | null>(null) |
| | | |
| | | const roleOptions = [ |
| | | { label: '网格员', value: '网格员' }, |
| | | { label: '运维管理', value: '运维管理' }, |
| | | { label: '角色1', value: '角色1' } |
| | | ] |
| | | const initialModel = { |
| | | shiftName: '', |
| | | } |
| | | const model = ref({ ...initialModel }) |
| | | const formRef = ref() |
| | | |
| | | const shiftList = ref<any[]>([]) |
| | | const modalRef = ref() |
| | |
| | | const showFormModal = ref(false) |
| | | const formMode = ref<'add' | 'edit'>('add') |
| | | |
| | | const handleSearch = () => { |
| | | console.log('搜索', shiftName.value) |
| | | function handleResetSearch() { |
| | | model.value = { ...initialModel } |
| | | handleSearch() |
| | | } |
| | | |
| | | const handleReset = () => { |
| | | shiftName.value = null |
| | | function handleSearch() { |
| | | pageParams.value.pageNum = 1 |
| | | getList() |
| | | } |
| | | |
| | | const handleAdd = () => { |
| | | function handleAdd() { |
| | | formMode.value = 'add' |
| | | showFormModal.value = true |
| | | } |
| | | |
| | | const handleEdit = (row: any) => { |
| | | function handleEdit(row: any) { |
| | | formMode.value = 'edit' |
| | | showFormModal.value = true |
| | | modalRef.value.openEdit(row) |
| | | } |
| | | |
| | | const handleConfirmForm = (data: any) => { |
| | | function handleConfirmForm(data: any) { |
| | | if (data.id) { |
| | | if (data.shiftColor !== '#D9F1EC') { |
| | | if (data.shiftName === '早班') { |
| | |
| | | }).finally(() => { |
| | | getList() |
| | | }) |
| | | } else { |
| | | } |
| | | else { |
| | | data.shiftColor = '#D9F1EC' |
| | | addShift(data).then((res) => { |
| | | if (res.code === 200) { |
| | |
| | | } |
| | | } |
| | | |
| | | const tableScrollX = computed(() => Math.min(1600, 70 + 160 + 320 + 180 + 140)) |
| | | |
| | | const columns = computed<DataTableColumns<any>>(() => [ |
| | | { title: '序号', key: 'serialNumber', width: 158, resizable: true, }, |
| | | { |
| | | title: '序号', |
| | | key: 'serialNumber', |
| | | width: 70, |
| | | align: 'left', |
| | | resizable: true, |
| | | }, |
| | | { |
| | | title: '状态', |
| | | key: 'shiftName', |
| | | width: 256, |
| | | width: 160, |
| | | align: 'left', |
| | | resizable: true, |
| | | render: (row) => { |
| | | return h('span', { |
| | |
| | | borderRadius: '4px', |
| | | fontSize: '12px', |
| | | backgroundColor: row.shiftColor, |
| | | color: 'rgba(23, 31, 38, 0.8)' |
| | | } |
| | | color: 'rgba(23, 31, 38, 0.8)', |
| | | }, |
| | | }, row.shiftName) |
| | | } |
| | | }, |
| | | }, |
| | | { |
| | | title: '工作时间', |
| | | key: 'workTime', |
| | | minWidth: 200, |
| | | width: 320, |
| | | align: 'left', |
| | | resizable: true, |
| | | render: (row) => ( |
| | | render: row => ( |
| | | <span> |
| | | {row.startTime}-{row.endTime} |
| | | {row.isRest && <span style={{ color: '#0068FF', marginLeft: '4px' }}>({row.restStartTime}-{row.restEndTime}休息)</span>} |
| | | <span>{`${row.startTime}-${row.endTime}`}</span> |
| | | {row.isRest && <span style={{ color: '#0068FF', marginLeft: '4px' }}>{`(${row.restStartTime}-${row.restEndTime}休息)`}</span>} |
| | | </span> |
| | | ) |
| | | ), |
| | | }, |
| | | { title: '添加时间', key: 'createTime', width: 290, resizable: true, }, |
| | | { |
| | | title: '添加时间', |
| | | key: 'createTime', |
| | | width: 180, |
| | | align: 'left', |
| | | resizable: true, |
| | | render: row => row.createTime ? dayjs(row.createTime).format('YYYY/MM/DD HH:mm') : '', |
| | | }, |
| | | { |
| | | title: '操作', |
| | | key: 'actions', |
| | | width: 134, |
| | | width: 140, |
| | | align: 'left', |
| | | fixed: 'right', |
| | | render: (row) => ( |
| | | <NSpace> |
| | | <NButton |
| | | text |
| | | v-permission="70242" |
| | | type='primary' |
| | | onClick={() => handleEdit(row)} |
| | | > |
| | | 编辑 |
| | | </NButton> |
| | | <NButton |
| | | text |
| | | v-permission="70243" |
| | | type='error' |
| | | disabled={(row.shiftName === '早班' || row.shiftName === '中班' || row.shiftName === '晚班' || row.shiftName === '全天') && row.shiftColor !== '#D9F1EC'} |
| | | onClick={() => { |
| | | dialog.create({ |
| | | render: row => ( |
| | | <NSpace> |
| | | <NButton |
| | | text |
| | | v-permission="70242" |
| | | type="primary" |
| | | onClick={() => handleEdit(row)} |
| | | > |
| | | 编辑 |
| | | </NButton> |
| | | <NButton |
| | | text |
| | | v-permission="70243" |
| | | type="primary" |
| | | disabled={(row.shiftName === '早班' || row.shiftName === '中班' || row.shiftName === '晚班' || row.shiftName === '全天') && row.shiftColor !== '#D9F1EC'} |
| | | onClick={() => { |
| | | dialog.create({ |
| | | title: '是否确认删除数据?', |
| | | content: `确认删除该班次?删除后不可恢复。`, |
| | | content: '确认删除该班次?删除后不可恢复。', |
| | | positiveText: '确认', |
| | | negativeText: '取消', |
| | | draggable: true, |
| | | onPositiveClick: () => { |
| | | deleteShift(row.id!).then((res) => { |
| | | if (res.code === 200) { |
| | | window.$message.success('删除成功') |
| | | } |
| | | }).finally(() => { |
| | | getList() |
| | | }) |
| | | } |
| | | }) |
| | | }} |
| | | > |
| | | 删除 |
| | | </NButton> |
| | | </NSpace> |
| | | ) |
| | | } |
| | | deleteShift(row.id!).then((res) => { |
| | | if (res.code === 200) { |
| | | window.$message.success('删除成功') |
| | | } |
| | | }).finally(() => { |
| | | getList() |
| | | }) |
| | | }, |
| | | }) |
| | | }} |
| | | > |
| | | 删除 |
| | | </NButton> |
| | | </NSpace> |
| | | ), |
| | | }, |
| | | ]) |
| | | |
| | | const filterItems = computed(() => [ |
| | | { |
| | | label: '班次名称', |
| | | path: 'shiftName', |
| | | render: () => <n-input v-model:value={model.value.shiftName} clearable />, |
| | | }, |
| | | ]) |
| | | |
| | | async function getList() { |
| | | startLoading() |
| | | const params = { |
| | | ...pageParams.value |
| | | try { |
| | | const params = { |
| | | ...pageParams.value, |
| | | } |
| | | const res = await getShiftList(params) |
| | | if (res.code === 200) { |
| | | shiftList.value = res.data.records || [] |
| | | shiftList.value.forEach((item: any, index: number) => { |
| | | item.serialNumber = (pageParams.value.pageNum - 1) * pageParams.value.pageSize + index + 1 |
| | | }) |
| | | total.value = res.data.total || res.data.records?.length |
| | | } |
| | | else { |
| | | shiftList.value = [] |
| | | total.value = 0 |
| | | } |
| | | } |
| | | getShiftList(params).then((res) => { |
| | | shiftList.value = res.data.records || [] |
| | | shiftList.value.forEach((item: any, index: number) => { |
| | | item.serialNumber = (pageParams.value.pageNum - 1) * pageParams.value.pageSize + index + 1 |
| | | }) |
| | | total.value = res.data.total || res.data.records?.length |
| | | }).finally(() => { |
| | | finally { |
| | | endLoading() |
| | | }) |
| | | } |
| | | } |
| | | |
| | | onMounted(() => { |
| | |
| | | |
| | | <template> |
| | | <div class="roboTaxi"> |
| | | <NSpace vertical class="flex-1"> |
| | | <!-- <n-card style="border-radius: 6px" content-style="padding: 12px"> |
| | | <div class="flex items-center justify-between"> |
| | | <div class="flex items-center gap-2"> |
| | | <div class="text-sm">角色</div> |
| | | <n-select v-model:value="shiftName" :options="roleOptions" placeholder="请选择" clearable |
| | | style="width: 269px" /> |
| | | </div> |
| | | <div class="flex gap-2"> |
| | | <n-button style="width: 84px; height: 32px" @click="handleReset">重置</n-button> |
| | | <n-button type="primary" style="width: 84px; height: 32px" @click="handleSearch">查询</n-button> |
| | | </div> |
| | | </div> |
| | | </n-card> --> |
| | | |
| | | <n-card class="flex-1"> |
| | | <template #header> |
| | | <n-flex justify="flex-end" v-permission="70241"> |
| | | <NButton type="primary" secondary @click="handleAdd"> |
| | | 新增班次 |
| | | <!-- <div class="roboTaxi-form-card mb-12px"> |
| | | <n-form ref="formRef" :model="model" label-placement="left" inline :show-feedback="false"> |
| | | <SearchPanel> |
| | | <n-gi v-for="item in filterItems" :key="item.path"> |
| | | <n-form-item :label="item.label" :path="item.path"> |
| | | <component :is="item.render" /> |
| | | </n-form-item> |
| | | </n-gi> |
| | | <template #action> |
| | | <NButton strong @click="handleResetSearch"> |
| | | {{ $t('common.reset') }} |
| | | </NButton> |
| | | </n-flex> |
| | | </template> |
| | | <NSpace vertical :size="12"> |
| | | <n-data-table :columns="columns" :data="shiftList" :loading="loading" :bordered="false" :header-height="18" |
| | | :row-height="18" /> |
| | | <Pagination :count="total" :page="pageParams.pageNum" :page-size="pageParams.pageSize" @change="changePage" /> |
| | | </NSpace> |
| | | </n-card> |
| | | </NSpace> |
| | | <NButton type="primary" @click="handleSearch"> |
| | | {{ $t('common.search') }} |
| | | </NButton> |
| | | </template> |
| | | </SearchPanel> |
| | | </n-form> |
| | | </div> --> |
| | | |
| | | <ShiftFormModal v-model:show="showFormModal" ref="modalRef" :mode="formMode" @confirm="handleConfirmForm" /> |
| | | <n-card class="flex-1 robotaxi-table-card"> |
| | | <template #header> |
| | | <n-flex v-permission="70241" justify="flex-end"> |
| | | <NButton type="primary" secondary @click="handleAdd"> |
| | | 新增 |
| | | </NButton> |
| | | </n-flex> |
| | | </template> |
| | | <div class="flex-1 flex flex-col h-full"> |
| | | <n-data-table |
| | | class="flex-1" |
| | | :columns="columns" |
| | | :data="shiftList" |
| | | :loading="loading" |
| | | :bordered="false" |
| | | flex-height |
| | | :scroll-x="tableScrollX" |
| | | :header-height="18" |
| | | :row-height="18" |
| | | /> |
| | | <Pagination |
| | | :count="total" |
| | | :page="pageParams.pageNum" |
| | | :page-size="pageParams.pageSize" |
| | | class="robotaxi-pagination mt-12px" |
| | | @change="changePage" |
| | | /> |
| | | </div> |
| | | |
| | | <ShiftFormModal ref="modalRef" v-model:show="showFormModal" :mode="formMode" @confirm="handleConfirmForm" /> |
| | | </n-card> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | </div> |
| | | <div class="flex gap-2"> |
| | | <n-button |
| | | style="width: 100px; height: 32px; background-color: #E6F4FF; color: #0068FF; border: 1px solid #7ABDFF" |
| | | style="width: 100px; height: 32px;" |
| | | @click="handleExport"> |
| | | 导出打卡明细 |
| | | </n-button> |
| | |
| | | <script setup lang="tsx"> |
| | | import { ref, computed } from 'vue' |
| | | import { computed, ref } from 'vue' |
| | | import { useBoolean } from '@/hooks' |
| | | import { useRouter } from 'vue-router' |
| | | import type { DataTableColumns } from 'naive-ui' |
| | | import { getClockInStatisticsList, exportClockInStatistics } from '@/service/api/attendance' |
| | | import { exportClockInStatistics, getClockInStatisticsList } from '@/service/api/attendance' |
| | | import { fetchRoleList } from '@/service/api/fleet' |
| | | import { NButton } from 'naive-ui' |
| | | import { downloadFile } from '@/utils' |
| | |
| | | const searchModel = ref({ |
| | | name: null as string | null, |
| | | roleId: null as string | null, |
| | | statisticDate: null as [number, number] | null |
| | | statisticDate: null as [number, number] | null, |
| | | }) |
| | | |
| | | const roleOptions = ref<any[]>([]) |
| | | const isRoleSelectOpen = ref(false) |
| | | |
| | | const statisticsList = ref<any[]>([]) |
| | | |
| | |
| | | pageSize: 10, |
| | | }) |
| | | |
| | | const handleReset = () => { |
| | | function handleReset() { |
| | | searchModel.value = { |
| | | name: null, |
| | | roleId: null, |
| | | statisticDate: null |
| | | statisticDate: null, |
| | | } |
| | | handleSearch() |
| | | } |
| | |
| | | getList() |
| | | } |
| | | |
| | | const handleExport = async () => { |
| | | async function handleExport() { |
| | | const params = { |
| | | ...pageParams.value, |
| | | name: searchModel.value.name, |
| | |
| | | } |
| | | } |
| | | |
| | | const handleViewDetail = (row: any) => { |
| | | function handleViewDetail(row: any) { |
| | | router.push(`/operations/attendance/statistics/detail/${row.appUserId}?shouldClockInDays=${row.shouldClockInDays}&clockInDays=${row.clockInDays}¬ClockInDays=${row.notClockInDays}&lateEarly=${row.lateEarly}`) |
| | | } |
| | | |
| | |
| | | { |
| | | label: '人员', |
| | | path: 'name', |
| | | render: () => <n-input v-model:value={searchModel.value.name} placeholder="请输入" clearable /> |
| | | render: () => <n-input v-model:value={searchModel.value.name} placeholder="请输入" clearable />, |
| | | }, |
| | | { |
| | | label: '角色', |
| | | path: 'searchModel.value.roleId', |
| | | render: () => <n-select v-model:value={searchModel.value.roleId} options={roleOptions.value} placeholder="请选择" clearable /> |
| | | path: 'roleId', |
| | | render: () => ( |
| | | <n-select |
| | | v-model:value={searchModel.value.roleId} |
| | | options={roleOptions.value} |
| | | placeholder="请选择" |
| | | clearable |
| | | onUpdate:show={(value: boolean) => { isRoleSelectOpen.value = value }} |
| | | > |
| | | {{ |
| | | arrow: () => ( |
| | | <img |
| | | src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/e120f81c-9f15-4e1a-9938-22252898edf9.png" |
| | | class={`arrow-icon ${isRoleSelectOpen.value ? 'arrow-icon--open' : ''}`} |
| | | style={{ width: '16px', height: '16px' }} |
| | | alt="arrow" |
| | | /> |
| | | ), |
| | | }} |
| | | </n-select> |
| | | ), |
| | | }, |
| | | { |
| | | label: '打卡日期', |
| | | path: 'statisticDate', |
| | | render: () => <n-date-picker v-model:value={searchModel.value.statisticDate} type="daterange" clearable start-placeholder="开始时间" end-placeholder="结束时间" /> |
| | | } |
| | | render: () => ( |
| | | <n-date-picker |
| | | v-model:value={searchModel.value.statisticDate} |
| | | type="daterange" |
| | | clearable |
| | | actions={[]} |
| | | close-on-select={true} |
| | | start-placeholder="开始时间" |
| | | end-placeholder="结束时间" |
| | | separator="-" |
| | | /> |
| | | ), |
| | | }, |
| | | ]) |
| | | |
| | | const columns = computed<DataTableColumns<any>>(() => [ |
| | | { title: '序号', key: 'serialNumber', width: 91, resizable: true, fixed: 'left' }, |
| | | { title: '人员', key: 'name', width: 143, resizable: true, fixed: 'left' }, |
| | | { title: '角色', key: 'roleName', width: 134, resizable: true, }, |
| | | { title: '所属队伍', key: 'teamName', width: 142, resizable: true, }, |
| | | { title: '应打卡天数', key: 'shouldClockInDays', minWidth: 100, resizable: true, }, |
| | | { title: '正常', key: 'clockInDays', minWidth: 100, resizable: true, }, |
| | | { title: '缺卡', key: 'notClockInDays', minWidth: 100, resizable: true, }, |
| | | { title: '迟到', key: 'lateEarly', minWidth: 100, resizable: true, }, |
| | | { title: '序号', key: 'serialNumber', width: 80, align: 'left', resizable: true, fixed: 'left' }, |
| | | { title: '人员', key: 'name', width: 140, align: 'left', resizable: true, fixed: 'left' }, |
| | | { title: '角色', key: 'roleName', width: 140, align: 'left', resizable: true }, |
| | | { title: '所属队伍', key: 'teamName', width: 160, align: 'left', resizable: true }, |
| | | { title: '应打卡天数', key: 'shouldClockInDays', width: 120, align: 'left', resizable: true }, |
| | | { title: '正常', key: 'clockInDays', width: 120, align: 'left', resizable: true }, |
| | | { title: '缺卡', key: 'notClockInDays', width: 120, align: 'left', resizable: true }, |
| | | { title: '迟到', key: 'lateEarly', width: 120, align: 'left', resizable: true }, |
| | | { |
| | | title: '操作', |
| | | key: 'actions', |
| | | width: 134, |
| | | width: 110, |
| | | align: 'left', |
| | | fixed: 'right', |
| | | render: (row) => ( |
| | | render: row => ( |
| | | <NButton v-permission="70222" text type="primary" onClick={() => handleViewDetail(row)}> |
| | | 打卡明细 |
| | | </NButton> |
| | | ) |
| | | } |
| | | ), |
| | | }, |
| | | ]) |
| | | |
| | | const tableScrollX = computed(() => { |
| | | const totalWidth = columns.value.reduce((sum, col) => { |
| | | const width = Number((col as any).width ?? (col as any).minWidth ?? 0) |
| | | return sum + (Number.isFinite(width) ? width : 0) |
| | | }, 0) |
| | | return Math.min(totalWidth, 1600) |
| | | }) |
| | | |
| | | async function getRoleList() { |
| | | const res = await fetchRoleList() |
| | |
| | | |
| | | <template> |
| | | <div class="roboTaxi"> |
| | | <n-card style="border-radius: 6px" content-style="padding: 12px"> |
| | | <div class="roboTaxi-form-card mb-12px"> |
| | | <n-form :model="searchModel" label-placement="left" inline :show-feedback="false"> |
| | | <SearchPanel> |
| | | <n-gi v-for="item in filterItems" :key="item.path"> |
| | |
| | | </n-form-item> |
| | | </n-gi> |
| | | <template #action> |
| | | <n-button style="width: 84px; height: 32px" @click="handleReset">重置</n-button> |
| | | <n-button type="primary" style="width: 84px; height: 32px" @click="handleSearch">查询</n-button> |
| | | <NButton strong @click="handleReset"> |
| | | 重置 |
| | | </NButton> |
| | | <NButton type="primary" @click="handleSearch"> |
| | | 查询 |
| | | </NButton> |
| | | </template> |
| | | </SearchPanel> |
| | | </n-form> |
| | | </n-card> |
| | | </div> |
| | | |
| | | <n-card style="border-radius: 6px; margin-top: 12px" content-style="padding: 12px"> |
| | | <template #header> |
| | | <n-flex justify="flex-end" v-permission="70221"> |
| | | <NButton type="primary" secondary @click="handleExport"> |
| | | 导出 |
| | | </NButton> |
| | | </n-flex> |
| | | </template> |
| | | <NSpace vertical :size="12"> |
| | | <n-data-table :columns="columns" :data="statisticsList" :loading="loading" :bordered="false" :header-height="18" |
| | | :row-height="18" /> |
| | | <Pagination :count="total" :page="pageParams.pageNum" :page-size="pageParams.pageSize" @change="changePage" /> |
| | | </NSpace> |
| | | <n-card class="flex-1 robotaxi-table-card"> |
| | | <template #header> |
| | | <n-flex v-permission="70221" justify="flex-end"> |
| | | <NButton type="primary" secondary @click="handleExport"> |
| | | 导出 |
| | | </NButton> |
| | | </n-flex> |
| | | </template> |
| | | <div class="flex-1 flex flex-col h-full"> |
| | | <n-data-table |
| | | class="flex-1" |
| | | :columns="columns" |
| | | :data="statisticsList" |
| | | :loading="loading" |
| | | :bordered="false" |
| | | flex-height |
| | | :scroll-x="tableScrollX" |
| | | :header-height="18" |
| | | :row-height="18" |
| | | /> |
| | | <Pagination |
| | | :count="total" |
| | | :page="pageParams.pageNum" |
| | | :page-size="pageParams.pageSize" |
| | | class="robotaxi-pagination mt-12px" |
| | | @change="changePage" |
| | | /> |
| | | </div> |
| | | </n-card> |
| | | </div> |
| | | </template> |
| | |
| | | :options="roleOptions" |
| | | clearable |
| | | multiple |
| | | placeholder="请选择" |
| | | placeholder="请选择(多选)" |
| | | @update:show="handleRoleSelectShow" |
| | | > |
| | | <template #arrow> |