| | |
| | | <script setup lang="tsx"> |
| | | import type { DataTableColumns } from 'naive-ui' |
| | | import { useBoolean } from '@/hooks' |
| | | import { NButton, NTag } from 'naive-ui' |
| | | import { NButton, NSwitch, 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' |
| | |
| | | const formMode = ref<'add' | 'edit'>('add') |
| | | |
| | | const tableScrollX = computed(() => Math.min(1600, 70 + 180 + 360 + 120 + 200)) |
| | | |
| | | async function handleSwitchChange(row: any) { |
| | | dialog.warning({ |
| | | title: `是否确认${row.status === 1 ? '禁用' : '启用'}打卡地?`, |
| | | content: '', |
| | | positiveText: '确认', |
| | | negativeText: '取消', |
| | | maskClosable: false, |
| | | autoFocus: false, |
| | | onPositiveClick: async () => { |
| | | updateCheckLocationStatus({ id: row.id, status: row.status === 1 ? 0 : 1 }).then((res) => { |
| | | if (res.code === 200) { |
| | | window.$message.success('操作成功') |
| | | getList() |
| | | } |
| | | }) |
| | | }, |
| | | onNegativeClick: () => { |
| | | getList() |
| | | }, |
| | | }) |
| | | } |
| | | |
| | | const columns = computed<DataTableColumns<any>>(() => [ |
| | | { |
| | |
| | | > |
| | | 编辑 |
| | | </span> |
| | | <span |
| | | {/* <span |
| | | class="cursor-pointer" |
| | | v-permission="70214" |
| | | style={{ color: isEnabled ? '#0068FF' : '#0068FF' }} |
| | |
| | | }} |
| | | > |
| | | {isEnabled ? '禁用' : '启用'} |
| | | </span> |
| | | </span> */} |
| | | <span |
| | | class="cursor-pointer" |
| | | v-permission="70213" |
| | |
| | | content: '确认删除该打卡地址?删除后不可恢复。', |
| | | positiveText: '确认', |
| | | negativeText: '取消', |
| | | maskClosable: false, |
| | | maskClosable: false, |
| | | autoFocus: false, |
| | | onPositiveClick: () => { |
| | | deleteCheckLocation(row.id!).then((res) => { |
| | |
| | | > |
| | | 删除 |
| | | </span> |
| | | <NSwitch |
| | | v-permission="70214" |
| | | value={row.status} |
| | | checked-value={1} |
| | | unchecked-value={0} |
| | | onUpdateValue={() => handleSwitchChange(row)} |
| | | > |
| | | {{ |
| | | checked: () => '启用', |
| | | unchecked: () => '禁用', |
| | | }} |
| | | </NSwitch> |
| | | </div> |
| | | ) |
| | | }, |
| | |
| | | </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"> |
| | | <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" |
| | | > |
| | | <template #empty> |
| | | <div class="flex-center"> |
| | | <img src="/src/assets/element-img/no-data.png" style="width:123px;height:125px" @error="(e: any) => { e.target.src = 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/ab6c7ff4-403a-4a2c-9ebf-310c023dc20f.png' }"> |
| | | </div> |
| | | </template> |
| | | </n-data-table> |
| | | <Pagination :count="count" :page="pageParams.pageNum" :page-size="pageParams.pageSize" |
| | | class="robotaxi-pagination mt-12px" @change="changePage" /> |
| | | <Pagination |
| | | :count="count" :page="pageParams.pageNum" :page-size="pageParams.pageSize" |
| | | class="robotaxi-pagination mt-12px" @change="changePage" |
| | | /> |
| | | </div> |
| | | |
| | | <AddressFormModal ref="modalRef" v-model:show="showFormModal" :mode="formMode" @confirm="handleConfirmForm" /> |
| | |
| | | <script setup lang="ts"> |
| | | import { ref, computed } from 'vue' |
| | | import { computed, ref } from 'vue' |
| | | import { useRouter } from 'vue-router' |
| | | import type { FormInst, FormRules } from 'naive-ui' |
| | | |
| | | const router = useRouter() |
| | | |
| | | interface FormModel { |
| | | month: number | null |
| | | } |
| | | |
| | | const props = defineProps<{ |
| | | show: boolean |
| | |
| | | |
| | | const emit = defineEmits<{ |
| | | 'update:show': [value: boolean] |
| | | confirm: [data: FormModel] |
| | | 'confirm': [data: FormModel] |
| | | }>() |
| | | |
| | | const router = useRouter() |
| | | |
| | | interface FormModel { |
| | | month: number | null |
| | | } |
| | | |
| | | const show = computed({ |
| | | get: () => props.show, |
| | | set: (value) => emit('update:show', value) |
| | | set: value => emit('update:show', value), |
| | | }) |
| | | |
| | | const formRef = ref<FormInst | null>(null) |
| | | const formModel = ref<FormModel>({ |
| | | month: null |
| | | month: null, |
| | | }) |
| | | |
| | | const rules: FormRules = { |
| | | month: [ |
| | | { required: true, type: 'number', message: '请选择排班月份', trigger: 'change' } |
| | | ] |
| | | { required: true, type: 'number', message: '请选择排班月份', trigger: 'change' }, |
| | | ], |
| | | } |
| | | |
| | | const handleCancel = () => { |
| | | function handleCancel() { |
| | | show.value = false |
| | | resetForm() |
| | | } |
| | | |
| | | const handleConfirm = async () => { |
| | | async function handleConfirm() { |
| | | try { |
| | | await formRef.value?.validate() |
| | | if (formModel.value.month) { |
| | |
| | | path: '/operations/attendance/plan/copy', |
| | | query: { |
| | | month: String(formModel.value.month), |
| | | } |
| | | }, |
| | | }) |
| | | resetForm() |
| | | } |
| | | } catch (error) { |
| | | } |
| | | catch (error) { |
| | | console.log('验证失败', error) |
| | | } |
| | | } |
| | | |
| | | const resetForm = () => { |
| | | function resetForm() { |
| | | formModel.value = { |
| | | month: null |
| | | month: null, |
| | | } |
| | | } |
| | | </script> |
| | |
| | | :segmented="{ |
| | | content: true, |
| | | action: true, |
| | | }"> |
| | | }" |
| | | > |
| | | <n-form |
| | | ref="formRef" |
| | | :model="formModel" |
| | |
| | | placeholder="请选择月份" |
| | | style="width: 100%" |
| | | format="yyyy/MM" |
| | | :actions="[]" |
| | | /> |
| | | </n-form-item> |
| | | </n-form> |
| | | |
| | | <template #action> |
| | | <n-space justify="end"> |
| | | <n-button @click="handleCancel">取消</n-button> |
| | | <n-button type="primary" @click="handleConfirm">确定</n-button> |
| | | <n-button @click="handleCancel"> |
| | | 取消 |
| | | </n-button> |
| | | <n-button type="primary" @click="handleConfirm"> |
| | | 确定 |
| | | </n-button> |
| | | </n-space> |
| | | </template> |
| | | </n-modal> |
| | |
| | | <script setup lang="ts"> |
| | | import { ref, computed, watch } from 'vue' |
| | | import { computed, ref, watch } from 'vue' |
| | | import { useRouter } from 'vue-router' |
| | | import type { FormInst, FormRules } from 'naive-ui' |
| | | |
| | | const router = useRouter() |
| | | |
| | | interface FormModel { |
| | | month: number | null |
| | | } |
| | | |
| | | const props = defineProps<{ |
| | | show: boolean |
| | |
| | | |
| | | const emit = defineEmits<{ |
| | | 'update:show': [value: boolean] |
| | | confirm: [month: number] |
| | | 'confirm': [month: number] |
| | | }>() |
| | | |
| | | const router = useRouter() |
| | | |
| | | interface FormModel { |
| | | month: number | null |
| | | } |
| | | |
| | | const show = computed({ |
| | | get: () => props.show, |
| | | set: (value) => emit('update:show', value) |
| | | set: value => emit('update:show', value), |
| | | }) |
| | | |
| | | const formRef = ref<FormInst | null>(null) |
| | | const formModel = ref<FormModel>({ |
| | | month: null |
| | | month: null, |
| | | }) |
| | | |
| | | const rules: FormRules = { |
| | |
| | | required: true, |
| | | type: 'number', |
| | | message: '请选择排班月份', |
| | | trigger: 'change' |
| | | } |
| | | ] |
| | | trigger: 'change', |
| | | }, |
| | | ], |
| | | } |
| | | |
| | | watch(() => props.show, (newVal) => { |
| | |
| | | } |
| | | }) |
| | | |
| | | const handleCancel = () => { |
| | | function handleCancel() { |
| | | show.value = false |
| | | resetForm() |
| | | } |
| | | |
| | | const handleConfirm = async () => { |
| | | async function handleConfirm() { |
| | | try { |
| | | await formRef.value?.validate() |
| | | if (formModel.value.month) { |
| | |
| | | // 跳转到编辑页面,携带月份参数 |
| | | router.push({ |
| | | path: '/operations/attendance/plan/edit', |
| | | query: { month: String(formModel.value.month) } |
| | | query: { month: String(formModel.value.month) }, |
| | | }) |
| | | resetForm() |
| | | } |
| | | } catch (error) { |
| | | } |
| | | catch (error) { |
| | | console.log('验证失败', error) |
| | | } |
| | | } |
| | | |
| | | const resetForm = () => { |
| | | function resetForm() { |
| | | formModel.value = { |
| | | month: null |
| | | month: null, |
| | | } |
| | | } |
| | | </script> |
| | |
| | | :segmented="{ |
| | | content: true, |
| | | action: true, |
| | | }"> |
| | | }" |
| | | > |
| | | <n-form |
| | | ref="formRef" |
| | | :model="formModel" |
| | |
| | | placeholder="请选择月份" |
| | | style="width: 100%" |
| | | format="yyyy/MM" |
| | | :actions="[]" |
| | | /> |
| | | </n-form-item> |
| | | </n-form> |
| | | |
| | | <template #action> |
| | | <n-space justify="end"> |
| | | <n-button @click="handleCancel">取消</n-button> |
| | | <n-button type="primary" @click="handleConfirm">确定</n-button> |
| | | <n-button @click="handleCancel"> |
| | | 取消 |
| | | </n-button> |
| | | <n-button type="primary" @click="handleConfirm"> |
| | | 确定 |
| | | </n-button> |
| | | </n-space> |
| | | </template> |
| | | </n-modal> |
| | |
| | | resizable: true, |
| | | }, |
| | | { |
| | | title: '状态', |
| | | title: '班次', |
| | | key: 'shiftName', |
| | | width: 160, |
| | | align: 'left', |
| | |
| | | positiveText: '确认', |
| | | negativeText: '取消', |
| | | maskClosable: false, |
| | | autoFocus: false, |
| | | autoFocus: false, |
| | | onPositiveClick: () => { |
| | | deleteShift(row.id!).then((res) => { |
| | | if (res.code === 200) { |
| | |
| | | :segmented="{ |
| | | content: true, |
| | | action: true, |
| | | }"> |
| | | }" |
| | | > |
| | | <n-form |
| | | ref="formRef" |
| | | :model="formModel" |
| | |
| | | <div class="form-two-col"> |
| | | <div class="form-col"> |
| | | <n-form-item label="用户名称" path="nickName"> |
| | | <n-input v-model:value="formModel.nickName" placeholder="请输入" clearable maxlength="20"/> |
| | | <n-input v-model:value="formModel.nickName" placeholder="请输入" clearable maxlength="20" /> |
| | | </n-form-item> |
| | | |
| | | <n-form-item v-if="mode === 'add'" label="初始密码" path="password"> |
| | | <n-input v-model:value="formModel.password" type="password" placeholder="请输入" clearable show-password-on="click" /> |
| | | <n-input v-model:value="formModel.password" placeholder="请输入" clearable show-password-on="click" /> |
| | | </n-form-item> |
| | | |
| | | <n-form-item label="头像" path="avatar"> |
| | |
| | | ref="uploadRef" |
| | | v-model:file-list="fileList" |
| | | :max="1" |
| | | accept="image/*" |
| | | list-type="image-card" |
| | | class="avatar-uploader-hidden" |
| | | :custom-request="handleCustomUpload" |
| | |
| | | |
| | | <n-form-item label="性别" path="sex" label-placement="left"> |
| | | <n-radio-group v-model:value="formModel.sex"> |
| | | <n-radio :value="1">男</n-radio> |
| | | <n-radio :value="2">女</n-radio> |
| | | <n-radio :value="1"> |
| | | 男 |
| | | </n-radio> |
| | | <n-radio :value="2"> |
| | | 女 |
| | | </n-radio> |
| | | </n-radio-group> |
| | | </n-form-item> |
| | | </div> |
| | | |
| | | <div class="form-col"> |
| | | <n-form-item label="编号" path="uniqueNumber"> |
| | | <n-input v-model:value="formModel.uniqueNumber" placeholder="请输入" clearable maxlength="20"/> |
| | | <n-input v-model:value="formModel.uniqueNumber" placeholder="请输入" clearable maxlength="20" /> |
| | | </n-form-item> |
| | | |
| | | <n-form-item label="联系电话" path="phoneNumber"> |
| | |
| | | |
| | | <template #action> |
| | | <n-space justify="end"> |
| | | <n-button @click="handleCancel">取消</n-button> |
| | | <n-button type="primary" @click="handleConfirm">保存</n-button> |
| | | <n-button @click="handleCancel"> |
| | | 取消 |
| | | </n-button> |
| | | <n-button type="primary" @click="handleConfirm"> |
| | | 保存 |
| | | </n-button> |
| | | </n-space> |
| | | </template> |
| | | </n-modal> |
| | |
| | | height: 13px; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | </style> |
| | |
| | | const res = await getOperationsUserDetail(route.params.id) |
| | | if (res.code === 200) { |
| | | personnelInfo.value = normalizePersonnel(res.data) |
| | | console.log(personnelInfo.value) |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | <template> |
| | | <div v-if="personnelInfo?.nickName" class="h-full flex flex-col overflow-y-auto"> |
| | | <n-card :bordered="false" class="rounded-6px h-full" content-style="padding: 24px"> |
| | | <n-card :bordered="false" class="rounded-6px h-full" content-style="padding: 20px 24px"> |
| | | <div class="flex items-center justify-between mb-24px"> |
| | | <div class="head"> |
| | | 员工详情 |
| | | </div> |
| | | <n-button type="primary" ghost class="w-84px h-32px" @click="handleEdit"> |
| | | <n-button type="primary" secondary style="border: 1px solid #7ABDFF;" class="w-84px h-32px" @click="handleEdit"> |
| | | 编辑 |
| | | </n-button> |
| | | </div> |
| | |
| | | round |
| | | :size="40" |
| | | :src="personnelInfo.avatarUrl" |
| | | fallback-src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/ab6c7ff4-403a-4a2c-9ebf-310c023dc20f.png" |
| | | /> |
| | | <img v-else-if="!personnelInfo.avatarUrl && personnelInfo.sex === 1" src="/src/assets/element-img/avatar.png" style="width:40px;height:40px"> |
| | | <img v-else src="/src/assets/element-img/acatar-woman.png" style="width:40px;height:40px"> |
| | | <img v-else-if="!personnelInfo.avatarUrl && personnelInfo.sex === 1" src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/7993ddfc-0743-4b4e-8ecc-776aaf1307cc.png" style="width:40px;height:40px"> |
| | | <img v-else src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/b8e18e26-9727-4a00-8fdf-0a3cd79b0a2c.png" style="width:40px;height:40px"> |
| | | <img v-if="personnelInfo.sex === 1" src="/src/assets/element-img/man.png" class="sex-icon"> |
| | | <img v-else src="/src/assets/element-img/woman.png" class="sex-icon"> |
| | | </div> |
| | |
| | | fallback-src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/ab6c7ff4-403a-4a2c-9ebf-310c023dc20f.png" |
| | | /> |
| | | <n-image |
| | | v-if="personnelInfo.idCardBackUrl" |
| | | width="213" |
| | | height="120" |
| | | :src="personnelInfo.idCardBackUrl" |
| | |
| | | 驾驶证: |
| | | </div> |
| | | <n-image |
| | | v-if="personnelInfo.driverLicenseUrl" |
| | | width="213" |
| | | height="120" |
| | | :src="personnelInfo.driverLicenseUrl" |
| | |
| | | 无犯罪记录证明: |
| | | </div> |
| | | <n-image |
| | | v-if="personnelInfo.certificateOfNoCriminalRecordUrl" |
| | | width="213" |
| | | height="120" |
| | | :src="personnelInfo.noCriminalRecordUrl" |
| | | :src="personnelInfo.certificateOfNoCriminalRecordUrl" |
| | | object-fit="cover" |
| | | class="rounded-4px border border-[#E5E5E5]" |
| | | fallback-src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/ab6c7ff4-403a-4a2c-9ebf-310c023dc20f.png" |
| | |
| | | ) |
| | | : row.sex === 1 |
| | | ? ( |
| | | <img src="/src/assets/element-img/avatar.png" class="w-24px h-24px rounded-full" alt="默认男头像" /> |
| | | <img src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/7993ddfc-0743-4b4e-8ecc-776aaf1307cc.png" class="w-24px h-24px rounded-full" alt="默认男头像" /> |
| | | ) |
| | | : ( |
| | | <img src="/src/assets/element-img/acatar-woman.png" class="w-24px h-24px rounded-full" alt="默认女头像" /> |
| | | <img src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/b8e18e26-9727-4a00-8fdf-0a3cd79b0a2c.png" class="w-24px h-24px rounded-full" alt="默认女头像" /> |
| | | )} |
| | | <NTooltip trigger="hover" placement="top" max-width="500px"> |
| | | {{ |